@aztec/aztec-node 0.0.1-commit.f650c0a5c → 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';
@@ -86,6 +93,7 @@ import type { NullifierLeafPreimage, PublicDataTreeLeafPreimage } from '@aztec/s
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,
@@ -127,7 +135,7 @@ import { NodeMetrics } from './node_metrics.js';
127
135
  /**
128
136
  * The aztec node.
129
137
  */
130
- export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
138
+ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDebug, Traceable {
131
139
  private metrics: NodeMetrics;
132
140
  private initialHeaderHashPromise: Promise<BlockHash> | undefined = undefined;
133
141
 
@@ -152,6 +160,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
152
160
  protected readonly l1ChainId: number,
153
161
  protected readonly version: number,
154
162
  protected readonly globalVariableBuilder: GlobalVariableBuilderInterface,
163
+ protected readonly feeProvider: FeeProvider,
155
164
  protected readonly epochCache: EpochCacheInterface,
156
165
  protected readonly packageVersion: string,
157
166
  private peerProofVerifier: ClientProtocolCircuitVerifier,
@@ -304,323 +313,344 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
304
313
 
305
314
  const epochCache = await EpochCache.create(config.l1Contracts.rollupAddress, config, { dateProvider });
306
315
 
307
- const archiver = await createArchiver(
308
- config,
309
- { blobClient, epochCache, telemetry, dateProvider },
310
- { blockUntilSync: !config.skipArchiverInitialSync },
311
- );
312
-
313
- // now create the merkle trees and the world state synchronizer
314
- const worldStateSynchronizer = await createWorldStateSynchronizer(config, archiver, options.genesis, telemetry);
315
- const useRealVerifiers = config.realProofs || config.debugForceTxProofVerification;
316
- let peerProofVerifier: ClientProtocolCircuitVerifier;
317
- let rpcProofVerifier: ClientProtocolCircuitVerifier;
318
- if (useRealVerifiers) {
319
- peerProofVerifier = await BatchChonkVerifier.new(config, config.bbChonkVerifyMaxBatch, 'peer');
320
- const rpcVerifier = await BBCircuitVerifier.new(config);
321
- rpcProofVerifier = new QueuedIVCVerifier(rpcVerifier, config.numConcurrentIVCVerifiers);
322
- } else {
323
- peerProofVerifier = new TestCircuitVerifier(config.proverTestVerificationDelayMs);
324
- rpcProofVerifier = new TestCircuitVerifier(config.proverTestVerificationDelayMs);
325
- }
326
-
327
- let debugLogStore: DebugLogStore;
328
- if (!config.realProofs) {
329
- log.warn(`Aztec node is accepting fake proofs`);
330
-
331
- debugLogStore = new InMemoryDebugLogStore();
332
- log.info(
333
- '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 },
334
323
  );
335
- } else {
336
- debugLogStore = new NullDebugLogStore();
337
- }
338
-
339
- const proverOnly = config.enableProverNode && config.disableValidator;
340
- if (proverOnly) {
341
- log.info('Starting in prover-only mode: skipping validator, sequencer, sentinel, and slasher subsystems');
342
- }
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);
343
341
 
344
- // create the tx pool and the p2p client, which will need the l2 block source
345
- const p2pClient = await createP2PClient(
346
- config,
347
- archiver,
348
- peerProofVerifier,
349
- worldStateSynchronizer,
350
- epochCache,
351
- packageVersion,
352
- dateProvider,
353
- telemetry,
354
- deps.p2pClientDeps,
355
- );
342
+ let debugLogStore: DebugLogStore;
343
+ if (!config.realProofs) {
344
+ log.warn(`Aztec node is accepting fake proofs`);
356
345
 
357
- // We'll accumulate sentinel watchers here
358
- 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
+ }
359
353
 
