@aztec/archiver 3.0.0-nightly.20251223 → 3.0.0-nightly.20251225

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.
@@ -4,6 +4,7 @@ import { Fr } from '@aztec/foundation/curves/bn254';
4
4
  import { createLogger } from '@aztec/foundation/log';
5
5
  import { BufferReader, numToUInt32BE } from '@aztec/foundation/serialize';
6
6
  import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
7
+ import type { AztecAddress } from '@aztec/stdlib/aztec-address';
7
8
  import { L2BlockHash, L2BlockNew } from '@aztec/stdlib/block';
8
9
  import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client';
9
10
  import {
@@ -13,6 +14,8 @@ import {
13
14
  type LogFilter,
14
15
  LogId,
15
16
  PublicLog,
17
+ type SiloedTag,
18
+ Tag,
16
19
  TxScopedL2Log,
17
20
  } from '@aztec/stdlib/logs';
18
21
 
@@ -22,8 +25,12 @@ import type { BlockStore } from './block_store.js';
22
25
  * A store for logs
23
26
  */
24
27
  export class LogStore {
25
- #logsByTag: AztecAsyncMap<string, Buffer[]>;
26
- #logTagsByBlock: AztecAsyncMap<number, string[]>;
28
+ // `tag` --> private logs
29
+ #privateLogsByTag: AztecAsyncMap<string, Buffer[]>;
30
+ // `{contractAddress}_${tag}` --> public logs
31
+ #publicLogsByContractAndTag: AztecAsyncMap<string, Buffer[]>;
32
+ #privateLogKeysByBlock: AztecAsyncMap<number, string[]>;
33
+ #publicLogKeysByBlock: AztecAsyncMap<number, string[]>;
27
34
  #publicLogsByBlock: AztecAsyncMap<number, Buffer>;
28
35
  #contractClassLogsByBlock: AztecAsyncMap<number, Buffer>;
29
36
  #logsMaxPageSize: number;
@@ -34,29 +41,42 @@ export class LogStore {
34
41
  private blockStore: BlockStore,
35
42
  logsMaxPageSize: number = 1000,
36
43
  ) {
37
- this.#logsByTag = db.openMap('archiver_tagged_logs_by_tag');
38
- this.#logTagsByBlock = db.openMap('archiver_log_tags_by_block');
44
+ this.#privateLogsByTag = db.openMap('archiver_private_tagged_logs_by_tag');
45
+ this.#publicLogsByContractAndTag = db.openMap('archiver_public_tagged_logs_by_tag');
46
+ this.#privateLogKeysByBlock = db.openMap('archiver_private_log_keys_by_block');
47
+ this.#publicLogKeysByBlock = db.openMap('archiver_public_log_keys_by_block');
39
48
  this.#publicLogsByBlock = db.openMap('archiver_public_logs_by_block');
40
49
  this.#contractClassLogsByBlock = db.openMap('archiver_contract_class_logs_by_block');
41
50
 
42
51
  this.#logsMaxPageSize = logsMaxPageSize;
43
52
  }
44
53
 
45
- async #extractTaggedLogs(block: L2BlockNew) {
54
+ /**
55
+ * Extracts tagged logs from a single block, grouping them into private and public maps.
56
+ *
57
+ * @param block - The L2 block to extract logs from.
58
+ * @returns An object containing the private and public tagged logs for the block.
59
+ */
60
+ async #extractTaggedLogsFromBlock(block: L2BlockNew) {
46
61
  const blockHash = L2BlockHash.fromField(await block.hash());
47
- const taggedLogs = new Map<string, Buffer[]>();
62
+ // SiloedTag (as string) -> array of log buffers.
63
+ const privateTaggedLogs = new Map<string, Buffer[]>();
64
+ // "{contractAddress}_{tag}" (as string) -> array of log buffers.
65
+ const publicTaggedLogs = new Map<string, Buffer[]>();
48
66
  const dataStartIndexForBlock =
49
67
  block.header.state.partial.noteHashTree.nextAvailableLeafIndex -
50
68
  block.body.txEffects.length * MAX_NOTE_HASHES_PER_TX;
69
+
51
70
  block.body.txEffects.forEach((txEffect, txIndex) => {
52
71
  const txHash = txEffect.txHash;
53
72
  const dataStartIndexForTx = dataStartIndexForBlock + txIndex * MAX_NOTE_HASHES_PER_TX;
54
73
 
55
74
  txEffect.privateLogs.forEach((log, logIndex) => {
75
+ // Private logs use SiloedTag (already siloed by kernel)
56
76
  const tag = log.fields[0];
57
77
  this.#log.debug(`Found private log with tag ${tag.toString()} in block ${block.number}`);
58
78
 
59
- const currentLogs = taggedLogs.get(tag.toString()) ?? [];
79
+ const currentLogs = privateTaggedLogs.get(tag.toString()) ?? [];
60
80
  currentLogs.push(
61
81
  new TxScopedL2Log(
62
82
  txHash,
@@ -68,14 +88,19 @@ export class LogStore {
68
88
  log,
69
89
  ).toBuffer(),
70
90
  );
71
- taggedLogs.set(tag.toString(), currentLogs);
91
+ privateTaggedLogs.set(tag.toString(), currentLogs);
72
92
  });
73
93
 
74
94
  txEffect.publicLogs.forEach((log, logIndex) => {
95
+ // Public logs use Tag directly (not siloed) and are stored with contract address
75
96
  const tag = log.fields[0];
76
- this.#log.debug(`Found public log with tag ${tag.toString()} in block ${block.number}`);
97
+ const contractAddress = log.contractAddress;
98
+ const key = `${contractAddress.toString()}_${tag.toString()}`;
99
+ this.#log.debug(
100
+ `Found public log with tag ${tag.toString()} from contract ${contractAddress.toString()} in block ${block.number}`,
101
+ );
77
102
 
78
- const currentLogs = taggedLogs.get(tag.toString()) ?? [];
103
+ const currentLogs = publicTaggedLogs.get(key) ?? [];
79
104
  currentLogs.push(
80
105
  new TxScopedL2Log(
81
106
  txHash,
@@ -87,49 +112,102 @@ export class LogStore {
87
112
  log,
88
113
  ).toBuffer(),
89
114
  );
90
- taggedLogs.set(tag.toString(), currentLogs);
115
+ publicTaggedLogs.set(key, currentLogs);
91
116
  });
92
117
  });
93
- return taggedLogs;
118
+
119
+ return { privateTaggedLogs, publicTaggedLogs };
94
120
  }
95
121
 
96
122
  /**
97
- * Append new logs to the store's list.
98
- * @param blocks - The blocks for which to add the logs.
99
- * @returns True if the operation is successful.
123
+ * Extracts and aggregates tagged logs from a list of blocks.
124
+ * @param blocks - The blocks to extract logs from.
125
+ * @returns A map from tag (as string) to an array of serialized private logs belonging to that tag, and a map from
126
+ * "{contractAddress}_{tag}" (as string) to an array of serialized public logs belonging to that key.
100
127
  */
101
- async addLogs(blocks: L2BlockNew[]): Promise<boolean> {
102
- const taggedLogsInBlocks = await Promise.all(blocks.map(block => this.#extractTaggedLogs(block)));
103
- const taggedLogsToAdd = taggedLogsInBlocks.reduce((acc, taggedLogs) => {
104
- for (const [tag, logs] of taggedLogs.entries()) {
128
+ async #extractTaggedLogs(
129
+ blocks: L2BlockNew[],
130
+ ): Promise<{ privateTaggedLogs: Map<string, Buffer[]>; publicTaggedLogs: Map<string, Buffer[]> }> {
131
+ const taggedLogsInBlocks = await Promise.all(blocks.map(block => this.#extractTaggedLogsFromBlock(block)));
132
+
133
+ // Now we merge the maps from each block into a single map.
134
+ const privateTaggedLogs = taggedLogsInBlocks.reduce((acc, { privateTaggedLogs }) => {
135
+ for (const [tag, logs] of privateTaggedLogs.entries()) {
105
136
  const currentLogs = acc.get(tag) ?? [];
106
137
  acc.set(tag, currentLogs.concat(logs));
107
138
  }
108
139
  return acc;
109
140
  }, new Map<string, Buffer[]>());
110
- const tagsToUpdate = Array.from(taggedLogsToAdd.keys());
141
+
142
+ const publicTaggedLogs = taggedLogsInBlocks.reduce((acc, { publicTaggedLogs }) => {
143
+ for (const [key, logs] of publicTaggedLogs.entries()) {
144
+ const currentLogs = acc.get(key) ?? [];
145
+ acc.set(key, currentLogs.concat(logs));
146
+ }
147
+ return acc;
148
+ }, new Map<string, Buffer[]>());
149
+
150
+ return { privateTaggedLogs, publicTaggedLogs };
151
+ }
152
+
153
+ /**
154
+ * Append new logs to the store's list.
155
+ * @param blocks - The blocks for which to add the logs.
156
+ * @returns True if the operation is successful.
157
+ */
158
+ async addLogs(blocks: L2BlockNew[]): Promise<boolean> {
159
+ const { privateTaggedLogs, publicTaggedLogs } = await this.#extractTaggedLogs(blocks);
160
+
161
+ const keysOfPrivateLogsToUpdate = Array.from(privateTaggedLogs.keys());
162
+ const keysOfPublicLogsToUpdate = Array.from(publicTaggedLogs.keys());
111
163
 
112
164
  return this.db.transactionAsync(async () => {
113
- const currentTaggedLogs = await Promise.all(
114
- tagsToUpdate.map(async tag => ({ tag, logBuffers: await this.#logsByTag.getAsync(tag) })),
165
+ const currentPrivateTaggedLogs = await Promise.all(
166
+ keysOfPrivateLogsToUpdate.map(async key => ({
167
+ tag: key,
168
+ logBuffers: await this.#privateLogsByTag.getAsync(key),
169
+ })),
115
170
  );
116
- currentTaggedLogs.forEach(taggedLogBuffer => {
171
+ currentPrivateTaggedLogs.forEach(taggedLogBuffer => {
117
172
  if (taggedLogBuffer.logBuffers && taggedLogBuffer.logBuffers.length > 0) {
118
- taggedLogsToAdd.set(
173
+ privateTaggedLogs.set(
119
174
  taggedLogBuffer.tag,
120
- taggedLogBuffer.logBuffers!.concat(taggedLogsToAdd.get(taggedLogBuffer.tag)!),
175
+ taggedLogBuffer.logBuffers!.concat(privateTaggedLogs.get(taggedLogBuffer.tag)!),
176
+ );
177
+ }
178
+ });
179
+
180
+ const currentPublicTaggedLogs = await Promise.all(
181
+ keysOfPublicLogsToUpdate.map(async key => ({
182
+ key,
183
+ logBuffers: await this.#publicLogsByContractAndTag.getAsync(key),
184
+ })),
185
+ );
186
+ currentPublicTaggedLogs.forEach(taggedLogBuffer => {
187
+ if (taggedLogBuffer.logBuffers && taggedLogBuffer.logBuffers.length > 0) {
188
+ publicTaggedLogs.set(
189
+ taggedLogBuffer.key,
190
+ taggedLogBuffer.logBuffers!.concat(publicTaggedLogs.get(taggedLogBuffer.key)!),
121
191
  );
122
192
  }
123
193
  });
194
+
124
195
  for (const block of blocks) {
125
196
  const blockHash = await block.hash();
126
197
 
127
- const tagsInBlock = [];
128
- for (const [tag, logs] of taggedLogsToAdd.entries()) {
129
- await this.#logsByTag.set(tag, logs);
130
- tagsInBlock.push(tag);
198
+ const privateTagsInBlock: string[] = [];
199
+ for (const [tag, logs] of privateTaggedLogs.entries()) {
200
+ await this.#privateLogsByTag.set(tag, logs);
201
+ privateTagsInBlock.push(tag);
131
202
  }
132
- await this.#logTagsByBlock.set(block.number, tagsInBlock);
203
+ await this.#privateLogKeysByBlock.set(block.number, privateTagsInBlock);
204
+
205
+ const publicKeysInBlock: string[] = [];
206
+ for (const [key, logs] of publicTaggedLogs.entries()) {
207
+ await this.#publicLogsByContractAndTag.set(key, logs);
208
+ publicKeysInBlock.push(key);
209
+ }
210
+ await this.#publicLogKeysByBlock.set(block.number, publicKeysInBlock);
133
211
 
134
212
  const publicLogsInBlock = block.body.txEffects
135
213
  .map((txEffect, txIndex) =>
@@ -178,44 +256,55 @@ export class LogStore {
178
256
 
179
257
  deleteLogs(blocks: L2BlockNew[]): Promise<boolean> {
180
258
  return this.db.transactionAsync(async () => {
181
- const tagsToDelete = (
182
- await Promise.all(
183
- blocks.map(async block => {
184
- const tags = await this.#logTagsByBlock.getAsync(block.number);
185
- return tags ?? [];
186
- }),
187
- )
188
- ).flat();
259
+ await Promise.all(
260
+ blocks.map(async block => {
261
+ // Delete private logs
262
+ const privateKeys = (await this.#privateLogKeysByBlock.getAsync(block.number)) ?? [];
263
+ await Promise.all(privateKeys.map(tag => this.#privateLogsByTag.delete(tag)));
264
+
265
+ // Delete public logs
266
+ const publicKeys = (await this.#publicLogKeysByBlock.getAsync(block.number)) ?? [];
267
+ await Promise.all(publicKeys.map(key => this.#publicLogsByContractAndTag.delete(key)));
268
+ }),
269
+ );
189
270
 
190
271
  await Promise.all(
191
272
  blocks.map(block =>
192
273
  Promise.all([
193
274
  this.#publicLogsByBlock.delete(block.number),
194
- this.#logTagsByBlock.delete(block.number),
275
+ this.#privateLogKeysByBlock.delete(block.number),
276
+ this.#publicLogKeysByBlock.delete(block.number),
195
277
  this.#contractClassLogsByBlock.delete(block.number),
196
278
  ]),
197
279
  ),
198
280
  );
199
281
 
200
- await Promise.all(tagsToDelete.map(tag => this.#logsByTag.delete(tag.toString())));
201
282
  return true;
202
283
  });
203
284
  }
204
285
 
205
286
  /**
206
- * Gets all logs that match any of the received tags (i.e. logs with their first field equal to a tag).
207
- * @param tags - The tags to filter the logs by.
208
- * @returns For each received tag, an array of matching logs is returned. An empty array implies no logs match
209
- * that tag.
287
+ * Gets all private logs that match any of the `tags`. For each tag, an array of matching logs is returned. An empty
288
+ * array implies no logs match that tag.
210
289
  */
211
- async getLogsByTags(tags: Fr[], limitPerTag?: number): Promise<TxScopedL2Log[][]> {
212
- if (limitPerTag !== undefined && limitPerTag <= 0) {
213
- throw new TypeError('limitPerTag needs to be greater than 0');
214
- }
215
- const logs = await Promise.all(tags.map(tag => this.#logsByTag.getAsync(tag.toString())));
216
- return logs.map(
217
- logBuffers => logBuffers?.slice(0, limitPerTag).map(logBuffer => TxScopedL2Log.fromBuffer(logBuffer)) ?? [],
290
+ async getPrivateLogsByTags(tags: SiloedTag[]): Promise<TxScopedL2Log[][]> {
291
+ const logs = await Promise.all(tags.map(tag => this.#privateLogsByTag.getAsync(tag.toString())));
292
+
293
+ return logs.map(logBuffers => logBuffers?.map(logBuffer => TxScopedL2Log.fromBuffer(logBuffer)) ?? []);
294
+ }
295
+
296
+ /**
297
+ * Gets all public logs that match any of the `tags` from the specified contract. For each tag, an array of matching
298
+ * logs is returned. An empty array implies no logs match that tag.
299
+ */
300
+ async getPublicLogsByTagsFromContract(contractAddress: AztecAddress, tags: Tag[]): Promise<TxScopedL2Log[][]> {
301
+ const logs = await Promise.all(
302
+ tags.map(tag => {
303
+ const key = `${contractAddress.toString()}_${tag.value.toString()}`;
304
+ return this.#publicLogsByContractAndTag.getAsync(key);
305
+ }),
218
306
  );
307
+ return logs.map(logBuffers => logBuffers?.map(logBuffer => TxScopedL2Log.fromBuffer(logBuffer)) ?? []);
219
308
  }
220
309
 
221
310
  /**
@@ -331,7 +331,7 @@ export async function getCheckpointBlobDataFromBlobs(
331
331
  logger: Logger,
332
332
  isHistoricalSync: boolean,
333
333
  ): Promise<CheckpointBlobData> {
334
- const blobBodies = await blobClient.getBlobSidecar(blockHash, blobHashes, undefined, { isHistoricalSync });
334
+ const blobBodies = await blobClient.getBlobSidecar(blockHash, blobHashes, { isHistoricalSync });
335
335
  if (blobBodies.length === 0) {
336
336
  throw new NoBlobBodiesFoundError(checkpointNumber);
337
337
  }
@@ -339,7 +339,7 @@ export async function getCheckpointBlobDataFromBlobs(
339
339
  let checkpointBlobData: CheckpointBlobData;
340
340
  try {
341
341
  // Attempt to decode the checkpoint blob data.
342
- checkpointBlobData = decodeCheckpointBlobDataFromBlobs(blobBodies.map(b => b.blob));
342
+ checkpointBlobData = decodeCheckpointBlobDataFromBlobs(blobBodies);
343
343
  } catch (err: any) {
344
344
  if (err instanceof BlobDeserializationError) {
345
345
  logger.fatal(err.message);
@@ -10,6 +10,7 @@ import type { AztecAddress } from '@aztec/stdlib/aztec-address';
10
10
  import {
11
11
  L2Block,
12
12
  L2BlockHash,
13
+ L2BlockNew,
13
14
  type L2BlockSource,
14
15
  type L2Tips,
15
16
  PublishedL2Block,
@@ -105,6 +106,16 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
105
106
  return Promise.resolve(this.l2Blocks[number - 1]);
106
107
  }
107
108
 
109
+ /**
110
+ * Gets an L2 block (new format).
111
+ * @param number - The block number to return.
112
+ * @returns The requested L2 block.
113
+ */
114
+ public getL2BlockNew(number: BlockNumber): Promise<L2BlockNew | undefined> {
115
+ const block = this.l2Blocks[number - 1];
116
+ return Promise.resolve(block?.toL2Block());
117
+ }
118
+
108
119
  /**
109
120
  * Gets up to `limit` amount of L2 blocks starting from `from`.
110
121
  * @param from - Number of the first block to return (inclusive).