@lodestar/beacon-node 1.47.0-dev.d9c16ea16f → 1.47.0-dev.e73f1f5165

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 (352) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +6 -8
  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.js +1 -1
  15. package/lib/api/impl/lodestar/index.js.map +1 -1
  16. package/lib/api/impl/validator/index.d.ts.map +1 -1
  17. package/lib/api/impl/validator/index.js +12 -22
  18. package/lib/api/impl/validator/index.js.map +1 -1
  19. package/lib/api/impl/validator/utils.d.ts +5 -0
  20. package/lib/api/impl/validator/utils.d.ts.map +1 -1
  21. package/lib/api/impl/validator/utils.js +18 -12
  22. package/lib/api/impl/validator/utils.js.map +1 -1
  23. package/lib/api/rest/base.d.ts.map +1 -1
  24. package/lib/api/rest/base.js +7 -0
  25. package/lib/api/rest/base.js.map +1 -1
  26. package/lib/chain/archiveStore/archiveStore.js +1 -1
  27. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  28. package/lib/chain/archiveStore/historicalState/metrics.d.ts.map +1 -1
  29. package/lib/chain/archiveStore/historicalState/metrics.js +21 -0
  30. package/lib/chain/archiveStore/historicalState/metrics.js.map +1 -1
  31. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  32. package/lib/chain/blocks/importBlock.js +2 -7
  33. package/lib/chain/blocks/importBlock.js.map +1 -1
  34. package/lib/chain/blocks/importExecutionPayload.d.ts +3 -2
  35. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  36. package/lib/chain/blocks/importExecutionPayload.js +11 -9
  37. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  38. package/lib/chain/blocks/index.d.ts.map +1 -1
  39. package/lib/chain/blocks/index.js +15 -7
  40. package/lib/chain/blocks/index.js.map +1 -1
  41. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +2 -1
  42. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  43. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +6 -2
  44. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  45. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +14 -5
  46. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  47. package/lib/chain/blocks/payloadEnvelopeInput/types.js +5 -0
  48. package/lib/chain/blocks/payloadEnvelopeInput/types.js.map +1 -1
  49. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
  50. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +22 -12
  51. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
  52. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +2 -2
  53. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  54. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  55. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  56. package/lib/chain/bls/index.d.ts +1 -1
  57. package/lib/chain/bls/index.d.ts.map +1 -1
  58. package/lib/chain/bls/interface.d.ts +5 -5
  59. package/lib/chain/bls/interface.d.ts.map +1 -1
  60. package/lib/chain/bls/multithread/index.d.ts +5 -11
  61. package/lib/chain/bls/multithread/index.d.ts.map +1 -1
  62. package/lib/chain/bls/multithread/index.js +116 -101
  63. package/lib/chain/bls/multithread/index.js.map +1 -1
  64. package/lib/chain/bls/multithread/jobItem.d.ts +5 -19
  65. package/lib/chain/bls/multithread/jobItem.d.ts.map +1 -1
  66. package/lib/chain/bls/multithread/jobItem.js +10 -65
  67. package/lib/chain/bls/multithread/jobItem.js.map +1 -1
  68. package/lib/chain/bls/multithread/types.d.ts +29 -8
  69. package/lib/chain/bls/multithread/types.d.ts.map +1 -1
  70. package/lib/chain/bls/multithread/types.js +12 -0
  71. package/lib/chain/bls/multithread/types.js.map +1 -1
  72. package/lib/chain/bls/multithread/utils.d.ts +4 -0
  73. package/lib/chain/bls/multithread/utils.d.ts.map +1 -1
  74. package/lib/chain/bls/multithread/utils.js +10 -0
  75. package/lib/chain/bls/multithread/utils.js.map +1 -1
  76. package/lib/chain/bls/multithread/worker.js +49 -23
  77. package/lib/chain/bls/multithread/worker.js.map +1 -1
  78. package/lib/chain/bls/singleThread.d.ts +4 -10
  79. package/lib/chain/bls/singleThread.d.ts.map +1 -1
  80. package/lib/chain/bls/singleThread.js +8 -45
  81. package/lib/chain/bls/singleThread.js.map +1 -1
  82. package/lib/chain/bls/utils.d.ts +4 -4
  83. package/lib/chain/bls/utils.d.ts.map +1 -1
  84. package/lib/chain/bls/utils.js +18 -19
  85. package/lib/chain/bls/utils.js.map +1 -1
  86. package/lib/chain/builderCircuitBreaker.d.ts +6 -5
  87. package/lib/chain/builderCircuitBreaker.d.ts.map +1 -1
  88. package/lib/chain/builderCircuitBreaker.js +28 -15
  89. package/lib/chain/builderCircuitBreaker.js.map +1 -1
  90. package/lib/chain/chain.d.ts +5 -3
  91. package/lib/chain/chain.d.ts.map +1 -1
  92. package/lib/chain/chain.js +18 -7
  93. package/lib/chain/chain.js.map +1 -1
  94. package/lib/chain/errors/blockError.d.ts +2 -0
  95. package/lib/chain/errors/blockError.d.ts.map +1 -1
  96. package/lib/chain/errors/blockError.js +6 -1
  97. package/lib/chain/errors/blockError.js.map +1 -1
  98. package/lib/chain/errors/proposerPreferences.d.ts +6 -0
  99. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
  100. package/lib/chain/errors/proposerPreferences.js +1 -0
  101. package/lib/chain/errors/proposerPreferences.js.map +1 -1
  102. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  103. package/lib/chain/forkChoice/index.js +4 -0
  104. package/lib/chain/forkChoice/index.js.map +1 -1
  105. package/lib/chain/interface.d.ts +4 -2
  106. package/lib/chain/interface.d.ts.map +1 -1
  107. package/lib/chain/interface.js.map +1 -1
  108. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  109. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  110. package/lib/chain/opPools/attestationPool.d.ts +1 -1
  111. package/lib/chain/opPools/attestationPool.d.ts.map +1 -1
  112. package/lib/chain/opPools/attestationPool.js +1 -1
  113. package/lib/chain/opPools/attestationPool.js.map +1 -1
  114. package/lib/chain/opPools/deferredVoluntaryExitPool.d.ts +16 -0
  115. package/lib/chain/opPools/deferredVoluntaryExitPool.d.ts.map +1 -0
  116. package/lib/chain/opPools/deferredVoluntaryExitPool.js +57 -0
  117. package/lib/chain/opPools/deferredVoluntaryExitPool.js.map +1 -0
  118. package/lib/chain/opPools/index.d.ts +1 -0
  119. package/lib/chain/opPools/index.d.ts.map +1 -1
  120. package/lib/chain/opPools/index.js +1 -0
  121. package/lib/chain/opPools/index.js.map +1 -1
  122. package/lib/chain/opPools/payloadAttestationPool.js +3 -3
  123. package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
  124. package/lib/chain/opPools/syncCommitteeMessagePool.js +1 -1
  125. package/lib/chain/opPools/syncCommitteeMessagePool.js.map +1 -1
  126. package/lib/chain/opPools/syncContributionAndProofPool.js +1 -1
  127. package/lib/chain/opPools/syncContributionAndProofPool.js.map +1 -1
  128. package/lib/chain/opPools/utils.d.ts +1 -1
  129. package/lib/chain/opPools/utils.d.ts.map +1 -1
  130. package/lib/chain/opPools/utils.js +1 -1
  131. package/lib/chain/opPools/utils.js.map +1 -1
  132. package/lib/chain/options.d.ts +1 -1
  133. package/lib/chain/options.d.ts.map +1 -1
  134. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  135. package/lib/chain/prepareNextSlot.js +24 -6
  136. package/lib/chain/prepareNextSlot.js.map +1 -1
  137. package/lib/chain/produceBlock/produceBlockBody.d.ts +2 -0
  138. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  139. package/lib/chain/produceBlock/produceBlockBody.js +6 -1
  140. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  141. package/lib/chain/regen/interface.d.ts +4 -2
  142. package/lib/chain/regen/interface.d.ts.map +1 -1
  143. package/lib/chain/regen/interface.js +2 -0
  144. package/lib/chain/regen/interface.js.map +1 -1
  145. package/lib/chain/regen/queued.d.ts +2 -2
  146. package/lib/chain/regen/queued.d.ts.map +1 -1
  147. package/lib/chain/regen/queued.js +5 -5
  148. package/lib/chain/regen/queued.js.map +1 -1
  149. package/lib/chain/seenCache/seenAttesters.d.ts +9 -5
  150. package/lib/chain/seenCache/seenAttesters.d.ts.map +1 -1
  151. package/lib/chain/seenCache/seenAttesters.js +25 -4
  152. package/lib/chain/seenCache/seenAttesters.js.map +1 -1
  153. package/lib/chain/seenCache/seenBlockProposers.d.ts +5 -1
  154. package/lib/chain/seenCache/seenBlockProposers.d.ts.map +1 -1
  155. package/lib/chain/seenCache/seenBlockProposers.js +10 -3
  156. package/lib/chain/seenCache/seenBlockProposers.js.map +1 -1
  157. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +28 -1
  158. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  159. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +121 -13
  160. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  161. package/lib/chain/stateCache/fifoBlockStateCache.d.ts +5 -1
  162. package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
  163. package/lib/chain/stateCache/fifoBlockStateCache.js +6 -0
  164. package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
  165. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +47 -5
  166. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  167. package/lib/chain/stateCache/persistentCheckpointsCache.js +117 -7
  168. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  169. package/lib/chain/stateCache/types.d.ts +3 -3
  170. package/lib/chain/stateCache/types.d.ts.map +1 -1
  171. package/lib/chain/validation/attestation.d.ts.map +1 -1
  172. package/lib/chain/validation/attestation.js +1 -4
  173. package/lib/chain/validation/attestation.js.map +1 -1
  174. package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
  175. package/lib/chain/validation/attesterSlashing.js +1 -1
  176. package/lib/chain/validation/attesterSlashing.js.map +1 -1
  177. package/lib/chain/validation/block.d.ts.map +1 -1
  178. package/lib/chain/validation/block.js +21 -7
  179. package/lib/chain/validation/block.js.map +1 -1
  180. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  181. package/lib/chain/validation/executionPayloadBid.js +1 -2
  182. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  183. package/lib/chain/validation/executionPayloadEnvelope.js +1 -1
  184. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  185. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  186. package/lib/chain/validation/payloadAttestationMessage.js +5 -14
  187. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  188. package/lib/chain/validation/proposerPreferences.d.ts +1 -1
  189. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  190. package/lib/chain/validation/proposerPreferences.js +13 -4
  191. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  192. package/lib/chain/validation/proposerSlashing.js +1 -1
  193. package/lib/chain/validation/proposerSlashing.js.map +1 -1
  194. package/lib/chain/validation/voluntaryExit.d.ts +9 -1
  195. package/lib/chain/validation/voluntaryExit.d.ts.map +1 -1
  196. package/lib/chain/validation/voluntaryExit.js +46 -1
  197. package/lib/chain/validation/voluntaryExit.js.map +1 -1
  198. package/lib/execution/builder/http.d.ts +10 -7
  199. package/lib/execution/builder/http.d.ts.map +1 -1
  200. package/lib/execution/builder/http.js +11 -8
  201. package/lib/execution/builder/http.js.map +1 -1
  202. package/lib/metrics/metrics/beacon.d.ts +3 -3
  203. package/lib/metrics/metrics/beacon.d.ts.map +1 -1
  204. package/lib/metrics/metrics/beacon.js +9 -9
  205. package/lib/metrics/metrics/beacon.js.map +1 -1
  206. package/lib/metrics/metrics/lodestar.d.ts +72 -22
  207. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  208. package/lib/metrics/metrics/lodestar.js +130 -52
  209. package/lib/metrics/metrics/lodestar.js.map +1 -1
  210. package/lib/network/gossip/topic.d.ts +13 -0
  211. package/lib/network/gossip/topic.d.ts.map +1 -1
  212. package/lib/network/gossip/topic.js +37 -0
  213. package/lib/network/gossip/topic.js.map +1 -1
  214. package/lib/network/libp2p/index.d.ts.map +1 -1
  215. package/lib/network/libp2p/index.js +10 -2
  216. package/lib/network/libp2p/index.js.map +1 -1
  217. package/lib/network/network.d.ts.map +1 -1
  218. package/lib/network/network.js +22 -43
  219. package/lib/network/network.js.map +1 -1
  220. package/lib/network/processor/constants.d.ts +7 -0
  221. package/lib/network/processor/constants.d.ts.map +1 -0
  222. package/lib/network/processor/constants.js +7 -0
  223. package/lib/network/processor/constants.js.map +1 -0
  224. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  225. package/lib/network/processor/gossipHandlers.js +37 -6
  226. package/lib/network/processor/gossipHandlers.js.map +1 -1
  227. package/lib/network/processor/index.d.ts +3 -0
  228. package/lib/network/processor/index.d.ts.map +1 -1
  229. package/lib/network/processor/index.js +36 -15
  230. package/lib/network/processor/index.js.map +1 -1
  231. package/lib/network/reqresp/score.d.ts.map +1 -1
  232. package/lib/network/reqresp/score.js +5 -11
  233. package/lib/network/reqresp/score.js.map +1 -1
  234. package/lib/network/util.d.ts +0 -1
  235. package/lib/network/util.d.ts.map +1 -1
  236. package/lib/network/util.js +0 -4
  237. package/lib/network/util.js.map +1 -1
  238. package/lib/node/deferredVoluntaryExitPublisher.d.ts +15 -0
  239. package/lib/node/deferredVoluntaryExitPublisher.d.ts.map +1 -0
  240. package/lib/node/deferredVoluntaryExitPublisher.js +38 -0
  241. package/lib/node/deferredVoluntaryExitPublisher.js.map +1 -0
  242. package/lib/node/nodejs.d.ts +2 -1
  243. package/lib/node/nodejs.d.ts.map +1 -1
  244. package/lib/node/nodejs.js +2 -0
  245. package/lib/node/nodejs.js.map +1 -1
  246. package/lib/sync/range/batch.d.ts +23 -5
  247. package/lib/sync/range/batch.d.ts.map +1 -1
  248. package/lib/sync/range/batch.js +61 -22
  249. package/lib/sync/range/batch.js.map +1 -1
  250. package/lib/sync/range/chain.d.ts +2 -1
  251. package/lib/sync/range/chain.d.ts.map +1 -1
  252. package/lib/sync/range/chain.js +23 -10
  253. package/lib/sync/range/chain.js.map +1 -1
  254. package/lib/sync/types.d.ts +32 -0
  255. package/lib/sync/types.d.ts.map +1 -1
  256. package/lib/sync/types.js +33 -0
  257. package/lib/sync/types.js.map +1 -1
  258. package/lib/sync/unknownBlock.d.ts +15 -2
  259. package/lib/sync/unknownBlock.d.ts.map +1 -1
  260. package/lib/sync/unknownBlock.js +234 -63
  261. package/lib/sync/unknownBlock.js.map +1 -1
  262. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  263. package/lib/sync/utils/downloadByRange.js +16 -11
  264. package/lib/sync/utils/downloadByRange.js.map +1 -1
  265. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  266. package/lib/sync/utils/downloadByRoot.js +3 -1
  267. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  268. package/package.json +35 -35
  269. package/src/api/impl/beacon/blocks/index.ts +7 -8
  270. package/src/api/impl/beacon/pool/index.ts +27 -2
  271. package/src/api/impl/beacon/state/utils.ts +2 -1
  272. package/src/api/impl/events/index.ts +12 -4
  273. package/src/api/impl/lodestar/index.ts +1 -1
  274. package/src/api/impl/validator/index.ts +22 -27
  275. package/src/api/impl/validator/utils.ts +30 -15
  276. package/src/api/rest/base.ts +7 -0
  277. package/src/chain/archiveStore/archiveStore.ts +1 -1
  278. package/src/chain/archiveStore/historicalState/metrics.ts +22 -0
  279. package/src/chain/blocks/importBlock.ts +1 -6
  280. package/src/chain/blocks/importExecutionPayload.ts +12 -11
  281. package/src/chain/blocks/index.ts +19 -7
  282. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +8 -2
  283. package/src/chain/blocks/payloadEnvelopeInput/types.ts +17 -4
  284. package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +25 -16
  285. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +1 -9
  286. package/src/chain/bls/index.ts +1 -1
  287. package/src/chain/bls/interface.ts +7 -3
  288. package/src/chain/bls/multithread/index.ts +148 -120
  289. package/src/chain/bls/multithread/jobItem.ts +12 -82
  290. package/src/chain/bls/multithread/types.ts +33 -8
  291. package/src/chain/bls/multithread/utils.ts +11 -0
  292. package/src/chain/bls/multithread/worker.ts +85 -27
  293. package/src/chain/bls/singleThread.ts +11 -55
  294. package/src/chain/bls/utils.ts +23 -24
  295. package/src/chain/builderCircuitBreaker.ts +31 -17
  296. package/src/chain/chain.ts +19 -6
  297. package/src/chain/errors/blockError.ts +9 -3
  298. package/src/chain/errors/proposerPreferences.ts +7 -0
  299. package/src/chain/forkChoice/index.ts +4 -0
  300. package/src/chain/interface.ts +4 -1
  301. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  302. package/src/chain/opPools/attestationPool.ts +1 -1
  303. package/src/chain/opPools/deferredVoluntaryExitPool.ts +65 -0
  304. package/src/chain/opPools/index.ts +1 -0
  305. package/src/chain/opPools/payloadAttestationPool.ts +3 -3
  306. package/src/chain/opPools/syncCommitteeMessagePool.ts +1 -1
  307. package/src/chain/opPools/syncContributionAndProofPool.ts +1 -1
  308. package/src/chain/opPools/utils.ts +1 -1
  309. package/src/chain/options.ts +1 -1
  310. package/src/chain/prepareNextSlot.ts +24 -6
  311. package/src/chain/produceBlock/produceBlockBody.ts +13 -0
  312. package/src/chain/regen/interface.ts +4 -2
  313. package/src/chain/regen/queued.ts +5 -5
  314. package/src/chain/seenCache/seenAttesters.ts +31 -5
  315. package/src/chain/seenCache/seenBlockProposers.ts +13 -3
  316. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +143 -14
  317. package/src/chain/stateCache/fifoBlockStateCache.ts +8 -1
  318. package/src/chain/stateCache/persistentCheckpointsCache.ts +133 -8
  319. package/src/chain/stateCache/types.ts +3 -3
  320. package/src/chain/validation/attestation.ts +1 -7
  321. package/src/chain/validation/attesterSlashing.ts +1 -8
  322. package/src/chain/validation/block.ts +21 -7
  323. package/src/chain/validation/executionPayloadBid.ts +1 -2
  324. package/src/chain/validation/executionPayloadEnvelope.ts +0 -1
  325. package/src/chain/validation/payloadAttestationMessage.ts +6 -17
  326. package/src/chain/validation/proposerPreferences.ts +15 -5
  327. package/src/chain/validation/proposerSlashing.ts +1 -1
  328. package/src/chain/validation/voluntaryExit.ts +59 -2
  329. package/src/execution/builder/http.ts +11 -11
  330. package/src/metrics/metrics/beacon.ts +9 -9
  331. package/src/metrics/metrics/lodestar.ts +142 -57
  332. package/src/network/gossip/topic.ts +38 -0
  333. package/src/network/libp2p/index.ts +11 -2
  334. package/src/network/network.ts +27 -55
  335. package/src/network/processor/constants.ts +6 -0
  336. package/src/network/processor/gossipHandlers.ts +40 -4
  337. package/src/network/processor/index.ts +42 -19
  338. package/src/network/reqresp/score.ts +5 -11
  339. package/src/network/util.ts +0 -5
  340. package/src/node/deferredVoluntaryExitPublisher.ts +54 -0
  341. package/src/node/nodejs.ts +5 -1
  342. package/src/sync/range/batch.ts +83 -28
  343. package/src/sync/range/chain.ts +26 -11
  344. package/src/sync/types.ts +36 -1
  345. package/src/sync/unknownBlock.ts +273 -70
  346. package/src/sync/utils/downloadByRange.ts +21 -12
  347. package/src/sync/utils/downloadByRoot.ts +3 -1
  348. package/lib/chain/bls/maybeBatch.d.ts +0 -12
  349. package/lib/chain/bls/maybeBatch.d.ts.map +0 -1
  350. package/lib/chain/bls/maybeBatch.js +0 -35
  351. package/lib/chain/bls/maybeBatch.js.map +0 -1
  352. package/src/chain/bls/maybeBatch.ts +0 -45
@@ -1,5 +1,5 @@
1
1
  import { routes } from "@lodestar/api";
2
- import { ForkSeq } from "@lodestar/params";
2
+ import { ForkSeq, SLOTS_PER_EPOCH, isForkPostGloas } from "@lodestar/params";
3
3
  import { RequestError, RequestErrorCode } from "@lodestar/reqresp";
4
4
  import { computeTimeAtSlot } from "@lodestar/state-transition";
5
5
  import { fromHex, prettyPrintIndices, pruneSetToMax, sleep, toRootHex } from "@lodestar/utils";
@@ -12,17 +12,22 @@ import { ChainEvent } from "../chain/index.js";
12
12
  import { validateGloasBlockDataColumnSidecars } from "../chain/validation/dataColumnSidecar.js";
13
13
  import { validateGossipExecutionPayloadEnvelope } from "../chain/validation/executionPayloadEnvelope.js";
14
14
  import { NetworkEvent, prettyPrintPeerIdStr } from "../network/index.js";
15
+ import { MAX_PEERS_PER_ROOT } from "../network/processor/constants.js";
15
16
  import { shuffle } from "../util/shuffle.js";
16
17
  import { sortBy } from "../util/sortBy.js";
17
18
  import { wrapError } from "../util/wrapError.js";
18
19
  import { MAX_CONCURRENT_REQUESTS } from "./constants.js";
19
- import { PendingBlockInputStatus, PendingBlockType, PendingPayloadInputStatus, getBlockInputSyncCacheItemRootHex, getBlockInputSyncCacheItemSlot, getPayloadSyncCacheItemRootHex, getPayloadSyncCacheItemSlot, isPendingBlockInput, isPendingPayloadEnvelope, isPendingPayloadInput, } from "./types.js";
20
+ import { DroppedItemReason, PendingBlockInputStatus, PendingBlockType, PendingPayloadInputStatus, getBlockInputSyncCacheItemRootHex, getBlockInputSyncCacheItemSlot, getPayloadSyncCacheItemRootHex, getPayloadSyncCacheItemSlot, isPendingBlockInput, isPendingPayloadEnvelope, isPendingPayloadInput, } from "./types.js";
20
21
  import { DownloadByRootError, downloadByRoot } from "./utils/downloadByRoot.js";
21
22
  import { getAllDescendantBlocks, getUnknownAndAncestorBlocks } from "./utils/pendingBlocksTree.js";
22
23
  import { getRateLimitedUntilMs } from "./utils/rateLimit.js";
23
24
  const MAX_ATTEMPTS_PER_BLOCK = 5;
24
25
  const MAX_KNOWN_BAD_BLOCKS = 500;
25
26
  const MAX_PENDING_BLOCKS = 100;
27
+ /**
28
+ * A pending payload without slot can only stay at most 2 epochs.
29
+ */
30
+ const PRUNE_UNRESOLVED_SLOT_EPOCHS = 2;
26
31
  var FetchResult;
27
32
  (function (FetchResult) {
28
33
  FetchResult["SuccessResolved"] = "success_resolved";
@@ -115,10 +120,20 @@ export class BlockInputSync {
115
120
  this.chain.emitter.on(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
116
121
  this.chain.emitter.on(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
117
122
  this.chain.emitter.on(ChainEvent.blockUnknownParent, this.onUnknownParent);
123
+ this.chain.emitter.on(ChainEvent.forkChoiceFinalized, this.pruneFinalized);
118
124
  this.chain.emitter.on(routes.events.EventType.block, this.onBlockImported);
119
125
  this.chain.emitter.on(routes.events.EventType.executionPayload, this.onPayloadImported);
120
126
  this.network.events.on(NetworkEvent.peerConnected, this.onPeerConnected);
121
127
  this.network.events.on(NetworkEvent.peerDisconnected, this.onPeerDisconnected);
128
+ // Seed the balancer with peers that connected before we subscribed
129
+ for (const peerId of this.network.getConnectedPeers()) {
130
+ try {
131
+ this.peerBalancer.onPeerConnected(peerId, this.network.getConnectedPeerSyncMeta(peerId));
132
+ }
133
+ catch (e) {
134
+ this.logger.debug("Failed to seed connected peer into BlockInputSync balancer", { peerId }, e);
135
+ }
136
+ }
122
137
  this.subscribedToNetworkEvents = true;
123
138
  }
124
139
  }
@@ -130,10 +145,12 @@ export class BlockInputSync {
130
145
  this.chain.emitter.off(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
131
146
  this.chain.emitter.off(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
132
147
  this.chain.emitter.off(ChainEvent.blockUnknownParent, this.onUnknownParent);
148
+ this.chain.emitter.off(ChainEvent.forkChoiceFinalized, this.pruneFinalized);
133
149
  this.chain.emitter.off(routes.events.EventType.block, this.onBlockImported);
134
150
  this.chain.emitter.off(routes.events.EventType.executionPayload, this.onPayloadImported);
135
151
  this.network.events.off(NetworkEvent.peerConnected, this.onPeerConnected);
136
152
  this.network.events.off(NetworkEvent.peerDisconnected, this.onPeerDisconnected);
153
+ this.peerBalancer.clear();
137
154
  this.subscribedToNetworkEvents = false;
138
155
  }
139
156
  close() {
@@ -147,10 +164,12 @@ export class BlockInputSync {
147
164
  */
148
165
  onUnknownBlockRoot = (data) => {
149
166
  try {
150
- this.addByRootHex(data.rootHex, data.peer);
151
- this.triggerUnknownBlockSearch();
152
- this.metrics?.blockInputSync.requests.inc({ type: PendingBlockType.UNKNOWN_BLOCK_ROOT });
153
- this.metrics?.blockInputSync.source.inc({ source: data.source });
167
+ const isNewRoot = this.addByRootHex(data.rootHex, data.peer);
168
+ if (isNewRoot) {
169
+ this.triggerUnknownBlockSearch();
170
+ this.metrics?.blockInputSync.requests.inc({ type: PendingBlockType.UNKNOWN_BLOCK_ROOT });
171
+ this.metrics?.blockInputSync.source.inc({ source: data.source });
172
+ }
154
173
  }
155
174
  catch (e) {
156
175
  this.logger.debug("Error handling unknownBlockRoot event", {}, e);
@@ -172,10 +191,12 @@ export class BlockInputSync {
172
191
  };
173
192
  onUnknownEnvelopeBlockRoot = (data) => {
174
193
  try {
175
- this.addByPayloadRootHex(data.rootHex, data.peer, data.slot);
176
- this.triggerUnknownBlockSearch();
177
- this.metrics?.blockInputSync.requests.inc({ type: PendingBlockType.UNKNOWN_PAYLOAD_BLOCK_ROOT });
178
- this.metrics?.blockInputSync.payloadSource.inc({ source: data.source });
194
+ const isNewRoot = this.addByPayloadRootHex(data.rootHex, data.peer, this.resolvePayloadSlot(data.rootHex));
195
+ if (isNewRoot) {
196
+ this.triggerUnknownBlockSearch();
197
+ this.metrics?.blockInputSync.requests.inc({ type: PendingBlockType.UNKNOWN_PAYLOAD_BLOCK_ROOT });
198
+ this.metrics?.blockInputSync.payloadSource.inc({ source: data.source });
199
+ }
179
200
  }
180
201
  catch (e) {
181
202
  this.logger.debug("Error handling unknownEnvelopeBlockRoot event", {}, e);
@@ -208,12 +229,12 @@ export class BlockInputSync {
208
229
  parentRoot: missingDependency.parentRootHex,
209
230
  parentBlockHash: missingDependency.parentBlockHashHex,
210
231
  });
211
- this.removeAndDownScoreAllDescendants(pendingBlock);
232
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidParentPayload);
212
233
  }
213
234
  return;
214
235
  }
215
236
  if (missingDependency.kind === "parentPayload") {
216
- this.addByPayloadRootHex(missingDependency.rootHex, data.peer);
237
+ this.addByPayloadRootHex(missingDependency.rootHex, data.peer, missingDependency.slot);
217
238
  }
218
239
  else if (missingDependency.kind === "parentBlock") {
219
240
  this.addByRootHex(missingDependency.rootHex, data.peer);
@@ -236,6 +257,71 @@ export class BlockInputSync {
236
257
  this.pendingPayloads.delete(blockRoot);
237
258
  this.triggerUnknownBlockSearch();
238
259
  };
260
+ /**
261
+ * Drop pending payloads and blocks that fell below the finalized slot.
262
+ * See https://github.com/ChainSafe/lodestar/issues/9800
263
+ */
264
+ pruneFinalized = (checkpoint) => {
265
+ // Use the actual finalized block slot, not computeStartSlotAtEpoch(checkpoint.epoch)
266
+ // because the payload at the finalized block is not finalized
267
+ const finalizedSlot = this.chain.forkChoice.getFinalizedBlock().slot;
268
+ const nowSec = Date.now() / 1000;
269
+ const maxSecInCache = (PRUNE_UNRESOLVED_SLOT_EPOCHS * SLOTS_PER_EPOCH * this.config.SLOT_DURATION_MS) / 1000;
270
+ let belowFinalizedPayloads = 0;
271
+ let agedOutPayloads = 0;
272
+ let unresolvedSlotPayloads = 0;
273
+ for (const [rootHex, payload] of this.pendingPayloads) {
274
+ // Last-effort resolve for a slot-less PendingPayloadRootHex, and store it back for later passes.
275
+ if (!isPendingPayloadInput(payload) && !isPendingPayloadEnvelope(payload) && payload.slot === undefined) {
276
+ payload.slot = this.resolvePayloadSlot(rootHex);
277
+ if (payload.slot === undefined) {
278
+ // Still unresolvable: age it out so the MAX_PENDING_BLOCKS cap cannot stay saturated.
279
+ if (nowSec - payload.timeAddedSec > maxSecInCache) {
280
+ this.pendingPayloads.delete(rootHex);
281
+ agedOutPayloads++;
282
+ }
283
+ else {
284
+ unresolvedSlotPayloads++;
285
+ this.logger.debug("BlockInputSync.pruneFinalized: pending payload with unresolved slot", {
286
+ root: rootHex,
287
+ finalizedSlot,
288
+ });
289
+ }
290
+ continue;
291
+ }
292
+ }
293
+ const slot = getPayloadSyncCacheItemSlot(payload);
294
+ if (typeof slot === "number" && slot < finalizedSlot) {
295
+ this.pendingPayloads.delete(rootHex);
296
+ belowFinalizedPayloads++;
297
+ }
298
+ }
299
+ let deletedBlocks = 0;
300
+ for (const [rootHex, block] of this.pendingBlocks) {
301
+ const slot = getBlockInputSyncCacheItemSlot(block);
302
+ if (typeof slot === "number" && slot < finalizedSlot) {
303
+ this.pendingBlocks.delete(rootHex);
304
+ deletedBlocks++;
305
+ }
306
+ }
307
+ if (belowFinalizedPayloads > 0) {
308
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: DroppedItemReason.belowFinalized }, belowFinalizedPayloads);
309
+ }
310
+ if (agedOutPayloads > 0) {
311
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: DroppedItemReason.agedOut }, agedOutPayloads);
312
+ }
313
+ if (deletedBlocks > 0) {
314
+ this.metrics?.blockInputSync.removedBlocks.inc({ reason: DroppedItemReason.belowFinalized }, deletedBlocks);
315
+ }
316
+ this.logger.debug("BlockInputSync.pruneFinalized dropped pre-finalized pending items", {
317
+ finalizedSlot,
318
+ finalizedRoot: checkpoint.rootHex,
319
+ belowFinalizedPayloads,
320
+ agedOutPayloads,
321
+ deletedBlocks,
322
+ unresolvedSlotPayloads,
323
+ });
324
+ };
239
325
  addByRootHex = (rootHex, peerIdStr) => {
240
326
  let pendingBlock = this.pendingBlocks.get(rootHex);
241
327
  let added = false;
@@ -253,13 +339,16 @@ export class BlockInputSync {
253
339
  peerIdStr: peerIdStr ?? "unknown peer",
254
340
  });
255
341
  }
256
- if (peerIdStr) {
342
+ // bound peers tracked per root: propagationSource is recorded pre-validation, so cap the set (#9923).
343
+ // Once at the cap, ignore further peers rather than evicting already-tracked ones.
344
+ if (peerIdStr && pendingBlock.peerIdStrings.size < MAX_PEERS_PER_ROOT) {
257
345
  pendingBlock.peerIdStrings.add(peerIdStr);
258
346
  }
259
347
  // TODO: check this prune methodology
260
348
  // Limit pending blocks to prevent DOS attacks that cause OOM
261
349
  const prunedItemCount = pruneSetToMax(this.pendingBlocks, this.maxPendingBlocks);
262
350
  if (prunedItemCount > 0) {
351
+ this.metrics?.blockInputSync.removedBlocks.inc({ reason: DroppedItemReason.capacity }, prunedItemCount);
263
352
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingBlocks`);
264
353
  }
265
354
  return added;
@@ -283,13 +372,16 @@ export class BlockInputSync {
283
372
  delaySec: this.chain.clock.secFromSlot(blockInput.slot),
284
373
  });
285
374
  }
286
- if (peerIdStr) {
375
+ // bound peers tracked per root: propagationSource is recorded pre-validation, so cap the set (#9923).
376
+ // Once at the cap, ignore further peers rather than evicting already-tracked ones.
377
+ if (peerIdStr && pendingBlock.peerIdStrings.size < MAX_PEERS_PER_ROOT) {
287
378
  pendingBlock.peerIdStrings.add(peerIdStr);
288
379
  }
289
380
  // TODO: check this prune methodology
290
381
  // Limit pending blocks to prevent DOS attacks that cause OOM
291
382
  const prunedItemCount = pruneSetToMax(this.pendingBlocks, this.maxPendingBlocks);
292
383
  if (prunedItemCount > 0) {
384
+ this.metrics?.blockInputSync.removedBlocks.inc({ reason: DroppedItemReason.capacity }, prunedItemCount);
293
385
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingBlocks`);
294
386
  }
295
387
  };
@@ -313,18 +405,47 @@ export class BlockInputSync {
313
405
  peerIdStr: peerIdStr ?? "unknown peer",
314
406
  });
315
407
  }
316
- if (peerIdStr) {
408
+ else if (
409
+ // update a previously slot-less entry once a slot becomes available
410
+ slot !== undefined &&
411
+ !isPendingPayloadInput(pendingPayload) &&
412
+ !isPendingPayloadEnvelope(pendingPayload) &&
413
+ pendingPayload.slot === undefined) {
414
+ pendingPayload.slot = slot;
415
+ }
416
+ if (peerIdStr && pendingPayload.peerIdStrings.size < MAX_PEERS_PER_ROOT) {
317
417
  pendingPayload.peerIdStrings.add(peerIdStr);
318
418
  }
319
419
  const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
320
420
  if (prunedItemCount > 0) {
421
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: DroppedItemReason.capacity }, prunedItemCount);
321
422
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
322
423
  }
323
424
  return added;
324
425
  };
426
+ /**
427
+ * Resolve the block slot for a payload root.
428
+ */
429
+ resolvePayloadSlot(rootHex) {
430
+ // a pending block of the same root
431
+ const pendingBlock = this.pendingBlocks.get(rootHex);
432
+ if (pendingBlock) {
433
+ const pendingBlockSlot = getBlockInputSyncCacheItemSlot(pendingBlock);
434
+ if (typeof pendingBlockSlot === "number") {
435
+ return pendingBlockSlot;
436
+ }
437
+ }
438
+ // the block is in fork choice
439
+ const forkChoiceSlot = this.chain.forkChoice.getBlockHexDefaultStatus(rootHex)?.slot;
440
+ if (forkChoiceSlot !== undefined) {
441
+ return forkChoiceSlot;
442
+ }
443
+ // a seen payload envelope input
444
+ return this.chain.seenPayloadEnvelopeInputCache.get(rootHex)?.slot;
445
+ }
325
446
  addByPayloadInput = (payloadInput, peerIdStr, envelope) => {
326
447
  const pendingPayload = this.toPendingPayloadInput(payloadInput, this.pendingPayloads.get(payloadInput.blockRootHex), envelope);
327
- if (peerIdStr) {
448
+ if (peerIdStr && pendingPayload.peerIdStrings.size < MAX_PEERS_PER_ROOT) {
328
449
  pendingPayload.peerIdStrings.add(peerIdStr);
329
450
  }
330
451
  this.pendingPayloads.set(payloadInput.blockRootHex, pendingPayload);
@@ -335,6 +456,7 @@ export class BlockInputSync {
335
456
  });
336
457
  const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
337
458
  if (prunedItemCount > 0) {
459
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: DroppedItemReason.capacity }, prunedItemCount);
338
460
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
339
461
  }
