@lodestar/beacon-node 1.47.0-dev.2a04194b90 → 1.47.0-dev.2fba076c28

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 (317) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +6 -8
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/pool/index.js +25 -2
  6. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  7. package/lib/api/impl/beacon/state/utils.d.ts +2 -1
  8. package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
  9. package/lib/api/impl/beacon/state/utils.js.map +1 -1
  10. package/lib/api/impl/events/index.d.ts +1 -1
  11. package/lib/api/impl/events/index.d.ts.map +1 -1
  12. package/lib/api/impl/events/index.js +12 -3
  13. package/lib/api/impl/events/index.js.map +1 -1
  14. package/lib/api/impl/lodestar/index.js +1 -1
  15. package/lib/api/impl/lodestar/index.js.map +1 -1
  16. package/lib/api/impl/validator/index.js +1 -1
  17. package/lib/api/impl/validator/index.js.map +1 -1
  18. package/lib/api/rest/base.d.ts.map +1 -1
  19. package/lib/api/rest/base.js +7 -0
  20. package/lib/api/rest/base.js.map +1 -1
  21. package/lib/chain/archiveStore/archiveStore.js +1 -1
  22. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  23. package/lib/chain/archiveStore/historicalState/metrics.d.ts.map +1 -1
  24. package/lib/chain/archiveStore/historicalState/metrics.js +21 -0
  25. package/lib/chain/archiveStore/historicalState/metrics.js.map +1 -1
  26. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  27. package/lib/chain/blocks/importBlock.js +2 -7
  28. package/lib/chain/blocks/importBlock.js.map +1 -1
  29. package/lib/chain/blocks/importExecutionPayload.d.ts +3 -2
  30. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  31. package/lib/chain/blocks/importExecutionPayload.js +11 -9
  32. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  33. package/lib/chain/blocks/index.d.ts.map +1 -1
  34. package/lib/chain/blocks/index.js +14 -6
  35. package/lib/chain/blocks/index.js.map +1 -1
  36. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +2 -1
  37. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  38. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +6 -2
  39. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  40. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +14 -5
  41. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  42. package/lib/chain/blocks/payloadEnvelopeInput/types.js +5 -0
  43. package/lib/chain/blocks/payloadEnvelopeInput/types.js.map +1 -1
  44. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
  45. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +22 -12
  46. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
  47. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +2 -2
  48. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  49. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  50. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  51. package/lib/chain/bls/index.d.ts +1 -1
  52. package/lib/chain/bls/index.d.ts.map +1 -1
  53. package/lib/chain/bls/interface.d.ts +5 -5
  54. package/lib/chain/bls/interface.d.ts.map +1 -1
  55. package/lib/chain/bls/multithread/index.d.ts +5 -11
  56. package/lib/chain/bls/multithread/index.d.ts.map +1 -1
  57. package/lib/chain/bls/multithread/index.js +116 -101
  58. package/lib/chain/bls/multithread/index.js.map +1 -1
  59. package/lib/chain/bls/multithread/jobItem.d.ts +5 -19
  60. package/lib/chain/bls/multithread/jobItem.d.ts.map +1 -1
  61. package/lib/chain/bls/multithread/jobItem.js +10 -65
  62. package/lib/chain/bls/multithread/jobItem.js.map +1 -1
  63. package/lib/chain/bls/multithread/types.d.ts +29 -8
  64. package/lib/chain/bls/multithread/types.d.ts.map +1 -1
  65. package/lib/chain/bls/multithread/types.js +12 -0
  66. package/lib/chain/bls/multithread/types.js.map +1 -1
  67. package/lib/chain/bls/multithread/utils.d.ts +4 -0
  68. package/lib/chain/bls/multithread/utils.d.ts.map +1 -1
  69. package/lib/chain/bls/multithread/utils.js +10 -0
  70. package/lib/chain/bls/multithread/utils.js.map +1 -1
  71. package/lib/chain/bls/multithread/worker.js +49 -23
  72. package/lib/chain/bls/multithread/worker.js.map +1 -1
  73. package/lib/chain/bls/singleThread.d.ts +4 -10
  74. package/lib/chain/bls/singleThread.d.ts.map +1 -1
  75. package/lib/chain/bls/singleThread.js +8 -45
  76. package/lib/chain/bls/singleThread.js.map +1 -1
  77. package/lib/chain/bls/utils.d.ts +4 -4
  78. package/lib/chain/bls/utils.d.ts.map +1 -1
  79. package/lib/chain/bls/utils.js +18 -19
  80. package/lib/chain/bls/utils.js.map +1 -1
  81. package/lib/chain/builderCircuitBreaker.d.ts +6 -6
  82. package/lib/chain/builderCircuitBreaker.d.ts.map +1 -1
  83. package/lib/chain/builderCircuitBreaker.js +19 -17
  84. package/lib/chain/builderCircuitBreaker.js.map +1 -1
  85. package/lib/chain/chain.d.ts +4 -2
  86. package/lib/chain/chain.d.ts.map +1 -1
  87. package/lib/chain/chain.js +16 -6
  88. package/lib/chain/chain.js.map +1 -1
  89. package/lib/chain/errors/blockError.d.ts +1 -0
  90. package/lib/chain/errors/blockError.d.ts.map +1 -1
  91. package/lib/chain/errors/blockError.js +6 -1
  92. package/lib/chain/errors/blockError.js.map +1 -1
  93. package/lib/chain/errors/proposerPreferences.d.ts +6 -0
  94. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
  95. package/lib/chain/errors/proposerPreferences.js +1 -0
  96. package/lib/chain/errors/proposerPreferences.js.map +1 -1
  97. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  98. package/lib/chain/forkChoice/index.js +4 -0
  99. package/lib/chain/forkChoice/index.js.map +1 -1
  100. package/lib/chain/interface.d.ts +4 -2
  101. package/lib/chain/interface.d.ts.map +1 -1
  102. package/lib/chain/interface.js.map +1 -1
  103. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  104. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  105. package/lib/chain/opPools/attestationPool.d.ts +1 -1
  106. package/lib/chain/opPools/attestationPool.d.ts.map +1 -1
  107. package/lib/chain/opPools/attestationPool.js +1 -1
  108. package/lib/chain/opPools/attestationPool.js.map +1 -1
  109. package/lib/chain/opPools/deferredVoluntaryExitPool.d.ts +16 -0
  110. package/lib/chain/opPools/deferredVoluntaryExitPool.d.ts.map +1 -0
  111. package/lib/chain/opPools/deferredVoluntaryExitPool.js +57 -0
  112. package/lib/chain/opPools/deferredVoluntaryExitPool.js.map +1 -0
  113. package/lib/chain/opPools/index.d.ts +1 -0
  114. package/lib/chain/opPools/index.d.ts.map +1 -1
  115. package/lib/chain/opPools/index.js +1 -0
  116. package/lib/chain/opPools/index.js.map +1 -1
  117. package/lib/chain/opPools/payloadAttestationPool.js +3 -3
  118. package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
  119. package/lib/chain/opPools/syncCommitteeMessagePool.js +1 -1
  120. package/lib/chain/opPools/syncCommitteeMessagePool.js.map +1 -1
  121. package/lib/chain/opPools/syncContributionAndProofPool.js +1 -1
  122. package/lib/chain/opPools/syncContributionAndProofPool.js.map +1 -1
  123. package/lib/chain/opPools/utils.d.ts +1 -1
  124. package/lib/chain/opPools/utils.d.ts.map +1 -1
  125. package/lib/chain/opPools/utils.js +1 -1
  126. package/lib/chain/opPools/utils.js.map +1 -1
  127. package/lib/chain/options.d.ts +1 -1
  128. package/lib/chain/options.d.ts.map +1 -1
  129. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  130. package/lib/chain/prepareNextSlot.js +24 -6
  131. package/lib/chain/prepareNextSlot.js.map +1 -1
  132. package/lib/chain/regen/interface.d.ts +4 -2
  133. package/lib/chain/regen/interface.d.ts.map +1 -1
  134. package/lib/chain/regen/interface.js +2 -0
  135. package/lib/chain/regen/interface.js.map +1 -1
  136. package/lib/chain/regen/queued.d.ts +2 -2
  137. package/lib/chain/regen/queued.d.ts.map +1 -1
  138. package/lib/chain/regen/queued.js +5 -5
  139. package/lib/chain/regen/queued.js.map +1 -1
  140. package/lib/chain/seenCache/seenAttesters.d.ts +9 -5
  141. package/lib/chain/seenCache/seenAttesters.d.ts.map +1 -1
  142. package/lib/chain/seenCache/seenAttesters.js +25 -4
  143. package/lib/chain/seenCache/seenAttesters.js.map +1 -1
  144. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +28 -1
  145. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  146. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +121 -13
  147. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  148. package/lib/chain/stateCache/fifoBlockStateCache.d.ts +5 -1
  149. package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
  150. package/lib/chain/stateCache/fifoBlockStateCache.js +6 -0
  151. package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
  152. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +47 -5
  153. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  154. package/lib/chain/stateCache/persistentCheckpointsCache.js +117 -7
  155. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  156. package/lib/chain/stateCache/types.d.ts +3 -3
  157. package/lib/chain/stateCache/types.d.ts.map +1 -1
  158. package/lib/chain/validation/attestation.d.ts.map +1 -1
  159. package/lib/chain/validation/attestation.js +1 -4
  160. package/lib/chain/validation/attestation.js.map +1 -1
  161. package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
  162. package/lib/chain/validation/attesterSlashing.js +1 -1
  163. package/lib/chain/validation/attesterSlashing.js.map +1 -1
  164. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  165. package/lib/chain/validation/executionPayloadBid.js +1 -2
  166. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  167. package/lib/chain/validation/executionPayloadEnvelope.js +1 -1
  168. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  169. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  170. package/lib/chain/validation/payloadAttestationMessage.js +5 -14
  171. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  172. package/lib/chain/validation/proposerPreferences.d.ts +1 -1
  173. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  174. package/lib/chain/validation/proposerPreferences.js +13 -4
  175. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  176. package/lib/chain/validation/proposerSlashing.js +1 -1
  177. package/lib/chain/validation/proposerSlashing.js.map +1 -1
  178. package/lib/chain/validation/voluntaryExit.d.ts +9 -1
  179. package/lib/chain/validation/voluntaryExit.d.ts.map +1 -1
  180. package/lib/chain/validation/voluntaryExit.js +46 -1
  181. package/lib/chain/validation/voluntaryExit.js.map +1 -1
  182. package/lib/metrics/metrics/beacon.d.ts +3 -3
  183. package/lib/metrics/metrics/beacon.d.ts.map +1 -1
  184. package/lib/metrics/metrics/beacon.js +9 -9
  185. package/lib/metrics/metrics/beacon.js.map +1 -1
  186. package/lib/metrics/metrics/lodestar.d.ts +72 -22
  187. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  188. package/lib/metrics/metrics/lodestar.js +130 -52
  189. package/lib/metrics/metrics/lodestar.js.map +1 -1
  190. package/lib/network/gossip/topic.d.ts +13 -0
  191. package/lib/network/gossip/topic.d.ts.map +1 -1
  192. package/lib/network/gossip/topic.js +37 -0
  193. package/lib/network/gossip/topic.js.map +1 -1
  194. package/lib/network/libp2p/index.d.ts.map +1 -1
  195. package/lib/network/libp2p/index.js +10 -2
  196. package/lib/network/libp2p/index.js.map +1 -1
  197. package/lib/network/network.d.ts.map +1 -1
  198. package/lib/network/network.js +22 -43
  199. package/lib/network/network.js.map +1 -1
  200. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  201. package/lib/network/processor/gossipHandlers.js +12 -3
  202. package/lib/network/processor/gossipHandlers.js.map +1 -1
  203. package/lib/network/reqresp/score.d.ts.map +1 -1
  204. package/lib/network/reqresp/score.js +5 -11
  205. package/lib/network/reqresp/score.js.map +1 -1
  206. package/lib/network/util.d.ts +0 -1
  207. package/lib/network/util.d.ts.map +1 -1
  208. package/lib/network/util.js +0 -4
  209. package/lib/network/util.js.map +1 -1
  210. package/lib/node/deferredVoluntaryExitPublisher.d.ts +15 -0
  211. package/lib/node/deferredVoluntaryExitPublisher.d.ts.map +1 -0
  212. package/lib/node/deferredVoluntaryExitPublisher.js +38 -0
  213. package/lib/node/deferredVoluntaryExitPublisher.js.map +1 -0
  214. package/lib/node/nodejs.d.ts +2 -1
  215. package/lib/node/nodejs.d.ts.map +1 -1
  216. package/lib/node/nodejs.js +2 -0
  217. package/lib/node/nodejs.js.map +1 -1
  218. package/lib/sync/range/batch.d.ts +23 -5
  219. package/lib/sync/range/batch.d.ts.map +1 -1
  220. package/lib/sync/range/batch.js +61 -22
  221. package/lib/sync/range/batch.js.map +1 -1
  222. package/lib/sync/range/chain.d.ts +2 -1
  223. package/lib/sync/range/chain.d.ts.map +1 -1
  224. package/lib/sync/range/chain.js +23 -10
  225. package/lib/sync/range/chain.js.map +1 -1
  226. package/lib/sync/types.d.ts +32 -0
  227. package/lib/sync/types.d.ts.map +1 -1
  228. package/lib/sync/types.js +33 -0
  229. package/lib/sync/types.js.map +1 -1
  230. package/lib/sync/unknownBlock.d.ts +9 -0
  231. package/lib/sync/unknownBlock.d.ts.map +1 -1
  232. package/lib/sync/unknownBlock.js +173 -45
  233. package/lib/sync/unknownBlock.js.map +1 -1
  234. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  235. package/lib/sync/utils/downloadByRange.js +16 -11
  236. package/lib/sync/utils/downloadByRange.js.map +1 -1
  237. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  238. package/lib/sync/utils/downloadByRoot.js +3 -1
  239. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  240. package/package.json +35 -35
  241. package/src/api/impl/beacon/blocks/index.ts +7 -8
  242. package/src/api/impl/beacon/pool/index.ts +27 -2
  243. package/src/api/impl/beacon/state/utils.ts +2 -1
  244. package/src/api/impl/events/index.ts +12 -4
  245. package/src/api/impl/lodestar/index.ts +1 -1
  246. package/src/api/impl/validator/index.ts +1 -1
  247. package/src/api/rest/base.ts +7 -0
  248. package/src/chain/archiveStore/archiveStore.ts +1 -1
  249. package/src/chain/archiveStore/historicalState/metrics.ts +22 -0
  250. package/src/chain/blocks/importBlock.ts +1 -6
  251. package/src/chain/blocks/importExecutionPayload.ts +12 -11
  252. package/src/chain/blocks/index.ts +18 -6
  253. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +8 -2
  254. package/src/chain/blocks/payloadEnvelopeInput/types.ts +17 -4
  255. package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +25 -16
  256. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +1 -9
  257. package/src/chain/bls/index.ts +1 -1
  258. package/src/chain/bls/interface.ts +7 -3
  259. package/src/chain/bls/multithread/index.ts +148 -120
  260. package/src/chain/bls/multithread/jobItem.ts +12 -82
  261. package/src/chain/bls/multithread/types.ts +33 -8
  262. package/src/chain/bls/multithread/utils.ts +11 -0
  263. package/src/chain/bls/multithread/worker.ts +85 -27
  264. package/src/chain/bls/singleThread.ts +11 -55
  265. package/src/chain/bls/utils.ts +23 -24
  266. package/src/chain/builderCircuitBreaker.ts +22 -19
  267. package/src/chain/chain.ts +17 -6
  268. package/src/chain/errors/blockError.ts +8 -2
  269. package/src/chain/errors/proposerPreferences.ts +7 -0
  270. package/src/chain/forkChoice/index.ts +4 -0
  271. package/src/chain/interface.ts +4 -1
  272. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  273. package/src/chain/opPools/attestationPool.ts +1 -1
  274. package/src/chain/opPools/deferredVoluntaryExitPool.ts +65 -0
  275. package/src/chain/opPools/index.ts +1 -0
  276. package/src/chain/opPools/payloadAttestationPool.ts +3 -3
  277. package/src/chain/opPools/syncCommitteeMessagePool.ts +1 -1
  278. package/src/chain/opPools/syncContributionAndProofPool.ts +1 -1
  279. package/src/chain/opPools/utils.ts +1 -1
  280. package/src/chain/options.ts +1 -1
  281. package/src/chain/prepareNextSlot.ts +24 -6
  282. package/src/chain/regen/interface.ts +4 -2
  283. package/src/chain/regen/queued.ts +5 -5
  284. package/src/chain/seenCache/seenAttesters.ts +31 -5
  285. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +143 -14
  286. package/src/chain/stateCache/fifoBlockStateCache.ts +8 -1
  287. package/src/chain/stateCache/persistentCheckpointsCache.ts +133 -8
  288. package/src/chain/stateCache/types.ts +3 -3
  289. package/src/chain/validation/attestation.ts +1 -7
  290. package/src/chain/validation/attesterSlashing.ts +1 -8
  291. package/src/chain/validation/executionPayloadBid.ts +1 -2
  292. package/src/chain/validation/executionPayloadEnvelope.ts +0 -1
  293. package/src/chain/validation/payloadAttestationMessage.ts +6 -17
  294. package/src/chain/validation/proposerPreferences.ts +15 -5
  295. package/src/chain/validation/proposerSlashing.ts +1 -1
  296. package/src/chain/validation/voluntaryExit.ts +59 -2
  297. package/src/metrics/metrics/beacon.ts +9 -9
  298. package/src/metrics/metrics/lodestar.ts +142 -57
  299. package/src/network/gossip/topic.ts +38 -0
  300. package/src/network/libp2p/index.ts +11 -2
  301. package/src/network/network.ts +27 -55
  302. package/src/network/processor/gossipHandlers.ts +14 -1
  303. package/src/network/reqresp/score.ts +5 -11
  304. package/src/network/util.ts +0 -5
  305. package/src/node/deferredVoluntaryExitPublisher.ts +54 -0
  306. package/src/node/nodejs.ts +5 -1
  307. package/src/sync/range/batch.ts +83 -28
  308. package/src/sync/range/chain.ts +26 -11
  309. package/src/sync/types.ts +36 -1
  310. package/src/sync/unknownBlock.ts +197 -51
  311. package/src/sync/utils/downloadByRange.ts +21 -12
  312. package/src/sync/utils/downloadByRoot.ts +3 -1
  313. package/lib/chain/bls/maybeBatch.d.ts +0 -12
  314. package/lib/chain/bls/maybeBatch.d.ts.map +0 -1
  315. package/lib/chain/bls/maybeBatch.js +0 -35
  316. package/lib/chain/bls/maybeBatch.js.map +0 -1
  317. package/src/chain/bls/maybeBatch.ts +0 -45
package/src/sync/types.ts CHANGED
@@ -36,6 +36,39 @@ export enum PendingBlockInputStatus {
36
36
  processing = "processing",
37
37
  }
38
38
 
39
+ /**
40
+ * Why a pending item was dropped from BlockInputSync WITHOUT completing.
41
+ * The happy path — a successfully imported/processed block or payload — is removed by the import
42
+ * flow (onPayloadImported, processReadyBlock/processPayload success) and is deliberately NOT tracked
43
+ * here
44
+ */
45
+ export enum DroppedItemReason {
46
+ /** prune: item's own slot is below the finalized slot (routine cleanup), or a downloaded block landed at/below finality */
47
+ belowFinalized = "below_finalized",
48
+ /** prune: slot-less, unresolvable payload evicted after PRUNE_UNRESOLVED_SLOT_EPOCHS */
49
+ agedOut = "aged_out",
50
+ /** pruneSetToMax evicted the oldest entry because the cache hit its capacity (DoS guard) */
51
+ capacity = "capacity",
52
+ /** descendant removed because an ancestor was invalid/removed */
53
+ invalidParent = "invalid_parent",
54
+ /** transient execution-engine error removal (EXECUTION_ENGINE_ERROR) */
55
+ elError = "el_error",
56
+ /** the execution engine declared the item invalid (EXECUTION_ENGINE_INVALID) */
57
+ elInvalid = "el_invalid",
58
+ /** a block failed our own checks (not correct w.r.t. our chain) */
59
+ invalidBlock = "invalid_block",
60
+ /** a block declares a parent payload hash that conflicts with the parent's actual payload (gloas) */
61
+ invalidParentPayload = "invalid_parent_payload",
62
+ /** a payload envelope failed gossip verification (ENVELOPE_VERIFICATION_ERROR) */
63
+ invalidEnvelope = "invalid_envelope",
64
+ /** a payload had an invalid signature (INVALID_SIGNATURE) */
65
+ invalidSignature = "invalid_signature",
66
+ /** a block root was given up on after exhausting download attempts (data could not be fetched) */
67
+ unavailable = "unavailable",
68
+ /** an unexpected / unhandled error code led to removal */
69
+ unknown = "unknown",
70
+ }
71
+
39
72
  export enum PendingPayloadInputStatus {
40
73
  pending = "pending",
41
74
  fetching = "fetching",
@@ -77,7 +110,9 @@ export type PendingPayloadInput = {
77
110
  export type PendingPayloadRootHex = {
78
111
  status: PendingPayloadInputStatus.pending | PendingPayloadInputStatus.fetching;
79
112
  rootHex: RootHex;
80
- // message slot hint, may be missing when resolving a parent payload
113
+ // Trusted slot only (fork choice / validated data), may be missing until resolved. NOT the gossip
114
+ // message slot from ChainEvent.unknownEnvelopeBlockRoot, which is untrusted and not necessarily the
115
+ // payload/block slot. See BlockInputSync.resolvePayloadSlot.
81
116
  slot?: Slot;
82
117
  timeAddedSec: number;
83
118
  timeSyncedSec?: number;
@@ -1,6 +1,7 @@
1
1
  import {routes} from "@lodestar/api";
2
2
  import {ChainForkConfig} from "@lodestar/config";
3
- import {ForkSeq, isForkPostGloas} from "@lodestar/params";
3
+ import {CheckpointWithHex} from "@lodestar/fork-choice";
4
+ import {ForkSeq, SLOTS_PER_EPOCH, isForkPostGloas} from "@lodestar/params";
4
5
  import {RequestError, RequestErrorCode} from "@lodestar/reqresp";
5
6
  import {computeTimeAtSlot} from "@lodestar/state-transition";
6
7
  import {RootHex, Slot, gloas} from "@lodestar/types";
@@ -24,6 +25,7 @@ import {MAX_CONCURRENT_REQUESTS} from "./constants.js";
24
25
  import {SyncOptions} from "./options.js";
25
26
  import {
26
27
  BlockInputSyncCacheItem,
28
+ DroppedItemReason,
27
29
  PayloadSyncCacheItem,
28
30
  PendingBlockInput,
29
31
  PendingBlockInputStatus,
@@ -47,6 +49,10 @@ import {getRateLimitedUntilMs} from "./utils/rateLimit.js";
47
49
  const MAX_ATTEMPTS_PER_BLOCK = 5;
48
50
  const MAX_KNOWN_BAD_BLOCKS = 500;
49
51
  const MAX_PENDING_BLOCKS = 100;
52
+ /**
53
+ * A pending payload without slot can only stay at most 2 epochs.
54
+ */
55
+ const PRUNE_UNRESOLVED_SLOT_EPOCHS = 2;
50
56
 
51
57
  type AdvancePendingBlockResult =
52
58
  | "ready"
@@ -154,6 +160,7 @@ export class BlockInputSync {
154
160
  this.chain.emitter.on(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
155
161
  this.chain.emitter.on(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
156
162
  this.chain.emitter.on(ChainEvent.blockUnknownParent, this.onUnknownParent);
163
+ this.chain.emitter.on(ChainEvent.forkChoiceFinalized, this.pruneFinalized);
157
164
  this.chain.emitter.on(routes.events.EventType.block, this.onBlockImported);
158
165
  this.chain.emitter.on(routes.events.EventType.executionPayload, this.onPayloadImported);
159
166
  this.network.events.on(NetworkEvent.peerConnected, this.onPeerConnected);
@@ -170,6 +177,7 @@ export class BlockInputSync {
170
177
  this.chain.emitter.off(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
171
178
  this.chain.emitter.off(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
172
179
  this.chain.emitter.off(ChainEvent.blockUnknownParent, this.onUnknownParent);
180
+ this.chain.emitter.off(ChainEvent.forkChoiceFinalized, this.pruneFinalized);
173
181
  this.chain.emitter.off(routes.events.EventType.block, this.onBlockImported);
174
182
  this.chain.emitter.off(routes.events.EventType.executionPayload, this.onPayloadImported);
175
183
  this.network.events.off(NetworkEvent.peerConnected, this.onPeerConnected);
@@ -215,7 +223,7 @@ export class BlockInputSync {
215
223
 
216
224
  private onUnknownEnvelopeBlockRoot = (data: ChainEventData[ChainEvent.unknownEnvelopeBlockRoot]): void => {
217
225
  try {
218
- this.addByPayloadRootHex(data.rootHex, data.peer, data.slot);
226
+ this.addByPayloadRootHex(data.rootHex, data.peer, this.resolvePayloadSlot(data.rootHex));
219
227
  this.triggerUnknownBlockSearch();
220
228
  this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.UNKNOWN_PAYLOAD_BLOCK_ROOT});
221
229
  this.metrics?.blockInputSync.payloadSource.inc({source: data.source});
@@ -252,13 +260,13 @@ export class BlockInputSync {
252
260
  parentRoot: missingDependency.parentRootHex,
253
261
  parentBlockHash: missingDependency.parentBlockHashHex,
254
262
  });
255
- this.removeAndDownScoreAllDescendants(pendingBlock);
263
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidParentPayload);
256
264
  }
257
265
  return;
258
266
  }
259
267
 
260
268
  if (missingDependency.kind === "parentPayload") {
261
- this.addByPayloadRootHex(missingDependency.rootHex, data.peer);
269
+ this.addByPayloadRootHex(missingDependency.rootHex, data.peer, missingDependency.slot);
262
270
  } else if (missingDependency.kind === "parentBlock") {
263
271
  this.addByRootHex(missingDependency.rootHex, data.peer);
264
272
  }
@@ -284,6 +292,80 @@ export class BlockInputSync {
284
292
  this.triggerUnknownBlockSearch();
285
293
  };
286
294
 
295
+ /**
296
+ * Drop pending payloads and blocks that fell below the finalized slot.
297
+ * See https://github.com/ChainSafe/lodestar/issues/9800
298
+ */
299
+ private pruneFinalized = (checkpoint: CheckpointWithHex): void => {
300
+ // Use the actual finalized block slot, not computeStartSlotAtEpoch(checkpoint.epoch)
301
+ // because the payload at the finalized block is not finalized
302
+ const finalizedSlot = this.chain.forkChoice.getFinalizedBlock().slot;
303
+ const nowSec = Date.now() / 1000;
304
+ const maxSecInCache = (PRUNE_UNRESOLVED_SLOT_EPOCHS * SLOTS_PER_EPOCH * this.config.SLOT_DURATION_MS) / 1000;
305
+
306
+ let belowFinalizedPayloads = 0;
307
+ let agedOutPayloads = 0;
308
+ let unresolvedSlotPayloads = 0;
309
+
310
+ for (const [rootHex, payload] of this.pendingPayloads) {
311
+ // Last-effort resolve for a slot-less PendingPayloadRootHex, and store it back for later passes.
312
+ if (!isPendingPayloadInput(payload) && !isPendingPayloadEnvelope(payload) && payload.slot === undefined) {
313
+ payload.slot = this.resolvePayloadSlot(rootHex);
314
+ if (payload.slot === undefined) {
315
+ // Still unresolvable: age it out so the MAX_PENDING_BLOCKS cap cannot stay saturated.
316
+ if (nowSec - payload.timeAddedSec > maxSecInCache) {
317
+ this.pendingPayloads.delete(rootHex);
318
+ agedOutPayloads++;
319
+ } else {
320
+ unresolvedSlotPayloads++;
321
+ this.logger.debug("BlockInputSync.pruneFinalized: pending payload with unresolved slot", {
322
+ root: rootHex,
323
+ finalizedSlot,
324
+ });
325
+ }
326
+ continue;
327
+ }
328
+ }
329
+
330
+ const slot = getPayloadSyncCacheItemSlot(payload);
331
+ if (typeof slot === "number" && slot < finalizedSlot) {
332
+ this.pendingPayloads.delete(rootHex);
333
+ belowFinalizedPayloads++;
334
+ }
335
+ }
336
+
337
+ let deletedBlocks = 0;
338
+ for (const [rootHex, block] of this.pendingBlocks) {
339
+ const slot = getBlockInputSyncCacheItemSlot(block);
340
+ if (typeof slot === "number" && slot < finalizedSlot) {
341
+ this.pendingBlocks.delete(rootHex);
342
+ deletedBlocks++;
343
+ }
344
+ }
345
+
346
+ if (belowFinalizedPayloads > 0) {
347
+ this.metrics?.blockInputSync.removedPayloads.inc(
348
+ {reason: DroppedItemReason.belowFinalized},
349
+ belowFinalizedPayloads
350
+ );
351
+ }
352
+ if (agedOutPayloads > 0) {
353
+ this.metrics?.blockInputSync.removedPayloads.inc({reason: DroppedItemReason.agedOut}, agedOutPayloads);
354
+ }
355
+ if (deletedBlocks > 0) {
356
+ this.metrics?.blockInputSync.removedBlocks.inc({reason: DroppedItemReason.belowFinalized}, deletedBlocks);
357
+ }
358
+
359
+ this.logger.debug("BlockInputSync.pruneFinalized dropped pre-finalized pending items", {
360
+ finalizedSlot,
361
+ finalizedRoot: checkpoint.rootHex,
362
+ belowFinalizedPayloads,
363
+ agedOutPayloads,
364
+ deletedBlocks,
365
+ unresolvedSlotPayloads,
366
+ });
367
+ };
368
+
287
369
  private addByRootHex = (rootHex: RootHex, peerIdStr?: PeerIdStr): boolean => {
288
370
  let pendingBlock = this.pendingBlocks.get(rootHex);
289
371
  let added = false;
@@ -311,6 +393,7 @@ export class BlockInputSync {
311
393
  // Limit pending blocks to prevent DOS attacks that cause OOM
312
394
  const prunedItemCount = pruneSetToMax(this.pendingBlocks, this.maxPendingBlocks);
313
395
  if (prunedItemCount > 0) {
396
+ this.metrics?.blockInputSync.removedBlocks.inc({reason: DroppedItemReason.capacity}, prunedItemCount);
314
397
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingBlocks`);
315
398
  }
316
399
  return added;
@@ -345,6 +428,7 @@ export class BlockInputSync {
345
428
  // Limit pending blocks to prevent DOS attacks that cause OOM
346
429
  const prunedItemCount = pruneSetToMax(this.pendingBlocks, this.maxPendingBlocks);
347
430
  if (prunedItemCount > 0) {
431
+ this.metrics?.blockInputSync.removedBlocks.inc({reason: DroppedItemReason.capacity}, prunedItemCount);
348
432
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingBlocks`);
349
433
  }
350
434
  };
@@ -369,6 +453,14 @@ export class BlockInputSync {
369
453
  root: rootHex,
370
454
  peerIdStr: peerIdStr ?? "unknown peer",
371
455
  });
456
+ } else if (
457
+ // update a previously slot-less entry once a slot becomes available
458
+ slot !== undefined &&
459
+ !isPendingPayloadInput(pendingPayload) &&
460
+ !isPendingPayloadEnvelope(pendingPayload) &&
461
+ pendingPayload.slot === undefined
462
+ ) {
463
+ pendingPayload.slot = slot;
372
464
  }
373
465
 
374
466
  if (peerIdStr) {
@@ -377,11 +469,35 @@ export class BlockInputSync {
377
469
 
378
470
  const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
379
471
  if (prunedItemCount > 0) {
472
+ this.metrics?.blockInputSync.removedPayloads.inc({reason: DroppedItemReason.capacity}, prunedItemCount);
380
473
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
381
474
  }
382
475
  return added;
383
476
  };
384
477
 
478
+ /**
479
+ * Resolve the block slot for a payload root.
480
+ */
481
+ private resolvePayloadSlot(rootHex: RootHex): Slot | undefined {
482
+ // a pending block of the same root
483
+ const pendingBlock = this.pendingBlocks.get(rootHex);
484
+ if (pendingBlock) {
485
+ const pendingBlockSlot = getBlockInputSyncCacheItemSlot(pendingBlock);
486
+ if (typeof pendingBlockSlot === "number") {
487
+ return pendingBlockSlot;
488
+ }
489
+ }
490
+
491
+ // the block is in fork choice
492
+ const forkChoiceSlot = this.chain.forkChoice.getBlockHexDefaultStatus(rootHex)?.slot;
493
+ if (forkChoiceSlot !== undefined) {
494
+ return forkChoiceSlot;
495
+ }
496
+
497
+ // a seen payload envelope input
498
+ return this.chain.seenPayloadEnvelopeInputCache.get(rootHex)?.slot;
499
+ }
500
+
385
501
  private addByPayloadInput = (
386
502
  payloadInput: PayloadEnvelopeInput,
387
503
  peerIdStr?: PeerIdStr,
@@ -407,6 +523,7 @@ export class BlockInputSync {
407
523
 
408
524
  const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
409
525
  if (prunedItemCount > 0) {
526
+ this.metrics?.blockInputSync.removedPayloads.inc({reason: DroppedItemReason.capacity}, prunedItemCount);
410
527
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
411
528
  }
412
529
  };
@@ -436,10 +553,12 @@ export class BlockInputSync {
436
553
  blockInput: IBlockInput
437
554
  ):
438
555
  | {kind: "ready"}
439
- | {kind: "block" | "parentBlock" | "parentPayload"; rootHex: RootHex}
556
+ | {kind: "block" | "parentBlock"; rootHex: RootHex}
557
+ | {kind: "parentPayload"; rootHex: RootHex; slot: Slot}
440
558
  | {kind: "invalidParentPayload"; parentRootHex: RootHex; parentBlockHashHex: RootHex} {
441
559
  const parentRootHex = blockInput.parentRootHex;
442
- if (!this.chain.forkChoice.hasBlockHex(parentRootHex)) {
560
+ const parentBlock = this.chain.forkChoice.getBlockHexDefaultStatus(parentRootHex);
561
+ if (!parentBlock) {
443
562
  return {kind: "parentBlock", rootHex: parentRootHex};
444
563
  }
445
564
 
@@ -461,16 +580,8 @@ export class BlockInputSync {
461
580
  return {kind: "invalidParentPayload", parentRootHex, parentBlockHashHex};
462
581
  }
463
582
 
464
- const parentPayloadInput = this.chain.seenPayloadEnvelopeInputCache.get(parentRootHex);
465
- if (parentPayloadInput) {
466
- if (parentPayloadInput.getBlockHashHex() === parentBlockHashHex) {
467
- return {kind: "parentPayload", rootHex: parentRootHex};
468
- }
469
-
470
- return {kind: "invalidParentPayload", parentRootHex, parentBlockHashHex};
471
- }
472
-
473
- return {kind: "parentPayload", rootHex: parentRootHex};
583
+ // Parent is in fork choice but its payload `parentBlockHashHex` isn't revealed yet
584
+ return {kind: "parentPayload", rootHex: parentRootHex, slot: parentBlock.slot};
474
585
  }
475
586
 
476
587
  private advancePendingBlock(pendingBlock: PendingBlockInput): AdvancePendingBlockResult {
@@ -493,9 +604,9 @@ export class BlockInputSync {
493
604
  }
494
605
 
495
606
  case "parentPayload": {
496
- let added = this.addByPayloadRootHex(missingDependency.rootHex);
607
+ let added = this.addByPayloadRootHex(missingDependency.rootHex, undefined, missingDependency.slot);
497
608
  for (const peerIdStr of pendingBlock.peerIdStrings) {
498
- added = this.addByPayloadRootHex(missingDependency.rootHex, peerIdStr) || added;
609
+ added = this.addByPayloadRootHex(missingDependency.rootHex, peerIdStr, missingDependency.slot) || added;
499
610
  }
500
611
  return added ? "queued_parent_payload" : "blocked";
501
612
  }
@@ -507,7 +618,7 @@ export class BlockInputSync {
507
618
  parentRoot: missingDependency.parentRootHex,
508
619
  parentBlockHash: missingDependency.parentBlockHashHex,
509
620
  });
510
- this.removeAndDownScoreAllDescendants(pendingBlock);
621
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidParentPayload);
511
622
  return "removed";
512
623
  }
513
624
  }
@@ -730,7 +841,7 @@ export class BlockInputSync {
730
841
  ...logCtx2,
731
842
  finalizedSlot,
732
843
  });
733
- this.removeAndDownScoreAllDescendants(block);
844
+ this.removeAndDownScoreAllDescendants(block, DroppedItemReason.belowFinalized);
734
845
  } else {
735
846
  this.onUnknownBlockRoot({rootHex: pending.blockInput.parentRootHex, source: BlockInputSource.byRoot});
736
847
  }
@@ -747,7 +858,7 @@ export class BlockInputSync {
747
858
 
748
859
  this.metrics?.blockInputSync.downloadedBlocksError.inc();
749
860
  this.logger.debug("Ignoring unknown block root after many failed downloads", logCtx, res.err);
750
- this.removeAndDownScoreAllDescendants(block);
861
+ this.removeAndDownScoreAllDescendants(block, DroppedItemReason.unavailable);
751
862
  }
752
863
  }
753
864
 
@@ -811,9 +922,9 @@ export class BlockInputSync {
811
922
  this.pendingBlocks.delete(blockRootHex);
812
923
 
813
924
  if (isForkPostGloas(fork)) {
814
- const payloadInput = this.chain.seenPayloadEnvelopeInputCache.get(blockRootHex);
925
+ const payloadInput = await this.chain.seenPayloadEnvelopeInputCache.getOrReload(blockRootHex);
815
926
  if (!payloadInput) {
816
- this.logger.warn("PayloadEnvelopeInput not seeded during unknown sync processReadyBlock()", logCtx);
927
+ this.logger.debug("PayloadEnvelopeInput unavailable during unknown sync processReadyBlock()", logCtx);
817
928
  } else {
818
929
  // Similar to the gossip path: immediately attempt fetch of data columns from the execution engine.
819
930
  // The bid already carries the kzg commitments, so there is no reason to wait for the payload to arrive.
@@ -855,19 +966,19 @@ export class BlockInputSync {
855
966
  case BlockErrorCode.EXECUTION_ENGINE_ERROR:
856
967
  // Removing the block(s) without penalizing the peers, hoping for EL to
857
968
  // recover on a latter download + verify attempt.
858
- this.removeAllDescendants(pendingBlock);
969
+ this.removeAllDescendants(pendingBlock, DroppedItemReason.elError, DroppedItemReason.elError);
859
970
  break;
860
971
 
861
972
  case BlockErrorCode.EXECUTION_ENGINE_INVALID:
862
973
  // the peer served a bad block
863
974
  this.logger.debug("Execution engine rejected block from unknown parent sync", errorData, res.err);
864
- this.removeAndDownScoreAllDescendants(pendingBlock);
975
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.elInvalid);
865
976
  break;
866
977
 
867
978
  default:
868
979
  // Block is not correct with respect to our chain. Log error loudly
869
980
  this.logger.debug("Error processing block from unknown parent sync", errorData, res.err);
870
- this.removeAndDownScoreAllDescendants(pendingBlock);
981
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidBlock);
871
982
  }
872
983
  }
873
984
 
@@ -886,12 +997,12 @@ export class BlockInputSync {
886
997
  */
887
998
  private async reconcilePayloadEnvelope(pendingPayload: PendingPayloadEnvelope): Promise<void> {
888
999
  const rootHex = getPayloadSyncCacheItemRootHex(pendingPayload);
1000
+
889
1001
  if (this.chain.forkChoice.hasPayloadHexUnsafe(rootHex)) {
890
1002
  this.pendingPayloads.delete(rootHex);
891
1003
  return;
892
1004
  }
893
1005
 
894
- const payloadInput = this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
895
1006
  if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
896
1007
  // Block not in fork choice yet. payloadInput may be seeded from the block body during download, so a
897
1008
  // non-null payloadInput does not imply the block is imported; defer regardless and pull the block first.
@@ -907,8 +1018,9 @@ export class BlockInputSync {
907
1018
  return;
908
1019
  }
909
1020
 
1021
+ const payloadInput = await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
910
1022
  if (!payloadInput) {
911
- this.logger.debug("Missing PayloadEnvelopeInput for known block while reconciling payload envelope", {
1023
+ this.logger.debug("PayloadEnvelopeInput not yet reloadable for imported block, will retry", {
912
1024
  root: rootHex,
913
1025
  });
914
1026
  return;
@@ -928,6 +1040,7 @@ export class BlockInputSync {
928
1040
  const pendingPayloadByRoot: PendingPayloadRootHex = {
929
1041
  status: PendingPayloadInputStatus.pending,
930
1042
  rootHex,
1043
+ slot: pendingPayload.envelope.message.payload.slotNumber,
931
1044
  timeAddedSec: pendingPayload.timeAddedSec,
932
1045
  peerIdStrings: new Set(pendingPayload.peerIdStrings),
933
1046
  };
@@ -967,6 +1080,10 @@ export class BlockInputSync {
967
1080
  return;
968
1081
  }
969
1082
 
1083
+ if (!isPendingPayloadInput(payload) && payload.slot === undefined) {
1084
+ payload.slot = this.resolvePayloadSlot(rootHex);
1085
+ }
1086
+
970
1087
  const payloadSlot = getPayloadSyncCacheItemSlot(payload);
971
1088
  const logCtx = {
972
1089
  slot: payloadSlot,
@@ -982,7 +1099,13 @@ export class BlockInputSync {
982
1099
  const res = await wrapError(this.fetchPayloadInput(payload));
983
1100
  if (!res.err) {
984
1101
  const pendingPayload = res.result;
985
- this.pendingPayloads.set(getPayloadSyncCacheItemRootHex(pendingPayload), pendingPayload);
1102
+ const resultRootHex = getPayloadSyncCacheItemRootHex(pendingPayload);
1103
+ // finalization may have deleted this entry while we were awaiting the network, do not resurrect it via the set below
1104
+ if (!this.pendingPayloads.has(resultRootHex)) {
1105
+ this.logger.verbose("Dropping downloaded payload, entry pruned during fetch", logCtx);
1106
+ return;
1107
+ }
1108
+ this.pendingPayloads.set(resultRootHex, pendingPayload);
986
1109
 
987
1110
  if (isPendingPayloadEnvelope(pendingPayload)) {
988
1111
  await this.reconcilePayloadEnvelope(pendingPayload);
@@ -1059,17 +1182,27 @@ export class BlockInputSync {
1059
1182
  break;
1060
1183
 
1061
1184
  case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
1185
+ this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1186
+ this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.elInvalid);
1187
+ break;
1188
+
1062
1189
  case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
1190
+ this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1191
+ this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidEnvelope);
1192
+ break;
1193
+
1063
1194
  case PayloadErrorCode.INVALID_SIGNATURE:
1064
1195
  // TODO GLOAS: Decide how invalid payload inputs should eventually leave memory without
1065
1196
  // reintroducing envelope replacement / recreation flows.
1066
1197
  this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1067
- this.removePendingPayloadAndDescendants(rootHex);
1198
+ this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidSignature);
1068
1199
  break;
1069
1200
 
1070
1201
  default:
1071
1202
  this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1072
- this.pendingPayloads.delete(rootHex);
1203
+ if (this.pendingPayloads.delete(rootHex)) {
1204
+ this.metrics?.blockInputSync.removedPayloads.inc({reason: DroppedItemReason.unknown}, 1);
1205
+ }
1073
1206
  }
1074
1207
  return;
1075
1208
  }
@@ -1093,7 +1226,7 @@ export class BlockInputSync {
1093
1226
  let slot = getPayloadSyncCacheItemSlot(cacheItem);
1094
1227
  let payloadInput = isPendingPayloadInput(cacheItem)
1095
1228
  ? cacheItem.payloadInput
1096
- : this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
1229
+ : await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
1097
1230
  let envelope = payloadInput?.hasPayloadEnvelope() ? payloadInput.getPayloadEnvelope() : undefined;
1098
1231
 
1099
1232
  let i = 0;
@@ -1110,9 +1243,11 @@ export class BlockInputSync {
1110
1243
  );
1111
1244
  }
1112
1245
 
1113
- throw Error(
1114
- `Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`
1115
- );
1246
+ const reason =
1247
+ pendingColumns.size > 0
1248
+ ? `cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`
1249
+ : "no peer available to download pending payload";
1250
+ throw Error(`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: ${reason}`);
1116
1251
  }
1117
1252
 
1118
1253
  const {peerId, client: peerClient} = peerMeta;
@@ -1124,7 +1259,7 @@ export class BlockInputSync {
1124
1259
  slot = envelope.message.payload.slotNumber;
1125
1260
  }
1126
1261
 
1127
- payloadInput ??= this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
1262
+ payloadInput ??= await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
1128
1263
  if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
1129
1264
  // Block not in fork choice yet. Validating now would throw BLOCK_ROOT_UNKNOWN, so keep the downloaded
1130
1265
  // envelope and wait for the block body; reconcilePayloadEnvelope validates once the block lands.
@@ -1457,9 +1592,9 @@ export class BlockInputSync {
1457
1592
  * Downscore all peers that have referenced any of this bad blocks. May report peers multiple times if they have
1458
1593
  * referenced more than one bad block.
1459
1594
  */
1460
- private removeAndDownScoreAllDescendants(block: BlockInputSyncCacheItem): void {
1595
+ private removeAndDownScoreAllDescendants(block: BlockInputSyncCacheItem, headReason: DroppedItemReason): void {
1461
1596
  // Get all blocks that are a descendant of this one
1462
- const badPendingBlocks = this.removeAllDescendants(block);
1597
+ const badPendingBlocks = this.removeAllDescendants(block, headReason, DroppedItemReason.invalidParent);
1463
1598
  // just console log and do not penalize on pending/bad blocks for debugging
1464
1599
  // console.log("removeAndDownscoreAllDescendants", {block});
1465
1600
 
@@ -1486,18 +1621,22 @@ export class BlockInputSync {
1486
1621
  }
1487
1622
 
1488
1623
  // Once a parent payload is invalid, every descendant waiting on that payload lineage becomes unrecoverable too.
1489
- private removePendingPayloadAndDescendants(rootHex: RootHex): void {
1624
+ private removePendingPayloadAndDescendants(rootHex: RootHex, headReason: DroppedItemReason): void {
1490
1625
  // Keep PayloadEnvelopeInput resident in the seen cache. importBlock() owns that object and
1491
1626
  // later validation/finalization logic decides when it can leave memory.
1492
- this.pendingPayloads.delete(rootHex);
1627
+ if (this.pendingPayloads.delete(rootHex)) {
1628
+ this.metrics?.blockInputSync.removedPayloads.inc({reason: headReason}, 1);
1629
+ }
1493
1630
 
1494
1631
  const badPendingBlocks = getAllDescendantBlocks(rootHex, this.pendingBlocks);
1495
- this.metrics?.blockInputSync.removedBlocks.inc(badPendingBlocks.length);
1496
-
1497
1632
  for (const block of badPendingBlocks) {
1498
1633
  const descendantRootHex = getBlockInputSyncCacheItemRootHex(block);
1499
- this.pendingBlocks.delete(descendantRootHex);
1500
- this.pendingPayloads.delete(descendantRootHex);
1634
+ if (this.pendingBlocks.delete(descendantRootHex)) {
1635
+ this.metrics?.blockInputSync.removedBlocks.inc({reason: DroppedItemReason.invalidParent}, 1);
1636
+ }
1637
+ if (this.pendingPayloads.delete(descendantRootHex)) {
1638
+ this.metrics?.blockInputSync.removedPayloads.inc({reason: DroppedItemReason.invalidParent}, 1);
1639
+ }
1501
1640
  this.chain.seenBlockInputCache.prune(descendantRootHex);
1502
1641
  this.logger.debug("Removing pending descendant after invalid parent payload", {
1503
1642
  slot: getBlockInputSyncCacheItemSlot(block),
@@ -1507,18 +1646,25 @@ export class BlockInputSync {
1507
1646
  }
1508
1647
  }
1509
1648
 
1510
- private removeAllDescendants(block: BlockInputSyncCacheItem): BlockInputSyncCacheItem[] {
1649
+ private removeAllDescendants(
1650
+ block: BlockInputSyncCacheItem,
1651
+ headReason: DroppedItemReason,
1652
+ descendantReason: DroppedItemReason = DroppedItemReason.invalidParent
1653
+ ): BlockInputSyncCacheItem[] {
1511
1654
  const rootHex = getBlockInputSyncCacheItemRootHex(block);
1512
1655
  const slot = getBlockInputSyncCacheItemSlot(block);
1513
- // Get all blocks that are a descendant of this one
1656
+ // Get all blocks that are a descendant of this one (index 0 is the head item itself)
1514
1657
  const badPendingBlocks = [block, ...getAllDescendantBlocks(rootHex, this.pendingBlocks)];
1515
1658
 
1516
- this.metrics?.blockInputSync.removedBlocks.inc(badPendingBlocks.length);
1517
-
1518
- for (const block of badPendingBlocks) {
1659
+ for (const [i, block] of badPendingBlocks.entries()) {
1519
1660
  const rootHex = getBlockInputSyncCacheItemRootHex(block);
1520
- this.pendingBlocks.delete(rootHex);
1521
- this.pendingPayloads.delete(rootHex);
1661
+ const reason = i === 0 ? headReason : descendantReason;
1662
+ if (this.pendingBlocks.delete(rootHex)) {
1663
+ this.metrics?.blockInputSync.removedBlocks.inc({reason}, 1);
1664
+ }
1665
+ if (this.pendingPayloads.delete(rootHex)) {
1666
+ this.metrics?.blockInputSync.removedPayloads.inc({reason}, 1);
1667
+ }
1522
1668
  this.chain.seenBlockInputCache.prune(rootHex);
1523
1669
  // Keep PayloadEnvelopeInput resident in the seen cache for consistency with the
1524
1670
  // importBlock()-owned lifecycle.
@@ -136,6 +136,8 @@ export function cacheByRangeResponses({
136
136
  const source = BlockInputSource.byRange;
137
137
  const updatedBatchBlocks = new Map<Slot, IBlockInput>(batchBlocks.map((block) => [block.slot, block]));
138
138
 
139
+ const payloadEnvelopes = new Map<Slot, PayloadEnvelopeInput>(existingPayloadEnvelopes);
140
+
139
141
  const blocks = responses.validatedBlocks ?? [];
140
142
  for (let i = 0; i < blocks.length; i++) {
141
143
  const {block, blockRoot} = blocks[i];
@@ -172,15 +174,17 @@ export function cacheByRangeResponses({
172
174
  // later step) can throw and abort the batch — otherwise a gloas block would sit in
173
175
  // seenBlockInputCache but unseeded here, and payload-by-root sync would later throw "Missing
174
176
  // PayloadEnvelopeInput for known block" (see issue #9306). add() is idempotent.
175
- if (isForkPostGloas(blockInput.forkName)) {
176
- seenPayloadEnvelopeInputCache.add({
177
+ if (isForkPostGloas(blockInput.forkName) && !payloadEnvelopes.has(blockInput.slot)) {
178
+ const payloadInput = seenPayloadEnvelopeInputCache.add({
177
179
  blockRootHex: blockInput.blockRootHex,
178
180
  block: blockInput.getBlock() as SignedBeaconBlock<ForkPostGloas>,
179
181
  forkName: blockInput.forkName,
180
182
  sampledColumns: custodyConfig.sampledColumns,
181
183
  custodyColumns: custodyConfig.custodyColumns,
182
- timeCreatedSec: seenTimestampSec,
184
+ seenTimestampSec,
185
+ source: PayloadEnvelopeInputSource.byRange,
183
186
  });
187
+ payloadEnvelopes.set(blockInput.slot, payloadInput);
184
188
  }
185
189
  }
186
190
 
@@ -222,17 +226,22 @@ export function cacheByRangeResponses({
222
226
  }
223
227
  }
224
228
 
225
- let payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null =
226
- existingPayloadEnvelopes !== null ? new Map(existingPayloadEnvelopes) : null;
227
229
  if (downloadedPayloadEnvelopes !== null) {
228
- payloadEnvelopes ??= new Map();
229
230
  for (const [slot, envelope] of downloadedPayloadEnvelopes) {
230
- const envelopeBlockRootHex = toRootHex(envelope.message.beaconBlockRoot);
231
- const payloadInput = seenPayloadEnvelopeInputCache.get(envelopeBlockRootHex);
231
+ // the only PayloadEnvelopeInput miss is the dangling parent, which we can get from the seen cache
232
+ let payloadInput = payloadEnvelopes.get(slot);
232
233
  if (payloadInput === undefined) {
233
- // Unreachable given the validatedBlocks loop above seeded an entry for every gloas block in
234
- // the batch. for the parent block, it's populated at BeaconChain init
235
- throw new Error(`Missing PayloadEnvelopeInput for block ${envelopeBlockRootHex}`);
234
+ if (updatedBatchBlocks.has(slot)) {
235
+ throw new Error(
236
+ `Missing PayloadEnvelopeInput for in-batch slot ${slot} root ${toRootHex(envelope.message.beaconBlockRoot)}`
237
+ );
238
+ }
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
+ );
236
245
  }
237
246
 
238
247
  if (!payloadInput.hasPayloadEnvelope()) {
@@ -262,7 +271,7 @@ export function cacheByRangeResponses({
262
271
  // Gloas columns are attached to the matching PayloadEnvelopeInput, NOT to IBlockInput.
263
272
  // Gloas DataColumnSidecar has `slot` directly (no signedBlockHeader).
264
273
  const dataSlot = firstColumn.slot;
265
- const payloadInput = payloadEnvelopes?.get(dataSlot);
274
+ const payloadInput = payloadEnvelopes.get(dataSlot);
266
275
  if (!payloadInput) {
267
276
  // Should not happen: we built payloadInputs for all gloas blocks above
268
277
  continue;
@@ -13,6 +13,7 @@ import {BlobIndex, ColumnIndex, SignedBeaconBlock, Slot, deneb, fulu} from "@lod
13
13
  import {LodestarError, byteArrayEquals, fromHex, prettyPrintIndices, toHex, toRootHex} from "@lodestar/utils";
14
14
  import {isBlockInputBlobs, isBlockInputColumns} from "../../chain/blocks/blockInput/blockInput.js";
15
15
  import {BlockInputSource, IBlockInput} from "../../chain/blocks/blockInput/types.js";
16
+ import {PayloadEnvelopeInputSource} from "../../chain/blocks/payloadEnvelopeInput/index.js";
16
17
  import {ChainEventEmitter} from "../../chain/emitter.js";
17
18
  import {IBeaconChain} from "../../chain/interface.js";
18
19
  import {validateBlockBlobSidecars} from "../../chain/validation/blobSidecar.js";
@@ -122,7 +123,8 @@ export async function downloadByRoot({
122
123
  forkName: blockInput.forkName,
123
124
  sampledColumns: chain.custodyConfig.sampledColumns,
124
125
  custodyColumns: chain.custodyConfig.custodyColumns,
125
- timeCreatedSec: Date.now() / 1000,
126
+ seenTimestampSec: Date.now() / 1000,
127
+ source: PayloadEnvelopeInputSource.byRoot,
126
128
  });
127
129
  }
128
130
 
@@ -1,12 +0,0 @@
1
- import { PublicKey } from "@chainsafe/blst";
2
- export type SignatureSetDeserialized = {
3
- publicKey: PublicKey;
4
- message: Uint8Array;
5
- signature: Uint8Array;
6
- };
7
- /**
8
- * Verify signatures sets with batch verification or regular core verify depending on the set count.
9
- * Abstracted in a separate file to be consumed by the threaded pool and the main thread implementation.
10
- */
11
- export declare function verifySignatureSetsMaybeBatch(sets: SignatureSetDeserialized[]): boolean;
12
- //# sourceMappingURL=maybeBatch.d.ts.map