@lodestar/beacon-node 1.47.0-dev.09607ce6d4 → 1.47.0-dev.101e2c290d

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 (303) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +3 -2
  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 +21 -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/rest/base.d.ts.map +1 -1
  17. package/lib/api/rest/base.js +7 -0
  18. package/lib/api/rest/base.js.map +1 -1
  19. package/lib/chain/archiveStore/archiveStore.js +1 -1
  20. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  21. package/lib/chain/blocks/importExecutionPayload.d.ts +3 -2
  22. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  23. package/lib/chain/blocks/importExecutionPayload.js +11 -9
  24. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  25. package/lib/chain/blocks/index.d.ts.map +1 -1
  26. package/lib/chain/blocks/index.js +14 -6
  27. package/lib/chain/blocks/index.js.map +1 -1
  28. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +2 -1
  29. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  30. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +6 -2
  31. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  32. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +14 -5
  33. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  34. package/lib/chain/blocks/payloadEnvelopeInput/types.js +5 -0
  35. package/lib/chain/blocks/payloadEnvelopeInput/types.js.map +1 -1
  36. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
  37. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +22 -12
  38. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
  39. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +2 -2
  40. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  41. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  42. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  43. package/lib/chain/bls/index.d.ts +1 -1
  44. package/lib/chain/bls/index.d.ts.map +1 -1
  45. package/lib/chain/bls/interface.d.ts +5 -5
  46. package/lib/chain/bls/interface.d.ts.map +1 -1
  47. package/lib/chain/bls/multithread/index.d.ts +5 -11
  48. package/lib/chain/bls/multithread/index.d.ts.map +1 -1
  49. package/lib/chain/bls/multithread/index.js +116 -101
  50. package/lib/chain/bls/multithread/index.js.map +1 -1
  51. package/lib/chain/bls/multithread/jobItem.d.ts +5 -19
  52. package/lib/chain/bls/multithread/jobItem.d.ts.map +1 -1
  53. package/lib/chain/bls/multithread/jobItem.js +10 -65
  54. package/lib/chain/bls/multithread/jobItem.js.map +1 -1
  55. package/lib/chain/bls/multithread/types.d.ts +29 -8
  56. package/lib/chain/bls/multithread/types.d.ts.map +1 -1
  57. package/lib/chain/bls/multithread/types.js +12 -0
  58. package/lib/chain/bls/multithread/types.js.map +1 -1
  59. package/lib/chain/bls/multithread/utils.d.ts +4 -0
  60. package/lib/chain/bls/multithread/utils.d.ts.map +1 -1
  61. package/lib/chain/bls/multithread/utils.js +10 -0
  62. package/lib/chain/bls/multithread/utils.js.map +1 -1
  63. package/lib/chain/bls/multithread/worker.js +49 -23
  64. package/lib/chain/bls/multithread/worker.js.map +1 -1
  65. package/lib/chain/bls/singleThread.d.ts +4 -10
  66. package/lib/chain/bls/singleThread.d.ts.map +1 -1
  67. package/lib/chain/bls/singleThread.js +8 -45
  68. package/lib/chain/bls/singleThread.js.map +1 -1
  69. package/lib/chain/bls/utils.d.ts +4 -4
  70. package/lib/chain/bls/utils.d.ts.map +1 -1
  71. package/lib/chain/bls/utils.js +18 -19
  72. package/lib/chain/bls/utils.js.map +1 -1
  73. package/lib/chain/chain.d.ts +4 -2
  74. package/lib/chain/chain.d.ts.map +1 -1
  75. package/lib/chain/chain.js +16 -6
  76. package/lib/chain/chain.js.map +1 -1
  77. package/lib/chain/errors/blockError.d.ts +1 -0
  78. package/lib/chain/errors/blockError.d.ts.map +1 -1
  79. package/lib/chain/errors/blockError.js +6 -1
  80. package/lib/chain/errors/blockError.js.map +1 -1
  81. package/lib/chain/errors/proposerPreferences.d.ts +6 -0
  82. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
  83. package/lib/chain/errors/proposerPreferences.js +1 -0
  84. package/lib/chain/errors/proposerPreferences.js.map +1 -1
  85. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  86. package/lib/chain/forkChoice/index.js +4 -0
  87. package/lib/chain/forkChoice/index.js.map +1 -1
  88. package/lib/chain/interface.d.ts +4 -2
  89. package/lib/chain/interface.d.ts.map +1 -1
  90. package/lib/chain/interface.js.map +1 -1
  91. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  92. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  93. package/lib/chain/opPools/attestationPool.d.ts +1 -1
  94. package/lib/chain/opPools/attestationPool.d.ts.map +1 -1
  95. package/lib/chain/opPools/attestationPool.js +1 -1
  96. package/lib/chain/opPools/attestationPool.js.map +1 -1
  97. package/lib/chain/opPools/deferredVoluntaryExitPool.d.ts +16 -0
  98. package/lib/chain/opPools/deferredVoluntaryExitPool.d.ts.map +1 -0
  99. package/lib/chain/opPools/deferredVoluntaryExitPool.js +57 -0
  100. package/lib/chain/opPools/deferredVoluntaryExitPool.js.map +1 -0
  101. package/lib/chain/opPools/index.d.ts +1 -0
  102. package/lib/chain/opPools/index.d.ts.map +1 -1
  103. package/lib/chain/opPools/index.js +1 -0
  104. package/lib/chain/opPools/index.js.map +1 -1
  105. package/lib/chain/opPools/payloadAttestationPool.js +3 -3
  106. package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
  107. package/lib/chain/opPools/syncCommitteeMessagePool.js +1 -1
  108. package/lib/chain/opPools/syncCommitteeMessagePool.js.map +1 -1
  109. package/lib/chain/opPools/syncContributionAndProofPool.js +1 -1
  110. package/lib/chain/opPools/syncContributionAndProofPool.js.map +1 -1
  111. package/lib/chain/opPools/utils.d.ts +1 -1
  112. package/lib/chain/opPools/utils.d.ts.map +1 -1
  113. package/lib/chain/opPools/utils.js +1 -1
  114. package/lib/chain/opPools/utils.js.map +1 -1
  115. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  116. package/lib/chain/prepareNextSlot.js +23 -5
  117. package/lib/chain/prepareNextSlot.js.map +1 -1
  118. package/lib/chain/regen/interface.d.ts +4 -2
  119. package/lib/chain/regen/interface.d.ts.map +1 -1
  120. package/lib/chain/regen/interface.js +2 -0
  121. package/lib/chain/regen/interface.js.map +1 -1
  122. package/lib/chain/regen/queued.d.ts +2 -2
  123. package/lib/chain/regen/queued.d.ts.map +1 -1
  124. package/lib/chain/regen/queued.js +5 -5
  125. package/lib/chain/regen/queued.js.map +1 -1
  126. package/lib/chain/seenCache/seenAttesters.d.ts +9 -5
  127. package/lib/chain/seenCache/seenAttesters.d.ts.map +1 -1
  128. package/lib/chain/seenCache/seenAttesters.js +25 -4
  129. package/lib/chain/seenCache/seenAttesters.js.map +1 -1
  130. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +28 -1
  131. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  132. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +121 -13
  133. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  134. package/lib/chain/stateCache/fifoBlockStateCache.d.ts +5 -1
  135. package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
  136. package/lib/chain/stateCache/fifoBlockStateCache.js +6 -0
  137. package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
  138. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +47 -5
  139. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  140. package/lib/chain/stateCache/persistentCheckpointsCache.js +117 -7
  141. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  142. package/lib/chain/stateCache/types.d.ts +3 -3
  143. package/lib/chain/stateCache/types.d.ts.map +1 -1
  144. package/lib/chain/validation/attestation.d.ts.map +1 -1
  145. package/lib/chain/validation/attestation.js +1 -4
  146. package/lib/chain/validation/attestation.js.map +1 -1
  147. package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
  148. package/lib/chain/validation/attesterSlashing.js +1 -1
  149. package/lib/chain/validation/attesterSlashing.js.map +1 -1
  150. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  151. package/lib/chain/validation/executionPayloadBid.js +1 -2
  152. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  153. package/lib/chain/validation/executionPayloadEnvelope.js +1 -1
  154. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  155. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  156. package/lib/chain/validation/payloadAttestationMessage.js +5 -14
  157. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  158. package/lib/chain/validation/proposerPreferences.d.ts +1 -1
  159. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  160. package/lib/chain/validation/proposerPreferences.js +13 -4
  161. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  162. package/lib/chain/validation/proposerSlashing.js +1 -1
  163. package/lib/chain/validation/proposerSlashing.js.map +1 -1
  164. package/lib/chain/validation/voluntaryExit.d.ts +9 -1
  165. package/lib/chain/validation/voluntaryExit.d.ts.map +1 -1
  166. package/lib/chain/validation/voluntaryExit.js +46 -1
  167. package/lib/chain/validation/voluntaryExit.js.map +1 -1
  168. package/lib/metrics/metrics/lodestar.d.ts +62 -22
  169. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  170. package/lib/metrics/metrics/lodestar.js +114 -52
  171. package/lib/metrics/metrics/lodestar.js.map +1 -1
  172. package/lib/network/gossip/topic.d.ts +13 -0
  173. package/lib/network/gossip/topic.d.ts.map +1 -1
  174. package/lib/network/gossip/topic.js +37 -0
  175. package/lib/network/gossip/topic.js.map +1 -1
  176. package/lib/network/libp2p/index.d.ts.map +1 -1
  177. package/lib/network/libp2p/index.js +10 -2
  178. package/lib/network/libp2p/index.js.map +1 -1
  179. package/lib/network/network.d.ts.map +1 -1
  180. package/lib/network/network.js +22 -43
  181. package/lib/network/network.js.map +1 -1
  182. package/lib/network/processor/constants.d.ts +7 -0
  183. package/lib/network/processor/constants.d.ts.map +1 -0
  184. package/lib/network/processor/constants.js +7 -0
  185. package/lib/network/processor/constants.js.map +1 -0
  186. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  187. package/lib/network/processor/gossipHandlers.js +5 -1
  188. package/lib/network/processor/gossipHandlers.js.map +1 -1
  189. package/lib/network/processor/index.d.ts +3 -0
  190. package/lib/network/processor/index.d.ts.map +1 -1
  191. package/lib/network/processor/index.js +36 -15
  192. package/lib/network/processor/index.js.map +1 -1
  193. package/lib/network/reqresp/score.d.ts.map +1 -1
  194. package/lib/network/reqresp/score.js +5 -11
  195. package/lib/network/reqresp/score.js.map +1 -1
  196. package/lib/network/util.d.ts +0 -1
  197. package/lib/network/util.d.ts.map +1 -1
  198. package/lib/network/util.js +0 -4
  199. package/lib/network/util.js.map +1 -1
  200. package/lib/node/deferredVoluntaryExitPublisher.d.ts +15 -0
  201. package/lib/node/deferredVoluntaryExitPublisher.d.ts.map +1 -0
  202. package/lib/node/deferredVoluntaryExitPublisher.js +38 -0
  203. package/lib/node/deferredVoluntaryExitPublisher.js.map +1 -0
  204. package/lib/node/nodejs.d.ts +2 -1
  205. package/lib/node/nodejs.d.ts.map +1 -1
  206. package/lib/node/nodejs.js +2 -0
  207. package/lib/node/nodejs.js.map +1 -1
  208. package/lib/sync/range/batch.d.ts +23 -5
  209. package/lib/sync/range/batch.d.ts.map +1 -1
  210. package/lib/sync/range/batch.js +61 -22
  211. package/lib/sync/range/batch.js.map +1 -1
  212. package/lib/sync/range/chain.d.ts +2 -1
  213. package/lib/sync/range/chain.d.ts.map +1 -1
  214. package/lib/sync/range/chain.js +23 -10
  215. package/lib/sync/range/chain.js.map +1 -1
  216. package/lib/sync/types.d.ts +32 -0
  217. package/lib/sync/types.d.ts.map +1 -1
  218. package/lib/sync/types.js +33 -0
  219. package/lib/sync/types.js.map +1 -1
  220. package/lib/sync/unknownBlock.d.ts +13 -2
  221. package/lib/sync/unknownBlock.d.ts.map +1 -1
  222. package/lib/sync/unknownBlock.js +204 -61
  223. package/lib/sync/unknownBlock.js.map +1 -1
  224. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  225. package/lib/sync/utils/downloadByRange.js +16 -11
  226. package/lib/sync/utils/downloadByRange.js.map +1 -1
  227. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  228. package/lib/sync/utils/downloadByRoot.js +3 -1
  229. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  230. package/package.json +35 -35
  231. package/src/api/impl/beacon/blocks/index.ts +3 -2
  232. package/src/api/impl/beacon/pool/index.ts +22 -2
  233. package/src/api/impl/beacon/state/utils.ts +2 -1
  234. package/src/api/impl/events/index.ts +12 -4
  235. package/src/api/impl/lodestar/index.ts +1 -1
  236. package/src/api/rest/base.ts +7 -0
  237. package/src/chain/archiveStore/archiveStore.ts +1 -1
  238. package/src/chain/blocks/importExecutionPayload.ts +12 -11
  239. package/src/chain/blocks/index.ts +18 -6
  240. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +8 -2
  241. package/src/chain/blocks/payloadEnvelopeInput/types.ts +17 -4
  242. package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +25 -16
  243. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +1 -9
  244. package/src/chain/bls/index.ts +1 -1
  245. package/src/chain/bls/interface.ts +7 -3
  246. package/src/chain/bls/multithread/index.ts +148 -120
  247. package/src/chain/bls/multithread/jobItem.ts +12 -82
  248. package/src/chain/bls/multithread/types.ts +33 -8
  249. package/src/chain/bls/multithread/utils.ts +11 -0
  250. package/src/chain/bls/multithread/worker.ts +85 -27
  251. package/src/chain/bls/singleThread.ts +11 -55
  252. package/src/chain/bls/utils.ts +23 -24
  253. package/src/chain/chain.ts +17 -6
  254. package/src/chain/errors/blockError.ts +8 -2
  255. package/src/chain/errors/proposerPreferences.ts +7 -0
  256. package/src/chain/forkChoice/index.ts +4 -0
  257. package/src/chain/interface.ts +4 -1
  258. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  259. package/src/chain/opPools/attestationPool.ts +1 -1
  260. package/src/chain/opPools/deferredVoluntaryExitPool.ts +65 -0
  261. package/src/chain/opPools/index.ts +1 -0
  262. package/src/chain/opPools/payloadAttestationPool.ts +3 -3
  263. package/src/chain/opPools/syncCommitteeMessagePool.ts +1 -1
  264. package/src/chain/opPools/syncContributionAndProofPool.ts +1 -1
  265. package/src/chain/opPools/utils.ts +1 -1
  266. package/src/chain/prepareNextSlot.ts +23 -5
  267. package/src/chain/regen/interface.ts +4 -2
  268. package/src/chain/regen/queued.ts +5 -5
  269. package/src/chain/seenCache/seenAttesters.ts +31 -5
  270. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +143 -14
  271. package/src/chain/stateCache/fifoBlockStateCache.ts +8 -1
  272. package/src/chain/stateCache/persistentCheckpointsCache.ts +133 -8
  273. package/src/chain/stateCache/types.ts +3 -3
  274. package/src/chain/validation/attestation.ts +1 -7
  275. package/src/chain/validation/attesterSlashing.ts +1 -8
  276. package/src/chain/validation/executionPayloadBid.ts +1 -2
  277. package/src/chain/validation/executionPayloadEnvelope.ts +0 -1
  278. package/src/chain/validation/payloadAttestationMessage.ts +6 -17
  279. package/src/chain/validation/proposerPreferences.ts +15 -5
  280. package/src/chain/validation/proposerSlashing.ts +1 -1
  281. package/src/chain/validation/voluntaryExit.ts +59 -2
  282. package/src/metrics/metrics/lodestar.ts +126 -57
  283. package/src/network/gossip/topic.ts +38 -0
  284. package/src/network/libp2p/index.ts +11 -2
  285. package/src/network/network.ts +27 -55
  286. package/src/network/processor/constants.ts +6 -0
  287. package/src/network/processor/gossipHandlers.ts +5 -1
  288. package/src/network/processor/index.ts +42 -19
  289. package/src/network/reqresp/score.ts +5 -11
  290. package/src/network/util.ts +0 -5
  291. package/src/node/deferredVoluntaryExitPublisher.ts +54 -0
  292. package/src/node/nodejs.ts +5 -1
  293. package/src/sync/range/batch.ts +83 -28
  294. package/src/sync/range/chain.ts +26 -11
  295. package/src/sync/types.ts +36 -1
  296. package/src/sync/unknownBlock.ts +240 -68
  297. package/src/sync/utils/downloadByRange.ts +21 -12
  298. package/src/sync/utils/downloadByRoot.ts +3 -1
  299. package/lib/chain/bls/maybeBatch.d.ts +0 -12
  300. package/lib/chain/bls/maybeBatch.d.ts.map +0 -1
  301. package/lib/chain/bls/maybeBatch.js +0 -35
  302. package/lib/chain/bls/maybeBatch.js.map +0 -1
  303. package/src/chain/bls/maybeBatch.ts +0 -45
