Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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)
}
34 changes: 34 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,34 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package message

import (
"fmt"

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

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

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("failed to parse syncable summary: %w", err)
}

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
}
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())
}
45 changes: 45 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,45 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package message

import (
"context"
"encoding/base64"
"testing"

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

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

func TestMarshalBlockSyncSummary(t *testing.T) {
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()))

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.TODO())
require.NoError(t, err)
require.Equal(t, block.StateSyncSkipped, mode)
require.True(t, called)
}
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{}

// 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"`
}
58 changes: 58 additions & 0 deletions vms/evm/sync/message/code_request_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// 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"
)

// TestMarshalCodeRequest requires that the structure or serialization logic hasn't changed, primarily to
// ensure compatibility with the network.
func TestMarshalCodeRequest(t *testing.T) {
codeRequest := CodeRequest{
Hashes: []common.Hash{common.BytesToHash([]byte("some code pls"))},
}

base64CodeRequest := "AAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAHNvbWUgY29kZSBwbHM="

codeRequestBytes, err := Codec.Marshal(Version, codeRequest)
require.NoError(t, err)
require.Equal(t, base64CodeRequest, base64.StdEncoding.EncodeToString(codeRequestBytes))

var c CodeRequest
_, err = Codec.Unmarshal(codeRequestBytes, &c)
require.NoError(t, err)
require.Equal(t, codeRequest.Hashes, c.Hashes)
}

// TestMarshalCodeResponse requires that the structure or serialization logic hasn't changed, primarily to
// ensure compatibility with the network.
func TestMarshalCodeResponse(t *testing.T) {
// generate some random code data
// set random seed for deterministic random
codeData := make([]byte, 50)
r := rand.New(rand.NewSource(1)) //nolint:gosec // deterministic bytes for golden assertion
_, err := r.Read(codeData)
require.NoError(t, err)

codeResponse := CodeResponse{
Data: [][]byte{codeData},
}

base64CodeResponse := "AAAAAAABAAAAMlL9/AchgmVPFj9fD5piHXKVZsdNEAN8TXu7BAfR4sZJgYVa2GgdDYbR6R4AFnk5y2aU"

codeResponseBytes, err := Codec.Marshal(Version, codeResponse)
require.NoError(t, err)
require.Equal(t, base64CodeResponse, base64.StdEncoding.EncodeToString(codeResponseBytes))

var c CodeResponse
_, err = Codec.Unmarshal(codeResponseBytes, &c)
require.NoError(t, err)
require.Equal(t, codeResponse.Data, c.Data)
}
Loading