@aztec/aztec-node 0.0.0-test.0 → 0.0.1-commit.21caa21

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 +15 -10
  2. package/dest/aztec-node/config.d.ts.map +1 -1
  3. package/dest/aztec-node/config.js +75 -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 +91 -51
  8. package/dest/aztec-node/server.d.ts.map +1 -1
  9. package/dest/aztec-node/server.js +525 -218
  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 +128 -25
  35. package/src/aztec-node/node_metrics.ts +28 -0
  36. package/src/aztec-node/server.ts +686 -278
  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,61 @@
1
- import { createArchiver } from '@aztec/archiver';
2
- import { BBCircuitVerifier, TestCircuitVerifier } from '@aztec/bb-prover';
1
+ import { Archiver, createArchiver } from '@aztec/archiver';
2
+ import { BBCircuitVerifier, QueuedIVCVerifier, TestCircuitVerifier } from '@aztec/bb-prover';
3
3
  import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob-sink/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 {
14
+ type L1ContractAddresses,
15
+ RegistryContract,
16
+ RollupContract,
17
+ createEthereumChain,
18
+ getPublicClient,
19
+ } from '@aztec/ethereum';
20
+ import { SlotNumber } from '@aztec/foundation/branded-types';
21
+ import { compactArray, pick } from '@aztec/foundation/collection';
16
22
  import { EthAddress } from '@aztec/foundation/eth-address';
17
23
  import { Fr } from '@aztec/foundation/fields';
24
+ import { BadRequestError } from '@aztec/foundation/json-rpc';
18
25
  import { type Logger, createLogger } from '@aztec/foundation/log';
26
+ import { count } from '@aztec/foundation/string';
19
27
  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';
28
+ import { MembershipWitness, SiblingPath } from '@aztec/foundation/trees';
29
+ import { KeystoreManager, loadKeystores, mergeKeystores } from '@aztec/node-keystore';
30
+ import { trySnapshotSync, uploadSnapshot } from '@aztec/node-lib/actions';
31
+ import { createL1TxUtilsWithBlobsFromEthSigner } from '@aztec/node-lib/factories';
32
+ import { type P2P, type P2PClientDeps, createP2PClient, getDefaultAllowedSetupFunctions } from '@aztec/p2p';
25
33
  import { ProtocolContractAddress } from '@aztec/protocol-contracts';
26
34
  import {
35
+ BlockBuilder,
27
36
  GlobalVariableBuilder,
28
37
  SequencerClient,
29
38
  type SequencerPublisher,
30
- createSlasherClient,
31
39
  createValidatorForAcceptingTxs,
32
- getDefaultAllowedSetupFunctions,
33
40
  } from '@aztec/sequencer-client';
34
41
  import { PublicProcessorFactory } from '@aztec/simulator/server';
