Skip to content

Commit

Permalink
warp: add block signature handler (#962)
Browse files Browse the repository at this point in the history
* warp: add block signature handler

* warp/aggregator: handle block/message types in aggregator

* warp/aggregator: refactor aggregator

* tests/warp: update e2e tests

* Fix lint

* cleanup

* add logs to e2e test

* Fix bug

* address comments

* tests/warp: add const for num nodes per subnet
  • Loading branch information
aaronbuchwald committed Oct 23, 2023
1 parent 46a0d3c commit 4d97c64
Show file tree
Hide file tree
Showing 21 changed files with 798 additions and 389 deletions.
18 changes: 0 additions & 18 deletions plugin/evm/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,17 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"

"github.com/ava-labs/avalanchego/vms/platformvm/warp/payload"
"github.com/ava-labs/subnet-evm/core"
"github.com/ava-labs/subnet-evm/core/rawdb"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/precompile/precompileconfig"
"github.com/ava-labs/subnet-evm/predicate"
"github.com/ava-labs/subnet-evm/x/warp"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/snow/choices"
"github.com/ava-labs/avalanchego/snow/consensus/snowman"
"github.com/ava-labs/avalanchego/snow/engine/snowman/block"
avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp"
)

var (
Expand Down Expand Up @@ -130,21 +127,6 @@ func (b *Block) handlePrecompileAccept(rules *params.Rules, sharedMemoryWriter *
}
}

// If Warp is enabled, add the block hash as an unsigned message to the warp backend.
if rules.IsPrecompileEnabled(warp.ContractAddress) {
blockHashPayload, err := payload.NewHash(ids.ID(b.ethBlock.Hash()))
if err != nil {
return fmt.Errorf("failed to create block hash payload: %w", err)
}
unsignedMessage, err := avalancheWarp.NewUnsignedMessage(b.vm.ctx.NetworkID, b.vm.ctx.ChainID, blockHashPayload.Bytes())
if err != nil {
return fmt.Errorf("failed to create unsigned message for block hash payload: %w", err)
}
if err := b.vm.warpBackend.AddMessage(unsignedMessage); err != nil {
return fmt.Errorf("failed to add block hash payload unsigned message: %w", err)
}
}

return nil
}

Expand Down
3 changes: 2 additions & 1 deletion plugin/evm/message/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ func init() {
c.RegisterType(CodeResponse{}),

// Warp request types
c.RegisterType(SignatureRequest{}),
c.RegisterType(MessageSignatureRequest{}),
c.RegisterType(BlockSignatureRequest{}),
c.RegisterType(SignatureResponse{}),

Codec.RegisterCodec(Version, c),
Expand Down
9 changes: 7 additions & 2 deletions plugin/evm/message/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ 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, 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)
HandleMessageSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest MessageSignatureRequest) ([]byte, error)
HandleBlockSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest BlockSignatureRequest) ([]byte, error)
}

// ResponseHandler handles response for a sent request
Expand All @@ -64,7 +65,11 @@ 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) {
func (NoopRequestHandler) HandleMessageSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest MessageSignatureRequest) ([]byte, error) {
return nil, nil
}

func (NoopRequestHandler) HandleBlockSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest BlockSignatureRequest) ([]byte, error) {
return nil, nil
}

Expand Down
32 changes: 24 additions & 8 deletions plugin/evm/message/signature_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,38 @@ import (
"github.com/ava-labs/avalanchego/utils/crypto/bls"
)

var _ Request = SignatureRequest{}
var (
_ Request = MessageSignatureRequest{}
_ Request = BlockSignatureRequest{}
)

