@lodestar/beacon-node 1.47.0-dev.cec7fd63e3 → 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 (345) 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.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 +232 -49
  18. package/lib/api/impl/validator/index.js.map +1 -1
  19. package/lib/api/impl/validator/utils.js +1 -1
  20. package/lib/api/impl/validator/utils.js.map +1 -1
  21. package/lib/api/rest/base.d.ts.map +1 -1
  22. package/lib/api/rest/base.js +7 -0
  23. package/lib/api/rest/base.js.map +1 -1
  24. package/lib/chain/archiveStore/archiveStore.js +1 -1
  25. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  26. package/lib/chain/archiveStore/historicalState/metrics.d.ts.map +1 -1
  27. package/lib/chain/archiveStore/historicalState/metrics.js +21 -0
  28. package/lib/chain/archiveStore/historicalState/metrics.js.map +1 -1
  29. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  30. package/lib/chain/blocks/importBlock.js +2 -7
  31. package/lib/chain/blocks/importBlock.js.map +1 -1
  32. package/lib/chain/blocks/importExecutionPayload.d.ts +3 -2
  33. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  34. package/lib/chain/blocks/importExecutionPayload.js +11 -9
  35. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  36. package/lib/chain/blocks/index.d.ts.map +1 -1
  37. package/lib/chain/blocks/index.js +14 -6
  38. package/lib/chain/blocks/index.js.map +1 -1
  39. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +2 -1
  40. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  41. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +6 -2
  42. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  43. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +14 -5
  44. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  45. package/lib/chain/blocks/payloadEnvelopeInput/types.js +5 -0
  46. package/lib/chain/blocks/payloadEnvelopeInput/types.js.map +1 -1
  47. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
  48. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +22 -12
  49. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
  50. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +2 -2
  51. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  52. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  53. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  54. package/lib/chain/bls/index.d.ts +1 -1
  55. package/lib/chain/bls/index.d.ts.map +1 -1
  56. package/lib/chain/bls/interface.d.ts +5 -5
  57. package/lib/chain/bls/interface.d.ts.map +1 -1
  58. package/lib/chain/bls/multithread/index.d.ts +5 -11
  59. package/lib/chain/bls/multithread/index.d.ts.map +1 -1
  60. package/lib/chain/bls/multithread/index.js +116 -101
  61. package/lib/chain/bls/multithread/index.js.map +1 -1
  62. package/lib/chain/bls/multithread/jobItem.d.ts +5 -19
  63. package/lib/chain/bls/multithread/jobItem.d.ts.map +1 -1
  64. package/lib/chain/bls/multithread/jobItem.js +10 -65
  65. package/lib/chain/bls/multithread/jobItem.js.map +1 -1
  66. package/lib/chain/bls/multithread/types.d.ts +29 -8
  67. package/lib/chain/bls/multithread/types.d.ts.map +1 -1
  68. package/lib/chain/bls/multithread/types.js +12 -0
  69. package/lib/chain/bls/multithread/types.js.map +1 -1
  70. package/lib/chain/bls/multithread/utils.d.ts +4 -0
  71. package/lib/chain/bls/multithread/utils.d.ts.map +1 -1
  72. package/lib/chain/bls/multithread/utils.js +10 -0
  73. package/lib/chain/bls/multithread/utils.js.map +1 -1
  74. package/lib/chain/bls/multithread/worker.js +49 -23
  75. package/lib/chain/bls/multithread/worker.js.map +1 -1
  76. package/lib/chain/bls/singleThread.d.ts +4 -10
  77. package/lib/chain/bls/singleThread.d.ts.map +1 -1
  78. package/lib/chain/bls/singleThread.js +8 -45
  79. package/lib/chain/bls/singleThread.js.map +1 -1
  80. package/lib/chain/bls/utils.d.ts +4 -4
  81. package/lib/chain/bls/utils.d.ts.map +1 -1
  82. package/lib/chain/bls/utils.js +18 -19
  83. package/lib/chain/bls/utils.js.map +1 -1
  84. package/lib/chain/chain.d.ts +9 -4
  85. package/lib/chain/chain.d.ts.map +1 -1
  86. package/lib/chain/chain.js +22 -8
  87. package/lib/chain/chain.js.map +1 -1
  88. package/lib/chain/errors/blockError.d.ts +1 -0
  89. package/lib/chain/errors/blockError.d.ts.map +1 -1
  90. package/lib/chain/errors/blockError.js +6 -1
  91. package/lib/chain/errors/blockError.js.map +1 -1
  92. package/lib/chain/errors/proposerPreferences.d.ts +6 -0
  93. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
  94. package/lib/chain/errors/proposerPreferences.js +1 -0
  95. package/lib/chain/errors/proposerPreferences.js.map +1 -1
  96. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  97. package/lib/chain/forkChoice/index.js +4 -0
  98. package/lib/chain/forkChoice/index.js.map +1 -1
  99. package/lib/chain/interface.d.ts +8 -4
  100. package/lib/chain/interface.d.ts.map +1 -1
  101. package/lib/chain/interface.js.map +1 -1
  102. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  103. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  104. package/lib/chain/opPools/attestationPool.d.ts +1 -1
  105. package/lib/chain/opPools/attestationPool.d.ts.map +1 -1
  106. package/lib/chain/opPools/attestationPool.js +1 -1
  107. package/lib/chain/opPools/attestationPool.js.map +1 -1
  108. package/lib/chain/opPools/deferredVoluntaryExitPool.d.ts +16 -0
  109. package/lib/chain/opPools/deferredVoluntaryExitPool.d.ts.map +1 -0
  110. package/lib/chain/opPools/deferredVoluntaryExitPool.js +57 -0
  111. package/lib/chain/opPools/deferredVoluntaryExitPool.js.map +1 -0
  112. package/lib/chain/opPools/executionPayloadBidPool.d.ts +7 -2
  113. package/lib/chain/opPools/executionPayloadBidPool.d.ts.map +1 -1
  114. package/lib/chain/opPools/executionPayloadBidPool.js +4 -4
  115. package/lib/chain/opPools/executionPayloadBidPool.js.map +1 -1
  116. package/lib/chain/opPools/index.d.ts +2 -1
  117. package/lib/chain/opPools/index.d.ts.map +1 -1
  118. package/lib/chain/opPools/index.js +1 -0
  119. package/lib/chain/opPools/index.js.map +1 -1
  120. package/lib/chain/opPools/payloadAttestationPool.js +3 -3
  121. package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
  122. package/lib/chain/opPools/syncCommitteeMessagePool.js +1 -1
  123. package/lib/chain/opPools/syncCommitteeMessagePool.js.map +1 -1
  124. package/lib/chain/opPools/syncContributionAndProofPool.js +1 -1
  125. package/lib/chain/opPools/syncContributionAndProofPool.js.map +1 -1
  126. package/lib/chain/opPools/utils.d.ts +1 -1
  127. package/lib/chain/opPools/utils.d.ts.map +1 -1
  128. package/lib/chain/opPools/utils.js +1 -1
  129. package/lib/chain/opPools/utils.js.map +1 -1
  130. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  131. package/lib/chain/prepareNextSlot.js +24 -6
  132. package/lib/chain/prepareNextSlot.js.map +1 -1
  133. package/lib/chain/produceBlock/computeNewStateRoot.d.ts.map +1 -1
  134. package/lib/chain/produceBlock/computeNewStateRoot.js +3 -25
  135. package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
  136. package/lib/chain/produceBlock/produceBlockBody.js +1 -1
  137. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  138. package/lib/chain/regen/interface.d.ts +5 -2
  139. package/lib/chain/regen/interface.d.ts.map +1 -1
  140. package/lib/chain/regen/interface.js +3 -0
  141. package/lib/chain/regen/interface.js.map +1 -1
  142. package/lib/chain/regen/queued.d.ts +2 -2
  143. package/lib/chain/regen/queued.d.ts.map +1 -1
  144. package/lib/chain/regen/queued.js +5 -5
  145. package/lib/chain/regen/queued.js.map +1 -1
  146. package/lib/chain/seenCache/seenAttesters.d.ts +9 -5
  147. package/lib/chain/seenCache/seenAttesters.d.ts.map +1 -1
  148. package/lib/chain/seenCache/seenAttesters.js +25 -4
  149. package/lib/chain/seenCache/seenAttesters.js.map +1 -1
  150. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +28 -1
  151. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  152. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +121 -13
  153. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  154. package/lib/chain/stateCache/fifoBlockStateCache.d.ts +5 -1
  155. package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
  156. package/lib/chain/stateCache/fifoBlockStateCache.js +6 -0
  157. package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
  158. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +47 -5
  159. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  160. package/lib/chain/stateCache/persistentCheckpointsCache.js +117 -7
  161. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  162. package/lib/chain/stateCache/types.d.ts +3 -3
  163. package/lib/chain/stateCache/types.d.ts.map +1 -1
  164. package/lib/chain/validation/attestation.d.ts.map +1 -1
  165. package/lib/chain/validation/attestation.js +1 -4
  166. package/lib/chain/validation/attestation.js.map +1 -1
  167. package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
  168. package/lib/chain/validation/attesterSlashing.js +1 -1
  169. package/lib/chain/validation/attesterSlashing.js.map +1 -1
  170. package/lib/chain/validation/executionPayloadBid.d.ts +13 -3
  171. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  172. package/lib/chain/validation/executionPayloadBid.js +147 -12
  173. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  174. package/lib/chain/validation/executionPayloadEnvelope.js +1 -1
  175. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  176. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  177. package/lib/chain/validation/payloadAttestationMessage.js +5 -14
  178. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  179. package/lib/chain/validation/proposerPreferences.d.ts +1 -1
  180. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  181. package/lib/chain/validation/proposerPreferences.js +13 -4
  182. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  183. package/lib/chain/validation/proposerSlashing.js +1 -1
  184. package/lib/chain/validation/proposerSlashing.js.map +1 -1
  185. package/lib/chain/validation/voluntaryExit.d.ts +9 -1
  186. package/lib/chain/validation/voluntaryExit.d.ts.map +1 -1
  187. package/lib/chain/validation/voluntaryExit.js +46 -1
  188. package/lib/chain/validation/voluntaryExit.js.map +1 -1
  189. package/lib/execution/builder/apiClient.d.ts +64 -0
  190. package/lib/execution/builder/apiClient.d.ts.map +1 -0
  191. package/lib/execution/builder/apiClient.js +184 -0
  192. package/lib/execution/builder/apiClient.js.map +1 -0
  193. package/lib/execution/builder/validateBid.d.ts +22 -0
  194. package/lib/execution/builder/validateBid.d.ts.map +1 -0
  195. package/lib/execution/builder/validateBid.js +111 -0
  196. package/lib/execution/builder/validateBid.js.map +1 -0
  197. package/lib/metrics/metrics/lodestar.d.ts +88 -23
  198. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  199. package/lib/metrics/metrics/lodestar.js +164 -52
  200. package/lib/metrics/metrics/lodestar.js.map +1 -1
  201. package/lib/network/gossip/topic.d.ts +13 -0
  202. package/lib/network/gossip/topic.d.ts.map +1 -1
  203. package/lib/network/gossip/topic.js +37 -0
  204. package/lib/network/gossip/topic.js.map +1 -1
  205. package/lib/network/libp2p/index.d.ts.map +1 -1
  206. package/lib/network/libp2p/index.js +10 -2
  207. package/lib/network/libp2p/index.js.map +1 -1
  208. package/lib/network/network.d.ts.map +1 -1
  209. package/lib/network/network.js +23 -43
  210. package/lib/network/network.js.map +1 -1
  211. package/lib/network/processor/constants.d.ts +7 -0
  212. package/lib/network/processor/constants.d.ts.map +1 -0
  213. package/lib/network/processor/constants.js +7 -0
  214. package/lib/network/processor/constants.js.map +1 -0
  215. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  216. package/lib/network/processor/gossipHandlers.js +13 -4
  217. package/lib/network/processor/gossipHandlers.js.map +1 -1
  218. package/lib/network/processor/gossipQueues/indexed.d.ts.map +1 -1
  219. package/lib/network/processor/gossipQueues/indexed.js +0 -1
  220. package/lib/network/processor/gossipQueues/indexed.js.map +1 -1
  221. package/lib/network/processor/index.d.ts +3 -0
  222. package/lib/network/processor/index.d.ts.map +1 -1
  223. package/lib/network/processor/index.js +41 -17
  224. package/lib/network/processor/index.js.map +1 -1
  225. package/lib/network/reqresp/score.d.ts.map +1 -1
  226. package/lib/network/reqresp/score.js +5 -11
  227. package/lib/network/reqresp/score.js.map +1 -1
  228. package/lib/network/util.d.ts +0 -1
  229. package/lib/network/util.d.ts.map +1 -1
  230. package/lib/network/util.js +0 -4
  231. package/lib/network/util.js.map +1 -1
  232. package/lib/node/deferredVoluntaryExitPublisher.d.ts +15 -0
  233. package/lib/node/deferredVoluntaryExitPublisher.d.ts.map +1 -0
  234. package/lib/node/deferredVoluntaryExitPublisher.js +38 -0
  235. package/lib/node/deferredVoluntaryExitPublisher.js.map +1 -0
  236. package/lib/node/nodejs.d.ts +2 -1
  237. package/lib/node/nodejs.d.ts.map +1 -1
  238. package/lib/node/nodejs.js +7 -0
  239. package/lib/node/nodejs.js.map +1 -1
  240. package/lib/sync/range/batch.d.ts +23 -5
  241. package/lib/sync/range/batch.d.ts.map +1 -1
  242. package/lib/sync/range/batch.js +61 -22
  243. package/lib/sync/range/batch.js.map +1 -1
  244. package/lib/sync/range/chain.d.ts +2 -1
  245. package/lib/sync/range/chain.d.ts.map +1 -1
  246. package/lib/sync/range/chain.js +23 -10
  247. package/lib/sync/range/chain.js.map +1 -1
  248. package/lib/sync/types.d.ts +32 -0
  249. package/lib/sync/types.d.ts.map +1 -1
  250. package/lib/sync/types.js +33 -0
  251. package/lib/sync/types.js.map +1 -1
  252. package/lib/sync/unknownBlock.d.ts +15 -2
  253. package/lib/sync/unknownBlock.d.ts.map +1 -1
  254. package/lib/sync/unknownBlock.js +220 -61
  255. package/lib/sync/unknownBlock.js.map +1 -1
  256. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  257. package/lib/sync/utils/downloadByRange.js +16 -11
  258. package/lib/sync/utils/downloadByRange.js.map +1 -1
  259. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  260. package/lib/sync/utils/downloadByRoot.js +3 -1
  261. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  262. package/package.json +35 -35
  263. package/src/api/impl/beacon/blocks/index.ts +55 -21
  264. package/src/api/impl/beacon/pool/index.ts +27 -2
  265. package/src/api/impl/beacon/state/utils.ts +2 -1
  266. package/src/api/impl/events/index.ts +12 -4
  267. package/src/api/impl/lodestar/index.ts +1 -1
  268. package/src/api/impl/validator/index.ts +285 -49
  269. package/src/api/impl/validator/utils.ts +1 -1
  270. package/src/api/rest/base.ts +7 -0
  271. package/src/chain/archiveStore/archiveStore.ts +1 -1
  272. package/src/chain/archiveStore/historicalState/metrics.ts +22 -0
  273. package/src/chain/blocks/importBlock.ts +1 -6
  274. package/src/chain/blocks/importExecutionPayload.ts +12 -11
  275. package/src/chain/blocks/index.ts +18 -6
  276. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +8 -2
  277. package/src/chain/blocks/payloadEnvelopeInput/types.ts +17 -4
  278. package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +25 -16
  279. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +1 -9
  280. package/src/chain/bls/index.ts +1 -1
  281. package/src/chain/bls/interface.ts +7 -3
  282. package/src/chain/bls/multithread/index.ts +148 -120
  283. package/src/chain/bls/multithread/jobItem.ts +12 -82
  284. package/src/chain/bls/multithread/types.ts +33 -8
  285. package/src/chain/bls/multithread/utils.ts +11 -0
  286. package/src/chain/bls/multithread/worker.ts +85 -27
  287. package/src/chain/bls/singleThread.ts +11 -55
  288. package/src/chain/bls/utils.ts +23 -24
  289. package/src/chain/chain.ts +25 -7
  290. package/src/chain/errors/blockError.ts +8 -2
  291. package/src/chain/errors/proposerPreferences.ts +7 -0
  292. package/src/chain/forkChoice/index.ts +4 -0
  293. package/src/chain/interface.ts +8 -3
  294. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  295. package/src/chain/opPools/attestationPool.ts +1 -1
  296. package/src/chain/opPools/deferredVoluntaryExitPool.ts +65 -0
  297. package/src/chain/opPools/executionPayloadBidPool.ts +13 -7
  298. package/src/chain/opPools/index.ts +2 -1
  299. package/src/chain/opPools/payloadAttestationPool.ts +3 -3
  300. package/src/chain/opPools/syncCommitteeMessagePool.ts +1 -1
  301. package/src/chain/opPools/syncContributionAndProofPool.ts +1 -1
  302. package/src/chain/opPools/utils.ts +1 -1
  303. package/src/chain/prepareNextSlot.ts +24 -6
  304. package/src/chain/produceBlock/computeNewStateRoot.ts +3 -38
  305. package/src/chain/produceBlock/produceBlockBody.ts +1 -1
  306. package/src/chain/regen/interface.ts +5 -2
  307. package/src/chain/regen/queued.ts +5 -5
  308. package/src/chain/seenCache/seenAttesters.ts +31 -5
  309. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +143 -14
  310. package/src/chain/stateCache/fifoBlockStateCache.ts +8 -1
  311. package/src/chain/stateCache/persistentCheckpointsCache.ts +133 -8
  312. package/src/chain/stateCache/types.ts +3 -3
  313. package/src/chain/validation/attestation.ts +1 -7
  314. package/src/chain/validation/attesterSlashing.ts +1 -8
  315. package/src/chain/validation/executionPayloadBid.ts +168 -14
  316. package/src/chain/validation/executionPayloadEnvelope.ts +0 -1
  317. package/src/chain/validation/payloadAttestationMessage.ts +6 -17
  318. package/src/chain/validation/proposerPreferences.ts +15 -5
  319. package/src/chain/validation/proposerSlashing.ts +1 -1
  320. package/src/chain/validation/voluntaryExit.ts +59 -2
  321. package/src/execution/builder/apiClient.ts +244 -0
  322. package/src/execution/builder/validateBid.ts +166 -0
  323. package/src/metrics/metrics/lodestar.ts +178 -58
  324. package/src/network/gossip/topic.ts +38 -0
  325. package/src/network/libp2p/index.ts +11 -2
  326. package/src/network/network.ts +29 -54
  327. package/src/network/processor/constants.ts +6 -0
  328. package/src/network/processor/gossipHandlers.ts +15 -2
  329. package/src/network/processor/gossipQueues/indexed.ts +0 -1
  330. package/src/network/processor/index.ts +53 -21
  331. package/src/network/reqresp/score.ts +5 -11
  332. package/src/network/util.ts +0 -5
  333. package/src/node/deferredVoluntaryExitPublisher.ts +54 -0
  334. package/src/node/nodejs.ts +10 -1
  335. package/src/sync/range/batch.ts +83 -28
  336. package/src/sync/range/chain.ts +26 -11
  337. package/src/sync/types.ts +36 -1
  338. package/src/sync/unknownBlock.ts +258 -68
  339. package/src/sync/utils/downloadByRange.ts +21 -12
  340. package/src/sync/utils/downloadByRoot.ts +3 -1
  341. package/lib/chain/bls/maybeBatch.d.ts +0 -12
  342. package/lib/chain/bls/maybeBatch.d.ts.map +0 -1
  343. package/lib/chain/bls/maybeBatch.js +0 -35
  344. package/lib/chain/bls/maybeBatch.js.map +0 -1
  345. package/src/chain/bls/maybeBatch.ts +0 -45
@@ -1,6 +1,7 @@
1
1
  import {routes} from "@lodestar/api";
2
2
  import {ChainForkConfig} from "@lodestar/config";
3
- import {ForkSeq, isForkPostGloas} from "@lodestar/params";
3
+ import {CheckpointWithHex} from "@lodestar/fork-choice";
4
+ import {ForkSeq, SLOTS_PER_EPOCH, isForkPostGloas} from "@lodestar/params";
4
5
  import {RequestError, RequestErrorCode} from "@lodestar/reqresp";
5
6
  import {computeTimeAtSlot} from "@lodestar/state-transition";
6
7
  import {RootHex, Slot, gloas} from "@lodestar/types";
@@ -16,6 +17,7 @@ import {validateGossipExecutionPayloadEnvelope} from "../chain/validation/execut
16
17
  import {Metrics} from "../metrics/index.js";
17
18
  import {INetwork, NetworkEvent, NetworkEventData, prettyPrintPeerIdStr} from "../network/index.js";
18
19
  import {PeerSyncMeta} from "../network/peers/peersData.js";
20
+ import {MAX_PEERS_PER_ROOT} from "../network/processor/constants.js";
19
21
  import {PeerIdStr} from "../util/peerId.js";
20
22
  import {shuffle} from "../util/shuffle.js";
