@aztec/archiver 0.0.1-commit.3e3d0c9cd → 0.0.1-commit.3fd054f6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dest/archiver.d.ts +1 -2
  2. package/dest/archiver.d.ts.map +1 -1
  3. package/dest/archiver.js +21 -11
  4. package/dest/config.d.ts +3 -3
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +2 -1
  7. package/dest/factory.d.ts +2 -2
  8. package/dest/factory.d.ts.map +1 -1
  9. package/dest/factory.js +7 -6
  10. package/dest/modules/data_store_updater.d.ts +3 -6
  11. package/dest/modules/data_store_updater.d.ts.map +1 -1
  12. package/dest/modules/data_store_updater.js +47 -65
  13. package/dest/modules/l1_synchronizer.d.ts +1 -1
  14. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  15. package/dest/modules/l1_synchronizer.js +8 -5
  16. package/dest/store/contract_class_store.d.ts +2 -3
  17. package/dest/store/contract_class_store.d.ts.map +1 -1
  18. package/dest/store/contract_class_store.js +7 -67
  19. package/dest/store/contract_instance_store.d.ts +1 -1
  20. package/dest/store/contract_instance_store.d.ts.map +1 -1
  21. package/dest/store/contract_instance_store.js +6 -2
  22. package/dest/store/kv_archiver_store.d.ts +6 -9
  23. package/dest/store/kv_archiver_store.d.ts.map +1 -1
  24. package/dest/store/kv_archiver_store.js +4 -8
  25. package/dest/store/log_store.d.ts +1 -1
  26. package/dest/store/log_store.d.ts.map +1 -1
  27. package/dest/store/log_store.js +29 -9
  28. package/package.json +13 -13
  29. package/src/archiver.ts +24 -11
  30. package/src/config.ts +8 -1
  31. package/src/factory.ts +12 -7
  32. package/src/modules/data_store_updater.ts +54 -94
  33. package/src/modules/l1_synchronizer.ts +14 -10
  34. package/src/store/contract_class_store.ts +8 -106
  35. package/src/store/contract_instance_store.ts +8 -5
  36. package/src/store/kv_archiver_store.ts +5 -20
  37. package/src/store/log_store.ts +42 -11
@@ -1,11 +1,7 @@
1
1
  import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
2
- import { Fr } from '@aztec/foundation/curves/bn254';
2
+ import { filterAsync } from '@aztec/foundation/collection';
3
3
  import { createLogger } from '@aztec/foundation/log';
