Skip to content
41 changes: 41 additions & 0 deletions vms/evm/sync/message/block_request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package message

import (
"context"
"fmt"

"github.com/ava-labs/libevm/common"

"github.com/ava-labs/avalanchego/ids"
)

var _ Request = (*BlockRequest)(nil)

// BlockRequest is a request to retrieve Parents number of blocks starting from Hash from newest-oldest manner
type BlockRequest struct {
Hash common.Hash `serialize:"true"`
Height uint64 `serialize:"true"`
Parents uint16 `serialize:"true"`
}

func (b BlockRequest) String() string {
return fmt.Sprintf(
"BlockRequest(Hash=%s, Height=%d, Parents=%d)",
b.Hash, b.Height, b.Parents,
)
}

func (b BlockRequest) Handle(ctx context.Context, nodeID ids.NodeID, requestID uint32, handler RequestHandler) ([]byte, error) {
return handler.HandleBlockRequest(ctx, nodeID, requestID, b)
}

// BlockResponse is a response to a BlockRequest
// Blocks is slice of RLP encoded blocks starting with the block
// requested in BlockRequest.Hash. The next block is the parent, etc.
// handler: handlers.BlockRequestHandler
Copy link
Contributor

@DracoLi DracoLi Oct 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is referring to the handlers package in coreth right? are we looking to move this over as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, sorry I missed this reference in the comment, but the handlers will be migrated as well in a follow up PR.

type BlockResponse struct {
Blocks [][]byte `serialize:"true"`
}
65 changes: 65 additions & 0 deletions vms/evm/sync/message/block_request_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package message

import (
"encoding/base64"
"math/rand"
"testing"

"github.com/ava-labs/libevm/common"
"github.com/stretchr/testify/require"
)

// TestMarshalBlockRequest requires that the structure or serialization logic hasn't changed, primarily to
// ensure compatibility with the network.
func TestMarshalBlockRequest(t *testing.T) {
blockRequest := BlockRequest{
Hash: common.BytesToHash([]byte("some hash is here yo")),
Height: 1337,
Parents: 64,
}

base64BlockRequest := "AAAAAAAAAAAAAAAAAABzb21lIGhhc2ggaXMgaGVyZSB5bwAAAAAAAAU5AEA="

blockRequestBytes, err := Codec.Marshal(Version, blockRequest)
require.NoError(t, err)
require.Equal(t, base64BlockRequest, base64.StdEncoding.EncodeToString(blockRequestBytes))

var b BlockRequest
_, err = Codec.Unmarshal(blockRequestBytes, &b)
require.NoError(t, err)
require.Equal(t, blockRequest.Hash, b.Hash)
require.Equal(t, blockRequest.Height, b.Height)
require.Equal(t, blockRequest.Parents, b.Parents)
}

// TestMarshalBlockResponse requires that the structure or serialization logic hasn't changed, primarily to
// ensure compatibility with the network.
func TestMarshalBlockResponse(t *testing.T) {
// create some random bytes
// set seed to ensure deterministic random behaviour
r := rand.New(rand.NewSource(1)) //nolint:gosec // deterministic bytes for golden assertion
blocksBytes := make([][]byte, 32)
for i := range blocksBytes {
blocksBytes[i] = make([]byte, r.Intn(32)+32)
_, err := r.Read(blocksBytes[i])
require.NoError(t, err)
}

blockResponse := BlockResponse{
Blocks: blocksBytes,
}

base64BlockResponse := "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"

blockResponseBytes, err := Codec.Marshal(Version, blockResponse)
require.NoError(t, err)
require.Equal(t, base64BlockResponse, base64.StdEncoding.EncodeToString(blockResponseBytes))

var b BlockResponse
_, err = Codec.Unmarshal(blockResponseBytes, &b)
require.NoError(t, err)
require.Equal(t, blockResponse.Blocks, b.Blocks)
}
82 changes: 82 additions & 0 deletions vms/evm/sync/message/block_sync_summary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package message

import (
"context"
"fmt"

"github.com/ava-labs/libevm/common"
"github.com/ava-labs/libevm/crypto"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/snow/engine/snowman/block"
)

