@lodestar/beacon-node 1.43.0-dev.bc569affb9 → 1.43.0-dev.c9dc2b2332

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 (322) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +16 -5
  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/debug/index.d.ts.map +1 -1
  8. package/lib/api/impl/debug/index.js +0 -1
  9. package/lib/api/impl/debug/index.js.map +1 -1
  10. package/lib/api/impl/lodestar/index.js +1 -1
  11. package/lib/api/impl/lodestar/index.js.map +1 -1
  12. package/lib/api/impl/validator/index.d.ts.map +1 -1
  13. package/lib/api/impl/validator/index.js +68 -2
  14. package/lib/api/impl/validator/index.js.map +1 -1
  15. package/lib/chain/blocks/blockInput/blockInput.d.ts +3 -0
  16. package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
  17. package/lib/chain/blocks/blockInput/blockInput.js +4 -1
  18. package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
  19. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  20. package/lib/chain/blocks/importBlock.js +16 -28
  21. package/lib/chain/blocks/importBlock.js.map +1 -1
  22. package/lib/chain/blocks/importExecutionPayload.d.ts +23 -6
  23. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  24. package/lib/chain/blocks/importExecutionPayload.js +57 -24
  25. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  26. package/lib/chain/blocks/index.d.ts +5 -3
  27. package/lib/chain/blocks/index.d.ts.map +1 -1
  28. package/lib/chain/blocks/index.js +58 -25
  29. package/lib/chain/blocks/index.js.map +1 -1
  30. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +12 -1
  31. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  32. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +28 -2
  33. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  34. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +17 -0
  35. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  36. package/lib/chain/blocks/payloadEnvelopeProcessor.js +2 -2
  37. package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
  38. package/lib/chain/blocks/types.d.ts +4 -3
  39. package/lib/chain/blocks/types.d.ts.map +1 -1
  40. package/lib/chain/blocks/utils/chainSegment.d.ts +21 -2
  41. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  42. package/lib/chain/blocks/utils/chainSegment.js +92 -12
  43. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  44. package/lib/chain/blocks/verifyBlock.d.ts +5 -3
  45. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  46. package/lib/chain/blocks/verifyBlock.js +50 -7
  47. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  48. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts +0 -4
  49. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts.map +1 -1
  50. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +5 -2
  51. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
  52. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts +2 -1
  53. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
  54. package/lib/chain/blocks/verifyBlocksSanityChecks.js +25 -5
  55. package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
  56. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +2 -2
  57. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  58. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +12 -8
  59. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  60. package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -1
  61. package/lib/chain/blocks/verifyPayloadsDataAvailability.js +8 -3
  62. package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -1
  63. package/lib/chain/chain.d.ts +3 -2
  64. package/lib/chain/chain.d.ts.map +1 -1
  65. package/lib/chain/chain.js +35 -12
  66. package/lib/chain/chain.js.map +1 -1
  67. package/lib/chain/emitter.d.ts +0 -11
  68. package/lib/chain/emitter.d.ts.map +1 -1
  69. package/lib/chain/emitter.js +0 -4
  70. package/lib/chain/emitter.js.map +1 -1
  71. package/lib/chain/errors/blockError.d.ts +8 -1
  72. package/lib/chain/errors/blockError.d.ts.map +1 -1
  73. package/lib/chain/errors/blockError.js +2 -0
  74. package/lib/chain/errors/blockError.js.map +1 -1
  75. package/lib/chain/errors/index.d.ts +1 -0
  76. package/lib/chain/errors/index.d.ts.map +1 -1
  77. package/lib/chain/errors/index.js +1 -0
  78. package/lib/chain/errors/index.js.map +1 -1
  79. package/lib/chain/errors/proposerPreferences.d.ts +40 -0
  80. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -0
  81. package/lib/chain/errors/proposerPreferences.js +14 -0
  82. package/lib/chain/errors/proposerPreferences.js.map +1 -0
  83. package/lib/chain/initState.d.ts.map +1 -1
  84. package/lib/chain/initState.js +6 -1
  85. package/lib/chain/initState.js.map +1 -1
  86. package/lib/chain/interface.d.ts +3 -2
  87. package/lib/chain/interface.d.ts.map +1 -1
  88. package/lib/chain/interface.js.map +1 -1
  89. package/lib/chain/opPools/payloadAttestationPool.d.ts +3 -2
  90. package/lib/chain/opPools/payloadAttestationPool.d.ts.map +1 -1
  91. package/lib/chain/opPools/payloadAttestationPool.js +26 -4
  92. package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
  93. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  94. package/lib/chain/prepareNextSlot.js +16 -18
  95. package/lib/chain/prepareNextSlot.js.map +1 -1
  96. package/lib/chain/produceBlock/produceBlockBody.d.ts +12 -3
  97. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  98. package/lib/chain/produceBlock/produceBlockBody.js +35 -17
  99. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  100. package/lib/chain/regen/interface.d.ts +1 -0
  101. package/lib/chain/regen/interface.d.ts.map +1 -1
  102. package/lib/chain/regen/interface.js +1 -0
  103. package/lib/chain/regen/interface.js.map +1 -1
  104. package/lib/chain/regen/queued.d.ts.map +1 -1
  105. package/lib/chain/regen/queued.js +1 -4
  106. package/lib/chain/regen/queued.js.map +1 -1
  107. package/lib/chain/regen/regen.d.ts.map +1 -1
  108. package/lib/chain/regen/regen.js +1 -4
  109. package/lib/chain/regen/regen.js.map +1 -1
  110. package/lib/chain/seenCache/index.d.ts +1 -0
  111. package/lib/chain/seenCache/index.d.ts.map +1 -1
  112. package/lib/chain/seenCache/index.js +1 -0
  113. package/lib/chain/seenCache/index.js.map +1 -1
  114. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +21 -11
  115. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  116. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +70 -20
  117. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  118. package/lib/chain/seenCache/seenProposerPreferences.d.ts +16 -0
  119. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -0
  120. package/lib/chain/seenCache/seenProposerPreferences.js +26 -0
  121. package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -0
  122. package/lib/chain/validation/block.d.ts.map +1 -1
  123. package/lib/chain/validation/block.js +1 -0
  124. package/lib/chain/validation/block.js.map +1 -1
  125. package/lib/chain/validation/executionPayloadBid.js +11 -8
  126. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  127. package/lib/chain/validation/proposerPreferences.d.ts +8 -0
  128. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -0
  129. package/lib/chain/validation/proposerPreferences.js +91 -0
  130. package/lib/chain/validation/proposerPreferences.js.map +1 -0
  131. package/lib/metrics/metrics/lodestar.d.ts +1 -0
  132. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  133. package/lib/metrics/metrics/lodestar.js +4 -0
  134. package/lib/metrics/metrics/lodestar.js.map +1 -1
  135. package/lib/network/gossip/interface.d.ts +7 -1
  136. package/lib/network/gossip/interface.d.ts.map +1 -1
  137. package/lib/network/gossip/interface.js +1 -0
  138. package/lib/network/gossip/interface.js.map +1 -1
  139. package/lib/network/gossip/scoringParameters.d.ts.map +1 -1
  140. package/lib/network/gossip/scoringParameters.js +12 -1
  141. package/lib/network/gossip/scoringParameters.js.map +1 -1
  142. package/lib/network/gossip/topic.d.ts +29 -766
  143. package/lib/network/gossip/topic.d.ts.map +1 -1
  144. package/lib/network/gossip/topic.js +6 -0
  145. package/lib/network/gossip/topic.js.map +1 -1
  146. package/lib/network/interface.d.ts +2 -0
  147. package/lib/network/interface.d.ts.map +1 -1
  148. package/lib/network/network.d.ts +2 -0
  149. package/lib/network/network.d.ts.map +1 -1
  150. package/lib/network/network.js +8 -0
  151. package/lib/network/network.js.map +1 -1
  152. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  153. package/lib/network/processor/gossipHandlers.js +38 -16
  154. package/lib/network/processor/gossipHandlers.js.map +1 -1
  155. package/lib/network/processor/gossipQueues/index.d.ts.map +1 -1
  156. package/lib/network/processor/gossipQueues/index.js +5 -0
  157. package/lib/network/processor/gossipQueues/index.js.map +1 -1
  158. package/lib/network/processor/index.d.ts.map +1 -1
  159. package/lib/network/processor/index.js +6 -5
  160. package/lib/network/processor/index.js.map +1 -1
  161. package/lib/network/reqresp/ReqRespBeaconNode.d.ts.map +1 -1
  162. package/lib/network/reqresp/ReqRespBeaconNode.js +1 -1
  163. package/lib/network/reqresp/ReqRespBeaconNode.js.map +1 -1
  164. package/lib/network/reqresp/handlers/beaconBlocksByHead.d.ts +9 -0
  165. package/lib/network/reqresp/handlers/beaconBlocksByHead.d.ts.map +1 -0
  166. package/lib/network/reqresp/handlers/beaconBlocksByHead.js +61 -0
  167. package/lib/network/reqresp/handlers/beaconBlocksByHead.js.map +1 -0
  168. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
  169. package/lib/network/reqresp/handlers/beaconBlocksByRange.js +14 -6
  170. package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
  171. package/lib/network/reqresp/handlers/blobSidecarsByRange.d.ts.map +1 -1
  172. package/lib/network/reqresp/handlers/blobSidecarsByRange.js +11 -5
  173. package/lib/network/reqresp/handlers/blobSidecarsByRange.js.map +1 -1
  174. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  175. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +17 -5
  176. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  177. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
  178. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +7 -4
  179. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
  180. package/lib/network/reqresp/handlers/index.d.ts.map +1 -1
  181. package/lib/network/reqresp/handlers/index.js +5 -0
  182. package/lib/network/reqresp/handlers/index.js.map +1 -1
  183. package/lib/network/reqresp/interface.d.ts +1 -1
  184. package/lib/network/reqresp/interface.js +1 -1
  185. package/lib/network/reqresp/protocols.d.ts +1 -0
  186. package/lib/network/reqresp/protocols.d.ts.map +1 -1
  187. package/lib/network/reqresp/protocols.js +5 -0
  188. package/lib/network/reqresp/protocols.js.map +1 -1
  189. package/lib/network/reqresp/rateLimit.d.ts.map +1 -1
  190. package/lib/network/reqresp/rateLimit.js +4 -0
  191. package/lib/network/reqresp/rateLimit.js.map +1 -1
  192. package/lib/network/reqresp/score.d.ts.map +1 -1
  193. package/lib/network/reqresp/score.js +1 -0
  194. package/lib/network/reqresp/score.js.map +1 -1
  195. package/lib/network/reqresp/types.d.ts +3 -0
  196. package/lib/network/reqresp/types.d.ts.map +1 -1
  197. package/lib/network/reqresp/types.js +3 -0
  198. package/lib/network/reqresp/types.js.map +1 -1
  199. package/lib/node/nodejs.js +2 -2
  200. package/lib/node/nodejs.js.map +1 -1
  201. package/lib/sync/constants.d.ts +7 -1
  202. package/lib/sync/constants.d.ts.map +1 -1
  203. package/lib/sync/constants.js +7 -4
  204. package/lib/sync/constants.js.map +1 -1
  205. package/lib/sync/range/batch.d.ts +55 -8
  206. package/lib/sync/range/batch.d.ts.map +1 -1
  207. package/lib/sync/range/batch.js +291 -66
  208. package/lib/sync/range/batch.js.map +1 -1
  209. package/lib/sync/range/chain.d.ts +22 -4
  210. package/lib/sync/range/chain.d.ts.map +1 -1
  211. package/lib/sync/range/chain.js +103 -11
  212. package/lib/sync/range/chain.js.map +1 -1
  213. package/lib/sync/range/range.d.ts.map +1 -1
  214. package/lib/sync/range/range.js +71 -11
  215. package/lib/sync/range/range.js.map +1 -1
  216. package/lib/sync/range/utils/peerBalancer.d.ts +2 -1
  217. package/lib/sync/range/utils/peerBalancer.d.ts.map +1 -1
  218. package/lib/sync/range/utils/peerBalancer.js +8 -4
  219. package/lib/sync/range/utils/peerBalancer.js.map +1 -1
  220. package/lib/sync/sync.d.ts.map +1 -1
  221. package/lib/sync/sync.js +13 -0
  222. package/lib/sync/sync.js.map +1 -1
  223. package/lib/sync/types.d.ts +34 -0
  224. package/lib/sync/types.d.ts.map +1 -1
  225. package/lib/sync/types.js +34 -0
  226. package/lib/sync/types.js.map +1 -1
  227. package/lib/sync/unknownBlock.d.ts +29 -1
  228. package/lib/sync/unknownBlock.d.ts.map +1 -1
  229. package/lib/sync/unknownBlock.js +727 -61
  230. package/lib/sync/unknownBlock.js.map +1 -1
  231. package/lib/sync/utils/downloadByRange.d.ts +73 -10
  232. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  233. package/lib/sync/utils/downloadByRange.js +213 -26
  234. package/lib/sync/utils/downloadByRange.js.map +1 -1
  235. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  236. package/lib/sync/utils/downloadByRoot.js +16 -2
  237. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  238. package/lib/sync/utils/pendingBlocksTree.d.ts +0 -1
  239. package/lib/sync/utils/pendingBlocksTree.d.ts.map +1 -1
  240. package/lib/sync/utils/pendingBlocksTree.js +0 -9
  241. package/lib/sync/utils/pendingBlocksTree.js.map +1 -1
  242. package/lib/sync/utils/rateLimit.d.ts +2 -0
  243. package/lib/sync/utils/rateLimit.d.ts.map +1 -0
  244. package/lib/sync/utils/rateLimit.js +15 -0
  245. package/lib/sync/utils/rateLimit.js.map +1 -0
  246. package/lib/util/sszBytes.d.ts.map +1 -1
  247. package/lib/util/sszBytes.js +8 -6
  248. package/lib/util/sszBytes.js.map +1 -1
  249. package/package.json +16 -16
  250. package/src/api/impl/beacon/blocks/index.ts +21 -5
  251. package/src/api/impl/beacon/pool/index.ts +83 -1
  252. package/src/api/impl/debug/index.ts +0 -1
  253. package/src/api/impl/lodestar/index.ts +1 -1
  254. package/src/api/impl/validator/index.ts +82 -1
  255. package/src/chain/blocks/blockInput/blockInput.ts +4 -1
  256. package/src/chain/blocks/importBlock.ts +16 -48
  257. package/src/chain/blocks/importExecutionPayload.ts +76 -30
  258. package/src/chain/blocks/index.ts +71 -22
  259. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +37 -3
  260. package/src/chain/blocks/payloadEnvelopeInput/types.ts +18 -0
  261. package/src/chain/blocks/payloadEnvelopeProcessor.ts +2 -2
  262. package/src/chain/blocks/types.ts +4 -3
  263. package/src/chain/blocks/utils/chainSegment.ts +117 -17
  264. package/src/chain/blocks/verifyBlock.ts +70 -9
  265. package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +6 -4
  266. package/src/chain/blocks/verifyBlocksSanityChecks.ts +26 -7
  267. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +16 -8
  268. package/src/chain/blocks/verifyPayloadsDataAvailability.ts +7 -4
  269. package/src/chain/chain.ts +43 -10
  270. package/src/chain/emitter.ts +0 -11
  271. package/src/chain/errors/blockError.ts +4 -1
  272. package/src/chain/errors/index.ts +1 -0
  273. package/src/chain/errors/proposerPreferences.ts +47 -0
  274. package/src/chain/initState.ts +9 -1
  275. package/src/chain/interface.ts +7 -1
  276. package/src/chain/opPools/payloadAttestationPool.ts +29 -8
  277. package/src/chain/prepareNextSlot.ts +21 -29
  278. package/src/chain/produceBlock/produceBlockBody.ts +46 -22
  279. package/src/chain/regen/interface.ts +1 -0
  280. package/src/chain/regen/queued.ts +2 -7
  281. package/src/chain/regen/regen.ts +2 -7
  282. package/src/chain/seenCache/index.ts +1 -0
  283. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +90 -24
  284. package/src/chain/seenCache/seenProposerPreferences.ts +32 -0
  285. package/src/chain/validation/block.ts +1 -0
  286. package/src/chain/validation/executionPayloadBid.ts +11 -8
  287. package/src/chain/validation/proposerPreferences.ts +110 -0
  288. package/src/metrics/metrics/lodestar.ts +4 -0
  289. package/src/network/gossip/interface.ts +6 -0
  290. package/src/network/gossip/scoringParameters.ts +14 -1
  291. package/src/network/gossip/topic.ts +6 -0
  292. package/src/network/interface.ts +2 -0
  293. package/src/network/network.ts +23 -0
  294. package/src/network/processor/gossipHandlers.ts +53 -17
  295. package/src/network/processor/gossipQueues/index.ts +5 -0
  296. package/src/network/processor/index.ts +6 -5
  297. package/src/network/reqresp/ReqRespBeaconNode.ts +1 -0
  298. package/src/network/reqresp/handlers/beaconBlocksByHead.ts +91 -0
  299. package/src/network/reqresp/handlers/beaconBlocksByRange.ts +14 -6
  300. package/src/network/reqresp/handlers/blobSidecarsByRange.ts +11 -5
  301. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +17 -5
  302. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +7 -4
  303. package/src/network/reqresp/handlers/index.ts +5 -0
  304. package/src/network/reqresp/interface.ts +1 -1
  305. package/src/network/reqresp/protocols.ts +6 -0
  306. package/src/network/reqresp/rateLimit.ts +4 -0
  307. package/src/network/reqresp/score.ts +1 -0
  308. package/src/network/reqresp/types.ts +5 -0
  309. package/src/node/nodejs.ts +2 -2
  310. package/src/sync/constants.ts +8 -4
  311. package/src/sync/range/batch.ts +388 -75
  312. package/src/sync/range/chain.ts +131 -14
  313. package/src/sync/range/range.ts +86 -11
  314. package/src/sync/range/utils/peerBalancer.ts +9 -3
  315. package/src/sync/sync.ts +13 -1
  316. package/src/sync/types.ts +72 -0
  317. package/src/sync/unknownBlock.ts +915 -65
  318. package/src/sync/utils/downloadByRange.ts +386 -39
  319. package/src/sync/utils/downloadByRoot.ts +24 -2
  320. package/src/sync/utils/pendingBlocksTree.ts +0 -15
  321. package/src/sync/utils/rateLimit.ts +16 -0
  322. package/src/util/sszBytes.ts +8 -6
@@ -1,17 +1,19 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
- import {ForkName, isForkPostDeneb, isForkPostFulu} from "@lodestar/params";
3
- import {Epoch, RootHex, Slot, phase0} from "@lodestar/types";
4
- import {LodestarError} from "@lodestar/utils";
2
+ import {ForkName, isForkPostDeneb, isForkPostFulu, isForkPostGloas} from "@lodestar/params";
3
+ import {Epoch, RootHex, SignedBeaconBlock, Slot, gloas, phase0} from "@lodestar/types";
4
+ import {LodestarError, byteArrayEquals, prettyPrintIndices, toRootHex} from "@lodestar/utils";
5
5
  import {isBlockInputColumns} from "../../chain/blocks/blockInput/blockInput.js";
6
6
  import {IBlockInput} from "../../chain/blocks/blockInput/types.js";
7
7
  import {isDaOutOfRange} from "../../chain/blocks/blockInput/utils.js";
8
+ import {PayloadEnvelopeInput} from "../../chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js";
8
9
  import {BlockError, BlockErrorCode} from "../../chain/errors/index.js";
10
+ import {ZERO_HASH} from "../../constants/constants.js";
9
11
  import {PeerSyncMeta} from "../../network/peers/peersData.js";
10
12
  import {IClock} from "../../util/clock.js";
