@lodestar/beacon-node 1.47.0-dev.f3dcc639a0 → 1.47.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +40 -12
  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 +21 -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/blocks/importExecutionPayload.d.ts +3 -2
  27. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  28. package/lib/chain/blocks/importExecutionPayload.js +11 -9
  29. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  30. package/lib/chain/blocks/index.d.ts.map +1 -1
  31. package/lib/chain/blocks/index.js +14 -6
  32. package/lib/chain/blocks/index.js.map +1 -1
  33. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +2 -1
  34. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  35. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +6 -2
  36. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  37. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +14 -5
  38. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  39. package/lib/chain/blocks/payloadEnvelopeInput/types.js +5 -0
  40. package/lib/chain/blocks/payloadEnvelopeInput/types.js.map +1 -1
  41. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
  42. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +22 -12
  43. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
  44. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +2 -2
  45. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  46. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  47. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  48. package/lib/chain/bls/index.d.ts +1 -1
  49. package/lib/chain/bls/index.d.ts.map +1 -1
  50. package/lib/chain/bls/interface.d.ts +5 -5
  51. package/lib/chain/bls/interface.d.ts.map +1 -1
  52. package/lib/chain/bls/multithread/index.d.ts +5 -11
  53. package/lib/chain/bls/multithread/index.d.ts.map +1 -1
  54. package/lib/chain/bls/multithread/index.js +116 -101
  55. package/lib/chain/bls/multithread/index.js.map +1 -1
  56. package/lib/chain/bls/multithread/jobItem.d.ts +5 -19
  57. package/lib/chain/bls/multithread/jobItem.d.ts.map +1 -1
  58. package/lib/chain/bls/multithread/jobItem.js +10 -65
  59. package/lib/chain/bls/multithread/jobItem.js.map +1 -1
  60. package/lib/chain/bls/multithread/types.d.ts +29 -8
  61. package/lib/chain/bls/multithread/types.d.ts.map +1 -1
  62. package/lib/chain/bls/multithread/types.js +12 -0
  63. package/lib/chain/bls/multithread/types.js.map +1 -1
  64. package/lib/chain/bls/multithread/utils.d.ts +4 -0
  65. package/lib/chain/bls/multithread/utils.d.ts.map +1 -1
  66. package/lib/chain/bls/multithread/utils.js +10 -0
  67. package/lib/chain/bls/multithread/utils.js.map +1 -1
  68. package/lib/chain/bls/multithread/worker.js +49 -23
  69. package/lib/chain/bls/multithread/worker.js.map +1 -1
  70. package/lib/chain/bls/singleThread.d.ts +4 -10
  71. package/lib/chain/bls/singleThread.d.ts.map +1 -1
  72. package/lib/chain/bls/singleThread.js +8 -45
  73. package/lib/chain/bls/singleThread.js.map +1 -1
  74. package/lib/chain/bls/utils.d.ts +4 -4
  75. package/lib/chain/bls/utils.d.ts.map +1 -1
  76. package/lib/chain/bls/utils.js +18 -19
  77. package/lib/chain/bls/utils.js.map +1 -1
  78. package/lib/chain/chain.d.ts +8 -3
  79. package/lib/chain/chain.d.ts.map +1 -1
  80. package/lib/chain/chain.js +20 -7
  81. package/lib/chain/chain.js.map +1 -1
  82. package/lib/chain/errors/blockError.d.ts +1 -0
  83. package/lib/chain/errors/blockError.d.ts.map +1 -1
  84. package/lib/chain/errors/blockError.js +6 -1
  85. package/lib/chain/errors/blockError.js.map +1 -1
  86. package/lib/chain/errors/proposerPreferences.d.ts +6 -0
  87. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
  88. package/lib/chain/errors/proposerPreferences.js +1 -0
  89. package/lib/chain/errors/proposerPreferences.js.map +1 -1
  90. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  91. package/lib/chain/forkChoice/index.js +4 -0
  92. package/lib/chain/forkChoice/index.js.map +1 -1
  93. package/lib/chain/interface.d.ts +6 -2
  94. package/lib/chain/interface.d.ts.map +1 -1
  95. package/lib/chain/interface.js.map +1 -1
  96. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  97. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  98. package/lib/chain/opPools/attestationPool.d.ts +1 -1
  99. package/lib/chain/opPools/attestationPool.d.ts.map +1 -1
  100. package/lib/chain/opPools/attestationPool.js +1 -1
  101. package/lib/chain/opPools/attestationPool.js.map +1 -1
  102. package/lib/chain/opPools/deferredVoluntaryExitPool.d.ts +16 -0
  103. package/lib/chain/opPools/deferredVoluntaryExitPool.d.ts.map +1 -0
  104. package/lib/chain/opPools/deferredVoluntaryExitPool.js +57 -0
  105. package/lib/chain/opPools/deferredVoluntaryExitPool.js.map +1 -0
  106. package/lib/chain/opPools/executionPayloadBidPool.d.ts +7 -2
  107. package/lib/chain/opPools/executionPayloadBidPool.d.ts.map +1 -1
  108. package/lib/chain/opPools/executionPayloadBidPool.js +4 -4
  109. package/lib/chain/opPools/executionPayloadBidPool.js.map +1 -1
  110. package/lib/chain/opPools/index.d.ts +2 -1
  111. package/lib/chain/opPools/index.d.ts.map +1 -1
  112. package/lib/chain/opPools/index.js +1 -0
  113. package/lib/chain/opPools/index.js.map +1 -1
  114. package/lib/chain/opPools/payloadAttestationPool.js +3 -3
  115. package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
  116. package/lib/chain/opPools/syncCommitteeMessagePool.js +1 -1
  117. package/lib/chain/opPools/syncCommitteeMessagePool.js.map +1 -1
  118. package/lib/chain/opPools/syncContributionAndProofPool.js +1 -1
  119. package/lib/chain/opPools/syncContributionAndProofPool.js.map +1 -1
  120. package/lib/chain/opPools/utils.d.ts +1 -1
  121. package/lib/chain/opPools/utils.d.ts.map +1 -1
  122. package/lib/chain/opPools/utils.js +1 -1
  123. package/lib/chain/opPools/utils.js.map +1 -1
  124. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  125. package/lib/chain/prepareNextSlot.js +23 -5
  126. package/lib/chain/prepareNextSlot.js.map +1 -1
  127. package/lib/chain/produceBlock/produceBlockBody.js +1 -1
  128. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  129. package/lib/chain/regen/interface.d.ts +4 -2
  130. package/lib/chain/regen/interface.d.ts.map +1 -1
  131. package/lib/chain/regen/interface.js +2 -0
  132. package/lib/chain/regen/interface.js.map +1 -1
  133. package/lib/chain/regen/queued.d.ts +2 -2
  134. package/lib/chain/regen/queued.d.ts.map +1 -1
  135. package/lib/chain/regen/queued.js +5 -5
  136. package/lib/chain/regen/queued.js.map +1 -1
  137. package/lib/chain/seenCache/seenAttesters.d.ts +9 -5
  138. package/lib/chain/seenCache/seenAttesters.d.ts.map +1 -1
  139. package/lib/chain/seenCache/seenAttesters.js +25 -4
  140. package/lib/chain/seenCache/seenAttesters.js.map +1 -1
  141. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +28 -1
  142. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  143. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +121 -13
  144. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  145. package/lib/chain/stateCache/fifoBlockStateCache.d.ts +5 -1
  146. package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
  147. package/lib/chain/stateCache/fifoBlockStateCache.js +6 -0
  148. package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
  149. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +47 -5
  150. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  151. package/lib/chain/stateCache/persistentCheckpointsCache.js +117 -7
  152. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  153. package/lib/chain/stateCache/types.d.ts +3 -3
  154. package/lib/chain/stateCache/types.d.ts.map +1 -1
  155. package/lib/chain/validation/attestation.d.ts.map +1 -1
  156. package/lib/chain/validation/attestation.js +1 -4
  157. package/lib/chain/validation/attestation.js.map +1 -1
  158. package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
  159. package/lib/chain/validation/attesterSlashing.js +1 -1
  160. package/lib/chain/validation/attesterSlashing.js.map +1 -1
  161. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  162. package/lib/chain/validation/executionPayloadBid.js +3 -4
  163. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  164. package/lib/chain/validation/executionPayloadEnvelope.js +1 -1
  165. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  166. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  167. package/lib/chain/validation/payloadAttestationMessage.js +5 -14
  168. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  169. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  170. package/lib/chain/validation/proposerPreferences.js +12 -3
  171. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  172. package/lib/chain/validation/proposerSlashing.js +1 -1
  173. package/lib/chain/validation/proposerSlashing.js.map +1 -1
  174. package/lib/chain/validation/voluntaryExit.d.ts +9 -1
  175. package/lib/chain/validation/voluntaryExit.d.ts.map +1 -1
  176. package/lib/chain/validation/voluntaryExit.js +46 -1
  177. package/lib/chain/validation/voluntaryExit.js.map +1 -1
  178. package/lib/execution/builder/apiClient.d.ts +64 -0
  179. package/lib/execution/builder/apiClient.d.ts.map +1 -0
  180. package/lib/execution/builder/apiClient.js +184 -0
  181. package/lib/execution/builder/apiClient.js.map +1 -0
  182. package/lib/execution/builder/validateBid.d.ts +22 -0
  183. package/lib/execution/builder/validateBid.d.ts.map +1 -0
  184. package/lib/execution/builder/validateBid.js +111 -0
  185. package/lib/execution/builder/validateBid.js.map +1 -0
  186. package/lib/metrics/metrics/lodestar.d.ts +74 -22
  187. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  188. package/lib/metrics/metrics/lodestar.js +142 -52
  189. package/lib/metrics/metrics/lodestar.js.map +1 -1
  190. package/lib/network/gossip/topic.d.ts +13 -0
  191. package/lib/network/gossip/topic.d.ts.map +1 -1
  192. package/lib/network/gossip/topic.js +37 -0
  193. package/lib/network/gossip/topic.js.map +1 -1
  194. package/lib/network/libp2p/index.d.ts.map +1 -1
  195. package/lib/network/libp2p/index.js +10 -2
  196. package/lib/network/libp2p/index.js.map +1 -1
  197. package/lib/network/network.d.ts.map +1 -1
  198. package/lib/network/network.js +22 -43
  199. package/lib/network/network.js.map +1 -1
  200. package/lib/network/processor/constants.d.ts +7 -0
  201. package/lib/network/processor/constants.d.ts.map +1 -0
  202. package/lib/network/processor/constants.js +7 -0
  203. package/lib/network/processor/constants.js.map +1 -0
  204. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  205. package/lib/network/processor/gossipHandlers.js +6 -2
  206. package/lib/network/processor/gossipHandlers.js.map +1 -1
  207. package/lib/network/processor/index.d.ts +3 -0
  208. package/lib/network/processor/index.d.ts.map +1 -1
  209. package/lib/network/processor/index.js +36 -15
  210. package/lib/network/processor/index.js.map +1 -1
  211. package/lib/network/reqresp/score.d.ts.map +1 -1
  212. package/lib/network/reqresp/score.js +5 -11
  213. package/lib/network/reqresp/score.js.map +1 -1
  214. package/lib/network/util.d.ts +0 -1
  215. package/lib/network/util.d.ts.map +1 -1
  216. package/lib/network/util.js +0 -4
  217. package/lib/network/util.js.map +1 -1
  218. package/lib/node/deferredVoluntaryExitPublisher.d.ts +15 -0
  219. package/lib/node/deferredVoluntaryExitPublisher.d.ts.map +1 -0
  220. package/lib/node/deferredVoluntaryExitPublisher.js +38 -0
  221. package/lib/node/deferredVoluntaryExitPublisher.js.map +1 -0
  222. package/lib/node/nodejs.d.ts +2 -1
  223. package/lib/node/nodejs.d.ts.map +1 -1
  224. package/lib/node/nodejs.js +7 -0
  225. package/lib/node/nodejs.js.map +1 -1
  226. package/lib/sync/range/batch.d.ts +23 -5
  227. package/lib/sync/range/batch.d.ts.map +1 -1
  228. package/lib/sync/range/batch.js +61 -22
  229. package/lib/sync/range/batch.js.map +1 -1
  230. package/lib/sync/range/chain.d.ts +2 -1
  231. package/lib/sync/range/chain.d.ts.map +1 -1
  232. package/lib/sync/range/chain.js +23 -10
  233. package/lib/sync/range/chain.js.map +1 -1
  234. package/lib/sync/types.d.ts +32 -0
  235. package/lib/sync/types.d.ts.map +1 -1
  236. package/lib/sync/types.js +33 -0
  237. package/lib/sync/types.js.map +1 -1
  238. package/lib/sync/unknownBlock.d.ts +15 -2
  239. package/lib/sync/unknownBlock.d.ts.map +1 -1
  240. package/lib/sync/unknownBlock.js +220 -61
  241. package/lib/sync/unknownBlock.js.map +1 -1
  242. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  243. package/lib/sync/utils/downloadByRange.js +16 -11
  244. package/lib/sync/utils/downloadByRange.js.map +1 -1
  245. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  246. package/lib/sync/utils/downloadByRoot.js +3 -1
  247. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  248. package/package.json +35 -35
  249. package/src/api/impl/beacon/blocks/index.ts +47 -15
  250. package/src/api/impl/beacon/pool/index.ts +22 -2
  251. package/src/api/impl/beacon/state/utils.ts +2 -1
  252. package/src/api/impl/events/index.ts +12 -4
  253. package/src/api/impl/lodestar/index.ts +1 -1
  254. package/src/api/impl/validator/index.ts +285 -49
  255. package/src/api/impl/validator/utils.ts +1 -1
  256. package/src/api/rest/base.ts +7 -0
  257. package/src/chain/archiveStore/archiveStore.ts +1 -1
  258. package/src/chain/blocks/importExecutionPayload.ts +12 -11
  259. package/src/chain/blocks/index.ts +18 -6
  260. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +8 -2
  261. package/src/chain/blocks/payloadEnvelopeInput/types.ts +17 -4
  262. package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +25 -16
  263. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +1 -9
  264. package/src/chain/bls/index.ts +1 -1
  265. package/src/chain/bls/interface.ts +7 -3
  266. package/src/chain/bls/multithread/index.ts +148 -120
  267. package/src/chain/bls/multithread/jobItem.ts +12 -82
  268. package/src/chain/bls/multithread/types.ts +33 -8
  269. package/src/chain/bls/multithread/utils.ts +11 -0
  270. package/src/chain/bls/multithread/worker.ts +85 -27
  271. package/src/chain/bls/singleThread.ts +11 -55
  272. package/src/chain/bls/utils.ts +23 -24
  273. package/src/chain/chain.ts +23 -6
  274. package/src/chain/errors/blockError.ts +8 -2
  275. package/src/chain/errors/proposerPreferences.ts +7 -0
  276. package/src/chain/forkChoice/index.ts +4 -0
  277. package/src/chain/interface.ts +6 -1
  278. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  279. package/src/chain/opPools/attestationPool.ts +1 -1
  280. package/src/chain/opPools/deferredVoluntaryExitPool.ts +65 -0
  281. package/src/chain/opPools/executionPayloadBidPool.ts +13 -7
  282. package/src/chain/opPools/index.ts +2 -1
  283. package/src/chain/opPools/payloadAttestationPool.ts +3 -3
  284. package/src/chain/opPools/syncCommitteeMessagePool.ts +1 -1
  285. package/src/chain/opPools/syncContributionAndProofPool.ts +1 -1
  286. package/src/chain/opPools/utils.ts +1 -1
  287. package/src/chain/prepareNextSlot.ts +23 -5
  288. package/src/chain/produceBlock/produceBlockBody.ts +1 -1
  289. package/src/chain/regen/interface.ts +4 -2
  290. package/src/chain/regen/queued.ts +5 -5
  291. package/src/chain/seenCache/seenAttesters.ts +31 -5
  292. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +143 -14
  293. package/src/chain/stateCache/fifoBlockStateCache.ts +8 -1
  294. package/src/chain/stateCache/persistentCheckpointsCache.ts +133 -8
  295. package/src/chain/stateCache/types.ts +3 -3
  296. package/src/chain/validation/attestation.ts +1 -7
  297. package/src/chain/validation/attesterSlashing.ts +1 -8
  298. package/src/chain/validation/executionPayloadBid.ts +3 -4
  299. package/src/chain/validation/executionPayloadEnvelope.ts +0 -1
  300. package/src/chain/validation/payloadAttestationMessage.ts +6 -17
  301. package/src/chain/validation/proposerPreferences.ts +14 -4
  302. package/src/chain/validation/proposerSlashing.ts +1 -1
  303. package/src/chain/validation/voluntaryExit.ts +59 -2
  304. package/src/execution/builder/apiClient.ts +244 -0
  305. package/src/execution/builder/validateBid.ts +166 -0
  306. package/src/metrics/metrics/lodestar.ts +155 -57
  307. package/src/network/gossip/topic.ts +38 -0
  308. package/src/network/libp2p/index.ts +11 -2
  309. package/src/network/network.ts +27 -55
  310. package/src/network/processor/constants.ts +6 -0
  311. package/src/network/processor/gossipHandlers.ts +6 -2
  312. package/src/network/processor/index.ts +42 -19
  313. package/src/network/reqresp/score.ts +5 -11
  314. package/src/network/util.ts +0 -5
  315. package/src/node/deferredVoluntaryExitPublisher.ts +54 -0
  316. package/src/node/nodejs.ts +10 -1
  317. package/src/sync/range/batch.ts +83 -28
  318. package/src/sync/range/chain.ts +26 -11
  319. package/src/sync/types.ts +36 -1
  320. package/src/sync/unknownBlock.ts +258 -68
  321. package/src/sync/utils/downloadByRange.ts +21 -12
  322. package/src/sync/utils/downloadByRoot.ts +3 -1
  323. package/lib/chain/bls/maybeBatch.d.ts +0 -12
  324. package/lib/chain/bls/maybeBatch.d.ts.map +0 -1
  325. package/lib/chain/bls/maybeBatch.js +0 -35
  326. package/lib/chain/bls/maybeBatch.js.map +0 -1
  327. package/src/chain/bls/maybeBatch.ts +0 -45
