@lodestar/beacon-node 1.47.0-dev.0bcaaaebb5 → 1.47.0-dev.101e2c290d

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 (332) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +6 -8
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/pool/index.js +25 -2
  6. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  7. package/lib/api/impl/beacon/state/utils.d.ts +2 -1
  8. package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
  9. package/lib/api/impl/beacon/state/utils.js.map +1 -1
  10. package/lib/api/impl/events/index.d.ts +1 -1
  11. package/lib/api/impl/events/index.d.ts.map +1 -1
  12. package/lib/api/impl/events/index.js +12 -3
  13. package/lib/api/impl/events/index.js.map +1 -1
  14. package/lib/api/impl/lodestar/index.js +1 -1
  15. package/lib/api/impl/lodestar/index.js.map +1 -1
  16. package/lib/api/impl/validator/index.js +1 -1
  17. package/lib/api/impl/validator/index.js.map +1 -1
  18. package/lib/api/rest/base.d.ts.map +1 -1
  19. package/lib/api/rest/base.js +7 -0
  20. package/lib/api/rest/base.js.map +1 -1
  21. package/lib/chain/archiveStore/archiveStore.js +1 -1
  22. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  23. package/lib/chain/archiveStore/historicalState/metrics.d.ts.map +1 -1
  24. package/lib/chain/archiveStore/historicalState/metrics.js +21 -0
  25. package/lib/chain/archiveStore/historicalState/metrics.js.map +1 -1
  26. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  27. package/lib/chain/blocks/importBlock.js +2 -7
  28. package/lib/chain/blocks/importBlock.js.map +1 -1
  29. package/lib/chain/blocks/importExecutionPayload.d.ts +3 -2
  30. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  31. package/lib/chain/blocks/importExecutionPayload.js +11 -9
  32. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  33. package/lib/chain/blocks/index.d.ts.map +1 -1
  34. package/lib/chain/blocks/index.js +14 -6
  35. package/lib/chain/blocks/index.js.map +1 -1
  36. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +2 -1
  37. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  38. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +6 -2
  39. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  40. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +14 -5
  41. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  42. package/lib/chain/blocks/payloadEnvelopeInput/types.js +5 -0
  43. package/lib/chain/blocks/payloadEnvelopeInput/types.js.map +1 -1
  44. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
  45. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +22 -12
  46. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
  47. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +2 -2
  48. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  49. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  50. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  51. package/lib/chain/bls/index.d.ts +1 -1
  52. package/lib/chain/bls/index.d.ts.map +1 -1
  53. package/lib/chain/bls/interface.d.ts +5 -5
  54. package/lib/chain/bls/interface.d.ts.map +1 -1
  55. package/lib/chain/bls/multithread/index.d.ts +5 -11
  56. package/lib/chain/bls/multithread/index.d.ts.map +1 -1
  57. package/lib/chain/bls/multithread/index.js +116 -101
  58. package/lib/chain/bls/multithread/index.js.map +1 -1
  59. package/lib/chain/bls/multithread/jobItem.d.ts +5 -19
  60. package/lib/chain/bls/multithread/jobItem.d.ts.map +1 -1
  61. package/lib/chain/bls/multithread/jobItem.js +10 -65
  62. package/lib/chain/bls/multithread/jobItem.js.map +1 -1
  63. package/lib/chain/bls/multithread/types.d.ts +29 -8
  64. package/lib/chain/bls/multithread/types.d.ts.map +1 -1
  65. package/lib/chain/bls/multithread/types.js +12 -0
  66. package/lib/chain/bls/multithread/types.js.map +1 -1
  67. package/lib/chain/bls/multithread/utils.d.ts +4 -0
  68. package/lib/chain/bls/multithread/utils.d.ts.map +1 -1
  69. package/lib/chain/bls/multithread/utils.js +10 -0
  70. package/lib/chain/bls/multithread/utils.js.map +1 -1
  71. package/lib/chain/bls/multithread/worker.js +49 -23
  72. package/lib/chain/bls/multithread/worker.js.map +1 -1
  73. package/lib/chain/bls/singleThread.d.ts +4 -10
  74. package/lib/chain/bls/singleThread.d.ts.map +1 -1
  75. package/lib/chain/bls/singleThread.js +8 -45
  76. package/lib/chain/bls/singleThread.js.map +1 -1
  77. package/lib/chain/bls/utils.d.ts +4 -4
  78. package/lib/chain/bls/utils.d.ts.map +1 -1
  79. package/lib/chain/bls/utils.js +18 -19
  80. package/lib/chain/bls/utils.js.map +1 -1
  81. package/lib/chain/builderCircuitBreaker.d.ts +6 -5
  82. package/lib/chain/builderCircuitBreaker.d.ts.map +1 -1
  83. package/lib/chain/builderCircuitBreaker.js +28 -15
  84. package/lib/chain/builderCircuitBreaker.js.map +1 -1
  85. package/lib/chain/chain.d.ts +4 -2
  86. package/lib/chain/chain.d.ts.map +1 -1
  87. package/lib/chain/chain.js +16 -6
  88. package/lib/chain/chain.js.map +1 -1
  89. package/lib/chain/errors/blockError.d.ts +1 -0
  90. package/lib/chain/errors/blockError.d.ts.map +1 -1
  91. package/lib/chain/errors/blockError.js +6 -1
  92. package/lib/chain/errors/blockError.js.map +1 -1
  93. package/lib/chain/errors/proposerPreferences.d.ts +6 -0
  94. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
  95. package/lib/chain/errors/proposerPreferences.js +1 -0
  96. package/lib/chain/errors/proposerPreferences.js.map +1 -1
  97. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  98. package/lib/chain/forkChoice/index.js +4 -0
  99. package/lib/chain/forkChoice/index.js.map +1 -1
  100. package/lib/chain/interface.d.ts +4 -2
  101. package/lib/chain/interface.d.ts.map +1 -1
  102. package/lib/chain/interface.js.map +1 -1
  103. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  104. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  105. package/lib/chain/opPools/attestationPool.d.ts +1 -1
  106. package/lib/chain/opPools/attestationPool.d.ts.map +1 -1
  107. package/lib/chain/opPools/attestationPool.js +1 -1
  108. package/lib/chain/opPools/attestationPool.js.map +1 -1
  109. package/lib/chain/opPools/deferredVoluntaryExitPool.d.ts +16 -0
  110. package/lib/chain/opPools/deferredVoluntaryExitPool.d.ts.map +1 -0
  111. package/lib/chain/opPools/deferredVoluntaryExitPool.js +57 -0
  112. package/lib/chain/opPools/deferredVoluntaryExitPool.js.map +1 -0
  113. package/lib/chain/opPools/index.d.ts +1 -0
  114. package/lib/chain/opPools/index.d.ts.map +1 -1
  115. package/lib/chain/opPools/index.js +1 -0
  116. package/lib/chain/opPools/index.js.map +1 -1
  117. package/lib/chain/opPools/payloadAttestationPool.js +3 -3
  118. package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
  119. package/lib/chain/opPools/syncCommitteeMessagePool.js +1 -1
  120. package/lib/chain/opPools/syncCommitteeMessagePool.js.map +1 -1
  121. package/lib/chain/opPools/syncContributionAndProofPool.js +1 -1
  122. package/lib/chain/opPools/syncContributionAndProofPool.js.map +1 -1
  123. package/lib/chain/opPools/utils.d.ts +1 -1
  124. package/lib/chain/opPools/utils.d.ts.map +1 -1
  125. package/lib/chain/opPools/utils.js +1 -1
  126. package/lib/chain/opPools/utils.js.map +1 -1
  127. package/lib/chain/options.d.ts +1 -1
  128. package/lib/chain/options.d.ts.map +1 -1
  129. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  130. package/lib/chain/prepareNextSlot.js +24 -6
  131. package/lib/chain/prepareNextSlot.js.map +1 -1
  132. package/lib/chain/regen/interface.d.ts +4 -2
  133. package/lib/chain/regen/interface.d.ts.map +1 -1
  134. package/lib/chain/regen/interface.js +2 -0
  135. package/lib/chain/regen/interface.js.map +1 -1
  136. package/lib/chain/regen/queued.d.ts +2 -2
  137. package/lib/chain/regen/queued.d.ts.map +1 -1
  138. package/lib/chain/regen/queued.js +5 -5
  139. package/lib/chain/regen/queued.js.map +1 -1
  140. package/lib/chain/seenCache/seenAttesters.d.ts +9 -5
  141. package/lib/chain/seenCache/seenAttesters.d.ts.map +1 -1
  142. package/lib/chain/seenCache/seenAttesters.js +25 -4
  143. package/lib/chain/seenCache/seenAttesters.js.map +1 -1
  144. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +28 -1
  145. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  146. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +121 -13
  147. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  148. package/lib/chain/stateCache/fifoBlockStateCache.d.ts +5 -1
  149. package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
  150. package/lib/chain/stateCache/fifoBlockStateCache.js +6 -0
  151. package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
  152. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +47 -5
  153. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  154. package/lib/chain/stateCache/persistentCheckpointsCache.js +117 -7
  155. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  156. package/lib/chain/stateCache/types.d.ts +3 -3
  157. package/lib/chain/stateCache/types.d.ts.map +1 -1
  158. package/lib/chain/validation/attestation.d.ts.map +1 -1
  159. package/lib/chain/validation/attestation.js +1 -4
  160. package/lib/chain/validation/attestation.js.map +1 -1
  161. package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
  162. package/lib/chain/validation/attesterSlashing.js +1 -1
  163. package/lib/chain/validation/attesterSlashing.js.map +1 -1
  164. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  165. package/lib/chain/validation/executionPayloadBid.js +1 -2
  166. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  167. package/lib/chain/validation/executionPayloadEnvelope.js +1 -1
  168. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  169. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  170. package/lib/chain/validation/payloadAttestationMessage.js +5 -14
  171. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  172. package/lib/chain/validation/proposerPreferences.d.ts +1 -1
  173. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  174. package/lib/chain/validation/proposerPreferences.js +13 -4
  175. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  176. package/lib/chain/validation/proposerSlashing.js +1 -1
  177. package/lib/chain/validation/proposerSlashing.js.map +1 -1
  178. package/lib/chain/validation/voluntaryExit.d.ts +9 -1
  179. package/lib/chain/validation/voluntaryExit.d.ts.map +1 -1
  180. package/lib/chain/validation/voluntaryExit.js +46 -1
  181. package/lib/chain/validation/voluntaryExit.js.map +1 -1
  182. package/lib/execution/builder/http.d.ts +10 -7
  183. package/lib/execution/builder/http.d.ts.map +1 -1
  184. package/lib/execution/builder/http.js +11 -8
  185. package/lib/execution/builder/http.js.map +1 -1
  186. package/lib/metrics/metrics/beacon.d.ts +3 -3
  187. package/lib/metrics/metrics/beacon.d.ts.map +1 -1
  188. package/lib/metrics/metrics/beacon.js +9 -9
  189. package/lib/metrics/metrics/beacon.js.map +1 -1
  190. package/lib/metrics/metrics/lodestar.d.ts +72 -22
  191. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  192. package/lib/metrics/metrics/lodestar.js +130 -52
  193. package/lib/metrics/metrics/lodestar.js.map +1 -1
  194. package/lib/network/gossip/topic.d.ts +13 -0
  195. package/lib/network/gossip/topic.d.ts.map +1 -1
  196. package/lib/network/gossip/topic.js +37 -0
  197. package/lib/network/gossip/topic.js.map +1 -1
  198. package/lib/network/libp2p/index.d.ts.map +1 -1
  199. package/lib/network/libp2p/index.js +10 -2
  200. package/lib/network/libp2p/index.js.map +1 -1
  201. package/lib/network/network.d.ts.map +1 -1
  202. package/lib/network/network.js +22 -43
  203. package/lib/network/network.js.map +1 -1
  204. package/lib/network/processor/constants.d.ts +7 -0
  205. package/lib/network/processor/constants.d.ts.map +1 -0
  206. package/lib/network/processor/constants.js +7 -0
  207. package/lib/network/processor/constants.js.map +1 -0
  208. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  209. package/lib/network/processor/gossipHandlers.js +12 -3
  210. package/lib/network/processor/gossipHandlers.js.map +1 -1
  211. package/lib/network/processor/index.d.ts +3 -0
  212. package/lib/network/processor/index.d.ts.map +1 -1
  213. package/lib/network/processor/index.js +36 -15
  214. package/lib/network/processor/index.js.map +1 -1
  215. package/lib/network/reqresp/score.d.ts.map +1 -1
  216. package/lib/network/reqresp/score.js +5 -11
  217. package/lib/network/reqresp/score.js.map +1 -1
  218. package/lib/network/util.d.ts +0 -1
  219. package/lib/network/util.d.ts.map +1 -1
  220. package/lib/network/util.js +0 -4
  221. package/lib/network/util.js.map +1 -1
  222. package/lib/node/deferredVoluntaryExitPublisher.d.ts +15 -0
  223. package/lib/node/deferredVoluntaryExitPublisher.d.ts.map +1 -0
  224. package/lib/node/deferredVoluntaryExitPublisher.js +38 -0
  225. package/lib/node/deferredVoluntaryExitPublisher.js.map +1 -0
  226. package/lib/node/nodejs.d.ts +2 -1
  227. package/lib/node/nodejs.d.ts.map +1 -1
  228. package/lib/node/nodejs.js +2 -0
  229. package/lib/node/nodejs.js.map +1 -1
  230. package/lib/sync/range/batch.d.ts +23 -5
  231. package/lib/sync/range/batch.d.ts.map +1 -1
  232. package/lib/sync/range/batch.js +61 -22
  233. package/lib/sync/range/batch.js.map +1 -1
  234. package/lib/sync/range/chain.d.ts +2 -1
  235. package/lib/sync/range/chain.d.ts.map +1 -1
  236. package/lib/sync/range/chain.js +23 -10
  237. package/lib/sync/range/chain.js.map +1 -1
  238. package/lib/sync/types.d.ts +32 -0
  239. package/lib/sync/types.d.ts.map +1 -1
  240. package/lib/sync/types.js +33 -0
  241. package/lib/sync/types.js.map +1 -1
  242. package/lib/sync/unknownBlock.d.ts +13 -2
  243. package/lib/sync/unknownBlock.d.ts.map +1 -1
  244. package/lib/sync/unknownBlock.js +218 -63
  245. package/lib/sync/unknownBlock.js.map +1 -1
  246. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  247. package/lib/sync/utils/downloadByRange.js +16 -11
  248. package/lib/sync/utils/downloadByRange.js.map +1 -1
  249. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  250. package/lib/sync/utils/downloadByRoot.js +3 -1
  251. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  252. package/package.json +35 -35
  253. package/src/api/impl/beacon/blocks/index.ts +7 -8
  254. package/src/api/impl/beacon/pool/index.ts +27 -2
  255. package/src/api/impl/beacon/state/utils.ts +2 -1
  256. package/src/api/impl/events/index.ts +12 -4
  257. package/src/api/impl/lodestar/index.ts +1 -1
  258. package/src/api/impl/validator/index.ts +1 -1
  259. package/src/api/rest/base.ts +7 -0
  260. package/src/chain/archiveStore/archiveStore.ts +1 -1
  261. package/src/chain/archiveStore/historicalState/metrics.ts +22 -0
  262. package/src/chain/blocks/importBlock.ts +1 -6
  263. package/src/chain/blocks/importExecutionPayload.ts +12 -11
  264. package/src/chain/blocks/index.ts +18 -6
  265. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +8 -2
  266. package/src/chain/blocks/payloadEnvelopeInput/types.ts +17 -4
  267. package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +25 -16
  268. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +1 -9
  269. package/src/chain/bls/index.ts +1 -1
  270. package/src/chain/bls/interface.ts +7 -3
  271. package/src/chain/bls/multithread/index.ts +148 -120
  272. package/src/chain/bls/multithread/jobItem.ts +12 -82
  273. package/src/chain/bls/multithread/types.ts +33 -8
  274. package/src/chain/bls/multithread/utils.ts +11 -0
  275. package/src/chain/bls/multithread/worker.ts +85 -27
  276. package/src/chain/bls/singleThread.ts +11 -55
  277. package/src/chain/bls/utils.ts +23 -24
  278. package/src/chain/builderCircuitBreaker.ts +31 -17
  279. package/src/chain/chain.ts +17 -6
  280. package/src/chain/errors/blockError.ts +8 -2
  281. package/src/chain/errors/proposerPreferences.ts +7 -0
  282. package/src/chain/forkChoice/index.ts +4 -0
  283. package/src/chain/interface.ts +4 -1
  284. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  285. package/src/chain/opPools/attestationPool.ts +1 -1
  286. package/src/chain/opPools/deferredVoluntaryExitPool.ts +65 -0
  287. package/src/chain/opPools/index.ts +1 -0
  288. package/src/chain/opPools/payloadAttestationPool.ts +3 -3
  289. package/src/chain/opPools/syncCommitteeMessagePool.ts +1 -1
  290. package/src/chain/opPools/syncContributionAndProofPool.ts +1 -1
  291. package/src/chain/opPools/utils.ts +1 -1
  292. package/src/chain/options.ts +1 -1
  293. package/src/chain/prepareNextSlot.ts +24 -6
  294. package/src/chain/regen/interface.ts +4 -2
  295. package/src/chain/regen/queued.ts +5 -5
  296. package/src/chain/seenCache/seenAttesters.ts +31 -5
  297. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +143 -14
  298. package/src/chain/stateCache/fifoBlockStateCache.ts +8 -1
  299. package/src/chain/stateCache/persistentCheckpointsCache.ts +133 -8
  300. package/src/chain/stateCache/types.ts +3 -3
  301. package/src/chain/validation/attestation.ts +1 -7
  302. package/src/chain/validation/attesterSlashing.ts +1 -8
  303. package/src/chain/validation/executionPayloadBid.ts +1 -2
  304. package/src/chain/validation/executionPayloadEnvelope.ts +0 -1
  305. package/src/chain/validation/payloadAttestationMessage.ts +6 -17
  306. package/src/chain/validation/proposerPreferences.ts +15 -5
  307. package/src/chain/validation/proposerSlashing.ts +1 -1
  308. package/src/chain/validation/voluntaryExit.ts +59 -2
  309. package/src/execution/builder/http.ts +11 -11
  310. package/src/metrics/metrics/beacon.ts +9 -9
  311. package/src/metrics/metrics/lodestar.ts +142 -57
  312. package/src/network/gossip/topic.ts +38 -0
  313. package/src/network/libp2p/index.ts +11 -2
  314. package/src/network/network.ts +27 -55
  315. package/src/network/processor/constants.ts +6 -0
  316. package/src/network/processor/gossipHandlers.ts +14 -1
  317. package/src/network/processor/index.ts +42 -19
  318. package/src/network/reqresp/score.ts +5 -11
  319. package/src/network/util.ts +0 -5
  320. package/src/node/deferredVoluntaryExitPublisher.ts +54 -0
  321. package/src/node/nodejs.ts +5 -1
  322. package/src/sync/range/batch.ts +83 -28
  323. package/src/sync/range/chain.ts +26 -11
  324. package/src/sync/types.ts +36 -1
  325. package/src/sync/unknownBlock.ts +255 -70
  326. package/src/sync/utils/downloadByRange.ts +21 -12
  327. package/src/sync/utils/downloadByRoot.ts +3 -1
  328. package/lib/chain/bls/maybeBatch.d.ts +0 -12
  329. package/lib/chain/bls/maybeBatch.d.ts.map +0 -1
  330. package/lib/chain/bls/maybeBatch.js +0 -35
  331. package/lib/chain/bls/maybeBatch.js.map +0 -1
  332. 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} 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,6 +161,7 @@ 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);
