fix(archiver): treat re-publish of preloaded protocol contracts as idempotent - #24227
Merged
PhilWindle merged 1 commit intoJun 23, 2026
Conversation
…empotent The archiver preloads bundled protocol contract classes and instances at synthetic block 0. When a bundled protocol class id (or instance address) was later published on chain, the contract stores threw "already exists" while re-adding the already-present key, stalling L1 sync. Make the stores idempotent for protocol-preloaded entries: - addContractClass / addContractInstance now skip (no-op) when the key already exists and it belongs to a bundled protocol contract, keeping the existing block-0 entry untouched. Genuine non-protocol redefinitions still throw. - deleteContractClass / deleteContractInstance never delete protocol entries, so they survive reorgs of the publishing block. Adds isProtocolContractClass to protocol-contracts as a sibling of isProtocolContract, plus store unit tests and an integration test that publishes a bundled protocol class id through ArchiverDataStoreUpdater. Fixes A-1257
Collaborator
Flakey Tests🤖 says: This CI run detected 2 tests that failed, but were tolerated due to a .test_patterns.yml entry. |
PhilWindle
approved these changes
Jun 23, 2026
Collaborator
|
❌ Failed to cherry-pick to |
spalladino
added a commit
that referenced
this pull request
Jun 23, 2026
…empotent (backport #24227) (#24232) ## Summary Backport of #24227 to the `v4` release line (base: `backport-to-v4-next-staging`). The archiver preloads bundled protocol contract classes/instances at synthetic block 0. A first on-chain `publish` of a bundled protocol class id is protocol-valid, but the store threw on the pre-existing block-0 key, aborting the block-store transaction and stalling L1 sync. This makes the archiver treat protocol-preloaded entries as idempotent and immutable at the store layer: - `addContractClass` / `addContractInstance`: re-publish of an already-present protocol class id / magic protocol address is a no-op, keeping the block-0 entry (block number not bumped). - `deleteContractClasses` / `deleteContractInstance`: protocol entries are never deleted, so a reorg of the publishing block cannot roll out the preload. - protocol-contracts: new `isProtocolContractClass(classId)` helper backed by the generated `ProtocolContractClassId` set. ## Conflict resolution The automatic cherry-pick of merge commit `24a0bc5` conflicted because the archiver store/test layout on v4 differs from `next`: - **`contract_class_store.ts`** — kept v4's import of `FunctionSelector` alongside the new `isProtocolContractClass` import, and kept v4's method name `deleteContractClasses` (callers on v4 use that name) while grafting in the protocol-delete guard. - **`contract_instance_store.ts` / `protocol_contract.ts`** — auto-merged; guards and the new helper applied cleanly. - **`data_store_updater.test.ts`** — the new A-1257 idempotency integration test was adapted to v4's `KVArchiverDataStore` harness: dropped `next`-only imports (`createArchiverDataStores`, `L2TipsCache`, `GENESIS_BLOCK_HEADER_HASH`, `CheckpointHeader`) and rewrote `store.contractClasses.getContractClass(...)` to v4's `store.getContractClass(...)`. - **`contract_class_store.test.ts` / `contract_instance_store.test.ts`** — these store-level unit test files do not exist on the v4 line (only `kv_archiver_store.test.ts` does) and the PR's versions use `next`'s array-based store API, so they were not introduced. The behavioral change is covered by the adapted integration test above. ## Verification The workspace was not bootstrapped (no `node_modules`/generated artifacts), so a full `yarn build` was not run here. Every import and store API call in the resolution was statically confirmed against `origin/v4-next` (`registerProtocolContracts`, `ContractClassLogFields`, `bufferAsFields`, `CONTRACT_CLASS_LOG_SIZE_IN_FIELDS`/`CONTRACT_CLASS_PUBLISHED_MAGIC_VALUE`, `BundledProtocolContractsProvider`, `ProtocolContractClassId`, `isProtocolContract`). CI on this PR provides the build/test signal. --- *Created by [claudebox](https://claudebox.work/v2/sessions/fe21ec2d87559552) · group: `slackbot`*
aminsammara
added a commit
that referenced
this pull request
Jun 26, 2026
## Summary - Bumps v4 to 4.4.0 (version manifest was already set in a prior commit) - Adds 4.4.0 and 4.3.1 changelog entries covering all commits since v4.3.1 - Includes bug fixes for archiver, world-state, p2p, release-image, and mac install ## Changes included in 4.4.0 - fix(archiver): harden v4 L2 tips cache refresh (#24237) - fix(world-state): backport archive root guard (#24240) - fix(archiver): treat re-publish of preloaded protocol contracts as idempotent (#24227) - fix(p2p): guard ENR address parsing against malformed TCP fields (#24215) - fix(archiver): index zero-field logs under empty tag instead of throwing (#24212) - fix: released contract artifact aztec version (#23470) - fix(release-image): stamp aztec_version in published contract artifacts - fix: some mac install fixes (#20630) - feat(ci): run aztec-cli acceptance test on macOS (#23309) - fix(ci): skip aztec-cli notify job when acceptance test is skipped (#23534) - docs(operators): expand ATP and ATV on first use (#23254)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
The archiver preloads every bundled protocol contract class (and its canonical instance) into its local store at synthetic block 0, before L1 sync. World-state genesis, however, seeds no registration nullifiers for those classes/instances. As a result a first on-chain
ContractClassRegistry.publishof a bundled protocol class id is protocol-valid (fresh class-id nullifier +ContractClassPublishedlog). On replay the archiver recomputes the same class id and unconditionally re-inserts it, but the store throws on the pre-existing block-0 key (Contract class <id> already exists, cannot add again). Because that insert runs inside the block/checkpoint store transaction, the throw aborts persistence, and L1 sync retries the same valid checkpoint indefinitely — a sync stall.Approach
Make the archiver treat protocol-preloaded entries as idempotent and immutable, guarded at the store layer (the single chokepoint for both the add-throw and the block-gated delete):
addContractClass/addContractInstance: when the key already exists and it is a protocol class id / magic protocol address, treat the (re-)publish as a no-op and keep the existing block-0 entry — crucially without bumping its recorded block number. Genuine non-protocol duplicates still throw unchanged.deleteContractClass/deleteContractInstance: skip deletion for protocol entries, so a reorg of the publishing block can never roll out the preload.Keeping the preload at block 0 is deliberate:
deleteContractClassonly deletes when the storedl2BlockNumber >= blockNumber, so block 0 makes protocol entries survive any reorg; bumping to the publish block would let a deep reorg delete them.The instance-side guards are defensive only: on-chain
publish_for_public_executionalways emits the derived address, never a magic address, so the instance store is not reached with a magic address via the on-chain replay path today — the guards exist for symmetry and to protect future code paths.This is a non-breaking, node-local resilience fix. A follow-up PR (PR2) seeds the protocol registration nullifiers into world-state genesis so the on-chain re-publish is rejected at the protocol level — the root-cause fix for the class path.
Changes
isProtocolContractClass(classId)(sibling of the existingisProtocolContract(address)), backed by a set of the generatedProtocolContractClassIdvalues.ContractClassStore/ContractInstanceStore.L2Blockcarrying aContractClassPublishedlog for a bundled class id, and assertsaddProposedBlockcommits and the class stays queryable.Fixes A-1257