@aztec/aztec-node 0.0.1-commit.ec5f612 → 0.0.1-commit.ec7ac5448

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,19 +372,21 @@ 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';
377
378
  import { Blob } from '@aztec/blob-lib';
378
379
  import { EpochCache } from '@aztec/epoch-cache';
379
380
  import { createEthereumChain } from '@aztec/ethereum/chain';
380
- import { getPublicClient } from '@aztec/ethereum/client';
381
+ import { getPublicClient, makeL1HttpTransport } from '@aztec/ethereum/client';
381
382
  import { RegistryContract, RollupContract } from '@aztec/ethereum/contracts';
382
- import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
383
- import { compactArray, pick, unique } from '@aztec/foundation/collection';
383
+ import { BlockNumber } from '@aztec/foundation/branded-types';
384
+ import { chunkBy, compactArray, pick, unique } from '@aztec/foundation/collection';
384
385
  import { Fr } from '@aztec/foundation/curves/bn254';
385
386
  import { EthAddress } from '@aztec/foundation/eth-address';
386
387
  import { BadRequestError } from '@aztec/foundation/json-rpc';
387
388
  import { createLogger } from '@aztec/foundation/log';
389
+ import { retryUntil } from '@aztec/foundation/retry';
388
390
  import { count } from '@aztec/foundation/string';
389
391
  import { DateProvider, Timer } from '@aztec/foundation/timer';
390
392
  import { MembershipWitness } from '@aztec/foundation/trees';
@@ -411,9 +413,9 @@ import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@az
411
413
  import { PublicSimulationOutput, TxReceipt, TxStatus } from '@aztec/stdlib/tx';
412
414
  import { getPackageVersion } from '@aztec/stdlib/update-checker';
413
415
  import { Attributes, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
414
- import { FullNodeCheckpointsBuilder as CheckpointsBuilder, FullNodeCheckpointsBuilder, NodeKeystoreAdapter, ValidatorClient, createBlockProposalHandler, createValidatorClient } from '@aztec/validator-client';
416
+ import { FullNodeCheckpointsBuilder as CheckpointsBuilder, FullNodeCheckpointsBuilder, NodeKeystoreAdapter, ValidatorClient, createProposalHandler, createValidatorClient } from '@aztec/validator-client';
415
417
  import { createWorldStateSynchronizer } from '@aztec/world-state';
416
- import { createPublicClient, fallback, http } from 'viem';
418
+ import { createPublicClient } from 'viem';
417
419
  import { createSentinel } from '../sentinel/factory.js';
418
420
  import { createKeyStoreForValidator } from './config.js';
419
421
  import { NodeMetrics } from './node_metrics.js';
@@ -440,7 +442,8 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
440
442
  globalVariableBuilder;
441
443
  epochCache;
442
444
  packageVersion;
443
- proofVerifier;
445
+ peerProofVerifier;
446
+ rpcProofVerifier;
444
447
  telemetry;
445
448
  log;
446
449
  blobClient;
@@ -461,7 +464,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
461
464
  // Prevent two snapshot operations to happen simultaneously
462
465
  isUploadingSnapshot;
463
466
  tracer;
464
- constructor(config, p2pClient, blockSource, logsSource, contractDataSource, l1ToL2MessageSource, worldStateSynchronizer, sequencer, proverNode, slasherClient, validatorsSentinel, epochPruneWatcher, l1ChainId, version, globalVariableBuilder, epochCache, packageVersion, proofVerifier, telemetry = getTelemetryClient(), log = createLogger('node'), blobClient, validatorClient, keyStoreManager, debugLogStore = new NullDebugLogStore()){
467
+ 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()){
465
468
  this.config = config;
466
469
  this.p2pClient = p2pClient;
467
470
  this.blockSource = blockSource;
@@ -479,7 +482,8 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
479
482
  this.globalVariableBuilder = globalVariableBuilder;
480
483
  this.epochCache = epochCache;
481
484
  this.packageVersion = packageVersion;
482
- this.proofVerifier = proofVerifier;
485
+ this.peerProofVerifier = peerProofVerifier;
486
+ this.rpcProofVerifier = rpcProofVerifier;
483
487
  this.telemetry = telemetry;
484
488
  this.log = log;
485
489
  this.blobClient = blobClient;
@@ -499,6 +503,9 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
499
503
  throw new Error('debugLogStore should never be enabled when realProofs are set');
500
504
  }
501
505
  }
506
+ /** @internal Exposed for testing — returns the RPC proof verifier. */ getProofVerifier() {
507
+ return this.rpcProofVerifier;
508
+ }
502
509
  async getWorldStateSyncStatus() {
503
510
  const status = await this.worldStateSynchronizer.status();
504
511
  return status.syncSummary;
@@ -559,9 +566,9 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
559
566
  }
560
567
  const publicClient = createPublicClient({
561
568
  chain: ethereumChain.chainInfo,
562
- transport: fallback(config.l1RpcUrls.map((url)=>http(url, {
563
- batch: false
564
- }))),
569
+ transport: makeL1HttpTransport(config.l1RpcUrls, {
570
+ timeout: config.l1HttpTimeoutMS
571
+ }),
565
572
  pollingInterval: config.viemPollingIntervalMS
566
573
  });
567
574
  const l1ContractsAddresses = await RegistryContract.collectAddresses(publicClient, config.l1Contracts.registryAddress, config.rollupVersion ?? 'canonical');
@@ -571,10 +578,11 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
571
578
  ...l1ContractsAddresses
572
579
  };
573
580
  const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString());
574
- const [l1GenesisTime, slotDuration, rollupVersionFromRollup] = await Promise.all([
581
+ const [l1GenesisTime, slotDuration, rollupVersionFromRollup, rollupManaLimit] = await Promise.all([
575
582
  rollupContract.getL1GenesisTime(),
576
583
  rollupContract.getSlotDuration(),
577
- rollupContract.getVersion()
584
+ rollupContract.getVersion(),
585
+ rollupContract.getManaLimit().then(Number)
578
586
  ]);
579
587
  config.rollupVersion ??= Number(rollupVersionFromRollup);
580
588
  if (config.rollupVersion !== Number(rollupVersionFromRollup)) {
@@ -595,8 +603,18 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
595
603
  blockUntilSync: !config.skipArchiverInitialSync
596
604
  });
597
605
  // now create the merkle trees and the world state synchronizer
598
- const worldStateSynchronizer = await createWorldStateSynchronizer(config, archiver, options.prefilledPublicData, telemetry);
599
- const circuitVerifier = config.realProofs || config.debugForceTxProofVerification ? await BBCircuitVerifier.new(config) : new TestCircuitVerifier(config.proverTestVerificationDelayMs);
606
+ const worldStateSynchronizer = await createWorldStateSynchronizer(config, archiver, options.genesis, telemetry);
607
+ const useRealVerifiers = config.realProofs || config.debugForceTxProofVerification;
608
+ let peerProofVerifier;
609
+ let rpcProofVerifier;
610
+ if (useRealVerifiers) {
611
+ peerProofVerifier = await BatchChonkVerifier.new(config, config.bbChonkVerifyMaxBatch, 'peer');
612
+ const rpcVerifier = await BBCircuitVerifier.new(config);
613
+ rpcProofVerifier = new QueuedIVCVerifier(rpcVerifier, config.numConcurrentIVCVerifiers);
614
+ } else {
615
+ peerProofVerifier = new TestCircuitVerifier(config.proverTestVerificationDelayMs);
616
+ rpcProofVerifier = new TestCircuitVerifier(config.proverTestVerificationDelayMs);
617
+ }
600
618
  let debugLogStore;
601
619
  if (!config.realProofs) {
602
620
  log.warn(`Aztec node is accepting fake proofs`);
@@ -605,22 +623,22 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
605
623
  } else {
606
624
  debugLogStore = new NullDebugLogStore();
607
625
  }
608
- const proofVerifier = new QueuedIVCVerifier(config, circuitVerifier);
609
626
  const proverOnly = config.enableProverNode && config.disableValidator;
610
627
  if (proverOnly) {
611
628
  log.info('Starting in prover-only mode: skipping validator, sequencer, sentinel, and slasher subsystems');
612
629
  }
613
630
  // create the tx pool and the p2p client, which will need the l2 block source
614
- const p2pClient = await createP2PClient(config, archiver, proofVerifier, worldStateSynchronizer, epochCache, packageVersion, dateProvider, telemetry, deps.p2pClientDeps);
615
- // We should really not be modifying the config object
616
- config.txPublicSetupAllowList = config.txPublicSetupAllowList ?? await getDefaultAllowedSetupFunctions();
631
+ const p2pClient = await createP2PClient(config, archiver, peerProofVerifier, worldStateSynchronizer, epochCache, packageVersion, dateProvider, telemetry, deps.p2pClientDeps);
617
632
  // We'll accumulate sentinel watchers here
618
633
  const watchers = [];
619
- // Create FullNodeCheckpointsBuilder for block proposal handling and tx validation
634
+ // Create FullNodeCheckpointsBuilder for block proposal handling and tx validation.
635
+ // Override maxTxsPerCheckpoint with the validator-specific limit if set.
620
636
  const validatorCheckpointsBuilder = new FullNodeCheckpointsBuilder({
621
637
  ...config,
622
638
  l1GenesisTime,
623
- slotDuration: Number(slotDuration)
639
+ slotDuration: Number(slotDuration),
640
+ rollupManaLimit,
641
+ maxTxsPerCheckpoint: config.validateMaxTxsPerCheckpoint
624
642
  }, worldStateSynchronizer, archiver, dateProvider, telemetry);
625
643
  let validatorClient;
626
644
  if (!proverOnly) {
@@ -635,32 +653,40 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
635
653
  blockSource: archiver,
636
654
  l1ToL2MessageSource: archiver,
637
655
  keyStoreManager,
638
- blobClient
656
+ blobClient,
657
+ slashingProtectionDb: deps.slashingProtectionDb
639
658
  });
640
659
  // If we have a validator client, register it as a source of offenses for the slasher,
641
660
  // and have it register callbacks on the p2p client *before* we start it, otherwise messages
642
661
  // like attestations or auths will fail.
643
662
  if (validatorClient) {
644
663
  watchers.push(validatorClient);
664
+ const vc = validatorClient;
665
+ const getValidatorAddresses = ()=>vc.getValidatorAddresses().map((a)=>a.toString());
666
+ validatorClient.getProposalHandler().register(p2pClient, true, archiver, getValidatorAddresses);
645
667
  if (!options.dontStartSequencer) {
646
668
  await validatorClient.registerHandlers();
647
669
  }
648
670
  }
649
- // If there's no validator client but alwaysReexecuteBlockProposals is enabled,
650
- // create a BlockProposalHandler to reexecute block proposals for monitoring
651
- if (!validatorClient && config.alwaysReexecuteBlockProposals) {
652
- log.info('Setting up block proposal reexecution for monitoring');
653
- createBlockProposalHandler(config, {
654
- checkpointsBuilder: validatorCheckpointsBuilder,
655
- worldState: worldStateSynchronizer,
656
- epochCache,
657
- blockSource: archiver,
658
- l1ToL2MessageSource: archiver,
659
- p2pClient,
660
- dateProvider,
661
- telemetry
662
- }).registerForReexecution(p2pClient);
663
- }
671
+ }
672
+ // If there's no validator client, create a ProposalHandler to handle block and checkpoint proposals
673
+ // for monitoring or reexecution. Reexecution (default) allows us to follow the pending chain,
674
+ // while non-reexecution is used for validating the proposals and collecting their txs.
675
+ // Checkpoint proposals rebuild blobs if the blob client can upload blobs.
676
+ if (!validatorClient) {
677
+ const reexecute = !!config.alwaysReexecuteBlockProposals;
678
+ log.info(`Setting up proposal handler` + (reexecute ? ' with reexecution of proposals' : ''));
679
+ createProposalHandler(config, {
680
+ checkpointsBuilder: validatorCheckpointsBuilder,
681
+ worldState: worldStateSynchronizer,
682
+ epochCache,
683
+ blockSource: archiver,
684
+ l1ToL2MessageSource: archiver,
685
+ p2pClient,
686
+ blobClient,
687
+ dateProvider,
688
+ telemetry
689
+ }).register(p2pClient, reexecute, archiver);
664
690
  }
665
691
  // Start world state and wait for it to sync to the archiver.
666
692
  await worldStateSynchronizer.start();
@@ -686,12 +712,18 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
686
712
  }
687
713
  // Start p2p-related services once the archiver has completed sync
688
714
  void archiver.waitForInitialSync().then(async ()=>{
689
- await p2pClient.start();
690
715
  await validatorsSentinel?.start();
691
716
  await epochPruneWatcher?.start();
692
717
  await attestationsBlockWatcher?.start();
693
718
  log.info(`All p2p services started`);
694
719
  }).catch((err)=>log.error('Failed to start p2p services after archiver sync', err));
720
+ const globalVariableBuilder = new GlobalVariableBuilder(dateProvider, publicClient, {
721
+ l1Contracts: config.l1Contracts,
722
+ ethereumSlotDuration: config.ethereumSlotDuration,
723
+ rollupVersion: BigInt(config.rollupVersion),
724
+ l1GenesisTime,
725
+ slotDuration: Number(slotDuration)
726
+ });
695
727
  // Validator enabled, create/start relevant service
696
728
  let sequencer;
697
729
  let slasherClient;
@@ -718,16 +750,34 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
718
750
  dateProvider,
719
751
  kzg: Blob.getViemKzgInstance()
720
752
  });
753
+ // Create a funder L1TxUtils from the keystore funding account (if configured)
754
+ const fundingSigner = keyStoreManager?.createFundingSigner();
755
+ let funderL1TxUtils;
756
+ if (fundingSigner) {
757
+ const [funder] = await createL1TxUtilsFromSigners(publicClient, [
758
+ fundingSigner
759
+ ], {
760
+ ...config,
761
+ scope: 'sequencer'
762
+ }, {
763
+ telemetry,
764
+ logger: log.createChild('l1-tx-utils:funder'),
765
+ dateProvider
766
+ });
767
+ funderL1TxUtils = funder;
768
+ }
721
769
  // Create and start the sequencer client
722
770
  const checkpointsBuilder = new CheckpointsBuilder({
723
771
  ...config,
724
772
  l1GenesisTime,
725
- slotDuration: Number(slotDuration)
773
+ slotDuration: Number(slotDuration),
774
+ rollupManaLimit
726
775
  }, worldStateSynchronizer, archiver, dateProvider, telemetry, debugLogStore);
727
776
  sequencer = await SequencerClient.new(config, {
728
777
  ...deps,
729
778
  epochCache,
730
779
  l1TxUtils,
780
+ funderL1TxUtils,
731
781
  validatorClient,
732
782
  p2pClient,
733
783
  worldStateSynchronizer,
@@ -738,7 +788,8 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
738
788
  telemetry,
739
789
  dateProvider,
740
790
  blobClient,
741
- nodeKeyStore: keyStoreManager
791
+ nodeKeyStore: keyStoreManager,
792
+ globalVariableBuilder
742
793
  });
743
794
  }
744
795
  if (!options.dontStartSequencer && sequencer) {
@@ -768,13 +819,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
768
819
  log.info(`Prover node subsystem created but not started`);
769
820
  }
770
821
  }
771
- const globalVariableBuilder = new GlobalVariableBuilder({
772
- ...config,
773
- rollupVersion: BigInt(config.rollupVersion),
774
- l1GenesisTime,
775
- slotDuration: Number(slotDuration)
776
- });
777
- const node = new AztecNodeService(config, p2pClient, archiver, archiver, archiver, archiver, worldStateSynchronizer, sequencer, proverNode, slasherClient, validatorsSentinel, epochPruneWatcher, ethereumChain.chainInfo.id, config.rollupVersion, globalVariableBuilder, epochCache, packageVersion, proofVerifier, telemetry, log, blobClient, validatorClient, keyStoreManager, debugLogStore);
822
+ 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);
778
823
  return node;
779
824
  }
