diff --git a/core/scripts/cre/environment/configs/mixed-env-confidential-workflows.toml.tmpl b/core/scripts/cre/environment/configs/mixed-env-confidential-workflows.toml.tmpl index 191d17150b8..62a7230ff24 100644 --- a/core/scripts/cre/environment/configs/mixed-env-confidential-workflows.toml.tmpl +++ b/core/scripts/cre/environment/configs/mixed-env-confidential-workflows.toml.tmpl @@ -246,12 +246,11 @@ image = "postgres:15.0" port = 13200 - # The bootstrap/gateway node does not run the reporting plugin, so its code - # version does not affect the non-determinism signal. Keep it on develop for stability. + # The Gateway DON uses only 1 node per request, so it should run the PR image, exercise the code in PR. [[nodesets.node_specs]] roles = ["bootstrap", "gateway"] [nodesets.node_specs.node] - image = "${CRE_BASELINE_IMAGE}" + image = "${CRE_PR_IMAGE}" pull_image = ${CRE_PULL_IMAGE} # 5002 is the web API capabilities port for incoming requests # 15002 is the vault port for incoming requests diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 4e6f72719aa..3c44ec151b9 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -33,7 +33,7 @@ require ( github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chain-selectors v1.0.111 github.com/smartcontractkit/chainlink-ccip/chains/evm v0.0.0-20260916222901-720a003dab50 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260921174556-538f4bf0cb6f + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260928161532-9b1062f5076f github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d github.com/smartcontractkit/chainlink-deployments-framework v0.122.2 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260917132551-2a2b38e8b744 @@ -210,7 +210,6 @@ require ( github.com/docker/docker-credential-helpers v0.9.8 // indirect github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/dominikbraun/graph v0.23.0 // indirect github.com/doyensec/safeurl v0.2.5 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.7.0 // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 8da9be8c9a7..2b1cbe117e4 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -540,8 +540,6 @@ github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQ github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo= -github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc= github.com/doyensec/safeurl v0.2.5 h1:kKu0JNQy0tJ8jkDyB5h6Aml9vWWniq+mpoa12EGLcOQ= github.com/doyensec/safeurl v0.2.5/go.mod h1:3H0cgRpPYPSpgxRRn5yGD35Ns/LgGX/BVWSBbzUqXtY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -1685,8 +1683,8 @@ github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260916222901-720a github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260916222901-720a003dab50/go.mod h1:Dm/Abz0vr7F2v9TygUX947q8HIwJEJikDfSdanX1Cic= github.com/smartcontractkit/chainlink-ccv v0.13.1-0.20260918171034-c93b0d2ef3c0 h1:3f+Ijgr0vghawMUAitgFILQpt4KsoYHGv1Zb/YBeeQM= github.com/smartcontractkit/chainlink-ccv v0.13.1-0.20260918171034-c93b0d2ef3c0/go.mod h1:gX7FJzSnGQx4RAlsn2rRW8IQBlEFZYiXFXEEB5jnQgA= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260921174556-538f4bf0cb6f h1:mj37sJTagtZwTVhU7IIxu6/XCGiK740XmcwXihdKtvk= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260921174556-538f4bf0cb6f/go.mod h1:BniOJQOh52lgDDNyfOQJEmQUVQ68txTSeT0Qxwt0zQI= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260928161532-9b1062f5076f h1:gasuIea3r0AIuEkXDQvIw+TclWt184xBRZGwed+fP6I= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260928161532-9b1062f5076f/go.mod h1:2AvwgNr3I6s0Ny1qmovy/stHQIihEv/9g74aNYzQYYo= github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d h1:M9xuiIvdiHtim4nlxCVajyC/Mk63tAg9ESWb/biAhNA= github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d/go.mod h1:rI2njmTlyTvh+xhmORmK6qX3+V1ycDAqGJLu8LS8WpU= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260915184316-2730f1867c92 h1:Z1OIGukDhJD+Gknr5G07Vtml24E7BwKaIQoWzV/SdqA= diff --git a/core/services/workflows/cmd/cre/utils/standalone_engine.go b/core/services/workflows/cmd/cre/utils/standalone_engine.go index 2d8ab448468..0ce6df6a408 100644 --- a/core/services/workflows/cmd/cre/utils/standalone_engine.go +++ b/core/services/workflows/cmd/cre/utils/standalone_engine.go @@ -176,7 +176,7 @@ func NewStandaloneEngine( Request: &sdkpb.ExecuteRequest_Subscribe{}, MaxResponseSize: uint64(moduleExecuteMaxResponseSizeBytes), Config: config, - }, v2.NewDisallowedExecutionHelper(lggr, nil, &types.LocalTimeProvider{}, secretsFetcher)) + }, v2.NewDisallowedExecutionHelper(lggr, nil, &types.LocalTimeProvider{})) if err != nil { return nil, nil, fmt.Errorf("failed to execute subscribe: %w", err) } diff --git a/core/services/workflows/monitoring/monitoring.go b/core/services/workflows/monitoring/monitoring.go index 1289af6952d..4ec9c9e2055 100644 --- a/core/services/workflows/monitoring/monitoring.go +++ b/core/services/workflows/monitoring/monitoring.go @@ -31,6 +31,7 @@ type EngineMetrics struct { workflowExecutionLatencyGauge metric.Int64Gauge // ms workflowStepErrorCounter metric.Int64Counter workflowInitializationCounter metric.Int64Counter + workflowInitializationFailureCounter metric.Int64Counter workflowTriggerEventErrorCounter metric.Int64Counter workflowTriggerEventQueueFullCounter metric.Int64Counter @@ -164,6 +165,13 @@ func InitMonitoringResources() (em *EngineMetrics, err error) { return nil, fmt.Errorf("failed to register workflow initialization counter: %w", err) } + em.workflowInitializationFailureCounter, err = beholder.GetMeter().Int64Counter( + "platform_engine_workflow_initialization_failures_total", + metric.WithDescription("Count of failed engine initializations by failure reason")) + if err != nil { + return nil, fmt.Errorf("failed to register workflow initialization failure counter: %w", err) + } + em.workflowStepErrorCounter, err = beholder.GetMeter().Int64Counter("platform_engine_workflow_errors") if err != nil { return nil, fmt.Errorf("failed to register workflow step error counter: %w", err) @@ -633,6 +641,18 @@ func (c WorkflowsMetricLabeler) IncrementWorkflowInitializationCounter(ctx conte c.em.workflowInitializationCounter.Add(ctx, 1, metric.WithAttributes(otelLabels...)) } +// IncrementWorkflowInitializationFailureCounter records one failed engine +// initialization. reason must be a low-cardinality value identifying the +// failing phase; see the initFailure* constants in the engine package. +func (c WorkflowsMetricLabeler) IncrementWorkflowInitializationFailureCounter(ctx context.Context, reason string) { + if reason == "" { + reason = "unknown" + } + otelLabels := beholder.OtelAttributes(c.Labels).AsStringAttributes() + otelLabels = append(otelLabels, attribute.String("reason", reason)) + c.em.workflowInitializationFailureCounter.Add(ctx, 1, metric.WithAttributes(otelLabels...)) +} + func (c WorkflowsMetricLabeler) IncrementWorkflowTriggerEventErrorCounter(ctx context.Context) { otelLabels := beholder.OtelAttributes(c.Labels).AsStringAttributes() c.em.workflowTriggerEventErrorCounter.Add(ctx, 1, metric.WithAttributes(otelLabels...)) diff --git a/core/services/workflows/shardownership/shard_index_mapper.go b/core/services/workflows/shardownership/shard_index_mapper.go index ea66dfb8d49..46603654ad1 100644 --- a/core/services/workflows/shardownership/shard_index_mapper.go +++ b/core/services/workflows/shardownership/shard_index_mapper.go @@ -11,13 +11,9 @@ import ( commoncap "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/capabilities/registry" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/workflows/shardownership/types" ) -// shardNameMarker is the marker a workflow DON's name is suffixed with to -// encode its shard index (e.g. "workflow-1-zone-a-shard-1" -> shard 1). A -// name with no such suffix (e.g. "workflow-1-zone-a") is shard index 0. -const shardNameMarker = "shard-" - // ShardIndexMapper tracks the workflow DONs currently known to the Capabilities // Registry and detects their shard indices (0, 1, 2, ...). // It's a global singleton object used by ManualShardResolver and, in consequence, @@ -117,13 +113,13 @@ func shardDONsByIndex(reg *registry.RegistryMetadata, localDON commoncap.DON, na // workflow DON's name (e.g. "workflow-1-zone-a-shard-1" -> 1). A name with no // "shard-" suffix is shard index 0. func shardIndexFromName(name string) (uint32, error) { - idx := strings.LastIndex(name, shardNameMarker) + idx := strings.LastIndex(name, types.ShardNameMarker) if idx == -1 { return 0, nil } - suffix := name[idx+len(shardNameMarker):] + suffix := name[idx+len(types.ShardNameMarker):] if len(suffix) != 1 || suffix[0] < '0' || suffix[0] > '9' { - return 0, fmt.Errorf("expected DON name %q to end with %q followed by a single digit, got suffix %q", name, shardNameMarker, suffix) + return 0, fmt.Errorf("expected DON name %q to end with %q followed by a single digit, got suffix %q", name, types.ShardNameMarker, suffix) } return uint32(suffix[0] - '0'), nil } @@ -134,7 +130,7 @@ func shardIndexFromName(name string) (uint32, error) { // "workflow-1-zone-a-shard-1" -> "workflow-1-zone-a"). A name with no such // suffix is returned unchanged. func shardGroupNamePrefix(name string) string { - idx := strings.LastIndex(name, shardNameMarker) + idx := strings.LastIndex(name, types.ShardNameMarker) if idx == -1 { return name } diff --git a/core/services/workflows/shardownership/types/types.go b/core/services/workflows/shardownership/types/types.go new file mode 100644 index 00000000000..cce88707a28 --- /dev/null +++ b/core/services/workflows/shardownership/types/types.go @@ -0,0 +1,15 @@ +package types + +const ( + // ShardNameMarker is the marker a workflow DON's name is suffixed with to + // encode its shard index (e.g. "workflow-1-zone-a-shard-1" -> shard 1). A + // name with no such suffix (e.g. "workflow-1-zone-a") is shard index 0. + ShardNameMarker = "shard-" + + // WorkflowFamilySuffix is the suffix appended to a DON family name to + // denote its dedicated workflow-fetching family. A DON belonging to a + // family named "WorkflowFamilySuffix" (e.g. "zone-a_workflows") + // should also fetch workflows registered under the base family with the + // suffix stripped (e.g. "zone-a"). + WorkflowFamilySuffix = "_workflows" +) diff --git a/core/services/workflows/syncer/v2/activation_errors.go b/core/services/workflows/syncer/v2/activation_errors.go index fb6048603f5..e8078d40601 100644 --- a/core/services/workflows/syncer/v2/activation_errors.go +++ b/core/services/workflows/syncer/v2/activation_errors.go @@ -7,6 +7,7 @@ import ( eventsv2 "github.com/smartcontractkit/chainlink-protos/workflows/go/v2" "github.com/smartcontractkit/chainlink/v2/core/services/workflows/types" + wfv2 "github.com/smartcontractkit/chainlink/v2/core/services/workflows/v2" ) // ActivationRetryPolicy controls how failed workflow activations are retried. @@ -96,7 +97,12 @@ func isPermanentEngineInitError(err error) bool { // cron failure when it can't parse the schedule strings.Contains(msg, "failed to initialize job"), // cron rejection when the schedule fires faster than the allowed minimum - strings.Contains(msg, "maximum fastest cron schedule"): + strings.Contains(msg, "maximum fastest cron schedule"), + // guest attempted a disallowed host call (capability or secrets) during + // the trigger subscription phase; a deterministic user error that can + // never succeed on retry + strings.Contains(msg, wfv2.ErrCapabilityCallDuringSubscription.Error()), + strings.Contains(msg, wfv2.ErrSecretsCallDuringSubscription.Error()): return true } err = errors.Unwrap(err) diff --git a/core/services/workflows/syncer/v2/activation_errors_test.go b/core/services/workflows/syncer/v2/activation_errors_test.go index 3fef6808a3e..cffab58bcbb 100644 --- a/core/services/workflows/syncer/v2/activation_errors_test.go +++ b/core/services/workflows/syncer/v2/activation_errors_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink/v2/core/services/workflows/types" + wfv2 "github.com/smartcontractkit/chainlink/v2/core/services/workflows/v2" ) func Test_classifyActivationError(t *testing.T) { @@ -60,6 +61,20 @@ func Test_classifyActivationError(t *testing.T) { errors.New("[3]InvalidArgument: maximum fastest cron schedule is 30s")), want: ActivationNonRetryable, }, + { + // mirrors the engine's wrapping of the guest error string: the + // host rejects the secrets call, the guest reports it back and + // Subscribe fails + name: "secrets call during trigger subscription is non-retryable", + err: fmt.Errorf("failed to execute subscribe: failed to get secrets for call 1: %s", + wfv2.ErrSecretsCallDuringSubscription.Error()), + want: ActivationNonRetryable, + }, + { + name: "capability call during trigger subscription is non-retryable", + err: fmt.Errorf("failed to execute subscribe: %s", wfv2.ErrCapabilityCallDuringSubscription.Error()), + want: ActivationNonRetryable, + }, { name: "unknown error is retryable", err: errors.New("unexpected engine failure"), diff --git a/core/services/workflows/syncer/v2/contract_workflow_source.go b/core/services/workflows/syncer/v2/contract_workflow_source.go index 6d75fa478fa..8d8317f6474 100644 --- a/core/services/workflows/syncer/v2/contract_workflow_source.go +++ b/core/services/workflows/syncer/v2/contract_workflow_source.go @@ -72,7 +72,7 @@ func (c *ContractWorkflowSource) ListWorkflowMetadata(ctx context.Context, don c var headAtLastRead *commontypes.Head var allWorkflows []WorkflowMetadataView - for _, family := range don.Families { + for _, family := range ExpandWorkflowFamilies(don.Families) { params := GetWorkflowListByDONParams{ DonFamily: family, Start: big.NewInt(0), diff --git a/core/services/workflows/syncer/v2/evictable_module.go b/core/services/workflows/syncer/v2/evictable_module.go index 65eca305478..200d6a1a5a0 100644 --- a/core/services/workflows/syncer/v2/evictable_module.go +++ b/core/services/workflows/syncer/v2/evictable_module.go @@ -220,10 +220,6 @@ func (m *EvictableModule) Close() { } } -func (m *EvictableModule) IsLegacyDAG() bool { - return false -} - func (m *EvictableModule) Execute(ctx context.Context, request *sdkpb.ExecuteRequest, handler host.ExecutionHelper) (*sdkpb.ExecutionResult, error) { // Each loaded module is held behind a refcounted loadedModule. Pinning is // a CAS-conditional refcount increment: it succeeds only if the holder is diff --git a/core/services/workflows/syncer/v2/evictable_module_test.go b/core/services/workflows/syncer/v2/evictable_module_test.go index e4d7e789d5a..abf5fb52ec2 100644 --- a/core/services/workflows/syncer/v2/evictable_module_test.go +++ b/core/services/workflows/syncer/v2/evictable_module_test.go @@ -44,9 +44,8 @@ type fakeModule struct { closeCalls atomic.Int32 } -func (f *fakeModule) Start() {} -func (f *fakeModule) IsLegacyDAG() bool { return false } -func (f *fakeModule) Close() { f.closeCalls.Add(1) } +func (f *fakeModule) Start() {} +func (f *fakeModule) Close() { f.closeCalls.Add(1) } func (f *fakeModule) Execute(_ context.Context, _ *sdkpb.ExecuteRequest, _ host.ExecutionHelper) (*sdkpb.ExecutionResult, error) { return &sdkpb.ExecutionResult{}, nil } @@ -180,7 +179,6 @@ func TestEvictable_DelegatesToInner(t *testing.T) { //nolint:paralleltest // pac result, err := em.Execute(context.Background(), &sdkpb.ExecuteRequest{}, nil) require.NoError(t, err) assert.NotNil(t, result) - assert.False(t, em.IsLegacyDAG()) } func TestEvictable_LastUsedUpdated(t *testing.T) { //nolint:paralleltest // package-level hooks and eviction config diff --git a/core/services/workflows/syncer/v2/file_workflow_source.go b/core/services/workflows/syncer/v2/file_workflow_source.go index 5cb2ec36346..b18f3c80f48 100644 --- a/core/services/workflows/syncer/v2/file_workflow_source.go +++ b/core/services/workflows/syncer/v2/file_workflow_source.go @@ -107,7 +107,7 @@ func (f *FileWorkflowSource) ListWorkflowMetadata(ctx context.Context, don capab // Build a set of DON families for efficient lookup donFamilySet := make(map[string]bool) - for _, family := range don.Families { + for _, family := range ExpandWorkflowFamilies(don.Families) { donFamilySet[family] = true } diff --git a/core/services/workflows/syncer/v2/grpc_workflow_source.go b/core/services/workflows/syncer/v2/grpc_workflow_source.go index 820c0e05b50..de1ac32a931 100644 --- a/core/services/workflows/syncer/v2/grpc_workflow_source.go +++ b/core/services/workflows/syncer/v2/grpc_workflow_source.go @@ -173,10 +173,11 @@ func (g *GRPCWorkflowSource) ListWorkflowMetadata(ctx context.Context, don capab var allViews []WorkflowMetadataView var start int64 + families := ExpandWorkflowFamilies(don.Families) // Fetch all pages for { - workflows, hasMore, err := g.fetchPageWithRetry(ctx, don.Families, start) + workflows, hasMore, err := g.fetchPageWithRetry(ctx, families, start) if err != nil { return nil, nil, err } diff --git a/core/services/workflows/syncer/v2/handler.go b/core/services/workflows/syncer/v2/handler.go index 8fffb25c9f8..f0ffcae60d5 100644 --- a/core/services/workflows/syncer/v2/handler.go +++ b/core/services/workflows/syncer/v2/handler.go @@ -422,7 +422,7 @@ func (h *eventHandler) close() error { // untouched. A spec that is genuinely released stops being snapshotted // (the spec meter sub-service unregisters itself after this hook runs). cs := make([]io.Closer, 0, len(es)+1) - cs = append(cs, h.engineLimiters) + cs = append(cs, h.engineLimiters, h.featureFlags) for _, e := range es { cs = append(cs, e) } diff --git a/core/services/workflows/syncer/v2/helpers.go b/core/services/workflows/syncer/v2/helpers.go index 60e33a9d07f..0f6c15b9f38 100644 --- a/core/services/workflows/syncer/v2/helpers.go +++ b/core/services/workflows/syncer/v2/helpers.go @@ -3,6 +3,7 @@ package v2 import ( "context" "errors" + "strings" "sync" "github.com/ethereum/go-ethereum/common/hexutil" @@ -13,9 +14,38 @@ import ( eventsv2 "github.com/smartcontractkit/chainlink-protos/workflows/go/v2" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/workflows/ratelimiter" + shardownershiptypes "github.com/smartcontractkit/chainlink/v2/core/services/workflows/shardownership/types" "github.com/smartcontractkit/chainlink/v2/core/services/workflows/syncerlimiter" ) +// ExpandWorkflowFamilies returns families with an extra entry added for every +// family ending in shardownershiptypes.WorkflowFamilySuffix (e.g. +// "zone-a_workflows"): the base family name with that suffix stripped (e.g. +// "zone-a"). This lets a DON belonging to a "_workflows" family also +// fetch workflows registered directly under the bare "" family. Order +// is preserved and duplicates are dropped. +func ExpandWorkflowFamilies(families []string) []string { + seen := make(map[string]bool, len(families)) + expanded := make([]string, 0, len(families)) + + add := func(family string) { + if seen[family] { + return + } + seen[family] = true + expanded = append(expanded, family) + } + + for _, family := range families { + add(family) + if base, ok := strings.CutSuffix(family, shardownershiptypes.WorkflowFamilySuffix); ok { + add(base) + } + } + + return expanded +} + var rlConfig = ratelimiter.Config{ GlobalRPS: 1000.0, GlobalBurst: 1000, diff --git a/core/services/workflows/syncer/v2/helpers_test.go b/core/services/workflows/syncer/v2/helpers_test.go new file mode 100644 index 00000000000..4366e363dad --- /dev/null +++ b/core/services/workflows/syncer/v2/helpers_test.go @@ -0,0 +1,49 @@ +package v2 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestExpandWorkflowFamilies(t *testing.T) { + t.Parallel() + tests := []struct { + name string + families []string + want []string + }{ + { + name: "no workflows suffix", + families: []string{"zone-a"}, + want: []string{"zone-a"}, + }, + { + name: "workflows suffix expands to base family", + families: []string{"zone-a_workflows"}, + want: []string{"zone-a_workflows", "zone-a"}, + }, + { + name: "base family already present is not duplicated", + families: []string{"zone-a_workflows", "zone-a"}, + want: []string{"zone-a_workflows", "zone-a"}, + }, + { + name: "mixed families", + families: []string{"zone-a", "zone-b_workflows"}, + want: []string{"zone-a", "zone-b_workflows", "zone-b"}, + }, + { + name: "empty input", + families: []string{}, + want: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tt.want, ExpandWorkflowFamilies(tt.families)) + }) + } +} diff --git a/core/services/workflows/v2/base_engine.go b/core/services/workflows/v2/base_engine.go index d5879177a70..7bf11b8b288 100644 --- a/core/services/workflows/v2/base_engine.go +++ b/core/services/workflows/v2/base_engine.go @@ -250,7 +250,7 @@ func (e *baseEngine) Subscribe(ctx context.Context) ([]*sdkpb.TriggerSubscriptio Request: &sdkpb.ExecuteRequest_Subscribe{}, MaxResponseSize: uint64(moduleExecuteMaxResponseSizeBytes), Config: e.cfg.WorkflowConfig, - }, NewDisallowedExecutionHelper(e.logger(), userLogChan, timeProvider, e.secretsFetcher(e.cfg.WorkflowID))) + }, NewDisallowedExecutionHelper(e.logger(), userLogChan, timeProvider)) if err != nil { return nil, fmt.Errorf("failed to execute subscribe: %w", err) } @@ -766,7 +766,7 @@ func (e *baseEngine) startExecution(ctx context.Context, event triggers.Coordina return nil } -func (e *baseEngine) secretsFetcher(phaseID string) SecretsFetcher { +func (e *baseEngine) secretsFetcher(executionID string) SecretsFetcher { if e.cfg.SecretsFetcher != nil { return e.cfg.SecretsFetcher } @@ -782,9 +782,7 @@ func (e *baseEngine) secretsFetcher(phaseID string) SecretsFetcher { e.cfg.WorkflowOwner, e.cfg.WorkflowName.String(), e.cfg.WorkflowID, - // phaseID is the executionID if called during an execution, - // or the workflowID if called during trigger subscription - phaseID, + executionID, e.cfg.WorkflowEncryptionKey, e.cfg.OverrideFetcher, ) diff --git a/core/services/workflows/v2/confidential_module.go b/core/services/workflows/v2/confidential_module.go index 4f64cbd939e..4bb56129078 100644 --- a/core/services/workflows/v2/confidential_module.go +++ b/core/services/workflows/v2/confidential_module.go @@ -150,9 +150,8 @@ func NewConfidentialModule(capRegistry registry.CapabilitiesRegistry, executionH }, nil } -func (m *ConfidentialModule) Start() {} -func (m *ConfidentialModule) Close() {} -func (m *ConfidentialModule) IsLegacyDAG() bool { return false } +func (m *ConfidentialModule) Start() {} +func (m *ConfidentialModule) Close() {} func (m *ConfidentialModule) Execute( ctx context.Context, diff --git a/core/services/workflows/v2/confidential_module_test.go b/core/services/workflows/v2/confidential_module_test.go index e6334ff7f60..60eef3c0c75 100644 --- a/core/services/workflows/v2/confidential_module_test.go +++ b/core/services/workflows/v2/confidential_module_test.go @@ -823,7 +823,6 @@ func TestConfidentialModule_InterfaceMethods(t *testing.T) { // These are no-ops but should not panic. mod.Start() mod.Close() - assert.False(t, mod.IsLegacyDAG()) } func mustNewConfidentialModule(t *testing.T, capRegistry *regmocks.CapabilitiesRegistry, executionHandlers *confidentialrelay.ExecutionHandlers, binaryURL string, binaryHash []byte, workflowID, workflowOwner, workflowName, workflowTag string, enabledGate limits.GateLimiter, lggr logger.Logger) *ConfidentialModule { diff --git a/core/services/workflows/v2/config.go b/core/services/workflows/v2/config.go index 268c994c56e..bf993076a24 100644 --- a/core/services/workflows/v2/config.go +++ b/core/services/workflows/v2/config.go @@ -403,6 +403,13 @@ func NewFeatureFlags(lf limits.Factory, cfgFn func(*cresettings.Workflows)) (*En }, nil } +func (e *EngineFeatureFlags) Close() error { + if e == nil || e.WorkflowTagBackfill == nil { + return nil + } + return e.WorkflowTagBackfill.Close() +} + const ( defaultHeartbeatFrequencyMs = 1000 * 60 // 1 minute defaultShutdownTimeoutMs = 5000 diff --git a/core/services/workflows/v2/config_test.go b/core/services/workflows/v2/config_test.go index ee32cce2380..c54b3a0467e 100644 --- a/core/services/workflows/v2/config_test.go +++ b/core/services/workflows/v2/config_test.go @@ -71,13 +71,15 @@ func defaultTestConfig(t *testing.T, cfgFn func(*cresettings.Workflows)) *v2.Eng lggr := logger.TestLogger(t) sLimiter, err := syncerlimiter.NewWorkflowLimits(lggr, syncerlimiter.Config{}, lf) require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, sLimiter.Close()) }) limiters, err := v2.NewLimiters(lf, cfgFn) require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, limiters.Close()) }) featureFlags, err := v2.NewFeatureFlags(lf, cfgFn) require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, featureFlags.Close()) }) subscriberMock := capmocks.NewDonSubscriber(t) subscriberMock.EXPECT().Subscribe(matches.AnyContext).Return(make(<-chan commoncaps.DON), func() {}, nil).Maybe() - t.Cleanup(func() { assert.NoError(t, limiters.Close()) }) return &v2.EngineConfig{ Lggr: lggr, diff --git a/core/services/workflows/v2/coordinated_engine.go b/core/services/workflows/v2/coordinated_engine.go index d4079ae13a5..c84b9d4c10e 100644 --- a/core/services/workflows/v2/coordinated_engine.go +++ b/core/services/workflows/v2/coordinated_engine.go @@ -107,6 +107,7 @@ func (e *coordinatedEngine) init(ctx context.Context) { defer span.End() if err := e.base.initDONSubscribe(ctx); err != nil { + e.base.metrics.IncrementWorkflowInitializationFailureCounter(ctx, initFailureDONSubscribe) e.base.cfg.Hooks.OnInitialized(err) return } diff --git a/core/services/workflows/v2/disallowed_execution_helper.go b/core/services/workflows/v2/disallowed_execution_helper.go index 054bbab6b6a..08c3ada5a9d 100644 --- a/core/services/workflows/v2/disallowed_execution_helper.go +++ b/core/services/workflows/v2/disallowed_execution_helper.go @@ -16,22 +16,35 @@ type disallowedExecutionHelper struct { lggr logger.Logger UserLogChan chan<- *protoevents.LogLine TimeProvider - SecretsFetcher } -func NewDisallowedExecutionHelper(lggr logger.Logger, userLogChan chan<- *protoevents.LogLine, timeProvider TimeProvider, secretsFetcher SecretsFetcher) *disallowedExecutionHelper { +func NewDisallowedExecutionHelper(lggr logger.Logger, userLogChan chan<- *protoevents.LogLine, timeProvider TimeProvider) *disallowedExecutionHelper { return &disallowedExecutionHelper{ - lggr: lggr, - UserLogChan: userLogChan, - TimeProvider: timeProvider, - SecretsFetcher: secretsFetcher, + lggr: lggr, + UserLogChan: userLogChan, + TimeProvider: timeProvider, } } var _ host.ExecutionHelper = &disallowedExecutionHelper{} +var ( + // ErrCapabilityCallDuringSubscription is returned to a guest that attempts + // a capability call during the trigger subscription phase. Capability + // calls are only allowed during workflow executions. + ErrCapabilityCallDuringSubscription = errors.New("capability calls cannot be made during trigger subscription") + // ErrSecretsCallDuringSubscription is returned to a guest that attempts a + // secrets call during the trigger subscription phase. Secrets calls are + // only allowed during workflow executions. + ErrSecretsCallDuringSubscription = errors.New("secrets calls cannot be made during trigger subscription") +) + func (d disallowedExecutionHelper) CallCapability(_ context.Context, _ *sdkpb.CapabilityRequest) (*sdkpb.CapabilityResponse, error) { - return nil, errors.New("capability calls cannot be made during this execution") + return nil, ErrCapabilityCallDuringSubscription +} + +func (d disallowedExecutionHelper) GetSecrets(_ context.Context, _ *sdkpb.GetSecretsRequest) ([]*sdkpb.SecretResponse, error) { + return nil, ErrSecretsCallDuringSubscription } func (d disallowedExecutionHelper) GetWorkflowExecutionID() string { diff --git a/core/services/workflows/v2/disallowed_execution_helper_test.go b/core/services/workflows/v2/disallowed_execution_helper_test.go new file mode 100644 index 00000000000..bcb63131639 --- /dev/null +++ b/core/services/workflows/v2/disallowed_execution_helper_test.go @@ -0,0 +1,43 @@ +package v2 + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + sdkpb "github.com/smartcontractkit/chainlink-protos/cre/go/sdk" + protoevents "github.com/smartcontractkit/chainlink-protos/workflows/go/events" + eventsv2 "github.com/smartcontractkit/chainlink-protos/workflows/go/v2" + "github.com/smartcontractkit/chainlink/v2/core/services/workflows/types" +) + +func TestDisallowedExecutionHelper_GetSecretsReturnsError(t *testing.T) { + t.Parallel() + + h := NewDisallowedExecutionHelper(logger.Test(t), make(chan<- *protoevents.LogLine), &types.LocalTimeProvider{}) + + _, err := h.GetSecrets(t.Context(), &sdkpb.GetSecretsRequest{}) + require.ErrorIs(t, err, ErrSecretsCallDuringSubscription) + require.ErrorContains(t, err, "secrets calls cannot be made during trigger subscription") +} + +func TestDisallowedExecutionHelper_CallCapabilityReturnsError(t *testing.T) { + t.Parallel() + + h := NewDisallowedExecutionHelper(logger.Test(t), make(chan<- *protoevents.LogLine), &types.LocalTimeProvider{}) + + _, err := h.CallCapability(context.Background(), &sdkpb.CapabilityRequest{}) + require.ErrorIs(t, err, ErrCapabilityCallDuringSubscription) + require.ErrorContains(t, err, "capability calls cannot be made during trigger subscription") +} + +func TestDisallowedExecutionHelper_OtherHelpers(t *testing.T) { + t.Parallel() + + h := NewDisallowedExecutionHelper(logger.Test(t), make(chan<- *protoevents.LogLine), &types.LocalTimeProvider{}) + + require.Empty(t, h.GetWorkflowExecutionID()) + require.Error(t, h.EmitUserMetric(context.Background(), &eventsv2.WorkflowUserMetric{})) +} diff --git a/core/services/workflows/v2/engine.go b/core/services/workflows/v2/engine.go index 4b1d4a33990..4ee09347ee7 100644 --- a/core/services/workflows/v2/engine.go +++ b/core/services/workflows/v2/engine.go @@ -138,10 +138,12 @@ func (e *engine) init(ctx context.Context) { defer span.End() if err := e.useWorkflowLimit(ctx); err != nil { + e.base.metrics.IncrementWorkflowInitializationFailureCounter(ctx, initFailureWorkflowLimit) e.base.cfg.Hooks.OnInitialized(err) return } if err := e.base.initDONSubscribe(ctx); err != nil { + e.base.metrics.IncrementWorkflowInitializationFailureCounter(ctx, initFailureDONSubscribe) e.base.cfg.Hooks.OnInitialized(err) return } @@ -149,11 +151,13 @@ func (e *engine) init(ctx context.Context) { subscriptions, err := e.base.Subscribe(ctx) if err != nil { e.base.logger().Errorw("failed to subscribe to triggers", "err", err) + e.base.metrics.IncrementWorkflowInitializationFailureCounter(ctx, initFailureReasonForSubscribe(err)) e.base.cfg.Hooks.OnInitialized(err) return } if err := e.runTriggerSubscriptionPhase(ctx, subscriptions); err != nil { e.base.logger().Errorw("Workflow Engine initialization failed", "err", err) + e.base.metrics.IncrementWorkflowInitializationFailureCounter(ctx, initFailureTriggerRegistration) e.base.cfg.Hooks.OnInitialized(err) return } diff --git a/core/services/workflows/v2/engine_init_failure_metrics_test.go b/core/services/workflows/v2/engine_init_failure_metrics_test.go new file mode 100644 index 00000000000..a9e2f60c271 --- /dev/null +++ b/core/services/workflows/v2/engine_init_failure_metrics_test.go @@ -0,0 +1,122 @@ +package v2_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + + "github.com/smartcontractkit/chainlink-common/pkg/beholder" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" + regmocks "github.com/smartcontractkit/chainlink-common/pkg/types/core/mocks" + modulemocks "github.com/smartcontractkit/chainlink-common/pkg/workflows/wasm/host/mocks" + sdkpb "github.com/smartcontractkit/chainlink-protos/cre/go/sdk" + v2 "github.com/smartcontractkit/chainlink/v2/core/services/workflows/v2" + "github.com/smartcontractkit/chainlink/v2/core/utils/matches" +) + +// setupInitFailureTestMeter swaps the global beholder client for one backed +// by a manual meter reader, so the engine's counters can be collected. Mirrors +// setupTestMeter in the internal test package. +// +// The swap is global: parallel full-engine tests may register their counters +// on this meter too, so assertions here must tolerate foreign increments +// (reason-unique labels and lower bounds, not exact totals). +func setupInitFailureTestMeter(t *testing.T) *sdkmetric.ManualReader { + t.Helper() + + reader := sdkmetric.NewManualReader() + mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) + t.Cleanup(func() { _ = mp.Shutdown(context.Background()) }) + + prevClient := beholder.GetClient() + t.Cleanup(func() { beholder.SetClient(prevClient) }) + + client := beholder.NoopClientConfig{Lggr: logger.Test(t)}.New() + client.Meter = mp.Meter("beholder") + client.MeterProvider = mp + beholder.SetClient(client) + + return reader +} + +// collectInitFailureCounter sums the named counter and returns the values +// observed for the "reason" attribute. +func collectInitFailureCounter(t *testing.T, reader *sdkmetric.ManualReader, name string) (int64, []string) { + t.Helper() + + var rm metricdata.ResourceMetrics + require.NoError(t, reader.Collect(context.Background(), &rm)) + + var total int64 + var reasons []string + for _, sm := range rm.ScopeMetrics { + for _, m := range sm.Metrics { + if m.Name != name { + continue + } + data, ok := m.Data.(metricdata.Sum[int64]) + require.True(t, ok, "expected Sum[int64] for %s, got %T", name, m.Data) + for _, dp := range data.DataPoints { + total += dp.Value + if r, ok := dp.Attributes.Value("reason"); ok { + reasons = append(reasons, r.AsString()) + } + } + } + } + return total, reasons +} + +// TestEngine_InitFailureCounter_DisallowedSecretsCall verifies end to end +// that a module failing the Subscribe phase by attempting a secrets call +// during trigger subscription fails engine initialization and increments the +// initialization failure counter with the disallowed-secrets reason. +// +//nolint:paralleltest // swaps the global beholder client +func TestEngine_InitFailureCounter_DisallowedSecretsCall(t *testing.T) { + reader := setupInitFailureTestMeter(t) + + module := modulemocks.NewModuleV2(t) + capreg := regmocks.NewCapabilitiesRegistry(t) + capreg.EXPECT().LocalNode(matches.AnyContext).Return(newNode(t), nil) + + initDoneCh := make(chan error, 1) + + cfg := defaultTestConfig(t, nil) + cfg.Module = module + cfg.CapRegistry = capreg + cfg.Hooks = v2.LifecycleHooks{ + OnInitialized: func(err error) { + initDoneCh <- err + }, + } + + engine, err := v2.NewEngine(cfg) + require.NoError(t, err) + + module.EXPECT().Start().Once() + module.EXPECT().Close().Once() + // the module reports the host's secrets rejection back as its error result + module.EXPECT().Execute(matches.AnyContext, mock.Anything, mock.Anything). + Return(&sdkpb.ExecutionResult{ + Result: &sdkpb.ExecutionResult_Error{ + Error: "failed to get secrets for call 1: secrets calls cannot be made during trigger subscription", + }, + }, nil).Once() + + servicetest.Run(t, engine) + + initErr := <-initDoneCh + require.ErrorContains(t, initErr, "secrets calls cannot be made during trigger subscription") + + // count may include foreign increments from parallel engine tests sharing + // the swapped meter; the disallowed-secrets reason is unique to this test + gotCount, gotReasons := collectInitFailureCounter(t, reader, "platform_engine_workflow_initialization_failures_total") + require.GreaterOrEqual(t, gotCount, int64(1)) + require.Contains(t, gotReasons, "disallowed_secrets_call_during_subscription") +} diff --git a/core/services/workflows/v2/engine_test.go b/core/services/workflows/v2/engine_test.go index a3b73b6e3b3..d15b3eb0991 100644 --- a/core/services/workflows/v2/engine_test.go +++ b/core/services/workflows/v2/engine_test.go @@ -231,6 +231,7 @@ WorkflowLimit = "1" PerOwner: 0, }, limits.Factory{Settings: getter}) require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, sLimiter.Close()) }) module := modulemocks.NewModuleV2(t) module.EXPECT().Start() @@ -432,6 +433,42 @@ func TestEngine_TriggerSubscriptions(t *testing.T) { }) } +func TestEngine_TriggerSubscriptionPhase_DisallowsSecretsCalls(t *testing.T) { + t.Parallel() + + module := modulemocks.NewModuleV2(t) + capreg := regmocks.NewCapabilitiesRegistry(t) + capreg.EXPECT().LocalNode(matches.AnyContext).Return(newNode(t), nil) + + initDoneCh := make(chan error) + + cfg := defaultTestConfig(t, nil) + cfg.Module = module + cfg.CapRegistry = capreg + cfg.Hooks = v2.LifecycleHooks{ + OnInitialized: func(err error) { + initDoneCh <- err + }, + } + + engine, err := v2.NewEngine(cfg) + require.NoError(t, err) + + module.EXPECT().Start().Once() + module.EXPECT().Execute(matches.AnyContext, mock.Anything, mock.Anything). + Run(func(_ context.Context, _ *sdkpb.ExecuteRequest, helper host.ExecutionHelper) { + _, err := helper.GetSecrets(context.Background(), &sdkpb.GetSecretsRequest{}) + assert.ErrorContains(t, err, "secrets calls cannot be made during trigger subscription") + }). + Return(newTriggerSubs(0), nil).Once() + require.NoError(t, engine.Start(t.Context())) + + require.NoError(t, <-initDoneCh) + + module.EXPECT().Close().Once() + require.NoError(t, engine.Close()) +} + func TestEngine_TriggerRegistrationLogging(t *testing.T) { t.Parallel() @@ -3291,6 +3328,7 @@ func createTestEngineForDonVersionTest( sLimiter, err := syncerlimiter.NewWorkflowLimits(lggr, syncerlimiter.Config{}, lf) require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, sLimiter.Close()) }) // Use a mock WASM module (only mock we need!) wasmModule := modulemocks.NewModuleV2(t) diff --git a/core/services/workflows/v2/init_failures.go b/core/services/workflows/v2/init_failures.go new file mode 100644 index 00000000000..caa91bd0061 --- /dev/null +++ b/core/services/workflows/v2/init_failures.go @@ -0,0 +1,45 @@ +package v2 + +import "strings" + +// Engine initialization failure reasons labeling the +// platform_engine_workflow_initialization_failures_total counter. Values must +// stay low-cardinality: these are metric labels, not logs. +const ( + // initFailureWorkflowLimit labels the node's workflow-count limit being + // reached before initialization began. + initFailureWorkflowLimit = "workflow_limit" + // initFailureDONSubscribe labels a failure subscribing to DON + // configuration notifications. + initFailureDONSubscribe = "don_subscribe" + // initFailureSubscribe labels the module's Subscribe (trigger + // subscription) request failing for any reason not classified below. + initFailureSubscribe = "subscribe" + // initFailureTriggerRegistration labels a failure registering the + // module's trigger subscriptions with the capabilities registry. + initFailureTriggerRegistration = "trigger_registration" + // initFailureDisallowedCapabilityCall labels a module that attempted a + // capability call during the trigger subscription phase. + initFailureDisallowedCapabilityCall = "disallowed_capability_call_during_subscription" + // initFailureDisallowedSecretsCall labels a module that attempted a + // secrets call during the trigger subscription phase. + initFailureDisallowedSecretsCall = "disallowed_secrets_call_during_subscription" +) + +// initFailureReasonForSubscribe classifies a failed Subscribe phase for the +// initialization failure counter. The disallowed-host-call rejections cross +// the WASM boundary as plain strings (the guest re-reports the host's error +// text), so they are matched by text rather than errors.Is. +func initFailureReasonForSubscribe(err error) string { + if err == nil { + return initFailureSubscribe + } + switch { + case strings.Contains(err.Error(), ErrSecretsCallDuringSubscription.Error()): + return initFailureDisallowedSecretsCall + case strings.Contains(err.Error(), ErrCapabilityCallDuringSubscription.Error()): + return initFailureDisallowedCapabilityCall + default: + return initFailureSubscribe + } +} diff --git a/core/services/workflows/v2/init_failures_test.go b/core/services/workflows/v2/init_failures_test.go new file mode 100644 index 00000000000..f71ba6033fe --- /dev/null +++ b/core/services/workflows/v2/init_failures_test.go @@ -0,0 +1,90 @@ +package v2 + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/metrics" + "github.com/smartcontractkit/chainlink/v2/core/services/workflows/monitoring" +) + +func Test_initFailureReasonForSubscribe(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + err error + want string + }{ + { + name: "nil error is a generic subscribe failure", + err: nil, + want: initFailureSubscribe, + }, + { + // mirrors the chain: host rejects the secrets call, the guest + // re-reports the host's error text, Subscribe wraps it as a string + name: "guest-reported secrets rejection is classified", + err: fmt.Errorf("failed to execute subscribe: failed to get secrets for call 1: %s", ErrSecretsCallDuringSubscription.Error()), + want: initFailureDisallowedSecretsCall, + }, + { + name: "guest-reported capability rejection is classified", + err: fmt.Errorf("failed to execute subscribe: %s", ErrCapabilityCallDuringSubscription.Error()), + want: initFailureDisallowedCapabilityCall, + }, + { + name: "module error is a generic subscribe failure", + err: fmt.Errorf("failed to execute subscribe: %w", errors.New("wasm trap")), + want: initFailureSubscribe, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + require.Equal(t, tt.want, initFailureReasonForSubscribe(tt.err)) + }) + } +} + +// TestEngine_WorkflowInitializationFailureCounter verifies the counter +// records failures with the reason label, defaulting an empty reason to +// "unknown". +// +//nolint:paralleltest // setupTestMeter swaps the global beholder client +func TestEngine_WorkflowInitializationFailureCounter(t *testing.T) { + const counterName = "platform_engine_workflow_initialization_failures_total" + + t.Run("records failures with reason label", func(t *testing.T) { + reader := setupTestMeter(t) + + em, err := monitoring.InitMonitoringResources() + require.NoError(t, err) + labeler := monitoring.NewWorkflowsMetricLabeler(metrics.NewLabeler(), em) + + labeler.IncrementWorkflowInitializationFailureCounter(t.Context(), initFailureDisallowedSecretsCall) + labeler.IncrementWorkflowInitializationFailureCounter(t.Context(), initFailureTriggerRegistration) + labeler.IncrementWorkflowInitializationFailureCounter(t.Context(), "") + + gotCount, gotReasons := collectCounterValue(t, reader, counterName) + require.Equal(t, int64(3), gotCount) + require.ElementsMatch(t, []string{initFailureDisallowedSecretsCall, initFailureTriggerRegistration, "unknown"}, gotReasons) + }) + + t.Run("not incremented on successful initialization", func(t *testing.T) { + reader := setupTestMeter(t) + + em, err := monitoring.InitMonitoringResources() + require.NoError(t, err) + labeler := monitoring.NewWorkflowsMetricLabeler(metrics.NewLabeler(), em) + + labeler.IncrementWorkflowInitializationCounter(t.Context()) + + gotCount, _ := collectCounterValue(t, reader, counterName) + require.Zero(t, gotCount) + }) +} diff --git a/deployment/cre/capabilities_registry/v2/changeset/add_capabilities.go b/deployment/cre/capabilities_registry/v2/changeset/add_capabilities.go index da9e46daff9..9e4621bdc0e 100644 --- a/deployment/cre/capabilities_registry/v2/changeset/add_capabilities.go +++ b/deployment/cre/capabilities_registry/v2/changeset/add_capabilities.go @@ -39,11 +39,15 @@ type AddCapabilitiesInput struct { // capability here, the changeset will fail if it cannot read the current config // count from the registry, preventing accidental config count collisions. FirstOCR3ConfigCapabilities map[string][]string `json:"firstOCR3ConfigCapabilities" yaml:"firstOCR3ConfigCapabilities"` + + // SkipDuplicateCapabilityValidation disables the precondition check that rejects assigning + // the same capability to multiple DONs within a DON family. + SkipDuplicateCapabilityValidation bool `json:"skipDuplicateCapabilityValidation,omitempty" yaml:"skipDuplicateCapabilityValidation,omitempty"` } type AddCapabilities struct{} -func (u AddCapabilities) VerifyPreconditions(_ cldf.Environment, config AddCapabilitiesInput) error { +func (u AddCapabilities) VerifyPreconditions(e cldf.Environment, config AddCapabilitiesInput) error { if len(config.DonCapabilityConfigs) == 0 { return errors.New("donCapabilityConfigs must contain at least one DON entry") } @@ -55,6 +59,18 @@ func (u AddCapabilities) VerifyPreconditions(_ cldf.Environment, config AddCapab return fmt.Errorf("donCapabilityConfigs[%q] must contain at least one capability config", donName) } } + + if !config.SkipDuplicateCapabilityValidation { + existingDONs, err := getExistingDONsForPreconditionCheck(e, config.RegistryChainSel, config.RegistryQualifier) + if err != nil { + return err + } + + if err := sequences.ValidateNoDuplicateCapabilitiesAcrossDONs(config.DonCapabilityConfigs, existingDONs); err != nil { + return fmt.Errorf("AddCapabilities precondition failed: %w", err) + } + } + return nil } diff --git a/deployment/cre/capabilities_registry/v2/changeset/add_capabilities_test.go b/deployment/cre/capabilities_registry/v2/changeset/add_capabilities_test.go index e8f21a818ed..a7198073c46 100644 --- a/deployment/cre/capabilities_registry/v2/changeset/add_capabilities_test.go +++ b/deployment/cre/capabilities_registry/v2/changeset/add_capabilities_test.go @@ -78,6 +78,7 @@ func TestAddCapabilities_VerifyPreconditions(t *testing.T) { chainSelector := h.RegistrySelector capCfg := []contracts.CapabilityConfig{{Capability: contracts.Capability{CapabilityID: "cap@1.0.0"}, Config: map[string]any{"k": "v"}}} + anotherCapCfg := []contracts.CapabilityConfig{{Capability: contracts.Capability{CapabilityID: "another-cap@1.0.0"}, Config: map[string]any{"k": "v"}}} // Empty map err := cs.VerifyPreconditions(h.Runtime.Environment(), changeset.AddCapabilitiesInput{ @@ -113,23 +114,144 @@ func TestAddCapabilities_VerifyPreconditions(t *testing.T) { // Valid (single DON) err = cs.VerifyPreconditions(h.Runtime.Environment(), changeset.AddCapabilitiesInput{ RegistryChainSel: chainSelector, - RegistryQualifier: "qual", + RegistryQualifier: test.RegistryQualifier, DonCapabilityConfigs: map[string][]contracts.CapabilityConfig{ "don-1": capCfg, }, }) require.NoError(t, err) - // Valid (multiple DONs) + // Valid (multiple DONs, disjoint capabilities) err = cs.VerifyPreconditions(h.Runtime.Environment(), changeset.AddCapabilitiesInput{ RegistryChainSel: chainSelector, - RegistryQualifier: "qual", + RegistryQualifier: test.RegistryQualifier, + DonCapabilityConfigs: map[string][]contracts.CapabilityConfig{ + "don-1": capCfg, + "don-2": anotherCapCfg, + }, + }) + require.NoError(t, err) + + // Invalid: same capability ID assigned to two different DONs in the input itself + err = cs.VerifyPreconditions(h.Runtime.Environment(), changeset.AddCapabilitiesInput{ + RegistryChainSel: chainSelector, + RegistryQualifier: test.RegistryQualifier, DonCapabilityConfigs: map[string][]contracts.CapabilityConfig{ "don-1": capCfg, "don-2": capCfg, }, }) + require.Error(t, err) + assert.Contains(t, err.Error(), "is assigned to both DON") + + // Invalid: capability already assigned to an existing on-chain DON, being assigned to a new DON + existingCapCfg := []contracts.CapabilityConfig{{Capability: contracts.Capability{CapabilityID: test.TestCapabilityID}, Config: map[string]any{"k": "v"}}} + err = cs.VerifyPreconditions(h.Runtime.Environment(), changeset.AddCapabilitiesInput{ + RegistryChainSel: chainSelector, + RegistryQualifier: test.RegistryQualifier, + DonCapabilityConfigs: map[string][]contracts.CapabilityConfig{ + "don-1": existingCapCfg, + }, + }) + require.Error(t, err) + assert.Contains(t, err.Error(), "already assigned to on-chain DON") + + // Valid: re-assigning a capability to the on-chain DON that already holds it + err = cs.VerifyPreconditions(h.Runtime.Environment(), changeset.AddCapabilitiesInput{ + RegistryChainSel: chainSelector, + RegistryQualifier: test.RegistryQualifier, + DonCapabilityConfigs: map[string][]contracts.CapabilityConfig{ + test.DONName: existingCapCfg, + }, + }) require.NoError(t, err) + + // Valid: duplicate checks are skipped when explicitly opted out (e.g. mainline tiering migrations) + err = cs.VerifyPreconditions(h.Runtime.Environment(), changeset.AddCapabilitiesInput{ + RegistryChainSel: chainSelector, + RegistryQualifier: test.RegistryQualifier, + SkipDuplicateCapabilityValidation: true, + DonCapabilityConfigs: map[string][]contracts.CapabilityConfig{ + "don-1": capCfg, + "don-2": capCfg, + "don-3": existingCapCfg, + }, + }) + require.NoError(t, err) +} + +// The same capability may live on DONs in different families (e.g. zone-a and zone-b), +// but not on two DONs within the same family. +func TestAddCapabilities_VerifyPreconditions_DONFamilyScoping(t *testing.T) { + cs := changeset.AddCapabilities{} + + h := test.NewTestHarness(t) + + // Register a DON in a different family holding the same capability as test.DONName. + otherZoneDON := "test-don-zone-b" + err := h.Runtime.Exec( + runtime.ChangesetTask(changeset.ConfigureCapabilitiesRegistry{}, changeset.ConfigureCapabilitiesRegistryInput{ + ChainSelector: h.RegistrySelector, + CapabilitiesRegistryAddress: h.RegistryAddress.Hex(), + DONs: []changeset.CapabilitiesRegistryNewDONParams{ + { + Name: otherZoneDON, + DonFamilies: []string{"zone-b"}, // harness DON is in "test-family" + Config: map[string]any{"defaultConfig": map[string]any{}}, + CapabilityConfigurations: []changeset.CapabilitiesRegistryCapabilityConfiguration{ + {CapabilityID: test.TestCapabilityID}, + }, + Nodes: h.Don.GetP2PIDs().Strings(), + F: 1, + IsPublic: true, + }, + }, + }), + ) + require.NoError(t, err) + + existingCapCfg := []contracts.CapabilityConfig{{Capability: contracts.Capability{CapabilityID: test.TestCapabilityID}, Config: map[string]any{"k": "v"}}} + + // Valid: the zone-b DON shares no family with the harness DON that already holds the capability. + err = cs.VerifyPreconditions(h.Runtime.Environment(), changeset.AddCapabilitiesInput{ + RegistryChainSel: h.RegistrySelector, + RegistryQualifier: test.RegistryQualifier, + DonCapabilityConfigs: map[string][]contracts.CapabilityConfig{ + otherZoneDON: existingCapCfg, + }, + }) + require.NoError(t, err) + + // Invalid: a DON in the same family as the harness DON cannot also hold the capability. + sameFamilyDON := "test-don-same-family" + err = h.Runtime.Exec( + runtime.ChangesetTask(changeset.ConfigureCapabilitiesRegistry{}, changeset.ConfigureCapabilitiesRegistryInput{ + ChainSelector: h.RegistrySelector, + CapabilitiesRegistryAddress: h.RegistryAddress.Hex(), + DONs: []changeset.CapabilitiesRegistryNewDONParams{ + { + Name: sameFamilyDON, + DonFamilies: []string{"test-family"}, // same family as the harness DON + Config: map[string]any{"defaultConfig": map[string]any{}}, + Nodes: h.Don.GetP2PIDs().Strings(), + F: 1, + IsPublic: true, + }, + }, + }), + ) + require.NoError(t, err) + + err = cs.VerifyPreconditions(h.Runtime.Environment(), changeset.AddCapabilitiesInput{ + RegistryChainSel: h.RegistrySelector, + RegistryQualifier: test.RegistryQualifier, + DonCapabilityConfigs: map[string][]contracts.CapabilityConfig{ + sameFamilyDON: existingCapCfg, + }, + }) + require.Error(t, err) + assert.Contains(t, err.Error(), "already assigned to on-chain DON") + assert.Contains(t, err.Error(), test.DONName) } func addNewCapability(t *testing.T, h *test.Harness, capID string) { diff --git a/deployment/cre/capabilities_registry/v2/changeset/sequences/validate_capabilities.go b/deployment/cre/capabilities_registry/v2/changeset/sequences/validate_capabilities.go new file mode 100644 index 00000000000..c82a32d3ae7 --- /dev/null +++ b/deployment/cre/capabilities_registry/v2/changeset/sequences/validate_capabilities.go @@ -0,0 +1,87 @@ +package sequences + +import ( + "fmt" + "slices" + + capabilities_registry_v2 "github.com/smartcontractkit/chainlink-evm/gethwrappers/workflow/generated/capabilities_registry_wrapper_v2" + + "github.com/smartcontractkit/chainlink/deployment/cre/capabilities_registry/v2/changeset/operations/contracts" +) + +// ValidateNoDuplicateCapabilitiesAcrossDONs ensures a capability is not assigned to two +// different DONs within the same DON family. DONs in disjoint families (e.g. zone-a and +// zone-b) may hold the same capability. +func ValidateNoDuplicateCapabilitiesAcrossDONs( + donCapabilityConfigs map[string][]contracts.CapabilityConfig, + existingDONs []capabilities_registry_v2.CapabilitiesRegistryDONInfo, +) error { + existingByName := make(map[string]capabilities_registry_v2.CapabilitiesRegistryDONInfo, len(existingDONs)) + for _, don := range existingDONs { + existingByName[don.Name] = don + } + + // capID -> DON name it is claimed by, across the input map itself. + claimedByInput := make(map[string]string, len(donCapabilityConfigs)) + for donName, configs := range donCapabilityConfigs { + for _, cfg := range configs { + capID := cfg.Capability.CapabilityID + otherDON, ok := claimedByInput[capID] + if !ok { + claimedByInput[capID] = donName + continue + } + if otherDON == donName { + continue + } + if !donsShareFamily(otherDON, donName, existingByName) { + continue + } + return fmt.Errorf( + "capability %q is assigned to both DON %q and DON %q, which share a DON family; "+ + "a capability can only be assigned to one DON per family", + capID, otherDON, donName) + } + } + + for _, don := range existingDONs { + for _, cfg := range don.CapabilityConfigurations { + donName, ok := claimedByInput[cfg.CapabilityId] + if !ok { + continue + } + if don.Name == donName { + // Re-assigning a capability to the DON that already holds it is not a conflict. + continue + } + if !donsShareFamily(don.Name, donName, existingByName) { + continue + } + return fmt.Errorf( + "capability %q is already assigned to on-chain DON %q, which shares a DON family with DON %q; "+ + "a capability can only be assigned to one DON per family", + cfg.CapabilityId, don.Name, donName) + } + } + + return nil +} + +// donsShareFamily reports whether two DONs share at least one DON family. A DON not found +// on-chain is conservatively treated as sharing a family with every other DON. +func donsShareFamily( + donA, donB string, + existingByName map[string]capabilities_registry_v2.CapabilitiesRegistryDONInfo, +) bool { + a, okA := existingByName[donA] + b, okB := existingByName[donB] + if !okA || !okB { + return true + } + for _, family := range a.DonFamilies { + if slices.Contains(b.DonFamilies, family) { + return true + } + } + return false +} diff --git a/deployment/cre/capabilities_registry/v2/changeset/types.go b/deployment/cre/capabilities_registry/v2/changeset/types.go index 60fcc346bb5..d14a6b8938b 100644 --- a/deployment/cre/capabilities_registry/v2/changeset/types.go +++ b/deployment/cre/capabilities_registry/v2/changeset/types.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" @@ -180,3 +181,34 @@ func (don CapabilitiesRegistryNewDONParams) applyModifiersToCapabilityConfigs(e return capabilityConfigurations, nil } + +// getExistingDONsForPreconditionCheck reads the current on-chain DONs for the capabilities +// registry identified by chainSel/qualifier, so preconditions can check a proposed capability +// assignment against what other DONs already have configured. +func getExistingDONsForPreconditionCheck( + e cldf.Environment, chainSel uint64, qualifier string, +) ([]capabilities_registry_v2.CapabilitiesRegistryDONInfo, error) { + chain, ok := e.BlockChains.EVMChains()[chainSel] + if !ok { + return nil, fmt.Errorf("chain not found for selector %d", chainSel) + } + + registryAddressRef, err := e.DataStore.Addresses().Get(pkg.GetCapRegV2AddressRefKey(chainSel, qualifier)) + if err != nil { + return nil, fmt.Errorf("failed to get registry address: %w", err) + } + + capReg, err := capabilities_registry_v2.NewCapabilitiesRegistry( + common.HexToAddress(registryAddressRef.Address), chain.Client, + ) + if err != nil { + return nil, fmt.Errorf("failed to create CapabilitiesRegistry: %w", err) + } + + existingDONs, err := pkg.GetDONs(&bind.CallOpts{}, capReg) + if err != nil { + return nil, fmt.Errorf("failed to get existing DONs: %w", err) + } + + return existingDONs, nil +} diff --git a/deployment/cre/capabilities_registry/v2/changeset/update_don.go b/deployment/cre/capabilities_registry/v2/changeset/update_don.go index a5b5e9d8e03..e3d910ec158 100644 --- a/deployment/cre/capabilities_registry/v2/changeset/update_don.go +++ b/deployment/cre/capabilities_registry/v2/changeset/update_don.go @@ -62,16 +62,32 @@ type UpdateDONInput struct { // Safe to combine with MergeCapabilityConfigsWithOnChain=true when CapabilityConfigs is empty. Nodes []string `json:"nodes,omitempty" yaml:"nodes,omitempty"` + // SkipDuplicateCapabilityValidation disables the precondition check that rejects assigning + // the same capability to multiple DONs within a DON family. + SkipDuplicateCapabilityValidation bool `json:"skipDuplicateCapabilityValidation,omitempty" yaml:"skipDuplicateCapabilityValidation,omitempty"` + MCMSConfig *crecontracts.MCMSConfig `json:"mcmsConfig" yaml:"mcmsConfig"` } type UpdateDON struct{} -func (u UpdateDON) VerifyPreconditions(_ cldf.Environment, config UpdateDONInput) error { +func (u UpdateDON) VerifyPreconditions(e cldf.Environment, config UpdateDONInput) error { if config.DONName == "" { return errors.New("must provide a non-empty DONName") } + if !config.SkipDuplicateCapabilityValidation { + existingDONs, err := getExistingDONsForPreconditionCheck(e, config.RegistryChainSel, config.RegistryQualifier) + if err != nil { + return err + } + + donCapabilityConfigs := map[string][]contracts.CapabilityConfig{config.DONName: config.CapabilityConfigs} + if err := sequences.ValidateNoDuplicateCapabilitiesAcrossDONs(donCapabilityConfigs, existingDONs); err != nil { + return fmt.Errorf("UpdateDON precondition failed: %w", err) + } + } + return nil } diff --git a/deployment/cre/capabilities_registry/v2/changeset/update_don_test.go b/deployment/cre/capabilities_registry/v2/changeset/update_don_test.go index ad9ca2d7c4d..9727f19994b 100644 --- a/deployment/cre/capabilities_registry/v2/changeset/update_don_test.go +++ b/deployment/cre/capabilities_registry/v2/changeset/update_don_test.go @@ -365,6 +365,104 @@ func TestUpdateDONChangeset_VerifyPreconditions_EmptyName(t *testing.T) { require.ErrorContains(t, err, "must provide a non-empty DONName") } +// A capability already assigned to a different on-chain DON in the same family cannot be +// assigned to another DON in that family. +func TestUpdateDONChangeset_VerifyPreconditions_RejectsCapabilityAlreadyOnAnotherDON(t *testing.T) { + t.Parallel() + fx := setupRegistryForUpdateDON(t, false, false) + + otherDONName := fx.donName + "-other" + err := fx.rt.Exec( + runtime.ChangesetTask(changeset.ConfigureCapabilitiesRegistry{}, changeset.ConfigureCapabilitiesRegistryInput{ + ChainSelector: fx.selector, + CapabilitiesRegistryAddress: fx.address, + DONs: []changeset.CapabilitiesRegistryNewDONParams{ + { + Name: otherDONName, + DonFamilies: []string{"upd-family"}, // same family as fx.donName + Config: map[string]any{"defaultConfig": map[string]any{}}, + Nodes: []string{p2pID1, p2pID2}, + F: 1, + IsPublic: true, + }, + }, + }), + ) + require.NoError(t, err) + + var cs changeset.UpdateDON + err = cs.VerifyPreconditions(fx.rt.Environment(), changeset.UpdateDONInput{ + RegistryQualifier: fx.qualifier, + RegistryChainSel: fx.selector, + DONName: otherDONName, + CapabilityConfigs: []contracts.CapabilityConfig{ + {Capability: contracts.Capability{CapabilityID: fx.capIDs[0]}}, // already assigned to fx.donName + }, + }) + require.Error(t, err) + require.ErrorContains(t, err, "already assigned to on-chain DON") + require.ErrorContains(t, err, fx.donName) + + // Re-applying to the DON that already has the capability is not a conflict. + err = cs.VerifyPreconditions(fx.rt.Environment(), changeset.UpdateDONInput{ + RegistryQualifier: fx.qualifier, + RegistryChainSel: fx.selector, + DONName: fx.donName, + CapabilityConfigs: []contracts.CapabilityConfig{ + {Capability: contracts.Capability{CapabilityID: fx.capIDs[0]}}, + }, + }) + require.NoError(t, err) + + // With validation explicitly skipped (e.g. mainline tiering migrations), the same assignment is allowed. + err = cs.VerifyPreconditions(fx.rt.Environment(), changeset.UpdateDONInput{ + RegistryQualifier: fx.qualifier, + RegistryChainSel: fx.selector, + DONName: otherDONName, + SkipDuplicateCapabilityValidation: true, + CapabilityConfigs: []contracts.CapabilityConfig{ + {Capability: contracts.Capability{CapabilityID: fx.capIDs[0]}}, + }, + }) + require.NoError(t, err) +} + +// The same capability may be assigned to DONs in different families (e.g. zone-a and zone-b). +func TestUpdateDONChangeset_VerifyPreconditions_AllowsCapabilityOnDONInDifferentFamily(t *testing.T) { + t.Parallel() + fx := setupRegistryForUpdateDON(t, false, false) + + otherDONName := fx.donName + "-other-zone" + err := fx.rt.Exec( + runtime.ChangesetTask(changeset.ConfigureCapabilitiesRegistry{}, changeset.ConfigureCapabilitiesRegistryInput{ + ChainSelector: fx.selector, + CapabilitiesRegistryAddress: fx.address, + DONs: []changeset.CapabilitiesRegistryNewDONParams{ + { + Name: otherDONName, + DonFamilies: []string{"other-family"}, // disjoint from fx.donName's "upd-family" + Config: map[string]any{"defaultConfig": map[string]any{}}, + Nodes: []string{p2pID1, p2pID2}, + F: 1, + IsPublic: true, + }, + }, + }), + ) + require.NoError(t, err) + + var cs changeset.UpdateDON + err = cs.VerifyPreconditions(fx.rt.Environment(), changeset.UpdateDONInput{ + RegistryQualifier: fx.qualifier, + RegistryChainSel: fx.selector, + DONName: otherDONName, + CapabilityConfigs: []contracts.CapabilityConfig{ + {Capability: contracts.Capability{CapabilityID: fx.capIDs[0]}}, // already assigned to fx.donName, but in another family + }, + }) + require.NoError(t, err) +} + // Chain not found: Apply should fail early with a clear message. func TestUpdateDONChangeset_ByName_ChainNotFound(t *testing.T) { t.Parallel() diff --git a/deployment/go.mod b/deployment/go.mod index a698ba1f784..a5c18580870 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -46,7 +46,7 @@ require ( github.com/smartcontractkit/chainlink-ccip/chains/solana/deployment v0.0.0-20260916222901-720a003dab50 github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260916222901-720a003dab50 github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260916222901-720a003dab50 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260921174556-538f4bf0cb6f + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260928161532-9b1062f5076f github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d github.com/smartcontractkit/chainlink-data-streams v1.1.1 github.com/smartcontractkit/chainlink-deployments-framework v0.122.2 @@ -219,7 +219,6 @@ require ( github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/dominikbraun/graph v0.23.0 // indirect github.com/doyensec/safeurl v0.2.5 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.7.0 // indirect diff --git a/deployment/go.sum b/deployment/go.sum index b682ecfd148..524ce8bf8e5 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -454,8 +454,6 @@ github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo= -github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc= github.com/doyensec/safeurl v0.2.5 h1:kKu0JNQy0tJ8jkDyB5h6Aml9vWWniq+mpoa12EGLcOQ= github.com/doyensec/safeurl v0.2.5/go.mod h1:3H0cgRpPYPSpgxRRn5yGD35Ns/LgGX/BVWSBbzUqXtY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -1405,8 +1403,8 @@ github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260916222901-720a github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260916222901-720a003dab50/go.mod h1:Dm/Abz0vr7F2v9TygUX947q8HIwJEJikDfSdanX1Cic= github.com/smartcontractkit/chainlink-ccv v0.13.1-0.20260918171034-c93b0d2ef3c0 h1:3f+Ijgr0vghawMUAitgFILQpt4KsoYHGv1Zb/YBeeQM= github.com/smartcontractkit/chainlink-ccv v0.13.1-0.20260918171034-c93b0d2ef3c0/go.mod h1:gX7FJzSnGQx4RAlsn2rRW8IQBlEFZYiXFXEEB5jnQgA= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260921174556-538f4bf0cb6f h1:mj37sJTagtZwTVhU7IIxu6/XCGiK740XmcwXihdKtvk= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260921174556-538f4bf0cb6f/go.mod h1:BniOJQOh52lgDDNyfOQJEmQUVQ68txTSeT0Qxwt0zQI= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260928161532-9b1062f5076f h1:gasuIea3r0AIuEkXDQvIw+TclWt184xBRZGwed+fP6I= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260928161532-9b1062f5076f/go.mod h1:2AvwgNr3I6s0Ny1qmovy/stHQIihEv/9g74aNYzQYYo= github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d h1:M9xuiIvdiHtim4nlxCVajyC/Mk63tAg9ESWb/biAhNA= github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d/go.mod h1:rI2njmTlyTvh+xhmORmK6qX3+V1ycDAqGJLu8LS8WpU= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260915184316-2730f1867c92 h1:Z1OIGukDhJD+Gknr5G07Vtml24E7BwKaIQoWzV/SdqA= diff --git a/go.mod b/go.mod index d83b5706203..73c4825a890 100644 --- a/go.mod +++ b/go.mod @@ -82,7 +82,7 @@ require ( github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260908155618-50f521d70e62 github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260908155618-50f521d70e62 github.com/smartcontractkit/chainlink-ccv v0.13.1-0.20260918171034-c93b0d2ef3c0 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260921174556-538f4bf0cb6f + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260928161532-9b1062f5076f github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260915184316-2730f1867c92 github.com/smartcontractkit/chainlink-data-streams v1.1.1 @@ -214,7 +214,6 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect github.com/dgraph-io/badger/v4 v4.7.0 // indirect github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect - github.com/dominikbraun/graph v0.23.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.7.0 // indirect github.com/emicklei/dot v1.6.2 // indirect diff --git a/go.sum b/go.sum index 9ca84dc2eb3..e067dafc04d 100644 --- a/go.sum +++ b/go.sum @@ -311,8 +311,6 @@ github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo= -github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc= github.com/doyensec/safeurl v0.2.5 h1:kKu0JNQy0tJ8jkDyB5h6Aml9vWWniq+mpoa12EGLcOQ= github.com/doyensec/safeurl v0.2.5/go.mod h1:3H0cgRpPYPSpgxRRn5yGD35Ns/LgGX/BVWSBbzUqXtY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -1106,8 +1104,8 @@ github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260 github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260908155618-50f521d70e62/go.mod h1:c++Ed0jeYn4w8WIfiAboRwN6ClRRVGglL0qwB/QjJvM= github.com/smartcontractkit/chainlink-ccv v0.13.1-0.20260918171034-c93b0d2ef3c0 h1:3f+Ijgr0vghawMUAitgFILQpt4KsoYHGv1Zb/YBeeQM= github.com/smartcontractkit/chainlink-ccv v0.13.1-0.20260918171034-c93b0d2ef3c0/go.mod h1:gX7FJzSnGQx4RAlsn2rRW8IQBlEFZYiXFXEEB5jnQgA= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260921174556-538f4bf0cb6f h1:mj37sJTagtZwTVhU7IIxu6/XCGiK740XmcwXihdKtvk= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260921174556-538f4bf0cb6f/go.mod h1:BniOJQOh52lgDDNyfOQJEmQUVQ68txTSeT0Qxwt0zQI= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260928161532-9b1062f5076f h1:gasuIea3r0AIuEkXDQvIw+TclWt184xBRZGwed+fP6I= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260928161532-9b1062f5076f/go.mod h1:2AvwgNr3I6s0Ny1qmovy/stHQIihEv/9g74aNYzQYYo= github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d h1:M9xuiIvdiHtim4nlxCVajyC/Mk63tAg9ESWb/biAhNA= github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d/go.mod h1:rI2njmTlyTvh+xhmORmK6qX3+V1ycDAqGJLu8LS8WpU= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260915184316-2730f1867c92 h1:Z1OIGukDhJD+Gknr5G07Vtml24E7BwKaIQoWzV/SdqA= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 74ea5146188..f07c1d098d7 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -34,7 +34,7 @@ require ( github.com/smartcontractkit/chainlink-ccip/chains/evm v0.0.0-20260916222901-720a003dab50 github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260916222901-720a003dab50 github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260916222901-720a003dab50 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260921174556-538f4bf0cb6f + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260928161532-9b1062f5076f github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d github.com/smartcontractkit/chainlink-deployments-framework v0.122.2 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260917132551-2a2b38e8b744 @@ -185,7 +185,6 @@ require ( github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/dominikbraun/graph v0.23.0 // indirect github.com/doyensec/safeurl v0.2.5 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.7.0 // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index f37e92a6dfd..8b7eceeeb22 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -441,8 +441,6 @@ github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo= -github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc= github.com/doyensec/safeurl v0.2.5 h1:kKu0JNQy0tJ8jkDyB5h6Aml9vWWniq+mpoa12EGLcOQ= github.com/doyensec/safeurl v0.2.5/go.mod h1:3H0cgRpPYPSpgxRRn5yGD35Ns/LgGX/BVWSBbzUqXtY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -1394,8 +1392,8 @@ github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260916222901-720a github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260916222901-720a003dab50/go.mod h1:Dm/Abz0vr7F2v9TygUX947q8HIwJEJikDfSdanX1Cic= github.com/smartcontractkit/chainlink-ccv v0.13.1-0.20260918171034-c93b0d2ef3c0 h1:3f+Ijgr0vghawMUAitgFILQpt4KsoYHGv1Zb/YBeeQM= github.com/smartcontractkit/chainlink-ccv v0.13.1-0.20260918171034-c93b0d2ef3c0/go.mod h1:gX7FJzSnGQx4RAlsn2rRW8IQBlEFZYiXFXEEB5jnQgA= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260921174556-538f4bf0cb6f h1:mj37sJTagtZwTVhU7IIxu6/XCGiK740XmcwXihdKtvk= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260921174556-538f4bf0cb6f/go.mod h1:BniOJQOh52lgDDNyfOQJEmQUVQ68txTSeT0Qxwt0zQI= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260928161532-9b1062f5076f h1:gasuIea3r0AIuEkXDQvIw+TclWt184xBRZGwed+fP6I= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260928161532-9b1062f5076f/go.mod h1:2AvwgNr3I6s0Ny1qmovy/stHQIihEv/9g74aNYzQYYo= github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d h1:M9xuiIvdiHtim4nlxCVajyC/Mk63tAg9ESWb/biAhNA= github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d/go.mod h1:rI2njmTlyTvh+xhmORmK6qX3+V1ycDAqGJLu8LS8WpU= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260915184316-2730f1867c92 h1:Z1OIGukDhJD+Gknr5G07Vtml24E7BwKaIQoWzV/SdqA= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 2aadbe7a5c1..6f29b5d19f7 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -24,7 +24,7 @@ require ( github.com/smartcontractkit/chainlink-ccip/chains/evm v0.0.0-20260916222901-720a003dab50 github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260916222901-720a003dab50 github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260916222901-720a003dab50 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260921174556-538f4bf0cb6f + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260928161532-9b1062f5076f github.com/smartcontractkit/chainlink-deployments-framework v0.122.2 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260917132551-2a2b38e8b744 github.com/smartcontractkit/chainlink-testing-framework/framework v0.16.8 @@ -182,7 +182,6 @@ require ( github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/dominikbraun/graph v0.23.0 // indirect github.com/doyensec/safeurl v0.2.5 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.7.0 // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 4df6d217e75..0848f18e14f 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -515,8 +515,6 @@ github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo= -github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc= github.com/doyensec/safeurl v0.2.5 h1:kKu0JNQy0tJ8jkDyB5h6Aml9vWWniq+mpoa12EGLcOQ= github.com/doyensec/safeurl v0.2.5/go.mod h1:3H0cgRpPYPSpgxRRn5yGD35Ns/LgGX/BVWSBbzUqXtY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -1632,8 +1630,8 @@ github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260916222901-720a github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260916222901-720a003dab50/go.mod h1:Dm/Abz0vr7F2v9TygUX947q8HIwJEJikDfSdanX1Cic= github.com/smartcontractkit/chainlink-ccv v0.13.1-0.20260918171034-c93b0d2ef3c0 h1:3f+Ijgr0vghawMUAitgFILQpt4KsoYHGv1Zb/YBeeQM= github.com/smartcontractkit/chainlink-ccv v0.13.1-0.20260918171034-c93b0d2ef3c0/go.mod h1:gX7FJzSnGQx4RAlsn2rRW8IQBlEFZYiXFXEEB5jnQgA= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260921174556-538f4bf0cb6f h1:mj37sJTagtZwTVhU7IIxu6/XCGiK740XmcwXihdKtvk= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260921174556-538f4bf0cb6f/go.mod h1:BniOJQOh52lgDDNyfOQJEmQUVQ68txTSeT0Qxwt0zQI= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260928161532-9b1062f5076f h1:gasuIea3r0AIuEkXDQvIw+TclWt184xBRZGwed+fP6I= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260928161532-9b1062f5076f/go.mod h1:2AvwgNr3I6s0Ny1qmovy/stHQIihEv/9g74aNYzQYYo= github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d h1:M9xuiIvdiHtim4nlxCVajyC/Mk63tAg9ESWb/biAhNA= github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d/go.mod h1:rI2njmTlyTvh+xhmORmK6qX3+V1ycDAqGJLu8LS8WpU= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260915184316-2730f1867c92 h1:Z1OIGukDhJD+Gknr5G07Vtml24E7BwKaIQoWzV/SdqA= diff --git a/system-tests/lib/go.mod b/system-tests/lib/go.mod index 5f3bb3c7041..1346828d714 100644 --- a/system-tests/lib/go.mod +++ b/system-tests/lib/go.mod @@ -37,7 +37,7 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.111 github.com/smartcontractkit/chainlink-aptos v0.0.0-20260923062809-ca886ade4d26 github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260916222901-720a003dab50 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260921174556-538f4bf0cb6f + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260928161532-9b1062f5076f github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d github.com/smartcontractkit/chainlink-confidential-compute v1.3.0 github.com/smartcontractkit/chainlink-deployments-framework v0.122.2 @@ -219,7 +219,6 @@ require ( github.com/docker/docker-credential-helpers v0.9.8 // indirect github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/dominikbraun/graph v0.23.0 // indirect github.com/doyensec/safeurl v0.2.5 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.7.0 // indirect diff --git a/system-tests/lib/go.sum b/system-tests/lib/go.sum index e3bf489d441..0bfe752f939 100644 --- a/system-tests/lib/go.sum +++ b/system-tests/lib/go.sum @@ -535,8 +535,6 @@ github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQ github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo= -github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc= github.com/doyensec/safeurl v0.2.5 h1:kKu0JNQy0tJ8jkDyB5h6Aml9vWWniq+mpoa12EGLcOQ= github.com/doyensec/safeurl v0.2.5/go.mod h1:3H0cgRpPYPSpgxRRn5yGD35Ns/LgGX/BVWSBbzUqXtY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -1656,8 +1654,8 @@ github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260916222901-720a github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260916222901-720a003dab50/go.mod h1:Dm/Abz0vr7F2v9TygUX947q8HIwJEJikDfSdanX1Cic= github.com/smartcontractkit/chainlink-ccv v0.13.1-0.20260918171034-c93b0d2ef3c0 h1:3f+Ijgr0vghawMUAitgFILQpt4KsoYHGv1Zb/YBeeQM= github.com/smartcontractkit/chainlink-ccv v0.13.1-0.20260918171034-c93b0d2ef3c0/go.mod h1:gX7FJzSnGQx4RAlsn2rRW8IQBlEFZYiXFXEEB5jnQgA= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260921174556-538f4bf0cb6f h1:mj37sJTagtZwTVhU7IIxu6/XCGiK740XmcwXihdKtvk= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260921174556-538f4bf0cb6f/go.mod h1:BniOJQOh52lgDDNyfOQJEmQUVQ68txTSeT0Qxwt0zQI= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260928161532-9b1062f5076f h1:gasuIea3r0AIuEkXDQvIw+TclWt184xBRZGwed+fP6I= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260928161532-9b1062f5076f/go.mod h1:2AvwgNr3I6s0Ny1qmovy/stHQIihEv/9g74aNYzQYYo= github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d h1:M9xuiIvdiHtim4nlxCVajyC/Mk63tAg9ESWb/biAhNA= github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d/go.mod h1:rI2njmTlyTvh+xhmORmK6qX3+V1ycDAqGJLu8LS8WpU= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260915184316-2730f1867c92 h1:Z1OIGukDhJD+Gknr5G07Vtml24E7BwKaIQoWzV/SdqA= diff --git a/system-tests/tests/go.mod b/system-tests/tests/go.mod index 8057161b687..82f34694426 100644 --- a/system-tests/tests/go.mod +++ b/system-tests/tests/go.mod @@ -76,7 +76,7 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.111 github.com/smartcontractkit/chainlink-aptos v0.0.0-20260923062809-ca886ade4d26 github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260916222901-720a003dab50 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260921174556-538f4bf0cb6f + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260928161532-9b1062f5076f github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260915184316-2730f1867c92 github.com/smartcontractkit/chainlink-confidential-compute v1.3.0 @@ -292,7 +292,6 @@ require ( github.com/docker/docker-credential-helpers v0.9.8 // indirect github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/dominikbraun/graph v0.23.0 // indirect github.com/doyensec/safeurl v0.2.5 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.7.0 // indirect diff --git a/system-tests/tests/go.sum b/system-tests/tests/go.sum index ed6c08db2c8..fc68484ffa6 100644 --- a/system-tests/tests/go.sum +++ b/system-tests/tests/go.sum @@ -593,8 +593,6 @@ github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQ github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo= -github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc= github.com/doyensec/safeurl v0.2.5 h1:kKu0JNQy0tJ8jkDyB5h6Aml9vWWniq+mpoa12EGLcOQ= github.com/doyensec/safeurl v0.2.5/go.mod h1:3H0cgRpPYPSpgxRRn5yGD35Ns/LgGX/BVWSBbzUqXtY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -1835,8 +1833,8 @@ github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260916222901-720a github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20260916222901-720a003dab50/go.mod h1:Dm/Abz0vr7F2v9TygUX947q8HIwJEJikDfSdanX1Cic= github.com/smartcontractkit/chainlink-ccv v0.13.1-0.20260918171034-c93b0d2ef3c0 h1:3f+Ijgr0vghawMUAitgFILQpt4KsoYHGv1Zb/YBeeQM= github.com/smartcontractkit/chainlink-ccv v0.13.1-0.20260918171034-c93b0d2ef3c0/go.mod h1:gX7FJzSnGQx4RAlsn2rRW8IQBlEFZYiXFXEEB5jnQgA= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260921174556-538f4bf0cb6f h1:mj37sJTagtZwTVhU7IIxu6/XCGiK740XmcwXihdKtvk= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260921174556-538f4bf0cb6f/go.mod h1:BniOJQOh52lgDDNyfOQJEmQUVQ68txTSeT0Qxwt0zQI= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260928161532-9b1062f5076f h1:gasuIea3r0AIuEkXDQvIw+TclWt184xBRZGwed+fP6I= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260928161532-9b1062f5076f/go.mod h1:2AvwgNr3I6s0Ny1qmovy/stHQIihEv/9g74aNYzQYYo= github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d h1:M9xuiIvdiHtim4nlxCVajyC/Mk63tAg9ESWb/biAhNA= github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d/go.mod h1:rI2njmTlyTvh+xhmORmK6qX3+V1ycDAqGJLu8LS8WpU= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260915184316-2730f1867c92 h1:Z1OIGukDhJD+Gknr5G07Vtml24E7BwKaIQoWzV/SdqA=