@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.
Files changed (57) hide show
  1. package/README.md +53 -24
  2. package/dest/block_proposal_handler.d.ts +9 -9
  3. package/dest/block_proposal_handler.d.ts.map +1 -1
  4. package/dest/block_proposal_handler.js +35 -54
  5. package/dest/checkpoint_builder.d.ts +24 -25
  6. package/dest/checkpoint_builder.d.ts.map +1 -1
  7. package/dest/checkpoint_builder.js +60 -34
  8. package/dest/config.d.ts +1 -1
  9. package/dest/config.d.ts.map +1 -1
  10. package/dest/config.js +12 -14
  11. package/dest/duties/validation_service.d.ts +20 -7
  12. package/dest/duties/validation_service.d.ts.map +1 -1
  13. package/dest/duties/validation_service.js +75 -22
  14. package/dest/factory.d.ts +2 -2
  15. package/dest/factory.d.ts.map +1 -1
  16. package/dest/factory.js +1 -1
  17. package/dest/key_store/ha_key_store.d.ts +99 -0
  18. package/dest/key_store/ha_key_store.d.ts.map +1 -0
  19. package/dest/key_store/ha_key_store.js +208 -0
  20. package/dest/key_store/index.d.ts +2 -1
  21. package/dest/key_store/index.d.ts.map +1 -1
  22. package/dest/key_store/index.js +1 -0
  23. package/dest/key_store/interface.d.ts +36 -6
  24. package/dest/key_store/interface.d.ts.map +1 -1
  25. package/dest/key_store/local_key_store.d.ts +10 -5
  26. package/dest/key_store/local_key_store.d.ts.map +1 -1
  27. package/dest/key_store/local_key_store.js +8 -4
  28. package/dest/key_store/node_keystore_adapter.d.ts +18 -5
  29. package/dest/key_store/node_keystore_adapter.d.ts.map +1 -1
  30. package/dest/key_store/node_keystore_adapter.js +18 -4
  31. package/dest/key_store/web3signer_key_store.d.ts +10 -5
  32. package/dest/key_store/web3signer_key_store.d.ts.map +1 -1
  33. package/dest/key_store/web3signer_key_store.js +8 -4
  34. package/dest/metrics.d.ts +4 -3
  35. package/dest/metrics.d.ts.map +1 -1
  36. package/dest/metrics.js +34 -5
  37. package/dest/tx_validator/tx_validator_factory.d.ts +4 -3
  38. package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
  39. package/dest/tx_validator/tx_validator_factory.js +17 -16
  40. package/dest/validator.d.ts +43 -18
  41. package/dest/validator.d.ts.map +1 -1
  42. package/dest/validator.js +233 -94
  43. package/package.json +21 -17
  44. package/src/block_proposal_handler.ts +48 -69
  45. package/src/checkpoint_builder.ts +101 -38
  46. package/src/config.ts +11 -13
  47. package/src/duties/validation_service.ts +100 -25
  48. package/src/factory.ts +1 -0
  49. package/src/key_store/ha_key_store.ts +269 -0
  50. package/src/key_store/index.ts +1 -0
  51. package/src/key_store/interface.ts +44 -5
  52. package/src/key_store/local_key_store.ts +13 -4
  53. package/src/key_store/node_keystore_adapter.ts +27 -4
  54. package/src/key_store/web3signer_key_store.ts +17 -4
  55. package/src/metrics.ts +45 -6
  56. package/src/tx_validator/tx_validator_factory.ts +52 -31
  57. 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 { L2BlockNew, L2BlockSink, L2BlockSource } from '@aztec/stdlib/block';
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 { BlockHeader, type CheckpointGlobalVariables, type FailedTx, type Tx } from '@aztec/stdlib/tx';
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: L2BlockNew;
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: 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 invalidProposal = await this.blockProposalValidator.validate(proposal);
144
- if (invalidProposal) {
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 parentBlockHeader = await this.getParentBlock(proposal);
151
- if (parentBlockHeader === undefined) {
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 less than the proposal's slot (should not happen, but we check anyway)
157
- if (parentBlockHeader !== 'genesis' && parentBlockHeader.getSlot() >= slotNumber) {
158
- this.log.warn(`Parent block slot is greater than or equal to proposal slot, skipping processing`, {
159
- parentBlockSlot: parentBlockHeader.getSlot().toString(),
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
- parentBlockHeader === 'genesis'
169
+ parentBlock === 'genesis'
169
170
  ? BlockNumber(INITIAL_L2_BLOCK_NUM)
170
- : BlockNumber(parentBlockHeader.getBlockNumber() + 1);
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 = await this.computeCheckpointNumber(proposal, parentBlockHeader, proposalInfo);
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' | BlockHeader | undefined> {
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.getBlockHeaderByArchive(parentArchive)) ??
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 async computeCheckpointNumber(
288
+ private computeCheckpointNumber(
283
289
  proposal: BlockProposal,
284
- parentBlockHeader: 'genesis' | BlockHeader,
290
+ parentBlock: 'genesis' | BlockData,
285
291
  proposalInfo: object,
286
- ): Promise<CheckpointComputationResult> {
287
- if (parentBlockHeader === 'genesis') {
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() > parentBlockHeader.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() !== parentBlockHeader.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: L2BlockNew,
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
- const msNeededForPropagationAndPublishing = this.config.validatorReexecuteDeadlineMs;
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 and checkpoint number)
477
- const priorBlocks = await this.getBlocksInCheckpoint(slot, blockNumber, checkpointNumber);
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
- using fork = await this.worldState.fork(parentBlockNumber);
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 and timestamp which are per-block)
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, Timer, elapsed } from '@aztec/foundation/timer';
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 { L2BlockNew } from '@aztec/stdlib/block';
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, type FailedTx, GlobalVariables, StateReference, Tx } from '@aztec/stdlib/tx';
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
- 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
- }
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`, { slot, blockNumber, ...opts });
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
- const res = {
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
- undefined,
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
- * Factory for creating checkpoint builders.
170
- */
171
- export class FullNodeCheckpointsBuilder {
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: L2BlockNew[] = [],
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(checkpointNumber, constants, l1ToL2Messages, fork);
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
- ...booleanConfigHelper(false),
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: true)',
76
- defaultValue: true,
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: true)',
81
- defaultValue: true,
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
  /**