@@ -1,5 +1,5 @@
1
1
  import { routes } from "@lodestar/api";
2
- import { ForkSeq, isForkPostGloas } from "@lodestar/params";
2
+ import { ForkSeq, SLOTS_PER_EPOCH, isForkPostGloas } from "@lodestar/params";
3
3
  import { RequestError, RequestErrorCode } from "@lodestar/reqresp";
4
4
  import { computeTimeAtSlot } from "@lodestar/state-transition";
5
5
  import { fromHex, prettyPrintIndices, pruneSetToMax, sleep, toRootHex } from "@lodestar/utils";
@@ -12,17 +12,22 @@ import { ChainEvent } from "../chain/index.js";
12
12
  import { validateGloasBlockDataColumnSidecars } from "../chain/validation/dataColumnSidecar.js";
13
13
  import { validateGossipExecutionPayloadEnvelope } from "../chain/validation/executionPayloadEnvelope.js";
14
14
  import { NetworkEvent, prettyPrintPeerIdStr } from "../network/index.js";
15
+ import { MAX_PEERS_PER_ROOT } from "../network/processor/constants.js";
15
16
  import { shuffle } from "../util/shuffle.js";
16
17
  import { sortBy } from "../util/sortBy.js";
17
18
  import { wrapError } from "../util/wrapError.js";
18
19
  import { MAX_CONCURRENT_REQUESTS } from "./constants.js";
19
- import { PendingBlockInputStatus, PendingBlockType, PendingPayloadInputStatus, getBlockInputSyncCacheItemRootHex, getBlockInputSyncCacheItemSlot, getPayloadSyncCacheItemRootHex, getPayloadSyncCacheItemSlot, isPendingBlockInput, isPendingPayloadEnvelope, isPendingPayloadInput, } from "./types.js";
20
+ import { DroppedItemReason, PendingBlockInputStatus, PendingBlockType, PendingPayloadInputStatus, getBlockInputSyncCacheItemRootHex, getBlockInputSyncCacheItemSlot, getPayloadSyncCacheItemRootHex, getPayloadSyncCacheItemSlot, isPendingBlockInput, isPendingPayloadEnvelope, isPendingPayloadInput, } from "./types.js";
20
21
  import { DownloadByRootError, downloadByRoot } from "./utils/downloadByRoot.js";
21
22
  import { getAllDescendantBlocks, getUnknownAndAncestorBlocks } from "./utils/pendingBlocksTree.js";
22
23
  import { getRateLimitedUntilMs } from "./utils/rateLimit.js";
23
24
  const MAX_ATTEMPTS_PER_BLOCK = 5;
