@aztec/archiver 0.72.1 → 0.74.0

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 (62) hide show
  1. package/dest/archiver/archiver.d.ts +2 -2
  2. package/dest/archiver/archiver.d.ts.map +1 -1
  3. package/dest/archiver/archiver.js +31 -16
  4. package/dest/archiver/archiver_store.d.ts +2 -2
  5. package/dest/archiver/archiver_store.d.ts.map +1 -1
  6. package/dest/archiver/archiver_store_test_suite.d.ts +1 -1
  7. package/dest/archiver/archiver_store_test_suite.d.ts.map +1 -1
  8. package/dest/archiver/archiver_store_test_suite.js +14 -14
  9. package/dest/archiver/config.d.ts +1 -1
  10. package/dest/archiver/config.d.ts.map +1 -1
  11. package/dest/archiver/config.js +4 -4
  12. package/dest/archiver/data_retrieval.d.ts +3 -2
  13. package/dest/archiver/data_retrieval.d.ts.map +1 -1
  14. package/dest/archiver/data_retrieval.js +83 -16
  15. package/dest/archiver/errors.d.ts +4 -0
  16. package/dest/archiver/errors.d.ts.map +1 -0
  17. package/dest/archiver/errors.js +6 -0
  18. package/dest/archiver/kv_archiver_store/block_store.d.ts +16 -16
  19. package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
  20. package/dest/archiver/kv_archiver_store/block_store.js +53 -53
  21. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +5 -5
  22. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +1 -1
  23. package/dest/archiver/kv_archiver_store/contract_class_store.js +13 -12
  24. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +3 -3
  25. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
  26. package/dest/archiver/kv_archiver_store/contract_instance_store.js +3 -3
  27. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +3 -7
  28. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
  29. package/dest/archiver/kv_archiver_store/kv_archiver_store.js +39 -61
  30. package/dest/archiver/kv_archiver_store/log_store.d.ts +5 -5
  31. package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
  32. package/dest/archiver/kv_archiver_store/log_store.js +54 -53
  33. package/dest/archiver/kv_archiver_store/message_store.d.ts +6 -6
  34. package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
  35. package/dest/archiver/kv_archiver_store/message_store.js +16 -16
  36. package/dest/archiver/kv_archiver_store/nullifier_store.d.ts +2 -2
  37. package/dest/archiver/kv_archiver_store/nullifier_store.d.ts.map +1 -1
  38. package/dest/archiver/kv_archiver_store/nullifier_store.js +31 -22
  39. package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts +2 -2
  40. package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts.map +1 -1
  41. package/dest/archiver/memory_archiver_store/memory_archiver_store.js +24 -22
  42. package/dest/factory.js +7 -7
  43. package/dest/test/mock_l2_block_source.d.ts +2 -2
  44. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  45. package/dest/test/mock_l2_block_source.js +12 -9
  46. package/package.json +12 -12
  47. package/src/archiver/archiver.ts +38 -18
  48. package/src/archiver/archiver_store.ts +1 -1
  49. package/src/archiver/archiver_store_test_suite.ts +17 -14
  50. package/src/archiver/config.ts +4 -4
  51. package/src/archiver/data_retrieval.ts +108 -12
  52. package/src/archiver/errors.ts +5 -0
  53. package/src/archiver/kv_archiver_store/block_store.ts +66 -67
  54. package/src/archiver/kv_archiver_store/contract_class_store.ts +17 -15
  55. package/src/archiver/kv_archiver_store/contract_instance_store.ts +5 -5
  56. package/src/archiver/kv_archiver_store/kv_archiver_store.ts +43 -62
  57. package/src/archiver/kv_archiver_store/log_store.ts +79 -71
  58. package/src/archiver/kv_archiver_store/message_store.ts +22 -22
  59. package/src/archiver/kv_archiver_store/nullifier_store.ts +48 -30
  60. package/src/archiver/memory_archiver_store/memory_archiver_store.ts +44 -43
  61. package/src/factory.ts +10 -8
  62. package/src/test/mock_l2_block_source.ts +18 -16
@@ -30,7 +30,10 @@ import { type L1Published } from './structs/published.js';
30
30
  * @param testName - The name of the test suite.
31
31
  * @param getStore - Returns an instance of a store that's already been initialized.
32
32
  */
33
- export function describeArchiverDataStore(testName: string, getStore: () => ArchiverDataStore) {
33
+ export function describeArchiverDataStore(
34
+ testName: string,
35
+ getStore: () => ArchiverDataStore | Promise<ArchiverDataStore>,
36
+ ) {
34
37
  describe(testName, () => {
35
38
  let store: ArchiverDataStore;
36
39
  let blocks: L1Published<L2Block>[];
@@ -52,7 +55,7 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
52
55
  });
53
56
 
54
57
  beforeEach(async () => {
55
- store = getStore();
58
+ store = await getStore();
56
59
  blocks = await timesParallel(10, async i => makeL1Published(await L2Block.random(i + 1), i + 10));
57
60
  });
58
61
 
@@ -209,7 +212,7 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
209
212
  () => wrapInBlock(blocks[5].data.body.txEffects[2], blocks[5].data),
210
213
  () => wrapInBlock(blocks[1].data.body.txEffects[0], blocks[1].data),
211
214
  ])('retrieves a previously stored transaction', async getExpectedTx => {
212
- const expectedTx = getExpectedTx();
215
+ const expectedTx = await getExpectedTx();
213
216
  const actualTx = await store.getTxEffect(expectedTx.data.txHash);
214
217
  expect(actualTx).toEqual(expectedTx);
215
218
  });
@@ -227,7 +230,7 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
227
230
  ])('tries to retrieves a previously stored transaction after deleted', async getExpectedTx => {
228
231
  await store.unwindBlocks(blocks.length, blocks.length);
229
232
 
230
- const expectedTx = getExpectedTx();
233
+ const expectedTx = await getExpectedTx();
231
234
  const actualTx = await store.getTxEffect(expectedTx.data.txHash);
232
235
  expect(actualTx).toEqual(undefined);
233
236
  });
@@ -300,10 +303,10 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
300
303
  const blockNum = 10;
301
304
 
302
305
  beforeEach(async () => {
303
- contractClass = makeContractClassPublic();
306
+ contractClass = await makeContractClassPublic();
304
307
  await store.addContractClasses(
305
308
  [contractClass],
306
- [computePublicBytecodeCommitment(contractClass.packedBytecode)],
309
+ [await computePublicBytecodeCommitment(contractClass.packedBytecode)],
307
310
  blockNum,
308
311
  );
309
312
  });
