@aztec/archiver 0.56.0 → 0.58.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 (79) hide show
  1. package/README.md +1 -1
  2. package/dest/archiver/archiver.d.ts +27 -22
  3. package/dest/archiver/archiver.d.ts.map +1 -1
  4. package/dest/archiver/archiver.js +421 -115
  5. package/dest/archiver/archiver_store.d.ts +39 -10
  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 +84 -31
  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 +23 -22
  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 +3 -3
  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 +12 -5
  22. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +2 -2
  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 +5 -2
  25. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +29 -10
  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 +2 -1
  32. package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
  33. package/dest/archiver/kv_archiver_store/message_store.js +17 -13
  34. package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts +3 -2
  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 +12 -14
  37. package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts +23 -23
  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 +130 -70
  40. package/dest/factory.d.ts.map +1 -1
  41. package/dest/factory.js +17 -1
  42. package/dest/index.js +2 -1
  43. package/dest/test/index.d.ts +4 -0
  44. package/dest/test/index.d.ts.map +1 -0
  45. package/dest/test/index.js +4 -0
  46. package/dest/test/mock_archiver.d.ts +22 -0
  47. package/dest/test/mock_archiver.d.ts.map +1 -0
  48. package/dest/test/mock_archiver.js +44 -0
  49. package/dest/test/mock_l1_to_l2_message_source.d.ts +16 -0
  50. package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -0
  51. package/dest/test/mock_l1_to_l2_message_source.js +25 -0
  52. package/dest/test/mock_l2_block_source.d.ts +75 -0
  53. package/dest/test/mock_l2_block_source.d.ts.map +1 -0
  54. package/dest/test/mock_l2_block_source.js +154 -0
  55. package/package.json +15 -11
  56. package/src/archiver/archiver.ts +553 -170
  57. package/src/archiver/archiver_store.ts +48 -19
  58. package/src/archiver/archiver_store_test_suite.ts +111 -73
  59. package/src/archiver/config.ts +5 -5
  60. package/src/archiver/data_retrieval.ts +25 -26
  61. package/src/archiver/epoch_helpers.ts +26 -0
  62. package/src/archiver/kv_archiver_store/block_store.ts +70 -2
  63. package/src/archiver/kv_archiver_store/contract_class_store.ts +24 -9
  64. package/src/archiver/kv_archiver_store/contract_instance_store.ts +5 -2
  65. package/src/archiver/kv_archiver_store/kv_archiver_store.ts +71 -29
  66. package/src/archiver/kv_archiver_store/log_store.ts +18 -18
  67. package/src/archiver/kv_archiver_store/message_store.ts +16 -18
  68. package/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts +13 -19
  69. package/src/archiver/memory_archiver_store/memory_archiver_store.ts +154 -83
  70. package/src/factory.ts +18 -0
  71. package/src/index.ts +1 -0
  72. package/src/test/index.ts +3 -0
  73. package/src/test/mock_archiver.ts +55 -0
  74. package/src/test/mock_l1_to_l2_message_source.ts +31 -0
  75. package/src/test/mock_l2_block_source.ts +190 -0
  76. package/dest/archiver/kv_archiver_store/proven_store.d.ts +0 -14
  77. package/dest/archiver/kv_archiver_store/proven_store.d.ts.map +0 -1
  78. package/dest/archiver/kv_archiver_store/proven_store.js +0 -30
  79. 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,19 +9,19 @@ 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';
17
- import { type ContractArtifact } from '@aztec/foundation/abi';
18
- import { type AztecAddress } from '@aztec/foundation/aztec-address';
19
13
  import {
20
14
  type ContractClassPublic,
21
15
  type ContractInstanceWithAddress,
22
16
  type ExecutablePrivateFunctionWithMembershipProof,
17
+ type Fr,
18
+ type Header,
23
19
  type UnconstrainedFunctionWithMembershipProof,
24
- } from '@aztec/types/contracts';
20
+ } from '@aztec/circuits.js';
21
+ import { type ContractArtifact } from '@aztec/foundation/abi';
22
+ import { type AztecAddress } from '@aztec/foundation/aztec-address';
25
23
 
