@aztec/archiver 0.0.0-test.0 → 0.0.1-commit.24de95ac

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 (102) hide show
  1. package/README.md +27 -6
  2. package/dest/archiver/archiver.d.ts +126 -46
  3. package/dest/archiver/archiver.d.ts.map +1 -1
  4. package/dest/archiver/archiver.js +683 -261
  5. package/dest/archiver/archiver_store.d.ts +84 -49
  6. package/dest/archiver/archiver_store.d.ts.map +1 -1
  7. package/dest/archiver/archiver_store_test_suite.d.ts.map +1 -1
  8. package/dest/archiver/archiver_store_test_suite.js +707 -213
  9. package/dest/archiver/config.d.ts +4 -20
  10. package/dest/archiver/config.d.ts.map +1 -1
  11. package/dest/archiver/config.js +16 -12
  12. package/dest/archiver/data_retrieval.d.ts +25 -20
  13. package/dest/archiver/data_retrieval.d.ts.map +1 -1
  14. package/dest/archiver/data_retrieval.js +147 -68
  15. package/dest/archiver/errors.d.ts +8 -0
  16. package/dest/archiver/errors.d.ts.map +1 -1
  17. package/dest/archiver/errors.js +12 -0
  18. package/dest/archiver/index.d.ts +2 -3
  19. package/dest/archiver/index.d.ts.map +1 -1
  20. package/dest/archiver/index.js +1 -2
  21. package/dest/archiver/instrumentation.d.ts +9 -3
  22. package/dest/archiver/instrumentation.d.ts.map +1 -1
  23. package/dest/archiver/instrumentation.js +58 -17
  24. package/dest/archiver/kv_archiver_store/block_store.d.ts +47 -10
  25. package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
  26. package/dest/archiver/kv_archiver_store/block_store.js +216 -63
  27. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +2 -2
  28. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +1 -1
  29. package/dest/archiver/kv_archiver_store/contract_class_store.js +12 -18
  30. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +10 -7
  31. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
  32. package/dest/archiver/kv_archiver_store/contract_instance_store.js +30 -16
  33. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +49 -34
  34. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
  35. package/dest/archiver/kv_archiver_store/kv_archiver_store.js +88 -46
  36. package/dest/archiver/kv_archiver_store/log_store.d.ts +1 -1
  37. package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
  38. package/dest/archiver/kv_archiver_store/log_store.js +18 -46
  39. package/dest/archiver/kv_archiver_store/message_store.d.ts +22 -16
  40. package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
  41. package/dest/archiver/kv_archiver_store/message_store.js +150 -48
  42. package/dest/archiver/structs/inbox_message.d.ts +15 -0
  43. package/dest/archiver/structs/inbox_message.d.ts.map +1 -0
  44. package/dest/archiver/structs/inbox_message.js +38 -0
  45. package/dest/archiver/structs/published.d.ts +1 -10
  46. package/dest/archiver/structs/published.d.ts.map +1 -1
  47. package/dest/archiver/structs/published.js +1 -1
  48. package/dest/archiver/validation.d.ts +11 -0
  49. package/dest/archiver/validation.d.ts.map +1 -0
  50. package/dest/archiver/validation.js +90 -0
  51. package/dest/factory.d.ts +7 -12
  52. package/dest/factory.d.ts.map +1 -1
  53. package/dest/factory.js +18 -49
  54. package/dest/rpc/index.d.ts +1 -2
  55. package/dest/rpc/index.d.ts.map +1 -1
  56. package/dest/rpc/index.js +1 -4
  57. package/dest/test/mock_archiver.d.ts +1 -1
  58. package/dest/test/mock_l1_to_l2_message_source.d.ts +4 -2
  59. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
  60. package/dest/test/mock_l1_to_l2_message_source.js +14 -1
  61. package/dest/test/mock_l2_block_source.d.ts +32 -5
  62. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  63. package/dest/test/mock_l2_block_source.js +118 -7
  64. package/dest/test/mock_structs.d.ts +9 -0
  65. package/dest/test/mock_structs.d.ts.map +1 -0
  66. package/dest/test/mock_structs.js +37 -0
  67. package/package.json +25 -27
  68. package/src/archiver/archiver.ts +858 -317
  69. package/src/archiver/archiver_store.ts +97 -55
  70. package/src/archiver/archiver_store_test_suite.ts +663 -210
  71. package/src/archiver/config.ts +23 -41
  72. package/src/archiver/data_retrieval.ts +215 -92
  73. package/src/archiver/errors.ts +21 -0
  74. package/src/archiver/index.ts +2 -3
  75. package/src/archiver/instrumentation.ts +75 -20
  76. package/src/archiver/kv_archiver_store/block_store.ts +270 -72
  77. package/src/archiver/kv_archiver_store/contract_class_store.ts +13 -23
  78. package/src/archiver/kv_archiver_store/contract_instance_store.ts +35 -27
  79. package/src/archiver/kv_archiver_store/kv_archiver_store.ts +127 -63
  80. package/src/archiver/kv_archiver_store/log_store.ts +24 -62
  81. package/src/archiver/kv_archiver_store/message_store.ts +209 -53
  82. package/src/archiver/structs/inbox_message.ts +41 -0
  83. package/src/archiver/structs/published.ts +1 -11
  84. package/src/archiver/validation.ts +99 -0
  85. package/src/factory.ts +24 -66
  86. package/src/rpc/index.ts +1 -5
  87. package/src/test/mock_archiver.ts +1 -1
  88. package/src/test/mock_l1_to_l2_message_source.ts +14 -3
  89. package/src/test/mock_l2_block_source.ts +152 -8
  90. package/src/test/mock_structs.ts +49 -0
  91. package/dest/archiver/kv_archiver_store/nullifier_store.d.ts +0 -12
  92. package/dest/archiver/kv_archiver_store/nullifier_store.d.ts.map +0 -1
  93. package/dest/archiver/kv_archiver_store/nullifier_store.js +0 -73
  94. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts +0 -23
  95. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts.map +0 -1
  96. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.js +0 -49
  97. package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts +0 -175
  98. package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts.map +0 -1
  99. package/dest/archiver/memory_archiver_store/memory_archiver_store.js +0 -636
  100. package/src/archiver/kv_archiver_store/nullifier_store.ts +0 -97
  101. package/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts +0 -61
  102. package/src/archiver/memory_archiver_store/memory_archiver_store.ts +0 -801
@@ -1,15 +1,26 @@
1
1
  import {
2
2
  INITIAL_L2_BLOCK_NUM,
3
- L1_TO_L2_MSG_SUBTREE_HEIGHT,
4
- MAX_NULLIFIERS_PER_TX,
3
+ NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
5
4
  PRIVATE_LOG_SIZE_IN_FIELDS,
6
- PUBLIC_LOG_DATA_SIZE_IN_FIELDS,
7
5
  } from '@aztec/constants';
6
+ import { makeTuple } from '@aztec/foundation/array';
7
+ import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
8
8
  import { times, timesParallel } from '@aztec/foundation/collection';
9
9
  import { randomInt } from '@aztec/foundation/crypto';
10
10
  import { Fr } from '@aztec/foundation/fields';
