@aztec/p2p 0.0.0-test.0

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 (311) hide show
  1. package/README.md +7 -0
  2. package/dest/bootstrap/bootstrap.d.ts +38 -0
  3. package/dest/bootstrap/bootstrap.d.ts.map +1 -0
  4. package/dest/bootstrap/bootstrap.js +123 -0
  5. package/dest/client/factory.d.ts +21 -0
  6. package/dest/client/factory.d.ts.map +1 -0
  7. package/dest/client/factory.js +37 -0
  8. package/dest/client/index.d.ts +3 -0
  9. package/dest/client/index.d.ts.map +1 -0
  10. package/dest/client/index.js +2 -0
  11. package/dest/client/p2p_client.d.ts +314 -0
  12. package/dest/client/p2p_client.d.ts.map +1 -0
  13. package/dest/client/p2p_client.js +505 -0
  14. package/dest/config.d.ts +180 -0
  15. package/dest/config.d.ts.map +1 -0
  16. package/dest/config.js +193 -0
  17. package/dest/enr/generate-enr.d.ts +9 -0
  18. package/dest/enr/generate-enr.d.ts.map +1 -0
  19. package/dest/enr/generate-enr.js +30 -0
  20. package/dest/enr/index.d.ts +2 -0
  21. package/dest/enr/index.d.ts.map +1 -0
  22. package/dest/enr/index.js +1 -0
  23. package/dest/errors/reqresp.error.d.ts +28 -0
  24. package/dest/errors/reqresp.error.d.ts.map +1 -0
  25. package/dest/errors/reqresp.error.js +30 -0
  26. package/dest/index.d.ts +8 -0
  27. package/dest/index.d.ts.map +1 -0
  28. package/dest/index.js +7 -0
  29. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +57 -0
  30. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -0
  31. package/dest/mem_pools/attestation_pool/attestation_pool.js +6 -0
  32. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +3 -0
  33. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -0
  34. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +195 -0
  35. package/dest/mem_pools/attestation_pool/index.d.ts +3 -0
  36. package/dest/mem_pools/attestation_pool/index.d.ts.map +1 -0
  37. package/dest/mem_pools/attestation_pool/index.js +2 -0
  38. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +22 -0
  39. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +1 -0
  40. package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +112 -0
  41. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +17 -0
  42. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +1 -0
  43. package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +129 -0
  44. package/dest/mem_pools/attestation_pool/mocks.d.ts +19 -0
  45. package/dest/mem_pools/attestation_pool/mocks.d.ts.map +1 -0
  46. package/dest/mem_pools/attestation_pool/mocks.js +33 -0
  47. package/dest/mem_pools/index.d.ts +4 -0
  48. package/dest/mem_pools/index.d.ts.map +1 -0
  49. package/dest/mem_pools/index.js +1 -0
  50. package/dest/mem_pools/instrumentation.d.ts +30 -0
  51. package/dest/mem_pools/instrumentation.d.ts.map +1 -0
  52. package/dest/mem_pools/instrumentation.js +84 -0
  53. package/dest/mem_pools/interface.d.ts +11 -0
  54. package/dest/mem_pools/interface.d.ts.map +1 -0
  55. package/dest/mem_pools/interface.js +3 -0
  56. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts +66 -0
  57. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts.map +1 -0
  58. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.js +245 -0
  59. package/dest/mem_pools/tx_pool/index.d.ts +4 -0
  60. package/dest/mem_pools/tx_pool/index.d.ts.map +1 -0
  61. package/dest/mem_pools/tx_pool/index.js +3 -0
  62. package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts +56 -0
  63. package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts.map +1 -0
  64. package/dest/mem_pools/tx_pool/memory_tx_pool.js +141 -0
  65. package/dest/mem_pools/tx_pool/priority.d.ts +8 -0
  66. package/dest/mem_pools/tx_pool/priority.d.ts.map +1 -0
  67. package/dest/mem_pools/tx_pool/priority.js +10 -0
  68. package/dest/mem_pools/tx_pool/tx_pool.d.ts +66 -0
  69. package/dest/mem_pools/tx_pool/tx_pool.d.ts.map +1 -0
  70. package/dest/mem_pools/tx_pool/tx_pool.js +3 -0
  71. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts +7 -0
  72. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +1 -0
  73. package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +169 -0
  74. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +8 -0
  75. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -0
  76. package/dest/msg_validators/attestation_validator/attestation_validator.js +19 -0
  77. package/dest/msg_validators/attestation_validator/index.d.ts +2 -0
  78. package/dest/msg_validators/attestation_validator/index.d.ts.map +1 -0
  79. package/dest/msg_validators/attestation_validator/index.js +1 -0
  80. package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts +8 -0
  81. package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts.map +1 -0
  82. package/dest/msg_validators/block_proposal_validator/block_proposal_validator.js +21 -0
  83. package/dest/msg_validators/block_proposal_validator/index.d.ts +2 -0
  84. package/dest/msg_validators/block_proposal_validator/index.d.ts.map +1 -0
  85. package/dest/msg_validators/block_proposal_validator/index.js +1 -0
  86. package/dest/msg_validators/index.d.ts +4 -0
  87. package/dest/msg_validators/index.d.ts.map +1 -0
  88. package/dest/msg_validators/index.js +3 -0
  89. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts +7 -0
  90. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -0
  91. package/dest/msg_validators/tx_validator/aggregate_tx_validator.js +31 -0
  92. package/dest/msg_validators/tx_validator/block_header_validator.d.ts +11 -0
  93. package/dest/msg_validators/tx_validator/block_header_validator.d.ts.map +1 -0
  94. package/dest/msg_validators/tx_validator/block_header_validator.js +26 -0
  95. package/dest/msg_validators/tx_validator/data_validator.d.ts +6 -0
  96. package/dest/msg_validators/tx_validator/data_validator.d.ts.map +1 -0
  97. package/dest/msg_validators/tx_validator/data_validator.js +107 -0
  98. package/dest/msg_validators/tx_validator/double_spend_validator.d.ts +12 -0
  99. package/dest/msg_validators/tx_validator/double_spend_validator.d.ts.map +1 -0
  100. package/dest/msg_validators/tx_validator/double_spend_validator.js +41 -0
  101. package/dest/msg_validators/tx_validator/index.d.ts +7 -0
  102. package/dest/msg_validators/tx_validator/index.d.ts.map +1 -0
  103. package/dest/msg_validators/tx_validator/index.js +6 -0
  104. package/dest/msg_validators/tx_validator/metadata_validator.d.ts +10 -0
  105. package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -0
  106. package/dest/msg_validators/tx_validator/metadata_validator.js +44 -0
  107. package/dest/msg_validators/tx_validator/tx_proof_validator.d.ts +9 -0
  108. package/dest/msg_validators/tx_validator/tx_proof_validator.d.ts.map +1 -0
  109. package/dest/msg_validators/tx_validator/tx_proof_validator.js +25 -0
  110. package/dest/services/data_store.d.ts +27 -0
  111. package/dest/services/data_store.d.ts.map +1 -0
  112. package/dest/services/data_store.js +188 -0
  113. package/dest/services/discv5/discV5_service.d.ts +42 -0
  114. package/dest/services/discv5/discV5_service.d.ts.map +1 -0
  115. package/dest/services/discv5/discV5_service.js +214 -0
  116. package/dest/services/dummy_service.d.ts +85 -0
  117. package/dest/services/dummy_service.d.ts.map +1 -0
  118. package/dest/services/dummy_service.js +92 -0
  119. package/dest/services/encoding.d.ts +31 -0
  120. package/dest/services/encoding.d.ts.map +1 -0
  121. package/dest/services/encoding.js +66 -0
  122. package/dest/services/gossipsub/scoring.d.ts +7 -0
  123. package/dest/services/gossipsub/scoring.d.ts.map +1 -0
  124. package/dest/services/gossipsub/scoring.js +10 -0
  125. package/dest/services/index.d.ts +3 -0
  126. package/dest/services/index.d.ts.map +1 -0
  127. package/dest/services/index.js +2 -0
  128. package/dest/services/libp2p/libp2p_service.d.ts +186 -0
  129. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -0
  130. package/dest/services/libp2p/libp2p_service.js +712 -0
  131. package/dest/services/peer-manager/metrics.d.ts +12 -0
  132. package/dest/services/peer-manager/metrics.d.ts.map +1 -0
  133. package/dest/services/peer-manager/metrics.js +33 -0
  134. package/dest/services/peer-manager/peer_manager.d.ts +94 -0
  135. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -0
  136. package/dest/services/peer-manager/peer_manager.js +445 -0
  137. package/dest/services/peer-manager/peer_scoring.d.ts +28 -0
  138. package/dest/services/peer-manager/peer_scoring.d.ts.map +1 -0
  139. package/dest/services/peer-manager/peer_scoring.js +86 -0
  140. package/dest/services/reqresp/config.d.ts +16 -0
  141. package/dest/services/reqresp/config.d.ts.map +1 -0
  142. package/dest/services/reqresp/config.js +20 -0
  143. package/dest/services/reqresp/connection-sampler/batch_connection_sampler.d.ts +45 -0
  144. package/dest/services/reqresp/connection-sampler/batch_connection_sampler.d.ts.map +1 -0
  145. package/dest/services/reqresp/connection-sampler/batch_connection_sampler.js +88 -0
  146. package/dest/services/reqresp/connection-sampler/connection_sampler.d.ts +61 -0
  147. package/dest/services/reqresp/connection-sampler/connection_sampler.d.ts.map +1 -0
  148. package/dest/services/reqresp/connection-sampler/connection_sampler.js +181 -0
  149. package/dest/services/reqresp/index.d.ts +6 -0
  150. package/dest/services/reqresp/index.d.ts.map +1 -0
  151. package/dest/services/reqresp/index.js +4 -0
  152. package/dest/services/reqresp/interface.d.ts +116 -0
  153. package/dest/services/reqresp/interface.d.ts.map +1 -0
  154. package/dest/services/reqresp/interface.js +84 -0
  155. package/dest/services/reqresp/metrics.d.ts +15 -0
  156. package/dest/services/reqresp/metrics.d.ts.map +1 -0
  157. package/dest/services/reqresp/metrics.js +55 -0
  158. package/dest/services/reqresp/protocols/block.d.ts +4 -0
  159. package/dest/services/reqresp/protocols/block.d.ts.map +1 -0
  160. package/dest/services/reqresp/protocols/block.js +8 -0
  161. package/dest/services/reqresp/protocols/goodbye.d.ts +51 -0
  162. package/dest/services/reqresp/protocols/goodbye.d.ts.map +1 -0
  163. package/dest/services/reqresp/protocols/goodbye.js +87 -0
  164. package/dest/services/reqresp/protocols/index.d.ts +9 -0
  165. package/dest/services/reqresp/protocols/index.d.ts.map +1 -0
  166. package/dest/services/reqresp/protocols/index.js +7 -0
  167. package/dest/services/reqresp/protocols/ping.d.ts +9 -0
  168. package/dest/services/reqresp/protocols/ping.d.ts.map +1 -0
  169. package/dest/services/reqresp/protocols/ping.js +7 -0
  170. package/dest/services/reqresp/protocols/status.d.ts +9 -0
  171. package/dest/services/reqresp/protocols/status.d.ts.map +1 -0
  172. package/dest/services/reqresp/protocols/status.js +7 -0
  173. package/dest/services/reqresp/protocols/tx.d.ts +13 -0
  174. package/dest/services/reqresp/protocols/tx.d.ts.map +1 -0
  175. package/dest/services/reqresp/protocols/tx.js +20 -0
  176. package/dest/services/reqresp/rate-limiter/index.d.ts +2 -0
  177. package/dest/services/reqresp/rate-limiter/index.d.ts.map +1 -0
  178. package/dest/services/reqresp/rate-limiter/index.js +1 -0
  179. package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts +102 -0
  180. package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts.map +1 -0
  181. package/dest/services/reqresp/rate-limiter/rate_limiter.js +184 -0
  182. package/dest/services/reqresp/rate-limiter/rate_limits.d.ts +3 -0
  183. package/dest/services/reqresp/rate-limiter/rate_limits.d.ts.map +1 -0
  184. package/dest/services/reqresp/rate-limiter/rate_limits.js +54 -0
  185. package/dest/services/reqresp/reqresp.d.ts +166 -0
  186. package/dest/services/reqresp/reqresp.d.ts.map +1 -0
  187. package/dest/services/reqresp/reqresp.js +516 -0
  188. package/dest/services/reqresp/status.d.ts +31 -0
  189. package/dest/services/reqresp/status.d.ts.map +1 -0
  190. package/dest/services/reqresp/status.js +51 -0
  191. package/dest/services/service.d.ts +87 -0
  192. package/dest/services/service.d.ts.map +1 -0
  193. package/dest/services/service.js +5 -0
  194. package/dest/test-helpers/generate-peer-id-private-keys.d.ts +7 -0
  195. package/dest/test-helpers/generate-peer-id-private-keys.d.ts.map +1 -0
  196. package/dest/test-helpers/generate-peer-id-private-keys.js +13 -0
  197. package/dest/test-helpers/get-ports.d.ts +7 -0
  198. package/dest/test-helpers/get-ports.d.ts.map +1 -0
  199. package/dest/test-helpers/get-ports.js +8 -0
  200. package/dest/test-helpers/index.d.ts +6 -0
  201. package/dest/test-helpers/index.d.ts.map +1 -0
  202. package/dest/test-helpers/index.js +5 -0
  203. package/dest/test-helpers/make-enrs.d.ts +16 -0
  204. package/dest/test-helpers/make-enrs.d.ts.map +1 -0
  205. package/dest/test-helpers/make-enrs.js +32 -0
  206. package/dest/test-helpers/make-test-p2p-clients.d.ts +36 -0
  207. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -0
  208. package/dest/test-helpers/make-test-p2p-clients.js +68 -0
  209. package/dest/test-helpers/reqresp-nodes.d.ts +66 -0
  210. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -0
  211. package/dest/test-helpers/reqresp-nodes.js +207 -0
  212. package/dest/testbench/p2p_client_testbench_worker.d.ts +2 -0
  213. package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -0
  214. package/dest/testbench/p2p_client_testbench_worker.js +132 -0
  215. package/dest/testbench/parse_log_file.d.ts +2 -0
  216. package/dest/testbench/parse_log_file.d.ts.map +1 -0
  217. package/dest/testbench/parse_log_file.js +131 -0
  218. package/dest/testbench/testbench.d.ts +2 -0
  219. package/dest/testbench/testbench.d.ts.map +1 -0
  220. package/dest/testbench/testbench.js +61 -0
  221. package/dest/testbench/worker_client_manager.d.ts +56 -0
  222. package/dest/testbench/worker_client_manager.d.ts.map +1 -0
  223. package/dest/testbench/worker_client_manager.js +266 -0
  224. package/dest/types/index.d.ts +32 -0
  225. package/dest/types/index.d.ts.map +1 -0
  226. package/dest/types/index.js +28 -0
  227. package/dest/util.d.ts +53 -0
  228. package/dest/util.d.ts.map +1 -0
  229. package/dest/util.js +140 -0
  230. package/dest/versioning.d.ts +12 -0
  231. package/dest/versioning.d.ts.map +1 -0
  232. package/dest/versioning.js +33 -0
  233. package/package.json +127 -0
  234. package/src/bootstrap/bootstrap.ts +146 -0
  235. package/src/client/factory.ts +89 -0
  236. package/src/client/index.ts +2 -0
  237. package/src/client/p2p_client.ts +754 -0
  238. package/src/config.ts +371 -0
  239. package/src/enr/generate-enr.ts +39 -0
  240. package/src/enr/index.ts +1 -0
  241. package/src/errors/reqresp.error.ts +35 -0
  242. package/src/index.ts +7 -0
  243. package/src/mem_pools/attestation_pool/attestation_pool.ts +62 -0
  244. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +230 -0
  245. package/src/mem_pools/attestation_pool/index.ts +2 -0
  246. package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +159 -0
  247. package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +161 -0
  248. package/src/mem_pools/attestation_pool/mocks.ts +44 -0
  249. package/src/mem_pools/index.ts +3 -0
  250. package/src/mem_pools/instrumentation.ts +126 -0
  251. package/src/mem_pools/interface.ts +12 -0
  252. package/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +309 -0
  253. package/src/mem_pools/tx_pool/index.ts +3 -0
  254. package/src/mem_pools/tx_pool/memory_tx_pool.ts +174 -0
  255. package/src/mem_pools/tx_pool/priority.ts +13 -0
  256. package/src/mem_pools/tx_pool/tx_pool.ts +76 -0
  257. package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +130 -0
  258. package/src/msg_validators/attestation_validator/attestation_validator.ts +26 -0
  259. package/src/msg_validators/attestation_validator/index.ts +1 -0
  260. package/src/msg_validators/block_proposal_validator/block_proposal_validator.ts +29 -0
  261. package/src/msg_validators/block_proposal_validator/index.ts +1 -0
  262. package/src/msg_validators/index.ts +3 -0
  263. package/src/msg_validators/tx_validator/aggregate_tx_validator.ts +32 -0
  264. package/src/msg_validators/tx_validator/block_header_validator.ts +25 -0
  265. package/src/msg_validators/tx_validator/data_validator.ts +106 -0
  266. package/src/msg_validators/tx_validator/double_spend_validator.ts +38 -0
  267. package/src/msg_validators/tx_validator/index.ts +6 -0
  268. package/src/msg_validators/tx_validator/metadata_validator.ts +48 -0
  269. package/src/msg_validators/tx_validator/tx_proof_validator.ts +18 -0
  270. package/src/services/data_store.ts +235 -0
  271. package/src/services/discv5/discV5_service.ts +256 -0
  272. package/src/services/dummy_service.ts +134 -0
  273. package/src/services/encoding.ts +79 -0
  274. package/src/services/gossipsub/scoring.ts +13 -0
  275. package/src/services/index.ts +2 -0
  276. package/src/services/libp2p/libp2p_service.ts +871 -0
  277. package/src/services/peer-manager/metrics.ts +41 -0
  278. package/src/services/peer-manager/peer_manager.ts +530 -0
  279. package/src/services/peer-manager/peer_scoring.ts +105 -0
  280. package/src/services/reqresp/config.ts +35 -0
  281. package/src/services/reqresp/connection-sampler/batch_connection_sampler.ts +94 -0
  282. package/src/services/reqresp/connection-sampler/connection_sampler.ts +217 -0
  283. package/src/services/reqresp/index.ts +4 -0
  284. package/src/services/reqresp/interface.ts +185 -0
  285. package/src/services/reqresp/metrics.ts +57 -0
  286. package/src/services/reqresp/protocols/block.ts +15 -0
  287. package/src/services/reqresp/protocols/goodbye.ts +101 -0
  288. package/src/services/reqresp/protocols/index.ts +8 -0
  289. package/src/services/reqresp/protocols/ping.ts +8 -0
  290. package/src/services/reqresp/protocols/status.ts +8 -0
  291. package/src/services/reqresp/protocols/tx.ts +29 -0
  292. package/src/services/reqresp/rate-limiter/index.ts +1 -0
  293. package/src/services/reqresp/rate-limiter/rate_limiter.ts +228 -0
  294. package/src/services/reqresp/rate-limiter/rate_limits.ts +55 -0
  295. package/src/services/reqresp/reqresp.ts +661 -0
  296. package/src/services/reqresp/status.ts +59 -0
  297. package/src/services/service.ts +112 -0
  298. package/src/test-helpers/generate-peer-id-private-keys.ts +15 -0
  299. package/src/test-helpers/get-ports.ts +8 -0
  300. package/src/test-helpers/index.ts +5 -0
  301. package/src/test-helpers/make-enrs.ts +44 -0
  302. package/src/test-helpers/make-test-p2p-clients.ts +122 -0
  303. package/src/test-helpers/reqresp-nodes.ts +289 -0
  304. package/src/testbench/README.md +20 -0
  305. package/src/testbench/p2p_client_testbench_worker.ts +152 -0
  306. package/src/testbench/parse_log_file.ts +175 -0
  307. package/src/testbench/testbench.ts +66 -0
  308. package/src/testbench/worker_client_manager.ts +318 -0
  309. package/src/types/index.ts +36 -0
  310. package/src/util.ts +196 -0
  311. package/src/versioning.ts +50 -0
