@aztec/aztec-node 0.0.1-commit.c80b6263 → 0.0.1-commit.cd76b27

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,20 +1,15 @@
1
1
  import { Archiver, createArchiver } from '@aztec/archiver';
2
2
  import { BBCircuitVerifier, QueuedIVCVerifier, TestCircuitVerifier } from '@aztec/bb-prover';
3
3
  import { type BlobClientInterface, createBlobClientWithFileStores } from '@aztec/blob-client/client';
4
- import {
5
- ARCHIVE_HEIGHT,
6
- type L1_TO_L2_MSG_TREE_HEIGHT,
7
- type NOTE_HASH_TREE_HEIGHT,
8
- type NULLIFIER_TREE_HEIGHT,
9
- type PUBLIC_DATA_TREE_HEIGHT,
10
- } from '@aztec/constants';
4
+ import { Blob } from '@aztec/blob-lib';
5
+ import { ARCHIVE_HEIGHT, type L1_TO_L2_MSG_TREE_HEIGHT, type NOTE_HASH_TREE_HEIGHT } from '@aztec/constants';
11
6
  import { EpochCache, type EpochCacheInterface } from '@aztec/epoch-cache';
12
7
  import { createEthereumChain } from '@aztec/ethereum/chain';
13
8
  import { getPublicClient } from '@aztec/ethereum/client';
14
9
  import { RegistryContract, RollupContract } from '@aztec/ethereum/contracts';
15
10
  import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
16
11
  import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
17
- import { compactArray, pick } from '@aztec/foundation/collection';
12
+ import { compactArray, pick, unique } from '@aztec/foundation/collection';
18
13
  import { Fr } from '@aztec/foundation/curves/bn254';
19
14
  import { EthAddress } from '@aztec/foundation/eth-address';
20
15
  import { BadRequestError } from '@aztec/foundation/json-rpc';
@@ -22,14 +17,13 @@ import { type Logger, createLogger } from '@aztec/foundation/log';
22
17
  import { count } from '@aztec/foundation/string';
23
18
  import { DateProvider, Timer } from '@aztec/foundation/timer';
24
19
  import { MembershipWitness, SiblingPath } from '@aztec/foundation/trees';
25
- import { KeystoreManager, loadKeystores, mergeKeystores } from '@aztec/node-keystore';
20
+ import { type KeyStore, KeystoreManager, loadKeystores, mergeKeystores } from '@aztec/node-keystore';
26
21
  import { trySnapshotSync, uploadSnapshot } from '@aztec/node-lib/actions';
27
- import {
28
- createForwarderL1TxUtilsFromEthSigner,
29
- createL1TxUtilsWithBlobsFromEthSigner,
30
- } from '@aztec/node-lib/factories';
22
+ import { createForwarderL1TxUtilsFromSigners, createL1TxUtilsFromSigners } from '@aztec/node-lib/factories';
31
23
  import { type P2P, type P2PClientDeps, createP2PClient, getDefaultAllowedSetupFunctions } from '@aztec/p2p';
32
24
  import { ProtocolContractAddress } from '@aztec/protocol-contracts';
25
+ import { type ProverNode, type ProverNodeDeps, createProverNode } from '@aztec/prover-node';
26
+ import { createKeyStoreForProver } from '@aztec/prover-node/config';
33
27
  import { GlobalVariableBuilder, SequencerClient, type SequencerPublisher } from '@aztec/sequencer-client';
34
28
  import { PublicProcessorFactory } from '@aztec/simulator/server';
35
29
  import {
@@ -41,7 +35,14 @@ import {
41
35
  } from '@aztec/slasher';
42
36
  import { CollectionLimitsConfig, PublicSimulatorConfig } from '@aztec/stdlib/avm';
43
37
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
44
- import { BlockHash, type BlockParameter, type DataInBlock, L2Block, type L2BlockSource } from '@aztec/stdlib/block';
38
+ import {
39
+ type BlockData,
40
+ BlockHash,
41
+ type BlockParameter,
42
+ type DataInBlock,
43
+ L2Block,
44
+ type L2BlockSource,
45
+ } from '@aztec/stdlib/block';
45
46
  import type { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
46
47
  import type {
47
48
  ContractClassPublic,
@@ -135,6 +136,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
135
136
  protected readonly l1ToL2MessageSource: L1ToL2MessageSource,
136
137
  protected readonly worldStateSynchronizer: WorldStateSynchronizer,
137
138
  protected readonly sequencer: SequencerClient | undefined,
139
+ protected readonly proverNode: ProverNode | undefined,
138
140
  protected readonly slasherClient: SlasherClientInterface | undefined,
139
141
  protected readonly validatorsSentinel: Sentinel | undefined,
140
142
  protected readonly epochPruneWatcher: EpochPruneWatcher | undefined,
@@ -147,6 +149,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
147
149
  private telemetry: TelemetryClient = getTelemetryClient(),
148
150
  private log = createLogger('node'),
149
151
  private blobClient?: BlobClientInterface,
152
+ private validatorClient?: ValidatorClient,
153
+ private keyStoreManager?: KeystoreManager,
150
154
  ) {
151
155
  this.metrics = new NodeMetrics(telemetry, 'AztecNodeService');
152
156
  this.tracer = telemetry.getTracer('AztecNodeService');
@@ -177,10 +181,12 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
177
181
  publisher?: SequencerPublisher;
178
182
  dateProvider?: DateProvider;
179
183
  p2pClientDeps?: P2PClientDeps<P2PClientType.Full>;
184
+ proverNodeDeps?: Partial<ProverNodeDeps>;
180
185
  } = {},
181
186
  options: {
182
187
  prefilledPublicData?: PublicDataTreeLeaf[];
183
188
  dontStartSequencer?: boolean;
189
+ dontStartProverNode?: boolean;
184
190
  } = {},
185
191
  ): Promise<AztecNodeService> {
186
192
  const config = { ...inputConfig }; // Copy the config so we dont mutate the input object
@@ -190,16 +196,29 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
190
196
  const dateProvider = deps.dateProvider ?? new DateProvider();
191
197
  const ethereumChain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
192
198
 
193
- // Build a key store from file if given or from environment otherwise
199
+ // Build a key store from file if given or from environment otherwise.
200
+ // We keep the raw KeyStore available so we can merge with prover keys if enableProverNode is set.
194
201
  let keyStoreManager: KeystoreManager | undefined;
195
202
  const keyStoreProvided = config.keyStoreDirectory !== undefined && config.keyStoreDirectory.length > 0;
196
203
  if (keyStoreProvided) {
197
204
  const keyStores = loadKeystores(config.keyStoreDirectory!);
198
205
  keyStoreManager = new KeystoreManager(mergeKeystores(keyStores));
199
206
  } else {
200
- const keyStore = createKeyStoreForValidator(config);
201
- if (keyStore) {
202
- keyStoreManager = new KeystoreManager(keyStore);
207
+ const rawKeyStores: KeyStore[] = [];
208
+ const validatorKeyStore = createKeyStoreForValidator(config);
209
+ if (validatorKeyStore) {
210
+ rawKeyStores.push(validatorKeyStore);
211
+ }
212
+ if (config.enableProverNode) {
213
+ const proverKeyStore = createKeyStoreForProver(config);
214
+ if (proverKeyStore) {
215
+ rawKeyStores.push(proverKeyStore);
216
+ }
217
+ }
218
+ if (rawKeyStores.length > 0) {
219
+ keyStoreManager = new KeystoreManager(
220
+ rawKeyStores.length === 1 ? rawKeyStores[0] : mergeKeystores(rawKeyStores),
221
+ );
203
222
  }
204
223
  }
205
224
 
@@ -210,10 +229,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
210
229
  if (keyStoreManager === undefined) {
211
230
  throw new Error('Failed to create key store, a requirement for running a validator');
212
231
  }
213
- if (!keyStoreProvided) {
214
- log.warn(
215
- 'KEY STORE CREATED FROM ENVIRONMENT, IT IS RECOMMENDED TO USE A FILE-BASED KEY STORE IN PRODUCTION ENVIRONMENTS',
216
- );
232
+ if (!keyStoreProvided && process.env.NODE_ENV !== 'test') {
233
+ log.warn("Keystore created from env: it's recommended to use a file-based key store for production");
217
234
  }
218
235
  ValidatorClient.validateKeyStoreConfiguration(keyStoreManager, log);
219
236
  }
@@ -255,7 +272,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
255
272
  );
256
273
  }
257
274
 
258
- const blobClient = await createBlobClientWithFileStores(config, createLogger('node:blob-client:client'));
275
+ const blobClient = await createBlobClientWithFileStores(config, log.createChild('blob-client'));
259
276
 
260
277
  // attempt snapshot sync if possible
261
278
  await trySnapshotSync(config, log);
@@ -418,19 +435,19 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
418
435
  );
419
436
  await slasherClient.start();
420
437
 
421
- const l1TxUtils = config.publisherForwarderAddress
422
- ? await createForwarderL1TxUtilsFromEthSigner(
438
+ const l1TxUtils = config.sequencerPublisherForwarderAddress
439
+ ? await createForwarderL1TxUtilsFromSigners(
423
440
  publicClient,
424
441
  keyStoreManager!.createAllValidatorPublisherSigners(),
425
- config.publisherForwarderAddress,
442
+ config.sequencerPublisherForwarderAddress,
426
443
  { ...config, scope: 'sequencer' },
427
- { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider },
444
+ { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() },
428
445
  )
429
- : await createL1TxUtilsWithBlobsFromEthSigner(
446
+ : await createL1TxUtilsFromSigners(
430
447
  publicClient,
431
448
  keyStoreManager!.createAllValidatorPublisherSigners(),
432
449
  { ...config, scope: 'sequencer' },
433
- { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider },
450
+ { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() },
434
451
  );
435
452
 
436
453
  // Create and start the sequencer client
@@ -467,6 +484,29 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
467
484
  log.warn(`Sequencer created but not started`);
468
485
  }
469
486
 
487
+ // Create prover node subsystem if enabled
488
+ let proverNode: ProverNode | undefined;
489
+ if (config.enableProverNode) {
490
+ proverNode = await createProverNode(config, {
491
+ ...deps.proverNodeDeps,
492
+ telemetry,
493
+ dateProvider,
494
+ archiver,
495
+ worldStateSynchronizer,
496
+ p2pClient,
497
+ epochCache,
498
+ blobClient,
499
+ keyStoreManager,
500
+ });
501
+
502
+ if (!options.dontStartProverNode) {
503
+ await proverNode.start();
504
+ log.info(`Prover node subsystem started`);
505
+ } else {
506
+ log.info(`Prover node subsystem created but not started`);
507
+ }
508
+ }
509
+
470
510
  const globalVariableBuilder = new GlobalVariableBuilder({
471
511
  ...config,
472
512
  rollupVersion: BigInt(config.rollupVersion),
@@ -474,7 +514,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
474
514
  slotDuration: Number(slotDuration),
475
515
  });
476
516
 
477
- return new AztecNodeService(
517
+ const node = new AztecNodeService(
478
518
  config,
479
519
  p2pClient,
480
520
  archiver,
@@ -483,6 +523,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
483
523
  archiver,
484
524
  worldStateSynchronizer,
485
525
  sequencer,
526
+ proverNode,
486
527
  slasherClient,
487
528
  validatorsSentinel,
488
529
  epochPruneWatcher,
@@ -495,7 +536,11 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
495
536
  telemetry,
496
537
  log,
497
538
  blobClient,
539
+ validatorClient,
540
+ keyStoreManager,
498
541
  );
542
+
543
+ return node;
499
544
  }
500
545
 
501
546
  /**
@@ -506,6 +551,11 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
506
551
  return this.sequencer;
507
552
  }
508
553
 
554
+ /** Returns the prover node subsystem, if enabled. */
555
+ public getProverNode(): ProverNode | undefined {
556
+ return this.proverNode;
557
+ }
558
+
509
559
  public getBlockSource(): L2BlockSource {
510
560
  return this.blockSource;
511
561
  }
@@ -559,6 +609,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
559
609
  enr,
560
610
  l1ContractAddresses: contractAddresses,
561
611
  protocolContractAddresses: protocolContractAddresses,
612
+ realProofs: !!this.config.realProofs,
562
613
  };
563
614
 
564
615
  return nodeInfo;
@@ -570,8 +621,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
570
621
  * @returns The requested block.
571
622
  */
572
623
  public async getBlock(block: BlockParameter): Promise<L2Block | undefined> {
573
- if (BlockHash.isL2BlockHash(block)) {
574
- return this.getBlockByHash(Fr.fromBuffer(block.toBuffer()));
624
+ if (BlockHash.isBlockHash(block)) {
625
+ return this.getBlockByHash(block);
575
626
  }
576
627
  const blockNumber = block === 'latest' ? await this.getBlockNumber() : (block as BlockNumber);
577
628
  if (blockNumber === BlockNumber.ZERO) {
@@ -585,9 +636,9 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
585
636
  * @param blockHash - The block hash being requested.
586
637
  * @returns The requested block.
587
638
  */
588
- public async getBlockByHash(blockHash: Fr): Promise<L2Block | undefined> {
639
+ public async getBlockByHash(blockHash: BlockHash): Promise<L2Block | undefined> {
589
640
  const initialBlockHash = await this.#getInitialHeaderHash();
590
- if (blockHash.equals(Fr.fromBuffer(initialBlockHash.toBuffer()))) {
641
+ if (blockHash.equals(initialBlockHash)) {
591
642
  return this.buildInitialBlock();
592
643
  }
593
644
  return await this.blockSource.getL2BlockByHash(blockHash);
@@ -697,8 +748,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
697
748
  if (referenceBlock) {
698
749
  const initialBlockHash = await this.#getInitialHeaderHash();
699
750
  if (!referenceBlock.equals(initialBlockHash)) {
700
- const blockHashFr = Fr.fromBuffer(referenceBlock.toBuffer());
701
- const header = await this.blockSource.getBlockHeaderByHash(blockHashFr);
751
+ const header = await this.blockSource.getBlockHeaderByHash(referenceBlock);
702
752
  if (!header) {
703
753
  throw new Error(
704
754
  `Block ${referenceBlock.toString()} not found in the node. This might indicate a reorg has occurred.`,
@@ -718,8 +768,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
718
768
  if (referenceBlock) {
719
769
  const initialBlockHash = await this.#getInitialHeaderHash();
720
770
  if (!referenceBlock.equals(initialBlockHash)) {
721
- const blockHashFr = Fr.fromBuffer(referenceBlock.toBuffer());
722
- const header = await this.blockSource.getBlockHeaderByHash(blockHashFr);
771
+ const header = await this.blockSource.getBlockHeaderByHash(referenceBlock);
723
772
  if (!header) {
724
773
  throw new Error(
725
774
  `Block ${referenceBlock.toString()} not found in the node. This might indicate a reorg has occurred.`,
@@ -810,6 +859,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
810
859
  await tryStop(this.slasherClient);
811
860
  await tryStop(this.proofVerifier);
812
861
  await tryStop(this.sequencer);
862
+ await tryStop(this.proverNode);
813
863
  await tryStop(this.p2pClient);
814
864
  await tryStop(this.worldStateSynchronizer);
815
865
  await tryStop(this.blockSource);
@@ -859,11 +909,11 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
859
909
  }
860
910
 
861
911
  public async findLeavesIndexes(
862
- block: BlockParameter,
912
+ referenceBlock: BlockParameter,
863
913
  treeId: MerkleTreeId,
864
914
  leafValues: Fr[],
865
915
  ): Promise<(DataInBlock<bigint> | undefined)[]> {
866
- const committedDb = await this.#getWorldState(block);
916
+ const committedDb = await this.#getWorldState(referenceBlock);
867
917
  const maybeIndices = await committedDb.findLeafIndices(
868
918
  treeId,
869
919
  leafValues.map(x => x.toBuffer()),
@@ -915,44 +965,28 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
915
965
  }
916
966
  return {
917
967
  l2BlockNumber: BlockNumber(Number(blockNumber)),
918
- l2BlockHash: BlockHash.fromField(blockHash),
968
+ l2BlockHash: new BlockHash(blockHash),
919
969
  data: index,
920
970
  };
921
971
  });
922
972
  }
923
973
 
924
- public async getNullifierSiblingPath(
925
- block: BlockParameter,
926
- leafIndex: bigint,
927
- ): Promise<SiblingPath<typeof NULLIFIER_TREE_HEIGHT>> {
928
- const committedDb = await this.#getWorldState(block);
929
- return committedDb.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, leafIndex);
930
- }
931
-
932
- public async getNoteHashSiblingPath(
933
- block: BlockParameter,
934
- leafIndex: bigint,
935
- ): Promise<SiblingPath<typeof NOTE_HASH_TREE_HEIGHT>> {
936
- const committedDb = await this.#getWorldState(block);
937
- return committedDb.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex);
938
- }
939
-
940
- public async getArchiveMembershipWitness(
941
- block: BlockParameter,
942
- archive: Fr,
974
+ public async getBlockHashMembershipWitness(
975
+ referenceBlock: BlockParameter,
976
+ blockHash: BlockHash,
943
977
  ): Promise<MembershipWitness<typeof ARCHIVE_HEIGHT> | undefined> {
944
- const committedDb = await this.#getWorldState(block);
945
- const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.ARCHIVE>(MerkleTreeId.ARCHIVE, [archive]);
978
+ const committedDb = await this.#getWorldState(referenceBlock);
979
+ const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.ARCHIVE>(MerkleTreeId.ARCHIVE, [blockHash]);
946
980
  return pathAndIndex === undefined
947
981
  ? undefined
948
982
  : MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
949
983
  }
950
984
 
951
985
  public async getNoteHashMembershipWitness(
952
- block: BlockParameter,
986
+ referenceBlock: BlockParameter,
953
987
  noteHash: Fr,
954
988
  ): Promise<MembershipWitness<typeof NOTE_HASH_TREE_HEIGHT> | undefined> {
955
- const committedDb = await this.#getWorldState(block);
989
+ const committedDb = await this.#getWorldState(referenceBlock);
956
990
  const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.NOTE_HASH_TREE>(
957
991
  MerkleTreeId.NOTE_HASH_TREE,
958
992
  [noteHash],
@@ -963,10 +997,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
963
997
  }
964
998
 
965
999
  public async getL1ToL2MessageMembershipWitness(
966
- block: BlockParameter,
1000
+ referenceBlock: BlockParameter,
967
1001
  l1ToL2Message: Fr,
968
1002
  ): Promise<[bigint, SiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>] | undefined> {
969
- const db = await this.#getWorldState(block);
1003
+ const db = await this.#getWorldState(referenceBlock);
970
1004
  const [witness] = await db.findSiblingPaths(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, [l1ToL2Message]);
971
1005
  if (!witness) {
972
1006
  return undefined;
@@ -1019,27 +1053,11 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1019
1053
  );
1020
1054
  }
1021
1055
 
1022
- public async getArchiveSiblingPath(
1023
- block: BlockParameter,
1024
- leafIndex: bigint,
1025
- ): Promise<SiblingPath<typeof ARCHIVE_HEIGHT>> {
1026
- const committedDb = await this.#getWorldState(block);
1027
- return committedDb.getSiblingPath(MerkleTreeId.ARCHIVE, leafIndex);
1028
- }
1029
-
1030
- public async getPublicDataSiblingPath(
1031
- block: BlockParameter,
1032
- leafIndex: bigint,
1033
- ): Promise<SiblingPath<typeof PUBLIC_DATA_TREE_HEIGHT>> {
1034
- const committedDb = await this.#getWorldState(block);
1035
- return committedDb.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, leafIndex);
1036
- }
1037
-
1038
1056
  public async getNullifierMembershipWitness(
1039
- block: BlockParameter,
1057
+ referenceBlock: BlockParameter,
1040
1058
  nullifier: Fr,
1041
1059
  ): Promise<NullifierMembershipWitness | undefined> {
1042
- const db = await this.#getWorldState(block);
1060
+ const db = await this.#getWorldState(referenceBlock);
1043
1061
  const [witness] = await db.findSiblingPaths(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]);
1044
1062
  if (!witness) {
1045
1063
  return undefined;
@@ -1056,7 +1074,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1056
1074
 
1057
1075
  /**
1058
1076
  * Returns a low nullifier membership witness for a given nullifier at a given block.
1059
- * @param block - The block parameter (block number, block hash, or 'latest') at which to get the data.
1077
+ * @param referenceBlock - The block parameter (block number, block hash, or 'latest') at which to get the data
1078
+ * (which contains the root of the nullifier tree in which we are searching for the nullifier).
1060
1079
  * @param nullifier - Nullifier we try to find the low nullifier witness for.
1061
1080
  * @returns The low nullifier membership witness (if found).
1062
1081
  * @remarks Low nullifier witness can be used to perform a nullifier non-inclusion proof by leveraging the "linked
@@ -1069,10 +1088,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1069
1088
  * TODO: This is a confusing behavior and we should eventually address that.
1070
1089
  */
1071
1090
  public async getLowNullifierMembershipWitness(
1072
- block: BlockParameter,
1091
+ referenceBlock: BlockParameter,
1073
1092
  nullifier: Fr,
1074
1093
  ): Promise<NullifierMembershipWitness | undefined> {
1075
- const committedDb = await this.#getWorldState(block);
1094
+ const committedDb = await this.#getWorldState(referenceBlock);
1076
1095
  const findResult = await committedDb.getPreviousValueIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBigInt());
1077
1096
  if (!findResult) {
1078
1097
  return undefined;
@@ -1087,8 +1106,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1087
1106
  return new NullifierMembershipWitness(BigInt(index), preimageData as NullifierLeafPreimage, siblingPath);
1088
1107
  }
1089
1108
 
1090
- async getPublicDataWitness(block: BlockParameter, leafSlot: Fr): Promise<PublicDataWitness | undefined> {
1091
- const committedDb = await this.#getWorldState(block);
1109
+ async getPublicDataWitness(referenceBlock: BlockParameter, leafSlot: Fr): Promise<PublicDataWitness | undefined> {
1110
+ const committedDb = await this.#getWorldState(referenceBlock);
1092
1111
  const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
1093
1112
  if (!lowLeafResult) {
1094
1113
  return undefined;
@@ -1102,8 +1121,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1102
1121
  }
1103
1122
  }
1104
1123
 
1105
- public async getPublicStorageAt(block: BlockParameter, contract: AztecAddress, slot: Fr): Promise<Fr> {
1106
- const committedDb = await this.#getWorldState(block);
1124
+ public async getPublicStorageAt(referenceBlock: BlockParameter, contract: AztecAddress, slot: Fr): Promise<Fr> {
1125
+ const committedDb = await this.#getWorldState(referenceBlock);
1107
1126
  const leafSlot = await computePublicDataTreeLeafSlot(contract, slot);
1108
1127
 
1109
1128
  const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
@@ -1118,14 +1137,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1118
1137
  }
1119
1138
 
1120
1139
  public async getBlockHeader(block: BlockParameter = 'latest'): Promise<BlockHeader | undefined> {
1121
- if (BlockHash.isL2BlockHash(block)) {
1140
+ if (BlockHash.isBlockHash(block)) {
1122
1141
  const initialBlockHash = await this.#getInitialHeaderHash();
1123
1142
  if (block.equals(initialBlockHash)) {
1124
1143
  // Block source doesn't handle initial header so we need to handle the case separately.
1125
1144
  return this.worldStateSynchronizer.getCommitted().getInitialHeader();
1126
1145
  }
1127
- const blockHashFr = Fr.fromBuffer(block.toBuffer());
1128
- return this.blockSource.getBlockHeaderByHash(blockHashFr);
1146
+ return this.blockSource.getBlockHeaderByHash(block);
1129
1147
  } else {
1130
1148
  // Block source doesn't handle initial header so we need to handle the case separately.
1131
1149
  const blockNumber = block === 'latest' ? await this.getBlockNumber() : (block as BlockNumber);
@@ -1145,6 +1163,14 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1145
1163
  return await this.blockSource.getBlockHeaderByArchive(archive);
1146
1164
  }
1147
1165
 
1166
+ public getBlockData(number: BlockNumber): Promise<BlockData | undefined> {
1167
+ return this.blockSource.getBlockData(number);
1168
+ }
1169
+
1170
+ public getBlockDataByArchive(archive: Fr): Promise<BlockData | undefined> {
1171
+ return this.blockSource.getBlockDataByArchive(archive);
1172
+ }
1173
+
1148
1174
  /**
1149
1175
  * Simulates the public part of a transaction with the current state.
1150
1176
  * @param tx - The transaction to simulate.
@@ -1168,7 +1194,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1168
1194
  }
1169
1195
 
1170
1196
  const txHash = tx.getTxHash();
1171
- const blockNumber = BlockNumber((await this.blockSource.getBlockNumber()) + 1);
1197
+ const latestBlockNumber = await this.blockSource.getBlockNumber();
1198
+ const blockNumber = BlockNumber.add(latestBlockNumber, 1);
1172
1199
 
1173
1200
  // If sequencer is not initialized, we just set these values to zero for simulation.
1174
1201
  const coinbase = EthAddress.ZERO;
@@ -1192,6 +1219,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1192
1219
  blockNumber,
1193
1220
  });
1194
1221
 
1222
+ // Ensure world-state has caught up with the latest block we loaded from the archiver
1223
+ await this.worldStateSynchronizer.syncImmediate(latestBlockNumber);
1195
1224
  const merkleTreeFork = await this.worldStateSynchronizer.fork();
1196
1225
  try {
1197
1226
  const config = PublicSimulatorConfig.from({
@@ -1234,7 +1263,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1234
1263
  const db = this.worldStateSynchronizer.getCommitted();
1235
1264
  const verifier = isSimulation ? undefined : this.proofVerifier;
1236
1265
 
1237
- // We accept transactions if they are not expired by the next slot (checked based on the IncludeByTimestamp field)
1266
+ // We accept transactions if they are not expired by the next slot (checked based on the ExpirationTimestamp field)
1238
1267
  const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
1239
1268
  const blockNumber = BlockNumber((await this.blockSource.getBlockNumber()) + 1);
1240
1269
  const validator = createValidatorForAcceptingTxs(
@@ -1416,6 +1445,94 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1416
1445
  }
1417
1446
  }
1418
1447
 
1448
+ public async reloadKeystore(): Promise<void> {
1449
+ if (!this.config.keyStoreDirectory?.length) {
1450
+ throw new BadRequestError(
1451
+ 'Cannot reload keystore: node is not using a file-based keystore. ' +
1452
+ 'Set KEY_STORE_DIRECTORY to use file-based keystores.',
1453
+ );
1454
+ }
1455
+ if (!this.validatorClient) {
1456
+ throw new BadRequestError('Cannot reload keystore: validator is not enabled.');
1457
+ }
1458
+
1459
+ this.log.info('Reloading keystore from disk');
1460
+
1461
+ // Re-read and validate keystore files
1462
+ const keyStores = loadKeystores(this.config.keyStoreDirectory);
1463
+ const newManager = new KeystoreManager(mergeKeystores(keyStores));
1464
+ await newManager.validateSigners();
1465
+ ValidatorClient.validateKeyStoreConfiguration(newManager, this.log);
1466
+
1467
+ // Validate that every validator's publisher keys overlap with the L1 signers
1468
+ // that were initialized at startup. Publishers cannot be hot-reloaded, so a
1469
+ // validator with a publisher key that doesn't match any existing L1 signer
1470
+ // would silently fail on every proposer slot.
1471
+ if (this.keyStoreManager && this.sequencer) {
1472
+ const oldAdapter = NodeKeystoreAdapter.fromKeyStoreManager(this.keyStoreManager);
1473
+ const availablePublishers = new Set(
1474
+ oldAdapter
1475
+ .getAttesterAddresses()
1476
+ .flatMap(a => oldAdapter.getPublisherAddresses(a).map(p => p.toString().toLowerCase())),
1477
+ );
1478
+
1479
+ const newAdapter = NodeKeystoreAdapter.fromKeyStoreManager(newManager);
1480
+ for (const attester of newAdapter.getAttesterAddresses()) {
1481
+ const pubs = newAdapter.getPublisherAddresses(attester);
1482
+ if (pubs.length > 0 && !pubs.some(p => availablePublishers.has(p.toString().toLowerCase()))) {
1483
+ throw new BadRequestError(
1484
+ `Cannot reload keystore: validator ${attester} has publisher keys ` +
1485
+ `[${pubs.map(p => p.toString()).join(', ')}] but none match the L1 signers initialized at startup ` +
1486
+ `[${[...availablePublishers].join(', ')}]. Publishers cannot be hot-reloaded — ` +
1487
+ `use an existing publisher key or restart the node.`,
1488
+ );
1489
+ }
1490
+ }
1491
+ }
1492
+
1493
+ // Build adapters for old and new keystores to compute diff
1494
+ const newAdapter = NodeKeystoreAdapter.fromKeyStoreManager(newManager);
1495
+ const newAddresses = newAdapter.getAttesterAddresses();
1496
+ const oldAddresses = this.keyStoreManager
1497
+ ? NodeKeystoreAdapter.fromKeyStoreManager(this.keyStoreManager).getAttesterAddresses()
1498
+ : [];
1499
+
1500
+ const oldSet = new Set(oldAddresses.map(a => a.toString()));
1501
+ const newSet = new Set(newAddresses.map(a => a.toString()));
1502
+ const added = newAddresses.filter(a => !oldSet.has(a.toString()));
1503
+ const removed = oldAddresses.filter(a => !newSet.has(a.toString()));
1504
+
1505
+ if (added.length > 0) {
1506
+ this.log.info(`Keystore reload: adding attester keys: ${added.map(a => a.toString()).join(', ')}`);
1507
+ }
1508
+ if (removed.length > 0) {
1509
+ this.log.info(`Keystore reload: removing attester keys: ${removed.map(a => a.toString()).join(', ')}`);
1510
+ }
1511
+ if (added.length === 0 && removed.length === 0) {
1512
+ this.log.info('Keystore reload: attester keys unchanged');
1513
+ }
1514
+
1515
+ // Update the validator client (coinbase, feeRecipient, attester keys)
1516
+ this.validatorClient.reloadKeystore(newManager);
1517
+
1518
+ // Update the publisher factory's keystore so newly-added validators
1519
+ // can be matched to existing publisher keys when proposing blocks.
1520
+ if (this.sequencer) {
1521
+ this.sequencer.updatePublisherNodeKeyStore(newAdapter);
1522
+ }
1523
+
1524
+ // Update slasher's "don't-slash-self" list with new validator addresses
1525
+ if (this.slasherClient && !this.config.slashSelfAllowed) {
1526
+ const slashValidatorsNever = unique(
1527
+ [...(this.config.slashValidatorsNever ?? []), ...newAddresses].map(a => a.toString()),
1528
+ ).map(EthAddress.fromString);
1529
+ this.slasherClient.updateConfig({ slashValidatorsNever });
1530
+ }
1531
+
1532
+ this.keyStoreManager = newManager;
1533
+ this.log.info('Keystore reloaded: coinbase, feeRecipient, and attester keys updated');
1534
+ }
1535
+
1419
1536
  #getInitialHeaderHash(): Promise<BlockHash> {
1420
1537
  if (!this.initialHeaderHashPromise) {
1421
1538
  this.initialHeaderHashPromise = this.worldStateSynchronizer.getCommitted().getInitialHeader().hash();
@@ -1442,15 +1559,14 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1442
1559
  return this.worldStateSynchronizer.getCommitted();
1443
1560
  }
1444
1561
 
1445
- if (BlockHash.isL2BlockHash(block)) {
1562
+ if (BlockHash.isBlockHash(block)) {
1446
1563
  const initialBlockHash = await this.#getInitialHeaderHash();
1447
1564
  if (block.equals(initialBlockHash)) {
1448
1565
  // Block source doesn't handle initial header so we need to handle the case separately.
1449
1566
  return this.worldStateSynchronizer.getSnapshot(BlockNumber.ZERO);
1450
1567
  }
1451
1568
 
1452
- const blockHashFr = Fr.fromBuffer(block.toBuffer());
1453
- const header = await this.blockSource.getBlockHeaderByHash(blockHashFr);
1569
+ const header = await this.blockSource.getBlockHeaderByHash(block);
1454
1570
  if (!header) {
1455
1571
  throw new Error(
1456
1572
  `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.`,