@lodestar/beacon-node 1.50.0-dev.c04a499311 → 1.50.0-dev.c24762bc60

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 (228) hide show
  1. package/lib/api/impl/beacon/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/index.js +13 -0
  3. package/lib/api/impl/beacon/index.js.map +1 -1
  4. package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/state/index.js +28 -6
  6. package/lib/api/impl/beacon/state/index.js.map +1 -1
  7. package/lib/api/impl/debug/index.d.ts.map +1 -1
  8. package/lib/api/impl/debug/index.js +26 -18
  9. package/lib/api/impl/debug/index.js.map +1 -1
  10. package/lib/api/impl/validator/index.d.ts.map +1 -1
  11. package/lib/api/impl/validator/index.js +32 -0
  12. package/lib/api/impl/validator/index.js.map +1 -1
  13. package/lib/chain/archiveStore/archiveStore.d.ts +5 -0
  14. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  15. package/lib/chain/archiveStore/archiveStore.js +13 -23
  16. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  17. package/lib/chain/archiveStore/utils/pruneHistory.d.ts +1 -1
  18. package/lib/chain/archiveStore/utils/pruneHistory.d.ts.map +1 -1
  19. package/lib/chain/archiveStore/utils/pruneHistory.js +2 -1
  20. package/lib/chain/archiveStore/utils/pruneHistory.js.map +1 -1
  21. package/lib/chain/beaconProposerCache.d.ts +6 -0
  22. package/lib/chain/beaconProposerCache.d.ts.map +1 -1
  23. package/lib/chain/beaconProposerCache.js +16 -0
  24. package/lib/chain/beaconProposerCache.js.map +1 -1
  25. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  26. package/lib/chain/blocks/importBlock.js +14 -8
  27. package/lib/chain/blocks/importBlock.js.map +1 -1
  28. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  29. package/lib/chain/blocks/verifyBlock.js +6 -6
  30. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  31. package/lib/chain/chain.d.ts +5 -1
  32. package/lib/chain/chain.d.ts.map +1 -1
  33. package/lib/chain/chain.js +73 -20
  34. package/lib/chain/chain.js.map +1 -1
  35. package/lib/chain/interface.d.ts +3 -0
  36. package/lib/chain/interface.d.ts.map +1 -1
  37. package/lib/chain/opPools/aggregatedAttestationPool.d.ts +4 -2
  38. package/lib/chain/opPools/aggregatedAttestationPool.d.ts.map +1 -1
  39. package/lib/chain/opPools/aggregatedAttestationPool.js +30 -8
  40. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  41. package/lib/chain/opPools/opPool.d.ts.map +1 -1
  42. package/lib/chain/opPools/opPool.js +1 -23
  43. package/lib/chain/opPools/opPool.js.map +1 -1
  44. package/lib/chain/opPools/proposerPreferencesPool.d.ts +8 -4
  45. package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
  46. package/lib/chain/opPools/proposerPreferencesPool.js +27 -12
  47. package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
  48. package/lib/chain/opPools/utils.d.ts +11 -0
  49. package/lib/chain/opPools/utils.d.ts.map +1 -1
  50. package/lib/chain/opPools/utils.js +22 -0
  51. package/lib/chain/opPools/utils.js.map +1 -1
  52. package/lib/chain/options.d.ts +1 -0
  53. package/lib/chain/options.d.ts.map +1 -1
  54. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  55. package/lib/chain/prepareNextSlot.js +11 -3
  56. package/lib/chain/prepareNextSlot.js.map +1 -1
  57. package/lib/chain/produceBlock/produceBlockBody.d.ts +8 -0
  58. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  59. package/lib/chain/produceBlock/produceBlockBody.js +40 -28
  60. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  61. package/lib/chain/seenCache/seenGossipBlockInput.d.ts +7 -0
  62. package/lib/chain/seenCache/seenGossipBlockInput.d.ts.map +1 -1
  63. package/lib/chain/seenCache/seenGossipBlockInput.js +21 -0
  64. package/lib/chain/seenCache/seenGossipBlockInput.js.map +1 -1
  65. package/lib/chain/shufflingCache.d.ts +18 -8
  66. package/lib/chain/shufflingCache.d.ts.map +1 -1
  67. package/lib/chain/shufflingCache.js +72 -18
  68. package/lib/chain/shufflingCache.js.map +1 -1
  69. package/lib/db/beacon.d.ts +4 -1
  70. package/lib/db/beacon.d.ts.map +1 -1
  71. package/lib/db/beacon.js +6 -1
  72. package/lib/db/beacon.js.map +1 -1
  73. package/lib/db/buckets.d.ts +3 -1
  74. package/lib/db/buckets.d.ts.map +1 -1
  75. package/lib/db/buckets.js +2 -0
  76. package/lib/db/buckets.js.map +1 -1
  77. package/lib/db/interface.d.ts +4 -1
  78. package/lib/db/interface.d.ts.map +1 -1
  79. package/lib/db/repositories/blockArchiveIndex.d.ts +2 -2
  80. package/lib/db/repositories/blockArchiveIndex.d.ts.map +1 -1
  81. package/lib/db/repositories/earliestAvailableSlot.d.ts +11 -0
  82. package/lib/db/repositories/earliestAvailableSlot.d.ts.map +1 -0
  83. package/lib/db/repositories/earliestAvailableSlot.js +19 -0
  84. package/lib/db/repositories/earliestAvailableSlot.js.map +1 -0
  85. package/lib/db/repositories/index.d.ts +1 -0
  86. package/lib/db/repositories/index.d.ts.map +1 -1
  87. package/lib/db/repositories/index.js +1 -0
  88. package/lib/db/repositories/index.js.map +1 -1
  89. package/lib/db/repositories/proposerPreferences.d.ts +14 -0
  90. package/lib/db/repositories/proposerPreferences.d.ts.map +1 -0
  91. package/lib/db/repositories/proposerPreferences.js +21 -0
  92. package/lib/db/repositories/proposerPreferences.js.map +1 -0
  93. package/lib/execution/engine/blobVersionedHashes.d.ts +9 -0
  94. package/lib/execution/engine/blobVersionedHashes.d.ts.map +1 -0
  95. package/lib/execution/engine/blobVersionedHashes.js +74 -0
  96. package/lib/execution/engine/blobVersionedHashes.js.map +1 -0
  97. package/lib/execution/engine/http.d.ts +45 -13
  98. package/lib/execution/engine/http.d.ts.map +1 -1
  99. package/lib/execution/engine/http.js +236 -198
  100. package/lib/execution/engine/http.js.map +1 -1
  101. package/lib/execution/engine/index.d.ts +3 -2
  102. package/lib/execution/engine/index.d.ts.map +1 -1
  103. package/lib/execution/engine/index.js +33 -8
  104. package/lib/execution/engine/index.js.map +1 -1
  105. package/lib/execution/engine/interface.d.ts +1 -1
  106. package/lib/execution/engine/interface.d.ts.map +1 -1
  107. package/lib/execution/engine/jsonRpcTransport.d.ts +25 -0
  108. package/lib/execution/engine/jsonRpcTransport.d.ts.map +1 -0
  109. package/lib/execution/engine/jsonRpcTransport.js +158 -0
  110. package/lib/execution/engine/jsonRpcTransport.js.map +1 -0
  111. package/lib/execution/engine/restHttpClient.d.ts +71 -0
  112. package/lib/execution/engine/restHttpClient.d.ts.map +1 -0
  113. package/lib/execution/engine/restHttpClient.js +201 -0
  114. package/lib/execution/engine/restHttpClient.js.map +1 -0
  115. package/lib/execution/engine/restTransport.d.ts +46 -0
  116. package/lib/execution/engine/restTransport.d.ts.map +1 -0
  117. package/lib/execution/engine/restTransport.js +399 -0
  118. package/lib/execution/engine/restTransport.js.map +1 -0
  119. package/lib/execution/engine/sszTypes.d.ts +1261 -0
  120. package/lib/execution/engine/sszTypes.d.ts.map +1 -0
  121. package/lib/execution/engine/sszTypes.js +216 -0
  122. package/lib/execution/engine/sszTypes.js.map +1 -0
  123. package/lib/execution/engine/transport.d.ts +39 -0
  124. package/lib/execution/engine/transport.d.ts.map +1 -0
  125. package/lib/execution/engine/transport.js +2 -0
  126. package/lib/execution/engine/transport.js.map +1 -0
  127. package/lib/execution/engine/types.d.ts +2 -0
  128. package/lib/execution/engine/types.d.ts.map +1 -1
  129. package/lib/execution/engine/types.js +24 -57
  130. package/lib/execution/engine/types.js.map +1 -1
  131. package/lib/execution/engine/utils.d.ts.map +1 -1
  132. package/lib/execution/engine/utils.js +9 -2
  133. package/lib/execution/engine/utils.js.map +1 -1
  134. package/lib/metrics/metrics/lodestar.d.ts +10 -0
  135. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  136. package/lib/metrics/metrics/lodestar.js +22 -0
  137. package/lib/metrics/metrics/lodestar.js.map +1 -1
  138. package/lib/network/core/events.d.ts +5 -1
  139. package/lib/network/core/events.d.ts.map +1 -1
  140. package/lib/network/core/events.js +8 -0
  141. package/lib/network/core/events.js.map +1 -1
  142. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  143. package/lib/network/processor/gossipHandlers.js +60 -64
  144. package/lib/network/processor/gossipHandlers.js.map +1 -1
  145. package/lib/node/nodejs.d.ts +3 -1
  146. package/lib/node/nodejs.d.ts.map +1 -1
  147. package/lib/node/nodejs.js +2 -1
  148. package/lib/node/nodejs.js.map +1 -1
  149. package/lib/node/options.d.ts +3 -2
  150. package/lib/node/options.d.ts.map +1 -1
  151. package/lib/node/options.js +2 -2
  152. package/lib/node/options.js.map +1 -1
  153. package/lib/sync/range/batch.d.ts +5 -5
  154. package/lib/sync/range/batch.d.ts.map +1 -1
  155. package/lib/sync/range/batch.js +11 -17
  156. package/lib/sync/range/batch.js.map +1 -1
  157. package/lib/sync/range/chain.d.ts +4 -4
  158. package/lib/sync/range/chain.d.ts.map +1 -1
  159. package/lib/sync/range/chain.js +9 -5
  160. package/lib/sync/range/chain.js.map +1 -1
  161. package/lib/sync/range/range.d.ts.map +1 -1
  162. package/lib/sync/range/range.js +25 -11
  163. package/lib/sync/range/range.js.map +1 -1
  164. package/lib/sync/unknownBlock.d.ts +5 -2
  165. package/lib/sync/unknownBlock.d.ts.map +1 -1
  166. package/lib/sync/unknownBlock.js +44 -20
  167. package/lib/sync/unknownBlock.js.map +1 -1
  168. package/lib/sync/utils/downloadByRange.d.ts +38 -14
  169. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  170. package/lib/sync/utils/downloadByRange.js +84 -39
  171. package/lib/sync/utils/downloadByRange.js.map +1 -1
  172. package/lib/util/asyncIterableToEvents.d.ts +10 -2
  173. package/lib/util/asyncIterableToEvents.d.ts.map +1 -1
  174. package/lib/util/asyncIterableToEvents.js +29 -3
  175. package/lib/util/asyncIterableToEvents.js.map +1 -1
  176. package/lib/util/metadata.d.ts.map +1 -1
  177. package/lib/util/metadata.js +2 -1
  178. package/lib/util/metadata.js.map +1 -1
  179. package/package.json +17 -16
  180. package/src/api/impl/beacon/index.ts +15 -0
  181. package/src/api/impl/beacon/state/index.ts +43 -6
  182. package/src/api/impl/debug/index.ts +28 -19
  183. package/src/api/impl/validator/index.ts +45 -0
  184. package/src/chain/archiveStore/archiveStore.ts +18 -24
  185. package/src/chain/archiveStore/utils/pruneHistory.ts +4 -1
  186. package/src/chain/beaconProposerCache.ts +19 -1
  187. package/src/chain/blocks/importBlock.ts +17 -7
  188. package/src/chain/blocks/verifyBlock.ts +7 -6
  189. package/src/chain/chain.ts +97 -19
  190. package/src/chain/interface.ts +3 -0
  191. package/src/chain/opPools/aggregatedAttestationPool.ts +34 -10
  192. package/src/chain/opPools/opPool.ts +1 -31
  193. package/src/chain/opPools/proposerPreferencesPool.ts +34 -11
  194. package/src/chain/opPools/utils.ts +31 -0
  195. package/src/chain/options.ts +1 -1
  196. package/src/chain/prepareNextSlot.ts +15 -3
  197. package/src/chain/produceBlock/produceBlockBody.ts +68 -34
  198. package/src/chain/seenCache/seenGossipBlockInput.ts +23 -0
  199. package/src/chain/shufflingCache.ts +84 -22
  200. package/src/db/beacon.ts +6 -0
  201. package/src/db/buckets.ts +2 -0
  202. package/src/db/interface.ts +4 -0
  203. package/src/db/repositories/earliestAvailableSlot.ts +19 -0
  204. package/src/db/repositories/index.ts +1 -0
  205. package/src/db/repositories/proposerPreferences.ts +23 -0
  206. package/src/execution/engine/blobVersionedHashes.ts +72 -0
  207. package/src/execution/engine/http.ts +304 -271
  208. package/src/execution/engine/index.ts +41 -7
  209. package/src/execution/engine/interface.ts +1 -1
  210. package/src/execution/engine/jsonRpcTransport.ts +247 -0
  211. package/src/execution/engine/restHttpClient.ts +269 -0
  212. package/src/execution/engine/restTransport.ts +544 -0
  213. package/src/execution/engine/sszTypes.ts +354 -0
  214. package/src/execution/engine/transport.ts +61 -0
  215. package/src/execution/engine/types.ts +34 -74
  216. package/src/execution/engine/utils.ts +12 -2
  217. package/src/metrics/metrics/lodestar.ts +23 -0
  218. package/src/network/core/events.ts +16 -1
  219. package/src/network/processor/gossipHandlers.ts +91 -77
  220. package/src/node/nodejs.ts +4 -0
  221. package/src/node/options.ts +10 -1
  222. package/src/sync/range/batch.ts +14 -19
  223. package/src/sync/range/chain.ts +12 -7
  224. package/src/sync/range/range.ts +26 -12
  225. package/src/sync/unknownBlock.ts +57 -21
  226. package/src/sync/utils/downloadByRange.ts +123 -58
  227. package/src/util/asyncIterableToEvents.ts +44 -5
  228. package/src/util/metadata.ts +2 -1
@@ -1,21 +1,30 @@
1
1
  import EventEmitter from "node:events";
2
2
  import {ResponseIncoming, ResponseOutgoing} from "@lodestar/reqresp";
3
- import {AsyncIterableEventBus, IteratorEvent, RequestEvent} from "../../util/asyncIterableToEvents.js";
3
+ import {
4
+ AsyncIterableEventBus,
5
+ IteratorEvent,
6
+ RequestCancelEvent,
7
+ RequestEvent,
8
+ } from "../../util/asyncIterableToEvents.js";
4
9
  import {StrictEventEmitterSingleArg} from "../../util/strictEvents.js";
5
10
  import {EventDirection} from "../events.js";
6
11
  import {IncomingRequestArgs, OutgoingRequestArgs} from "../reqresp/types.js";
7
12
 
8
13
  export enum ReqRespBridgeEvent {
9
14
  outgoingRequest = "reqresp.outgoingRequest",
15
+ outgoingRequestCancel = "reqresp.outgoingRequestCancel",
10
16
  outgoingResponse = "reqresp.outgoingResponse",
11
17
  incomingRequest = "reqresp.incomingRequest",
18
+ incomingRequestCancel = "reqresp.incomingRequestCancel",
12
19
  incomingResponse = "reqresp.incomingResponse",
13
20
  }
14
21
 
15
22
  export type ReqRespBridgeEventData = {
16
23
  [ReqRespBridgeEvent.outgoingRequest]: RequestEvent<OutgoingRequestArgs>;
24
+ [ReqRespBridgeEvent.outgoingRequestCancel]: RequestCancelEvent;
17
25
  [ReqRespBridgeEvent.outgoingResponse]: IteratorEvent<ResponseOutgoing>;
18
26
  [ReqRespBridgeEvent.incomingRequest]: RequestEvent<IncomingRequestArgs>;
27
+ [ReqRespBridgeEvent.incomingRequestCancel]: RequestCancelEvent;
19
28
  [ReqRespBridgeEvent.incomingResponse]: IteratorEvent<ResponseIncoming>;
20
29
  };
21
30
 
@@ -26,8 +35,10 @@ export class ReqRespBridgeEventBus extends (EventEmitter as {new (): IReqRespBri
26
35
  // NOTE: If the same event is on this two arrays it can create an infinite cycle
27
36
  export const reqRespBridgeEventDirection: Record<ReqRespBridgeEvent, EventDirection> = {
28
37
  [ReqRespBridgeEvent.outgoingRequest]: EventDirection.mainToWorker,
38
+ [ReqRespBridgeEvent.outgoingRequestCancel]: EventDirection.mainToWorker,
29
39
  [ReqRespBridgeEvent.outgoingResponse]: EventDirection.mainToWorker,
30
40
  [ReqRespBridgeEvent.incomingRequest]: EventDirection.workerToMain,
41
+ [ReqRespBridgeEvent.incomingRequestCancel]: EventDirection.workerToMain,
31
42
  [ReqRespBridgeEvent.incomingResponse]: EventDirection.workerToMain,
32
43
  };
33
44
 
@@ -36,8 +47,10 @@ export function getReqRespBridgeReqEvents(
36
47
  ): AsyncIterableEventBus<OutgoingRequestArgs, ResponseIncoming> {
37
48
  return {
38
49
  emitRequest: (data) => events.emit(ReqRespBridgeEvent.outgoingRequest, data),
50
+ emitRequestCancel: (data) => events.emit(ReqRespBridgeEvent.outgoingRequestCancel, data),
39
51
  emitResponse: (data) => events.emit(ReqRespBridgeEvent.incomingResponse, data),
40
52
  onRequest: (cb) => events.on(ReqRespBridgeEvent.outgoingRequest, cb),
53
+ onRequestCancel: (cb) => events.on(ReqRespBridgeEvent.outgoingRequestCancel, cb),
41
54
  onResponse: (cb) => events.on(ReqRespBridgeEvent.incomingResponse, cb),
42
55
  };
43
56
  }
@@ -47,8 +60,10 @@ export function getReqRespBridgeRespEvents(
47
60
  ): AsyncIterableEventBus<IncomingRequestArgs, ResponseOutgoing> {
48
61
  return {
49
62
  emitRequest: (data) => events.emit(ReqRespBridgeEvent.incomingRequest, data),
63
+ emitRequestCancel: (data) => events.emit(ReqRespBridgeEvent.incomingRequestCancel, data),
50
64
  emitResponse: (data) => events.emit(ReqRespBridgeEvent.outgoingResponse, data),
51
65
  onRequest: (cb) => events.on(ReqRespBridgeEvent.incomingRequest, cb),
66
+ onRequestCancel: (cb) => events.on(ReqRespBridgeEvent.incomingRequestCancel, cb),
52
67
  onResponse: (cb) => events.on(ReqRespBridgeEvent.outgoingResponse, cb),
53
68
  };
54
69
  }
@@ -12,9 +12,10 @@ import {
12
12
  isForkPostElectra,
13
13
  isForkPostGloas,
14
14
  } from "@lodestar/params";
15
- import {computeTimeAtSlot} from "@lodestar/state-transition";
15
+ import {computeTimeAtSlot, signedBlockToSignedHeader} from "@lodestar/state-transition";
16
16
  import {
17
17
  Root,
18
+ RootHex,
18
19
  SignedBeaconBlock,
19
20
  SingleAttestation,
20
21
  Slot,
@@ -78,6 +79,7 @@ import {
78
79
  validateGossipSyncCommittee,
79
80
  validateGossipVoluntaryExit,
80
81
  validateSyncCommitteeGossipContributionAndProof,
82
+ verifyBlockProposerSignature,
81
83
  } from "../../chain/validation/index.js";
82
84
  import {validateLightClientFinalityUpdate} from "../../chain/validation/lightClientFinalityUpdate.js";
83
85
  import {validateLightClientOptimisticUpdate} from "../../chain/validation/lightClientOptimisticUpdate.js";
@@ -148,37 +150,16 @@ export function getGossipHandlers(modules: ValidatorFnsModules, options: GossipH
148
150
  function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHandlerOpts): SequentialGossipHandlers {
149
151
  const {chain, config, metrics, logger, core} = modules;
150
152
 
151
- async function validateBeaconBlock(
153
+ /**
154
+ * Add a gossip block to the seen caches. The proposer signature of the block MUST be verified.
155
+ */
156
+ function cacheSignatureVerifiedBlock(
152
157
  signedBlock: SignedBeaconBlock,
158
+ blockRootHex: RootHex,
153
159
  fork: ForkName,
154
160
  peerIdStr: string,
155
161
  seenTimestampSec: number
156
- ): Promise<IBlockInput> {
157
- const slot = signedBlock.message.slot;
158
- const forkTypes = config.getForkTypes(slot);
159
- const blockRootHex = toRootHex(forkTypes.BeaconBlock.hashTreeRoot(signedBlock.message));
160
- const delaySec = chain.clock.secFromSlot(slot, seenTimestampSec);
161
- const recvToValLatency = Date.now() / 1000 - seenTimestampSec;
162
-
163
- // always set block to seen cache for all forks so that we don't need to download it
164
- // TODO: validate block before adding to cache
165
- // tracked in https://github.com/ChainSafe/lodestar/issues/7957
166
-
167
- const logCtx = {
168
- slot,
169
- root: blockRootHex,
170
- currentSlot: chain.clock.currentSlot,
171
- peerId: peerIdStr,
172
- delaySec,
173
- recvToValLatency,
174
- };
175
-
176
- logger.debug("Received gossip block", {...logCtx});
177
-
178
- // optimistically add gossip block to the seen cache
179
- // if validation fails, we will NOT forward this gossip block to peers
180
- // - if PARENT_BLOCK_UNKNOWN error, blockInput will then be queued inside BlockInputSync. If the gossip block is really invalid, it will be pruned there
181
- // - if other validator errors, blockInput will stay in the seen cache and will be pruned on finalization
162
+ ): BlockInput {
182
163
  const blockInput = chain.seenBlockInputCache.getByBlock({
183
164
  block: signedBlock,
184
165
  blockRootHex,
@@ -187,8 +168,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
187
168
  peerIdStr,
188
169
  });
189
170
 
190
- // Optimistically seed the payload-envelope cache too, mirroring seenBlockInputCache above.
191
- // This ensures we have PayloadEnvelopeInput, even through "PARENT_BLOCK_UNKNOWN" error
171
+ // Also seed the payload-envelope cache, so we have PayloadEnvelopeInput even through "PARENT_BLOCK_UNKNOWN" error
192
172
  // see https://github.com/ChainSafe/lodestar/issues/9475
193
173
  if (isForkPostGloas(fork)) {
194
174
  chain.seenPayloadEnvelopeInputCache.add({
@@ -202,8 +182,37 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
202
182
  });
203
183
  }
204
184
 
185
+ return blockInput;
186
+ }
187
+
188
+ async function validateBeaconBlock(
189
+ signedBlock: SignedBeaconBlock,
190
+ fork: ForkName,
191
+ peerIdStr: string,
192
+ seenTimestampSec: number
193
+ ): Promise<IBlockInput> {
194
+ const slot = signedBlock.message.slot;
195
+ const forkTypes = config.getForkTypes(slot);
196
+ const blockRootHex = toRootHex(forkTypes.BeaconBlock.hashTreeRoot(signedBlock.message));
197
+ const delaySec = chain.clock.secFromSlot(slot, seenTimestampSec);
198
+ const recvToValLatency = Date.now() / 1000 - seenTimestampSec;
199
+
200
+ const logCtx = {
201
+ slot,
202
+ root: blockRootHex,
203
+ currentSlot: chain.clock.currentSlot,
204
+ peerId: peerIdStr,
205
+ delaySec,
206
+ recvToValLatency,
207
+ };
208
+
209
+ logger.debug("Received gossip block", {...logCtx});
210
+
211
+ chain.seenBlockInputCache.markValidatingBlock(blockRootHex);
212
+
205
213
  try {
206
214
  const {skippedSlots} = await validateGossipBlock(config, chain, signedBlock, fork);
215
+ const blockInput = cacheSignatureVerifiedBlock(signedBlock, blockRootHex, fork, peerIdStr, seenTimestampSec);
207
216
 
208
217
  const blockInputMeta = blockInput.getLogMeta();
209
218
 
@@ -229,54 +238,54 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
229
238
  if (e instanceof BlockGossipError) {
230
239
  logger.debug("Gossip block has error", {slot, root: blockRootHex, code: e.type.code});
231
240
  if (
232
- (e.type.code === BlockErrorCode.PARENT_BLOCK_UNKNOWN ||
233
- e.type.code === BlockErrorCode.PARENT_PAYLOAD_UNKNOWN) &&
234
- blockInput
241
+ e.type.code === BlockErrorCode.PARENT_BLOCK_UNKNOWN ||
242
+ e.type.code === BlockErrorCode.PARENT_PAYLOAD_UNKNOWN
235
243
  ) {
244
+ // The block is retained for unknown parent sync without full validation. Authenticate it first, this
245
+ // does not need the parent state
246
+ const proposerIndex = signedBlock.message.proposerIndex;
247
+ await verifyBlockProposerSignature(chain, signedBlock, blockRootHex);
248
+ chain.seenBlockProposers.observeBlockRoot(
249
+ slot,
250
+ proposerIndex,
251
+ blockRootHex,
252
+ signedBlockToSignedHeader(config, signedBlock)
253
+ );
254
+
255
+ // Check again after the signature verification so concurrent proposals cannot both be retained
256
+ if (chain.seenBlockProposers.isKnown(slot, proposerIndex)) {
257
+ if (chain.seenBlockProposers.isRepeatProposal(slot, proposerIndex, blockRootHex)) {
258
+ throw new BlockGossipError(GossipAction.IGNORE, {
259
+ code: BlockErrorCode.REPEAT_PROPOSAL,
260
+ proposerIndex,
261
+ root: blockRootHex,
262
+ });
263
+ }
264
+ throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.ALREADY_KNOWN, root: blockRootHex});
265
+ }
266
+ chain.seenBlockProposers.add(slot, proposerIndex, blockRootHex);
267
+
268
+ const blockInput = cacheSignatureVerifiedBlock(signedBlock, blockRootHex, fork, peerIdStr, seenTimestampSec);
236
269
  chain.emitter.emit(ChainEvent.blockUnknownParent, {
237
270
  blockInput,
238
271
  peer: peerIdStr,
239
272
  source: BlockInputSource.gossip,
240
273
  });
241
- // throw error (don't prune the blockInput)
242
274
  throw e;
243
275
  }
244
276
 
245
- // IGNORE means the block is acceptable (e.g. FUTURE_SLOT, ALREADY_KNOWN), just not propagated.
246
- // Keep the optimistically-added cache entries; they are pruned on finalization. Only REJECT
247
- // (provably invalid), unexpected errors and repeat proposals that are not imported prune.
248
- if (e.action === GossipAction.IGNORE) {
249
- // Only a signature-verified sibling is imported by the beacon_block handler, any other repeat proposal
250
- // is dropped from the caches and re-downloaded by sync if it ever becomes relevant. Its signature was not
251
- // verified, so only its own entry is removed, never its claimed ancestors
252
- if (
253
- e.type.code === BlockErrorCode.REPEAT_PROPOSAL &&
254
- !chain.seenBlockProposers.hasBlockRoot(slot, signedBlock.message.proposerIndex, blockRootHex)
255
- ) {
256
- chain.seenBlockInputCache.remove(blockRootHex);
257
- if (isForkPostGloas(fork)) {
258
- chain.seenPayloadEnvelopeInputCache.remove(blockRootHex);
259
- }
260
- }
261
- throw e;
277
+ if (e.action === GossipAction.REJECT) {
278
+ chain.persistInvalidSszValue(
279
+ forkTypes.SignedBeaconBlock,
280
+ signedBlock,
281
+ `gossip_reject_slot_${slot}`,
282
+ blockRootHex
283
+ );
262
284
  }
263
-
264
- chain.persistInvalidSszValue(
265
- forkTypes.SignedBeaconBlock,
266
- signedBlock,
267
- `gossip_reject_slot_${slot}`,
268
- blockRootHex
269
- );
270
- }
271
-
272
- // REJECT or unexpected (non-BlockGossipError) error: drop the optimistically-added entries from
273
- // both caches, keeping them consistent. The block may carry any parent root, so only its own entry is removed
274
- chain.seenBlockInputCache.remove(blockRootHex);
275
- if (isForkPostGloas(fork)) {
276
- chain.seenPayloadEnvelopeInputCache.remove(blockRootHex);
277
285
  }
278
286
  throw e;
279
287
  } finally {
288
+ chain.seenBlockInputCache.unmarkValidatingBlock(blockRootHex);
280
289
  // The block received from the network may have established an equivocation, either by conflicting
281
290
  // with a previously observed block root (REPEAT_PROPOSAL) or with a root observed during validation
282
291
  const proposerIndex = signedBlock.message.proposerIndex;
@@ -752,18 +761,23 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
752
761
  e.type.code === BlockErrorCode.REPEAT_PROPOSAL &&
753
762
  // Only a signature-verified sibling recorded in the seen cache is imported. The cache holds at most two
754
763
  // roots per proposer and slot, which bounds full imports over gossip to one alternate
755
- chain.seenBlockProposers.hasBlockRoot(signedBlock.message.slot, e.type.proposerIndex, e.type.root)
764
+ chain.seenBlockProposers.hasBlockRoot(signedBlock.message.slot, e.type.proposerIndex, e.type.root) &&
765
+ // A sibling with an unknown parent cannot be imported, sync fetches it if its branch becomes relevant
766
+ chain.forkChoice.getBlockHexDefaultStatus(toRootHex(signedBlock.message.parentRoot)) !== null
756
767
  ) {
757
- // blockInput was optimistically seeded in validateBeaconBlock and retained on IGNORE
758
- const blockInput = chain.seenBlockInputCache.get(e.type.root);
759
- if (blockInput) {
760
- // We're returning IGNORE (thrown below), so this block is not forwarded to peers. Unlike the
761
- // happy path there is no rush to forward, so we don't need to wrap in callInNextEventLoop.
762
- chain.serializedCache.set(signedBlock, serializedData);
763
- // this is technically not a valid gossip block but gossip validation is a cheap subset of checks
764
- // this runs the full state transition, so importing an equivocating-but-valid block here is safe.
765
- handleValidBeaconBlock(blockInput, peerIdStr, seenTimestampSec);
766
- }
768
+ const blockInput = cacheSignatureVerifiedBlock(
769
+ signedBlock,
770
+ e.type.root,
771
+ topic.boundary.fork,
772
+ peerIdStr,
773
+ seenTimestampSec
774
+ );
775
+ // We're returning IGNORE (thrown below), so this block is not forwarded to peers. Unlike the
776
+ // happy path there is no rush to forward, so we don't need to wrap in callInNextEventLoop.
777
+ chain.serializedCache.set(signedBlock, serializedData);
778
+ // this is technically not a valid gossip block but gossip validation is a cheap subset of checks
779
+ // this runs the full state transition, so importing an equivocating-but-valid block here is safe.
780
+ handleValidBeaconBlock(blockInput, peerIdStr, seenTimestampSec);
767
781
  }
768
782
  // rethrow so gossipValidatorFn maps IGNORE -> TopicValidatorResult.Ignore (message not forwarded)
769
783
  throw e;
@@ -1381,7 +1395,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1381
1395
  const delaySec = chain.clock.secFromSlot(payloadAttestationMessage.data.slot, seenTimestampSec);
1382
1396
  metrics?.gossipPayloadAttestationMessage.elapsedTimeTillReceived.observe({source: OpSource.gossip}, delaySec);
1383
1397
  const nextSlotProposer = chain.getHeadState().getBeaconProposer(payloadAttestationMessage.data.slot + 1);
1384
- const isNextSlotProposer = chain.beaconProposerCache.get(nextSlotProposer) !== undefined;
1398
+ const isNextSlotProposer = chain.beaconProposerCache.has(nextSlotProposer);
1385
1399
 
1386
1400
  if (isNextSlotProposer) {
1387
1401
  try {
@@ -14,6 +14,7 @@ import {BeaconConfig} from "@lodestar/config";
14
14
  import type {LoggerNode} from "@lodestar/logger/node";
15
15
  import {ZERO_HASH_HEX} from "@lodestar/params";
16
16
  import {IBeaconStateView, isStatePostBellatrix, isStatePostGloas} from "@lodestar/state-transition";
17
+ import {Slot} from "@lodestar/types";
17
18
  import {sleep, toRootHex} from "@lodestar/utils";
18
19
  import {ProcessShutdownCallback} from "@lodestar/validator";
19
20
  import {BeaconRestApiServer, getApi} from "../api/index.js";
@@ -61,6 +62,7 @@ export type BeaconNodeInitModules = {
61
62
  peerStoreDir?: string;
62
63
  anchorState: IBeaconStateView;
63
64
  isAnchorStateFinalized: boolean;
65
+ earliestAvailableSlot: Slot;
64
66
  metricsRegistries?: Registry[];
65
67
  };
66
68
 
@@ -159,6 +161,7 @@ export class BeaconNode {
159
161
  peerStoreDir,
160
162
  anchorState,
161
163
  isAnchorStateFinalized,
164
+ earliestAvailableSlot,
162
165
  metricsRegistries = [],
163
166
  }: BeaconNodeInitModules): Promise<T> {
164
167
  if (hasher.name !== "hashtree") {
@@ -266,6 +269,7 @@ export class BeaconNode {
266
269
  validatorMonitor,
267
270
  anchorState,
268
271
  isAnchorStateFinalized,
272
+ earliestAvailableSlot,
269
273
  executionEngine: initializeExecutionEngine(executionEngineOpts, {
270
274
  metrics,
271
275
  signal,
@@ -3,12 +3,14 @@ import {ArchiveMode, DEFAULT_ARCHIVE_MODE, IChainOptions, defaultChainOptions} f
3
3
  import {ValidatorMonitorOpts, defaultValidatorMonitorOpts} from "../chain/validatorMonitor.js";
4
4
  import {DatabaseOptions, defaultDbOptions} from "../db/options.js";
5
5
  import {
6
+ EngineApiMode,
6
7
  ExecutionBuilderOpts,
7
8
  ExecutionEngineOpts,
8
9
  defaultExecutionBuilderHttpOpts,
9
10
  defaultExecutionBuilderOpts,
10
11
  defaultExecutionEngineHttpOpts,
11
12
  defaultExecutionEngineOpts,
13
+ engineApiModes,
12
14
  } from "../execution/index.js";
13
15
  import {MetricsOptions, defaultMetricsOptions} from "../metrics/options.js";
14
16
  import {MonitoringOptions, defaultMonitoringOptions} from "../monitoring/options.js";
@@ -19,7 +21,14 @@ import {SyncOptions, defaultSyncOptions} from "../sync/options.js";
19
21
  export {allNamespaces} from "../api/rest/index.js";
20
22
 
21
23
  // Re-export to use as default values in CLI args
22
- export {defaultExecutionEngineHttpOpts, defaultExecutionBuilderHttpOpts, ArchiveMode, DEFAULT_ARCHIVE_MODE};
24
+ export {
25
+ defaultExecutionEngineHttpOpts,
26
+ defaultExecutionBuilderHttpOpts,
27
+ engineApiModes,
28
+ ArchiveMode,
29
+ DEFAULT_ARCHIVE_MODE,
30
+ };
31
+ export type {EngineApiMode};
23
32
 
24
33
  export interface IBeaconNodeOptions {
25
34
  api: ApiOptions;
@@ -14,7 +14,7 @@ import {IClock} from "../../util/clock.js";
14
14
  import {CustodyConfig} from "../../util/dataColumns.js";
15
15
  import {PeerIdStr} from "../../util/peerId.js";
16
16
  import {MAX_BATCH_DOWNLOAD_ATTEMPTS, MAX_BATCH_PROCESSING_ATTEMPTS} from "../constants.js";
17
- import {DownloadByRangeRequests, ParentPayloadCommitments} from "../utils/downloadByRange.js";
17
+ import {DownloadByRangeRequests, ParentPayload} from "../utils/downloadByRange.js";
18
18
  import {getBatchSlotRange, hashBlocks} from "./utils/index.js";
19
19
 
20
20
  /**
@@ -175,7 +175,7 @@ export class Batch {
175
175
  private readonly clock: IClock;
176
176
  private readonly custodyConfig: CustodyConfig;
177
177
  private readonly isFirstBatchInChain: boolean;
178
- private readonly latestBid: gloas.ExecutionPayloadBid | undefined;
178
+ private readonly parentPayload: ParentPayload | undefined;
179
179
 
180
180
  constructor(
181
181
  startEpoch: Epoch,
@@ -183,7 +183,7 @@ export class Batch {
183
183
  clock: IClock,
184
184
  custodyConfig: CustodyConfig,
185
185
  isFirstBatchInChain: boolean,
186
- latestBid: gloas.ExecutionPayloadBid | undefined,
186
+ parentPayload: ParentPayload | undefined,
187
187
  targetSlot: Slot
188
188
  ) {
189
189
  this.config = config;
@@ -196,7 +196,7 @@ export class Batch {
196
196
  this.startSlot = startSlot;
197
197
  this.count = Math.min(count, targetSlot - startSlot + 1);
198
198
  this.isFirstBatchInChain = isFirstBatchInChain;
199
- this.latestBid = latestBid;
199
+ this.parentPayload = parentPayload;
200
200
  this.requests = this.getRequests([]);
201
201
  }
202
202
 
@@ -208,24 +208,19 @@ export class Batch {
208
208
  }
209
209
 
210
210
  // we only know if we should download parent envelope if firstBlock is downloaded
211
- if (firstBlock === undefined) return false;
212
- if (this.latestBid === undefined) return false;
213
- const firstBlockBidParentHash = (firstBlock.message.body as gloas.BeaconBlockBody).signedExecutionPayloadBid.message
214
- .parentBlockHash;
215
- return byteArrayEquals(firstBlockBidParentHash, this.latestBid.blockHash);
211
+ if (firstBlock === undefined || this.parentPayload === undefined) return false;
212
+ const firstBlockBid = (firstBlock.message.body as gloas.BeaconBlockBody).signedExecutionPayloadBid.message;
213
+ return (
214
+ byteArrayEquals(firstBlock.message.parentRoot, this.parentPayload.blockRoot) &&
215
+ byteArrayEquals(firstBlockBid.parentBlockHash, this.parentPayload.bid.blockHash)
216
+ );
216
217
  }
217
218
 
218
- getParentPayloadCommitments(parentBlockRoot: Uint8Array): ParentPayloadCommitments {
219
- if (this.latestBid === undefined) {
220
- throw new Error(
221
- `Coding error: getParentPayloadCommitments called without latestBid for parentBlockRoot=${toRootHex(parentBlockRoot)}`
222
- );
219
+ getParentPayload(): ParentPayload {
220
+ if (this.parentPayload === undefined) {
221
+ throw new Error("Coding error: getParentPayload called without parentPayload");
223
222
  }
224
- return {
225
- blockRoot: parentBlockRoot,
226
- blockRootHex: toRootHex(parentBlockRoot),
227
- kzgCommitments: this.latestBid.blobKzgCommitments,
228
- };
223
+ return this.parentPayload;
229
224
  }
230
225
 
231
226
  /**
@@ -1,5 +1,5 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
- import {Epoch, Root, Slot, gloas} from "@lodestar/types";
2
+ import {Epoch, Root, Slot} from "@lodestar/types";
3
3
  import {ErrorAborted, LodestarError, Logger, prettyPrintIndices, toRootHex} from "@lodestar/utils";
4
4
  import {isBlockInputBlobs, isBlockInputColumns} from "../../chain/blocks/blockInput/blockInput.js";
5
5
  import {BlockInputErrorCode} from "../../chain/blocks/blockInput/errors.js";
@@ -16,7 +16,7 @@ import {ItTrigger} from "../../util/itTrigger.js";
16
16
  import {PeerIdStr} from "../../util/peerId.js";
17
17
  import {WarnResult, wrapError} from "../../util/wrapError.js";
18
18
  import {BATCH_BUFFER_SIZE, EPOCHS_PER_BATCH, MAX_LOOK_AHEAD_EPOCHS} from "../constants.js";
19
- import {DownloadByRangeError, DownloadByRangeErrorCode} from "../utils/downloadByRange.js";
19
+ import {DownloadByRangeError, DownloadByRangeErrorCode, ParentPayload} from "../utils/downloadByRange.js";
20
20
  import {getRateLimitedUntilMs} from "../utils/rateLimit.js";
21
21
  import {RangeSyncType} from "../utils/remoteSyncType.js";
22
22
  import {Batch, BatchError, BatchErrorCode, BatchMetadata, BatchStatus} from "./batch.js";
@@ -160,7 +160,7 @@ export class SyncChain {
160
160
  private readonly clock: IClock;
161
161
  private readonly metrics: Metrics | null;
162
162
  private readonly custodyConfig: CustodyConfig;
163
- private readonly latestBid: gloas.ExecutionPayloadBid | undefined;
163
+ private readonly parentPayload: ParentPayload | undefined;
164
164
 
165
165
  constructor(
166
166
  initialBatchEpoch: Epoch,
@@ -168,7 +168,7 @@ export class SyncChain {
168
168
  syncType: RangeSyncType,
169
169
  fns: SyncChainFns,
170
170
  modules: SyncChainModules,
171
- latestBid: gloas.ExecutionPayloadBid | undefined
171
+ parentPayload: ParentPayload | undefined
172
172
  ) {
173
173
  const {config, clock, custodyConfig, logger, metrics} = modules;
174
174
  this.firstBatchEpoch = initialBatchEpoch;
@@ -184,7 +184,7 @@ export class SyncChain {
184
184
  this.clock = clock;
185
185
  this.metrics = metrics;
186
186
  this.custodyConfig = custodyConfig;
187
- this.latestBid = latestBid;
187
+ this.parentPayload = parentPayload;
188
188
  this.logger = logger;
189
189
  this.logId = `${syncType}-${nextChainId++}`;
190
190
 
@@ -225,6 +225,9 @@ export class SyncChain {
225
225
  Math.floor((localFinalizedEpoch - this.lastEpochWithProcessBlocks) / EPOCHS_PER_BATCH) * EPOCHS_PER_BATCH;
226
226
  this.advanceChain(lastEpochWithProcessBlocksAligned);
227
227
 
228
+ // stopSyncing() cleared the retry timer, but peers can still be in rate limit backoff
229
+ this.scheduleRateLimitBackoffRetry();
230
+
228
231
  // Potentially download new batches and process pending
229
232
  this.triggerBatchDownloader();
230
233
  this.triggerBatchProcessor();
@@ -533,8 +536,8 @@ export class SyncChain {
533
536
  this.clock,
534
537
  this.custodyConfig,
535
538
  this.isFirstBatch,
536
- // `latestBid` is only meaningful for the first batch's parent-payload check
537
- this.isFirstBatch ? this.latestBid : undefined,
539
+ // `parentPayload` is only meaningful for the first batch
540
+ this.isFirstBatch ? this.parentPayload : undefined,
538
541
  this.target.slot
539
542
  );
540
543
  this.isFirstBatch = false;
@@ -583,6 +586,8 @@ export class SyncChain {
583
586
  case DownloadByRangeErrorCode.OUT_OF_RANGE_BLOCKS:
584
587
  case DownloadByRangeErrorCode.PARENT_ROOT_MISMATCH:
585
588
  case DownloadByRangeErrorCode.INVALID_ENVELOPE_BEACON_BLOCK_ROOT:
589
+ case DownloadByRangeErrorCode.INVALID_ENVELOPE_BLOCK_HASH:
590
+ case DownloadByRangeErrorCode.INVALID_ENVELOPE_SLOT:
586
591
  case DownloadByRangeErrorCode.INVALID_CHAIN_SEGMENT:
587
592
  case BlobSidecarErrorCode.INCLUSION_PROOF_INVALID:
588
593
  case BlobSidecarErrorCode.INVALID_KZG_PROOF_BATCH:
@@ -2,8 +2,8 @@ import {EventEmitter} from "node:events";
2
2
  import {StrictEventEmitter} from "strict-event-emitter-types";
3
3
  import {BeaconConfig} from "@lodestar/config";
4
4
  import {IBeaconStateViewGloas, computeStartSlotAtEpoch, isStatePostGloas} from "@lodestar/state-transition";
5
- import {Epoch, Status, fulu} from "@lodestar/types";
6
- import {Logger, prettyPrintIndices, toRootHex} from "@lodestar/utils";
5
+ import {Epoch, Status, fulu, ssz} from "@lodestar/types";
6
+ import {Logger, byteArrayEquals, prettyPrintIndices, toRootHex} from "@lodestar/utils";
7
7
  import {IBlockInput} from "../../chain/blocks/blockInput/types.js";
8
8
  import {PayloadError, PayloadErrorCode} from "../../chain/blocks/importExecutionPayload.js";
9
9
  import {AttestationImportOpt, ImportBlockOpts} from "../../chain/blocks/index.js";
@@ -16,6 +16,7 @@ import {PeerIdStr} from "../../util/peerId.js";
16
16
  import {
17
17
  DownloadByRangeError,
18
18
  DownloadByRangeErrorCode,
19
+ ParentPayload,
19
20
  cacheByRangeResponses,
20
21
  downloadByRange,
21
22
  } from "../utils/downloadByRange.js";
@@ -278,15 +279,14 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
278
279
  private downloadByRange: SyncChainFns["downloadByRange"] = async (peer, batch) => {
279
280
  const batchBlocks = batch.getBlocks();
280
281
  const requests = batch.getRequestsForPeer(peer);
281
- const parentRoot = requests.parentPayloadRequest?.envelopeBlockRoot ?? requests.parentPayloadRequest?.blockRoot;
282
- const parentPayloadCommitments = parentRoot ? batch.getParentPayloadCommitments(parentRoot) : undefined;
282
+ const parentPayload = requests.parentPayloadRequest ? batch.getParentPayload() : undefined;
283
283
  const {result, warnings} = await downloadByRange({
284
284
  config: this.config,
285
285
  network: this.network,
286
286
  logger: this.logger,
287
287
  peerIdStr: peer.peerId,
288
288
  batchBlocks,
289
- parentPayloadCommitments,
289
+ parentPayload,
290
290
  peerDasMetrics: this.chain.metrics?.peerDas,
291
291
  ...requests,
292
292
  });
@@ -299,6 +299,7 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
299
299
  batchBlocks,
300
300
  downloadedPayloadEnvelopes,
301
301
  existingPayloadEnvelopes: batch.getPayloadEnvelopes(),
302
+ parentPayload,
302
303
  custodyConfig: this.chain.custodyConfig,
303
304
  seenTimestampSec: Date.now() / 1000,
304
305
  });
@@ -376,13 +377,26 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
376
377
  private addPeerOrCreateChain(startEpoch: Epoch, target: ChainTarget, peer: PeerIdStr, syncType: RangeSyncType): void {
377
378
  let syncChain = this.chains.get(syncType);
378
379
  if (!syncChain) {
379
- // The first batch of a new sync chain may need to detect whether the parent block was an
380
- // gloas "empty" block (no envelope produced). It does so by comparing the first
381
- // downloaded block's `bid.parentBlockHash` against the head state's `latestExecutionPayloadBid.blockHash`.
380
+ // The first block of the first batch may build on the head's FULL variant before its payload is imported,
381
+ // the batch then downloads the head's payload as dangling parent and validates it against the bid
382
382
  const headState = this.chain.getHeadState();
383
- const latestBid = isStatePostGloas(headState)
384
- ? (headState as IBeaconStateViewGloas).latestExecutionPayloadBid
385
- : undefined;
383
+ let parentPayload: ParentPayload | undefined;
384
+ if (isStatePostGloas(headState) && headState.latestBlockHeader.slot > 0) {
385
+ const header = ssz.phase0.BeaconBlockHeader.clone(headState.latestBlockHeader);
386
+ if (byteArrayEquals(header.stateRoot, ssz.Root.defaultValue())) {
387
+ header.stateRoot = headState.hashTreeRoot();
388
+ }
389
+ const headRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(header);
390
+ const headSlot = headState.latestBlockHeader.slot;
391
+ parentPayload = {
392
+ blockRoot: headRoot,
393
+ blockRootHex: toRootHex(headRoot),
394
+ slot: headSlot,
395
+ proposerIndex: headState.latestBlockHeader.proposerIndex,
396
+ forkName: this.config.getForkName(headSlot),
397
+ bid: (headState as IBeaconStateViewGloas).latestExecutionPayloadBid,
398
+ };
399
+ }
386
400
 
387
401
  syncChain = new SyncChain(
388
402
  startEpoch,
@@ -403,7 +417,7 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
403
417
  custodyConfig: this.chain.custodyConfig,
404
418
  metrics: this.metrics,
405
419
  },
406
- latestBid
420
+ parentPayload
407
421
  );
408
422
  this.chains.set(syncType, syncChain);
409
423