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

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