Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ func executeCapabilityRequest(ctx context.Context, lggr logger.Logger, capabilit
// calling DON so it cannot be spoofed. All F+1 aggregated requests share this
// payload (WorkflowDonID is part of the request hash), so a single check here
// covers the quorum. The gate is guaranteed non-nil by NewServerRequest.
enabled, gerr := workflowDONBindingGate.Limit(ctx)
Comment thread
tarcisiozf marked this conversation as resolved.
enabled, gerr := workflowDONBindingGate.IsOpen(ctx)
if gerr != nil {
lggr.Errorw("failed to evaluate workflow DON binding gate", "err", gerr)
return nil, errors.New("failed to evaluate workflow DON binding gate")
Expand Down
2 changes: 1 addition & 1 deletion core/capabilities/vault/zone_b_restriction.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func newZoneBRestrictor(lggr logger.Logger, limitsFactory limits.Factory, capabi
// resolves to a zone-b DON. The owner is read from ctx, which must already carry
// the (normalized) CRE owner via RequestMetadata.ContextWithCRE.
func (z *zoneBRestrictor) enforce(ctx context.Context, workflowDonID uint32) error {
enabled, err := z.restrictEnabled.Limit(ctx)
enabled, err := z.restrictEnabled.IsOpen(ctx)
if err != nil {
return fmt.Errorf("could not evaluate zone-b vault read restriction gate: %w", err)
}
Expand Down
1 change: 1 addition & 0 deletions core/platform/monitoring.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const (
KeyCapabilityID = "capabilityID"
KeyTriggerID = "triggerID"
KeyTriggerDropReason = "dropReason"
KeyLimitKey = "limitKey"
KeyWorkflowID = "workflowID"
KeyWorkflowExecutionID = "workflowExecutionID"
KeyWorkflowName = "workflowName"
Expand Down
2 changes: 1 addition & 1 deletion core/scripts/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ require (
github.com/shopspring/decimal v1.4.0
github.com/smartcontractkit/chain-selectors v1.0.109
github.com/smartcontractkit/chainlink-ccip/chains/evm v0.0.0-20260908164107-3de1349d970b
github.com/smartcontractkit/chainlink-common v0.11.2-0.20260914191328-10cc2b41997e
github.com/smartcontractkit/chainlink-common v0.11.2-0.20260915135448-9ec0150ecb5d
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.20260914143713-424babc506ab
Expand Down
4 changes: 2 additions & 2 deletions core/scripts/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions core/services/ocr2/plugins/vault/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ func (r *ReportingPlugin) prepareObservationPendingQueueBlobs(
}

func (r *ReportingPlugin) shouldPurgePendingQueue(ctx context.Context) bool {
if gateAllows(ctx, r.lggr, r.cfg.VaultForceEmptyOCRRounds, "VaultForceEmptyOCRRounds") {
if r.forceEmptyOCRRounds(ctx) {
return true
}
stallThreshold, err := r.cfg.VaultPendingQueueStallThreshold.Limit(ctx)
Expand Down Expand Up @@ -1289,7 +1289,7 @@ func (r *ReportingPlugin) ValidateObservation(ctx context.Context, seqNr uint64,

readKV := NewReadStore(keyValueReader, r.metrics)
var pendingQueueItems []*vaultcommon.StoredPendingQueueItem
if !gateAllows(ctx, r.lggr, r.cfg.VaultForceEmptyOCRRounds, "VaultForceEmptyOCRRounds") {
if !r.forceEmptyOCRRounds(ctx) {
var err error
pendingQueueItems, err = readKV.GetPendingQueue(ctx)
if err != nil {
Expand Down Expand Up @@ -1325,7 +1325,7 @@ func (r *ReportingPlugin) ValidateObservation(ctx context.Context, seqNr uint64,
// This is because honest nodes may omit tail items when the full Observations proto would exceed the
// max observation byte limit.
// - that all pending queue items can be fetched as blobs.
if !gateAllows(ctx, r.lggr, r.cfg.VaultForceEmptyOCRRounds, "VaultForceEmptyOCRRounds") {
if !r.forceEmptyOCRRounds(ctx) {
if err := r.validatePendingQueueObservationsPrefix(pendingQueueItems, obs); err != nil {
return err
}
Expand Down Expand Up @@ -1434,7 +1434,7 @@ func (r *ReportingPlugin) ObservationQuorum(ctx context.Context, seqNr uint64, a
return true, nil
}

if gateAllows(ctx, r.lggr, r.cfg.VaultForceEmptyOCRRounds, "VaultForceEmptyOCRRounds") {
if r.forceEmptyOCRRounds(ctx) {
return true, nil
}

Expand Down
25 changes: 11 additions & 14 deletions core/services/ocr2/plugins/vault/plugin_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,6 @@ import (
"github.com/smartcontractkit/chainlink/v2/core/logger"
)

// gateAllows reports whether the given CRE gate allows the gated behavior.
// When evaluation errors for reasons other than ErrorNotAllowed, it logs an error and returns false.
func gateAllows(ctx context.Context, lggr logger.Logger, gate limits.GateLimiter, gateName string) bool {
err := gate.AllowErr(ctx)
if err == nil {
return true
}
if errors.Is(err, limits.ErrorNotAllowed{}) {
return false
}
lggr.Errorw("unexpected error evaluating CRE gate", "gate", gateName, "error", err)
return false
}

// resolveVaultOCRBoundLimitInt builds a short-lived BoundLimiter for an integer-sized CRE setting, reads Limit once, and closes the limiter.
func resolveVaultOCRBoundLimitInt[I constraints.Integer](
ctx context.Context,
Expand Down Expand Up @@ -183,6 +169,17 @@ func initializePluginLimits(ctx context.Context, limitsFactory limits.Factory) (
}, nil
}

// forceEmptyOCRRounds reports whether the VaultForceEmptyOCRRounds gate is open,
// treating an unevaluatable gate as closed.
func (r *ReportingPlugin) forceEmptyOCRRounds(ctx context.Context) bool {
open, err := r.cfg.VaultForceEmptyOCRRounds.IsOpen(ctx)
if err != nil {
r.lggr.Errorw("unexpected error evaluating CRE gate", "gate", "VaultForceEmptyOCRRounds", "error", err)
return false
}
return open
}

func (r *ReportingPlugin) roundLggr(seqNr uint64) logger.Logger {
return r.lggr.With("seqNr", seqNr)
}
Expand Down
40 changes: 40 additions & 0 deletions core/services/workflows/monitoring/monitoring.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ type EngineMetrics struct {
donTimeErrorsCounter metric.Int64Counter

orgIDMissingCounter metric.Int64Counter

limitReadFallbackTotal metric.Int64Counter
limitCheckUnenforcedTotal metric.Int64Counter
}

func InitMonitoringResources() (em *EngineMetrics, err error) {
Expand Down Expand Up @@ -449,6 +452,22 @@ func InitMonitoringResources() (em *EngineMetrics, err error) {
return nil, fmt.Errorf("failed to register org id missing counter: %w", err)
}

em.limitReadFallbackTotal, err = beholder.GetMeter().Int64Counter(
"platform_engine_limit_read_fallback_total",
metric.WithDescription("Limit reads that failed and fell back to the static default, by limitKey"),
)
Comment thread
Copilot marked this conversation as resolved.
if err != nil {
return nil, fmt.Errorf("failed to register limit read fallback counter: %w", err)
}

em.limitCheckUnenforcedTotal, err = beholder.GetMeter().Int64Counter(
"platform_engine_limit_check_unenforced_total",
metric.WithDescription("Limit checks that could not be evaluated, so the limit went unenforced (failed open), by limitKey"),
)
if err != nil {
return nil, fmt.Errorf("failed to register limit check unenforced counter: %w", err)
}

return em, nil
}

Expand Down Expand Up @@ -823,3 +842,24 @@ func (c WorkflowsMetricLabeler) IncrementOrgIDMissingCounter(ctx context.Context
otelLabels = append(otelLabels, attribute.String("reason", reason))
c.em.orgIDMissingCounter.Add(ctx, 1, metric.WithAttributes(otelLabels...))
}

// IncrementLimitReadFallbackCounter records one limit read that failed and fell back
// to a default instead of dropping the execution/event. limitKey should be the
// canonical settings.Setting.Key for the limit that failed.
func (c WorkflowsMetricLabeler) IncrementLimitReadFallbackCounter(ctx context.Context, limitKey string) {
lc := c.With(platform.KeyLimitKey, limitKey)
otelLabels := beholder.OtelAttributes(lc.Labels).AsStringAttributes()
lc.em.limitReadFallbackTotal.Add(ctx, 1, metric.WithAttributes(otelLabels...))
}

// IncrementLimitCheckUnenforcedCounter records one limit Check that could not be
// evaluated (a settings read failure rather than the bound being exceeded), so the
// limit was skipped and the operation allowed through. Distinct from
// IncrementLimitReadFallbackCounter: there is no default to substitute here, the limit
// simply went unenforced, so a non-zero rate means a limit is not being applied.
// limitKey should be the canonical settings.Setting.Key for the limit that failed.
func (c WorkflowsMetricLabeler) IncrementLimitCheckUnenforcedCounter(ctx context.Context, limitKey string) {
lc := c.With(platform.KeyLimitKey, limitKey)
otelLabels := beholder.OtelAttributes(lc.Labels).AsStringAttributes()
lc.em.limitCheckUnenforcedTotal.Add(ctx, 1, metric.WithAttributes(otelLabels...))
}
9 changes: 5 additions & 4 deletions core/services/workflows/v2/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"time"

"github.com/jonboulle/clockwork"

Expand Down Expand Up @@ -92,7 +93,7 @@ type EngineLimiters struct {
TriggerRegistrationsTime limits.TimeLimiter
TriggerSubscription limits.BoundLimiter[int]
TriggerEventQueue limits.QueueLimiter[RoutedTriggerEvent]
TriggerEventQueueTime limits.TimeLimiter
TriggerEventQueueTimeout limits.BoundLimiter[time.Duration]
ExecutionConcurrency limits.ResourcePoolLimiter[int]

WASMBinarySize limits.BoundLimiter[config.Size]
Expand Down Expand Up @@ -161,7 +162,7 @@ func (l *EngineLimiters) init(lf limits.Factory, cfgFn func(*cresettings.Workflo
if err != nil {
return err
}
l.TriggerEventQueueTime, err = lf.MakeTimeLimiter(cfg.TriggerEventQueueTimeout)
l.TriggerEventQueueTimeout, err = limits.MakeUpperBoundLimiter(lf, cfg.TriggerEventQueueTimeout)
if err != nil {
return err
}
Expand Down Expand Up @@ -305,7 +306,7 @@ func (l *EngineLimiters) EvictWorkflow(workflowID string) error {
l.TriggerRegistrationsTime,
l.TriggerSubscription,
l.TriggerEventQueue,
l.TriggerEventQueueTime,
l.TriggerEventQueueTimeout,
l.ExecutionConcurrency,
l.WASMBinarySize,
l.WASMMemorySize,
Expand Down Expand Up @@ -349,7 +350,7 @@ func (l *EngineLimiters) Close() error {
l.TriggerRegistrationsTime,
l.TriggerSubscription,
l.TriggerEventQueue,
l.TriggerEventQueueTime,
l.TriggerEventQueueTimeout,
l.ExecutionConcurrency,
l.WASMBinarySize,
l.WASMMemorySize,
Expand Down
Loading
Loading