11
+ import { toArray } from '@aztec/foundation/iterable';
12
+ import { sleep } from '@aztec/foundation/sleep';
11
13
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
12
- import { L2Block, wrapInBlock } from '@aztec/stdlib/block';
14
+ import {
15
+ CommitteeAttestation,
16
+ EthAddress,
17
+ L2Block,
18
+ L2BlockHash,
19
+ PublishedL2Block,
20
+ type ValidateBlockResult,
21
+ randomBlockInfo,
22
+ wrapInBlock,
23
+ } from '@aztec/stdlib/block';
13
24
  import {
14
25
  type ContractClassPublic,
15
26
  type ContractInstanceWithAddress,
@@ -21,13 +32,16 @@ import { InboxLeaf } from '@aztec/stdlib/messaging';
21
32
  import {
22
33
  makeContractClassPublic,
23
34
  makeExecutablePrivateFunctionWithMembershipProof,
24
- makeUnconstrainedFunctionWithMembershipProof,
35
+ makeUtilityFunctionWithMembershipProof,
25
36
  } from '@aztec/stdlib/testing';
26
37
  import '@aztec/stdlib/testing/jest';
27
- import { TxEffect, TxHash } from '@aztec/stdlib/tx';
38
+ import { type IndexedTxEffect, TxEffect, TxHash } from '@aztec/stdlib/tx';
28
39
 
40
+ import { makeInboxMessage, makeInboxMessages } from '../test/mock_structs.js';
29
41
  import type { ArchiverDataStore, ArchiverL1SynchPoint } from './archiver_store.js';
30
- import type { L1Published } from './structs/published.js';
42
+ import { BlockNumberNotSequentialError, InitialBlockNumberNotSequentialError } from './errors.js';
43
+ import { MessageStoreError } from './kv_archiver_store/message_store.js';
44
+ import type { InboxMessage } from './structs/inbox_message.js';
31
45
 
32
46
  /**
33
47
  * @param testName - The name of the test suite.
@@ -39,8 +53,9 @@ export function describeArchiverDataStore(
39
53
  ) {
40
54
  describe(testName, () => {
41
55
  let store: ArchiverDataStore;
42
- let blocks: L1Published<L2Block>[];
43
- const blockTests: [number, number, () => L1Published<L2Block>[]][] = [
56
+ let blocks: PublishedL2Block[];
57
+
58
+ const blockTests: [number, number, () => PublishedL2Block[]][] = [
44
59
  [1, 1, () => blocks.slice(0, 1)],
45
60
  [10, 1, () => blocks.slice(9, 10)],
46
61
  [1, 10, () => blocks.slice(0, 10)],
@@ -48,18 +63,33 @@ export function describeArchiverDataStore(
48
63
  [5, 2, () => blocks.slice(4, 6)],
49
64
  ];
50
65
 
51
- const makeL1Published = (block: L2Block, l1BlockNumber: number): L1Published<L2Block> => ({
52
- data: block,
53
- l1: {
54
- blockNumber: BigInt(l1BlockNumber),
55
- blockHash: `0x${l1BlockNumber}`,
56
- timestamp: BigInt(l1BlockNumber * 1000),
57
- },
58
- });
66
+ const makeBlockHash = (blockNumber: number) => `0x${blockNumber.toString(16).padStart(64, '0')}`;
67
+
68
+ const makePublished = (block: L2Block, l1BlockNumber: number): PublishedL2Block =>
69
+ PublishedL2Block.fromFields({
70
+ block: block,
71
+ l1: {
72
+ blockNumber: BigInt(l1BlockNumber),
73
+ blockHash: makeBlockHash(l1BlockNumber),
74
+ timestamp: BigInt(l1BlockNumber * 1000),
75
+ },
76
+ attestations: times(3, CommitteeAttestation.random),
77
+ });
78
+
79
+ const expectBlocksEqual = (actual: PublishedL2Block[], expected: PublishedL2Block[]) => {
80
+ expect(actual.length).toEqual(expected.length);
81
+ for (let i = 0; i < expected.length; i++) {
82
+ const expectedBlock = expected[i];
83
+ const actualBlock = actual[i];
84
+ expect(actualBlock.l1).toEqual(expectedBlock.l1);
85
+ expect(actualBlock.block.equals(expectedBlock.block)).toBe(true);
86
+ expect(actualBlock.attestations.every((a, i) => a.equals(expectedBlock.attestations[i]))).toBe(true);
87
+ }
88
+ };
59
89
 
60
90
  beforeEach(async () => {
61
91
  store = await getStore();
62
- blocks = await timesParallel(10, async i => makeL1Published(await L2Block.random(i + 1), i + 10));
92
+ blocks = await timesParallel(10, async i => makePublished(await L2Block.random(i + 1), i + 10));
63
93
  });
64
94
 
65
95
  describe('addBlocks', () => {
@@ -71,6 +101,18 @@ export function describeArchiverDataStore(
71
101
  await store.addBlocks(blocks);
72
102
  await expect(store.addBlocks(blocks)).resolves.toBe(true);
73
103
  });
104
+
105
+ it('throws an error if the previous block does not exist in the store', async () => {
106
+ const block = makePublished(await L2Block.random(2), 2);
107
+ await expect(store.addBlocks([block])).rejects.toThrow(InitialBlockNumberNotSequentialError);
108
+ await expect(store.getPublishedBlocks(1, 10)).resolves.toEqual([]);
109
+ });
110
+
111
+ it('throws an error if there is a gap in the blocks being added', async () => {
112
+ const blocks = [makePublished(await L2Block.random(1), 1), makePublished(await L2Block.random(3), 3)];
113
+ await expect(store.addBlocks(blocks)).rejects.toThrow(BlockNumberNotSequentialError);
114
+ await expect(store.getPublishedBlocks(1, 10)).resolves.toEqual([]);
115
+ });
74
116
  });
75
117
 
76
118
  describe('unwindBlocks', () => {
@@ -78,28 +120,50 @@ export function describeArchiverDataStore(
78
120
  await store.addBlocks(blocks);
79
121
  const blockNumber = await store.getSynchedL2BlockNumber();
80
122
 
81
- expect(await store.getBlocks(blockNumber, 1)).toEqual([blocks[blocks.length - 1]]);
123
+ expectBlocksEqual(await store.getPublishedBlocks(blockNumber, 1), [blocks[blocks.length - 1]]);
82
124
 
83
125
  await store.unwindBlocks(blockNumber, 1);
84
126
 
85
127
  expect(await store.getSynchedL2BlockNumber()).toBe(blockNumber - 1);
86
- expect(await store.getBlocks(blockNumber, 1)).toEqual([]);
128
+ expect(await store.getPublishedBlocks(blockNumber, 1)).toEqual([]);
87
129
  });
88
130
 
89
131
  it('can unwind multiple empty blocks', async () => {
90
- const emptyBlocks = await timesParallel(10, async i => makeL1Published(await L2Block.random(i + 1, 0), i + 10));
132
+ const emptyBlocks = await timesParallel(10, async i => makePublished(await L2Block.random(i + 1, 0), i + 10));
91
133
  await store.addBlocks(emptyBlocks);
92
134
  expect(await store.getSynchedL2BlockNumber()).toBe(10);
93
135
 
94
136
  await store.unwindBlocks(10, 3);
95
137
  expect(await store.getSynchedL2BlockNumber()).toBe(7);
96
- expect((await store.getBlocks(1, 10)).map(b => b.data.number)).toEqual([1, 2, 3, 4, 5, 6, 7]);
138
+ expect((await store.getPublishedBlocks(1, 10)).map(b => b.block.number)).toEqual([1, 2, 3, 4, 5, 6, 7]);
97
139
  });
98
140
 
99
141
  it('refuses to unwind blocks if the tip is not the last block', async () => {
100
142
  await store.addBlocks(blocks);
101
143
  await expect(store.unwindBlocks(5, 1)).rejects.toThrow(/can only unwind blocks from the tip/i);
102
144
  });
145
+
146
+ it('unwound blocks and headers cannot be retrieved by hash or archive', async () => {
147
+ await store.addBlocks(blocks);
148
+ const lastBlock = blocks[blocks.length - 1];
149
+ const blockHash = await lastBlock.block.hash();
150
+ const archive = lastBlock.block.archive.root;
151
+
152
+ // Verify block and header exist before unwinding
153
+ expect(await store.getPublishedBlockByHash(blockHash)).toBeDefined();
154
+ expect(await store.getPublishedBlockByArchive(archive)).toBeDefined();
155
+ expect(await store.getBlockHeaderByHash(blockHash)).toBeDefined();
156
+ expect(await store.getBlockHeaderByArchive(archive)).toBeDefined();
157
+
158
+ // Unwind the block
159
+ await store.unwindBlocks(lastBlock.block.number, 1);
160
+
161
+ // Verify neither block nor header can be retrieved after unwinding
162
+ expect(await store.getPublishedBlockByHash(blockHash)).toBeUndefined();
163
+ expect(await store.getPublishedBlockByArchive(archive)).toBeUndefined();
164
+ expect(await store.getBlockHeaderByHash(blockHash)).toBeUndefined();
165
+ expect(await store.getBlockHeaderByArchive(archive)).toBeUndefined();
166
+ });
103
167
  });
104
168
 
105
169
  describe('getBlocks', () => {
@@ -108,19 +172,112 @@ export function describeArchiverDataStore(
108
172
  });
109
173
 
110
174
  it.each(blockTests)('retrieves previously stored blocks', async (start, limit, getExpectedBlocks) => {
111
- await expect(store.getBlocks(start, limit)).resolves.toEqual(getExpectedBlocks());
175
+ expectBlocksEqual(await store.getPublishedBlocks(start, limit), getExpectedBlocks());
112
176
  });
113
177
 
114
178
  it('returns an empty array if no blocks are found', async () => {
115
- await expect(store.getBlocks(12, 1)).resolves.toEqual([]);
179
+ await expect(store.getPublishedBlocks(12, 1)).resolves.toEqual([]);
116
180
  });
117
181
 
118
182
  it('throws an error if limit is invalid', async () => {
119
- await expect(store.getBlocks(1, 0)).rejects.toThrow('Invalid limit: 0');
183
+ await expect(store.getPublishedBlocks(1, 0)).rejects.toThrow('Invalid limit: 0');
120
184
  });
121
185
 
122
186
  it('throws an error if `from` it is out of range', async () => {
123
- await expect(store.getBlocks(INITIAL_L2_BLOCK_NUM - 100, 1)).rejects.toThrow('Invalid start: -99');
187
+ await expect(store.getPublishedBlocks(INITIAL_L2_BLOCK_NUM - 100, 1)).rejects.toThrow('Invalid start: -99');
188
+ });
189
+
190
+ it('throws an error if unexpected initial block number is found', async () => {
191
+ await store.addBlocks([makePublished(await L2Block.random(21), 31)], { force: true });
192
+ await expect(store.getPublishedBlocks(20, 1)).rejects.toThrow(`mismatch`);
193
+ });
194
+
195
+ it('throws an error if a gap is found', async () => {
196
+ await store.addBlocks(
197
+ [makePublished(await L2Block.random(20), 30), makePublished(await L2Block.random(22), 32)],
198
+ { force: true },
199
+ );
200
+ await expect(store.getPublishedBlocks(20, 2)).rejects.toThrow(`mismatch`);
201
+ });
202
+ });
203
+
204
+ describe('getPublishedBlockByHash', () => {
205
+ beforeEach(async () => {
206
+ await store.addBlocks(blocks);
207
+ });
208
+
209
+ it('retrieves a block by its hash', async () => {
210
+ const expectedBlock = blocks[5];
211
+ const blockHash = await expectedBlock.block.hash();
212
+ const retrievedBlock = await store.getPublishedBlockByHash(blockHash);
213
+
214
+ expect(retrievedBlock).toBeDefined();
215
+ expectBlocksEqual([retrievedBlock!], [expectedBlock]);
216
+ });
217
+
218
+ it('returns undefined for non-existent block hash', async () => {
219
+ const nonExistentHash = Fr.random();
220
+ await expect(store.getPublishedBlockByHash(nonExistentHash)).resolves.toBeUndefined();
221
+ });
222
+ });
223
+
224
+ describe('getPublishedBlockByArchive', () => {
225
+ beforeEach(async () => {
226
+ await store.addBlocks(blocks);
227
+ });
228
+
229
+ it('retrieves a block by its archive root', async () => {
230
+ const expectedBlock = blocks[3];
231
+ const archive = expectedBlock.block.archive.root;
232
+ const retrievedBlock = await store.getPublishedBlockByArchive(archive);
233
+
234
+ expect(retrievedBlock).toBeDefined();
235
+ expectBlocksEqual([retrievedBlock!], [expectedBlock]);
236
+ });
237
+
238
+ it('returns undefined for non-existent archive root', async () => {
239
+ const nonExistentArchive = Fr.random();
240
+ await expect(store.getPublishedBlockByArchive(nonExistentArchive)).resolves.toBeUndefined();
241
+ });
242
+ });
243
+
244
+ describe('getBlockHeaderByHash', () => {
245
+ beforeEach(async () => {
246
+ await store.addBlocks(blocks);
247
+ });
248
+
249
+ it('retrieves a block header by its hash', async () => {
250
+ const expectedBlock = blocks[7];
251
+ const blockHash = await expectedBlock.block.hash();
252
+ const retrievedHeader = await store.getBlockHeaderByHash(blockHash);
253
+
254
+ expect(retrievedHeader).toBeDefined();
255
+ expect(retrievedHeader!.equals(expectedBlock.block.getBlockHeader())).toBe(true);
256
+ });
257
+
258
+ it('returns undefined for non-existent block hash', async () => {
259
+ const nonExistentHash = Fr.random();
260
+ await expect(store.getBlockHeaderByHash(nonExistentHash)).resolves.toBeUndefined();
261
+ });
262
+ });
263
+
264
+ describe('getBlockHeaderByArchive', () => {
265
+ beforeEach(async () => {
266
+ await store.addBlocks(blocks);
267
+ });
268
+
269
+ it('retrieves a block header by its archive root', async () => {
270
+ const expectedBlock = blocks[2];
271
+ const archive = expectedBlock.block.archive.root;
272
+ const retrievedHeader = await store.getBlockHeaderByArchive(archive);
273
+
274
+ expect(retrievedHeader).toBeDefined();
275
+ expect(retrievedHeader!.equals(expectedBlock.block.getBlockHeader())).toBe(true);
276
+ });
277
+
278
+ it('returns undefined for non-existent archive root', async () => {
279
+ const nonExistentArchive = Fr.random();
280
+ await expect(store.getBlockHeaderByArchive(nonExistentArchive)).resolves.toBeUndefined();
124
281
  });
125
282
  });
126
283
 
@@ -131,7 +288,7 @@ export function describeArchiverDataStore(
131
288
 
132
289
  it("returns the most recently added block's number", async () => {
133
290
  await store.addBlocks(blocks);
134
- await expect(store.getSynchedL2BlockNumber()).resolves.toEqual(blocks.at(-1)!.data.number);
291
+ await expect(store.getSynchedL2BlockNumber()).resolves.toEqual(blocks.at(-1)!.block.number);
135
292
  });
136
293
  });
137
294
 
@@ -152,27 +309,39 @@ export function describeArchiverDataStore(
152
309
  });
153
310
 
154
311
  it('returns the L1 block number that most recently added messages from inbox', async () => {
155
- await store.addL1ToL2Messages({
156
- lastProcessedL1BlockNumber: 1n,
157
- retrievedData: [new InboxLeaf(1n, Fr.ZERO)],
158
- });
312
+ const l1BlockHash = Buffer32.random();
313
+ const l1BlockNumber = 10n;
314
+ await store.setMessageSynchedL1Block({ l1BlockNumber: 5n, l1BlockHash: Buffer32.random() });
315
+ await store.addL1ToL2Messages([makeInboxMessage(Buffer16.ZERO, { l1BlockNumber, l1BlockHash })]);
159
316
  await expect(store.getSynchPoint()).resolves.toEqual({
160
317
  blocksSynchedTo: undefined,
161
- messagesSynchedTo: 1n,
318
+ messagesSynchedTo: { l1BlockHash, l1BlockNumber },
319
+ } satisfies ArchiverL1SynchPoint);
320
+ });
321
+
322
+ it('returns the latest syncpoint if latest message is behind', async () => {
323
+ const l1BlockHash = Buffer32.random();
324
+ const l1BlockNumber = 10n;
325
+ await store.setMessageSynchedL1Block({ l1BlockNumber, l1BlockHash });
326
+ const msg = makeInboxMessage(Buffer16.ZERO, { l1BlockNumber: 5n, l1BlockHash: Buffer32.random() });
327
+ await store.addL1ToL2Messages([msg]);
328
+ await expect(store.getSynchPoint()).resolves.toEqual({
329
+ blocksSynchedTo: undefined,
330
+ messagesSynchedTo: { l1BlockHash, l1BlockNumber },
162
331
  } satisfies ArchiverL1SynchPoint);
163
332
  });
164
333
  });
165
334
 
166
335
  describe('addLogs', () => {
167
336
  it('adds private & public logs', async () => {
168
- const block = blocks[0].data;
337
+ const block = blocks[0].block;
169
338
  await expect(store.addLogs([block])).resolves.toEqual(true);
170
339
  });
171
340
  });
172
341
 
173
342
  describe('deleteLogs', () => {
174
343
  it('deletes private & public logs', async () => {
175
- const block = blocks[0].data;
344
+ const block = blocks[0].block;
176
345
  await store.addBlocks([blocks[0]]);
177
346
  await expect(store.addLogs([block])).resolves.toEqual(true);
178
347
 
@@ -193,7 +362,7 @@ export function describeArchiverDataStore(
193
362
 
194
363
  describe('getPrivateLogs', () => {
195
364
  it('gets added private logs', async () => {
196
- const block = blocks[0].data;
365
+ const block = blocks[0].block;
197
366
  await store.addBlocks([blocks[0]]);
198
367
  await store.addLogs([block]);
199
368
 
@@ -204,19 +373,25 @@ export function describeArchiverDataStore(
204
373
 
205
374
  describe('getTxEffect', () => {
206
375
  beforeEach(async () => {
207
- await store.addLogs(blocks.map(b => b.data));
376
+ await store.addLogs(blocks.map(b => b.block));
208
377
  await store.addBlocks(blocks);
209
378
  });
210
379
 
211
380
  it.each([
212
- () => wrapInBlock(blocks[0].data.body.txEffects[0], blocks[0].data),
213
- () => wrapInBlock(blocks[9].data.body.txEffects[3], blocks[9].data),
214
- () => wrapInBlock(blocks[3].data.body.txEffects[1], blocks[3].data),
215
- () => wrapInBlock(blocks[5].data.body.txEffects[2], blocks[5].data),
216
- () => wrapInBlock(blocks[1].data.body.txEffects[0], blocks[1].data),
381
+ () => ({ data: blocks[0].block.body.txEffects[0], block: blocks[0].block, txIndexInBlock: 0 }),
382
+ () => ({ data: blocks[9].block.body.txEffects[3], block: blocks[9].block, txIndexInBlock: 3 }),
383
+ () => ({ data: blocks[3].block.body.txEffects[1], block: blocks[3].block, txIndexInBlock: 1 }),
384
+ () => ({ data: blocks[5].block.body.txEffects[2], block: blocks[5].block, txIndexInBlock: 2 }),
385
+ () => ({ data: blocks[1].block.body.txEffects[0], block: blocks[1].block, txIndexInBlock: 0 }),
217
386
  ])('retrieves a previously stored transaction', async getExpectedTx => {
218
- const expectedTx = await getExpectedTx();
219
- const actualTx = await store.getTxEffect(expectedTx.data.txHash);
387
+ const { data, block, txIndexInBlock } = getExpectedTx();
388
+ const expectedTx: IndexedTxEffect = {
389
+ data,
390
+ l2BlockNumber: block.number,
391
+ l2BlockHash: L2BlockHash.fromField(await block.hash()),
392
+ txIndexInBlock,
393
+ };
394
+ const actualTx = await store.getTxEffect(data.txHash);
220
395
  expect(actualTx).toEqual(expectedTx);
221
396
  });
222
397
 
@@ -225,11 +400,11 @@ export function describeArchiverDataStore(
225
400
  });
226
401
 
227
402
  it.each([
228
- () => wrapInBlock(blocks[0].data.body.txEffects[0], blocks[0].data),
229
- () => wrapInBlock(blocks[9].data.body.txEffects[3], blocks[9].data),
230
- () => wrapInBlock(blocks[3].data.body.txEffects[1], blocks[3].data),
231
- () => wrapInBlock(blocks[5].data.body.txEffects[2], blocks[5].data),
232
- () => wrapInBlock(blocks[1].data.body.txEffects[0], blocks[1].data),
403
+ () => wrapInBlock(blocks[0].block.body.txEffects[0], blocks[0].block),
404
+ () => wrapInBlock(blocks[9].block.body.txEffects[3], blocks[9].block),
405
+ () => wrapInBlock(blocks[3].block.body.txEffects[1], blocks[3].block),
406
+ () => wrapInBlock(blocks[5].block.body.txEffects[2], blocks[5].block),
407
+ () => wrapInBlock(blocks[1].block.body.txEffects[0], blocks[1].block),
233
408
  ])('tries to retrieves a previously stored transaction after deleted', async getExpectedTx => {
234
409
  await store.unwindBlocks(blocks.length, blocks.length);
235
410
 
@@ -241,45 +416,219 @@ export function describeArchiverDataStore(
241
416
  it('returns undefined if tx is not found', async () => {
242
417
  await expect(store.getTxEffect(TxHash.random())).resolves.toBeUndefined();
243
418
  });
419
+
420
+ it('does not fail if the block is unwound while requesting a tx', async () => {
421
+ const expectedTx = await wrapInBlock(blocks[1].block.body.txEffects[0], blocks[1].block);
422
+ let done = false;
423
+ void (async () => {
424
+ while (!done) {
425
+ void store.getTxEffect(expectedTx.data.txHash);
426
+ await sleep(1);
427
+ }
428
+ })();
429
+ await store.unwindBlocks(blocks.length, blocks.length);
430
+ done = true;
431
+ expect(await store.getTxEffect(expectedTx.data.txHash)).toEqual(undefined);
432
+ });
244
433
  });
245
434
 
246
435
  describe('L1 to L2 Messages', () => {
247
- const l2BlockNumber = 13n;
248
- const l1ToL2MessageSubtreeSize = 2 ** L1_TO_L2_MSG_SUBTREE_HEIGHT;
436
+ const initialL2BlockNumber = 13;
437
+
438
+ const checkMessages = async (msgs: InboxMessage[]) => {
439
+ expect(await store.getLastL1ToL2Message()).toEqual(msgs.at(-1));
440
+ expect(await toArray(store.iterateL1ToL2Messages())).toEqual(msgs);
441
+ expect(await store.getTotalL1ToL2MessageCount()).toEqual(BigInt(msgs.length));
442
+ };
443
+
444
+ const makeInboxMessagesWithFullBlocks = (blockCount: number, opts: { initialL2BlockNumber?: number } = {}) =>
445
+ makeInboxMessages(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * blockCount, {
446
+ overrideFn: (msg, i) => {
447
+ const l2BlockNumber =
448
+ (opts.initialL2BlockNumber ?? initialL2BlockNumber) + Math.floor(i / NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
449
+ const index =
450
+ InboxLeaf.smallestIndexFromL2Block(l2BlockNumber) + BigInt(i % NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
451
+ return { ...msg, l2BlockNumber, index };
452
+ },
453
+ });
454
+
455
+ it('stores first message ever', async () => {
456
+ const msg = makeInboxMessage(Buffer16.ZERO, { index: 0n, l2BlockNumber: 1 });
457
+ await store.addL1ToL2Messages([msg]);
458
+
459
+ await checkMessages([msg]);
460
+ expect(await store.getL1ToL2Messages(1)).toEqual([msg.leaf]);
461
+ });
249
462
 
250
- const generateBlockMessages = (blockNumber: bigint, numMessages: number) =>
251
- Array.from(
252
- { length: numMessages },
253
- (_, i) => new InboxLeaf(InboxLeaf.smallestIndexFromL2Block(blockNumber) + BigInt(i), Fr.random()),
463
+ it('stores single message', async () => {
464
+ const msg = makeInboxMessage(Buffer16.ZERO, { l2BlockNumber: 2 });
465
+ await store.addL1ToL2Messages([msg]);
466
+
467
+ await checkMessages([msg]);
468
+ expect(await store.getL1ToL2Messages(2)).toEqual([msg.leaf]);
469
+ });
470
+
471
+ it('stores and returns messages across different blocks', async () => {
472
+ const msgs = makeInboxMessages(5, { initialL2BlockNumber });
473
+ await store.addL1ToL2Messages(msgs);
474
+
475
+ await checkMessages(msgs);
476
+ expect(await store.getL1ToL2Messages(initialL2BlockNumber + 2)).toEqual([msgs[2]].map(m => m.leaf));
477
+ });
478
+
479
+ it('stores the same messages again', async () => {
480
+ const msgs = makeInboxMessages(5, { initialL2BlockNumber });
481
+ await store.addL1ToL2Messages(msgs);
482
+ await store.addL1ToL2Messages(msgs.slice(2));
483
+
484
+ await checkMessages(msgs);
485
+ });
486
+
487
+ it('stores and returns messages across different blocks with gaps', async () => {
488
+ const msgs1 = makeInboxMessages(3, { initialL2BlockNumber: 1 });
489
+ const msgs2 = makeInboxMessages(3, { initialL2BlockNumber: 20, initialHash: msgs1.at(-1)!.rollingHash });
490
+
491
+ await store.addL1ToL2Messages(msgs1);
492
+ await store.addL1ToL2Messages(msgs2);
493
+
494
+ await checkMessages([...msgs1, ...msgs2]);
495
+
496
+ expect(await store.getL1ToL2Messages(1)).toEqual([msgs1[0].leaf]);
497
+ expect(await store.getL1ToL2Messages(4)).toEqual([]);
498
+ expect(await store.getL1ToL2Messages(20)).toEqual([msgs2[0].leaf]);
499
+ expect(await store.getL1ToL2Messages(24)).toEqual([]);
500
+ });
501
+
502
+ it('stores and returns messages with block numbers larger than a byte', async () => {
503
+ const msgs = makeInboxMessages(5, { initialL2BlockNumber: 1000 });
504
+ await store.addL1ToL2Messages(msgs);
505
+
506
+ await checkMessages(msgs);
507
+ expect(await store.getL1ToL2Messages(1002)).toEqual([msgs[2]].map(m => m.leaf));
508
+ });
509
+
510
+ it('stores and returns multiple messages per block', async () => {
511
+ const msgs = makeInboxMessagesWithFullBlocks(4);
512
+ await store.addL1ToL2Messages(msgs);
513
+
514
+ await checkMessages(msgs);
515
+ const blockMessages = await store.getL1ToL2Messages(initialL2BlockNumber + 1);
516
+ expect(blockMessages).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
517
+ expect(blockMessages).toEqual(
518
+ msgs.slice(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * 2).map(m => m.leaf),
254
519
  );
520
+ });
255
521
 
256
- it('returns messages in correct order', async () => {
257
- const msgs = generateBlockMessages(l2BlockNumber, l1ToL2MessageSubtreeSize);
258
- const shuffledMessages = msgs.slice().sort(() => randomInt(1) - 0.5);
259
- await store.addL1ToL2Messages({ lastProcessedL1BlockNumber: 100n, retrievedData: shuffledMessages });
260
- const retrievedMessages = await store.getL1ToL2Messages(l2BlockNumber);
522
+ it('stores messages in multiple operations', async () => {
523
+ const msgs = makeInboxMessages(20, { initialL2BlockNumber });
524
+ await store.addL1ToL2Messages(msgs.slice(0, 10));
525
+ await store.addL1ToL2Messages(msgs.slice(10, 20));
261
526
 
262
- const expectedLeavesOrder = msgs.map(msg => msg.leaf);
263
- expect(expectedLeavesOrder).toEqual(retrievedMessages);
527
+ expect(await store.getL1ToL2Messages(initialL2BlockNumber + 2)).toEqual([msgs[2]].map(m => m.leaf));
528
+ expect(await store.getL1ToL2Messages(initialL2BlockNumber + 12)).toEqual([msgs[12]].map(m => m.leaf));
529
+ await checkMessages(msgs);
264
530
  });
265
531
 
266
- it('throws if it is impossible to sequence messages correctly', async () => {
267
- const msgs = generateBlockMessages(l2BlockNumber, l1ToL2MessageSubtreeSize - 1);
268
- // We replace a message with index 4 with a message with index at the end of the tree
269
- // --> with that there will be a gap and it will be impossible to sequence the
270
- // end of tree = start of next tree/block - 1
271
- msgs[4] = new InboxLeaf(InboxLeaf.smallestIndexFromL2Block(l2BlockNumber + 1n) - 1n, Fr.random());
532
+ it('iterates over messages from start index', async () => {
533
+ const msgs = makeInboxMessages(10, { initialL2BlockNumber });
534
+ await store.addL1ToL2Messages(msgs);
272
535
 
273
- await store.addL1ToL2Messages({ lastProcessedL1BlockNumber: 100n, retrievedData: msgs });
274
- await expect(async () => {
275
- await store.getL1ToL2Messages(l2BlockNumber);
276
- }).rejects.toThrow(`L1 to L2 message gap found in block ${l2BlockNumber}`);
536
+ const iterated = await toArray(store.iterateL1ToL2Messages({ start: msgs[3].index }));
537
+ expect(iterated).toEqual(msgs.slice(3));
538
+ });
539
+
540
+ it('iterates over messages in reverse', async () => {
541
+ const msgs = makeInboxMessages(10, { initialL2BlockNumber });
542
+ await store.addL1ToL2Messages(msgs);
543
+
544
+ const iterated = await toArray(store.iterateL1ToL2Messages({ reverse: true, end: msgs[3].index }));
545
+ expect(iterated).toEqual(msgs.slice(0, 4).reverse());
546
+ });
547
+
548
+ it('throws if messages are added out of order', async () => {
549
+ const msgs = makeInboxMessages(5, { overrideFn: (msg, i) => ({ ...msg, index: BigInt(10 - i) }) });
550
+ await expect(store.addL1ToL2Messages(msgs)).rejects.toThrow(MessageStoreError);
551
+ });
552
+
553
+ it('throws if block number for the first message is out of order', async () => {
554
+ const msgs = makeInboxMessages(4, { initialL2BlockNumber });
555
+ msgs[2].l2BlockNumber = initialL2BlockNumber - 1;
556
+ await store.addL1ToL2Messages(msgs.slice(0, 2));
557
+ await expect(store.addL1ToL2Messages(msgs.slice(2, 4))).rejects.toThrow(MessageStoreError);
558
+ });
559
+
560
+ it('throws if rolling hash is not correct', async () => {
561
+ const msgs = makeInboxMessages(5);
562
+ msgs[1].rollingHash = Buffer16.random();
563
+ await expect(store.addL1ToL2Messages(msgs)).rejects.toThrow(MessageStoreError);
564
+ });
565
+
566
+ it('throws if rolling hash for first message is not correct', async () => {
567
+ const msgs = makeInboxMessages(4);
568
+ msgs[2].rollingHash = Buffer16.random();
569
+ await store.addL1ToL2Messages(msgs.slice(0, 2));
570
+ await expect(store.addL1ToL2Messages(msgs.slice(2, 4))).rejects.toThrow(MessageStoreError);
571
+ });
572
+
573
+ it('throws if index is not in the correct range', async () => {
574
+ const msgs = makeInboxMessages(5, { initialL2BlockNumber });
575
+ msgs.at(-1)!.index += 100n;
576
+ await expect(store.addL1ToL2Messages(msgs)).rejects.toThrow(MessageStoreError);
577
+ });
578
+
579
+ it('throws if first index in block has gaps', async () => {
580
+ const msgs = makeInboxMessages(4, { initialL2BlockNumber });
581
+ msgs[2].index++;
582
+ await expect(store.addL1ToL2Messages(msgs)).rejects.toThrow(MessageStoreError);
583
+ });
584
+
585
+ it('throws if index does not follow previous one', async () => {
586
+ const msgs = makeInboxMessages(2, {
587
+ initialL2BlockNumber,
588
+ overrideFn: (msg, i) => ({
589
+ ...msg,
590
+ l2BlockNumber: 2,
591
+ index: BigInt(i + NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * 2),
592
+ }),
593
+ });
594
+ msgs[1].index++;
595
+ await expect(store.addL1ToL2Messages(msgs)).rejects.toThrow(MessageStoreError);
596
+ });
597
+
598
+ it('removes messages up to the given block number', async () => {
599
+ const msgs = makeInboxMessagesWithFullBlocks(4, { initialL2BlockNumber: 1 });
600
+
601
+ await store.addL1ToL2Messages(msgs);
602
+ await checkMessages(msgs);
603
+
604
+ expect(await store.getL1ToL2Messages(1)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
605
+ expect(await store.getL1ToL2Messages(2)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
606
+ expect(await store.getL1ToL2Messages(3)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
607
+ expect(await store.getL1ToL2Messages(4)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
608
+
609
+ await store.rollbackL1ToL2MessagesToL2Block(2);
610
+
611
+ expect(await store.getL1ToL2Messages(1)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
612
+ expect(await store.getL1ToL2Messages(2)).toHaveLength(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
613
+ expect(await store.getL1ToL2Messages(3)).toHaveLength(0);
614
+ expect(await store.getL1ToL2Messages(4)).toHaveLength(0);
615
+
616
+ await checkMessages(msgs.slice(0, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP * 2));
617
+ });
618
+
619
+ it('removes messages starting with the given index', async () => {
620
+ const msgs = makeInboxMessagesWithFullBlocks(4, { initialL2BlockNumber: 1 });
621
+ await store.addL1ToL2Messages(msgs);
622
+
623
+ await store.removeL1ToL2Messages(msgs[13].index);
624
+ await checkMessages(msgs.slice(0, 13));
277
625
  });
278
626
  });
279
627
 
280
628
  describe('contractInstances', () => {
281
629
  let contractInstance: ContractInstanceWithAddress;
282
630
  const blockNum = 10;
631
+ const timestamp = 3600n;
283
632
 
284
633
  beforeEach(async () => {
285
634
  const classId = Fr.random();
@@ -292,16 +641,111 @@ export function describeArchiverDataStore(
292
641
  });
293
642
 
294
643
  it('returns previously stored contract instances', async () => {
295
- await expect(store.getContractInstance(contractInstance.address)).resolves.toMatchObject(contractInstance);
644
+ await expect(store.getContractInstance(contractInstance.address, timestamp)).resolves.toMatchObject(
645
+ contractInstance,
646
+ );
296
647
  });
297
648
 
298
649
  it('returns undefined if contract instance is not found', async () => {
299
- await expect(store.getContractInstance(await AztecAddress.random())).resolves.toBeUndefined();
650
+ await expect(store.getContractInstance(await AztecAddress.random(), timestamp)).resolves.toBeUndefined();
300
651
  });
301
652
 
302
653
  it('returns undefined if previously stored contract instances was deleted', async () => {
303
654
  await store.deleteContractInstances([contractInstance], blockNum);
304
- await expect(store.getContractInstance(contractInstance.address)).resolves.toBeUndefined();
655
+ await expect(store.getContractInstance(contractInstance.address, timestamp)).resolves.toBeUndefined();
656
+ });
657
+ });
658
+
659
+ describe('contractInstanceUpdates', () => {
660
+ let contractInstance: ContractInstanceWithAddress;
661
+ let classId: Fr;
662
+ let nextClassId: Fr;
663
+ const timestampOfChange = 3600n;
664
+
665
+ beforeEach(async () => {
666
+ classId = Fr.random();
667
+ nextClassId = Fr.random();
668
+ const randomInstance = await SerializableContractInstance.random({
669
+ currentContractClassId: classId,
670
+ originalContractClassId: classId,
671
+ });
672
+ contractInstance = { ...randomInstance, address: await AztecAddress.random() };
673
+ await store.addContractInstances([contractInstance], 1);
674
+ await store.addContractInstanceUpdates(
675
+ [
676
+ {
677
+ prevContractClassId: classId,
678
+ newContractClassId: nextClassId,
679
+ timestampOfChange,
680
+ address: contractInstance.address,
681
+ },
682
+ ],
683
+ timestampOfChange - 1n,
684
+ );
685
+ });
686
+
687
+ it('gets the correct current class id for a contract not updated yet', async () => {
688
+ const fetchedInstance = await store.getContractInstance(contractInstance.address, timestampOfChange - 1n);
689
+ expect(fetchedInstance?.originalContractClassId).toEqual(classId);
690
+ expect(fetchedInstance?.currentContractClassId).toEqual(classId);
691
+ });
692
+
693
+ it('gets the correct current class id for a contract that has just been updated', async () => {
694
+ const fetchedInstance = await store.getContractInstance(contractInstance.address, timestampOfChange);
695
+ expect(fetchedInstance?.originalContractClassId).toEqual(classId);
696
+ expect(fetchedInstance?.currentContractClassId).toEqual(nextClassId);
697
+ });
698
+
699
+ it('gets the correct current class id for a contract that was updated in the past', async () => {
700
+ const fetchedInstance = await store.getContractInstance(contractInstance.address, timestampOfChange + 1n);
701
+ expect(fetchedInstance?.originalContractClassId).toEqual(classId);
702
+ expect(fetchedInstance?.currentContractClassId).toEqual(nextClassId);
703
+ });
704
+
705
+ it('ignores updates for the wrong contract', async () => {
706
+ const otherClassId = Fr.random();
707
+ const randomInstance = await SerializableContractInstance.random({
708
+ currentContractClassId: otherClassId,
709
+ originalContractClassId: otherClassId,
710
+ });
711
+ const otherContractInstance = {
712
+ ...randomInstance,
713
+ address: await AztecAddress.random(),
714
+ };
715
+ await store.addContractInstances([otherContractInstance], 1);
716
+
717
+ const fetchedInstance = await store.getContractInstance(otherContractInstance.address, timestampOfChange + 1n);
718
+ expect(fetchedInstance?.originalContractClassId).toEqual(otherClassId);
719
+ expect(fetchedInstance?.currentContractClassId).toEqual(otherClassId);
720
+ });
721
+
722
+ it('bounds its search to the right contract if more than than one update exists', async () => {
723
+ const otherClassId = Fr.random();
724
+ const otherNextClassId = Fr.random();
725
+ const randomInstance = await SerializableContractInstance.random({
726
+ currentContractClassId: otherClassId,
727
+ originalContractClassId: otherNextClassId,
728
+ });
729
+ const otherContractInstance = {
730
+ ...randomInstance,
731
+ address: await AztecAddress.random(),
732
+ };
733
+ await store.addContractInstances([otherContractInstance], 1);
734
+ await store.addContractInstanceUpdates(
735
+ [
736
+ {
737
+ prevContractClassId: otherClassId,
738
+ newContractClassId: otherNextClassId,
739
+ timestampOfChange,
740
+ address: otherContractInstance.address,
741
+ },
742
+ ],
743
+ timestampOfChange - 1n,
744
+ );
745
+
746
+ const fetchedInstance = await store.getContractInstance(contractInstance.address, timestampOfChange + 1n);
747
+ expect(fetchedInstance?.originalContractClassId).toEqual(classId);
748
+ expect(fetchedInstance?.currentContractClassId).toEqual(nextClassId);
305
749
  });
306
750
  });
307
751
 
@@ -356,19 +800,19 @@ export function describeArchiverDataStore(
356
800
  expect(stored?.privateFunctions).toEqual(fns);
357
801
  });
358
802
 
359
- it('adds new unconstrained functions', async () => {
360
- const fns = times(3, makeUnconstrainedFunctionWithMembershipProof);
803
+ it('adds new utility functions', async () => {
804
+ const fns = times(3, makeUtilityFunctionWithMembershipProof);
361
805
  await store.addFunctions(contractClass.id, [], fns);
362
806
  const stored = await store.getContractClass(contractClass.id);
363
- expect(stored?.unconstrainedFunctions).toEqual(fns);
807
+ expect(stored?.utilityFunctions).toEqual(fns);
364
808
  });
365
809
 
366
- it('does not duplicate unconstrained functions', async () => {
367
- const fns = times(3, makeUnconstrainedFunctionWithMembershipProof);
810
+ it('does not duplicate utility functions', async () => {
811
+ const fns = times(3, makeUtilityFunctionWithMembershipProof);
368
812
  await store.addFunctions(contractClass.id, [], fns.slice(0, 1));
369
813
  await store.addFunctions(contractClass.id, [], fns);
370
814
  const stored = await store.getContractClass(contractClass.id);
371
- expect(stored?.unconstrainedFunctions).toEqual(fns);
815
+ expect(stored?.utilityFunctions).toEqual(fns);
372
816
  });
373
817
  });
374
818
 
@@ -378,32 +822,25 @@ export function describeArchiverDataStore(
378
822
  const numPrivateLogsPerTx = 3;
379
823
  const numPublicLogsPerTx = 2;
380
824
 
381
- let blocks: L1Published<L2Block>[];
825
+ let blocks: PublishedL2Block[];
382
826
 
383
827
  const makeTag = (blockNumber: number, txIndex: number, logIndex: number, isPublic = false) =>
384
- new Fr((blockNumber * 100 + txIndex * 10 + logIndex) * (isPublic ? 123 : 1));
385
-
386
- // See parseLogFromPublic
387
- // 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.
388
- const makeLengthsField = (publicValuesLen: number, privateValuesLen: number) => {
389
- const buf = Buffer.alloc(32);
390
- buf.writeUint16BE(publicValuesLen, 27);
391
- buf.writeUint16BE(privateValuesLen, 30);
392
- return Fr.fromBuffer(buf);
393
- };
828
+ blockNumber === 1 && txIndex === 0 && logIndex === 0
829
+ ? Fr.ZERO // Shared tag
830
+ : new Fr((blockNumber * 100 + txIndex * 10 + logIndex) * (isPublic ? 123 : 1));
394
831
 
395
832
  const makePrivateLog = (tag: Fr) =>
396
- PrivateLog.fromFields([tag, ...times(PRIVATE_LOG_SIZE_IN_FIELDS - 1, i => new Fr(tag.toNumber() + i))]);
833
+ PrivateLog.from({
834
+ fields: makeTuple(PRIVATE_LOG_SIZE_IN_FIELDS, i => (!i ? tag : new Fr(tag.toNumber() + i))),
835
+ emittedLength: PRIVATE_LOG_SIZE_IN_FIELDS,
836
+ });
397
837
 
398
- // The tag lives in field 1, not 0, of a public log
399
- // See extractTaggedLogsFromPublic and noir-projects/aztec-nr/aztec/src/macros/notes/mod.nr -> emit_log
400
838
  const makePublicLog = (tag: Fr) =>
401
- PublicLog.fromFields([
402
- AztecAddress.fromNumber(1).toField(), // log address
403
- makeLengthsField(2, PUBLIC_LOG_DATA_SIZE_IN_FIELDS - 3), // field 0
404
- tag, // field 1
405
- ...times(PUBLIC_LOG_DATA_SIZE_IN_FIELDS - 1, i => new Fr(tag.toNumber() + i)), // fields 2 to end
406
- ]);
839
+ PublicLog.from({
840
+ contractAddress: AztecAddress.fromNumber(1),
841
+ // Arbitrary length
842
+ fields: new Array(10).fill(null).map((_, i) => (!i ? tag : new Fr(tag.toNumber() + i))),
843
+ });
407
844
 
408
845
  const mockPrivateLogs = (blockNumber: number, txIndex: number) => {
409
846
  return times(numPrivateLogsPerTx, (logIndex: number) => {
@@ -419,9 +856,9 @@ export function describeArchiverDataStore(
419
856
  });
420
857
  };
421
858
 
422
- const mockBlockWithLogs = async (blockNumber: number): Promise<L1Published<L2Block>> => {
859
+ const mockBlockWithLogs = async (blockNumber: number): Promise<PublishedL2Block> => {
423
860
  const block = await L2Block.random(blockNumber);
424
- block.header.globalVariables.blockNumber = new Fr(blockNumber);
861
+ block.header.globalVariables.blockNumber = blockNumber;
425
862
 
426
863
  block.body.txEffects = await timesParallel(numTxsPerBlock, async (txIndex: number) => {
427
864
  const txEffect = await TxEffect.random();
@@ -430,36 +867,41 @@ export function describeArchiverDataStore(
430
867
  return txEffect;
431
868
  });
432
869
 
433
- return {
434
- data: block,
435
- l1: { blockNumber: BigInt(blockNumber), blockHash: `0x${blockNumber}`, timestamp: BigInt(blockNumber) },
436
- };
870
+ return PublishedL2Block.fromFields({
871
+ block: block,
872
+ attestations: times(3, CommitteeAttestation.random),
873
+ l1: {
874
+ blockNumber: BigInt(blockNumber),
875
+ blockHash: makeBlockHash(blockNumber),
876
+ timestamp: BigInt(blockNumber),
877
+ },
878
+ });
437
879
  };
438
880
 
439
881
  beforeEach(async () => {
440
- blocks = await timesParallel(numBlocks, (index: number) => mockBlockWithLogs(index));
882
+ blocks = await timesParallel(numBlocks, (index: number) => mockBlockWithLogs(index + 1));
441
883
 
442
884
  await store.addBlocks(blocks);
443
- await store.addLogs(blocks.map(b => b.data));
885
+ await store.addLogs(blocks.map(b => b.block));
444
886
  });
445
887
 
446
888
  it('is possible to batch request private logs via tags', async () => {
447
- const tags = [makeTag(1, 1, 2), makeTag(0, 2, 0)];
889
+ const tags = [makeTag(2, 1, 2), makeTag(1, 2, 0)];
448
890
 
449
891
  const logsByTags = await store.getLogsByTags(tags);
450
892
 
451
893
  expect(logsByTags).toEqual([
452
894
  [
453
895
  expect.objectContaining({
454
- blockNumber: 1,
455
- logData: makePrivateLog(tags[0]).toBuffer(),
896
+ blockNumber: 2,
897
+ log: makePrivateLog(tags[0]),
456
898
  isFromPublic: false,
457
899
  }),
458
900
  ],
459
901
  [
460
902
  expect.objectContaining({
461
- blockNumber: 0,
462
- logData: makePrivateLog(tags[1]).toBuffer(),
903
+ blockNumber: 1,
904
+ log: makePrivateLog(tags[1]),
463
905
  isFromPublic: false,
464
906
  }),
465
907
  ],
@@ -467,21 +909,21 @@ export function describeArchiverDataStore(
467
909
  });
468
910
 
469
911
  it('is possible to batch request all logs (private and public) via tags', async () => {
470
- // Tag(0, 0, 0) is shared with the first private log and the first public log.
471
- const tags = [makeTag(0, 0, 0)];
912
+ // Tag(1, 0, 0) is shared with the first private log and the first public log.
913
+ const tags = [makeTag(1, 0, 0)];
472
914
 
473
915
  const logsByTags = await store.getLogsByTags(tags);
474
916
 
475
917
  expect(logsByTags).toEqual([
476
918
  [
477
919
  expect.objectContaining({
478
- blockNumber: 0,
479
- logData: makePrivateLog(tags[0]).toBuffer(),
920
+ blockNumber: 1,
921
+ log: makePrivateLog(tags[0]),
480
922
  isFromPublic: false,
481
923
  }),
482
924
  expect.objectContaining({
483
- blockNumber: 0,
484
- logData: makePublicLog(tags[0]).toBuffer(),
925
+ blockNumber: 1,
926
+ log: makePublicLog(tags[0]),
485
927
  isFromPublic: true,
486
928
  }),
487
929
  ],
@@ -494,11 +936,11 @@ export function describeArchiverDataStore(
494
936
  // Create a block containing logs that have the same tag as the blocks before.
495
937
  const newBlockNumber = numBlocks;
496
938
  const newBlock = await mockBlockWithLogs(newBlockNumber);
497
- const newLog = newBlock.data.body.txEffects[1].privateLogs[1];
939
+ const newLog = newBlock.block.body.txEffects[1].privateLogs[1];
498
940
  newLog.fields[0] = tags[0];
499
- newBlock.data.body.txEffects[1].privateLogs[1] = newLog;
941
+ newBlock.block.body.txEffects[1].privateLogs[1] = newLog;
500
942
  await store.addBlocks([newBlock]);
501
- await store.addLogs([newBlock.data]);
943
+ await store.addLogs([newBlock.block]);
502
944
 
503
945
  const logsByTags = await store.getLogsByTags(tags);
504
946
 
@@ -506,12 +948,12 @@ export function describeArchiverDataStore(
506
948
  [
507
949
  expect.objectContaining({
508
950
  blockNumber: 1,
509
- logData: makePrivateLog(tags[0]).toBuffer(),
951
+ log: makePrivateLog(tags[0]),
510
952
  isFromPublic: false,
511
953
  }),
512
954
  expect.objectContaining({
513
955
  blockNumber: newBlockNumber,
514
- logData: newLog.toBuffer(),
956
+ log: newLog,
515
957
  isFromPublic: false,
516
958
  }),
517
959
  ],
@@ -530,42 +972,12 @@ export function describeArchiverDataStore(
530
972
  [
531
973
  expect.objectContaining({
532
974
  blockNumber: 1,
533
- logData: makePrivateLog(tags[1]).toBuffer(),
975
+ log: makePrivateLog(tags[1]),
534
976
  isFromPublic: false,
535
977
  }),
536
978
  ],
537
979
  ]);
538
980
  });
539
-
540
- it('is not possible to add public logs by tag if they are invalid', async () => {
541
- const tag = makeTag(99, 88, 77);
542
- const invalidLogs = [
543
- PublicLog.fromFields([
544
- AztecAddress.fromNumber(1).toField(),
545
- makeLengthsField(2, 3), // This field claims we have 5 items, but we actually have more
546
- tag,
547
- ...times(PUBLIC_LOG_DATA_SIZE_IN_FIELDS - 1, i => new Fr(tag.toNumber() + i)),
548
- ]),
549
- PublicLog.fromFields([
550
- AztecAddress.fromNumber(1).toField(),
551
- makeLengthsField(2, PUBLIC_LOG_DATA_SIZE_IN_FIELDS), // This field claims we have more than the max items
552
- tag,
553
- ...times(PUBLIC_LOG_DATA_SIZE_IN_FIELDS - 1, i => new Fr(tag.toNumber() + i)),
554
- ]),
555
- ];
556
-
557
- // Create a block containing these invalid logs
558
- const newBlockNumber = numBlocks;
559
- const newBlock = await mockBlockWithLogs(newBlockNumber);
560
- newBlock.data.body.txEffects[0].publicLogs = invalidLogs;
561
- await store.addBlocks([newBlock]);
562
- await store.addLogs([newBlock.data]);
563
-
564
- const logsByTags = await store.getLogsByTags([tag]);
565
-
566
- // Neither of the logs should have been added:
567
- expect(logsByTags).toEqual([[]]);
568
- });
569
981
  });
570
982
 
571
983
  describe('getPublicLogs', () => {
@@ -573,27 +985,30 @@ export function describeArchiverDataStore(
573
985
  const numPublicFunctionCalls = 3;
574
986
  const numPublicLogs = 2;
575
987
  const numBlocks = 10;
576
- let blocks: L1Published<L2Block>[];
988
+ let blocks: PublishedL2Block[];
577
989
 
578
990
  beforeEach(async () => {
579
- blocks = await timesParallel(numBlocks, async (index: number) => ({
580
- data: await L2Block.random(index + 1, txsPerBlock, numPublicFunctionCalls, numPublicLogs),
581
- l1: { blockNumber: BigInt(index), blockHash: `0x${index}`, timestamp: BigInt(index) },
582
- }));
991
+ blocks = await timesParallel(numBlocks, async (index: number) =>
992
+ PublishedL2Block.fromFields({
993
+ block: await L2Block.random(index + 1, txsPerBlock, numPublicFunctionCalls, numPublicLogs),
994
+ l1: { blockNumber: BigInt(index), blockHash: makeBlockHash(index), timestamp: BigInt(index) },
995
+ attestations: times(3, CommitteeAttestation.random),
996
+ }),
997
+ );
583
998
 
584
999
  await store.addBlocks(blocks);
585
- await store.addLogs(blocks.map(b => b.data));
1000
+ await store.addLogs(blocks.map(b => b.block));
586
1001
  });
587
1002
 
588
1003
  it('no logs returned if deleted ("txHash" filter param is respected variant)', async () => {
589
1004
  // get random tx
590
1005
  const targetBlockIndex = randomInt(numBlocks);
591
1006
  const targetTxIndex = randomInt(txsPerBlock);
592
- const targetTxHash = blocks[targetBlockIndex].data.body.txEffects[targetTxIndex].txHash;
1007
+ const targetTxHash = blocks[targetBlockIndex].block.body.txEffects[targetTxIndex].txHash;
593
1008
 
594
1009
  await Promise.all([
595
1010
  store.unwindBlocks(blocks.length, blocks.length),
596
- store.deleteLogs(blocks.map(b => b.data)),
1011
+ store.deleteLogs(blocks.map(b => b.block)),
597
1012
  ]);
598
1013
 
599
1014
  const response = await store.getPublicLogs({ txHash: targetTxHash });
@@ -607,7 +1022,7 @@ export function describeArchiverDataStore(
607
1022
  // get random tx
608
1023
  const targetBlockIndex = randomInt(numBlocks);
609
1024
  const targetTxIndex = randomInt(txsPerBlock);
610
- const targetTxHash = blocks[targetBlockIndex].data.body.txEffects[targetTxIndex].txHash;
1025
+ const targetTxHash = blocks[targetBlockIndex].block.body.txEffects[targetTxIndex].txHash;
611
1026
 
612
1027
  const response = await store.getPublicLogs({ txHash: targetTxHash });
613
1028
  const logs = response.logs;
@@ -650,7 +1065,7 @@ export function describeArchiverDataStore(
650
1065
  const targetTxIndex = randomInt(txsPerBlock);
651
1066
  const targetLogIndex = randomInt(numPublicLogs * numPublicFunctionCalls);
652
1067
  const targetContractAddress =
653
- blocks[targetBlockIndex].data.body.txEffects[targetTxIndex].publicLogs[targetLogIndex].contractAddress;
1068
+ blocks[targetBlockIndex].block.body.txEffects[targetTxIndex].publicLogs[targetLogIndex].contractAddress;
654
1069
 
655
1070
  const response = await store.getPublicLogs({ contractAddress: targetContractAddress });
656
1071
 
@@ -754,56 +1169,94 @@ export function describeArchiverDataStore(
754
1169
  });
755
1170
  });
756
1171
 
757
- describe('findNullifiersIndexesWithBlock', () => {
758
- let blocks: L2Block[];
759
- const numBlocks = 10;
760
- const nullifiersPerBlock = new Map<number, Fr[]>();
1172
+ describe('pendingChainValidationStatus', () => {
1173
+ it('should return undefined when no status is set', async () => {
1174
+ const status = await store.getPendingChainValidationStatus();
1175
+ expect(status).toBeUndefined();
1176
+ });
761
1177
 
762
- beforeEach(async () => {
763
- blocks = await timesParallel(numBlocks, (index: number) => L2Block.random(index + 1, 1));
1178
+ it('should store and retrieve a valid validation status', async () => {
1179
+ const validStatus: ValidateBlockResult = { valid: true };
764
1180
 
765
- blocks.forEach((block, blockIndex) => {
766
- nullifiersPerBlock.set(
767
- blockIndex,
768
- block.body.txEffects.flatMap(txEffect => txEffect.nullifiers),
769
- );
770
- });
1181
+ await store.setPendingChainValidationStatus(validStatus);
1182
+ const retrievedStatus = await store.getPendingChainValidationStatus();
1183
+
1184
+ expect(retrievedStatus).toEqual(validStatus);
771
1185
  });
772
1186
 
773
- it('returns wrapped nullifiers with blocks if they exist', async () => {
774
- await store.addNullifiers(blocks);
775
- const nullifiersToRetrieve = [...nullifiersPerBlock.get(0)!, ...nullifiersPerBlock.get(5)!, Fr.random()];
776
- const blockScopedNullifiers = await store.findNullifiersIndexesWithBlock(10, nullifiersToRetrieve);
1187
+ it('should store and retrieve an invalid validation status with insufficient attestations', async () => {
1188
+ const invalidStatus: ValidateBlockResult = {
1189
+ valid: false,
1190
+ block: randomBlockInfo(1),
1191
+ committee: [EthAddress.random(), EthAddress.random()],
1192
+ epoch: 123n,
1193
+ seed: 456n,
1194
+ attestors: [EthAddress.random()],
1195
+ attestations: [CommitteeAttestation.random()],
1196
+ reason: 'insufficient-attestations',
1197
+ };
777
1198
 
778
- expect(blockScopedNullifiers).toHaveLength(nullifiersToRetrieve.length);
779
- const [undefinedNullifier] = blockScopedNullifiers.slice(-1);
780
- const realNullifiers = blockScopedNullifiers.slice(0, -1);
781
- realNullifiers.forEach((blockScopedNullifier, index) => {
782
- expect(blockScopedNullifier).not.toBeUndefined();
783
- const { data, l2BlockNumber } = blockScopedNullifier!;
784
- expect(data).toEqual(expect.any(BigInt));
785
- expect(l2BlockNumber).toEqual(index < MAX_NULLIFIERS_PER_TX ? 1 : 6);
786
- });
787
- expect(undefinedNullifier).toBeUndefined();
788
- });
789
-
790
- it('returns wrapped nullifiers filtering by blockNumber', async () => {
791
- await store.addNullifiers(blocks);
792
- const nullifiersToRetrieve = [...nullifiersPerBlock.get(0)!, ...nullifiersPerBlock.get(5)!];
793
- const blockScopedNullifiers = await store.findNullifiersIndexesWithBlock(5, nullifiersToRetrieve);
794
-
795
- expect(blockScopedNullifiers).toHaveLength(nullifiersToRetrieve.length);
796
- const undefinedNullifiers = blockScopedNullifiers.slice(-MAX_NULLIFIERS_PER_TX);
797
- const realNullifiers = blockScopedNullifiers.slice(0, -MAX_NULLIFIERS_PER_TX);
798
- realNullifiers.forEach(blockScopedNullifier => {
799
- expect(blockScopedNullifier).not.toBeUndefined();
800
- const { data, l2BlockNumber } = blockScopedNullifier!;
801
- expect(data).toEqual(expect.any(BigInt));
802
- expect(l2BlockNumber).toEqual(1);
803
- });
804
- undefinedNullifiers.forEach(undefinedNullifier => {
805
- expect(undefinedNullifier).toBeUndefined();
806
- });
1199
+ await store.setPendingChainValidationStatus(invalidStatus);
1200
+ const retrievedStatus = await store.getPendingChainValidationStatus();
1201
+
1202
+ expect(retrievedStatus).toEqual(invalidStatus);
1203
+ });
1204
+
1205
+ it('should store and retrieve an invalid validation status with invalid attestation', async () => {
1206
+ const invalidStatus: ValidateBlockResult = {
1207
+ valid: false,
1208
+ block: randomBlockInfo(2),
1209
+ committee: [EthAddress.random()],
1210
+ attestors: [EthAddress.random()],
1211
+ epoch: 789n,
1212
+ seed: 101n,
1213
+ attestations: [CommitteeAttestation.random()],
1214
+ reason: 'invalid-attestation',
1215
+ invalidIndex: 5,
1216
+ };
1217
+
1218
+ await store.setPendingChainValidationStatus(invalidStatus);
1219
+ const retrievedStatus = await store.getPendingChainValidationStatus();
1220
+
1221
+ expect(retrievedStatus).toEqual(invalidStatus);
1222
+ });
1223
+
1224
+ it('should overwrite existing status when setting a new one', async () => {
1225
+ const firstStatus: ValidateBlockResult = { valid: true };
1226
+ const secondStatus: ValidateBlockResult = {
1227
+ valid: false,
1228
+ block: randomBlockInfo(3),
1229
+ committee: [EthAddress.random()],
1230
+ epoch: 999n,
1231
+ seed: 888n,
1232
+ attestors: [EthAddress.random()],
1233
+ attestations: [CommitteeAttestation.random()],
1234
+ reason: 'insufficient-attestations',
1235
+ };
1236
+
1237
+ await store.setPendingChainValidationStatus(firstStatus);
1238
+ await store.setPendingChainValidationStatus(secondStatus);
1239
+ const retrievedStatus = await store.getPendingChainValidationStatus();
1240
+
1241
+ expect(retrievedStatus).toEqual(secondStatus);
1242
+ });
1243
+
1244
+ it('should handle empty committee and attestations arrays', async () => {
1245
+ const statusWithEmptyArrays: ValidateBlockResult = {
1246
+ valid: false,
1247
+ block: randomBlockInfo(4),
1248
+ committee: [],
1249
+ epoch: 0n,
1250
+ seed: 0n,
1251
+ attestors: [],
1252
+ attestations: [],
1253
+ reason: 'insufficient-attestations',
1254
+ };
1255
+
1256
+ await store.setPendingChainValidationStatus(statusWithEmptyArrays);
1257
+ const retrievedStatus = await store.getPendingChainValidationStatus();
1258
+
1259
+ expect(retrievedStatus).toEqual(statusWithEmptyArrays);
807
1260
  });
808
1261
  });
809
1262
  });