@aztec/aztec-node 0.0.1-commit.b655e406 → 0.0.1-commit.d3ec352c

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.
@@ -17,12 +17,12 @@ import {
17
17
  createEthereumChain,
18
18
  getPublicClient,
19
19
  } from '@aztec/ethereum';
20
+ import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
20
21
  import { compactArray, pick } from '@aztec/foundation/collection';
21
22
  import { EthAddress } from '@aztec/foundation/eth-address';
22
23
  import { Fr } from '@aztec/foundation/fields';
23
24
  import { BadRequestError } from '@aztec/foundation/json-rpc';
24
25
  import { type Logger, createLogger } from '@aztec/foundation/log';
25
- import { SerialQueue } from '@aztec/foundation/queue';
26
26
  import { count } from '@aztec/foundation/string';
27
27
  import { DateProvider, Timer } from '@aztec/foundation/timer';
28
28
  import { MembershipWitness, SiblingPath } from '@aztec/foundation/trees';
@@ -46,12 +46,13 @@ import {
46
46
  type Watcher,
47
47
  createSlasher,
48
48
  } from '@aztec/slasher';
49
+ import { CollectionLimitsConfig, PublicSimulatorConfig } from '@aztec/stdlib/avm';
49
50
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
50
51
  import {
51
- type InBlock,
52
+ type BlockParameter,
53
+ type DataInBlock,
52
54
  type L2Block,
53
55
  L2BlockHash,
54
- type L2BlockNumber,
55
56
  type L2BlockSource,
56
57
  type PublishedL2Block,
57
58
  } from '@aztec/stdlib/block';
@@ -62,7 +63,7 @@ import type {
62
63
  NodeInfo,
63
64
  ProtocolContractAddresses,
64
65
  } from '@aztec/stdlib/contract';
65
- import type { GasFees } from '@aztec/stdlib/gas';
66
+ import { GasFees } from '@aztec/stdlib/gas';
66
67
  import { computePublicDataTreeLeafSlot } from '@aztec/stdlib/hash';
