@aztec/archiver 0.55.1 → 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 (78) hide show
  1. package/README.md +1 -1
  2. package/dest/archiver/archiver.d.ts +27 -25
  3. package/dest/archiver/archiver.d.ts.map +1 -1
  4. package/dest/archiver/archiver.js +391 -169
  5. package/dest/archiver/archiver_store.d.ts +47 -23
  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 -42
  9. package/dest/archiver/config.js +6 -6
  10. package/dest/archiver/data_retrieval.d.ts +32 -5
  11. package/dest/archiver/data_retrieval.d.ts.map +1 -1
  12. package/dest/archiver/data_retrieval.js +126 -16
  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 +22 -3
  17. package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
  18. package/dest/archiver/kv_archiver_store/block_store.js +75 -12
  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 +31 -23
  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 +65 -38
  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 -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 +18 -8
  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 -39
  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 +132 -91
  40. package/dest/index.d.ts +0 -1
  41. package/dest/index.d.ts.map +1 -1
  42. package/dest/index.js +2 -2
  43. package/dest/test/index.d.ts +2 -0
  44. package/dest/test/index.d.ts.map +1 -0
  45. package/dest/test/index.js +2 -0
  46. package/dest/test/mock_l2_block_source.d.ts +73 -0
  47. package/dest/test/mock_l2_block_source.d.ts.map +1 -0
  48. package/dest/test/mock_l2_block_source.js +134 -0
  49. package/package.json +15 -11
  50. package/src/archiver/archiver.ts +531 -248
  51. package/src/archiver/archiver_store.ts +53 -31
  52. package/src/archiver/archiver_store_test_suite.ts +93 -81
  53. package/src/archiver/config.ts +5 -5
  54. package/src/archiver/data_retrieval.ts +189 -30
  55. package/src/archiver/epoch_helpers.ts +26 -0
  56. package/src/archiver/kv_archiver_store/block_store.ts +87 -12
  57. package/src/archiver/kv_archiver_store/contract_class_store.ts +18 -5
  58. package/src/archiver/kv_archiver_store/contract_instance_store.ts +4 -0
  59. package/src/archiver/kv_archiver_store/kv_archiver_store.ts +74 -47
  60. package/src/archiver/kv_archiver_store/log_store.ts +18 -18
  61. package/src/archiver/kv_archiver_store/message_store.ts +18 -5
  62. package/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts +4 -0
  63. package/src/archiver/memory_archiver_store/memory_archiver_store.ts +155 -108
  64. package/src/index.ts +1 -2
  65. package/src/test/index.ts +1 -0
  66. package/src/test/mock_l2_block_source.ts +165 -0
  67. package/dest/archiver/eth_log_handlers.d.ts +0 -59
  68. package/dest/archiver/eth_log_handlers.d.ts.map +0 -1
  69. package/dest/archiver/eth_log_handlers.js +0 -155
  70. package/dest/archiver/kv_archiver_store/block_body_store.d.ts +0 -34
  71. package/dest/archiver/kv_archiver_store/block_body_store.d.ts.map +0 -1
  72. package/dest/archiver/kv_archiver_store/block_body_store.js +0 -65
  73. package/dest/archiver/kv_archiver_store/proven_store.d.ts +0 -14
  74. package/dest/archiver/kv_archiver_store/proven_store.d.ts.map +0 -1
  75. package/dest/archiver/kv_archiver_store/proven_store.js +0 -30
  76. package/src/archiver/eth_log_handlers.ts +0 -213
  77. package/src/archiver/kv_archiver_store/block_body_store.ts +0 -74
  78. package/src/archiver/kv_archiver_store/proven_store.ts +0 -34
@@ -1,20 +1,25 @@
1
- import { type InboxLeaf, type L2Block } from '@aztec/circuit-types';
2
- import { Fr, type Proof } from '@aztec/circuits.js';
1
+ import { Body, InboxLeaf, L2Block } from '@aztec/circuit-types';
2
+ import { AppendOnlyTreeSnapshot, Fr, Header, Proof } from '@aztec/circuits.js';
3
3
  import { type EthAddress } from '@aztec/foundation/eth-address';
4
+ import { type ViemSignature } from '@aztec/foundation/eth-signature';
4
5
  import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log';
5
- import { RollupAbi } from '@aztec/l1-artifacts';
6
-
7
- import { type Hex, type PublicClient, getAbiItem } from 'viem';
6
+ import { numToUInt32BE } from '@aztec/foundation/serialize';
7
+ import { type InboxAbi, RollupAbi } from '@aztec/l1-artifacts';
8
8
 
