@lodestar/beacon-node 1.45.0-dev.fadf0fbb1f → 1.46.0-dev.8fc4e297d2

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 (240) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +186 -57
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/index.d.ts +1 -1
  5. package/lib/api/impl/beacon/index.d.ts.map +1 -1
  6. package/lib/api/impl/beacon/index.js.map +1 -1
  7. package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
  8. package/lib/api/impl/beacon/pool/index.js +4 -0
  9. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  10. package/lib/api/impl/beacon/state/index.d.ts +1 -1
  11. package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
  12. package/lib/api/impl/beacon/state/index.js +2 -2
  13. package/lib/api/impl/beacon/state/index.js.map +1 -1
  14. package/lib/api/impl/beacon/state/utils.d.ts +2 -1
  15. package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
  16. package/lib/api/impl/beacon/state/utils.js +9 -1
  17. package/lib/api/impl/beacon/state/utils.js.map +1 -1
  18. package/lib/api/impl/debug/index.d.ts +1 -1
  19. package/lib/api/impl/debug/index.d.ts.map +1 -1
  20. package/lib/api/impl/debug/index.js +2 -2
  21. package/lib/api/impl/debug/index.js.map +1 -1
  22. package/lib/api/impl/lodestar/index.js +2 -2
  23. package/lib/api/impl/lodestar/index.js.map +1 -1
  24. package/lib/api/impl/proof/index.d.ts +1 -1
  25. package/lib/api/impl/proof/index.d.ts.map +1 -1
  26. package/lib/api/impl/proof/index.js +2 -2
  27. package/lib/api/impl/proof/index.js.map +1 -1
  28. package/lib/api/impl/utils.d.ts +23 -0
  29. package/lib/api/impl/utils.d.ts.map +1 -1
  30. package/lib/api/impl/utils.js +44 -1
  31. package/lib/api/impl/utils.js.map +1 -1
  32. package/lib/api/impl/validator/index.d.ts +0 -14
  33. package/lib/api/impl/validator/index.d.ts.map +1 -1
  34. package/lib/api/impl/validator/index.js +192 -143
  35. package/lib/api/impl/validator/index.js.map +1 -1
  36. package/lib/api/rest/base.d.ts.map +1 -1
  37. package/lib/api/rest/base.js +14 -3
  38. package/lib/api/rest/base.js.map +1 -1
  39. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  40. package/lib/chain/blocks/importBlock.js +3 -21
  41. package/lib/chain/blocks/importBlock.js.map +1 -1
  42. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  43. package/lib/chain/blocks/utils/chainSegment.js +13 -5
  44. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  45. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +1 -1
  46. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
  47. package/lib/chain/blocks/verifyBlocksSanityChecks.js +1 -1
  48. package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
  49. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  50. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  51. package/lib/chain/builderCircuitBreaker.d.ts +31 -0
  52. package/lib/chain/builderCircuitBreaker.d.ts.map +1 -0
  53. package/lib/chain/builderCircuitBreaker.js +59 -0
  54. package/lib/chain/builderCircuitBreaker.js.map +1 -0
  55. package/lib/chain/chain.d.ts +2 -0
  56. package/lib/chain/chain.d.ts.map +1 -1
  57. package/lib/chain/chain.js +26 -2
  58. package/lib/chain/chain.js.map +1 -1
  59. package/lib/chain/errors/blockError.d.ts +48 -6
  60. package/lib/chain/errors/blockError.d.ts.map +1 -1
  61. package/lib/chain/errors/blockError.js +25 -4
  62. package/lib/chain/errors/blockError.js.map +1 -1
  63. package/lib/chain/errors/executionPayloadEnvelope.d.ts +11 -0
  64. package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
  65. package/lib/chain/errors/executionPayloadEnvelope.js +2 -0
  66. package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
  67. package/lib/chain/errors/proposerPreferences.d.ts +14 -1
  68. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
  69. package/lib/chain/errors/proposerPreferences.js +2 -0
  70. package/lib/chain/errors/proposerPreferences.js.map +1 -1
  71. package/lib/chain/interface.d.ts +2 -0
  72. package/lib/chain/interface.d.ts.map +1 -1
  73. package/lib/chain/interface.js.map +1 -1
  74. package/lib/chain/lightClient/index.d.ts.map +1 -1
  75. package/lib/chain/lightClient/index.js +11 -6
  76. package/lib/chain/lightClient/index.js.map +1 -1
  77. package/lib/chain/lightClient/proofs.d.ts.map +1 -1
  78. package/lib/chain/lightClient/proofs.js +28 -2
  79. package/lib/chain/lightClient/proofs.js.map +1 -1
  80. package/lib/chain/lightClient/types.d.ts +3 -1
  81. package/lib/chain/lightClient/types.d.ts.map +1 -1
  82. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  83. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  84. package/lib/chain/opPools/proposerPreferencesPool.d.ts +1 -1
  85. package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
  86. package/lib/chain/opPools/proposerPreferencesPool.js +1 -1
  87. package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
  88. package/lib/chain/options.d.ts +4 -0
  89. package/lib/chain/options.d.ts.map +1 -1
  90. package/lib/chain/options.js.map +1 -1
  91. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  92. package/lib/chain/prepareNextSlot.js +12 -7
  93. package/lib/chain/prepareNextSlot.js.map +1 -1
  94. package/lib/chain/validation/aggregateAndProof.js +10 -6
  95. package/lib/chain/validation/aggregateAndProof.js.map +1 -1
  96. package/lib/chain/validation/attestation.d.ts.map +1 -1
  97. package/lib/chain/validation/attestation.js +10 -6
  98. package/lib/chain/validation/attestation.js.map +1 -1
  99. package/lib/chain/validation/block.d.ts.map +1 -1
  100. package/lib/chain/validation/block.js +53 -4
  101. package/lib/chain/validation/block.js.map +1 -1
  102. package/lib/chain/validation/executionPayloadBid.js +13 -12
  103. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  104. package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
  105. package/lib/chain/validation/executionPayloadEnvelope.js +29 -0
  106. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  107. package/lib/chain/validation/proposerPreferences.d.ts +9 -2
  108. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  109. package/lib/chain/validation/proposerPreferences.js +64 -11
  110. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  111. package/lib/chain/validation/signatureSets/aggregateAndProof.d.ts.map +1 -1
  112. package/lib/chain/validation/signatureSets/aggregateAndProof.js +2 -3
  113. package/lib/chain/validation/signatureSets/aggregateAndProof.js.map +1 -1
  114. package/lib/db/repositories/lightclientSyncCommitteeWitness.d.ts.map +1 -1
  115. package/lib/db/repositories/lightclientSyncCommitteeWitness.js +42 -7
  116. package/lib/db/repositories/lightclientSyncCommitteeWitness.js.map +1 -1
  117. package/lib/execution/builder/http.d.ts +18 -0
  118. package/lib/execution/builder/http.d.ts.map +1 -1
  119. package/lib/execution/builder/http.js +20 -11
  120. package/lib/execution/builder/http.js.map +1 -1
  121. package/lib/metrics/metrics/beacon.d.ts +6 -0
  122. package/lib/metrics/metrics/beacon.d.ts.map +1 -1
  123. package/lib/metrics/metrics/beacon.js +18 -0
  124. package/lib/metrics/metrics/beacon.js.map +1 -1
  125. package/lib/network/core/metrics.d.ts +65 -0
  126. package/lib/network/core/metrics.d.ts.map +1 -1
  127. package/lib/network/core/metrics.js +94 -0
  128. package/lib/network/core/metrics.js.map +1 -1
  129. package/lib/network/discv5/utils.d.ts.map +1 -1
  130. package/lib/network/discv5/utils.js +12 -3
  131. package/lib/network/discv5/utils.js.map +1 -1
  132. package/lib/network/gossip/encoding.d.ts +2 -2
  133. package/lib/network/gossip/encoding.d.ts.map +1 -1
  134. package/lib/network/gossip/encoding.js +22 -9
  135. package/lib/network/gossip/encoding.js.map +1 -1
  136. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  137. package/lib/network/gossip/gossipsub.js +4 -3
  138. package/lib/network/gossip/gossipsub.js.map +1 -1
  139. package/lib/network/gossip/topic.d.ts +80 -232
  140. package/lib/network/gossip/topic.d.ts.map +1 -1
  141. package/lib/network/gossip/topic.js +47 -4
  142. package/lib/network/gossip/topic.js.map +1 -1
  143. package/lib/network/interface.d.ts +4 -1
  144. package/lib/network/interface.d.ts.map +1 -1
  145. package/lib/network/network.d.ts +4 -1
  146. package/lib/network/network.d.ts.map +1 -1
  147. package/lib/network/network.js +19 -11
  148. package/lib/network/network.js.map +1 -1
  149. package/lib/network/peers/peerManager.d.ts.map +1 -1
  150. package/lib/network/peers/peerManager.js +10 -0
  151. package/lib/network/peers/peerManager.js.map +1 -1
  152. package/lib/network/peers/score/score.d.ts +3 -1
  153. package/lib/network/peers/score/score.d.ts.map +1 -1
  154. package/lib/network/peers/score/score.js +6 -1
  155. package/lib/network/peers/score/score.js.map +1 -1
  156. package/lib/network/peers/score/store.d.ts.map +1 -1
  157. package/lib/network/peers/score/store.js +6 -1
  158. package/lib/network/peers/score/store.js.map +1 -1
  159. package/lib/network/peers/utils/prioritizePeers.d.ts.map +1 -1
  160. package/lib/network/peers/utils/prioritizePeers.js +14 -0
  161. package/lib/network/peers/utils/prioritizePeers.js.map +1 -1
  162. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  163. package/lib/network/processor/gossipHandlers.js +16 -6
  164. package/lib/network/processor/gossipHandlers.js.map +1 -1
  165. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
  166. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +4 -2
  167. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
  168. package/lib/network/reqresp/handlers/index.d.ts.map +1 -1
  169. package/lib/network/reqresp/handlers/index.js +20 -11
  170. package/lib/network/reqresp/handlers/index.js.map +1 -1
  171. package/lib/network/util.d.ts +1 -0
  172. package/lib/network/util.d.ts.map +1 -1
  173. package/lib/network/util.js +4 -0
  174. package/lib/network/util.js.map +1 -1
  175. package/lib/sync/sync.d.ts.map +1 -1
  176. package/lib/sync/sync.js +7 -0
  177. package/lib/sync/sync.js.map +1 -1
  178. package/lib/sync/unknownBlock.d.ts.map +1 -1
  179. package/lib/sync/unknownBlock.js +7 -2
  180. package/lib/sync/unknownBlock.js.map +1 -1
  181. package/lib/util/execution.js +1 -1
  182. package/lib/util/execution.js.map +1 -1
  183. package/package.json +23 -21
  184. package/src/api/impl/beacon/blocks/index.ts +221 -63
  185. package/src/api/impl/beacon/index.ts +1 -1
  186. package/src/api/impl/beacon/pool/index.ts +5 -0
  187. package/src/api/impl/beacon/state/index.ts +3 -2
  188. package/src/api/impl/beacon/state/utils.ts +11 -0
  189. package/src/api/impl/debug/index.ts +3 -2
  190. package/src/api/impl/lodestar/index.ts +2 -2
  191. package/src/api/impl/proof/index.ts +2 -2
  192. package/src/api/impl/utils.ts +51 -1
  193. package/src/api/impl/validator/index.ts +224 -157
  194. package/src/api/rest/base.ts +17 -3
  195. package/src/chain/blocks/importBlock.ts +1 -23
  196. package/src/chain/blocks/utils/chainSegment.ts +16 -5
  197. package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +1 -1
  198. package/src/chain/blocks/verifyBlocksSanityChecks.ts +1 -1
  199. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -2
  200. package/src/chain/builderCircuitBreaker.ts +84 -0
  201. package/src/chain/chain.ts +38 -2
  202. package/src/chain/errors/blockError.ts +44 -7
  203. package/src/chain/errors/executionPayloadEnvelope.ts +9 -0
  204. package/src/chain/errors/proposerPreferences.ts +16 -1
  205. package/src/chain/interface.ts +2 -0
  206. package/src/chain/lightClient/index.ts +12 -7
  207. package/src/chain/lightClient/proofs.ts +36 -1
  208. package/src/chain/lightClient/types.ts +3 -1
  209. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  210. package/src/chain/opPools/proposerPreferencesPool.ts +1 -1
  211. package/src/chain/options.ts +4 -0
  212. package/src/chain/prepareNextSlot.ts +11 -7
  213. package/src/chain/validation/aggregateAndProof.ts +12 -6
  214. package/src/chain/validation/attestation.ts +12 -6
  215. package/src/chain/validation/block.ts +69 -4
  216. package/src/chain/validation/executionPayloadBid.ts +16 -11
  217. package/src/chain/validation/executionPayloadEnvelope.ts +37 -0
  218. package/src/chain/validation/proposerPreferences.ts +74 -11
  219. package/src/chain/validation/signatureSets/aggregateAndProof.ts +3 -3
  220. package/src/db/repositories/lightclientSyncCommitteeWitness.ts +57 -9
  221. package/src/execution/builder/http.ts +30 -17
  222. package/src/metrics/metrics/beacon.ts +19 -0
  223. package/src/network/core/metrics.ts +94 -0
  224. package/src/network/discv5/utils.ts +11 -3
  225. package/src/network/gossip/encoding.ts +20 -7
  226. package/src/network/gossip/gossipsub.ts +9 -3
  227. package/src/network/gossip/topic.ts +58 -4
  228. package/src/network/interface.ts +3 -1
  229. package/src/network/network.ts +24 -15
  230. package/src/network/peers/peerManager.ts +11 -0
  231. package/src/network/peers/score/score.ts +8 -1
  232. package/src/network/peers/score/store.ts +8 -1
  233. package/src/network/peers/utils/prioritizePeers.ts +15 -0
  234. package/src/network/processor/gossipHandlers.ts +17 -6
  235. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +4 -2
  236. package/src/network/reqresp/handlers/index.ts +21 -12
  237. package/src/network/util.ts +5 -0
  238. package/src/sync/sync.ts +10 -0
  239. package/src/sync/unknownBlock.ts +8 -2
  240. package/src/util/execution.ts +1 -1
@@ -3,7 +3,7 @@ import { ExecutionStatus, PayloadStatus } from "@lodestar/fork-choice";
3
3
  import { BUILDER_INDEX_SELF_BUILD, ForkName, ForkSeq, GENESIS_SLOT, SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT, SYNC_COMMITTEE_SUBNET_SIZE, isForkPostBellatrix, isForkPostDeneb, isForkPostElectra, isForkPostFulu, isForkPostGloas, } from "@lodestar/params";
4
4
  import { DataAvailabilityStatus, beaconBlockToBlinded, calculateCommitteeAssignments, computeEpochAtSlot, computeStartSlotAtEpoch, computeTimeAtSlot, getCurrentSlot, isStatePostAltair, isStatePostGloas, proposerShufflingDecisionRoot, } from "@lodestar/state-transition";
5
5
  import { ProducedBlockSource, getValidatorStatus, ssz, } from "@lodestar/types";
6
- import { TimeoutError, defer, formatWeiToEth, fromHex, prettyWeiToEth, resolveOrRacePromises, toHex, toRootHex, } from "@lodestar/utils";
6
+ import { GWEI_TO_WEI, TimeoutError, defer, formatWeiToEth, fromHex, prettyWeiToEth, resolveOrRacePromises, toHex, toRootHex, } from "@lodestar/utils";
7
7
  import { MAX_BUILDER_BOOST_FACTOR } from "@lodestar/validator";
8
8
  import { BlockInputSource } from "../../../chain/blocks/blockInput/types.js";
9
9
  import { AttestationError, AttestationErrorCode, GossipAction, ProposerPreferencesError, ProposerPreferencesErrorCode, SyncCommitteeError, SyncCommitteeErrorCode, } from "../../../chain/errors/index.js";
@@ -17,28 +17,14 @@ import { validateSyncCommitteeGossipContributionAndProof } from "../../../chain/
17
17
  import { ZERO_HASH } from "../../../constants/index.js";
18
18
  import { BuilderStatus, NoBidReceived } from "../../../execution/builder/http.js";
19
19
  import { validateGossipFnRetryUnknownRoot } from "../../../network/processor/gossipHandlers.js";
20
- import { SyncState } from "../../../sync/index.js";
21
20
  import { callInNextEventLoop } from "../../../util/eventLoop.js";
22
21
  import { isOptimisticBlock } from "../../../util/forkChoice.js";
23
22
  import { getBlockGraffiti, toGraffitiBytes } from "../../../util/graffiti.js";
24
23
  import { getLodestarClientVersion } from "../../../util/metadata.js";
25
24
  import { getStateResponseWithRegen } from "../beacon/state/utils.js";
26
25
  import { ApiError, IndexedError, NodeIsSyncing, OnlySupportedByDVT } from "../errors.js";
26
+ import { notWhileSyncing } from "../utils.js";
27
27
  import { computeSubnetForCommitteesAtSlot, getPubkeysForIndices, selectBlockProductionSource } from "./utils.js";
28
- /**
29
- * If the node is within this many epochs from the head, we declare it to be synced regardless of
30
- * the network sync state.
31
- *
32
- * This helps prevent attacks where nodes can convince us that we're syncing some non-existent
33
- * finalized head.
34
- *
35
- * TODO: Lighthouse uses 8 for the attack described above. However, 8 kills Lodestar since validators
36
- * can trigger regen to fast-forward head state 8 epochs to be immediately invalidated as sync sets
37
- * a new head. Then the checkpoint state cache grows unbounded with very different states (because
38
- * they are 8 epochs apart) and causes an OOM. Research a proper solution once regen and the state
39
- * caches are better.
40
- */
41
- export const SYNC_TOLERANCE_EPOCHS = 1;
42
28
  /**
43
29
  * Cutoff time to wait from start of the slot for execution and builder block production apis to resolve.
44
30
  * Post this time, race execution and builder to pick whatever resolves first.
@@ -47,7 +33,7 @@ export const SYNC_TOLERANCE_EPOCHS = 1;
47
33
  * A cutoff of 2 seconds gives enough time and if there are unexpected delays it ensures we publish
48
34
  * in time as proposals post 4 seconds into the slot will likely be orphaned due to proposer boost reorg.
49
35
  *
50
- * TODO GLOAS: re-evaluate cutoff timing
36
+ * TODO GLOAS: re-evaluate cutoff timing due to attestation deadline changes in gloas
51
37
  */
52
38
  const BLOCK_PRODUCTION_RACE_CUTOFF_MS = 2_000;
53
39
  /** Overall timeout for execution and block production apis */
@@ -222,31 +208,6 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
222
208
  }
223
209
  return null;
224
210
  }
225
- /**
226
- * Reject any request while the node is syncing
227
- */
228
- function notWhileSyncing() {
229
- // Consider node synced before or close to genesis
230
- if (chain.clock.currentSlot < SLOTS_PER_EPOCH) {
231
- return;
232
- }
233
- const syncState = sync.state;
234
- switch (syncState) {
235
- case SyncState.SyncingFinalized:
236
- case SyncState.SyncingHead: {
237
- const currentSlot = chain.clock.currentSlot;
238
- const headSlot = chain.forkChoice.getHead().slot;
239
- if (currentSlot - headSlot > SYNC_TOLERANCE_EPOCHS * SLOTS_PER_EPOCH) {
240
- throw new NodeIsSyncing(`headSlot ${headSlot} currentSlot ${currentSlot}`);
241
- }
242
- return;
243
- }
244
- case SyncState.Synced:
245
- return;
246
- case SyncState.Stalled:
247
- throw new NodeIsSyncing("waiting for peers");
248
- }
249
- }
250
211
  /**
251
212
  * Post merge, the CL and EL could be out of step in the sync, and could result in
252
213
  * Syncing status of the chain head. To be precise:
@@ -403,7 +364,16 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
403
364
  async function produceEngineOrBuilderBlock(slot, randaoReveal, graffiti,
404
365
  // TODO deneb: skip randao verification
405
366
  _skipRandaoVerification, builderBoostFactor, { feeRecipient, builderSelection, strictFeeRecipientCheck } = {}) {
406
- notWhileSyncing();
367
+ builderSelection = builderSelection ?? routes.validator.BuilderSelection.MaxProfit;
368
+ if (builderSelection === routes.validator.BuilderSelection.BuilderOnly) {
369
+ logger.warn("Builder selection builderonly is no longer supported, treating as builderalways");
370
+ builderSelection = routes.validator.BuilderSelection.BuilderAlways;
371
+ }
372
+ builderBoostFactor = builderBoostFactor ?? BigInt(100);
373
+ if (builderBoostFactor > MAX_BUILDER_BOOST_FACTOR) {
374
+ throw new ApiError(400, `Invalid builderBoostFactor=${builderBoostFactor} > MAX_BUILDER_BOOST_FACTOR`);
375
+ }
376
+ notWhileSyncing(chain, sync.state);
407
377
  await waitForSlot(slot); // Must never request for a future slot > currentSlot
408
378
  const parentBlock = chain.getProposerHead(slot);
409
379
  const { blockRoot: parentBlockRootHex, slot: parentSlot } = parentBlock;
@@ -411,29 +381,9 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
411
381
  notOnOutOfRangeData(parentBlockRoot);
412
382
  metrics?.blockProductionSlotDelta.set(slot - parentSlot);
413
383
  const fork = config.getForkName(slot);
414
- // set some sensible opts
415
- // builderSelection will be deprecated and will run in mode MaxProfit if builder is enabled
416
- // and the actual selection will be determined using builderBoostFactor passed by the validator
417
- builderSelection = builderSelection ?? routes.validator.BuilderSelection.MaxProfit;
418
- builderBoostFactor = builderBoostFactor ?? BigInt(100);
419
- if (builderBoostFactor > MAX_BUILDER_BOOST_FACTOR) {
420
- throw new ApiError(400, `Invalid builderBoostFactor=${builderBoostFactor} > MAX_BUILDER_BOOST_FACTOR`);
421
- }
422
384
  const isBuilderEnabled = ForkSeq[fork] >= ForkSeq.bellatrix &&
423
385
  chain.executionBuilder !== undefined &&
424
386
  builderSelection !== routes.validator.BuilderSelection.ExecutionOnly;
425
- // At any point either the builder or execution or both flows should be active.
426
- //
427
- // Ideally such a scenario should be prevented on startup, but proposerSettingsFile or keymanager
428
- // configurations could cause a validator pubkey to have builder disabled with builder selection builder only
429
- // (TODO: independently make sure such an options update is not successful for a validator pubkey)
430
- //
431
- // So if builder is disabled ignore builder selection of builder only if caused by user mistake
432
- // https://github.com/ChainSafe/lodestar/issues/6338
433
- const isEngineEnabled = !isBuilderEnabled || builderSelection !== routes.validator.BuilderSelection.BuilderOnly;
434
- if (!isEngineEnabled && !isBuilderEnabled) {
435
- throw Error(`Internal Error: Neither builder nor execution proposal flow activated isBuilderEnabled=${isBuilderEnabled} builderSelection=${builderSelection}`);
436
- }
437
387
  const graffitiBytes = toGraffitiBytes(getBlockGraffiti(graffiti, getLodestarClientVersion(opts), chain.executionEngine.clientVersion, {
438
388
  private: opts.private,
439
389
  graffitiAppend: chain.opts.graffitiAppend,
@@ -445,7 +395,6 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
445
395
  fork,
446
396
  builderSelection,
447
397
  isBuilderEnabled,
448
- isEngineEnabled,
449
398
  strictFeeRecipientCheck,
450
399
  // winston logger doesn't like bigint
451
400
  builderBoostFactor: `${builderBoostFactor}`,
@@ -466,25 +415,23 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
466
415
  parentBlock,
467
416
  })
468
417
  : Promise.reject(new Error("Builder disabled"));
469
- const enginePromise = isEngineEnabled
470
- ? produceEngineBlockContents(slot, randaoReveal, graffitiBytes, {
471
- feeRecipient,
472
- strictFeeRecipientCheck,
473
- commonBlockBodyPromise,
474
- parentBlock,
475
- }).then((engineBlock) => {
476
- // Once the engine returns a block, in the event of either:
477
- // - suspected builder censorship
478
- // - builder boost factor set to 0 or builder selection `executionalways`
479
- // we don't need to wait for builder block as engine block will always be selected
480
- if (engineBlock.shouldOverrideBuilder ||
481
- builderBoostFactor === BigInt(0) ||
482
- builderSelection === routes.validator.BuilderSelection.ExecutionAlways) {
483
- controller.abort();
484
- }
485
- return engineBlock;
486
- })
487
- : Promise.reject(new Error("Engine disabled"));
418
+ const enginePromise = produceEngineBlockContents(slot, randaoReveal, graffitiBytes, {
419
+ feeRecipient,
420
+ strictFeeRecipientCheck,
421
+ commonBlockBodyPromise,
422
+ parentBlock,
423
+ }).then((engineBlock) => {
424
+ // Once the engine returns a block, in the event of either:
425
+ // - suspected builder censorship
426
+ // - builder boost factor set to 0 or builder selection `executionalways`
427
+ // we don't need to wait for builder block as engine block will always be selected
428
+ if (engineBlock.shouldOverrideBuilder ||
429
+ builderBoostFactor === BigInt(0) ||
430
+ builderSelection === routes.validator.BuilderSelection.ExecutionAlways) {
431
+ controller.abort();
432
+ }
433
+ return engineBlock;
434
+ });
488
435
  // Calculate cutoff time based on start of the slot
489
436
  const cutoffMs = Math.max(0, BLOCK_PRODUCTION_RACE_CUTOFF_MS - chain.clock.msFromSlot(slot));
490
437
  logger.verbose("Block production race (builder vs execution) starting", {
@@ -523,25 +470,20 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
523
470
  if (builder.status === "pending" && engine.status === "pending") {
524
471
  throw Error("Builder and engine both failed to produce the block within timeout");
525
472
  }
526
- if (builder.status === "pending" && !isEngineEnabled) {
527
- throw Error("Builder failed to produce the block within timeout");
528
- }
529
473
  if (engine.status === "pending" && !isBuilderEnabled) {
530
474
  throw Error("Engine failed to produce the block within timeout");
531
475
  }
532
- if (isEngineEnabled) {
533
- if (engine.status === "rejected") {
534
- logger.warn("Engine failed to produce the block", {
535
- ...loggerContext,
536
- durationMs: engine.durationMs,
537
- }, engine.reason);
538
- }
539
- else if (engine.status === "pending") {
540
- logger.warn("Engine failed to produce the block within cutoff time", {
541
- ...loggerContext,
542
- cutoffMs,
543
- });
544
- }
476
+ if (engine.status === "rejected") {
477
+ logger.warn("Engine failed to produce the block", {
478
+ ...loggerContext,
479
+ durationMs: engine.durationMs,
480
+ }, engine.reason);
481
+ }
482
+ else if (engine.status === "pending") {
483
+ logger.warn("Engine failed to produce the block within cutoff time", {
484
+ ...loggerContext,
485
+ cutoffMs,
486
+ });
545
487
  }
546
488
  if (isBuilderEnabled) {
547
489
  if (builder.status === "rejected") {
@@ -566,11 +508,11 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
566
508
  }
567
509
  }
568
510
  if (builder.status === "rejected" && engine.status === "rejected") {
569
- throw Error(`${isBuilderEnabled && isEngineEnabled ? "Builder and engine both" : isBuilderEnabled ? "Builder" : "Engine"} failed to produce the block`);
511
+ throw Error(`${isBuilderEnabled ? "Builder and engine both" : "Engine"} failed to produce the block`);
570
512
  }
571
513
  // handle shouldOverrideBuilder separately
572
- if (engine.status === "fulfilled" && engine.value.shouldOverrideBuilder) {
573
- logger.info("Selected engine block: censorship suspected in builder blocks", {
514
+ if (engine.status === "fulfilled" && engine.value.shouldOverrideBuilder && isBuilderEnabled) {
515
+ logger.warn("Selected engine block: censorship suspected in builder blocks", {
574
516
  ...loggerContext,
575
517
  durationMs: engine.durationMs,
576
518
  shouldOverrideBuilder: engine.value.shouldOverrideBuilder,
@@ -583,11 +525,9 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
583
525
  return { ...engine.value, executionPayloadBlinded: false, executionPayloadSource: ProducedBlockSource.engine };
584
526
  }
585
527
  if (builder.status === "fulfilled" && engine.status !== "fulfilled") {
586
- const reason = isEngineEnabled === false
587
- ? BuilderBlockSelectionReason.EngineDisabled
588
- : engine.status === "pending"
589
- ? BuilderBlockSelectionReason.EnginePending
590
- : BuilderBlockSelectionReason.EngineError;
528
+ const reason = engine.status === "pending"
529
+ ? BuilderBlockSelectionReason.EnginePending
530
+ : BuilderBlockSelectionReason.EngineError;
591
531
  logger.info("Selected builder block: no engine block produced", {
592
532
  reason,
593
533
  ...loggerContext,
@@ -671,12 +611,21 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
671
611
  }
672
612
  return { data, meta };
673
613
  },
674
- async produceBlockV4({ slot, randaoReveal, graffiti, feeRecipient }) {
614
+ async produceBlockV4({ slot, randaoReveal, graffiti, feeRecipient, includePayload, builderSelection, builderBoostFactor, }) {
675
615
  const fork = config.getForkName(slot);
676
616
  if (!isForkPostGloas(fork)) {
677
617
  throw new ApiError(400, `produceBlockV4 not supported for pre-gloas fork=${fork}`);
678
618
  }
679
- notWhileSyncing();
619
+ builderSelection = builderSelection ?? routes.validator.BuilderSelection.MaxProfit;
620
+ if (builderSelection === routes.validator.BuilderSelection.BuilderOnly) {
621
+ logger.warn("Builder selection builderonly is no longer supported, treating as builderalways");
622
+ builderSelection = routes.validator.BuilderSelection.BuilderAlways;
623
+ }
624
+ builderBoostFactor = builderBoostFactor ?? BigInt(100);
625
+ if (builderBoostFactor > MAX_BUILDER_BOOST_FACTOR) {
626
+ throw new ApiError(400, `Invalid builderBoostFactor=${builderBoostFactor} > MAX_BUILDER_BOOST_FACTOR`);
627
+ }
628
+ notWhileSyncing(chain, sync.state);
680
629
  await waitForSlot(slot);
681
630
  const parentBlock = chain.getProposerHead(slot);
682
631
  const { blockRoot: parentBlockRootHex, slot: parentSlot } = parentBlock;
@@ -687,18 +636,25 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
687
636
  private: opts.private,
688
637
  graffitiAppend: chain.opts.graffitiAppend,
689
638
  }));
690
- // TODO GLOAS: respect builderSelection (MaxProfit, BuilderAlways, ExecutionAlways, etc.) to let
691
- // the user control bid source preferences and value comparison. Also add external builder api
692
- // support when it is implemented.
639
+ // TODO GLOAS: add external builder api support when it is implemented
693
640
  const isBuildingOnFull = chain.forkChoice.shouldBuildOnFull(parentBlock, slot);
694
641
  const bidParentBlockHash = isBuildingOnFull ? parentBlock.executionPayloadBlockHash : parentBlock.parentBlockHash;
695
- const builderBid = chain.executionPayloadBidPool.getBestBid(slot, bidParentBlockHash, parentBlockRootHex);
642
+ // Bids are only skipped entirely with executiononly or while the circuit breaker is active,
643
+ // other engine-preferring selections still build a block with the best bid as fallback in
644
+ // case local production fails
645
+ const circuitBreakerActive = chain.builderCircuitBreaker.isActive(slot);
646
+ const builderBid = builderSelection === routes.validator.BuilderSelection.ExecutionOnly || circuitBreakerActive
647
+ ? null
648
+ : chain.executionPayloadBidPool.getBestBid(slot, bidParentBlockHash, parentBlockRootHex);
696
649
  const logCtx = {
697
650
  slot,
698
651
  parentSlot,
699
652
  parentBlockRoot: parentBlockRootHex,
700
653
  parentBlockHash: parentBlock.executionPayloadBlockHash,
701
654
  fork,
655
+ builderSelection,
656
+ builderBoostFactor,
657
+ circuitBreakerActive,
702
658
  ...(builderBid !== null
703
659
  ? {
704
660
  bidValue: builderBid.message.value,
@@ -729,32 +685,91 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
729
685
  const t = metrics?.blockProductionTime.startTimer();
730
686
  return fn().finally(() => t?.({ source }));
731
687
  };
732
- // Always build local block. If builder bid available, also build with it in parallel and prefer it.
733
- const [engineResult, bidResult] = await Promise.allSettled([
734
- timed(ProducedBlockSource.engine, () => chain.produceBlock(baseAttrs)),
735
- builderBid !== null
736
- ? timed(ProducedBlockSource.builder, () => chain.produceBlock({ ...baseAttrs, builderBid }))
737
- : Promise.reject(),
738
- ]);
688
+ // Calculate cutoff time based on start of the slot, ensures a slow local payload build does
689
+ // not delay the proposal when a builder bid block is available (and vice versa)
690
+ const cutoffMs = Math.max(0, BLOCK_PRODUCTION_RACE_CUTOFF_MS - chain.clock.msFromSlot(slot));
691
+ // use abort controller to stop waiting for the bid block if the engine block will be selected
692
+ const controller = new AbortController();
693
+ const enginePromise = timed(ProducedBlockSource.engine, () => chain.produceBlock(baseAttrs)).then((engineBlock) => {
694
+ // No need to wait for the bid block if the engine block will always be selected due to
695
+ // suspected builder censorship, a builder boost factor of 0 or executionalways selection
696
+ if (engineBlock.shouldOverrideBuilder ||
697
+ builderBoostFactor === BigInt(0) ||
698
+ builderSelection === routes.validator.BuilderSelection.ExecutionAlways) {
699
+ controller.abort();
700
+ }
701
+ return engineBlock;
702
+ });
703
+ const bidPromise = builderBid !== null
704
+ ? timed(ProducedBlockSource.builder, () => chain.produceBlock({ ...baseAttrs, builderBid }))
705
+ : Promise.reject(new Error("No builder bid available"));
706
+ const [engineResult, bidResult] = await resolveOrRacePromises([enginePromise, bidPromise], {
707
+ resolveTimeoutMs: cutoffMs,
708
+ raceTimeoutMs: BLOCK_PRODUCTION_RACE_TIMEOUT_MS,
709
+ signal: controller.signal,
710
+ });
739
711
  let bestResult = null;
740
712
  let source = ProducedBlockSource.engine;
741
- if (builderBid !== null && bidResult.status === "fulfilled") {
713
+ // handle shouldOverrideBuilder separately
714
+ if (engineResult.status === "fulfilled" && engineResult.value.shouldOverrideBuilder && builderBid !== null) {
715
+ source = ProducedBlockSource.engine;
716
+ bestResult = engineResult;
717
+ metrics?.blockProductionSelectionResults.inc({
718
+ source: ProducedBlockSource.engine,
719
+ reason: EngineBlockSelectionReason.BuilderCensorship,
720
+ });
721
+ logger.warn("Selected local block: censorship suspected in builder bid", logCtx);
722
+ }
723
+ else if (engineResult.status === "fulfilled" && bidResult.status === "fulfilled") {
724
+ const result = selectBlockProductionSource({
725
+ builderSelection,
726
+ builderBoostFactor,
727
+ engineExecutionPayloadValue: engineResult.value.executionPayloadValue,
728
+ // The bid value is the payment to the proposer, in Gwei
729
+ builderExecutionPayloadValue: BigInt(builderBid?.message.value ?? 0) * GWEI_TO_WEI,
730
+ });
731
+ source = result.source;
732
+ metrics?.blockProductionSelectionResults.inc(result);
733
+ logger.info(`Selected ${source} block`, { reason: result.reason, ...logCtx });
734
+ bestResult = source === ProducedBlockSource.builder ? bidResult : engineResult;
735
+ }
736
+ else if (bidResult.status === "fulfilled") {
742
737
  source = ProducedBlockSource.builder;
743
738
  bestResult = bidResult;
744
- logger.info("Selected builder bid block", logCtx);
739
+ const reason = engineResult.status === "pending"
740
+ ? BuilderBlockSelectionReason.EnginePending
741
+ : BuilderBlockSelectionReason.EngineError;
742
+ metrics?.blockProductionSelectionResults.inc({ source: ProducedBlockSource.builder, reason });
743
+ logger.info("Selected builder bid block: no local block produced", {
744
+ reason,
745
+ ...logCtx,
746
+ error: engineResult.status === "rejected" ? engineResult.reason.message : undefined,
747
+ });
745
748
  }
746
749
  else if (engineResult.status === "fulfilled") {
747
750
  source = ProducedBlockSource.engine;
748
751
  bestResult = engineResult;
749
- if (builderBid !== null) {
750
- logger.warn("Builder bid block production failed, using local block", logCtx);
751
- }
752
+ const reason = builderBid === null
753
+ ? EngineBlockSelectionReason.BuilderNoBid
754
+ : bidResult.status === "pending"
755
+ ? EngineBlockSelectionReason.BuilderPending
756
+ : EngineBlockSelectionReason.BuilderError;
757
+ metrics?.blockProductionSelectionResults.inc({ source: ProducedBlockSource.engine, reason });
758
+ logger.info("Selected local block: no builder bid block produced", {
759
+ reason,
760
+ ...logCtx,
761
+ error: bidResult.status === "rejected" ? bidResult.reason.message : undefined,
762
+ });
752
763
  }
753
764
  if (bestResult === null || bestResult.status !== "fulfilled") {
754
- const engineReason = engineResult.status === "rejected" ? engineResult.reason : undefined;
755
- const bidReason = builderBid !== null && bidResult.status === "rejected" ? bidResult.reason : undefined;
756
- logger.error("Block production failed", { ...logCtx, engineReason, bidReason });
757
- throw Error(`Block production failed: engine=${engineReason ?? "n/a"} builder=${bidReason ?? "n/a"}`);
765
+ const engineReason = engineResult.status === "rejected" ? engineResult.reason : engineResult.status;
766
+ const bidReason = bidResult.status === "rejected" ? bidResult.reason : bidResult.status;
767
+ logger.error("Block production failed", {
768
+ ...logCtx,
769
+ engineReason: String(engineReason),
770
+ bidReason: String(bidReason),
771
+ });
772
+ throw Error(`Block production failed: engine=${String(engineReason)} builder=${String(bidReason)}`);
758
773
  }
759
774
  const { block, executionPayloadValue, consensusBlockValue } = bestResult.value;
760
775
  metrics?.blockProductionSuccess.inc({ source });
@@ -771,13 +786,40 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
771
786
  if (chain.opts.persistProducedBlocks) {
772
787
  void chain.persistBlock(block, "produced_engine_block");
773
788
  }
789
+ // Include the payload for self-builds unless disabled (stateless flow)
790
+ const isSelfBuild = source === ProducedBlockSource.engine;
791
+ if (isSelfBuild && includePayload) {
792
+ const produceResult = chain.blockProductionCache.get(blockRoot);
793
+ if (produceResult === undefined ||
794
+ !isForkPostGloas(produceResult.fork) ||
795
+ produceResult.type !== BlockType.Full) {
796
+ throw Error(`Missing cached block production result for produced block slot=${slot} blockRoot=${blockRoot}`);
797
+ }
798
+ const { executionPayload, executionRequests, blobsBundle, parentBlockRoot } = produceResult;
799
+ const blockContents = {
800
+ block: block,
801
+ executionPayloadEnvelope: {
802
+ payload: executionPayload,
803
+ executionRequests,
804
+ builderIndex: BUILDER_INDEX_SELF_BUILD,
805
+ beaconBlockRoot: fromHex(blockRoot),
806
+ parentBeaconBlockRoot: parentBlockRoot,
807
+ },
808
+ kzgProofs: blobsBundle.proofs,
809
+ blobs: blobsBundle.blobs,
810
+ };
811
+ return {
812
+ data: blockContents,
813
+ meta: { version: fork, consensusBlockValue, executionPayloadValue, executionPayloadIncluded: true },
814
+ };
815
+ }
774
816
  return {
775
817
  data: block,
776
- meta: { version: fork, consensusBlockValue },
818
+ meta: { version: fork, consensusBlockValue, executionPayloadValue, executionPayloadIncluded: false },
777
819
  };
778
820
  },
779
821
  async produceAttestationData({ committeeIndex, slot }) {
780
- notWhileSyncing();
822
+ notWhileSyncing(chain, sync.state);
781
823
  await waitForSlot(slot); // Must never request for a future slot > currentSlot
782
824
  // This needs a state in the same epoch as `slot` such that state.currentJustifiedCheckpoint is correct.
783
825
  // Note: This may trigger an epoch transition if there skipped slots at the beginning of the epoch.
@@ -848,7 +890,7 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
848
890
  if (!isForkPostGloas(fork)) {
849
891
  throw new ApiError(400, `producePayloadAttestationData is not supported before Gloas fork=${fork}`);
850
892
  }
851
- notWhileSyncing();
893
+ notWhileSyncing(chain, sync.state);
852
894
  await waitForSlot(slot);
853
895
  const block = chain.forkChoice.getCanonicalBlockAtSlot(slot);
854
896
  if (!block) {
@@ -917,7 +959,7 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
917
959
  return { data: contribution };
918
960
  },
919
961
  async getProposerDuties({ epoch }, _context, opts) {
920
- notWhileSyncing();
962
+ notWhileSyncing(chain, sync.state);
921
963
  const currentEpoch = currentEpochWithDisparity();
922
964
  const nextEpoch = currentEpoch + 1;
923
965
  const startSlot = computeStartSlotAtEpoch(epoch);
@@ -959,7 +1001,7 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
959
1001
  state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.getDuties);
960
1002
  }
961
1003
  else {
962
- const res = await getStateResponseWithRegen(chain, startSlot);
1004
+ const res = await getStateResponseWithRegen(chain, sync, startSlot);
963
1005
  state = res.state instanceof Uint8Array ? chain.getHeadState().loadOtherState(res.state) : res.state;
964
1006
  if (state.epoch !== epoch) {
965
1007
  throw Error(`Loaded state epoch ${state.epoch} does not match requested epoch ${epoch}`);
@@ -1035,7 +1077,7 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
1035
1077
  return this.getProposerDuties(args, context, { v2: true });
1036
1078
  },
1037
1079
  async getAttesterDuties({ epoch, indices }) {
1038
- notWhileSyncing();
1080
+ notWhileSyncing(chain, sync.state);
1039
1081
  if (indices.length === 0) {
1040
1082
  throw new ApiError(400, "No validator to get attester duties");
1041
1083
  }
@@ -1083,7 +1125,7 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
1083
1125
  };
1084
1126
  },
1085
1127
  async getPtcDuties({ epoch, indices }) {
1086
- notWhileSyncing();
1128
+ notWhileSyncing(chain, sync.state);
1087
1129
  if (indices.length === 0) {
1088
1130
  throw new ApiError(400, "No validator to get PTC duties");
1089
1131
  }
@@ -1136,7 +1178,7 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
1136
1178
  * @param validatorIndices an array of the validator indices for which to obtain the duties.
1137
1179
  */
1138
1180
  async getSyncCommitteeDuties({ epoch, indices }) {
1139
- notWhileSyncing();
1181
+ notWhileSyncing(chain, sync.state);
1140
1182
  if (indices.length === 0) {
1141
1183
  throw new ApiError(400, "No validator to get attester duties");
1142
1184
  }
@@ -1176,7 +1218,7 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
1176
1218
  };
1177
1219
  },
1178
1220
  async getAggregatedAttestationV2({ attestationDataRoot, slot, committeeIndex }) {
1179
- notWhileSyncing();
1221
+ notWhileSyncing(chain, sync.state);
1180
1222
  await waitForSlot(slot); // Must never request for a future slot > currentSlot
1181
1223
  const dataRootHex = toRootHex(attestationDataRoot);
1182
1224
  const aggregate = chain.attestationPool.getAggregate(slot, dataRootHex, committeeIndex);
@@ -1190,7 +1232,7 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
1190
1232
  };
1191
1233
  },
1192
1234
  async publishAggregateAndProofsV2({ signedAggregateAndProofs }) {
1193
- notWhileSyncing();
1235
+ notWhileSyncing(chain, sync.state);
1194
1236
  const seenTimestampSec = Date.now() / 1000;
1195
1237
  const failures = [];
1196
1238
  const fork = chain.config.getForkName(chain.clock.currentSlot);
@@ -1236,7 +1278,7 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
1236
1278
  * https://github.com/ethereum/beacon-APIs/pull/137
1237
1279
  */
1238
1280
  async publishContributionAndProofs({ contributionAndProofs }) {
1239
- notWhileSyncing();
1281
+ notWhileSyncing(chain, sync.state);
1240
1282
  const failures = [];
1241
1283
  await Promise.all(contributionAndProofs.map(async (contributionAndProof, i) => {
1242
1284
  try {
@@ -1269,7 +1311,7 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
1269
1311
  }
1270
1312
  },
1271
1313
  async prepareBeaconCommitteeSubnet({ subscriptions }) {
1272
- notWhileSyncing();
1314
+ notWhileSyncing(chain, sync.state);
1273
1315
  await network.prepareBeaconCommitteeSubnets(subscriptions.map(({ validatorIndex, slot, isAggregator, committeesAtSlot, committeeIndex }) => ({
1274
1316
  validatorIndex: validatorIndex,
1275
1317
  subnet: computeSubnetForCommitteesAtSlot(slot, committeesAtSlot, committeeIndex),
@@ -1296,7 +1338,7 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
1296
1338
  * https://github.com/ethereum/beacon-APIs/pull/136
1297
1339
  */
1298
1340
  async prepareSyncCommitteeSubnets({ subscriptions }) {
1299
- notWhileSyncing();
1341
+ notWhileSyncing(chain, sync.state);
1300
1342
  // A `validatorIndex` can be in multiple subnets, so compute the CommitteeSubscription with double for loop
1301
1343
  const subs = [];
1302
1344
  for (const sub of subscriptions) {
@@ -1409,7 +1451,7 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
1409
1451
  if (!isForkPostGloas(fork)) {
1410
1452
  throw new ApiError(400, `getExecutionPayloadEnvelope not supported for pre-gloas fork=${fork}`);
1411
1453
  }
1412
- notWhileSyncing();
1454
+ notWhileSyncing(chain, sync.state);
1413
1455
  await waitForSlot(slot);
1414
1456
  const blockRootHex = toRootHex(beaconBlockRoot);
1415
1457
  const produceResult = chain.blockProductionCache.get(blockRootHex);
@@ -1423,6 +1465,13 @@ export function getValidatorApi(opts, { chain, config, logger, metrics, network,
1423
1465
  throw Error("Cached block production result is not full block");
1424
1466
  }
1425
1467
  const { executionPayload, executionRequests, parentBlockRoot } = produceResult;
1468
+ if (executionPayload === undefined) {
1469
+ // Blocks committing to a builder bid are cached as full but without payload data
1470
+ throw new ApiError(404, `No local execution payload cached for block root ${blockRootHex}`);
1471
+ }
1472
+ if (executionPayload.slotNumber !== slot) {
1473
+ throw new ApiError(404, `Cached execution payload is for slot=${executionPayload.slotNumber}, requested slot=${slot}`);
1474
+ }
1426
1475
  const envelope = {
1427
1476
  payload: executionPayload,
1428
1477
  executionRequests: executionRequests,