@aztec/p2p 1.2.1 → 2.0.0-nightly.20250813

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 (221) hide show
  1. package/dest/client/factory.d.ts +5 -1
  2. package/dest/client/factory.d.ts.map +1 -1
  3. package/dest/client/factory.js +29 -12
  4. package/dest/client/interface.d.ts +8 -13
  5. package/dest/client/interface.d.ts.map +1 -1
  6. package/dest/client/p2p_client.d.ts +18 -22
  7. package/dest/client/p2p_client.d.ts.map +1 -1
  8. package/dest/client/p2p_client.js +86 -83
  9. package/dest/config.d.ts +30 -1
  10. package/dest/config.d.ts.map +1 -1
  11. package/dest/config.js +34 -1
  12. package/dest/index.d.ts +1 -0
  13. package/dest/index.d.ts.map +1 -1
  14. package/dest/index.js +1 -0
  15. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +13 -1
  16. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  17. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
  18. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +117 -10
  19. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +4 -1
  20. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +1 -1
  21. package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +22 -1
  22. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +4 -1
  23. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +1 -1
  24. package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +21 -1
  25. package/dest/mem_pools/attestation_pool/mocks.d.ts +1 -2
  26. package/dest/mem_pools/attestation_pool/mocks.d.ts.map +1 -1
  27. package/dest/mem_pools/attestation_pool/mocks.js +2 -10
  28. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts +8 -3
  29. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts.map +1 -1
  30. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.js +64 -37
  31. package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts +8 -3
  32. package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts.map +1 -1
  33. package/dest/mem_pools/tx_pool/memory_tx_pool.js +18 -10
  34. package/dest/mem_pools/tx_pool/tx_pool.d.ts +11 -2
  35. package/dest/mem_pools/tx_pool/tx_pool.d.ts.map +1 -1
  36. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +1 -1
  37. package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +73 -44
  38. package/dest/msg_validators/attestation_validator/attestation_validator.js +1 -1
  39. package/dest/msg_validators/tx_validator/block_header_validator.d.ts.map +1 -1
  40. package/dest/msg_validators/tx_validator/block_header_validator.js +2 -2
  41. package/dest/msg_validators/tx_validator/data_validator.d.ts.map +1 -1
  42. package/dest/msg_validators/tx_validator/data_validator.js +35 -59
  43. package/dest/msg_validators/tx_validator/double_spend_validator.js +2 -2
  44. package/dest/msg_validators/tx_validator/gas_validator.js +4 -4
  45. package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -1
  46. package/dest/msg_validators/tx_validator/metadata_validator.js +21 -21
  47. package/dest/msg_validators/tx_validator/phases_validator.js +3 -3
  48. package/dest/msg_validators/tx_validator/tx_proof_validator.js +3 -3
  49. package/dest/services/discv5/discV5_service.d.ts.map +1 -1
  50. package/dest/services/discv5/discV5_service.js +4 -1
  51. package/dest/services/dummy_service.d.ts +13 -3
  52. package/dest/services/dummy_service.d.ts.map +1 -1
  53. package/dest/services/dummy_service.js +26 -3
  54. package/dest/services/index.d.ts +3 -1
  55. package/dest/services/index.d.ts.map +1 -1
  56. package/dest/services/index.js +3 -1
  57. package/dest/services/libp2p/libp2p_service.d.ts +13 -20
  58. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  59. package/dest/services/libp2p/libp2p_service.js +123 -46
  60. package/dest/services/peer-manager/interface.d.ts +8 -1
  61. package/dest/services/peer-manager/interface.d.ts.map +1 -1
  62. package/dest/services/peer-manager/peer_manager.d.ts +70 -3
  63. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  64. package/dest/services/peer-manager/peer_manager.js +369 -39
  65. package/dest/services/reqresp/config.d.ts +3 -3
  66. package/dest/services/reqresp/config.d.ts.map +1 -1
  67. package/dest/services/reqresp/config.js +3 -3
  68. package/dest/services/reqresp/connection-sampler/connection_sampler.d.ts +3 -4
  69. package/dest/services/reqresp/connection-sampler/connection_sampler.d.ts.map +1 -1
  70. package/dest/services/reqresp/connection-sampler/connection_sampler.js +35 -52
  71. package/dest/services/reqresp/index.d.ts +2 -1
  72. package/dest/services/reqresp/index.d.ts.map +1 -1
  73. package/dest/services/reqresp/index.js +2 -1
  74. package/dest/services/reqresp/interface.d.ts +61 -10
  75. package/dest/services/reqresp/interface.d.ts.map +1 -1
  76. package/dest/services/reqresp/interface.js +41 -6
  77. package/dest/services/reqresp/protocols/auth.d.ts +43 -0
  78. package/dest/services/reqresp/protocols/auth.d.ts.map +1 -0
  79. package/dest/services/reqresp/protocols/auth.js +71 -0
  80. package/dest/services/reqresp/protocols/block.d.ts +5 -0
  81. package/dest/services/reqresp/protocols/block.d.ts.map +1 -1
  82. package/dest/services/reqresp/protocols/block.js +28 -5
  83. package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts +30 -0
  84. package/dest/services/reqresp/protocols/block_txs/bitvector.d.ts.map +1 -0
  85. package/dest/services/reqresp/protocols/block_txs/bitvector.js +75 -0
  86. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts +11 -0
  87. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts.map +1 -0
  88. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.js +39 -0
  89. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts +49 -0
  90. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts.map +1 -0
  91. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.js +75 -0
  92. package/dest/services/reqresp/protocols/block_txs/index.d.ts +4 -0
  93. package/dest/services/reqresp/protocols/block_txs/index.d.ts.map +1 -0
  94. package/dest/services/reqresp/protocols/block_txs/index.js +3 -0
  95. package/dest/services/reqresp/protocols/goodbye.js +3 -5
  96. package/dest/services/reqresp/protocols/index.d.ts +2 -0
  97. package/dest/services/reqresp/protocols/index.d.ts.map +1 -1
  98. package/dest/services/reqresp/protocols/index.js +2 -0
  99. package/dest/services/reqresp/protocols/status.d.ts +2 -0
  100. package/dest/services/reqresp/protocols/status.d.ts.map +1 -1
  101. package/dest/services/reqresp/protocols/status.js +7 -0
  102. package/dest/services/reqresp/protocols/tx.d.ts +12 -1
  103. package/dest/services/reqresp/protocols/tx.d.ts.map +1 -1
  104. package/dest/services/reqresp/protocols/tx.js +34 -6
  105. package/dest/services/reqresp/rate-limiter/rate_limits.d.ts.map +1 -1
  106. package/dest/services/reqresp/rate-limiter/rate_limits.js +20 -0
  107. package/dest/services/reqresp/reqresp.d.ts +37 -39
  108. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  109. package/dest/services/reqresp/reqresp.js +220 -220
  110. package/dest/services/reqresp/status.d.ts +8 -3
  111. package/dest/services/reqresp/status.d.ts.map +1 -1
  112. package/dest/services/reqresp/status.js +6 -2
  113. package/dest/services/service.d.ts +10 -10
  114. package/dest/services/service.d.ts.map +1 -1
  115. package/dest/services/tx_collection/config.d.ts +25 -0
  116. package/dest/services/tx_collection/config.d.ts.map +1 -0
  117. package/dest/services/tx_collection/config.js +58 -0
  118. package/dest/services/tx_collection/fast_tx_collection.d.ts +56 -0
  119. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -0
  120. package/dest/services/tx_collection/fast_tx_collection.js +295 -0
  121. package/dest/services/tx_collection/index.d.ts +3 -0
  122. package/dest/services/tx_collection/index.d.ts.map +1 -0
  123. package/dest/services/tx_collection/index.js +2 -0
  124. package/dest/services/tx_collection/instrumentation.d.ts +10 -0
  125. package/dest/services/tx_collection/instrumentation.d.ts.map +1 -0
  126. package/dest/services/tx_collection/instrumentation.js +34 -0
  127. package/dest/services/tx_collection/slow_tx_collection.d.ts +54 -0
  128. package/dest/services/tx_collection/slow_tx_collection.d.ts.map +1 -0
  129. package/dest/services/tx_collection/slow_tx_collection.js +176 -0
  130. package/dest/services/tx_collection/tx_collection.d.ts +109 -0
  131. package/dest/services/tx_collection/tx_collection.d.ts.map +1 -0
  132. package/dest/services/tx_collection/tx_collection.js +127 -0
  133. package/dest/services/tx_collection/tx_collection_sink.d.ts +30 -0
  134. package/dest/services/tx_collection/tx_collection_sink.d.ts.map +1 -0
  135. package/dest/services/tx_collection/tx_collection_sink.js +81 -0
  136. package/dest/services/tx_collection/tx_source.d.ts +18 -0
  137. package/dest/services/tx_collection/tx_source.d.ts.map +1 -0
  138. package/dest/services/tx_collection/tx_source.js +31 -0
  139. package/dest/services/tx_provider.d.ts +49 -0
  140. package/dest/services/tx_provider.d.ts.map +1 -0
  141. package/dest/services/tx_provider.js +206 -0
  142. package/dest/services/{tx_collect_instrumentation.d.ts → tx_provider_instrumentation.d.ts} +2 -2
  143. package/dest/services/tx_provider_instrumentation.d.ts.map +1 -0
  144. package/dest/services/{tx_collect_instrumentation.js → tx_provider_instrumentation.js} +5 -5
  145. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  146. package/dest/test-helpers/make-test-p2p-clients.js +4 -3
  147. package/dest/test-helpers/mock-pubsub.d.ts +2 -1
  148. package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
  149. package/dest/test-helpers/mock-pubsub.js +2 -1
  150. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  151. package/dest/test-helpers/reqresp-nodes.js +8 -4
  152. package/dest/testbench/p2p_client_testbench_worker.js +11 -5
  153. package/dest/util.d.ts +1 -1
  154. package/dest/util.d.ts.map +1 -1
  155. package/package.json +14 -15
  156. package/src/client/factory.ts +87 -12
  157. package/src/client/interface.ts +19 -15
  158. package/src/client/p2p_client.ts +108 -92
  159. package/src/config.ts +52 -1
  160. package/src/index.ts +1 -0
  161. package/src/mem_pools/attestation_pool/attestation_pool.ts +15 -1
  162. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +155 -4
  163. package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +28 -1
  164. package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +28 -2
  165. package/src/mem_pools/attestation_pool/mocks.ts +1 -3
  166. package/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +59 -41
  167. package/src/mem_pools/tx_pool/memory_tx_pool.ts +19 -9
  168. package/src/mem_pools/tx_pool/tx_pool.ts +7 -2
  169. package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +63 -40
  170. package/src/msg_validators/attestation_validator/attestation_validator.ts +1 -1
  171. package/src/msg_validators/tx_validator/block_header_validator.ts +2 -2
  172. package/src/msg_validators/tx_validator/data_validator.ts +36 -27
  173. package/src/msg_validators/tx_validator/double_spend_validator.ts +2 -2
  174. package/src/msg_validators/tx_validator/gas_validator.ts +4 -4
  175. package/src/msg_validators/tx_validator/metadata_validator.ts +22 -28
  176. package/src/msg_validators/tx_validator/phases_validator.ts +2 -2
  177. package/src/msg_validators/tx_validator/tx_proof_validator.ts +2 -2
  178. package/src/services/discv5/discV5_service.ts +4 -1
  179. package/src/services/dummy_service.ts +44 -4
  180. package/src/services/index.ts +3 -1
  181. package/src/services/libp2p/libp2p_service.ts +147 -55
  182. package/src/services/peer-manager/interface.ts +10 -1
  183. package/src/services/peer-manager/peer_manager.ts +441 -41
  184. package/src/services/reqresp/config.ts +3 -3
  185. package/src/services/reqresp/connection-sampler/connection_sampler.ts +38 -63
  186. package/src/services/reqresp/index.ts +2 -0
  187. package/src/services/reqresp/interface.ts +63 -17
  188. package/src/services/reqresp/protocols/auth.ts +83 -0
  189. package/src/services/reqresp/protocols/block.ts +24 -3
  190. package/src/services/reqresp/protocols/block_txs/bitvector.ts +90 -0
  191. package/src/services/reqresp/protocols/block_txs/block_txs_handler.ts +53 -0
  192. package/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts +79 -0
  193. package/src/services/reqresp/protocols/block_txs/index.ts +3 -0
  194. package/src/services/reqresp/protocols/goodbye.ts +3 -3
  195. package/src/services/reqresp/protocols/index.ts +2 -0
  196. package/src/services/reqresp/protocols/status.ts +20 -0
  197. package/src/services/reqresp/protocols/tx.ts +35 -6
  198. package/src/services/reqresp/rate-limiter/rate_limits.ts +20 -0
  199. package/src/services/reqresp/reqresp.ts +294 -264
  200. package/src/services/reqresp/status.ts +9 -3
  201. package/src/services/service.ts +23 -14
  202. package/src/services/tx_collection/config.ts +84 -0
  203. package/src/services/tx_collection/fast_tx_collection.ts +338 -0
  204. package/src/services/tx_collection/index.ts +2 -0
  205. package/src/services/tx_collection/instrumentation.ts +43 -0
  206. package/src/services/tx_collection/slow_tx_collection.ts +232 -0
  207. package/src/services/tx_collection/tx_collection.ts +214 -0
  208. package/src/services/tx_collection/tx_collection_sink.ts +98 -0
  209. package/src/services/tx_collection/tx_source.ts +37 -0
  210. package/src/services/tx_provider.ts +215 -0
  211. package/src/services/{tx_collect_instrumentation.ts → tx_provider_instrumentation.ts} +5 -5
  212. package/src/test-helpers/make-test-p2p-clients.ts +4 -2
  213. package/src/test-helpers/mock-pubsub.ts +1 -0
  214. package/src/test-helpers/reqresp-nodes.ts +7 -1
  215. package/src/testbench/p2p_client_testbench_worker.ts +9 -2
  216. package/src/util.ts +1 -1
  217. package/dest/services/tx_collect_instrumentation.d.ts.map +0 -1
  218. package/dest/services/tx_collector.d.ts +0 -23
  219. package/dest/services/tx_collector.d.ts.map +0 -1
  220. package/dest/services/tx_collector.js +0 -95
  221. package/src/services/tx_collector.ts +0 -134
