@aztec/archiver 0.0.1-commit.d1f2d6c → 0.0.1-commit.d20b825a7
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.
- package/README.md +12 -6
- package/dest/archiver.d.ts +16 -10
- package/dest/archiver.d.ts.map +1 -1
- package/dest/archiver.js +110 -122
- package/dest/config.d.ts +5 -3
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +15 -3
- package/dest/errors.d.ts +55 -10
- package/dest/errors.d.ts.map +1 -1
- package/dest/errors.js +74 -15
- package/dest/factory.d.ts +5 -4
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +34 -29
- package/dest/index.d.ts +4 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +3 -1
- package/dest/l1/bin/retrieve-calldata.js +36 -33
- package/dest/l1/calldata_retriever.d.ts +73 -50
- package/dest/l1/calldata_retriever.d.ts.map +1 -1
- package/dest/l1/calldata_retriever.js +191 -259
- package/dest/l1/data_retrieval.d.ts +26 -17
- package/dest/l1/data_retrieval.d.ts.map +1 -1
- package/dest/l1/data_retrieval.js +43 -48
- package/dest/l1/spire_proposer.d.ts +5 -5
- package/dest/l1/spire_proposer.d.ts.map +1 -1
- package/dest/l1/spire_proposer.js +9 -17
- package/dest/l1/validate_historical_logs.d.ts +23 -0
- package/dest/l1/validate_historical_logs.d.ts.map +1 -0
- package/dest/l1/validate_historical_logs.js +108 -0
- package/dest/l1/validate_trace.d.ts +6 -3
- package/dest/l1/validate_trace.d.ts.map +1 -1
- package/dest/l1/validate_trace.js +13 -9
- package/dest/modules/data_source_base.d.ts +17 -10
- package/dest/modules/data_source_base.d.ts.map +1 -1
- package/dest/modules/data_source_base.js +39 -77
- package/dest/modules/data_store_updater.d.ts +50 -26
- package/dest/modules/data_store_updater.d.ts.map +1 -1
- package/dest/modules/data_store_updater.js +169 -130
- package/dest/modules/instrumentation.d.ts +21 -3
- package/dest/modules/instrumentation.d.ts.map +1 -1
- package/dest/modules/instrumentation.js +58 -18
- package/dest/modules/l1_synchronizer.d.ts +10 -9
- package/dest/modules/l1_synchronizer.d.ts.map +1 -1
- package/dest/modules/l1_synchronizer.js +285 -157
- package/dest/modules/validation.d.ts +1 -1
- package/dest/modules/validation.d.ts.map +1 -1
- package/dest/modules/validation.js +2 -2
- package/dest/store/block_store.d.ts +85 -36
- package/dest/store/block_store.d.ts.map +1 -1
- package/dest/store/block_store.js +433 -162
- package/dest/store/contract_class_store.d.ts +2 -3
- package/dest/store/contract_class_store.d.ts.map +1 -1
- package/dest/store/contract_class_store.js +16 -72
- package/dest/store/contract_instance_store.d.ts +1 -1
- package/dest/store/contract_instance_store.d.ts.map +1 -1
- package/dest/store/contract_instance_store.js +6 -2
- package/dest/store/kv_archiver_store.d.ts +76 -32
- package/dest/store/kv_archiver_store.d.ts.map +1 -1
- package/dest/store/kv_archiver_store.js +92 -37
- package/dest/store/l2_tips_cache.d.ts +20 -0
- package/dest/store/l2_tips_cache.d.ts.map +1 -0
- package/dest/store/l2_tips_cache.js +109 -0
- package/dest/store/log_store.d.ts +6 -3
- package/dest/store/log_store.d.ts.map +1 -1
- package/dest/store/log_store.js +151 -56
- package/dest/store/message_store.d.ts +5 -1
- package/dest/store/message_store.d.ts.map +1 -1
- package/dest/store/message_store.js +21 -9
- package/dest/test/fake_l1_state.d.ts +24 -1
- package/dest/test/fake_l1_state.d.ts.map +1 -1
- package/dest/test/fake_l1_state.js +145 -28
- package/dest/test/index.js +3 -1
- package/dest/test/mock_archiver.d.ts +1 -1
- package/dest/test/mock_archiver.d.ts.map +1 -1
- package/dest/test/mock_archiver.js +3 -2
- package/dest/test/mock_l1_to_l2_message_source.d.ts +1 -1
- package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
- package/dest/test/mock_l1_to_l2_message_source.js +2 -1
- package/dest/test/mock_l2_block_source.d.ts +31 -10
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +163 -92
- package/dest/test/mock_structs.d.ts +6 -2
- package/dest/test/mock_structs.d.ts.map +1 -1
- package/dest/test/mock_structs.js +20 -6
- package/dest/test/noop_l1_archiver.d.ts +26 -0
- package/dest/test/noop_l1_archiver.d.ts.map +1 -0
- package/dest/test/noop_l1_archiver.js +74 -0
- package/package.json +14 -13
- package/src/archiver.ts +150 -146
- package/src/config.ts +22 -2
- package/src/errors.ts +116 -26
- package/src/factory.ts +49 -26
- package/src/index.ts +3 -1
- package/src/l1/README.md +25 -68
- package/src/l1/bin/retrieve-calldata.ts +46 -39
- package/src/l1/calldata_retriever.ts +250 -379
- package/src/l1/data_retrieval.ts +59 -70
- package/src/l1/spire_proposer.ts +7 -15
- package/src/l1/validate_historical_logs.ts +140 -0
- package/src/l1/validate_trace.ts +24 -6
- package/src/modules/data_source_base.ts +81 -101
- package/src/modules/data_store_updater.ts +202 -160
- package/src/modules/instrumentation.ts +71 -19
- package/src/modules/l1_synchronizer.ts +365 -197
- package/src/modules/validation.ts +2 -2
- package/src/store/block_store.ts +546 -206
- package/src/store/contract_class_store.ts +16 -110
- package/src/store/contract_instance_store.ts +8 -5
- package/src/store/kv_archiver_store.ts +143 -53
- package/src/store/l2_tips_cache.ts +134 -0
- package/src/store/log_store.ts +225 -67
- package/src/store/message_store.ts +27 -10
- package/src/structs/inbox_message.ts +1 -1
- package/src/test/fake_l1_state.ts +193 -32
- package/src/test/index.ts +3 -0
- package/src/test/mock_archiver.ts +3 -2
- package/src/test/mock_l1_to_l2_message_source.ts +1 -0
- package/src/test/mock_l2_block_source.ts +217 -90
- package/src/test/mock_structs.ts +42 -12
- package/src/test/noop_l1_archiver.ts +117 -0
package/src/store/block_store.ts
CHANGED
|
@@ -9,17 +9,26 @@ 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
17
|
L2Block,
|
|
16
|
-
L2BlockHash,
|
|
17
18
|
type ValidateCheckpointResult,
|
|
18
19
|
deserializeValidateCheckpointResult,
|
|
19
20
|
serializeValidateCheckpointResult,
|
|
20
21
|
} from '@aztec/stdlib/block';
|
|
21
|
-
import {
|
|
22
|
-
|
|
22
|
+
import {
|
|
23
|
+
Checkpoint,
|
|
24
|
+
type CheckpointData,
|
|
25
|
+
type CommonCheckpointData,
|
|
26
|
+
L1PublishedData,
|
|
27
|
+
type ProposedCheckpointData,
|
|
28
|
+
type ProposedCheckpointInput,
|
|
29
|
+
PublishedCheckpoint,
|
|
30
|
+
} from '@aztec/stdlib/checkpoint';
|
|
31
|
+
import { type L1RollupConstants, getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
|
|
23
32
|
import { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
24
33
|
import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
|
|
25
34
|
import {
|
|
@@ -34,15 +43,20 @@ import {
|
|
|
34
43
|
} from '@aztec/stdlib/tx';
|
|
35
44
|
|
|
36
45
|
import {
|
|
46
|
+
BlockAlreadyCheckpointedError,
|
|
37
47
|
BlockArchiveNotConsistentError,
|
|
38
48
|
BlockIndexNotSequentialError,
|
|
39
49
|
BlockNotFoundError,
|
|
40
50
|
BlockNumberNotSequentialError,
|
|
51
|
+
CannotOverwriteCheckpointedBlockError,
|
|
41
52
|
CheckpointNotFoundError,
|
|
42
|
-
CheckpointNumberNotConsistentError,
|
|
43
53
|
CheckpointNumberNotSequentialError,
|
|
44
|
-
InitialBlockNumberNotSequentialError,
|
|
45
54
|
InitialCheckpointNumberNotSequentialError,
|
|
55
|
+
NoProposedCheckpointToPromoteError,
|
|
56
|
+
ProposedCheckpointArchiveRootMismatchError,
|
|
57
|
+
ProposedCheckpointNotSequentialError,
|
|
58
|
+
ProposedCheckpointPromotionNotSequentialError,
|
|
59
|
+
ProposedCheckpointStaleError,
|
|
46
60
|
} from '../errors.js';
|
|
47
61
|
|
|
48
62
|
export { TxReceipt, type TxEffect, type TxHash } from '@aztec/stdlib/tx';
|
|
@@ -57,26 +71,29 @@ type BlockStorage = {
|
|
|
57
71
|
indexWithinCheckpoint: number;
|
|
58
72
|
};
|
|
59
73
|
|
|
60
|
-
|
|
74
|
+
/** Checkpoint Storage shared between Checkpoints + Proposed Checkpoints */
|
|
75
|
+
type CommonCheckpointStorage = {
|
|
61
76
|
header: Buffer;
|
|
62
77
|
archive: Buffer;
|
|
78
|
+
checkpointOutHash: Buffer;
|
|
63
79
|
checkpointNumber: number;
|
|
64
80
|
startBlock: number;
|
|
65
|
-
|
|
81
|
+
blockCount: number;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
type CheckpointStorage = CommonCheckpointStorage & {
|
|
66
85
|
l1: Buffer;
|
|
67
86
|
attestations: Buffer[];
|
|
68
87
|
};
|
|
69
88
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
startBlock: number;
|
|
75
|
-
numBlocks: number;
|
|
76
|
-
l1: L1PublishedData;
|
|
77
|
-
attestations: Buffer[];
|
|
89
|
+
/** Storage format for a proposed checkpoint (attested but not yet L1-confirmed). */
|
|
90
|
+
type ProposedCheckpointStorage = CommonCheckpointStorage & {
|
|
91
|
+
totalManaUsed: string;
|
|
92
|
+
feeAssetPriceModifier: string;
|
|
78
93
|
};
|
|
79
94
|
|
|
95
|
+
export type RemoveCheckpointsResult = { blocksRemoved: L2Block[] | undefined };
|
|
96
|
+
|
|
80
97
|
/**
|
|
81
98
|
* LMDB-based block storage for the archiver.
|
|
82
99
|
*/
|
|
@@ -87,6 +104,9 @@ export class BlockStore {
|
|
|
87
104
|
/** Map checkpoint number to checkpoint data */
|
|
88
105
|
#checkpoints: AztecAsyncMap<number, CheckpointStorage>;
|
|
89
106
|
|
|
107
|
+
/** Map slot number to checkpoint number, for looking up checkpoints by slot range. */
|
|
108
|
+
#slotToCheckpoint: AztecAsyncMap<number, number>;
|
|
109
|
+
|
|
90
110
|
/** Map block hash to list of tx hashes */
|
|
91
111
|
#blockTxs: AztecAsyncMap<string, Buffer>;
|
|
92
112
|
|
|
@@ -99,6 +119,9 @@ export class BlockStore {
|
|
|
99
119
|
/** Stores last proven checkpoint */
|
|
100
120
|
#lastProvenCheckpoint: AztecAsyncSingleton<number>;
|
|
101
121
|
|
|
122
|
+
/** Stores last finalized checkpoint (proven at or before the finalized L1 block) */
|
|
123
|
+
#lastFinalizedCheckpoint: AztecAsyncSingleton<number>;
|
|
124
|
+
|
|
102
125
|
/** Stores the pending chain validation status */
|
|
103
126
|
#pendingChainValidationStatus: AztecAsyncSingleton<Buffer>;
|
|
104
127
|
|
|
@@ -111,12 +134,12 @@ export class BlockStore {
|
|
|
111
134
|
/** Index mapping block archive to block number */
|
|
112
135
|
#blockArchiveIndex: AztecAsyncMap<string, number>;
|
|
113
136
|
|
|
137
|
+
/** Singleton: assumes max 1-deep pipeline. For deeper pipelining, replace with a map keyed by checkpoint number. */
|
|
138
|
+
#proposedCheckpoint: AztecAsyncSingleton<ProposedCheckpointStorage>;
|
|
139
|
+
|
|
114
140
|
#log = createLogger('archiver:block_store');
|
|
115
141
|
|
|
116
|
-
constructor(
|
|
117
|
-
private db: AztecAsyncKVStore,
|
|
118
|
-
private l1Constants: Pick<L1RollupConstants, 'epochDuration'>,
|
|
119
|
-
) {
|
|
142
|
+
constructor(private db: AztecAsyncKVStore) {
|
|
120
143
|
this.#blocks = db.openMap('archiver_blocks');
|
|
121
144
|
this.#blockTxs = db.openMap('archiver_block_txs');
|
|
122
145
|
this.#txEffects = db.openMap('archiver_tx_effects');
|
|
@@ -125,111 +148,114 @@ export class BlockStore {
|
|
|
125
148
|
this.#blockArchiveIndex = db.openMap('archiver_block_archive_index');
|
|
126
149
|
this.#lastSynchedL1Block = db.openSingleton('archiver_last_synched_l1_block');
|
|
127
150
|
this.#lastProvenCheckpoint = db.openSingleton('archiver_last_proven_l2_checkpoint');
|
|
151
|
+
this.#lastFinalizedCheckpoint = db.openSingleton('archiver_last_finalized_l2_checkpoint');
|
|
128
152
|
this.#pendingChainValidationStatus = db.openSingleton('archiver_pending_chain_validation_status');
|
|
129
153
|
this.#checkpoints = db.openMap('archiver_checkpoints');
|
|
154
|
+
this.#slotToCheckpoint = db.openMap('archiver_slot_to_checkpoint');
|
|
155
|
+
this.#proposedCheckpoint = db.openSingleton('proposed_checkpoint_data');
|
|
130
156
|
}
|
|
131
157
|
|
|
132
158
|
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* TODO(#13569): Compute proper finalized block number based on L1 finalized block.
|
|
136
|
-
* TODO(palla/mbps): Even the provisional computation is wrong, since it should subtract checkpoints, not blocks
|
|
159
|
+
* Returns the finalized L2 block number. An L2 block is finalized when it was proven
|
|
160
|
+
* in an L1 block that has itself been finalized on Ethereum.
|
|
137
161
|
* @returns The finalized block number.
|
|
138
162
|
*/
|
|
139
163
|
async getFinalizedL2BlockNumber(): Promise<BlockNumber> {
|
|
140
|
-
const
|
|
141
|
-
|
|
164
|
+
const finalizedCheckpointNumber = await this.getFinalizedCheckpointNumber();
|
|
165
|
+
if (finalizedCheckpointNumber === INITIAL_CHECKPOINT_NUMBER - 1) {
|
|
166
|
+
return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
|
|
167
|
+
}
|
|
168
|
+
const checkpointStorage = await this.#checkpoints.getAsync(finalizedCheckpointNumber);
|
|
169
|
+
if (!checkpointStorage) {
|
|
170
|
+
throw new CheckpointNotFoundError(finalizedCheckpointNumber);
|
|
171
|
+
}
|
|
172
|
+
return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
|
|
142
173
|
}
|
|
143
174
|
|
|
144
175
|
/**
|
|
145
|
-
* Append new
|
|
146
|
-
*
|
|
176
|
+
* Append a new proposed block to the store.
|
|
177
|
+
* This is an uncheckpointed block that has been proposed by the sequencer but not yet included in a checkpoint on L1.
|
|
178
|
+
* For checkpointed blocks (already published to L1), use addCheckpoints() instead.
|
|
179
|
+
* @param block - The proposed L2 block to be added to the store.
|
|
147
180
|
* @returns True if the operation is successful.
|
|
148
181
|
*/
|
|
149
|
-
async
|
|
150
|
-
if (blocks.length === 0) {
|
|
151
|
-
return true;
|
|
152
|
-
}
|
|
153
|
-
|
|
182
|
+
async addProposedBlock(block: L2Block, opts: { force?: boolean } = {}): Promise<boolean> {
|
|
154
183
|
return await this.db.transactionAsync(async () => {
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
const firstBlockLastArchive = blocks[0].header.lastArchive.root;
|
|
184
|
+
const blockNumber = block.number;
|
|
185
|
+
const blockCheckpointNumber = block.checkpointNumber;
|
|
186
|
+
const blockIndex = block.indexWithinCheckpoint;
|
|
187
|
+
const blockLastArchive = block.header.lastArchive.root;
|
|
160
188
|
|
|
161
189
|
// Extract the latest block and checkpoint numbers
|
|
162
|
-
const previousBlockNumber = await this.
|
|
190
|
+
const previousBlockNumber = await this.getLatestL2BlockNumber();
|
|
191
|
+
const proposedCheckpointNumber = await this.getProposedCheckpointNumber();
|
|
163
192
|
const previousCheckpointNumber = await this.getLatestCheckpointNumber();
|
|
164
193
|
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
194
|
+
// Verify we're not overwriting checkpointed blocks
|
|
195
|
+
const lastCheckpointedBlockNumber = await this.getCheckpointedL2BlockNumber();
|
|
196
|
+
if (!opts.force && blockNumber <= lastCheckpointedBlockNumber) {
|
|
197
|
+
// Check if the proposed block matches the already-checkpointed one
|
|
198
|
+
const existingBlock = await this.getBlock(BlockNumber(blockNumber));
|
|
199
|
+
if (existingBlock && existingBlock.archive.root.equals(block.archive.root)) {
|
|
200
|
+
throw new BlockAlreadyCheckpointedError(blockNumber);
|
|
201
|
+
}
|
|
202
|
+
throw new CannotOverwriteCheckpointedBlockError(blockNumber, lastCheckpointedBlockNumber);
|
|
168
203
|
}
|
|
169
204
|
|
|
170
|
-
//
|
|
171
|
-
if (!opts.force &&
|
|
172
|
-
throw new
|
|
205
|
+
// Check that the block number is the expected one
|
|
206
|
+
if (!opts.force && previousBlockNumber !== blockNumber - 1) {
|
|
207
|
+
throw new BlockNumberNotSequentialError(blockNumber, previousBlockNumber);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// The same check as above but for checkpoints. Accept the block if either the confirmed
|
|
211
|
+
// checkpoint or the pending (locally validated but not yet confirmed) checkpoint matches.
|
|
212
|
+
const expectedCheckpointNumber = blockCheckpointNumber - 1;
|
|
213
|
+
if (
|
|
214
|
+
!opts.force &&
|
|
215
|
+
previousCheckpointNumber !== expectedCheckpointNumber &&
|
|
216
|
+
proposedCheckpointNumber !== expectedCheckpointNumber
|
|
217
|
+
) {
|
|
218
|
+
const [reported, source]: [CheckpointNumber, 'confirmed' | 'proposed'] =
|
|
219
|
+
proposedCheckpointNumber > previousCheckpointNumber
|
|
220
|
+
? [proposedCheckpointNumber, 'proposed']
|
|
221
|
+
: [previousCheckpointNumber, 'confirmed'];
|
|
222
|
+
throw new CheckpointNumberNotSequentialError(blockCheckpointNumber, reported, source);
|
|
173
223
|
}
|
|
174
224
|
|
|
175
225
|
// Extract the previous block if there is one and see if it is for the same checkpoint or not
|
|
176
226
|
const previousBlockResult = await this.getBlock(previousBlockNumber);
|
|
177
227
|
|
|
178
|
-
let
|
|
228
|
+
let expectedBlockIndex = 0;
|
|
179
229
|
let previousBlockIndex: number | undefined = undefined;
|
|
180
230
|
if (previousBlockResult !== undefined) {
|
|
181
|
-
if (previousBlockResult.checkpointNumber ===
|
|
231
|
+
if (previousBlockResult.checkpointNumber === blockCheckpointNumber) {
|
|
182
232
|
// The previous block is for the same checkpoint, therefore our index should follow it
|
|
183
233
|
previousBlockIndex = previousBlockResult.indexWithinCheckpoint;
|
|
184
|
-
|
|
234
|
+
expectedBlockIndex = previousBlockIndex + 1;
|
|
185
235
|
}
|
|
186
|
-
if (!previousBlockResult.archive.root.equals(
|
|
236
|
+
if (!previousBlockResult.archive.root.equals(blockLastArchive)) {
|
|
187
237
|
throw new BlockArchiveNotConsistentError(
|
|
188
|
-
|
|
238
|
+
blockNumber,
|
|
189
239
|
previousBlockResult.number,
|
|
190
|
-
|
|
240
|
+
blockLastArchive,
|
|
191
241
|
previousBlockResult.archive.root,
|
|
192
242
|
);
|
|
193
243
|
}
|
|
194
244
|
}
|
|
195
245
|
|
|
196
|
-
// Now check that the
|
|
197
|
-
if (!opts.force &&
|
|
198
|
-
throw new BlockIndexNotSequentialError(
|
|
246
|
+
// Now check that the block has the expected index value
|
|
247
|
+
if (!opts.force && expectedBlockIndex !== blockIndex) {
|
|
248
|
+
throw new BlockIndexNotSequentialError(blockIndex, previousBlockIndex);
|
|
199
249
|
}
|
|
200
250
|
|
|
201
|
-
|
|
202
|
-
let previousBlock: L2Block | undefined = undefined;
|
|
203
|
-
for (const block of blocks) {
|
|
204
|
-
if (!opts.force && previousBlock) {
|
|
205
|
-
if (previousBlock.number + 1 !== block.number) {
|
|
206
|
-
throw new BlockNumberNotSequentialError(block.number, previousBlock.number);
|
|
207
|
-
}
|
|
208
|
-
if (previousBlock.indexWithinCheckpoint + 1 !== block.indexWithinCheckpoint) {
|
|
209
|
-
throw new BlockIndexNotSequentialError(block.indexWithinCheckpoint, previousBlock.indexWithinCheckpoint);
|
|
210
|
-
}
|
|
211
|
-
if (!previousBlock.archive.root.equals(block.header.lastArchive.root)) {
|
|
212
|
-
throw new BlockArchiveNotConsistentError(
|
|
213
|
-
block.number,
|
|
214
|
-
previousBlock.number,
|
|
215
|
-
block.header.lastArchive.root,
|
|
216
|
-
previousBlock.archive.root,
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
if (!opts.force && firstBlockCheckpointNumber !== block.checkpointNumber) {
|
|
221
|
-
throw new CheckpointNumberNotConsistentError(block.checkpointNumber, firstBlockCheckpointNumber);
|
|
222
|
-
}
|
|
223
|
-
previousBlock = block;
|
|
224
|
-
await this.addBlockToDatabase(block, block.checkpointNumber, block.indexWithinCheckpoint);
|
|
225
|
-
}
|
|
251
|
+
await this.addBlockToDatabase(block, block.checkpointNumber, block.indexWithinCheckpoint);
|
|
226
252
|
|
|
227
253
|
return true;
|
|
228
254
|
});
|
|
229
255
|
}
|
|
230
256
|
|
|
231
257
|
/**
|
|
232
|
-
* Append new
|
|
258
|
+
* Append new checkpoints to the store's list.
|
|
233
259
|
* @param checkpoints - The L2 checkpoints to be added to the store.
|
|
234
260
|
* @returns True if the operation is successful.
|
|
235
261
|
*/
|
|
@@ -239,37 +265,29 @@ export class BlockStore {
|
|
|
239
265
|
}
|
|
240
266
|
|
|
241
267
|
return await this.db.transactionAsync(async () => {
|
|
242
|
-
// Check that the checkpoint immediately before the first block to be added is present in the store.
|
|
243
268
|
const firstCheckpointNumber = checkpoints[0].checkpoint.number;
|
|
244
269
|
const previousCheckpointNumber = await this.getLatestCheckpointNumber();
|
|
245
270
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
// There should be a previous checkpoint
|
|
254
|
-
previousCheckpointData = await this.getCheckpointData(previousCheckpointNumber);
|
|
255
|
-
if (previousCheckpointData === undefined) {
|
|
256
|
-
throw new CheckpointNotFoundError(previousCheckpointNumber);
|
|
271
|
+
// Handle already-stored checkpoints at the start of the batch.
|
|
272
|
+
// This can happen after an L1 reorg re-includes a checkpoint in a different L1 block.
|
|
273
|
+
// We accept them if archives match (same content) and update their L1 metadata.
|
|
274
|
+
if (!opts.force && firstCheckpointNumber <= previousCheckpointNumber) {
|
|
275
|
+
checkpoints = await this.skipOrUpdateAlreadyStoredCheckpoints(checkpoints, previousCheckpointNumber);
|
|
276
|
+
if (checkpoints.length === 0) {
|
|
277
|
+
return true;
|
|
257
278
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
// If we have a previous checkpoint then we need to get the previous block number
|
|
264
|
-
if (previousCheckpointData !== undefined) {
|
|
265
|
-
previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.numBlocks - 1);
|
|
266
|
-
previousBlock = await this.getBlock(previousBlockNumber);
|
|
267
|
-
if (previousBlock === undefined) {
|
|
268
|
-
// We should be able to get the required previous block
|
|
269
|
-
throw new BlockNotFoundError(previousBlockNumber);
|
|
279
|
+
// Re-check sequentiality after skipping
|
|
280
|
+
const newFirstNumber = checkpoints[0].checkpoint.number;
|
|
281
|
+
if (previousCheckpointNumber !== newFirstNumber - 1) {
|
|
282
|
+
throw new InitialCheckpointNumberNotSequentialError(newFirstNumber, previousCheckpointNumber);
|
|
270
283
|
}
|
|
284
|
+
} else if (previousCheckpointNumber !== firstCheckpointNumber - 1 && !opts.force) {
|
|
285
|
+
throw new InitialCheckpointNumberNotSequentialError(firstCheckpointNumber, previousCheckpointNumber);
|
|
271
286
|
}
|
|
272
287
|
|
|
288
|
+
// Get the last block of the previous checkpoint for archive chaining
|
|
289
|
+
let previousBlock = await this.getPreviousCheckpointBlock(checkpoints[0].checkpoint.number);
|
|
290
|
+
|
|
273
291
|
// Iterate over checkpoints array and insert them, checking that the block numbers are sequential.
|
|
274
292
|
let previousCheckpoint: PublishedCheckpoint | undefined = undefined;
|
|
275
293
|
for (const checkpoint of checkpoints) {
|
|
@@ -285,62 +303,148 @@ export class BlockStore {
|
|
|
285
303
|
}
|
|
286
304
|
previousCheckpoint = checkpoint;
|
|
287
305
|
|
|
288
|
-
//
|
|
289
|
-
|
|
290
|
-
const block = checkpoint.checkpoint.blocks[i];
|
|
291
|
-
if (previousBlock) {
|
|
292
|
-
// The blocks should have a sequential block number
|
|
293
|
-
if (previousBlock.number !== block.number - 1) {
|
|
294
|
-
throw new BlockNumberNotSequentialError(block.number, previousBlock.number);
|
|
295
|
-
}
|
|
296
|
-
// If the blocks are for the same checkpoint then they should have sequential indexes
|
|
297
|
-
if (
|
|
298
|
-
previousBlock.checkpointNumber === block.checkpointNumber &&
|
|
299
|
-
previousBlock.indexWithinCheckpoint !== block.indexWithinCheckpoint - 1
|
|
300
|
-
) {
|
|
301
|
-
throw new BlockIndexNotSequentialError(block.indexWithinCheckpoint, previousBlock.indexWithinCheckpoint);
|
|
302
|
-
}
|
|
303
|
-
if (!previousBlock.archive.root.equals(block.header.lastArchive.root)) {
|
|
304
|
-
throw new BlockArchiveNotConsistentError(
|
|
305
|
-
block.number,
|
|
306
|
-
previousBlock.number,
|
|
307
|
-
block.header.lastArchive.root,
|
|
308
|
-
previousBlock.archive.root,
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
} else {
|
|
312
|
-
// No previous block, must be block 1 at checkpoint index 0
|
|
313
|
-
if (block.indexWithinCheckpoint !== 0) {
|
|
314
|
-
throw new BlockIndexNotSequentialError(block.indexWithinCheckpoint, undefined);
|
|
315
|
-
}
|
|
316
|
-
if (block.number !== INITIAL_L2_BLOCK_NUM) {
|
|
317
|
-
throw new BlockNumberNotSequentialError(block.number, undefined);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
306
|
+
// Validate block sequencing, indexes, and archive chaining
|
|
307
|
+
this.validateCheckpointBlocks(checkpoint.checkpoint.blocks, previousBlock);
|
|
320
308
|
|
|
321
|
-
|
|
322
|
-
|
|
309
|
+
// Store every block in the database (may already exist, but L1 data is authoritative)
|
|
310
|
+
for (let i = 0; i < checkpoint.checkpoint.blocks.length; i++) {
|
|
311
|
+
await this.addBlockToDatabase(checkpoint.checkpoint.blocks[i], checkpoint.checkpoint.number, i);
|
|
323
312
|
}
|
|
313
|
+
previousBlock = checkpoint.checkpoint.blocks.at(-1);
|
|
324
314
|
|
|
325
315
|
// Store the checkpoint in the database
|
|
326
316
|
await this.#checkpoints.set(checkpoint.checkpoint.number, {
|
|
327
317
|
header: checkpoint.checkpoint.header.toBuffer(),
|
|
328
318
|
archive: checkpoint.checkpoint.archive.toBuffer(),
|
|
319
|
+
checkpointOutHash: checkpoint.checkpoint.getCheckpointOutHash().toBuffer(),
|
|
329
320
|
l1: checkpoint.l1.toBuffer(),
|
|
330
321
|
attestations: checkpoint.attestations.map(attestation => attestation.toBuffer()),
|
|
331
322
|
checkpointNumber: checkpoint.checkpoint.number,
|
|
332
323
|
startBlock: checkpoint.checkpoint.blocks[0].number,
|
|
333
|
-
|
|
324
|
+
blockCount: checkpoint.checkpoint.blocks.length,
|
|
334
325
|
});
|
|
326
|
+
|
|
327
|
+
// Update slot-to-checkpoint index
|
|
328
|
+
await this.#slotToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, checkpoint.checkpoint.number);
|
|
335
329
|
}
|
|
336
330
|
|
|
331
|
+
// Clear the proposed checkpoint if any of the confirmed checkpoints match or supersede it
|
|
332
|
+
const lastConfirmedCheckpointNumber = checkpoints[checkpoints.length - 1].checkpoint.number;
|
|
333
|
+
await this.clearProposedCheckpointIfSuperseded(lastConfirmedCheckpointNumber);
|
|
334
|
+
|
|
337
335
|
await this.#lastSynchedL1Block.set(checkpoints[checkpoints.length - 1].l1.blockNumber);
|
|
338
336
|
return true;
|
|
339
337
|
});
|
|
340
338
|
}
|
|
341
339
|
|
|
340
|
+
/**
|
|
341
|
+
* Handles checkpoints at the start of a batch that are already stored (e.g. due to L1 reorg).
|
|
342
|
+
* Verifies the archive root matches, updates L1 metadata, and returns only the new checkpoints.
|
|
343
|
+
*/
|
|
344
|
+
private async skipOrUpdateAlreadyStoredCheckpoints(
|
|
345
|
+
checkpoints: PublishedCheckpoint[],
|
|
346
|
+
latestStored: CheckpointNumber,
|
|
347
|
+
): Promise<PublishedCheckpoint[]> {
|
|
348
|
+
let i = 0;
|
|
349
|
+
for (; i < checkpoints.length && checkpoints[i].checkpoint.number <= latestStored; i++) {
|
|
350
|
+
const incoming = checkpoints[i];
|
|
351
|
+
const stored = await this.getCheckpointData(incoming.checkpoint.number);
|
|
352
|
+
if (!stored) {
|
|
353
|
+
// Should not happen if latestStored is correct, but be safe
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
// Verify the checkpoint content matches (archive root)
|
|
357
|
+
if (!stored.archive.root.equals(incoming.checkpoint.archive.root)) {
|
|
358
|
+
throw new Error(
|
|
359
|
+
`Checkpoint ${incoming.checkpoint.number} already exists in store but with a different archive root. ` +
|
|
360
|
+
`Stored: ${stored.archive.root}, incoming: ${incoming.checkpoint.archive.root}`,
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
// Update L1 metadata and attestations for the already-stored checkpoint
|
|
364
|
+
this.#log.warn(
|
|
365
|
+
`Checkpoint ${incoming.checkpoint.number} already stored, updating L1 info ` +
|
|
366
|
+
`(L1 block ${stored.l1.blockNumber} -> ${incoming.l1.blockNumber})`,
|
|
367
|
+
);
|
|
368
|
+
await this.#checkpoints.set(incoming.checkpoint.number, {
|
|
369
|
+
header: incoming.checkpoint.header.toBuffer(),
|
|
370
|
+
archive: incoming.checkpoint.archive.toBuffer(),
|
|
371
|
+
checkpointOutHash: incoming.checkpoint.getCheckpointOutHash().toBuffer(),
|
|
372
|
+
l1: incoming.l1.toBuffer(),
|
|
373
|
+
attestations: incoming.attestations.map(a => a.toBuffer()),
|
|
374
|
+
checkpointNumber: incoming.checkpoint.number,
|
|
375
|
+
startBlock: incoming.checkpoint.blocks[0].number,
|
|
376
|
+
blockCount: incoming.checkpoint.blocks.length,
|
|
377
|
+
});
|
|
378
|
+
// Update the sync point to reflect the new L1 block
|
|
379
|
+
await this.#lastSynchedL1Block.set(incoming.l1.blockNumber);
|
|
380
|
+
}
|
|
381
|
+
return checkpoints.slice(i);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Gets the last block of the checkpoint before the given one.
|
|
386
|
+
* Returns undefined if there is no previous checkpoint (i.e. genesis).
|
|
387
|
+
*/
|
|
388
|
+
private async getPreviousCheckpointBlock(checkpointNumber: CheckpointNumber): Promise<L2Block | undefined> {
|
|
389
|
+
const previousCheckpointNumber = CheckpointNumber(checkpointNumber - 1);
|
|
390
|
+
if (previousCheckpointNumber === INITIAL_CHECKPOINT_NUMBER - 1) {
|
|
391
|
+
return undefined;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const previousCheckpointData = await this.getCheckpointData(previousCheckpointNumber);
|
|
395
|
+
if (previousCheckpointData === undefined) {
|
|
396
|
+
throw new CheckpointNotFoundError(previousCheckpointNumber);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const previousBlockNumber = BlockNumber(previousCheckpointData.startBlock + previousCheckpointData.blockCount - 1);
|
|
400
|
+
const previousBlock = await this.getBlock(previousBlockNumber);
|
|
401
|
+
if (previousBlock === undefined) {
|
|
402
|
+
throw new BlockNotFoundError(previousBlockNumber);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return previousBlock;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Validates that blocks are sequential, have correct indexes, and chain via archive roots.
|
|
410
|
+
* This is the same validation used for both confirmed checkpoints (addCheckpoints) and
|
|
411
|
+
* proposed checkpoints (setProposedCheckpoint).
|
|
412
|
+
*/
|
|
413
|
+
private validateCheckpointBlocks(blocks: L2Block[], previousBlock: L2Block | undefined): void {
|
|
414
|
+
for (const block of blocks) {
|
|
415
|
+
if (previousBlock) {
|
|
416
|
+
if (previousBlock.number !== block.number - 1) {
|
|
417
|
+
throw new BlockNumberNotSequentialError(block.number, previousBlock.number);
|
|
418
|
+
}
|
|
419
|
+
if (previousBlock.checkpointNumber === block.checkpointNumber) {
|
|
420
|
+
if (previousBlock.indexWithinCheckpoint !== block.indexWithinCheckpoint - 1) {
|
|
421
|
+
throw new BlockIndexNotSequentialError(block.indexWithinCheckpoint, previousBlock.indexWithinCheckpoint);
|
|
422
|
+
}
|
|
423
|
+
} else if (block.indexWithinCheckpoint !== 0) {
|
|
424
|
+
throw new BlockIndexNotSequentialError(block.indexWithinCheckpoint, previousBlock.indexWithinCheckpoint);
|
|
425
|
+
}
|
|
426
|
+
if (!previousBlock.archive.root.equals(block.header.lastArchive.root)) {
|
|
427
|
+
throw new BlockArchiveNotConsistentError(
|
|
428
|
+
block.number,
|
|
429
|
+
previousBlock.number,
|
|
430
|
+
block.header.lastArchive.root,
|
|
431
|
+
previousBlock.archive.root,
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
} else {
|
|
435
|
+
if (block.indexWithinCheckpoint !== 0) {
|
|
436
|
+
throw new BlockIndexNotSequentialError(block.indexWithinCheckpoint, undefined);
|
|
437
|
+
}
|
|
438
|
+
if (block.number !== INITIAL_L2_BLOCK_NUM) {
|
|
439
|
+
throw new BlockNumberNotSequentialError(block.number, undefined);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
previousBlock = block;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
342
446
|
private async addBlockToDatabase(block: L2Block, checkpointNumber: number, indexWithinCheckpoint: number) {
|
|
343
|
-
const blockHash =
|
|
447
|
+
const blockHash = await block.hash();
|
|
344
448
|
|
|
345
449
|
await this.#blocks.set(block.number, {
|
|
346
450
|
header: block.header.toBuffer(),
|
|
@@ -385,51 +489,59 @@ export class BlockStore {
|
|
|
385
489
|
}
|
|
386
490
|
|
|
387
491
|
/**
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
* @param checkpointsToUnwind - The number of checkpoints we are to unwind
|
|
392
|
-
* @returns True if the operation is successful
|
|
492
|
+
* Removes all checkpoints with checkpoint number > checkpointNumber.
|
|
493
|
+
* Also removes ALL blocks (both checkpointed and uncheckpointed) after the last block of the given checkpoint.
|
|
494
|
+
* @param checkpointNumber - Remove all checkpoints strictly after this one.
|
|
393
495
|
*/
|
|
394
|
-
async
|
|
496
|
+
async removeCheckpointsAfter(checkpointNumber: CheckpointNumber): Promise<RemoveCheckpointsResult> {
|
|
395
497
|
return await this.db.transactionAsync(async () => {
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
498
|
+
const latestCheckpointNumber = await this.getLatestCheckpointNumber();
|
|
499
|
+
|
|
500
|
+
if (checkpointNumber >= latestCheckpointNumber) {
|
|
501
|
+
this.#log.warn(`No checkpoints to remove after ${checkpointNumber} (latest is ${latestCheckpointNumber})`);
|
|
502
|
+
return { blocksRemoved: undefined };
|
|
399
503
|
}
|
|
400
504
|
|
|
505
|
+
// If the proven checkpoint is beyond the target, update it
|
|
401
506
|
const proven = await this.getProvenCheckpointNumber();
|
|
402
|
-
if (
|
|
403
|
-
|
|
507
|
+
if (proven > checkpointNumber) {
|
|
508
|
+
this.#log.warn(`Updating proven checkpoint ${proven} to last valid checkpoint ${checkpointNumber}`);
|
|
509
|
+
await this.setProvenCheckpointNumber(checkpointNumber);
|
|
404
510
|
}
|
|
405
511
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
512
|
+
// Find the last block number to keep (last block of the given checkpoint, or 0 if no checkpoint)
|
|
513
|
+
let lastBlockToKeep: BlockNumber;
|
|
514
|
+
if (checkpointNumber <= 0) {
|
|
515
|
+
lastBlockToKeep = BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
|
|
516
|
+
} else {
|
|
517
|
+
const targetCheckpoint = await this.#checkpoints.getAsync(checkpointNumber);
|
|
518
|
+
if (!targetCheckpoint) {
|
|
519
|
+
throw new Error(`Target checkpoint ${checkpointNumber} not found in store`);
|
|
413
520
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
for (let blockNumber = checkpoint.startBlock; blockNumber <= maxBlock; blockNumber++) {
|
|
418
|
-
const block = await this.getBlock(BlockNumber(blockNumber));
|
|
521
|
+
lastBlockToKeep = BlockNumber(targetCheckpoint.startBlock + targetCheckpoint.blockCount - 1);
|
|
522
|
+
}
|
|
419
523
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
continue;
|
|
423
|
-
}
|
|
524
|
+
// Remove all blocks after lastBlockToKeep (both checkpointed and uncheckpointed)
|
|
525
|
+
const blocksRemoved = await this.removeBlocksAfter(lastBlockToKeep);
|
|
424
526
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
527
|
+
// Remove all checkpoints after the target
|
|
528
|
+
for (let c = latestCheckpointNumber; c > checkpointNumber; c = CheckpointNumber(c - 1)) {
|
|
529
|
+
const checkpointStorage = await this.#checkpoints.getAsync(c);
|
|
530
|
+
if (checkpointStorage) {
|
|
531
|
+
const slotNumber = CheckpointHeader.fromBuffer(checkpointStorage.header).slotNumber;
|
|
532
|
+
await this.#slotToCheckpoint.delete(slotNumber);
|
|
429
533
|
}
|
|
534
|
+
await this.#checkpoints.delete(c);
|
|
535
|
+
this.#log.debug(`Removed checkpoint ${c}`);
|
|
430
536
|
}
|
|
431
537
|
|
|
432
|
-
|
|
538
|
+
// Clear any proposed checkpoint that was orphaned by the removal (its base chain no longer exists)
|
|
539
|
+
const proposedCheckpointNumber = await this.getProposedCheckpointNumber();
|
|
540
|
+
if (proposedCheckpointNumber > checkpointNumber) {
|
|
541
|
+
await this.#proposedCheckpoint.delete();
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return { blocksRemoved };
|
|
433
545
|
});
|
|
434
546
|
}
|
|
435
547
|
|
|
@@ -453,17 +565,32 @@ export class BlockStore {
|
|
|
453
565
|
return checkpoints;
|
|
454
566
|
}
|
|
455
567
|
|
|
456
|
-
|
|
457
|
-
|
|
568
|
+
/** Returns checkpoint data for all checkpoints whose slot falls within the given range (inclusive). */
|
|
569
|
+
async getCheckpointDataForSlotRange(startSlot: SlotNumber, endSlot: SlotNumber): Promise<CheckpointData[]> {
|
|
570
|
+
const result: CheckpointData[] = [];
|
|
571
|
+
for await (const [, checkpointNumber] of this.#slotToCheckpoint.entriesAsync({
|
|
572
|
+
start: startSlot,
|
|
573
|
+
end: endSlot + 1,
|
|
574
|
+
})) {
|
|
575
|
+
const checkpointStorage = await this.#checkpoints.getAsync(checkpointNumber);
|
|
576
|
+
if (checkpointStorage) {
|
|
577
|
+
result.push(this.checkpointDataFromCheckpointStorage(checkpointStorage));
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return result;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
private checkpointDataFromCheckpointStorage(checkpointStorage: CheckpointStorage): CheckpointData {
|
|
584
|
+
return {
|
|
458
585
|
header: CheckpointHeader.fromBuffer(checkpointStorage.header),
|
|
459
586
|
archive: AppendOnlyTreeSnapshot.fromBuffer(checkpointStorage.archive),
|
|
587
|
+
checkpointOutHash: Fr.fromBuffer(checkpointStorage.checkpointOutHash),
|
|
460
588
|
checkpointNumber: CheckpointNumber(checkpointStorage.checkpointNumber),
|
|
461
|
-
startBlock: checkpointStorage.startBlock,
|
|
462
|
-
|
|
589
|
+
startBlock: BlockNumber(checkpointStorage.startBlock),
|
|
590
|
+
blockCount: checkpointStorage.blockCount,
|
|
463
591
|
l1: L1PublishedData.fromBuffer(checkpointStorage.l1),
|
|
464
|
-
attestations: checkpointStorage.attestations,
|
|
592
|
+
attestations: checkpointStorage.attestations.map(buf => CommitteeAttestation.fromBuffer(buf)),
|
|
465
593
|
};
|
|
466
|
-
return data;
|
|
467
594
|
}
|
|
468
595
|
|
|
469
596
|
async getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2Block[] | undefined> {
|
|
@@ -475,7 +602,7 @@ export class BlockStore {
|
|
|
475
602
|
const blocksForCheckpoint = await toArray(
|
|
476
603
|
this.#blocks.entriesAsync({
|
|
477
604
|
start: checkpoint.startBlock,
|
|
478
|
-
end: checkpoint.startBlock + checkpoint.
|
|
605
|
+
end: checkpoint.startBlock + checkpoint.blockCount,
|
|
479
606
|
}),
|
|
480
607
|
);
|
|
481
608
|
|
|
@@ -510,15 +637,16 @@ export class BlockStore {
|
|
|
510
637
|
|
|
511
638
|
/**
|
|
512
639
|
* Removes all blocks with block number > blockNumber.
|
|
640
|
+
* Does not remove any associated checkpoints.
|
|
513
641
|
* @param blockNumber - The block number to remove after.
|
|
514
642
|
* @returns The removed blocks (for event emission).
|
|
515
643
|
*/
|
|
516
|
-
async
|
|
644
|
+
async removeBlocksAfter(blockNumber: BlockNumber): Promise<L2Block[]> {
|
|
517
645
|
return await this.db.transactionAsync(async () => {
|
|
518
646
|
const removedBlocks: L2Block[] = [];
|
|
519
647
|
|
|
520
648
|
// Get the latest block number to determine the range
|
|
521
|
-
const latestBlockNumber = await this.
|
|
649
|
+
const latestBlockNumber = await this.getLatestL2BlockNumber();
|
|
522
650
|
|
|
523
651
|
// Iterate from blockNumber + 1 to latestBlockNumber
|
|
524
652
|
for (let bn = blockNumber + 1; bn <= latestBlockNumber; bn++) {
|
|
@@ -547,17 +675,10 @@ export class BlockStore {
|
|
|
547
675
|
if (!checkpointStorage) {
|
|
548
676
|
throw new CheckpointNotFoundError(provenCheckpointNumber);
|
|
549
677
|
} else {
|
|
550
|
-
return BlockNumber(checkpointStorage.startBlock + checkpointStorage.
|
|
678
|
+
return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
|
|
551
679
|
}
|
|
552
680
|
}
|
|
553
681
|
|
|
554
|
-
async getLatestBlockNumber(): Promise<BlockNumber> {
|
|
555
|
-
const [latestBlocknumber] = await toArray(this.#blocks.keysAsync({ reverse: true, limit: 1 }));
|
|
556
|
-
return typeof latestBlocknumber === 'number'
|
|
557
|
-
? BlockNumber(latestBlocknumber)
|
|
558
|
-
: BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
682
|
async getLatestCheckpointNumber(): Promise<CheckpointNumber> {
|
|
562
683
|
const [latestCheckpointNumber] = await toArray(this.#checkpoints.keysAsync({ reverse: true, limit: 1 }));
|
|
563
684
|
if (latestCheckpointNumber === undefined) {
|
|
@@ -566,6 +687,133 @@ export class BlockStore {
|
|
|
566
687
|
return CheckpointNumber(latestCheckpointNumber);
|
|
567
688
|
}
|
|
568
689
|
|
|
690
|
+
async hasProposedCheckpoint(): Promise<boolean> {
|
|
691
|
+
const proposed = await this.#proposedCheckpoint.getAsync();
|
|
692
|
+
return proposed !== undefined;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/** Deletes the proposed checkpoint from storage. */
|
|
696
|
+
async deleteProposedCheckpoint(): Promise<void> {
|
|
697
|
+
await this.#proposedCheckpoint.delete();
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Promotes the proposed checkpoint singleton to a confirmed checkpoint entry.
|
|
702
|
+
* This persists the checkpoint to the store, clears the proposed singleton, and updates the L1 sync point.
|
|
703
|
+
* Should only be called after the checkpoint has been validated.
|
|
704
|
+
* @param expectedArchiveRoot - The archive root to match against the proposed checkpoint, to guard against races.
|
|
705
|
+
*/
|
|
706
|
+
async promoteProposedToCheckpointed(
|
|
707
|
+
l1: L1PublishedData,
|
|
708
|
+
attestations: CommitteeAttestation[],
|
|
709
|
+
expectedArchiveRoot: Fr,
|
|
710
|
+
): Promise<void> {
|
|
711
|
+
return await this.db.transactionAsync(async () => {
|
|
712
|
+
const proposed = await this.getProposedCheckpointOnly();
|
|
713
|
+
if (!proposed) {
|
|
714
|
+
throw new NoProposedCheckpointToPromoteError();
|
|
715
|
+
}
|
|
716
|
+
if (!proposed.archive.root.equals(expectedArchiveRoot)) {
|
|
717
|
+
throw new ProposedCheckpointArchiveRootMismatchError(expectedArchiveRoot, proposed.archive.root);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Verify sequentiality: promoted checkpoint must follow the latest confirmed one
|
|
721
|
+
const latestCheckpointNumber = await this.getLatestCheckpointNumber();
|
|
722
|
+
if (latestCheckpointNumber !== proposed.checkpointNumber - 1) {
|
|
723
|
+
throw new ProposedCheckpointPromotionNotSequentialError(proposed.checkpointNumber, latestCheckpointNumber);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Write the checkpoint entry
|
|
727
|
+
await this.#checkpoints.set(proposed.checkpointNumber, {
|
|
728
|
+
header: proposed.header.toBuffer(),
|
|
729
|
+
archive: proposed.archive.toBuffer(),
|
|
730
|
+
checkpointOutHash: proposed.checkpointOutHash.toBuffer(),
|
|
731
|
+
l1: l1.toBuffer(),
|
|
732
|
+
attestations: attestations.map(attestation => attestation.toBuffer()),
|
|
733
|
+
checkpointNumber: proposed.checkpointNumber,
|
|
734
|
+
startBlock: proposed.startBlock,
|
|
735
|
+
blockCount: proposed.blockCount,
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
// Update the slot-to-checkpoint index
|
|
739
|
+
await this.#slotToCheckpoint.set(proposed.header.slotNumber, proposed.checkpointNumber);
|
|
740
|
+
|
|
741
|
+
// Clear the proposed checkpoint singleton
|
|
742
|
+
await this.#proposedCheckpoint.delete();
|
|
743
|
+
|
|
744
|
+
// Update the last synced L1 block
|
|
745
|
+
await this.#lastSynchedL1Block.set(l1.blockNumber);
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/** Clears the proposed checkpoint if the given confirmed checkpoint number supersedes it. */
|
|
750
|
+
async clearProposedCheckpointIfSuperseded(confirmedCheckpointNumber: CheckpointNumber): Promise<void> {
|
|
751
|
+
const proposedCheckpointNumber = await this.getProposedCheckpointNumber();
|
|
752
|
+
if (proposedCheckpointNumber <= confirmedCheckpointNumber) {
|
|
753
|
+
await this.#proposedCheckpoint.delete();
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/** Returns the proposed checkpoint data, or undefined if no proposed checkpoint exists. No fallback to confirmed. */
|
|
758
|
+
async getProposedCheckpointOnly(): Promise<ProposedCheckpointData | undefined> {
|
|
759
|
+
const stored = await this.#proposedCheckpoint.getAsync();
|
|
760
|
+
if (!stored) {
|
|
761
|
+
return undefined;
|
|
762
|
+
}
|
|
763
|
+
return this.convertToProposedCheckpointData(stored);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Gets the checkpoint at the proposed tip
|
|
768
|
+
* - pending checkpoint if it exists
|
|
769
|
+
* - fallsback to latest confirmed checkpoint otherwise
|
|
770
|
+
* @returns CommonCheckpointData
|
|
771
|
+
*/
|
|
772
|
+
async getProposedCheckpoint(): Promise<CommonCheckpointData | undefined> {
|
|
773
|
+
const stored = await this.#proposedCheckpoint.getAsync();
|
|
774
|
+
if (!stored) {
|
|
775
|
+
return this.getCheckpointData(await this.getLatestCheckpointNumber());
|
|
776
|
+
}
|
|
777
|
+
return this.convertToProposedCheckpointData(stored);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
private convertToProposedCheckpointData(stored: ProposedCheckpointStorage): ProposedCheckpointData {
|
|
781
|
+
return {
|
|
782
|
+
checkpointNumber: CheckpointNumber(stored.checkpointNumber),
|
|
783
|
+
header: CheckpointHeader.fromBuffer(stored.header),
|
|
784
|
+
archive: AppendOnlyTreeSnapshot.fromBuffer(stored.archive),
|
|
785
|
+
checkpointOutHash: Fr.fromBuffer(stored.checkpointOutHash),
|
|
786
|
+
startBlock: BlockNumber(stored.startBlock),
|
|
787
|
+
blockCount: stored.blockCount,
|
|
788
|
+
totalManaUsed: BigInt(stored.totalManaUsed),
|
|
789
|
+
feeAssetPriceModifier: BigInt(stored.feeAssetPriceModifier),
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Attempts to get the proposedCheckpoint's number, if there is not one, then fallback to the latest confirmed checkpoint number.
|
|
795
|
+
* @returns CheckpointNumber
|
|
796
|
+
*/
|
|
797
|
+
async getProposedCheckpointNumber(): Promise<CheckpointNumber> {
|
|
798
|
+
const proposed = await this.getProposedCheckpoint();
|
|
799
|
+
if (!proposed) {
|
|
800
|
+
return await this.getLatestCheckpointNumber();
|
|
801
|
+
}
|
|
802
|
+
return CheckpointNumber(proposed.checkpointNumber);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Attempts to get the proposedCheckpoint's block number, if there is not one, then fallback to the checkpointed block number
|
|
807
|
+
* @returns BlockNumber
|
|
808
|
+
*/
|
|
809
|
+
async getProposedCheckpointL2BlockNumber(): Promise<BlockNumber> {
|
|
810
|
+
const proposed = await this.getProposedCheckpoint();
|
|
811
|
+
if (!proposed) {
|
|
812
|
+
return await this.getCheckpointedL2BlockNumber();
|
|
813
|
+
}
|
|
814
|
+
return BlockNumber(proposed.startBlock + proposed.blockCount - 1);
|
|
815
|
+
}
|
|
816
|
+
|
|
569
817
|
async getCheckpointedBlock(number: BlockNumber): Promise<CheckpointedL2Block | undefined> {
|
|
570
818
|
const blockStorage = await this.#blocks.getAsync(number);
|
|
571
819
|
if (!blockStorage) {
|
|
@@ -615,7 +863,7 @@ export class BlockStore {
|
|
|
615
863
|
}
|
|
616
864
|
}
|
|
617
865
|
|
|
618
|
-
async getCheckpointedBlockByHash(blockHash:
|
|
866
|
+
async getCheckpointedBlockByHash(blockHash: BlockHash): Promise<CheckpointedL2Block | undefined> {
|
|
619
867
|
const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
|
|
620
868
|
if (blockNumber === undefined) {
|
|
621
869
|
return undefined;
|
|
@@ -646,6 +894,32 @@ export class BlockStore {
|
|
|
646
894
|
}
|
|
647
895
|
}
|
|
648
896
|
|
|
897
|
+
/**
|
|
898
|
+
* Gets block metadata (without tx data) by block number.
|
|
899
|
+
* @param blockNumber - The number of the block to return.
|
|
900
|
+
* @returns The requested block data.
|
|
901
|
+
*/
|
|
902
|
+
async getBlockData(blockNumber: BlockNumber): Promise<BlockData | undefined> {
|
|
903
|
+
const blockStorage = await this.#blocks.getAsync(blockNumber);
|
|
904
|
+
if (!blockStorage || !blockStorage.header) {
|
|
905
|
+
return undefined;
|
|
906
|
+
}
|
|
907
|
+
return this.getBlockDataFromBlockStorage(blockStorage);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Gets block metadata (without tx data) by archive root.
|
|
912
|
+
* @param archive - The archive root of the block to return.
|
|
913
|
+
* @returns The requested block data.
|
|
914
|
+
*/
|
|
915
|
+
async getBlockDataByArchive(archive: Fr): Promise<BlockData | undefined> {
|
|
916
|
+
const blockNumber = await this.#blockArchiveIndex.getAsync(archive.toString());
|
|
917
|
+
if (blockNumber === undefined) {
|
|
918
|
+
return undefined;
|
|
919
|
+
}
|
|
920
|
+
return this.getBlockData(BlockNumber(blockNumber));
|
|
921
|
+
}
|
|
922
|
+
|
|
649
923
|
/**
|
|
650
924
|
* Gets an L2 block.
|
|
651
925
|
* @param blockNumber - The number of the block to return.
|
|
@@ -664,7 +938,7 @@ export class BlockStore {
|
|
|
664
938
|
* @param blockHash - The hash of the block to return.
|
|
665
939
|
* @returns The requested L2 block.
|
|
666
940
|
*/
|
|
667
|
-
async getBlockByHash(blockHash:
|
|
941
|
+
async getBlockByHash(blockHash: BlockHash): Promise<L2Block | undefined> {
|
|
668
942
|
const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
|
|
669
943
|
if (blockNumber === undefined) {
|
|
670
944
|
return undefined;
|
|
@@ -690,7 +964,7 @@ export class BlockStore {
|
|
|
690
964
|
* @param blockHash - The hash of the block to return.
|
|
691
965
|
* @returns The requested block header.
|
|
692
966
|
*/
|
|
693
|
-
async getBlockHeaderByHash(blockHash:
|
|
967
|
+
async getBlockHeaderByHash(blockHash: BlockHash): Promise<BlockHeader | undefined> {
|
|
694
968
|
const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
|
|
695
969
|
if (blockNumber === undefined) {
|
|
696
970
|
return undefined;
|
|
@@ -750,15 +1024,24 @@ export class BlockStore {
|
|
|
750
1024
|
}
|
|
751
1025
|
}
|
|
752
1026
|
|
|
1027
|
+
private getBlockDataFromBlockStorage(blockStorage: BlockStorage): BlockData {
|
|
1028
|
+
return {
|
|
1029
|
+
header: BlockHeader.fromBuffer(blockStorage.header),
|
|
1030
|
+
archive: AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive),
|
|
1031
|
+
blockHash: BlockHash.fromBuffer(blockStorage.blockHash),
|
|
1032
|
+
checkpointNumber: CheckpointNumber(blockStorage.checkpointNumber),
|
|
1033
|
+
indexWithinCheckpoint: IndexWithinCheckpoint(blockStorage.indexWithinCheckpoint),
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
|
|
753
1037
|
private async getBlockFromBlockStorage(
|
|
754
1038
|
blockNumber: number,
|
|
755
1039
|
blockStorage: BlockStorage,
|
|
756
1040
|
): Promise<L2Block | undefined> {
|
|
757
|
-
const header =
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
const blockHashString = bufferToHex(blockHash);
|
|
1041
|
+
const { header, archive, blockHash, checkpointNumber, indexWithinCheckpoint } =
|
|
1042
|
+
this.getBlockDataFromBlockStorage(blockStorage);
|
|
1043
|
+
header.setHash(blockHash);
|
|
1044
|
+
const blockHashString = bufferToHex(blockStorage.blockHash);
|
|
762
1045
|
const blockTxsBuffer = await this.#blockTxs.getAsync(blockHashString);
|
|
763
1046
|
if (blockTxsBuffer === undefined) {
|
|
764
1047
|
this.#log.warn(`Could not find body for block ${header.globalVariables.blockNumber} ${blockHash}`);
|
|
@@ -777,13 +1060,7 @@ export class BlockStore {
|
|
|
777
1060
|
txEffects.push(deserializeIndexedTxEffect(txEffect).data);
|
|
778
1061
|
}
|
|
779
1062
|
const body = new Body(txEffects);
|
|
780
|
-
const block = new L2Block(
|
|
781
|
-
archive,
|
|
782
|
-
header,
|
|
783
|
-
body,
|
|
784
|
-
CheckpointNumber(blockStorage.checkpointNumber!),
|
|
785
|
-
IndexWithinCheckpoint(blockStorage.indexWithinCheckpoint),
|
|
786
|
-
);
|
|
1063
|
+
const block = new L2Block(archive, header, body, checkpointNumber, indexWithinCheckpoint);
|
|
787
1064
|
|
|
788
1065
|
if (block.number !== blockNumber) {
|
|
789
1066
|
throw new Error(
|
|
@@ -813,7 +1090,10 @@ export class BlockStore {
|
|
|
813
1090
|
* @param txHash - The hash of a tx we try to get the receipt for.
|
|
814
1091
|
* @returns The requested tx receipt (or undefined if not found).
|
|
815
1092
|
*/
|
|
816
|
-
async getSettledTxReceipt(
|
|
1093
|
+
async getSettledTxReceipt(
|
|
1094
|
+
txHash: TxHash,
|
|
1095
|
+
l1Constants?: Pick<L1RollupConstants, 'epochDuration'>,
|
|
1096
|
+
): Promise<TxReceipt | undefined> {
|
|
817
1097
|
const txEffect = await this.getTxEffect(txHash);
|
|
818
1098
|
if (!txEffect) {
|
|
819
1099
|
return undefined;
|
|
@@ -822,10 +1102,11 @@ export class BlockStore {
|
|
|
822
1102
|
const blockNumber = BlockNumber(txEffect.l2BlockNumber);
|
|
823
1103
|
|
|
824
1104
|
// Use existing archiver methods to determine finalization level
|
|
825
|
-
const [provenBlockNumber, checkpointedBlockNumber, finalizedBlockNumber] = await Promise.all([
|
|
1105
|
+
const [provenBlockNumber, checkpointedBlockNumber, finalizedBlockNumber, blockData] = await Promise.all([
|
|
826
1106
|
this.getProvenBlockNumber(),
|
|
827
1107
|
this.getCheckpointedL2BlockNumber(),
|
|
828
1108
|
this.getFinalizedL2BlockNumber(),
|
|
1109
|
+
this.getBlockData(blockNumber),
|
|
829
1110
|
]);
|
|
830
1111
|
|
|
831
1112
|
let status: TxStatus;
|
|
@@ -839,6 +1120,9 @@ export class BlockStore {
|
|
|
839
1120
|
status = TxStatus.PROPOSED;
|
|
840
1121
|
}
|
|
841
1122
|
|
|
1123
|
+
const epochNumber =
|
|
1124
|
+
blockData && l1Constants ? getEpochAtSlot(blockData.header.globalVariables.slotNumber, l1Constants) : undefined;
|
|
1125
|
+
|
|
842
1126
|
return new TxReceipt(
|
|
843
1127
|
txHash,
|
|
844
1128
|
status,
|
|
@@ -847,6 +1131,7 @@ export class BlockStore {
|
|
|
847
1131
|
txEffect.data.transactionFee.toBigInt(),
|
|
848
1132
|
txEffect.l2BlockHash,
|
|
849
1133
|
blockNumber,
|
|
1134
|
+
epochNumber,
|
|
850
1135
|
);
|
|
851
1136
|
}
|
|
852
1137
|
|
|
@@ -883,7 +1168,7 @@ export class BlockStore {
|
|
|
883
1168
|
if (!checkpoint) {
|
|
884
1169
|
return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
|
|
885
1170
|
}
|
|
886
|
-
return BlockNumber(checkpoint.startBlock + checkpoint.
|
|
1171
|
+
return BlockNumber(checkpoint.startBlock + checkpoint.blockCount - 1);
|
|
887
1172
|
}
|
|
888
1173
|
|
|
889
1174
|
async getLatestL2BlockNumber(): Promise<BlockNumber> {
|
|
@@ -903,6 +1188,47 @@ export class BlockStore {
|
|
|
903
1188
|
return this.#lastSynchedL1Block.set(l1BlockNumber);
|
|
904
1189
|
}
|
|
905
1190
|
|
|
1191
|
+
/** Sets the proposed checkpoint (not yet L1-confirmed). Only accepts confirmed + 1.
|
|
1192
|
+
* Computes archive and checkpointOutHash from the stored blocks. */
|
|
1193
|
+
async setProposedCheckpoint(proposed: ProposedCheckpointInput) {
|
|
1194
|
+
return await this.db.transactionAsync(async () => {
|
|
1195
|
+
const current = await this.getProposedCheckpointNumber();
|
|
1196
|
+
if (proposed.checkpointNumber <= current) {
|
|
1197
|
+
throw new ProposedCheckpointStaleError(proposed.checkpointNumber, current);
|
|
1198
|
+
}
|
|
1199
|
+
const confirmed = await this.getLatestCheckpointNumber();
|
|
1200
|
+
if (proposed.checkpointNumber !== confirmed + 1) {
|
|
1201
|
+
throw new ProposedCheckpointNotSequentialError(proposed.checkpointNumber, confirmed);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
// Ensure the previous checkpoint + blocks exist
|
|
1205
|
+
const previousBlock = await this.getPreviousCheckpointBlock(proposed.checkpointNumber);
|
|
1206
|
+
const blocks: L2Block[] = [];
|
|
1207
|
+
for (let i = 0; i < proposed.blockCount; i++) {
|
|
1208
|
+
const block = await this.getBlock(BlockNumber(proposed.startBlock + i));
|
|
1209
|
+
if (!block) {
|
|
1210
|
+
throw new BlockNotFoundError(proposed.startBlock + i);
|
|
1211
|
+
}
|
|
1212
|
+
blocks.push(block);
|
|
1213
|
+
}
|
|
1214
|
+
this.validateCheckpointBlocks(blocks, previousBlock);
|
|
1215
|
+
|
|
1216
|
+
const archive = blocks[blocks.length - 1].archive;
|
|
1217
|
+
const checkpointOutHash = Checkpoint.getCheckpointOutHash(blocks);
|
|
1218
|
+
|
|
1219
|
+
await this.#proposedCheckpoint.set({
|
|
1220
|
+
header: proposed.header.toBuffer(),
|
|
1221
|
+
archive: archive.toBuffer(),
|
|
1222
|
+
checkpointOutHash: checkpointOutHash.toBuffer(),
|
|
1223
|
+
checkpointNumber: proposed.checkpointNumber,
|
|
1224
|
+
startBlock: proposed.startBlock,
|
|
1225
|
+
blockCount: proposed.blockCount,
|
|
1226
|
+
totalManaUsed: proposed.totalManaUsed.toString(),
|
|
1227
|
+
feeAssetPriceModifier: proposed.feeAssetPriceModifier.toString(),
|
|
1228
|
+
});
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
|
|
906
1232
|
async getProvenCheckpointNumber(): Promise<CheckpointNumber> {
|
|
907
1233
|
const [latestCheckpointNumber, provenCheckpointNumber] = await Promise.all([
|
|
908
1234
|
this.getLatestCheckpointNumber(),
|
|
@@ -918,6 +1244,20 @@ export class BlockStore {
|
|
|
918
1244
|
return result;
|
|
919
1245
|
}
|
|
920
1246
|
|
|
1247
|
+
async getFinalizedCheckpointNumber(): Promise<CheckpointNumber> {
|
|
1248
|
+
const [latestCheckpointNumber, finalizedCheckpointNumber] = await Promise.all([
|
|
1249
|
+
this.getLatestCheckpointNumber(),
|
|
1250
|
+
this.#lastFinalizedCheckpoint.getAsync(),
|
|
1251
|
+
]);
|
|
1252
|
+
return (finalizedCheckpointNumber ?? 0) > latestCheckpointNumber
|
|
1253
|
+
? latestCheckpointNumber
|
|
1254
|
+
: CheckpointNumber(finalizedCheckpointNumber ?? 0);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
setFinalizedCheckpointNumber(checkpointNumber: CheckpointNumber) {
|
|
1258
|
+
return this.#lastFinalizedCheckpoint.set(checkpointNumber);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
921
1261
|
#computeBlockRange(start: BlockNumber, limit: number): Required<Pick<Range<number>, 'start' | 'limit'>> {
|
|
922
1262
|
if (limit < 1) {
|
|
923
1263
|
throw new Error(`Invalid limit: ${limit}`);
|