@aztec/archiver 0.56.0 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +1 -1
  2. package/dest/archiver/archiver.d.ts +23 -20
  3. package/dest/archiver/archiver.d.ts.map +1 -1
  4. package/dest/archiver/archiver.js +353 -103
  5. package/dest/archiver/archiver_store.d.ts +39 -9
  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 +75 -18
  9. package/dest/archiver/config.js +6 -6
  10. package/dest/archiver/data_retrieval.d.ts +2 -3
  11. package/dest/archiver/data_retrieval.d.ts.map +1 -1
  12. package/dest/archiver/data_retrieval.js +21 -20
  13. package/dest/archiver/epoch_helpers.d.ts +15 -0
  14. package/dest/archiver/epoch_helpers.d.ts.map +1 -0
  15. package/dest/archiver/epoch_helpers.js +23 -0
  16. package/dest/archiver/kv_archiver_store/block_store.d.ts +20 -1
  17. package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
  18. package/dest/archiver/kv_archiver_store/block_store.js +62 -5
  19. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +2 -1
  20. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +1 -1
  21. package/dest/archiver/kv_archiver_store/contract_class_store.js +11 -4
  22. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +1 -0
  23. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
  24. package/dest/archiver/kv_archiver_store/contract_instance_store.js +4 -1
  25. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +29 -9
  26. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
  27. package/dest/archiver/kv_archiver_store/kv_archiver_store.js +57 -17
  28. package/dest/archiver/kv_archiver_store/log_store.d.ts +4 -5
  29. package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
  30. package/dest/archiver/kv_archiver_store/log_store.js +18 -14
  31. package/dest/archiver/kv_archiver_store/message_store.d.ts +1 -0
  32. package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
  33. package/dest/archiver/kv_archiver_store/message_store.js +10 -3
  34. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts +1 -0
  35. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts.map +1 -1
  36. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.js +4 -1
  37. package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts +23 -22
  38. package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts.map +1 -1
  39. package/dest/archiver/memory_archiver_store/memory_archiver_store.js +129 -69
  40. package/dest/index.js +2 -1
  41. package/dest/test/index.d.ts +2 -0
  42. package/dest/test/index.d.ts.map +1 -0
  43. package/dest/test/index.js +2 -0
  44. package/dest/test/mock_l2_block_source.d.ts +73 -0
  45. package/dest/test/mock_l2_block_source.d.ts.map +1 -0
  46. package/dest/test/mock_l2_block_source.js +134 -0
  47. package/package.json +15 -11
  48. package/src/archiver/archiver.ts +457 -149
  49. package/src/archiver/archiver_store.ts +44 -16
  50. package/src/archiver/archiver_store_test_suite.ts +91 -52
  51. package/src/archiver/config.ts +5 -5
  52. package/src/archiver/data_retrieval.ts +23 -24
  53. package/src/archiver/epoch_helpers.ts +26 -0
  54. package/src/archiver/kv_archiver_store/block_store.ts +70 -2
  55. package/src/archiver/kv_archiver_store/contract_class_store.ts +18 -5
  56. package/src/archiver/kv_archiver_store/contract_instance_store.ts +4 -0
  57. package/src/archiver/kv_archiver_store/kv_archiver_store.ts +65 -24
  58. package/src/archiver/kv_archiver_store/log_store.ts +18 -18
  59. package/src/archiver/kv_archiver_store/message_store.ts +9 -0
  60. package/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts +4 -0
  61. package/src/archiver/memory_archiver_store/memory_archiver_store.ts +149 -80
  62. package/src/index.ts +1 -0
  63. package/src/test/index.ts +1 -0
  64. package/src/test/mock_l2_block_source.ts +165 -0
  65. package/dest/archiver/kv_archiver_store/proven_store.d.ts +0 -14
  66. package/dest/archiver/kv_archiver_store/proven_store.d.ts.map +0 -1
  67. package/dest/archiver/kv_archiver_store/proven_store.js +0 -30
  68. package/src/archiver/kv_archiver_store/proven_store.ts +0 -34
