@aztec/archiver 0.0.0-test.1 → 0.0.1-commit.5476d83

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