@aztec/archiver 0.0.1-commit.001888fc → 0.0.1-commit.017a351

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 (121) hide show
  1. package/README.md +19 -11
  2. package/dest/archiver.d.ts +53 -16
  3. package/dest/archiver.d.ts.map +1 -1
  4. package/dest/archiver.js +248 -76
  5. package/dest/config.d.ts +4 -1
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +21 -14
  8. package/dest/errors.d.ts +44 -10
  9. package/dest/errors.d.ts.map +1 -1
  10. package/dest/errors.js +66 -12
  11. package/dest/factory.d.ts +13 -7
  12. package/dest/factory.d.ts.map +1 -1
  13. package/dest/factory.js +37 -25
  14. package/dest/index.d.ts +11 -3
  15. package/dest/index.d.ts.map +1 -1
  16. package/dest/index.js +10 -2
  17. package/dest/l1/calldata_retriever.d.ts +2 -1
  18. package/dest/l1/calldata_retriever.d.ts.map +1 -1
  19. package/dest/l1/calldata_retriever.js +15 -5
  20. package/dest/l1/data_retrieval.d.ts +19 -10
  21. package/dest/l1/data_retrieval.d.ts.map +1 -1
  22. package/dest/l1/data_retrieval.js +25 -32
  23. package/dest/l1/trace_tx.d.ts +12 -66
  24. package/dest/l1/trace_tx.d.ts.map +1 -1
  25. package/dest/l1/validate_historical_logs.d.ts +23 -0
  26. package/dest/l1/validate_historical_logs.d.ts.map +1 -0
  27. package/dest/l1/validate_historical_logs.js +108 -0
  28. package/dest/modules/contract_data_source_adapter.d.ts +25 -0
  29. package/dest/modules/contract_data_source_adapter.d.ts.map +1 -0
  30. package/dest/modules/contract_data_source_adapter.js +40 -0
  31. package/dest/modules/data_source_base.d.ts +71 -47
  32. package/dest/modules/data_source_base.d.ts.map +1 -1
  33. package/dest/modules/data_source_base.js +269 -137
  34. package/dest/modules/data_store_updater.d.ts +30 -13
  35. package/dest/modules/data_store_updater.d.ts.map +1 -1
  36. package/dest/modules/data_store_updater.js +166 -116
  37. package/dest/modules/instrumentation.d.ts +7 -2
  38. package/dest/modules/instrumentation.d.ts.map +1 -1
  39. package/dest/modules/instrumentation.js +25 -7
  40. package/dest/modules/l1_synchronizer.d.ts +11 -6
  41. package/dest/modules/l1_synchronizer.d.ts.map +1 -1
  42. package/dest/modules/l1_synchronizer.js +414 -214
  43. package/dest/modules/outbox_trees_resolver.d.ts +62 -0
  44. package/dest/modules/outbox_trees_resolver.d.ts.map +1 -0
  45. package/dest/modules/outbox_trees_resolver.js +162 -0
  46. package/dest/modules/validation.d.ts +4 -3
  47. package/dest/modules/validation.d.ts.map +1 -1
  48. package/dest/modules/validation.js +6 -6
  49. package/dest/store/block_store.d.ts +165 -66
  50. package/dest/store/block_store.d.ts.map +1 -1
  51. package/dest/store/block_store.js +648 -233
  52. package/dest/store/contract_class_store.d.ts +17 -4
  53. package/dest/store/contract_class_store.d.ts.map +1 -1
  54. package/dest/store/contract_class_store.js +24 -68
  55. package/dest/store/contract_instance_store.d.ts +28 -1
  56. package/dest/store/contract_instance_store.d.ts.map +1 -1
  57. package/dest/store/contract_instance_store.js +37 -2
  58. package/dest/store/data_stores.d.ts +68 -0
  59. package/dest/store/data_stores.d.ts.map +1 -0
  60. package/dest/store/data_stores.js +54 -0
  61. package/dest/store/function_names_cache.d.ts +17 -0
  62. package/dest/store/function_names_cache.d.ts.map +1 -0
  63. package/dest/store/function_names_cache.js +30 -0
  64. package/dest/store/l2_tips_cache.d.ts +13 -7
  65. package/dest/store/l2_tips_cache.d.ts.map +1 -1
  66. package/dest/store/l2_tips_cache.js +13 -76
  67. package/dest/store/log_store.d.ts +42 -40
  68. package/dest/store/log_store.d.ts.map +1 -1
  69. package/dest/store/log_store.js +259 -462
  70. package/dest/store/log_store_codec.d.ts +70 -0
  71. package/dest/store/log_store_codec.d.ts.map +1 -0
  72. package/dest/store/log_store_codec.js +101 -0
  73. package/dest/store/message_store.d.ts +9 -3
  74. package/dest/store/message_store.d.ts.map +1 -1
  75. package/dest/store/message_store.js +39 -10
  76. package/dest/test/fake_l1_state.d.ts +15 -3
  77. package/dest/test/fake_l1_state.d.ts.map +1 -1
  78. package/dest/test/fake_l1_state.js +80 -18
  79. package/dest/test/mock_l1_to_l2_message_source.d.ts +1 -1
  80. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
  81. package/dest/test/mock_l1_to_l2_message_source.js +2 -1
  82. package/dest/test/mock_l2_block_source.d.ts +51 -50
  83. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  84. package/dest/test/mock_l2_block_source.js +244 -181
  85. package/dest/test/noop_l1_archiver.d.ts +10 -6
  86. package/dest/test/noop_l1_archiver.d.ts.map +1 -1
  87. package/dest/test/noop_l1_archiver.js +22 -9
  88. package/package.json +14 -14
  89. package/src/archiver.ts +315 -83
  90. package/src/config.ts +24 -11
  91. package/src/errors.ts +103 -18
  92. package/src/factory.ts +49 -25
  93. package/src/index.ts +18 -2
  94. package/src/l1/calldata_retriever.ts +16 -5
  95. package/src/l1/data_retrieval.ts +36 -45
  96. package/src/l1/validate_historical_logs.ts +140 -0
  97. package/src/modules/contract_data_source_adapter.ts +55 -0
  98. package/src/modules/data_source_base.ts +341 -179
  99. package/src/modules/data_store_updater.ts +201 -147
  100. package/src/modules/instrumentation.ts +28 -8
  101. package/src/modules/l1_synchronizer.ts +549 -255
  102. package/src/modules/outbox_trees_resolver.ts +199 -0
  103. package/src/modules/validation.ts +10 -9
  104. package/src/store/block_store.ts +807 -285
  105. package/src/store/contract_class_store.ts +31 -103
  106. package/src/store/contract_instance_store.ts +51 -5
  107. package/src/store/data_stores.ts +104 -0
  108. package/src/store/function_names_cache.ts +37 -0
  109. package/src/store/l2_tips_cache.ts +16 -70
  110. package/src/store/log_store.ts +297 -623
  111. package/src/store/log_store_codec.ts +132 -0
  112. package/src/store/message_store.ts +43 -12
  113. package/src/structs/inbox_message.ts +1 -1
  114. package/src/test/fake_l1_state.ts +99 -27
  115. package/src/test/mock_l1_to_l2_message_source.ts +1 -0
  116. package/src/test/mock_l2_block_source.ts +299 -232
  117. package/src/test/noop_l1_archiver.ts +42 -9
  118. package/dest/store/kv_archiver_store.d.ts +0 -367
  119. package/dest/store/kv_archiver_store.d.ts.map +0 -1
  120. package/dest/store/kv_archiver_store.js +0 -481
  121. package/src/store/kv_archiver_store.ts +0 -697