360
- // Create FullNodeCheckpointsBuilder for block proposal handling and tx validation.
361
- // Override maxTxsPerCheckpoint with the validator-specific limit if set.
362
- const validatorCheckpointsBuilder = new FullNodeCheckpointsBuilder(
363
- {
364
- ...config,
354
+ const globalVariableBuilderConfig = {
355
+ l1Contracts: config.l1Contracts,
356
+ ethereumSlotDuration: config.ethereumSlotDuration,
357
+ rollupVersion: BigInt(config.rollupVersion),
365
358
  l1GenesisTime,
366
359
  slotDuration: Number(slotDuration),
367
- rollupManaLimit,
368
- maxTxsPerCheckpoint: config.validateMaxTxsPerCheckpoint,
369
- },
370
- worldStateSynchronizer,
371
- archiver,
372
- dateProvider,
373
- telemetry,
374
- );
375
-
376
- let validatorClient: ValidatorClient | undefined;
377
-
378
- if (!proverOnly) {
379
- // Create validator client if required
380
- validatorClient = await createValidatorClient(config, {
381
- checkpointsBuilder: validatorCheckpointsBuilder,
382
- worldState: worldStateSynchronizer,
383
- p2pClient,
384
- telemetry,
385
- dateProvider,
386
- epochCache,
387
- blockSource: archiver,
388
- l1ToL2MessageSource: archiver,
389
- keyStoreManager,
390
- blobClient,
391
- slashingProtectionDb: deps.slashingProtectionDb,
392
- });
393
-
394
- // If we have a validator client, register it as a source of offenses for the slasher,
395
- // and have it register callbacks on the p2p client *before* we start it, otherwise messages
396
- // like attestations or auths will fail.
397
- if (validatorClient) {
398
- watchers.push(validatorClient);
360
+ };
399
361
 
400
- const vc = validatorClient;
401
- const getValidatorAddresses = () => vc.getValidatorAddresses().map(a => a.toString());
402
- validatorClient.getProposalHandler().register(p2pClient, true, archiver, getValidatorAddresses);
362
+ const globalVariableBuilder = new GlobalVariableBuilder(dateProvider, publicClient, globalVariableBuilderConfig);
363
+ const feeProvider = new FeeProviderImpl(dateProvider, publicClient, globalVariableBuilderConfig);
403
364
 
404
- if (!options.dontStartSequencer) {
405
- await validatorClient.registerHandlers();
406
- }
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');
407
368
  }
408
- }
409
369
 
410
- // If there's no validator client, create a ProposalHandler to handle block and checkpoint proposals
411
- // for monitoring or reexecution. Reexecution (default) allows us to follow the pending chain,
412
- // while non-reexecution is used for validating the proposals and collecting their txs.
413
- // Checkpoint proposals rebuild blobs if the blob client can upload blobs.
414
- if (!validatorClient) {
415
- const reexecute = !!config.alwaysReexecuteBlockProposals;
416
- log.info(`Setting up proposal handler` + (reexecute ? ' with reexecution of proposals' : ''));
417
- createProposalHandler(config, {
418
- checkpointsBuilder: validatorCheckpointsBuilder,
419
- 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,
420
376
  epochCache,
421
- blockSource: archiver,
422
- l1ToL2MessageSource: archiver,
423
- p2pClient,
424
- blobClient,
377
+ feeProvider,
378
+ packageVersion,
425
379
  dateProvider,
426
380
  telemetry,
427
- }).register(p2pClient, reexecute, archiver);
428
- }
429
-
430
- // Start world state and wait for it to sync to the archiver.
431
- await worldStateSynchronizer.start();
432
-
433
- // Start p2p. Note that it depends on world state to be running.
434
- 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
+ );
435
403
 
436
- let validatorsSentinel: Awaited<ReturnType<typeof createSentinel>> | undefined;
437
- let epochPruneWatcher: EpochPruneWatcher | undefined;
438
- let attestationsBlockWatcher: AttestationsBlockWatcher | undefined;
404
+ let validatorClient: ValidatorClient | undefined;
439
405
 
