Skip to content
Merged
4 changes: 2 additions & 2 deletions network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ func NewNetwork(
appSender,
registerer,
"p2p",
p2pValidators,
p2pValidators, // p2pValidators implements ConnectionHandler
)
if err != nil {
return nil, fmt.Errorf("failed to initialize p2p network: %w", err)
Expand Down Expand Up @@ -492,7 +492,7 @@ func (n *network) SendSyncedAppRequest(ctx context.Context, nodeID ids.NodeID, r
}

func (n *network) NewClient(protocol uint64) *p2p.Client {
return n.sdkNetwork.NewClient(protocol, n.p2pValidators)
return n.sdkNetwork.NewClient(protocol, n.p2pValidators) // p2pValidators implements NodeSampler
}

func (n *network) AddHandler(protocol uint64, handler p2p.Handler) error {
Expand Down
12 changes: 12 additions & 0 deletions plugin/evm/extension/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/ava-labs/subnet-evm/plugin/evm/message"
"github.com/ava-labs/subnet-evm/plugin/evm/sync"
"github.com/ava-labs/subnet-evm/sync/handlers"
)

var (
Expand All @@ -19,6 +20,17 @@ var (
errNilClock = errors.New("nil clock")
)

// LeafRequestConfig is the configuration to handle leaf requests
// in the network and syncer
type LeafRequestConfig struct {
// LeafType is the type of the leaf node
LeafType message.NodeType
// MetricName is the name of the metric to use for the leaf request
MetricName string
// Handler is the handler to use for the leaf request
Handler handlers.LeafRequestHandler
}

// Config is the configuration for the VM extension
type Config struct {
// SyncSummaryProvider is the sync summary provider to use
Expand Down
5 changes: 2 additions & 3 deletions plugin/evm/message/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ var _ RequestHandler = NoopRequestHandler{}
// Must have methods in format of handleType(context.Context, ids.NodeID, uint32, request Type) error
// so that the Request object of relevant Type can invoke its respective handle method
// on this struct.
// Also see GossipHandler for implementation style.
type RequestHandler interface {
HandleStateTrieLeafsRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, leafsRequest LeafsRequest) ([]byte, error)
HandleLeafsRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, leafsRequest LeafsRequest) ([]byte, error)
HandleBlockRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, request BlockRequest) ([]byte, error)
HandleCodeRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, codeRequest CodeRequest) ([]byte, error)
}
Expand All @@ -33,7 +32,7 @@ type ResponseHandler interface {

type NoopRequestHandler struct{}

func (NoopRequestHandler) HandleStateTrieLeafsRequest(context.Context, ids.NodeID, uint32, LeafsRequest) ([]byte, error) {
func (NoopRequestHandler) HandleLeafsRequest(context.Context, ids.NodeID, uint32, LeafsRequest) ([]byte, error) {
return nil, nil
}

Expand Down
29 changes: 20 additions & 9 deletions plugin/evm/message/leafs_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,38 @@ import (

const MaxCodeHashesPerRequest = 5

var _ Request = LeafsRequest{}
// NodeType outlines the trie that a leaf node belongs to
// handlers.LeafsRequestHandler uses this information to determine
// which trie type to fetch the information from
type NodeType uint8

const (
StateTrieNode = NodeType(1)
StateTrieKeyLength = common.HashLength
)

// LeafsRequest is a request to receive trie leaves at specified Root within Start and End byte range
// Limit outlines maximum number of leaves to returns starting at Start
// NodeType outlines which trie to read from state/atomic.
// NOTE: NodeType is not serialized to avoid breaking changes. We rely on the single used node type (StateTrieNode).
type LeafsRequest struct {
Root common.Hash `serialize:"true"`
Account common.Hash `serialize:"true"`
Start []byte `serialize:"true"`
End []byte `serialize:"true"`
Limit uint16 `serialize:"true"`
Root common.Hash `serialize:"true"`
Account common.Hash `serialize:"true"`
Start []byte `serialize:"true"`
End []byte `serialize:"true"`
Limit uint16 `serialize:"true"`
NodeType NodeType // TODO(JonathanOppenheimer): Not serialized to avoid breaking changes. We rely on the single used node type (StateTrieNode).
}

func (l LeafsRequest) String() string {
return fmt.Sprintf(
"LeafsRequest(Root=%s, Account=%s, Start=%s, End %s, Limit=%d)",
l.Root, l.Account, common.Bytes2Hex(l.Start), common.Bytes2Hex(l.End), l.Limit,
"LeafsRequest(Root=%s, Account=%s, Start=%s, End=%s, Limit=%d, NodeType=%d)",
l.Root, l.Account, common.Bytes2Hex(l.Start), common.Bytes2Hex(l.End), l.Limit, l.NodeType,
)
}

func (l LeafsRequest) Handle(ctx context.Context, nodeID ids.NodeID, requestID uint32, handler RequestHandler) ([]byte, error) {
return handler.HandleStateTrieLeafsRequest(ctx, nodeID, requestID, l)
return handler.HandleLeafsRequest(ctx, nodeID, requestID, l)
}

// LeafsResponse is a response to a LeafsRequest
Expand Down
97 changes: 43 additions & 54 deletions plugin/evm/message/leafs_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,11 @@
package message

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

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

Expand Down Expand Up @@ -52,6 +48,7 @@ func TestMarshalLeafsRequest(t *testing.T) {
require.Equal(t, leafsRequest.Start, l.Start)
require.Equal(t, leafsRequest.End, l.End)
require.Equal(t, leafsRequest.Limit, l.Limit)
require.Equal(t, NodeType(0), l.NodeType) // make sure it is not serialized
}

// TestMarshalLeafsResponse requires that the structure or serialization logic hasn't changed, primarily to
Expand Down Expand Up @@ -107,61 +104,53 @@ func TestMarshalLeafsResponse(t *testing.T) {
require.Equal(t, leafsResponse.ProofVals, l.ProofVals)
}

func TestLeafsRequestValidation(t *testing.T) {
mockRequestHandler := &mockHandler{}

tests := map[string]struct {
request LeafsRequest
assertResponse func(t *testing.T)
}{
"node type StateTrieNode": {
request: LeafsRequest{
Root: common.BytesToHash([]byte("some hash goes here")),
Start: bytes.Repeat([]byte{0x00}, common.HashLength),
End: bytes.Repeat([]byte{0xff}, common.HashLength),
Limit: 10,
},
assertResponse: func(t *testing.T) {
assert.True(t, mockRequestHandler.handleStateTrieCalled)
assert.False(t, mockRequestHandler.handleBlockRequestCalled)
assert.False(t, mockRequestHandler.handleCodeRequestCalled)
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
_, _ = test.request.Handle(context.Background(), ids.GenerateTestNodeID(), 1, mockRequestHandler)
test.assertResponse(t)
mockRequestHandler.reset()
})
// TestLeafsRequestNodeTypeNotSerialized verifies that NodeType is not serialized
// and does not affect the encoded output. This ensures backward compatibility.
func TestLeafsRequestNodeTypeNotSerialized(t *testing.T) {
// set random seed for deterministic random
rand := rand.New(rand.NewSource(1))

startBytes := make([]byte, common.HashLength)
endBytes := make([]byte, common.HashLength)

_, err := rand.Read(startBytes)
require.NoError(t, err)

_, err = rand.Read(endBytes)
require.NoError(t, err)

// Create request without explicit NodeType (defaults to 0)
leafsRequestDefault := LeafsRequest{
Root: common.BytesToHash([]byte("test root")),
Start: startBytes,
End: endBytes,
Limit: 512,
}
}

var _ RequestHandler = (*mockHandler)(nil)
// Create request with explicit NodeType
leafsRequestWithNodeType := LeafsRequest{
Root: common.BytesToHash([]byte("test root")),
Start: startBytes,
End: endBytes,
Limit: 512,
NodeType: StateTrieNode,
}

type mockHandler struct {
handleStateTrieCalled,
handleBlockRequestCalled,
handleCodeRequestCalled bool
}
bytesDefault, err := Codec.Marshal(Version, leafsRequestDefault)
require.NoError(t, err)

func (m *mockHandler) HandleStateTrieLeafsRequest(context.Context, ids.NodeID, uint32, LeafsRequest) ([]byte, error) {
m.handleStateTrieCalled = true
return nil, nil
}
bytesWithNodeType, err := Codec.Marshal(Version, leafsRequestWithNodeType)
require.NoError(t, err)

func (m *mockHandler) HandleBlockRequest(context.Context, ids.NodeID, uint32, BlockRequest) ([]byte, error) {
m.handleBlockRequestCalled = true
return nil, nil
}
require.Equal(t, bytesDefault, bytesWithNodeType, "NodeType should not affect serialization")

func (m *mockHandler) HandleCodeRequest(context.Context, ids.NodeID, uint32, CodeRequest) ([]byte, error) {
m.handleCodeRequestCalled = true
return nil, nil
}
var unmarshaled LeafsRequest
_, err = Codec.Unmarshal(bytesWithNodeType, &unmarshaled)
require.NoError(t, err)

func (m *mockHandler) reset() {
m.handleStateTrieCalled = false
m.handleBlockRequestCalled = false
m.handleCodeRequestCalled = false
require.Equal(t, NodeType(0), unmarshaled.NodeType, "NodeType should not be serialized")
require.Equal(t, leafsRequestDefault.Root, unmarshaled.Root)
require.Equal(t, leafsRequestDefault.Start, unmarshaled.Start)
require.Equal(t, leafsRequestDefault.End, unmarshaled.End)
require.Equal(t, leafsRequestDefault.Limit, unmarshaled.Limit)
}
38 changes: 30 additions & 8 deletions plugin/evm/network_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/ava-labs/avalanchego/codec"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/libevm/ethdb"
"github.com/ava-labs/libevm/metrics"
"github.com/ava-labs/libevm/log"
"github.com/ava-labs/libevm/triedb"

"github.com/ava-labs/subnet-evm/plugin/evm/message"
Expand All @@ -20,29 +20,51 @@ import (

var _ message.RequestHandler = (*networkHandler)(nil)

type LeafHandlers map[message.NodeType]syncHandlers.LeafRequestHandler

type networkHandler struct {
leafRequestHandler *syncHandlers.LeafsRequestHandler
leafRequestHandlers LeafHandlers
blockRequestHandler *syncHandlers.BlockRequestHandler
codeRequestHandler *syncHandlers.CodeRequestHandler
}

type LeafRequestTypeConfig struct {
NodeType message.NodeType
NodeKeyLen int
TrieDB *triedb.Database
UseSnapshots bool
MetricName string
}

// newNetworkHandler constructs the handler for serving network requests.
func newNetworkHandler(
provider syncHandlers.SyncDataProvider,
diskDB ethdb.KeyValueReader,
evmTrieDB *triedb.Database,
networkCodec codec.Manager,
) message.RequestHandler {
syncStats := syncStats.NewHandlerStats(metrics.Enabled)
leafRequestHandlers LeafHandlers,
syncStats syncStats.HandlerStats,
) *networkHandler {
return &networkHandler{
leafRequestHandler: syncHandlers.NewLeafsRequestHandler(evmTrieDB, nil, networkCodec, syncStats),
leafRequestHandlers: leafRequestHandlers,
blockRequestHandler: syncHandlers.NewBlockRequestHandler(provider, networkCodec, syncStats),
codeRequestHandler: syncHandlers.NewCodeRequestHandler(diskDB, networkCodec, syncStats),
}
}

func (n networkHandler) HandleStateTrieLeafsRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, leafsRequest message.LeafsRequest) ([]byte, error) {
return n.leafRequestHandler.OnLeafsRequest(ctx, nodeID, requestID, leafsRequest)
func (n networkHandler) HandleLeafsRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, leafsRequest message.LeafsRequest) ([]byte, error) {
nodeType := leafsRequest.NodeType
// TODO(JonathanOppenheimer):Handle legacy requests where NodeType was not serialized (defaults to 0)
// In this interim period, we treat NodeType 0 as StateTrieNode
if nodeType == 0 {
nodeType = message.StateTrieNode
}

handler, ok := n.leafRequestHandlers[nodeType]
if !ok {
log.Debug("node type is not recognised, dropping request", "nodeID", nodeID, "requestID", requestID, "nodeType", leafsRequest.NodeType)
return nil, nil
}
return handler.OnLeafsRequest(ctx, nodeID, requestID, leafsRequest)
}

func (n networkHandler) HandleBlockRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, blockRequest message.BlockRequest) ([]byte, error) {
Expand Down
Loading
Loading