@@ -170,6 +178,7 @@ export class BlockInputSync {
170
178
  this.chain.emitter.off(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
171
179
  this.chain.emitter.off(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
172
180
  this.chain.emitter.off(ChainEvent.blockUnknownParent, this.onUnknownParent);
181
+ this.chain.emitter.off(ChainEvent.forkChoiceFinalized, this.pruneFinalized);
173
182
  this.chain.emitter.off(routes.events.EventType.block, this.onBlockImported);
174
183
  this.chain.emitter.off(routes.events.EventType.executionPayload, this.onPayloadImported);
175
184
  this.network.events.off(NetworkEvent.peerConnected, this.onPeerConnected);
@@ -190,10 +199,12 @@ export class BlockInputSync {
190
199
  */
191
200
  private onUnknownBlockRoot = (data: ChainEventData[ChainEvent.unknownBlockRoot]): void => {
192
201
  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});
202
+ const isNewRoot = this.addByRootHex(data.rootHex, data.peer);
203
+ if (isNewRoot) {
204
+ this.triggerUnknownBlockSearch();
205
+ this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.UNKNOWN_BLOCK_ROOT});
206
+ this.metrics?.blockInputSync.source.inc({source: data.source});
207
+ }
197
208
  } catch (e) {
198
209
  this.logger.debug("Error handling unknownBlockRoot event", {}, e as Error);
199
210
  }
