@aztec/aztec-node 0.0.0-test.1 → 0.0.1-commit.5476d83

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 +93 -52
  8. package/dest/aztec-node/server.d.ts.map +1 -1
  9. package/dest/aztec-node/server.js +535 -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 +695 -279
  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,
@@ -41,34 +63,44 @@ import type {
41
63
  NodeInfo,
42
64
  ProtocolContractAddresses,
43
65
  } 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';
66
+ import { GasFees } from '@aztec/stdlib/gas';
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,
305
+ telemetry,
306
+ deps.p2pClientDeps,
307
+ );
308
+
309
+ // We should really not be modifying the config object
310
+ config.txPublicSetupAllowList = config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions());
311
+
312
+ const blockBuilder = new BlockBuilder(
313
+ { ...config, l1GenesisTime, slotDuration: Number(slotDuration) },
314
+ worldStateSynchronizer,
315
+ archiver,
316
+ dateProvider,
183
317
  telemetry,
184
318
  );
185
319
 
186
- const slasherClient = createSlasherClient(config, archiver, telemetry);
320
+ // We'll accumulate sentinel watchers here
321
+ const watchers: Watcher[] = [];
322
+
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
+ }
187
344
 
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`);
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
+ }
191
359
 
192
- const validatorClient = createValidatorClient(config, { p2pClient, telemetry, dateProvider, epochCache });
360
+ // Start world state and wait for it to sync to the archiver.
361
+ await worldStateSynchronizer.start();
193
362
 
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
- });
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.
@@ -317,8 +596,16 @@ export class AztecNodeService implements AztecNode, Traceable {
317
596
  return await this.globalVariableBuilder.getCurrentBaseFees();
318
597
  }
319
598
 
599
+ public async getMaxPriorityFees(): Promise<GasFees> {
600
+ for await (const tx of this.p2pClient.iteratePendingTxs()) {
601
+ return tx.getGasSettings().maxPriorityFeesPerGas;
602
+ }
603
+
604
+ return GasFees.from({ feePerDaGas: 0n, feePerL2Gas: 0n });
605
+ }
606
+
320
607
  /**
321
- * Method to fetch the current block number.
608
+ * Method to fetch the latest block number synchronized by the node.
322
609
  * @returns The block number.
323
610
  */
324
611
  public async getBlockNumber(): Promise<number> {
@@ -353,25 +640,8 @@ export class AztecNodeService implements AztecNode, Traceable {
353
640
  return Promise.resolve(this.l1ChainId);
354
641
  }
355
642
 
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;
643
+ public getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
644
+ return this.contractDataSource.getContractClass(id);
375
645
  }
376
646
 
377
647
  public getContract(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
@@ -391,11 +661,12 @@ export class AztecNodeService implements AztecNode, Traceable {
391
661
  /**
392
662
  * Gets all logs that match any of the received tags (i.e. logs with their first field equal to a tag).
393
663
  * @param tags - The tags to filter the logs by.
664
+ * @param logsPerTag - The maximum number of logs to return for each tag. By default no limit is set
394
665
  * @returns For each received tag, an array of matching logs is returned. An empty array implies no logs match
395
666
  * that tag.
396
667
  */
397
- public getLogsByTags(tags: Fr[]): Promise<TxScopedL2Log[][]> {
398
- return this.logsSource.getLogsByTags(tags);
668
+ public getLogsByTags(tags: Fr[], logsPerTag?: number): Promise<TxScopedL2Log[][]> {
669
+ return this.logsSource.getLogsByTags(tags, logsPerTag);
399
670
  }
400
671
 
401
672
  /**
@@ -421,17 +692,19 @@ export class AztecNodeService implements AztecNode, Traceable {
421
692
  * @param tx - The transaction to be submitted.
422
693
  */
423
694
  public async sendTx(tx: Tx) {
695
+ await this.#sendTx(tx);
696
+ }
697
+
698
+ async #sendTx(tx: Tx) {
424
699
  const timer = new Timer();
425
- const txHash = (await tx.getTxHash()).toString();
700
+ const txHash = tx.getTxHash().toString();
426
701
 
427
702
  const valid = await this.isValidTx(tx);
428
703
  if (valid.result !== 'valid') {
429
704
  const reason = valid.reason.join(', ');
430
705
  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;
706
+ this.log.warn(`Received invalid tx ${txHash}: ${reason}`, { txHash });
707
+ throw new Error(`Invalid tx: ${reason}`);
435
708
  }
436
709
 
437
710
  await this.p2pClient!.sendTx(tx);
@@ -457,7 +730,7 @@ export class AztecNodeService implements AztecNode, Traceable {
457
730
  return txReceipt;
458
731
  }
459
732
 
460
- public getTxEffect(txHash: TxHash): Promise<InBlock<TxEffect> | undefined> {
733
+ public getTxEffect(txHash: TxHash): Promise<IndexedTxEffect | undefined> {
461
734
  return this.blockSource.getTxEffect(txHash);
462
735
  }
463
736
 
@@ -465,89 +738,120 @@ export class AztecNodeService implements AztecNode, Traceable {
465
738
  * Method to stop the aztec node.
466
739
  */
467
740
  public async stop() {
468
- this.log.info(`Stopping`);
469
- await this.sequencer?.stop();
470
- await this.p2pClient.stop();
471
- await this.worldStateSynchronizer.stop();
741
+ this.log.info(`Stopping Aztec Node`);
742
+ await tryStop(this.validatorsSentinel);
743
+ await tryStop(this.epochPruneWatcher);
744
+ await tryStop(this.slasherClient);
745
+ await tryStop(this.proofVerifier);
746
+ await tryStop(this.sequencer);
747
+ await tryStop(this.p2pClient);
748
+ await tryStop(this.worldStateSynchronizer);
472
749
  await tryStop(this.blockSource);
473
- await this.telemetry.stop();
474
- this.log.info(`Stopped`);
750
+ await tryStop(this.telemetry);
751
+ this.log.info(`Stopped Aztec Node`);
475
752
  }
476
753
 
477
754
  /**
478
755
  * Method to retrieve pending txs.
756
+ * @param limit - The number of items to returns
757
+ * @param after - The last known pending tx. Used for pagination
479
758
  * @returns - The pending txs.
480
759
  */
481
- public getPendingTxs() {
482
- return this.p2pClient!.getPendingTxs();
760
+ public getPendingTxs(limit?: number, after?: TxHash): Promise<Tx[]> {
761
+ return this.p2pClient!.getPendingTxs(limit, after);
483
762
  }
484
763
 
485
- public async getPendingTxCount() {
486
- const pendingTxs = await this.getPendingTxs();
487
- return pendingTxs.length;
764
+ public getPendingTxCount(): Promise<number> {
765
+ return this.p2pClient!.getPendingTxCount();
488
766
  }
489
767
 
490
768
  /**
491
- * Method to retrieve a single tx from the mempool or unfinalised chain.
769
+ * Method to retrieve a single tx from the mempool or unfinalized chain.
492
770
  * @param txHash - The transaction hash to return.
493
771
  * @returns - The tx if it exists.
494
772
  */
495
- public getTxByHash(txHash: TxHash) {
773
+ public getTxByHash(txHash: TxHash): Promise<Tx | undefined> {
496
774
  return Promise.resolve(this.p2pClient!.getTxByHashFromPool(txHash));
497
775
  }
498
776
 
499
777
  /**
500
- * Method to retrieve txs from the mempool or unfinalised chain.
778
+ * Method to retrieve txs from the mempool or unfinalized chain.
501
779
  * @param txHash - The transaction hash to return.
502
780
  * @returns - The txs if it exists.
503
781
  */
504
- public async getTxsByHash(txHashes: TxHash[]) {
782
+ public async getTxsByHash(txHashes: TxHash[]): Promise<Tx[]> {
505
783
  return compactArray(await Promise.all(txHashes.map(txHash => this.getTxByHash(txHash))));
506
784
  }
507
785
 
508
786
  /**
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
787
+ * Find the indexes of the given leaves in the given tree along with a block metadata pointing to the block in which
788
+ * the leaves were inserted.
789
+ * @param blockNumber - The block number at which to get the data or 'latest' for latest data.
511
790
  * @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.
791
+ * @param leafValues - The values to search for.
792
+ * @returns The indices of leaves and the block metadata of a block in which the leaves were inserted.
514
793
  */
515
794
  public async findLeavesIndexes(
516
795
  blockNumber: L2BlockNumber,
517
796
  treeId: MerkleTreeId,
518
797
  leafValues: Fr[],
519
- ): Promise<(bigint | undefined)[]> {
798
+ ): Promise<(InBlock<bigint> | undefined)[]> {
520
799
  const committedDb = await this.#getWorldState(blockNumber);
521
- return await committedDb.findLeafIndices(
800
+ const maybeIndices = await committedDb.findLeafIndices(
522
801
  treeId,
523
802
  leafValues.map(x => x.toBuffer()),
524
803
  );
525
- }
804
+ // We filter out undefined values
805
+ const indices = maybeIndices.filter(x => x !== undefined) as bigint[];
526
806
 
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
- }
807
+ // Now we find the block numbers for the indices
808
+ const blockNumbers = await committedDb.getBlockNumbersForLeafIndices(treeId, indices);
542
809
 
543
- public async findNullifiersIndexesWithBlock(
544
- blockNumber: L2BlockNumber,
545
- nullifiers: Fr[],
546
- ): Promise<(InBlock<bigint> | undefined)[]> {
547
- if (blockNumber === 'latest') {
548
- blockNumber = await this.getBlockNumber();
810
+ // If any of the block numbers are undefined, we throw an error.
811
+ for (let i = 0; i < indices.length; i++) {
812
+ if (blockNumbers[i] === undefined) {
813
+ throw new Error(`Block number is undefined for leaf index ${indices[i]} in tree ${MerkleTreeId[treeId]}`);
814
+ }
549
815
  }
550
- return this.nullifierSource.findNullifiersIndexesWithBlock(blockNumber, nullifiers);
816
+
817
+ // Get unique block numbers in order to optimize num calls to getLeafValue function.
818
+ const uniqueBlockNumbers = [...new Set(blockNumbers.filter(x => x !== undefined))];
819
+
820
+ // Now we obtain the block hashes from the archive tree by calling await `committedDb.getLeafValue(treeId, index)`
821
+ // (note that block number corresponds to the leaf index in the archive tree).
822
+ const blockHashes = await Promise.all(
823
+ uniqueBlockNumbers.map(blockNumber => {
824
+ return committedDb.getLeafValue(MerkleTreeId.ARCHIVE, blockNumber!);
825
+ }),
826
+ );
827
+
828
+ // If any of the block hashes are undefined, we throw an error.
829
+ for (let i = 0; i < uniqueBlockNumbers.length; i++) {
830
+ if (blockHashes[i] === undefined) {
831
+ throw new Error(`Block hash is undefined for block number ${uniqueBlockNumbers[i]}`);
832
+ }
833
+ }
834
+
835
+ // Create InBlock objects by combining indices, blockNumbers and blockHashes and return them.
836
+ return maybeIndices.map((index, i) => {
837
+ if (index === undefined) {
838
+ return undefined;
839
+ }
840
+ const blockNumber = blockNumbers[i];
841
+ if (blockNumber === undefined) {
842
+ return undefined;
843
+ }
844
+ const blockHashIndex = uniqueBlockNumbers.indexOf(blockNumber);
845
+ const blockHash = blockHashes[blockHashIndex];
846
+ if (!blockHash) {
847
+ return undefined;
848
+ }
849
+ return {
850
+ l2BlockNumber: Number(blockNumber),
851
+ l2BlockHash: L2BlockHash.fromField(blockHash),
852
+ data: index,
853
+ };
854
+ });
551
855
  }
552
856
 
553
857
  /**
@@ -578,6 +882,31 @@ export class AztecNodeService implements AztecNode, Traceable {
578
882
  return committedDb.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex);
579
883
  }
580
884
 
885
+ public async getArchiveMembershipWitness(
886
+ blockNumber: L2BlockNumber,
887
+ archive: Fr,
888
+ ): Promise<MembershipWitness<typeof ARCHIVE_HEIGHT> | undefined> {
889
+ const committedDb = await this.#getWorldState(blockNumber);
890
+ const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.ARCHIVE>(MerkleTreeId.ARCHIVE, [archive]);
891
+ return pathAndIndex === undefined
892
+ ? undefined
893
+ : MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
894
+ }
895
+
896
+ public async getNoteHashMembershipWitness(
897
+ blockNumber: L2BlockNumber,
898
+ noteHash: Fr,
899
+ ): Promise<MembershipWitness<typeof NOTE_HASH_TREE_HEIGHT> | undefined> {
900
+ const committedDb = await this.#getWorldState(blockNumber);
901
+ const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.NOTE_HASH_TREE>(
902
+ MerkleTreeId.NOTE_HASH_TREE,
903
+ [noteHash],
904
+ );
905
+ return pathAndIndex === undefined
906
+ ? undefined
907
+ : MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
908
+ }
909
+
581
910
  /**
582
911
  * Returns the index and a sibling path for a leaf in the committed l1 to l2 data tree.
583
912
  * @param blockNumber - The block number at which to get the data.
@@ -588,16 +917,19 @@ export class AztecNodeService implements AztecNode, Traceable {
588
917
  blockNumber: L2BlockNumber,
589
918
  l1ToL2Message: Fr,
590
919
  ): Promise<[bigint, SiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>] | undefined> {
591
- const index = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
592
- if (index === undefined) {
920
+ const db = await this.#getWorldState(blockNumber);
921
+ const [witness] = await db.findSiblingPaths(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, [l1ToL2Message]);
922
+ if (!witness) {
593
923
  return undefined;
594
924
  }
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];
925
+
926
+ // REFACTOR: Return a MembershipWitness object
927
+ return [witness.index, witness.path];
928
+ }
929
+
930
+ public async getL1ToL2MessageBlock(l1ToL2Message: Fr): Promise<number | undefined> {
931
+ const messageIndex = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
932
+ return messageIndex ? InboxLeaf.l2BlockFromIndex(messageIndex) : undefined;
601
933
  }
602
934
 
603
935
  /**
@@ -606,89 +938,18 @@ export class AztecNodeService implements AztecNode, Traceable {
606
938
  * @returns Whether the message is synced and ready to be included in a block.
607
939
  */
608
940
  public async isL1ToL2MessageSynced(l1ToL2Message: Fr): Promise<boolean> {
609
- return (await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message)) !== undefined;
941
+ const messageIndex = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
942
+ return messageIndex !== undefined;
610
943
  }
611
944
 
612
945
  /**
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.
946
+ * Returns all the L2 to L1 messages in a block.
618
947
  * @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.
948
+ * @returns The L2 to L1 messages (undefined if the block number is not found).
621
949
  */
622
- public async getL2ToL1MessageMembershipWitness(
623
- blockNumber: L2BlockNumber,
624
- l2ToL1Message: Fr,
625
- ): Promise<[bigint, SiblingPath<number>]> {
950
+ public async getL2ToL1Messages(blockNumber: L2BlockNumber): Promise<Fr[][] | undefined> {
626
951
  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)];
952
+ return block?.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs);
692
953
  }
693
954
 
694
955
  /**
@@ -730,24 +991,18 @@ export class AztecNodeService implements AztecNode, Traceable {
730
991
  nullifier: Fr,
731
992
  ): Promise<NullifierMembershipWitness | undefined> {
732
993
  const db = await this.#getWorldState(blockNumber);
733
- const index = (await db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]))[0];
734
- if (!index) {
994
+ const [witness] = await db.findSiblingPaths(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]);
995
+ if (!witness) {
735
996
  return undefined;
736
997
  }
737
998
 
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
-
999
+ const { index, path } = witness;
1000
+ const leafPreimage = await db.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index);
746
1001
  if (!leafPreimage) {
747
1002
  return undefined;
748
1003
  }
749
1004
 
750
- return new NullifierMembershipWitness(BigInt(index), leafPreimage as NullifierLeafPreimage, siblingPath);
1005
+ return new NullifierMembershipWitness(index, leafPreimage as NullifierLeafPreimage, path);
751
1006
  }
752
1007
 
753
1008
  /**
@@ -779,14 +1034,11 @@ export class AztecNodeService implements AztecNode, Traceable {
779
1034
  }
780
1035
  const preimageData = (await committedDb.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index))!;
781
1036
 
782
- const siblingPath = await committedDb.getSiblingPath<typeof NULLIFIER_TREE_HEIGHT>(
783
- MerkleTreeId.NULLIFIER_TREE,
784
- BigInt(index),
785
- );
1037
+ const siblingPath = await committedDb.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, BigInt(index));
786
1038
  return new NullifierMembershipWitness(BigInt(index), preimageData as NullifierLeafPreimage, siblingPath);
787
1039
  }
788
1040
 
789
- async getPublicDataTreeWitness(blockNumber: L2BlockNumber, leafSlot: Fr): Promise<PublicDataWitness | undefined> {
1041
+ async getPublicDataWitness(blockNumber: L2BlockNumber, leafSlot: Fr): Promise<PublicDataWitness | undefined> {
790
1042
  const committedDb = await this.#getWorldState(blockNumber);
791
1043
  const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
792
1044
  if (!lowLeafResult) {
@@ -796,10 +1048,7 @@ export class AztecNodeService implements AztecNode, Traceable {
796
1048
  MerkleTreeId.PUBLIC_DATA_TREE,
797
1049
  lowLeafResult.index,
798
1050
  )) as PublicDataTreeLeafPreimage;
799
- const path = await committedDb.getSiblingPath<typeof PUBLIC_DATA_TREE_HEIGHT>(
800
- MerkleTreeId.PUBLIC_DATA_TREE,
801
- lowLeafResult.index,
802
- );
1051
+ const path = await committedDb.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index);
803
1052
  return new PublicDataWitness(lowLeafResult.index, preimage, path);
804
1053
  }
805
1054
  }
@@ -827,7 +1076,7 @@ export class AztecNodeService implements AztecNode, Traceable {
827
1076
  MerkleTreeId.PUBLIC_DATA_TREE,
828
1077
  lowLeafResult.index,
829
1078
  )) as PublicDataTreeLeafPreimage;
830
- return preimage.value;
1079
+ return preimage.leaf.value;
831
1080
  }
832
1081
 
833
1082
  /**
@@ -840,23 +1089,55 @@ export class AztecNodeService implements AztecNode, Traceable {
840
1089
  : this.blockSource.getBlockHeader(blockNumber);
841
1090
  }
842
1091
 
1092
+ /**
1093
+ * Get a block header specified by its hash.
1094
+ * @param blockHash - The block hash being requested.
1095
+ * @returns The requested block header.
1096
+ */
1097
+ public async getBlockHeaderByHash(blockHash: Fr): Promise<BlockHeader | undefined> {
1098
+ return await this.blockSource.getBlockHeaderByHash(blockHash);
1099
+ }
1100
+
1101
+ /**
1102
+ * Get a block header specified by its archive root.
1103
+ * @param archive - The archive root being requested.
1104
+ * @returns The requested block header.
1105
+ */
1106
+ public async getBlockHeaderByArchive(archive: Fr): Promise<BlockHeader | undefined> {
1107
+ return await this.blockSource.getBlockHeaderByArchive(archive);
1108
+ }
1109
+
843
1110
  /**
844
1111
  * Simulates the public part of a transaction with the current state.
845
1112
  * @param tx - The transaction to simulate.
846
1113
  **/
847
- @trackSpan('AztecNodeService.simulatePublicCalls', async (tx: Tx) => ({
848
- [Attributes.TX_HASH]: (await tx.getTxHash()).toString(),
1114
+ @trackSpan('AztecNodeService.simulatePublicCalls', (tx: Tx) => ({
1115
+ [Attributes.TX_HASH]: tx.getTxHash().toString(),
849
1116
  }))
850
1117
  public async simulatePublicCalls(tx: Tx, skipFeeEnforcement = false): Promise<PublicSimulationOutput> {
851
- const txHash = await tx.getTxHash();
1118
+ // Check total gas limit for simulation
1119
+ const gasSettings = tx.data.constants.txContext.gasSettings;
1120
+ const txGasLimit = gasSettings.gasLimits.l2Gas;
1121
+ const teardownGasLimit = gasSettings.teardownGasLimits.l2Gas;
1122
+ if (txGasLimit + teardownGasLimit > this.config.rpcSimulatePublicMaxGasLimit) {
1123
+ throw new BadRequestError(
1124
+ `Transaction total gas limit ${
1125
+ txGasLimit + teardownGasLimit
1126
+ } (${txGasLimit} + ${teardownGasLimit}) exceeds maximum gas limit ${
1127
+ this.config.rpcSimulatePublicMaxGasLimit
1128
+ } for simulation`,
1129
+ );
1130
+ }
1131
+
1132
+ const txHash = tx.getTxHash();
852
1133
  const blockNumber = (await this.blockSource.getBlockNumber()) + 1;
853
1134
 
854
1135
  // 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;
1136
+ const coinbase = EthAddress.ZERO;
1137
+ const feeRecipient = AztecAddress.ZERO;
857
1138
 
858
1139
  const newGlobalVariables = await this.globalVariableBuilder.buildGlobalVariables(
859
- new Fr(blockNumber),
1140
+ blockNumber,
860
1141
  coinbase,
861
1142
  feeRecipient,
862
1143
  );
@@ -865,7 +1146,6 @@ export class AztecNodeService implements AztecNode, Traceable {
865
1146
  new DateProvider(),
866
1147
  this.telemetry,
867
1148
  );
868
- const fork = await this.worldStateSynchronizer.fork();
869
1149
 
870
1150
  this.log.verbose(`Simulating public calls for tx ${txHash}`, {
871
1151
  globalVariables: newGlobalVariables.toInspect(),
@@ -873,11 +1153,20 @@ export class AztecNodeService implements AztecNode, Traceable {
873
1153
  blockNumber,
874
1154
  });
875
1155
 
1156
+ const merkleTreeFork = await this.worldStateSynchronizer.fork();
876
1157
  try {
877
- const processor = publicProcessorFactory.create(fork, newGlobalVariables, skipFeeEnforcement);
1158
+ const config = PublicSimulatorConfig.from({
1159
+ skipFeeEnforcement,
1160
+ collectDebugLogs: true,
1161
+ collectHints: false,
1162
+ collectCallMetadata: true,
1163
+ maxDebugLogMemoryReads: this.config.rpcSimulatePublicMaxDebugLogMemoryReads,
1164
+ collectStatistics: false,
1165
+ });
1166
+ const processor = publicProcessorFactory.create(merkleTreeFork, newGlobalVariables, config);
878
1167
 
879
1168
  // REFACTOR: Consider merging ProcessReturnValues into ProcessedTx
880
- const [processedTxs, failedTxs, returns] = await processor.process([tx]);
1169
+ const [processedTxs, failedTxs, _usedTxs, returns] = await processor.process([tx]);
881
1170
  // REFACTOR: Consider returning the error rather than throwing
882
1171
  if (failedTxs.length) {
883
1172
  this.log.warn(`Simulated tx ${txHash} fails: ${failedTxs[0].error}`, { txHash });
@@ -887,13 +1176,13 @@ export class AztecNodeService implements AztecNode, Traceable {
887
1176
  const [processedTx] = processedTxs;
888
1177
  return new PublicSimulationOutput(
889
1178
  processedTx.revertReason,
890
- processedTx.constants,
1179
+ processedTx.globalVariables,
891
1180
  processedTx.txEffect,
892
1181
  returns,
893
1182
  processedTx.gasUsed,
894
1183
  );
895
1184
  } finally {
896
- await fork.close();
1185
+ await merkleTreeFork.close();
897
1186
  }
898
1187
  }
899
1188
 
@@ -901,24 +1190,42 @@ export class AztecNodeService implements AztecNode, Traceable {
901
1190
  tx: Tx,
902
1191
  { isSimulation, skipFeeEnforcement }: { isSimulation?: boolean; skipFeeEnforcement?: boolean } = {},
903
1192
  ): Promise<TxValidationResult> {
904
- const blockNumber = (await this.blockSource.getBlockNumber()) + 1;
905
1193
  const db = this.worldStateSynchronizer.getCommitted();
906
1194
  const verifier = isSimulation ? undefined : this.proofVerifier;
1195
+
1196
+ // We accept transactions if they are not expired by the next slot (checked based on the IncludeByTimestamp field)
1197
+ const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
1198
+ const blockNumber = (await this.blockSource.getBlockNumber()) + 1;
907
1199
  const validator = createValidatorForAcceptingTxs(db, this.contractDataSource, verifier, {
1200
+ timestamp: nextSlotTimestamp,
908
1201
  blockNumber,
909
1202
  l1ChainId: this.l1ChainId,
910
- setupAllowList: this.config.allowedInSetup ?? (await getDefaultAllowedSetupFunctions()),
1203
+ rollupVersion: this.version,
1204
+ setupAllowList: this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions()),
911
1205
  gasFees: await this.getCurrentBaseFees(),
912
1206
  skipFeeEnforcement,
1207
+ txsPermitted: !this.config.disableTransactions,
913
1208
  });
914
1209
 
915
1210
  return await validator.validateTx(tx);
916
1211
  }
917
1212
 
918
- public async setConfig(config: Partial<SequencerConfig & ProverConfig>): Promise<void> {
919
- const newConfig = { ...this.config, ...config };
920
- await this.sequencer?.updateSequencerConfig(config);
1213
+ public getConfig(): Promise<AztecNodeAdminConfig> {
1214
+ const schema = AztecNodeAdminConfigSchema;
1215
+ const keys = schema.keyof().options;
1216
+ return Promise.resolve(pick(this.config, ...keys));
1217
+ }
921
1218
 
1219
+ public async setConfig(config: Partial<AztecNodeAdminConfig>): Promise<void> {
1220
+ const newConfig = { ...this.config, ...config };
1221
+ this.sequencer?.updateConfig(config);
1222
+ this.slasherClient?.updateConfig(config);
1223
+ this.validatorsSentinel?.updateConfig(config);
1224
+ await this.p2pClient.updateP2PConfig(config);
1225
+ const archiver = this.blockSource as Archiver;
1226
+ if ('updateConfig' in archiver) {
1227
+ archiver.updateConfig(config);
1228
+ }
922
1229
  if (newConfig.realProofs !== this.config.realProofs) {
923
1230
  this.proofVerifier = config.realProofs ? await BBCircuitVerifier.new(newConfig) : new TestCircuitVerifier();
924
1231
  }
@@ -928,31 +1235,140 @@ export class AztecNodeService implements AztecNode, Traceable {
928
1235
 
929
1236
  public getProtocolContractAddresses(): Promise<ProtocolContractAddresses> {
930
1237
  return Promise.resolve({
931
- classRegisterer: ProtocolContractAddress.ContractClassRegisterer,
1238
+ classRegistry: ProtocolContractAddress.ContractClassRegistry,
932
1239
  feeJuice: ProtocolContractAddress.FeeJuice,
933
- instanceDeployer: ProtocolContractAddress.ContractInstanceDeployer,
1240
+ instanceRegistry: ProtocolContractAddress.ContractInstanceRegistry,
934
1241
  multiCallEntrypoint: ProtocolContractAddress.MultiCallEntrypoint,
935
1242
  });
936
1243
  }
937
1244
 
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);
1245
+ public registerContractFunctionSignatures(signatures: string[]): Promise<void> {
1246
+ return this.contractDataSource.registerContractFunctionSignatures(signatures);
1247
+ }
1248
+
1249
+ public getValidatorsStats(): Promise<ValidatorsStats> {
1250
+ return this.validatorsSentinel?.computeStats() ?? Promise.resolve({ stats: {}, slotWindow: 0 });
942
1251
  }
943
1252
 
944
- public registerContractFunctionSignatures(_address: AztecAddress, signatures: string[]): Promise<void> {
945
- return this.contractDataSource.registerContractFunctionSignatures(_address, signatures);
1253
+ public getValidatorStats(
1254
+ validatorAddress: EthAddress,
1255
+ fromSlot?: SlotNumber,
1256
+ toSlot?: SlotNumber,
1257
+ ): Promise<SingleValidatorStats | undefined> {
1258
+ return this.validatorsSentinel?.getValidatorStats(validatorAddress, fromSlot, toSlot) ?? Promise.resolve(undefined);
946
1259
  }
947
1260
 
948
- public flushTxs(): Promise<void> {
949
- if (!this.sequencer) {
950
- throw new Error(`Sequencer is not initialized`);
1261
+ public async startSnapshotUpload(location: string): Promise<void> {
1262
+ // Note that we are forcefully casting the blocksource as an archiver
1263
+ // We break support for archiver running remotely to the node
1264
+ const archiver = this.blockSource as Archiver;
1265
+ if (!('backupTo' in archiver)) {
1266
+ this.metrics.recordSnapshotError();
1267
+ throw new Error('Archiver implementation does not support backups. Cannot generate snapshot.');
1268
+ }
1269
+
1270
+ // Test that the archiver has done an initial sync.
1271
+ if (!archiver.isInitialSyncComplete()) {
1272
+ this.metrics.recordSnapshotError();
1273
+ throw new Error(`Archiver initial sync not complete. Cannot start snapshot.`);
1274
+ }
1275
+
1276
+ // And it has an L2 block hash
1277
+ const l2BlockHash = await archiver.getL2Tips().then(tips => tips.latest.hash);
1278
+ if (!l2BlockHash) {
1279
+ this.metrics.recordSnapshotError();
1280
+ throw new Error(`Archiver has no latest L2 block hash downloaded. Cannot start snapshot.`);
1281
+ }
1282
+
1283
+ if (this.isUploadingSnapshot) {
1284
+ this.metrics.recordSnapshotError();
1285
+ throw new Error(`Snapshot upload already in progress. Cannot start another one until complete.`);
951
1286
  }
952
- this.sequencer.flush();
1287
+
1288
+ // Do not wait for the upload to be complete to return to the caller, but flag that an operation is in progress
1289
+ this.isUploadingSnapshot = true;
1290
+ const timer = new Timer();
1291
+ void uploadSnapshot(location, this.blockSource as Archiver, this.worldStateSynchronizer, this.config, this.log)
1292
+ .then(() => {
1293
+ this.isUploadingSnapshot = false;
1294
+ this.metrics.recordSnapshot(timer.ms());
1295
+ })
1296
+ .catch(err => {
1297
+ this.isUploadingSnapshot = false;
1298
+ this.metrics.recordSnapshotError();
1299
+ this.log.error(`Error uploading snapshot: ${err}`);
1300
+ });
1301
+
953
1302
  return Promise.resolve();
954
1303
  }
955
1304
 
1305
+ public async rollbackTo(targetBlock: number, force?: boolean): Promise<void> {
1306
+ const archiver = this.blockSource as Archiver;
1307
+ if (!('rollbackTo' in archiver)) {
1308
+ throw new Error('Archiver implementation does not support rollbacks.');
1309
+ }
1310
+
1311
+ const finalizedBlock = await archiver.getL2Tips().then(tips => tips.finalized.number);
1312
+ if (targetBlock < finalizedBlock) {
1313
+ if (force) {
1314
+ this.log.warn(`Clearing world state database to allow rolling back behind finalized block ${finalizedBlock}`);
1315
+ await this.worldStateSynchronizer.clear();
1316
+ await this.p2pClient.clear();
1317
+ } else {
1318
+ throw new Error(`Cannot rollback to block ${targetBlock} as it is before finalized ${finalizedBlock}`);
1319
+ }
1320
+ }
1321
+
1322
+ try {
1323
+ this.log.info(`Pausing archiver and world state sync to start rollback`);
1324
+ await archiver.stop();
1325
+ await this.worldStateSynchronizer.stopSync();
1326
+ const currentBlock = await archiver.getBlockNumber();
1327
+ const blocksToUnwind = currentBlock - targetBlock;
1328
+ this.log.info(`Unwinding ${count(blocksToUnwind, 'block')} from L2 block ${currentBlock} to ${targetBlock}`);
1329
+ await archiver.rollbackTo(targetBlock);
1330
+ this.log.info(`Unwinding complete.`);
1331
+ } catch (err) {
1332
+ this.log.error(`Error during rollback`, err);
1333
+ throw err;
1334
+ } finally {
1335
+ this.log.info(`Resuming world state and archiver sync.`);
1336
+ this.worldStateSynchronizer.resumeSync();
1337
+ archiver.resume();
1338
+ }
1339
+ }
1340
+
1341
+ public async pauseSync(): Promise<void> {
1342
+ this.log.info(`Pausing archiver and world state sync`);
1343
+ await (this.blockSource as Archiver).stop();
1344
+ await this.worldStateSynchronizer.stopSync();
1345
+ }
1346
+
1347
+ public resumeSync(): Promise<void> {
1348
+ this.log.info(`Resuming world state and archiver sync.`);
1349
+ this.worldStateSynchronizer.resumeSync();
1350
+ (this.blockSource as Archiver).resume();
1351
+ return Promise.resolve();
1352
+ }
1353
+
1354
+ public getSlashPayloads(): Promise<SlashPayloadRound[]> {
1355
+ if (!this.slasherClient) {
1356
+ throw new Error(`Slasher client not enabled`);
1357
+ }
1358
+ return this.slasherClient.getSlashPayloads();
1359
+ }
1360
+
1361
+ public getSlashOffenses(round: bigint | 'all' | 'current'): Promise<Offense[]> {
1362
+ if (!this.slasherClient) {
1363
+ throw new Error(`Slasher client not enabled`);
1364
+ }
1365
+ if (round === 'all') {
1366
+ return this.slasherClient.getPendingOffenses();
1367
+ } else {
1368
+ return this.slasherClient.gatherOffensesForRound(round === 'current' ? undefined : BigInt(round));
1369
+ }
1370
+ }
1371
+
956
1372
  /**
957
1373
  * Returns an instance of MerkleTreeOperations having first ensured the world state is fully synched
958
1374
  * @param blockNumber - The block number at which to get the data.