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/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
StephenButtolph marked this conversation as resolved.
Show resolved Hide resolved

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 defines whether a warp message payload should be verified
StephenButtolph marked this conversation as resolved.
Show resolved Hide resolved
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,
StephenButtolph marked this conversation as resolved.
Show resolved Hide resolved
Message: fmt.Sprintf("failed to initialize 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,
StephenButtolph marked this conversation as resolved.
Show resolved Hide resolved
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/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: int32(123)}},
expectedErr: &common.AppError{Code: int32(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 verifies
type testVerifier struct {
Err *common.AppError
}

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