42
+ import {
43
+ AttestationsBlockWatcher,
44
+ EpochPruneWatcher,
45
+ type SlasherClientInterface,
46
+ type Watcher,
47
+ createSlasher,
48
+ } from '@aztec/slasher';
49
+ import { PublicSimulatorConfig } from '@aztec/stdlib/avm';
35
50
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
36
- import type { InBlock, L2Block, L2BlockNumber, L2BlockSource, NullifierWithBlockSource } from '@aztec/stdlib/block';
51
+ import {
52
+ type InBlock,
53
+ type L2Block,
54
+ L2BlockHash,
55
+ type L2BlockNumber,
56
+ type L2BlockSource,
57
+ type PublishedL2Block,
58
+ } from '@aztec/stdlib/block';
37
59
  import type {
38
60
  ContractClassPublic,
39
61
  ContractDataSource,
@@ -42,33 +64,43 @@ import type {
42
64
  ProtocolContractAddresses,
43
65
  } from '@aztec/stdlib/contract';
44
66
  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';
67
+ import { computePublicDataTreeLeafSlot } from '@aztec/stdlib/hash';
68
+ import {
69
+ type AztecNode,
70
+ type AztecNodeAdmin,
71
+ type AztecNodeAdminConfig,
72
+ AztecNodeAdminConfigSchema,
73
+ type GetContractClassLogsResponse,
74
+ type GetPublicLogsResponse,
75
+ } from '@aztec/stdlib/interfaces/client';
47
76
  import {
77
+ type AllowedElement,
48
78
  type ClientProtocolCircuitVerifier,
49
79
  type L2LogsSource,
50
- type ProverConfig,
51
- type SequencerConfig,
52
80
  type Service,
53
81
  type WorldStateSyncStatus,
54
82
  type WorldStateSynchronizer,
55
83
  tryStop,
56
84
  } from '@aztec/stdlib/interfaces/server';
57
85
  import type { LogFilter, PrivateLog, TxScopedL2Log } from '@aztec/stdlib/logs';
58
- import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
86
+ import { InboxLeaf, type L1ToL2MessageSource } from '@aztec/stdlib/messaging';
59
87
  import { P2PClientType } from '@aztec/stdlib/p2p';
60
- import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
88
+ import type { Offense, SlashPayloadRound } from '@aztec/stdlib/slashing';
61
89
  import type { NullifierLeafPreimage, PublicDataTreeLeaf, PublicDataTreeLeafPreimage } from '@aztec/stdlib/trees';
90
+ import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
62
91
  import {
63
92
  type BlockHeader,
93
+ type GlobalVariableBuilder as GlobalVariableBuilderInterface,
94
+ type IndexedTxEffect,
64
95
  PublicSimulationOutput,
65
96
  Tx,
66
- TxEffect,
67
97
  type TxHash,
68
98
  TxReceipt,
69
99
  TxStatus,
70
100
  type TxValidationResult,
71
101
  } from '@aztec/stdlib/tx';
102
+ import { getPackageVersion } from '@aztec/stdlib/update-checker';
103
+ import type { SingleValidatorStats, ValidatorsStats } from '@aztec/stdlib/validators';
72
104
  import {
73
105
  Attributes,
74
106
  type TelemetryClient,
@@ -77,19 +109,30 @@ import {
77
109
  getTelemetryClient,
78
110
  trackSpan,
79
111
  } from '@aztec/telemetry-client';
80
- import { createValidatorClient } from '@aztec/validator-client';
112
+ import {
113
+ NodeKeystoreAdapter,
114
+ ValidatorClient,
115
+ createBlockProposalHandler,
116
+ createValidatorClient,
117
+ } from '@aztec/validator-client';
81
118
  import { createWorldStateSynchronizer } from '@aztec/world-state';
82
119
 
83
- import { type AztecNodeConfig, getPackageVersion } from './config.js';
120
+ import { createPublicClient, fallback, http } from 'viem';
121
+
122
+ import { createSentinel } from '../sentinel/factory.js';
123
+ import { Sentinel } from '../sentinel/sentinel.js';
124
+ import { type AztecNodeConfig, createKeyStoreForValidator } from './config.js';
84
125
  import { NodeMetrics } from './node_metrics.js';
85
126
 
86
127
  /**
87
128
  * The aztec node.
88
129
  */
89
- export class AztecNodeService implements AztecNode, Traceable {
90
- private packageVersion: string;
130
+ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
91
131
  private metrics: NodeMetrics;
92
132
 
133
+ // Prevent two snapshot operations to happen simultaneously
134
+ private isUploadingSnapshot = false;
135
+
93
136
  public readonly tracer: Tracer;
94
137
 
95
138
  constructor(
@@ -99,17 +142,20 @@ export class AztecNodeService implements AztecNode, Traceable {
99
142
  protected readonly logsSource: L2LogsSource,
100
143
  protected readonly contractDataSource: ContractDataSource,
101
144
  protected readonly l1ToL2MessageSource: L1ToL2MessageSource,
102
- protected readonly nullifierSource: NullifierWithBlockSource,
103
145
  protected readonly worldStateSynchronizer: WorldStateSynchronizer,
104
146
  protected readonly sequencer: SequencerClient | undefined,
147
+ protected readonly slasherClient: SlasherClientInterface | undefined,
148
+ protected readonly validatorsSentinel: Sentinel | undefined,
149
+ protected readonly epochPruneWatcher: EpochPruneWatcher | undefined,
105
150
  protected readonly l1ChainId: number,
106
151
  protected readonly version: number,
107
- protected readonly globalVariableBuilder: GlobalVariableBuilder,
152
+ protected readonly globalVariableBuilder: GlobalVariableBuilderInterface,
153
+ protected readonly epochCache: EpochCacheInterface,
154
+ protected readonly packageVersion: string,
108
155
  private proofVerifier: ClientProtocolCircuitVerifier,
109
156
  private telemetry: TelemetryClient = getTelemetryClient(),
110
157
  private log = createLogger('node'),
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,104 @@ 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
187
  blobSinkClient?: BlobSinkClientInterface;
188
+ p2pClientDeps?: P2PClientDeps<P2PClientType.Full>;
142
189
  } = {},
143
190
  options: {
144
191
  prefilledPublicData?: PublicDataTreeLeaf[];
192
+ dontStartSequencer?: boolean;
145
193
  } = {},
146
194
  ): Promise<AztecNodeService> {
147
- const telemetry = deps.telemetry ?? getTelemetryClient();
195
+ const config = { ...inputConfig }; // Copy the config so we dont mutate the input object
148
196
  const log = deps.logger ?? createLogger('node');
197
+ const packageVersion = getPackageVersion() ?? '';
198
+ const telemetry = deps.telemetry ?? getTelemetryClient();
149
199
  const dateProvider = deps.dateProvider ?? new DateProvider();
150
- const blobSinkClient = deps.blobSinkClient ?? createBlobSinkClient(config);
200
+ const blobSinkClient =
201
+ deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('node:blob-sink:client') });
151
202
  const ethereumChain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
152
- //validate that the actual chain id matches that specified in configuration
203
+
204
+ // Build a key store from file if given or from environment otherwise
205
+ let keyStoreManager: KeystoreManager | undefined;
206
+ const keyStoreProvided = config.keyStoreDirectory !== undefined && config.keyStoreDirectory.length > 0;
207
+ if (keyStoreProvided) {
208
+ const keyStores = loadKeystores(config.keyStoreDirectory!);
209
+ keyStoreManager = new KeystoreManager(mergeKeystores(keyStores));
210
+ } else {
211
+ const keyStore = createKeyStoreForValidator(config);
212
+ if (keyStore) {
213
+ keyStoreManager = new KeystoreManager(keyStore);
214
+ }
215
+ }
216
+
217
+ await keyStoreManager?.validateSigners();
218
+
219
+ // If we are a validator, verify our configuration before doing too much more.
220
+ if (!config.disableValidator) {
221
+ if (keyStoreManager === undefined) {
222
+ throw new Error('Failed to create key store, a requirement for running a validator');
223
+ }
224
+ if (!keyStoreProvided) {
225
+ log.warn(
226
+ 'KEY STORE CREATED FROM ENVIRONMENT, IT IS RECOMMENDED TO USE A FILE-BASED KEY STORE IN PRODUCTION ENVIRONMENTS',
227
+ );
228
+ }
229
+ ValidatorClient.validateKeyStoreConfiguration(keyStoreManager, log);
230
+ }
231
+
232
+ // validate that the actual chain id matches that specified in configuration
153
233
  if (config.l1ChainId !== ethereumChain.chainInfo.id) {
154
234
  throw new Error(
155
235
  `RPC URL configured for chain id ${ethereumChain.chainInfo.id} but expected id ${config.l1ChainId}`,
156
236
  );
157
237
  }
158
238
 
159
- const archiver = await createArchiver(config, blobSinkClient, { blockUntilSync: true }, telemetry);
239
+ const publicClient = createPublicClient({
240
+ chain: ethereumChain.chainInfo,
241
+ transport: fallback(config.l1RpcUrls.map((url: string) => http(url))),
242
+ pollingInterval: config.viemPollingIntervalMS,
243
+ });
244
+
245
+ const l1ContractsAddresses = await RegistryContract.collectAddresses(
246
+ publicClient,
247
+ config.l1Contracts.registryAddress,
248
+ config.rollupVersion ?? 'canonical',
249
+ );
250
+
251
+ // Overwrite the passed in vars.
252
+ config.l1Contracts = { ...config.l1Contracts, ...l1ContractsAddresses };
253
+
254
+ const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString());
255
+ const [l1GenesisTime, slotDuration, rollupVersionFromRollup] = await Promise.all([
256
+ rollupContract.getL1GenesisTime(),
257
+ rollupContract.getSlotDuration(),
258
+ rollupContract.getVersion(),
259
+ ] as const);
260
+
261
+ config.rollupVersion ??= Number(rollupVersionFromRollup);
262
+
263
+ if (config.rollupVersion !== Number(rollupVersionFromRollup)) {
264
+ log.warn(
265
+ `Registry looked up and returned a rollup with version (${config.rollupVersion}), but this does not match with version detected from the rollup directly: (${rollupVersionFromRollup}).`,
266
+ );
267
+ }
268
+
269
+ // attempt snapshot sync if possible
270
+ await trySnapshotSync(config, log);
271
+
272
+ const epochCache = await EpochCache.create(config.l1Contracts.rollupAddress, config, { dateProvider });
273
+
274
+ const archiver = await createArchiver(
275
+ config,
276
+ { blobSinkClient, epochCache, telemetry, dateProvider },
277
+ { blockUntilSync: !config.skipArchiverInitialSync },
278
+ );
160
279
 
161
280
  // now create the merkle trees and the world state synchronizer
162
281
  const worldStateSynchronizer = await createWorldStateSynchronizer(
@@ -165,12 +284,13 @@ export class AztecNodeService implements AztecNode, Traceable {
165
284
  options.prefilledPublicData,
166
285
  telemetry,
167
286
  );
168
- const proofVerifier = config.realProofs ? await BBCircuitVerifier.new(config) : new TestCircuitVerifier();
287
+ const circuitVerifier = config.realProofs
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,160 @@ 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
+ const blockBuilder = new BlockBuilder(
313
+ { ...config, l1GenesisTime, slotDuration: Number(slotDuration) },
314
+ worldStateSynchronizer,
315
+ archiver,
316
+ dateProvider,
317
+ telemetry,
318
+ );
191
319
 
192
- const validatorClient = createValidatorClient(config, { p2pClient, telemetry, dateProvider, epochCache });
320
+ // We'll accumulate sentinel watchers here
321
+ const watchers: Watcher[] = [];
193
322
 
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
- });
323
+ // Create validator client if required
324
+ const validatorClient = createValidatorClient(config, {
325
+ p2pClient,
326
+ telemetry,
327
+ dateProvider,
328
+ epochCache,
329
+ blockBuilder,
330
+ blockSource: archiver,
331
+ l1ToL2MessageSource: archiver,
332
+ keyStoreManager,
333
+ });
334
+
335
+ // If we have a validator client, register it as a source of offenses for the slasher,
336
+ // and have it register callbacks on the p2p client *before* we start it, otherwise messages
337
+ // like attestations or auths will fail.
338
+ if (validatorClient) {
339
+ watchers.push(validatorClient);
340
+ if (!options.dontStartSequencer) {
341
+ await validatorClient.registerHandlers();
342
+ }
343
+ }
344
+
345
+ // If there's no validator client but alwaysReexecuteBlockProposals is enabled,
346
+ // create a BlockProposalHandler to reexecute block proposals for monitoring
347
+ if (!validatorClient && config.alwaysReexecuteBlockProposals) {
348
+ log.info('Setting up block proposal reexecution for monitoring');
349
+ createBlockProposalHandler(config, {
350
+ blockBuilder,
351
+ epochCache,
352
+ blockSource: archiver,
353
+ l1ToL2MessageSource: archiver,
354
+ p2pClient,
355
+ dateProvider,
356
+ telemetry,
357
+ }).registerForReexecution(p2pClient);
358
+ }
359
+
360
+ // Start world state and wait for it to sync to the archiver.
361
+ await worldStateSynchronizer.start();
362
+
363
+ // Start p2p. Note that it depends on world state to be running.
364
+ await p2pClient.start();
365
+
366
+ const validatorsSentinel = await createSentinel(epochCache, archiver, p2pClient, config);
367
+ if (validatorsSentinel && config.slashInactivityPenalty > 0n) {
368
+ watchers.push(validatorsSentinel);
369
+ }
370
+
371
+ let epochPruneWatcher: EpochPruneWatcher | undefined;
372
+ if (config.slashPrunePenalty > 0n || config.slashDataWithholdingPenalty > 0n) {
373
+ epochPruneWatcher = new EpochPruneWatcher(
374
+ archiver,
375
+ archiver,
376
+ epochCache,
377
+ p2pClient.getTxProvider(),
378
+ blockBuilder,
379
+ config,
380
+ );
381
+ watchers.push(epochPruneWatcher);
382
+ }
383
+
384
+ // We assume we want to slash for invalid attestations unless all max penalties are set to 0
385
+ let attestationsBlockWatcher: AttestationsBlockWatcher | undefined;
386
+ if (config.slashProposeInvalidAttestationsPenalty > 0n || config.slashAttestDescendantOfInvalidPenalty > 0n) {
387
+ attestationsBlockWatcher = new AttestationsBlockWatcher(archiver, epochCache, config);
388
+ watchers.push(attestationsBlockWatcher);
389
+ }
390
+
391
+ // Start p2p-related services once the archiver has completed sync
392
+ void archiver
393
+ .waitForInitialSync()
394
+ .then(async () => {
395
+ await p2pClient.start();
396
+ await validatorsSentinel?.start();
397
+ await epochPruneWatcher?.start();
398
+ await attestationsBlockWatcher?.start();
399
+ log.info(`All p2p services started`);
400
+ })
401
+ .catch(err => log.error('Failed to start p2p services after archiver sync', err));
402
+
403
+ // Validator enabled, create/start relevant service
404
+ let sequencer: SequencerClient | undefined;
405
+ let slasherClient: SlasherClientInterface | undefined;
406
+ if (!config.disableValidator) {
407
+ // We create a slasher only if we have a sequencer, since all slashing actions go through the sequencer publisher
408
+ // as they are executed when the node is selected as proposer.
409
+ const validatorAddresses = keyStoreManager
410
+ ? NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager).getAddresses()
411
+ : [];
412
+
413
+ slasherClient = await createSlasher(
414
+ config,
415
+ config.l1Contracts,
416
+ getPublicClient(config),
417
+ watchers,
418
+ dateProvider,
419
+ epochCache,
420
+ validatorAddresses,
421
+ undefined, // logger
422
+ );
423
+ await slasherClient.start();
424
+
425
+ const l1TxUtils = await createL1TxUtilsWithBlobsFromEthSigner(
426
+ publicClient,
427
+ keyStoreManager!.createAllValidatorPublisherSigners(),
428
+ { ...config, scope: 'sequencer' },
429
+ { telemetry, logger: log.createChild('l1-tx-utils'), dateProvider },
430
+ );
431
+
432
+ // Create and start the sequencer client
433
+ sequencer = await SequencerClient.new(config, {
434
+ ...deps,
435
+ epochCache,
436
+ l1TxUtils,
437
+ validatorClient,
438
+ p2pClient,
439
+ worldStateSynchronizer,
440
+ slasherClient,
441
+ blockBuilder,
442
+ l2BlockSource: archiver,
443
+ l1ToL2MessageSource: archiver,
444
+ telemetry,
445
+ dateProvider,
446
+ blobSinkClient,
447
+ nodeKeyStore: keyStoreManager!,
448
+ });
449
+ }
450
+
451
+ if (!options.dontStartSequencer && sequencer) {
452
+ await sequencer.start();
453
+ log.verbose(`Sequencer started`);
454
+ } else if (sequencer) {
455
+ log.warn(`Sequencer created but not started`);
456
+ }
210
457
 
