Skip to content

Fault injection for billing reconciler recall - #2479

Open
DylanTinianov wants to merge 8 commits into
mainfrom
CRE-6809-fault-injection
Open

DylanTinianov wants to merge 8 commits into
mainfrom
CRE-6809-fault-injection

Conversation

@DylanTinianov

@DylanTinianov DylanTinianov commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Adds resourcemanager.UsageFaultInjection, a deterministic sampler applied inside the resource manager's usage emit path so the billing reconcilers can measure their own recall against known faults. The zero value is off; this is for test environments only.

Behaviour

  • Drop and perturb run together on two independent samples (DropRate, PerturbRate), keyed apart on the capability event id (drop|<id>, perturb|<id>); an event in both is dropped. FaultSelected(rate, key) hashes the key with FNV-1a 64, applies the murmur3 fmix64 finalizer so near-identical ids spread out, and takes the top 53 bits as a fraction below rate. Every node of a DON therefore selects the same events, and a reconciler that knows rate can recompute the sample without any shared table. Only records of the orgs in Orgs (our canaries) are ever faulted, an unresolved org is never faulted, and a rate without an allowlist fails validation, so the injector can run in production without touching a customer.
  • drop: the selected usage record is not emitted on any node. The producer's own "Emitted capability usage meter record" line still fires, which is what lets the emit reconciler score the record as emitted-but-unbilled.
  • perturb: Delta is added to the value on the nodes selected by FaultSelected(nodeRate, nodeID+"|"+eventID); NodeRate 1 (or 0) means every node. Delta must be negative and the result is clamped at zero, so a leak past the quorum median can only under-bill. A node rate below 1 produces node disagreement for the data reconciler; a node rate of 1 produces a wrong settled value that only the on-chain check can see.
  • Every applied fault logs Injected usage fault with mode, resourceType, eventID, resourceId, workflowID, executionID, orgID, nodeID, value and (perturb) originalValue. Durable delta and snapshot records are never faulted.

Plumbing

ResourceManagerConfig.FaultInjection carries the settings. loop.EnvConfig gains MeterFaultOrgs, MeterFaultDropRate, MeterFaultPerturbRate, MeterFaultDelta, MeterFaultNodeRate (env CL_METER_FAULT_*), round-tripped by AsCmdEnv/parse and mapped by EnvConfig.MeteringConfig, so LOOP capability producers that build their resource manager from the metering config get the fault with no code change. UsageFaultInjection.Validate is exposed for the host config layer.

Tested locally

Beyond the unit tests: a Local CRE stack whose node image carried this change and the host TOML plumbing, with perturb on half the workflow DON. Compute records from the engine and gas records from the EVM write capability (configured through loop.EnvConfig.MeteringConfig) carried the delta on exactly the nodes the sampler selected, each applied fault logged Injected usage fault, and a reconciler recomputing the sample from the same rates identified the same records.

…ecall (CRE-6809)

Adds UsageFaultInjection (drop or perturb a deterministic sample of usage
records keyed on event id) to ResourceManagerConfig, applied in the usage emit
path and logged as "Injected usage fault". LOOP EnvConfig carries the settings
as CL_METER_FAULT_* and MeteringConfig maps them, so capability producers get
the fault with no code change. Test environments only; zero value is off.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ API Diff Results - github.com/smartcontractkit/chainlink-common

⚠️ Breaking Changes (2)

pkg/resourcemanager (2)
  • Config — Old is comparable, new is not

  • ResourceManagerConfig — Old is comparable, new is not

✅ Compatible Changes (11)

pkg/loop.EnvConfig (5)
  • MeterFaultDelta — ➕ Added

  • MeterFaultDropRate — ➕ Added

  • MeterFaultNodeRate — ➕ Added

  • MeterFaultOrgs — ➕ Added

  • MeterFaultPerturbRate — ➕ Added

pkg/resourcemanager (4)
  • FaultModeDrop — ➕ Added

  • FaultModePerturb — ➕ Added

  • FaultSelected — ➕ Added

  • UsageFaultInjection — ➕ Added

pkg/resourcemanager.Config (1)
  • FaultInjection — ➕ Added
pkg/resourcemanager.ResourceManagerConfig (1)
  • FaultInjection — ➕ Added

📄 View full apidiff report

@DylanTinianov
DylanTinianov requested a balanced review from Copilot October 27, 2026 14:26
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The reconcilers recompute the sample from the rate alone, so the salt
only added a value that had to match on every node of a DON. The
sampler now hashes the event id directly, and the node sample hashes
node id and event id.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The LOOP configuration initializer does not compile, and rate validation and parsing can silently produce unintended fault selection.

2 open issues
What changed in this PR

Adds deterministic, test-only usage fault injection so billing reconcilers can measure recall against reproducible dropped or altered records.

Changes:

  • Adds event and node sampling, drop/perturb modes, validation, and fault logging.
  • Connects fault settings to the usage emit path and LOOP environment configuration.
  • Adds tests for sampling, validation, perturbations, drops, and durable-delta exclusion.
File Description
pkg/​resourcemanager/​resourcemanager.go Stores fault settings and applies them to usage emission.
pkg/​resourcemanager/​fault_injection.go Implements deterministic selection, validation, and fault application.
pkg/​resourcemanager/​fault_injection_test.go Tests sampling and fault behavior.
pkg/​loop/​metering_config.go Maps LOOP fault settings into resource manager configuration.
pkg/​loop/​config.go Adds fault environment variables, serialization, and parsing.

Review effort: Balanced

Comment thread pkg/loop/metering_config.go
Comment thread pkg/resourcemanager/fault_injection.go Outdated
DylanTinianov and others added 4 commits October 27, 2026 10:45
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The injector is meant to run in production against our own canary
workflows, so it must never touch a real customer's records or raise
a value. Mode now requires an org allowlist, only records of those
orgs are dropped or perturbed, Delta must be negative, and a perturbed
value is clamped at zero. CL_METER_FAULT_ORGS carries the list to LOOP
plugins.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Both billing reconcilers need faults at the same time, so a single
Mode/Rate pair becomes DropRate and PerturbRate: two deterministic
samples keyed apart on the event id, drop winning when both select.
CL_METER_FAULT_MODE/RATE become CL_METER_FAULT_DROP_RATE/PERTURB_RATE.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
NaN compares false against every bound, so it passed Validate and
silently disabled the sample. All three rates now test for it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@DylanTinianov DylanTinianov changed the title resourcemanager: deterministic usage fault injection for billing reconciler recall Fault injection for billing reconciler recall Oct 27, 2026
@DylanTinianov
DylanTinianov marked this pull request as ready for review October 27, 2026 16:58
@DylanTinianov
DylanTinianov requested a review from a team as a code owner October 27, 2026 16:58

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants