-
Notifications
You must be signed in to change notification settings - Fork 674
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ACP-118: Implement p2p handler (#3434)
Signed-off-by: Joshua Kim <[email protected]> Co-authored-by: Stephen Buttolph <[email protected]>
- Loading branch information
1 parent
a7e78e4
commit 8545a4c
Showing
2 changed files
with
209 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |