Skip to content

Commit

Permalink
Add ACP-118 caching support (#3451)
Browse files Browse the repository at this point in the history
  • Loading branch information
StephenButtolph authored Oct 9, 2024
1 parent d2e9d12 commit ab58a80
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 20 deletions.
23 changes: 23 additions & 0 deletions network/p2p/acp118/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"google.golang.org/protobuf/proto"

"github.com/ava-labs/avalanchego/cache"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/network/p2p"
"github.com/ava-labs/avalanchego/proto/pb/sdk"
Expand All @@ -30,7 +31,22 @@ type Verifier interface {

// NewHandler returns an instance of Handler
func NewHandler(verifier Verifier, signer warp.Signer) *Handler {
return NewCachedHandler(
&cache.Empty[ids.ID, []byte]{},
verifier,
signer,
)
}

// NewCachedHandler returns an instance of Handler that caches successful
// requests.
func NewCachedHandler(
cacher cache.Cacher[ids.ID, []byte],
verifier Verifier,
signer warp.Signer,
) *Handler {
return &Handler{
cacher: cacher,
verifier: verifier,
signer: signer,
}
Expand All @@ -40,6 +56,7 @@ func NewHandler(verifier Verifier, signer warp.Signer) *Handler {
type Handler struct {
p2p.NoOpHandler

cacher cache.Cacher[ids.ID, []byte]
verifier Verifier
signer warp.Signer
}
Expand All @@ -66,6 +83,11 @@ func (h *Handler) AppRequest(
}
}

msgID := msg.ID()
if responseBytes, ok := h.cacher.Get(msgID); ok {
return responseBytes, nil
}

if err := h.verifier.Verify(ctx, msg, request.Justification); err != nil {
return nil, err
}
Expand All @@ -90,5 +112,6 @@ func (h *Handler) AppRequest(
}
}

h.cacher.Put(msgID, responseBytes)
return responseBytes, nil
}
79 changes: 59 additions & 20 deletions network/p2p/acp118/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"

"github.com/ava-labs/avalanchego/cache"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/network/p2p/p2ptest"
"github.com/ava-labs/avalanchego/proto/pb/sdk"
Expand All @@ -23,20 +24,46 @@ var _ Verifier = (*testVerifier)(nil)

func TestHandler(t *testing.T) {
tests := []struct {
name string
verifier Verifier
expectedErr error
expectedVerify bool
name string
cacher cache.Cacher[ids.ID, []byte]
verifier Verifier
expectedErrs []error
}{
{
name: "signature fails verification",
verifier: &testVerifier{Err: &common.AppError{Code: 123}},
expectedErr: &common.AppError{Code: 123},
name: "signature fails verification",
cacher: &cache.Empty[ids.ID, []byte]{},
verifier: &testVerifier{
Errs: []*common.AppError{
{Code: 123},
},
},
expectedErrs: []error{
&common.AppError{Code: 123},
},
},
{
name: "signature signed",
verifier: &testVerifier{},
expectedVerify: true,
name: "signature signed",
cacher: &cache.Empty[ids.ID, []byte]{},
verifier: &testVerifier{},
expectedErrs: []error{
nil,
},
},
{
name: "signature is cached",
cacher: &cache.LRU[ids.ID, []byte]{
Size: 1,
},
verifier: &testVerifier{
Errs: []*common.AppError{
nil,
{Code: 123}, // The valid response should be cached
},
},
expectedErrs: []error{
nil,
nil,
},
},
}

Expand All @@ -51,7 +78,7 @@ func TestHandler(t *testing.T) {
networkID := uint32(123)
chainID := ids.GenerateTestID()
signer := warp.NewSigner(sk, networkID, chainID)
h := NewHandler(tt.verifier, signer)
h := NewCachedHandler(tt.cacher, tt.verifier, signer)
clientNodeID := ids.GenerateTestNodeID()
serverNodeID := ids.GenerateTestNodeID()
c := p2ptest.NewClient(
Expand All @@ -77,12 +104,17 @@ func TestHandler(t *testing.T) {
requestBytes, err := proto.Marshal(request)
require.NoError(err)

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

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

Expand All @@ -92,24 +124,31 @@ func TestHandler(t *testing.T) {
signature, err := bls.SignatureFromBytes(response.Signature)
require.NoError(err)

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

require.NoError(c.AppRequest(ctx, set.Of(clientNodeID), requestBytes, onResponse))
<-done
for _, expectedErr = range tt.expectedErrs {
require.NoError(c.AppRequest(ctx, set.Of(clientNodeID), requestBytes, onResponse))
<-handled
}
})
}
}

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

func (t testVerifier) Verify(
func (t *testVerifier) Verify(
context.Context,
*warp.UnsignedMessage,
[]byte,
) *common.AppError {
return t.Err
if len(t.Errs) == 0 {
return nil
}
err := t.Errs[0]
t.Errs = t.Errs[1:]
return err
}

0 comments on commit ab58a80

Please sign in to comment.