@aztec/aztec-node 4.0.0-devnet.2-patch.3 → 4.0.0-devnet.3-patch.0

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.
@@ -5,11 +5,11 @@ import { Blob } from '@aztec/blob-lib';
5
5
  import { ARCHIVE_HEIGHT, type L1_TO_L2_MSG_TREE_HEIGHT, type NOTE_HASH_TREE_HEIGHT } from '@aztec/constants';
6
6
  import { EpochCache, type EpochCacheInterface } from '@aztec/epoch-cache';
7
7
  import { createEthereumChain } from '@aztec/ethereum/chain';
8
- import { getPublicClient } from '@aztec/ethereum/client';
8
+ import { getPublicClient, makeL1HttpTransport } from '@aztec/ethereum/client';
9
9
  import { RegistryContract, RollupContract } from '@aztec/ethereum/contracts';
10
10
  import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
11
11
  import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
12
- import { compactArray, pick, unique } from '@aztec/foundation/collection';
12
+ import { chunkBy, compactArray, pick, unique } from '@aztec/foundation/collection';
13
13
  import { Fr } from '@aztec/foundation/curves/bn254';
14
14
  import { EthAddress } from '@aztec/foundation/eth-address';
15
15
  import { BadRequestError } from '@aztec/foundation/json-rpc';
@@ -20,7 +20,13 @@ import { MembershipWitness, SiblingPath } from '@aztec/foundation/trees';
20
20
  import { type KeyStore, KeystoreManager, loadKeystores, mergeKeystores } from '@aztec/node-keystore';
21
21
  import { trySnapshotSync, uploadSnapshot } from '@aztec/node-lib/actions';
22
22
  import { createForwarderL1TxUtilsFromSigners, createL1TxUtilsFromSigners } from '@aztec/node-lib/factories';
23
- import { type P2P, type P2PClientDeps, createP2PClient, getDefaultAllowedSetupFunctions } from '@aztec/p2p';
23
+ import {
24
+ type P2P,
25
+ type P2PClientDeps,
26
+ createP2PClient,
27
+ createTxValidatorForAcceptingTxsOverRPC,
28
+ getDefaultAllowedSetupFunctions,
29
+ } from '@aztec/p2p';
24
30
  import { ProtocolContractAddress } from '@aztec/protocol-contracts';
25
31
  import { type ProverNode, type ProverNodeDeps, createProverNode } from '@aztec/prover-node';
26
32
  import { createKeyStoreForProver } from '@aztec/prover-node/config';
@@ -70,9 +76,9 @@ import {
70
76
  type WorldStateSynchronizer,
71
77
  tryStop,
72
78
  } from '@aztec/stdlib/interfaces/server';
73
- import type { LogFilter, SiloedTag, Tag, TxScopedL2Log } from '@aztec/stdlib/logs';
79
+ import type { DebugLogStore, LogFilter, SiloedTag, Tag, TxScopedL2Log } from '@aztec/stdlib/logs';
80
+ import { InMemoryDebugLogStore, NullDebugLogStore } from '@aztec/stdlib/logs';
74
81
  import { InboxLeaf, type L1ToL2MessageSource } from '@aztec/stdlib/messaging';
75
- import { P2PClientType } from '@aztec/stdlib/p2p';
76
82
  import type { Offense, SlashPayloadRound } from '@aztec/stdlib/slashing';
77
83
  import type { NullifierLeafPreimage, PublicDataTreeLeaf, PublicDataTreeLeafPreimage } from '@aztec/stdlib/trees';
78
84
  import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
@@ -102,13 +108,13 @@ import {
102
108
  FullNodeCheckpointsBuilder,
103
109
  NodeKeystoreAdapter,
104
110
  ValidatorClient,
105
- createBlockProposalHandler,
111
+ createProposalHandler,
106
112
  createValidatorClient,
107
- createValidatorForAcceptingTxs,
108
113
  } from '@aztec/validator-client';
114
+ import type { SlashingProtectionDatabase } from '@aztec/validator-ha-signer/types';
109
115
  import { createWorldStateSynchronizer } from '@aztec/world-state';
110
116
 
111
- import { createPublicClient, fallback, http } from 'viem';
117
+ import { createPublicClient } from 'viem';
112
118
 
113
119
  import { createSentinel } from '../sentinel/factory.js';
114
120
  import { Sentinel } from '../sentinel/sentinel.js';
@@ -151,12 +157,20 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
151
157
  private blobClient?: BlobClientInterface,
152
158
  private validatorClient?: ValidatorClient,
153
159
  private keyStoreManager?: KeystoreManager,
160
+ private debugLogStore: DebugLogStore = new NullDebugLogStore(),
154
161
  ) {
155
162
  this.metrics = new NodeMetrics(telemetry, 'AztecNodeService');
156
163
  this.tracer = telemetry.getTracer('AztecNodeService');
157
164
 
158
165
  this.log.info(`Aztec Node version: ${this.packageVersion}`);
159
166
  this.log.info(`Aztec Node started on chain 0x${l1ChainId.toString(16)}`, config.l1Contracts);
167
+
168
+ // A defensive check that protects us against introducing a bug in the complex `createAndSync` function. We must
169
+ // never have debugLogStore enabled when not in test mode because then we would be accumulating debug logs in
170
+ // memory which could be a DoS vector on the sequencer (since no fees are paid for debug logs).
171
+ if (debugLogStore.isEnabled && config.realProofs) {
172
+ throw new Error('debugLogStore should never be enabled when realProofs are set');
173
+ }
160
174
  }
161
175
 
162
176
  public async getWorldStateSyncStatus(): Promise<WorldStateSyncStatus> {
@@ -180,8 +194,9 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
180
194
  logger?: Logger;
181
195
  publisher?: SequencerPublisher;
182
196
  dateProvider?: DateProvider;
183
- p2pClientDeps?: P2PClientDeps<P2PClientType.Full>;
197
+ p2pClientDeps?: P2PClientDeps;
184
198
  proverNodeDeps?: Partial<ProverNodeDeps>;
199
+ slashingProtectionDb?: SlashingProtectionDatabase;
185
200
  } = {},
186
201
  options: {
187
202
  prefilledPublicData?: PublicDataTreeLeaf[];
@@ -244,7 +259,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
244
259
 
245
260
  const publicClient = createPublicClient({
246
261
  chain: ethereumChain.chainInfo,
247
- transport: fallback(config.l1RpcUrls.map((url: string) => http(url, { batch: false }))),
262
+ transport: makeL1HttpTransport(config.l1RpcUrls, { timeout: config.l1HttpTimeoutMS }),
248
263
  pollingInterval: config.viemPollingIntervalMS,
249
264
  });
250
265
 
@@ -258,10 +273,11 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
258
273
  config.l1Contracts = { ...config.l1Contracts, ...l1ContractsAddresses };
259
274
 
260
275
  const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString());
261
- const [l1GenesisTime, slotDuration, rollupVersionFromRollup] = await Promise.all([
276
+ const [l1GenesisTime, slotDuration, rollupVersionFromRollup, rollupManaLimit] = await Promise.all([
262
277
  rollupContract.getL1GenesisTime(),
263
278
  rollupContract.getSlotDuration(),
264
279
  rollupContract.getVersion(),
280
+ rollupContract.getManaLimit().then(Number),
265
281
  ] as const);
266
282
 
267
283
  config.rollupVersion ??= Number(rollupVersionFromRollup);
@@ -296,14 +312,28 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
296
312
  config.realProofs || config.debugForceTxProofVerification
297
313
  ? await BBCircuitVerifier.new(config)
298
314
  : new TestCircuitVerifier(config.proverTestVerificationDelayMs);
315
+
316
+ let debugLogStore: DebugLogStore;
299
317
  if (!config.realProofs) {
300
318
  log.warn(`Aztec node is accepting fake proofs`);
319
+
320
+ debugLogStore = new InMemoryDebugLogStore();
321
+ log.info(
322
+ 'Aztec node started in test mode (realProofs set to false) hence debug logs from public functions will be collected and served',
323
+ );
324
+ } else {
325
+ debugLogStore = new NullDebugLogStore();
301
326
  }
327
+
302
328
  const proofVerifier = new QueuedIVCVerifier(config, circuitVerifier);
303
329
 
330
+ const proverOnly = config.enableProverNode && config.disableValidator;
331
+ if (proverOnly) {
332
+ log.info('Starting in prover-only mode: skipping validator, sequencer, sentinel, and slasher subsystems');
333
+ }
334
+
304
335
  // create the tx pool and the p2p client, which will need the l2 block source
305
336
  const p2pClient = await createP2PClient(
306
- P2PClientType.Full,
307
337
  config,
308
338
  archiver,
309
339
  proofVerifier,
@@ -315,59 +345,72 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
315
345
  deps.p2pClientDeps,
316
346
  );
317
347
 
318
- // We should really not be modifying the config object
319
- config.txPublicSetupAllowList = config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions());
348
+ // We'll accumulate sentinel watchers here
349
+ const watchers: Watcher[] = [];
320
350
 
321
- // Create FullNodeCheckpointsBuilder for validator and non-validator block proposal handling
351
+ // Create FullNodeCheckpointsBuilder for block proposal handling and tx validation.
352
+ // Override maxTxsPerCheckpoint with the validator-specific limit if set.
322
353
  const validatorCheckpointsBuilder = new FullNodeCheckpointsBuilder(
323
- { ...config, l1GenesisTime, slotDuration: Number(slotDuration) },
354
+ {
355
+ ...config,
356
+ l1GenesisTime,
357
+ slotDuration: Number(slotDuration),
358
+ rollupManaLimit,
359
+ maxTxsPerCheckpoint: config.validateMaxTxsPerCheckpoint,
360
+ },
324
361
  worldStateSynchronizer,
325
362
  archiver,
326
363
  dateProvider,
327
364
  telemetry,
328
365
  );
329
366
 
330
- // We'll accumulate sentinel watchers here
331
- const watchers: Watcher[] = [];
367
+ let validatorClient: ValidatorClient | undefined;
332
368
 
333
- // Create validator client if required
334
- const validatorClient = await createValidatorClient(config, {
335
- checkpointsBuilder: validatorCheckpointsBuilder,
336
- worldState: worldStateSynchronizer,
337
- p2pClient,
338
- telemetry,
339
- dateProvider,
340
- epochCache,
341
- blockSource: archiver,
342
- l1ToL2MessageSource: archiver,
343
- keyStoreManager,
344
- blobClient,
345
- });
369
+ if (!proverOnly) {
370
+ // Create validator client if required
371
+ validatorClient = await createValidatorClient(config, {
372
+ checkpointsBuilder: validatorCheckpointsBuilder,
373
+ worldState: worldStateSynchronizer,
374
+ p2pClient,
375
+ telemetry,
376
+ dateProvider,
377
+ epochCache,
378
+ blockSource: archiver,
379
+ l1ToL2MessageSource: archiver,
380
+ keyStoreManager,
381
+ blobClient,
382
+ slashingProtectionDb: deps.slashingProtectionDb,
383
+ });
346
384
 
347
- // If we have a validator client, register it as a source of offenses for the slasher,
348
- // and have it register callbacks on the p2p client *before* we start it, otherwise messages
349
- // like attestations or auths will fail.
350
- if (validatorClient) {
351
- watchers.push(validatorClient);
352
- if (!options.dontStartSequencer) {
353
- await validatorClient.registerHandlers();
385
+ // If we have a validator client, register it as a source of offenses for the slasher,
386
+ // and have it register callbacks on the p2p client *before* we start it, otherwise messages
387
+ // like attestations or auths will fail.
388
+ if (validatorClient) {
389
+ watchers.push(validatorClient);
390
+ if (!options.dontStartSequencer) {
391
+ await validatorClient.registerHandlers();
392
+ }
354
393
  }
355
394
  }
356
395
 
357
- // If there's no validator client but alwaysReexecuteBlockProposals is enabled,
358
- // create a BlockProposalHandler to reexecute block proposals for monitoring
359
- if (!validatorClient && config.alwaysReexecuteBlockProposals) {
360
- log.info('Setting up block proposal reexecution for monitoring');
361
- createBlockProposalHandler(config, {
396
+ // If there's no validator client, create a ProposalHandler to handle block and checkpoint proposals
397
+ // for monitoring or reexecution. Reexecution (default) allows us to follow the pending chain,
398
+ // while non-reexecution is used for validating the proposals and collecting their txs.
399
+ // Checkpoint proposals are handled if the blob client can upload blobs.
400
+ if (!validatorClient) {
401
+ const reexecute = !!config.alwaysReexecuteBlockProposals;
402
+ log.info(`Setting up proposal handler` + (reexecute ? ' with reexecution of proposals' : ''));
403
+ createProposalHandler(config, {
362
404
  checkpointsBuilder: validatorCheckpointsBuilder,
363
405
  worldState: worldStateSynchronizer,
364
406
  epochCache,
365
407
  blockSource: archiver,
366
408
  l1ToL2MessageSource: archiver,
367
409
  p2pClient,
410
+ blobClient,
368
411
  dateProvider,
369
412
  telemetry,
370
- }).registerForReexecution(p2pClient);
413
+ }).register(p2pClient, reexecute);
371
414
  }
372
415
 
373
416
  // Start world state and wait for it to sync to the archiver.
@@ -376,29 +419,33 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
376
419
  // Start p2p. Note that it depends on world state to be running.
377
420
  await p2pClient.start();
378
421
 
379
- const validatorsSentinel = await createSentinel(epochCache, archiver, p2pClient, config);
380
- if (validatorsSentinel && config.slashInactivityPenalty > 0n) {
381
- watchers.push(validatorsSentinel);
382
- }
383
-
422
+ let validatorsSentinel: Awaited<ReturnType<typeof createSentinel>> | undefined;
384
423
  let epochPruneWatcher: EpochPruneWatcher | undefined;
385
- if (config.slashPrunePenalty > 0n || config.slashDataWithholdingPenalty > 0n) {
386
- epochPruneWatcher = new EpochPruneWatcher(
387
- archiver,
388
- archiver,
389
- epochCache,
390
- p2pClient.getTxProvider(),
391
- validatorCheckpointsBuilder,
392
- config,
393
- );
394
- watchers.push(epochPruneWatcher);
395
- }
396
-
397
- // We assume we want to slash for invalid attestations unless all max penalties are set to 0
398
424
  let attestationsBlockWatcher: AttestationsBlockWatcher | undefined;
399
- if (config.slashProposeInvalidAttestationsPenalty > 0n || config.slashAttestDescendantOfInvalidPenalty > 0n) {
400
- attestationsBlockWatcher = new AttestationsBlockWatcher(archiver, epochCache, config);
401
- watchers.push(attestationsBlockWatcher);
425
+
426
+ if (!proverOnly) {
427
+ validatorsSentinel = await createSentinel(epochCache, archiver, p2pClient, config);
428
+ if (validatorsSentinel && config.slashInactivityPenalty > 0n) {
429
+ watchers.push(validatorsSentinel);
430
+ }
431
+
432
+ if (config.slashPrunePenalty > 0n || config.slashDataWithholdingPenalty > 0n) {
433
+ epochPruneWatcher = new EpochPruneWatcher(
434
+ archiver,
435
+ archiver,
436
+ epochCache,
437
+ p2pClient.getTxProvider(),
438
+ validatorCheckpointsBuilder,
439
+ config,
440
+ );
441
+ watchers.push(epochPruneWatcher);
442
+ }
443
+
444
+ // We assume we want to slash for invalid attestations unless all max penalties are set to 0
445
+ if (config.slashProposeInvalidAttestationsPenalty > 0n || config.slashAttestDescendantOfInvalidPenalty > 0n) {
446
+ attestationsBlockWatcher = new AttestationsBlockWatcher(archiver, epochCache, config);
447
+ watchers.push(attestationsBlockWatcher);
448
+ }
402
449
  }
403
450
 
404
451
  // Start p2p-related services once the archiver has completed sync
@@ -413,6 +460,14 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
413
460
  })
414
461
  .catch(err => log.error('Failed to start p2p services after archiver sync', err));
415
462
 
463
+ const globalVariableBuilder = new GlobalVariableBuilder(dateProvider, publicClient, {
464
+ l1Contracts: config.l1Contracts,
465
+ ethereumSlotDuration: config.ethereumSlotDuration,
466
+ rollupVersion: BigInt(config.rollupVersion),
467
+ l1GenesisTime,
468
+ slotDuration: Number(slotDuration),
469
+ });
470
+
416
471
  // Validator enabled, create/start relevant service
417
472
  let sequencer: SequencerClient | undefined;
418
473
  let slasherClient: SlasherClientInterface | undefined;
@@ -452,11 +507,12 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
452
507
 
453
508
  // Create and start the sequencer client
454
509
  const checkpointsBuilder = new CheckpointsBuilder(
455
- { ...config, l1GenesisTime, slotDuration: Number(slotDuration) },
510
+ { ...config, l1GenesisTime, slotDuration: Number(slotDuration), rollupManaLimit },
456
511
  worldStateSynchronizer,
457
512
  archiver,
458
513
  dateProvider,
459
514
  telemetry,
515
+ debugLogStore,
460
516
  );
461
517
 
462
518
  sequencer = await SequencerClient.new(config, {
@@ -474,6 +530,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
474
530
  dateProvider,
475
531
  blobClient,
476
532
  nodeKeyStore: keyStoreManager!,
533
+ globalVariableBuilder,
477
534
  });
478
535
  }
479
536
 
@@ -507,13 +564,6 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
507
564
  }
508
565
  }
509
566
 
510
- const globalVariableBuilder = new GlobalVariableBuilder({
511
- ...config,
512
- rollupVersion: BigInt(config.rollupVersion),
513
- l1GenesisTime,
514
- slotDuration: Number(slotDuration),
515
- });
516
-
517
567
  const node = new AztecNodeService(
518
568
  config,
519
569
  p2pClient,
@@ -538,6 +588,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
538
588
  blobClient,
539
589
  validatorClient,
540
590
  keyStoreManager,
591
+ debugLogStore,
541
592
  );
542
593
 
543
594
  return node;
@@ -581,7 +632,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
581
632
  }
582
633
 
583
634
  public async getAllowedPublicSetup(): Promise<AllowedElement[]> {
584
- return this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions());
635
+ return [...(await getDefaultAllowedSetupFunctions()), ...(this.config.txPublicSetupAllowListExtend ?? [])];
585
636
  }
586
637
 
587
638
  /**
@@ -676,6 +727,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
676
727
  return (await this.blockSource.getCheckpointedBlocks(from, limit)) ?? [];
677
728
  }
678
729
 
730
+ public getCheckpointsDataForEpoch(epochNumber: EpochNumber) {
731
+ return this.blockSource.getCheckpointsDataForEpoch(epochNumber);
732
+ }
733
+
679
734
  /**
680
735
  * Method to fetch the current min L2 fees.
681
736
  * @returns The current min L2 fees.
@@ -708,6 +763,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
708
763
  return await this.blockSource.getCheckpointedL2BlockNumber();
709
764
  }
710
765
 
766
+ public getCheckpointNumber(): Promise<CheckpointNumber> {
767
+ return this.blockSource.getCheckpointNumber();
768
+ }
769
+
711
770
  /**
712
771
  * Method to fetch the version of the package.
713
772
  * @returns The node package version
@@ -818,8 +877,9 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
818
877
  }
819
878
 
820
879
  await this.p2pClient!.sendTx(tx);
821
- this.metrics.receivedTx(timer.ms(), true);
822
- this.log.info(`Received tx ${txHash}`, { txHash });
880
+ const duration = timer.ms();
881
+ this.metrics.receivedTx(duration, true);
882
+ this.log.info(`Received tx ${txHash} in ${duration}ms`, { txHash });
823
883
  }
824
884
 
825
885
  public async getTxReceipt(txHash: TxHash): Promise<TxReceipt> {
@@ -831,18 +891,22 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
831
891
  // Then get the actual tx from the archiver, which tracks every tx in a mined block.
832
892
  const settledTxReceipt = await this.blockSource.getSettledTxReceipt(txHash);
833
893
 
894
+ let receipt: TxReceipt;
834
895
  if (settledTxReceipt) {
835
- // If the archiver has the receipt then return it.
836
- return settledTxReceipt;
896
+ receipt = settledTxReceipt;
837
897
  } else if (isKnownToPool) {
838
898
  // If the tx is in the pool but not in the archiver, it's pending.
839
899
  // This handles race conditions between archiver and p2p, where the archiver
840
900
  // has pruned the block in which a tx was mined, but p2p has not caught up yet.
841
- return new TxReceipt(txHash, TxStatus.PENDING, undefined, undefined);
901
+ receipt = new TxReceipt(txHash, TxStatus.PENDING, undefined, undefined);
842
902
  } else {
843
903
  // Otherwise, if we don't know the tx, we consider it dropped.
844
- return new TxReceipt(txHash, TxStatus.DROPPED, undefined, 'Tx dropped by P2P node');
904
+ receipt = new TxReceipt(txHash, TxStatus.DROPPED, undefined, 'Tx dropped by P2P node');
845
905
  }
906
+
907
+ this.debugLogStore.decorateReceiptWithLogs(txHash.toString(), receipt);
908
+
909
+ return receipt;
846
910
  }
847
911
 
848
912
  public getTxEffect(txHash: TxHash): Promise<IndexedTxEffect | undefined> {
@@ -913,58 +977,64 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
913
977
  treeId: MerkleTreeId,
914
978
  leafValues: Fr[],
915
979
  ): Promise<(DataInBlock<bigint> | undefined)[]> {
916
- const committedDb = await this.#getWorldState(referenceBlock);
980
+ const committedDb = await this.getWorldState(referenceBlock);
917
981
  const maybeIndices = await committedDb.findLeafIndices(
918
982
  treeId,
919
983
  leafValues.map(x => x.toBuffer()),
920
984
  );
921
- // We filter out undefined values
922
- const indices = maybeIndices.filter(x => x !== undefined) as bigint[];
985
+ // Filter out undefined values to query block numbers only for found leaves
986
+ const definedIndices = maybeIndices.filter(x => x !== undefined);
923
987
 
924
- // Now we find the block numbers for the indices
925
- const blockNumbers = await committedDb.getBlockNumbersForLeafIndices(treeId, indices);
988
+ // Now we find the block numbers for the defined indices
989
+ const blockNumbers = await committedDb.getBlockNumbersForLeafIndices(treeId, definedIndices);
926
990
 
927
- // If any of the block numbers are undefined, we throw an error.
928
- for (let i = 0; i < indices.length; i++) {
929
- if (blockNumbers[i] === undefined) {
930
- throw new Error(`Block number is undefined for leaf index ${indices[i]} in tree ${MerkleTreeId[treeId]}`);
991
+ // Build a map from leaf index to block number
992
+ const indexToBlockNumber = new Map<bigint, BlockNumber>();
993
+ for (let i = 0; i < definedIndices.length; i++) {
994
+ const blockNumber = blockNumbers[i];
995
+ if (blockNumber === undefined) {
996
+ throw new Error(
997
+ `Block number is undefined for leaf index ${definedIndices[i]} in tree ${MerkleTreeId[treeId]}`,
998
+ );
931
999
  }
1000
+ indexToBlockNumber.set(definedIndices[i], blockNumber);
932
1001
  }
933
1002
 
934
1003
  // Get unique block numbers in order to optimize num calls to getLeafValue function.
935
- const uniqueBlockNumbers = [...new Set(blockNumbers.filter(x => x !== undefined))];
1004
+ const uniqueBlockNumbers = [...new Set(indexToBlockNumber.values())];
936
1005
 
937
- // Now we obtain the block hashes from the archive tree by calling await `committedDb.getLeafValue(treeId, index)`
938
- // (note that block number corresponds to the leaf index in the archive tree).
1006
+ // Now we obtain the block hashes from the archive tree (block number = leaf index in archive tree).
939
1007
  const blockHashes = await Promise.all(
940
1008
  uniqueBlockNumbers.map(blockNumber => {
941
1009
  return committedDb.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber));
942
1010
  }),
943
1011
  );
944
1012
 
945
- // If any of the block hashes are undefined, we throw an error.
1013
+ // Build a map from block number to block hash
1014
+ const blockNumberToHash = new Map<BlockNumber, Fr>();
946
1015
  for (let i = 0; i < uniqueBlockNumbers.length; i++) {
947
- if (blockHashes[i] === undefined) {
1016
+ const blockHash = blockHashes[i];
1017
+ if (blockHash === undefined) {
948
1018
  throw new Error(`Block hash is undefined for block number ${uniqueBlockNumbers[i]}`);
949
1019
  }
1020
+ blockNumberToHash.set(uniqueBlockNumbers[i], blockHash);
950
1021
  }
951
1022
 
952
1023
  // Create DataInBlock objects by combining indices, blockNumbers and blockHashes and return them.
953
- return maybeIndices.map((index, i) => {
1024
+ return maybeIndices.map(index => {
954
1025
  if (index === undefined) {
955
1026
  return undefined;
956
1027
  }
957
- const blockNumber = blockNumbers[i];
1028
+ const blockNumber = indexToBlockNumber.get(index);
958
1029
  if (blockNumber === undefined) {
959
- return undefined;
1030
+ throw new Error(`Block number not found for leaf index ${index} in tree ${MerkleTreeId[treeId]}`);
960
1031
  }
961
- const blockHashIndex = uniqueBlockNumbers.indexOf(blockNumber);
962
- const blockHash = blockHashes[blockHashIndex];
963
- if (!blockHash) {
964
- return undefined;
1032
+ const blockHash = blockNumberToHash.get(blockNumber);
1033
+ if (blockHash === undefined) {
1034
+ throw new Error(`Block hash not found for block number ${blockNumber}`);
965
1035
  }
966
1036
  return {
967
- l2BlockNumber: BlockNumber(Number(blockNumber)),
1037
+ l2BlockNumber: blockNumber,
968
1038
  l2BlockHash: new BlockHash(blockHash),
969
1039
  data: index,
970
1040
  };
@@ -975,7 +1045,21 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
975
1045
  referenceBlock: BlockParameter,
976
1046
  blockHash: BlockHash,
977
1047
  ): Promise<MembershipWitness<typeof ARCHIVE_HEIGHT> | undefined> {
978
- const committedDb = await this.#getWorldState(referenceBlock);
1048
+ // Block 0 (the initial block) has an empty archive, so no membership witness can exist.
1049
+ if (referenceBlock === BlockNumber.ZERO) {
1050
+ return undefined;
1051
+ }
1052
+ if (BlockHash.isBlockHash(referenceBlock)) {
1053
+ const initialBlockHash = await this.#getInitialHeaderHash();
1054
+ if (referenceBlock.equals(initialBlockHash)) {
1055
+ return undefined;
1056
+ }
1057
+ }
1058
+ // The Noir circuit checks the archive membership proof against `anchor_block_header.last_archive.root`,
1059
+ // which is the archive tree root BEFORE the anchor block was added (i.e. the state after block N-1).
1060
+ // So we need the world state at block N-1, not block N, to produce a sibling path matching that root.
1061
+ const referenceBlockNumber = await this.resolveBlockNumber(referenceBlock);
1062
+ const committedDb = await this.getWorldState(BlockNumber(referenceBlockNumber - 1));
979
1063
  const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.ARCHIVE>(MerkleTreeId.ARCHIVE, [blockHash]);
980
1064
  return pathAndIndex === undefined
981
1065
  ? undefined
@@ -986,7 +1070,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
986
1070
  referenceBlock: BlockParameter,
987
1071
  noteHash: Fr,
988
1072
  ): Promise<MembershipWitness<typeof NOTE_HASH_TREE_HEIGHT> | undefined> {
989
- const committedDb = await this.#getWorldState(referenceBlock);
1073
+ const committedDb = await this.getWorldState(referenceBlock);
990
1074
  const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.NOTE_HASH_TREE>(
991
1075
  MerkleTreeId.NOTE_HASH_TREE,
992
1076
  [noteHash],
@@ -1000,7 +1084,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1000
1084
  referenceBlock: BlockParameter,
1001
1085
  l1ToL2Message: Fr,
1002
1086
  ): Promise<[bigint, SiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>] | undefined> {
1003
- const db = await this.#getWorldState(referenceBlock);
1087
+ const db = await this.getWorldState(referenceBlock);
1004
1088
  const [witness] = await db.findSiblingPaths(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, [l1ToL2Message]);
1005
1089
  if (!witness) {
1006
1090
  return undefined;
@@ -1010,11 +1094,9 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1010
1094
  return [witness.index, witness.path];
1011
1095
  }
1012
1096
 
1013
- public async getL1ToL2MessageBlock(l1ToL2Message: Fr): Promise<BlockNumber | undefined> {
1097
+ public async getL1ToL2MessageCheckpoint(l1ToL2Message: Fr): Promise<CheckpointNumber | undefined> {
1014
1098
  const messageIndex = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
1015
- return messageIndex
1016
- ? BlockNumber.fromCheckpointNumber(InboxLeaf.checkpointNumberFromIndex(messageIndex))
1017
- : undefined;
1099
+ return messageIndex ? InboxLeaf.checkpointNumberFromIndex(messageIndex) : undefined;
1018
1100
  }
1019
1101
 
1020
1102
  /**
@@ -1035,19 +1117,9 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1035
1117
  public async getL2ToL1Messages(epoch: EpochNumber): Promise<Fr[][][][]> {
1036
1118
  // Assumes `getCheckpointedBlocksForEpoch` returns blocks in ascending order of block number.
1037
1119
  const checkpointedBlocks = await this.blockSource.getCheckpointedBlocksForEpoch(epoch);
1038
- const blocksInCheckpoints: L2Block[][] = [];
1039
- let previousSlotNumber = SlotNumber.ZERO;
1040
- let checkpointIndex = -1;
1041
- for (const checkpointedBlock of checkpointedBlocks) {
1042
- const block = checkpointedBlock.block;
1043
- const slotNumber = block.header.globalVariables.slotNumber;
1044
- if (slotNumber !== previousSlotNumber) {
1045
- checkpointIndex++;
1046
- blocksInCheckpoints.push([]);
1047
- previousSlotNumber = slotNumber;
1048
- }
1049
- blocksInCheckpoints[checkpointIndex].push(block);
1050
- }
1120
+ const blocksInCheckpoints = chunkBy(checkpointedBlocks, cb => cb.block.header.globalVariables.slotNumber).map(
1121
+ group => group.map(cb => cb.block),
1122
+ );
1051
1123
  return blocksInCheckpoints.map(blocks =>
1052
1124
  blocks.map(block => block.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs)),
1053
1125
  );
@@ -1057,7 +1129,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1057
1129
  referenceBlock: BlockParameter,
1058
1130
  nullifier: Fr,
1059
1131
  ): Promise<NullifierMembershipWitness | undefined> {
1060
- const db = await this.#getWorldState(referenceBlock);
1132
+ const db = await this.getWorldState(referenceBlock);
1061
1133
  const [witness] = await db.findSiblingPaths(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]);
1062
1134
  if (!witness) {
1063
1135
  return undefined;
@@ -1072,33 +1144,20 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1072
1144
  return new NullifierMembershipWitness(index, leafPreimage as NullifierLeafPreimage, path);
1073
1145
  }
1074
1146
 
1075
- /**
1076
- * Returns a low nullifier membership witness for a given nullifier at a given block.
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).
1079
- * @param nullifier - Nullifier we try to find the low nullifier witness for.
1080
- * @returns The low nullifier membership witness (if found).
1081
- * @remarks Low nullifier witness can be used to perform a nullifier non-inclusion proof by leveraging the "linked
1082
- * list structure" of leaves and proving that a lower nullifier is pointing to a bigger next value than the nullifier
1083
- * we are trying to prove non-inclusion for.
1084
- *
1085
- * Note: This function returns the membership witness of the nullifier itself and not the low nullifier when
1086
- * the nullifier already exists in the tree. This is because the `getPreviousValueIndex` function returns the
1087
- * index of the nullifier itself when it already exists in the tree.
1088
- * TODO: This is a confusing behavior and we should eventually address that.
1089
- */
1090
1147
  public async getLowNullifierMembershipWitness(
1091
1148
  referenceBlock: BlockParameter,
1092
1149
  nullifier: Fr,
1093
1150
  ): Promise<NullifierMembershipWitness | undefined> {
1094
- const committedDb = await this.#getWorldState(referenceBlock);
1151
+ const committedDb = await this.getWorldState(referenceBlock);
1095
1152
  const findResult = await committedDb.getPreviousValueIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBigInt());
1096
1153
  if (!findResult) {
1097
1154
  return undefined;
1098
1155
  }
1099
1156
  const { index, alreadyPresent } = findResult;
1100
1157
  if (alreadyPresent) {
1101
- this.log.warn(`Nullifier ${nullifier.toBigInt()} already exists in the tree`);
1158
+ throw new Error(
1159
+ `Cannot prove nullifier non-inclusion: nullifier ${nullifier.toBigInt()} already exists in the tree`,
1160
+ );
1102
1161
  }
1103
1162
  const preimageData = (await committedDb.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index))!;
1104
1163
 
@@ -1107,7 +1166,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1107
1166
  }
1108
1167
 
1109
1168
  async getPublicDataWitness(referenceBlock: BlockParameter, leafSlot: Fr): Promise<PublicDataWitness | undefined> {
1110
- const committedDb = await this.#getWorldState(referenceBlock);
1169
+ const committedDb = await this.getWorldState(referenceBlock);
1111
1170
  const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
1112
1171
  if (!lowLeafResult) {
1113
1172
  return undefined;
@@ -1122,7 +1181,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1122
1181
  }
1123
1182
 
1124
1183
  public async getPublicStorageAt(referenceBlock: BlockParameter, contract: AztecAddress, slot: Fr): Promise<Fr> {
1125
- const committedDb = await this.#getWorldState(referenceBlock);
1184
+ const committedDb = await this.getWorldState(referenceBlock);
1126
1185
  const leafSlot = await computePublicDataTreeLeafSlot(contract, slot);
1127
1186
 
1128
1187
  const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
@@ -1236,7 +1295,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1236
1295
  const processor = publicProcessorFactory.create(merkleTreeFork, newGlobalVariables, config);
1237
1296
 
1238
1297
  // REFACTOR: Consider merging ProcessReturnValues into ProcessedTx
1239
- const [processedTxs, failedTxs, _usedTxs, returns] = await processor.process([tx]);
1298
+ const [processedTxs, failedTxs, _usedTxs, returns, debugLogs] = await processor.process([tx]);
1240
1299
  // REFACTOR: Consider returning the error rather than throwing
1241
1300
  if (failedTxs.length) {
1242
1301
  this.log.warn(`Simulated tx ${txHash} fails: ${failedTxs[0].error}`, { txHash });
@@ -1250,6 +1309,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1250
1309
  processedTx.txEffect,
1251
1310
  returns,
1252
1311
  processedTx.gasUsed,
1312
+ debugLogs,
1253
1313
  );
1254
1314
  } finally {
1255
1315
  await merkleTreeFork.close();
@@ -1266,7 +1326,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1266
1326
  // We accept transactions if they are not expired by the next slot (checked based on the ExpirationTimestamp field)
1267
1327
  const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
1268
1328
  const blockNumber = BlockNumber((await this.blockSource.getBlockNumber()) + 1);
1269
- const validator = createValidatorForAcceptingTxs(
1329
+ const l1Constants = await this.blockSource.getL1Constants();
1330
+ const validator = createTxValidatorForAcceptingTxsOverRPC(
1270
1331
  db,
1271
1332
  this.contractDataSource,
1272
1333
  verifier,
@@ -1275,10 +1336,16 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1275
1336
  blockNumber,
1276
1337
  l1ChainId: this.l1ChainId,
1277
1338
  rollupVersion: this.version,
1278
- setupAllowList: this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions()),
1339
+ setupAllowList: [
1340
+ ...(await getDefaultAllowedSetupFunctions()),
1341
+ ...(this.config.txPublicSetupAllowListExtend ?? []),
1342
+ ],
1279
1343
  gasFees: await this.getCurrentMinFees(),
1280
1344
  skipFeeEnforcement,
1281
1345
  txsPermitted: !this.config.disableTransactions,
1346
+ rollupManaLimit: l1Constants.rollupManaLimit,
1347
+ maxBlockL2Gas: this.config.validateMaxL2BlockGas,
1348
+ maxBlockDAGas: this.config.validateMaxDABlockGas,
1282
1349
  },
1283
1350
  this.log.getBindings(),
1284
1351
  );
@@ -1545,7 +1612,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1545
1612
  * @param block - The block parameter (block number, block hash, or 'latest') at which to get the data.
1546
1613
  * @returns An instance of a committed MerkleTreeOperations
1547
1614
  */
1548
- async #getWorldState(block: BlockParameter) {
1615
+ protected async getWorldState(block: BlockParameter) {
1549
1616
  let blockSyncedTo: BlockNumber = BlockNumber.ZERO;
1550
1617
  try {
1551
1618
  // Attempt to sync the world state if necessary
@@ -1559,6 +1626,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1559
1626
  return this.worldStateSynchronizer.getCommitted();
1560
1627
  }
1561
1628
 
1629
+ // Get the block number, either directly from the parameter or by quering the archiver with the block hash
1630
+ let blockNumber: BlockNumber;
1562
1631
  if (BlockHash.isBlockHash(block)) {
1563
1632
  const initialBlockHash = await this.#getInitialHeaderHash();
1564
1633
  if (block.equals(initialBlockHash)) {
@@ -1572,22 +1641,50 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1572
1641
  `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.`,
1573
1642
  );
1574
1643
  }
1575
- const blockNumber = header.getBlockNumber();
1576
- this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
1577
- return this.worldStateSynchronizer.getSnapshot(blockNumber);
1644
+
1645
+ blockNumber = header.getBlockNumber();
1646
+ } else {
1647
+ blockNumber = block as BlockNumber;
1648
+ }
1649
+
1650
+ // Check it's within world state sync range
1651
+ if (blockNumber > blockSyncedTo) {
1652
+ throw new Error(`Queried block ${block} not yet synced by the node (node is synced upto ${blockSyncedTo}).`);
1578
1653
  }
1654
+ this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
1579
1655
 
1580
- // Block number provided
1581
- {
1582
- const blockNumber = block as BlockNumber;
1656
+ const snapshot = this.worldStateSynchronizer.getSnapshot(blockNumber);
1583
1657
 
1584
- if (blockNumber > blockSyncedTo) {
1585
- throw new Error(`Queried block ${block} not yet synced by the node (node is synced upto ${blockSyncedTo}).`);
1658
+ // Double-check world-state synced to the same block hash as was requested
1659
+ if (BlockHash.isBlockHash(block)) {
1660
+ const blockHash = await snapshot.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber));
1661
+ if (!blockHash || !new BlockHash(blockHash).equals(block)) {
1662
+ throw new Error(
1663
+ `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.`,
1664
+ );
1586
1665
  }
1666
+ }
1667
+
1668
+ return snapshot;
1669
+ }
1587
1670
 
1588
- this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
1589
- return this.worldStateSynchronizer.getSnapshot(blockNumber);
1671
+ /** Resolves a block parameter to a block number. */
1672
+ protected async resolveBlockNumber(block: BlockParameter): Promise<BlockNumber> {
1673
+ if (block === 'latest') {
1674
+ return BlockNumber(await this.blockSource.getBlockNumber());
1675
+ }
1676
+ if (BlockHash.isBlockHash(block)) {
1677
+ const initialBlockHash = await this.#getInitialHeaderHash();
1678
+ if (block.equals(initialBlockHash)) {
1679
+ return BlockNumber.ZERO;
1680
+ }
1681
+ const header = await this.blockSource.getBlockHeaderByHash(block);
1682
+ if (!header) {
1683
+ throw new Error(`Block hash ${block.toString()} not found.`);
1684
+ }
1685
+ return header.getBlockNumber();
1590
1686
  }
1687
+ return block as BlockNumber;
1591
1688
  }
1592
1689
 
1593
1690
  /**