@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,5 +1,5 @@
1
1
  import { routes } from "@lodestar/api";
2
- import { ForkSeq } from "@lodestar/params";
2
+ import { ForkSeq, SLOTS_PER_EPOCH, isForkPostGloas } from "@lodestar/params";
3
3
  import { RequestError, RequestErrorCode } from "@lodestar/reqresp";
4
4
  import { computeTimeAtSlot } from "@lodestar/state-transition";
5
5
  import { fromHex, prettyPrintIndices, pruneSetToMax, sleep, toRootHex } from "@lodestar/utils";
@@ -12,17 +12,22 @@ import { ChainEvent } from "../chain/index.js";
12
12
  import { validateGloasBlockDataColumnSidecars } from "../chain/validation/dataColumnSidecar.js";
13
13
  import { validateGossipExecutionPayloadEnvelope } from "../chain/validation/executionPayloadEnvelope.js";
14
14
  import { NetworkEvent, prettyPrintPeerIdStr } from "../network/index.js";
15
+ import { MAX_PEERS_PER_ROOT } from "../network/processor/constants.js";
15
16
  import { shuffle } from "../util/shuffle.js";
16
17
  import { sortBy } from "../util/sortBy.js";
17
18
  import { wrapError } from "../util/wrapError.js";
18
19
  import { MAX_CONCURRENT_REQUESTS } from "./constants.js";
19
- import { PendingBlockInputStatus, PendingBlockType, PendingPayloadInputStatus, getBlockInputSyncCacheItemRootHex, getBlockInputSyncCacheItemSlot, getPayloadSyncCacheItemRootHex, getPayloadSyncCacheItemSlot, isPendingBlockInput, isPendingPayloadEnvelope, isPendingPayloadInput, } from "./types.js";
20
+ import { DroppedItemReason, PendingBlockInputStatus, PendingBlockType, PendingPayloadInputStatus, getBlockInputSyncCacheItemRootHex, getBlockInputSyncCacheItemSlot, getPayloadSyncCacheItemRootHex, getPayloadSyncCacheItemSlot, isPendingBlockInput, isPendingPayloadEnvelope, isPendingPayloadInput, } from "./types.js";
20
21
  import { DownloadByRootError, downloadByRoot } from "./utils/downloadByRoot.js";
21
22
  import { getAllDescendantBlocks, getUnknownAndAncestorBlocks } from "./utils/pendingBlocksTree.js";
22
23
  import { getRateLimitedUntilMs } from "./utils/rateLimit.js";
23
24
  const MAX_ATTEMPTS_PER_BLOCK = 5;
24
25
  const MAX_KNOWN_BAD_BLOCKS = 500;
25
26
  const MAX_PENDING_BLOCKS = 100;
27
+ /**
28
+ * A pending payload without slot can only stay at most 2 epochs.
29
+ */
30
+ const PRUNE_UNRESOLVED_SLOT_EPOCHS = 2;
26
31
  var FetchResult;
27
32
  (function (FetchResult) {
28
33
  FetchResult["SuccessResolved"] = "success_resolved";
@@ -115,6 +120,7 @@ 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);
@@ -130,6 +136,7 @@ export class BlockInputSync {
130
136
  this.chain.emitter.off(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
131
137
  this.chain.emitter.off(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
132
138
  this.chain.emitter.off(ChainEvent.blockUnknownParent, this.onUnknownParent);
139
+ this.chain.emitter.off(ChainEvent.forkChoiceFinalized, this.pruneFinalized);
133
140
  this.chain.emitter.off(routes.events.EventType.block, this.onBlockImported);
134
141
  this.chain.emitter.off(routes.events.EventType.executionPayload, this.onPayloadImported);
135
142
  this.network.events.off(NetworkEvent.peerConnected, this.onPeerConnected);
@@ -147,10 +154,12 @@ export class BlockInputSync {
147
154
  */
148
155
  onUnknownBlockRoot = (data) => {
149
156
  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 });
157
+ const isNewRoot = this.addByRootHex(data.rootHex, data.peer);
158
+ if (isNewRoot) {
159
+ this.triggerUnknownBlockSearch();
160
+ this.metrics?.blockInputSync.requests.inc({ type: PendingBlockType.UNKNOWN_BLOCK_ROOT });
161
+ this.metrics?.blockInputSync.source.inc({ source: data.source });
162
+ }
154
163
  }
155
164
  catch (e) {
156
165
  this.logger.debug("Error handling unknownBlockRoot event", {}, e);
@@ -172,10 +181,12 @@ export class BlockInputSync {
172
181
  };
173
182
  onUnknownEnvelopeBlockRoot = (data) => {
174
183
  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 });
184
+ const isNewRoot = this.addByPayloadRootHex(data.rootHex, data.peer, this.resolvePayloadSlot(data.rootHex));
185
+ if (isNewRoot) {
186
+ this.triggerUnknownBlockSearch();
187
+ this.metrics?.blockInputSync.requests.inc({ type: PendingBlockType.UNKNOWN_PAYLOAD_BLOCK_ROOT });
188
+ this.metrics?.blockInputSync.payloadSource.inc({ source: data.source });
189
+ }
179
190
  }
180
191
  catch (e) {
181
192
  this.logger.debug("Error handling unknownEnvelopeBlockRoot event", {}, e);
@@ -208,12 +219,12 @@ export class BlockInputSync {
208
219
  parentRoot: missingDependency.parentRootHex,
209
220
  parentBlockHash: missingDependency.parentBlockHashHex,
210
221
  });
211
- this.removeAndDownScoreAllDescendants(pendingBlock);
222
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidParentPayload);
212
223
  }
213
224
  return;
214
225
  }
215
226
  if (missingDependency.kind === "parentPayload") {
216
- this.addByPayloadRootHex(missingDependency.rootHex, data.peer);
227
+ this.addByPayloadRootHex(missingDependency.rootHex, data.peer, missingDependency.slot);
217
228
  }
218
229
  else if (missingDependency.kind === "parentBlock") {
219
230
  this.addByRootHex(missingDependency.rootHex, data.peer);
@@ -236,6 +247,71 @@ export class BlockInputSync {
236
247
  this.pendingPayloads.delete(blockRoot);
237
248
  this.triggerUnknownBlockSearch();
238
249
  };
250
+ /**
251
+ * Drop pending payloads and blocks that fell below the finalized slot.
252
+ * See https://github.com/ChainSafe/lodestar/issues/9800
253
+ */
254
+ pruneFinalized = (checkpoint) => {
255
+ // Use the actual finalized block slot, not computeStartSlotAtEpoch(checkpoint.epoch)
256
+ // because the payload at the finalized block is not finalized
257
+ const finalizedSlot = this.chain.forkChoice.getFinalizedBlock().slot;
258
+ const nowSec = Date.now() / 1000;
259
+ const maxSecInCache = (PRUNE_UNRESOLVED_SLOT_EPOCHS * SLOTS_PER_EPOCH * this.config.SLOT_DURATION_MS) / 1000;
260
+ let belowFinalizedPayloads = 0;
261
+ let agedOutPayloads = 0;
262
+ let unresolvedSlotPayloads = 0;
263
+ for (const [rootHex, payload] of this.pendingPayloads) {
264
+ // Last-effort resolve for a slot-less PendingPayloadRootHex, and store it back for later passes.
265
+ if (!isPendingPayloadInput(payload) && !isPendingPayloadEnvelope(payload) && payload.slot === undefined) {
266
+ payload.slot = this.resolvePayloadSlot(rootHex);
267
+ if (payload.slot === undefined) {
268
+ // Still unresolvable: age it out so the MAX_PENDING_BLOCKS cap cannot stay saturated.
269
+ if (nowSec - payload.timeAddedSec > maxSecInCache) {
270
+ this.pendingPayloads.delete(rootHex);
271
+ agedOutPayloads++;
272
+ }
273
+ else {
274
+ unresolvedSlotPayloads++;
275
+ this.logger.debug("BlockInputSync.pruneFinalized: pending payload with unresolved slot", {
276
+ root: rootHex,
277
+ finalizedSlot,
278
+ });
279
+ }
280
+ continue;
281
+ }
282
+ }
283
+ const slot = getPayloadSyncCacheItemSlot(payload);
284
+ if (typeof slot === "number" && slot < finalizedSlot) {
285
+ this.pendingPayloads.delete(rootHex);
286
+ belowFinalizedPayloads++;
287
+ }
288
+ }
289
+ let deletedBlocks = 0;
290
+ for (const [rootHex, block] of this.pendingBlocks) {
291
+ const slot = getBlockInputSyncCacheItemSlot(block);
292
+ if (typeof slot === "number" && slot < finalizedSlot) {
293
+ this.pendingBlocks.delete(rootHex);
294
+ deletedBlocks++;
295
+ }
296
+ }
297
+ if (belowFinalizedPayloads > 0) {
298
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: DroppedItemReason.belowFinalized }, belowFinalizedPayloads);
299
+ }
300
+ if (agedOutPayloads > 0) {
301
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: DroppedItemReason.agedOut }, agedOutPayloads);
302
+ }
303
+ if (deletedBlocks > 0) {
304
+ this.metrics?.blockInputSync.removedBlocks.inc({ reason: DroppedItemReason.belowFinalized }, deletedBlocks);
305
+ }
306
+ this.logger.debug("BlockInputSync.pruneFinalized dropped pre-finalized pending items", {
307
+ finalizedSlot,
308
+ finalizedRoot: checkpoint.rootHex,
309
+ belowFinalizedPayloads,
310
+ agedOutPayloads,
311
+ deletedBlocks,
312
+ unresolvedSlotPayloads,
313
+ });
314
+ };
239
315
  addByRootHex = (rootHex, peerIdStr) => {
240
316
  let pendingBlock = this.pendingBlocks.get(rootHex);
241
317
  let added = false;
@@ -253,13 +329,16 @@ export class BlockInputSync {
253
329
  peerIdStr: peerIdStr ?? "unknown peer",
254
330
  });