24
25
  const MAX_KNOWN_BAD_BLOCKS = 500;
25
26
  const MAX_PENDING_BLOCKS = 100;
27
+ /**
28
+ * A pending payload without slot can only stay at most 2 epochs.
29
+ */
30
+ const PRUNE_UNRESOLVED_SLOT_EPOCHS = 2;
26
31
  var FetchResult;
27
32
  (function (FetchResult) {
28
33
  FetchResult["SuccessResolved"] = "success_resolved";
@@ -115,6 +120,7 @@ export class BlockInputSync {
115
120
  this.chain.emitter.on(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
116
121
  this.chain.emitter.on(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
117
122
  this.chain.emitter.on(ChainEvent.blockUnknownParent, this.onUnknownParent);
123
+ this.chain.emitter.on(ChainEvent.forkChoiceFinalized, this.pruneFinalized);
118
124
  this.chain.emitter.on(routes.events.EventType.block, this.onBlockImported);
119
125
  this.chain.emitter.on(routes.events.EventType.executionPayload, this.onPayloadImported);
120
126
  this.network.events.on(NetworkEvent.peerConnected, this.onPeerConnected);
@@ -130,6 +136,7 @@ export class BlockInputSync {
130
136
  this.chain.emitter.off(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
131
137
  this.chain.emitter.off(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
132
138
  this.chain.emitter.off(ChainEvent.blockUnknownParent, this.onUnknownParent);
139
+ this.chain.emitter.off(ChainEvent.forkChoiceFinalized, this.pruneFinalized);
133
140
  this.chain.emitter.off(routes.events.EventType.block, this.onBlockImported);
134
141
  this.chain.emitter.off(routes.events.EventType.executionPayload, this.onPayloadImported);
135
142
  this.network.events.off(NetworkEvent.peerConnected, this.onPeerConnected);
@@ -147,10 +154,12 @@ export class BlockInputSync {
147
154
  */
148
155
  onUnknownBlockRoot = (data) => {
149
156
  try {
150
- this.addByRootHex(data.rootHex, data.peer);
151
- this.triggerUnknownBlockSearch();
152
- this.metrics?.blockInputSync.requests.inc({ type: PendingBlockType.UNKNOWN_BLOCK_ROOT });
153
- this.metrics?.blockInputSync.source.inc({ source: data.source });
157
+ const isNewRoot = this.addByRootHex(data.rootHex, data.peer);
158
+ if (isNewRoot) {
159
+ this.triggerUnknownBlockSearch();
160
+ this.metrics?.blockInputSync.requests.inc({ type: PendingBlockType.UNKNOWN_BLOCK_ROOT });
161
+ this.metrics?.blockInputSync.source.inc({ source: data.source });
162
+ }
154
163
  }
155
164
  catch (e) {
156
165
  this.logger.debug("Error handling unknownBlockRoot event", {}, e);
@@ -172,10 +181,12 @@ export class BlockInputSync {
172
181
  };
173
182
  onUnknownEnvelopeBlockRoot = (data) => {
174
183
  try {
175
- this.addByPayloadRootHex(data.rootHex, data.peer, data.slot);
176
- this.triggerUnknownBlockSearch();
177
- this.metrics?.blockInputSync.requests.inc({ type: PendingBlockType.UNKNOWN_PAYLOAD_BLOCK_ROOT });
178
- this.metrics?.blockInputSync.payloadSource.inc({ source: data.source });
184
+ const isNewRoot = this.addByPayloadRootHex(data.rootHex, data.peer, this.resolvePayloadSlot(data.rootHex));
185
+ if (isNewRoot) {
186
+ this.triggerUnknownBlockSearch();
187
+ this.metrics?.blockInputSync.requests.inc({ type: PendingBlockType.UNKNOWN_PAYLOAD_BLOCK_ROOT });
188
+ this.metrics?.blockInputSync.payloadSource.inc({ source: data.source });
189
+ }
179
190
  }
180
191
  catch (e) {
181
192
  this.logger.debug("Error handling unknownEnvelopeBlockRoot event", {}, e);
@@ -208,12 +219,12 @@ export class BlockInputSync {
208
219
  parentRoot: missingDependency.parentRootHex,
209
220
  parentBlockHash: missingDependency.parentBlockHashHex,
210
221
  });
211
- this.removeAndDownScoreAllDescendants(pendingBlock);
222
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidParentPayload);
212
223
  }
213
224
  return;
214
225
  }
215
226
  if (missingDependency.kind === "parentPayload") {
216
- this.addByPayloadRootHex(missingDependency.rootHex, data.peer);
227
+ this.addByPayloadRootHex(missingDependency.rootHex, data.peer, missingDependency.slot);
217
228
  }
218
229
  else if (missingDependency.kind === "parentBlock") {
219
230
  this.addByRootHex(missingDependency.rootHex, data.peer);
@@ -236,6 +247,71 @@ export class BlockInputSync {
236
247
  this.pendingPayloads.delete(blockRoot);
237
248
  this.triggerUnknownBlockSearch();
238
249
  };
250
+ /**
251
+ * Drop pending payloads and blocks that fell below the finalized slot.
252
+ * See https://github.com/ChainSafe/lodestar/issues/9800
253
+ */
254
+ pruneFinalized = (checkpoint) => {
255
+ // Use the actual finalized block slot, not computeStartSlotAtEpoch(checkpoint.epoch)
256
+ // because the payload at the finalized block is not finalized
257
+ const finalizedSlot = this.chain.forkChoice.getFinalizedBlock().slot;
258
+ const nowSec = Date.now() / 1000;
259
+ const maxSecInCache = (PRUNE_UNRESOLVED_SLOT_EPOCHS * SLOTS_PER_EPOCH * this.config.SLOT_DURATION_MS) / 1000;
260
+ let belowFinalizedPayloads = 0;
261
+ let agedOutPayloads = 0;
262
+ let unresolvedSlotPayloads = 0;
263
+ for (const [rootHex, payload] of this.pendingPayloads) {
264
+ // Last-effort resolve for a slot-less PendingPayloadRootHex, and store it back for later passes.
265
+ if (!isPendingPayloadInput(payload) && !isPendingPayloadEnvelope(payload) && payload.slot === undefined) {
266
+ payload.slot = this.resolvePayloadSlot(rootHex);
267
+ if (payload.slot === undefined) {
268
+ // Still unresolvable: age it out so the MAX_PENDING_BLOCKS cap cannot stay saturated.
269
+ if (nowSec - payload.timeAddedSec > maxSecInCache) {
270
+ this.pendingPayloads.delete(rootHex);
271
+ agedOutPayloads++;
272
+ }
273
+ else {
274
+ unresolvedSlotPayloads++;
275
+ this.logger.debug("BlockInputSync.pruneFinalized: pending payload with unresolved slot", {
276
+ root: rootHex,
277
+ finalizedSlot,
278
+ });
279
+ }
280
+ continue;
281
+ }
282
+ }
283
+ const slot = getPayloadSyncCacheItemSlot(payload);
284
+ if (typeof slot === "number" && slot < finalizedSlot) {
285
+ this.pendingPayloads.delete(rootHex);
286
+ belowFinalizedPayloads++;
287
+ }
288
+ }
289
+ let deletedBlocks = 0;
290
+ for (const [rootHex, block] of this.pendingBlocks) {
291
+ const slot = getBlockInputSyncCacheItemSlot(block);
292
+ if (typeof slot === "number" && slot < finalizedSlot) {
293
+ this.pendingBlocks.delete(rootHex);
294
+ deletedBlocks++;
295
+ }
296
+ }
297
+ if (belowFinalizedPayloads > 0) {
298
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: DroppedItemReason.belowFinalized }, belowFinalizedPayloads);
299
+ }
300
+ if (agedOutPayloads > 0) {
301
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: DroppedItemReason.agedOut }, agedOutPayloads);
302
+ }
303
+ if (deletedBlocks > 0) {
304
+ this.metrics?.blockInputSync.removedBlocks.inc({ reason: DroppedItemReason.belowFinalized }, deletedBlocks);
305
+ }
306
+ this.logger.debug("BlockInputSync.pruneFinalized dropped pre-finalized pending items", {
307
+ finalizedSlot,
308
+ finalizedRoot: checkpoint.rootHex,
309
+ belowFinalizedPayloads,
310
+ agedOutPayloads,
311
+ deletedBlocks,
312
+ unresolvedSlotPayloads,
313
+ });
314
+ };
239
315
  addByRootHex = (rootHex, peerIdStr) => {
240
316
  let pendingBlock = this.pendingBlocks.get(rootHex);
241
317
  let added = false;
@@ -253,13 +329,16 @@ export class BlockInputSync {
253
329
  peerIdStr: peerIdStr ?? "unknown peer",
254
330
  });
255
331
  }
256
- if (peerIdStr) {
332
+ // bound peers tracked per root: propagationSource is recorded pre-validation, so cap the set (#9923).
333
+ // Once at the cap, ignore further peers rather than evicting already-tracked ones.
334
+ if (peerIdStr && pendingBlock.peerIdStrings.size < MAX_PEERS_PER_ROOT) {
257
335
  pendingBlock.peerIdStrings.add(peerIdStr);
258
336
  }
259
337
  // TODO: check this prune methodology
260
338
  // Limit pending blocks to prevent DOS attacks that cause OOM
261
339
  const prunedItemCount = pruneSetToMax(this.pendingBlocks, this.maxPendingBlocks);
262
340
  if (prunedItemCount > 0) {
341
+ this.metrics?.blockInputSync.removedBlocks.inc({ reason: DroppedItemReason.capacity }, prunedItemCount);
263
342
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingBlocks`);
264
343
  }
265
344
  return added;
@@ -283,13 +362,16 @@ export class BlockInputSync {
283
362
  delaySec: this.chain.clock.secFromSlot(blockInput.slot),
284
363
  });
285
364
  }
286
- if (peerIdStr) {
365
+ // bound peers tracked per root: propagationSource is recorded pre-validation, so cap the set (#9923).
366
+ // Once at the cap, ignore further peers rather than evicting already-tracked ones.
367
+ if (peerIdStr && pendingBlock.peerIdStrings.size < MAX_PEERS_PER_ROOT) {
287
368
  pendingBlock.peerIdStrings.add(peerIdStr);
288
369
  }
289
370
  // TODO: check this prune methodology
290
371
  // Limit pending blocks to prevent DOS attacks that cause OOM
291
372
  const prunedItemCount = pruneSetToMax(this.pendingBlocks, this.maxPendingBlocks);
292
373
  if (prunedItemCount > 0) {
374
+ this.metrics?.blockInputSync.removedBlocks.inc({ reason: DroppedItemReason.capacity }, prunedItemCount);
293
375
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingBlocks`);
294
376
  }
295
377
  };
@@ -313,18 +395,47 @@ export class BlockInputSync {
313
395
  peerIdStr: peerIdStr ?? "unknown peer",
314
396
  });
315
397
  }
316
- if (peerIdStr) {
398
+ else if (
399
+ // update a previously slot-less entry once a slot becomes available
400
+ slot !== undefined &&
401
+ !isPendingPayloadInput(pendingPayload) &&
402
+ !isPendingPayloadEnvelope(pendingPayload) &&
403
+ pendingPayload.slot === undefined) {
404
+ pendingPayload.slot = slot;
405
+ }
406
+ if (peerIdStr && pendingPayload.peerIdStrings.size < MAX_PEERS_PER_ROOT) {
317
407
  pendingPayload.peerIdStrings.add(peerIdStr);
318
408
  }
319
409
  const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
320
410
  if (prunedItemCount > 0) {
411
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: DroppedItemReason.capacity }, prunedItemCount);
321
412
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
322
413
  }
323
414
  return added;
324
415
  };
416
+ /**
417
+ * Resolve the block slot for a payload root.
418
+ */
419
+ resolvePayloadSlot(rootHex) {
420
+ // a pending block of the same root
421
+ const pendingBlock = this.pendingBlocks.get(rootHex);
422
+ if (pendingBlock) {
423
+ const pendingBlockSlot = getBlockInputSyncCacheItemSlot(pendingBlock);
424
+ if (typeof pendingBlockSlot === "number") {
425
+ return pendingBlockSlot;
426
+ }
427
+ }
428
+ // the block is in fork choice
429
+ const forkChoiceSlot = this.chain.forkChoice.getBlockHexDefaultStatus(rootHex)?.slot;
430
+ if (forkChoiceSlot !== undefined) {
431
+ return forkChoiceSlot;
432
+ }
433
+ // a seen payload envelope input
434
+ return this.chain.seenPayloadEnvelopeInputCache.get(rootHex)?.slot;
435
+ }
325
436
  addByPayloadInput = (payloadInput, peerIdStr, envelope) => {
326
437
  const pendingPayload = this.toPendingPayloadInput(payloadInput, this.pendingPayloads.get(payloadInput.blockRootHex), envelope);
327
- if (peerIdStr) {
438
+ if (peerIdStr && pendingPayload.peerIdStrings.size < MAX_PEERS_PER_ROOT) {
328
439
  pendingPayload.peerIdStrings.add(peerIdStr);
329
440
  }
330
441
  this.pendingPayloads.set(payloadInput.blockRootHex, pendingPayload);
@@ -335,6 +446,7 @@ export class BlockInputSync {
335
446
  });
336
447
  const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
337
448
  if (prunedItemCount > 0) {
449
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: DroppedItemReason.capacity }, prunedItemCount);
338
450
  this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
