@aztec/aztec-node 0.0.1-commit.03f7ef2 → 0.0.1-commit.0b941701

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.
@@ -1,14 +1,8 @@
1
1
  import { Archiver, createArchiver } from '@aztec/archiver';
2
2
  import { BBCircuitVerifier, QueuedIVCVerifier, TestCircuitVerifier } from '@aztec/bb-prover';
3
- import { type BlobClientInterface, createBlobClient } from '@aztec/blob-client/client';
4
- import {
5
- type BlobFileStoreMetadata,
6
- createReadOnlyFileStoreBlobClients,
7
- createWritableFileStoreBlobClient,
8
- } from '@aztec/blob-client/filestore';
3
+ import { type BlobClientInterface, createBlobClientWithFileStores } from '@aztec/blob-client/client';
9
4
  import {
10
5
  ARCHIVE_HEIGHT,
11
- INITIAL_L2_BLOCK_NUM,
12
6
  type L1_TO_L2_MSG_TREE_HEIGHT,
13
7
  type NOTE_HASH_TREE_HEIGHT,
14
8
  type NULLIFIER_TREE_HEIGHT,
@@ -19,7 +13,7 @@ import { createEthereumChain } from '@aztec/ethereum/chain';
19
13
  import { getPublicClient } from '@aztec/ethereum/client';
20
14
  import { RegistryContract, RollupContract } from '@aztec/ethereum/contracts';
21
15
  import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
22
- import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
16
+ import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
23
17
  import { compactArray, pick } from '@aztec/foundation/collection';
24
18
  import { Fr } from '@aztec/foundation/curves/bn254';
25
19
  import { EthAddress } from '@aztec/foundation/eth-address';
@@ -36,14 +30,7 @@ import {
36
30
  } from '@aztec/node-lib/factories';
37
31
  import { type P2P, type P2PClientDeps, createP2PClient, getDefaultAllowedSetupFunctions } from '@aztec/p2p';
38
32
  import { ProtocolContractAddress } from '@aztec/protocol-contracts';
39
- import {
40
- BlockBuilder,
41
- GlobalVariableBuilder,
42
- SequencerClient,
43
- type SequencerPublisher,
44
- createValidatorForAcceptingTxs,
45
- } from '@aztec/sequencer-client';
46
- import { CheckpointsBuilder } from '@aztec/sequencer-client';
33
+ import { GlobalVariableBuilder, SequencerClient, type SequencerPublisher } from '@aztec/sequencer-client';
47
34
  import { PublicProcessorFactory } from '@aztec/simulator/server';
48
35
  import {
49
36
  AttestationsBlockWatcher,
@@ -54,14 +41,8 @@ import {
54
41
  } from '@aztec/slasher';
55
42
  import { CollectionLimitsConfig, PublicSimulatorConfig } from '@aztec/stdlib/avm';
56
43
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
57
- import {
58
- type BlockParameter,
59
- type DataInBlock,
60
- type L2Block,
61
- L2BlockHash,
62
- type L2BlockSource,
63
- type PublishedL2Block,
64
- } from '@aztec/stdlib/block';
44
+ import { type BlockParameter, type DataInBlock, L2Block, L2BlockHash, type L2BlockSource } from '@aztec/stdlib/block';
45
+ import type { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
65
46
  import type {
66
47
  ContractClassPublic,
67
48
  ContractDataSource,
@@ -116,10 +97,13 @@ import {
116
97
  trackSpan,
117
98
  } from '@aztec/telemetry-client';
118
99
  import {
100
+ FullNodeCheckpointsBuilder as CheckpointsBuilder,
101
+ FullNodeCheckpointsBuilder,
119
102
  NodeKeystoreAdapter,
120
103
  ValidatorClient,
121
104
  createBlockProposalHandler,
122
105
  createValidatorClient,
106
+ createValidatorForAcceptingTxs,
123
107
  } from '@aztec/validator-client';
124
108
  import { createWorldStateSynchronizer } from '@aztec/world-state';
125
109
 
@@ -135,6 +119,7 @@ import { NodeMetrics } from './node_metrics.js';
135
119
  */
136
120
  export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
137
121
  private metrics: NodeMetrics;
122
+ private initialHeaderHashPromise: Promise<L2BlockHash> | undefined = undefined;
138
123
 
139
124
  // Prevent two snapshot operations to happen simultaneously
140
125
  private isUploadingSnapshot = false;
@@ -161,6 +146,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
161
146
  private proofVerifier: ClientProtocolCircuitVerifier,
162
147
  private telemetry: TelemetryClient = getTelemetryClient(),
163
148
  private log = createLogger('node'),
149
+ private blobClient?: BlobClientInterface,
164
150
  ) {
165
151
  this.metrics = new NodeMetrics(telemetry, 'AztecNodeService');
166
152
  this.tracer = telemetry.getTracer('AztecNodeService');
@@ -190,7 +176,6 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
190
176
  logger?: Logger;
191
177
  publisher?: SequencerPublisher;
192
178
  dateProvider?: DateProvider;
193
- blobClient?: BlobClientInterface;
194
179
  p2pClientDeps?: P2PClientDeps<P2PClientType.Full>;
195
180
  } = {},
196
181
  options: {
@@ -270,24 +255,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
270
255
  );
271
256
  }
272
257
 
273
- const blobFileStoreMetadata: BlobFileStoreMetadata = {
274
- l1ChainId: config.l1ChainId,
275
- rollupVersion: config.rollupVersion,
276
- rollupAddress: config.l1Contracts.rollupAddress.toString(),
277
- };
278
-
279
- const [fileStoreClients, fileStoreUploadClient] = await Promise.all([
280
- createReadOnlyFileStoreBlobClients(config.blobFileStoreUrls, blobFileStoreMetadata, log),
281
- createWritableFileStoreBlobClient(config.blobFileStoreUploadUrl, blobFileStoreMetadata, log),
282
- ]);
283
-
284
- const blobClient =
285
- deps.blobClient ??
286
- createBlobClient(config, {
287
- logger: createLogger('node:blob-client:client'),
288
- fileStoreClients,
289
- fileStoreUploadClient,
290
- });
258
+ const blobClient = await createBlobClientWithFileStores(config, createLogger('node:blob-client:client'));
291
259
 
292
260
  // attempt snapshot sync if possible
293
261
  await trySnapshotSync(config, log);
@@ -333,7 +301,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
333
301
  // We should really not be modifying the config object
334
302
  config.txPublicSetupAllowList = config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions());
335
303
 
336
- const blockBuilder = new BlockBuilder(
304
+ // Create FullNodeCheckpointsBuilder for validator and non-validator block proposal handling
305
+ const validatorCheckpointsBuilder = new FullNodeCheckpointsBuilder(
337
306
  { ...config, l1GenesisTime, slotDuration: Number(slotDuration) },
338
307
  worldStateSynchronizer,
339
308
  archiver,
@@ -345,16 +314,17 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
345
314
  const watchers: Watcher[] = [];
346
315
 
347
316
  // Create validator client if required
348
- const validatorClient = createValidatorClient(config, {
317
+ const validatorClient = await createValidatorClient(config, {
318
+ checkpointsBuilder: validatorCheckpointsBuilder,
319
+ worldState: worldStateSynchronizer,
349
320
  p2pClient,
350
321
  telemetry,
351
322
  dateProvider,
352
323
  epochCache,
353
- blockBuilder,
354
324
  blockSource: archiver,
355
325
  l1ToL2MessageSource: archiver,
356
326
  keyStoreManager,
357
- fileStoreBlobUploadClient: fileStoreUploadClient,
327
+ blobClient,
358
328
  });
359
329
 
360
330
  // If we have a validator client, register it as a source of offenses for the slasher,
@@ -372,7 +342,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
372
342
  if (!validatorClient && config.alwaysReexecuteBlockProposals) {
373
343
  log.info('Setting up block proposal reexecution for monitoring');
374
344
  createBlockProposalHandler(config, {
375
- blockBuilder,
345
+ checkpointsBuilder: validatorCheckpointsBuilder,
346
+ worldState: worldStateSynchronizer,
376
347
  epochCache,
377
348
  blockSource: archiver,
378
349
  l1ToL2MessageSource: archiver,
@@ -400,7 +371,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
400
371
  archiver,
401
372
  epochCache,
402
373
  p2pClient.getTxProvider(),
403
- blockBuilder,
374
+ validatorCheckpointsBuilder,
404
375
  config,
405
376
  );
406
377
  watchers.push(epochPruneWatcher);
@@ -465,6 +436,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
465
436
  // Create and start the sequencer client
466
437
  const checkpointsBuilder = new CheckpointsBuilder(
467
438
  { ...config, l1GenesisTime, slotDuration: Number(slotDuration) },
439
+ worldStateSynchronizer,
468
440
  archiver,
469
441
  dateProvider,
470
442
  telemetry,
@@ -522,6 +494,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
522
494
  proofVerifier,
523
495
  telemetry,
524
496
  log,
497
+ blobClient,
525
498
  );
526
499
  }
527
500
 
@@ -592,13 +565,19 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
592
565
  }
593
566
 
594
567
  /**
595
- * Get a block specified by its number.
596
- * @param number - The block number being requested.
568
+ * Get a block specified by its block number, block hash, or 'latest'.
569
+ * @param block - The block parameter (block number, block hash, or 'latest').
597
570
  * @returns The requested block.
598
571
  */
599
- public async getBlock(number: BlockParameter): Promise<L2Block | undefined> {
600
- const blockNumber = number === 'latest' ? await this.getBlockNumber() : (number as BlockNumber);
601
- return await this.blockSource.getBlock(blockNumber);
572
+ public async getBlock(block: BlockParameter): Promise<L2Block | undefined> {
573
+ if (L2BlockHash.isL2BlockHash(block)) {
574
+ return this.getBlockByHash(Fr.fromBuffer(block.toBuffer()));
575
+ }
576
+ const blockNumber = block === 'latest' ? await this.getBlockNumber() : (block as BlockNumber);
577
+ if (blockNumber === BlockNumber.ZERO) {
578
+ return this.buildInitialBlock();
579
+ }
580
+ return await this.blockSource.getL2Block(blockNumber);
602
581
  }
603
582
 
604
583
  /**
@@ -607,8 +586,16 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
607
586
  * @returns The requested block.
608
587
  */
609
588
  public async getBlockByHash(blockHash: Fr): Promise<L2Block | undefined> {
610
- const publishedBlock = await this.blockSource.getPublishedBlockByHash(blockHash);
611
- return publishedBlock?.block;
589
+ const initialBlockHash = await this.#getInitialHeaderHash();
590
+ if (blockHash.equals(Fr.fromBuffer(initialBlockHash.toBuffer()))) {
591
+ return this.buildInitialBlock();
592
+ }
593
+ return await this.blockSource.getL2BlockByHash(blockHash);
594
+ }
595
+
596
+ private buildInitialBlock(): L2Block {
597
+ const initialHeader = this.worldStateSynchronizer.getCommitted().getInitialHeader();
598
+ return L2Block.empty(initialHeader);
612
599
  }
613
600
 
614
601
  /**
@@ -617,8 +604,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
617
604
  * @returns The requested block.
618
605
  */
619
606
  public async getBlockByArchive(archive: Fr): Promise<L2Block | undefined> {
620
- const publishedBlock = await this.blockSource.getPublishedBlockByArchive(archive);
621
- return publishedBlock?.block;
607
+ return await this.blockSource.getL2BlockByArchive(archive);
622
608
  }
623
609
 
624
610
  /**
@@ -628,19 +614,23 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
628
614
  * @returns The blocks requested.
629
615
  */
630
616
  public async getBlocks(from: BlockNumber, limit: number): Promise<L2Block[]> {
631
- return (await this.blockSource.getBlocks(from, limit)) ?? [];
617
+ return (await this.blockSource.getBlocks(from, BlockNumber(limit))) ?? [];
632
618
  }
633
619
 
634
- public async getPublishedBlocks(from: BlockNumber, limit: number): Promise<PublishedL2Block[]> {
635
- return (await this.blockSource.getPublishedBlocks(from, limit)) ?? [];
620
+ public async getCheckpoints(from: CheckpointNumber, limit: number): Promise<PublishedCheckpoint[]> {
621
+ return (await this.blockSource.getCheckpoints(from, limit)) ?? [];
622
+ }
623
+
624
+ public async getCheckpointedBlocks(from: BlockNumber, limit: number) {
625
+ return (await this.blockSource.getCheckpointedBlocks(from, limit)) ?? [];
636
626
  }
637
627
 
638
628
  /**
639
- * Method to fetch the current base fees.
640
- * @returns The current base fees.
629
+ * Method to fetch the current min L2 fees.
630
+ * @returns The current min L2 fees.
641
631
  */
642
- public async getCurrentBaseFees(): Promise<GasFees> {
643
- return await this.globalVariableBuilder.getCurrentBaseFees();
632
+ public async getCurrentMinFees(): Promise<GasFees> {
633
+ return await this.globalVariableBuilder.getCurrentMinFees();
644
634
  }
645
635
 
646
636
  public async getMaxPriorityFees(): Promise<GasFees> {
@@ -663,6 +653,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
663
653
  return await this.blockSource.getProvenBlockNumber();
664
654
  }
665
655
 
656
+ public async getCheckpointedBlockNumber(): Promise<BlockNumber> {
657
+ return await this.blockSource.getCheckpointedL2BlockNumber();
658
+ }
659
+
666
660
  /**
667
661
  * Method to fetch the version of the package.
668
662
  * @returns The node package version
@@ -695,12 +689,45 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
695
689
  return this.contractDataSource.getContract(address);
696
690
  }
697
691
 
698
- public getPrivateLogsByTags(tags: SiloedTag[]): Promise<TxScopedL2Log[][]> {
699
- return this.logsSource.getPrivateLogsByTags(tags);
700
- }
701
-
702
- public getPublicLogsByTagsFromContract(contractAddress: AztecAddress, tags: Tag[]): Promise<TxScopedL2Log[][]> {
703
- return this.logsSource.getPublicLogsByTagsFromContract(contractAddress, tags);
692
+ public async getPrivateLogsByTags(
693
+ tags: SiloedTag[],
694
+ page?: number,
695
+ referenceBlock?: L2BlockHash,
696
+ ): Promise<TxScopedL2Log[][]> {
697
+ if (referenceBlock) {
698
+ const initialBlockHash = await this.#getInitialHeaderHash();
699
+ if (!referenceBlock.equals(initialBlockHash)) {
700
+ const blockHashFr = Fr.fromBuffer(referenceBlock.toBuffer());
701
+ const header = await this.blockSource.getBlockHeaderByHash(blockHashFr);
702
+ if (!header) {
703
+ throw new Error(
704
+ `Block ${referenceBlock.toString()} not found in the node. This might indicate a reorg has occurred.`,
705
+ );
706
+ }
707
+ }
708
+ }
709
+ return this.logsSource.getPrivateLogsByTags(tags, page);
710
+ }
711
+
712
+ public async getPublicLogsByTagsFromContract(
713
+ contractAddress: AztecAddress,
714
+ tags: Tag[],
715
+ page?: number,
716
+ referenceBlock?: L2BlockHash,
717
+ ): Promise<TxScopedL2Log[][]> {
718
+ if (referenceBlock) {
719
+ const initialBlockHash = await this.#getInitialHeaderHash();
720
+ if (!referenceBlock.equals(initialBlockHash)) {
721
+ const blockHashFr = Fr.fromBuffer(referenceBlock.toBuffer());
722
+ const header = await this.blockSource.getBlockHeaderByHash(blockHashFr);
723
+ if (!header) {
724
+ throw new Error(
725
+ `Block ${referenceBlock.toString()} not found in the node. This might indicate a reorg has occurred.`,
726
+ );
727
+ }
728
+ }
729
+ }
730
+ return this.logsSource.getPublicLogsByTagsFromContract(contractAddress, tags, page);
704
731
  }
705
732
 
706
733
  /**
@@ -747,21 +774,26 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
747
774
  }
748
775
 
749
776
  public async getTxReceipt(txHash: TxHash): Promise<TxReceipt> {
750
- let txReceipt = new TxReceipt(txHash, TxStatus.DROPPED, 'Tx dropped by P2P node.');
751
-
752
- // We first check if the tx is in pending (instead of first checking if it is mined) because if we first check
753
- // for mined and then for pending there could be a race condition where the tx is mined between the two checks
754
- // and we would incorrectly return a TxReceipt with status DROPPED
755
- if ((await this.p2pClient.getTxStatus(txHash)) === 'pending') {
756
- txReceipt = new TxReceipt(txHash, TxStatus.PENDING, '');
757
- }
777
+ // Check the tx pool status first. If the tx is known to the pool (pending or mined), we'll use that
778
+ // as a fallback if we don't find a settled receipt in the archiver.
779
+ const txPoolStatus = await this.p2pClient.getTxStatus(txHash);
780
+ const isKnownToPool = txPoolStatus === 'pending' || txPoolStatus === 'mined';
758
781
 
782
+ // Then get the actual tx from the archiver, which tracks every tx in a mined block.
759
783
  const settledTxReceipt = await this.blockSource.getSettledTxReceipt(txHash);
784
+
760
785
  if (settledTxReceipt) {
761
- txReceipt = settledTxReceipt;
786
+ // If the archiver has the receipt then return it.
787
+ return settledTxReceipt;
788
+ } else if (isKnownToPool) {
789
+ // If the tx is in the pool but not in the archiver, it's pending.
790
+ // This handles race conditions between archiver and p2p, where the archiver
791
+ // has pruned the block in which a tx was mined, but p2p has not caught up yet.
792
+ return new TxReceipt(txHash, TxStatus.PENDING, undefined, undefined);
793
+ } else {
794
+ // Otherwise, if we don't know the tx, we consider it dropped.
795
+ return new TxReceipt(txHash, TxStatus.DROPPED, undefined, 'Tx dropped by P2P node');
762
796
  }
763
-
764
- return txReceipt;
765
797
  }
766
798
 
767
799
  public getTxEffect(txHash: TxHash): Promise<IndexedTxEffect | undefined> {
@@ -781,10 +813,19 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
781
813
  await tryStop(this.p2pClient);
782
814
  await tryStop(this.worldStateSynchronizer);
783
815
  await tryStop(this.blockSource);
816
+ await tryStop(this.blobClient);
784
817
  await tryStop(this.telemetry);
785
818
  this.log.info(`Stopped Aztec Node`);
786
819
  }
787
820
 
821
+ /**
822
+ * Returns the blob client used by this node.
823
+ * @internal - Exposed for testing purposes only.
824
+ */
825
+ public getBlobClient(): BlobClientInterface | undefined {
826
+ return this.blobClient;
827
+ }
828
+
788
829
  /**
789
830
  * Method to retrieve pending txs.
790
831
  * @param limit - The number of items to returns
@@ -817,20 +858,12 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
817
858
  return compactArray(await Promise.all(txHashes.map(txHash => this.getTxByHash(txHash))));
818
859
  }
819
860
 
820
- /**
821
- * Find the indexes of the given leaves in the given tree along with a block metadata pointing to the block in which
822
- * the leaves were inserted.
823
- * @param blockNumber - The block number at which to get the data or 'latest' for latest data.
824
- * @param treeId - The tree to search in.
825
- * @param leafValues - The values to search for.
826
- * @returns The indices of leaves and the block metadata of a block in which the leaves were inserted.
827
- */
828
861
  public async findLeavesIndexes(
829
- blockNumber: BlockParameter,
862
+ block: BlockParameter,
830
863
  treeId: MerkleTreeId,
831
864
  leafValues: Fr[],
832
865
  ): Promise<(DataInBlock<bigint> | undefined)[]> {
833
- const committedDb = await this.#getWorldState(blockNumber);
866
+ const committedDb = await this.#getWorldState(block);
834
867
  const maybeIndices = await committedDb.findLeafIndices(
835
868
  treeId,
836
869
  leafValues.map(x => x.toBuffer()),
@@ -888,39 +921,27 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
888
921
  });
889
922
  }
890
923
 
891
- /**
892
- * Returns a sibling path for the given index in the nullifier tree.
893
- * @param blockNumber - The block number at which to get the data.
894
- * @param leafIndex - The index of the leaf for which the sibling path is required.
895
- * @returns The sibling path for the leaf index.
896
- */
897
924
  public async getNullifierSiblingPath(
898
- blockNumber: BlockParameter,
925
+ block: BlockParameter,
899
926
  leafIndex: bigint,
900
927
  ): Promise<SiblingPath<typeof NULLIFIER_TREE_HEIGHT>> {
901
- const committedDb = await this.#getWorldState(blockNumber);
928
+ const committedDb = await this.#getWorldState(block);
902
929
  return committedDb.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, leafIndex);
903
930
  }
904
931
 
905
- /**
906
- * Returns a sibling path for the given index in the data tree.
907
- * @param blockNumber - The block number at which to get the data.
908
- * @param leafIndex - The index of the leaf for which the sibling path is required.
909
- * @returns The sibling path for the leaf index.
910
- */
911
932
  public async getNoteHashSiblingPath(
912
- blockNumber: BlockParameter,
933
+ block: BlockParameter,
913
934
  leafIndex: bigint,
914
935
  ): Promise<SiblingPath<typeof NOTE_HASH_TREE_HEIGHT>> {
915
- const committedDb = await this.#getWorldState(blockNumber);
936
+ const committedDb = await this.#getWorldState(block);
916
937
  return committedDb.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex);
917
938
  }
918
939
 
919
940
  public async getArchiveMembershipWitness(
920
- blockNumber: BlockParameter,
941
+ block: BlockParameter,
921
942
  archive: Fr,
922
943
  ): Promise<MembershipWitness<typeof ARCHIVE_HEIGHT> | undefined> {
923
- const committedDb = await this.#getWorldState(blockNumber);
944
+ const committedDb = await this.#getWorldState(block);
924
945
  const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.ARCHIVE>(MerkleTreeId.ARCHIVE, [archive]);
925
946
  return pathAndIndex === undefined
926
947
  ? undefined
@@ -928,10 +949,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
928
949
  }
929
950
 
930
951
  public async getNoteHashMembershipWitness(
931
- blockNumber: BlockParameter,
952
+ block: BlockParameter,
932
953
  noteHash: Fr,
933
954
  ): Promise<MembershipWitness<typeof NOTE_HASH_TREE_HEIGHT> | undefined> {
934
- const committedDb = await this.#getWorldState(blockNumber);
955
+ const committedDb = await this.#getWorldState(block);
935
956
  const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.NOTE_HASH_TREE>(
936
957
  MerkleTreeId.NOTE_HASH_TREE,
937
958
  [noteHash],
@@ -941,17 +962,11 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
941
962
  : MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
942
963
  }
943
964
 
944
- /**
945
- * Returns the index and a sibling path for a leaf in the committed l1 to l2 data tree.
946
- * @param blockNumber - The block number at which to get the data.
947
- * @param l1ToL2Message - The l1ToL2Message to get the index / sibling path for.
948
- * @returns A tuple of the index and the sibling path of the L1ToL2Message (undefined if not found).
949
- */
950
965
  public async getL1ToL2MessageMembershipWitness(
951
- blockNumber: BlockParameter,
966
+ block: BlockParameter,
952
967
  l1ToL2Message: Fr,
953
968
  ): Promise<[bigint, SiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>] | undefined> {
954
- const db = await this.#getWorldState(blockNumber);
969
+ const db = await this.#getWorldState(block);
955
970
  const [witness] = await db.findSiblingPaths(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, [l1ToL2Message]);
956
971
  if (!witness) {
957
972
  return undefined;
@@ -979,56 +994,52 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
979
994
  }
980
995
 
981
996
  /**
982
- * Returns all the L2 to L1 messages in a block.
983
- * @param blockNumber - The block number at which to get the data.
984
- * @returns The L2 to L1 messages (undefined if the block number is not found).
997
+ * Returns all the L2 to L1 messages in an epoch.
998
+ * @param epoch - The epoch at which to get the data.
999
+ * @returns The L2 to L1 messages (empty array if the epoch is not found).
985
1000
  */
986
- public async getL2ToL1Messages(blockNumber: BlockParameter): Promise<Fr[][] | undefined> {
987
- const block = await this.blockSource.getBlock(
988
- blockNumber === 'latest' ? await this.getBlockNumber() : (blockNumber as BlockNumber),
1001
+ public async getL2ToL1Messages(epoch: EpochNumber): Promise<Fr[][][][]> {
1002
+ // Assumes `getCheckpointedBlocksForEpoch` returns blocks in ascending order of block number.
1003
+ const checkpointedBlocks = await this.blockSource.getCheckpointedBlocksForEpoch(epoch);
1004
+ const blocksInCheckpoints: L2Block[][] = [];
1005
+ let previousSlotNumber = SlotNumber.ZERO;
1006
+ let checkpointIndex = -1;
1007
+ for (const checkpointedBlock of checkpointedBlocks) {
1008
+ const block = checkpointedBlock.block;
1009
+ const slotNumber = block.header.globalVariables.slotNumber;
1010
+ if (slotNumber !== previousSlotNumber) {
1011
+ checkpointIndex++;
1012
+ blocksInCheckpoints.push([]);
1013
+ previousSlotNumber = slotNumber;
1014
+ }
1015
+ blocksInCheckpoints[checkpointIndex].push(block);
1016
+ }
1017
+ return blocksInCheckpoints.map(blocks =>
1018
+ blocks.map(block => block.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs)),
989
1019
  );
990
- return block?.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs);
991
1020
  }
992
1021
 
993
- /**
994
- * Returns a sibling path for a leaf in the committed blocks tree.
995
- * @param blockNumber - The block number at which to get the data.
996
- * @param leafIndex - Index of the leaf in the tree.
997
- * @returns The sibling path.
998
- */
999
1022
  public async getArchiveSiblingPath(
1000
- blockNumber: BlockParameter,
1023
+ block: BlockParameter,
1001
1024
  leafIndex: bigint,
1002
1025
  ): Promise<SiblingPath<typeof ARCHIVE_HEIGHT>> {
1003
- const committedDb = await this.#getWorldState(blockNumber);
1026
+ const committedDb = await this.#getWorldState(block);
1004
1027
  return committedDb.getSiblingPath(MerkleTreeId.ARCHIVE, leafIndex);
1005
1028
  }
1006
1029
 
1007
- /**
1008
- * Returns a sibling path for a leaf in the committed public data tree.
1009
- * @param blockNumber - The block number at which to get the data.
1010
- * @param leafIndex - Index of the leaf in the tree.
1011
- * @returns The sibling path.
1012
- */
1013
1030
  public async getPublicDataSiblingPath(
1014
- blockNumber: BlockParameter,
1031
+ block: BlockParameter,
1015
1032
  leafIndex: bigint,
1016
1033
  ): Promise<SiblingPath<typeof PUBLIC_DATA_TREE_HEIGHT>> {
1017
- const committedDb = await this.#getWorldState(blockNumber);
1034
+ const committedDb = await this.#getWorldState(block);
1018
1035
  return committedDb.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, leafIndex);
1019
1036
  }
1020
1037
 
1021
- /**
1022
- * Returns a nullifier membership witness for a given nullifier at a given block.
1023
- * @param blockNumber - The block number at which to get the index.
1024
- * @param nullifier - Nullifier we try to find witness for.
1025
- * @returns The nullifier membership witness (if found).
1026
- */
1027
1038
  public async getNullifierMembershipWitness(
1028
- blockNumber: BlockParameter,
1039
+ block: BlockParameter,
1029
1040
  nullifier: Fr,
1030
1041
  ): Promise<NullifierMembershipWitness | undefined> {
1031
- const db = await this.#getWorldState(blockNumber);
1042
+ const db = await this.#getWorldState(block);
1032
1043
  const [witness] = await db.findSiblingPaths(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]);
1033
1044
  if (!witness) {
1034
1045
  return undefined;
@@ -1045,7 +1056,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1045
1056
 
1046
1057
  /**
1047
1058
  * Returns a low nullifier membership witness for a given nullifier at a given block.
1048
- * @param blockNumber - The block number at which to get the index.
1059
+ * @param block - The block parameter (block number, block hash, or 'latest') at which to get the data.
1049
1060
  * @param nullifier - Nullifier we try to find the low nullifier witness for.
1050
1061
  * @returns The low nullifier membership witness (if found).
1051
1062
  * @remarks Low nullifier witness can be used to perform a nullifier non-inclusion proof by leveraging the "linked
@@ -1058,10 +1069,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1058
1069
  * TODO: This is a confusing behavior and we should eventually address that.
1059
1070
  */
1060
1071
  public async getLowNullifierMembershipWitness(
1061
- blockNumber: BlockParameter,
1072
+ block: BlockParameter,
1062
1073
  nullifier: Fr,
1063
1074
  ): Promise<NullifierMembershipWitness | undefined> {
1064
- const committedDb = await this.#getWorldState(blockNumber);
1075
+ const committedDb = await this.#getWorldState(block);
1065
1076
  const findResult = await committedDb.getPreviousValueIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBigInt());
1066
1077
  if (!findResult) {
1067
1078
  return undefined;
@@ -1076,8 +1087,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1076
1087
  return new NullifierMembershipWitness(BigInt(index), preimageData as NullifierLeafPreimage, siblingPath);
1077
1088
  }
1078
1089
 
1079
- async getPublicDataWitness(blockNumber: BlockParameter, leafSlot: Fr): Promise<PublicDataWitness | undefined> {
1080
- const committedDb = await this.#getWorldState(blockNumber);
1090
+ async getPublicDataWitness(block: BlockParameter, leafSlot: Fr): Promise<PublicDataWitness | undefined> {
1091
+ const committedDb = await this.#getWorldState(block);
1081
1092
  const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
1082
1093
  if (!lowLeafResult) {
1083
1094
  return undefined;
@@ -1091,19 +1102,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1091
1102
  }
1092
1103
  }
1093
1104
 
1094
- /**
1095
- * Gets the storage value at the given contract storage slot.
1096
- *
1097
- * @remarks The storage slot here refers to the slot as it is defined in Noir not the index in the merkle tree.
1098
- * Aztec's version of `eth_getStorageAt`.
1099
- *
1100
- * @param contract - Address of the contract to query.
1101
- * @param slot - Slot to query.
1102
- * @param blockNumber - The block number at which to get the data or 'latest'.
1103
- * @returns Storage value at the given contract slot.
1104
- */
1105
- public async getPublicStorageAt(blockNumber: BlockParameter, contract: AztecAddress, slot: Fr): Promise<Fr> {
1106
- const committedDb = await this.#getWorldState(blockNumber);
1105
+ public async getPublicStorageAt(block: BlockParameter, contract: AztecAddress, slot: Fr): Promise<Fr> {
1106
+ const committedDb = await this.#getWorldState(block);
1107
1107
  const leafSlot = await computePublicDataTreeLeafSlot(contract, slot);
1108
1108
 
1109
1109
  const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
@@ -1117,24 +1117,23 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1117
1117
  return preimage.leaf.value;
1118
1118
  }
1119
1119
 
1120
- /**
1121
- * Returns the currently committed block header, or the initial header if no blocks have been produced.
1122
- * @returns The current committed block header.
1123
- */
1124
- public async getBlockHeader(blockNumber: BlockParameter = 'latest'): Promise<BlockHeader | undefined> {
1125
- return blockNumber === BlockNumber.ZERO ||
1126
- (blockNumber === 'latest' && (await this.blockSource.getBlockNumber()) === BlockNumber.ZERO)
1127
- ? this.worldStateSynchronizer.getCommitted().getInitialHeader()
1128
- : this.blockSource.getBlockHeader(blockNumber === 'latest' ? blockNumber : (blockNumber as BlockNumber));
1129
- }
1130
-
1131
- /**
1132
- * Get a block header specified by its hash.
1133
- * @param blockHash - The block hash being requested.
1134
- * @returns The requested block header.
1135
- */
1136
- public async getBlockHeaderByHash(blockHash: Fr): Promise<BlockHeader | undefined> {
1137
- return await this.blockSource.getBlockHeaderByHash(blockHash);
1120
+ public async getBlockHeader(block: BlockParameter = 'latest'): Promise<BlockHeader | undefined> {
1121
+ if (L2BlockHash.isL2BlockHash(block)) {
1122
+ const initialBlockHash = await this.#getInitialHeaderHash();
1123
+ if (block.equals(initialBlockHash)) {
1124
+ // Block source doesn't handle initial header so we need to handle the case separately.
1125
+ return this.worldStateSynchronizer.getCommitted().getInitialHeader();
1126
+ }
1127
+ const blockHashFr = Fr.fromBuffer(block.toBuffer());
1128
+ return this.blockSource.getBlockHeaderByHash(blockHashFr);
1129
+ } else {
1130
+ // Block source doesn't handle initial header so we need to handle the case separately.
1131
+ const blockNumber = block === 'latest' ? await this.getBlockNumber() : (block as BlockNumber);
1132
+ if (blockNumber === BlockNumber.ZERO) {
1133
+ return this.worldStateSynchronizer.getCommitted().getInitialHeader();
1134
+ }
1135
+ return this.blockSource.getBlockHeader(block);
1136
+ }
1138
1137
  }
1139
1138
 
1140
1139
  /**
@@ -1243,7 +1242,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1243
1242
  l1ChainId: this.l1ChainId,
1244
1243
  rollupVersion: this.version,
1245
1244
  setupAllowList: this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions()),
1246
- gasFees: await this.getCurrentBaseFees(),
1245
+ gasFees: await this.getCurrentMinFees(),
1247
1246
  skipFeeEnforcement,
1248
1247
  txsPermitted: !this.config.disableTransactions,
1249
1248
  });
@@ -1315,7 +1314,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1315
1314
  }
1316
1315
 
1317
1316
  // And it has an L2 block hash
1318
- const l2BlockHash = await archiver.getL2Tips().then(tips => tips.latest.hash);
1317
+ const l2BlockHash = await archiver.getL2Tips().then(tips => tips.proposed.hash);
1319
1318
  if (!l2BlockHash) {
1320
1319
  this.metrics.recordSnapshotError();
1321
1320
  throw new Error(`Archiver has no latest L2 block hash downloaded. Cannot start snapshot.`);
@@ -1349,7 +1348,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1349
1348
  throw new Error('Archiver implementation does not support rollbacks.');
1350
1349
  }
1351
1350
 
1352
- const finalizedBlock = await archiver.getL2Tips().then(tips => tips.finalized.number);
1351
+ const finalizedBlock = await archiver.getL2Tips().then(tips => tips.finalized.block.number);
1353
1352
  if (targetBlock < finalizedBlock) {
1354
1353
  if (force) {
1355
1354
  this.log.warn(`Clearing world state database to allow rolling back behind finalized block ${finalizedBlock}`);
@@ -1410,16 +1409,19 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1410
1409
  }
1411
1410
  }
1412
1411
 
1412
+ #getInitialHeaderHash(): Promise<L2BlockHash> {
1413
+ if (!this.initialHeaderHashPromise) {
1414
+ this.initialHeaderHashPromise = this.worldStateSynchronizer.getCommitted().getInitialHeader().hash();
1415
+ }
1416
+ return this.initialHeaderHashPromise;
1417
+ }
1418
+
1413
1419
  /**
1414
1420
  * Returns an instance of MerkleTreeOperations having first ensured the world state is fully synched
1415
- * @param blockNumber - The block number at which to get the data.
1421
+ * @param block - The block parameter (block number, block hash, or 'latest') at which to get the data.
1416
1422
  * @returns An instance of a committed MerkleTreeOperations
1417
1423
  */
1418
- async #getWorldState(blockNumber: BlockParameter) {
1419
- if (typeof blockNumber === 'number' && blockNumber < INITIAL_L2_BLOCK_NUM - 1) {
1420
- throw new Error('Invalid block number to get world state for: ' + blockNumber);
1421
- }
1422
-
1424
+ async #getWorldState(block: BlockParameter) {
1423
1425
  let blockSyncedTo: BlockNumber = BlockNumber.ZERO;
1424
1426
  try {
1425
1427
  // Attempt to sync the world state if necessary
@@ -1428,15 +1430,40 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1428
1430
  this.log.error(`Error getting world state: ${err}`);
1429
1431
  }
1430
1432
 
1431
- // using a snapshot could be less efficient than using the committed db
1432
- if (blockNumber === 'latest' /*|| blockNumber === blockSyncedTo*/) {
1433
- this.log.debug(`Using committed db for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
1433
+ if (block === 'latest') {
1434
+ this.log.debug(`Using committed db for block 'latest', world state synced upto ${blockSyncedTo}`);
1434
1435
  return this.worldStateSynchronizer.getCommitted();
1435
- } else if (blockNumber <= blockSyncedTo) {
1436
+ }
1437
+
1438
+ if (L2BlockHash.isL2BlockHash(block)) {
1439
+ const initialBlockHash = await this.#getInitialHeaderHash();
1440
+ if (block.equals(initialBlockHash)) {
1441
+ // Block source doesn't handle initial header so we need to handle the case separately.
1442
+ return this.worldStateSynchronizer.getSnapshot(BlockNumber.ZERO);
1443
+ }
1444
+
1445
+ const blockHashFr = Fr.fromBuffer(block.toBuffer());
1446
+ const header = await this.blockSource.getBlockHeaderByHash(blockHashFr);
1447
+ if (!header) {
1448
+ throw new Error(
1449
+ `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.`,
1450
+ );
1451
+ }
1452
+ const blockNumber = header.getBlockNumber();
1436
1453
  this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
1437
- return this.worldStateSynchronizer.getSnapshot(blockNumber as BlockNumber);
1438
- } else {
1439
- throw new Error(`Block ${blockNumber} not yet synced`);
1454
+ return this.worldStateSynchronizer.getSnapshot(blockNumber);
1455
+ }
1456
+
1457
+ // Block number provided
1458
+ {
1459
+ const blockNumber = block as BlockNumber;
1460
+
1461
+ if (blockNumber > blockSyncedTo) {
1462
+ throw new Error(`Queried block ${block} not yet synced by the node (node is synced upto ${blockSyncedTo}).`);
1463
+ }
1464
+
1465
+ this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
1466
+ return this.worldStateSynchronizer.getSnapshot(blockNumber);
1440
1467
  }
1441
1468
  }
1442
1469