@@ -215,10 +226,12 @@ export class BlockInputSync {
215
226
 
216
227
  private onUnknownEnvelopeBlockRoot = (data: ChainEventData[ChainEvent.unknownEnvelopeBlockRoot]): void => {
217
228
  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});
229
+ const isNewRoot = this.addByPayloadRootHex(data.rootHex, data.peer, this.resolvePayloadSlot(data.rootHex));
230
+ if (isNewRoot) {
231
+ this.triggerUnknownBlockSearch();
232
+ this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.UNKNOWN_PAYLOAD_BLOCK_ROOT});
233
+ this.metrics?.blockInputSync.payloadSource.inc({source: data.source});
234
+ }
222
235
  } catch (e) {
223
236
  this.logger.debug("Error handling unknownEnvelopeBlockRoot event", {}, e as Error);
224
237
  }
@@ -252,13 +265,13 @@ export class BlockInputSync {
252
265
  parentRoot: missingDependency.parentRootHex,
253
266
  parentBlockHash: missingDependency.parentBlockHashHex,
254
267
  });
255
- this.removeAndDownScoreAllDescendants(pendingBlock);
268
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidParentPayload);
256
269
  }
257
270
  return;
258
271
  }
259
272
 
260
273
  if (missingDependency.kind === "parentPayload") {
261
- this.addByPayloadRootHex(missingDependency.rootHex, data.peer);
274
+ this.addByPayloadRootHex(missingDependency.rootHex, data.peer, missingDependency.slot);
262
275
  } else if (missingDependency.kind === "parentBlock") {
263
276
  this.addByRootHex(missingDependency.rootHex, data.peer);
264
277
  }