780
825
  /**
@@ -805,7 +850,10 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
805
850
  return Promise.resolve(this.p2pClient.getEnr()?.encodeTxt());
806
851
  }
807
852
  async getAllowedPublicSetup() {
808
- return this.config.txPublicSetupAllowList ?? await getDefaultAllowedSetupFunctions();
853
+ return [
854
+ ...await getDefaultAllowedSetupFunctions(),
855
+ ...this.config.txPublicSetupAllowListExtend ?? []
856
+ ];
809
857
  }
810
858
  /**
811
859
  * Method to determine if the node is ready to accept transactions.
@@ -883,6 +931,9 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
883
931
  async getCheckpointedBlocks(from, limit) {
884
932
  return await this.blockSource.getCheckpointedBlocks(from, limit) ?? [];
885
933
  }
934
+ getCheckpointsDataForEpoch(epochNumber) {
935
+ return this.blockSource.getCheckpointsDataForEpoch(epochNumber);
936
+ }
886
937
  /**
887
938
  * Method to fetch the current min L2 fees.
888
939
  * @returns The current min L2 fees.
@@ -910,6 +961,9 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
910
961
  async getCheckpointedBlockNumber() {
911
962
  return await this.blockSource.getCheckpointedL2BlockNumber();
912
963
  }
964
+ getCheckpointNumber() {
965
+ return this.blockSource.getCheckpointNumber();
966
+ }
913
967
  /**
914
968
  * Method to fetch the version of the package.
915
969
  * @returns The node package version
@@ -935,28 +989,36 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
935
989
  return this.contractDataSource.getContract(address);
936
990
  }
937
991
  async getPrivateLogsByTags(tags, page, referenceBlock) {
992
+ let upToBlockNumber;
938
993
  if (referenceBlock) {
939
994
  const initialBlockHash = await this.#getInitialHeaderHash();
940
- if (!referenceBlock.equals(initialBlockHash)) {
995
+ if (referenceBlock.equals(initialBlockHash)) {
996
+ upToBlockNumber = BlockNumber(0);
997
+ } else {
941
998
  const header = await this.blockSource.getBlockHeaderByHash(referenceBlock);
942
999
  if (!header) {
943
1000
  throw new Error(`Block ${referenceBlock.toString()} not found in the node. This might indicate a reorg has occurred.`);
944
1001
  }
1002
+ upToBlockNumber = header.globalVariables.blockNumber;
945
1003
  }
946
1004
  }
947
- return this.logsSource.getPrivateLogsByTags(tags, page);
1005
+ return this.logsSource.getPrivateLogsByTags(tags, page, upToBlockNumber);
948
1006
  }
949
1007
  async getPublicLogsByTagsFromContract(contractAddress, tags, page, referenceBlock) {
1008
+ let upToBlockNumber;
950
1009
  if (referenceBlock) {
951
1010
  const initialBlockHash = await this.#getInitialHeaderHash();
952
- if (!referenceBlock.equals(initialBlockHash)) {
1011
+ if (referenceBlock.equals(initialBlockHash)) {
1012
+ upToBlockNumber = BlockNumber(0);
1013
+ } else {
953
1014
  const header = await this.blockSource.getBlockHeaderByHash(referenceBlock);
954
1015
  if (!header) {
955
1016
  throw new Error(`Block ${referenceBlock.toString()} not found in the node. This might indicate a reorg has occurred.`);
956
1017
  }
1018
+ upToBlockNumber = header.globalVariables.blockNumber;
957
1019
  }
958
1020
  }
959
- return this.logsSource.getPublicLogsByTagsFromContract(contractAddress, tags, page);
1021
+ return this.logsSource.getPublicLogsByTagsFromContract(contractAddress, tags, page, upToBlockNumber);
960
1022
  }
961
1023
  /**
962
1024
  * Gets public logs based on the provided filter.
@@ -1029,7 +1091,10 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1029
1091
  await tryStop(this.validatorsSentinel);
1030
1092
  await tryStop(this.epochPruneWatcher);
1031
1093
  await tryStop(this.slasherClient);
1032
- await tryStop(this.proofVerifier);
1094
+ await Promise.all([
1095
+ tryStop(this.peerProofVerifier),
1096
+ tryStop(this.rpcProofVerifier)
1097
+ ]);
1033
1098
  await tryStop(this.sequencer);
1034
1099
  await tryStop(this.proverNode);
1035
1100
  await tryStop(this.p2pClient);
@@ -1071,70 +1136,82 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1071
1136
  return compactArray(await Promise.all(txHashes.map((txHash)=>this.getTxByHash(txHash))));
1072
1137
  }
1073
1138
  async findLeavesIndexes(referenceBlock, treeId, leafValues) {
1074
- const committedDb = await this.#getWorldState(referenceBlock);
1139
+ const committedDb = await this.getWorldState(referenceBlock);
1075
1140
  const maybeIndices = await committedDb.findLeafIndices(treeId, leafValues.map((x)=>x.toBuffer()));
1076
- // We filter out undefined values
1077
- const indices = maybeIndices.filter((x)=>x !== undefined);
1078
- // Now we find the block numbers for the indices
1079
- const blockNumbers = await committedDb.getBlockNumbersForLeafIndices(treeId, indices);
1080
- // If any of the block numbers are undefined, we throw an error.
1081
- for(let i = 0; i < indices.length; i++){
1082
- if (blockNumbers[i] === undefined) {
1083
- throw new Error(`Block number is undefined for leaf index ${indices[i]} in tree ${MerkleTreeId[treeId]}`);
1141
+ // Filter out undefined values to query block numbers only for found leaves
1142
+ const definedIndices = maybeIndices.filter((x)=>x !== undefined);
1143
+ // Now we find the block numbers for the defined indices
1144
+ const blockNumbers = await committedDb.getBlockNumbersForLeafIndices(treeId, definedIndices);
1145
+ // Build a map from leaf index to block number
1146
+ const indexToBlockNumber = new Map();
1147
+ for(let i = 0; i < definedIndices.length; i++){
1148
+ const blockNumber = blockNumbers[i];
1149
+ if (blockNumber === undefined) {
1150
+ throw new Error(`Block number is undefined for leaf index ${definedIndices[i]} in tree ${MerkleTreeId[treeId]}`);
1084
1151
  }
1152
+ indexToBlockNumber.set(definedIndices[i], blockNumber);
1085
1153
  }
1086
1154
  // Get unique block numbers in order to optimize num calls to getLeafValue function.
1087
1155
  const uniqueBlockNumbers = [
1088
- ...new Set(blockNumbers.filter((x)=>x !== undefined))
1156
+ ...new Set(indexToBlockNumber.values())
1089
1157
  ];
1090
- // Now we obtain the block hashes from the archive tree by calling await `committedDb.getLeafValue(treeId, index)`
1091
- // (note that block number corresponds to the leaf index in the archive tree).
1158
+ // Now we obtain the block hashes from the archive tree (block number = leaf index in archive tree).
1092
1159
  const blockHashes = await Promise.all(uniqueBlockNumbers.map((blockNumber)=>{
1093
1160
  return committedDb.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber));
1094
1161
  }));
1095
- // If any of the block hashes are undefined, we throw an error.
1162
+ // Build a map from block number to block hash
1163
+ const blockNumberToHash = new Map();
1096
1164
  for(let i = 0; i < uniqueBlockNumbers.length; i++){
1097
- if (blockHashes[i] === undefined) {
1165
+ const blockHash = blockHashes[i];
1166
+ if (blockHash === undefined) {
1098
1167
  throw new Error(`Block hash is undefined for block number ${uniqueBlockNumbers[i]}`);
1099
1168
  }
1169
+ blockNumberToHash.set(uniqueBlockNumbers[i], blockHash);
1100
1170
  }
1101
1171
  // Create DataInBlock objects by combining indices, blockNumbers and blockHashes and return them.
1102
- return maybeIndices.map((index, i)=>{
1172
+ return maybeIndices.map((index)=>{
1103
1173
  if (index === undefined) {
1104
1174
  return undefined;
1105
1175
  }
1106
- const blockNumber = blockNumbers[i];
1176
+ const blockNumber = indexToBlockNumber.get(index);
1107
1177
  if (blockNumber === undefined) {
1108
- return undefined;
1178
+ throw new Error(`Block number not found for leaf index ${index} in tree ${MerkleTreeId[treeId]}`);
1109
1179
  }
1110
- const blockHashIndex = uniqueBlockNumbers.indexOf(blockNumber);
1111
- const blockHash = blockHashes[blockHashIndex];
1112
- if (!blockHash) {
1113
- return undefined;
1180
+ const blockHash = blockNumberToHash.get(blockNumber);
1181
+ if (blockHash === undefined) {
1182
+ throw new Error(`Block hash not found for block number ${blockNumber}`);
1114
1183
  }
1115
1184
  return {
1116
- l2BlockNumber: BlockNumber(Number(blockNumber)),
1185
+ l2BlockNumber: blockNumber,
1117
1186
  l2BlockHash: new BlockHash(blockHash),
1118
1187
  data: index
1119
1188
  };
1120
1189
  });
1121
1190
  }
1122
1191
  async getBlockHashMembershipWitness(referenceBlock, blockHash) {
1123
- const committedDb = await this.#getWorldState(referenceBlock);
1192
+ // The Noir circuit checks the archive membership proof against `anchor_block_header.last_archive.root`,
1193
+ // which is the archive tree root BEFORE the anchor block was added (i.e. the state after block N-1).
1194
+ // So we need the world state at block N-1, not block N, to produce a sibling path matching that root.
1195
+ const referenceBlockNumber = await this.resolveBlockNumber(referenceBlock);
1196
+ if (referenceBlockNumber === BlockNumber.ZERO) {
1197
+ // Block 0 (the initial block) has an empty archive, so no membership witness can exist.
1198
+ return undefined;
1199
+ }
1200
+ const committedDb = await this.getWorldState(BlockNumber(referenceBlockNumber - 1));
1124
1201
  const [pathAndIndex] = await committedDb.findSiblingPaths(MerkleTreeId.ARCHIVE, [
1125
1202
  blockHash
1126
1203
  ]);
1127
1204
  return pathAndIndex === undefined ? undefined : MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
1128
1205
  }
1129
1206
  async getNoteHashMembershipWitness(referenceBlock, noteHash) {
1130
- const committedDb = await this.#getWorldState(referenceBlock);
1207
+ const committedDb = await this.getWorldState(referenceBlock);
1131
1208
  const [pathAndIndex] = await committedDb.findSiblingPaths(MerkleTreeId.NOTE_HASH_TREE, [
1132
1209
  noteHash
1133
1210
  ]);
1134
1211
  return pathAndIndex === undefined ? undefined : MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
1135
1212
  }
1136
1213
  async getL1ToL2MessageMembershipWitness(referenceBlock, l1ToL2Message) {
1137
- const db = await this.#getWorldState(referenceBlock);
1214
+ const db = await this.getWorldState(referenceBlock);
1138
1215
  const [witness] = await db.findSiblingPaths(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, [
1139
1216
  l1ToL2Message
1140
1217
  ]);
@@ -1147,9 +1224,9 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1147
1224
  witness.path
1148
1225
  ];
1149
1226
  }
1150
- async getL1ToL2MessageBlock(l1ToL2Message) {
1227
+ async getL1ToL2MessageCheckpoint(l1ToL2Message) {
1151
1228
  const messageIndex = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
1152
- return messageIndex ? BlockNumber.fromCheckpointNumber(InboxLeaf.checkpointNumberFromIndex(messageIndex)) : undefined;
1229
+ return messageIndex ? InboxLeaf.checkpointNumberFromIndex(messageIndex) : undefined;
1153
1230
  }
1154
1231
  /**
1155
1232
  * Returns whether an L1 to L2 message is synced by archiver and if it's ready to be included in a block.
@@ -1166,23 +1243,11 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1166
1243
  */ async getL2ToL1Messages(epoch) {
1167
1244
  // Assumes `getCheckpointedBlocksForEpoch` returns blocks in ascending order of block number.
1168
1245
  const checkpointedBlocks = await this.blockSource.getCheckpointedBlocksForEpoch(epoch);
1169
- const blocksInCheckpoints = [];
1170
- let previousSlotNumber = SlotNumber.ZERO;
1171
- let checkpointIndex = -1;
1172
- for (const checkpointedBlock of checkpointedBlocks){
1173
- const block = checkpointedBlock.block;
1174
- const slotNumber = block.header.globalVariables.slotNumber;
1175
- if (slotNumber !== previousSlotNumber) {
1176
- checkpointIndex++;
1177
- blocksInCheckpoints.push([]);
1178
- previousSlotNumber = slotNumber;
1179
- }
1180
- blocksInCheckpoints[checkpointIndex].push(block);
1181
- }
1246
+ const blocksInCheckpoints = chunkBy(checkpointedBlocks, (cb)=>cb.block.header.globalVariables.slotNumber).map((group)=>group.map((cb)=>cb.block));
1182
1247
  return blocksInCheckpoints.map((blocks)=>blocks.map((block)=>block.body.txEffects.map((txEffect)=>txEffect.l2ToL1Msgs)));
1183
1248
  }
1184
1249
  async getNullifierMembershipWitness(referenceBlock, nullifier) {
1185
- const db = await this.#getWorldState(referenceBlock);
1250
+ const db = await this.getWorldState(referenceBlock);
1186
1251
  const [witness] = await db.findSiblingPaths(MerkleTreeId.NULLIFIER_TREE, [
1187
1252
  nullifier.toBuffer()
1188
1253
  ]);
@@ -1196,36 +1261,22 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1196
1261
  }
1197
1262
  return new NullifierMembershipWitness(index, leafPreimage, path);
1198
1263
  }
1199
- /**
1200
- * Returns a low nullifier membership witness for a given nullifier at a given block.
1201
- * @param referenceBlock - The block parameter (block number, block hash, or 'latest') at which to get the data
1202
- * (which contains the root of the nullifier tree in which we are searching for the nullifier).
1203
- * @param nullifier - Nullifier we try to find the low nullifier witness for.
1204
- * @returns The low nullifier membership witness (if found).
1205
- * @remarks Low nullifier witness can be used to perform a nullifier non-inclusion proof by leveraging the "linked
1206
- * list structure" of leaves and proving that a lower nullifier is pointing to a bigger next value than the nullifier
1207
- * we are trying to prove non-inclusion for.
1208
- *
1209
- * Note: This function returns the membership witness of the nullifier itself and not the low nullifier when
1210
- * the nullifier already exists in the tree. This is because the `getPreviousValueIndex` function returns the
1211
- * index of the nullifier itself when it already exists in the tree.
1212
- * TODO: This is a confusing behavior and we should eventually address that.
1213
- */ async getLowNullifierMembershipWitness(referenceBlock, nullifier) {
1214
- const committedDb = await this.#getWorldState(referenceBlock);
1264
+ async getLowNullifierMembershipWitness(referenceBlock, nullifier) {
1265
+ const committedDb = await this.getWorldState(referenceBlock);
1215
1266
  const findResult = await committedDb.getPreviousValueIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBigInt());
1216
1267
  if (!findResult) {
1217
1268
  return undefined;
1218
1269
  }
1219
1270
  const { index, alreadyPresent } = findResult;
1220
1271
  if (alreadyPresent) {
1221
- this.log.warn(`Nullifier ${nullifier.toBigInt()} already exists in the tree`);
1272
+ throw new Error(`Cannot prove nullifier non-inclusion: nullifier ${nullifier.toBigInt()} already exists in the tree`);
1222
1273
  }
1223
1274
  const preimageData = await committedDb.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index);