@@ -1,5 +1,5 @@
1
1
  import { routes } from "@lodestar/api";
2
- import { ForkSeq, isForkPostGloas } 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
  /**
@@ -679,9 +796,9 @@ export class BlockInputSync {
679
796
  // no need to update status to "processed", delete anyway
680
797
  this.pendingBlocks.delete(blockRootHex);
681
798
  if (isForkPostGloas(fork)) {
682
- const payloadInput = this.chain.seenPayloadEnvelopeInputCache.get(blockRootHex);
799
+ const payloadInput = await this.chain.seenPayloadEnvelopeInputCache.getOrReload(blockRootHex);
683
800
  if (!payloadInput) {
684
- this.logger.warn("PayloadEnvelopeInput not seeded during unknown sync processReadyBlock()", logCtx);
801
+ this.logger.debug("PayloadEnvelopeInput unavailable during unknown sync processReadyBlock()", logCtx);
685
802
  }
686
803
  else {
687
804
  // Similar to the gossip path: immediately attempt fetch of data columns from the execution engine.
@@ -717,17 +834,17 @@ export class BlockInputSync {
717
834
  case BlockErrorCode.EXECUTION_ENGINE_ERROR:
718
835
  // Removing the block(s) without penalizing the peers, hoping for EL to
719
836
  // recover on a latter download + verify attempt.
720
- this.removeAllDescendants(pendingBlock);
837
+ this.removeAllDescendants(pendingBlock, DroppedItemReason.elError, DroppedItemReason.elError);
721
838
  break;
722
839
  case BlockErrorCode.EXECUTION_ENGINE_INVALID:
723
840
  // the peer served a bad block
724
841
  this.logger.debug("Execution engine rejected block from unknown parent sync", errorData, res.err);
725
- this.removeAndDownScoreAllDescendants(pendingBlock);
842
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.elInvalid);
726
843
  break;
727
844
  default:
728
845
  // Block is not correct with respect to our chain. Log error loudly
729
846
  this.logger.debug("Error processing block from unknown parent sync", errorData, res.err);
730
- this.removeAndDownScoreAllDescendants(pendingBlock);
847
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidBlock);
731
848
  }
732
849
  }
733
850
  // Probably a queue error or something unwanted happened, mark as pending to try again latter
@@ -748,7 +865,6 @@ export class BlockInputSync {
748
865
  this.pendingPayloads.delete(rootHex);
749
866
  return;
750
867
  }
751
- const payloadInput = this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
752
868
  if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
753
869
  // Block not in fork choice yet. payloadInput may be seeded from the block body during download, so a
754
870
  // non-null payloadInput does not imply the block is imported; defer regardless and pull the block first.
@@ -762,8 +878,9 @@ export class BlockInputSync {
762
878
  }
763
879
  return;
764
880
  }
881
+ const payloadInput = await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
765
882
  if (!payloadInput) {
766
- 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", {
767
884
  root: rootHex,
768
885
  });
769
886
  return;
@@ -775,6 +892,7 @@ export class BlockInputSync {
775
892
  const pendingPayloadByRoot = {
776
893
  status: PendingPayloadInputStatus.pending,
777
894
  rootHex,
895
+ slot: pendingPayload.envelope.message.payload.slotNumber,
778
896
  timeAddedSec: pendingPayload.timeAddedSec,
779
897
  peerIdStrings: new Set(pendingPayload.peerIdStrings),
780
898
  };
@@ -806,6 +924,9 @@ export class BlockInputSync {
806
924
  if (payload.status !== PendingPayloadInputStatus.pending) {
807
925
  return;
808
926
  }
927
+ if (!isPendingPayloadInput(payload) && payload.slot === undefined) {
928
+ payload.slot = this.resolvePayloadSlot(rootHex);
929
+ }
809
930
  const payloadSlot = getPayloadSyncCacheItemSlot(payload);
810
931
  const logCtx = {
811
932
  slot: payloadSlot,
@@ -818,7 +939,13 @@ export class BlockInputSync {
818
939
  const res = await wrapError(this.fetchPayloadInput(payload));
819
940
  if (!res.err) {
820
941
  const pendingPayload = res.result;
821
- 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);
822
949
  if (isPendingPayloadEnvelope(pendingPayload)) {
823
950
  await this.reconcilePayloadEnvelope(pendingPayload);
824
951
  }
@@ -885,16 +1012,24 @@ export class BlockInputSync {
885
1012
  pendingPayload.status = PendingPayloadInputStatus.downloaded;
886
1013
  break;
887
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;
888
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;
889
1022
  case PayloadErrorCode.INVALID_SIGNATURE:
890
1023
  // TODO GLOAS: Decide how invalid payload inputs should eventually leave memory without
891
1024
  // reintroducing envelope replacement / recreation flows.
892
1025
  this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
893
- this.removePendingPayloadAndDescendants(rootHex);
1026
+ this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidSignature);
894
1027
  break;
895
1028
  default:
896
1029
  this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
897
- this.pendingPayloads.delete(rootHex);
1030
+ if (this.pendingPayloads.delete(rootHex)) {
1031
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: DroppedItemReason.unknown }, 1);
1032
+ }
898
1033
  }
899
1034
  return;
900
1035
  }
@@ -913,7 +1048,7 @@ export class BlockInputSync {
913
1048
  let slot = getPayloadSyncCacheItemSlot(cacheItem);
914
1049
  let payloadInput = isPendingPayloadInput(cacheItem)
915
1050
  ? cacheItem.payloadInput
916
- : this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
1051
+ : await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
917
1052
  let envelope = payloadInput?.hasPayloadEnvelope() ? payloadInput.getPayloadEnvelope() : undefined;
918
1053
  let i = 0;
919
1054
  let deferredByRateLimit = false;
@@ -921,12 +1056,16 @@ export class BlockInputSync {
921
1056
  const pendingColumns = payloadInput?.hasAllData()
922
1057
  ? new Set()
923
1058
  : new Set(payloadInput?.getMissingSampledColumnMeta().missing ?? []);
924
- 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);
925
1061
  if (peerMeta === null) {
926
1062
  if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
927
1063
  throw new UnknownBlockRateLimitedError(`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`);
928
1064
  }
929
- 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}`);
930
1069
  }
931
1070
  const { peerId, client: peerClient } = peerMeta;
932
1071
  cacheItem.peerIdStrings.add(peerId);
@@ -935,7 +1074,7 @@ export class BlockInputSync {
935
1074
  envelope = await this.fetchExecutionPayloadEnvelope(peerId, blockRoot, rootHex);
936
1075
  slot = envelope.message.payload.slotNumber;
937
1076
  }
938
- payloadInput ??= this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
1077
+ payloadInput ??= await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
939
1078
  if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
940
1079
  // Block not in fork choice yet. Validating now would throw BLOCK_ROOT_UNKNOWN, so keep the downloaded
941
1080
  // envelope and wait for the block body; reconcilePayloadEnvelope validates once the block lands.
@@ -1080,7 +1219,8 @@ export class BlockInputSync {
1080
1219
  const pendingColumns = isPendingBlockInput(cacheItem) && isBlockInputColumns(cacheItem.blockInput)
1081
1220
  ? new Set(cacheItem.blockInput.getMissingSampledColumnMeta().missing)
1082
1221
  : defaultPendingColumns;
1083
- 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);
1084
1224
  if (peerMeta === null) {
1085
1225
  if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
1086
1226
  throw new UnknownBlockRateLimitedError(`Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`);
@@ -1206,9 +1346,9 @@ export class BlockInputSync {
1206
1346
  * Downscore all peers that have referenced any of this bad blocks. May report peers multiple times if they have
1207
1347
  * referenced more than one bad block.
1208
1348
  */
