Skip to content

fix(archiver): guard getL1ToL2Messages against incomplete message sync#21494

Merged
spalladino merged 1 commit intomerge-train/spartanfrom
palla/guard-l1-to-l2-messages
Mar 13, 2026
Merged

fix(archiver): guard getL1ToL2Messages against incomplete message sync#21494
spalladino merged 1 commit intomerge-train/spartanfrom
palla/guard-l1-to-l2-messages

Conversation

@spalladino
Copy link
Contributor

@spalladino spalladino commented Mar 13, 2026

Motivation

getL1ToL2Messages(checkpointNumber) returns the L1-to-L2 messages for a given checkpoint by reading from the local message store. However, if the message tree for that checkpoint hasn't been fully sealed on L1 yet (or the archiver hasn't synced it), the method silently returns incomplete data — or an empty array for a checkpoint that will eventually have messages. This is indistinguishable from a legitimately empty checkpoint (one where no L1-to-L2 messages were sent).

Any caller that uses this result to compute inHash — the sequencer, validator, or slasher — would derive an incorrect hash, leading to mismatches and potential block validation failures.

Approach

The L1 Inbox contract exposes a treeInProgress value: the checkpoint number whose message tree is currently being filled. Trees for checkpoints strictly below this value are sealed and complete. We persist this value in the archiver's message store during each L1 sync cycle and use it as a guard: getL1ToL2Messages now throws L1ToL2MessagesNotReadyError if the requested checkpoint number is >= treeInProgress. On first startup (before any sync), the guard is permissive (skipped) since the value hasn't been set yet.

This approach was chosen over a simpler "last message checkpoint" bound because it correctly handles empty checkpoints — a checkpoint with zero messages is still sealed once treeInProgress moves past it.

Changes

  • archiver: Added L1ToL2MessagesNotReadyError. The message store now persists treeInProgress as an LMDB singleton and guards getL1ToL2Messages against unsealed checkpoints. The L1 synchronizer writes the value on every sync cycle immediately after fetching inbox state.
  • archiver (tests): Updated the fake L1 state mock to compute treeInProgress dynamically from both messages and checkpoints. Added unit tests for the guard (sealed, unsealed, unset). Updated the L1 reorg test to expect the new error for unsealed checkpoints.
  • stdlib: Documented the new throw condition on the L1ToL2MessageSource interface.

Fixes A-659

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@spalladino spalladino added ci-no-fail-fast Sets NO_FAIL_FAST in the CI so the run is not aborted on the first failure backport-to-v4-next labels Mar 13, 2026
@spalladino spalladino merged commit 5ccf3d7 into merge-train/spartan Mar 13, 2026
29 of 33 checks passed
@spalladino spalladino deleted the palla/guard-l1-to-l2-messages branch March 13, 2026 18:46
@AztecBot
Copy link
Collaborator

❌ Failed to cherry-pick to v4-next due to conflicts. (🤖) View backport run.

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

Labels

backport-to-v4-next ci-no-fail-fast Sets NO_FAIL_FAST in the CI so the run is not aborted on the first failure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants