@aztec/archiver 0.86.0 → 0.87.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 (57) hide show
  1. package/dest/archiver/archiver.d.ts +62 -5
  2. package/dest/archiver/archiver.d.ts.map +1 -1
  3. package/dest/archiver/archiver.js +362 -91
  4. package/dest/archiver/archiver_store.d.ts +33 -17
  5. package/dest/archiver/archiver_store.d.ts.map +1 -1
  6. package/dest/archiver/archiver_store_test_suite.d.ts.map +1 -1
  7. package/dest/archiver/archiver_store_test_suite.js +364 -62
  8. package/dest/archiver/data_retrieval.d.ts +23 -14
  9. package/dest/archiver/data_retrieval.d.ts.map +1 -1
  10. package/dest/archiver/data_retrieval.js +86 -38
  11. package/dest/archiver/errors.d.ts +8 -0
  12. package/dest/archiver/errors.d.ts.map +1 -1
  13. package/dest/archiver/errors.js +12 -0
  14. package/dest/archiver/instrumentation.d.ts.map +1 -1
  15. package/dest/archiver/kv_archiver_store/block_store.d.ts +4 -1
  16. package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
  17. package/dest/archiver/kv_archiver_store/block_store.js +43 -6
  18. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +3 -1
  19. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
  20. package/dest/archiver/kv_archiver_store/contract_instance_store.js +17 -3
  21. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +24 -14
  22. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
  23. package/dest/archiver/kv_archiver_store/kv_archiver_store.js +37 -11
  24. package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
  25. package/dest/archiver/kv_archiver_store/log_store.js +1 -1
  26. package/dest/archiver/kv_archiver_store/message_store.d.ts +21 -15
  27. package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
  28. package/dest/archiver/kv_archiver_store/message_store.js +150 -48
  29. package/dest/archiver/structs/inbox_message.d.ts +14 -0
  30. package/dest/archiver/structs/inbox_message.d.ts.map +1 -0
  31. package/dest/archiver/structs/inbox_message.js +38 -0
  32. package/dest/rpc/index.d.ts +1 -1
  33. package/dest/test/mock_l2_block_source.d.ts +2 -0
  34. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  35. package/dest/test/mock_l2_block_source.js +8 -1
  36. package/dest/test/mock_structs.d.ts +9 -0
  37. package/dest/test/mock_structs.d.ts.map +1 -0
  38. package/dest/test/mock_structs.js +37 -0
  39. package/package.json +15 -15
  40. package/src/archiver/archiver.ts +431 -108
  41. package/src/archiver/archiver_store.ts +37 -18
  42. package/src/archiver/archiver_store_test_suite.ts +307 -52
  43. package/src/archiver/data_retrieval.ts +130 -53
  44. package/src/archiver/errors.ts +21 -0
  45. package/src/archiver/instrumentation.ts +4 -1
  46. package/src/archiver/kv_archiver_store/block_store.ts +46 -8
  47. package/src/archiver/kv_archiver_store/contract_instance_store.ts +20 -7
  48. package/src/archiver/kv_archiver_store/kv_archiver_store.ts +61 -17
  49. package/src/archiver/kv_archiver_store/log_store.ts +6 -2
  50. package/src/archiver/kv_archiver_store/message_store.ts +209 -53
  51. package/src/archiver/structs/inbox_message.ts +40 -0
  52. package/src/test/mock_l2_block_source.ts +9 -1
  53. package/src/test/mock_structs.ts +49 -0
  54. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts +0 -23
  55. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts.map +0 -1
  56. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.js +0 -49
  57. package/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts +0 -61
@@ -1,4 +1,6 @@
1
+ import type { L1BlockId } from '@aztec/ethereum';
1
2
  import type { Fr } from '@aztec/foundation/fields';
3
+ import type { CustomRange } from '@aztec/kv-store';
2
4
  import type { FunctionSelector } from '@aztec/stdlib/abi';
3
5
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
4
6
  import type { L2Block } from '@aztec/stdlib/block';
@@ -11,10 +13,9 @@ import type {
11
13
  } from '@aztec/stdlib/contract';
12
14
  import type { GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client';
13
15
  import type { LogFilter, PrivateLog, TxScopedL2Log } from '@aztec/stdlib/logs';
14
- import type { InboxLeaf } from '@aztec/stdlib/messaging';
15
16
  import { BlockHeader, type IndexedTxEffect, type TxHash, type TxReceipt } from '@aztec/stdlib/tx';
16
17
 
17
- import type { DataRetrieval } from './structs/data_retrieval.js';
18
+ import type { InboxMessage } from './structs/inbox_message.js';
18
19
  import type { PublishedL2Block } from './structs/published.js';
19
20
 
20
21
  /**
@@ -23,10 +24,8 @@ import type { PublishedL2Block } from './structs/published.js';
23
24
  export type ArchiverL1SynchPoint = {
24
25
  /** Number of the last L1 block that added a new L2 block metadata. */
25
26
  blocksSynchedTo?: bigint;
26
- /** Number of the last L1 block that added L1 -> L2 messages from the Inbox. */
27
- messagesSynchedTo?: bigint;
28
- /** Number of the last L1 block that added a new proven block. */
29
- provenLogsSynchedTo?: bigint;
27
+ /** Last L1 block checked for L1 to L2 messages. */
28
+ messagesSynchedTo?: L1BlockId;
30
29
  };
31
30
 
32
31
  /**
@@ -34,12 +33,17 @@ export type ArchiverL1SynchPoint = {
34
33
  * (blocks, encrypted logs, aztec contract data extended contract data).
35
34
  */
36
35
  export interface ArchiverDataStore {
36
+ /** Opens a new transaction to the underlying store and runs all operations within it. */
37
+ transactionAsync<T>(callback: () => Promise<T>): Promise<T>;
38
+
37
39
  /**
38
40
  * Append new blocks to the store's list.
39
41
  * @param blocks - The L2 blocks to be added to the store and the last processed L1 block.
42
+ * @param opts - Options for the operation.
43
+ * @param opts.force - If true, the blocks will be added even if they have gaps.
40
44
  * @returns True if the operation is successful.
41
45
  */
42
- addBlocks(blocks: PublishedL2Block[]): Promise<boolean>;
46
+ addBlocks(blocks: PublishedL2Block[], opts?: { force?: boolean }): Promise<boolean>;
43
47
 
44
48
  /**
45
49
  * Unwinds blocks from the database
@@ -51,12 +55,18 @@ export interface ArchiverDataStore {
51
55
  unwindBlocks(from: number, blocksToUnwind: number): Promise<boolean>;
52
56
 
53
57
  /**
54
- * Gets up to `limit` amount of L2 blocks starting from `from`.
58
+ * Returns the block for the given number, or undefined if not exists.
59
+ * @param number - The block number to return.
60
+ */
61
+ getPublishedBlock(number: number): Promise<PublishedL2Block | undefined>;
62
+
63
+ /**
64
+ * Gets up to `limit` amount of published L2 blocks starting from `from`.
55
65
  * @param from - Number of the first block to return (inclusive).
56
66
  * @param limit - The number of blocks to return.
57
67
  * @returns The requested L2 blocks.
58
68
  */
59
- getBlocks(from: number, limit: number): Promise<PublishedL2Block[]>;
69
+ getPublishedBlocks(from: number, limit: number): Promise<PublishedL2Block[]>;
60
70
 
61
71
  /**
62
72
  * Gets up to `limit` amount of L2 block headers starting from `from`.
@@ -90,10 +100,10 @@ export interface ArchiverDataStore {
90
100
 
91
101
  /**
92
102
  * Append L1 to L2 messages to the store.
93
- * @param messages - The L1 to L2 messages to be added to the store and the last processed L1 block.
103
+ * @param messages - The L1 to L2 messages to be added to the store.
94
104
  * @returns True if the operation is successful.
95
105
  */
96
- addL1ToL2Messages(messages: DataRetrieval<InboxLeaf>): Promise<boolean>;
106
+ addL1ToL2Messages(messages: InboxMessage[]): Promise<void>;
97
107
 
98
108
  /**
99
109
  * Gets L1 to L2 message (to be) included in a given block.
@@ -170,10 +180,9 @@ export interface ArchiverDataStore {
170
180
  setBlockSynchedL1BlockNumber(l1BlockNumber: bigint): Promise<void>;
171
181
 
172
182
  /**
173
- * Stores the l1 block number that messages have been synched until
174
- * @param l1BlockNumber - The l1 block number
183
+ * Stores the l1 block that messages have been synched until
175
184
  */
176
- setMessageSynchedL1BlockNumber(l1BlockNumber: bigint): Promise<void>;
185
+ setMessageSynchedL1Block(l1Block: L1BlockId): Promise<void>;
177
186
 
178
187
  /**
179
188
  * Gets the synch point of the archiver
@@ -240,14 +249,24 @@ export interface ArchiverDataStore {
240
249
  registerContractFunctionSignatures(address: AztecAddress, signatures: string[]): Promise<void>;
241
250
  getDebugFunctionName(address: AztecAddress, selector: FunctionSelector): Promise<string | undefined>;
242
251
 
243
- /**
244
- * Estimates the size of the store in bytes.
245
- */
246
- estimateSize(): Promise<{ mappingSize: number; actualSize: number; numItems: number }>;
252
+ /** Estimates the size of the store in bytes. */
253
+ estimateSize(): Promise<{ mappingSize: number; physicalFileSize: number; actualSize: number; numItems: number }>;
247
254
 
248
255
  /** Backups the archiver db to the target folder. Returns the path to the db file. */
249
256
  backupTo(path: string): Promise<string>;
250
257
 
251
258
  /** Closes the underlying data store. */
252
259
  close(): Promise<void>;
260
+
261
+ /** Deletes all L1 to L2 messages up until (excluding) the target L2 block number. */
262
+ rollbackL1ToL2MessagesToL2Block(targetBlockNumber: number | bigint): Promise<void>;
263
+
264
+ /** Returns an async iterator to all L1 to L2 messages on the range. */
265
+ iterateL1ToL2Messages(range?: CustomRange<bigint>): AsyncIterableIterator<InboxMessage>;
266
+
267
+ /** Removes all L1 to L2 messages starting from the given index (inclusive). */
268
+ removeL1ToL2Messages(startIndex: bigint): Promise<void>;
269
+
270
+ /** Returns the last L1 to L2 message stored. */
271
+ getLastL1ToL2Message(): Promise<InboxMessage | undefined>;
253
272
  }
@@ -1,13 +1,16 @@
1
1
  import {
2
2
  INITIAL_L2_BLOCK_NUM,
3
- L1_TO_L2_MSG_SUBTREE_HEIGHT,
3
+ NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
4
4
  PRIVATE_LOG_SIZE_IN_FIELDS,
5
- PUBLIC_LOG_DATA_SIZE_IN_FIELDS,
5
+ PUBLIC_LOG_SIZE_IN_FIELDS,
6
6
  } from '@aztec/constants';
7
+ import { makeTuple } from '@aztec/foundation/array';
8
+ import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
7
9
  import { times, timesParallel } from '@aztec/foundation/collection';
8
10
  import { randomInt } from '@aztec/foundation/crypto';
9
11
  import { Signature } from '@aztec/foundation/eth-signature';
10
12
  import { Fr } from '@aztec/foundation/fields';
13
+ import { toArray } from '@aztec/foundation/iterable';
11
14
  import { sleep } from '@aztec/foundation/sleep';
12
15
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
13
16
  import { L2Block, wrapInBlock } from '@aztec/stdlib/block';
@@ -27,7 +30,11 @@ import {
27
30
  import '@aztec/stdlib/testing/jest';
28
31
  import { TxEffect, TxHash } from '@aztec/stdlib/tx';
29
32
 
33
+ import { makeInboxMessage, makeInboxMessages } from '../test/mock_structs.js';
30
34
  import type { ArchiverDataStore, ArchiverL1SynchPoint } from './archiver_store.js';
35
+ import { BlockNumberNotSequentialError, InitialBlockNumberNotSequentialError } from './errors.js';
36
+ import { MessageStoreError } from './kv_archiver_store/message_store.js';
37
+ import type { InboxMessage } from './structs/inbox_message.js';
31
38
  import type { PublishedL2Block } from './structs/published.js';
32
39
 
33
40
  /**
@@ -85,6 +92,18 @@ export function describeArchiverDataStore(
85
92
  await store.addBlocks(blocks);
86
93
  await expect(store.addBlocks(blocks)).resolves.toBe(true);
87
94
  });
95
+
96
+ it('throws an error if the previous block does not exist in the store', async () => {
97
+ const block = makePublished(await L2Block.random(2), 2);
98
+ await expect(store.addBlocks([block])).rejects.toThrow(InitialBlockNumberNotSequentialError);
99
+ await expect(store.getPublishedBlocks(1, 10)).resolves.toEqual([]);
100
+ });
101
+
102
+ it('throws an error if there is a gap in the blocks being added', async () => {
103
+ const blocks = [makePublished(await L2Block.random(1), 1), makePublished(await L2Block.random(3), 3)];
104
+ await expect(store.addBlocks(blocks)).rejects.toThrow(BlockNumberNotSequentialError);
105
+ await expect(store.getPublishedBlocks(1, 10)).resolves.toEqual([]);
106
+ });
88
107
  });
89
108
 
90
109
  describe('unwindBlocks', () => {
@@ -92,12 +111,12 @@ export function describeArchiverDataStore(
92
111
  await store.addBlocks(blocks);
93
112
  const blockNumber = await store.getSynchedL2BlockNumber();
94
113
 
95
- expectBlocksEqual(await store.getBlocks(blockNumber, 1), [blocks[blocks.length - 1]]);
114
+ expectBlocksEqual(await store.getPublishedBlocks(blockNumber, 1), [blocks[blocks.length - 1]]);
96
115
 
97
116
  await store.unwindBlocks(blockNumber, 1);
98
117
 
99
118
  expect(await store.getSynchedL2BlockNumber()).toBe(blockNumber - 1);
100
- expect(await store.getBlocks(blockNumber, 1)).toEqual([]);
119
+ expect(await store.getPublishedBlocks(blockNumber, 1)).toEqual([]);
101
120
  });
102
121
 
103
122
  it('can unwind multiple empty blocks', async () => {
@@ -107,7 +126,7 @@ export function describeArchiverDataStore(
107
126
 
108
127
  await store.unwindBlocks(10, 3);
109
128
  expect(await store.getSynchedL2BlockNumber()).toBe(7);
110
- expect((await store.getBlocks(1, 10)).map(b => b.block.number)).toEqual([1, 2, 3, 4, 5, 6, 7]);
129
+ expect((await store.getPublishedBlocks(1, 10)).map(b => b.block.number)).toEqual([1, 2, 3, 4, 5, 6, 7]);
111
130
  });
112
131
 
113
132
  it('refuses to unwind blocks if the tip is not the last block', async () => {
@@ -122,19 +141,32 @@ export function describeArchiverDataStore(
122
141
  });
123
142
 
124
143
  it.each(blockTests)('retrieves previously stored blocks', async (start, limit, getExpectedBlocks) => {
125
- expectBlocksEqual(await store.getBlocks(start, limit), getExpectedBlocks());
144
+ expectBlocksEqual(await store.getPublishedBlocks(start, limit), getExpectedBlocks());
126
145
  });
127
146
 
128
147
  it('returns an empty array if no blocks are found', async () => {
129
- await expect(store.getBlocks(12, 1)).resolves.toEqual([]);
148
+ await expect(store.getPublishedBlocks(12, 1)).resolves.toEqual([]);
130
149
  });
131
150
 
132
151
  it('throws an error if limit is invalid', async () => {
133
- await expect(store.getBlocks(1, 0)).rejects.toThrow('Invalid limit: 0');
152
+ await expect(store.getPublishedBlocks(1, 0)).rejects.toThrow('Invalid limit: 0');
134
153
  });
135
154
 
136
155
  it('throws an error if `from` it is out of range', async () => {
137
- await expect(store.getBlocks(INITIAL_L2_BLOCK_NUM - 100, 1)).rejects.toThrow('Invalid start: -99');
156
+ await expect(store.getPublishedBlocks(INITIAL_L2_BLOCK_NUM - 100, 1)).rejects.toThrow('Invalid start: -99');
157
+ });
158
+
159
+ it('throws an error if unexpected initial block number is found', async () => {
160
+ await store.addBlocks([makePublished(await L2Block.random(21), 31)], { force: true });
161
+ await expect(store.getPublishedBlocks(20, 1)).rejects.toThrow(`mismatch`);
162
+ });
163
+
164
+ it('throws an error if a gap is found', async () => {
165
+ await store.addBlocks(
166
+ [makePublished(await L2Block.random(20), 30), makePublished(await L2Block.random(22), 32)],
167
+ { force: true },
168
+ );
169
+ await expect(store.getPublishedBlocks(20, 2)).rejects.toThrow(`mismatch`);
138
170
  });
139
171
  });
140
172
 
@@ -166,13 +198,25 @@ export function describeArchiverDataStore(
166
198
  });
167
199
 
168
200
  it('returns the L1 block number that most recently added messages from inbox', async () => {
169
- await store.addL1ToL2Messages({
170
- lastProcessedL1BlockNumber: 1n,
171
- retrievedData: [new InboxLeaf(1n, Fr.ZERO)],
172
- });
201
+ const l1BlockHash = Buffer32.random();
202
+ const l1BlockNumber = 10n;
203
+ await store.setMessageSynchedL1Block({ l1BlockNumber: 5n, l1BlockHash: Buffer32.random() });
204
+ await store.addL1ToL2Messages([makeInboxMessage(Buffer16.ZERO, { l1BlockNumber, l1BlockHash })]);
173
205
  await expect(store.getSynchPoint()).resolves.toEqual({
174
206
  blocksSynchedTo: undefined,
175
- messagesSynchedTo: 1n,
207
+ messagesSynchedTo: { l1BlockHash, l1BlockNumber },
208
+ } satisfies ArchiverL1SynchPoint);
209
+ });
210
+
211
+ it('returns the latest syncpoint if latest message is behind', async () => {
212
+ const l1BlockHash = Buffer32.random();
213
+ const l1BlockNumber = 10n;
214
+ await store.setMessageSynchedL1Block({ l1BlockNumber, l1BlockHash });
215
+ const msg = makeInboxMessage(Buffer16.ZERO, { l1BlockNumber: 5n, l1BlockHash: Buffer32.random() });
216
+ await store.addL1ToL2Messages([msg]);
217
+ await expect(store.getSynchPoint()).resolves.toEqual({
218
+ blocksSynchedTo: undefined,
219
+ messagesSynchedTo: { l1BlockHash, l1BlockNumber },
176
220
  } satisfies ArchiverL1SynchPoint);
177
221
  });
178
222
  });
@@ -278,36 +322,196 @@ export function describeArchiverDataStore(
278
322
  });
279
323
 
280
324
  describe('L1 to L2 Messages', () => {
281
- const l2BlockNumber = 13n;
282
- const l1ToL2MessageSubtreeSize = 2 ** L1_TO_L2_MSG_SUBTREE_HEIGHT;
325
+ const initialL2BlockNumber = 13n;
326
+
327
+ const checkMessages = async (msgs: InboxMessage[]) => {
328
+ expect(await store.getLastL1ToL2Message()).toEqual(msgs.at(-1));
329
+ expect(await toArray(store.iterateL1ToL2Messages())).toEqual(msgs);
330
+ expect(await store.getTotalL1ToL2MessageCount()).toEqual(BigInt(msgs.length));
331
+ };
332
+
333
+ const makeInboxMessagesWithFullBlocks = (blockCount: number, opts: { initialL2BlockNumber?: bigint } = {}) =>
334
+ makeInboxMessages(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * blockCount, {
335
+ overrideFn: (msg, i) => {
336
+ const l2BlockNumber =
337
+ (opts.initialL2BlockNumber ?? initialL2BlockNumber) +
338
+ BigInt(Math.floor(i / NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP));
339
+ const index =
340
+ InboxLeaf.smallestIndexFromL2Block(l2BlockNumber) + BigInt(i % NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
341
+ return { ...msg, l2BlockNumber, index };
342
+ },
343
+ });
344
+
345
+ it('stores first message ever', async () => {
346
+ const msg = makeInboxMessage(Buffer16.ZERO, { index: 0n, l2BlockNumber: 1n });
347
+ await store.addL1ToL2Messages([msg]);
348
+
349
+ await checkMessages([msg]);
350
+ expect(await store.getL1ToL2Messages(1n)).toEqual([msg.leaf]);
351
+ });
352
+
353
+ it('stores single message', async () => {
354
+ const msg = makeInboxMessage(Buffer16.ZERO, { l2BlockNumber: 2n });
355
+ await store.addL1ToL2Messages([msg]);
356
+
357
+ await checkMessages([msg]);
358
+ expect(await store.getL1ToL2Messages(2n)).toEqual([msg.leaf]);
359
+ });
283
360
 
284
- const generateBlockMessages = (blockNumber: bigint, numMessages: number) =>
285
- Array.from(
286
- { length: numMessages },
287
- (_, i) => new InboxLeaf(InboxLeaf.smallestIndexFromL2Block(blockNumber) + BigInt(i), Fr.random()),
361
+ it('stores and returns messages across different blocks', async () => {
362
+ const msgs = makeInboxMessages(5, { initialL2BlockNumber });
363
+ await store.addL1ToL2Messages(msgs);
364
+
365
+ await checkMessages(msgs);
366
+ expect(await store.getL1ToL2Messages(initialL2BlockNumber + 2n)).toEqual([msgs[2]].map(m => m.leaf));
367
+ });
368
+
369
+ it('stores the same messages again', async () => {
370
+ const msgs = makeInboxMessages(5, { initialL2BlockNumber });
371
+ await store.addL1ToL2Messages(msgs);
372
+ await store.addL1ToL2Messages(msgs.slice(2));
373
+
374
+ await checkMessages(msgs);
375
+ });
376
+
377
+ it('stores and returns messages across different blocks with gaps', async () => {
378
+ const msgs1 = makeInboxMessages(3, { initialL2BlockNumber: 1n });
379
+ const msgs2 = makeInboxMessages(3, { initialL2BlockNumber: 20n, initialHash: msgs1.at(-1)!.rollingHash });
380
+
381
+ await store.addL1ToL2Messages(msgs1);
382
+ await store.addL1ToL2Messages(msgs2);
383
+
384
+ await checkMessages([...msgs1, ...msgs2]);
385
+
386
+ expect(await store.getL1ToL2Messages(1n)).toEqual([msgs1[0].leaf]);
387
+ expect(await store.getL1ToL2Messages(4n)).toEqual([]);
388
+ expect(await store.getL1ToL2Messages(20n)).toEqual([msgs2[0].leaf]);
389
+ expect(await store.getL1ToL2Messages(24n)).toEqual([]);
390
+ });
391
+
392
+ it('stores and returns messages with block numbers larger than a byte', async () => {
393
+ const msgs = makeInboxMessages(5, { initialL2BlockNumber: 1000n });
394
+ await store.addL1ToL2Messages(msgs);
395
+
396
+ await checkMessages(msgs);
397
+ expect(await store.getL1ToL2Messages(1002n)).toEqual([msgs[2]].map(m => m.leaf));
398
+ });
399
+
400
+ it('stores and returns multiple messages per block', async () => {
401
+ const msgs = makeInboxMessagesWithFullBlocks(4);
402
+ await store.addL1ToL2Messages(msgs);
403
+
404
+ await checkMessages(msgs);
405
+ const blockMessages = await store.getL1ToL2Messages(initialL2BlockNumber + 1n);
406
+ expect(blockMessages).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
407
+ expect(blockMessages).toEqual(
408
+ msgs.slice(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * 2).map(m => m.leaf),
288
409
  );
410
+ });
411
+
412
+ it('stores messages in multiple operations', async () => {
413
+ const msgs = makeInboxMessages(20, { initialL2BlockNumber });
414
+ await store.addL1ToL2Messages(msgs.slice(0, 10));
415
+ await store.addL1ToL2Messages(msgs.slice(10, 20));
416
+
417
+ expect(await store.getL1ToL2Messages(initialL2BlockNumber + 2n)).toEqual([msgs[2]].map(m => m.leaf));
418
+ expect(await store.getL1ToL2Messages(initialL2BlockNumber + 12n)).toEqual([msgs[12]].map(m => m.leaf));
419
+ await checkMessages(msgs);
420
+ });
289
421
 
290
- it('returns messages in correct order', async () => {
291
- const msgs = generateBlockMessages(l2BlockNumber, l1ToL2MessageSubtreeSize);
292
- const shuffledMessages = msgs.slice().sort(() => randomInt(1) - 0.5);
293
- await store.addL1ToL2Messages({ lastProcessedL1BlockNumber: 100n, retrievedData: shuffledMessages });
294
- const retrievedMessages = await store.getL1ToL2Messages(l2BlockNumber);
422
+ it('iterates over messages from start index', async () => {
423
+ const msgs = makeInboxMessages(10, { initialL2BlockNumber });
424
+ await store.addL1ToL2Messages(msgs);
295
425
 
296
- const expectedLeavesOrder = msgs.map(msg => msg.leaf);
297
- expect(expectedLeavesOrder).toEqual(retrievedMessages);
426
+ const iterated = await toArray(store.iterateL1ToL2Messages({ start: msgs[3].index }));
427
+ expect(iterated).toEqual(msgs.slice(3));
298
428
  });
299
429
 
300
- it('throws if it is impossible to sequence messages correctly', async () => {
301
- const msgs = generateBlockMessages(l2BlockNumber, l1ToL2MessageSubtreeSize - 1);
302
- // We replace a message with index 4 with a message with index at the end of the tree
303
- // --> with that there will be a gap and it will be impossible to sequence the
304
- // end of tree = start of next tree/block - 1
305
- msgs[4] = new InboxLeaf(InboxLeaf.smallestIndexFromL2Block(l2BlockNumber + 1n) - 1n, Fr.random());
430
+ it('iterates over messages in reverse', async () => {
431
+ const msgs = makeInboxMessages(10, { initialL2BlockNumber });
432
+ await store.addL1ToL2Messages(msgs);
433
+
434
+ const iterated = await toArray(store.iterateL1ToL2Messages({ reverse: true, end: msgs[3].index }));
435
+ expect(iterated).toEqual(msgs.slice(0, 4).reverse());
436
+ });
437
+
438
+ it('throws if messages are added out of order', async () => {
439
+ const msgs = makeInboxMessages(5, { overrideFn: (msg, i) => ({ ...msg, index: BigInt(10 - i) }) });
440
+ await expect(store.addL1ToL2Messages(msgs)).rejects.toThrow(MessageStoreError);
441
+ });
442
+
443
+ it('throws if block number for the first message is out of order', async () => {
444
+ const msgs = makeInboxMessages(4, { initialL2BlockNumber });
445
+ msgs[2].l2BlockNumber = initialL2BlockNumber - 1n;
446
+ await store.addL1ToL2Messages(msgs.slice(0, 2));
447
+ await expect(store.addL1ToL2Messages(msgs.slice(2, 4))).rejects.toThrow(MessageStoreError);
448
+ });
306
449
 
307
- await store.addL1ToL2Messages({ lastProcessedL1BlockNumber: 100n, retrievedData: msgs });
308
- await expect(async () => {
309
- await store.getL1ToL2Messages(l2BlockNumber);
310
- }).rejects.toThrow(`L1 to L2 message gap found in block ${l2BlockNumber}`);
450
+ it('throws if rolling hash is not correct', async () => {
451
+ const msgs = makeInboxMessages(5);
452
+ msgs[1].rollingHash = Buffer16.random();
453
+ await expect(store.addL1ToL2Messages(msgs)).rejects.toThrow(MessageStoreError);
454
+ });
455
+
456
+ it('throws if rolling hash for first message is not correct', async () => {
457
+ const msgs = makeInboxMessages(4);
458
+ msgs[2].rollingHash = Buffer16.random();
459
+ await store.addL1ToL2Messages(msgs.slice(0, 2));
460
+ await expect(store.addL1ToL2Messages(msgs.slice(2, 4))).rejects.toThrow(MessageStoreError);
461
+ });
462
+
463
+ it('throws if index is not in the correct range', async () => {
464
+ const msgs = makeInboxMessages(5, { initialL2BlockNumber });
465
+ msgs.at(-1)!.index += 100n;
466
+ await expect(store.addL1ToL2Messages(msgs)).rejects.toThrow(MessageStoreError);
467
+ });
468
+
469
+ it('throws if first index in block has gaps', async () => {
470
+ const msgs = makeInboxMessages(4, { initialL2BlockNumber });
471
+ msgs[2].index++;
472
+ await expect(store.addL1ToL2Messages(msgs)).rejects.toThrow(MessageStoreError);
473
+ });
474
+
475
+ it('throws if index does not follow previous one', async () => {
476
+ const msgs = makeInboxMessages(2, {
477
+ initialL2BlockNumber,
478
+ overrideFn: (msg, i) => ({
479
+ ...msg,
480
+ l2BlockNumber: 2n,
481
+ index: BigInt(i + NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * 2),
482
+ }),
483
+ });
484
+ msgs[1].index++;
485
+ await expect(store.addL1ToL2Messages(msgs)).rejects.toThrow(MessageStoreError);
486
+ });
487
+
488
+ it('removes messages up to the given block number', async () => {
489
+ const msgs = makeInboxMessagesWithFullBlocks(4, { initialL2BlockNumber: 1n });
490
+
491
+ await store.addL1ToL2Messages(msgs);
492
+ await checkMessages(msgs);
493
+
494
+ expect(await store.getL1ToL2Messages(1n)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
495
+ expect(await store.getL1ToL2Messages(2n)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
496
+ expect(await store.getL1ToL2Messages(3n)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
497
+ expect(await store.getL1ToL2Messages(4n)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
498
+
499
+ await store.rollbackL1ToL2MessagesToL2Block(2n);
500
+
501
+ expect(await store.getL1ToL2Messages(1n)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
502
+ expect(await store.getL1ToL2Messages(2n)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
503
+ expect(await store.getL1ToL2Messages(3n)).toHaveLength(0);
504
+ expect(await store.getL1ToL2Messages(4n)).toHaveLength(0);
505
+
506
+ await checkMessages(msgs.slice(0, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * 2));
507
+ });
508
+
509
+ it('removes messages starting with the given index', async () => {
510
+ const msgs = makeInboxMessagesWithFullBlocks(4, { initialL2BlockNumber: 1n });
511
+ await store.addL1ToL2Messages(msgs);
512
+
513
+ await store.removeL1ToL2Messages(msgs[13].index);
514
+ await checkMessages(msgs.slice(0, 13));
311
515
  });
312
516
  });
313
517
 
@@ -386,6 +590,52 @@ export function describeArchiverDataStore(
386
590
  expect(fetchedInstance?.originalContractClassId).toEqual(classId);
387
591
  expect(fetchedInstance?.currentContractClassId).toEqual(nextClassId);
388
592
  });
593
+
594
+ it('ignores updates for the wrong contract', async () => {
595
+ const otherClassId = Fr.random();
596
+ const randomInstance = await SerializableContractInstance.random({
597
+ currentContractClassId: otherClassId,
598
+ originalContractClassId: otherClassId,
599
+ });
600
+ const otherContractInstance = {
601
+ ...randomInstance,
602
+ address: await AztecAddress.random(),
603
+ };
604
+ await store.addContractInstances([otherContractInstance], 1);
605
+
606
+ const fetchedInstance = await store.getContractInstance(otherContractInstance.address, blockOfChange + 1);
607
+ expect(fetchedInstance?.originalContractClassId).toEqual(otherClassId);
608
+ expect(fetchedInstance?.currentContractClassId).toEqual(otherClassId);
609
+ });
610
+
611
+ it('bounds its search to the right contract if more than than one update exists', async () => {
612
+ const otherClassId = Fr.random();
613
+ const otherNextClassId = Fr.random();
614
+ const randomInstance = await SerializableContractInstance.random({
615
+ currentContractClassId: otherClassId,
616
+ originalContractClassId: otherNextClassId,
617
+ });
618
+ const otherContractInstance = {
619
+ ...randomInstance,
620
+ address: await AztecAddress.random(),
621
+ };
622
+ await store.addContractInstances([otherContractInstance], 1);
623
+ await store.addContractInstanceUpdates(
624
+ [
625
+ {
626
+ prevContractClassId: otherClassId,
627
+ newContractClassId: otherNextClassId,
628
+ blockOfChange,
629
+ address: otherContractInstance.address,
630
+ },
631
+ ],
632
+ blockOfChange - 1,
633
+ );
634
+
635
+ const fetchedInstance = await store.getContractInstance(contractInstance.address, blockOfChange + 1);
636
+ expect(fetchedInstance?.originalContractClassId).toEqual(classId);
637
+ expect(fetchedInstance?.currentContractClassId).toEqual(nextClassId);
638
+ });
389
639
  });
390
640
 
391
641
  describe('contractClasses', () => {
@@ -464,17 +714,22 @@ export function describeArchiverDataStore(
464
714
  let blocks: PublishedL2Block[];
465
715
 
466
716
  const makeTag = (blockNumber: number, txIndex: number, logIndex: number, isPublic = false) =>
467
- new Fr((blockNumber * 100 + txIndex * 10 + logIndex) * (isPublic ? 123 : 1));
717
+ blockNumber === 1 && txIndex === 0 && logIndex === 0
718
+ ? Fr.ZERO // Shared tag
719
+ : new Fr((blockNumber * 100 + txIndex * 10 + logIndex) * (isPublic ? 123 : 1));
468
720
 
469
721
  const makePrivateLog = (tag: Fr) =>
470
- PrivateLog.fromFields([tag, ...times(PRIVATE_LOG_SIZE_IN_FIELDS - 1, i => new Fr(tag.toNumber() + i))]);
722
+ PrivateLog.from({
723
+ fields: makeTuple(PRIVATE_LOG_SIZE_IN_FIELDS, i => (!i ? tag : new Fr(tag.toNumber() + i))),
724
+ emittedLength: PRIVATE_LOG_SIZE_IN_FIELDS,
725
+ });
471
726
 
472
727
  const makePublicLog = (tag: Fr) =>
473
- PublicLog.fromFields([
474
- AztecAddress.fromNumber(1).toField(), // log address
475
- tag, // field 0
476
- ...times(PUBLIC_LOG_DATA_SIZE_IN_FIELDS - 1, i => new Fr(tag.toNumber() + i)), // fields 1 to end
477
- ]);
728
+ PublicLog.from({
729
+ contractAddress: AztecAddress.fromNumber(1),
730
+ fields: makeTuple(PUBLIC_LOG_SIZE_IN_FIELDS, i => (!i ? tag : new Fr(tag.toNumber() + i))),
731
+ emittedLength: PUBLIC_LOG_SIZE_IN_FIELDS,
732
+ });
478
733
 
479
734
  const mockPrivateLogs = (blockNumber: number, txIndex: number) => {
480
735
  return times(numPrivateLogsPerTx, (logIndex: number) => {
@@ -509,28 +764,28 @@ export function describeArchiverDataStore(
509
764
  };
510
765
 
511
766
  beforeEach(async () => {
512
- blocks = await timesParallel(numBlocks, (index: number) => mockBlockWithLogs(index));
767
+ blocks = await timesParallel(numBlocks, (index: number) => mockBlockWithLogs(index + 1));
513
768
 
514
769
  await store.addBlocks(blocks);
515
770
  await store.addLogs(blocks.map(b => b.block));
516
771
  });
517
772
 
518
773
  it('is possible to batch request private logs via tags', async () => {
519
- const tags = [makeTag(1, 1, 2), makeTag(0, 2, 0)];
774
+ const tags = [makeTag(2, 1, 2), makeTag(1, 2, 0)];
520
775
 
521
776
  const logsByTags = await store.getLogsByTags(tags);
522
777
 
523
778
  expect(logsByTags).toEqual([
524
779
  [
525
780
  expect.objectContaining({
526
- blockNumber: 1,
781
+ blockNumber: 2,
527
782
  log: makePrivateLog(tags[0]),
528
783
  isFromPublic: false,
529
784
  }),
530
785
  ],
531
786
  [
532
787
  expect.objectContaining({
533
- blockNumber: 0,
788
+ blockNumber: 1,
534
789
  log: makePrivateLog(tags[1]),
535
790
  isFromPublic: false,
536
791
  }),
@@ -539,20 +794,20 @@ export function describeArchiverDataStore(
539
794
  });
540
795
 
541
796
  it('is possible to batch request all logs (private and public) via tags', async () => {
542
- // Tag(0, 0, 0) is shared with the first private log and the first public log.
543
- const tags = [makeTag(0, 0, 0)];
797
+ // Tag(1, 0, 0) is shared with the first private log and the first public log.
798
+ const tags = [makeTag(1, 0, 0)];
544
799
 
545
800
  const logsByTags = await store.getLogsByTags(tags);
546
801
 
547
802
  expect(logsByTags).toEqual([
548
803
  [
549
804
  expect.objectContaining({
550
- blockNumber: 0,
805
+ blockNumber: 1,
551
806
  log: makePrivateLog(tags[0]),
552
807
  isFromPublic: false,
553
808
  }),
554
809
  expect.objectContaining({
555
- blockNumber: 0,
810
+ blockNumber: 1,
556
811
  log: makePublicLog(tags[0]),
557
812
  isFromPublic: true,
558
813
  }),