1224
1275
  const siblingPath = await committedDb.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, BigInt(index));
1225
1276
  return new NullifierMembershipWitness(BigInt(index), preimageData, siblingPath);
1226
1277
  }
1227
1278
  async getPublicDataWitness(referenceBlock, leafSlot) {
1228
- const committedDb = await this.#getWorldState(referenceBlock);
1279
+ const committedDb = await this.getWorldState(referenceBlock);
1229
1280
  const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
1230
1281
  if (!lowLeafResult) {
1231
1282
  return undefined;
@@ -1236,7 +1287,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1236
1287
  }
1237
1288
  }
1238
1289
  async getPublicStorageAt(referenceBlock, contract, slot) {
1239
- const committedDb = await this.#getWorldState(referenceBlock);
1290
+ const committedDb = await this.getWorldState(referenceBlock);
1240
1291
  const leafSlot = await computePublicDataTreeLeafSlot(contract, slot);
1241
1292
  const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
1242
1293
  if (!lowLeafResult || !lowLeafResult.alreadyPresent) {
@@ -1315,7 +1366,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1315
1366
  });
1316
1367
  const processor = publicProcessorFactory.create(merkleTreeFork, newGlobalVariables, config);
1317
1368
  // REFACTOR: Consider merging ProcessReturnValues into ProcessedTx