340
462
  };
@@ -360,7 +482,8 @@ export class BlockInputSync {
360
482
  */
361
483
  getMissingBlockDependency(blockInput) {
362
484
  const parentRootHex = blockInput.parentRootHex;
363
- if (!this.chain.forkChoice.hasBlockHex(parentRootHex)) {
485
+ const parentBlock = this.chain.forkChoice.getBlockHexDefaultStatus(parentRootHex);
486
+ if (!parentBlock) {
364
487
  return { kind: "parentBlock", rootHex: parentRootHex };
365
488
  }
366
489
  if (!blockInput.hasBlock()) {
@@ -377,14 +500,8 @@ export class BlockInputSync {
377
500
  if (this.chain.forkChoice.hasPayloadHexUnsafe(parentRootHex)) {
378
501
  return { kind: "invalidParentPayload", parentRootHex, parentBlockHashHex };
379
502
  }
380
- const parentPayloadInput = this.chain.seenPayloadEnvelopeInputCache.get(parentRootHex);
381
- if (parentPayloadInput) {
382
- if (parentPayloadInput.getBlockHashHex() === parentBlockHashHex) {
383
- return { kind: "parentPayload", rootHex: parentRootHex };
384
- }
385
- return { kind: "invalidParentPayload", parentRootHex, parentBlockHashHex };
386
- }
387
- return { kind: "parentPayload", rootHex: parentRootHex };
503
+ // Parent is in fork choice but its payload `parentBlockHashHex` isn't revealed yet
504
+ return { kind: "parentPayload", rootHex: parentRootHex, slot: parentBlock.slot };
388
505
  }
389
506
  advancePendingBlock(pendingBlock) {
390
507
  const missingDependency = this.getMissingBlockDependency(pendingBlock.blockInput);
@@ -402,9 +519,9 @@ export class BlockInputSync {
402
519
  return added ? "queued_parent_block" : "blocked";
403
520
  }
404
521
  case "parentPayload": {
405
- let added = this.addByPayloadRootHex(missingDependency.rootHex);
522
+ let added = this.addByPayloadRootHex(missingDependency.rootHex, undefined, missingDependency.slot);
406
523
  for (const peerIdStr of pendingBlock.peerIdStrings) {
407
- added = this.addByPayloadRootHex(missingDependency.rootHex, peerIdStr) || added;
524
+ added = this.addByPayloadRootHex(missingDependency.rootHex, peerIdStr, missingDependency.slot) || added;
408
525
  }
409
526
  return added ? "queued_parent_payload" : "blocked";
410
527
  }
@@ -415,7 +532,7 @@ export class BlockInputSync {
415
532
  parentRoot: missingDependency.parentRootHex,
416
533
  parentBlockHash: missingDependency.parentBlockHashHex,
417
534
  });
418
- this.removeAndDownScoreAllDescendants(pendingBlock);
535
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidParentPayload);
419
536
  return "removed";
420
537
  }
421
538
  }
@@ -604,7 +721,7 @@ export class BlockInputSync {
604
721
  ...logCtx2,
605
722
  finalizedSlot,
606
723
  });
607
- this.removeAndDownScoreAllDescendants(block);
724
+ this.removeAndDownScoreAllDescendants(block, DroppedItemReason.belowFinalized);
608
725
  }
609
726
  else {
610
727
  this.onUnknownBlockRoot({ rootHex: pending.blockInput.parentRootHex, source: BlockInputSource.byRoot });
@@ -622,7 +739,7 @@ export class BlockInputSync {
622
739
  }
623
740
  this.metrics?.blockInputSync.downloadedBlocksError.inc();
624
741
  this.logger.debug("Ignoring unknown block root after many failed downloads", logCtx, res.err);
625
- this.removeAndDownScoreAllDescendants(block);
742
+ this.removeAndDownScoreAllDescendants(block, DroppedItemReason.unavailable);
626
743
  }
627
744
  }
