Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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 the specified number of parent blocks
// starting from the given hash, ordered from newest to oldest.
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 a slice of RLP-encoded blocks starting with the block
// requested in BlockRequest.Hash. The next block is the parent, etc.
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)
}
143 changes: 143 additions & 0 deletions vms/evm/sync/message/block_sync_summary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package message

import (
"context"
"errors"
"fmt"

"github.com/ava-labs/libevm/common"
"github.com/ava-labs/libevm/core/types"
"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)
_ SyncableParser = (*BlockSyncSummaryParser)(nil)

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

// errAcceptImplNotSpecified is returned when Accept is called on a BlockSyncSummary
// that doesn't have an acceptImpl set.
errAcceptImplNotSpecified = errors.New("accept implementation not specified")
)

// Syncable extends [block.StateSummary] with EVM-specific block information.
type Syncable interface {
block.StateSummary
GetBlockHash() common.Hash
GetBlockRoot() common.Hash
}

// SyncableParser parses raw bytes into a [Syncable] instance.
type SyncableParser interface {
Parse(summaryBytes []byte, acceptImpl AcceptImplFn) (Syncable, error)
}

// AcceptImplFn is a function that determines the state sync mode for a given [Syncable].
type AcceptImplFn func(Syncable) (block.StateSyncMode, error)

// 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
}

// NewBlockSyncSummary creates a new [BlockSyncSummary] for the given block.
// The acceptImpl is intentionally left unset and should be set by the parser.
func NewBlockSyncSummary(blockHash common.Hash, blockNumber uint64, blockRoot common.Hash) (*BlockSyncSummary, error) {
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, errAcceptImplNotSpecified
}
return s.acceptImpl(s)
}

// BlockSyncSummaryParser parses [BlockSyncSummary] instances from raw bytes.
type BlockSyncSummaryParser struct{}

// NewBlockSyncSummaryParser creates a new [BlockSyncSummaryParser].
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: %w", errInvalidBlockSyncSummary, 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
}

// BlockSyncSummaryProvider provides state summaries for blocks.
type BlockSyncSummaryProvider struct{}

// StateSummaryAtBlock returns the block state summary for the given block if valid.
func (*BlockSyncSummaryProvider) StateSummaryAtBlock(blk *types.Block) (block.StateSummary, error) {
return NewBlockSyncSummary(blk.Hash(), blk.NumberU64(), blk.Root())
}
Loading