@aztec/aztec-node 0.0.1-commit.0dc957cde → 0.0.1-commit.0ec55a70b

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.
@@ -50,14 +50,13 @@ import {
50
50
  import { CollectionLimitsConfig, PublicSimulatorConfig } from '@aztec/stdlib/avm';
51
51
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
52
52
  import {
53
- type BlockData,
54
53
  BlockHash,
55
54
  type BlockParameter,
56
55
  type DataInBlock,
57
56
  L2Block,
58
57
  type L2BlockSource,
58
+ inspectBlockParameter,
59
59
  } from '@aztec/stdlib/block';
60
- import type { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
61
60
  import type {
62
61
  ContractClassPublic,
63
62
  ContractDataSource,
@@ -67,15 +66,22 @@ import type {
67
66
  } from '@aztec/stdlib/contract';
68
67
  import { GasFees, type ManaUsageEstimate } from '@aztec/stdlib/gas';
69
68
  import { computePublicDataTreeLeafSlot } from '@aztec/stdlib/hash';
70
- import {
71
- type AztecNode,
72
- type AztecNodeAdmin,
73
- type AztecNodeAdminConfig,
74
- AztecNodeAdminConfigSchema,
75
- type AztecNodeDebug,
76
- type GetContractClassLogsResponse,
77
- type GetPublicLogsResponse,
69
+ import type {
70
+ AztecNode,
71
+ AztecNodeAdmin,
72
+ AztecNodeAdminConfig,
73
+ AztecNodeDebug,
74
+ BlockIncludeOptions,
75
+ BlockResponse,
76
+ ChainTip,
77
+ ChainTips,
78
+ CheckpointIncludeOptions,
79
+ CheckpointParameter,
80
+ CheckpointResponse,
81
+ GetContractClassLogsResponse,
82
+ GetPublicLogsResponse,
78
83
  } from '@aztec/stdlib/interfaces/client';
84
+ import { AztecNodeAdminConfigSchema } from '@aztec/stdlib/interfaces/client';
79
85
  import {
80
86
  type AllowedElement,
81
87
  type ClientProtocolCircuitVerifier,
@@ -129,6 +135,12 @@ import { createPublicClient } from 'viem';
129
135
 
130
136
  import { createSentinel } from '../sentinel/factory.js';
131
137
  import { Sentinel } from '../sentinel/sentinel.js';
138
+ import {
139
+ blockResponseFromBlockData,
140
+ blockResponseFromL2Block,
141
+ checkpointResponseFromCheckpointData,
142
+ checkpointResponseFromPublishedCheckpoint,
143
+ } from './block_response_helpers.js';
132
144
  import { type AztecNodeConfig, createKeyStoreForValidator } from './config.js';
133
145
  import { NodeMetrics } from './node_metrics.js';
134
146
 
@@ -157,6 +169,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
157
169
  protected readonly slasherClient: SlasherClientInterface | undefined,
158
170
  protected readonly validatorsSentinel: Sentinel | undefined,
159
171
  protected readonly epochPruneWatcher: EpochPruneWatcher | undefined,
172
+ protected readonly attestationsBlockWatcher: AttestationsBlockWatcher | undefined,
160
173
  protected readonly l1ChainId: number,
161
174
  protected readonly version: number,
162
175
  protected readonly globalVariableBuilder: GlobalVariableBuilderInterface,
@@ -196,10 +209,281 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
196
209
  return status.syncSummary;
197
210
  }
198
211
 
212
+ public async getChainTips(): Promise<ChainTips> {
213
+ const { proposed, checkpointed, proven, finalized } = await this.blockSource.getL2Tips();
214
+ return { proposed, checkpointed, proven, finalized };
215
+ }
216
+
199
217
  public getL2Tips() {
200
218
  return this.blockSource.getL2Tips();
201
219
  }
202
220
 
221
+ public async getBlockHeader(number: BlockNumber | 'latest'): Promise<BlockHeader | undefined> {
222
+ const resolvedNumber = number === 'latest' ? await this.blockSource.getBlockNumber() : number;
223
+ if (resolvedNumber === BlockNumber.ZERO) {
224
+ return this.worldStateSynchronizer.getCommitted().getInitialHeader();
225
+ }
226
+ return this.blockSource.getBlockHeader(resolvedNumber);
227
+ }
228
+
229
+ public async getCheckpointedBlocks(from: BlockNumber, limit: number) {
230
+ return (await this.blockSource.getCheckpointedBlocks(from, limit)) ?? [];
231
+ }
232
+
233
+ public getCheckpointsDataForEpoch(epoch: EpochNumber) {
234
+ return this.blockSource.getCheckpointsDataForEpoch(epoch);
235
+ }
236
+
237
+ public getBlockNumber(tip?: ChainTip): Promise<BlockNumber> {
238
+ switch (tip) {
239
+ case undefined:
240
+ case 'proposed':
241
+ return this.blockSource.getBlockNumber();
242
+ case 'checkpointed':
243
+ return this.blockSource.getCheckpointedL2BlockNumber();
244
+ case 'proven':
245
+ return this.blockSource.getProvenBlockNumber();
246
+ case 'finalized':
247
+ return this.blockSource.getFinalizedL2BlockNumber();
248
+ }
249
+ }
250
+
251
+ public async getCheckpointNumber(tip?: ChainTip): Promise<CheckpointNumber> {
252
+ switch (tip) {
253
+ case undefined:
254
+ case 'proposed':
255
+ case 'checkpointed':
256
+ return await this.blockSource.getCheckpointNumber();
257
+ case 'proven':
258
+ case 'finalized': {
259
+ const tips = await this.blockSource.getL2Tips();
260
+ return tip === 'proven' ? tips.proven.checkpoint.number : tips.finalized.checkpoint.number;
261
+ }
262
+ }
263
+ }
264
+
265
+ private isChainTip(value: unknown): value is ChainTip {
266
+ return value === 'proposed' || value === 'checkpointed' || value === 'proven' || value === 'finalized';
267
+ }
268
+
269
+ private async resolveBlockParameter(
270
+ param: BlockParameter,
271
+ ): Promise<{ number?: BlockNumber; hash?: BlockHash; archive?: Fr }> {
272
+ if (BlockHash.isBlockHash(param)) {
273
+ return { hash: param };
274
+ }
275
+ if (typeof param === 'number') {
276
+ return { number: param as BlockNumber };
277
+ }
278
+ if (param === 'latest') {
279
+ return { number: await this.blockSource.getBlockNumber() };
280
+ }
281
+ if (this.isChainTip(param)) {
282
+ return { number: await this.getBlockNumber(param) };
283
+ }
284
+ if (typeof param === 'object' && param !== null) {
285
+ if ('number' in param) {
286
+ return { number: param.number };
287
+ }
288
+ if ('hash' in param) {
289
+ return { hash: param.hash };
290
+ }
291
+ if ('archive' in param) {
292
+ return { archive: param.archive };
293
+ }
294
+ }
295
+ throw new BadRequestError(`Invalid BlockParameter: ${JSON.stringify(param)}`);
296
+ }
297
+
298
+ private async resolveCheckpointParameter(
299
+ param: CheckpointParameter,
300
+ ): Promise<{ number?: CheckpointNumber; slot?: SlotNumber }> {
301
+ if (typeof param === 'number') {
302
+ return { number: param as CheckpointNumber };
303
+ }
304
+ if (param === 'latest') {
305
+ return { number: await this.blockSource.getCheckpointNumber() };
306
+ }
307
+ if (this.isChainTip(param)) {
308
+ return { number: await this.getCheckpointNumber(param) };
309
+ }
310
+ if (typeof param === 'object' && param !== null) {
311
+ if ('number' in param) {
312
+ return { number: param.number };
313
+ }
314
+ if ('slot' in param) {
315
+ return { slot: param.slot };
316
+ }
317
+ }
318
+ throw new BadRequestError(`Invalid CheckpointParameter: ${JSON.stringify(param)}`);
319
+ }
320
+
321
+ public async getBlock<Opts extends BlockIncludeOptions = {}>(
322
+ param: BlockParameter,
323
+ options: Opts = {} as Opts,
324
+ ): Promise<BlockResponse<Opts> | undefined> {
325
+ const resolved = await this.resolveBlockParameter(param);
326
+ const wantTxs = !!options.includeTransactions;
327
+ const wantContext = !!options.includeL1PublishInfo || !!options.includeAttestations;
328
+
329
+ if (resolved.hash !== undefined) {
330
+ const initial = await this.#getInitialHeaderHash();
331
+ if (resolved.hash.equals(initial)) {
332
+ return (await this.buildGenesisBlockResponse(options)) as BlockResponse<Opts>;
333
+ }
334
+ if (wantTxs) {
335
+ const block = await this.blockSource.getL2BlockByHash(resolved.hash);
336
+ if (!block) {
337
+ return undefined;
338
+ }
339
+ const ctx = wantContext ? await this.blockSource.getBlockDataWithCheckpointContext(block.number) : undefined;
340
+ return (await blockResponseFromL2Block(block, options, ctx)) as BlockResponse<Opts>;
341
+ }
342
+ const data = await this.blockSource.getBlockHeaderByHash(resolved.hash);
343
+ if (!data) {
344
+ return undefined;
345
+ }
346
+ const blockNumber = data.globalVariables.blockNumber;
347
+ const ctx = wantContext ? await this.blockSource.getBlockDataWithCheckpointContext(blockNumber) : undefined;
348
+ if (ctx) {
349
+ return blockResponseFromBlockData(ctx.data, blockNumber, options, ctx) as BlockResponse<Opts>;
350
+ }
351
+ const blockData = await this.blockSource.getBlockData(blockNumber);
352
+ if (!blockData) {
353
+ return undefined;
354
+ }
355
+ return blockResponseFromBlockData(blockData, blockNumber, options) as BlockResponse<Opts>;
356
+ }
357
+
358
+ if (resolved.archive !== undefined) {
359
+ if (wantTxs) {
360
+ const block = await this.blockSource.getL2BlockByArchive(resolved.archive);
361
+ if (!block) {
362
+ return undefined;
363
+ }
364
+ const ctx = wantContext ? await this.blockSource.getBlockDataWithCheckpointContext(block.number) : undefined;
365
+ return (await blockResponseFromL2Block(block, options, ctx)) as BlockResponse<Opts>;
366
+ }
367
+ const data = await this.blockSource.getBlockDataByArchive(resolved.archive);
368
+ if (!data) {
369
+ return undefined;
370
+ }
371
+ const blockNumber = data.header.globalVariables.blockNumber;
372
+ const ctx = wantContext ? await this.blockSource.getBlockDataWithCheckpointContext(blockNumber) : undefined;
373
+ return blockResponseFromBlockData(data, blockNumber, options, ctx) as BlockResponse<Opts>;
374
+ }
375
+
376
+ const blockNumber = resolved.number!;
377
+ if (blockNumber === BlockNumber.ZERO) {
378
+ return (await this.buildGenesisBlockResponse(options)) as BlockResponse<Opts>;
379
+ }
380
+ if (wantTxs) {
381
+ const block = await this.blockSource.getL2Block(blockNumber);
382
+ if (!block) {
383
+ return undefined;
384
+ }
385
+ const ctx = wantContext ? await this.blockSource.getBlockDataWithCheckpointContext(blockNumber) : undefined;
386
+ return (await blockResponseFromL2Block(block, options, ctx)) as BlockResponse<Opts>;
387
+ }
388
+ const ctx = await this.blockSource.getBlockDataWithCheckpointContext(blockNumber);
389
+ if (!ctx) {
390
+ return undefined;
391
+ }
392
+ return blockResponseFromBlockData(ctx.data, blockNumber, options, ctx) as BlockResponse<Opts>;
393
+ }
394
+
395
+ public async getBlocks<Opts extends BlockIncludeOptions = {}>(
396
+ from: BlockNumber,
397
+ limit: number,
398
+ options: Opts = {} as Opts,
399
+ ): Promise<BlockResponse<Opts>[]> {
400
+ const wantTxs = !!options.includeTransactions;
401
+ const wantContext = !!options.includeL1PublishInfo || !!options.includeAttestations;
402
+ if (wantTxs) {
403
+ const blocks = await this.blockSource.getBlocks(from, limit);
404
+ return (await Promise.all(
405
+ blocks.map(async block => {
406
+ const ctx = wantContext ? await this.blockSource.getBlockDataWithCheckpointContext(block.number) : undefined;
407
+ return blockResponseFromL2Block(block, options, ctx);
408
+ }),
409
+ )) as BlockResponse<Opts>[];
410
+ }
411
+ const results: BlockResponse<Opts>[] = [];
412
+ for (let i = 0; i < limit; i++) {
413
+ const blockNumber = BlockNumber(from + i);
414
+ const ctx = await this.blockSource.getBlockDataWithCheckpointContext(blockNumber);
415
+ if (!ctx) {
416
+ break;
417
+ }
418
+ results.push(blockResponseFromBlockData(ctx.data, blockNumber, options, ctx) as BlockResponse<Opts>);
419
+ }
420
+ return results;
421
+ }
422
+
423
+ public async getCheckpoint<Opts extends CheckpointIncludeOptions = {}>(
424
+ param: CheckpointParameter,
425
+ options: Opts = {} as Opts,
426
+ ): Promise<CheckpointResponse<Opts> | undefined> {
427
+ const resolved = await this.resolveCheckpointParameter(param);
428
+ let checkpointNumber = resolved.number;
429
+ if (checkpointNumber === undefined && resolved.slot !== undefined) {
430
+ checkpointNumber = await this.blockSource.getCheckpointNumberBySlot(resolved.slot);
431
+ }
432
+ if (checkpointNumber === undefined) {
433
+ return undefined;
434
+ }
435
+ if (options.includeBlocks) {
436
+ const [checkpoint] = await this.blockSource.getCheckpoints(checkpointNumber, 1);
437
+ if (!checkpoint) {
438
+ return undefined;
439
+ }
440
+ return (await checkpointResponseFromPublishedCheckpoint(checkpoint, options)) as CheckpointResponse<Opts>;
441
+ }
442
+ const data = await this.blockSource.getCheckpointData(checkpointNumber);
443
+ if (!data) {
444
+ return undefined;
445
+ }
446
+ return checkpointResponseFromCheckpointData(data, options) as CheckpointResponse<Opts>;
447
+ }
448
+
449
+ public async getCheckpoints<Opts extends CheckpointIncludeOptions = {}>(
450
+ from: CheckpointNumber,
451
+ limit: number,
452
+ options: Opts = {} as Opts,
453
+ ): Promise<CheckpointResponse<Opts>[]> {
454
+ if (options.includeBlocks) {
455
+ const checkpoints = await this.blockSource.getCheckpoints(from, limit);
456
+ return (await Promise.all(
457
+ checkpoints.map(cp => checkpointResponseFromPublishedCheckpoint(cp, options)),
458
+ )) as CheckpointResponse<Opts>[];
459
+ }
460
+ const datas = await this.blockSource.getCheckpointDataRange(from, limit);
461
+ return datas.map(d => checkpointResponseFromCheckpointData(d, options)) as CheckpointResponse<Opts>[];
462
+ }
463
+
464
+ private async buildGenesisBlockResponse(options: BlockIncludeOptions): Promise<BlockResponse> {
465
+ const initial = this.worldStateSynchronizer.getCommitted().getInitialHeader();
466
+ const empty = L2Block.empty(initial);
467
+ const response: BlockResponse = {
468
+ header: empty.header,
469
+ archive: empty.archive,
470
+ hash: await this.#getInitialHeaderHash(),
471
+ checkpointNumber: empty.checkpointNumber,
472
+ indexWithinCheckpoint: empty.indexWithinCheckpoint,
473
+ number: empty.number,
474
+ };
475
+ if (options.includeTransactions) {
476
+ (response as BlockResponse).body = empty.body;
477
+ }
478
+ if (options.includeL1PublishInfo) {
479
+ (response as BlockResponse).l1 = { published: false };
480
+ }
481
+ if (options.includeAttestations) {
482
+ (response as BlockResponse).attestations = [];
483
+ }
484
+ return response;
485
+ }
486
+
203
487
  /**
204
488
  * initializes the Aztec Node, wait for component to sync.
205
489
  * @param config - The configuration to be used by the aztec node.
@@ -403,7 +687,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
403
687
 
404
688
  let validatorClient: ValidatorClient | undefined;
405
689
 
406
- if (!proverOnly) {
690
+ if (!config.disableValidator) {
407
691
  // Create validator client if required
408
692
  validatorClient = await createValidatorClient(config, {
409
693
  checkpointsBuilder: validatorCheckpointsBuilder,
@@ -494,9 +778,18 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
494
778
  void archiver
495
779
  .waitForInitialSync()
496
780
  .then(async () => {
497
- await validatorsSentinel?.start();
498
- await epochPruneWatcher?.start();
499
- await attestationsBlockWatcher?.start();
781
+ if (validatorsSentinel) {
782
+ await validatorsSentinel.start();
783
+ started.push(validatorsSentinel);
784
+ }
785
+ if (epochPruneWatcher) {
786
+ await epochPruneWatcher.start();
787
+ started.push(epochPruneWatcher);
788
+ }
789
+ if (attestationsBlockWatcher) {
790
+ await attestationsBlockWatcher.start();
791
+ started.push(attestationsBlockWatcher);
792
+ }
500
793
  log.info(`All p2p services started`);
501
794
  })
502
795
  .catch(err => log.error('Failed to start p2p services after archiver sync', err));
@@ -627,6 +920,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
627
920
  slasherClient,
628
921
  validatorsSentinel,
629
922
  epochPruneWatcher,
923
+ attestationsBlockWatcher,
630
924
  ethereumChain.chainInfo.id,
631
925
  config.rollupVersion,
632
926
  globalVariableBuilder,
@@ -725,71 +1019,6 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
725
1019
  return nodeInfo;
726
1020
  }
727
1021
 
728
- /**
729
- * Get a block specified by its block number, block hash, or 'latest'.
730
- * @param block - The block parameter (block number, block hash, or 'latest').
731
- * @returns The requested block.
732
- */
733
- public async getBlock(block: BlockParameter): Promise<L2Block | undefined> {
734
- if (BlockHash.isBlockHash(block)) {
735
- return this.getBlockByHash(block);
736
- }
737
- const blockNumber = block === 'latest' ? await this.getBlockNumber() : (block as BlockNumber);
738
- if (blockNumber === BlockNumber.ZERO) {
739
- return this.buildInitialBlock();
740
- }
741
- return await this.blockSource.getL2Block(blockNumber);
742
- }
743
-
744
- /**
745
- * Get a block specified by its hash.
746
- * @param blockHash - The block hash being requested.
747
- * @returns The requested block.
748
- */
749
- public async getBlockByHash(blockHash: BlockHash): Promise<L2Block | undefined> {
750
- const initialBlockHash = await this.#getInitialHeaderHash();
751
- if (blockHash.equals(initialBlockHash)) {
752
- return this.buildInitialBlock();
753
- }
754
- return await this.blockSource.getL2BlockByHash(blockHash);
755
- }
756
-
757
- private buildInitialBlock(): L2Block {
758
- const initialHeader = this.worldStateSynchronizer.getCommitted().getInitialHeader();
759
- return L2Block.empty(initialHeader);
760
- }
761
-
762
- /**
763
- * Get a block specified by its archive root.
764
- * @param archive - The archive root being requested.
765
- * @returns The requested block.
766
- */
767
- public async getBlockByArchive(archive: Fr): Promise<L2Block | undefined> {
768
- return await this.blockSource.getL2BlockByArchive(archive);
769
- }
770
-
771
- /**
772
- * Method to request blocks. Will attempt to return all requested blocks but will return only those available.
773
- * @param from - The start of the range of blocks to return.
774
- * @param limit - The maximum number of blocks to obtain.
775
- * @returns The blocks requested.
776
- */
777
- public async getBlocks(from: BlockNumber, limit: number): Promise<L2Block[]> {
778
- return (await this.blockSource.getBlocks(from, BlockNumber(limit))) ?? [];
779
- }
780
-
781
- public async getCheckpoints(from: CheckpointNumber, limit: number): Promise<PublishedCheckpoint[]> {
782
- return (await this.blockSource.getCheckpoints(from, limit)) ?? [];
783
- }
784
-
785
- public async getCheckpointedBlocks(from: BlockNumber, limit: number) {
786
- return (await this.blockSource.getCheckpointedBlocks(from, limit)) ?? [];
787
- }
788
-
789
- public getCheckpointsDataForEpoch(epochNumber: EpochNumber) {
790
- return this.blockSource.getCheckpointsDataForEpoch(epochNumber);
791
- }
792
-
793
1022
  public async getCurrentMinFees(): Promise<GasFees> {
794
1023
  return await this.feeProvider.getCurrentMinFees();
795
1024
  }
@@ -807,26 +1036,6 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
807
1036
  return GasFees.from({ feePerDaGas: 0n, feePerL2Gas: 0n });
808
1037
  }
809
1038
 
810
- /**
811
- * Method to fetch the latest block number synchronized by the node.
812
- * @returns The block number.
813
- */
814
- public async getBlockNumber(): Promise<BlockNumber> {
815
- return await this.blockSource.getBlockNumber();
816
- }
817
-
818
- public async getProvenBlockNumber(): Promise<BlockNumber> {
819
- return await this.blockSource.getProvenBlockNumber();
820
- }
821
-
822
- public async getCheckpointedBlockNumber(): Promise<BlockNumber> {
823
- return await this.blockSource.getCheckpointedL2BlockNumber();
824
- }
825
-
826
- public getCheckpointNumber(): Promise<CheckpointNumber> {
827
- return this.blockSource.getCheckpointNumber();
828
- }
829
-
830
1039
  /**
831
1040
  * Method to fetch the version of the package.
832
1041
  * @returns The node package version
@@ -944,7 +1153,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
944
1153
  throw new Error(`Invalid tx: ${reason}`);
945
1154
  }
946
1155
 
947
- await this.p2pClient!.sendTx(tx);
1156
+ try {
1157
+ await this.p2pClient!.sendTx(tx);
1158
+ } catch (err) {
1159
+ this.metrics.receivedTx(timer.ms(), false);
1160
+ this.log.warn(`Mempool rejected tx ${txHash}: ${(err as Error).message}`, { txHash });
1161
+ throw err;
1162
+ }
948
1163
  const duration = timer.ms();
949
1164
  this.metrics.receivedTx(duration, true);
950
1165
  this.log.info(`Received tx ${txHash} in ${duration}ms`, { txHash });
@@ -966,10 +1181,15 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
966
1181
  // If the tx is in the pool but not in the archiver, it's pending.
967
1182
  // This handles race conditions between archiver and p2p, where the archiver
968
1183
  // has pruned the block in which a tx was mined, but p2p has not caught up yet.
969
- receipt = new TxReceipt(txHash, TxStatus.PENDING, undefined, undefined);
1184
+ receipt = new TxReceipt(txHash, TxStatus.PENDING, /*executionResult=*/ undefined, /*error=*/ undefined);
970
1185
  } else {
971
1186
  // Otherwise, if we don't know the tx, we consider it dropped.
972
- receipt = new TxReceipt(txHash, TxStatus.DROPPED, undefined, 'Tx dropped by P2P node');
1187
+ receipt = new TxReceipt(
1188
+ txHash,
1189
+ TxStatus.DROPPED,
1190
+ /*executionResult=*/ undefined,
1191
+ /*error=*/ 'Tx dropped by P2P node',
1192
+ );
973
1193
  }
974
1194
 
975
1195
  this.debugLogStore.decorateReceiptWithLogs(txHash.toString(), receipt);
@@ -986,6 +1206,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
986
1206
  */
987
1207
  public async stop() {
988
1208
  this.log.info(`Stopping Aztec Node`);
1209
+ await tryStop(this.attestationsBlockWatcher);
989
1210
  await tryStop(this.validatorsSentinel);
990
1211
  await tryStop(this.epochPruneWatcher);
991
1212
  await tryStop(this.slasherClient);
@@ -1257,41 +1478,6 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
1257
1478
  return preimage.leaf.value;
1258
1479
  }
1259
1480
 
1260
- public async getBlockHeader(block: BlockParameter = 'latest'): Promise<BlockHeader | undefined> {
1261
- if (BlockHash.isBlockHash(block)) {
1262
- const initialBlockHash = await this.#getInitialHeaderHash();
1263
- if (block.equals(initialBlockHash)) {
1264
- // Block source doesn't handle initial header so we need to handle the case separately.
1265
- return this.worldStateSynchronizer.getCommitted().getInitialHeader();
1266
- }
1267
- return this.blockSource.getBlockHeaderByHash(block);
1268
- } else {
1269
- // Block source doesn't handle initial header so we need to handle the case separately.
1270
- const blockNumber = block === 'latest' ? await this.getBlockNumber() : (block as BlockNumber);
1271
- if (blockNumber === BlockNumber.ZERO) {
1272
- return this.worldStateSynchronizer.getCommitted().getInitialHeader();
1273
- }
1274
- return this.blockSource.getBlockHeader(block);
1275
- }
1276
- }
1277
-
1278
- /**
1279
- * Get a block header specified by its archive root.
1280
- * @param archive - The archive root being requested.
1281
- * @returns The requested block header.
1282
- */
1283
- public async getBlockHeaderByArchive(archive: Fr): Promise<BlockHeader | undefined> {
1284
- return await this.blockSource.getBlockHeaderByArchive(archive);
1285
- }
1286
-
1287
- public getBlockData(number: BlockNumber): Promise<BlockData | undefined> {
1288
- return this.blockSource.getBlockData(number);
1289
- }
1290
-
1291
- public getBlockDataByArchive(archive: Fr): Promise<BlockData | undefined> {
1292
- return this.blockSource.getBlockDataByArchive(archive);
1293
- }
1294
-
1295
1481
  /**
1296
1482
  * Simulates the public part of a transaction with the current state.
1297
1483
  * @param tx - The transaction to simulate.
@@ -1751,7 +1937,9 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
1751
1937
 
1752
1938
  // Check it's within world state sync range
1753
1939
  if (blockNumber > blockSyncedTo) {
1754
- throw new Error(`Queried block ${block} not yet synced by the node (node is synced upto ${blockSyncedTo}).`);
1940
+ throw new Error(
1941
+ `Queried block ${inspectBlockParameter(block)} not yet synced by the node (node is synced upto ${blockSyncedTo}).`,
1942
+ );
1755
1943
  }
1756
1944
  this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
1757
1945
 
@@ -1771,23 +1959,31 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
1771
1959
  return snapshot;
1772
1960
  }
1773
1961
 
1774
- /** Resolves a block parameter to a block number. */
1962
+ /** Resolves any {@link BlockParameter} variant to a concrete block number. */
1775
1963
  protected async resolveBlockNumber(block: BlockParameter): Promise<BlockNumber> {
1776
- if (block === 'latest') {
1777
- return BlockNumber(await this.blockSource.getBlockNumber());
1964
+ const resolved = await this.resolveBlockParameter(block);
1965
+ if (resolved.number !== undefined) {
1966
+ return resolved.number;
1778
1967
  }
1779
- if (BlockHash.isBlockHash(block)) {
1968
+ if (resolved.hash !== undefined) {
1780
1969
  const initialBlockHash = await this.#getInitialHeaderHash();
1781
- if (block.equals(initialBlockHash)) {
1970
+ if (resolved.hash.equals(initialBlockHash)) {
1782
1971
  return BlockNumber.ZERO;
1783
1972
  }
1784
- const header = await this.blockSource.getBlockHeaderByHash(block);
1973
+ const header = await this.blockSource.getBlockHeaderByHash(resolved.hash);
1974
+ if (!header) {
1975
+ throw new Error(`Block hash ${resolved.hash.toString()} not found.`);
1976
+ }
1977
+ return header.getBlockNumber();
1978
+ }
1979
+ if (resolved.archive !== undefined) {
1980
+ const header = await this.blockSource.getBlockHeaderByArchive(resolved.archive);
1785
1981
  if (!header) {
1786
- throw new Error(`Block hash ${block.toString()} not found.`);
1982
+ throw new Error(`Block with archive ${resolved.archive.toString()} not found.`);
1787
1983
  }
1788
1984
  return header.getBlockNumber();
1789
1985
  }
1790
- return block as BlockNumber;
1986
+ throw new BadRequestError(`Invalid BlockParameter: ${JSON.stringify(block)}`);
1791
1987
  }
1792
1988
 
1793
1989
  /**
@@ -3,6 +3,7 @@ import { createLogger } from '@aztec/foundation/log';
3
3
  import { createStore } from '@aztec/kv-store/lmdb-v2';
4
4
  import type { P2PClient } from '@aztec/p2p';
5
5
  import type { L2BlockSource } from '@aztec/stdlib/block';
6
+ import type { ChainConfig } from '@aztec/stdlib/config';
6
7
  import type { SlasherConfig } from '@aztec/stdlib/interfaces/server';
7
8
  import type { DataStoreConfig } from '@aztec/stdlib/kv-store';
8
9
 
@@ -14,7 +15,7 @@ export async function createSentinel(
14
15
  epochCache: EpochCache,
15
16
  archiver: L2BlockSource,
16
17
  p2p: P2PClient,
17
- config: SentinelConfig & DataStoreConfig & SlasherConfig,
18
+ config: SentinelConfig & DataStoreConfig & SlasherConfig & Pick<ChainConfig, 'l1ChainId' | 'l1Contracts'>,
18
19
  logger = createLogger('node:sentinel'),
19
20
  ): Promise<Sentinel | undefined> {
20
21
  if (!config.sentinelEnabled) {
@@ -21,7 +21,9 @@ import {
21
21
  type L2BlockStreamEventHandler,
22
22
  getAttestationInfoFromPublishedCheckpoint,
23
23
  } from '@aztec/stdlib/block';
24
+ import type { ChainConfig } from '@aztec/stdlib/config';
24
25
  import { getEpochAtSlot, getSlotRangeForEpoch, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
26
+ import type { CoordinationSignatureContext } from '@aztec/stdlib/p2p';
25
27
  import type {
26
28
  SingleValidatorStats,
27
29
  ValidatorStats,
@@ -36,6 +38,12 @@ import EventEmitter from 'node:events';
36
38
 
37
39
  import { SentinelStore } from './store.js';
38
40
 
41
+ export type SentinelRuntimeConfig = Pick<
42
+ SlasherConfig,
43
+ 'slashInactivityTargetPercentage' | 'slashInactivityPenalty' | 'slashInactivityConsecutiveEpochThreshold'
44
+ > &
45
+ Pick<ChainConfig, 'l1ChainId' | 'l1Contracts'>;
46
+
39
47
  /** Maps a validator status to its category: proposer or attestation. */
40
48
  function statusToCategory(status: ValidatorStatusInSlot): ValidatorStatusType {
41
49
  switch (status) {
@@ -65,10 +73,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
65
73
  protected archiver: L2BlockSource,
66
74
  protected p2p: P2PClient,
67
75
  protected store: SentinelStore,
68
- protected config: Pick<
69
- SlasherConfig,
70
- 'slashInactivityTargetPercentage' | 'slashInactivityPenalty' | 'slashInactivityConsecutiveEpochThreshold'
71
- >,
76
+ protected config: SentinelRuntimeConfig,
72
77
  protected logger = createLogger('node:sentinel'),
73
78
  ) {
74
79
  super();
@@ -77,6 +82,13 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
77
82
  this.runningPromise = new RunningPromise(this.work.bind(this), logger, interval);
78
83
  }
79
84
 
85
+ private getSignatureContext(): CoordinationSignatureContext {
86
+ return {
87
+ chainId: this.config.l1ChainId,
88
+ rollupAddress: this.config.l1Contracts.rollupAddress,
89
+ };
90
+ }
91
+
80
92
  public updateConfig(config: Partial<SlasherConfig>) {
81
93
  this.config = { ...this.config, ...config };
82
94
  }
@@ -117,7 +129,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
117
129
  this.slotNumberToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, {
118
130
  checkpointNumber: checkpoint.checkpoint.number,
119
131
  archive: checkpoint.checkpoint.archive.root.toString(),
120
- attestors: getAttestationInfoFromPublishedCheckpoint(checkpoint)
132
+ attestors: getAttestationInfoFromPublishedCheckpoint(checkpoint, this.getSignatureContext())
121
133
  .filter(a => a.status === 'recovered-from-signature')
122
134
  .map(a => a.address!),
123
135
  });