Skip to main content

Module chain_events

Module chain_events 

Source
Expand description

Shared chain-event log streaming for the on-chain watchers.

Every watcher registers a Route on one multiplexed_poller, a single eth_getLogs polling loop that merges all routes into one call per tick and unifies historical backfill and the live tail into one resumable, cursor-persisting loop (#1092). The node makes no watch_logs/eth_newFilter call: the default public Arbitrum Sepolia RPC and most keyless endpoints reject those with -32601 (#1106).

§Watchers scan to head, deliberately

Every watcher’s scan upper bound is the head block itself. There is no confirmation lag and no knob for one.

Two distinct things can go wrong at the unstable tip, and re-scan idempotency covers only one of them:

  • Re-delivery of a canonical log. Covered. The retry and reorg-rewind paths already re-deliver, so resumable_watcher’s module doc does not merely observe sink idempotency, it requires it (dedup by id / set insertion / authoritative re-read). A re-mined event is re-applied harmlessly.
  • A log that never becomes canonical. Not covered, and not the same property: idempotency makes a repeated apply cheap, whereas a confirmation lag would prevent the apply happening at all. eth_getLogs returns only canonical logs, so no sink ever observes a removal and none has an un-apply path. An event from an orphaned block therefore stays applied.

That second case is an accepted residual, not a solved problem. It is accepted because reorgs on the target chain are shallow and rare (see backfill’s REORG_MARGIN_BLOCKS, which carries the sizing rationale for an Arbitrum-Sepolia-class L2 and is the single place that claim lives), and because the sinks holding an authoritative on-chain enumeration source re-read it and self-heal. The one without one is where an orphan sticks: settlement’s register_open_channel (durable — an fsynced phantom channel, reclaimed only by the expiry sweep). It predates #1227 and is unchanged by it — deleting an always-zero field cannot alter what a lag never did.

For the settlement watcher specifically, a lag also carries a concrete cost, which is what makes zero an active choice there rather than an inherited one: it would delay channel registration, so a client’s first request on a freshly-opened channel would be rejected as unknown.

If a future chain needs a lag, reintroduce it as a config knob with a non-zero default and a test asserting a live production config engages it — not a silently-zero field whose unit test exercises the arithmetic rather than the wiring.

Modules§

boot_retry
In-process retry for the boot-time chain reads (#2159).
multiplexed_poller
Multiplexed eth_getLogs polling: one poll tick, one get_logs call per backfill window, demuxed by (address, topic0) into per-route sinks.
shared_head
One shared, TTL-cached eth_blockNumber read for every chain watcher.

Structs§

WatcherHandle
Owns a running watcher task and the shutdown token that stops it. The token is minted inside super::multiplexed_poller::spawn and never escapes except through shutdown, so no call site can conjure or drop an inert token — the mistake #1230 fixed by convention, made unrepresentable (#1236). shutdown is the graceful path (the loop flushes every route’s cursor and returns); the wrapped AbortOnDropHandle guard is the hard safety net when the handle drops without a prior shutdown.