diff --git a/.changeset/compute-usage-meter-records.md b/.changeset/compute-usage-meter-records.md new file mode 100644 index 00000000000..3f0f92cd20d --- /dev/null +++ b/.changeset/compute-usage-meter-records.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Emit a `cre:workflow:compute` usage MeterRecord per workflow execution when `[Metering].MeterRecordsEnabled` is on. The compute duration is reported by the module that ran the workflow (the guest run for WASM, the enclave-measured run for confidential workflows). #internal diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 983ea3a7210..79383f1c4eb 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -29,7 +29,7 @@ require ( github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chain-selectors v1.0.112 github.com/smartcontractkit/chainlink-ccip/chains/evm v0.0.0-20261001213322-a2d686f610ca - github.com/smartcontractkit/chainlink-common v0.11.2-0.20261014202458-d375ab88face + github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57 github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d github.com/smartcontractkit/chainlink-deployments-framework v0.123.3 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20261005112317-b723176adfe8 diff --git a/core/scripts/go.sum b/core/scripts/go.sum index ae73a29cb9c..481d19d685b 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1688,8 +1688,8 @@ github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20261001213322-a2d6 github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20261001213322-a2d686f610ca/go.mod h1:Dm/Abz0vr7F2v9TygUX947q8HIwJEJikDfSdanX1Cic= github.com/smartcontractkit/chainlink-ccv v0.14.0 h1:184WuWwWAOo8b5uTuxv8v+zI7UcjSQCKz5z2V1zHVPQ= github.com/smartcontractkit/chainlink-ccv v0.14.0/go.mod h1:k/RxmdtzRqqiNcl5X2Af7Q7R53ZAaMwgyd9ctMKtK6Q= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20261014202458-d375ab88face h1:3kepaeHtyG+dZ0xI9vu0BOuv3u8nlmKBGZf0r6KmZbE= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20261014202458-d375ab88face/go.mod h1:QeHe1mlcVzzg+awYAkRfl/8mDLRAhsl18IIOg4MVZec= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57 h1:71QCghDEu9+vSDAjhrUrIzL01MlrIOWOW8gABXuBtMg= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57/go.mod h1:QeHe1mlcVzzg+awYAkRfl/8mDLRAhsl18IIOg4MVZec= 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.20261005132539-b7d63d5fd549 h1:J4vYRJxkBO8Yf//17wScKjYWmS45VZaFWx0zt+4ETEU= diff --git a/core/services/cre/cre.go b/core/services/cre/cre.go index c30a2484f9c..95fe4995416 100644 --- a/core/services/cre/cre.go +++ b/core/services/cre/cre.go @@ -982,6 +982,14 @@ func newWorkflowRegistrySyncerV2( handlerOpts = append(handlerOpts, syncerV2.WithSpecMeter(specMeter)) } + // Compute usage records share the [Metering].MeterRecordsEnabled gate. + if meterRecordsEnabled { + usageIdentity := meterIdentity + usageIdentity.Service = resourcemanager.EmittingServiceWorkflowEngine + usageIdentity = resourcemanager.WithWorkflowUsagePool(usageIdentity, resourcemanager.ResourceTypeWorkflowCompute) + handlerOpts = append(handlerOpts, syncerV2.WithUsageMetering(usageIdentity)) + } + mc := capCfg.WorkflowRegistry().ModuleCache() cacheEnabled := mc.Enabled() diskMonitorEnabled := mc.DiskMonitorEnabled() || cacheEnabled diff --git a/core/services/workflows/syncer/v2/handler.go b/core/services/workflows/syncer/v2/handler.go index 46191f7a5ce..8893e08d83e 100644 --- a/core/services/workflows/syncer/v2/handler.go +++ b/core/services/workflows/syncer/v2/handler.go @@ -108,6 +108,10 @@ type eventHandler struct { // disabled: its handler-facing methods are nil-receiver safe no-ops. specMeter *SpecMeter + // meterRecordsEnabled gates the engines' compute usage records. + meterRecordsEnabled bool + usageIdentity resourcemanager.ResourceIdentity + orgResolver orgresolver.OrgResolver secretsFetcher v2.SecretsFetcher // localSecretOverrides is keyed by owner address; values are secret id -> secret value @@ -193,6 +197,14 @@ func WithBillingClient(client metering.BillingClient) func(*eventHandler) { } } +// WithUsageMetering enables compute usage records on the engines with identity. +func WithUsageMetering(identity resourcemanager.ResourceIdentity) func(*eventHandler) { + return func(e *eventHandler) { + e.meterRecordsEnabled = true + e.usageIdentity = identity + } +} + // WithSpecMeter supplies the SpecMeter that emits metering.v1.MeterRecord // events for the workflow_specs_v2 storage lifecycle. The handler runs it as a // sub-service and reports storage transitions through EmitSpecDelta; all other @@ -1505,6 +1517,8 @@ func (h *eventHandler) newV2EngineConfig( WorkflowRegistryAddress: h.workflowRegistryAddress, WorkflowRegistryChainSelector: h.workflowRegistryChainSelector, OrgResolver: h.orgResolver, + MeterRecordsEnabled: h.meterRecordsEnabled, + UsageIdentity: h.usageIdentity, SecretsFetcher: h.secretsFetcher, OverrideFetcher: h.overrideFetcherForOwner(owner), DebugMode: h.debugMode, diff --git a/core/services/workflows/v2/base_engine.go b/core/services/workflows/v2/base_engine.go index 03068faa65f..e04ea909121 100644 --- a/core/services/workflows/v2/base_engine.go +++ b/core/services/workflows/v2/base_engine.go @@ -25,8 +25,10 @@ import ( commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-common/pkg/contexts" "github.com/smartcontractkit/chainlink-common/pkg/custmsg" + "github.com/smartcontractkit/chainlink-common/pkg/durableemitter" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/metrics" + "github.com/smartcontractkit/chainlink-common/pkg/resourcemanager" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/services/orgresolver" "github.com/smartcontractkit/chainlink-common/pkg/settings/cresettings" @@ -111,7 +113,8 @@ type baseEngine struct { // tracer is the OTel tracer for this engine. It's a noop tracer when DebugMode is false. tracer trace.Tracer - orgID string + usageMeter *resourcemanager.ResourceManager + orgID string // orgIDMissingReason records why orgID is empty ("resolver_nil", // "resolver_error", or "empty_response"). It is set once in startWith() and // read by startExecution() to label the org_id_missing counter. It is only @@ -186,6 +189,17 @@ func newBaseEngine(cfg *EngineConfig, engineMode string) (*baseEngine, logger.Su engine.setLogger(beholderLogger) engine.meterReports = metering.NewReports(cfg.BillingClient, cfg.WorkflowOwner, cfg.WorkflowID, beholderLogger, labelsMap, metricsLabeler, cfg.WorkflowRegistryAddress, cfg.WorkflowRegistryChainSelector, metering.EngineVersionV2) engine.metrics = metricsLabeler + if cfg.MeterRecordsEnabled { + emitter := cfg.UsageEmitter + if emitter == nil { + if d := durableemitter.GetGlobalEmitter(); d != nil { + emitter = d + } else { + beholderLogger.Errorw("Compute usage metering enabled but no durable emitter is configured; compute usage records will not be delivered") + } + } + engine.usageMeter = resourcemanager.NewResourceManager(cfg.Lggr, resourcemanager.ResourceManagerConfig{MeterRecordsEnabled: true, Emitter: emitter}) + } engine.loggerLabels.Store(&labelsMap) engine.localNode.Store(&localNode) @@ -728,13 +742,6 @@ func (e *baseEngine) startExecution(ctx context.Context, event triggers.Coordina } } - // Track time the guest spends blocked in DON-time host calls so it can be - // excluded from metered compute (see compute_metering.go). The wrapper is - // always installed; whether the recorded wait is subtracted is gated by - // e.cfg.MeterComputeExcludingHostWait below. - suspension := &suspensionTracker{} - timeProvider = newMeasuredTimeProvider(timeProvider, e.cfg.Clock, suspension) - // Limit is always usable even on a read failure; err is advisory. moduleExecuteMaxResponseSizeBytes, err := e.cfg.LocalLimiters.ExecutionResponse.Limit(ctx) if err != nil { @@ -761,7 +768,6 @@ func (e *baseEngine) startExecution(ctx context.Context, event triggers.Coordina base: e, WorkflowExecutionID: executionID, ExecutionTimestamp: executionTimestamp, UserLogChan: userLogChan, TimeProvider: timeProvider, SecretsFetcher: e.secretsFetcher(executionID), executionProfile: newExecutionProfileCollector(), - suspension: suspension, } execHelper.initLimiters(e.cfg.LocalLimiters) @@ -783,25 +789,14 @@ func (e *baseEngine) startExecution(ctx context.Context, event triggers.Coordina endTime := e.cfg.Clock.Now() executionDuration := endTime.Sub(startTime) - // computeDuration is what we meter as RESOURCE_TYPE_COMPUTE. We start from the - // end-to-end wall-clock and subtract time the guest spent blocked in DON-time - // host calls: that wait is not compute and is non-deterministic across nodes - // (a node whose DON-time request lands in the current consensus round returns - // in microseconds; one whose request slips to the next round blocks ~a full - // round). Operational latency metrics below intentionally keep using - // executionDuration. + // Metered compute: the module's reported duration, else wall-clock. computeDuration := executionDuration - if hostWait := suspension.total(); hostWait > 0 { - computeDuration -= hostWait - if computeDuration < 0 { - computeDuration = 0 - } - executionLogger.Debugw("Excluding DON-time wait from metered compute", - "wallClockMs", executionDuration.Milliseconds(), - "hostWaitMs", hostWait.Milliseconds(), - "computeMs", computeDuration.Milliseconds()) + if result != nil && result.ComputeDurationMs != nil { + computeDuration = time.Duration(result.GetComputeDurationMs()) * time.Millisecond } + e.emitComputeUsage(ctx, executionLogger, executionID, computeDuration) + if isMetering { computeUnit := billing.ResourceType_name[int32(billing.ResourceType_RESOURCE_TYPE_COMPUTE)] mrErr := meteringReport.Settle(computeUnit, @@ -1146,3 +1141,36 @@ func resolveOrgID(ctx context.Context, resolver orgresolver.OrgResolver, workflo } return resolvedOrg{ID: orgID} } + +// emitComputeUsage emits the cre:workflow:compute usage MeterRecord for one +// execution and logs the emission. The log line is a contract consumed by the +// billing reconciler (fields: executionID, eventID, resourceType, value, orgID) +// and must stay stable. Fail-open: never returns an error to the execution. +func (e *baseEngine) emitComputeUsage(ctx context.Context, lggr logger.Logger, executionID string, computeDuration time.Duration) { + if e.usageMeter == nil { + return + } + resourceID, err := resourcemanager.WorkflowUsageResourceID(e.cfg.WorkflowID, executionID) + if err != nil { + lggr.Errorw("Compute usage meter record not emitted", "err", err) + return + } + identity := e.cfg.UsageIdentity + if node := e.localNode.Load(); node != nil { + identity = identity.WithDonID(strconv.FormatUint(uint64(node.WorkflowDON.ID), 10)) + } + valueMs := computeDuration.Milliseconds() + // The capability event id for compute is the execution id itself: one + // compute record per execution, identical on every node of the DON. + e.usageMeter.EmitUsage(ctx, identity, executionID, valueMs, resourcemanager.UtilizationFields{ + ResourceType: resourcemanager.ResourceTypeWorkflowCompute, + ResourceID: resourceID, + OrgID: e.orgID, + }) + lggr.Infow("Emitted capability usage meter record", + "eventID", executionID, + "resourceType", resourcemanager.ResourceTypeWorkflowCompute, + "value", valueMs, + "orgID", e.orgID, + ) +} diff --git a/core/services/workflows/v2/capability_executor.go b/core/services/workflows/v2/capability_executor.go index 294ed153fa1..01f7198b530 100644 --- a/core/services/workflows/v2/capability_executor.go +++ b/core/services/workflows/v2/capability_executor.go @@ -46,10 +46,6 @@ type ExecutionHelper struct { callCounts map[limits.Limiter[int]]int executionProfile *executionProfileCollector - - // suspension accumulates wall-clock time the guest spent blocked in DON-time - // host calls, so it can be excluded from metered compute. See compute_metering.go. - suspension *suspensionTracker } func (c *ExecutionHelper) initLimiters(limiters *EngineLimiters) { diff --git a/core/services/workflows/v2/compute_metering.go b/core/services/workflows/v2/compute_metering.go deleted file mode 100644 index 389f8b4650e..00000000000 --- a/core/services/workflows/v2/compute_metering.go +++ /dev/null @@ -1,76 +0,0 @@ -package v2 - -import ( - "sync/atomic" - "time" - - "github.com/jonboulle/clockwork" -) - -// suspensionTracker accumulates the wall-clock time during which the WASM guest -// is suspended inside a blocking DON-time host call (GetDONTime). -// -// Why this exists: -// Compute (RESOURCE_TYPE_COMPUTE) is metered as the end-to-end wall-clock around -// Module.Execute. That wall-clock also includes time the guest spends blocked in -// GetDONTime, which can stall for a full DON-time consensus round. That wait is -// not compute: it is non-deterministic across nodes (a node whose request lands -// in the current round returns in microseconds; a node whose request slips to -// the next round blocks for ~a round), and it has produced >100x divergence in -// per-node metered compute - and therefore consumed credits - for -// otherwise-identical executions on the same DON. -// -// Why summing is correct: -// The WASM guest is single-threaded, so its GetDONTime calls happen one at a -// time and never overlap with guest execution. Summing the individual wait -// intervals therefore yields the total non-compute DON-time wait, and -// -// computeTime = wallClock - totalDONTimeWait -// -// Scope: this only covers the DON-time wait, which is the one blocking host call -// the engine observes (via TimeProvider). It is NOT a general "guest suspended" -// accountant - see the package note in the PR for the capability/secrets-await -// gap, which is only measurable inside chainlink-common's host runtime. -type suspensionTracker struct { - totalNanos atomic.Int64 -} - -// add records a single guest-suspension interval. Safe for concurrent use and -// nil-safe so callers never have to guard the pointer. -func (s *suspensionTracker) add(d time.Duration) { - if s == nil || d <= 0 { - return - } - s.totalNanos.Add(int64(d)) -} - -// total returns the cumulative guest-suspended duration recorded so far. -func (s *suspensionTracker) total() time.Duration { - if s == nil { - return 0 - } - return time.Duration(s.totalNanos.Load()) -} - -// measuredTimeProvider decorates a TimeProvider, recording the wall-clock spent -// in each blocking time call into the supplied suspension tracker. -// -// GetDONTime can block on DON-time consensus; that wait must not be billed as -// compute. GetNodeTime is served locally and is effectively instant, so it is -// inherited unchanged from the embedded TimeProvider (no measurement needed). -type measuredTimeProvider struct { - TimeProvider - clock clockwork.Clock - tracker *suspensionTracker -} - -func newMeasuredTimeProvider(tp TimeProvider, clock clockwork.Clock, tracker *suspensionTracker) measuredTimeProvider { - return measuredTimeProvider{TimeProvider: tp, clock: clock, tracker: tracker} -} - -func (m measuredTimeProvider) GetDONTime() (time.Time, error) { - start := m.clock.Now() - t, err := m.TimeProvider.GetDONTime() - m.tracker.add(m.clock.Now().Sub(start)) - return t, err -} diff --git a/core/services/workflows/v2/compute_metering_test.go b/core/services/workflows/v2/compute_metering_test.go deleted file mode 100644 index a48ed9f6552..00000000000 --- a/core/services/workflows/v2/compute_metering_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package v2 - -import ( - "testing" - "time" - - "github.com/jonboulle/clockwork" - "github.com/stretchr/testify/require" -) - -// stubTimeProvider simulates a TimeProvider whose DON-time fetch blocks on -// consensus by advancing a fake clock for the duration of the call. -type stubTimeProvider struct { - clock *clockwork.FakeClock - donBlock time.Duration - donErr error -} - -func (s *stubTimeProvider) GetNodeTime() time.Time { return s.clock.Now() } - -func (s *stubTimeProvider) GetDONTime() (time.Time, error) { - s.clock.Advance(s.donBlock) // simulate blocking on the DON-time consensus round - return s.clock.Now(), s.donErr -} - -func TestSuspensionTracker_SumsSequentialIntervals(t *testing.T) { - t.Parallel() - - var tr suspensionTracker - tr.add(100 * time.Millisecond) - tr.add(0) // ignored - tr.add(-5 * time.Millisecond) // ignored - tr.add(250 * time.Millisecond) - require.Equal(t, 350*time.Millisecond, tr.total()) -} - -func TestSuspensionTracker_NilSafe(t *testing.T) { - t.Parallel() - - var tr *suspensionTracker - tr.add(time.Second) // must not panic - require.Equal(t, time.Duration(0), tr.total()) -} - -func TestMeasuredTimeProvider_RecordsDONTimeWait(t *testing.T) { - t.Parallel() - - clock := clockwork.NewFakeClock() - tracker := &suspensionTracker{} - stub := &stubTimeProvider{clock: clock, donBlock: 10 * time.Second} - mtp := newMeasuredTimeProvider(stub, clock, tracker) - - _, err := mtp.GetDONTime() - require.NoError(t, err) - require.Equal(t, 10*time.Second, tracker.total(), "DON-time wait should be recorded as guest suspension") - - // A second blocking call accumulates on top of the first. - stub.donBlock = 2 * time.Second - _, err = mtp.GetDONTime() - require.NoError(t, err) - require.Equal(t, 12*time.Second, tracker.total()) -} - -func TestMeasuredTimeProvider_NodeTimeNotMeasured(t *testing.T) { - t.Parallel() - - clock := clockwork.NewFakeClock() - tracker := &suspensionTracker{} - stub := &stubTimeProvider{clock: clock} - mtp := newMeasuredTimeProvider(stub, clock, tracker) - - _ = mtp.GetNodeTime() // local, instant; not a guest suspension - require.Equal(t, time.Duration(0), tracker.total()) -} diff --git a/core/services/workflows/v2/compute_usage_test.go b/core/services/workflows/v2/compute_usage_test.go new file mode 100644 index 00000000000..142890a4206 --- /dev/null +++ b/core/services/workflows/v2/compute_usage_test.go @@ -0,0 +1,299 @@ +package v2_test + +import ( + "context" + "errors" + "strconv" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" + + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + confworkflowtypes "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/actions/confidentialworkflow" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/resourcemanager" + "github.com/smartcontractkit/chainlink-common/pkg/settings/limits" + regmocks "github.com/smartcontractkit/chainlink-common/pkg/types/core/mocks" + "github.com/smartcontractkit/chainlink-common/pkg/workflows/host" + modulemocks "github.com/smartcontractkit/chainlink-common/pkg/workflows/wasm/host/mocks" + sdkpb "github.com/smartcontractkit/chainlink-protos/cre/go/sdk" + valuespb "github.com/smartcontractkit/chainlink-protos/cre/go/values/pb" + meteringpb "github.com/smartcontractkit/chainlink-protos/metering/go" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/confidentialrelay" + capmocks "github.com/smartcontractkit/chainlink/v2/core/capabilities/mocks" + v2 "github.com/smartcontractkit/chainlink/v2/core/services/workflows/v2" + "github.com/smartcontractkit/chainlink/v2/core/utils/matches" +) + +// recordingEmitter captures MeterRecords emitted by a ResourceManager. +type recordingEmitter struct { + mu sync.Mutex + records []*meteringpb.MeterRecord +} + +func (r *recordingEmitter) Emit(_ context.Context, body []byte, _ ...any) error { + var rec meteringpb.MeterRecord + if err := proto.Unmarshal(body, &rec); err != nil { + return err + } + r.mu.Lock() + defer r.mu.Unlock() + r.records = append(r.records, &rec) + return nil +} + +func (r *recordingEmitter) all() []*meteringpb.MeterRecord { + r.mu.Lock() + defer r.mu.Unlock() + return append([]*meteringpb.MeterRecord(nil), r.records...) +} + +// moduleRun is what the mocked module does for the one trigger execution. +type moduleRun struct { + result *sdkpb.ExecutionResult + err error + onExecute func() +} + +// runOneExecution starts an engine with cfg, fires one trigger event, and +// returns the execution id. +func runOneExecution(t *testing.T, cfg *v2.EngineConfig, run moduleRun) string { + module := modulemocks.NewModuleV2(t) + module.EXPECT().Start() + module.EXPECT().Close() + capreg := regmocks.NewCapabilitiesRegistry(t) + capreg.EXPECT().LocalNode(matches.AnyContext).Return(newNode(t), nil) + + initDoneCh := make(chan error) + subscribedCh := make(chan []string, 1) + finishedCh := make(chan string) + cfg.Module = module + cfg.CapRegistry = capreg + cfg.BillingClient = setupMockBillingClient(t) + cfg.OrgResolver = &mockOrgResolver{orgID: "org-123"} + cfg.Hooks = v2.LifecycleHooks{ + OnInitialized: func(err error) { initDoneCh <- err }, + OnSubscribedToTriggers: func(ids []string) { subscribedCh <- ids }, + OnExecutionFinished: func(id string, _ string) { finishedCh <- id }, + } + + engine, err := v2.NewEngine(cfg) + require.NoError(t, err) + + module.EXPECT().Execute(matches.AnyContext, mock.Anything, mock.Anything).Return(newTriggerSubs(1), nil).Once() + trigger := capmocks.NewTriggerCapability(t) + capreg.EXPECT().GetTrigger(matches.AnyContext, "id_0").Return(trigger, nil).Once() + eventCh := make(chan capabilities.TriggerResponse) + trigger.EXPECT().RegisterTrigger(matches.AnyContext, mock.Anything).Return(eventCh, nil).Once() + trigger.EXPECT().UnregisterTrigger(matches.AnyContext, mock.Anything).Return(nil).Once() + trigger.EXPECT().AckEvent(matches.AnyContext, mock.Anything, mock.Anything, mock.Anything).Return(nil) + + require.NoError(t, engine.Start(t.Context())) + require.NoError(t, <-initDoneCh) + require.Equal(t, []string{"id_0"}, <-subscribedCh) + + module.EXPECT().Execute(matches.AnyContext, mock.Anything, mock.Anything).Run(func(context.Context, *sdkpb.ExecuteRequest, host.ExecutionHelper) { + if run.onExecute != nil { + run.onExecute() + } + }).Return(run.result, run.err).Once() + eventCh <- capabilities.TriggerResponse{Event: capabilities.TriggerEvent{TriggerType: "basic-trigger@1.0.0", ID: "usage_event"}} + executionID := <-finishedCh + require.NoError(t, engine.Close()) + return executionID +} + +func TestEngine_ComputeUsageMeterRecord(t *testing.T) { + t.Parallel() + + identity := resourcemanager.WithWorkflowUsagePool(resourcemanager.ResourceIdentity{Product: "cre", Service: resourcemanager.EmittingServiceWorkflowEngine}, resourcemanager.ResourceTypeWorkflowCompute) + + t.Run("emits one compute record per execution and logs the contract line", func(t *testing.T) { + t.Parallel() + lggr, obs := logger.TestObserved(t, zapcore.InfoLevel) + cfg := defaultTestConfig(t, nil) + cfg.Lggr = lggr + emitter := &recordingEmitter{} + cfg.MeterRecordsEnabled = true + cfg.UsageEmitter = emitter + cfg.UsageIdentity = identity + + executionID := runOneExecution(t, cfg, moduleRun{}) + + records := emitter.all() + require.Len(t, records, 1) + rec := records[0] + require.Equal(t, meteringpb.MeterAction_METER_ACTION_USAGE, rec.GetAction()) + require.Equal(t, resourcemanager.EmittingServiceWorkflowEngine, rec.GetIdentity().GetService()) + require.Equal(t, "cre:workflow:compute", rec.GetIdentity().GetResourcePool()) + require.Equal(t, "cre:workflow:compute", rec.GetIdentity().GetResourcePoolId()) + require.NotEmpty(t, rec.GetIdentity().GetDon().GetDonId(), "don id must be stamped from the local node") + require.Len(t, rec.GetUtilizations(), 1) + u := rec.GetUtilizations()[0] + require.Equal(t, resourcemanager.ResourceTypeWorkflowCompute, u.GetResourceType()) + require.Equal(t, cfg.WorkflowID+":"+executionID, u.GetResourceId()) + require.Equal(t, executionID, u.GetEventId(), "compute capability event id is the execution id") + require.Equal(t, "org-123", u.GetOrgId()) + require.NotEmpty(t, u.GetValue()) + + logs := obs.FilterMessage("Emitted capability usage meter record").All() + require.Len(t, logs, 1) + fields := logs[0].ContextMap() + require.Equal(t, executionID, fields["eventID"]) + require.Equal(t, resourcemanager.ResourceTypeWorkflowCompute, fields["resourceType"]) + require.Equal(t, "org-123", fields["orgID"]) + require.Contains(t, fields, "value") + }) + + t.Run("emits for failed executions too", func(t *testing.T) { + t.Parallel() + cfg := defaultTestConfig(t, nil) + emitter := &recordingEmitter{} + cfg.MeterRecordsEnabled = true + cfg.UsageEmitter = emitter + cfg.UsageIdentity = identity + + runOneExecution(t, cfg, moduleRun{err: errors.New("module failed")}) + + require.Len(t, emitter.all(), 1) + }) + + t.Run("bills the compute duration the module reports", func(t *testing.T) { + t.Parallel() + cfg := defaultTestConfig(t, nil) + emitter := &recordingEmitter{} + cfg.MeterRecordsEnabled = true + cfg.UsageEmitter = emitter + cfg.UsageIdentity = identity + runOneExecution(t, cfg, moduleRun{result: &sdkpb.ExecutionResult{ComputeDurationMs: proto.Int64(1234)}}) + + records := emitter.all() + require.Len(t, records, 1) + require.Equal(t, "1234", records[0].GetUtilizations()[0].GetValue()) + }) + + t.Run("falls back to wall-clock when the module reports no duration", func(t *testing.T) { + t.Parallel() + cfg := defaultTestConfig(t, nil) + emitter := &recordingEmitter{} + cfg.MeterRecordsEnabled = true + cfg.UsageEmitter = emitter + cfg.UsageIdentity = identity + + runOneExecution(t, cfg, moduleRun{onExecute: func() { time.Sleep(20 * time.Millisecond) }}) + + records := emitter.all() + require.Len(t, records, 1) + ms, err := strconv.ParseInt(records[0].GetUtilizations()[0].GetValue(), 10, 64) + require.NoError(t, err) + require.GreaterOrEqual(t, ms, int64(20)) + }) + + t.Run("emits nothing when metering is disabled", func(t *testing.T) { + t.Parallel() + lggr, obs := logger.TestObserved(t, zapcore.InfoLevel) + cfg := defaultTestConfig(t, nil) + cfg.Lggr = lggr + + runOneExecution(t, cfg, moduleRun{}) + + require.Empty(t, obs.FilterMessage("Emitted capability usage meter record").All()) + }) +} + +// A TEE-routed trigger is metered from the duration the enclave set on the result. +func TestEngine_ComputeUsageMeterRecord_ConfidentialRun(t *testing.T) { + t.Parallel() + const capID = "confidential-workflows@1.0.0-alpha" + lggr := logger.Test(t) + cfg := defaultTestConfig(t, nil) + emitter := &recordingEmitter{} + cfg.MeterRecordsEnabled = true + cfg.UsageEmitter = emitter + cfg.UsageIdentity = resourcemanager.WithWorkflowUsagePool(resourcemanager.ResourceIdentity{Product: "cre", Service: resourcemanager.EmittingServiceWorkflowEngine}, resourcemanager.ResourceTypeWorkflowCompute) + + capreg := regmocks.NewCapabilitiesRegistry(t) + capreg.EXPECT().LocalNode(matches.AnyContext).Return(newNode(t), nil) + execCap := capmocks.NewExecutableCapability(t) + capreg.EXPECT().GetExecutable(matches.AnyContext, capID).Return(execCap, nil) + + tees, err := anypb.New(&confworkflowtypes.ProvidedTeesResponse{Tee: []*sdkpb.TeeTypeAndRegions{ + {Type: sdkpb.TeeType_TEE_TYPE_AWS_NITRO, Regions: []string{"us-east-1"}}, + }}) + require.NoError(t, err) + execCap.EXPECT().Execute(matches.AnyContext, mock.MatchedBy(func(req capabilities.CapabilityRequest) bool { + return req.Method == "ProvidedTees" + })).Return(capabilities.CapabilityResponse{Payload: tees}, nil).Once() + // The enclave runs the same WASM host, which sets compute_duration_ms. + enclaveResp, err := anypb.New(&confworkflowtypes.ConfidentialWorkflowResponse{SdkExecutionResult: &sdkpb.ExecutionResult{ + Result: &sdkpb.ExecutionResult_Value{Value: valuespb.NewStringValue("enclave-output")}, + ComputeDurationMs: proto.Int64(1234), + }}) + require.NoError(t, err) + execCap.EXPECT().Execute(matches.AnyContext, mock.MatchedBy(func(req capabilities.CapabilityRequest) bool { + return req.Method == "Execute" + })).Run(func(context.Context, capabilities.CapabilityRequest) { + time.Sleep(20 * time.Millisecond) // round trip the engine must not bill + }).Return(capabilities.CapabilityResponse{Payload: enclaveResp}, nil).Once() + + conf, err := v2.NewConfidentialModule(capreg, &confidentialrelay.ExecutionHandlers{}, "https://example.com/binary.wasm", []byte("hash"), + cfg.WorkflowID, cfg.WorkflowOwner, cfg.WorkflowName.String(), cfg.WorkflowTag, + func(context.Context, string) (string, error) { return "org-123", nil }, + limits.NewGateLimiter(true), cfg.LocalLimiters.Settings, lggr) + require.NoError(t, err) + + local := modulemocks.NewModuleV2(t) + local.EXPECT().Start().Maybe() + local.EXPECT().Close().Maybe() + teeSubs := newTriggerSubs(1) + teeSubs.GetTriggerSubscriptions().Subscriptions[0].Requirements = &sdkpb.Requirements{Tee: &sdkpb.Tee{ + Item: &sdkpb.Tee_AnyRegions{AnyRegions: &sdkpb.Regions{Regions: []string{"us-east-1"}}}, + }} + // Subscribe runs locally; the trigger is routed to the enclave. + local.EXPECT().Execute(matches.AnyContext, mock.Anything, mock.Anything).Return(teeSubs, nil).Once() + cfg.Module = host.NewRequirementSelectingModule(host.ModuleAndHandler{Module: local}, []host.ModuleAndHandler{{ + Module: conf, + RequirementsHandler: host.RequirementsHandler{Tee: conf.Tee}, + }}) + cfg.CapRegistry = capreg + cfg.BillingClient = setupMockBillingClient(t) + cfg.OrgResolver = &mockOrgResolver{orgID: "org-123"} + + initDoneCh := make(chan error) + subscribedCh := make(chan []string, 1) + finishedCh := make(chan string) + cfg.Hooks = v2.LifecycleHooks{ + OnInitialized: func(err error) { initDoneCh <- err }, + OnSubscribedToTriggers: func(ids []string) { subscribedCh <- ids }, + OnExecutionFinished: func(id string, _ string) { finishedCh <- id }, + } + engine, err := v2.NewEngine(cfg) + require.NoError(t, err) + + trigger := capmocks.NewTriggerCapability(t) + capreg.EXPECT().GetTrigger(matches.AnyContext, "id_0").Return(trigger, nil).Once() + eventCh := make(chan capabilities.TriggerResponse) + trigger.EXPECT().RegisterTrigger(matches.AnyContext, mock.Anything).Return(eventCh, nil).Once() + trigger.EXPECT().UnregisterTrigger(matches.AnyContext, mock.Anything).Return(nil).Once() + trigger.EXPECT().AckEvent(matches.AnyContext, mock.Anything, mock.Anything, mock.Anything).Return(nil) + + require.NoError(t, engine.Start(t.Context())) + require.NoError(t, <-initDoneCh) + require.Equal(t, []string{"id_0"}, <-subscribedCh) + + eventCh <- capabilities.TriggerResponse{Event: capabilities.TriggerEvent{TriggerType: "basic-trigger@1.0.0", ID: "confidential_event"}} + executionID := <-finishedCh + require.NoError(t, engine.Close()) + + records := emitter.all() + require.Len(t, records, 1) + u := records[0].GetUtilizations()[0] + require.Equal(t, cfg.WorkflowID+":"+executionID, u.GetResourceId()) + require.Equal(t, "1234", u.GetValue(), "enclave-measured duration, not the round trip") +} diff --git a/core/services/workflows/v2/config.go b/core/services/workflows/v2/config.go index eeb94ccdcfc..46ff8f3a64f 100644 --- a/core/services/workflows/v2/config.go +++ b/core/services/workflows/v2/config.go @@ -15,6 +15,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/contexts" "github.com/smartcontractkit/chainlink-common/pkg/custmsg" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/resourcemanager" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/services/orgresolver" "github.com/smartcontractkit/chainlink-common/pkg/settings" @@ -70,6 +71,12 @@ type EngineConfig struct { // OrgResolver is used to resolve organization IDs from workflow owners OrgResolver orgresolver.OrgResolver + // MeterRecordsEnabled emits a cre:workflow:compute usage record per execution. + MeterRecordsEnabled bool + UsageIdentity resourcemanager.ResourceIdentity + // UsageEmitter overrides the global durable emitter. + UsageEmitter resourcemanager.Emitter + // includes additional logging of events internal to user workflows DebugMode bool diff --git a/deployment/go.mod b/deployment/go.mod index f9a4249eb9c..61fd6e4403e 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -42,7 +42,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-20261001213322-a2d686f610ca - github.com/smartcontractkit/chainlink-common v0.11.2-0.20261014202458-d375ab88face + github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57 github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d github.com/smartcontractkit/chainlink-data-streams v1.1.2-0.20261009064810-68424c714170 github.com/smartcontractkit/chainlink-deployments-framework v0.123.3 diff --git a/deployment/go.sum b/deployment/go.sum index 9e32bfd7adb..83918f10f8e 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -1409,8 +1409,8 @@ github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20261001213322-a2d6 github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20261001213322-a2d686f610ca/go.mod h1:Dm/Abz0vr7F2v9TygUX947q8HIwJEJikDfSdanX1Cic= github.com/smartcontractkit/chainlink-ccv v0.14.0 h1:184WuWwWAOo8b5uTuxv8v+zI7UcjSQCKz5z2V1zHVPQ= github.com/smartcontractkit/chainlink-ccv v0.14.0/go.mod h1:k/RxmdtzRqqiNcl5X2Af7Q7R53ZAaMwgyd9ctMKtK6Q= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20261014202458-d375ab88face h1:3kepaeHtyG+dZ0xI9vu0BOuv3u8nlmKBGZf0r6KmZbE= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20261014202458-d375ab88face/go.mod h1:QeHe1mlcVzzg+awYAkRfl/8mDLRAhsl18IIOg4MVZec= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57 h1:71QCghDEu9+vSDAjhrUrIzL01MlrIOWOW8gABXuBtMg= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57/go.mod h1:QeHe1mlcVzzg+awYAkRfl/8mDLRAhsl18IIOg4MVZec= 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.20261005132539-b7d63d5fd549 h1:J4vYRJxkBO8Yf//17wScKjYWmS45VZaFWx0zt+4ETEU= diff --git a/go.mod b/go.mod index 6a758efbe97..193e95114e4 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.14.0 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20261014202458-d375ab88face + github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57 github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20261005132539-b7d63d5fd549 github.com/smartcontractkit/chainlink-data-streams v1.1.2-0.20261009064810-68424c714170 diff --git a/go.sum b/go.sum index 7b21043eb1c..407e3461cfa 100644 --- a/go.sum +++ b/go.sum @@ -1104,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.14.0 h1:184WuWwWAOo8b5uTuxv8v+zI7UcjSQCKz5z2V1zHVPQ= github.com/smartcontractkit/chainlink-ccv v0.14.0/go.mod h1:k/RxmdtzRqqiNcl5X2Af7Q7R53ZAaMwgyd9ctMKtK6Q= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20261014202458-d375ab88face h1:3kepaeHtyG+dZ0xI9vu0BOuv3u8nlmKBGZf0r6KmZbE= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20261014202458-d375ab88face/go.mod h1:QeHe1mlcVzzg+awYAkRfl/8mDLRAhsl18IIOg4MVZec= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57 h1:71QCghDEu9+vSDAjhrUrIzL01MlrIOWOW8gABXuBtMg= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57/go.mod h1:QeHe1mlcVzzg+awYAkRfl/8mDLRAhsl18IIOg4MVZec= 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.20261005132539-b7d63d5fd549 h1:J4vYRJxkBO8Yf//17wScKjYWmS45VZaFWx0zt+4ETEU= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index d41ea0fd944..2f85ccde377 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -30,7 +30,7 @@ require ( github.com/smartcontractkit/chainlink-ccip/chains/evm v0.0.0-20261001213322-a2d686f610ca 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.20261014202458-d375ab88face + github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57 github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d github.com/smartcontractkit/chainlink-deployments-framework v0.123.3 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20261005112317-b723176adfe8 diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 7761db22357..09afe0066d6 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1394,8 +1394,8 @@ github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20261001213322-a2d6 github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20261001213322-a2d686f610ca/go.mod h1:Dm/Abz0vr7F2v9TygUX947q8HIwJEJikDfSdanX1Cic= github.com/smartcontractkit/chainlink-ccv v0.14.0 h1:184WuWwWAOo8b5uTuxv8v+zI7UcjSQCKz5z2V1zHVPQ= github.com/smartcontractkit/chainlink-ccv v0.14.0/go.mod h1:k/RxmdtzRqqiNcl5X2Af7Q7R53ZAaMwgyd9ctMKtK6Q= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20261014202458-d375ab88face h1:3kepaeHtyG+dZ0xI9vu0BOuv3u8nlmKBGZf0r6KmZbE= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20261014202458-d375ab88face/go.mod h1:QeHe1mlcVzzg+awYAkRfl/8mDLRAhsl18IIOg4MVZec= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57 h1:71QCghDEu9+vSDAjhrUrIzL01MlrIOWOW8gABXuBtMg= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57/go.mod h1:QeHe1mlcVzzg+awYAkRfl/8mDLRAhsl18IIOg4MVZec= 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.20261005132539-b7d63d5fd549 h1:J4vYRJxkBO8Yf//17wScKjYWmS45VZaFWx0zt+4ETEU= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 0f219c60465..cf50e31d013 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -20,7 +20,7 @@ require ( github.com/smartcontractkit/chainlink-ccip/chains/evm v0.0.0-20261001213322-a2d686f610ca 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.20261014202458-d375ab88face + github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57 github.com/smartcontractkit/chainlink-deployments-framework v0.123.3 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20261005112317-b723176adfe8 github.com/smartcontractkit/chainlink-testing-framework/framework v0.16.8 diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 59d3ef9b2d2..4a8bdbb82c5 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1634,8 +1634,8 @@ github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20261001213322-a2d6 github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20261001213322-a2d686f610ca/go.mod h1:Dm/Abz0vr7F2v9TygUX947q8HIwJEJikDfSdanX1Cic= github.com/smartcontractkit/chainlink-ccv v0.14.0 h1:184WuWwWAOo8b5uTuxv8v+zI7UcjSQCKz5z2V1zHVPQ= github.com/smartcontractkit/chainlink-ccv v0.14.0/go.mod h1:k/RxmdtzRqqiNcl5X2Af7Q7R53ZAaMwgyd9ctMKtK6Q= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20261014202458-d375ab88face h1:3kepaeHtyG+dZ0xI9vu0BOuv3u8nlmKBGZf0r6KmZbE= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20261014202458-d375ab88face/go.mod h1:QeHe1mlcVzzg+awYAkRfl/8mDLRAhsl18IIOg4MVZec= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57 h1:71QCghDEu9+vSDAjhrUrIzL01MlrIOWOW8gABXuBtMg= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57/go.mod h1:QeHe1mlcVzzg+awYAkRfl/8mDLRAhsl18IIOg4MVZec= 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.20261005132539-b7d63d5fd549 h1:J4vYRJxkBO8Yf//17wScKjYWmS45VZaFWx0zt+4ETEU= diff --git a/plugins/plugins.public.yaml b/plugins/plugins.public.yaml index 66446d206f9..8983d002f7b 100644 --- a/plugins/plugins.public.yaml +++ b/plugins/plugins.public.yaml @@ -84,12 +84,12 @@ plugins: capability-evm: - moduleURI: "github.com/smartcontractkit/capabilities/chain_capabilities/evm" - gitRef: "e6f512fbf3bdc3adaa96bfdd1d497c4507eab05d" + gitRef: "2951f2cf06c3c0d52fe8b738764218cdb571635c" installPath: "." capability-solana: - moduleURI: "github.com/smartcontractkit/capabilities/chain_capabilities/solana" - gitRef: "7ae963289733be8764d0b49e8e4c6233bd34680b" + gitRef: "2951f2cf06c3c0d52fe8b738764218cdb571635c" installPath: "." capability-aptos: diff --git a/system-tests/lib/go.mod b/system-tests/lib/go.mod index 5b0398921f7..89578998c3c 100644 --- a/system-tests/lib/go.mod +++ b/system-tests/lib/go.mod @@ -33,7 +33,7 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.112 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.20261014202458-d375ab88face + github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57 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.123.3 diff --git a/system-tests/lib/go.sum b/system-tests/lib/go.sum index 69264131433..d1d75692ab5 100644 --- a/system-tests/lib/go.sum +++ b/system-tests/lib/go.sum @@ -1659,8 +1659,8 @@ github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20261001213322-a2d6 github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20261001213322-a2d686f610ca/go.mod h1:Dm/Abz0vr7F2v9TygUX947q8HIwJEJikDfSdanX1Cic= github.com/smartcontractkit/chainlink-ccv v0.14.0 h1:184WuWwWAOo8b5uTuxv8v+zI7UcjSQCKz5z2V1zHVPQ= github.com/smartcontractkit/chainlink-ccv v0.14.0/go.mod h1:k/RxmdtzRqqiNcl5X2Af7Q7R53ZAaMwgyd9ctMKtK6Q= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20261014202458-d375ab88face h1:3kepaeHtyG+dZ0xI9vu0BOuv3u8nlmKBGZf0r6KmZbE= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20261014202458-d375ab88face/go.mod h1:QeHe1mlcVzzg+awYAkRfl/8mDLRAhsl18IIOg4MVZec= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57 h1:71QCghDEu9+vSDAjhrUrIzL01MlrIOWOW8gABXuBtMg= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57/go.mod h1:QeHe1mlcVzzg+awYAkRfl/8mDLRAhsl18IIOg4MVZec= 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.20261005132539-b7d63d5fd549 h1:J4vYRJxkBO8Yf//17wScKjYWmS45VZaFWx0zt+4ETEU= diff --git a/system-tests/tests/canaries_sentinels/proof-of-reserve/cron-based/go.mod b/system-tests/tests/canaries_sentinels/proof-of-reserve/cron-based/go.mod index 7b4f60ceeb6..5900cbd99ce 100644 --- a/system-tests/tests/canaries_sentinels/proof-of-reserve/cron-based/go.mod +++ b/system-tests/tests/canaries_sentinels/proof-of-reserve/cron-based/go.mod @@ -1,12 +1,12 @@ module main -go 1.26.6 +go 1.27.1 require ( github.com/ethereum/go-ethereum v1.17.4 github.com/smartcontractkit/chain-selectors v1.0.107 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260915135448-9ec0150ecb5d - github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260804191526-b7a850ae7648 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57 + github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20261014151355-5868490cb3e3 github.com/smartcontractkit/cre-sdk-go v1.5.0 github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v0.10.0 github.com/smartcontractkit/cre-sdk-go/capabilities/networking/http v1.3.0 @@ -53,7 +53,7 @@ require ( github.com/prometheus/procfs v0.20.1 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/shopspring/decimal v1.4.0 // indirect - github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260724142814-45996a1bcb72 // indirect + github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20261005132539-b7d63d5fd549 // indirect github.com/smartcontractkit/libocr v0.0.0-20260810200708-618b5bf7f342 // indirect github.com/stretchr/testify v1.12.1 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect diff --git a/system-tests/tests/canaries_sentinels/proof-of-reserve/cron-based/go.sum b/system-tests/tests/canaries_sentinels/proof-of-reserve/cron-based/go.sum index 57713d77a8b..88eab15e419 100644 --- a/system-tests/tests/canaries_sentinels/proof-of-reserve/cron-based/go.sum +++ b/system-tests/tests/canaries_sentinels/proof-of-reserve/cron-based/go.sum @@ -100,12 +100,12 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/smartcontractkit/chain-selectors v1.0.107 h1:LlQI72DqaFrJXWRmcv78H48MjUNhFzKm+xBOt+5ZXbg= github.com/smartcontractkit/chain-selectors v1.0.107/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260915135448-9ec0150ecb5d h1:4heqJxOTXBHR3425SuXe9EzkZq+CqWOdKe4KWzAoGYA= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260915135448-9ec0150ecb5d/go.mod h1:ks0kniCCg8Siv2XflmwLOq5ib9Ifoh6VHXHwfEWIMQA= -github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260724142814-45996a1bcb72 h1:uWEwl7i2ryuRVoV4DmIKm6mqYevf1lH/8cQYhw/JXko= -github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260724142814-45996a1bcb72/go.mod h1:UYcRMb4dZcoaIPgZJ3hckCySTqtJc9K4Q+tOKErwTq0= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260804191526-b7a850ae7648 h1:WEUMkKQPAgcNMRgES6CBWrRUiII+HKEWQjulKQBSuMA= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260804191526-b7a850ae7648/go.mod h1:/i8hjTPFdVWHiY+QjeSiVS2Z3GB3WAZznGgXHstC02E= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57 h1:71QCghDEu9+vSDAjhrUrIzL01MlrIOWOW8gABXuBtMg= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57/go.mod h1:QeHe1mlcVzzg+awYAkRfl/8mDLRAhsl18IIOg4MVZec= +github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20261005132539-b7d63d5fd549 h1:J4vYRJxkBO8Yf//17wScKjYWmS45VZaFWx0zt+4ETEU= +github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20261005132539-b7d63d5fd549/go.mod h1:Scyk7O80fde5rJF3Ty3fEE3xiS6j84eWbu8/2gl3pIY= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20261014151355-5868490cb3e3 h1:rUUx0roqc0jAWDpdqL+oMvZiUORyYrGEGji0PJaXwjI= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20261014151355-5868490cb3e3/go.mod h1:/i8hjTPFdVWHiY+QjeSiVS2Z3GB3WAZznGgXHstC02E= github.com/smartcontractkit/cre-sdk-go v1.5.0 h1:kepW3QDKARrOOHjXwWAZ9j5KLk6bxLzvi6OMrLsFwVo= github.com/smartcontractkit/cre-sdk-go v1.5.0/go.mod h1:yYrQFz1UH7hhRbPO0q4fgo1tfsJNd4yXnI3oCZE0RzM= github.com/smartcontractkit/cre-sdk-go/capabilities/blockchain/evm v0.10.0 h1:G0w0cLzHy/5m74IzSGz1Ynjffym4ZxLeUrRLp8EFP5w= diff --git a/system-tests/tests/go.mod b/system-tests/tests/go.mod index 6ae0f5a3ecf..114c3be5aec 100644 --- a/system-tests/tests/go.mod +++ b/system-tests/tests/go.mod @@ -74,7 +74,7 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.112 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.20261014202458-d375ab88face + github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57 github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260903141829-ef07b52a737d github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20261005132539-b7d63d5fd549 github.com/smartcontractkit/chainlink-confidential-compute v1.3.0 diff --git a/system-tests/tests/go.sum b/system-tests/tests/go.sum index 9aaf57b7ca7..f514c647aa3 100644 --- a/system-tests/tests/go.sum +++ b/system-tests/tests/go.sum @@ -1838,8 +1838,8 @@ github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20261001213322-a2d6 github.com/smartcontractkit/chainlink-ccip/deployment v0.0.0-20261001213322-a2d686f610ca/go.mod h1:Dm/Abz0vr7F2v9TygUX947q8HIwJEJikDfSdanX1Cic= github.com/smartcontractkit/chainlink-ccv v0.14.0 h1:184WuWwWAOo8b5uTuxv8v+zI7UcjSQCKz5z2V1zHVPQ= github.com/smartcontractkit/chainlink-ccv v0.14.0/go.mod h1:k/RxmdtzRqqiNcl5X2Af7Q7R53ZAaMwgyd9ctMKtK6Q= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20261014202458-d375ab88face h1:3kepaeHtyG+dZ0xI9vu0BOuv3u8nlmKBGZf0r6KmZbE= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20261014202458-d375ab88face/go.mod h1:QeHe1mlcVzzg+awYAkRfl/8mDLRAhsl18IIOg4MVZec= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57 h1:71QCghDEu9+vSDAjhrUrIzL01MlrIOWOW8gABXuBtMg= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20261015152047-f1f2b6d3bd57/go.mod h1:QeHe1mlcVzzg+awYAkRfl/8mDLRAhsl18IIOg4MVZec= 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.20261005132539-b7d63d5fd549 h1:J4vYRJxkBO8Yf//17wScKjYWmS45VZaFWx0zt+4ETEU=