// SignatureRequest is used to request a warp message's signature.
type SignatureRequest struct {
// MessageSignatureRequest is used to request a warp message's signature.
type MessageSignatureRequest struct {
MessageID ids.ID `serialize:"true"`
}

func (s SignatureRequest) String() string {
return fmt.Sprintf("SignatureRequest(MessageID=%s)", s.MessageID.String())
func (s MessageSignatureRequest) String() string {
return fmt.Sprintf("MessageSignatureRequest(MessageID=%s)", s.MessageID.String())
}

func (s MessageSignatureRequest) Handle(ctx context.Context, nodeID ids.NodeID, requestID uint32, handler RequestHandler) ([]byte, error) {
return handler.HandleMessageSignatureRequest(ctx, nodeID, requestID, s)
}

// BlockSignatureRequest is used to request a warp message's signature.
type BlockSignatureRequest struct {
BlockID ids.ID `serialize:"true"`
}

func (s BlockSignatureRequest) String() string {
return fmt.Sprintf("BlockSignatureRequest(BlockID=%s)", s.BlockID.String())
}

func (s SignatureRequest) Handle(ctx context.Context, nodeID ids.NodeID, requestID uint32, handler RequestHandler) ([]byte, error) {
return handler.HandleSignatureRequest(ctx, nodeID, requestID, s)
func (s BlockSignatureRequest) Handle(ctx context.Context, nodeID ids.NodeID, requestID uint32, handler RequestHandler) ([]byte, error) {
return handler.HandleBlockSignatureRequest(ctx, nodeID, requestID, s)
}

// SignatureResponse is the response to a SignatureRequest.
// SignatureResponse is the response to a BlockSignatureRequest or MessageSignatureRequest.
// 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"`
Expand Down
33 changes: 23 additions & 10 deletions plugin/evm/message/signature_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,40 @@ import (
"github.com/stretchr/testify/require"
)

// TestMarshalSignatureRequest asserts that the structure or serialization logic hasn't changed, primarily to
// TestMarshalMessageSignatureRequest 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")
func TestMarshalMessageSignatureRequest(t *testing.T) {
signatureRequest := MessageSignatureRequest{
MessageID: ids.ID{68, 79, 70, 65, 72, 73, 64, 107},
}

base64MessageSignatureRequest := "AABET0ZBSElAawAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
signatureRequestBytes, err := Codec.Marshal(Version, signatureRequest)
require.NoError(t, err)
messageID, err := ids.ToID(messageIDBytes)
require.Equal(t, base64MessageSignatureRequest, base64.StdEncoding.EncodeToString(signatureRequestBytes))

var s MessageSignatureRequest
_, err = Codec.Unmarshal(signatureRequestBytes, &s)
require.NoError(t, err)
require.Equal(t, signatureRequest.MessageID, s.MessageID)
}

signatureRequest := SignatureRequest{
MessageID: messageID,
// TestMarshalBlockSignatureRequest asserts that the structure or serialization logic hasn't changed, primarily to
// ensure compatibility with the network.
func TestMarshalBlockSignatureRequest(t *testing.T) {
signatureRequest := BlockSignatureRequest{
BlockID: ids.ID{68, 79, 70, 65, 72, 73, 64, 107},
}

base64SignatureRequest := "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
base64BlockSignatureRequest := "AABET0ZBSElAawAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
signatureRequestBytes, err := Codec.Marshal(Version, signatureRequest)
require.NoError(t, err)
require.Equal(t, base64SignatureRequest, base64.StdEncoding.EncodeToString(signatureRequestBytes))
require.Equal(t, base64BlockSignatureRequest, base64.StdEncoding.EncodeToString(signatureRequestBytes))

var s SignatureRequest
var s BlockSignatureRequest
_, err = Codec.Unmarshal(signatureRequestBytes, &s)
require.NoError(t, err)
require.Equal(t, signatureRequest.MessageID, s.MessageID)
require.Equal(t, signatureRequest.BlockID, s.BlockID)
}

// TestMarshalSignatureResponse asserts that the structure or serialization logic hasn't changed, primarily to
Expand Down
8 changes: 6 additions & 2 deletions plugin/evm/network_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ func (n networkHandler) HandleCodeRequest(ctx context.Context, nodeID ids.NodeID
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)
func (n networkHandler) HandleMessageSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, messageSignatureRequest message.MessageSignatureRequest) ([]byte, error) {
return n.signatureRequestHandler.OnMessageSignatureRequest(ctx, nodeID, requestID, messageSignatureRequest)
}

func (n networkHandler) HandleBlockSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, blockSignatureRequest message.BlockSignatureRequest) ([]byte, error) {
return n.signatureRequestHandler.OnBlockSignatureRequest(ctx, nodeID, requestID, blockSignatureRequest)
}
7 changes: 2 additions & 5 deletions plugin/evm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ import (
"github.com/ava-labs/subnet-evm/sync/client/stats"
"github.com/ava-labs/subnet-evm/trie"
"github.com/ava-labs/subnet-evm/warp"
"github.com/ava-labs/subnet-evm/warp/aggregator"
warpValidators "github.com/ava-labs/subnet-evm/warp/validators"

// Force-load tracer engine to trigger registration
Expand Down Expand Up @@ -472,7 +471,7 @@ func (vm *VM) Initialize(
vm.client = peer.NewNetworkClient(vm.Network)

// initialize warp backend
vm.warpBackend = warp.NewBackend(vm.ctx.WarpSigner, vm.warpDB, warpSignatureCacheSize)
vm.warpBackend = warp.NewBackend(vm.ctx.NetworkID, vm.ctx.ChainID, vm.ctx.WarpSigner, vm, vm.warpDB, warpSignatureCacheSize)

// clear warpdb on initialization if config enabled
if vm.config.PruneWarpDB {
Expand Down Expand Up @@ -936,9 +935,7 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]http.Handler, error) {

if vm.config.WarpAPIEnabled {
validatorsState := warpValidators.NewState(vm.ctx)
signatureGetter := &aggregator.NetworkSignatureGetter{Client: vm.client}
warpAggregator := aggregator.New(vm.ctx.SubnetID, validatorsState, signatureGetter)
if err := handler.RegisterName("warp", warp.NewAPI(vm.warpBackend, warpAggregator)); err != nil {
if err := handler.RegisterName("warp", warp.NewAPI(vm.ctx.NetworkID, vm.ctx.SubnetID, vm.ctx.ChainID, validatorsState, vm.warpBackend, vm.client)); err != nil {
return nil, err
}
enabledAPIs = append(enabledAPIs, "warp")
Expand Down
62 changes: 59 additions & 3 deletions plugin/evm/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3273,7 +3273,7 @@ func TestCrossChainMessagestoVM(t *testing.T) {
require.True(calledSendCrossChainAppResponseFn, "sendCrossChainAppResponseFn was not called")
}

func TestSignatureRequestsToVM(t *testing.T) {
func TestMessageSignatureRequestsToVM(t *testing.T) {
_, vm, _, appSender := GenesisVM(t, true, genesisJSONSubnetEVM, "", "")

defer func() {
Expand All @@ -3288,7 +3288,7 @@ func TestSignatureRequestsToVM(t *testing.T) {
// Add the known message and get its signature to confirm.
err = vm.warpBackend.AddMessage(warpMessage)
require.NoError(t, err)
signature, err := vm.warpBackend.GetSignature(warpMessage.ID())
signature, err := vm.warpBackend.GetMessageSignature(warpMessage.ID())
require.NoError(t, err)

tests := map[string]struct {
Expand Down Expand Up @@ -3317,7 +3317,7 @@ func TestSignatureRequestsToVM(t *testing.T) {
return nil
}
t.Run(name, func(t *testing.T) {
var signatureRequest message.Request = message.SignatureRequest{
var signatureRequest message.Request = message.MessageSignatureRequest{
MessageID: test.messageID,
}

Expand All @@ -3332,3 +3332,59 @@ func TestSignatureRequestsToVM(t *testing.T) {
})
}
}

func TestBlockSignatureRequestsToVM(t *testing.T) {
_, vm, _, appSender := GenesisVM(t, true, genesisJSONSubnetEVM, "", "")

defer func() {
err := vm.Shutdown(context.Background())
require.NoError(t, err)
}()

lastAcceptedID, err := vm.LastAccepted(context.Background())
require.NoError(t, err)

signature, err := vm.warpBackend.GetBlockSignature(lastAcceptedID)
require.NoError(t, err)

tests := map[string]struct {
blockID ids.ID
expectedResponse [bls.SignatureLen]byte
}{
"known": {
blockID: lastAcceptedID,
expectedResponse: signature,
},
"unknown": {
blockID: ids.GenerateTestID(),
expectedResponse: [bls.SignatureLen]byte{},
},
}

for name, test := range tests {
calledSendAppResponseFn := false
appSender.SendAppResponseF = func(ctx context.Context, nodeID ids.NodeID, requestID uint32, responseBytes []byte) error {
calledSendAppResponseFn = true
var response message.SignatureResponse
_, err := message.Codec.Unmarshal(responseBytes, &response)
require.NoError(t, err)
require.Equal(t, test.expectedResponse, response.Signature)

return nil
}
t.Run(name, func(t *testing.T) {
var signatureRequest message.Request = message.BlockSignatureRequest{
BlockID: test.blockID,
}

requestBytes, err := message.Codec.Marshal(message.Version, &signatureRequest)
require.NoError(t, err)

// Send the app request and make sure we called SendAppResponseFn
deadline := time.Now().Add(60 * time.Second)
err = vm.Network.AppRequest(context.Background(), ids.GenerateTestNodeID(), 1, deadline, requestBytes)
require.NoError(t, err)
require.True(t, calledSendAppResponseFn)
})
}
}
24 changes: 21 additions & 3 deletions plugin/evm/vm_warp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,17 @@ func TestSendWarpMessage(t *testing.T) {
unsignedMessageID := unsignedMessage.ID()

// Verify the signature cannot be fetched before the block is accepted
_, err = vm.warpBackend.GetSignature(unsignedMessageID)
_, err = vm.warpBackend.GetMessageSignature(unsignedMessageID)
require.Error(err)
_, err = vm.warpBackend.GetBlockSignature(blk.ID())
require.Error(err)

require.NoError(vm.SetPreference(context.Background(), blk.ID()))
require.NoError(blk.Accept(context.Background()))
vm.blockChain.DrainAcceptorQueue()
rawSignatureBytes, err := vm.warpBackend.GetSignature(unsignedMessageID)

// Verify the message signature after accepting the block.
rawSignatureBytes, err := vm.warpBackend.GetMessageSignature(unsignedMessageID)
require.NoError(err)
blsSignature, err := bls.SignatureFromBytes(rawSignatureBytes[:])
require.NoError(err)
Expand All @@ -132,7 +136,21 @@ func TestSendWarpMessage(t *testing.T) {
require.Fail("Failed to read accepted logs from subscription")
}

// Verify the produced signature is valid
// Verify the produced message signature is valid
require.True(bls.Verify(vm.ctx.PublicKey, blsSignature, unsignedMessage.Bytes()))

// Verify the blockID will now be signed by the backend and produces a valid signature.
rawSignatureBytes, err = vm.warpBackend.GetBlockSignature(blk.ID())
require.NoError(err)
blsSignature, err = bls.SignatureFromBytes(rawSignatureBytes[:])
require.NoError(err)

blockHashPayload, err := payload.NewHash(blk.ID())
require.NoError(err)
unsignedMessage, err = avalancheWarp.NewUnsignedMessage(vm.ctx.NetworkID, vm.ctx.ChainID, blockHashPayload.Bytes())
require.NoError(err)

// Verify the produced message signature is valid
require.True(bls.Verify(vm.ctx.PublicKey, blsSignature, unsignedMessage.Bytes()))
}

Expand Down
Loading

0 comments on commit 4d97c64

Please sign in to comment.