Skip to content

fix(archiver): resolve L2-to-L1 witness from a single store snapshot#24754

Open
spalladino wants to merge 1 commit into
merge-train/spartan-v5from
spl/atomic-l2-to-l1-witness-resolution
Open

fix(archiver): resolve L2-to-L1 witness from a single store snapshot#24754
spalladino wants to merge 1 commit into
merge-train/spartan-v5from
spl/atomic-l2-to-l1-witness-resolution

Conversation

@spalladino

@spalladino spalladino commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

getL2ToL1MembershipWitness assembled its witness from several non-atomic archiver reads (getTxEffect, then the epoch blocks, target block and checkpoint metadata read inside computeL2ToL1MembershipWitness). A store commit landing between those reads could splice together two different chain states and surface a spurious "message does not exist" error even when the tx-effect index was healthy.

Wrap the witness assembly in a single store.transactionAsync so every archiver read binds to one write transaction and observes a consistent snapshot (the store serializes writers, so no commit can interleave). The L1 Outbox roots fetch stays outside the transaction to avoid holding the archiver's writer lock across a network round-trip, and the tx effect is re-read inside the snapshot so the receipt's block number and tx index stay consistent with the block data.

Cost: witness assembly briefly serializes with archiver writers (it holds the store's writer queue while it reads), but the work under the lock is pure store reads plus an in-memory tree build — no network I/O.

Context: this came out of the A-1426 investigation. The incident symptom itself turned out to be a downstream client bug following a v5 behavior change in L2BlockSynchronizer — not this race, and not archiver index corruption. This PR is therefore hardening against a real but so-far-unobserved torn-read window, not the incident fix. See #24765 for the companion store hardening from the same investigation.

Related to A-1426

@spalladino
spalladino changed the base branch from next to merge-train/spartan July 16, 2026 18:09
@spalladino
spalladino force-pushed the spl/atomic-l2-to-l1-witness-resolution branch from d259113 to 0f5c608 Compare July 17, 2026 17:31
getL2ToL1MembershipWitness assembled its witness from several
non-atomic archiver reads (getTxEffect, then the epoch blocks, target
block and checkpoint metadata read inside computeL2ToL1MembershipWitness).
A store commit landing between those reads could splice together two
different chain states and surface a spurious "message does not exist"
error even when the tx-effect index was healthy.

Wrap the witness assembly in a single store.transactionAsync so every
archiver read binds to one write transaction and observes a consistent
snapshot (the store serializes writers, so no commit can interleave).
The L1 Outbox roots fetch stays outside the transaction to avoid holding
the archiver's writer lock across a network round-trip, and the tx
effect is re-read inside the snapshot so the receipt's block number and
tx index stay consistent with the block data.

Add a kv-store test asserting reads inside a transaction see a
consistent snapshot while a concurrent write is queued behind it.
@spalladino
spalladino force-pushed the spl/atomic-l2-to-l1-witness-resolution branch from 0f5c608 to 92db3bb Compare July 17, 2026 17:34
@spalladino
spalladino changed the base branch from merge-train/spartan to merge-train/spartan-v5 July 17, 2026 17:35
@spalladino spalladino added T-fix Type: fix this. Not quite a bug. and removed T-fix Type: fix this. Not quite a bug. labels Jul 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant