fix(stream_eth): seed the block stream from eth_blockNumber and poll heads alongside newHeads - #1345
DylanVerstraete wants to merge 2 commits into
Conversation
PR SummaryMedium Risk Overview
Archiver adds Reviewed by Cursor Bugbot for commit e5ce953. Bugbot is set up for automated code reviews on this repo. Configure here. |
4c33daa to
038ba11
Compare
…heads alongside newHeads Liveness audit finding 1: `stream_rpc` waited for the first `newHeads` frame with no deadline before any historical work could begin, and the archiver awaited that constructor at startup, in backfill and inside its reconnect loop. A subscription that acknowledges but never delivers (proxies, load balancers, an idle chain) wedged the process while `/status` kept answering. - The starting head now comes from `eth_blockNumber`; catch-up begins at once and never depends on a future header. Each subscribe/head attempt is bounded by `rpc_call_timeout` (default 30 s; alloy transports have none). - Head observations from the subscription are merged with a periodic `eth_blockNumber` poll (`head_poll_interval`, default 12 s). Observations that do not advance the frontier contribute nothing, so the two sources overlap freely; the merged stream still ends when the subscription ends, so a dead socket surfaces as before and triggers reconnection. - `expand_heads` / `merge_heads` are pure and unit-tested (first tests in stream_eth). - Archiver: `--head-poll-interval-secs` / `--rpc-timeout-secs` plumbed into every stream construction. The attestor picks up the defaults unchanged. Reproduced the audit scenario with a frame-dropping WS proxy against `reth --dev` (poll 3 s): started with `eth_subscription` frames suppressed the archiver came up, served `/status`, and advanced 14 -> 21 -> 27 through polls; silenced again mid-run it advanced 27 -> 33 (head 34); zero watchdog reconnects; SIGTERM still exited cleanly.
038ba11 to
12370dd
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 12370dd. Configure here.
The poll stream is not driven while expand_heads drains the seeded range; with tokio's default Burst the ticks missed during a long catch-up fired back-to-back as a flood of eth_blockNumber calls on the block-fetch socket.

Stacked on #1344 (base is
fix/archiver-liveness-a; retarget tousc-devonce that merges). Liveness audit finding 1.Problem
stream_rpcwaited for the firstnewHeadsframe with no deadline before any historical work could start, and the archiver awaited that constructor at startup, in backfill and inside its reconnect loop. A subscription that acknowledges but never delivers (seen through proxies/load balancers, or simply an idle chain) wedged the process indefinitely while/statuskept returning 200. The audit reproduced this: "suppress notifications on replacement sockets → stalled at height 24 with no further connection attempts".Fix
eth_blockNumber: catch-up begins immediately and never depends on a future header. Each subscribe/head attempt is bounded byrpc_call_timeout(default 30 s; alloy transports have no default timeout).eth_blockNumberpoll (head_poll_interval, default 12 s). Observations that don't advance the frontier contribute nothing, so the two sources overlap freely. The merged stream still ends when the subscription ends, so a dead socket surfaces exactly as before and triggers reconnection.expand_heads/merge_headsextracted as pure functions with unit tests (the first tests instream_eth).--head-poll-interval-secs/HEAD_POLL_INTERVAL_SECSand--rpc-timeout-secs/RPC_TIMEOUT_SECS, plumbed into all three stream constructions. The attestor uses the sameStreamRootsand picks up the defaults unchanged.Verification
cargo test -p stream_eth -p archiver: 4 new + 20 passing; clippy-D warningson stream_eth/archiver/attestor; fmt clean.reth --dev(1 s blocks, poll 3 s):eth_subscriptionframes suppressedPreviously the first scenario left the process alive with no API listener and no progress (audit table, last row).