Skip to content

Commit 4c56250

Browse files
committed
chore(sync/message): migrate message package for EVM state sync from coreth
- Introduce vms/evm/sync/message with request/response types: - BlockRequest/Response, LeafsRequest/Response, CodeRequest/Response. - Add Request and RequestHandler interfaces and wiring. - Add codec setup with versioning and max message size. - Add BlockSyncSummary and parser for sync bootstrap. - Add golden serialization tests and table-driven tests for handler dispatch and interface round-trips. resolves #4416 Signed-off-by: Tsvetan Dimitrov (tsvetan.dimitrov@avalabs.org)
1 parent e60f757 commit 4c56250

15 files changed

+899
-0
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package message
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
"github.com/ava-labs/libevm/common"
11+
12+
"github.com/ava-labs/avalanchego/ids"
13+
)
14+
15+
var _ Request = (*BlockRequest)(nil)
16+
17+
// BlockRequest is a request to retrieve Parents number of blocks starting from Hash from newest-oldest manner
18+
type BlockRequest struct {
19+
Hash common.Hash `serialize:"true"`
20+
Height uint64 `serialize:"true"`
21+
Parents uint16 `serialize:"true"`
22+
}
23+
24+
func (b BlockRequest) String() string {
25+
return fmt.Sprintf(
26+
"BlockRequest(Hash=%s, Height=%d, Parents=%d)",
27+
b.Hash, b.Height, b.Parents,
28+
)
29+
}
30+
31+
func (b BlockRequest) Handle(ctx context.Context, nodeID ids.NodeID, requestID uint32, handler RequestHandler) ([]byte, error) {
32+
return handler.HandleBlockRequest(ctx, nodeID, requestID, b)
33+
}
34+
35+
// BlockResponse is a response to a BlockRequest
36+
// Blocks is slice of RLP encoded blocks starting with the block
37+
// requested in BlockRequest.Hash. The next block is the parent, etc.
38+
// handler: handlers.BlockRequestHandler
39+
type BlockResponse struct {
40+
Blocks [][]byte `serialize:"true"`
41+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package message
5+
6+
import (
7+
"encoding/base64"
8+
"math/rand"
9+
"testing"
10+
11+
"github.com/ava-labs/libevm/common"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
// TestMarshalBlockRequest requires that the structure or serialization logic hasn't changed, primarily to
16+
// ensure compatibility with the network.
17+
func TestMarshalBlockRequest(t *testing.T) {
18+
blockRequest := BlockRequest{
19+
Hash: common.BytesToHash([]byte("some hash is here yo")),
20+
Height: 1337,
21+
Parents: 64,
22+
}
23+
24+
base64BlockRequest := "AAAAAAAAAAAAAAAAAABzb21lIGhhc2ggaXMgaGVyZSB5bwAAAAAAAAU5AEA="
25+
26+
blockRequestBytes, err := Codec.Marshal(Version, blockRequest)
27+
require.NoError(t, err)
28+
require.Equal(t, base64BlockRequest, base64.StdEncoding.EncodeToString(blockRequestBytes))
29+
30+
var b BlockRequest
31+
_, err = Codec.Unmarshal(blockRequestBytes, &b)
32+
require.NoError(t, err)
33+
require.Equal(t, blockRequest.Hash, b.Hash)
34+
require.Equal(t, blockRequest.Height, b.Height)
35+
require.Equal(t, blockRequest.Parents, b.Parents)
36+
}
37+
38+
// TestMarshalBlockResponse requires that the structure or serialization logic hasn't changed, primarily to
39+
// ensure compatibility with the network.
40+
func TestMarshalBlockResponse(t *testing.T) {
41+
// create some random bytes
42+
// set seed to ensure deterministic random behaviour
43+
r := rand.New(rand.NewSource(1)) //nolint:gosec // deterministic bytes for golden assertion
44+
blocksBytes := make([][]byte, 32)
45+
for i := range blocksBytes {
46+
blocksBytes[i] = make([]byte, r.Intn(32)+32)
47+
_, err := r.Read(blocksBytes[i])
48+
require.NoError(t, err)
49+
}
50+
51+
blockResponse := BlockResponse{
52+
Blocks: blocksBytes,
53+
}
54+
55+
base64BlockResponse := "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"
56+
57+
blockResponseBytes, err := Codec.Marshal(Version, blockResponse)
58+
require.NoError(t, err)
59+
require.Equal(t, base64BlockResponse, base64.StdEncoding.EncodeToString(blockResponseBytes))
60+
61+
var b BlockResponse
62+
_, err = Codec.Unmarshal(blockResponseBytes, &b)
63+
require.NoError(t, err)
64+
require.Equal(t, blockResponse.Blocks, b.Blocks)
65+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package message
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
"github.com/ava-labs/libevm/common"
11+
"github.com/ava-labs/libevm/crypto"
12+
13+
"github.com/ava-labs/avalanchego/ids"
14+
"github.com/ava-labs/avalanchego/snow/engine/snowman/block"
15+
)
16+
17+
var _ Syncable = (*BlockSyncSummary)(nil)
18+
19+
// BlockSyncSummary provides the information necessary to sync a node starting
20+
// at the given block.
21+
type BlockSyncSummary struct {
22+
BlockNumber uint64 `serialize:"true"`
23+
BlockHash common.Hash `serialize:"true"`
24+
BlockRoot common.Hash `serialize:"true"`
25+
26+
summaryID ids.ID
27+
bytes []byte
28+
acceptImpl AcceptImplFn
29+
}
30+
31+
func NewBlockSyncSummary(blockHash common.Hash, blockNumber uint64, blockRoot common.Hash) (*BlockSyncSummary, error) {
32+
// We intentionally do not use the acceptImpl here and leave it for the parser to set.
33+
summary := BlockSyncSummary{
34+
BlockNumber: blockNumber,
35+
BlockHash: blockHash,
36+
BlockRoot: blockRoot,
37+
}
38+
bytes, err := Codec.Marshal(Version, &summary)
39+
if err != nil {
40+
return nil, fmt.Errorf("failed to marshal syncable summary: %w", err)
41+
}
42+
43+
summary.bytes = bytes
44+
summaryID, err := ids.ToID(crypto.Keccak256(bytes))
45+
if err != nil {
46+
return nil, fmt.Errorf("failed to compute summary ID: %w", err)
47+
}
48+
summary.summaryID = summaryID
49+
50+
return &summary, nil
51+
}
52+
53+
func (s *BlockSyncSummary) GetBlockHash() common.Hash {
54+
return s.BlockHash
55+
}
56+
57+
func (s *BlockSyncSummary) GetBlockRoot() common.Hash {
58+
return s.BlockRoot
59+
}
60+
61+
func (s *BlockSyncSummary) Bytes() []byte {
62+
return s.bytes
63+
}
64+
65+
func (s *BlockSyncSummary) Height() uint64 {
66+
return s.BlockNumber
67+
}
68+
69+
func (s *BlockSyncSummary) ID() ids.ID {
70+
return s.summaryID
71+
}
72+
73+
func (s *BlockSyncSummary) String() string {
74+
return fmt.Sprintf("BlockSyncSummary(BlockHash=%s, BlockNumber=%d, BlockRoot=%s)", s.BlockHash, s.BlockNumber, s.BlockRoot)
75+
}
76+
77+
func (s *BlockSyncSummary) Accept(context.Context) (block.StateSyncMode, error) {
78+
if s.acceptImpl == nil {
79+
return block.StateSyncSkipped, fmt.Errorf("accept implementation not specified for summary: %s", s)
80+
}
81+
return s.acceptImpl(s)
82+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package message
5+
6+
import (
7+
"fmt"
8+
9+
"github.com/ava-labs/libevm/crypto"
10+
11+
"github.com/ava-labs/avalanchego/ids"
12+
)
13+
14+
type BlockSyncSummaryParser struct{}
15+
16+
func NewBlockSyncSummaryParser() *BlockSyncSummaryParser {
17+
return &BlockSyncSummaryParser{}
18+
}
19+
20+
func (*BlockSyncSummaryParser) Parse(summaryBytes []byte, acceptImpl AcceptImplFn) (Syncable, error) {
21+
summary := BlockSyncSummary{}
22+
if _, err := Codec.Unmarshal(summaryBytes, &summary); err != nil {
23+
return nil, fmt.Errorf("failed to parse syncable summary: %w", err)
24+
}
25+
26+
summary.bytes = summaryBytes
27+
summaryID, err := ids.ToID(crypto.Keccak256(summaryBytes))
28+
if err != nil {
29+
return nil, fmt.Errorf("failed to compute summary ID: %w", err)
30+
}
31+
summary.summaryID = summaryID
32+
summary.acceptImpl = acceptImpl
33+
return &summary, nil
34+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package message
5+
6+
import (
7+
"github.com/ava-labs/libevm/core/types"
8+
9+
"github.com/ava-labs/avalanchego/snow/engine/snowman/block"
10+
)
11+
12+
type BlockSyncSummaryProvider struct{}
13+
14+
// StateSummaryAtBlock returns the block state summary at [block] if valid.
15+
func (*BlockSyncSummaryProvider) StateSummaryAtBlock(blk *types.Block) (block.StateSummary, error) {
16+
return NewBlockSyncSummary(blk.Hash(), blk.NumberU64(), blk.Root())
17+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package message
5+
6+
import (
7+
"context"
8+
"encoding/base64"
9+
"testing"
10+
11+
"github.com/ava-labs/libevm/common"
12+
"github.com/stretchr/testify/require"
13+
14+
"github.com/ava-labs/avalanchego/snow/engine/snowman/block"
15+
)
16+
17+
func TestMarshalBlockSyncSummary(t *testing.T) {
18+
blockSyncSummary, err := NewBlockSyncSummary(common.Hash{1}, 2, common.Hash{3})
19+
require.NoError(t, err)
20+
21+
require.Equal(t, common.Hash{1}, blockSyncSummary.GetBlockHash())
22+
require.Equal(t, uint64(2), blockSyncSummary.Height())
23+
require.Equal(t, common.Hash{3}, blockSyncSummary.GetBlockRoot())
24+
25+
expectedBase64Bytes := "AAAAAAAAAAAAAgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
26+
require.Equal(t, expectedBase64Bytes, base64.StdEncoding.EncodeToString(blockSyncSummary.Bytes()))
27+
28+
parser := NewBlockSyncSummaryParser()
29+
called := false
30+
acceptImplTest := func(Syncable) (block.StateSyncMode, error) {
31+
called = true
32+
return block.StateSyncSkipped, nil
33+
}
34+
s, err := parser.Parse(blockSyncSummary.Bytes(), acceptImplTest)
35+
require.NoError(t, err)
36+
require.Equal(t, blockSyncSummary.GetBlockHash(), s.GetBlockHash())
37+
require.Equal(t, blockSyncSummary.Height(), s.Height())
38+
require.Equal(t, blockSyncSummary.GetBlockRoot(), s.GetBlockRoot())
39+
require.Equal(t, blockSyncSummary.Bytes(), s.Bytes())
40+
41+
mode, err := s.Accept(context.TODO())
42+
require.NoError(t, err)
43+
require.Equal(t, block.StateSyncSkipped, mode)
44+
require.True(t, called)
45+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package message
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"strings"
10+
11+
"github.com/ava-labs/libevm/common"
12+
13+
"github.com/ava-labs/avalanchego/ids"
14+
)
15+
16+
var _ Request = CodeRequest{}
17+
18+
// CodeRequest is a request to retrieve a contract code with specified Hash
19+
type CodeRequest struct {
20+
// Hashes is a list of contract code hashes
21+
Hashes []common.Hash `serialize:"true"`
22+
}
23+
24+
func (c CodeRequest) String() string {
25+
hashStrs := make([]string, len(c.Hashes))
26+
for i, hash := range c.Hashes {
27+
hashStrs[i] = hash.String()
28+
}
29+
return fmt.Sprintf("CodeRequest(Hashes=%s)", strings.Join(hashStrs, ", "))
30+
}
31+
32+
func (c CodeRequest) Handle(ctx context.Context, nodeID ids.NodeID, requestID uint32, handler RequestHandler) ([]byte, error) {
33+
return handler.HandleCodeRequest(ctx, nodeID, requestID, c)
34+
}
35+
36+
func NewCodeRequest(hashes []common.Hash) CodeRequest {
37+
return CodeRequest{
38+
Hashes: hashes,
39+
}
40+
}
41+
42+
// CodeResponse is a response to a CodeRequest
43+
// crypto.Keccak256Hash of each element in Data is expected to equal
44+
// the corresponding element in CodeRequest.Hashes
45+
// handler: handlers.CodeRequestHandler
46+
type CodeResponse struct {
47+
Data [][]byte `serialize:"true"`
48+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package message
5+
6+
import (
7+
"encoding/base64"
8+
"math/rand"
9+
"testing"
10+
11+
"github.com/ava-labs/libevm/common"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
// TestMarshalCodeRequest requires that the structure or serialization logic hasn't changed, primarily to
16+
// ensure compatibility with the network.
17+
func TestMarshalCodeRequest(t *testing.T) {
18+
codeRequest := CodeRequest{
19+
Hashes: []common.Hash{common.BytesToHash([]byte("some code pls"))},
20+
}
21+
22+
base64CodeRequest := "AAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAHNvbWUgY29kZSBwbHM="
23+
24+
codeRequestBytes, err := Codec.Marshal(Version, codeRequest)
25+
require.NoError(t, err)
26+
require.Equal(t, base64CodeRequest, base64.StdEncoding.EncodeToString(codeRequestBytes))
27+
28+
var c CodeRequest
29+
_, err = Codec.Unmarshal(codeRequestBytes, &c)
30+
require.NoError(t, err)
31+
require.Equal(t, codeRequest.Hashes, c.Hashes)
32+
}
33+
34+
// TestMarshalCodeResponse requires that the structure or serialization logic hasn't changed, primarily to
35+
// ensure compatibility with the network.
36+
func TestMarshalCodeResponse(t *testing.T) {
37+
// generate some random code data
38+
// set random seed for deterministic random
39+
codeData := make([]byte, 50)
40+
r := rand.New(rand.NewSource(1)) //nolint:gosec // deterministic bytes for golden assertion
41+
_, err := r.Read(codeData)
42+
require.NoError(t, err)
43+
44+
codeResponse := CodeResponse{
45+
Data: [][]byte{codeData},
46+
}
47+
48+
base64CodeResponse := "AAAAAAABAAAAMlL9/AchgmVPFj9fD5piHXKVZsdNEAN8TXu7BAfR4sZJgYVa2GgdDYbR6R4AFnk5y2aU"
49+
50+
codeResponseBytes, err := Codec.Marshal(Version, codeResponse)
51+
require.NoError(t, err)
52+
require.Equal(t, base64CodeResponse, base64.StdEncoding.EncodeToString(codeResponseBytes))
53+
54+
var c CodeResponse
55+
_, err = Codec.Unmarshal(codeResponseBytes, &c)
56+
require.NoError(t, err)
57+
require.Equal(t, codeResponse.Data, c.Data)
58+
}

0 commit comments

Comments
 (0)