@aztec/aztec-node 0.0.1-commit.e588bc7e5 → 0.0.1-commit.e5a3663dd

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.
@@ -16,6 +16,7 @@ import { Fr } from '@aztec/foundation/curves/bn254';
16
16
  import { EthAddress } from '@aztec/foundation/eth-address';
17
17
  import { BadRequestError } from '@aztec/foundation/json-rpc';
18
18
  import { type Logger, createLogger } from '@aztec/foundation/log';
19
+ import { retryUntil } from '@aztec/foundation/retry';
19
20
  import { count } from '@aztec/foundation/string';
20
21
  import { DateProvider, Timer } from '@aztec/foundation/timer';
21
22
  import { MembershipWitness, SiblingPath } from '@aztec/foundation/trees';
@@ -32,7 +33,12 @@ import {
32
33
  import { ProtocolContractAddress } from '@aztec/protocol-contracts';
33
34
  import { type ProverNode, type ProverNodeDeps, createProverNode } from '@aztec/prover-node';
34
35
  import { createKeyStoreForProver } from '@aztec/prover-node/config';
35
- import { GlobalVariableBuilder, SequencerClient, type SequencerPublisher } from '@aztec/sequencer-client';
36
+ import {
37
+ FeeProviderImpl,
38
+ GlobalVariableBuilder,
39
+ SequencerClient,
40
+ type SequencerPublisher,
41
+ } from '@aztec/sequencer-client';
36
42
  import { PublicProcessorFactory } from '@aztec/simulator/server';
37
43
  import {
38
44
  AttestationsBlockWatcher,
@@ -44,14 +50,13 @@ import {
44
50
  import { CollectionLimitsConfig, PublicSimulatorConfig } from '@aztec/stdlib/avm';
45
51
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
46
52
  import {
47
- type BlockData,
48
53
  BlockHash,
49
54
  type BlockParameter,
50
55
  type DataInBlock,
51
56
  L2Block,
52
57
  type L2BlockSource,
58
+ inspectBlockParameter,
53
59
  } from '@aztec/stdlib/block';
54
- import type { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
55
60
  import type {
56
61
  ContractClassPublic,
57
62
  ContractDataSource,
@@ -59,16 +64,24 @@ import type {
59
64
  NodeInfo,
60
65
  ProtocolContractAddresses,
61
66
  } from '@aztec/stdlib/contract';
62
- import { GasFees } from '@aztec/stdlib/gas';
67
+ import { GasFees, type ManaUsageEstimate } from '@aztec/stdlib/gas';
63
68
  import { computePublicDataTreeLeafSlot } from '@aztec/stdlib/hash';
64
- import {
65
- type AztecNode,
66
- type AztecNodeAdmin,
67
- type AztecNodeAdminConfig,
68
- AztecNodeAdminConfigSchema,
69
- type GetContractClassLogsResponse,
70
- type GetPublicLogsResponse,
69
+ import type {
70
+ AztecNode,
71
+ AztecNodeAdmin,
72
+ AztecNodeAdminConfig,
73
+ AztecNodeDebug,
74
+ BlockIncludeOptions,
75
+ BlockResponse,
76
+ ChainTip,
77
+ ChainTips,
78
+ CheckpointIncludeOptions,
79
+ CheckpointParameter,
80
+ CheckpointResponse,
81
+ GetContractClassLogsResponse,
82
+ GetPublicLogsResponse,
71
83
  } from '@aztec/stdlib/interfaces/client';
84
+ import { AztecNodeAdminConfigSchema } from '@aztec/stdlib/interfaces/client';
72
85
  import {
73
86
  type AllowedElement,
74
87
  type ClientProtocolCircuitVerifier,
@@ -81,11 +94,12 @@ import {
81
94
  import type { DebugLogStore, LogFilter, SiloedTag, Tag, TxScopedL2Log } from '@aztec/stdlib/logs';
82
95
  import { InMemoryDebugLogStore, NullDebugLogStore } from '@aztec/stdlib/logs';
83
96
  import { InboxLeaf, type L1ToL2MessageSource } from '@aztec/stdlib/messaging';
84
- import type { Offense, SlashPayloadRound } from '@aztec/stdlib/slashing';
85
- import type { NullifierLeafPreimage, PublicDataTreeLeaf, PublicDataTreeLeafPreimage } from '@aztec/stdlib/trees';
97
+ import type { Offense } from '@aztec/stdlib/slashing';
98
+ import type { NullifierLeafPreimage, PublicDataTreeLeafPreimage } from '@aztec/stdlib/trees';
86
99
  import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
87
100
  import {
88
101
  type BlockHeader,
102
+ type FeeProvider,
89
103
  type GlobalVariableBuilder as GlobalVariableBuilderInterface,
90
104
  type IndexedTxEffect,
91
105
  PublicSimulationOutput,
@@ -97,6 +111,7 @@ import {
97
111
  } from '@aztec/stdlib/tx';
98
112
  import { getPackageVersion } from '@aztec/stdlib/update-checker';
99
113
  import type { SingleValidatorStats, ValidatorsStats } from '@aztec/stdlib/validators';
114
+ import type { GenesisData } from '@aztec/stdlib/world-state';
100
115
  import {
101
116
  Attributes,
102
117
  type TelemetryClient,
@@ -120,13 +135,19 @@ import { createPublicClient } from 'viem';
120
135
 
121
136
  import { createSentinel } from '../sentinel/factory.js';
122
137
  import { Sentinel } from '../sentinel/sentinel.js';
138
+ import {
139
+ blockResponseFromBlockData,
140
+ blockResponseFromL2Block,
141
+ checkpointResponseFromCheckpointData,
142
+ checkpointResponseFromPublishedCheckpoint,
143
+ } from './block_response_helpers.js';
123
144
  import { type AztecNodeConfig, createKeyStoreForValidator } from './config.js';
124
145
  import { NodeMetrics } from './node_metrics.js';
125
146
 
126
147
  /**
127
148
  * The aztec node.
128
149
  */
129
- export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
150
+ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDebug, Traceable {
130
151
  private metrics: NodeMetrics;
131
152
  private initialHeaderHashPromise: Promise<BlockHash> | undefined = undefined;
132
153
 
@@ -148,9 +169,11 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
148
169
  protected readonly slasherClient: SlasherClientInterface | undefined,
149
170
  protected readonly validatorsSentinel: Sentinel | undefined,
150
171
  protected readonly epochPruneWatcher: EpochPruneWatcher | undefined,
172
+ protected readonly attestationsBlockWatcher: AttestationsBlockWatcher | undefined,
151
173
  protected readonly l1ChainId: number,
152
174
  protected readonly version: number,
153
175
  protected readonly globalVariableBuilder: GlobalVariableBuilderInterface,
176
+ protected readonly feeProvider: FeeProvider,
154
177
  protected readonly epochCache: EpochCacheInterface,
155
178
  protected readonly packageVersion: string,
156
179
  private peerProofVerifier: ClientProtocolCircuitVerifier,
@@ -186,10 +209,281 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
186
209
  return status.syncSummary;
187
210
  }
188
211
 
212
+ public async getChainTips(): Promise<ChainTips> {
213
+ const { proposed, checkpointed, proven, finalized } = await this.blockSource.getL2Tips();
214
+ return { proposed, checkpointed, proven, finalized };
215
+ }
216
+
189
217
  public getL2Tips() {
190
218
  return this.blockSource.getL2Tips();
191
219
  }
192
220
 
221
+ public async getBlockHeader(number: BlockNumber | 'latest'): Promise<BlockHeader | undefined> {
222
+ const resolvedNumber = number === 'latest' ? await this.blockSource.getBlockNumber() : number;
223
+ if (resolvedNumber === BlockNumber.ZERO) {
224
+ return this.worldStateSynchronizer.getCommitted().getInitialHeader();
225
+ }
226
+ return this.blockSource.getBlockHeader(resolvedNumber);
227
+ }
228
+
229
+ public async getCheckpointedBlocks(from: BlockNumber, limit: number) {
230
+ return (await this.blockSource.getCheckpointedBlocks(from, limit)) ?? [];
231
+ }
232
+
233
+ public getCheckpointsDataForEpoch(epoch: EpochNumber) {
234
+ return this.blockSource.getCheckpointsDataForEpoch(epoch);
235
+ }
236
+
237
+ public getBlockNumber(tip?: ChainTip): Promise<BlockNumber> {
238
+ switch (tip) {
239
+ case undefined:
240
+ case 'proposed':
241
+ return this.blockSource.getBlockNumber();
242
+ case 'checkpointed':
243
+ return this.blockSource.getCheckpointedL2BlockNumber();
244
+ case 'proven':
245
+ return this.blockSource.getProvenBlockNumber();
246
+ case 'finalized':
247
+ return this.blockSource.getFinalizedL2BlockNumber();
248
+ }
249
+ }
250
+
251
+ public async getCheckpointNumber(tip?: ChainTip): Promise<CheckpointNumber> {
252
+ switch (tip) {
253
+ case undefined:
254
+ case 'proposed':
255
+ case 'checkpointed':
256
+ return await this.blockSource.getCheckpointNumber();
257
+ case 'proven':
258
+ case 'finalized': {
259
+ const tips = await this.blockSource.getL2Tips();
260
+ return tip === 'proven' ? tips.proven.checkpoint.number : tips.finalized.checkpoint.number;
261
+ }
262
+ }
263
+ }
264
+
265
+ private isChainTip(value: unknown): value is ChainTip {
266
+ return value === 'proposed' || value === 'checkpointed' || value === 'proven' || value === 'finalized';
267
+ }
268
+
269
+ private async resolveBlockParameter(
270
+ param: BlockParameter,
271
+ ): Promise<{ number?: BlockNumber; hash?: BlockHash; archive?: Fr }> {
272
+ if (BlockHash.isBlockHash(param)) {
273
+ return { hash: param };
274
+ }
275
+ if (typeof param === 'number') {
276
+ return { number: param as BlockNumber };
277
+ }
278
+ if (param === 'latest') {
279
+ return { number: await this.blockSource.getBlockNumber() };
280
+ }
281
+ if (this.isChainTip(param)) {
282
+ return { number: await this.getBlockNumber(param) };
283
+ }
284
+ if (typeof param === 'object' && param !== null) {
285
+ if ('number' in param) {
286
+ return { number: param.number };
287
+ }
288
+ if ('hash' in param) {
289
+ return { hash: param.hash };
290
+ }
291
+ if ('archive' in param) {
292
+ return { archive: param.archive };
293
+ }
294
+ }
295
+ throw new BadRequestError(`Invalid BlockParameter: ${JSON.stringify(param)}`);
296
+ }
297
+
298
+ private async resolveCheckpointParameter(
299
+ param: CheckpointParameter,
300
+ ): Promise<{ number?: CheckpointNumber; slot?: SlotNumber }> {
301
+ if (typeof param === 'number') {
302
+ return { number: param as CheckpointNumber };
303
+ }
304
+ if (param === 'latest') {
305
+ return { number: await this.blockSource.getCheckpointNumber() };
306
+ }
307
+ if (this.isChainTip(param)) {
308
+ return { number: await this.getCheckpointNumber(param) };
309
+ }
310
+ if (typeof param === 'object' && param !== null) {
311
+ if ('number' in param) {
312
+ return { number: param.number };
313
+ }
314
+ if ('slot' in param) {
315
+ return { slot: param.slot };
316
+ }
317
+ }
318
+ throw new BadRequestError(`Invalid CheckpointParameter: ${JSON.stringify(param)}`);
319
+ }
320
+
321
+ public async getBlock<Opts extends BlockIncludeOptions = {}>(
322
+ param: BlockParameter,
323
+ options: Opts = {} as Opts,
324
+ ): Promise<BlockResponse<Opts> | undefined> {
325
+ const resolved = await this.resolveBlockParameter(param);
326
+ const wantTxs = !!options.includeTransactions;
327
+ const wantContext = !!options.includeL1PublishInfo || !!options.includeAttestations;
328
+
329
+ if (resolved.hash !== undefined) {
330
+ const initial = await this.#getInitialHeaderHash();
331
+ if (resolved.hash.equals(initial)) {
332
+ return (await this.buildGenesisBlockResponse(options)) as BlockResponse<Opts>;
333
+ }
334
+ if (wantTxs) {
335
+ const block = await this.blockSource.getL2BlockByHash(resolved.hash);
336
+ if (!block) {
337
+ return undefined;
338
+ }
339
+ const ctx = wantContext ? await this.blockSource.getBlockDataWithCheckpointContext(block.number) : undefined;
340
+ return (await blockResponseFromL2Block(block, options, ctx)) as BlockResponse<Opts>;
341
+ }
342
+ const data = await this.blockSource.getBlockHeaderByHash(resolved.hash);
343
+ if (!data) {
344
+ return undefined;
345
+ }
346
+ const blockNumber = data.globalVariables.blockNumber;
347
+ const ctx = wantContext ? await this.blockSource.getBlockDataWithCheckpointContext(blockNumber) : undefined;
348
+ if (ctx) {
349
+ return blockResponseFromBlockData(ctx.data, blockNumber, options, ctx) as BlockResponse<Opts>;
350
+ }
351
+ const blockData = await this.blockSource.getBlockData(blockNumber);
352
+ if (!blockData) {
353
+ return undefined;
354
+ }
355
+ return blockResponseFromBlockData(blockData, blockNumber, options) as BlockResponse<Opts>;
356
+ }
357
+
358
+ if (resolved.archive !== undefined) {
359
+ if (wantTxs) {
360
+ const block = await this.blockSource.getL2BlockByArchive(resolved.archive);
361
+ if (!block) {
362
+ return undefined;
363
+ }
364
+ const ctx = wantContext ? await this.blockSource.getBlockDataWithCheckpointContext(block.number) : undefined;
365
+ return (await blockResponseFromL2Block(block, options, ctx)) as BlockResponse<Opts>;
366
+ }
367
+ const data = await this.blockSource.getBlockDataByArchive(resolved.archive);
368
+ if (!data) {
369
+ return undefined;
370
+ }
371
+ const blockNumber = data.header.globalVariables.blockNumber;
372
+ const ctx = wantContext ? await this.blockSource.getBlockDataWithCheckpointContext(blockNumber) : undefined;
373
+ return blockResponseFromBlockData(data, blockNumber, options, ctx) as BlockResponse<Opts>;
374
+ }
375
+
376
+ const blockNumber = resolved.number!;
377
+ if (blockNumber === BlockNumber.ZERO) {
378
+ return (await this.buildGenesisBlockResponse(options)) as BlockResponse<Opts>;
379
+ }
380
+ if (wantTxs) {
381
+ const block = await this.blockSource.getL2Block(blockNumber);
382
+ if (!block) {
383
+ return undefined;
384
+ }
385
+ const ctx = wantContext ? await this.blockSource.getBlockDataWithCheckpointContext(blockNumber) : undefined;
386
+ return (await blockResponseFromL2Block(block, options, ctx)) as BlockResponse<Opts>;
387
+ }
388
+ const ctx = await this.blockSource.getBlockDataWithCheckpointContext(blockNumber);
389
+ if (!ctx) {
390
+ return undefined;
391
+ }
392
+ return blockResponseFromBlockData(ctx.data, blockNumber, options, ctx) as BlockResponse<Opts>;
393
+ }
394
+
395
+ public async getBlocks<Opts extends BlockIncludeOptions = {}>(
396
+ from: BlockNumber,
397
+ limit: number,
398
+ options: Opts = {} as Opts,
399
+ ): Promise<BlockResponse<Opts>[]> {
400
+ const wantTxs = !!options.includeTransactions;
401
+ const wantContext = !!options.includeL1PublishInfo || !!options.includeAttestations;
402
+ if (wantTxs) {
403
+ const blocks = await this.blockSource.getBlocks(from, limit);
404
+ return (await Promise.all(
405
+ blocks.map(async block => {
406
+ const ctx = wantContext ? await this.blockSource.getBlockDataWithCheckpointContext(block.number) : undefined;
407
+ return blockResponseFromL2Block(block, options, ctx);
408
+ }),
409
+ )) as BlockResponse<Opts>[];
410
+ }
411
+ const results: BlockResponse<Opts>[] = [];
412
+ for (let i = 0; i < limit; i++) {
413
+ const blockNumber = BlockNumber(from + i);
414
+ const ctx = await this.blockSource.getBlockDataWithCheckpointContext(blockNumber);
415
+ if (!ctx) {
416
+ break;
417
+ }
418
+ results.push(blockResponseFromBlockData(ctx.data, blockNumber, options, ctx) as BlockResponse<Opts>);
419
+ }
420
+ return results;
421
+ }
422
+
423
+ public async getCheckpoint<Opts extends CheckpointIncludeOptions = {}>(
424
+ param: CheckpointParameter,
425
+ options: Opts = {} as Opts,
426
+ ): Promise<CheckpointResponse<Opts> | undefined> {
427
+ const resolved = await this.resolveCheckpointParameter(param);
428
+ let checkpointNumber = resolved.number;
429
+ if (checkpointNumber === undefined && resolved.slot !== undefined) {
430
+ checkpointNumber = await this.blockSource.getCheckpointNumberBySlot(resolved.slot);
431
+ }
432
+ if (checkpointNumber === undefined) {
433
+ return undefined;
434
+ }
435
+ if (options.includeBlocks) {
436
+ const [checkpoint] = await this.blockSource.getCheckpoints(checkpointNumber, 1);
437
+ if (!checkpoint) {
438
+ return undefined;
439
+ }
440
+ return (await checkpointResponseFromPublishedCheckpoint(checkpoint, options)) as CheckpointResponse<Opts>;
441
+ }
442
+ const data = await this.blockSource.getCheckpointData(checkpointNumber);
443
+ if (!data) {
444
+ return undefined;
445
+ }
446
+ return checkpointResponseFromCheckpointData(data, options) as CheckpointResponse<Opts>;
447
+ }
448
+
449
+ public async getCheckpoints<Opts extends CheckpointIncludeOptions = {}>(
450
+ from: CheckpointNumber,
451
+ limit: number,
452
+ options: Opts = {} as Opts,
453
+ ): Promise<CheckpointResponse<Opts>[]> {
454
+ if (options.includeBlocks) {
455
+ const checkpoints = await this.blockSource.getCheckpoints(from, limit);
456
+ return (await Promise.all(
457
+ checkpoints.map(cp => checkpointResponseFromPublishedCheckpoint(cp, options)),
458
+ )) as CheckpointResponse<Opts>[];
459
+ }
460
+ const datas = await this.blockSource.getCheckpointDataRange(from, limit);
461
+ return datas.map(d => checkpointResponseFromCheckpointData(d, options)) as CheckpointResponse<Opts>[];
462
+ }
463
+
464
+ private async buildGenesisBlockResponse(options: BlockIncludeOptions): Promise<BlockResponse> {
465
+ const initial = this.worldStateSynchronizer.getCommitted().getInitialHeader();
466
+ const empty = L2Block.empty(initial);
467
+ const response: BlockResponse = {
468
+ header: empty.header,
469
+ archive: empty.archive,
470
+ hash: await this.#getInitialHeaderHash(),
471
+ checkpointNumber: empty.checkpointNumber,
472
+ indexWithinCheckpoint: empty.indexWithinCheckpoint,
473
+ number: empty.number,
474
+ };
475
+ if (options.includeTransactions) {
476
+ (response as BlockResponse).body = empty.body;
477
+ }
478
+ if (options.includeL1PublishInfo) {
479
+ (response as BlockResponse).l1 = { published: false };
480
+ }
481
+ if (options.includeAttestations) {
482
+ (response as BlockResponse).attestations = [];
483
+ }
484
+ return response;
485
+ }
486
+
193
487
  /**
194
488
  * initializes the Aztec Node, wait for component to sync.
195
489
  * @param config - The configuration to be used by the aztec node.
@@ -207,7 +501,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
207
501
  slashingProtectionDb?: SlashingProtectionDatabase;
208
502
  } = {},
209
503
  options: {
210
- prefilledPublicData?: PublicDataTreeLeaf[];
504
+ genesis?: GenesisData;
211
505
  dontStartSequencer?: boolean;
212
506
  dontStartProverNode?: boolean;
213
507
  } = {},
@@ -303,328 +597,354 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
303
597
 
304
598
  const epochCache = await EpochCache.create(config.l1Contracts.rollupAddress, config, { dateProvider });
305
599
 
306
- const archiver = await createArchiver(
307
- config,
308
- { blobClient, epochCache, telemetry, dateProvider },
309
- { blockUntilSync: !config.skipArchiverInitialSync },
310
- );
311
-
312
- // now create the merkle trees and the world state synchronizer
313
- const worldStateSynchronizer = await createWorldStateSynchronizer(
314
- config,
315
- archiver,
316
- options.prefilledPublicData,
317
- telemetry,
318
- );
319
- const useRealVerifiers = config.realProofs || config.debugForceTxProofVerification;
320
- let peerProofVerifier: ClientProtocolCircuitVerifier;
321
- let rpcProofVerifier: ClientProtocolCircuitVerifier;
322
- if (useRealVerifiers) {
323
- peerProofVerifier = await BatchChonkVerifier.new(config, config.bbChonkVerifyMaxBatch, 'peer');
324
- const rpcVerifier = await BBCircuitVerifier.new(config);
325
- rpcProofVerifier = new QueuedIVCVerifier(rpcVerifier, config.numConcurrentIVCVerifiers);
326
- } else {
327
- peerProofVerifier = new TestCircuitVerifier(config.proverTestVerificationDelayMs);
328
- rpcProofVerifier = new TestCircuitVerifier(config.proverTestVerificationDelayMs);
329
- }
330
-
331
- let debugLogStore: DebugLogStore;
332
- if (!config.realProofs) {
333
- log.warn(`Aztec node is accepting fake proofs`);
334
-
335
- debugLogStore = new InMemoryDebugLogStore();
336
- log.info(
337
- 'Aztec node started in test mode (realProofs set to false) hence debug logs from public functions will be collected and served',
600
+ // Track started resources so we can clean up on partial failure during node creation.
601
+ const started: { stop?(): Promise<void> | void }[] = [];
602
+ try {
603
+ const archiver = await createArchiver(
604
+ config,
605
+ { blobClient, epochCache, telemetry, dateProvider },
606
+ { blockUntilSync: !config.skipArchiverInitialSync },
338
607
  );
339
- } else {
340
- debugLogStore = new NullDebugLogStore();
341
- }
342
-
343
- const proverOnly = config.enableProverNode && config.disableValidator;
344
- if (proverOnly) {
345
- log.info('Starting in prover-only mode: skipping validator, sequencer, sentinel, and slasher subsystems');
346
- }
608
+ started.push(archiver);
609
+
610
+ // now create the merkle trees and the world state synchronizer
611
+ const worldStateSynchronizer = await createWorldStateSynchronizer(config, archiver, options.genesis, telemetry);
612
+ started.push(worldStateSynchronizer);
613
+ const useRealVerifiers = config.realProofs || config.debugForceTxProofVerification;
614
+ let peerProofVerifier: ClientProtocolCircuitVerifier;
615
+ let rpcProofVerifier: ClientProtocolCircuitVerifier;
616
+ if (useRealVerifiers) {
617
+ peerProofVerifier = await BatchChonkVerifier.new(config, config.bbChonkVerifyMaxBatch, 'peer');
618
+ const rpcVerifier = await BBCircuitVerifier.new(config);
619
+ rpcProofVerifier = new QueuedIVCVerifier(rpcVerifier, config.numConcurrentIVCVerifiers);
620
+ } else {
621
+ peerProofVerifier = new TestCircuitVerifier(config.proverTestVerificationDelayMs);
622
+ rpcProofVerifier = new TestCircuitVerifier(config.proverTestVerificationDelayMs);
623
+ }
624
+ started.push(peerProofVerifier, rpcProofVerifier);
347
625
 
348
- // create the tx pool and the p2p client, which will need the l2 block source
349
- const p2pClient = await createP2PClient(
350
- config,
351
- archiver,
352
- peerProofVerifier,
353
- worldStateSynchronizer,
354
- epochCache,
355
- packageVersion,
356
- dateProvider,
357
- telemetry,
358
- deps.p2pClientDeps,
359
- );
626
+ let debugLogStore: DebugLogStore;
627
+ if (!config.realProofs) {
628
+ log.warn(`Aztec node is accepting fake proofs`);
360
629
 
361
- // We'll accumulate sentinel watchers here
362
- const watchers: Watcher[] = [];
630
+ debugLogStore = new InMemoryDebugLogStore();
631
+ log.info(
632
+ 'Aztec node started in test mode (realProofs set to false) hence debug logs from public functions will be collected and served',
633
+ );
634
+ } else {
635
+ debugLogStore = new NullDebugLogStore();
636
+ }
363
637
 
364
- // Create FullNodeCheckpointsBuilder for block proposal handling and tx validation.
365
- // Override maxTxsPerCheckpoint with the validator-specific limit if set.
366
- const validatorCheckpointsBuilder = new FullNodeCheckpointsBuilder(
367
- {
368
- ...config,
638
+ const globalVariableBuilderConfig = {
639
+ l1Contracts: config.l1Contracts,
640
+ ethereumSlotDuration: config.ethereumSlotDuration,
641
+ rollupVersion: BigInt(config.rollupVersion),
369
642
  l1GenesisTime,
370
643
  slotDuration: Number(slotDuration),
371
- rollupManaLimit,
372
- maxTxsPerCheckpoint: config.validateMaxTxsPerCheckpoint,
373
- },
374
- worldStateSynchronizer,
375
- archiver,
376
- dateProvider,
377
- telemetry,
378
- );
379
-
380
- let validatorClient: ValidatorClient | undefined;
381
-
382
- if (!proverOnly) {
383
- // Create validator client if required
384
- validatorClient = await createValidatorClient(config, {
385
- checkpointsBuilder: validatorCheckpointsBuilder,
386
- worldState: worldStateSynchronizer,
387
- p2pClient,
388
- telemetry,
389
- dateProvider,
390
- epochCache,
391
- blockSource: archiver,
392
- l1ToL2MessageSource: archiver,
393
- keyStoreManager,
394
- blobClient,
395
- slashingProtectionDb: deps.slashingProtectionDb,
396
- });
397
-
398
- // If we have a validator client, register it as a source of offenses for the slasher,
399
- // and have it register callbacks on the p2p client *before* we start it, otherwise messages
400
- // like attestations or auths will fail.
401
- if (validatorClient) {
402
- watchers.push(validatorClient);
644
+ };
403
645
 
404
- const vc = validatorClient;
405
- const getValidatorAddresses = () => vc.getValidatorAddresses().map(a => a.toString());
406
- validatorClient.getProposalHandler().register(p2pClient, true, archiver, getValidatorAddresses);
646
+ const globalVariableBuilder = new GlobalVariableBuilder(dateProvider, publicClient, globalVariableBuilderConfig);
647
+ const feeProvider = new FeeProviderImpl(dateProvider, publicClient, globalVariableBuilderConfig);
407
648
 
408
- if (!options.dontStartSequencer) {
409
- await validatorClient.registerHandlers();
410
- }
649
+ const proverOnly = config.enableProverNode && config.disableValidator;
650
+ if (proverOnly) {
651
+ log.info('Starting in prover-only mode: skipping validator, sequencer, sentinel, and slasher subsystems');
411
652
  }
412
- }
413
653
 
414
- // If there's no validator client, create a ProposalHandler to handle block and checkpoint proposals
415
- // for monitoring or reexecution. Reexecution (default) allows us to follow the pending chain,
416
- // while non-reexecution is used for validating the proposals and collecting their txs.
417
- // Checkpoint proposals rebuild blobs if the blob client can upload blobs.
418
- if (!validatorClient) {
419
- const reexecute = !!config.alwaysReexecuteBlockProposals;
420
- log.info(`Setting up proposal handler` + (reexecute ? ' with reexecution of proposals' : ''));
421
- createProposalHandler(config, {
422
- checkpointsBuilder: validatorCheckpointsBuilder,
423
- worldState: worldStateSynchronizer,
654
+ // create the tx pool and the p2p client, which will need the l2 block source
655
+ const p2pClient = await createP2PClient(
656
+ config,
657
+ archiver,
658
+ peerProofVerifier,
659
+ worldStateSynchronizer,
424
660
  epochCache,
425
- blockSource: archiver,
426
- l1ToL2MessageSource: archiver,
427
- p2pClient,
428
- blobClient,
661
+ feeProvider,
662
+ packageVersion,
429
663
  dateProvider,
430
664
  telemetry,
431
- }).register(p2pClient, reexecute, archiver);
432
- }
433
-
434
- // Start world state and wait for it to sync to the archiver.
435
- await worldStateSynchronizer.start();
436
-
437
- // Start p2p. Note that it depends on world state to be running.
438
- await p2pClient.start();
665
+ deps.p2pClientDeps,
666
+ );
667
+ started.push(p2pClient);
668
+
669
+ // We'll accumulate sentinel watchers here
670
+ const watchers: Watcher[] = [];
671
+
672
+ // Create FullNodeCheckpointsBuilder for block proposal handling and tx validation.
673
+ // Override maxTxsPerCheckpoint with the validator-specific limit if set.
674
+ const validatorCheckpointsBuilder = new FullNodeCheckpointsBuilder(
675
+ {
676
+ ...config,
677
+ l1GenesisTime,
678
+ slotDuration: Number(slotDuration),
679
+ rollupManaLimit,
680
+ maxTxsPerCheckpoint: config.validateMaxTxsPerCheckpoint,
681
+ },
682
+ worldStateSynchronizer,
683
+ archiver,
684
+ dateProvider,
685
+ telemetry,
686
+ );
439
687
 
440
- let validatorsSentinel: Awaited<ReturnType<typeof createSentinel>> | undefined;
441
- let epochPruneWatcher: EpochPruneWatcher | undefined;
442
- let attestationsBlockWatcher: AttestationsBlockWatcher | undefined;
688
+ let validatorClient: ValidatorClient | undefined;
443
689
 
444
- if (!proverOnly) {
445
- validatorsSentinel = await createSentinel(epochCache, archiver, p2pClient, config);
446
- if (validatorsSentinel && config.slashInactivityPenalty > 0n) {
447
- watchers.push(validatorsSentinel);
690
+ if (!config.disableValidator) {
691
+ // Create validator client if required
692
+ validatorClient = await createValidatorClient(config, {
693
+ checkpointsBuilder: validatorCheckpointsBuilder,
694
+ worldState: worldStateSynchronizer,
695
+ p2pClient,
696
+ telemetry,
697
+ dateProvider,
698
+ epochCache,
699
+ blockSource: archiver,
700
+ l1ToL2MessageSource: archiver,
701
+ keyStoreManager,
702
+ blobClient,
703
+ slashingProtectionDb: deps.slashingProtectionDb,
704
+ });
705
+
706
+ // If we have a validator client, register it as a source of offenses for the slasher,
707
+ // and have it register callbacks on the p2p client *before* we start it, otherwise messages
708
+ // like attestations or auths will fail.
709
+ if (validatorClient) {
710
+ watchers.push(validatorClient);
711
+
712
+ const vc = validatorClient;
713
+ const getValidatorAddresses = () => vc.getValidatorAddresses().map(a => a.toString());
714
+ validatorClient.getProposalHandler().register(p2pClient, true, archiver, getValidatorAddresses);
715
+
716
+ if (!options.dontStartSequencer) {
717
+ await validatorClient.registerHandlers();
718
+ }
719
+ }
448
720
  }
449
721
 
450
- if (config.slashPrunePenalty > 0n || config.slashDataWithholdingPenalty > 0n) {
451
- epochPruneWatcher = new EpochPruneWatcher(
452
- archiver,
453
- archiver,
722
+ // If there's no validator client, create a ProposalHandler to handle block and checkpoint proposals
723
+ // for monitoring or reexecution. Reexecution (default) allows us to follow the pending chain,
724
+ // while non-reexecution is used for validating the proposals and collecting their txs.
725
+ // Checkpoint proposals rebuild blobs if the blob client can upload blobs.
726
+ if (!validatorClient) {
727
+ const reexecute = !!config.alwaysReexecuteBlockProposals;
728
+ log.info(`Setting up proposal handler` + (reexecute ? ' with reexecution of proposals' : ''));
729
+ createProposalHandler(config, {
730
+ checkpointsBuilder: validatorCheckpointsBuilder,
731
+ worldState: worldStateSynchronizer,
454
732
  epochCache,
455
- p2pClient.getTxProvider(),
456
- validatorCheckpointsBuilder,
457
- config,
458
- );
459
- watchers.push(epochPruneWatcher);
733
+ blockSource: archiver,
734
+ l1ToL2MessageSource: archiver,
735
+ p2pClient,
736
+ blobClient,
737
+ dateProvider,
738
+ telemetry,
739
+ }).register(p2pClient, reexecute, archiver);
460
740
  }
461
741
 
462
- // We assume we want to slash for invalid attestations unless all max penalties are set to 0
463
- if (config.slashProposeInvalidAttestationsPenalty > 0n || config.slashAttestDescendantOfInvalidPenalty > 0n) {
464
- attestationsBlockWatcher = new AttestationsBlockWatcher(archiver, epochCache, config);
465
- watchers.push(attestationsBlockWatcher);
466
- }
467
- }
742
+ // Start world state and wait for it to sync to the archiver.
743
+ await worldStateSynchronizer.start();
468
744
 
469
- // Start p2p-related services once the archiver has completed sync
470
- void archiver
471
- .waitForInitialSync()
472
- .then(async () => {
473
- await p2pClient.start();
474
- await validatorsSentinel?.start();
475
- await epochPruneWatcher?.start();
476
- await attestationsBlockWatcher?.start();
477
- log.info(`All p2p services started`);
478
- })
479
- .catch(err => log.error('Failed to start p2p services after archiver sync', err));
480
-
481
- const globalVariableBuilder = new GlobalVariableBuilder(dateProvider, publicClient, {
482
- l1Contracts: config.l1Contracts,
483
- ethereumSlotDuration: config.ethereumSlotDuration,
484
- rollupVersion: BigInt(config.rollupVersion),
485
- l1GenesisTime,
486
- slotDuration: Number(slotDuration),
487
- });
745
+ // Start p2p. Note that it depends on world state to be running.
746
+ await p2pClient.start();
488
747
 
489
- // Validator enabled, create/start relevant service
490
- let sequencer: SequencerClient | undefined;
491
- let slasherClient: SlasherClientInterface | undefined;
492
- if (!config.disableValidator && validatorClient) {
493
- // We create a slasher only if we have a sequencer, since all slashing actions go through the sequencer publisher
494
- // as they are executed when the node is selected as proposer.
495
- const validatorAddresses = keyStoreManager
496
- ? NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager).getAddresses()
497
- : [];
498
-
499
- slasherClient = await createSlasher(
500
- config,
501
- config.l1Contracts,
502
- getPublicClient(config),
503
- watchers,
504
- dateProvider,
505
- epochCache,
506
- validatorAddresses,
507
- undefined, // logger
508
- );
509
- await slasherClient.start();
748
+ let validatorsSentinel: Awaited<ReturnType<typeof createSentinel>> | undefined;
749
+ let epochPruneWatcher: EpochPruneWatcher | undefined;
750
+ let attestationsBlockWatcher: AttestationsBlockWatcher | undefined;
510
751
 
511
- const l1TxUtils = config.sequencerPublisherForwarderAddress
512
- ? await createForwarderL1TxUtilsFromSigners(
513
- publicClient,
514
- keyStoreManager!.createAllValidatorPublisherSigners(),
515
- config.sequencerPublisherForwarderAddress,
516
- { ...config, scope: 'sequencer' },
517
- { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() },
518
- )
519
- : await createL1TxUtilsFromSigners(
752
+ if (!proverOnly) {
753
+ validatorsSentinel = await createSentinel(epochCache, archiver, p2pClient, config);
754
+ if (validatorsSentinel && config.slashInactivityPenalty > 0n) {
755
+ watchers.push(validatorsSentinel);
756
+ }
757
+
758
+ if (config.slashPrunePenalty > 0n || config.slashDataWithholdingPenalty > 0n) {
759
+ epochPruneWatcher = new EpochPruneWatcher(
760
+ archiver,
761
+ archiver,
762
+ epochCache,
763
+ p2pClient.getTxProvider(),
764
+ validatorCheckpointsBuilder,
765
+ config,
766
+ );
767
+ watchers.push(epochPruneWatcher);
768
+ }
769
+
770
+ // We assume we want to slash for invalid attestations unless all max penalties are set to 0
771
+ if (config.slashProposeInvalidAttestationsPenalty > 0n || config.slashAttestDescendantOfInvalidPenalty > 0n) {
772
+ attestationsBlockWatcher = new AttestationsBlockWatcher(archiver, epochCache, config);
773
+ watchers.push(attestationsBlockWatcher);
774
+ }
775
+ }
776
+
777
+ // Start p2p-related services once the archiver has completed sync
778
+ void archiver
779
+ .waitForInitialSync()
780
+ .then(async () => {
781
+ if (validatorsSentinel) {
782
+ await validatorsSentinel.start();
783
+ started.push(validatorsSentinel);
784
+ }
785
+ if (epochPruneWatcher) {
786
+ await epochPruneWatcher.start();
787
+ started.push(epochPruneWatcher);
788
+ }
789
+ if (attestationsBlockWatcher) {
790
+ await attestationsBlockWatcher.start();
791
+ started.push(attestationsBlockWatcher);
792
+ }
793
+ log.info(`All p2p services started`);
794
+ })
795
+ .catch(err => log.error('Failed to start p2p services after archiver sync', err));
796
+
797
+ // Validator enabled, create/start relevant service
798
+ let sequencer: SequencerClient | undefined;
799
+ let slasherClient: SlasherClientInterface | undefined;
800
+ if (!config.disableValidator && validatorClient) {
801
+ // We create a slasher only if we have a sequencer, since all slashing actions go through the sequencer publisher
802
+ // as they are executed when the node is selected as proposer.
803
+ const validatorAddresses = keyStoreManager
804
+ ? NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager).getAddresses()
805
+ : [];
806
+
807
+ slasherClient = await createSlasher(
808
+ config,
809
+ config.l1Contracts,
810
+ getPublicClient(config),
811
+ watchers,
812
+ dateProvider,
813
+ epochCache,
814
+ validatorAddresses,
815
+ undefined, // logger
816
+ );
817
+ await slasherClient.start();
818
+ started.push(slasherClient);
819
+
820
+ const l1TxUtils = config.sequencerPublisherForwarderAddress
821
+ ? await createForwarderL1TxUtilsFromSigners(
822
+ publicClient,
823
+ keyStoreManager!.createAllValidatorPublisherSigners(),
824
+ config.sequencerPublisherForwarderAddress,
825
+ { ...config, scope: 'sequencer' },
826
+ { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() },
827
+ )
828
+ : await createL1TxUtilsFromSigners(
829
+ publicClient,
830
+ keyStoreManager!.createAllValidatorPublisherSigners(),
831
+ { ...config, scope: 'sequencer' },
832
+ { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() },
833
+ );
834
+
835
+ // Create a funder L1TxUtils from the keystore funding account (if configured)
836
+ const fundingSigner = keyStoreManager?.createFundingSigner();
837
+ let funderL1TxUtils: L1TxUtils | undefined;
838
+ if (fundingSigner) {
839
+ const [funder] = await createL1TxUtilsFromSigners(
520
840
  publicClient,
521
- keyStoreManager!.createAllValidatorPublisherSigners(),
841
+ [fundingSigner],
522
842
  { ...config, scope: 'sequencer' },
523
- { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() },
843
+ { telemetry, logger: log.createChild('l1-tx-utils:funder'), dateProvider },
524
844
  );
845
+ funderL1TxUtils = funder;
846
+ }
525
847
 
526
- // Create a funder L1TxUtils from the keystore funding account (if configured)
527
- const fundingSigner = keyStoreManager?.createFundingSigner();
528
- let funderL1TxUtils: L1TxUtils | undefined;
529
- if (fundingSigner) {
530
- const [funder] = await createL1TxUtilsFromSigners(
531
- publicClient,
532
- [fundingSigner],
533
- { ...config, scope: 'sequencer' },
534
- { telemetry, logger: log.createChild('l1-tx-utils:funder'), dateProvider },
848
+ // Create and start the sequencer client
849
+ const checkpointsBuilder = new CheckpointsBuilder(
850
+ { ...config, l1GenesisTime, slotDuration: Number(slotDuration), rollupManaLimit },
851
+ worldStateSynchronizer,
852
+ archiver,
853
+ dateProvider,
854
+ telemetry,
855
+ debugLogStore,
535
856
  );
536
- funderL1TxUtils = funder;
857
+
858
+ sequencer = await SequencerClient.new(config, {
859
+ ...deps,
860
+ epochCache,
861
+ l1TxUtils,
862
+ funderL1TxUtils,
863
+ validatorClient,
864
+ p2pClient,
865
+ worldStateSynchronizer,
866
+ slasherClient,
867
+ checkpointsBuilder,
868
+ l2BlockSource: archiver,
869
+ l1ToL2MessageSource: archiver,
870
+ telemetry,
871
+ dateProvider,
872
+ blobClient,
873
+ nodeKeyStore: keyStoreManager!,
874
+ globalVariableBuilder,
875
+ });
537
876
  }
538
877
 
539
- // Create and start the sequencer client
540
- const checkpointsBuilder = new CheckpointsBuilder(
541
- { ...config, l1GenesisTime, slotDuration: Number(slotDuration), rollupManaLimit },
542
- worldStateSynchronizer,
543
- archiver,
544
- dateProvider,
545
- telemetry,
546
- debugLogStore,
547
- );
878
+ if (!options.dontStartSequencer && sequencer) {
879
+ await sequencer.start();
880
+ started.push(sequencer);
881
+ log.verbose(`Sequencer started`);
882
+ } else if (sequencer) {
883
+ log.warn(`Sequencer created but not started`);
884
+ }
548
885
 
549
- sequencer = await SequencerClient.new(config, {
550
- ...deps,
551
- epochCache,
552
- l1TxUtils,
553
- funderL1TxUtils,
554
- validatorClient,
886
+ // Create prover node subsystem if enabled
887
+ let proverNode: ProverNode | undefined;
888
+ if (config.enableProverNode) {
889
+ proverNode = await createProverNode(config, {
890
+ ...deps.proverNodeDeps,
891
+ telemetry,
892
+ dateProvider,
893
+ archiver,
894
+ worldStateSynchronizer,
895
+ p2pClient,
896
+ epochCache,
897
+ blobClient,
898
+ keyStoreManager,
899
+ });
900
+
901
+ if (!options.dontStartProverNode) {
902
+ await proverNode.start();
903
+ started.push(proverNode);
904
+ log.info(`Prover node subsystem started`);
905
+ } else {
906
+ log.info(`Prover node subsystem created but not started`);
907
+ }
908
+ }
909
+
910
+ const node = new AztecNodeService(
911
+ config,
555
912
  p2pClient,
913
+ archiver,
914
+ archiver,
915
+ archiver,
916
+ archiver,
556
917
  worldStateSynchronizer,
918
+ sequencer,
919
+ proverNode,
557
920
  slasherClient,
558
- checkpointsBuilder,
559
- l2BlockSource: archiver,
560
- l1ToL2MessageSource: archiver,
561
- telemetry,
562
- dateProvider,
563
- blobClient,
564
- nodeKeyStore: keyStoreManager!,
921
+ validatorsSentinel,
922
+ epochPruneWatcher,
923
+ attestationsBlockWatcher,
924
+ ethereumChain.chainInfo.id,
925
+ config.rollupVersion,
565
926
  globalVariableBuilder,
566
- });
567
- }
568
-
569
- if (!options.dontStartSequencer && sequencer) {
570
- await sequencer.start();
571
- log.verbose(`Sequencer started`);
572
- } else if (sequencer) {
573
- log.warn(`Sequencer created but not started`);
574
- }
575
-
576
- // Create prover node subsystem if enabled
577
- let proverNode: ProverNode | undefined;
578
- if (config.enableProverNode) {
579
- proverNode = await createProverNode(config, {
580
- ...deps.proverNodeDeps,
581
- telemetry,
582
- dateProvider,
583
- archiver,
584
- worldStateSynchronizer,
585
- p2pClient,
927
+ feeProvider,
586
928
  epochCache,
929
+ packageVersion,
930
+ peerProofVerifier,
931
+ rpcProofVerifier,
932
+ telemetry,
933
+ log,
587
934
  blobClient,
935
+ validatorClient,
588
936
  keyStoreManager,
589
- });
937
+ debugLogStore,
938
+ );
590
939
 
591
- if (!options.dontStartProverNode) {
592
- await proverNode.start();
593
- log.info(`Prover node subsystem started`);
594
- } else {
595
- log.info(`Prover node subsystem created but not started`);
940
+ return node;
941
+ } catch (err) {
942
+ log.error('Failed during node creation, stopping started resources', err);
943
+ for (const resource of started.reverse()) {
944
+ await tryStop(resource);
596
945
  }
946
+ throw err;
597
947
  }
598
-
599
- const node = new AztecNodeService(
600
- config,
601
- p2pClient,
602
- archiver,
603
- archiver,
604
- archiver,
605
- archiver,
606
- worldStateSynchronizer,
607
- sequencer,
608
- proverNode,
609
- slasherClient,
610
- validatorsSentinel,
611
- epochPruneWatcher,
612
- ethereumChain.chainInfo.id,
613
- config.rollupVersion,
614
- globalVariableBuilder,
615
- epochCache,
616
- packageVersion,
617
- peerProofVerifier,
618
- rpcProofVerifier,
619
- telemetry,
620
- log,
621
- blobClient,
622
- validatorClient,
623
- keyStoreManager,
624
- debugLogStore,
625
- );
626
-
627
- return node;
628
948
  }
629
949
 
630
950
  /**
@@ -699,77 +1019,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
699
1019
  return nodeInfo;
700
1020
  }
701
1021
 
702
- /**
703
- * Get a block specified by its block number, block hash, or 'latest'.
704
- * @param block - The block parameter (block number, block hash, or 'latest').
705
- * @returns The requested block.
706
- */
707
- public async getBlock(block: BlockParameter): Promise<L2Block | undefined> {
708
- if (BlockHash.isBlockHash(block)) {
709
- return this.getBlockByHash(block);
710
- }
711
- const blockNumber = block === 'latest' ? await this.getBlockNumber() : (block as BlockNumber);
712
- if (blockNumber === BlockNumber.ZERO) {
713
- return this.buildInitialBlock();
714
- }
715
- return await this.blockSource.getL2Block(blockNumber);
716
- }
717
-
718
- /**
719
- * Get a block specified by its hash.
720
- * @param blockHash - The block hash being requested.
721
- * @returns The requested block.
722
- */
723
- public async getBlockByHash(blockHash: BlockHash): Promise<L2Block | undefined> {
724
- const initialBlockHash = await this.#getInitialHeaderHash();
725
- if (blockHash.equals(initialBlockHash)) {
726
- return this.buildInitialBlock();
727
- }
728
- return await this.blockSource.getL2BlockByHash(blockHash);
729
- }
730
-
731
- private buildInitialBlock(): L2Block {
732
- const initialHeader = this.worldStateSynchronizer.getCommitted().getInitialHeader();
733
- return L2Block.empty(initialHeader);
734
- }
735
-
736
- /**
737
- * Get a block specified by its archive root.
738
- * @param archive - The archive root being requested.
739
- * @returns The requested block.
740
- */
741
- public async getBlockByArchive(archive: Fr): Promise<L2Block | undefined> {
742
- return await this.blockSource.getL2BlockByArchive(archive);
743
- }
744
-
745
- /**
746
- * Method to request blocks. Will attempt to return all requested blocks but will return only those available.
747
- * @param from - The start of the range of blocks to return.
748
- * @param limit - The maximum number of blocks to obtain.
749
- * @returns The blocks requested.
750
- */
751
- public async getBlocks(from: BlockNumber, limit: number): Promise<L2Block[]> {
752
- return (await this.blockSource.getBlocks(from, BlockNumber(limit))) ?? [];
753
- }
754
-
755
- public async getCheckpoints(from: CheckpointNumber, limit: number): Promise<PublishedCheckpoint[]> {
756
- return (await this.blockSource.getCheckpoints(from, limit)) ?? [];
757
- }
758
-
759
- public async getCheckpointedBlocks(from: BlockNumber, limit: number) {
760
- return (await this.blockSource.getCheckpointedBlocks(from, limit)) ?? [];
761
- }
762
-
763
- public getCheckpointsDataForEpoch(epochNumber: EpochNumber) {
764
- return this.blockSource.getCheckpointsDataForEpoch(epochNumber);
1022
+ public async getCurrentMinFees(): Promise<GasFees> {
1023
+ return await this.feeProvider.getCurrentMinFees();
765
1024
  }
766
1025
 
767
- /**
768
- * Method to fetch the current min L2 fees.
769
- * @returns The current min L2 fees.
770
- */
771
- public async getCurrentMinFees(): Promise<GasFees> {
772
- return await this.globalVariableBuilder.getCurrentMinFees();
1026
+ /** Returns predicted min fees for the current slot and next N slots. */
1027
+ public async getPredictedMinFees(manaUsage?: ManaUsageEstimate): Promise<GasFees[]> {
1028
+ return await this.feeProvider.getPredictedMinFees(manaUsage);
773
1029
  }
774
1030
 
775
1031
  public async getMaxPriorityFees(): Promise<GasFees> {
@@ -780,26 +1036,6 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
780
1036
  return GasFees.from({ feePerDaGas: 0n, feePerL2Gas: 0n });
781
1037
  }
782
1038
 
783
- /**
784
- * Method to fetch the latest block number synchronized by the node.
785
- * @returns The block number.
786
- */
787
- public async getBlockNumber(): Promise<BlockNumber> {
788
- return await this.blockSource.getBlockNumber();
789
- }
790
-
791
- public async getProvenBlockNumber(): Promise<BlockNumber> {
792
- return await this.blockSource.getProvenBlockNumber();
793
- }
794
-
795
- public async getCheckpointedBlockNumber(): Promise<BlockNumber> {
796
- return await this.blockSource.getCheckpointedL2BlockNumber();
797
- }
798
-
799
- public getCheckpointNumber(): Promise<CheckpointNumber> {
800
- return this.blockSource.getCheckpointNumber();
801
- }
802
-
803
1039
  /**
804
1040
  * Method to fetch the version of the package.
805
1041
  * @returns The node package version
@@ -917,7 +1153,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
917
1153
  throw new Error(`Invalid tx: ${reason}`);
918
1154
  }
919
1155
 
920
- await this.p2pClient!.sendTx(tx);
1156
+ try {
1157
+ await this.p2pClient!.sendTx(tx);
1158
+ } catch (err) {
1159
+ this.metrics.receivedTx(timer.ms(), false);
1160
+ this.log.warn(`Mempool rejected tx ${txHash}: ${(err as Error).message}`, { txHash });
1161
+ throw err;
1162
+ }
921
1163
  const duration = timer.ms();
922
1164
  this.metrics.receivedTx(duration, true);
923
1165
  this.log.info(`Received tx ${txHash} in ${duration}ms`, { txHash });
@@ -939,10 +1181,15 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
939
1181
  // If the tx is in the pool but not in the archiver, it's pending.
940
1182
  // This handles race conditions between archiver and p2p, where the archiver
941
1183
  // has pruned the block in which a tx was mined, but p2p has not caught up yet.
942
- receipt = new TxReceipt(txHash, TxStatus.PENDING, undefined, undefined);
1184
+ receipt = new TxReceipt(txHash, TxStatus.PENDING, /*executionResult=*/ undefined, /*error=*/ undefined);
943
1185
  } else {
944
1186
  // Otherwise, if we don't know the tx, we consider it dropped.
945
- receipt = new TxReceipt(txHash, TxStatus.DROPPED, undefined, 'Tx dropped by P2P node');
1187
+ receipt = new TxReceipt(
1188
+ txHash,
1189
+ TxStatus.DROPPED,
1190
+ /*executionResult=*/ undefined,
1191
+ /*error=*/ 'Tx dropped by P2P node',
1192
+ );
946
1193
  }
947
1194
 
948
1195
  this.debugLogStore.decorateReceiptWithLogs(txHash.toString(), receipt);
@@ -959,6 +1206,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
959
1206
  */
960
1207
  public async stop() {
961
1208
  this.log.info(`Stopping Aztec Node`);
1209
+ await tryStop(this.attestationsBlockWatcher);
962
1210
  await tryStop(this.validatorsSentinel);
963
1211
  await tryStop(this.epochPruneWatcher);
964
1212
  await tryStop(this.slasherClient);
@@ -1052,7 +1300,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1052
1300
  );
1053
1301
 
1054
1302
  // Build a map from block number to block hash
1055
- const blockNumberToHash = new Map<BlockNumber, Fr>();
1303
+ const blockNumberToHash = new Map<BlockNumber, BlockHash>();
1056
1304
  for (let i = 0; i < uniqueBlockNumbers.length; i++) {
1057
1305
  const blockHash = blockHashes[i];
1058
1306
  if (blockHash === undefined) {
@@ -1070,13 +1318,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1070
1318
  if (blockNumber === undefined) {
1071
1319
  throw new Error(`Block number not found for leaf index ${index} in tree ${MerkleTreeId[treeId]}`);
1072
1320
  }
1073
- const blockHash = blockNumberToHash.get(blockNumber);
1074
- if (blockHash === undefined) {
1321
+ const l2BlockHash = blockNumberToHash.get(blockNumber);
1322
+ if (l2BlockHash === undefined) {
1075
1323
  throw new Error(`Block hash not found for block number ${blockNumber}`);
1076
1324
  }
1077
1325
  return {
1078
1326
  l2BlockNumber: blockNumber,
1079
- l2BlockHash: new BlockHash(blockHash),
1327
+ l2BlockHash,
1080
1328
  data: index,
1081
1329
  };
1082
1330
  });
@@ -1230,41 +1478,6 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1230
1478
  return preimage.leaf.value;
1231
1479
  }
1232
1480
 
1233
- public async getBlockHeader(block: BlockParameter = 'latest'): Promise<BlockHeader | undefined> {
1234
- if (BlockHash.isBlockHash(block)) {
1235
- const initialBlockHash = await this.#getInitialHeaderHash();
1236
- if (block.equals(initialBlockHash)) {
1237
- // Block source doesn't handle initial header so we need to handle the case separately.
1238
- return this.worldStateSynchronizer.getCommitted().getInitialHeader();
1239
- }
1240
- return this.blockSource.getBlockHeaderByHash(block);
1241
- } else {
1242
- // Block source doesn't handle initial header so we need to handle the case separately.
1243
- const blockNumber = block === 'latest' ? await this.getBlockNumber() : (block as BlockNumber);
1244
- if (blockNumber === BlockNumber.ZERO) {
1245
- return this.worldStateSynchronizer.getCommitted().getInitialHeader();
1246
- }
1247
- return this.blockSource.getBlockHeader(block);
1248
- }
1249
- }
1250
-
1251
- /**
1252
- * Get a block header specified by its archive root.
1253
- * @param archive - The archive root being requested.
1254
- * @returns The requested block header.
1255
- */
1256
- public async getBlockHeaderByArchive(archive: Fr): Promise<BlockHeader | undefined> {
1257
- return await this.blockSource.getBlockHeaderByArchive(archive);
1258
- }
1259
-
1260
- public getBlockData(number: BlockNumber): Promise<BlockData | undefined> {
1261
- return this.blockSource.getBlockData(number);
1262
- }
1263
-
1264
- public getBlockDataByArchive(archive: Fr): Promise<BlockData | undefined> {
1265
- return this.blockSource.getBlockDataByArchive(archive);
1266
- }
1267
-
1268
1481
  /**
1269
1482
  * Simulates the public part of a transaction with the current state.
1270
1483
  * @param tx - The transaction to simulate.
@@ -1488,7 +1701,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1488
1701
  return Promise.resolve();
1489
1702
  }
1490
1703
 
1491
- public async rollbackTo(targetBlock: BlockNumber, force?: boolean): Promise<void> {
1704
+ public async rollbackTo(targetBlock: BlockNumber, force?: boolean, resumeSync = true): Promise<void> {
1492
1705
  const archiver = this.blockSource as Archiver;
1493
1706
  if (!('rollbackTo' in archiver)) {
1494
1707
  throw new Error('Archiver implementation does not support rollbacks.');
@@ -1518,9 +1731,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1518
1731
  this.log.error(`Error during rollback`, err);
1519
1732
  throw err;
1520
1733
  } finally {
1521
- this.log.info(`Resuming world state and archiver sync.`);
1522
- this.worldStateSynchronizer.resumeSync();
1523
- archiver.resume();
1734
+ if (resumeSync) {
1735
+ this.log.info(`Resuming world state and archiver sync.`);
1736
+ this.worldStateSynchronizer.resumeSync();
1737
+ archiver.resume();
1738
+ } else {
1739
+ this.log.info(`Sync left paused after rollback (resumeSync=false).`);
1740
+ }
1524
1741
  }
1525
1742
  }
1526
1743
 
@@ -1537,19 +1754,12 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1537
1754
  return Promise.resolve();
1538
1755
  }
1539
1756
 
1540
- public getSlashPayloads(): Promise<SlashPayloadRound[]> {
1541
- if (!this.slasherClient) {
1542
- throw new Error(`Slasher client not enabled`);
1543
- }
1544
- return this.slasherClient.getSlashPayloads();
1545
- }
1546
-
1547
1757
  public getSlashOffenses(round: bigint | 'all' | 'current'): Promise<Offense[]> {
1548
1758
  if (!this.slasherClient) {
1549
1759
  throw new Error(`Slasher client not enabled`);
1550
1760
  }
1551
1761
  if (round === 'all') {
1552
- return this.slasherClient.getPendingOffenses();
1762
+ return this.slasherClient.getOffenses();
1553
1763
  } else {
1554
1764
  return this.slasherClient.gatherOffensesForRound(round === 'current' ? undefined : BigInt(round));
1555
1765
  }
@@ -1643,6 +1853,40 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1643
1853
  this.log.info('Keystore reloaded: coinbase, feeRecipient, and attester keys updated');
1644
1854
  }
1645
1855
 
1856
+ public async mineBlock(): Promise<void> {
1857
+ if (!this.sequencer) {
1858
+ throw new BadRequestError('Cannot mine block: no sequencer is running');
1859
+ }
1860
+
1861
+ const currentBlockNumber = await this.getBlockNumber();
1862
+
1863
+ // Use slot duration + 50% buffer as the timeout so this works on running networks too
1864
+ const { slotDuration } = await this.blockSource.getL1Constants();
1865
+ const timeoutSeconds = Math.ceil(slotDuration * 1.5);
1866
+
1867
+ // Temporarily set minTxsPerBlock to 0 so the sequencer produces a block even with no txs
1868
+ const originalMinTxsPerBlock = this.sequencer.getSequencer().getConfig().minTxsPerBlock;
1869
+ this.sequencer.updateConfig({ minTxsPerBlock: 0 });
1870
+
1871
+ try {
1872
+ // Trigger the sequencer to produce a block immediately
1873
+ void this.sequencer.trigger();
1874
+
1875
+ // Wait for the new L2 block to appear
1876
+ await retryUntil(
1877
+ async () => {
1878
+ const newBlockNumber = await this.getBlockNumber();
1879
+ return newBlockNumber > currentBlockNumber ? true : undefined;
1880
+ },
1881
+ 'mineBlock',
1882
+ timeoutSeconds,
1883
+ 0.1,
1884
+ );
1885
+ } finally {
1886
+ this.sequencer.updateConfig({ minTxsPerBlock: originalMinTxsPerBlock });
1887
+ }
1888
+ }
1889
+
1646
1890
  #getInitialHeaderHash(): Promise<BlockHash> {
1647
1891
  if (!this.initialHeaderHashPromise) {
1648
1892
  this.initialHeaderHashPromise = this.worldStateSynchronizer.getCommitted().getInitialHeader().hash();
@@ -1674,25 +1918,28 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1674
1918
  if (BlockHash.isBlockHash(block)) {
1675
1919
  const initialBlockHash = await this.#getInitialHeaderHash();
1676
1920
  if (block.equals(initialBlockHash)) {
1677
- // Block source doesn't handle initial header so we need to handle the case separately.
1678
- return this.worldStateSynchronizer.getSnapshot(BlockNumber.ZERO);
1679
- }
1680
-
1681
- const header = await this.blockSource.getBlockHeaderByHash(block);
1682
- if (!header) {
1683
- throw new Error(
1684
- `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.`,
1685
- );
1921
+ // Block 0 is a first-class historical block: its state lives in the trees' persisted
1922
+ // block-0 payload. Resolving the genesis hash to block number 0 lets the snapshot path
1923
+ // pin reads to genesis state even after the node has advanced past it.
1924
+ blockNumber = BlockNumber.ZERO;
1925
+ } else {
1926
+ const header = await this.blockSource.getBlockHeaderByHash(block);
1927
+ if (!header) {
1928
+ throw new Error(
1929
+ `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.`,
1930
+ );
1931
+ }
1932
+ blockNumber = header.getBlockNumber();
1686
1933
  }
1687
-
1688
- blockNumber = header.getBlockNumber();
1689
1934
  } else {
1690
1935
  blockNumber = block as BlockNumber;
1691
1936
  }
1692
1937
 
1693
1938
  // Check it's within world state sync range
1694
1939
  if (blockNumber > blockSyncedTo) {
1695
- throw new Error(`Queried block ${block} not yet synced by the node (node is synced upto ${blockSyncedTo}).`);
1940
+ throw new Error(
1941
+ `Queried block ${inspectBlockParameter(block)} not yet synced by the node (node is synced upto ${blockSyncedTo}).`,
1942
+ );
1696
1943
  }
1697
1944
  this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
1698
1945
 
@@ -1701,9 +1948,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1701
1948
  // Double-check world-state synced to the same block hash as was requested
1702
1949
  if (BlockHash.isBlockHash(block)) {
1703
1950
  const blockHash = await snapshot.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber));
1704
- if (!blockHash || !new BlockHash(blockHash).equals(block)) {
1951
+ if (!blockHash || !block.equals(blockHash)) {
1952
+ const initialBlockHash = await this.#getInitialHeaderHash();
1705
1953
  throw new Error(
1706
- `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.`,
1954
+ `Block hash ${block.toString()} not found in world state at block number ${blockNumber} (world state has ${blockHash?.toString() ?? 'no hash'} at that index, genesis header hash is ${initialBlockHash.toString()}). If the node API has been queried with anchor block hash possibly a reorg has occurred.`,
1707
1955
  );
1708
1956
  }
1709
1957
  }
@@ -1711,23 +1959,31 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1711
1959
  return snapshot;
1712
1960
  }
1713
1961
 
1714
- /** Resolves a block parameter to a block number. */
1962
+ /** Resolves any {@link BlockParameter} variant to a concrete block number. */
1715
1963
  protected async resolveBlockNumber(block: BlockParameter): Promise<BlockNumber> {
1716
- if (block === 'latest') {
1717
- return BlockNumber(await this.blockSource.getBlockNumber());
1964
+ const resolved = await this.resolveBlockParameter(block);
1965
+ if (resolved.number !== undefined) {
1966
+ return resolved.number;
1718
1967
  }
1719
- if (BlockHash.isBlockHash(block)) {
1968
+ if (resolved.hash !== undefined) {
1720
1969
  const initialBlockHash = await this.#getInitialHeaderHash();
1721
- if (block.equals(initialBlockHash)) {
1970
+ if (resolved.hash.equals(initialBlockHash)) {
1722
1971
  return BlockNumber.ZERO;
1723
1972
  }
1724
- const header = await this.blockSource.getBlockHeaderByHash(block);
1973
+ const header = await this.blockSource.getBlockHeaderByHash(resolved.hash);
1974
+ if (!header) {
1975
+ throw new Error(`Block hash ${resolved.hash.toString()} not found.`);
1976
+ }
1977
+ return header.getBlockNumber();
1978
+ }
1979
+ if (resolved.archive !== undefined) {
1980
+ const header = await this.blockSource.getBlockHeaderByArchive(resolved.archive);
1725
1981
  if (!header) {
1726
- throw new Error(`Block hash ${block.toString()} not found.`);
1982
+ throw new Error(`Block with archive ${resolved.archive.toString()} not found.`);
1727
1983
  }
1728
1984
  return header.getBlockNumber();
1729
1985
  }
1730
- return block as BlockNumber;
1986
+ throw new BadRequestError(`Invalid BlockParameter: ${JSON.stringify(block)}`);
1731
1987
  }
1732
1988
 
1733
1989
  /**