@aztec/archiver 0.0.1-commit.3469e52 → 0.0.1-commit.35158ae7e
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 +9 -0
- package/dest/archiver.d.ts +12 -9
- package/dest/archiver.d.ts.map +1 -1
- package/dest/archiver.js +98 -124
- package/dest/config.d.ts +3 -3
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +2 -1
- package/dest/errors.d.ts +26 -9
- package/dest/errors.d.ts.map +1 -1
- package/dest/errors.js +34 -13
- package/dest/factory.d.ts +4 -2
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +29 -23
- package/dest/index.d.ts +2 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -0
- package/dest/l1/bin/retrieve-calldata.js +35 -32
- 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 +190 -259
- package/dest/l1/data_retrieval.d.ts +9 -9
- package/dest/l1/data_retrieval.d.ts.map +1 -1
- package/dest/l1/data_retrieval.js +24 -22
- 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_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 +27 -23
- package/dest/modules/data_source_base.d.ts.map +1 -1
- package/dest/modules/data_source_base.js +49 -124
- package/dest/modules/data_store_updater.d.ts +42 -26
- package/dest/modules/data_store_updater.d.ts.map +1 -1
- package/dest/modules/data_store_updater.js +149 -129
- package/dest/modules/instrumentation.d.ts +17 -4
- package/dest/modules/instrumentation.d.ts.map +1 -1
- package/dest/modules/instrumentation.js +36 -12
- package/dest/modules/l1_synchronizer.d.ts +5 -8
- package/dest/modules/l1_synchronizer.d.ts.map +1 -1
- package/dest/modules/l1_synchronizer.js +59 -25
- package/dest/store/block_store.d.ts +49 -32
- package/dest/store/block_store.d.ts.map +1 -1
- package/dest/store/block_store.js +188 -95
- 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 +64 -36
- package/dest/store/kv_archiver_store.d.ts.map +1 -1
- package/dest/store/kv_archiver_store.js +63 -29
- package/dest/store/l2_tips_cache.d.ts +19 -0
- package/dest/store/l2_tips_cache.d.ts.map +1 -0
- package/dest/store/l2_tips_cache.js +89 -0
- package/dest/store/log_store.d.ts +9 -6
- package/dest/store/log_store.d.ts.map +1 -1
- package/dest/store/log_store.js +150 -53
- 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 +14 -1
- package/dest/test/fake_l1_state.d.ts +16 -4
- package/dest/test/fake_l1_state.d.ts.map +1 -1
- package/dest/test/fake_l1_state.js +95 -23
- 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_l2_block_source.d.ts +39 -23
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +157 -112
- 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 +24 -10
- 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 +72 -0
- package/package.json +14 -13
- package/src/archiver.ts +126 -151
- package/src/config.ts +8 -1
- package/src/errors.ts +52 -24
- package/src/factory.ts +46 -22
- package/src/index.ts +1 -0
- package/src/l1/README.md +25 -68
- package/src/l1/bin/retrieve-calldata.ts +45 -33
- package/src/l1/calldata_retriever.ts +249 -379
- package/src/l1/data_retrieval.ts +27 -29
- package/src/l1/spire_proposer.ts +7 -15
- package/src/l1/validate_trace.ts +24 -6
- package/src/modules/data_source_base.ts +84 -169
- package/src/modules/data_store_updater.ts +166 -161
- package/src/modules/instrumentation.ts +46 -14
- package/src/modules/l1_synchronizer.ts +72 -36
- package/src/store/block_store.ts +239 -140
- 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 +104 -53
- package/src/store/l2_tips_cache.ts +89 -0
- package/src/store/log_store.ts +231 -70
- package/src/store/message_store.ts +20 -1
- package/src/test/fake_l1_state.ts +127 -28
- package/src/test/index.ts +3 -0
- package/src/test/mock_archiver.ts +3 -2
- package/src/test/mock_l2_block_source.ts +211 -129
- package/src/test/mock_structs.ts +45 -15
- package/src/test/noop_l1_archiver.ts +115 -0
package/src/store/block_store.ts
CHANGED
|
@@ -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
|
-
|
|
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,22 +61,15 @@ 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
|
-
|
|
67
|
+
blockCount: number;
|
|
64
68
|
l1: Buffer;
|
|
65
69
|
attestations: Buffer[];
|
|
66
70
|
};
|
|
67
71
|
|
|
68
|
-
export type
|
|
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
75
|
* LMDB-based block storage for the archiver.
|
|
@@ -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
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* @returns
|
|
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
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
const
|
|
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
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
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 !==
|
|
155
|
-
throw new
|
|
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
|
|
190
|
+
let expectedBlockIndex = 0;
|
|
162
191
|
let previousBlockIndex: number | undefined = undefined;
|
|
163
192
|
if (previousBlockResult !== undefined) {
|
|
164
|
-
if (previousBlockResult.checkpointNumber ===
|
|
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
|
-
|
|
196
|
+
expectedBlockIndex = previousBlockIndex + 1;
|
|
168
197
|
}
|
|
169
|
-
if (!previousBlockResult.archive.root.equals(
|
|
198
|
+
if (!previousBlockResult.archive.root.equals(blockLastArchive)) {
|
|
170
199
|
throw new BlockArchiveNotConsistentError(
|
|
171
|
-
|
|
200
|
+
blockNumber,
|
|
172
201
|
previousBlockResult.number,
|
|
173
|
-
|
|
202
|
+
blockLastArchive,
|
|
174
203
|
previousBlockResult.archive.root,
|
|
175
204
|
);
|
|
176
205
|
}
|
|
177
206
|
}
|
|
178
207
|
|
|
179
|
-
// Now check that the
|
|
180
|
-
if (!opts.force &&
|
|
181
|
-
throw new BlockIndexNotSequentialError(
|
|
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
|
-
|
|
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:
|
|
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.
|
|
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
|
-
|
|
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:
|
|
326
|
-
const blockHash =
|
|
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(),
|
|
@@ -351,7 +360,7 @@ export class BlockStore {
|
|
|
351
360
|
}
|
|
352
361
|
|
|
353
362
|
/** Deletes a block and all associated data (tx effects, indices). */
|
|
354
|
-
private async deleteBlock(block:
|
|
363
|
+
private async deleteBlock(block: L2Block): Promise<void> {
|
|
355
364
|
// Delete the block from the main blocks map
|
|
356
365
|
await this.#blocks.delete(block.number);
|
|
357
366
|
|
|
@@ -368,51 +377,53 @@ export class BlockStore {
|
|
|
368
377
|
}
|
|
369
378
|
|
|
370
379
|
/**
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
* @param checkpointsToUnwind - The number of checkpoints we are to unwind
|
|
375
|
-
* @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.
|
|
376
383
|
*/
|
|
377
|
-
async
|
|
384
|
+
async removeCheckpointsAfter(checkpointNumber: CheckpointNumber): Promise<RemoveCheckpointsResult> {
|
|
378
385
|
return await this.db.transactionAsync(async () => {
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
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 };
|
|
382
391
|
}
|
|
383
392
|
|
|
393
|
+
// If the proven checkpoint is beyond the target, update it
|
|
384
394
|
const proven = await this.getProvenCheckpointNumber();
|
|
385
|
-
if (
|
|
386
|
-
|
|
395
|
+
if (proven > checkpointNumber) {
|
|
396
|
+
this.#log.warn(`Updating proven checkpoint ${proven} to last valid checkpoint ${checkpointNumber}`);
|
|
397
|
+
await this.setProvenCheckpointNumber(checkpointNumber);
|
|
387
398
|
}
|
|
388
399
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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`);
|
|
396
408
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
for (let blockNumber = checkpoint.startBlock; blockNumber <= maxBlock; blockNumber++) {
|
|
401
|
-
const block = await this.getBlock(BlockNumber(blockNumber));
|
|
409
|
+
lastBlockToKeep = BlockNumber(targetCheckpoint.startBlock + targetCheckpoint.blockCount - 1);
|
|
410
|
+
}
|
|
402
411
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
continue;
|
|
406
|
-
}
|
|
412
|
+
// Remove all blocks after lastBlockToKeep (both checkpointed and uncheckpointed)
|
|
413
|
+
const blocksRemoved = await this.removeBlocksAfter(lastBlockToKeep);
|
|
407
414
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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);
|
|
412
421
|
}
|
|
422
|
+
await this.#checkpoints.delete(c);
|
|
423
|
+
this.#log.debug(`Removed checkpoint ${c}`);
|
|
413
424
|
}
|
|
414
425
|
|
|
415
|
-
return
|
|
426
|
+
return { blocksRemoved };
|
|
416
427
|
});
|
|
417
428
|
}
|
|
418
429
|
|
|
@@ -436,20 +447,35 @@ export class BlockStore {
|
|
|
436
447
|
return checkpoints;
|
|
437
448
|
}
|
|
438
449
|
|
|
439
|
-
|
|
440
|
-
|
|
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 {
|
|
441
467
|
header: CheckpointHeader.fromBuffer(checkpointStorage.header),
|
|
442
468
|
archive: AppendOnlyTreeSnapshot.fromBuffer(checkpointStorage.archive),
|
|
469
|
+
checkpointOutHash: Fr.fromBuffer(checkpointStorage.checkpointOutHash),
|
|
443
470
|
checkpointNumber: CheckpointNumber(checkpointStorage.checkpointNumber),
|
|
444
|
-
startBlock: checkpointStorage.startBlock,
|
|
445
|
-
|
|
471
|
+
startBlock: BlockNumber(checkpointStorage.startBlock),
|
|
472
|
+
blockCount: checkpointStorage.blockCount,
|
|
446
473
|
l1: L1PublishedData.fromBuffer(checkpointStorage.l1),
|
|
447
|
-
attestations: checkpointStorage.attestations,
|
|
474
|
+
attestations: checkpointStorage.attestations.map(buf => CommitteeAttestation.fromBuffer(buf)),
|
|
448
475
|
};
|
|
449
|
-
return data;
|
|
450
476
|
}
|
|
451
477
|
|
|
452
|
-
async getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<
|
|
478
|
+
async getBlocksForCheckpoint(checkpointNumber: CheckpointNumber): Promise<L2Block[] | undefined> {
|
|
453
479
|
const checkpoint = await this.#checkpoints.getAsync(checkpointNumber);
|
|
454
480
|
if (!checkpoint) {
|
|
455
481
|
return undefined;
|
|
@@ -458,7 +484,7 @@ export class BlockStore {
|
|
|
458
484
|
const blocksForCheckpoint = await toArray(
|
|
459
485
|
this.#blocks.entriesAsync({
|
|
460
486
|
start: checkpoint.startBlock,
|
|
461
|
-
end: checkpoint.startBlock + checkpoint.
|
|
487
|
+
end: checkpoint.startBlock + checkpoint.blockCount,
|
|
462
488
|
}),
|
|
463
489
|
);
|
|
464
490
|
|
|
@@ -472,8 +498,8 @@ export class BlockStore {
|
|
|
472
498
|
* @param slotNumber - The slot number to search for.
|
|
473
499
|
* @returns All blocks with the given slot number, in ascending block number order.
|
|
474
500
|
*/
|
|
475
|
-
async getBlocksForSlot(slotNumber: SlotNumber): Promise<
|
|
476
|
-
const blocks:
|
|
501
|
+
async getBlocksForSlot(slotNumber: SlotNumber): Promise<L2Block[]> {
|
|
502
|
+
const blocks: L2Block[] = [];
|
|
477
503
|
|
|
478
504
|
// Iterate backwards through all blocks and filter by slot number
|
|
479
505
|
// This is more efficient since we usually query for the most recent slot
|
|
@@ -493,12 +519,13 @@ export class BlockStore {
|
|
|
493
519
|
|
|
494
520
|
/**
|
|
495
521
|
* Removes all blocks with block number > blockNumber.
|
|
522
|
+
* Does not remove any associated checkpoints.
|
|
496
523
|
* @param blockNumber - The block number to remove after.
|
|
497
524
|
* @returns The removed blocks (for event emission).
|
|
498
525
|
*/
|
|
499
|
-
async
|
|
526
|
+
async removeBlocksAfter(blockNumber: BlockNumber): Promise<L2Block[]> {
|
|
500
527
|
return await this.db.transactionAsync(async () => {
|
|
501
|
-
const removedBlocks:
|
|
528
|
+
const removedBlocks: L2Block[] = [];
|
|
502
529
|
|
|
503
530
|
// Get the latest block number to determine the range
|
|
504
531
|
const latestBlockNumber = await this.getLatestBlockNumber();
|
|
@@ -530,7 +557,7 @@ export class BlockStore {
|
|
|
530
557
|
if (!checkpointStorage) {
|
|
531
558
|
throw new CheckpointNotFoundError(provenCheckpointNumber);
|
|
532
559
|
} else {
|
|
533
|
-
return BlockNumber(checkpointStorage.startBlock + checkpointStorage.
|
|
560
|
+
return BlockNumber(checkpointStorage.startBlock + checkpointStorage.blockCount - 1);
|
|
534
561
|
}
|
|
535
562
|
}
|
|
536
563
|
|
|
@@ -598,7 +625,7 @@ export class BlockStore {
|
|
|
598
625
|
}
|
|
599
626
|
}
|
|
600
627
|
|
|
601
|
-
async getCheckpointedBlockByHash(blockHash:
|
|
628
|
+
async getCheckpointedBlockByHash(blockHash: BlockHash): Promise<CheckpointedL2Block | undefined> {
|
|
602
629
|
const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
|
|
603
630
|
if (blockNumber === undefined) {
|
|
604
631
|
return undefined;
|
|
@@ -620,7 +647,7 @@ export class BlockStore {
|
|
|
620
647
|
* @param limit - The number of blocks to return.
|
|
621
648
|
* @returns The requested L2 blocks
|
|
622
649
|
*/
|
|
623
|
-
async *getBlocks(start: BlockNumber, limit: number): AsyncIterableIterator<
|
|
650
|
+
async *getBlocks(start: BlockNumber, limit: number): AsyncIterableIterator<L2Block> {
|
|
624
651
|
for await (const [blockNumber, blockStorage] of this.getBlockStorages(start, limit)) {
|
|
625
652
|
const block = await this.getBlockFromBlockStorage(blockNumber, blockStorage);
|
|
626
653
|
if (block) {
|
|
@@ -629,12 +656,38 @@ export class BlockStore {
|
|
|
629
656
|
}
|
|
630
657
|
}
|
|
631
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
|
+
|
|
632
685
|
/**
|
|
633
686
|
* Gets an L2 block.
|
|
634
687
|
* @param blockNumber - The number of the block to return.
|
|
635
688
|
* @returns The requested L2 block.
|
|
636
689
|
*/
|
|
637
|
-
async getBlock(blockNumber: BlockNumber): Promise<
|
|
690
|
+
async getBlock(blockNumber: BlockNumber): Promise<L2Block | undefined> {
|
|
638
691
|
const blockStorage = await this.#blocks.getAsync(blockNumber);
|
|
639
692
|
if (!blockStorage || !blockStorage.header) {
|
|
640
693
|
return Promise.resolve(undefined);
|
|
@@ -647,7 +700,7 @@ export class BlockStore {
|
|
|
647
700
|
* @param blockHash - The hash of the block to return.
|
|
648
701
|
* @returns The requested L2 block.
|
|
649
702
|
*/
|
|
650
|
-
async getBlockByHash(blockHash:
|
|
703
|
+
async getBlockByHash(blockHash: BlockHash): Promise<L2Block | undefined> {
|
|
651
704
|
const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
|
|
652
705
|
if (blockNumber === undefined) {
|
|
653
706
|
return undefined;
|
|
@@ -660,7 +713,7 @@ export class BlockStore {
|
|
|
660
713
|
* @param archive - The archive root of the block to return.
|
|
661
714
|
* @returns The requested L2 block.
|
|
662
715
|
*/
|
|
663
|
-
async getBlockByArchive(archive: Fr): Promise<
|
|
716
|
+
async getBlockByArchive(archive: Fr): Promise<L2Block | undefined> {
|
|
664
717
|
const blockNumber = await this.#blockArchiveIndex.getAsync(archive.toString());
|
|
665
718
|
if (blockNumber === undefined) {
|
|
666
719
|
return undefined;
|
|
@@ -673,7 +726,7 @@ export class BlockStore {
|
|
|
673
726
|
* @param blockHash - The hash of the block to return.
|
|
674
727
|
* @returns The requested block header.
|
|
675
728
|
*/
|
|
676
|
-
async getBlockHeaderByHash(blockHash:
|
|
729
|
+
async getBlockHeaderByHash(blockHash: BlockHash): Promise<BlockHeader | undefined> {
|
|
677
730
|
const blockNumber = await this.#blockHashIndex.getAsync(blockHash.toString());
|
|
678
731
|
if (blockNumber === undefined) {
|
|
679
732
|
return undefined;
|
|
@@ -733,15 +786,24 @@ export class BlockStore {
|
|
|
733
786
|
}
|
|
734
787
|
}
|
|
735
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
|
+
|
|
736
799
|
private async getBlockFromBlockStorage(
|
|
737
800
|
blockNumber: number,
|
|
738
801
|
blockStorage: BlockStorage,
|
|
739
|
-
): Promise<
|
|
740
|
-
const header =
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
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);
|
|
745
807
|
const blockTxsBuffer = await this.#blockTxs.getAsync(blockHashString);
|
|
746
808
|
if (blockTxsBuffer === undefined) {
|
|
747
809
|
this.#log.warn(`Could not find body for block ${header.globalVariables.blockNumber} ${blockHash}`);
|
|
@@ -760,13 +822,7 @@ export class BlockStore {
|
|
|
760
822
|
txEffects.push(deserializeIndexedTxEffect(txEffect).data);
|
|
761
823
|
}
|
|
762
824
|
const body = new Body(txEffects);
|
|
763
|
-
const block = new
|
|
764
|
-
archive,
|
|
765
|
-
header,
|
|
766
|
-
body,
|
|
767
|
-
CheckpointNumber(blockStorage.checkpointNumber!),
|
|
768
|
-
IndexWithinCheckpoint(blockStorage.indexWithinCheckpoint),
|
|
769
|
-
);
|
|
825
|
+
const block = new L2Block(archive, header, body, checkpointNumber, indexWithinCheckpoint);
|
|
770
826
|
|
|
771
827
|
if (block.number !== blockNumber) {
|
|
772
828
|
throw new Error(
|
|
@@ -796,19 +852,48 @@ export class BlockStore {
|
|
|
796
852
|
* @param txHash - The hash of a tx we try to get the receipt for.
|
|
797
853
|
* @returns The requested tx receipt (or undefined if not found).
|
|
798
854
|
*/
|
|
799
|
-
async getSettledTxReceipt(
|
|
855
|
+
async getSettledTxReceipt(
|
|
856
|
+
txHash: TxHash,
|
|
857
|
+
l1Constants?: Pick<L1RollupConstants, 'epochDuration'>,
|
|
858
|
+
): Promise<TxReceipt | undefined> {
|
|
800
859
|
const txEffect = await this.getTxEffect(txHash);
|
|
801
860
|
if (!txEffect) {
|
|
802
861
|
return undefined;
|
|
803
862
|
}
|
|
804
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
|
+
|
|
805
888
|
return new TxReceipt(
|
|
806
889
|
txHash,
|
|
807
|
-
|
|
808
|
-
|
|
890
|
+
status,
|
|
891
|
+
TxReceipt.executionResultFromRevertCode(txEffect.data.revertCode),
|
|
892
|
+
undefined,
|
|
809
893
|
txEffect.data.transactionFee.toBigInt(),
|
|
810
894
|
txEffect.l2BlockHash,
|
|
811
|
-
|
|
895
|
+
blockNumber,
|
|
896
|
+
epochNumber,
|
|
812
897
|
);
|
|
813
898
|
}
|
|
814
899
|
|
|
@@ -845,7 +930,7 @@ export class BlockStore {
|
|
|
845
930
|
if (!checkpoint) {
|
|
846
931
|
return BlockNumber(INITIAL_L2_BLOCK_NUM - 1);
|
|
847
932
|
}
|
|
848
|
-
return BlockNumber(checkpoint.startBlock + checkpoint.
|
|
933
|
+
return BlockNumber(checkpoint.startBlock + checkpoint.blockCount - 1);
|
|
849
934
|
}
|
|
850
935
|
|
|
851
936
|
async getLatestL2BlockNumber(): Promise<BlockNumber> {
|
|
@@ -880,6 +965,20 @@ export class BlockStore {
|
|
|
880
965
|
return result;
|
|
881
966
|
}
|
|
882
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
|
+
|
|
883
982
|
#computeBlockRange(start: BlockNumber, limit: number): Required<Pick<Range<number>, 'start' | 'limit'>> {
|
|
884
983
|
if (limit < 1) {
|
|
885
984
|
throw new Error(`Invalid limit: ${limit}`);
|