diff --git a/handlers/handler.go b/handlers/handler.go new file mode 100644 index 0000000000..0ab0a626fb --- /dev/null +++ b/handlers/handler.go @@ -0,0 +1,61 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package handlers + +import ( + "context" + + "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/subnet-evm/handlers/stats" + warpHandlers "github.com/ava-labs/subnet-evm/handlers/warp" + "github.com/ava-labs/subnet-evm/metrics" + "github.com/ava-labs/subnet-evm/plugin/evm/message" + "github.com/ava-labs/subnet-evm/sync/handlers" + syncHandlers "github.com/ava-labs/subnet-evm/sync/handlers" + "github.com/ava-labs/subnet-evm/trie" +) + +var _ message.RequestHandler = &networkHandler{} + +type networkHandler struct { + stateTrieLeafsRequestHandler *syncHandlers.LeafsRequestHandler + blockRequestHandler *syncHandlers.BlockRequestHandler + codeRequestHandler *syncHandlers.CodeRequestHandler + signatureRequestHandler warpHandlers.SignatureRequestHandler +} + +// NewNetworkHandler constructs the handler for serving network requests. +func NewNetworkHandler( + provider handlers.SyncDataProvider, + evmTrieDB *trie.Database, + networkCodec codec.Manager, +) message.RequestHandler { + handlerStats := stats.NewHandlerStats(metrics.Enabled) + return &networkHandler{ + // State sync handlers + stateTrieLeafsRequestHandler: syncHandlers.NewLeafsRequestHandler(evmTrieDB, provider, networkCodec, handlerStats), + blockRequestHandler: syncHandlers.NewBlockRequestHandler(provider, networkCodec, handlerStats), + codeRequestHandler: syncHandlers.NewCodeRequestHandler(evmTrieDB.DiskDB(), networkCodec, handlerStats), + + // TODO: initialize actual signature request handler when warp is ready + signatureRequestHandler: &warpHandlers.NoopSignatureRequestHandler{}, + } +} + +func (n networkHandler) HandleTrieLeafsRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, leafsRequest message.LeafsRequest) ([]byte, error) { + return n.stateTrieLeafsRequestHandler.OnLeafsRequest(ctx, nodeID, requestID, leafsRequest) +} + +func (n networkHandler) HandleBlockRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, blockRequest message.BlockRequest) ([]byte, error) { + return n.blockRequestHandler.OnBlockRequest(ctx, nodeID, requestID, blockRequest) +} + +func (n networkHandler) HandleCodeRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, codeRequest message.CodeRequest) ([]byte, error) { + return n.codeRequestHandler.OnCodeRequest(ctx, nodeID, requestID, codeRequest) +} + +func (n networkHandler) HandleSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest message.SignatureRequest) ([]byte, error) { + return n.signatureRequestHandler.OnSignatureRequest(ctx, nodeID, requestID, signatureRequest) +} diff --git a/handlers/stats/mock_stats.go b/handlers/stats/mock_stats.go new file mode 100644 index 0000000000..45ffb7aaa2 --- /dev/null +++ b/handlers/stats/mock_stats.go @@ -0,0 +1,22 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package stats + +import ( + warpStats "github.com/ava-labs/subnet-evm/handlers/warp/stats" + syncStats "github.com/ava-labs/subnet-evm/sync/handlers/stats" +) + +var _ HandlerStats = &MockHandlerStats{} + +// MockHandlerStats is mock for capturing and asserting on handler metrics in test +type MockHandlerStats struct { + syncStats.MockHandlerStats + warpStats.MockSignatureRequestHandlerStats +} + +func (m *MockHandlerStats) Reset() { + m.MockHandlerStats.Reset() + m.MockSignatureRequestHandlerStats.Reset() +} diff --git a/handlers/stats/stats.go b/handlers/stats/stats.go new file mode 100644 index 0000000000..357eb18f0b --- /dev/null +++ b/handlers/stats/stats.go @@ -0,0 +1,38 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package stats + +import ( + warpStats "github.com/ava-labs/subnet-evm/handlers/warp/stats" + syncStats "github.com/ava-labs/subnet-evm/sync/handlers/stats" +) + +var ( + _ HandlerStats = &handlerStats{} + _ HandlerStats = &MockHandlerStats{} +) + +// HandlerStats reports prometheus metrics for the network handlers +type HandlerStats interface { + warpStats.SignatureRequestHandlerStats + syncStats.HandlerStats +} + +type handlerStats struct { + // State sync handler metrics + syncStats.HandlerStats + + // Warp handler metrics + warpStats.SignatureRequestHandlerStats +} + +func NewHandlerStats(enabled bool) HandlerStats { + if !enabled { + return &MockHandlerStats{} + } + return &handlerStats{ + HandlerStats: syncStats.NewHandlerStats(enabled), + SignatureRequestHandlerStats: warpStats.NewStats(enabled), + } +} diff --git a/handlers/warp/signature_request.go b/handlers/warp/signature_request.go new file mode 100644 index 0000000000..13b6c7e66b --- /dev/null +++ b/handlers/warp/signature_request.go @@ -0,0 +1,75 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package warp + +import ( + "context" + "time" + + "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/subnet-evm/handlers/warp/stats" + "github.com/ava-labs/subnet-evm/plugin/evm/message" + "github.com/ava-labs/subnet-evm/plugin/evm/warp" + "github.com/ethereum/go-ethereum/log" +) + +// SignatureRequestHandler is a peer.RequestHandler for message.SignatureRequest +// serving requested BLS signature data +type SignatureRequestHandler interface { + OnSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest message.SignatureRequest) ([]byte, error) +} + +// signatureRequestHandler implements the SignatureRequestHandler interface +type signatureRequestHandler struct { + backend warp.WarpBackend + codec codec.Manager + stats stats.SignatureRequestHandlerStats +} + +func NewSignatureRequestHandler(backend warp.WarpBackend, codec codec.Manager, stats stats.SignatureRequestHandlerStats) SignatureRequestHandler { + return &signatureRequestHandler{ + backend: backend, + codec: codec, + stats: stats, + } +} + +// OnSignatureRequest handles message.SignatureRequest, and retrieves a warp signature for the requested message ID. +// Never returns an error +// Expects returned errors to be treated as FATAL +// Returns empty response if signature is not found +// Assumes ctx is active +func (s *signatureRequestHandler) OnSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest message.SignatureRequest) ([]byte, error) { + startTime := time.Now() + s.stats.IncSignatureRequest() + + // Always report signature request time + defer func() { + s.stats.UpdateSignatureRequestTime(time.Since(startTime)) + }() + + signature, err := s.backend.GetSignature(ctx, signatureRequest.MessageID) + if err != nil { + log.Debug("Unknown warp signature requested", "messageID", signatureRequest.MessageID) + s.stats.IncSignatureMiss() + return nil, nil + } + + s.stats.IncSignatureHit() + response := message.SignatureResponse{Signature: signature} + responseBytes, err := s.codec.Marshal(message.Version, &response) + if err != nil { + log.Warn("could not marshal SignatureResponse, dropping request", "nodeID", nodeID, "requestID", requestID, "err", err) + return nil, nil + } + + return responseBytes, nil +} + +type NoopSignatureRequestHandler struct{} + +func (s *NoopSignatureRequestHandler) OnSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest message.SignatureRequest) ([]byte, error) { + return nil, nil +} diff --git a/handlers/warp/signature_request_test.go b/handlers/warp/signature_request_test.go new file mode 100644 index 0000000000..e2b435c509 --- /dev/null +++ b/handlers/warp/signature_request_test.go @@ -0,0 +1,99 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package warp + +import ( + "context" + "testing" + "time" + + "github.com/ava-labs/avalanchego/database/memdb" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/hashing" + "github.com/ava-labs/avalanchego/vms/platformvm/teleporter" + "github.com/ava-labs/subnet-evm/handlers/stats" + "github.com/ava-labs/subnet-evm/plugin/evm/message" + "github.com/ava-labs/subnet-evm/plugin/evm/warp" + "github.com/stretchr/testify/require" +) + +func TestSignatureHandler(t *testing.T) { + database := memdb.New() + snowCtx := snow.DefaultContextTest() + blsSecretKey, err := bls.NewSecretKey() + require.NoError(t, err) + + snowCtx.TeleporterSigner = teleporter.NewSigner(blsSecretKey, snowCtx.ChainID) + warpBackend := warp.NewWarpBackend(snowCtx, database, 100) + + msg, err := teleporter.NewUnsignedMessage(snowCtx.ChainID, snowCtx.CChainID, []byte("test")) + require.NoError(t, err) + + messageID := hashing.ComputeHash256Array(msg.Bytes()) + require.NoError(t, warpBackend.AddMessage(context.Background(), msg)) + signature, err := warpBackend.GetSignature(context.Background(), messageID) + require.NoError(t, err) + unknownMessageID := ids.GenerateTestID() + + mockHandlerStats := &stats.MockHandlerStats{} + signatureRequestHandler := NewSignatureRequestHandler(warpBackend, message.Codec, mockHandlerStats) + + tests := map[string]struct { + setup func() (request message.SignatureRequest, expectedResponse []byte) + verifyStats func(t *testing.T, stats *stats.MockHandlerStats) + }{ + "normal": { + setup: func() (request message.SignatureRequest, expectedResponse []byte) { + return message.SignatureRequest{ + MessageID: messageID, + }, signature[:] + }, + verifyStats: func(t *testing.T, stats *stats.MockHandlerStats) { + require.EqualValues(t, 1, mockHandlerStats.SignatureRequestCount) + require.EqualValues(t, 1, mockHandlerStats.SignatureRequestHit) + require.EqualValues(t, 0, mockHandlerStats.SignatureRequestMiss) + require.Greater(t, mockHandlerStats.SignatureRequestDuration, time.Duration(0)) + }, + }, + "unknown": { + setup: func() (request message.SignatureRequest, expectedResponse []byte) { + return message.SignatureRequest{ + MessageID: unknownMessageID, + }, nil + }, + verifyStats: func(t *testing.T, stats *stats.MockHandlerStats) { + require.EqualValues(t, 1, mockHandlerStats.SignatureRequestCount) + require.EqualValues(t, 1, mockHandlerStats.SignatureRequestMiss) + require.EqualValues(t, 0, mockHandlerStats.SignatureRequestHit) + require.Greater(t, mockHandlerStats.SignatureRequestDuration, time.Duration(0)) + }, + }, + } + + for name, test := range tests { + // Reset stats before each test + mockHandlerStats.Reset() + + t.Run(name, func(t *testing.T) { + request, expectedResponse := test.setup() + responseBytes, err := signatureRequestHandler.OnSignatureRequest(context.Background(), ids.GenerateTestNodeID(), 1, request) + require.NoError(t, err) + + // If the expected response is empty, assert that the handler returns an empty response and return early. + if len(expectedResponse) == 0 { + test.verifyStats(t, mockHandlerStats) + require.Len(t, responseBytes, 0, "expected response to be empty") + return + } + var response message.SignatureResponse + _, err = message.Codec.Unmarshal(responseBytes, &response) + require.NoError(t, err, "error unmarshalling SignatureResponse") + + require.Equal(t, expectedResponse, response.Signature[:]) + test.verifyStats(t, mockHandlerStats) + }) + } +} diff --git a/handlers/warp/stats/stats.go b/handlers/warp/stats/stats.go new file mode 100644 index 0000000000..2cc593667e --- /dev/null +++ b/handlers/warp/stats/stats.go @@ -0,0 +1,95 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package stats + +import ( + "sync" + "time" + + "github.com/ava-labs/subnet-evm/metrics" +) + +var ( + _ SignatureRequestHandlerStats = (*handlerStats)(nil) + _ SignatureRequestHandlerStats = (*MockSignatureRequestHandlerStats)(nil) +) + +type SignatureRequestHandlerStats interface { + IncSignatureRequest() + IncSignatureHit() + IncSignatureMiss() + UpdateSignatureRequestTime(duration time.Duration) +} + +type handlerStats struct { + // SignatureRequestHandler metrics + signatureRequest metrics.Counter + signatureHit metrics.Counter + signatureMiss metrics.Counter + signatureProcessingTime metrics.Timer +} + +func NewStats(enabled bool) SignatureRequestHandlerStats { + if !enabled { + return &MockSignatureRequestHandlerStats{} + } + + return &handlerStats{ + signatureRequest: metrics.GetOrRegisterCounter("signature_request_count", nil), + signatureHit: metrics.GetOrRegisterCounter("signature_request_hit", nil), + signatureMiss: metrics.GetOrRegisterCounter("signature_request_miss", nil), + signatureProcessingTime: metrics.GetOrRegisterTimer("signature_request_duration", nil), + } +} + +func (h *handlerStats) IncSignatureRequest() { h.signatureRequest.Inc(1) } +func (h *handlerStats) IncSignatureHit() { h.signatureHit.Inc(1) } +func (h *handlerStats) IncSignatureMiss() { h.signatureMiss.Inc(1) } +func (h *handlerStats) UpdateSignatureRequestTime(duration time.Duration) { + h.signatureProcessingTime.Update(duration) +} + +// MockSignatureRequestHandlerStats is mock for capturing and asserting on handler metrics in test +type MockSignatureRequestHandlerStats struct { + lock sync.Mutex + + SignatureRequestCount, + SignatureRequestHit, + SignatureRequestMiss uint32 + SignatureRequestDuration time.Duration +} + +func (m *MockSignatureRequestHandlerStats) Reset() { + m.lock.Lock() + defer m.lock.Unlock() + + m.SignatureRequestCount = 0 + m.SignatureRequestHit = 0 + m.SignatureRequestMiss = 0 + m.SignatureRequestDuration = 0 +} + +func (m *MockSignatureRequestHandlerStats) IncSignatureRequest() { + m.lock.Lock() + defer m.lock.Unlock() + m.SignatureRequestCount++ +} + +func (m *MockSignatureRequestHandlerStats) IncSignatureHit() { + m.lock.Lock() + defer m.lock.Unlock() + m.SignatureRequestHit++ +} + +func (m *MockSignatureRequestHandlerStats) IncSignatureMiss() { + m.lock.Lock() + defer m.lock.Unlock() + m.SignatureRequestMiss++ +} + +func (m *MockSignatureRequestHandlerStats) UpdateSignatureRequestTime(duration time.Duration) { + m.lock.Lock() + defer m.lock.Unlock() + m.SignatureRequestDuration += duration +} diff --git a/plugin/evm/message/codec.go b/plugin/evm/message/codec.go index dcb1a9e5e9..8b9a84d43a 100644 --- a/plugin/evm/message/codec.go +++ b/plugin/evm/message/codec.go @@ -40,6 +40,10 @@ func init() { c.RegisterType(CodeRequest{}), c.RegisterType(CodeResponse{}), + // Warp request types + c.RegisterType(SignatureRequest{}), + c.RegisterType(SignatureResponse{}), + Codec.RegisterCodec(Version, c), ) diff --git a/plugin/evm/message/handler.go b/plugin/evm/message/handler.go index 042e432b65..659908aaee 100644 --- a/plugin/evm/message/handler.go +++ b/plugin/evm/message/handler.go @@ -36,8 +36,9 @@ func (NoopMempoolGossipHandler) HandleTxs(nodeID ids.NodeID, _ TxsGossip) error // Also see GossipHandler for implementation style. type RequestHandler interface { HandleTrieLeafsRequest(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) + HandleBlockRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, blockRequest BlockRequest) ([]byte, error) HandleCodeRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, codeRequest CodeRequest) ([]byte, error) + HandleSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest SignatureRequest) ([]byte, error) } // ResponseHandler handles response for a sent request @@ -63,6 +64,10 @@ func (NoopRequestHandler) HandleCodeRequest(ctx context.Context, nodeID ids.Node return nil, nil } +func (NoopRequestHandler) HandleSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest SignatureRequest) ([]byte, error) { + return nil, nil +} + // CrossChainRequestHandler interface handles incoming requests from another chain type CrossChainRequestHandler interface { HandleEthCallRequest(ctx context.Context, requestingchainID ids.ID, requestID uint32, ethCallRequest EthCallRequest) ([]byte, error) diff --git a/plugin/evm/message/signature_request.go b/plugin/evm/message/signature_request.go new file mode 100644 index 0000000000..3ed8b64829 --- /dev/null +++ b/plugin/evm/message/signature_request.go @@ -0,0 +1,33 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package message + +import ( + "context" + "fmt" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/bls" +) + +var _ Request = SignatureRequest{} + +// SignatureRequest is used to request a warp message's signature. +type SignatureRequest struct { + MessageID ids.ID `serialize:"true"` +} + +func (s SignatureRequest) String() string { + return fmt.Sprintf("SignatureRequest(MessageID=%s)", s.MessageID.String()) +} + +func (s SignatureRequest) Handle(ctx context.Context, nodeID ids.NodeID, requestID uint32, handler RequestHandler) ([]byte, error) { + return handler.HandleSignatureRequest(ctx, nodeID, requestID, s) +} + +// SignatureResponse is the response to a SignatureRequest. +// The response contains a BLS signature of the requested message, signed by the responding node's BLS private key. +type SignatureResponse struct { + Signature [bls.SignatureLen]byte `serialize:"true"` +} diff --git a/plugin/evm/message/signature_request_test.go b/plugin/evm/message/signature_request_test.go new file mode 100644 index 0000000000..9e4c2fd96e --- /dev/null +++ b/plugin/evm/message/signature_request_test.go @@ -0,0 +1,60 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package message + +import ( + "encoding/base64" + "encoding/hex" + "testing" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/stretchr/testify/require" +) + +// TestMarshalSignatureRequest asserts that the structure or serialization logic hasn't changed, primarily to +// ensure compatibility with the network. +func TestMarshalSignatureRequest(t *testing.T) { + messageIDBytes, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000000") + require.NoError(t, err) + messageID, err := ids.ToID(messageIDBytes) + require.NoError(t, err) + + signatureRequest := SignatureRequest{ + MessageID: messageID, + } + + base64SignatureRequest := "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + signatureRequestBytes, err := Codec.Marshal(Version, signatureRequest) + require.NoError(t, err) + require.Equal(t, base64SignatureRequest, base64.StdEncoding.EncodeToString(signatureRequestBytes)) + + var s SignatureRequest + _, err = Codec.Unmarshal(signatureRequestBytes, &s) + require.NoError(t, err) + require.Equal(t, signatureRequest.MessageID, s.MessageID) +} + +// TestMarshalSignatureResponse asserts that the structure or serialization logic hasn't changed, primarily to +// ensure compatibility with the network. +func TestMarshalSignatureResponse(t *testing.T) { + var signature [bls.SignatureLen]byte + sig, err := hex.DecodeString("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") + require.NoError(t, err, "failed to decode string to hex") + + copy(signature[:], sig) + signatureResponse := SignatureResponse{ + Signature: signature, + } + + base64SignatureResponse := "AAABI0VniavN7wEjRWeJq83vASNFZ4mrze8BI0VniavN7wEjRWeJq83vASNFZ4mrze8BI0VniavN7wEjRWeJq83vASNFZ4mrze8BI0VniavN7wEjRWeJq83vASNFZ4mrze8=" + signatureResponseBytes, err := Codec.Marshal(Version, signatureResponse) + require.NoError(t, err) + require.Equal(t, base64SignatureResponse, base64.StdEncoding.EncodeToString(signatureResponseBytes)) + + var s SignatureResponse + _, err = Codec.Unmarshal(signatureResponseBytes, &s) + require.NoError(t, err) + require.Equal(t, signatureResponse.Signature, s.Signature) +} diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 5a9e80cf8d..3d61c9bb64 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -15,6 +15,7 @@ import ( "time" avalanchegoMetrics "github.com/ava-labs/avalanchego/api/metrics" + "github.com/ava-labs/subnet-evm/handlers" "github.com/prometheus/client_golang/prometheus" "github.com/ava-labs/subnet-evm/commontype" @@ -34,8 +35,6 @@ import ( "github.com/ava-labs/subnet-evm/rpc" statesyncclient "github.com/ava-labs/subnet-evm/sync/client" "github.com/ava-labs/subnet-evm/sync/client/stats" - "github.com/ava-labs/subnet-evm/sync/handlers" - handlerstats "github.com/ava-labs/subnet-evm/sync/handlers/stats" "github.com/ava-labs/subnet-evm/trie" // Force-load tracer engine to trigger registration @@ -606,13 +605,9 @@ func (vm *VM) setAppRequestHandlers() { Cache: vm.config.StateSyncServerTrieCache, }, ) - syncRequestHandler := handlers.NewSyncHandler( - vm.blockChain, - evmTrieDB, - vm.networkCodec, - handlerstats.NewHandlerStats(metrics.Enabled), - ) - vm.Network.SetRequestHandler(syncRequestHandler) + + networkHandler := handlers.NewNetworkHandler(vm.blockChain, evmTrieDB, vm.networkCodec) + vm.Network.SetRequestHandler(networkHandler) } // setCrossChainAppRequestHandler sets the request handlers for the VM to serve cross chain diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 65f98e92db..2725596b48 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -3157,3 +3157,26 @@ func TestCrossChainMessagestoVM(t *testing.T) { require.NoError(err) require.True(calledSendCrossChainAppResponseFn, "sendCrossChainAppResponseFn was not called") } + +func TestSignatureRequestsToVM(t *testing.T) { + _, vm, _, _ := GenesisVM(t, true, genesisJSONSubnetEVM, "", "") + + defer func() { + err := vm.Shutdown(context.Background()) + require.NoError(t, err) + }() + + // Generate a SignatureRequest for an unknown message + var signatureRequest message.Request = message.SignatureRequest{ + MessageID: ids.GenerateTestID(), + } + + requestBytes, err := message.Codec.Marshal(message.Version, &signatureRequest) + require.NoError(t, err) + + // Currently with warp not being initialized we just need to make sure the NoopSignatureRequestHandler does not + // panic/crash when sent a SignatureRequest. + // TODO: We will need to update the test when warp is initialized to check for expected response. + err = vm.Network.AppRequest(context.Background(), ids.GenerateTestNodeID(), 1, time.Now().Add(60*time.Second), requestBytes) + require.NoError(t, err) +} diff --git a/plugin/evm/warp/backend.go b/plugin/evm/warp/backend.go index 7e973105ab..8cfac0b54e 100644 --- a/plugin/evm/warp/backend.go +++ b/plugin/evm/warp/backend.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/hashing" "github.com/ava-labs/avalanchego/vms/platformvm/teleporter" ) @@ -24,14 +25,14 @@ type WarpBackend interface { AddMessage(ctx context.Context, unsignedMessage *teleporter.UnsignedMessage) error // GetSignature returns the signature of the requested message hash. - GetSignature(ctx context.Context, messageHash ids.ID) ([]byte, error) + GetSignature(ctx context.Context, messageHash ids.ID) ([bls.SignatureLen]byte, error) } // warpBackend implements WarpBackend, keeps track of warp messages, and generates message signatures. type warpBackend struct { db database.Database snowCtx *snow.Context - signatureCache *cache.LRU[ids.ID, []byte] + signatureCache *cache.LRU[ids.ID, [bls.SignatureLen]byte] } // NewWarpBackend creates a new WarpBackend, and initializes the signature cache and message tracking database. @@ -39,7 +40,7 @@ func NewWarpBackend(snowCtx *snow.Context, db database.Database, signatureCacheS return &warpBackend{ db: db, snowCtx: snowCtx, - signatureCache: &cache.LRU[ids.ID, []byte]{Size: signatureCacheSize}, + signatureCache: &cache.LRU[ids.ID, [bls.SignatureLen]byte]{Size: signatureCacheSize}, } } @@ -53,35 +54,39 @@ func (w *warpBackend) AddMessage(ctx context.Context, unsignedMessage *teleporte return fmt.Errorf("failed to put warp signature in db: %w", err) } - signature, err := w.snowCtx.TeleporterSigner.Sign(unsignedMessage) + var signature [bls.SignatureLen]byte + sig, err := w.snowCtx.TeleporterSigner.Sign(unsignedMessage) if err != nil { return fmt.Errorf("failed to sign warp message: %w", err) } - w.signatureCache.Put(ids.ID(messageID), signature) + copy(signature[:], sig) + w.signatureCache.Put(messageID, signature) return nil } -func (w *warpBackend) GetSignature(ctx context.Context, messageID ids.ID) ([]byte, error) { +func (w *warpBackend) GetSignature(ctx context.Context, messageID ids.ID) ([bls.SignatureLen]byte, error) { if sig, ok := w.signatureCache.Get(messageID); ok { return sig, nil } unsignedMessageBytes, err := w.db.Get(messageID[:]) if err != nil { - return nil, fmt.Errorf("failed to get warp message %s from db: %w", messageID.String(), err) + return [bls.SignatureLen]byte{}, fmt.Errorf("failed to get warp message %s from db: %w", messageID.String(), err) } unsignedMessage, err := teleporter.ParseUnsignedMessage(unsignedMessageBytes) if err != nil { - return nil, fmt.Errorf("failed to parse unsigned message %s: %w", messageID.String(), err) + return [bls.SignatureLen]byte{}, fmt.Errorf("failed to parse unsigned message %s: %w", messageID.String(), err) } - signature, err := w.snowCtx.TeleporterSigner.Sign(unsignedMessage) + var signature [bls.SignatureLen]byte + sig, err := w.snowCtx.TeleporterSigner.Sign(unsignedMessage) if err != nil { - return nil, fmt.Errorf("failed to sign warp message: %w", err) + return [bls.SignatureLen]byte{}, fmt.Errorf("failed to sign warp message: %w", err) } + copy(signature[:], sig) w.signatureCache.Put(messageID, signature) return signature, nil } diff --git a/plugin/evm/warp/backend_test.go b/plugin/evm/warp/backend_test.go index de38246d30..c4449245f5 100644 --- a/plugin/evm/warp/backend_test.go +++ b/plugin/evm/warp/backend_test.go @@ -44,7 +44,7 @@ func TestAddAndGetValidMessage(t *testing.T) { expectedSig, err := snowCtx.TeleporterSigner.Sign(unsignedMsg) require.NoError(t, err) - require.Equal(t, expectedSig, signature) + require.Equal(t, expectedSig, signature[:]) } func TestAddAndGetUnknownMessage(t *testing.T) { @@ -84,5 +84,5 @@ func TestZeroSizedCache(t *testing.T) { expectedSig, err := snowCtx.TeleporterSigner.Sign(unsignedMsg) require.NoError(t, err) - require.Equal(t, expectedSig, signature) + require.Equal(t, expectedSig, signature[:]) } diff --git a/sync/handlers/code_request_test.go b/sync/handlers/code_request_test.go index cbf6b277bb..a7094742e1 100644 --- a/sync/handlers/code_request_test.go +++ b/sync/handlers/code_request_test.go @@ -94,7 +94,7 @@ func TestCodeRequestHandler(t *testing.T) { responseBytes, err := codeRequestHandler.OnCodeRequest(context.Background(), ids.GenerateTestNodeID(), 1, request) assert.NoError(t, err) - // If the expected resposne is empty, assert that the handler returns an empty response and return early. + // If the expected response is empty, assert that the handler returns an empty response and return early. if len(expectedResponse) == 0 { assert.Len(t, responseBytes, 0, "expected response to be empty") return diff --git a/sync/handlers/handler.go b/sync/handlers/handler.go index 5c0389cf40..867941aa83 100644 --- a/sync/handlers/handler.go +++ b/sync/handlers/handler.go @@ -4,20 +4,11 @@ package handlers import ( - "context" - - "github.com/ava-labs/avalanchego/codec" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/subnet-evm/core/state/snapshot" "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/plugin/evm/message" - "github.com/ava-labs/subnet-evm/sync/handlers/stats" - "github.com/ava-labs/subnet-evm/trie" "github.com/ethereum/go-ethereum/common" ) -var _ message.RequestHandler = &syncHandler{} - type BlockProvider interface { GetBlock(common.Hash, uint64) *types.Block } @@ -30,35 +21,3 @@ type SyncDataProvider interface { BlockProvider SnapshotProvider } - -type syncHandler struct { - stateTrieLeafsRequestHandler *LeafsRequestHandler - blockRequestHandler *BlockRequestHandler - codeRequestHandler *CodeRequestHandler -} - -// NewSyncHandler constructs the handler for serving state sync. -func NewSyncHandler( - provider SyncDataProvider, - evmTrieDB *trie.Database, - networkCodec codec.Manager, - stats stats.HandlerStats, -) message.RequestHandler { - return &syncHandler{ - stateTrieLeafsRequestHandler: NewLeafsRequestHandler(evmTrieDB, provider, networkCodec, stats), - blockRequestHandler: NewBlockRequestHandler(provider, networkCodec, stats), - codeRequestHandler: NewCodeRequestHandler(evmTrieDB.DiskDB(), networkCodec, stats), - } -} - -func (s *syncHandler) HandleTrieLeafsRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, leafsRequest message.LeafsRequest) ([]byte, error) { - return s.stateTrieLeafsRequestHandler.OnLeafsRequest(ctx, nodeID, requestID, leafsRequest) -} - -func (s *syncHandler) HandleBlockRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, blockRequest message.BlockRequest) ([]byte, error) { - return s.blockRequestHandler.OnBlockRequest(ctx, nodeID, requestID, blockRequest) -} - -func (s *syncHandler) HandleCodeRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, codeRequest message.CodeRequest) ([]byte, error) { - return s.codeRequestHandler.OnCodeRequest(ctx, nodeID, requestID, codeRequest) -}