@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
@@ -1,15 +1,23 @@
1
1
  import {Logger} from "@lodestar/logger";
2
- import {ForkName, ForkPostFulu, ForkPreFulu, ForkSeq, SLOTS_PER_EPOCH, isForkPostFulu} from "@lodestar/params";
2
+ import {
3
+ ForkName,
4
+ ForkPostFulu,
5
+ ForkPreFulu,
6
+ ForkSeq,
7
+ SLOTS_PER_EPOCH,
8
+ isForkPostBellatrix,
9
+ isForkPostFulu,
10
+ } from "@lodestar/params";
3
11
  import {BlobsBundle, ExecutionPayload, ExecutionRequests, Root, RootHex, Wei} from "@lodestar/types";
4
12
  import {BlobAndProof} from "@lodestar/types/deneb";
5
13
  import {BlobAndProofV2} from "@lodestar/types/fulu";
6
- import {strip0xPrefix} from "@lodestar/utils";
14
+ import {isErrorAborted} from "@lodestar/utils";
7
15
  import {Metrics} from "../../metrics/index.js";
8
16
  import {EPOCHS_PER_BATCH} from "../../sync/constants.js";
9
17
  import {getLodestarClientVersion} from "../../util/metadata.js";
18
+ import {isQueueErrorAborted} from "../../util/queue/errors.js";
10
19
  import {JobItemQueue} from "../../util/queue/index.js";