26
- import { type DataRetrieval, type SingletonDataRetrieval } from './structs/data_retrieval.js';
24
+ import { type DataRetrieval } from './structs/data_retrieval.js';
27
25
  import { type L1Published } from './structs/published.js';
28
26
 
29
27
  /**
@@ -50,6 +48,15 @@ export interface ArchiverDataStore {
50
48
  */
51
49
  addBlocks(blocks: L1Published<L2Block>[]): Promise<boolean>;
52
50
 
51
+ /**
52
+ * Unwinds blocks from the database
53
+ * @param from - The tip of the chain, passed for verification purposes,
54
+ * ensuring that we don't end up deleting something we did not intend
55
+ * @param blocksToUnwind - The number of blocks we are to unwind
56
+ * @returns True if the operation is successful
57
+ */
58
+ unwindBlocks(from: number, blocksToUnwind: number): Promise<boolean>;
59
+
53
60
  /**
54
61
  * Gets up to `limit` amount of L2 blocks starting from `from`.
55
62
  * @param from - Number of the first block to return (inclusive).
@@ -58,6 +65,14 @@ export interface ArchiverDataStore {
58
65
  */
59
66
  getBlocks(from: number, limit: number): Promise<L1Published<L2Block>[]>;
60
67
 
68
+ /**
69
+ * Gets up to `limit` amount of L2 block headers starting from `from`.
70
+ * @param from - Number of the first block to return (inclusive).
71
+ * @param limit - The number of blocks to return.
72
+ * @returns The requested L2 block headers.
73
+ */
74
+ getBlockHeaders(from: number, limit: number): Promise<Header[]>;
75
+
61
76
  /**
62
77
  * Gets a tx effect.
63
78
  * @param txHash - The txHash of the tx corresponding to the tx effect.
@@ -74,18 +89,11 @@ export interface ArchiverDataStore {
74
89
 
75
90
  /**
76
91
  * 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.
92
+ * @param blocks - The blocks for which to add the logs.
81
93
  * @returns True if the operation is successful.
82
94
  */
83
- addLogs(
84
- noteEncryptedLogs: EncryptedNoteL2BlockL2Logs | undefined,
85
- encryptedLogs: EncryptedL2BlockL2Logs | undefined,
86
- unencryptedLogs: UnencryptedL2BlockL2Logs | undefined,
87
- blockNumber: number,
88
- ): Promise<boolean>;
95
+ addLogs(blocks: L2Block[]): Promise<boolean>;
96
+ deleteLogs(blocks: L2Block[]): Promise<boolean>;
89
97
 
90
98
  /**
91
99
  * Append L1 to L2 messages to the store.
@@ -109,6 +117,12 @@ export interface ArchiverDataStore {
109
117
  */
110
118
  getL1ToL2MessageIndex(l1ToL2Message: Fr, startIndex: bigint): Promise<bigint | undefined>;
111
119
 
120
+ /**
121
+ * Get the total number of L1 to L2 messages
122
+ * @returns The number of L1 to L2 messages in the store
123
+ */
124
+ getTotalL1ToL2MessageCount(): Promise<bigint>;
125
+
112
126
  /**
113
127
  * Gets up to `limit` amount of logs starting from `from`.
114
128
  * @param from - Number of the L2 block to which corresponds the first logs to be returned.
@@ -141,11 +155,23 @@ export interface ArchiverDataStore {
141
155
  */
142
156
  getProvenL2BlockNumber(): Promise<number>;
143
157
 
158
+ /**
159
+ * Gets the number of the latest proven L2 epoch.
160
+ * @returns The number of the latest proven L2 epoch.
161
+ */
162
+ getProvenL2EpochNumber(): Promise<number | undefined>;
163
+
144
164
  /**
145
165
  * Stores the number of the latest proven L2 block processed.
146
166
  * @param l2BlockNumber - The number of the latest proven L2 block processed.
147
167
  */
