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

ACP-118: Implement p2p handler #3434

Merged
merged 16 commits into from
Oct 1, 2024
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
}
Loading