@lodestar/beacon-node 1.43.0-dev.aef3645690 → 1.43.0-dev.b05ea98d04

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 (401) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +17 -9
  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 +45 -2
  6. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  7. package/lib/api/impl/beacon/state/utils.d.ts +2 -2
  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/debug/index.d.ts.map +1 -1
  11. package/lib/api/impl/debug/index.js +0 -1
  12. package/lib/api/impl/debug/index.js.map +1 -1
  13. package/lib/api/impl/lodestar/index.js +1 -1
  14. package/lib/api/impl/lodestar/index.js.map +1 -1
  15. package/lib/api/impl/validator/index.d.ts.map +1 -1
  16. package/lib/api/impl/validator/index.js +68 -5
  17. package/lib/api/impl/validator/index.js.map +1 -1
  18. package/lib/chain/GetBlobsTracker.d.ts +1 -1
  19. package/lib/chain/GetBlobsTracker.d.ts.map +1 -1
  20. package/lib/chain/GetBlobsTracker.js +1 -2
  21. package/lib/chain/GetBlobsTracker.js.map +1 -1
  22. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  23. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  24. package/lib/chain/archiveStore/interface.d.ts +4 -4
  25. package/lib/chain/archiveStore/interface.d.ts.map +1 -1
  26. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.d.ts +4 -4
  27. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.d.ts.map +1 -1
  28. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js +2 -4
  29. package/lib/chain/archiveStore/strategies/frequencyStateArchiveStrategy.js.map +1 -1
  30. package/lib/chain/archiveStore/utils/archiveBlocks.d.ts +2 -2
  31. package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
  32. package/lib/chain/archiveStore/utils/archiveBlocks.js +110 -58
  33. package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
  34. package/lib/chain/blocks/blockInput/blockInput.d.ts +3 -0
  35. package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
  36. package/lib/chain/blocks/blockInput/blockInput.js +4 -1
  37. package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
  38. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  39. package/lib/chain/blocks/importBlock.js +38 -58
  40. package/lib/chain/blocks/importBlock.js.map +1 -1
  41. package/lib/chain/blocks/importExecutionPayload.d.ts +28 -14
  42. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  43. package/lib/chain/blocks/importExecutionPayload.js +89 -89
  44. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  45. package/lib/chain/blocks/index.d.ts +5 -3
  46. package/lib/chain/blocks/index.d.ts.map +1 -1
  47. package/lib/chain/blocks/index.js +59 -26
  48. package/lib/chain/blocks/index.js.map +1 -1
  49. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +7 -0
  50. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  51. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +29 -2
  52. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  53. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +1 -0
  54. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  55. package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts +5 -0
  56. package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts.map +1 -1
  57. package/lib/chain/blocks/payloadEnvelopeProcessor.js +7 -5
  58. package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
  59. package/lib/chain/blocks/types.d.ts +16 -21
  60. package/lib/chain/blocks/types.d.ts.map +1 -1
  61. package/lib/chain/blocks/utils/chainSegment.d.ts +23 -2
  62. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  63. package/lib/chain/blocks/utils/chainSegment.js +89 -12
  64. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  65. package/lib/chain/blocks/verifyBlock.d.ts +5 -3
  66. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  67. package/lib/chain/blocks/verifyBlock.js +50 -7
  68. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  69. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts +0 -4
  70. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts.map +1 -1
  71. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +5 -2
  72. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
  73. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts +2 -1
  74. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
  75. package/lib/chain/blocks/verifyBlocksSanityChecks.js +25 -5
  76. package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
  77. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +24 -0
  78. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -0
  79. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +79 -0
  80. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -0
  81. package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts +14 -0
  82. package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -0
  83. package/lib/chain/blocks/verifyPayloadsDataAvailability.js +30 -0
  84. package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -0
  85. package/lib/chain/blocks/writePayloadEnvelopeInputToDb.d.ts +1 -1
  86. package/lib/chain/blocks/writePayloadEnvelopeInputToDb.d.ts.map +1 -1
  87. package/lib/chain/blocks/writePayloadEnvelopeInputToDb.js +2 -11
  88. package/lib/chain/blocks/writePayloadEnvelopeInputToDb.js.map +1 -1
  89. package/lib/chain/chain.d.ts +8 -6
  90. package/lib/chain/chain.d.ts.map +1 -1
  91. package/lib/chain/chain.js +46 -50
  92. package/lib/chain/chain.js.map +1 -1
  93. package/lib/chain/emitter.d.ts +16 -15
  94. package/lib/chain/emitter.d.ts.map +1 -1
  95. package/lib/chain/emitter.js +5 -4
  96. package/lib/chain/emitter.js.map +1 -1
  97. package/lib/chain/errors/attestationError.d.ts +8 -1
  98. package/lib/chain/errors/attestationError.d.ts.map +1 -1
  99. package/lib/chain/errors/attestationError.js +4 -0
  100. package/lib/chain/errors/attestationError.js.map +1 -1
  101. package/lib/chain/errors/blockError.d.ts +8 -1
  102. package/lib/chain/errors/blockError.d.ts.map +1 -1
  103. package/lib/chain/errors/blockError.js +2 -0
  104. package/lib/chain/errors/blockError.js.map +1 -1
  105. package/lib/chain/errors/executionPayloadBid.d.ts +5 -0
  106. package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
  107. package/lib/chain/errors/executionPayloadBid.js +1 -0
  108. package/lib/chain/errors/executionPayloadBid.js.map +1 -1
  109. package/lib/chain/errors/executionPayloadEnvelope.d.ts +5 -0
  110. package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
  111. package/lib/chain/errors/executionPayloadEnvelope.js +1 -0
  112. package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
  113. package/lib/chain/errors/index.d.ts +1 -0
  114. package/lib/chain/errors/index.d.ts.map +1 -1
  115. package/lib/chain/errors/index.js +1 -0
  116. package/lib/chain/errors/index.js.map +1 -1
  117. package/lib/chain/errors/proposerPreferences.d.ts +40 -0
  118. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -0
  119. package/lib/chain/errors/proposerPreferences.js +14 -0
  120. package/lib/chain/errors/proposerPreferences.js.map +1 -0
  121. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  122. package/lib/chain/forkChoice/index.js +11 -15
  123. package/lib/chain/forkChoice/index.js.map +1 -1
  124. package/lib/chain/initState.d.ts.map +1 -1
  125. package/lib/chain/initState.js +6 -1
  126. package/lib/chain/initState.js.map +1 -1
  127. package/lib/chain/interface.d.ts +7 -5
  128. package/lib/chain/interface.d.ts.map +1 -1
  129. package/lib/chain/interface.js.map +1 -1
  130. package/lib/chain/opPools/payloadAttestationPool.d.ts +3 -2
  131. package/lib/chain/opPools/payloadAttestationPool.d.ts.map +1 -1
  132. package/lib/chain/opPools/payloadAttestationPool.js +26 -4
  133. package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
  134. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  135. package/lib/chain/prepareNextSlot.js +47 -23
  136. package/lib/chain/prepareNextSlot.js.map +1 -1
  137. package/lib/chain/produceBlock/computeNewStateRoot.d.ts +3 -9
  138. package/lib/chain/produceBlock/computeNewStateRoot.d.ts.map +1 -1
  139. package/lib/chain/produceBlock/computeNewStateRoot.js +5 -32
  140. package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
  141. package/lib/chain/produceBlock/produceBlockBody.d.ts +13 -8
  142. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  143. package/lib/chain/produceBlock/produceBlockBody.js +68 -25
  144. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  145. package/lib/chain/regen/errors.d.ts +1 -11
  146. package/lib/chain/regen/errors.d.ts.map +1 -1
  147. package/lib/chain/regen/errors.js +0 -2
  148. package/lib/chain/regen/errors.js.map +1 -1
  149. package/lib/chain/regen/interface.d.ts +7 -12
  150. package/lib/chain/regen/interface.d.ts.map +1 -1
  151. package/lib/chain/regen/interface.js +1 -0
  152. package/lib/chain/regen/interface.js.map +1 -1
  153. package/lib/chain/regen/queued.d.ts +6 -11
  154. package/lib/chain/regen/queued.d.ts.map +1 -1
  155. package/lib/chain/regen/queued.js +9 -44
  156. package/lib/chain/regen/queued.js.map +1 -1
  157. package/lib/chain/regen/regen.d.ts +0 -5
  158. package/lib/chain/regen/regen.d.ts.map +1 -1
  159. package/lib/chain/regen/regen.js +8 -38
  160. package/lib/chain/regen/regen.js.map +1 -1
  161. package/lib/chain/seenCache/index.d.ts +1 -0
  162. package/lib/chain/seenCache/index.d.ts.map +1 -1
  163. package/lib/chain/seenCache/index.js +1 -0
  164. package/lib/chain/seenCache/index.js.map +1 -1
  165. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +22 -6
  166. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  167. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +53 -17
  168. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  169. package/lib/chain/seenCache/seenProposerPreferences.d.ts +16 -0
  170. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -0
  171. package/lib/chain/seenCache/seenProposerPreferences.js +26 -0
  172. package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -0
  173. package/lib/chain/stateCache/datastore/db.d.ts +5 -4
  174. package/lib/chain/stateCache/datastore/db.d.ts.map +1 -1
  175. package/lib/chain/stateCache/datastore/db.js +10 -32
  176. package/lib/chain/stateCache/datastore/db.js.map +1 -1
  177. package/lib/chain/stateCache/datastore/file.d.ts +1 -1
  178. package/lib/chain/stateCache/datastore/file.d.ts.map +1 -1
  179. package/lib/chain/stateCache/datastore/file.js +5 -5
  180. package/lib/chain/stateCache/datastore/file.js.map +1 -1
  181. package/lib/chain/stateCache/datastore/types.d.ts +1 -1
  182. package/lib/chain/stateCache/datastore/types.d.ts.map +1 -1
  183. package/lib/chain/stateCache/fifoBlockStateCache.d.ts +1 -7
  184. package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
  185. package/lib/chain/stateCache/fifoBlockStateCache.js +0 -8
  186. package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
  187. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +13 -30
  188. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  189. package/lib/chain/stateCache/persistentCheckpointsCache.js +120 -216
  190. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  191. package/lib/chain/stateCache/types.d.ts +8 -15
  192. package/lib/chain/stateCache/types.d.ts.map +1 -1
  193. package/lib/chain/stateCache/types.js.map +1 -1
  194. package/lib/chain/validation/aggregateAndProof.js +12 -0
  195. package/lib/chain/validation/aggregateAndProof.js.map +1 -1
  196. package/lib/chain/validation/attestation.d.ts.map +1 -1
  197. package/lib/chain/validation/attestation.js +12 -0
  198. package/lib/chain/validation/attestation.js.map +1 -1
  199. package/lib/chain/validation/block.d.ts.map +1 -1
  200. package/lib/chain/validation/block.js +1 -0
  201. package/lib/chain/validation/block.js.map +1 -1
  202. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  203. package/lib/chain/validation/executionPayloadBid.js +24 -9
  204. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  205. package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
  206. package/lib/chain/validation/executionPayloadEnvelope.js +21 -11
  207. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  208. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  209. package/lib/chain/validation/payloadAttestationMessage.js +4 -3
  210. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  211. package/lib/chain/validation/proposerPreferences.d.ts +8 -0
  212. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -0
  213. package/lib/chain/validation/proposerPreferences.js +91 -0
  214. package/lib/chain/validation/proposerPreferences.js.map +1 -0
  215. package/lib/db/repositories/executionPayloadEnvelopeArchive.js +1 -1
  216. package/lib/db/repositories/executionPayloadEnvelopeArchive.js.map +1 -1
  217. package/lib/execution/engine/http.d.ts.map +1 -1
  218. package/lib/execution/engine/http.js +21 -14
  219. package/lib/execution/engine/http.js.map +1 -1
  220. package/lib/execution/engine/interface.d.ts +1 -0
  221. package/lib/execution/engine/interface.d.ts.map +1 -1
  222. package/lib/execution/engine/mock.d.ts.map +1 -1
  223. package/lib/execution/engine/mock.js +6 -0
  224. package/lib/execution/engine/mock.js.map +1 -1
  225. package/lib/execution/engine/types.d.ts +20 -0
  226. package/lib/execution/engine/types.d.ts.map +1 -1
  227. package/lib/execution/engine/types.js +18 -0
  228. package/lib/execution/engine/types.js.map +1 -1
  229. package/lib/metrics/metrics/lodestar.d.ts +1 -0
  230. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  231. package/lib/metrics/metrics/lodestar.js +4 -0
  232. package/lib/metrics/metrics/lodestar.js.map +1 -1
  233. package/lib/network/gossip/interface.d.ts +7 -1
  234. package/lib/network/gossip/interface.d.ts.map +1 -1
  235. package/lib/network/gossip/interface.js +1 -0
  236. package/lib/network/gossip/interface.js.map +1 -1
  237. package/lib/network/gossip/scoringParameters.d.ts.map +1 -1
  238. package/lib/network/gossip/scoringParameters.js +12 -1
  239. package/lib/network/gossip/scoringParameters.js.map +1 -1
  240. package/lib/network/gossip/topic.d.ts +32 -748
  241. package/lib/network/gossip/topic.d.ts.map +1 -1
  242. package/lib/network/gossip/topic.js +6 -0
  243. package/lib/network/gossip/topic.js.map +1 -1
  244. package/lib/network/interface.d.ts +1 -0
  245. package/lib/network/interface.d.ts.map +1 -1
  246. package/lib/network/network.d.ts +1 -0
  247. package/lib/network/network.d.ts.map +1 -1
  248. package/lib/network/network.js +6 -1
  249. package/lib/network/network.js.map +1 -1
  250. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  251. package/lib/network/processor/gossipHandlers.js +60 -22
  252. package/lib/network/processor/gossipHandlers.js.map +1 -1
  253. package/lib/network/processor/gossipQueues/index.d.ts.map +1 -1
  254. package/lib/network/processor/gossipQueues/index.js +5 -0
  255. package/lib/network/processor/gossipQueues/index.js.map +1 -1
  256. package/lib/network/processor/index.d.ts.map +1 -1
  257. package/lib/network/processor/index.js +6 -5
  258. package/lib/network/processor/index.js.map +1 -1
  259. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
  260. package/lib/network/reqresp/handlers/beaconBlocksByRange.js +14 -6
  261. package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
  262. package/lib/network/reqresp/handlers/blobSidecarsByRange.d.ts.map +1 -1
  263. package/lib/network/reqresp/handlers/blobSidecarsByRange.js +11 -5
  264. package/lib/network/reqresp/handlers/blobSidecarsByRange.js.map +1 -1
  265. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  266. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +17 -5
  267. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  268. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
  269. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +7 -4
  270. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
  271. package/lib/node/nodejs.d.ts.map +1 -1
  272. package/lib/node/nodejs.js +6 -4
  273. package/lib/node/nodejs.js.map +1 -1
  274. package/lib/sync/constants.d.ts +3 -1
  275. package/lib/sync/constants.d.ts.map +1 -1
  276. package/lib/sync/constants.js +3 -4
  277. package/lib/sync/constants.js.map +1 -1
  278. package/lib/sync/range/batch.d.ts +28 -2
  279. package/lib/sync/range/batch.d.ts.map +1 -1
  280. package/lib/sync/range/batch.js +146 -44
  281. package/lib/sync/range/batch.js.map +1 -1
  282. package/lib/sync/range/chain.d.ts +12 -2
  283. package/lib/sync/range/chain.d.ts.map +1 -1
  284. package/lib/sync/range/chain.js +53 -9
  285. package/lib/sync/range/chain.js.map +1 -1
  286. package/lib/sync/range/range.d.ts.map +1 -1
  287. package/lib/sync/range/range.js +17 -6
  288. package/lib/sync/range/range.js.map +1 -1
  289. package/lib/sync/types.d.ts +34 -0
  290. package/lib/sync/types.d.ts.map +1 -1
  291. package/lib/sync/types.js +34 -0
  292. package/lib/sync/types.js.map +1 -1
  293. package/lib/sync/unknownBlock.d.ts +22 -1
  294. package/lib/sync/unknownBlock.d.ts.map +1 -1
  295. package/lib/sync/unknownBlock.js +604 -53
  296. package/lib/sync/unknownBlock.js.map +1 -1
  297. package/lib/sync/utils/downloadByRange.d.ts +46 -10
  298. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  299. package/lib/sync/utils/downloadByRange.js +162 -24
  300. package/lib/sync/utils/downloadByRange.js.map +1 -1
  301. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  302. package/lib/sync/utils/downloadByRoot.js +16 -2
  303. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  304. package/lib/sync/utils/pendingBlocksTree.d.ts +0 -1
  305. package/lib/sync/utils/pendingBlocksTree.d.ts.map +1 -1
  306. package/lib/sync/utils/pendingBlocksTree.js +0 -9
  307. package/lib/sync/utils/pendingBlocksTree.js.map +1 -1
  308. package/lib/util/sszBytes.d.ts.map +1 -1
  309. package/lib/util/sszBytes.js +20 -5
  310. package/lib/util/sszBytes.js.map +1 -1
  311. package/package.json +17 -16
  312. package/src/api/impl/beacon/blocks/index.ts +22 -9
  313. package/src/api/impl/beacon/pool/index.ts +83 -1
  314. package/src/api/impl/beacon/state/utils.ts +2 -2
  315. package/src/api/impl/debug/index.ts +0 -1
  316. package/src/api/impl/lodestar/index.ts +1 -1
  317. package/src/api/impl/validator/index.ts +84 -6
  318. package/src/chain/GetBlobsTracker.ts +1 -2
  319. package/src/chain/archiveStore/archiveStore.ts +5 -5
  320. package/src/chain/archiveStore/interface.ts +4 -4
  321. package/src/chain/archiveStore/strategies/frequencyStateArchiveStrategy.ts +6 -8
  322. package/src/chain/archiveStore/utils/archiveBlocks.ts +153 -94
  323. package/src/chain/blocks/blockInput/blockInput.ts +4 -1
  324. package/src/chain/blocks/importBlock.ts +38 -83
  325. package/src/chain/blocks/importExecutionPayload.ts +110 -102
  326. package/src/chain/blocks/index.ts +74 -24
  327. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +37 -2
  328. package/src/chain/blocks/payloadEnvelopeInput/types.ts +1 -0
  329. package/src/chain/blocks/payloadEnvelopeProcessor.ts +7 -6
  330. package/src/chain/blocks/types.ts +16 -26
  331. package/src/chain/blocks/utils/chainSegment.ts +114 -17
  332. package/src/chain/blocks/verifyBlock.ts +70 -9
  333. package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +6 -4
  334. package/src/chain/blocks/verifyBlocksSanityChecks.ts +26 -7
  335. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +134 -0
  336. package/src/chain/blocks/verifyPayloadsDataAvailability.ts +41 -0
  337. package/src/chain/blocks/writePayloadEnvelopeInputToDb.ts +9 -18
  338. package/src/chain/chain.ts +63 -72
  339. package/src/chain/emitter.ts +15 -14
  340. package/src/chain/errors/attestationError.ts +6 -1
  341. package/src/chain/errors/blockError.ts +4 -1
  342. package/src/chain/errors/executionPayloadBid.ts +6 -0
  343. package/src/chain/errors/executionPayloadEnvelope.ts +6 -0
  344. package/src/chain/errors/index.ts +1 -0
  345. package/src/chain/errors/proposerPreferences.ts +47 -0
  346. package/src/chain/forkChoice/index.ts +8 -20
  347. package/src/chain/initState.ts +9 -1
  348. package/src/chain/interface.ts +11 -3
  349. package/src/chain/opPools/payloadAttestationPool.ts +29 -8
  350. package/src/chain/prepareNextSlot.ts +55 -24
  351. package/src/chain/produceBlock/computeNewStateRoot.ts +6 -43
  352. package/src/chain/produceBlock/produceBlockBody.ts +91 -27
  353. package/src/chain/regen/errors.ts +1 -6
  354. package/src/chain/regen/interface.ts +7 -12
  355. package/src/chain/regen/queued.ts +14 -55
  356. package/src/chain/regen/regen.ts +10 -43
  357. package/src/chain/seenCache/index.ts +1 -0
  358. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +71 -20
  359. package/src/chain/seenCache/seenProposerPreferences.ts +32 -0
  360. package/src/chain/stateCache/datastore/db.ts +10 -33
  361. package/src/chain/stateCache/datastore/file.ts +5 -6
  362. package/src/chain/stateCache/datastore/types.ts +2 -3
  363. package/src/chain/stateCache/fifoBlockStateCache.ts +1 -10
  364. package/src/chain/stateCache/persistentCheckpointsCache.ts +139 -247
  365. package/src/chain/stateCache/types.ts +8 -14
  366. package/src/chain/validation/aggregateAndProof.ts +13 -0
  367. package/src/chain/validation/attestation.ts +13 -0
  368. package/src/chain/validation/block.ts +1 -0
  369. package/src/chain/validation/executionPayloadBid.ts +25 -8
  370. package/src/chain/validation/executionPayloadEnvelope.ts +22 -12
  371. package/src/chain/validation/payloadAttestationMessage.ts +5 -3
  372. package/src/chain/validation/proposerPreferences.ts +110 -0
  373. package/src/db/repositories/executionPayloadEnvelopeArchive.ts +1 -1
  374. package/src/execution/engine/http.ts +21 -14
  375. package/src/execution/engine/interface.ts +1 -0
  376. package/src/execution/engine/mock.ts +8 -1
  377. package/src/execution/engine/types.ts +41 -0
  378. package/src/metrics/metrics/lodestar.ts +4 -0
  379. package/src/network/gossip/interface.ts +6 -0
  380. package/src/network/gossip/scoringParameters.ts +14 -1
  381. package/src/network/gossip/topic.ts +6 -0
  382. package/src/network/interface.ts +1 -0
  383. package/src/network/network.ts +12 -1
  384. package/src/network/processor/gossipHandlers.ts +79 -27
  385. package/src/network/processor/gossipQueues/index.ts +5 -0
  386. package/src/network/processor/index.ts +6 -5
  387. package/src/network/reqresp/handlers/beaconBlocksByRange.ts +14 -6
  388. package/src/network/reqresp/handlers/blobSidecarsByRange.ts +11 -5
  389. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +17 -5
  390. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +7 -4
  391. package/src/node/nodejs.ts +6 -4
  392. package/src/sync/constants.ts +4 -4
  393. package/src/sync/range/batch.ts +204 -49
  394. package/src/sync/range/chain.ts +69 -11
  395. package/src/sync/range/range.ts +18 -6
  396. package/src/sync/types.ts +72 -0
  397. package/src/sync/unknownBlock.ts +762 -57
  398. package/src/sync/utils/downloadByRange.ts +272 -39
  399. package/src/sync/utils/downloadByRoot.ts +24 -2
  400. package/src/sync/utils/pendingBlocksTree.ts +0 -15
  401. package/src/util/sszBytes.ts +25 -5
