Skip to content

fix(archiver): make addContractClass/Instance idempotent#23207

Draft
AztecBot wants to merge 1 commit into
merge-train/spartanfrom
claudebox/fix-archiver-duplicate-contract-class
Draft

fix(archiver): make addContractClass/Instance idempotent#23207
AztecBot wants to merge 1 commit into
merge-train/spartanfrom
claudebox/fix-archiver-duplicate-contract-class

Conversation

@AztecBot
Copy link
Copy Markdown
Collaborator

Summary

The Merge branch 'next' into merge-train/spartan CI (run 25747371483, log 1778600703708803) failed on e2e_p2p/add_rollup.test.ts with a Timeout awaiting L1 to L2 message ... ready. The visible timeout was a symptom — throughout the run, validators on the new rollup repeatedly logged:

Failed to commit transaction: Error: Contract class 0x0eb8e3f8...4 already exists, cannot add again at block 6
  at ContractClassStore.addContractClass
  at ArchiverDataStoreUpdater.addProposedBlock

…followed by Checkpoint proposal validation failed: block_fetch_error and Block proposal validation failed: parent_block_not_found. With validators unable to attest, no checkpoint landed on L1, the L1→L2 inbox tree was never sealed for the next checkpoint, and the test eventually timed out.

The throw was introduced by 1a9249a ("fix(archiver): throw on duplicate contract class or instance additions"), replacing the prior setIfNotExists (silent-skip) behavior. The intent was to surface unexpected double-adds, but:

  • The class id is content-derived from (artifactHash, privateFunctionsRoot, publicBytecodeCommitment) — duplicate adds are guaranteed to carry identical data. Same for contract instances: the address is content-derived from instance fields.
  • Under proposer pipelining (enabled for add_rollup on merge-train/spartan in fix(validator): include proposed checkpoint out-hashes when validating checkpoint proposals #23119), legitimate double-adds occur naturally — e.g. two transactions in the same block both publish the same class.
  • Symmetric deleteContractClass/deleteContractInstance was already idempotent, skipping deletes when the existing entry's block is older than the rollback block. The throw on add was the only asymmetric failure point.

Fix

Revert addContractClass and addContractInstance to use setIfNotExists semantics — silently skip duplicates and keep the original entry's l2BlockNumber/blockNumber. Preserving the original block number is important because the rollback path keys delete decisions on existing.l2BlockNumber >= blockNumber: if block 5 first registers class X and block 6 re-emits the same event, on rollback of block 6 we want to leave class X intact (block 5 still references it).

The two unit tests that asserted the old throw behavior are updated to assert the new no-op behavior, with the second test verifying that the original l2BlockNumber is preserved across a duplicate add.

Full analysis: https://gist.github.com/AztecBot/325efd9268bd6001350f22bcf2e2a601

Test plan

  • e2e_p2p/add_rollup.test.ts passes on merge-train/spartan CI.
  • archiver unit tests pass (covering the new idempotent contract-class/instance store behavior).

ClaudeBox log: https://claudebox.work/s/017d5e2dc6891bf4?run=1

@AztecBot AztecBot added ci-draft Run CI on draft PRs. claudebox Owned by claudebox. it can push to this PR. labels May 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-draft Run CI on draft PRs. claudebox Owned by claudebox. it can push to this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant