@lodestar/beacon-node 1.44.0-dev.6ef8199cfa → 1.44.0-dev.762dd5f186

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 (238) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +66 -33
  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 +1 -26
  6. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  7. package/lib/api/impl/config/constants.d.ts +5 -0
  8. package/lib/api/impl/config/constants.d.ts.map +1 -1
  9. package/lib/api/impl/config/constants.js +6 -1
  10. package/lib/api/impl/config/constants.js.map +1 -1
  11. package/lib/api/impl/debug/index.d.ts.map +1 -1
  12. package/lib/api/impl/debug/index.js +69 -12
  13. package/lib/api/impl/debug/index.js.map +1 -1
  14. package/lib/api/impl/lodestar/index.d.ts.map +1 -1
  15. package/lib/api/impl/lodestar/index.js +28 -0
  16. package/lib/api/impl/lodestar/index.js.map +1 -1
  17. package/lib/api/impl/validator/index.d.ts.map +1 -1
  18. package/lib/api/impl/validator/index.js +117 -72
  19. package/lib/api/impl/validator/index.js.map +1 -1
  20. package/lib/chain/archiveStore/archiveStore.d.ts +0 -1
  21. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  22. package/lib/chain/archiveStore/archiveStore.js +0 -4
  23. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  24. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  25. package/lib/chain/blocks/importBlock.js +19 -4
  26. package/lib/chain/blocks/importBlock.js.map +1 -1
  27. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  28. package/lib/chain/blocks/importExecutionPayload.js +5 -3
  29. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  30. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  31. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +3 -3
  32. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  33. package/lib/chain/chain.d.ts +3 -3
  34. package/lib/chain/chain.d.ts.map +1 -1
  35. package/lib/chain/chain.js +11 -3
  36. package/lib/chain/chain.js.map +1 -1
  37. package/lib/chain/emitter.d.ts +2 -1
  38. package/lib/chain/emitter.d.ts.map +1 -1
  39. package/lib/chain/emitter.js.map +1 -1
  40. package/lib/chain/errors/blockError.d.ts +0 -7
  41. package/lib/chain/errors/blockError.d.ts.map +1 -1
  42. package/lib/chain/errors/blockError.js +0 -3
  43. package/lib/chain/errors/blockError.js.map +1 -1
  44. package/lib/chain/errors/executionPayloadBid.d.ts +23 -1
  45. package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
  46. package/lib/chain/errors/executionPayloadBid.js +4 -0
  47. package/lib/chain/errors/executionPayloadBid.js.map +1 -1
  48. package/lib/chain/errors/payloadAttestation.d.ts +6 -0
  49. package/lib/chain/errors/payloadAttestation.d.ts.map +1 -1
  50. package/lib/chain/errors/payloadAttestation.js +1 -0
  51. package/lib/chain/errors/payloadAttestation.js.map +1 -1
  52. package/lib/chain/forkChoice/index.d.ts +4 -4
  53. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  54. package/lib/chain/forkChoice/index.js +24 -11
  55. package/lib/chain/forkChoice/index.js.map +1 -1
  56. package/lib/chain/interface.d.ts +2 -2
  57. package/lib/chain/interface.d.ts.map +1 -1
  58. package/lib/chain/interface.js.map +1 -1
  59. package/lib/chain/opPools/executionPayloadBidPool.d.ts +4 -4
  60. package/lib/chain/opPools/executionPayloadBidPool.d.ts.map +1 -1
  61. package/lib/chain/opPools/executionPayloadBidPool.js +6 -4
  62. package/lib/chain/opPools/executionPayloadBidPool.js.map +1 -1
  63. package/lib/chain/options.d.ts.map +1 -1
  64. package/lib/chain/options.js +1 -0
  65. package/lib/chain/options.js.map +1 -1
  66. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  67. package/lib/chain/prepareNextSlot.js +2 -10
  68. package/lib/chain/prepareNextSlot.js.map +1 -1
  69. package/lib/chain/produceBlock/produceBlockBody.d.ts +4 -2
  70. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  71. package/lib/chain/produceBlock/produceBlockBody.js +81 -27
  72. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  73. package/lib/chain/regen/interface.d.ts +2 -1
  74. package/lib/chain/regen/interface.d.ts.map +1 -1
  75. package/lib/chain/regen/interface.js +2 -0
  76. package/lib/chain/regen/interface.js.map +1 -1
  77. package/lib/chain/regen/queued.d.ts +0 -1
  78. package/lib/chain/regen/queued.d.ts.map +1 -1
  79. package/lib/chain/regen/queued.js +0 -4
  80. package/lib/chain/regen/queued.js.map +1 -1
  81. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  82. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +8 -2
  83. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  84. package/lib/chain/stateCache/fifoBlockStateCache.d.ts +0 -5
  85. package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
  86. package/lib/chain/stateCache/fifoBlockStateCache.js +0 -5
  87. package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
  88. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +1 -4
  89. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  90. package/lib/chain/stateCache/persistentCheckpointsCache.js +5 -2
  91. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  92. package/lib/chain/stateCache/types.d.ts +0 -2
  93. package/lib/chain/stateCache/types.d.ts.map +1 -1
  94. package/lib/chain/stateCache/types.js.map +1 -1
  95. package/lib/chain/validation/block.d.ts +5 -1
  96. package/lib/chain/validation/block.d.ts.map +1 -1
  97. package/lib/chain/validation/block.js +4 -14
  98. package/lib/chain/validation/block.js.map +1 -1
  99. package/lib/chain/validation/executionPayloadBid.d.ts +7 -3
  100. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  101. package/lib/chain/validation/executionPayloadBid.js +81 -16
  102. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  103. package/lib/chain/validation/executionPayloadEnvelope.js +1 -3
  104. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  105. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  106. package/lib/chain/validation/payloadAttestationMessage.js +24 -4
  107. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  108. package/lib/chain/validatorMonitor.d.ts +1 -0
  109. package/lib/chain/validatorMonitor.d.ts.map +1 -1
  110. package/lib/chain/validatorMonitor.js +16 -0
  111. package/lib/chain/validatorMonitor.js.map +1 -1
  112. package/lib/execution/builder/index.d.ts +1 -2
  113. package/lib/execution/builder/index.d.ts.map +1 -1
  114. package/lib/execution/builder/index.js +0 -1
  115. package/lib/execution/builder/index.js.map +1 -1
  116. package/lib/execution/engine/http.js +1 -1
  117. package/lib/execution/engine/http.js.map +1 -1
  118. package/lib/execution/engine/interface.d.ts +2 -2
  119. package/lib/execution/engine/interface.d.ts.map +1 -1
  120. package/lib/execution/engine/interface.js +6 -2
  121. package/lib/execution/engine/interface.js.map +1 -1
  122. package/lib/execution/engine/mock.d.ts.map +1 -1
  123. package/lib/execution/engine/mock.js +3 -5
  124. package/lib/execution/engine/mock.js.map +1 -1
  125. package/lib/execution/engine/types.d.ts +15 -7
  126. package/lib/execution/engine/types.d.ts.map +1 -1
  127. package/lib/execution/engine/types.js +47 -9
  128. package/lib/execution/engine/types.js.map +1 -1
  129. package/lib/metrics/metrics/lodestar.d.ts +5 -1
  130. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  131. package/lib/metrics/metrics/lodestar.js +14 -3
  132. package/lib/metrics/metrics/lodestar.js.map +1 -1
  133. package/lib/network/gossip/topic.d.ts +770 -3
  134. package/lib/network/gossip/topic.d.ts.map +1 -1
  135. package/lib/network/interface.d.ts +1 -0
  136. package/lib/network/interface.d.ts.map +1 -1
  137. package/lib/network/network.d.ts +1 -0
  138. package/lib/network/network.d.ts.map +1 -1
  139. package/lib/network/network.js +5 -0
  140. package/lib/network/network.js.map +1 -1
  141. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  142. package/lib/network/processor/gossipHandlers.js +21 -6
  143. package/lib/network/processor/gossipHandlers.js.map +1 -1
  144. package/lib/network/processor/index.d.ts +2 -2
  145. package/lib/network/processor/index.d.ts.map +1 -1
  146. package/lib/network/processor/index.js +25 -23
  147. package/lib/network/processor/index.js.map +1 -1
  148. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
  149. package/lib/network/reqresp/handlers/beaconBlocksByRange.js +9 -5
  150. package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
  151. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  152. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +13 -3
  153. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  154. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +1 -1
  155. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
  156. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts +2 -1
  157. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
  158. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +16 -6
  159. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
  160. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.d.ts +2 -1
  161. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.d.ts.map +1 -1
  162. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js +15 -1
  163. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js.map +1 -1
  164. package/lib/network/reqresp/handlers/index.js +4 -4
  165. package/lib/network/reqresp/handlers/index.js.map +1 -1
  166. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts.map +1 -1
  167. package/lib/network/reqresp/utils/dataColumnResponseValidation.js +23 -5
  168. package/lib/network/reqresp/utils/dataColumnResponseValidation.js.map +1 -1
  169. package/lib/sync/types.d.ts +9 -1
  170. package/lib/sync/types.d.ts.map +1 -1
  171. package/lib/sync/types.js +9 -2
  172. package/lib/sync/types.js.map +1 -1
  173. package/lib/sync/unknownBlock.d.ts.map +1 -1
  174. package/lib/sync/unknownBlock.js +64 -30
  175. package/lib/sync/unknownBlock.js.map +1 -1
  176. package/lib/util/dataColumns.d.ts.map +1 -1
  177. package/lib/util/dataColumns.js +16 -11
  178. package/lib/util/dataColumns.js.map +1 -1
  179. package/lib/util/sszBytes.js +1 -1
  180. package/package.json +15 -17
  181. package/src/api/impl/beacon/blocks/index.ts +76 -42
  182. package/src/api/impl/beacon/pool/index.ts +4 -42
  183. package/src/api/impl/config/constants.ts +10 -0
  184. package/src/api/impl/debug/index.ts +73 -12
  185. package/src/api/impl/lodestar/index.ts +30 -0
  186. package/src/api/impl/validator/index.ts +137 -86
  187. package/src/chain/archiveStore/archiveStore.ts +0 -5
  188. package/src/chain/blocks/importBlock.ts +29 -7
  189. package/src/chain/blocks/importExecutionPayload.ts +8 -2
  190. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +3 -5
  191. package/src/chain/chain.ts +14 -4
  192. package/src/chain/emitter.ts +3 -2
  193. package/src/chain/errors/blockError.ts +0 -4
  194. package/src/chain/errors/executionPayloadBid.ts +20 -1
  195. package/src/chain/errors/payloadAttestation.ts +2 -0
  196. package/src/chain/forkChoice/index.ts +27 -4
  197. package/src/chain/interface.ts +1 -2
  198. package/src/chain/opPools/executionPayloadBidPool.ts +10 -9
  199. package/src/chain/options.ts +1 -0
  200. package/src/chain/prepareNextSlot.ts +2 -11
  201. package/src/chain/produceBlock/produceBlockBody.ts +114 -40
  202. package/src/chain/regen/interface.ts +2 -1
  203. package/src/chain/regen/queued.ts +0 -5
  204. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +8 -3
  205. package/src/chain/stateCache/fifoBlockStateCache.ts +0 -6
  206. package/src/chain/stateCache/persistentCheckpointsCache.ts +6 -2
  207. package/src/chain/stateCache/types.ts +0 -2
  208. package/src/chain/validation/block.ts +12 -16
  209. package/src/chain/validation/executionPayloadBid.ts +90 -19
  210. package/src/chain/validation/executionPayloadEnvelope.ts +1 -3
  211. package/src/chain/validation/payloadAttestationMessage.ts +26 -4
  212. package/src/chain/validatorMonitor.ts +18 -0
  213. package/src/execution/builder/index.ts +1 -4
  214. package/src/execution/engine/http.ts +1 -1
  215. package/src/execution/engine/interface.ts +12 -2
  216. package/src/execution/engine/mock.ts +6 -5
  217. package/src/execution/engine/types.ts +75 -14
  218. package/src/metrics/metrics/lodestar.ts +15 -3
  219. package/src/network/interface.ts +1 -0
  220. package/src/network/network.ts +11 -0
  221. package/src/network/processor/gossipHandlers.ts +26 -6
  222. package/src/network/processor/index.ts +27 -27
  223. package/src/network/reqresp/handlers/beaconBlocksByRange.ts +12 -5
  224. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +17 -3
  225. package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +1 -1
  226. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +22 -6
  227. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRoot.ts +20 -1
  228. package/src/network/reqresp/handlers/index.ts +4 -4
  229. package/src/network/reqresp/utils/dataColumnResponseValidation.ts +22 -5
  230. package/src/sync/types.ts +11 -2
  231. package/src/sync/unknownBlock.ts +70 -31
  232. package/src/util/dataColumns.ts +17 -12
  233. package/src/util/sszBytes.ts +1 -1
  234. package/lib/execution/builder/utils.d.ts +0 -5
  235. package/lib/execution/builder/utils.d.ts.map +0 -1
  236. package/lib/execution/builder/utils.js +0 -17
  237. package/lib/execution/builder/utils.js.map +0 -1
  238. package/src/execution/builder/utils.ts +0 -19
@@ -1,7 +1,9 @@
1
+ import {routes} from "@lodestar/api";
1
2
  import {ChainForkConfig} from "@lodestar/config";
2
3
  import {
3
4
  ExecutionStatus,
4
5
  ForkChoice,
6
+ ForkChoiceStateGetter,
5
7
  ForkChoiceStore,
6
8
  JustifiedBalancesGetter,
7
9
  PayloadStatus,
@@ -45,6 +47,7 @@ export function initializeForkChoice(
45
47
  isFinalizedState: boolean,
46
48
  opts: ForkChoiceOpts,
47
49
  justifiedBalancesGetter: JustifiedBalancesGetter,
50
+ stateGetter: ForkChoiceStateGetter,
48
51
  metrics: Metrics | null,
49
52
  logger?: Logger
50
53
  ): ForkChoice {
@@ -56,6 +59,7 @@ export function initializeForkChoice(
56
59
  state,
57
60
  opts,
58
61
  justifiedBalancesGetter,
62
+ stateGetter,
59
63
  metrics,
60
64
  logger
61
65
  )
@@ -66,6 +70,7 @@ export function initializeForkChoice(
66
70
  state,
67
71
  opts,
68
72
  justifiedBalancesGetter,
73
+ stateGetter,
69
74
  metrics,
70
75
  logger
71
76
  );
@@ -81,6 +86,7 @@ export function initializeForkChoiceFromFinalizedState(
81
86
  state: IBeaconStateView,
82
87
  opts: ForkChoiceOpts,
83
88
  justifiedBalancesGetter: JustifiedBalancesGetter,
89
+ stateGetter: ForkChoiceStateGetter,
84
90
  metrics: Metrics | null,
85
91
  logger?: Logger
86
92
  ): ForkChoice {
@@ -112,9 +118,12 @@ export function initializeForkChoiceFromFinalizedState(
112
118
  finalizedCheckpoint,
113
119
  justifiedBalances,
114
120
  justifiedBalancesGetter,
121
+ stateGetter,
115
122
  {
116
123
  onJustified: (cp) => emitter.emit(ChainEvent.forkChoiceJustified, cp),
117
124
  onFinalized: (cp) => emitter.emit(ChainEvent.forkChoiceFinalized, cp),
125
+ onFastConfirmation: ({block, slot, currentSlot}) =>
126
+ emitter.emit(routes.events.EventType.fastConfirmation, {block, slot, currentSlot}),
118
127
  }
119
128
  ),
120
129
 
@@ -140,9 +149,11 @@ export function initializeForkChoiceFromFinalizedState(
140
149
  executionPayloadBlockHash: isStatePostGloas(state)
141
150
  ? toRootHex(state.latestBlockHash)
142
151
  : toRootHex(state.latestExecutionPayloadHeader.blockHash),
143
- // TODO GLOAS: executionPayloadNumber is not tracked in BeaconState post-gloas (EIP-7732 removed
144
- // latestExecutionPayloadHeader). Using 0 as unavailable fallback until a solution is found.
152
+ // TODO GLOAS: executionPayloadNumber/GasLimit are not tracked in BeaconState post-gloas
153
+ // (EIP-7732 removed latestExecutionPayloadHeader). Using 0 as unavailable fallback
154
+ // see initializeForkChoiceFromUnfinalizedState for the same caveat on validation.
145
155
  executionPayloadNumber: isStatePostGloas(state) ? 0 : state.payloadBlockNumber,
156
+ executionPayloadGasLimit: isStatePostGloas(state) ? 0 : state.latestExecutionPayloadHeader.gasLimit,
146
157
  executionStatus: blockHeader.slot === GENESIS_SLOT ? ExecutionStatus.Valid : ExecutionStatus.Syncing,
147
158
  }
148
159
  : {executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}),
@@ -170,6 +181,7 @@ export function initializeForkChoiceFromUnfinalizedState(
170
181
  unfinalizedState: IBeaconStateView,
171
182
  opts: ForkChoiceOpts,
172
183
  justifiedBalancesGetter: JustifiedBalancesGetter,
184
+ stateGetter: ForkChoiceStateGetter,
173
185
  metrics: Metrics | null,
174
186
  logger?: Logger
175
187
  ): ForkChoice {
@@ -201,9 +213,12 @@ export function initializeForkChoiceFromUnfinalizedState(
201
213
  finalizedCheckpoint,
202
214
  justifiedBalances,
203
215
  justifiedBalancesGetter,
216
+ stateGetter,
204
217
  {
205
218
  onJustified: (cp) => emitter.emit(ChainEvent.forkChoiceJustified, cp),
206
219
  onFinalized: (cp) => emitter.emit(ChainEvent.forkChoiceFinalized, cp),
220
+ onFastConfirmation: ({block, slot, currentSlot}) =>
221
+ emitter.emit(routes.events.EventType.fastConfirmation, {block, slot, currentSlot}),
207
222
  }
208
223
  );
209
224
 
@@ -232,9 +247,17 @@ export function initializeForkChoiceFromUnfinalizedState(
232
247
  executionPayloadBlockHash: isStatePostGloas(unfinalizedState)
233
248
  ? toRootHex(unfinalizedState.latestBlockHash)
234
249
  : toRootHex(unfinalizedState.latestExecutionPayloadHeader.blockHash),
235
- // TODO GLOAS: executionPayloadNumber is not tracked in BeaconState post-gloas (EIP-7732 removed
236
- // latestExecutionPayloadHeader). Using 0 as unavailable fallback until a solution is found.
250
+ // TODO GLOAS: executionPayloadNumber/GasLimit are not tracked in BeaconState post-gloas
251
+ // (EIP-7732 removed latestExecutionPayloadHeader). Using 0 as unavailable fallback until
252
+ // a solution is found. The 0 doesn't gate validation in practice: at boot the head's
253
+ // PENDING variant's `executionPayloadBlockHash` is the *parent's* payload hash (per the
254
+ // PENDING/EMPTY convention), so gossip bids that reference the head's *own* payload
255
+ // hash won't match this variant anyway and will IGNORE until `onExecutionPayload`
256
+ // upgrades the head to FULL with real values.
237
257
  executionPayloadNumber: isStatePostGloas(unfinalizedState) ? 0 : unfinalizedState.payloadBlockNumber,
258
+ executionPayloadGasLimit: isStatePostGloas(unfinalizedState)
259
+ ? 0
260
+ : unfinalizedState.latestExecutionPayloadHeader.gasLimit,
238
261
  executionStatus: blockHeader.slot === GENESIS_SLOT ? ExecutionStatus.Valid : ExecutionStatus.Syncing,
239
262
  }
240
263
  : {executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}),
@@ -18,7 +18,6 @@ import {
18
18
  altair,
19
19
  capella,
20
20
  deneb,
21
- electra,
22
21
  gloas,
23
22
  phase0,
24
23
  rewards,
@@ -236,7 +235,7 @@ export interface IBeaconChain {
236
235
  blockSlot: Slot,
237
236
  blockRootHex: string
238
237
  ): Promise<gloas.SignedExecutionPayloadEnvelope | null>;
239
- getParentExecutionRequests(parentBlockSlot: Slot, parentBlockRootHex: RootHex): Promise<electra.ExecutionRequests>;
238
+ getParentExecutionRequests(parentBlockSlot: Slot, parentBlockRootHex: RootHex): Promise<gloas.ExecutionRequests>;
240
239
 
241
240
  produceCommonBlockBody(blockAttributes: BlockAttributes): Promise<CommonBlockBody>;
242
241
  produceBlock(blockAttributes: BlockAttributes & {commonBlockBodyPromise: Promise<CommonBlockBody>}): Promise<{
@@ -12,13 +12,13 @@ type BlockRootHex = string;
12
12
  type BlockHashHex = string;
13
13
 
14
14
  /**
15
- * Store the best execution payload bid per slot / (parent block root, parent block hash).
15
+ * Store the best signed execution payload bid per slot / (parent block root, parent block hash).
16
16
  */
17
17
  export class ExecutionPayloadBidPool {
18
18
  private readonly bidByParentHashByParentRootBySlot = new MapDef<
19
19
  Slot,
20
- MapDef<BlockRootHex, Map<BlockHashHex, gloas.ExecutionPayloadBid>>
21
- >(() => new MapDef<BlockRootHex, Map<BlockHashHex, gloas.ExecutionPayloadBid>>(() => new Map()));
20
+ MapDef<BlockRootHex, Map<BlockHashHex, gloas.SignedExecutionPayloadBid>>
21
+ >(() => new MapDef<BlockRootHex, Map<BlockHashHex, gloas.SignedExecutionPayloadBid>>(() => new Map()));
22
22
  private lowestPermissibleSlot = 0;
23
23
 
24
24
  get size(): number {
@@ -31,8 +31,8 @@ export class ExecutionPayloadBidPool {
31
31
  return count;
32
32
  }
33
33
 
34
- add(bid: gloas.ExecutionPayloadBid): InsertOutcome {
35
- const {slot, parentBlockRoot, parentBlockHash, value} = bid;
34
+ add(bid: gloas.SignedExecutionPayloadBid): InsertOutcome {
35
+ const {slot, parentBlockRoot, parentBlockHash, value} = bid.message;
36
36
  const lowestPermissibleSlot = this.lowestPermissibleSlot;
37
37
 
38
38
  if (slot < lowestPermissibleSlot) {
@@ -45,7 +45,7 @@ export class ExecutionPayloadBidPool {
45
45
  const existing = bidByParentHash.get(parentHashHex);
46
46
 
47
47
  if (existing) {
48
- const existingValue = existing.value;
48
+ const existingValue = existing.message.value;
49
49
  const newValue = value;
50
50
  if (newValue > existingValue) {
51
51
  bidByParentHash.set(parentHashHex, bid);
@@ -59,14 +59,15 @@ export class ExecutionPayloadBidPool {
59
59
  }
60
60
 
61
61
  /**
62
- * Return the highest-value bid matching slot, parent block hash, and parent block root.
62
+ * Return the highest-value signed bid matching slot, parent block hash, and parent block root.
63
63
  * Used for gossip validation and block production.
64
64
  */
65
65
  getBestBid(
66
66
  slot: Slot,
67
- parentBlockHash: BlockHashHex,
67
+ parentBlockHash: BlockHashHex | null,
68
68
  parentBlockRoot: BlockRootHex
69
- ): gloas.ExecutionPayloadBid | null {
69
+ ): gloas.SignedExecutionPayloadBid | null {
70
+ if (parentBlockHash === null) return null;
70
71
  const bidByParentHash = this.bidByParentHashByParentRootBySlot.get(slot)?.get(parentBlockRoot);
71
72
  return bidByParentHash?.get(parentBlockHash) ?? null;
72
73
  }
@@ -105,6 +105,7 @@ export const defaultChainOptions: IChainOptions = {
105
105
  proposerBoost: true,
106
106
  proposerBoostReorg: true,
107
107
  computeUnrealized: true,
108
+ fastConfirmation: false,
108
109
  suggestedFeeRecipient: defaultValidatorOptions.suggestedFeeRecipient,
109
110
  serveHistoricalState: false,
110
111
  assertCorrectProgressiveBalances: false,
@@ -169,7 +169,8 @@ export class PrepareNextSlotScheduler {
169
169
  // Apply parent payload once here as it's reused by EL prep and SSE emit below
170
170
  let stateAfterParentPayload: IBeaconStateViewBellatrix = updatedPrepareState;
171
171
  if (isStatePostGloas(updatedPrepareState)) {
172
- if (this.chain.forkChoice.shouldExtendPayload(updatedHead.blockRoot)) {
172
+ // Spec: should_build_on_full(store, head) — see produceBlockBody.ts for context.
173
+ if (this.chain.forkChoice.shouldBuildOnFull(updatedHead, prepareSlot)) {
173
174
  parentBlockHash = updatedPrepareState.latestExecutionPayloadBid.blockHash;
174
175
  // Skip applying parent payload unless we're proposing the next slot or have to emit payload_attributes events
175
176
  if (feeRecipient !== undefined || this.chain.opts.emitPayloadAttributes === true) {
@@ -216,16 +217,6 @@ export class PrepareNextSlotScheduler {
216
217
  });
217
218
  }
218
219
 
219
- if (ForkSeq[fork] >= ForkSeq.gloas) {
220
- // Cutoff = slot of the parent of the block we'll actually build on (post-reorg).
221
- // Steady state: cache holds just 2 entries — head (parent for next-slot production)
222
- // and head.parent (proposer-boost-reorg fallback). Anything older is evicted.
223
- const updatedHeadParent = this.chain.forkChoice.getBlockHexDefaultStatus(updatedHead.parentRoot);
224
- if (updatedHeadParent) {
225
- this.chain.seenPayloadEnvelopeInputCache.pruneBelowParent(updatedHeadParent);
226
- }
227
- }
228
-
229
220
  this.computeStateHashTreeRoot(updatedPrepareState, isEpochTransition);
230
221
 
231
222
  // If emitPayloadAttributes is true emit a SSE payloadAttributes event for
@@ -18,9 +18,9 @@ import {
18
18
  G2_POINT_AT_INFINITY,
19
19
  IBeaconStateView,
20
20
  type IBeaconStateViewBellatrix,
21
- type IBeaconStateViewGloas,
22
21
  computeEpochAtSlot,
23
22
  computeTimeAtSlot,
23
+ getExpectedGasLimit,
24
24
  isStatePostBellatrix,
25
25
  isStatePostCapella,
26
26
  isStatePostGloas,
@@ -50,16 +50,10 @@ import {
50
50
  gloas,
51
51
  ssz,
52
52
  } from "@lodestar/types";
53
- import {Logger, byteArrayEquals, fromHex, sleep, toHex, toPubkeyHex, toRootHex} from "@lodestar/utils";
53
+ import {GWEI_TO_WEI, Logger, byteArrayEquals, fromHex, sleep, toHex, toPubkeyHex, toRootHex} from "@lodestar/utils";
54
54
  import {ZERO_HASH_HEX} from "../../constants/index.js";
55
55
  import {numToQuantity} from "../../execution/engine/utils.js";
56
- import {
57
- IExecutionBuilder,
58
- IExecutionEngine,
59
- PayloadAttributes,
60
- PayloadId,
61
- getExpectedGasLimit,
62
- } from "../../execution/index.js";
56
+ import {IExecutionBuilder, IExecutionEngine, PayloadAttributes, PayloadId} from "../../execution/index.js";
63
57
  import {getShufflingDependentRoot} from "../../util/dependentRoot.js";
64
58
  import {fromGraffitiBytes} from "../../util/graffiti.js";
65
59
  import {kzg} from "../../util/kzg.js";
@@ -97,6 +91,8 @@ export type BlockAttributes = {
97
91
  slot: Slot;
98
92
  parentBlock: ProtoBlock;
99
93
  feeRecipient?: string;
94
+ /** When provided, build block with this builder bid instead of a self-build bid */
95
+ builderBid?: gloas.SignedExecutionPayloadBid;
100
96
  };
101
97
 
102
98
  export enum BlockType {
@@ -112,7 +108,7 @@ export type ProduceFullGloas = {
112
108
  type: BlockType.Full;
113
109
  fork: ForkPostGloas;
114
110
  executionPayload: ExecutionPayload<ForkPostGloas>;
115
- executionRequests: electra.ExecutionRequests;
111
+ executionRequests: gloas.ExecutionRequests;
116
112
  blobsBundle: BlobsBundle<ForkPostGloas>;
117
113
  cells: fulu.Cell[][];
118
114
  parentBlockRoot: Root;
@@ -156,6 +152,28 @@ export type ProduceResult =
156
152
  | ProduceFullPhase0
157
153
  | ProduceBlinded;
158
154
 
155
+ /**
156
+ * Drop voluntary exits that `parent_execution_requests` have invalidated (e.g. a withdrawal
157
+ * request initiating an exit on the same validator). Op pool selected against the unapplied
158
+ * state, so re-validate against the post-apply state to avoid producing an invalid block.
159
+ *
160
+ * `getStateAfterParentPayload` is a thunk so the post-apply state is only materialized when
161
+ * actually needed (i.e. when extending the parent payload and there are exits to filter).
162
+ */
163
+ function maybeFilterInvalidatedVoluntaryExits(
164
+ commonBlockBody: CommonBlockBody,
165
+ isExtendingPayload: boolean,
166
+ getStateAfterParentPayload: () => IBeaconStateViewBellatrix
167
+ ): CommonBlockBody["voluntaryExits"] {
168
+ if (!isExtendingPayload || commonBlockBody.voluntaryExits.length === 0) {
169
+ return commonBlockBody.voluntaryExits;
170
+ }
171
+ const state = getStateAfterParentPayload();
172
+ return commonBlockBody.voluntaryExits.filter((signedVoluntaryExit) =>
173
+ state.isValidVoluntaryExit(signedVoluntaryExit, false)
174
+ );
175
+ }
176
+
159
177
  export async function produceBlockBody<T extends BlockType>(
160
178
  this: BeaconChain,
161
179
  blockType: T,
@@ -178,6 +196,7 @@ export async function produceBlockBody<T extends BlockType>(
178
196
  proposerIndex,
179
197
  proposerPubKey,
180
198
  commonBlockBodyPromise,
199
+ builderBid,
181
200
  } = blockAttr;
182
201
  let executionPayloadValue: Wei;
183
202
  let blockBody: AssembledBodyType<T>;
@@ -198,7 +217,43 @@ export async function produceBlockBody<T extends BlockType>(
198
217
  };
199
218
  this.logger.verbose("Producing beacon block body", logMeta);
200
219
 
201
- if (isForkPostGloas(fork)) {
220
+ if (builderBid !== undefined) {
221
+ if (!isStatePostGloas(currentState)) {
222
+ throw new Error("Expected Gloas state for builder bid block production");
223
+ }
224
+
225
+ const isExtendingPayload = byteArrayEquals(
226
+ builderBid.message.parentBlockHash,
227
+ currentState.latestExecutionPayloadBid.blockHash
228
+ );
229
+ const parentExecutionRequests = isExtendingPayload
230
+ ? await this.getParentExecutionRequests(parentBlock.slot, parentBlock.blockRoot)
231
+ : ssz.gloas.ExecutionRequests.defaultValue();
232
+ executionPayloadValue = BigInt(builderBid.message.value) * GWEI_TO_WEI;
233
+
234
+ const commonBlockBody = await commonBlockBodyPromise;
235
+ const gloasBody = Object.assign({}, commonBlockBody) as gloas.BeaconBlockBody;
236
+ gloasBody.signedExecutionPayloadBid = builderBid;
237
+ gloasBody.payloadAttestations = this.payloadAttestationPool.getPayloadAttestationsForBlock(
238
+ parentBlock.blockRoot,
239
+ blockSlot - 1
240
+ );
241
+ gloasBody.parentExecutionRequests = parentExecutionRequests;
242
+ gloasBody.voluntaryExits = maybeFilterInvalidatedVoluntaryExits(commonBlockBody, isExtendingPayload, () =>
243
+ currentState.withParentPayloadApplied(parentExecutionRequests)
244
+ );
245
+ blockBody = gloasBody as AssembledBodyType<T>;
246
+
247
+ this.logger.verbose("Produced block with builder bid", {
248
+ slot: blockSlot,
249
+ builderIndex: builderBid.message.builderIndex,
250
+ bidValue: builderBid.message.value,
251
+ parentBlockHash: toRootHex(builderBid.message.parentBlockHash),
252
+ parentBlockRoot: toRootHex(builderBid.message.parentBlockRoot),
253
+ blockHash: toRootHex(builderBid.message.blockHash),
254
+ isExtendingPayload,
255
+ });
256
+ } else if (isForkPostGloas(fork)) {
202
257
  if (!isStatePostGloas(currentState)) {
203
258
  throw new Error("Expected Gloas state for Gloas block production");
204
259
  }
@@ -215,25 +270,22 @@ export async function produceBlockBody<T extends BlockType>(
215
270
 
216
271
  const endExecutionPayload = this.metrics?.executionBlockProductionTimeSteps.startTimer();
217
272
 
218
- this.logger.verbose("Preparing execution payload from engine", {
219
- slot: blockSlot,
220
- parentBlockRoot: toRootHex(parentBlockRoot),
221
- feeRecipient,
222
- });
223
-
224
273
  // Get execution payload from EL
225
274
  let parentBlockHash: Bytes32;
226
- let parentExecutionRequests: electra.ExecutionRequests;
275
+ let parentExecutionRequests: gloas.ExecutionRequests;
227
276
  // Apply parent payload once here as it's reused by EL prep and voluntary exit filtering below
228
277
  let stateAfterParentPayload: IBeaconStateViewBellatrix = currentState;
229
- const isExtendingPayload = this.forkChoice.shouldExtendPayload(toRootHex(parentBlockRoot));
230
- if (isExtendingPayload) {
278
+ // Spec: should_build_on_full(store, head). `parentBlock` is the proposer's head
279
+ // (set by chain.getProposerHead(slot)). Returns false when the PTC majority signalled
280
+ // the blob data is not available or the payload was not timely, forcing a build on EMPTY (reorg).
281
+ const isBuildingOnFull = this.forkChoice.shouldBuildOnFull(parentBlock, blockSlot);
282
+ if (isBuildingOnFull) {
231
283
  parentBlockHash = currentState.latestExecutionPayloadBid.blockHash;
232
284
  parentExecutionRequests = await this.getParentExecutionRequests(parentBlock.slot, parentBlock.blockRoot);
233
285
  stateAfterParentPayload = currentState.withParentPayloadApplied(parentExecutionRequests);
234
286
  } else {
235
287
  parentBlockHash = currentState.latestExecutionPayloadBid.parentBlockHash;
236
- parentExecutionRequests = ssz.electra.ExecutionRequests.defaultValue();
288
+ parentExecutionRequests = ssz.gloas.ExecutionRequests.defaultValue();
237
289
  }
238
290
  const prepareRes = await prepareExecutionPayload(
239
291
  this,
@@ -250,6 +302,16 @@ export async function produceBlockBody<T extends BlockType>(
250
302
  const {prepType, payloadId} = prepareRes;
251
303
  Object.assign(logMeta, {executionPayloadPrepType: prepType});
252
304
 
305
+ this.logger.verbose("Prepared execution payload from engine", {
306
+ slot: blockSlot,
307
+ parentBlockRoot: toRootHex(parentBlockRoot),
308
+ parentBlockHash: toRootHex(parentBlockHash),
309
+ feeRecipient,
310
+ prepType,
311
+ payloadId,
312
+ isBuildingOnFull,
313
+ });
314
+
253
315
  if (prepType !== PayloadPreparationType.Cached) {
254
316
  await sleep(PAYLOAD_GENERATION_TIME_MS);
255
317
  }
@@ -282,13 +344,13 @@ export async function produceBlockBody<T extends BlockType>(
282
344
  blockHash: executionPayload.blockHash,
283
345
  prevRandao: currentState.getRandaoMix(currentState.epoch),
284
346
  feeRecipient: executionPayload.feeRecipient,
285
- gasLimit: BigInt(executionPayload.gasLimit),
347
+ gasLimit: executionPayload.gasLimit,
286
348
  builderIndex: BUILDER_INDEX_SELF_BUILD,
287
349
  slot: blockSlot,
288
350
  value: 0,
289
351
  executionPayment: 0,
290
352
  blobKzgCommitments: blobsBundle.commitments,
291
- executionRequestsRoot: ssz.electra.ExecutionRequests.hashTreeRoot(executionRequests),
353
+ executionRequestsRoot: ssz.gloas.ExecutionRequests.hashTreeRoot(executionRequests as gloas.ExecutionRequests),
292
354
  };
293
355
  const signedBid: gloas.SignedExecutionPayloadBid = {
294
356
  message: bid,
@@ -303,20 +365,17 @@ export async function produceBlockBody<T extends BlockType>(
303
365
  blockSlot - 1
304
366
  );
305
367
  gloasBody.parentExecutionRequests = parentExecutionRequests;
306
- // Drop voluntary exits that parent_execution_requests have invalidated (e.g. a withdrawal
307
- // request initiating an exit on the same validator). Op pool selected against the unapplied
308
- // state, so re-validate against the post-apply state to avoid producing an invalid block.
309
- if (isExtendingPayload && commonBlockBody.voluntaryExits.length > 0) {
310
- gloasBody.voluntaryExits = commonBlockBody.voluntaryExits.filter((signedVoluntaryExit) =>
311
- stateAfterParentPayload.isValidVoluntaryExit(signedVoluntaryExit, false)
312
- );
313
- }
368
+ gloasBody.voluntaryExits = maybeFilterInvalidatedVoluntaryExits(
369
+ commonBlockBody,
370
+ isBuildingOnFull,
371
+ () => stateAfterParentPayload
372
+ );
314
373
  blockBody = gloasBody as AssembledBodyType<T>;
315
374
 
316
375
  // Store execution payload data required to construct execution payload envelope later
317
376
  const gloasResult = produceResult as ProduceFullGloas;
318
377
  gloasResult.executionPayload = executionPayload as ExecutionPayload<ForkPostGloas>;
319
- gloasResult.executionRequests = executionRequests;
378
+ gloasResult.executionRequests = executionRequests as gloas.ExecutionRequests;
320
379
  gloasResult.blobsBundle = blobsBundle;
321
380
  gloasResult.cells = cells;
322
381
  gloasResult.parentBlockRoot = fromHex(parentBlock.blockRoot);
@@ -331,6 +390,7 @@ export async function produceBlockBody<T extends BlockType>(
331
390
  fetchedTime,
332
391
  executionBlockHash: toRootHex(executionPayload.blockHash),
333
392
  blobs: blobsBundle.commitments.length,
393
+ gasLimit: executionPayload.gasLimit,
334
394
  });
335
395
 
336
396
  Object.assign(logMeta, {
@@ -869,9 +929,9 @@ function preparePayloadAttributes(
869
929
  (payloadAttributes as gloas.SSEPayloadAttributes["payloadAttributes"]).slotNumber = prepareSlot;
870
930
  (payloadAttributes as gloas.SSEPayloadAttributes["payloadAttributes"]).targetGasLimit = getProposerTargetGasLimit(
871
931
  chain,
872
- prepareState,
873
932
  prepareSlot,
874
- parentBlockRoot
933
+ parentBlockRoot,
934
+ parentBlockHash
875
935
  );
876
936
  }
877
937
 
@@ -886,14 +946,20 @@ function preparePayloadAttributes(
886
946
  * (same `(slot, dependent_root)` lookup as gossip bid validation). When no matching
887
947
  * preferences are pooled, target the parent payload's gas limit so the gas limit stays
888
948
  * unchanged (`is_gas_limit_target_compatible` then requires `gas_limit == parent_gas_limit`).
949
+ *
950
+ * The parent payload's gas_limit is read from fork choice — the variant matching
951
+ * `(parentBlockRoot, parentBlockHash)` carries the correct value for both FULL parents
952
+ * (FULL.executionPayloadGasLimit = delivered payload's gas_limit) and EMPTY parents
953
+ * (EMPTY.executionPayloadGasLimit = inherited grandparent's gas_limit).
889
954
  */
890
955
  function getProposerTargetGasLimit(
891
956
  chain: {forkChoice: IForkChoice; proposerPreferencesPool: ProposerPreferencesPool},
892
- state: IBeaconStateViewGloas,
893
957
  prepareSlot: Slot,
894
- parentBlockRoot: Root
958
+ parentBlockRoot: Root,
959
+ parentBlockHash: Bytes32
895
960
  ): number {
896
- const parentBlock = chain.forkChoice.getBlockHexDefaultStatus(toRootHex(parentBlockRoot));
961
+ const parentBlockRootHex = toRootHex(parentBlockRoot);
962
+ const parentBlock = chain.forkChoice.getBlockHexDefaultStatus(parentBlockRootHex);
897
963
  const dependentRootHex = (() => {
898
964
  if (parentBlock === null) {
899
965
  return null;
@@ -911,9 +977,17 @@ function getProposerTargetGasLimit(
911
977
  })();
912
978
 
913
979
  const pref = dependentRootHex !== null ? chain.proposerPreferencesPool.get(prepareSlot, dependentRootHex) : null;
914
- // TODO GLOAS: state.latestExecutionPayloadBid is the latest *bid*, not the latest *executed*
915
- // payload — for EMPTY parents this drifts. Consider having a default value like Prysm's DefaultBuilderGasLimit.
916
- return Number(pref ? pref.message.targetGasLimit : state.latestExecutionPayloadBid.gasLimit);
980
+ if (pref !== null) {
981
+ return pref.message.targetGasLimit;
982
+ }
983
+
984
+ const parentPayloadVariant = chain.forkChoice.getBlockHexAndBlockHash(parentBlockRootHex, toRootHex(parentBlockHash));
985
+ if (parentPayloadVariant === null || parentPayloadVariant.executionPayloadBlockHash === null) {
986
+ throw new Error(
987
+ `Cannot resolve parent payload gas_limit for proposer targetGasLimit fallback parentBlockRoot=${parentBlockRootHex} parentBlockHash=${toRootHex(parentBlockHash)}`
988
+ );
989
+ }
990
+ return parentPayloadVariant.executionPayloadGasLimit;
917
991
  }
918
992
 
919
993
  export async function produceCommonBlockBody<T extends BlockType>(
@@ -17,10 +17,12 @@ export enum RegenCaller {
17
17
  predictProposerHead = "predictProposerHead",
18
18
  produceAttestationData = "produceAttestationData",
19
19
  processBlocksInEpoch = "processBlocksInEpoch",
20
+ importExecutionPayload = "importExecutionPayload",
20
21
  validateGossipAggregateAndProof = "validateGossipAggregateAndProof",
21
22
  validateGossipAttestation = "validateGossipAttestation",
22
23
  validateGossipVoluntaryExit = "validateGossipVoluntaryExit",
23
24
  validateGossipExecutionPayloadBid = "validateGossipExecutionPayloadBid",
25
+ validateGossipPayloadAttestationMessage = "validateGossipPayloadAttestationMessage",
24
26
  validateGossipProposerPreferences = "validateGossipProposerPreferences",
25
27
  onForkChoiceFinalized = "onForkChoiceFinalized",
26
28
  restApi = "restApi",
@@ -45,7 +47,6 @@ export interface IStateRegenerator extends IStateRegeneratorInternal {
45
47
  getCheckpointStateSync(cp: CheckpointHex): IBeaconStateView | null;
46
48
  getClosestHeadState(head: ProtoBlock): IBeaconStateView | null;
47
49
  pruneOnCheckpoint(finalizedEpoch: Epoch, justifiedEpoch: Epoch, headStateRoot: RootHex): void;
48
- pruneOnFinalized(finalizedEpoch: Epoch): void;
49
50
  processState(blockRootHex: RootHex, postState: IBeaconStateView): void;
50
51
  addCheckpointState(cp: phase0.Checkpoint, item: IBeaconStateView): void;
51
52
  updateHeadState(newHead: ProtoBlock, maybeHeadState: IBeaconStateView): void;
@@ -143,11 +143,6 @@ export class QueuedStateRegenerator implements IStateRegenerator {
143
143
  this.blockStateCache.prune(headStateRoot);
144
144
  }
145
145
 
146
- pruneOnFinalized(finalizedEpoch: number): void {
147
- this.checkpointStateCache.pruneFinalized(finalizedEpoch);
148
- this.blockStateCache.deleteAllBeforeEpoch(finalizedEpoch);
149
- }
150
-
151
146
  processState(blockRootHex: RootHex, postState: IBeaconStateView): void {
152
147
  this.blockStateCache.add(postState);
153
148
  this.checkpointStateCache.processState(blockRootHex, postState).catch((e) => {
@@ -1,5 +1,5 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
- import {CheckpointWithHex, IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
2
+ import {CheckpointWithHex, IForkChoice, PayloadStatus, ProtoBlock} from "@lodestar/fork-choice";
3
3
  import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
4
4
  import {RootHex} from "@lodestar/types";
5
5
  import {Logger} from "@lodestar/utils";
@@ -154,9 +154,14 @@ export class SeenPayloadEnvelopeInput {
154
154
 
155
155
  pruneBelowParent(parentBlock: ProtoBlock): void {
156
156
  for (const block of this.forkChoice.getAllAncestorBlocks(parentBlock.blockRoot, parentBlock.payloadStatus)) {
157
- if (block.slot < parentBlock.slot) {
157
+ // Only evict once the payload is FULL (revealed/imported) — on an EMPTY/PENDING branch we may
158
+ // still need to download the FULL envelope (see #9475), and evicting would make payload-by-root
159
+ // sync throw "Missing PayloadEnvelopeInput for known block".
160
+ if (block.slot < parentBlock.slot && block.payloadStatus === PayloadStatus.FULL) {
158
161
  const input = this.payloadInputs.get(block.blockRoot);
159
- if (input) {
162
+ // ...and don't evict while columns are still being gathered: writeDataColumnsToDb awaits the
163
+ // same hasComputedAllData() before persisting. Such entries are pruned by a later call.
164
+ if (input?.hasComputedAllData()) {
160
165
  this.evictPayloadInput(input);
161
166
  this.logger?.verbose("SeenPayloadEnvelopeInput.pruneBelowParent deleted", {
162
167
  slot: block.slot,
@@ -167,12 +167,6 @@ export class FIFOBlockStateCache implements BlockStateCache {
167
167
  }
168
168
  }
169
169
 
170
- /**
171
- * No need for this implementation
172
- * This is only to conform to the old api
173
- */
174
- deleteAllBeforeEpoch(): void {}
175
-
176
170
  /**
177
171
  * ONLY FOR DEBUGGING PURPOSES. For lodestar debug API.
178
172
  */
@@ -414,11 +414,12 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
414
414
 
415
415
  /**
416
416
  * Prune all checkpoint states before the provided finalized epoch.
417
+ * Driven sequentially from processState() so it never interleaves with persist.
417
418
  */
418
- pruneFinalized(finalizedEpoch: Epoch): void {
419
+ private async pruneFinalized(finalizedEpoch: Epoch): Promise<void> {
419
420
  for (const epoch of this.epochIndex.keys()) {
420
421
  if (epoch < finalizedEpoch) {
421
- this.deleteAllEpochItems(epoch).catch((e) =>
422
+ await this.deleteAllEpochItems(epoch).catch((e) =>
422
423
  this.logger.debug("Error delete all epoch items", {epoch, finalizedEpoch}, e as Error)
423
424
  );
424
425
  }
@@ -476,6 +477,9 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
476
477
  * As of Mar 2024, it takes <=350ms to persist a holesky state on fast server
477
478
  */
478
479
  async processState(blockRootHex: RootHex, state: IBeaconStateView): Promise<number> {
480
+ // prune finalized in the same flow so a finalized cp state is pruned, never persisted
481
+ await this.pruneFinalized(state.finalizedCheckpoint.epoch);
482
+
479
483
  let persistCount = 0;
480
484
  // it's important to sort the epochs in ascending order, in case of big reorg we always want to keep the most recent checkpoint states
481
485
  const sortedEpochs = Array.from(this.epochIndex.keys()).sort((a, b) => a - b);
@@ -30,7 +30,6 @@ export interface BlockStateCache {
30
30
  clear(): void;
31
31
  size: number;
32
32
  prune(headStateRootHex: RootHex): void;
33
- deleteAllBeforeEpoch(finalizedEpoch: Epoch): void;
34
33
  dumpSummary(): routes.lodestar.StateCacheItem[];
35
34
  /** Expose beacon states stored in cache. Use with caution */
36
35
  getStates(): IterableIterator<IBeaconStateView>;
@@ -67,7 +66,6 @@ export interface CheckpointStateCache {
67
66
  getOrReloadLatest(rootHex: RootHex, maxEpoch: Epoch): Promise<IBeaconStateView | null>;
68
67
  updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch): number | null;
69
68
  prune(finalizedEpoch: Epoch, justifiedEpoch: Epoch): void;
70
- pruneFinalized(finalizedEpoch: Epoch): void;
71
69
  processState(blockRootHex: RootHex, state: IBeaconStateView): Promise<number>;
72
70
  clear(): void;
73
71
  dumpSummary(): routes.lodestar.StateCacheItem[];