feat(config): per-chain configuration and runtime registry - #237
Open
Nexha-dev wants to merge 1 commit into
Open
feat(config): per-chain configuration and runtime registry#237Nexha-dev wants to merge 1 commit into
Nexha-dev wants to merge 1 commit into
Conversation
Replaces single-chain AppState fields (network/horizon/horizon_url/ friendbot_url) with Arc<ChainRegistry>, built from per-chain config, each carrying its own RPC endpoint, confirmation depth, poll interval, and resilience policy (RetryPolicy + CircuitBreaker) so a degraded RPC on one chain can never open another chain's circuit breaker. Config source, in precedence order: a CHAIN_CONFIG_PATH TOML file ([[chains]] entries, see octo.chains.example.toml) with per-chain OCTO_CHAIN_<CHAIN_ID>_RPC_URL env overrides for secrets; else the legacy flat env vars (NETWORK/HORIZON_URL/FRIENDBOT_URL/HORIZON_*) build one implicit Stellar chain, so existing single-chain deployments keep working unmodified. bin/server fails fast at startup: chain ids must be unique and non-empty, at least one chain must be enabled, and every enabled chain's RPC must pass a liveness probe before the server serves traffic. RPC URLs use a redacting Debug/Display impl (RedactedUrl) — provider URLs embed API keys, and reaching the raw value requires an explicit expose_secret() call. GET /health/chains reports per-chain reachability without ever including the RPC URL. AppState::new/new_with_resilience keep their existing signatures (building a single-chain registry internally) so the many existing call sites and tests are unaffected; AppState::from_chain_registry is the new multi-chain construction path bin/server uses. ChainRegistry here is deliberately lightweight (config + resilience + poll-health state only) — the trait-based chain abstraction from the chain-adapter epic supersedes it once that lands. This PR does not add EVM support; it delivers the shape for N chains, EVM adapters plug in later. Closes Octo-Protocol-org#216
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
AppState's single-chain fields (network/horizon/horizon_url/friendbot_url) withArc<ChainRegistry>, built from a newChainConfig/AppConfigstructure — one entry per chain, each carrying its own RPC endpoint, confirmation depth, poll interval, and resilience policy (RetryPolicy+CircuitBreaker), so a degraded RPC on one chain can never open another chain's circuit breaker.crates/api/src/chain_config.rs: a TOML file (CHAIN_CONFIG_PATH,[[chains]]entries — seeocto.chains.example.toml) with per-chainOCTO_CHAIN_<CHAIN_ID>_RPC_URLenv overrides for secrets; if no TOML file is present, the legacy flat env vars (NETWORK/HORIZON_URL/FRIENDBOT_URL/HORIZON_*) build one implicit Stellar chain, so existing single-chain deployments keep working unmodified.bin/serverfails fast and loudly at startup: chain ids must be unique and non-empty, at least one chain must be enabled, and every enabled chain's RPC must pass a liveness probe before the server serves traffic — a bad RPC URL aborts boot instead of failing lazily on the first customer deposit.Debug/Displayimpl (RedactedUrl) — provider URLs (Alchemy/Infura) embed API keys, and reaching the raw value requires an explicitexpose_secret()call, so a leak into a log line or error message takes a deliberate choice rather than an accidental{:?}.GET /health/chains, reporting per-chain reachability and last successful poll (reusing theLastPollTrackershape) without ever including the RPC URL.friendbot_urlmoves into the Stellar chain config — it's a Stellar-only concept and doesn't belong in shared state.AppState::new/new_with_resiliencekeep their existing signatures (building a single-chain registry internally), so the many existing call sites and tests are unaffected;AppState::from_chain_registryis the new multi-chain construction pathbin/serveruses.Scope
Per the issue's guideline, this PR is deliberately mechanical: it delivers the shape for N chains (config, registry, per-chain resilience isolation, redaction, health) and does not add any EVM chain.
ChainRegistryhere is a lightweight config+resilience registry, not a trait-based chain-adapter registry — the adapter abstraction (#213) can supersede or wrap it later without disturbing the config/validation/isolation work done here.Test plan
cargo fmt --all -- --checkcargo clippy --workspace --all-targets --locked -- -D warnings— cleancargo build --workspace --all-targets— cleancargo test --workspace --lockedagainst a live Postgres — clean, including:Debug/Displayoutput and from the/health/chainsresponse bodyChainRegistry-level isolation).env.example,README.md, anddocs/architecture.mdupdated with a worked multi-chain example (octo.chains.example.toml)Closes #216