339
451
  }
340
452
  };
@@ -360,7 +472,8 @@ export class BlockInputSync {
360
472
  */
361
473
  getMissingBlockDependency(blockInput) {
362
474
  const parentRootHex = blockInput.parentRootHex;
363
- if (!this.chain.forkChoice.hasBlockHex(parentRootHex)) {
475
+ const parentBlock = this.chain.forkChoice.getBlockHexDefaultStatus(parentRootHex);
476
+ if (!parentBlock) {
364
477
  return { kind: "parentBlock", rootHex: parentRootHex };
365
478
  }
366
479
  if (!blockInput.hasBlock()) {
@@ -377,14 +490,8 @@ export class BlockInputSync {
377
490
  if (this.chain.forkChoice.hasPayloadHexUnsafe(parentRootHex)) {
378
491
  return { kind: "invalidParentPayload", parentRootHex, parentBlockHashHex };
379
492
  }
380
- const parentPayloadInput = this.chain.seenPayloadEnvelopeInputCache.get(parentRootHex);
381
- if (parentPayloadInput) {
382
- if (parentPayloadInput.getBlockHashHex() === parentBlockHashHex) {
383
- return { kind: "parentPayload", rootHex: parentRootHex };
384
- }
385
- return { kind: "invalidParentPayload", parentRootHex, parentBlockHashHex };
386
- }
387
- return { kind: "parentPayload", rootHex: parentRootHex };
493
+ // Parent is in fork choice but its payload `parentBlockHashHex` isn't revealed yet
494
+ return { kind: "parentPayload", rootHex: parentRootHex, slot: parentBlock.slot };
388
495
  }
389
496
  advancePendingBlock(pendingBlock) {
390
497
  const missingDependency = this.getMissingBlockDependency(pendingBlock.blockInput);
@@ -402,9 +509,9 @@ export class BlockInputSync {
402
509
  return added ? "queued_parent_block" : "blocked";
403
510
  }
404
511
  case "parentPayload": {
405
- let added = this.addByPayloadRootHex(missingDependency.rootHex);
512
+ let added = this.addByPayloadRootHex(missingDependency.rootHex, undefined, missingDependency.slot);
406
513
  for (const peerIdStr of pendingBlock.peerIdStrings) {
407
- added = this.addByPayloadRootHex(missingDependency.rootHex, peerIdStr) || added;
514
+ added = this.addByPayloadRootHex(missingDependency.rootHex, peerIdStr, missingDependency.slot) || added;
408
515
  }
409
516
  return added ? "queued_parent_payload" : "blocked";
410
517
  }
@@ -415,7 +522,7 @@ export class BlockInputSync {
415
522
  parentRoot: missingDependency.parentRootHex,
416
523
  parentBlockHash: missingDependency.parentBlockHashHex,
417
524
  });
418
- this.removeAndDownScoreAllDescendants(pendingBlock);
525
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidParentPayload);
419
526
  return "removed";
420
527
  }
421
528
  }
@@ -604,7 +711,7 @@ export class BlockInputSync {
604
711
  ...logCtx2,
605
712
  finalizedSlot,
606
713
  });
607
- this.removeAndDownScoreAllDescendants(block);
714
+ this.removeAndDownScoreAllDescendants(block, DroppedItemReason.belowFinalized);
608
715
  }
609
716
  else {
610
717
  this.onUnknownBlockRoot({ rootHex: pending.blockInput.parentRootHex, source: BlockInputSource.byRoot });
@@ -622,7 +729,7 @@ export class BlockInputSync {
622
729
  }
623
730
  this.metrics?.blockInputSync.downloadedBlocksError.inc();
624
731
  this.logger.debug("Ignoring unknown block root after many failed downloads", logCtx, res.err);
625
- this.removeAndDownScoreAllDescendants(block);
732
+ this.removeAndDownScoreAllDescendants(block, DroppedItemReason.unavailable);
626
733
  }
627
734
  }
