@aztec/validator-client 0.0.1-commit.6d3c34e → 0.0.1-commit.7ac86ea28
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 +21 -25
- package/dest/checkpoint_builder.d.ts.map +1 -1
- package/dest/checkpoint_builder.js +54 -34
- 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/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/tx_validator/tx_validator_factory.d.ts +4 -3
- package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
- package/dest/tx_validator/tx_validator_factory.js +17 -16
- package/dest/validator.d.ts +43 -18
- package/dest/validator.d.ts.map +1 -1
- package/dest/validator.js +230 -94
- package/package.json +21 -17
- package/src/block_proposal_handler.ts +48 -69
- package/src/checkpoint_builder.ts +92 -38
- package/src/config.ts +11 -13
- package/src/duties/validation_service.ts +100 -25
- package/src/factory.ts +1 -0
- 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/tx_validator/tx_validator_factory.ts +52 -31
- package/src/validator.ts +303 -114
|
@@ -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,9 +1,9 @@
|
|
|
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,
|
|
6
|
+
import { DateProvider, elapsed } from '@aztec/foundation/timer';
|
|
7
7
|
import { getDefaultAllowedSetupFunctions } from '@aztec/p2p/msg_validators';
|
|
8
8
|
import { LightweightCheckpointBuilder } from '@aztec/prover-client/light';
|
|
9
9
|
import {
|
|
@@ -12,39 +12,38 @@ 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';
|
|
25
31
|
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
26
|
-
import { type CheckpointGlobalVariables,
|
|
32
|
+
import { type CheckpointGlobalVariables, GlobalVariables, StateReference, Tx } from '@aztec/stdlib/tx';
|
|
27
33
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
28
34
|
|
|
29
35
|
import { createValidatorForBlockBuilding } from './tx_validator/tx_validator_factory.js';
|
|
30
36
|
|
|
31
|
-
|
|
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
|
-
}
|
|
37
|
+
// Re-export for backward compatibility
|
|
38
|
+
export type { BuildBlockInCheckpointResult } from '@aztec/stdlib/interfaces/server';
|
|
42
39
|
|
|
43
40
|
/**
|
|
44
41
|
* Builder for a single checkpoint. Handles building blocks within the checkpoint
|
|
45
42
|
* and completing it.
|
|
46
43
|
*/
|
|
47
|
-
export class CheckpointBuilder {
|
|
44
|
+
export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
45
|
+
private log: Logger;
|
|
46
|
+
|
|
48
47
|
constructor(
|
|
49
48
|
private checkpointBuilder: LightweightCheckpointBuilder,
|
|
50
49
|
private fork: MerkleTreeWriteOperations,
|
|
@@ -52,7 +51,13 @@ export class CheckpointBuilder {
|
|
|
52
51
|
private contractDataSource: ContractDataSource,
|
|
53
52
|
private dateProvider: DateProvider,
|
|
54
53
|
private telemetryClient: TelemetryClient,
|
|
55
|
-
|
|
54
|
+
bindings?: LoggerBindings,
|
|
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,10 +94,16 @@ 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
108
|
const block = await this.checkpointBuilder.addBlock(globalVariables, processedTxs, {
|
|
94
109
|
expectedEndState: opts.expectedEndState,
|
|
@@ -97,24 +112,28 @@ export class CheckpointBuilder {
|
|
|
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,16 @@ 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 bindings = this.log.getBindings();
|
|
136
156
|
const publicTxSimulator = createPublicTxSimulatorForBlockBuilding(
|
|
137
157
|
guardedFork,
|
|
138
158
|
contractsDB,
|
|
139
159
|
globalVariables,
|
|
140
160
|
this.telemetryClient,
|
|
161
|
+
bindings,
|
|
141
162
|
);
|
|
142
163
|
|
|
143
164
|
const processor = new PublicProcessor(
|
|
@@ -147,7 +168,7 @@ export class CheckpointBuilder {
|
|
|
147
168
|
publicTxSimulator,
|
|
148
169
|
this.dateProvider,
|
|
149
170
|
this.telemetryClient,
|
|
150
|
-
|
|
171
|
+
createLogger('simulator:public-processor', bindings),
|
|
151
172
|
this.config,
|
|
152
173
|
);
|
|
153
174
|
|
|
@@ -156,6 +177,7 @@ export class CheckpointBuilder {
|
|
|
156
177
|
this.contractDataSource,
|
|
157
178
|
globalVariables,
|
|
158
179
|
txPublicSetupAllowList,
|
|
180
|
+
this.log.getBindings(),
|
|
159
181
|
);
|
|
160
182
|
|
|
161
183
|
return {
|
|
@@ -165,16 +187,19 @@ export class CheckpointBuilder {
|
|
|
165
187
|
}
|
|
166
188
|
}
|
|
167
189
|
|
|
168
|
-
/**
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
190
|
+
/** Factory for creating checkpoint builders. */
|
|
191
|
+
export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
192
|
+
private log: Logger;
|
|
193
|
+
|
|
172
194
|
constructor(
|
|
173
|
-
private config: FullNodeBlockBuilderConfig,
|
|
195
|
+
private config: FullNodeBlockBuilderConfig & Pick<L1RollupConstants, 'l1GenesisTime' | 'slotDuration'>,
|
|
196
|
+
private worldState: WorldStateSynchronizer,
|
|
174
197
|
private contractDataSource: ContractDataSource,
|
|
175
198
|
private dateProvider: DateProvider,
|
|
176
199
|
private telemetryClient: TelemetryClient = getTelemetryClient(),
|
|
177
|
-
) {
|
|
200
|
+
) {
|
|
201
|
+
this.log = createLogger('checkpoint-builder');
|
|
202
|
+
}
|
|
178
203
|
|
|
179
204
|
public getConfig(): FullNodeBlockBuilderConfig {
|
|
180
205
|
return this.config;
|
|
@@ -190,25 +215,32 @@ export class FullNodeCheckpointsBuilder {
|
|
|
190
215
|
async startCheckpoint(
|
|
191
216
|
checkpointNumber: CheckpointNumber,
|
|
192
217
|
constants: CheckpointGlobalVariables,
|
|
218
|
+
feeAssetPriceModifier: bigint,
|
|
193
219
|
l1ToL2Messages: Fr[],
|
|
220
|
+
previousCheckpointOutHashes: Fr[],
|
|
194
221
|
fork: MerkleTreeWriteOperations,
|
|
222
|
+
bindings?: LoggerBindings,
|
|
195
223
|
): Promise<CheckpointBuilder> {
|
|
196
224
|
const stateReference = await fork.getStateReference();
|
|
197
225
|
const archiveTree = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|
|
198
226
|
|
|
199
|
-
log.verbose(`Building new checkpoint ${checkpointNumber}`, {
|
|
227
|
+
this.log.verbose(`Building new checkpoint ${checkpointNumber}`, {
|
|
200
228
|
checkpointNumber,
|
|
201
229
|
msgCount: l1ToL2Messages.length,
|
|
202
230
|
initialStateReference: stateReference.toInspect(),
|
|
203
231
|
initialArchiveRoot: bufferToHex(archiveTree.root),
|
|
204
232
|
constants,
|
|
233
|
+
feeAssetPriceModifier,
|
|
205
234
|
});
|
|
206
235
|
|
|
207
236
|
const lightweightBuilder = await LightweightCheckpointBuilder.startNewCheckpoint(
|
|
208
237
|
checkpointNumber,
|
|
209
238
|
constants,
|
|
210
239
|
l1ToL2Messages,
|
|
240
|
+
previousCheckpointOutHashes,
|
|
211
241
|
fork,
|
|
242
|
+
bindings,
|
|
243
|
+
feeAssetPriceModifier,
|
|
212
244
|
);
|
|
213
245
|
|
|
214
246
|
return new CheckpointBuilder(
|
|
@@ -218,6 +250,7 @@ export class FullNodeCheckpointsBuilder {
|
|
|
218
250
|
this.contractDataSource,
|
|
219
251
|
this.dateProvider,
|
|
220
252
|
this.telemetryClient,
|
|
253
|
+
bindings,
|
|
221
254
|
);
|
|
222
255
|
}
|
|
223
256
|
|
|
@@ -227,32 +260,47 @@ export class FullNodeCheckpointsBuilder {
|
|
|
227
260
|
async openCheckpoint(
|
|
228
261
|
checkpointNumber: CheckpointNumber,
|
|
229
262
|
constants: CheckpointGlobalVariables,
|
|
263
|
+
feeAssetPriceModifier: bigint,
|
|
230
264
|
l1ToL2Messages: Fr[],
|
|
265
|
+
previousCheckpointOutHashes: Fr[],
|
|
231
266
|
fork: MerkleTreeWriteOperations,
|
|
232
|
-
existingBlocks:
|
|
267
|
+
existingBlocks: L2Block[] = [],
|
|
268
|
+
bindings?: LoggerBindings,
|
|
233
269
|
): Promise<CheckpointBuilder> {
|
|
234
270
|
const stateReference = await fork.getStateReference();
|
|
235
271
|
const archiveTree = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|
|
236
272
|
|
|
237
273
|
if (existingBlocks.length === 0) {
|
|
238
|
-
return this.startCheckpoint(
|
|
274
|
+
return this.startCheckpoint(
|
|
275
|
+
checkpointNumber,
|
|
276
|
+
constants,
|
|
277
|
+
feeAssetPriceModifier,
|
|
278
|
+
l1ToL2Messages,
|
|
279
|
+
previousCheckpointOutHashes,
|
|
280
|
+
fork,
|
|
281
|
+
bindings,
|
|
282
|
+
);
|
|
239
283
|
}
|
|
240
284
|
|
|
241
|
-
log.verbose(`Resuming checkpoint ${checkpointNumber} with ${existingBlocks.length} existing blocks`, {
|
|
285
|
+
this.log.verbose(`Resuming checkpoint ${checkpointNumber} with ${existingBlocks.length} existing blocks`, {
|
|
242
286
|
checkpointNumber,
|
|
243
287
|
msgCount: l1ToL2Messages.length,
|
|
244
288
|
existingBlockCount: existingBlocks.length,
|
|
245
289
|
initialStateReference: stateReference.toInspect(),
|
|
246
290
|
initialArchiveRoot: bufferToHex(archiveTree.root),
|
|
247
291
|
constants,
|
|
292
|
+
feeAssetPriceModifier,
|
|
248
293
|
});
|
|
249
294
|
|
|
250
295
|
const lightweightBuilder = await LightweightCheckpointBuilder.resumeCheckpoint(
|
|
251
296
|
checkpointNumber,
|
|
252
297
|
constants,
|
|
298
|
+
feeAssetPriceModifier,
|
|
253
299
|
l1ToL2Messages,
|
|
300
|
+
previousCheckpointOutHashes,
|
|
254
301
|
fork,
|
|
255
302
|
existingBlocks,
|
|
303
|
+
bindings,
|
|
256
304
|
);
|
|
257
305
|
|
|
258
306
|
return new CheckpointBuilder(
|
|
@@ -262,6 +310,12 @@ export class FullNodeCheckpointsBuilder {
|
|
|
262
310
|
this.contractDataSource,
|
|
263
311
|
this.dateProvider,
|
|
264
312
|
this.telemetryClient,
|
|
313
|
+
bindings,
|
|
265
314
|
);
|
|
266
315
|
}
|
|
316
|
+
|
|
317
|
+
/** Returns a fork of the world state at the given block number. */
|
|
318
|
+
getFork(blockNumber: BlockNumber): Promise<MerkleTreeWriteOperations> {
|
|
319
|
+
return this.worldState.fork(blockNumber);
|
|
320
|
+
}
|
|
267
321
|
}
|
package/src/config.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from '@aztec/foundation/config';
|
|
8
8
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
9
9
|
import type { ValidatorClientConfig } from '@aztec/stdlib/interfaces/server';
|
|
10
|
+
import { validatorHASignerConfigMappings } from '@aztec/validator-ha-signer/config';
|
|
10
11
|
|
|
11
12
|
export type { ValidatorClientConfig };
|
|
12
13
|
|
|
@@ -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
|
/**
|