diff --git a/core/capabilities/compute/test/fetch/cmd/testdata/output.wasm.br b/core/capabilities/compute/test/fetch/cmd/testdata/output.wasm.br index 5050a7f3587..552bd6338cc 100644 Binary files a/core/capabilities/compute/test/fetch/cmd/testdata/output.wasm.br and b/core/capabilities/compute/test/fetch/cmd/testdata/output.wasm.br differ diff --git a/core/capabilities/compute/test/simple/cmd/testdata/output.wasm.br b/core/capabilities/compute/test/simple/cmd/testdata/output.wasm.br index 1ac1a0f4667..2efc1e4d799 100644 Binary files a/core/capabilities/compute/test/simple/cmd/testdata/output.wasm.br and b/core/capabilities/compute/test/simple/cmd/testdata/output.wasm.br differ diff --git a/core/capabilities/vault/vaultutils/json_test.go b/core/capabilities/vault/vaultutils/json_test.go index dabe12ebca9..477a4e9c774 100644 --- a/core/capabilities/vault/vaultutils/json_test.go +++ b/core/capabilities/vault/vaultutils/json_test.go @@ -51,6 +51,7 @@ func TestToCanonicalJSON_OmitsEmptyFields(t *testing.T) { t.Parallel() msg := &vaultcommon.CreateSecretsResponse{ + RequestId: "owner::req-1", Responses: []*vaultcommon.CreateSecretResponse{ { Id: id, @@ -63,12 +64,14 @@ func TestToCanonicalJSON_OmitsEmptyFields(t *testing.T) { withEmptyFields, err := ToCanonicalJSON(msg, false) require.NoError(t, err) assert.JSONEq(t, `{ + "requestId":"owner::req-1", "responses":[{"id":{"owner":"owner","namespace":"main","key":"secret1"},"success":true,"error":""}] }`, string(withEmptyFields)) canonicalJSON, err := ToCanonicalJSON(msg, true) require.NoError(t, err) assert.JSONEq(t, `{ + "requestId":"owner::req-1", "responses":[{"id":{"owner":"owner","namespace":"main","key":"secret1"},"success":true}] }`, string(canonicalJSON)) assert.NotEqual(t, string(withEmptyFields), string(canonicalJSON)) @@ -83,7 +86,7 @@ func TestToCanonicalJSON_OmitsEmptyFields(t *testing.T) { withEmptyFields, err := ToCanonicalJSON(msg, false) require.NoError(t, err) - assert.JSONEq(t, `{"responses":[]}`, string(withEmptyFields)) + assert.JSONEq(t, `{"responses":[],"requestId":""}`, string(withEmptyFields)) canonicalJSON, err := ToCanonicalJSON(msg, true) require.NoError(t, err) @@ -95,16 +98,17 @@ func TestToCanonicalJSON_OmitsEmptyFields(t *testing.T) { t.Parallel() msg := &vaultcommon.CreateSecretsResponse{ + RequestId: "owner::req-1", Responses: []*vaultcommon.CreateSecretResponse{}, } withEmptyFields, err := ToCanonicalJSON(msg, false) require.NoError(t, err) - assert.JSONEq(t, `{"responses":[]}`, string(withEmptyFields)) + assert.JSONEq(t, `{"responses":[],"requestId":"owner::req-1"}`, string(withEmptyFields)) canonicalJSON, err := ToCanonicalJSON(msg, true) require.NoError(t, err) - assert.JSONEq(t, `{}`, string(canonicalJSON)) + assert.JSONEq(t, `{"requestId":"owner::req-1"}`, string(canonicalJSON)) assert.NotEqual(t, string(withEmptyFields), string(canonicalJSON)) }) } diff --git a/core/capabilities/vault/vaultutils/signed_payload_request_id.go b/core/capabilities/vault/vaultutils/signed_payload_request_id.go new file mode 100644 index 00000000000..4bdbd4e0a90 --- /dev/null +++ b/core/capabilities/vault/vaultutils/signed_payload_request_id.go @@ -0,0 +1,49 @@ +package vaultutils + +import ( + "encoding/json" + "errors" + "fmt" + + "google.golang.org/protobuf/encoding/protojson" + + vaultcommon "github.com/smartcontractkit/chainlink-common/pkg/capabilities/actions/vault" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/vault/vaulttypes" +) + +// SignedPayloadRequestID extracts the requestId field from a vault OCR signed response payload. +func SignedPayloadRequestID(method string, payload json.RawMessage) (string, error) { + if len(payload) == 0 { + return "", errors.New("signed payload is empty") + } + + unmarshal := protojson.UnmarshalOptions{DiscardUnknown: true} + switch method { + case vaulttypes.MethodSecretsCreate: + resp := &vaultcommon.CreateSecretsResponse{} + if err := unmarshal.Unmarshal(payload, resp); err != nil { + return "", fmt.Errorf("failed to unmarshal signed create secrets payload: %w", err) + } + return resp.RequestId, nil + case vaulttypes.MethodSecretsUpdate: + resp := &vaultcommon.UpdateSecretsResponse{} + if err := unmarshal.Unmarshal(payload, resp); err != nil { + return "", fmt.Errorf("failed to unmarshal signed update secrets payload: %w", err) + } + return resp.RequestId, nil + case vaulttypes.MethodSecretsDelete: + resp := &vaultcommon.DeleteSecretsResponse{} + if err := unmarshal.Unmarshal(payload, resp); err != nil { + return "", fmt.Errorf("failed to unmarshal signed delete secrets payload: %w", err) + } + return resp.RequestId, nil + case vaulttypes.MethodSecretsList: + resp := &vaultcommon.ListSecretIdentifiersResponse{} + if err := unmarshal.Unmarshal(payload, resp); err != nil { + return "", fmt.Errorf("failed to unmarshal signed list secret identifiers payload: %w", err) + } + return resp.RequestId, nil + default: + return "", fmt.Errorf("unsupported method for signed payload request id validation: %s", method) + } +} diff --git a/core/capabilities/vault/vaultutils/signed_payload_request_id_test.go b/core/capabilities/vault/vaultutils/signed_payload_request_id_test.go new file mode 100644 index 00000000000..b4dfc0dc4c0 --- /dev/null +++ b/core/capabilities/vault/vaultutils/signed_payload_request_id_test.go @@ -0,0 +1,29 @@ +package vaultutils + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" + + vaultcommon "github.com/smartcontractkit/chainlink-common/pkg/capabilities/actions/vault" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/vault/vaulttypes" +) + +func TestSignedPayloadRequestID(t *testing.T) { + t.Parallel() + + payload, err := ToCanonicalJSON(&vaultcommon.CreateSecretsResponse{ + RequestId: "owner::req-1", + Responses: []*vaultcommon.CreateSecretResponse{}, + }, false) + require.NoError(t, err) + + got, err := SignedPayloadRequestID(vaulttypes.MethodSecretsCreate, json.RawMessage(payload)) + require.NoError(t, err) + require.Equal(t, "owner::req-1", got) + + got, err = SignedPayloadRequestID(vaulttypes.MethodSecretsCreate, json.RawMessage(`{"responses":[]}`)) + require.NoError(t, err) + require.Empty(t, got) +} diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 711632da375..be5a14ec576 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -47,7 +47,7 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.104 github.com/smartcontractkit/chainlink-automation v0.8.1 github.com/smartcontractkit/chainlink-ccip/chains/evm v0.0.0-20260618155522-3600f66e26cd - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260701091216-9264d4444ce0 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260706171511-193f044645f3 github.com/smartcontractkit/chainlink-common/keystore v1.2.0 github.com/smartcontractkit/chainlink-data-streams v0.1.15-0.20260522094612-5f9f748bd87a github.com/smartcontractkit/chainlink-deployments-framework v0.111.1-0.20260612191326-e31c0ae4cd54 diff --git a/core/scripts/go.sum b/core/scripts/go.sum index b51f0e50b3a..93088cf38b6 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1580,8 +1580,8 @@ github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260 github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260511195239-0f6e1b177fc7/go.mod h1:67YbnoglYD61Pz/jTVCgav9wFq7S35OU8UyQSvPllRw= github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260706093831-dad26b360094 h1:uMn1w/n95p+pSxK6hYNqji/sDaab8D0Cxuph9qUkM2g= github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260706093831-dad26b360094/go.mod h1:3XQmy2zQHrmufebP7dM+x595+gghxzyIKxK9wv91Rh8= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260701091216-9264d4444ce0 h1:ywWOEZYL4DhVwf/TQ5jXsGlx6CzERsYbqd6ov3OS1sc= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260701091216-9264d4444ce0/go.mod h1:ncZiIgraMh4F9lWDoJwB1TX395qZFR5bvnZcHbD0A1Q= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260706171511-193f044645f3 h1:z4coZM3fbnGxo7kXsG2L78h678b4j4wKQtcdvX7KMcA= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260706171511-193f044645f3/go.mod h1:ncZiIgraMh4F9lWDoJwB1TX395qZFR5bvnZcHbD0A1Q= github.com/smartcontractkit/chainlink-common/keystore v1.2.0 h1:1BH/b14CkGjArfzznlioQpIJiynECWVT48JUP9E277U= github.com/smartcontractkit/chainlink-common/keystore v1.2.0/go.mod h1:9R/74vN+bJ5PbkOyM/pUy/AeAZaRwYb/k4XPeXcbDio= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260626151909-052e55e62e62 h1:o7vfwNQjQbMKQ9YsZFQOxvU7RMXD/wKnZsX5N9sDS3w= diff --git a/core/services/gateway/handlers/vault/aggregator.go b/core/services/gateway/handlers/vault/aggregator.go index dd658f3ff48..f12106e4f1d 100644 --- a/core/services/gateway/handlers/vault/aggregator.go +++ b/core/services/gateway/handlers/vault/aggregator.go @@ -13,16 +13,25 @@ import ( "strings" "github.com/ethereum/go-ethereum/common" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" "github.com/smartcontractkit/chainlink-common/pkg/capabilities" vaultcommon "github.com/smartcontractkit/chainlink-common/pkg/capabilities/actions/vault" jsonrpc "github.com/smartcontractkit/chainlink-common/pkg/jsonrpc2" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/settings/limits" "github.com/smartcontractkit/chainlink/v2/core/capabilities/vault/vaulttypes" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/vault/vaultutils" ) +var errSignedPayloadRequestIDMismatch = errors.New("signed payload request id mismatch") + type baseAggregator struct { - capabilitiesRegistry capabilitiesRegistry + capabilitiesRegistry capabilitiesRegistry + metrics *metrics + donID string + signedResponseRequestIDGate limits.GateLimiter // vaultHandlerDonID scopes registry lookup when several vault DONs exist. // // Source: gateway job TOML [[gatewayConfig.ShardedDONs]] DonName (see deployment/cre/jobs/pkg/gateway_job.go), @@ -33,18 +42,51 @@ type baseAggregator struct { vaultHandlerDonID string } -func (a *baseAggregator) Aggregate(ctx context.Context, l logger.Logger, resps map[string]jsonrpc.Response[json.RawMessage], currResp *jsonrpc.Response[json.RawMessage]) (*jsonrpc.Response[json.RawMessage], error) { +func methodSupportsSignedOCRValidation(method string) bool { + switch method { + case vaulttypes.MethodSecretsCreate, + vaulttypes.MethodSecretsUpdate, + vaulttypes.MethodSecretsDelete, + vaulttypes.MethodSecretsList: + return true + default: + return false + } +} + +func (a *baseAggregator) signedResponseRequestIDEnabled(ctx context.Context, l logger.Logger) bool { + allowed, err := a.signedResponseRequestIDGate.Limit(ctx) + if err != nil { + l.Errorw("unexpected error evaluating CRE gate", "gate", "VaultSignedResponseRequestIDEnabled", "error", err) + return false + } + return allowed +} + +func (a *baseAggregator) Aggregate(ctx context.Context, l logger.Logger, requestID string, resps map[string]jsonrpc.Response[json.RawMessage], currResp *jsonrpc.Response[json.RawMessage]) (*jsonrpc.Response[json.RawMessage], error) { don, err := a.donForVaultCapability(ctx) if err != nil { return nil, fmt.Errorf("failed to get DON for vault capability: %w", err) } - currResp, err = a.validateUsingSignatures(don.DON, don.Nodes, currResp) - if err == nil { - return currResp, nil + if a.signedResponseRequestIDEnabled(ctx, l) { + if methodSupportsSignedOCRValidation(currResp.Method) { + currResp, err = a.validateUsingSignatures(ctx, l, don.DON, don.Nodes, requestID, currResp, true) + if err == nil { + return currResp, nil + } + + l.Debugw("failed to validate signatures, falling back to quorum aggregation", "error", err) + } + } else { + currResp, err = a.validateUsingSignatures(ctx, l, don.DON, don.Nodes, requestID, currResp, false) + if err == nil { + return currResp, nil + } + + l.Debugw("failed to validate signatures, falling back to quorum aggregation", "error", err) } - l.Debugw("failed to validate signatures, falling back to quorum aggregation", "error", err) currResp, err = a.validateUsingQuorum(don.DON, resps, l) if err != nil { return nil, fmt.Errorf("failed to validate using quorum: %w", err) @@ -211,7 +253,14 @@ func (a *baseAggregator) sha(resp *jsonrpc.Response[json.RawMessage]) (string, e return copied.Digest() } -func (a *baseAggregator) validateUsingSignatures(don capabilities.DON, nodes []capabilities.Node, resp *jsonrpc.Response[json.RawMessage]) (*jsonrpc.Response[json.RawMessage], error) { +func (a *baseAggregator) recordSignedPayloadRequestIDMismatch(ctx context.Context) { + a.metrics.requestInternalError.Add(ctx, 1, metric.WithAttributes( + attribute.String("don_id", a.donID), + attribute.String("error", "signed_payload_request_id_mismatch"), + )) +} + +func (a *baseAggregator) validateUsingSignatures(ctx context.Context, l logger.Logger, don capabilities.DON, nodes []capabilities.Node, requestID string, resp *jsonrpc.Response[json.RawMessage], validateSignedPayloadRequestID bool) (*jsonrpc.Response[json.RawMessage], error) { if resp.Result == nil { if resp.Error != nil { return nil, errors.New("response has an error, cannot validate signatures. Error: " + resp.Error.Error()) @@ -235,5 +284,25 @@ func (a *baseAggregator) validateUsingSignatures(don capabilities.DON, nodes []c return nil, fmt.Errorf("failed to validate signatures: %w", err) } + if !validateSignedPayloadRequestID { + return resp, nil + } + + payloadRequestID, err := vaultutils.SignedPayloadRequestID(resp.Method, r.Payload) + if err != nil { + l.Errorw("failed to read signed payload request id, discarding response", "requestID", requestID, "method", resp.Method, "error", err) + a.recordSignedPayloadRequestIDMismatch(ctx) + return nil, fmt.Errorf("%w: %w", errSignedPayloadRequestIDMismatch, err) + } + // Temporarily tolerate signed OCR reports from vault nodes that have not upgraded to + // include requestId in the signed payload. Once all vault nodes are upgraded, the + // gateway should start rejecting responses with a missing requestId. + // https://smartcontract-it.atlassian.net/browse/CRE-4875 + if payloadRequestID != "" && payloadRequestID != requestID { + logger.Sugared(l).Criticalw("signed payload request id mismatch, discarding response", "requestID", requestID, "signedPayloadRequestID", payloadRequestID, "method", resp.Method) + a.recordSignedPayloadRequestIDMismatch(ctx) + return nil, errSignedPayloadRequestIDMismatch + } + return resp, nil } diff --git a/core/services/gateway/handlers/vault/aggregator_test.go b/core/services/gateway/handlers/vault/aggregator_test.go index e5cef5b6617..d70c609004e 100644 --- a/core/services/gateway/handlers/vault/aggregator_test.go +++ b/core/services/gateway/handlers/vault/aggregator_test.go @@ -13,13 +13,18 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/capabilities" jsonrpc "github.com/smartcontractkit/chainlink-common/pkg/jsonrpc2" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/settings/limits" "github.com/smartcontractkit/chainlink/v2/core/capabilities/vault/vaulttypes" ) -func testAggregator(t *testing.T, mcr *mockCapabilitiesRegistry) *baseAggregator { +func testAggregator(t *testing.T, mcr *mockCapabilitiesRegistry, signedResponseRequestIDEnabled bool) *baseAggregator { t.Helper() + m, err := newMetrics() + require.NoError(t, err) return &baseAggregator{ - capabilitiesRegistry: mcr, + capabilitiesRegistry: mcr, + metrics: m, + signedResponseRequestIDGate: limits.NewGateLimiter(signedResponseRequestIDEnabled), } } @@ -36,12 +41,59 @@ func makeNodes(t *testing.T, signers []string) []capabilities.Node { func TestAggregator_Valid_Signatures(t *testing.T) { currResp, nodes := makeSignedCreateSecretsResponse(t, "1", 2) mcr := &mockCapabilitiesRegistry{F: 1, Nodes: nodes} - agg := testAggregator(t, mcr) + agg := testAggregator(t, mcr, false) + + responses := map[string]jsonrpc.Response[json.RawMessage]{ + "a": currResp, + } + resp, err := agg.Aggregate(t.Context(), logger.Test(t), currResp.ID, responses, &currResp) + require.NoError(t, err) + assert.Equal(t, &currResp, resp) +} + +func TestAggregator_SignedResponseRequestIDMismatch(t *testing.T) { + t.Parallel() + currResp, nodes := makeSignedCreateSecretsResponse(t, "signed-for-other-request", 2) + mcr := &mockCapabilitiesRegistry{F: 1, Nodes: nodes} + agg := testAggregator(t, mcr, true) + + responses := map[string]jsonrpc.Response[json.RawMessage]{ + "a": currResp, + } + + _, err := agg.Aggregate(t.Context(), logger.Test(t), "expected-request", responses, &currResp) + require.ErrorContains(t, err, "insufficient valid responses to reach quorum") +} + +func TestAggregator_PublicKeyGet_SkipsSignatureValidation(t *testing.T) { + t.Parallel() + currResp, nodes := makeSignedCreateSecretsResponse(t, "1", 2) + currResp.Method = vaulttypes.MethodPublicKeyGet + + mcr := &mockCapabilitiesRegistry{F: 1, Nodes: nodes} + agg := testAggregator(t, mcr, true) + + responses := map[string]jsonrpc.Response[json.RawMessage]{ + "a": currResp, + "b": currResp, + "c": currResp, + } + resp, err := agg.Aggregate(t.Context(), logger.Test(t), currResp.ID, responses, &currResp) + require.NoError(t, err) + assert.Equal(t, &currResp, resp) +} + +func TestAggregator_SignedResponseMissingRequestID_Accepted(t *testing.T) { + t.Parallel() + payload := json.RawMessage([]byte(`{"responses":[{"error":"failed to verify ciphertext: cannot unmarshal data: unexpected end of JSON input","id":{"key":"W","namespace":"","owner":"foo"},"success":false}]}`)) + currResp, nodes := makeSignedVaultResponse(t, vaulttypes.MethodSecretsCreate, "expected-request", payload, 2) + mcr := &mockCapabilitiesRegistry{F: 1, Nodes: nodes} + agg := testAggregator(t, mcr, true) responses := map[string]jsonrpc.Response[json.RawMessage]{ "a": currResp, } - resp, err := agg.Aggregate(t.Context(), logger.Test(t), responses, &currResp) + resp, err := agg.Aggregate(t.Context(), logger.Test(t), "expected-request", responses, &currResp) require.NoError(t, err) assert.Equal(t, &currResp, resp) } @@ -90,7 +142,7 @@ func TestAggregator_Valid_FallsBackToQuorum(t *testing.T) { } nodes := makeNodes(t, signers) mcr := &mockCapabilitiesRegistry{F: 1, Nodes: nodes} - agg := testAggregator(t, mcr) + agg := testAggregator(t, mcr, false) currResp := jsonrpc.Response[json.RawMessage]{ Version: jsonrpc.JsonRpcVersion, @@ -107,7 +159,7 @@ func TestAggregator_Valid_FallsBackToQuorum(t *testing.T) { "b": currResp, "c": currResp, } - resp, err := agg.Aggregate(t.Context(), logger.Test(t), responses, &currResp) + resp, err := agg.Aggregate(t.Context(), logger.Test(t), currResp.ID, responses, &currResp) require.NoError(t, err) assert.Equal(t, &currResp, resp) } @@ -122,7 +174,7 @@ func TestAggregator_Valid_FallsBackToQuorum_ExcludesSignaturesInSha(t *testing.T } nodes := makeNodes(t, signers) mcr := &mockCapabilitiesRegistry{F: 1, Nodes: nodes} - agg := testAggregator(t, mcr) + agg := testAggregator(t, mcr, false) oldResp1 := newMessage(t) oldResp2 := newMessage(t) @@ -132,7 +184,7 @@ func TestAggregator_Valid_FallsBackToQuorum_ExcludesSignaturesInSha(t *testing.T "b": *oldResp2, "c": *currResp, } - resp, err := agg.Aggregate(t.Context(), logger.Test(t), responses, currResp) + resp, err := agg.Aggregate(t.Context(), logger.Test(t), currResp.ID, responses, currResp) require.NoError(t, err) respDigests := []string{} @@ -206,7 +258,7 @@ func TestValidateUsingQuorum_tiedMajoritiesPickDigestDeterministically(t *testin func TestAggregator_InsufficientResponses(t *testing.T) { mcr := &mockCapabilitiesRegistry{F: 1} - agg := testAggregator(t, mcr) + agg := testAggregator(t, mcr, false) rm := json.RawMessage([]byte(`{}`)) currResp := jsonrpc.Response[json.RawMessage]{ @@ -218,7 +270,7 @@ func TestAggregator_InsufficientResponses(t *testing.T) { responses := map[string]jsonrpc.Response[json.RawMessage]{ "a": currResp, } - _, err := agg.Aggregate(t.Context(), logger.Test(t), responses, &currResp) + _, err := agg.Aggregate(t.Context(), logger.Test(t), currResp.ID, responses, &currResp) require.ErrorContains(t, err, "insufficient valid responses to reach quorum") } @@ -232,7 +284,7 @@ func TestAggregator_QuorumUnobtainable(t *testing.T) { } nodes := makeNodes(t, signers) mcr := &mockCapabilitiesRegistry{F: 1, Nodes: nodes} - agg := testAggregator(t, mcr) + agg := testAggregator(t, mcr, false) rm1 := json.RawMessage([]byte(`{}`)) resp1 := &jsonrpc.Response[json.RawMessage]{ @@ -260,7 +312,7 @@ func TestAggregator_QuorumUnobtainable(t *testing.T) { "b": *resp2, "c": *resp3, } - _, err := agg.Aggregate(t.Context(), logger.Test(t), responses, resp3) + _, err := agg.Aggregate(t.Context(), logger.Test(t), resp3.ID, responses, resp3) require.ErrorContains(t, err, "failed to validate using quorum: quorum unobtainable") } @@ -291,8 +343,9 @@ func TestAggregator_MultipleRegistryDONs_SelectsByVaultHandlerDonName(t *testing donMine := makeDONWithNodesForTest(t, "cre-reliability-vault", 2, 1, 0x20, 4) mcr := &mockCapabilitiesRegistry{DONs: []capabilities.DONWithNodes{donOther, donMine}} agg := &baseAggregator{ - capabilitiesRegistry: mcr, - vaultHandlerDonID: "cre-reliability-vault", + capabilitiesRegistry: mcr, + vaultHandlerDonID: "cre-reliability-vault", + signedResponseRequestIDGate: limits.NewGateLimiter(false), } rm := json.RawMessage([]byte(`{}`)) @@ -307,7 +360,7 @@ func TestAggregator_MultipleRegistryDONs_SelectsByVaultHandlerDonName(t *testing "b": currResp, "c": currResp, } - resp, err := agg.Aggregate(t.Context(), logger.Test(t), responses, &currResp) + resp, err := agg.Aggregate(t.Context(), logger.Test(t), currResp.ID, responses, &currResp) require.NoError(t, err) require.Equal(t, currResp.ID, resp.ID) } @@ -317,8 +370,9 @@ func TestAggregator_MultipleRegistryDONs_SelectsByIDWhenNameEmpty(t *testing.T) donMine := makeDONWithNodesForTest(t, "", 99, 1, 0x20, 4) mcr := &mockCapabilitiesRegistry{DONs: []capabilities.DONWithNodes{donOther, donMine}} agg := &baseAggregator{ - capabilitiesRegistry: mcr, - vaultHandlerDonID: "99", + capabilitiesRegistry: mcr, + vaultHandlerDonID: "99", + signedResponseRequestIDGate: limits.NewGateLimiter(false), } rm := json.RawMessage([]byte(`{}`)) @@ -333,7 +387,7 @@ func TestAggregator_MultipleRegistryDONs_SelectsByIDWhenNameEmpty(t *testing.T) "b": currResp, "c": currResp, } - resp, err := agg.Aggregate(t.Context(), logger.Test(t), responses, &currResp) + resp, err := agg.Aggregate(t.Context(), logger.Test(t), currResp.ID, responses, &currResp) require.NoError(t, err) require.Equal(t, currResp.ID, resp.ID) } @@ -343,8 +397,9 @@ func TestAggregator_MultipleRegistryDONs_NoMatchingVaultHandlerDonId(t *testing. donB := makeDONWithNodesForTest(t, "don-b", 2, 1, 0x20, 4) mcr := &mockCapabilitiesRegistry{DONs: []capabilities.DONWithNodes{donA, donB}} agg := &baseAggregator{ - capabilitiesRegistry: mcr, - vaultHandlerDonID: "unknown-vault", + capabilitiesRegistry: mcr, + vaultHandlerDonID: "unknown-vault", + signedResponseRequestIDGate: limits.NewGateLimiter(false), } rm := json.RawMessage([]byte(`{}`)) @@ -355,7 +410,7 @@ func TestAggregator_MultipleRegistryDONs_NoMatchingVaultHandlerDonId(t *testing. Result: &rm, } responses := map[string]jsonrpc.Response[json.RawMessage]{"a": currResp} - _, err := agg.Aggregate(t.Context(), logger.Test(t), responses, &currResp) + _, err := agg.Aggregate(t.Context(), logger.Test(t), currResp.ID, responses, &currResp) require.ErrorContains(t, err, "none match vault handler DonId") } @@ -364,8 +419,9 @@ func TestAggregator_MultipleRegistryDONs_AmbiguousMatchingVaultHandlerDonId(t *t donB := makeDONWithNodesForTest(t, "same-name", 2, 1, 0x20, 4) mcr := &mockCapabilitiesRegistry{DONs: []capabilities.DONWithNodes{donA, donB}} agg := &baseAggregator{ - capabilitiesRegistry: mcr, - vaultHandlerDonID: "same-name", + capabilitiesRegistry: mcr, + vaultHandlerDonID: "same-name", + signedResponseRequestIDGate: limits.NewGateLimiter(false), } rm := json.RawMessage([]byte(`{}`)) @@ -376,6 +432,6 @@ func TestAggregator_MultipleRegistryDONs_AmbiguousMatchingVaultHandlerDonId(t *t Result: &rm, } responses := map[string]jsonrpc.Response[json.RawMessage]{"a": currResp} - _, err := agg.Aggregate(t.Context(), logger.Test(t), responses, &currResp) + _, err := agg.Aggregate(t.Context(), logger.Test(t), currResp.ID, responses, &currResp) require.ErrorContains(t, err, "2 DONs match vault handler DonId") } diff --git a/core/services/gateway/handlers/vault/aggregator_test_helpers.go b/core/services/gateway/handlers/vault/aggregator_test_helpers.go index 638a2d91405..3fcf3d84818 100644 --- a/core/services/gateway/handlers/vault/aggregator_test_helpers.go +++ b/core/services/gateway/handlers/vault/aggregator_test_helpers.go @@ -31,6 +31,7 @@ func makeSignedCreateSecretsResponse(t *testing.T, requestID string, numSigners t.Helper() createResp := &vaultcommon.CreateSecretsResponse{ + RequestId: requestID, Responses: []*vaultcommon.CreateSecretResponse{ { Id: &vaultcommon.SecretIdentifier{ diff --git a/core/services/gateway/handlers/vault/handler.go b/core/services/gateway/handlers/vault/handler.go index 6e6e6f99ffb..ce15a2a837e 100644 --- a/core/services/gateway/handlers/vault/handler.go +++ b/core/services/gateway/handlers/vault/handler.go @@ -127,7 +127,7 @@ type capabilitiesRegistry interface { } type aggregator interface { - Aggregate(ctx context.Context, l logger.Logger, resps map[string]jsonrpc.Response[json.RawMessage], currResp *jsonrpc.Response[json.RawMessage]) (*jsonrpc.Response[json.RawMessage], error) + Aggregate(ctx context.Context, l logger.Logger, requestID string, resps map[string]jsonrpc.Response[json.RawMessage], currResp *jsonrpc.Response[json.RawMessage]) (*jsonrpc.Response[json.RawMessage], error) } type handler struct { @@ -146,9 +146,10 @@ type handler struct { nodeRateLimiter *ratelimit.RateLimiter requestTimeout time.Duration - writeMethodsEnabled limits.GateLimiter - activeRequests map[string]*activeRequest - metrics *metrics + writeMethodsEnabled limits.GateLimiter + signedResponseRequestIDEnabled limits.GateLimiter + activeRequests map[string]*activeRequest + metrics *metrics aggregator aggregator @@ -242,23 +243,32 @@ func newHandlerWithAuthorizer(methodConfig json.RawMessage, donConfig *config.DO return nil, fmt.Errorf("failed to create gateway vault request processor: %w", err) } + signedResponseRequestIDEnabled, err := limits.MakeGateLimiter(limitsFactory, cresettings.Default.VaultSignedResponseRequestIDEnabled) + if err != nil { + return nil, fmt.Errorf("could not create VaultSignedResponseRequestIDEnabled limiter: %w", err) + } + return &handler{ - methodConfig: cfg, - donConfig: donConfig, - don: don, - lggr: logger.Named(lggr, "VaultHandler:"+donConfig.DonId), - requestTimeout: time.Duration(cfg.RequestTimeoutSec) * time.Second, - nodeRateLimiter: nodeRateLimiter, - writeMethodsEnabled: writeMethodsEnabled, - activeRequests: make(map[string]*activeRequest), - mu: sync.RWMutex{}, - authorizer: authorizer, - jwtAuth: jwtAuth, - stopCh: make(services.StopChan), - metrics: metrics, + methodConfig: cfg, + donConfig: donConfig, + don: don, + lggr: logger.Named(lggr, "VaultHandler:"+donConfig.DonId), + requestTimeout: time.Duration(cfg.RequestTimeoutSec) * time.Second, + nodeRateLimiter: nodeRateLimiter, + writeMethodsEnabled: writeMethodsEnabled, + signedResponseRequestIDEnabled: signedResponseRequestIDEnabled, + activeRequests: make(map[string]*activeRequest), + mu: sync.RWMutex{}, + authorizer: authorizer, + jwtAuth: jwtAuth, + stopCh: make(services.StopChan), + metrics: metrics, aggregator: &baseAggregator{ - capabilitiesRegistry: capabilitiesRegistry, - vaultHandlerDonID: donConfig.DonId, + capabilitiesRegistry: capabilitiesRegistry, + metrics: metrics, + donID: donConfig.DonId, + vaultHandlerDonID: donConfig.DonId, + signedResponseRequestIDGate: signedResponseRequestIDEnabled, }, clock: clock, requestProcessor: requestProcessor, @@ -308,6 +318,7 @@ func (h *handler) Close() error { jwtAuthErr, h.writeMethodsEnabled.Close(), h.requestProcessor.Close(), + h.signedResponseRequestIDEnabled.Close(), ) }) } @@ -498,7 +509,7 @@ func (h *handler) HandleNodeMessage(ctx context.Context, resp *jsonrpc.Response[ } copiedResponses := ar.copiedResponses() - resp, err := h.aggregator.Aggregate(ctx, l, copiedResponses, resp) + resp, err := h.aggregator.Aggregate(ctx, l, ar.req.ID, copiedResponses, resp) switch { case errors.Is(err, errInsufficientResponsesForQuorum): l.Debugw("aggregating responses, waiting for other nodes...", "error", err) diff --git a/core/services/gateway/handlers/vault/handler_test.go b/core/services/gateway/handlers/vault/handler_test.go index a18d6cd4947..cd281055a10 100644 --- a/core/services/gateway/handlers/vault/handler_test.go +++ b/core/services/gateway/handlers/vault/handler_test.go @@ -108,7 +108,7 @@ type mockAggregator struct { err error } -func (m *mockAggregator) Aggregate(_ context.Context, _ logger.Logger, _ map[string]jsonrpc.Response[json.RawMessage], currResp *jsonrpc.Response[json.RawMessage]) (*jsonrpc.Response[json.RawMessage], error) { +func (m *mockAggregator) Aggregate(_ context.Context, _ logger.Logger, _ string, _ map[string]jsonrpc.Response[json.RawMessage], currResp *jsonrpc.Response[json.RawMessage]) (*jsonrpc.Response[json.RawMessage], error) { if m.err != nil { return nil, m.err } @@ -1002,8 +1002,9 @@ func TestVaultHandler_HandleNodeMessage_SignatureValidatedResponse_RejectsUnknow nodes := makeNodes(t, signers) mcr := &mockCapabilitiesRegistry{F: 1, Nodes: nodes} h.(*handler).aggregator = &baseAggregator{ - capabilitiesRegistry: mcr, - vaultHandlerDonID: h.(*handler).donConfig.DonId, + capabilitiesRegistry: mcr, + vaultHandlerDonID: h.(*handler).donConfig.DonId, + signedResponseRequestIDGate: limits.NewGateLimiter(true), } ocrContext, err := hex.DecodeString("000ec4f6a2ba011e909eccf64628855b848e08876a1edd938a1372a9e51adff100000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000") @@ -1077,8 +1078,9 @@ func TestVaultHandler_PublicKeyGet(t *testing.T) { nodes := makeNodes(t, signers) mcr := &mockCapabilitiesRegistry{F: 1, Nodes: nodes} h.(*handler).aggregator = &baseAggregator{ - capabilitiesRegistry: mcr, - vaultHandlerDonID: h.(*handler).donConfig.DonId, + capabilitiesRegistry: mcr, + vaultHandlerDonID: h.(*handler).donConfig.DonId, + signedResponseRequestIDGate: limits.NewGateLimiter(true), } don.On("SendToNode", mock.Anything, mock.Anything, mock.Anything).Return(nil) diff --git a/core/services/ocr2/plugins/vault/plugin.go b/core/services/ocr2/plugins/vault/plugin.go index d3e1fea0297..eecbb76e171 100644 --- a/core/services/ocr2/plugins/vault/plugin.go +++ b/core/services/ocr2/plugins/vault/plugin.go @@ -62,6 +62,7 @@ type ReportingPluginConfig struct { VaultForceEmptyOCRRounds limits.GateLimiter VaultOptimizationsEnabled limits.GateLimiter VaultJSONOmitUnpopulatedEnabled limits.GateLimiter + VaultSignedResponseRequestIDEnabled limits.GateLimiter VaultGetSecretsShareAggregationIncludesPublicKeys limits.GateLimiter } @@ -197,6 +198,11 @@ func newReportingPluginConfigLimiters(factory limits.Factory) (*ReportingPluginC return nil, fmt.Errorf("VaultJSONOmitUnpopulatedEnabled: %w", err) } + vaultSignedResponseRequestIDEnabled, err := limits.MakeGateLimiter(factory, cresettings.Default.VaultSignedResponseRequestIDEnabled) + if err != nil { + return nil, fmt.Errorf("VaultSignedResponseRequestIDEnabled: %w", err) + } + maxBlobPayloadBytesLimiter, err := limits.MakeUpperBoundLimiter(factory, cresettings.Default.VaultMaxBlobPayloadSizeLimit) if err != nil { return nil, fmt.Errorf("VaultMaxBlobPayloadSizeLimit: %w", err) @@ -214,6 +220,7 @@ func newReportingPluginConfigLimiters(factory limits.Factory) (*ReportingPluginC VaultForceEmptyOCRRounds: vaultForceEmptyOCRRounds, VaultOptimizationsEnabled: vaultOptimizationsEnabled, VaultJSONOmitUnpopulatedEnabled: vaultJSONOmitUnpopulatedEnabled, + VaultSignedResponseRequestIDEnabled: vaultSignedResponseRequestIDEnabled, VaultGetSecretsShareAggregationIncludesPublicKeys: vaultGetSecretsShareAggregationIncludesPublicKeys, }, nil } @@ -584,6 +591,10 @@ func (r *ReportingPlugin) jsonOmitUnpopulatedEnabled(ctx context.Context) bool { return gateAllows(ctx, r.lggr, r.cfg.VaultJSONOmitUnpopulatedEnabled, "VaultJSONOmitUnpopulatedEnabled") } +func (r *ReportingPlugin) signedResponseRequestIDEnabled(ctx context.Context) bool { + return gateAllows(ctx, r.lggr, r.cfg.VaultSignedResponseRequestIDEnabled, "VaultSignedResponseRequestIDEnabled") +} + type pendingQueueStore interface { WritePendingQueue(ctx context.Context, pending []*vaultcommon.StoredPendingQueueItem) error } @@ -2457,7 +2468,11 @@ func (r *ReportingPlugin) Reports(ctx context.Context, seqNr uint64, reportsPlus ReportWithInfo: rep, }) case vaultcommon.RequestType_CREATE_SECRETS: - rep, err := r.generateJSONReport(o.Id, o.RequestType, o.GetCreateSecretsResponse(), r.jsonOmitUnpopulatedEnabled(ctx)) + createResp := proto.Clone(o.GetCreateSecretsResponse()).(*vaultcommon.CreateSecretsResponse) + if r.signedResponseRequestIDEnabled(ctx) { + createResp.RequestId = o.Id + } + rep, err := r.generateJSONReport(o.Id, o.RequestType, createResp, r.jsonOmitUnpopulatedEnabled(ctx)) if err != nil { r.lggr.Errorw("failed to generate JSON report", "error", err, "id", o.Id) continue @@ -2467,7 +2482,11 @@ func (r *ReportingPlugin) Reports(ctx context.Context, seqNr uint64, reportsPlus ReportWithInfo: rep, }) case vaultcommon.RequestType_UPDATE_SECRETS: - rep, err := r.generateJSONReport(o.Id, o.RequestType, o.GetUpdateSecretsResponse(), r.jsonOmitUnpopulatedEnabled(ctx)) + updateResp := proto.Clone(o.GetUpdateSecretsResponse()).(*vaultcommon.UpdateSecretsResponse) + if r.signedResponseRequestIDEnabled(ctx) { + updateResp.RequestId = o.Id + } + rep, err := r.generateJSONReport(o.Id, o.RequestType, updateResp, r.jsonOmitUnpopulatedEnabled(ctx)) if err != nil { r.lggr.Errorw("failed to generate JSON report", "error", err, "id", o.Id) continue @@ -2477,7 +2496,11 @@ func (r *ReportingPlugin) Reports(ctx context.Context, seqNr uint64, reportsPlus ReportWithInfo: rep, }) case vaultcommon.RequestType_DELETE_SECRETS: - rep, err := r.generateJSONReport(o.Id, o.RequestType, o.GetDeleteSecretsResponse(), r.jsonOmitUnpopulatedEnabled(ctx)) + deleteResp := proto.Clone(o.GetDeleteSecretsResponse()).(*vaultcommon.DeleteSecretsResponse) + if r.signedResponseRequestIDEnabled(ctx) { + deleteResp.RequestId = o.Id + } + rep, err := r.generateJSONReport(o.Id, o.RequestType, deleteResp, r.jsonOmitUnpopulatedEnabled(ctx)) if err != nil { r.lggr.Errorw("failed to generate JSON report", "error", err, "id", o.Id) continue @@ -2487,7 +2510,11 @@ func (r *ReportingPlugin) Reports(ctx context.Context, seqNr uint64, reportsPlus ReportWithInfo: rep, }) case vaultcommon.RequestType_LIST_SECRET_IDENTIFIERS: - rep, err := r.generateJSONReport(o.Id, o.RequestType, o.GetListSecretIdentifiersResponse(), r.jsonOmitUnpopulatedEnabled(ctx)) + listResp := proto.Clone(o.GetListSecretIdentifiersResponse()).(*vaultcommon.ListSecretIdentifiersResponse) + if r.signedResponseRequestIDEnabled(ctx) { + listResp.RequestId = o.Id + } + rep, err := r.generateJSONReport(o.Id, o.RequestType, listResp, r.jsonOmitUnpopulatedEnabled(ctx)) if err != nil { r.lggr.Errorw("failed to generate JSON report", "error", err, "id", o.Id) continue diff --git a/core/services/ocr2/plugins/vault/plugin_helpers_test.go b/core/services/ocr2/plugins/vault/plugin_helpers_test.go index 06aa42a477a..e6ef736abea 100644 --- a/core/services/ocr2/plugins/vault/plugin_helpers_test.go +++ b/core/services/ocr2/plugins/vault/plugin_helpers_test.go @@ -37,6 +37,7 @@ type testPluginBuildOpts struct { maxBlobPayloadBytes int vaultOptimizationsEnabled bool vaultJSONOmitUnpopulatedEnabled bool + vaultSignedResponseRequestIDEnabled bool vaultShareAggregationIncludesPublicKeys bool marshalBlob func(ocr3_1types.BlobHandle) ([]byte, error) unmarshalBlob func([]byte) (ocr3_1types.BlobHandle, error) @@ -87,6 +88,10 @@ func withVaultJSONOmitUnpopulatedEnabled() testPluginOption { return func(o *testPluginBuildOpts) { o.vaultJSONOmitUnpopulatedEnabled = true } } +func withVaultSignedResponseRequestIDEnabled() testPluginOption { + return func(o *testPluginBuildOpts) { o.vaultSignedResponseRequestIDEnabled = true } +} + func withOnchainCfg(n int, f int) testPluginOption { return func(o *testPluginBuildOpts) { o.onchainCfg = ocr3types.ReportingPluginConfig{N: n, F: f} @@ -146,6 +151,9 @@ func newTestReportingPlugin(t *testing.T, opts ...testPluginOption) *ReportingPl if o.vaultJSONOmitUnpopulatedEnabled { cfg.VaultJSONOmitUnpopulatedEnabled = limits.NewGateLimiter(true) } + if o.vaultSignedResponseRequestIDEnabled { + cfg.VaultSignedResponseRequestIDEnabled = limits.NewGateLimiter(true) + } ctx := context.Background() pl, err := initializePluginLimits(ctx, limits.Factory{Settings: cresettings.DefaultGetter}) require.NoError(t, err) @@ -243,16 +251,17 @@ func makeReportingPluginConfig( require.NoError(t, err) return &ReportingPluginConfig{ - MaxBatchSize: bsl, - MaxPendingQueueWriteSize: maxPendingQueueWriteSizeLimiter, - PublicKey: publicKey, - PrivateKeyShare: privateKeyShare, - MaxSecretsPerOwner: msl, - MaxShareLengthBytes: shareLimiter, - MaxBlobPayloadBytes: maxBlobPayloadLimiter, - VaultForceEmptyOCRRounds: limits.NewGateLimiter(false), - VaultOptimizationsEnabled: limits.NewGateLimiter(false), - VaultJSONOmitUnpopulatedEnabled: limits.NewGateLimiter(false), + MaxBatchSize: bsl, + MaxPendingQueueWriteSize: maxPendingQueueWriteSizeLimiter, + PublicKey: publicKey, + PrivateKeyShare: privateKeyShare, + MaxSecretsPerOwner: msl, + MaxShareLengthBytes: shareLimiter, + MaxBlobPayloadBytes: maxBlobPayloadLimiter, + VaultForceEmptyOCRRounds: limits.NewGateLimiter(false), + VaultOptimizationsEnabled: limits.NewGateLimiter(false), + VaultJSONOmitUnpopulatedEnabled: limits.NewGateLimiter(false), + VaultSignedResponseRequestIDEnabled: limits.NewGateLimiter(false), VaultGetSecretsShareAggregationIncludesPublicKeys: limits.NewGateLimiter(false), } } diff --git a/core/services/ocr2/plugins/vault/plugin_test.go b/core/services/ocr2/plugins/vault/plugin_test.go index 529c8820347..3e4611e4a69 100644 --- a/core/services/ocr2/plugins/vault/plugin_test.go +++ b/core/services/ocr2/plugins/vault/plugin_test.go @@ -4045,7 +4045,7 @@ func TestPlugin_Reports(t *testing.T) { _, pk, shares, err := tdh2easy.GenerateKeys(1, 3) require.NoError(t, err) - r := newTestReportingPlugin(t, withKeys(pk, shares[0]), withOnchainCfg(4, 1), withVaultJSONOmitUnpopulatedEnabled()) + r := newTestReportingPlugin(t, withKeys(pk, shares[0]), withOnchainCfg(4, 1), withVaultJSONOmitUnpopulatedEnabled(), withVaultSignedResponseRequestIDEnabled()) rs, err := r.Reports(t.Context(), uint64(1), osb) require.NoError(t, err) @@ -4062,7 +4062,9 @@ func TestPlugin_Reports(t *testing.T) { RequestType: vaultcommon.RequestType_CREATE_SECRETS, }, info1)) - expectedBytes, err := vaultutils.ToCanonicalJSON(resp, true) + signedResp := proto.Clone(resp).(*vaultcommon.CreateSecretsResponse) + signedResp.RequestId = vaulttypes.KeyFor(id) + expectedBytes, err := vaultutils.ToCanonicalJSON(signedResp, true) require.NoError(t, err) assert.Equal(t, expectedBytes, []byte(o1.ReportWithInfo.Report)) @@ -4703,7 +4705,7 @@ func TestPlugin_Reports_UpdateSecretsRequest(t *testing.T) { _, pk, shares, err := tdh2easy.GenerateKeys(1, 3) require.NoError(t, err) - r := newTestReportingPlugin(t, withKeys(pk, shares[0]), withOnchainCfg(4, 1), withVaultJSONOmitUnpopulatedEnabled()) + r := newTestReportingPlugin(t, withKeys(pk, shares[0]), withOnchainCfg(4, 1), withVaultJSONOmitUnpopulatedEnabled(), withVaultSignedResponseRequestIDEnabled()) rs, err := r.Reports(t.Context(), uint64(1), osb) require.NoError(t, err) @@ -4720,7 +4722,9 @@ func TestPlugin_Reports_UpdateSecretsRequest(t *testing.T) { RequestType: vaultcommon.RequestType_UPDATE_SECRETS, }, info1)) - expectedBytes, err := vaultutils.ToCanonicalJSON(resp, true) + signedResp := proto.Clone(resp).(*vaultcommon.UpdateSecretsResponse) + signedResp.RequestId = vaulttypes.KeyFor(id) + expectedBytes, err := vaultutils.ToCanonicalJSON(signedResp, true) require.NoError(t, err) assert.Equal(t, expectedBytes, []byte(o.ReportWithInfo.Report)) } @@ -5097,7 +5101,7 @@ func TestPlugin_Reports_DeleteSecretsRequest(t *testing.T) { _, pk, shares, err := tdh2easy.GenerateKeys(1, 3) require.NoError(t, err) - r := newTestReportingPlugin(t, withKeys(pk, shares[0]), withOnchainCfg(4, 1), withVaultJSONOmitUnpopulatedEnabled()) + r := newTestReportingPlugin(t, withKeys(pk, shares[0]), withOnchainCfg(4, 1), withVaultJSONOmitUnpopulatedEnabled(), withVaultSignedResponseRequestIDEnabled()) rs, err := r.Reports(t.Context(), uint64(1), osb) require.NoError(t, err) @@ -5114,7 +5118,9 @@ func TestPlugin_Reports_DeleteSecretsRequest(t *testing.T) { RequestType: vaultcommon.RequestType_DELETE_SECRETS, }, info1)) - expectedBytes, err := vaultutils.ToCanonicalJSON(resp, true) + signedResp := proto.Clone(resp).(*vaultcommon.DeleteSecretsResponse) + signedResp.RequestId = vaulttypes.KeyFor(id) + expectedBytes, err := vaultutils.ToCanonicalJSON(signedResp, true) require.NoError(t, err) assert.Equal(t, expectedBytes, []byte(o.ReportWithInfo.Report)) } @@ -5439,7 +5445,7 @@ func TestPlugin_Reports_ListSecretIdentifiersRequest(t *testing.T) { _, pk, shares, err := tdh2easy.GenerateKeys(1, 3) require.NoError(t, err) - r := newTestReportingPlugin(t, withKeys(pk, shares[0]), withOnchainCfg(4, 1), withVaultJSONOmitUnpopulatedEnabled()) + r := newTestReportingPlugin(t, withKeys(pk, shares[0]), withOnchainCfg(4, 1), withVaultJSONOmitUnpopulatedEnabled(), withVaultSignedResponseRequestIDEnabled()) rs, err := r.Reports(t.Context(), uint64(1), osb) require.NoError(t, err) @@ -5456,7 +5462,9 @@ func TestPlugin_Reports_ListSecretIdentifiersRequest(t *testing.T) { RequestType: vaultcommon.RequestType_LIST_SECRET_IDENTIFIERS, }, info1)) - expectedBytes, err := vaultutils.ToCanonicalJSON(resp, true) + signedResp := proto.Clone(resp).(*vaultcommon.ListSecretIdentifiersResponse) + signedResp.RequestId = vaulttypes.KeyFor(id) + expectedBytes, err := vaultutils.ToCanonicalJSON(signedResp, true) require.NoError(t, err) assert.Equal(t, expectedBytes, []byte(o.ReportWithInfo.Report)) } diff --git a/core/services/workflows/cmd/cre/examples/legacy/data_feeds/testdata/output.wasm.br b/core/services/workflows/cmd/cre/examples/legacy/data_feeds/testdata/output.wasm.br index 63b2675a9b0..fbe794a284e 100644 Binary files a/core/services/workflows/cmd/cre/examples/legacy/data_feeds/testdata/output.wasm.br and b/core/services/workflows/cmd/cre/examples/legacy/data_feeds/testdata/output.wasm.br differ diff --git a/core/services/workflows/test/break/cmd/testdata/output.wasm.br b/core/services/workflows/test/break/cmd/testdata/output.wasm.br index c1b69ad152e..2871f3bb5e7 100644 Binary files a/core/services/workflows/test/break/cmd/testdata/output.wasm.br and b/core/services/workflows/test/break/cmd/testdata/output.wasm.br differ diff --git a/core/services/workflows/test/wasm/legacy/cmd/testdata/output.wasm.br b/core/services/workflows/test/wasm/legacy/cmd/testdata/output.wasm.br index d9687b07d61..c872c268dca 100644 Binary files a/core/services/workflows/test/wasm/legacy/cmd/testdata/output.wasm.br and b/core/services/workflows/test/wasm/legacy/cmd/testdata/output.wasm.br differ diff --git a/core/services/workflows/test/zerotimeout/cmd/testdata/output.wasm.br b/core/services/workflows/test/zerotimeout/cmd/testdata/output.wasm.br index c7a02c90a38..1928729e83f 100644 Binary files a/core/services/workflows/test/zerotimeout/cmd/testdata/output.wasm.br and b/core/services/workflows/test/zerotimeout/cmd/testdata/output.wasm.br differ diff --git a/deployment/go.mod b/deployment/go.mod index 77286d5ddba..8e4c4703c9a 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -46,7 +46,7 @@ require ( github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260415165642-49f23e4d76cc github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260511195239-0f6e1b177fc7 github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260618155522-3600f66e26cd - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260701091216-9264d4444ce0 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260706171511-193f044645f3 github.com/smartcontractkit/chainlink-common/keystore v1.2.0 github.com/smartcontractkit/chainlink-data-streams v0.1.15-0.20260522094612-5f9f748bd87a github.com/smartcontractkit/chainlink-deployments-framework v0.111.1-0.20260612191326-e31c0ae4cd54 diff --git a/deployment/go.sum b/deployment/go.sum index aaa375dedf4..b6ad1457620 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -1383,8 +1383,8 @@ github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260706093831-dad26b360094 h github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260706093831-dad26b360094/go.mod h1:3XQmy2zQHrmufebP7dM+x595+gghxzyIKxK9wv91Rh8= github.com/smartcontractkit/chainlink-ccv/deployment v0.0.2-0.20260616151800-9a3a31c4e194 h1:QxZkbKtQyPtVLYP4eMwc+VbXY7M5ve1deSiLZ2pOA+Y= github.com/smartcontractkit/chainlink-ccv/deployment v0.0.2-0.20260616151800-9a3a31c4e194/go.mod h1:bNMFRxwWdgVFdSsFZRmsUUPoBUncU3RM765K99svIKM= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260701091216-9264d4444ce0 h1:ywWOEZYL4DhVwf/TQ5jXsGlx6CzERsYbqd6ov3OS1sc= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260701091216-9264d4444ce0/go.mod h1:ncZiIgraMh4F9lWDoJwB1TX395qZFR5bvnZcHbD0A1Q= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260706171511-193f044645f3 h1:z4coZM3fbnGxo7kXsG2L78h678b4j4wKQtcdvX7KMcA= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260706171511-193f044645f3/go.mod h1:ncZiIgraMh4F9lWDoJwB1TX395qZFR5bvnZcHbD0A1Q= github.com/smartcontractkit/chainlink-common/keystore v1.2.0 h1:1BH/b14CkGjArfzznlioQpIJiynECWVT48JUP9E277U= github.com/smartcontractkit/chainlink-common/keystore v1.2.0/go.mod h1:9R/74vN+bJ5PbkOyM/pUy/AeAZaRwYb/k4XPeXcbDio= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260626151909-052e55e62e62 h1:o7vfwNQjQbMKQ9YsZFQOxvU7RMXD/wKnZsX5N9sDS3w= diff --git a/go.mod b/go.mod index 3014ddb9f4d..3b1a58e0358 100644 --- a/go.mod +++ b/go.mod @@ -85,7 +85,7 @@ require ( github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260415165642-49f23e4d76cc github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260415165642-49f23e4d76cc github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260706093831-dad26b360094 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260701091216-9264d4444ce0 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260706171511-193f044645f3 github.com/smartcontractkit/chainlink-common/keystore v1.2.0 github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260626151909-052e55e62e62 github.com/smartcontractkit/chainlink-data-streams v0.1.15-0.20260522094612-5f9f748bd87a diff --git a/go.sum b/go.sum index bfab02b97a6..70dca9eb1ca 100644 --- a/go.sum +++ b/go.sum @@ -1162,8 +1162,8 @@ github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260 github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260415165642-49f23e4d76cc/go.mod h1:67YbnoglYD61Pz/jTVCgav9wFq7S35OU8UyQSvPllRw= github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260706093831-dad26b360094 h1:uMn1w/n95p+pSxK6hYNqji/sDaab8D0Cxuph9qUkM2g= github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260706093831-dad26b360094/go.mod h1:3XQmy2zQHrmufebP7dM+x595+gghxzyIKxK9wv91Rh8= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260701091216-9264d4444ce0 h1:ywWOEZYL4DhVwf/TQ5jXsGlx6CzERsYbqd6ov3OS1sc= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260701091216-9264d4444ce0/go.mod h1:ncZiIgraMh4F9lWDoJwB1TX395qZFR5bvnZcHbD0A1Q= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260706171511-193f044645f3 h1:z4coZM3fbnGxo7kXsG2L78h678b4j4wKQtcdvX7KMcA= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260706171511-193f044645f3/go.mod h1:ncZiIgraMh4F9lWDoJwB1TX395qZFR5bvnZcHbD0A1Q= github.com/smartcontractkit/chainlink-common/keystore v1.2.0 h1:1BH/b14CkGjArfzznlioQpIJiynECWVT48JUP9E277U= github.com/smartcontractkit/chainlink-common/keystore v1.2.0/go.mod h1:9R/74vN+bJ5PbkOyM/pUy/AeAZaRwYb/k4XPeXcbDio= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260626151909-052e55e62e62 h1:o7vfwNQjQbMKQ9YsZFQOxvU7RMXD/wKnZsX5N9sDS3w= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 76a9572a0aa..a8e212c860c 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -33,7 +33,7 @@ require ( github.com/smartcontractkit/chainlink-ccip/chains/evm v0.0.0-20260618155522-3600f66e26cd github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260506144252-c100eabfda74 github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260511195239-0f6e1b177fc7 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260701091216-9264d4444ce0 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260706171511-193f044645f3 github.com/smartcontractkit/chainlink-common/keystore v1.2.0 github.com/smartcontractkit/chainlink-deployments-framework v0.111.1-0.20260612191326-e31c0ae4cd54 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260623170329-4577ef4ba0ae diff --git a/integration-tests/go.sum b/integration-tests/go.sum index c31786746d0..0b1bdd4c8b9 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1370,8 +1370,8 @@ github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260706093831-dad26b360094 h github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260706093831-dad26b360094/go.mod h1:3XQmy2zQHrmufebP7dM+x595+gghxzyIKxK9wv91Rh8= github.com/smartcontractkit/chainlink-ccv/deployment v0.0.2-0.20260616151800-9a3a31c4e194 h1:QxZkbKtQyPtVLYP4eMwc+VbXY7M5ve1deSiLZ2pOA+Y= github.com/smartcontractkit/chainlink-ccv/deployment v0.0.2-0.20260616151800-9a3a31c4e194/go.mod h1:bNMFRxwWdgVFdSsFZRmsUUPoBUncU3RM765K99svIKM= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260701091216-9264d4444ce0 h1:ywWOEZYL4DhVwf/TQ5jXsGlx6CzERsYbqd6ov3OS1sc= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260701091216-9264d4444ce0/go.mod h1:ncZiIgraMh4F9lWDoJwB1TX395qZFR5bvnZcHbD0A1Q= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260706171511-193f044645f3 h1:z4coZM3fbnGxo7kXsG2L78h678b4j4wKQtcdvX7KMcA= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260706171511-193f044645f3/go.mod h1:ncZiIgraMh4F9lWDoJwB1TX395qZFR5bvnZcHbD0A1Q= github.com/smartcontractkit/chainlink-common/keystore v1.2.0 h1:1BH/b14CkGjArfzznlioQpIJiynECWVT48JUP9E277U= github.com/smartcontractkit/chainlink-common/keystore v1.2.0/go.mod h1:9R/74vN+bJ5PbkOyM/pUy/AeAZaRwYb/k4XPeXcbDio= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260626151909-052e55e62e62 h1:o7vfwNQjQbMKQ9YsZFQOxvU7RMXD/wKnZsX5N9sDS3w= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index c9a99dbcf5c..4c346711527 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -24,7 +24,7 @@ require ( github.com/smartcontractkit/chainlink-ccip/chains/evm v0.0.0-20260618155522-3600f66e26cd github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260506144252-c100eabfda74 github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260511195239-0f6e1b177fc7 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260701091216-9264d4444ce0 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260706171511-193f044645f3 github.com/smartcontractkit/chainlink-deployments-framework v0.111.1-0.20260612191326-e31c0ae4cd54 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260623170329-4577ef4ba0ae github.com/smartcontractkit/chainlink-testing-framework/framework v0.16.6-0.20260630120514-36abe27604df diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index ea64ea73bf2..df3cfd881a6 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1632,8 +1632,8 @@ github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260706093831-dad26b360094 h github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260706093831-dad26b360094/go.mod h1:3XQmy2zQHrmufebP7dM+x595+gghxzyIKxK9wv91Rh8= github.com/smartcontractkit/chainlink-ccv/deployment v0.0.2-0.20260616151800-9a3a31c4e194 h1:QxZkbKtQyPtVLYP4eMwc+VbXY7M5ve1deSiLZ2pOA+Y= github.com/smartcontractkit/chainlink-ccv/deployment v0.0.2-0.20260616151800-9a3a31c4e194/go.mod h1:bNMFRxwWdgVFdSsFZRmsUUPoBUncU3RM765K99svIKM= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260701091216-9264d4444ce0 h1:ywWOEZYL4DhVwf/TQ5jXsGlx6CzERsYbqd6ov3OS1sc= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260701091216-9264d4444ce0/go.mod h1:ncZiIgraMh4F9lWDoJwB1TX395qZFR5bvnZcHbD0A1Q= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260706171511-193f044645f3 h1:z4coZM3fbnGxo7kXsG2L78h678b4j4wKQtcdvX7KMcA= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260706171511-193f044645f3/go.mod h1:ncZiIgraMh4F9lWDoJwB1TX395qZFR5bvnZcHbD0A1Q= github.com/smartcontractkit/chainlink-common/keystore v1.2.0 h1:1BH/b14CkGjArfzznlioQpIJiynECWVT48JUP9E277U= github.com/smartcontractkit/chainlink-common/keystore v1.2.0/go.mod h1:9R/74vN+bJ5PbkOyM/pUy/AeAZaRwYb/k4XPeXcbDio= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260626151909-052e55e62e62 h1:o7vfwNQjQbMKQ9YsZFQOxvU7RMXD/wKnZsX5N9sDS3w= diff --git a/system-tests/lib/go.mod b/system-tests/lib/go.mod index e1e8dfb3fd2..643c07d7728 100644 --- a/system-tests/lib/go.mod +++ b/system-tests/lib/go.mod @@ -37,7 +37,7 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.104 github.com/smartcontractkit/chainlink-aptos v0.0.0-20260609211101-71d38bd6a0a9 github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260415165642-49f23e4d76cc - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260701091216-9264d4444ce0 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260706171511-193f044645f3 github.com/smartcontractkit/chainlink-common/keystore v1.2.0 github.com/smartcontractkit/chainlink-deployments-framework v0.111.1-0.20260612191326-e31c0ae4cd54 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260623170329-4577ef4ba0ae diff --git a/system-tests/lib/go.sum b/system-tests/lib/go.sum index 430939337f4..3c450d40e44 100644 --- a/system-tests/lib/go.sum +++ b/system-tests/lib/go.sum @@ -1545,8 +1545,8 @@ github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260 github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260511195239-0f6e1b177fc7/go.mod h1:67YbnoglYD61Pz/jTVCgav9wFq7S35OU8UyQSvPllRw= github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260706093831-dad26b360094 h1:uMn1w/n95p+pSxK6hYNqji/sDaab8D0Cxuph9qUkM2g= github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260706093831-dad26b360094/go.mod h1:3XQmy2zQHrmufebP7dM+x595+gghxzyIKxK9wv91Rh8= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260701091216-9264d4444ce0 h1:ywWOEZYL4DhVwf/TQ5jXsGlx6CzERsYbqd6ov3OS1sc= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260701091216-9264d4444ce0/go.mod h1:ncZiIgraMh4F9lWDoJwB1TX395qZFR5bvnZcHbD0A1Q= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260706171511-193f044645f3 h1:z4coZM3fbnGxo7kXsG2L78h678b4j4wKQtcdvX7KMcA= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260706171511-193f044645f3/go.mod h1:ncZiIgraMh4F9lWDoJwB1TX395qZFR5bvnZcHbD0A1Q= github.com/smartcontractkit/chainlink-common/keystore v1.2.0 h1:1BH/b14CkGjArfzznlioQpIJiynECWVT48JUP9E277U= github.com/smartcontractkit/chainlink-common/keystore v1.2.0/go.mod h1:9R/74vN+bJ5PbkOyM/pUy/AeAZaRwYb/k4XPeXcbDio= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260626151909-052e55e62e62 h1:o7vfwNQjQbMKQ9YsZFQOxvU7RMXD/wKnZsX5N9sDS3w= diff --git a/system-tests/tests/go.mod b/system-tests/tests/go.mod index f576eb686de..71e9891f118 100644 --- a/system-tests/tests/go.mod +++ b/system-tests/tests/go.mod @@ -62,7 +62,7 @@ require ( github.com/rs/zerolog v1.35.1 github.com/smartcontractkit/chain-selectors v1.0.104 github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260415165642-49f23e4d76cc - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260701091216-9264d4444ce0 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260706171511-193f044645f3 github.com/smartcontractkit/chainlink-common/keystore v1.2.0 github.com/smartcontractkit/chainlink-deployments-framework v0.111.1-0.20260612191326-e31c0ae4cd54 github.com/smartcontractkit/chainlink-evm/contracts/cre/gobindings v0.0.0-20260403151002-2c91155b5501 diff --git a/system-tests/tests/go.sum b/system-tests/tests/go.sum index f177e4c8e41..abfe6566ba9 100644 --- a/system-tests/tests/go.sum +++ b/system-tests/tests/go.sum @@ -1559,8 +1559,8 @@ github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260 github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260511195239-0f6e1b177fc7/go.mod h1:67YbnoglYD61Pz/jTVCgav9wFq7S35OU8UyQSvPllRw= github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260706093831-dad26b360094 h1:uMn1w/n95p+pSxK6hYNqji/sDaab8D0Cxuph9qUkM2g= github.com/smartcontractkit/chainlink-ccv v0.0.2-0.20260706093831-dad26b360094/go.mod h1:3XQmy2zQHrmufebP7dM+x595+gghxzyIKxK9wv91Rh8= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260701091216-9264d4444ce0 h1:ywWOEZYL4DhVwf/TQ5jXsGlx6CzERsYbqd6ov3OS1sc= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260701091216-9264d4444ce0/go.mod h1:ncZiIgraMh4F9lWDoJwB1TX395qZFR5bvnZcHbD0A1Q= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260706171511-193f044645f3 h1:z4coZM3fbnGxo7kXsG2L78h678b4j4wKQtcdvX7KMcA= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260706171511-193f044645f3/go.mod h1:ncZiIgraMh4F9lWDoJwB1TX395qZFR5bvnZcHbD0A1Q= github.com/smartcontractkit/chainlink-common/keystore v1.2.0 h1:1BH/b14CkGjArfzznlioQpIJiynECWVT48JUP9E277U= github.com/smartcontractkit/chainlink-common/keystore v1.2.0/go.mod h1:9R/74vN+bJ5PbkOyM/pUy/AeAZaRwYb/k4XPeXcbDio= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260626151909-052e55e62e62 h1:o7vfwNQjQbMKQ9YsZFQOxvU7RMXD/wKnZsX5N9sDS3w= diff --git a/system-tests/tests/smoke/cre/vault_don_test.go b/system-tests/tests/smoke/cre/vault_don_test.go index 86a58d1f969..c92ebe5cbe3 100644 --- a/system-tests/tests/smoke/cre/vault_don_test.go +++ b/system-tests/tests/smoke/cre/vault_don_test.go @@ -641,6 +641,7 @@ func sendConcurrentVaultCreate(t *testing.T, gwURL, requestID string, jsonReques require.Nil(t, parsed.Error, "gateway returned error: %v", parsed.Error) require.Equal(t, requestID, parsed.ID) require.Equal(t, vaulttypes.MethodSecretsCreate, parsed.Method) + requireSignedPayloadRequestID(t, vaulttypes.MethodSecretsCreate, requestID, authorizedOwner, parsed.Result.Payload) var createResp vault_helpers.CreateSecretsResponse require.NoError(t, protojson.Unmarshal(parsed.Result.Payload, &createResp), "failed to decode CreateSecretsResponse") require.Len(t, createResp.Responses, len(namespaces), "Expected one item in the response per namespace") @@ -885,6 +886,11 @@ func TestVaultJSONOmitUnpopulatedEnabled_CRESettingDefaultsDisabled(t *testing.T require.False(t, cresettings.Default.VaultJSONOmitUnpopulatedEnabled.DefaultValue) } +func TestVaultSignedResponseRequestIDEnabled_CRESettingDefaultsDisabled(t *testing.T) { + t.Parallel() + require.False(t, cresettings.Default.VaultSignedResponseRequestIDEnabled.DefaultValue) +} + func TestVaultStaticTopologies_LoadExpectedConfig(t *testing.T) { t.Parallel() dockerHost := strings.TrimPrefix(framework.HostDockerInternal(), "http://") diff --git a/system-tests/tests/smoke/cre/vault_don_test_helpers.go b/system-tests/tests/smoke/cre/vault_don_test_helpers.go index 2a4e26bd029..572602779a9 100644 --- a/system-tests/tests/smoke/cre/vault_don_test_helpers.go +++ b/system-tests/tests/smoke/cre/vault_don_test_helpers.go @@ -467,6 +467,37 @@ func buildSecretIdentifiers(secretID, owner string, namespaces []string) []*vaul return identifiers } +// requireSignedPayloadRequestID asserts the vault OCR signed payload carries the gateway +// request ID inside the signed bytes. The gateway prefixes authorizedOwner to the user +// request ID (owner::requestID) before forwarding to the vault DON; OCR signs that value. +// JSON-RPC response ID is stripped back to the user request ID and is not signature-covered. +func requireSignedPayloadRequestID(t *testing.T, method, userRequestID, authorizedOwner string, payload json.RawMessage) { + t.Helper() + + require.NotEmpty(t, userRequestID) + require.NotEmpty(t, payload) + + signedRequestID, err := vaultutils.SignedPayloadRequestID(method, payload) + require.NoError(t, err) + if signedRequestID == "" { + // VaultSignedResponseRequestIDEnabled is off on vault nodes; skip until the gate is enabled in the test stack. + return + } + + expectedSuffix := vaulttypes.RequestIDSeparator + userRequestID + require.True(t, strings.HasSuffix(signedRequestID, expectedSuffix), + "signed payload requestId %q should end with gateway user request ID suffix %q", signedRequestID, expectedSuffix) + + if authorizedOwner != "" { + require.Equal(t, authorizedOwner+expectedSuffix, signedRequestID, + "signed payload requestId must match gateway-prefixed request ID") + return + } + + ownerPrefix := strings.TrimSuffix(signedRequestID, expectedSuffix) + require.NotEmpty(t, ownerPrefix, "signed payload requestId should include gateway authorized owner prefix") +} + func sendVaultSignedOCRRequestToGateway(t *testing.T, gatewayURL string, jsonRequest jsonrpc.Request[json.RawMessage], authorizedOwner string) jsonrpc.Response[vaulttypes.SignedOCRResponse] { t.Helper() @@ -501,6 +532,8 @@ func sendVaultSignedOCRRequestToGateway(t *testing.T, gatewayURL string, jsonReq require.Equal(t, jsonrpc.JsonRpcVersion, jsonResponse.Version) + requireSignedPayloadRequestID(t, jsonRequest.Method, jsonRequest.ID, authorizedOwner, jsonResponse.Result.Payload) + return jsonResponse }