11
13
  import {CustodyConfig} from "../../util/dataColumns.js";
12
14
  import {PeerIdStr} from "../../util/peerId.js";
13
15
  import {MAX_BATCH_DOWNLOAD_ATTEMPTS, MAX_BATCH_PROCESSING_ATTEMPTS} from "../constants.js";
14
- import {DownloadByRangeRequests} from "../utils/downloadByRange.js";
16
+ import {DownloadByRangeRequests, ParentPayloadCommitments} from "../utils/downloadByRange.js";
15
17
  import {getBatchSlotRange, hashBlocks} from "./utils/index.js";
16
18
 
17
19
  /**
@@ -43,28 +45,88 @@ export type Attempt = {
43
45
  hash: RootHex;
44
46
  };
45
47
 
48
+ type TrackedRequest = {
49
+ /** only happen for the 1st batch in checkpoint sync */
50
+ parentPayload: boolean;
51
+ /**
52
+ * we always issue by_range before parent_payload, so we don't model this as null
53
+ */
54
+ byRangeColumns: Set<number>;
55
+ };
56
+
46
57
  export type AwaitingDownloadState = {
47
58
  status: BatchStatus.AwaitingDownload;
48
59
  blocks: IBlockInput[];
60
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null;
49
61
  };
50
62
 
