@lodestar/beacon-node 1.50.0-dev.fef46df5ba → 1.50.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (349) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +6 -1
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/index.js +13 -0
  6. package/lib/api/impl/beacon/index.js.map +1 -1
  7. package/lib/api/impl/beacon/pool/index.js +4 -4
  8. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  9. package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
  10. package/lib/api/impl/beacon/state/index.js +28 -6
  11. package/lib/api/impl/beacon/state/index.js.map +1 -1
  12. package/lib/api/impl/debug/index.d.ts.map +1 -1
  13. package/lib/api/impl/debug/index.js +26 -18
  14. package/lib/api/impl/debug/index.js.map +1 -1
  15. package/lib/api/impl/validator/index.d.ts +1 -0
  16. package/lib/api/impl/validator/index.d.ts.map +1 -1
  17. package/lib/api/impl/validator/index.js +49 -1
  18. package/lib/api/impl/validator/index.js.map +1 -1
  19. package/lib/chain/archiveStore/archiveStore.d.ts +5 -0
  20. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  21. package/lib/chain/archiveStore/archiveStore.js +14 -24
  22. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  23. package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts +2 -2
  24. package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts.map +1 -1
  25. package/lib/chain/archiveStore/historicalState/getHistoricalState.js +5 -7
  26. package/lib/chain/archiveStore/historicalState/getHistoricalState.js.map +1 -1
  27. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js +1 -1
  28. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js.map +1 -1
  29. package/lib/chain/archiveStore/historicalState/types.d.ts +2 -2
  30. package/lib/chain/archiveStore/historicalState/types.d.ts.map +1 -1
  31. package/lib/chain/archiveStore/historicalState/worker.js +1 -1
  32. package/lib/chain/archiveStore/historicalState/worker.js.map +1 -1
  33. package/lib/chain/archiveStore/interface.d.ts +1 -1
  34. package/lib/chain/archiveStore/interface.d.ts.map +1 -1
  35. package/lib/chain/archiveStore/utils/pruneHistory.d.ts +1 -1
  36. package/lib/chain/archiveStore/utils/pruneHistory.d.ts.map +1 -1
  37. package/lib/chain/archiveStore/utils/pruneHistory.js +2 -1
  38. package/lib/chain/archiveStore/utils/pruneHistory.js.map +1 -1
  39. package/lib/chain/beaconProposerCache.d.ts +6 -0
  40. package/lib/chain/beaconProposerCache.d.ts.map +1 -1
  41. package/lib/chain/beaconProposerCache.js +16 -0
  42. package/lib/chain/beaconProposerCache.js.map +1 -1
  43. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  44. package/lib/chain/blocks/importBlock.js +18 -8
  45. package/lib/chain/blocks/importBlock.js.map +1 -1
  46. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +2 -1
  47. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  48. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  49. package/lib/chain/blocks/verifyBlock.js +7 -7
  50. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  51. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts +2 -1
  52. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
  53. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +5 -4
  54. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
  55. package/lib/chain/chain.d.ts +10 -5
  56. package/lib/chain/chain.d.ts.map +1 -1
  57. package/lib/chain/chain.js +105 -36
  58. package/lib/chain/chain.js.map +1 -1
  59. package/lib/chain/emitter.d.ts +7 -1
  60. package/lib/chain/emitter.d.ts.map +1 -1
  61. package/lib/chain/emitter.js +5 -0
  62. package/lib/chain/emitter.js.map +1 -1
  63. package/lib/chain/errors/envelopeReconstructionError.d.ts +5 -0
  64. package/lib/chain/errors/envelopeReconstructionError.d.ts.map +1 -1
  65. package/lib/chain/errors/envelopeReconstructionError.js +2 -0
  66. package/lib/chain/errors/envelopeReconstructionError.js.map +1 -1
  67. package/lib/chain/errors/gossipValidation.d.ts +3 -1
  68. package/lib/chain/errors/gossipValidation.d.ts.map +1 -1
  69. package/lib/chain/errors/gossipValidation.js +2 -0
  70. package/lib/chain/errors/gossipValidation.js.map +1 -1
  71. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  72. package/lib/chain/forkChoice/index.js +2 -0
  73. package/lib/chain/forkChoice/index.js.map +1 -1
  74. package/lib/chain/interface.d.ts +5 -2
  75. package/lib/chain/interface.d.ts.map +1 -1
  76. package/lib/chain/opPools/aggregatedAttestationPool.d.ts +4 -2
  77. package/lib/chain/opPools/aggregatedAttestationPool.d.ts.map +1 -1
  78. package/lib/chain/opPools/aggregatedAttestationPool.js +30 -8
  79. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  80. package/lib/chain/opPools/opPool.d.ts +5 -4
  81. package/lib/chain/opPools/opPool.d.ts.map +1 -1
  82. package/lib/chain/opPools/opPool.js +60 -38
  83. package/lib/chain/opPools/opPool.js.map +1 -1
  84. package/lib/chain/opPools/proposerPreferencesPool.d.ts +8 -4
  85. package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
  86. package/lib/chain/opPools/proposerPreferencesPool.js +27 -12
  87. package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
  88. package/lib/chain/opPools/utils.d.ts +15 -1
  89. package/lib/chain/opPools/utils.d.ts.map +1 -1
  90. package/lib/chain/opPools/utils.js +33 -1
  91. package/lib/chain/opPools/utils.js.map +1 -1
  92. package/lib/chain/options.d.ts +2 -1
  93. package/lib/chain/options.d.ts.map +1 -1
  94. package/lib/chain/options.js +1 -1
  95. package/lib/chain/options.js.map +1 -1
  96. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  97. package/lib/chain/prepareNextSlot.js +16 -5
  98. package/lib/chain/prepareNextSlot.js.map +1 -1
  99. package/lib/chain/produceBlock/computeNewStateRoot.js +1 -1
  100. package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
  101. package/lib/chain/produceBlock/produceBlockBody.d.ts +11 -1
  102. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  103. package/lib/chain/produceBlock/produceBlockBody.js +46 -30
  104. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  105. package/lib/chain/regen/regen.d.ts +2 -0
  106. package/lib/chain/regen/regen.d.ts.map +1 -1
  107. package/lib/chain/regen/regen.js +10 -4
  108. package/lib/chain/regen/regen.js.map +1 -1
  109. package/lib/chain/seenCache/seenGossipBlockInput.d.ts +7 -0
  110. package/lib/chain/seenCache/seenGossipBlockInput.d.ts.map +1 -1
  111. package/lib/chain/seenCache/seenGossipBlockInput.js +21 -0
  112. package/lib/chain/seenCache/seenGossipBlockInput.js.map +1 -1
  113. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +9 -2
  114. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  115. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +23 -13
  116. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  117. package/lib/chain/shufflingCache.d.ts +18 -8
  118. package/lib/chain/shufflingCache.d.ts.map +1 -1
  119. package/lib/chain/shufflingCache.js +72 -18
  120. package/lib/chain/shufflingCache.js.map +1 -1
  121. package/lib/chain/validation/attesterSlashing.d.ts +4 -4
  122. package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
  123. package/lib/chain/validation/attesterSlashing.js +3 -0
  124. package/lib/chain/validation/attesterSlashing.js.map +1 -1
  125. package/lib/chain/validation/proposerSlashing.d.ts +3 -3
  126. package/lib/chain/validation/proposerSlashing.d.ts.map +1 -1
  127. package/lib/chain/validation/proposerSlashing.js +3 -0
  128. package/lib/chain/validation/proposerSlashing.js.map +1 -1
  129. package/lib/chain/validatorMonitor.d.ts +5 -1
  130. package/lib/chain/validatorMonitor.d.ts.map +1 -1
  131. package/lib/chain/validatorMonitor.js +64 -54
  132. package/lib/chain/validatorMonitor.js.map +1 -1
  133. package/lib/db/beacon.d.ts +4 -1
  134. package/lib/db/beacon.d.ts.map +1 -1
  135. package/lib/db/beacon.js +6 -1
  136. package/lib/db/beacon.js.map +1 -1
  137. package/lib/db/buckets.d.ts +3 -1
  138. package/lib/db/buckets.d.ts.map +1 -1
  139. package/lib/db/buckets.js +2 -0
  140. package/lib/db/buckets.js.map +1 -1
  141. package/lib/db/interface.d.ts +4 -1
  142. package/lib/db/interface.d.ts.map +1 -1
  143. package/lib/db/repositories/blockArchiveIndex.d.ts +2 -2
  144. package/lib/db/repositories/blockArchiveIndex.d.ts.map +1 -1
  145. package/lib/db/repositories/earliestAvailableSlot.d.ts +11 -0
  146. package/lib/db/repositories/earliestAvailableSlot.d.ts.map +1 -0
  147. package/lib/db/repositories/earliestAvailableSlot.js +19 -0
  148. package/lib/db/repositories/earliestAvailableSlot.js.map +1 -0
  149. package/lib/db/repositories/index.d.ts +1 -0
  150. package/lib/db/repositories/index.d.ts.map +1 -1
  151. package/lib/db/repositories/index.js +1 -0
  152. package/lib/db/repositories/index.js.map +1 -1
  153. package/lib/db/repositories/proposerPreferences.d.ts +14 -0
  154. package/lib/db/repositories/proposerPreferences.d.ts.map +1 -0
  155. package/lib/db/repositories/proposerPreferences.js +21 -0
  156. package/lib/db/repositories/proposerPreferences.js.map +1 -0
  157. package/lib/db/repositories/proposerSlashing.d.ts +1 -0
  158. package/lib/db/repositories/proposerSlashing.d.ts.map +1 -1
  159. package/lib/db/repositories/proposerSlashing.js +5 -1
  160. package/lib/db/repositories/proposerSlashing.js.map +1 -1
  161. package/lib/execution/engine/blobVersionedHashes.d.ts +9 -0
  162. package/lib/execution/engine/blobVersionedHashes.d.ts.map +1 -0
  163. package/lib/execution/engine/blobVersionedHashes.js +74 -0
  164. package/lib/execution/engine/blobVersionedHashes.js.map +1 -0
  165. package/lib/execution/engine/disabled.d.ts +0 -2
  166. package/lib/execution/engine/disabled.d.ts.map +1 -1
  167. package/lib/execution/engine/disabled.js +0 -6
  168. package/lib/execution/engine/disabled.js.map +1 -1
  169. package/lib/execution/engine/http.d.ts +46 -16
  170. package/lib/execution/engine/http.d.ts.map +1 -1
  171. package/lib/execution/engine/http.js +236 -213
  172. package/lib/execution/engine/http.js.map +1 -1
  173. package/lib/execution/engine/index.d.ts +3 -2
  174. package/lib/execution/engine/index.d.ts.map +1 -1
  175. package/lib/execution/engine/index.js +33 -8
  176. package/lib/execution/engine/index.js.map +1 -1
  177. package/lib/execution/engine/interface.d.ts +2 -4
  178. package/lib/execution/engine/interface.d.ts.map +1 -1
  179. package/lib/execution/engine/jsonRpcTransport.d.ts +25 -0
  180. package/lib/execution/engine/jsonRpcTransport.d.ts.map +1 -0
  181. package/lib/execution/engine/jsonRpcTransport.js +158 -0
  182. package/lib/execution/engine/jsonRpcTransport.js.map +1 -0
  183. package/lib/execution/engine/mock.d.ts +0 -2
  184. package/lib/execution/engine/mock.d.ts.map +1 -1
  185. package/lib/execution/engine/mock.js +0 -8
  186. package/lib/execution/engine/mock.js.map +1 -1
  187. package/lib/execution/engine/restHttpClient.d.ts +71 -0
  188. package/lib/execution/engine/restHttpClient.d.ts.map +1 -0
  189. package/lib/execution/engine/restHttpClient.js +201 -0
  190. package/lib/execution/engine/restHttpClient.js.map +1 -0
  191. package/lib/execution/engine/restTransport.d.ts +46 -0
  192. package/lib/execution/engine/restTransport.d.ts.map +1 -0
  193. package/lib/execution/engine/restTransport.js +399 -0
  194. package/lib/execution/engine/restTransport.js.map +1 -0
  195. package/lib/execution/engine/sszTypes.d.ts +1261 -0
  196. package/lib/execution/engine/sszTypes.d.ts.map +1 -0
  197. package/lib/execution/engine/sszTypes.js +216 -0
  198. package/lib/execution/engine/sszTypes.js.map +1 -0
  199. package/lib/execution/engine/transport.d.ts +39 -0
  200. package/lib/execution/engine/transport.d.ts.map +1 -0
  201. package/lib/execution/engine/transport.js +2 -0
  202. package/lib/execution/engine/transport.js.map +1 -0
  203. package/lib/execution/engine/types.d.ts +2 -8
  204. package/lib/execution/engine/types.d.ts.map +1 -1
  205. package/lib/execution/engine/types.js +24 -57
  206. package/lib/execution/engine/types.js.map +1 -1
  207. package/lib/execution/engine/utils.d.ts.map +1 -1
  208. package/lib/execution/engine/utils.js +9 -2
  209. package/lib/execution/engine/utils.js.map +1 -1
  210. package/lib/metrics/metrics/lodestar.d.ts +10 -0
  211. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  212. package/lib/metrics/metrics/lodestar.js +22 -0
  213. package/lib/metrics/metrics/lodestar.js.map +1 -1
  214. package/lib/metrics/metrics.d.ts +7 -3
  215. package/lib/metrics/metrics.d.ts.map +1 -1
  216. package/lib/metrics/metrics.js +4 -4
  217. package/lib/metrics/metrics.js.map +1 -1
  218. package/lib/network/core/events.d.ts +5 -1
  219. package/lib/network/core/events.d.ts.map +1 -1
  220. package/lib/network/core/events.js +8 -0
  221. package/lib/network/core/events.js.map +1 -1
  222. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  223. package/lib/network/processor/gossipHandlers.js +67 -68
  224. package/lib/network/processor/gossipHandlers.js.map +1 -1
  225. package/lib/network/processor/gossipValidatorFn.d.ts +7 -3
  226. package/lib/network/processor/gossipValidatorFn.d.ts.map +1 -1
  227. package/lib/network/processor/gossipValidatorFn.js +7 -3
  228. package/lib/network/processor/gossipValidatorFn.js.map +1 -1
  229. package/lib/network/processor/index.d.ts +9 -0
  230. package/lib/network/processor/index.d.ts.map +1 -1
  231. package/lib/network/processor/index.js +77 -10
  232. package/lib/network/processor/index.js.map +1 -1
  233. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js +1 -1
  234. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js.map +1 -1
  235. package/lib/node/nodejs.d.ts +3 -1
  236. package/lib/node/nodejs.d.ts.map +1 -1
  237. package/lib/node/nodejs.js +27 -4
  238. package/lib/node/nodejs.js.map +1 -1
  239. package/lib/node/options.d.ts +3 -2
  240. package/lib/node/options.d.ts.map +1 -1
  241. package/lib/node/options.js +2 -2
  242. package/lib/node/options.js.map +1 -1
  243. package/lib/sync/range/batch.d.ts +11 -5
  244. package/lib/sync/range/batch.d.ts.map +1 -1
  245. package/lib/sync/range/batch.js +21 -17
  246. package/lib/sync/range/batch.js.map +1 -1
  247. package/lib/sync/range/chain.d.ts +4 -4
  248. package/lib/sync/range/chain.d.ts.map +1 -1
  249. package/lib/sync/range/chain.js +12 -6
  250. package/lib/sync/range/chain.js.map +1 -1
  251. package/lib/sync/range/range.d.ts.map +1 -1
  252. package/lib/sync/range/range.js +75 -29
  253. package/lib/sync/range/range.js.map +1 -1
  254. package/lib/sync/unknownBlock.d.ts +5 -2
  255. package/lib/sync/unknownBlock.d.ts.map +1 -1
  256. package/lib/sync/unknownBlock.js +49 -24
  257. package/lib/sync/unknownBlock.js.map +1 -1
  258. package/lib/sync/utils/downloadByRange.d.ts +38 -14
  259. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  260. package/lib/sync/utils/downloadByRange.js +84 -39
  261. package/lib/sync/utils/downloadByRange.js.map +1 -1
  262. package/lib/util/asyncIterableToEvents.d.ts +10 -2
  263. package/lib/util/asyncIterableToEvents.d.ts.map +1 -1
  264. package/lib/util/asyncIterableToEvents.js +29 -3
  265. package/lib/util/asyncIterableToEvents.js.map +1 -1
  266. package/lib/util/execution.d.ts +9 -11
  267. package/lib/util/execution.d.ts.map +1 -1
  268. package/lib/util/execution.js +5 -1
  269. package/lib/util/execution.js.map +1 -1
  270. package/lib/util/metadata.d.ts.map +1 -1
  271. package/lib/util/metadata.js +2 -1
  272. package/lib/util/metadata.js.map +1 -1
  273. package/package.json +18 -17
  274. package/src/api/impl/beacon/blocks/index.ts +7 -4
  275. package/src/api/impl/beacon/index.ts +15 -0
  276. package/src/api/impl/beacon/pool/index.ts +4 -4
  277. package/src/api/impl/beacon/state/index.ts +43 -6
  278. package/src/api/impl/debug/index.ts +28 -19
  279. package/src/api/impl/validator/index.ts +61 -2
  280. package/src/chain/archiveStore/archiveStore.ts +19 -25
  281. package/src/chain/archiveStore/historicalState/getHistoricalState.ts +5 -7
  282. package/src/chain/archiveStore/historicalState/historicalStateRegen.ts +1 -1
  283. package/src/chain/archiveStore/historicalState/types.ts +2 -2
  284. package/src/chain/archiveStore/historicalState/worker.ts +1 -2
  285. package/src/chain/archiveStore/interface.ts +1 -1
  286. package/src/chain/archiveStore/utils/pruneHistory.ts +4 -1
  287. package/src/chain/beaconProposerCache.ts +19 -1
  288. package/src/chain/blocks/importBlock.ts +21 -7
  289. package/src/chain/blocks/payloadEnvelopeInput/types.ts +2 -1
  290. package/src/chain/blocks/verifyBlock.ts +8 -6
  291. package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +6 -3
  292. package/src/chain/chain.ts +135 -38
  293. package/src/chain/emitter.ts +7 -1
  294. package/src/chain/errors/envelopeReconstructionError.ts +3 -0
  295. package/src/chain/errors/gossipValidation.ts +2 -0
  296. package/src/chain/forkChoice/index.ts +2 -0
  297. package/src/chain/interface.ts +5 -2
  298. package/src/chain/opPools/aggregatedAttestationPool.ts +34 -10
  299. package/src/chain/opPools/opPool.ts +89 -46
  300. package/src/chain/opPools/proposerPreferencesPool.ts +34 -11
  301. package/src/chain/opPools/utils.ts +62 -3
  302. package/src/chain/options.ts +3 -3
  303. package/src/chain/prepareNextSlot.ts +21 -6
  304. package/src/chain/produceBlock/computeNewStateRoot.ts +1 -1
  305. package/src/chain/produceBlock/produceBlockBody.ts +76 -35
  306. package/src/chain/regen/regen.ts +12 -4
  307. package/src/chain/seenCache/seenGossipBlockInput.ts +23 -0
  308. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +31 -13
  309. package/src/chain/shufflingCache.ts +84 -22
  310. package/src/chain/validation/attesterSlashing.ts +7 -4
  311. package/src/chain/validation/proposerSlashing.ts +7 -4
  312. package/src/chain/validatorMonitor.ts +75 -57
  313. package/src/db/beacon.ts +6 -0
  314. package/src/db/buckets.ts +2 -0
  315. package/src/db/interface.ts +4 -0
  316. package/src/db/repositories/earliestAvailableSlot.ts +19 -0
  317. package/src/db/repositories/index.ts +1 -0
  318. package/src/db/repositories/proposerPreferences.ts +23 -0
  319. package/src/db/repositories/proposerSlashing.ts +6 -1
  320. package/src/execution/engine/blobVersionedHashes.ts +72 -0
  321. package/src/execution/engine/disabled.ts +0 -8
  322. package/src/execution/engine/http.ts +304 -300
  323. package/src/execution/engine/index.ts +41 -7
  324. package/src/execution/engine/interface.ts +2 -6
  325. package/src/execution/engine/jsonRpcTransport.ts +247 -0
  326. package/src/execution/engine/mock.ts +1 -19
  327. package/src/execution/engine/restHttpClient.ts +269 -0
  328. package/src/execution/engine/restTransport.ts +544 -0
  329. package/src/execution/engine/sszTypes.ts +354 -0
  330. package/src/execution/engine/transport.ts +61 -0
  331. package/src/execution/engine/types.ts +34 -84
  332. package/src/execution/engine/utils.ts +12 -2
  333. package/src/metrics/metrics/lodestar.ts +23 -0
  334. package/src/metrics/metrics.ts +21 -6
  335. package/src/network/core/events.ts +16 -1
  336. package/src/network/processor/gossipHandlers.ts +98 -81
  337. package/src/network/processor/gossipValidatorFn.ts +13 -3
  338. package/src/network/processor/index.ts +89 -10
  339. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRoot.ts +1 -1
  340. package/src/node/nodejs.ts +35 -3
  341. package/src/node/options.ts +10 -1
  342. package/src/sync/range/batch.ts +25 -19
  343. package/src/sync/range/chain.ts +15 -8
  344. package/src/sync/range/range.ts +76 -30
  345. package/src/sync/unknownBlock.ts +62 -25
  346. package/src/sync/utils/downloadByRange.ts +123 -58
  347. package/src/util/asyncIterableToEvents.ts +44 -5
  348. package/src/util/execution.ts +16 -8
  349. package/src/util/metadata.ts +2 -1