255
331
  }
256
- if (peerIdStr) {
332
+ // bound peers tracked per root: propagationSource is recorded pre-validation, so cap the set (#9923).
333
+ // Once at the cap, ignore further peers rather than evicting already-tracked ones.
334
+ if (peerIdStr && pendingBlock.peerIdStrings.size < MAX_PEERS_PER_ROOT) {
257
335
  pendingBlock.peerIdStrings.add(peerIdStr);
258
336
  }
259
337
  // TODO: check this prune methodology
260
338
  // Limit pending blocks to prevent DOS attacks that cause OOM
261
339
  const prunedItemCount = pruneSetToMax(this.pendingBlocks, this.maxPendingBlocks);
262
340
  if (prunedItemCount > 0) {
341
+ this.metrics?.blockInputSync.removedBlocks.inc({ reason: DroppedItemReason.capacity }, prunedItemCount);
263
342
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingBlocks`);
264
343
  }
265
344
  return added;
@@ -283,13 +362,16 @@ export class BlockInputSync {
283
362
  delaySec: this.chain.clock.secFromSlot(blockInput.slot),
284
363
  });
285
364
  }
286
- if (peerIdStr) {
365
+ // bound peers tracked per root: propagationSource is recorded pre-validation, so cap the set (#9923).
366
+ // Once at the cap, ignore further peers rather than evicting already-tracked ones.
367
+ if (peerIdStr && pendingBlock.peerIdStrings.size < MAX_PEERS_PER_ROOT) {
287
368
  pendingBlock.peerIdStrings.add(peerIdStr);
288
369
  }
289
370
  // TODO: check this prune methodology
290
371
  // Limit pending blocks to prevent DOS attacks that cause OOM
291
372
  const prunedItemCount = pruneSetToMax(this.pendingBlocks, this.maxPendingBlocks);
292
373
  if (prunedItemCount > 0) {
374
+ this.metrics?.blockInputSync.removedBlocks.inc({ reason: DroppedItemReason.capacity }, prunedItemCount);
293
375
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingBlocks`);
294
376
  }
295
377
  };
@@ -313,18 +395,47 @@ export class BlockInputSync {
313
395
  peerIdStr: peerIdStr ?? "unknown peer",
314
396
  });
315
397
  }
316
- if (peerIdStr) {
398
+ else if (
399
+ // update a previously slot-less entry once a slot becomes available
400
+ slot !== undefined &&
401
+ !isPendingPayloadInput(pendingPayload) &&
402
+ !isPendingPayloadEnvelope(pendingPayload) &&
403
+ pendingPayload.slot === undefined) {
404
+ pendingPayload.slot = slot;
405
+ }
406
+ if (peerIdStr && pendingPayload.peerIdStrings.size < MAX_PEERS_PER_ROOT) {
317
407
  pendingPayload.peerIdStrings.add(peerIdStr);
318
408
  }
319
409
  const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
320
410
  if (prunedItemCount > 0) {
411
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: DroppedItemReason.capacity }, prunedItemCount);
321
412
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
322
413
  }
323
414
  return added;
324
415
  };
