@lodestar/beacon-node 1.47.0-dev.d2f4b96574 → 1.47.0-dev.d79d81f994

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 (377) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +47 -18
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/pool/index.js +25 -2
  6. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  7. package/lib/api/impl/beacon/state/utils.d.ts +2 -1
  8. package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
  9. package/lib/api/impl/beacon/state/utils.js.map +1 -1
  10. package/lib/api/impl/events/index.d.ts +1 -1
  11. package/lib/api/impl/events/index.d.ts.map +1 -1
  12. package/lib/api/impl/events/index.js +12 -3
  13. package/lib/api/impl/events/index.js.map +1 -1
  14. package/lib/api/impl/lodestar/index.d.ts.map +1 -1
  15. package/lib/api/impl/lodestar/index.js +12 -13
  16. package/lib/api/impl/lodestar/index.js.map +1 -1
  17. package/lib/api/impl/validator/index.d.ts.map +1 -1
  18. package/lib/api/impl/validator/index.js +238 -65
  19. package/lib/api/impl/validator/index.js.map +1 -1
  20. package/lib/api/impl/validator/utils.d.ts +5 -0
  21. package/lib/api/impl/validator/utils.d.ts.map +1 -1
  22. package/lib/api/impl/validator/utils.js +18 -12
  23. package/lib/api/impl/validator/utils.js.map +1 -1
  24. package/lib/api/rest/base.d.ts.map +1 -1
  25. package/lib/api/rest/base.js +7 -0
  26. package/lib/api/rest/base.js.map +1 -1
  27. package/lib/chain/archiveStore/archiveStore.js +1 -1
  28. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  29. package/lib/chain/archiveStore/historicalState/metrics.d.ts.map +1 -1
  30. package/lib/chain/archiveStore/historicalState/metrics.js +21 -0
  31. package/lib/chain/archiveStore/historicalState/metrics.js.map +1 -1
  32. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  33. package/lib/chain/blocks/importBlock.js +2 -7
  34. package/lib/chain/blocks/importBlock.js.map +1 -1
  35. package/lib/chain/blocks/importExecutionPayload.d.ts +3 -2
  36. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  37. package/lib/chain/blocks/importExecutionPayload.js +11 -9
  38. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  39. package/lib/chain/blocks/index.d.ts.map +1 -1
  40. package/lib/chain/blocks/index.js +15 -7
  41. package/lib/chain/blocks/index.js.map +1 -1
  42. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +2 -1
  43. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  44. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +6 -2
  45. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  46. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +14 -5
  47. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  48. package/lib/chain/blocks/payloadEnvelopeInput/types.js +5 -0
  49. package/lib/chain/blocks/payloadEnvelopeInput/types.js.map +1 -1
  50. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
  51. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +22 -12
  52. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
  53. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +2 -2
  54. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  55. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  56. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  57. package/lib/chain/bls/index.d.ts +1 -1
  58. package/lib/chain/bls/index.d.ts.map +1 -1
  59. package/lib/chain/bls/interface.d.ts +5 -5
  60. package/lib/chain/bls/interface.d.ts.map +1 -1
  61. package/lib/chain/bls/multithread/index.d.ts +5 -11
  62. package/lib/chain/bls/multithread/index.d.ts.map +1 -1
  63. package/lib/chain/bls/multithread/index.js +116 -101
  64. package/lib/chain/bls/multithread/index.js.map +1 -1
  65. package/lib/chain/bls/multithread/jobItem.d.ts +5 -19
  66. package/lib/chain/bls/multithread/jobItem.d.ts.map +1 -1
  67. package/lib/chain/bls/multithread/jobItem.js +10 -65
  68. package/lib/chain/bls/multithread/jobItem.js.map +1 -1
  69. package/lib/chain/bls/multithread/types.d.ts +29 -8
  70. package/lib/chain/bls/multithread/types.d.ts.map +1 -1
  71. package/lib/chain/bls/multithread/types.js +12 -0
  72. package/lib/chain/bls/multithread/types.js.map +1 -1
  73. package/lib/chain/bls/multithread/utils.d.ts +4 -0
  74. package/lib/chain/bls/multithread/utils.d.ts.map +1 -1
  75. package/lib/chain/bls/multithread/utils.js +10 -0
  76. package/lib/chain/bls/multithread/utils.js.map +1 -1
  77. package/lib/chain/bls/multithread/worker.js +49 -23
  78. package/lib/chain/bls/multithread/worker.js.map +1 -1
  79. package/lib/chain/bls/singleThread.d.ts +4 -10
  80. package/lib/chain/bls/singleThread.d.ts.map +1 -1
  81. package/lib/chain/bls/singleThread.js +8 -45
  82. package/lib/chain/bls/singleThread.js.map +1 -1
  83. package/lib/chain/bls/utils.d.ts +4 -4
  84. package/lib/chain/bls/utils.d.ts.map +1 -1
  85. package/lib/chain/bls/utils.js +18 -19
  86. package/lib/chain/bls/utils.js.map +1 -1
  87. package/lib/chain/builderCircuitBreaker.d.ts +6 -5
  88. package/lib/chain/builderCircuitBreaker.d.ts.map +1 -1
  89. package/lib/chain/builderCircuitBreaker.js +28 -15
  90. package/lib/chain/builderCircuitBreaker.js.map +1 -1
  91. package/lib/chain/chain.d.ts +10 -5
  92. package/lib/chain/chain.d.ts.map +1 -1
  93. package/lib/chain/chain.js +24 -9
  94. package/lib/chain/chain.js.map +1 -1
  95. package/lib/chain/errors/blockError.d.ts +2 -0
  96. package/lib/chain/errors/blockError.d.ts.map +1 -1
  97. package/lib/chain/errors/blockError.js +6 -1
  98. package/lib/chain/errors/blockError.js.map +1 -1
  99. package/lib/chain/errors/proposerPreferences.d.ts +6 -0
  100. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
  101. package/lib/chain/errors/proposerPreferences.js +1 -0
  102. package/lib/chain/errors/proposerPreferences.js.map +1 -1
  103. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  104. package/lib/chain/forkChoice/index.js +4 -0
  105. package/lib/chain/forkChoice/index.js.map +1 -1
  106. package/lib/chain/interface.d.ts +8 -4
  107. package/lib/chain/interface.d.ts.map +1 -1
  108. package/lib/chain/interface.js.map +1 -1
  109. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  110. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  111. package/lib/chain/opPools/attestationPool.d.ts +1 -1
  112. package/lib/chain/opPools/attestationPool.d.ts.map +1 -1
  113. package/lib/chain/opPools/attestationPool.js +1 -1
  114. package/lib/chain/opPools/attestationPool.js.map +1 -1
  115. package/lib/chain/opPools/deferredVoluntaryExitPool.d.ts +16 -0
  116. package/lib/chain/opPools/deferredVoluntaryExitPool.d.ts.map +1 -0
  117. package/lib/chain/opPools/deferredVoluntaryExitPool.js +57 -0
  118. package/lib/chain/opPools/deferredVoluntaryExitPool.js.map +1 -0
  119. package/lib/chain/opPools/executionPayloadBidPool.d.ts +7 -2
  120. package/lib/chain/opPools/executionPayloadBidPool.d.ts.map +1 -1
  121. package/lib/chain/opPools/executionPayloadBidPool.js +4 -4
  122. package/lib/chain/opPools/executionPayloadBidPool.js.map +1 -1
  123. package/lib/chain/opPools/index.d.ts +2 -1
  124. package/lib/chain/opPools/index.d.ts.map +1 -1
  125. package/lib/chain/opPools/index.js +1 -0
  126. package/lib/chain/opPools/index.js.map +1 -1
  127. package/lib/chain/opPools/payloadAttestationPool.js +3 -3
  128. package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
  129. package/lib/chain/opPools/syncCommitteeMessagePool.js +1 -1
  130. package/lib/chain/opPools/syncCommitteeMessagePool.js.map +1 -1
  131. package/lib/chain/opPools/syncContributionAndProofPool.js +1 -1
  132. package/lib/chain/opPools/syncContributionAndProofPool.js.map +1 -1
  133. package/lib/chain/opPools/utils.d.ts +1 -1
  134. package/lib/chain/opPools/utils.d.ts.map +1 -1
  135. package/lib/chain/opPools/utils.js +1 -1
  136. package/lib/chain/opPools/utils.js.map +1 -1
  137. package/lib/chain/options.d.ts +1 -1
  138. package/lib/chain/options.d.ts.map +1 -1
  139. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  140. package/lib/chain/prepareNextSlot.js +25 -7
  141. package/lib/chain/prepareNextSlot.js.map +1 -1
  142. package/lib/chain/produceBlock/computeNewStateRoot.d.ts.map +1 -1
  143. package/lib/chain/produceBlock/computeNewStateRoot.js +3 -25
  144. package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
  145. package/lib/chain/produceBlock/produceBlockBody.d.ts +2 -0
  146. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  147. package/lib/chain/produceBlock/produceBlockBody.js +7 -2
  148. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  149. package/lib/chain/regen/interface.d.ts +5 -2
  150. package/lib/chain/regen/interface.d.ts.map +1 -1
  151. package/lib/chain/regen/interface.js +3 -0
  152. package/lib/chain/regen/interface.js.map +1 -1
  153. package/lib/chain/regen/queued.d.ts +2 -2
  154. package/lib/chain/regen/queued.d.ts.map +1 -1
  155. package/lib/chain/regen/queued.js +5 -5
  156. package/lib/chain/regen/queued.js.map +1 -1
  157. package/lib/chain/seenCache/seenAttesters.d.ts +9 -5
  158. package/lib/chain/seenCache/seenAttesters.d.ts.map +1 -1
  159. package/lib/chain/seenCache/seenAttesters.js +25 -4
  160. package/lib/chain/seenCache/seenAttesters.js.map +1 -1
  161. package/lib/chain/seenCache/seenBlockProposers.d.ts +5 -1
  162. package/lib/chain/seenCache/seenBlockProposers.d.ts.map +1 -1
  163. package/lib/chain/seenCache/seenBlockProposers.js +10 -3
  164. package/lib/chain/seenCache/seenBlockProposers.js.map +1 -1
  165. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +28 -1
  166. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  167. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +121 -13
  168. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  169. package/lib/chain/stateCache/fifoBlockStateCache.d.ts +5 -1
  170. package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
  171. package/lib/chain/stateCache/fifoBlockStateCache.js +6 -0
  172. package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
  173. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +47 -5
  174. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  175. package/lib/chain/stateCache/persistentCheckpointsCache.js +117 -7
  176. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  177. package/lib/chain/stateCache/types.d.ts +3 -3
  178. package/lib/chain/stateCache/types.d.ts.map +1 -1
  179. package/lib/chain/validation/attestation.d.ts.map +1 -1
  180. package/lib/chain/validation/attestation.js +1 -4
  181. package/lib/chain/validation/attestation.js.map +1 -1
  182. package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
  183. package/lib/chain/validation/attesterSlashing.js +1 -1
  184. package/lib/chain/validation/attesterSlashing.js.map +1 -1
  185. package/lib/chain/validation/block.d.ts.map +1 -1
  186. package/lib/chain/validation/block.js +21 -7
  187. package/lib/chain/validation/block.js.map +1 -1
  188. package/lib/chain/validation/executionPayloadBid.d.ts +13 -3
  189. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  190. package/lib/chain/validation/executionPayloadBid.js +147 -12
  191. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  192. package/lib/chain/validation/executionPayloadEnvelope.js +1 -1
  193. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  194. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  195. package/lib/chain/validation/payloadAttestationMessage.js +5 -14
  196. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  197. package/lib/chain/validation/proposerPreferences.d.ts +1 -1
  198. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  199. package/lib/chain/validation/proposerPreferences.js +13 -4
  200. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  201. package/lib/chain/validation/proposerSlashing.js +1 -1
  202. package/lib/chain/validation/proposerSlashing.js.map +1 -1
  203. package/lib/chain/validation/voluntaryExit.d.ts +9 -1
  204. package/lib/chain/validation/voluntaryExit.d.ts.map +1 -1
  205. package/lib/chain/validation/voluntaryExit.js +46 -1
  206. package/lib/chain/validation/voluntaryExit.js.map +1 -1
  207. package/lib/execution/builder/apiClient.d.ts +64 -0
  208. package/lib/execution/builder/apiClient.d.ts.map +1 -0
  209. package/lib/execution/builder/apiClient.js +184 -0
  210. package/lib/execution/builder/apiClient.js.map +1 -0
  211. package/lib/execution/builder/http.d.ts +10 -7
  212. package/lib/execution/builder/http.d.ts.map +1 -1
  213. package/lib/execution/builder/http.js +11 -8
  214. package/lib/execution/builder/http.js.map +1 -1
  215. package/lib/execution/builder/validateBid.d.ts +22 -0
  216. package/lib/execution/builder/validateBid.d.ts.map +1 -0
  217. package/lib/execution/builder/validateBid.js +111 -0
  218. package/lib/execution/builder/validateBid.js.map +1 -0
  219. package/lib/metrics/metrics/beacon.d.ts +3 -3
  220. package/lib/metrics/metrics/beacon.d.ts.map +1 -1
  221. package/lib/metrics/metrics/beacon.js +9 -9
  222. package/lib/metrics/metrics/beacon.js.map +1 -1
  223. package/lib/metrics/metrics/lodestar.d.ts +88 -23
  224. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  225. package/lib/metrics/metrics/lodestar.js +164 -52
  226. package/lib/metrics/metrics/lodestar.js.map +1 -1
  227. package/lib/network/gossip/topic.d.ts +13 -0
  228. package/lib/network/gossip/topic.d.ts.map +1 -1
  229. package/lib/network/gossip/topic.js +37 -0
  230. package/lib/network/gossip/topic.js.map +1 -1
  231. package/lib/network/libp2p/index.d.ts.map +1 -1
  232. package/lib/network/libp2p/index.js +10 -2
  233. package/lib/network/libp2p/index.js.map +1 -1
  234. package/lib/network/network.d.ts.map +1 -1
  235. package/lib/network/network.js +23 -43
  236. package/lib/network/network.js.map +1 -1
  237. package/lib/network/processor/constants.d.ts +7 -0
  238. package/lib/network/processor/constants.d.ts.map +1 -0
  239. package/lib/network/processor/constants.js +7 -0
  240. package/lib/network/processor/constants.js.map +1 -0
  241. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  242. package/lib/network/processor/gossipHandlers.js +38 -7
  243. package/lib/network/processor/gossipHandlers.js.map +1 -1
  244. package/lib/network/processor/gossipQueues/indexed.d.ts.map +1 -1
  245. package/lib/network/processor/gossipQueues/indexed.js +0 -1
  246. package/lib/network/processor/gossipQueues/indexed.js.map +1 -1
  247. package/lib/network/processor/index.d.ts +3 -0
  248. package/lib/network/processor/index.d.ts.map +1 -1
  249. package/lib/network/processor/index.js +41 -17
  250. package/lib/network/processor/index.js.map +1 -1
  251. package/lib/network/reqresp/score.d.ts.map +1 -1
  252. package/lib/network/reqresp/score.js +5 -11
  253. package/lib/network/reqresp/score.js.map +1 -1
  254. package/lib/network/util.d.ts +0 -1
  255. package/lib/network/util.d.ts.map +1 -1
  256. package/lib/network/util.js +0 -4
  257. package/lib/network/util.js.map +1 -1
  258. package/lib/node/deferredVoluntaryExitPublisher.d.ts +15 -0
  259. package/lib/node/deferredVoluntaryExitPublisher.d.ts.map +1 -0
  260. package/lib/node/deferredVoluntaryExitPublisher.js +38 -0
  261. package/lib/node/deferredVoluntaryExitPublisher.js.map +1 -0
  262. package/lib/node/nodejs.d.ts +2 -1
  263. package/lib/node/nodejs.d.ts.map +1 -1
  264. package/lib/node/nodejs.js +7 -0
  265. package/lib/node/nodejs.js.map +1 -1
  266. package/lib/sync/range/batch.d.ts +23 -5
  267. package/lib/sync/range/batch.d.ts.map +1 -1
  268. package/lib/sync/range/batch.js +61 -22
  269. package/lib/sync/range/batch.js.map +1 -1
  270. package/lib/sync/range/chain.d.ts +2 -1
  271. package/lib/sync/range/chain.d.ts.map +1 -1
  272. package/lib/sync/range/chain.js +23 -10
  273. package/lib/sync/range/chain.js.map +1 -1
  274. package/lib/sync/types.d.ts +32 -0
  275. package/lib/sync/types.d.ts.map +1 -1
  276. package/lib/sync/types.js +33 -0
  277. package/lib/sync/types.js.map +1 -1
  278. package/lib/sync/unknownBlock.d.ts +15 -2
  279. package/lib/sync/unknownBlock.d.ts.map +1 -1
  280. package/lib/sync/unknownBlock.js +234 -63
  281. package/lib/sync/unknownBlock.js.map +1 -1
  282. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  283. package/lib/sync/utils/downloadByRange.js +16 -11
  284. package/lib/sync/utils/downloadByRange.js.map +1 -1
  285. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  286. package/lib/sync/utils/downloadByRoot.js +3 -1
  287. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  288. package/package.json +35 -35
  289. package/src/api/impl/beacon/blocks/index.ts +55 -21
  290. package/src/api/impl/beacon/pool/index.ts +27 -2
  291. package/src/api/impl/beacon/state/utils.ts +2 -1
  292. package/src/api/impl/events/index.ts +12 -4
  293. package/src/api/impl/lodestar/index.ts +12 -13
  294. package/src/api/impl/validator/index.ts +300 -69
  295. package/src/api/impl/validator/utils.ts +30 -15
  296. package/src/api/rest/base.ts +7 -0
  297. package/src/chain/archiveStore/archiveStore.ts +1 -1
  298. package/src/chain/archiveStore/historicalState/metrics.ts +22 -0
  299. package/src/chain/blocks/importBlock.ts +1 -6
  300. package/src/chain/blocks/importExecutionPayload.ts +12 -11
  301. package/src/chain/blocks/index.ts +19 -7
  302. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +8 -2
  303. package/src/chain/blocks/payloadEnvelopeInput/types.ts +17 -4
  304. package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +25 -16
  305. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +1 -9
  306. package/src/chain/bls/index.ts +1 -1
  307. package/src/chain/bls/interface.ts +7 -3
  308. package/src/chain/bls/multithread/index.ts +148 -120
  309. package/src/chain/bls/multithread/jobItem.ts +12 -82
  310. package/src/chain/bls/multithread/types.ts +33 -8
  311. package/src/chain/bls/multithread/utils.ts +11 -0
  312. package/src/chain/bls/multithread/worker.ts +85 -27
  313. package/src/chain/bls/singleThread.ts +11 -55
  314. package/src/chain/bls/utils.ts +23 -24
  315. package/src/chain/builderCircuitBreaker.ts +31 -17
  316. package/src/chain/chain.ts +27 -7
  317. package/src/chain/errors/blockError.ts +9 -3
  318. package/src/chain/errors/proposerPreferences.ts +7 -0
  319. package/src/chain/forkChoice/index.ts +4 -0
  320. package/src/chain/interface.ts +8 -3
  321. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  322. package/src/chain/opPools/attestationPool.ts +1 -1
  323. package/src/chain/opPools/deferredVoluntaryExitPool.ts +65 -0
  324. package/src/chain/opPools/executionPayloadBidPool.ts +13 -7
  325. package/src/chain/opPools/index.ts +2 -1
  326. package/src/chain/opPools/payloadAttestationPool.ts +3 -3
  327. package/src/chain/opPools/syncCommitteeMessagePool.ts +1 -1
  328. package/src/chain/opPools/syncContributionAndProofPool.ts +1 -1
  329. package/src/chain/opPools/utils.ts +1 -1
  330. package/src/chain/options.ts +1 -1
  331. package/src/chain/prepareNextSlot.ts +25 -7
  332. package/src/chain/produceBlock/computeNewStateRoot.ts +3 -38
  333. package/src/chain/produceBlock/produceBlockBody.ts +14 -1
  334. package/src/chain/regen/interface.ts +5 -2
  335. package/src/chain/regen/queued.ts +5 -5
  336. package/src/chain/seenCache/seenAttesters.ts +31 -5
  337. package/src/chain/seenCache/seenBlockProposers.ts +13 -3
  338. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +143 -14
  339. package/src/chain/stateCache/fifoBlockStateCache.ts +8 -1
  340. package/src/chain/stateCache/persistentCheckpointsCache.ts +133 -8
  341. package/src/chain/stateCache/types.ts +3 -3
  342. package/src/chain/validation/attestation.ts +1 -7
  343. package/src/chain/validation/attesterSlashing.ts +1 -8
  344. package/src/chain/validation/block.ts +21 -7
  345. package/src/chain/validation/executionPayloadBid.ts +168 -14
  346. package/src/chain/validation/executionPayloadEnvelope.ts +0 -1
  347. package/src/chain/validation/payloadAttestationMessage.ts +6 -17
  348. package/src/chain/validation/proposerPreferences.ts +15 -5
  349. package/src/chain/validation/proposerSlashing.ts +1 -1
  350. package/src/chain/validation/voluntaryExit.ts +59 -2
  351. package/src/execution/builder/apiClient.ts +244 -0
  352. package/src/execution/builder/http.ts +11 -11
  353. package/src/execution/builder/validateBid.ts +166 -0
  354. package/src/metrics/metrics/beacon.ts +9 -9
  355. package/src/metrics/metrics/lodestar.ts +178 -58
  356. package/src/network/gossip/topic.ts +38 -0
  357. package/src/network/libp2p/index.ts +11 -2
  358. package/src/network/network.ts +29 -54
  359. package/src/network/processor/constants.ts +6 -0
  360. package/src/network/processor/gossipHandlers.ts +41 -5
  361. package/src/network/processor/gossipQueues/indexed.ts +0 -1
  362. package/src/network/processor/index.ts +53 -21
  363. package/src/network/reqresp/score.ts +5 -11
  364. package/src/network/util.ts +0 -5
  365. package/src/node/deferredVoluntaryExitPublisher.ts +54 -0
  366. package/src/node/nodejs.ts +10 -1
  367. package/src/sync/range/batch.ts +83 -28
  368. package/src/sync/range/chain.ts +26 -11
  369. package/src/sync/types.ts +36 -1
  370. package/src/sync/unknownBlock.ts +273 -70
  371. package/src/sync/utils/downloadByRange.ts +21 -12
  372. package/src/sync/utils/downloadByRoot.ts +3 -1
  373. package/lib/chain/bls/maybeBatch.d.ts +0 -12
  374. package/lib/chain/bls/maybeBatch.d.ts.map +0 -1
  375. package/lib/chain/bls/maybeBatch.js +0 -35
  376. package/lib/chain/bls/maybeBatch.js.map +0 -1
  377. package/src/chain/bls/maybeBatch.ts +0 -45
@@ -414,3 +414,41 @@ export const gossipTopicIgnoreDuplicatePublishError: Record<GossipType, boolean>
414
414
  [GossipType.execution_payload_bid]: true,
415
415
  [GossipType.proposer_preferences]: true,
416
416
  };
417
+
418
+ /**
419
+ * Whether a publish that reached zero subscribed peers is an acceptable outcome for a topic.
420
+ *
421
+ * Publishing to a topic with no subscribed peers throws `PublishError.NoPeersSubscribedToTopic`, which
422
+ * propagates to the caller and, for messages submitted through the beacon API, is surfaced to the
423
+ * validator client. That is the right default: a message that reached nobody is a failed broadcast, and a
424
+ * VC configured with several beacon nodes can fall back to another one.
425
+ *
426
+ * Only topics that have a concrete reason to tolerate it opt out here, ie. are set to `true`, and the
427
+ * reason is named on the entry. Everything else is `false` and keeps raising. Note this is applied per
428
+ * publish, a topic set to `false` still honors the global `--network.allowPublishToZeroPeers` flag.
429
+ */
430
+ export const gossipTopicAllowPublishToZeroPeers: Record<GossipType, boolean> = {
431
+ [GossipType.beacon_block]: false,
432
+ [GossipType.blob_sidecar]: false,
433
+ // data_column_sidecar: we ensure having all topic peers via prioritizePeers(). Even with 0 peers on the
434
+ // topic the overall publish can still succeed, because a supernode rebuilds and publishes the missing
435
+ // columns for us, so track sent peers as 0 instead of raising
436
+ [GossipType.data_column_sidecar]: true,
437
+ [GossipType.beacon_aggregate_and_proof]: false,
438
+ [GossipType.beacon_attestation]: false,
439
+ [GossipType.voluntary_exit]: false,
440
+ [GossipType.proposer_slashing]: false,
441
+ [GossipType.attester_slashing]: false,
442
+ [GossipType.sync_committee_contribution_and_proof]: false,
443
+ [GossipType.sync_committee]: false,
444
+ // light_client_finality_update and light_client_optimistic_update: non-mandatory route on most of the
445
+ // network as of Oct 2022, there may be no peer subscribed to the topic at all. Reason carried over from
446
+ // the handlers in network.ts, which used to swallow the error instead
447
+ [GossipType.light_client_finality_update]: true,
448
+ [GossipType.light_client_optimistic_update]: true,
449
+ [GossipType.bls_to_execution_change]: false,
450
+ [GossipType.execution_payload]: false,
451
+ [GossipType.payload_attestation_message]: false,
452
+ [GossipType.execution_payload_bid]: false,
453
+ [GossipType.proposer_preferences]: false,
454
+ };
@@ -1,3 +1,4 @@
1
+ import {getCiphers} from "node:crypto";
1
2
  import {bootstrap} from "@libp2p/bootstrap";
