Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion yarn-project/archiver/src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [],
Expand Down
10 changes: 7 additions & 3 deletions yarn-project/archiver/src/store/contract_class_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,15 @@ export class ContractClassStore {
blockNumber: number,
): Promise<void> {
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());
});
}

Expand Down
13 changes: 8 additions & 5 deletions yarn-project/archiver/src/store/contract_instance_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ export class ContractInstanceStore {

addContractInstance(contractInstance: ContractInstanceWithAddress, blockNumber: number): Promise<void> {
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);
});
}

Expand Down
41 changes: 14 additions & 27 deletions yarn-project/archiver/src/store/kv_archiver_store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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],
Expand All @@ -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 () => {
Expand Down
Loading