1318
- const [processedTxs, failedTxs, _usedTxs, returns, _blobFields, debugLogs] = await processor.process([
1369
+ const [processedTxs, failedTxs, _usedTxs, returns, debugLogs] = await processor.process([
1319
1370
  tx
1320
1371
  ]);
1321
1372
  // REFACTOR: Consider returning the error rather than throwing
@@ -1333,19 +1384,26 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1333
1384
  }
1334
1385
  async isValidTx(tx, { isSimulation, skipFeeEnforcement } = {}) {
1335
1386
  const db = this.worldStateSynchronizer.getCommitted();
1336
- const verifier = isSimulation ? undefined : this.proofVerifier;
1387
+ const verifier = isSimulation ? undefined : this.rpcProofVerifier;
1337
1388
  // We accept transactions if they are not expired by the next slot (checked based on the ExpirationTimestamp field)
1338
1389
  const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
1339
1390
  const blockNumber = BlockNumber(await this.blockSource.getBlockNumber() + 1);
1391
+ const l1Constants = await this.blockSource.getL1Constants();
1340
1392
  const validator = createTxValidatorForAcceptingTxsOverRPC(db, this.contractDataSource, verifier, {
1341
1393
  timestamp: nextSlotTimestamp,
1342
1394
  blockNumber,
1343
1395
  l1ChainId: this.l1ChainId,
1344
1396
  rollupVersion: this.version,
1345
- setupAllowList: this.config.txPublicSetupAllowList ?? await getDefaultAllowedSetupFunctions(),
1397
+ setupAllowList: [
1398
+ ...await getDefaultAllowedSetupFunctions(),
1399
+ ...this.config.txPublicSetupAllowListExtend ?? []
1400
+ ],
1346
1401
  gasFees: await this.getCurrentMinFees(),
1347
1402
  skipFeeEnforcement,
1348
- txsPermitted: !this.config.disableTransactions
1403
+ txsPermitted: !this.config.disableTransactions,
1404
+ rollupManaLimit: l1Constants.rollupManaLimit,
1405
+ maxBlockL2Gas: this.config.validateMaxL2BlockGas,
1406
+ maxBlockDAGas: this.config.validateMaxDABlockGas
1349
1407
  }, this.log.getBindings());
1350
1408
  return await validator.validateTx(tx);
1351
1409
  }
@@ -1368,7 +1426,18 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1368
1426
  archiver.updateConfig(config);
1369
1427
  }
1370
1428
  if (newConfig.realProofs !== this.config.realProofs) {
1371
- this.proofVerifier = config.realProofs ? await BBCircuitVerifier.new(newConfig) : new TestCircuitVerifier();
1429
+ await Promise.all([
1430
+ tryStop(this.peerProofVerifier),
1431
+ tryStop(this.rpcProofVerifier)
1432
+ ]);
1433
+ if (newConfig.realProofs) {
1434
+ this.peerProofVerifier = await BatchChonkVerifier.new(newConfig, newConfig.bbChonkVerifyMaxBatch, 'peer');
1435
+ const rpcVerifier = await BBCircuitVerifier.new(newConfig);
1436
+ this.rpcProofVerifier = new QueuedIVCVerifier(rpcVerifier, newConfig.numConcurrentIVCVerifiers);
1437
+ } else {
1438
+ this.peerProofVerifier = new TestCircuitVerifier();
1439
+ this.rpcProofVerifier = new TestCircuitVerifier();
1440
+ }
1372
1441
  }
1373
1442
  this.config = newConfig;
1374
1443
  }
@@ -1428,7 +1497,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1428
1497
  });
1429
1498
  return Promise.resolve();
1430
1499
  }