628
745
  /**
@@ -637,9 +754,10 @@ export class BlockInputSync {
637
754
  // this prevents unbundling attack
638
755
  // see https://lighthouse-blog.sigmaprime.io/mev-unbundling-rpc.html
639
756
  const { slot: blockSlot, proposerIndex } = pendingBlock.blockInput.getBlock().message;
757
+ const blockRootHex = pendingBlock.blockInput.blockRootHex;
640
758
  const logCtx = {
641
759
  slot: blockSlot,
642
- root: pendingBlock.blockInput.blockRootHex,
760
+ root: blockRootHex,
643
761
  delaySec: this.chain.clock.secFromSlot(blockSlot),
644
762
  };
645
763
  this.logger.verbose("Processing downloaded block", logCtx);
@@ -651,7 +769,7 @@ export class BlockInputSync {
651
769
  // eligible for proposer boost to prevent unbundling attack
652
770
  this.logger.verbose("Avoid proposer boost for this block of known proposer", {
653
771
  slot: blockSlot,
654
- root: pendingBlock.blockInput.blockRootHex,
772
+ root: blockRootHex,
655
773
  proposerIndex,
656
774
  });
657
775
  await sleep(proposerBoostWindowMs);
@@ -676,13 +794,24 @@ export class BlockInputSync {
676
794
  if (!res.err) {
677
795
  this.logger.verbose("Processed block from unknown sync", logCtx);
678
796
  // no need to update status to "processed", delete anyway
679
- this.pendingBlocks.delete(pendingBlock.blockInput.blockRootHex);
797
+ this.pendingBlocks.delete(blockRootHex);
798
+ if (isForkPostGloas(fork)) {
799
+ const payloadInput = await this.chain.seenPayloadEnvelopeInputCache.getOrReload(blockRootHex);
800
+ if (!payloadInput) {
801
+ this.logger.debug("PayloadEnvelopeInput unavailable during unknown sync processReadyBlock()", logCtx);
802
+ }
803
+ else {
804
+ // Similar to the gossip path: immediately attempt fetch of data columns from the execution engine.
805
+ // The bid already carries the kzg commitments, so there is no reason to wait for the payload to arrive.
806
+ this.chain.getBlobsTracker.triggerGetBlobs(payloadInput);
807
+ }
808
+ }
680
809
  // Re-enter the scheduler so descendants blocked on either parent blocks or parent payloads
681
810
  // are advanced through the same dependency checks as every other pending item.
682
811
  this.triggerUnknownBlockSearch();
683
812
  }
684
813
  else {
685
- const errorData = { slot: pendingBlock.blockInput.slot, root: pendingBlock.blockInput.blockRootHex };
814
+ const errorData = { slot: pendingBlock.blockInput.slot, root: blockRootHex };
686
815
  if (res.err instanceof BlockError) {
687
816
  switch (res.err.type.code) {
688
817
  // This cases are already handled with `{ignoreIfKnown: true}`
@@ -705,17 +834,17 @@ export class BlockInputSync {
705
834
  case BlockErrorCode.EXECUTION_ENGINE_ERROR:
706
835
  // Removing the block(s) without penalizing the peers, hoping for EL to
707
836
  // recover on a latter download + verify attempt.
708
- this.removeAllDescendants(pendingBlock);
837
+ this.removeAllDescendants(pendingBlock, DroppedItemReason.elError, DroppedItemReason.elError);
709
838
  break;
710
839
  case BlockErrorCode.EXECUTION_ENGINE_INVALID:
711
840
  // the peer served a bad block
712
841
  this.logger.debug("Execution engine rejected block from unknown parent sync", errorData, res.err);
713
- this.removeAndDownScoreAllDescendants(pendingBlock);
842
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.elInvalid);
714
843
  break;
715
844
  default:
716
845
  // Block is not correct with respect to our chain. Log error loudly
717
846
  this.logger.debug("Error processing block from unknown parent sync", errorData, res.err);
718
- this.removeAndDownScoreAllDescendants(pendingBlock);
847
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidBlock);
719
848
  }
720
849
  }
721
850
  // Probably a queue error or something unwanted happened, mark as pending to try again latter
@@ -736,7 +865,6 @@ export class BlockInputSync {
736
865
  this.pendingPayloads.delete(rootHex);
737
866
  return;
738
867
  }
739
- const payloadInput = this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
740
868
  if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
741
869
  // Block not in fork choice yet. payloadInput may be seeded from the block body during download, so a
742
870
  // non-null payloadInput does not imply the block is imported; defer regardless and pull the block first.
@@ -750,8 +878,9 @@ export class BlockInputSync {
750
878
  }
751
879
  return;
752
880
  }
881
+ const payloadInput = await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
753
882
  if (!payloadInput) {
754
- this.logger.debug("Missing PayloadEnvelopeInput for known block while reconciling payload envelope", {
883
+ this.logger.debug("PayloadEnvelopeInput not yet reloadable for imported block, will retry", {
755
884
  root: rootHex,
756
885
  });
757
886
  return;
@@ -763,6 +892,7 @@ export class BlockInputSync {
763
892
  const pendingPayloadByRoot = {
764
893
  status: PendingPayloadInputStatus.pending,
765
894
  rootHex,
895
+ slot: pendingPayload.envelope.message.payload.slotNumber,
766
896
  timeAddedSec: pendingPayload.timeAddedSec,
767
897
  peerIdStrings: new Set(pendingPayload.peerIdStrings),
768
898
  };
@@ -794,6 +924,9 @@ export class BlockInputSync {
794
924
  if (payload.status !== PendingPayloadInputStatus.pending) {
795
925
  return;
796
926
  }
927
+ if (!isPendingPayloadInput(payload) && payload.slot === undefined) {
928
+ payload.slot = this.resolvePayloadSlot(rootHex);
929
+ }
797
930
  const payloadSlot = getPayloadSyncCacheItemSlot(payload);
798
931
  const logCtx = {
799
932
  slot: payloadSlot,
@@ -806,7 +939,13 @@ export class BlockInputSync {
806
939
  const res = await wrapError(this.fetchPayloadInput(payload));
807
940
  if (!res.err) {
808
941
  const pendingPayload = res.result;
809
- this.pendingPayloads.set(getPayloadSyncCacheItemRootHex(pendingPayload), pendingPayload);
942
+ const resultRootHex = getPayloadSyncCacheItemRootHex(pendingPayload);
943
+ // finalization may have deleted this entry while we were awaiting the network, do not resurrect it via the set below
944
+ if (!this.pendingPayloads.has(resultRootHex)) {
945
+ this.logger.verbose("Dropping downloaded payload, entry pruned during fetch", logCtx);
946
+ return;
947
+ }
948
+ this.pendingPayloads.set(resultRootHex, pendingPayload);
810
949
  if (isPendingPayloadEnvelope(pendingPayload)) {
811
950
  await this.reconcilePayloadEnvelope(pendingPayload);
812
951
  }
@@ -873,16 +1012,24 @@ export class BlockInputSync {
873
1012
  pendingPayload.status = PendingPayloadInputStatus.downloaded;
874
1013
  break;
875
1014
  case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
1015
+ this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1016
+ this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.elInvalid);
1017
+ break;
876
1018
  case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
1019
+ this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1020
+ this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidEnvelope);
1021
+ break;
877
1022
  case PayloadErrorCode.INVALID_SIGNATURE:
878
1023
  // TODO GLOAS: Decide how invalid payload inputs should eventually leave memory without
879
1024
  // reintroducing envelope replacement / recreation flows.
880
1025
  this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
881
- this.removePendingPayloadAndDescendants(rootHex);
1026
+ this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidSignature);
882
1027
  break;
883
1028
  default:
884
1029
  this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
885
- this.pendingPayloads.delete(rootHex);
1030
+ if (this.pendingPayloads.delete(rootHex)) {
1031
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: DroppedItemReason.unknown }, 1);
1032
+ }
886
1033
  }
887
1034
  return;
888
1035
  }
@@ -901,7 +1048,7 @@ export class BlockInputSync {
901
1048
  let slot = getPayloadSyncCacheItemSlot(cacheItem);
902
1049
  let payloadInput = isPendingPayloadInput(cacheItem)
903
1050
  ? cacheItem.payloadInput
904
- : this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
1051
+ : await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
905
1052
  let envelope = payloadInput?.hasPayloadEnvelope() ? payloadInput.getPayloadEnvelope() : undefined;
906
1053
  let i = 0;
907
1054
  let deferredByRateLimit = false;
@@ -909,12 +1056,16 @@ export class BlockInputSync {
909
1056
  const pendingColumns = payloadInput?.hasAllData()
910
1057
  ? new Set()
911
1058
  : new Set(payloadInput?.getMissingSampledColumnMeta().missing ?? []);
912
- const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
1059
+ // prefer peers that gossiped this payload root to us (#9923)
1060
+ const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers, cacheItem.peerIdStrings);
913
1061
  if (peerMeta === null) {
914
1062
  if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
915
1063
  throw new UnknownBlockRateLimitedError(`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`);
916
1064
  }
917
- throw Error(`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`);
1065
+ const reason = pendingColumns.size > 0
1066
+ ? `cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`
1067
+ : "no peer available to download pending payload";
1068
+ throw Error(`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: ${reason}`);
918
1069
  }
919
1070
  const { peerId, client: peerClient } = peerMeta;
920
1071
  cacheItem.peerIdStrings.add(peerId);
@@ -923,7 +1074,7 @@ export class BlockInputSync {
923
1074
  envelope = await this.fetchExecutionPayloadEnvelope(peerId, blockRoot, rootHex);
924
1075
  slot = envelope.message.payload.slotNumber;
925
1076
  }
926
- payloadInput ??= this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
1077
+ payloadInput ??= await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
927
1078
  if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
928
1079
  // Block not in fork choice yet. Validating now would throw BLOCK_ROOT_UNKNOWN, so keep the downloaded
929
1080
  // envelope and wait for the block body; reconcilePayloadEnvelope validates once the block lands.
@@ -1068,7 +1219,8 @@ export class BlockInputSync {
1068
1219
  const pendingColumns = isPendingBlockInput(cacheItem) && isBlockInputColumns(cacheItem.blockInput)
1069
1220
  ? new Set(cacheItem.blockInput.getMissingSampledColumnMeta().missing)
1070
1221
  : defaultPendingColumns;
1071
- const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
1222
+ // prefer peers that gossiped this block root to us (#9923)
1223
+ const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers, cacheItem.peerIdStrings);
1072
1224
  if (peerMeta === null) {
1073
1225
  if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
1074
1226
  throw new UnknownBlockRateLimitedError(`Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`);
@@ -1194,9 +1346,9 @@ export class BlockInputSync {
1194
1346
  * Downscore all peers that have referenced any of this bad blocks. May report peers multiple times if they have
1195
1347
  * referenced more than one bad block.
1196
1348
  */
