@aztec/aztec-node 0.0.1-commit.f5d02921e → 0.0.1-commit.f7ea82942

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.
@@ -16,6 +16,7 @@ import { Fr } from '@aztec/foundation/curves/bn254';
16
16
  import { EthAddress } from '@aztec/foundation/eth-address';
17
17
  import { BadRequestError } from '@aztec/foundation/json-rpc';
18
18
  import { type Logger, createLogger } from '@aztec/foundation/log';
19
+ import { retryUntil } from '@aztec/foundation/retry';
19
20
  import { count } from '@aztec/foundation/string';
20
21
  import { DateProvider, Timer } from '@aztec/foundation/timer';
21
22
  import { MembershipWitness, SiblingPath } from '@aztec/foundation/trees';
@@ -32,7 +33,12 @@ import {
32
33
  import { ProtocolContractAddress } from '@aztec/protocol-contracts';
33
34
  import { type ProverNode, type ProverNodeDeps, createProverNode } from '@aztec/prover-node';
34
35
  import { createKeyStoreForProver } from '@aztec/prover-node/config';
35
- import { GlobalVariableBuilder, SequencerClient, type SequencerPublisher } from '@aztec/sequencer-client';
36
+ import {
37
+ FeeProviderImpl,
38
+ GlobalVariableBuilder,
39
+ SequencerClient,
40
+ type SequencerPublisher,
41
+ } from '@aztec/sequencer-client';
36
42
  import { PublicProcessorFactory } from '@aztec/simulator/server';
37
43
  import {
38
44
  AttestationsBlockWatcher,
@@ -59,13 +65,14 @@ import type {
59
65
  NodeInfo,
60
66
  ProtocolContractAddresses,
61
67
  } from '@aztec/stdlib/contract';
62
- import { GasFees } from '@aztec/stdlib/gas';
68
+ import { GasFees, type ManaUsageEstimate } from '@aztec/stdlib/gas';
63
69
  import { computePublicDataTreeLeafSlot } from '@aztec/stdlib/hash';
64
70
  import {
65
71
  type AztecNode,
66
72
  type AztecNodeAdmin,
67
73
  type AztecNodeAdminConfig,
68
74
  AztecNodeAdminConfigSchema,
75
+ type AztecNodeDebug,
69
76
  type GetContractClassLogsResponse,
70
77
  type GetPublicLogsResponse,
71
78
  } from '@aztec/stdlib/interfaces/client';
@@ -81,11 +88,12 @@ import {
81
88
  import type { DebugLogStore, LogFilter, SiloedTag, Tag, TxScopedL2Log } from '@aztec/stdlib/logs';
82
89
  import { InMemoryDebugLogStore, NullDebugLogStore } from '@aztec/stdlib/logs';
83
90
  import { InboxLeaf, type L1ToL2MessageSource } from '@aztec/stdlib/messaging';
84
- import type { Offense, SlashPayloadRound } from '@aztec/stdlib/slashing';
85
- import type { NullifierLeafPreimage, PublicDataTreeLeaf, PublicDataTreeLeafPreimage } from '@aztec/stdlib/trees';
91
+ import type { Offense } from '@aztec/stdlib/slashing';
92
+ import type { NullifierLeafPreimage, PublicDataTreeLeafPreimage } from '@aztec/stdlib/trees';
86
93
  import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
87
94
  import {
88
95
  type BlockHeader,
96
+ type FeeProvider,
89
97
  type GlobalVariableBuilder as GlobalVariableBuilderInterface,
90
98
  type IndexedTxEffect,
91
99
  PublicSimulationOutput,
@@ -97,6 +105,7 @@ import {
97
105
  } from '@aztec/stdlib/tx';
98
106
  import { getPackageVersion } from '@aztec/stdlib/update-checker';
99
107
  import type { SingleValidatorStats, ValidatorsStats } from '@aztec/stdlib/validators';
108
+ import type { GenesisData } from '@aztec/stdlib/world-state';
100
109
  import {
101
110
  Attributes,
102
111
  type TelemetryClient,
@@ -126,7 +135,7 @@ import { NodeMetrics } from './node_metrics.js';
126
135
  /**
127
136
  * The aztec node.
128
137
  */
129
- export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
138
+ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDebug, Traceable {
130
139
  private metrics: NodeMetrics;
131
140
  private initialHeaderHashPromise: Promise<BlockHash> | undefined = undefined;
132
141
 
@@ -151,6 +160,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
151
160
  protected readonly l1ChainId: number,
152
161
  protected readonly version: number,
153
162
  protected readonly globalVariableBuilder: GlobalVariableBuilderInterface,
163
+ protected readonly feeProvider: FeeProvider,
154
164
  protected readonly epochCache: EpochCacheInterface,
155
165
  protected readonly packageVersion: string,
156
166
  private peerProofVerifier: ClientProtocolCircuitVerifier,
@@ -207,7 +217,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
207
217
  slashingProtectionDb?: SlashingProtectionDatabase;
208
218
  } = {},
209
219
  options: {
210
- prefilledPublicData?: PublicDataTreeLeaf[];
220
+ genesis?: GenesisData;
211
221
  dontStartSequencer?: boolean;
212
222
  dontStartProverNode?: boolean;
213
223
  } = {},
@@ -303,328 +313,344 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
303
313
 
304
314
  const epochCache = await EpochCache.create(config.l1Contracts.rollupAddress, config, { dateProvider });
305
315
 
306
- const archiver = await createArchiver(
307
- config,
308
- { blobClient, epochCache, telemetry, dateProvider },
309
- { blockUntilSync: !config.skipArchiverInitialSync },
310
- );
311
-
312
- // now create the merkle trees and the world state synchronizer
313
- const worldStateSynchronizer = await createWorldStateSynchronizer(
314
- config,
315
- archiver,
316
- options.prefilledPublicData,
317
- telemetry,
318
- );
319
- const useRealVerifiers = config.realProofs || config.debugForceTxProofVerification;
320
- let peerProofVerifier: ClientProtocolCircuitVerifier;
321
- let rpcProofVerifier: ClientProtocolCircuitVerifier;
322
- if (useRealVerifiers) {
323
- peerProofVerifier = await BatchChonkVerifier.new(config, config.bbChonkVerifyMaxBatch, 'peer');
324
- const rpcVerifier = await BBCircuitVerifier.new(config);
325
- rpcProofVerifier = new QueuedIVCVerifier(rpcVerifier, config.numConcurrentIVCVerifiers);
326
- } else {
327
- peerProofVerifier = new TestCircuitVerifier(config.proverTestVerificationDelayMs);
328
- rpcProofVerifier = new TestCircuitVerifier(config.proverTestVerificationDelayMs);
329
- }
330
-
331
- let debugLogStore: DebugLogStore;
332
- if (!config.realProofs) {
333
- log.warn(`Aztec node is accepting fake proofs`);
334
-
335
- debugLogStore = new InMemoryDebugLogStore();
336
- log.info(
337
- 'Aztec node started in test mode (realProofs set to false) hence debug logs from public functions will be collected and served',
316
+ // Track started resources so we can clean up on partial failure during node creation.
317
+ const started: { stop?(): Promise<void> | void }[] = [];
318
+ try {
319
+ const archiver = await createArchiver(
320
+ config,
321
+ { blobClient, epochCache, telemetry, dateProvider },
322
+ { blockUntilSync: !config.skipArchiverInitialSync },
338
323
  );
339
- } else {
340
- debugLogStore = new NullDebugLogStore();
341
- }
342
-
343
- const proverOnly = config.enableProverNode && config.disableValidator;
344
- if (proverOnly) {
345
- log.info('Starting in prover-only mode: skipping validator, sequencer, sentinel, and slasher subsystems');
346
- }
324
+ started.push(archiver);
325
+
326
+ // now create the merkle trees and the world state synchronizer
327
+ const worldStateSynchronizer = await createWorldStateSynchronizer(config, archiver, options.genesis, telemetry);
328
+ started.push(worldStateSynchronizer);
329
+ const useRealVerifiers = config.realProofs || config.debugForceTxProofVerification;
330
+ let peerProofVerifier: ClientProtocolCircuitVerifier;
331
+ let rpcProofVerifier: ClientProtocolCircuitVerifier;
332
+ if (useRealVerifiers) {
333
+ peerProofVerifier = await BatchChonkVerifier.new(config, config.bbChonkVerifyMaxBatch, 'peer');
334
+ const rpcVerifier = await BBCircuitVerifier.new(config);
335
+ rpcProofVerifier = new QueuedIVCVerifier(rpcVerifier, config.numConcurrentIVCVerifiers);
336
+ } else {
337
+ peerProofVerifier = new TestCircuitVerifier(config.proverTestVerificationDelayMs);
338
+ rpcProofVerifier = new TestCircuitVerifier(config.proverTestVerificationDelayMs);
339
+ }
340
+ started.push(peerProofVerifier, rpcProofVerifier);
347
341
 
348
- // create the tx pool and the p2p client, which will need the l2 block source
349
- const p2pClient = await createP2PClient(
350
- config,
351
- archiver,
352
- peerProofVerifier,
353
- worldStateSynchronizer,
354
- epochCache,
355
- packageVersion,
356
- dateProvider,
357
- telemetry,
358
- deps.p2pClientDeps,
359
- );
342
+ let debugLogStore: DebugLogStore;
343
+ if (!config.realProofs) {
344
+ log.warn(`Aztec node is accepting fake proofs`);
360
345
 
361
- // We'll accumulate sentinel watchers here
362
- const watchers: Watcher[] = [];
346
+ debugLogStore = new InMemoryDebugLogStore();
347
+ log.info(
348
+ 'Aztec node started in test mode (realProofs set to false) hence debug logs from public functions will be collected and served',
349
+ );
350
+ } else {
351
+ debugLogStore = new NullDebugLogStore();
352
+ }
363
353
 
364
- // Create FullNodeCheckpointsBuilder for block proposal handling and tx validation.
365
- // Override maxTxsPerCheckpoint with the validator-specific limit if set.
366
- const validatorCheckpointsBuilder = new FullNodeCheckpointsBuilder(
367
- {
368
- ...config,
354
+ const globalVariableBuilderConfig = {
355
+ l1Contracts: config.l1Contracts,
356
+ ethereumSlotDuration: config.ethereumSlotDuration,
357
+ rollupVersion: BigInt(config.rollupVersion),
369
358
  l1GenesisTime,
370
359
  slotDuration: Number(slotDuration),
371
- rollupManaLimit,
372
- maxTxsPerCheckpoint: config.validateMaxTxsPerCheckpoint,
373
- },
374
- worldStateSynchronizer,
375
- archiver,
376
- dateProvider,
377
- telemetry,
378
- );
379
-
380
- let validatorClient: ValidatorClient | undefined;
381
-
382
- if (!proverOnly) {
383
- // Create validator client if required
384
- validatorClient = await createValidatorClient(config, {
385
- checkpointsBuilder: validatorCheckpointsBuilder,
386
- worldState: worldStateSynchronizer,
387
- p2pClient,
388
- telemetry,
389
- dateProvider,
390
- epochCache,
391
- blockSource: archiver,
392
- l1ToL2MessageSource: archiver,
393
- keyStoreManager,
394
- blobClient,
395
- slashingProtectionDb: deps.slashingProtectionDb,
396
- });
397
-
398
- // If we have a validator client, register it as a source of offenses for the slasher,
399
- // and have it register callbacks on the p2p client *before* we start it, otherwise messages
400
- // like attestations or auths will fail.
401
- if (validatorClient) {
402
- watchers.push(validatorClient);
360
+ };
403
361
 
404
- const vc = validatorClient;
405
- const getValidatorAddresses = () => vc.getValidatorAddresses().map(a => a.toString());
406
- validatorClient.getProposalHandler().register(p2pClient, true, archiver, getValidatorAddresses);
362
+ const globalVariableBuilder = new GlobalVariableBuilder(dateProvider, publicClient, globalVariableBuilderConfig);
363
+ const feeProvider = new FeeProviderImpl(dateProvider, publicClient, globalVariableBuilderConfig);
407
364
 
408
- if (!options.dontStartSequencer) {
409
- await validatorClient.registerHandlers();
410
- }
365
+ const proverOnly = config.enableProverNode && config.disableValidator;
366
+ if (proverOnly) {
367
+ log.info('Starting in prover-only mode: skipping validator, sequencer, sentinel, and slasher subsystems');
411
368
  }
412
- }
413
369
 
414
- // If there's no validator client, create a ProposalHandler to handle block and checkpoint proposals
415
- // for monitoring or reexecution. Reexecution (default) allows us to follow the pending chain,
416
- // while non-reexecution is used for validating the proposals and collecting their txs.
417
- // Checkpoint proposals rebuild blobs if the blob client can upload blobs.
418
- if (!validatorClient) {
419
- const reexecute = !!config.alwaysReexecuteBlockProposals;
420
- log.info(`Setting up proposal handler` + (reexecute ? ' with reexecution of proposals' : ''));
421
- createProposalHandler(config, {
422
- checkpointsBuilder: validatorCheckpointsBuilder,
423
- worldState: worldStateSynchronizer,
370
+ // create the tx pool and the p2p client, which will need the l2 block source
371
+ const p2pClient = await createP2PClient(
372
+ config,
373
+ archiver,
374
+ peerProofVerifier,
375
+ worldStateSynchronizer,
424
376
  epochCache,
425
- blockSource: archiver,
426
- l1ToL2MessageSource: archiver,
427
- p2pClient,
428
- blobClient,
377
+ feeProvider,
378
+ packageVersion,
429
379
  dateProvider,
430
380
  telemetry,
431
- }).register(p2pClient, reexecute, archiver);
432
- }
433
-
434
- // Start world state and wait for it to sync to the archiver.
435
- await worldStateSynchronizer.start();
436
-
437
- // Start p2p. Note that it depends on world state to be running.
438
- await p2pClient.start();
381
+ deps.p2pClientDeps,
382
+ );
383
+ started.push(p2pClient);
384
+
385
+ // We'll accumulate sentinel watchers here
386
+ const watchers: Watcher[] = [];
387
+
388
+ // Create FullNodeCheckpointsBuilder for block proposal handling and tx validation.
389
+ // Override maxTxsPerCheckpoint with the validator-specific limit if set.
390
+ const validatorCheckpointsBuilder = new FullNodeCheckpointsBuilder(
391
+ {
392
+ ...config,
393
+ l1GenesisTime,
394
+ slotDuration: Number(slotDuration),
395
+ rollupManaLimit,
396
+ maxTxsPerCheckpoint: config.validateMaxTxsPerCheckpoint,
397
+ },
398
+ worldStateSynchronizer,
399
+ archiver,
400
+ dateProvider,
401
+ telemetry,
402
+ );
439
403
 
440
- let validatorsSentinel: Awaited<ReturnType<typeof createSentinel>> | undefined;
441
- let epochPruneWatcher: EpochPruneWatcher | undefined;
442
- let attestationsBlockWatcher: AttestationsBlockWatcher | undefined;
404
+ let validatorClient: ValidatorClient | undefined;
443
405
 
444
- if (!proverOnly) {
445
- validatorsSentinel = await createSentinel(epochCache, archiver, p2pClient, config);
446
- if (validatorsSentinel && config.slashInactivityPenalty > 0n) {
447
- watchers.push(validatorsSentinel);
406
+ if (!proverOnly) {
407
+ // Create validator client if required
408
+ validatorClient = await createValidatorClient(config, {
409
+ checkpointsBuilder: validatorCheckpointsBuilder,
410
+ worldState: worldStateSynchronizer,
411
+ p2pClient,
412
+ telemetry,
413
+ dateProvider,
414
+ epochCache,
415
+ blockSource: archiver,
416
+ l1ToL2MessageSource: archiver,
417
+ keyStoreManager,
418
+ blobClient,
419
+ slashingProtectionDb: deps.slashingProtectionDb,
420
+ });
421
+
422
+ // If we have a validator client, register it as a source of offenses for the slasher,
423
+ // and have it register callbacks on the p2p client *before* we start it, otherwise messages
424
+ // like attestations or auths will fail.
425
+ if (validatorClient) {
426
+ watchers.push(validatorClient);
427
+
428
+ const vc = validatorClient;
429
+ const getValidatorAddresses = () => vc.getValidatorAddresses().map(a => a.toString());
430
+ validatorClient.getProposalHandler().register(p2pClient, true, archiver, getValidatorAddresses);
431
+
432
+ if (!options.dontStartSequencer) {
433
+ await validatorClient.registerHandlers();
434
+ }
435
+ }
448
436
  }
449
437
 
450
- if (config.slashPrunePenalty > 0n || config.slashDataWithholdingPenalty > 0n) {
451
- epochPruneWatcher = new EpochPruneWatcher(
452
- archiver,
453
- archiver,
438
+ // If there's no validator client, create a ProposalHandler to handle block and checkpoint proposals
439
+ // for monitoring or reexecution. Reexecution (default) allows us to follow the pending chain,
440
+ // while non-reexecution is used for validating the proposals and collecting their txs.
441
+ // Checkpoint proposals rebuild blobs if the blob client can upload blobs.
442
+ if (!validatorClient) {
443
+ const reexecute = !!config.alwaysReexecuteBlockProposals;
444
+ log.info(`Setting up proposal handler` + (reexecute ? ' with reexecution of proposals' : ''));
445
+ createProposalHandler(config, {
446
+ checkpointsBuilder: validatorCheckpointsBuilder,
447
+ worldState: worldStateSynchronizer,
454
448
  epochCache,
455
- p2pClient.getTxProvider(),
456
- validatorCheckpointsBuilder,
457
- config,
458
- );
459
- watchers.push(epochPruneWatcher);
449
+ blockSource: archiver,
450
+ l1ToL2MessageSource: archiver,
451
+ p2pClient,
452
+ blobClient,
453
+ dateProvider,
454
+ telemetry,
455
+ }).register(p2pClient, reexecute, archiver);
460
456
  }
461
457
 
462
- // We assume we want to slash for invalid attestations unless all max penalties are set to 0
463
- if (config.slashProposeInvalidAttestationsPenalty > 0n || config.slashAttestDescendantOfInvalidPenalty > 0n) {
464
- attestationsBlockWatcher = new AttestationsBlockWatcher(archiver, epochCache, config);
465
- watchers.push(attestationsBlockWatcher);
466
- }
467
- }
458
+ // Start world state and wait for it to sync to the archiver.
459
+ await worldStateSynchronizer.start();
468
460
 
469
- // Start p2p-related services once the archiver has completed sync
470
- void archiver
471
- .waitForInitialSync()
472
- .then(async () => {
473
- await p2pClient.start();
474
- await validatorsSentinel?.start();
475
- await epochPruneWatcher?.start();
476
- await attestationsBlockWatcher?.start();
477
- log.info(`All p2p services started`);
478
- })
479
- .catch(err => log.error('Failed to start p2p services after archiver sync', err));
480
-
481
- const globalVariableBuilder = new GlobalVariableBuilder(dateProvider, publicClient, {
482
- l1Contracts: config.l1Contracts,
483
- ethereumSlotDuration: config.ethereumSlotDuration,
484
- rollupVersion: BigInt(config.rollupVersion),
485
- l1GenesisTime,
486
- slotDuration: Number(slotDuration),
487
- });
461
+ // Start p2p. Note that it depends on world state to be running.
462
+ await p2pClient.start();
488
463
 
489
- // Validator enabled, create/start relevant service
490
- let sequencer: SequencerClient | undefined;
491
- let slasherClient: SlasherClientInterface | undefined;
492
- if (!config.disableValidator && validatorClient) {
493
- // We create a slasher only if we have a sequencer, since all slashing actions go through the sequencer publisher
494
- // as they are executed when the node is selected as proposer.
495
- const validatorAddresses = keyStoreManager
496
- ? NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager).getAddresses()
497
- : [];
498
-
499
- slasherClient = await createSlasher(
500
- config,
501
- config.l1Contracts,
502
- getPublicClient(config),
503
- watchers,
504
- dateProvider,
505
- epochCache,
506
- validatorAddresses,
507
- undefined, // logger
508
- );
509
- await slasherClient.start();
464
+ let validatorsSentinel: Awaited<ReturnType<typeof createSentinel>> | undefined;
465
+ let epochPruneWatcher: EpochPruneWatcher | undefined;
466
+ let attestationsBlockWatcher: AttestationsBlockWatcher | undefined;
510
467
 
511
- const l1TxUtils = config.sequencerPublisherForwarderAddress
512
- ? await createForwarderL1TxUtilsFromSigners(
513
- publicClient,
514
- keyStoreManager!.createAllValidatorPublisherSigners(),
515
- config.sequencerPublisherForwarderAddress,
516
- { ...config, scope: 'sequencer' },
517
- { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() },
518
- )
519
- : await createL1TxUtilsFromSigners(
468
+ if (!proverOnly) {
469
+ validatorsSentinel = await createSentinel(epochCache, archiver, p2pClient, config);
470
+ if (validatorsSentinel && config.slashInactivityPenalty > 0n) {
471
+ watchers.push(validatorsSentinel);
472
+ }
473
+
474
+ if (config.slashPrunePenalty > 0n || config.slashDataWithholdingPenalty > 0n) {
475
+ epochPruneWatcher = new EpochPruneWatcher(
476
+ archiver,
477
+ archiver,
478
+ epochCache,
479
+ p2pClient.getTxProvider(),
480
+ validatorCheckpointsBuilder,
481
+ config,
482
+ );
483
+ watchers.push(epochPruneWatcher);
484
+ }
485
+
486
+ // We assume we want to slash for invalid attestations unless all max penalties are set to 0
487
+ if (config.slashProposeInvalidAttestationsPenalty > 0n || config.slashAttestDescendantOfInvalidPenalty > 0n) {
488
+ attestationsBlockWatcher = new AttestationsBlockWatcher(archiver, epochCache, config);
489
+ watchers.push(attestationsBlockWatcher);
490
+ }
491
+ }
492
+
493
+ // Start p2p-related services once the archiver has completed sync
494
+ void archiver
495
+ .waitForInitialSync()
496
+ .then(async () => {
497
+ await validatorsSentinel?.start();
498
+ await epochPruneWatcher?.start();
499
+ await attestationsBlockWatcher?.start();
500
+ log.info(`All p2p services started`);
501
+ })
502
+ .catch(err => log.error('Failed to start p2p services after archiver sync', err));
503
+
504
+ // Validator enabled, create/start relevant service
505
+ let sequencer: SequencerClient | undefined;
506
+ let slasherClient: SlasherClientInterface | undefined;
507
+ if (!config.disableValidator && validatorClient) {
508
+ // We create a slasher only if we have a sequencer, since all slashing actions go through the sequencer publisher
509
+ // as they are executed when the node is selected as proposer.
510
+ const validatorAddresses = keyStoreManager
511
+ ? NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager).getAddresses()
512
+ : [];
513
+
514
+ slasherClient = await createSlasher(
515
+ config,
516
+ config.l1Contracts,
517
+ getPublicClient(config),
518
+ watchers,
519
+ dateProvider,
520
+ epochCache,
521
+ validatorAddresses,
522
+ undefined, // logger
523
+ );
524
+ await slasherClient.start();
525
+ started.push(slasherClient);
526
+
527
+ const l1TxUtils = config.sequencerPublisherForwarderAddress
528
+ ? await createForwarderL1TxUtilsFromSigners(
529
+ publicClient,
530
+ keyStoreManager!.createAllValidatorPublisherSigners(),
531
+ config.sequencerPublisherForwarderAddress,
532
+ { ...config, scope: 'sequencer' },
533
+ { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() },
534
+ )
535
+ : await createL1TxUtilsFromSigners(
536
+ publicClient,
537
+ keyStoreManager!.createAllValidatorPublisherSigners(),
538
+ { ...config, scope: 'sequencer' },
539
+ { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() },
540
+ );
541
+
542
+ // Create a funder L1TxUtils from the keystore funding account (if configured)
543
+ const fundingSigner = keyStoreManager?.createFundingSigner();
544
+ let funderL1TxUtils: L1TxUtils | undefined;
545
+ if (fundingSigner) {
546
+ const [funder] = await createL1TxUtilsFromSigners(
520
547
  publicClient,
521
- keyStoreManager!.createAllValidatorPublisherSigners(),
548
+ [fundingSigner],
522
549
  { ...config, scope: 'sequencer' },
523
- { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() },
550
+ { telemetry, logger: log.createChild('l1-tx-utils:funder'), dateProvider },
524
551
  );
552
+ funderL1TxUtils = funder;
553
+ }
525
554
 
526
- // Create a funder L1TxUtils from the keystore funding account (if configured)
527
- const fundingSigner = keyStoreManager?.createFundingSigner();
528
- let funderL1TxUtils: L1TxUtils | undefined;
529
- if (fundingSigner) {
530
- const [funder] = await createL1TxUtilsFromSigners(
531
- publicClient,
532
- [fundingSigner],
533
- { ...config, scope: 'sequencer' },
534
- { telemetry, logger: log.createChild('l1-tx-utils:funder'), dateProvider },
555
+ // Create and start the sequencer client
556
+ const checkpointsBuilder = new CheckpointsBuilder(
557
+ { ...config, l1GenesisTime, slotDuration: Number(slotDuration), rollupManaLimit },
558
+ worldStateSynchronizer,
559
+ archiver,
560
+ dateProvider,
561
+ telemetry,
562
+ debugLogStore,
535
563
  );
536
- funderL1TxUtils = funder;
564
+
565
+ sequencer = await SequencerClient.new(config, {
566
+ ...deps,
567
+ epochCache,
568
+ l1TxUtils,
569
+ funderL1TxUtils,
570
+ validatorClient,
571
+ p2pClient,
572
+ worldStateSynchronizer,
573
+ slasherClient,
574
+ checkpointsBuilder,
575
+ l2BlockSource: archiver,
576
+ l1ToL2MessageSource: archiver,
577
+ telemetry,
578
+ dateProvider,
579
+ blobClient,
580
+ nodeKeyStore: keyStoreManager!,
581
+ globalVariableBuilder,
582
+ });
537
583
  }
538
584
 
539
- // Create and start the sequencer client
540
- const checkpointsBuilder = new CheckpointsBuilder(
541
- { ...config, l1GenesisTime, slotDuration: Number(slotDuration), rollupManaLimit },
542
- worldStateSynchronizer,
543
- archiver,
544
- dateProvider,
545
- telemetry,
546
- debugLogStore,
547
- );
585
+ if (!options.dontStartSequencer && sequencer) {
586
+ await sequencer.start();
587
+ started.push(sequencer);
588
+ log.verbose(`Sequencer started`);
589
+ } else if (sequencer) {
590
+ log.warn(`Sequencer created but not started`);
591
+ }
548
592
 
549
- sequencer = await SequencerClient.new(config, {
550
- ...deps,
551
- epochCache,
552
- l1TxUtils,
553
- funderL1TxUtils,
554
- validatorClient,
593
+ // Create prover node subsystem if enabled
594
+ let proverNode: ProverNode | undefined;
595
+ if (config.enableProverNode) {
596
+ proverNode = await createProverNode(config, {
597
+ ...deps.proverNodeDeps,
598
+ telemetry,
599
+ dateProvider,
600
+ archiver,
601
+ worldStateSynchronizer,
602
+ p2pClient,
603
+ epochCache,
604
+ blobClient,
605
+ keyStoreManager,
606
+ });
607
+
608
+ if (!options.dontStartProverNode) {
609
+ await proverNode.start();
610
+ started.push(proverNode);
611
+ log.info(`Prover node subsystem started`);
612
+ } else {
613
+ log.info(`Prover node subsystem created but not started`);
614
+ }
615
+ }
616
+
617
+ const node = new AztecNodeService(
618
+ config,
555
619
  p2pClient,
620
+ archiver,
621
+ archiver,
622
+ archiver,
623
+ archiver,
556
624
  worldStateSynchronizer,
625
+ sequencer,
626
+ proverNode,
557
627
  slasherClient,
558
- checkpointsBuilder,
559
- l2BlockSource: archiver,
560
- l1ToL2MessageSource: archiver,
561
- telemetry,
562
- dateProvider,
563
- blobClient,
564
- nodeKeyStore: keyStoreManager!,
628
+ validatorsSentinel,
629
+ epochPruneWatcher,
630
+ ethereumChain.chainInfo.id,
631
+ config.rollupVersion,
565
632
  globalVariableBuilder,
566
- });
567
- }
568
-
569
- if (!options.dontStartSequencer && sequencer) {
570
- await sequencer.start();
571
- log.verbose(`Sequencer started`);
572
- } else if (sequencer) {
573
- log.warn(`Sequencer created but not started`);
574
- }
575
-
576
- // Create prover node subsystem if enabled
577
- let proverNode: ProverNode | undefined;
578
- if (config.enableProverNode) {
579
- proverNode = await createProverNode(config, {
580
- ...deps.proverNodeDeps,
581
- telemetry,
582
- dateProvider,
583
- archiver,
584
- worldStateSynchronizer,
585
- p2pClient,
633
+ feeProvider,
586
634
  epochCache,
635
+ packageVersion,
636
+ peerProofVerifier,
637
+ rpcProofVerifier,
638
+ telemetry,
639
+ log,
587
640
  blobClient,
641
+ validatorClient,
588
642
  keyStoreManager,
589
- });
643
+ debugLogStore,
644
+ );
590
645
 
591
- if (!options.dontStartProverNode) {
592
- await proverNode.start();
593
- log.info(`Prover node subsystem started`);
594
- } else {
595
- log.info(`Prover node subsystem created but not started`);
646
+ return node;
647
+ } catch (err) {
648
+ log.error('Failed during node creation, stopping started resources', err);
649
+ for (const resource of started.reverse()) {
650
+ await tryStop(resource);
596
651
  }
652
+ throw err;
597
653
  }
598
-
599
- const node = new AztecNodeService(
600
- config,
601
- p2pClient,
602
- archiver,
603
- archiver,
604
- archiver,
605
- archiver,
606
- worldStateSynchronizer,
607
- sequencer,
608
- proverNode,
609
- slasherClient,
610
- validatorsSentinel,
611
- epochPruneWatcher,
612
- ethereumChain.chainInfo.id,
613
- config.rollupVersion,
614
- globalVariableBuilder,
615
- epochCache,
616
- packageVersion,
617
- peerProofVerifier,
618
- rpcProofVerifier,
619
- telemetry,
620
- log,
621
- blobClient,
622
- validatorClient,
623
- keyStoreManager,
624
- debugLogStore,
625
- );
626
-
627
- return node;
628
654
  }
629
655
 
630
656
  /**
@@ -764,12 +790,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
764
790
  return this.blockSource.getCheckpointsDataForEpoch(epochNumber);
765
791
  }
766
792
 
767
- /**
768
- * Method to fetch the current min L2 fees.
769
- * @returns The current min L2 fees.
770
- */
771
793
  public async getCurrentMinFees(): Promise<GasFees> {
772
- return await this.globalVariableBuilder.getCurrentMinFees();
794
+ return await this.feeProvider.getCurrentMinFees();
795
+ }
796
+
797
+ /** Returns predicted min fees for the current slot and next N slots. */
798
+ public async getPredictedMinFees(manaUsage?: ManaUsageEstimate): Promise<GasFees[]> {
799
+ return await this.feeProvider.getPredictedMinFees(manaUsage);
773
800
  }
774
801
 
775
802
  public async getMaxPriorityFees(): Promise<GasFees> {
@@ -1052,7 +1079,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1052
1079
  );
1053
1080
 
1054
1081
  // Build a map from block number to block hash
1055
- const blockNumberToHash = new Map<BlockNumber, Fr>();
1082
+ const blockNumberToHash = new Map<BlockNumber, BlockHash>();
1056
1083
  for (let i = 0; i < uniqueBlockNumbers.length; i++) {
1057
1084
  const blockHash = blockHashes[i];
1058
1085
  if (blockHash === undefined) {
@@ -1070,13 +1097,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1070
1097
  if (blockNumber === undefined) {
1071
1098
  throw new Error(`Block number not found for leaf index ${index} in tree ${MerkleTreeId[treeId]}`);
1072
1099
  }
1073
- const blockHash = blockNumberToHash.get(blockNumber);
1074
- if (blockHash === undefined) {
1100
+ const l2BlockHash = blockNumberToHash.get(blockNumber);
1101
+ if (l2BlockHash === undefined) {
1075
1102
  throw new Error(`Block hash not found for block number ${blockNumber}`);
1076
1103
  }
1077
1104
  return {
1078
1105
  l2BlockNumber: blockNumber,
1079
- l2BlockHash: new BlockHash(blockHash),
1106
+ l2BlockHash,
1080
1107
  data: index,
1081
1108
  };
1082
1109
  });
@@ -1488,7 +1515,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1488
1515
  return Promise.resolve();
1489
1516
  }
1490
1517
 
1491
- public async rollbackTo(targetBlock: BlockNumber, force?: boolean): Promise<void> {
1518
+ public async rollbackTo(targetBlock: BlockNumber, force?: boolean, resumeSync = true): Promise<void> {
1492
1519
  const archiver = this.blockSource as Archiver;
1493
1520
  if (!('rollbackTo' in archiver)) {
1494
1521
  throw new Error('Archiver implementation does not support rollbacks.');
@@ -1518,9 +1545,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1518
1545
  this.log.error(`Error during rollback`, err);
1519
1546
  throw err;
1520
1547
  } finally {
1521
- this.log.info(`Resuming world state and archiver sync.`);
1522
- this.worldStateSynchronizer.resumeSync();
1523
- archiver.resume();
1548
+ if (resumeSync) {
1549
+ this.log.info(`Resuming world state and archiver sync.`);
1550
+ this.worldStateSynchronizer.resumeSync();
1551
+ archiver.resume();
1552
+ } else {
1553
+ this.log.info(`Sync left paused after rollback (resumeSync=false).`);
1554
+ }
1524
1555
  }
1525
1556
  }
1526
1557
 
@@ -1537,19 +1568,12 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1537
1568
  return Promise.resolve();
1538
1569
  }
1539
1570
 
1540
- public getSlashPayloads(): Promise<SlashPayloadRound[]> {
1541
- if (!this.slasherClient) {
1542
- throw new Error(`Slasher client not enabled`);
1543
- }
1544
- return this.slasherClient.getSlashPayloads();
1545
- }
1546
-
1547
1571
  public getSlashOffenses(round: bigint | 'all' | 'current'): Promise<Offense[]> {
1548
1572
  if (!this.slasherClient) {
1549
1573
  throw new Error(`Slasher client not enabled`);
1550
1574
  }
1551
1575
  if (round === 'all') {
1552
- return this.slasherClient.getPendingOffenses();
1576
+ return this.slasherClient.getOffenses();
1553
1577
  } else {
1554
1578
  return this.slasherClient.gatherOffensesForRound(round === 'current' ? undefined : BigInt(round));
1555
1579
  }
@@ -1643,6 +1667,40 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1643
1667
  this.log.info('Keystore reloaded: coinbase, feeRecipient, and attester keys updated');
1644
1668
  }
1645
1669
 
1670
+ public async mineBlock(): Promise<void> {
1671
+ if (!this.sequencer) {
1672
+ throw new BadRequestError('Cannot mine block: no sequencer is running');
1673
+ }
1674
+
1675
+ const currentBlockNumber = await this.getBlockNumber();
1676
+
1677
+ // Use slot duration + 50% buffer as the timeout so this works on running networks too
1678
+ const { slotDuration } = await this.blockSource.getL1Constants();
1679
+ const timeoutSeconds = Math.ceil(slotDuration * 1.5);
1680
+
1681
+ // Temporarily set minTxsPerBlock to 0 so the sequencer produces a block even with no txs
1682
+ const originalMinTxsPerBlock = this.sequencer.getSequencer().getConfig().minTxsPerBlock;
1683
+ this.sequencer.updateConfig({ minTxsPerBlock: 0 });
1684
+
1685
+ try {
1686
+ // Trigger the sequencer to produce a block immediately
1687
+ void this.sequencer.trigger();
1688
+
1689
+ // Wait for the new L2 block to appear
1690
+ await retryUntil(
1691
+ async () => {
1692
+ const newBlockNumber = await this.getBlockNumber();
1693
+ return newBlockNumber > currentBlockNumber ? true : undefined;
1694
+ },
1695
+ 'mineBlock',
1696
+ timeoutSeconds,
1697
+ 0.1,
1698
+ );
1699
+ } finally {
1700
+ this.sequencer.updateConfig({ minTxsPerBlock: originalMinTxsPerBlock });
1701
+ }
1702
+ }
1703
+
1646
1704
  #getInitialHeaderHash(): Promise<BlockHash> {
1647
1705
  if (!this.initialHeaderHashPromise) {
1648
1706
  this.initialHeaderHashPromise = this.worldStateSynchronizer.getCommitted().getInitialHeader().hash();
@@ -1674,18 +1732,19 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1674
1732
  if (BlockHash.isBlockHash(block)) {
1675
1733
  const initialBlockHash = await this.#getInitialHeaderHash();
1676
1734
  if (block.equals(initialBlockHash)) {
1677
- // Block source doesn't handle initial header so we need to handle the case separately.
1678
- return this.worldStateSynchronizer.getSnapshot(BlockNumber.ZERO);
1679
- }
1680
-
1681
- const header = await this.blockSource.getBlockHeaderByHash(block);
1682
- if (!header) {
1683
- throw new Error(
1684
- `Block hash ${block.toString()} not found when querying world state. If the node API has been queried with anchor block hash possibly a reorg has occurred.`,
1685
- );
1735
+ // Block 0 is a first-class historical block: its state lives in the trees' persisted
1736
+ // block-0 payload. Resolving the genesis hash to block number 0 lets the snapshot path
1737
+ // pin reads to genesis state even after the node has advanced past it.
1738
+ blockNumber = BlockNumber.ZERO;
1739
+ } else {
1740
+ const header = await this.blockSource.getBlockHeaderByHash(block);
1741
+ if (!header) {
1742
+ throw new Error(
1743
+ `Block hash ${block.toString()} not found when querying world state. If the node API has been queried with anchor block hash possibly a reorg has occurred.`,
1744
+ );
1745
+ }
1746
+ blockNumber = header.getBlockNumber();
1686
1747
  }
1687
-
1688
- blockNumber = header.getBlockNumber();
1689
1748
  } else {
1690
1749
  blockNumber = block as BlockNumber;
1691
1750
  }
@@ -1701,9 +1760,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1701
1760
  // Double-check world-state synced to the same block hash as was requested
1702
1761
  if (BlockHash.isBlockHash(block)) {
1703
1762
  const blockHash = await snapshot.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber));
1704
- if (!blockHash || !new BlockHash(blockHash).equals(block)) {
1763
+ if (!blockHash || !block.equals(blockHash)) {
1764
+ const initialBlockHash = await this.#getInitialHeaderHash();
1705
1765
  throw new Error(
1706
- `Block hash ${block.toString()} not found in world state at block number ${blockNumber}. If the node API has been queried with anchor block hash possibly a reorg has occurred.`,
1766
+ `Block hash ${block.toString()} not found in world state at block number ${blockNumber} (world state has ${blockHash?.toString() ?? 'no hash'} at that index, genesis header hash is ${initialBlockHash.toString()}). If the node API has been queried with anchor block hash possibly a reorg has occurred.`,
1707
1767
  );
1708
1768
  }
1709
1769
  }