Repository navigation
Oracle provider sandbox, graceful pipeline shutdown, endpoint failover, and network config validation - #468
Merged
OxDev-max merged 5 commits intoOct 23, 2026
Conversation
Adds a sandbox deployment mode (ORACLE_SANDBOX_MODE=true). In it, data providers register a Stellar key, submit signed readings, and inspect the corroboration decisions and rejections those readings produce. Nothing is published on-chain and production feeds are untouched. - Domain-separated ed25519 signing (sandbox signatures never verify in production), decimal-string values, per-provider nonce replay protection - Pure corroboration engine: latest reading per provider in the window, median, outliers past a ratio/absolute tolerance, quorum check - In-memory service with bounded storage and a submission trace response - /sandbox/oracle API, signing script, and docs - Boot guard: refuses to start with a relayer key or on mainnet Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A container restart could interrupt a publish halfway, or close Postgres before an evidence write committed. That left duplicate or missing evidence. - OraclePipelineLifecycle: stops new polls on onModuleDestroy, drains in-flight polls/publishes/evidence writes in beforeApplicationShutdown within ORACLE_SHUTDOWN_DRAIN_MS (capped below SHUTDOWN_GRACE_PERIOD_MS), then releases the leader lock and logs anything abandoned - Leader lock: LocalLeaderLock by default, RedisLeaderLock (ORACLE_LEADER_LOCK=redis) with token-checked renew/release - OracleScheduler polls through the lifecycle; OracleEventRepository tracks evidence writes - DbService closes its pool in onApplicationShutdown, after the drain - main.ts no longer calls enableShutdownHooks() alongside its own signal handlers, which made app.close() run twice Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each oracle data-source adapter (CoinGecko, DeFiLlama, Horizon) now has an ordered list of endpoints: primary, mirrors, other regions. Calls go to the healthiest one, so a single unreachable host no longer stalls a feed. - EndpointHealth: circuit breaker driven by live call outcomes (consecutive failures, latency EWMA). Degraded status expires and a probe trial runs after cooldown, so a recovered primary gets traffic back - ProviderEndpointSet.execute(): per-attempt timeout with AbortSignal, failover within one call, opt-out for non-retryable errors, last-resort try when every endpoint is down - ORACLE_ENDPOINTS_<ADAPTER> config (`url` or `label=url`), falling back to the existing single-URL settings - GET /api/v1/oracle/endpoints/health with credentials/query redacted Separate from SOROBAN_RPC_URLS, which handles on-chain RPC failover. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mixed-network configurations used to fail only at the first publish. Worse, they could succeed against the wrong deployment. Bootstrap now checks before any module starts: - STELLAR_NETWORK is a known network (public/pubnet → mainnet), and is set explicitly whenever network-suffixed values exist - No RPC/Horizon/passphrase/contract value is set for another network's suffix; suffixed and unsuffixed values agree - Passphrase matches; RPC/Horizon URLs (including SOROBAN_RPC_URLS) don't point at another network, by known SDF host or network word in the URL; https required on mainnet - Pool/oracle/policy contract IDs are valid and not reused across roles - With STELLAR_VALIDATE_ONCHAIN=true, RPC getNetwork and Horizon / must return the expected passphrase Every problem is reported at once. scripts/check-network-config.ts runs the same checks before a deploy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@lekescrew22 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
…-shutdown-endpoints-network-guard # Conflicts: # .env.example # src/app.module.ts # src/main.ts # src/oracle/oracle-event.repository.ts # src/oracle/oracle.scheduler.ts
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.
Summary
Implements four oracle and operations issues, one commit each:
feat(oracle): add provider sandbox for signed readings and corroborationfeat(oracle): drain the oracle pipeline gracefully on SIGTERM/SIGINTfeat(oracle): let provider adapters fail over across ordered endpointsfeat(config): require one consistent Stellar network at startupThe new code is self-contained modules with unit tests (63 tests in 5 new suites), wired into
AppModule/main.ts.#253 Provider sandbox
ORACLE_SANDBOX_MODE=truemounts/sandbox/oracle/*. Outside providers can register a key, submit signed readings, and inspect what corroboration did with them. Nothing is published on-chain.Done
provider-submission.ts)sandbox/production), so a sandbox signature never verifies in production.valueis a decimal string, so the signed bytes match in every language.corroboration.ts)max(|median| × ratio, per-feed absolute floor).GET /sandbox/oracle/decisionsreturns the value, median, tolerance, agreeing and outlier providers, and contributing submissions.GET /rejectionsreturns typed reasons (invalid_shape,unknown_feed,unregistered_provider,stale,from_future,bad_signature,rate_limited,replayed_nonce,outlier,insufficient_agreement).GET /feeds/:feedreturns the readings in the current window.STELLAR_NETWORKis mainnet.ORACLE_SANDBOX_RETENTION_MS(24h). Storage is bounded.docs/oracle-provider-sandbox.md, plusscripts/sandbox-sign-reading.tsto produce signed payloads.Not done
#252 Graceful shutdown and in-flight draining
Found while implementing
DbServiceclosed the Postgres pool inonModuleDestroy. Nest runs that hook before any drain, so in-flight evidence writes lost their connection.main.tscalledenableShutdownHooks()and registered its own SIGTERM/SIGINT handlers. As a result,app.close()ran twice.stop_grace_periodis 10s, but the app's grace period is 30s. Docker sent SIGKILL mid-drain.Done
OraclePipelineLifecycleruns in Nest's shutdown order:onModuleDestroy: stop starting polls.beforeApplicationShutdown: drain in-flight polls, publishes and evidence writes withinORACLE_SHUTDOWN_DRAIN_MS. This deadline is capped 5s belowSHUTDOWN_GRACE_PERIOD_MS. Then release the leader lock.onApplicationShutdown: Postgres and Redis close.oracle_event:MarketCrash) and the process exits 1.LocalLeaderLockis the default.ORACLE_LEADER_LOCK=redisenablesRedisLeaderLock, usingSET NX PXwith token-checked renew and release, so a replica whose lock expired can't delete the new leader's lock.OracleSchedulerpolls throughrunPoll()(skipped while shutting down or when not leader).OracleEventRepositorytracks its writes.DbServicenow closes inonApplicationShutdown.docker-compose.ymlsetsstop_grace_period: 40sand passes the deadlines; the Dockerfile has an explicitSTOPSIGNAL SIGTERM.Not done
track("publish", …)yet, becauseoracle-publisher.service.tsis empty onmain. A submit is still covered today because it runs inside a tracked poll.ClaimScheduleris not wired.#254 Per-adapter endpoint failover
Done
ORACLE_ENDPOINTS_<COINGECKO|DEFILLAMA|HORIZON>takes an ordered list ofurlorlabel=urlentries (primary, mirrors, regions).STELLAR_NETWORK.EndpointHealth: a circuit breaker driven by the outcomes of real calls (consecutive failures, latency EWMA, last success).ProviderEndpointSet.execute():AbortSignal, and fails over to the next endpoint within the same call.shouldFailoveropt-out for errors every endpoint would return (e.g. a 400).GET /api/v1/oracle/endpoints/healthshows the current state with credentials and query strings redacted.SOROBAN_RPC_URLS.Not done
oracle.service.tsis empty onmain. Adapting them is a short change:registry.get("coingecko").execute((baseUrl, signal) => …).#251 Cross-network configuration validation
Done
assertNetworkConsistency()checks:STELLAR_NETWORKis a known network (public/pubnet→ mainnet), and is set explicitly whenever suffixed values exist.*_TESTNET/_FUTURENET/_MAINNET/_PUBLICvalue is set for a non-selected network. Errors name the offending keys.POOL_CONTRACT_ID-style) values agree.SOROBAN_RPC_URLSand Horizon URLs don't point at another network, judged by known SDF hosts or a network word in the URL.STELLAR_VALIDATE_ONCHAIN=true: RPCgetNetworkand Horizon/must report the expected passphrase.StellarConfigValidatorcheck rather than duplicating it.scripts/check-network-config.ts [--live]runs the same checks.Not done
check:envnpm script and no CI step.package.jsonis empty onmain; once it is restored, add"check:env": "ts-node scripts/check-network-config.ts".Type of change
app.close(), Compose SIGKILL timing)Verification
The repo's own toolchain can't run (
package.jsonis empty), so I tested in a separate harness with the sametsconfigstrictness flags (strict,noUnusedLocals,noUnusedParameters):All new modules, controllers and Nest modules type-check. Edits to
main.ts,app.module.ts,oracle.scheduler.tsandoracle-event.repository.tscould not be compiled, because those files import modules that are empty or missing onmain.Checklist
.envvalues committed.env.example, README anddocs/updatedCloses #251
Closes #252
Closes #253
Closes #254
🤖 Generated with Claude Code