@@ -1,6 +1,4 @@
1
1
  import {
2
- type EncryptedL2BlockL2Logs,
3
- type EncryptedNoteL2BlockL2Logs,
4
2
  type FromLogType,
5
3
  type GetUnencryptedLogsResponse,
6
4
  type InboxLeaf,
@@ -11,9 +9,8 @@ import {
11
9
  type TxEffect,
12
10
  type TxHash,
13
11
  type TxReceipt,
14
- type UnencryptedL2BlockL2Logs,
15
12
  } from '@aztec/circuit-types';
16
- import { type Fr } from '@aztec/circuits.js';
13
+ import { type Fr, type Header } from '@aztec/circuits.js';
17
14
  import { type ContractArtifact } from '@aztec/foundation/abi';
18
15
  import { type AztecAddress } from '@aztec/foundation/aztec-address';
19
16
  import {
@@ -23,7 +20,7 @@ import {
23
20
  type UnconstrainedFunctionWithMembershipProof,
24
21
  } from '@aztec/types/contracts';
25
22
 
26
- import { type DataRetrieval, type SingletonDataRetrieval } from './structs/data_retrieval.js';
23
+ import { type DataRetrieval } from './structs/data_retrieval.js';
27
24
  import { type L1Published } from './structs/published.js';
28
25
 
29
26
  /**
@@ -50,6 +47,15 @@ export interface ArchiverDataStore {
50
47
  */
51
48
  addBlocks(blocks: L1Published<L2Block>[]): Promise<boolean>;
52
49
 
50
+ /**
51
+ * Unwinds blocks from the database
52
+ * @param from - The tip of the chain, passed for verification purposes,
53
+ * ensuring that we don't end up deleting something we did not intend
54
+ * @param blocksToUnwind - The number of blocks we are to unwind
55
+ * @returns True if the operation is successful
56
+ */
57
+ unwindBlocks(from: number, blocksToUnwind: number): Promise<boolean>;
58
+
53
59
  /**
54
60
  * Gets up to `limit` amount of L2 blocks starting from `from`.
55
61
  * @param from - Number of the first block to return (inclusive).
@@ -58,6 +64,14 @@ export interface ArchiverDataStore {
58
64
  */
59
65
  getBlocks(from: number, limit: number): Promise<L1Published<L2Block>[]>;
60
66
 
67
+ /**
68
+ * Gets up to `limit` amount of L2 block headers starting from `from`.
69
+ * @param from - Number of the first block to return (inclusive).
70
+ * @param limit - The number of blocks to return.
71
+ * @returns The requested L2 block headers.
72
+ */
73
+ getBlockHeaders(from: number, limit: number): Promise<Header[]>;
74
+
61
75
  /**
62
76
  * Gets a tx effect.
63
77
  * @param txHash - The txHash of the tx corresponding to the tx effect.
@@ -74,18 +88,11 @@ export interface ArchiverDataStore {
74
88
 
75
89
  /**
76
90
  * Append new logs to the store's list.
77
- * @param noteEncryptedLogs - The note encrypted logs to be added to the store.
78
- * @param encryptedLogs - The encrypted logs to be added to the store.
79
- * @param unencryptedLogs - The unencrypted logs to be added to the store.
80
- * @param blockNumber - The block for which to add the logs.
91
+ * @param blocks - The blocks for which to add the logs.
81
92
  * @returns True if the operation is successful.
82
93
  */
83
- addLogs(
84
- noteEncryptedLogs: EncryptedNoteL2BlockL2Logs | undefined,
85
- encryptedLogs: EncryptedL2BlockL2Logs | undefined,
86
- unencryptedLogs: UnencryptedL2BlockL2Logs | undefined,
87
- blockNumber: number,
88
- ): Promise<boolean>;
94
+ addLogs(blocks: L2Block[]): Promise<boolean>;
95
+ deleteLogs(blocks: L2Block[]): Promise<boolean>;
89
96
 
90
97
  /**
91
98
  * Append L1 to L2 messages to the store.
@@ -109,6 +116,12 @@ export interface ArchiverDataStore {
109
116
  */
110
117
  getL1ToL2MessageIndex(l1ToL2Message: Fr, startIndex: bigint): Promise<bigint | undefined>;
111
118
 
119
+ /**
120
+ * Get the total number of L1 to L2 messages
121
+ * @returns The number of L1 to L2 messages in the store
122
+ */
123
+ getTotalL1ToL2MessageCount(): Promise<bigint>;
124
+
112
125
  /**
113
126
  * Gets up to `limit` amount of logs starting from `from`.
114
127
  * @param from - Number of the L2 block to which corresponds the first logs to be returned.
@@ -141,11 +154,23 @@ export interface ArchiverDataStore {
141
154
  */
142
155
  getProvenL2BlockNumber(): Promise<number>;
143
156
 
157
+ /**
158
+ * Gets the number of the latest proven L2 epoch.
159
+ * @returns The number of the latest proven L2 epoch.
160
+ */
161
+ getProvenL2EpochNumber(): Promise<number | undefined>;
162
+
144
163
  /**
145
164
  * Stores the number of the latest proven L2 block processed.
146
165
  * @param l2BlockNumber - The number of the latest proven L2 block processed.
147
166
  */
148
- setProvenL2BlockNumber(l2BlockNumber: SingletonDataRetrieval<number>): Promise<void>;
167
+ setProvenL2BlockNumber(l2BlockNumber: number): Promise<void>;
168
+
169
+ /**
170
+ * Stores the number of the latest proven L2 epoch.
171
+ * @param l2EpochNumber - The number of the latest proven L2 epoch.
172
+ */
173
+ setProvenL2EpochNumber(l2EpochNumber: number): Promise<void>;
149
174
 
150
175
  /**
151
176
  * Stores the l1 block number that blocks have been synched until
@@ -172,6 +197,8 @@ export interface ArchiverDataStore {
172
197
  */
173
198
  addContractClasses(data: ContractClassPublic[], blockNumber: number): Promise<boolean>;
174
199
 
200
+ deleteContractClasses(data: ContractClassPublic[], blockNumber: number): Promise<boolean>;
201
+
175
202
  /**
176
203
  * Returns a contract class given its id, or undefined if not exists.
177
204
  * @param id - Id of the contract class.
@@ -185,6 +212,7 @@ export interface ArchiverDataStore {
185
212
  * @returns True if the operation is successful.
186
213
  */
187
214
  addContractInstances(data: ContractInstanceWithAddress[], blockNumber: number): Promise<boolean>;
215
+ deleteContractInstances(data: ContractInstanceWithAddress[], blockNumber: number): Promise<boolean>;
188
216
 
189
217
  /**
190
218
  * Adds private functions to a contract class.
@@ -52,6 +52,20 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
52
52
  });
53
53
  });
54
54
 
55
+ describe('unwindBlocks', () => {
56
+ it('unwinding blocks will remove blocks from the chain', async () => {
57
+ await store.addBlocks(blocks);
58
+ const blockNumber = await store.getSynchedL2BlockNumber();
59
+
60
+ expect(await store.getBlocks(blockNumber, 1)).toEqual([blocks[blocks.length - 1]]);
61
+
62
+ await store.unwindBlocks(blockNumber, 1);
63
+
64
+ expect(await store.getSynchedL2BlockNumber()).toBe(blockNumber - 1);
65
+ expect(await store.getBlocks(blockNumber, 1)).toEqual([]);
66
+ });
67
+ });
68
+
55
69
  describe('getBlocks', () => {
56
70
  beforeEach(async () => {
57
71
  await store.addBlocks(blocks);
@@ -69,8 +83,8 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
69
83
  await expect(store.getBlocks(1, 0)).rejects.toThrow('Invalid limit: 0');
70
84
  });
71
85
 
72
- it('resets `from` to the first block if it is out of range', async () => {
73
- await expect(store.getBlocks(INITIAL_L2_BLOCK_NUM - 100, 1)).resolves.toEqual(blocks.slice(0, 1));
86
+ it('throws an error if `from` it is out of range', async () => {
87
+ await expect(store.getBlocks(INITIAL_L2_BLOCK_NUM - 100, 1)).rejects.toThrow('Invalid start: -99');
74
88
  });
75
89
  });
76
90
 
@@ -90,7 +104,6 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
90
104
  await expect(store.getSynchPoint()).resolves.toEqual({
91
105
  blocksSynchedTo: undefined,
92
106
  messagesSynchedTo: undefined,
93
- provenLogsSynchedTo: undefined,
94
107
  } satisfies ArchiverL1SynchPoint);
95
108
  });
96
109
 
@@ -99,7 +112,6 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
99
112
  await expect(store.getSynchPoint()).resolves.toEqual({
100
113
  blocksSynchedTo: 19n,
101
114
  messagesSynchedTo: undefined,
102
- provenLogsSynchedTo: undefined,
103
115
  } satisfies ArchiverL1SynchPoint);
104
116
  });
105
117
 
@@ -111,16 +123,6 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
111
123
  await expect(store.getSynchPoint()).resolves.toEqual({
112
124
  blocksSynchedTo: undefined,
113
125
  messagesSynchedTo: 1n,
114
- provenLogsSynchedTo: undefined,
115
- } satisfies ArchiverL1SynchPoint);
116
- });
117
-
118
- it('returns the L1 block number that most recently logged a proven block', async () => {
119
- await store.setProvenL2BlockNumber({ lastProcessedL1BlockNumber: 3n, retrievedData: 5 });
120
- await expect(store.getSynchPoint()).resolves.toEqual({
121
- blocksSynchedTo: undefined,
122
- messagesSynchedTo: undefined,
123
- provenLogsSynchedTo: 3n,
124
126
  } satisfies ArchiverL1SynchPoint);
125
127
  });
126
128
  });
@@ -128,14 +130,26 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
128
130
  describe('addLogs', () => {
129
131
  it('adds encrypted & unencrypted logs', async () => {
130
132
  const block = blocks[0].data;
131
- await expect(
132
- store.addLogs(
133
- block.body.noteEncryptedLogs,
134
- block.body.encryptedLogs,
135
- block.body.unencryptedLogs,
136
- block.number,
137
- ),
138
- ).resolves.toEqual(true);
133
+ await expect(store.addLogs([block])).resolves.toEqual(true);
134
+ });
135
+ });
136
+
137
+ describe('deleteLogs', () => {
138
+ it('deletes encrypted & unencrypted logs', async () => {
139
+ const block = blocks[0].data;
140
+ await store.addBlocks([blocks[0]]);
141
+ await expect(store.addLogs([block])).resolves.toEqual(true);
142
+
143
+ expect((await store.getLogs(1, 1, LogType.NOTEENCRYPTED))[0]).toEqual(block.body.noteEncryptedLogs);
144
+ expect((await store.getLogs(1, 1, LogType.ENCRYPTED))[0]).toEqual(block.body.encryptedLogs);
145
+ expect((await store.getLogs(1, 1, LogType.UNENCRYPTED))[0]).toEqual(block.body.unencryptedLogs);
146
+
147
+ // This one is a pain for memory as we would never want to just delete memory in the middle.
148
+ await store.deleteLogs([block]);
149
+
150
+ expect((await store.getLogs(1, 1, LogType.NOTEENCRYPTED))[0]).toEqual(undefined);
151
+ expect((await store.getLogs(1, 1, LogType.ENCRYPTED))[0]).toEqual(undefined);
152
+ expect((await store.getLogs(1, 1, LogType.UNENCRYPTED))[0]).toEqual(undefined);
139
153
  });
140
154
  });
141
155
 
@@ -145,16 +159,8 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
145
159
  ['unencrypted', LogType.UNENCRYPTED],
146
160
  ])('getLogs (%s)', (_, logType) => {
147
161
  beforeEach(async () => {
148
- await Promise.all(
149
- blocks.map(block =>
150
- store.addLogs(
151
- block.data.body.noteEncryptedLogs,
152
- block.data.body.encryptedLogs,
153
- block.data.body.unencryptedLogs,
154
- block.data.number,
155
- ),
156
- ),
157
- );
162
+ await store.addBlocks(blocks);
163
+ await store.addLogs(blocks.map(b => b.data));
158
164
  });
159
165
 
160
166
  it.each(blockTests)('retrieves previously stored logs', async (from, limit, getExpectedBlocks) => {
@@ -176,16 +182,7 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
176
182
 
177
183
  describe('getTxEffect', () => {
178
184
  beforeEach(async () => {
179
- await Promise.all(
180
- blocks.map(block =>
181
- store.addLogs(
182
- block.data.body.noteEncryptedLogs,
183
- block.data.body.encryptedLogs,
184
- block.data.body.unencryptedLogs,
185
- block.data.number,
186
- ),
187
- ),
188
- );
185
+ await store.addLogs(blocks.map(b => b.data));
189
186
  await store.addBlocks(blocks);
190
187
  });
191
188
 
@@ -204,6 +201,24 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
204
201
  it('returns undefined if tx is not found', async () => {
205
202
  await expect(store.getTxEffect(new TxHash(Fr.random().toBuffer()))).resolves.toBeUndefined();
206
203
  });
204
+
205
+ it.each([
206
+ () => blocks[0].data.body.txEffects[0],
207
+ () => blocks[9].data.body.txEffects[3],
208
+ () => blocks[3].data.body.txEffects[1],
209
+ () => blocks[5].data.body.txEffects[2],
210
+ () => blocks[1].data.body.txEffects[0],
211
+ ])('tries to retrieves a previously stored transaction after deleted', async getExpectedTx => {
212
+ await store.unwindBlocks(blocks.length, blocks.length);
213
+
214
+ const expectedTx = getExpectedTx();
215
+ const actualTx = await store.getTxEffect(expectedTx.txHash);
216
+ expect(actualTx).toEqual(undefined);
217
+ });
218
+
219
+ it('returns undefined if tx is not found', async () => {
220
+ await expect(store.getTxEffect(new TxHash(Fr.random().toBuffer()))).resolves.toBeUndefined();
221
+ });
207
222
  });
208
223
 
209
224
  describe('L1 to L2 Messages', () => {
@@ -274,6 +289,11 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
274
289
  it('returns undefined if contract instance is not found', async () => {
275
290
  await expect(store.getContractInstance(AztecAddress.random())).resolves.toBeUndefined();
276
291
  });
292
+
293
+ it('returns undefined if previously stored contract instances was deleted', async () => {
294
+ await store.deleteContractInstances([contractInstance], blockNum);
295
+ await expect(store.getContractInstance(contractInstance.address)).resolves.toBeUndefined();
296
+ });
277
297
  });
278
298
 
279
299
  describe('contractClasses', () => {
@@ -289,6 +309,17 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
289
309
  await expect(store.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass);
290
310
  });
291
311
 
312
+ it('returns undefined if the initial deployed contract class was deleted', async () => {
313
+ await store.deleteContractClasses([contractClass], blockNum);
314
+ await expect(store.getContractClass(contractClass.id)).resolves.toBeUndefined();
315
+ });
316
+
317
+ it('returns contract class if later "deployment" class was deleted', async () => {
318
+ await store.addContractClasses([contractClass], blockNum + 1);
319
+ await store.deleteContractClasses([contractClass], blockNum + 1);
320
+ await expect(store.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass);
321
+ });
322
+
292
323
  it('returns undefined if contract class is not found', async () => {
293
324
  await expect(store.getContractClass(Fr.random())).resolves.toBeUndefined();
294
325
  });
@@ -338,17 +369,25 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
338
369
  }));
339
370
 
340
371
  await store.addBlocks(blocks);
372
+ await store.addLogs(blocks.map(b => b.data));
373
+ });
341
374
 
342
- await Promise.all(
343
- blocks.map(block =>
344
- store.addLogs(
345
- block.data.body.noteEncryptedLogs,
346
- block.data.body.encryptedLogs,
347
- block.data.body.unencryptedLogs,
348
- block.data.number,
349
- ),
350
- ),
351
- );
375
+ it('no logs returned if deleted ("txHash" filter param is respected variant)', async () => {
376
+ // get random tx
377
+ const targetBlockIndex = randomInt(numBlocks);
378
+ const targetTxIndex = randomInt(txsPerBlock);
379
+ const targetTxHash = blocks[targetBlockIndex].data.body.txEffects[targetTxIndex].txHash;
380
+
381
+ await Promise.all([
382
+ store.unwindBlocks(blocks.length, blocks.length),
383
+ store.deleteLogs(blocks.map(b => b.data)),
384
+ ]);
385
+
386
+ const response = await store.getUnencryptedLogs({ txHash: targetTxHash });
387
+ const logs = response.logs;
388
+
389
+ expect(response.maxLogsHit).toBeFalsy();
390
+ expect(logs.length).toEqual(0);
352
391
  });
353
392
 
354
393
  it('"txHash" filter param is respected', async () => {
@@ -52,11 +52,6 @@ export const archiverConfigMappings: ConfigMappingsType<ArchiverConfig> = {
52
52
  description: 'The polling interval in ms for retrieving new L2 blocks and encrypted logs.',
53
53
  ...numberConfigHelper(1000),
54
54
  },
55
- viemPollingIntervalMS: {
56
- env: 'ARCHIVER_VIEM_POLLING_INTERVAL_MS',
57
- description: 'The polling interval viem uses in ms',
58
- ...numberConfigHelper(1000),
59
- },
60
55
  dataDirectory: {
61
56
  env: 'DATA_DIRECTORY',
62
57
  description: 'Optional dir to store data. If omitted will store in memory.',
@@ -67,6 +62,11 @@ export const archiverConfigMappings: ConfigMappingsType<ArchiverConfig> = {
67
62
  ...numberConfigHelper(1_000),
68
63
  },
69
64
  ...l1ReaderConfigMappings,
65
+ viemPollingIntervalMS: {
66
+ env: 'ARCHIVER_VIEM_POLLING_INTERVAL_MS',
67
+ description: 'The polling interval viem uses in ms',
68
+ ...numberConfigHelper(1000),
69
+ },
70
70
  };
71
71
 
72
72
  /**
@@ -134,7 +134,9 @@ async function getBlockFromRollupTx(
134
134
  data,
135
135
  });
136
136
 
137
- if (!(functionName === 'propose')) {
137
+ const allowedMethods = ['propose', 'proposeAndClaim'];
138
+
139
+ if (!allowedMethods.includes(functionName)) {
138
140
  throw new Error(`Unexpected method called ${functionName}`);
139
141
  }
140
142
  const [headerHex, archiveRootHex, , , , bodyHex] = args! as readonly [Hex, Hex, Hex, Hex[], ViemSignature[], Hex];
@@ -237,7 +239,7 @@ export async function retrieveL2ProofsFromRollup(
237
239
  const lastProcessedL1BlockNumber = logs.length > 0 ? logs.at(-1)!.l1BlockNumber : searchStartBlock - 1n;
238
240
 
239
241
  for (const { txHash, proverId, l2BlockNumber } of logs) {
240
- const proofData = await getBlockProofFromSubmitProofTx(publicClient, txHash, l2BlockNumber, proverId);
242
+ const proofData = await getProofFromSubmitProofTx(publicClient, txHash, proverId);
241
243
  retrievedData.push({ proof: proofData.proof, proverId: proofData.proverId, l2BlockNumber, txHash });
242
244
  }
243
245
  return {
@@ -247,7 +249,6 @@ export async function retrieveL2ProofsFromRollup(
247
249
  }
248
250
 
249
251
  export type SubmitBlockProof = {
250
- header: Header;
251
252
  archiveRoot: Fr;
252
253
  proverId: Fr;
253
254
  aggregationObject: Buffer;
@@ -263,39 +264,37 @@ export type SubmitBlockProof = {
263
264
  * @param l2BlockNum - L2 block number.
264
265
  * @returns L2 block metadata (header and archive) from the calldata, deserialized
265
266
  */
266
- export async function getBlockProofFromSubmitProofTx(
267
+ export async function getProofFromSubmitProofTx(
267
268
  publicClient: PublicClient,
268
269
  txHash: `0x${string}`,
269
- l2BlockNum: bigint,
270
270
  expectedProverId: Fr,
271
271
  ): Promise<SubmitBlockProof> {
272
272
  const { input: data } = await publicClient.getTransaction({ hash: txHash });
273
- const { functionName, args } = decodeFunctionData({
274
- abi: RollupAbi,
275
- data,
276
- });
277
-
278
- if (!(functionName === 'submitBlockRootProof')) {
279
- throw new Error(`Unexpected method called ${functionName}`);
273
+ const { functionName, args } = decodeFunctionData({ abi: RollupAbi, data });
274
+
275
+ let proverId: Fr;
276
+ let archiveRoot: Fr;
277
+ let aggregationObject: Buffer;
278
+ let proof: Proof;
279
+
280
+ if (functionName === 'submitEpochRootProof') {
281
+ const [_epochSize, nestedArgs, _fees, aggregationObjectHex, proofHex] = args!;
282
+ aggregationObject = Buffer.from(hexToBytes(aggregationObjectHex));
283
+ proverId = Fr.fromString(nestedArgs[6]);
284
+ archiveRoot = Fr.fromString(nestedArgs[1]);
285
+ proof = Proof.fromBuffer(Buffer.from(hexToBytes(proofHex)));
286
+ } else {
287
+ throw new Error(`Unexpected proof method called ${functionName}`);
280
288
  }
281
- const [headerHex, archiveHex, proverIdHex, aggregationObjectHex, proofHex] = args!;
282
-
283
- const header = Header.fromBuffer(Buffer.from(hexToBytes(headerHex)));
284
- const proverId = Fr.fromString(proverIdHex);
285
289
 
286
- const blockNumberFromHeader = header.globalVariables.blockNumber.toBigInt();
287
- if (blockNumberFromHeader !== l2BlockNum) {
288
- throw new Error(`Block number mismatch: expected ${l2BlockNum} but got ${blockNumberFromHeader}`);
289
- }
290
290
  if (!proverId.equals(expectedProverId)) {
291
291
  throw new Error(`Prover ID mismatch: expected ${expectedProverId} but got ${proverId}`);
292
292
  }
293
293
 
294
294
  return {
295
- header,
296
295
  proverId,
297
- aggregationObject: Buffer.from(hexToBytes(aggregationObjectHex)),
298
- archiveRoot: Fr.fromString(archiveHex),
299
- proof: Proof.fromBuffer(Buffer.from(hexToBytes(proofHex))),
296
+ aggregationObject,
297
+ archiveRoot,
298
+ proof,
300
299
  };
301
300
  }
@@ -0,0 +1,26 @@
1
+ import { AZTEC_EPOCH_DURATION, AZTEC_SLOT_DURATION } from '@aztec/circuits.js';
2
+
3
+ /** Returns the slot number for a given timestamp. */
4
+ export function getSlotAtTimestamp(ts: bigint, constants: { l1GenesisTime: bigint }) {
5
+ return ts < constants.l1GenesisTime ? 0n : (ts - constants.l1GenesisTime) / BigInt(AZTEC_SLOT_DURATION);
6
+ }
7
+
8
+ /** Returns the epoch number for a given timestamp. */
9
+ export function getEpochNumberAtTimestamp(ts: bigint, constants: { l1GenesisTime: bigint }) {
10
+ return getSlotAtTimestamp(ts, constants) / BigInt(AZTEC_EPOCH_DURATION);
11
+ }
12
+
13
+ /** Returns the range of slots (inclusive) for a given epoch number. */
14
+ export function getSlotRangeForEpoch(epochNumber: bigint) {
15
+ const startSlot = epochNumber * BigInt(AZTEC_EPOCH_DURATION);
16
+ return [startSlot, startSlot + BigInt(AZTEC_EPOCH_DURATION) - 1n];
17
+ }
18
+
19
+ /** Returns the range of L1 timestamps (inclusive) for a given epoch number. */
20
+ export function getTimestampRangeForEpoch(epochNumber: bigint, constants: { l1GenesisTime: bigint }) {
21
+ const [startSlot, endSlot] = getSlotRangeForEpoch(epochNumber);
22
+ return [
23
+ constants.l1GenesisTime + startSlot * BigInt(AZTEC_SLOT_DURATION),
24
+ constants.l1GenesisTime + endSlot * BigInt(AZTEC_SLOT_DURATION),
25
+ ];
26
+ }
@@ -26,6 +26,12 @@ export class BlockStore {
26
26
  /** Stores L1 block number in which the last processed L2 block was included */
27
27
  #lastSynchedL1Block: AztecSingleton<bigint>;
28
28
 
29
+ /** Stores l2 block number of the last proven block */
30
+ #lastProvenL2Block: AztecSingleton<number>;
31
+
32
+ /** Stores l2 epoch number of the last proven epoch */
33
+ #lastProvenL2Epoch: AztecSingleton<number>;
34
+
29
35
  /** Index mapping transaction hash (as a string) to its location in a block */
30
36
  #txIndex: AztecMap<string, BlockIndexValue>;
31
37
 
@@ -40,6 +46,8 @@ export class BlockStore {
40
46
  this.#txIndex = db.openMap('archiver_tx_index');
41
47
  this.#contractIndex = db.openMap('archiver_contract_index');
42
48
  this.#lastSynchedL1Block = db.openSingleton('archiver_last_synched_l1_block');
49
+ this.#lastProvenL2Block = db.openSingleton('archiver_last_proven_l2_block');
50
+ this.#lastProvenL2Epoch = db.openSingleton('archiver_last_proven_l2_epoch');
43
51
  }
44
52
 
45
53
  /**
@@ -73,6 +81,38 @@ export class BlockStore {
73
81
  });
74
82
  }
75
83
 
84
+ /**
85
+ * Unwinds blocks from the database
86
+ * @param from - The tip of the chain, passed for verification purposes,
87
+ * ensuring that we don't end up deleting something we did not intend
88
+ * @param blocksToUnwind - The number of blocks we are to unwind
89
+ * @returns True if the operation is successful
90
+ */
91
+ unwindBlocks(from: number, blocksToUnwind: number) {
92
+ return this.db.transaction(() => {
93
+ const last = this.getSynchedL2BlockNumber();
94
+ if (from != last) {
95
+ throw new Error(`Can only remove from the tip`);
96
+ }
97
+
98
+ for (let i = 0; i < blocksToUnwind; i++) {
99
+ const blockNumber = from - i;
100
+ const block = this.getBlock(blockNumber);
101
+
102
+ if (block === undefined) {
103
+ throw new Error(`Cannot remove block ${blockNumber} from the store, we don't have it`);
104
+ }
105
+ void this.#blocks.delete(block.data.number);
106
+ block.data.body.txEffects.forEach(tx => {
107
+ void this.#txIndex.delete(tx.txHash.toString());
108
+ });
109
+ void this.#blockBodies.delete(block.data.body.getTxsEffectsHash().toString('hex'));
110
+ }
111
+
112
+ return true;
113
+ });
114
+ }
115
+
76
116
  /**
77
117
  * Gets up to `limit` amount of L2 blocks starting from `from`.
78
118
  * @param start - Number of the first block to return (inclusive).
@@ -99,6 +139,18 @@ export class BlockStore {
99
139
  return this.getBlockFromBlockStorage(blockStorage);
100
140
  }
101
141
 
142
+ /**
143
+ * Gets the headers for a sequence of L2 blocks.
144
+ * @param start - Number of the first block to return (inclusive).
145
+ * @param limit - The number of blocks to return.
146
+ * @returns The requested L2 block headers
147
+ */
148
+ *getBlockHeaders(start: number, limit: number): IterableIterator<Header> {
149
+ for (const blockStorage of this.#blocks.values(this.#computeBlockRange(start, limit))) {
150
+ yield Header.fromBuffer(blockStorage.header);
151
+ }
152
+ }
153
+
102
154
  private getBlockFromBlockStorage(blockStorage: BlockStorage) {
103
155
  const header = Header.fromBuffer(blockStorage.header);
104
156
  const archive = AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive);
@@ -109,7 +161,7 @@ export class BlockStore {
109
161
  }
110
162
  const body = Body.fromBuffer(blockBodyBuffer);
111
163
 
112
- const l2Block = L2Block.fromFields({ header, archive, body });
164
+ const l2Block = new L2Block(archive, header, body);
113
165
  return { data: l2Block, l1: blockStorage.l1 };
114
166
  }
115
167
 
@@ -191,13 +243,29 @@ export class BlockStore {
191
243
  void this.#lastSynchedL1Block.set(l1BlockNumber);
192
244
  }
193
245
 
246
+ getProvenL2BlockNumber(): number {
247
+ return this.#lastProvenL2Block.get() ?? 0;
248
+ }
249
+
250
+ setProvenL2BlockNumber(blockNumber: number) {
251
+ void this.#lastProvenL2Block.set(blockNumber);
252
+ }
253
+
254
+ getProvenL2EpochNumber(): number | undefined {
255
+ return this.#lastProvenL2Epoch.get();
256
+ }
257
+
258
+ setProvenL2EpochNumber(epochNumber: number) {
259
+ void this.#lastProvenL2Epoch.set(epochNumber);
260
+ }
261
+
194
262
  #computeBlockRange(start: number, limit: number): Required<Pick<Range<number>, 'start' | 'end'>> {
195
263
  if (limit < 1) {
196
264
  throw new Error(`Invalid limit: ${limit}`);
197
265
  }
198
266
 
199
267
  if (start < INITIAL_L2_BLOCK_NUM) {
200
- start = INITIAL_L2_BLOCK_NUM;
268
+ throw new Error(`Invalid start: ${start}`);
201
269
  }
202
270
 
203
271
  const end = start + limit;
@@ -3,6 +3,7 @@ import { BufferReader, numToUInt8, serializeToBuffer } from '@aztec/foundation/s
3
3
  import { type AztecKVStore, type AztecMap } from '@aztec/kv-store';
4
4
  import {
5
5
  type ContractClassPublic,
6
+ type ContractClassPublicWithBlockNumber,
6
7
  type ExecutablePrivateFunctionWithMembershipProof,
7
8
  type UnconstrainedFunctionWithMembershipProof,
8
9
  } from '@aztec/types/contracts';
@@ -17,8 +18,18 @@ export class ContractClassStore {
17
18
  this.#contractClasses = db.openMap('archiver_contract_classes');
18
19
  }
19
20
 
20
- addContractClass(contractClass: ContractClassPublic): Promise<void> {
21
- return this.#contractClasses.set(contractClass.id.toString(), serializeContractClassPublic(contractClass));
21
+ async addContractClass(contractClass: ContractClassPublic, blockNumber: number): Promise<void> {
22
+ await this.#contractClasses.setIfNotExists(
23
+ contractClass.id.toString(),
24
+ serializeContractClassPublic({ ...contractClass, l2BlockNumber: blockNumber }),
25
+ );
26
+ }
27
+
28
+ async deleteContractClasses(contractClass: ContractClassPublic, blockNumber: number): Promise<void> {
29
+ const restoredContractClass = this.#contractClasses.get(contractClass.id.toString());
30
+ if (restoredContractClass && deserializeContractClassPublic(restoredContractClass).l2BlockNumber >= blockNumber) {
31
+ await this.#contractClasses.delete(contractClass.id.toString());
32
+ }
22
33
  }
23
34
 
24
35
  getContractClass(id: Fr): ContractClassPublic | undefined {
@@ -44,7 +55,7 @@ export class ContractClassStore {
44
55
  const existingClass = deserializeContractClassPublic(existingClassBuffer);
45
56
  const { privateFunctions: existingPrivateFns, unconstrainedFunctions: existingUnconstrainedFns } = existingClass;
46
57
 
47
- const updatedClass: Omit<ContractClassPublic, 'id'> = {
58
+ const updatedClass: Omit<ContractClassPublicWithBlockNumber, 'id'> = {
48
59
  ...existingClass,
49
60
  privateFunctions: [
50
61
  ...existingPrivateFns,
@@ -63,8 +74,9 @@ export class ContractClassStore {
63
74
  }
64
75
  }
65
76
 
66
- function serializeContractClassPublic(contractClass: Omit<ContractClassPublic, 'id'>): Buffer {
77
+ function serializeContractClassPublic(contractClass: Omit<ContractClassPublicWithBlockNumber, 'id'>): Buffer {
67
78
  return serializeToBuffer(
79
+ contractClass.l2BlockNumber,
68
80
  numToUInt8(contractClass.version),
69
81
  contractClass.artifactHash,
70
82
  contractClass.publicFunctions.length,
@@ -108,9 +120,10 @@ function serializeUnconstrainedFunction(fn: UnconstrainedFunctionWithMembershipP
108
120
  );
109
121
  }
110
122
 
111
- function deserializeContractClassPublic(buffer: Buffer): Omit<ContractClassPublic, 'id'> {
123
+ function deserializeContractClassPublic(buffer: Buffer): Omit<ContractClassPublicWithBlockNumber, 'id'> {
112
124
  const reader = BufferReader.asReader(buffer);
113
125
  return {
126
+ l2BlockNumber: reader.readNumber(),
114
127
  version: reader.readUInt8() as 1,
115
128
  artifactHash: reader.readObject(Fr),
116
129
  publicFunctions: reader.readVector({