Summary
RPC failover never triggers for a partially-degraded endpoint, so every write fails hard even though healthy fallbacks are configured.
The base-sepolia preset in swarm_provenance_mcp/chain/provider.py already ships fallbacks:
"rpc_url": "https://sepolia.base.org",
"rpc_fallbacks": [
"https://base-sepolia-rpc.publicnode.com",
"https://base-sepolia.drpc.org",
],
but _try_fallback() decides whether an endpoint is usable with is_connected(), which only exercises a cached method (eth_chainId / net_version). An endpoint that serves those while failing on state methods is judged healthy, so the fallback list is never consulted.
Observed 2026-08-19
https://sepolia.base.org was serving cached methods and returning 503 -32011 "no backend is currently healthy to serve traffic" on state methods:
eth_chainId 200 {"result":"0x14a34"}
eth_blockNumber 200 {"result":"0x2b90b34"}
net_version 200 {"result":"84532"}
eth_gasPrice 503 {"error":{"code":-32011,...}}
eth_getTransactionCount 503 {"error":{"code":-32011,...}}
Every anchor_hash call failed with:
Chain error: 503 Server Error: Service Unavailable for url: https://sepolia.base.org/
retryable: false
Meanwhile chain_health reported Connected: true — because it, too, only needs the cached methods. The three configured alternatives were all fully healthy at that moment, verified independently with curl.
Two things compound it: the failure is reported as retryable: false, which tells the caller not to retry when retrying against a different URL would have worked immediately; and the read path appeared fine, so the problem only surfaced at the point of spending gas.
Impact
Anchoring is unavailable for as long as the default public endpoint is partially degraded, with no automatic recovery and a misleading health signal. Workaround is to set CHAIN_RPC_URL to a healthy endpoint, which requires knowing the failure mode first.
Suggested fix
- Probe with a state method in
_try_fallback() — eth_gasPrice or eth_getTransactionCount — rather than is_connected(). Liveness for a write path should exercise what the write path needs.
- On a write failure that looks transport-level (503/502/504 or a
-32011-class RPC error), advance to the next configured URL and retry before surfacing an error.
- Mark those failures
retryable: true.
- Have
chain_health report the probe it actually ran, so a green result cannot mean "chainId answered".
Reproduce by pointing CHAIN_RPC_URL at an endpoint that answers eth_chainId and 503s eth_gasPrice.
Summary
RPC failover never triggers for a partially-degraded endpoint, so every write fails hard even though healthy fallbacks are configured.
The
base-sepoliapreset inswarm_provenance_mcp/chain/provider.pyalready ships fallbacks:but
_try_fallback()decides whether an endpoint is usable withis_connected(), which only exercises a cached method (eth_chainId/net_version). An endpoint that serves those while failing on state methods is judged healthy, so the fallback list is never consulted.Observed 2026-08-19
https://sepolia.base.orgwas serving cached methods and returning 503-32011 "no backend is currently healthy to serve traffic"on state methods:Every
anchor_hashcall failed with:Meanwhile
chain_healthreportedConnected: true— because it, too, only needs the cached methods. The three configured alternatives were all fully healthy at that moment, verified independently with curl.Two things compound it: the failure is reported as
retryable: false, which tells the caller not to retry when retrying against a different URL would have worked immediately; and the read path appeared fine, so the problem only surfaced at the point of spending gas.Impact
Anchoring is unavailable for as long as the default public endpoint is partially degraded, with no automatic recovery and a misleading health signal. Workaround is to set
CHAIN_RPC_URLto a healthy endpoint, which requires knowing the failure mode first.Suggested fix
_try_fallback()—eth_gasPriceoreth_getTransactionCount— rather thanis_connected(). Liveness for a write path should exercise what the write path needs.-32011-class RPC error), advance to the next configured URL and retry before surfacing an error.retryable: true.chain_healthreport the probe it actually ran, so a green result cannot mean "chainId answered".Reproduce by pointing
CHAIN_RPC_URLat an endpoint that answerseth_chainIdand 503seth_gasPrice.