11
20
  import {
12
- ClientCode,
13
21
  ClientVersion,
14
22
  ExecutePayloadResponse,
15
23
  ExecutionEngineState,
@@ -19,34 +27,14 @@ import {
19
27
  PayloadId,
20
28
  VersionedHashes,
21
29
  } from "./interface.js";
22
- import {
23
- ErrorJsonRpcResponse,
24
- HttpRpcError,
25
- IJsonRpcHttpClient,
26
- JsonRpcHttpClientEvent,
27
- ReqOpts,
28
- } from "./jsonRpcHttpClient.js";
30
+ import {ErrorJsonRpcResponse, HttpRpcError, JsonRpcHttpClientEvent} from "./jsonRpcHttpClient.js";
29
31
  import {PayloadIdCache} from "./payloadIdCache.js";
30
- import {
31
- BLOB_AND_PROOF_V2_RPC_BYTES,
32
- EngineApiRpcParamTypes,
33
- EngineApiRpcReturnTypes,
34
- ExecutionPayloadBody,
35
- ExecutionPayloadBodyV2,
36
- assertReqSizeLimit,
37
- deserializeBlobAndProofs,
38
- deserializeBlobAndProofsV2,
39
- deserializeBlobAndProofsV2IntoBytes,
40
- deserializeExecutionPayloadBody,
41
- deserializeExecutionPayloadBodyV2,
42
- parseExecutionPayload,
43
- serializeBeaconBlockRoot,
44
- serializeExecutionPayload,
45
- serializeExecutionRequests,
46
- serializePayloadAttributes,
47
- serializeVersionedHashes,
48
- } from "./types.js";
49
- import {bytesToData, getExecutionEngineState, numToQuantity} from "./utils.js";
32
+ import {EngineRestError, EngineRestResponseError, isRetryableEngineRestError} from "./restHttpClient.js";
33
+ import {EngineCapabilities, RestEngineTransport} from "./restTransport.js";
34
+ import {executionForkName} from "./sszTypes.js";
35
+ import {IEngineTransport, PayloadStatusResult} from "./transport.js";
36
+ import {ExecutionPayloadBodyV2, serializePayloadAttributes} from "./types.js";
37
+ import {getExecutionEngineState} from "./utils.js";
50
38
 
51
39
  export type ExecutionEngineModules = {
52
40
  signal: AbortSignal;
@@ -54,11 +42,21 @@ export type ExecutionEngineModules = {
54
42
  logger: Logger;
55
43
  };
56
44
 
45
+ /**
46
+ * Engine API transport selection
47
+ * - `auto`: negotiate REST with one execution URL; use JSON-RPC for multiple URLs or unadvertised features
48
+ * - `ssz`: always use the REST API with SSZ encoding
49
+ * - `json-rpc`: always use the legacy JSON-RPC API
50
+ */
51
+ export type EngineApiMode = "auto" | "ssz" | "json-rpc";
52
+ export const engineApiModes: EngineApiMode[] = ["auto", "ssz", "json-rpc"];
53
+
57
54
  export type ExecutionEngineHttpOpts = {
58
55
  urls: string[];
59
56
  retries: number;
60
57
  retryDelay: number;
61
58
  timeout?: number;
59
+ engineApi?: EngineApiMode;
62
60
  /**
63
61
  * 256 bit jwt secret in hex format without the leading 0x. If provided, the execution engine
64
62
  * rpc requests will be bundled by an authorization header having a fresh jwt token on each
@@ -94,39 +92,37 @@ export const defaultExecutionEngineHttpOpts: ExecutionEngineHttpOpts = {
94
92
  retries: 2,
95
93
  retryDelay: 2000,
96
94
  timeout: 12000,
95
+ engineApi: "auto",
97
96
  };
98
97
 
98
+ export type ExecutionEngineTransports = {
99
+ jsonRpc: IEngineTransport;
100
+ rest?: RestEngineTransport;
101
+ };
102
+
103
+ type RestSupport =
104
+ | {state: "pending"; error?: Error}
105
+ | {state: "unsupported"}
106
+ | {state: "supported"; capabilities: EngineCapabilities};
107
+
99
108
  /**
100
109
  * Size for the serializing queue for fcUs and new payloads, the max length could be equal to
101
110
  * EPOCHS_PER_BATCH * 2 in case new payloads are also not awaited serially
102
111
  */
103
112
  const QUEUE_MAX_LENGTH = EPOCHS_PER_BATCH * SLOTS_PER_EPOCH * 2;
104
113
 
105
- /**
106
- * Maximum number of version hashes that can be sent in a getBlobs request
107
- * Clients must support at least 128 versionedHashes, so we avoid sending more
108
- * https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#specification-3
109
- */
110
- const MAX_VERSIONED_HASHES = 128;
111
-
112
- // Define static options once to prevent extra allocations
113
- const notifyNewPayloadOpts: ReqOpts = {routeId: "notifyNewPayload"};
114
- const forkchoiceUpdatedV1Opts: ReqOpts = {routeId: "forkchoiceUpdated"};
115
- const getPayloadOpts: ReqOpts = {routeId: "getPayload"};
116
- const getPayloadBodiesByHashOpts: ReqOpts = {routeId: "getPayloadBodiesByHash"};
117
- const getPayloadBodiesByRangeOpts: ReqOpts = {routeId: "getPayloadBodiesByRange"};
118
- const getBlobsV1Opts: ReqOpts = {routeId: "getBlobsV1"};
119
- const getBlobsV2Opts: ReqOpts = {routeId: "getBlobsV2"};
120
- const getClientVersionOpts: ReqOpts = {routeId: "getClientVersion"};
114
+ /** Minimum delay between capabilities probes while the execution client is unreachable */
115
+ const REST_PROBE_RETRY_MS = 12_000;
121
116
 
122
117
  /**
123
- * based on Ethereum JSON-RPC API and inherits the following properties of this standard:
124
- * - Supported communication protocols (HTTP and WebSocket)
125
- * - Message format and encoding notation
126
- * - Error codes improvement proposal
118
+ * Drives the execution client over the engine API, either the legacy `engine_*` JSON-RPC methods
119
+ * or the REST API with SSZ encoded bodies.
120
+ * https://github.com/ethereum/execution-apis/tree/main/src/engine
127
121
  *
128
- * Client software MUST expose Engine API at a port independent from JSON-RPC API. The default port for the Engine API is 8550 for HTTP and 8551 for WebSocket.
129
- * https://github.com/ethereum/execution-apis/blob/v1.0.0-alpha.1/src/engine/interop/specification.md
122
+ * In `auto` mode a capabilities response from a server without the REST API, a 4xx other than
123
+ * 401/403, selects JSON-RPC until the EL reconnects. Transient discovery failures use JSON-RPC while
124
+ * awaiting another probe; authentication failures fail visibly, malformed capabilities fall back to
125
+ * JSON-RPC with a warning. Forks and blob revisions the EL does not advertise also use JSON-RPC.
130
126
  */
131
127
  export class ExecutionEngineHttp implements IExecutionEngine {
132
128
  private logger: Logger;
@@ -150,42 +146,55 @@ export class ExecutionEngineHttp implements IExecutionEngine {
150
146
  * the order of new payloads and fcUs is pretty important to EL, this queue will serialize the calls in the
151
147
  * order with which we make them.
152
148
  */
153
- private readonly rpcFetchQueue: JobItemQueue<[EngineRequest], EngineResponse>;
149
+ private readonly queue: JobItemQueue<[() => Promise<unknown>], unknown>;
154
150
 
155
- private jobQueueProcessor = async ({method, params, methodOpts}: EngineRequest): Promise<EngineResponse> => {
156
- return this.rpc.fetchWithRetries<EngineApiRpcReturnTypes[typeof method], EngineApiRpcParamTypes[typeof method]>(
157
- {method, params},
158
- methodOpts
159
- );
160
- };
151
+ private readonly jsonRpc: IEngineTransport;
152
+ private readonly rest: RestEngineTransport | null;
153
+ private readonly engineApi: EngineApiMode;
154
+ private restSupport: RestSupport = {state: "pending"};
155
+ private restProbe: Promise<RestSupport> | null = null;
156
+ private lastRestProbeMs = Number.NEGATIVE_INFINITY;
157
+ private readonly loggedRestFallbacks = new Set<ForkName | "v1" | "v2">();
161
158
 
162
159
  constructor(
163
- private readonly rpc: IJsonRpcHttpClient,
160
+ {jsonRpc, rest}: ExecutionEngineTransports,
164
161
  {metrics, signal, logger}: ExecutionEngineModules,
165
162
  private readonly opts?: ExecutionEngineHttpOpts
166
163
  ) {
167
- this.rpcFetchQueue = new JobItemQueue<[EngineRequest], EngineResponse>(
168
- this.jobQueueProcessor,
164
+ this.queue = new JobItemQueue<[() => Promise<unknown>], unknown>(
165
+ (job) => job(),
169
166
  {maxLength: QUEUE_MAX_LENGTH, maxConcurrency: 1, noYieldIfOneItem: true, signal},
170
167
  metrics?.engineHttpProcessorQueue
171
168
  );
172
169
  this.logger = logger;
173
170
  this.metrics = metrics ?? null;
171
+ this.jsonRpc = jsonRpc;
172
+ this.rest = rest ?? null;
173
+ this.engineApi = opts?.engineApi ?? "auto";
174
174
 
175
- this.rpc.emitter.on(JsonRpcHttpClientEvent.ERROR, ({error}) => {
176
- this.updateEngineState(getExecutionEngineState({payloadError: error, oldState: this.state}));
177
- });
175
+ if (this.engineApi === "ssz" && this.rest === null) {
176
+ throw Error("REST transport is required for engineApi=ssz");
177
+ }
178
178
 
179
- this.rpc.emitter.on(JsonRpcHttpClientEvent.RESPONSE, () => {
180
- if (this.clientVersion === undefined) {
181
- this.clientVersion = null;
182
- // This statement should only be called first time receiving response after startup
183
- this.getClientVersion(getLodestarClientVersion(this.opts)).catch((e) => {
184
- this.logger.debug("Unable to get execution client version", {}, e);
185
- });
186
- }
187
- this.updateEngineState(getExecutionEngineState({targetState: ExecutionEngineState.ONLINE, oldState: this.state}));
179
+ // REST errors are handled in withTransport after compatibility fallback.
180
+ this.jsonRpc.emitter.on(JsonRpcHttpClientEvent.ERROR, ({error}) => {
181
+ this.updateEngineState(getExecutionEngineState({payloadError: error, oldState: this.state}), error);
188
182
  });
183
+
184
+ for (const transport of [this.jsonRpc, this.rest]) {
185
+ transport?.emitter.on(JsonRpcHttpClientEvent.RESPONSE, () => {
186
+ if (this.clientVersion === undefined) {
187
+ this.clientVersion = null;
188
+ // This statement should only be called first time receiving response after startup
189
+ this.getClientVersion(getLodestarClientVersion(this.opts)).catch((e) => {
190
+ this.logger.debug("Unable to get execution client version", {}, e);
191
+ });
192
+ }
193
+ this.updateEngineState(
194
+ getExecutionEngineState({targetState: ExecutionEngineState.ONLINE, oldState: this.state})
195
+ );
196
+ });
197
+ }
189
198
  }
190
199
 
191
200
  /**
@@ -217,20 +226,7 @@ export class ExecutionEngineHttp implements IExecutionEngine {
217
226
  parentBlockRoot?: Root,
218
227
  executionRequests?: ExecutionRequests
219
228
  ): Promise<ExecutePayloadResponse> {
220
- const method =
221
- ForkSeq[fork] >= ForkSeq.gloas
222
- ? "engine_newPayloadV5"
223
- : ForkSeq[fork] >= ForkSeq.electra
224
- ? "engine_newPayloadV4"
225
- : ForkSeq[fork] >= ForkSeq.deneb
226
- ? "engine_newPayloadV3"
227
- : ForkSeq[fork] >= ForkSeq.capella
228
- ? "engine_newPayloadV2"
229
- : "engine_newPayloadV1";
230
-
231
- const serializedExecutionPayload = serializeExecutionPayload(fork, executionPayload);
232
-
233
- let engineRequest: EngineRequest;
229
+ // Validate before queueing, argument errors must not be reported as an unavailable execution client
234
230
  if (ForkSeq[fork] >= ForkSeq.deneb) {
235
231
  if (versionedHashes === undefined) {
236
232
  throw Error(`versionedHashes required in notifyNewPayload for fork=${fork}`);
@@ -238,49 +234,31 @@ export class ExecutionEngineHttp implements IExecutionEngine {
238
234
  if (parentBlockRoot === undefined) {
239
235
  throw Error(`parentBlockRoot required in notifyNewPayload for fork=${fork}`);
240
236
  }
241
-
242
- const serializedVersionedHashes = serializeVersionedHashes(versionedHashes);
243
- const parentBeaconBlockRoot = serializeBeaconBlockRoot(parentBlockRoot);
244
-
245
- if (ForkSeq[fork] >= ForkSeq.electra) {
246
- if (executionRequests === undefined) {
247
- throw Error(`executionRequests required in notifyNewPayload for fork=${fork}`);
248
- }
249
- const serializedExecutionRequests = serializeExecutionRequests(fork, executionRequests);
250
- engineRequest = {
251
- method: ForkSeq[fork] >= ForkSeq.gloas ? "engine_newPayloadV5" : "engine_newPayloadV4",
252
- params: [
253
- serializedExecutionPayload,
254
- serializedVersionedHashes,
255
- parentBeaconBlockRoot,
256
- serializedExecutionRequests,
257
- ],
258
- methodOpts: notifyNewPayloadOpts,
259
- };
260
- } else {
261
- engineRequest = {
262
- method: "engine_newPayloadV3",
263
- params: [serializedExecutionPayload, serializedVersionedHashes, parentBeaconBlockRoot],
264
- methodOpts: notifyNewPayloadOpts,
265
- };
237
+ if (ForkSeq[fork] >= ForkSeq.electra && executionRequests === undefined) {
238
+ throw Error(`executionRequests required in notifyNewPayload for fork=${fork}`);
266
239
  }
267
- } else {
268
- const method = ForkSeq[fork] >= ForkSeq.capella ? "engine_newPayloadV2" : "engine_newPayloadV1";
269
- engineRequest = {
270
- method,
271
- params: [serializedExecutionPayload],
272
- methodOpts: notifyNewPayloadOpts,
273
- };
274
240
  }
275
241
 
276
- const {status, latestValidHash, validationError} = await (
277
- this.rpcFetchQueue.push(engineRequest) as Promise<EngineApiRpcReturnTypes[typeof method]>
278
- ).catch((e: Error) => {
279
- if (e instanceof HttpRpcError || e instanceof ErrorJsonRpcResponse) {
280
- return {status: ExecutionPayloadStatus.ELERROR, latestValidHash: null, validationError: e.message};
281
- }
282
- return {status: ExecutionPayloadStatus.UNAVAILABLE, latestValidHash: null, validationError: e.message};
283
- });
242
+ let result: PayloadStatusResult;
243
+ try {
244
+ result = await this.enqueue(() =>
245
+ this.withTransport(fork, undefined, (transport) =>
246
+ transport.newPayload(fork, executionPayload, versionedHashes, parentBlockRoot, executionRequests)
247
+ )
248
+ );
249
+ } catch (e) {
250
+ const status = isEngineResponseError(e as Error)
251
+ ? ExecutionPayloadStatus.ELERROR
252
+ : ExecutionPayloadStatus.UNAVAILABLE;
253
+ // Only newPayload treats an unreachable execution client, including a timeout, as offline
254
+ const newState =
255
+ status === ExecutionPayloadStatus.UNAVAILABLE && !isErrorAborted(e) && !isQueueErrorAborted(e)
256
+ ? getExecutionEngineState({payloadStatus: status, oldState: this.state})
257
+ : getExecutionEngineState({payloadError: e, oldState: this.state});
258
+ this.updateEngineState(newState, e as Error);
259
+ return {status, latestValidHash: null, validationError: (e as Error).message};
260
+ }
261
+ const {status, latestValidHash, validationError} = result;
284
262
 
285
263
  this.updateEngineState(getExecutionEngineState({payloadStatus: status, oldState: this.state}));
286
264
 
@@ -349,32 +327,14 @@ export class ExecutionEngineHttp implements IExecutionEngine {
349
327
  finalizedBlockHash: RootHex,
350
328
  payloadAttributes?: PayloadAttributes
351
329
  ): Promise<PayloadId | null> {
352
- // Once on capella, should this need to be permanently switched to v2 when payload attrs
353
- // not provided
354
- const method =
355
- ForkSeq[fork] >= ForkSeq.gloas
356
- ? "engine_forkchoiceUpdatedV4"
357
- : ForkSeq[fork] >= ForkSeq.deneb
358
- ? "engine_forkchoiceUpdatedV3"
359
- : ForkSeq[fork] >= ForkSeq.capella
360
- ? "engine_forkchoiceUpdatedV2"
361
- : "engine_forkchoiceUpdatedV1";
362
- const payloadAttributesRpc = payloadAttributes ? serializePayloadAttributes(payloadAttributes) : undefined;
363
- // If we are just fcUing and not asking execution for payload, retry is not required
364
- // and we can move on, as the next fcU will be issued soon on the new slot
365
- const fcUReqOpts =
366
- payloadAttributes !== undefined ? forkchoiceUpdatedV1Opts : {...forkchoiceUpdatedV1Opts, retries: 0};
367
-
368
- const request = this.rpcFetchQueue.push({
369
- method,
370
- params: [{headBlockHash, safeBlockHash, finalizedBlockHash}, payloadAttributesRpc],
371
- methodOpts: fcUReqOpts,
372
- }) as Promise<EngineApiRpcReturnTypes[typeof method]>;
373
-
374
330
  const {
375
- payloadStatus: {status, latestValidHash: _latestValidHash, validationError},
331
+ payloadStatus: {status, validationError},
376
332
  payloadId,
377
- } = await request;
333
+ } = await this.enqueue(() =>
334
+ this.withTransport(fork, undefined, (transport) =>
335
+ transport.forkchoiceUpdated(fork, headBlockHash, safeBlockHash, finalizedBlockHash, payloadAttributes)
336
+ )
337
+ );
378
338
 
379
339
  this.updateEngineState(getExecutionEngineState({payloadStatus: status, oldState: this.state}));
380
340
  this.metrics?.engineNotifyForkchoiceUpdateResult.inc({result: status});
@@ -382,15 +342,16 @@ export class ExecutionEngineHttp implements IExecutionEngine {
382
342
  switch (status) {
383
343
  case ExecutionPayloadStatus.VALID:
384
344
  // if payloadAttributes are provided, a valid payloadId is expected
385
- if (payloadAttributesRpc) {
386
- if (!payloadId || payloadId === "0x") {
345
+ if (payloadAttributes) {
346
+ if (payloadId === null) {
387
347
  throw Error(`Received invalid payloadId=${payloadId}`);
388
348
  }
389
349
 
350
+ const payloadAttributesRpc = serializePayloadAttributes(payloadAttributes);
390
351
  this.payloadIdCache.add({headBlockHash, finalizedBlockHash, ...payloadAttributesRpc}, payloadId);
391
352
  void this.prunePayloadIdCache();
392
353
  }
393
- return payloadId !== "0x" ? payloadId : null;
354
+ return payloadId;
394
355
 
395
356
  case ExecutionPayloadStatus.SYNCING:
396
357
  // Throw error on syncing if requested to produce a block, else silently ignore
@@ -428,80 +389,17 @@ export class ExecutionEngineHttp implements IExecutionEngine {
428
389
  executionRequests?: ExecutionRequests;
429
390
  shouldOverrideBuilder?: boolean;
430
391
  }> {
431
- let method: keyof EngineApiRpcReturnTypes;
432
- switch (fork) {
433
- case ForkName.phase0:
434
- case ForkName.altair:
435
- case ForkName.bellatrix:
436
- method = "engine_getPayloadV1";
437
- break;
438
- case ForkName.capella:
439
- method = "engine_getPayloadV2";
440
- break;
441
- case ForkName.deneb:
442
- method = "engine_getPayloadV3";
443
- break;
444
- case ForkName.electra:
445
- method = "engine_getPayloadV4";
446
- break;
447
- case ForkName.fulu:
448
- method = "engine_getPayloadV5";
449
- break;
450
- default:
451
- method = "engine_getPayloadV6";
452
- break;
453
- }
454
- const payloadResponse = await this.rpc.fetchWithRetries<
455
- EngineApiRpcReturnTypes[typeof method],
456
- EngineApiRpcParamTypes[typeof method]
457
- >(
458
- {
459
- method,
460
- params: [payloadId],
461
- },
462
- getPayloadOpts
463
- );
464
- return parseExecutionPayload(fork, payloadResponse);
392
+ return this.withTransport(fork, undefined, (transport) => transport.getPayload(fork, payloadId));
465
393
  }
466
394
 
467
395
  async prunePayloadIdCache(): Promise<void> {
468
396
  this.payloadIdCache.prune();
469
397
  }
470
398
 
471
- async getPayloadBodiesByHash(_fork: ForkName, blockHashes: RootHex[]): Promise<(ExecutionPayloadBody | null)[]> {
472
- const method = "engine_getPayloadBodiesByHashV1";
473
- assertReqSizeLimit(blockHashes.length, 32);
474
- const response = await this.rpc.fetchWithRetries<
475
- EngineApiRpcReturnTypes[typeof method],
476
- EngineApiRpcParamTypes[typeof method]
477
- >({method, params: [blockHashes]}, getPayloadBodiesByHashOpts);
478
- return response.map(deserializeExecutionPayloadBody);
479
- }
480
-
481
399
  async getPayloadBodiesByHashV2(blockHashes: RootHex[]): Promise<(ExecutionPayloadBodyV2 | null)[]> {
482
- const method = "engine_getPayloadBodiesByHashV2";
483
- assertReqSizeLimit(blockHashes.length, 32);
484
- const response = await this.rpc.fetchWithRetries<
485
- EngineApiRpcReturnTypes[typeof method],
486
- EngineApiRpcParamTypes[typeof method]
487
- >({method, params: [blockHashes]}, getPayloadBodiesByHashOpts);
488
- return response.map(deserializeExecutionPayloadBodyV2);
489
- }
490
-
491
- async getPayloadBodiesByRange(
492
- _fork: ForkName,
493
- startBlockNumber: number,
494
- blockCount: number
495
- ): Promise<(ExecutionPayloadBody | null)[]> {
496
- const method = "engine_getPayloadBodiesByRangeV1";
497
- assertReqSizeLimit(blockCount, 32);
498
- const start = numToQuantity(startBlockNumber);
499
- const count = numToQuantity(blockCount);
500
- const response = await this.rpc.fetchWithRetries<
501
- EngineApiRpcReturnTypes[typeof method],
502
- EngineApiRpcParamTypes[typeof method]
503
- >({method, params: [start, count]}, getPayloadBodiesByRangeOpts);
504
- return response.map(deserializeExecutionPayloadBody);
400
+ return this.withTransport(ForkName.gloas, undefined, (transport) =>
401
+ transport.getPayloadBodiesByHashV2(blockHashes)
402
+ );
505
403
  }
506
404
 
507
405
  async getBlobs(
@@ -519,95 +417,168 @@ export class ExecutionEngineHttp implements IExecutionEngine {
519
417
  versionedHashes: VersionedHashes,
520
418
  buffers?: Uint8Array[]
521
419
  ): Promise<BlobAndProofV2[] | (BlobAndProof | null)[] | null> {
522
- assertReqSizeLimit(versionedHashes.length, MAX_VERSIONED_HASHES);
523
- const versionedHashesHex = versionedHashes.map(bytesToData);
524
420
  if (isForkPostFulu(fork)) {
525
- return await this.getBlobsV2(versionedHashesHex, buffers);
421
+ return this.withTransport(undefined, "v2", (transport) => transport.getBlobsV2(versionedHashes, buffers));
526
422
  }
527
- return await this.getBlobsV1(versionedHashesHex);
423
+ return this.withTransport(undefined, "v1", (transport) => transport.getBlobsV1(versionedHashes));
528
424
  }
529
425
 
530
- private async getBlobsV1(versionedHashesHex: string[]) {
531
- const response = await this.rpc.fetchWithRetries<
532
- EngineApiRpcReturnTypes["engine_getBlobsV1"],
533
- EngineApiRpcParamTypes["engine_getBlobsV1"]
534
- >(
535
- {
536
- method: "engine_getBlobsV1",
537
- params: [versionedHashesHex],
538
- },
539
- getBlobsV1Opts
540
- );
541
-
542
- const invalidLength = response.length !== versionedHashesHex.length;
426
+ private enqueue<T>(job: () => Promise<T>): Promise<T> {
427
+ return this.queue.push(job) as Promise<T>;
428
+ }
543
429
 
544
- if (invalidLength) {
545
- const error = `Invalid engine_getBlobsV1 response length=${response.length} versionedHashes=${versionedHashesHex.length}`;
546
- this.logger.error(error);
547
- throw Error(error);
430
+ /**
431
+ * Run a call on the selected transport. An execution client that advertises a fork in its
432
+ * capabilities but rejects it with `unsupported-fork` is not spec compliant, keep the node
433
+ * functional by serving that fork over JSON-RPC from then on.
434
+ */
435
+ private async withTransport<T>(
436
+ fork: ForkName | undefined,
437
+ blobsRevision: "v1" | "v2" | undefined,
438
+ fn: (transport: IEngineTransport) => Promise<T>
439
+ ): Promise<T> {
440
+ const transport = await this.getTransport(fork, blobsRevision);
441
+ this.metrics?.engineApiRequests.inc({transport: transport === this.rest ? "ssz" : "json-rpc"});
442
+ try {
443
+ return await fn(transport);
444
+ } catch (e) {
445
+ if (fork !== undefined && transport === this.rest && this.engineApi === "auto" && isUnsupportedForkError(e)) {
446
+ this.disableRestForFork(fork, e);
447
+ this.metrics?.engineApiRequests.inc({transport: "json-rpc"});
448
+ return fn(this.jsonRpc);
449
+ }
450
+ if (transport === this.rest) {
451
+ this.updateEngineState(getExecutionEngineState({payloadError: e, oldState: this.state}), e as Error);
452
+ }
453
+ throw e;
548
454
  }
455
+ }
549
456
 
550
- return response.map(deserializeBlobAndProofs);
457
+ private disableRestForFork(fork: ForkName, e: EngineRestError): void {
458
+ const executionFork = isForkPostBellatrix(fork) ? executionForkName[fork] : null;
459
+ if (this.restSupport.state === "supported" && executionFork !== null) {
460
+ this.restSupport.capabilities.supportedForks.delete(executionFork);
461
+ }
462
+ if (!this.loggedRestFallbacks.has(fork)) {
463
+ this.loggedRestFallbacks.add(fork);
464
+ this.logger.debug("REST engine API rejected an advertised fork, using JSON-RPC until reconnect", {
465
+ fork,
466
+ executionFork,
467
+ status: e.status,
468
+ type: e.type,
469
+ detail: e.detail,
470
+ });
471
+ }
551
472
  }
552
473
 
553
- private async getBlobsV2(versionedHashesHex: string[], buffers?: Uint8Array[]) {
554
- if (buffers) {
555
- // Callers preallocate one buffer per max blobs of the epoch, only the first entries are used
556
- if (buffers.length < versionedHashesHex.length) {
557
- throw Error(`Not enough buffers length=${buffers.length} versionedHashes=${versionedHashesHex.length}`);
558
- }
474
+ /**
475
+ * Pick the transport for a call. Fork-scoped calls only go over REST if the execution client
476
+ * advertises that fork, blob requests only if it serves the needed `/blobs/vN` revision.
477
+ */
478
+ private async getTransport(fork?: ForkName, blobsRevision?: "v1" | "v2"): Promise<IEngineTransport> {
479
+ if (this.rest === null || this.engineApi === "json-rpc") {
480
+ return this.jsonRpc;
481
+ }
482
+ const restSupport = await this.probeRestSupport(this.rest);
483
+ if (restSupport.state === "pending" && restSupport.error) {
484
+ this.updateEngineState(
485
+ getExecutionEngineState({payloadError: restSupport.error, oldState: this.state}),
486
+ restSupport.error
487
+ );
488
+ throw restSupport.error;
489
+ }
490
+ if (this.engineApi === "ssz") {
491
+ return this.rest;
492
+ }
493
+ if (restSupport.state !== "supported") {
494
+ return this.jsonRpc;
495
+ }
559
496
 
560
- for (const [i, buffer] of buffers.entries()) {
561
- if (buffer.length !== BLOB_AND_PROOF_V2_RPC_BYTES) {
562
- throw Error(`Invalid buffer[${i}] length=${buffer.length} expected=${BLOB_AND_PROOF_V2_RPC_BYTES}`);
497
+ if (fork !== undefined) {
498
+ const executionFork = isForkPostBellatrix(fork) ? executionForkName[fork] : null;
499
+ if (executionFork === null || !restSupport.capabilities.supportedForks.has(executionFork)) {
500
+ if (!this.loggedRestFallbacks.has(fork)) {
501
+ this.loggedRestFallbacks.add(fork);
502
+ this.logger.debug("Using JSON-RPC for a fork not advertised by the REST engine API", {fork, executionFork});
563
503
  }
504
+ return this.jsonRpc;
564
505
  }
565
506
  }
507
+ if (blobsRevision !== undefined && !restSupport.capabilities.blobsRevisions.has(blobsRevision)) {
508
+ if (!this.loggedRestFallbacks.has(blobsRevision)) {
509
+ this.loggedRestFallbacks.add(blobsRevision);
510
+ this.logger.debug("Using JSON-RPC for a blob revision not advertised by the REST engine API", {blobsRevision});
511
+ }
512
+ return this.jsonRpc;
513
+ }
566
514
 
567
- const response = await this.rpc.fetchWithRetries<
568
- EngineApiRpcReturnTypes["engine_getBlobsV2"],
569
- EngineApiRpcParamTypes["engine_getBlobsV2"]
570
- >(
571
- {
572
- method: "engine_getBlobsV2",
573
- params: [versionedHashesHex],
574
- },
575
- getBlobsV2Opts
576
- );
577
-
578
- // engine_getBlobsV2 does not return partial responses. It returns null if any blob is not found
579
- const invalidLength = !!response && response.length !== versionedHashesHex.length;
515
+ return this.rest;
516
+ }
580
517
 
581
- if (invalidLength) {
582
- const error = `Invalid engine_getBlobsV2 response length=${response?.length ?? "null"} versionedHashes=${versionedHashesHex.length}`;
583
- this.logger.error(error);
584
- throw Error(error);
518
+ private probeRestSupport(rest: RestEngineTransport): Promise<RestSupport> {
519
+ if (this.restSupport.state !== "pending") {
520
+ return Promise.resolve(this.restSupport);
585
521
  }
586
-
587
- if (response == null) {
588
- return null;
522
+ if (this.restProbe !== null) {
523
+ return this.restProbe;
589
524
  }
590
-
591
- if (buffers) {
592
- // getBlobsV2() is designed to called once per slot so we expect to have buffers
593
- return response.map((data, i) => deserializeBlobAndProofsV2IntoBytes(data, buffers[i]));
525
+ // Transport errors keep the probe pending, do not retry on every call while the EL is down
526
+ if (Date.now() - this.lastRestProbeMs < REST_PROBE_RETRY_MS) {
527
+ return Promise.resolve(this.restSupport);
594
528
  }
595
529
 
596
- return response.map(deserializeBlobAndProofsV2);
530
+ this.restProbe = rest
531
+ .getCapabilities()
532
+ .then(
533
+ (capabilities): RestSupport => {
534
+ this.restSupport = {state: "supported", capabilities};
535
+ this.loggedRestFallbacks.clear();
536
+ this.logger.debug("Discovered REST engine API capabilities", {
537
+ supportedForks: Array.from(capabilities.supportedForks).join(","),
538
+ blobsRevisions: Array.from(capabilities.blobsRevisions).join(","),
539
+ ...capabilities.limits,
540
+ });
541
+ return this.restSupport;
542
+ },
543
+ (e: Error): RestSupport => {
544
+ if (this.engineApi === "auto" && isRestApiAbsent(e)) {
545
+ this.restSupport = {state: "unsupported"};
546
+ this.logger.debug("Execution client does not support engine API over REST, using JSON-RPC", {
547
+ status: e.status,
548
+ type: e.type ?? "unknown",
549
+ });
550
+ } else if (this.engineApi === "auto" && e instanceof EngineRestResponseError) {
551
+ // The REST API answered but cannot be trusted, auto mode must keep the node on a working transport
552
+ this.restSupport = {state: "unsupported"};
553
+ this.logger.warn("Invalid engine API capabilities, using JSON-RPC until reconnect", {}, e);
554
+ } else {
555
+ const transient = isRetryableEngineRestError(e);
556
+ this.restSupport = {state: "pending", error: this.engineApi === "auto" && transient ? undefined : e};
557
+ this.logger.debug(
558
+ "Unable to probe engine API capabilities",
559
+ {
560
+ engineApi: this.engineApi,
561
+ fallback: this.restSupport.error ? "none" : "json-rpc",
562
+ retryAfterMs: REST_PROBE_RETRY_MS,
563
+ },
564
+ e
565
+ );
566
+ }
567
+ return this.restSupport;
568
+ }
569
+ )
570
+ .finally(() => {
571
+ this.restProbe = null;
572
+ this.lastRestProbeMs = Date.now();
573
+ });
574
+
575
+ return this.restProbe;
597
576
  }
598
577
 
599
578
  private async getClientVersion(clientVersion: ClientVersion): Promise<ClientVersion[]> {
600
- const method = "engine_getClientVersionV1";
601
-
602
- const response = await this.rpc.fetchWithRetries<
603
- EngineApiRpcReturnTypes[typeof method],
604
- EngineApiRpcParamTypes[typeof method]
605
- >({method, params: [{...clientVersion, commit: `0x${clientVersion.commit}`}]}, getClientVersionOpts);
606
-
607
- const clientVersions = response.map((cv) => {
608
- const code = cv.code in ClientCode ? ClientCode[cv.code as keyof typeof ClientCode] : ClientCode.XX;
609
- return {code, name: cv.name, version: cv.version, commit: strip0xPrefix(cv.commit)};
610
- });
579
+ const clientVersions = await this.withTransport(undefined, undefined, (transport) =>
580
+ transport.getClientVersion(clientVersion)
581
+ );
611
582
 
612
583
  if (clientVersions.length === 0) {
613
584
  throw Error("Received empty client versions array");
@@ -619,7 +590,7 @@ export class ExecutionEngineHttp implements IExecutionEngine {
619
590
  return clientVersions;
620
591
  }
621
592
 
622
- private updateEngineState(newState: ExecutionEngineState): void {
593
+ private updateEngineState(newState: ExecutionEngineState, error?: Error): void {
623
594
  const oldState = this.state;
624
595
 
625
596
  if (oldState === newState) return;
@@ -627,22 +598,33 @@ export class ExecutionEngineHttp implements IExecutionEngine {
627
598
  switch (newState) {
628
599
  case ExecutionEngineState.ONLINE:
629
600
  this.logger.info("Execution client became online", {oldState, newState});
601
+ if (oldState === ExecutionEngineState.AUTH_FAILED) {
602
+ this.restSupport = {state: "pending"};
603
+ this.lastRestProbeMs = Number.NEGATIVE_INFINITY;
604
+ }
630
605
  this.getClientVersion(getLodestarClientVersion(this.opts)).catch((e) => {
631
606
  this.logger.debug("Unable to get execution client version", {}, e);
632
607
  this.clientVersion = null;
633
608
  });
634
609
  break;
635
610
  case ExecutionEngineState.OFFLINE:
636
- this.logger.error("Execution client went offline", {oldState, newState});
611
+ // Reprobe before choosing a transport for the next call after a disconnect.
612
+ this.restSupport = {state: "pending"};
613
+ this.lastRestProbeMs = Number.NEGATIVE_INFINITY;
614
+ this.logger.error("Execution client went offline", {oldState, newState}, error);
637
615
  break;
638
616
  case ExecutionEngineState.SYNCED:
639
617
  this.logger.info("Execution client is synced", {oldState, newState});
640
618
  break;
641
619
  case ExecutionEngineState.SYNCING:
642
- this.logger.warn("Execution client is syncing", {oldState, newState});
620
+ this.logger.warn(
621
+ error ? "Execution client request failed" : "Execution client is syncing",
622
+ {oldState, newState},
623
+ error
624
+ );
643
625
  break;
644
626
  case ExecutionEngineState.AUTH_FAILED:
645
- this.logger.error("Execution client authentication failed", {oldState, newState});
627
+ this.logger.error("Execution client authentication failed", {oldState, newState}, error);
646
628
  break;
647
629
  }
648
630
 
@@ -650,10 +632,32 @@ export class ExecutionEngineHttp implements IExecutionEngine {
650
632
  }
651
633
  }
652
634
 
653
- type EngineRequestKey = keyof EngineApiRpcParamTypes;
654
- type EngineRequestByKey = {
655
- [K in EngineRequestKey]: {method: K; params: EngineApiRpcParamTypes[K]; methodOpts: ReqOpts};
656
- };
657
- type EngineRequest = EngineRequestByKey[EngineRequestKey];
658
- type EngineResponseByKey = {[K in EngineRequestKey]: EngineApiRpcReturnTypes[K]};
659
- type EngineResponse = EngineResponseByKey[EngineRequestKey];
635
+ /** The execution client answered, as opposed to being unreachable */
636
+ function isEngineResponseError(e: Error): boolean {
637
+ return (
638
+ e instanceof HttpRpcError ||
639
+ e instanceof ErrorJsonRpcResponse ||
640
+ e instanceof EngineRestError ||
641
+ e instanceof EngineRestResponseError
642
+ );
643
+ }
644
+
645
+ /**
646
+ * Legacy JSON-RPC servers and proxies reject the capabilities probe in different ways, a 404 or
647
+ * another client error such as 405. Neither serves the REST API. Authentication failures and
648
+ * transient client errors say nothing about REST support.
649
+ */
650
+ function isRestApiAbsent(e: Error): e is EngineRestError {
651
+ return (
652
+ e instanceof EngineRestError &&
653
+ e.status >= 400 &&
654
+ e.status < 500 &&
655
+ e.status !== 401 &&
656
+ e.status !== 403 &&
657
+ !isRetryableEngineRestError(e)
658
+ );
659
+ }
660
+
661
+ function isUnsupportedForkError(e: unknown): e is EngineRestError {
662
+ return e instanceof EngineRestError && e.status === 400 && e.type === "/engine-api/errors/unsupported-fork";
663
+ }