@aztec/aztec-node 0.0.0-test.1 → 0.0.1-commit.1142ef1

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.
Files changed (48) hide show
  1. package/dest/aztec-node/config.d.ts +18 -10
  2. package/dest/aztec-node/config.d.ts.map +1 -1
  3. package/dest/aztec-node/config.js +81 -14
  4. package/dest/aztec-node/node_metrics.d.ts +5 -1
  5. package/dest/aztec-node/node_metrics.d.ts.map +1 -1
  6. package/dest/aztec-node/node_metrics.js +17 -7
  7. package/dest/aztec-node/server.d.ts +123 -86
  8. package/dest/aztec-node/server.d.ts.map +1 -1
  9. package/dest/aztec-node/server.js +994 -250
  10. package/dest/bin/index.d.ts +1 -1
  11. package/dest/bin/index.js +4 -2
  12. package/dest/index.d.ts +1 -2
  13. package/dest/index.d.ts.map +1 -1
  14. package/dest/index.js +0 -1
  15. package/dest/sentinel/config.d.ts +8 -0
  16. package/dest/sentinel/config.d.ts.map +1 -0
  17. package/dest/sentinel/config.js +29 -0
  18. package/dest/sentinel/factory.d.ts +9 -0
  19. package/dest/sentinel/factory.d.ts.map +1 -0
  20. package/dest/sentinel/factory.js +17 -0
  21. package/dest/sentinel/index.d.ts +3 -0
  22. package/dest/sentinel/index.d.ts.map +1 -0
  23. package/dest/sentinel/index.js +1 -0
  24. package/dest/sentinel/sentinel.d.ts +93 -0
  25. package/dest/sentinel/sentinel.d.ts.map +1 -0
  26. package/dest/sentinel/sentinel.js +403 -0
  27. package/dest/sentinel/store.d.ts +35 -0
  28. package/dest/sentinel/store.d.ts.map +1 -0
  29. package/dest/sentinel/store.js +170 -0
  30. package/dest/test/index.d.ts +31 -0
  31. package/dest/test/index.d.ts.map +1 -0
  32. package/dest/test/index.js +1 -0
  33. package/package.json +45 -35
  34. package/src/aztec-node/config.ts +132 -25
  35. package/src/aztec-node/node_metrics.ts +24 -14
  36. package/src/aztec-node/server.ts +809 -330
  37. package/src/bin/index.ts +4 -2
  38. package/src/index.ts +0 -1
  39. package/src/sentinel/config.ts +37 -0
  40. package/src/sentinel/factory.ts +36 -0
  41. package/src/sentinel/index.ts +8 -0
  42. package/src/sentinel/sentinel.ts +510 -0
  43. package/src/sentinel/store.ts +185 -0
  44. package/src/test/index.ts +32 -0
  45. package/dest/aztec-node/http_rpc_server.d.ts +0 -8
  46. package/dest/aztec-node/http_rpc_server.d.ts.map +0 -1
  47. package/dest/aztec-node/http_rpc_server.js +0 -9
  48. package/src/aztec-node/http_rpc_server.ts +0 -11
@@ -1,39 +1,57 @@
1
- import { createArchiver } from '@aztec/archiver';
2
- import { BBCircuitVerifier, TestCircuitVerifier } from '@aztec/bb-prover';
3
- import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob-sink/client';
1
+ import { Archiver, createArchiver } from '@aztec/archiver';
2
+ import { BBCircuitVerifier, QueuedIVCVerifier, TestCircuitVerifier } from '@aztec/bb-prover';
3
+ import { type BlobClientInterface, createBlobClientWithFileStores } from '@aztec/blob-client/client';
4
4
  import {
5
- type ARCHIVE_HEIGHT,
5
+ ARCHIVE_HEIGHT,
6
6
  INITIAL_L2_BLOCK_NUM,
7
7
  type L1_TO_L2_MSG_TREE_HEIGHT,
8
8
  type NOTE_HASH_TREE_HEIGHT,
9
9
  type NULLIFIER_TREE_HEIGHT,
10
10
  type PUBLIC_DATA_TREE_HEIGHT,
11
- REGISTERER_CONTRACT_ADDRESS,
12
11
  } 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';
12
+ import { EpochCache, type EpochCacheInterface } from '@aztec/epoch-cache';
13
+ import { createEthereumChain } from '@aztec/ethereum/chain';
14
+ import { getPublicClient } from '@aztec/ethereum/client';
15
+ import { RegistryContract, RollupContract } from '@aztec/ethereum/contracts';
16
+ import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
17
+ import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
18
+ import { compactArray, pick } from '@aztec/foundation/collection';
19
+ import { Fr } from '@aztec/foundation/curves/bn254';
16
20
  import { EthAddress } from '@aztec/foundation/eth-address';
17
- import { Fr } from '@aztec/foundation/fields';
21
+ import { BadRequestError } from '@aztec/foundation/json-rpc';
18
22
  import { type Logger, createLogger } from '@aztec/foundation/log';
23
+ import { count } from '@aztec/foundation/string';
19
24
  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';
25
+ import { MembershipWitness, SiblingPath } from '@aztec/foundation/trees';
26
+ import { KeystoreManager, loadKeystores, mergeKeystores } from '@aztec/node-keystore';
27
+ import { trySnapshotSync, uploadSnapshot } from '@aztec/node-lib/actions';
26
28
  import {
27
- GlobalVariableBuilder,
28
- SequencerClient,
29
- type SequencerPublisher,
30
- createSlasherClient,
31
- createValidatorForAcceptingTxs,
32
- getDefaultAllowedSetupFunctions,
33
- } from '@aztec/sequencer-client';
29
+ createForwarderL1TxUtilsFromEthSigner,
30
+ createL1TxUtilsWithBlobsFromEthSigner,
31
+ } from '@aztec/node-lib/factories';
32
+ import { type P2P, type P2PClientDeps, createP2PClient, getDefaultAllowedSetupFunctions } from '@aztec/p2p';
33
+ import { ProtocolContractAddress } from '@aztec/protocol-contracts';
34
+ import { BlockBuilder, GlobalVariableBuilder, SequencerClient, type SequencerPublisher } from '@aztec/sequencer-client';
34
35
  import { PublicProcessorFactory } from '@aztec/simulator/server';
36
+ import {
37
+ AttestationsBlockWatcher,
38
+ EpochPruneWatcher,
39
+ type SlasherClientInterface,
40
+ type Watcher,
41
+ createSlasher,
42
+ } from '@aztec/slasher';
43
+ import { CollectionLimitsConfig, PublicSimulatorConfig } from '@aztec/stdlib/avm';
35
44
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
36
- import type { InBlock, L2Block, L2BlockNumber, L2BlockSource, NullifierWithBlockSource } from '@aztec/stdlib/block';
45
+ import {
46
+ type BlockParameter,
47
+ type DataInBlock,
48
+ type L2Block,
49
+ L2BlockHash,
50
+ L2BlockNew,
51
+ type L2BlockSource,
52
+ type PublishedL2Block,
53
+ } from '@aztec/stdlib/block';
54
+ import type { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
37
55
  import type {
38
56
  ContractClassPublic,
39
57
  ContractDataSource,
@@ -41,34 +59,44 @@ import type {
41
59
  NodeInfo,
42
60
  ProtocolContractAddresses,
43
61
  } 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';
62
+ import { GasFees } from '@aztec/stdlib/gas';
63
+ import { computePublicDataTreeLeafSlot } from '@aztec/stdlib/hash';
64
+ import {
65
+ type AztecNode,
66
+ type AztecNodeAdmin,
67
+ type AztecNodeAdminConfig,
68
+ AztecNodeAdminConfigSchema,
69
+ type GetContractClassLogsResponse,
70
+ type GetPublicLogsResponse,
71
+ } from '@aztec/stdlib/interfaces/client';
47
72
  import {
73
+ type AllowedElement,
48
74
  type ClientProtocolCircuitVerifier,
49
75
  type L2LogsSource,
50
- type ProverConfig,
51
- type SequencerConfig,
52
76
  type Service,
53
77
  type WorldStateSyncStatus,
54
78
  type WorldStateSynchronizer,
55
79
  tryStop,
56
80
  } from '@aztec/stdlib/interfaces/server';
57
- import type { LogFilter, PrivateLog, TxScopedL2Log } from '@aztec/stdlib/logs';
58
- import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
81
+ import type { LogFilter, SiloedTag, Tag, TxScopedL2Log } from '@aztec/stdlib/logs';
82
+ import { InboxLeaf, type L1ToL2MessageSource } from '@aztec/stdlib/messaging';
59
83
  import { P2PClientType } from '@aztec/stdlib/p2p';
60
- import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
84
+ import type { Offense, SlashPayloadRound } from '@aztec/stdlib/slashing';
61
85
  import type { NullifierLeafPreimage, PublicDataTreeLeaf, PublicDataTreeLeafPreimage } from '@aztec/stdlib/trees';
86
+ import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
62
87
  import {
63
88
  type BlockHeader,
89
+ type GlobalVariableBuilder as GlobalVariableBuilderInterface,
90
+ type IndexedTxEffect,
64
91
  PublicSimulationOutput,
65
92
  Tx,
66
- TxEffect,
67
93
  type TxHash,
68
94
  TxReceipt,
69
95
  TxStatus,
70
96
  type TxValidationResult,
71
97
  } from '@aztec/stdlib/tx';
98
+ import { getPackageVersion } from '@aztec/stdlib/update-checker';
99
+ import type { SingleValidatorStats, ValidatorsStats } from '@aztec/stdlib/validators';
72
100
  import {
73
101
  Attributes,
74
102
  type TelemetryClient,
@@ -77,19 +105,33 @@ import {
77
105
  getTelemetryClient,
78
106
  trackSpan,
79
107
  } from '@aztec/telemetry-client';
80
- import { createValidatorClient } from '@aztec/validator-client';
108
+ import {
109
+ FullNodeCheckpointsBuilder as CheckpointsBuilder,
110
+ FullNodeCheckpointsBuilder,
111
+ NodeKeystoreAdapter,
112
+ ValidatorClient,
113
+ createBlockProposalHandler,
114
+ createValidatorClient,
115
+ createValidatorForAcceptingTxs,
116
+ } from '@aztec/validator-client';
81
117
  import { createWorldStateSynchronizer } from '@aztec/world-state';
82
118
 
83
- import { type AztecNodeConfig, getPackageVersion } from './config.js';
119
+ import { createPublicClient, fallback, http } from 'viem';
120
+
121
+ import { createSentinel } from '../sentinel/factory.js';
122
+ import { Sentinel } from '../sentinel/sentinel.js';
123
+ import { type AztecNodeConfig, createKeyStoreForValidator } from './config.js';
84
124
  import { NodeMetrics } from './node_metrics.js';
85
125
 
86
126
  /**
87
127
  * The aztec node.
88
128
  */
89
- export class AztecNodeService implements AztecNode, Traceable {
90
- private packageVersion: string;
129
+ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
91
130
  private metrics: NodeMetrics;
92
131
 
132
+ // Prevent two snapshot operations to happen simultaneously
133
+ private isUploadingSnapshot = false;
134
+
93
135
  public readonly tracer: Tracer;
94
136
 
95
137
  constructor(
@@ -99,17 +141,21 @@ export class AztecNodeService implements AztecNode, Traceable {
99
141
  protected readonly logsSource: L2LogsSource,
100
142
  protected readonly contractDataSource: ContractDataSource,
101
143
  protected readonly l1ToL2MessageSource: L1ToL2MessageSource,
102
- protected readonly nullifierSource: NullifierWithBlockSource,
103
144
  protected readonly worldStateSynchronizer: WorldStateSynchronizer,
104
145
  protected readonly sequencer: SequencerClient | undefined,
146
+ protected readonly slasherClient: SlasherClientInterface | undefined,
147
+ protected readonly validatorsSentinel: Sentinel | undefined,
148
+ protected readonly epochPruneWatcher: EpochPruneWatcher | undefined,
105
149
  protected readonly l1ChainId: number,
106
150
  protected readonly version: number,
107
- protected readonly globalVariableBuilder: GlobalVariableBuilder,
151
+ protected readonly globalVariableBuilder: GlobalVariableBuilderInterface,
152
+ protected readonly epochCache: EpochCacheInterface,
153
+ protected readonly packageVersion: string,
108
154
  private proofVerifier: ClientProtocolCircuitVerifier,
109
155
  private telemetry: TelemetryClient = getTelemetryClient(),
110
156
  private log = createLogger('node'),
157
+ private blobClient?: BlobClientInterface,
111
158
  ) {
112
- this.packageVersion = getPackageVersion();
113
159
  this.metrics = new NodeMetrics(telemetry, 'AztecNodeService');
114
160
  this.tracer = telemetry.getTracer('AztecNodeService');
115
161
 
@@ -132,31 +178,103 @@ export class AztecNodeService implements AztecNode, Traceable {
132
178
  * @returns - A fully synced Aztec Node for use in development/testing.
133
179
  */
134
180
  public static async createAndSync(
135
- config: AztecNodeConfig,
181
+ inputConfig: AztecNodeConfig,
136
182
  deps: {
137
183
  telemetry?: TelemetryClient;
138
184
  logger?: Logger;
139
185
  publisher?: SequencerPublisher;
140
186
  dateProvider?: DateProvider;
141
- blobSinkClient?: BlobSinkClientInterface;
187
+ p2pClientDeps?: P2PClientDeps<P2PClientType.Full>;
142
188
  } = {},
143
189
  options: {
144
190
  prefilledPublicData?: PublicDataTreeLeaf[];
191
+ dontStartSequencer?: boolean;
145
192
  } = {},
146
193
  ): Promise<AztecNodeService> {
147
- const telemetry = deps.telemetry ?? getTelemetryClient();
194
+ const config = { ...inputConfig }; // Copy the config so we dont mutate the input object
148
195
  const log = deps.logger ?? createLogger('node');
196
+ const packageVersion = getPackageVersion() ?? '';
197
+ const telemetry = deps.telemetry ?? getTelemetryClient();
149
198
  const dateProvider = deps.dateProvider ?? new DateProvider();
150
- const blobSinkClient = deps.blobSinkClient ?? createBlobSinkClient(config);
151
199
  const ethereumChain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
152
- //validate that the actual chain id matches that specified in configuration
200
+
201
+ // Build a key store from file if given or from environment otherwise
202
+ let keyStoreManager: KeystoreManager | undefined;
203
+ const keyStoreProvided = config.keyStoreDirectory !== undefined && config.keyStoreDirectory.length > 0;
204
+ if (keyStoreProvided) {
205
+ const keyStores = loadKeystores(config.keyStoreDirectory!);
206
+ keyStoreManager = new KeystoreManager(mergeKeystores(keyStores));
207
+ } else {
208
+ const keyStore = createKeyStoreForValidator(config);
209
+ if (keyStore) {
210
+ keyStoreManager = new KeystoreManager(keyStore);
211
+ }
212
+ }
213
+
214
+ await keyStoreManager?.validateSigners();
215
+
216
+ // If we are a validator, verify our configuration before doing too much more.
217
+ if (!config.disableValidator) {
218
+ if (keyStoreManager === undefined) {
219
+ throw new Error('Failed to create key store, a requirement for running a validator');
220
+ }
221
+ if (!keyStoreProvided) {
222
+ log.warn(
223
+ 'KEY STORE CREATED FROM ENVIRONMENT, IT IS RECOMMENDED TO USE A FILE-BASED KEY STORE IN PRODUCTION ENVIRONMENTS',
224
+ );
225
+ }
226
+ ValidatorClient.validateKeyStoreConfiguration(keyStoreManager, log);
227
+ }
228
+
229
+ // validate that the actual chain id matches that specified in configuration
153
230
  if (config.l1ChainId !== ethereumChain.chainInfo.id) {
154
231
  throw new Error(
155
232
  `RPC URL configured for chain id ${ethereumChain.chainInfo.id} but expected id ${config.l1ChainId}`,
156
233
  );
157
234
  }
158
235
 
159
- const archiver = await createArchiver(config, blobSinkClient, { blockUntilSync: true }, telemetry);
236
+ const publicClient = createPublicClient({
237
+ chain: ethereumChain.chainInfo,
238
+ transport: fallback(config.l1RpcUrls.map((url: string) => http(url, { batch: false }))),
239
+ pollingInterval: config.viemPollingIntervalMS,
240
+ });
241
+
242
+ const l1ContractsAddresses = await RegistryContract.collectAddresses(
243
+ publicClient,
244
+ config.l1Contracts.registryAddress,
245
+ config.rollupVersion ?? 'canonical',
246
+ );
247
+
248
+ // Overwrite the passed in vars.
249
+ config.l1Contracts = { ...config.l1Contracts, ...l1ContractsAddresses };
250
+
251
+ const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString());
252
+ const [l1GenesisTime, slotDuration, rollupVersionFromRollup] = await Promise.all([
253
+ rollupContract.getL1GenesisTime(),
254
+ rollupContract.getSlotDuration(),
255
+ rollupContract.getVersion(),
256
+ ] as const);
257
+
258
+ config.rollupVersion ??= Number(rollupVersionFromRollup);
259
+
260
+ if (config.rollupVersion !== Number(rollupVersionFromRollup)) {
261
+ log.warn(
262
+ `Registry looked up and returned a rollup with version (${config.rollupVersion}), but this does not match with version detected from the rollup directly: (${rollupVersionFromRollup}).`,
263
+ );
264
+ }
265
+
266
+ const blobClient = await createBlobClientWithFileStores(config, createLogger('node:blob-client:client'));
267
+
268
+ // attempt snapshot sync if possible
269
+ await trySnapshotSync(config, log);
270
+
271
+ const epochCache = await EpochCache.create(config.l1Contracts.rollupAddress, config, { dateProvider });
272
+
273
+ const archiver = await createArchiver(
274
+ config,
275
+ { blobClient, epochCache, telemetry, dateProvider },
276
+ { blockUntilSync: !config.skipArchiverInitialSync },
277
+ );
160
278
 
161
279
  // now create the merkle trees and the world state synchronizer
162
280
  const worldStateSynchronizer = await createWorldStateSynchronizer(
@@ -165,12 +283,14 @@ export class AztecNodeService implements AztecNode, Traceable {
165
283
  options.prefilledPublicData,
166
284
  telemetry,
167
285
  );
168
- const proofVerifier = config.realProofs ? await BBCircuitVerifier.new(config) : new TestCircuitVerifier();
286
+ const circuitVerifier =
287
+ config.realProofs || config.debugForceTxProofVerification
288
+ ? await BBCircuitVerifier.new(config)
289
+ : new TestCircuitVerifier(config.proverTestVerificationDelayMs);
169
290
  if (!config.realProofs) {
170
291
  log.warn(`Aztec node is accepting fake proofs`);
171
292
  }
172
-
173
- const epochCache = await EpochCache.create(config.l1Contracts.rollupAddress, config, { dateProvider });
293
+ const proofVerifier = new QueuedIVCVerifier(config, circuitVerifier);
174
294
 
175
295
  // create the tx pool and the p2p client, which will need the l2 block source
176
296
  const p2pClient = await createP2PClient(
@@ -180,33 +300,194 @@ export class AztecNodeService implements AztecNode, Traceable {
180
300
  proofVerifier,
181
301
  worldStateSynchronizer,
182
302
  epochCache,
303
+ packageVersion,
304
+ dateProvider,
183
305
  telemetry,
306
+ deps.p2pClientDeps,
184
307
  );
185
308
 
186
- const slasherClient = createSlasherClient(config, archiver, telemetry);
309
+ // We should really not be modifying the config object
310
+ config.txPublicSetupAllowList = config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions());
187
311
 
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`);
312
+ // Create BlockBuilder for EpochPruneWatcher (slasher functionality)
313
+ const blockBuilder = new BlockBuilder(
314
+ { ...config, l1GenesisTime, slotDuration: Number(slotDuration) },
315
+ worldStateSynchronizer,
316
+ archiver,
317
+ dateProvider,
318
+ telemetry,
319
+ );
191
320
 
192
- const validatorClient = createValidatorClient(config, { p2pClient, telemetry, dateProvider, epochCache });
321
+ // Create FullNodeCheckpointsBuilder for validator and non-validator block proposal handling
322
+ const validatorCheckpointsBuilder = new FullNodeCheckpointsBuilder(
323
+ { ...config, l1GenesisTime, slotDuration: Number(slotDuration) },
324
+ archiver,
325
+ dateProvider,
326
+ telemetry,
327
+ );
193
328
 
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
- });
329
+ // We'll accumulate sentinel watchers here
330
+ const watchers: Watcher[] = [];
331
+
332
+ // Create validator client if required
333
+ const validatorClient = createValidatorClient(config, {
334
+ checkpointsBuilder: validatorCheckpointsBuilder,
335
+ worldState: worldStateSynchronizer,
336
+ p2pClient,
337
+ telemetry,
338
+ dateProvider,
339
+ epochCache,
340
+ blockSource: archiver,
341
+ l1ToL2MessageSource: archiver,
342
+ keyStoreManager,
343
+ blobClient,
344
+ });
345
+
346
+ // If we have a validator client, register it as a source of offenses for the slasher,
347
+ // and have it register callbacks on the p2p client *before* we start it, otherwise messages
348
+ // like attestations or auths will fail.
349
+ if (validatorClient) {
350
+ watchers.push(validatorClient);
351
+ if (!options.dontStartSequencer) {
352
+ await validatorClient.registerHandlers();
353
+ }
354
+ }
355
+
356
+ // If there's no validator client but alwaysReexecuteBlockProposals is enabled,
357
+ // create a BlockProposalHandler to reexecute block proposals for monitoring
358
+ if (!validatorClient && config.alwaysReexecuteBlockProposals) {
359
+ log.info('Setting up block proposal reexecution for monitoring');
360
+ createBlockProposalHandler(config, {
361
+ checkpointsBuilder: validatorCheckpointsBuilder,
362
+ worldState: worldStateSynchronizer,
363
+ epochCache,
364
+ blockSource: archiver,
365
+ l1ToL2MessageSource: archiver,
366
+ p2pClient,
367
+ dateProvider,
368
+ telemetry,
369
+ }).registerForReexecution(p2pClient);
370
+ }
371
+
372
+ // Start world state and wait for it to sync to the archiver.
373
+ await worldStateSynchronizer.start();
374
+
375
+ // Start p2p. Note that it depends on world state to be running.
376
+ await p2pClient.start();
377
+
378
+ const validatorsSentinel = await createSentinel(epochCache, archiver, p2pClient, config);
379
+ if (validatorsSentinel && config.slashInactivityPenalty > 0n) {
380
+ watchers.push(validatorsSentinel);
381
+ }
382
+
383
+ let epochPruneWatcher: EpochPruneWatcher | undefined;
384
+ if (config.slashPrunePenalty > 0n || config.slashDataWithholdingPenalty > 0n) {
385
+ epochPruneWatcher = new EpochPruneWatcher(
386
+ archiver,
387
+ archiver,
388
+ epochCache,
389
+ p2pClient.getTxProvider(),
390
+ blockBuilder,
391
+ config,
392
+ );
393
+ watchers.push(epochPruneWatcher);
394
+ }
395
+
396
+ // We assume we want to slash for invalid attestations unless all max penalties are set to 0
397
+ let attestationsBlockWatcher: AttestationsBlockWatcher | undefined;
398
+ if (config.slashProposeInvalidAttestationsPenalty > 0n || config.slashAttestDescendantOfInvalidPenalty > 0n) {
399
+ attestationsBlockWatcher = new AttestationsBlockWatcher(archiver, epochCache, config);
400
+ watchers.push(attestationsBlockWatcher);
401
+ }
402
+
403
+ // Start p2p-related services once the archiver has completed sync
404
+ void archiver
405
+ .waitForInitialSync()
406
+ .then(async () => {
407
+ await p2pClient.start();
408
+ await validatorsSentinel?.start();
409
+ await epochPruneWatcher?.start();
410
+ await attestationsBlockWatcher?.start();
411
+ log.info(`All p2p services started`);
412
+ })
413
+ .catch(err => log.error('Failed to start p2p services after archiver sync', err));
414
+
415
+ // Validator enabled, create/start relevant service
416
+ let sequencer: SequencerClient | undefined;
417
+ let slasherClient: SlasherClientInterface | undefined;
418
+ if (!config.disableValidator && validatorClient) {
419
+ // We create a slasher only if we have a sequencer, since all slashing actions go through the sequencer publisher
420
+ // as they are executed when the node is selected as proposer.
421
+ const validatorAddresses = keyStoreManager
422
+ ? NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager).getAddresses()
423
+ : [];
424
+
425
+ slasherClient = await createSlasher(
426
+ config,
427
+ config.l1Contracts,
428
+ getPublicClient(config),
429
+ watchers,
430
+ dateProvider,
431
+ epochCache,
432
+ validatorAddresses,
433
+ undefined, // logger
434
+ );
435
+ await slasherClient.start();
436
+
437
+ const l1TxUtils = config.publisherForwarderAddress
438
+ ? await createForwarderL1TxUtilsFromEthSigner(
439
+ publicClient,
440
+ keyStoreManager!.createAllValidatorPublisherSigners(),
441
+ config.publisherForwarderAddress,
442
+ { ...config, scope: 'sequencer' },
443
+ { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider },
444
+ )
445
+ : await createL1TxUtilsWithBlobsFromEthSigner(
446
+ publicClient,
447
+ keyStoreManager!.createAllValidatorPublisherSigners(),
448
+ { ...config, scope: 'sequencer' },
449
+ { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider },
450
+ );
451
+
452
+ // Create and start the sequencer client
453
+ const checkpointsBuilder = new CheckpointsBuilder(
454
+ { ...config, l1GenesisTime, slotDuration: Number(slotDuration) },
455
+ archiver,
456
+ dateProvider,
457
+ telemetry,
458
+ );
459
+
460
+ sequencer = await SequencerClient.new(config, {
461
+ ...deps,
462
+ epochCache,
463
+ l1TxUtils,
464
+ validatorClient,
465
+ p2pClient,
466
+ worldStateSynchronizer,
467
+ slasherClient,
468
+ checkpointsBuilder,
469
+ l2BlockSource: archiver,
470
+ l1ToL2MessageSource: archiver,
471
+ telemetry,
472
+ dateProvider,
473
+ blobClient,
474
+ nodeKeyStore: keyStoreManager!,
475
+ });
476
+ }
477
+
478
+ if (!options.dontStartSequencer && sequencer) {
479
+ await sequencer.start();
480
+ log.verbose(`Sequencer started`);
481
+ } else if (sequencer) {
482
+ log.warn(`Sequencer created but not started`);
483
+ }
484
+
485
+ const globalVariableBuilder = new GlobalVariableBuilder({
486
+ ...config,
487
+ rollupVersion: BigInt(config.rollupVersion),
488
+ l1GenesisTime,
489
+ slotDuration: Number(slotDuration),
490
+ });
210
491
 
211
492
  return new AztecNodeService(
212
493
  config,
@@ -215,15 +496,20 @@ export class AztecNodeService implements AztecNode, Traceable {
215
496
  archiver,
216
497
  archiver,
217
498
  archiver,
218
- archiver,
219
499
  worldStateSynchronizer,
220
500
  sequencer,
501
+ slasherClient,
502
+ validatorsSentinel,
503
+ epochPruneWatcher,
221
504
  ethereumChain.chainInfo.id,
222
- config.version,
223
- new GlobalVariableBuilder(config),
505
+ config.rollupVersion,
506
+ globalVariableBuilder,
507
+ epochCache,
508
+ packageVersion,
224
509
  proofVerifier,
225
510
  telemetry,
226
511
  log,
512
+ blobClient,
227
513
  );
228
514
  }
229
515
 
@@ -259,6 +545,10 @@ export class AztecNodeService implements AztecNode, Traceable {
259
545
  return Promise.resolve(this.p2pClient.getEnr()?.encodeTxt());
260
546
  }
261
547
 
548
+ public async getAllowedPublicSetup(): Promise<AllowedElement[]> {
549
+ return this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions());
550
+ }
551
+
262
552
  /**
263
553
  * Method to determine if the node is ready to accept transactions.
264
554
  * @returns - Flag indicating the readiness for tx submission.
@@ -268,20 +558,19 @@ export class AztecNodeService implements AztecNode, Traceable {
268
558
  }
269
559
 
270
560
  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
- ]);
561
+ const [nodeVersion, rollupVersion, chainId, enr, contractAddresses, protocolContractAddresses] = await Promise.all([
562
+ this.getNodeVersion(),
563
+ this.getVersion(),
564
+ this.getChainId(),
565
+ this.getEncodedEnr(),
566
+ this.getL1ContractAddresses(),
567
+ this.getProtocolContractAddresses(),
568
+ ]);
280
569
 
281
570
  const nodeInfo: NodeInfo = {
282
571
  nodeVersion,
283
572
  l1ChainId: chainId,
284
- protocolVersion,
573
+ rollupVersion,
285
574
  enr,
286
575
  l1ContractAddresses: contractAddresses,
287
576
  protocolContractAddresses: protocolContractAddresses,
@@ -295,8 +584,29 @@ export class AztecNodeService implements AztecNode, Traceable {
295
584
  * @param number - The block number being requested.
296
585
  * @returns The requested block.
297
586
  */
298
- public async getBlock(number: number): Promise<L2Block | undefined> {
299
- return await this.blockSource.getBlock(number);
587
+ public async getBlock(number: BlockParameter): Promise<L2Block | undefined> {
588
+ const blockNumber = number === 'latest' ? await this.getBlockNumber() : (number as BlockNumber);
589
+ return await this.blockSource.getBlock(blockNumber);
590
+ }
591
+
592
+ /**
593
+ * Get a block specified by its hash.
594
+ * @param blockHash - The block hash being requested.
595
+ * @returns The requested block.
596
+ */
597
+ public async getBlockByHash(blockHash: Fr): Promise<L2Block | undefined> {
598
+ const publishedBlock = await this.blockSource.getPublishedBlockByHash(blockHash);
599
+ return publishedBlock?.block;
600
+ }
601
+
602
+ /**
603
+ * Get a block specified by its archive root.
604
+ * @param archive - The archive root being requested.
605
+ * @returns The requested block.
606
+ */
607
+ public async getBlockByArchive(archive: Fr): Promise<L2Block | undefined> {
608
+ const publishedBlock = await this.blockSource.getPublishedBlockByArchive(archive);
609
+ return publishedBlock?.block;
300
610
  }
301
611
 
302
612
  /**
@@ -305,27 +615,51 @@ export class AztecNodeService implements AztecNode, Traceable {
305
615
  * @param limit - The maximum number of blocks to obtain.
306
616
  * @returns The blocks requested.
307
617
  */
308
- public async getBlocks(from: number, limit: number): Promise<L2Block[]> {
618
+ public async getBlocks(from: BlockNumber, limit: number): Promise<L2Block[]> {
309
619
  return (await this.blockSource.getBlocks(from, limit)) ?? [];
310
620
  }
311
621
 
622
+ public async getPublishedBlocks(from: BlockNumber, limit: number): Promise<PublishedL2Block[]> {
623
+ return (await this.blockSource.getPublishedBlocks(from, limit)) ?? [];
624
+ }
625
+
626
+ public async getPublishedCheckpoints(from: CheckpointNumber, limit: number): Promise<PublishedCheckpoint[]> {
627
+ return (await this.blockSource.getPublishedCheckpoints(from, limit)) ?? [];
628
+ }
629
+
630
+ public async getL2BlocksNew(from: BlockNumber, limit: number): Promise<L2BlockNew[]> {
631
+ return (await this.blockSource.getL2BlocksNew(from, limit)) ?? [];
632
+ }
633
+
634
+ public async getCheckpointedBlocks(from: BlockNumber, limit: number, proven?: boolean) {
635
+ return (await this.blockSource.getCheckpointedBlocks(from, limit, proven)) ?? [];
636
+ }
637
+
312
638
  /**
313
- * Method to fetch the current base fees.
314
- * @returns The current base fees.
639
+ * Method to fetch the current min L2 fees.
640
+ * @returns The current min L2 fees.
315
641
  */
316
- public async getCurrentBaseFees(): Promise<GasFees> {
317
- return await this.globalVariableBuilder.getCurrentBaseFees();
642
+ public async getCurrentMinFees(): Promise<GasFees> {
643
+ return await this.globalVariableBuilder.getCurrentMinFees();
644
+ }
645
+
646
+ public async getMaxPriorityFees(): Promise<GasFees> {
647
+ for await (const tx of this.p2pClient.iteratePendingTxs()) {
648
+ return tx.getGasSettings().maxPriorityFeesPerGas;
649
+ }
650
+
651
+ return GasFees.from({ feePerDaGas: 0n, feePerL2Gas: 0n });
318
652
  }
319
653
 
320
654
  /**
321
- * Method to fetch the current block number.
655
+ * Method to fetch the latest block number synchronized by the node.
322
656
  * @returns The block number.
323
657
  */
324
- public async getBlockNumber(): Promise<number> {
658
+ public async getBlockNumber(): Promise<BlockNumber> {
325
659
  return await this.blockSource.getBlockNumber();
326
660
  }
327
661
 
328
- public async getProvenBlockNumber(): Promise<number> {
662
+ public async getProvenBlockNumber(): Promise<BlockNumber> {
329
663
  return await this.blockSource.getProvenBlockNumber();
330
664
  }
331
665
 
@@ -353,49 +687,20 @@ export class AztecNodeService implements AztecNode, Traceable {
353
687
  return Promise.resolve(this.l1ChainId);
354
688
  }
355
689
 
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;
690
+ public getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
691
+ return this.contractDataSource.getContractClass(id);
375
692
  }
376
693
 
377
694
  public getContract(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
378
695
  return this.contractDataSource.getContract(address);
379
696
  }
380
697
 
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);
698
+ public getPrivateLogsByTags(tags: SiloedTag[]): Promise<TxScopedL2Log[][]> {
699
+ return this.logsSource.getPrivateLogsByTags(tags);
389
700
  }
390
701
 
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);
702
+ public getPublicLogsByTagsFromContract(contractAddress: AztecAddress, tags: Tag[]): Promise<TxScopedL2Log[][]> {
703
+ return this.logsSource.getPublicLogsByTagsFromContract(contractAddress, tags);
399
704
  }
400
705
 
401
706
  /**
@@ -421,17 +726,19 @@ export class AztecNodeService implements AztecNode, Traceable {
421
726
  * @param tx - The transaction to be submitted.
422
727
  */
423
728
  public async sendTx(tx: Tx) {
729
+ await this.#sendTx(tx);
730
+ }
731
+
732
+ async #sendTx(tx: Tx) {
424
733
  const timer = new Timer();
425
- const txHash = (await tx.getTxHash()).toString();
734
+ const txHash = tx.getTxHash().toString();
426
735
 
427
736
  const valid = await this.isValidTx(tx);
428
737
  if (valid.result !== 'valid') {
429
738
  const reason = valid.reason.join(', ');
430
739
  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;
740
+ this.log.warn(`Received invalid tx ${txHash}: ${reason}`, { txHash });
741
+ throw new Error(`Invalid tx: ${reason}`);
435
742
  }
436
743
 
437
744
  await this.p2pClient!.sendTx(tx);
@@ -457,7 +764,7 @@ export class AztecNodeService implements AztecNode, Traceable {
457
764
  return txReceipt;
458
765
  }
459
766
 
460
- public getTxEffect(txHash: TxHash): Promise<InBlock<TxEffect> | undefined> {
767
+ public getTxEffect(txHash: TxHash): Promise<IndexedTxEffect | undefined> {
461
768
  return this.blockSource.getTxEffect(txHash);
462
769
  }
463
770
 
@@ -465,89 +772,129 @@ export class AztecNodeService implements AztecNode, Traceable {
465
772
  * Method to stop the aztec node.
466
773
  */
467
774
  public async stop() {
468
- this.log.info(`Stopping`);
469
- await this.sequencer?.stop();
470
- await this.p2pClient.stop();
471
- await this.worldStateSynchronizer.stop();
775
+ this.log.info(`Stopping Aztec Node`);
776
+ await tryStop(this.validatorsSentinel);
777
+ await tryStop(this.epochPruneWatcher);
778
+ await tryStop(this.slasherClient);
779
+ await tryStop(this.proofVerifier);
780
+ await tryStop(this.sequencer);
781
+ await tryStop(this.p2pClient);
782
+ await tryStop(this.worldStateSynchronizer);
472
783
  await tryStop(this.blockSource);
473
- await this.telemetry.stop();
474
- this.log.info(`Stopped`);
784
+ await tryStop(this.blobClient);
785
+ await tryStop(this.telemetry);
786
+ this.log.info(`Stopped Aztec Node`);
787
+ }
788
+
789
+ /**
790
+ * Returns the blob client used by this node.
791
+ * @internal - Exposed for testing purposes only.
792
+ */
793
+ public getBlobClient(): BlobClientInterface | undefined {
794
+ return this.blobClient;
475
795
  }
476
796
 
477
797
  /**
478
798
  * Method to retrieve pending txs.
799
+ * @param limit - The number of items to returns
800
+ * @param after - The last known pending tx. Used for pagination
479
801
  * @returns - The pending txs.
480
802
  */
481
- public getPendingTxs() {
482
- return this.p2pClient!.getPendingTxs();
803
+ public getPendingTxs(limit?: number, after?: TxHash): Promise<Tx[]> {
804
+ return this.p2pClient!.getPendingTxs(limit, after);
483
805
  }
484
806
 
485
- public async getPendingTxCount() {
486
- const pendingTxs = await this.getPendingTxs();
487
- return pendingTxs.length;
807
+ public getPendingTxCount(): Promise<number> {
808
+ return this.p2pClient!.getPendingTxCount();
488
809
  }
489
810
 
490
811
  /**
491
- * Method to retrieve a single tx from the mempool or unfinalised chain.
812
+ * Method to retrieve a single tx from the mempool or unfinalized chain.
492
813
  * @param txHash - The transaction hash to return.
493
814
  * @returns - The tx if it exists.
494
815
  */
495
- public getTxByHash(txHash: TxHash) {
816
+ public getTxByHash(txHash: TxHash): Promise<Tx | undefined> {
496
817
  return Promise.resolve(this.p2pClient!.getTxByHashFromPool(txHash));
497
818
  }
498
819
 
499
820
  /**
500
- * Method to retrieve txs from the mempool or unfinalised chain.
821
+ * Method to retrieve txs from the mempool or unfinalized chain.
501
822
  * @param txHash - The transaction hash to return.
502
823
  * @returns - The txs if it exists.
503
824
  */
504
- public async getTxsByHash(txHashes: TxHash[]) {
825
+ public async getTxsByHash(txHashes: TxHash[]): Promise<Tx[]> {
505
826
  return compactArray(await Promise.all(txHashes.map(txHash => this.getTxByHash(txHash))));
506
827
  }
507
828
 
508
829
  /**
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
830
+ * Find the indexes of the given leaves in the given tree along with a block metadata pointing to the block in which
831
+ * the leaves were inserted.
832
+ * @param blockNumber - The block number at which to get the data or 'latest' for latest data.
511
833
  * @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.
834
+ * @param leafValues - The values to search for.
835
+ * @returns The indices of leaves and the block metadata of a block in which the leaves were inserted.
514
836
  */
515
837
  public async findLeavesIndexes(
516
- blockNumber: L2BlockNumber,
838
+ blockNumber: BlockParameter,
517
839
  treeId: MerkleTreeId,
518
840
  leafValues: Fr[],
519
- ): Promise<(bigint | undefined)[]> {
841
+ ): Promise<(DataInBlock<bigint> | undefined)[]> {
520
842
  const committedDb = await this.#getWorldState(blockNumber);
521
- return await committedDb.findLeafIndices(
843
+ const maybeIndices = await committedDb.findLeafIndices(
522
844
  treeId,
523
845
  leafValues.map(x => x.toBuffer()),
524
846
  );
525
- }
847
+ // We filter out undefined values
848
+ const indices = maybeIndices.filter(x => x !== undefined) as bigint[];
526
849
 
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
- }
850
+ // Now we find the block numbers for the indices
851
+ const blockNumbers = await committedDb.getBlockNumbersForLeafIndices(treeId, indices);
852
+
853
+ // If any of the block numbers are undefined, we throw an error.
854
+ for (let i = 0; i < indices.length; i++) {
855
+ if (blockNumbers[i] === undefined) {
856
+ throw new Error(`Block number is undefined for leaf index ${indices[i]} in tree ${MerkleTreeId[treeId]}`);
857
+ }
858
+ }
859
+
860
+ // Get unique block numbers in order to optimize num calls to getLeafValue function.
861
+ const uniqueBlockNumbers = [...new Set(blockNumbers.filter(x => x !== undefined))];
862
+
863
+ // Now we obtain the block hashes from the archive tree by calling await `committedDb.getLeafValue(treeId, index)`
864
+ // (note that block number corresponds to the leaf index in the archive tree).
865
+ const blockHashes = await Promise.all(
866
+ uniqueBlockNumbers.map(blockNumber => {
867
+ return committedDb.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber));
868
+ }),
869
+ );
542
870
 
543
- public async findNullifiersIndexesWithBlock(
544
- blockNumber: L2BlockNumber,
545
- nullifiers: Fr[],
546
- ): Promise<(InBlock<bigint> | undefined)[]> {
547
- if (blockNumber === 'latest') {
548
- blockNumber = await this.getBlockNumber();
871
+ // If any of the block hashes are undefined, we throw an error.
872
+ for (let i = 0; i < uniqueBlockNumbers.length; i++) {
873
+ if (blockHashes[i] === undefined) {
874
+ throw new Error(`Block hash is undefined for block number ${uniqueBlockNumbers[i]}`);
875
+ }
549
876
  }
550
- return this.nullifierSource.findNullifiersIndexesWithBlock(blockNumber, nullifiers);
877
+
878
+ // Create DataInBlock objects by combining indices, blockNumbers and blockHashes and return them.
879
+ return maybeIndices.map((index, i) => {
880
+ if (index === undefined) {
881
+ return undefined;
882
+ }
883
+ const blockNumber = blockNumbers[i];
884
+ if (blockNumber === undefined) {
885
+ return undefined;
886
+ }
887
+ const blockHashIndex = uniqueBlockNumbers.indexOf(blockNumber);
888
+ const blockHash = blockHashes[blockHashIndex];
889
+ if (!blockHash) {
890
+ return undefined;
891
+ }
892
+ return {
893
+ l2BlockNumber: BlockNumber(Number(blockNumber)),
894
+ l2BlockHash: L2BlockHash.fromField(blockHash),
895
+ data: index,
896
+ };
897
+ });
551
898
  }
552
899
 
553
900
  /**
@@ -557,7 +904,7 @@ export class AztecNodeService implements AztecNode, Traceable {
557
904
  * @returns The sibling path for the leaf index.
558
905
  */
559
906
  public async getNullifierSiblingPath(
560
- blockNumber: L2BlockNumber,
907
+ blockNumber: BlockParameter,
561
908
  leafIndex: bigint,
562
909
  ): Promise<SiblingPath<typeof NULLIFIER_TREE_HEIGHT>> {
563
910
  const committedDb = await this.#getWorldState(blockNumber);
@@ -571,13 +918,38 @@ export class AztecNodeService implements AztecNode, Traceable {
571
918
  * @returns The sibling path for the leaf index.
572
919
  */
573
920
  public async getNoteHashSiblingPath(
574
- blockNumber: L2BlockNumber,
921
+ blockNumber: BlockParameter,
575
922
  leafIndex: bigint,
576
923
  ): Promise<SiblingPath<typeof NOTE_HASH_TREE_HEIGHT>> {
577
924
  const committedDb = await this.#getWorldState(blockNumber);
578
925
  return committedDb.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex);
579
926
  }
580
927
 
928
+ public async getArchiveMembershipWitness(
929
+ blockNumber: BlockParameter,
930
+ archive: Fr,
931
+ ): Promise<MembershipWitness<typeof ARCHIVE_HEIGHT> | undefined> {
932
+ const committedDb = await this.#getWorldState(blockNumber);
933
+ const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.ARCHIVE>(MerkleTreeId.ARCHIVE, [archive]);
934
+ return pathAndIndex === undefined
935
+ ? undefined
936
+ : MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
937
+ }
938
+
939
+ public async getNoteHashMembershipWitness(
940
+ blockNumber: BlockParameter,
941
+ noteHash: Fr,
942
+ ): Promise<MembershipWitness<typeof NOTE_HASH_TREE_HEIGHT> | undefined> {
943
+ const committedDb = await this.#getWorldState(blockNumber);
944
+ const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.NOTE_HASH_TREE>(
945
+ MerkleTreeId.NOTE_HASH_TREE,
946
+ [noteHash],
947
+ );
948
+ return pathAndIndex === undefined
949
+ ? undefined
950
+ : MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
951
+ }
952
+
581
953
  /**
582
954
  * Returns the index and a sibling path for a leaf in the committed l1 to l2 data tree.
583
955
  * @param blockNumber - The block number at which to get the data.
@@ -585,19 +957,24 @@ export class AztecNodeService implements AztecNode, Traceable {
585
957
  * @returns A tuple of the index and the sibling path of the L1ToL2Message (undefined if not found).
586
958
  */
587
959
  public async getL1ToL2MessageMembershipWitness(
588
- blockNumber: L2BlockNumber,
960
+ blockNumber: BlockParameter,
589
961
  l1ToL2Message: Fr,
590
962
  ): Promise<[bigint, SiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>] | undefined> {
591
- const index = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
592
- if (index === undefined) {
963
+ const db = await this.#getWorldState(blockNumber);
964
+ const [witness] = await db.findSiblingPaths(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, [l1ToL2Message]);
965
+ if (!witness) {
593
966
  return undefined;
594
967
  }
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];
968
+
969
+ // REFACTOR: Return a MembershipWitness object
970
+ return [witness.index, witness.path];
971
+ }
972
+
973
+ public async getL1ToL2MessageBlock(l1ToL2Message: Fr): Promise<BlockNumber | undefined> {
974
+ const messageIndex = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
975
+ return messageIndex
976
+ ? BlockNumber.fromCheckpointNumber(InboxLeaf.checkpointNumberFromIndex(messageIndex))
977
+ : undefined;
601
978
  }
602
979
 
603
980
  /**
@@ -606,89 +983,33 @@ export class AztecNodeService implements AztecNode, Traceable {
606
983
  * @returns Whether the message is synced and ready to be included in a block.
607
984
  */
608
985
  public async isL1ToL2MessageSynced(l1ToL2Message: Fr): Promise<boolean> {
609
- return (await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message)) !== undefined;
986
+ const messageIndex = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
987
+ return messageIndex !== undefined;
610
988
  }
611
989
 
612
990
  /**
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.
991
+ * Returns all the L2 to L1 messages in an epoch.
992
+ * @param epoch - The epoch at which to get the data.
993
+ * @returns The L2 to L1 messages (empty array if the epoch is not found).
621
994
  */
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');
995
+ public async getL2ToL1Messages(epoch: EpochNumber): Promise<Fr[][][][]> {
996
+ // Assumes `getBlocksForEpoch` returns blocks in ascending order of block number.
997
+ const blocks = await this.blockSource.getBlocksForEpoch(epoch);
998
+ const blocksInCheckpoints: L2Block[][] = [];
999
+ let previousSlotNumber = SlotNumber.ZERO;
1000
+ let checkpointIndex = -1;
1001
+ for (const block of blocks) {
1002
+ const slotNumber = block.header.globalVariables.slotNumber;
1003
+ if (slotNumber !== previousSlotNumber) {
1004
+ checkpointIndex++;
1005
+ blocksInCheckpoints.push([]);
1006
+ previousSlotNumber = slotNumber;
1007
+ }
1008
+ blocksInCheckpoints[checkpointIndex].push(block);
644
1009
  }
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
- }),
1010
+ return blocksInCheckpoints.map(blocks =>
1011
+ blocks.map(block => block.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs)),
658
1012
  );
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
1013
  }
693
1014
 
694
1015
  /**
@@ -698,7 +1019,7 @@ export class AztecNodeService implements AztecNode, Traceable {
698
1019
  * @returns The sibling path.
699
1020
  */
700
1021
  public async getArchiveSiblingPath(
701
- blockNumber: L2BlockNumber,
1022
+ blockNumber: BlockParameter,
702
1023
  leafIndex: bigint,
703
1024
  ): Promise<SiblingPath<typeof ARCHIVE_HEIGHT>> {
704
1025
  const committedDb = await this.#getWorldState(blockNumber);
@@ -712,7 +1033,7 @@ export class AztecNodeService implements AztecNode, Traceable {
712
1033
  * @returns The sibling path.
713
1034
  */
714
1035
  public async getPublicDataSiblingPath(
715
- blockNumber: L2BlockNumber,
1036
+ blockNumber: BlockParameter,
716
1037
  leafIndex: bigint,
717
1038
  ): Promise<SiblingPath<typeof PUBLIC_DATA_TREE_HEIGHT>> {
718
1039
  const committedDb = await this.#getWorldState(blockNumber);
@@ -726,28 +1047,22 @@ export class AztecNodeService implements AztecNode, Traceable {
726
1047
  * @returns The nullifier membership witness (if found).
727
1048
  */
728
1049
  public async getNullifierMembershipWitness(
729
- blockNumber: L2BlockNumber,
1050
+ blockNumber: BlockParameter,
730
1051
  nullifier: Fr,
731
1052
  ): Promise<NullifierMembershipWitness | undefined> {
732
1053
  const db = await this.#getWorldState(blockNumber);
733
- const index = (await db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]))[0];
734
- if (!index) {
1054
+ const [witness] = await db.findSiblingPaths(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]);
1055
+ if (!witness) {
735
1056
  return undefined;
736
1057
  }
737
1058
 
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
-
1059
+ const { index, path } = witness;
1060
+ const leafPreimage = await db.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index);
746
1061
  if (!leafPreimage) {
747
1062
  return undefined;
748
1063
  }
749
1064
 
750
- return new NullifierMembershipWitness(BigInt(index), leafPreimage as NullifierLeafPreimage, siblingPath);
1065
+ return new NullifierMembershipWitness(index, leafPreimage as NullifierLeafPreimage, path);
751
1066
  }
752
1067
 
753
1068
  /**
@@ -765,7 +1080,7 @@ export class AztecNodeService implements AztecNode, Traceable {
765
1080
  * TODO: This is a confusing behavior and we should eventually address that.
766
1081
  */
767
1082
  public async getLowNullifierMembershipWitness(
768
- blockNumber: L2BlockNumber,
1083
+ blockNumber: BlockParameter,
769
1084
  nullifier: Fr,
770
1085
  ): Promise<NullifierMembershipWitness | undefined> {
771
1086
  const committedDb = await this.#getWorldState(blockNumber);
@@ -779,14 +1094,11 @@ export class AztecNodeService implements AztecNode, Traceable {
779
1094
  }
780
1095
  const preimageData = (await committedDb.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index))!;
781
1096
 
782
- const siblingPath = await committedDb.getSiblingPath<typeof NULLIFIER_TREE_HEIGHT>(
783
- MerkleTreeId.NULLIFIER_TREE,
784
- BigInt(index),
785
- );
1097
+ const siblingPath = await committedDb.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, BigInt(index));
786
1098
  return new NullifierMembershipWitness(BigInt(index), preimageData as NullifierLeafPreimage, siblingPath);
787
1099
  }
788
1100
 
789
- async getPublicDataTreeWitness(blockNumber: L2BlockNumber, leafSlot: Fr): Promise<PublicDataWitness | undefined> {
1101
+ async getPublicDataWitness(blockNumber: BlockParameter, leafSlot: Fr): Promise<PublicDataWitness | undefined> {
790
1102
  const committedDb = await this.#getWorldState(blockNumber);
791
1103
  const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
792
1104
  if (!lowLeafResult) {
@@ -796,10 +1108,7 @@ export class AztecNodeService implements AztecNode, Traceable {
796
1108
  MerkleTreeId.PUBLIC_DATA_TREE,
797
1109
  lowLeafResult.index,
798
1110
  )) as PublicDataTreeLeafPreimage;
799
- const path = await committedDb.getSiblingPath<typeof PUBLIC_DATA_TREE_HEIGHT>(
800
- MerkleTreeId.PUBLIC_DATA_TREE,
801
- lowLeafResult.index,
802
- );
1111
+ const path = await committedDb.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index);
803
1112
  return new PublicDataWitness(lowLeafResult.index, preimage, path);
804
1113
  }
805
1114
  }
@@ -815,7 +1124,7 @@ export class AztecNodeService implements AztecNode, Traceable {
815
1124
  * @param blockNumber - The block number at which to get the data or 'latest'.
816
1125
  * @returns Storage value at the given contract slot.
817
1126
  */
818
- public async getPublicStorageAt(blockNumber: L2BlockNumber, contract: AztecAddress, slot: Fr): Promise<Fr> {
1127
+ public async getPublicStorageAt(blockNumber: BlockParameter, contract: AztecAddress, slot: Fr): Promise<Fr> {
819
1128
  const committedDb = await this.#getWorldState(blockNumber);
820
1129
  const leafSlot = await computePublicDataTreeLeafSlot(contract, slot);
821
1130
 
@@ -827,36 +1136,69 @@ export class AztecNodeService implements AztecNode, Traceable {
827
1136
  MerkleTreeId.PUBLIC_DATA_TREE,
828
1137
  lowLeafResult.index,
829
1138
  )) as PublicDataTreeLeafPreimage;
830
- return preimage.value;
1139
+ return preimage.leaf.value;
831
1140
  }
832
1141
 
833
1142
  /**
834
1143
  * Returns the currently committed block header, or the initial header if no blocks have been produced.
835
1144
  * @returns The current committed block header.
836
1145
  */
837
- public async getBlockHeader(blockNumber: L2BlockNumber = 'latest'): Promise<BlockHeader | undefined> {
838
- return blockNumber === 0 || (blockNumber === 'latest' && (await this.blockSource.getBlockNumber()) === 0)
1146
+ public async getBlockHeader(blockNumber: BlockParameter = 'latest'): Promise<BlockHeader | undefined> {
1147
+ return blockNumber === BlockNumber.ZERO ||
1148
+ (blockNumber === 'latest' && (await this.blockSource.getBlockNumber()) === BlockNumber.ZERO)
839
1149
  ? this.worldStateSynchronizer.getCommitted().getInitialHeader()
840
- : this.blockSource.getBlockHeader(blockNumber);
1150
+ : this.blockSource.getBlockHeader(blockNumber === 'latest' ? blockNumber : (blockNumber as BlockNumber));
1151
+ }
1152
+
1153
+ /**
1154
+ * Get a block header specified by its hash.
1155
+ * @param blockHash - The block hash being requested.
1156
+ * @returns The requested block header.
1157
+ */
1158
+ public async getBlockHeaderByHash(blockHash: Fr): Promise<BlockHeader | undefined> {
1159
+ return await this.blockSource.getBlockHeaderByHash(blockHash);
1160
+ }
1161
+
1162
+ /**
1163
+ * Get a block header specified by its archive root.
1164
+ * @param archive - The archive root being requested.
1165
+ * @returns The requested block header.
1166
+ */
1167
+ public async getBlockHeaderByArchive(archive: Fr): Promise<BlockHeader | undefined> {
1168
+ return await this.blockSource.getBlockHeaderByArchive(archive);
841
1169
  }
842
1170
 
843
1171
  /**
844
1172
  * Simulates the public part of a transaction with the current state.
845
1173
  * @param tx - The transaction to simulate.
846
1174
  **/
847
- @trackSpan('AztecNodeService.simulatePublicCalls', async (tx: Tx) => ({
848
- [Attributes.TX_HASH]: (await tx.getTxHash()).toString(),
1175
+ @trackSpan('AztecNodeService.simulatePublicCalls', (tx: Tx) => ({
1176
+ [Attributes.TX_HASH]: tx.getTxHash().toString(),
849
1177
  }))
850
1178
  public async simulatePublicCalls(tx: Tx, skipFeeEnforcement = false): Promise<PublicSimulationOutput> {
851
- const txHash = await tx.getTxHash();
852
- const blockNumber = (await this.blockSource.getBlockNumber()) + 1;
1179
+ // Check total gas limit for simulation
1180
+ const gasSettings = tx.data.constants.txContext.gasSettings;
1181
+ const txGasLimit = gasSettings.gasLimits.l2Gas;
1182
+ const teardownGasLimit = gasSettings.teardownGasLimits.l2Gas;
1183
+ if (txGasLimit + teardownGasLimit > this.config.rpcSimulatePublicMaxGasLimit) {
1184
+ throw new BadRequestError(
1185
+ `Transaction total gas limit ${
1186
+ txGasLimit + teardownGasLimit
1187
+ } (${txGasLimit} + ${teardownGasLimit}) exceeds maximum gas limit ${
1188
+ this.config.rpcSimulatePublicMaxGasLimit
1189
+ } for simulation`,
1190
+ );
1191
+ }
1192
+
1193
+ const txHash = tx.getTxHash();
1194
+ const blockNumber = BlockNumber((await this.blockSource.getBlockNumber()) + 1);
853
1195
 
854
1196
  // 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;
1197
+ const coinbase = EthAddress.ZERO;
1198
+ const feeRecipient = AztecAddress.ZERO;
857
1199
 
858
1200
  const newGlobalVariables = await this.globalVariableBuilder.buildGlobalVariables(
859
- new Fr(blockNumber),
1201
+ blockNumber,
860
1202
  coinbase,
861
1203
  feeRecipient,
862
1204
  );
@@ -865,7 +1207,6 @@ export class AztecNodeService implements AztecNode, Traceable {
865
1207
  new DateProvider(),
866
1208
  this.telemetry,
867
1209
  );
868
- const fork = await this.worldStateSynchronizer.fork();
869
1210
 
870
1211
  this.log.verbose(`Simulating public calls for tx ${txHash}`, {
871
1212
  globalVariables: newGlobalVariables.toInspect(),
@@ -873,11 +1214,22 @@ export class AztecNodeService implements AztecNode, Traceable {
873
1214
  blockNumber,
874
1215
  });
875
1216
 
1217
+ const merkleTreeFork = await this.worldStateSynchronizer.fork();
876
1218
  try {
877
- const processor = publicProcessorFactory.create(fork, newGlobalVariables, skipFeeEnforcement);
1219
+ const config = PublicSimulatorConfig.from({
1220
+ skipFeeEnforcement,
1221
+ collectDebugLogs: true,
1222
+ collectHints: false,
1223
+ collectCallMetadata: true,
1224
+ collectStatistics: false,
1225
+ collectionLimits: CollectionLimitsConfig.from({
1226
+ maxDebugLogMemoryReads: this.config.rpcSimulatePublicMaxDebugLogMemoryReads,
1227
+ }),
1228
+ });
1229
+ const processor = publicProcessorFactory.create(merkleTreeFork, newGlobalVariables, config);
878
1230
 
879
1231
  // REFACTOR: Consider merging ProcessReturnValues into ProcessedTx
880
- const [processedTxs, failedTxs, returns] = await processor.process([tx]);
1232
+ const [processedTxs, failedTxs, _usedTxs, returns] = await processor.process([tx]);
881
1233
  // REFACTOR: Consider returning the error rather than throwing
882
1234
  if (failedTxs.length) {
883
1235
  this.log.warn(`Simulated tx ${txHash} fails: ${failedTxs[0].error}`, { txHash });
@@ -887,13 +1239,13 @@ export class AztecNodeService implements AztecNode, Traceable {
887
1239
  const [processedTx] = processedTxs;
888
1240
  return new PublicSimulationOutput(
889
1241
  processedTx.revertReason,
890
- processedTx.constants,
1242
+ processedTx.globalVariables,
891
1243
  processedTx.txEffect,
892
1244
  returns,
893
1245
  processedTx.gasUsed,
894
1246
  );
895
1247
  } finally {
896
- await fork.close();
1248
+ await merkleTreeFork.close();
897
1249
  }
898
1250
  }
899
1251
 
@@ -901,24 +1253,42 @@ export class AztecNodeService implements AztecNode, Traceable {
901
1253
  tx: Tx,
902
1254
  { isSimulation, skipFeeEnforcement }: { isSimulation?: boolean; skipFeeEnforcement?: boolean } = {},
903
1255
  ): Promise<TxValidationResult> {
904
- const blockNumber = (await this.blockSource.getBlockNumber()) + 1;
905
1256
  const db = this.worldStateSynchronizer.getCommitted();
906
1257
  const verifier = isSimulation ? undefined : this.proofVerifier;
1258
+
1259
+ // We accept transactions if they are not expired by the next slot (checked based on the IncludeByTimestamp field)
1260
+ const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
1261
+ const blockNumber = BlockNumber((await this.blockSource.getBlockNumber()) + 1);
907
1262
  const validator = createValidatorForAcceptingTxs(db, this.contractDataSource, verifier, {
1263
+ timestamp: nextSlotTimestamp,
908
1264
  blockNumber,
909
1265
  l1ChainId: this.l1ChainId,
910
- setupAllowList: this.config.allowedInSetup ?? (await getDefaultAllowedSetupFunctions()),
911
- gasFees: await this.getCurrentBaseFees(),
1266
+ rollupVersion: this.version,
1267
+ setupAllowList: this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions()),
1268
+ gasFees: await this.getCurrentMinFees(),
912
1269
  skipFeeEnforcement,
1270
+ txsPermitted: !this.config.disableTransactions,
913
1271
  });
914
1272
 
915
1273
  return await validator.validateTx(tx);
916
1274
  }
917
1275
 
918
- public async setConfig(config: Partial<SequencerConfig & ProverConfig>): Promise<void> {
919
- const newConfig = { ...this.config, ...config };
920
- await this.sequencer?.updateSequencerConfig(config);
1276
+ public getConfig(): Promise<AztecNodeAdminConfig> {
1277
+ const schema = AztecNodeAdminConfigSchema;
1278
+ const keys = schema.keyof().options;
1279
+ return Promise.resolve(pick(this.config, ...keys));
1280
+ }
921
1281
 
1282
+ public async setConfig(config: Partial<AztecNodeAdminConfig>): Promise<void> {
1283
+ const newConfig = { ...this.config, ...config };
1284
+ this.sequencer?.updateConfig(config);
1285
+ this.slasherClient?.updateConfig(config);
1286
+ this.validatorsSentinel?.updateConfig(config);
1287
+ await this.p2pClient.updateP2PConfig(config);
1288
+ const archiver = this.blockSource as Archiver;
1289
+ if ('updateConfig' in archiver) {
1290
+ archiver.updateConfig(config);
1291
+ }
922
1292
  if (newConfig.realProofs !== this.config.realProofs) {
923
1293
  this.proofVerifier = config.realProofs ? await BBCircuitVerifier.new(newConfig) : new TestCircuitVerifier();
924
1294
  }
@@ -928,42 +1298,151 @@ export class AztecNodeService implements AztecNode, Traceable {
928
1298
 
929
1299
  public getProtocolContractAddresses(): Promise<ProtocolContractAddresses> {
930
1300
  return Promise.resolve({
931
- classRegisterer: ProtocolContractAddress.ContractClassRegisterer,
1301
+ classRegistry: ProtocolContractAddress.ContractClassRegistry,
932
1302
  feeJuice: ProtocolContractAddress.FeeJuice,
933
- instanceDeployer: ProtocolContractAddress.ContractInstanceDeployer,
1303
+ instanceRegistry: ProtocolContractAddress.ContractInstanceRegistry,
934
1304
  multiCallEntrypoint: ProtocolContractAddress.MultiCallEntrypoint,
935
1305
  });
936
1306
  }
937
1307
 
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);
1308
+ public registerContractFunctionSignatures(signatures: string[]): Promise<void> {
1309
+ return this.contractDataSource.registerContractFunctionSignatures(signatures);
1310
+ }
1311
+
1312
+ public getValidatorsStats(): Promise<ValidatorsStats> {
1313
+ return this.validatorsSentinel?.computeStats() ?? Promise.resolve({ stats: {}, slotWindow: 0 });
942
1314
  }
943
1315
 
944
- public registerContractFunctionSignatures(_address: AztecAddress, signatures: string[]): Promise<void> {
945
- return this.contractDataSource.registerContractFunctionSignatures(_address, signatures);
1316
+ public getValidatorStats(
1317
+ validatorAddress: EthAddress,
1318
+ fromSlot?: SlotNumber,
1319
+ toSlot?: SlotNumber,
1320
+ ): Promise<SingleValidatorStats | undefined> {
1321
+ return this.validatorsSentinel?.getValidatorStats(validatorAddress, fromSlot, toSlot) ?? Promise.resolve(undefined);
1322
+ }
1323
+
1324
+ public async startSnapshotUpload(location: string): Promise<void> {
1325
+ // Note that we are forcefully casting the blocksource as an archiver
1326
+ // We break support for archiver running remotely to the node
1327
+ const archiver = this.blockSource as Archiver;
1328
+ if (!('backupTo' in archiver)) {
1329
+ this.metrics.recordSnapshotError();
1330
+ throw new Error('Archiver implementation does not support backups. Cannot generate snapshot.');
1331
+ }
1332
+
1333
+ // Test that the archiver has done an initial sync.
1334
+ if (!archiver.isInitialSyncComplete()) {
1335
+ this.metrics.recordSnapshotError();
1336
+ throw new Error(`Archiver initial sync not complete. Cannot start snapshot.`);
1337
+ }
1338
+
1339
+ // And it has an L2 block hash
1340
+ const l2BlockHash = await archiver.getL2Tips().then(tips => tips.proposed.hash);
1341
+ if (!l2BlockHash) {
1342
+ this.metrics.recordSnapshotError();
1343
+ throw new Error(`Archiver has no latest L2 block hash downloaded. Cannot start snapshot.`);
1344
+ }
1345
+
1346
+ if (this.isUploadingSnapshot) {
1347
+ this.metrics.recordSnapshotError();
1348
+ throw new Error(`Snapshot upload already in progress. Cannot start another one until complete.`);
1349
+ }
1350
+
1351
+ // Do not wait for the upload to be complete to return to the caller, but flag that an operation is in progress
1352
+ this.isUploadingSnapshot = true;
1353
+ const timer = new Timer();
1354
+ void uploadSnapshot(location, this.blockSource as Archiver, this.worldStateSynchronizer, this.config, this.log)
1355
+ .then(() => {
1356
+ this.isUploadingSnapshot = false;
1357
+ this.metrics.recordSnapshot(timer.ms());
1358
+ })
1359
+ .catch(err => {
1360
+ this.isUploadingSnapshot = false;
1361
+ this.metrics.recordSnapshotError();
1362
+ this.log.error(`Error uploading snapshot: ${err}`);
1363
+ });
1364
+
1365
+ return Promise.resolve();
946
1366
  }
947
1367
 
948
- public flushTxs(): Promise<void> {
949
- if (!this.sequencer) {
950
- throw new Error(`Sequencer is not initialized`);
1368
+ public async rollbackTo(targetBlock: BlockNumber, force?: boolean): Promise<void> {
1369
+ const archiver = this.blockSource as Archiver;
1370
+ if (!('rollbackTo' in archiver)) {
1371
+ throw new Error('Archiver implementation does not support rollbacks.');
1372
+ }
1373
+
1374
+ const finalizedBlock = await archiver.getL2Tips().then(tips => tips.finalized.block.number);
1375
+ if (targetBlock < finalizedBlock) {
1376
+ if (force) {
1377
+ this.log.warn(`Clearing world state database to allow rolling back behind finalized block ${finalizedBlock}`);
1378
+ await this.worldStateSynchronizer.clear();
1379
+ await this.p2pClient.clear();
1380
+ } else {
1381
+ throw new Error(`Cannot rollback to block ${targetBlock} as it is before finalized ${finalizedBlock}`);
1382
+ }
951
1383
  }
952
- this.sequencer.flush();
1384
+
1385
+ try {
1386
+ this.log.info(`Pausing archiver and world state sync to start rollback`);
1387
+ await archiver.stop();
1388
+ await this.worldStateSynchronizer.stopSync();
1389
+ const currentBlock = await archiver.getBlockNumber();
1390
+ const blocksToUnwind = currentBlock - targetBlock;
1391
+ this.log.info(`Unwinding ${count(blocksToUnwind, 'block')} from L2 block ${currentBlock} to ${targetBlock}`);
1392
+ await archiver.rollbackTo(targetBlock);
1393
+ this.log.info(`Unwinding complete.`);
1394
+ } catch (err) {
1395
+ this.log.error(`Error during rollback`, err);
1396
+ throw err;
1397
+ } finally {
1398
+ this.log.info(`Resuming world state and archiver sync.`);
1399
+ this.worldStateSynchronizer.resumeSync();
1400
+ archiver.resume();
1401
+ }
1402
+ }
1403
+
1404
+ public async pauseSync(): Promise<void> {
1405
+ this.log.info(`Pausing archiver and world state sync`);
1406
+ await (this.blockSource as Archiver).stop();
1407
+ await this.worldStateSynchronizer.stopSync();
1408
+ }
1409
+
1410
+ public resumeSync(): Promise<void> {
1411
+ this.log.info(`Resuming world state and archiver sync.`);
1412
+ this.worldStateSynchronizer.resumeSync();
1413
+ (this.blockSource as Archiver).resume();
953
1414
  return Promise.resolve();
954
1415
  }
955
1416
 
1417
+ public getSlashPayloads(): Promise<SlashPayloadRound[]> {
1418
+ if (!this.slasherClient) {
1419
+ throw new Error(`Slasher client not enabled`);
1420
+ }
1421
+ return this.slasherClient.getSlashPayloads();
1422
+ }
1423
+
1424
+ public getSlashOffenses(round: bigint | 'all' | 'current'): Promise<Offense[]> {
1425
+ if (!this.slasherClient) {
1426
+ throw new Error(`Slasher client not enabled`);
1427
+ }
1428
+ if (round === 'all') {
1429
+ return this.slasherClient.getPendingOffenses();
1430
+ } else {
1431
+ return this.slasherClient.gatherOffensesForRound(round === 'current' ? undefined : BigInt(round));
1432
+ }
1433
+ }
1434
+
956
1435
  /**
957
1436
  * Returns an instance of MerkleTreeOperations having first ensured the world state is fully synched
958
1437
  * @param blockNumber - The block number at which to get the data.
959
1438
  * @returns An instance of a committed MerkleTreeOperations
960
1439
  */
961
- async #getWorldState(blockNumber: L2BlockNumber) {
1440
+ async #getWorldState(blockNumber: BlockParameter) {
962
1441
  if (typeof blockNumber === 'number' && blockNumber < INITIAL_L2_BLOCK_NUM - 1) {
963
1442
  throw new Error('Invalid block number to get world state for: ' + blockNumber);
964
1443
  }
965
1444
 
966
- let blockSyncedTo: number = 0;
1445
+ let blockSyncedTo: BlockNumber = BlockNumber.ZERO;
967
1446
  try {
968
1447
  // Attempt to sync the world state if necessary
969
1448
  blockSyncedTo = await this.#syncWorldState();
@@ -977,7 +1456,7 @@ export class AztecNodeService implements AztecNode, Traceable {
977
1456
  return this.worldStateSynchronizer.getCommitted();
978
1457
  } else if (blockNumber <= blockSyncedTo) {
979
1458
  this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
980
- return this.worldStateSynchronizer.getSnapshot(blockNumber);
1459
+ return this.worldStateSynchronizer.getSnapshot(blockNumber as BlockNumber);
981
1460
  } else {
982
1461
  throw new Error(`Block ${blockNumber} not yet synced`);
983
1462
  }
@@ -987,8 +1466,8 @@ export class AztecNodeService implements AztecNode, Traceable {
987
1466
  * Ensure we fully sync the world state
988
1467
  * @returns A promise that fulfils once the world state is synced
989
1468
  */
990
- async #syncWorldState(): Promise<number> {
1469
+ async #syncWorldState(): Promise<BlockNumber> {
991
1470
  const blockSourceHeight = await this.blockSource.getBlockNumber();
992
- return this.worldStateSynchronizer.syncImmediate(blockSourceHeight);
1471
+ return await this.worldStateSynchronizer.syncImmediate(blockSourceHeight);
993
1472
  }
994
1473
  }