@aztec/archiver 0.0.1-commit.6d3c34e → 0.0.1-commit.7035c9bd6

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 (204) hide show
  1. package/README.md +156 -22
  2. package/dest/archiver.d.ts +138 -0
  3. package/dest/archiver.d.ts.map +1 -0
  4. package/dest/archiver.js +732 -0
  5. package/dest/config.d.ts +30 -0
  6. package/dest/config.d.ts.map +1 -0
  7. package/dest/{archiver/config.js → config.js} +11 -1
  8. package/dest/errors.d.ts +53 -0
  9. package/dest/errors.d.ts.map +1 -0
  10. package/dest/errors.js +75 -0
  11. package/dest/factory.d.ts +8 -7
  12. package/dest/factory.d.ts.map +1 -1
  13. package/dest/factory.js +90 -8
  14. package/dest/index.d.ts +11 -4
  15. package/dest/index.d.ts.map +1 -1
  16. package/dest/index.js +9 -3
  17. package/dest/interfaces.d.ts +9 -0
  18. package/dest/interfaces.d.ts.map +1 -0
  19. package/dest/interfaces.js +3 -0
  20. package/dest/{archiver/l1 → l1}/bin/retrieve-calldata.d.ts +1 -1
  21. package/dest/l1/bin/retrieve-calldata.d.ts.map +1 -0
  22. package/dest/{archiver/l1 → l1}/bin/retrieve-calldata.js +35 -32
  23. package/dest/l1/calldata_retriever.d.ts +135 -0
  24. package/dest/l1/calldata_retriever.d.ts.map +1 -0
  25. package/dest/l1/calldata_retriever.js +402 -0
  26. package/dest/l1/data_retrieval.d.ts +88 -0
  27. package/dest/l1/data_retrieval.d.ts.map +1 -0
  28. package/dest/{archiver/l1 → l1}/data_retrieval.js +54 -71
  29. package/dest/{archiver/l1 → l1}/debug_tx.d.ts +1 -1
  30. package/dest/l1/debug_tx.d.ts.map +1 -0
  31. package/dest/{archiver/l1 → l1}/spire_proposer.d.ts +5 -5
  32. package/dest/l1/spire_proposer.d.ts.map +1 -0
  33. package/dest/{archiver/l1 → l1}/spire_proposer.js +9 -17
  34. package/dest/{archiver/l1 → l1}/trace_tx.d.ts +1 -1
  35. package/dest/l1/trace_tx.d.ts.map +1 -0
  36. package/dest/l1/types.d.ts +12 -0
  37. package/dest/l1/types.d.ts.map +1 -0
  38. package/dest/{archiver/l1 → l1}/validate_trace.d.ts +6 -3
  39. package/dest/l1/validate_trace.d.ts.map +1 -0
  40. package/dest/{archiver/l1 → l1}/validate_trace.js +13 -9
  41. package/dest/modules/data_source_base.d.ts +89 -0
  42. package/dest/modules/data_source_base.d.ts.map +1 -0
  43. package/dest/modules/data_source_base.js +216 -0
  44. package/dest/modules/data_store_updater.d.ts +88 -0
  45. package/dest/modules/data_store_updater.d.ts.map +1 -0
  46. package/dest/modules/data_store_updater.js +342 -0
  47. package/dest/modules/instrumentation.d.ts +50 -0
  48. package/dest/modules/instrumentation.d.ts.map +1 -0
  49. package/dest/{archiver → modules}/instrumentation.js +36 -12
  50. package/dest/modules/l1_synchronizer.d.ts +72 -0
  51. package/dest/modules/l1_synchronizer.d.ts.map +1 -0
  52. package/dest/modules/l1_synchronizer.js +1147 -0
  53. package/dest/{archiver → modules}/validation.d.ts +1 -1
  54. package/dest/modules/validation.d.ts.map +1 -0
  55. package/dest/{archiver → modules}/validation.js +6 -0
  56. package/dest/store/block_store.d.ts +195 -0
  57. package/dest/store/block_store.d.ts.map +1 -0
  58. package/dest/{archiver/kv_archiver_store → store}/block_store.js +248 -101
  59. package/dest/store/contract_class_store.d.ts +18 -0
  60. package/dest/store/contract_class_store.d.ts.map +1 -0
  61. package/dest/{archiver/kv_archiver_store → store}/contract_class_store.js +12 -8
  62. package/dest/store/contract_instance_store.d.ts +24 -0
  63. package/dest/store/contract_instance_store.d.ts.map +1 -0
  64. package/dest/{archiver/kv_archiver_store → store}/contract_instance_store.js +1 -1
  65. package/dest/store/kv_archiver_store.d.ts +367 -0
  66. package/dest/store/kv_archiver_store.d.ts.map +1 -0
  67. package/dest/store/kv_archiver_store.js +481 -0
  68. package/dest/store/l2_tips_cache.d.ts +19 -0
  69. package/dest/store/l2_tips_cache.d.ts.map +1 -0
  70. package/dest/store/l2_tips_cache.js +89 -0
  71. package/dest/store/log_store.d.ts +57 -0
  72. package/dest/store/log_store.d.ts.map +1 -0
  73. package/dest/store/log_store.js +533 -0
  74. package/dest/store/message_store.d.ts +44 -0
  75. package/dest/store/message_store.d.ts.map +1 -0
  76. package/dest/{archiver/kv_archiver_store → store}/message_store.js +14 -1
  77. package/dest/{archiver/structs → structs}/data_retrieval.d.ts +1 -1
  78. package/dest/structs/data_retrieval.d.ts.map +1 -0
  79. package/dest/structs/inbox_message.d.ts +15 -0
  80. package/dest/structs/inbox_message.d.ts.map +1 -0
  81. package/dest/{archiver/structs → structs}/published.d.ts +1 -1
  82. package/dest/structs/published.d.ts.map +1 -0
  83. package/dest/test/fake_l1_state.d.ts +202 -0
  84. package/dest/test/fake_l1_state.d.ts.map +1 -0
  85. package/dest/test/fake_l1_state.js +455 -0
  86. package/dest/test/index.d.ts +2 -1
  87. package/dest/test/index.d.ts.map +1 -1
  88. package/dest/test/index.js +4 -1
  89. package/dest/test/mock_archiver.d.ts +2 -2
  90. package/dest/test/mock_archiver.d.ts.map +1 -1
  91. package/dest/test/mock_archiver.js +3 -3
  92. package/dest/test/mock_l2_block_source.d.ts +38 -19
  93. package/dest/test/mock_l2_block_source.d.ts.map +1 -1
  94. package/dest/test/mock_l2_block_source.js +183 -77
  95. package/dest/test/mock_structs.d.ts +81 -3
  96. package/dest/test/mock_structs.d.ts.map +1 -1
  97. package/dest/test/mock_structs.js +152 -7
  98. package/dest/test/noop_l1_archiver.d.ts +26 -0
  99. package/dest/test/noop_l1_archiver.d.ts.map +1 -0
  100. package/dest/test/noop_l1_archiver.js +72 -0
  101. package/package.json +16 -17
  102. package/src/archiver.ts +486 -0
  103. package/src/{archiver/config.ts → config.ts} +19 -1
  104. package/src/{archiver/errors.ts → errors.ts} +52 -24
  105. package/src/factory.ts +141 -10
  106. package/src/index.ts +11 -3
  107. package/src/interfaces.ts +9 -0
  108. package/src/l1/README.md +55 -0
  109. package/src/{archiver/l1 → l1}/bin/retrieve-calldata.ts +45 -33
  110. package/src/l1/calldata_retriever.ts +511 -0
  111. package/src/{archiver/l1 → l1}/data_retrieval.ts +75 -94
  112. package/src/{archiver/l1 → l1}/spire_proposer.ts +7 -15
  113. package/src/{archiver/l1 → l1}/validate_trace.ts +24 -6
  114. package/src/modules/data_source_base.ts +333 -0
  115. package/src/modules/data_store_updater.ts +464 -0
  116. package/src/{archiver → modules}/instrumentation.ts +46 -14
  117. package/src/modules/l1_synchronizer.ts +967 -0
  118. package/src/{archiver → modules}/validation.ts +5 -0
  119. package/src/{archiver/kv_archiver_store → store}/block_store.ts +309 -141
  120. package/src/{archiver/kv_archiver_store → store}/contract_class_store.ts +12 -8
  121. package/src/{archiver/kv_archiver_store → store}/contract_instance_store.ts +1 -1
  122. package/src/{archiver/kv_archiver_store → store}/kv_archiver_store.ts +294 -39
  123. package/src/store/l2_tips_cache.ts +89 -0
  124. package/src/store/log_store.ts +736 -0
  125. package/src/{archiver/kv_archiver_store → store}/message_store.ts +20 -1
  126. package/src/test/fake_l1_state.ts +698 -0
  127. package/src/test/index.ts +4 -0
  128. package/src/test/mock_archiver.ts +4 -3
  129. package/src/test/mock_l2_block_source.ts +233 -93
  130. package/src/test/mock_structs.ts +283 -8
  131. package/src/test/noop_l1_archiver.ts +115 -0
  132. package/dest/archiver/archiver.d.ts +0 -307
  133. package/dest/archiver/archiver.d.ts.map +0 -1
  134. package/dest/archiver/archiver.js +0 -2102
  135. package/dest/archiver/archiver_store.d.ts +0 -315
  136. package/dest/archiver/archiver_store.d.ts.map +0 -1
  137. package/dest/archiver/archiver_store.js +0 -4
  138. package/dest/archiver/archiver_store_test_suite.d.ts +0 -8
  139. package/dest/archiver/archiver_store_test_suite.d.ts.map +0 -1
  140. package/dest/archiver/archiver_store_test_suite.js +0 -2770
  141. package/dest/archiver/config.d.ts +0 -22
  142. package/dest/archiver/config.d.ts.map +0 -1
  143. package/dest/archiver/errors.d.ts +0 -36
  144. package/dest/archiver/errors.d.ts.map +0 -1
  145. package/dest/archiver/errors.js +0 -54
  146. package/dest/archiver/index.d.ts +0 -7
  147. package/dest/archiver/index.d.ts.map +0 -1
  148. package/dest/archiver/index.js +0 -4
  149. package/dest/archiver/instrumentation.d.ts +0 -37
  150. package/dest/archiver/instrumentation.d.ts.map +0 -1
  151. package/dest/archiver/kv_archiver_store/block_store.d.ts +0 -164
  152. package/dest/archiver/kv_archiver_store/block_store.d.ts.map +0 -1
  153. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +0 -18
  154. package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +0 -1
  155. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +0 -24
  156. package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +0 -1
  157. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +0 -159
  158. package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +0 -1
  159. package/dest/archiver/kv_archiver_store/kv_archiver_store.js +0 -316
  160. package/dest/archiver/kv_archiver_store/log_store.d.ts +0 -45
  161. package/dest/archiver/kv_archiver_store/log_store.d.ts.map +0 -1
  162. package/dest/archiver/kv_archiver_store/log_store.js +0 -401
  163. package/dest/archiver/kv_archiver_store/message_store.d.ts +0 -40
  164. package/dest/archiver/kv_archiver_store/message_store.d.ts.map +0 -1
  165. package/dest/archiver/l1/bin/retrieve-calldata.d.ts.map +0 -1
  166. package/dest/archiver/l1/calldata_retriever.d.ts +0 -112
  167. package/dest/archiver/l1/calldata_retriever.d.ts.map +0 -1
  168. package/dest/archiver/l1/calldata_retriever.js +0 -471
  169. package/dest/archiver/l1/data_retrieval.d.ts +0 -90
  170. package/dest/archiver/l1/data_retrieval.d.ts.map +0 -1
  171. package/dest/archiver/l1/debug_tx.d.ts.map +0 -1
  172. package/dest/archiver/l1/spire_proposer.d.ts.map +0 -1
  173. package/dest/archiver/l1/trace_tx.d.ts.map +0 -1
  174. package/dest/archiver/l1/types.d.ts +0 -12
  175. package/dest/archiver/l1/types.d.ts.map +0 -1
  176. package/dest/archiver/l1/validate_trace.d.ts.map +0 -1
  177. package/dest/archiver/structs/data_retrieval.d.ts.map +0 -1
  178. package/dest/archiver/structs/inbox_message.d.ts +0 -15
  179. package/dest/archiver/structs/inbox_message.d.ts.map +0 -1
  180. package/dest/archiver/structs/published.d.ts.map +0 -1
  181. package/dest/archiver/validation.d.ts.map +0 -1
  182. package/dest/rpc/index.d.ts +0 -9
  183. package/dest/rpc/index.d.ts.map +0 -1
  184. package/dest/rpc/index.js +0 -15
  185. package/src/archiver/archiver.ts +0 -2265
  186. package/src/archiver/archiver_store.ts +0 -380
  187. package/src/archiver/archiver_store_test_suite.ts +0 -2842
  188. package/src/archiver/index.ts +0 -6
  189. package/src/archiver/kv_archiver_store/log_store.ts +0 -516
  190. package/src/archiver/l1/README.md +0 -98
  191. package/src/archiver/l1/calldata_retriever.ts +0 -641
  192. package/src/rpc/index.ts +0 -16
  193. /package/dest/{archiver/l1 → l1}/debug_tx.js +0 -0
  194. /package/dest/{archiver/l1 → l1}/trace_tx.js +0 -0
  195. /package/dest/{archiver/l1 → l1}/types.js +0 -0
  196. /package/dest/{archiver/structs → structs}/data_retrieval.js +0 -0
  197. /package/dest/{archiver/structs → structs}/inbox_message.js +0 -0
  198. /package/dest/{archiver/structs → structs}/published.js +0 -0
  199. /package/src/{archiver/l1 → l1}/debug_tx.ts +0 -0
  200. /package/src/{archiver/l1 → l1}/trace_tx.ts +0 -0
  201. /package/src/{archiver/l1 → l1}/types.ts +0 -0
  202. /package/src/{archiver/structs → structs}/data_retrieval.ts +0 -0
  203. /package/src/{archiver/structs → structs}/inbox_message.ts +0 -0
  204. /package/src/{archiver/structs → structs}/published.ts +0 -0