@@ -1,19 +1,24 @@
1
+ import { routes } from "@lodestar/api";
1
2
  import { ForkSeq } from "@lodestar/params";
2
3
  import { RequestError, RequestErrorCode } from "@lodestar/reqresp";
3
4
  import { computeTimeAtSlot } from "@lodestar/state-transition";
4
- import { prettyPrintIndices, pruneSetToMax, sleep } from "@lodestar/utils";
5
+ import { fromHex, prettyPrintIndices, pruneSetToMax, sleep, toRootHex } from "@lodestar/utils";
5
6
  import { isBlockInputBlobs, isBlockInputColumns } from "../chain/blocks/blockInput/blockInput.js";
6
7
  import { BlockInputSource } from "../chain/blocks/blockInput/types.js";
8
+ import { PayloadError, PayloadErrorCode } from "../chain/blocks/importExecutionPayload.js";
9
+ import { PayloadEnvelopeInputSource } from "../chain/blocks/payloadEnvelopeInput/index.js";
7
10
  import { BlockError, BlockErrorCode } from "../chain/errors/index.js";
8
11
  import { ChainEvent } from "../chain/index.js";
12
+ import { validateGloasBlockDataColumnSidecars } from "../chain/validation/dataColumnSidecar.js";
13
+ import { validateGossipExecutionPayloadEnvelope } from "../chain/validation/executionPayloadEnvelope.js";
9
14
  import { NetworkEvent, prettyPrintPeerIdStr } from "../network/index.js";