51
63
  export type DownloadSuccessState = {
52
64
  status: BatchStatus.AwaitingProcessing;
53
65
  blocks: IBlockInput[];
66
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null;
54
67
  };
55
68
 
56
69
  export type BatchState =
57
70
  | AwaitingDownloadState
58
- | {status: BatchStatus.Downloading; peer: PeerIdStr; blocks: IBlockInput[]}
71
+ | {
72
+ status: BatchStatus.Downloading;
73
+ peer: PeerIdStr;
74
+ request: TrackedRequest;
75
+ blocks: IBlockInput[];
76
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null;
77
+ }
59
78
  | DownloadSuccessState
60
- | {status: BatchStatus.Processing; blocks: IBlockInput[]; attempt: Attempt}
61
- | {status: BatchStatus.AwaitingValidation; blocks: IBlockInput[]; attempt: Attempt};
79
+ | {
80
+ status: BatchStatus.Processing;
81
+ blocks: IBlockInput[];
82
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null;
83
+ attempt: Attempt;
84
+ }
85
+ | {
86
+ status: BatchStatus.AwaitingValidation;
87
+ blocks: IBlockInput[];
88
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null;
89
+ attempt: Attempt;
90
+ };
62
91
 
63
92
  export type BatchMetadata = {
93
+ // Batch-level slot window (always present)
64
94
  startEpoch: Epoch;
95
+ startSlot: Slot;
96
+ count: number;
65
97
  status: BatchStatus;
98
+
99
+ // Per-type outstanding request shapes; only present when that sub-request exists.
100
+ // Format: "startSlot=<n>,count=<n>" (plus ",cols=<indices>" for columns).
101
+ blocksReq?: string;
102
+ blobsReq?: string;
103
+ columnsReq?: string;
104
+ envelopesReq?: string;
105
+
106
+ // Retry counters
107
+ downloadAttempts: number;
108
+ processingAttempts: number;
109
+
110
+ // Cumulative peer attribution for failed attempts (only present when non-empty)
111
+ failedDownloadPeers?: string;
112
+ failedProcessingPeers?: string;
66
113
  };
67
114
 
115
+ function formatRangeReq(req: {startSlot: Slot; count: number}): string {
116
+ return `startSlot=${req.startSlot},count=${req.count}`;
117
+ }
118
+
119
+ function formatColumnsReq(req: {startSlot: Slot; count: number; columns: number[]}): string {
120
+ return `startSlot=${req.startSlot},count=${req.count},cols=${prettyPrintIndices(req.columns)}`;
121
+ }
122
+
123
+ function getTrackedRequest({parentPayloadRequest, columnsRequest}: DownloadByRangeRequests): TrackedRequest {
124
+ return {
125
+ parentPayload: parentPayloadRequest != null,
126
+ byRangeColumns: new Set(parentPayloadRequest == null ? (columnsRequest?.columns ?? []) : []),
127
+ };
128
+ }
129
+
68
130
  /**
69
131
  * Batches are downloaded at the first block of the epoch.
70
132
  *
@@ -85,9 +147,9 @@ export class Batch {
85
147
  /** Block, blob and column requests that are used to determine the best peer and are used in downloadByRange */
86
148
  requests: DownloadByRangeRequests;
87
149
  /** State of the batch. */
88
- state: BatchState = {status: BatchStatus.AwaitingDownload, blocks: []};
89
- /** Peers that provided good data */
90
- goodPeers: PeerIdStr[] = [];
150
+ state: BatchState = {status: BatchStatus.AwaitingDownload, blocks: [], payloadEnvelopes: null};
151
+ /** Peers that provided good data, with column coverage for by_range requests */
152
+ private readonly successfulDownloads = new Map<PeerIdStr, TrackedRequest>();
91
153
  /** The `Attempts` that have been made and failed to send us this batch. */
92
154
  readonly failedProcessingAttempts: Attempt[] = [];
93
155
  /** The `Attempts` that have been made and failed because of execution malfunction. */