@@ -1,5 +1,5 @@
1
1
  import { INITIAL_CHECKPOINT_NUMBER, INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
2
- import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
2
+ import { BlockNumber, CheckpointNumber, IndexWithinCheckpoint, SlotNumber } from '@aztec/foundation/branded-types';
3
3
  import { Fr } from '@aztec/foundation/curves/bn254';
4
4
  import { toArray } from '@aztec/foundation/iterable';
5
5
  import { createLogger } from '@aztec/foundation/log';
@@ -9,16 +9,18 @@ import { isDefined } from '@aztec/foundation/types';
9
9
  import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncSingleton, Range } from '@aztec/kv-store';
10
10
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
11
11
  import {
12
+ type BlockData,
13
+ BlockHash,
12
14
  Body,
13
15
  CheckpointedL2Block,
14
16
  CommitteeAttestation,
15
- L2BlockHash,
16
- L2BlockNew,
17
+ L2Block,
17
18
  type ValidateCheckpointResult,
18
19
  deserializeValidateCheckpointResult,
19
20
  serializeValidateCheckpointResult,
20
21
  } from '@aztec/stdlib/block';
21
- import { L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
22
+ import { type CheckpointData, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
23
+ import { type L1RollupConstants, getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
22
24
  import { CheckpointHeader } from '@aztec/stdlib/rollup';
23
25
  import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
24
26
  import {
@@ -27,19 +29,20 @@ import {
27
29
  TxEffect,
28
30
  TxHash,
29
31
  TxReceipt,
32
+ TxStatus,
30
33
  deserializeIndexedTxEffect,
31
34
  serializeIndexedTxEffect,
32
35
  } from '@aztec/stdlib/tx';
33
36
 
34
37
  import {
38
+ BlockAlreadyCheckpointedError,
35
39
  BlockArchiveNotConsistentError,
36
40
  BlockIndexNotSequentialError,
37
41
  BlockNotFoundError,
38
42
  BlockNumberNotSequentialError,
43
+ CannotOverwriteCheckpointedBlockError,
39
44
  CheckpointNotFoundError,
40
- CheckpointNumberNotConsistentError,
41
45
  CheckpointNumberNotSequentialError,
42
- InitialBlockNumberNotSequentialError,
43
46
  InitialCheckpointNumberNotSequentialError,
44
47
  } from '../errors.js';
45
48
 
@@ -58,25 +61,18 @@ type BlockStorage = {
58
61
  type CheckpointStorage = {
59
62
  header: Buffer;
60
63
  archive: Buffer;
64
+ checkpointOutHash: Buffer;
61
65
  checkpointNumber: number;
62
66
  startBlock: number;
63
- numBlocks: number;
67
+ blockCount: number;
64
68
  l1: Buffer;
65
69
  attestations: Buffer[];
66
70
  };
67
71
 
68
- export type CheckpointData = {
69
- checkpointNumber: CheckpointNumber;
70
- header: CheckpointHeader;
71
- archive: AppendOnlyTreeSnapshot;
72
- startBlock: number;
73
- numBlocks: number;
74
- l1: L1PublishedData;
75
- attestations: Buffer[];
76
- };
72
+ export type RemoveCheckpointsResult = { blocksRemoved: L2Block[] | undefined };
77
73
 
78
74
  /**
79
- * LMDB implementation of the ArchiverDataStore interface.
75
+ * LMDB-based block storage for the archiver.
80
76
  */
81
77
  export class BlockStore {
82
78
  /** Map block number to block data */
@@ -85,6 +81,9 @@ export class BlockStore {
85
81
  /** Map checkpoint number to checkpoint data */
86
82
  #checkpoints: AztecAsyncMap<number, CheckpointStorage>;
87
83
 
84
+ /** Map slot number to checkpoint number, for looking up checkpoints by slot range. */
85
+ #slotToCheckpoint: AztecAsyncMap<number, number>;
86
+
88
87
  /** Map block hash to list of tx hashes */
89
88
  #blockTxs: AztecAsyncMap<string, Buffer>;
90
89
 
@@ -97,6 +96,9 @@ export class BlockStore {
97
96
  /** Stores last proven checkpoint */
98
97
  #lastProvenCheckpoint: AztecAsyncSingleton<number>;
99
98
 
99
+ /** Stores last finalized checkpoint (proven at or before the finalized L1 block) */
100
+ #lastFinalizedCheckpoint: AztecAsyncSingleton<number>;
101
+
100
102
  /** Stores the pending chain validation status */
101
103
  #pendingChainValidationStatus: AztecAsyncSingleton<Buffer>;
102
104
 
@@ -120,92 +122,95 @@ export class BlockStore {
120
122
  this.#blockArchiveIndex = db.openMap('archiver_block_archive_index');
121
123
  this.#lastSynchedL1Block = db.openSingleton('archiver_last_synched_l1_block');
122
124
  this.#lastProvenCheckpoint = db.openSingleton('archiver_last_proven_l2_checkpoint');
125
+ this.#lastFinalizedCheckpoint = db.openSingleton('archiver_last_finalized_l2_checkpoint');
123
126
  this.#pendingChainValidationStatus = db.openSingleton('archiver_pending_chain_validation_status');
124
127
  this.#checkpoints = db.openMap('archiver_checkpoints');
128
+ this.#slotToCheckpoint = db.openMap('archiver_slot_to_checkpoint');
125
129
  }
126
130
 
127
131
  /**
128
- * Append new blocks to the store's list. All blocks must be for the 'current' checkpoint
129
- * @param blocks - The L2 blocks to be added to the store.
130
- * @returns True if the operation is successful.
132
+ * Returns the finalized L2 block number. An L2 block is finalized when it was proven
133
+ * in an L1 block that has itself been finalized on Ethereum.
134
+ * @returns The finalized block number.
131
135
  */
132
- async addBlocks(blocks: L2BlockNew[], opts: { force?: boolean } = {}): Promise<boolean> {
133
- if (blocks.length === 0) {
134
- return true;
136
+ async getFinalizedL2BlockNumber(): Promise<BlockNumber> {
137
+ const finalizedCheckpointNumber = await this.getFinalizedCheckpointNumber();
138
+ if (finalizedCheckpointNumber === INITIAL_CHECKPOINT_NUMBER - 1) {
139
+ return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
140
+ }
141
+ const checkpointStorage = await this.#checkpoints.getAsync(finalizedCheckpointNumber);
142
+ if (!checkpointStorage) {
143
+ throw new CheckpointNotFoundError(finalizedCheckpointNumber);
135
144
  }
145
+ return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
146
+ }
136
147
 
148
+ /**
149
+ * Append a new proposed block to the store.
150
+ * This is an uncheckpointed block that has been proposed by the sequencer but not yet included in a checkpoint on L1.
151
+ * For checkpointed blocks (already published to L1), use addCheckpoints() instead.
152
+ * @param block - The proposed L2 block to be added to the store.
153
+ * @returns True if the operation is successful.
154
+ */
155
+ async addProposedBlock(block: L2Block, opts: { force?: boolean } = {}): Promise<boolean> {
137
156
  return await this.db.transactionAsync(async () => {
138
- // Check that the block immediately before the first block to be added is present in the store.
139
- const firstBlockNumber = blocks[0].number;
140
- const firstBlockCheckpointNumber = blocks[0].checkpointNumber;
141
- const firstBlockIndex = blocks[0].indexWithinCheckpoint;
142
- const firstBlockLastArchive = blocks[0].header.lastArchive.root;
157
+ const blockNumber = block.number;
158
+ const blockCheckpointNumber = block.checkpointNumber;
159
+ const blockIndex = block.indexWithinCheckpoint;
160
+ const blockLastArchive = block.header.lastArchive.root;
143
161
 
144
162
  // Extract the latest block and checkpoint numbers
145
163
  const previousBlockNumber = await this.getLatestBlockNumber();
146
164
  const previousCheckpointNumber = await this.getLatestCheckpointNumber();
147
165
 
148
- // Check that the first block number is the expected one
149
- if (!opts.force && previousBlockNumber !== firstBlockNumber - 1) {
150
- throw new InitialBlockNumberNotSequentialError(firstBlockNumber, previousBlockNumber);
166
+ // Verify we're not overwriting checkpointed blocks
167
+ const lastCheckpointedBlockNumber = await this.getCheckpointedL2BlockNumber();
168
+ if (!opts.force && blockNumber <= lastCheckpointedBlockNumber) {
169
+ // Check if the proposed block matches the already-checkpointed one
170
+ const existingBlock = await this.getBlock(BlockNumber(blockNumber));
171
+ if (existingBlock && existingBlock.archive.root.equals(block.archive.root)) {
172
+ throw new BlockAlreadyCheckpointedError(blockNumber);
173
+ }
174
+ throw new CannotOverwriteCheckpointedBlockError(blockNumber, lastCheckpointedBlockNumber);
175
+ }
176
+
177
+ // Check that the block number is the expected one
178
+ if (!opts.force && previousBlockNumber !== blockNumber - 1) {
179
+ throw new BlockNumberNotSequentialError(blockNumber, previousBlockNumber);
151
180
  }
152
181
 
153
182
  // The same check as above but for checkpoints
154
- if (!opts.force && previousCheckpointNumber !== firstBlockCheckpointNumber - 1) {
155
- throw new InitialCheckpointNumberNotSequentialError(firstBlockCheckpointNumber, previousCheckpointNumber);
183
+ if (!opts.force && previousCheckpointNumber !== blockCheckpointNumber - 1) {
184
+ throw new CheckpointNumberNotSequentialError(blockCheckpointNumber, previousCheckpointNumber);
156
185
  }
157
186
 
158
187
  // Extract the previous block if there is one and see if it is for the same checkpoint or not
159
188
  const previousBlockResult = await this.getBlock(previousBlockNumber);
160
189
 
161
- let expectedFirstblockIndex = 0;
190
+ let expectedBlockIndex = 0;
162
191
  let previousBlockIndex: number | undefined = undefined;
163
192
  if (previousBlockResult !== undefined) {
164
- if (previousBlockResult.checkpointNumber === firstBlockCheckpointNumber) {
193
+ if (previousBlockResult.checkpointNumber === blockCheckpointNumber) {
165
194
  // The previous block is for the same checkpoint, therefore our index should follow it
166
195
  previousBlockIndex = previousBlockResult.indexWithinCheckpoint;
167
- expectedFirstblockIndex = previousBlockIndex + 1;
196
+ expectedBlockIndex = previousBlockIndex + 1;
168
197
  }
169
- if (!previousBlockResult.archive.root.equals(firstBlockLastArchive)) {
198
+ if (!previousBlockResult.archive.root.equals(blockLastArchive)) {
170
199
  throw new BlockArchiveNotConsistentError(
171
- firstBlockNumber,
200
+ blockNumber,
172
201
  previousBlockResult.number,
173
- firstBlockLastArchive,
202
+ blockLastArchive,
174
203
  previousBlockResult.archive.root,
175
204
  );
176
205
  }
177
206
  }
178
207
 
179
- // Now check that the first block has the expected index value
180
- if (!opts.force && expectedFirstblockIndex !== firstBlockIndex) {
181
- throw new BlockIndexNotSequentialError(firstBlockIndex, previousBlockIndex);
208
+ // Now check that the block has the expected index value
209
+ if (!opts.force && expectedBlockIndex !== blockIndex) {
210
+ throw new BlockIndexNotSequentialError(blockIndex, previousBlockIndex);
182
211
  }
183
212
 
184
- // Iterate over blocks array and insert them, checking that the block numbers and indexes are sequential. Also check they are for the correct checkpoint.
185
- let previousBlock: L2BlockNew | undefined = undefined;
186
- for (const block of blocks) {
187
- if (!opts.force && previousBlock) {
188
- if (previousBlock.number + 1 !== block.number) {
189
- throw new BlockNumberNotSequentialError(block.number, previousBlock.number);
190
- }
191
- if (previousBlock.indexWithinCheckpoint + 1 !== block.indexWithinCheckpoint) {
192
- throw new BlockIndexNotSequentialError(block.indexWithinCheckpoint, previousBlock.indexWithinCheckpoint);
193
- }
194
- if (!previousBlock.archive.root.equals(block.header.lastArchive.root)) {
195
- throw new BlockArchiveNotConsistentError(
196
- block.number,
197
- previousBlock.number,
198
- block.header.lastArchive.root,
199
- previousBlock.archive.root,
200
- );
201
- }
202
- }
203
- if (!opts.force && firstBlockCheckpointNumber !== block.checkpointNumber) {
204
- throw new CheckpointNumberNotConsistentError(block.checkpointNumber, firstBlockCheckpointNumber);
205
- }
206
- previousBlock = block;
207
- await this.addBlockToDatabase(block, block.checkpointNumber, block.indexWithinCheckpoint);
208
- }
213
+ await this.addBlockToDatabase(block, block.checkpointNumber, block.indexWithinCheckpoint);
209
214
 
210
215
  return true;
211
216
  });
@@ -241,11 +246,11 @@ export class BlockStore {
241
246
  }
242
247
 
243
248
  let previousBlockNumber: BlockNumber | undefined = undefined;
244
- let previousBlock: L2BlockNew | undefined = undefined;
249
+ let previousBlock: L2Block | undefined = undefined;
245
250
 
246
251
  // If we have a previous checkpoint then we need to get the previous block number
247
252
  if (previousCheckpointData !== undefined) {
248
- previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.numBlocks - 1);
253
+ previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.blockCount - 1);
249
254
  previousBlock = await this.getBlock(previousBlockNumber);
250
255
  if (previousBlock === undefined) {
251
256
  // We should be able to get the required previous block
@@ -309,12 +314,16 @@ export class BlockStore {
309
314
  await this.#checkpoints.set(checkpoint.checkpoint.number, {
310
315
  header: checkpoint.checkpoint.header.toBuffer(),
311
316
  archive: checkpoint.checkpoint.archive.toBuffer(),
317
+ checkpointOutHash: checkpoint.checkpoint.getCheckpointOutHash().toBuffer(),
312
318
  l1: checkpoint.l1.toBuffer(),
313
319
  attestations: checkpoint.attestations.map(attestation => attestation.toBuffer()),
314
320
  checkpointNumber: checkpoint.checkpoint.number,
315
321
  startBlock: checkpoint.checkpoint.blocks[0].number,
316
- numBlocks: checkpoint.checkpoint.blocks.length,
322
+ blockCount: checkpoint.checkpoint.blocks.length,
317
323
  });
324
+
325
+ // Update slot-to-checkpoint index
326
+ await this.#slotToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, checkpoint.checkpoint.number);
318
327
  }
319
328
 
320
329
  await this.#lastSynchedL1Block.set(checkpoints[checkpoints.length - 1].l1.blockNumber);
@@ -322,8 +331,8 @@ export class BlockStore {
322
331
  });
323
332
  }
324
333
 
325
- private async addBlockToDatabase(block: L2BlockNew, checkpointNumber: number, indexWithinCheckpoint: number) {
326
- const blockHash = L2BlockHash.fromField(await block.hash());
334
+ private async addBlockToDatabase(block: L2Block, checkpointNumber: number, indexWithinCheckpoint: number) {
335
+ const blockHash = await block.hash();
327
336
 
328
337
  await this.#blocks.set(block.number, {
329
338
  header: block.header.toBuffer(),
@@ -350,57 +359,71 @@ export class BlockStore {
350
359
  await this.#blockArchiveIndex.set(block.archive.root.toString(), block.number);
351
360
  }
352
361
 
362
+ /** Deletes a block and all associated data (tx effects, indices). */
363
+ private async deleteBlock(block: L2Block): Promise<void> {
364
+ // Delete the block from the main blocks map
365
+ await this.#blocks.delete(block.number);
366
+
367
+ // Delete all tx effects for this block
368
+ await Promise.all(block.body.txEffects.map(tx => this.#txEffects.delete(tx.txHash.toString())));
369
+
370
+ // Delete block txs mapping
371
+ const blockHash = (await block.hash()).toString();
372
+ await this.#blockTxs.delete(blockHash);
373
+
374
+ // Clean up indices
375
+ await this.#blockHashIndex.delete(blockHash);
376
+ await this.#blockArchiveIndex.delete(block.archive.root.toString());
377
+ }
378
+
353
379
  /**
354
- * Unwinds checkpoints from the database
355
- * @param from - The tip of the chain, passed for verification purposes,
356
- * ensuring that we don't end up deleting something we did not intend
357
- * @param checkpointsToUnwind - The number of checkpoints we are to unwind
358
- * @returns True if the operation is successful
380
+ * Removes all checkpoints with checkpoint number > checkpointNumber.
381
+ * Also removes ALL blocks (both checkpointed and uncheckpointed) after the last block of the given checkpoint.
382
+ * @param checkpointNumber - Remove all checkpoints strictly after this one.
359
383
  */
360
- async unwindCheckpoints(from: CheckpointNumber, checkpointsToUnwind: number) {
384
+ async removeCheckpointsAfter(checkpointNumber: CheckpointNumber): Promise<RemoveCheckpointsResult> {
361
385
  return await this.db.transactionAsync(async () => {
362
- const last = await this.getLatestCheckpointNumber();
363
- if (from !== last) {
364
- throw new Error(`Can only unwind checkpoints from the tip (requested ${from} but current tip is ${last})`);
386
+ const latestCheckpointNumber = await this.getLatestCheckpointNumber();
387
+
388
+ if (checkpointNumber >= latestCheckpointNumber) {
389
+ this.#log.warn(`No checkpoints to remove after ${checkpointNumber} (latest is ${latestCheckpointNumber})`);
390
+ return { blocksRemoved: undefined };
365
391
  }
366
392
 
393
+ // If the proven checkpoint is beyond the target, update it
367
394
  const proven = await this.getProvenCheckpointNumber();
368
- if (from - checkpointsToUnwind < proven) {
369
- await this.setProvenCheckpointNumber(CheckpointNumber(from - checkpointsToUnwind));
395
+ if (proven > checkpointNumber) {
396
+ this.#log.warn(`Updating proven checkpoint ${proven} to last valid checkpoint ${checkpointNumber}`);
397
+ await this.setProvenCheckpointNumber(checkpointNumber);
370
398
  }
371
399
 
372
- for (let i = 0; i < checkpointsToUnwind; i++) {
373
- const checkpointNumber = from - i;
374
- const checkpoint = await this.#checkpoints.getAsync(checkpointNumber);
375
-
376
- if (checkpoint === undefined) {
377
- this.#log.warn(`Cannot remove checkpoint ${checkpointNumber} from the store since we don't have it`);
378
- continue;
400
+ // Find the last block number to keep (last block of the given checkpoint, or 0 if no checkpoint)
401
+ let lastBlockToKeep: BlockNumber;
402
+ if (checkpointNumber <= 0) {
403
+ lastBlockToKeep = BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
404
+ } else {
405
+ const targetCheckpoint = await this.#checkpoints.getAsync(checkpointNumber);
406
+ if (!targetCheckpoint) {
407
+ throw new Error(`Target checkpoint ${checkpointNumber} not found in store`);
379
408
  }
380
- await this.#checkpoints.delete(checkpointNumber);
381
- const maxBlock = checkpoint.startBlock + checkpoint.numBlocks - 1;
382
-
383
- for (let blockNumber = checkpoint.startBlock; blockNumber <= maxBlock; blockNumber++) {
384
- const block = await this.getBlock(BlockNumber(blockNumber));
385
-
386
- if (block === undefined) {
387
- this.#log.warn(`Cannot remove block ${blockNumber} from the store since we don't have it`);
388
- continue;
389
- }
390
- await this.#blocks.delete(block.number);
391
- await Promise.all(block.body.txEffects.map(tx => this.#txEffects.delete(tx.txHash.toString())));
392
- const blockHash = (await block.hash()).toString();
393
- await this.#blockTxs.delete(blockHash);
409
+ lastBlockToKeep = BlockNumber(targetCheckpoint.startBlock + targetCheckpoint.blockCount - 1);
410
+ }
394
411
 
395
- // Clean up indices
396
- await this.#blockHashIndex.delete(blockHash);
397
- await this.#blockArchiveIndex.delete(block.archive.root.toString());
412
+ // Remove all blocks after lastBlockToKeep (both checkpointed and uncheckpointed)
413
+ const blocksRemoved = await this.removeBlocksAfter(lastBlockToKeep);
398
414
 
399
- this.#log.debug(`Unwound block ${blockNumber} ${blockHash} for checkpoint ${checkpointNumber}`);
415
+ // Remove all checkpoints after the target
416
+ for (let c = latestCheckpointNumber; c > checkpointNumber; c = CheckpointNumber(c - 1)) {
417
+ const checkpointStorage = await this.#checkpoints.getAsync(c);
418
+ if (checkpointStorage) {
419
+ const slotNumber = CheckpointHeader.fromBuffer(checkpointStorage.header).slotNumber;
420
+ await this.#slotToCheckpoint.delete(slotNumber);
400
421
  }
422
+ await this.#checkpoints.delete(c);
423
+ this.#log.debug(`Removed checkpoint ${c}`);
401
424
  }
402
425
 
403
- return true;
426
+ return { blocksRemoved };
404
427
  });
405
428
  }
406
429
 
@@ -424,20 +447,35 @@ export class BlockStore {
424
447
  return checkpoints;
425
448
  }
426
449
 
427
- private checkpointDataFromCheckpointStorage(checkpointStorage: CheckpointStorage) {
428
- const data: CheckpointData = {
450
+ /** Returns checkpoint data for all checkpoints whose slot falls within the given range (inclusive). */
451
+ async getCheckpointDataForSlotRange(startSlot: SlotNumber, endSlot: SlotNumber): Promise<CheckpointData[]> {
452
+ const result: CheckpointData[] = [];
453
+ for await (const [, checkpointNumber] of this.#slotToCheckpoint.entriesAsync({
454
+ start: startSlot,
455
+ end: endSlot + 1,
456
+ })) {
457
+ const checkpointStorage = await this.#checkpoints.getAsync(checkpointNumber);
458
+ if (checkpointStorage) {
459
+ result.push(this.checkpointDataFromCheckpointStorage(checkpointStorage));
460
+ }
461
+ }
462
+ return result;
463
+ }
464
+
465
+ private checkpointDataFromCheckpointStorage(checkpointStorage: CheckpointStorage): CheckpointData {
466
+ return {
429
467
  header: CheckpointHeader.fromBuffer(checkpointStorage.header),
430
468
  archive: AppendOnlyTreeSnapshot.fromBuffer(checkpointStorage.archive),
469
+ checkpointOutHash: Fr.fromBuffer(checkpointStorage.checkpointOutHash),
431
470
  checkpointNumber: CheckpointNumber(checkpointStorage.checkpointNumber),
432
- startBlock: checkpointStorage.startBlock,
433
- numBlocks: checkpointStorage.numBlocks,
471
+ startBlock: BlockNumber(checkpointStorage.startBlock),
472
+ blockCount: checkpointStorage.blockCount,
434
473
  l1: L1PublishedData.fromBuffer(checkpointStorage.l1),
435
- attestations: checkpointStorage.attestations,
474
+ attestations: checkpointStorage.attestations.map(buf => CommitteeAttestation.fromBuffer(buf)),
436
475
  };
437
- return data;
438
476
  }
439
477
 
440
- async getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2BlockNew[] | undefined> {
478
+ async getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2Block[] | undefined> {
441
479
  const checkpoint = await this.#checkpoints.getAsync(checkpointNumber);
442
480
  if (!checkpoint) {
443
481
  return undefined;
@@ -446,7 +484,7 @@ export class BlockStore {
446
484
  const blocksForCheckpoint = await toArray(
447
485
  this.#blocks.entriesAsync({
448
486
  start: checkpoint.startBlock,
449
- end: checkpoint.startBlock + checkpoint.numBlocks,
487
+ end: checkpoint.startBlock + checkpoint.blockCount,
450
488
  }),
451
489
  );
452
490
 
@@ -454,6 +492,62 @@ export class BlockStore {
454
492
  return converted.filter(isDefined);
455
493
  }
456
494
 
495
+ /**
496
+ * Gets all blocks that have the given slot number.
497
+ * Iterates backwards through blocks for efficiency since we usually query for the last slot.
498
+ * @param slotNumber - The slot number to search for.
499
+ * @returns All blocks with the given slot number, in ascending block number order.
500
+ */
501
+ async getBlocksForSlot(slotNumber: SlotNumber): Promise<L2Block[]> {
502
+ const blocks: L2Block[] = [];
503
+
504
+ // Iterate backwards through all blocks and filter by slot number
505
+ // This is more efficient since we usually query for the most recent slot
506
+ for await (const [blockNumber, blockStorage] of this.#blocks.entriesAsync({ reverse: true })) {
507
+ const block = await this.getBlockFromBlockStorage(blockNumber, blockStorage);
508
+ const blockSlot = block?.header.globalVariables.slotNumber;
509
+ if (block && blockSlot === slotNumber) {
510
+ blocks.push(block);
511
+ } else if (blockSlot && blockSlot < slotNumber) {
512
+ break; // Blocks are stored in slot ascending order, so we can stop searching
513
+ }
514
+ }
515
+
516
+ // Reverse to return blocks in ascending order (block number order)
517
+ return blocks.reverse();
518
+ }
519
+
520
+ /**
521
+ * Removes all blocks with block number > blockNumber.
522
+ * Does not remove any associated checkpoints.
523
+ * @param blockNumber - The block number to remove after.
524
+ * @returns The removed blocks (for event emission).
525
+ */
526
+ async removeBlocksAfter(blockNumber: BlockNumber): Promise<L2Block[]> {
527
+ return await this.db.transactionAsync(async () => {
528
+ const removedBlocks: L2Block[] = [];
529
+
530
+ // Get the latest block number to determine the range
531
+ const latestBlockNumber = await this.getLatestBlockNumber();
532
+
533
+ // Iterate from blockNumber + 1 to latestBlockNumber
534
+ for (let bn = blockNumber + 1; bn <= latestBlockNumber; bn++) {
535
+ const block = await this.getBlock(BlockNumber(bn));
536
+
537
+ if (block === undefined) {
538
+ this.#log.warn(`Cannot remove block ${bn} from the store since we don't have it`);
539
+ continue;
540
+ }
541
+
542
+ removedBlocks.push(block);
543
+ await this.deleteBlock(block);
544
+ this.#log.debug(`Removed block ${bn} ${(await block.hash()).toString()}`);
545
+ }
546
+
547
+ return removedBlocks;
548
+ });
549
+ }
550
+
457
551
  async getProvenBlockNumber(): Promise<BlockNumber> {
458
552
  const provenCheckpointNumber = await this.getProvenCheckpointNumber();
459
553
  if (provenCheckpointNumber === INITIAL_CHECKPOINT_NUMBER - 1) {
@@ -463,7 +557,7 @@ export class BlockStore {
463
557
  if (!checkpointStorage) {
464
558
  throw new CheckpointNotFoundError(provenCheckpointNumber);
465
559
  } else {
466
- return BlockNumber(checkpointStorage.startBlock + checkpointStorage.numBlocks - 1);
560
+ return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
467
561
  }
468
562
  }
469
563
 
@@ -531,13 +625,14 @@ export class BlockStore {
531
625
  }
532
626
  }
533
627
 
534
- async getCheckpointedBlockByHash(blockHash: Fr): Promise<CheckpointedL2Block | undefined> {
628
+ async getCheckpointedBlockByHash(blockHash: BlockHash): Promise<CheckpointedL2Block | undefined> {
535
629
  const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
536
630
  if (blockNumber === undefined) {
537
631
  return undefined;
538
632
  }
539
633
  return this.getCheckpointedBlock(BlockNumber(blockNumber));
540
634
  }
635
+
541
636
  async getCheckpointedBlockByArchive(archive: Fr): Promise<CheckpointedL2Block | undefined> {
542
637
  const blockNumber = await this.#blockArchiveIndex.getAsync(archive.toString());
543
638
  if (blockNumber === undefined) {
@@ -552,7 +647,7 @@ export class BlockStore {
552
647
  * @param limit - The number of blocks to return.
553
648
  * @returns The requested L2 blocks
554
649
  */
555
- async *getBlocks(start: BlockNumber, limit: number): AsyncIterableIterator<L2BlockNew> {
650
+ async *getBlocks(start: BlockNumber, limit: number): AsyncIterableIterator<L2Block> {
556
651
  for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) {
557
652
  const block = await this.getBlockFromBlockStorage(blockNumber, blockStorage);
558
653
  if (block) {
@@ -561,12 +656,38 @@ export class BlockStore {
561
656
  }
562
657
  }
563
658
 
659
+ /**
660
+ * Gets block metadata (without tx data) by block number.
661
+ * @param blockNumber - The number of the block to return.
662
+ * @returns The requested block data.
663
+ */
664
+ async getBlockData(blockNumber: BlockNumber): Promise<BlockData | undefined> {
665
+ const blockStorage = await this.#blocks.getAsync(blockNumber);
666
+ if (!blockStorage || !blockStorage.header) {
667
+ return undefined;
668
+ }
669
+ return this.getBlockDataFromBlockStorage(blockStorage);
670
+ }
671
+
672
+ /**
673
+ * Gets block metadata (without tx data) by archive root.
674
+ * @param archive - The archive root of the block to return.
675
+ * @returns The requested block data.
676
+ */
677
+ async getBlockDataByArchive(archive: Fr): Promise<BlockData | undefined> {
678
+ const blockNumber = await this.#blockArchiveIndex.getAsync(archive.toString());
679
+ if (blockNumber === undefined) {
680
+ return undefined;
681
+ }
682
+ return this.getBlockData(BlockNumber(blockNumber));
683
+ }
684
+
564
685
  /**
565
686
  * Gets an L2 block.
566
687
  * @param blockNumber - The number of the block to return.
567
688
  * @returns The requested L2 block.
568
689
  */
569
- async getBlock(blockNumber: BlockNumber): Promise<L2BlockNew | undefined> {
690
+ async getBlock(blockNumber: BlockNumber): Promise<L2Block | undefined> {
570
691
  const blockStorage = await this.#blocks.getAsync(blockNumber);
571
692
  if (!blockStorage || !blockStorage.header) {
572
693
  return Promise.resolve(undefined);
@@ -579,7 +700,7 @@ export class BlockStore {
579
700
  * @param blockHash - The hash of the block to return.
580
701
  * @returns The requested L2 block.
581
702
  */
582
- async getBlockByHash(blockHash: L2BlockHash): Promise<L2BlockNew | undefined> {
703
+ async getBlockByHash(blockHash: BlockHash): Promise<L2Block | undefined> {
583
704
  const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
584
705
  if (blockNumber === undefined) {
585
706
  return undefined;
@@ -592,7 +713,7 @@ export class BlockStore {
592
713
  * @param archive - The archive root of the block to return.
593
714
  * @returns The requested L2 block.
594
715
  */
595
- async getBlockByArchive(archive: Fr): Promise<L2BlockNew | undefined> {
716
+ async getBlockByArchive(archive: Fr): Promise<L2Block | undefined> {
596
717
  const blockNumber = await this.#blockArchiveIndex.getAsync(archive.toString());
597
718
  if (blockNumber === undefined) {
598
719
  return undefined;
@@ -605,7 +726,7 @@ export class BlockStore {
605
726
  * @param blockHash - The hash of the block to return.
606
727
  * @returns The requested block header.
607
728
  */
608
- async getBlockHeaderByHash(blockHash: L2BlockHash): Promise<BlockHeader | undefined> {
729
+ async getBlockHeaderByHash(blockHash: BlockHash): Promise<BlockHeader | undefined> {
609
730
  const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
610
731
  if (blockNumber === undefined) {
611
732
  return undefined;
@@ -665,14 +786,24 @@ export class BlockStore {
665
786
  }
666
787
  }
667
788
 
789
+ private getBlockDataFromBlockStorage(blockStorage: BlockStorage): BlockData {
790
+ return {
791
+ header: BlockHeader.fromBuffer(blockStorage.header),
792
+ archive: AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive),
793
+ blockHash: Fr.fromBuffer(blockStorage.blockHash),
794
+ checkpointNumber: CheckpointNumber(blockStorage.checkpointNumber),
795
+ indexWithinCheckpoint: IndexWithinCheckpoint(blockStorage.indexWithinCheckpoint),
796
+ };
797
+ }
798
+
668
799
  private async getBlockFromBlockStorage(
669
800
  blockNumber: number,
670
801
  blockStorage: BlockStorage,
671
- ): Promise<L2BlockNew | undefined> {
672
- const header = BlockHeader.fromBuffer(blockStorage.header);
673
- const archive = AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive);
674
- const blockHash = blockStorage.blockHash;
675
- const blockHashString = bufferToHex(blockHash);
802
+ ): Promise<L2Block | undefined> {
803
+ const { header, archive, blockHash, checkpointNumber, indexWithinCheckpoint } =
804
+ this.getBlockDataFromBlockStorage(blockStorage);
805
+ header.setHash(blockHash);
806
+ const blockHashString = bufferToHex(blockStorage.blockHash);
676
807
  const blockTxsBuffer = await this.#blockTxs.getAsync(blockHashString);
677
808
  if (blockTxsBuffer === undefined) {
678
809
  this.#log.warn(`Could not find body for block ${header.globalVariables.blockNumber} ${blockHash}`);
@@ -691,13 +822,7 @@ export class BlockStore {
691
822
  txEffects.push(deserializeIndexedTxEffect(txEffect).data);
692
823
  }
693
824
  const body = new Body(txEffects);
694
- const block = new L2BlockNew(
695
- archive,
696
- header,
697
- body,
698
- CheckpointNumber(blockStorage.checkpointNumber!),
699
- blockStorage.indexWithinCheckpoint,
700
- );
825
+ const block = new L2Block(archive, header, body, checkpointNumber, indexWithinCheckpoint);
701
826
 
702
827
  if (block.number !== blockNumber) {
703
828
  throw new Error(
@@ -727,19 +852,48 @@ export class BlockStore {
727
852
  * @param txHash - The hash of a tx we try to get the receipt for.
728
853
  * @returns The requested tx receipt (or undefined if not found).
729
854
  */
730
- async getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
855
+ async getSettledTxReceipt(
856
+ txHash: TxHash,
857
+ l1Constants?: Pick<L1RollupConstants, 'epochDuration'>,
858
+ ): Promise<TxReceipt | undefined> {
731
859
  const txEffect = await this.getTxEffect(txHash);
732
860
  if (!txEffect) {
733
861
  return undefined;
734
862
  }
735
863
 
864
+ const blockNumber = BlockNumber(txEffect.l2BlockNumber);
865
+
866
+ // Use existing archiver methods to determine finalization level
867
+ const [provenBlockNumber, checkpointedBlockNumber, finalizedBlockNumber, blockData] = await Promise.all([
868
+ this.getProvenBlockNumber(),
869
+ this.getCheckpointedL2BlockNumber(),
870
+ this.getFinalizedL2BlockNumber(),
871
+ this.getBlockData(blockNumber),
872
+ ]);
873
+
874
+ let status: TxStatus;
875
+ if (blockNumber <= finalizedBlockNumber) {
876
+ status = TxStatus.FINALIZED;
877
+ } else if (blockNumber <= provenBlockNumber) {
878
+ status = TxStatus.PROVEN;
879
+ } else if (blockNumber <= checkpointedBlockNumber) {
880
+ status = TxStatus.CHECKPOINTED;
881
+ } else {
882
+ status = TxStatus.PROPOSED;
883
+ }
884
+
885
+ const epochNumber =
886
+ blockData && l1Constants ? getEpochAtSlot(blockData.header.globalVariables.slotNumber, l1Constants) : undefined;
887
+
736
888
  return new TxReceipt(
737
889
  txHash,
738
- TxReceipt.statusFromRevertCode(txEffect.data.revertCode),
739
- '',
890
+ status,
891
+ TxReceipt.executionResultFromRevertCode(txEffect.data.revertCode),
892
+ undefined,
740
893
  txEffect.data.transactionFee.toBigInt(),
741
894
  txEffect.l2BlockHash,
742
- BlockNumber(txEffect.l2BlockNumber),
895
+ blockNumber,
896
+ epochNumber,
743
897
  );
744
898
  }
745
899
 
@@ -776,7 +930,7 @@ export class BlockStore {
776
930
  if (!checkpoint) {
777
931
  return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
778
932
  }
779
- return BlockNumber(checkpoint.startBlock + checkpoint.numBlocks - 1);
933
+ return BlockNumber(checkpoint.startBlock + checkpoint.blockCount - 1);
780
934
  }
781
935
 
782
936
  async getLatestL2BlockNumber(): Promise<BlockNumber> {
@@ -811,6 +965,20 @@ export class BlockStore {
811
965
  return result;
812
966
  }
813
967
 
968
+ async getFinalizedCheckpointNumber(): Promise<CheckpointNumber> {
969
+ const [latestCheckpointNumber, finalizedCheckpointNumber] = await Promise.all([
970
+ this.getLatestCheckpointNumber(),
971
+ this.#lastFinalizedCheckpoint.getAsync(),
972
+ ]);
973
+ return (finalizedCheckpointNumber ?? 0) > latestCheckpointNumber
974
+ ? latestCheckpointNumber
975
+ : CheckpointNumber(finalizedCheckpointNumber ?? 0);
976
+ }
977
+
978
+ setFinalizedCheckpointNumber(checkpointNumber: CheckpointNumber) {
979
+ return this.#lastFinalizedCheckpoint.set(checkpointNumber);
980
+ }
981
+
814
982
  #computeBlockRange(start: BlockNumber, limit: number): Required<Pick<Range<number>, 'start' | 'limit'>> {
815
983
  if (limit < 1) {
816
984
  throw new Error(`Invalid limit: ${limit}`);