@aztec/aztec-node 0.0.1-commit.5de5ca79e → 0.0.1-commit.6201a7b05

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import { Archiver, createArchiver } from '@aztec/archiver';
2
- import { BBCircuitVerifier, QueuedIVCVerifier, TestCircuitVerifier } from '@aztec/bb-prover';
2
+ import { BBCircuitVerifier, BatchChonkVerifier, QueuedIVCVerifier } from '@aztec/bb-prover';
3
+ import { TestCircuitVerifier } from '@aztec/bb-prover/test';
3
4
  import { type BlobClientInterface, createBlobClientWithFileStores } from '@aztec/blob-client/client';
4
5
  import { Blob } from '@aztec/blob-lib';
5
6
  import { ARCHIVE_HEIGHT, type L1_TO_L2_MSG_TREE_HEIGHT, type NOTE_HASH_TREE_HEIGHT } from '@aztec/constants';
@@ -8,12 +9,14 @@ import { createEthereumChain } from '@aztec/ethereum/chain';
8
9
  import { getPublicClient, makeL1HttpTransport } from '@aztec/ethereum/client';
9
10
  import { RegistryContract, RollupContract } from '@aztec/ethereum/contracts';
10
11
  import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
12
+ import type { L1TxUtils } from '@aztec/ethereum/l1-tx-utils';
11
13
  import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
12
14
  import { chunkBy, compactArray, pick, unique } from '@aztec/foundation/collection';
13
15
  import { Fr } from '@aztec/foundation/curves/bn254';
14
16
  import { EthAddress } from '@aztec/foundation/eth-address';
15
17
  import { BadRequestError } from '@aztec/foundation/json-rpc';
16
18
  import { type Logger, createLogger } from '@aztec/foundation/log';
19
+ import { retryUntil } from '@aztec/foundation/retry';
17
20
  import { count } from '@aztec/foundation/string';
18
21
  import { DateProvider, Timer } from '@aztec/foundation/timer';
19
22
  import { MembershipWitness, SiblingPath } from '@aztec/foundation/trees';
@@ -30,7 +33,12 @@ import {
30
33
  import { ProtocolContractAddress } from '@aztec/protocol-contracts';
31
34
  import { type ProverNode, type ProverNodeDeps, createProverNode } from '@aztec/prover-node';
32
35
  import { createKeyStoreForProver } from '@aztec/prover-node/config';
33
- 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';
34
42
  import { PublicProcessorFactory } from '@aztec/simulator/server';
35
43
  import {
36
44
  AttestationsBlockWatcher,
@@ -42,14 +50,13 @@ import {
42
50
  import { CollectionLimitsConfig, PublicSimulatorConfig } from '@aztec/stdlib/avm';
43
51
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
44
52
  import {
45
- type BlockData,
46
53
  BlockHash,
47
54
  type BlockParameter,
48
55
  type DataInBlock,
49
56
  L2Block,
50
57
  type L2BlockSource,
58
+ inspectBlockParameter,
51
59
  } from '@aztec/stdlib/block';
52
- import type { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
53
60
  import type {
54
61
  ContractClassPublic,
55
62
  ContractDataSource,
@@ -57,16 +64,24 @@ import type {
57
64
  NodeInfo,
58
65
  ProtocolContractAddresses,
59
66
  } from '@aztec/stdlib/contract';
60
- import { GasFees } from '@aztec/stdlib/gas';
67
+ import { GasFees, type ManaUsageEstimate } from '@aztec/stdlib/gas';
61
68
  import { computePublicDataTreeLeafSlot } from '@aztec/stdlib/hash';
62
- import {
63
- type AztecNode,
64
- type AztecNodeAdmin,
65
- type AztecNodeAdminConfig,
66
- AztecNodeAdminConfigSchema,
67
- type GetContractClassLogsResponse,
68
- 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,
69
83
  } from '@aztec/stdlib/interfaces/client';
84
+ import { AztecNodeAdminConfigSchema } from '@aztec/stdlib/interfaces/client';
70
85
  import {
71
86
  type AllowedElement,
72
87
  type ClientProtocolCircuitVerifier,
@@ -79,11 +94,12 @@ import {
79
94
  import type { DebugLogStore, LogFilter, SiloedTag, Tag, TxScopedL2Log } from '@aztec/stdlib/logs';
80
95
  import { InMemoryDebugLogStore, NullDebugLogStore } from '@aztec/stdlib/logs';
81
96
  import { InboxLeaf, type L1ToL2MessageSource } from '@aztec/stdlib/messaging';
82
- import type { Offense, SlashPayloadRound } from '@aztec/stdlib/slashing';
83
- 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';
84
99
  import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
85
100
  import {
86
101
  type BlockHeader,
102
+ type FeeProvider,
87
103
  type GlobalVariableBuilder as GlobalVariableBuilderInterface,
88
104
  type IndexedTxEffect,
89
105
  PublicSimulationOutput,
@@ -95,6 +111,7 @@ import {
95
111
  } from '@aztec/stdlib/tx';
96
112
  import { getPackageVersion } from '@aztec/stdlib/update-checker';
97
113
  import type { SingleValidatorStats, ValidatorsStats } from '@aztec/stdlib/validators';
114
+ import type { GenesisData } from '@aztec/stdlib/world-state';
98
115
  import {
99
116
  Attributes,
100
117
  type TelemetryClient,
@@ -108,7 +125,7 @@ import {
108
125
  FullNodeCheckpointsBuilder,
109
126
  NodeKeystoreAdapter,
110
127
  ValidatorClient,
111
- createBlockProposalHandler,
128
+ createProposalHandler,
112
129
  createValidatorClient,
113
130
  } from '@aztec/validator-client';
114
131
  import type { SlashingProtectionDatabase } from '@aztec/validator-ha-signer/types';
@@ -118,13 +135,19 @@ import { createPublicClient } from 'viem';
118
135
 
119
136
  import { createSentinel } from '../sentinel/factory.js';
120
137
  import { Sentinel } from '../sentinel/sentinel.js';
138
+ import {
139
+ blockResponseFromBlockData,
140
+ blockResponseFromL2Block,
141
+ checkpointResponseFromCheckpointData,
142
+ checkpointResponseFromPublishedCheckpoint,
143
+ } from './block_response_helpers.js';
121
144
  import { type AztecNodeConfig, createKeyStoreForValidator } from './config.js';
122
145
  import { NodeMetrics } from './node_metrics.js';
123
146
 
124
147
  /**
125
148
  * The aztec node.
126
149
  */
127
- export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
150
+ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDebug, Traceable {
128
151
  private metrics: NodeMetrics;
129
152
  private initialHeaderHashPromise: Promise<BlockHash> | undefined = undefined;
130
153
 
@@ -146,12 +169,15 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
146
169
  protected readonly slasherClient: SlasherClientInterface | undefined,
147
170
  protected readonly validatorsSentinel: Sentinel | undefined,
148
171
  protected readonly epochPruneWatcher: EpochPruneWatcher | undefined,
172
+ protected readonly attestationsBlockWatcher: AttestationsBlockWatcher | undefined,
149
173
  protected readonly l1ChainId: number,
150
174
  protected readonly version: number,
151
175
  protected readonly globalVariableBuilder: GlobalVariableBuilderInterface,
176
+ protected readonly feeProvider: FeeProvider,
152
177
  protected readonly epochCache: EpochCacheInterface,
153
178
  protected readonly packageVersion: string,
154
- private proofVerifier: ClientProtocolCircuitVerifier,
179
+ private peerProofVerifier: ClientProtocolCircuitVerifier,
180
+ private rpcProofVerifier: ClientProtocolCircuitVerifier,
155
181
  private telemetry: TelemetryClient = getTelemetryClient(),
156
182
  private log = createLogger('node'),
157
183
  private blobClient?: BlobClientInterface,
@@ -173,15 +199,291 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
173
199
  }
174
200
  }
175
201
 
202
+ /** @internal Exposed for testing — returns the RPC proof verifier. */
203
+ public getProofVerifier(): ClientProtocolCircuitVerifier {
204
+ return this.rpcProofVerifier;
205
+ }
206
+
176
207
  public async getWorldStateSyncStatus(): Promise<WorldStateSyncStatus> {
177
208
  const status = await this.worldStateSynchronizer.status();
178
209
  return status.syncSummary;
179
210
  }
180
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
+
181
217
  public getL2Tips() {
182
218
  return this.blockSource.getL2Tips();
183
219
  }
184
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
+
185
487
  /**
186
488
  * initializes the Aztec Node, wait for component to sync.
187
489
  * @param config - The configuration to be used by the aztec node.
@@ -199,7 +501,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
199
501
  slashingProtectionDb?: SlashingProtectionDatabase;
200
502
  } = {},
201
503
  options: {
202
- prefilledPublicData?: PublicDataTreeLeaf[];
504
+ genesis?: GenesisData;
203
505
  dontStartSequencer?: boolean;
204
506
  dontStartProverNode?: boolean;
205
507
  } = {},
@@ -295,299 +597,354 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
295
597
 
296
598
  const epochCache = await EpochCache.create(config.l1Contracts.rollupAddress, config, { dateProvider });
297
599
 
298
- const archiver = await createArchiver(
299
- config,
300
- { blobClient, epochCache, telemetry, dateProvider },
301
- { blockUntilSync: !config.skipArchiverInitialSync },
302
- );
303
-
304
- // now create the merkle trees and the world state synchronizer
305
- const worldStateSynchronizer = await createWorldStateSynchronizer(
306
- config,
307
- archiver,
308
- options.prefilledPublicData,
309
- telemetry,
310
- );
311
- const circuitVerifier =
312
- config.realProofs || config.debugForceTxProofVerification
313
- ? await BBCircuitVerifier.new(config)
314
- : new TestCircuitVerifier(config.proverTestVerificationDelayMs);
315
-
316
- let debugLogStore: DebugLogStore;
317
- if (!config.realProofs) {
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',
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 },
323
607
  );
324
- } else {
325
- debugLogStore = new NullDebugLogStore();
326
- }
327
-
328
- const proofVerifier = new QueuedIVCVerifier(config, circuitVerifier);
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
- }
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);
334
625
 
335
- // create the tx pool and the p2p client, which will need the l2 block source
336
- const p2pClient = await createP2PClient(
337
- config,
338
- archiver,
339
- proofVerifier,
340
- worldStateSynchronizer,
341
- epochCache,
342
- packageVersion,
343
- dateProvider,
344
- telemetry,
345
- deps.p2pClientDeps,
346
- );
626
+ let debugLogStore: DebugLogStore;
627
+ if (!config.realProofs) {
628
+ log.warn(`Aztec node is accepting fake proofs`);
347
629
 
348
- // We'll accumulate sentinel watchers here
349
- 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
+ }
350
637
 
351
- // Create FullNodeCheckpointsBuilder for block proposal handling and tx validation.
352
- // Override maxTxsPerCheckpoint with the validator-specific limit if set.
353
- const validatorCheckpointsBuilder = new FullNodeCheckpointsBuilder(
354
- {
355
- ...config,
638
+ const globalVariableBuilderConfig = {
639
+ l1Contracts: config.l1Contracts,
640
+ ethereumSlotDuration: config.ethereumSlotDuration,
641
+ rollupVersion: BigInt(config.rollupVersion),
356
642
  l1GenesisTime,
357
643
  slotDuration: Number(slotDuration),
358
- rollupManaLimit,
359
- maxTxsPerCheckpoint: config.validateMaxTxsPerCheckpoint,
360
- },
361
- worldStateSynchronizer,
362
- archiver,
363
- dateProvider,
364
- telemetry,
365
- );
366
-
367
- let validatorClient: ValidatorClient | undefined;
644
+ };
368
645
 
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
- });
646
+ const globalVariableBuilder = new GlobalVariableBuilder(dateProvider, publicClient, globalVariableBuilderConfig);
647
+ const feeProvider = new FeeProviderImpl(dateProvider, publicClient, globalVariableBuilderConfig);
384
648
 
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
- }
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');
393
652
  }
394
- }
395
653
 
396
- // If there's no validator client, create a BlockProposalHandler to handle block 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
- if (!validatorClient) {
400
- const reexecute = !!config.alwaysReexecuteBlockProposals;
401
- log.info(`Setting up block proposal handler` + (reexecute ? ' with reexecution of proposals' : ''));
402
- createBlockProposalHandler(config, {
403
- checkpointsBuilder: validatorCheckpointsBuilder,
404
- 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,
405
660
  epochCache,
406
- blockSource: archiver,
407
- l1ToL2MessageSource: archiver,
408
- p2pClient,
661
+ feeProvider,
662
+ packageVersion,
409
663
  dateProvider,
410
664
  telemetry,
411
- }).register(p2pClient, reexecute);
412
- }
413
-
414
- // Start world state and wait for it to sync to the archiver.
415
- await worldStateSynchronizer.start();
416
-
417
- // Start p2p. Note that it depends on world state to be running.
418
- 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
+ );
419
687
 
420
- let validatorsSentinel: Awaited<ReturnType<typeof createSentinel>> | undefined;
421
- let epochPruneWatcher: EpochPruneWatcher | undefined;
422
- let attestationsBlockWatcher: AttestationsBlockWatcher | undefined;
688
+ let validatorClient: ValidatorClient | undefined;
423
689
 
424
- if (!proverOnly) {
425
- validatorsSentinel = await createSentinel(epochCache, archiver, p2pClient, config);
426
- if (validatorsSentinel && config.slashInactivityPenalty > 0n) {
427
- 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
+ }
428
720
  }
429
721
 
430
- if (config.slashPrunePenalty > 0n || config.slashDataWithholdingPenalty > 0n) {
431
- epochPruneWatcher = new EpochPruneWatcher(
432
- archiver,
433
- 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,
434
732
  epochCache,
435
- p2pClient.getTxProvider(),
436
- validatorCheckpointsBuilder,
437
- config,
438
- );
439
- watchers.push(epochPruneWatcher);
733
+ blockSource: archiver,
734
+ l1ToL2MessageSource: archiver,
735
+ p2pClient,
736
+ blobClient,
737
+ dateProvider,
738
+ telemetry,
739
+ }).register(p2pClient, reexecute, archiver);
440
740
  }
441
741
 
442
- // We assume we want to slash for invalid attestations unless all max penalties are set to 0
443
- if (config.slashProposeInvalidAttestationsPenalty > 0n || config.slashAttestDescendantOfInvalidPenalty > 0n) {
444
- attestationsBlockWatcher = new AttestationsBlockWatcher(archiver, epochCache, config);
445
- watchers.push(attestationsBlockWatcher);
446
- }
447
- }
742
+ // Start world state and wait for it to sync to the archiver.
743
+ await worldStateSynchronizer.start();
448
744
 
449
- // Start p2p-related services once the archiver has completed sync
450
- void archiver
451
- .waitForInitialSync()
452
- .then(async () => {
453
- await p2pClient.start();
454
- await validatorsSentinel?.start();
455
- await epochPruneWatcher?.start();
456
- await attestationsBlockWatcher?.start();
457
- log.info(`All p2p services started`);
458
- })
459
- .catch(err => log.error('Failed to start p2p services after archiver sync', err));
460
-
461
- // Validator enabled, create/start relevant service
462
- let sequencer: SequencerClient | undefined;
463
- let slasherClient: SlasherClientInterface | undefined;
464
- if (!config.disableValidator && validatorClient) {
465
- // We create a slasher only if we have a sequencer, since all slashing actions go through the sequencer publisher
466
- // as they are executed when the node is selected as proposer.
467
- const validatorAddresses = keyStoreManager
468
- ? NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager).getAddresses()
469
- : [];
470
-
471
- slasherClient = await createSlasher(
472
- config,
473
- config.l1Contracts,
474
- getPublicClient(config),
475
- watchers,
476
- dateProvider,
477
- epochCache,
478
- validatorAddresses,
479
- undefined, // logger
480
- );
481
- await slasherClient.start();
745
+ // Start p2p. Note that it depends on world state to be running.
746
+ await p2pClient.start();
482
747
 
483
- const l1TxUtils = config.sequencerPublisherForwarderAddress
484
- ? await createForwarderL1TxUtilsFromSigners(
485
- publicClient,
486
- keyStoreManager!.createAllValidatorPublisherSigners(),
487
- config.sequencerPublisherForwarderAddress,
488
- { ...config, scope: 'sequencer' },
489
- { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() },
490
- )
491
- : await createL1TxUtilsFromSigners(
748
+ let validatorsSentinel: Awaited<ReturnType<typeof createSentinel>> | undefined;
749
+ let epochPruneWatcher: EpochPruneWatcher | undefined;
750
+ let attestationsBlockWatcher: AttestationsBlockWatcher | undefined;
751
+
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(
492
840
  publicClient,
493
- keyStoreManager!.createAllValidatorPublisherSigners(),
841
+ [fundingSigner],
494
842
  { ...config, scope: 'sequencer' },
495
- { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() },
843
+ { telemetry, logger: log.createChild('l1-tx-utils:funder'), dateProvider },
496
844
  );
845
+ funderL1TxUtils = funder;
846
+ }
497
847
 
498
- // Create and start the sequencer client
499
- const checkpointsBuilder = new CheckpointsBuilder(
500
- { ...config, l1GenesisTime, slotDuration: Number(slotDuration), rollupManaLimit },
501
- worldStateSynchronizer,
502
- archiver,
503
- dateProvider,
504
- telemetry,
505
- debugLogStore,
506
- );
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,
856
+ );
507
857
 
508
- sequencer = await SequencerClient.new(config, {
509
- ...deps,
510
- epochCache,
511
- l1TxUtils,
512
- validatorClient,
513
- p2pClient,
514
- worldStateSynchronizer,
515
- slasherClient,
516
- checkpointsBuilder,
517
- l2BlockSource: archiver,
518
- l1ToL2MessageSource: archiver,
519
- telemetry,
520
- dateProvider,
521
- blobClient,
522
- nodeKeyStore: keyStoreManager!,
523
- });
524
- }
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
+ });
876
+ }
525
877
 
526
- if (!options.dontStartSequencer && sequencer) {
527
- await sequencer.start();
528
- log.verbose(`Sequencer started`);
529
- } else if (sequencer) {
530
- log.warn(`Sequencer created but not started`);
531
- }
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
+ }
532
885
 
533
- // Create prover node subsystem if enabled
534
- let proverNode: ProverNode | undefined;
535
- if (config.enableProverNode) {
536
- proverNode = await createProverNode(config, {
537
- ...deps.proverNodeDeps,
538
- telemetry,
539
- dateProvider,
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,
912
+ p2pClient,
913
+ archiver,
914
+ archiver,
915
+ archiver,
540
916
  archiver,
541
917
  worldStateSynchronizer,
542
- p2pClient,
918
+ sequencer,
919
+ proverNode,
920
+ slasherClient,
921
+ validatorsSentinel,
922
+ epochPruneWatcher,
923
+ attestationsBlockWatcher,
924
+ ethereumChain.chainInfo.id,
925
+ config.rollupVersion,
926
+ globalVariableBuilder,
927
+ feeProvider,
543
928
  epochCache,
929
+ packageVersion,
930
+ peerProofVerifier,
931
+ rpcProofVerifier,
932
+ telemetry,
933
+ log,
544
934
  blobClient,
935
+ validatorClient,
545
936
  keyStoreManager,
546
- });
937
+ debugLogStore,
938
+ );
547
939
 
548
- if (!options.dontStartProverNode) {
549
- await proverNode.start();
550
- log.info(`Prover node subsystem started`);
551
- } else {
552
- 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);
553
945
  }
946
+ throw err;
554
947
  }
555
-
556
- const globalVariableBuilder = new GlobalVariableBuilder({
557
- ...config,
558
- rollupVersion: BigInt(config.rollupVersion),
559
- l1GenesisTime,
560
- slotDuration: Number(slotDuration),
561
- });
562
-
563
- const node = new AztecNodeService(
564
- config,
565
- p2pClient,
566
- archiver,
567
- archiver,
568
- archiver,
569
- archiver,
570
- worldStateSynchronizer,
571
- sequencer,
572
- proverNode,
573
- slasherClient,
574
- validatorsSentinel,
575
- epochPruneWatcher,
576
- ethereumChain.chainInfo.id,
577
- config.rollupVersion,
578
- globalVariableBuilder,
579
- epochCache,
580
- packageVersion,
581
- proofVerifier,
582
- telemetry,
583
- log,
584
- blobClient,
585
- validatorClient,
586
- keyStoreManager,
587
- debugLogStore,
588
- );
589
-
590
- return node;
591
948
  }
592
949
 
593
950
  /**
@@ -662,77 +1019,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
662
1019
  return nodeInfo;
663
1020
  }
664
1021
 
665
- /**
666
- * Get a block specified by its block number, block hash, or 'latest'.
667
- * @param block - The block parameter (block number, block hash, or 'latest').
668
- * @returns The requested block.
669
- */
670
- public async getBlock(block: BlockParameter): Promise<L2Block | undefined> {
671
- if (BlockHash.isBlockHash(block)) {
672
- return this.getBlockByHash(block);
673
- }
674
- const blockNumber = block === 'latest' ? await this.getBlockNumber() : (block as BlockNumber);
675
- if (blockNumber === BlockNumber.ZERO) {
676
- return this.buildInitialBlock();
677
- }
678
- return await this.blockSource.getL2Block(blockNumber);
679
- }
680
-
681
- /**
682
- * Get a block specified by its hash.
683
- * @param blockHash - The block hash being requested.
684
- * @returns The requested block.
685
- */
686
- public async getBlockByHash(blockHash: BlockHash): Promise<L2Block | undefined> {
687
- const initialBlockHash = await this.#getInitialHeaderHash();
688
- if (blockHash.equals(initialBlockHash)) {
689
- return this.buildInitialBlock();
690
- }
691
- return await this.blockSource.getL2BlockByHash(blockHash);
692
- }
693
-
694
- private buildInitialBlock(): L2Block {
695
- const initialHeader = this.worldStateSynchronizer.getCommitted().getInitialHeader();
696
- return L2Block.empty(initialHeader);
697
- }
698
-
699
- /**
700
- * Get a block specified by its archive root.
701
- * @param archive - The archive root being requested.
702
- * @returns The requested block.
703
- */
704
- public async getBlockByArchive(archive: Fr): Promise<L2Block | undefined> {
705
- return await this.blockSource.getL2BlockByArchive(archive);
706
- }
707
-
708
- /**
709
- * Method to request blocks. Will attempt to return all requested blocks but will return only those available.
710
- * @param from - The start of the range of blocks to return.
711
- * @param limit - The maximum number of blocks to obtain.
712
- * @returns The blocks requested.
713
- */
714
- public async getBlocks(from: BlockNumber, limit: number): Promise<L2Block[]> {
715
- return (await this.blockSource.getBlocks(from, BlockNumber(limit))) ?? [];
716
- }
717
-
718
- public async getCheckpoints(from: CheckpointNumber, limit: number): Promise<PublishedCheckpoint[]> {
719
- return (await this.blockSource.getCheckpoints(from, limit)) ?? [];
720
- }
721
-
722
- public async getCheckpointedBlocks(from: BlockNumber, limit: number) {
723
- return (await this.blockSource.getCheckpointedBlocks(from, limit)) ?? [];
1022
+ public async getCurrentMinFees(): Promise<GasFees> {
1023
+ return await this.feeProvider.getCurrentMinFees();
724
1024
  }
725
1025
 
726
- public getCheckpointsDataForEpoch(epochNumber: EpochNumber) {
727
- return this.blockSource.getCheckpointsDataForEpoch(epochNumber);
728
- }
729
-
730
- /**
731
- * Method to fetch the current min L2 fees.
732
- * @returns The current min L2 fees.
733
- */
734
- public async getCurrentMinFees(): Promise<GasFees> {
735
- 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);
736
1029
  }
737
1030
 
738
1031
  public async getMaxPriorityFees(): Promise<GasFees> {
@@ -743,26 +1036,6 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
743
1036
  return GasFees.from({ feePerDaGas: 0n, feePerL2Gas: 0n });
744
1037
  }
745
1038
 
746
- /**
747
- * Method to fetch the latest block number synchronized by the node.
748
- * @returns The block number.
749
- */
750
- public async getBlockNumber(): Promise<BlockNumber> {
751
- return await this.blockSource.getBlockNumber();
752
- }
753
-
754
- public async getProvenBlockNumber(): Promise<BlockNumber> {
755
- return await this.blockSource.getProvenBlockNumber();
756
- }
757
-
758
- public async getCheckpointedBlockNumber(): Promise<BlockNumber> {
759
- return await this.blockSource.getCheckpointedL2BlockNumber();
760
- }
761
-
762
- public getCheckpointNumber(): Promise<CheckpointNumber> {
763
- return this.blockSource.getCheckpointNumber();
764
- }
765
-
766
1039
  /**
767
1040
  * Method to fetch the version of the package.
768
1041
  * @returns The node package version
@@ -880,7 +1153,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
880
1153
  throw new Error(`Invalid tx: ${reason}`);
881
1154
  }
882
1155
 
883
- 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
+ }
884
1163
  const duration = timer.ms();
885
1164
  this.metrics.receivedTx(duration, true);
886
1165
  this.log.info(`Received tx ${txHash} in ${duration}ms`, { txHash });
@@ -902,10 +1181,15 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
902
1181
  // If the tx is in the pool but not in the archiver, it's pending.
903
1182
  // This handles race conditions between archiver and p2p, where the archiver
904
1183
  // has pruned the block in which a tx was mined, but p2p has not caught up yet.
905
- receipt = new TxReceipt(txHash, TxStatus.PENDING, undefined, undefined);
1184
+ receipt = new TxReceipt(txHash, TxStatus.PENDING, /*executionResult=*/ undefined, /*error=*/ undefined);
906
1185
  } else {
907
1186
  // Otherwise, if we don't know the tx, we consider it dropped.
908
- 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
+ );
909
1193
  }
910
1194
 
911
1195
  this.debugLogStore.decorateReceiptWithLogs(txHash.toString(), receipt);
@@ -922,10 +1206,11 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
922
1206
  */
923
1207
  public async stop() {
924
1208
  this.log.info(`Stopping Aztec Node`);
1209
+ await tryStop(this.attestationsBlockWatcher);
925
1210
  await tryStop(this.validatorsSentinel);
926
1211
  await tryStop(this.epochPruneWatcher);
927
1212
  await tryStop(this.slasherClient);
928
- await tryStop(this.proofVerifier);
1213
+ await Promise.all([tryStop(this.peerProofVerifier), tryStop(this.rpcProofVerifier)]);
929
1214
  await tryStop(this.sequencer);
930
1215
  await tryStop(this.proverNode);
931
1216
  await tryStop(this.p2pClient);
@@ -1015,7 +1300,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1015
1300
  );
1016
1301
 
1017
1302
  // Build a map from block number to block hash
1018
- const blockNumberToHash = new Map<BlockNumber, Fr>();
1303
+ const blockNumberToHash = new Map<BlockNumber, BlockHash>();
1019
1304
  for (let i = 0; i < uniqueBlockNumbers.length; i++) {
1020
1305
  const blockHash = blockHashes[i];
1021
1306
  if (blockHash === undefined) {
@@ -1033,13 +1318,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1033
1318
  if (blockNumber === undefined) {
1034
1319
  throw new Error(`Block number not found for leaf index ${index} in tree ${MerkleTreeId[treeId]}`);
1035
1320
  }
1036
- const blockHash = blockNumberToHash.get(blockNumber);
1037
- if (blockHash === undefined) {
1321
+ const l2BlockHash = blockNumberToHash.get(blockNumber);
1322
+ if (l2BlockHash === undefined) {
1038
1323
  throw new Error(`Block hash not found for block number ${blockNumber}`);
1039
1324
  }
1040
1325
  return {
1041
1326
  l2BlockNumber: blockNumber,
1042
- l2BlockHash: new BlockHash(blockHash),
1327
+ l2BlockHash,
1043
1328
  data: index,
1044
1329
  };
1045
1330
  });
@@ -1053,6 +1338,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1053
1338
  // which is the archive tree root BEFORE the anchor block was added (i.e. the state after block N-1).
1054
1339
  // So we need the world state at block N-1, not block N, to produce a sibling path matching that root.
1055
1340
  const referenceBlockNumber = await this.resolveBlockNumber(referenceBlock);
1341
+ if (referenceBlockNumber === BlockNumber.ZERO) {
1342
+ // Block 0 (the initial block) has an empty archive, so no membership witness can exist.
1343
+ return undefined;
1344
+ }
1056
1345
  const committedDb = await this.getWorldState(BlockNumber(referenceBlockNumber - 1));
1057
1346
  const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.ARCHIVE>(MerkleTreeId.ARCHIVE, [blockHash]);
1058
1347
  return pathAndIndex === undefined
@@ -1189,41 +1478,6 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1189
1478
  return preimage.leaf.value;
1190
1479
  }
1191
1480
 
1192
- public async getBlockHeader(block: BlockParameter = 'latest'): Promise<BlockHeader | undefined> {
1193
- if (BlockHash.isBlockHash(block)) {
1194
- const initialBlockHash = await this.#getInitialHeaderHash();
1195
- if (block.equals(initialBlockHash)) {
1196
- // Block source doesn't handle initial header so we need to handle the case separately.
1197
- return this.worldStateSynchronizer.getCommitted().getInitialHeader();
1198
- }
1199
- return this.blockSource.getBlockHeaderByHash(block);
1200
- } else {
1201
- // Block source doesn't handle initial header so we need to handle the case separately.
1202
- const blockNumber = block === 'latest' ? await this.getBlockNumber() : (block as BlockNumber);
1203
- if (blockNumber === BlockNumber.ZERO) {
1204
- return this.worldStateSynchronizer.getCommitted().getInitialHeader();
1205
- }
1206
- return this.blockSource.getBlockHeader(block);
1207
- }
1208
- }
1209
-
1210
- /**
1211
- * Get a block header specified by its archive root.
1212
- * @param archive - The archive root being requested.
1213
- * @returns The requested block header.
1214
- */
1215
- public async getBlockHeaderByArchive(archive: Fr): Promise<BlockHeader | undefined> {
1216
- return await this.blockSource.getBlockHeaderByArchive(archive);
1217
- }
1218
-
1219
- public getBlockData(number: BlockNumber): Promise<BlockData | undefined> {
1220
- return this.blockSource.getBlockData(number);
1221
- }
1222
-
1223
- public getBlockDataByArchive(archive: Fr): Promise<BlockData | undefined> {
1224
- return this.blockSource.getBlockDataByArchive(archive);
1225
- }
1226
-
1227
1481
  /**
1228
1482
  * Simulates the public part of a transaction with the current state.
1229
1483
  * @param tx - The transaction to simulate.
@@ -1315,7 +1569,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1315
1569
  { isSimulation, skipFeeEnforcement }: { isSimulation?: boolean; skipFeeEnforcement?: boolean } = {},
1316
1570
  ): Promise<TxValidationResult> {
1317
1571
  const db = this.worldStateSynchronizer.getCommitted();
1318
- const verifier = isSimulation ? undefined : this.proofVerifier;
1572
+ const verifier = isSimulation ? undefined : this.rpcProofVerifier;
1319
1573
 
1320
1574
  // We accept transactions if they are not expired by the next slot (checked based on the ExpirationTimestamp field)
1321
1575
  const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
@@ -1364,7 +1618,15 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1364
1618
  archiver.updateConfig(config);
1365
1619
  }
1366
1620
  if (newConfig.realProofs !== this.config.realProofs) {
1367
- this.proofVerifier = config.realProofs ? await BBCircuitVerifier.new(newConfig) : new TestCircuitVerifier();
1621
+ await Promise.all([tryStop(this.peerProofVerifier), tryStop(this.rpcProofVerifier)]);
1622
+ if (newConfig.realProofs) {
1623
+ this.peerProofVerifier = await BatchChonkVerifier.new(newConfig, newConfig.bbChonkVerifyMaxBatch, 'peer');
1624
+ const rpcVerifier = await BBCircuitVerifier.new(newConfig);
1625
+ this.rpcProofVerifier = new QueuedIVCVerifier(rpcVerifier, newConfig.numConcurrentIVCVerifiers);
1626
+ } else {
1627
+ this.peerProofVerifier = new TestCircuitVerifier();
1628
+ this.rpcProofVerifier = new TestCircuitVerifier();
1629
+ }
1368
1630
  }
1369
1631
 
1370
1632
  this.config = newConfig;
@@ -1439,7 +1701,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1439
1701
  return Promise.resolve();
1440
1702
  }
1441
1703
 
1442
- public async rollbackTo(targetBlock: BlockNumber, force?: boolean): Promise<void> {
1704
+ public async rollbackTo(targetBlock: BlockNumber, force?: boolean, resumeSync = true): Promise<void> {
1443
1705
  const archiver = this.blockSource as Archiver;
1444
1706
  if (!('rollbackTo' in archiver)) {
1445
1707
  throw new Error('Archiver implementation does not support rollbacks.');
@@ -1469,9 +1731,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1469
1731
  this.log.error(`Error during rollback`, err);
1470
1732
  throw err;
1471
1733
  } finally {
1472
- this.log.info(`Resuming world state and archiver sync.`);
1473
- this.worldStateSynchronizer.resumeSync();
1474
- 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
+ }
1475
1741
  }
1476
1742
  }
1477
1743
 
@@ -1488,19 +1754,12 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1488
1754
  return Promise.resolve();
1489
1755
  }
1490
1756
 
1491
- public getSlashPayloads(): Promise<SlashPayloadRound[]> {
1492
- if (!this.slasherClient) {
1493
- throw new Error(`Slasher client not enabled`);
1494
- }
1495
- return this.slasherClient.getSlashPayloads();
1496
- }
1497
-
1498
1757
  public getSlashOffenses(round: bigint | 'all' | 'current'): Promise<Offense[]> {
1499
1758
  if (!this.slasherClient) {
1500
1759
  throw new Error(`Slasher client not enabled`);
1501
1760
  }
1502
1761
  if (round === 'all') {
1503
- return this.slasherClient.getPendingOffenses();
1762
+ return this.slasherClient.getOffenses();
1504
1763
  } else {
1505
1764
  return this.slasherClient.gatherOffensesForRound(round === 'current' ? undefined : BigInt(round));
1506
1765
  }
@@ -1594,6 +1853,40 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1594
1853
  this.log.info('Keystore reloaded: coinbase, feeRecipient, and attester keys updated');
1595
1854
  }
1596
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
+
1597
1890
  #getInitialHeaderHash(): Promise<BlockHash> {
1598
1891
  if (!this.initialHeaderHashPromise) {
1599
1892
  this.initialHeaderHashPromise = this.worldStateSynchronizer.getCommitted().getInitialHeader().hash();
@@ -1625,25 +1918,28 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1625
1918
  if (BlockHash.isBlockHash(block)) {
1626
1919
  const initialBlockHash = await this.#getInitialHeaderHash();
1627
1920
  if (block.equals(initialBlockHash)) {
1628
- // Block source doesn't handle initial header so we need to handle the case separately.
1629
- return this.worldStateSynchronizer.getSnapshot(BlockNumber.ZERO);
1630
- }
1631
-
1632
- const header = await this.blockSource.getBlockHeaderByHash(block);
1633
- if (!header) {
1634
- throw new Error(
1635
- `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.`,
1636
- );
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();
1637
1933
  }
1638
-
1639
- blockNumber = header.getBlockNumber();
1640
1934
  } else {
1641
1935
  blockNumber = block as BlockNumber;
1642
1936
  }
1643
1937
 
1644
1938
  // Check it's within world state sync range
1645
1939
  if (blockNumber > blockSyncedTo) {
1646
- 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
+ );
1647
1943
  }
1648
1944
  this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
1649
1945
 
@@ -1652,9 +1948,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1652
1948
  // Double-check world-state synced to the same block hash as was requested
1653
1949
  if (BlockHash.isBlockHash(block)) {
1654
1950
  const blockHash = await snapshot.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber));
1655
- if (!blockHash || !new BlockHash(blockHash).equals(block)) {
1951
+ if (!blockHash || !block.equals(blockHash)) {
1952
+ const initialBlockHash = await this.#getInitialHeaderHash();
1656
1953
  throw new Error(
1657
- `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.`,
1658
1955
  );
1659
1956
  }
1660
1957
  }
@@ -1662,23 +1959,31 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1662
1959
  return snapshot;
1663
1960
  }
1664
1961
 
1665
- /** Resolves a block parameter to a block number. */
1962
+ /** Resolves any {@link BlockParameter} variant to a concrete block number. */
1666
1963
  protected async resolveBlockNumber(block: BlockParameter): Promise<BlockNumber> {
1667
- if (block === 'latest') {
1668
- return BlockNumber(await this.blockSource.getBlockNumber());
1964
+ const resolved = await this.resolveBlockParameter(block);
1965
+ if (resolved.number !== undefined) {
1966
+ return resolved.number;
1669
1967
  }
1670
- if (BlockHash.isBlockHash(block)) {
1968
+ if (resolved.hash !== undefined) {
1671
1969
  const initialBlockHash = await this.#getInitialHeaderHash();
1672
- if (block.equals(initialBlockHash)) {
1970
+ if (resolved.hash.equals(initialBlockHash)) {
1673
1971
  return BlockNumber.ZERO;
1674
1972
  }
1675
- 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);
1676
1981
  if (!header) {
1677
- throw new Error(`Block hash ${block.toString()} not found.`);
1982
+ throw new Error(`Block with archive ${resolved.archive.toString()} not found.`);
1678
1983
  }
1679
1984
  return header.getBlockNumber();
1680
1985
  }
1681
- return block as BlockNumber;
1986
+ throw new BadRequestError(`Invalid BlockParameter: ${JSON.stringify(block)}`);
1682
1987
  }
1683
1988
 
1684
1989
  /**