feat(archiver): event-trigger L2BlockStream sync from archiver updates - #24317
Merged
PhilWindle merged 6 commits intoJun 30, 2026
Conversation
spalladino
force-pushed
the
spl/a-1298-event-trigger-l2blockstream-sync-from-archiver-updates
branch
2 times, most recently
from
June 29, 2026 15:21
4c489e8 to
7b3f4b7
Compare
Add an aggregate `l2BlockSourceUpdated` event emitted once per committed archiver sync pass, carrying the chain tips before/after the pass and the hydrated blocks added (and, best-effort, pruned). A new `EventDrivenL2BlockStream` wrapper subscribes to this event to reconcile immediately on an archiver update, while keeping periodic polling as the correctness fallback so a missed event only affects latency. The wrapper serves hydrated blocks from a hot-block cache so a triggered sync does not re-read archiver block bodies, and the no-op polling invariant is preserved: a fully-synced pass only calls `getL2Tips()`, backed by `L2TipsCache` once warm. World-state, p2p, and prover-node are switched to the wrapper; RPC-backed consumers (pxe) are left on the plain `L2BlockStream`.
Drop the unused blocksPruned from the aggregate update delta/event, pass the event into the triggered sync pass via RunningPromise.trigger's new arg, and arm the hot-block fast path only when local tips match the event's fromTips. Extract the block stream's lenient tip comparison into shared localBlockIdDiffers / localTipsMatch helpers and reuse them in both the stream and the wrapper.
…ck array Drop the L2BlockSourceUpdate type and its empty/merge/has helpers; the pass-level accumulator is now a plain L2Block[] of added blocks threaded through the archiver and L1 synchronizer. handleCheckpoints carries it as RollupStatus.blocksAdded.
spalladino
force-pushed
the
spl/a-1298-event-trigger-l2blockstream-sync-from-archiver-updates
branch
from
June 29, 2026 18:42
7b3f4b7 to
0dd9e45
Compare
PhilWindle
reviewed
Jun 30, 2026
| } | ||
|
|
||
| /** Fast-path context for a single sync pass: blocks to serve by number, plus the tips to report as the source's. */ | ||
| type ActiveUpdate = { byNumber: Map<number, L2Block>; toTips: L2Tips }; |
Contributor
There was a problem hiding this comment.
Can be patched on a follow-up but presumably these 'byNumber' Maps can be keyed on branded types.
PhilWindle
approved these changes
Jun 30, 2026
PhilWindle
deleted the
spl/a-1298-event-trigger-l2blockstream-sync-from-archiver-updates
branch
June 30, 2026 09:03
rangozd
pushed a commit
to rangozd/aztec-packages
that referenced
this pull request
Aug 5, 2026
BEGIN_COMMIT_OVERRIDE refactor(e2e): relocate no-node straggler tests (AztecProtocol#24344) test(e2e): pin gas_estimation public-payment txs to one block to deflake fee comparison (AztecProtocol#24382) feat(archiver): event-trigger L2BlockStream sync from archiver updates (AztecProtocol#24317) test(e2e): speed up individual e2e tests (AztecProtocol#24345) chore(e2e): warm blob KZGs in parallel during setup (AztecProtocol#24383) chore: add perf as a valid PR title prefix (AztecProtocol#24412) test(e2e): adopt shared wait helpers (AztecProtocol#24404) test(e2e): run prover client.test.ts in CI (AztecProtocol#24399) fix(ethereum): mine empty L1 blocks without touching the mempool (AztecProtocol#24414) test(e2e): deflake empty block proving test (AztecProtocol#24411) test(e2e): allocate HA node p2p ports above the ephemeral range to deflake e2e_ha_full (AztecProtocol#24418) fix(sequencer): use evmMine in automine auto-settle to avoid dropping test L1 txs (AztecProtocol#24421) test(e2e): instrument common spans for wall-clock tracking (AztecProtocol#24407) test(e2e): remove redundant reqresp_no_handshake e2e test (AztecProtocol#24424) END_COMMIT_OVERRIDE
spalladino
added a commit
that referenced
this pull request
Aug 14, 2026
…25206) Fixes a race where `worldState.syncImmediate(N)` failed with `block_not_available` even though the archiver had block N, alerting `Eviction rule FeePayerBalanceEviction failed` on production nodes. ## Context `EventDrivenL2BlockStream.sync()` goes through `RunningPromise.trigger()`, whose pending-request slot was only cleared after the pass serving it completed. Since the world-state stream is event-driven, nearly every block-processing pass serves a request, so a `sync()` arriving mid-pass attached to a pass that started before the archiver committed the target block and resolved at N-1. Additionally, a trigger coalescing onto a pending request silently dropped its event payload, so the next pass could run armed with an older event's hot-block cache, which capped `getL2Tips()` below the archiver's real tip. Both mechanisms produce the observed error; the 100ms fallback poll self-healed right after. ## Approach - `RunningPromise` now captures and clears the request slot when a pass starts, so `trigger()` resolves only after a full run that started after the call; unserved requests are rejected when the loop exits instead of hanging; the per-trigger argument is removed entirely. - `EventDrivenL2BlockStream` is reduced to a doorbell: the archiver's aggregate event just triggers an immediate reconciliation pass, and every pass reads tips and blocks authoritatively from the source. The `HotBlockSourceAdapter` fast path and the event's `blocksAdded` payload are deleted — their arming conditions were the source of the race, and their only real saving was block-body re-hydration on event-triggered passes. - The latency win that motivated the event-driven stream (#24317) is preserved; if the removed body-read saving ever shows up in profiles, it should be recovered inside the archiver with a content-addressed block cache instead (tracked separately). Fixes A-1659
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
Archiver-driven block sync is poll-based: every subsystem block stream waits for its next poll interval to notice that the archiver advanced. This adds avoidable latency to syncing world-state, p2p, and the prover-node, especially in fast e2e runs.
Approach
Make archiver-driven block sync event-triggered without changing the correctness model. Polling stays enabled, so a missed event only affects latency, never correctness.
l2BlockSourceUpdatedevent per committed sync pass, carryingfromTips,toTips, and the hydratedblocksAddedalready in hand from the pass (inbound queue + L1 checkpoint payloads — no extra storage reads). It is emitted only after store transactions commit andL2TipsCache.refresh()completes, and is suppressed on a no-op pass (tips unchanged and no blocks added); prunes and tier-only movements still emit because they move the tips.EventDrivenL2BlockStreamwrapsL2BlockStream: it subscribes to the aggregate event and threads it throughRunningPromise.trigger(arg)to drive an immediate reconciliation, coalescing bursts onto a single pass while the periodic poll remains the fallback.fromTips(a full local-vs-event tip comparison), the pass serves blocks from the event and reportstoTipsdirectly, so it re-reads neither block bodies nor tips from the archiver. When the stream is not caught up, every read delegates to the source, so a stale or partial event can never change the sync outcome.localBlockIdDiffers/localTipsMatchhelpers, reused by both the stream and the wrapper.RunningPromise<T>gains an optional type parameter (defaulting tovoid) sotrigger(arg?: T)can forward per-trigger data to the polled function; existing call sites are unaffected.Subsystems keep consuming the existing
L2BlockStreamEvents; the aggregate event is handled entirely inside the wrapper. World-state, p2p, and the prover-node are switched to the wrapper; RPC/node-backed consumers (pxe) stay on the plainL2BlockStream.Fixes A-1298