Skip to content

Commit

Permalink
ACP-118: Implement p2p handler (#3434)
Browse files Browse the repository at this point in the history
Signed-off-by: Joshua Kim <[email protected]>
Co-authored-by: Stephen Buttolph <[email protected]>
  • Loading branch information
joshua-kim and StephenButtolph authored Oct 1, 2024
1 parent a7e78e4 commit 8545a4c
Show file tree
Hide file tree
Showing 2 changed files with 209 additions and 0 deletions.
94 changes: 94 additions & 0 deletions network/p2p/acp118/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package acp118

import (
"context"
"fmt"
"time"

"google.golang.org/protobuf/proto"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/network/p2p"
"github.com/ava-labs/avalanchego/proto/pb/sdk"
"github.com/ava-labs/avalanchego/snow/engine/common"
"github.com/ava-labs/avalanchego/vms/platformvm/warp"
)

var _ p2p.Handler = (*Handler)(nil)

// Verifier verifies that a warp message should be signed
type Verifier interface {
Verify(
ctx context.Context,
message *warp.UnsignedMessage,
justification []byte,
) *common.AppError
}

// NewHandler returns an instance of Handler
func NewHandler(verifier Verifier, signer warp.Signer) *Handler {
return &Handler{
verifier: verifier,
signer: signer,
}
}

// Handler signs warp messages
type Handler struct {
p2p.NoOpHandler

verifier Verifier
signer warp.Signer
}

func (h *Handler) AppRequest(
ctx context.Context,
_ ids.NodeID,
_ time.Time,
requestBytes []byte,
) ([]byte, *common.AppError) {
request := &sdk.SignatureRequest{}
if err := proto.Unmarshal(requestBytes, request); err != nil {
return nil, &common.AppError{
Code: p2p.ErrUnexpected.Code,
Message: fmt.Sprintf("failed to unmarshal request: %s", err),
}
}

msg, err := warp.ParseUnsignedMessage(request.Message)
if err != nil {
return nil, &common.AppError{
Code: p2p.ErrUnexpected.Code,
Message: fmt.Sprintf("failed to parse warp unsigned message: %s", err),
}
}

if err := h.verifier.Verify(ctx, msg, request.Justification); err != nil {
return nil, err
}

signature, err := h.signer.Sign(msg)
if err != nil {
return nil, &common.AppError{
Code: p2p.ErrUnexpected.Code,
Message: fmt.Sprintf("failed to sign message: %s", err),
}
}

response := &sdk.SignatureResponse{
Signature: signature,
}

responseBytes, err := proto.Marshal(response)
if err != nil {
return nil, &common.AppError{
Code: p2p.ErrUnexpected.Code,
Message: fmt.Sprintf("failed to marshal response: %s", err),
}
}

return responseBytes, nil
}
115 changes: 115 additions & 0 deletions network/p2p/acp118/handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package acp118

import (
"context"
"testing"

"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/network/p2p/p2ptest"
"github.com/ava-labs/avalanchego/proto/pb/sdk"
"github.com/ava-labs/avalanchego/snow/engine/common"
"github.com/ava-labs/avalanchego/utils/crypto/bls"
"github.com/ava-labs/avalanchego/utils/set"
"github.com/ava-labs/avalanchego/vms/platformvm/warp"
)

var _ Verifier = (*testVerifier)(nil)

func TestHandler(t *testing.T) {
tests := []struct {
name string
verifier Verifier
expectedErr error
expectedVerify bool
}{
{
name: "signature fails verification",
verifier: &testVerifier{Err: &common.AppError{Code: 123}},
expectedErr: &common.AppError{Code: 123},
},
{
name: "signature signed",
verifier: &testVerifier{},
expectedVerify: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require := require.New(t)

ctx := context.Background()
sk, err := bls.NewSecretKey()
require.NoError(err)
pk := bls.PublicFromSecretKey(sk)
networkID := uint32(123)
chainID := ids.GenerateTestID()
signer := warp.NewSigner(sk, networkID, chainID)
h := NewHandler(tt.verifier, signer)
clientNodeID := ids.GenerateTestNodeID()
serverNodeID := ids.GenerateTestNodeID()
c := p2ptest.NewClient(
t,
ctx,
h,
clientNodeID,
serverNodeID,
)

unsignedMessage, err := warp.NewUnsignedMessage(
networkID,
chainID,
[]byte("payload"),
)
require.NoError(err)

request := &sdk.SignatureRequest{
Message: unsignedMessage.Bytes(),
Justification: []byte("justification"),
}

requestBytes, err := proto.Marshal(request)
require.NoError(err)

done := make(chan struct{})
onResponse := func(_ context.Context, _ ids.NodeID, responseBytes []byte, appErr error) {
defer close(done)

if appErr != nil {
require.ErrorIs(tt.expectedErr, appErr)
return
}

response := &sdk.SignatureResponse{}
require.NoError(proto.Unmarshal(responseBytes, response))

signature, err := bls.SignatureFromBytes(response.Signature)
require.NoError(err)

require.Equal(tt.expectedVerify, bls.Verify(pk, signature, request.Message))
}

require.NoError(c.AppRequest(ctx, set.Of(clientNodeID), requestBytes, onResponse))
<-done
})
}
}

// The zero value of testVerifier allows signing
type testVerifier struct {
Err *common.AppError
}

func (t testVerifier) Verify(
context.Context,
*warp.UnsignedMessage,
[]byte,
) *common.AppError {
return t.Err
}

0 comments on commit 8545a4c

Please sign in to comment.