211
458
  return new AztecNodeService(
212
459
  config,
@@ -215,12 +462,16 @@ export class AztecNodeService implements AztecNode, Traceable {
215
462
  archiver,
216
463
  archiver,
217
464
  archiver,
218
- archiver,
219
465
  worldStateSynchronizer,
220
466
  sequencer,
467
+ slasherClient,
468
+ validatorsSentinel,
469
+ epochPruneWatcher,
221
470
  ethereumChain.chainInfo.id,
222
- config.version,
471
+ config.rollupVersion,
223
472
  new GlobalVariableBuilder(config),
473
+ epochCache,
474
+ packageVersion,
224
475
  proofVerifier,
225
476
  telemetry,
226
477
  log,
@@ -259,6 +510,10 @@ export class AztecNodeService implements AztecNode, Traceable {
259
510
  return Promise.resolve(this.p2pClient.getEnr()?.encodeTxt());
260
511
  }
261
512
 
513
+ public async getAllowedPublicSetup(): Promise<AllowedElement[]> {
514
+ return this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions());
515
+ }
516
+
262
517
  /**
263
518
  * Method to determine if the node is ready to accept transactions.
264
519
  * @returns - Flag indicating the readiness for tx submission.
@@ -268,20 +523,19 @@ export class AztecNodeService implements AztecNode, Traceable {
268
523
  }
269
524
 
270
525
  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
- ]);
526
+ const [nodeVersion, rollupVersion, chainId, enr, contractAddresses, protocolContractAddresses] = await Promise.all([
527
+ this.getNodeVersion(),
528
+ this.getVersion(),
529
+ this.getChainId(),
530
+ this.getEncodedEnr(),
531
+ this.getL1ContractAddresses(),
532
+ this.getProtocolContractAddresses(),
533
+ ]);
280
534
 
281
535
  const nodeInfo: NodeInfo = {
282
536
  nodeVersion,
283
537
  l1ChainId: chainId,
284
- protocolVersion,
538
+ rollupVersion,
285
539
  enr,
286
540
  l1ContractAddresses: contractAddresses,
287
541
  protocolContractAddresses: protocolContractAddresses,
@@ -295,8 +549,29 @@ export class AztecNodeService implements AztecNode, Traceable {
295
549
  * @param number - The block number being requested.
296
550
  * @returns The requested block.
297
551
  */
298
- public async getBlock(number: number): Promise<L2Block | undefined> {
299
- return await this.blockSource.getBlock(number);
552
+ public async getBlock(number: L2BlockNumber): Promise<L2Block | undefined> {
553
+ const blockNumber = number === 'latest' ? await this.getBlockNumber() : number;
554
+ return await this.blockSource.getBlock(blockNumber);
555
+ }
556
+
557
+ /**
558
+ * Get a block specified by its hash.
559
+ * @param blockHash - The block hash being requested.
560
+ * @returns The requested block.
561
+ */
562
+ public async getBlockByHash(blockHash: Fr): Promise<L2Block | undefined> {
563
+ const publishedBlock = await this.blockSource.getPublishedBlockByHash(blockHash);
564
+ return publishedBlock?.block;
565
+ }
566
+
567
+ /**
568
+ * Get a block specified by its archive root.
569
+ * @param archive - The archive root being requested.
570
+ * @returns The requested block.
571
+ */
572
+ public async getBlockByArchive(archive: Fr): Promise<L2Block | undefined> {
573
+ const publishedBlock = await this.blockSource.getPublishedBlockByArchive(archive);
574
+ return publishedBlock?.block;
300
575
  }
301
576
 
302
577
  /**
@@ -309,6 +584,10 @@ export class AztecNodeService implements AztecNode, Traceable {
309
584
  return (await this.blockSource.getBlocks(from, limit)) ?? [];
310
585
  }
311
586
 
587
+ public async getPublishedBlocks(from: number, limit: number): Promise<PublishedL2Block[]> {
588
+ return (await this.blockSource.getPublishedBlocks(from, limit)) ?? [];
589
+ }
590
+
312
591
  /**
313
592
  * Method to fetch the current base fees.
314
593
  * @returns The current base fees.
@@ -318,7 +597,7 @@ export class AztecNodeService implements AztecNode, Traceable {
318
597
  }
319
598
 
320
599
  /**
321
- * Method to fetch the current block number.
600
+ * Method to fetch the latest block number synchronized by the node.
322
601
  * @returns The block number.
323
602
  */
324
603
  public async getBlockNumber(): Promise<number> {
@@ -353,25 +632,8 @@ export class AztecNodeService implements AztecNode, Traceable {
353
632
  return Promise.resolve(this.l1ChainId);
354
633
  }
355
634
 
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;
635
+ public getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
636
+ return this.contractDataSource.getContractClass(id);
375
637
  }
376
638
 
377
639
  public getContract(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
@@ -391,11 +653,12 @@ export class AztecNodeService implements AztecNode, Traceable {
391
653
  /**
392
654
  * Gets all logs that match any of the received tags (i.e. logs with their first field equal to a tag).
393
655
  * @param tags - The tags to filter the logs by.
656
+ * @param logsPerTag - The maximum number of logs to return for each tag. By default no limit is set
394
657
  * @returns For each received tag, an array of matching logs is returned. An empty array implies no logs match
395
658
  * that tag.
396
659
  */
397
- public getLogsByTags(tags: Fr[]): Promise<TxScopedL2Log[][]> {
398
- return this.logsSource.getLogsByTags(tags);
660
+ public getLogsByTags(tags: Fr[], logsPerTag?: number): Promise<TxScopedL2Log[][]> {
661
+ return this.logsSource.getLogsByTags(tags, logsPerTag);
399
662
  }
400
663
 
401
664
  /**
@@ -421,17 +684,19 @@ export class AztecNodeService implements AztecNode, Traceable {
421
684
  * @param tx - The transaction to be submitted.
422
685
  */
423
686
  public async sendTx(tx: Tx) {
687
+ await this.#sendTx(tx);
688
+ }
689
+
690
+ async #sendTx(tx: Tx) {
424
691
  const timer = new Timer();
425
- const txHash = (await tx.getTxHash()).toString();
692
+ const txHash = tx.getTxHash().toString();
426
693
 
427
694
  const valid = await this.isValidTx(tx);
428
695
  if (valid.result !== 'valid') {
429
696
  const reason = valid.reason.join(', ');
430
697
  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;
698
+ this.log.warn(`Received invalid tx ${txHash}: ${reason}`, { txHash });
699
+ throw new Error(`Invalid tx: ${reason}`);
435
700
  }
436
701
 
437
702
  await this.p2pClient!.sendTx(tx);
@@ -457,7 +722,7 @@ export class AztecNodeService implements AztecNode, Traceable {
457
722
  return txReceipt;
458
723
  }
459
724
 
460
- public getTxEffect(txHash: TxHash): Promise<InBlock<TxEffect> | undefined> {
725
+ public getTxEffect(txHash: TxHash): Promise<IndexedTxEffect | undefined> {
461
726
  return this.blockSource.getTxEffect(txHash);
462
727
  }
463
728
 
@@ -465,89 +730,120 @@ export class AztecNodeService implements AztecNode, Traceable {
465
730
  * Method to stop the aztec node.
466
731
  */
467
732
  public async stop() {
468
- this.log.info(`Stopping`);
469
- await this.sequencer?.stop();
470
- await this.p2pClient.stop();
471
- await this.worldStateSynchronizer.stop();
733
+ this.log.info(`Stopping Aztec Node`);
734
+ await tryStop(this.validatorsSentinel);
735
+ await tryStop(this.epochPruneWatcher);
736
+ await tryStop(this.slasherClient);
737
+ await tryStop(this.proofVerifier);
738
+ await tryStop(this.sequencer);
739
+ await tryStop(this.p2pClient);
740
+ await tryStop(this.worldStateSynchronizer);
472
741
  await tryStop(this.blockSource);
473
- await this.telemetry.stop();
474
- this.log.info(`Stopped`);
742
+ await tryStop(this.telemetry);
743
+ this.log.info(`Stopped Aztec Node`);
475
744
  }
476
745
 
477
746
  /**
478
747
  * Method to retrieve pending txs.
748
+ * @param limit - The number of items to returns
749
+ * @param after - The last known pending tx. Used for pagination
479
750
  * @returns - The pending txs.
480
751
  */
481
- public getPendingTxs() {
482
- return this.p2pClient!.getPendingTxs();
752
+ public getPendingTxs(limit?: number, after?: TxHash): Promise<Tx[]> {
753
+ return this.p2pClient!.getPendingTxs(limit, after);
483
754
  }
484
755
 
485
- public async getPendingTxCount() {
486
- const pendingTxs = await this.getPendingTxs();
487
- return pendingTxs.length;
756
+ public getPendingTxCount(): Promise<number> {
757
+ return this.p2pClient!.getPendingTxCount();
488
758
  }
489
759
 
490
760
  /**
491
- * Method to retrieve a single tx from the mempool or unfinalised chain.
761
+ * Method to retrieve a single tx from the mempool or unfinalized chain.
492
762
  * @param txHash - The transaction hash to return.
493
763
  * @returns - The tx if it exists.
494
764
  */
495
- public getTxByHash(txHash: TxHash) {
765
+ public getTxByHash(txHash: TxHash): Promise<Tx | undefined> {
496
766
  return Promise.resolve(this.p2pClient!.getTxByHashFromPool(txHash));
497
767
  }
498
768
 
499
769
  /**
500
- * Method to retrieve txs from the mempool or unfinalised chain.
770
+ * Method to retrieve txs from the mempool or unfinalized chain.
501
771
  * @param txHash - The transaction hash to return.
502
772
  * @returns - The txs if it exists.
503
773
  */
504
- public async getTxsByHash(txHashes: TxHash[]) {
774
+ public async getTxsByHash(txHashes: TxHash[]): Promise<Tx[]> {
505
775
  return compactArray(await Promise.all(txHashes.map(txHash => this.getTxByHash(txHash))));
506
776
  }
507
777
 
508
778
  /**
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
779
+ * Find the indexes of the given leaves in the given tree along with a block metadata pointing to the block in which
780
+ * the leaves were inserted.
781
+ * @param blockNumber - The block number at which to get the data or 'latest' for latest data.
511
782
  * @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.
783
+ * @param leafValues - The values to search for.
784
+ * @returns The indices of leaves and the block metadata of a block in which the leaves were inserted.
514
785
  */
515
786
  public async findLeavesIndexes(
516
787
  blockNumber: L2BlockNumber,
517
788
  treeId: MerkleTreeId,
518
789
  leafValues: Fr[],
519
- ): Promise<(bigint | undefined)[]> {
790
+ ): Promise<(InBlock<bigint> | undefined)[]> {
520
791
  const committedDb = await this.#getWorldState(blockNumber);
521
- return await committedDb.findLeafIndices(
792
+ const maybeIndices = await committedDb.findLeafIndices(
522
793
  treeId,
523
794
  leafValues.map(x => x.toBuffer()),
524
795
  );
525
- }
796
+ // We filter out undefined values
797
+ const indices = maybeIndices.filter(x => x !== undefined) as bigint[];
526
798
 
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
- }
799
+ // Now we find the block numbers for the indices
800
+ const blockNumbers = await committedDb.getBlockNumbersForLeafIndices(treeId, indices);
542
801
 
543
- public async findNullifiersIndexesWithBlock(
544
- blockNumber: L2BlockNumber,
545
- nullifiers: Fr[],
546
- ): Promise<(InBlock<bigint> | undefined)[]> {
547
- if (blockNumber === 'latest') {
548
- blockNumber = await this.getBlockNumber();
802
+ // If any of the block numbers are undefined, we throw an error.
803
+ for (let i = 0; i < indices.length; i++) {
804
+ if (blockNumbers[i] === undefined) {
805
+ throw new Error(`Block number is undefined for leaf index ${indices[i]} in tree ${MerkleTreeId[treeId]}`);
806
+ }
549
807
  }
550
- return this.nullifierSource.findNullifiersIndexesWithBlock(blockNumber, nullifiers);
808
+
809
+ // Get unique block numbers in order to optimize num calls to getLeafValue function.
810
+ const uniqueBlockNumbers = [...new Set(blockNumbers.filter(x => x !== undefined))];
811
+
812
+ // Now we obtain the block hashes from the archive tree by calling await `committedDb.getLeafValue(treeId, index)`
813
+ // (note that block number corresponds to the leaf index in the archive tree).
814
+ const blockHashes = await Promise.all(
815
+ uniqueBlockNumbers.map(blockNumber => {
816
+ return committedDb.getLeafValue(MerkleTreeId.ARCHIVE, blockNumber!);
817
+ }),
818
+ );
819
+
820
+ // If any of the block hashes are undefined, we throw an error.
821
+ for (let i = 0; i < uniqueBlockNumbers.length; i++) {
822
+ if (blockHashes[i] === undefined) {
823
+ throw new Error(`Block hash is undefined for block number ${uniqueBlockNumbers[i]}`);
824
+ }
825
+ }
826
+
827
+ // Create InBlock objects by combining indices, blockNumbers and blockHashes and return them.
828
+ return maybeIndices.map((index, i) => {
829
+ if (index === undefined) {
830
+ return undefined;
831
+ }
832
+ const blockNumber = blockNumbers[i];
833
+ if (blockNumber === undefined) {
834
+ return undefined;
835
+ }
836
+ const blockHashIndex = uniqueBlockNumbers.indexOf(blockNumber);
837
+ const blockHash = blockHashes[blockHashIndex];
838
+ if (!blockHash) {
839
+ return undefined;
840
+ }
841
+ return {
842
+ l2BlockNumber: Number(blockNumber),
843
+ l2BlockHash: L2BlockHash.fromField(blockHash),
844
+ data: index,
845
+ };
846
+ });
551
847
  }
552
848
 
553
849
  /**
@@ -578,6 +874,31 @@ export class AztecNodeService implements AztecNode, Traceable {
578
874
  return committedDb.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex);
579
875
  }
580
876
 
877
+ public async getArchiveMembershipWitness(
878
+ blockNumber: L2BlockNumber,
879
+ archive: Fr,
880
+ ): Promise<MembershipWitness<typeof ARCHIVE_HEIGHT> | undefined> {
881
+ const committedDb = await this.#getWorldState(blockNumber);
882
+ const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.ARCHIVE>(MerkleTreeId.ARCHIVE, [archive]);
883
+ return pathAndIndex === undefined
884
+ ? undefined
885
+ : MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
886
+ }
887
+
888
+ public async getNoteHashMembershipWitness(
889
+ blockNumber: L2BlockNumber,
890
+ noteHash: Fr,
891
+ ): Promise<MembershipWitness<typeof NOTE_HASH_TREE_HEIGHT> | undefined> {
892
+ const committedDb = await this.#getWorldState(blockNumber);
893
+ const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.NOTE_HASH_TREE>(
894
+ MerkleTreeId.NOTE_HASH_TREE,
895
+ [noteHash],
896
+ );
897
+ return pathAndIndex === undefined
898
+ ? undefined
899
+ : MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
900
+ }
901
+
581
902
  /**
582
903
  * Returns the index and a sibling path for a leaf in the committed l1 to l2 data tree.
583
904
  * @param blockNumber - The block number at which to get the data.
@@ -588,16 +909,19 @@ export class AztecNodeService implements AztecNode, Traceable {
588
909
  blockNumber: L2BlockNumber,
589
910
  l1ToL2Message: Fr,
590
911
  ): Promise<[bigint, SiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>] | undefined> {
591
- const index = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
592
- if (index === undefined) {
912
+ const db = await this.#getWorldState(blockNumber);
913
+ const [witness] = await db.findSiblingPaths(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, [l1ToL2Message]);
914
+ if (!witness) {
593
915
  return undefined;
594
916
  }
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];
917
+
918
+ // REFACTOR: Return a MembershipWitness object
919
+ return [witness.index, witness.path];
920
+ }
921
+
922
+ public async getL1ToL2MessageBlock(l1ToL2Message: Fr): Promise<number | undefined> {
923
+ const messageIndex = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
924
+ return messageIndex ? InboxLeaf.l2BlockFromIndex(messageIndex) : undefined;
601
925
  }
602
926
 
603
927
  /**
@@ -606,89 +930,18 @@ export class AztecNodeService implements AztecNode, Traceable {
606
930
  * @returns Whether the message is synced and ready to be included in a block.
607
931
  */
608
932
  public async isL1ToL2MessageSynced(l1ToL2Message: Fr): Promise<boolean> {
609
- return (await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message)) !== undefined;
933
+ const messageIndex = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
934
+ return messageIndex !== undefined;
610
935
  }
611
936
 
612
937
  /**
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.
938
+ * Returns all the L2 to L1 messages in a block.
618
939
  * @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.
940
+ * @returns The L2 to L1 messages (undefined if the block number is not found).
621
941
  */
622
- public async getL2ToL1MessageMembershipWitness(
623
- blockNumber: L2BlockNumber,
624
- l2ToL1Message: Fr,
625
- ): Promise<[bigint, SiblingPath<number>]> {
942
+ public async getL2ToL1Messages(blockNumber: L2BlockNumber): Promise<Fr[][] | undefined> {
626
943
  const block = await this.blockSource.getBlock(blockNumber === 'latest' ? await this.getBlockNumber() : blockNumber);
627
-
628
- if (block === undefined) {
629
- throw new Error('Block is not defined');
630
- }
631
-
632
- const l2ToL1Messages = block.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs);
633
-
634
- // Find index of message
635
- let indexOfMsgInSubtree = -1;
636
- const indexOfMsgTx = l2ToL1Messages.findIndex(msgs => {
637
- const idx = msgs.findIndex(msg => msg.equals(l2ToL1Message));
638
- indexOfMsgInSubtree = Math.max(indexOfMsgInSubtree, idx);
639
- return idx !== -1;
640
- });
641
-
642
- if (indexOfMsgTx === -1) {
643
- throw new Error('The L2ToL1Message you are trying to prove inclusion of does not exist');
644
- }
645
-
646
- const tempStores: AztecKVStore[] = [];
647
-
648
- // Construct message subtrees
649
- const l2toL1Subtrees = await Promise.all(
650
- l2ToL1Messages.map(async (msgs, i) => {
651
- const store = openTmpStore(true);
652
- tempStores.push(store);
653
- const treeHeight = msgs.length <= 1 ? 1 : Math.ceil(Math.log2(msgs.length));
654
- const tree = new StandardTree(store, new SHA256Trunc(), `temp_msgs_subtrees_${i}`, treeHeight, 0n, Fr);
655
- await tree.appendLeaves(msgs);
656
- return tree;
657
- }),
658
- );
659
-
660
- // path of the input msg from leaf -> first out hash calculated in base rolllup
661
- const subtreePathOfL2ToL1Message = await l2toL1Subtrees[indexOfMsgTx].getSiblingPath(
662
- BigInt(indexOfMsgInSubtree),
663
- true,
664
- );
665
-
666
- const numTxs = block.body.txEffects.length;
667
- if (numTxs === 1) {
668
- return [BigInt(indexOfMsgInSubtree), subtreePathOfL2ToL1Message];
669
- }
670
-
671
- const l2toL1SubtreeRoots = l2toL1Subtrees.map(t => Fr.fromBuffer(t.getRoot(true)));
672
- const maxTreeHeight = Math.ceil(Math.log2(l2toL1SubtreeRoots.length));
673
- // The root of this tree is the out_hash calculated in Noir => we truncate to match Noir's SHA
674
- const outHashTree = new UnbalancedTree(new SHA256Trunc(), 'temp_outhash_sibling_path', maxTreeHeight, Fr);
675
- await outHashTree.appendLeaves(l2toL1SubtreeRoots);
676
-
677
- const pathOfTxInOutHashTree = await outHashTree.getSiblingPath(l2toL1SubtreeRoots[indexOfMsgTx].toBigInt());
678
- // Append subtree path to out hash tree path
679
- const mergedPath = subtreePathOfL2ToL1Message.toBufferArray().concat(pathOfTxInOutHashTree.toBufferArray());
680
- // Append binary index of subtree path to binary index of out hash tree path
681
- const mergedIndex = parseInt(
682
- indexOfMsgTx
683
- .toString(2)
684
- .concat(indexOfMsgInSubtree.toString(2).padStart(l2toL1Subtrees[indexOfMsgTx].getDepth(), '0')),
685
- 2,
686
- );
687
-
688
- // clear the tmp stores
689
- await Promise.all(tempStores.map(store => store.delete()));
690
-
691
- return [BigInt(mergedIndex), new SiblingPath(mergedPath.length, mergedPath)];
944
+ return block?.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs);
692
945
  }
693
946
 
694
947
  /**
@@ -730,24 +983,18 @@ export class AztecNodeService implements AztecNode, Traceable {
730
983
  nullifier: Fr,
731
984
  ): Promise<NullifierMembershipWitness | undefined> {
732
985
  const db = await this.#getWorldState(blockNumber);
733
- const index = (await db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]))[0];
734
- if (!index) {
986
+ const [witness] = await db.findSiblingPaths(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]);
987
+ if (!witness) {
735
988
  return undefined;
736
989
  }
737
990
 
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
-
991
+ const { index, path } = witness;
992
+ const leafPreimage = await db.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index);
746
993
  if (!leafPreimage) {
747
994
  return undefined;
748
995
  }
749
996
 
750
- return new NullifierMembershipWitness(BigInt(index), leafPreimage as NullifierLeafPreimage, siblingPath);
997
+ return new NullifierMembershipWitness(index, leafPreimage as NullifierLeafPreimage, path);
751
998
  }
752
999
 
753
1000
  /**
@@ -779,14 +1026,11 @@ export class AztecNodeService implements AztecNode, Traceable {
779
1026
  }
780
1027
  const preimageData = (await committedDb.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index))!;
781
1028
 
782
- const siblingPath = await committedDb.getSiblingPath<typeof NULLIFIER_TREE_HEIGHT>(
783
- MerkleTreeId.NULLIFIER_TREE,
784
- BigInt(index),
785
- );
1029
+ const siblingPath = await committedDb.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, BigInt(index));
786
1030
  return new NullifierMembershipWitness(BigInt(index), preimageData as NullifierLeafPreimage, siblingPath);
787
1031
  }
788
1032
 
789
- async getPublicDataTreeWitness(blockNumber: L2BlockNumber, leafSlot: Fr): Promise<PublicDataWitness | undefined> {
1033
+ async getPublicDataWitness(blockNumber: L2BlockNumber, leafSlot: Fr): Promise<PublicDataWitness | undefined> {
790
1034
  const committedDb = await this.#getWorldState(blockNumber);
791
1035
  const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
792
1036
  if (!lowLeafResult) {
@@ -796,10 +1040,7 @@ export class AztecNodeService implements AztecNode, Traceable {
796
1040
  MerkleTreeId.PUBLIC_DATA_TREE,
797
1041
  lowLeafResult.index,
798
1042
  )) as PublicDataTreeLeafPreimage;
799
- const path = await committedDb.getSiblingPath<typeof PUBLIC_DATA_TREE_HEIGHT>(
800
- MerkleTreeId.PUBLIC_DATA_TREE,
801
- lowLeafResult.index,
802
- );
1043
+ const path = await committedDb.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index);
803
1044
  return new PublicDataWitness(lowLeafResult.index, preimage, path);
804
1045
  }
805
1046
  }
@@ -827,7 +1068,7 @@ export class AztecNodeService implements AztecNode, Traceable {
827
1068
  MerkleTreeId.PUBLIC_DATA_TREE,
828
1069
  lowLeafResult.index,
829
1070
  )) as PublicDataTreeLeafPreimage;
830
- return preimage.value;
1071
+ return preimage.leaf.value;
831
1072
  }
832
1073
 
833
1074
  /**
@@ -840,23 +1081,55 @@ export class AztecNodeService implements AztecNode, Traceable {
840
1081
  : this.blockSource.getBlockHeader(blockNumber);
841
1082
  }
842
1083
 
1084
+ /**
1085
+ * Get a block header specified by its hash.
1086
+ * @param blockHash - The block hash being requested.
1087
+ * @returns The requested block header.
1088
+ */
1089
+ public async getBlockHeaderByHash(blockHash: Fr): Promise<BlockHeader | undefined> {
1090
+ return await this.blockSource.getBlockHeaderByHash(blockHash);
1091
+ }
1092
+
1093
+ /**
1094
+ * Get a block header specified by its archive root.
1095
+ * @param archive - The archive root being requested.
1096
+ * @returns The requested block header.
1097
+ */
1098
+ public async getBlockHeaderByArchive(archive: Fr): Promise<BlockHeader | undefined> {
1099
+ return await this.blockSource.getBlockHeaderByArchive(archive);
1100
+ }
1101
+
843
1102
  /**
844
1103
  * Simulates the public part of a transaction with the current state.
845
1104
  * @param tx - The transaction to simulate.
846
1105
  **/
847
- @trackSpan('AztecNodeService.simulatePublicCalls', async (tx: Tx) => ({
848
- [Attributes.TX_HASH]: (await tx.getTxHash()).toString(),
1106
+ @trackSpan('AztecNodeService.simulatePublicCalls', (tx: Tx) => ({
1107
+ [Attributes.TX_HASH]: tx.getTxHash().toString(),
849
1108
  }))
850
1109
  public async simulatePublicCalls(tx: Tx, skipFeeEnforcement = false): Promise<PublicSimulationOutput> {
851
- const txHash = await tx.getTxHash();
1110
+ // Check total gas limit for simulation
1111
+ const gasSettings = tx.data.constants.txContext.gasSettings;
1112
+ const txGasLimit = gasSettings.gasLimits.l2Gas;
1113
+ const teardownGasLimit = gasSettings.teardownGasLimits.l2Gas;
1114
+ if (txGasLimit + teardownGasLimit > this.config.rpcSimulatePublicMaxGasLimit) {
1115
+ throw new BadRequestError(
1116
+ `Transaction total gas limit ${
1117
+ txGasLimit + teardownGasLimit
1118
+ } (${txGasLimit} + ${teardownGasLimit}) exceeds maximum gas limit ${
1119
+ this.config.rpcSimulatePublicMaxGasLimit
1120
+ } for simulation`,
1121
+ );
1122
+ }
1123
+
1124
+ const txHash = tx.getTxHash();
852
1125
  const blockNumber = (await this.blockSource.getBlockNumber()) + 1;
853
1126
 
854
1127
  // 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;
1128
+ const coinbase = EthAddress.ZERO;
1129
+ const feeRecipient = AztecAddress.ZERO;
857
1130
 
858
1131
  const newGlobalVariables = await this.globalVariableBuilder.buildGlobalVariables(
859
- new Fr(blockNumber),
1132
+ blockNumber,
860
1133
  coinbase,
861
1134
  feeRecipient,
862
1135
  );
@@ -865,7 +1138,6 @@ export class AztecNodeService implements AztecNode, Traceable {
865
1138
  new DateProvider(),
866
1139
  this.telemetry,
867
1140
  );
868
- const fork = await this.worldStateSynchronizer.fork();
869
1141
 
870
1142
  this.log.verbose(`Simulating public calls for tx ${txHash}`, {
871
1143
  globalVariables: newGlobalVariables.toInspect(),
@@ -873,11 +1145,20 @@ export class AztecNodeService implements AztecNode, Traceable {
873
1145
  blockNumber,
874
1146
  });
875
1147
 
1148
+ const merkleTreeFork = await this.worldStateSynchronizer.fork();
876
1149
  try {
877
- const processor = publicProcessorFactory.create(fork, newGlobalVariables, skipFeeEnforcement);
1150
+ const config = PublicSimulatorConfig.from({
1151
+ skipFeeEnforcement,
1152
+ collectDebugLogs: true,
1153
+ collectHints: false,
1154
+ collectCallMetadata: true,
1155
+ maxDebugLogMemoryReads: this.config.rpcSimulatePublicMaxDebugLogMemoryReads,
1156
+ collectStatistics: false,
1157
+ });
1158
+ const processor = publicProcessorFactory.create(merkleTreeFork, newGlobalVariables, config);
878
1159
 
879
1160
  // REFACTOR: Consider merging ProcessReturnValues into ProcessedTx
880
- const [processedTxs, failedTxs, returns] = await processor.process([tx]);
1161
+ const [processedTxs, failedTxs, _usedTxs, returns] = await processor.process([tx]);
881
1162
  // REFACTOR: Consider returning the error rather than throwing
882
1163
  if (failedTxs.length) {
883
1164
  this.log.warn(`Simulated tx ${txHash} fails: ${failedTxs[0].error}`, { txHash });
@@ -887,13 +1168,13 @@ export class AztecNodeService implements AztecNode, Traceable {
887
1168
  const [processedTx] = processedTxs;
888
1169
  return new PublicSimulationOutput(
889
1170
  processedTx.revertReason,
890
- processedTx.constants,
1171
+ processedTx.globalVariables,
891
1172
  processedTx.txEffect,
892
1173
  returns,
893
1174
  processedTx.gasUsed,
894
1175
  );
895
1176
  } finally {
896
- await fork.close();
1177
+ await merkleTreeFork.close();
897
1178
  }
898
1179
  }
899
1180
 
@@ -901,24 +1182,42 @@ export class AztecNodeService implements AztecNode, Traceable {
901
1182
  tx: Tx,
902
1183
  { isSimulation, skipFeeEnforcement }: { isSimulation?: boolean; skipFeeEnforcement?: boolean } = {},
903
1184
  ): Promise<TxValidationResult> {
904
- const blockNumber = (await this.blockSource.getBlockNumber()) + 1;
905
1185
  const db = this.worldStateSynchronizer.getCommitted();
906
1186
  const verifier = isSimulation ? undefined : this.proofVerifier;
1187
+
1188
+ // We accept transactions if they are not expired by the next slot (checked based on the IncludeByTimestamp field)
1189
+ const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
1190
+ const blockNumber = (await this.blockSource.getBlockNumber()) + 1;
907
1191
  const validator = createValidatorForAcceptingTxs(db, this.contractDataSource, verifier, {
1192
+ timestamp: nextSlotTimestamp,
908
1193
  blockNumber,
909
1194
  l1ChainId: this.l1ChainId,
910
- setupAllowList: this.config.allowedInSetup ?? (await getDefaultAllowedSetupFunctions()),
1195
+ rollupVersion: this.version,
1196
+ setupAllowList: this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions()),
911
1197
  gasFees: await this.getCurrentBaseFees(),
912
1198
  skipFeeEnforcement,
1199
+ txsPermitted: !this.config.disableTransactions,
913
1200
  });
914
1201
 
915
1202
  return await validator.validateTx(tx);
916
1203
  }
917
1204
 
918
- public async setConfig(config: Partial<SequencerConfig & ProverConfig>): Promise<void> {
919
- const newConfig = { ...this.config, ...config };
920
- await this.sequencer?.updateSequencerConfig(config);
1205
+ public getConfig(): Promise<AztecNodeAdminConfig> {
1206
+ const schema = AztecNodeAdminConfigSchema;
1207
+ const keys = schema.keyof().options;
1208
+ return Promise.resolve(pick(this.config, ...keys));
1209
+ }
921
1210
 
1211
+ public async setConfig(config: Partial<AztecNodeAdminConfig>): Promise<void> {
1212
+ const newConfig = { ...this.config, ...config };
1213
+ this.sequencer?.updateConfig(config);
1214
+ this.slasherClient?.updateConfig(config);
1215
+ this.validatorsSentinel?.updateConfig(config);
1216
+ await this.p2pClient.updateP2PConfig(config);
1217
+ const archiver = this.blockSource as Archiver;
1218
+ if ('updateConfig' in archiver) {
1219
+ archiver.updateConfig(config);
1220
+ }
922
1221
  if (newConfig.realProofs !== this.config.realProofs) {
923
1222
  this.proofVerifier = config.realProofs ? await BBCircuitVerifier.new(newConfig) : new TestCircuitVerifier();
924
1223
  }
@@ -928,31 +1227,140 @@ export class AztecNodeService implements AztecNode, Traceable {
928
1227
 
929
1228
  public getProtocolContractAddresses(): Promise<ProtocolContractAddresses> {
930
1229
  return Promise.resolve({
931
- classRegisterer: ProtocolContractAddress.ContractClassRegisterer,
1230
+ classRegistry: ProtocolContractAddress.ContractClassRegistry,
932
1231
  feeJuice: ProtocolContractAddress.FeeJuice,
933
- instanceDeployer: ProtocolContractAddress.ContractInstanceDeployer,
1232
+ instanceRegistry: ProtocolContractAddress.ContractInstanceRegistry,
934
1233
  multiCallEntrypoint: ProtocolContractAddress.MultiCallEntrypoint,
935
1234
  });
936
1235
  }
937
1236
 
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);
1237
+ public registerContractFunctionSignatures(signatures: string[]): Promise<void> {
1238
+ return this.contractDataSource.registerContractFunctionSignatures(signatures);
1239
+ }
1240
+
1241
+ public getValidatorsStats(): Promise<ValidatorsStats> {
1242
+ return this.validatorsSentinel?.computeStats() ?? Promise.resolve({ stats: {}, slotWindow: 0 });
942
1243
  }
943
1244
 
944
- public registerContractFunctionSignatures(_address: AztecAddress, signatures: string[]): Promise<void> {
945
- return this.contractDataSource.registerContractFunctionSignatures(_address, signatures);
1245
+ public getValidatorStats(
1246
+ validatorAddress: EthAddress,
1247
+ fromSlot?: SlotNumber,
1248
+ toSlot?: SlotNumber,
1249
+ ): Promise<SingleValidatorStats | undefined> {
1250
+ return this.validatorsSentinel?.getValidatorStats(validatorAddress, fromSlot, toSlot) ?? Promise.resolve(undefined);
1251
+ }
1252
+
1253
+ public async startSnapshotUpload(location: string): Promise<void> {
1254
+ // Note that we are forcefully casting the blocksource as an archiver
1255
+ // We break support for archiver running remotely to the node
1256
+ const archiver = this.blockSource as Archiver;
1257
+ if (!('backupTo' in archiver)) {
1258
+ this.metrics.recordSnapshotError();
1259
+ throw new Error('Archiver implementation does not support backups. Cannot generate snapshot.');
1260
+ }
1261
+
1262
+ // Test that the archiver has done an initial sync.
1263
+ if (!archiver.isInitialSyncComplete()) {
1264
+ this.metrics.recordSnapshotError();
1265
+ throw new Error(`Archiver initial sync not complete. Cannot start snapshot.`);
1266
+ }
1267
+
1268
+ // And it has an L2 block hash
1269
+ const l2BlockHash = await archiver.getL2Tips().then(tips => tips.latest.hash);
1270
+ if (!l2BlockHash) {
1271
+ this.metrics.recordSnapshotError();
1272
+ throw new Error(`Archiver has no latest L2 block hash downloaded. Cannot start snapshot.`);
1273
+ }
1274
+
1275
+ if (this.isUploadingSnapshot) {
1276
+ this.metrics.recordSnapshotError();
1277
+ throw new Error(`Snapshot upload already in progress. Cannot start another one until complete.`);
1278
+ }
1279
+
1280
+ // Do not wait for the upload to be complete to return to the caller, but flag that an operation is in progress
1281
+ this.isUploadingSnapshot = true;
1282
+ const timer = new Timer();
1283
+ void uploadSnapshot(location, this.blockSource as Archiver, this.worldStateSynchronizer, this.config, this.log)
1284
+ .then(() => {
1285
+ this.isUploadingSnapshot = false;
1286
+ this.metrics.recordSnapshot(timer.ms());
1287
+ })
1288
+ .catch(err => {
1289
+ this.isUploadingSnapshot = false;
1290
+ this.metrics.recordSnapshotError();
1291
+ this.log.error(`Error uploading snapshot: ${err}`);
1292
+ });
1293
+
1294
+ return Promise.resolve();
946
1295
  }
947
1296
 
948
- public flushTxs(): Promise<void> {
949
- if (!this.sequencer) {
950
- throw new Error(`Sequencer is not initialized`);
1297
+ public async rollbackTo(targetBlock: number, force?: boolean): Promise<void> {
1298
+ const archiver = this.blockSource as Archiver;
1299
+ if (!('rollbackTo' in archiver)) {
1300
+ throw new Error('Archiver implementation does not support rollbacks.');
951
1301
  }
952
- this.sequencer.flush();
1302
+
1303
+ const finalizedBlock = await archiver.getL2Tips().then(tips => tips.finalized.number);
1304
+ if (targetBlock < finalizedBlock) {
1305
+ if (force) {
1306
+ this.log.warn(`Clearing world state database to allow rolling back behind finalized block ${finalizedBlock}`);
1307
+ await this.worldStateSynchronizer.clear();
1308
+ await this.p2pClient.clear();
1309
+ } else {
1310
+ throw new Error(`Cannot rollback to block ${targetBlock} as it is before finalized ${finalizedBlock}`);
1311
+ }
1312
+ }
1313
+
1314
+ try {
1315
+ this.log.info(`Pausing archiver and world state sync to start rollback`);
1316
+ await archiver.stop();
1317
+ await this.worldStateSynchronizer.stopSync();
1318
+ const currentBlock = await archiver.getBlockNumber();
1319
+ const blocksToUnwind = currentBlock - targetBlock;
1320
+ this.log.info(`Unwinding ${count(blocksToUnwind, 'block')} from L2 block ${currentBlock} to ${targetBlock}`);
1321
+ await archiver.rollbackTo(targetBlock);
1322
+ this.log.info(`Unwinding complete.`);
1323
+ } catch (err) {
1324
+ this.log.error(`Error during rollback`, err);
1325
+ throw err;
1326
+ } finally {
1327
+ this.log.info(`Resuming world state and archiver sync.`);
1328
+ this.worldStateSynchronizer.resumeSync();
1329
+ archiver.resume();
1330
+ }
1331
+ }
1332
+
1333
+ public async pauseSync(): Promise<void> {
1334
+ this.log.info(`Pausing archiver and world state sync`);
1335
+ await (this.blockSource as Archiver).stop();
1336
+ await this.worldStateSynchronizer.stopSync();
1337
+ }
1338
+
1339
+ public resumeSync(): Promise<void> {
1340
+ this.log.info(`Resuming world state and archiver sync.`);
1341
+ this.worldStateSynchronizer.resumeSync();
1342
+ (this.blockSource as Archiver).resume();
953
1343
  return Promise.resolve();
954
1344
  }
955
1345
 
1346
+ public getSlashPayloads(): Promise<SlashPayloadRound[]> {
1347
+ if (!this.slasherClient) {
1348
+ throw new Error(`Slasher client not enabled`);
1349
+ }
1350
+ return this.slasherClient.getSlashPayloads();
1351
+ }
1352
+
1353
+ public getSlashOffenses(round: bigint | 'all' | 'current'): Promise<Offense[]> {
1354
+ if (!this.slasherClient) {
1355
+ throw new Error(`Slasher client not enabled`);
1356
+ }
1357
+ if (round === 'all') {
1358
+ return this.slasherClient.getPendingOffenses();
1359
+ } else {
1360
+ return this.slasherClient.gatherOffensesForRound(round === 'current' ? undefined : BigInt(round));
1361
+ }
1362
+ }
1363
+
956
1364
  /**
957
1365
  * Returns an instance of MerkleTreeOperations having first ensured the world state is fully synched
958
1366
  * @param blockNumber - The block number at which to get the data.