@aztec/archiver 0.0.1-commit.343b43af6 → 0.0.1-commit.35158ae7e

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 (61) 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/errors.d.ts +15 -1
  8. package/dest/errors.d.ts.map +1 -1
  9. package/dest/errors.js +18 -0
  10. package/dest/factory.d.ts +2 -2
  11. package/dest/factory.d.ts.map +1 -1
  12. package/dest/factory.js +16 -13
  13. package/dest/modules/data_source_base.d.ts +3 -3
  14. package/dest/modules/data_source_base.d.ts.map +1 -1
  15. package/dest/modules/data_source_base.js +5 -5
  16. package/dest/modules/data_store_updater.d.ts +3 -6
  17. package/dest/modules/data_store_updater.d.ts.map +1 -1
  18. package/dest/modules/data_store_updater.js +47 -65
  19. package/dest/modules/l1_synchronizer.d.ts +1 -1
  20. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  21. package/dest/modules/l1_synchronizer.js +9 -5
  22. package/dest/store/block_store.d.ts +3 -2
  23. package/dest/store/block_store.d.ts.map +1 -1
  24. package/dest/store/block_store.js +7 -4
  25. package/dest/store/contract_class_store.d.ts +2 -3
  26. package/dest/store/contract_class_store.d.ts.map +1 -1
  27. package/dest/store/contract_class_store.js +7 -67
  28. package/dest/store/contract_instance_store.d.ts +1 -1
  29. package/dest/store/contract_instance_store.d.ts.map +1 -1
  30. package/dest/store/contract_instance_store.js +6 -2
  31. package/dest/store/kv_archiver_store.d.ts +14 -12
  32. package/dest/store/kv_archiver_store.d.ts.map +1 -1
  33. package/dest/store/kv_archiver_store.js +15 -14
  34. package/dest/store/log_store.d.ts +6 -3
  35. package/dest/store/log_store.d.ts.map +1 -1
  36. package/dest/store/log_store.js +93 -16
  37. package/dest/store/message_store.d.ts +5 -1
  38. package/dest/store/message_store.d.ts.map +1 -1
  39. package/dest/store/message_store.js +13 -0
  40. package/dest/test/fake_l1_state.d.ts +1 -1
  41. package/dest/test/fake_l1_state.d.ts.map +1 -1
  42. package/dest/test/fake_l1_state.js +11 -3
  43. package/dest/test/mock_l2_block_source.d.ts +1 -1
  44. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  45. package/dest/test/mock_l2_block_source.js +2 -2
  46. package/package.json +13 -13
  47. package/src/archiver.ts +24 -11
  48. package/src/config.ts +8 -1
  49. package/src/errors.ts +30 -0
  50. package/src/factory.ts +17 -10
  51. package/src/modules/data_source_base.ts +9 -4
  52. package/src/modules/data_store_updater.ts +54 -94
  53. package/src/modules/l1_synchronizer.ts +15 -10
  54. package/src/store/block_store.ts +11 -2
  55. package/src/store/contract_class_store.ts +8 -106
  56. package/src/store/contract_instance_store.ts +8 -5
  57. package/src/store/kv_archiver_store.ts +22 -25
  58. package/src/store/log_store.ts +126 -27
  59. package/src/store/message_store.ts +19 -0
  60. package/src/test/fake_l1_state.ts +15 -5
  61. package/src/test/mock_l2_block_source.ts +7 -1