@@ -1,705 +1,379 @@
1
1
  import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
2
- import { BlockNumber } from '@aztec/foundation/branded-types';
3
- import { filterAsync } from '@aztec/foundation/collection';
4
2
  import { Fr } from '@aztec/foundation/curves/bn254';
5
3
  import { createLogger } from '@aztec/foundation/log';
6
- import { BufferReader, numToUInt32BE } from '@aztec/foundation/serialize';
7
4
  import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
8
- import type { AztecAddress } from '@aztec/stdlib/aztec-address';
9
- import { BlockHash, L2Block } from '@aztec/stdlib/block';
5
+ import type { BlockHash, L2Block } from '@aztec/stdlib/block';
10
6
  import { MAX_LOGS_PER_TAG } from '@aztec/stdlib/interfaces/api-limit';
11
- import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client';
12
- import {
13
- ContractClassLog,
14
- ExtendedContractClassLog,
15
- ExtendedPublicLog,
16
- type LogFilter,
17
- LogId,
18
- PublicLog,
19
- type SiloedTag,
7
+ import type {
8
+ LogCursor,
9
+ LogResult,
10
+ PrivateLogsQuery,
11
+ PublicLogsQuery,
12
+ SiloedTag,
20
13
  Tag,
21
- TxScopedL2Log,
14
+ TagQuery,
22
15
  } from '@aztec/stdlib/logs';
23
16
  import { TxHash } from '@aztec/stdlib/tx';
24
17
 
25
- import { OutOfOrderLogInsertionError } from '../errors.js';
26
18
  import type { BlockStore } from './block_store.js';
19
+ import {
20
+ decodeKeyTail,
21
+ decodeValue,
22
+ encodeKey,
23
+ encodePublicPrefix,
24
+ encodeValue,
25
+ endOfTagRange,
26
+ endOfTxRange,
27
+ fieldHex,
28
+ incKey,
29
+ } from './log_store_codec.js';
27
30
 
28
31
  /**
29
- * A store for logs
32
+ * Indexes every emitted private and public log under a composite hex-string key
33
+ * `[contractAddress (public only)]-tag-blockNumber-txIndexWithinBlock-logIndexWithinTx`,
34
+ * where each numeric segment is zero-padded to 8 lowercase hex digits (4 bytes BE) and
35
+ * `contractAddress` / `tag` are the bare 64-hex-char field representations (no `0x` prefix). The
36
+ * fixed-width zero-padded hex segments sort lexicographically in the same order as the canonical
37
+ * `(contract, tag, blockNumber, txIndexWithinBlock, logIndexWithinTx)` tuple, so a single ordered
38
+ * range scan answers every {@link PrivateLogsQuery} / {@link PublicLogsQuery}.
39
+ *
40
+ * Per-block secondary indices (`#privateKeysByBlock`, `#publicKeysByBlock`) record the exact primary
41
+ * keys written for each block so {@link deleteLogs} can drop them on reorg without having to range
42
+ * scan by block (block isn't the leading key segment).
43
+ *
44
+ * Contract-class logs are no longer stored or served by the log store.
30
45
  */
31
46
  export class LogStore {
32
- // `tag` --> private logs
33
- #privateLogsByTag: AztecAsyncMap<string, Buffer[]>;
34
- // `{contractAddress}_${tag}` --> public logs
35
- #publicLogsByContractAndTag: AztecAsyncMap<string, Buffer[]>;
36
- #privateLogKeysByBlock: AztecAsyncMap<number, string[]>;
37
- #publicLogKeysByBlock: AztecAsyncMap<number, string[]>;
38
- #publicLogsByBlock: AztecAsyncMap<number, Buffer>;
39
- #contractClassLogsByBlock: AztecAsyncMap<number, Buffer>;
40
- #logsMaxPageSize: number;
47
+ /** Primary map: composite private key (tag + tail = 96 hex chars + separators) -> serialized {@link StoredLogValue}. */
48
+ #privateLogs: AztecAsyncMap<string, Buffer>;
49
+ /** Primary map: composite public key (contract + tag + tail) -> serialized {@link StoredLogValue}. */
50
+ #publicLogs: AztecAsyncMap<string, Buffer>;
51
+
52
+ /** Secondary deletion index: blockNumber -> the exact primary keys written for that block. */
53
+ #privateKeysByBlock: AztecAsyncMap<number, string[]>;
54
+ #publicKeysByBlock: AztecAsyncMap<number, string[]>;
55
+
41
56
  #log = createLogger('archiver:log_store');
42
57
 
58
+ /**
59
+ * @param genesisBlockHash - Hash of the synthetic genesis block. During early sync the PXE anchors to
60
+ * genesis and passes its hash as a query `referenceBlock`; since the archiver never indexes the
61
+ * genesis block, the store recognizes this hash directly and resolves it to the genesis block number
62
+ * rather than mistaking it for a reorg.
63
+ */
43
64
  constructor(
44
65
  private db: AztecAsyncKVStore,
45
66
  private blockStore: BlockStore,
46
- logsMaxPageSize: number = 1000,
67
+ private readonly genesisBlockHash: BlockHash,
47
68
  ) {
48
- this.#privateLogsByTag = db.openMap('archiver_private_tagged_logs_by_tag');
49
- this.#publicLogsByContractAndTag = db.openMap('archiver_public_tagged_logs_by_tag');
50
- this.#privateLogKeysByBlock = db.openMap('archiver_private_log_keys_by_block');
51
- this.#publicLogKeysByBlock = db.openMap('archiver_public_log_keys_by_block');
52
- this.#publicLogsByBlock = db.openMap('archiver_public_logs_by_block');
53
- this.#contractClassLogsByBlock = db.openMap('archiver_contract_class_logs_by_block');
54
-
55
- this.#logsMaxPageSize = logsMaxPageSize;
69
+ this.#privateLogs = db.openMap('archiver_private_logs');
70
+ this.#publicLogs = db.openMap('archiver_public_logs');
71
+ this.#privateKeysByBlock = db.openMap('archiver_private_log_keys_by_block');
72
+ this.#publicKeysByBlock = db.openMap('archiver_public_log_keys_by_block');
56
73
  }
57
74
 
58
75
  /**
59
- * Extracts tagged logs from a single block, grouping them into private and public maps.
76
+ * Indexes every emitted private and public log from the given blocks. Wraps the write in a single
77
+ * `db.transactionAsync` so the primary entries and the per-block secondary indices stay consistent.
60
78
  *
61
- * @param block - The L2 block to extract logs from.
62
- * @returns An object containing the private and public tagged logs for the block.
79
+ * A block is only ever added once; on reorg the archiver calls {@link deleteLogs} first, so we write
80
+ * the secondary index entries with a plain `set` (overwrite) rather than read-modify-append.
63
81
  */
64
- #extractTaggedLogsFromBlock(block: L2Block) {
65
- // SiloedTag (as string) -> array of log buffers.
66
- const privateTaggedLogs = new Map<string, Buffer[]>();
67
- // "{contractAddress}_{tag}" (as string) -> array of log buffers.
68
- const publicTaggedLogs = new Map<string, Buffer[]>();
69
-
70
- block.body.txEffects.forEach(txEffect => {
71
- const txHash = txEffect.txHash;
72
-
73
- txEffect.privateLogs.forEach(log => {
74
- // Private logs use SiloedTag (already siloed by kernel)
75
- const tag = log.fields[0];
76
- this.#log.debug(`Found private log with tag ${tag.toString()} in block ${block.number}`);
77
-
78
- const currentLogs = privateTaggedLogs.get(tag.toString()) ?? [];
79
- currentLogs.push(
80
- new TxScopedL2Log(
81
- txHash,
82
- block.number,
83
- block.timestamp,
84
- log.getEmittedFields(),
85
- txEffect.noteHashes,
86
- txEffect.nullifiers[0],
87
- ).toBuffer(),
88
- );
89
- privateTaggedLogs.set(tag.toString(), currentLogs);
90
- });
91
-
92
- txEffect.publicLogs.forEach(log => {
93
- // Public logs use Tag directly (not siloed) and are stored with contract address
94
- const tag = log.fields[0];
95
- const contractAddress = log.contractAddress;
96
- const key = `${contractAddress.toString()}_${tag.toString()}`;
97
- this.#log.debug(
98
- `Found public log with tag ${tag.toString()} from contract ${contractAddress.toString()} in block ${block.number}`,
99
- );
100
-
101
- const currentLogs = publicTaggedLogs.get(key) ?? [];
102
- currentLogs.push(
103
- new TxScopedL2Log(
104
- txHash,
105
- block.number,
106
- block.timestamp,
107
- log.getEmittedFields(),
108
- txEffect.noteHashes,
109
- txEffect.nullifiers[0],
110
- ).toBuffer(),
111
- );
112
- publicTaggedLogs.set(key, currentLogs);
113
- });
114
- });
115
-
116
- return { privateTaggedLogs, publicTaggedLogs };
117
- }
118
-
119
- /**
120
- * Extracts and aggregates tagged logs from a list of blocks.
121
- * @param blocks - The blocks to extract logs from.
122
- * @returns A map from tag (as string) to an array of serialized private logs belonging to that tag, and a map from
123
- * "{contractAddress}_{tag}" (as string) to an array of serialized public logs belonging to that key.
124
- */
125
- #extractTaggedLogs(blocks: L2Block[]): {
126
- privateTaggedLogs: Map<string, Buffer[]>;
127
- publicTaggedLogs: Map<string, Buffer[]>;
128
- } {
129
- const taggedLogsInBlocks = blocks.map(block => this.#extractTaggedLogsFromBlock(block));
130
-
131
- // Now we merge the maps from each block into a single map.
132
- const privateTaggedLogs = taggedLogsInBlocks.reduce((acc, { privateTaggedLogs }) => {
133
- for (const [tag, logs] of privateTaggedLogs.entries()) {
134
- const currentLogs = acc.get(tag) ?? [];
135
- acc.set(tag, currentLogs.concat(logs));
136
- }
137
- return acc;
138
- }, new Map<string, Buffer[]>());
139
-
140
- const publicTaggedLogs = taggedLogsInBlocks.reduce((acc, { publicTaggedLogs }) => {
141
- for (const [key, logs] of publicTaggedLogs.entries()) {
142
- const currentLogs = acc.get(key) ?? [];
143
- acc.set(key, currentLogs.concat(logs));
144
- }
145
- return acc;
146
- }, new Map<string, Buffer[]>());
147
-
148
- return { privateTaggedLogs, publicTaggedLogs };
149
- }
150
-
151
- async #addPrivateLogs(blocks: L2Block[]): Promise<void> {
152
- const newBlocks = await filterAsync(
153
- blocks,
154
- async block => !(await this.#privateLogKeysByBlock.hasAsync(block.number)),
155
- );
156
-
157
- const { privateTaggedLogs } = this.#extractTaggedLogs(newBlocks);
158
- const keysOfPrivateLogsToUpdate = Array.from(privateTaggedLogs.keys());
159
-
160
- const currentPrivateTaggedLogs = await Promise.all(
161
- keysOfPrivateLogsToUpdate.map(async key => ({
162
- tag: key,
163
- logBuffers: await this.#privateLogsByTag.getAsync(key),
164
- })),
165
- );
166
-
167
- for (const taggedLogBuffer of currentPrivateTaggedLogs) {
168
- if (taggedLogBuffer.logBuffers && taggedLogBuffer.logBuffers.length > 0) {
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
- );
82
+ addLogs(blocks: L2Block[]): Promise<boolean> {
83
+ return this.db.transactionAsync(async () => {
84
+ for (const block of blocks) {
85
+ const blockHash = await block.hash();
86
+ const blockNumber = block.number;
87
+ const blockTimestamp = block.timestamp;
88
+
89
+ const privateKeys: string[] = [];
90
+ const privateValues: Buffer[] = [];
91
+ const publicKeys: string[] = [];
92
+ const publicValues: Buffer[] = [];
93
+
94
+ for (let txIndexWithinBlock = 0; txIndexWithinBlock < block.body.txEffects.length; txIndexWithinBlock++) {
95
+ const txEffect = block.body.txEffects[txIndexWithinBlock];
96
+ const txHash = txEffect.txHash;
97
+
98
+ // Private and public log indices are counted independently per tx, each starting at 0.
99
+ let privateLogIndexWithinTx = 0;
100
+ let publicLogIndexWithinTx = 0;
101
+
102
+ for (const log of txEffect.privateLogs) {
103
+ const tagHex = fieldHex(log.fields[0]);
104
+ const key = encodeKey(tagHex, blockNumber, txIndexWithinBlock, privateLogIndexWithinTx);
105
+ const value = encodeValue({
106
+ txHash,
107
+ blockHash,
108
+ blockTimestamp,
109
+ logData: log.getEmittedFields(),
110
+ });
111
+ privateKeys.push(key);
112
+ privateValues.push(value);
113
+ privateLogIndexWithinTx++;
114
+ }
115
+
116
+ for (const log of txEffect.publicLogs) {
117
+ const contractHex = fieldHex(log.contractAddress);
118
+ const tagHex = fieldHex(log.fields[0]);
119
+ const key = encodeKey(
120
+ encodePublicPrefix(contractHex, tagHex),
121
+ blockNumber,
122
+ txIndexWithinBlock,
123
+ publicLogIndexWithinTx,
124
+ );
125
+ const value = encodeValue({
126
+ txHash,
127
+ blockHash,
128
+ blockTimestamp,
129
+ logData: log.getEmittedFields(),
130
+ });
131
+ publicKeys.push(key);
132
+ publicValues.push(value);
133
+ publicLogIndexWithinTx++;
134
+ }
182
135
  }
183
- privateTaggedLogs.set(taggedLogBuffer.tag, taggedLogBuffer.logBuffers.concat(newLogs));
184
- }
185
- }
186
-
187
- for (const block of newBlocks) {
188
- const privateTagsInBlock: string[] = [];
189
- for (const [tag, logs] of privateTaggedLogs.entries()) {
190
- await this.#privateLogsByTag.set(tag, logs);
191
- privateTagsInBlock.push(tag);
192
- }
193
- await this.#privateLogKeysByBlock.set(block.number, privateTagsInBlock);
194
- }
195
- }
196
-
197
- async #addPublicLogs(blocks: L2Block[]): Promise<void> {
198
- const newBlocks = await filterAsync(
199
- blocks,
200
- async block => !(await this.#publicLogKeysByBlock.hasAsync(block.number)),
201
- );
202
-
203
- const { publicTaggedLogs } = this.#extractTaggedLogs(newBlocks);
204
- const keysOfPublicLogsToUpdate = Array.from(publicTaggedLogs.keys());
205
136
 
206
- const currentPublicTaggedLogs = await Promise.all(
207
- keysOfPublicLogsToUpdate.map(async key => ({
208
- tag: key,
209
- logBuffers: await this.#publicLogsByContractAndTag.getAsync(key),
210
- })),
211
- );
212
-
213
- for (const taggedLogBuffer of currentPublicTaggedLogs) {
214
- if (taggedLogBuffer.logBuffers && taggedLogBuffer.logBuffers.length > 0) {
215
- const newLogs = publicTaggedLogs.get(taggedLogBuffer.tag)!;
216
- if (newLogs.length === 0) {
217
- continue;
137
+ for (let i = 0; i < privateKeys.length; i++) {
138
+ await this.#privateLogs.set(privateKeys[i], privateValues[i]);
218
139
  }
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
- );
140
+ for (let i = 0; i < publicKeys.length; i++) {
141
+ await this.#publicLogs.set(publicKeys[i], publicValues[i]);
228
142
  }
229
- publicTaggedLogs.set(taggedLogBuffer.tag, taggedLogBuffer.logBuffers.concat(newLogs));
230
- }
231
- }
232
-
233
- for (const block of newBlocks) {
234
- const blockHash = await block.hash();
235
- const publicTagsInBlock: string[] = [];
236
- for (const [tag, logs] of publicTaggedLogs.entries()) {
237
- await this.#publicLogsByContractAndTag.set(tag, logs);
238
- publicTagsInBlock.push(tag);
239
- }
240
- await this.#publicLogKeysByBlock.set(block.number, publicTagsInBlock);
241
-
242
- const publicLogsInBlock = block.body.txEffects
243
- .map((txEffect, txIndex) =>
244
- [
245
- numToUInt32BE(txIndex),
246
- txEffect.txHash.toBuffer(),
247
- numToUInt32BE(txEffect.publicLogs.length),
248
- txEffect.publicLogs.map(log => log.toBuffer()),
249
- ].flat(),
250
- )
251
- .flat();
252
-
253
- await this.#publicLogsByBlock.set(block.number, this.#packWithBlockHash(blockHash, publicLogsInBlock));
254
- }
255
- }
256
143
 
257
- async #addContractClassLogs(blocks: L2Block[]): Promise<void> {
258
- const newBlocks = await filterAsync(
259
- blocks,
260
- async block => !(await this.#contractClassLogsByBlock.hasAsync(block.number)),
261
- );
144
+ await this.#privateKeysByBlock.set(blockNumber, privateKeys);
145
+ await this.#publicKeysByBlock.set(blockNumber, publicKeys);
262
146
 
263
- for (const block of newBlocks) {
264
- const blockHash = await block.hash();
265
-
266
- const contractClassLogsInBlock = block.body.txEffects
267
- .map((txEffect, txIndex) =>
268
- [
269
- numToUInt32BE(txIndex),
270
- txEffect.txHash.toBuffer(),
271
- numToUInt32BE(txEffect.contractClassLogs.length),
272
- txEffect.contractClassLogs.map(log => log.toBuffer()),
273
- ].flat(),
274
- )
275
- .flat();
276
-
277
- await this.#contractClassLogsByBlock.set(
278
- block.number,
279
- this.#packWithBlockHash(blockHash, contractClassLogsInBlock),
280
- );
281
- }
147
+ this.#log.debug(`Indexed logs for block ${blockNumber}`, {
148
+ blockNumber,
149
+ privateCount: privateKeys.length,
150
+ publicCount: publicKeys.length,
151
+ });
152
+ }
153
+ return true;
154
+ });
282
155
  }