628
735
  /**
@@ -679,9 +786,9 @@ export class BlockInputSync {
679
786
  // no need to update status to "processed", delete anyway
680
787
  this.pendingBlocks.delete(blockRootHex);
681
788
  if (isForkPostGloas(fork)) {
682
- const payloadInput = this.chain.seenPayloadEnvelopeInputCache.get(blockRootHex);
789
+ const payloadInput = await this.chain.seenPayloadEnvelopeInputCache.getOrReload(blockRootHex);
683
790
  if (!payloadInput) {
684
- this.logger.warn("PayloadEnvelopeInput not seeded during unknown sync processReadyBlock()", logCtx);
791
+ this.logger.debug("PayloadEnvelopeInput unavailable during unknown sync processReadyBlock()", logCtx);
685
792
  }
686
793
  else {
687
794
  // Similar to the gossip path: immediately attempt fetch of data columns from the execution engine.
@@ -717,17 +824,17 @@ export class BlockInputSync {
717
824
  case BlockErrorCode.EXECUTION_ENGINE_ERROR:
718
825
  // Removing the block(s) without penalizing the peers, hoping for EL to
719
826
  // recover on a latter download + verify attempt.
720
- this.removeAllDescendants(pendingBlock);
827
+ this.removeAllDescendants(pendingBlock, DroppedItemReason.elError, DroppedItemReason.elError);
721
828
  break;
722
829
  case BlockErrorCode.EXECUTION_ENGINE_INVALID:
723
830
  // the peer served a bad block
724
831
  this.logger.debug("Execution engine rejected block from unknown parent sync", errorData, res.err);
725
- this.removeAndDownScoreAllDescendants(pendingBlock);
832
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.elInvalid);
726
833
  break;
727
834
  default:
728
835
  // Block is not correct with respect to our chain. Log error loudly
729
836
  this.logger.debug("Error processing block from unknown parent sync", errorData, res.err);
730
- this.removeAndDownScoreAllDescendants(pendingBlock);
837
+ this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidBlock);
731
838
  }
732
839
  }
733
840
  // Probably a queue error or something unwanted happened, mark as pending to try again latter
@@ -748,7 +855,6 @@ export class BlockInputSync {
748
855
  this.pendingPayloads.delete(rootHex);
749
856
  return;
750
857
  }
751
- const payloadInput = this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
752
858
  if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
753
859
  // Block not in fork choice yet. payloadInput may be seeded from the block body during download, so a
754
860
  // non-null payloadInput does not imply the block is imported; defer regardless and pull the block first.
@@ -762,8 +868,9 @@ export class BlockInputSync {
762
868
  }
763
869
  return;
764
870
  }
871
+ const payloadInput = await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
765
872
  if (!payloadInput) {
766
- this.logger.debug("Missing PayloadEnvelopeInput for known block while reconciling payload envelope", {
873
+ this.logger.debug("PayloadEnvelopeInput not yet reloadable for imported block, will retry", {
767
874
  root: rootHex,
768
875
  });
769
876
  return;
@@ -775,6 +882,7 @@ export class BlockInputSync {
775
882
  const pendingPayloadByRoot = {
776
883
  status: PendingPayloadInputStatus.pending,
777
884
  rootHex,
885
+ slot: pendingPayload.envelope.message.payload.slotNumber,
778
886
  timeAddedSec: pendingPayload.timeAddedSec,
779
887
  peerIdStrings: new Set(pendingPayload.peerIdStrings),
780
888
  };
@@ -806,6 +914,9 @@ export class BlockInputSync {
806
914
  if (payload.status !== PendingPayloadInputStatus.pending) {
807
915
  return;
808
916
  }
917
+ if (!isPendingPayloadInput(payload) && payload.slot === undefined) {
918
+ payload.slot = this.resolvePayloadSlot(rootHex);
919
+ }
809
920
  const payloadSlot = getPayloadSyncCacheItemSlot(payload);
810
921
  const logCtx = {
811
922
  slot: payloadSlot,
@@ -818,7 +929,13 @@ export class BlockInputSync {
818
929
  const res = await wrapError(this.fetchPayloadInput(payload));
819
930
  if (!res.err) {
820
931
  const pendingPayload = res.result;
821
- this.pendingPayloads.set(getPayloadSyncCacheItemRootHex(pendingPayload), pendingPayload);
932
+ const resultRootHex = getPayloadSyncCacheItemRootHex(pendingPayload);
933
+ // finalization may have deleted this entry while we were awaiting the network, do not resurrect it via the set below
934
+ if (!this.pendingPayloads.has(resultRootHex)) {
935
+ this.logger.verbose("Dropping downloaded payload, entry pruned during fetch", logCtx);
936
+ return;
937
+ }
938
+ this.pendingPayloads.set(resultRootHex, pendingPayload);
822
939
  if (isPendingPayloadEnvelope(pendingPayload)) {
823
940
  await this.reconcilePayloadEnvelope(pendingPayload);
824
941
  }
@@ -885,16 +1002,24 @@ export class BlockInputSync {
885
1002
  pendingPayload.status = PendingPayloadInputStatus.downloaded;
886
1003
  break;
887
1004
  case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
1005
+ this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1006
+ this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.elInvalid);
1007
+ break;
888
1008
  case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
1009
+ this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1010
+ this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidEnvelope);
1011
+ break;
889
1012
  case PayloadErrorCode.INVALID_SIGNATURE:
890
1013
  // TODO GLOAS: Decide how invalid payload inputs should eventually leave memory without
891
1014
  // reintroducing envelope replacement / recreation flows.
892
1015
  this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
893
- this.removePendingPayloadAndDescendants(rootHex);
1016
+ this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidSignature);
894
1017
  break;
895
1018
  default:
896
1019
  this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
897
- this.pendingPayloads.delete(rootHex);
1020
+ if (this.pendingPayloads.delete(rootHex)) {
1021
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: DroppedItemReason.unknown }, 1);
1022
+ }
898
1023
  }
899
1024
  return;
900
1025
  }
@@ -913,7 +1038,7 @@ export class BlockInputSync {
913
1038
  let slot = getPayloadSyncCacheItemSlot(cacheItem);
914
1039
  let payloadInput = isPendingPayloadInput(cacheItem)
915
1040
  ? cacheItem.payloadInput
916
- : this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
1041
+ : await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
917
1042
  let envelope = payloadInput?.hasPayloadEnvelope() ? payloadInput.getPayloadEnvelope() : undefined;
918
1043
  let i = 0;
919
1044
  let deferredByRateLimit = false;
@@ -921,12 +1046,16 @@ export class BlockInputSync {
921
1046
  const pendingColumns = payloadInput?.hasAllData()
922
1047
  ? new Set()
923
1048
  : new Set(payloadInput?.getMissingSampledColumnMeta().missing ?? []);
924
- const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
1049
+ // prefer peers that gossiped this payload root to us (#9923)
1050
+ const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers, cacheItem.peerIdStrings);
925
1051
  if (peerMeta === null) {
926
1052
  if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
927
1053
  throw new UnknownBlockRateLimitedError(`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`);
928
1054
  }
929
- throw Error(`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`);
1055
+ const reason = pendingColumns.size > 0
1056
+ ? `cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`
1057
+ : "no peer available to download pending payload";
1058
+ throw Error(`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: ${reason}`);
930
1059
  }
931
1060
  const { peerId, client: peerClient } = peerMeta;
932
1061
  cacheItem.peerIdStrings.add(peerId);
@@ -935,7 +1064,7 @@ export class BlockInputSync {
935
1064
  envelope = await this.fetchExecutionPayloadEnvelope(peerId, blockRoot, rootHex);
936
1065
  slot = envelope.message.payload.slotNumber;
937
1066
  }
938
- payloadInput ??= this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
1067
+ payloadInput ??= await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
939
1068
  if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
940
1069
  // Block not in fork choice yet. Validating now would throw BLOCK_ROOT_UNKNOWN, so keep the downloaded
941
1070
  // envelope and wait for the block body; reconcilePayloadEnvelope validates once the block lands.
@@ -1080,7 +1209,8 @@ export class BlockInputSync {
1080
1209
  const pendingColumns = isPendingBlockInput(cacheItem) && isBlockInputColumns(cacheItem.blockInput)
1081
1210
  ? new Set(cacheItem.blockInput.getMissingSampledColumnMeta().missing)
1082
1211
  : defaultPendingColumns;
1083
- const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
1212
+ // prefer peers that gossiped this block root to us (#9923)
1213
+ const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers, cacheItem.peerIdStrings);
1084
1214
  if (peerMeta === null) {
1085
1215
  if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
1086
1216
  throw new UnknownBlockRateLimitedError(`Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`);
@@ -1206,9 +1336,9 @@ export class BlockInputSync {
1206
1336
  * Downscore all peers that have referenced any of this bad blocks. May report peers multiple times if they have
1207
1337
  * referenced more than one bad block.
1208
1338
  */