1431
- async rollbackTo(targetBlock, force) {
1500
+ async rollbackTo(targetBlock, force, resumeSync = true) {
1432
1501
  const archiver = this.blockSource;
1433
1502
  if (!('rollbackTo' in archiver)) {
1434
1503
  throw new Error('Archiver implementation does not support rollbacks.');
@@ -1456,9 +1525,13 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1456
1525
  this.log.error(`Error during rollback`, err);
1457
1526
  throw err;
1458
1527
  } finally{
1459
- this.log.info(`Resuming world state and archiver sync.`);
1460
- this.worldStateSynchronizer.resumeSync();
1461
- archiver.resume();
1528
+ if (resumeSync) {
1529
+ this.log.info(`Resuming world state and archiver sync.`);
1530
+ this.worldStateSynchronizer.resumeSync();
1531
+ archiver.resume();
1532
+ } else {
1533
+ this.log.info(`Sync left paused after rollback (resumeSync=false).`);
1534
+ }
1462
1535
  }
1463
1536
  }
1464
1537
  async pauseSync() {
@@ -1472,18 +1545,12 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1472
1545
  this.blockSource.resume();
1473
1546
  return Promise.resolve();
1474
1547
  }
1475
- getSlashPayloads() {
1476
- if (!this.slasherClient) {
1477
- throw new Error(`Slasher client not enabled`);
1478
- }
1479
- return this.slasherClient.getSlashPayloads();
1480
- }
1481
1548
  getSlashOffenses(round) {
1482
1549
  if (!this.slasherClient) {
1483
1550
  throw new Error(`Slasher client not enabled`);
1484
1551
  }
1485
1552
  if (round === 'all') {
1486
- return this.slasherClient.getPendingOffenses();
1553
+ return this.slasherClient.getOffenses();
1487
1554
  } else {
1488
1555
  return this.slasherClient.gatherOffensesForRound(round === 'current' ? undefined : BigInt(round));
1489
1556
  }
@@ -1555,6 +1622,33 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1555
1622
  this.keyStoreManager = newManager;
1556
1623
  this.log.info('Keystore reloaded: coinbase, feeRecipient, and attester keys updated');
1557
1624
  }
1625
+ async mineBlock() {
1626
+ if (!this.sequencer) {
1627
+ throw new BadRequestError('Cannot mine block: no sequencer is running');
1628
+ }
1629
+ const currentBlockNumber = await this.getBlockNumber();
1630
+ // Use slot duration + 50% buffer as the timeout so this works on running networks too
1631
+ const { slotDuration } = await this.blockSource.getL1Constants();
1632
+ const timeoutSeconds = Math.ceil(slotDuration * 1.5);
1633
+ // Temporarily set minTxsPerBlock to 0 so the sequencer produces a block even with no txs
1634
+ const originalMinTxsPerBlock = this.sequencer.getSequencer().getConfig().minTxsPerBlock;
1635
+ this.sequencer.updateConfig({
1636
+ minTxsPerBlock: 0
1637
+ });
1638
+ try {
1639
+ // Trigger the sequencer to produce a block immediately
1640
+ void this.sequencer.trigger();
1641
+ // Wait for the new L2 block to appear
1642
+ await retryUntil(async ()=>{
1643
+ const newBlockNumber = await this.getBlockNumber();
1644
+ return newBlockNumber > currentBlockNumber ? true : undefined;
1645
+ }, 'mineBlock', timeoutSeconds, 0.1);
1646
+ } finally{
1647
+ this.sequencer.updateConfig({
1648
+ minTxsPerBlock: originalMinTxsPerBlock
1649
+ });
1650
+ }
1651
+ }
1558
1652
  #getInitialHeaderHash() {
1559
1653
  if (!this.initialHeaderHashPromise) {
1560
1654
  this.initialHeaderHashPromise = this.worldStateSynchronizer.getCommitted().getInitialHeader().hash();
@@ -1565,7 +1659,7 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1565
1659
  * Returns an instance of MerkleTreeOperations having first ensured the world state is fully synched
1566
1660
  * @param block - The block parameter (block number, block hash, or 'latest') at which to get the data.
1567
1661
  * @returns An instance of a committed MerkleTreeOperations
1568
- */ async #getWorldState(block) {
1662
+ */ async getWorldState(block) {
1569
1663
  let blockSyncedTo = BlockNumber.ZERO;
1570
1664
  try {
1571
1665
  // Attempt to sync the world state if necessary
@@ -1577,6 +1671,8 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1577
1671
  this.log.debug(`Using committed db for block 'latest', world state synced upto ${blockSyncedTo}`);
1578
1672
  return this.worldStateSynchronizer.getCommitted();
1579
1673
  }
1674
+ // Get the block number, either directly from the parameter or by quering the archiver with the block hash
1675
+ let blockNumber;
1580
1676
  if (BlockHash.isBlockHash(block)) {
1581
1677
  const initialBlockHash = await this.#getInitialHeaderHash();
1582
1678
  if (block.equals(initialBlockHash)) {
@@ -1587,19 +1683,41 @@ _dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
1587
1683
  if (!header) {
1588
1684
  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.`);
1589
1685
  }
1590
- const blockNumber = header.getBlockNumber();
1591
- this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
1592
- return this.worldStateSynchronizer.getSnapshot(blockNumber);
1593
- }
1594
- // Block number provided
1595
- {
1596
- const blockNumber = block;
1597
- if (blockNumber > blockSyncedTo) {
1598
- throw new Error(`Queried block ${block} not yet synced by the node (node is synced upto ${blockSyncedTo}).`);
1686
+ blockNumber = header.getBlockNumber();
1687
+ } else {
1688
+ blockNumber = block;
1689
+ }
1690
+ // Check it's within world state sync range
1691
+ if (blockNumber > blockSyncedTo) {
1692
+ throw new Error(`Queried block ${block} not yet synced by the node (node is synced upto ${blockSyncedTo}).`);
1693
+ }
1694
+ this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
1695
+ const snapshot = this.worldStateSynchronizer.getSnapshot(blockNumber);
1696
+ // Double-check world-state synced to the same block hash as was requested
1697
+ if (BlockHash.isBlockHash(block)) {
1698
+ const blockHash = await snapshot.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber));
1699
+ if (!blockHash || !new BlockHash(blockHash).equals(block)) {
1700
+ 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.`);
1701
+ }
1702
+ }
1703
+ return snapshot;
1704
+ }
1705
+ /** Resolves a block parameter to a block number. */ async resolveBlockNumber(block) {
1706
+ if (block === 'latest') {
1707
+ return BlockNumber(await this.blockSource.getBlockNumber());
1708
+ }
1709
+ if (BlockHash.isBlockHash(block)) {
1710
+ const initialBlockHash = await this.#getInitialHeaderHash();
1711
+ if (block.equals(initialBlockHash)) {
1712
+ return BlockNumber.ZERO;
1713
+ }
1714
+ const header = await this.blockSource.getBlockHeaderByHash(block);
1715
+ if (!header) {
1716
+ throw new Error(`Block hash ${block.toString()} not found.`);
1599
1717
  }
1600
- this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
1601
- return this.worldStateSynchronizer.getSnapshot(blockNumber);
1718
+ return header.getBlockNumber();
1602
1719
  }
1720
+ return block;
1603
1721
  }
1604
1722
  /**
1605
1723
  * Ensure we fully sync the world state