1209
- removeAndDownScoreAllDescendants(block) {
1349
+ removeAndDownScoreAllDescendants(block, headReason) {
1210
1350
  // Get all blocks that are a descendant of this one
1211
- const badPendingBlocks = this.removeAllDescendants(block);
1351
+ const badPendingBlocks = this.removeAllDescendants(block, headReason, DroppedItemReason.invalidParent);
1212
1352
  // just console log and do not penalize on pending/bad blocks for debugging
1213
1353
  // console.log("removeAndDownscoreAllDescendants", {block});
1214
1354
  for (const block of badPendingBlocks) {
@@ -1232,16 +1372,21 @@ export class BlockInputSync {
1232
1372
  pruneSetToMax(this.knownBadBlocks, MAX_KNOWN_BAD_BLOCKS);
1233
1373
  }
1234
1374
  // Once a parent payload is invalid, every descendant waiting on that payload lineage becomes unrecoverable too.
1235
- removePendingPayloadAndDescendants(rootHex) {
1375
+ removePendingPayloadAndDescendants(rootHex, headReason) {
1236
1376
  // Keep PayloadEnvelopeInput resident in the seen cache. importBlock() owns that object and
1237
1377
  // later validation/finalization logic decides when it can leave memory.
1238
- this.pendingPayloads.delete(rootHex);
1378
+ if (this.pendingPayloads.delete(rootHex)) {
1379
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: headReason }, 1);
1380
+ }
1239
1381
  const badPendingBlocks = getAllDescendantBlocks(rootHex, this.pendingBlocks);
1240
- this.metrics?.blockInputSync.removedBlocks.inc(badPendingBlocks.length);
1241
1382
  for (const block of badPendingBlocks) {
1242
1383
  const descendantRootHex = getBlockInputSyncCacheItemRootHex(block);
1243
- this.pendingBlocks.delete(descendantRootHex);
1244
- 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
+ }
1245
1390
  this.chain.seenBlockInputCache.prune(descendantRootHex);
1246
1391
  this.logger.debug("Removing pending descendant after invalid parent payload", {
1247
1392
  slot: getBlockInputSyncCacheItemSlot(block),
@@ -1250,16 +1395,20 @@ export class BlockInputSync {
1250
1395
  });
1251
1396
  }
1252
1397
  }
1253
- removeAllDescendants(block) {
1398
+ removeAllDescendants(block, headReason, descendantReason = DroppedItemReason.invalidParent) {
1254
1399
  const rootHex = getBlockInputSyncCacheItemRootHex(block);
1255
1400
  const slot = getBlockInputSyncCacheItemSlot(block);
1256
- // 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)
1257
1402
  const badPendingBlocks = [block, ...getAllDescendantBlocks(rootHex, this.pendingBlocks)];
1258
- this.metrics?.blockInputSync.removedBlocks.inc(badPendingBlocks.length);
1259
- for (const block of badPendingBlocks) {
1403
+ for (const [i, block] of badPendingBlocks.entries()) {
1260
1404
  const rootHex = getBlockInputSyncCacheItemRootHex(block);
1261
- this.pendingBlocks.delete(rootHex);
1262
- 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
+ }
1263
1412
  this.chain.seenBlockInputCache.prune(rootHex);
1264
1413
  // Keep PayloadEnvelopeInput resident in the seen cache for consistency with the
1265
1414
  // importBlock()-owned lifecycle.
@@ -1303,6 +1452,12 @@ export class UnknownBlockPeerBalancer {
1303
1452
  this.activeRequests.delete(peerId);
1304
1453
  this.rateLimitedUntilByPeer.delete(peerId);
1305
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
+ }
1306
1461
  onRateLimited(peerId, rateLimitedUntilMs) {
1307
1462
  this.rateLimitedUntilByPeer.set(peerId, rateLimitedUntilMs);
1308
1463
  }
@@ -1330,17 +1485,21 @@ export class UnknownBlockPeerBalancer {
1330
1485
  return retryAt;
1331
1486
  }
1332
1487
  /**
1333
- * called from fetchBlockInput() where we only have block root and nothing else
1488
+ * called from fetchBlockInput()/fetchPayloadInput() with a block/payload root.
1334
1489
  * excludedPeers are the peers that we requested already so we don't want to try again
1335
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.
1336
1493
  */
1337
- bestPeerForPendingColumns(pendingColumns, excludedPeers) {
1494
+ bestPeerForPendingColumns(pendingColumns, excludedPeers, preferredPeers) {
1338
1495
  const eligiblePeers = this.filterPeers(pendingColumns, excludedPeers);
1339
1496
  if (eligiblePeers.length === 0) {
1340
1497
  return null;
1341
1498
  }
1342
1499
  const sortedEligiblePeers = sortBy(shuffle(eligiblePeers),
1343
- // 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
1344
1503
  (peerId) => this.activeRequests.get(peerId) ?? 0);
1345
1504
  const bestPeerId = sortedEligiblePeers[0];
1346
1505
  this.onRequest(bestPeerId);