From fa65a23ad63a338e10e2479dfe1f968165657e65 Mon Sep 17 00:00:00 2001 From: Tejaswi Nadahalli Date: Mon, 23 Mar 2026 20:55:35 +0100 Subject: [PATCH 01/12] [CRE] [2/5] Relay DON node handler for confidential relay Add GatewayConnectorHandler that validates Nitro attestation and proxies enclave requests to VaultDON (secrets_get) and capability DONs (capability_exec). Supports multi-PCR validation for enclave pools. Part of #21635 --- .../capabilities/confidentialrelay/handler.go | 495 ++++++++++++++++++ .../confidentialrelay/handler_test.go | 342 ++++++++++++ .../capabilities/confidentialrelay/service.go | 70 +++ deployment/go.mod | 2 +- deployment/go.sum | 4 +- go.md | 8 + go.mod | 4 +- go.sum | 9 +- integration-tests/go.mod | 2 +- integration-tests/go.sum | 4 +- integration-tests/load/go.mod | 2 +- integration-tests/load/go.sum | 4 +- 12 files changed, 934 insertions(+), 12 deletions(-) create mode 100644 core/capabilities/confidentialrelay/handler.go create mode 100644 core/capabilities/confidentialrelay/handler_test.go create mode 100644 core/capabilities/confidentialrelay/service.go diff --git a/core/capabilities/confidentialrelay/handler.go b/core/capabilities/confidentialrelay/handler.go new file mode 100644 index 00000000000..e70157f4a23 --- /dev/null +++ b/core/capabilities/confidentialrelay/handler.go @@ -0,0 +1,495 @@ +package confidentialrelay + +import ( + "context" + "encoding/base64" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" + + "github.com/smartcontractkit/chainlink-common/pkg/beholder" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + vault "github.com/smartcontractkit/chainlink-common/pkg/capabilities/actions/vault" + confidentialrelaytypes "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/actions/confidentialrelay" + jsonrpc "github.com/smartcontractkit/chainlink-common/pkg/jsonrpc2" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/types/core" + sdkpb "github.com/smartcontractkit/chainlink-protos/cre/go/sdk" + "github.com/smartcontractkit/chainlink-protos/cre/go/values" + valuespb "github.com/smartcontractkit/chainlink-protos/cre/go/values/pb" + + "github.com/smartcontractkit/chainlink-common/pkg/teeattestation" + "github.com/smartcontractkit/chainlink-common/pkg/teeattestation/nitro" + + vaulttypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/vault/vaulttypes" +) + +var _ core.GatewayConnectorHandler = (*Handler)(nil) + +const HandlerName = "EnclaveRelayHandler" + +type handlerMetrics struct { + requestInternalError metric.Int64Counter + requestSuccess metric.Int64Counter +} + +func newMetrics() (*handlerMetrics, error) { + requestInternalError, err := beholder.GetMeter().Int64Counter("enclave_relay_request_internal_error") + if err != nil { + return nil, fmt.Errorf("failed to register internal error counter: %w", err) + } + requestSuccess, err := beholder.GetMeter().Int64Counter("enclave_relay_request_success") + if err != nil { + return nil, fmt.Errorf("failed to register success counter: %w", err) + } + return &handlerMetrics{ + requestInternalError: requestInternalError, + requestSuccess: requestSuccess, + }, nil +} + +type gatewayConnector interface { + SendToGateway(ctx context.Context, gatewayID string, resp *jsonrpc.Response[json.RawMessage]) error + AddHandler(ctx context.Context, methods []string, handler core.GatewayConnectorHandler) error + RemoveHandler(ctx context.Context, methods []string) error +} + +// attestationValidatorFunc validates a Nitro attestation document. +type attestationValidatorFunc func(attestation []byte, expectedUserData []byte, trustedMeasurements []byte, caRootsPEM string) error + +// Handler processes enclave relay requests from the gateway. +// It validates Nitro attestations and proxies requests to VaultDON or capability DONs. +type Handler struct { + services.Service + eng *services.Engine + + capRegistry core.CapabilitiesRegistry + gatewayConnector gatewayConnector + trustedPCRs []byte + lggr logger.Logger + metrics *handlerMetrics + + // validateAttestation validates Nitro attestation documents. + // Defaults to the real nitro validator; overridden in tests. + validateAttestation attestationValidatorFunc + + // caRootsPEM is an optional PEM-encoded CA root certificate for attestation + // validation. Empty string uses DefaultCARoots (production default). + caRootsPEM string +} + +func NewHandler(capRegistry core.CapabilitiesRegistry, conn gatewayConnector, trustedPCRs []byte, lggr logger.Logger, caRootsPEM ...string) (*Handler, error) { + m, err := newMetrics() + if err != nil { + return nil, fmt.Errorf("failed to create metrics: %w", err) + } + + var roots string + if len(caRootsPEM) > 0 { + roots = caRootsPEM[0] + } + h := &Handler{ + capRegistry: capRegistry, + gatewayConnector: conn, + trustedPCRs: trustedPCRs, + lggr: logger.Named(lggr, HandlerName), + metrics: m, + validateAttestation: nitro.ValidateAttestation, + caRootsPEM: roots, + } + h.Service, h.eng = services.Config{ + Name: HandlerName, + Start: h.start, + Close: h.close, + }.NewServiceEngine(lggr) + return h, nil +} + +func (h *Handler) start(ctx context.Context) error { + if err := h.gatewayConnector.AddHandler(ctx, h.Methods(), h); err != nil { + return fmt.Errorf("failed to add enclave relay handler to connector: %w", err) + } + return nil +} + +func (h *Handler) close() error { + if err := h.gatewayConnector.RemoveHandler(context.Background(), h.Methods()); err != nil { + return fmt.Errorf("failed to remove enclave relay handler from connector: %w", err) + } + return nil +} + +func (h *Handler) ID(_ context.Context) (string, error) { + return HandlerName, nil +} + +func (h *Handler) Methods() []string { + return []string{confidentialrelaytypes.MethodSecretsGet, confidentialrelaytypes.MethodCapabilityExec} +} + +func (h *Handler) HandleGatewayMessage(ctx context.Context, gatewayID string, req *jsonrpc.Request[json.RawMessage]) error { + h.lggr.Debugw("received message from gateway", "gatewayID", gatewayID, "requestID", req.ID) + + var response *jsonrpc.Response[json.RawMessage] + switch req.Method { + case confidentialrelaytypes.MethodSecretsGet: + response = h.handleSecretsGet(ctx, gatewayID, req) + case confidentialrelaytypes.MethodCapabilityExec: + response = h.handleCapabilityExecute(ctx, gatewayID, req) + default: + response = h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrMethodNotFound, errors.New("unsupported method: "+req.Method)) + } + + if err := h.gatewayConnector.SendToGateway(ctx, gatewayID, response); err != nil { + h.lggr.Errorw("failed to send message to gateway", "gatewayID", gatewayID, "err", err) + return err + } + + h.lggr.Infow("sent message to gateway", "gatewayID", gatewayID, "requestID", req.ID) + if response != nil && response.Error == nil { + h.metrics.requestSuccess.Add(ctx, 1, metric.WithAttributes( + attribute.String("gateway_id", gatewayID), + )) + } + return nil +} + +func (h *Handler) handleSecretsGet(ctx context.Context, gatewayID string, req *jsonrpc.Request[json.RawMessage]) *jsonrpc.Response[json.RawMessage] { + if req.Params == nil { + return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInvalidParams, errors.New("missing params")) + } + var params confidentialrelaytypes.SecretsRequestParams + if err := json.Unmarshal(*req.Params, ¶ms); err != nil { + return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInvalidParams, err) + } + + att := params.Attestation + params.Attestation = "" + if err := h.verifyAttestationHash(att, params, confidentialrelaytypes.DomainSecretsGet); err != nil { + return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, err) + } + + vaultCap, err := h.capRegistry.GetExecutable(ctx, vault.CapabilityID) + if err != nil { + return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, fmt.Errorf("failed to get vault capability: %w", err)) + } + + if !common.IsHexAddress(params.Owner) { + return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInvalidParams, fmt.Errorf("invalid owner address: %q", params.Owner)) + } + // Normalize owner to EIP-55 checksum format, matching how secrets are stored. + normalizedOwner := common.HexToAddress(params.Owner).Hex() + + vaultReq := &vault.GetSecretsRequest{ + Requests: make([]*vault.SecretRequest, 0, len(params.Secrets)), + } + for _, s := range params.Secrets { + namespace := s.Namespace + if namespace == "" { + namespace = vaulttypes.DefaultNamespace + } + vaultReq.Requests = append(vaultReq.Requests, &vault.SecretRequest{ + Id: &vault.SecretIdentifier{ + Key: s.Key, + Namespace: namespace, + Owner: normalizedOwner, + }, + EncryptionKeys: []string{params.EnclavePublicKey}, + }) + } + + anypbReq, err := anypb.New(vaultReq) + if err != nil { + return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, fmt.Errorf("failed to wrap vault request: %w", err)) + } + + localNode, err := h.capRegistry.LocalNode(ctx) + if err != nil { + return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, fmt.Errorf("failed to get local node: %w", err)) + } + + capResp, err := vaultCap.Execute(ctx, capabilities.CapabilityRequest{ + Payload: anypbReq, + Method: vault.MethodGetSecrets, + CapabilityId: vault.CapabilityID, + Config: values.EmptyMap(), + Metadata: capabilities.RequestMetadata{ + WorkflowID: params.WorkflowID, + WorkflowOwner: params.Owner, + WorkflowExecutionID: params.ExecutionID, + WorkflowDonID: localNode.WorkflowDON.ID, + WorkflowDonConfigVersion: localNode.WorkflowDON.ConfigVersion, + ReferenceID: req.ID, + }, + }) + if err != nil { + return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, fmt.Errorf("vault execute failed: %w", err)) + } + + vaultResp := &vault.GetSecretsResponse{} + if err = capResp.Payload.UnmarshalTo(vaultResp); err != nil { + return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, fmt.Errorf("failed to unmarshal vault response: %w", err)) + } + + result, err := translateVaultResponse(vaultResp, params.EnclavePublicKey) + if err != nil { + return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, err) + } + + return h.jsonResponse(req, result) +} + +// resolveDONID determines the DON ID for a capability. +// Keeping for potential future use by handleCapabilityExecute. +func (h *Handler) resolveDONID(ctx context.Context, capability capabilities.ExecutableCapability) (uint32, error) { //nolint:unused // reserved for future multi-DON routing in handleCapabilityExecute + info, err := capability.Info(ctx) + if err != nil { + return 0, fmt.Errorf("failed to get capability info: %w", err) + } + if info.IsLocal { + localNode, err := h.capRegistry.LocalNode(ctx) + if err != nil { + return 0, fmt.Errorf("failed to get local node: %w", err) + } + return localNode.WorkflowDON.ID, nil + } + if info.DON == nil { + return 0, errors.New("capability is not associated with any DON") + } + return info.DON.ID, nil +} + +// translateVaultResponse converts a vault GetSecretsResponse to the enclave relay protocol format. +// Encoding conversion: hex (vault) -> base64 (enclave relay). +func translateVaultResponse(vaultResp *vault.GetSecretsResponse, enclaveKey string) (*confidentialrelaytypes.SecretsResponseResult, error) { + result := &confidentialrelaytypes.SecretsResponseResult{} + + for _, sr := range vaultResp.Responses { + if sr.GetError() != "" { + return nil, fmt.Errorf("vault error for secret %s/%s: %s", sr.Id.GetNamespace(), sr.Id.GetKey(), sr.GetError()) + } + + data := sr.GetData() + if data == nil { + return nil, fmt.Errorf("vault returned no data for secret %s/%s", sr.Id.GetNamespace(), sr.Id.GetKey()) + } + + encryptedBytes, err := hex.DecodeString(data.EncryptedValue) + if err != nil { + return nil, fmt.Errorf("failed to decode encrypted value for %s: %w", sr.Id.GetKey(), err) + } + + var shares []string + for _, es := range data.EncryptedDecryptionKeyShares { + if es.EncryptionKey == enclaveKey { + for _, share := range es.Shares { + shareBytes, err := hex.DecodeString(share) + if err != nil { + return nil, fmt.Errorf("failed to decode share: %w", err) + } + shares = append(shares, base64.StdEncoding.EncodeToString(shareBytes)) + } + break + } + } + if len(shares) == 0 { + return nil, fmt.Errorf("no shares found for enclave key in secret %s/%s", sr.Id.GetNamespace(), sr.Id.GetKey()) + } + + result.Secrets = append(result.Secrets, confidentialrelaytypes.SecretEntry{ + ID: confidentialrelaytypes.SecretIdentifier{ + Key: sr.Id.GetKey(), + Namespace: sr.Id.GetNamespace(), + }, + Ciphertext: base64.StdEncoding.EncodeToString(encryptedBytes), + EncryptedShares: shares, + }) + } + + return result, nil +} + +func (h *Handler) handleCapabilityExecute(ctx context.Context, gatewayID string, req *jsonrpc.Request[json.RawMessage]) *jsonrpc.Response[json.RawMessage] { + if req.Params == nil { + return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInvalidParams, errors.New("missing params")) + } + var params confidentialrelaytypes.CapabilityRequestParams + if err := json.Unmarshal(*req.Params, ¶ms); err != nil { + return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInvalidParams, err) + } + + att := params.Attestation + params.Attestation = "" + if err := h.verifyAttestationHash(att, params, confidentialrelaytypes.DomainCapabilityExec); err != nil { + return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, err) + } + + capability, err := h.capRegistry.GetExecutable(ctx, params.CapabilityID) + if err != nil { + return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, fmt.Errorf("capability not found: %w", err)) + } + + payloadBytes, err := base64.StdEncoding.DecodeString(params.Payload) + if err != nil { + return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInvalidParams, fmt.Errorf("failed to decode payload: %w", err)) + } + + var sdkReq sdkpb.CapabilityRequest + if err := proto.Unmarshal(payloadBytes, &sdkReq); err != nil { + return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInvalidParams, fmt.Errorf("failed to unmarshal capability request: %w", err)) + } + + capReq := capabilities.CapabilityRequest{ + Payload: sdkReq.Payload, + Method: sdkReq.Method, + CapabilityId: params.CapabilityID, + Metadata: capabilities.RequestMetadata{ + WorkflowID: params.WorkflowID, + }, + } + + // Backward compatibility: extract values.Map from Payload into Inputs + // for old-style capabilities that only look at Inputs. + if sdkReq.Payload != nil { + var valPB valuespb.Value + if sdkReq.Payload.UnmarshalTo(&valPB) == nil { + if v, vErr := values.FromProto(&valPB); vErr == nil { + if m, ok := v.(*values.Map); ok { + capReq.Inputs = m + } + } + } + } + + capResp, execErr := capability.Execute(ctx, capReq) + + var result confidentialrelaytypes.CapabilityResponseResult + if execErr != nil { + result.Error = execErr.Error() + } else { + sdkResp, err := toSDKCapabilityResponse(capResp) + if err != nil { + return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, fmt.Errorf("converting capability response: %w", err)) + } + respBytes, err := proto.Marshal(sdkResp) + if err != nil { + return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, fmt.Errorf("marshalling capability response: %w", err)) + } + result.Payload = base64.StdEncoding.EncodeToString(respBytes) + } + + return h.jsonResponse(req, result) +} + +func (h *Handler) verifyAttestationHash(attestationB64 string, cleanParams any, domainTag string) error { + if attestationB64 == "" { + return errors.New("missing attestation") + } + + paramsJSON, err := json.Marshal(cleanParams) + if err != nil { + return fmt.Errorf("failed to marshal params for attestation: %w", err) + } + + hash := teeattestation.DomainHash(domainTag, paramsJSON) + + attestationBytes, err := base64.StdEncoding.DecodeString(attestationB64) + if err != nil { + return fmt.Errorf("failed to decode attestation: %w", err) + } + + // Each enclave instance has different PCR values (WireGuard keys baked + // per CID), so trustedPCRs may be a JSON array of PCR objects. Try each + // set and succeed if any match, same as pool.go's + // validateAttestationAgainstMultipleMeasurements. + var pcrArray []json.RawMessage + if json.Unmarshal(h.trustedPCRs, &pcrArray) == nil && len(pcrArray) > 0 { + var validationErr error + for _, pcrs := range pcrArray { + err := h.validateAttestation(attestationBytes, hash, pcrs, h.caRootsPEM) + if err == nil { + return nil + } + validationErr = errors.Join(validationErr, err) + } + return fmt.Errorf("no trusted PCR set matched: %w", validationErr) + } + + return h.validateAttestation(attestationBytes, hash, h.trustedPCRs, h.caRootsPEM) +} + +func toSDKCapabilityResponse(capResp capabilities.CapabilityResponse) (*sdkpb.CapabilityResponse, error) { + if capResp.Payload != nil { + return &sdkpb.CapabilityResponse{ + Response: &sdkpb.CapabilityResponse_Payload{Payload: capResp.Payload}, + }, nil + } + + if capResp.Value != nil { + valProto := values.Proto(capResp.Value) + wrapped, err := anypb.New(valProto) + if err != nil { + return nil, fmt.Errorf("wrapping value map in Any: %w", err) + } + return &sdkpb.CapabilityResponse{ + Response: &sdkpb.CapabilityResponse_Payload{Payload: wrapped}, + }, nil + } + + return &sdkpb.CapabilityResponse{}, nil +} + +func (h *Handler) jsonResponse(req *jsonrpc.Request[json.RawMessage], result any) *jsonrpc.Response[json.RawMessage] { + resultBytes, err := json.Marshal(result) + if err != nil { + h.lggr.Errorw("failed to marshal response", "err", err) + return &jsonrpc.Response[json.RawMessage]{ + Version: jsonrpc.JsonRpcVersion, + ID: req.ID, + Method: req.Method, + Error: &jsonrpc.WireError{ + Code: jsonrpc.ErrInternal, + Message: err.Error(), + }, + } + } + resultJSON := json.RawMessage(resultBytes) + return &jsonrpc.Response[json.RawMessage]{ + Version: jsonrpc.JsonRpcVersion, + ID: req.ID, + Method: req.Method, + Result: &resultJSON, + } +} + +func (h *Handler) errorResponse( + ctx context.Context, + gatewayID string, + req *jsonrpc.Request[json.RawMessage], + errorCode int64, + err error, +) *jsonrpc.Response[json.RawMessage] { + h.lggr.Errorw("request error", "errorCode", errorCode, "err", err) + h.metrics.requestInternalError.Add(ctx, 1, metric.WithAttributes( + attribute.String("gateway_id", gatewayID), + attribute.Int64("error_code", errorCode), + )) + + return &jsonrpc.Response[json.RawMessage]{ + Version: jsonrpc.JsonRpcVersion, + ID: req.ID, + Method: req.Method, + Error: &jsonrpc.WireError{ + Code: errorCode, + Message: err.Error(), + }, + } +} diff --git a/core/capabilities/confidentialrelay/handler_test.go b/core/capabilities/confidentialrelay/handler_test.go new file mode 100644 index 00000000000..a0c494a32f0 --- /dev/null +++ b/core/capabilities/confidentialrelay/handler_test.go @@ -0,0 +1,342 @@ +package confidentialrelay + +import ( + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" + + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + confidentialrelaytypes "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/actions/confidentialrelay" + jsonrpc "github.com/smartcontractkit/chainlink-common/pkg/jsonrpc2" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/types/core" + sdkpb "github.com/smartcontractkit/chainlink-protos/cre/go/sdk" + "github.com/smartcontractkit/chainlink-protos/cre/go/values" + valuespb "github.com/smartcontractkit/chainlink-protos/cre/go/values/pb" +) + +func makeCapabilityPayload(t *testing.T, inputs map[string]any) string { + t.Helper() + wrapped, err := values.Wrap(inputs) + require.NoError(t, err) + payload, err := anypb.New(values.Proto(wrapped)) + require.NoError(t, err) + sdkReq := &sdkpb.CapabilityRequest{ + Id: "my-cap@1.0.0", + Payload: payload, + Method: "Execute", + } + b, err := proto.Marshal(sdkReq) + require.NoError(t, err) + return base64.StdEncoding.EncodeToString(b) +} + +const testAttestationB64 = "ZHVtbXktYXR0ZXN0YXRpb24=" // base64("dummy-attestation") + +func noopValidator(_ []byte, _, _ []byte, _ string) error { return nil } + +type mockGatewayConnector struct { + lastResp *jsonrpc.Response[json.RawMessage] + addedMethods []string + removed bool +} + +func (m *mockGatewayConnector) SendToGateway(_ context.Context, _ string, resp *jsonrpc.Response[json.RawMessage]) error { + m.lastResp = resp + return nil +} +func (m *mockGatewayConnector) AddHandler(_ context.Context, methods []string, _ core.GatewayConnectorHandler) error { + m.addedMethods = methods + return nil +} +func (m *mockGatewayConnector) RemoveHandler(_ context.Context, _ []string) error { + m.removed = true + return nil +} + +type mockExecutable struct { + infoResult capabilities.CapabilityInfo + infoErr error + execResult capabilities.CapabilityResponse + execErr error + lastRequest *capabilities.CapabilityRequest +} + +func (m *mockExecutable) Info(_ context.Context) (capabilities.CapabilityInfo, error) { + return m.infoResult, m.infoErr +} +func (m *mockExecutable) Execute(_ context.Context, req capabilities.CapabilityRequest) (capabilities.CapabilityResponse, error) { + m.lastRequest = &req + return m.execResult, m.execErr +} +func (m *mockExecutable) RegisterToWorkflow(_ context.Context, _ capabilities.RegisterToWorkflowRequest) error { + return nil +} +func (m *mockExecutable) UnregisterFromWorkflow(_ context.Context, _ capabilities.UnregisterFromWorkflowRequest) error { + return nil +} + +type mockCapRegistry struct { + core.UnimplementedCapabilitiesRegistry + executables map[string]*mockExecutable + configs map[string]capabilities.CapabilityConfiguration + localNode capabilities.Node +} + +func (m *mockCapRegistry) GetExecutable(_ context.Context, id string) (capabilities.ExecutableCapability, error) { + if exec, ok := m.executables[id]; ok { + return exec, nil + } + return nil, fmt.Errorf("capability not found: %s", id) +} +func (m *mockCapRegistry) ConfigForCapability(_ context.Context, capID string, _ uint32) (capabilities.CapabilityConfiguration, error) { + if cfg, ok := m.configs[capID]; ok { + return cfg, nil + } + return capabilities.CapabilityConfiguration{}, fmt.Errorf("config not found: %s", capID) +} +func (m *mockCapRegistry) LocalNode(_ context.Context) (capabilities.Node, error) { + return m.localNode, nil +} + +func newTestHandler(t *testing.T, registry core.CapabilitiesRegistry, gwConn gatewayConnector) *Handler { + t.Helper() + lggr, err := logger.New() + require.NoError(t, err) + h, err := NewHandler(registry, gwConn, []byte(`{}`), lggr) + require.NoError(t, err) + h.validateAttestation = noopValidator + return h +} + +func makeRequest(t *testing.T, method string, params any) *jsonrpc.Request[json.RawMessage] { + t.Helper() + b, err := json.Marshal(params) + require.NoError(t, err) + raw := json.RawMessage(b) + return &jsonrpc.Request[json.RawMessage]{ + Method: method, + ID: "req-1", + Params: &raw, + } +} + +func TestHandler_HandleGatewayMessage(t *testing.T) { + tests := []struct { + name string + registry func(t *testing.T) *mockCapRegistry + req func(t *testing.T) *jsonrpc.Request[json.RawMessage] + checkResp func(t *testing.T, resp *jsonrpc.Response[json.RawMessage]) + checkExecutable func(t *testing.T, reg *mockCapRegistry) + }{ + { + name: "capability execute success", + registry: func(_ *testing.T) *mockCapRegistry { + return &mockCapRegistry{ + executables: map[string]*mockExecutable{ + "my-cap@1.0.0": { + execResult: capabilities.CapabilityResponse{ + Payload: &anypb.Any{Value: []byte("result-proto-bytes")}, + }, + }, + }, + } + }, + req: func(t *testing.T) *jsonrpc.Request[json.RawMessage] { + return makeRequest(t, confidentialrelaytypes.MethodCapabilityExec, confidentialrelaytypes.CapabilityRequestParams{ + WorkflowID: "wf-1", + CapabilityID: "my-cap@1.0.0", + Payload: makeCapabilityPayload(t, map[string]any{"key": "val"}), + Attestation: testAttestationB64, + }) + }, + checkResp: func(t *testing.T, resp *jsonrpc.Response[json.RawMessage]) { + require.Nil(t, resp.Error) + var result confidentialrelaytypes.CapabilityResponseResult + require.NoError(t, json.Unmarshal(*resp.Result, &result)) + decoded, err := base64.StdEncoding.DecodeString(result.Payload) + require.NoError(t, err) + var capResp sdkpb.CapabilityResponse + require.NoError(t, proto.Unmarshal(decoded, &capResp)) + require.NotNil(t, capResp.GetPayload()) + assert.Equal(t, "result-proto-bytes", string(capResp.GetPayload().GetValue())) + assert.Empty(t, result.Error) + }, + }, + { + name: "capability execute sets Inputs from Payload for backward compat", + registry: func(_ *testing.T) *mockCapRegistry { + return &mockCapRegistry{ + executables: map[string]*mockExecutable{ + "my-cap@1.0.0": { + execResult: capabilities.CapabilityResponse{}, + }, + }, + } + }, + req: func(t *testing.T) *jsonrpc.Request[json.RawMessage] { + return makeRequest(t, confidentialrelaytypes.MethodCapabilityExec, confidentialrelaytypes.CapabilityRequestParams{ + WorkflowID: "wf-1", + CapabilityID: "my-cap@1.0.0", + Payload: makeCapabilityPayload(t, map[string]any{"echo": "hello"}), + Attestation: testAttestationB64, + }) + }, + checkResp: func(t *testing.T, resp *jsonrpc.Response[json.RawMessage]) { + require.Nil(t, resp.Error) + }, + checkExecutable: func(t *testing.T, reg *mockCapRegistry) { + exec := reg.executables["my-cap@1.0.0"] + require.NotNil(t, exec.lastRequest, "Execute should have been called") + require.NotNil(t, exec.lastRequest.Payload) + var valPB valuespb.Value + require.NoError(t, exec.lastRequest.Payload.UnmarshalTo(&valPB)) + require.NotNil(t, exec.lastRequest.Inputs) + unwrapped, err := exec.lastRequest.Inputs.Unwrap() + require.NoError(t, err) + m, ok := unwrapped.(map[string]any) + require.True(t, ok) + assert.Equal(t, "hello", m["echo"]) + }, + }, + { + name: "capability execute attestation failure", + registry: func(_ *testing.T) *mockCapRegistry { + return &mockCapRegistry{} + }, + req: func(t *testing.T) *jsonrpc.Request[json.RawMessage] { + return makeRequest(t, confidentialrelaytypes.MethodCapabilityExec, confidentialrelaytypes.CapabilityRequestParams{ + WorkflowID: "wf-1", + CapabilityID: "my-cap@1.0.0", + Payload: base64.StdEncoding.EncodeToString([]byte("payload")), + }) + }, + checkResp: func(t *testing.T, resp *jsonrpc.Response[json.RawMessage]) { + require.NotNil(t, resp.Error) + assert.Equal(t, jsonrpc.ErrInternal, resp.Error.Code) + }, + }, + { + name: "capability execute not found", + registry: func(_ *testing.T) *mockCapRegistry { + return &mockCapRegistry{executables: map[string]*mockExecutable{}} + }, + req: func(t *testing.T) *jsonrpc.Request[json.RawMessage] { + return makeRequest(t, confidentialrelaytypes.MethodCapabilityExec, confidentialrelaytypes.CapabilityRequestParams{ + WorkflowID: "wf-1", + CapabilityID: "missing-cap@1.0.0", + Payload: base64.StdEncoding.EncodeToString([]byte("payload")), + Attestation: testAttestationB64, + }) + }, + checkResp: func(t *testing.T, resp *jsonrpc.Response[json.RawMessage]) { + require.NotNil(t, resp.Error) + assert.Equal(t, jsonrpc.ErrInternal, resp.Error.Code) + assert.Contains(t, resp.Error.Message, "capability not found") + }, + }, + { + name: "capability execute error returned in result", + registry: func(_ *testing.T) *mockCapRegistry { + return &mockCapRegistry{ + executables: map[string]*mockExecutable{ + "fail-cap@1.0.0": {execErr: errors.New("execution failed")}, + }, + } + }, + req: func(t *testing.T) *jsonrpc.Request[json.RawMessage] { + sdkReq := &sdkpb.CapabilityRequest{Id: "fail-cap@1.0.0", Method: "Execute"} + b, err := proto.Marshal(sdkReq) + require.NoError(t, err) + return makeRequest(t, confidentialrelaytypes.MethodCapabilityExec, confidentialrelaytypes.CapabilityRequestParams{ + WorkflowID: "wf-1", + CapabilityID: "fail-cap@1.0.0", + Payload: base64.StdEncoding.EncodeToString(b), + Attestation: testAttestationB64, + }) + }, + checkResp: func(t *testing.T, resp *jsonrpc.Response[json.RawMessage]) { + require.Nil(t, resp.Error) + var result confidentialrelaytypes.CapabilityResponseResult + require.NoError(t, json.Unmarshal(*resp.Result, &result)) + assert.Equal(t, "execution failed", result.Error) + assert.Empty(t, result.Payload) + }, + }, + { + name: "unsupported method", + registry: func(_ *testing.T) *mockCapRegistry { + return &mockCapRegistry{} + }, + req: func(t *testing.T) *jsonrpc.Request[json.RawMessage] { + return makeRequest(t, "unknown.method", nil) + }, + checkResp: func(t *testing.T, resp *jsonrpc.Response[json.RawMessage]) { + require.NotNil(t, resp.Error) + assert.Equal(t, jsonrpc.ErrMethodNotFound, resp.Error.Code) + }, + }, + { + name: "invalid params JSON", + registry: func(_ *testing.T) *mockCapRegistry { + return &mockCapRegistry{} + }, + req: func(_ *testing.T) *jsonrpc.Request[json.RawMessage] { + raw := json.RawMessage([]byte(`{invalid json`)) + return &jsonrpc.Request[json.RawMessage]{ + Method: confidentialrelaytypes.MethodCapabilityExec, + ID: "req-1", + Params: &raw, + } + }, + checkResp: func(t *testing.T, resp *jsonrpc.Response[json.RawMessage]) { + require.NotNil(t, resp.Error) + assert.Equal(t, jsonrpc.ErrInvalidParams, resp.Error.Code) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gwConn := &mockGatewayConnector{} + reg := tt.registry(t) + h := newTestHandler(t, reg, gwConn) + err := h.HandleGatewayMessage(t.Context(), "gw-1", tt.req(t)) + require.NoError(t, err) + require.NotNil(t, gwConn.lastResp) + tt.checkResp(t, gwConn.lastResp) + if tt.checkExecutable != nil { + tt.checkExecutable(t, reg) + } + }) + } +} + +func TestHandler_Lifecycle(t *testing.T) { + gwConn := &mockGatewayConnector{} + h := newTestHandler(t, &mockCapRegistry{}, gwConn) + + t.Run("start registers handler", func(t *testing.T) { + require.NoError(t, h.Start(t.Context())) + assert.Equal(t, h.Methods(), gwConn.addedMethods) + }) + + t.Run("close removes handler", func(t *testing.T) { + require.NoError(t, h.Close()) + assert.True(t, gwConn.removed) + }) + + t.Run("ID returns handler name", func(t *testing.T) { + id, err := h.ID(t.Context()) + require.NoError(t, err) + assert.Equal(t, HandlerName, id) + }) +} diff --git a/core/capabilities/confidentialrelay/service.go b/core/capabilities/confidentialrelay/service.go new file mode 100644 index 00000000000..b9ae214848f --- /dev/null +++ b/core/capabilities/confidentialrelay/service.go @@ -0,0 +1,70 @@ +package confidentialrelay + +import ( + "context" + "errors" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/types/core" + + gatewayconnector "github.com/smartcontractkit/chainlink/v2/core/capabilities/gateway_connector" +) + +// Service is a thin lifecycle wrapper around the confidential relay handler. +// The relay handler needs the gateway connector, which isn't available until +// the ServiceWrapper starts. This wrapper defers handler creation to Start(). +type Service struct { + services.Service + eng *services.Engine + + wrapper *gatewayconnector.ServiceWrapper + capRegistry core.CapabilitiesRegistry + trustedPCRs []byte + caRootsPEM string + lggr logger.Logger + + handler *Handler +} + +func NewService( + wrapper *gatewayconnector.ServiceWrapper, + capRegistry core.CapabilitiesRegistry, + trustedPCRs []byte, + caRootsPEM string, + lggr logger.Logger, +) *Service { + s := &Service{ + wrapper: wrapper, + capRegistry: capRegistry, + trustedPCRs: trustedPCRs, + caRootsPEM: caRootsPEM, + lggr: lggr, + } + s.Service, s.eng = services.Config{ + Name: "ConfidentialRelayService", + Start: s.start, + Close: s.close, + }.NewServiceEngine(lggr) + return s +} + +func (s *Service) start(ctx context.Context) error { + conn := s.wrapper.GetGatewayConnector() + if conn == nil { + return errors.New("gateway connector not available") + } + h, err := NewHandler(s.capRegistry, conn, s.trustedPCRs, s.lggr, s.caRootsPEM) + if err != nil { + return err + } + s.handler = h + return h.Start(ctx) +} + +func (s *Service) close() error { + if s.handler != nil { + return s.handler.Close() + } + return nil +} diff --git a/deployment/go.mod b/deployment/go.mod index e84ed0b4b2b..d743293223e 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -233,7 +233,7 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gagliardetto/treeout v0.1.4 // indirect github.com/gagliardetto/utilz v0.1.3 // indirect diff --git a/deployment/go.sum b/deployment/go.sum index 96952eb652c..ae4f6d0d6f3 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -519,8 +519,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gagliardetto/anchor-go v1.0.0 h1:YNt9I/9NOrNzz5uuzfzByAcbp39Ft07w63iPqC/wi34= diff --git a/go.md b/go.md index 3bedafbab43..58e13c7b65b 100644 --- a/go.md +++ b/go.md @@ -66,6 +66,8 @@ flowchart LR click chainlink-common/pkg/chipingress href "https://github.com/smartcontractkit/chainlink-common" chainlink-common/pkg/monitoring click chainlink-common/pkg/monitoring href "https://github.com/smartcontractkit/chainlink-common" + chainlink-common/pkg/teeattestation + click chainlink-common/pkg/teeattestation href "https://github.com/smartcontractkit/chainlink-common" chainlink-data-streams --> chainlink-common/keystore click chainlink-data-streams href "https://github.com/smartcontractkit/chainlink-data-streams" chainlink-evm --> chainlink-data-streams @@ -138,6 +140,7 @@ flowchart LR click chainlink-tron/relayer href "https://github.com/smartcontractkit/chainlink-tron" chainlink/v2 --> chainlink-automation chainlink/v2 --> chainlink-ccv + chainlink/v2 --> chainlink-common/pkg/teeattestation chainlink/v2 --> chainlink-evm/contracts/cre/gobindings chainlink/v2 --> chainlink-feeds chainlink/v2 --> chainlink-protos/ring/go @@ -188,6 +191,7 @@ flowchart LR chainlink-common/keystore chainlink-common/pkg/chipingress chainlink-common/pkg/monitoring + chainlink-common/pkg/teeattestation end click chainlink-common-repo href "https://github.com/smartcontractkit/chainlink-common" @@ -318,6 +322,8 @@ flowchart LR click chainlink-common/pkg/chipingress href "https://github.com/smartcontractkit/chainlink-common" chainlink-common/pkg/monitoring click chainlink-common/pkg/monitoring href "https://github.com/smartcontractkit/chainlink-common" + chainlink-common/pkg/teeattestation + click chainlink-common/pkg/teeattestation href "https://github.com/smartcontractkit/chainlink-common" chainlink-common/pkg/values click chainlink-common/pkg/values href "https://github.com/smartcontractkit/chainlink-common" chainlink-common/pkg/workflows/sdk/v2/pb --> chainlink-common/pkg/values @@ -530,6 +536,7 @@ flowchart LR click chainlink/system-tests/tests/smoke/cre/vaultsecret href "https://github.com/smartcontractkit/chainlink" chainlink/v2 --> chainlink-automation chainlink/v2 --> chainlink-ccv + chainlink/v2 --> chainlink-common/pkg/teeattestation chainlink/v2 --> chainlink-evm/contracts/cre/gobindings chainlink/v2 --> chainlink-feeds chainlink/v2 --> chainlink-protos/ring/go @@ -625,6 +632,7 @@ flowchart LR chainlink-common/keystore chainlink-common/pkg/chipingress chainlink-common/pkg/monitoring + chainlink-common/pkg/teeattestation chainlink-common/pkg/values chainlink-common/pkg/workflows/sdk/v2/pb end diff --git a/go.mod b/go.mod index 85ee2f01b1a..137f3c2ec36 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/esote/minmaxheap v1.0.0 github.com/ethereum/go-ethereum v1.17.1 github.com/fatih/color v1.18.0 - github.com/fxamacker/cbor/v2 v2.7.0 + github.com/fxamacker/cbor/v2 v2.9.0 github.com/gagliardetto/binary v0.8.0 github.com/gagliardetto/solana-go v1.13.0 github.com/getsentry/sentry-go v0.27.0 @@ -88,6 +88,7 @@ require ( github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843 github.com/smartcontractkit/chainlink-common/keystore v1.0.2 github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 + github.com/smartcontractkit/chainlink-common/pkg/teeattestation v0.0.0-20260326144312-af5248caa880 github.com/smartcontractkit/chainlink-data-streams v0.1.13 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260330133421-5151ea0c3b05 github.com/smartcontractkit/chainlink-evm/contracts/cre/gobindings v0.0.0-20260107191744-4b93f62cffe3 @@ -292,6 +293,7 @@ require ( github.com/hashicorp/yamux v0.1.2 // indirect github.com/hasura/go-graphql-client v0.15.1 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect + github.com/hf/nitrite v0.0.0-20241225144000-c2d5d3c4f303 // indirect github.com/holiman/billy v0.0.0-20250707135307-f2f9b9aae7db // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/huin/goupnp v1.3.0 // indirect diff --git a/go.sum b/go.sum index 528e58b8358..d85a017a6b9 100644 --- a/go.sum +++ b/go.sum @@ -425,8 +425,9 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gagliardetto/anchor-go v1.0.0 h1:YNt9I/9NOrNzz5uuzfzByAcbp39Ft07w63iPqC/wi34= @@ -743,6 +744,8 @@ github.com/hasura/go-graphql-client v0.15.1 h1:mCb5I+8Bk3FU3GKWvf/zDXkTh7FbGlqJm github.com/hasura/go-graphql-client v0.15.1/go.mod h1:jfSZtBER3or+88Q9vFhWHiFMPppfYILRyl+0zsgPIIw= github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= +github.com/hf/nitrite v0.0.0-20241225144000-c2d5d3c4f303 h1:XBSq4rXFUgD8ic6Mr7dBwJN/47yg87XpZQhiknfr4Cg= +github.com/hf/nitrite v0.0.0-20241225144000-c2d5d3c4f303/go.mod h1:ycRhVmo6wegyEl6WN+zXOHUTJvB0J2tiuH88q/McTK8= github.com/holiman/billy v0.0.0-20250707135307-f2f9b9aae7db h1:IZUYC/xb3giYwBLMnr8d0TGTzPKFGNTCGgGLoyeX330= github.com/holiman/billy v0.0.0-20250707135307-f2f9b9aae7db/go.mod h1:xTEYN9KCHxuYHs+NmrmzFcnvHMzLLNiGFafCb1n3Mfg= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= @@ -1243,6 +1246,8 @@ github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9 github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10/go.mod h1:oiDa54M0FwxevWwyAX773lwdWvFYYlYHHQV1LQ5HpWY= github.com/smartcontractkit/chainlink-common/pkg/monitoring v0.0.0-20251215152504-b1e41f508340 h1:PsjEI+5jZIz9AS4eOsLS5VpSWJINf38clXV3wryPyMk= github.com/smartcontractkit/chainlink-common/pkg/monitoring v0.0.0-20251215152504-b1e41f508340/go.mod h1:P/0OSXUlFaxxD4B/P6HWbxYtIRmmWGDJAvanq19879c= +github.com/smartcontractkit/chainlink-common/pkg/teeattestation v0.0.0-20260326144312-af5248caa880 h1:qLrZSgc7e/exxIE4M020A52ep6xN0tXOJESiots9DvE= +github.com/smartcontractkit/chainlink-common/pkg/teeattestation v0.0.0-20260326144312-af5248caa880/go.mod h1:+X7Cb8ysHfNnPd74htGIInzZMHfUrpdDrjlr6+VW0gU= github.com/smartcontractkit/chainlink-data-streams v0.1.13 h1:YOmt545DW6U0SyaqBf+NTGDLm1yMurVI7yOvxP5hlJk= github.com/smartcontractkit/chainlink-data-streams v0.1.13/go.mod h1:00aL7OK0BJdF9gn/4t4f/pctUu2VLwwfA8G/tl9rCrM= github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260330133421-5151ea0c3b05 h1:mUaNoTKE3lqlANSOlQmiYDIAjJoqHiShw+r5rl1xh7g= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 0e76e93edb5..99d455a3036 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -213,7 +213,7 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gagliardetto/anchor-go v1.0.0 // indirect github.com/gagliardetto/binary v0.8.0 // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index fc524bc8e82..be7aeaf6d19 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -504,8 +504,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gagliardetto/anchor-go v1.0.0 h1:YNt9I/9NOrNzz5uuzfzByAcbp39Ft07w63iPqC/wi34= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 689db65f6a0..910a687d96c 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -208,7 +208,7 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/ferranbt/fastssz v0.1.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gagliardetto/anchor-go v1.0.0 // indirect github.com/gagliardetto/binary v0.8.0 // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 8dae65f12b1..479d468f23a 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -569,8 +569,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gagliardetto/anchor-go v1.0.0 h1:YNt9I/9NOrNzz5uuzfzByAcbp39Ft07w63iPqC/wi34= From c302c921980851dc684dd404d2d36c24180c0383 Mon Sep 17 00:00:00 2001 From: Tejaswi Nadahalli Date: Tue, 24 Mar 2026 13:31:18 +0100 Subject: [PATCH 02/12] Add ConfidentialRelay config for relay DON handler --- core/config/cre_config.go | 8 ++++++++ core/config/toml/types.go | 27 ++++++++++++++++++++++++- core/services/chainlink/config_cre.go | 29 +++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/core/config/cre_config.go b/core/config/cre_config.go index 57bc4ad31a9..78a600d1490 100644 --- a/core/config/cre_config.go +++ b/core/config/cre_config.go @@ -13,6 +13,7 @@ type CRE interface { // When enabled, additional OTel tracing and logging is performed. DebugMode() bool LocalSecrets() map[string]string + ConfidentialRelay() CREConfidentialRelay } // WorkflowFetcher defines configuration for fetching workflow files @@ -21,6 +22,13 @@ type WorkflowFetcher interface { URL() string } +// CREConfidentialRelay defines configuration for the confidential relay handler. +type CREConfidentialRelay interface { + Enabled() bool + TrustedPCRs() string + CARootsPEM() string +} + // CRELinking defines configuration for connecting to the CRE linking service type CRELinking interface { URL() string diff --git a/core/config/toml/types.go b/core/config/toml/types.go index 0865babfc96..87da02a1eb8 100644 --- a/core/config/toml/types.go +++ b/core/config/toml/types.go @@ -1985,7 +1985,8 @@ type CreConfig struct { // Requires [Tracing].Enabled = true for traces to be exported (trace export is gated by // Tracing.Enabled in initGlobals; Telemetry.Enabled is optional—traces work with or without it). // WARNING: This is not suitable for production use due to performance overhead. - DebugMode *bool `toml:",omitempty"` + DebugMode *bool `toml:",omitempty"` + ConfidentialRelay *ConfidentialRelayConfig `toml:",omitempty"` } // WorkflowFetcherConfig holds the configuration for fetching workflow files @@ -1993,6 +1994,15 @@ type WorkflowFetcherConfig struct { URL *string `toml:",omitempty"` } +// ConfidentialRelayConfig holds the configuration for the confidential relay handler. +// When Enabled is true, the node participates in the confidential relay DON, +// validating enclave attestations and proxying capability requests. +type ConfidentialRelayConfig struct { + Enabled *bool `toml:",omitempty"` + TrustedPCRs *string `toml:",omitempty"` + CARootsPEM *string `toml:",omitempty"` +} + // LinkingConfig holds the configuration for connecting to the CRE linking service type LinkingConfig struct { URL *string `toml:",omitempty"` @@ -2046,6 +2056,21 @@ func (c *CreConfig) setFrom(f *CreConfig) { if f.DebugMode != nil { c.DebugMode = f.DebugMode } + + if f.ConfidentialRelay != nil { + if c.ConfidentialRelay == nil { + c.ConfidentialRelay = &ConfidentialRelayConfig{} + } + if v := f.ConfidentialRelay.Enabled; v != nil { + c.ConfidentialRelay.Enabled = v + } + if v := f.ConfidentialRelay.TrustedPCRs; v != nil { + c.ConfidentialRelay.TrustedPCRs = v + } + if v := f.ConfidentialRelay.CARootsPEM; v != nil { + c.ConfidentialRelay.CARootsPEM = v + } + } } func (w *WorkflowFetcherConfig) ValidateConfig() error { diff --git a/core/services/chainlink/config_cre.go b/core/services/chainlink/config_cre.go index 6714d6a63d5..dbb9c404f21 100644 --- a/core/services/chainlink/config_cre.go +++ b/core/services/chainlink/config_cre.go @@ -105,6 +105,35 @@ func (c *creConfig) Linking() config.CRELinking { return &linkingConfig{url: url, tlsEnabled: tlsEnabled} } +type confidentialRelayConfig struct { + enabled bool + trustedPCRs string + caRootsPEM string +} + +func (cr *confidentialRelayConfig) Enabled() bool { return cr.enabled } +func (cr *confidentialRelayConfig) TrustedPCRs() string { return cr.trustedPCRs } +func (cr *confidentialRelayConfig) CARootsPEM() string { return cr.caRootsPEM } + +func (c *creConfig) ConfidentialRelay() config.CREConfidentialRelay { + if c.c.ConfidentialRelay == nil { + return &confidentialRelayConfig{} + } + enabled := false + if c.c.ConfidentialRelay.Enabled != nil { + enabled = *c.c.ConfidentialRelay.Enabled + } + trustedPCRs := "" + if c.c.ConfidentialRelay.TrustedPCRs != nil { + trustedPCRs = *c.c.ConfidentialRelay.TrustedPCRs + } + caRootsPEM := "" + if c.c.ConfidentialRelay.CARootsPEM != nil { + caRootsPEM = *c.c.ConfidentialRelay.CARootsPEM + } + return &confidentialRelayConfig{enabled: enabled, trustedPCRs: trustedPCRs, caRootsPEM: caRootsPEM} +} + func (c *creConfig) LocalSecrets() map[string]string { return c.s.LocalSecrets } From 42a9798462e8080112934f5132e4bb26ad90cfa0 Mon Sep 17 00:00:00 2001 From: Tejaswi Nadahalli Date: Wed, 25 Mar 2026 12:15:47 +0100 Subject: [PATCH 03/12] Remove Nitro-specific wording from attestation comments --- core/capabilities/confidentialrelay/handler.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/capabilities/confidentialrelay/handler.go b/core/capabilities/confidentialrelay/handler.go index e70157f4a23..454075f13b7 100644 --- a/core/capabilities/confidentialrelay/handler.go +++ b/core/capabilities/confidentialrelay/handler.go @@ -62,11 +62,11 @@ type gatewayConnector interface { RemoveHandler(ctx context.Context, methods []string) error } -// attestationValidatorFunc validates a Nitro attestation document. +// attestationValidatorFunc validates a TEE attestation document. type attestationValidatorFunc func(attestation []byte, expectedUserData []byte, trustedMeasurements []byte, caRootsPEM string) error // Handler processes enclave relay requests from the gateway. -// It validates Nitro attestations and proxies requests to VaultDON or capability DONs. +// It validates attestations and proxies requests to VaultDON or capability DONs. type Handler struct { services.Service eng *services.Engine @@ -77,8 +77,8 @@ type Handler struct { lggr logger.Logger metrics *handlerMetrics - // validateAttestation validates Nitro attestation documents. - // Defaults to the real nitro validator; overridden in tests. + // validateAttestation validates TEE attestation documents. + // Defaults to the Nitro validator; overridden in tests. validateAttestation attestationValidatorFunc // caRootsPEM is an optional PEM-encoded CA root certificate for attestation From c1c8533a3a20cc812fd5eeb5ccd2fb7a8ba34d30 Mon Sep 17 00:00:00 2001 From: Tejaswi Nadahalli Date: Wed, 25 Mar 2026 12:38:22 +0100 Subject: [PATCH 04/12] Look up trusted measurements from cap registry instead of TOML config Replace the static trustedPCRs TOML parameter with a per-request lookup from the capabilities registry. The handler reads the confidential-workflows capability config to extract enclave TrustedValues, same pattern as CC's EnsureFreshEnclaves. Remove TrustedPCRs from CREConfidentialRelay config interface, ConfidentialRelayConfig TOML struct, and config_cre.go. --- .../capabilities/confidentialrelay/handler.go | 98 ++++++++++++++----- .../confidentialrelay/handler_test.go | 51 +++++++--- .../capabilities/confidentialrelay/service.go | 5 +- core/config/cre_config.go | 1 - core/config/toml/types.go | 8 +- core/services/chainlink/config_cre.go | 16 +-- 6 files changed, 123 insertions(+), 56 deletions(-) diff --git a/core/capabilities/confidentialrelay/handler.go b/core/capabilities/confidentialrelay/handler.go index 454075f13b7..7f717d653c5 100644 --- a/core/capabilities/confidentialrelay/handler.go +++ b/core/capabilities/confidentialrelay/handler.go @@ -34,7 +34,24 @@ import ( var _ core.GatewayConnectorHandler = (*Handler)(nil) -const HandlerName = "EnclaveRelayHandler" +const ( + HandlerName = "EnclaveRelayHandler" + + // confidentialWorkflowsCapID is the capability ID for the confidential + // workflows enclave pool. The relay handler uses it to look up trusted + // enclave measurements from the capabilities registry. + confidentialWorkflowsCapID = "confidential-workflows@1.0.0-alpha" +) + +// enclaveEntry mirrors the enclave config shape stored in the capabilities +// registry. Only the fields needed for attestation validation are included. +type enclaveEntry struct { + TrustedValues []json.RawMessage `json:"trustedValues"` +} + +type enclavesList struct { + Enclaves []enclaveEntry +} type handlerMetrics struct { requestInternalError metric.Int64Counter @@ -73,7 +90,6 @@ type Handler struct { capRegistry core.CapabilitiesRegistry gatewayConnector gatewayConnector - trustedPCRs []byte lggr logger.Logger metrics *handlerMetrics @@ -86,7 +102,7 @@ type Handler struct { caRootsPEM string } -func NewHandler(capRegistry core.CapabilitiesRegistry, conn gatewayConnector, trustedPCRs []byte, lggr logger.Logger, caRootsPEM ...string) (*Handler, error) { +func NewHandler(capRegistry core.CapabilitiesRegistry, conn gatewayConnector, lggr logger.Logger, caRootsPEM ...string) (*Handler, error) { m, err := newMetrics() if err != nil { return nil, fmt.Errorf("failed to create metrics: %w", err) @@ -99,7 +115,6 @@ func NewHandler(capRegistry core.CapabilitiesRegistry, conn gatewayConnector, tr h := &Handler{ capRegistry: capRegistry, gatewayConnector: conn, - trustedPCRs: trustedPCRs, lggr: logger.Named(lggr, HandlerName), metrics: m, validateAttestation: nitro.ValidateAttestation, @@ -173,7 +188,7 @@ func (h *Handler) handleSecretsGet(ctx context.Context, gatewayID string, req *j att := params.Attestation params.Attestation = "" - if err := h.verifyAttestationHash(att, params, confidentialrelaytypes.DomainSecretsGet); err != nil { + if err := h.verifyAttestationHash(ctx, att, params, confidentialrelaytypes.DomainSecretsGet); err != nil { return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, err) } @@ -328,7 +343,7 @@ func (h *Handler) handleCapabilityExecute(ctx context.Context, gatewayID string, att := params.Attestation params.Attestation = "" - if err := h.verifyAttestationHash(att, params, confidentialrelaytypes.DomainCapabilityExec); err != nil { + if err := h.verifyAttestationHash(ctx, att, params, confidentialrelaytypes.DomainCapabilityExec); err != nil { return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInternal, err) } @@ -389,7 +404,41 @@ func (h *Handler) handleCapabilityExecute(ctx context.Context, gatewayID string, return h.jsonResponse(req, result) } -func (h *Handler) verifyAttestationHash(attestationB64 string, cleanParams any, domainTag string) error { +// getTrustedMeasurements reads the enclave pool configuration from the +// capabilities registry and returns all trusted measurement sets. Called +// per-request so measurements stay fresh (same pattern as CC's +// EnsureFreshEnclaves). +func (h *Handler) getTrustedMeasurements(ctx context.Context) ([]json.RawMessage, error) { + dons, err := h.capRegistry.DONsForCapability(ctx, confidentialWorkflowsCapID) + if err != nil { + return nil, fmt.Errorf("failed to find DON for %s: %w", confidentialWorkflowsCapID, err) + } + if len(dons) == 0 { + return nil, fmt.Errorf("no DON found hosting %s", confidentialWorkflowsCapID) + } + + capConfig, err := h.capRegistry.ConfigForCapability(ctx, confidentialWorkflowsCapID, dons[0].DON.ID) + if err != nil { + return nil, fmt.Errorf("failed to get config for %s: %w", confidentialWorkflowsCapID, err) + } + + if capConfig.DefaultConfig == nil { + return nil, fmt.Errorf("no default config for %s", confidentialWorkflowsCapID) + } + + var enclaves enclavesList + if err := capConfig.DefaultConfig.UnwrapTo(&enclaves); err != nil { + return nil, fmt.Errorf("failed to unwrap enclave config: %w", err) + } + + var measurements []json.RawMessage + for _, e := range enclaves.Enclaves { + measurements = append(measurements, e.TrustedValues...) + } + return measurements, nil +} + +func (h *Handler) verifyAttestationHash(ctx context.Context, attestationB64 string, cleanParams any, domainTag string) error { if attestationB64 == "" { return errors.New("missing attestation") } @@ -406,24 +455,27 @@ func (h *Handler) verifyAttestationHash(attestationB64 string, cleanParams any, return fmt.Errorf("failed to decode attestation: %w", err) } - // Each enclave instance has different PCR values (WireGuard keys baked - // per CID), so trustedPCRs may be a JSON array of PCR objects. Try each - // set and succeed if any match, same as pool.go's - // validateAttestationAgainstMultipleMeasurements. - var pcrArray []json.RawMessage - if json.Unmarshal(h.trustedPCRs, &pcrArray) == nil && len(pcrArray) > 0 { - var validationErr error - for _, pcrs := range pcrArray { - err := h.validateAttestation(attestationBytes, hash, pcrs, h.caRootsPEM) - if err == nil { - return nil - } - validationErr = errors.Join(validationErr, err) - } - return fmt.Errorf("no trusted PCR set matched: %w", validationErr) + // Look up trusted measurements from the capabilities registry. Each enclave + // instance may have different PCR values, so we try each set and succeed if + // any match (same approach as pool.go's validateAttestationAgainstMultipleMeasurements). + measurements, err := h.getTrustedMeasurements(ctx) + if err != nil { + return fmt.Errorf("failed to get trusted measurements: %w", err) + } + + if len(measurements) == 0 { + return errors.New("no trusted measurements found in capabilities registry") } - return h.validateAttestation(attestationBytes, hash, h.trustedPCRs, h.caRootsPEM) + var validationErr error + for _, m := range measurements { + err := h.validateAttestation(attestationBytes, hash, m, h.caRootsPEM) + if err == nil { + return nil + } + validationErr = errors.Join(validationErr, err) + } + return fmt.Errorf("no trusted measurement set matched: %w", validationErr) } func toSDKCapabilityResponse(capResp capabilities.CapabilityResponse) (*sdkpb.CapabilityResponse, error) { diff --git a/core/capabilities/confidentialrelay/handler_test.go b/core/capabilities/confidentialrelay/handler_test.go index a0c494a32f0..bcf90b77f57 100644 --- a/core/capabilities/confidentialrelay/handler_test.go +++ b/core/capabilities/confidentialrelay/handler_test.go @@ -88,6 +88,7 @@ type mockCapRegistry struct { core.UnimplementedCapabilitiesRegistry executables map[string]*mockExecutable configs map[string]capabilities.CapabilityConfiguration + dons map[string][]capabilities.DONWithNodes localNode capabilities.Node } @@ -103,6 +104,12 @@ func (m *mockCapRegistry) ConfigForCapability(_ context.Context, capID string, _ } return capabilities.CapabilityConfiguration{}, fmt.Errorf("config not found: %s", capID) } +func (m *mockCapRegistry) DONsForCapability(_ context.Context, capID string) ([]capabilities.DONWithNodes, error) { + if dons, ok := m.dons[capID]; ok { + return dons, nil + } + return nil, fmt.Errorf("no DONs found for: %s", capID) +} func (m *mockCapRegistry) LocalNode(_ context.Context) (capabilities.Node, error) { return m.localNode, nil } @@ -111,12 +118,34 @@ func newTestHandler(t *testing.T, registry core.CapabilitiesRegistry, gwConn gat t.Helper() lggr, err := logger.New() require.NoError(t, err) - h, err := NewHandler(registry, gwConn, []byte(`{}`), lggr) + h, err := NewHandler(registry, gwConn, lggr) require.NoError(t, err) h.validateAttestation = noopValidator return h } +// withEnclaveConfig adds the default confidential-workflows enclave config +// to a mock registry so getTrustedMeasurements succeeds during tests. +func withEnclaveConfig(reg *mockCapRegistry) *mockCapRegistry { + enclaveConfig := enclavesList{ + Enclaves: []enclaveEntry{{TrustedValues: []json.RawMessage{json.RawMessage(`{}`)}}}, + } + wrapped, _ := values.WrapMap(enclaveConfig) + if reg.configs == nil { + reg.configs = map[string]capabilities.CapabilityConfiguration{} + } + reg.configs[confidentialWorkflowsCapID] = capabilities.CapabilityConfiguration{ + DefaultConfig: wrapped, + } + if reg.dons == nil { + reg.dons = map[string][]capabilities.DONWithNodes{} + } + reg.dons[confidentialWorkflowsCapID] = []capabilities.DONWithNodes{ + {DON: capabilities.DON{ID: 1}}, + } + return reg +} + func makeRequest(t *testing.T, method string, params any) *jsonrpc.Request[json.RawMessage] { t.Helper() b, err := json.Marshal(params) @@ -140,7 +169,7 @@ func TestHandler_HandleGatewayMessage(t *testing.T) { { name: "capability execute success", registry: func(_ *testing.T) *mockCapRegistry { - return &mockCapRegistry{ + return withEnclaveConfig(&mockCapRegistry{ executables: map[string]*mockExecutable{ "my-cap@1.0.0": { execResult: capabilities.CapabilityResponse{ @@ -148,7 +177,7 @@ func TestHandler_HandleGatewayMessage(t *testing.T) { }, }, }, - } + }) }, req: func(t *testing.T) *jsonrpc.Request[json.RawMessage] { return makeRequest(t, confidentialrelaytypes.MethodCapabilityExec, confidentialrelaytypes.CapabilityRequestParams{ @@ -174,13 +203,13 @@ func TestHandler_HandleGatewayMessage(t *testing.T) { { name: "capability execute sets Inputs from Payload for backward compat", registry: func(_ *testing.T) *mockCapRegistry { - return &mockCapRegistry{ + return withEnclaveConfig(&mockCapRegistry{ executables: map[string]*mockExecutable{ "my-cap@1.0.0": { execResult: capabilities.CapabilityResponse{}, }, }, - } + }) }, req: func(t *testing.T) *jsonrpc.Request[json.RawMessage] { return makeRequest(t, confidentialrelaytypes.MethodCapabilityExec, confidentialrelaytypes.CapabilityRequestParams{ @@ -210,7 +239,7 @@ func TestHandler_HandleGatewayMessage(t *testing.T) { { name: "capability execute attestation failure", registry: func(_ *testing.T) *mockCapRegistry { - return &mockCapRegistry{} + return withEnclaveConfig(&mockCapRegistry{}) }, req: func(t *testing.T) *jsonrpc.Request[json.RawMessage] { return makeRequest(t, confidentialrelaytypes.MethodCapabilityExec, confidentialrelaytypes.CapabilityRequestParams{ @@ -227,7 +256,7 @@ func TestHandler_HandleGatewayMessage(t *testing.T) { { name: "capability execute not found", registry: func(_ *testing.T) *mockCapRegistry { - return &mockCapRegistry{executables: map[string]*mockExecutable{}} + return withEnclaveConfig(&mockCapRegistry{executables: map[string]*mockExecutable{}}) }, req: func(t *testing.T) *jsonrpc.Request[json.RawMessage] { return makeRequest(t, confidentialrelaytypes.MethodCapabilityExec, confidentialrelaytypes.CapabilityRequestParams{ @@ -246,11 +275,11 @@ func TestHandler_HandleGatewayMessage(t *testing.T) { { name: "capability execute error returned in result", registry: func(_ *testing.T) *mockCapRegistry { - return &mockCapRegistry{ + return withEnclaveConfig(&mockCapRegistry{ executables: map[string]*mockExecutable{ "fail-cap@1.0.0": {execErr: errors.New("execution failed")}, }, - } + }) }, req: func(t *testing.T) *jsonrpc.Request[json.RawMessage] { sdkReq := &sdkpb.CapabilityRequest{Id: "fail-cap@1.0.0", Method: "Execute"} @@ -274,7 +303,7 @@ func TestHandler_HandleGatewayMessage(t *testing.T) { { name: "unsupported method", registry: func(_ *testing.T) *mockCapRegistry { - return &mockCapRegistry{} + return withEnclaveConfig(&mockCapRegistry{}) }, req: func(t *testing.T) *jsonrpc.Request[json.RawMessage] { return makeRequest(t, "unknown.method", nil) @@ -287,7 +316,7 @@ func TestHandler_HandleGatewayMessage(t *testing.T) { { name: "invalid params JSON", registry: func(_ *testing.T) *mockCapRegistry { - return &mockCapRegistry{} + return withEnclaveConfig(&mockCapRegistry{}) }, req: func(_ *testing.T) *jsonrpc.Request[json.RawMessage] { raw := json.RawMessage([]byte(`{invalid json`)) diff --git a/core/capabilities/confidentialrelay/service.go b/core/capabilities/confidentialrelay/service.go index b9ae214848f..236e4442f8c 100644 --- a/core/capabilities/confidentialrelay/service.go +++ b/core/capabilities/confidentialrelay/service.go @@ -20,7 +20,6 @@ type Service struct { wrapper *gatewayconnector.ServiceWrapper capRegistry core.CapabilitiesRegistry - trustedPCRs []byte caRootsPEM string lggr logger.Logger @@ -30,14 +29,12 @@ type Service struct { func NewService( wrapper *gatewayconnector.ServiceWrapper, capRegistry core.CapabilitiesRegistry, - trustedPCRs []byte, caRootsPEM string, lggr logger.Logger, ) *Service { s := &Service{ wrapper: wrapper, capRegistry: capRegistry, - trustedPCRs: trustedPCRs, caRootsPEM: caRootsPEM, lggr: lggr, } @@ -54,7 +51,7 @@ func (s *Service) start(ctx context.Context) error { if conn == nil { return errors.New("gateway connector not available") } - h, err := NewHandler(s.capRegistry, conn, s.trustedPCRs, s.lggr, s.caRootsPEM) + h, err := NewHandler(s.capRegistry, conn, s.lggr, s.caRootsPEM) if err != nil { return err } diff --git a/core/config/cre_config.go b/core/config/cre_config.go index 78a600d1490..880793147fa 100644 --- a/core/config/cre_config.go +++ b/core/config/cre_config.go @@ -25,7 +25,6 @@ type WorkflowFetcher interface { // CREConfidentialRelay defines configuration for the confidential relay handler. type CREConfidentialRelay interface { Enabled() bool - TrustedPCRs() string CARootsPEM() string } diff --git a/core/config/toml/types.go b/core/config/toml/types.go index 87da02a1eb8..a445e89d3ac 100644 --- a/core/config/toml/types.go +++ b/core/config/toml/types.go @@ -1998,9 +1998,8 @@ type WorkflowFetcherConfig struct { // When Enabled is true, the node participates in the confidential relay DON, // validating enclave attestations and proxying capability requests. type ConfidentialRelayConfig struct { - Enabled *bool `toml:",omitempty"` - TrustedPCRs *string `toml:",omitempty"` - CARootsPEM *string `toml:",omitempty"` + Enabled *bool `toml:",omitempty"` + CARootsPEM *string `toml:",omitempty"` } // LinkingConfig holds the configuration for connecting to the CRE linking service @@ -2064,9 +2063,6 @@ func (c *CreConfig) setFrom(f *CreConfig) { if v := f.ConfidentialRelay.Enabled; v != nil { c.ConfidentialRelay.Enabled = v } - if v := f.ConfidentialRelay.TrustedPCRs; v != nil { - c.ConfidentialRelay.TrustedPCRs = v - } if v := f.ConfidentialRelay.CARootsPEM; v != nil { c.ConfidentialRelay.CARootsPEM = v } diff --git a/core/services/chainlink/config_cre.go b/core/services/chainlink/config_cre.go index dbb9c404f21..fc14b3448c5 100644 --- a/core/services/chainlink/config_cre.go +++ b/core/services/chainlink/config_cre.go @@ -106,14 +106,12 @@ func (c *creConfig) Linking() config.CRELinking { } type confidentialRelayConfig struct { - enabled bool - trustedPCRs string - caRootsPEM string + enabled bool + caRootsPEM string } -func (cr *confidentialRelayConfig) Enabled() bool { return cr.enabled } -func (cr *confidentialRelayConfig) TrustedPCRs() string { return cr.trustedPCRs } -func (cr *confidentialRelayConfig) CARootsPEM() string { return cr.caRootsPEM } +func (cr *confidentialRelayConfig) Enabled() bool { return cr.enabled } +func (cr *confidentialRelayConfig) CARootsPEM() string { return cr.caRootsPEM } func (c *creConfig) ConfidentialRelay() config.CREConfidentialRelay { if c.c.ConfidentialRelay == nil { @@ -123,15 +121,11 @@ func (c *creConfig) ConfidentialRelay() config.CREConfidentialRelay { if c.c.ConfidentialRelay.Enabled != nil { enabled = *c.c.ConfidentialRelay.Enabled } - trustedPCRs := "" - if c.c.ConfidentialRelay.TrustedPCRs != nil { - trustedPCRs = *c.c.ConfidentialRelay.TrustedPCRs - } caRootsPEM := "" if c.c.ConfidentialRelay.CARootsPEM != nil { caRootsPEM = *c.c.ConfidentialRelay.CARootsPEM } - return &confidentialRelayConfig{enabled: enabled, trustedPCRs: trustedPCRs, caRootsPEM: caRootsPEM} + return &confidentialRelayConfig{enabled: enabled, caRootsPEM: caRootsPEM} } func (c *creConfig) LocalSecrets() map[string]string { From 452ca83097bee356ab977ee43580583f3f7d7b58 Mon Sep 17 00:00:00 2001 From: Tejaswi Nadahalli Date: Wed, 25 Mar 2026 12:45:10 +0100 Subject: [PATCH 05/12] Also read CARootsPEM from cap registry, remove from TOML config CARootsPEM is stored per-enclave in the same cap registry config as TrustedValues. Read both in getEnclaveAttestationConfig, eliminating the last TOML field besides Enabled from CRE.ConfidentialRelay. --- .../capabilities/confidentialrelay/handler.go | 51 +++++++++---------- .../confidentialrelay/handler_test.go | 2 +- .../capabilities/confidentialrelay/service.go | 5 +- core/config/cre_config.go | 1 - core/config/toml/types.go | 6 +-- core/services/chainlink/config_cre.go | 12 ++--- 6 files changed, 29 insertions(+), 48 deletions(-) diff --git a/core/capabilities/confidentialrelay/handler.go b/core/capabilities/confidentialrelay/handler.go index 7f717d653c5..e824dc14974 100644 --- a/core/capabilities/confidentialrelay/handler.go +++ b/core/capabilities/confidentialrelay/handler.go @@ -47,6 +47,7 @@ const ( // registry. Only the fields needed for attestation validation are included. type enclaveEntry struct { TrustedValues []json.RawMessage `json:"trustedValues"` + CARootsPEM string `json:"caRootsPEM,omitempty"` } type enclavesList struct { @@ -96,29 +97,20 @@ type Handler struct { // validateAttestation validates TEE attestation documents. // Defaults to the Nitro validator; overridden in tests. validateAttestation attestationValidatorFunc - - // caRootsPEM is an optional PEM-encoded CA root certificate for attestation - // validation. Empty string uses DefaultCARoots (production default). - caRootsPEM string } -func NewHandler(capRegistry core.CapabilitiesRegistry, conn gatewayConnector, lggr logger.Logger, caRootsPEM ...string) (*Handler, error) { +func NewHandler(capRegistry core.CapabilitiesRegistry, conn gatewayConnector, lggr logger.Logger) (*Handler, error) { m, err := newMetrics() if err != nil { return nil, fmt.Errorf("failed to create metrics: %w", err) } - var roots string - if len(caRootsPEM) > 0 { - roots = caRootsPEM[0] - } h := &Handler{ capRegistry: capRegistry, gatewayConnector: conn, lggr: logger.Named(lggr, HandlerName), metrics: m, validateAttestation: nitro.ValidateAttestation, - caRootsPEM: roots, } h.Service, h.eng = services.Config{ Name: HandlerName, @@ -404,38 +396,40 @@ func (h *Handler) handleCapabilityExecute(ctx context.Context, gatewayID string, return h.jsonResponse(req, result) } -// getTrustedMeasurements reads the enclave pool configuration from the -// capabilities registry and returns all trusted measurement sets. Called -// per-request so measurements stay fresh (same pattern as CC's -// EnsureFreshEnclaves). -func (h *Handler) getTrustedMeasurements(ctx context.Context) ([]json.RawMessage, error) { +// getEnclaveAttestationConfig reads the enclave pool configuration from the +// capabilities registry and returns trusted measurement sets and CA roots +// for attestation validation. Called per-request so the config stays fresh +// (same pattern as CC's EnsureFreshEnclaves). +func (h *Handler) getEnclaveAttestationConfig(ctx context.Context) (measurements []json.RawMessage, caRootsPEM string, err error) { dons, err := h.capRegistry.DONsForCapability(ctx, confidentialWorkflowsCapID) if err != nil { - return nil, fmt.Errorf("failed to find DON for %s: %w", confidentialWorkflowsCapID, err) + return nil, "", fmt.Errorf("failed to find DON for %s: %w", confidentialWorkflowsCapID, err) } if len(dons) == 0 { - return nil, fmt.Errorf("no DON found hosting %s", confidentialWorkflowsCapID) + return nil, "", fmt.Errorf("no DON found hosting %s", confidentialWorkflowsCapID) } capConfig, err := h.capRegistry.ConfigForCapability(ctx, confidentialWorkflowsCapID, dons[0].DON.ID) if err != nil { - return nil, fmt.Errorf("failed to get config for %s: %w", confidentialWorkflowsCapID, err) + return nil, "", fmt.Errorf("failed to get config for %s: %w", confidentialWorkflowsCapID, err) } if capConfig.DefaultConfig == nil { - return nil, fmt.Errorf("no default config for %s", confidentialWorkflowsCapID) + return nil, "", fmt.Errorf("no default config for %s", confidentialWorkflowsCapID) } var enclaves enclavesList if err := capConfig.DefaultConfig.UnwrapTo(&enclaves); err != nil { - return nil, fmt.Errorf("failed to unwrap enclave config: %w", err) + return nil, "", fmt.Errorf("failed to unwrap enclave config: %w", err) } - var measurements []json.RawMessage for _, e := range enclaves.Enclaves { measurements = append(measurements, e.TrustedValues...) + if caRootsPEM == "" && e.CARootsPEM != "" { + caRootsPEM = e.CARootsPEM + } } - return measurements, nil + return measurements, caRootsPEM, nil } func (h *Handler) verifyAttestationHash(ctx context.Context, attestationB64 string, cleanParams any, domainTag string) error { @@ -455,12 +449,13 @@ func (h *Handler) verifyAttestationHash(ctx context.Context, attestationB64 stri return fmt.Errorf("failed to decode attestation: %w", err) } - // Look up trusted measurements from the capabilities registry. Each enclave - // instance may have different PCR values, so we try each set and succeed if - // any match (same approach as pool.go's validateAttestationAgainstMultipleMeasurements). - measurements, err := h.getTrustedMeasurements(ctx) + // Look up trusted measurements and CA roots from the capabilities registry. + // Each enclave instance may have different PCR values, so we try each set + // and succeed if any match (same approach as pool.go's + // validateAttestationAgainstMultipleMeasurements). + measurements, caRootsPEM, err := h.getEnclaveAttestationConfig(ctx) if err != nil { - return fmt.Errorf("failed to get trusted measurements: %w", err) + return fmt.Errorf("failed to get enclave attestation config: %w", err) } if len(measurements) == 0 { @@ -469,7 +464,7 @@ func (h *Handler) verifyAttestationHash(ctx context.Context, attestationB64 stri var validationErr error for _, m := range measurements { - err := h.validateAttestation(attestationBytes, hash, m, h.caRootsPEM) + err := h.validateAttestation(attestationBytes, hash, m, caRootsPEM) if err == nil { return nil } diff --git a/core/capabilities/confidentialrelay/handler_test.go b/core/capabilities/confidentialrelay/handler_test.go index bcf90b77f57..42d2f4eb222 100644 --- a/core/capabilities/confidentialrelay/handler_test.go +++ b/core/capabilities/confidentialrelay/handler_test.go @@ -125,7 +125,7 @@ func newTestHandler(t *testing.T, registry core.CapabilitiesRegistry, gwConn gat } // withEnclaveConfig adds the default confidential-workflows enclave config -// to a mock registry so getTrustedMeasurements succeeds during tests. +// to a mock registry so getEnclaveAttestationConfig succeeds during tests. func withEnclaveConfig(reg *mockCapRegistry) *mockCapRegistry { enclaveConfig := enclavesList{ Enclaves: []enclaveEntry{{TrustedValues: []json.RawMessage{json.RawMessage(`{}`)}}}, diff --git a/core/capabilities/confidentialrelay/service.go b/core/capabilities/confidentialrelay/service.go index 236e4442f8c..b00ca76eb95 100644 --- a/core/capabilities/confidentialrelay/service.go +++ b/core/capabilities/confidentialrelay/service.go @@ -20,7 +20,6 @@ type Service struct { wrapper *gatewayconnector.ServiceWrapper capRegistry core.CapabilitiesRegistry - caRootsPEM string lggr logger.Logger handler *Handler @@ -29,13 +28,11 @@ type Service struct { func NewService( wrapper *gatewayconnector.ServiceWrapper, capRegistry core.CapabilitiesRegistry, - caRootsPEM string, lggr logger.Logger, ) *Service { s := &Service{ wrapper: wrapper, capRegistry: capRegistry, - caRootsPEM: caRootsPEM, lggr: lggr, } s.Service, s.eng = services.Config{ @@ -51,7 +48,7 @@ func (s *Service) start(ctx context.Context) error { if conn == nil { return errors.New("gateway connector not available") } - h, err := NewHandler(s.capRegistry, conn, s.lggr, s.caRootsPEM) + h, err := NewHandler(s.capRegistry, conn, s.lggr) if err != nil { return err } diff --git a/core/config/cre_config.go b/core/config/cre_config.go index 880793147fa..30aa3ce3c93 100644 --- a/core/config/cre_config.go +++ b/core/config/cre_config.go @@ -25,7 +25,6 @@ type WorkflowFetcher interface { // CREConfidentialRelay defines configuration for the confidential relay handler. type CREConfidentialRelay interface { Enabled() bool - CARootsPEM() string } // CRELinking defines configuration for connecting to the CRE linking service diff --git a/core/config/toml/types.go b/core/config/toml/types.go index a445e89d3ac..64f5980aeb9 100644 --- a/core/config/toml/types.go +++ b/core/config/toml/types.go @@ -1998,8 +1998,7 @@ type WorkflowFetcherConfig struct { // When Enabled is true, the node participates in the confidential relay DON, // validating enclave attestations and proxying capability requests. type ConfidentialRelayConfig struct { - Enabled *bool `toml:",omitempty"` - CARootsPEM *string `toml:",omitempty"` + Enabled *bool `toml:",omitempty"` } // LinkingConfig holds the configuration for connecting to the CRE linking service @@ -2063,9 +2062,6 @@ func (c *CreConfig) setFrom(f *CreConfig) { if v := f.ConfidentialRelay.Enabled; v != nil { c.ConfidentialRelay.Enabled = v } - if v := f.ConfidentialRelay.CARootsPEM; v != nil { - c.ConfidentialRelay.CARootsPEM = v - } } } diff --git a/core/services/chainlink/config_cre.go b/core/services/chainlink/config_cre.go index fc14b3448c5..00d2878d00c 100644 --- a/core/services/chainlink/config_cre.go +++ b/core/services/chainlink/config_cre.go @@ -106,12 +106,10 @@ func (c *creConfig) Linking() config.CRELinking { } type confidentialRelayConfig struct { - enabled bool - caRootsPEM string + enabled bool } -func (cr *confidentialRelayConfig) Enabled() bool { return cr.enabled } -func (cr *confidentialRelayConfig) CARootsPEM() string { return cr.caRootsPEM } +func (cr *confidentialRelayConfig) Enabled() bool { return cr.enabled } func (c *creConfig) ConfidentialRelay() config.CREConfidentialRelay { if c.c.ConfidentialRelay == nil { @@ -121,11 +119,7 @@ func (c *creConfig) ConfidentialRelay() config.CREConfidentialRelay { if c.c.ConfidentialRelay.Enabled != nil { enabled = *c.c.ConfidentialRelay.Enabled } - caRootsPEM := "" - if c.c.ConfidentialRelay.CARootsPEM != nil { - caRootsPEM = *c.c.ConfidentialRelay.CARootsPEM - } - return &confidentialRelayConfig{enabled: enabled, caRootsPEM: caRootsPEM} + return &confidentialRelayConfig{enabled: enabled} } func (c *creConfig) LocalSecrets() map[string]string { From 1b80ace93e92241271e6d24b33d81c633cd07fe4 Mon Sep 17 00:00:00 2001 From: Tejaswi Nadahalli Date: Thu, 26 Mar 2026 13:13:31 +0100 Subject: [PATCH 06/12] Remove CARootsPEM from relay handler ValidateAttestation in chainlink-common now always uses the hardcoded AWS Nitro root cert (c7b8c137). Drop the caRootsPEM parameter from the handler's attestation validator func, getEnclaveAttestationConfig, and the enclaveEntry struct. --- .../capabilities/confidentialrelay/handler.go | 27 +++++++++---------- .../confidentialrelay/handler_test.go | 2 +- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/core/capabilities/confidentialrelay/handler.go b/core/capabilities/confidentialrelay/handler.go index e824dc14974..3e743888717 100644 --- a/core/capabilities/confidentialrelay/handler.go +++ b/core/capabilities/confidentialrelay/handler.go @@ -47,7 +47,6 @@ const ( // registry. Only the fields needed for attestation validation are included. type enclaveEntry struct { TrustedValues []json.RawMessage `json:"trustedValues"` - CARootsPEM string `json:"caRootsPEM,omitempty"` } type enclavesList struct { @@ -81,7 +80,7 @@ type gatewayConnector interface { } // attestationValidatorFunc validates a TEE attestation document. -type attestationValidatorFunc func(attestation []byte, expectedUserData []byte, trustedMeasurements []byte, caRootsPEM string) error +type attestationValidatorFunc func(attestation []byte, expectedUserData []byte, trustedMeasurements []byte) error // Handler processes enclave relay requests from the gateway. // It validates attestations and proxies requests to VaultDON or capability DONs. @@ -400,36 +399,34 @@ func (h *Handler) handleCapabilityExecute(ctx context.Context, gatewayID string, // capabilities registry and returns trusted measurement sets and CA roots // for attestation validation. Called per-request so the config stays fresh // (same pattern as CC's EnsureFreshEnclaves). -func (h *Handler) getEnclaveAttestationConfig(ctx context.Context) (measurements []json.RawMessage, caRootsPEM string, err error) { +func (h *Handler) getEnclaveAttestationConfig(ctx context.Context) ([]json.RawMessage, error) { dons, err := h.capRegistry.DONsForCapability(ctx, confidentialWorkflowsCapID) if err != nil { - return nil, "", fmt.Errorf("failed to find DON for %s: %w", confidentialWorkflowsCapID, err) + return nil, fmt.Errorf("failed to find DON for %s: %w", confidentialWorkflowsCapID, err) } if len(dons) == 0 { - return nil, "", fmt.Errorf("no DON found hosting %s", confidentialWorkflowsCapID) + return nil, fmt.Errorf("no DON found hosting %s", confidentialWorkflowsCapID) } capConfig, err := h.capRegistry.ConfigForCapability(ctx, confidentialWorkflowsCapID, dons[0].DON.ID) if err != nil { - return nil, "", fmt.Errorf("failed to get config for %s: %w", confidentialWorkflowsCapID, err) + return nil, fmt.Errorf("failed to get config for %s: %w", confidentialWorkflowsCapID, err) } if capConfig.DefaultConfig == nil { - return nil, "", fmt.Errorf("no default config for %s", confidentialWorkflowsCapID) + return nil, fmt.Errorf("no default config for %s", confidentialWorkflowsCapID) } var enclaves enclavesList if err := capConfig.DefaultConfig.UnwrapTo(&enclaves); err != nil { - return nil, "", fmt.Errorf("failed to unwrap enclave config: %w", err) + return nil, fmt.Errorf("failed to unwrap enclave config: %w", err) } + var measurements []json.RawMessage for _, e := range enclaves.Enclaves { measurements = append(measurements, e.TrustedValues...) - if caRootsPEM == "" && e.CARootsPEM != "" { - caRootsPEM = e.CARootsPEM - } } - return measurements, caRootsPEM, nil + return measurements, nil } func (h *Handler) verifyAttestationHash(ctx context.Context, attestationB64 string, cleanParams any, domainTag string) error { @@ -449,11 +446,11 @@ func (h *Handler) verifyAttestationHash(ctx context.Context, attestationB64 stri return fmt.Errorf("failed to decode attestation: %w", err) } - // Look up trusted measurements and CA roots from the capabilities registry. + // Look up trusted measurements from the capabilities registry. // Each enclave instance may have different PCR values, so we try each set // and succeed if any match (same approach as pool.go's // validateAttestationAgainstMultipleMeasurements). - measurements, caRootsPEM, err := h.getEnclaveAttestationConfig(ctx) + measurements, err := h.getEnclaveAttestationConfig(ctx) if err != nil { return fmt.Errorf("failed to get enclave attestation config: %w", err) } @@ -464,7 +461,7 @@ func (h *Handler) verifyAttestationHash(ctx context.Context, attestationB64 stri var validationErr error for _, m := range measurements { - err := h.validateAttestation(attestationBytes, hash, m, caRootsPEM) + err := h.validateAttestation(attestationBytes, hash, m) if err == nil { return nil } diff --git a/core/capabilities/confidentialrelay/handler_test.go b/core/capabilities/confidentialrelay/handler_test.go index 42d2f4eb222..7de6e246b5e 100644 --- a/core/capabilities/confidentialrelay/handler_test.go +++ b/core/capabilities/confidentialrelay/handler_test.go @@ -41,7 +41,7 @@ func makeCapabilityPayload(t *testing.T, inputs map[string]any) string { const testAttestationB64 = "ZHVtbXktYXR0ZXN0YXRpb24=" // base64("dummy-attestation") -func noopValidator(_ []byte, _, _ []byte, _ string) error { return nil } +func noopValidator(_ []byte, _, _ []byte) error { return nil } type mockGatewayConnector struct { lastResp *jsonrpc.Response[json.RawMessage] From d9939f70cf6102f28d1c1e62a0fe5b6c0bebc991 Mon Sep 17 00:00:00 2001 From: Tejaswi Nadahalli Date: Fri, 27 Mar 2026 14:58:11 +0100 Subject: [PATCH 07/12] Relay handler: use CARootsPEM from registry for fake enclave attestation --- .../capabilities/confidentialrelay/handler.go | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/core/capabilities/confidentialrelay/handler.go b/core/capabilities/confidentialrelay/handler.go index 3e743888717..5b6b54905c9 100644 --- a/core/capabilities/confidentialrelay/handler.go +++ b/core/capabilities/confidentialrelay/handler.go @@ -47,6 +47,7 @@ const ( // registry. Only the fields needed for attestation validation are included. type enclaveEntry struct { TrustedValues []json.RawMessage `json:"trustedValues"` + CARootsPEM string `json:"caRootsPEM,omitempty"` } type enclavesList struct { @@ -399,34 +400,38 @@ func (h *Handler) handleCapabilityExecute(ctx context.Context, gatewayID string, // capabilities registry and returns trusted measurement sets and CA roots // for attestation validation. Called per-request so the config stays fresh // (same pattern as CC's EnsureFreshEnclaves). -func (h *Handler) getEnclaveAttestationConfig(ctx context.Context) ([]json.RawMessage, error) { +func (h *Handler) getEnclaveAttestationConfig(ctx context.Context) ([]json.RawMessage, string, error) { dons, err := h.capRegistry.DONsForCapability(ctx, confidentialWorkflowsCapID) if err != nil { - return nil, fmt.Errorf("failed to find DON for %s: %w", confidentialWorkflowsCapID, err) + return nil, "", fmt.Errorf("failed to find DON for %s: %w", confidentialWorkflowsCapID, err) } if len(dons) == 0 { - return nil, fmt.Errorf("no DON found hosting %s", confidentialWorkflowsCapID) + return nil, "", fmt.Errorf("no DON found hosting %s", confidentialWorkflowsCapID) } capConfig, err := h.capRegistry.ConfigForCapability(ctx, confidentialWorkflowsCapID, dons[0].DON.ID) if err != nil { - return nil, fmt.Errorf("failed to get config for %s: %w", confidentialWorkflowsCapID, err) + return nil, "", fmt.Errorf("failed to get config for %s: %w", confidentialWorkflowsCapID, err) } if capConfig.DefaultConfig == nil { - return nil, fmt.Errorf("no default config for %s", confidentialWorkflowsCapID) + return nil, "", fmt.Errorf("no default config for %s", confidentialWorkflowsCapID) } var enclaves enclavesList if err := capConfig.DefaultConfig.UnwrapTo(&enclaves); err != nil { - return nil, fmt.Errorf("failed to unwrap enclave config: %w", err) + return nil, "", fmt.Errorf("failed to unwrap enclave config: %w", err) } var measurements []json.RawMessage + var caRootsPEM string for _, e := range enclaves.Enclaves { measurements = append(measurements, e.TrustedValues...) + if caRootsPEM == "" && e.CARootsPEM != "" { + caRootsPEM = e.CARootsPEM + } } - return measurements, nil + return measurements, caRootsPEM, nil } func (h *Handler) verifyAttestationHash(ctx context.Context, attestationB64 string, cleanParams any, domainTag string) error { @@ -450,7 +455,7 @@ func (h *Handler) verifyAttestationHash(ctx context.Context, attestationB64 stri // Each enclave instance may have different PCR values, so we try each set // and succeed if any match (same approach as pool.go's // validateAttestationAgainstMultipleMeasurements). - measurements, err := h.getEnclaveAttestationConfig(ctx) + measurements, caRootsPEM, err := h.getEnclaveAttestationConfig(ctx) if err != nil { return fmt.Errorf("failed to get enclave attestation config: %w", err) } @@ -461,7 +466,12 @@ func (h *Handler) verifyAttestationHash(ctx context.Context, attestationB64 stri var validationErr error for _, m := range measurements { - err := h.validateAttestation(attestationBytes, hash, m) + var err error + if caRootsPEM != "" { + err = nitro.ValidateAttestationWithRoots(attestationBytes, hash, m, caRootsPEM) + } else { + err = h.validateAttestation(attestationBytes, hash, m) + } if err == nil { return nil } From 3f16bf6d51bb4c89f90555f9786fca71a2b6ded7 Mon Sep 17 00:00:00 2001 From: Tejaswi Nadahalli Date: Tue, 31 Mar 2026 18:08:47 +0200 Subject: [PATCH 08/12] forward relay metadata for remote capability execution (cherry picked from commit 16e4fc9417aefeba5740b5aac4c4198e6ff7c60d) --- core/capabilities/confidentialrelay/handler.go | 10 +++++++++- core/capabilities/confidentialrelay/handler_test.go | 11 +++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/core/capabilities/confidentialrelay/handler.go b/core/capabilities/confidentialrelay/handler.go index 5b6b54905c9..f3cd1f1cbb0 100644 --- a/core/capabilities/confidentialrelay/handler.go +++ b/core/capabilities/confidentialrelay/handler.go @@ -354,12 +354,20 @@ func (h *Handler) handleCapabilityExecute(ctx context.Context, gatewayID string, return h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrInvalidParams, fmt.Errorf("failed to unmarshal capability request: %w", err)) } + referenceID := params.ReferenceID + if referenceID == "" { + referenceID = req.ID + } + capReq := capabilities.CapabilityRequest{ Payload: sdkReq.Payload, Method: sdkReq.Method, CapabilityId: params.CapabilityID, Metadata: capabilities.RequestMetadata{ - WorkflowID: params.WorkflowID, + WorkflowID: params.WorkflowID, + WorkflowOwner: params.Owner, + WorkflowExecutionID: params.ExecutionID, + ReferenceID: referenceID, }, } diff --git a/core/capabilities/confidentialrelay/handler_test.go b/core/capabilities/confidentialrelay/handler_test.go index 7de6e246b5e..1135242ae2c 100644 --- a/core/capabilities/confidentialrelay/handler_test.go +++ b/core/capabilities/confidentialrelay/handler_test.go @@ -182,6 +182,9 @@ func TestHandler_HandleGatewayMessage(t *testing.T) { req: func(t *testing.T) *jsonrpc.Request[json.RawMessage] { return makeRequest(t, confidentialrelaytypes.MethodCapabilityExec, confidentialrelaytypes.CapabilityRequestParams{ WorkflowID: "wf-1", + Owner: "0xowner", + ExecutionID: "32c631d295ef5e32deb99a10ee6804bc4af13855687559d7ff6552ac6dbb2ce1", + ReferenceID: "17", CapabilityID: "my-cap@1.0.0", Payload: makeCapabilityPayload(t, map[string]any{"key": "val"}), Attestation: testAttestationB64, @@ -199,6 +202,14 @@ func TestHandler_HandleGatewayMessage(t *testing.T) { assert.Equal(t, "result-proto-bytes", string(capResp.GetPayload().GetValue())) assert.Empty(t, result.Error) }, + checkExecutable: func(t *testing.T, reg *mockCapRegistry) { + exec := reg.executables["my-cap@1.0.0"] + require.NotNil(t, exec.lastRequest, "Execute should have been called") + assert.Equal(t, "wf-1", exec.lastRequest.Metadata.WorkflowID) + assert.Equal(t, "0xowner", exec.lastRequest.Metadata.WorkflowOwner) + assert.Equal(t, "32c631d295ef5e32deb99a10ee6804bc4af13855687559d7ff6552ac6dbb2ce1", exec.lastRequest.Metadata.WorkflowExecutionID) + assert.Equal(t, "17", exec.lastRequest.Metadata.ReferenceID) + }, }, { name: "capability execute sets Inputs from Payload for backward compat", From 51a6276f344ac587213083c66f28cd5a31ef9b41 Mon Sep 17 00:00:00 2001 From: Tejaswi Nadahalli Date: Wed, 1 Apr 2026 14:02:52 +0200 Subject: [PATCH 09/12] add relay request metrics and use exported connector type --- .../capabilities/confidentialrelay/handler.go | 43 +++++++++++++++---- .../confidentialrelay/handler_test.go | 3 +- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/core/capabilities/confidentialrelay/handler.go b/core/capabilities/confidentialrelay/handler.go index f3cd1f1cbb0..c5fba968151 100644 --- a/core/capabilities/confidentialrelay/handler.go +++ b/core/capabilities/confidentialrelay/handler.go @@ -7,6 +7,7 @@ import ( "encoding/json" "errors" "fmt" + "time" "github.com/ethereum/go-ethereum/common" "go.opentelemetry.io/otel/attribute" @@ -55,11 +56,21 @@ type enclavesList struct { } type handlerMetrics struct { + requestCount metric.Int64Counter + requestLatency metric.Int64Histogram requestInternalError metric.Int64Counter requestSuccess metric.Int64Counter } func newMetrics() (*handlerMetrics, error) { + requestCount, err := beholder.GetMeter().Int64Counter("enclave_relay_request_count") + if err != nil { + return nil, fmt.Errorf("failed to register request count counter: %w", err) + } + requestLatency, err := beholder.GetMeter().Int64Histogram("enclave_relay_request_latency_ms", metric.WithUnit("ms")) + if err != nil { + return nil, fmt.Errorf("failed to register request latency histogram: %w", err) + } requestInternalError, err := beholder.GetMeter().Int64Counter("enclave_relay_request_internal_error") if err != nil { return nil, fmt.Errorf("failed to register internal error counter: %w", err) @@ -69,17 +80,13 @@ func newMetrics() (*handlerMetrics, error) { return nil, fmt.Errorf("failed to register success counter: %w", err) } return &handlerMetrics{ + requestCount: requestCount, + requestLatency: requestLatency, requestInternalError: requestInternalError, requestSuccess: requestSuccess, }, nil } -type gatewayConnector interface { - SendToGateway(ctx context.Context, gatewayID string, resp *jsonrpc.Response[json.RawMessage]) error - AddHandler(ctx context.Context, methods []string, handler core.GatewayConnectorHandler) error - RemoveHandler(ctx context.Context, methods []string) error -} - // attestationValidatorFunc validates a TEE attestation document. type attestationValidatorFunc func(attestation []byte, expectedUserData []byte, trustedMeasurements []byte) error @@ -90,7 +97,7 @@ type Handler struct { eng *services.Engine capRegistry core.CapabilitiesRegistry - gatewayConnector gatewayConnector + gatewayConnector core.GatewayConnector lggr logger.Logger metrics *handlerMetrics @@ -99,7 +106,7 @@ type Handler struct { validateAttestation attestationValidatorFunc } -func NewHandler(capRegistry core.CapabilitiesRegistry, conn gatewayConnector, lggr logger.Logger) (*Handler, error) { +func NewHandler(capRegistry core.CapabilitiesRegistry, conn core.GatewayConnector, lggr logger.Logger) (*Handler, error) { m, err := newMetrics() if err != nil { return nil, fmt.Errorf("failed to create metrics: %w", err) @@ -144,6 +151,21 @@ func (h *Handler) Methods() []string { func (h *Handler) HandleGatewayMessage(ctx context.Context, gatewayID string, req *jsonrpc.Request[json.RawMessage]) error { h.lggr.Debugw("received message from gateway", "gatewayID", gatewayID, "requestID", req.ID) + startTime := time.Now() + outcome := "success" + var errorCode int64 + defer func() { + attrs := []attribute.KeyValue{ + attribute.String("gateway_id", gatewayID), + attribute.String("method", req.Method), + attribute.String("outcome", outcome), + } + if errorCode != 0 { + attrs = append(attrs, attribute.Int64("error_code", errorCode)) + } + h.metrics.requestCount.Add(ctx, 1, metric.WithAttributes(attrs...)) + h.metrics.requestLatency.Record(ctx, time.Since(startTime).Milliseconds(), metric.WithAttributes(attrs...)) + }() var response *jsonrpc.Response[json.RawMessage] switch req.Method { @@ -154,8 +176,13 @@ func (h *Handler) HandleGatewayMessage(ctx context.Context, gatewayID string, re default: response = h.errorResponse(ctx, gatewayID, req, jsonrpc.ErrMethodNotFound, errors.New("unsupported method: "+req.Method)) } + if response != nil && response.Error != nil { + outcome = "error" + errorCode = response.Error.Code + } if err := h.gatewayConnector.SendToGateway(ctx, gatewayID, response); err != nil { + outcome = "send_error" h.lggr.Errorw("failed to send message to gateway", "gatewayID", gatewayID, "err", err) return err } diff --git a/core/capabilities/confidentialrelay/handler_test.go b/core/capabilities/confidentialrelay/handler_test.go index 1135242ae2c..1dcb0dd79ee 100644 --- a/core/capabilities/confidentialrelay/handler_test.go +++ b/core/capabilities/confidentialrelay/handler_test.go @@ -44,6 +44,7 @@ const testAttestationB64 = "ZHVtbXktYXR0ZXN0YXRpb24=" // base64("dummy-attestati func noopValidator(_ []byte, _, _ []byte) error { return nil } type mockGatewayConnector struct { + core.UnimplementedGatewayConnector lastResp *jsonrpc.Response[json.RawMessage] addedMethods []string removed bool @@ -114,7 +115,7 @@ func (m *mockCapRegistry) LocalNode(_ context.Context) (capabilities.Node, error return m.localNode, nil } -func newTestHandler(t *testing.T, registry core.CapabilitiesRegistry, gwConn gatewayConnector) *Handler { +func newTestHandler(t *testing.T, registry core.CapabilitiesRegistry, gwConn core.GatewayConnector) *Handler { t.Helper() lggr, err := logger.New() require.NoError(t, err) From 0112f40d6b636ee31ac70a8ceab523a3e5392df0 Mon Sep 17 00:00:00 2001 From: Tejaswi Nadahalli Date: Wed, 1 Apr 2026 14:08:15 +0200 Subject: [PATCH 10/12] sanitize internal relay error messages --- core/capabilities/confidentialrelay/handler.go | 12 +++++++++--- core/capabilities/confidentialrelay/handler_test.go | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/core/capabilities/confidentialrelay/handler.go b/core/capabilities/confidentialrelay/handler.go index c5fba968151..d16d5a440cc 100644 --- a/core/capabilities/confidentialrelay/handler.go +++ b/core/capabilities/confidentialrelay/handler.go @@ -36,7 +36,8 @@ import ( var _ core.GatewayConnectorHandler = (*Handler)(nil) const ( - HandlerName = "EnclaveRelayHandler" + HandlerName = "EnclaveRelayHandler" + internalErrorMessage = "internal error" // confidentialWorkflowsCapID is the capability ID for the confidential // workflows enclave pool. The relay handler uses it to look up trusted @@ -546,7 +547,7 @@ func (h *Handler) jsonResponse(req *jsonrpc.Request[json.RawMessage], result any Method: req.Method, Error: &jsonrpc.WireError{ Code: jsonrpc.ErrInternal, - Message: err.Error(), + Message: internalErrorMessage, }, } } @@ -572,13 +573,18 @@ func (h *Handler) errorResponse( attribute.Int64("error_code", errorCode), )) + message := err.Error() + if errorCode == jsonrpc.ErrInternal { + message = internalErrorMessage + } + return &jsonrpc.Response[json.RawMessage]{ Version: jsonrpc.JsonRpcVersion, ID: req.ID, Method: req.Method, Error: &jsonrpc.WireError{ Code: errorCode, - Message: err.Error(), + Message: message, }, } } diff --git a/core/capabilities/confidentialrelay/handler_test.go b/core/capabilities/confidentialrelay/handler_test.go index 1dcb0dd79ee..42a1f478951 100644 --- a/core/capabilities/confidentialrelay/handler_test.go +++ b/core/capabilities/confidentialrelay/handler_test.go @@ -281,7 +281,7 @@ func TestHandler_HandleGatewayMessage(t *testing.T) { checkResp: func(t *testing.T, resp *jsonrpc.Response[json.RawMessage]) { require.NotNil(t, resp.Error) assert.Equal(t, jsonrpc.ErrInternal, resp.Error.Code) - assert.Contains(t, resp.Error.Message, "capability not found") + assert.Equal(t, internalErrorMessage, resp.Error.Message) }, }, { From 00a16e622bc44c2b09c95194b698fb9881c39476 Mon Sep 17 00:00:00 2001 From: Tejaswi Nadahalli Date: Thu, 2 Apr 2026 15:59:57 +0200 Subject: [PATCH 11/12] bump chainlink-common and sync relay config snapshots --- core/config/docs/core.toml | 4 ++++ .../chainlink/testdata/config-empty-effective.toml | 3 +++ core/services/chainlink/testdata/config-full.toml | 3 +++ .../chainlink/testdata/config-multi-chain-effective.toml | 3 +++ core/web/resolver/testdata/config-empty-effective.toml | 3 +++ core/web/resolver/testdata/config-full.toml | 3 +++ .../resolver/testdata/config-multi-chain-effective.toml | 3 +++ go.mod | 4 +--- go.sum | 9 ++------- 9 files changed, 25 insertions(+), 10 deletions(-) diff --git a/core/config/docs/core.toml b/core/config/docs/core.toml index aa2599e6584..fdaf318108d 100644 --- a/core/config/docs/core.toml +++ b/core/config/docs/core.toml @@ -956,6 +956,10 @@ EnableDKGRecipient = false # Default # DebugMode enables additional tracing and logging for workflow engines. DebugMode = false # Default +[CRE.ConfidentialRelay] +# Enabled controls whether the confidential relay gateway handler should be configured. +Enabled = false # Default + # Sharding holds settings for node sharding configuration. [Sharding] # ShardingEnabled enables workflow sharding across multiple nodes. diff --git a/core/services/chainlink/testdata/config-empty-effective.toml b/core/services/chainlink/testdata/config-empty-effective.toml index 9afbf080f23..c6498692f99 100644 --- a/core/services/chainlink/testdata/config-empty-effective.toml +++ b/core/services/chainlink/testdata/config-empty-effective.toml @@ -385,6 +385,9 @@ URL = '' URL = '' TLSEnabled = true +[CRE.ConfidentialRelay] +Enabled = false + [Billing] URL = 'localhost:4319' TLSEnabled = true diff --git a/core/services/chainlink/testdata/config-full.toml b/core/services/chainlink/testdata/config-full.toml index b6193c84964..9947224a815 100644 --- a/core/services/chainlink/testdata/config-full.toml +++ b/core/services/chainlink/testdata/config-full.toml @@ -423,6 +423,9 @@ URL = 'https://workflow.fetcher.url' URL = '' TLSEnabled = true +[CRE.ConfidentialRelay] +Enabled = false + [Billing] URL = 'localhost:4319' TLSEnabled = true diff --git a/core/services/chainlink/testdata/config-multi-chain-effective.toml b/core/services/chainlink/testdata/config-multi-chain-effective.toml index 4023cf493f3..9e1d7747312 100644 --- a/core/services/chainlink/testdata/config-multi-chain-effective.toml +++ b/core/services/chainlink/testdata/config-multi-chain-effective.toml @@ -385,6 +385,9 @@ URL = '' URL = '' TLSEnabled = true +[CRE.ConfidentialRelay] +Enabled = false + [Billing] URL = 'localhost:4319' TLSEnabled = true diff --git a/core/web/resolver/testdata/config-empty-effective.toml b/core/web/resolver/testdata/config-empty-effective.toml index 9afbf080f23..c6498692f99 100644 --- a/core/web/resolver/testdata/config-empty-effective.toml +++ b/core/web/resolver/testdata/config-empty-effective.toml @@ -385,6 +385,9 @@ URL = '' URL = '' TLSEnabled = true +[CRE.ConfidentialRelay] +Enabled = false + [Billing] URL = 'localhost:4319' TLSEnabled = true diff --git a/core/web/resolver/testdata/config-full.toml b/core/web/resolver/testdata/config-full.toml index 9dd766db1c1..b68aac9cdb3 100644 --- a/core/web/resolver/testdata/config-full.toml +++ b/core/web/resolver/testdata/config-full.toml @@ -402,6 +402,9 @@ URL = 'https://workflow.fetcher.url' URL = '' TLSEnabled = true +[CRE.ConfidentialRelay] +Enabled = false + [Billing] URL = 'localhost:4319' TLSEnabled = true diff --git a/core/web/resolver/testdata/config-multi-chain-effective.toml b/core/web/resolver/testdata/config-multi-chain-effective.toml index c79af87df64..a9b2bdca387 100644 --- a/core/web/resolver/testdata/config-multi-chain-effective.toml +++ b/core/web/resolver/testdata/config-multi-chain-effective.toml @@ -385,6 +385,9 @@ URL = '' URL = '' TLSEnabled = true +[CRE.ConfidentialRelay] +Enabled = false + [Billing] URL = 'localhost:4319' TLSEnabled = true diff --git a/go.mod b/go.mod index 137f3c2ec36..5d54ffc1011 100644 --- a/go.mod +++ b/go.mod @@ -85,10 +85,9 @@ require ( github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260224214816-cb23ec38649f github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20250912190424-fd2e35d7deb5 github.com/smartcontractkit/chainlink-ccv v0.0.0-20260324000441-d4cfddc9f7d2 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260402120824-48154c0c65a6 github.com/smartcontractkit/chainlink-common/keystore v1.0.2 github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 - github.com/smartcontractkit/chainlink-common/pkg/teeattestation v0.0.0-20260326144312-af5248caa880 github.com/smartcontractkit/chainlink-data-streams v0.1.13 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260330133421-5151ea0c3b05 github.com/smartcontractkit/chainlink-evm/contracts/cre/gobindings v0.0.0-20260107191744-4b93f62cffe3 @@ -293,7 +292,6 @@ require ( github.com/hashicorp/yamux v0.1.2 // indirect github.com/hasura/go-graphql-client v0.15.1 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect - github.com/hf/nitrite v0.0.0-20241225144000-c2d5d3c4f303 // indirect github.com/holiman/billy v0.0.0-20250707135307-f2f9b9aae7db // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/huin/goupnp v1.3.0 // indirect diff --git a/go.sum b/go.sum index d85a017a6b9..468bdf0820d 100644 --- a/go.sum +++ b/go.sum @@ -425,7 +425,6 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= @@ -744,8 +743,6 @@ github.com/hasura/go-graphql-client v0.15.1 h1:mCb5I+8Bk3FU3GKWvf/zDXkTh7FbGlqJm github.com/hasura/go-graphql-client v0.15.1/go.mod h1:jfSZtBER3or+88Q9vFhWHiFMPppfYILRyl+0zsgPIIw= github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= -github.com/hf/nitrite v0.0.0-20241225144000-c2d5d3c4f303 h1:XBSq4rXFUgD8ic6Mr7dBwJN/47yg87XpZQhiknfr4Cg= -github.com/hf/nitrite v0.0.0-20241225144000-c2d5d3c4f303/go.mod h1:ycRhVmo6wegyEl6WN+zXOHUTJvB0J2tiuH88q/McTK8= github.com/holiman/billy v0.0.0-20250707135307-f2f9b9aae7db h1:IZUYC/xb3giYwBLMnr8d0TGTzPKFGNTCGgGLoyeX330= github.com/holiman/billy v0.0.0-20250707135307-f2f9b9aae7db/go.mod h1:xTEYN9KCHxuYHs+NmrmzFcnvHMzLLNiGFafCb1n3Mfg= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= @@ -1238,16 +1235,14 @@ github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20250 github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20250912190424-fd2e35d7deb5/go.mod h1:xtZNi6pOKdC3sLvokDvXOhgHzT+cyBqH/gWwvxTxqrg= github.com/smartcontractkit/chainlink-ccv v0.0.0-20260324000441-d4cfddc9f7d2 h1:5HdH/A6yn8INZAltYDLb7UkUi5IKemhJzJkDW4Bgxyg= github.com/smartcontractkit/chainlink-ccv v0.0.0-20260324000441-d4cfddc9f7d2/go.mod h1:wDHq2E0KwUWG0lQ9f5frW1a7CKVW17MJLPuvKmtSRDg= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843 h1:yzkeWzWoPTbpDvVIz0ohmNVqAkvE8UwuLqqcUt47gYk= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843/go.mod h1:6tlxlsiWypGdpaZI+Kz5gFm53gCAcU/pTU3PR9CiFB8= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260402120824-48154c0c65a6 h1:oaXslIvcy5HD3zkWhx3nu8vRGdWGedYJ+XCsBD8mYkA= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260402120824-48154c0c65a6/go.mod h1:Ea94/OgfFPRTByGO2Qo+uZ7/4sWhwE2HKu7dDwdojME= github.com/smartcontractkit/chainlink-common/keystore v1.0.2 h1:AWisx4JT3QV8tcgh6J5NCrex+wAgTYpWyHsyNPSXzsQ= github.com/smartcontractkit/chainlink-common/keystore v1.0.2/go.mod h1:rSkIHdomyak3YnUtXLenl6poIq8q0V3UZPiiyYqPdGA= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9oqASnkS03RE1HQwYQQxrO4l46O5JSzxqLgg= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10/go.mod h1:oiDa54M0FwxevWwyAX773lwdWvFYYlYHHQV1LQ5HpWY= github.com/smartcontractkit/chainlink-common/pkg/monitoring v0.0.0-20251215152504-b1e41f508340 h1:PsjEI+5jZIz9AS4eOsLS5VpSWJINf38clXV3wryPyMk= github.com/smartcontractkit/chainlink-common/pkg/monitoring v0.0.0-20251215152504-b1e41f508340/go.mod h1:P/0OSXUlFaxxD4B/P6HWbxYtIRmmWGDJAvanq19879c= -github.com/smartcontractkit/chainlink-common/pkg/teeattestation v0.0.0-20260326144312-af5248caa880 h1:qLrZSgc7e/exxIE4M020A52ep6xN0tXOJESiots9DvE= -github.com/smartcontractkit/chainlink-common/pkg/teeattestation v0.0.0-20260326144312-af5248caa880/go.mod h1:+X7Cb8ysHfNnPd74htGIInzZMHfUrpdDrjlr6+VW0gU= github.com/smartcontractkit/chainlink-data-streams v0.1.13 h1:YOmt545DW6U0SyaqBf+NTGDLm1yMurVI7yOvxP5hlJk= github.com/smartcontractkit/chainlink-data-streams v0.1.13/go.mod h1:00aL7OK0BJdF9gn/4t4f/pctUu2VLwwfA8G/tl9rCrM= github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260330133421-5151ea0c3b05 h1:mUaNoTKE3lqlANSOlQmiYDIAjJoqHiShw+r5rl1xh7g= From b84b85e5ffb5a80ac337d5f1902881d72d260ac6 Mon Sep 17 00:00:00 2001 From: Tejaswi Nadahalli Date: Thu, 2 Apr 2026 17:49:43 +0200 Subject: [PATCH 12/12] Fix confidential relay CI fallout --- core/scripts/go.mod | 2 +- core/scripts/go.sum | 4 ++-- core/services/chainlink/config_test.go | 3 +++ deployment/go.mod | 2 +- deployment/go.sum | 4 ++-- integration-tests/go.mod | 2 +- integration-tests/go.sum | 4 ++-- integration-tests/load/go.mod | 2 +- integration-tests/load/go.sum | 4 ++-- system-tests/lib/go.mod | 2 +- system-tests/lib/go.sum | 4 ++-- system-tests/tests/go.mod | 2 +- system-tests/tests/go.sum | 4 ++-- 13 files changed, 21 insertions(+), 18 deletions(-) diff --git a/core/scripts/go.mod b/core/scripts/go.mod index d4383bb6274..ec5bcfb4afa 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -45,7 +45,7 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.97 github.com/smartcontractkit/chainlink-automation v0.8.1 github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260317185256-d5f7db87ae70 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260402120824-48154c0c65a6 github.com/smartcontractkit/chainlink-common/keystore v1.0.2 github.com/smartcontractkit/chainlink-data-streams v0.1.13 github.com/smartcontractkit/chainlink-deployments-framework v0.86.3 diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 576f3339ecd..ca1f837539f 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1634,8 +1634,8 @@ github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260317185256-d5f7 github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260317185256-d5f7db87ae70/go.mod h1:P0/tjeeIIxfsBupk5MneRjq5uI9mj+ZQpMpYnFla6WM= github.com/smartcontractkit/chainlink-ccv v0.0.0-20260324000441-d4cfddc9f7d2 h1:5HdH/A6yn8INZAltYDLb7UkUi5IKemhJzJkDW4Bgxyg= github.com/smartcontractkit/chainlink-ccv v0.0.0-20260324000441-d4cfddc9f7d2/go.mod h1:wDHq2E0KwUWG0lQ9f5frW1a7CKVW17MJLPuvKmtSRDg= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843 h1:yzkeWzWoPTbpDvVIz0ohmNVqAkvE8UwuLqqcUt47gYk= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843/go.mod h1:6tlxlsiWypGdpaZI+Kz5gFm53gCAcU/pTU3PR9CiFB8= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260402120824-48154c0c65a6 h1:oaXslIvcy5HD3zkWhx3nu8vRGdWGedYJ+XCsBD8mYkA= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260402120824-48154c0c65a6/go.mod h1:Ea94/OgfFPRTByGO2Qo+uZ7/4sWhwE2HKu7dDwdojME= github.com/smartcontractkit/chainlink-common/keystore v1.0.2 h1:AWisx4JT3QV8tcgh6J5NCrex+wAgTYpWyHsyNPSXzsQ= github.com/smartcontractkit/chainlink-common/keystore v1.0.2/go.mod h1:rSkIHdomyak3YnUtXLenl6poIq8q0V3UZPiiyYqPdGA= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20251211140724-319861e514c4 h1:NOUsjsMzNecbjiPWUQGlRSRAutEvCFrqqyETDJeh5q4= diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index 17f6f5ec542..fee0518a5f8 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -663,6 +663,9 @@ func TestConfig_Marshal(t *testing.T) { URL: ptr(""), TLSEnabled: ptr(true), }, + ConfidentialRelay: &toml.ConfidentialRelayConfig{ + Enabled: ptr(false), + }, } full.Billing = toml.Billing{ URL: ptr("localhost:4319"), diff --git a/deployment/go.mod b/deployment/go.mod index d743293223e..550136bfd35 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -44,7 +44,7 @@ require ( github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260224214816-cb23ec38649f github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260310183131-8d0f0e383288 github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260317175207-e9ff89561326 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260402120824-48154c0c65a6 github.com/smartcontractkit/chainlink-common/keystore v1.0.2 github.com/smartcontractkit/chainlink-deployments-framework v0.86.3 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260330133421-5151ea0c3b05 diff --git a/deployment/go.sum b/deployment/go.sum index ae4f6d0d6f3..ee90c55a682 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -1387,8 +1387,8 @@ github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260317175207-e9ff github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260317175207-e9ff89561326/go.mod h1:P0/tjeeIIxfsBupk5MneRjq5uI9mj+ZQpMpYnFla6WM= github.com/smartcontractkit/chainlink-ccv v0.0.0-20260324000441-d4cfddc9f7d2 h1:5HdH/A6yn8INZAltYDLb7UkUi5IKemhJzJkDW4Bgxyg= github.com/smartcontractkit/chainlink-ccv v0.0.0-20260324000441-d4cfddc9f7d2/go.mod h1:wDHq2E0KwUWG0lQ9f5frW1a7CKVW17MJLPuvKmtSRDg= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843 h1:yzkeWzWoPTbpDvVIz0ohmNVqAkvE8UwuLqqcUt47gYk= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843/go.mod h1:6tlxlsiWypGdpaZI+Kz5gFm53gCAcU/pTU3PR9CiFB8= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260402120824-48154c0c65a6 h1:oaXslIvcy5HD3zkWhx3nu8vRGdWGedYJ+XCsBD8mYkA= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260402120824-48154c0c65a6/go.mod h1:Ea94/OgfFPRTByGO2Qo+uZ7/4sWhwE2HKu7dDwdojME= github.com/smartcontractkit/chainlink-common/keystore v1.0.2 h1:AWisx4JT3QV8tcgh6J5NCrex+wAgTYpWyHsyNPSXzsQ= github.com/smartcontractkit/chainlink-common/keystore v1.0.2/go.mod h1:rSkIHdomyak3YnUtXLenl6poIq8q0V3UZPiiyYqPdGA= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9oqASnkS03RE1HQwYQQxrO4l46O5JSzxqLgg= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 99d455a3036..f92893bdfb1 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -38,7 +38,7 @@ require ( github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260317185256-d5f7db87ae70 github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260310183131-8d0f0e383288 github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260310183131-8d0f0e383288 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260402120824-48154c0c65a6 github.com/smartcontractkit/chainlink-common/keystore v1.0.2 github.com/smartcontractkit/chainlink-deployments-framework v0.86.3 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260330133421-5151ea0c3b05 diff --git a/integration-tests/go.sum b/integration-tests/go.sum index be7aeaf6d19..30491f0cda2 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1374,8 +1374,8 @@ github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260317185256-d5f7 github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260317185256-d5f7db87ae70/go.mod h1:P0/tjeeIIxfsBupk5MneRjq5uI9mj+ZQpMpYnFla6WM= github.com/smartcontractkit/chainlink-ccv v0.0.0-20260324000441-d4cfddc9f7d2 h1:5HdH/A6yn8INZAltYDLb7UkUi5IKemhJzJkDW4Bgxyg= github.com/smartcontractkit/chainlink-ccv v0.0.0-20260324000441-d4cfddc9f7d2/go.mod h1:wDHq2E0KwUWG0lQ9f5frW1a7CKVW17MJLPuvKmtSRDg= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843 h1:yzkeWzWoPTbpDvVIz0ohmNVqAkvE8UwuLqqcUt47gYk= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843/go.mod h1:6tlxlsiWypGdpaZI+Kz5gFm53gCAcU/pTU3PR9CiFB8= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260402120824-48154c0c65a6 h1:oaXslIvcy5HD3zkWhx3nu8vRGdWGedYJ+XCsBD8mYkA= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260402120824-48154c0c65a6/go.mod h1:Ea94/OgfFPRTByGO2Qo+uZ7/4sWhwE2HKu7dDwdojME= github.com/smartcontractkit/chainlink-common/keystore v1.0.2 h1:AWisx4JT3QV8tcgh6J5NCrex+wAgTYpWyHsyNPSXzsQ= github.com/smartcontractkit/chainlink-common/keystore v1.0.2/go.mod h1:rSkIHdomyak3YnUtXLenl6poIq8q0V3UZPiiyYqPdGA= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9oqASnkS03RE1HQwYQQxrO4l46O5JSzxqLgg= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 910a687d96c..5dfdd0fa8e3 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -27,7 +27,7 @@ require ( github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260317185256-d5f7db87ae70 github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260310183131-8d0f0e383288 github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260310183131-8d0f0e383288 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260402120824-48154c0c65a6 github.com/smartcontractkit/chainlink-deployments-framework v0.86.3 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260330133421-5151ea0c3b05 github.com/smartcontractkit/chainlink-testing-framework/framework v0.15.3 diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 479d468f23a..c25f5818c21 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1588,8 +1588,8 @@ github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260317185256-d5f7 github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260317185256-d5f7db87ae70/go.mod h1:P0/tjeeIIxfsBupk5MneRjq5uI9mj+ZQpMpYnFla6WM= github.com/smartcontractkit/chainlink-ccv v0.0.0-20260324000441-d4cfddc9f7d2 h1:5HdH/A6yn8INZAltYDLb7UkUi5IKemhJzJkDW4Bgxyg= github.com/smartcontractkit/chainlink-ccv v0.0.0-20260324000441-d4cfddc9f7d2/go.mod h1:wDHq2E0KwUWG0lQ9f5frW1a7CKVW17MJLPuvKmtSRDg= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843 h1:yzkeWzWoPTbpDvVIz0ohmNVqAkvE8UwuLqqcUt47gYk= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843/go.mod h1:6tlxlsiWypGdpaZI+Kz5gFm53gCAcU/pTU3PR9CiFB8= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260402120824-48154c0c65a6 h1:oaXslIvcy5HD3zkWhx3nu8vRGdWGedYJ+XCsBD8mYkA= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260402120824-48154c0c65a6/go.mod h1:Ea94/OgfFPRTByGO2Qo+uZ7/4sWhwE2HKu7dDwdojME= github.com/smartcontractkit/chainlink-common/keystore v1.0.2 h1:AWisx4JT3QV8tcgh6J5NCrex+wAgTYpWyHsyNPSXzsQ= github.com/smartcontractkit/chainlink-common/keystore v1.0.2/go.mod h1:rSkIHdomyak3YnUtXLenl6poIq8q0V3UZPiiyYqPdGA= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9oqASnkS03RE1HQwYQQxrO4l46O5JSzxqLgg= diff --git a/system-tests/lib/go.mod b/system-tests/lib/go.mod index 56f655f6b68..3ec3065cb5c 100644 --- a/system-tests/lib/go.mod +++ b/system-tests/lib/go.mod @@ -35,7 +35,7 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.97 github.com/smartcontractkit/chainlink-aptos v0.0.0-20260324144720-484863604698 github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260310183131-8d0f0e383288 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260402120824-48154c0c65a6 github.com/smartcontractkit/chainlink-common/keystore v1.0.2 github.com/smartcontractkit/chainlink-deployments-framework v0.86.3 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260330133421-5151ea0c3b05 diff --git a/system-tests/lib/go.sum b/system-tests/lib/go.sum index 20cce88dc92..2e7e1bf574e 100644 --- a/system-tests/lib/go.sum +++ b/system-tests/lib/go.sum @@ -1601,8 +1601,8 @@ github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260317185256-d5f7 github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260317185256-d5f7db87ae70/go.mod h1:P0/tjeeIIxfsBupk5MneRjq5uI9mj+ZQpMpYnFla6WM= github.com/smartcontractkit/chainlink-ccv v0.0.0-20260324000441-d4cfddc9f7d2 h1:5HdH/A6yn8INZAltYDLb7UkUi5IKemhJzJkDW4Bgxyg= github.com/smartcontractkit/chainlink-ccv v0.0.0-20260324000441-d4cfddc9f7d2/go.mod h1:wDHq2E0KwUWG0lQ9f5frW1a7CKVW17MJLPuvKmtSRDg= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843 h1:yzkeWzWoPTbpDvVIz0ohmNVqAkvE8UwuLqqcUt47gYk= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843/go.mod h1:6tlxlsiWypGdpaZI+Kz5gFm53gCAcU/pTU3PR9CiFB8= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260402120824-48154c0c65a6 h1:oaXslIvcy5HD3zkWhx3nu8vRGdWGedYJ+XCsBD8mYkA= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260402120824-48154c0c65a6/go.mod h1:Ea94/OgfFPRTByGO2Qo+uZ7/4sWhwE2HKu7dDwdojME= github.com/smartcontractkit/chainlink-common/keystore v1.0.2 h1:AWisx4JT3QV8tcgh6J5NCrex+wAgTYpWyHsyNPSXzsQ= github.com/smartcontractkit/chainlink-common/keystore v1.0.2/go.mod h1:rSkIHdomyak3YnUtXLenl6poIq8q0V3UZPiiyYqPdGA= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9oqASnkS03RE1HQwYQQxrO4l46O5JSzxqLgg= diff --git a/system-tests/tests/go.mod b/system-tests/tests/go.mod index 4c2ef6c9094..bb7136e9246 100644 --- a/system-tests/tests/go.mod +++ b/system-tests/tests/go.mod @@ -62,7 +62,7 @@ require ( github.com/rs/zerolog v1.34.0 github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chain-selectors v1.0.97 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260402120824-48154c0c65a6 github.com/smartcontractkit/chainlink-common/keystore v1.0.2 github.com/smartcontractkit/chainlink-data-streams v0.1.13 github.com/smartcontractkit/chainlink-deployments-framework v0.86.3 diff --git a/system-tests/tests/go.sum b/system-tests/tests/go.sum index 1481b3e768b..6df70e6640e 100644 --- a/system-tests/tests/go.sum +++ b/system-tests/tests/go.sum @@ -1785,8 +1785,8 @@ github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260317185256-d5f7 github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260317185256-d5f7db87ae70/go.mod h1:P0/tjeeIIxfsBupk5MneRjq5uI9mj+ZQpMpYnFla6WM= github.com/smartcontractkit/chainlink-ccv v0.0.0-20260324000441-d4cfddc9f7d2 h1:5HdH/A6yn8INZAltYDLb7UkUi5IKemhJzJkDW4Bgxyg= github.com/smartcontractkit/chainlink-ccv v0.0.0-20260324000441-d4cfddc9f7d2/go.mod h1:wDHq2E0KwUWG0lQ9f5frW1a7CKVW17MJLPuvKmtSRDg= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843 h1:yzkeWzWoPTbpDvVIz0ohmNVqAkvE8UwuLqqcUt47gYk= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260331163339-a3c0d217e843/go.mod h1:6tlxlsiWypGdpaZI+Kz5gFm53gCAcU/pTU3PR9CiFB8= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260402120824-48154c0c65a6 h1:oaXslIvcy5HD3zkWhx3nu8vRGdWGedYJ+XCsBD8mYkA= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260402120824-48154c0c65a6/go.mod h1:Ea94/OgfFPRTByGO2Qo+uZ7/4sWhwE2HKu7dDwdojME= github.com/smartcontractkit/chainlink-common/keystore v1.0.2 h1:AWisx4JT3QV8tcgh6J5NCrex+wAgTYpWyHsyNPSXzsQ= github.com/smartcontractkit/chainlink-common/keystore v1.0.2/go.mod h1:rSkIHdomyak3YnUtXLenl6poIq8q0V3UZPiiyYqPdGA= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20251211140724-319861e514c4 h1:NOUsjsMzNecbjiPWUQGlRSRAutEvCFrqqyETDJeh5q4=