diff --git a/.github/workflows/build-macos-release.yml b/.github/workflows/build-macos-release.yml index 62856e8131da..b37d1dc01545 100644 --- a/.github/workflows/build-macos-release.yml +++ b/.github/workflows/build-macos-release.yml @@ -18,7 +18,7 @@ jobs: # This workflow contains a single job called "build" build-mac: # The type of runner that the job will run on - runs-on: macos-12 + runs-on: macos-14 permissions: id-token: write contents: read diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0851d0cf44a..4a489b3c16c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: strategy: fail-fast: false matrix: - os: [macos-12, ubuntu-20.04, ubuntu-22.04, ubuntu-24.04, windows-2022, custom-arm64-jammy, custom-arm64-noble] + os: [macos-14, ubuntu-20.04, ubuntu-22.04, ubuntu-24.04, windows-2022, custom-arm64-jammy, custom-arm64-noble] steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-go-for-project diff --git a/network/p2p/acp118/handler.go b/network/p2p/acp118/handler.go index ebceefb1fb85..fd7ac8dc4f36 100644 --- a/network/p2p/acp118/handler.go +++ b/network/p2p/acp118/handler.go @@ -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" @@ -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, } @@ -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 } @@ -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 } @@ -90,5 +112,6 @@ func (h *Handler) AppRequest( } } + h.cacher.Put(msgID, responseBytes) return responseBytes, nil } diff --git a/network/p2p/acp118/handler_test.go b/network/p2p/acp118/handler_test.go index 4ca7add318da..081ecd1f0351 100644 --- a/network/p2p/acp118/handler_test.go +++ b/network/p2p/acp118/handler_test.go @@ -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" @@ -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, + }, }, } @@ -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( @@ -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 } @@ -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 } diff --git a/network/p2p/router.go b/network/p2p/router.go index 9c020ab49356..dba1bd124d19 100644 --- a/network/p2p/router.go +++ b/network/p2p/router.go @@ -33,12 +33,6 @@ type pendingAppRequest struct { callback AppResponseCallback } -// meteredHandler emits metrics for a Handler -type meteredHandler struct { - *responder - metrics -} - type metrics struct { msgTime *prometheus.GaugeVec msgCount *prometheus.CounterVec @@ -69,7 +63,7 @@ type router struct { metrics metrics lock sync.RWMutex - handlers map[uint64]*meteredHandler + handlers map[uint64]*responder pendingAppRequests map[uint32]pendingAppRequest requestID uint32 } @@ -84,7 +78,7 @@ func newRouter( log: log, sender: sender, metrics: metrics, - handlers: make(map[uint64]*meteredHandler), + handlers: make(map[uint64]*responder), pendingAppRequests: make(map[uint32]pendingAppRequest), // invariant: sdk uses odd-numbered requestIDs requestID: 1, @@ -99,14 +93,11 @@ func (r *router) addHandler(handlerID uint64, handler Handler) error { return fmt.Errorf("failed to register handler id %d: %w", handlerID, ErrExistingAppProtocol) } - r.handlers[handlerID] = &meteredHandler{ - responder: &responder{ - Handler: handler, - handlerID: handlerID, - log: r.log, - sender: r.sender, - }, - metrics: r.metrics, + r.handlers[handlerID] = &responder{ + Handler: handler, + handlerID: handlerID, + log: r.log, + sender: r.sender, } return nil @@ -235,7 +226,7 @@ func (r *router) AppGossip(ctx context.Context, nodeID ids.NodeID, gossip []byte // - A boolean indicating that parsing succeeded. // // Invariant: Assumes [r.lock] isn't held. -func (r *router) parse(prefixedMsg []byte) ([]byte, *meteredHandler, string, bool) { +func (r *router) parse(prefixedMsg []byte) ([]byte, *responder, string, bool) { handlerID, msg, ok := ParseMessage(prefixedMsg) if !ok { return nil, nil, "", false diff --git a/tests/fixture/bootstrapmonitor/e2e/e2e_test.go b/tests/fixture/bootstrapmonitor/e2e/e2e_test.go index 2f5a319fc02d..45de49bce990 100644 --- a/tests/fixture/bootstrapmonitor/e2e/e2e_test.go +++ b/tests/fixture/bootstrapmonitor/e2e/e2e_test.go @@ -234,7 +234,10 @@ func buildImage(tc tests.TestContext, imageName string, forceNewHash bool, scrip repoRoot, err := e2e.GetRepoRootPath(repoRelativePath) require.NoError(err) - var args []string + args := []string{ + "-x", // Ensure script output to aid in debugging + filepath.Join(repoRoot, "scripts", scriptName), + } if forceNewHash { // Ensure the build results in a new image hash by preventing use of a cached final stage args = append(args, "--no-cache-filter", "execution") @@ -242,7 +245,7 @@ func buildImage(tc tests.TestContext, imageName string, forceNewHash bool, scrip cmd := exec.CommandContext( tc.DefaultContext(), - filepath.Join(repoRoot, "scripts", scriptName), + "bash", args..., ) // #nosec G204 cmd.Env = append(os.Environ(),