Repository navigation
feat(rpc): backup providers and probe-gated blacklist recovery - #127
Conversation
- Add `backup: true` node config; backup providers are only used while every primary is unavailable, and traffic returns to a primary as soon as one recovers. - Escalate blacklist cooldowns (doubling per strike, capped at 30m), reset on a successful request. - For EVM, gate recovery from error blacklists on a background probe (eth_blockNumber) that must succeed and be within 20 blocks of the active provider's head. - Emergency recovery now points currentIndex at the recovered provider instead of index 0.
- Compare the recovering provider against the active one by latest-block
timestamp with a 60s limit, so the allowed lag scales with each EVM
chain's block time instead of a fixed block count.
- Add EthereumAPI.GetLatestBlockTimestamp, which decodes only the
timestamp from eth_getBlockByNumber("latest", false); hash-only
transactions can't be decoded into Block.Transactions.
- Fetch both heads concurrently so the gap between calls isn't counted
as lag on fast chains.
- count one blacklist strike per incident instead of per failed request - blacklist a force-rotated provider when a backup exists so traffic reaches the backup and returns on a timer - reuse the strike after emergency recovery so a total outage does not escalate cooldowns - drop probe results when the provider changed state while the probe ran - take the failover write lock only when a primary is available - remove the 5s sleep from the backup fallback test
|
Reviewed this and put the suggested fixes in a follow-up PR against this branch: #128 Main points:
Left as is: a single |
|
Ran the last unchecked test-plan item ("Run the EVM indexer with a primary and a backup node and confirm failover and return to primary in the logs"). Setup. Built from the #128 branch ( Scenario 1: primary returns HTTP 429 From 14:32 to 14:47 the indexer processed 78 blocks (26397384 to 26397461) with no gaps and no error-level log lines; Scenario 2: primary returns unclassified errors (HTTP 500) This is the force-rotate path, which only reaches the backup with the #128 change. Failover, cooldown escalation on failed probes, and return to primary all show up as expected in both scenarios. |
fix(rpc): harden backup failover blacklisting and probes
Summary
backup: trueflag on node config. A backup provider is only used while every primary is unavailable. Traffic goes back to a primary as soon as one recovers. Wired through every chain builder inworker/factory.go.MaxBlacklistDuration(default 30m, or the base cooldown if that's larger). A successful request resets the count.currentIndex = 0. It now points at the provider it actually recovered.How it works
1. Failover to the backup
2. Recovery probe: test the primary before giving it traffic again
Before this PR, a blacklisted provider got real traffic again as soon as its cooldown expired. If it was still broken, real requests failed again. Now a small test runs in the background first, while real traffic stays on the backup:
Step 2 matters because a node can be alive but stuck: it answers instantly but returns an old block. Step 1 alone would let it back in, and the indexer would stop moving forward.
When step 2 is skipped. If there's nothing reliable to compare against, a broken reference shouldn't keep a working primary out, so the probe passes on step 1 alone:
3. Why timestamps instead of block numbers
A fixed block count means very different amounts of time on each EVM chain. A time limit adapts by itself:
Both heads are fetched concurrently, so the time between the two calls isn't counted as lag on fast chains.
4. Cooldown escalation (primary stays broken)
A dead primary costs at most about 2 lightweight calls every 30 minutes. With every provider healthy, the probe makes zero calls. Probe calls go through the same per-endpoint rate limiter as normal traffic.
5. Single provider
There's nowhere to fail over to, so emergency recovery puts the only provider back immediately. Cooldowns and the probe never take effect, and the indexer keeps retrying, paced by the rate limiter and the worker's retry logic. That's the same behavior as before this PR.
Config
Notes
EthereumAPIgainsGetLatestBlockTimestamp. It decodes onlytimestampfrometh_getBlockByNumber("latest", false), because withfalsethe node returns transactions as hash strings, which can't be decoded intoBlock.Transactions []Txn.Test plan
go test -race ./internal/rpc/ ./internal/worker/: primary preference, return to primary, falling back to the backup on retry, cooldown escalation and reset, probe fail/pass/lag, timer recovery for capability blacklistsGetLatestBlockTimestampunit tests covering a hash-only transaction list and a null blocketh_getBlockByNumber("latest", false)against an Ethereum public RPC: transactions came back as hash strings and the latest block was 4s old