416
+ /**
417
+ * Resolve the block slot for a payload root.
418
+ */
419
+ resolvePayloadSlot(rootHex) {
420
+ // a pending block of the same root
421
+ const pendingBlock = this.pendingBlocks.get(rootHex);
422
+ if (pendingBlock) {
423
+ const pendingBlockSlot = getBlockInputSyncCacheItemSlot(pendingBlock);
424
+ if (typeof pendingBlockSlot === "number") {
425
+ return pendingBlockSlot;
426
+ }
427
+ }
428
+ // the block is in fork choice
429
+ const forkChoiceSlot = this.chain.forkChoice.getBlockHexDefaultStatus(rootHex)?.slot;
430
+ if (forkChoiceSlot !== undefined) {
431
+ return forkChoiceSlot;
432
+ }
433
+ // a seen payload envelope input
434
+ return this.chain.seenPayloadEnvelopeInputCache.get(rootHex)?.slot;
435
+ }
325
436
  addByPayloadInput = (payloadInput, peerIdStr, envelope) => {
326
437
  const pendingPayload = this.toPendingPayloadInput(payloadInput, this.pendingPayloads.get(payloadInput.blockRootHex), envelope);
327
- if (peerIdStr) {
438
+ if (peerIdStr && pendingPayload.peerIdStrings.size < MAX_PEERS_PER_ROOT) {
328
439
  pendingPayload.peerIdStrings.add(peerIdStr);
329
440
  }
330
441
  this.pendingPayloads.set(payloadInput.blockRootHex, pendingPayload);
@@ -335,6 +446,7 @@ export class BlockInputSync {
335
446
  });
336
447
  const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
337
448
  if (prunedItemCount > 0) {
449
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: DroppedItemReason.capacity }, prunedItemCount);
338
450
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
339
451
  }
340
452
  };
@@ -360,7 +472,8 @@ export class BlockInputSync {
360
472
  */
361
473
  getMissingBlockDependency(blockInput) {
362
474
  const parentRootHex = blockInput.parentRootHex;
363
- if (!this.chain.forkChoice.hasBlockHex(parentRootHex)) {
475
+ const parentBlock = this.chain.forkChoice.getBlockHexDefaultStatus(parentRootHex);
476
+ if (!parentBlock) {
364
477
  return { kind: "parentBlock", rootHex: parentRootHex };
365
478
  }
366
479
  if (!blockInput.hasBlock()) {
@@ -377,14 +490,8 @@ export class BlockInputSync {
377
490
  if (this.chain.forkChoice.hasPayloadHexUnsafe(parentRootHex)) {
378
491
  return { kind: "invalidParentPayload", parentRootHex, parentBlockHashHex };
379
492
  }
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 };
493
+ // Parent is in fork choice but its payload `parentBlockHashHex` isn't revealed yet
494
+ return { kind: "parentPayload", rootHex: parentRootHex, slot: parentBlock.slot };
388
495
  }
389
496
  advancePendingBlock(pendingBlock) {
390
497
  const missingDependency = this.getMissingBlockDependency(pendingBlock.blockInput);
@@ -402,9 +509,9 @@ export class BlockInputSync {
402
509
  return added ? "queued_parent_block" : "blocked";
403
510
  }
404
511
  case "parentPayload": {
405
- let added = this.addByPayloadRootHex(missingDependency.rootHex);
512
+ let added = this.addByPayloadRootHex(missingDependency.rootHex, undefined, missingDependency.slot);
406
513
  for (const peerIdStr of pendingBlock.peerIdStrings) {
407
- added = this.addByPayloadRootHex(missingDependency.rootHex, peerIdStr) || added;
514
+ added = this.addByPayloadRootHex(missingDependency.rootHex, peerIdStr, missingDependency.slot) || added;
408
515
  }
409
516
  return added ? "queued_parent_payload" : "blocked";
410
517
  }
@@ -415,7 +522,7 @@ export class BlockInputSync {
415
522
  parentRoot: missingDependency.parentRootHex,
416
523
  parentBlockHash: missingDependency.parentBlockHashHex,
417
524
  });
418
- this.removeAndDownScoreAllDescendants(pendingBlock);
525
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidParentPayload);
419
526
  return "removed";
420
527
  }
421
528
  }
@@ -604,7 +711,7 @@ export class BlockInputSync {
604
711
  ...logCtx2,
605
712
  finalizedSlot,
606
713
  });
607
- this.removeAndDownScoreAllDescendants(block);
714
+ this.removeAndDownScoreAllDescendants(block, DroppedItemReason.belowFinalized);
608
715
  }
609
716
  else {
610
717
  this.onUnknownBlockRoot({ rootHex: pending.blockInput.parentRootHex, source: BlockInputSource.byRoot });
@@ -622,7 +729,7 @@ export class BlockInputSync {
622
729
  }
623
730
  this.metrics?.blockInputSync.downloadedBlocksError.inc();
624
731
  this.logger.debug("Ignoring unknown block root after many failed downloads", logCtx, res.err);
625
- this.removeAndDownScoreAllDescendants(block);
732
+ this.removeAndDownScoreAllDescendants(block, DroppedItemReason.unavailable);
626
733
  }
627
734
  }
628
735
  /**
@@ -637,9 +744,10 @@ export class BlockInputSync {
637
744
  // this prevents unbundling attack
638
745
  // see https://lighthouse-blog.sigmaprime.io/mev-unbundling-rpc.html
639
746
  const { slot: blockSlot, proposerIndex } = pendingBlock.blockInput.getBlock().message;
747
+ const blockRootHex = pendingBlock.blockInput.blockRootHex;
640
748
  const logCtx = {
641
749
  slot: blockSlot,
642
- root: pendingBlock.blockInput.blockRootHex,
750
+ root: blockRootHex,
643
751
  delaySec: this.chain.clock.secFromSlot(blockSlot),
644
752
  };
645
753
  this.logger.verbose("Processing downloaded block", logCtx);
@@ -651,7 +759,7 @@ export class BlockInputSync {
651
759
  // eligible for proposer boost to prevent unbundling attack
652
760
  this.logger.verbose("Avoid proposer boost for this block of known proposer", {
653
761
  slot: blockSlot,
654
- root: pendingBlock.blockInput.blockRootHex,
762
+ root: blockRootHex,
655
763
  proposerIndex,
656
764
  });
657
765
  await sleep(proposerBoostWindowMs);
@@ -676,13 +784,24 @@ export class BlockInputSync {
676
784
  if (!res.err) {
677
785
  this.logger.verbose("Processed block from unknown sync", logCtx);
678
786
  // no need to update status to "processed", delete anyway
679
- this.pendingBlocks.delete(pendingBlock.blockInput.blockRootHex);
787
+ this.pendingBlocks.delete(blockRootHex);
788
+ if (isForkPostGloas(fork)) {
789
+ const payloadInput = await this.chain.seenPayloadEnvelopeInputCache.getOrReload(blockRootHex);
790
+ if (!payloadInput) {
791
+ this.logger.debug("PayloadEnvelopeInput unavailable during unknown sync processReadyBlock()", logCtx);
792
+ }
793
+ else {
794
+ // Similar to the gossip path: immediately attempt fetch of data columns from the execution engine.
795
+ // The bid already carries the kzg commitments, so there is no reason to wait for the payload to arrive.
796
+ this.chain.getBlobsTracker.triggerGetBlobs(payloadInput);
797
+ }
798
+ }
680
799
  // Re-enter the scheduler so descendants blocked on either parent blocks or parent payloads
681
800
  // are advanced through the same dependency checks as every other pending item.
682
801
  this.triggerUnknownBlockSearch();
683
802
  }
684
803
  else {
685
- const errorData = { slot: pendingBlock.blockInput.slot, root: pendingBlock.blockInput.blockRootHex };
804
+ const errorData = { slot: pendingBlock.blockInput.slot, root: blockRootHex };
686
805
  if (res.err instanceof BlockError) {
687
806
  switch (res.err.type.code) {
688
807
  // This cases are already handled with `{ignoreIfKnown: true}`
@@ -705,17 +824,17 @@ export class BlockInputSync {
705
824
  case BlockErrorCode.EXECUTION_ENGINE_ERROR:
706
825
  // Removing the block(s) without penalizing the peers, hoping for EL to
707
826
  // recover on a latter download + verify attempt.
708
- this.removeAllDescendants(pendingBlock);
827
+ this.removeAllDescendants(pendingBlock, DroppedItemReason.elError, DroppedItemReason.elError);
709
828
  break;
710
829
  case BlockErrorCode.EXECUTION_ENGINE_INVALID:
711
830
  // the peer served a bad block
712
831
  this.logger.debug("Execution engine rejected block from unknown parent sync", errorData, res.err);
713
- this.removeAndDownScoreAllDescendants(pendingBlock);
832
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.elInvalid);
714
833
  break;
715
834
  default:
716
835
  // Block is not correct with respect to our chain. Log error loudly
717
836
  this.logger.debug("Error processing block from unknown parent sync", errorData, res.err);
718
- this.removeAndDownScoreAllDescendants(pendingBlock);
837
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidBlock);
719
838
  }
720
839
  }
721
840
  // Probably a queue error or something unwanted happened, mark as pending to try again latter
@@ -736,7 +855,6 @@ export class BlockInputSync {
736
855
  this.pendingPayloads.delete(rootHex);
737
856
  return;
738
857
  }
739
- const payloadInput = this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
740
858
  if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
741
859
  // Block not in fork choice yet. payloadInput may be seeded from the block body during download, so a
742
860
  // non-null payloadInput does not imply the block is imported; defer regardless and pull the block first.
@@ -750,8 +868,9 @@ export class BlockInputSync {
750
868
  }
751
869
  return;
752
870
  }
871
+ const payloadInput = await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
753
872
  if (!payloadInput) {
754
- this.logger.debug("Missing PayloadEnvelopeInput for known block while reconciling payload envelope", {
873
+ this.logger.debug("PayloadEnvelopeInput not yet reloadable for imported block, will retry", {
755
874
  root: rootHex,
756
875
  });
757
876
  return;
@@ -763,6 +882,7 @@ export class BlockInputSync {
763
882
  const pendingPayloadByRoot = {
764
883
  status: PendingPayloadInputStatus.pending,
765
884
  rootHex,
885
+ slot: pendingPayload.envelope.message.payload.slotNumber,
766
886
  timeAddedSec: pendingPayload.timeAddedSec,
767
887
  peerIdStrings: new Set(pendingPayload.peerIdStrings),
768
888
  };
@@ -794,6 +914,9 @@ export class BlockInputSync {
794
914
  if (payload.status !== PendingPayloadInputStatus.pending) {
795
915
  return;
796
916
  }
917
+ if (!isPendingPayloadInput(payload) && payload.slot === undefined) {
918
+ payload.slot = this.resolvePayloadSlot(rootHex);
919
+ }
797
920
  const payloadSlot = getPayloadSyncCacheItemSlot(payload);
798
921
  const logCtx = {
799
922
  slot: payloadSlot,
@@ -806,7 +929,13 @@ export class BlockInputSync {
806
929
  const res = await wrapError(this.fetchPayloadInput(payload));
807
930
  if (!res.err) {
808
931
  const pendingPayload = res.result;
809
- this.pendingPayloads.set(getPayloadSyncCacheItemRootHex(pendingPayload), pendingPayload);
932
+ const resultRootHex = getPayloadSyncCacheItemRootHex(pendingPayload);
933
+ // finalization may have deleted this entry while we were awaiting the network, do not resurrect it via the set below
934
+ if (!this.pendingPayloads.has(resultRootHex)) {
935
+ this.logger.verbose("Dropping downloaded payload, entry pruned during fetch", logCtx);
936
+ return;
937
+ }
938
+ this.pendingPayloads.set(resultRootHex, pendingPayload);
810
939
  if (isPendingPayloadEnvelope(pendingPayload)) {
811
940
  await this.reconcilePayloadEnvelope(pendingPayload);
812
941
  }
@@ -873,16 +1002,24 @@ export class BlockInputSync {
873
1002
  pendingPayload.status = PendingPayloadInputStatus.downloaded;
874
1003
  break;
875
1004
  case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
1005
+ this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1006
+ this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.elInvalid);
1007
+ break;
876
1008
  case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
1009
+ this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1010
+ this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidEnvelope);
1011
+ break;
877
1012
  case PayloadErrorCode.INVALID_SIGNATURE:
878
1013
  // TODO GLOAS: Decide how invalid payload inputs should eventually leave memory without
879
1014
  // reintroducing envelope replacement / recreation flows.
880
1015
  this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
881
- this.removePendingPayloadAndDescendants(rootHex);
1016
+ this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidSignature);
882
1017
  break;
883
1018
  default:
884
1019
  this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
885
- this.pendingPayloads.delete(rootHex);
1020
+ if (this.pendingPayloads.delete(rootHex)) {
1021
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: DroppedItemReason.unknown }, 1);
1022
+ }
886
1023
  }
887
1024
  return;
888
1025
  }
@@ -901,7 +1038,7 @@ export class BlockInputSync {
901
1038
  let slot = getPayloadSyncCacheItemSlot(cacheItem);
902
1039
  let payloadInput = isPendingPayloadInput(cacheItem)
903
1040
  ? cacheItem.payloadInput
904
- : this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
1041
+ : await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
905
1042
  let envelope = payloadInput?.hasPayloadEnvelope() ? payloadInput.getPayloadEnvelope() : undefined;
906
1043
  let i = 0;
907
1044
  let deferredByRateLimit = false;
@@ -909,12 +1046,16 @@ export class BlockInputSync {
909
1046
  const pendingColumns = payloadInput?.hasAllData()
910
1047
  ? new Set()
911
1048
  : new Set(payloadInput?.getMissingSampledColumnMeta().missing ?? []);
912
- const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
1049
+ // prefer peers that gossiped this payload root to us (#9923)
1050
+ const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers, cacheItem.peerIdStrings);
913
1051
  if (peerMeta === null) {
914
1052
  if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
915
1053
  throw new UnknownBlockRateLimitedError(`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`);
916
1054
  }
917
- throw Error(`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`);
1055
+ const reason = pendingColumns.size > 0
1056
+ ? `cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`
1057
+ : "no peer available to download pending payload";
1058
+ throw Error(`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: ${reason}`);
918
1059
  }
919
1060
  const { peerId, client: peerClient } = peerMeta;
920
1061
  cacheItem.peerIdStrings.add(peerId);
@@ -923,7 +1064,7 @@ export class BlockInputSync {
923
1064
  envelope = await this.fetchExecutionPayloadEnvelope(peerId, blockRoot, rootHex);
924
1065
  slot = envelope.message.payload.slotNumber;
925
1066
  }
926
- payloadInput ??= this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
1067
+ payloadInput ??= await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
927
1068
  if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
928
1069
  // Block not in fork choice yet. Validating now would throw BLOCK_ROOT_UNKNOWN, so keep the downloaded
929
1070
  // envelope and wait for the block body; reconcilePayloadEnvelope validates once the block lands.
@@ -1068,7 +1209,8 @@ export class BlockInputSync {
1068
1209
  const pendingColumns = isPendingBlockInput(cacheItem) && isBlockInputColumns(cacheItem.blockInput)
1069
1210
  ? new Set(cacheItem.blockInput.getMissingSampledColumnMeta().missing)
1070
1211
  : defaultPendingColumns;
1071
- const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
1212
+ // prefer peers that gossiped this block root to us (#9923)
1213
+ const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers, cacheItem.peerIdStrings);
1072
1214
  if (peerMeta === null) {
1073
1215
  if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
1074
1216
  throw new UnknownBlockRateLimitedError(`Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`);
@@ -1194,9 +1336,9 @@ export class BlockInputSync {
1194
1336
  * Downscore all peers that have referenced any of this bad blocks. May report peers multiple times if they have
1195
1337
  * referenced more than one bad block.
1196
1338
  */
1197
- removeAndDownScoreAllDescendants(block) {
1339
+ removeAndDownScoreAllDescendants(block, headReason) {
1198
1340
  // Get all blocks that are a descendant of this one
1199
- const badPendingBlocks = this.removeAllDescendants(block);
1341
+ const badPendingBlocks = this.removeAllDescendants(block, headReason, DroppedItemReason.invalidParent);
1200
1342
  // just console log and do not penalize on pending/bad blocks for debugging
1201
1343
  // console.log("removeAndDownscoreAllDescendants", {block});
1202
1344
  for (const block of badPendingBlocks) {
@@ -1220,16 +1362,21 @@ export class BlockInputSync {
1220
1362
  pruneSetToMax(this.knownBadBlocks, MAX_KNOWN_BAD_BLOCKS);
1221
1363
  }
1222
1364
  // Once a parent payload is invalid, every descendant waiting on that payload lineage becomes unrecoverable too.
1223
- removePendingPayloadAndDescendants(rootHex) {
1365
+ removePendingPayloadAndDescendants(rootHex, headReason) {
1224
1366
  // Keep PayloadEnvelopeInput resident in the seen cache. importBlock() owns that object and
1225
1367
  // later validation/finalization logic decides when it can leave memory.
1226
- this.pendingPayloads.delete(rootHex);
1368
+ if (this.pendingPayloads.delete(rootHex)) {
1369
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: headReason }, 1);
1370
+ }
1227
1371
  const badPendingBlocks = getAllDescendantBlocks(rootHex, this.pendingBlocks);
1228
- this.metrics?.blockInputSync.removedBlocks.inc(badPendingBlocks.length);
1229
1372
  for (const block of badPendingBlocks) {
1230
1373
  const descendantRootHex = getBlockInputSyncCacheItemRootHex(block);
1231
- this.pendingBlocks.delete(descendantRootHex);
1232
- this.pendingPayloads.delete(descendantRootHex);
1374
+ if (this.pendingBlocks.delete(descendantRootHex)) {
1375
+ this.metrics?.blockInputSync.removedBlocks.inc({ reason: DroppedItemReason.invalidParent }, 1);
1376
+ }
1377
+ if (this.pendingPayloads.delete(descendantRootHex)) {
1378
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: DroppedItemReason.invalidParent }, 1);
1379
+ }
1233
1380
  this.chain.seenBlockInputCache.prune(descendantRootHex);
1234
1381
  this.logger.debug("Removing pending descendant after invalid parent payload", {
1235
1382
  slot: getBlockInputSyncCacheItemSlot(block),
@@ -1238,16 +1385,20 @@ export class BlockInputSync {
1238
1385
  });
1239
1386
  }
1240
1387
  }
1241
- removeAllDescendants(block) {
1388
+ removeAllDescendants(block, headReason, descendantReason = DroppedItemReason.invalidParent) {
1242
1389
  const rootHex = getBlockInputSyncCacheItemRootHex(block);
1243
1390
  const slot = getBlockInputSyncCacheItemSlot(block);
1244
- // Get all blocks that are a descendant of this one
1391
+ // Get all blocks that are a descendant of this one (index 0 is the head item itself)
1245
1392
  const badPendingBlocks = [block, ...getAllDescendantBlocks(rootHex, this.pendingBlocks)];
1246
- this.metrics?.blockInputSync.removedBlocks.inc(badPendingBlocks.length);
1247
- for (const block of badPendingBlocks) {
1393
+ for (const [i, block] of badPendingBlocks.entries()) {
1248
1394
  const rootHex = getBlockInputSyncCacheItemRootHex(block);
1249
- this.pendingBlocks.delete(rootHex);
1250
- this.pendingPayloads.delete(rootHex);
1395
+ const reason = i === 0 ? headReason : descendantReason;
1396
+ if (this.pendingBlocks.delete(rootHex)) {
1397
+ this.metrics?.blockInputSync.removedBlocks.inc({ reason }, 1);
1398
+ }
1399
+ if (this.pendingPayloads.delete(rootHex)) {
1400
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason }, 1);
1401
+ }
1251
1402
  this.chain.seenBlockInputCache.prune(rootHex);
1252
1403
  // Keep PayloadEnvelopeInput resident in the seen cache for consistency with the
1253
1404
  // importBlock()-owned lifecycle.
@@ -1318,17 +1469,21 @@ export class UnknownBlockPeerBalancer {
1318
1469
  return retryAt;
1319
1470
  }
1320
1471
  /**
1321
- * called from fetchBlockInput() where we only have block root and nothing else
1472
+ * called from fetchBlockInput()/fetchPayloadInput() with a block/payload root.
1322
1473
  * excludedPeers are the peers that we requested already so we don't want to try again
1323
1474
  * pendingColumns is empty for prefulu, or the 1st time we we download a block by root
1475
+ * preferredPeers are the peers NetworkProcessor told us gossiped this root (from
1476
+ * pendingBlock/cacheItem.peerIdStrings). They are preferred but not required.
1324
1477
  */
1325
- bestPeerForPendingColumns(pendingColumns, excludedPeers) {
1478
+ bestPeerForPendingColumns(pendingColumns, excludedPeers, preferredPeers) {
1326
1479
  const eligiblePeers = this.filterPeers(pendingColumns, excludedPeers);
1327
1480
  if (eligiblePeers.length === 0) {
1328
1481
  return null;
1329
1482
  }
1330
1483
  const sortedEligiblePeers = sortBy(shuffle(eligiblePeers),
1331
- // prefer peers with least active req
1484
+ // 1st key: prefer peers we heard about this root from
1485
+ (peerId) => (preferredPeers?.has(peerId) ? 0 : 1),
1486
+ // 2nd key (tiebreak): prefer peers with least active req
1332
1487
  (peerId) => this.activeRequests.get(peerId) ?? 0);
1333
1488
  const bestPeerId = sortedEligiblePeers[0];
1334
1489
  this.onRequest(bestPeerId);