Skip to content

RPC failover never fires for a partially-degraded endpoint, so anchoring writes fail hard #140

Description

@crtahlin

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

  1. 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.
  2. 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.
  3. Mark those failures retryable: true.
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingchain-anchoringOn-chain provenance & blockchain anchoringpriority-highHigh priority

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions