diff --git a/yarn-project/archiver/src/factory.ts b/yarn-project/archiver/src/factory.ts index 5aa14b3ca7a0..aaccab4346cd 100644 --- a/yarn-project/archiver/src/factory.ts +++ b/yarn-project/archiver/src/factory.ts @@ -173,12 +173,18 @@ export async function createArchiver( return archiver; } -/** Registers protocol contracts in the archiver store. */ +/** Registers protocol contracts in the archiver store. Idempotent — skips contracts that already exist (e.g. on node restart). */ export async function registerProtocolContracts(store: KVArchiverDataStore) { const blockNumber = 0; for (const name of protocolContractNames) { const provider = new BundledProtocolContractsProvider(); const contract = await provider.getProtocolContractArtifact(name); + + // Skip if already registered (happens on node restart with a persisted store). + if (await store.getContractClass(contract.contractClass.id)) { + continue; + } + const contractClassPublic: ContractClassPublic = { ...contract.contractClass, privateFunctions: [], diff --git a/yarn-project/archiver/src/store/contract_class_store.ts b/yarn-project/archiver/src/store/contract_class_store.ts index 36de477aad42..ebe0d6f9400f 100644 --- a/yarn-project/archiver/src/store/contract_class_store.ts +++ b/yarn-project/archiver/src/store/contract_class_store.ts @@ -29,11 +29,15 @@ export class ContractClassStore { blockNumber: number, ): Promise { await this.db.transactionAsync(async () => { - await this.#contractClasses.setIfNotExists( - contractClass.id.toString(), + const key = contractClass.id.toString(); + if (await this.#contractClasses.hasAsync(key)) { + throw new Error(`Contract class ${key} already exists, cannot add again at block ${blockNumber}`); + } + await this.#contractClasses.set( + key, serializeContractClassPublic({ ...contractClass, l2BlockNumber: blockNumber }), ); - await this.#bytecodeCommitments.setIfNotExists(contractClass.id.toString(), bytecodeCommitment.toBuffer()); + await this.#bytecodeCommitments.set(key, bytecodeCommitment.toBuffer()); }); } diff --git a/yarn-project/archiver/src/store/contract_instance_store.ts b/yarn-project/archiver/src/store/contract_instance_store.ts index 63ea37ff9bcb..332605240e04 100644 --- a/yarn-project/archiver/src/store/contract_instance_store.ts +++ b/yarn-project/archiver/src/store/contract_instance_store.ts @@ -27,11 +27,14 @@ export class ContractInstanceStore { addContractInstance(contractInstance: ContractInstanceWithAddress, blockNumber: number): Promise { return this.db.transactionAsync(async () => { - await this.#contractInstances.set( - contractInstance.address.toString(), - new SerializableContractInstance(contractInstance).toBuffer(), - ); - await this.#contractInstancePublishedAt.set(contractInstance.address.toString(), blockNumber); + const key = contractInstance.address.toString(); + if (await this.#contractInstances.hasAsync(key)) { + throw new Error( + `Contract instance at ${key} already exists (deployed at block ${await this.#contractInstancePublishedAt.getAsync(key)}), cannot add again at block ${blockNumber}`, + ); + } + await this.#contractInstances.set(key, new SerializableContractInstance(contractInstance).toBuffer()); + await this.#contractInstancePublishedAt.set(key, blockNumber); }); } diff --git a/yarn-project/archiver/src/store/kv_archiver_store.test.ts b/yarn-project/archiver/src/store/kv_archiver_store.test.ts index 92ae927f0ef3..bea6c6a6e81c 100644 --- a/yarn-project/archiver/src/store/kv_archiver_store.test.ts +++ b/yarn-project/archiver/src/store/kv_archiver_store.test.ts @@ -2367,14 +2367,14 @@ describe('KVArchiverDataStore', () => { await expect(store.getContractClass(contractClass.id)).resolves.toBeUndefined(); }); - it('returns contract class if later "deployment" class was deleted', async () => { - await store.addContractClasses( - [contractClass], - [await computePublicBytecodeCommitment(contractClass.packedBytecode)], - BlockNumber(blockNum + 1), - ); - await store.deleteContractClasses([contractClass], BlockNumber(blockNum + 1)); - await expect(store.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass); + it('throws if the same contract class is added again', async () => { + await expect( + store.addContractClasses( + [contractClass], + [await computePublicBytecodeCommitment(contractClass.packedBytecode)], + BlockNumber(blockNum + 1), + ), + ).rejects.toThrow(/already exists/); }); it('returns undefined if contract class is not found', async () => { @@ -3431,22 +3431,17 @@ describe('KVArchiverDataStore', () => { expect(storedBlock?.archive.root.equals(provisionalBlock.archive.root)).toBe(true); }); - it('does not throw when adding the same contract class twice', async () => { + it('throws when adding the same contract class twice', async () => { const contractClass = await makeContractClassPublic(); const commitment = await computePublicBytecodeCommitment(contractClass.packedBytecode); - // Add contract class first time await store.addContractClasses([contractClass], [commitment], BlockNumber(1)); - - // Add same contract class again - should not throw (uses setIfNotExists) - await store.addContractClasses([contractClass], [commitment], BlockNumber(2)); - - // Verify contract class exists - const retrieved = await store.getContractClass(contractClass.id); - expect(retrieved).toBeDefined(); + await expect(store.addContractClasses([contractClass], [commitment], BlockNumber(2))).rejects.toThrow( + /already exists/, + ); }); - it('does not throw when adding the same contract instance twice', async () => { + it('throws when adding the same contract instance twice', async () => { const contractClass = await makeContractClassPublic(); await store.addContractClasses( [contractClass], @@ -3462,16 +3457,8 @@ describe('KVArchiverDataStore', () => { address: await AztecAddress.random(), }; - // Add contract instance first time await store.addContractInstances([instance], BlockNumber(1)); - - // Add same contract instance again - should not throw (uses set) - await store.addContractInstances([instance], BlockNumber(2)); - - // Verify instance exists - const retrieved = await store.getContractInstance(instance.address, 1000n); - expect(retrieved).toBeDefined(); - expect(retrieved?.address.equals(instance.address)).toBe(true); + await expect(store.addContractInstances([instance], BlockNumber(2))).rejects.toThrow(/already exists/); }); it('does not duplicate logs when addLogs is called twice with same block', async () => {