@@ -0,0 +1,41 @@
1
+ import {
2
+ Attributes,
3
+ Metrics,
4
+ type TelemetryClient,
5
+ type Tracer,
6
+ type UpDownCounter,
7
+ ValueType,
8
+ } from '@aztec/telemetry-client';
9
+
10
+ import { type GoodByeReason, prettyGoodbyeReason } from '../reqresp/protocols/index.js';
11
+
12
+ export class PeerManagerMetrics {
13
+ private sentGoodbyes: UpDownCounter;
14
+ private receivedGoodbyes: UpDownCounter;
15
+
16
+ public readonly tracer: Tracer;
17
+
18
+ constructor(public readonly telemetryClient: TelemetryClient, name = 'PeerManager') {
19
+ this.tracer = telemetryClient.getTracer(name);
20
+
21
+ const meter = telemetryClient.getMeter(name);
22
+ this.sentGoodbyes = meter.createUpDownCounter(Metrics.PEER_MANAGER_GOODBYES_SENT, {
23
+ description: 'Number of goodbyes sent to peers',
24
+ unit: 'peers',
25
+ valueType: ValueType.INT,
26
+ });
27
+ this.receivedGoodbyes = meter.createUpDownCounter(Metrics.PEER_MANAGER_GOODBYES_RECEIVED, {
28
+ description: 'Number of goodbyes received from peers',
29
+ unit: 'peers',
30
+ valueType: ValueType.INT,
31
+ });
32
+ }
33
+
34
+ public recordGoodbyeSent(reason: GoodByeReason) {
35
+ this.sentGoodbyes.add(1, { [Attributes.P2P_GOODBYE_REASON]: prettyGoodbyeReason(reason) });
36
+ }
37
+
38
+ public recordGoodbyeReceived(reason: GoodByeReason) {
39
+ this.receivedGoodbyes.add(1, { [Attributes.P2P_GOODBYE_REASON]: prettyGoodbyeReason(reason) });
40
+ }
41
+ }
@@ -0,0 +1,530 @@
1
+ import { createLogger } from '@aztec/foundation/log';
2
+ import type { PeerInfo } from '@aztec/stdlib/interfaces/server';
3
+ import type { PeerErrorSeverity } from '@aztec/stdlib/p2p';
4
+ import { type TelemetryClient, trackSpan } from '@aztec/telemetry-client';
5
+
6
+ import type { ENR } from '@chainsafe/enr';
7
+ import type { Connection, PeerId } from '@libp2p/interface';
8
+ import type { Multiaddr } from '@multiformats/multiaddr';
9
+ import { inspect } from 'util';
10
+
11
+ import type { P2PConfig } from '../../config.js';
12
+ import { PeerEvent } from '../../types/index.js';
13
+ import type { PubSubLibp2p } from '../../util.js';
14
+ import { ReqRespSubProtocol } from '../reqresp/interface.js';
15
+ import { GoodByeReason, prettyGoodbyeReason } from '../reqresp/protocols/goodbye.js';
16
+ import type { ReqResp } from '../reqresp/reqresp.js';
17
+ import type { PeerDiscoveryService } from '../service.js';
18
+ import { PeerManagerMetrics } from './metrics.js';
19
+ import { PeerScoreState, type PeerScoring } from './peer_scoring.js';
20
+
21
+ const MAX_DIAL_ATTEMPTS = 3;
22
+ const MAX_CACHED_PEERS = 100;
23
+ const MAX_CACHED_PEER_AGE_MS = 5 * 60 * 1000; // 5 minutes
24
+ const FAILED_PEER_BAN_TIME_MS = 5 * 60 * 1000; // 5 minutes timeout after failing MAX_DIAL_ATTEMPTS
25
+
26
+ type CachedPeer = {
27
+ peerId: PeerId;
28
+ enr: ENR;
29
+ multiaddrTcp: Multiaddr;
30
+ dialAttempts: number;
31
+ addedUnixMs: number;
32
+ };
33
+
34
+ type TimedOutPeer = {
35
+ peerId: string;
36
+ timeoutUntilMs: number;
37
+ };
38
+
39
+ export class PeerManager {
40
+ private cachedPeers: Map<string, CachedPeer> = new Map();
41
+ private heartbeatCounter: number = 0;
42
+ private displayPeerCountsPeerHeartbeat: number = 0;
43
+ private timedOutPeers: Map<string, TimedOutPeer> = new Map();
44
+
45
+ private metrics: PeerManagerMetrics;
46
+ private discoveredPeerHandler;
47
+
48
+ constructor(
49
+ private libP2PNode: PubSubLibp2p,
50
+ private peerDiscoveryService: PeerDiscoveryService,
51
+ private config: P2PConfig,
52
+ telemetryClient: TelemetryClient,
53
+ private logger = createLogger('p2p:peer-manager'),
54
+ private peerScoring: PeerScoring,
55
+ private reqresp: ReqResp,
56
+ ) {
57
+ this.metrics = new PeerManagerMetrics(telemetryClient, 'PeerManager');
58
+
59
+ // Handle new established connections
60
+ this.libP2PNode.addEventListener(PeerEvent.CONNECTED, this.handleConnectedPeerEvent.bind(this));
61
+ // Handle lost connections
62
+ this.libP2PNode.addEventListener(PeerEvent.DISCONNECTED, this.handleDisconnectedPeerEvent.bind(this));
63
+
64
+ // Handle Discovered peers
65
+ this.discoveredPeerHandler = (enr: ENR) =>
66
+ this.handleDiscoveredPeer(enr).catch(e => this.logger.error('Error handling discovered peer', e));
67
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
68
+ this.peerDiscoveryService.on(PeerEvent.DISCOVERED, this.discoveredPeerHandler);
69
+
70
+ // Display peer counts every 60 seconds
71
+ this.displayPeerCountsPeerHeartbeat = Math.floor(60_000 / this.config.peerCheckIntervalMS);
72
+ }
73
+
74
+ get tracer() {
75
+ return this.metrics.tracer;
76
+ }
77
+
78
+ @trackSpan('PeerManager.heartbeat')
79
+ public heartbeat() {
80
+ this.heartbeatCounter++;
81
+ this.peerScoring.decayAllScores();
82
+
83
+ this.cleanupExpiredTimeouts();
84
+
85
+ this.discover();
86
+ }
87
+
88
+ /**
89
+ * Cleans up expired timeouts.
90
+ *
91
+ * When peers fail to dial after a number of retries, they are temporarily timed out.
92
+ * This function removes any peers that have been in the timed out state for too long.
93
+ * To give them a chance to reconnect.
94
+ */
95
+ private cleanupExpiredTimeouts() {
96
+ // Clean up expired timeouts
97
+ const now = Date.now();
98
+ for (const [peerId, timedOutPeer] of this.timedOutPeers.entries()) {
99
+ if (now >= timedOutPeer.timeoutUntilMs) {
100
+ this.timedOutPeers.delete(peerId);
101
+ }
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Simply logs the type of connected peer.
107
+ * @param e - The connected peer event.
108
+ */
109
+ private handleConnectedPeerEvent(e: CustomEvent<PeerId>) {
110
+ const peerId = e.detail;
111
+ if (this.peerDiscoveryService.isBootstrapPeer(peerId)) {
112
+ this.logger.verbose(`Connected to bootstrap peer ${peerId.toString()}`);
113
+ } else {
114
+ this.logger.verbose(`Connected to transaction peer ${peerId.toString()}`);
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Simply logs the type of disconnected peer.
120
+ * @param e - The disconnected peer event.
121
+ */
122
+ private handleDisconnectedPeerEvent(e: CustomEvent<PeerId>) {
123
+ const peerId = e.detail;
124
+ if (this.peerDiscoveryService.isBootstrapPeer(peerId)) {
125
+ this.logger.verbose(`Disconnected from bootstrap peer ${peerId.toString()}`);
126
+ } else {
127
+ this.logger.verbose(`Disconnected from transaction peer ${peerId.toString()}`);
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Handles a goodbye received from a peer.
133
+ *
134
+ * Used as the reqresp handler when a peer sends us goodbye message.
135
+ * @param peerId - The peer ID.
136
+ * @param reason - The reason for the goodbye.
137
+ */
138
+ public goodbyeReceived(peerId: PeerId, reason: GoodByeReason) {
139
+ this.logger.debug(`Goodbye received from peer ${peerId.toString()} with reason ${prettyGoodbyeReason(reason)}`);
140
+
141
+ this.metrics.recordGoodbyeReceived(reason);
142
+
143
+ void this.disconnectPeer(peerId);
144
+ }
145
+
146
+ public penalizePeer(peerId: PeerId, penalty: PeerErrorSeverity) {
147
+ this.peerScoring.penalizePeer(peerId, penalty);
148
+ }
149
+
150
+ public getPeerScore(peerId: string): number {
151
+ return this.peerScoring.getScore(peerId);
152
+ }
153
+
154
+ public getPeers(includePending = false): PeerInfo[] {
155
+ const connected = this.libP2PNode
156
+ .getPeers()
157
+ .map(peer => ({ id: peer.toString(), score: this.getPeerScore(peer.toString()), status: 'connected' as const }));
158
+
159
+ if (!includePending) {
160
+ return connected;
161
+ }
162
+
163
+ const dialQueue = this.libP2PNode
164
+ .getDialQueue()
165
+ .filter(peer => !!peer.peerId)
166
+ .map(peer => ({
167
+ id: peer.peerId!.toString(),
168
+ status: 'dialing' as const,
169
+ dialStatus: peer.status,
170
+ addresses: peer.multiaddrs.map(m => m.toString()),
171
+ }));
172
+
173
+ const cachedPeers = Array.from(this.cachedPeers.values())
174
+ .filter(peer => !dialQueue.some(dialPeer => dialPeer.id && peer.peerId.toString() === dialPeer.id.toString()))
175
+ .filter(peer => !connected.some(connPeer => connPeer.id.toString() === peer.peerId.toString()))
176
+ .map(peer => ({
177
+ status: 'cached' as const,
178
+ id: peer.peerId.toString(),
179
+ addresses: [peer.multiaddrTcp.toString()],
180
+ dialAttempts: peer.dialAttempts,
181
+ enr: peer.enr.encodeTxt(),
182
+ }));
183
+
184
+ return [...connected, ...dialQueue, ...cachedPeers];
185
+ }
186
+
187
+ /**
188
+ * Discovers peers.
189
+ */
190
+ private discover() {
191
+ const connections = this.libP2PNode.getConnections();
192
+
193
+ const healthyConnections = this.prioritizePeers(this.pruneUnhealthyPeers(this.pruneDuplicatePeers(connections)));
194
+
195
+ // Calculate how many connections we're looking to make
196
+ const peersToConnect = this.config.maxPeerCount - healthyConnections.length;
197
+
198
+ const logLevel = this.heartbeatCounter % this.displayPeerCountsPeerHeartbeat === 0 ? 'info' : 'debug';
199
+ this.logger[logLevel](`Connected to ${healthyConnections.length} peers`, {
200
+ connections: healthyConnections.length,
201
+ maxPeerCount: this.config.maxPeerCount,
202
+ cachedPeers: this.cachedPeers.size,
203
+ ...this.peerScoring.getStats(),
204
+ });
205
+
206
+ // Exit if no peers to connect
207
+ if (peersToConnect <= 0) {
208
+ return;
209
+ }
210
+
211
+ const cachedPeersToDial: CachedPeer[] = [];
212
+
213
+ const pendingDials = new Set(
214
+ this.libP2PNode
215
+ .getDialQueue()
216
+ .map(pendingDial => pendingDial.peerId?.toString())
217
+ .filter(Boolean) as string[],
218
+ );
219
+
220
+ for (const [id, peerData] of this.cachedPeers.entries()) {
221
+ // if already dialling or connected to, remove from cache
222
+ if (
223
+ pendingDials.has(id) ||
224
+ healthyConnections.some(conn => conn.remotePeer.equals(peerData.peerId)) ||
225
+ // if peer has been in cache for the max cache age, remove from cache
226
+ Date.now() - peerData.addedUnixMs > MAX_CACHED_PEER_AGE_MS
227
+ ) {
228
+ this.cachedPeers.delete(id);
229
+ } else {
230
+ // cachedPeersToDial.set(id, enr);
231
+ cachedPeersToDial.push(peerData);
232
+ }
233
+ }
234
+
235
+ // reverse to dial older entries first
236
+ cachedPeersToDial.reverse();
237
+
238
+ for (const peer of cachedPeersToDial) {
239
+ // We remove from the cache before, as dialling will add it back if it fails
240
+ this.cachedPeers.delete(peer.peerId.toString());
241
+ void this.dialPeer(peer);
242
+ }
243
+
244
+ // if we need more peers, start randomNodesQuery
245
+ if (peersToConnect > 0) {
246
+ this.logger.trace(`Running random nodes query to connect to ${peersToConnect} peers`);
247
+ void this.peerDiscoveryService.runRandomNodesQuery();
248
+ }
249
+ }
250
+
251
+ private pruneUnhealthyPeers(connections: Connection[]): Connection[] {
252
+ const connectedHealthyPeers: Connection[] = [];
253
+
254
+ for (const peer of connections) {
255
+ const score = this.peerScoring.getScoreState(peer.remotePeer.toString());
256
+ switch (score) {
257
+ case PeerScoreState.Banned:
258
+ void this.goodbyeAndDisconnectPeer(peer.remotePeer, GoodByeReason.BANNED);
259
+ break;
260
+ case PeerScoreState.Disconnect:
261
+ void this.goodbyeAndDisconnectPeer(peer.remotePeer, GoodByeReason.LOW_SCORE);
262
+ break;
263
+ case PeerScoreState.Healthy:
264
+ connectedHealthyPeers.push(peer);
265
+ }
266
+ }
267
+
268
+ return connectedHealthyPeers;
269
+ }
270
+
271
+ /**
272
+ * If the max peer count is reached, the lowest scoring peers will be pruned to satisfy the max peer count.
273
+ *
274
+ * @param connections - The list of connections to prune low scoring peers above the max peer count from.
275
+ * @returns The pruned list of connections.
276
+ */
277
+ private prioritizePeers(connections: Connection[]): Connection[] {
278
+ if (connections.length > this.config.maxPeerCount) {
279
+ // Sort the peer scores from lowest to highest
280
+ const prioritizedConnections = connections.sort((connectionA, connectionB) => {
281
+ const connectionScoreA = this.peerScoring.getScore(connectionA.remotePeer.toString());
282
+ const connectionScoreB = this.peerScoring.getScore(connectionB.remotePeer.toString());
283
+ return connectionScoreB - connectionScoreA;
284
+ });
285
+
286
+ // Disconnect from the lowest scoring connections.
287
+ for (const conn of prioritizedConnections.slice(this.config.maxPeerCount)) {
288
+ void this.goodbyeAndDisconnectPeer(conn.remotePeer, GoodByeReason.MAX_PEERS);
289
+ }
290
+ return prioritizedConnections.slice(0, this.config.maxPeerCount);
291
+ } else {
292
+ return connections;
293
+ }
294
+ }
295
+
296
+ /**
297
+ * If multiple connections to the same peer are found, the oldest connection is kept and the duplicates are pruned.
298
+ *
299
+ * This is necessary to resolve a race condition where multiple connections to the same peer are established if
300
+ * they are discovered at the same time.
301
+ *
302
+ * @param connections - The list of connections to prune duplicate peers from.
303
+ * @returns The pruned list of connections.
304
+ */
305
+ private pruneDuplicatePeers(connections: Connection[]): Connection[] {
306
+ const peerConnections = new Map<string, Connection>();
307
+
308
+ for (const conn of connections) {
309
+ const peerId = conn.remotePeer.toString();
310
+ const existingConnection = peerConnections.get(peerId);
311
+ if (!existingConnection) {
312
+ peerConnections.set(peerId, conn);
313
+ } else {
314
+ // Keep the oldest connection for each peer
315
+ this.logger.debug(`Found duplicate connection to peer ${peerId}, keeping oldest connection`);
316
+ if (conn.timeline.open < existingConnection.timeline.open) {
317
+ peerConnections.set(peerId, conn);
318
+ void existingConnection.close();
319
+ } else {
320
+ void conn.close();
321
+ }
322
+ }
323
+ }
324
+
325
+ return [...peerConnections.values()];
326
+ }
327
+
328
+ private async goodbyeAndDisconnectPeer(peer: PeerId, reason: GoodByeReason) {
329
+ this.logger.debug(`Disconnecting peer ${peer.toString()} with reason ${prettyGoodbyeReason(reason)}`);
330
+
331
+ this.metrics.recordGoodbyeSent(reason);
332
+
333
+ try {
334
+ await this.reqresp.sendRequestToPeer(peer, ReqRespSubProtocol.GOODBYE, Buffer.from([reason]));
335
+ } catch (error) {
336
+ this.logger.debug(`Failed to send goodbye to peer ${peer.toString()}: ${error}`);
337
+ } finally {
338
+ await this.disconnectPeer(peer);
339
+ }
340
+ }
341
+
342
+ private async disconnectPeer(peer: PeerId) {
343
+ try {
344
+ await this.libP2PNode.hangUp(peer);
345
+ } catch (error) {
346
+ this.logger.debug(`Failed to disconnect peer ${peer.toString()}`, { error: inspect(error) });
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Handles a discovered peer.
352
+ * @param enr - The discovered peer's ENR.
353
+ */
354
+ private async handleDiscoveredPeer(enr: ENR) {
355
+ // Check that the peer has not already been banned
356
+ const peerId = await enr.peerId();
357
+ const peerIdString = peerId.toString();
358
+
359
+ // Check if peer is temporarily timed out
360
+ const timedOutPeer = this.timedOutPeers.get(peerIdString);
361
+ if (timedOutPeer) {
362
+ if (Date.now() < timedOutPeer.timeoutUntilMs) {
363
+ this.logger.trace(`Skipping timed out peer ${peerId}`);
364
+ return;
365
+ }
366
+ // Timeout period expired, remove from timed out peers
367
+ this.timedOutPeers.delete(peerIdString);
368
+ }
369
+
370
+ if (this.peerScoring.getScoreState(peerIdString) != PeerScoreState.Healthy) {
371
+ return;
372
+ }
373
+
374
+ const [multiaddrTcp] = await Promise.all([enr.getFullMultiaddr('tcp')]);
375
+
376
+ this.logger.trace(`Handling discovered peer ${peerId} at ${multiaddrTcp?.toString() ?? 'undefined address'}`);
377
+
378
+ // stop if no tcp addr in multiaddr
379
+ if (!multiaddrTcp) {
380
+ this.logger.debug(`No TCP address in discovered node's multiaddr ${enr.encodeTxt()}`);
381
+ return;
382
+ }
383
+ // check if peer is already connected
384
+ const connections = this.libP2PNode.getConnections();
385
+ if (connections.some((conn: Connection) => conn.remotePeer.equals(peerId))) {
386
+ this.logger.trace(`Already connected to peer ${peerId}`);
387
+ return;
388
+ }
389
+
390
+ // check if peer is already in cache
391
+ if (this.cachedPeers.has(peerIdString)) {
392
+ this.logger.trace(`Peer already in cache ${peerIdString}`);
393
+ return;
394
+ }
395
+
396
+ // create cached peer object
397
+ const cachedPeer: CachedPeer = {
398
+ peerId,
399
+ enr,
400
+ multiaddrTcp,
401
+ dialAttempts: 0,
402
+ addedUnixMs: Date.now(),
403
+ };
404
+
405
+ // Determine if we should dial immediately or not
406
+ if (this.shouldDialPeer()) {
407
+ void this.dialPeer(cachedPeer);
408
+ } else {
409
+ this.logger.trace(`Caching peer ${peerIdString}`);
410
+ this.cachedPeers.set(peerIdString, cachedPeer);
411
+ // Prune set of cached peers
412
+ this.pruneCachedPeers();
413
+ }
414
+ }
415
+
416
+ private async dialPeer(peer: CachedPeer) {
417
+ const id = peer.peerId.toString();
418
+
419
+ // Add to the address book before dialing
420
+ await this.libP2PNode.peerStore.merge(peer.peerId, { multiaddrs: [peer.multiaddrTcp] });
421
+
422
+ this.logger.trace(`Dialing peer ${id}`);
423
+ try {
424
+ await this.libP2PNode.dial(peer.multiaddrTcp);
425
+ } catch (error) {
426
+ peer.dialAttempts++;
427
+ if (peer.dialAttempts < MAX_DIAL_ATTEMPTS) {
428
+ this.logger.trace(`Failed to dial peer ${id} (attempt ${peer.dialAttempts})`, { error: inspect(error) });
429
+ this.cachedPeers.set(id, peer);
430
+ } else {
431
+ formatLibp2pDialError(error as Error);
432
+ this.logger.debug(`Failed to dial peer ${id} (dropping)`, { error: inspect(error) });
433
+ this.cachedPeers.delete(id);
434
+ // Add to timed out peers
435
+ this.timedOutPeers.set(id, {
436
+ peerId: id,
437
+ timeoutUntilMs: Date.now() + FAILED_PEER_BAN_TIME_MS,
438
+ });
439
+ }
440
+ }
441
+ }
442
+
443
+ private shouldDialPeer(): boolean {
444
+ const connections = this.libP2PNode.getConnections().length;
445
+ if (connections >= this.config.maxPeerCount) {
446
+ this.logger.trace(
447
+ `Not dialing peer due to max peer count of ${this.config.maxPeerCount} reached (${connections} current connections)`,
448
+ );
449
+ return false;
450
+ }
451
+ return true;
452
+ }
453
+
454
+ private pruneCachedPeers() {
455
+ let peersToDelete = this.cachedPeers.size - MAX_CACHED_PEERS;
456
+ if (peersToDelete <= 0) {
457
+ return;
458
+ }
459
+
460
+ // Remove the oldest peers
461
+ for (const key of this.cachedPeers.keys()) {
462
+ this.cachedPeers.delete(key);
463
+ this.logger.trace(`Pruning peer ${key} from cache`);
464
+ peersToDelete--;
465
+ if (peersToDelete <= 0) {
466
+ break;
467
+ }
468
+ }
469
+ }
470
+
471
+ /**
472
+ * Stops the peer manager.
473
+ * Removing all event listeners.
474
+ */
475
+ public async stop() {
476
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
477
+ this.peerDiscoveryService.off(PeerEvent.DISCOVERED, this.discoveredPeerHandler);
478
+
479
+ // Send goodbyes to all peers
480
+ await Promise.all(
481
+ this.libP2PNode.getPeers().map(peer => this.goodbyeAndDisconnectPeer(peer, GoodByeReason.SHUTDOWN)),
482
+ );
483
+
484
+ this.libP2PNode.removeEventListener(PeerEvent.CONNECTED, this.handleConnectedPeerEvent);
485
+ this.libP2PNode.removeEventListener(PeerEvent.DISCONNECTED, this.handleDisconnectedPeerEvent);
486
+ }
487
+ }
488
+
489
+ /**
490
+ * copied from github.com/ChainSafe/lodestar
491
+ * libp2p errors with extremely noisy errors here, which are deeply nested taking 30-50 lines.
492
+ * Some known errors:
493
+ * ```
494
+ * Error: The operation was aborted
495
+ * Error: stream ended before 1 bytes became available
496
+ * Error: Error occurred during XX handshake: Error occurred while verifying signed payload: Peer ID doesn't match libp2p public key
497
+ * ```
498
+ *
499
+ * Also the error's message is not properly formatted, where the error message is indented and includes the full stack
500
+ * ```
501
+ * {
502
+ * emessage: '\n' +
503
+ * ' Error: stream ended before 1 bytes became available\n' +
504
+ * ' at /home/lion/Code/eth2.0/lodestar/node_modules/it-reader/index.js:37:9\n' +
505
+ * ' at runMicrotasks (<anonymous>)\n' +
506
+ * ' at decoder (/home/lion/Code/eth2.0/lodestar/node_modules/it-length-prefixed/src/decode.js:113:22)\n' +
507
+ * ' at first (/home/lion/Code/eth2.0/lodestar/node_modules/it-first/index.js:11:20)\n' +
508
+ * ' at Object.exports.read (/home/lion/Code/eth2.0/lodestar/node_modules/multistream-select/src/multistream.js:31:15)\n' +
509
+ * ' at module.exports (/home/lion/Code/eth2.0/lodestar/node_modules/multistream-select/src/select.js:21:19)\n' +
510
+ * ' at Upgrader._encryptOutbound (/home/lion/Code/eth2.0/lodestar/node_modules/libp2p/src/upgrader.js:397:36)\n' +
511
+ * ' at Upgrader.upgradeOutbound (/home/lion/Code/eth2.0/lodestar/node_modules/libp2p/src/upgrader.js:176:11)\n' +
512
+ * ' at ClassIsWrapper.dial (/home/lion/Code/eth2.0/lodestar/node_modules/libp2p-tcp/src/index.js:49:18)'
513
+ * }
514
+ * ```
515
+ *
516
+ * Tracking issue https://github.com/libp2p/js-libp2p/issues/996
517
+ */
518
+ function formatLibp2pDialError(e: Error): void {
519
+ const errorMessage = e.message.trim();
520
+ const newlineIndex = errorMessage.indexOf('\n');
521
+ e.message = newlineIndex !== -1 ? errorMessage.slice(0, newlineIndex) : errorMessage;
522
+
523
+ if (
524
+ e.message.includes('The operation was aborted') ||
525
+ e.message.includes('stream ended before 1 bytes became available') ||
526
+ e.message.includes('The operation was aborted')
527
+ ) {
528
+ e.stack = undefined;
529
+ }
530
+ }
@@ -0,0 +1,105 @@
1
+ import { median } from '@aztec/foundation/collection';
2
+ import { createLogger } from '@aztec/foundation/log';
3
+ import { PeerErrorSeverity } from '@aztec/stdlib/p2p';
4
+
5
+ import type { PeerId } from '@libp2p/interface';
6
+
7
+ import type { P2PConfig } from '../../config.js';
8
+
9
+ const DefaultPeerPenalties = {
10
+ [PeerErrorSeverity.LowToleranceError]: 50,
11
+ [PeerErrorSeverity.MidToleranceError]: 10,
12
+ [PeerErrorSeverity.HighToleranceError]: 2,
13
+ };
14
+
15
+ export enum PeerScoreState {
16
+ Banned,
17
+ Disconnect,
18
+ Healthy,
19
+ }
20
+
21
+ // TODO: move into config / constants
22
+ const MIN_SCORE_BEFORE_BAN = -100;
23
+ const MIN_SCORE_BEFORE_DISCONNECT = -50;
24
+
25
+ export class PeerScoring {
26
+ private logger = createLogger('p2p:peer-scoring');
27
+ private scores: Map<string, number> = new Map();
28
+ private lastUpdateTime: Map<string, number> = new Map();
29
+ private decayInterval = 1000 * 60; // 1 minute
30
+ private decayFactor = 0.9;
31
+ peerPenalties: { [key in PeerErrorSeverity]: number };
32
+
33
+ constructor(config: P2PConfig) {
34
+ const orderedValues = config.peerPenaltyValues?.sort((a, b) => a - b);
35
+ this.peerPenalties = {
36
+ [PeerErrorSeverity.HighToleranceError]:
37
+ orderedValues?.[0] ?? DefaultPeerPenalties[PeerErrorSeverity.HighToleranceError],
38
+ [PeerErrorSeverity.MidToleranceError]:
39
+ orderedValues?.[1] ?? DefaultPeerPenalties[PeerErrorSeverity.MidToleranceError],
40
+ [PeerErrorSeverity.LowToleranceError]:
41
+ orderedValues?.[2] ?? DefaultPeerPenalties[PeerErrorSeverity.LowToleranceError],
42
+ };
43
+ }
44
+
45
+ public penalizePeer(peerId: PeerId, penalty: PeerErrorSeverity) {
46
+ const id = peerId.toString();
47
+ const penaltyValue = this.peerPenalties[penalty];
48
+ const newScore = this.updateScore(id, -penaltyValue);
49
+ this.logger.verbose(`Penalizing peer ${id} with ${penalty} (new score is ${newScore})`);
50
+ return newScore;
51
+ }
52
+
53
+ updateScore(peerId: string, scoreDelta: number): number {
54
+ const currentTime = Date.now();
55
+ const lastUpdate = this.lastUpdateTime.get(peerId) || currentTime;
56
+ const timePassed = currentTime - lastUpdate;
57
+ const decayPeriods = Math.floor(timePassed / this.decayInterval);
58
+
59
+ let currentScore = this.scores.get(peerId) || 0;
60
+
61
+ // Apply decay
62
+ currentScore *= Math.pow(this.decayFactor, decayPeriods);
63
+
64
+ // Apply new score delta
65
+ currentScore += scoreDelta;
66
+
67
+ this.scores.set(peerId, currentScore);
68
+ this.lastUpdateTime.set(peerId, currentTime);
69
+ return currentScore;
70
+ }
71
+
72
+ decayAllScores(): void {
73
+ const currentTime = Date.now();
74
+ for (const [peerId, lastUpdate] of this.lastUpdateTime.entries()) {
75
+ const timePassed = currentTime - lastUpdate;
76
+ const decayPeriods = Math.floor(timePassed / this.decayInterval);
77
+ if (decayPeriods > 0) {
78
+ let score = this.scores.get(peerId) || 0;
79
+ score *= Math.pow(this.decayFactor, decayPeriods);
80
+ this.scores.set(peerId, score);
81
+ this.lastUpdateTime.set(peerId, currentTime);
82
+ }
83
+ }
84
+ }
85
+
86
+ getScore(peerId: string): number {
87
+ return this.scores.get(peerId) || 0;
88
+ }
89
+
90
+ public getScoreState(peerId: string): PeerScoreState {
91
+ // TODO(#11329): permanently store banned peers?
92
+ const score = this.getScore(peerId);
93
+ if (score < MIN_SCORE_BEFORE_BAN) {
94
+ return PeerScoreState.Banned;
95
+ }
96
+ if (score < MIN_SCORE_BEFORE_DISCONNECT) {
97
+ return PeerScoreState.Disconnect;
98
+ }
99
+ return PeerScoreState.Healthy;
100
+ }
101
+
102
+ getStats(): { medianScore: number } {
103
+ return { medianScore: median(Array.from(this.scores.values())) ?? 0 };
104
+ }
105
+ }