@aztec/aztec-node 0.0.0-test.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,994 @@
1
+ import { createArchiver } from '@aztec/archiver';
2
+ import { BBCircuitVerifier, TestCircuitVerifier } from '@aztec/bb-prover';
3
+ import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob-sink/client';
4
+ import {
5
+ type ARCHIVE_HEIGHT,
6
+ INITIAL_L2_BLOCK_NUM,
7
+ type L1_TO_L2_MSG_TREE_HEIGHT,
8
+ type NOTE_HASH_TREE_HEIGHT,
9
+ type NULLIFIER_TREE_HEIGHT,
10
+ type PUBLIC_DATA_TREE_HEIGHT,
11
+ REGISTERER_CONTRACT_ADDRESS,
12
+ } from '@aztec/constants';
13
+ import { EpochCache } from '@aztec/epoch-cache';
14
+ import { type L1ContractAddresses, createEthereumChain } from '@aztec/ethereum';
15
+ import { compactArray } from '@aztec/foundation/collection';
16
+ import { EthAddress } from '@aztec/foundation/eth-address';
17
+ import { Fr } from '@aztec/foundation/fields';
18
+ import { type Logger, createLogger } from '@aztec/foundation/log';
19
+ import { DateProvider, Timer } from '@aztec/foundation/timer';
20
+ import { SiblingPath } from '@aztec/foundation/trees';
21
+ import type { AztecKVStore } from '@aztec/kv-store';
22
+ import { openTmpStore } from '@aztec/kv-store/lmdb';
23
+ import { SHA256Trunc, StandardTree, UnbalancedTree } from '@aztec/merkle-tree';
24
+ import { type P2P, createP2PClient } from '@aztec/p2p';
25
+ import { ProtocolContractAddress } from '@aztec/protocol-contracts';
26
+ import {
27
+ GlobalVariableBuilder,
28
+ SequencerClient,
29
+ type SequencerPublisher,
30
+ createSlasherClient,
31
+ createValidatorForAcceptingTxs,
32
+ getDefaultAllowedSetupFunctions,
33
+ } from '@aztec/sequencer-client';
34
+ import { PublicProcessorFactory } from '@aztec/simulator/server';
35
+ import { AztecAddress } from '@aztec/stdlib/aztec-address';
36
+ import type { InBlock, L2Block, L2BlockNumber, L2BlockSource, NullifierWithBlockSource } from '@aztec/stdlib/block';
37
+ import type {
38
+ ContractClassPublic,
39
+ ContractDataSource,
40
+ ContractInstanceWithAddress,
41
+ NodeInfo,
42
+ ProtocolContractAddresses,
43
+ } from '@aztec/stdlib/contract';
44
+ import type { GasFees } from '@aztec/stdlib/gas';
45
+ import { computePublicDataTreeLeafSlot, siloNullifier } from '@aztec/stdlib/hash';
46
+ import type { AztecNode, GetContractClassLogsResponse, GetPublicLogsResponse } from '@aztec/stdlib/interfaces/client';
47
+ import {
48
+ type ClientProtocolCircuitVerifier,
49
+ type L2LogsSource,
50
+ type ProverConfig,
51
+ type SequencerConfig,
52
+ type Service,
53
+ type WorldStateSyncStatus,
54
+ type WorldStateSynchronizer,
55
+ tryStop,
56
+ } from '@aztec/stdlib/interfaces/server';
57
+ import type { LogFilter, PrivateLog, TxScopedL2Log } from '@aztec/stdlib/logs';
58
+ import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
59
+ import { P2PClientType } from '@aztec/stdlib/p2p';
60
+ import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
61
+ import type { NullifierLeafPreimage, PublicDataTreeLeaf, PublicDataTreeLeafPreimage } from '@aztec/stdlib/trees';
62
+ import {
63
+ type BlockHeader,
64
+ PublicSimulationOutput,
65
+ Tx,
66
+ TxEffect,
67
+ type TxHash,
68
+ TxReceipt,
69
+ TxStatus,
70
+ type TxValidationResult,
71
+ } from '@aztec/stdlib/tx';
72
+ import {
73
+ Attributes,
74
+ type TelemetryClient,
75
+ type Traceable,
76
+ type Tracer,
77
+ getTelemetryClient,
78
+ trackSpan,
79
+ } from '@aztec/telemetry-client';
80
+ import { createValidatorClient } from '@aztec/validator-client';
81
+ import { createWorldStateSynchronizer } from '@aztec/world-state';
82
+
83
+ import { type AztecNodeConfig, getPackageVersion } from './config.js';
84
+ import { NodeMetrics } from './node_metrics.js';
85
+
86
+ /**
87
+ * The aztec node.
88
+ */
89
+ export class AztecNodeService implements AztecNode, Traceable {
90
+ private packageVersion: string;
91
+ private metrics: NodeMetrics;
92
+
93
+ public readonly tracer: Tracer;
94
+
95
+ constructor(
96
+ protected config: AztecNodeConfig,
97
+ protected readonly p2pClient: P2P,
98
+ protected readonly blockSource: L2BlockSource & Partial<Service>,
99
+ protected readonly logsSource: L2LogsSource,
100
+ protected readonly contractDataSource: ContractDataSource,
101
+ protected readonly l1ToL2MessageSource: L1ToL2MessageSource,
102
+ protected readonly nullifierSource: NullifierWithBlockSource,
103
+ protected readonly worldStateSynchronizer: WorldStateSynchronizer,
104
+ protected readonly sequencer: SequencerClient | undefined,
105
+ protected readonly l1ChainId: number,
106
+ protected readonly version: number,
107
+ protected readonly globalVariableBuilder: GlobalVariableBuilder,
108
+ private proofVerifier: ClientProtocolCircuitVerifier,
109
+ private telemetry: TelemetryClient = getTelemetryClient(),
110
+ private log = createLogger('node'),
111
+ ) {
112
+ this.packageVersion = getPackageVersion();
113
+ this.metrics = new NodeMetrics(telemetry, 'AztecNodeService');
114
+ this.tracer = telemetry.getTracer('AztecNodeService');
115
+
116
+ this.log.info(`Aztec Node version: ${this.packageVersion}`);
117
+ this.log.info(`Aztec Node started on chain 0x${l1ChainId.toString(16)}`, config.l1Contracts);
118
+ }
119
+
120
+ public async getWorldStateSyncStatus(): Promise<WorldStateSyncStatus> {
121
+ const status = await this.worldStateSynchronizer.status();
122
+ return status.syncSummary;
123
+ }
124
+
125
+ public getL2Tips() {
126
+ return this.blockSource.getL2Tips();
127
+ }
128
+
129
+ /**
130
+ * initializes the Aztec Node, wait for component to sync.
131
+ * @param config - The configuration to be used by the aztec node.
132
+ * @returns - A fully synced Aztec Node for use in development/testing.
133
+ */
134
+ public static async createAndSync(
135
+ config: AztecNodeConfig,
136
+ deps: {
137
+ telemetry?: TelemetryClient;
138
+ logger?: Logger;
139
+ publisher?: SequencerPublisher;
140
+ dateProvider?: DateProvider;
141
+ blobSinkClient?: BlobSinkClientInterface;
142
+ } = {},
143
+ options: {
144
+ prefilledPublicData?: PublicDataTreeLeaf[];
145
+ } = {},
146
+ ): Promise<AztecNodeService> {
147
+ const telemetry = deps.telemetry ?? getTelemetryClient();
148
+ const log = deps.logger ?? createLogger('node');
149
+ const dateProvider = deps.dateProvider ?? new DateProvider();
150
+ const blobSinkClient = deps.blobSinkClient ?? createBlobSinkClient(config);
151
+ const ethereumChain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
152
+ //validate that the actual chain id matches that specified in configuration
153
+ if (config.l1ChainId !== ethereumChain.chainInfo.id) {
154
+ throw new Error(
155
+ `RPC URL configured for chain id ${ethereumChain.chainInfo.id} but expected id ${config.l1ChainId}`,
156
+ );
157
+ }
158
+
159
+ const archiver = await createArchiver(config, blobSinkClient, { blockUntilSync: true }, telemetry);
160
+
161
+ // now create the merkle trees and the world state synchronizer
162
+ const worldStateSynchronizer = await createWorldStateSynchronizer(
163
+ config,
164
+ archiver,
165
+ options.prefilledPublicData,
166
+ telemetry,
167
+ );
168
+ const proofVerifier = config.realProofs ? await BBCircuitVerifier.new(config) : new TestCircuitVerifier();
169
+ if (!config.realProofs) {
170
+ log.warn(`Aztec node is accepting fake proofs`);
171
+ }
172
+
173
+ const epochCache = await EpochCache.create(config.l1Contracts.rollupAddress, config, { dateProvider });
174
+
175
+ // create the tx pool and the p2p client, which will need the l2 block source
176
+ const p2pClient = await createP2PClient(
177
+ P2PClientType.Full,
178
+ config,
179
+ archiver,
180
+ proofVerifier,
181
+ worldStateSynchronizer,
182
+ epochCache,
183
+ telemetry,
184
+ );
185
+
186
+ const slasherClient = createSlasherClient(config, archiver, telemetry);
187
+
188
+ // start both and wait for them to sync from the block source
189
+ await Promise.all([p2pClient.start(), worldStateSynchronizer.start(), slasherClient.start()]);
190
+ log.verbose(`All Aztec Node subsystems synced`);
191
+
192
+ const validatorClient = createValidatorClient(config, { p2pClient, telemetry, dateProvider, epochCache });
193
+
194
+ // now create the sequencer
195
+ const sequencer = config.disableValidator
196
+ ? undefined
197
+ : await SequencerClient.new(config, {
198
+ ...deps,
199
+ validatorClient,
200
+ p2pClient,
201
+ worldStateSynchronizer,
202
+ slasherClient,
203
+ contractDataSource: archiver,
204
+ l2BlockSource: archiver,
205
+ l1ToL2MessageSource: archiver,
206
+ telemetry,
207
+ dateProvider,
208
+ blobSinkClient,
209
+ });
210
+
211
+ return new AztecNodeService(
212
+ config,
213
+ p2pClient,
214
+ archiver,
215
+ archiver,
216
+ archiver,
217
+ archiver,
218
+ archiver,
219
+ worldStateSynchronizer,
220
+ sequencer,
221
+ ethereumChain.chainInfo.id,
222
+ config.version,
223
+ new GlobalVariableBuilder(config),
224
+ proofVerifier,
225
+ telemetry,
226
+ log,
227
+ );
228
+ }
229
+
230
+ /**
231
+ * Returns the sequencer client instance.
232
+ * @returns The sequencer client instance.
233
+ */
234
+ public getSequencer(): SequencerClient | undefined {
235
+ return this.sequencer;
236
+ }
237
+
238
+ public getBlockSource(): L2BlockSource {
239
+ return this.blockSource;
240
+ }
241
+
242
+ public getContractDataSource(): ContractDataSource {
243
+ return this.contractDataSource;
244
+ }
245
+
246
+ public getP2P(): P2P {
247
+ return this.p2pClient;
248
+ }
249
+
250
+ /**
251
+ * Method to return the currently deployed L1 contract addresses.
252
+ * @returns - The currently deployed L1 contract addresses.
253
+ */
254
+ public getL1ContractAddresses(): Promise<L1ContractAddresses> {
255
+ return Promise.resolve(this.config.l1Contracts);
256
+ }
257
+
258
+ public getEncodedEnr(): Promise<string | undefined> {
259
+ return Promise.resolve(this.p2pClient.getEnr()?.encodeTxt());
260
+ }
261
+
262
+ /**
263
+ * Method to determine if the node is ready to accept transactions.
264
+ * @returns - Flag indicating the readiness for tx submission.
265
+ */
266
+ public isReady() {
267
+ return Promise.resolve(this.p2pClient.isReady() ?? false);
268
+ }
269
+
270
+ public async getNodeInfo(): Promise<NodeInfo> {
271
+ const [nodeVersion, protocolVersion, chainId, enr, contractAddresses, protocolContractAddresses] =
272
+ await Promise.all([
273
+ this.getNodeVersion(),
274
+ this.getVersion(),
275
+ this.getChainId(),
276
+ this.getEncodedEnr(),
277
+ this.getL1ContractAddresses(),
278
+ this.getProtocolContractAddresses(),
279
+ ]);
280
+
281
+ const nodeInfo: NodeInfo = {
282
+ nodeVersion,
283
+ l1ChainId: chainId,
284
+ protocolVersion,
285
+ enr,
286
+ l1ContractAddresses: contractAddresses,
287
+ protocolContractAddresses: protocolContractAddresses,
288
+ };
289
+
290
+ return nodeInfo;
291
+ }
292
+
293
+ /**
294
+ * Get a block specified by its number.
295
+ * @param number - The block number being requested.
296
+ * @returns The requested block.
297
+ */
298
+ public async getBlock(number: number): Promise<L2Block | undefined> {
299
+ return await this.blockSource.getBlock(number);
300
+ }
301
+
302
+ /**
303
+ * Method to request blocks. Will attempt to return all requested blocks but will return only those available.
304
+ * @param from - The start of the range of blocks to return.
305
+ * @param limit - The maximum number of blocks to obtain.
306
+ * @returns The blocks requested.
307
+ */
308
+ public async getBlocks(from: number, limit: number): Promise<L2Block[]> {
309
+ return (await this.blockSource.getBlocks(from, limit)) ?? [];
310
+ }
311
+
312
+ /**
313
+ * Method to fetch the current base fees.
314
+ * @returns The current base fees.
315
+ */
316
+ public async getCurrentBaseFees(): Promise<GasFees> {
317
+ return await this.globalVariableBuilder.getCurrentBaseFees();
318
+ }
319
+
320
+ /**
321
+ * Method to fetch the current block number.
322
+ * @returns The block number.
323
+ */
324
+ public async getBlockNumber(): Promise<number> {
325
+ return await this.blockSource.getBlockNumber();
326
+ }
327
+
328
+ public async getProvenBlockNumber(): Promise<number> {
329
+ return await this.blockSource.getProvenBlockNumber();
330
+ }
331
+
332
+ /**
333
+ * Method to fetch the version of the package.
334
+ * @returns The node package version
335
+ */
336
+ public getNodeVersion(): Promise<string> {
337
+ return Promise.resolve(this.packageVersion);
338
+ }
339
+
340
+ /**
341
+ * Method to fetch the version of the rollup the node is connected to.
342
+ * @returns The rollup version.
343
+ */
344
+ public getVersion(): Promise<number> {
345
+ return Promise.resolve(this.version);
346
+ }
347
+
348
+ /**
349
+ * Method to fetch the chain id of the base-layer for the rollup.
350
+ * @returns The chain id.
351
+ */
352
+ public getChainId(): Promise<number> {
353
+ return Promise.resolve(this.l1ChainId);
354
+ }
355
+
356
+ public async getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
357
+ const klazz = await this.contractDataSource.getContractClass(id);
358
+
359
+ // TODO(#10007): Remove this check. This is needed only because we're manually registering
360
+ // some contracts in the archiver so they are available to all nodes (see `registerCommonContracts`
361
+ // in `archiver/src/factory.ts`), but we still want clients to send the registration tx in order
362
+ // to emit the corresponding nullifier, which is now being checked. Note that this method
363
+ // is only called by the PXE to check if a contract is publicly registered.
364
+ if (klazz) {
365
+ const classNullifier = await siloNullifier(AztecAddress.fromNumber(REGISTERER_CONTRACT_ADDRESS), id);
366
+ const worldState = await this.#getWorldState('latest');
367
+ const [index] = await worldState.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [classNullifier.toBuffer()]);
368
+ this.log.debug(`Registration nullifier ${classNullifier} for contract class ${id} found at index ${index}`);
369
+ if (index === undefined) {
370
+ return undefined;
371
+ }
372
+ }
373
+
374
+ return klazz;
375
+ }
376
+
377
+ public getContract(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
378
+ return this.contractDataSource.getContract(address);
379
+ }
380
+
381
+ /**
382
+ * Retrieves all private logs from up to `limit` blocks, starting from the block number `from`.
383
+ * @param from - The block number from which to begin retrieving logs.
384
+ * @param limit - The maximum number of blocks to retrieve logs from.
385
+ * @returns An array of private logs from the specified range of blocks.
386
+ */
387
+ public getPrivateLogs(from: number, limit: number): Promise<PrivateLog[]> {
388
+ return this.logsSource.getPrivateLogs(from, limit);
389
+ }
390
+
391
+ /**
392
+ * Gets all logs that match any of the received tags (i.e. logs with their first field equal to a tag).
393
+ * @param tags - The tags to filter the logs by.
394
+ * @returns For each received tag, an array of matching logs is returned. An empty array implies no logs match
395
+ * that tag.
396
+ */
397
+ public getLogsByTags(tags: Fr[]): Promise<TxScopedL2Log[][]> {
398
+ return this.logsSource.getLogsByTags(tags);
399
+ }
400
+
401
+ /**
402
+ * Gets public logs based on the provided filter.
403
+ * @param filter - The filter to apply to the logs.
404
+ * @returns The requested logs.
405
+ */
406
+ getPublicLogs(filter: LogFilter): Promise<GetPublicLogsResponse> {
407
+ return this.logsSource.getPublicLogs(filter);
408
+ }
409
+
410
+ /**
411
+ * Gets contract class logs based on the provided filter.
412
+ * @param filter - The filter to apply to the logs.
413
+ * @returns The requested logs.
414
+ */
415
+ getContractClassLogs(filter: LogFilter): Promise<GetContractClassLogsResponse> {
416
+ return this.logsSource.getContractClassLogs(filter);
417
+ }
418
+
419
+ /**
420
+ * Method to submit a transaction to the p2p pool.
421
+ * @param tx - The transaction to be submitted.
422
+ */
423
+ public async sendTx(tx: Tx) {
424
+ const timer = new Timer();
425
+ const txHash = (await tx.getTxHash()).toString();
426
+
427
+ const valid = await this.isValidTx(tx);
428
+ if (valid.result !== 'valid') {
429
+ const reason = valid.reason.join(', ');
430
+ this.metrics.receivedTx(timer.ms(), false);
431
+ this.log.warn(`Invalid tx ${txHash}: ${reason}`, { txHash });
432
+ // TODO(#10967): Throw when receiving an invalid tx instead of just returning
433
+ // throw new Error(`Invalid tx: ${reason}`);
434
+ return;
435
+ }
436
+
437
+ await this.p2pClient!.sendTx(tx);
438
+ this.metrics.receivedTx(timer.ms(), true);
439
+ this.log.info(`Received tx ${txHash}`, { txHash });
440
+ }
441
+
442
+ public async getTxReceipt(txHash: TxHash): Promise<TxReceipt> {
443
+ let txReceipt = new TxReceipt(txHash, TxStatus.DROPPED, 'Tx dropped by P2P node.');
444
+
445
+ // We first check if the tx is in pending (instead of first checking if it is mined) because if we first check
446
+ // for mined and then for pending there could be a race condition where the tx is mined between the two checks
447
+ // and we would incorrectly return a TxReceipt with status DROPPED
448
+ if ((await this.p2pClient.getTxStatus(txHash)) === 'pending') {
449
+ txReceipt = new TxReceipt(txHash, TxStatus.PENDING, '');
450
+ }
451
+
452
+ const settledTxReceipt = await this.blockSource.getSettledTxReceipt(txHash);
453
+ if (settledTxReceipt) {
454
+ txReceipt = settledTxReceipt;
455
+ }
456
+
457
+ return txReceipt;
458
+ }
459
+
460
+ public getTxEffect(txHash: TxHash): Promise<InBlock<TxEffect> | undefined> {
461
+ return this.blockSource.getTxEffect(txHash);
462
+ }
463
+
464
+ /**
465
+ * Method to stop the aztec node.
466
+ */
467
+ public async stop() {
468
+ this.log.info(`Stopping`);
469
+ await this.sequencer?.stop();
470
+ await this.p2pClient.stop();
471
+ await this.worldStateSynchronizer.stop();
472
+ await tryStop(this.blockSource);
473
+ await this.telemetry.stop();
474
+ this.log.info(`Stopped`);
475
+ }
476
+
477
+ /**
478
+ * Method to retrieve pending txs.
479
+ * @returns - The pending txs.
480
+ */
481
+ public getPendingTxs() {
482
+ return this.p2pClient!.getPendingTxs();
483
+ }
484
+
485
+ public async getPendingTxCount() {
486
+ const pendingTxs = await this.getPendingTxs();
487
+ return pendingTxs.length;
488
+ }
489
+
490
+ /**
491
+ * Method to retrieve a single tx from the mempool or unfinalised chain.
492
+ * @param txHash - The transaction hash to return.
493
+ * @returns - The tx if it exists.
494
+ */
495
+ public getTxByHash(txHash: TxHash) {
496
+ return Promise.resolve(this.p2pClient!.getTxByHashFromPool(txHash));
497
+ }
498
+
499
+ /**
500
+ * Method to retrieve txs from the mempool or unfinalised chain.
501
+ * @param txHash - The transaction hash to return.
502
+ * @returns - The txs if it exists.
503
+ */
504
+ public async getTxsByHash(txHashes: TxHash[]) {
505
+ return compactArray(await Promise.all(txHashes.map(txHash => this.getTxByHash(txHash))));
506
+ }
507
+
508
+ /**
509
+ * Find the indexes of the given leaves in the given tree.
510
+ * @param blockNumber - The block number at which to get the data or 'latest' for latest data
511
+ * @param treeId - The tree to search in.
512
+ * @param leafValue - The values to search for
513
+ * @returns The indexes of the given leaves in the given tree or undefined if not found.
514
+ */
515
+ public async findLeavesIndexes(
516
+ blockNumber: L2BlockNumber,
517
+ treeId: MerkleTreeId,
518
+ leafValues: Fr[],
519
+ ): Promise<(bigint | undefined)[]> {
520
+ const committedDb = await this.#getWorldState(blockNumber);
521
+ return await committedDb.findLeafIndices(
522
+ treeId,
523
+ leafValues.map(x => x.toBuffer()),
524
+ );
525
+ }
526
+
527
+ /**
528
+ * Find the block numbers of the given leaf indices in the given tree.
529
+ * @param blockNumber - The block number at which to get the data or 'latest' for latest data
530
+ * @param treeId - The tree to search in.
531
+ * @param leafIndices - The values to search for
532
+ * @returns The indexes of the given leaves in the given tree or undefined if not found.
533
+ */
534
+ public async findBlockNumbersForIndexes(
535
+ blockNumber: L2BlockNumber,
536
+ treeId: MerkleTreeId,
537
+ leafIndices: bigint[],
538
+ ): Promise<(bigint | undefined)[]> {
539
+ const committedDb = await this.#getWorldState(blockNumber);
540
+ return await committedDb.getBlockNumbersForLeafIndices(treeId, leafIndices);
541
+ }
542
+
543
+ public async findNullifiersIndexesWithBlock(
544
+ blockNumber: L2BlockNumber,
545
+ nullifiers: Fr[],
546
+ ): Promise<(InBlock<bigint> | undefined)[]> {
547
+ if (blockNumber === 'latest') {
548
+ blockNumber = await this.getBlockNumber();
549
+ }
550
+ return this.nullifierSource.findNullifiersIndexesWithBlock(blockNumber, nullifiers);
551
+ }
552
+
553
+ /**
554
+ * Returns a sibling path for the given index in the nullifier tree.
555
+ * @param blockNumber - The block number at which to get the data.
556
+ * @param leafIndex - The index of the leaf for which the sibling path is required.
557
+ * @returns The sibling path for the leaf index.
558
+ */
559
+ public async getNullifierSiblingPath(
560
+ blockNumber: L2BlockNumber,
561
+ leafIndex: bigint,
562
+ ): Promise<SiblingPath<typeof NULLIFIER_TREE_HEIGHT>> {
563
+ const committedDb = await this.#getWorldState(blockNumber);
564
+ return committedDb.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, leafIndex);
565
+ }
566
+
567
+ /**
568
+ * Returns a sibling path for the given index in the data tree.
569
+ * @param blockNumber - The block number at which to get the data.
570
+ * @param leafIndex - The index of the leaf for which the sibling path is required.
571
+ * @returns The sibling path for the leaf index.
572
+ */
573
+ public async getNoteHashSiblingPath(
574
+ blockNumber: L2BlockNumber,
575
+ leafIndex: bigint,
576
+ ): Promise<SiblingPath<typeof NOTE_HASH_TREE_HEIGHT>> {
577
+ const committedDb = await this.#getWorldState(blockNumber);
578
+ return committedDb.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex);
579
+ }
580
+
581
+ /**
582
+ * Returns the index and a sibling path for a leaf in the committed l1 to l2 data tree.
583
+ * @param blockNumber - The block number at which to get the data.
584
+ * @param l1ToL2Message - The l1ToL2Message to get the index / sibling path for.
585
+ * @returns A tuple of the index and the sibling path of the L1ToL2Message (undefined if not found).
586
+ */
587
+ public async getL1ToL2MessageMembershipWitness(
588
+ blockNumber: L2BlockNumber,
589
+ l1ToL2Message: Fr,
590
+ ): Promise<[bigint, SiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>] | undefined> {
591
+ const index = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
592
+ if (index === undefined) {
593
+ return undefined;
594
+ }
595
+ const committedDb = await this.#getWorldState(blockNumber);
596
+ const siblingPath = await committedDb.getSiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>(
597
+ MerkleTreeId.L1_TO_L2_MESSAGE_TREE,
598
+ index,
599
+ );
600
+ return [index, siblingPath];
601
+ }
602
+
603
+ /**
604
+ * Returns whether an L1 to L2 message is synced by archiver and if it's ready to be included in a block.
605
+ * @param l1ToL2Message - The L1 to L2 message to check.
606
+ * @returns Whether the message is synced and ready to be included in a block.
607
+ */
608
+ public async isL1ToL2MessageSynced(l1ToL2Message: Fr): Promise<boolean> {
609
+ return (await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message)) !== undefined;
610
+ }
611
+
612
+ /**
613
+ * Returns the index of a l2ToL1Message in a ephemeral l2 to l1 data tree as well as its sibling path.
614
+ * @remarks This tree is considered ephemeral because it is created on-demand by: taking all the l2ToL1 messages
615
+ * in a single block, and then using them to make a variable depth append-only tree with these messages as leaves.
616
+ * The tree is discarded immediately after calculating what we need from it.
617
+ * TODO: Handle the case where two messages in the same tx have the same hash.
618
+ * @param blockNumber - The block number at which to get the data.
619
+ * @param l2ToL1Message - The l2ToL1Message get the index / sibling path for.
620
+ * @returns A tuple of the index and the sibling path of the L2ToL1Message.
621
+ */
622
+ public async getL2ToL1MessageMembershipWitness(
623
+ blockNumber: L2BlockNumber,
624
+ l2ToL1Message: Fr,
625
+ ): Promise<[bigint, SiblingPath<number>]> {
626
+ const block = await this.blockSource.getBlock(blockNumber === 'latest' ? await this.getBlockNumber() : blockNumber);
627
+
628
+ if (block === undefined) {
629
+ throw new Error('Block is not defined');
630
+ }
631
+
632
+ const l2ToL1Messages = block.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs);
633
+
634
+ // Find index of message
635
+ let indexOfMsgInSubtree = -1;
636
+ const indexOfMsgTx = l2ToL1Messages.findIndex(msgs => {
637
+ const idx = msgs.findIndex(msg => msg.equals(l2ToL1Message));
638
+ indexOfMsgInSubtree = Math.max(indexOfMsgInSubtree, idx);
639
+ return idx !== -1;
640
+ });
641
+
642
+ if (indexOfMsgTx === -1) {
643
+ throw new Error('The L2ToL1Message you are trying to prove inclusion of does not exist');
644
+ }
645
+
646
+ const tempStores: AztecKVStore[] = [];
647
+
648
+ // Construct message subtrees
649
+ const l2toL1Subtrees = await Promise.all(
650
+ l2ToL1Messages.map(async (msgs, i) => {
651
+ const store = openTmpStore(true);
652
+ tempStores.push(store);
653
+ const treeHeight = msgs.length <= 1 ? 1 : Math.ceil(Math.log2(msgs.length));
654
+ const tree = new StandardTree(store, new SHA256Trunc(), `temp_msgs_subtrees_${i}`, treeHeight, 0n, Fr);
655
+ await tree.appendLeaves(msgs);
656
+ return tree;
657
+ }),
658
+ );
659
+
660
+ // path of the input msg from leaf -> first out hash calculated in base rolllup
661
+ const subtreePathOfL2ToL1Message = await l2toL1Subtrees[indexOfMsgTx].getSiblingPath(
662
+ BigInt(indexOfMsgInSubtree),
663
+ true,
664
+ );
665
+
666
+ const numTxs = block.body.txEffects.length;
667
+ if (numTxs === 1) {
668
+ return [BigInt(indexOfMsgInSubtree), subtreePathOfL2ToL1Message];
669
+ }
670
+
671
+ const l2toL1SubtreeRoots = l2toL1Subtrees.map(t => Fr.fromBuffer(t.getRoot(true)));
672
+ const maxTreeHeight = Math.ceil(Math.log2(l2toL1SubtreeRoots.length));
673
+ // The root of this tree is the out_hash calculated in Noir => we truncate to match Noir's SHA
674
+ const outHashTree = new UnbalancedTree(new SHA256Trunc(), 'temp_outhash_sibling_path', maxTreeHeight, Fr);
675
+ await outHashTree.appendLeaves(l2toL1SubtreeRoots);
676
+
677
+ const pathOfTxInOutHashTree = await outHashTree.getSiblingPath(l2toL1SubtreeRoots[indexOfMsgTx].toBigInt());
678
+ // Append subtree path to out hash tree path
679
+ const mergedPath = subtreePathOfL2ToL1Message.toBufferArray().concat(pathOfTxInOutHashTree.toBufferArray());
680
+ // Append binary index of subtree path to binary index of out hash tree path
681
+ const mergedIndex = parseInt(
682
+ indexOfMsgTx
683
+ .toString(2)
684
+ .concat(indexOfMsgInSubtree.toString(2).padStart(l2toL1Subtrees[indexOfMsgTx].getDepth(), '0')),
685
+ 2,
686
+ );
687
+
688
+ // clear the tmp stores
689
+ await Promise.all(tempStores.map(store => store.delete()));
690
+
691
+ return [BigInt(mergedIndex), new SiblingPath(mergedPath.length, mergedPath)];
692
+ }
693
+
694
+ /**
695
+ * Returns a sibling path for a leaf in the committed blocks tree.
696
+ * @param blockNumber - The block number at which to get the data.
697
+ * @param leafIndex - Index of the leaf in the tree.
698
+ * @returns The sibling path.
699
+ */
700
+ public async getArchiveSiblingPath(
701
+ blockNumber: L2BlockNumber,
702
+ leafIndex: bigint,
703
+ ): Promise<SiblingPath<typeof ARCHIVE_HEIGHT>> {
704
+ const committedDb = await this.#getWorldState(blockNumber);
705
+ return committedDb.getSiblingPath(MerkleTreeId.ARCHIVE, leafIndex);
706
+ }
707
+
708
+ /**
709
+ * Returns a sibling path for a leaf in the committed public data tree.
710
+ * @param blockNumber - The block number at which to get the data.
711
+ * @param leafIndex - Index of the leaf in the tree.
712
+ * @returns The sibling path.
713
+ */
714
+ public async getPublicDataSiblingPath(
715
+ blockNumber: L2BlockNumber,
716
+ leafIndex: bigint,
717
+ ): Promise<SiblingPath<typeof PUBLIC_DATA_TREE_HEIGHT>> {
718
+ const committedDb = await this.#getWorldState(blockNumber);
719
+ return committedDb.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, leafIndex);
720
+ }
721
+
722
+ /**
723
+ * Returns a nullifier membership witness for a given nullifier at a given block.
724
+ * @param blockNumber - The block number at which to get the index.
725
+ * @param nullifier - Nullifier we try to find witness for.
726
+ * @returns The nullifier membership witness (if found).
727
+ */
728
+ public async getNullifierMembershipWitness(
729
+ blockNumber: L2BlockNumber,
730
+ nullifier: Fr,
731
+ ): Promise<NullifierMembershipWitness | undefined> {
732
+ const db = await this.#getWorldState(blockNumber);
733
+ const index = (await db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]))[0];
734
+ if (!index) {
735
+ return undefined;
736
+ }
737
+
738
+ const leafPreimagePromise = db.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index);
739
+ const siblingPathPromise = db.getSiblingPath<typeof NULLIFIER_TREE_HEIGHT>(
740
+ MerkleTreeId.NULLIFIER_TREE,
741
+ BigInt(index),
742
+ );
743
+
744
+ const [leafPreimage, siblingPath] = await Promise.all([leafPreimagePromise, siblingPathPromise]);
745
+
746
+ if (!leafPreimage) {
747
+ return undefined;
748
+ }
749
+
750
+ return new NullifierMembershipWitness(BigInt(index), leafPreimage as NullifierLeafPreimage, siblingPath);
751
+ }
752
+
753
+ /**
754
+ * Returns a low nullifier membership witness for a given nullifier at a given block.
755
+ * @param blockNumber - The block number at which to get the index.
756
+ * @param nullifier - Nullifier we try to find the low nullifier witness for.
757
+ * @returns The low nullifier membership witness (if found).
758
+ * @remarks Low nullifier witness can be used to perform a nullifier non-inclusion proof by leveraging the "linked
759
+ * list structure" of leaves and proving that a lower nullifier is pointing to a bigger next value than the nullifier
760
+ * we are trying to prove non-inclusion for.
761
+ *
762
+ * Note: This function returns the membership witness of the nullifier itself and not the low nullifier when
763
+ * the nullifier already exists in the tree. This is because the `getPreviousValueIndex` function returns the
764
+ * index of the nullifier itself when it already exists in the tree.
765
+ * TODO: This is a confusing behavior and we should eventually address that.
766
+ */
767
+ public async getLowNullifierMembershipWitness(
768
+ blockNumber: L2BlockNumber,
769
+ nullifier: Fr,
770
+ ): Promise<NullifierMembershipWitness | undefined> {
771
+ const committedDb = await this.#getWorldState(blockNumber);
772
+ const findResult = await committedDb.getPreviousValueIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBigInt());
773
+ if (!findResult) {
774
+ return undefined;
775
+ }
776
+ const { index, alreadyPresent } = findResult;
777
+ if (alreadyPresent) {
778
+ this.log.warn(`Nullifier ${nullifier.toBigInt()} already exists in the tree`);
779
+ }
780
+ const preimageData = (await committedDb.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index))!;
781
+
782
+ const siblingPath = await committedDb.getSiblingPath<typeof NULLIFIER_TREE_HEIGHT>(
783
+ MerkleTreeId.NULLIFIER_TREE,
784
+ BigInt(index),
785
+ );
786
+ return new NullifierMembershipWitness(BigInt(index), preimageData as NullifierLeafPreimage, siblingPath);
787
+ }
788
+
789
+ async getPublicDataTreeWitness(blockNumber: L2BlockNumber, leafSlot: Fr): Promise<PublicDataWitness | undefined> {
790
+ const committedDb = await this.#getWorldState(blockNumber);
791
+ const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
792
+ if (!lowLeafResult) {
793
+ return undefined;
794
+ } else {
795
+ const preimage = (await committedDb.getLeafPreimage(
796
+ MerkleTreeId.PUBLIC_DATA_TREE,
797
+ lowLeafResult.index,
798
+ )) as PublicDataTreeLeafPreimage;
799
+ const path = await committedDb.getSiblingPath<typeof PUBLIC_DATA_TREE_HEIGHT>(
800
+ MerkleTreeId.PUBLIC_DATA_TREE,
801
+ lowLeafResult.index,
802
+ );
803
+ return new PublicDataWitness(lowLeafResult.index, preimage, path);
804
+ }
805
+ }
806
+
807
+ /**
808
+ * Gets the storage value at the given contract storage slot.
809
+ *
810
+ * @remarks The storage slot here refers to the slot as it is defined in Noir not the index in the merkle tree.
811
+ * Aztec's version of `eth_getStorageAt`.
812
+ *
813
+ * @param contract - Address of the contract to query.
814
+ * @param slot - Slot to query.
815
+ * @param blockNumber - The block number at which to get the data or 'latest'.
816
+ * @returns Storage value at the given contract slot.
817
+ */
818
+ public async getPublicStorageAt(blockNumber: L2BlockNumber, contract: AztecAddress, slot: Fr): Promise<Fr> {
819
+ const committedDb = await this.#getWorldState(blockNumber);
820
+ const leafSlot = await computePublicDataTreeLeafSlot(contract, slot);
821
+
822
+ const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
823
+ if (!lowLeafResult || !lowLeafResult.alreadyPresent) {
824
+ return Fr.ZERO;
825
+ }
826
+ const preimage = (await committedDb.getLeafPreimage(
827
+ MerkleTreeId.PUBLIC_DATA_TREE,
828
+ lowLeafResult.index,
829
+ )) as PublicDataTreeLeafPreimage;
830
+ return preimage.value;
831
+ }
832
+
833
+ /**
834
+ * Returns the currently committed block header, or the initial header if no blocks have been produced.
835
+ * @returns The current committed block header.
836
+ */
837
+ public async getBlockHeader(blockNumber: L2BlockNumber = 'latest'): Promise<BlockHeader | undefined> {
838
+ return blockNumber === 0 || (blockNumber === 'latest' && (await this.blockSource.getBlockNumber()) === 0)
839
+ ? this.worldStateSynchronizer.getCommitted().getInitialHeader()
840
+ : this.blockSource.getBlockHeader(blockNumber);
841
+ }
842
+
843
+ /**
844
+ * Simulates the public part of a transaction with the current state.
845
+ * @param tx - The transaction to simulate.
846
+ **/
847
+ @trackSpan('AztecNodeService.simulatePublicCalls', async (tx: Tx) => ({
848
+ [Attributes.TX_HASH]: (await tx.getTxHash()).toString(),
849
+ }))
850
+ public async simulatePublicCalls(tx: Tx, skipFeeEnforcement = false): Promise<PublicSimulationOutput> {
851
+ const txHash = await tx.getTxHash();
852
+ const blockNumber = (await this.blockSource.getBlockNumber()) + 1;
853
+
854
+ // If sequencer is not initialized, we just set these values to zero for simulation.
855
+ const coinbase = this.sequencer?.coinbase || EthAddress.ZERO;
856
+ const feeRecipient = this.sequencer?.feeRecipient || AztecAddress.ZERO;
857
+
858
+ const newGlobalVariables = await this.globalVariableBuilder.buildGlobalVariables(
859
+ new Fr(blockNumber),
860
+ coinbase,
861
+ feeRecipient,
862
+ );
863
+ const publicProcessorFactory = new PublicProcessorFactory(
864
+ this.contractDataSource,
865
+ new DateProvider(),
866
+ this.telemetry,
867
+ );
868
+ const fork = await this.worldStateSynchronizer.fork();
869
+
870
+ this.log.verbose(`Simulating public calls for tx ${txHash}`, {
871
+ globalVariables: newGlobalVariables.toInspect(),
872
+ txHash,
873
+ blockNumber,
874
+ });
875
+
876
+ try {
877
+ const processor = publicProcessorFactory.create(fork, newGlobalVariables, skipFeeEnforcement);
878
+
879
+ // REFACTOR: Consider merging ProcessReturnValues into ProcessedTx
880
+ const [processedTxs, failedTxs, returns] = await processor.process([tx]);
881
+ // REFACTOR: Consider returning the error rather than throwing
882
+ if (failedTxs.length) {
883
+ this.log.warn(`Simulated tx ${txHash} fails: ${failedTxs[0].error}`, { txHash });
884
+ throw failedTxs[0].error;
885
+ }
886
+
887
+ const [processedTx] = processedTxs;
888
+ return new PublicSimulationOutput(
889
+ processedTx.revertReason,
890
+ processedTx.constants,
891
+ processedTx.txEffect,
892
+ returns,
893
+ processedTx.gasUsed,
894
+ );
895
+ } finally {
896
+ await fork.close();
897
+ }
898
+ }
899
+
900
+ public async isValidTx(
901
+ tx: Tx,
902
+ { isSimulation, skipFeeEnforcement }: { isSimulation?: boolean; skipFeeEnforcement?: boolean } = {},
903
+ ): Promise<TxValidationResult> {
904
+ const blockNumber = (await this.blockSource.getBlockNumber()) + 1;
905
+ const db = this.worldStateSynchronizer.getCommitted();
906
+ const verifier = isSimulation ? undefined : this.proofVerifier;
907
+ const validator = createValidatorForAcceptingTxs(db, this.contractDataSource, verifier, {
908
+ blockNumber,
909
+ l1ChainId: this.l1ChainId,
910
+ setupAllowList: this.config.allowedInSetup ?? (await getDefaultAllowedSetupFunctions()),
911
+ gasFees: await this.getCurrentBaseFees(),
912
+ skipFeeEnforcement,
913
+ });
914
+
915
+ return await validator.validateTx(tx);
916
+ }
917
+
918
+ public async setConfig(config: Partial<SequencerConfig & ProverConfig>): Promise<void> {
919
+ const newConfig = { ...this.config, ...config };
920
+ await this.sequencer?.updateSequencerConfig(config);
921
+
922
+ if (newConfig.realProofs !== this.config.realProofs) {
923
+ this.proofVerifier = config.realProofs ? await BBCircuitVerifier.new(newConfig) : new TestCircuitVerifier();
924
+ }
925
+
926
+ this.config = newConfig;
927
+ }
928
+
929
+ public getProtocolContractAddresses(): Promise<ProtocolContractAddresses> {
930
+ return Promise.resolve({
931
+ classRegisterer: ProtocolContractAddress.ContractClassRegisterer,
932
+ feeJuice: ProtocolContractAddress.FeeJuice,
933
+ instanceDeployer: ProtocolContractAddress.ContractInstanceDeployer,
934
+ multiCallEntrypoint: ProtocolContractAddress.MultiCallEntrypoint,
935
+ });
936
+ }
937
+
938
+ // TODO(#10007): Remove this method
939
+ public addContractClass(contractClass: ContractClassPublic): Promise<void> {
940
+ this.log.info(`Adding contract class via API ${contractClass.id}`);
941
+ return this.contractDataSource.addContractClass(contractClass);
942
+ }
943
+
944
+ public registerContractFunctionSignatures(_address: AztecAddress, signatures: string[]): Promise<void> {
945
+ return this.contractDataSource.registerContractFunctionSignatures(_address, signatures);
946
+ }
947
+
948
+ public flushTxs(): Promise<void> {
949
+ if (!this.sequencer) {
950
+ throw new Error(`Sequencer is not initialized`);
951
+ }
952
+ this.sequencer.flush();
953
+ return Promise.resolve();
954
+ }
955
+
956
+ /**
957
+ * Returns an instance of MerkleTreeOperations having first ensured the world state is fully synched
958
+ * @param blockNumber - The block number at which to get the data.
959
+ * @returns An instance of a committed MerkleTreeOperations
960
+ */
961
+ async #getWorldState(blockNumber: L2BlockNumber) {
962
+ if (typeof blockNumber === 'number' && blockNumber < INITIAL_L2_BLOCK_NUM - 1) {
963
+ throw new Error('Invalid block number to get world state for: ' + blockNumber);
964
+ }
965
+
966
+ let blockSyncedTo: number = 0;
967
+ try {
968
+ // Attempt to sync the world state if necessary
969
+ blockSyncedTo = await this.#syncWorldState();
970
+ } catch (err) {
971
+ this.log.error(`Error getting world state: ${err}`);
972
+ }
973
+
974
+ // using a snapshot could be less efficient than using the committed db
975
+ if (blockNumber === 'latest' /*|| blockNumber === blockSyncedTo*/) {
976
+ this.log.debug(`Using committed db for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
977
+ return this.worldStateSynchronizer.getCommitted();
978
+ } else if (blockNumber <= blockSyncedTo) {
979
+ this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
980
+ return this.worldStateSynchronizer.getSnapshot(blockNumber);
981
+ } else {
982
+ throw new Error(`Block ${blockNumber} not yet synced`);
983
+ }
984
+ }
985
+
986
+ /**
987
+ * Ensure we fully sync the world state
988
+ * @returns A promise that fulfils once the world state is synced
989
+ */
990
+ async #syncWorldState(): Promise<number> {
991
+ const blockSourceHeight = await this.blockSource.getBlockNumber();
992
+ return this.worldStateSynchronizer.syncImmediate(blockSourceHeight);
993
+ }
994
+ }