@aztec/p2p 0.0.1-commit.6b113946b → 0.0.1-commit.6bd18f1aa

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 (133) hide show
  1. package/dest/client/factory.d.ts +1 -1
  2. package/dest/client/factory.d.ts.map +1 -1
  3. package/dest/client/factory.js +6 -5
  4. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +16 -6
  5. package/dest/index.d.ts +1 -2
  6. package/dest/index.d.ts.map +1 -1
  7. package/dest/index.js +0 -1
  8. package/dest/mem_pools/index.d.ts +1 -2
  9. package/dest/mem_pools/index.d.ts.map +1 -1
  10. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +1 -1
  11. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  12. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +5 -1
  13. package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts +9 -0
  14. package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts.map +1 -0
  15. package/dest/msg_validators/tx_validator/contract_instance_validator.js +48 -0
  16. package/dest/msg_validators/tx_validator/data_validator.d.ts +1 -1
  17. package/dest/msg_validators/tx_validator/data_validator.d.ts.map +1 -1
  18. package/dest/msg_validators/tx_validator/data_validator.js +35 -2
  19. package/dest/msg_validators/tx_validator/factory.d.ts +1 -1
  20. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  21. package/dest/msg_validators/tx_validator/factory.js +8 -2
  22. package/dest/msg_validators/tx_validator/phases_validator.js +1 -1
  23. package/dest/services/discv5/discV5_service.d.ts +1 -1
  24. package/dest/services/discv5/discV5_service.d.ts.map +1 -1
  25. package/dest/services/discv5/discV5_service.js +4 -2
  26. package/dest/services/libp2p/libp2p_service.d.ts +7 -2
  27. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  28. package/dest/services/libp2p/libp2p_service.js +115 -34
  29. package/dest/services/peer-manager/peer_manager.d.ts +6 -2
  30. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  31. package/dest/services/peer-manager/peer_manager.js +18 -6
  32. package/dest/services/peer-manager/peer_scoring.d.ts +5 -2
  33. package/dest/services/peer-manager/peer_scoring.d.ts.map +1 -1
  34. package/dest/services/peer-manager/peer_scoring.js +28 -10
  35. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +1 -1
  36. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  37. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +3 -0
  38. package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts +5 -4
  39. package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts.map +1 -1
  40. package/dest/services/reqresp/rate-limiter/rate_limiter.js +10 -8
  41. package/dest/services/service.d.ts +7 -1
  42. package/dest/services/service.d.ts.map +1 -1
  43. package/dest/services/tx_collection/file_store_tx_source.d.ts +5 -4
  44. package/dest/services/tx_collection/file_store_tx_source.d.ts.map +1 -1
  45. package/dest/services/tx_collection/file_store_tx_source.js +39 -29
  46. package/dest/services/tx_collection/tx_source.d.ts +6 -5
  47. package/dest/services/tx_collection/tx_source.d.ts.map +1 -1
  48. package/dest/services/tx_collection/tx_source.js +9 -7
  49. package/dest/test-helpers/mock-pubsub.d.ts +6 -1
  50. package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
  51. package/dest/test-helpers/mock-pubsub.js +9 -1
  52. package/dest/testbench/p2p_client_testbench_worker.d.ts +1 -1
  53. package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -1
  54. package/dest/testbench/p2p_client_testbench_worker.js +43 -15
  55. package/dest/testbench/worker_client_manager.d.ts +1 -1
  56. package/dest/testbench/worker_client_manager.d.ts.map +1 -1
  57. package/dest/testbench/worker_client_manager.js +0 -1
  58. package/dest/util.d.ts +8 -3
  59. package/dest/util.d.ts.map +1 -1
  60. package/dest/util.js +2 -9
  61. package/package.json +14 -14
  62. package/src/client/factory.ts +8 -3
  63. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +17 -6
  64. package/src/index.ts +0 -1
  65. package/src/mem_pools/index.ts +0 -3
  66. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +7 -1
  67. package/src/msg_validators/proposal_validator/README.md +1 -1
  68. package/src/msg_validators/tx_validator/contract_instance_validator.ts +56 -0
  69. package/src/msg_validators/tx_validator/data_validator.ts +42 -1
  70. package/src/msg_validators/tx_validator/factory.ts +7 -0
  71. package/src/msg_validators/tx_validator/phases_validator.ts +1 -1
  72. package/src/services/discv5/discV5_service.ts +4 -2
  73. package/src/services/libp2p/libp2p_service.ts +117 -43
  74. package/src/services/peer-manager/peer_manager.ts +21 -6
  75. package/src/services/peer-manager/peer_scoring.ts +21 -5
  76. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +3 -0
  77. package/src/services/reqresp/rate-limiter/rate_limiter.ts +13 -9
  78. package/src/services/service.ts +7 -0
  79. package/src/services/tx_collection/file_store_tx_source.ts +43 -31
  80. package/src/services/tx_collection/tx_source.ts +8 -7
  81. package/src/test-helpers/mock-pubsub.ts +9 -0
  82. package/src/testbench/p2p_client_testbench_worker.ts +43 -12
  83. package/src/testbench/worker_client_manager.ts +0 -1
  84. package/src/util.ts +8 -12
  85. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts +0 -125
  86. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts.map +0 -1
  87. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.js +0 -596
  88. package/dest/mem_pools/tx_pool/eviction/eviction_manager.d.ts +0 -32
  89. package/dest/mem_pools/tx_pool/eviction/eviction_manager.d.ts.map +0 -1
  90. package/dest/mem_pools/tx_pool/eviction/eviction_manager.js +0 -112
  91. package/dest/mem_pools/tx_pool/eviction/eviction_strategy.d.ts +0 -157
  92. package/dest/mem_pools/tx_pool/eviction/eviction_strategy.d.ts.map +0 -1
  93. package/dest/mem_pools/tx_pool/eviction/eviction_strategy.js +0 -52
  94. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts +0 -16
  95. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts.map +0 -1
  96. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.js +0 -123
  97. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.d.ts +0 -17
  98. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.d.ts.map +0 -1
  99. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.js +0 -84
  100. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.d.ts +0 -19
  101. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.d.ts.map +0 -1
  102. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.js +0 -78
  103. package/dest/mem_pools/tx_pool/eviction/low_priority_eviction_rule.d.ts +0 -26
  104. package/dest/mem_pools/tx_pool/eviction/low_priority_eviction_rule.d.ts.map +0 -1
  105. package/dest/mem_pools/tx_pool/eviction/low_priority_eviction_rule.js +0 -84
  106. package/dest/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.d.ts +0 -25
  107. package/dest/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.d.ts.map +0 -1
  108. package/dest/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.js +0 -57
  109. package/dest/mem_pools/tx_pool/index.d.ts +0 -3
  110. package/dest/mem_pools/tx_pool/index.d.ts.map +0 -1
  111. package/dest/mem_pools/tx_pool/index.js +0 -2
  112. package/dest/mem_pools/tx_pool/priority.d.ts +0 -12
  113. package/dest/mem_pools/tx_pool/priority.d.ts.map +0 -1
  114. package/dest/mem_pools/tx_pool/priority.js +0 -15
  115. package/dest/mem_pools/tx_pool/tx_pool.d.ts +0 -127
  116. package/dest/mem_pools/tx_pool/tx_pool.d.ts.map +0 -1
  117. package/dest/mem_pools/tx_pool/tx_pool.js +0 -3
  118. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts +0 -7
  119. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +0 -1
  120. package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +0 -402
  121. package/src/mem_pools/tx_pool/README.md +0 -270
  122. package/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +0 -746
  123. package/src/mem_pools/tx_pool/eviction/eviction_manager.ts +0 -132
  124. package/src/mem_pools/tx_pool/eviction/eviction_strategy.ts +0 -208
  125. package/src/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.ts +0 -163
  126. package/src/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.ts +0 -104
  127. package/src/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.ts +0 -93
  128. package/src/mem_pools/tx_pool/eviction/low_priority_eviction_rule.ts +0 -106
  129. package/src/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.ts +0 -75
  130. package/src/mem_pools/tx_pool/index.ts +0 -2
  131. package/src/mem_pools/tx_pool/priority.ts +0 -20
  132. package/src/mem_pools/tx_pool/tx_pool.ts +0 -141
  133. package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +0 -321
@@ -51,9 +51,10 @@ import { yamux } from '@chainsafe/libp2p-yamux';
51
51
  import { bootstrap } from '@libp2p/bootstrap';
52
52
  import { identify } from '@libp2p/identify';
53
53
  import { type Message, type MultiaddrConnection, type PeerId, TopicValidatorResult } from '@libp2p/interface';
54
- import type { ConnectionManager } from '@libp2p/interface-internal';
54
+ import type { AddressManager, ConnectionManager } from '@libp2p/interface-internal';
55
55
  import { mplex } from '@libp2p/mplex';
56
56
  import { tcp } from '@libp2p/tcp';
57
+ import { multiaddr } from '@multiformats/multiaddr';
57
58
  import { ENR } from '@nethermindeth/enr';
58
59
  import { createLibp2p } from 'libp2p';
59
60
 
@@ -130,7 +131,7 @@ type ValidationOutcome = { allPassed: true } | { allPassed: false; failure: Vali
130
131
  // REFACTOR: Unify with the type above
131
132
  type ReceivedMessageValidationResult<T, M = undefined> =
132
133
  | { obj: T; result: Exclude<TopicValidatorResult, TopicValidatorResult.Reject>; metadata?: M }
133
- | { obj?: T; result: TopicValidatorResult.Reject; metadata?: M };
134
+ | { obj?: T; result: TopicValidatorResult.Reject; metadata?: M; severity: PeerErrorSeverity };
134
135
 
135
136
  /**
136
137
  * Lib P2P implementation of the P2PService interface.
@@ -174,6 +175,10 @@ export class LibP2PService extends WithTracer implements P2PService {
174
175
  private checkpointReceivedCallback: P2PCheckpointReceivedCallback;
175
176
 
176
177
  private gossipSubEventHandler: (e: CustomEvent<GossipsubMessage>) => void;
178
+ private ipChangedHandler?: (ip: string) => void;
179
+
180
+ /** Discovered public IP address (set when queryForIp is enabled and no static IP was configured). */
181
+ private discoveredP2pIp?: string;
177
182
 
178
183
  private instrumentation: P2PInstrumentation;
179
184
 
@@ -442,8 +447,9 @@ export class LibP2PService extends WithTracer implements P2PService {
442
447
  topics: topicScoreParams,
443
448
  }),
444
449
  }) as (components: GossipSubComponents) => GossipSub,
