Skip to content

fix(archiver): reject checkpoints that repeat a tx hash - #338

Merged
spalladino merged 7 commits into
mainfrom
spl/a-2181-reject-duplicate-tx-hashes
Oct 3, 2026
Merged

spalladino merged 7 commits into
mainfrom
spl/a-2181-reject-duplicate-tx-hashes

Conversation

@spalladino

@spalladino spalladino commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

A checkpoint that repeats a tx hash from an earlier block could permanently corrupt that block in the archiver. Now, we validate that and throw. This stalls the syncing process until a prune comes along.

Context

Tx effects are stored keyed by tx hash alone, and the last write wins. A backup (escape-hatch) checkpoint is stored without being executed, so it can list a tx hash already included in an earlier, proven block. The later block then takes over the tx-effect row. When the later checkpoint is pruned, deleting it also deletes that row. The proven block below it can then no longer be loaded, and the node never fetches it again.

Approach

  • The archiver now refuses to ingest such a checkpoint, and the whole addCheckpoints batch is aborted:
    • validateCheckpointStructure rejects a tx hash that appears more than once within a checkpoint;
    • the block store throws DuplicateTxHashError when a tx hash already belongs to a lower block that is still stored.
  • Repeats at the same or a higher height are accepted. Honest flows produce them: a losing local proposal replaced by the L1 checkpoint, a checkpoint presented again after an L1 reorg, and promotion of a proposed checkpoint. The check relies on the updater removing replaced local blocks (and their tx-effect rows) before inserting the checkpoint.
  • A repeated tx hash makes the checkpoint unprovable, because the same hash means the same kernel output and so duplicate nullifiers. Sync therefore stalls only until L1 prunes the checkpoint:
  • Docs: the archiver README and the comment at the archiveAt check now describe this recovery path. I also corrected two sequencer comments that said the build-start gate runs after checkSync.
  • The existing test "fully cleans up blocks sharing a tx effect" built exactly the state the store now refuses, so it was rewritten to check the rejection and the cleanup of the ancestor block.
  • Cost: one extra tx-effect read per tx when a block is inserted.

Fixes A-2181

A block that lists a tx hash already stored under a lower (ancestor) block took over that tx's
effect entry, so the ancestor served the wrong effect and became unloadable once the later
block was pruned. validateCheckpointStructure now rejects a checkpoint that repeats a tx hash
across its blocks, and BlockStore refuses to insert a block that repeats a tx owned by a stored
ancestor, throwing DuplicateTxHashError and aborting the whole write transaction. Owners at the
same or a higher height, or no longer stored at their height, are replaced blocks and are still
overwritten.
Document that CheckpointProposed logs of pruned or replaced checkpoints are dropped by the
archiveAt check, so a checkpoint the archiver refuses stalls sync only until L1 prunes it, and
that proposers send the lazy prune() from their cannot-build fallback even when their own sync
is stuck.
@spalladino
spalladino requested a review from alexghr as a code owner September 25, 2026 20:40
@spalladino
spalladino enabled auto-merge (squash) September 25, 2026 20:59
Comment thread yarn-project/archiver/src/store/block_store.ts Outdated
@spalladino
spalladino requested a review from alexghr October 2, 2026 20:46
@spalladino
spalladino merged commit 5a104dc into main Oct 3, 2026
6 checks passed
@spalladino
spalladino deleted the spl/a-2181-reject-duplicate-tx-hashes branch October 3, 2026 21:08
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.

2 participants