@aztec/validator-client 0.0.1-commit.96bb3f7 → 0.0.1-commit.96dac018d
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 +53 -24
- package/dest/block_proposal_handler.d.ts +9 -9
- package/dest/block_proposal_handler.d.ts.map +1 -1
- package/dest/block_proposal_handler.js +35 -54
- package/dest/checkpoint_builder.d.ts +24 -25
- package/dest/checkpoint_builder.d.ts.map +1 -1
- package/dest/checkpoint_builder.js +62 -37
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +12 -14
- package/dest/duties/validation_service.d.ts +20 -7
- package/dest/duties/validation_service.d.ts.map +1 -1
- package/dest/duties/validation_service.js +75 -22
- package/dest/factory.d.ts +2 -2
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +1 -1
- package/dest/index.d.ts +1 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +0 -1
- package/dest/key_store/ha_key_store.d.ts +99 -0
- package/dest/key_store/ha_key_store.d.ts.map +1 -0
- package/dest/key_store/ha_key_store.js +208 -0
- package/dest/key_store/index.d.ts +2 -1
- package/dest/key_store/index.d.ts.map +1 -1
- package/dest/key_store/index.js +1 -0
- package/dest/key_store/interface.d.ts +36 -6
- package/dest/key_store/interface.d.ts.map +1 -1
- package/dest/key_store/local_key_store.d.ts +10 -5
- package/dest/key_store/local_key_store.d.ts.map +1 -1
- package/dest/key_store/local_key_store.js +8 -4
- package/dest/key_store/node_keystore_adapter.d.ts +18 -5
- package/dest/key_store/node_keystore_adapter.d.ts.map +1 -1
- package/dest/key_store/node_keystore_adapter.js +18 -4
- package/dest/key_store/web3signer_key_store.d.ts +10 -5
- package/dest/key_store/web3signer_key_store.d.ts.map +1 -1
- package/dest/key_store/web3signer_key_store.js +8 -4
- package/dest/metrics.d.ts +4 -3
- package/dest/metrics.d.ts.map +1 -1
- package/dest/metrics.js +34 -5
- package/dest/validator.d.ts +43 -18
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +233 -94
- package/package.json +21 -17
- package/src/block_proposal_handler.ts +48 -69
- package/src/checkpoint_builder.ts +104 -43
- package/src/config.ts +11 -13
- package/src/duties/validation_service.ts +100 -25
- package/src/factory.ts +1 -0
- package/src/index.ts +0 -1
- package/src/key_store/ha_key_store.ts +269 -0
- package/src/key_store/index.ts +1 -0
- package/src/key_store/interface.ts +44 -5
- package/src/key_store/local_key_store.ts +13 -4
- package/src/key_store/node_keystore_adapter.ts +27 -4
- package/src/key_store/web3signer_key_store.ts +17 -4
- package/src/metrics.ts +45 -6
- package/src/validator.ts +303 -114
- package/dest/tx_validator/index.d.ts +0 -3
- package/dest/tx_validator/index.d.ts.map +0 -1
- package/dest/tx_validator/index.js +0 -2
- package/dest/tx_validator/nullifier_cache.d.ts +0 -14
- package/dest/tx_validator/nullifier_cache.d.ts.map +0 -1
- package/dest/tx_validator/nullifier_cache.js +0 -24
- package/dest/tx_validator/tx_validator_factory.d.ts +0 -18
- package/dest/tx_validator/tx_validator_factory.d.ts.map +0 -1
- package/dest/tx_validator/tx_validator_factory.js +0 -53
- package/src/tx_validator/index.ts +0 -2
- package/src/tx_validator/nullifier_cache.ts +0 -30
- package/src/tx_validator/tx_validator_factory.ts +0 -133
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
|
|
2
|
+
import type { EpochCache } from '@aztec/epoch-cache';
|
|
2
3
|
import { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
3
4
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
5
|
import { TimeoutError } from '@aztec/foundation/error';
|
|
@@ -6,14 +7,13 @@ import { createLogger } from '@aztec/foundation/log';
|
|
|
6
7
|
import { retryUntil } from '@aztec/foundation/retry';
|
|
7
8
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
8
9
|
import type { P2P, PeerId } from '@aztec/p2p';
|
|
9
|
-
import { TxProvider } from '@aztec/p2p';
|
|
10
10
|
import { BlockProposalValidator } from '@aztec/p2p/msg_validators';
|
|
11
|
-
import type {
|
|
12
|
-
import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
13
|
-
import type { ValidatorClientFullConfig, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
|
|
11
|
+
import type { BlockData, L2Block, L2BlockSink, L2BlockSource } from '@aztec/stdlib/block';
|
|
12
|
+
import { getEpochAtSlot, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
13
|
+
import type { ITxProvider, ValidatorClientFullConfig, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
|
|
14
14
|
import { type L1ToL2MessageSource, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
|
|
15
15
|
import type { BlockProposal } from '@aztec/stdlib/p2p';
|
|
16
|
-
import {
|
|
16
|
+
import type { CheckpointGlobalVariables, FailedTx, Tx } from '@aztec/stdlib/tx';
|
|
17
17
|
import {
|
|
18
18
|
ReExFailedTxsError,
|
|
19
19
|
ReExStateMismatchError,
|
|
@@ -39,7 +39,7 @@ export type BlockProposalValidationFailureReason =
|
|
|
39
39
|
| 'unknown_error';
|
|
40
40
|
|
|
41
41
|
type ReexecuteTransactionsResult = {
|
|
42
|
-
block:
|
|
42
|
+
block: L2Block;
|
|
43
43
|
failedTxs: FailedTx[];
|
|
44
44
|
reexecutionTimeMs: number;
|
|
45
45
|
totalManaUsed: number;
|
|
@@ -72,8 +72,9 @@ export class BlockProposalHandler {
|
|
|
72
72
|
private worldState: WorldStateSynchronizer,
|
|
73
73
|
private blockSource: L2BlockSource & L2BlockSink,
|
|
74
74
|
private l1ToL2MessageSource: L1ToL2MessageSource,
|
|
75
|
-
private txProvider:
|
|
75
|
+
private txProvider: ITxProvider,
|
|
76
76
|
private blockProposalValidator: BlockProposalValidator,
|
|
77
|
+
private epochCache: EpochCache,
|
|
77
78
|
private config: ValidatorClientFullConfig,
|
|
78
79
|
private metrics?: ValidatorMetrics,
|
|
79
80
|
private dateProvider: DateProvider = new DateProvider(),
|
|
@@ -140,23 +141,23 @@ export class BlockProposalHandler {
|
|
|
140
141
|
|
|
141
142
|
// Check that the proposal is from the current proposer, or the next proposer
|
|
142
143
|
// This should have been handled by the p2p layer, but we double check here out of caution
|
|
143
|
-
const
|
|
144
|
-
if (
|
|
144
|
+
const validationResult = await this.blockProposalValidator.validate(proposal);
|
|
145
|
+
if (validationResult.result !== 'accept') {
|
|
145
146
|
this.log.warn(`Proposal is not valid, skipping processing`, proposalInfo);
|
|
146
147
|
return { isValid: false, reason: 'invalid_proposal' };
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
// Check that the parent proposal is a block we know, otherwise reexecution would fail
|
|
150
|
-
const
|
|
151
|
-
if (
|
|
151
|
+
const parentBlock = await this.getParentBlock(proposal);
|
|
152
|
+
if (parentBlock === undefined) {
|
|
152
153
|
this.log.warn(`Parent block for proposal not found, skipping processing`, proposalInfo);
|
|
153
154
|
return { isValid: false, reason: 'parent_block_not_found' };
|
|
154
155
|
}
|
|
155
156
|
|
|
156
|
-
// Check that the parent block's slot is
|
|
157
|
-
if (
|
|
158
|
-
this.log.warn(`Parent block slot is greater than
|
|
159
|
-
parentBlockSlot:
|
|
157
|
+
// Check that the parent block's slot is not greater than the proposal's slot.
|
|
158
|
+
if (parentBlock !== 'genesis' && parentBlock.header.getSlot() > slotNumber) {
|
|
159
|
+
this.log.warn(`Parent block slot is greater than proposal slot, skipping processing`, {
|
|
160
|
+
parentBlockSlot: parentBlock.header.getSlot().toString(),
|
|
160
161
|
proposalSlot: slotNumber.toString(),
|
|
161
162
|
...proposalInfo,
|
|
162
163
|
});
|
|
@@ -165,9 +166,9 @@ export class BlockProposalHandler {
|
|
|
165
166
|
|
|
166
167
|
// Compute the block number based on the parent block
|
|
167
168
|
const blockNumber =
|
|
168
|
-
|
|
169
|
+
parentBlock === 'genesis'
|
|
169
170
|
? BlockNumber(INITIAL_L2_BLOCK_NUM)
|
|
170
|
-
: BlockNumber(
|
|
171
|
+
: BlockNumber(parentBlock.header.getBlockNumber() + 1);
|
|
171
172
|
|
|
172
173
|
// Check that this block number does not exist already
|
|
173
174
|
const existingBlock = await this.blockSource.getBlockHeader(blockNumber);
|
|
@@ -184,7 +185,7 @@ export class BlockProposalHandler {
|
|
|
184
185
|
});
|
|
185
186
|
|
|
186
187
|
// Compute the checkpoint number for this block and validate checkpoint consistency
|
|
187
|
-
const checkpointResult =
|
|
188
|
+
const checkpointResult = this.computeCheckpointNumber(proposal, parentBlock, proposalInfo);
|
|
188
189
|
if (checkpointResult.reason) {
|
|
189
190
|
return { isValid: false, blockNumber, reason: checkpointResult.reason };
|
|
190
191
|
}
|
|
@@ -212,6 +213,12 @@ export class BlockProposalHandler {
|
|
|
212
213
|
// Try re-executing the transactions in the proposal if needed
|
|
213
214
|
let reexecutionResult;
|
|
214
215
|
if (shouldReexecute) {
|
|
216
|
+
// Collect the out hashes of all the checkpoints before this one in the same epoch
|
|
217
|
+
const epoch = getEpochAtSlot(slotNumber, this.epochCache.getL1Constants());
|
|
218
|
+
const previousCheckpointOutHashes = (await this.blockSource.getCheckpointsDataForEpoch(epoch))
|
|
219
|
+
.filter(c => c.checkpointNumber < checkpointNumber)
|
|
220
|
+
.map(c => c.checkpointOutHash);
|
|
221
|
+
|
|
215
222
|
try {
|
|
216
223
|
this.log.verbose(`Re-executing transactions in the proposal`, proposalInfo);
|
|
217
224
|
reexecutionResult = await this.reexecuteTransactions(
|
|
@@ -220,6 +227,7 @@ export class BlockProposalHandler {
|
|
|
220
227
|
checkpointNumber,
|
|
221
228
|
txs,
|
|
222
229
|
l1ToL2Messages,
|
|
230
|
+
previousCheckpointOutHashes,
|
|
223
231
|
);
|
|
224
232
|
} catch (error) {
|
|
225
233
|
this.log.error(`Error reexecuting txs while processing block proposal`, error, proposalInfo);
|
|
@@ -229,7 +237,6 @@ export class BlockProposalHandler {
|
|
|
229
237
|
}
|
|
230
238
|
|
|
231
239
|
// If we succeeded, push this block into the archiver (unless disabled)
|
|
232
|
-
// TODO(palla/mbps): Change default to false once block sync is stable.
|
|
233
240
|
if (reexecutionResult?.block && this.config.skipPushProposedBlocksToArchiver === false) {
|
|
234
241
|
await this.blockSource.addBlock(reexecutionResult?.block);
|
|
235
242
|
}
|
|
@@ -242,7 +249,7 @@ export class BlockProposalHandler {
|
|
|
242
249
|
return { isValid: true, blockNumber, reexecutionResult };
|
|
243
250
|
}
|
|
244
251
|
|
|
245
|
-
private async getParentBlock(proposal: BlockProposal): Promise<'genesis' |
|
|
252
|
+
private async getParentBlock(proposal: BlockProposal): Promise<'genesis' | BlockData | undefined> {
|
|
246
253
|
const parentArchive = proposal.blockHeader.lastArchive.root;
|
|
247
254
|
const slot = proposal.slotNumber;
|
|
248
255
|
const config = this.checkpointsBuilder.getConfig();
|
|
@@ -258,12 +265,11 @@ export class BlockProposalHandler {
|
|
|
258
265
|
|
|
259
266
|
try {
|
|
260
267
|
return (
|
|
261
|
-
(await this.blockSource.
|
|
268
|
+
(await this.blockSource.getBlockDataByArchive(parentArchive)) ??
|
|
262
269
|
(timeoutDurationMs <= 0
|
|
263
270
|
? undefined
|
|
264
271
|
: await retryUntil(
|
|
265
|
-
() =>
|
|
266
|
-
this.blockSource.syncImmediate().then(() => this.blockSource.getBlockHeaderByArchive(parentArchive)),
|
|
272
|
+
() => this.blockSource.syncImmediate().then(() => this.blockSource.getBlockDataByArchive(parentArchive)),
|
|
267
273
|
'force archiver sync',
|
|
268
274
|
timeoutDurationMs / 1000,
|
|
269
275
|
0.5,
|
|
@@ -279,12 +285,12 @@ export class BlockProposalHandler {
|
|
|
279
285
|
}
|
|
280
286
|
}
|
|
281
287
|
|
|
282
|
-
private
|
|
288
|
+
private computeCheckpointNumber(
|
|
283
289
|
proposal: BlockProposal,
|
|
284
|
-
|
|
290
|
+
parentBlock: 'genesis' | BlockData,
|
|
285
291
|
proposalInfo: object,
|
|
286
|
-
):
|
|
287
|
-
if (
|
|
292
|
+
): CheckpointComputationResult {
|
|
293
|
+
if (parentBlock === 'genesis') {
|
|
288
294
|
// First block is in checkpoint 1
|
|
289
295
|
if (proposal.indexWithinCheckpoint !== 0) {
|
|
290
296
|
this.log.warn(`First block proposal has non-zero indexWithinCheckpoint`, proposalInfo);
|
|
@@ -293,19 +299,9 @@ export class BlockProposalHandler {
|
|
|
293
299
|
return { checkpointNumber: CheckpointNumber.INITIAL };
|
|
294
300
|
}
|
|
295
301
|
|
|
296
|
-
// Get the parent block to find its checkpoint number
|
|
297
|
-
// TODO(palla/mbps): The block header should include the checkpoint number to avoid this lookup,
|
|
298
|
-
// or at least the L2BlockSource should return a different struct that includes it.
|
|
299
|
-
const parentBlockNumber = parentBlockHeader.getBlockNumber();
|
|
300
|
-
const parentBlock = await this.blockSource.getL2BlockNew(parentBlockNumber);
|
|
301
|
-
if (!parentBlock) {
|
|
302
|
-
this.log.warn(`Parent block ${parentBlockNumber} not found in archiver`, proposalInfo);
|
|
303
|
-
return { reason: 'invalid_proposal' };
|
|
304
|
-
}
|
|
305
|
-
|
|
306
302
|
if (proposal.indexWithinCheckpoint === 0) {
|
|
307
303
|
// If this is the first block in a new checkpoint, increment the checkpoint number
|
|
308
|
-
if (!(proposal.blockHeader.getSlot() >
|
|
304
|
+
if (!(proposal.blockHeader.getSlot() > parentBlock.header.getSlot())) {
|
|
309
305
|
this.log.warn(`Slot should be greater than parent block slot for first block in checkpoint`, proposalInfo);
|
|
310
306
|
return { reason: 'invalid_proposal' };
|
|
311
307
|
}
|
|
@@ -317,7 +313,7 @@ export class BlockProposalHandler {
|
|
|
317
313
|
this.log.warn(`Non-sequential indexWithinCheckpoint`, proposalInfo);
|
|
318
314
|
return { reason: 'invalid_proposal' };
|
|
319
315
|
}
|
|
320
|
-
if (proposal.blockHeader.getSlot() !==
|
|
316
|
+
if (proposal.blockHeader.getSlot() !== parentBlock.header.getSlot()) {
|
|
321
317
|
this.log.warn(`Slot should be equal to parent block slot for non-first block in checkpoint`, proposalInfo);
|
|
322
318
|
return { reason: 'invalid_proposal' };
|
|
323
319
|
}
|
|
@@ -338,7 +334,7 @@ export class BlockProposalHandler {
|
|
|
338
334
|
*/
|
|
339
335
|
private validateNonFirstBlockInCheckpoint(
|
|
340
336
|
proposal: BlockProposal,
|
|
341
|
-
parentBlock:
|
|
337
|
+
parentBlock: BlockData,
|
|
342
338
|
proposalInfo: object,
|
|
343
339
|
): CheckpointComputationResult | undefined {
|
|
344
340
|
const proposalGlobals = proposal.blockHeader.globalVariables;
|
|
@@ -414,31 +410,7 @@ export class BlockProposalHandler {
|
|
|
414
410
|
|
|
415
411
|
private getReexecutionDeadline(slot: SlotNumber, config: { l1GenesisTime: bigint; slotDuration: number }): Date {
|
|
416
412
|
const nextSlotTimestampSeconds = Number(getTimestampForSlot(SlotNumber(slot + 1), config));
|
|
417
|
-
|
|
418
|
-
return new Date(nextSlotTimestampSeconds * 1000 - msNeededForPropagationAndPublishing);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* Gets all prior blocks in the same checkpoint (same slot and checkpoint number) up to but not including upToBlockNumber.
|
|
423
|
-
*/
|
|
424
|
-
private async getBlocksInCheckpoint(
|
|
425
|
-
slot: SlotNumber,
|
|
426
|
-
upToBlockNumber: BlockNumber,
|
|
427
|
-
checkpointNumber: CheckpointNumber,
|
|
428
|
-
): Promise<L2BlockNew[]> {
|
|
429
|
-
const blocks: L2BlockNew[] = [];
|
|
430
|
-
let currentBlockNumber = BlockNumber(upToBlockNumber - 1);
|
|
431
|
-
|
|
432
|
-
while (currentBlockNumber >= INITIAL_L2_BLOCK_NUM) {
|
|
433
|
-
const block = await this.blockSource.getL2BlockNew(currentBlockNumber);
|
|
434
|
-
if (!block || block.header.getSlot() !== slot || block.checkpointNumber !== checkpointNumber) {
|
|
435
|
-
break;
|
|
436
|
-
}
|
|
437
|
-
blocks.unshift(block);
|
|
438
|
-
currentBlockNumber = BlockNumber(currentBlockNumber - 1);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
return blocks;
|
|
413
|
+
return new Date(nextSlotTimestampSeconds * 1000);
|
|
442
414
|
}
|
|
443
415
|
|
|
444
416
|
private getReexecuteFailureReason(err: any) {
|
|
@@ -459,6 +431,7 @@ export class BlockProposalHandler {
|
|
|
459
431
|
checkpointNumber: CheckpointNumber,
|
|
460
432
|
txs: Tx[],
|
|
461
433
|
l1ToL2Messages: Fr[],
|
|
434
|
+
previousCheckpointOutHashes: Fr[],
|
|
462
435
|
): Promise<ReexecuteTransactionsResult> {
|
|
463
436
|
const { blockHeader, txHashes } = proposal;
|
|
464
437
|
|
|
@@ -473,18 +446,21 @@ export class BlockProposalHandler {
|
|
|
473
446
|
const slot = proposal.slotNumber;
|
|
474
447
|
const config = this.checkpointsBuilder.getConfig();
|
|
475
448
|
|
|
476
|
-
// Get prior blocks in this checkpoint (same slot
|
|
477
|
-
const
|
|
449
|
+
// Get prior blocks in this checkpoint (same slot before current block)
|
|
450
|
+
const allBlocksInSlot = await this.blockSource.getBlocksForSlot(slot);
|
|
451
|
+
const priorBlocks = allBlocksInSlot.filter(b => b.number < blockNumber && b.header.getSlot() === slot);
|
|
478
452
|
|
|
479
453
|
// Fork before the block to be built
|
|
480
454
|
const parentBlockNumber = BlockNumber(blockNumber - 1);
|
|
481
|
-
|
|
455
|
+
await this.worldState.syncImmediate(parentBlockNumber);
|
|
456
|
+
await using fork = await this.worldState.fork(parentBlockNumber);
|
|
482
457
|
|
|
483
|
-
// Build checkpoint constants from proposal (excludes blockNumber
|
|
458
|
+
// Build checkpoint constants from proposal (excludes blockNumber which is per-block)
|
|
484
459
|
const constants: CheckpointGlobalVariables = {
|
|
485
460
|
chainId: new Fr(config.l1ChainId),
|
|
486
461
|
version: new Fr(config.rollupVersion),
|
|
487
462
|
slotNumber: slot,
|
|
463
|
+
timestamp: blockHeader.globalVariables.timestamp,
|
|
488
464
|
coinbase: blockHeader.globalVariables.coinbase,
|
|
489
465
|
feeRecipient: blockHeader.globalVariables.feeRecipient,
|
|
490
466
|
gasFees: blockHeader.globalVariables.gasFees,
|
|
@@ -494,9 +470,12 @@ export class BlockProposalHandler {
|
|
|
494
470
|
const checkpointBuilder = await this.checkpointsBuilder.openCheckpoint(
|
|
495
471
|
checkpointNumber,
|
|
496
472
|
constants,
|
|
473
|
+
0n, // only takes effect in the following checkpoint.
|
|
497
474
|
l1ToL2Messages,
|
|
475
|
+
previousCheckpointOutHashes,
|
|
498
476
|
fork,
|
|
499
477
|
priorBlocks,
|
|
478
|
+
this.log.getBindings(),
|
|
500
479
|
);
|
|
501
480
|
|
|
502
481
|
// Build the new block
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
2
2
|
import { merge, pick } from '@aztec/foundation/collection';
|
|
3
3
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
|
-
import { createLogger } from '@aztec/foundation/log';
|
|
4
|
+
import { type Logger, type LoggerBindings, createLogger } from '@aztec/foundation/log';
|
|
5
5
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
6
|
-
import { DateProvider,
|
|
7
|
-
import { getDefaultAllowedSetupFunctions } from '@aztec/p2p/msg_validators';
|
|
6
|
+
import { DateProvider, elapsed } from '@aztec/foundation/timer';
|
|
7
|
+
import { createTxValidatorForBlockBuilding, getDefaultAllowedSetupFunctions } from '@aztec/p2p/msg_validators';
|
|
8
8
|
import { LightweightCheckpointBuilder } from '@aztec/prover-client/light';
|
|
9
9
|
import {
|
|
10
10
|
GuardedMerkleTreeOperations,
|
|
@@ -12,39 +12,37 @@ import {
|
|
|
12
12
|
PublicProcessor,
|
|
13
13
|
createPublicTxSimulatorForBlockBuilding,
|
|
14
14
|
} from '@aztec/simulator/server';
|
|
15
|
-
import {
|
|
15
|
+
import { L2Block } from '@aztec/stdlib/block';
|
|
16
16
|
import { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
17
17
|
import type { ContractDataSource } from '@aztec/stdlib/contract';
|
|
18
|
+
import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
18
19
|
import { Gas } from '@aztec/stdlib/gas';
|
|
19
20
|
import {
|
|
21
|
+
type BuildBlockInCheckpointResult,
|
|
20
22
|
type FullNodeBlockBuilderConfig,
|
|
21
23
|
FullNodeBlockBuilderConfigKeys,
|
|
24
|
+
type ICheckpointBlockBuilder,
|
|
25
|
+
type ICheckpointsBuilder,
|
|
22
26
|
type MerkleTreeWriteOperations,
|
|
27
|
+
NoValidTxsError,
|
|
23
28
|
type PublicProcessorLimits,
|
|
29
|
+
type WorldStateSynchronizer,
|
|
24
30
|
} from '@aztec/stdlib/interfaces/server';
|
|
31
|
+
import { type DebugLogStore, NullDebugLogStore } from '@aztec/stdlib/logs';
|
|
25
32
|
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
26
|
-
import { type CheckpointGlobalVariables,
|
|
33
|
+
import { type CheckpointGlobalVariables, GlobalVariables, StateReference, Tx } from '@aztec/stdlib/tx';
|
|
27
34
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
28
35
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const log = createLogger('checkpoint-builder');
|
|
32
|
-
|
|
33
|
-
export interface BuildBlockInCheckpointResult {
|
|
34
|
-
block: L2BlockNew;
|
|
35
|
-
publicGas: Gas;
|
|
36
|
-
publicProcessorDuration: number;
|
|
37
|
-
numTxs: number;
|
|
38
|
-
failedTxs: FailedTx[];
|
|
39
|
-
blockBuildingTimer: Timer;
|
|
40
|
-
usedTxs: Tx[];
|
|
41
|
-
}
|
|
36
|
+
// Re-export for backward compatibility
|
|
37
|
+
export type { BuildBlockInCheckpointResult } from '@aztec/stdlib/interfaces/server';
|
|
42
38
|
|
|
43
39
|
/**
|
|
44
40
|
* Builder for a single checkpoint. Handles building blocks within the checkpoint
|
|
45
41
|
* and completing it.
|
|
46
42
|
*/
|
|
47
|
-
export class CheckpointBuilder {
|
|
43
|
+
export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
44
|
+
private log: Logger;
|
|
45
|
+
|
|
48
46
|
constructor(
|
|
49
47
|
private checkpointBuilder: LightweightCheckpointBuilder,
|
|
50
48
|
private fork: MerkleTreeWriteOperations,
|
|
@@ -52,7 +50,14 @@ export class CheckpointBuilder {
|
|
|
52
50
|
private contractDataSource: ContractDataSource,
|
|
53
51
|
private dateProvider: DateProvider,
|
|
54
52
|
private telemetryClient: TelemetryClient,
|
|
55
|
-
|
|
53
|
+
bindings?: LoggerBindings,
|
|
54
|
+
private debugLogStore: DebugLogStore = new NullDebugLogStore(),
|
|
55
|
+
) {
|
|
56
|
+
this.log = createLogger('checkpoint-builder', {
|
|
57
|
+
...bindings,
|
|
58
|
+
instanceId: `checkpoint-${checkpointBuilder.checkpointNumber}`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
56
61
|
|
|
57
62
|
getConstantData(): CheckpointGlobalVariables {
|
|
58
63
|
return this.checkpointBuilder.constants;
|
|
@@ -65,12 +70,16 @@ export class CheckpointBuilder {
|
|
|
65
70
|
pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
|
|
66
71
|
blockNumber: BlockNumber,
|
|
67
72
|
timestamp: bigint,
|
|
68
|
-
opts: PublicProcessorLimits & { expectedEndState?: StateReference },
|
|
73
|
+
opts: PublicProcessorLimits & { expectedEndState?: StateReference } = {},
|
|
69
74
|
): Promise<BuildBlockInCheckpointResult> {
|
|
70
|
-
const blockBuildingTimer = new Timer();
|
|
71
75
|
const slot = this.checkpointBuilder.constants.slotNumber;
|
|
72
76
|
|
|
73
|
-
log.verbose(`Building block ${blockNumber} for slot ${slot} within checkpoint`, {
|
|
77
|
+
this.log.verbose(`Building block ${blockNumber} for slot ${slot} within checkpoint`, {
|
|
78
|
+
slot,
|
|
79
|
+
blockNumber,
|
|
80
|
+
...opts,
|
|
81
|
+
currentTime: new Date(this.dateProvider.now()),
|
|
82
|
+
});
|
|
74
83
|
|
|
75
84
|
const constants = this.checkpointBuilder.constants;
|
|
76
85
|
const globalVariables = GlobalVariables.from({
|
|
@@ -85,36 +94,46 @@ export class CheckpointBuilder {
|
|
|
85
94
|
});
|
|
86
95
|
const { processor, validator } = await this.makeBlockBuilderDeps(globalVariables, this.fork);
|
|
87
96
|
|
|
88
|
-
const [publicProcessorDuration, [processedTxs, failedTxs, usedTxs]] = await elapsed(() =>
|
|
97
|
+
const [publicProcessorDuration, [processedTxs, failedTxs, usedTxs, _, usedTxBlobFields]] = await elapsed(() =>
|
|
89
98
|
processor.process(pendingTxs, opts, validator),
|
|
90
99
|
);
|
|
91
100
|
|
|
101
|
+
// Throw if we didn't collect a single valid tx and we're not allowed to build empty blocks
|
|
102
|
+
// (only the first block in a checkpoint can be empty)
|
|
103
|
+
if (processedTxs.length === 0 && this.checkpointBuilder.getBlockCount() > 0) {
|
|
104
|
+
throw new NoValidTxsError(failedTxs);
|
|
105
|
+
}
|
|
106
|
+
|
|
92
107
|
// Add block to checkpoint
|
|
93
|
-
const block = await this.checkpointBuilder.addBlock(globalVariables, processedTxs, {
|
|
108
|
+
const { block } = await this.checkpointBuilder.addBlock(globalVariables, processedTxs, {
|
|
94
109
|
expectedEndState: opts.expectedEndState,
|
|
95
110
|
});
|
|
96
111
|
|
|
97
112
|
// How much public gas was processed
|
|
98
113
|
const publicGas = processedTxs.reduce((acc, tx) => acc.add(tx.gasUsed.publicGas), Gas.empty());
|
|
99
114
|
|
|
100
|
-
|
|
115
|
+
this.log.debug('Built block within checkpoint', {
|
|
116
|
+
header: block.header.toInspect(),
|
|
117
|
+
processedTxs: processedTxs.map(tx => tx.hash.toString()),
|
|
118
|
+
failedTxs: failedTxs.map(tx => tx.tx.txHash.toString()),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return {
|
|
101
122
|
block,
|
|
102
123
|
publicGas,
|
|
103
124
|
publicProcessorDuration,
|
|
104
125
|
numTxs: processedTxs.length,
|
|
105
126
|
failedTxs,
|
|
106
|
-
blockBuildingTimer,
|
|
107
127
|
usedTxs,
|
|
128
|
+
usedTxBlobFields,
|
|
108
129
|
};
|
|
109
|
-
log.debug('Built block within checkpoint', res.block.header);
|
|
110
|
-
return res;
|
|
111
130
|
}
|
|
112
131
|
|
|
113
132
|
/** Completes the checkpoint and returns it. */
|
|
114
133
|
async completeCheckpoint(): Promise<Checkpoint> {
|
|
115
134
|
const checkpoint = await this.checkpointBuilder.completeCheckpoint();
|
|
116
135
|
|
|
117
|
-
log.verbose(`Completed checkpoint ${checkpoint.number}`, {
|
|
136
|
+
this.log.verbose(`Completed checkpoint ${checkpoint.number}`, {
|
|
118
137
|
checkpointNumber: checkpoint.number,
|
|
119
138
|
numBlocks: checkpoint.blocks.length,
|
|
120
139
|
archiveRoot: checkpoint.archive.root.toString(),
|
|
@@ -130,14 +149,19 @@ export class CheckpointBuilder {
|
|
|
130
149
|
|
|
131
150
|
protected async makeBlockBuilderDeps(globalVariables: GlobalVariables, fork: MerkleTreeWriteOperations) {
|
|
132
151
|
const txPublicSetupAllowList = this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions());
|
|
133
|
-
const contractsDB = new PublicContractsDB(this.contractDataSource);
|
|
152
|
+
const contractsDB = new PublicContractsDB(this.contractDataSource, this.log.getBindings());
|
|
134
153
|
const guardedFork = new GuardedMerkleTreeOperations(fork);
|
|
135
154
|
|
|
155
|
+
const collectDebugLogs = this.debugLogStore.isEnabled;
|
|
156
|
+
|
|
157
|
+
const bindings = this.log.getBindings();
|
|
136
158
|
const publicTxSimulator = createPublicTxSimulatorForBlockBuilding(
|
|
137
159
|
guardedFork,
|
|
138
160
|
contractsDB,
|
|
139
161
|
globalVariables,
|
|
140
162
|
this.telemetryClient,
|
|
163
|
+
bindings,
|
|
164
|
+
collectDebugLogs,
|
|
141
165
|
);
|
|
142
166
|
|
|
143
167
|
const processor = new PublicProcessor(
|
|
@@ -147,15 +171,17 @@ export class CheckpointBuilder {
|
|
|
147
171
|
publicTxSimulator,
|
|
148
172
|
this.dateProvider,
|
|
149
173
|
this.telemetryClient,
|
|
150
|
-
|
|
174
|
+
createLogger('simulator:public-processor', bindings),
|
|
151
175
|
this.config,
|
|
176
|
+
this.debugLogStore,
|
|
152
177
|
);
|
|
153
178
|
|
|
154
|
-
const validator =
|
|
179
|
+
const validator = createTxValidatorForBlockBuilding(
|
|
155
180
|
fork,
|
|
156
181
|
this.contractDataSource,
|
|
157
182
|
globalVariables,
|
|
158
183
|
txPublicSetupAllowList,
|
|
184
|
+
this.log.getBindings(),
|
|
159
185
|
);
|
|
160
186
|
|
|
161
187
|
return {
|
|
@@ -165,16 +191,20 @@ export class CheckpointBuilder {
|
|
|
165
191
|
}
|
|
166
192
|
}
|
|
167
193
|
|
|
168
|
-
/**
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
194
|
+
/** Factory for creating checkpoint builders. */
|
|
195
|
+
export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
196
|
+
private log: Logger;
|
|
197
|
+
|
|
172
198
|
constructor(
|
|
173
|
-
private config: FullNodeBlockBuilderConfig,
|
|
199
|
+
private config: FullNodeBlockBuilderConfig & Pick<L1RollupConstants, 'l1GenesisTime' | 'slotDuration'>,
|
|
200
|
+
private worldState: WorldStateSynchronizer,
|
|
174
201
|
private contractDataSource: ContractDataSource,
|
|
175
202
|
private dateProvider: DateProvider,
|
|
176
203
|
private telemetryClient: TelemetryClient = getTelemetryClient(),
|
|
177
|
-
|
|
204
|
+
private debugLogStore: DebugLogStore = new NullDebugLogStore(),
|
|
205
|
+
) {
|
|
206
|
+
this.log = createLogger('checkpoint-builder');
|
|
207
|
+
}
|
|
178
208
|
|
|
179
209
|
public getConfig(): FullNodeBlockBuilderConfig {
|
|
180
210
|
return this.config;
|
|
@@ -190,25 +220,32 @@ export class FullNodeCheckpointsBuilder {
|
|
|
190
220
|
async startCheckpoint(
|
|
191
221
|
checkpointNumber: CheckpointNumber,
|
|
192
222
|
constants: CheckpointGlobalVariables,
|
|
223
|
+
feeAssetPriceModifier: bigint,
|
|
193
224
|
l1ToL2Messages: Fr[],
|
|
225
|
+
previousCheckpointOutHashes: Fr[],
|
|
194
226
|
fork: MerkleTreeWriteOperations,
|
|
227
|
+
bindings?: LoggerBindings,
|
|
195
228
|
): Promise<CheckpointBuilder> {
|
|
196
229
|
const stateReference = await fork.getStateReference();
|
|
197
230
|
const archiveTree = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|
|
198
231
|
|
|
199
|
-
log.verbose(`Building new checkpoint ${checkpointNumber}`, {
|
|
232
|
+
this.log.verbose(`Building new checkpoint ${checkpointNumber}`, {
|
|
200
233
|
checkpointNumber,
|
|
201
234
|
msgCount: l1ToL2Messages.length,
|
|
202
235
|
initialStateReference: stateReference.toInspect(),
|
|
203
236
|
initialArchiveRoot: bufferToHex(archiveTree.root),
|
|
204
237
|
constants,
|
|
238
|
+
feeAssetPriceModifier,
|
|
205
239
|
});
|
|
206
240
|
|
|
207
241
|
const lightweightBuilder = await LightweightCheckpointBuilder.startNewCheckpoint(
|
|
208
242
|
checkpointNumber,
|
|
209
243
|
constants,
|
|
210
244
|
l1ToL2Messages,
|
|
245
|
+
previousCheckpointOutHashes,
|
|
211
246
|
fork,
|
|
247
|
+
bindings,
|
|
248
|
+
feeAssetPriceModifier,
|
|
212
249
|
);
|
|
213
250
|
|
|
214
251
|
return new CheckpointBuilder(
|
|
@@ -218,6 +255,8 @@ export class FullNodeCheckpointsBuilder {
|
|
|
218
255
|
this.contractDataSource,
|
|
219
256
|
this.dateProvider,
|
|
220
257
|
this.telemetryClient,
|
|
258
|
+
bindings,
|
|
259
|
+
this.debugLogStore,
|
|
221
260
|
);
|
|
222
261
|
}
|
|
223
262
|
|
|
@@ -227,32 +266,47 @@ export class FullNodeCheckpointsBuilder {
|
|
|
227
266
|
async openCheckpoint(
|
|
228
267
|
checkpointNumber: CheckpointNumber,
|
|
229
268
|
constants: CheckpointGlobalVariables,
|
|
269
|
+
feeAssetPriceModifier: bigint,
|
|
230
270
|
l1ToL2Messages: Fr[],
|
|
271
|
+
previousCheckpointOutHashes: Fr[],
|
|
231
272
|
fork: MerkleTreeWriteOperations,
|
|
232
|
-
existingBlocks:
|
|
273
|
+
existingBlocks: L2Block[] = [],
|
|
274
|
+
bindings?: LoggerBindings,
|
|
233
275
|
): Promise<CheckpointBuilder> {
|
|
234
276
|
const stateReference = await fork.getStateReference();
|
|
235
277
|
const archiveTree = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|
|
236
278
|
|
|
237
279
|
if (existingBlocks.length === 0) {
|
|
238
|
-
return this.startCheckpoint(
|
|
280
|
+
return this.startCheckpoint(
|
|
281
|
+
checkpointNumber,
|
|
282
|
+
constants,
|
|
283
|
+
feeAssetPriceModifier,
|
|
284
|
+
l1ToL2Messages,
|
|
285
|
+
previousCheckpointOutHashes,
|
|
286
|
+
fork,
|
|
287
|
+
bindings,
|
|
288
|
+
);
|
|
239
289
|
}
|
|
240
290
|
|
|
241
|
-
log.verbose(`Resuming checkpoint ${checkpointNumber} with ${existingBlocks.length} existing blocks`, {
|
|
291
|
+
this.log.verbose(`Resuming checkpoint ${checkpointNumber} with ${existingBlocks.length} existing blocks`, {
|
|
242
292
|
checkpointNumber,
|
|
243
293
|
msgCount: l1ToL2Messages.length,
|
|
244
294
|
existingBlockCount: existingBlocks.length,
|
|
245
295
|
initialStateReference: stateReference.toInspect(),
|
|
246
296
|
initialArchiveRoot: bufferToHex(archiveTree.root),
|
|
247
297
|
constants,
|
|
298
|
+
feeAssetPriceModifier,
|
|
248
299
|
});
|
|
249
300
|
|
|
250
301
|
const lightweightBuilder = await LightweightCheckpointBuilder.resumeCheckpoint(
|
|
251
302
|
checkpointNumber,
|
|
252
303
|
constants,
|
|
304
|
+
feeAssetPriceModifier,
|
|
253
305
|
l1ToL2Messages,
|
|
306
|
+
previousCheckpointOutHashes,
|
|
254
307
|
fork,
|
|
255
308
|
existingBlocks,
|
|
309
|
+
bindings,
|
|
256
310
|
);
|
|
257
311
|
|
|
258
312
|
return new CheckpointBuilder(
|
|
@@ -262,6 +316,13 @@ export class FullNodeCheckpointsBuilder {
|
|
|
262
316
|
this.contractDataSource,
|
|
263
317
|
this.dateProvider,
|
|
264
318
|
this.telemetryClient,
|
|
319
|
+
bindings,
|
|
320
|
+
this.debugLogStore,
|
|
265
321
|
);
|
|
266
322
|
}
|
|
323
|
+
|
|
324
|
+
/** Returns a fork of the world state at the given block number. */
|
|
325
|
+
getFork(blockNumber: BlockNumber): Promise<MerkleTreeWriteOperations> {
|
|
326
|
+
return this.worldState.fork(blockNumber);
|
|
327
|
+
}
|
|
267
328
|
}
|
package/src/config.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
secretValueConfigHelper,
|
|
7
7
|
} from '@aztec/foundation/config';
|
|
8
8
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
9
|
+
import { validatorHASignerConfigMappings } from '@aztec/stdlib/ha-signing';
|
|
9
10
|
import type { ValidatorClientConfig } from '@aztec/stdlib/interfaces/server';
|
|
10
11
|
|
|
11
12
|
export type { ValidatorClientConfig };
|
|
@@ -53,16 +54,10 @@ export const validatorClientConfigMappings: ConfigMappingsType<ValidatorClientCo
|
|
|
53
54
|
description: 'Re-execute transactions before attesting',
|
|
54
55
|
...booleanConfigHelper(true),
|
|
55
56
|
},
|
|
56
|
-
validatorReexecuteDeadlineMs: {
|
|
57
|
-
env: 'VALIDATOR_REEXECUTE_DEADLINE_MS',
|
|
58
|
-
description: 'Will re-execute until this many milliseconds are left in the slot',
|
|
59
|
-
...numberConfigHelper(6000),
|
|
60
|
-
},
|
|
61
57
|
alwaysReexecuteBlockProposals: {
|
|
62
|
-
env: 'ALWAYS_REEXECUTE_BLOCK_PROPOSALS',
|
|
63
58
|
description:
|
|
64
59
|
'Whether to always reexecute block proposals, even for non-validator nodes (useful for monitoring network status).',
|
|
65
|
-
|
|
60
|
+
defaultValue: true,
|
|
66
61
|
},
|
|
67
62
|
fishermanMode: {
|
|
68
63
|
env: 'FISHERMAN_MODE',
|
|
@@ -70,16 +65,19 @@ export const validatorClientConfigMappings: ConfigMappingsType<ValidatorClientCo
|
|
|
70
65
|
'Whether to run in fisherman mode: validates all proposals and attestations but does not broadcast attestations or participate in consensus.',
|
|
71
66
|
...booleanConfigHelper(false),
|
|
72
67
|
},
|
|
73
|
-
// TODO(palla/mbps): Change default to false once checkpoint validation is stable
|
|
74
68
|
skipCheckpointProposalValidation: {
|
|
75
|
-
description: 'Skip checkpoint proposal validation and always attest (default:
|
|
76
|
-
defaultValue:
|
|
69
|
+
description: 'Skip checkpoint proposal validation and always attest (default: false)',
|
|
70
|
+
defaultValue: false,
|
|
77
71
|
},
|
|
78
|
-
// TODO(palla/mbps): Change default to false once block sync is stable
|
|
79
72
|
skipPushProposedBlocksToArchiver: {
|
|
80
|
-
description: 'Skip pushing re-executed blocks to archiver (default:
|
|
81
|
-
defaultValue:
|
|
73
|
+
description: 'Skip pushing re-executed blocks to archiver (default: false)',
|
|
74
|
+
defaultValue: false,
|
|
75
|
+
},
|
|
76
|
+
attestToEquivocatedProposals: {
|
|
77
|
+
description: 'Agree to attest to equivocated checkpoint proposals (for testing purposes only)',
|
|
78
|
+
...booleanConfigHelper(false),
|
|
82
79
|
},
|
|
80
|
+
...validatorHASignerConfigMappings,
|
|
83
81
|
};
|
|
84
82
|
|
|
85
83
|
/**
|