@@ -320,7 +323,7 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
320
323
  it('returns contract class if later "deployment" class was deleted', async () => {
321
324
  await store.addContractClasses(
322
325
  [contractClass],
323
- [computePublicBytecodeCommitment(contractClass.packedBytecode)],
326
+ [await computePublicBytecodeCommitment(contractClass.packedBytecode)],
324
327
  blockNum + 1,
325
328
  );
326
329
  await store.deleteContractClasses([contractClass], blockNum + 1);
@@ -374,11 +377,11 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
374
377
  new Fr((blockNumber * 100 + txIndex * 10 + logIndex) * (isPublic ? 123 : 1));
375
378
 
376
379
  // See parseLogFromPublic
377
- const makeLengthsField = (publicValuesLen: number, privateValuesLen: number, ciphertextLen: number) => {
380
+ // Search the codebase for "disgusting encoding" to see other hardcoded instances of this encoding, that you might need to change if you ever find yourself here.
381
+ const makeLengthsField = (publicValuesLen: number, privateValuesLen: number) => {
378
382
  const buf = Buffer.alloc(32);
379
- buf.writeUint16BE(publicValuesLen, 24);
380
- buf.writeUint16BE(privateValuesLen, 27);
381
- buf.writeUint16BE(ciphertextLen, 30);
383
+ buf.writeUint16BE(publicValuesLen, 27);
384
+ buf.writeUint16BE(privateValuesLen, 30);
382
385
  return Fr.fromBuffer(buf);
383
386
  };
384
387
 
@@ -390,7 +393,7 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
390
393
  const makePublicLog = (tag: Fr) =>
391
394
  PublicLog.fromFields([
392
395
  AztecAddress.fromNumber(1).toField(), // log address
393
- makeLengthsField(2, PUBLIC_LOG_DATA_SIZE_IN_FIELDS - 3, 42), // field 0
396
+ makeLengthsField(2, PUBLIC_LOG_DATA_SIZE_IN_FIELDS - 3), // field 0
394
397
  tag, // field 1
395
398
  ...times(PUBLIC_LOG_DATA_SIZE_IN_FIELDS - 1, i => new Fr(tag.toNumber() + i)), // fields 2 to end
396
399
  ]);
@@ -532,13 +535,13 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
532
535
  const invalidLogs = [
533
536
  PublicLog.fromFields([
534
537
  AztecAddress.fromNumber(1).toField(),
535
- makeLengthsField(2, 3, 42), // This field claims we have 5 items, but we actually have more
538
+ makeLengthsField(2, 3), // This field claims we have 5 items, but we actually have more
536
539
  tag,
537
540
  ...times(PUBLIC_LOG_DATA_SIZE_IN_FIELDS - 1, i => new Fr(tag.toNumber() + i)),
538
541
  ]),
539
542
  PublicLog.fromFields([
540
543
  AztecAddress.fromNumber(1).toField(),
541
- makeLengthsField(2, PUBLIC_LOG_DATA_SIZE_IN_FIELDS, 42), // This field claims we have more than the max items
544
+ makeLengthsField(2, PUBLIC_LOG_DATA_SIZE_IN_FIELDS), // This field claims we have more than the max items
542
545
  tag,
543
546
  ...times(PUBLIC_LOG_DATA_SIZE_IN_FIELDS - 1, i => new Fr(tag.toNumber() + i)),
544
547
  ]),
@@ -22,7 +22,7 @@ export type ArchiverConfig = {
22
22
  archiverUrl?: string;
23
23
 
24
24
  /** URL for an L1 consensus client */
25
- l1ConsensusClientUrl: string;
25
+ l1ConsensusHostUrl?: string;
26
26
 
27
27
  /** The polling interval in ms for retrieving new L2 blocks and encrypted logs. */
28
28
  archiverPollingIntervalMS?: number;
@@ -47,10 +47,10 @@ export const archiverConfigMappings: ConfigMappingsType<ArchiverConfig> = {
47
47
  description:
48
48
  'URL for an archiver service. If set, will return an archiver client as opposed to starting a new one.',
49
49
  },
50
- l1ConsensusClientUrl: {
51
- env: 'L1_CONSENSUS_CLIENT_URL',
50
+ l1ConsensusHostUrl: {
51
+ env: 'L1_CONSENSUS_HOST_URL',
52
52
  description: 'URL for an L1 consensus client.',
53
- parseEnv: (val: string) => (val ? val : 'http://localhost:5052'),
53
+ parseEnv: (val: string) => val,
54
54
  },
55
55
  archiverPollingIntervalMS: {
56
56
  env: 'ARCHIVER_POLLING_INTERVAL_MS',
@@ -1,12 +1,13 @@
1
+ import { type BlobSinkClientInterface } from '@aztec/blob-sink/client';
1
2
  import { Body, InboxLeaf, L2Block } from '@aztec/circuit-types';
2
3
  import { AppendOnlyTreeSnapshot, BlockHeader, Fr, Proof } from '@aztec/circuits.js';
3
4
  import { asyncPool } from '@aztec/foundation/async-pool';
4
- import { Blob } from '@aztec/foundation/blob';
5
+ import { Blob, BlobDeserializationError } from '@aztec/foundation/blob';
5
6
  import { type EthAddress } from '@aztec/foundation/eth-address';
6
7
  import { type ViemSignature } from '@aztec/foundation/eth-signature';
7
8
  import { type Logger, createLogger } from '@aztec/foundation/log';
8
9
  import { numToUInt32BE } from '@aztec/foundation/serialize';
9
- import { type InboxAbi, RollupAbi } from '@aztec/l1-artifacts';
10
+ import { ForwarderAbi, type InboxAbi, RollupAbi } from '@aztec/l1-artifacts';
10
11
 
11
12
  import {
12
13
  type Chain,
@@ -20,6 +21,7 @@ import {
20
21
  hexToBytes,
21
22
  } from 'viem';
22
23
 
24
+ import { NoBlobBodiesFoundError } from './errors.js';
23
25
  import { type DataRetrieval } from './structs/data_retrieval.js';
24
26
  import { type L1Published, type L1PublishedData } from './structs/published.js';
25
27
 
@@ -35,6 +37,7 @@ import { type L1Published, type L1PublishedData } from './structs/published.js';
35
37
  export async function retrieveBlocksFromRollup(
36
38
  rollup: GetContractReturnType<typeof RollupAbi, PublicClient<HttpTransport, Chain>>,
37
39
  publicClient: PublicClient,
40
+ blobSinkClient: BlobSinkClientInterface,
38
41
  searchStartBlock: bigint,
39
42
  searchEndBlock: bigint,
40
43
  logger: Logger = createLogger('archiver'),
@@ -63,7 +66,13 @@ export async function retrieveBlocksFromRollup(
63
66
  `Got ${l2BlockProposedLogs.length} L2 block processed logs for L2 blocks ${l2BlockProposedLogs[0].args.blockNumber}-${lastLog.args.blockNumber} between L1 blocks ${searchStartBlock}-${searchEndBlock}`,
64
67
  );
65
68
 
66
- const newBlocks = await processL2BlockProposedLogs(rollup, publicClient, l2BlockProposedLogs, logger);
69
+ const newBlocks = await processL2BlockProposedLogs(
70
+ rollup,
71
+ publicClient,
72
+ blobSinkClient,
73
+ l2BlockProposedLogs,
74
+ logger,
75
+ );
67
76
  retrievedBlocks.push(...newBlocks);
68
77
  searchStartBlock = lastLog.blockNumber! + 1n;
69
78
  } while (searchStartBlock <= searchEndBlock);
@@ -80,6 +89,7 @@ export async function retrieveBlocksFromRollup(
80
89
  export async function processL2BlockProposedLogs(
81
90
  rollup: GetContractReturnType<typeof RollupAbi, PublicClient<HttpTransport, Chain>>,
82
91
  publicClient: PublicClient,
92
+ blobSinkClient: BlobSinkClientInterface,
83
93
  logs: GetContractEventsReturnType<typeof RollupAbi, 'L2BlockProposed'>,
84
94
  logger: Logger,
85
95
  ): Promise<L1Published<L2Block>[]> {
@@ -88,10 +98,19 @@ export async function processL2BlockProposedLogs(
88
98
  const l2BlockNumber = log.args.blockNumber!;
89
99
  const archive = log.args.archive!;
90
100
  const archiveFromChain = await rollup.read.archiveAt([l2BlockNumber]);
101
+ const blobHashes = log.args.versionedBlobHashes!.map(blobHash => Buffer.from(blobHash.slice(2), 'hex'));
91
102
 
92
103
  // The value from the event and contract will match only if the block is in the chain.
93
104
  if (archive === archiveFromChain) {
94
- const block = await getBlockFromRollupTx(publicClient, log.transactionHash!, l2BlockNumber);
105
+ const block = await getBlockFromRollupTx(
106
+ publicClient,
107
+ blobSinkClient,
108
+ log.transactionHash!,
109
+ blobHashes,
110
+ l2BlockNumber,
111
+ rollup.address,
112
+ logger,
113
+ );
95
114
 
96
115
  const l1: L1PublishedData = {
97
116
  blockNumber: log.blockNumber,
@@ -116,6 +135,57 @@ export async function getL1BlockTime(publicClient: PublicClient, blockNumber: bi
116
135
  return block.timestamp;
117
136
  }
118
137
 
138
+ /**
139
+ * Extracts the first 'propose' method calldata from a forwarder transaction's data.
140
+ * @param forwarderData - The forwarder transaction input data
141
+ * @param rollupAddress - The address of the rollup contract
142
+ * @returns The calldata for the first 'propose' method call to the rollup contract
143
+ */
144
+ function extractRollupProposeCalldata(forwarderData: Hex, rollupAddress: Hex): Hex {
145
+ // TODO(#11451): custom forwarders
146
+ const { functionName: forwarderFunctionName, args: forwarderArgs } = decodeFunctionData({
147
+ abi: ForwarderAbi,
148
+ data: forwarderData,
149
+ });
150
+
151
+ if (forwarderFunctionName !== 'forward') {
152
+ throw new Error(`Unexpected forwarder method called ${forwarderFunctionName}`);
153
+ }
154
+
155
+ if (forwarderArgs.length !== 2) {
156
+ throw new Error(`Unexpected number of arguments for forwarder`);
157
+ }
158
+
159
+ const [to, data] = forwarderArgs;
160
+
161
+ // Find all rollup calls
162
+ const rollupAddressLower = rollupAddress.toLowerCase();
163
+
164
+ for (let i = 0; i < to.length; i++) {
165
+ const addr = to[i];
166
+ if (addr.toLowerCase() !== rollupAddressLower) {
167
+ continue;
168
+ }
169
+ const callData = data[i];
170
+
171
+ try {
172
+ const { functionName: rollupFunctionName } = decodeFunctionData({
173
+ abi: RollupAbi,
174
+ data: callData,
175
+ });
176
+
177
+ if (rollupFunctionName === 'propose') {
178
+ return callData;
179
+ }
180
+ } catch (err) {
181
+ // Skip invalid function data
182
+ continue;
183
+ }
184
+ }
185
+
186
+ throw new Error(`Rollup address not found in forwarder args`);
187
+ }
188
+
119
189
  /**
120
190
  * Gets block from the calldata of an L1 transaction.
121
191
  * Assumes that the block was published from an EOA.
@@ -127,19 +197,27 @@ export async function getL1BlockTime(publicClient: PublicClient, blockNumber: bi
127
197
  */
128
198
  async function getBlockFromRollupTx(
129
199
  publicClient: PublicClient,
200
+ blobSinkClient: BlobSinkClientInterface,
130
201
  txHash: `0x${string}`,
202
+ blobHashes: Buffer[], // WORKTODO(md): buffer32?
131
203
  l2BlockNum: bigint,
204
+ rollupAddress: Hex,
205
+ logger: Logger,
132
206
  ): Promise<L2Block> {
133
- const { input: data } = await publicClient.getTransaction({ hash: txHash });
134
- const { functionName, args } = decodeFunctionData({ abi: RollupAbi, data });
207
+ const { input: forwarderData, blockHash } = await publicClient.getTransaction({ hash: txHash });
135
208
 
136
- const allowedMethods = ['propose', 'proposeAndClaim'];
209
+ const rollupData = extractRollupProposeCalldata(forwarderData, rollupAddress);
210
+ const { functionName: rollupFunctionName, args: rollupArgs } = decodeFunctionData({
211
+ abi: RollupAbi,
212
+ data: rollupData,
213
+ });
137
214
 
138
- if (!allowedMethods.includes(functionName)) {
139
- throw new Error(`Unexpected method called ${functionName}`);
215
+ if (rollupFunctionName !== 'propose') {
216
+ throw new Error(`Unexpected rollup method called ${rollupFunctionName}`);
140
217
  }
218
+
141
219
  // TODO(#9101): 'bodyHex' will be removed from below
142
- const [decodedArgs, , bodyHex, blobInputs] = args! as readonly [
220
+ const [decodedArgs, , bodyHex, blobInputs] = rollupArgs! as readonly [
143
221
  {
144
222
  header: Hex;
145
223
  archive: Hex;
@@ -156,12 +234,30 @@ async function getBlockFromRollupTx(
156
234
  ];
157
235
 
158
236
  const header = BlockHeader.fromBuffer(Buffer.from(hexToBytes(decodedArgs.header)));
237
+ const blobBodies = await blobSinkClient.getBlobSidecar(blockHash, blobHashes);
238
+ if (blobBodies.length === 0) {
239
+ throw new NoBlobBodiesFoundError(Number(l2BlockNum));
240
+ }
241
+
242
+ // TODO(#9101): Once calldata is removed, we can remove this field encoding and update
243
+ // Body.fromBlobFields to accept blob buffers directly
244
+ let blockFields: Fr[];
245
+ try {
246
+ blockFields = blobBodies.flatMap(b => b.toEncodedFields());
247
+ } catch (err: any) {
248
+ if (err instanceof BlobDeserializationError) {
249
+ logger.fatal(err.message);
250
+ } else {
251
+ logger.fatal('Unable to sync: failed to decode fetched blob, this blob was likely not created by us');
252
+ }
253
+ throw err;
254
+ }
255
+
159
256
  // TODO(#9101): Retreiving the block body from calldata is a temporary soln before we have
160
257
  // either a beacon chain client or link to some blob store. Web2 is ok because we will
161
258
  // verify the block body vs the blob as below.
162
259
  const blockBody = Body.fromBuffer(Buffer.from(hexToBytes(bodyHex)));
163
260
 
164
- const blockFields = blockBody.toBlobFields();
165
261
  // TODO(#9101): The below reconstruction is currently redundant, but once we extract blobs will be the way to construct blocks.
166
262
  // The blob source will give us blockFields, and we must construct the body from them:
167
263
  // TODO(#8954): When logs are refactored into fields, we won't need to inject them here.
@@ -173,7 +269,7 @@ async function getBlockFromRollupTx(
173
269
  }
174
270
 
175
271
  // TODO(#9101): Once we stop publishing calldata, we will still need the blobCheck below to ensure that the block we are building does correspond to the blob fields
176
- const blobCheck = Blob.getBlobs(blockFields);
272
+ const blobCheck = await Blob.getBlobs(blockFields);
177
273
  if (Blob.getEthBlobEvaluationInputs(blobCheck) !== blobInputs) {
178
274
  // NB: We can just check the blobhash here, which is the first 32 bytes of blobInputs
179
275
  // A mismatch means that the fields published in the blob in propose() do NOT match those in the extracted block.
@@ -0,0 +1,5 @@
1
+ export class NoBlobBodiesFoundError extends Error {
2
+ constructor(l2BlockNum: number) {
3
+ super(`No blob bodies found for block ${l2BlockNum}`);
4
+ }
5
+ }
@@ -1,7 +1,8 @@
1
1
  import { Body, type InBlock, L2Block, L2BlockHash, type TxEffect, type TxHash, TxReceipt } from '@aztec/circuit-types';
2
2
  import { AppendOnlyTreeSnapshot, type AztecAddress, BlockHeader, INITIAL_L2_BLOCK_NUM } from '@aztec/circuits.js';
3
+ import { toArray } from '@aztec/foundation/iterable';
3
4
  import { createLogger } from '@aztec/foundation/log';
4
- import { type AztecKVStore, type AztecMap, type AztecSingleton, type Range } from '@aztec/kv-store';
5
+ import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncSingleton, Range } from '@aztec/kv-store';
5
6
 
6
7
  import { type L1Published, type L1PublishedData } from '../structs/published.js';
7
8
 
@@ -18,29 +19,29 @@ type BlockStorage = {
18
19
  */
19
20
  export class BlockStore {
20
21
  /** Map block number to block data */
21
- #blocks: AztecMap<number, BlockStorage>;
22
+ #blocks: AztecAsyncMap<number, BlockStorage>;
22
23
 
23
24
  /** Map block hash to block body */
24
- #blockBodies: AztecMap<string, Buffer>;
25
+ #blockBodies: AztecAsyncMap<string, Buffer>;
25
26
 
26
27
  /** Stores L1 block number in which the last processed L2 block was included */
27
- #lastSynchedL1Block: AztecSingleton<bigint>;
28
+ #lastSynchedL1Block: AztecAsyncSingleton<bigint>;
28
29
 
29
30
  /** Stores l2 block number of the last proven block */
30
- #lastProvenL2Block: AztecSingleton<number>;
31
+ #lastProvenL2Block: AztecAsyncSingleton<number>;
31
32
 
32
33
  /** Stores l2 epoch number of the last proven epoch */
33
- #lastProvenL2Epoch: AztecSingleton<number>;
34
+ #lastProvenL2Epoch: AztecAsyncSingleton<number>;
34
35
 
35
36
  /** Index mapping transaction hash (as a string) to its location in a block */
36
- #txIndex: AztecMap<string, BlockIndexValue>;
37
+ #txIndex: AztecAsyncMap<string, BlockIndexValue>;
37
38
 
38
39
  /** Index mapping a contract's address (as a string) to its location in a block */
39
- #contractIndex: AztecMap<string, BlockIndexValue>;
40
+ #contractIndex: AztecAsyncMap<string, BlockIndexValue>;
40
41
 
41
42
  #log = createLogger('archiver:block_store');
42
43
 
43
- constructor(private db: AztecKVStore) {
44
+ constructor(private db: AztecAsyncKVStore) {
44
45
  this.#blocks = db.openMap('archiver_blocks');
45
46
  this.#blockBodies = db.openMap('archiver_block_bodies');
46
47
  this.#txIndex = db.openMap('archiver_tx_index');
@@ -55,28 +56,28 @@ export class BlockStore {
55
56
  * @param blocks - The L2 blocks to be added to the store.
56
57
  * @returns True if the operation is successful.
57
58
  */
58
- addBlocks(blocks: L1Published<L2Block>[]): Promise<boolean> {
59
+ async addBlocks(blocks: L1Published<L2Block>[]): Promise<boolean> {
59
60
  if (blocks.length === 0) {
60
- return Promise.resolve(true);
61
+ return true;
61
62
  }
62
63
 
63
- return this.db.transaction(() => {
64
+ return await this.db.transactionAsync(async () => {
64
65
  for (const block of blocks) {
65
- void this.#blocks.set(block.data.number, {
66
+ await this.#blocks.set(block.data.number, {
66
67
  header: block.data.header.toBuffer(),
67
68
  archive: block.data.archive.toBuffer(),
68
69
  l1: block.l1,
69
70
  });
70
71
 
71
- block.data.body.txEffects.forEach((tx, i) => {
72
- void this.#txIndex.set(tx.txHash.toString(), [block.data.number, i]);
73
- });
72
+ for (let i = 0; i < block.data.body.txEffects.length; i++) {
73
+ const txEffect = block.data.body.txEffects[i];
74
+ await this.#txIndex.set(txEffect.txHash.toString(), [block.data.number, i]);
75
+ }
74
76
 
75
- void this.#blockBodies.set(block.data.hash().toString(), block.data.body.toBuffer());
77
+ await this.#blockBodies.set((await block.data.hash()).toString(), block.data.body.toBuffer());
76
78
  }
77
79
 
78
- void this.#lastSynchedL1Block.set(blocks[blocks.length - 1].l1.blockNumber);
79
-
80
+ await this.#lastSynchedL1Block.set(blocks[blocks.length - 1].l1.blockNumber);
80
81
  return true;
81
82
  });
82
83
  }
@@ -88,26 +89,24 @@ export class BlockStore {
88
89
  * @param blocksToUnwind - The number of blocks we are to unwind
89
90
  * @returns True if the operation is successful
90
91
  */
91
- unwindBlocks(from: number, blocksToUnwind: number) {
92
- return this.db.transaction(() => {
93
- const last = this.getSynchedL2BlockNumber();
94
- if (from != last) {
92
+ async unwindBlocks(from: number, blocksToUnwind: number) {
93
+ return await this.db.transactionAsync(async () => {
94
+ const last = await this.getSynchedL2BlockNumber();
95
+ if (from !== last) {
95
96
  throw new Error(`Can only unwind blocks from the tip (requested ${from} but current tip is ${last})`);
96
97
  }
97
98
 
98
99
  for (let i = 0; i < blocksToUnwind; i++) {
99
100
  const blockNumber = from - i;
100
- const block = this.getBlock(blockNumber);
101
+ const block = await this.getBlock(blockNumber);
101
102
 
102
103
  if (block === undefined) {
103
104
  throw new Error(`Cannot remove block ${blockNumber} from the store, we don't have it`);
104
105
  }
105
- void this.#blocks.delete(block.data.number);
106
- block.data.body.txEffects.forEach(tx => {
107
- void this.#txIndex.delete(tx.txHash.toString());
108
- });
109
- const blockHash = block.data.hash().toString();
110
- void this.#blockBodies.delete(blockHash);
106
+ await this.#blocks.delete(block.data.number);
107
+ await Promise.all(block.data.body.txEffects.map(tx => this.#txIndex.delete(tx.txHash.toString())));
108
+ const blockHash = (await block.data.hash()).toString();
109
+ await this.#blockBodies.delete(blockHash);
111
110
  this.#log.debug(`Unwound block ${blockNumber} ${blockHash}`);
112
111
  }
113
112
 
@@ -121,9 +120,10 @@ export class BlockStore {
121
120
  * @param limit - The number of blocks to return.
122
121
  * @returns The requested L2 blocks
123
122
  */
124
- *getBlocks(start: number, limit: number): IterableIterator<L1Published<L2Block>> {
125
- for (const blockStorage of this.#blocks.values(this.#computeBlockRange(start, limit))) {
126
- yield this.getBlockFromBlockStorage(blockStorage);
123
+ async *getBlocks(start: number, limit: number): AsyncIterableIterator<L1Published<L2Block>> {
124
+ for await (const blockStorage of this.#blocks.valuesAsync(this.#computeBlockRange(start, limit))) {
125
+ const block = await this.getBlockFromBlockStorage(blockStorage);
126
+ yield block;
127
127
  }
128
128
  }
129
129
 
@@ -132,10 +132,10 @@ export class BlockStore {
132
132
  * @param blockNumber - The number of the block to return.
133
133
  * @returns The requested L2 block.
134
134
  */
135
- getBlock(blockNumber: number): L1Published<L2Block> | undefined {
136
- const blockStorage = this.#blocks.get(blockNumber);
135
+ async getBlock(blockNumber: number): Promise<L1Published<L2Block> | undefined> {
136
+ const blockStorage = await this.#blocks.getAsync(blockNumber);
137
137
  if (!blockStorage || !blockStorage.header) {
138
- return undefined;
138
+ return Promise.resolve(undefined);
139
139
  }
140
140
 
141
141
  return this.getBlockFromBlockStorage(blockStorage);
@@ -147,17 +147,17 @@ export class BlockStore {
147
147
  * @param limit - The number of blocks to return.
148
148
  * @returns The requested L2 block headers
149
149
  */
150
- *getBlockHeaders(start: number, limit: number): IterableIterator<BlockHeader> {
151
- for (const blockStorage of this.#blocks.values(this.#computeBlockRange(start, limit))) {
150
+ async *getBlockHeaders(start: number, limit: number): AsyncIterableIterator<BlockHeader> {
151
+ for await (const blockStorage of this.#blocks.valuesAsync(this.#computeBlockRange(start, limit))) {
152
152
  yield BlockHeader.fromBuffer(blockStorage.header);
153
153
  }
154
154
  }
155
155
 
156
- private getBlockFromBlockStorage(blockStorage: BlockStorage) {
156
+ private async getBlockFromBlockStorage(blockStorage: BlockStorage) {
157
157
  const header = BlockHeader.fromBuffer(blockStorage.header);
158
158
  const archive = AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive);
159
- const blockHash = header.hash().toString();
160
- const blockBodyBuffer = this.#blockBodies.get(blockHash);
159
+ const blockHash = (await header.hash()).toString();
160
+ const blockBodyBuffer = await this.#blockBodies.getAsync(blockHash);
161
161
  if (blockBodyBuffer === undefined) {
162
162
  throw new Error(
163
163
  `Could not retrieve body for block ${header.globalVariables.blockNumber.toNumber()} ${blockHash}`,
@@ -174,13 +174,13 @@ export class BlockStore {
174
174
  * @param txHash - The txHash of the tx corresponding to the tx effect.
175
175
  * @returns The requested tx effect (or undefined if not found).
176
176
  */
177
- getTxEffect(txHash: TxHash): InBlock<TxEffect> | undefined {
178
- const [blockNumber, txIndex] = this.getTxLocation(txHash) ?? [];
177
+ async getTxEffect(txHash: TxHash): Promise<InBlock<TxEffect> | undefined> {
178
+ const [blockNumber, txIndex] = (await this.getTxLocation(txHash)) ?? [];
179
179
  if (typeof blockNumber !== 'number' || typeof txIndex !== 'number') {
180
180
  return undefined;
181
181
  }
182
182
 
183
- const block = this.getBlock(blockNumber);
183
+ const block = await this.getBlock(blockNumber);
184
184
  if (!block) {
185
185
  return undefined;
186
186
  }
@@ -188,7 +188,7 @@ export class BlockStore {
188
188
  return {
189
189
  data: block.data.body.txEffects[txIndex],
190
190
  l2BlockNumber: block.data.number,
191
- l2BlockHash: block.data.hash().toString(),
191
+ l2BlockHash: (await block.data.hash()).toString(),
192
192
  };
193
193
  }
194
194
 
@@ -197,13 +197,13 @@ export class BlockStore {
197
197
  * @param txHash - The hash of a tx we try to get the receipt for.
198
198
  * @returns The requested tx receipt (or undefined if not found).
199
199
  */
200
- getSettledTxReceipt(txHash: TxHash): TxReceipt | undefined {
201
- const [blockNumber, txIndex] = this.getTxLocation(txHash) ?? [];
200
+ async getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
201
+ const [blockNumber, txIndex] = (await this.getTxLocation(txHash)) ?? [];
202
202
  if (typeof blockNumber !== 'number' || typeof txIndex !== 'number') {
203
203
  return undefined;
204
204
  }
205
205
 
206
- const block = this.getBlock(blockNumber)!;
206
+ const block = (await this.getBlock(blockNumber))!;
207
207
  const tx = block.data.body.txEffects[txIndex];
208
208
 
209
209
  return new TxReceipt(
@@ -211,7 +211,7 @@ export class BlockStore {
211
211
  TxReceipt.statusFromRevertCode(tx.revertCode),
212
212
  '',
213
213
  tx.transactionFee.toBigInt(),
214
- L2BlockHash.fromField(block.data.hash()),
214
+ L2BlockHash.fromField(await block.data.hash()),
215
215
  block.data.number,
216
216
  );
217
217
  }
@@ -221,8 +221,8 @@ export class BlockStore {
221
221
  * @param txHash - The txHash of the tx.
222
222
  * @returns The block number and index of the tx.
223
223
  */
224
- getTxLocation(txHash: TxHash): [blockNumber: number, txIndex: number] | undefined {
225
- return this.#txIndex.get(txHash.toString());
224
+ getTxLocation(txHash: TxHash): Promise<[blockNumber: number, txIndex: number] | undefined> {
225
+ return this.#txIndex.getAsync(txHash.toString());
226
226
  }
227
227
 
228
228
  /**
@@ -230,16 +230,16 @@ export class BlockStore {
230
230
  * @param contractAddress - The address of the contract to look up.
231
231
  * @returns The block number and index of the contract.
232
232
  */
233
- getContractLocation(contractAddress: AztecAddress): [blockNumber: number, index: number] | undefined {
234
- return this.#contractIndex.get(contractAddress.toString());
233
+ getContractLocation(contractAddress: AztecAddress): Promise<[blockNumber: number, index: number] | undefined> {
234
+ return this.#contractIndex.getAsync(contractAddress.toString());
235
235
  }
236
236
 
237
237
  /**
238
238
  * Gets the number of the latest L2 block processed.
239
239
  * @returns The number of the latest L2 block processed.
240
240
  */
241
- getSynchedL2BlockNumber(): number {
242
- const [lastBlockNumber] = this.#blocks.keys({ reverse: true, limit: 1 });
241
+ async getSynchedL2BlockNumber(): Promise<number> {
242
+ const [lastBlockNumber] = await toArray(this.#blocks.keysAsync({ reverse: true, limit: 1 }));
243
243
  return typeof lastBlockNumber === 'number' ? lastBlockNumber : INITIAL_L2_BLOCK_NUM - 1;
244
244
  }
245
245
 
@@ -247,31 +247,31 @@ export class BlockStore {
247
247
  * Gets the most recent L1 block processed.
248
248
  * @returns The L1 block that published the latest L2 block
249
249
  */
250
- getSynchedL1BlockNumber(): bigint | undefined {
251
- return this.#lastSynchedL1Block.get();
250
+ getSynchedL1BlockNumber(): Promise<bigint | undefined> {
251
+ return this.#lastSynchedL1Block.getAsync();
252
252
  }
253
253
 
254
254
  setSynchedL1BlockNumber(l1BlockNumber: bigint) {
255
- void this.#lastSynchedL1Block.set(l1BlockNumber);
255
+ return this.#lastSynchedL1Block.set(l1BlockNumber);
256
256
  }
257
257
 
258
- getProvenL2BlockNumber(): number {
259
- return this.#lastProvenL2Block.get() ?? 0;
258
+ async getProvenL2BlockNumber(): Promise<number> {
259
+ return (await this.#lastProvenL2Block.getAsync()) ?? 0;
260
260
  }
261
261
 
262
262
  setProvenL2BlockNumber(blockNumber: number) {
263
- void this.#lastProvenL2Block.set(blockNumber);
263
+ return this.#lastProvenL2Block.set(blockNumber);
264
264
  }
265
265
 
266
- getProvenL2EpochNumber(): number | undefined {
267
- return this.#lastProvenL2Epoch.get();
266
+ getProvenL2EpochNumber(): Promise<number | undefined> {
267
+ return this.#lastProvenL2Epoch.getAsync();
268
268
  }
269
269
 
270
270
  setProvenL2EpochNumber(epochNumber: number) {
271
- void this.#lastProvenL2Epoch.set(epochNumber);
271
+ return this.#lastProvenL2Epoch.set(epochNumber);
272
272
  }
273
273
 
274
- #computeBlockRange(start: number, limit: number): Required<Pick<Range<number>, 'start' | 'end'>> {
274
+ #computeBlockRange(start: number, limit: number): Required<Pick<Range<number>, 'start' | 'limit'>> {
275
275
  if (limit < 1) {
276
276
  throw new Error(`Invalid limit: ${limit}`);
277
277
  }
@@ -280,7 +280,6 @@ export class BlockStore {
280
280
  throw new Error(`Invalid start: ${start}`);
281
281
  }
282
282
 
283
- const end = start + limit;
284
- return { start, end };
283
+ return { start, limit };
285
284
  }
286
285
  }