1209
- removeAndDownScoreAllDescendants(block) {
1339
+ removeAndDownScoreAllDescendants(block, headReason) {
1210
1340
  // Get all blocks that are a descendant of this one
1211
- const badPendingBlocks = this.removeAllDescendants(block);
1341
+ const badPendingBlocks = this.removeAllDescendants(block, headReason, DroppedItemReason.invalidParent);
1212
1342
  // just console log and do not penalize on pending/bad blocks for debugging
1213
1343
  // console.log("removeAndDownscoreAllDescendants", {block});
1214
1344
  for (const block of badPendingBlocks) {
@@ -1232,16 +1362,21 @@ export class BlockInputSync {
1232
1362
  pruneSetToMax(this.knownBadBlocks, MAX_KNOWN_BAD_BLOCKS);
1233
1363
  }
1234
1364
  // Once a parent payload is invalid, every descendant waiting on that payload lineage becomes unrecoverable too.
1235
- removePendingPayloadAndDescendants(rootHex) {
1365
+ removePendingPayloadAndDescendants(rootHex, headReason) {
1236
1366
  // Keep PayloadEnvelopeInput resident in the seen cache. importBlock() owns that object and
1237
1367
  // later validation/finalization logic decides when it can leave memory.
1238
- this.pendingPayloads.delete(rootHex);
1368
+ if (this.pendingPayloads.delete(rootHex)) {
1369
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: headReason }, 1);
1370
+ }
1239
1371
  const badPendingBlocks = getAllDescendantBlocks(rootHex, this.pendingBlocks);
1240
- this.metrics?.blockInputSync.removedBlocks.inc(badPendingBlocks.length);
1241
1372
  for (const block of badPendingBlocks) {
1242
1373
  const descendantRootHex = getBlockInputSyncCacheItemRootHex(block);
1243
- this.pendingBlocks.delete(descendantRootHex);
1244
- this.pendingPayloads.delete(descendantRootHex);
1374
+ if (this.pendingBlocks.delete(descendantRootHex)) {
1375
+ this.metrics?.blockInputSync.removedBlocks.inc({ reason: DroppedItemReason.invalidParent }, 1);
1376
+ }
1377
+ if (this.pendingPayloads.delete(descendantRootHex)) {
1378
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason: DroppedItemReason.invalidParent }, 1);
1379
+ }
1245
1380
  this.chain.seenBlockInputCache.prune(descendantRootHex);
1246
1381
  this.logger.debug("Removing pending descendant after invalid parent payload", {
1247
1382
  slot: getBlockInputSyncCacheItemSlot(block),
@@ -1250,16 +1385,20 @@ export class BlockInputSync {
1250
1385
  });
1251
1386
  }
1252
1387
  }
1253
- removeAllDescendants(block) {
1388
+ removeAllDescendants(block, headReason, descendantReason = DroppedItemReason.invalidParent) {
1254
1389
  const rootHex = getBlockInputSyncCacheItemRootHex(block);
1255
1390
  const slot = getBlockInputSyncCacheItemSlot(block);
1256
- // Get all blocks that are a descendant of this one
1391
+ // Get all blocks that are a descendant of this one (index 0 is the head item itself)
1257
1392
  const badPendingBlocks = [block, ...getAllDescendantBlocks(rootHex, this.pendingBlocks)];
1258
- this.metrics?.blockInputSync.removedBlocks.inc(badPendingBlocks.length);
1259
- for (const block of badPendingBlocks) {
1393
+ for (const [i, block] of badPendingBlocks.entries()) {
1260
1394
  const rootHex = getBlockInputSyncCacheItemRootHex(block);
1261
- this.pendingBlocks.delete(rootHex);
1262
- this.pendingPayloads.delete(rootHex);
1395
+ const reason = i === 0 ? headReason : descendantReason;
1396
+ if (this.pendingBlocks.delete(rootHex)) {
1397
+ this.metrics?.blockInputSync.removedBlocks.inc({ reason }, 1);
1398
+ }
1399
+ if (this.pendingPayloads.delete(rootHex)) {
1400
+ this.metrics?.blockInputSync.removedPayloads.inc({ reason }, 1);
1401
+ }
1263
1402
  this.chain.seenBlockInputCache.prune(rootHex);
1264
1403
  // Keep PayloadEnvelopeInput resident in the seen cache for consistency with the
1265
1404
  // importBlock()-owned lifecycle.
@@ -1330,17 +1469,21 @@ export class UnknownBlockPeerBalancer {
1330
1469
  return retryAt;
1331
1470
  }
1332
1471
  /**
1333
- * called from fetchBlockInput() where we only have block root and nothing else
1472
+ * called from fetchBlockInput()/fetchPayloadInput() with a block/payload root.
1334
1473
  * excludedPeers are the peers that we requested already so we don't want to try again
1335
1474
  * pendingColumns is empty for prefulu, or the 1st time we we download a block by root
1475
+ * preferredPeers are the peers NetworkProcessor told us gossiped this root (from
1476
+ * pendingBlock/cacheItem.peerIdStrings). They are preferred but not required.
1336
1477
  */
1337
- bestPeerForPendingColumns(pendingColumns, excludedPeers) {
1478
+ bestPeerForPendingColumns(pendingColumns, excludedPeers, preferredPeers) {
1338
1479
  const eligiblePeers = this.filterPeers(pendingColumns, excludedPeers);
1339
1480
  if (eligiblePeers.length === 0) {
1340
1481
  return null;
1341
1482
  }
1342
1483
  const sortedEligiblePeers = sortBy(shuffle(eligiblePeers),
1343
- // prefer peers with least active req
1484
+ // 1st key: prefer peers we heard about this root from
1485
+ (peerId) => (preferredPeers?.has(peerId) ? 0 : 1),
1486
+ // 2nd key (tiebreak): prefer peers with least active req
1344
1487
  (peerId) => this.activeRequests.get(peerId) ?? 0);
1345
1488
  const bestPeerId = sortedEligiblePeers[0];
1346
1489
  this.onRequest(bestPeerId);