var _ Syncable = (*BlockSyncSummary)(nil)

// BlockSyncSummary provides the information necessary to sync a node starting
// at the given block.
type BlockSyncSummary struct {
BlockNumber uint64 `serialize:"true"`
BlockHash common.Hash `serialize:"true"`
BlockRoot common.Hash `serialize:"true"`

summaryID ids.ID
bytes []byte
acceptImpl AcceptImplFn
}

func NewBlockSyncSummary(blockHash common.Hash, blockNumber uint64, blockRoot common.Hash) (*BlockSyncSummary, error) {
// We intentionally do not use the acceptImpl here and leave it for the parser to set.
summary := BlockSyncSummary{
BlockNumber: blockNumber,
BlockHash: blockHash,
BlockRoot: blockRoot,
}
bytes, err := Codec.Marshal(Version, &summary)
if err != nil {
return nil, fmt.Errorf("failed to marshal syncable summary: %w", err)
}

summary.bytes = bytes
summaryID, err := ids.ToID(crypto.Keccak256(bytes))
if err != nil {
return nil, fmt.Errorf("failed to compute summary ID: %w", err)
}
summary.summaryID = summaryID

return &summary, nil
}

func (s *BlockSyncSummary) GetBlockHash() common.Hash {
return s.BlockHash
}

func (s *BlockSyncSummary) GetBlockRoot() common.Hash {
return s.BlockRoot
}

func (s *BlockSyncSummary) Bytes() []byte {
return s.bytes
}

func (s *BlockSyncSummary) Height() uint64 {
return s.BlockNumber
}

func (s *BlockSyncSummary) ID() ids.ID {
return s.summaryID
}

func (s *BlockSyncSummary) String() string {
return fmt.Sprintf("BlockSyncSummary(BlockHash=%s, BlockNumber=%d, BlockRoot=%s)", s.BlockHash, s.BlockNumber, s.BlockRoot)
}

func (s *BlockSyncSummary) Accept(context.Context) (block.StateSyncMode, error) {
if s.acceptImpl == nil {
return block.StateSyncSkipped, fmt.Errorf("accept implementation not specified for summary: %s", s)
}
return s.acceptImpl(s)
}
39 changes: 39 additions & 0 deletions vms/evm/sync/message/block_sync_summary_parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package message

import (
"errors"
"fmt"

"github.com/ava-labs/libevm/crypto"

"github.com/ava-labs/avalanchego/ids"
)

// errInvalidBlockSyncSummary is returned when the provided bytes cannot be
// parsed into a valid BlockSyncSummary.
var errInvalidBlockSyncSummary = errors.New("invalid block sync summary")

type BlockSyncSummaryParser struct{}

func NewBlockSyncSummaryParser() *BlockSyncSummaryParser {
return &BlockSyncSummaryParser{}
}

func (*BlockSyncSummaryParser) Parse(summaryBytes []byte, acceptImpl AcceptImplFn) (Syncable, error) {
summary := BlockSyncSummary{}
if _, err := Codec.Unmarshal(summaryBytes, &summary); err != nil {
return nil, fmt.Errorf("%w", errInvalidBlockSyncSummary)
}

summary.bytes = summaryBytes
summaryID, err := ids.ToID(crypto.Keccak256(summaryBytes))
if err != nil {
return nil, fmt.Errorf("failed to compute summary ID: %w", err)
}
summary.summaryID = summaryID
summary.acceptImpl = acceptImpl
return &summary, nil
}
47 changes: 47 additions & 0 deletions vms/evm/sync/message/block_sync_summary_parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package message

import (
"context"
"testing"

"github.com/ava-labs/libevm/common"
"github.com/stretchr/testify/require"

"github.com/ava-labs/avalanchego/snow/engine/snowman/block"
)

