@aztec/aztec-node 0.0.1-commit.3469e52 → 0.0.1-commit.35158ae7e

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.
@@ -372,14 +372,16 @@ function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) {
372
372
  }
373
373
  var _dec, _initProto;
374
374
  import { createArchiver } from '@aztec/archiver';
375
- import { BBCircuitVerifier, QueuedIVCVerifier, TestCircuitVerifier } from '@aztec/bb-prover';
375
+ import { BBCircuitVerifier, BatchChonkVerifier, QueuedIVCVerifier } from '@aztec/bb-prover';
376
+ import { TestCircuitVerifier } from '@aztec/bb-prover/test';
376
377
  import { createBlobClientWithFileStores } from '@aztec/blob-client/client';
378
+ import { Blob } from '@aztec/blob-lib';
377
379
  import { EpochCache } from '@aztec/epoch-cache';
378
380
  import { createEthereumChain } from '@aztec/ethereum/chain';
379
- import { getPublicClient } from '@aztec/ethereum/client';
381
+ import { getPublicClient, makeL1HttpTransport } from '@aztec/ethereum/client';
380
382
  import { RegistryContract, RollupContract } from '@aztec/ethereum/contracts';
381
- import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
382
- import { compactArray, pick } from '@aztec/foundation/collection';
383
+ import { BlockNumber } from '@aztec/foundation/branded-types';
384
+ import { chunkBy, compactArray, pick, unique } from '@aztec/foundation/collection';
383
385
  import { Fr } from '@aztec/foundation/curves/bn254';
384
386
  import { EthAddress } from '@aztec/foundation/eth-address';
385
387
  import { BadRequestError } from '@aztec/foundation/json-rpc';
@@ -389,28 +391,30 @@ import { DateProvider, Timer } from '@aztec/foundation/timer';
389
391
  import { MembershipWitness } from '@aztec/foundation/trees';
390
392
  import { KeystoreManager, loadKeystores, mergeKeystores } from '@aztec/node-keystore';
391
393
  import { trySnapshotSync, uploadSnapshot } from '@aztec/node-lib/actions';
392
- import { createForwarderL1TxUtilsFromEthSigner, createL1TxUtilsWithBlobsFromEthSigner } from '@aztec/node-lib/factories';
393
- import { createP2PClient, getDefaultAllowedSetupFunctions } from '@aztec/p2p';
394
+ import { createForwarderL1TxUtilsFromSigners, createL1TxUtilsFromSigners } from '@aztec/node-lib/factories';
395
+ import { createP2PClient, createTxValidatorForAcceptingTxsOverRPC, getDefaultAllowedSetupFunctions } from '@aztec/p2p';
394
396
  import { ProtocolContractAddress } from '@aztec/protocol-contracts';
397
+ import { createProverNode } from '@aztec/prover-node';
398
+ import { createKeyStoreForProver } from '@aztec/prover-node/config';
395
399
  import { GlobalVariableBuilder, SequencerClient } from '@aztec/sequencer-client';
396
400
  import { PublicProcessorFactory } from '@aztec/simulator/server';
397
401
  import { AttestationsBlockWatcher, EpochPruneWatcher, createSlasher } from '@aztec/slasher';
398
402
  import { CollectionLimitsConfig, PublicSimulatorConfig } from '@aztec/stdlib/avm';
399
403
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
400
- import { L2BlockHash, L2BlockNew } from '@aztec/stdlib/block';
404
+ import { BlockHash, L2Block } from '@aztec/stdlib/block';
401
405
  import { GasFees } from '@aztec/stdlib/gas';
402
406
  import { computePublicDataTreeLeafSlot } from '@aztec/stdlib/hash';
403
407
  import { AztecNodeAdminConfigSchema } from '@aztec/stdlib/interfaces/client';
404
408
  import { tryStop } from '@aztec/stdlib/interfaces/server';
409
+ import { InMemoryDebugLogStore, NullDebugLogStore } from '@aztec/stdlib/logs';
405
410
  import { InboxLeaf } from '@aztec/stdlib/messaging';
406
- import { P2PClientType } from '@aztec/stdlib/p2p';
407
411
  import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
408
412
  import { PublicSimulationOutput, TxReceipt, TxStatus } from '@aztec/stdlib/tx';
409
413
  import { getPackageVersion } from '@aztec/stdlib/update-checker';