445
- components: (components: { connectionManager: ConnectionManager }) => ({
450
+ components: (components: { connectionManager: ConnectionManager; addressManager: AddressManager }) => ({
446
451
  connectionManager: components.connectionManager,
452
+ addressManager: components.addressManager,
447
453
  }),
448
454
  },
449
455
  logger: createLibp2pComponentLogger(logger.module, logger.getBindings()),
@@ -502,10 +508,10 @@ export class LibP2PService extends WithTracer implements P2PService {
502
508
 
503
509
  // Get listen & announce addresses for logging
504
510
  const { p2pIp, p2pPort } = this.config;
505
- if (!p2pIp) {
511
+ if (!p2pIp && !this.config.queryForIp) {
506
512
  throw new Error('Announce address not provided.');
507
513
  }
508
- const announceTcpMultiaddr = convertToMultiaddr(p2pIp, p2pPort, 'tcp');
514
+ const announceTcpMultiaddr = p2pIp ? convertToMultiaddr(p2pIp, p2pPort, 'tcp') : undefined;
509
515
 
510
516
  // Create request response protocol handlers
511
517
  const txHandler = reqRespTxHandler(this.mempools);
@@ -559,6 +565,31 @@ export class LibP2PService extends WithTracer implements P2PService {
559
565
  if (!this.config.p2pDiscoveryDisabled) {
560
566
  await this.peerDiscoveryService.start();
561
567
  }
568
+
569
+ // When queryForIp is enabled and no static IP was configured, bridge discv5 IP discovery to libp2p.
570
+ // Discv5 discovers our public IP via peer WHOAREYOU exchanges (enrUpdate=true) and emits 'ip:changed'.
571
+ // We confirm the discovered address in the libp2p AddressManager so it appears in getMultiaddrs()
572
+ // and is pushed to all connected peers via the identify protocol.
573
+ if (this.config.queryForIp && !p2pIp) {
574
+ this.ipChangedHandler = (ip: string) => {
575
+ const addressManager = this.node.services.components.addressManager;
576
+ const newAddr = multiaddr(convertToMultiaddr(ip, this.config.p2pPort, 'tcp'));
577
+
578
+ // Remove old discovered IP if one exists
579
+ if (this.discoveredP2pIp) {
580
+ const oldAddr = multiaddr(convertToMultiaddr(this.discoveredP2pIp, this.config.p2pPort, 'tcp'));
581
+ addressManager.removeObservedAddr(oldAddr);
582
+ }
583
+
584
+ addressManager.addObservedAddr(newAddr);
585
+ addressManager.confirmObservedAddr(newAddr);
586
+ // Store discovered IP
587
+ this.discoveredP2pIp = ip;
588
+ this.logger.info('Public IP discovered via discv5', { ip });
589
+ };
590
+ this.peerDiscoveryService.on('ip:changed', this.ipChangedHandler);
591
+ }
592
+
562
593
  this.discoveryRunningPromise = new RunningPromise(
563
594
  async () => {
564
595
  await this.peerManager.heartbeat();
@@ -571,7 +602,7 @@ export class LibP2PService extends WithTracer implements P2PService {
571
602
  this.logger.info(`Started P2P service`, {
572
603
  listen: this.config.listenAddress,
573
604
  port: this.config.p2pPort,
574
- announce: announceTcpMultiaddr,
605
+ announce: announceTcpMultiaddr ?? 'pending (queryForIp=true)',
575
606
  peerId: this.node.peerId.toString(),
576
607
  });
577
608
  }
@@ -584,6 +615,12 @@ export class LibP2PService extends WithTracer implements P2PService {
584
615
  // Remove gossip sub listener
585
616
  this.node.services.pubsub.removeEventListener(GossipSubEvent.MESSAGE, this.gossipSubEventHandler);
586
617
 
618
+ // Remove ip:changed listener if registered
619
+ if (this.ipChangedHandler) {
620
+ this.peerDiscoveryService.off('ip:changed', this.ipChangedHandler);
621
+ this.ipChangedHandler = undefined;
622
+ }
623
+
587
624
  // Stop peer manager
588
625
  this.logger.debug('Stopping peer manager...');
589
626
  await this.peerManager.stop();
@@ -882,30 +919,56 @@ export class LibP2PService extends WithTracer implements P2PService {
882
919
  source: PeerId,
883
920
  topicType: TopicType,
884
921
  ): Promise<ReceivedMessageValidationResult<T, M>> {
885
- let resultAndObj: ReceivedMessageValidationResult<T, M> = { result: TopicValidatorResult.Reject };
922
+ // Default to reject result with a penalty if validation function throws an error
923
+ let resultAndObj: ReceivedMessageValidationResult<T, M> = {
924
+ result: TopicValidatorResult.Reject,
925
+ severity: PeerErrorSeverity.MidToleranceError,
926
+ };
886
927
  const timer = new Timer();
887
928
  try {
888
929
  resultAndObj = await validationFunc();
889
930
  } catch (err) {
890
- this.peerManager.penalizePeer(source, PeerErrorSeverity.LowToleranceError);
891
- this.logger.error(`Error deserializing and validating gossipsub message`, err, {
892
- msgId,
893
- source: source.toString(),
894
- topicType,
895
- });
931
+ this.logger.error(`Error validating gossipsub message`, err, { msgId, source: source.toString(), topicType });
896
932
  }
897
933
 
898
934
  if (resultAndObj.result === TopicValidatorResult.Accept) {
935
+ this.logger.debug(`Message ${topicType} accepted by validator`, { msgId, source: source.toString(), topicType });
899
936
  this.instrumentation.recordMessageValidation(topicType, timer);
937
+ } else if (resultAndObj.result === TopicValidatorResult.Reject) {
938
+ this.logger.warn(`Message ${topicType} rejected by validator with severity ${resultAndObj.severity}`, {
939
+ msgId,
940
+ source: source.toString(),
941
+ topicType,
942
+ severity: resultAndObj.severity,
943
+ });
944
+ this.peerManager.penalizePeer(source, resultAndObj.severity);
945
+ } else {
946
+ this.logger.trace(`Message ${topicType} ignored by validator`, { msgId, source: source.toString(), topicType });
900
947
  }
901
948
 
902
949
  this.node.services.pubsub.reportMessageValidationResult(msgId, source.toString(), resultAndObj.result);
903
950
  return resultAndObj;
904
951
  }
905
952
 
953
+ private tryDeserialize<T>(deserializeFunc: () => T, msgId: string, source: PeerId): T | undefined {
954
+ try {
955
+ return deserializeFunc();
956
+ } catch (err) {
957
+ this.logger.warn(`Failed to deserialize gossipsub message from buffer`, {
958
+ err,
959
+ msgId,
960
+ source: source.toString(),
961
+ });
962
+ return undefined;
963
+ }
964
+ }
965
+
906
966
  protected async handleGossipedTx(payloadData: Buffer, msgId: string, source: PeerId) {
907
967
  const validationFunc: () => Promise<ReceivedMessageValidationResult<Tx>> = async () => {
908
- const tx = Tx.fromBuffer(payloadData);
968
+ const tx = this.tryDeserialize(() => Tx.fromBuffer(payloadData), msgId, source);
969
+ if (!tx) {
970
+ return { result: TopicValidatorResult.Reject, severity: PeerErrorSeverity.LowToleranceError };
971
+ }
909
972
 
910
973
  const currentBlockNumber = await this.archiver.getBlockNumber();
911
974
  const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
@@ -930,8 +993,7 @@ export class LibP2PService extends WithTracer implements P2PService {
930
993
  severity,
931
994
  source: source.toString(),
932
995
  });
933
- this.peerManager.penalizePeer(source, severity);
934
- return { result: TopicValidatorResult.Reject };
996
+ return { result: TopicValidatorResult.Reject, severity };
935
997
  }
936
998
 
937
999
  // Pool pre-check: see if the pool would accept this tx before doing expensive proof verification
@@ -953,8 +1015,7 @@ export class LibP2PService extends WithTracer implements P2PService {
953
1015
  severity,
954
1016
  source: source.toString(),
955
1017
  });
956
- this.peerManager.penalizePeer(source, severity);
957
- return { result: TopicValidatorResult.Reject };
1018
+ return { result: TopicValidatorResult.Reject, severity };
958
1019
  }
959
1020
 
960
1021
  // Pool add: persist the tx
@@ -979,8 +1040,7 @@ export class LibP2PService extends WithTracer implements P2PService {
979
1040
  source: source.toString(),
980
1041
  txHash: txHash.toString(),
981
1042
  });
982
- this.peerManager.penalizePeer(source, PeerErrorSeverity.HighToleranceError);
983
- return { result: TopicValidatorResult.Reject };
1043
+ return { result: TopicValidatorResult.Reject, severity: PeerErrorSeverity.HighToleranceError };
984
1044
  }
985
1045
  };
986
1046
 
@@ -1010,7 +1070,16 @@ export class LibP2PService extends WithTracer implements P2PService {
1010
1070
  source: PeerId,
1011
1071
  ): Promise<void> {
1012
1072
  const { result, obj: attestation } = await this.validateReceivedMessage<CheckpointAttestation>(
1013
- () => this.validateAndStoreCheckpointAttestation(source, CheckpointAttestation.fromBuffer(payloadData)),
1073
+ () => {
1074
+ const attestation = this.tryDeserialize(() => CheckpointAttestation.fromBuffer(payloadData), msgId, source);
1075
+ if (!attestation) {
1076
+ return Promise.resolve({
1077
+ result: TopicValidatorResult.Reject,
1078
+ severity: PeerErrorSeverity.LowToleranceError,
1079
+ });
1080
+ }
1081
+ return this.validateAndStoreCheckpointAttestation(source, attestation);
1082
+ },
1014
1083
  msgId,
1015
1084
  source,
1016
1085
  TopicType.checkpoint_attestation,
@@ -1043,8 +1112,7 @@ export class LibP2PService extends WithTracer implements P2PService {
1043
1112
 
1044
1113
  if (validationResult.result === 'reject') {
1045
1114
  this.logger.warn(`Penalizing peer ${peerId} for checkpoint attestation validation failure`);
1046
- this.peerManager.penalizePeer(peerId, validationResult.severity);
1047
- return { result: TopicValidatorResult.Reject };
1115
+ return { result: TopicValidatorResult.Reject, severity: validationResult.severity };
1048
1116
  }
1049
1117
 
1050
1118
  if (validationResult.result === 'ignore') {
@@ -1070,16 +1138,16 @@ export class LibP2PService extends WithTracer implements P2PService {
1070
1138
  return { result: TopicValidatorResult.Ignore, obj: attestation };
1071
1139
  }
1072
1140
 
1073
- // Could not add (cap reached for signer), no need to re-broadcast
1141
+ // Could not add (cap reached for signer), penalize and do not re-broadcast
1074
1142
  if (!added) {
1075
- this.logger.warn(`Dropping checkpoint attestation due to cap`, {
1143
+ this.logger.warn(`Rejecting checkpoint attestation due to cap`, {
1076
1144
  slot: slot.toString(),
1077
1145
  archive: attestation.archive.toString(),
1078
1146
  source: peerId.toString(),
1079
1147
  attester: attestation.getSender()?.toString(),
1080
1148
  count,
1081
1149
  });
1082
- return { result: TopicValidatorResult.Ignore, obj: attestation };
1150
+ return { result: TopicValidatorResult.Reject, severity: PeerErrorSeverity.HighToleranceError };
1083
1151
  }
1084
1152
 
1085
1153
  // Check if this is a duplicate attestation (signer attested to a different proposal at the same slot)
@@ -1134,8 +1202,7 @@ export class LibP2PService extends WithTracer implements P2PService {
1134
1202
 
1135
1203
  if (validationResult.result === 'reject') {
1136
1204
  this.logger.warn(`Penalizing peer ${peerId} for block proposal validation failure`);
1137
- this.peerManager.penalizePeer(peerId, validationResult.severity);
1138
- return { result: TopicValidatorResult.Reject };
1205
+ return { result: TopicValidatorResult.Reject, severity: validationResult.severity };
1139
1206
  }
1140
1207
 
1141
1208
  if (validationResult.result === 'ignore') {
@@ -1159,7 +1226,6 @@ export class LibP2PService extends WithTracer implements P2PService {
1159
1226
 
1160
1227
  // Too many blocks received for this slot and index, penalize peer and do not re-broadcast
1161
1228
  if (!added) {
1162
- this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError);
1163
1229
  this.logger.warn(`Penalizing peer for block proposal exceeding per-position cap`, {
1164
1230
  ...block.toBlockInfo(),
1165
1231
  indexWithinCheckpoint: block.indexWithinCheckpoint,
@@ -1167,7 +1233,11 @@ export class LibP2PService extends WithTracer implements P2PService {
1167
1233
  proposer: block.getSender()?.toString(),
1168
1234
  source: peerId.toString(),
1169
1235
  });
1170
- return { result: TopicValidatorResult.Reject, metadata: { isEquivocated } };
1236
+ return {
1237
+ result: TopicValidatorResult.Reject,
1238
+ metadata: { isEquivocated },
1239
+ severity: PeerErrorSeverity.HighToleranceError,
1240
+ };
1171
1241
  }
1172
1242
 
1173
1243
  // If this was a duplicate proposal, do not process it, but do invoke the duplicate callback,
@@ -1260,8 +1330,7 @@ export class LibP2PService extends WithTracer implements P2PService {
1260
1330
 
1261
1331
  if (validationResult.result === 'reject') {
1262
1332
  this.logger.warn(`Penalizing peer ${peerId} for checkpoint proposal validation failure`);
1263
- this.peerManager.penalizePeer(peerId, validationResult.severity);
1264
- return { result: TopicValidatorResult.Reject };
1333
+ return { result: TopicValidatorResult.Reject, severity: validationResult.severity };
1265
1334
  }
1266
1335
 
1267
1336
  if (validationResult.result === 'ignore') {
@@ -1276,20 +1345,21 @@ export class LibP2PService extends WithTracer implements P2PService {
1276
1345
  [Attributes.SLOT_NUMBER]: checkpoint.slotNumber.toString(),
1277
1346
  [Attributes.P2P_ID]: peerId.toString(),
1278
1347
  });
1279
- const {
1280
- result,
1281
- obj,
1282
- metadata: { isEquivocated } = {},
1283
- } = await this.validateAndStoreBlockProposal(peerId, blockProposal);
1284
- if (result === TopicValidatorResult.Reject || !obj || isEquivocated) {
1348
+ const blockProposalResult = await this.validateAndStoreBlockProposal(peerId, blockProposal);
1349
+ const { obj, metadata: { isEquivocated } = {} } = blockProposalResult;
1350
+ if (blockProposalResult.result === TopicValidatorResult.Reject || !obj || isEquivocated) {
1285
1351
  this.logger.debug(`Rejecting checkpoint due to invalid last block proposal`, {
1286
1352
  [Attributes.SLOT_NUMBER]: checkpoint.slotNumber.toString(),
1287
1353
  [Attributes.P2P_ID]: peerId.toString(),
1288
1354
  isEquivocated,
1289
- result,
1355
+ result: blockProposalResult.result,
1290
1356
  });
1291
- return { result: TopicValidatorResult.Reject };
1292
- } else if (result === TopicValidatorResult.Accept && obj && !isEquivocated) {
1357
+ return {
1358
+ result: TopicValidatorResult.Reject,
1359
+ severity:
1360
+ 'severity' in blockProposalResult ? blockProposalResult.severity : PeerErrorSeverity.MidToleranceError,
1361
+ };
1362
+ } else if (blockProposalResult.result === TopicValidatorResult.Accept && obj && !isEquivocated) {
1293
1363
  processBlock = true;
1294
1364
  }
1295
1365
  }
@@ -1316,13 +1386,17 @@ export class LibP2PService extends WithTracer implements P2PService {
1316
1386
  // Too many checkpoint proposals received for this slot, penalize peer and do not re-broadcast
1317
1387
  // Note: We still return the checkpoint obj so the lastBlock can be processed if valid
1318
1388
  if (!added) {
1319
- this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError);
1320
1389
  this.logger.warn(`Penalizing peer for checkpoint proposal exceeding per-slot cap`, {
1321
1390
  ...checkpoint.toCheckpointInfo(),
1322
1391
  count,
1323
1392
  source: peerId.toString(),
1324
1393
  });
1325
- return { result: TopicValidatorResult.Reject, obj: checkpoint, metadata: { isEquivocated, processBlock } };
1394
+ return {
1395
+ result: TopicValidatorResult.Reject,
1396
+ obj: checkpoint,
1397
+ metadata: { isEquivocated, processBlock },
1398
+ severity: PeerErrorSeverity.HighToleranceError,
1399
+ };
1326
1400
  }
1327
1401
 
1328
1402
  // If this was a duplicate proposal, do not process it, but do invoke the duplicate callback,
@@ -226,20 +226,30 @@ export class PeerManager implements PeerManagerInterface {
226
226
  }
227
227
 
228
228
  /**
229
- * Cleans up expired timeouts.
229
+ * Cleans up expired timeouts and stale failed-auth-handshake entries.
230
230
  *
231
231
  * When peers fail to dial after a number of retries, they are temporarily timed out.
232
232
  * This function removes any peers that have been in the timed out state for too long.
233
233
  * To give them a chance to reconnect.
234
+ *
235
+ * Also evicts entries from the failed-auth-handshake map whose expiry window has passed.
236
+ * Without this, peers that probe once and never reconnect would leave their entries in the
237
+ * map forever, causing an unbounded memory leak.
234
238
  */
235
239
  private cleanupExpiredTimeouts() {
236
- // Clean up expired timeouts
237
240
  const now = this.dateProvider.now();
241
+
238
242
  for (const [peerId, timedOutPeer] of this.timedOutPeers.entries()) {
239
243
  if (now >= timedOutPeer.timeoutUntilMs) {
240
244
  this.timedOutPeers.delete(peerId);
241
245
  }
242
246
  }
247
+
248
+ for (const [id, entry] of this.failedAuthHandshakes.entries()) {
249
+ if (now - entry.lastFailureTimestamp > FAILED_AUTH_HANDSHAKE_EXPIRY_MS) {
250
+ this.failedAuthHandshakes.delete(id);
251
+ }
252
+ }
243
253
  }
244
254
 
245
255
  /**
@@ -303,15 +313,20 @@ export class PeerManager implements PeerManagerInterface {
303
313
  */
304
314
  private handleDisconnectedPeerEvent(e: CustomEvent<PeerId>) {
305
315
  const peerId = e.detail;
316
+ const peerIdStr = peerId.toString();
306
317
  this.metrics.peerDisconnected(peerId);
307
- this.logger.verbose(`Disconnected from peer ${peerId.toString()}`);
308
- const validatorAddress = this.authenticatedPeerIdToValidatorAddress.get(peerId.toString());
318
+ this.logger.verbose(`Disconnected from peer ${peerIdStr}`);
319
+ const validatorAddress = this.authenticatedPeerIdToValidatorAddress.get(peerIdStr);
309
320
  if (validatorAddress !== undefined) {
310
321
  this.logger.info(
311
- `Removing authentication for validator ${validatorAddress} at peer id ${peerId.toString()} due to disconnection`,
322
+ `Removing authentication for validator ${validatorAddress} at peer id ${peerIdStr} due to disconnection`,
312
323
  );
313
324
  this.authenticatedValidatorAddressToPeerId.delete(validatorAddress.toString());
314
- this.authenticatedPeerIdToValidatorAddress.delete(peerId.toString());
325
+ this.authenticatedPeerIdToValidatorAddress.delete(peerIdStr);
326
+ }
327
+
328
+ if (this.peerScoring.getScoreState(peerIdStr) === PeerScoreState.Healthy) {
329
+ this.peerScoring.removePeer(peerIdStr);
315
330
  }
316
331
  }
317
332
 
@@ -1,5 +1,6 @@
1
1
  import { median } from '@aztec/foundation/collection';
2
2
  import { createLogger } from '@aztec/foundation/log';
3
+ import { DateProvider } from '@aztec/foundation/timer';
3
4
  import { PeerErrorSeverity } from '@aztec/stdlib/p2p';
4
5
  import {
5
6
  Attributes,
@@ -54,6 +55,7 @@ export enum PeerScoreState {
54
55
  // TODO: move into config / constants
55
56
  const MIN_SCORE_BEFORE_BAN = -100;
56
57
  const MIN_SCORE_BEFORE_DISCONNECT = -50;
58
+ const SCORE_CLEANUP_THRESHOLD = 0.1;
57
59
 
58
60
  export class PeerScoring {
59
61
  private logger = createLogger('p2p:peer-scoring');
@@ -65,7 +67,11 @@ export class PeerScoring {
65
67
 
66
68
  private peerStateCounter: UpDownCounter;
67
69
 
68
- constructor(config: P2PConfig, telemetry: TelemetryClient = getTelemetryClient()) {
70
+ constructor(
71
+ config: P2PConfig,
72
+ telemetry: TelemetryClient = getTelemetryClient(),
73
+ private readonly dateProvider: DateProvider = new DateProvider(),
74
+ ) {
69
75
  const orderedValues = config.peerPenaltyValues?.sort((a, b) => a - b);
70
76
  this.peerPenalties = {
71
77
  [PeerErrorSeverity.HighToleranceError]:
@@ -92,7 +98,7 @@ export class PeerScoring {
92
98
  }
93
99
 
94
100
  updateScore(peerId: string, scoreDelta: number): number {
95
- const currentTime = Date.now();
101
+ const currentTime = this.dateProvider.now();
96
102
  const lastUpdate = this.lastUpdateTime.get(peerId) || currentTime;
97
103
  const timePassed = currentTime - lastUpdate;
98
104
  const decayPeriods = Math.floor(timePassed / this.decayInterval);
@@ -111,19 +117,29 @@ export class PeerScoring {
111
117
  }
112
118
 
113
119
  decayAllScores(): void {
114
- const currentTime = Date.now();
120
+ const currentTime = this.dateProvider.now();
115
121
  for (const [peerId, lastUpdate] of this.lastUpdateTime.entries()) {
116
122
  const timePassed = currentTime - lastUpdate;
117
123
  const decayPeriods = Math.floor(timePassed / this.decayInterval);
118
124
  if (decayPeriods > 0) {
119
125
  let score = this.scores.get(peerId) || 0;
120
126
  score *= Math.pow(this.decayFactor, decayPeriods);
121
- this.scores.set(peerId, score);
122
- this.lastUpdateTime.set(peerId, currentTime);
127
+ if (Math.abs(score) < SCORE_CLEANUP_THRESHOLD) {
128
+ this.scores.delete(peerId);
129
+ this.lastUpdateTime.delete(peerId);
130
+ } else {
131
+ this.scores.set(peerId, score);
132
+ this.lastUpdateTime.set(peerId, currentTime);
133
+ }
123
134
  }
124
135
  }
125
136
  }
126
137
 
138
+ removePeer(peerId: string): void {
139
+ this.scores.delete(peerId);
140
+ this.lastUpdateTime.delete(peerId);
141
+ }
142
+
127
143
  getScore(peerId: string): number {
128
144
  return this.scores.get(peerId) || 0;
129
145
  }
@@ -514,6 +514,9 @@ export class BatchTxRequester {
514
514
  });
515
515
 
516
516
  if (hasInvalidTx) {
517
+ this.logger.warn(`Penalizing peer ${peerId.toString()} for sending invalid transactions in batch response`, {
518
+ peerId,
519
+ });
517
520
  this.peers.penalisePeer(peerId, PeerErrorSeverity.LowToleranceError);
518
521
  } else {
519
522
  // If we have received successful response from the peer, they have "redeemed" themselves and not considered bad anymore
@@ -97,9 +97,10 @@ export function prettyPrintRateLimitStatus(status: RateLimitStatus) {
97
97
  * 2. Individual rate limits for each peer.
98
98
  *
99
99
  * How it works:
100
- * - When a request comes in, it first checks against the global rate limit.
101
- * - If the global limit allows, it then checks against the specific peer's rate limit.
102
- * - The request is only allowed if both the global and peer-specific limits allow it.
100
+ * - When a request comes in, it first checks against the peer's individual rate limit.
101
+ * - If the peer limit allows, it then checks against the global rate limit.
102
+ * - The request is only allowed if both the peer-specific and global limits allow it.
103
+ * - Checking peer limit first ensures a rate-limited peer cannot exhaust the global quota.
103
104
  * - It automatically creates and manages rate limiters for new peers as they make requests.
104
105
  * - It periodically cleans up rate limiters for inactive peers to conserve memory.
105
106
  *
@@ -119,10 +120,6 @@ export class SubProtocolRateLimiter {
119
120
  }
120
121
 
121
122
  allow(peerId: PeerId): RateLimitStatus {
122
- if (!this.globalLimiter.allow()) {
123
- return RateLimitStatus.DeniedGlobal;
124
- }
125
-
126
123
  const peerIdStr = peerId.toString();
127
124
  let peerLimiter: PeerRateLimiter | undefined = this.peerLimiters.get(peerIdStr);
128
125
  if (!peerLimiter) {
@@ -135,10 +132,17 @@ export class SubProtocolRateLimiter {
135
132
  } else {
136
133
  peerLimiter.lastAccess = Date.now();
137
134
  }
138
- const peerLimitAllowed = peerLimiter.limiter.allow();
139
- if (!peerLimitAllowed) {
135
+
136
+ // Check peer limit first: a rate-limited peer must not consume global quota,
137
+ // otherwise one spamming peer can starve all others by exhausting the global bucket.
138
+ if (!peerLimiter.limiter.allow()) {
140
139
  return RateLimitStatus.DeniedPeer;
141
140
  }
141
+
142
+ if (!this.globalLimiter.allow()) {
143
+ return RateLimitStatus.DeniedGlobal;
144
+ }
145
+
142
146
  return RateLimitStatus.Allowed;
143
147
  }
144
148
 
@@ -196,6 +196,13 @@ export interface PeerDiscoveryService extends EventEmitter {
196
196
  on(event: 'peer:discovered', listener: (enr: ENR) => void): this;
197
197
  emit(event: 'peer:discovered', enr: ENR): boolean;
198
198
 
199
+ /**
200
+ * Event emitted when our public IP is discovered or changes via discv5 peer interactions.
201
+ * Only emitted when enrUpdate is enabled (i.e. queryForIp=true and no static p2pIp).
202
+ */
203
+ on(event: 'ip:changed', listener: (ip: string) => void): this;
204
+ emit(event: 'ip:changed', ip: string): boolean;
205
+
199
206
  getStatus(): PeerDiscoveryState;
200
207
 
201
208
  getEnr(): ENR | undefined;
@@ -1,7 +1,8 @@
1
+ import { partitionAsync } from '@aztec/foundation/collection';
1
2
  import { type Logger, createLogger } from '@aztec/foundation/log';
2
3
  import { Timer } from '@aztec/foundation/timer';
3
4
  import { type ReadOnlyFileStore, createReadOnlyFileStore } from '@aztec/stdlib/file-store';
4
- import { Tx, type TxHash } from '@aztec/stdlib/tx';
5
+ import { Tx, type TxHash, type TxValidator } from '@aztec/stdlib/tx';
5
6
  import {
6
7
  type Histogram,
7
8
  Metrics,
@@ -23,6 +24,7 @@ export class FileStoreTxSource implements TxSource {
23
24
  private readonly fileStore: ReadOnlyFileStore,
24
25
  private readonly baseUrl: string,
25
26
  private readonly basePath: string,
27
+ private readonly txValidator: TxValidator,
26
28
  private readonly log: Logger,
27
29
  telemetry: TelemetryClient,
28
30
  ) {
@@ -44,6 +46,7 @@ export class FileStoreTxSource implements TxSource {
44
46
  public static async create(
45
47
  url: string,
46
48
  basePath: string,
49
+ txValidator: TxValidator,
47
50
  log: Logger = createLogger('p2p:file_store_tx_source'),
48
51
  telemetry: TelemetryClient = getTelemetryClient(),
49
52
  ): Promise<FileStoreTxSource | undefined> {
@@ -53,7 +56,7 @@ export class FileStoreTxSource implements TxSource {
53
56
  log.warn(`Failed to create file store for URL: ${url}`);
54
57
  return undefined;
55
58
  }
56
- return new FileStoreTxSource(fileStore, url, basePath, log, telemetry);
59
+ return new FileStoreTxSource(fileStore, url, basePath, txValidator, log, telemetry);
57
60
  } catch (err) {
58
61
  log.warn(`Error creating file store for URL: ${url}`, { error: err });
59
62
  return undefined;
@@ -65,35 +68,41 @@ export class FileStoreTxSource implements TxSource {
65
68
  }
66
69
 
67
70
  public async getTxsByHash(txHashes: TxHash[]): Promise<TxSourceCollectionResult> {
68
- const invalidTxHashes: string[] = [];
71
+ const results = await Promise.all(
72
+ txHashes.map(async txHash => {
73
+ const path = `${this.basePath}/txs/${txHash.toString()}.bin`;
74
+ const timer = new Timer();
75
+ try {
76
+ const buffer = await this.fileStore.read(path);
77
+ const tx = Tx.fromBuffer(buffer);
78
+ return { tx, downloadDuration: timer.ms(), downloadSize: buffer.length };
79
+ } catch {
80
+ this.downloadsFailed.add(1);
81
+ return undefined;
82
+ }
83
+ }),
84
+ );
85
+
86
+ const txs = results.filter(tx => tx !== undefined);
87
+ const [validTxs, invalidTxs] = await partitionAsync(
88
+ txs,
89
+ async ({ tx, downloadDuration, downloadSize }): Promise<boolean> => {
90
+ const valid = await this.txValidator.validateTx(tx);
91
+ if (valid.result === 'valid') {
92
+ this.downloadsSuccess.add(1);
93
+ this.downloadDuration.record(Math.ceil(downloadDuration));
94
+ this.downloadSize.record(downloadSize);
95
+ return true;
96
+ } else {
97
+ this.downloadsFailed.add(1);
98
+ return false;
99
+ }
100
+ },
101
+ );
102
+
69
103
  return {
70
- validTxs: (
71
- await Promise.all(
72
- txHashes.map(async txHash => {
73
- const path = `${this.basePath}/txs/${txHash.toString()}.bin`;
74
- const timer = new Timer();
75
- try {
76
- const buffer = await this.fileStore.read(path);
77
- const tx = Tx.fromBuffer(buffer);
78
- if ((await tx.validateTxHash()) && txHash.equals(tx.txHash)) {
79
- this.downloadsSuccess.add(1);
80
- this.downloadDuration.record(Math.ceil(timer.ms()));
81
- this.downloadSize.record(buffer.length);
82
- return tx;
83
- } else {
84
- invalidTxHashes.push(tx.txHash.toString());
85
- this.downloadsFailed.add(1);
86
- return undefined;
87
- }
88
- } catch {
89
- // Tx not found or error reading - return undefined
90
- this.downloadsFailed.add(1);
91
- return undefined;
92
- }
93
- }),
94
- )
95
- ).filter(tx => tx !== undefined),
96
- invalidTxHashes: invalidTxHashes,
104
+ validTxs: validTxs.map(({ tx }) => tx),
105
+ invalidTxHashes: invalidTxs.map(({ tx }) => tx.getTxHash().toString()),
97
106
  };
98
107
  }
99
108
  }
@@ -109,9 +118,12 @@ export class FileStoreTxSource implements TxSource {
109
118
  export async function createFileStoreTxSources(
110
119
  urls: string[],
111
120
  basePath: string,
121
+ txValidator: TxValidator,
112
122
  log: Logger = createLogger('p2p:file_store_tx_source'),
113
123
  telemetry: TelemetryClient = getTelemetryClient(),
114
124
  ): Promise<FileStoreTxSource[]> {
115
- const sources = await Promise.all(urls.map(url => FileStoreTxSource.create(url, basePath, log, telemetry)));
125
+ const sources = await Promise.all(
126
+ urls.map(url => FileStoreTxSource.create(url, basePath, txValidator, log, telemetry)),
127
+ );
116
128
  return sources.filter((s): s is FileStoreTxSource => s !== undefined);
117
129
  }