@@ -284,6 +297,80 @@ export class BlockInputSync {
284
297
  this.triggerUnknownBlockSearch();
285
298
  };
286
299
 
300
+ /**
301
+ * Drop pending payloads and blocks that fell below the finalized slot.
302
+ * See https://github.com/ChainSafe/lodestar/issues/9800
303
+ */
304
+ private pruneFinalized = (checkpoint: CheckpointWithHex): void => {
305
+ // Use the actual finalized block slot, not computeStartSlotAtEpoch(checkpoint.epoch)
306
+ // because the payload at the finalized block is not finalized
307
+ const finalizedSlot = this.chain.forkChoice.getFinalizedBlock().slot;
308
+ const nowSec = Date.now() / 1000;
309
+ const maxSecInCache = (PRUNE_UNRESOLVED_SLOT_EPOCHS * SLOTS_PER_EPOCH * this.config.SLOT_DURATION_MS) / 1000;
310
+
311
+ let belowFinalizedPayloads = 0;
312
+ let agedOutPayloads = 0;
313
+ let unresolvedSlotPayloads = 0;
314
+
315
+ for (const [rootHex, payload] of this.pendingPayloads) {
316
+ // Last-effort resolve for a slot-less PendingPayloadRootHex, and store it back for later passes.
317
+ if (!isPendingPayloadInput(payload) && !isPendingPayloadEnvelope(payload) && payload.slot === undefined) {
318
+ payload.slot = this.resolvePayloadSlot(rootHex);
319
+ if (payload.slot === undefined) {
320
+ // Still unresolvable: age it out so the MAX_PENDING_BLOCKS cap cannot stay saturated.
321
+ if (nowSec - payload.timeAddedSec > maxSecInCache) {
322
+ this.pendingPayloads.delete(rootHex);
323
+ agedOutPayloads++;
324
+ } else {
325
+ unresolvedSlotPayloads++;
326
+ this.logger.debug("BlockInputSync.pruneFinalized: pending payload with unresolved slot", {
327
+ root: rootHex,
328
+ finalizedSlot,
329
+ });
330
+ }
331
+ continue;
332
+ }
333
+ }
334
+
335
+ const slot = getPayloadSyncCacheItemSlot(payload);
336
+ if (typeof slot === "number" && slot < finalizedSlot) {
337
+ this.pendingPayloads.delete(rootHex);
338
+ belowFinalizedPayloads++;
339
+ }
340
+ }
341
+
342
+ let deletedBlocks = 0;
343
+ for (const [rootHex, block] of this.pendingBlocks) {
344
+ const slot = getBlockInputSyncCacheItemSlot(block);
345
+ if (typeof slot === "number" && slot < finalizedSlot) {
346
+ this.pendingBlocks.delete(rootHex);
347
+ deletedBlocks++;
348
+ }
349
+ }
350
+
351
+ if (belowFinalizedPayloads > 0) {
352
+ this.metrics?.blockInputSync.removedPayloads.inc(
353
+ {reason: DroppedItemReason.belowFinalized},
354
+ belowFinalizedPayloads
355
+ );
356
+ }
357
+ if (agedOutPayloads > 0) {
358
+ this.metrics?.blockInputSync.removedPayloads.inc({reason: DroppedItemReason.agedOut}, agedOutPayloads);
359
+ }
360
+ if (deletedBlocks > 0) {
361
+ this.metrics?.blockInputSync.removedBlocks.inc({reason: DroppedItemReason.belowFinalized}, deletedBlocks);
362
+ }
363
+
364
+ this.logger.debug("BlockInputSync.pruneFinalized dropped pre-finalized pending items", {
365
+ finalizedSlot,
366
+ finalizedRoot: checkpoint.rootHex,
367
+ belowFinalizedPayloads,
368
+ agedOutPayloads,
369
+ deletedBlocks,
370
+ unresolvedSlotPayloads,
371
+ });
372
+ };
373
+
287
374
  private addByRootHex = (rootHex: RootHex, peerIdStr?: PeerIdStr): boolean => {
288
375
  let pendingBlock = this.pendingBlocks.get(rootHex);
289
376
  let added = false;
@@ -303,7 +390,9 @@ export class BlockInputSync {
303
390
  });