283
156
 
284
157
  /**
285
- * Append new logs to the store's list.
286
- * @param blocks - The blocks for which to add the logs.
287
- * @returns True if the operation is successful.
158
+ * Deletes every log indexed under any of the given blocks. Secondary-index driven, so it doesn't
159
+ * have to range-scan the primary maps.
288
160
  */
289
- addLogs(blocks: L2Block[]): Promise<boolean> {
161
+ deleteLogs(blocks: L2Block[]): Promise<boolean> {
290
162
  return this.db.transactionAsync(async () => {
291
- await Promise.all([
292
- this.#addPrivateLogs(blocks),
293
- this.#addPublicLogs(blocks),
294
- this.#addContractClassLogs(blocks),
295
- ]);
163
+ for (const block of blocks) {
164
+ const blockNumber = block.number;
165
+
166
+ const [privateKeys, publicKeys] = await Promise.all([
167
+ this.#privateKeysByBlock.getAsync(blockNumber),
168
+ this.#publicKeysByBlock.getAsync(blockNumber),
169
+ ]);
170
+
171
+ if (privateKeys) {
172
+ for (const key of privateKeys) {
173
+ await this.#privateLogs.delete(key);
174
+ }
175
+ await this.#privateKeysByBlock.delete(blockNumber);
176
+ }
177
+ if (publicKeys) {
178
+ for (const key of publicKeys) {
179
+ await this.#publicLogs.delete(key);
180
+ }
181
+ await this.#publicKeysByBlock.delete(blockNumber);
182
+ }
183
+ }
296
184
  return true;
297
185
  });
298
186
  }
299
187
 
300
- #packWithBlockHash(blockHash: BlockHash, data: Buffer<ArrayBufferLike>[]): Buffer<ArrayBufferLike> {
301
- return Buffer.concat([blockHash.toBuffer(), ...data]);
188
+ /** Returns one inner array per element of `query.tags`, in input order. */
189
+ getPrivateLogsByTags(query: PrivateLogsQuery): Promise<LogResult[][]> {
190
+ LogStore.#validateQuery(query);
191
+ return this.db.transactionAsync(() => this.#runQuery(query, /* contractHex */ undefined));
302
192
  }
303
193
 
304
- #unpackBlockHash(reader: BufferReader): BlockHash {
305
- const blockHash = reader.remainingBytes() > 0 ? reader.readObject(Fr) : undefined;
306
-
307
- if (!blockHash) {
308
- throw new Error('Failed to read block hash from log entry buffer');
309
- }
310
-
311
- return new BlockHash(blockHash);
194
+ /** Returns one inner array per element of `query.tags`, in input order. */
195
+ getPublicLogsByTags(query: PublicLogsQuery): Promise<LogResult[][]> {
196
+ LogStore.#validateQuery(query);
197
+ return this.db.transactionAsync(() => this.#runQuery(query, fieldHex(query.contractAddress)));
312
198
  }
313
199
 
314
- deleteLogs(blocks: L2Block[]): Promise<boolean> {
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
- }),
326
- );
327
-
328
- await Promise.all(
329
- blocks.map(block =>
330
- Promise.all([
331
- this.#publicLogsByBlock.delete(block.number),
332
- this.#privateLogKeysByBlock.delete(block.number),
333
- this.#publicLogKeysByBlock.delete(block.number),
334
- this.#contractClassLogsByBlock.delete(block.number),
335
- ]),
336
- ),
337
- );
338
-
339
- return true;
340
- });
341
- }
342
-
343
- /**
344
- * Gets private logs that match any of the `tags`. For each tag, an array of matching logs is returned. An empty
345
- * array implies no logs match that tag.
346
- * @param tags - The tags to search for.
347
- * @param page - The page number (0-indexed) for pagination.
348
- * @param upToBlockNumber - If set, only return logs from blocks up to and including this block number.
349
- * @returns An array of log arrays, one per tag. Returns at most MAX_LOGS_PER_TAG logs per tag per page. If
350
- * MAX_LOGS_PER_TAG logs are returned for a tag, the caller should fetch the next page to check for more logs.
351
- */
352
- async getPrivateLogsByTags(
353
- tags: SiloedTag[],
354
- page: number = 0,
355
- upToBlockNumber?: BlockNumber,
356
- ): Promise<TxScopedL2Log[][]> {
357
- const logs = await Promise.all(tags.map(tag => this.#privateLogsByTag.getAsync(tag.toString())));
358
-
359
- const start = page * MAX_LOGS_PER_TAG;
360
- const end = start + MAX_LOGS_PER_TAG;
361
-
362
- return logs.map(logBuffers => {
363
- const deserialized = logBuffers?.slice(start, end).map(buf => TxScopedL2Log.fromBuffer(buf)) ?? [];
364
- if (upToBlockNumber !== undefined) {
365
- const cutoff = deserialized.findIndex(log => log.blockNumber > upToBlockNumber);
366
- if (cutoff !== -1) {
367
- return deserialized.slice(0, cutoff);
368
- }
369
- }
370
- return deserialized;
371
- });
200
+ static #validateQuery(query: { txHash?: TxHash; fromBlock?: unknown; toBlock?: unknown }): void {
201
+ if (query.txHash !== undefined && (query.fromBlock !== undefined || query.toBlock !== undefined)) {
202
+ throw new Error('`txHash` is mutually exclusive with `fromBlock`/`toBlock`');
203
+ }
372
204
  }
373
205
 
374
- /**
375
- * Gets public logs that match any of the `tags` from the specified contract. For each tag, an array of matching
376
- * logs is returned. An empty array implies no logs match that tag.
377
- * @param contractAddress - The contract address to search logs for.
378
- * @param tags - The tags to search for.
379
- * @param page - The page number (0-indexed) for pagination.
380
- * @param upToBlockNumber - If set, only return logs from blocks up to and including this block number.
381
- * @returns An array of log arrays, one per tag. Returns at most MAX_LOGS_PER_TAG logs per tag per page. If
382
- * MAX_LOGS_PER_TAG logs are returned for a tag, the caller should fetch the next page to check for more logs.
383
- */
384
- async getPublicLogsByTagsFromContract(
385
- contractAddress: AztecAddress,
386
- tags: Tag[],
387
- page: number = 0,
388
- upToBlockNumber?: BlockNumber,
389
- ): Promise<TxScopedL2Log[][]> {
390
- const logs = await Promise.all(
391
- tags.map(tag => {
392
- const key = `${contractAddress.toString()}_${tag.value.toString()}`;
393
- return this.#publicLogsByContractAndTag.getAsync(key);
394
- }),
395
- );
396
- const start = page * MAX_LOGS_PER_TAG;
397
- const end = start + MAX_LOGS_PER_TAG;
398
-
399
- return logs.map(logBuffers => {
400
- const deserialized = logBuffers?.slice(start, end).map(buf => TxScopedL2Log.fromBuffer(buf)) ?? [];
401
- if (upToBlockNumber !== undefined) {
402
- const cutoff = deserialized.findIndex(log => log.blockNumber > upToBlockNumber);
403
- if (cutoff !== -1) {
404
- return deserialized.slice(0, cutoff);
206
+ async #runQuery(query: PrivateLogsQuery | PublicLogsQuery, contractHex: string | undefined): Promise<LogResult[][]> {
207
+ const isPublic = contractHex !== undefined;
208
+ const tags = (query.tags as ReadonlyArray<TagQuery<Tag | SiloedTag>>) ?? [];
209
+ const primaryMap = isPublic ? this.#publicLogs : this.#privateLogs;
210
+
211
+ // referenceBlock reorg check, in-transaction, against the same db the log primary maps live on. The
212
+ // genesis block is a valid anchor during early sync but is synthetic and never indexed in the block
213
+ // store, so resolve it directly to the genesis block number rather than mistaking it for a reorg.
214
+ let referenceBlockNumber: number | undefined;
215
+ if (query.referenceBlock) {
216
+ if (query.referenceBlock.equals(this.genesisBlockHash)) {
217
+ referenceBlockNumber = INITIAL_L2_BLOCK_NUM - 1;
218
+ } else {
219
+ const refBlk = await this.blockStore.getBlockData({ hash: query.referenceBlock });
220
+ if (!refBlk) {
221
+ throw new Error(
222
+ `Reference block ${query.referenceBlock.toString()} not found in the node. This might indicate a reorg has occurred.`,
223
+ );
405
224
  }
225
+ referenceBlockNumber = refBlk.header.globalVariables.blockNumber;
406
226
  }
407
- return deserialized;
408
- });
409
- }
410
-
411
- /**
412
- * Gets public logs based on the provided filter.
413
- * @param filter - The filter to apply to the logs.
414
- * @returns The requested logs.
415
- */
416
- getPublicLogs(filter: LogFilter): Promise<GetPublicLogsResponse> {
417
- if (filter.afterLog) {
418
- return this.#filterPublicLogsBetweenBlocks(filter);
419
- } else if (filter.txHash) {
420
- return this.#filterPublicLogsOfTx(filter);
421
- } else {
422
- return this.#filterPublicLogsBetweenBlocks(filter);
423
227
  }
424
- }
425
228
 
426
- async #filterPublicLogsOfTx(filter: LogFilter): Promise<GetPublicLogsResponse> {
427
- if (!filter.txHash) {
428
- throw new Error('Missing txHash');
229
+ // Compute the exclusive upper-block bound across `toBlock` and `referenceBlock`.
230
+ // `toBlock` is already exclusive; `referenceBlock` caps inclusively, so its exclusive form is +1.
231
+ let upperExclusive: number | undefined;
232
+ if (query.toBlock !== undefined) {
233
+ upperExclusive = query.toBlock;
429
234
  }
430
-
431
- const [blockNumber, txIndex] = (await this.blockStore.getTxLocation(filter.txHash)) ?? [];
432
- if (typeof blockNumber !== 'number' || typeof txIndex !== 'number') {
433
- return { logs: [], maxLogsHit: false };
235
+ if (referenceBlockNumber !== undefined) {
236
+ const refExclusive = referenceBlockNumber + 1;
237
+ upperExclusive = upperExclusive === undefined ? refExclusive : Math.min(upperExclusive, refExclusive);
434
238
  }
435
239
 
436
- const buffer = (await this.#publicLogsByBlock.getAsync(blockNumber)) ?? Buffer.alloc(0);
437
- const publicLogsInBlock: { txHash: TxHash; logs: PublicLog[] }[] = [];
438
- const reader = new BufferReader(buffer);
439
-
440
- const blockHash = this.#unpackBlockHash(reader);
441
-
442
- while (reader.remainingBytes() > 0) {
443
- const indexOfTx = reader.readNumber();
444
- const txHash = reader.readObject(TxHash);
445
- const numLogsInTx = reader.readNumber();
446
- publicLogsInBlock[indexOfTx] = { txHash, logs: [] };
447
- for (let i = 0; i < numLogsInTx; i++) {
448
- publicLogsInBlock[indexOfTx].logs.push(reader.readObject(PublicLog));
240
+ // Resolve txHash -> (blockNumber, txIndexInBlock) once for the whole query.
241
+ let txLocation: [number, number] | undefined;
242
+ if (query.txHash) {
243
+ const loc = await this.blockStore.getTxLocation(query.txHash);
244
+ if (!loc) {
245
+ return tags.map(() => []);
246
+ }
247
+ txLocation = loc;
248
+ if (upperExclusive !== undefined && txLocation[0] >= upperExclusive) {
249
+ return tags.map(() => []);
449
250
  }
450
251
  }
451
252
 
452
- const txData = publicLogsInBlock[txIndex];
453
-
454
- const logs: ExtendedPublicLog[] = [];
455
- const maxLogsHit = this.#accumulatePublicLogs(
456
- logs,
457
- blockNumber,
458
- blockHash,
459
- txIndex,
460
- txData.txHash,
461
- txData.logs,
462
- filter,
463
- );
464
-
465
- return { logs, maxLogsHit };
466
- }
467
-
468
- async #filterPublicLogsBetweenBlocks(filter: LogFilter): Promise<GetPublicLogsResponse> {
469
- const start =
470
- filter.afterLog?.blockNumber ?? Math.max(filter.fromBlock ?? INITIAL_L2_BLOCK_NUM, INITIAL_L2_BLOCK_NUM);
471
- const end = filter.toBlock;
253
+ const fromBlock = query.fromBlock ?? INITIAL_L2_BLOCK_NUM;
254
+ const includeEffects = query.includeEffects === true;
255
+
256
+ const perTagResults: LogResult[][] = [];
257
+ for (const tagEntry of tags) {
258
+ const { tagHex, afterLog } = normalizeTagEntry(tagEntry);
259
+ const prefix = contractHex !== undefined ? encodePublicPrefix(contractHex, tagHex) : tagHex;
260
+
261
+ const end = txLocation
262
+ ? endOfTxRange(prefix, txLocation[0], txLocation[1])
263
+ : endOfTagRange(prefix, upperExclusive);
264
+
265
+ let start: string;
266
+ if (afterLog) {
267
+ // Cursor wins as the start; `fromBlock` is ignored (fine if the cursor sits below it). The cursor
268
+ // carries `(blockNumber, txIndexWithinBlock, logIndexWithinTx)`, which slot directly into the
269
+ // composite key no tx-hash lookup needed.
270
+ start = incKey(encodeKey(prefix, afterLog.blockNumber, afterLog.txIndexWithinBlock, afterLog.logIndexWithinTx));
271
+ } else if (txLocation) {
272
+ start = encodeKey(prefix, txLocation[0], txLocation[1], 0);
273
+ } else {
274
+ start = encodeKey(prefix, fromBlock, 0, 0);
275
+ }
472
276
 
473
- if (typeof end === 'number' && end < start) {
474
- return {
475
- logs: [],
476
- maxLogsHit: true,
477
- };
277
+ const limit = query.limitPerTag ?? MAX_LOGS_PER_TAG;
278
+ const out: LogResult[] = [];
279
+ for await (const [rawKey, rawVal] of primaryMap.entriesAsync({ start, end, limit })) {
280
+ const tail = decodeKeyTail(rawKey);
281
+ const value = decodeValue(rawVal);
282
+ out.push({
283
+ logData: value.logData,
284
+ blockNumber: tail.blockNumber,
285
+ blockHash: value.blockHash,
286
+ blockTimestamp: value.blockTimestamp,
287
+ txHash: value.txHash,
288
+ txIndexWithinBlock: tail.txIndexWithinBlock,
289
+ logIndexWithinTx: tail.logIndexWithinTx,
290
+ });
291
+ }
292
+ perTagResults.push(out);
478
293
  }
479
294
 
480
- const logs: ExtendedPublicLog[] = [];
481
-
482
- let maxLogsHit = false;
483
- loopOverBlocks: for await (const [blockNumber, logBuffer] of this.#publicLogsByBlock.entriesAsync({ start, end })) {
484
- const publicLogsInBlock: { txHash: TxHash; logs: PublicLog[] }[] = [];
485
- const reader = new BufferReader(logBuffer);
486
-
487
- const blockHash = this.#unpackBlockHash(reader);
488
-
489
- while (reader.remainingBytes() > 0) {
490
- const indexOfTx = reader.readNumber();
491
- const txHash = reader.readObject(TxHash);
492
- const numLogsInTx = reader.readNumber();
493
- publicLogsInBlock[indexOfTx] = { txHash, logs: [] };
494
- for (let i = 0; i < numLogsInTx; i++) {
495
- publicLogsInBlock[indexOfTx].logs.push(reader.readObject(PublicLog));
295
+ if (includeEffects) {
296
+ // Dedupe by txHash across the entire page so a tx with many tagged logs costs one fetch.
297
+ const txHashByKey = new Map<string, TxHash>();
298
+ for (const arr of perTagResults) {
299
+ for (const log of arr) {
300
+ txHashByKey.set(log.txHash.toString(), log.txHash);
496
301
  }
497
302
  }
498
- for (let txIndex = filter.afterLog?.txIndex ?? 0; txIndex < publicLogsInBlock.length; txIndex++) {
499
- const txData = publicLogsInBlock[txIndex];
500
- maxLogsHit = this.#accumulatePublicLogs(
501
- logs,
502
- blockNumber,
503
- blockHash,
504
- txIndex,
505
- txData.txHash,
506
- txData.logs,
507
- filter,
508
- );
509
- if (maxLogsHit) {
510
- this.#log.debug(`Max logs hit at block ${blockNumber}`);
511
- break loopOverBlocks;
303
+ const uniqueTxs = Array.from(txHashByKey.values());
304
+ if (uniqueTxs.length > 0) {
305
+ const effects = await this.blockStore.getNoteHashesAndNullifiers(uniqueTxs);
306
+ const byTxHash = new Map<string, [Fr[], Fr[]]>();
307
+ uniqueTxs.forEach((tx, i) => byTxHash.set(tx.toString(), effects[i]));
308
+ for (let i = 0; i < perTagResults.length; i++) {
309
+ perTagResults[i] = perTagResults[i].map(log => {
310
+ const [noteHashes, nullifiers] = byTxHash.get(log.txHash.toString()) ?? [[], []];
311
+ return { ...log, noteHashes, nullifiers };
312
+ });
512
313
  }
513
314
  }
514
315
  }
515
316
 
516
- return { logs, maxLogsHit };
317
+ return perTagResults;
517
318
  }
518
319
 
519
320
  /**
520
- * Gets contract class logs based on the provided filter.
521
- * @param filter - The filter to apply to the logs.
522
- * @returns The requested logs.
321
+ * Reads back every private log indexed for the given block via the per-block secondary index. Order
322
+ * matches the canonical composite-key order (`tag`, `blockNumber`, `txIndexWithinBlock`,
323
+ * `logIndexWithinTx`). Used by the data-store-updater test suite to verify the indexed-vs-block-body
324
+ * counts without depending on the removed `getPublicLogs(LogFilter)` API.
523
325
  */
524
- getContractClassLogs(filter: LogFilter): Promise<GetContractClassLogsResponse> {
525
- if (filter.afterLog) {
526
- return this.#filterContractClassLogsBetweenBlocks(filter);
527
- } else if (filter.txHash) {
528
- return this.#filterContractClassLogsOfTx(filter);
529
- } else {
530
- return this.#filterContractClassLogsBetweenBlocks(filter);
531
- }
532
- }
533
-
534
- async #filterContractClassLogsOfTx(filter: LogFilter): Promise<GetContractClassLogsResponse> {
535
- if (!filter.txHash) {
536
- throw new Error('Missing txHash');
537
- }
538
-
539
- const [blockNumber, txIndex] = (await this.blockStore.getTxLocation(filter.txHash)) ?? [];
540
- if (typeof blockNumber !== 'number' || typeof txIndex !== 'number') {
541
- return { logs: [], maxLogsHit: false };
542
- }
543
- const contractClassLogsBuffer = (await this.#contractClassLogsByBlock.getAsync(blockNumber)) ?? Buffer.alloc(0);
544
- const contractClassLogsInBlock: { txHash: TxHash; logs: ContractClassLog[] }[] = [];
545
-
546
- const reader = new BufferReader(contractClassLogsBuffer);
547
- const blockHash = this.#unpackBlockHash(reader);
548
-
549
- while (reader.remainingBytes() > 0) {
550
- const indexOfTx = reader.readNumber();
551
- const txHash = reader.readObject(TxHash);
552
- const numLogsInTx = reader.readNumber();
553
- contractClassLogsInBlock[indexOfTx] = { txHash, logs: [] };
554
- for (let i = 0; i < numLogsInTx; i++) {
555
- contractClassLogsInBlock[indexOfTx].logs.push(reader.readObject(ContractClassLog));
556
- }
557
- }
558
-
559
- const txData = contractClassLogsInBlock[txIndex];
560
-
561
- const logs: ExtendedContractClassLog[] = [];
562
- const maxLogsHit = this.#accumulateContractClassLogs(
563
- logs,
564
- blockNumber,
565
- blockHash,
566
- txIndex,
567
- txData.txHash,
568
- txData.logs,
569
- filter,
326
+ getPrivateLogsForBlock(blockNumber: number): Promise<LogResult[]> {
327
+ return this.db.transactionAsync(() =>
328
+ this.#readBlockLogs(this.#privateKeysByBlock, this.#privateLogs, blockNumber),
570
329
  );
571
-
572
- return { logs, maxLogsHit };
573
330
  }
574
331
 
575
- async #filterContractClassLogsBetweenBlocks(filter: LogFilter): Promise<GetContractClassLogsResponse> {
576
- const start =
577
- filter.afterLog?.blockNumber ?? Math.max(filter.fromBlock ?? INITIAL_L2_BLOCK_NUM, INITIAL_L2_BLOCK_NUM);
578
- const end = filter.toBlock;
579
-
580
- if (typeof end === 'number' && end < start) {
581
- return {
582
- logs: [],
583
- maxLogsHit: true,
584
- };
585
- }
586
-
587
- const logs: ExtendedContractClassLog[] = [];
588
-
589
- let maxLogsHit = false;
590
- loopOverBlocks: for await (const [blockNumber, logBuffer] of this.#contractClassLogsByBlock.entriesAsync({
591
- start,
592
- end,
593
- })) {
594
- const contractClassLogsInBlock: { txHash: TxHash; logs: ContractClassLog[] }[] = [];
595
- const reader = new BufferReader(logBuffer);
596
- const blockHash = this.#unpackBlockHash(reader);
597
- while (reader.remainingBytes() > 0) {
598
- const indexOfTx = reader.readNumber();
599
- const txHash = reader.readObject(TxHash);
600
- const numLogsInTx = reader.readNumber();
601
- contractClassLogsInBlock[indexOfTx] = { txHash, logs: [] };
602
- for (let i = 0; i < numLogsInTx; i++) {
603
- contractClassLogsInBlock[indexOfTx].logs.push(reader.readObject(ContractClassLog));
604
- }
605
- }
606
- for (let txIndex = filter.afterLog?.txIndex ?? 0; txIndex < contractClassLogsInBlock.length; txIndex++) {
607
- const txData = contractClassLogsInBlock[txIndex];
608
- maxLogsHit = this.#accumulateContractClassLogs(
609
- logs,
610
- blockNumber,
611
- blockHash,
612
- txIndex,
613
- txData.txHash,
614
- txData.logs,
615
- filter,
616
- );
617
- if (maxLogsHit) {
618
- this.#log.debug(`Max logs hit at block ${blockNumber}`);
619
- break loopOverBlocks;
620
- }
621
- }
622
- }
623
-
624
- return { logs, maxLogsHit };
332
+ /** {@inheritDoc LogStore.getPrivateLogsForBlock} */
333
+ getPublicLogsForBlock(blockNumber: number): Promise<LogResult[]> {
334
+ return this.db.transactionAsync(() => this.#readBlockLogs(this.#publicKeysByBlock, this.#publicLogs, blockNumber));
625
335
  }
626
336
 
627
- #accumulatePublicLogs(
628
- results: ExtendedPublicLog[],
337
+ async #readBlockLogs(
338
+ keysByBlock: AztecAsyncMap<number, string[]>,
339
+ primaryMap: AztecAsyncMap<string, Buffer>,
629
340
  blockNumber: number,
630
- blockHash: BlockHash,
631
- txIndex: number,
632
- txHash: TxHash,
633
- txLogs: PublicLog[],
634
- filter: LogFilter = {},
635
- ): boolean {
636
- if (filter.fromBlock && blockNumber < filter.fromBlock) {
637
- return false;
341
+ ): Promise<LogResult[]> {
342
+ const keys = await keysByBlock.getAsync(blockNumber);
343
+ if (!keys || keys.length === 0) {
344
+ return [];
638
345
  }
639
- if (filter.toBlock && blockNumber >= filter.toBlock) {
640
- return false;
641
- }
642
- if (filter.txHash && !txHash.equals(filter.txHash)) {
643
- return false;
644
- }
645
-
646
- let maxLogsHit = false;
647
- let logIndex = typeof filter.afterLog?.logIndex === 'number' ? filter.afterLog.logIndex + 1 : 0;
648
- for (; logIndex < txLogs.length; logIndex++) {
649
- const log = txLogs[logIndex];
650
- if (
651
- (!filter.contractAddress || log.contractAddress.equals(filter.contractAddress)) &&
652
- (!filter.tag || log.fields[0]?.equals(filter.tag))
653
- ) {
654
- results.push(
655
- new ExtendedPublicLog(new LogId(BlockNumber(blockNumber), blockHash, txHash, txIndex, logIndex), log),
656
- );
657
-
658
- if (results.length >= this.#logsMaxPageSize) {
659
- maxLogsHit = true;
660
- break;
661
- }
346
+ const results: LogResult[] = [];
347
+ for (const key of keys) {
348
+ const raw = await primaryMap.getAsync(key);
349
+ if (!raw) {
350
+ continue;
662
351
  }
352
+ const tail = decodeKeyTail(key);
353
+ const value = decodeValue(raw);
354
+ results.push({
355
+ logData: value.logData,
356
+ blockNumber: tail.blockNumber,
357
+ blockHash: value.blockHash,
358
+ blockTimestamp: value.blockTimestamp,
359
+ txHash: value.txHash,
360
+ txIndexWithinBlock: tail.txIndexWithinBlock,
361
+ logIndexWithinTx: tail.logIndexWithinTx,
362
+ });
663
363
  }
664
-
665
- return maxLogsHit;
364
+ return results;
666
365
  }
366
+ }
667
367
 
668
- #accumulateContractClassLogs(
669
- results: ExtendedContractClassLog[],
670
- blockNumber: number,
671
- blockHash: BlockHash,
672
- txIndex: number,
673
- txHash: TxHash,
674
- txLogs: ContractClassLog[],
675
- filter: LogFilter = {},
676
- ): boolean {
677
- if (filter.fromBlock && blockNumber < filter.fromBlock) {
678
- return false;
679
- }
680
- if (filter.toBlock && blockNumber >= filter.toBlock) {
681
- return false;
682
- }
683
- if (filter.txHash && !txHash.equals(filter.txHash)) {
684
- return false;
685
- }
686
-
687
- let maxLogsHit = false;
688
- let logIndex = typeof filter.afterLog?.logIndex === 'number' ? filter.afterLog.logIndex + 1 : 0;
689
- for (; logIndex < txLogs.length; logIndex++) {
690
- const log = txLogs[logIndex];
691
- if (!filter.contractAddress || log.contractAddress.equals(filter.contractAddress)) {
692
- results.push(
693
- new ExtendedContractClassLog(new LogId(BlockNumber(blockNumber), blockHash, txHash, txIndex, logIndex), log),
694
- );
695
-
696
- if (results.length >= this.#logsMaxPageSize) {
697
- maxLogsHit = true;
698
- break;
699
- }
700
- }
701
- }
702
-
703
- return maxLogsHit;
368
+ /** Pulls `{ tagHex, afterLog }` out of a {@link TagQuery}, normalizing the bare-tag form. */
369
+ function normalizeTagEntry<T extends Tag | SiloedTag>(
370
+ entry: TagQuery<T>,
371
+ ): {
372
+ tagHex: string;
373
+ afterLog: LogCursor | undefined;
374
+ } {
375
+ if (typeof entry === 'object' && entry !== null && 'tag' in entry) {
376
+ return { tagHex: fieldHex(entry.tag.value), afterLog: entry.afterLog };
704
377
  }
378
+ return { tagHex: fieldHex((entry as T).value), afterLog: undefined };
705
379
  }