fix(network-config): require ESPLORA_URL + ESPLORA_WS_URL on Mainnet - #115
Merged
TaprootFreak merged 1 commit intoMay 26, 2026
Merged
Conversation
`NETWORK_CONFIG` silently fell back to the Mutinynet defaults (`https://mutinynet.com/api`, `wss://mutinynet.com/api/v1/ws`) when the env var was missing, regardless of `IS_MAINNET`. On DEV that matches the chain. On Mainnet it's a silent footgun: - An HTTP-only mismatch panics quickly on the first publisher round-trip and the operator sees the breakage immediately. - The new event-driven scanner (#84) instead subscribes to Mutinynet block events and tries to fetch them from the Mainnet HTTP Esplora. Every `get_block_txids` returns 404 and `scanner_runtime` enters a 5 s HTTP-retry loop that never updates `processed_blocks`: the service stays up, `/health/ready` reports green, no chain ingestion happens, no on-chain mint or send commit is ever picked up. The binary never self-heals after restart because no env-derived state has changed. `ESPLORA_URL` and `ESPLORA_WS_URL` are now both **required env vars when `IS_MAINNET=true`** (panic with diagnostic message, mirroring the existing `PUBLISHER_KEY` / `USERNAME_DOMAIN` / `DATABASE_URL` idiom in the same file). Empty / whitespace-only values are treated as unset so a `ESPLORA_URL=` line in a compose file panics with the same message instead of leaving `EsploraConfig.url = ""`. The `IS_MAINNET=false` (DEV / Mutinynet) path is unchanged — both URLs keep their Mutinynet defaults, the pre-push hook and the M3 Ultra coverage gate are unaffected. Implementation: pulled the env-resolution out of the `lazy_static!` block into a pure `build_network_config_from_env<F>(env: F)` so the panic rules are unit-testable without `std::env::set_var` (which would poison the `NETWORK_CONFIG` cell across tests in the same binary). Seven new `#[test]`s cover the headline shapes plus the empty-string and whitespace-only rejection paths. The guard is enforced at the `NETWORK_CONFIG` access path only. `scanner_ws::ScannerWsConfig::from_env` and `publisher.rs` still read `ESPLORA_WS_URL` independently with the Mutinynet fallback — in the main binary the panic in this builder fires first (main.rs dereferences `NETWORK_CONFIG` during bootstrap, before any scanner or publisher env read), so the structural bypass is unreachable today. Closing that bypass by having those sites consume `NETWORK_CONFIG.ws_url` directly is tracked as a follow-up.
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
NETWORK_CONFIGsilently fell back to the Mutinynet defaults(
https://mutinynet.com/api,wss://mutinynet.com/api/v1/ws)regardless of
IS_MAINNET. On Mainnet that produced today's PRDoutage (Deploy PRD run
26441824314after PR #18 merged): the event-driven scanner (#84) subscribed to
Mutinynet block events while HTTP pointed at the Mainnet
electrs-mainnet, everyget_block_txids404'd, andscanner_runtimeentered a 5 s HTTP-retry loop that never advancesprocessed_blocks. Service stayed up,/health/readyreportedgreen, no chain ingestion happened.
This PR makes both URLs required env vars when
IS_MAINNET=true,mirroring the existing
PUBLISHER_KEY/USERNAME_DOMAIN/DATABASE_URLpanic-on-missing idiom already inlib.rs. Empty /whitespace-only values are rejected the same way so a stray
ESPLORA_URL=line in a compose file panics with the samediagnostic instead of leaving the field as
"".Why this is a real outage, not a smoke-test miss
/health/health/ready/api/inforeturns 200The deploy-prd.yaml smoke test gated on
/api/info200 and let thebroken build through. The panic in this PR puts the failure on the
bootstrap path so a future broken Mainnet config never reaches
listening at all.
DEV / Mutinynet impact
None.
IS_MAINNET=false(the unset default) keeps the existingMutinynet defaults for both URLs, the pre-push hook still passes
without setting any new env var, and the CI
node-testsjob(
.github/workflows/ci.yaml) does not setIS_MAINNET=true.Implementation
lazy_static!block into apure
pub fn build_network_config_from_env<F>(env: F)so thepanic rules are unit-testable via a fake-env closure instead of
std::env::set_var(which would poison theNETWORK_CONFIGcellacross other tests in the same binary).
IS_MAINNET=truebranches:ESPLORA_URLrequired, non-emptyESPLORA_WS_URLrequired, non-emptyIS_MAINNET=falsebranches: unchanged Mutinynet defaults.rationale, and point at concrete working values
(
http://electrs-mainnet:3000,https://mempool.space/api,wss://mempool.space/api/v1/ws).Test plan
cargo fmt --all --checkcleancargo clippy -p node -p shared -- -D warningscleancargo clippy -p node --all-features -- -D warningscleancargo clippy -p zkcoins-program-plonky2 -p zkcoins-prover-plonky2 --lib -- -D warningscleancargo check --workspace --all-featuresclean#[test]s innode/src/main_tests.rscovering:IS_MAINNETunset"true"IS_MAINNET(e.g."1")NETWORK_NAMEoverride#[should_panic]on Mainnet missingESPLORA_URL#[should_panic]on Mainnet emptyESPLORA_URL("")#[should_panic]on Mainnet missingESPLORA_WS_URL#[should_panic]on Mainnet whitespace-onlyESPLORA_WS_URLlint-and-buildgreen on this PRci:fulllabel or auto-release-PR sync, the M3 Ultranode-tests+ coverage gate greenDefense-in-depth pairing
Server-side compose for the DFX dfxprd Mainnet stack will set
ESPLORA_WS_URL=wss://mempool.space/api/v1/wsexplicitly so thedeploy is self-documenting and matches this new contract:
https://github.com/DFXServer/server/pull/250
Out of scope / follow-ups
scanner_ws::ScannerWsConfig::from_envandpublisher.rs(lines440, 722) still call
std::env::var("ESPLORA_WS_URL")independently with the Mutinynet fallback. In the production
binary
main.rsdereferencesNETWORK_CONFIGduring bootstrapbefore any of those sites runs, so the panic in this builder
fires first and the structural bypass is unreachable today.
Having those sites consume
NETWORK_CONFIG.ws_url(or take anexplicit
&EsploraConfig) directly is a separate refactor andworth a follow-up to close the bypass for future entry points.
mod testsin the crate carries#[cfg_attr(coverage_nightly, coverage(off))](CONTRIBUTING.md§7.10). Not introduced by this PR; tracking separately.
deploy-prd.yamlis missing theZKCOINS_E2E_ALLOW_FEATURE_TRIMMED_SERVER: "true"env on itsapi-e2ejob — tracked as issue fix(ci): set ZKCOINS_E2E_ALLOW_FEATURE_TRIMMED_SERVER on PRD api-e2e #114, separate PR.