21
23
  import {sortBy} from "../util/sortBy.js";
@@ -24,6 +26,7 @@ import {MAX_CONCURRENT_REQUESTS} from "./constants.js";
24
26
  import {SyncOptions} from "./options.js";
25
27
  import {
26
28
  BlockInputSyncCacheItem,
29
+ DroppedItemReason,
27
30
  PayloadSyncCacheItem,
28
31
  PendingBlockInput,
29
32
  PendingBlockInputStatus,
@@ -47,6 +50,10 @@ import {getRateLimitedUntilMs} from "./utils/rateLimit.js";
47
50
  const MAX_ATTEMPTS_PER_BLOCK = 5;
48
51
  const MAX_KNOWN_BAD_BLOCKS = 500;
49
52
  const MAX_PENDING_BLOCKS = 100;
53
+ /**
54
+ * A pending payload without slot can only stay at most 2 epochs.
55
+ */
56
+ const PRUNE_UNRESOLVED_SLOT_EPOCHS = 2;
50
57
 
51
58
  type AdvancePendingBlockResult =
52
59
  | "ready"
@@ -154,10 +161,21 @@ export class BlockInputSync {
154
161
  this.chain.emitter.on(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
155
162
  this.chain.emitter.on(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
156
163
  this.chain.emitter.on(ChainEvent.blockUnknownParent, this.onUnknownParent);
164
+ this.chain.emitter.on(ChainEvent.forkChoiceFinalized, this.pruneFinalized);
157
165
  this.chain.emitter.on(routes.events.EventType.block, this.onBlockImported);
158
166
  this.chain.emitter.on(routes.events.EventType.executionPayload, this.onPayloadImported);
159
167
  this.network.events.on(NetworkEvent.peerConnected, this.onPeerConnected);
160
168
  this.network.events.on(NetworkEvent.peerDisconnected, this.onPeerDisconnected);
169
+
170
+ // Seed the balancer with peers that connected before we subscribed
171
+ for (const peerId of this.network.getConnectedPeers()) {
172
+ try {
173
+ this.peerBalancer.onPeerConnected(peerId, this.network.getConnectedPeerSyncMeta(peerId));
174
+ } catch (e) {
175
+ this.logger.debug("Failed to seed connected peer into BlockInputSync balancer", {peerId}, e as Error);
176
+ }
177
+ }
178
+
161
179
  this.subscribedToNetworkEvents = true;
162
180
  }
163
181
  }
@@ -170,10 +188,12 @@ export class BlockInputSync {
170
188
  this.chain.emitter.off(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
171
189
  this.chain.emitter.off(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
172
190
  this.chain.emitter.off(ChainEvent.blockUnknownParent, this.onUnknownParent);
191
+ this.chain.emitter.off(ChainEvent.forkChoiceFinalized, this.pruneFinalized);
173
192
  this.chain.emitter.off(routes.events.EventType.block, this.onBlockImported);
174
193
  this.chain.emitter.off(routes.events.EventType.executionPayload, this.onPayloadImported);
175
194
  this.network.events.off(NetworkEvent.peerConnected, this.onPeerConnected);
176
195
  this.network.events.off(NetworkEvent.peerDisconnected, this.onPeerDisconnected);
196
+ this.peerBalancer.clear();
177
197
  this.subscribedToNetworkEvents = false;
178
198
  }
179
199
 
@@ -190,10 +210,12 @@ export class BlockInputSync {
190
210
  */
191
211
  private onUnknownBlockRoot = (data: ChainEventData[ChainEvent.unknownBlockRoot]): void => {
192
212
  try {
193
- this.addByRootHex(data.rootHex, data.peer);
194
- this.triggerUnknownBlockSearch();
195
- this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.UNKNOWN_BLOCK_ROOT});
196
- this.metrics?.blockInputSync.source.inc({source: data.source});
213
+ const isNewRoot = this.addByRootHex(data.rootHex, data.peer);
214
+ if (isNewRoot) {
215
+ this.triggerUnknownBlockSearch();
216
+ this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.UNKNOWN_BLOCK_ROOT});
217
+ this.metrics?.blockInputSync.source.inc({source: data.source});
218
+ }
197
219
  } catch (e) {
198
220
  this.logger.debug("Error handling unknownBlockRoot event", {}, e as Error);
199
221
  }
@@ -215,10 +237,12 @@ export class BlockInputSync {
215
237
 
216
238
  private onUnknownEnvelopeBlockRoot = (data: ChainEventData[ChainEvent.unknownEnvelopeBlockRoot]): void => {
217
239
  try {
218
- this.addByPayloadRootHex(data.rootHex, data.peer, data.slot);
219
- this.triggerUnknownBlockSearch();
220
- this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.UNKNOWN_PAYLOAD_BLOCK_ROOT});
221
- this.metrics?.blockInputSync.payloadSource.inc({source: data.source});
240
+ const isNewRoot = this.addByPayloadRootHex(data.rootHex, data.peer, this.resolvePayloadSlot(data.rootHex));
241
+ if (isNewRoot) {
242
+ this.triggerUnknownBlockSearch();
243
+ this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.UNKNOWN_PAYLOAD_BLOCK_ROOT});
244
+ this.metrics?.blockInputSync.payloadSource.inc({source: data.source});
245
+ }
222
246
  } catch (e) {
223
247
  this.logger.debug("Error handling unknownEnvelopeBlockRoot event", {}, e as Error);
224
248
  }
@@ -252,13 +276,13 @@ export class BlockInputSync {
252
276
  parentRoot: missingDependency.parentRootHex,
253
277
  parentBlockHash: missingDependency.parentBlockHashHex,
254
278
  });
255
- this.removeAndDownScoreAllDescendants(pendingBlock);
279
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidParentPayload);
256
280
  }
257
281
  return;
258
282
  }
259
283
 
260
284
  if (missingDependency.kind === "parentPayload") {
261
- this.addByPayloadRootHex(missingDependency.rootHex, data.peer);
285
+ this.addByPayloadRootHex(missingDependency.rootHex, data.peer, missingDependency.slot);
262
286
  } else if (missingDependency.kind === "parentBlock") {
263
287
  this.addByRootHex(missingDependency.rootHex, data.peer);
264
288
  }
@@ -284,6 +308,80 @@ export class BlockInputSync {
284
308
  this.triggerUnknownBlockSearch();
285
309
  };
286
310
 
311
+ /**
312
+ * Drop pending payloads and blocks that fell below the finalized slot.
313
+ * See https://github.com/ChainSafe/lodestar/issues/9800
314
+ */
315
+ private pruneFinalized = (checkpoint: CheckpointWithHex): void => {
316
+ // Use the actual finalized block slot, not computeStartSlotAtEpoch(checkpoint.epoch)
317
+ // because the payload at the finalized block is not finalized
318
+ const finalizedSlot = this.chain.forkChoice.getFinalizedBlock().slot;
319
+ const nowSec = Date.now() / 1000;
320
+ const maxSecInCache = (PRUNE_UNRESOLVED_SLOT_EPOCHS * SLOTS_PER_EPOCH * this.config.SLOT_DURATION_MS) / 1000;
321
+
322
+ let belowFinalizedPayloads = 0;
323
+ let agedOutPayloads = 0;
324
+ let unresolvedSlotPayloads = 0;
325
+
326
+ for (const [rootHex, payload] of this.pendingPayloads) {
327
+ // Last-effort resolve for a slot-less PendingPayloadRootHex, and store it back for later passes.
328
+ if (!isPendingPayloadInput(payload) && !isPendingPayloadEnvelope(payload) && payload.slot === undefined) {
329
+ payload.slot = this.resolvePayloadSlot(rootHex);
330
+ if (payload.slot === undefined) {
331
+ // Still unresolvable: age it out so the MAX_PENDING_BLOCKS cap cannot stay saturated.
332
+ if (nowSec - payload.timeAddedSec > maxSecInCache) {
333
+ this.pendingPayloads.delete(rootHex);
334
+ agedOutPayloads++;
335
+ } else {
336
+ unresolvedSlotPayloads++;
337
+ this.logger.debug("BlockInputSync.pruneFinalized: pending payload with unresolved slot", {
338
+ root: rootHex,
339
+ finalizedSlot,
340
+ });
341
+ }
342
+ continue;
343
+ }
344
+ }
345
+
346
+ const slot = getPayloadSyncCacheItemSlot(payload);
347
+ if (typeof slot === "number" && slot < finalizedSlot) {
348
+ this.pendingPayloads.delete(rootHex);
349
+ belowFinalizedPayloads++;
350
+ }
351
+ }
352
+
353
+ let deletedBlocks = 0;
354
+ for (const [rootHex, block] of this.pendingBlocks) {
355
+ const slot = getBlockInputSyncCacheItemSlot(block);
356
+ if (typeof slot === "number" && slot < finalizedSlot) {
357
+ this.pendingBlocks.delete(rootHex);
358
+ deletedBlocks++;
359
+ }
360
+ }
361
+
362
+ if (belowFinalizedPayloads > 0) {
363
+ this.metrics?.blockInputSync.removedPayloads.inc(
364
+ {reason: DroppedItemReason.belowFinalized},
365
+ belowFinalizedPayloads
366
+ );
367
+ }
368
+ if (agedOutPayloads > 0) {
369
+ this.metrics?.blockInputSync.removedPayloads.inc({reason: DroppedItemReason.agedOut}, agedOutPayloads);
370
+ }
371
+ if (deletedBlocks > 0) {
372
+ this.metrics?.blockInputSync.removedBlocks.inc({reason: DroppedItemReason.belowFinalized}, deletedBlocks);
373
+ }
374
+
375
+ this.logger.debug("BlockInputSync.pruneFinalized dropped pre-finalized pending items", {
376
+ finalizedSlot,
377
+ finalizedRoot: checkpoint.rootHex,
378
+ belowFinalizedPayloads,
379
+ agedOutPayloads,
380
+ deletedBlocks,
381
+ unresolvedSlotPayloads,
382
+ });
383
+ };
384
+
287
385
  private addByRootHex = (rootHex: RootHex, peerIdStr?: PeerIdStr): boolean => {
288
386
  let pendingBlock = this.pendingBlocks.get(rootHex);
289
387
  let added = false;
@@ -303,7 +401,9 @@ export class BlockInputSync {
303
401
  });
304
402
  }
305
403
 
306
- if (peerIdStr) {
404
+ // bound peers tracked per root: propagationSource is recorded pre-validation, so cap the set (#9923).
405
+ // Once at the cap, ignore further peers rather than evicting already-tracked ones.
406
+ if (peerIdStr && pendingBlock.peerIdStrings.size < MAX_PEERS_PER_ROOT) {
307
407
  pendingBlock.peerIdStrings.add(peerIdStr);
308
408
  }
309
409
 
@@ -311,6 +411,7 @@ export class BlockInputSync {
311
411
  // Limit pending blocks to prevent DOS attacks that cause OOM
312
412
  const prunedItemCount = pruneSetToMax(this.pendingBlocks, this.maxPendingBlocks);
313
413
  if (prunedItemCount > 0) {
414
+ this.metrics?.blockInputSync.removedBlocks.inc({reason: DroppedItemReason.capacity}, prunedItemCount);
314
415
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingBlocks`);
315
416
  }
316
417
  return added;
@@ -337,7 +438,9 @@ export class BlockInputSync {
337
438
  });
338
439
  }
339
440
 
340
- if (peerIdStr) {
441
+ // bound peers tracked per root: propagationSource is recorded pre-validation, so cap the set (#9923).
442
+ // Once at the cap, ignore further peers rather than evicting already-tracked ones.
443
+ if (peerIdStr && pendingBlock.peerIdStrings.size < MAX_PEERS_PER_ROOT) {
341
444
  pendingBlock.peerIdStrings.add(peerIdStr);
342
445
  }
343
446
 
@@ -345,6 +448,7 @@ export class BlockInputSync {
345
448
  // Limit pending blocks to prevent DOS attacks that cause OOM
346
449
  const prunedItemCount = pruneSetToMax(this.pendingBlocks, this.maxPendingBlocks);
347
450
  if (prunedItemCount > 0) {
451
+ this.metrics?.blockInputSync.removedBlocks.inc({reason: DroppedItemReason.capacity}, prunedItemCount);
348
452
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingBlocks`);
349
453
  }
350
454
  };
@@ -369,19 +473,50 @@ export class BlockInputSync {
369
473
  root: rootHex,
370
474
  peerIdStr: peerIdStr ?? "unknown peer",
371
475
  });
476
+ } else if (
477
+ // update a previously slot-less entry once a slot becomes available
478
+ slot !== undefined &&
479
+ !isPendingPayloadInput(pendingPayload) &&
480
+ !isPendingPayloadEnvelope(pendingPayload) &&
481
+ pendingPayload.slot === undefined
482
+ ) {
483
+ pendingPayload.slot = slot;
372
484
  }
373
-
374
- if (peerIdStr) {
485
+ if (peerIdStr && pendingPayload.peerIdStrings.size < MAX_PEERS_PER_ROOT) {
375
486
  pendingPayload.peerIdStrings.add(peerIdStr);
376
487
  }
377
488
 
378
489
  const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
379
490
  if (prunedItemCount > 0) {
491
+ this.metrics?.blockInputSync.removedPayloads.inc({reason: DroppedItemReason.capacity}, prunedItemCount);
380
492
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
381
493
  }
382
494
  return added;
383
495
  };
384
496
 
497
+ /**
498
+ * Resolve the block slot for a payload root.
499
+ */
500
+ private resolvePayloadSlot(rootHex: RootHex): Slot | undefined {
501
+ // a pending block of the same root
502
+ const pendingBlock = this.pendingBlocks.get(rootHex);
503
+ if (pendingBlock) {
504
+ const pendingBlockSlot = getBlockInputSyncCacheItemSlot(pendingBlock);
505
+ if (typeof pendingBlockSlot === "number") {
506
+ return pendingBlockSlot;
507
+ }
508
+ }
509
+
510
+ // the block is in fork choice
511
+ const forkChoiceSlot = this.chain.forkChoice.getBlockHexDefaultStatus(rootHex)?.slot;
512
+ if (forkChoiceSlot !== undefined) {
513
+ return forkChoiceSlot;
514
+ }
515
+
516
+ // a seen payload envelope input
517
+ return this.chain.seenPayloadEnvelopeInputCache.get(rootHex)?.slot;
518
+ }
519
+
385
520
  private addByPayloadInput = (
386
521
  payloadInput: PayloadEnvelopeInput,
387
522
  peerIdStr?: PeerIdStr,
@@ -393,7 +528,7 @@ export class BlockInputSync {
393
528
  envelope
394
529
  );
395
530
 
396
- if (peerIdStr) {
531
+ if (peerIdStr && pendingPayload.peerIdStrings.size < MAX_PEERS_PER_ROOT) {
397
532
  pendingPayload.peerIdStrings.add(peerIdStr);
398
533
  }
399
534
 
@@ -407,6 +542,7 @@ export class BlockInputSync {
407
542
 
408
543
  const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
409
544
  if (prunedItemCount > 0) {
545
+ this.metrics?.blockInputSync.removedPayloads.inc({reason: DroppedItemReason.capacity}, prunedItemCount);
410
546
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
411
547
  }
412
548
  };
@@ -436,10 +572,12 @@ export class BlockInputSync {
436
572
  blockInput: IBlockInput
437
573
  ):
438
574
  | {kind: "ready"}
439
- | {kind: "block" | "parentBlock" | "parentPayload"; rootHex: RootHex}
575
+ | {kind: "block" | "parentBlock"; rootHex: RootHex}
576
+ | {kind: "parentPayload"; rootHex: RootHex; slot: Slot}
440
577
  | {kind: "invalidParentPayload"; parentRootHex: RootHex; parentBlockHashHex: RootHex} {
441
578
  const parentRootHex = blockInput.parentRootHex;
442
- if (!this.chain.forkChoice.hasBlockHex(parentRootHex)) {
579
+ const parentBlock = this.chain.forkChoice.getBlockHexDefaultStatus(parentRootHex);
580
+ if (!parentBlock) {
443
581
  return {kind: "parentBlock", rootHex: parentRootHex};
444
582
  }
445
583
 
@@ -461,16 +599,8 @@ export class BlockInputSync {
461
599
  return {kind: "invalidParentPayload", parentRootHex, parentBlockHashHex};
462
600
  }
463
601
 
464
- const parentPayloadInput = this.chain.seenPayloadEnvelopeInputCache.get(parentRootHex);
465
- if (parentPayloadInput) {
466
- if (parentPayloadInput.getBlockHashHex() === parentBlockHashHex) {
467
- return {kind: "parentPayload", rootHex: parentRootHex};
468
- }
469
-
470
- return {kind: "invalidParentPayload", parentRootHex, parentBlockHashHex};
471
- }
472
-
473
- return {kind: "parentPayload", rootHex: parentRootHex};
602
+ // Parent is in fork choice but its payload `parentBlockHashHex` isn't revealed yet
603
+ return {kind: "parentPayload", rootHex: parentRootHex, slot: parentBlock.slot};
474
604
  }
475
605
 
476
606
  private advancePendingBlock(pendingBlock: PendingBlockInput): AdvancePendingBlockResult {
@@ -493,9 +623,9 @@ export class BlockInputSync {
493
623
  }
494
624
 
495
625
  case "parentPayload": {
496
- let added = this.addByPayloadRootHex(missingDependency.rootHex);
626
+ let added = this.addByPayloadRootHex(missingDependency.rootHex, undefined, missingDependency.slot);
497
627
  for (const peerIdStr of pendingBlock.peerIdStrings) {
498
- added = this.addByPayloadRootHex(missingDependency.rootHex, peerIdStr) || added;
628
+ added = this.addByPayloadRootHex(missingDependency.rootHex, peerIdStr, missingDependency.slot) || added;
499
629
  }
500
630
  return added ? "queued_parent_payload" : "blocked";
501
631
  }
@@ -507,7 +637,7 @@ export class BlockInputSync {
507
637
  parentRoot: missingDependency.parentRootHex,
508
638
  parentBlockHash: missingDependency.parentBlockHashHex,
509
639
  });
510
- this.removeAndDownScoreAllDescendants(pendingBlock);
640
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidParentPayload);
511
641
  return "removed";
512
642
  }
513
643
  }
@@ -730,7 +860,7 @@ export class BlockInputSync {
730
860
  ...logCtx2,
731
861
  finalizedSlot,
732
862
  });
733
- this.removeAndDownScoreAllDescendants(block);
863
+ this.removeAndDownScoreAllDescendants(block, DroppedItemReason.belowFinalized);
734
864
  } else {
735
865
  this.onUnknownBlockRoot({rootHex: pending.blockInput.parentRootHex, source: BlockInputSource.byRoot});
736
866
  }
@@ -747,7 +877,7 @@ export class BlockInputSync {
747
877
 
748
878
  this.metrics?.blockInputSync.downloadedBlocksError.inc();
749
879
  this.logger.debug("Ignoring unknown block root after many failed downloads", logCtx, res.err);
750
- this.removeAndDownScoreAllDescendants(block);
880
+ this.removeAndDownScoreAllDescendants(block, DroppedItemReason.unavailable);
751
881
  }
752
882
  }
753
883
 
@@ -811,9 +941,9 @@ export class BlockInputSync {
811
941
  this.pendingBlocks.delete(blockRootHex);
812
942
 
813
943
  if (isForkPostGloas(fork)) {
814
- const payloadInput = this.chain.seenPayloadEnvelopeInputCache.get(blockRootHex);
944
+ const payloadInput = await this.chain.seenPayloadEnvelopeInputCache.getOrReload(blockRootHex);
815
945
  if (!payloadInput) {
816
- this.logger.warn("PayloadEnvelopeInput not seeded during unknown sync processReadyBlock()", logCtx);
946
+ this.logger.debug("PayloadEnvelopeInput unavailable during unknown sync processReadyBlock()", logCtx);
817
947
  } else {
818
948
  // Similar to the gossip path: immediately attempt fetch of data columns from the execution engine.
819
949
  // The bid already carries the kzg commitments, so there is no reason to wait for the payload to arrive.
@@ -855,19 +985,19 @@ export class BlockInputSync {
855
985
  case BlockErrorCode.EXECUTION_ENGINE_ERROR:
856
986
  // Removing the block(s) without penalizing the peers, hoping for EL to
857
987
  // recover on a latter download + verify attempt.
858
- this.removeAllDescendants(pendingBlock);
988
+ this.removeAllDescendants(pendingBlock, DroppedItemReason.elError, DroppedItemReason.elError);
859
989
  break;
860
990
 
861
991
  case BlockErrorCode.EXECUTION_ENGINE_INVALID:
862
992
  // the peer served a bad block
863
993
  this.logger.debug("Execution engine rejected block from unknown parent sync", errorData, res.err);
864
- this.removeAndDownScoreAllDescendants(pendingBlock);
994
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.elInvalid);
865
995
  break;
866
996
 
867
997
  default:
868
998
  // Block is not correct with respect to our chain. Log error loudly
869
999
  this.logger.debug("Error processing block from unknown parent sync", errorData, res.err);
870
- this.removeAndDownScoreAllDescendants(pendingBlock);
1000
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidBlock);
871
1001
  }
872
1002
  }
873
1003
 
