Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Signature Request Handler #459

Merged
merged 59 commits into from
Feb 9, 2023
Merged
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
e6219a2
base warp backend
Jan 19, 2023
b054615
add signature caching
Jan 19, 2023
183a5cd
add docs
Jan 20, 2023
6043c13
error handling
Jan 20, 2023
24fe40e
pr fixes
Jan 20, 2023
dc3e441
basic signature request
Jan 20, 2023
7820ec1
hash unsigned message for key
Jan 20, 2023
a2fa3a3
Merge branch 'warp-backend' into signature-handler
Jan 20, 2023
ff4d54d
implement new Request and RequestHandler interfaces
Jan 20, 2023
4f0403e
signature handler impl without constructing one
Jan 20, 2023
c729040
fix import
Jan 20, 2023
618a0de
Merge remote-tracking branch 'origin/master' into signature-handler
Jan 23, 2023
738d19e
quick pr fixes and merge
Jan 23, 2023
8d33345
quick pr fixes and merge
Jan 23, 2023
be5a4d6
Merge remote-tracking branch 'origin/master' into warp-backend
Jan 23, 2023
e8ac670
save signature instead of whole msg
Jan 23, 2023
bd75e6d
use avaGO cache
Jan 23, 2023
5d61bb0
rename warpBackend and docs
Jan 23, 2023
7b650b6
fix nits
Jan 23, 2023
41b86bf
Merge remote-tracking branch 'origin/master' into warp-backend
Jan 23, 2023
2cdd440
Merge branch 'warp-backend' into signature-handler
Jan 23, 2023
887fd6f
Update plugin/evm/warp_backend.go
Jan 23, 2023
7c8e06f
Update plugin/evm/warp_backend.go
Jan 23, 2023
b0f2ff3
fix pr nits
Jan 23, 2023
8fc39cd
pr fixes and testing
Jan 24, 2023
76a8a0e
type check for caching
Jan 24, 2023
4dc1d23
Merge branch 'warp-backend' into signature-handler
Jan 24, 2023
c745420
handlers and request before tests
Jan 24, 2023
17d1101
fix imports
Jan 24, 2023
4582f47
signature handler with stats and test
Jan 25, 2023
6a44563
use memdb and remove extra test
Jan 25, 2023
fb76223
remove unused
Jan 26, 2023
b7bfa17
fix imports
Jan 26, 2023
3d47a86
fix imports
Jan 26, 2023
ba6ee49
Merge remote-tracking branch 'origin/warp-backend' into signature-han…
Jan 26, 2023
cffb9cc
nit
Jan 26, 2023
005d90b
update license year
Jan 27, 2023
66a8238
use require noError
Jan 27, 2023
1389af4
Merge remote-tracking branch 'origin/master' into warp-backend
Jan 30, 2023
9a99797
saving message in db and pr fixes
Jan 30, 2023
67b7e6f
Merge branch 'warp-backend' into signature-handler
Jan 31, 2023
70e1d00
create noop signature handler and refactor code handler
Jan 31, 2023
6d86d97
Merge remote-tracking branch 'origin/master' into signature-handler
Feb 2, 2023
76febba
Update sync/handlers/handler.go
Feb 2, 2023
48513b1
Merge branch 'signature-handler' of github.com:ava-labs/subnet-evm in…
Feb 2, 2023
ce25575
update backend return value
Feb 2, 2023
9d93fd1
refactor handlers to network handler
Feb 2, 2023
cfd843e
change constructor of handler stats
Feb 2, 2023
4b636e9
pr cleanups
Feb 2, 2023
62f250f
signature request test for noop signature request handler
Feb 6, 2023
3679478
remove tree changes
Feb 7, 2023
5c08d07
fix pr comments
Feb 7, 2023
56dc93b
Signature handler refactor (#495)
aaronbuchwald Feb 7, 2023
8e7ce7f
Merge remote-tracking branch 'origin/master' into signature-handler
Feb 7, 2023
1f99cb1
pr fixes
Feb 8, 2023
b9fdd83
Merge branch 'master' into signature-handler
Feb 8, 2023
943da39
resolve conflicts merge master
Feb 8, 2023
36eee41
add verify stats for nil count
Feb 8, 2023
38a64a6
Signature handler nits (#501)
darioush Feb 9, 2023
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
61 changes: 61 additions & 0 deletions handlers/handler.go
Original file line number Diff line number Diff line change
@@ -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)
}
22 changes: 22 additions & 0 deletions handlers/stats/mock_stats.go
Original file line number Diff line number Diff line change
@@ -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()
}
38 changes: 38 additions & 0 deletions handlers/stats/stats.go
Original file line number Diff line number Diff line change
@@ -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),
}
}
75 changes: 75 additions & 0 deletions handlers/warp/signature_request.go
Original file line number Diff line number Diff line change
@@ -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
}
99 changes: 99 additions & 0 deletions handlers/warp/signature_request_test.go
Original file line number Diff line number Diff line change
@@ -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/assert"
"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) {
assert.EqualValues(t, 1, mockHandlerStats.SignatureRequestCount)
assert.EqualValues(t, 1, mockHandlerStats.SignatureRequestHit)
minghinmatthewlam marked this conversation as resolved.
Show resolved Hide resolved
assert.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) {
assert.EqualValues(t, 1, mockHandlerStats.SignatureRequestCount)
assert.EqualValues(t, 1, mockHandlerStats.SignatureRequestMiss)
assert.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)
assert.NoError(t, err)

// 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
}
var response message.SignatureResponse
_, err = message.Codec.Unmarshal(responseBytes, &response)
require.NoError(t, err, "error unmarshalling SignatureResponse")

var expectedSignature [bls.SignatureLen]byte
copy(expectedSignature[:], expectedResponse)
assert.Equal(t, expectedSignature, response.Signature)
test.verifyStats(t, mockHandlerStats)
})
}
}
95 changes: 95 additions & 0 deletions handlers/warp/stats/stats.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading