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
95 changes: 95 additions & 0 deletions network/acp118/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// 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)

// Attestor defines whether to a warp message payload should be attested to
type Attestor interface {
StephenButtolph marked this conversation as resolved.
Show resolved Hide resolved
Attest(message *warp.UnsignedMessage, justification []byte) (bool, *common.AppError)
StephenButtolph marked this conversation as resolved.
Show resolved Hide resolved
StephenButtolph marked this conversation as resolved.
Show resolved Hide resolved
}

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

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

attestor Attestor
signer warp.Signer
}

func (h *Handler) AppRequest(
_ 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),
}
}

ok, appErr := h.attestor.Attest(msg, request.Justification)
if appErr != nil {
return nil, appErr
}

if !ok {
return nil, p2p.ErrAttestFailed
}

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
}
118 changes: 118 additions & 0 deletions network/acp118/handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// 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"
"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 _ Attestor = (*testAttestor)(nil)

func TestHandler(t *testing.T) {
tests := []struct {
name string
attestor Attestor
expectedErr error
expectedVerify bool
}{
{
name: "signature fails attestation",
attestor: &testAttestor{Err: &common.AppError{Code: int32(123)}},
expectedErr: &common.AppError{Code: int32(123)},
},
{
name: "signature not attested",
attestor: &testAttestor{CantAttest: true},
expectedErr: p2p.ErrAttestFailed,
},
{
name: "signature attested",
attestor: &testAttestor{},
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.attestor, 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(serverNodeID), requestBytes, onResponse))
<-done
})
}
}

// The zero value of testAttestor attests
type testAttestor struct {
CantAttest bool
Err *common.AppError
}

func (t testAttestor) Attest(*warp.UnsignedMessage, []byte) (bool, *common.AppError) {
return !t.CantAttest, t.Err
}
6 changes: 6 additions & 0 deletions network/p2p/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,10 @@ var (
Code: -4,
Message: "throttled",
}
// ErrAttestFailed should be used to indicate that a request failed
// to be signed due to the peer being unable to attest the message
ErrAttestFailed = &common.AppError{
Code: -5,
Message: "failed attestation",
}
)
Loading