@@ -73,7 +73,11 @@ type AdvancePendingBlockResult =
73
73
  | "removed";
74
74
 
75
75
  class UnknownBlockRateLimitedError extends Error {
76
- constructor(message: string) {
76
+ constructor(
77
+ message: string,
78
+ /** Earliest backoff expiry seen when the download was deferred, it is retried once that has passed */
79
+ readonly retryAt: number
80
+ ) {
77
81
  super(message);
78
82
  this.name = "UnknownBlockRateLimitedError";
79
83
  }
@@ -849,17 +853,20 @@ export class BlockInputSync {
849
853
  }
850
854
  };
851
855
 
852
- private scheduleRateLimitBackoffRetry(): void {
856
+ /**
857
+ * Returns false if there is no backoff left to wait for, in which case no retry is scheduled.
858
+ */
859
+ private scheduleRateLimitBackoffRetry(): boolean {
853
860
  this.clearRateLimitBackoffTimer();
854
861
 
855
862
  if (!this.subscribedToNetworkEvents || (this.pendingBlocks.size === 0 && this.pendingPayloads.size === 0)) {
856
- return;
863
+ return false;
857
864
  }
858
865
 
859
866
  const now = Date.now();
860
867
  const retryAt = this.peerBalancer.getNextRateLimitRetryAt();
861
868
  if (retryAt === null) {
862
- return;
869
+ return false;
863
870
  }
864
871
 
865
872
  this.rateLimitBackoffTimeout = setTimeout(
@@ -870,6 +877,7 @@ export class BlockInputSync {
870
877
  },
871
878
  Math.max(0, retryAt - now)
872
879
  );
880
+ return true;
873
881
  }
874
882
 
875
883
  private clearRateLimitBackoffTimer(): void {
@@ -1081,7 +1089,11 @@ export class BlockInputSync {
1081
1089
  pendingBlock.status = PendingBlockInputStatus.pending;
1082
1090
  }
1083
1091
  this.logger.debug("Deferring unknown block download due to peer rate limit", logCtx, res.err);
1084
- this.scheduleRateLimitBackoffRetry();
1092
+ if (!this.scheduleRateLimitBackoffRetry() || res.err.retryAt <= Date.now()) {
1093
+ // The backoff this download waited for expired in the meantime, a timer is only armed if another
1094
+ // peer is still in backoff and would retry it later than needed
1095
+ this.triggerUnknownBlockSearch();
1096
+ }
1085
1097
  return;
1086
1098
  }
1087
1099
 
@@ -1364,6 +1376,14 @@ export class BlockInputSync {
1364
1376
  if (!isPendingPayloadEnvelope(payload)) {
1365
1377
  payload.status = PendingPayloadInputStatus.pending;
1366
1378
  }
1379
+ if (
1380
+ res.err instanceof UnknownBlockRateLimitedError &&
1381
+ (!this.scheduleRateLimitBackoffRetry() || res.err.retryAt <= Date.now())
1382
+ ) {
1383
+ // The backoff this download waited for expired in the meantime, a timer is only armed if another
1384
+ // peer is still in backoff and would retry it later than needed
1385
+ this.triggerUnknownBlockSearch();
1386
+ }
1367
1387
  }
1368
1388
 
1369
1389
  private async processPayload(pendingPayload: PendingPayloadInput): Promise<void> {
@@ -1426,20 +1446,23 @@ export class BlockInputSync {
1426
1446
  pendingPayload.status = PendingPayloadInputStatus.downloaded;
1427
1447
  break;
1428
1448
 
1449
+ // The invalid envelope is evicted from the seen cache in the cases below, otherwise the next
1450
+ // attempt would reuse it instead of fetching another envelope from peers
1429
1451
  case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
1430
1452
  this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1453
+ this.chain.seenPayloadEnvelopeInputCache.removeInvalid(pendingPayload.payloadInput);
1431
1454
  this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.elInvalid);
1432
1455
  break;
1433
1456
 
1434
1457
  case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
1435
1458
  this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1459
+ this.chain.seenPayloadEnvelopeInputCache.removeInvalid(pendingPayload.payloadInput);
1436
1460
  this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidEnvelope);
1437
1461
  break;
1438
1462
 
1439
1463
  case PayloadErrorCode.INVALID_SIGNATURE:
1440
- // TODO GLOAS: Decide how invalid payload inputs should eventually leave memory without
1441
- // reintroducing envelope replacement / recreation flows.
1442
1464
  this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1465
+ this.chain.seenPayloadEnvelopeInputCache.removeInvalid(pendingPayload.payloadInput);
1443
1466
  this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidSignature);
1444
1467
  break;
1445
1468
 
@@ -1485,16 +1508,21 @@ export class BlockInputSync {
1485
1508
  const pendingColumns = payloadInput?.hasAllData()
1486
1509
  ? new Set<number>()
1487
1510
  : new Set(payloadInput?.getMissingSampledColumnMeta().missing ?? []);
1511
+ // Same `now` for both calls, a backoff expiring in between would be reported as no peer available
1512
+ const now = Date.now();
1488
1513
  // prefer peers that gossiped this payload root to us (#9923)
1489
1514
  const peerMeta = this.peerBalancer.bestPeerForPendingColumns(
1490
1515
  pendingColumns,
1491
1516
  excludedPeers,
1492
- cacheItem.peerIdStrings
1517
+ cacheItem.peerIdStrings,
1518
+ now
1493
1519
  );
1494
1520
  if (peerMeta === null) {
1495
- if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
1521
+ const retryAt = this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers, now);
1522
+ if (retryAt !== null) {
1496
1523
  throw new UnknownBlockRateLimitedError(
1497
- `Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`
1524
+ `Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`,
1525
+ retryAt
1498
1526
  );
1499
1527
  }
1500
1528
 
@@ -1633,9 +1661,11 @@ export class BlockInputSync {
1633
1661
  );
1634
1662
  this.metrics?.blockInputSync.payloadFetchPeers.set({result: FetchResult.FailureMaxAttempts}, i - 1);
1635
1663
 
1636
- if (deferredByRateLimit && this.peerBalancer.getNextRateLimitRetryAt() !== null) {
1664
+ const retryAt = deferredByRateLimit ? this.peerBalancer.getNextRateLimitRetryAt() : null;
1665
+ if (retryAt !== null) {
1637
1666
  throw new UnknownBlockRateLimitedError(
1638
- `Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts: peers are rate-limited`
1667
+ `Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts: peers are rate-limited`,
1668
+ retryAt
1639
1669
  );
1640
1670
  }
1641
1671
  throw Error(`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts.`);
@@ -1727,16 +1757,21 @@ export class BlockInputSync {
1727
1757
  isPendingBlockInput(cacheItem) && isBlockInputColumns(cacheItem.blockInput)
1728
1758
  ? new Set(cacheItem.blockInput.getMissingSampledColumnMeta().missing)
1729
1759
  : defaultPendingColumns;
1760
+ // Same `now` for both calls, a backoff expiring in between would be reported as no peer available
1761
+ const now = Date.now();
1730
1762
  // prefer peers that gossiped this block root to us (#9923)
1731
1763
  const peerMeta = this.peerBalancer.bestPeerForPendingColumns(
1732
1764
  pendingColumns,
1733
1765
  excludedPeers,
1734
- cacheItem.peerIdStrings
1766
+ cacheItem.peerIdStrings,
1767
+ now
1735
1768
  );
1736
1769
  if (peerMeta === null) {
1737
- if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
1770
+ const retryAt = this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers, now);
1771
+ if (retryAt !== null) {
1738
1772
  throw new UnknownBlockRateLimitedError(
1739
- `Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`
1773
+ `Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`,
1774
+ retryAt
1740
1775
  );
1741
1776
  }
1742
1777
 
@@ -1842,8 +1877,9 @@ export class BlockInputSync {
1842
1877
 
1843
1878
  const message = `Error fetching BlockInput with slot=${slot} root=${rootHex} after ${i - 1} attempts.`;
1844
1879
 
1845
- if (deferredByRateLimit && this.peerBalancer.getNextRateLimitRetryAt() !== null) {
1846
- throw new UnknownBlockRateLimitedError(`${message} Peers are rate-limited.`);
1880
+ const retryAt = deferredByRateLimit ? this.peerBalancer.getNextRateLimitRetryAt() : null;
1881
+ if (retryAt !== null) {
1882
+ throw new UnknownBlockRateLimitedError(`${message} Peers are rate-limited.`, retryAt);
1847
1883
  }
1848
1884
 
1849
1885
  if (!isPendingBlockInput(cacheItem)) {
@@ -1879,8 +1915,6 @@ export class BlockInputSync {
1879
1915
 
1880
1916
  // Once a parent payload is invalid, every descendant waiting on that payload lineage becomes unrecoverable too.
1881
1917
  private removePendingPayloadAndDescendants(rootHex: RootHex, headReason: DroppedItemReason): void {
1882
- // Keep PayloadEnvelopeInput resident in the seen cache. importBlock() owns that object and
1883
- // later validation/finalization logic decides when it can leave memory.
1884
1918
  if (this.pendingPayloads.delete(rootHex)) {
1885
1919
  this.metrics?.blockInputSync.removedPayloads.inc({reason: headReason}, 1);
1886
1920
  }
@@ -2036,8 +2070,11 @@ export class UnknownBlockPeerBalancer {
2036
2070
  this.rateLimitedUntilByPeer.set(peerId, rateLimitedUntilMs);
2037
2071
  }
2038
2072
 
2039
- getNextRateLimitRetryAt(pendingColumns?: Set<number>, excludedPeers?: Set<PeerIdStr>): number | null {
2040
- const now = Date.now();
2073
+ getNextRateLimitRetryAt(
2074
+ pendingColumns?: Set<number>,
2075
+ excludedPeers?: Set<PeerIdStr>,
2076
+ now = Date.now()
2077
+ ): number | null {
2041
2078
  let retryAt: number | null = null;
2042
2079
 
2043
2080
  for (const [peerId, rateLimitedUntil] of this.rateLimitedUntilByPeer.entries()) {
@@ -2076,9 +2113,10 @@ export class UnknownBlockPeerBalancer {
2076
2113
  bestPeerForPendingColumns(
2077
2114
  pendingColumns: Set<number>,
2078
2115
  excludedPeers: Set<PeerIdStr>,
2079
- preferredPeers?: Set<PeerIdStr>
2116
+ preferredPeers?: Set<PeerIdStr>,
2117
+ now = Date.now()
2080
2118
  ): PeerSyncMeta | null {
2081
- const eligiblePeers = this.filterPeers(pendingColumns, excludedPeers);
2119
+ const eligiblePeers = this.filterPeers(pendingColumns, excludedPeers, now);
2082
2120
  if (eligiblePeers.length === 0) {
2083
2121
  return null;
2084
2122
  }
@@ -2119,8 +2157,7 @@ export class UnknownBlockPeerBalancer {
2119
2157
  return totalActiveRequests;
2120
2158
  }
2121
2159
 
2122
- private filterPeers(pendingDataColumns: Set<number>, excludedPeers: Set<PeerIdStr>): PeerIdStr[] {
2123
- const now = Date.now();
2160
+ private filterPeers(pendingDataColumns: Set<number>, excludedPeers: Set<PeerIdStr>, now: number): PeerIdStr[] {
2124
2161
  let maxColumnCount = 0;
2125
2162
  const considerPeers: {peerId: PeerIdStr; columnCount: number}[] = [];
2126
2163
  for (const [peerId, syncMeta] of this.peersMeta.entries()) {
@@ -1,5 +1,6 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
2
  import {
3
+ ForkName,
3
4
  ForkPostDeneb,
4
5
  ForkPostFulu,
5
6
  ForkPostGloas,
@@ -13,9 +14,11 @@ import {
13
14
  RootHex,
14
15
  SignedBeaconBlock,
15
16
  Slot,
17
+ ValidatorIndex,
16
18
  deneb,
17
19
  fulu,
18
20
  gloas,
21
+ isGloasBeaconBlock,
19
22
  isGloasDataColumnSidecar,
20
23
  phase0,
21
24
  } from "@lodestar/types";
@@ -59,10 +62,17 @@ export type DownloadByRangeRequests = {
59
62
  };
60
63
  };
61
64
 
62
- export type ParentPayloadCommitments = {
65
+ /**
66
+ * The head block when the `SyncChain` was created. The first block of the first batch may build on its FULL variant
67
+ * while the payload is not imported yet, the batch then downloads the payload by root as its dangling parent.
68
+ */
69
+ export type ParentPayload = {
63
70
  blockRoot: Uint8Array;
64
71
  blockRootHex: RootHex;
65
- kzgCommitments: deneb.BlobKzgCommitments;
72
+ slot: Slot;
73
+ proposerIndex: ValidatorIndex;
74
+ forkName: ForkName;
75
+ bid: gloas.ExecutionPayloadBid;
66
76
  };
67
77
 
68
78
  export type DownloadByRangeResponses = {
@@ -78,14 +88,14 @@ export type DownloadAndCacheByRangeProps = DownloadByRangeRequests & {
78
88
  logger: Logger;
79
89
  peerIdStr: string;
80
90
  batchBlocks?: IBlockInput[];
81
- /** Required when `parentPayloadRequest` is set; supplies the data needed to validate the parent's columns. */
82
- parentPayloadCommitments?: ParentPayloadCommitments;
91
+ /** Required when `parentPayloadRequest` is set */
92
+ parentPayload?: ParentPayload;
83
93
  peerDasMetrics?: BeaconMetrics["peerDas"] | null;
84
94
  };
85
95
 
86
96
  export type CacheByRangeResponsesProps = {
87
- cache: SeenBlockInput;
88
- seenPayloadEnvelopeInputCache: SeenPayloadEnvelopeInput;
97
+ cache: Pick<SeenBlockInput, "getByBlock">;
98
+ seenPayloadEnvelopeInputCache: Pick<SeenPayloadEnvelopeInput, "add" | "addFromBid">;
89
99
  peerIdStr: string;
90
100
  responses: ValidatedResponses;
91
101
  batchBlocks: IBlockInput[];
@@ -93,6 +103,8 @@ export type CacheByRangeResponsesProps = {
93
103
  downloadedPayloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null;
94
104
  /** Envelopes already wrapped from previous partial downloads on this batch */
95
105
  existingPayloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null;
106
+ /** Set when `downloadedPayloadEnvelopes` may contain the dangling parent envelope */
107
+ parentPayload?: ParentPayload;
96
108
  /** Sampled/custody column indices for building PayloadEnvelopeInputs */
97
109
  custodyConfig: Pick<CustodyConfig, "sampledColumns" | "custodyColumns">;
98
110
  seenTimestampSec: number;
@@ -130,6 +142,7 @@ export function cacheByRangeResponses({
130
142
  batchBlocks,
131
143
  downloadedPayloadEnvelopes,
132
144
  existingPayloadEnvelopes,
145
+ parentPayload,
133
146
  custodyConfig,
134
147
  seenTimestampSec,
135
148
  }: CacheByRangeResponsesProps): {blocks: IBlockInput[]; payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null} {
@@ -228,20 +241,29 @@ export function cacheByRangeResponses({
228
241
 
229
242
  if (downloadedPayloadEnvelopes !== null) {
230
243
  for (const [slot, envelope] of downloadedPayloadEnvelopes) {
231
- // the only PayloadEnvelopeInput miss is the dangling parent, which we can get from the seen cache
244
+ // the only PayloadEnvelopeInput miss is the dangling parent
232
245
  let payloadInput = payloadEnvelopes.get(slot);
233
246
  if (payloadInput === undefined) {
247
+ const blockRootHex = toRootHex(envelope.message.beaconBlockRoot);
234
248
  if (updatedBatchBlocks.has(slot)) {
235
- throw new Error(
236
- `Missing PayloadEnvelopeInput for in-batch slot ${slot} root ${toRootHex(envelope.message.beaconBlockRoot)}`
237
- );
249
+ throw new Error(`Missing PayloadEnvelopeInput for in-batch slot ${slot} root ${blockRootHex}`);
238
250
  }
239
- payloadInput = seenPayloadEnvelopeInputCache.get(toRootHex(envelope.message.beaconBlockRoot));
240
- }
241
- if (payloadInput === undefined) {
242
- throw new Error(
243
- `Missing PayloadEnvelopeInput for slot ${slot} root ${toRootHex(envelope.message.beaconBlockRoot)}`
244
- );
251
+ if (parentPayload === undefined || blockRootHex !== parentPayload.blockRootHex) {
252
+ throw new Error(`Coding error: envelope for slot ${slot} root ${blockRootHex} is not the dangling parent`);
253
+ }
254
+ // Recreates the entry if it was evicted, by the cache cap or after an invalid envelope failed the import,
255
+ // the parent block is not part of the batch to recreate it from
256
+ payloadInput = seenPayloadEnvelopeInputCache.addFromBid({
257
+ blockRootHex,
258
+ slot: parentPayload.slot,
259
+ forkName: parentPayload.forkName,
260
+ proposerIndex: parentPayload.proposerIndex,
261
+ bid: parentPayload.bid,
262
+ sampledColumns: custodyConfig.sampledColumns,
263
+ custodyColumns: custodyConfig.custodyColumns,
264
+ seenTimestampSec,
265
+ source: PayloadEnvelopeInputSource.byRange,
266
+ });
245
267
  }
246
268
 
247
269
  if (!payloadInput.hasPayloadEnvelope()) {
@@ -332,7 +354,7 @@ export async function downloadByRange({
332
354
  columnsRequest,
333
355
  envelopesRequest,
334
356
  parentPayloadRequest,
335
- parentPayloadCommitments,
357
+ parentPayload,
336
358
  peerDasMetrics,
337
359
  }: DownloadAndCacheByRangeProps): Promise<
338
360
  WarnResult<
@@ -367,7 +389,7 @@ export async function downloadByRange({
367
389
  columnsRequest,
368
390
  envelopesRequest,
369
391
  parentPayloadRequest,
370
- parentPayloadCommitments,
392
+ parentPayload,
371
393
  peerDasMetrics,
372
394
  ...response,
373
395
  });
@@ -471,7 +493,7 @@ export async function validateResponses({
471
493
  columnsRequest,
472
494
  envelopesRequest,
473
495
  parentPayloadRequest,
474
- parentPayloadCommitments,
496
+ parentPayload,
475
497
  blocks,
476
498
  blobSidecars,
477
499
  columnSidecars,
@@ -481,7 +503,7 @@ export async function validateResponses({
481
503
  DownloadByRangeResponses & {
482
504
  config: ChainForkConfig;
483
505
  batchBlocks?: IBlockInput[];
484
- parentPayloadCommitments?: ParentPayloadCommitments;
506
+ parentPayload?: ParentPayload;
485
507
  peerDasMetrics?: BeaconMetrics["peerDas"] | null;
486
508
  }): Promise<
487
509
  WarnResult<
@@ -502,9 +524,9 @@ export async function validateResponses({
502
524
  );
503
525
  }
504
526
 
505
- // `parentPayloadRequest` and `parentPayloadCommitments` must be supplied together
506
- if ((parentPayloadRequest === undefined) !== (parentPayloadCommitments === undefined)) {
507
- throw new Error("Coding error: parentPayloadRequest and parentPayloadCommitments must be both set or both unset");
527
+ // `parentPayloadRequest` and `parentPayload` must be supplied together
528
+ if ((parentPayloadRequest === undefined) !== (parentPayload === undefined)) {
529
+ throw new Error("Coding error: parentPayloadRequest and parentPayload must be both set or both unset");
508
530
  }
509
531
 
510
532
  const validatedResponses: ValidatedResponses = {};
@@ -518,7 +540,7 @@ export async function validateResponses({
518
540
  validatedResponses.validatedBlocks = result.result;
519
541
  }
520
542
 
521
- const needsEnvelopeValidation = !!envelopesRequest || parentPayloadCommitments !== undefined;
543
+ const needsEnvelopeValidation = !!envelopesRequest || parentPayload !== undefined;
522
544
  const dataRequest = blobsRequest ?? columnsRequest;
523
545
  if (!dataRequest && !needsEnvelopeValidation) {
524
546
  return {result: {responses: validatedResponses, payloadEnvelopes: null}, warnings};
@@ -526,12 +548,8 @@ export async function validateResponses({
526
548
 
527
549
  if (!dataRequest) {
528
550
  // Only envelope and/or parent-by-root validation needed
529
- if (parentPayloadCommitments !== undefined) {
530
- const parentValidated = await validateParentPayloadColumns(
531
- parentPayloadCommitments,
532
- columnSidecars ?? [],
533
- peerDasMetrics
534
- );
551
+ if (parentPayload !== undefined) {
552
+ const parentValidated = await validateParentPayloadColumns(parentPayload, columnSidecars ?? [], peerDasMetrics);
535
553
  if (parentValidated) {
536
554
  validatedResponses.validatedColumnSidecars = [parentValidated];
537
555
  }
@@ -540,7 +558,7 @@ export async function validateResponses({
540
558
  validatedResponses.validatedBlocks ?? [],
541
559
  batchBlocks,
542
560
  payloadEnvelopes ?? [],
543
- parentPayloadCommitments
561
+ parentPayload
544
562
  );
545
563
  return {result: {responses: validatedResponses, payloadEnvelopes: validatedPayloadEnvelopes}, warnings};
546
564
  }
@@ -601,12 +619,8 @@ export async function validateResponses({
601
619
  }
602
620
 
603
621
  // Parent columns (by-root): KZG-validate against parent's bid commitments and append.
604
- if (parentPayloadCommitments !== undefined) {
605
- const parentValidated = await validateParentPayloadColumns(
606
- parentPayloadCommitments,
607
- columnSidecars ?? [],
608
- peerDasMetrics
609
- );
622
+ if (parentPayload !== undefined) {
623
+ const parentValidated = await validateParentPayloadColumns(parentPayload, columnSidecars ?? [], peerDasMetrics);
610
624
  if (parentValidated) {
611
625
  validatedResponses.validatedColumnSidecars = [
612
626
  ...(validatedResponses.validatedColumnSidecars ?? []),
@@ -621,7 +635,7 @@ export async function validateResponses({
621
635
  validatedResponses.validatedBlocks ?? [],
622
636
  batchBlocks,
623
637
  payloadEnvelopes ?? [],
624
- parentPayloadCommitments
638
+ parentPayload
625
639
  );
626
640
  }
627
641
 
@@ -629,13 +643,13 @@ export async function validateResponses({
629
643
  }
630
644
 
631
645
  async function validateParentPayloadColumns(
632
- parentPayloadCommitments: ParentPayloadCommitments,
646
+ parentPayload: ParentPayload,
633
647
  columnSidecars: DataColumnSidecar[],
634
648
  peerDasMetrics?: BeaconMetrics["peerDas"] | null
635
649
  ): Promise<ValidatedColumnSidecars | null> {
636
650
  const parentColumns: gloas.DataColumnSidecar[] = [];
637
651
  for (const cs of columnSidecars) {
638
- if (isGloasDataColumnSidecar(cs) && byteArrayEquals(cs.beaconBlockRoot, parentPayloadCommitments.blockRoot)) {
652
+ if (isGloasDataColumnSidecar(cs) && byteArrayEquals(cs.beaconBlockRoot, parentPayload.blockRoot)) {
639
653
  parentColumns.push(cs);
640
654
  }
641
655
  }
@@ -645,17 +659,16 @@ async function validateParentPayloadColumns(
645
659
  }
646
660
 
647
661
  parentColumns.sort((a, b) => a.index - b.index);
648
- const parentSlot = parentColumns[0].slot;
649
662
 
650
663
  await validateGloasBlockDataColumnSidecars(
651
- parentSlot,
652
- parentPayloadCommitments.blockRoot,
653
- parentPayloadCommitments.kzgCommitments,
664
+ parentPayload.slot,
665
+ parentPayload.blockRoot,
666
+ parentPayload.bid.blobKzgCommitments,
654
667
  parentColumns,
655
668
  peerDasMetrics
656
669
  );
657
670
 
658
- return {blockRoot: parentPayloadCommitments.blockRoot, columnSidecars: parentColumns};
671
+ return {blockRoot: parentPayload.blockRoot, columnSidecars: parentColumns};
659
672
  }
660
673
 
661
674
  /**
@@ -1171,6 +1184,10 @@ export enum DownloadByRangeErrorCode {
1171
1184
 
1172
1185
  /** Envelope beaconBlockRoot does not match the block's root */
1173
1186
  INVALID_ENVELOPE_BEACON_BLOCK_ROOT = "DOWNLOAD_BY_RANGE_ERROR_INVALID_ENVELOPE_BEACON_BLOCK_ROOT",
1187
+ /** Envelope payload.blockHash does not match the block's bid */
1188
+ INVALID_ENVELOPE_BLOCK_HASH = "DOWNLOAD_BY_RANGE_ERROR_INVALID_ENVELOPE_BLOCK_HASH",
1189
+ /** Dangling parent envelope payload.slotNumber does not match the parent's slot */
1190
+ INVALID_ENVELOPE_SLOT = "DOWNLOAD_BY_RANGE_ERROR_INVALID_ENVELOPE_SLOT",
1174
1191
 
1175
1192
  /** Block segment + envelopes failed chain-segment linearity / FULL-chain checks */
1176
1193
  INVALID_CHAIN_SEGMENT = "DOWNLOAD_BY_RANGE_ERROR_INVALID_CHAIN_SEGMENT",
@@ -1272,6 +1289,17 @@ export type DownloadByRangeErrorType =
1272
1289
  expected: string;
1273
1290
  actual: string;
1274
1291
  }
1292
+ | {
1293
+ code: DownloadByRangeErrorCode.INVALID_ENVELOPE_BLOCK_HASH;
1294
+ slot: Slot;
1295
+ expected: string;
1296
+ actual: string;
1297
+ }
1298
+ | {
1299
+ code: DownloadByRangeErrorCode.INVALID_ENVELOPE_SLOT;
1300
+ expected: Slot;
1301
+ actual: Slot;
1302
+ }
1275
1303
  | {
1276
1304
  code: DownloadByRangeErrorCode.INVALID_CHAIN_SEGMENT;
1277
1305
  slot: Slot;
@@ -1285,57 +1313,94 @@ export class DownloadByRangeError extends LodestarError<DownloadByRangeErrorType
1285
1313
  *
1286
1314
  * Three categories of envelope slots:
1287
1315
  * - In-batch slot whose block we have: verify envelope.beaconBlockRoot matches.
1288
- * - Dangling-parent envelope (only when `parentPayloadCommitments` is set, i.e. the first
1289
- * batch of a `SyncChain`): keep if `envelope.beaconBlockRoot === parentPayloadCommitments.blockRoot`.
1316
+ * - Dangling-parent envelope (only when `parentPayload` is set, i.e. the first
1317
+ * batch of a `SyncChain`): keep if `envelope.beaconBlockRoot === parentPayload.blockRoot`, its slot and
1318
+ * block hash must match the parent's bid.
1290
1319
  * - Other "orphan" envelopes (e.g. unrelated slots): ignored.
1291
1320
  */
1292
1321
  export function validateEnvelopesByRangeResponse(
1293
1322
  validatedBlocks: ValidatedBlock[],
1294
1323
  batchBlocks: IBlockInput[] | undefined,
1295
1324
  payloadEnvelopes: gloas.SignedExecutionPayloadEnvelope[],
1296
- parentPayloadCommitments?: ParentPayloadCommitments
1325
+ parentPayload?: ParentPayload
1297
1326
  ): Map<Slot, gloas.SignedExecutionPayloadEnvelope> {
1298
- // Build a map of slot -> blockRoot for all blocks in the batch
1299
- const batchBlockRoots = new Map<Slot, Uint8Array>();
1327
+ // Build a map of slot -> block root and bid block hash for all blocks in the batch
1328
+ const batchBlocksBySlot = new Map<Slot, {blockRoot: Uint8Array; block: SignedBeaconBlock | null}>();
1300
1329
  if (batchBlocks) {
1301
1330
  for (const blockInput of batchBlocks) {
1302
- batchBlockRoots.set(blockInput.slot, fromHex(blockInput.blockRootHex));
1331
+ batchBlocksBySlot.set(blockInput.slot, {
1332
+ blockRoot: fromHex(blockInput.blockRootHex),
1333
+ block: blockInput.hasBlock() ? blockInput.getBlock() : null,
1334
+ });
1303
1335
  }
1304
1336
  }
1305
1337
  for (const {block, blockRoot} of validatedBlocks) {
1306
- batchBlockRoots.set(block.message.slot, blockRoot);
1338
+ batchBlocksBySlot.set(block.message.slot, {blockRoot, block});
1307
1339
  }
1308
1340
 
1309
1341
  const payloadEnvelopeMap = new Map<Slot, gloas.SignedExecutionPayloadEnvelope>();
1310
1342
 
1311
1343
  for (const payloadEnvelope of payloadEnvelopes) {
1312
1344
  const slot = payloadEnvelope.message.payload.slotNumber;
1313
- const batchBlockRoot = batchBlockRoots.get(slot);
1345
+ const batchBlock = batchBlocksBySlot.get(slot);
1314
1346
 
1315
- if (batchBlockRoot === undefined) {
1347
+ if (batchBlock === undefined) {
1316
1348
  // Keep the requested dangling-parent envelope only when its beaconBlockRoot matches
1317
1349
  // exactly. All other unrelated envelopes are dropped.
1318
1350
  if (
1319
- parentPayloadCommitments !== undefined &&
1320
- byteArrayEquals(payloadEnvelope.message.beaconBlockRoot, parentPayloadCommitments.blockRoot)
1351
+ parentPayload !== undefined &&
1352
+ byteArrayEquals(payloadEnvelope.message.beaconBlockRoot, parentPayload.blockRoot)
1321
1353
  ) {
1354
+ if (slot !== parentPayload.slot) {
1355
+ throw new DownloadByRangeError({
1356
+ code: DownloadByRangeErrorCode.INVALID_ENVELOPE_SLOT,
1357
+ expected: parentPayload.slot,
1358
+ actual: slot,
1359
+ });
1360
+ }
1361
+ assertEnvelopeBlockHash(payloadEnvelope, parentPayload.bid.blockHash);
1322
1362
  payloadEnvelopeMap.set(slot, payloadEnvelope);
1323
1363
  }
1324
1364
  continue;
1325
1365
  }
1326
1366
 
1327
1367
  // Verify beaconBlockRoot matches the block's root
1328
- if (!byteArrayEquals(payloadEnvelope.message.beaconBlockRoot, batchBlockRoot)) {
1368
+ if (!byteArrayEquals(payloadEnvelope.message.beaconBlockRoot, batchBlock.blockRoot)) {
1329
1369
  throw new DownloadByRangeError({
1330
1370
  code: DownloadByRangeErrorCode.INVALID_ENVELOPE_BEACON_BLOCK_ROOT,
1331
1371
  slot,
1332
- expected: toRootHex(batchBlockRoot),
1372
+ expected: toRootHex(batchBlock.blockRoot),
1333
1373
  actual: toRootHex(payloadEnvelope.message.beaconBlockRoot),
1334
1374
  });
1335
1375
  }
1336
1376
 
1377
+ if (batchBlock.block !== null && isGloasBeaconBlock(batchBlock.block.message)) {
1378
+ assertEnvelopeBlockHash(
1379
+ payloadEnvelope,
1380
+ batchBlock.block.message.body.signedExecutionPayloadBid.message.blockHash
1381
+ );
1382
+ }
1383
+
1337
1384
  payloadEnvelopeMap.set(slot, payloadEnvelope);
1338
1385
  }
1339
1386
 
1340
1387
  return payloadEnvelopeMap;
1341
1388
  }
1389
+
1390
+ /**
1391
+ * Rejected before it is cached. In-batch, `assertLinearChainSegment` fails on a wrong hash without evicting the
1392
+ * envelope. For the dangling parent, the mismatch would only be caught at import.
1393
+ */
1394
+ function assertEnvelopeBlockHash(
1395
+ payloadEnvelope: gloas.SignedExecutionPayloadEnvelope,
1396
+ bidBlockHash: Uint8Array
1397
+ ): void {
1398
+ if (!byteArrayEquals(payloadEnvelope.message.payload.blockHash, bidBlockHash)) {
1399
+ throw new DownloadByRangeError({
1400
+ code: DownloadByRangeErrorCode.INVALID_ENVELOPE_BLOCK_HASH,
1401
+ slot: payloadEnvelope.message.payload.slotNumber,
1402
+ expected: toRootHex(bidBlockHash),
1403
+ actual: toRootHex(payloadEnvelope.message.payload.blockHash),
1404
+ });
1405
+ }
1406
+ }