9
9
  import {
10
- getBlockProofFromSubmitProofTx,
11
- getL2BlockProposedLogs,
12
- getMessageSentLogs,
13
- processL2BlockProposedLogs,
14
- processMessageSentLogs,
15
- } from './eth_log_handlers.js';
10
+ type Chain,
11
+ type GetContractEventsReturnType,
12
+ type GetContractReturnType,
13
+ type Hex,
14
+ type HttpTransport,
15
+ type PublicClient,
16
+ decodeFunctionData,
17
+ getAbiItem,
18
+ hexToBytes,
19
+ } from 'viem';
20
+
16
21
  import { type DataRetrieval } from './structs/data_retrieval.js';
17
- import { type L1Published } from './structs/published.js';
22
+ import { type L1Published, type L1PublishedData } from './structs/published.js';
18
23
 
19
24
  /**
20
25
  * Fetches new L2 blocks.
@@ -27,12 +32,11 @@ import { type L1Published } from './structs/published.js';
27
32
  * @returns An array of block; as well as the next eth block to search from.
28
33
  */
29
34
  export async function retrieveBlockFromRollup(
35
+ rollup: GetContractReturnType<typeof RollupAbi, PublicClient<HttpTransport, Chain>>,
30
36
  publicClient: PublicClient,
31
- rollupAddress: EthAddress,
32
37
  blockUntilSynced: boolean,
33
38
  searchStartBlock: bigint,
34
39
  searchEndBlock: bigint,
35
- expectedNextL2BlockNum: bigint,
36
40
  logger: DebugLogger = createDebugLogger('aztec:archiver'),
37
41
  ): Promise<L1Published<L2Block>[]> {
38
42
  const retrievedBlocks: L1Published<L2Block>[] = [];
@@ -40,29 +44,122 @@ export async function retrieveBlockFromRollup(
40
44
  if (searchStartBlock > searchEndBlock) {
41
45
  break;
42
46
  }
43
- const L2BlockProposedLogs = await getL2BlockProposedLogs(
44
- publicClient,
45
- rollupAddress,
46
- searchStartBlock,
47
- searchEndBlock,
47
+ const l2BlockProposedLogs = await rollup.getEvents.L2BlockProposed(
48
+ {},
49
+ {
50
+ fromBlock: searchStartBlock,
51
+ toBlock: searchEndBlock + 1n,
52
+ },
48
53
  );
49
- if (L2BlockProposedLogs.length === 0) {
54
+
55
+ if (l2BlockProposedLogs.length === 0) {
50
56
  break;
51
57
  }
52
58
 
53
- const lastLog = L2BlockProposedLogs[L2BlockProposedLogs.length - 1];
59
+ const lastLog = l2BlockProposedLogs[l2BlockProposedLogs.length - 1];
54
60
  logger.debug(
55
- `Got L2 block processed logs for ${L2BlockProposedLogs[0].blockNumber}-${lastLog.blockNumber} between ${searchStartBlock}-${searchEndBlock} L1 blocks`,
61
+ `Got L2 block processed logs for ${l2BlockProposedLogs[0].blockNumber}-${lastLog.blockNumber} between ${searchStartBlock}-${searchEndBlock} L1 blocks`,
56
62
  );
57
63
 
58
- const newBlocks = await processL2BlockProposedLogs(publicClient, expectedNextL2BlockNum, L2BlockProposedLogs);
64
+ const newBlocks = await processL2BlockProposedLogs(rollup, publicClient, l2BlockProposedLogs, logger);
59
65
  retrievedBlocks.push(...newBlocks);
60
66
  searchStartBlock = lastLog.blockNumber! + 1n;
61
- expectedNextL2BlockNum += BigInt(newBlocks.length);
62
67
  } while (blockUntilSynced && searchStartBlock <= searchEndBlock);
63
68
  return retrievedBlocks;
64
69
  }
65
70
 
71
+ /**
72
+ * Processes newly received L2BlockProposed logs.
73
+ * @param rollup - The rollup contract
74
+ * @param publicClient - The viem public client to use for transaction retrieval.
75
+ * @param logs - L2BlockProposed logs.
76
+ * @returns - An array blocks.
77
+ */
78
+ export async function processL2BlockProposedLogs(
79
+ rollup: GetContractReturnType<typeof RollupAbi, PublicClient<HttpTransport, Chain>>,
80
+ publicClient: PublicClient,
81
+ logs: GetContractEventsReturnType<typeof RollupAbi, 'L2BlockProposed'>,
82
+ logger: DebugLogger,
83
+ ): Promise<L1Published<L2Block>[]> {
84
+ const retrievedBlocks: L1Published<L2Block>[] = [];
85
+ for (const log of logs) {
86
+ const l2BlockNumber = log.args.blockNumber!;
87
+ const archive = log.args.archive!;
88
+ const archiveFromChain = await rollup.read.archiveAt([l2BlockNumber]);
89
+
90
+ // The value from the event and contract will match only if the block is in the chain.
91
+ if (archive === archiveFromChain) {
92
+ // TODO: Fetch blocks from calldata in parallel
93
+ const block = await getBlockFromRollupTx(publicClient, log.transactionHash!, l2BlockNumber);
94
+
95
+ const l1: L1PublishedData = {
96
+ blockNumber: log.blockNumber,
97
+ blockHash: log.blockHash,
98
+ timestamp: await getL1BlockTime(publicClient, log.blockNumber),
99
+ };
100
+
101
+ retrievedBlocks.push({ data: block, l1 });
102
+ } else {
103
+ logger.warn(
104
+ `Archive mismatch matching, ignoring block ${l2BlockNumber} with archive: ${archive}, expected ${archiveFromChain}`,
105
+ );
106
+ }
107
+ }
108
+
109
+ return retrievedBlocks;
110
+ }
111
+
112
+ export async function getL1BlockTime(publicClient: PublicClient, blockNumber: bigint): Promise<bigint> {
113
+ const block = await publicClient.getBlock({ blockNumber, includeTransactions: false });
114
+ return block.timestamp;
115
+ }
116
+
117
+ /**
118
+ * Gets block from the calldata of an L1 transaction.
119
+ * Assumes that the block was published from an EOA.
120
+ * TODO: Add retries and error management.
121
+ * @param publicClient - The viem public client to use for transaction retrieval.
122
+ * @param txHash - Hash of the tx that published it.
123
+ * @param l2BlockNum - L2 block number.
124
+ * @returns L2 block from the calldata, deserialized
125
+ */
126
+ async function getBlockFromRollupTx(
127
+ publicClient: PublicClient,
128
+ txHash: `0x${string}`,
129
+ l2BlockNum: bigint,
130
+ ): Promise<L2Block> {
131
+ const { input: data } = await publicClient.getTransaction({ hash: txHash });
132
+ const { functionName, args } = decodeFunctionData({
133
+ abi: RollupAbi,
134
+ data,
135
+ });
136
+
137
+ const allowedMethods = ['propose', 'proposeAndClaim'];
138
+
139
+ if (!allowedMethods.includes(functionName)) {
140
+ throw new Error(`Unexpected method called ${functionName}`);
141
+ }
142
+ const [headerHex, archiveRootHex, , , , bodyHex] = args! as readonly [Hex, Hex, Hex, Hex[], ViemSignature[], Hex];
143
+
144
+ const header = Header.fromBuffer(Buffer.from(hexToBytes(headerHex)));
145
+ const blockBody = Body.fromBuffer(Buffer.from(hexToBytes(bodyHex)));
146
+
147
+ const blockNumberFromHeader = header.globalVariables.blockNumber.toBigInt();
148
+
149
+ if (blockNumberFromHeader !== l2BlockNum) {
150
+ throw new Error(`Block number mismatch: expected ${l2BlockNum} but got ${blockNumberFromHeader}`);
151
+ }
152
+
153
+ const archive = AppendOnlyTreeSnapshot.fromBuffer(
154
+ Buffer.concat([
155
+ Buffer.from(hexToBytes(archiveRootHex)), // L2Block.archive.root
156
+ numToUInt32BE(Number(l2BlockNum + 1n)), // L2Block.archive.nextAvailableLeafIndex
157
+ ]),
158
+ );
159
+
160
+ return new L2Block(archive, header, blockBody);
161
+ }
162
+
66
163
  /**
67
164
  * Fetch L1 to L2 messages.
68
165
  * @param publicClient - The viem public client to use for transaction retrieval.
@@ -73,8 +170,7 @@ export async function retrieveBlockFromRollup(
73
170
  * @returns An array of InboxLeaf and next eth block to search from.
74
171
  */
75
172
  export async function retrieveL1ToL2Messages(
76
- publicClient: PublicClient,
77
- inboxAddress: EthAddress,
173
+ inbox: GetContractReturnType<typeof InboxAbi, PublicClient<HttpTransport, Chain>>,
78
174
  blockUntilSynced: boolean,
79
175
  searchStartBlock: bigint,
80
176
  searchEndBlock: bigint,
@@ -84,12 +180,24 @@ export async function retrieveL1ToL2Messages(
84
180
  if (searchStartBlock > searchEndBlock) {
85
181
  break;
86
182
  }
87
- const messageSentLogs = await getMessageSentLogs(publicClient, inboxAddress, searchStartBlock, searchEndBlock);
183
+
184
+ const messageSentLogs = await inbox.getEvents.MessageSent(
185
+ {},
186
+ {
187
+ fromBlock: searchStartBlock,
188
+ toBlock: searchEndBlock + 1n,
189
+ },
190
+ );
191
+
88
192
  if (messageSentLogs.length === 0) {
89
193
  break;
90
194
  }
91
- const l1ToL2Messages = processMessageSentLogs(messageSentLogs);
92
- retrievedL1ToL2Messages.push(...l1ToL2Messages);
195
+
196
+ for (const log of messageSentLogs) {
197
+ const { l2BlockNumber, index, hash } = log.args;
198
+ retrievedL1ToL2Messages.push(new InboxLeaf(l2BlockNumber!, index!, Fr.fromString(hash!)));
199
+ }
200
+
93
201
  // handles the case when there are no new messages:
94
202
  searchStartBlock = (messageSentLogs.findLast(msgLog => !!msgLog)?.blockNumber || searchStartBlock) + 1n;
95
203
  } while (blockUntilSynced && searchStartBlock <= searchEndBlock);
@@ -131,7 +239,7 @@ export async function retrieveL2ProofsFromRollup(
131
239
  const lastProcessedL1BlockNumber = logs.length > 0 ? logs.at(-1)!.l1BlockNumber : searchStartBlock - 1n;
132
240
 
133
241
  for (const { txHash, proverId, l2BlockNumber } of logs) {
134
- const proofData = await getBlockProofFromSubmitProofTx(publicClient, txHash, l2BlockNumber, proverId);
242
+ const proofData = await getProofFromSubmitProofTx(publicClient, txHash, proverId);
135
243
  retrievedData.push({ proof: proofData.proof, proverId: proofData.proverId, l2BlockNumber, txHash });
136
244
  }
137
245
  return {
@@ -139,3 +247,54 @@ export async function retrieveL2ProofsFromRollup(
139
247
  lastProcessedL1BlockNumber,
140
248
  };
141
249
  }
250
+
251
+ export type SubmitBlockProof = {
252
+ archiveRoot: Fr;
253
+ proverId: Fr;
254
+ aggregationObject: Buffer;
255
+ proof: Proof;
256
+ };
257
+
258
+ /**
259
+ * Gets block metadata (header and archive snapshot) from the calldata of an L1 transaction.
260
+ * Assumes that the block was published from an EOA.
261
+ * TODO: Add retries and error management.
262
+ * @param publicClient - The viem public client to use for transaction retrieval.
263
+ * @param txHash - Hash of the tx that published it.
264
+ * @param l2BlockNum - L2 block number.
265
+ * @returns L2 block metadata (header and archive) from the calldata, deserialized
266
+ */
267
+ export async function getProofFromSubmitProofTx(
268
+ publicClient: PublicClient,
269
+ txHash: `0x${string}`,
270
+ expectedProverId: Fr,
271
+ ): Promise<SubmitBlockProof> {
272
+ const { input: data } = await publicClient.getTransaction({ hash: txHash });
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}`);
288
+ }
289
+
290
+ if (!proverId.equals(expectedProverId)) {
291
+ throw new Error(`Prover ID mismatch: expected ${expectedProverId} but got ${proverId}`);
292
+ }
293
+
294
+ return {
295
+ proverId,
296
+ aggregationObject,
297
+ archiveRoot,
298
+ proof,
299
+ };
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
+ }
@@ -1,10 +1,9 @@
1
- import { L2Block, type TxEffect, type TxHash, TxReceipt } from '@aztec/circuit-types';
1
+ import { Body, L2Block, type TxEffect, type TxHash, TxReceipt } from '@aztec/circuit-types';
2
2
  import { AppendOnlyTreeSnapshot, type AztecAddress, Header, INITIAL_L2_BLOCK_NUM } from '@aztec/circuits.js';
3
3
  import { createDebugLogger } from '@aztec/foundation/log';
4
4
  import { type AztecKVStore, type AztecMap, type AztecSingleton, type Range } from '@aztec/kv-store';
5
5
 
6
6
  import { type L1Published, type L1PublishedData } from '../structs/published.js';
7
- import { type BlockBodyStore } from './block_body_store.js';
8
7
 
9
8
  type BlockIndexValue = [blockNumber: number, index: number];
10
9
 
@@ -20,9 +19,19 @@ type BlockStorage = {
20
19
  export class BlockStore {
21
20
  /** Map block number to block data */
22
21
  #blocks: AztecMap<number, BlockStorage>;
22
+
23
+ /** Map block body hash to block body */
24
+ #blockBodies: AztecMap<string, Buffer>;
25
+
23
26
  /** Stores L1 block number in which the last processed L2 block was included */
24
27
  #lastSynchedL1Block: AztecSingleton<bigint>;
25
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
+
26
35
  /** Index mapping transaction hash (as a string) to its location in a block */
27
36
  #txIndex: AztecMap<string, BlockIndexValue>;
28
37
 
@@ -31,15 +40,14 @@ export class BlockStore {
31
40
 
32
41
  #log = createDebugLogger('aztec:archiver:block_store');
33
42
 
34
- #blockBodyStore: BlockBodyStore;
35
-
36
- constructor(private db: AztecKVStore, blockBodyStore: BlockBodyStore) {
37
- this.#blockBodyStore = blockBodyStore;
38
-
43
+ constructor(private db: AztecKVStore) {
39
44
  this.#blocks = db.openMap('archiver_blocks');
45
+ this.#blockBodies = db.openMap('archiver_block_bodies');
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
  /**
@@ -63,6 +71,8 @@ export class BlockStore {
63
71
  block.data.body.txEffects.forEach((tx, i) => {
64
72
  void this.#txIndex.set(tx.txHash.toString(), [block.data.number, i]);
65
73
  });
74
+
75
+ void this.#blockBodies.set(block.data.body.getTxsEffectsHash().toString('hex'), block.data.body.toBuffer());
66
76
  }
67
77
 
68
78
  void this.#lastSynchedL1Block.set(blocks[blocks.length - 1].l1.blockNumber);
@@ -71,6 +81,38 @@ export class BlockStore {
71
81
  });
72
82
  }
73
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
+
74
116
  /**
75
117
  * Gets up to `limit` amount of L2 blocks starting from `from`.
76
118
  * @param start - Number of the first block to return (inclusive).
@@ -97,16 +139,29 @@ export class BlockStore {
97
139
  return this.getBlockFromBlockStorage(blockStorage);
98
140
  }
99
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
+
100
154
  private getBlockFromBlockStorage(blockStorage: BlockStorage) {
101
155
  const header = Header.fromBuffer(blockStorage.header);
102
156
  const archive = AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive);
103
- const body = this.#blockBodyStore.getBlockBody(header.contentCommitment.txsEffectsHash);
104
157
 
105
- if (body === undefined) {
106
- throw new Error('Body is not able to be retrieved from BodyStore');
158
+ const blockBodyBuffer = this.#blockBodies.get(header.contentCommitment.txsEffectsHash.toString('hex'));
159
+ if (blockBodyBuffer === undefined) {
160
+ throw new Error('Body could not be retrieved');
107
161
  }
162
+ const body = Body.fromBuffer(blockBodyBuffer);
108
163
 
109
- const l2Block = L2Block.fromFields({ header, archive, body });
164
+ const l2Block = new L2Block(archive, header, body);
110
165
  return { data: l2Block, l1: blockStorage.l1 };
111
166
  }
112
167
 
@@ -184,13 +239,33 @@ export class BlockStore {
184
239
  return this.#lastSynchedL1Block.get();
185
240
  }
186
241
 
242
+ setSynchedL1BlockNumber(l1BlockNumber: bigint) {
243
+ void this.#lastSynchedL1Block.set(l1BlockNumber);
244
+ }
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
+
187
262
  #computeBlockRange(start: number, limit: number): Required<Pick<Range<number>, 'start' | 'end'>> {
188
263
  if (limit < 1) {
189
264
  throw new Error(`Invalid limit: ${limit}`);
190
265
  }
191
266
 
192
267
  if (start < INITIAL_L2_BLOCK_NUM) {
193
- start = INITIAL_L2_BLOCK_NUM;
268
+ throw new Error(`Invalid start: ${start}`);
194
269
  }
195
270
 
196
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({
@@ -19,6 +19,10 @@ export class ContractInstanceStore {
19
19
  );
20
20
  }
21
21
 
22
+ deleteContractInstance(contractInstance: ContractInstanceWithAddress): Promise<void> {
23
+ return this.#contractInstances.delete(contractInstance.address.toString());
24
+ }
25
+
22
26
  getContractInstance(address: AztecAddress): ContractInstanceWithAddress | undefined {
23
27
  const contractInstance = this.#contractInstances.get(address.toString());
24
28
  return contractInstance && SerializableContractInstance.fromBuffer(contractInstance).withAddress(address);