4
- import {
5
- ContractClassPublishedEvent,
6
- PrivateFunctionBroadcastedEvent,
7
- UtilityFunctionBroadcastedEvent,
8
- } from '@aztec/protocol-contracts/class-registry';
4
+ import { ContractClassPublishedEvent } from '@aztec/protocol-contracts/class-registry';
9
5
  import {
10
6
  ContractInstancePublishedEvent,
11
7
  ContractInstanceUpdatedEvent,
@@ -13,17 +9,13 @@ import {
13
9
  import type { L2Block, ValidateCheckpointResult } from '@aztec/stdlib/block';
14
10
  import { type PublishedCheckpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint';
15
11
  import {
16
- type ExecutablePrivateFunctionWithMembershipProof,
17
- type UtilityFunctionWithMembershipProof,
18
- computePublicBytecodeCommitment,
19
- isValidPrivateFunctionMembershipProof,
20
- isValidUtilityFunctionMembershipProof,
12
+ type ContractClassPublicWithCommitment,
13
+ computeContractAddressFromInstance,
14
+ computeContractClassId,
21
15
  } from '@aztec/stdlib/contract';
22
16
  import type { ContractClassLog, PrivateLog, PublicLog } from '@aztec/stdlib/logs';
23
17
  import type { UInt64 } from '@aztec/stdlib/types';
24
18
 
25
- import groupBy from 'lodash.groupby';
26
-
27
19
  import type { KVArchiverDataStore } from '../store/kv_archiver_store.js';
28
20
  import type { L2TipsCache } from '../store/l2_tips_cache.js';
29
21
 
@@ -54,8 +46,7 @@ export class ArchiverDataStoreUpdater {
54
46
  /**
55
47
  * Adds a proposed block to the store with contract class/instance extraction from logs.
56
48
  * This is an uncheckpointed block that has been proposed by the sequencer but not yet included in a checkpoint on L1.
57
- * Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events,
58
- * and individually broadcasted functions from the block logs.
49
+ * Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events from the block logs.
59
50
  *
60
51
  * @param block - The proposed L2 block to add.
61
52
  * @param pendingChainValidationStatus - Optional validation status to set.
@@ -87,8 +78,7 @@ export class ArchiverDataStoreUpdater {
87
78
  * Reconciles local blocks with incoming checkpoints from L1.
88
79
  * Adds new checkpoints to the store with contract class/instance extraction from logs.
89
80
  * Prunes any local blocks that conflict with checkpoint data (by comparing archive roots).
90
- * Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events,
91
- * and individually broadcasted functions from the checkpoint block logs.
81
+ * Extracts ContractClassPublished, ContractInstancePublished, ContractInstanceUpdated events from the checkpoint block logs.
92
82
  *
93
83
  * @param checkpoints - The published checkpoints to add.
94
84
  * @param pendingChainValidationStatus - Optional validation status to set.
@@ -313,9 +303,6 @@ export class ArchiverDataStoreUpdater {
313
303
  this.updatePublishedContractClasses(contractClassLogs, block.number, operation),
314
304
  this.updateDeployedContractInstances(privateLogs, block.number, operation),
315
305
  this.updateUpdatedContractInstances(publicLogs, block.header.globalVariables.timestamp, operation),
316
- operation === Operation.Store
317
- ? this.storeBroadcastedIndividualFunctions(contractClassLogs, block.number)
318
- : Promise.resolve(true),
319
306
  ])
320
307
  ).every(Boolean);
321
308
  }
@@ -332,18 +319,37 @@ export class ArchiverDataStoreUpdater {
332
319
  .filter(log => ContractClassPublishedEvent.isContractClassPublishedEvent(log))
333
320
  .map(log => ContractClassPublishedEvent.fromLog(log));
334
321
 
335
- const contractClasses = await Promise.all(contractClassPublishedEvents.map(e => e.toContractClassPublic()));
336
- if (contractClasses.length > 0) {
337
- contractClasses.forEach(c => this.log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`));
338
- if (operation == Operation.Store) {
339
- // TODO: Will probably want to create some worker threads to compute these bytecode commitments as they are expensive
340
- const commitments = await Promise.all(
341
- contractClasses.map(c => computePublicBytecodeCommitment(c.packedBytecode)),
342
- );
343
- return await this.store.addContractClasses(contractClasses, commitments, blockNum);
344
- } else if (operation == Operation.Delete) {
322
+ if (operation == Operation.Delete) {
323
+ const contractClasses = contractClassPublishedEvents.map(e => e.toContractClassPublic());
324
+ if (contractClasses.length > 0) {
325
+ contractClasses.forEach(c => this.log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`));
345
326
  return await this.store.deleteContractClasses(contractClasses, blockNum);
346
327
  }
328
+ return true;
329
+ }
330
+
331
+ // Compute bytecode commitments and validate class IDs in a single pass.
332
+ const contractClasses: ContractClassPublicWithCommitment[] = [];
333
+ for (const event of contractClassPublishedEvents) {
334
+ const contractClass = await event.toContractClassPublicWithBytecodeCommitment();
335
+ const computedClassId = await computeContractClassId({
336
+ artifactHash: contractClass.artifactHash,
337
+ privateFunctionsRoot: contractClass.privateFunctionsRoot,
338
+ publicBytecodeCommitment: contractClass.publicBytecodeCommitment,
339
+ });
340
+ if (!computedClassId.equals(contractClass.id)) {
341
+ this.log.warn(
342
+ `Skipping contract class with mismatched id at block ${blockNum}. Claimed ${contractClass.id}, computed ${computedClassId}`,
343
+ { blockNum, contractClassId: event.contractClassId.toString() },
344
+ );
345
+ continue;
346
+ }
347
+ contractClasses.push(contractClass);
348
+ }
349
+
350
+ if (contractClasses.length > 0) {
351
+ contractClasses.forEach(c => this.log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`));
352
+ return await this.store.addContractClasses(contractClasses, blockNum);
347
353
  }
348
354
  return true;
349
355
  }
@@ -356,10 +362,27 @@ export class ArchiverDataStoreUpdater {
356
362
  blockNum: BlockNumber,
357
363
  operation: Operation,
358
364
  ): Promise<boolean> {
359
- const contractInstances = allLogs
365
+ const allInstances = allLogs
360
366
  .filter(log => ContractInstancePublishedEvent.isContractInstancePublishedEvent(log))
361
367
  .map(log => ContractInstancePublishedEvent.fromLog(log))
362
368
  .map(e => e.toContractInstance());
369
+
370
+ // Verify that each instance's address matches the one derived from its fields if we're adding
371
+ const contractInstances =
372
+ operation === Operation.Delete
373
+ ? allInstances
374
+ : await filterAsync(allInstances, async instance => {
375
+ const computedAddress = await computeContractAddressFromInstance(instance);
376
+ if (!computedAddress.equals(instance.address)) {
377
+ this.log.warn(
378
+ `Found contract instance with mismatched address at block ${blockNum}. Claimed ${instance.address} but computed ${computedAddress}.`,
379
+ { instanceAddress: instance.address.toString(), computedAddress: computedAddress.toString(), blockNum },
380
+ );
381
+ return false;
382
+ }
383
+ return true;
384
+ });
385
+
363
386
  if (contractInstances.length > 0) {
364
387
  contractInstances.forEach(c =>
365
388
  this.log.verbose(`${Operation[operation]} contract instance at ${c.address.toString()}`),
@@ -398,67 +421,4 @@ export class ArchiverDataStoreUpdater {
398
421
  }
399
422
  return true;
400
423
  }
401
-
402
- /**
403
- * Stores the functions that were broadcasted individually.
404
- *
405
- * @dev Beware that there is not a delete variant of this, since they are added to contract classes
406
- * and will be deleted as part of the class if needed.
407
- */
408
- private async storeBroadcastedIndividualFunctions(
409
- allLogs: ContractClassLog[],
410
- _blockNum: BlockNumber,
411
- ): Promise<boolean> {
412
- // Filter out private and utility function broadcast events
413
- const privateFnEvents = allLogs
414
- .filter(log => PrivateFunctionBroadcastedEvent.isPrivateFunctionBroadcastedEvent(log))
415
- .map(log => PrivateFunctionBroadcastedEvent.fromLog(log));
416
- const utilityFnEvents = allLogs
417
- .filter(log => UtilityFunctionBroadcastedEvent.isUtilityFunctionBroadcastedEvent(log))
418
- .map(log => UtilityFunctionBroadcastedEvent.fromLog(log));
419
-
420
- // Group all events by contract class id
421
- for (const [classIdString, classEvents] of Object.entries(
422
- groupBy([...privateFnEvents, ...utilityFnEvents], e => e.contractClassId.toString()),
423
- )) {
424
- const contractClassId = Fr.fromHexString(classIdString);
425
- const contractClass = await this.store.getContractClass(contractClassId);
426
- if (!contractClass) {
427
- this.log.warn(`Skipping broadcasted functions as contract class ${contractClassId.toString()} was not found`);
428
- continue;
429
- }
430
-
431
- // Split private and utility functions, and filter out invalid ones
432
- const allFns = classEvents.map(e => e.toFunctionWithMembershipProof());
433
- const privateFns = allFns.filter(
434
- (fn): fn is ExecutablePrivateFunctionWithMembershipProof => 'utilityFunctionsTreeRoot' in fn,
435
- );
436
- const utilityFns = allFns.filter(
437
- (fn): fn is UtilityFunctionWithMembershipProof => 'privateFunctionsArtifactTreeRoot' in fn,
438
- );
439
-
440
- const privateFunctionsWithValidity = await Promise.all(
441
- privateFns.map(async fn => ({ fn, valid: await isValidPrivateFunctionMembershipProof(fn, contractClass) })),
442
- );
443
- const validPrivateFns = privateFunctionsWithValidity.filter(({ valid }) => valid).map(({ fn }) => fn);
444
- const utilityFunctionsWithValidity = await Promise.all(
445
- utilityFns.map(async fn => ({
446
- fn,
447
- valid: await isValidUtilityFunctionMembershipProof(fn, contractClass),
448
- })),
449
- );
450
- const validUtilityFns = utilityFunctionsWithValidity.filter(({ valid }) => valid).map(({ fn }) => fn);
451
- const validFnCount = validPrivateFns.length + validUtilityFns.length;
452
- if (validFnCount !== allFns.length) {
453
- this.log.warn(`Skipping ${allFns.length - validFnCount} invalid functions`);
454
- }
455
-
456
- // Store the functions in the contract class in a single operation
457
- if (validFnCount > 0) {
458
- this.log.verbose(`Storing ${validFnCount} functions for contract class ${contractClassId.toString()}`);
459
- }
460
- return await this.store.addFunctions(contractClassId, validPrivateFns, validUtilityFns);
461
- }
462
- return true;
463
- }
464
424
  }
@@ -3,6 +3,7 @@ import { EpochCache } from '@aztec/epoch-cache';
3
3
  import { InboxContract, RollupContract } from '@aztec/ethereum/contracts';
4
4
  import type { L1BlockId } from '@aztec/ethereum/l1-types';
5
5
  import type { ViemPublicClient, ViemPublicDebugClient } from '@aztec/ethereum/types';
6
+ import { asyncPool } from '@aztec/foundation/async-pool';
6
7
  import { maxBigint } from '@aztec/foundation/bigint';
7
8
  import { BlockNumber, CheckpointNumber, EpochNumber } from '@aztec/foundation/branded-types';
8
9
  import { Buffer32 } from '@aztec/foundation/buffer';
@@ -333,17 +334,20 @@ export class ArchiverL1Synchronizer implements Traceable {
333
334
 
334
335
  const checkpointsToUnwind = localPendingCheckpointNumber - provenCheckpointNumber;
335
336
 
336
- const checkpointPromises = Array.from({ length: checkpointsToUnwind })
337
- .fill(0)
338
- .map((_, i) => this.store.getCheckpointData(CheckpointNumber(i + pruneFrom)));
339
- const checkpoints = await Promise.all(checkpointPromises);
340
-
341
- const blockPromises = await Promise.all(
342
- checkpoints
343
- .filter(isDefined)
344
- .map(cp => this.store.getBlocksForCheckpoint(CheckpointNumber(cp.checkpointNumber))),
337
+ // Fetch checkpoints and blocks in bounded batches to avoid unbounded concurrent
338
+ // promises when the gap between local pending and proven checkpoint numbers is large.
339
+ const BATCH_SIZE = 10;
340
+ const indices = Array.from({ length: checkpointsToUnwind }, (_, i) => CheckpointNumber(i + pruneFrom));
341
+ const checkpoints = (await asyncPool(BATCH_SIZE, indices, idx => this.store.getCheckpointData(idx))).filter(
342
+ isDefined,
345
343
  );
346
- const newBlocks = blockPromises.filter(isDefined).flat();
344
+ const newBlocks = (
345
+ await asyncPool(BATCH_SIZE, checkpoints, cp =>
346
+ this.store.getBlocksForCheckpoint(CheckpointNumber(cp.checkpointNumber)),
347
+ )
348
+ )
349
+ .filter(isDefined)
350
+ .flat();
347
351
 
348
352
  // Emit an event for listening services to react to the chain prune
349
353
  this.events.emit(L2BlockSourceEvents.L2PruneUnproven, {
@@ -2,14 +2,7 @@ import { Fr } from '@aztec/foundation/curves/bn254';
2
2
  import { toArray } from '@aztec/foundation/iterable';
3
3
  import { BufferReader, numToUInt8, serializeToBuffer } from '@aztec/foundation/serialize';
4
4
  import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
5
- import { FunctionSelector } from '@aztec/stdlib/abi';
6
- import type {
7
- ContractClassPublic,
8
- ContractClassPublicWithBlockNumber,
9
- ExecutablePrivateFunctionWithMembershipProof,
10
- UtilityFunctionWithMembershipProof,
11
- } from '@aztec/stdlib/contract';
12
- import { Vector } from '@aztec/stdlib/types';
5
+ import type { ContractClassPublic, ContractClassPublicWithBlockNumber } from '@aztec/stdlib/contract';
13
6
 
14
7
  /**
15
8
  * LMDB-based contract class storage for the archiver.
@@ -29,11 +22,15 @@ export class ContractClassStore {
29
22
  blockNumber: number,
30
23
  ): Promise<void> {
31
24
  await this.db.transactionAsync(async () => {
32
- await this.#contractClasses.setIfNotExists(
33
- contractClass.id.toString(),
25
+ const key = contractClass.id.toString();
26
+ if (await this.#contractClasses.hasAsync(key)) {
27
+ throw new Error(`Contract class ${key} already exists, cannot add again at block ${blockNumber}`);
28
+ }
29
+ await this.#contractClasses.set(
30
+ key,
34
31
  serializeContractClassPublic({ ...contractClass, l2BlockNumber: blockNumber }),
35
32
  );
36
- await this.#bytecodeCommitments.setIfNotExists(contractClass.id.toString(), bytecodeCommitment.toBuffer());
33
+ await this.#bytecodeCommitments.set(key, bytecodeCommitment.toBuffer());
37
34
  });
38
35
  }
39
36
 
@@ -60,37 +57,6 @@ export class ContractClassStore {
60
57
  async getContractClassIds(): Promise<Fr[]> {
61
58
  return (await toArray(this.#contractClasses.keysAsync())).map(key => Fr.fromHexString(key));
62
59
  }
63
-
64
- async addFunctions(
65
- contractClassId: Fr,
66
- newPrivateFunctions: ExecutablePrivateFunctionWithMembershipProof[],
67
- newUtilityFunctions: UtilityFunctionWithMembershipProof[],
68
- ): Promise<boolean> {
69
- await this.db.transactionAsync(async () => {
70
- const existingClassBuffer = await this.#contractClasses.getAsync(contractClassId.toString());
71
- if (!existingClassBuffer) {
72
- throw new Error(`Unknown contract class ${contractClassId} when adding private functions to store`);
73
- }
74
-
75
- const existingClass = deserializeContractClassPublic(existingClassBuffer);
76
- const { privateFunctions: existingPrivateFns, utilityFunctions: existingUtilityFns } = existingClass;
77
-
78
- const updatedClass: Omit<ContractClassPublicWithBlockNumber, 'id'> = {
79
- ...existingClass,
80
- privateFunctions: [
81
- ...existingPrivateFns,
82
- ...newPrivateFunctions.filter(newFn => !existingPrivateFns.some(f => f.selector.equals(newFn.selector))),
83
- ],
84
- utilityFunctions: [
85
- ...existingUtilityFns,
86
- ...newUtilityFunctions.filter(newFn => !existingUtilityFns.some(f => f.selector.equals(newFn.selector))),
87
- ],
88
- };
89
- await this.#contractClasses.set(contractClassId.toString(), serializeContractClassPublic(updatedClass));
90
- });
91
-
92
- return true;
93
- }
94
60
  }
95
61
 
96
62
  function serializeContractClassPublic(contractClass: Omit<ContractClassPublicWithBlockNumber, 'id'>): Buffer {
@@ -98,83 +64,19 @@ function serializeContractClassPublic(contractClass: Omit<ContractClassPublicWit
98
64
  contractClass.l2BlockNumber,
99
65
  numToUInt8(contractClass.version),
100
66
  contractClass.artifactHash,
101
- contractClass.privateFunctions.length,
102
- contractClass.privateFunctions.map(serializePrivateFunction),
103
- contractClass.utilityFunctions.length,
104
- contractClass.utilityFunctions.map(serializeUtilityFunction),
105
67
  contractClass.packedBytecode.length,
106
68
  contractClass.packedBytecode,
107
69
  contractClass.privateFunctionsRoot,
108
70
  );
109
71
  }
110
72
 
111
- function serializePrivateFunction(fn: ExecutablePrivateFunctionWithMembershipProof): Buffer {
112
- return serializeToBuffer(
113
- fn.selector,
114
- fn.vkHash,
115
- fn.bytecode.length,
116
- fn.bytecode,
117
- fn.functionMetadataHash,
118
- fn.artifactMetadataHash,
119
- fn.utilityFunctionsTreeRoot,
120
- new Vector(fn.privateFunctionTreeSiblingPath),
121
- fn.privateFunctionTreeLeafIndex,
122
- new Vector(fn.artifactTreeSiblingPath),
123
- fn.artifactTreeLeafIndex,
124
- );
125
- }
126
-
127
- function serializeUtilityFunction(fn: UtilityFunctionWithMembershipProof): Buffer {
128
- return serializeToBuffer(
129
- fn.selector,
130
- fn.bytecode.length,
131
- fn.bytecode,
132
- fn.functionMetadataHash,
133
- fn.artifactMetadataHash,
134
- fn.privateFunctionsArtifactTreeRoot,
135
- new Vector(fn.artifactTreeSiblingPath),
136
- fn.artifactTreeLeafIndex,
137
- );
138
- }
139
-
140
73
  function deserializeContractClassPublic(buffer: Buffer): Omit<ContractClassPublicWithBlockNumber, 'id'> {
141
74
  const reader = BufferReader.asReader(buffer);
142
75
  return {
143
76
  l2BlockNumber: reader.readNumber(),
144
77
  version: reader.readUInt8() as 1,
145
78
  artifactHash: reader.readObject(Fr),
146
- privateFunctions: reader.readVector({ fromBuffer: deserializePrivateFunction }),
147
- utilityFunctions: reader.readVector({ fromBuffer: deserializeUtilityFunction }),
148
79
  packedBytecode: reader.readBuffer(),
149
80
  privateFunctionsRoot: reader.readObject(Fr),
150
81
  };
151
82
  }
152
-
153
- function deserializePrivateFunction(buffer: Buffer | BufferReader): ExecutablePrivateFunctionWithMembershipProof {
154
- const reader = BufferReader.asReader(buffer);
155
- return {
156
- selector: reader.readObject(FunctionSelector),
157
- vkHash: reader.readObject(Fr),
158
- bytecode: reader.readBuffer(),
159
- functionMetadataHash: reader.readObject(Fr),
160
- artifactMetadataHash: reader.readObject(Fr),
161
- utilityFunctionsTreeRoot: reader.readObject(Fr),
162
- privateFunctionTreeSiblingPath: reader.readVector(Fr),
163
- privateFunctionTreeLeafIndex: reader.readNumber(),
164
- artifactTreeSiblingPath: reader.readVector(Fr),
165
- artifactTreeLeafIndex: reader.readNumber(),
166
- };
167
- }
168
-
169
- function deserializeUtilityFunction(buffer: Buffer | BufferReader): UtilityFunctionWithMembershipProof {
170
- const reader = BufferReader.asReader(buffer);
171
- return {
172
- selector: reader.readObject(FunctionSelector),
173
- bytecode: reader.readBuffer(),
174
- functionMetadataHash: reader.readObject(Fr),
175
- artifactMetadataHash: reader.readObject(Fr),
176
- privateFunctionsArtifactTreeRoot: reader.readObject(Fr),
177
- artifactTreeSiblingPath: reader.readVector(Fr),
178
- artifactTreeLeafIndex: reader.readNumber(),
179
- };
180
- }
@@ -27,11 +27,14 @@ export class ContractInstanceStore {
27
27
 
28
28
  addContractInstance(contractInstance: ContractInstanceWithAddress, blockNumber: number): Promise<void> {
29
29
  return this.db.transactionAsync(async () => {
30
- await this.#contractInstances.set(
31
- contractInstance.address.toString(),
32
- new SerializableContractInstance(contractInstance).toBuffer(),
33
- );
34
- await this.#contractInstancePublishedAt.set(contractInstance.address.toString(), blockNumber);
30
+ const key = contractInstance.address.toString();
31
+ if (await this.#contractInstances.hasAsync(key)) {
32
+ throw new Error(
33
+ `Contract instance at ${key} already exists (deployed at block ${await this.#contractInstancePublishedAt.getAsync(key)}), cannot add again at block ${blockNumber}`,
34
+ );
35
+ }
36
+ await this.#contractInstances.set(key, new SerializableContractInstance(contractInstance).toBuffer());
37
+ await this.#contractInstancePublishedAt.set(key, blockNumber);
35
38
  });
36
39
  }
37
40
 
@@ -16,11 +16,10 @@ import {
16
16
  import type { CheckpointData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
17
17
  import type {
18
18
  ContractClassPublic,
19
+ ContractClassPublicWithCommitment,
19
20
  ContractDataSource,
20
21
  ContractInstanceUpdateWithAddress,
21
22
  ContractInstanceWithAddress,
22
- ExecutablePrivateFunctionWithMembershipProof,
23
- UtilityFunctionWithMembershipProof,
24
23
  } from '@aztec/stdlib/contract';
25
24
  import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
26
25
  import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client';
@@ -37,7 +36,7 @@ import { ContractInstanceStore } from './contract_instance_store.js';
37
36
  import { LogStore } from './log_store.js';
38
37
  import { MessageStore } from './message_store.js';
39
38
 
40
- export const ARCHIVER_DB_VERSION = 5;
39
+ export const ARCHIVER_DB_VERSION = 6;
41
40
  export const MAX_FUNCTION_SIGNATURES = 1000;
42
41
  export const MAX_FUNCTION_NAME_LEN = 256;
43
42
 
@@ -167,19 +166,14 @@ export class KVArchiverDataStore implements ContractDataSource {
167
166
 
168
167
  /**
169
168
  * Add new contract classes from an L2 block to the store's list.
170
- * @param data - List of contract classes to be added.
171
- * @param bytecodeCommitments - Bytecode commitments for the contract classes.
169
+ * @param data - List of contract classes (with bytecode commitments) to be added.
172
170
  * @param blockNumber - Number of the L2 block the contracts were registered in.
173
171
  * @returns True if the operation is successful.
174
172
  */
175
- async addContractClasses(
176
- data: ContractClassPublic[],
177
- bytecodeCommitments: Fr[],
178
- blockNumber: BlockNumber,
179
- ): Promise<boolean> {
173
+ async addContractClasses(data: ContractClassPublicWithCommitment[], blockNumber: BlockNumber): Promise<boolean> {
180
174
  return (
181
175
  await Promise.all(
182
- data.map((c, i) => this.#contractClassStore.addContractClass(c, bytecodeCommitments[i], blockNumber)),
176
+ data.map(c => this.#contractClassStore.addContractClass(c, c.publicBytecodeCommitment, blockNumber)),
183
177
  )
184
178
  ).every(Boolean);
185
179
  }
@@ -194,15 +188,6 @@ export class KVArchiverDataStore implements ContractDataSource {
194
188
  return this.#contractClassStore.getBytecodeCommitment(contractClassId);
195
189
  }
196
190
 
197
- /** Adds private functions to a contract class. */
198
- addFunctions(
199
- contractClassId: Fr,
200
- privateFunctions: ExecutablePrivateFunctionWithMembershipProof[],
201
- utilityFunctions: UtilityFunctionWithMembershipProof[],
202
- ): Promise<boolean> {
203
- return this.#contractClassStore.addFunctions(contractClassId, privateFunctions, utilityFunctions);
204
- }
205
-
206
191
  /**
207
192
  * Add new contract instances from an L2 block to the store's list.
208
193
  * @param data - List of contract instances to be added.
@@ -1,6 +1,6 @@
1
1
  import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
2
2
  import { BlockNumber } from '@aztec/foundation/branded-types';
3
- import { filterAsync } from '@aztec/foundation/collection';
3
+ import { compactArray, filterAsync } from '@aztec/foundation/collection';
4
4
  import { Fr } from '@aztec/foundation/curves/bn254';
5
5
  import { createLogger } from '@aztec/foundation/log';
6
6
  import { BufferReader, numToUInt32BE } from '@aztec/foundation/serialize';
@@ -313,18 +313,49 @@ export class LogStore {
313
313
 
314
314
  deleteLogs(blocks: L2Block[]): Promise<boolean> {
315
315
  return this.db.transactionAsync(async () => {
316
- await Promise.all(
317
- blocks.map(async block => {
318
- // Delete private logs
319
- const privateKeys = (await this.#privateLogKeysByBlock.getAsync(block.number)) ?? [];
320
- await Promise.all(privateKeys.map(tag => this.#privateLogsByTag.delete(tag)));
321
-
322
- // Delete public logs
323
- const publicKeys = (await this.#publicLogKeysByBlock.getAsync(block.number)) ?? [];
324
- await Promise.all(publicKeys.map(key => this.#publicLogsByContractAndTag.delete(key)));
325
- }),
316
+ const blockNumbers = new Set(blocks.map(block => block.number));
317
+ const firstBlockToDelete = Math.min(...blockNumbers);
318
+
319
+ // Collect all unique private tags across all blocks being deleted
320
+ const allPrivateTags = new Set(
321
+ compactArray(await Promise.all(blocks.map(block => this.#privateLogKeysByBlock.getAsync(block.number)))).flat(),
322
+ );
323
+
324
+ // Trim private logs: for each tag, delete all instances including and after the first block being deleted.
325
+ // This hinges on the invariant that logs for a given tag are always inserted in order of block number, which is enforced in #addPrivateLogs.
326
+ for (const tag of allPrivateTags) {
327
+ const existing = await this.#privateLogsByTag.getAsync(tag);
328
+ if (existing === undefined || existing.length === 0) {
329
+ continue;
330
+ }
331
+ const lastIndexToKeep = existing.findLastIndex(
332
+ buf => TxScopedL2Log.getBlockNumberFromBuffer(buf) < firstBlockToDelete,
333
+ );
334
+ const remaining = existing.slice(0, lastIndexToKeep + 1);
335
+ await (remaining.length > 0 ? this.#privateLogsByTag.set(tag, remaining) : this.#privateLogsByTag.delete(tag));
336
+ }
337
+
338
+ // Collect all unique public keys across all blocks being deleted
339
+ const allPublicKeys = new Set(
340
+ compactArray(await Promise.all(blocks.map(block => this.#publicLogKeysByBlock.getAsync(block.number)))).flat(),
326
341
  );
327
342
 
343
+ // And do the same as we did with private logs
344
+ for (const key of allPublicKeys) {
345
+ const existing = await this.#publicLogsByContractAndTag.getAsync(key);
346
+ if (existing === undefined || existing.length === 0) {
347
+ continue;
348
+ }
349
+ const lastIndexToKeep = existing.findLastIndex(
350
+ buf => TxScopedL2Log.getBlockNumberFromBuffer(buf) < firstBlockToDelete,
351
+ );
352
+ const remaining = existing.slice(0, lastIndexToKeep + 1);
353
+ await (remaining.length > 0
354
+ ? this.#publicLogsByContractAndTag.set(key, remaining)
355
+ : this.#publicLogsByContractAndTag.delete(key));
356
+ }
357
+
358
+ // After trimming the tagged logs, we can delete the block-level keys that track which tags are in which blocks.
328
359
  await Promise.all(
329
360
  blocks.map(block =>
330
361
  Promise.all([