@@ -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, {
@@ -391,6 +395,7 @@ export class ArchiverL1Synchronizer implements Traceable {
391
395
  const localMessagesInserted = await this.store.getTotalL1ToL2MessageCount();
392
396
  const localLastMessage = await this.store.getLastL1ToL2Message();
393
397
  const remoteMessagesState = await this.inbox.getState({ blockNumber: currentL1BlockNumber });
398
+ await this.store.setInboxTreeInProgress(remoteMessagesState.treeInProgress);
394
399
 
395
400
  this.log.trace(`Retrieved remote inbox state at L1 block ${currentL1BlockNumber}.`, {
396
401
  localMessagesInserted,
@@ -20,6 +20,7 @@ import {
20
20
  serializeValidateCheckpointResult,
21
21
  } from '@aztec/stdlib/block';
22
22
  import { type CheckpointData, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
23
+ import { type L1RollupConstants, getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
23
24
  import { CheckpointHeader } from '@aztec/stdlib/rollup';
24
25
  import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
25
26
  import {
@@ -851,7 +852,10 @@ export class BlockStore {
851
852
  * @param txHash - The hash of a tx we try to get the receipt for.
852
853
  * @returns The requested tx receipt (or undefined if not found).
853
854
  */
854
- async getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
855
+ async getSettledTxReceipt(
856
+ txHash: TxHash,
857
+ l1Constants?: Pick<L1RollupConstants, 'epochDuration'>,
858
+ ): Promise<TxReceipt | undefined> {
855
859
  const txEffect = await this.getTxEffect(txHash);
856
860
  if (!txEffect) {
857
861
  return undefined;
@@ -860,10 +864,11 @@ export class BlockStore {
860
864
  const blockNumber = BlockNumber(txEffect.l2BlockNumber);
861
865
 
862
866
  // Use existing archiver methods to determine finalization level
863
- const [provenBlockNumber, checkpointedBlockNumber, finalizedBlockNumber] = await Promise.all([
867
+ const [provenBlockNumber, checkpointedBlockNumber, finalizedBlockNumber, blockData] = await Promise.all([
864
868
  this.getProvenBlockNumber(),
865
869
  this.getCheckpointedL2BlockNumber(),
866
870
  this.getFinalizedL2BlockNumber(),
871
+ this.getBlockData(blockNumber),
867
872
  ]);
868
873
 
869
874
  let status: TxStatus;
@@ -877,6 +882,9 @@ export class BlockStore {
877
882
  status = TxStatus.PROPOSED;
878
883
  }
879
884
 
885
+ const epochNumber =
886
+ blockData && l1Constants ? getEpochAtSlot(blockData.header.globalVariables.slotNumber, l1Constants) : undefined;
887
+
880
888
  return new TxReceipt(
881
889
  txHash,
882
890
  status,
@@ -885,6 +893,7 @@ export class BlockStore {
885
893
  txEffect.data.transactionFee.toBigInt(),
886
894
  txEffect.l2BlockHash,
887
895
  blockNumber,
896
+ epochNumber,
888
897
  );
889
898
  }
890
899
 
@@ -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,12 +16,12 @@ 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';
24
+ import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
25
25
  import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client';
26
26
  import type { LogFilter, SiloedTag, Tag, TxScopedL2Log } from '@aztec/stdlib/logs';
27
27
  import type { BlockHeader, TxHash, TxReceipt } from '@aztec/stdlib/tx';
@@ -36,7 +36,7 @@ import { ContractInstanceStore } from './contract_instance_store.js';
36
36
  import { LogStore } from './log_store.js';
37
37
  import { MessageStore } from './message_store.js';
38
38
 
39
- export const ARCHIVER_DB_VERSION = 5;
39
+ export const ARCHIVER_DB_VERSION = 6;
40
40
  export const MAX_FUNCTION_SIGNATURES = 1000;
41
41
  export const MAX_FUNCTION_NAME_LEN = 256;
42
42
 
@@ -166,19 +166,14 @@ export class KVArchiverDataStore implements ContractDataSource {
166
166
 
167
167
  /**
168
168
  * Add new contract classes from an L2 block to the store's list.
169
- * @param data - List of contract classes to be added.
170
- * @param bytecodeCommitments - Bytecode commitments for the contract classes.
169
+ * @param data - List of contract classes (with bytecode commitments) to be added.
171
170
  * @param blockNumber - Number of the L2 block the contracts were registered in.
172
171
  * @returns True if the operation is successful.
173
172
  */
174
- async addContractClasses(
175
- data: ContractClassPublic[],
176
- bytecodeCommitments: Fr[],
177
- blockNumber: BlockNumber,
178
- ): Promise<boolean> {
173
+ async addContractClasses(data: ContractClassPublicWithCommitment[], blockNumber: BlockNumber): Promise<boolean> {
179
174
  return (
180
175
  await Promise.all(
181
- data.map((c, i) => this.#contractClassStore.addContractClass(c, bytecodeCommitments[i], blockNumber)),
176
+ data.map(c => this.#contractClassStore.addContractClass(c, c.publicBytecodeCommitment, blockNumber)),
182
177
  )
183
178
  ).every(Boolean);
184
179
  }
@@ -193,15 +188,6 @@ export class KVArchiverDataStore implements ContractDataSource {
193
188
  return this.#contractClassStore.getBytecodeCommitment(contractClassId);
194
189
  }
195
190
 
196
- /** Adds private functions to a contract class. */
197
- addFunctions(
198
- contractClassId: Fr,
199
- privateFunctions: ExecutablePrivateFunctionWithMembershipProof[],
200
- utilityFunctions: UtilityFunctionWithMembershipProof[],
201
- ): Promise<boolean> {
202
- return this.#contractClassStore.addFunctions(contractClassId, privateFunctions, utilityFunctions);
203
- }
204
-
205
191
  /**
206
192
  * Add new contract instances from an L2 block to the store's list.
207
193
  * @param data - List of contract instances to be added.
@@ -408,8 +394,11 @@ export class KVArchiverDataStore implements ContractDataSource {
408
394
  * @param txHash - The hash of a tx we try to get the receipt for.
409
395
  * @returns The requested tx receipt (or undefined if not found).
410
396
  */
411
- getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
412
- return this.#blockStore.getSettledTxReceipt(txHash);
397
+ getSettledTxReceipt(
398
+ txHash: TxHash,
399
+ l1Constants?: Pick<L1RollupConstants, 'epochDuration'>,
400
+ ): Promise<TxReceipt | undefined> {
401
+ return this.#blockStore.getSettledTxReceipt(txHash, l1Constants);
413
402
  }
414
403
 
415
404
  /**
@@ -470,10 +459,11 @@ export class KVArchiverDataStore implements ContractDataSource {
470
459
  * array implies no logs match that tag.
471
460
  * @param tags - The tags to search for.
472
461
  * @param page - The page number (0-indexed) for pagination. Returns at most 10 logs per tag per page.
462
+ * @param upToBlockNumber - If set, only return logs from blocks up to and including this block number.
473
463
  */
474
- getPrivateLogsByTags(tags: SiloedTag[], page?: number): Promise<TxScopedL2Log[][]> {
464
+ getPrivateLogsByTags(tags: SiloedTag[], page?: number, upToBlockNumber?: BlockNumber): Promise<TxScopedL2Log[][]> {
475
465
  try {
476
- return this.#logStore.getPrivateLogsByTags(tags, page);
466
+ return this.#logStore.getPrivateLogsByTags(tags, page, upToBlockNumber);
477
467
  } catch (err) {
478
468
  return Promise.reject(err);
479
469
  }
@@ -485,14 +475,16 @@ export class KVArchiverDataStore implements ContractDataSource {
485
475
  * @param contractAddress - The contract address to search logs for.
486
476
  * @param tags - The tags to search for.
487
477
  * @param page - The page number (0-indexed) for pagination. Returns at most 10 logs per tag per page.
478
+ * @param upToBlockNumber - If set, only return logs from blocks up to and including this block number.
488
479
  */
489
480
  getPublicLogsByTagsFromContract(
490
481
  contractAddress: AztecAddress,
491
482
  tags: Tag[],
492
483
  page?: number,
484
+ upToBlockNumber?: BlockNumber,
493
485
  ): Promise<TxScopedL2Log[][]> {
494
486
  try {
495
- return this.#logStore.getPublicLogsByTagsFromContract(contractAddress, tags, page);
487
+ return this.#logStore.getPublicLogsByTagsFromContract(contractAddress, tags, page, upToBlockNumber);
496
488
  } catch (err) {
497
489
  return Promise.reject(err);
498
490
  }
@@ -599,6 +591,11 @@ export class KVArchiverDataStore implements ContractDataSource {
599
591
  return this.#messageStore.rollbackL1ToL2MessagesToCheckpoint(targetCheckpointNumber);
600
592
  }
601
593
 
594
+ /** Persists the inbox tree-in-progress checkpoint number from L1 state. */
595
+ public setInboxTreeInProgress(value: bigint): Promise<void> {
596
+ return this.#messageStore.setInboxTreeInProgress(value);
597
+ }
598
+
602
599
  /** Returns an async iterator to all L1 to L2 messages on the range. */
603
600
  public iterateL1ToL2Messages(range: CustomRange<bigint> = {}): AsyncIterableIterator<InboxMessage> {
604
601
  return this.#messageStore.iterateL1ToL2Messages(range);
@@ -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';
@@ -22,6 +22,7 @@ import {
22
22
  } from '@aztec/stdlib/logs';
23
23
  import { TxHash } from '@aztec/stdlib/tx';
24
24
 
25
+ import { OutOfOrderLogInsertionError } from '../errors.js';
25
26
  import type { BlockStore } from './block_store.js';
26
27
 
27
28
  /**
@@ -165,10 +166,21 @@ export class LogStore {
165
166
 
166
167
  for (const taggedLogBuffer of currentPrivateTaggedLogs) {
167
168
  if (taggedLogBuffer.logBuffers && taggedLogBuffer.logBuffers.length > 0) {
168
- privateTaggedLogs.set(
169
- taggedLogBuffer.tag,
170
- taggedLogBuffer.logBuffers!.concat(privateTaggedLogs.get(taggedLogBuffer.tag)!),
171
- );
169
+ const newLogs = privateTaggedLogs.get(taggedLogBuffer.tag)!;
170
+ if (newLogs.length === 0) {
171
+ continue;
172
+ }
173
+ const lastExisting = TxScopedL2Log.fromBuffer(taggedLogBuffer.logBuffers.at(-1)!);
174
+ const firstNew = TxScopedL2Log.fromBuffer(newLogs[0]);
175
+ if (lastExisting.blockNumber > firstNew.blockNumber) {
176
+ throw new OutOfOrderLogInsertionError(
177
+ 'private',
178
+ taggedLogBuffer.tag,
179
+ lastExisting.blockNumber,
180
+ firstNew.blockNumber,
181
+ );
182
+ }
183
+ privateTaggedLogs.set(taggedLogBuffer.tag, taggedLogBuffer.logBuffers.concat(newLogs));
172
184
  }
173
185
  }
174
186
 
@@ -200,10 +212,21 @@ export class LogStore {
200
212
 
201
213
  for (const taggedLogBuffer of currentPublicTaggedLogs) {
202
214
  if (taggedLogBuffer.logBuffers && taggedLogBuffer.logBuffers.length > 0) {
203
- publicTaggedLogs.set(
204
- taggedLogBuffer.tag,
205
- taggedLogBuffer.logBuffers!.concat(publicTaggedLogs.get(taggedLogBuffer.tag)!),
206
- );
215
+ const newLogs = publicTaggedLogs.get(taggedLogBuffer.tag)!;
216
+ if (newLogs.length === 0) {
217
+ continue;
218
+ }
219
+ const lastExisting = TxScopedL2Log.fromBuffer(taggedLogBuffer.logBuffers.at(-1)!);
220
+ const firstNew = TxScopedL2Log.fromBuffer(newLogs[0]);
221
+ if (lastExisting.blockNumber > firstNew.blockNumber) {
222
+ throw new OutOfOrderLogInsertionError(
223
+ 'public',
224
+ taggedLogBuffer.tag,
225
+ lastExisting.blockNumber,
226
+ firstNew.blockNumber,
227
+ );
228
+ }
229
+ publicTaggedLogs.set(taggedLogBuffer.tag, taggedLogBuffer.logBuffers.concat(newLogs));
207
230
  }
208
231
  }
209
232
 
@@ -290,18 +313,49 @@ export class LogStore {
290
313
 
291
314
  deleteLogs(blocks: L2Block[]): Promise<boolean> {
292
315
  return this.db.transactionAsync(async () => {
293
- await Promise.all(
294
- blocks.map(async block => {
295
- // Delete private logs
296
- const privateKeys = (await this.#privateLogKeysByBlock.getAsync(block.number)) ?? [];
297
- await Promise.all(privateKeys.map(tag => this.#privateLogsByTag.delete(tag)));
298
-
299
- // Delete public logs
300
- const publicKeys = (await this.#publicLogKeysByBlock.getAsync(block.number)) ?? [];
301
- await Promise.all(publicKeys.map(key => this.#publicLogsByContractAndTag.delete(key)));
302
- }),
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(),
303
322
  );
304
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(),
341
+ );
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.
305
359
  await Promise.all(
306
360
  blocks.map(block =>
307
361
  Promise.all([
@@ -322,17 +376,30 @@ export class LogStore {
322
376
  * array implies no logs match that tag.
323
377
  * @param tags - The tags to search for.
324
378
  * @param page - The page number (0-indexed) for pagination.
379
+ * @param upToBlockNumber - If set, only return logs from blocks up to and including this block number.
325
380
  * @returns An array of log arrays, one per tag. Returns at most MAX_LOGS_PER_TAG logs per tag per page. If
326
381
  * MAX_LOGS_PER_TAG logs are returned for a tag, the caller should fetch the next page to check for more logs.
327
382
  */
328
- async getPrivateLogsByTags(tags: SiloedTag[], page: number = 0): Promise<TxScopedL2Log[][]> {
383
+ async getPrivateLogsByTags(
384
+ tags: SiloedTag[],
385
+ page: number = 0,
386
+ upToBlockNumber?: BlockNumber,
387
+ ): Promise<TxScopedL2Log[][]> {
329
388
  const logs = await Promise.all(tags.map(tag => this.#privateLogsByTag.getAsync(tag.toString())));
389
+
330
390
  const start = page * MAX_LOGS_PER_TAG;
331
391
  const end = start + MAX_LOGS_PER_TAG;
332
392
 
333
- return logs.map(
334
- logBuffers => logBuffers?.slice(start, end).map(logBuffer => TxScopedL2Log.fromBuffer(logBuffer)) ?? [],
335
- );
393
+ return logs.map(logBuffers => {
394
+ const deserialized = logBuffers?.slice(start, end).map(buf => TxScopedL2Log.fromBuffer(buf)) ?? [];
395
+ if (upToBlockNumber !== undefined) {
396
+ const cutoff = deserialized.findIndex(log => log.blockNumber > upToBlockNumber);
397
+ if (cutoff !== -1) {
398
+ return deserialized.slice(0, cutoff);
399
+ }
400
+ }
401
+ return deserialized;
402
+ });
336
403
  }
337
404
 
338
405
  /**
@@ -341,6 +408,7 @@ export class LogStore {
341
408
  * @param contractAddress - The contract address to search logs for.
342
409
  * @param tags - The tags to search for.
343
410
  * @param page - The page number (0-indexed) for pagination.
411
+ * @param upToBlockNumber - If set, only return logs from blocks up to and including this block number.
344
412
  * @returns An array of log arrays, one per tag. Returns at most MAX_LOGS_PER_TAG logs per tag per page. If
345
413
  * MAX_LOGS_PER_TAG logs are returned for a tag, the caller should fetch the next page to check for more logs.
346
414
  */
@@ -348,6 +416,7 @@ export class LogStore {
348
416
  contractAddress: AztecAddress,
349
417
  tags: Tag[],
350
418
  page: number = 0,
419
+ upToBlockNumber?: BlockNumber,
351
420
  ): Promise<TxScopedL2Log[][]> {
352
421
  const logs = await Promise.all(
353
422
  tags.map(tag => {
@@ -358,9 +427,16 @@ export class LogStore {
358
427
  const start = page * MAX_LOGS_PER_TAG;
359
428
  const end = start + MAX_LOGS_PER_TAG;
360
429
 
361
- return logs.map(
362
- logBuffers => logBuffers?.slice(start, end).map(logBuffer => TxScopedL2Log.fromBuffer(logBuffer)) ?? [],
363
- );
430
+ return logs.map(logBuffers => {
431
+ const deserialized = logBuffers?.slice(start, end).map(buf => TxScopedL2Log.fromBuffer(buf)) ?? [];
432
+ if (upToBlockNumber !== undefined) {
433
+ const cutoff = deserialized.findIndex(log => log.blockNumber > upToBlockNumber);
434
+ if (cutoff !== -1) {
435
+ return deserialized.slice(0, cutoff);
436
+ }
437
+ }
438
+ return deserialized;
439
+ });
364
440
  }
365
441
 
366
442
  /**
@@ -588,11 +664,24 @@ export class LogStore {
588
664
  txLogs: PublicLog[],
589
665
  filter: LogFilter = {},
590
666
  ): boolean {
667
+ if (filter.fromBlock && blockNumber < filter.fromBlock) {
668
+ return false;
669
+ }
670
+ if (filter.toBlock && blockNumber >= filter.toBlock) {
671
+ return false;
672
+ }
673
+ if (filter.txHash && !txHash.equals(filter.txHash)) {
674
+ return false;
675
+ }
676
+
591
677
  let maxLogsHit = false;
592
678
  let logIndex = typeof filter.afterLog?.logIndex === 'number' ? filter.afterLog.logIndex + 1 : 0;
593
679
  for (; logIndex < txLogs.length; logIndex++) {
594
680
  const log = txLogs[logIndex];
595
- if (!filter.contractAddress || log.contractAddress.equals(filter.contractAddress)) {
681
+ if (
682
+ (!filter.contractAddress || log.contractAddress.equals(filter.contractAddress)) &&
683
+ (!filter.tag || log.fields[0]?.equals(filter.tag))
684
+ ) {
596
685
  results.push(
597
686
  new ExtendedPublicLog(new LogId(BlockNumber(blockNumber), blockHash, txHash, txIndex, logIndex), log),
598
687
  );
@@ -616,6 +705,16 @@ export class LogStore {
616
705
  txLogs: ContractClassLog[],
617
706
  filter: LogFilter = {},
618
707
  ): boolean {
708
+ if (filter.fromBlock && blockNumber < filter.fromBlock) {
709
+ return false;
710
+ }
711
+ if (filter.toBlock && blockNumber >= filter.toBlock) {
712
+ return false;
713
+ }
714
+ if (filter.txHash && !txHash.equals(filter.txHash)) {
715
+ return false;
716
+ }
717
+
619
718
  let maxLogsHit = false;
620
719
  let logIndex = typeof filter.afterLog?.logIndex === 'number' ? filter.afterLog.logIndex + 1 : 0;
621
720
  for (; logIndex < txLogs.length; logIndex++) {