10
15
  import { shuffle } from "../util/shuffle.js";
11
16
  import { sortBy } from "../util/sortBy.js";
12
17
  import { wrapError } from "../util/wrapError.js";
13
18
  import { MAX_CONCURRENT_REQUESTS } from "./constants.js";
14
- import { PendingBlockInputStatus, PendingBlockType, getBlockInputSyncCacheItemRootHex, getBlockInputSyncCacheItemSlot, isPendingBlockInput, } from "./types.js";
19
+ import { PendingBlockInputStatus, PendingBlockType, PendingPayloadInputStatus, getBlockInputSyncCacheItemRootHex, getBlockInputSyncCacheItemSlot, getPayloadSyncCacheItemRootHex, getPayloadSyncCacheItemSlot, isPendingBlockInput, isPendingPayloadEnvelope, isPendingPayloadInput, } from "./types.js";
15
20
  import { DownloadByRootError, downloadByRoot } from "./utils/downloadByRoot.js";
16
- import { getAllDescendantBlocks, getDescendantBlocks, getUnknownAndAncestorBlocks } from "./utils/pendingBlocksTree.js";
21
+ import { getAllDescendantBlocks, getUnknownAndAncestorBlocks } from "./utils/pendingBlocksTree.js";
17
22
  const MAX_ATTEMPTS_PER_BLOCK = 5;
