fix(#202): add CircuitBreaker utility + StellarRpcCircuit wrapper#288
Open
Yzgaming005 wants to merge 1 commit into
Open
fix(#202): add CircuitBreaker utility + StellarRpcCircuit wrapper#288Yzgaming005 wants to merge 1 commit into
Yzgaming005 wants to merge 1 commit into
Conversation
…t wrapper Adds a generic, framework-agnostic circuit breaker that can be wrapped around the existing withStellarRpcRetry helper (PR BountyOnChain#218) to fail fast when the Stellar RPC is degraded, preventing cascading overload. ## New files - apps/backend/src/common/circuit-breaker.ts CircuitBreaker class with CLOSED / OPEN / HALF_OPEN state machine. Configurable failureThreshold (default 5), failureWindowMs (default 60s), cooldownMs (default 30s). Snapshot + subscribe API for metrics/logging. Custom `now` clock injection for deterministic tests. - apps/backend/src/common/circuit-breaker.module.ts CircuitBreakerManager — single source of truth for breaker instances (named lookups, "stellar-rpc" defaults wired to issue BountyOnChain#202 spec). Pass-through mode when CIRCUIT_BREAKER_DISABLED=1. - apps/backend/src/common/stellar-rpc-circuit.ts withStellarRpcCircuit() — convenience wrapper that gates a withStellarRpcRetry call behind a CircuitBreaker.execute(). On OPEN: short-circuits with CircuitBreakerOpenError before the retry loop runs. circuitStateToNumber() helper for Prometheus gauges. - apps/backend/src/common/circuit-breaker.spec.ts (8 cases) All transitions + sliding-window aging + subscriber notifications + the "fails fast without calling fn" acceptance criterion. ## Verification - ✅ npx tsc --noEmit -p tsconfig.json clean - ✅ npx jest src/common/circuit-breaker.spec.ts → 8/8 pass - ✅ Full backend suite: 112 + 8 new = 120/120 pass (the 124/124 from BountyOnChain#170 was inflated by sanitize-html; same applies here) - ✅ Matches issue BountyOnChain#202 spec: opens after 5 consecutive failures within 60s, cooldown 30s before HALF_OPEN probe, fail-fast when OPEN (fn not called). ## Out of scope for this PR - SubmissionsService / StellarRpcClient integration: needs PR #169s StellarRpcClient (closed as duplicate) or refactor of the PR BountyOnChain#218 retry helper. Left for a follow-up PR. - Prometheus gauge for circuit state — circuitStateToNumber() helper is ready to be wired into MetricsService.appendStellarRpcMetrics(). - Module registration (NestJS DynamicModule). Use CircuitBreakerManager directly until module wiring is needed.
Contributor
Author
|
Hey 👋 Nudge for #202 — CircuitBreaker is implemented and ready. What's in #288:
Behavior:
Wiring:
Ready for review. Happy to address feedback / split commits. Closes #202 |
This was referenced Jun 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #202
Summary
Generic circuit breaker pattern for the Stellar RPC. Wraps the existing
withStellarRpcRetryhelper (PR #218) so requests fail fast when the RPCis degraded, instead of hammering it with retries that cause cascading
overload.
New files
apps/backend/src/common/circuit-breaker.tsFramework-agnostic
CircuitBreakerclass with the full 3-state machine:CircuitBreakerOpenError.No RPC invocation happens.
cooldownMs, exactly one probe is allowed. Successcloses the circuit; failure re-opens it.
Defaults match issue #202 spec:
failureThresholdfailureWindowMscooldownMsPublic API:
execute(fn)— primary entry point; auto-transitions statesrecordSuccess()/recordFailure(error)— manual controlsnapshot()/subscribe(listener)— for metrics + loggingnowclock injection → deterministic testsapps/backend/src/common/circuit-breaker.module.tsCircuitBreakerManager— named lookup of breaker instances, with built-instellar-rpcdefaults (matches #202 spec) and a pass-through mode whenCIRCUIT_BREAKER_DISABLED=1. ExposesforceOpen/forceClosefor ops.apps/backend/src/common/stellar-rpc-circuit.tswithStellarRpcCircuit(breaker, op, fn, runner)— gates awithStellarRpcRetrycall behind a breaker. Also exportscircuitStateToNumber(state)for the Prometheus gauge.apps/backend/src/common/circuit-breaker.spec.ts(8 cases)Verification
npx tsc --noEmit -p tsconfig.jsoncleannpx jest src/common/circuit-breaker.spec.ts→ 8/8 passOut of scope (deliberately)
This PR is the library half of the fix. The integration half — wiring
the breaker into
SubmissionsService/ refactoring the retry helper from#218 — is left for a follow-up PR because:
with the PR [codex] Add Stellar RPC retry handling #218 retry pattern before wiring.
metrics, otherwise we'd add the gauge without a real consumer.
DynamicModulewiring can land together with the integration.The acceptance criteria are met by the library itself, so this PR closes
the design decision and unblocks the integration work.