@@ -97,8 +159,18 @@ export class Batch {
97
159
  private readonly config: ChainForkConfig;
98
160
  private readonly clock: IClock;
99
161
  private readonly custodyConfig: CustodyConfig;
100
-
101
- constructor(startEpoch: Epoch, config: ChainForkConfig, clock: IClock, custodyConfig: CustodyConfig) {
162
+ private readonly isFirstBatchInChain: boolean;
163
+ private readonly latestBid: gloas.ExecutionPayloadBid | undefined;
164
+
165
+ constructor(
166
+ startEpoch: Epoch,
167
+ config: ChainForkConfig,
168
+ clock: IClock,
169
+ custodyConfig: CustodyConfig,
170
+ isFirstBatchInChain: boolean,
171
+ latestBid: gloas.ExecutionPayloadBid | undefined,
172
+ targetSlot: Slot
173
+ ) {
102
174
  this.config = config;
103
175
  this.clock = clock;
104
176
  this.custodyConfig = custodyConfig;
@@ -107,10 +179,40 @@ export class Batch {
107
179
  this.forkName = this.config.getForkName(startSlot);
108
180
  this.startEpoch = startEpoch;
109
181
  this.startSlot = startSlot;
110
- this.count = count;
182
+ this.count = Math.min(count, targetSlot - startSlot + 1);
183
+ this.isFirstBatchInChain = isFirstBatchInChain;
184
+ this.latestBid = latestBid;
111
185
  this.requests = this.getRequests([]);
112
186
  }
113
187
 
188
+ private shouldDownloadParentEnvelope(firstBlock?: SignedBeaconBlock): boolean {
189
+ if (!this.isFirstBatchInChain) return false;
190
+
191
+ if (this.startSlot === 0 || !isForkPostGloas(this.config.getForkName(this.startSlot - 1))) {
192
+ return false;
193
+ }
194
+
195
+ // we only know if we should download parent envelope if firstBlock is downloaded
196
+ if (firstBlock === undefined) return false;
197
+ if (this.latestBid === undefined) return false;
198
+ const firstBlockBidParentHash = (firstBlock.message.body as gloas.BeaconBlockBody).signedExecutionPayloadBid.message
199
+ .parentBlockHash;
200
+ return byteArrayEquals(firstBlockBidParentHash, this.latestBid.blockHash);
201
+ }
202
+
203
+ getParentPayloadCommitments(parentBlockRoot: Uint8Array): ParentPayloadCommitments {
204
+ if (this.latestBid === undefined) {
205
+ throw new Error(
206
+ `Coding error: getParentPayloadCommitments called without latestBid for parentBlockRoot=${toRootHex(parentBlockRoot)}`
207
+ );
208
+ }
209
+ return {
210
+ blockRoot: parentBlockRoot,
211
+ blockRootHex: toRootHex(parentBlockRoot),
212
+ kzgCommitments: this.latestBid.blobKzgCommitments,
213
+ };
214
+ }
215
+
114
216
  /**
115
217
  * Builds ByRange requests for block, blobs and columns
116
218
  */
@@ -129,37 +231,36 @@ export class Batch {
129
231
  count: this.count,
130
232
  step: 1,
131
233
  };
132
- if (isForkPostFulu(this.forkName) && withinValidRequestWindow) {
133
- return {
134
- blocksRequest,
135
- columnsRequest: {
136
- startSlot: this.startSlot,
137
- count: this.count,
138
- columns: this.custodyConfig.sampledColumns,
139
- },
140
- };
234
+ const requests: DownloadByRangeRequests = {blocksRequest};
235
+
236
+ // Post-Gloas envelopes are required for block processing, independent of DA retention window.
237
+ if (isForkPostGloas(this.forkName)) {
238
+ requests.envelopesRequest = {startSlot: this.startSlot, count: this.count};
141
239
  }
142
- if (isForkPostDeneb(this.forkName) && withinValidRequestWindow) {
143
- return {
144
- blocksRequest,
145
- blobsRequest: {
146
- startSlot: this.startSlot,
147
- count: this.count,
148
- },
240
+
241
+ if (isForkPostFulu(this.forkName) && withinValidRequestWindow) {
242
+ requests.columnsRequest = {
243
+ startSlot: this.startSlot,
244
+ count: this.count,
245
+ columns: this.custodyConfig.sampledColumns,
149
246
  };
247
+ } else if (isForkPostDeneb(this.forkName) && withinValidRequestWindow) {
248
+ requests.blobsRequest = {startSlot: this.startSlot, count: this.count};
150
249
  }
151
- return {
152
- blocksRequest,
153
- };
250
+
251
+ return requests;
154
252
  }
155
253
 
156
254
  // subsequent request where part of the epoch has already been downloaded. Need to figure out what is the beginning
157
255
  // of the range where download needs to resume
158
256
  let blockStartSlot = this.startSlot;
159
257
  let dataStartSlot = this.startSlot;
258
+ let envelopeStartSlot = this.startSlot;
160
259
  const neededColumns = new Set<number>();
260
+ const envelopesBySlot = this.state.payloadEnvelopes ?? new Map<Slot, PayloadEnvelopeInput>();
161
261
 
162
262
  // ensure blocks are in slot-wise order
263
+ const isPostGloas = isForkPostGloas(this.forkName);
163
264
  for (const blockInput of blocks) {
164
265
  const blockSlot = blockInput.slot;
165
266
  // check if block/data is present (hasBlock/hasAllData). If present then check if startSlot is the same as
@@ -175,14 +276,36 @@ export class Batch {
175
276
  if (blockInput.hasBlock() && blockStartSlot === blockSlot) {
176
277
  blockStartSlot = blockSlot + 1;
177
278
  }
178
- if (!blockInput.hasAllData()) {
179
- if (isBlockInputColumns(blockInput)) {
180
- for (const index of blockInput.getMissingSampledColumnMeta().missing) {
279
+
280
+ // Range sync uses hasComputedAllData (all sampled columns physically present), not hasAllData
281
+ // which flips at the reconstruction threshold. Sync never triggers reconstruction, so accepting
282
+ // a half-downloaded block here makes writeBlockInputToDb later block on waitForComputedAllData.
283
+ if (isPostGloas) {
284
+ // Post-Gloas: column data lives on PayloadEnvelopeInput, not on BlockInputNoData.
285
+ const payloadInput = envelopesBySlot.get(blockSlot);
286
+ if (blockInput.hasBlock() && envelopeStartSlot === blockSlot && payloadInput?.hasPayloadEnvelope()) {
287
+ envelopeStartSlot = blockSlot + 1;
288
+ }
289
+ if (payloadInput && !payloadInput.hasComputedAllData()) {
290
+ for (const index of payloadInput.getMissingSampledColumnMeta().missing) {
181
291
  neededColumns.add(index);
182
292
  }
293
+ } else if (payloadInput?.hasComputedAllData() && dataStartSlot === blockSlot) {
294
+ // Only advance dataStartSlot when we know columns for this slot are complete. If
295
+ // payloadInput is missing entirely we cannot tell, so stop here so the next round
296
+ // re-requests columns (and envelopes) starting at this slot.
297
+ dataStartSlot = blockSlot + 1;
298
+ }
299
+ } else {
300
+ if (isBlockInputColumns(blockInput) ? !blockInput.hasComputedAllData() : !blockInput.hasAllData()) {
301
+ if (isBlockInputColumns(blockInput)) {
302
+ for (const index of blockInput.getMissingSampledColumnMeta().missing) {
303
+ neededColumns.add(index);
304
+ }
305
+ }
306
+ } else if (dataStartSlot === blockSlot) {
307
+ dataStartSlot = blockSlot + 1;
183
308
  }
184
- } else if (dataStartSlot === blockSlot) {
185
- dataStartSlot = blockSlot + 1;
186
309
  }
187
310
  }
188
311
 
@@ -202,11 +325,15 @@ export class Batch {
202
325
  // range of 40 - 63, startSlot will be inclusive but subtraction will exclusive so need to + 1
203
326
  const count = endSlot - dataStartSlot + 1;
204
327
  if (isForkPostFulu(this.forkName) && withinValidRequestWindow) {
205
- requests.columnsRequest = {
206
- count,
207
- startSlot: dataStartSlot,
208
- columns: Array.from(neededColumns),
209
- };
328
+ // Skip the column re-request when we have no specific column indices outstanding.
329
+ // Peer rejects an empty `columns` list
330
+ if (neededColumns.size > 0) {
331
+ requests.columnsRequest = {
332
+ count,
333
+ startSlot: dataStartSlot,
334
+ columns: Array.from(neededColumns),
335
+ };
336
+ }
210
337
  } else if (isForkPostDeneb(this.forkName) && withinValidRequestWindow) {
211
338
  requests.blobsRequest = {
212
339
  count,
@@ -216,6 +343,43 @@ export class Batch {
216
343
  // dataSlot will still have a value but do not create a request for preDeneb forks
217
344
  }
218
345
 
346
+ if (isForkPostGloas(this.forkName) && envelopeStartSlot <= endSlot) {
347
+ requests.envelopesRequest = {
348
+ startSlot: envelopeStartSlot,
349
+ count: endSlot - envelopeStartSlot + 1,
350
+ };
351
+ }
352
+
353
+ // Only the first batch of a SyncChain may need the dangling-parent payload by-root.
354
+ if (blocks.length > 0 && this.shouldDownloadParentEnvelope(blocks[0].getBlock())) {
355
+ // shouldDownloadParentEnvelope() = true means there are at least 1 block
356
+ const parentRoot = blocks[0].getBlock().message.parentRoot;
357
+ if (!byteArrayEquals(parentRoot, ZERO_HASH)) {
358
+ const parentRootHex = toRootHex(parentRoot);
359
+ let parentPayloadInput: PayloadEnvelopeInput | undefined;
360
+ if (this.state.payloadEnvelopes) {
361
+ for (const pi of this.state.payloadEnvelopes.values()) {
362
+ if (pi.blockRootHex === parentRootHex) {
363
+ parentPayloadInput = pi;
364
+ break;
365
+ }
366
+ }
367
+ }
368
+
369
+ const needsEnvelope = !parentPayloadInput?.hasPayloadEnvelope();
370
+ const missingColumns = parentPayloadInput
371
+ ? parentPayloadInput.getMissingSampledColumnMeta().missing
372
+ : this.custodyConfig.sampledColumns;
373
+
374
+ if (needsEnvelope || missingColumns.length > 0) {
375
+ requests.parentPayloadRequest = {
376
+ ...(needsEnvelope ? {envelopeBlockRoot: parentRoot} : {}),
377
+ ...(missingColumns.length > 0 ? {blockRoot: parentRoot, columns: missingColumns} : {}),
378
+ };
379
+ }
380
+ }
381
+ }
382
+
219
383
  return requests;
220
384
  }
221
385
 
@@ -227,24 +391,28 @@ export class Batch {
227
391
  return this.requests;
228
392
  }
229
393
 
230
- // post-fulu we need to ensure that we only request columns that the peer has advertised
231
- const {columnsRequest} = this.requests;
232
- if (columnsRequest == null) {
233
- return this.requests;
234
- }
394
+ // post-fulu we need to ensure that we only request columns that the peer has advertised.
395
+ const {columnsRequest, parentPayloadRequest} = this.requests;
235
396
 
236
397
  const peerColumns = new Set(peer.custodyColumns ?? []);
237
- const requestedColumns = columnsRequest.columns.filter((c) => peerColumns.has(c));
238
- if (requestedColumns.length === columnsRequest.columns.length) {
239
- return this.requests;
240
- }
398
+ const filteredColumnsRequest =
399
+ columnsRequest != null ? columnsRequest.columns.filter((c) => peerColumns.has(c)) : null;
400
+ const parentColumns = parentPayloadRequest?.columns;
401
+ const filteredParentColumns = parentColumns != null ? parentColumns.filter((c) => peerColumns.has(c)) : null;
402
+
403
+ const updatedColumnRequest =
404
+ columnsRequest != null && filteredColumnsRequest != null
405
+ ? {columnsRequest: {...columnsRequest, columns: filteredColumnsRequest}}
406
+ : {};
407
+ const updatedParentPayloadRequest =
408
+ parentPayloadRequest != null && filteredParentColumns != null
409
+ ? {parentPayloadRequest: {...parentPayloadRequest, columns: filteredParentColumns}}
410
+ : {};
241
411
 
242
412
  return {
243
413
  ...this.requests,
244
- columnsRequest: {
245
- ...columnsRequest,
246
- columns: requestedColumns,
247
- },
414
+ ...updatedColumnRequest,
415
+ ...updatedParentPayloadRequest,
248
416
  };
249
417
  }
250
418
 
@@ -255,29 +423,93 @@ export class Batch {
255
423
  return [...this.failedDownloadAttempts, ...this.failedProcessingAttempts.flatMap((a) => a.peers)];
256
424
  }
257
425
 
426
+ /**
427
+ * True only if the peer has already returned a successful response for the current request.
428
+ * A by_range success may update `this.requests` to parent_payload, and the same peer is then
429
+ * still eligible for the newly discovered parent payload data.
430
+ * For by_range, a peer that previously succeeded with a superset of requested columns is skipped.
431
+ */
432
+ hasPeerSucceededCurrentRequest(peer: PeerSyncMeta): boolean {
433
+ const successfulDownload = this.successfulDownloads.get(peer.peerId);
434
+ if (successfulDownload == null) return false;
435
+
436
+ const request = getTrackedRequest(this.getRequestsForPeer(peer));
437
+ if (request.parentPayload) return successfulDownload.parentPayload;
438
+
439
+ const requestByRangeColumns = request.byRangeColumns;
440
+
441
+ if (requestByRangeColumns.size === 0) {
442
+ // this means a download blocks/envelops by_range only
443
+ // don't do that again if we already did it
444
+ // see https://github.com/ChainSafe/lodestar/issues/9357
445
+ return true;
446
+ }
447
+
448
+ return [...requestByRangeColumns].every((column) => successfulDownload.byRangeColumns.has(column));
449
+ }
450
+
451
+ private getSuccessfulPeers(): PeerIdStr[] {
452
+ return Array.from(this.successfulDownloads.keys());
453
+ }
454
+
258
455
  getMetadata(): BatchMetadata {
259
- return {startEpoch: this.startEpoch, status: this.state.status};
456
+ const {blocksRequest, blobsRequest, columnsRequest, envelopesRequest} = this.requests;
457
+ const failedProcessingPeerList = this.failedProcessingAttempts.flatMap((a) => a.peers);
458
+ return {
459
+ startEpoch: this.startEpoch,
460
+ startSlot: this.startSlot,
461
+ count: this.count,
462
+ status: this.state.status,
463
+ ...(blocksRequest && {blocksReq: formatRangeReq(blocksRequest)}),
464
+ ...(blobsRequest && {blobsReq: formatRangeReq(blobsRequest)}),
465
+ ...(columnsRequest && {columnsReq: formatColumnsReq(columnsRequest)}),
466
+ ...(envelopesRequest && {envelopesReq: formatRangeReq(envelopesRequest)}),
467
+ downloadAttempts: this.failedDownloadAttempts.length,
468
+ processingAttempts: this.failedProcessingAttempts.length,
469
+ ...(this.failedDownloadAttempts.length > 0 && {
470
+ failedDownloadPeers: this.failedDownloadAttempts.join(","),
471
+ }),
472
+ ...(failedProcessingPeerList.length > 0 && {
473
+ failedProcessingPeers: failedProcessingPeerList.join(","),
474
+ }),
475
+ };
260
476
  }
261
477
 
262
478
  getBlocks(): IBlockInput[] {
263
479
  return this.state.blocks;
264
480
  }
265
481
 
482
+ getPayloadEnvelopes(): Map<Slot, PayloadEnvelopeInput> | null {
483
+ return this.state.payloadEnvelopes;
484
+ }
485
+
266
486
  /**
267
487
  * AwaitingDownload -> Downloading
268
488
  */
269
- startDownloading(peer: PeerIdStr): void {
489
+ startDownloading(peer: PeerSyncMeta): void {
270
490
  if (this.state.status !== BatchStatus.AwaitingDownload) {
271
491
  throw new BatchError(this.wrongStatusErrorType(BatchStatus.AwaitingDownload));
272
492
  }
273
493
 
274
- this.state = {status: BatchStatus.Downloading, peer, blocks: this.state.blocks};
494
+ const request = getTrackedRequest(this.getRequestsForPeer(peer));
495
+
496
+ this.state = {
497
+ status: BatchStatus.Downloading,
498
+ peer: peer.peerId,
499
+ request,
500
+ blocks: this.state.blocks,
501
+ payloadEnvelopes: this.state.payloadEnvelopes,
502
+ };
275
503
  }
276
504
 
277
505
  /**
278
506
  * Downloading -> AwaitingProcessing
279
507
  */
280
- downloadingSuccess(peer: PeerIdStr, blocks: IBlockInput[]): DownloadSuccessState {
508
+ downloadingSuccess(
509
+ peer: PeerIdStr,
510
+ blocks: IBlockInput[],
511
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null
512
+ ): DownloadSuccessState {
281
513
  if (this.state.status !== BatchStatus.Downloading) {
282
514
  throw new BatchError(this.wrongStatusErrorType(BatchStatus.Downloading));
283
515
  }
@@ -285,13 +517,27 @@ export class Batch {
285
517
  // ensure that blocks are always sorted before getting stored on the batch.state or being used to getRequests
286
518
  blocks.sort((a, b) => a.slot - b.slot);
287
519
 
288
- this.goodPeers.push(peer);
520
+ const successfulDownload = this.successfulDownloads.get(peer) ?? {
521
+ parentPayload: false,
522
+ byRangeColumns: new Set<number>(),
523
+ };
524
+ successfulDownload.parentPayload ||= this.state.request.parentPayload;
525
+ if (!this.state.request.parentPayload) {
526
+ for (const column of this.state.request.byRangeColumns) {
527
+ successfulDownload.byRangeColumns.add(column);
528
+ }
529
+ }
530
+ this.successfulDownloads.set(peer, successfulDownload);
289
531
 
290
532
  let allComplete = true;
291
533
  const slots = new Set<number>();
292
534
  for (const block of blocks) {
293
535
  slots.add(block.slot);
294
- if (!block.hasBlockAndAllData()) {
536
+ const dataComplete = isBlockInputColumns(block)
537
+ ? // by_range needs to download all columns
538
+ block.hasBlock() && block.hasComputedAllData()
539
+ : block.hasBlockAndAllData();
540
+ if (!dataComplete) {
295
541
  allComplete = false;
296
542
  }
297
543
  }
@@ -305,11 +551,48 @@ export class Batch {
305
551
  status: this.state.status,
306
552
  });
307
553
  }
554
+ const newPayloadEnvelopes = payloadEnvelopes ?? this.state.payloadEnvelopes;
555
+
556
+ if (allComplete && isForkPostGloas(this.forkName)) {
557
+ for (const block of blocks) {
558
+ const payloadInput = newPayloadEnvelopes?.get(block.slot);
559
+ // only need to make sure envelope has all columns, not all blocks have payload
560
+ // assertLinearChainSegment() was called before reaching this
561
+ if (payloadInput?.hasPayloadEnvelope() && !payloadInput.hasComputedAllData()) {
562
+ allComplete = false;
563
+ break;
564
+ }
565
+ }
566
+ }
567
+
568
+ // First batch of a sync chain must additionally have the dangling-parent payload fully
569
+ // present, otherwise `processBlocks` will throw PARENT_PAYLOAD_UNKNOWN. The parent's
570
+ // `PayloadEnvelopeInput` is identified by `blockRootHex` matching `blocks[0].parentRoot`.
571
+ if (allComplete && blocks.length > 0 && this.shouldDownloadParentEnvelope(blocks[0].getBlock())) {
572
+ const parentRoot = blocks[0].getBlock().message.parentRoot;
573
+ // Genesis has no parent payload — nothing to wait for.
574
+ if (!byteArrayEquals(parentRoot, ZERO_HASH)) {
575
+ const parentRootHex = toRootHex(parentRoot);
576
+ let parentPayloadComplete = false;
577
+ if (newPayloadEnvelopes) {
578
+ for (const payloadInput of newPayloadEnvelopes.values()) {
579
+ if (payloadInput.blockRootHex === parentRootHex) {
580
+ parentPayloadComplete = payloadInput.hasPayloadEnvelope() && payloadInput.hasComputedAllData();
581
+ break;
582
+ }
583
+ }
584
+ }
585
+ if (!parentPayloadComplete) {
586
+ allComplete = false;
587
+ }
588
+ }
589
+ }
590
+
308
591
  if (allComplete) {
309
- this.state = {status: BatchStatus.AwaitingProcessing, blocks};
592
+ this.state = {status: BatchStatus.AwaitingProcessing, blocks, payloadEnvelopes: newPayloadEnvelopes};
310
593
  } else {
594
+ this.state = {status: BatchStatus.AwaitingDownload, blocks, payloadEnvelopes: newPayloadEnvelopes};
311
595
  this.requests = this.getRequests(blocks);
312
- this.state = {status: BatchStatus.AwaitingDownload, blocks};
313
596
  }
314
597
 
315
598
  return this.state as DownloadSuccessState;
@@ -328,25 +611,50 @@ export class Batch {
328
611
  throw new BatchError(this.errorType({code: BatchErrorCode.MAX_DOWNLOAD_ATTEMPTS}));
329
612
  }
330
613
 
331
- this.state = {status: BatchStatus.AwaitingDownload, blocks: this.state.blocks};
614
+ this.state = {
615
+ status: BatchStatus.AwaitingDownload,
616
+ blocks: this.state.blocks,
617
+ payloadEnvelopes: this.state.payloadEnvelopes,
618
+ };
619
+ }
620
+
621
+ /**
622
+ * Downloading -> AwaitingDownload (without counting as a failed attempt).
623
+ * Used when the peer rate-limited us — the request was never actually served.
624
+ */
625
+ downloadingRateLimited(): void {
626
+ if (this.state.status !== BatchStatus.Downloading) {
627
+ throw new BatchError(this.wrongStatusErrorType(BatchStatus.Downloading));
628
+ }
629
+
630
+ this.state = {
631
+ status: BatchStatus.AwaitingDownload,
632
+ blocks: this.state.blocks,
633
+ payloadEnvelopes: this.state.payloadEnvelopes,
634
+ };
332
635
  }
333
636
 
334
637
  /**
335
638
  * AwaitingProcessing -> Processing
336
639
  */
337
- startProcessing(): IBlockInput[] {
640
+ startProcessing(): {
641
+ blocks: IBlockInput[];
642
+ payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null;
643
+ peers: PeerIdStr[];
644
+ } {
338
645
  if (this.state.status !== BatchStatus.AwaitingProcessing) {
339
646
  throw new BatchError(this.wrongStatusErrorType(BatchStatus.AwaitingProcessing));
340
647
  }
341
648
 
342
649
  const blocks = this.state.blocks;
650
+ const payloadEnvelopes = this.state.payloadEnvelopes;
343
651
  const hash = hashBlocks(blocks, this.config); // tracks blocks to report peer on processing error
344
- // Reset goodPeers in case another download attempt needs to be made. When Attempt is successful or not the peers
345
- // that the data came from will be handled by the Attempt that goes for processing
346
- const peers = this.goodPeers;
347
- this.goodPeers = [];
348
- this.state = {status: BatchStatus.Processing, blocks, attempt: {peers, hash}};
349
- return blocks;
652
+ // Reset successfulDownloads in case another download attempt needs to be made. When Attempt is successful or not
653
+ // the peers that the data came from will be handled by the Attempt that goes for processing.
654
+ const peers = this.getSuccessfulPeers();
655
+ this.successfulDownloads.clear();
656
+ this.state = {status: BatchStatus.Processing, blocks, payloadEnvelopes, attempt: {peers, hash}};
657
+ return {blocks, payloadEnvelopes, peers};
350
658
  }
351
659
 
352
660
  /**
@@ -357,7 +665,12 @@ export class Batch {
357
665
  throw new BatchError(this.wrongStatusErrorType(BatchStatus.Processing));
358
666
  }
359
667
 
360
- this.state = {status: BatchStatus.AwaitingValidation, blocks: this.state.blocks, attempt: this.state.attempt};
668
+ this.state = {
669
+ status: BatchStatus.AwaitingValidation,
670
+ blocks: this.state.blocks,
671
+ payloadEnvelopes: this.state.payloadEnvelopes,
672
+ attempt: this.state.attempt,
673
+ };
361
674
  }
362
675
 
363
676
  /**
@@ -408,7 +721,7 @@ export class Batch {
408
721
 
409
722
  // remove any downloaded blocks and re-attempt
410
723
  // TODO(fulu): need to remove the bad blocks from the SeenBlockInputCache
411
- this.state = {status: BatchStatus.AwaitingDownload, blocks: []};
724
+ this.state = {status: BatchStatus.AwaitingDownload, blocks: [], payloadEnvelopes: null};
412
725
  }
413
726
 
414
727
  private onProcessingError(attempt: Attempt): void {
@@ -419,12 +732,12 @@ export class Batch {
419
732
 
420
733
  // remove any downloaded blocks and re-attempt
421
734
  // TODO(fulu): need to remove the bad blocks from the SeenBlockInputCache
422
- this.state = {status: BatchStatus.AwaitingDownload, blocks: []};
735
+ this.state = {status: BatchStatus.AwaitingDownload, blocks: [], payloadEnvelopes: null};
423
736
  }
424
737
 
425
738
  /** Helper to construct typed BatchError. Stack traces are correct as the error is thrown above */
426
739
  private errorType(type: BatchErrorType): BatchErrorType & BatchErrorMetadata {
427
- return {...type, ...this.getMetadata()};
740
+ return {...type, startEpoch: this.startEpoch, status: this.state.status};
428
741
  }
429
742
 
430
743
  private wrongStatusErrorType(expectedStatus: BatchStatus): BatchErrorType & BatchErrorMetadata {