67
68
  import {
68
69
  type AztecNode,
@@ -132,9 +133,6 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
132
133
  // Prevent two snapshot operations to happen simultaneously
133
134
  private isUploadingSnapshot = false;
134
135
 
135
- // Serial queue to ensure that we only send one tx at a time
136
- private txQueue: SerialQueue = new SerialQueue();
137
-
138
136
  public readonly tracer: Tracer;
139
137
 
140
138
  constructor(
@@ -160,7 +158,6 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
160
158
  ) {
161
159
  this.metrics = new NodeMetrics(telemetry, 'AztecNodeService');
162
160
  this.tracer = telemetry.getTracer('AztecNodeService');
163
- this.txQueue.start();
164
161
 
165
162
  this.log.info(`Aztec Node version: ${this.packageVersion}`);
166
163
  this.log.info(`Aztec Node started on chain 0x${l1ChainId.toString(16)}`, config.l1Contracts);
@@ -287,7 +284,9 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
287
284
  options.prefilledPublicData,
288
285
  telemetry,
289
286
  );
290
- const circuitVerifier = config.realProofs ? await BBCircuitVerifier.new(config) : new TestCircuitVerifier();
287
+ const circuitVerifier = config.realProofs
288
+ ? await BBCircuitVerifier.new(config)
289
+ : new TestCircuitVerifier(config.proverTestVerificationDelayMs);
291
290
  if (!config.realProofs) {
292
291
  log.warn(`Aztec node is accepting fake proofs`);
293
292
  }
@@ -550,8 +549,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
550
549
  * @param number - The block number being requested.
551
550
  * @returns The requested block.
552
551
  */
553
- public async getBlock(number: L2BlockNumber): Promise<L2Block | undefined> {
554
- const blockNumber = number === 'latest' ? await this.getBlockNumber() : number;
552
+ public async getBlock(number: BlockParameter): Promise<L2Block | undefined> {
553
+ const blockNumber = number === 'latest' ? await this.getBlockNumber() : (number as BlockNumber);
555
554
  return await this.blockSource.getBlock(blockNumber);
556
555
  }
557
556
 
@@ -581,11 +580,11 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
581
580
  * @param limit - The maximum number of blocks to obtain.
582
581
  * @returns The blocks requested.
583
582
  */
584
- public async getBlocks(from: number, limit: number): Promise<L2Block[]> {
583
+ public async getBlocks(from: BlockNumber, limit: number): Promise<L2Block[]> {
585
584
  return (await this.blockSource.getBlocks(from, limit)) ?? [];
586
585
  }
587
586
 
588
- public async getPublishedBlocks(from: number, limit: number): Promise<PublishedL2Block[]> {
587
+ public async getPublishedBlocks(from: BlockNumber, limit: number): Promise<PublishedL2Block[]> {
589
588
  return (await this.blockSource.getPublishedBlocks(from, limit)) ?? [];
590
589
  }
591
590
 
@@ -597,15 +596,23 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
597
596
  return await this.globalVariableBuilder.getCurrentBaseFees();
598
597
  }
599
598
 
599
+ public async getMaxPriorityFees(): Promise<GasFees> {
600
+ for await (const tx of this.p2pClient.iteratePendingTxs()) {
601
+ return tx.getGasSettings().maxPriorityFeesPerGas;
602
+ }
603
+
604
+ return GasFees.from({ feePerDaGas: 0n, feePerL2Gas: 0n });
605
+ }
606
+
600
607
  /**
601
608
  * Method to fetch the latest block number synchronized by the node.
602
609
  * @returns The block number.
603
610
  */
604
- public async getBlockNumber(): Promise<number> {
611
+ public async getBlockNumber(): Promise<BlockNumber> {
605
612
  return await this.blockSource.getBlockNumber();
606
613
  }
607
614
 
608
- public async getProvenBlockNumber(): Promise<number> {
615
+ public async getProvenBlockNumber(): Promise<BlockNumber> {
609
616
  return await this.blockSource.getProvenBlockNumber();
610
617
  }
611
618
 
@@ -647,7 +654,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
647
654
  * @param limit - The maximum number of blocks to retrieve logs from.
648
655
  * @returns An array of private logs from the specified range of blocks.
649
656
  */
650
- public getPrivateLogs(from: number, limit: number): Promise<PrivateLog[]> {
657
+ public getPrivateLogs(from: BlockNumber, limit: number): Promise<PrivateLog[]> {
651
658
  return this.logsSource.getPrivateLogs(from, limit);
652
659
  }
653
660
 
@@ -685,7 +692,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
685
692
  * @param tx - The transaction to be submitted.
686
693
  */
687
694
  public async sendTx(tx: Tx) {
688
- await this.txQueue.put(() => this.#sendTx(tx));
695
+ await this.#sendTx(tx);
689
696
  }
690
697
 
691
698
  async #sendTx(tx: Tx) {
@@ -732,7 +739,6 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
732
739
  */
733
740
  public async stop() {
734
741
  this.log.info(`Stopping Aztec Node`);
735
- await this.txQueue.end();
736
742
  await tryStop(this.validatorsSentinel);
737
743
  await tryStop(this.epochPruneWatcher);
738
744
  await tryStop(this.slasherClient);
@@ -786,10 +792,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
786
792
  * @returns The indices of leaves and the block metadata of a block in which the leaves were inserted.
787
793
  */
788
794
  public async findLeavesIndexes(
789
- blockNumber: L2BlockNumber,
795
+ blockNumber: BlockParameter,
790
796
  treeId: MerkleTreeId,
791
797
  leafValues: Fr[],
792
- ): Promise<(InBlock<bigint> | undefined)[]> {
798
+ ): Promise<(DataInBlock<bigint> | undefined)[]> {
793
799
  const committedDb = await this.#getWorldState(blockNumber);
794
800
  const maybeIndices = await committedDb.findLeafIndices(
795
801
  treeId,
@@ -815,7 +821,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
815
821
  // (note that block number corresponds to the leaf index in the archive tree).
816
822
  const blockHashes = await Promise.all(
817
823
  uniqueBlockNumbers.map(blockNumber => {
818
- return committedDb.getLeafValue(MerkleTreeId.ARCHIVE, blockNumber!);
824
+ return committedDb.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber));
819
825
  }),
820
826
  );
821
827
 
@@ -826,7 +832,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
826
832
  }
827
833
  }
828
834
 
829
- // Create InBlock objects by combining indices, blockNumbers and blockHashes and return them.
835
+ // Create DataInBlock objects by combining indices, blockNumbers and blockHashes and return them.
830
836
  return maybeIndices.map((index, i) => {
831
837
  if (index === undefined) {
832
838
  return undefined;
@@ -841,7 +847,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
841
847
  return undefined;
842
848
  }
843
849
  return {
844
- l2BlockNumber: Number(blockNumber),
850
+ l2BlockNumber: BlockNumber(Number(blockNumber)),
845
851
  l2BlockHash: L2BlockHash.fromField(blockHash),
846
852
  data: index,
847
853
  };
@@ -855,7 +861,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
855
861
  * @returns The sibling path for the leaf index.
856
862
  */
857
863
  public async getNullifierSiblingPath(
858
- blockNumber: L2BlockNumber,
864
+ blockNumber: BlockParameter,
859
865
  leafIndex: bigint,
860
866
  ): Promise<SiblingPath<typeof NULLIFIER_TREE_HEIGHT>> {
861
867
  const committedDb = await this.#getWorldState(blockNumber);
@@ -869,7 +875,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
869
875
  * @returns The sibling path for the leaf index.
870
876
  */
871
877
  public async getNoteHashSiblingPath(
872
- blockNumber: L2BlockNumber,
878
+ blockNumber: BlockParameter,
873
879
  leafIndex: bigint,
874
880
  ): Promise<SiblingPath<typeof NOTE_HASH_TREE_HEIGHT>> {
875
881
  const committedDb = await this.#getWorldState(blockNumber);
@@ -877,7 +883,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
877
883
  }
878
884
 
879
885
  public async getArchiveMembershipWitness(
880
- blockNumber: L2BlockNumber,
886
+ blockNumber: BlockParameter,
881
887
  archive: Fr,
882
888
  ): Promise<MembershipWitness<typeof ARCHIVE_HEIGHT> | undefined> {
883
889
  const committedDb = await this.#getWorldState(blockNumber);
@@ -888,7 +894,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
888
894
  }
889
895
 
890
896
  public async getNoteHashMembershipWitness(
891
- blockNumber: L2BlockNumber,
897
+ blockNumber: BlockParameter,
892
898
  noteHash: Fr,
893
899
  ): Promise<MembershipWitness<typeof NOTE_HASH_TREE_HEIGHT> | undefined> {
894
900
  const committedDb = await this.#getWorldState(blockNumber);
@@ -908,7 +914,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
908
914
  * @returns A tuple of the index and the sibling path of the L1ToL2Message (undefined if not found).
909
915
  */
910
916
  public async getL1ToL2MessageMembershipWitness(
911
- blockNumber: L2BlockNumber,
917
+ blockNumber: BlockParameter,
912
918
  l1ToL2Message: Fr,
913
919
  ): Promise<[bigint, SiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>] | undefined> {
914
920
  const db = await this.#getWorldState(blockNumber);
@@ -921,9 +927,9 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
921
927
  return [witness.index, witness.path];
922
928
  }
923
929
 
924
- public async getL1ToL2MessageBlock(l1ToL2Message: Fr): Promise<number | undefined> {
930
+ public async getL1ToL2MessageBlock(l1ToL2Message: Fr): Promise<BlockNumber | undefined> {
925
931
  const messageIndex = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
926
- return messageIndex ? InboxLeaf.l2BlockFromIndex(messageIndex) : undefined;
932
+ return messageIndex ? BlockNumber(InboxLeaf.l2BlockFromIndex(messageIndex)) : undefined;
927
933
  }
928
934
 
929
935
  /**
@@ -941,8 +947,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
941
947
  * @param blockNumber - The block number at which to get the data.
942
948
  * @returns The L2 to L1 messages (undefined if the block number is not found).
943
949
  */
944
- public async getL2ToL1Messages(blockNumber: L2BlockNumber): Promise<Fr[][] | undefined> {
945
- const block = await this.blockSource.getBlock(blockNumber === 'latest' ? await this.getBlockNumber() : blockNumber);
950
+ public async getL2ToL1Messages(blockNumber: BlockParameter): Promise<Fr[][] | undefined> {
951
+ const block = await this.blockSource.getBlock(
952
+ blockNumber === 'latest' ? await this.getBlockNumber() : (blockNumber as BlockNumber),
953
+ );
946
954
  return block?.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs);
947
955
  }
948
956
 
@@ -953,7 +961,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
953
961
  * @returns The sibling path.
954
962
  */
955
963
  public async getArchiveSiblingPath(
956
- blockNumber: L2BlockNumber,
964
+ blockNumber: BlockParameter,
957
965
  leafIndex: bigint,
958
966
  ): Promise<SiblingPath<typeof ARCHIVE_HEIGHT>> {
959
967
  const committedDb = await this.#getWorldState(blockNumber);
@@ -967,7 +975,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
967
975
  * @returns The sibling path.
968
976
  */
969
977
  public async getPublicDataSiblingPath(
970
- blockNumber: L2BlockNumber,
978
+ blockNumber: BlockParameter,
971
979
  leafIndex: bigint,
972
980
  ): Promise<SiblingPath<typeof PUBLIC_DATA_TREE_HEIGHT>> {
973
981
  const committedDb = await this.#getWorldState(blockNumber);
@@ -981,7 +989,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
981
989
  * @returns The nullifier membership witness (if found).
982
990
  */
983
991
  public async getNullifierMembershipWitness(
984
- blockNumber: L2BlockNumber,
992
+ blockNumber: BlockParameter,
985
993
  nullifier: Fr,
986
994
  ): Promise<NullifierMembershipWitness | undefined> {
987
995
  const db = await this.#getWorldState(blockNumber);
@@ -1014,7 +1022,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1014
1022
  * TODO: This is a confusing behavior and we should eventually address that.
1015
1023
  */
1016
1024
  public async getLowNullifierMembershipWitness(
1017
- blockNumber: L2BlockNumber,
1025
+ blockNumber: BlockParameter,
1018
1026
  nullifier: Fr,
1019
1027
  ): Promise<NullifierMembershipWitness | undefined> {
1020
1028
  const committedDb = await this.#getWorldState(blockNumber);
@@ -1032,7 +1040,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1032
1040
  return new NullifierMembershipWitness(BigInt(index), preimageData as NullifierLeafPreimage, siblingPath);
1033
1041
  }
1034
1042
 
1035
- async getPublicDataWitness(blockNumber: L2BlockNumber, leafSlot: Fr): Promise<PublicDataWitness | undefined> {
1043
+ async getPublicDataWitness(blockNumber: BlockParameter, leafSlot: Fr): Promise<PublicDataWitness | undefined> {
1036
1044
  const committedDb = await this.#getWorldState(blockNumber);
1037
1045
  const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
1038
1046
  if (!lowLeafResult) {
@@ -1058,7 +1066,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1058
1066
  * @param blockNumber - The block number at which to get the data or 'latest'.
1059
1067
  * @returns Storage value at the given contract slot.
1060
1068
  */
1061
- public async getPublicStorageAt(blockNumber: L2BlockNumber, contract: AztecAddress, slot: Fr): Promise<Fr> {
1069
+ public async getPublicStorageAt(blockNumber: BlockParameter, contract: AztecAddress, slot: Fr): Promise<Fr> {
1062
1070
  const committedDb = await this.#getWorldState(blockNumber);
1063
1071
  const leafSlot = await computePublicDataTreeLeafSlot(contract, slot);
1064
1072
 
@@ -1077,10 +1085,11 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1077
1085
  * Returns the currently committed block header, or the initial header if no blocks have been produced.
1078
1086
  * @returns The current committed block header.
1079
1087
  */
1080
- public async getBlockHeader(blockNumber: L2BlockNumber = 'latest'): Promise<BlockHeader | undefined> {
1081
- return blockNumber === 0 || (blockNumber === 'latest' && (await this.blockSource.getBlockNumber()) === 0)
1088
+ public async getBlockHeader(blockNumber: BlockParameter = 'latest'): Promise<BlockHeader | undefined> {
1089
+ return blockNumber === BlockNumber.ZERO ||
1090
+ (blockNumber === 'latest' && (await this.blockSource.getBlockNumber()) === BlockNumber.ZERO)
1082
1091
  ? this.worldStateSynchronizer.getCommitted().getInitialHeader()
1083
- : this.blockSource.getBlockHeader(blockNumber);
1092
+ : this.blockSource.getBlockHeader(blockNumber === 'latest' ? blockNumber : (blockNumber as BlockNumber));
1084
1093
  }
1085
1094
 
1086
1095
  /**
@@ -1124,7 +1133,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1124
1133
  }
1125
1134
 
1126
1135
  const txHash = tx.getTxHash();
1127
- const blockNumber = (await this.blockSource.getBlockNumber()) + 1;
1136
+ const blockNumber = BlockNumber((await this.blockSource.getBlockNumber()) + 1);
1128
1137
 
1129
1138
  // If sequencer is not initialized, we just set these values to zero for simulation.
1130
1139
  const coinbase = EthAddress.ZERO;
@@ -1149,11 +1158,17 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1149
1158
 
1150
1159
  const merkleTreeFork = await this.worldStateSynchronizer.fork();
1151
1160
  try {
1152
- const processor = publicProcessorFactory.create(merkleTreeFork, newGlobalVariables, {
1161
+ const config = PublicSimulatorConfig.from({
1153
1162
  skipFeeEnforcement,
1154
- clientInitiatedSimulation: true,
1155
- maxDebugLogMemoryReads: this.config.rpcSimulatePublicMaxDebugLogMemoryReads,
1163
+ collectDebugLogs: true,
1164
+ collectHints: false,
1165
+ collectCallMetadata: true,
1166
+ collectStatistics: false,
1167
+ collectionLimits: CollectionLimitsConfig.from({
1168
+ maxDebugLogMemoryReads: this.config.rpcSimulatePublicMaxDebugLogMemoryReads,
1169
+ }),
1156
1170
  });
1171
+ const processor = publicProcessorFactory.create(merkleTreeFork, newGlobalVariables, config);
1157
1172
 
1158
1173
  // REFACTOR: Consider merging ProcessReturnValues into ProcessedTx
1159
1174
  const [processedTxs, failedTxs, _usedTxs, returns] = await processor.process([tx]);
@@ -1185,7 +1200,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1185
1200
 
1186
1201
  // We accept transactions if they are not expired by the next slot (checked based on the IncludeByTimestamp field)
1187
1202
  const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
1188
- const blockNumber = (await this.blockSource.getBlockNumber()) + 1;
1203
+ const blockNumber = BlockNumber((await this.blockSource.getBlockNumber()) + 1);
1189
1204
  const validator = createValidatorForAcceptingTxs(db, this.contractDataSource, verifier, {
1190
1205
  timestamp: nextSlotTimestamp,
1191
1206
  blockNumber,
@@ -1242,8 +1257,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1242
1257
 
1243
1258
  public getValidatorStats(
1244
1259
  validatorAddress: EthAddress,
1245
- fromSlot?: bigint,
1246
- toSlot?: bigint,
1260
+ fromSlot?: SlotNumber,
1261
+ toSlot?: SlotNumber,
1247
1262
  ): Promise<SingleValidatorStats | undefined> {
1248
1263
  return this.validatorsSentinel?.getValidatorStats(validatorAddress, fromSlot, toSlot) ?? Promise.resolve(undefined);
1249
1264
  }
@@ -1292,7 +1307,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1292
1307
  return Promise.resolve();
1293
1308
  }
1294
1309
 
1295
- public async rollbackTo(targetBlock: number, force?: boolean): Promise<void> {
1310
+ public async rollbackTo(targetBlock: BlockNumber, force?: boolean): Promise<void> {
1296
1311
  const archiver = this.blockSource as Archiver;
1297
1312
  if (!('rollbackTo' in archiver)) {
1298
1313
  throw new Error('Archiver implementation does not support rollbacks.');
@@ -1364,12 +1379,12 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1364
1379
  * @param blockNumber - The block number at which to get the data.
1365
1380
  * @returns An instance of a committed MerkleTreeOperations
1366
1381
  */
1367
- async #getWorldState(blockNumber: L2BlockNumber) {
1382
+ async #getWorldState(blockNumber: BlockParameter) {
1368
1383
  if (typeof blockNumber === 'number' && blockNumber < INITIAL_L2_BLOCK_NUM - 1) {
1369
1384
  throw new Error('Invalid block number to get world state for: ' + blockNumber);
1370
1385
  }
1371
1386
 
1372
- let blockSyncedTo: number = 0;
1387
+ let blockSyncedTo: BlockNumber = BlockNumber.ZERO;
1373
1388
  try {
1374
1389
  // Attempt to sync the world state if necessary
1375
1390
  blockSyncedTo = await this.#syncWorldState();
@@ -1383,7 +1398,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1383
1398
  return this.worldStateSynchronizer.getCommitted();
1384
1399
  } else if (blockNumber <= blockSyncedTo) {
1385
1400
  this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
1386
- return this.worldStateSynchronizer.getSnapshot(blockNumber);
1401
+ return this.worldStateSynchronizer.getSnapshot(blockNumber as BlockNumber);
1387
1402
  } else {
1388
1403
  throw new Error(`Block ${blockNumber} not yet synced`);
1389
1404
  }
@@ -1393,8 +1408,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
1393
1408
  * Ensure we fully sync the world state
1394
1409
  * @returns A promise that fulfils once the world state is synced
1395
1410
  */
1396
- async #syncWorldState(): Promise<number> {
1411
+ async #syncWorldState(): Promise<BlockNumber> {
1397
1412
  const blockSourceHeight = await this.blockSource.getBlockNumber();
1398
- return this.worldStateSynchronizer.syncImmediate(blockSourceHeight);
1413
+ return await this.worldStateSynchronizer.syncImmediate(blockSourceHeight);
1399
1414
  }
1400
1415
  }
@@ -1,4 +1,5 @@
1
1
  import type { EpochCache } from '@aztec/epoch-cache';
2
+ import { BlockNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
2
3
  import { countWhile, filterAsync, fromEntries, getEntries, mapValues } from '@aztec/foundation/collection';
3
4
  import { EthAddress } from '@aztec/foundation/eth-address';
4
5
  import { createLogger } from '@aztec/foundation/log';
@@ -40,9 +41,10 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
40
41
  protected blockStream!: L2BlockStream;
41
42
  protected l2TipsStore: L2TipsStore;
42
43
 
43
- protected initialSlot: bigint | undefined;
44
- protected lastProcessedSlot: bigint | undefined;
45
- protected slotNumberToBlock: Map<bigint, { blockNumber: number; archive: string; attestors: EthAddress[] }> =
44
+ protected initialSlot: SlotNumber | undefined;
45
+ protected lastProcessedSlot: SlotNumber | undefined;
46
+ // eslint-disable-next-line aztec-custom/no-non-primitive-in-collections
47
+ protected slotNumberToBlock: Map<SlotNumber, { blockNumber: BlockNumber; archive: string; attestors: EthAddress[] }> =
46
48
  new Map();
47
49
 
48
50
  constructor(
@@ -74,7 +76,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
74
76
  /** Loads initial slot and initializes blockstream. We will not process anything at or before the initial slot. */
75
77
  protected async init() {
76
78
  this.initialSlot = this.epochCache.getEpochAndSlotNow().slot;
77
- const startingBlock = await this.archiver.getBlockNumber();
79
+ const startingBlock = BlockNumber(await this.archiver.getBlockNumber());
78
80
  this.logger.info(`Starting validator sentinel with initial slot ${this.initialSlot} and block ${startingBlock}`);
79
81
  this.blockStream = new L2BlockStream(this.archiver, this.l2TipsStore, this, this.logger, { startingBlock });
80
82
  }
@@ -89,7 +91,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
89
91
  // Store mapping from slot to archive, block number, and attestors
90
92
  for (const block of event.blocks) {
91
93
  this.slotNumberToBlock.set(block.block.header.getSlot(), {
92
- blockNumber: block.block.number,
94
+ blockNumber: BlockNumber(block.block.number),
93
95
  archive: block.block.archive.root.toString(),
94
96
  attestors: getAttestationInfoFromPublishedL2Block(block)
95
97
  .filter(a => a.status === 'recovered-from-signature')
@@ -116,7 +118,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
116
118
  if (event.type !== 'chain-proven') {
117
119
  return;
118
120
  }
119
- const blockNumber = event.block.number;
121
+ const blockNumber = BlockNumber(event.block.number);
120
122
  const block = await this.archiver.getBlock(blockNumber);
121
123
  if (!block) {
122
124
  this.logger.error(`Failed to get block ${blockNumber}`, { block });
@@ -134,7 +136,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
134
136
  await this.handleProvenPerformance(epoch, performance);
135
137
  }
136
138
 
137
- protected async computeProvenPerformance(epoch: bigint): Promise<ValidatorsEpochPerformance> {
139
+ protected async computeProvenPerformance(epoch: EpochNumber): Promise<ValidatorsEpochPerformance> {
138
140
  const [fromSlot, toSlot] = getSlotRangeForEpoch(epoch, this.epochCache.getL1Constants());
139
141
  const { committee } = await this.epochCache.getCommittee(fromSlot);
140
142
  if (!committee) {
@@ -142,7 +144,11 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
142
144
  return {};
143
145
  }
144
146
 
145
- const stats = await this.computeStats({ fromSlot, toSlot, validators: committee });
147
+ const stats = await this.computeStats({
148
+ fromSlot,
149
+ toSlot,
150
+ validators: committee,
151
+ });
146
152
  this.logger.debug(`Stats for epoch ${epoch}`, { ...stats, fromSlot, toSlot, epoch });
147
153
 
148
154
  // Note that we are NOT using the total slots in the epoch as `total` here, since we only
@@ -165,7 +171,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
165
171
  */
166
172
  protected async checkPastInactivity(
167
173
  validator: EthAddress,
168
- currentEpoch: bigint,
174
+ currentEpoch: EpochNumber,
169
175
  requiredConsecutiveEpochs: number,
170
176
  ): Promise<boolean> {
171
177
  if (requiredConsecutiveEpochs === 0) {
@@ -175,23 +181,24 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
175
181
  // Get all historical performance for this validator
176
182
  const allPerformance = await this.store.getProvenPerformance(validator);
177
183
 
184
+ // Sort by epoch descending to get most recent first, keep only epochs strictly before the current one, and get the first N
185
+ const pastEpochs = allPerformance.sort((a, b) => Number(b.epoch - a.epoch)).filter(p => p.epoch < currentEpoch);
186
+
178
187
  // If we don't have enough historical data, don't slash
179
- if (allPerformance.length < requiredConsecutiveEpochs) {
188
+ if (pastEpochs.length < requiredConsecutiveEpochs) {
180
189
  this.logger.debug(
181
190
  `Not enough historical data for slashing ${validator} for inactivity (${allPerformance.length} epochs < ${requiredConsecutiveEpochs} required)`,
182
191
  );
183
192
  return false;
184
193
  }
185
194
 
186
- // Sort by epoch descending to get most recent first, keep only epochs strictly before the current one, and get the first N
187
- return allPerformance
188
- .sort((a, b) => Number(b.epoch - a.epoch))
189
- .filter(p => p.epoch < currentEpoch)
195
+ // Check that we have at least requiredConsecutiveEpochs and that all of them are above the inactivity threshold
196
+ return pastEpochs
190
197
  .slice(0, requiredConsecutiveEpochs)
191
198
  .every(p => p.missed / p.total >= this.config.slashInactivityTargetPercentage);
192
199
  }
193
200
 
194
- protected async handleProvenPerformance(epoch: bigint, performance: ValidatorsEpochPerformance) {
201
+ protected async handleProvenPerformance(epoch: EpochNumber, performance: ValidatorsEpochPerformance) {
195
202
  if (this.config.slashInactivityPenalty === 0n) {
196
203
  return;
197
204
  }
@@ -215,7 +222,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
215
222
  validator: EthAddress.fromString(address),
216
223
  amount: this.config.slashInactivityPenalty,
217
224
  offenseType: OffenseType.INACTIVITY,
218
- epochOrSlot: epoch,
225
+ epochOrSlot: BigInt(epoch),
219
226
  }));
220
227
 
221
228
  if (criminals.length > 0) {
@@ -256,8 +263,13 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
256
263
  * We also don't move past the archiver last synced L2 slot, as we don't want to process data that is not yet available.
257
264
  * Last, we check the p2p is synced with the archiver, so it has pulled all attestations from it.
258
265
  */
259
- protected async isReadyToProcess(currentSlot: bigint) {
260
- const targetSlot = currentSlot - 2n;
266
+ protected async isReadyToProcess(currentSlot: SlotNumber): Promise<SlotNumber | false> {
267
+ if (currentSlot < 2) {
268
+ this.logger.trace(`Current slot ${currentSlot} too early.`);
269
+ return false;
270
+ }
271
+
272
+ const targetSlot = SlotNumber(currentSlot - 2);
261
273
  if (this.lastProcessedSlot && this.lastProcessedSlot >= targetSlot) {
262
274
  this.logger.trace(`Already processed slot ${targetSlot}`, { lastProcessedSlot: this.lastProcessedSlot });
263
275
  return false;
@@ -294,7 +306,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
294
306
  * Gathers committee and proposer data for a given slot, computes slot stats,
295
307
  * and updates overall stats.
296
308
  */
297
- protected async processSlot(slot: bigint) {
309
+ protected async processSlot(slot: SlotNumber) {
298
310
  const { epoch, seed, committee } = await this.epochCache.getCommittee(slot);
299
311
  if (!committee || committee.length === 0) {
300
312
  this.logger.trace(`No committee found for slot ${slot} at epoch ${epoch}`);
@@ -310,7 +322,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
310
322
  }
311
323
 
312
324
  /** Computes activity for a given slot. */
313
- protected async getSlotActivity(slot: bigint, epoch: bigint, proposer: EthAddress, committee: EthAddress[]) {
325
+ protected async getSlotActivity(slot: SlotNumber, epoch: EpochNumber, proposer: EthAddress, committee: EthAddress[]) {
314
326
  this.logger.debug(`Computing stats for slot ${slot} at epoch ${epoch}`, { slot, epoch, proposer, committee });
315
327
 
316
328
  // Check if there is an L2 block in L1 for this L2 slot
@@ -372,7 +384,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
372
384
  }
373
385
 
374
386
  /** Push the status for each slot for each validator. */
375
- protected updateValidators(slot: bigint, stats: Record<`0x${string}`, ValidatorStatusInSlot | undefined>) {
387
+ protected updateValidators(slot: SlotNumber, stats: Record<`0x${string}`, ValidatorStatusInSlot | undefined>) {
376
388
  return this.store.updateValidators(slot, stats);
377
389
  }
378
390
 
@@ -381,13 +393,13 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
381
393
  fromSlot,
382
394
  toSlot,
383
395
  validators,
384
- }: { fromSlot?: bigint; toSlot?: bigint; validators?: EthAddress[] } = {}): Promise<ValidatorsStats> {
396
+ }: { fromSlot?: SlotNumber; toSlot?: SlotNumber; validators?: EthAddress[] } = {}): Promise<ValidatorsStats> {
385
397
  const histories = validators
386
398
  ? fromEntries(await Promise.all(validators.map(async v => [v.toString(), await this.store.getHistory(v)])))
387
399
  : await this.store.getHistories();
388
400
 
389
401
  const slotNow = this.epochCache.getEpochAndSlotNow().slot;
390
- fromSlot ??= (this.lastProcessedSlot ?? slotNow) - BigInt(this.store.getHistoryLength());
402
+ fromSlot ??= SlotNumber(Math.max((this.lastProcessedSlot ?? slotNow) - this.store.getHistoryLength(), 0));
391
403
  toSlot ??= this.lastProcessedSlot ?? slotNow;
392
404
 
393
405
  const stats = mapValues(histories, (history, address) =>
@@ -405,8 +417,8 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
405
417
  /** Computes stats for a single validator. */
406
418
  public async getValidatorStats(
407
419
  validatorAddress: EthAddress,
408
- fromSlot?: bigint,
409
- toSlot?: bigint,
420
+ fromSlot?: SlotNumber,
421
+ toSlot?: SlotNumber,
410
422
  ): Promise<SingleValidatorStats | undefined> {
411
423
  const history = await this.store.getHistory(validatorAddress);
412
424
 
@@ -415,13 +427,14 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
415
427
  }
416
428
 
417
429
  const slotNow = this.epochCache.getEpochAndSlotNow().slot;
418
- const effectiveFromSlot = fromSlot ?? (this.lastProcessedSlot ?? slotNow) - BigInt(this.store.getHistoryLength());
430
+ const effectiveFromSlot =
431
+ fromSlot ?? SlotNumber(Math.max((this.lastProcessedSlot ?? slotNow) - this.store.getHistoryLength(), 0));
419
432
  const effectiveToSlot = toSlot ?? this.lastProcessedSlot ?? slotNow;
420
433
 
421
434
  const historyLength = BigInt(this.store.getHistoryLength());
422
- if (effectiveToSlot - effectiveFromSlot > historyLength) {
435
+ if (BigInt(effectiveToSlot) - BigInt(effectiveFromSlot) > historyLength) {
423
436
  throw new Error(
424
- `Slot range (${effectiveToSlot - effectiveFromSlot}) exceeds history length (${historyLength}). ` +
437
+ `Slot range (${BigInt(effectiveToSlot) - BigInt(effectiveFromSlot)}) exceeds history length (${historyLength}). ` +
425
438
  `Requested range: ${effectiveFromSlot} to ${effectiveToSlot}.`,
426
439
  );
427
440
  }
@@ -432,11 +445,10 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
432
445
  effectiveFromSlot,
433
446
  effectiveToSlot,
434
447
  );
435
- const allTimeProvenPerformance = await this.store.getProvenPerformance(validatorAddress);
436
448
 
437
449
  return {
438
450
  validator,
439
- allTimeProvenPerformance,
451
+ allTimeProvenPerformance: await this.store.getProvenPerformance(validatorAddress),
440
452
  lastProcessedSlot: this.lastProcessedSlot,
441
453
  initialSlot: this.initialSlot,
442
454
  slotWindow: this.store.getHistoryLength(),
@@ -446,11 +458,11 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
446
458
  protected computeStatsForValidator(
447
459
  address: `0x${string}`,
448
460
  allHistory: ValidatorStatusHistory,
449
- fromSlot?: bigint,
450
- toSlot?: bigint,
461
+ fromSlot?: SlotNumber,
462
+ toSlot?: SlotNumber,
451
463
  ): ValidatorStats {
452
- let history = fromSlot ? allHistory.filter(h => h.slot >= fromSlot) : allHistory;
453
- history = toSlot ? history.filter(h => h.slot <= toSlot) : history;
464
+ let history = fromSlot ? allHistory.filter(h => BigInt(h.slot) >= fromSlot) : allHistory;
465
+ history = toSlot ? history.filter(h => BigInt(h.slot) <= toSlot) : history;
454
466
  const lastProposal = history.filter(h => h.status === 'block-proposed' || h.status === 'block-mined').at(-1);
455
467
  const lastAttestation = history.filter(h => h.status === 'attestation-sent').at(-1);
456
468
  return {
@@ -479,7 +491,7 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
479
491
  };
480
492
  }
481
493
 
482
- protected computeFromSlot(slot: bigint | undefined) {
494
+ protected computeFromSlot(slot: SlotNumber | undefined) {
483
495
  if (slot === undefined) {
484
496
  return undefined;
485
497
  }