@@ -1,18 +1,25 @@
1
+ import type { EpochCacheInterface } from '@aztec/epoch-cache';
2
+ import { makeEthSignDigest, recoverAddress } from '@aztec/foundation/crypto';
3
+ import type { EthAddress } from '@aztec/foundation/eth-address';
4
+ import { Fr } from '@aztec/foundation/fields';
1
5
  import { createLogger } from '@aztec/foundation/log';
2
6
  import { bufferToHex } from '@aztec/foundation/string';
7
+ import { DateProvider } from '@aztec/foundation/timer';
3
8
  import type { PeerInfo, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
4
9
  import type { PeerErrorSeverity } from '@aztec/stdlib/p2p';
5
10
  import { type TelemetryClient, trackSpan } from '@aztec/telemetry-client';
6
11
 
7
12
  import { ENR } from '@chainsafe/enr';
8
13
  import type { Connection, PeerId } from '@libp2p/interface';
14
+ import { peerIdFromString } from '@libp2p/peer-id';
9
15
  import type { Multiaddr } from '@multiformats/multiaddr';
10
- import type { Libp2p } from 'libp2p';
11
16
  import { inspect } from 'util';
12
17
 
13
18
  import type { P2PConfig } from '../../config.js';
14
19
  import { PeerEvent } from '../../types/index.js';
20
+ import type { FullLibp2p } from '../../util.js';
15
21
  import { ReqRespSubProtocol } from '../reqresp/interface.js';
22
+ import { AuthRequest, AuthResponse } from '../reqresp/protocols/auth.js';
16
23
  import { GoodByeReason, prettyGoodbyeReason } from '../reqresp/protocols/goodbye.js';
17
24
  import { StatusMessage } from '../reqresp/protocols/status.js';
18
25
  import type { ReqResp } from '../reqresp/reqresp.js';
@@ -27,6 +34,7 @@ const MAX_CACHED_PEERS = 100;
27
34
  const MAX_CACHED_PEER_AGE_MS = 5 * 60 * 1000; // 5 minutes
28
35
  const FAILED_PEER_BAN_TIME_MS = 5 * 60 * 1000; // 5 minutes timeout after failing MAX_DIAL_ATTEMPTS
29
36
  const GOODBYE_DIAL_TIMEOUT_MS = 1000;
37
+ const FAILED_AUTH_HANDSHAKE_EXPIRY_MS = 60 * 60 * 1000; // 1 hour
30
38
 
31
39
  type CachedPeer = {
32
40
  peerId: PeerId;
@@ -41,6 +49,11 @@ type TimedOutPeer = {
41
49
  timeoutUntilMs: number;
42
50
  };
43
51
 
52
+ type FailedAuthHandshakeEntry = {
53
+ count: number;
54
+ lastFailureTimestamp: number;
55
+ };
56
+
44
57
  export class PeerManager implements PeerManagerInterface {
45
58
  private cachedPeers: Map<string, CachedPeer> = new Map();
46
59
  private heartbeatCounter: number = 0;
@@ -50,6 +63,13 @@ export class PeerManager implements PeerManagerInterface {
50
63
  private trustedPeersInitialized: boolean = false;
51
64
  private privatePeers: Set<string> = new Set();
52
65
  private privatePeersInitialized: boolean = false;
66
+ private preferredPeers: Set<string> = new Set();
67
+ private authenticatedPeerIdToValidatorAddress: Map<string, EthAddress> = new Map();
68
+ private authenticatedValidatorAddressToPeerId: Map<string, PeerId> = new Map();
69
+ private peersToBeDisconnected: Set<string> = new Set();
70
+ private failedAuthHandshakes: Map<string, FailedAuthHandshakeEntry> = new Map();
71
+ private validatorAddresses: EthAddress[] = [];
72
+ private initializedPreferredPeers: boolean = false;
53
73
 
54
74
  private metrics: PeerManagerMetrics;
55
75
  private handlers: {
@@ -59,7 +79,7 @@ export class PeerManager implements PeerManagerInterface {
59
79
  };
60
80
 
61
81
  constructor(
62
- private libP2PNode: Libp2p,
82
+ private libP2PNode: FullLibp2p,
63
83
  private peerDiscoveryService: PeerDiscoveryService,
64
84
  private config: P2PConfig,
65
85
  telemetryClient: TelemetryClient,
@@ -68,7 +88,12 @@ export class PeerManager implements PeerManagerInterface {
68
88
  private reqresp: ReqResp,
69
89
  private readonly worldStateSynchronizer: WorldStateSynchronizer,
70
90
  private readonly protocolVersion: string,
91
+ private readonly epochCache: EpochCacheInterface,
92
+ private readonly dateProvider: DateProvider = new DateProvider(),
71
93
  ) {
94
+ if (this.config.p2pDisableStatusHandshake && this.config.p2pAllowOnlyValidators) {
95
+ throw new Error('Status handshake disabled but is required to allow only validators to connect.');
96
+ }
72
97
  this.metrics = new PeerManagerMetrics(telemetryClient, 'PeerManager');
73
98
 
74
99
  // Handle Discovered peers
@@ -85,12 +110,11 @@ export class PeerManager implements PeerManagerInterface {
85
110
  this.libP2PNode.addEventListener(PeerEvent.DISCONNECTED, this.handlers.handleDisconnectedPeerEvent);
86
111
 
87
112
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
88
- this.peerDiscoveryService.on(PeerEvent.DISCOVERED, this.handlers.handleDiscoveredPeer);
113
+ this.peerDiscoveryService?.on(PeerEvent.DISCOVERED, this.handlers.handleDiscoveredPeer);
89
114
 
90
115
  // Display peer counts every 60 seconds
91
116
  this.displayPeerCountsPeerHeartbeat = Math.floor(60_000 / this.config.peerCheckIntervalMS);
92
117
  }
93
-
94
118
  /**
95
119
  * Initializes the trusted peers.
96
120
  *
@@ -124,6 +148,13 @@ export class PeerManager implements PeerManagerInterface {
124
148
  })
125
149
  .catch(e => this.logger.error('Error initializing private peers', e));
126
150
  }
151
+
152
+ if (this.config.preferredPeers) {
153
+ const preferredPeersEnrs: ENR[] = this.config.preferredPeers.map(enr => ENR.decodeTxt(enr));
154
+ await Promise.all(preferredPeersEnrs.map(enr => enr.peerId()))
155
+ .then(peerIds => peerIds.forEach(peerId => this.preferredPeers.add(peerId.toString())))
156
+ .catch(e => this.logger.error('Error initializing preferred peers', e));
157
+ }
127
158
  }
128
159
 
129
160
  get tracer() {
@@ -131,15 +162,70 @@ export class PeerManager implements PeerManagerInterface {
131
162
  }
132
163
 
133
164
  @trackSpan('PeerManager.heartbeat')
134
- public heartbeat() {
165
+ public async heartbeat() {
135
166
  this.heartbeatCounter++;
136
167
  this.peerScoring.decayAllScores();
137
-
138
168
  this.cleanupExpiredTimeouts();
139
169
 
170
+ await this.setupDirectPeersIfValidator();
171
+ await this.updateAuthenticatedPeers();
172
+ await this.processScheduledDisconnects();
173
+
140
174
  this.discover();
141
175
  }
142
176
 
177
+ /*
178
+ * If this node is connecting to preferred peers, make sure it is registered validator */
179
+ async setupDirectPeersIfValidator() {
180
+ if (!this.config.preferredPeers) {
181
+ return;
182
+ }
183
+
184
+ // Already initialized preferred peers, don't wastefully repeat the same work
185
+ if (this.initializedPreferredPeers) {
186
+ return;
187
+ }
188
+
189
+ const registeredValidators = await this.epochCache.getRegisteredValidators();
190
+ const validatorSet = new Set(registeredValidators.map(v => v.toString()));
191
+ const isThisNodePartOfValidatorSet = this.validatorAddresses.some(v => validatorSet.has(v.toString()));
192
+
193
+ if (!isThisNodePartOfValidatorSet) {
194
+ return;
195
+ }
196
+
197
+ const preferredPeersEnrs: ENR[] = this.config.preferredPeers.map(enr => ENR.decodeTxt(enr));
198
+ await Promise.all(preferredPeersEnrs.map(enr => enr.peerId()))
199
+ .then(peerIds => peerIds.forEach(peerId => this.preferredPeers.add(peerId.toString())))
200
+ .catch(e => this.logger.error('Error initializing preferred peers', e));
201
+
202
+ const directPeers = (
203
+ await Promise.all(
204
+ preferredPeersEnrs.map(async enr => {
205
+ const peerId = await enr.peerId();
206
+ const address = enr.getLocationMultiaddr('tcp');
207
+ if (address === undefined) {
208
+ throw new Error(`Direct peer ${peerId.toString()} has no TCP address, ENR: ${enr.encodeTxt()}`);
209
+ }
210
+ return {
211
+ id: peerId,
212
+ addrs: [address],
213
+ };
214
+ }),
215
+ )
216
+ ).filter(peer => peer !== undefined);
217
+
218
+ await Promise.all(
219
+ directPeers.map(peer => {
220
+ this.libP2PNode.services.pubsub.direct.add(peer.id.toString());
221
+
222
+ return this.libP2PNode.peerStore.merge(peer.id, { multiaddrs: peer.addrs });
223
+ }),
224
+ );
225
+
226
+ this.initializedPreferredPeers = true;
227
+ }
228
+
143
229
  /**
144
230
  * Cleans up expired timeouts.
145
231
  *
@@ -149,7 +235,7 @@ export class PeerManager implements PeerManagerInterface {
149
235
  */
150
236
  private cleanupExpiredTimeouts() {
151
237
  // Clean up expired timeouts
152
- const now = Date.now();
238
+ const now = this.dateProvider.now();
153
239
  for (const [peerId, timedOutPeer] of this.timedOutPeers.entries()) {
154
240
  if (now >= timedOutPeer.timeoutUntilMs) {
155
241
  this.timedOutPeers.delete(peerId);
@@ -157,20 +243,58 @@ export class PeerManager implements PeerManagerInterface {
157
243
  }
158
244
  }
159
245
 
246
+ /**
247
+ * Processes scheduled disconnects during heartbeat.
248
+ *
249
+ * This batch processes all peers that have been marked for disconnect.
250
+ * preventing immediate disconnects that could cause libp2p state corruption.
251
+ */
252
+ private async processScheduledDisconnects() {
253
+ if (this.peersToBeDisconnected.size === 0) {
254
+ return;
255
+ }
256
+
257
+ const peersToDisconnect = Array.from(this.peersToBeDisconnected);
258
+
259
+ this.logger.debug(`Processing ${peersToDisconnect.length} scheduled disconnects`);
260
+ try {
261
+ await Promise.all(
262
+ peersToDisconnect.map(async peerIdStr => {
263
+ if (await this.disconnectPeer(peerIdFromString(peerIdStr))) {
264
+ this.peersToBeDisconnected.delete(peerIdStr);
265
+ }
266
+ }),
267
+ );
268
+ this.logger.verbose(`Disconnected ${peersToDisconnect.length} peers`, { peersToDisconnect });
269
+ } catch (error) {
270
+ this.logger.error('Error when disconnecting from peers', error);
271
+ }
272
+ }
273
+
160
274
  /**
161
275
  * Performs Status Handshake with a connected peer.
162
276
  * @param e - The connected peer event.
163
277
  */
164
278
  private handleConnectedPeerEvent(e: CustomEvent<PeerId>) {
165
279
  const peerId = e.detail;
166
- if (this.peerDiscoveryService.isBootstrapPeer(peerId)) {
167
- this.logger.verbose(`Connected to bootstrap peer ${peerId.toString()}`);
168
- } else {
169
- this.logger.verbose(`Connected to transaction peer ${peerId.toString()}`);
280
+ this.logger.verbose(`Connected to peer ${peerId.toString()}`);
281
+ if (this.config.p2pDisableStatusHandshake) {
282
+ return;
170
283
  }
171
- if (!this.config.p2pDisableStatusHandshake) {
284
+ // If we are not configured to only allow validators then perform a status handshake
285
+ if (!this.config.p2pAllowOnlyValidators) {
172
286
  void this.exchangeStatusHandshake(peerId);
287
+ return;
288
+ }
289
+
290
+ // We are configured to only allow validators, but this doesn't apply to trusted, private peers or preferred peers
291
+ if (this.isProtectedPeer(peerId)) {
292
+ void this.exchangeStatusHandshake(peerId);
293
+ return;
173
294
  }
295
+
296
+ // Initiate auth handshake
297
+ void this.exchangeAuthHandshake(peerId);
174
298
  }
175
299
 
176
300
  /**
@@ -179,13 +303,21 @@ export class PeerManager implements PeerManagerInterface {
179
303
  */
180
304
  private handleDisconnectedPeerEvent(e: CustomEvent<PeerId>) {
181
305
  const peerId = e.detail;
182
- if (this.peerDiscoveryService.isBootstrapPeer(peerId)) {
183
- this.logger.verbose(`Disconnected from bootstrap peer ${peerId.toString()}`);
184
- } else {
185
- this.logger.verbose(`Disconnected from transaction peer ${peerId.toString()}`);
306
+ this.logger.verbose(`Disconnected from peer ${peerId.toString()}`);
307
+ const validatorAddress = this.authenticatedPeerIdToValidatorAddress.get(peerId.toString());
308
+ if (validatorAddress !== undefined) {
309
+ this.logger.info(
310
+ `Removing authentication for validator ${validatorAddress} at peer id ${peerId.toString()} due to disconnection`,
311
+ );
312
+ this.authenticatedValidatorAddressToPeerId.delete(validatorAddress.toString());
313
+ this.authenticatedPeerIdToValidatorAddress.delete(peerId.toString());
186
314
  }
187
315
  }
188
316
 
317
+ public registerThisValidatorAddresses(address: EthAddress[]): void {
318
+ this.validatorAddresses = [...address];
319
+ }
320
+
189
321
  /**
190
322
  * Checks if a peer is trusted.
191
323
  * @param peerId - The peer ID.
@@ -239,13 +371,33 @@ export class PeerManager implements PeerManagerInterface {
239
371
  return this.privatePeers.has(peerId.toString());
240
372
  }
241
373
 
374
+ /**
375
+ * Adds a peer to the preferred peers set.
376
+ * @param peerId - The peer ID to add to preferred peers.
377
+ */
378
+ public addPreferredPeer(peerId: PeerId): void {
379
+ const peerIdStr = peerId.toString();
380
+
381
+ this.preferredPeers.add(peerIdStr);
382
+ this.logger.verbose(`Added preferred peer ${peerIdStr}`);
383
+ }
384
+
385
+ /**
386
+ * Checks if a peer is preferred.
387
+ * @param peerId - The peer ID.
388
+ * @returns True if the peer is preferred, false otherwise.
389
+ */
390
+ private isPreferredPeer(peerId: PeerId): boolean {
391
+ return this.preferredPeers.has(peerId.toString());
392
+ }
393
+
242
394
  /**
243
395
  * Checks if a peer is protected (either trusted or private).
244
396
  * @param peerId - The peer ID.
245
397
  * @returns True if the peer is protected, false otherwise.
246
398
  */
247
399
  private isProtectedPeer(peerId: PeerId): boolean {
248
- return this.isTrustedPeer(peerId) || this.isPrivatePeer(peerId);
400
+ return this.isTrustedPeer(peerId) || this.isPrivatePeer(peerId) || this.isPreferredPeer(peerId);
249
401
  }
250
402
 
251
403
  /**
@@ -260,7 +412,7 @@ export class PeerManager implements PeerManagerInterface {
260
412
 
261
413
  this.metrics.recordGoodbyeReceived(reason);
262
414
 
263
- void this.disconnectPeer(peerId);
415
+ this.markPeerForDisconnect(peerId);
264
416
  }
265
417
 
266
418
  public penalizePeer(peerId: PeerId, penalty: PeerErrorSeverity) {
@@ -271,6 +423,11 @@ export class PeerManager implements PeerManagerInterface {
271
423
  return this.peerScoring.getScore(peerId);
272
424
  }
273
425
 
426
+ public shouldDisableP2PGossip(peerId: string): boolean {
427
+ const isAuthenticated = this.isAuthenticatedPeer(peerIdFromString(peerId));
428
+ return (this.config.p2pAllowOnlyValidators ?? false) && !isAuthenticated;
429
+ }
430
+
274
431
  public getPeers(includePending = false): PeerInfo[] {
275
432
  const connected = this.libP2PNode
276
433
  .getPeers()
@@ -304,6 +461,38 @@ export class PeerManager implements PeerManagerInterface {
304
461
  return [...connected, ...dialQueue, ...cachedPeers];
305
462
  }
306
463
 
464
+ public isAuthenticatedPeer(peerId: PeerId): boolean {
465
+ const peerIdAsString = peerId.toString();
466
+ return (
467
+ this.privatePeers.has(peerIdAsString) ||
468
+ this.trustedPeers.has(peerIdAsString) ||
469
+ this.preferredPeers.has(peerIdAsString) ||
470
+ this.authenticatedPeerIdToValidatorAddress.has(peerIdAsString)
471
+ );
472
+ }
473
+
474
+ /*
475
+ * Checks whether peer is allowed to connect
476
+ *
477
+ * @param id: Address of the node or it's peerId
478
+ *
479
+ * @returns: True if node is allowed to connect, otherwise false
480
+ * */
481
+ public isNodeAllowedToConnect(id: string | PeerId): boolean {
482
+ const entry = this.failedAuthHandshakes.get(id.toString());
483
+ if (!entry) {
484
+ return true;
485
+ }
486
+
487
+ // In case entry is too old, remove it and allow connection
488
+ if (this.dateProvider.now() - entry.lastFailureTimestamp > FAILED_AUTH_HANDSHAKE_EXPIRY_MS) {
489
+ this.failedAuthHandshakes.delete(id.toString());
490
+ return true;
491
+ }
492
+
493
+ return entry.count <= this.config.p2pMaxFailedAuthAttemptsAllowed;
494
+ }
495
+
307
496
  /**
308
497
  * Discovers peers.
309
498
  */
@@ -315,12 +504,13 @@ export class PeerManager implements PeerManagerInterface {
315
504
  );
316
505
 
317
506
  // Calculate how many connections we're looking to make
318
- const peersToConnect = this.config.maxPeerCount - healthyConnections.length - this.trustedPeers.size;
507
+ const protectedPeerCount = this.getProtectedPeerCount();
508
+ const peersToConnect = this.config.maxPeerCount - healthyConnections.length - protectedPeerCount;
319
509
 
320
510
  const logLevel = this.heartbeatCounter % this.displayPeerCountsPeerHeartbeat === 0 ? 'info' : 'debug';
321
511
  this.logger[logLevel](`Connected to ${healthyConnections.length + this.trustedPeers.size} peers`, {
322
512
  discoveredConnections: healthyConnections.length,
323
- protectedConnections: this.trustedPeers.size,
513
+ protectedConnections: protectedPeerCount,
324
514
  maxPeerCount: this.config.maxPeerCount,
325
515
  cachedPeers: this.cachedPeers.size,
326
516
  ...this.peerScoring.getStats(),
@@ -342,13 +532,14 @@ export class PeerManager implements PeerManagerInterface {
342
532
  .filter(Boolean) as string[],
343
533
  );
344
534
 
535
+ const now = this.dateProvider.now();
345
536
  for (const [id, peerData] of this.cachedPeers.entries()) {
346
537
  // if already dialling or connected to, remove from cache
347
538
  if (
348
539
  pendingDials.has(id) ||
349
540
  healthyConnections.some(conn => conn.remotePeer.equals(peerData.peerId)) ||
350
541
  // if peer has been in cache for the max cache age, remove from cache
351
- Date.now() - peerData.addedUnixMs > MAX_CACHED_PEER_AGE_MS
542
+ now - peerData.addedUnixMs > MAX_CACHED_PEER_AGE_MS
352
543
  ) {
353
544
  this.cachedPeers.delete(id);
354
545
  } else {
@@ -377,6 +568,10 @@ export class PeerManager implements PeerManagerInterface {
377
568
  return connections.filter(conn => !this.isProtectedPeer(conn.remotePeer));
378
569
  }
379
570
 
571
+ private getProtectedPeerCount(): number {
572
+ return this.trustedPeers.size + this.privatePeers.size + this.preferredPeers.size;
573
+ }
574
+
380
575
  private pruneUnhealthyPeers(connections: Connection[]): Connection[] {
381
576
  const connectedHealthyPeers: Connection[] = [];
382
577
 
@@ -404,7 +599,8 @@ export class PeerManager implements PeerManagerInterface {
404
599
  * @returns The pruned list of connections.
405
600
  */
406
601
  private prioritizePeers(connections: Connection[]): Connection[] {
407
- if (connections.length > this.config.maxPeerCount - this.trustedPeers.size) {
602
+ const protectedPeerCount = this.getProtectedPeerCount();
603
+ if (connections.length > this.config.maxPeerCount - protectedPeerCount) {
408
604
  // Sort the regular peer scores from highest to lowest
409
605
  const prioritizedConnections = connections.sort((connectionA, connectionB) => {
410
606
  const connectionScoreA = this.peerScoring.getScore(connectionA.remotePeer.toString());
@@ -413,7 +609,7 @@ export class PeerManager implements PeerManagerInterface {
413
609
  });
414
610
 
415
611
  // Calculate how many regular peers we can keep
416
- const peersToKeep = Math.max(0, this.config.maxPeerCount - this.trustedPeers.size);
612
+ const peersToKeep = Math.max(0, this.config.maxPeerCount - protectedPeerCount);
417
613
 
418
614
  // Disconnect from the lowest scoring regular connections that exceed our limit
419
615
  for (const conn of prioritizedConnections.slice(peersToKeep)) {
@@ -484,15 +680,36 @@ export class PeerManager implements PeerManagerInterface {
484
680
  } catch (error) {
485
681
  this.logger.debug(`Failed to send goodbye to peer ${peer.toString()}: ${error}`);
486
682
  } finally {
487
- await this.disconnectPeer(peer);
683
+ this.markPeerForDisconnect(peer);
488
684
  }
489
685
  }
490
686
 
491
- private async disconnectPeer(peer: PeerId) {
687
+ /*
688
+ * Marks peer to be disconnected on the next heartbeat
689
+ * */
690
+ private markPeerForDisconnect(peer: PeerId) {
691
+ const peerIdStr = peer.toString();
692
+ this.logger.debug(`Scheduling peer ${peerIdStr} for disconnection`);
693
+ this.peersToBeDisconnected.add(peerIdStr);
694
+ }
695
+
696
+ /**
697
+ * Performs the actual disconnection of a peer.
698
+ * This is called during heartbeat processing to avoid immediate disconnections.
699
+ *
700
+ * @returns True if peer was disconnect, otherwise false
701
+ */
702
+ private async disconnectPeer(peer: PeerId): Promise<boolean> {
703
+ const peerIdStr = peer.toString();
704
+
492
705
  try {
493
706
  await this.libP2PNode.hangUp(peer);
707
+
708
+ this.logger.debug(`Successfully disconnected peer ${peerIdStr}`);
709
+ return true;
494
710
  } catch (error) {
495
- this.logger.debug(`Failed to disconnect peer ${peer.toString()}`, { error: inspect(error) });
711
+ this.logger.warn(`Failed to disconnect peer ${peerIdStr}`, { error });
712
+ return false;
496
713
  }
497
714
  }
498
715
 
@@ -505,10 +722,16 @@ export class PeerManager implements PeerManagerInterface {
505
722
  const peerId = await enr.peerId();
506
723
  const peerIdString = peerId.toString();
507
724
 
725
+ // Don't attempt to connect to peers scheduled for disconnection
726
+ if (this.peersToBeDisconnected.has(peerIdString)) {
727
+ this.logger.trace(`Skipping peer scheduled for disconnection ${peerId}`);
728
+ return;
729
+ }
730
+
508
731
  // Check if peer is temporarily timed out
509
732
  const timedOutPeer = this.timedOutPeers.get(peerIdString);
510
733
  if (timedOutPeer) {
511
- if (Date.now() < timedOutPeer.timeoutUntilMs) {
734
+ if (this.dateProvider.now() < timedOutPeer.timeoutUntilMs) {
512
735
  this.logger.trace(`Skipping timed out peer ${peerId}`);
513
736
  return;
514
737
  }
@@ -548,7 +771,7 @@ export class PeerManager implements PeerManagerInterface {
548
771
  enr,
549
772
  multiaddrTcp,
550
773
  dialAttempts: 0,
551
- addedUnixMs: Date.now(),
774
+ addedUnixMs: this.dateProvider.now(),
552
775
  };
553
776
 
554
777
  // Determine if we should dial immediately or not
@@ -583,7 +806,7 @@ export class PeerManager implements PeerManagerInterface {
583
806
  // Add to timed out peers
584
807
  this.timedOutPeers.set(id, {
585
808
  peerId: id,
586
- timeoutUntilMs: Date.now() + FAILED_PEER_BAN_TIME_MS,
809
+ timeoutUntilMs: this.dateProvider.now() + FAILED_PEER_BAN_TIME_MS,
587
810
  });
588
811
  }
589
812
  }
@@ -622,6 +845,11 @@ export class PeerManager implements PeerManagerInterface {
622
845
  }
623
846
  }
624
847
 
848
+ private async createStatusMessage() {
849
+ const syncSummary = (await this.worldStateSynchronizer.status()).syncSummary;
850
+ return StatusMessage.fromWorldStateSyncStatus(this.protocolVersion, syncSummary);
851
+ }
852
+
625
853
  /**
626
854
  * Performs status Handshake with the Peer
627
855
  * The way the protocol is designed is that each peer will call this method on newly established p2p connection.
@@ -634,30 +862,30 @@ export class PeerManager implements PeerManagerInterface {
634
862
  * */
635
863
  private async exchangeStatusHandshake(peerId: PeerId) {
636
864
  try {
637
- const syncSummary = (await this.worldStateSynchronizer.status()).syncSummary;
638
- const ourStatus = StatusMessage.fromWorldStateSyncStatus(this.protocolVersion, syncSummary);
865
+ const ourStatus = await this.createStatusMessage();
639
866
  //Note: Technically we don't have to send out status to peer as well, but we do.
640
867
  //It will be easier to update protocol in the future this way if need be.
641
868
  this.logger.trace(`Initiating status handshake with peer ${peerId}`);
642
- const { status, data } = await this.reqresp.sendRequestToPeer(
643
- peerId,
644
- ReqRespSubProtocol.STATUS,
645
- ourStatus.toBuffer(),
646
- );
647
- const logData = { peerId, status: ReqRespStatus[status], data: data ? bufferToHex(data) : undefined };
869
+ const response = await this.reqresp.sendRequestToPeer(peerId, ReqRespSubProtocol.STATUS, ourStatus.toBuffer());
870
+ const { status } = response;
648
871
  if (status !== ReqRespStatus.SUCCESS) {
649
872
  //TODO: maybe hard ban these peers in the future.
650
873
  //We could allow this to happen up to N times, and then hard ban?
651
874
  //Hard ban: Disallow connection via e.g. libp2p's Gater
652
- this.logger.debug(`Disconnecting peer ${peerId} who failed to respond status handshake`, logData);
653
- await this.disconnectPeer(peerId);
875
+ this.logger.debug(`Disconnecting peer ${peerId} who failed to respond status handshake`, {
876
+ peerId,
877
+ status: ReqRespStatus[status],
878
+ });
879
+ this.markPeerForDisconnect(peerId);
654
880
  return;
655
881
  }
656
882
 
883
+ const { data } = response;
884
+ const logData = { peerId, status: ReqRespStatus[status], data: data ? bufferToHex(data) : undefined };
657
885
  const peerStatusMessage = StatusMessage.fromBuffer(data);
658
886
  if (!ourStatus.validate(peerStatusMessage)) {
659
887
  this.logger.debug(`Disconnecting peer ${peerId} due to failed status handshake.`, logData);
660
- await this.disconnectPeer(peerId);
888
+ this.markPeerForDisconnect(peerId);
661
889
  return;
662
890
  }
663
891
  this.logger.debug(`Successfully completed status handshake with peer ${peerId}`, logData);
@@ -666,10 +894,134 @@ export class PeerManager implements PeerManagerInterface {
666
894
  this.logger.debug(`Disconnecting peer ${peerId} due to error during status handshake: ${err.message ?? err}`, {
667
895
  peerId,
668
896
  });
669
- await this.disconnectPeer(peerId);
897
+ this.markPeerForDisconnect(peerId);
670
898
  }
671
899
  }
672
900
 
901
+ /**
902
+ * Performs auth Handshake with the Peer
903
+ * A superset of the status handshake. Also includes a challenge that needs to be signed by the peer's validator key.
904
+ * @param: peerId The Id of the peer to request the Status from.
905
+ * */
906
+ private async exchangeAuthHandshake(peerId: PeerId) {
907
+ const peerIdString = peerId.toString();
908
+
909
+ try {
910
+ const ourStatus = await this.createStatusMessage();
911
+ const authRequest = new AuthRequest(ourStatus, Fr.random());
912
+
913
+ // Note: Technically we don't have to send our status to peer as well, but we do.
914
+ // It will be easier to update protocol in the future this way if need be.
915
+ // We also need to send the challenge at least, so that the peer can sign it.
916
+ this.logger.debug(`Initiating auth handshake with peer ${peerId}`);
917
+ const response = await this.reqresp.sendRequestToPeer(peerId, ReqRespSubProtocol.AUTH, authRequest.toBuffer());
918
+ const { status } = response;
919
+ if (status !== ReqRespStatus.SUCCESS) {
920
+ this.logger.debug(`Disconnecting peer ${peerId} who failed to respond auth handshake`, {
921
+ peerId,
922
+ status: ReqRespStatus[status],
923
+ });
924
+ this.markAuthHandshakeFailed(peerId);
925
+ this.markPeerForDisconnect(peerId);
926
+ return;
927
+ }
928
+
929
+ const { data } = response;
930
+ const logData = { peerId, status: ReqRespStatus[status], data: data ? bufferToHex(data) : undefined };
931
+
932
+ const peerAuthResponse = AuthResponse.fromBuffer(data);
933
+
934
+ const peerStatusMessage = peerAuthResponse.status;
935
+ if (!ourStatus.validate(peerStatusMessage)) {
936
+ this.logger.debug(`Disconnecting peer ${peerId} due to failed status handshake as part of auth.`, logData);
937
+ this.markAuthHandshakeFailed(peerId);
938
+ this.markPeerForDisconnect(peerId);
939
+ return;
940
+ }
941
+
942
+ const hashToRecover = authRequest.getPayloadToSign();
943
+ const ethSignedHash = makeEthSignDigest(hashToRecover);
944
+ const sender = recoverAddress(ethSignedHash, peerAuthResponse.signature);
945
+ const registeredValidators = await this.epochCache.getRegisteredValidators();
946
+ const found = registeredValidators.find(v => v.toString() === sender.toString()) !== undefined;
947
+ if (!found) {
948
+ this.logger.debug(
949
+ `Disconnecting peer ${peerId} due to failed auth handshake, peer is not a registered validator.`,
950
+ {
951
+ peerId,
952
+ address: sender.toString(),
953
+ },
954
+ );
955
+ this.markAuthHandshakeFailed(peerId);
956
+ this.markPeerForDisconnect(peerId);
957
+ return;
958
+ }
959
+
960
+ // Check to see that this validator address isn't already allocated to a different peer
961
+ const peerForAddress = this.authenticatedValidatorAddressToPeerId.get(sender.toString());
962
+ if (peerForAddress !== undefined && peerForAddress.toString() !== peerIdString) {
963
+ this.logger.debug(
964
+ `Received auth for validator ${sender.toString()} from peer ${peerIdString}, but this validator is already authenticated to peer ${peerForAddress.toString()}`,
965
+ );
966
+ return;
967
+ }
968
+
969
+ this.markAuthHandshakeSuccess(peerId);
970
+ this.authenticatedPeerIdToValidatorAddress.set(peerIdString, sender);
971
+ this.authenticatedValidatorAddressToPeerId.set(sender.toString(), peerId);
972
+ this.logger.info(
973
+ `Successfully completed auth handshake with peer ${peerId}, validator address ${sender.toString()}`,
974
+ logData,
975
+ );
976
+ } catch (err: any) {
977
+ //TODO: maybe hard ban these peers in the future
978
+ this.logger.debug(`Disconnecting peer ${peerId} due to error during auth handshake: ${err.message ?? err}`, {
979
+ peerId,
980
+ });
981
+ this.markAuthHandshakeFailed(peerId);
982
+ this.markPeerForDisconnect(peerId);
983
+ }
984
+ }
985
+
986
+ /*
987
+ * Marks when peer fails auth handshake
988
+ * */
989
+ private markAuthHandshakeFailed(peerId: PeerId) {
990
+ const now = this.dateProvider.now();
991
+ const peerIdStr = peerId.toString();
992
+
993
+ const existingEntry = this.failedAuthHandshakes.get(peerIdStr);
994
+ this.failedAuthHandshakes.set(peerIdStr, {
995
+ count: (existingEntry?.count || 0) + 1,
996
+ lastFailureTimestamp: now,
997
+ });
998
+
999
+ const connections = this.libP2PNode.getConnections(peerId);
1000
+ connections.forEach(conn => {
1001
+ // We mark the IP address
1002
+ const address = conn.remoteAddr.nodeAddress().address;
1003
+ const existingAddressEntry = this.failedAuthHandshakes.get(address);
1004
+ this.failedAuthHandshakes.set(address, {
1005
+ count: (existingAddressEntry?.count || 0) + 1,
1006
+ lastFailureTimestamp: now,
1007
+ });
1008
+ });
1009
+ }
1010
+
1011
+ /*
1012
+ * Marks when peer exchanges auth handshake
1013
+ * Removes any failed previous attempts
1014
+ * */
1015
+ private markAuthHandshakeSuccess(peerId: PeerId) {
1016
+ this.failedAuthHandshakes.delete(peerId.toString());
1017
+
1018
+ const connections = this.libP2PNode.getConnections(peerId);
1019
+ connections.forEach(conn => {
1020
+ const address = conn.remoteAddr.nodeAddress().address;
1021
+ this.failedAuthHandshakes.delete(address);
1022
+ });
1023
+ }
1024
+
673
1025
  /**
674
1026
  * Stops the peer manager.
675
1027
  * Removing all event listeners.
@@ -686,6 +1038,54 @@ export class PeerManager implements PeerManagerInterface {
686
1038
  this.libP2PNode.removeEventListener(PeerEvent.CONNECTED, this.handlers.handleConnectedPeerEvent);
687
1039
  this.libP2PNode.removeEventListener(PeerEvent.DISCONNECTED, this.handlers.handleDisconnectedPeerEvent);
688
1040
  }
1041
+
1042
+ private shouldTrustWithIdentity(peerId: PeerId): boolean {
1043
+ return this.isProtectedPeer(peerId);
1044
+ }
1045
+
1046
+ /**
1047
+ * Performs auth request verification from peer. An auth request is valid if requested by an authorized peer (a peer we trust).
1048
+ *
1049
+ * @param: _authRequest - Auth request (unused)
1050
+ * @param: peerId - The ID of the peer that requested the auth handshake
1051
+ *
1052
+ * @returns: StatusMessage if peer is trusted
1053
+ *
1054
+ * @throws: If peer is unauthorized
1055
+ * */
1056
+ public async handleAuthRequestFromPeer(_authRequest: AuthRequest, peerId: PeerId): Promise<StatusMessage> {
1057
+ if (!this.shouldTrustWithIdentity(peerId)) {
1058
+ this.logger.warn(`Received auth request from untrusted peer ${peerId.toString()}`);
1059
+ throw new Error('Unauthorised');
1060
+ }
1061
+ this.logger.debug(`Received auth request from trusted peer ${peerId.toString()}`);
1062
+ return await this.createStatusMessage();
1063
+ }
1064
+
1065
+ private async updateAuthenticatedPeers(): Promise<void> {
1066
+ const registeredValidators = await this.epochCache.getRegisteredValidators();
1067
+ const validatorSet = new Set(registeredValidators.map(v => v.toString()));
1068
+
1069
+ const peersToDelete: Set<string> = new Set();
1070
+ const addressesToDelete: Set<string> = new Set();
1071
+ for (const [peer, address] of this.authenticatedPeerIdToValidatorAddress.entries()) {
1072
+ const addressString = address.toString();
1073
+ if (!validatorSet.has(addressString)) {
1074
+ peersToDelete.add(peer);
1075
+ addressesToDelete.add(addressString);
1076
+ this.logger.info(
1077
+ `Removing authentication for peer ${peer.toString()} at address ${addressString} due to no longer being a registered validator`,
1078
+ );
1079
+ }
1080
+ }
1081
+
1082
+ for (const peer of peersToDelete) {
1083
+ this.authenticatedPeerIdToValidatorAddress.delete(peer);
1084
+ }
1085
+ for (const address of addressesToDelete) {
1086
+ this.authenticatedValidatorAddressToPeerId.delete(address);
1087
+ }
1088
+ }
689
1089
  }
690
1090
 
691
1091
  /**