2
3
  import {identify} from "@libp2p/identify";
3
4
  import type {PrivateKey} from "@libp2p/interface";
@@ -9,12 +10,20 @@ import {Libp2pInit, createLibp2p} from "libp2p";
9
10
  import {Registry} from "prom-client";
10
11
  import {ENR} from "@chainsafe/enr";
11
12
  import {noise} from "@chainsafe/libp2p-noise";
12
- import {defaultCrypto} from "@chainsafe/libp2p-noise/crypto";
13
+ import {asCrypto, defaultCrypto} from "@chainsafe/libp2p-noise/crypto";
13
14
  import {quic} from "@chainsafe/libp2p-quic";
14
15
  import {Libp2p, LodestarComponents} from "../interface.js";
15
16
  import {NetworkOptions, defaultNetworkOptions} from "../options.js";
16
17
  import {Eth2PeerDataStore} from "../peers/datastore.js";
17
18
 
19
+ const noiseCrypto = getCiphers().includes("chacha20-poly1305")
20
+ ? defaultCrypto
21
+ : {
22
+ ...defaultCrypto,
23
+ chaCha20Poly1305Encrypt: asCrypto.chaCha20Poly1305Encrypt,
24
+ chaCha20Poly1305Decrypt: asCrypto.chaCha20Poly1305Decrypt,
25
+ };
26
+
18
27
  export type NodeJsLibp2pOpts = {
19
28
  peerStoreDir?: string;
20
29
  disablePeerDiscovery?: boolean;
@@ -122,7 +131,7 @@ export async function createNodeJsLibp2p(
122
131
  listen: localMultiaddrs,
123
132
  announce: [],
124
133
  },
125
- connectionEncrypters: [noise({crypto: defaultCrypto})],
134
+ connectionEncrypters: [noise({crypto: noiseCrypto})],
126
135
  transports,
127
136
  streamMuxers: [mplex({disconnectThreshold})],
128
137
  peerDiscovery,
@@ -49,7 +49,12 @@ import {INetworkCore, NetworkCore, WorkerNetworkCore} from "./core/index.js";
49
49
  import {INetworkEventBus, NetworkEvent, NetworkEventBus, NetworkEventData} from "./events.js";
50
50
  import {getActiveForkBoundaries} from "./forks.js";
51
51
  import {GossipHandlers, GossipTopicMap, GossipType, GossipTypeMap} from "./gossip/index.js";
52
- import {getGossipSSZType, gossipTopicIgnoreDuplicatePublishError, stringifyGossipTopic} from "./gossip/topic.js";
52
+ import {
53
+ getGossipSSZType,
54
+ gossipTopicAllowPublishToZeroPeers,
55
+ gossipTopicIgnoreDuplicatePublishError,
56
+ stringifyGossipTopic,
57
+ } from "./gossip/topic.js";
53
58
  import {INetwork} from "./interface.js";
54
59
  import {NetworkOptions} from "./options.js";
55
60
  import {PeerAction, PeerScoreStats} from "./peers/index.js";
@@ -65,7 +70,7 @@ import {
65
70
  } from "./reqresp/utils/collect.js";
66
71
  import {collectSequentialBlocksInRange} from "./reqresp/utils/collectSequentialBlocksInRange.js";
67
72
  import {CommitteeSubscription} from "./subnets/index.js";
68
- import {isPublishDuplicateError, isPublishToZeroPeersError, prettyPrintPeerIdStr} from "./util.js";
73
+ import {isPublishDuplicateError, prettyPrintPeerIdStr} from "./util.js";
69
74
 
70
75
  type NetworkModules = {
71
76
  opts: NetworkOptions;
@@ -352,9 +357,7 @@ export class Network implements INetwork {
352
357
  const epoch = computeEpochAtSlot(signedBlock.message.slot);
353
358
  const boundary = this.config.getForkBoundaryAtEpoch(epoch);
354
359
 
355
- return this.publishGossip<GossipType.beacon_block>({type: GossipType.beacon_block, boundary}, signedBlock, {
356
- ignoreDuplicatePublishError: true,
357
- });
360
+ return this.publishGossip<GossipType.beacon_block>({type: GossipType.beacon_block, boundary}, signedBlock);
358
361
  }
359
362
 
360
363
  async publishBlobSidecar(blobSidecar: deneb.BlobSidecar): Promise<number> {
@@ -363,9 +366,7 @@ export class Network implements INetwork {
363
366
 
364
367
  const subnet = blobSidecar.index;
365
368
 
366
- return this.publishGossip<GossipType.blob_sidecar>({type: GossipType.blob_sidecar, boundary, subnet}, blobSidecar, {
367
- ignoreDuplicatePublishError: true,
368
- });
369
+ return this.publishGossip<GossipType.blob_sidecar>({type: GossipType.blob_sidecar, boundary, subnet}, blobSidecar);
369
370
  }
370
371
 
371
372
  async publishDataColumnSidecar(
@@ -381,14 +382,7 @@ export class Network implements INetwork {
381
382
  try {
382
383
  const sentPeers = await this.publishGossip<GossipType.data_column_sidecar>(
383
384
  {type: GossipType.data_column_sidecar, boundary, subnet},
384
- dataColumnSidecar,
385
- {
386
- // we ensure having all topic peers via prioritizePeers() function
387
- // in the worse case, if there is 0 peer on the topic, the overall publish operation could be still a success
388
- // because supernode will rebuild and publish missing data column sidecars for us
389
- // hence we want to track sent peers as 0 instead of an error
390
- allowPublishToZeroTopicPeers: true,
391
- }
385
+ dataColumnSidecar
392
386
  );
393
387
  return {sentPeers, alreadyPublished: false};
394
388
  } catch (e) {
@@ -405,8 +399,7 @@ export class Network implements INetwork {
405
399
 
406
400
  return this.publishGossip<GossipType.beacon_aggregate_and_proof>(
407
401
  {type: GossipType.beacon_aggregate_and_proof, boundary},
408
- aggregateAndProof,
409
- {ignoreDuplicatePublishError: true}
402
+ aggregateAndProof
410
403
  );
411
404
  }
412
405
 
@@ -416,8 +409,7 @@ export class Network implements INetwork {
416
409
 
417
410
  return this.publishGossip<GossipType.beacon_attestation>(
418
411
  {type: GossipType.beacon_attestation, boundary, subnet},
419
- attestation,
420
- {ignoreDuplicatePublishError: true}
412
+ attestation
421
413
  );
422
414
  }
423
415
 
@@ -425,9 +417,7 @@ export class Network implements INetwork {
425
417
  const epoch = voluntaryExit.message.epoch;
426
418
  const boundary = this.config.getForkBoundaryAtEpoch(epoch);
427
419
 
428
- return this.publishGossip<GossipType.voluntary_exit>({type: GossipType.voluntary_exit, boundary}, voluntaryExit, {
429
- ignoreDuplicatePublishError: true,
430
- });
420
+ return this.publishGossip<GossipType.voluntary_exit>({type: GossipType.voluntary_exit, boundary}, voluntaryExit);
431
421
  }
432
422
 
433
423
  async publishBlsToExecutionChange(blsToExecutionChange: capella.SignedBLSToExecutionChange): Promise<number> {
@@ -438,8 +428,7 @@ export class Network implements INetwork {
438
428
  if (fork >= ForkSeq.capella) {
439
429
  const publishPromise = this.publishGossip<GossipType.bls_to_execution_change>(
440
430
  {type: GossipType.bls_to_execution_change, boundary},
441
- blsToExecutionChange,
442
- {ignoreDuplicatePublishError: true}
431
+ blsToExecutionChange
443
432
  );
444
433
  publishChanges.push(publishPromise);
445
434
  }
@@ -457,8 +446,7 @@ export class Network implements INetwork {
457
446
 
458
447
  return this.publishGossip<GossipType.proposer_slashing>(
459
448
  {type: GossipType.proposer_slashing, boundary},
460
- proposerSlashing,
461
- {ignoreDuplicatePublishError: true}
449
+ proposerSlashing
462
450
  );
463
451
  }
464
452
 
@@ -478,10 +466,7 @@ export class Network implements INetwork {
478
466
 
479
467
  return this.publishGossip<GossipType.sync_committee>(
480
468
  {type: GossipType.sync_committee, boundary, subnet},
481
- signature,
482
- {
483
- ignoreDuplicatePublishError: true,
484
- }
469
+ signature
485
470
  );
486
471
  }
487
472
 
@@ -491,8 +476,7 @@ export class Network implements INetwork {
491
476
 
492
477
  return this.publishGossip<GossipType.sync_committee_contribution_and_proof>(
493
478
  {type: GossipType.sync_committee_contribution_and_proof, boundary},
494
- contributionAndProof,
495
- {ignoreDuplicatePublishError: true}
479
+ contributionAndProof
496
480
  );
497
481
  }
498
482
 
@@ -522,8 +506,7 @@ export class Network implements INetwork {
522
506
 
523
507
  return this.publishGossip<GossipType.execution_payload>(
524
508
  {type: GossipType.execution_payload, boundary},
525
- signedEnvelope,
526
- {ignoreDuplicatePublishError: true}
509
+ signedEnvelope
527
510
  );
528
511
  }
529
512
 
@@ -534,7 +517,7 @@ export class Network implements INetwork {
534
517
  return this.publishGossip<GossipType.execution_payload_bid>(
535
518
  {type: GossipType.execution_payload_bid, boundary},
536
519
  signedBid,
537
- {ignoreDuplicatePublishError: true}
520
+ {floodPublish: true}
538
521
  );
539
522
  }
540
523
 
@@ -544,8 +527,7 @@ export class Network implements INetwork {
544
527
 
545
528
  return this.publishGossip<GossipType.payload_attestation_message>(
546
529
  {type: GossipType.payload_attestation_message, boundary},
547
- payloadAttestationMessage,
548
- {ignoreDuplicatePublishError: true}
530
+ payloadAttestationMessage
549
531
  );
550
532
  }
551
533
 
@@ -555,24 +537,25 @@ export class Network implements INetwork {
555
537
 
556
538
  return this.publishGossip<GossipType.proposer_preferences>(
557
539
  {type: GossipType.proposer_preferences, boundary},
558
- signedProposerPreferences,
559
- {ignoreDuplicatePublishError: true}
540
+ signedProposerPreferences
560
541
  );
561
542
  }
562
543
 
563
544
  private async publishGossip<K extends GossipType>(
564
545
  topic: GossipTopicMap[K],
565
546
  object: GossipTypeMap[K],
566
- opts?: PublishOpts | undefined
547
+ opts?: PublishOpts
567
548
  ): Promise<number> {
568
549
  const topicStr = stringifyGossipTopic(this.config, topic);
569
550
  const sszType = getGossipSSZType(topic);
570
551
  const messageData = (sszType.serialize as (object: GossipTypeMap[GossipType]) => Uint8Array)(object);
571
- opts = {
572
- ...opts,
552
+ const publishOpts: PublishOpts = {
573
553
  ignoreDuplicatePublishError: gossipTopicIgnoreDuplicatePublishError[topic.type],
554
+ // Leave undefined unless the topic opts out, so `--network.allowPublishToZeroPeers` still applies
555
+ allowPublishToZeroTopicPeers: gossipTopicAllowPublishToZeroPeers[topic.type] ? true : undefined,
556
+ ...opts,
574
557
  };
575
- const sentPeers = await this.core.publishGossip(topicStr, messageData, opts);
558
+ const sentPeers = await this.core.publishGossip(topicStr, messageData, publishOpts);
576
559
 
577
560
  this.logger.verbose("Publish to topic", {topic: topicStr, sentPeers, currentSlot: this.clock.currentSlot});
578
561
  return sentPeers;
@@ -813,11 +796,7 @@ export class Network implements INetwork {
813
796
  await this.waitForSyncMessageCutoff(finalityUpdate.signatureSlot);
814
797
  await this.publishLightClientFinalityUpdate(finalityUpdate);
815
798
  } catch (e) {
816
- // Non-mandatory route on most of network as of Oct 2022. May not have found any peers on topic yet
817
- // Remove once https://github.com/ChainSafe/js-libp2p-gossipsub/issues/367
818
- if (!isPublishToZeroPeersError(e as Error)) {
819
- this.logger.debug("Error on BeaconGossipHandler.onLightclientFinalityUpdate", {}, e as Error);
820
- }
799
+ this.logger.debug("Error on BeaconGossipHandler.onLightclientFinalityUpdate", {}, e as Error);
821
800
  }
822
801
  };
823
802
 
@@ -830,11 +809,7 @@ export class Network implements INetwork {
830
809
  await this.waitForSyncMessageCutoff(optimisticUpdate.signatureSlot);
831
810
  await this.publishLightClientOptimisticUpdate(optimisticUpdate);
832
811
  } catch (e) {
833
- // Non-mandatory route on most of network as of Oct 2022. May not have found any peers on topic yet
834
- // Remove once https://github.com/ChainSafe/js-libp2p-gossipsub/issues/367
835
- if (!isPublishToZeroPeersError(e as Error)) {
836
- this.logger.debug("Error on BeaconGossipHandler.onLightclientOptimisticUpdate", {}, e as Error);
837
- }
812
+ this.logger.debug("Error on BeaconGossipHandler.onLightclientOptimisticUpdate", {}, e as Error);
838
813
  }
839
814
  };
840
815
 
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Max distinct peers tracked per unknown block/payload root.
3
+ * Normally we have 8 mesh peers per topic, and multi topics may point to the same root.
4
+ * Having 32 peers per root seems enough to search for missing blocks/payloads.
5
+ **/
6
+ export const MAX_PEERS_PER_ROOT = 32;
@@ -196,7 +196,8 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
196
196
  forkName: fork,
197
197
  sampledColumns: chain.custodyConfig.sampledColumns,
198
198
  custodyColumns: chain.custodyConfig.custodyColumns,
199
- timeCreatedSec: seenTimestampSec,
199
+ seenTimestampSec,
200
+ source: PayloadEnvelopeInputSource.gossip,
200
201
  });
201
202
  }
202
203
 
@@ -640,6 +641,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
640
641
  metrics?.gossipBlock.elapsedTimeTillProcessed.observe(delaySec);
641
642
 
642
643
  if (isForkPostGloas(blockInput.forkName)) {
644
+ // we should have the payloadInput in the seen cache so no need getOrReload() here
643
645
  const payloadInput = chain.seenPayloadEnvelopeInputCache.get(blockInput.blockRootHex);
644
646
  // This payloadInput should have been created just after gossip validation
645
647
  if (!payloadInput) {
@@ -713,9 +715,32 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
713
715
  const {serializedData} = gossipData;
714
716
 
715
717
  const signedBlock = sszDeserialize(topic, serializedData);
716
- const blockInput = await validateBeaconBlock(signedBlock, topic.boundary.fork, peerIdStr, seenTimestampSec);
717
- chain.serializedCache.set(signedBlock, serializedData);
718
- handleValidBeaconBlock(blockInput, peerIdStr, seenTimestampSec);
718
+ try {
719
+ const blockInput = await validateBeaconBlock(signedBlock, topic.boundary.fork, peerIdStr, seenTimestampSec);
720
+ chain.serializedCache.set(signedBlock, serializedData);
721
+ handleValidBeaconBlock(blockInput, peerIdStr, seenTimestampSec);
722
+ } catch (e) {
723
+ // Spec: IGNORE the block, ie not to re-publish to peers
724
+ // but we should still import an equivocating (REPEAT_PROPOSAL) block into fork choice because we don't
725
+ // know if this block (or the 1st known block with same slot) will become canonical yet
726
+ if (
727
+ e instanceof BlockGossipError &&
728
+ e.type.code === BlockErrorCode.REPEAT_PROPOSAL &&
729
+ // this is make sure the block's proposer signature was verified, it should be true anyway
730
+ chain.seenBlockProposers.hasBlockRoot(signedBlock.message.slot, e.type.proposerIndex, e.type.root)
731
+ ) {
732
+ // blockInput was optimistically seeded in validateBeaconBlock and retained on IGNORE
733
+ const blockInput = chain.seenBlockInputCache.get(e.type.root);
734
+ if (blockInput) {
735
+ chain.serializedCache.set(signedBlock, serializedData);
736
+ // this is technically not a valid gossip block but gossip validation is a cheap subset of checks
737
+ // this runs the full state transition, so importing an equivocating-but-valid block here is safe.
738
+ handleValidBeaconBlock(blockInput, peerIdStr, seenTimestampSec);
739
+ }
740
+ }
741
+ // rethrow so gossipValidatorFn maps IGNORE -> TopicValidatorResult.Ignore (message not forwarded)
742
+ throw e;
743
+ }
719
744
  },
720
745
 
721
746
  [GossipType.blob_sidecar]: async ({
@@ -1166,6 +1191,8 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1166
1191
  chain.validatorMonitor?.registerExecutionPayloadEnvelope(OpSource.gossip, delaySec, signedEnvelope);
1167
1192
 
1168
1193
  const blockRootHex = toRootHex(envelope.beaconBlockRoot);
1194
+ // a gossip payload cannot be `MAX_PAYLOAD_ENVELOPE_INPUT_CACHE_SIZE` slots after the block
1195
+ // otherwise it'll get to UnknownBlockInput flow
1169
1196
  const payloadInput = chain.seenPayloadEnvelopeInputCache.get(blockRootHex);
1170
1197
 
1171
1198
  if (!payloadInput) {
@@ -1232,11 +1259,15 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1232
1259
  [GossipType.payload_attestation_message]: async ({
1233
1260
  gossipData,
1234
1261
  topic,
1262
+ seenTimestampSec,
1235
1263
  }: GossipHandlerParamGeneric<GossipType.payload_attestation_message>) => {
1236
1264
  const {serializedData} = gossipData;
1237
1265
  const payloadAttestationMessage = sszDeserialize(topic, serializedData);
1238
1266
  const validationResult = await validateGossipPayloadAttestationMessage(chain, payloadAttestationMessage);
1239
1267
 
1268
+ const delaySec = chain.clock.secFromSlot(payloadAttestationMessage.data.slot, seenTimestampSec);
1269
+ metrics?.gossipPayloadAttestationMessage.elapsedTimeTillReceived.observe({source: OpSource.gossip}, delaySec);
1270
+
1240
1271
  try {
1241
1272
  const insertOutcome = chain.payloadAttestationPool.add(
1242
1273
  payloadAttestationMessage,
@@ -1263,14 +1294,19 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1263
1294
  [GossipType.execution_payload_bid]: async ({
1264
1295
  gossipData,
1265
1296
  topic,
1297
+ seenTimestampSec,
1266
1298
  }: GossipHandlerParamGeneric<GossipType.execution_payload_bid>) => {
1267
1299
  const {serializedData} = gossipData;
1268
1300
  const executionPayloadBid = sszDeserialize(topic, serializedData);
1269
1301
  const {proposerIndex} = await validateGossipExecutionPayloadBid(chain, executionPayloadBid);
1270
1302
 
1303
+ // this could be negative, because it's most likely the bid of next slot comes at this clock slot
1304
+ const elapsedSec = chain.clock.secFromSlot(executionPayloadBid.message.slot, seenTimestampSec);
1305
+ metrics?.gossipExecutionPayloadBid.elapsedTimeTillReceived.observe({source: OpSource.gossip}, elapsedSec);
1306
+
1271
1307
  // Handle valid payload bid by storing in a bid pool
1272
1308
  try {
1273
- const insertOutcome = chain.executionPayloadBidPool.add(executionPayloadBid);
1309
+ const insertOutcome = chain.executionPayloadBidPool.add(executionPayloadBid, Math.floor(elapsedSec * 1000));
1274
1310
  metrics?.opPool.executionPayloadBidPool.gossipInsertOutcome.inc({insertOutcome});
1275
1311
  } catch (e) {
1276
1312
  logger.error("Error adding to executionPayloadBid pool", {}, e as Error);
@@ -86,7 +86,6 @@ export class IndexedGossipQueueMinSize<T extends {indexed?: string; queueAddedMs
86
86
  const now = Date.now();
87
87
  // here we mutate item, which is used for gossip validation later
88
88
  item.indexed = key;
89
- item.queueAddedMs = now;
90
89
  let queueItem = this.indexedItems.get(key);
91
90
  if (queueItem == null) {
92
91
  queueItem = {firstSeenMs: now, listItems: new LinkedList<T>()};
@@ -30,6 +30,7 @@ import {
30
30
  GossipValidatorBatchFn,
31
31
  GossipValidatorFn,
32
32
  } from "../gossip/interface.js";
33
+ import {MAX_PEERS_PER_ROOT} from "./constants.js";
33
34
  import {createExtractBlockSlotRootFns} from "./extractSlotRootFns.js";
34
35
  import {GossipHandlerOpts, ValidatorFnsModules, getGossipHandlers} from "./gossipHandlers.js";
35
36
  import {createGossipQueues} from "./gossipQueues/index.js";
@@ -191,8 +192,12 @@ export class NetworkProcessor {
191
192
  // in that case PendingGossipsubMessage needs to be stored in this Map and reprocessed once the payload comes
192
193
  private readonly awaitingMessagesByPayloadBlockRoot: MapDef<RootHex, Set<PendingGossipsubMessage>>;
193
194
  private awaitingPayloadMessageCount = 0;
194
- private unknownBlocksBySlot = new MapDef<Slot, Set<RootHex>>(() => new Set());
195
- private unknownEnvelopesBySlot = new MapDef<Slot, Set<RootHex>>(() => new Set());
195
+ private unknownBlocksBySlot = new MapDef<Slot, MapDef<RootHex, Set<PeerIdStr>>>(
196
+ () => new MapDef<RootHex, Set<PeerIdStr>>(() => new Set())
197
+ );
198
+ private unknownEnvelopesBySlot = new MapDef<Slot, MapDef<RootHex, Set<PeerIdStr>>>(
199
+ () => new MapDef<RootHex, Set<PeerIdStr>>(() => new Set())
200
+ );
196
201
 
197
202
  constructor(
198
203
  modules: NetworkProcessorModules,
@@ -271,35 +276,53 @@ export class NetworkProcessor {
271
276
  * Search block via `ChainEvent.unknownBlockRoot` event
272
277
  * Slot is the message slot, which is not necessarily the same as the block's slot, but it can be used for a good prune strategy.
273
278
  * In the rare case, if 2 messages on 2 slots search for the same root (for example beacon_attestation) we may emit the same root twice but BlockInputSync should handle it well.
279
+ * We forward every distinct peer that gossiped this root (not just the first) so BlockInputSync can
280
+ * prefer them as by-root download candidates.
274
281
  */
275
282
  searchUnknownBlock({slot, root}: SlotRootHex, source: BlockInputSource, peer?: PeerIdStr): void {
276
- if (
277
- this.chain.seenBlock(root) ||
278
- this.awaitingMessagesByBlockRoot.has(root) ||
279
- this.unknownBlocksBySlot.getOrDefault(slot).has(root)
280
- ) {
283
+ if (this.chain.seenBlock(root)) {
281
284
  return;
282
285
  }
283
- // Search for the unknown block
284
- this.unknownBlocksBySlot.getOrDefault(slot).add(root);
285
- this.chain.emitter.emit(ChainEvent.unknownBlockRoot, {rootHex: root, peer, source});
286
+ const peersForRoot = this.unknownBlocksBySlot.getOrDefault(slot);
287
+ const alreadySearching = peersForRoot.has(root) || this.awaitingMessagesByBlockRoot.has(root);
288
+ const forwardedPeers = peersForRoot.getOrDefault(root);
289
+
290
+ // brand-new search always emits (peer may be undefined)
291
+ let shouldEmit = !alreadySearching;
292
+ if (peer !== undefined && !forwardedPeers.has(peer) && forwardedPeers.size < MAX_PEERS_PER_ROOT) {
293
+ // First time we forward THIS peer for THIS root: emit so BlockInputSync accumulates it.
294
+ forwardedPeers.add(peer);
295
+ shouldEmit = true;
296
+ }
297
+ if (shouldEmit) {
298
+ this.chain.emitter.emit(ChainEvent.unknownBlockRoot, {rootHex: root, peer, source});
299
+ }
286
300
  }
287
301
 
288
302
  /**
289
303
  * Search envelope via `ChainEvent.unknownEnvelopeBlockRoot` event
290
304
  * Slot is the message slot, which is not necessarily the same as the envelope's slot, but it can be used for a good prune strategy.
291
305
  * In the rare case, if 2 messages on 2 slots search for the same root (for example beacon_attestation) we may emit the same root twice but BlockInputSync should handle it well.
306
+ * Same peer-forwarding + dedup behavior as searchUnknownBlock.
292
307
  */
293
308
  searchUnknownEnvelope({slot, root}: SlotRootHex, source: BlockInputSource, peer?: PeerIdStr): void {
294
- if (
295
- this.chain.seenPayloadEnvelope(root) ||
296
- this.awaitingMessagesByPayloadBlockRoot.has(root) ||
297
- this.unknownEnvelopesBySlot.getOrDefault(slot).has(root)
298
- ) {
309
+ if (this.chain.seenPayloadEnvelope(root)) {
299
310
  return;
300
311
  }
301
- this.unknownEnvelopesBySlot.getOrDefault(slot).add(root);
302
- this.chain.emitter.emit(ChainEvent.unknownEnvelopeBlockRoot, {rootHex: root, slot, peer, source});
312
+ const peersForRoot = this.unknownEnvelopesBySlot.getOrDefault(slot);
313
+ // capture "already searching" BEFORE getOrDefault(root) creates the entry
314
+ const alreadySearching = peersForRoot.has(root) || this.awaitingMessagesByPayloadBlockRoot.has(root);
315
+ const forwardedPeers = peersForRoot.getOrDefault(root);
316
+
317
+ let shouldEmit = !alreadySearching; // brand-new search always emits (peer may be undefined)
318
+ if (peer !== undefined && !forwardedPeers.has(peer) && forwardedPeers.size < MAX_PEERS_PER_ROOT) {
319
+ // First time we forward THIS peer for THIS root: emit so BlockInputSync accumulates it.
320
+ forwardedPeers.add(peer);
321
+ shouldEmit = true;
322
+ }
323
+ if (shouldEmit) {
324
+ this.chain.emitter.emit(ChainEvent.unknownEnvelopeBlockRoot, {rootHex: root, slot, peer, source});
325
+ }
303
326
  }
304
327
 
305
328
  private onPendingGossipsubMessage = (message: PendingGossipsubMessage): void => {
@@ -537,6 +560,7 @@ export class NetworkProcessor {
537
560
 
538
561
  private pushPendingGossipsubMessageToQueue(message: PendingGossipsubMessage): void {
539
562
  const topicType = message.topic.type;
563
+ message.queueAddedMs = Date.now();
540
564
  const droppedCount = this.gossipQueues[topicType].add(message);
541
565
  if (droppedCount) {
542
566
  // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
@@ -616,7 +640,7 @@ export class NetworkProcessor {
616
640
 
617
641
  for (const [slot, roots] of this.unknownBlocksBySlot) {
618
642
  if (slot > minSlot) continue;
619
- for (const rootHex of roots) {
643
+ for (const rootHex of roots.keys()) {
620
644
  const gossipMessages = this.awaitingMessagesByBlockRoot.get(rootHex);
621
645
  if (gossipMessages !== undefined) {
622
646
  for (const message of gossipMessages) {
@@ -641,7 +665,7 @@ export class NetworkProcessor {
641
665
 
642
666
  for (const [slot, roots] of this.unknownEnvelopesBySlot) {
643
667
  if (slot > minSlot) continue;
644
- for (const rootHex of roots) {
668
+ for (const rootHex of roots.keys()) {
645
669
  const gossipMessages = this.awaitingMessagesByPayloadBlockRoot.get(rootHex);
646
670
  if (gossipMessages !== undefined) {
647
671
  for (const message of gossipMessages) {
@@ -723,12 +747,20 @@ export class NetworkProcessor {
723
747
  for (const msg of messageOrArray) {
724
748
  msg.startProcessUnixSec = nowSec;
725
749
  if (msg.queueAddedMs !== undefined) {
726
- this.metrics?.gossipValidationQueue.queueTime.observe(nowSec - msg.queueAddedMs / 1000);
750
+ this.metrics?.gossipValidationQueue.queueTime.observe(
751
+ {topic: msg.topic.type},
752
+ nowSec - msg.queueAddedMs / 1000
753
+ );
727
754
  }
728
755
  }
729
756
  } else {
730
- // indexed queue is not used here
731
757
  messageOrArray.startProcessUnixSec = nowSec;
758
+ if (messageOrArray.queueAddedMs !== undefined) {
759
+ this.metrics?.gossipValidationQueue.queueTime.observe(
760
+ {topic: messageOrArray.topic.type},
761
+ nowSec - messageOrArray.queueAddedMs / 1000
762
+ );
763
+ }
732
764
  }
733
765
 
734
766
  const acceptanceArr = Array.isArray(messageOrArray)
@@ -44,17 +44,11 @@ export function onOutgoingReqRespError(e: RequestError, method: ReqRespMethod):
44
44
  // failure, so keep the stronger penalty (Fatal for Ping, as before).
45
45
  return method === ReqRespMethod.Ping ? PeerAction.Fatal : PeerAction.LowToleranceError;
46
46
  }
47
- switch (method) {
48
- // Ping and Status are liveness probes; their dial timeouts are dominated by transient
49
- // network congestion rather than peer misbehavior, so penalize leniently to avoid
50
- // self-inflicted peer starvation (https://github.com/ChainSafe/lodestar/issues/9562),
51
- // while still applying some penalty so genuinely dead peers eventually free the slot.
52
- case ReqRespMethod.Ping:
53
- case ReqRespMethod.Status:
54
- return PeerAction.HighToleranceError;
55
- default:
56
- return PeerAction.LowToleranceError;
57
- }
47
+ // A dial failure means we could not open a stream. That is evidence about the transport, not
48
+ // about the peer's behavior, and it is dominated by transient congestion on our side, so
49
+ // penalize leniently for every method to avoid self-inflicted peer starvation (#9562). A
50
+ // genuinely dead peer still accumulates penalty and is eventually evicted, freeing the slot.
51
+ return PeerAction.HighToleranceError;
58
52
  // TODO: Detect SSZDecodeError and return PeerAction.Fatal
59
53
 
60
54
  case RequestErrorCode.RESP_TIMEOUT:
@@ -23,11 +23,6 @@ export function getConnection(libp2p: Libp2p, peerIdStr: string): Connection | u
23
23
  return getConnectionsMap(libp2p).get(peerIdStr)?.value[0] ?? undefined;
24
24
  }
25
25
 
26
- // https://github.com/libp2p/js-libp2p/blob/f87cba928991736d9646b3e054c367f55cab315c/packages/gossipsub/src/gossipsub.ts#L2076
27
- export function isPublishToZeroPeersError(e: Error): boolean {
28
- return e.message.includes("PublishError.NoPeersSubscribedToTopic");
29
- }
30
-
31
26
  // https://github.com/libp2p/js-libp2p/blob/f87cba928991736d9646b3e054c367f55cab315c/packages/gossipsub/src/gossipsub.ts#L2076
32
27
  export function isPublishDuplicateError(e: Error): boolean {
33
28
  return e.message.includes("PublishError.Duplicate");
@@ -0,0 +1,54 @@
1
+ import {routes} from "@lodestar/api";
2
+ import {Logger} from "@lodestar/utils";
3
+ import {IBeaconChain} from "../chain/index.js";
4
+ import {INetwork} from "../network/index.js";
5
+ import {ClockEvent} from "../util/clock.js";
6
+
7
+ /**
8
+ * On every epoch, drain the deferred voluntary exit pool and publish the exits that have become
9
+ * processable. Lives at the node layer rather than inside `BeaconChain` because publishing needs
10
+ * `network`, which the chain does not (and should not) depend on.
11
+ */
12
+ export function startDeferredVoluntaryExitPublisher({
13
+ chain,
14
+ network,
15
+ logger,
16
+ signal,
17
+ }: {
18
+ chain: IBeaconChain;
19
+ network: INetwork;
20
+ logger: Logger;
21
+ signal: AbortSignal;
22
+ }): void {
23
+ const onEpoch = async (): Promise<void> => {
24
+ try {
25
+ // Use the cached head state directly rather than getHeadStateAtCurrentEpoch, which would regen
26
+ // the head forward to the current epoch. At init from a stale db-finalized state that regen can
27
+ // be many epoch transitions. The head state is fine here: this is a best-effort drain that
28
+ // re-runs every epoch, so a just-processable exit is republished next tick.
29
+ const state = chain.getHeadState();
30
+ const exits = chain.deferredVoluntaryExitPool.drainProcessableExits(state);
31
+ for (const exit of exits) {
32
+ try {
33
+ chain.opPool.insertVoluntaryExit(exit);
34
+ chain.emitter.emit(routes.events.EventType.voluntaryExit, exit);
35
+ await network.publishVoluntaryExit(exit);
36
+ logger.info("Voluntary exit successfully published for validator", {
37
+ validatorIndex: exit.message.validatorIndex,
38
+ });
39
+ } catch (e) {
40
+ logger.warn(
41
+ "Failed to publish deferred voluntary exit",
42
+ {validatorIndex: exit.message.validatorIndex},
43
+ e as Error
44
+ );
45
+ }
46
+ }
47
+ } catch (e) {
48
+ logger.warn("Failed to drain deferred voluntary exit pool", {}, e as Error);
49
+ }
50
+ };
51
+
52
+ chain.clock.addListener(ClockEvent.epoch, onEpoch);
53
+ signal.addEventListener("abort", () => chain.clock.removeListener(ClockEvent.epoch, onEpoch), {once: true});
54
+ }