1197
- removeAndDownScoreAllDescendants(block) {
1349
+ removeAndDownScoreAllDescendants(block, headReason) {
1198
1350
  // Get all blocks that are a descendant of this one
1199
- const badPendingBlocks = this.removeAllDescendants(block);
1351
+ const badPendingBlocks = this.removeAllDescendants(block, headReason, DroppedItemReason.invalidParent);
1200
1352
  // just console log and do not penalize on pending/bad blocks for debugging
1201
1353
  // console.log("removeAndDownscoreAllDescendants", {block});
1202
1354
  for (const block of badPendingBlocks) {
@@ -1220,16 +1372,21 @@ export class BlockInputSync {
1220
1372
  pruneSetToMax(this.knownBadBlocks, MAX_KNOWN_BAD_BLOCKS);
1221
1373
  }
1222
1374
  // Once a parent payload is invalid, every descendant waiting on that payload lineage becomes unrecoverable too.
1223
- removePendingPayloadAndDescendants(rootHex) {
1375
+ removePendingPayloadAndDescendants(rootHex, headReason) {
1224
1376
  // Keep PayloadEnvelopeInput resident in the seen cache. importBlock() owns that object and
1225
1377
  // later validation/finalization logic decides when it can leave memory.
1226
- this.pendingPayloads.delete(rootHex);
1378
+ if (this.pendingPayloads.delete(rootHex)) {
1379
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: headReason }, 1);
1380
+ }
1227
1381
  const badPendingBlocks = getAllDescendantBlocks(rootHex, this.pendingBlocks);
1228
- this.metrics?.blockInputSync.removedBlocks.inc(badPendingBlocks.length);
1229
1382
  for (const block of badPendingBlocks) {
1230
1383
  const descendantRootHex = getBlockInputSyncCacheItemRootHex(block);
1231
- this.pendingBlocks.delete(descendantRootHex);
1232
- this.pendingPayloads.delete(descendantRootHex);
1384
+ if (this.pendingBlocks.delete(descendantRootHex)) {
1385
+ this.metrics?.blockInputSync.removedBlocks.inc({ reason: DroppedItemReason.invalidParent }, 1);
1386
+ }
1387
+ if (this.pendingPayloads.delete(descendantRootHex)) {
1388
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: DroppedItemReason.invalidParent }, 1);
1389
+ }
1233
1390
  this.chain.seenBlockInputCache.prune(descendantRootHex);
1234
1391
  this.logger.debug("Removing pending descendant after invalid parent payload", {
1235
1392
  slot: getBlockInputSyncCacheItemSlot(block),
@@ -1238,16 +1395,20 @@ export class BlockInputSync {
1238
1395
  });
1239
1396
  }
1240
1397
  }
1241
- removeAllDescendants(block) {
1398
+ removeAllDescendants(block, headReason, descendantReason = DroppedItemReason.invalidParent) {
1242
1399
  const rootHex = getBlockInputSyncCacheItemRootHex(block);
1243
1400
  const slot = getBlockInputSyncCacheItemSlot(block);
1244
- // Get all blocks that are a descendant of this one
1401
+ // Get all blocks that are a descendant of this one (index 0 is the head item itself)
1245
1402
  const badPendingBlocks = [block, ...getAllDescendantBlocks(rootHex, this.pendingBlocks)];
1246
- this.metrics?.blockInputSync.removedBlocks.inc(badPendingBlocks.length);
1247
- for (const block of badPendingBlocks) {
1403
+ for (const [i, block] of badPendingBlocks.entries()) {
1248
1404
  const rootHex = getBlockInputSyncCacheItemRootHex(block);
1249
- this.pendingBlocks.delete(rootHex);
1250
- this.pendingPayloads.delete(rootHex);
1405
+ const reason = i === 0 ? headReason : descendantReason;
1406
+ if (this.pendingBlocks.delete(rootHex)) {
1407
+ this.metrics?.blockInputSync.removedBlocks.inc({ reason }, 1);
1408
+ }
1409
+ if (this.pendingPayloads.delete(rootHex)) {
1410
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason }, 1);
1411
+ }
1251
1412
  this.chain.seenBlockInputCache.prune(rootHex);
1252
1413
  // Keep PayloadEnvelopeInput resident in the seen cache for consistency with the
1253
1414
  // importBlock()-owned lifecycle.
@@ -1291,6 +1452,12 @@ export class UnknownBlockPeerBalancer {
1291
1452
  this.activeRequests.delete(peerId);
1292
1453
  this.rateLimitedUntilByPeer.delete(peerId);
1293
1454
  }
1455
+ /** Reset all tracked peer state, e.g. when BlockInputSync unsubscribes from the network. */
1456
+ clear() {
1457
+ this.peersMeta.clear();
1458
+ this.activeRequests.clear();
1459
+ this.rateLimitedUntilByPeer.clear();
1460
+ }
1294
1461
  onRateLimited(peerId, rateLimitedUntilMs) {
1295
1462
  this.rateLimitedUntilByPeer.set(peerId, rateLimitedUntilMs);
1296
1463
  }
@@ -1318,17 +1485,21 @@ export class UnknownBlockPeerBalancer {
1318
1485
  return retryAt;
1319
1486
  }
1320
1487
  /**
1321
- * called from fetchBlockInput() where we only have block root and nothing else
1488
+ * called from fetchBlockInput()/fetchPayloadInput() with a block/payload root.
1322
1489
  * excludedPeers are the peers that we requested already so we don't want to try again
1323
1490
  * pendingColumns is empty for prefulu, or the 1st time we we download a block by root
1491
+ * preferredPeers are the peers NetworkProcessor told us gossiped this root (from
1492
+ * pendingBlock/cacheItem.peerIdStrings). They are preferred but not required.
1324
1493
  */
1325
- bestPeerForPendingColumns(pendingColumns, excludedPeers) {
1494
+ bestPeerForPendingColumns(pendingColumns, excludedPeers, preferredPeers) {
1326
1495
  const eligiblePeers = this.filterPeers(pendingColumns, excludedPeers);
1327
1496
  if (eligiblePeers.length === 0) {
1328
1497
  return null;
1329
1498
  }
1330
1499
  const sortedEligiblePeers = sortBy(shuffle(eligiblePeers),
1331
- // prefer peers with least active req
1500
+ // 1st key: prefer peers we heard about this root from
1501
+ (peerId) => (preferredPeers?.has(peerId) ? 0 : 1),
1502
+ // 2nd key (tiebreak): prefer peers with least active req
1332
1503
  (peerId) => this.activeRequests.get(peerId) ?? 0);
1333
1504
  const bestPeerId = sortedEligiblePeers[0];
1334
1505
  this.onRequest(bestPeerId);