148
- setProvenL2BlockNumber(l2BlockNumber: SingletonDataRetrieval<number>): Promise<void>;
168
+ setProvenL2BlockNumber(l2BlockNumber: number): Promise<void>;
169
+
170
+ /**
171
+ * Stores the number of the latest proven L2 epoch.
172
+ * @param l2EpochNumber - The number of the latest proven L2 epoch.
173
+ */
174
+ setProvenL2EpochNumber(l2EpochNumber: number): Promise<void>;
149
175
 
150
176
  /**
151
177
  * Stores the l1 block number that blocks have been synched until
@@ -172,6 +198,8 @@ export interface ArchiverDataStore {
172
198
  */
173
199
  addContractClasses(data: ContractClassPublic[], blockNumber: number): Promise<boolean>;
174
200
 
201
+ deleteContractClasses(data: ContractClassPublic[], blockNumber: number): Promise<boolean>;
202
+
175
203
  /**
176
204
  * Returns a contract class given its id, or undefined if not exists.
177
205
  * @param id - Id of the contract class.
@@ -185,6 +213,7 @@ export interface ArchiverDataStore {
185
213
  * @returns True if the operation is successful.
186
214
  */
187
215
  addContractInstances(data: ContractInstanceWithAddress[], blockNumber: number): Promise<boolean>;
216
+ deleteContractInstances(data: ContractInstanceWithAddress[], blockNumber: number): Promise<boolean>;
188
217
 
