Skip to content

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

Merged
PhilWindle merged 1 commit into
merge-train/spartan-v5from
spl/atomic-l2-to-l1-witness-resolution
Jul 27, 2026
Merged

fix(archiver): resolve L2-to-L1 witness from a single store snapshot#24754
PhilWindle merged 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
@PhilWindle PhilWindle added the port-to-next Forward-port this merged PR into next label Jul 27, 2026
@PhilWindle
PhilWindle merged commit c47ab3e into merge-train/spartan-v5 Jul 27, 2026
28 of 29 checks passed
@PhilWindle
PhilWindle deleted the spl/atomic-l2-to-l1-witness-resolution branch July 27, 2026 10:01
AztecBot pushed a commit that referenced this pull request Jul 27, 2026
…24754)

`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
@AztecBot

Copy link
Copy Markdown
Collaborator

✅ Successfully ported to port-to-next-staging #24964.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

port-to-next Forward-port this merged PR into next

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants