@aztec/aztec-node 0.0.0-test.0 → 0.0.1-commit.03f7ef2

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