304
391
  }
305
392
 
306
- if (peerIdStr) {
393
+ // bound peers tracked per root: propagationSource is recorded pre-validation, so cap the set (#9923).
394
+ // Once at the cap, ignore further peers rather than evicting already-tracked ones.
395
+ if (peerIdStr && pendingBlock.peerIdStrings.size < MAX_PEERS_PER_ROOT) {
307
396
  pendingBlock.peerIdStrings.add(peerIdStr);
308
397
  }
309
398
 
@@ -311,6 +400,7 @@ export class BlockInputSync {
311
400
  // Limit pending blocks to prevent DOS attacks that cause OOM
312
401
  const prunedItemCount = pruneSetToMax(this.pendingBlocks, this.maxPendingBlocks);
313
402
  if (prunedItemCount > 0) {
403
+ this.metrics?.blockInputSync.removedBlocks.inc({reason: DroppedItemReason.capacity}, prunedItemCount);
314
404
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingBlocks`);
315
405
  }
316
406
  return added;
@@ -337,7 +427,9 @@ export class BlockInputSync {
337
427
  });
338
428
  }
339
429
 
340
- if (peerIdStr) {
430
+ // bound peers tracked per root: propagationSource is recorded pre-validation, so cap the set (#9923).
431
+ // Once at the cap, ignore further peers rather than evicting already-tracked ones.
432
+ if (peerIdStr && pendingBlock.peerIdStrings.size < MAX_PEERS_PER_ROOT) {
341
433
  pendingBlock.peerIdStrings.add(peerIdStr);
342
434
  }
343
435
 
@@ -345,6 +437,7 @@ export class BlockInputSync {
345
437
  // Limit pending blocks to prevent DOS attacks that cause OOM
346
438
  const prunedItemCount = pruneSetToMax(this.pendingBlocks, this.maxPendingBlocks);
347
439
  if (prunedItemCount > 0) {
440
+ this.metrics?.blockInputSync.removedBlocks.inc({reason: DroppedItemReason.capacity}, prunedItemCount);
348
441
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingBlocks`);
349
442
  }
350
443
  };
@@ -369,19 +462,50 @@ export class BlockInputSync {
369
462
  root: rootHex,
370
463
  peerIdStr: peerIdStr ?? "unknown peer",
371
464
  });
465
+ } else if (
466
+ // update a previously slot-less entry once a slot becomes available
467
+ slot !== undefined &&
468
+ !isPendingPayloadInput(pendingPayload) &&
469
+ !isPendingPayloadEnvelope(pendingPayload) &&
470
+ pendingPayload.slot === undefined
471
+ ) {
472
+ pendingPayload.slot = slot;
372
473
  }
373
-
374
- if (peerIdStr) {
474
+ if (peerIdStr && pendingPayload.peerIdStrings.size < MAX_PEERS_PER_ROOT) {
375
475
  pendingPayload.peerIdStrings.add(peerIdStr);
376
476
  }
377
477
 
378
478
  const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
379
479
  if (prunedItemCount > 0) {
480
+ this.metrics?.blockInputSync.removedPayloads.inc({reason: DroppedItemReason.capacity}, prunedItemCount);
380
481
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
381
482
  }
382
483
  return added;
383
484
  };
384
485
 
486
+ /**
487
+ * Resolve the block slot for a payload root.
488
+ */
489
+ private resolvePayloadSlot(rootHex: RootHex): Slot | undefined {
490
+ // a pending block of the same root
491
+ const pendingBlock = this.pendingBlocks.get(rootHex);
492
+ if (pendingBlock) {
493
+ const pendingBlockSlot = getBlockInputSyncCacheItemSlot(pendingBlock);
494
+ if (typeof pendingBlockSlot === "number") {
495
+ return pendingBlockSlot;
496
+ }
497
+ }
498
+
499
+ // the block is in fork choice
500
+ const forkChoiceSlot = this.chain.forkChoice.getBlockHexDefaultStatus(rootHex)?.slot;
501
+ if (forkChoiceSlot !== undefined) {
502
+ return forkChoiceSlot;
503
+ }
504
+
505
+ // a seen payload envelope input
506
+ return this.chain.seenPayloadEnvelopeInputCache.get(rootHex)?.slot;
507
+ }
508
+
385
509
  private addByPayloadInput = (
386
510
  payloadInput: PayloadEnvelopeInput,
387
511
  peerIdStr?: PeerIdStr,
@@ -393,7 +517,7 @@ export class BlockInputSync {
393
517
  envelope
394
518
  );
395
519
 
396
- if (peerIdStr) {
520
+ if (peerIdStr && pendingPayload.peerIdStrings.size < MAX_PEERS_PER_ROOT) {
397
521
  pendingPayload.peerIdStrings.add(peerIdStr);
398
522
  }
399
523
 
@@ -407,6 +531,7 @@ export class BlockInputSync {
407
531
 
408
532
  const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
409
533
  if (prunedItemCount > 0) {
534
+ this.metrics?.blockInputSync.removedPayloads.inc({reason: DroppedItemReason.capacity}, prunedItemCount);
410
535
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
411
536
  }
412
537
  };
@@ -436,10 +561,12 @@ export class BlockInputSync {
436
561
  blockInput: IBlockInput
437
562
  ):
438
563
  | {kind: "ready"}
439
- | {kind: "block" | "parentBlock" | "parentPayload"; rootHex: RootHex}
564
+ | {kind: "block" | "parentBlock"; rootHex: RootHex}
565
+ | {kind: "parentPayload"; rootHex: RootHex; slot: Slot}
440
566
  | {kind: "invalidParentPayload"; parentRootHex: RootHex; parentBlockHashHex: RootHex} {
441
567
  const parentRootHex = blockInput.parentRootHex;
442
- if (!this.chain.forkChoice.hasBlockHex(parentRootHex)) {
568
+ const parentBlock = this.chain.forkChoice.getBlockHexDefaultStatus(parentRootHex);
569
+ if (!parentBlock) {
443
570
  return {kind: "parentBlock", rootHex: parentRootHex};
444
571
  }
445
572
 
@@ -461,16 +588,8 @@ export class BlockInputSync {
461
588
  return {kind: "invalidParentPayload", parentRootHex, parentBlockHashHex};
462
589
  }
463
590
 
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};
591
+ // Parent is in fork choice but its payload `parentBlockHashHex` isn't revealed yet
592
+ return {kind: "parentPayload", rootHex: parentRootHex, slot: parentBlock.slot};
474
593
  }
475
594
 
476
595
  private advancePendingBlock(pendingBlock: PendingBlockInput): AdvancePendingBlockResult {
@@ -493,9 +612,9 @@ export class BlockInputSync {
493
612
  }
494
613
 
495
614
  case "parentPayload": {
496
- let added = this.addByPayloadRootHex(missingDependency.rootHex);
615
+ let added = this.addByPayloadRootHex(missingDependency.rootHex, undefined, missingDependency.slot);
497
616
  for (const peerIdStr of pendingBlock.peerIdStrings) {
498
- added = this.addByPayloadRootHex(missingDependency.rootHex, peerIdStr) || added;
617
+ added = this.addByPayloadRootHex(missingDependency.rootHex, peerIdStr, missingDependency.slot) || added;
499
618
  }
500
619
  return added ? "queued_parent_payload" : "blocked";
501
620
  }
@@ -507,7 +626,7 @@ export class BlockInputSync {
507
626
  parentRoot: missingDependency.parentRootHex,
508
627
  parentBlockHash: missingDependency.parentBlockHashHex,
509
628
  });
510
- this.removeAndDownScoreAllDescendants(pendingBlock);
629
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidParentPayload);
511
630
  return "removed";
512
631
  }
