@aztec/validator-client 0.0.1-commit.7d4e6cd → 0.0.1-commit.808bf7f90
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 +60 -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 +233 -94
- package/package.json +21 -17
- package/src/block_proposal_handler.ts +48 -69
- package/src/checkpoint_builder.ts +101 -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,39 @@ 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
36
|
import { createValidatorForBlockBuilding } from './tx_validator/tx_validator_factory.js';
|
|
30
37
|
|
|
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
|
-
}
|
|
38
|
+
// Re-export for backward compatibility
|
|
39
|
+
export type { BuildBlockInCheckpointResult } from '@aztec/stdlib/interfaces/server';
|
|
42
40
|
|
|
43
41
|
/**
|
|
44
42
|
* Builder for a single checkpoint. Handles building blocks within the checkpoint
|
|
45
43
|
* and completing it.
|
|
46
44
|
*/
|
|
47
|
-
export class CheckpointBuilder {
|
|
45
|
+
export class CheckpointBuilder implements ICheckpointBlockBuilder {
|
|
46
|
+
private log: Logger;
|
|
47
|
+
|
|
48
48
|
constructor(
|
|
49
49
|
private checkpointBuilder: LightweightCheckpointBuilder,
|
|
50
50
|
private fork: MerkleTreeWriteOperations,
|
|
@@ -52,7 +52,14 @@ export class CheckpointBuilder {
|
|
|
52
52
|
private contractDataSource: ContractDataSource,
|
|
53
53
|
private dateProvider: DateProvider,
|
|
54
54
|
private telemetryClient: TelemetryClient,
|
|
55
|
-
|
|
55
|
+
bindings?: LoggerBindings,
|
|
56
|
+
private debugLogStore: DebugLogStore = new NullDebugLogStore(),
|
|
57
|
+
) {
|
|
58
|
+
this.log = createLogger('checkpoint-builder', {
|
|
59
|
+
...bindings,
|
|
60
|
+
instanceId: `checkpoint-${checkpointBuilder.checkpointNumber}`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
56
63
|
|
|
57
64
|
getConstantData(): CheckpointGlobalVariables {
|
|
58
65
|
return this.checkpointBuilder.constants;
|
|
@@ -65,12 +72,16 @@ export class CheckpointBuilder {
|
|
|
65
72
|
pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
|
|
66
73
|
blockNumber: BlockNumber,
|
|
67
74
|
timestamp: bigint,
|
|
68
|
-
opts: PublicProcessorLimits & { expectedEndState?: StateReference },
|
|
75
|
+
opts: PublicProcessorLimits & { expectedEndState?: StateReference } = {},
|
|
69
76
|
): Promise<BuildBlockInCheckpointResult> {
|
|
70
|
-
const blockBuildingTimer = new Timer();
|
|
71
77
|
const slot = this.checkpointBuilder.constants.slotNumber;
|
|
72
78
|
|
|
73
|
-
log.verbose(`Building block ${blockNumber} for slot ${slot} within checkpoint`, {
|
|
79
|
+
this.log.verbose(`Building block ${blockNumber} for slot ${slot} within checkpoint`, {
|
|
80
|
+
slot,
|
|
81
|
+
blockNumber,
|
|
82
|
+
...opts,
|
|
83
|
+
currentTime: new Date(this.dateProvider.now()),
|
|
84
|
+
});
|
|
74
85
|
|
|
75
86
|
const constants = this.checkpointBuilder.constants;
|
|
76
87
|
const globalVariables = GlobalVariables.from({
|
|
@@ -85,10 +96,16 @@ export class CheckpointBuilder {
|
|
|
85
96
|
});
|
|
86
97
|
const { processor, validator } = await this.makeBlockBuilderDeps(globalVariables, this.fork);
|
|
87
98
|
|
|
88
|
-
const [publicProcessorDuration, [processedTxs, failedTxs, usedTxs]] = await elapsed(() =>
|
|
99
|
+
const [publicProcessorDuration, [processedTxs, failedTxs, usedTxs, _, usedTxBlobFields]] = await elapsed(() =>
|
|
89
100
|
processor.process(pendingTxs, opts, validator),
|
|
90
101
|
);
|
|
91
102
|
|
|
103
|
+
// Throw if we didn't collect a single valid tx and we're not allowed to build empty blocks
|
|
104
|
+
// (only the first block in a checkpoint can be empty)
|
|
105
|
+
if (processedTxs.length === 0 && this.checkpointBuilder.getBlockCount() > 0) {
|
|
106
|
+
throw new NoValidTxsError(failedTxs);
|
|
107
|
+
}
|
|
108
|
+
|
|
92
109
|
// Add block to checkpoint
|
|
93
110
|
const block = await this.checkpointBuilder.addBlock(globalVariables, processedTxs, {
|
|
94
111
|
expectedEndState: opts.expectedEndState,
|
|
@@ -97,24 +114,28 @@ export class CheckpointBuilder {
|
|
|
97
114
|
// How much public gas was processed
|
|
98
115
|
const publicGas = processedTxs.reduce((acc, tx) => acc.add(tx.gasUsed.publicGas), Gas.empty());
|
|
99
116
|
|
|
100
|
-
|
|
117
|
+
this.log.debug('Built block within checkpoint', {
|
|
118
|
+
header: block.header.toInspect(),
|
|
119
|
+
processedTxs: processedTxs.map(tx => tx.hash.toString()),
|
|
120
|
+
failedTxs: failedTxs.map(tx => tx.tx.txHash.toString()),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return {
|
|
101
124
|
block,
|
|
102
125
|
publicGas,
|
|
103
126
|
publicProcessorDuration,
|
|
104
127
|
numTxs: processedTxs.length,
|
|
105
128
|
failedTxs,
|
|
106
|
-
blockBuildingTimer,
|
|
107
129
|
usedTxs,
|
|
130
|
+
usedTxBlobFields,
|
|
108
131
|
};
|
|
109
|
-
log.debug('Built block within checkpoint', res.block.header);
|
|
110
|
-
return res;
|
|
111
132
|
}
|
|
112
133
|
|
|
113
134
|
/** Completes the checkpoint and returns it. */
|
|
114
135
|
async completeCheckpoint(): Promise<Checkpoint> {
|
|
115
136
|
const checkpoint = await this.checkpointBuilder.completeCheckpoint();
|
|
116
137
|
|
|
117
|
-
log.verbose(`Completed checkpoint ${checkpoint.number}`, {
|
|
138
|
+
this.log.verbose(`Completed checkpoint ${checkpoint.number}`, {
|
|
118
139
|
checkpointNumber: checkpoint.number,
|
|
119
140
|
numBlocks: checkpoint.blocks.length,
|
|
120
141
|
archiveRoot: checkpoint.archive.root.toString(),
|
|
@@ -130,14 +151,19 @@ export class CheckpointBuilder {
|
|
|
130
151
|
|
|
131
152
|
protected async makeBlockBuilderDeps(globalVariables: GlobalVariables, fork: MerkleTreeWriteOperations) {
|
|
132
153
|
const txPublicSetupAllowList = this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions());
|
|
133
|
-
const contractsDB = new PublicContractsDB(this.contractDataSource);
|
|
154
|
+
const contractsDB = new PublicContractsDB(this.contractDataSource, this.log.getBindings());
|
|
134
155
|
const guardedFork = new GuardedMerkleTreeOperations(fork);
|
|
135
156
|
|
|
157
|
+
const collectDebugLogs = this.debugLogStore.isEnabled;
|
|
158
|
+
|
|
159
|
+
const bindings = this.log.getBindings();
|
|
136
160
|
const publicTxSimulator = createPublicTxSimulatorForBlockBuilding(
|
|
137
161
|
guardedFork,
|
|
138
162
|
contractsDB,
|
|
139
163
|
globalVariables,
|
|
140
164
|
this.telemetryClient,
|
|
165
|
+
bindings,
|
|
166
|
+
collectDebugLogs,
|
|
141
167
|
);
|
|
142
168
|
|
|
143
169
|
const processor = new PublicProcessor(
|
|
@@ -147,8 +173,9 @@ export class CheckpointBuilder {
|
|
|
147
173
|
publicTxSimulator,
|
|
148
174
|
this.dateProvider,
|
|
149
175
|
this.telemetryClient,
|
|
150
|
-
|
|
176
|
+
createLogger('simulator:public-processor', bindings),
|
|
151
177
|
this.config,
|
|
178
|
+
this.debugLogStore,
|
|
152
179
|
);
|
|
153
180
|
|
|
154
181
|
const validator = createValidatorForBlockBuilding(
|
|
@@ -156,6 +183,7 @@ export class CheckpointBuilder {
|
|
|
156
183
|
this.contractDataSource,
|
|
157
184
|
globalVariables,
|
|
158
185
|
txPublicSetupAllowList,
|
|
186
|
+
this.log.getBindings(),
|
|
159
187
|
);
|
|
160
188
|
|
|
161
189
|
return {
|
|
@@ -165,16 +193,20 @@ export class CheckpointBuilder {
|
|
|
165
193
|
}
|
|
166
194
|
}
|
|
167
195
|
|
|
168
|
-
/**
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
196
|
+
/** Factory for creating checkpoint builders. */
|
|
197
|
+
export class FullNodeCheckpointsBuilder implements ICheckpointsBuilder {
|
|
198
|
+
private log: Logger;
|
|
199
|
+
|
|
172
200
|
constructor(
|
|
173
|
-
private config: FullNodeBlockBuilderConfig,
|
|
201
|
+
private config: FullNodeBlockBuilderConfig & Pick<L1RollupConstants, 'l1GenesisTime' | 'slotDuration'>,
|
|
202
|
+
private worldState: WorldStateSynchronizer,
|
|
174
203
|
private contractDataSource: ContractDataSource,
|
|
175
204
|
private dateProvider: DateProvider,
|
|
176
205
|
private telemetryClient: TelemetryClient = getTelemetryClient(),
|
|
177
|
-
|
|
206
|
+
private debugLogStore: DebugLogStore = new NullDebugLogStore(),
|
|
207
|
+
) {
|
|
208
|
+
this.log = createLogger('checkpoint-builder');
|
|
209
|
+
}
|
|
178
210
|
|
|
179
211
|
public getConfig(): FullNodeBlockBuilderConfig {
|
|
180
212
|
return this.config;
|
|
@@ -190,25 +222,32 @@ export class FullNodeCheckpointsBuilder {
|
|
|
190
222
|
async startCheckpoint(
|
|
191
223
|
checkpointNumber: CheckpointNumber,
|
|
192
224
|
constants: CheckpointGlobalVariables,
|
|
225
|
+
feeAssetPriceModifier: bigint,
|
|
193
226
|
l1ToL2Messages: Fr[],
|
|
227
|
+
previousCheckpointOutHashes: Fr[],
|
|
194
228
|
fork: MerkleTreeWriteOperations,
|
|
229
|
+
bindings?: LoggerBindings,
|
|
195
230
|
): Promise<CheckpointBuilder> {
|
|
196
231
|
const stateReference = await fork.getStateReference();
|
|
197
232
|
const archiveTree = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|
|
198
233
|
|
|
199
|
-
log.verbose(`Building new checkpoint ${checkpointNumber}`, {
|
|
234
|
+
this.log.verbose(`Building new checkpoint ${checkpointNumber}`, {
|
|
200
235
|
checkpointNumber,
|
|
201
236
|
msgCount: l1ToL2Messages.length,
|
|
202
237
|
initialStateReference: stateReference.toInspect(),
|
|
203
238
|
initialArchiveRoot: bufferToHex(archiveTree.root),
|
|
204
239
|
constants,
|
|
240
|
+
feeAssetPriceModifier,
|
|
205
241
|
});
|
|
206
242
|
|
|
207
243
|
const lightweightBuilder = await LightweightCheckpointBuilder.startNewCheckpoint(
|
|
208
244
|
checkpointNumber,
|
|
209
245
|
constants,
|
|
210
246
|
l1ToL2Messages,
|
|
247
|
+
previousCheckpointOutHashes,
|
|
211
248
|
fork,
|
|
249
|
+
bindings,
|
|
250
|
+
feeAssetPriceModifier,
|
|
212
251
|
);
|
|
213
252
|
|
|
214
253
|
return new CheckpointBuilder(
|
|
@@ -218,6 +257,8 @@ export class FullNodeCheckpointsBuilder {
|
|
|
218
257
|
this.contractDataSource,
|
|
219
258
|
this.dateProvider,
|
|
220
259
|
this.telemetryClient,
|
|
260
|
+
bindings,
|
|
261
|
+
this.debugLogStore,
|
|
221
262
|
);
|
|
222
263
|
}
|
|
223
264
|
|
|
@@ -227,32 +268,47 @@ export class FullNodeCheckpointsBuilder {
|
|
|
227
268
|
async openCheckpoint(
|
|
228
269
|
checkpointNumber: CheckpointNumber,
|
|
229
270
|
constants: CheckpointGlobalVariables,
|
|
271
|
+
feeAssetPriceModifier: bigint,
|
|
230
272
|
l1ToL2Messages: Fr[],
|
|
273
|
+
previousCheckpointOutHashes: Fr[],
|
|
231
274
|
fork: MerkleTreeWriteOperations,
|
|
232
|
-
existingBlocks:
|
|
275
|
+
existingBlocks: L2Block[] = [],
|
|
276
|
+
bindings?: LoggerBindings,
|
|
233
277
|
): Promise<CheckpointBuilder> {
|
|
234
278
|
const stateReference = await fork.getStateReference();
|
|
235
279
|
const archiveTree = await fork.getTreeInfo(MerkleTreeId.ARCHIVE);
|
|
236
280
|
|
|
237
281
|
if (existingBlocks.length === 0) {
|
|
238
|
-
return this.startCheckpoint(
|
|
282
|
+
return this.startCheckpoint(
|
|
283
|
+
checkpointNumber,
|
|
284
|
+
constants,
|
|
285
|
+
feeAssetPriceModifier,
|
|
286
|
+
l1ToL2Messages,
|
|
287
|
+
previousCheckpointOutHashes,
|
|
288
|
+
fork,
|
|
289
|
+
bindings,
|
|
290
|
+
);
|
|
239
291
|
}
|
|
240
292
|
|
|
241
|
-
log.verbose(`Resuming checkpoint ${checkpointNumber} with ${existingBlocks.length} existing blocks`, {
|
|
293
|
+
this.log.verbose(`Resuming checkpoint ${checkpointNumber} with ${existingBlocks.length} existing blocks`, {
|
|
242
294
|
checkpointNumber,
|
|
243
295
|
msgCount: l1ToL2Messages.length,
|
|
244
296
|
existingBlockCount: existingBlocks.length,
|
|
245
297
|
initialStateReference: stateReference.toInspect(),
|
|
246
298
|
initialArchiveRoot: bufferToHex(archiveTree.root),
|
|
247
299
|
constants,
|
|
300
|
+
feeAssetPriceModifier,
|
|
248
301
|
});
|
|
249
302
|
|
|
250
303
|
const lightweightBuilder = await LightweightCheckpointBuilder.resumeCheckpoint(
|
|
251
304
|
checkpointNumber,
|
|
252
305
|
constants,
|
|
306
|
+
feeAssetPriceModifier,
|
|
253
307
|
l1ToL2Messages,
|
|
308
|
+
previousCheckpointOutHashes,
|
|
254
309
|
fork,
|
|
255
310
|
existingBlocks,
|
|
311
|
+
bindings,
|
|
256
312
|
);
|
|
257
313
|
|
|
258
314
|
return new CheckpointBuilder(
|
|
@@ -262,6 +318,13 @@ export class FullNodeCheckpointsBuilder {
|
|
|
262
318
|
this.contractDataSource,
|
|
263
319
|
this.dateProvider,
|
|
264
320
|
this.telemetryClient,
|
|
321
|
+
bindings,
|
|
322
|
+
this.debugLogStore,
|
|
265
323
|
);
|
|
266
324
|
}
|
|
325
|
+
|
|
326
|
+
/** Returns a fork of the world state at the given block number. */
|
|
327
|
+
getFork(blockNumber: BlockNumber): Promise<MerkleTreeWriteOperations> {
|
|
328
|
+
return this.worldState.fork(blockNumber);
|
|
329
|
+
}
|
|
267
330
|
}
|
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
|
/**
|