440
- if (!proverOnly) {
441
- validatorsSentinel = await createSentinel(epochCache, archiver, p2pClient, config);
442
- if (validatorsSentinel && config.slashInactivityPenalty > 0n) {
443
- 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
+ }
444
436
  }
445
437
 
446
- if (config.slashPrunePenalty > 0n || config.slashDataWithholdingPenalty > 0n) {
447
- epochPruneWatcher = new EpochPruneWatcher(
448
- archiver,
449
- 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,
450
448
  epochCache,
451
- p2pClient.getTxProvider(),
452
- validatorCheckpointsBuilder,
453
- config,
454
- );
455
- watchers.push(epochPruneWatcher);
449
+ blockSource: archiver,
450
+ l1ToL2MessageSource: archiver,
451
+ p2pClient,
452
+ blobClient,
453
+ dateProvider,
454
+ telemetry,
455
+ }).register(p2pClient, reexecute, archiver);
456
456
  }
457
457
 
458
- // We assume we want to slash for invalid attestations unless all max penalties are set to 0
459
- if (config.slashProposeInvalidAttestationsPenalty > 0n || config.slashAttestDescendantOfInvalidPenalty > 0n) {
460
- attestationsBlockWatcher = new AttestationsBlockWatcher(archiver, epochCache, config);
461
- watchers.push(attestationsBlockWatcher);
462
- }
463
- }
458
+ // Start world state and wait for it to sync to the archiver.
459
+ await worldStateSynchronizer.start();
464
460
 
465
- // Start p2p-related services once the archiver has completed sync
466
- void archiver
467
- .waitForInitialSync()
468
- .then(async () => {
469
- await p2pClient.start();
470
- await validatorsSentinel?.start();
471
- await epochPruneWatcher?.start();
472
- await attestationsBlockWatcher?.start();
473
- log.info(`All p2p services started`);
474
- })
475
- .catch(err => log.error('Failed to start p2p services after archiver sync', err));
476
-
477
- const globalVariableBuilder = new GlobalVariableBuilder(dateProvider, publicClient, {
478
- l1Contracts: config.l1Contracts,
479
- ethereumSlotDuration: config.ethereumSlotDuration,
480
- rollupVersion: BigInt(config.rollupVersion),
481
- l1GenesisTime,
482
- slotDuration: Number(slotDuration),
483
- });
461
+ // Start p2p. Note that it depends on world state to be running.
462
+ await p2pClient.start();
484
463
 
485
- // Validator enabled, create/start relevant service
486
- let sequencer: SequencerClient | undefined;
487
- let slasherClient: SlasherClientInterface | undefined;
488
- if (!config.disableValidator && validatorClient) {
489
- // We create a slasher only if we have a sequencer, since all slashing actions go through the sequencer publisher
490
- // as they are executed when the node is selected as proposer.
491
- const validatorAddresses = keyStoreManager
492
- ? NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager).getAddresses()
493
- : [];
494
-
495
- slasherClient = await createSlasher(
496
- config,
497
- config.l1Contracts,
498
- getPublicClient(config),
499
- watchers,
500
- dateProvider,
501
- epochCache,
502
- validatorAddresses,
503
- undefined, // logger
504
- );
505
- await slasherClient.start();
464
+ let validatorsSentinel: Awaited<ReturnType<typeof createSentinel>> | undefined;
465
+ let epochPruneWatcher: EpochPruneWatcher | undefined;
466
+ let attestationsBlockWatcher: AttestationsBlockWatcher | undefined;
506
467
 
507
- const l1TxUtils = config.sequencerPublisherForwarderAddress
508
- ? await createForwarderL1TxUtilsFromSigners(
509
- publicClient,
510
- keyStoreManager!.createAllValidatorPublisherSigners(),
511
- config.sequencerPublisherForwarderAddress,
512
- { ...config, scope: 'sequencer' },
513
- { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() },
514
- )
515
- : 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(
516
547
  publicClient,
517
- keyStoreManager!.createAllValidatorPublisherSigners(),
548
+ [fundingSigner],
518
549
  { ...config, scope: 'sequencer' },
519
- { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() },
550
+ { telemetry, logger: log.createChild('l1-tx-utils:funder'), dateProvider },
520
551
  );
552
+ funderL1TxUtils = funder;
553
+ }
521
554
 
522
- // Create a funder L1TxUtils from the keystore funding account (if configured)
523
- const fundingSigner = keyStoreManager?.createFundingSigner();
524
- let funderL1TxUtils: L1TxUtils | undefined;
525
- if (fundingSigner) {
526
- const [funder] = await createL1TxUtilsFromSigners(
527
- publicClient,
528
- [fundingSigner],
529
- { ...config, scope: 'sequencer' },
530
- { 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,
531
563
  );
532
- 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
+ });
533
583
  }
534
584
 
535
- // Create and start the sequencer client
536
- const checkpointsBuilder = new CheckpointsBuilder(
537
- { ...config, l1GenesisTime, slotDuration: Number(slotDuration), rollupManaLimit },
538
- worldStateSynchronizer,
539
- archiver,
540
- dateProvider,
541
- telemetry,
542
- debugLogStore,
543
- );
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
+ }
544
592
 
545
- sequencer = await SequencerClient.new(config, {
546
- ...deps,
547
- epochCache,
548
- l1TxUtils,
549
- funderL1TxUtils,
550
- 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,
551
619
  p2pClient,
620
+ archiver,
621
+ archiver,
622
+ archiver,
623
+ archiver,
552
624
  worldStateSynchronizer,
625
+ sequencer,
626
+ proverNode,
553
627
  slasherClient,
554
- checkpointsBuilder,
555
- l2BlockSource: archiver,
556
- l1ToL2MessageSource: archiver,
557
- telemetry,
558
- dateProvider,
559
- blobClient,
560
- nodeKeyStore: keyStoreManager!,
628
+ validatorsSentinel,
629
+ epochPruneWatcher,
630
+ ethereumChain.chainInfo.id,
631
+ config.rollupVersion,
561
632
  globalVariableBuilder,
562
- });
563
- }
564
-
565
- if (!options.dontStartSequencer && sequencer) {
566
- await sequencer.start();
567
- log.verbose(`Sequencer started`);
568
- } else if (sequencer) {
569
- log.warn(`Sequencer created but not started`);
570
- }
571
-
572
- // Create prover node subsystem if enabled
573
- let proverNode: ProverNode | undefined;
574
- if (config.enableProverNode) {
575
- proverNode = await createProverNode(config, {
576
- ...deps.proverNodeDeps,
577
- telemetry,
578
- dateProvider,
579
- archiver,
580
- worldStateSynchronizer,
581
- p2pClient,
633
+ feeProvider,
582
634
  epochCache,
635
+ packageVersion,
636
+ peerProofVerifier,
637
+ rpcProofVerifier,
638
+ telemetry,
639
+ log,
583
640
  blobClient,
641
+ validatorClient,
584
642
  keyStoreManager,
585
- });
643
+ debugLogStore,
644
+ );
586
645
 
587
- if (!options.dontStartProverNode) {
588
- await proverNode.start();
589
- log.info(`Prover node subsystem started`);
590
- } else {
591
- 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);
592
651
  }
652
+ throw err;
593
653
  }
594
-
595
- const node = new AztecNodeService(
596
- config,
597
- p2pClient,
598
- archiver,
599
- archiver,
600
- archiver,
601
- archiver,
602
- worldStateSynchronizer,
603
- sequencer,
604
- proverNode,
605
- slasherClient,
606
- validatorsSentinel,
607
- epochPruneWatcher,
608
- ethereumChain.chainInfo.id,
609
- config.rollupVersion,
610
- globalVariableBuilder,
611
- epochCache,
612
- packageVersion,
613
- peerProofVerifier,
614
- rpcProofVerifier,
615
- telemetry,
616
- log,
617
- blobClient,
618
- validatorClient,
619
- keyStoreManager,
620
- debugLogStore,
621
- );
622
-
623
- return node;
624
654
  }
