diff --git a/plugin/evm/extension/config.go b/plugin/evm/extension/config.go index 8dbdb7437d..544d13a214 100644 --- a/plugin/evm/extension/config.go +++ b/plugin/evm/extension/config.go @@ -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 ( @@ -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 diff --git a/plugin/evm/message/handler.go b/plugin/evm/message/handler.go index a6b0306cd9..42a5319249 100644 --- a/plugin/evm/message/handler.go +++ b/plugin/evm/message/handler.go @@ -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) } @@ -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 } diff --git a/plugin/evm/message/leafs_request.go b/plugin/evm/message/leafs_request.go index 2e345949e7..3cf5d6a05b 100644 --- a/plugin/evm/message/leafs_request.go +++ b/plugin/evm/message/leafs_request.go @@ -13,27 +13,37 @@ 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. 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 `serialize:"true"` } 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 diff --git a/plugin/evm/message/leafs_request_test.go b/plugin/evm/message/leafs_request_test.go index efe5920ecb..123d69302f 100644 --- a/plugin/evm/message/leafs_request_test.go +++ b/plugin/evm/message/leafs_request_test.go @@ -4,13 +4,10 @@ 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" ) @@ -38,7 +35,7 @@ func TestMarshalLeafsRequest(t *testing.T) { Limit: 1024, } - base64LeafsRequest := "AAAAAAAAAAAAAAAAAAAAAABpbSBST09UaW5nIGZvciB5YQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIFL9/AchgmVPFj9fD5piHXKVZsdNEAN8TXu7BAfR4sZJAAAAIIGFWthoHQ2G0ekeABZ5OctmlNLEIqzSCKAHKTlIf2mZBAA=" + base64LeafsRequest := "AAAAAAAAAAAAAAAAAAAAAABpbSBST09UaW5nIGZvciB5YQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIFL9/AchgmVPFj9fD5piHXKVZsdNEAN8TXu7BAfR4sZJAAAAIIGFWthoHQ2G0ekeABZ5OctmlNLEIqzSCKAHKTlIf2mZBAAA" leafsRequestBytes, err := Codec.Marshal(Version, leafsRequest) assert.NoError(t, err) @@ -105,62 +102,3 @@ func TestMarshalLeafsResponse(t *testing.T) { assert.False(t, l.More) // make sure it is not serialized assert.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() - }) - } -} - -var _ RequestHandler = (*mockHandler)(nil) - -type mockHandler struct { - handleStateTrieCalled, - handleBlockRequestCalled, - handleCodeRequestCalled bool -} - -func (m *mockHandler) HandleStateTrieLeafsRequest(context.Context, ids.NodeID, uint32, LeafsRequest) ([]byte, error) { - m.handleStateTrieCalled = true - return nil, nil -} - -func (m *mockHandler) HandleBlockRequest(context.Context, ids.NodeID, uint32, BlockRequest) ([]byte, error) { - m.handleBlockRequestCalled = true - return nil, nil -} - -func (m *mockHandler) HandleCodeRequest(context.Context, ids.NodeID, uint32, CodeRequest) ([]byte, error) { - m.handleCodeRequestCalled = true - return nil, nil -} - -func (m *mockHandler) reset() { - m.handleStateTrieCalled = false - m.handleBlockRequestCalled = false - m.handleCodeRequestCalled = false -} diff --git a/plugin/evm/network_handler.go b/plugin/evm/network_handler.go index 4fe51a98df..74952f8375 100644 --- a/plugin/evm/network_handler.go +++ b/plugin/evm/network_handler.go @@ -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" @@ -20,29 +20,44 @@ 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) { + handler, ok := n.leafRequestHandlers[leafsRequest.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) { diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index a2e6619307..7e2785a3de 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -76,6 +76,7 @@ import ( "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ava-labs/subnet-evm/rpc" "github.com/ava-labs/subnet-evm/sync/client/stats" + "github.com/ava-labs/subnet-evm/sync/handlers" "github.com/ava-labs/subnet-evm/triedb/hashdb" "github.com/ava-labs/subnet-evm/warp" @@ -88,6 +89,7 @@ import ( subnetevmlog "github.com/ava-labs/subnet-evm/plugin/evm/log" vmsync "github.com/ava-labs/subnet-evm/plugin/evm/sync" statesyncclient "github.com/ava-labs/subnet-evm/sync/client" + handlerstats "github.com/ava-labs/subnet-evm/sync/handlers/stats" avalancheRPC "github.com/gorilla/rpc/v2" ) @@ -503,11 +505,9 @@ func (vm *VM) Initialize( warpHandler := acp118.NewCachedHandler(meteredCache, vm.warpBackend, vm.ctx.WarpSigner) vm.Network.AddHandler(p2p.SignatureRequestHandlerID, warpHandler) - vm.setAppRequestHandlers() - vm.stateSyncDone = make(chan struct{}) - return vm.initializeStateSyncClient(lastAcceptedHeight) + return vm.initializeStateSync(lastAcceptedHeight) } func parseGenesis(ctx *snow.Context, genesisBytes []byte, upgradeBytes []byte, airdropFile string) (*core.Genesis, error) { @@ -651,7 +651,44 @@ func (vm *VM) initializeChain(lastAcceptedHash common.Hash, ethConfig ethconfig. // initializeStateSyncClient initializes the client for performing state sync. // If state sync is disabled, this function will wipe any ongoing summary from // disk to ensure that we do not continue syncing from an invalid snapshot. -func (vm *VM) initializeStateSyncClient(lastAcceptedHeight uint64) error { +func (vm *VM) initializeStateSync(lastAcceptedHeight uint64) error { + // Create standalone EVM TrieDB (read only) for serving leafs requests. + // We create a standalone TrieDB here, so that it has a standalone cache from the one + // used by the node when processing blocks. + evmTrieDB := triedb.NewDatabase( + vm.chaindb, + &triedb.Config{ + DBOverride: hashdb.Config{ + CleanCacheSize: vm.config.StateSyncServerTrieCache * units.MiB, + }.BackendConstructor, + }, + ) + + // register default leaf request handler for state trie + syncStats := handlerstats.GetOrRegisterHandlerStats(metrics.Enabled) + stateLeafRequestConfig := &extension.LeafRequestConfig{ + LeafType: message.StateTrieNode, + MetricName: "sync_state_trie_leaves", + Handler: handlers.NewLeafsRequestHandler(evmTrieDB, + message.StateTrieKeyLength, + vm.blockChain, vm.networkCodec, + syncStats, + ), + } + + leafHandlers := make(LeafHandlers) + leafHandlers[stateLeafRequestConfig.LeafType] = stateLeafRequestConfig.Handler + + networkHandler := newNetworkHandler( + vm.blockChain, + vm.chaindb, + vm.networkCodec, + leafHandlers, + syncStats, + ) + vm.Network.SetRequestHandler(networkHandler) + + vm.Server = vmsync.NewServer(vm.blockChain, vm.extensionConfig.SyncSummaryProvider, vm.config.StateSyncCommitInterval) // parse nodeIDs from state sync IDs in vm config // parse nodeIDs from state sync IDs in vm config var stateSyncIDs []ids.NodeID if vm.config.StateSyncEnabled && len(vm.config.StateSyncIDs) > 0 { @@ -666,15 +703,19 @@ func (vm *VM) initializeStateSyncClient(lastAcceptedHeight uint64) error { } } + // Initialize the state sync client + leafMetricsNames := make(map[message.NodeType]string) + leafMetricsNames[stateLeafRequestConfig.LeafType] = stateLeafRequestConfig.MetricName + vm.Client = vmsync.NewClient(&vmsync.ClientConfig{ + StateSyncDone: vm.stateSyncDone, Chain: vm.eth, State: vm.State, - StateSyncDone: vm.stateSyncDone, Client: statesyncclient.NewClient( &statesyncclient.ClientConfig{ NetworkClient: vm.Network, Codec: vm.networkCodec, - Stats: stats.NewClientSyncerStats(), + Stats: stats.NewClientSyncerStats(leafMetricsNames), StateSyncNodeIDs: stateSyncIDs, BlockParser: vm, }, @@ -895,27 +936,6 @@ func (vm *VM) onNormalOperationsStarted() error { return nil } -// setAppRequestHandlers sets the request handlers for the VM to serve state sync -// requests. -func (vm *VM) setAppRequestHandlers() { - // Create standalone EVM TrieDB (read only) for serving leafs requests. - // We create a standalone TrieDB here, so that it has a standalone cache from the one - // used by the node when processing blocks. - evmTrieDB := triedb.NewDatabase( - vm.chaindb, - &triedb.Config{ - DBOverride: hashdb.Config{ - CleanCacheSize: vm.config.StateSyncServerTrieCache * units.MiB, - }.BackendConstructor, - }, - ) - - networkHandler := newNetworkHandler(vm.blockChain, vm.chaindb, evmTrieDB, vm.networkCodec) - vm.Network.SetRequestHandler(networkHandler) - - vm.Server = vmsync.NewServer(vm.blockChain, vm.extensionConfig.SyncSummaryProvider, vm.config.StateSyncCommitInterval) -} - func (vm *VM) WaitForEvent(ctx context.Context) (commonEng.Message, error) { vm.builderLock.Lock() builder := vm.builder diff --git a/sync/README.md b/sync/README.md index ad991ada53..b96c4bcb8b 100644 --- a/sync/README.md +++ b/sync/README.md @@ -60,6 +60,8 @@ The following steps are executed by the VM to sync its state from peers (see `st 1. Update in-memory and on-disk pointers. Steps 3 and 4 involve syncing tries. To sync trie data, the VM will send a series of `LeafRequests` to its peers. Each request specifies: +- Type of trie (`NodeType`): + - `statesync.StateTrieNode` (account trie and storage tries share the same database) - `Root` of the trie to sync, - `Start` and `End` specify a range of keys. @@ -112,4 +114,4 @@ While state sync is faster than normal bootstrapping, the process may take sever | `state-sync-skip-resume` | `bool` | set to true to avoid resuming an ongoing sync | `false` | | `state-sync-min-blocks` | `uint64` | Minimum number of blocks the chain must be ahead of local state to prefer state sync over bootstrapping | `300,000` | | `state-sync-server-trie-cache` | `int` | Size of trie cache to serve state sync data in MB. Should be set to multiples of `64`. | `64` | -| `state-sync-ids` | `string` | a comma separated list of `NodeID-` prefixed node IDs to sync data from. If not provided, peers are randomly selected. | | +| `state-sync-ids` | `string` | a comma separated list of `NodeID-` prefixed node IDs to sync data from. If not provided, peers are randomly selected. | | \ No newline at end of file diff --git a/sync/client/client_test.go b/sync/client/client_test.go index b357c87272..1554910f6c 100644 --- a/sync/client/client_test.go +++ b/sync/client/client_test.go @@ -420,7 +420,7 @@ func TestGetLeafs(t *testing.T) { largeTrieRoot, largeTrieKeys, _ := statesynctest.GenerateTrie(t, r, trieDB, 100_000, common.HashLength) smallTrieRoot, _, _ := statesynctest.GenerateTrie(t, r, trieDB, leafsLimit, common.HashLength) - handler := handlers.NewLeafsRequestHandler(trieDB, nil, message.Codec, handlerstats.NewNoopHandlerStats()) + handler := handlers.NewLeafsRequestHandler(trieDB, message.StateTrieKeyLength, nil, message.Codec, handlerstats.NewNoopHandlerStats()) client := NewClient(&ClientConfig{ NetworkClient: &mockNetwork{}, Codec: message.Codec, @@ -789,7 +789,7 @@ func TestGetLeafsRetries(t *testing.T) { trieDB := triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil) root, _, _ := statesynctest.GenerateTrie(t, r, trieDB, 100_000, common.HashLength) - handler := handlers.NewLeafsRequestHandler(trieDB, nil, message.Codec, handlerstats.NewNoopHandlerStats()) + handler := handlers.NewLeafsRequestHandler(trieDB, message.StateTrieKeyLength, nil, message.Codec, handlerstats.NewNoopHandlerStats()) mockNetClient := &mockNetwork{} const maxAttempts = 8 diff --git a/sync/client/leaf_syncer.go b/sync/client/leaf_syncer.go index 2c5e3ad491..c430b3d85c 100644 --- a/sync/client/leaf_syncer.go +++ b/sync/client/leaf_syncer.go @@ -96,10 +96,11 @@ func (c *CallbackLeafSyncer) syncTask(ctx context.Context, task LeafSyncTask) er } leafsResponse, err := c.client.GetLeafs(ctx, message.LeafsRequest{ - Root: root, - Account: task.Account(), - Start: start, - Limit: c.requestSize, + Root: root, + Account: task.Account(), + Start: start, + Limit: c.requestSize, + NodeType: message.StateTrieNode, }) if err != nil { return fmt.Errorf("%w: %w", errFailedToFetchLeafs, err) diff --git a/sync/client/mock_client.go b/sync/client/mock_client.go index a43630a175..0b18b4c6b7 100644 --- a/sync/client/mock_client.go +++ b/sync/client/mock_client.go @@ -26,7 +26,7 @@ var ( // TODO replace with gomock library type MockClient struct { codec codec.Manager - leafsHandler *handlers.LeafsRequestHandler + leafsHandler handlers.LeafRequestHandler leavesReceived int32 codesHandler *handlers.CodeRequestHandler codeReceived int32 @@ -45,13 +45,13 @@ type MockClient struct { func NewMockClient( codec codec.Manager, - leafHandler *handlers.LeafsRequestHandler, + leafsHandler handlers.LeafRequestHandler, codesHandler *handlers.CodeRequestHandler, blocksHandler *handlers.BlockRequestHandler, ) *MockClient { return &MockClient{ codec: codec, - leafsHandler: leafHandler, + leafsHandler: leafsHandler, codesHandler: codesHandler, blocksHandler: blocksHandler, } diff --git a/sync/client/stats/stats.go b/sync/client/stats/stats.go index 834dbcb193..6d146f7f2c 100644 --- a/sync/client/stats/stats.go +++ b/sync/client/stats/stats.go @@ -76,17 +76,21 @@ func (m *messageMetric) UpdateRequestLatency(duration time.Duration) { } type clientSyncerStats struct { - stateTrieLeavesMetric, - codeRequestMetric, + leafMetrics map[message.NodeType]MessageMetric + codeRequestMetric MessageMetric blockRequestMetric MessageMetric } // NewClientSyncerStats returns stats for the client syncer -func NewClientSyncerStats() ClientSyncerStats { +func NewClientSyncerStats(leafMetricNames map[message.NodeType]string) *clientSyncerStats { + leafMetrics := make(map[message.NodeType]MessageMetric, len(leafMetricNames)) + for nodeType, name := range leafMetricNames { + leafMetrics[nodeType] = NewMessageMetric(name) + } return &clientSyncerStats{ - stateTrieLeavesMetric: NewMessageMetric("sync_state_trie_leaves"), - codeRequestMetric: NewMessageMetric("sync_code"), - blockRequestMetric: NewMessageMetric("sync_blocks"), + leafMetrics: leafMetrics, + codeRequestMetric: NewMessageMetric("sync_code"), + blockRequestMetric: NewMessageMetric("sync_blocks"), } } @@ -98,7 +102,11 @@ func (c *clientSyncerStats) GetMetric(msgIntf message.Request) (MessageMetric, e case message.CodeRequest: return c.codeRequestMetric, nil case message.LeafsRequest: - return c.stateTrieLeavesMetric, nil + metric, ok := c.leafMetrics[msg.NodeType] + if !ok { + return nil, fmt.Errorf("invalid leafs request for node type: %T", msg.NodeType) + } + return metric, nil default: return nil, fmt.Errorf("attempted to get metric for invalid request with type %T", msg) } @@ -125,12 +133,3 @@ func NewNoOpStats() ClientSyncerStats { func (n noopStats) GetMetric(_ message.Request) (MessageMetric, error) { return n.noop, nil } - -// NewStats returns syncer stats if enabled or a no-op version if disabled. -func NewStats(enabled bool) ClientSyncerStats { - if enabled { - return NewClientSyncerStats() - } else { - return NewNoOpStats() - } -} diff --git a/sync/handlers/leafs_request.go b/sync/handlers/leafs_request.go index 48ddb1ea3f..495d07a320 100644 --- a/sync/handlers/leafs_request.go +++ b/sync/handlers/leafs_request.go @@ -25,6 +25,8 @@ import ( "github.com/ava-labs/subnet-evm/utils" ) +var _ LeafRequestHandler = (*leafsRequestHandler)(nil) + const ( // Maximum number of leaves to return in a message.LeafsResponse // This parameter overrides any other Limit specified @@ -39,21 +41,27 @@ const ( keyLength = common.HashLength // length of the keys of the trie to sync ) -// LeafsRequestHandler is a peer.RequestHandler for types.LeafsRequest +type LeafRequestHandler interface { + OnLeafsRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, leafsRequest message.LeafsRequest) ([]byte, error) +} + +// leafsRequestHandler is a peer.RequestHandler for types.LeafsRequest // serving requested trie data -type LeafsRequestHandler struct { +type leafsRequestHandler struct { trieDB *triedb.Database snapshotProvider SnapshotProvider codec codec.Manager stats stats.LeafsRequestHandlerStats + trieKeyLength int } -func NewLeafsRequestHandler(trieDB *triedb.Database, snapshotProvider SnapshotProvider, codec codec.Manager, syncerStats stats.LeafsRequestHandlerStats) *LeafsRequestHandler { - return &LeafsRequestHandler{ +func NewLeafsRequestHandler(trieDB *triedb.Database, trieKeyLength int, snapshotProvider SnapshotProvider, codec codec.Manager, syncerStats stats.LeafsRequestHandlerStats) *leafsRequestHandler { + return &leafsRequestHandler{ trieDB: trieDB, snapshotProvider: snapshotProvider, codec: codec, stats: syncerStats, + trieKeyLength: trieKeyLength, } } @@ -65,9 +73,9 @@ func NewLeafsRequestHandler(trieDB *triedb.Database, snapshotProvider SnapshotPr // Specified Limit in message.LeafsRequest is overridden to maxLeavesLimit if it is greater than maxLeavesLimit // Expects returned errors to be treated as FATAL // Never returns errors -// Returns nothing if the requested trie root is not found +// Returns nothing if NodeType is invalid or requested trie root is not found // Assumes ctx is active -func (lrh *LeafsRequestHandler) OnLeafsRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, leafsRequest message.LeafsRequest) ([]byte, error) { +func (lrh *leafsRequestHandler) OnLeafsRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, leafsRequest message.LeafsRequest) ([]byte, error) { startTime := time.Now() lrh.stats.IncLeafsRequest() @@ -79,9 +87,9 @@ func (lrh *LeafsRequestHandler) OnLeafsRequest(ctx context.Context, nodeID ids.N lrh.stats.IncInvalidLeafsRequest() return nil, nil } - if len(leafsRequest.Start) != 0 && len(leafsRequest.Start) != keyLength || - len(leafsRequest.End) != 0 && len(leafsRequest.End) != keyLength { - log.Debug("invalid length for leafs request range, dropping request", "startLen", len(leafsRequest.Start), "endLen", len(leafsRequest.End), "expected", keyLength) + if (len(leafsRequest.Start) != 0 && len(leafsRequest.Start) != lrh.trieKeyLength) || + (len(leafsRequest.End) != 0 && len(leafsRequest.End) != lrh.trieKeyLength) { + log.Debug("invalid length for leafs request range, dropping request", "startLen", len(leafsRequest.Start), "endLen", len(leafsRequest.End), "expected", lrh.trieKeyLength) lrh.stats.IncInvalidLeafsRequest() return nil, nil } @@ -110,7 +118,7 @@ func (lrh *LeafsRequestHandler) OnLeafsRequest(ctx context.Context, nodeID ids.N request: &leafsRequest, response: &leafsResponse, t: t, - keyLength: keyLength, + keyLength: lrh.trieKeyLength, limit: limit, stats: lrh.stats, } diff --git a/sync/handlers/leafs_request_test.go b/sync/handlers/leafs_request_test.go index ee28bf45af..1b1bc4697f 100644 --- a/sync/handlers/leafs_request_test.go +++ b/sync/handlers/leafs_request_test.go @@ -77,7 +77,7 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { } } snapshotProvider := &TestSnapshotProvider{} - leafsHandler := NewLeafsRequestHandler(trieDB, snapshotProvider, message.Codec, mockHandlerStats) + leafsHandler := NewLeafsRequestHandler(trieDB, message.StateTrieKeyLength, snapshotProvider, message.Codec, mockHandlerStats) snapConfig := snapshot.Config{ CacheSize: 64, AsyncBuild: false, diff --git a/sync/handlers/stats/stats.go b/sync/handlers/stats/stats.go index d15edd160c..e8aa14841b 100644 --- a/sync/handlers/stats/stats.go +++ b/sync/handlers/stats/stats.go @@ -166,7 +166,10 @@ func (h *handlerStats) IncSnapshotReadSuccess() { h.snapshotReadSuccess.Inc(1 func (h *handlerStats) IncSnapshotSegmentValid() { h.snapshotSegmentValid.Inc(1) } func (h *handlerStats) IncSnapshotSegmentInvalid() { h.snapshotSegmentInvalid.Inc(1) } -func NewHandlerStats(enabled bool) HandlerStats { +// GetOrRegisterHandlerStats returns a [HandlerStats] to track state sync handler metrics. +// If `enabled` is false, a no-op implementation is returned. +// if `enabled` is true, calling this multiple times will return the same registered metrics. +func GetOrRegisterHandlerStats(enabled bool) HandlerStats { if !enabled { return NewNoopHandlerStats() } diff --git a/sync/statesync/sync_test.go b/sync/statesync/sync_test.go index 63f1ed21a0..d4b24f3e6b 100644 --- a/sync/statesync/sync_test.go +++ b/sync/statesync/sync_test.go @@ -52,7 +52,7 @@ func testSync(t *testing.T, test syncTest) { } r := rand.New(rand.NewSource(1)) clientDB, serverDB, serverTrieDB, root := test.prepareForTest(t, r) - leafsRequestHandler := handlers.NewLeafsRequestHandler(serverTrieDB, nil, message.Codec, handlerstats.NewNoopHandlerStats()) + leafsRequestHandler := handlers.NewLeafsRequestHandler(serverTrieDB, message.StateTrieKeyLength, nil, message.Codec, handlerstats.NewNoopHandlerStats()) codeRequestHandler := handlers.NewCodeRequestHandler(serverDB, message.Codec, handlerstats.NewNoopHandlerStats()) mockClient := statesyncclient.NewMockClient(message.Codec, leafsRequestHandler, codeRequestHandler, nil) // Set intercept functions for the mock client @@ -527,7 +527,7 @@ func TestDifferentWaitContext(t *testing.T) { // Track requests to show sync continues after Wait returns var requestCount int64 - leafsRequestHandler := handlers.NewLeafsRequestHandler(serverTrieDB, nil, message.Codec, handlerstats.NewNoopHandlerStats()) + leafsRequestHandler := handlers.NewLeafsRequestHandler(serverTrieDB, message.StateTrieKeyLength, nil, message.Codec, handlerstats.NewNoopHandlerStats()) codeRequestHandler := handlers.NewCodeRequestHandler(serverDB, message.Codec, handlerstats.NewNoopHandlerStats()) mockClient := statesyncclient.NewMockClient(message.Codec, leafsRequestHandler, codeRequestHandler, nil)