513
632
  }
@@ -730,7 +849,7 @@ export class BlockInputSync {
730
849
  ...logCtx2,
731
850
  finalizedSlot,
732
851
  });
733
- this.removeAndDownScoreAllDescendants(block);
852
+ this.removeAndDownScoreAllDescendants(block, DroppedItemReason.belowFinalized);
734
853
  } else {
735
854
  this.onUnknownBlockRoot({rootHex: pending.blockInput.parentRootHex, source: BlockInputSource.byRoot});
736
855
  }
@@ -747,7 +866,7 @@ export class BlockInputSync {
747
866
 
748
867
  this.metrics?.blockInputSync.downloadedBlocksError.inc();
749
868
  this.logger.debug("Ignoring unknown block root after many failed downloads", logCtx, res.err);
750
- this.removeAndDownScoreAllDescendants(block);
869
+ this.removeAndDownScoreAllDescendants(block, DroppedItemReason.unavailable);
751
870
  }
752
871
  }
753
872
 
@@ -764,9 +883,10 @@ export class BlockInputSync {
764
883
  // this prevents unbundling attack
765
884
  // see https://lighthouse-blog.sigmaprime.io/mev-unbundling-rpc.html
766
885
  const {slot: blockSlot, proposerIndex} = pendingBlock.blockInput.getBlock().message;
886
+ const blockRootHex = pendingBlock.blockInput.blockRootHex;
767
887
  const logCtx = {
768
888
  slot: blockSlot,
769
- root: pendingBlock.blockInput.blockRootHex,
889
+ root: blockRootHex,
770
890
  delaySec: this.chain.clock.secFromSlot(blockSlot),
771
891
  };
772
892
  this.logger.verbose("Processing downloaded block", logCtx);
@@ -780,7 +900,7 @@ export class BlockInputSync {
780
900
  // eligible for proposer boost to prevent unbundling attack
781
901
  this.logger.verbose("Avoid proposer boost for this block of known proposer", {
782
902
  slot: blockSlot,
783
- root: pendingBlock.blockInput.blockRootHex,
903
+ root: blockRootHex,
784
904
  proposerIndex,
785
905
  });
786
906
  await sleep(proposerBoostWindowMs);
@@ -807,12 +927,24 @@ export class BlockInputSync {
807
927
  if (!res.err) {
808
928
  this.logger.verbose("Processed block from unknown sync", logCtx);
809
929
  // no need to update status to "processed", delete anyway
810
- this.pendingBlocks.delete(pendingBlock.blockInput.blockRootHex);
930
+ this.pendingBlocks.delete(blockRootHex);
931
+
932
+ if (isForkPostGloas(fork)) {
933
+ const payloadInput = await this.chain.seenPayloadEnvelopeInputCache.getOrReload(blockRootHex);
934
+ if (!payloadInput) {
935
+ this.logger.debug("PayloadEnvelopeInput unavailable during unknown sync processReadyBlock()", logCtx);
936
+ } else {
937
+ // Similar to the gossip path: immediately attempt fetch of data columns from the execution engine.
938
+ // The bid already carries the kzg commitments, so there is no reason to wait for the payload to arrive.
939
+ this.chain.getBlobsTracker.triggerGetBlobs(payloadInput);
940
+ }
941
+ }
942
+
811
943
  // Re-enter the scheduler so descendants blocked on either parent blocks or parent payloads
812
944
  // are advanced through the same dependency checks as every other pending item.
813
945
  this.triggerUnknownBlockSearch();
814
946
  } else {
815
- const errorData = {slot: pendingBlock.blockInput.slot, root: pendingBlock.blockInput.blockRootHex};
947
+ const errorData = {slot: pendingBlock.blockInput.slot, root: blockRootHex};
816
948
  if (res.err instanceof BlockError) {
817
949
  switch (res.err.type.code) {
818
950
  // This cases are already handled with `{ignoreIfKnown: true}`
@@ -842,19 +974,19 @@ export class BlockInputSync {
842
974
  case BlockErrorCode.EXECUTION_ENGINE_ERROR:
843
975
  // Removing the block(s) without penalizing the peers, hoping for EL to
844
976
  // recover on a latter download + verify attempt.
845
- this.removeAllDescendants(pendingBlock);
977
+ this.removeAllDescendants(pendingBlock, DroppedItemReason.elError, DroppedItemReason.elError);
846
978
  break;
847
979
 
848
980
  case BlockErrorCode.EXECUTION_ENGINE_INVALID:
849
981
  // the peer served a bad block
850
982
  this.logger.debug("Execution engine rejected block from unknown parent sync", errorData, res.err);
851
- this.removeAndDownScoreAllDescendants(pendingBlock);
983
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.elInvalid);
852
984
  break;
853
985
 
854
986
  default:
855
987
  // Block is not correct with respect to our chain. Log error loudly
856
988
  this.logger.debug("Error processing block from unknown parent sync", errorData, res.err);
857
- this.removeAndDownScoreAllDescendants(pendingBlock);
989
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidBlock);
858
990
  }
859
991
  }
860
992
 
@@ -873,12 +1005,12 @@ export class BlockInputSync {
873
1005
  */
874
1006
  private async reconcilePayloadEnvelope(pendingPayload: PendingPayloadEnvelope): Promise<void> {
875
1007
  const rootHex = getPayloadSyncCacheItemRootHex(pendingPayload);
1008
+
876
1009
  if (this.chain.forkChoice.hasPayloadHexUnsafe(rootHex)) {
877
1010
  this.pendingPayloads.delete(rootHex);
878
1011
  return;
879
1012
  }
880
1013
 
881
- const payloadInput = this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
882
1014
  if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
883
1015
  // Block not in fork choice yet. payloadInput may be seeded from the block body during download, so a
884
1016
  // non-null payloadInput does not imply the block is imported; defer regardless and pull the block first.
@@ -894,8 +1026,9 @@ export class BlockInputSync {
894
1026
  return;
895
1027
  }
896
1028
 
1029
+ const payloadInput = await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
897
1030
  if (!payloadInput) {
898
- this.logger.debug("Missing PayloadEnvelopeInput for known block while reconciling payload envelope", {
1031
+ this.logger.debug("PayloadEnvelopeInput not yet reloadable for imported block, will retry", {
899
1032
  root: rootHex,
900
1033
  });
901
1034
  return;
@@ -915,6 +1048,7 @@ export class BlockInputSync {
915
1048
  const pendingPayloadByRoot: PendingPayloadRootHex = {
916
1049
  status: PendingPayloadInputStatus.pending,
917
1050
  rootHex,
1051
+ slot: pendingPayload.envelope.message.payload.slotNumber,
918
1052
  timeAddedSec: pendingPayload.timeAddedSec,
919
1053
  peerIdStrings: new Set(pendingPayload.peerIdStrings),
920
1054
  };
@@ -954,6 +1088,10 @@ export class BlockInputSync {
954
1088
  return;
955
1089
  }
956
1090
 
1091
+ if (!isPendingPayloadInput(payload) && payload.slot === undefined) {
1092
+ payload.slot = this.resolvePayloadSlot(rootHex);
1093
+ }
1094
+
957
1095
  const payloadSlot = getPayloadSyncCacheItemSlot(payload);
958
1096
  const logCtx = {
959
1097
  slot: payloadSlot,
@@ -969,7 +1107,13 @@ export class BlockInputSync {
969
1107
  const res = await wrapError(this.fetchPayloadInput(payload));
970
1108
  if (!res.err) {
971
1109
  const pendingPayload = res.result;
972
- this.pendingPayloads.set(getPayloadSyncCacheItemRootHex(pendingPayload), pendingPayload);
1110
+ const resultRootHex = getPayloadSyncCacheItemRootHex(pendingPayload);
1111
+ // finalization may have deleted this entry while we were awaiting the network, do not resurrect it via the set below
1112
+ if (!this.pendingPayloads.has(resultRootHex)) {
1113
+ this.logger.verbose("Dropping downloaded payload, entry pruned during fetch", logCtx);
1114
+ return;
1115
+ }
1116
+ this.pendingPayloads.set(resultRootHex, pendingPayload);
973
1117
 
974
1118
  if (isPendingPayloadEnvelope(pendingPayload)) {
975
1119
  await this.reconcilePayloadEnvelope(pendingPayload);
@@ -1046,17 +1190,27 @@ export class BlockInputSync {
1046
1190
  break;
1047
1191
 
1048
1192
  case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
1193
+ this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1194
+ this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.elInvalid);
1195
+ break;
1196
+
1049
1197
  case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
1198
+ this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1199
+ this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidEnvelope);
1200
+ break;
1201
+
1050
1202
  case PayloadErrorCode.INVALID_SIGNATURE:
1051
1203
  // TODO GLOAS: Decide how invalid payload inputs should eventually leave memory without
1052
1204
  // reintroducing envelope replacement / recreation flows.
1053
1205
  this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1054
- this.removePendingPayloadAndDescendants(rootHex);
1206
+ this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidSignature);
1055
1207
  break;
1056
1208
 
1057
1209
  default:
1058
1210
  this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1059
- this.pendingPayloads.delete(rootHex);
1211
+ if (this.pendingPayloads.delete(rootHex)) {
1212
+ this.metrics?.blockInputSync.removedPayloads.inc({reason: DroppedItemReason.unknown}, 1);
1213
+ }
1060
1214
  }
1061
1215
  return;
1062
1216
  }
@@ -1080,7 +1234,7 @@ export class BlockInputSync {
1080
1234
  let slot = getPayloadSyncCacheItemSlot(cacheItem);
1081
1235
  let payloadInput = isPendingPayloadInput(cacheItem)
1082
1236
  ? cacheItem.payloadInput
1083
- : this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
1237
+ : await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
1084
1238
  let envelope = payloadInput?.hasPayloadEnvelope() ? payloadInput.getPayloadEnvelope() : undefined;
1085
1239
 
1086
1240
  let i = 0;
@@ -1089,7 +1243,12 @@ export class BlockInputSync {
1089
1243
  const pendingColumns = payloadInput?.hasAllData()
1090
1244
  ? new Set<number>()
1091
1245
  : new Set(payloadInput?.getMissingSampledColumnMeta().missing ?? []);
1092
- const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
1246
+ // prefer peers that gossiped this payload root to us (#9923)
1247
+ const peerMeta = this.peerBalancer.bestPeerForPendingColumns(
1248
+ pendingColumns,
1249
+ excludedPeers,
1250
+ cacheItem.peerIdStrings
1251
+ );
1093
1252
  if (peerMeta === null) {
1094
1253
  if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
1095
1254
  throw new UnknownBlockRateLimitedError(
@@ -1097,9 +1256,11 @@ export class BlockInputSync {
1097
1256
  );
1098
1257
  }
1099
1258
 
1100
- throw Error(
1101
- `Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`
1102
- );
1259
+ const reason =
1260
+ pendingColumns.size > 0
1261
+ ? `cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`
1262
+ : "no peer available to download pending payload";
1263
+ throw Error(`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: ${reason}`);
1103
1264
  }
1104
1265
 
1105
1266
  const {peerId, client: peerClient} = peerMeta;
@@ -1111,7 +1272,7 @@ export class BlockInputSync {
1111
1272
  slot = envelope.message.payload.slotNumber;
1112
1273
  }
1113
1274
 
1114
- payloadInput ??= this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
1275
+ payloadInput ??= await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
1115
1276
  if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
1116
1277
  // Block not in fork choice yet. Validating now would throw BLOCK_ROOT_UNKNOWN, so keep the downloaded
1117
1278
  // envelope and wait for the block body; reconcilePayloadEnvelope validates once the block lands.
@@ -1296,7 +1457,12 @@ export class BlockInputSync {
1296
1457
  isPendingBlockInput(cacheItem) && isBlockInputColumns(cacheItem.blockInput)
1297
1458
  ? new Set(cacheItem.blockInput.getMissingSampledColumnMeta().missing)
1298
1459
  : defaultPendingColumns;
1299
- const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
1460
+ // prefer peers that gossiped this block root to us (#9923)
1461
+ const peerMeta = this.peerBalancer.bestPeerForPendingColumns(
1462
+ pendingColumns,
1463
+ excludedPeers,
1464
+ cacheItem.peerIdStrings
1465
+ );
1300
1466
  if (peerMeta === null) {
1301
1467
  if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
1302
1468
  throw new UnknownBlockRateLimitedError(
@@ -1444,9 +1610,9 @@ export class BlockInputSync {
1444
1610
  * Downscore all peers that have referenced any of this bad blocks. May report peers multiple times if they have
1445
1611
  * referenced more than one bad block.
1446
1612
  */
1447
- private removeAndDownScoreAllDescendants(block: BlockInputSyncCacheItem): void {
1613
+ private removeAndDownScoreAllDescendants(block: BlockInputSyncCacheItem, headReason: DroppedItemReason): void {
1448
1614
  // Get all blocks that are a descendant of this one
1449
- const badPendingBlocks = this.removeAllDescendants(block);
1615
+ const badPendingBlocks = this.removeAllDescendants(block, headReason, DroppedItemReason.invalidParent);
1450
1616
  // just console log and do not penalize on pending/bad blocks for debugging
1451
1617
  // console.log("removeAndDownscoreAllDescendants", {block});
1452
1618
 
@@ -1473,18 +1639,22 @@ export class BlockInputSync {
1473
1639
  }
1474
1640
 
1475
1641
  // Once a parent payload is invalid, every descendant waiting on that payload lineage becomes unrecoverable too.
1476
- private removePendingPayloadAndDescendants(rootHex: RootHex): void {
1642
+ private removePendingPayloadAndDescendants(rootHex: RootHex, headReason: DroppedItemReason): void {
1477
1643
  // Keep PayloadEnvelopeInput resident in the seen cache. importBlock() owns that object and
1478
1644
  // later validation/finalization logic decides when it can leave memory.
1479
- this.pendingPayloads.delete(rootHex);
1645
+ if (this.pendingPayloads.delete(rootHex)) {
1646
+ this.metrics?.blockInputSync.removedPayloads.inc({reason: headReason}, 1);
1647
+ }
1480
1648
 
1481
1649
  const badPendingBlocks = getAllDescendantBlocks(rootHex, this.pendingBlocks);
1482
- this.metrics?.blockInputSync.removedBlocks.inc(badPendingBlocks.length);
1483
-
1484
1650
  for (const block of badPendingBlocks) {
1485
1651
  const descendantRootHex = getBlockInputSyncCacheItemRootHex(block);
1486
- this.pendingBlocks.delete(descendantRootHex);
1487
- this.pendingPayloads.delete(descendantRootHex);
1652
+ if (this.pendingBlocks.delete(descendantRootHex)) {
1653
+ this.metrics?.blockInputSync.removedBlocks.inc({reason: DroppedItemReason.invalidParent}, 1);
1654
+ }
1655
+ if (this.pendingPayloads.delete(descendantRootHex)) {
1656
+ this.metrics?.blockInputSync.removedPayloads.inc({reason: DroppedItemReason.invalidParent}, 1);
1657
+ }
1488
1658
  this.chain.seenBlockInputCache.prune(descendantRootHex);
1489
1659
  this.logger.debug("Removing pending descendant after invalid parent payload", {
1490
1660
  slot: getBlockInputSyncCacheItemSlot(block),
@@ -1494,18 +1664,25 @@ export class BlockInputSync {
1494
1664
  }
1495
1665
  }
1496
1666
 
1497
- private removeAllDescendants(block: BlockInputSyncCacheItem): BlockInputSyncCacheItem[] {
1667
+ private removeAllDescendants(
1668
+ block: BlockInputSyncCacheItem,
1669
+ headReason: DroppedItemReason,
1670
+ descendantReason: DroppedItemReason = DroppedItemReason.invalidParent
1671
+ ): BlockInputSyncCacheItem[] {
1498
1672
  const rootHex = getBlockInputSyncCacheItemRootHex(block);
1499
1673
  const slot = getBlockInputSyncCacheItemSlot(block);
1500
- // Get all blocks that are a descendant of this one
1674
+ // Get all blocks that are a descendant of this one (index 0 is the head item itself)
1501
1675
  const badPendingBlocks = [block, ...getAllDescendantBlocks(rootHex, this.pendingBlocks)];
1502
1676
 
1503
- this.metrics?.blockInputSync.removedBlocks.inc(badPendingBlocks.length);
1504
-
1505
- for (const block of badPendingBlocks) {
1677
+ for (const [i, block] of badPendingBlocks.entries()) {
1506
1678
  const rootHex = getBlockInputSyncCacheItemRootHex(block);
1507
- this.pendingBlocks.delete(rootHex);
1508
- this.pendingPayloads.delete(rootHex);
1679
+ const reason = i === 0 ? headReason : descendantReason;
1680
+ if (this.pendingBlocks.delete(rootHex)) {
1681
+ this.metrics?.blockInputSync.removedBlocks.inc({reason}, 1);
1682
+ }
1683
+ if (this.pendingPayloads.delete(rootHex)) {
1684
+ this.metrics?.blockInputSync.removedPayloads.inc({reason}, 1);
1685
+ }
1509
1686
  this.chain.seenBlockInputCache.prune(rootHex);
1510
1687
  // Keep PayloadEnvelopeInput resident in the seen cache for consistency with the
1511
1688
  // importBlock()-owned lifecycle.
@@ -1596,11 +1773,17 @@ export class UnknownBlockPeerBalancer {
1596
1773
  }
1597
1774
 
1598
1775
  /**
1599
- * called from fetchBlockInput() where we only have block root and nothing else
1776
+ * called from fetchBlockInput()/fetchPayloadInput() with a block/payload root.
1600
1777
  * excludedPeers are the peers that we requested already so we don't want to try again
1601
1778
  * pendingColumns is empty for prefulu, or the 1st time we we download a block by root
1779
+ * preferredPeers are the peers NetworkProcessor told us gossiped this root (from
1780
+ * pendingBlock/cacheItem.peerIdStrings). They are preferred but not required.
1602
1781
  */
1603
- bestPeerForPendingColumns(pendingColumns: Set<number>, excludedPeers: Set<PeerIdStr>): PeerSyncMeta | null {
1782
+ bestPeerForPendingColumns(
1783
+ pendingColumns: Set<number>,
1784
+ excludedPeers: Set<PeerIdStr>,
1785
+ preferredPeers?: Set<PeerIdStr>
1786
+ ): PeerSyncMeta | null {
1604
1787
  const eligiblePeers = this.filterPeers(pendingColumns, excludedPeers);
1605
1788
  if (eligiblePeers.length === 0) {
1606
1789
  return null;
@@ -1608,7 +1791,9 @@ export class UnknownBlockPeerBalancer {
1608
1791
 
1609
1792
  const sortedEligiblePeers = sortBy(
1610
1793
  shuffle(eligiblePeers),
1611
- // prefer peers with least active req
1794
+ // 1st key: prefer peers we heard about this root from
1795
+ (peerId) => (preferredPeers?.has(peerId) ? 0 : 1),
1796
+ // 2nd key (tiebreak): prefer peers with least active req
1612
1797
  (peerId) => this.activeRequests.get(peerId) ?? 0
1613
1798
  );
1614
1799