410
414
  import { Attributes, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
411
- import { FullNodeCheckpointsBuilder as CheckpointsBuilder, FullNodeCheckpointsBuilder, NodeKeystoreAdapter, ValidatorClient, createBlockProposalHandler, createValidatorClient, createValidatorForAcceptingTxs } from '@aztec/validator-client';
415
+ import { FullNodeCheckpointsBuilder as CheckpointsBuilder, FullNodeCheckpointsBuilder, NodeKeystoreAdapter, ValidatorClient, createBlockProposalHandler, createValidatorClient } from '@aztec/validator-client';
412
416
  import { createWorldStateSynchronizer } from '@aztec/world-state';
413
- import { createPublicClient, fallback, http } from 'viem';
417
+ import { createPublicClient } from 'viem';
414
418
  import { createSentinel } from '../sentinel/factory.js';
415
419
  import { createKeyStoreForValidator } from './config.js';
416
420
  import { NodeMetrics } from './node_metrics.js';
@@ -428,6 +432,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
428
432
  l1ToL2MessageSource;
429
433
  worldStateSynchronizer;
430
434
  sequencer;
435
+ proverNode;
431
436
  slasherClient;
432
437
  validatorsSentinel;
433
438
  epochPruneWatcher;
@@ -436,10 +441,14 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
436
441
  globalVariableBuilder;
437
442
  epochCache;
438
443
  packageVersion;
439
- proofVerifier;
444
+ peerProofVerifier;
445
+ rpcProofVerifier;
440
446
  telemetry;
441
447
  log;
442
448
  blobClient;
449
+ validatorClient;
450
+ keyStoreManager;
451
+ debugLogStore;
443
452
  static{
444
453
  ({ e: [_initProto] } = _apply_decs_2203_r(this, [
445
454
  [
@@ -454,7 +463,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
454
463
  // Prevent two snapshot operations to happen simultaneously
455
464
  isUploadingSnapshot;
456
465
  tracer;
457
- constructor(config, p2pClient, blockSource, logsSource, contractDataSource, l1ToL2MessageSource, worldStateSynchronizer, sequencer, slasherClient, validatorsSentinel, epochPruneWatcher, l1ChainId, version, globalVariableBuilder, epochCache, packageVersion, proofVerifier, telemetry = getTelemetryClient(), log = createLogger('node'), blobClient){
466
+ constructor(config, p2pClient, blockSource, logsSource, contractDataSource, l1ToL2MessageSource, worldStateSynchronizer, sequencer, proverNode, slasherClient, validatorsSentinel, epochPruneWatcher, l1ChainId, version, globalVariableBuilder, epochCache, packageVersion, peerProofVerifier, rpcProofVerifier, telemetry = getTelemetryClient(), log = createLogger('node'), blobClient, validatorClient, keyStoreManager, debugLogStore = new NullDebugLogStore()){
458
467
  this.config = config;
459
468
  this.p2pClient = p2pClient;
460
469
  this.blockSource = blockSource;
@@ -463,6 +472,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
463
472
  this.l1ToL2MessageSource = l1ToL2MessageSource;
464
473
  this.worldStateSynchronizer = worldStateSynchronizer;
465
474
  this.sequencer = sequencer;
475
+ this.proverNode = proverNode;
466
476
  this.slasherClient = slasherClient;
467
477
  this.validatorsSentinel = validatorsSentinel;
468
478
  this.epochPruneWatcher = epochPruneWatcher;
@@ -471,16 +481,29 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
471
481
  this.globalVariableBuilder = globalVariableBuilder;
472
482
  this.epochCache = epochCache;
473
483
  this.packageVersion = packageVersion;
474
- this.proofVerifier = proofVerifier;
484
+ this.peerProofVerifier = peerProofVerifier;
485
+ this.rpcProofVerifier = rpcProofVerifier;
475
486
  this.telemetry = telemetry;
476
487
  this.log = log;
477
488
  this.blobClient = blobClient;
489
+ this.validatorClient = validatorClient;
490
+ this.keyStoreManager = keyStoreManager;
491
+ this.debugLogStore = debugLogStore;
478
492
  this.initialHeaderHashPromise = (_initProto(this), undefined);
479
493
  this.isUploadingSnapshot = false;
480
494
  this.metrics = new NodeMetrics(telemetry, 'AztecNodeService');
481
495
  this.tracer = telemetry.getTracer('AztecNodeService');
482
496
  this.log.info(`Aztec Node version: ${this.packageVersion}`);
483
497
  this.log.info(`Aztec Node started on chain 0x${l1ChainId.toString(16)}`, config.l1Contracts);
498
+ // A defensive check that protects us against introducing a bug in the complex `createAndSync` function. We must
499
+ // never have debugLogStore enabled when not in test mode because then we would be accumulating debug logs in
500
+ // memory which could be a DoS vector on the sequencer (since no fees are paid for debug logs).
501
+ if (debugLogStore.isEnabled && config.realProofs) {
502
+ throw new Error('debugLogStore should never be enabled when realProofs are set');
503
+ }
504
+ }
505
+ /** @internal Exposed for testing — returns the RPC proof verifier. */ getProofVerifier() {
506
+ return this.rpcProofVerifier;
484
507
  }
485
508
  async getWorldStateSyncStatus() {
486
509
  const status = await this.worldStateSynchronizer.status();
@@ -502,16 +525,27 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
502
525
  const telemetry = deps.telemetry ?? getTelemetryClient();
503
526
  const dateProvider = deps.dateProvider ?? new DateProvider();
504
527
  const ethereumChain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
505
- // Build a key store from file if given or from environment otherwise
528
+ // Build a key store from file if given or from environment otherwise.
529
+ // We keep the raw KeyStore available so we can merge with prover keys if enableProverNode is set.
506
530
  let keyStoreManager;
507
531
  const keyStoreProvided = config.keyStoreDirectory !== undefined && config.keyStoreDirectory.length > 0;
508
532
  if (keyStoreProvided) {
509
533
  const keyStores = loadKeystores(config.keyStoreDirectory);
510
534
  keyStoreManager = new KeystoreManager(mergeKeystores(keyStores));
511
535
  } else {
512
- const keyStore = createKeyStoreForValidator(config);
513
- if (keyStore) {
514
- keyStoreManager = new KeystoreManager(keyStore);
536
+ const rawKeyStores = [];
537
+ const validatorKeyStore = createKeyStoreForValidator(config);
538
+ if (validatorKeyStore) {
539
+ rawKeyStores.push(validatorKeyStore);
540
+ }
541
+ if (config.enableProverNode) {
542
+ const proverKeyStore = createKeyStoreForProver(config);
543
+ if (proverKeyStore) {
544
+ rawKeyStores.push(proverKeyStore);
545
+ }
546
+ }
547
+ if (rawKeyStores.length > 0) {
548
+ keyStoreManager = new KeystoreManager(rawKeyStores.length === 1 ? rawKeyStores[0] : mergeKeystores(rawKeyStores));
515
549
  }
516
550
  }
517
551
  await keyStoreManager?.validateSigners();
@@ -520,8 +554,8 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
520
554
  if (keyStoreManager === undefined) {
521
555
  throw new Error('Failed to create key store, a requirement for running a validator');
522
556
  }
523
- if (!keyStoreProvided) {
524
- log.warn('KEY STORE CREATED FROM ENVIRONMENT, IT IS RECOMMENDED TO USE A FILE-BASED KEY STORE IN PRODUCTION ENVIRONMENTS');
557
+ if (!keyStoreProvided && process.env.NODE_ENV !== 'test') {
558
+ log.warn("Keystore created from env: it's recommended to use a file-based key store for production");
525
559
  }
526
560
  ValidatorClient.validateKeyStoreConfiguration(keyStoreManager, log);
527
561
  }
@@ -531,9 +565,9 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
531
565
  }
532
566
  const publicClient = createPublicClient({
533
567
  chain: ethereumChain.chainInfo,
534
- transport: fallback(config.l1RpcUrls.map((url)=>http(url, {
535
- batch: false
536
- }))),
568
+ transport: makeL1HttpTransport(config.l1RpcUrls, {
569
+ timeout: config.l1HttpTimeoutMS
570
+ }),
537
571
  pollingInterval: config.viemPollingIntervalMS
538
572
  });
539
573
  const l1ContractsAddresses = await RegistryContract.collectAddresses(publicClient, config.l1Contracts.registryAddress, config.rollupVersion ?? 'canonical');
@@ -543,16 +577,17 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
543
577
  ...l1ContractsAddresses
544
578
  };
545
579
  const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString());
546
- const [l1GenesisTime, slotDuration, rollupVersionFromRollup] = await Promise.all([
580
+ const [l1GenesisTime, slotDuration, rollupVersionFromRollup, rollupManaLimit] = await Promise.all([
547
581
  rollupContract.getL1GenesisTime(),
548
582
  rollupContract.getSlotDuration(),
549
- rollupContract.getVersion()
583
+ rollupContract.getVersion(),
584
+ rollupContract.getManaLimit().then(Number)
550
585
  ]);
551
586
  config.rollupVersion ??= Number(rollupVersionFromRollup);
552
587
  if (config.rollupVersion !== Number(rollupVersionFromRollup)) {
553
588
  log.warn(`Registry looked up and returned a rollup with version (${config.rollupVersion}), but this does not match with version detected from the rollup directly: (${rollupVersionFromRollup}).`);
554
589
  }
555
- const blobClient = await createBlobClientWithFileStores(config, createLogger('node:blob-client:client'));
590
+ const blobClient = await createBlobClientWithFileStores(config, log.createChild('blob-client'));
556
591
  // attempt snapshot sync if possible
557
592
  await trySnapshotSync(config, log);
558
593
  const epochCache = await EpochCache.create(config.l1Contracts.rollupAddress, config, {
@@ -568,49 +603,74 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
568
603
  });
569
604
  // now create the merkle trees and the world state synchronizer
570
605
  const worldStateSynchronizer = await createWorldStateSynchronizer(config, archiver, options.prefilledPublicData, telemetry);
571
- const circuitVerifier = config.realProofs || config.debugForceTxProofVerification ? await BBCircuitVerifier.new(config) : new TestCircuitVerifier(config.proverTestVerificationDelayMs);
606
+ const useRealVerifiers = config.realProofs || config.debugForceTxProofVerification;
607
+ let peerProofVerifier;
608
+ let rpcProofVerifier;
609
+ if (useRealVerifiers) {
610
+ peerProofVerifier = await BatchChonkVerifier.new(config, config.bbChonkVerifyMaxBatch, 'peer');
611
+ const rpcVerifier = await BBCircuitVerifier.new(config);
612
+ rpcProofVerifier = new QueuedIVCVerifier(rpcVerifier, config.numConcurrentIVCVerifiers);
613
+ } else {
614
+ peerProofVerifier = new TestCircuitVerifier(config.proverTestVerificationDelayMs);
615
+ rpcProofVerifier = new TestCircuitVerifier(config.proverTestVerificationDelayMs);
616
+ }
617
+ let debugLogStore;
572
618
  if (!config.realProofs) {
573
619
  log.warn(`Aztec node is accepting fake proofs`);
620
+ debugLogStore = new InMemoryDebugLogStore();
621
+ log.info('Aztec node started in test mode (realProofs set to false) hence debug logs from public functions will be collected and served');
622
+ } else {
623
+ debugLogStore = new NullDebugLogStore();
624
+ }
625
+ const proverOnly = config.enableProverNode && config.disableValidator;
626
+ if (proverOnly) {
627
+ log.info('Starting in prover-only mode: skipping validator, sequencer, sentinel, and slasher subsystems');
574
628
  }
575
- const proofVerifier = new QueuedIVCVerifier(config, circuitVerifier);
576
629
  // create the tx pool and the p2p client, which will need the l2 block source
577
- const p2pClient = await createP2PClient(P2PClientType.Full, config, archiver, proofVerifier, worldStateSynchronizer, epochCache, packageVersion, dateProvider, telemetry, deps.p2pClientDeps);
578
- // We should really not be modifying the config object
579
- config.txPublicSetupAllowList = config.txPublicSetupAllowList ?? await getDefaultAllowedSetupFunctions();
580
- // Create FullNodeCheckpointsBuilder for validator and non-validator block proposal handling
630
+ const p2pClient = await createP2PClient(config, archiver, peerProofVerifier, worldStateSynchronizer, epochCache, packageVersion, dateProvider, telemetry, deps.p2pClientDeps);
631
+ // We'll accumulate sentinel watchers here
632
+ const watchers = [];
633
+ // Create FullNodeCheckpointsBuilder for block proposal handling and tx validation.
634
+ // Override maxTxsPerCheckpoint with the validator-specific limit if set.
581
635
  const validatorCheckpointsBuilder = new FullNodeCheckpointsBuilder({
582
636
  ...config,
583
637
  l1GenesisTime,
584
- slotDuration: Number(slotDuration)
638
+ slotDuration: Number(slotDuration),
639
+ rollupManaLimit,
640
+ maxTxsPerCheckpoint: config.validateMaxTxsPerCheckpoint
585
641
  }, worldStateSynchronizer, archiver, dateProvider, telemetry);
586
- // We'll accumulate sentinel watchers here
587
- const watchers = [];
588
- // Create validator client if required
589
- const validatorClient = await createValidatorClient(config, {
590
- checkpointsBuilder: validatorCheckpointsBuilder,
591
- worldState: worldStateSynchronizer,
592
- p2pClient,
593
- telemetry,
594
- dateProvider,
595
- epochCache,
596
- blockSource: archiver,
597
- l1ToL2MessageSource: archiver,
598
- keyStoreManager,
599
- blobClient
600
- });
601
- // If we have a validator client, register it as a source of offenses for the slasher,
602
- // and have it register callbacks on the p2p client *before* we start it, otherwise messages
603
- // like attestations or auths will fail.
604
- if (validatorClient) {
605
- watchers.push(validatorClient);
606
- if (!options.dontStartSequencer) {
607
- await validatorClient.registerHandlers();
642
+ let validatorClient;
643
+ if (!proverOnly) {
644
+ // Create validator client if required
645
+ validatorClient = await createValidatorClient(config, {
646
+ checkpointsBuilder: validatorCheckpointsBuilder,
647
+ worldState: worldStateSynchronizer,
648
+ p2pClient,
649
+ telemetry,
650
+ dateProvider,
651
+ epochCache,
652
+ blockSource: archiver,
653
+ l1ToL2MessageSource: archiver,
654
+ keyStoreManager,
655
+ blobClient,
656
+ slashingProtectionDb: deps.slashingProtectionDb
657
+ });
658
+ // If we have a validator client, register it as a source of offenses for the slasher,
659
+ // and have it register callbacks on the p2p client *before* we start it, otherwise messages
660
+ // like attestations or auths will fail.
661
+ if (validatorClient) {
662
+ watchers.push(validatorClient);
663
+ if (!options.dontStartSequencer) {
664
+ await validatorClient.registerHandlers();
665
+ }
608
666
  }
609
667
  }
610
- // If there's no validator client but alwaysReexecuteBlockProposals is enabled,
611
- // create a BlockProposalHandler to reexecute block proposals for monitoring
612
- if (!validatorClient && config.alwaysReexecuteBlockProposals) {
613
- log.info('Setting up block proposal reexecution for monitoring');
668
+ // If there's no validator client, create a BlockProposalHandler to handle block proposals
669
+ // for monitoring or reexecution. Reexecution (default) allows us to follow the pending chain,
670
+ // while non-reexecution is used for validating the proposals and collecting their txs.
671
+ if (!validatorClient) {
672
+ const reexecute = !!config.alwaysReexecuteBlockProposals;
673
+ log.info(`Setting up block proposal handler` + (reexecute ? ' with reexecution of proposals' : ''));
614
674
  createBlockProposalHandler(config, {
615
675
  checkpointsBuilder: validatorCheckpointsBuilder,
616
676
  worldState: worldStateSynchronizer,
@@ -620,26 +680,29 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
620
680
  p2pClient,
621
681
  dateProvider,
622
682
  telemetry
623
- }).registerForReexecution(p2pClient);
683
+ }).register(p2pClient, reexecute);
624
684
  }
625
685
  // Start world state and wait for it to sync to the archiver.
626
686
  await worldStateSynchronizer.start();
627
687
  // Start p2p. Note that it depends on world state to be running.
628
688
  await p2pClient.start();
629
- const validatorsSentinel = await createSentinel(epochCache, archiver, p2pClient, config);
630
- if (validatorsSentinel && config.slashInactivityPenalty > 0n) {
631
- watchers.push(validatorsSentinel);
632
- }
689
+ let validatorsSentinel;
633
690
  let epochPruneWatcher;
634
- if (config.slashPrunePenalty > 0n || config.slashDataWithholdingPenalty > 0n) {
635
- epochPruneWatcher = new EpochPruneWatcher(archiver, archiver, epochCache, p2pClient.getTxProvider(), validatorCheckpointsBuilder, config);
636
- watchers.push(epochPruneWatcher);
637
- }
638
- // We assume we want to slash for invalid attestations unless all max penalties are set to 0
639
691
  let attestationsBlockWatcher;
640
- if (config.slashProposeInvalidAttestationsPenalty > 0n || config.slashAttestDescendantOfInvalidPenalty > 0n) {
641
- attestationsBlockWatcher = new AttestationsBlockWatcher(archiver, epochCache, config);
642
- watchers.push(attestationsBlockWatcher);
692
+ if (!proverOnly) {
693
+ validatorsSentinel = await createSentinel(epochCache, archiver, p2pClient, config);
694
+ if (validatorsSentinel && config.slashInactivityPenalty > 0n) {
695
+ watchers.push(validatorsSentinel);
696
+ }
697
+ if (config.slashPrunePenalty > 0n || config.slashDataWithholdingPenalty > 0n) {
698
+ epochPruneWatcher = new EpochPruneWatcher(archiver, archiver, epochCache, p2pClient.getTxProvider(), validatorCheckpointsBuilder, config);
699
+ watchers.push(epochPruneWatcher);
700
+ }
701
+ // We assume we want to slash for invalid attestations unless all max penalties are set to 0
702
+ if (config.slashProposeInvalidAttestationsPenalty > 0n || config.slashAttestDescendantOfInvalidPenalty > 0n) {
703
+ attestationsBlockWatcher = new AttestationsBlockWatcher(archiver, epochCache, config);
704
+ watchers.push(attestationsBlockWatcher);
705
+ }
643
706
  }
644
707
  // Start p2p-related services once the archiver has completed sync
645
708
  void archiver.waitForInitialSync().then(async ()=>{
@@ -649,6 +712,13 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
649
712
  await attestationsBlockWatcher?.start();
650
713
  log.info(`All p2p services started`);
651
714
  }).catch((err)=>log.error('Failed to start p2p services after archiver sync', err));
715
+ const globalVariableBuilder = new GlobalVariableBuilder(dateProvider, publicClient, {
716
+ l1Contracts: config.l1Contracts,
717
+ ethereumSlotDuration: config.ethereumSlotDuration,
718
+ rollupVersion: BigInt(config.rollupVersion),
719
+ l1GenesisTime,
720
+ slotDuration: Number(slotDuration)
721
+ });
652
722
  // Validator enabled, create/start relevant service
653
723
  let sequencer;
654
724
  let slasherClient;
@@ -658,31 +728,51 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
658
728
  const validatorAddresses = keyStoreManager ? NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager).getAddresses() : [];
659
729
  slasherClient = await createSlasher(config, config.l1Contracts, getPublicClient(config), watchers, dateProvider, epochCache, validatorAddresses, undefined);
660
730
  await slasherClient.start();
661
- const l1TxUtils = config.publisherForwarderAddress ? await createForwarderL1TxUtilsFromEthSigner(publicClient, keyStoreManager.createAllValidatorPublisherSigners(), config.publisherForwarderAddress, {
731
+ const l1TxUtils = config.sequencerPublisherForwarderAddress ? await createForwarderL1TxUtilsFromSigners(publicClient, keyStoreManager.createAllValidatorPublisherSigners(), config.sequencerPublisherForwarderAddress, {
662
732
  ...config,
663
733
  scope: 'sequencer'
664
734
  }, {
665
735
  telemetry,
666
736
  logger: log.createChild('l1-tx-utils'),
667
- dateProvider
668
- }) : await createL1TxUtilsWithBlobsFromEthSigner(publicClient, keyStoreManager.createAllValidatorPublisherSigners(), {
737
+ dateProvider,
738
+ kzg: Blob.getViemKzgInstance()
739
+ }) : await createL1TxUtilsFromSigners(publicClient, keyStoreManager.createAllValidatorPublisherSigners(), {
669
740
  ...config,
670
741
  scope: 'sequencer'
671
742
  }, {
672
743
  telemetry,
673
744
  logger: log.createChild('l1-tx-utils'),
674
- dateProvider
745
+ dateProvider,
746
+ kzg: Blob.getViemKzgInstance()
675
747
  });
748
+ // Create a funder L1TxUtils from the keystore funding account (if configured)
749
+ const fundingSigner = keyStoreManager?.createFundingSigner();
750
+ let funderL1TxUtils;
751
+ if (fundingSigner) {
752
+ const [funder] = await createL1TxUtilsFromSigners(publicClient, [
753
+ fundingSigner
754
+ ], {
755
+ ...config,
756
+ scope: 'sequencer'
757
+ }, {
758
+ telemetry,
759
+ logger: log.createChild('l1-tx-utils:funder'),
760
+ dateProvider
761
+ });
762
+ funderL1TxUtils = funder;
763
+ }
676
764
  // Create and start the sequencer client
677
765
  const checkpointsBuilder = new CheckpointsBuilder({
678
766
  ...config,
679
767
  l1GenesisTime,
680
- slotDuration: Number(slotDuration)
681
- }, worldStateSynchronizer, archiver, dateProvider, telemetry);
768
+ slotDuration: Number(slotDuration),
769
+ rollupManaLimit
770
+ }, worldStateSynchronizer, archiver, dateProvider, telemetry, debugLogStore);
682
771
  sequencer = await SequencerClient.new(config, {
683
772
  ...deps,
684
773
  epochCache,
685
774
  l1TxUtils,
775
+ funderL1TxUtils,
686
776
  validatorClient,
687
777
  p2pClient,
688
778
  worldStateSynchronizer,
@@ -693,7 +783,8 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
693
783
  telemetry,
694
784
  dateProvider,
695
785
  blobClient,
696
- nodeKeyStore: keyStoreManager
786
+ nodeKeyStore: keyStoreManager,
787
+ globalVariableBuilder
697
788
  });
698
789
  }
699
790
  if (!options.dontStartSequencer && sequencer) {
@@ -702,13 +793,29 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
702
793
  } else if (sequencer) {
703
794
  log.warn(`Sequencer created but not started`);
704
795
  }
705
- const globalVariableBuilder = new GlobalVariableBuilder({
706
- ...config,
707
- rollupVersion: BigInt(config.rollupVersion),
708
- l1GenesisTime,
709
- slotDuration: Number(slotDuration)
710
- });
711
- return new AztecNodeService(config, p2pClient, archiver, archiver, archiver, archiver, worldStateSynchronizer, sequencer, slasherClient, validatorsSentinel, epochPruneWatcher, ethereumChain.chainInfo.id, config.rollupVersion, globalVariableBuilder, epochCache, packageVersion, proofVerifier, telemetry, log, blobClient);
796
+ // Create prover node subsystem if enabled
797
+ let proverNode;
798
+ if (config.enableProverNode) {
799
+ proverNode = await createProverNode(config, {
800
+ ...deps.proverNodeDeps,
801
+ telemetry,
802
+ dateProvider,
803
+ archiver,
804
+ worldStateSynchronizer,
805
+ p2pClient,
806
+ epochCache,
807
+ blobClient,
808
+ keyStoreManager
809
+ });
810
+ if (!options.dontStartProverNode) {
811
+ await proverNode.start();
812
+ log.info(`Prover node subsystem started`);
813
+ } else {
814
+ log.info(`Prover node subsystem created but not started`);
815
+ }
816
+ }
817
+ const node = new AztecNodeService(config, p2pClient, archiver, archiver, archiver, archiver, worldStateSynchronizer, sequencer, proverNode, slasherClient, validatorsSentinel, epochPruneWatcher, ethereumChain.chainInfo.id, config.rollupVersion, globalVariableBuilder, epochCache, packageVersion, peerProofVerifier, rpcProofVerifier, telemetry, log, blobClient, validatorClient, keyStoreManager, debugLogStore);
818
+ return node;
712
819
  }
713
820
  /**
714
821
  * Returns the sequencer client instance.
@@ -716,6 +823,9 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
716
823
  */ getSequencer() {
717
824
  return this.sequencer;
718
825
  }
826
+ /** Returns the prover node subsystem, if enabled. */ getProverNode() {
827
+ return this.proverNode;
828
+ }
719
829
  getBlockSource() {
720
830
  return this.blockSource;
721
831
  }
@@ -735,7 +845,10 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
735
845
  return Promise.resolve(this.p2pClient.getEnr()?.encodeTxt());
736
846
  }
737
847
  async getAllowedPublicSetup() {
738
- return this.config.txPublicSetupAllowList ?? await getDefaultAllowedSetupFunctions();
848
+ return [
849
+ ...await getDefaultAllowedSetupFunctions(),
850
+ ...this.config.txPublicSetupAllowListExtend ?? []
851
+ ];
739
852
  }
740
853
  /**
741
854
  * Method to determine if the node is ready to accept transactions.
@@ -758,7 +871,8 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
758
871
  rollupVersion,
759
872
  enr,
760
873
  l1ContractAddresses: contractAddresses,
761
- protocolContractAddresses: protocolContractAddresses
874
+ protocolContractAddresses: protocolContractAddresses,
875
+ realProofs: !!this.config.realProofs
762
876
  };
763
877
  return nodeInfo;
764
878
  }
@@ -767,14 +881,14 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
767
881
  * @param block - The block parameter (block number, block hash, or 'latest').
768
882
  * @returns The requested block.
769
883
  */ async getBlock(block) {
770
- if (L2BlockHash.isL2BlockHash(block)) {
771
- return this.getBlockByHash(Fr.fromBuffer(block.toBuffer()));
884
+ if (BlockHash.isBlockHash(block)) {
885
+ return this.getBlockByHash(block);
772
886
  }
773
887
  const blockNumber = block === 'latest' ? await this.getBlockNumber() : block;
774
888
  if (blockNumber === BlockNumber.ZERO) {
775
889
  return this.buildInitialBlock();
776
890
  }
777
- return await this.blockSource.getL2BlockNew(blockNumber);
891
+ return await this.blockSource.getL2Block(blockNumber);
778
892
  }
779
893
  /**
780
894
  * Get a block specified by its hash.
@@ -782,21 +896,21 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
782
896
  * @returns The requested block.
783
897
  */ async getBlockByHash(blockHash) {
784
898
  const initialBlockHash = await this.#getInitialHeaderHash();
785
- if (blockHash.equals(Fr.fromBuffer(initialBlockHash.toBuffer()))) {
899
+ if (blockHash.equals(initialBlockHash)) {
786
900
  return this.buildInitialBlock();
787
901
  }
788
- return await this.blockSource.getL2BlockNewByHash(blockHash);
902
+ return await this.blockSource.getL2BlockByHash(blockHash);
789
903
  }
790
904
  buildInitialBlock() {
791
905
  const initialHeader = this.worldStateSynchronizer.getCommitted().getInitialHeader();
792
- return L2BlockNew.empty(initialHeader);
906
+ return L2Block.empty(initialHeader);
793
907
  }
794
908
  /**
795
909
  * Get a block specified by its archive root.
796
910
  * @param archive - The archive root being requested.
797
911
  * @returns The requested block.
798
912
  */ async getBlockByArchive(archive) {
799
- return await this.blockSource.getL2BlockNewByArchive(archive);
913
+ return await this.blockSource.getL2BlockByArchive(archive);
800
914
  }
801
915
  /**
802
916
  * Method to request blocks. Will attempt to return all requested blocks but will return only those available.
@@ -804,19 +918,16 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
804
918
  * @param limit - The maximum number of blocks to obtain.
805
919
  * @returns The blocks requested.
806
920
  */ async getBlocks(from, limit) {
807
- return await this.blockSource.getL2BlocksNew(from, limit) ?? [];
921
+ return await this.blockSource.getBlocks(from, BlockNumber(limit)) ?? [];
808
922
  }
809
- async getPublishedBlocks(from, limit) {
810
- return await this.blockSource.getPublishedBlocks(from, limit) ?? [];
923
+ async getCheckpoints(from, limit) {
924
+ return await this.blockSource.getCheckpoints(from, limit) ?? [];
811
925
  }
812
- async getPublishedCheckpoints(from, limit) {
813
- return await this.blockSource.getPublishedCheckpoints(from, limit) ?? [];
926
+ async getCheckpointedBlocks(from, limit) {
927
+ return await this.blockSource.getCheckpointedBlocks(from, limit) ?? [];
814
928
  }
815
- async getL2BlocksNew(from, limit) {
816
- return await this.blockSource.getL2BlocksNew(from, limit) ?? [];
817
- }
818
- async getCheckpointedBlocks(from, limit, proven) {
819
- return await this.blockSource.getCheckpointedBlocks(from, limit, proven) ?? [];
929
+ getCheckpointsDataForEpoch(epochNumber) {
930
+ return this.blockSource.getCheckpointsDataForEpoch(epochNumber);
820
931
  }
821
932
  /**
822
933
  * Method to fetch the current min L2 fees.
@@ -842,6 +953,12 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
842
953
  async getProvenBlockNumber() {
843
954
  return await this.blockSource.getProvenBlockNumber();
844
955
  }
956
+ async getCheckpointedBlockNumber() {
957
+ return await this.blockSource.getCheckpointedL2BlockNumber();
958
+ }
959
+ getCheckpointNumber() {
960
+ return this.blockSource.getCheckpointNumber();
961
+ }
845
962
  /**
846
963
  * Method to fetch the version of the package.
847
964
  * @returns The node package version
@@ -866,11 +983,37 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
866
983
  getContract(address) {
867
984
  return this.contractDataSource.getContract(address);
868
985
  }
869
- getPrivateLogsByTags(tags, page) {
870
- return this.logsSource.getPrivateLogsByTags(tags, page);
986
+ async getPrivateLogsByTags(tags, page, referenceBlock) {
987
+ let upToBlockNumber;
988
+ if (referenceBlock) {
989
+ const initialBlockHash = await this.#getInitialHeaderHash();
990
+ if (referenceBlock.equals(initialBlockHash)) {
991
+ upToBlockNumber = BlockNumber(0);
992
+ } else {
993
+ const header = await this.blockSource.getBlockHeaderByHash(referenceBlock);
994
+ if (!header) {
995
+ throw new Error(`Block ${referenceBlock.toString()} not found in the node. This might indicate a reorg has occurred.`);
996
+ }
997
+ upToBlockNumber = header.globalVariables.blockNumber;
998
+ }
999
+ }
1000
+ return this.logsSource.getPrivateLogsByTags(tags, page, upToBlockNumber);
871
1001
  }
872
- getPublicLogsByTagsFromContract(contractAddress, tags, page) {
873
- return this.logsSource.getPublicLogsByTagsFromContract(contractAddress, tags, page);
1002
+ async getPublicLogsByTagsFromContract(contractAddress, tags, page, referenceBlock) {
1003
+ let upToBlockNumber;
1004
+ if (referenceBlock) {
1005
+ const initialBlockHash = await this.#getInitialHeaderHash();
1006
+ if (referenceBlock.equals(initialBlockHash)) {
1007
+ upToBlockNumber = BlockNumber(0);
1008
+ } else {
1009
+ const header = await this.blockSource.getBlockHeaderByHash(referenceBlock);
1010
+ if (!header) {
1011
+ throw new Error(`Block ${referenceBlock.toString()} not found in the node. This might indicate a reorg has occurred.`);
1012
+ }
1013
+ upToBlockNumber = header.globalVariables.blockNumber;
1014
+ }
1015
+ }
1016
+ return this.logsSource.getPublicLogsByTagsFromContract(contractAddress, tags, page, upToBlockNumber);
874
1017
  }
875
1018
  /**
876
1019
  * Gets public logs based on the provided filter.
@@ -905,24 +1048,33 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
905
1048
  throw new Error(`Invalid tx: ${reason}`);
906
1049
  }
907
1050
  await this.p2pClient.sendTx(tx);
908
- this.metrics.receivedTx(timer.ms(), true);
909
- this.log.info(`Received tx ${txHash}`, {
1051
+ const duration = timer.ms();
1052
+ this.metrics.receivedTx(duration, true);
1053
+ this.log.info(`Received tx ${txHash} in ${duration}ms`, {
910
1054
  txHash
911
1055
  });
912
1056
  }
913
1057
  async getTxReceipt(txHash) {
914
- let txReceipt = new TxReceipt(txHash, TxStatus.DROPPED, 'Tx dropped by P2P node.');
915
- // We first check if the tx is in pending (instead of first checking if it is mined) because if we first check
916
- // for mined and then for pending there could be a race condition where the tx is mined between the two checks
917
- // and we would incorrectly return a TxReceipt with status DROPPED
918
- if (await this.p2pClient.getTxStatus(txHash) === 'pending') {
919
- txReceipt = new TxReceipt(txHash, TxStatus.PENDING, '');
920
- }
1058
+ // Check the tx pool status first. If the tx is known to the pool (pending or mined), we'll use that
1059
+ // as a fallback if we don't find a settled receipt in the archiver.
1060
+ const txPoolStatus = await this.p2pClient.getTxStatus(txHash);
1061
+ const isKnownToPool = txPoolStatus === 'pending' || txPoolStatus === 'mined';
1062
+ // Then get the actual tx from the archiver, which tracks every tx in a mined block.
921
1063
  const settledTxReceipt = await this.blockSource.getSettledTxReceipt(txHash);
1064
+ let receipt;
922
1065
  if (settledTxReceipt) {
923
- txReceipt = settledTxReceipt;
1066
+ receipt = settledTxReceipt;
1067
+ } else if (isKnownToPool) {
1068
+ // If the tx is in the pool but not in the archiver, it's pending.
1069
+ // This handles race conditions between archiver and p2p, where the archiver
1070
+ // has pruned the block in which a tx was mined, but p2p has not caught up yet.
1071
+ receipt = new TxReceipt(txHash, TxStatus.PENDING, undefined, undefined);
1072
+ } else {
1073
+ // Otherwise, if we don't know the tx, we consider it dropped.
1074
+ receipt = new TxReceipt(txHash, TxStatus.DROPPED, undefined, 'Tx dropped by P2P node');
924
1075
  }
925
- return txReceipt;
1076
+ this.debugLogStore.decorateReceiptWithLogs(txHash.toString(), receipt);
1077
+ return receipt;
926
1078
  }
927
1079
  getTxEffect(txHash) {
928
1080
  return this.blockSource.getTxEffect(txHash);
@@ -934,8 +1086,12 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
934
1086
  await tryStop(this.validatorsSentinel);
935
1087
  await tryStop(this.epochPruneWatcher);
936
1088
  await tryStop(this.slasherClient);
937
- await tryStop(this.proofVerifier);
1089
+ await Promise.all([
1090
+ tryStop(this.peerProofVerifier),
1091
+ tryStop(this.rpcProofVerifier)
1092
+ ]);
938
1093
  await tryStop(this.sequencer);
1094
+ await tryStop(this.proverNode);
939
1095
  await tryStop(this.p2pClient);
940
1096
  await tryStop(this.worldStateSynchronizer);
941
1097
  await tryStop(this.blockSource);
@@ -974,79 +1130,83 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
974
1130
  */ async getTxsByHash(txHashes) {
975
1131
  return compactArray(await Promise.all(txHashes.map((txHash)=>this.getTxByHash(txHash))));
976
1132
  }
977
- async findLeavesIndexes(block, treeId, leafValues) {
978
- const committedDb = await this.#getWorldState(block);
1133
+ async findLeavesIndexes(referenceBlock, treeId, leafValues) {
1134
+ const committedDb = await this.getWorldState(referenceBlock);
979
1135
  const maybeIndices = await committedDb.findLeafIndices(treeId, leafValues.map((x)=>x.toBuffer()));
980
- // We filter out undefined values
981
- const indices = maybeIndices.filter((x)=>x !== undefined);
982
- // Now we find the block numbers for the indices
983
- const blockNumbers = await committedDb.getBlockNumbersForLeafIndices(treeId, indices);
984
- // If any of the block numbers are undefined, we throw an error.
985
- for(let i = 0; i < indices.length; i++){
986
- if (blockNumbers[i] === undefined) {
987
- throw new Error(`Block number is undefined for leaf index ${indices[i]} in tree ${MerkleTreeId[treeId]}`);
1136
+ // Filter out undefined values to query block numbers only for found leaves
1137
+ const definedIndices = maybeIndices.filter((x)=>x !== undefined);
1138
+ // Now we find the block numbers for the defined indices
1139
+ const blockNumbers = await committedDb.getBlockNumbersForLeafIndices(treeId, definedIndices);
1140
+ // Build a map from leaf index to block number
1141
+ const indexToBlockNumber = new Map();
1142
+ for(let i = 0; i < definedIndices.length; i++){
1143
+ const blockNumber = blockNumbers[i];
1144
+ if (blockNumber === undefined) {
1145
+ throw new Error(`Block number is undefined for leaf index ${definedIndices[i]} in tree ${MerkleTreeId[treeId]}`);
988
1146
  }
1147
+ indexToBlockNumber.set(definedIndices[i], blockNumber);
989
1148
  }
990
1149
  // Get unique block numbers in order to optimize num calls to getLeafValue function.
991
1150
  const uniqueBlockNumbers = [
992
- ...new Set(blockNumbers.filter((x)=>x !== undefined))
1151
+ ...new Set(indexToBlockNumber.values())
993
1152
  ];
994
- // Now we obtain the block hashes from the archive tree by calling await `committedDb.getLeafValue(treeId, index)`
995
- // (note that block number corresponds to the leaf index in the archive tree).
1153
+ // Now we obtain the block hashes from the archive tree (block number = leaf index in archive tree).
996
1154
  const blockHashes = await Promise.all(uniqueBlockNumbers.map((blockNumber)=>{
997
1155
  return committedDb.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber));
998
1156
  }));
999
- // If any of the block hashes are undefined, we throw an error.
1157
+ // Build a map from block number to block hash
1158
+ const blockNumberToHash = new Map();
1000
1159
  for(let i = 0; i < uniqueBlockNumbers.length; i++){
1001
- if (blockHashes[i] === undefined) {
1160
+ const blockHash = blockHashes[i];
1161
+ if (blockHash === undefined) {
1002
1162
  throw new Error(`Block hash is undefined for block number ${uniqueBlockNumbers[i]}`);
1003
1163
  }
1164
+ blockNumberToHash.set(uniqueBlockNumbers[i], blockHash);
1004
1165
  }
1005
1166
  // Create DataInBlock objects by combining indices, blockNumbers and blockHashes and return them.
1006
- return maybeIndices.map((index, i)=>{
1167
+ return maybeIndices.map((index)=>{
1007
1168
  if (index === undefined) {
1008
1169
  return undefined;
1009
1170
  }
1010
- const blockNumber = blockNumbers[i];
1171
+ const blockNumber = indexToBlockNumber.get(index);
1011
1172
  if (blockNumber === undefined) {
1012
- return undefined;
1173
+ throw new Error(`Block number not found for leaf index ${index} in tree ${MerkleTreeId[treeId]}`);
1013
1174
  }
1014
- const blockHashIndex = uniqueBlockNumbers.indexOf(blockNumber);
1015
- const blockHash = blockHashes[blockHashIndex];
1016
- if (!blockHash) {
1017
- return undefined;
1175
+ const blockHash = blockNumberToHash.get(blockNumber);
1176
+ if (blockHash === undefined) {
1177
+ throw new Error(`Block hash not found for block number ${blockNumber}`);
1018
1178
  }
1019
1179
  return {
1020
- l2BlockNumber: BlockNumber(Number(blockNumber)),
1021
- l2BlockHash: L2BlockHash.fromField(blockHash),
1180
+ l2BlockNumber: blockNumber,
1181
+ l2BlockHash: new BlockHash(blockHash),
1022
1182
  data: index
1023
1183
  };
1024
1184
  });
1025
1185
  }
1026
- async getNullifierSiblingPath(block, leafIndex) {
1027
- const committedDb = await this.#getWorldState(block);
1028
- return committedDb.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, leafIndex);
1029
- }
1030
- async getNoteHashSiblingPath(block, leafIndex) {
1031
- const committedDb = await this.#getWorldState(block);
1032
- return committedDb.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex);
1033
- }
1034
- async getArchiveMembershipWitness(block, archive) {
1035
- const committedDb = await this.#getWorldState(block);
1186
+ async getBlockHashMembershipWitness(referenceBlock, blockHash) {
1187
+ // The Noir circuit checks the archive membership proof against `anchor_block_header.last_archive.root`,
1188
+ // which is the archive tree root BEFORE the anchor block was added (i.e. the state after block N-1).
1189
+ // So we need the world state at block N-1, not block N, to produce a sibling path matching that root.
1190
+ const referenceBlockNumber = await this.resolveBlockNumber(referenceBlock);
1191
+ if (referenceBlockNumber === BlockNumber.ZERO) {
1192
+ // Block 0 (the initial block) has an empty archive, so no membership witness can exist.
1193
+ return undefined;
1194
+ }
1195
+ const committedDb = await this.getWorldState(BlockNumber(referenceBlockNumber - 1));
1036
1196
  const [pathAndIndex] = await committedDb.findSiblingPaths(MerkleTreeId.ARCHIVE, [
1037
- archive
1197
+ blockHash
1038
1198
  ]);
1039
1199
  return pathAndIndex === undefined ? undefined : MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
1040
1200
  }
1041
- async getNoteHashMembershipWitness(block, noteHash) {
1042
- const committedDb = await this.#getWorldState(block);
1201
+ async getNoteHashMembershipWitness(referenceBlock, noteHash) {
1202
+ const committedDb = await this.getWorldState(referenceBlock);
1043
1203
  const [pathAndIndex] = await committedDb.findSiblingPaths(MerkleTreeId.NOTE_HASH_TREE, [
1044
1204
  noteHash
1045
1205
  ]);
1046
1206
  return pathAndIndex === undefined ? undefined : MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
1047
1207
  }
1048
- async getL1ToL2MessageMembershipWitness(block, l1ToL2Message) {
1049
- const db = await this.#getWorldState(block);
1208
+ async getL1ToL2MessageMembershipWitness(referenceBlock, l1ToL2Message) {
1209
+ const db = await this.getWorldState(referenceBlock);
1050
1210
  const [witness] = await db.findSiblingPaths(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, [
1051
1211
  l1ToL2Message
1052
1212
  ]);
@@ -1059,9 +1219,9 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1059
1219
  witness.path
1060
1220
  ];
1061
1221
  }
1062
- async getL1ToL2MessageBlock(l1ToL2Message) {
1222
+ async getL1ToL2MessageCheckpoint(l1ToL2Message) {
1063
1223
  const messageIndex = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
1064
- return messageIndex ? BlockNumber.fromCheckpointNumber(InboxLeaf.checkpointNumberFromIndex(messageIndex)) : undefined;
1224
+ return messageIndex ? InboxLeaf.checkpointNumberFromIndex(messageIndex) : undefined;
1065
1225
  }
1066
1226
  /**
1067
1227
  * Returns whether an L1 to L2 message is synced by archiver and if it's ready to be included in a block.
@@ -1076,32 +1236,13 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1076
1236
  * @param epoch - The epoch at which to get the data.
1077
1237
  * @returns The L2 to L1 messages (empty array if the epoch is not found).
1078
1238
  */ async getL2ToL1Messages(epoch) {
1079
- // Assumes `getBlocksForEpoch` returns blocks in ascending order of block number.
1080
- const blocks = await this.blockSource.getBlocksForEpoch(epoch);
1081
- const blocksInCheckpoints = [];
1082
- let previousSlotNumber = SlotNumber.ZERO;
1083
- let checkpointIndex = -1;
1084
- for (const block of blocks){
1085
- const slotNumber = block.header.globalVariables.slotNumber;
1086
- if (slotNumber !== previousSlotNumber) {
1087
- checkpointIndex++;
1088
- blocksInCheckpoints.push([]);
1089
- previousSlotNumber = slotNumber;
1090
- }
1091
- blocksInCheckpoints[checkpointIndex].push(block);
1092
- }
1239
+ // Assumes `getCheckpointedBlocksForEpoch` returns blocks in ascending order of block number.
1240
+ const checkpointedBlocks = await this.blockSource.getCheckpointedBlocksForEpoch(epoch);
1241
+ const blocksInCheckpoints = chunkBy(checkpointedBlocks, (cb)=>cb.block.header.globalVariables.slotNumber).map((group)=>group.map((cb)=>cb.block));
1093
1242
  return blocksInCheckpoints.map((blocks)=>blocks.map((block)=>block.body.txEffects.map((txEffect)=>txEffect.l2ToL1Msgs)));
1094
1243
  }
1095
- async getArchiveSiblingPath(block, leafIndex) {
1096
- const committedDb = await this.#getWorldState(block);
1097
- return committedDb.getSiblingPath(MerkleTreeId.ARCHIVE, leafIndex);
1098
- }
1099
- async getPublicDataSiblingPath(block, leafIndex) {
1100
- const committedDb = await this.#getWorldState(block);
1101
- return committedDb.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, leafIndex);
1102
- }
1103
- async getNullifierMembershipWitness(block, nullifier) {
1104
- const db = await this.#getWorldState(block);
1244
+ async getNullifierMembershipWitness(referenceBlock, nullifier) {
1245
+ const db = await this.getWorldState(referenceBlock);
1105
1246
  const [witness] = await db.findSiblingPaths(MerkleTreeId.NULLIFIER_TREE, [
1106
1247
  nullifier.toBuffer()
1107
1248
  ]);
@@ -1115,35 +1256,22 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1115
1256
  }
1116
1257
  return new NullifierMembershipWitness(index, leafPreimage, path);
1117
1258
  }
1118
- /**
1119
- * Returns a low nullifier membership witness for a given nullifier at a given block.
1120
- * @param block - The block parameter (block number, block hash, or 'latest') at which to get the data.
1121
- * @param nullifier - Nullifier we try to find the low nullifier witness for.
1122
- * @returns The low nullifier membership witness (if found).
1123
- * @remarks Low nullifier witness can be used to perform a nullifier non-inclusion proof by leveraging the "linked
1124
- * list structure" of leaves and proving that a lower nullifier is pointing to a bigger next value than the nullifier
1125
- * we are trying to prove non-inclusion for.
1126
- *
1127
- * Note: This function returns the membership witness of the nullifier itself and not the low nullifier when
1128
- * the nullifier already exists in the tree. This is because the `getPreviousValueIndex` function returns the
1129
- * index of the nullifier itself when it already exists in the tree.
1130
- * TODO: This is a confusing behavior and we should eventually address that.
1131
- */ async getLowNullifierMembershipWitness(block, nullifier) {
1132
- const committedDb = await this.#getWorldState(block);
1259
+ async getLowNullifierMembershipWitness(referenceBlock, nullifier) {
1260
+ const committedDb = await this.getWorldState(referenceBlock);
1133
1261
  const findResult = await committedDb.getPreviousValueIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBigInt());
1134
1262
  if (!findResult) {
1135
1263
  return undefined;
1136
1264
  }
1137
1265
  const { index, alreadyPresent } = findResult;
1138
1266
  if (alreadyPresent) {
1139
- this.log.warn(`Nullifier ${nullifier.toBigInt()} already exists in the tree`);
1267
+ throw new Error(`Cannot prove nullifier non-inclusion: nullifier ${nullifier.toBigInt()} already exists in the tree`);
1140
1268
  }
1141
1269
  const preimageData = await committedDb.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index);
1142
1270
  const siblingPath = await committedDb.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, BigInt(index));
1143
1271
  return new NullifierMembershipWitness(BigInt(index), preimageData, siblingPath);
1144
1272
  }
1145
- async getPublicDataWitness(block, leafSlot) {
1146
- const committedDb = await this.#getWorldState(block);
1273
+ async getPublicDataWitness(referenceBlock, leafSlot) {
1274
+ const committedDb = await this.getWorldState(referenceBlock);
1147
1275
  const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
1148
1276
  if (!lowLeafResult) {
1149
1277
  return undefined;
@@ -1153,8 +1281,8 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1153
1281
  return new PublicDataWitness(lowLeafResult.index, preimage, path);
1154
1282
  }
1155
1283
  }
1156
- async getPublicStorageAt(block, contract, slot) {
1157
- const committedDb = await this.#getWorldState(block);
1284
+ async getPublicStorageAt(referenceBlock, contract, slot) {
1285
+ const committedDb = await this.getWorldState(referenceBlock);
1158
1286
  const leafSlot = await computePublicDataTreeLeafSlot(contract, slot);
1159
1287
  const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
1160
1288
  if (!lowLeafResult || !lowLeafResult.alreadyPresent) {
@@ -1164,14 +1292,13 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1164
1292
  return preimage.leaf.value;
1165
1293
  }
1166
1294
  async getBlockHeader(block = 'latest') {
1167
- if (L2BlockHash.isL2BlockHash(block)) {
1295
+ if (BlockHash.isBlockHash(block)) {
1168
1296
  const initialBlockHash = await this.#getInitialHeaderHash();
1169
1297
  if (block.equals(initialBlockHash)) {
1170
1298
  // Block source doesn't handle initial header so we need to handle the case separately.
1171
1299
  return this.worldStateSynchronizer.getCommitted().getInitialHeader();
1172
1300
  }
1173
- const blockHashFr = Fr.fromBuffer(block.toBuffer());
1174
- return this.blockSource.getBlockHeaderByHash(blockHashFr);
1301
+ return this.blockSource.getBlockHeaderByHash(block);
1175
1302
  } else {
1176
1303
  // Block source doesn't handle initial header so we need to handle the case separately.
1177
1304
  const blockNumber = block === 'latest' ? await this.getBlockNumber() : block;
@@ -1188,6 +1315,12 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1188
1315
  */ async getBlockHeaderByArchive(archive) {
1189
1316
  return await this.blockSource.getBlockHeaderByArchive(archive);
1190
1317
  }
1318
+ getBlockData(number) {
1319
+ return this.blockSource.getBlockData(number);
1320
+ }
1321
+ getBlockDataByArchive(archive) {
1322
+ return this.blockSource.getBlockDataByArchive(archive);
1323
+ }
1191
1324
  /**
1192
1325
  * Simulates the public part of a transaction with the current state.
1193
1326
  * @param tx - The transaction to simulate.
@@ -1200,17 +1333,20 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1200
1333
  throw new BadRequestError(`Transaction total gas limit ${txGasLimit + teardownGasLimit} (${txGasLimit} + ${teardownGasLimit}) exceeds maximum gas limit ${this.config.rpcSimulatePublicMaxGasLimit} for simulation`);
1201
1334
  }
1202
1335
  const txHash = tx.getTxHash();
1203
- const blockNumber = BlockNumber(await this.blockSource.getBlockNumber() + 1);
1336
+ const latestBlockNumber = await this.blockSource.getBlockNumber();
1337
+ const blockNumber = BlockNumber.add(latestBlockNumber, 1);
1204
1338
  // If sequencer is not initialized, we just set these values to zero for simulation.
1205
1339
  const coinbase = EthAddress.ZERO;
1206
1340
  const feeRecipient = AztecAddress.ZERO;
1207
1341
  const newGlobalVariables = await this.globalVariableBuilder.buildGlobalVariables(blockNumber, coinbase, feeRecipient);
1208
- const publicProcessorFactory = new PublicProcessorFactory(this.contractDataSource, new DateProvider(), this.telemetry);
1342
+ const publicProcessorFactory = new PublicProcessorFactory(this.contractDataSource, new DateProvider(), this.telemetry, this.log.getBindings());
1209
1343
  this.log.verbose(`Simulating public calls for tx ${txHash}`, {
1210
1344
  globalVariables: newGlobalVariables.toInspect(),
1211
1345
  txHash,
1212
1346
  blockNumber
1213
1347
  });
1348
+ // Ensure world-state has caught up with the latest block we loaded from the archiver
1349
+ await this.worldStateSynchronizer.syncImmediate(latestBlockNumber);
1214
1350
  const merkleTreeFork = await this.worldStateSynchronizer.fork();
1215
1351
  try {
1216
1352
  const config = PublicSimulatorConfig.from({
@@ -1225,7 +1361,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1225
1361
  });
1226
1362
  const processor = publicProcessorFactory.create(merkleTreeFork, newGlobalVariables, config);
1227
1363
  // REFACTOR: Consider merging ProcessReturnValues into ProcessedTx
1228
- const [processedTxs, failedTxs, _usedTxs, returns] = await processor.process([
1364
+ const [processedTxs, failedTxs, _usedTxs, returns, debugLogs] = await processor.process([
1229
1365
  tx
1230
1366
  ]);
1231
1367
  // REFACTOR: Consider returning the error rather than throwing
@@ -1236,27 +1372,34 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1236
1372
  throw failedTxs[0].error;
1237
1373
  }
1238
1374
  const [processedTx] = processedTxs;
1239
- return new PublicSimulationOutput(processedTx.revertReason, processedTx.globalVariables, processedTx.txEffect, returns, processedTx.gasUsed);
1375
+ return new PublicSimulationOutput(processedTx.revertReason, processedTx.globalVariables, processedTx.txEffect, returns, processedTx.gasUsed, debugLogs);
1240
1376
  } finally{
1241
1377
  await merkleTreeFork.close();
1242
1378
  }
1243
1379
  }
1244
1380
  async isValidTx(tx, { isSimulation, skipFeeEnforcement } = {}) {
1245
1381
  const db = this.worldStateSynchronizer.getCommitted();
1246
- const verifier = isSimulation ? undefined : this.proofVerifier;
1247
- // We accept transactions if they are not expired by the next slot (checked based on the IncludeByTimestamp field)
1382
+ const verifier = isSimulation ? undefined : this.rpcProofVerifier;
1383
+ // We accept transactions if they are not expired by the next slot (checked based on the ExpirationTimestamp field)
1248
1384
  const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
1249
1385
  const blockNumber = BlockNumber(await this.blockSource.getBlockNumber() + 1);
1250
- const validator = createValidatorForAcceptingTxs(db, this.contractDataSource, verifier, {
1386
+ const l1Constants = await this.blockSource.getL1Constants();
1387
+ const validator = createTxValidatorForAcceptingTxsOverRPC(db, this.contractDataSource, verifier, {
1251
1388
  timestamp: nextSlotTimestamp,
1252
1389
  blockNumber,
1253
1390
  l1ChainId: this.l1ChainId,
1254
1391
  rollupVersion: this.version,
1255
- setupAllowList: this.config.txPublicSetupAllowList ?? await getDefaultAllowedSetupFunctions(),
1392
+ setupAllowList: [
1393
+ ...await getDefaultAllowedSetupFunctions(),
1394
+ ...this.config.txPublicSetupAllowListExtend ?? []
1395
+ ],
1256
1396
  gasFees: await this.getCurrentMinFees(),
1257
1397
  skipFeeEnforcement,
1258
- txsPermitted: !this.config.disableTransactions
1259
- });
1398
+ txsPermitted: !this.config.disableTransactions,
1399
+ rollupManaLimit: l1Constants.rollupManaLimit,
1400
+ maxBlockL2Gas: this.config.validateMaxL2BlockGas,
1401
+ maxBlockDAGas: this.config.validateMaxDABlockGas
1402
+ }, this.log.getBindings());
1260
1403
  return await validator.validateTx(tx);
1261
1404
  }
1262
1405
  getConfig() {
@@ -1278,7 +1421,18 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1278
1421
  archiver.updateConfig(config);
1279
1422
  }
1280
1423
  if (newConfig.realProofs !== this.config.realProofs) {
1281
- this.proofVerifier = config.realProofs ? await BBCircuitVerifier.new(newConfig) : new TestCircuitVerifier();
1424
+ await Promise.all([
1425
+ tryStop(this.peerProofVerifier),
1426
+ tryStop(this.rpcProofVerifier)
1427
+ ]);
1428
+ if (newConfig.realProofs) {
1429
+ this.peerProofVerifier = await BatchChonkVerifier.new(newConfig, newConfig.bbChonkVerifyMaxBatch, 'peer');
1430
+ const rpcVerifier = await BBCircuitVerifier.new(newConfig);
1431
+ this.rpcProofVerifier = new QueuedIVCVerifier(rpcVerifier, newConfig.numConcurrentIVCVerifiers);
1432
+ } else {
1433
+ this.peerProofVerifier = new TestCircuitVerifier();
1434
+ this.rpcProofVerifier = new TestCircuitVerifier();
1435
+ }
1282
1436
  }
1283
1437
  this.config = newConfig;
1284
1438
  }
@@ -1398,9 +1552,76 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1398
1552
  return this.slasherClient.gatherOffensesForRound(round === 'current' ? undefined : BigInt(round));
1399
1553
  }
1400
1554
  }
1555
+ async reloadKeystore() {
1556
+ if (!this.config.keyStoreDirectory?.length) {
1557
+ throw new BadRequestError('Cannot reload keystore: node is not using a file-based keystore. ' + 'Set KEY_STORE_DIRECTORY to use file-based keystores.');
1558
+ }
1559
+ if (!this.validatorClient) {
1560
+ throw new BadRequestError('Cannot reload keystore: validator is not enabled.');
1561
+ }
1562
+ this.log.info('Reloading keystore from disk');
1563
+ // Re-read and validate keystore files
1564
+ const keyStores = loadKeystores(this.config.keyStoreDirectory);
1565
+ const newManager = new KeystoreManager(mergeKeystores(keyStores));
1566
+ await newManager.validateSigners();
1567
+ ValidatorClient.validateKeyStoreConfiguration(newManager, this.log);
1568
+ // Validate that every validator's publisher keys overlap with the L1 signers
1569
+ // that were initialized at startup. Publishers cannot be hot-reloaded, so a
1570
+ // validator with a publisher key that doesn't match any existing L1 signer
1571
+ // would silently fail on every proposer slot.
1572
+ if (this.keyStoreManager && this.sequencer) {
1573
+ const oldAdapter = NodeKeystoreAdapter.fromKeyStoreManager(this.keyStoreManager);
1574
+ const availablePublishers = new Set(oldAdapter.getAttesterAddresses().flatMap((a)=>oldAdapter.getPublisherAddresses(a).map((p)=>p.toString().toLowerCase())));
1575
+ const newAdapter = NodeKeystoreAdapter.fromKeyStoreManager(newManager);
1576
+ for (const attester of newAdapter.getAttesterAddresses()){
1577
+ const pubs = newAdapter.getPublisherAddresses(attester);
1578
+ if (pubs.length > 0 && !pubs.some((p)=>availablePublishers.has(p.toString().toLowerCase()))) {
1579
+ throw new BadRequestError(`Cannot reload keystore: validator ${attester} has publisher keys ` + `[${pubs.map((p)=>p.toString()).join(', ')}] but none match the L1 signers initialized at startup ` + `[${[
1580
+ ...availablePublishers
1581
+ ].join(', ')}]. Publishers cannot be hot-reloaded — ` + `use an existing publisher key or restart the node.`);
1582
+ }
1583
+ }
1584
+ }
1585
+ // Build adapters for old and new keystores to compute diff
1586
+ const newAdapter = NodeKeystoreAdapter.fromKeyStoreManager(newManager);
1587
+ const newAddresses = newAdapter.getAttesterAddresses();
1588
+ const oldAddresses = this.keyStoreManager ? NodeKeystoreAdapter.fromKeyStoreManager(this.keyStoreManager).getAttesterAddresses() : [];
1589
+ const oldSet = new Set(oldAddresses.map((a)=>a.toString()));
1590
+ const newSet = new Set(newAddresses.map((a)=>a.toString()));
1591
+ const added = newAddresses.filter((a)=>!oldSet.has(a.toString()));
1592
+ const removed = oldAddresses.filter((a)=>!newSet.has(a.toString()));
1593
+ if (added.length > 0) {
1594
+ this.log.info(`Keystore reload: adding attester keys: ${added.map((a)=>a.toString()).join(', ')}`);
1595
+ }
1596
+ if (removed.length > 0) {
1597
+ this.log.info(`Keystore reload: removing attester keys: ${removed.map((a)=>a.toString()).join(', ')}`);
1598
+ }
1599
+ if (added.length === 0 && removed.length === 0) {
1600
+ this.log.info('Keystore reload: attester keys unchanged');
1601
+ }
1602
+ // Update the validator client (coinbase, feeRecipient, attester keys)
1603
+ this.validatorClient.reloadKeystore(newManager);
1604
+ // Update the publisher factory's keystore so newly-added validators
1605
+ // can be matched to existing publisher keys when proposing blocks.
1606
+ if (this.sequencer) {
1607
+ this.sequencer.updatePublisherNodeKeyStore(newAdapter);
1608
+ }
1609
+ // Update slasher's "don't-slash-self" list with new validator addresses
1610
+ if (this.slasherClient && !this.config.slashSelfAllowed) {
1611
+ const slashValidatorsNever = unique([
1612
+ ...this.config.slashValidatorsNever ?? [],
1613
+ ...newAddresses
1614
+ ].map((a)=>a.toString())).map(EthAddress.fromString);
1615
+ this.slasherClient.updateConfig({
1616
+ slashValidatorsNever
1617
+ });
1618
+ }
1619
+ this.keyStoreManager = newManager;
1620
+ this.log.info('Keystore reloaded: coinbase, feeRecipient, and attester keys updated');
1621
+ }
1401
1622
  #getInitialHeaderHash() {
1402
1623
  if (!this.initialHeaderHashPromise) {
1403
- this.initialHeaderHashPromise = this.worldStateSynchronizer.getCommitted().getInitialHeader().hash().then((hash)=>L2BlockHash.fromField(hash));
1624
+ this.initialHeaderHashPromise = this.worldStateSynchronizer.getCommitted().getInitialHeader().hash();
1404
1625
  }
1405
1626
  return this.initialHeaderHashPromise;
1406
1627
  }
@@ -1408,7 +1629,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1408
1629
  * Returns an instance of MerkleTreeOperations having first ensured the world state is fully synched
1409
1630
  * @param block - The block parameter (block number, block hash, or 'latest') at which to get the data.
1410
1631
  * @returns An instance of a committed MerkleTreeOperations
1411
- */ async #getWorldState(block) {
1632
+ */ async getWorldState(block) {
1412
1633
  let blockSyncedTo = BlockNumber.ZERO;
1413
1634
  try {
1414
1635
  // Attempt to sync the world state if necessary
@@ -1420,30 +1641,53 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1420
1641
  this.log.debug(`Using committed db for block 'latest', world state synced upto ${blockSyncedTo}`);
1421
1642
  return this.worldStateSynchronizer.getCommitted();
1422
1643
  }
1423
- if (L2BlockHash.isL2BlockHash(block)) {
1644
+ // Get the block number, either directly from the parameter or by quering the archiver with the block hash
1645
+ let blockNumber;
1646
+ if (BlockHash.isBlockHash(block)) {
1424
1647
  const initialBlockHash = await this.#getInitialHeaderHash();
1425
1648
  if (block.equals(initialBlockHash)) {
1426
1649
  // Block source doesn't handle initial header so we need to handle the case separately.
1427
1650
  return this.worldStateSynchronizer.getSnapshot(BlockNumber.ZERO);
1428
1651
  }
1429
- const blockHashFr = Fr.fromBuffer(block.toBuffer());
1430
- const header = await this.blockSource.getBlockHeaderByHash(blockHashFr);
1652
+ const header = await this.blockSource.getBlockHeaderByHash(block);
1431
1653
  if (!header) {
1432
1654
  throw new Error(`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.`);
1433
1655
  }
1434
- const blockNumber = header.getBlockNumber();
1435
- this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
1436
- return this.worldStateSynchronizer.getSnapshot(blockNumber);
1437
- }
1438
- // Block number provided
1439
- {
1440
- const blockNumber = block;
1441
- if (blockNumber > blockSyncedTo) {
1442
- throw new Error(`Queried block ${block} not yet synced by the node (node is synced upto ${blockSyncedTo}).`);
1656
+ blockNumber = header.getBlockNumber();
1657
+ } else {
1658
+ blockNumber = block;
1659
+ }
1660
+ // Check it's within world state sync range
1661
+ if (blockNumber > blockSyncedTo) {
1662
+ throw new Error(`Queried block ${block} not yet synced by the node (node is synced upto ${blockSyncedTo}).`);
1663
+ }
1664
+ this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
1665
+ const snapshot = this.worldStateSynchronizer.getSnapshot(blockNumber);
1666
+ // Double-check world-state synced to the same block hash as was requested
1667
+ if (BlockHash.isBlockHash(block)) {
1668
+ const blockHash = await snapshot.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber));
1669
+ if (!blockHash || !new BlockHash(blockHash).equals(block)) {
1670
+ throw new Error(`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.`);
1671
+ }
1672
+ }
1673
+ return snapshot;
1674
+ }
1675
+ /** Resolves a block parameter to a block number. */ async resolveBlockNumber(block) {
1676
+ if (block === 'latest') {
1677
+ return BlockNumber(await this.blockSource.getBlockNumber());
1678
+ }
1679
+ if (BlockHash.isBlockHash(block)) {
1680
+ const initialBlockHash = await this.#getInitialHeaderHash();
1681
+ if (block.equals(initialBlockHash)) {
1682
+ return BlockNumber.ZERO;
1683
+ }
1684
+ const header = await this.blockSource.getBlockHeaderByHash(block);
1685
+ if (!header) {
1686
+ throw new Error(`Block hash ${block.toString()} not found.`);
1443
1687
  }
1444
- this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
1445
- return this.worldStateSynchronizer.getSnapshot(blockNumber);
1688
+ return header.getBlockNumber();
1446
1689
  }
1690
+ return block;
1447
1691
  }
1448
1692
  /**
1449
1693
  * Ensure we fully sync the world state