@@ -886,12 +1016,12 @@ export class BlockInputSync {
886
1016
  */
887
1017
  private async reconcilePayloadEnvelope(pendingPayload: PendingPayloadEnvelope): Promise<void> {
888
1018
  const rootHex = getPayloadSyncCacheItemRootHex(pendingPayload);
1019
+
889
1020
  if (this.chain.forkChoice.hasPayloadHexUnsafe(rootHex)) {
890
1021
  this.pendingPayloads.delete(rootHex);
891
1022
  return;
892
1023
  }
893
1024
 
894
- const payloadInput = this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
895
1025
  if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
896
1026
  // Block not in fork choice yet. payloadInput may be seeded from the block body during download, so a
897
1027
  // non-null payloadInput does not imply the block is imported; defer regardless and pull the block first.
@@ -907,8 +1037,9 @@ export class BlockInputSync {
907
1037
  return;
908
1038
  }
909
1039
 
1040
+ const payloadInput = await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
910
1041
  if (!payloadInput) {
911
- this.logger.debug("Missing PayloadEnvelopeInput for known block while reconciling payload envelope", {
1042
+ this.logger.debug("PayloadEnvelopeInput not yet reloadable for imported block, will retry", {
912
1043
  root: rootHex,
913
1044
  });
914
1045
  return;
@@ -928,6 +1059,7 @@ export class BlockInputSync {
928
1059
  const pendingPayloadByRoot: PendingPayloadRootHex = {
929
1060
  status: PendingPayloadInputStatus.pending,
930
1061
  rootHex,
1062
+ slot: pendingPayload.envelope.message.payload.slotNumber,
931
1063
  timeAddedSec: pendingPayload.timeAddedSec,
932
1064
  peerIdStrings: new Set(pendingPayload.peerIdStrings),
933
1065
  };
@@ -967,6 +1099,10 @@ export class BlockInputSync {
967
1099
  return;
968
1100
  }
969
1101
 
1102
+ if (!isPendingPayloadInput(payload) && payload.slot === undefined) {
1103
+ payload.slot = this.resolvePayloadSlot(rootHex);
1104
+ }
1105
+
970
1106
  const payloadSlot = getPayloadSyncCacheItemSlot(payload);
971
1107
  const logCtx = {
972
1108
  slot: payloadSlot,
@@ -982,7 +1118,13 @@ export class BlockInputSync {
982
1118
  const res = await wrapError(this.fetchPayloadInput(payload));
983
1119
  if (!res.err) {
984
1120
  const pendingPayload = res.result;
985
- this.pendingPayloads.set(getPayloadSyncCacheItemRootHex(pendingPayload), pendingPayload);
1121
+ const resultRootHex = getPayloadSyncCacheItemRootHex(pendingPayload);
1122
+ // finalization may have deleted this entry while we were awaiting the network, do not resurrect it via the set below
1123
+ if (!this.pendingPayloads.has(resultRootHex)) {
1124
+ this.logger.verbose("Dropping downloaded payload, entry pruned during fetch", logCtx);
1125
+ return;
1126
+ }
1127
+ this.pendingPayloads.set(resultRootHex, pendingPayload);
986
1128
 
987
1129
  if (isPendingPayloadEnvelope(pendingPayload)) {
988
1130
  await this.reconcilePayloadEnvelope(pendingPayload);
@@ -1059,17 +1201,27 @@ export class BlockInputSync {
1059
1201
  break;
1060
1202
 
1061
1203
  case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
1204
+ this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1205
+ this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.elInvalid);
1206
+ break;
1207
+
1062
1208
  case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
1209
+ this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1210
+ this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidEnvelope);
1211
+ break;
1212
+
1063
1213
  case PayloadErrorCode.INVALID_SIGNATURE:
1064
1214
  // TODO GLOAS: Decide how invalid payload inputs should eventually leave memory without
1065
1215
  // reintroducing envelope replacement / recreation flows.
1066
1216
  this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1067
- this.removePendingPayloadAndDescendants(rootHex);
1217
+ this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidSignature);
1068
1218
  break;
1069
1219
 
1070
1220
  default:
1071
1221
  this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1072
- this.pendingPayloads.delete(rootHex);
1222
+ if (this.pendingPayloads.delete(rootHex)) {
1223
+ this.metrics?.blockInputSync.removedPayloads.inc({reason: DroppedItemReason.unknown}, 1);
1224
+ }
1073
1225
  }
1074
1226
  return;
1075
1227
  }
@@ -1093,7 +1245,7 @@ export class BlockInputSync {
1093
1245
  let slot = getPayloadSyncCacheItemSlot(cacheItem);
1094
1246
  let payloadInput = isPendingPayloadInput(cacheItem)
1095
1247
  ? cacheItem.payloadInput
1096
- : this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
1248
+ : await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
1097
1249
  let envelope = payloadInput?.hasPayloadEnvelope() ? payloadInput.getPayloadEnvelope() : undefined;
1098
1250
 
1099
1251
  let i = 0;
@@ -1102,7 +1254,12 @@ export class BlockInputSync {
1102
1254
  const pendingColumns = payloadInput?.hasAllData()
1103
1255
  ? new Set<number>()
1104
1256
  : new Set(payloadInput?.getMissingSampledColumnMeta().missing ?? []);
1105
- const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
1257
+ // prefer peers that gossiped this payload root to us (#9923)
1258
+ const peerMeta = this.peerBalancer.bestPeerForPendingColumns(
1259
+ pendingColumns,
1260
+ excludedPeers,
1261
+ cacheItem.peerIdStrings
1262
+ );
1106
1263
  if (peerMeta === null) {
1107
1264
  if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
1108
1265
  throw new UnknownBlockRateLimitedError(
@@ -1110,9 +1267,11 @@ export class BlockInputSync {
1110
1267
  );
1111
1268
  }
1112
1269
 
1113
- throw Error(
1114
- `Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`
1115
- );
1270
+ const reason =
1271
+ pendingColumns.size > 0
1272
+ ? `cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`
1273
+ : "no peer available to download pending payload";
1274
+ throw Error(`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: ${reason}`);
1116
1275
  }
1117
1276
 
1118
1277
  const {peerId, client: peerClient} = peerMeta;
@@ -1124,7 +1283,7 @@ export class BlockInputSync {
1124
1283
  slot = envelope.message.payload.slotNumber;
1125
1284
  }
1126
1285
 
1127
- payloadInput ??= this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
1286
+ payloadInput ??= await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
1128
1287
  if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
1129
1288
  // Block not in fork choice yet. Validating now would throw BLOCK_ROOT_UNKNOWN, so keep the downloaded
1130
1289
  // envelope and wait for the block body; reconcilePayloadEnvelope validates once the block lands.
@@ -1309,7 +1468,12 @@ export class BlockInputSync {
1309
1468
  isPendingBlockInput(cacheItem) && isBlockInputColumns(cacheItem.blockInput)
1310
1469
  ? new Set(cacheItem.blockInput.getMissingSampledColumnMeta().missing)
1311
1470
  : defaultPendingColumns;
1312
- const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
1471
+ // prefer peers that gossiped this block root to us (#9923)
1472
+ const peerMeta = this.peerBalancer.bestPeerForPendingColumns(
1473
+ pendingColumns,
1474
+ excludedPeers,
1475
+ cacheItem.peerIdStrings
1476
+ );
1313
1477
  if (peerMeta === null) {
1314
1478
  if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
1315
1479
  throw new UnknownBlockRateLimitedError(
@@ -1457,9 +1621,9 @@ export class BlockInputSync {
1457
1621
  * Downscore all peers that have referenced any of this bad blocks. May report peers multiple times if they have
1458
1622
  * referenced more than one bad block.
1459
1623
  */
1460
- private removeAndDownScoreAllDescendants(block: BlockInputSyncCacheItem): void {
1624
+ private removeAndDownScoreAllDescendants(block: BlockInputSyncCacheItem, headReason: DroppedItemReason): void {
1461
1625
  // Get all blocks that are a descendant of this one
1462
- const badPendingBlocks = this.removeAllDescendants(block);
1626
+ const badPendingBlocks = this.removeAllDescendants(block, headReason, DroppedItemReason.invalidParent);
1463
1627
  // just console log and do not penalize on pending/bad blocks for debugging
1464
1628
  // console.log("removeAndDownscoreAllDescendants", {block});
1465
1629
 
@@ -1486,18 +1650,22 @@ export class BlockInputSync {
1486
1650
  }
1487
1651
 
1488
1652
  // Once a parent payload is invalid, every descendant waiting on that payload lineage becomes unrecoverable too.
1489
- private removePendingPayloadAndDescendants(rootHex: RootHex): void {
1653
+ private removePendingPayloadAndDescendants(rootHex: RootHex, headReason: DroppedItemReason): void {
1490
1654
  // Keep PayloadEnvelopeInput resident in the seen cache. importBlock() owns that object and
1491
1655
  // later validation/finalization logic decides when it can leave memory.
1492
- this.pendingPayloads.delete(rootHex);
1656
+ if (this.pendingPayloads.delete(rootHex)) {
1657
+ this.metrics?.blockInputSync.removedPayloads.inc({reason: headReason}, 1);
1658
+ }
1493
1659
 
1494
1660
  const badPendingBlocks = getAllDescendantBlocks(rootHex, this.pendingBlocks);
1495
- this.metrics?.blockInputSync.removedBlocks.inc(badPendingBlocks.length);
1496
-
1497
1661
  for (const block of badPendingBlocks) {
1498
1662
  const descendantRootHex = getBlockInputSyncCacheItemRootHex(block);
1499
- this.pendingBlocks.delete(descendantRootHex);
1500
- this.pendingPayloads.delete(descendantRootHex);
1663
+ if (this.pendingBlocks.delete(descendantRootHex)) {
1664
+ this.metrics?.blockInputSync.removedBlocks.inc({reason: DroppedItemReason.invalidParent}, 1);
1665
+ }
1666
+ if (this.pendingPayloads.delete(descendantRootHex)) {
1667
+ this.metrics?.blockInputSync.removedPayloads.inc({reason: DroppedItemReason.invalidParent}, 1);
1668
+ }
1501
1669
  this.chain.seenBlockInputCache.prune(descendantRootHex);
1502
1670
  this.logger.debug("Removing pending descendant after invalid parent payload", {
1503
1671
  slot: getBlockInputSyncCacheItemSlot(block),
@@ -1507,18 +1675,25 @@ export class BlockInputSync {
1507
1675
  }
1508
1676
  }
1509
1677
 
1510
- private removeAllDescendants(block: BlockInputSyncCacheItem): BlockInputSyncCacheItem[] {
1678
+ private removeAllDescendants(
1679
+ block: BlockInputSyncCacheItem,
1680
+ headReason: DroppedItemReason,
1681
+ descendantReason: DroppedItemReason = DroppedItemReason.invalidParent
1682
+ ): BlockInputSyncCacheItem[] {
1511
1683
  const rootHex = getBlockInputSyncCacheItemRootHex(block);
1512
1684
  const slot = getBlockInputSyncCacheItemSlot(block);
1513
- // Get all blocks that are a descendant of this one
1685
+ // Get all blocks that are a descendant of this one (index 0 is the head item itself)
1514
1686
  const badPendingBlocks = [block, ...getAllDescendantBlocks(rootHex, this.pendingBlocks)];
1515
1687
 
1516
- this.metrics?.blockInputSync.removedBlocks.inc(badPendingBlocks.length);
1517
-
1518
- for (const block of badPendingBlocks) {
1688
+ for (const [i, block] of badPendingBlocks.entries()) {
1519
1689
  const rootHex = getBlockInputSyncCacheItemRootHex(block);
1520
- this.pendingBlocks.delete(rootHex);
1521
- this.pendingPayloads.delete(rootHex);
1690
+ const reason = i === 0 ? headReason : descendantReason;
1691
+ if (this.pendingBlocks.delete(rootHex)) {
1692
+ this.metrics?.blockInputSync.removedBlocks.inc({reason}, 1);
1693
+ }
1694
+ if (this.pendingPayloads.delete(rootHex)) {
1695
+ this.metrics?.blockInputSync.removedPayloads.inc({reason}, 1);
1696
+ }
1522
1697
  this.chain.seenBlockInputCache.prune(rootHex);
1523
1698
  // Keep PayloadEnvelopeInput resident in the seen cache for consistency with the
1524
1699
  // importBlock()-owned lifecycle.
@@ -1574,6 +1749,13 @@ export class UnknownBlockPeerBalancer {
1574
1749
  this.rateLimitedUntilByPeer.delete(peerId);
1575
1750
  }
1576
1751
 
1752
+ /** Reset all tracked peer state, e.g. when BlockInputSync unsubscribes from the network. */
1753
+ clear(): void {
1754
+ this.peersMeta.clear();
1755
+ this.activeRequests.clear();
1756
+ this.rateLimitedUntilByPeer.clear();
1757
+ }
1758
+
1577
1759
  onRateLimited(peerId: PeerIdStr, rateLimitedUntilMs: number): void {
1578
1760
  this.rateLimitedUntilByPeer.set(peerId, rateLimitedUntilMs);
1579
1761
  }
@@ -1609,11 +1791,17 @@ export class UnknownBlockPeerBalancer {
1609
1791
  }
1610
1792
 
1611
1793
  /**
1612
- * called from fetchBlockInput() where we only have block root and nothing else
1794
+ * called from fetchBlockInput()/fetchPayloadInput() with a block/payload root.
1613
1795
  * excludedPeers are the peers that we requested already so we don't want to try again
1614
1796
  * pendingColumns is empty for prefulu, or the 1st time we we download a block by root
1797
+ * preferredPeers are the peers NetworkProcessor told us gossiped this root (from
1798
+ * pendingBlock/cacheItem.peerIdStrings). They are preferred but not required.
1615
1799
  */
1616
- bestPeerForPendingColumns(pendingColumns: Set<number>, excludedPeers: Set<PeerIdStr>): PeerSyncMeta | null {
1800
+ bestPeerForPendingColumns(
1801
+ pendingColumns: Set<number>,
1802
+ excludedPeers: Set<PeerIdStr>,
1803
+ preferredPeers?: Set<PeerIdStr>
1804
+ ): PeerSyncMeta | null {
1617
1805
  const eligiblePeers = this.filterPeers(pendingColumns, excludedPeers);
1618
1806
  if (eligiblePeers.length === 0) {
1619
1807
  return null;
@@ -1621,7 +1809,9 @@ export class UnknownBlockPeerBalancer {
1621
1809
 
1622
1810
  const sortedEligiblePeers = sortBy(
1623
1811
  shuffle(eligiblePeers),
1624
- // prefer peers with least active req
1812
+ // 1st key: prefer peers we heard about this root from
1813
+ (peerId) => (preferredPeers?.has(peerId) ? 0 : 1),
1814
+ // 2nd key (tiebreak): prefer peers with least active req
1625
1815
  (peerId) => this.activeRequests.get(peerId) ?? 0
1626
1816
  );
1627
1817