func TestBlockSyncSummaryParser_ParseValid(t *testing.T) {
t.Parallel()

blockSyncSummary, err := NewBlockSyncSummary(common.Hash{1}, 2, common.Hash{3})
require.NoError(t, err)

parser := NewBlockSyncSummaryParser()
called := false
acceptImplTest := func(Syncable) (block.StateSyncMode, error) {
called = true
return block.StateSyncSkipped, nil
}
s, err := parser.Parse(blockSyncSummary.Bytes(), acceptImplTest)
require.NoError(t, err)
require.Equal(t, blockSyncSummary.GetBlockHash(), s.GetBlockHash())
require.Equal(t, blockSyncSummary.Height(), s.Height())
require.Equal(t, blockSyncSummary.GetBlockRoot(), s.GetBlockRoot())
require.Equal(t, blockSyncSummary.Bytes(), s.Bytes())

mode, err := s.Accept(context.Background())
require.NoError(t, err)
require.Equal(t, block.StateSyncSkipped, mode)
require.True(t, called)
}

func TestBlockSyncSummaryParser_ParseInvalid(t *testing.T) {
t.Parallel()

parser := NewBlockSyncSummaryParser()
_, err := parser.Parse([]byte("not-a-summary"), nil)
require.ErrorIs(t, err, errInvalidBlockSyncSummary)
}
17 changes: 17 additions & 0 deletions vms/evm/sync/message/block_sync_summary_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package message

import (
"github.com/ava-labs/libevm/core/types"

"github.com/ava-labs/avalanchego/snow/engine/snowman/block"
)

type BlockSyncSummaryProvider struct{}

// StateSummaryAtBlock returns the block state summary at [block] if valid.
func (*BlockSyncSummaryProvider) StateSummaryAtBlock(blk *types.Block) (block.StateSummary, error) {
return NewBlockSyncSummary(blk.Hash(), blk.NumberU64(), blk.Root())
}
26 changes: 26 additions & 0 deletions vms/evm/sync/message/block_sync_summary_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package message

import (
"encoding/base64"
"testing"

"github.com/ava-labs/libevm/common"
"github.com/stretchr/testify/require"
)

func TestBlockSyncSummary_MarshalGolden(t *testing.T) {
t.Parallel()

blockSyncSummary, err := NewBlockSyncSummary(common.Hash{1}, 2, common.Hash{3})
require.NoError(t, err)

require.Equal(t, common.Hash{1}, blockSyncSummary.GetBlockHash())
require.Equal(t, uint64(2), blockSyncSummary.Height())
require.Equal(t, common.Hash{3}, blockSyncSummary.GetBlockRoot())

expectedBase64Bytes := "AAAAAAAAAAAAAgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
require.Equal(t, expectedBase64Bytes, base64.StdEncoding.EncodeToString(blockSyncSummary.Bytes()))
}
48 changes: 48 additions & 0 deletions vms/evm/sync/message/code_request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package message

import (
"context"
"fmt"
"strings"

"github.com/ava-labs/libevm/common"

"github.com/ava-labs/avalanchego/ids"
)

var _ Request = (*CodeRequest)(nil)

// CodeRequest is a request to retrieve a contract code with specified Hash
type CodeRequest struct {
// Hashes is a list of contract code hashes
Hashes []common.Hash `serialize:"true"`
}

func (c CodeRequest) String() string {
hashStrs := make([]string, len(c.Hashes))
for i, hash := range c.Hashes {
hashStrs[i] = hash.String()
}
return fmt.Sprintf("CodeRequest(Hashes=%s)", strings.Join(hashStrs, ", "))
}

func (c CodeRequest) Handle(ctx context.Context, nodeID ids.NodeID, requestID uint32, handler RequestHandler) ([]byte, error) {
return handler.HandleCodeRequest(ctx, nodeID, requestID, c)
}

func NewCodeRequest(hashes []common.Hash) CodeRequest {
return CodeRequest{
Hashes: hashes,
}
}

// CodeResponse is a response to a CodeRequest
// crypto.Keccak256Hash of each element in Data is expected to equal
// the corresponding element in CodeRequest.Hashes
// handler: handlers.CodeRequestHandler
type CodeResponse struct {
Data [][]byte `serialize:"true"`
}
Loading
Loading