189
218
  /**
190
219
  * Adds private functions to a contract class.
@@ -1,6 +1,14 @@
1
1
  import { InboxLeaf, L2Block, LogId, LogType, TxHash } from '@aztec/circuit-types';
2
2
  import '@aztec/circuit-types/jest';
3
- import { AztecAddress, Fr, INITIAL_L2_BLOCK_NUM, L1_TO_L2_MSG_SUBTREE_HEIGHT } from '@aztec/circuits.js';
3
+ import {
4
+ AztecAddress,
5
+ type ContractClassPublic,
6
+ type ContractInstanceWithAddress,
7
+ Fr,
8
+ INITIAL_L2_BLOCK_NUM,
9
+ L1_TO_L2_MSG_SUBTREE_HEIGHT,
10
+ SerializableContractInstance,
11
+ } from '@aztec/circuits.js';
4
12
  import {
5
13
  makeContractClassPublic,
6
14
  makeExecutablePrivateFunctionWithMembershipProof,
@@ -8,11 +16,6 @@ import {
8
16
  } from '@aztec/circuits.js/testing';
9
17
  import { times } from '@aztec/foundation/collection';
10
18
  import { randomBytes, randomInt } from '@aztec/foundation/crypto';
11
- import {
12
- type ContractClassPublic,
13
- type ContractInstanceWithAddress,
14
- SerializableContractInstance,
15
- } from '@aztec/types/contracts';
16
19
 
17
20
  import { type ArchiverDataStore, type ArchiverL1SynchPoint } from './archiver_store.js';
18
21
  import { type L1Published } from './structs/published.js';
@@ -52,6 +55,20 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
52
55
  });
53
56
  });
54
57
 
58
+ describe('unwindBlocks', () => {
59
+ it('unwinding blocks will remove blocks from the chain', async () => {
60
+ await store.addBlocks(blocks);
61
+ const blockNumber = await store.getSynchedL2BlockNumber();
62
+
63
+ expect(await store.getBlocks(blockNumber, 1)).toEqual([blocks[blocks.length - 1]]);
64
+
65
+ await store.unwindBlocks(blockNumber, 1);
66
+
67
+ expect(await store.getSynchedL2BlockNumber()).toBe(blockNumber - 1);
68
+ expect(await store.getBlocks(blockNumber, 1)).toEqual([]);
69
+ });
70
+ });
71
+
55
72
  describe('getBlocks', () => {
56
73
  beforeEach(async () => {
57
74
  await store.addBlocks(blocks);
@@ -69,8 +86,8 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
69
86
  await expect(store.getBlocks(1, 0)).rejects.toThrow('Invalid limit: 0');
70
87
  });
71
88
 
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));
89
+ it('throws an error if `from` it is out of range', async () => {
90
+ await expect(store.getBlocks(INITIAL_L2_BLOCK_NUM - 100, 1)).rejects.toThrow('Invalid start: -99');
74
91
  });
75
92
  });
76
93
 
@@ -90,7 +107,6 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
90
107
  await expect(store.getSynchPoint()).resolves.toEqual({
91
108
  blocksSynchedTo: undefined,
92
109
  messagesSynchedTo: undefined,
93
- provenLogsSynchedTo: undefined,
94
110
  } satisfies ArchiverL1SynchPoint);
95
111
  });
96
112
 
@@ -99,28 +115,17 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
99
115
  await expect(store.getSynchPoint()).resolves.toEqual({
100
116
  blocksSynchedTo: 19n,
101
117
  messagesSynchedTo: undefined,
102
- provenLogsSynchedTo: undefined,
103
118
  } satisfies ArchiverL1SynchPoint);
104
119
  });
105
120
 
106
121
  it('returns the L1 block number that most recently added messages from inbox', async () => {
107
122
  await store.addL1ToL2Messages({
108
123
  lastProcessedL1BlockNumber: 1n,
109
- retrievedData: [new InboxLeaf(0n, 0n, Fr.ZERO)],
124
+ retrievedData: [new InboxLeaf(1n, Fr.ZERO)],
110
125
  });
111
126
  await expect(store.getSynchPoint()).resolves.toEqual({
112
127
  blocksSynchedTo: undefined,
113
128
  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
129
  } satisfies ArchiverL1SynchPoint);
125
130
  });
126
131
  });
@@ -128,14 +133,26 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
128
133
  describe('addLogs', () => {
129
134
  it('adds encrypted & unencrypted logs', async () => {
130
135
  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);
136
+ await expect(store.addLogs([block])).resolves.toEqual(true);
137
+ });
138
+ });
139
+
140
+ describe('deleteLogs', () => {
141
+ it('deletes encrypted & unencrypted logs', async () => {
142
+ const block = blocks[0].data;
143
+ await store.addBlocks([blocks[0]]);
144
+ await expect(store.addLogs([block])).resolves.toEqual(true);
145
+
146
+ expect((await store.getLogs(1, 1, LogType.NOTEENCRYPTED))[0]).toEqual(block.body.noteEncryptedLogs);
147
+ expect((await store.getLogs(1, 1, LogType.ENCRYPTED))[0]).toEqual(block.body.encryptedLogs);
148
+ expect((await store.getLogs(1, 1, LogType.UNENCRYPTED))[0]).toEqual(block.body.unencryptedLogs);
149
+
150
+ // This one is a pain for memory as we would never want to just delete memory in the middle.
151
+ await store.deleteLogs([block]);
152
+
153
+ expect((await store.getLogs(1, 1, LogType.NOTEENCRYPTED))[0]).toEqual(undefined);
154
+ expect((await store.getLogs(1, 1, LogType.ENCRYPTED))[0]).toEqual(undefined);
155
+ expect((await store.getLogs(1, 1, LogType.UNENCRYPTED))[0]).toEqual(undefined);
139
156
  });
140
157
  });
141
158
 
@@ -145,16 +162,8 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
145
162
  ['unencrypted', LogType.UNENCRYPTED],
146
163
  ])('getLogs (%s)', (_, logType) => {
147
164
  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
- );
165
+ await store.addBlocks(blocks);
166
+ await store.addLogs(blocks.map(b => b.data));
158
167
  });
159
168
 
160
169
  it.each(blockTests)('retrieves previously stored logs', async (from, limit, getExpectedBlocks) => {
@@ -176,16 +185,7 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
176
185
 
177
186
  describe('getTxEffect', () => {
178
187
  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
- );
188
+ await store.addLogs(blocks.map(b => b.data));
189
189
  await store.addBlocks(blocks);
190
190
  });
191
191
 
@@ -204,6 +204,24 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
204
204
  it('returns undefined if tx is not found', async () => {
205
205
  await expect(store.getTxEffect(new TxHash(Fr.random().toBuffer()))).resolves.toBeUndefined();
206
206
  });
207
+
208
+ it.each([
209
+ () => blocks[0].data.body.txEffects[0],
210
+ () => blocks[9].data.body.txEffects[3],
211
+ () => blocks[3].data.body.txEffects[1],
212
+ () => blocks[5].data.body.txEffects[2],
213
+ () => blocks[1].data.body.txEffects[0],
214
+ ])('tries to retrieves a previously stored transaction after deleted', async getExpectedTx => {
215
+ await store.unwindBlocks(blocks.length, blocks.length);
216
+
217
+ const expectedTx = getExpectedTx();
218
+ const actualTx = await store.getTxEffect(expectedTx.txHash);
219
+ expect(actualTx).toEqual(undefined);
220
+ });
221
+
222
+ it('returns undefined if tx is not found', async () => {
223
+ await expect(store.getTxEffect(new TxHash(Fr.random().toBuffer()))).resolves.toBeUndefined();
224
+ });
207
225
  });
208
226
 
209
227
  describe('L1 to L2 Messages', () => {
@@ -211,7 +229,10 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
211
229
  const l1ToL2MessageSubtreeSize = 2 ** L1_TO_L2_MSG_SUBTREE_HEIGHT;
212
230
 
213
231
  const generateBlockMessages = (blockNumber: bigint, numMessages: number) =>
214
- Array.from({ length: numMessages }, (_, i) => new InboxLeaf(blockNumber, BigInt(i), Fr.random()));
232
+ Array.from(
233
+ { length: numMessages },
234
+ (_, i) => new InboxLeaf(InboxLeaf.smallestIndexFromL2Block(blockNumber) + BigInt(i), Fr.random()),
235
+ );
215
236
 
216
237
  it('returns messages in correct order', async () => {
217
238
  const msgs = generateBlockMessages(l2BlockNumber, l1ToL2MessageSubtreeSize);
@@ -226,8 +247,9 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
226
247
  it('throws if it is impossible to sequence messages correctly', async () => {
227
248
  const msgs = generateBlockMessages(l2BlockNumber, l1ToL2MessageSubtreeSize - 1);
228
249
  // We replace a message with index 4 with a message with index at the end of the tree
229
- // --> with that there will be a gap and it will be impossible to sequence the messages
230
- msgs[4] = new InboxLeaf(l2BlockNumber, BigInt(l1ToL2MessageSubtreeSize - 1), Fr.random());
250
+ // --> with that there will be a gap and it will be impossible to sequence the
251
+ // end of tree = start of next tree/block - 1
252
+ msgs[4] = new InboxLeaf(InboxLeaf.smallestIndexFromL2Block(l2BlockNumber + 1n) - 1n, Fr.random());
231
253
 
232
254
  await store.addL1ToL2Messages({ lastProcessedL1BlockNumber: 100n, retrievedData: msgs });
233
255
  await expect(async () => {
@@ -235,26 +257,18 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
235
257
  }).rejects.toThrow(`L1 to L2 message gap found in block ${l2BlockNumber}`);
236
258
  });
237
259
 
238
- it('throws if adding more messages than fits into a block', async () => {
239
- const msgs = generateBlockMessages(l2BlockNumber, l1ToL2MessageSubtreeSize + 1);
240
-
241
- await expect(async () => {
242
- await store.addL1ToL2Messages({ lastProcessedL1BlockNumber: 100n, retrievedData: msgs });
243
- }).rejects.toThrow(`Message index ${l1ToL2MessageSubtreeSize} out of subtree range`);
244
- });
245
-
246
260
  it('correctly handles duplicate messages', async () => {
247
261
  const messageHash = Fr.random();
248
-
249
- const msgs = [new InboxLeaf(1n, 0n, messageHash), new InboxLeaf(2n, 0n, messageHash)];
262
+ const msgs = [new InboxLeaf(0n, messageHash), new InboxLeaf(16n, messageHash)];
250
263
 
251
264
  await store.addL1ToL2Messages({ lastProcessedL1BlockNumber: 100n, retrievedData: msgs });
252
265
 
253
266
  const index1 = (await store.getL1ToL2MessageIndex(messageHash, 0n))!;
267
+ expect(index1).toBe(0n);
254
268
  const index2 = await store.getL1ToL2MessageIndex(messageHash, index1 + 1n);
255
-
256
269
  expect(index2).toBeDefined();
257
270
  expect(index2).toBeGreaterThan(index1);
271
+ expect(index2).toBe(16n);
258
272
  });
259
273
  });
260
274
 
@@ -274,6 +288,11 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
274
288
  it('returns undefined if contract instance is not found', async () => {
275
289
  await expect(store.getContractInstance(AztecAddress.random())).resolves.toBeUndefined();
276
290
  });
291
+
292
+ it('returns undefined if previously stored contract instances was deleted', async () => {
293
+ await store.deleteContractInstances([contractInstance], blockNum);
294
+ await expect(store.getContractInstance(contractInstance.address)).resolves.toBeUndefined();
295
+ });
277
296
  });
278
297
 
279
298
  describe('contractClasses', () => {
@@ -289,6 +308,17 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
289
308
  await expect(store.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass);
290
309
  });
291
310
 
311
+ it('returns undefined if the initial deployed contract class was deleted', async () => {
312
+ await store.deleteContractClasses([contractClass], blockNum);
313
+ await expect(store.getContractClass(contractClass.id)).resolves.toBeUndefined();
314
+ });
315
+
316
+ it('returns contract class if later "deployment" class was deleted', async () => {
317
+ await store.addContractClasses([contractClass], blockNum + 1);
318
+ await store.deleteContractClasses([contractClass], blockNum + 1);
319
+ await expect(store.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass);
320
+ });
321
+
292
322
  it('returns undefined if contract class is not found', async () => {
293
323
  await expect(store.getContractClass(Fr.random())).resolves.toBeUndefined();
294
324
  });
@@ -338,17 +368,25 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
338
368
  }));
339
369
 
340
370
  await store.addBlocks(blocks);
371
+ await store.addLogs(blocks.map(b => b.data));
372
+ });
341
373
 
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
- );
374
+ it('no logs returned if deleted ("txHash" filter param is respected variant)', async () => {
375
+ // get random tx
376
+ const targetBlockIndex = randomInt(numBlocks);
377
+ const targetTxIndex = randomInt(txsPerBlock);
378
+ const targetTxHash = blocks[targetBlockIndex].data.body.txEffects[targetTxIndex].txHash;
379
+
380
+ await Promise.all([
381
+ store.unwindBlocks(blocks.length, blocks.length),
382
+ store.deleteLogs(blocks.map(b => b.data)),
383
+ ]);
384
+
385
+ const response = await store.getUnencryptedLogs({ txHash: targetTxHash });
386
+ const logs = response.logs;
387
+
388
+ expect(response.maxLogsHit).toBeFalsy();
389
+ expect(logs.length).toEqual(0);
352
390
  });
353
391
 
354
392
  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];
@@ -192,8 +194,8 @@ export async function retrieveL1ToL2Messages(
192
194
  }
193
195
 
194
196
  for (const log of messageSentLogs) {
195
- const { l2BlockNumber, index, hash } = log.args;
196
- retrievedL1ToL2Messages.push(new InboxLeaf(l2BlockNumber!, index!, Fr.fromString(hash!)));
197
+ const { index, hash } = log.args;
198
+ retrievedL1ToL2Messages.push(new InboxLeaf(index!, Fr.fromString(hash!)));
197
199
  }
198
200
 
199
201
  // handles the case when there are no new messages:
@@ -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
+ }