18
23
  const MAX_KNOWN_BAD_BLOCKS = 500;
19
24
  const MAX_PENDING_BLOCKS = 100;
@@ -68,6 +73,8 @@ export class BlockInputSync {
68
73
  * block RootHex -> PendingBlock. To avoid finding same root at the same time
69
74
  */
70
75
  pendingBlocks = new Map();
76
+ // Payload sync is keyed by beacon block root as well, so block and payload queues can unblock each other.
77
+ pendingPayloads = new Map();
71
78
  knownBadBlocks = new Set();
72
79
  maxPendingBlocks;
73
80
  subscribedToNetworkEvents = false;
@@ -83,6 +90,7 @@ export class BlockInputSync {
83
90
  this.peerBalancer = new UnknownBlockPeerBalancer();
84
91
  if (metrics) {
85
92
  metrics.blockInputSync.pendingBlocks.addCollect(() => metrics.blockInputSync.pendingBlocks.set(this.pendingBlocks.size));
93
+ metrics.blockInputSync.pendingPayloads.addCollect(() => metrics.blockInputSync.pendingPayloads.set(this.pendingPayloads.size));
86
94
  metrics.blockInputSync.knownBadBlocks.addCollect(() => metrics.blockInputSync.knownBadBlocks.set(this.knownBadBlocks.size));
87
95
  }
88
96
  }
@@ -95,8 +103,12 @@ export class BlockInputSync {
95
103
  if (!this.subscribedToNetworkEvents) {
96
104
  this.logger.verbose("BlockInputSync enabled.");
97
105
  this.chain.emitter.on(ChainEvent.unknownBlockRoot, this.onUnknownBlockRoot);
106
+ this.chain.emitter.on(ChainEvent.unknownEnvelopeBlockRoot, this.onUnknownEnvelopeBlockRoot);
98
107
  this.chain.emitter.on(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
108
+ this.chain.emitter.on(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
99
109
  this.chain.emitter.on(ChainEvent.blockUnknownParent, this.onUnknownParent);
110
+ this.chain.emitter.on(routes.events.EventType.block, this.onBlockImported);
111
+ this.chain.emitter.on(routes.events.EventType.executionPayload, this.onPayloadImported);
100
112
  this.network.events.on(NetworkEvent.peerConnected, this.onPeerConnected);
101
113
  this.network.events.on(NetworkEvent.peerDisconnected, this.onPeerDisconnected);
102
114
  this.subscribedToNetworkEvents = true;
@@ -105,8 +117,12 @@ export class BlockInputSync {
105
117
  unsubscribeFromNetwork() {
106
118
  this.logger.verbose("BlockInputSync disabled.");
107
119
  this.chain.emitter.off(ChainEvent.unknownBlockRoot, this.onUnknownBlockRoot);
120
+ this.chain.emitter.off(ChainEvent.unknownEnvelopeBlockRoot, this.onUnknownEnvelopeBlockRoot);
108
121
  this.chain.emitter.off(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
122
+ this.chain.emitter.off(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
109
123
  this.chain.emitter.off(ChainEvent.blockUnknownParent, this.onUnknownParent);
124
+ this.chain.emitter.off(routes.events.EventType.block, this.onBlockImported);
125
+ this.chain.emitter.off(routes.events.EventType.executionPayload, this.onPayloadImported);
110
126
  this.network.events.off(NetworkEvent.peerConnected, this.onPeerConnected);
111
127
  this.network.events.off(NetworkEvent.peerDisconnected, this.onPeerDisconnected);
112
128
  this.subscribedToNetworkEvents = false;
@@ -145,12 +161,54 @@ export class BlockInputSync {
145
161
  this.logger.debug("Error handling incompleteBlockInput event", {}, e);
146
162
  }
147
163
  };
164
+ onUnknownEnvelopeBlockRoot = (data) => {
165
+ try {
166
+ this.addByPayloadRootHex(data.rootHex, data.peer);
167
+ this.triggerUnknownBlockSearch();
168
+ this.metrics?.blockInputSync.requests.inc({ type: PendingBlockType.UNKNOWN_DATA });
169
+ this.metrics?.blockInputSync.source.inc({ source: data.source });
170
+ }
171
+ catch (e) {
172
+ this.logger.debug("Error handling unknownEnvelopeBlockRoot event", {}, e);
173
+ }
174
+ };
175
+ onIncompletePayloadEnvelope = (data) => {
176
+ try {
177
+ this.addByPayloadInput(data.payloadInput, data.peer);
178
+ this.triggerUnknownBlockSearch();
179
+ this.metrics?.blockInputSync.requests.inc({ type: PendingBlockType.UNKNOWN_DATA });
180
+ this.metrics?.blockInputSync.source.inc({ source: data.source });
181
+ }
182
+ catch (e) {
183
+ this.logger.debug("Error handling incompletePayloadEnvelope event", {}, e);
184
+ }
185
+ };
148
186
  /**
149
187
  * Process an unknownBlockParent event and register the block in `pendingBlocks` Map.
150
188
  */
151
189
  onUnknownParent = (data) => {
152
190
  try {
153
- this.addByRootHex(data.blockInput.parentRootHex, data.peer);
191
+ const missingDependency = this.getMissingBlockDependency(data.blockInput);
192
+ if (missingDependency.kind === "invalidParentPayload") {
193
+ this.addByBlockInput(data.blockInput, data.peer);
194
+ const pendingBlock = this.pendingBlocks.get(data.blockInput.blockRootHex);
195
+ if (pendingBlock && isPendingBlockInput(pendingBlock)) {
196
+ this.logger.debug("Ignoring block with conflicting parent payload hash", {
197
+ slot: pendingBlock.blockInput.slot,
198
+ root: pendingBlock.blockInput.blockRootHex,
199
+ parentRoot: missingDependency.parentRootHex,
200
+ parentBlockHash: missingDependency.parentBlockHashHex,
201
+ });
202
+ this.removeAndDownScoreAllDescendants(pendingBlock);
203
+ }
204
+ return;
205
+ }
206
+ if (missingDependency.kind === "parentPayload") {
207
+ this.addByPayloadRootHex(missingDependency.rootHex, data.peer);
208
+ }
209
+ else if (missingDependency.kind === "parentBlock") {
210
+ this.addByRootHex(missingDependency.rootHex, data.peer);
211
+ }
154
212
  this.addByBlockInput(data.blockInput, data.peer);
155
213
  this.triggerUnknownBlockSearch();
156
214
  this.metrics?.blockInputSync.requests.inc({ type: PendingBlockType.UNKNOWN_PARENT });
@@ -160,8 +218,18 @@ export class BlockInputSync {
160
218
  this.logger.debug("Error handling unknownParent event", {}, e);
161
219
  }
162
220
  };
221
+ onBlockImported = () => {
222
+ if (this.pendingPayloads.size > 0) {
223
+ this.triggerUnknownBlockSearch();
224
+ }
225
+ };
226
+ onPayloadImported = ({ blockRoot, }) => {
227
+ this.pendingPayloads.delete(blockRoot);
228
+ this.triggerUnknownBlockSearch();
229
+ };
163
230
  addByRootHex = (rootHex, peerIdStr) => {
164
231
  let pendingBlock = this.pendingBlocks.get(rootHex);
232
+ let added = false;
165
233
  if (!pendingBlock) {
166
234
  pendingBlock = {
167
235
  status: PendingBlockInputStatus.pending,
@@ -170,6 +238,7 @@ export class BlockInputSync {
170
238
  timeAddedSec: Date.now() / 1000,
171
239
  };
172
240
  this.pendingBlocks.set(rootHex, pendingBlock);
241
+ added = true;
173
242
  this.logger.verbose("Added new rootHex to BlockInputSync.pendingBlocks", {
174
243
  root: pendingBlock.rootHex,
175
244
  peerIdStr: peerIdStr ?? "unknown peer",
@@ -184,6 +253,7 @@ export class BlockInputSync {
184
253
  if (prunedItemCount > 0) {
185
254
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingBlocks`);
186
255
  }
256
+ return added;
187
257
  };
188
258
  addByBlockInput = (blockInput, peerIdStr) => {
189
259
  let pendingBlock = this.pendingBlocks.get(blockInput.blockRootHex);
@@ -211,6 +281,43 @@ export class BlockInputSync {
211
281
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingBlocks`);
212
282
  }
213
283
  };
284
+ addByPayloadRootHex = (rootHex, peerIdStr) => {
285
+ let pendingPayload = this.pendingPayloads.get(rootHex);
286
+ let added = false;
287
+ if (!pendingPayload) {
288
+ pendingPayload = {
289
+ status: PendingPayloadInputStatus.pending,
290
+ rootHex,
291
+ peerIdStrings: new Set(),
292
+ timeAddedSec: Date.now() / 1000,
293
+ };
294
+ this.pendingPayloads.set(rootHex, pendingPayload);
295
+ added = true;
296
+ this.logger.verbose("Added new payload rootHex to BlockInputSync.pendingPayloads", {
297
+ root: rootHex,
298
+ peerIdStr: peerIdStr ?? "unknown peer",
299
+ });
300
+ }
301
+ if (peerIdStr) {
302
+ pendingPayload.peerIdStrings.add(peerIdStr);
303
+ }
304
+ const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
305
+ if (prunedItemCount > 0) {
306
+ this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
307
+ }
308
+ return added;
309
+ };
310
+ addByPayloadInput = (payloadInput, peerIdStr, envelope) => {
311
+ const pendingPayload = this.toPendingPayloadInput(payloadInput, this.pendingPayloads.get(payloadInput.blockRootHex), envelope);
312
+ if (peerIdStr) {
313
+ pendingPayload.peerIdStrings.add(peerIdStr);
314
+ }
315
+ this.pendingPayloads.set(payloadInput.blockRootHex, pendingPayload);
316
+ const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
317
+ if (prunedItemCount > 0) {
318
+ this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
319
+ }
320
+ };
214
321
  onPeerConnected = (data) => {
215
322
  try {
216
323
  const peerId = data.peer;
@@ -226,47 +333,172 @@ export class BlockInputSync {
226
333
  const peerId = data.peer;
227
334
  this.peerBalancer.onPeerDisconnected(peerId);
228
335
  };
336
+ /**
337
+ * Post-gloas, a locally complete block can still be blocked on its parent's execution payload lineage.
338
+ * Distinguish which dependency is missing so the scheduler can enqueue the right follow-up work.
339
+ */
340
+ getMissingBlockDependency(blockInput) {
341
+ const parentRootHex = blockInput.parentRootHex;
342
+ if (!this.chain.forkChoice.hasBlockHex(parentRootHex)) {
343
+ return { kind: "parentBlock", rootHex: parentRootHex };
344
+ }
345
+ if (!blockInput.hasBlock()) {
346
+ return { kind: "block", rootHex: blockInput.blockRootHex };
347
+ }
348
+ if (this.config.getForkSeq(blockInput.slot) < ForkSeq.gloas) {
349
+ return { kind: "ready" };
350
+ }
351
+ const block = blockInput.getBlock();
352
+ const parentBlockHashHex = toRootHex(block.message.body.signedExecutionPayloadBid.message.parentBlockHash);
353
+ if (this.chain.forkChoice.getBlockHexAndBlockHash(parentRootHex, parentBlockHashHex) !== null) {
354
+ return { kind: "ready" };
355
+ }
356
+ if (this.chain.forkChoice.hasPayloadHexUnsafe(parentRootHex)) {
357
+ return { kind: "invalidParentPayload", parentRootHex, parentBlockHashHex };
358
+ }
359
+ const parentPayloadInput = this.chain.seenPayloadEnvelopeInputCache.get(parentRootHex);
360
+ if (parentPayloadInput) {
361
+ if (parentPayloadInput.getBlockHashHex() === parentBlockHashHex) {
362
+ return { kind: "parentPayload", rootHex: parentRootHex };
363
+ }
364
+ return { kind: "invalidParentPayload", parentRootHex, parentBlockHashHex };
365
+ }
366
+ return { kind: "parentPayload", rootHex: parentRootHex };
367
+ }
368
+ advancePendingBlock(pendingBlock) {
369
+ const missingDependency = this.getMissingBlockDependency(pendingBlock.blockInput);
370
+ switch (missingDependency.kind) {
371
+ case "ready":
372
+ return "ready";
373
+ case "block":
374
+ pendingBlock.status = PendingBlockInputStatus.pending;
375
+ return "queued_block";
376
+ case "parentBlock": {
377
+ let added = this.addByRootHex(missingDependency.rootHex);
378
+ for (const peerIdStr of pendingBlock.peerIdStrings) {
379
+ added = this.addByRootHex(missingDependency.rootHex, peerIdStr) || added;
380
+ }
381
+ return added ? "queued_parent_block" : "blocked";
382
+ }
383
+ case "parentPayload": {
384
+ let added = this.addByPayloadRootHex(missingDependency.rootHex);
385
+ for (const peerIdStr of pendingBlock.peerIdStrings) {
386
+ added = this.addByPayloadRootHex(missingDependency.rootHex, peerIdStr) || added;
387
+ }
388
+ return added ? "queued_parent_payload" : "blocked";
389
+ }
390
+ case "invalidParentPayload":
391
+ this.logger.debug("Removing block with conflicting parent payload hash", {
392
+ slot: pendingBlock.blockInput.slot,
393
+ root: pendingBlock.blockInput.blockRootHex,
394
+ parentRoot: missingDependency.parentRootHex,
395
+ parentBlockHash: missingDependency.parentBlockHashHex,
396
+ });
397
+ this.removeAndDownScoreAllDescendants(pendingBlock);
398
+ return "removed";
399
+ }
400
+ }
401
+ toPendingPayloadInput(payloadInput, previous, envelope) {
402
+ // Normalize every payload queueing path into the same cache shape while preserving first-seen
403
+ // timing and peer provenance from any earlier by-root or envelope-only entry.
404
+ const queuedEnvelope = envelope ?? (previous && isPendingPayloadEnvelope(previous) ? previous.envelope : undefined);
405
+ if (queuedEnvelope && !payloadInput.hasPayloadEnvelope()) {
406
+ payloadInput.addPayloadEnvelope({
407
+ envelope: queuedEnvelope,
408
+ source: PayloadEnvelopeInputSource.byRoot,
409
+ seenTimestampSec: Date.now() / 1000,
410
+ });
411
+ }
412
+ return {
413
+ status: payloadInput.isComplete() ? PendingPayloadInputStatus.downloaded : PendingPayloadInputStatus.pending,
414
+ payloadInput,
415
+ timeAddedSec: previous?.timeAddedSec ?? Date.now() / 1000,
416
+ timeSyncedSec: payloadInput.isComplete() ? Date.now() / 1000 : undefined,
417
+ peerIdStrings: new Set(previous?.peerIdStrings ?? []),
418
+ };
419
+ }
229
420
  /**
230
421
  * Gather tip parent blocks with unknown parent and do a search for all of them
231
422
  */
232
423
  triggerUnknownBlockSearch = () => {
233
424
  // Cheap early stop to prevent calling the network.getConnectedPeers()
234
- if (this.pendingBlocks.size === 0) {
425
+ if (this.pendingBlocks.size === 0 && this.pendingPayloads.size === 0) {
235
426
  return;
236
427
  }
237
- // If the node loses all peers with pending unknown blocks, the sync will stall
428
+ // If the node loses all peers with pending unknown blocks or payloads, the sync will stall
238
429
  const connectedPeers = this.network.getConnectedPeers();
239
- if (connectedPeers.length === 0) {
240
- this.logger.debug("No connected peers, skipping unknown block search.");
241
- return;
242
- }
430
+ const hasConnectedPeers = connectedPeers.length > 0;
243
431
  const { unknowns, ancestors } = getUnknownAndAncestorBlocks(this.pendingBlocks);
244
- // it's rare when there is no unknown block
245
- // see https://github.com/ChainSafe/lodestar/issues/5649#issuecomment-1594213550
246
- if (unknowns.length === 0) {
247
- let processedBlocks = 0;
248
- for (const block of ancestors) {
249
- // when this happens, it's likely the block and parent block are processed by head sync
250
- if (this.chain.forkChoice.hasBlockHex(block.blockInput.parentRootHex)) {
432
+ let processedBlocks = 0;
433
+ let shouldRerunBlockSearch = false;
434
+ for (const block of ancestors) {
435
+ const advanceResult = this.advancePendingBlock(block);
436
+ switch (advanceResult) {
437
+ case "ready":
251
438
  processedBlocks++;
252
- this.processBlock(block).catch((e) => {
439
+ this.processReadyBlock(block).catch((e) => {
253
440
  this.logger.debug("Unexpected error - process old downloaded block", {}, e);
254
441
  });
442
+ break;
443
+ case "queued_block":
444
+ case "queued_parent_block":
445
+ shouldRerunBlockSearch = true;
446
+ break;
447
+ case "queued_parent_payload":
448
+ case "blocked":
449
+ case "removed":
450
+ break;
451
+ }
452
+ }
453
+ if (unknowns.length > 0) {
454
+ if (!hasConnectedPeers) {
455
+ this.logger.debug("No connected peers, skipping unknown block download.");
456
+ }
457
+ else {
458
+ // Most of the time there is exactly 1 unknown block
459
+ for (const block of unknowns) {
460
+ this.downloadBlock(block).catch((e) => {
461
+ this.logger.debug("Unexpected error - downloadBlock", { root: getBlockInputSyncCacheItemRootHex(block) }, e);
462
+ });
255
463
  }
256
464
  }
465
+ }
466
+ else if (ancestors.length > 0) {
467
+ // It's rare when there is no unknown block
468
+ // see https://github.com/ChainSafe/lodestar/issues/5649#issuecomment-1594213550
257
469
  this.logger.verbose("No unknown block, process ancestor downloaded blocks", {
258
470
  pendingBlocks: this.pendingBlocks.size,
259
471
  ancestorBlocks: ancestors.length,
260
472
  processedBlocks,
261
473
  });
262
- return;
263
474
  }
264
- // most of the time there is exactly 1 unknown block
265
- for (const block of unknowns) {
266
- this.downloadBlock(block).catch((e) => {
267
- this.logger.debug("Unexpected error - downloadBlock", { root: getBlockInputSyncCacheItemRootHex(block) }, e);
475
+ // Blocks can unblock payloads and payloads can unblock blocks, so every scheduler pass services both queues.
476
+ for (const payload of Array.from(this.pendingPayloads.values())) {
477
+ if (isPendingPayloadInput(payload) && payload.status === PendingPayloadInputStatus.downloaded) {
478
+ this.processPayload(payload).catch((e) => {
479
+ this.logger.debug("Unexpected error - process downloaded payload", {}, e);
480
+ });
481
+ continue;
482
+ }
483
+ if (isPendingPayloadEnvelope(payload)) {
484
+ this.reconcilePayloadEnvelope(payload).catch((e) => {
485
+ this.logger.debug("Unexpected error - reconcile pending payload envelope", {}, e);
486
+ });
487
+ continue;
488
+ }
489
+ if (!hasConnectedPeers) {
490
+ this.logger.debug("No connected peers, skipping unknown payload download.", {
491
+ root: getPayloadSyncCacheItemRootHex(payload),
492
+ });
493
+ continue;
494
+ }
495
+ this.downloadPayload(payload).catch((e) => {
496
+ this.logger.debug("Unexpected error - downloadPayload", { root: getPayloadSyncCacheItemRootHex(payload) }, e);
268
497
  });
269
498
  }
499
+ if (shouldRerunBlockSearch) {
500
+ this.triggerUnknownBlockSearch();
501
+ }
270
502
  };
271
503
  async downloadBlock(block) {
272
504
  if (block.status !== PendingBlockInputStatus.pending) {
@@ -297,10 +529,24 @@ export class BlockInputSync {
297
529
  };
298
530
  this.logger.verbose("Downloaded unknown block", logCtx2);
299
531
  if (parentInForkChoice) {
300
- // Bingo! Process block. Add to pending blocks anyway for recycle the cache that prevents duplicate processing
301
- this.processBlock(pending).catch((e) => {
302
- this.logger.debug("Unexpected error - process newly downloaded block", logCtx2, e);
303
- });
532
+ // If the direct parent is already in fork choice, let the block state machine decide if
533
+ // the next step is block import, parent payload download, or branch removal.
534
+ const advanceResult = this.advancePendingBlock(pending);
535
+ switch (advanceResult) {
536
+ case "ready":
537
+ this.processReadyBlock(pending).catch((e) => {
538
+ this.logger.debug("Unexpected error - process newly downloaded block", logCtx2, e);
539
+ });
540
+ break;
541
+ case "queued_block":
542
+ case "queued_parent_block":
543
+ case "queued_parent_payload":
544
+ this.triggerUnknownBlockSearch();
545
+ break;
546
+ case "blocked":
547
+ case "removed":
548
+ break;
549
+ }
304
550
  }
305
551
  else if (blockSlot <= finalizedSlot) {
306
552
  // the common ancestor of the downloading chain and canonical chain should be at least the finalized slot and
@@ -325,26 +571,11 @@ export class BlockInputSync {
325
571
  }
326
572
  }
327
573
  /**
328
- * Send block to the processor awaiting completition. If processed successfully, send all children to the processor.
329
- * On error, remove and downscore all descendants.
330
- * This function could run recursively for all descendant blocks
574
+ * Import a block that has already passed the local dependency checks in BlockInputSync.
575
+ * On error, remove and downscore descendants as appropriate for the failure type.
331
576
  */
332
- async processBlock(pendingBlock) {
333
- // pending block status is `downloaded` right after `downloadBlock`
334
- // but could be `pending` if added by `onUnknownBlockParent` event and this function is called recursively
577
+ async processReadyBlock(pendingBlock) {
335
578
  if (pendingBlock.status !== PendingBlockInputStatus.downloaded) {
336
- if (pendingBlock.status === PendingBlockInputStatus.pending) {
337
- const connectedPeers = this.network.getConnectedPeers();
338
- if (connectedPeers.length === 0) {
339
- this.logger.debug("No connected peers, skipping download block", {
340
- slot: pendingBlock.blockInput.slot,
341
- blockRoot: pendingBlock.blockInput.blockRootHex,
342
- });
343
- return;
344
- }
345
- // if the download is a success we'll call `processBlock()` for this block
346
- await this.downloadBlock(pendingBlock);
347
- }
348
579
  return;
349
580
  }
350
581
  pendingBlock.status = PendingBlockInputStatus.processing;
@@ -384,14 +615,9 @@ export class BlockInputSync {
384
615
  if (!res.err) {
385
616
  // no need to update status to "processed", delete anyway
386
617
  this.pendingBlocks.delete(pendingBlock.blockInput.blockRootHex);
387
- // Send child blocks to the processor
388
- for (const descendantBlock of getDescendantBlocks(pendingBlock.blockInput.blockRootHex, this.pendingBlocks)) {
389
- if (isPendingBlockInput(descendantBlock)) {
390
- this.processBlock(descendantBlock).catch((e) => {
391
- this.logger.debug("Unexpected error - process descendant block", {}, e);
392
- });
393
- }
394
- }
618
+ // Re-enter the scheduler so descendants blocked on either parent blocks or parent payloads
619
+ // are advanced through the same dependency checks as every other pending item.
620
+ this.triggerUnknownBlockSearch();
395
621
  }
396
622
  else {
397
623
  const errorData = { slot: pendingBlock.blockInput.slot, root: pendingBlock.blockInput.blockRootHex };
@@ -406,6 +632,14 @@ export class BlockInputSync {
406
632
  this.logger.debug("Attempted to process block but its parent was still unknown", errorData, res.err);
407
633
  pendingBlock.status = PendingBlockInputStatus.downloaded;
408
634
  break;
635
+ case BlockErrorCode.PARENT_PAYLOAD_UNKNOWN:
636
+ this.logger.error("processReadyBlock() hit unexpected parent payload dependency after readiness checks", {
637
+ ...errorData,
638
+ parentRoot: pendingBlock.blockInput.parentRootHex,
639
+ parentBlockHash: res.err.type.parentBlockHash,
640
+ }, res.err);
641
+ pendingBlock.status = PendingBlockInputStatus.downloaded;
642
+ break;
409
643
  case BlockErrorCode.EXECUTION_ENGINE_ERROR:
410
644
  // Removing the block(s) without penalizing the peers, hoping for EL to
411
645
  // recover on a latter download + verify attempt
@@ -424,6 +658,299 @@ export class BlockInputSync {
424
658
  }
425
659
  }
426
660
  }
661
+ /**
662
+ * Reconcile an envelope-first payload entry once the block import path has seeded its
663
+ * PayloadEnvelopeInput. This may queue block download, validate the speculative envelope, or
664
+ * downgrade back to by-root fetching when the cached envelope does not match the imported block.
665
+ */
666
+ async reconcilePayloadEnvelope(pendingPayload) {
667
+ const rootHex = getPayloadSyncCacheItemRootHex(pendingPayload);
668
+ if (this.chain.forkChoice.hasPayloadHexUnsafe(rootHex)) {
669
+ this.pendingPayloads.delete(rootHex);
670
+ return;
671
+ }
672
+ const payloadInput = this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
673
+ if (!payloadInput) {
674
+ if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
675
+ // Column commitments live on the block body, so an envelope-only entry has to pull the block first.
676
+ if (!this.pendingBlocks.has(rootHex)) {
677
+ this.addByRootHex(rootHex);
678
+ }
679
+ const pendingBlock = this.pendingBlocks.get(rootHex);
680
+ if (pendingBlock && this.network.getConnectedPeers().length > 0) {
681
+ await this.downloadBlock(pendingBlock);
682
+ }
683
+ }
684
+ else {
685
+ this.logger.debug("Missing PayloadEnvelopeInput for known block while reconciling payload envelope", {
686
+ root: rootHex,
687
+ });
688
+ }
689
+ return;
690
+ }
691
+ if (!payloadInput.hasPayloadEnvelope()) {
692
+ const validationResult = await wrapError(validateGossipExecutionPayloadEnvelope(this.chain, pendingPayload.envelope));
693
+ if (validationResult.err) {
694
+ this.logger.debug("Pending payload envelope failed validation after block import, refetching by root", { slot: pendingPayload.envelope.message.payload.slotNumber, root: rootHex }, validationResult.err);
695
+ const pendingPayloadByRoot = {
696
+ status: PendingPayloadInputStatus.pending,
697
+ rootHex,
698
+ timeAddedSec: pendingPayload.timeAddedSec,
699
+ peerIdStrings: new Set(pendingPayload.peerIdStrings),
700
+ };
701
+ this.pendingPayloads.set(rootHex, pendingPayloadByRoot);
702
+ if (this.network.getConnectedPeers().length > 0) {
703
+ await this.downloadPayload(pendingPayloadByRoot);
704
+ }
705
+ return;
706
+ }
707
+ }
708
+ const upgradedPayload = this.toPendingPayloadInput(payloadInput, pendingPayload, pendingPayload.envelope);
709
+ this.pendingPayloads.set(rootHex, upgradedPayload);
710
+ if (upgradedPayload.status === PendingPayloadInputStatus.downloaded) {
711
+ await this.processPayload(upgradedPayload);
712
+ return;
713
+ }
714
+ await this.downloadPayload(upgradedPayload);
715
+ }
716
+ async downloadPayload(payload) {
717
+ if (isPendingPayloadEnvelope(payload)) {
718
+ await this.reconcilePayloadEnvelope(payload);
719
+ return;
720
+ }
721
+ const rootHex = getPayloadSyncCacheItemRootHex(payload);
722
+ if (this.chain.forkChoice.hasPayloadHexUnsafe(rootHex)) {
723
+ this.pendingPayloads.delete(rootHex);
724
+ return;
725
+ }
726
+ if (payload.status !== PendingPayloadInputStatus.pending) {
727
+ return;
728
+ }
729
+ const logCtx = {
730
+ slot: getPayloadSyncCacheItemSlot(payload),
731
+ root: rootHex,
732
+ pendingPayloads: this.pendingPayloads.size,
733
+ };
734
+ this.logger.verbose("BlockInputSync.downloadPayload()", logCtx);
735
+ payload.status = PendingPayloadInputStatus.fetching;
736
+ const res = await wrapError(this.fetchPayloadInput(payload));
737
+ if (!res.err) {
738
+ const pendingPayload = res.result;
739
+ this.pendingPayloads.set(getPayloadSyncCacheItemRootHex(pendingPayload), pendingPayload);
740
+ if (isPendingPayloadEnvelope(pendingPayload)) {
741
+ await this.reconcilePayloadEnvelope(pendingPayload);
742
+ }
743
+ else if (pendingPayload.status === PendingPayloadInputStatus.downloaded) {
744
+ await this.processPayload(pendingPayload);
745
+ }
746
+ return;
747
+ }
748
+ this.logger.debug("Ignoring unknown payload root after failed download", logCtx, res.err);
749
+ if (!isPendingPayloadEnvelope(payload)) {
750
+ payload.status = PendingPayloadInputStatus.pending;
751
+ }
752
+ }
753
+ async processPayload(pendingPayload) {
754
+ const rootHex = pendingPayload.payloadInput.blockRootHex;
755
+ const logCtx = { slot: pendingPayload.payloadInput.slot, root: rootHex };
756
+ if (pendingPayload.status !== PendingPayloadInputStatus.downloaded) {
757
+ this.logger.debug("Skipping payload processing before payload input is downloaded", {
758
+ ...logCtx,
759
+ status: pendingPayload.status,
760
+ });
761
+ return;
762
+ }
763
+ if (this.chain.forkChoice.hasPayloadHexUnsafe(rootHex)) {
764
+ this.logger.debug("Payload already imported while processing unknown payload", logCtx);
765
+ this.pendingPayloads.delete(rootHex);
766
+ return;
767
+ }
768
+ if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
769
+ this.logger.debug("Payload input is ready before its block is in fork choice", logCtx);
770
+ const added = this.addByRootHex(rootHex);
771
+ pendingPayload.status = PendingPayloadInputStatus.downloaded;
772
+ if (added) {
773
+ this.triggerUnknownBlockSearch();
774
+ }
775
+ return;
776
+ }
777
+ pendingPayload.status = PendingPayloadInputStatus.processing;
778
+ const res = await wrapError(this.chain.processExecutionPayload(pendingPayload.payloadInput));
779
+ if (!res.err) {
780
+ this.logger.debug("Processed payload from unknown sync", logCtx);
781
+ this.pendingPayloads.delete(rootHex);
782
+ this.triggerUnknownBlockSearch();
783
+ return;
784
+ }
785
+ if (res.err instanceof PayloadError) {
786
+ switch (res.err.type.code) {
787
+ case PayloadErrorCode.BLOCK_NOT_IN_FORK_CHOICE:
788
+ // Payload sync discovered the block dependency before the block queue did. Re-enqueue the
789
+ // block and keep the payload ready so the scheduler can retry once the block reaches fork choice.
790
+ if (this.addByRootHex(rootHex)) {
791
+ this.triggerUnknownBlockSearch();
792
+ }
793
+ // Keep the payload out of any synchronous requeue pass; a later scheduler pass will retry it.
794
+ pendingPayload.status = PendingPayloadInputStatus.downloaded;
795
+ break;
796
+ case PayloadErrorCode.EXECUTION_ENGINE_ERROR:
797
+ this.logger.debug("Execution engine error while processing payload from unknown sync", logCtx, res.err);
798
+ pendingPayload.status = PendingPayloadInputStatus.downloaded;
799
+ break;
800
+ case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
801
+ case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
802
+ case PayloadErrorCode.INVALID_SIGNATURE:
803
+ // TODO GLOAS: Decide how invalid payload inputs should eventually leave memory without
804
+ // reintroducing envelope replacement / recreation flows.
805
+ this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
806
+ this.removePendingPayloadAndDescendants(rootHex);
807
+ break;
808
+ default:
809
+ this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
810
+ this.pendingPayloads.delete(rootHex);
811
+ }
812
+ return;
813
+ }
814
+ this.logger.debug("Unknown error processing payload from unknown sync", logCtx, res.err);
815
+ pendingPayload.status = PendingPayloadInputStatus.downloaded;
816
+ }
817
+ /**
818
+ * Download payload material keyed by beacon block root. Unlike block download, payload sync may
819
+ * already have a locally cached envelope or partial columns, so each attempt starts from local state
820
+ * and only asks peers for the remaining pieces.
821
+ */
822
+ async fetchPayloadInput(cacheItem) {
823
+ const rootHex = getPayloadSyncCacheItemRootHex(cacheItem);
824
+ const blockRoot = fromHex(rootHex);
825
+ const excludedPeers = new Set();
826
+ let slot = getPayloadSyncCacheItemSlot(cacheItem);
827
+ let payloadInput = isPendingPayloadInput(cacheItem)
828
+ ? cacheItem.payloadInput
829
+ : this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
830
+ let envelope = payloadInput?.hasPayloadEnvelope() ? payloadInput.getPayloadEnvelope() : undefined;
831
+ let i = 0;
832
+ while (i++ < this.getMaxDownloadAttempts()) {
833
+ const pendingColumns = payloadInput?.hasAllData()
834
+ ? new Set()
835
+ : new Set(payloadInput?.getMissingSampledColumnMeta().missing ?? []);
836
+ const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
837
+ if (peerMeta === null) {
838
+ throw Error(`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`);
839
+ }
840
+ const { peerId, client: peerClient } = peerMeta;
841
+ cacheItem.peerIdStrings.add(peerId);
842
+ try {
843
+ if (!envelope) {
844
+ envelope = await this.fetchExecutionPayloadEnvelope(peerId, blockRoot, rootHex);
845
+ slot = envelope.message.payload.slotNumber;
846
+ }
847
+ payloadInput ??= this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
848
+ if (!payloadInput) {
849
+ if (this.chain.forkChoice.hasBlockHex(rootHex)) {
850
+ throw new Error(`Missing PayloadEnvelopeInput for known block ${rootHex}`);
851
+ }
852
+ // Keep the validated envelope around, but wait for the block body before turning it into a full payload input.
853
+ return {
854
+ status: PendingPayloadInputStatus.waitingForBlock,
855
+ envelope,
856
+ timeAddedSec: cacheItem.timeAddedSec,
857
+ peerIdStrings: cacheItem.peerIdStrings,
858
+ };
859
+ }
860
+ if (!payloadInput.hasPayloadEnvelope()) {
861
+ await validateGossipExecutionPayloadEnvelope(this.chain, envelope);
862
+ }
863
+ let pendingPayload = this.toPendingPayloadInput(payloadInput, cacheItem, envelope);
864
+ if (!pendingPayload.payloadInput.hasAllData()) {
865
+ const missing = pendingPayload.payloadInput.getMissingSampledColumnMeta().missing;
866
+ if (missing.length > 0) {
867
+ const columnSidecars = await this.fetchPayloadColumns(peerMeta, pendingPayload.payloadInput, missing);
868
+ const seenTimestampSec = Date.now() / 1000;
869
+ for (const columnSidecar of columnSidecars) {
870
+ if (pendingPayload.payloadInput.hasColumn(columnSidecar.index)) {
871
+ continue;
872
+ }
873
+ pendingPayload.payloadInput.addColumn({
874
+ columnSidecar,
875
+ source: PayloadEnvelopeInputSource.byRoot,
876
+ seenTimestampSec,
877
+ peerIdStr: peerId,
878
+ });
879
+ }
880
+ pendingPayload = this.toPendingPayloadInput(pendingPayload.payloadInput, pendingPayload);
881
+ }
882
+ }
883
+ this.logger.verbose("BlockInputSync.fetchPayloadInput: successful download", {
884
+ slot,
885
+ rootHex,
886
+ peerId,
887
+ peerClient,
888
+ hasPayload: pendingPayload.payloadInput.hasPayloadEnvelope(),
889
+ hasAllData: pendingPayload.payloadInput.hasAllData(),
890
+ });
891
+ if (pendingPayload.status === PendingPayloadInputStatus.downloaded) {
892
+ return pendingPayload;
893
+ }
894
+ cacheItem = pendingPayload;
895
+ payloadInput = pendingPayload.payloadInput;
896
+ }
897
+ catch (e) {
898
+ this.logger.debug("Error downloading payload in BlockInputSync.fetchPayloadInput", { slot, rootHex, attempt: i, peer: peerId, peerClient }, e);
899
+ if (e instanceof RequestError) {
900
+ switch (e.type.code) {
901
+ case RequestErrorCode.REQUEST_RATE_LIMITED:
902
+ case RequestErrorCode.REQUEST_TIMEOUT:
903
+ break;
904
+ default:
905
+ excludedPeers.add(peerId);
906
+ break;
907
+ }
908
+ }
909
+ else {
910
+ excludedPeers.add(peerId);
911
+ }
912
+ }
913
+ finally {
914
+ this.peerBalancer.onRequestCompleted(peerId);
915
+ }
916
+ }
917
+ throw Error(`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts.`);
918
+ }
919
+ async fetchExecutionPayloadEnvelope(peerIdStr, blockRoot, rootHex) {
920
+ const response = await this.network.sendExecutionPayloadEnvelopesByRoot(peerIdStr, [blockRoot]);
921
+ const envelope = response.at(0);
922
+ if (!envelope) {
923
+ throw new Error(`Missing execution payload envelope for root=${rootHex}`);
924
+ }
925
+ const receivedRootHex = toRootHex(envelope.message.beaconBlockRoot);
926
+ if (receivedRootHex !== rootHex) {
927
+ throw new Error(`Execution payload envelope root mismatch requested=${rootHex} received=${receivedRootHex}`);
928
+ }
929
+ return envelope;
930
+ }
931
+ async fetchPayloadColumns(peerMeta, payloadInput, missing) {
932
+ const { peerId: peerIdStr } = peerMeta;
933
+ const peerColumns = new Set(peerMeta.custodyColumns ?? []);
934
+ const requestedColumns = missing.filter((columnIndex) => peerColumns.has(columnIndex));
935
+ if (requestedColumns.length === 0) {
936
+ return [];
937
+ }
938
+ const columnSidecars = (await this.network.sendDataColumnSidecarsByRoot(peerIdStr, [
939
+ { blockRoot: fromHex(payloadInput.blockRootHex), columns: requestedColumns },
940
+ ]));
941
+ if (columnSidecars.length === 0) {
942
+ throw new Error(`No data column sidecars returned for payload root=${payloadInput.blockRootHex}`);
943
+ }
944
+ const requestedColumnsSet = new Set(requestedColumns);
945
+ const extraColumns = columnSidecars.filter((columnSidecar) => !requestedColumnsSet.has(columnSidecar.index));
946
+ if (extraColumns.length > 0) {
947
+ throw new Error(`Received unexpected payload data columns indices=${prettyPrintIndices(extraColumns.map((column) => column.index))}`);
948
+ }
949
+ // PayloadEnvelopeInput already carries the block slot, root, and commitments, so reuse the
950
+ // block-based Gloas validator rather than maintaining a second payload-specific variant.
951
+ await validateGloasBlockDataColumnSidecars(payloadInput.slot, fromHex(payloadInput.blockRootHex), payloadInput.getBlobKzgCommitments(), columnSidecars, this.chain.metrics?.peerDas);
952
+ return columnSidecars;
953
+ }
427
954
  /**
428
955
  * From a set of shuffled peers:
429
956
  * - fetch the block
@@ -498,6 +1025,8 @@ export class BlockInputSync {
498
1025
  downloadByRootMetrics?.error.inc({ code: "req_resp", client: peerClient });
499
1026
  switch (e.type.code) {
500
1027
  case RequestErrorCode.REQUEST_RATE_LIMITED:
1028
+ case RequestErrorCode.RESP_RATE_LIMITED:
1029
+ case RequestErrorCode.REQUEST_SELF_RATE_LIMITED:
501
1030
  case RequestErrorCode.REQUEST_TIMEOUT:
502
1031
  // do not exclude peer for these errors
503
1032
  break;
@@ -582,6 +1111,25 @@ export class BlockInputSync {
582
1111
  // Prune knownBadBlocks
583
1112
  pruneSetToMax(this.knownBadBlocks, MAX_KNOWN_BAD_BLOCKS);
584
1113
  }
1114
+ // Once a parent payload is invalid, every descendant waiting on that payload lineage becomes unrecoverable too.
1115
+ removePendingPayloadAndDescendants(rootHex) {
1116
+ // Keep PayloadEnvelopeInput resident in the seen cache. importBlock() owns that object and
1117
+ // later validation/finalization logic decides when it can leave memory.
1118
+ this.pendingPayloads.delete(rootHex);
1119
+ const badPendingBlocks = getAllDescendantBlocks(rootHex, this.pendingBlocks);
1120
+ this.metrics?.blockInputSync.removedBlocks.inc(badPendingBlocks.length);
1121
+ for (const block of badPendingBlocks) {
1122
+ const descendantRootHex = getBlockInputSyncCacheItemRootHex(block);
1123
+ this.pendingBlocks.delete(descendantRootHex);
1124
+ this.pendingPayloads.delete(descendantRootHex);
1125
+ this.chain.seenBlockInputCache.prune(descendantRootHex);
1126
+ this.logger.debug("Removing pending descendant after invalid parent payload", {
1127
+ slot: getBlockInputSyncCacheItemSlot(block),
1128
+ blockRoot: descendantRootHex,
1129
+ parentPayloadRoot: rootHex,
1130
+ });
1131
+ }
1132
+ }
585
1133
  removeAllDescendants(block) {
586
1134
  const rootHex = getBlockInputSyncCacheItemRootHex(block);
587
1135
  const slot = getBlockInputSyncCacheItemSlot(block);
@@ -591,7 +1139,10 @@ export class BlockInputSync {
591
1139
  for (const block of badPendingBlocks) {
592
1140
  const rootHex = getBlockInputSyncCacheItemRootHex(block);
593
1141
  this.pendingBlocks.delete(rootHex);
1142
+ this.pendingPayloads.delete(rootHex);
594
1143
  this.chain.seenBlockInputCache.prune(rootHex);
1144
+ // Keep PayloadEnvelopeInput resident in the seen cache for consistency with the
1145
+ // importBlock()-owned lifecycle.
595
1146
  this.logger.debug("Removing bad/unknown/incomplete BlockInputSyncCacheItem", {
596
1147
  slot,
597
1148
  blockRoot: rootHex,