625
655
 
626
656
  /**
@@ -760,12 +790,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
760
790
  return this.blockSource.getCheckpointsDataForEpoch(epochNumber);
761
791
  }
762
792
 
763
- /**
764
- * Method to fetch the current min L2 fees.
765
- * @returns The current min L2 fees.
766
- */
767
793
  public async getCurrentMinFees(): Promise<GasFees> {
768
- 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);
769
800
  }
770
801
 
771
802
  public async getMaxPriorityFees(): Promise<GasFees> {
@@ -1048,7 +1079,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1048
1079
  );
1049
1080
 
1050
1081
  // Build a map from block number to block hash
1051
- const blockNumberToHash = new Map<BlockNumber, Fr>();
1082
+ const blockNumberToHash = new Map<BlockNumber, BlockHash>();
1052
1083
  for (let i = 0; i < uniqueBlockNumbers.length; i++) {
1053
1084
  const blockHash = blockHashes[i];
1054
1085
  if (blockHash === undefined) {
@@ -1066,13 +1097,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1066
1097
  if (blockNumber === undefined) {
1067
1098
  throw new Error(`Block number not found for leaf index ${index} in tree ${MerkleTreeId[treeId]}`);
1068
1099
  }
1069
- const blockHash = blockNumberToHash.get(blockNumber);
1070
- if (blockHash === undefined) {
1100
+ const l2BlockHash = blockNumberToHash.get(blockNumber);
1101
+ if (l2BlockHash === undefined) {
1071
1102
  throw new Error(`Block hash not found for block number ${blockNumber}`);
1072
1103
  }
1073
1104
  return {
1074
1105
  l2BlockNumber: blockNumber,
1075
- l2BlockHash: new BlockHash(blockHash),
1106
+ l2BlockHash,
1076
1107
  data: index,
1077
1108
  };
1078
1109
  });
@@ -1636,6 +1667,40 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1636
1667
  this.log.info('Keystore reloaded: coinbase, feeRecipient, and attester keys updated');
1637
1668
  }
1638
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
+
1639
1704
  #getInitialHeaderHash(): Promise<BlockHash> {
1640
1705
  if (!this.initialHeaderHashPromise) {
1641
1706
  this.initialHeaderHashPromise = this.worldStateSynchronizer.getCommitted().getInitialHeader().hash();
@@ -1667,18 +1732,19 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1667
1732
  if (BlockHash.isBlockHash(block)) {
1668
1733
  const initialBlockHash = await this.#getInitialHeaderHash();
1669
1734
  if (block.equals(initialBlockHash)) {
1670
- // Block source doesn't handle initial header so we need to handle the case separately.
1671
- return this.worldStateSynchronizer.getSnapshot(BlockNumber.ZERO);
1672
- }
1673
-
1674
- const header = await this.blockSource.getBlockHeaderByHash(block);
1675
- if (!header) {
1676
- throw new Error(
1677
- `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.`,
1678
- );
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();
1679
1747
  }
1680
-
1681
- blockNumber = header.getBlockNumber();
1682
1748
  } else {
1683
1749
  blockNumber = block as BlockNumber;
1684
1750
  }
@@ -1694,9 +1760,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1694
1760
  // Double-check world-state synced to the same block hash as was requested
1695
1761
  if (BlockHash.isBlockHash(block)) {
1696
1762
  const blockHash = await snapshot.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber));
1697
- if (!blockHash || !new BlockHash(blockHash).equals(block)) {
1763
+ if (!blockHash || !block.equals(blockHash)) {
1764
+ const initialBlockHash = await this.#getInitialHeaderHash();
1698
1765
  throw new Error(
1699
- `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.`,
1700
1767
  );
1701
1768
  }
1702
1769
  }