@lodestar/beacon-node 1.45.0-dev.51a1c44b27 → 1.45.0-dev.6568180f96

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 (282) 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 +6 -43
  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 +48 -5
  13. package/lib/api/impl/beacon/state/index.js.map +1 -1
  14. package/lib/api/impl/beacon/state/utils.d.ts +13 -3
  15. package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
  16. package/lib/api/impl/beacon/state/utils.js +48 -4
  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 +238 -149
  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 +4 -22
  41. package/lib/chain/blocks/importBlock.js.map +1 -1
  42. package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts +5 -2
  43. package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts.map +1 -1
  44. package/lib/chain/blocks/payloadEnvelopeProcessor.js +17 -17
  45. package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
  46. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  47. package/lib/chain/blocks/utils/chainSegment.js +13 -5
  48. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  49. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +1 -1
  50. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
  51. package/lib/chain/blocks/verifyBlocksSanityChecks.js +1 -1
  52. package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
  53. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  54. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  55. package/lib/chain/builderCircuitBreaker.d.ts +31 -0
  56. package/lib/chain/builderCircuitBreaker.d.ts.map +1 -0
  57. package/lib/chain/builderCircuitBreaker.js +59 -0
  58. package/lib/chain/builderCircuitBreaker.js.map +1 -0
  59. package/lib/chain/chain.d.ts +3 -2
  60. package/lib/chain/chain.d.ts.map +1 -1
  61. package/lib/chain/chain.js +27 -5
  62. package/lib/chain/chain.js.map +1 -1
  63. package/lib/chain/errors/blockError.d.ts +48 -6
  64. package/lib/chain/errors/blockError.d.ts.map +1 -1
  65. package/lib/chain/errors/blockError.js +25 -4
  66. package/lib/chain/errors/blockError.js.map +1 -1
  67. package/lib/chain/errors/executionPayloadEnvelope.d.ts +11 -0
  68. package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
  69. package/lib/chain/errors/executionPayloadEnvelope.js +2 -0
  70. package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
  71. package/lib/chain/errors/proposerPreferences.d.ts +14 -1
  72. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
  73. package/lib/chain/errors/proposerPreferences.js +2 -0
  74. package/lib/chain/errors/proposerPreferences.js.map +1 -1
  75. package/lib/chain/interface.d.ts +3 -2
  76. package/lib/chain/interface.d.ts.map +1 -1
  77. package/lib/chain/interface.js.map +1 -1
  78. package/lib/chain/lightClient/index.d.ts.map +1 -1
  79. package/lib/chain/lightClient/index.js +11 -6
  80. package/lib/chain/lightClient/index.js.map +1 -1
  81. package/lib/chain/lightClient/proofs.d.ts.map +1 -1
  82. package/lib/chain/lightClient/proofs.js +28 -2
  83. package/lib/chain/lightClient/proofs.js.map +1 -1
  84. package/lib/chain/lightClient/types.d.ts +3 -1
  85. package/lib/chain/lightClient/types.d.ts.map +1 -1
  86. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  87. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  88. package/lib/chain/opPools/proposerPreferencesPool.d.ts +3 -2
  89. package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
  90. package/lib/chain/opPools/proposerPreferencesPool.js +4 -1
  91. package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
  92. package/lib/chain/options.d.ts +5 -0
  93. package/lib/chain/options.d.ts.map +1 -1
  94. package/lib/chain/options.js +1 -0
  95. package/lib/chain/options.js.map +1 -1
  96. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  97. package/lib/chain/prepareNextSlot.js +12 -7
  98. package/lib/chain/prepareNextSlot.js.map +1 -1
  99. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  100. package/lib/chain/produceBlock/produceBlockBody.js +4 -12
  101. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  102. package/lib/chain/seenCache/index.d.ts +0 -1
  103. package/lib/chain/seenCache/index.d.ts.map +1 -1
  104. package/lib/chain/seenCache/index.js +0 -1
  105. package/lib/chain/seenCache/index.js.map +1 -1
  106. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  107. package/lib/chain/stateCache/persistentCheckpointsCache.js +3 -2
  108. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  109. package/lib/chain/validation/aggregateAndProof.js +10 -6
  110. package/lib/chain/validation/aggregateAndProof.js.map +1 -1
  111. package/lib/chain/validation/attestation.d.ts.map +1 -1
  112. package/lib/chain/validation/attestation.js +10 -6
  113. package/lib/chain/validation/attestation.js.map +1 -1
  114. package/lib/chain/validation/block.d.ts.map +1 -1
  115. package/lib/chain/validation/block.js +53 -4
  116. package/lib/chain/validation/block.js.map +1 -1
  117. package/lib/chain/validation/executionPayloadBid.js +13 -12
  118. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  119. package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
  120. package/lib/chain/validation/executionPayloadEnvelope.js +29 -0
  121. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  122. package/lib/chain/validation/proposerPreferences.d.ts +9 -2
  123. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  124. package/lib/chain/validation/proposerPreferences.js +75 -14
  125. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  126. package/lib/chain/validation/signatureSets/aggregateAndProof.d.ts.map +1 -1
  127. package/lib/chain/validation/signatureSets/aggregateAndProof.js +2 -3
  128. package/lib/chain/validation/signatureSets/aggregateAndProof.js.map +1 -1
  129. package/lib/db/repositories/lightclientSyncCommitteeWitness.d.ts.map +1 -1
  130. package/lib/db/repositories/lightclientSyncCommitteeWitness.js +42 -7
  131. package/lib/db/repositories/lightclientSyncCommitteeWitness.js.map +1 -1
  132. package/lib/execution/builder/http.d.ts +18 -0
  133. package/lib/execution/builder/http.d.ts.map +1 -1
  134. package/lib/execution/builder/http.js +20 -11
  135. package/lib/execution/builder/http.js.map +1 -1
  136. package/lib/metrics/metrics/beacon.d.ts +6 -0
  137. package/lib/metrics/metrics/beacon.d.ts.map +1 -1
  138. package/lib/metrics/metrics/beacon.js +18 -0
  139. package/lib/metrics/metrics/beacon.js.map +1 -1
  140. package/lib/network/core/metrics.d.ts +65 -0
  141. package/lib/network/core/metrics.d.ts.map +1 -1
  142. package/lib/network/core/metrics.js +94 -0
  143. package/lib/network/core/metrics.js.map +1 -1
  144. package/lib/network/discv5/utils.d.ts.map +1 -1
  145. package/lib/network/discv5/utils.js +12 -3
  146. package/lib/network/discv5/utils.js.map +1 -1
  147. package/lib/network/gossip/encoding.d.ts +2 -2
  148. package/lib/network/gossip/encoding.d.ts.map +1 -1
  149. package/lib/network/gossip/encoding.js +22 -9
  150. package/lib/network/gossip/encoding.js.map +1 -1
  151. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  152. package/lib/network/gossip/gossipsub.js +4 -3
  153. package/lib/network/gossip/gossipsub.js.map +1 -1
  154. package/lib/network/gossip/topic.d.ts +831 -226
  155. package/lib/network/gossip/topic.d.ts.map +1 -1
  156. package/lib/network/gossip/topic.js +47 -4
  157. package/lib/network/gossip/topic.js.map +1 -1
  158. package/lib/network/interface.d.ts +4 -1
  159. package/lib/network/interface.d.ts.map +1 -1
  160. package/lib/network/network.d.ts +4 -1
  161. package/lib/network/network.d.ts.map +1 -1
  162. package/lib/network/network.js +19 -11
  163. package/lib/network/network.js.map +1 -1
  164. package/lib/network/peers/peerManager.d.ts.map +1 -1
  165. package/lib/network/peers/peerManager.js +10 -0
  166. package/lib/network/peers/peerManager.js.map +1 -1
  167. package/lib/network/peers/score/score.d.ts +3 -1
  168. package/lib/network/peers/score/score.d.ts.map +1 -1
  169. package/lib/network/peers/score/score.js +6 -1
  170. package/lib/network/peers/score/score.js.map +1 -1
  171. package/lib/network/peers/score/store.d.ts.map +1 -1
  172. package/lib/network/peers/score/store.js +6 -1
  173. package/lib/network/peers/score/store.js.map +1 -1
  174. package/lib/network/peers/utils/prioritizePeers.d.ts.map +1 -1
  175. package/lib/network/peers/utils/prioritizePeers.js +14 -0
  176. package/lib/network/peers/utils/prioritizePeers.js.map +1 -1
  177. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  178. package/lib/network/processor/gossipHandlers.js +18 -9
  179. package/lib/network/processor/gossipHandlers.js.map +1 -1
  180. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
  181. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +4 -2
  182. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
  183. package/lib/network/reqresp/handlers/index.d.ts.map +1 -1
  184. package/lib/network/reqresp/handlers/index.js +20 -11
  185. package/lib/network/reqresp/handlers/index.js.map +1 -1
  186. package/lib/network/util.d.ts +1 -0
  187. package/lib/network/util.d.ts.map +1 -1
  188. package/lib/network/util.js +4 -0
  189. package/lib/network/util.js.map +1 -1
  190. package/lib/sync/range/chain.d.ts +4 -1
  191. package/lib/sync/range/chain.d.ts.map +1 -1
  192. package/lib/sync/range/chain.js +3 -3
  193. package/lib/sync/range/chain.js.map +1 -1
  194. package/lib/sync/range/range.d.ts.map +1 -1
  195. package/lib/sync/range/range.js +10 -0
  196. package/lib/sync/range/range.js.map +1 -1
  197. package/lib/sync/sync.d.ts.map +1 -1
  198. package/lib/sync/sync.js +7 -0
  199. package/lib/sync/sync.js.map +1 -1
  200. package/lib/sync/unknownBlock.d.ts.map +1 -1
  201. package/lib/sync/unknownBlock.js +7 -2
  202. package/lib/sync/unknownBlock.js.map +1 -1
  203. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  204. package/lib/sync/utils/downloadByRange.js +6 -0
  205. package/lib/sync/utils/downloadByRange.js.map +1 -1
  206. package/lib/util/execution.js +1 -1
  207. package/lib/util/execution.js.map +1 -1
  208. package/lib/util/graffiti.d.ts +15 -0
  209. package/lib/util/graffiti.d.ts.map +1 -1
  210. package/lib/util/graffiti.js +55 -0
  211. package/lib/util/graffiti.js.map +1 -1
  212. package/package.json +23 -21
  213. package/src/api/impl/beacon/blocks/index.ts +221 -63
  214. package/src/api/impl/beacon/index.ts +1 -1
  215. package/src/api/impl/beacon/pool/index.ts +5 -53
  216. package/src/api/impl/beacon/state/index.ts +55 -3
  217. package/src/api/impl/beacon/state/utils.ts +68 -4
  218. package/src/api/impl/debug/index.ts +3 -2
  219. package/src/api/impl/lodestar/index.ts +2 -2
  220. package/src/api/impl/proof/index.ts +2 -2
  221. package/src/api/impl/utils.ts +51 -1
  222. package/src/api/impl/validator/index.ts +278 -162
  223. package/src/api/rest/base.ts +17 -3
  224. package/src/chain/blocks/importBlock.ts +2 -24
  225. package/src/chain/blocks/payloadEnvelopeProcessor.ts +17 -17
  226. package/src/chain/blocks/utils/chainSegment.ts +16 -5
  227. package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +1 -1
  228. package/src/chain/blocks/verifyBlocksSanityChecks.ts +1 -1
  229. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -2
  230. package/src/chain/builderCircuitBreaker.ts +84 -0
  231. package/src/chain/chain.ts +38 -5
  232. package/src/chain/errors/blockError.ts +44 -7
  233. package/src/chain/errors/executionPayloadEnvelope.ts +9 -0
  234. package/src/chain/errors/proposerPreferences.ts +16 -1
  235. package/src/chain/interface.ts +2 -2
  236. package/src/chain/lightClient/index.ts +12 -7
  237. package/src/chain/lightClient/proofs.ts +36 -1
  238. package/src/chain/lightClient/types.ts +3 -1
  239. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  240. package/src/chain/opPools/proposerPreferencesPool.ts +6 -2
  241. package/src/chain/options.ts +6 -0
  242. package/src/chain/prepareNextSlot.ts +11 -7
  243. package/src/chain/produceBlock/produceBlockBody.ts +9 -17
  244. package/src/chain/seenCache/index.ts +0 -1
  245. package/src/chain/stateCache/persistentCheckpointsCache.ts +3 -2
  246. package/src/chain/validation/aggregateAndProof.ts +12 -6
  247. package/src/chain/validation/attestation.ts +12 -6
  248. package/src/chain/validation/block.ts +69 -4
  249. package/src/chain/validation/executionPayloadBid.ts +16 -11
  250. package/src/chain/validation/executionPayloadEnvelope.ts +37 -0
  251. package/src/chain/validation/proposerPreferences.ts +86 -14
  252. package/src/chain/validation/signatureSets/aggregateAndProof.ts +3 -3
  253. package/src/db/repositories/lightclientSyncCommitteeWitness.ts +57 -9
  254. package/src/execution/builder/http.ts +30 -17
  255. package/src/metrics/metrics/beacon.ts +19 -0
  256. package/src/network/core/metrics.ts +94 -0
  257. package/src/network/discv5/utils.ts +11 -3
  258. package/src/network/gossip/encoding.ts +20 -7
  259. package/src/network/gossip/gossipsub.ts +9 -3
  260. package/src/network/gossip/topic.ts +58 -4
  261. package/src/network/interface.ts +3 -1
  262. package/src/network/network.ts +24 -15
  263. package/src/network/peers/peerManager.ts +11 -0
  264. package/src/network/peers/score/score.ts +8 -1
  265. package/src/network/peers/score/store.ts +8 -1
  266. package/src/network/peers/utils/prioritizePeers.ts +15 -0
  267. package/src/network/processor/gossipHandlers.ts +18 -8
  268. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +4 -2
  269. package/src/network/reqresp/handlers/index.ts +21 -12
  270. package/src/network/util.ts +5 -0
  271. package/src/sync/range/chain.ts +4 -5
  272. package/src/sync/range/range.ts +10 -0
  273. package/src/sync/sync.ts +10 -0
  274. package/src/sync/unknownBlock.ts +8 -2
  275. package/src/sync/utils/downloadByRange.ts +6 -0
  276. package/src/util/execution.ts +1 -1
  277. package/src/util/graffiti.ts +78 -0
  278. package/lib/chain/seenCache/seenProposerPreferences.d.ts +0 -16
  279. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +0 -1
  280. package/lib/chain/seenCache/seenProposerPreferences.js +0 -26
  281. package/lib/chain/seenCache/seenProposerPreferences.js.map +0 -1
  282. package/src/chain/seenCache/seenProposerPreferences.ts +0 -32
@@ -50,6 +50,7 @@ import {
50
50
  ssz,
51
51
  } from "@lodestar/types";
52
52
  import {
53
+ GWEI_TO_WEI,
53
54
  TimeoutError,
54
55
  defer,
55
56
  formatWeiToEth,
@@ -65,6 +66,8 @@ import {
65
66
  AttestationError,
66
67
  AttestationErrorCode,
67
68
  GossipAction,
69
+ ProposerPreferencesError,
70
+ ProposerPreferencesErrorCode,
68
71
  SyncCommitteeError,
69
72
  SyncCommitteeErrorCode,
70
73
  } from "../../../chain/errors/index.js";
@@ -74,37 +77,23 @@ import {BlockType, ProduceFullDeneb, ProduceFullGloas} from "../../../chain/prod
74
77
  import {RegenCaller} from "../../../chain/regen/index.js";
75
78
  import {CheckpointHex} from "../../../chain/stateCache/types.js";
76
79
  import {validateApiAggregateAndProof} from "../../../chain/validation/index.js";
80
+ import {validateGossipProposerPreferences} from "../../../chain/validation/proposerPreferences.js";
77
81
  import {validateSyncCommitteeGossipContributionAndProof} from "../../../chain/validation/syncCommitteeContributionAndProof.js";
78
82
  import {ZERO_HASH} from "../../../constants/index.js";
79
83
  import {BuilderStatus, NoBidReceived} from "../../../execution/builder/http.js";
80
84
  import {validateGossipFnRetryUnknownRoot} from "../../../network/processor/gossipHandlers.js";
81
85
  import {CommitteeSubscription} from "../../../network/subnets/index.js";
82
- import {SyncState} from "../../../sync/index.js";
83
86
  import {callInNextEventLoop} from "../../../util/eventLoop.js";
84
87
  import {isOptimisticBlock} from "../../../util/forkChoice.js";
85
- import {getDefaultGraffiti, toGraffitiBytes} from "../../../util/graffiti.js";
88
+ import {getBlockGraffiti, toGraffitiBytes} from "../../../util/graffiti.js";
86
89
  import {getLodestarClientVersion} from "../../../util/metadata.js";
87
90
  import {ApiOptions} from "../../options.js";
88
91
  import {getStateResponseWithRegen} from "../beacon/state/utils.js";
89
92
  import {ApiError, FailureList, IndexedError, NodeIsSyncing, OnlySupportedByDVT} from "../errors.js";
90
93
  import {ApiModules} from "../types.js";
94
+ import {notWhileSyncing} from "../utils.js";
91
95
  import {computeSubnetForCommitteesAtSlot, getPubkeysForIndices, selectBlockProductionSource} from "./utils.js";
92
96
 
93
- /**
94
- * If the node is within this many epochs from the head, we declare it to be synced regardless of
95
- * the network sync state.
96
- *
97
- * This helps prevent attacks where nodes can convince us that we're syncing some non-existent
98
- * finalized head.
99
- *
100
- * TODO: Lighthouse uses 8 for the attack described above. However, 8 kills Lodestar since validators
101
- * can trigger regen to fast-forward head state 8 epochs to be immediately invalidated as sync sets
102
- * a new head. Then the checkpoint state cache grows unbounded with very different states (because
103
- * they are 8 epochs apart) and causes an OOM. Research a proper solution once regen and the state
104
- * caches are better.
105
- */
106
- export const SYNC_TOLERANCE_EPOCHS = 1;
107
-
108
97
  /**
109
98
  * Cutoff time to wait from start of the slot for execution and builder block production apis to resolve.
110
99
  * Post this time, race execution and builder to pick whatever resolves first.
@@ -113,7 +102,7 @@ export const SYNC_TOLERANCE_EPOCHS = 1;
113
102
  * A cutoff of 2 seconds gives enough time and if there are unexpected delays it ensures we publish
114
103
  * in time as proposals post 4 seconds into the slot will likely be orphaned due to proposer boost reorg.
115
104
  *
116
- * TODO GLOAS: re-evaluate cutoff timing
105
+ * TODO GLOAS: re-evaluate cutoff timing due to attestation deadline changes in gloas
117
106
  */
118
107
  const BLOCK_PRODUCTION_RACE_CUTOFF_MS = 2_000;
119
108
  /** Overall timeout for execution and block production apis */
@@ -338,36 +327,6 @@ export function getValidatorApi(
338
327
  return null;
339
328
  }
340
329
 
341
- /**
342
- * Reject any request while the node is syncing
343
- */
344
- function notWhileSyncing(): void {
345
- // Consider node synced before or close to genesis
346
- if (chain.clock.currentSlot < SLOTS_PER_EPOCH) {
347
- return;
348
- }
349
-
350
- const syncState = sync.state;
351
- switch (syncState) {
352
- case SyncState.SyncingFinalized:
353
- case SyncState.SyncingHead: {
354
- const currentSlot = chain.clock.currentSlot;
355
- const headSlot = chain.forkChoice.getHead().slot;
356
- if (currentSlot - headSlot > SYNC_TOLERANCE_EPOCHS * SLOTS_PER_EPOCH) {
357
- throw new NodeIsSyncing(`headSlot ${headSlot} currentSlot ${currentSlot}`);
358
- }
359
-
360
- return;
361
- }
362
-
363
- case SyncState.Synced:
364
- return;
365
-
366
- case SyncState.Stalled:
367
- throw new NodeIsSyncing("waiting for peers");
368
- }
369
- }
370
-
371
330
  /**
372
331
  * Post merge, the CL and EL could be out of step in the sync, and could result in
373
332
  * Syncing status of the chain head. To be precise:
@@ -570,7 +529,17 @@ export function getValidatorApi(
570
529
  builderBoostFactor?: bigint,
571
530
  {feeRecipient, builderSelection, strictFeeRecipientCheck}: routes.validator.ExtraProduceBlockOpts = {}
572
531
  ): Promise<ProduceBlindedBlockOrBlockContentsRes> {
573
- notWhileSyncing();
532
+ builderSelection = builderSelection ?? routes.validator.BuilderSelection.MaxProfit;
533
+ if (builderSelection === routes.validator.BuilderSelection.BuilderOnly) {
534
+ logger.warn("Builder selection builderonly is no longer supported, treating as builderalways");
535
+ builderSelection = routes.validator.BuilderSelection.BuilderAlways;
536
+ }
537
+ builderBoostFactor = builderBoostFactor ?? BigInt(100);
538
+ if (builderBoostFactor > MAX_BUILDER_BOOST_FACTOR) {
539
+ throw new ApiError(400, `Invalid builderBoostFactor=${builderBoostFactor} > MAX_BUILDER_BOOST_FACTOR`);
540
+ }
541
+
542
+ notWhileSyncing(chain, sync.state);
574
543
  await waitForSlot(slot); // Must never request for a future slot > currentSlot
575
544
 
576
545
  const parentBlock = chain.getProposerHead(slot);
@@ -580,38 +549,17 @@ export function getValidatorApi(
580
549
  metrics?.blockProductionSlotDelta.set(slot - parentSlot);
581
550
 
582
551
  const fork = config.getForkName(slot);
583
- // set some sensible opts
584
- // builderSelection will be deprecated and will run in mode MaxProfit if builder is enabled
585
- // and the actual selection will be determined using builderBoostFactor passed by the validator
586
- builderSelection = builderSelection ?? routes.validator.BuilderSelection.MaxProfit;
587
- builderBoostFactor = builderBoostFactor ?? BigInt(100);
588
- if (builderBoostFactor > MAX_BUILDER_BOOST_FACTOR) {
589
- throw new ApiError(400, `Invalid builderBoostFactor=${builderBoostFactor} > MAX_BUILDER_BOOST_FACTOR`);
590
- }
591
552
 
592
553
  const isBuilderEnabled =
593
554
  ForkSeq[fork] >= ForkSeq.bellatrix &&
594
555
  chain.executionBuilder !== undefined &&
595
556
  builderSelection !== routes.validator.BuilderSelection.ExecutionOnly;
596
557
 
597
- // At any point either the builder or execution or both flows should be active.
598
- //
599
- // Ideally such a scenario should be prevented on startup, but proposerSettingsFile or keymanager
600
- // configurations could cause a validator pubkey to have builder disabled with builder selection builder only
601
- // (TODO: independently make sure such an options update is not successful for a validator pubkey)
602
- //
603
- // So if builder is disabled ignore builder selection of builder only if caused by user mistake
604
- // https://github.com/ChainSafe/lodestar/issues/6338
605
- const isEngineEnabled = !isBuilderEnabled || builderSelection !== routes.validator.BuilderSelection.BuilderOnly;
606
-
607
- if (!isEngineEnabled && !isBuilderEnabled) {
608
- throw Error(
609
- `Internal Error: Neither builder nor execution proposal flow activated isBuilderEnabled=${isBuilderEnabled} builderSelection=${builderSelection}`
610
- );
611
- }
612
-
613
558
  const graffitiBytes = toGraffitiBytes(
614
- graffiti ?? getDefaultGraffiti(getLodestarClientVersion(opts), chain.executionEngine.clientVersion, opts)
559
+ getBlockGraffiti(graffiti, getLodestarClientVersion(opts), chain.executionEngine.clientVersion, {
560
+ private: opts.private,
561
+ graffitiAppend: chain.opts.graffitiAppend,
562
+ })
615
563
  );
616
564
 
617
565
  const loggerContext = {
@@ -621,7 +569,6 @@ export function getValidatorApi(
621
569
  fork,
622
570
  builderSelection,
623
571
  isBuilderEnabled,
624
- isEngineEnabled,
625
572
  strictFeeRecipientCheck,
626
573
  // winston logger doesn't like bigint
627
574
  builderBoostFactor: `${builderBoostFactor}`,
@@ -647,27 +594,25 @@ export function getValidatorApi(
647
594
  })
648
595
  : Promise.reject(new Error("Builder disabled"));
649
596
 
650
- const enginePromise = isEngineEnabled
651
- ? produceEngineBlockContents(slot, randaoReveal, graffitiBytes, {
652
- feeRecipient,
653
- strictFeeRecipientCheck,
654
- commonBlockBodyPromise,
655
- parentBlock,
656
- }).then((engineBlock) => {
657
- // Once the engine returns a block, in the event of either:
658
- // - suspected builder censorship
659
- // - builder boost factor set to 0 or builder selection `executionalways`
660
- // we don't need to wait for builder block as engine block will always be selected
661
- if (
662
- engineBlock.shouldOverrideBuilder ||
663
- builderBoostFactor === BigInt(0) ||
664
- builderSelection === routes.validator.BuilderSelection.ExecutionAlways
665
- ) {
666
- controller.abort();
667
- }
668
- return engineBlock;
669
- })
670
- : Promise.reject(new Error("Engine disabled"));
597
+ const enginePromise = produceEngineBlockContents(slot, randaoReveal, graffitiBytes, {
598
+ feeRecipient,
599
+ strictFeeRecipientCheck,
600
+ commonBlockBodyPromise,
601
+ parentBlock,
602
+ }).then((engineBlock) => {
603
+ // Once the engine returns a block, in the event of either:
604
+ // - suspected builder censorship
605
+ // - builder boost factor set to 0 or builder selection `executionalways`
606
+ // we don't need to wait for builder block as engine block will always be selected
607
+ if (
608
+ engineBlock.shouldOverrideBuilder ||
609
+ builderBoostFactor === BigInt(0) ||
610
+ builderSelection === routes.validator.BuilderSelection.ExecutionAlways
611
+ ) {
612
+ controller.abort();
613
+ }
614
+ return engineBlock;
615
+ });
671
616
 
672
617
  // Calculate cutoff time based on start of the slot
673
618
  const cutoffMs = Math.max(0, BLOCK_PRODUCTION_RACE_CUTOFF_MS - chain.clock.msFromSlot(slot));
@@ -714,30 +659,24 @@ export function getValidatorApi(
714
659
  throw Error("Builder and engine both failed to produce the block within timeout");
715
660
  }
716
661
 
717
- if (builder.status === "pending" && !isEngineEnabled) {
718
- throw Error("Builder failed to produce the block within timeout");
719
- }
720
-
721
662
  if (engine.status === "pending" && !isBuilderEnabled) {
722
663
  throw Error("Engine failed to produce the block within timeout");
723
664
  }
724
665
 
725
- if (isEngineEnabled) {
726
- if (engine.status === "rejected") {
727
- logger.warn(
728
- "Engine failed to produce the block",
729
- {
730
- ...loggerContext,
731
- durationMs: engine.durationMs,
732
- },
733
- engine.reason
734
- );
735
- } else if (engine.status === "pending") {
736
- logger.warn("Engine failed to produce the block within cutoff time", {
666
+ if (engine.status === "rejected") {
667
+ logger.warn(
668
+ "Engine failed to produce the block",
669
+ {
737
670
  ...loggerContext,
738
- cutoffMs,
739
- });
740
- }
671
+ durationMs: engine.durationMs,
672
+ },
673
+ engine.reason
674
+ );
675
+ } else if (engine.status === "pending") {
676
+ logger.warn("Engine failed to produce the block within cutoff time", {
677
+ ...loggerContext,
678
+ cutoffMs,
679
+ });
741
680
  }
742
681
 
743
682
  if (isBuilderEnabled) {
@@ -766,14 +705,12 @@ export function getValidatorApi(
766
705
  }
767
706
 
768
707
  if (builder.status === "rejected" && engine.status === "rejected") {
769
- throw Error(
770
- `${isBuilderEnabled && isEngineEnabled ? "Builder and engine both" : isBuilderEnabled ? "Builder" : "Engine"} failed to produce the block`
771
- );
708
+ throw Error(`${isBuilderEnabled ? "Builder and engine both" : "Engine"} failed to produce the block`);
772
709
  }
773
710
 
774
711
  // handle shouldOverrideBuilder separately
775
- if (engine.status === "fulfilled" && engine.value.shouldOverrideBuilder) {
776
- logger.info("Selected engine block: censorship suspected in builder blocks", {
712
+ if (engine.status === "fulfilled" && engine.value.shouldOverrideBuilder && isBuilderEnabled) {
713
+ logger.warn("Selected engine block: censorship suspected in builder blocks", {
777
714
  ...loggerContext,
778
715
  durationMs: engine.durationMs,
779
716
  shouldOverrideBuilder: engine.value.shouldOverrideBuilder,
@@ -790,11 +727,9 @@ export function getValidatorApi(
790
727
 
791
728
  if (builder.status === "fulfilled" && engine.status !== "fulfilled") {
792
729
  const reason =
793
- isEngineEnabled === false
794
- ? BuilderBlockSelectionReason.EngineDisabled
795
- : engine.status === "pending"
796
- ? BuilderBlockSelectionReason.EnginePending
797
- : BuilderBlockSelectionReason.EngineError;
730
+ engine.status === "pending"
731
+ ? BuilderBlockSelectionReason.EnginePending
732
+ : BuilderBlockSelectionReason.EngineError;
798
733
 
799
734
  logger.info("Selected builder block: no engine block produced", {
800
735
  reason,
@@ -904,14 +839,32 @@ export function getValidatorApi(
904
839
  return {data, meta};
905
840
  },
906
841
 
907
- async produceBlockV4({slot, randaoReveal, graffiti, feeRecipient}) {
842
+ async produceBlockV4({
843
+ slot,
844
+ randaoReveal,
845
+ graffiti,
846
+ feeRecipient,
847
+ includePayload,
848
+ builderSelection,
849
+ builderBoostFactor,
850
+ }) {
908
851
  const fork = config.getForkName(slot);
909
852
 
910
853
  if (!isForkPostGloas(fork)) {
911
854
  throw new ApiError(400, `produceBlockV4 not supported for pre-gloas fork=${fork}`);
912
855
  }
913
856
 
914
- notWhileSyncing();
857
+ builderSelection = builderSelection ?? routes.validator.BuilderSelection.MaxProfit;
858
+ if (builderSelection === routes.validator.BuilderSelection.BuilderOnly) {
859
+ logger.warn("Builder selection builderonly is no longer supported, treating as builderalways");
860
+ builderSelection = routes.validator.BuilderSelection.BuilderAlways;
861
+ }
862
+ builderBoostFactor = builderBoostFactor ?? BigInt(100);
863
+ if (builderBoostFactor > MAX_BUILDER_BOOST_FACTOR) {
864
+ throw new ApiError(400, `Invalid builderBoostFactor=${builderBoostFactor} > MAX_BUILDER_BOOST_FACTOR`);
865
+ }
866
+
867
+ notWhileSyncing(chain, sync.state);
915
868
  await waitForSlot(slot);
916
869
 
917
870
  const parentBlock = chain.getProposerHead(slot);
@@ -921,15 +874,23 @@ export function getValidatorApi(
921
874
  metrics?.blockProductionSlotDelta.set(slot - parentSlot);
922
875
 
923
876
  const graffitiBytes = toGraffitiBytes(
924
- graffiti ?? getDefaultGraffiti(getLodestarClientVersion(opts), chain.executionEngine.clientVersion, opts)
877
+ getBlockGraffiti(graffiti, getLodestarClientVersion(opts), chain.executionEngine.clientVersion, {
878
+ private: opts.private,
879
+ graffitiAppend: chain.opts.graffitiAppend,
880
+ })
925
881
  );
926
882
 
927
- // TODO GLOAS: respect builderSelection (MaxProfit, BuilderAlways, ExecutionAlways, etc.) to let
928
- // the user control bid source preferences and value comparison. Also add external builder api
929
- // support when it is implemented.
883
+ // TODO GLOAS: add external builder api support when it is implemented
930
884
  const isBuildingOnFull = chain.forkChoice.shouldBuildOnFull(parentBlock, slot);
931
885
  const bidParentBlockHash = isBuildingOnFull ? parentBlock.executionPayloadBlockHash : parentBlock.parentBlockHash;
932
- const builderBid = chain.executionPayloadBidPool.getBestBid(slot, bidParentBlockHash, parentBlockRootHex);
886
+ // Bids are only skipped entirely with executiononly or while the circuit breaker is active,
887
+ // other engine-preferring selections still build a block with the best bid as fallback in
888
+ // case local production fails
889
+ const circuitBreakerActive = chain.builderCircuitBreaker.isActive(slot);
890
+ const builderBid =
891
+ builderSelection === routes.validator.BuilderSelection.ExecutionOnly || circuitBreakerActive
892
+ ? null
893
+ : chain.executionPayloadBidPool.getBestBid(slot, bidParentBlockHash, parentBlockRootHex);
933
894
 
934
895
  const logCtx = {
935
896
  slot,
@@ -937,6 +898,9 @@ export function getValidatorApi(
937
898
  parentBlockRoot: parentBlockRootHex,
938
899
  parentBlockHash: parentBlock.executionPayloadBlockHash,
939
900
  fork,
901
+ builderSelection,
902
+ builderBoostFactor,
903
+ circuitBreakerActive,
940
904
  ...(builderBid !== null
941
905
  ? {
942
906
  bidValue: builderBid.message.value,
@@ -972,33 +936,101 @@ export function getValidatorApi(
972
936
  return fn().finally(() => t?.({source}));
973
937
  };
974
938
 
975
- // Always build local block. If builder bid available, also build with it in parallel and prefer it.
976
- const [engineResult, bidResult] = await Promise.allSettled([
977
- timed(ProducedBlockSource.engine, () => chain.produceBlock(baseAttrs)),
939
+ // Calculate cutoff time based on start of the slot, ensures a slow local payload build does
940
+ // not delay the proposal when a builder bid block is available (and vice versa)
941
+ const cutoffMs = Math.max(0, BLOCK_PRODUCTION_RACE_CUTOFF_MS - chain.clock.msFromSlot(slot));
942
+
943
+ // use abort controller to stop waiting for the bid block if the engine block will be selected
944
+ const controller = new AbortController();
945
+
946
+ const enginePromise: ReturnType<typeof chain.produceBlock> = timed(ProducedBlockSource.engine, () =>
947
+ chain.produceBlock(baseAttrs)
948
+ ).then((engineBlock) => {
949
+ // No need to wait for the bid block if the engine block will always be selected due to
950
+ // suspected builder censorship, a builder boost factor of 0 or executionalways selection
951
+ if (
952
+ engineBlock.shouldOverrideBuilder ||
953
+ builderBoostFactor === BigInt(0) ||
954
+ builderSelection === routes.validator.BuilderSelection.ExecutionAlways
955
+ ) {
956
+ controller.abort();
957
+ }
958
+ return engineBlock;
959
+ });
960
+ const bidPromise: ReturnType<typeof chain.produceBlock> =
978
961
  builderBid !== null
979
962
  ? timed(ProducedBlockSource.builder, () => chain.produceBlock({...baseAttrs, builderBid}))
980
- : Promise.reject(),
981
- ]);
963
+ : Promise.reject(new Error("No builder bid available"));
964
+
965
+ const [engineResult, bidResult] = await resolveOrRacePromises([enginePromise, bidPromise], {
966
+ resolveTimeoutMs: cutoffMs,
967
+ raceTimeoutMs: BLOCK_PRODUCTION_RACE_TIMEOUT_MS,
968
+ signal: controller.signal,
969
+ });
982
970
 
983
971
  let bestResult: typeof engineResult | null = null;
984
972
  let source: ProducedBlockSource = ProducedBlockSource.engine;
985
- if (builderBid !== null && bidResult.status === "fulfilled") {
973
+
974
+ // handle shouldOverrideBuilder separately
975
+ if (engineResult.status === "fulfilled" && engineResult.value.shouldOverrideBuilder && builderBid !== null) {
976
+ source = ProducedBlockSource.engine;
977
+ bestResult = engineResult;
978
+ metrics?.blockProductionSelectionResults.inc({
979
+ source: ProducedBlockSource.engine,
980
+ reason: EngineBlockSelectionReason.BuilderCensorship,
981
+ });
982
+ logger.warn("Selected local block: censorship suspected in builder bid", logCtx);
983
+ } else if (engineResult.status === "fulfilled" && bidResult.status === "fulfilled") {
984
+ const result = selectBlockProductionSource({
985
+ builderSelection,
986
+ builderBoostFactor,
987
+ engineExecutionPayloadValue: engineResult.value.executionPayloadValue,
988
+ // The bid value is the payment to the proposer, in Gwei
989
+ builderExecutionPayloadValue: BigInt(builderBid?.message.value ?? 0) * GWEI_TO_WEI,
990
+ });
991
+ source = result.source;
992
+ metrics?.blockProductionSelectionResults.inc(result);
993
+ logger.info(`Selected ${source} block`, {reason: result.reason, ...logCtx});
994
+ bestResult = source === ProducedBlockSource.builder ? bidResult : engineResult;
995
+ } else if (bidResult.status === "fulfilled") {
986
996
  source = ProducedBlockSource.builder;
987
997
  bestResult = bidResult;
988
- logger.info("Selected builder bid block", logCtx);
998
+ const reason =
999
+ engineResult.status === "pending"
1000
+ ? BuilderBlockSelectionReason.EnginePending
1001
+ : BuilderBlockSelectionReason.EngineError;
1002
+ metrics?.blockProductionSelectionResults.inc({source: ProducedBlockSource.builder, reason});
1003
+ logger.info("Selected builder bid block: no local block produced", {
1004
+ reason,
1005
+ ...logCtx,
1006
+ error: engineResult.status === "rejected" ? (engineResult.reason as Error).message : undefined,
1007
+ });
989
1008
  } else if (engineResult.status === "fulfilled") {
990
1009
  source = ProducedBlockSource.engine;
991
1010
  bestResult = engineResult;
992
- if (builderBid !== null) {
993
- logger.warn("Builder bid block production failed, using local block", logCtx);
994
- }
1011
+ const reason =
1012
+ builderBid === null
1013
+ ? EngineBlockSelectionReason.BuilderNoBid
1014
+ : bidResult.status === "pending"
1015
+ ? EngineBlockSelectionReason.BuilderPending
1016
+ : EngineBlockSelectionReason.BuilderError;
1017
+ metrics?.blockProductionSelectionResults.inc({source: ProducedBlockSource.engine, reason});
1018
+ logger.info("Selected local block: no builder bid block produced", {
1019
+ reason,
1020
+ ...logCtx,
1021
+ error: bidResult.status === "rejected" ? (bidResult.reason as Error).message : undefined,
1022
+ });
995
1023
  }
996
1024
 
997
1025
  if (bestResult === null || bestResult.status !== "fulfilled") {
998
- const engineReason = engineResult.status === "rejected" ? engineResult.reason : undefined;
999
- const bidReason = builderBid !== null && bidResult.status === "rejected" ? bidResult.reason : undefined;
1000
- logger.error("Block production failed", {...logCtx, engineReason, bidReason});
1001
- throw Error(`Block production failed: engine=${engineReason ?? "n/a"} builder=${bidReason ?? "n/a"}`);
1026
+ const engineReason = engineResult.status === "rejected" ? engineResult.reason : engineResult.status;
1027
+ const bidReason = bidResult.status === "rejected" ? bidResult.reason : bidResult.status;
1028
+ logger.error("Block production failed", {
1029
+ ...logCtx,
1030
+ engineReason: String(engineReason),
1031
+ bidReason: String(bidReason),
1032
+ });
1033
+ throw Error(`Block production failed: engine=${String(engineReason)} builder=${String(bidReason)}`);
1002
1034
  }
1003
1035
 
1004
1036
  const {block, executionPayloadValue, consensusBlockValue} = bestResult.value;
@@ -1019,14 +1051,46 @@ export function getValidatorApi(
1019
1051
  void chain.persistBlock(block, "produced_engine_block");
1020
1052
  }
1021
1053
 
1054
+ // Include the payload for self-builds unless disabled (stateless flow)
1055
+ const isSelfBuild = source === ProducedBlockSource.engine;
1056
+ if (isSelfBuild && includePayload) {
1057
+ const produceResult = chain.blockProductionCache.get(blockRoot);
1058
+ if (
1059
+ produceResult === undefined ||
1060
+ !isForkPostGloas(produceResult.fork) ||
1061
+ produceResult.type !== BlockType.Full
1062
+ ) {
1063
+ throw Error(`Missing cached block production result for produced block slot=${slot} blockRoot=${blockRoot}`);
1064
+ }
1065
+ const {executionPayload, executionRequests, blobsBundle, parentBlockRoot} = produceResult as ProduceFullGloas;
1066
+
1067
+ const blockContents: gloas.BlockContents = {
1068
+ block: block as gloas.BeaconBlock,
1069
+ executionPayloadEnvelope: {
1070
+ payload: executionPayload,
1071
+ executionRequests,
1072
+ builderIndex: BUILDER_INDEX_SELF_BUILD,
1073
+ beaconBlockRoot: fromHex(blockRoot),
1074
+ parentBeaconBlockRoot: parentBlockRoot,
1075
+ },
1076
+ kzgProofs: blobsBundle.proofs,
1077
+ blobs: blobsBundle.blobs,
1078
+ };
1079
+
1080
+ return {
1081
+ data: blockContents,
1082
+ meta: {version: fork, consensusBlockValue, executionPayloadValue, executionPayloadIncluded: true},
1083
+ };
1084
+ }
1085
+
1022
1086
  return {
1023
1087
  data: block as gloas.BeaconBlock,
1024
- meta: {version: fork, consensusBlockValue},
1088
+ meta: {version: fork, consensusBlockValue, executionPayloadValue, executionPayloadIncluded: false},
1025
1089
  };
1026
1090
  },
1027
1091
 
1028
1092
  async produceAttestationData({committeeIndex, slot}) {
1029
- notWhileSyncing();
1093
+ notWhileSyncing(chain, sync.state);
1030
1094
 
1031
1095
  await waitForSlot(slot); // Must never request for a future slot > currentSlot
1032
1096
 
@@ -1107,13 +1171,13 @@ export function getValidatorApi(
1107
1171
  throw new ApiError(400, `producePayloadAttestationData is not supported before Gloas fork=${fork}`);
1108
1172
  }
1109
1173
 
1110
- notWhileSyncing();
1174
+ notWhileSyncing(chain, sync.state);
1111
1175
  await waitForSlot(slot);
1112
1176
 
1113
1177
  const block = chain.forkChoice.getCanonicalBlockAtSlot(slot);
1114
1178
  if (!block) {
1115
- // No block is seen at slot. Return 404 so vc can skip casting payload attestation.
1116
- throw new ApiError(404, `No canonical block found at slot=${slot}`);
1179
+ // No canonical block is seen at slot. Return 204 so vc can skip casting payload attestation.
1180
+ return {data: undefined, meta: {version: fork}, status: 204};
1117
1181
  }
1118
1182
 
1119
1183
  const payloadInput = chain.seenPayloadEnvelopeInputCache.get(block.blockRoot);
@@ -1192,7 +1256,7 @@ export function getValidatorApi(
1192
1256
  },
1193
1257
 
1194
1258
  async getProposerDuties({epoch}, _context, opts?: {v2?: boolean}) {
1195
- notWhileSyncing();
1259
+ notWhileSyncing(chain, sync.state);
1196
1260
 
1197
1261
  const currentEpoch = currentEpochWithDisparity();
1198
1262
  const nextEpoch = currentEpoch + 1;
@@ -1239,7 +1303,7 @@ export function getValidatorApi(
1239
1303
  // requested epoch is within that range, we can use the head state at current epoch
1240
1304
  state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.getDuties);
1241
1305
  } else {
1242
- const res = await getStateResponseWithRegen(chain, startSlot);
1306
+ const res = await getStateResponseWithRegen(chain, sync, startSlot);
1243
1307
 
1244
1308
  state = res.state instanceof Uint8Array ? chain.getHeadState().loadOtherState(res.state) : res.state;
1245
1309
 
@@ -1333,7 +1397,7 @@ export function getValidatorApi(
1333
1397
  },
1334
1398
 
1335
1399
  async getAttesterDuties({epoch, indices}) {
1336
- notWhileSyncing();
1400
+ notWhileSyncing(chain, sync.state);
1337
1401
 
1338
1402
  if (indices.length === 0) {
1339
1403
  throw new ApiError(400, "No validator to get attester duties");
@@ -1393,7 +1457,7 @@ export function getValidatorApi(
1393
1457
  },
1394
1458
 
1395
1459
  async getPtcDuties({epoch, indices}) {
1396
- notWhileSyncing();
1460
+ notWhileSyncing(chain, sync.state);
1397
1461
 
1398
1462
  if (indices.length === 0) {
1399
1463
  throw new ApiError(400, "No validator to get PTC duties");
@@ -1455,7 +1519,7 @@ export function getValidatorApi(
1455
1519
  * @param validatorIndices an array of the validator indices for which to obtain the duties.
1456
1520
  */
1457
1521
  async getSyncCommitteeDuties({epoch, indices}) {
1458
- notWhileSyncing();
1522
+ notWhileSyncing(chain, sync.state);
1459
1523
 
1460
1524
  if (indices.length === 0) {
1461
1525
  throw new ApiError(400, "No validator to get attester duties");
@@ -1502,7 +1566,7 @@ export function getValidatorApi(
1502
1566
  },
1503
1567
 
1504
1568
  async getAggregatedAttestationV2({attestationDataRoot, slot, committeeIndex}) {
1505
- notWhileSyncing();
1569
+ notWhileSyncing(chain, sync.state);
1506
1570
 
1507
1571
  await waitForSlot(slot); // Must never request for a future slot > currentSlot
1508
1572
 
@@ -1525,7 +1589,7 @@ export function getValidatorApi(
1525
1589
  },
1526
1590
 
1527
1591
  async publishAggregateAndProofsV2({signedAggregateAndProofs}) {
1528
- notWhileSyncing();
1592
+ notWhileSyncing(chain, sync.state);
1529
1593
 
1530
1594
  const seenTimestampSec = Date.now() / 1000;
1531
1595
  const failures: FailureList = [];
@@ -1586,7 +1650,7 @@ export function getValidatorApi(
1586
1650
  * https://github.com/ethereum/beacon-APIs/pull/137
1587
1651
  */
1588
1652
  async publishContributionAndProofs({contributionAndProofs}) {
1589
- notWhileSyncing();
1653
+ notWhileSyncing(chain, sync.state);
1590
1654
 
1591
1655
  const failures: FailureList = [];
1592
1656
 
@@ -1635,7 +1699,7 @@ export function getValidatorApi(
1635
1699
  },
1636
1700
 
1637
1701
  async prepareBeaconCommitteeSubnet({subscriptions}) {
1638
- notWhileSyncing();
1702
+ notWhileSyncing(chain, sync.state);
1639
1703
 
1640
1704
  await network.prepareBeaconCommitteeSubnets(
1641
1705
  subscriptions.map(({validatorIndex, slot, isAggregator, committeesAtSlot, committeeIndex}) => ({
@@ -1668,7 +1732,7 @@ export function getValidatorApi(
1668
1732
  * https://github.com/ethereum/beacon-APIs/pull/136
1669
1733
  */
1670
1734
  async prepareSyncCommitteeSubnets({subscriptions}) {
1671
- notWhileSyncing();
1735
+ notWhileSyncing(chain, sync.state);
1672
1736
 
1673
1737
  // A `validatorIndex` can be in multiple subnets, so compute the CommitteeSubscription with double for loop
1674
1738
  const subs: CommitteeSubscription[] = [];
@@ -1765,6 +1829,46 @@ export function getValidatorApi(
1765
1829
  });
1766
1830
  },
1767
1831
 
1832
+ async submitProposerPreferences({signedProposerPreferences}) {
1833
+ const failures: FailureList = [];
1834
+
1835
+ await Promise.all(
1836
+ signedProposerPreferences.map(async (signed, i) => {
1837
+ try {
1838
+ await validateGossipProposerPreferences(chain, signed);
1839
+
1840
+ chain.proposerPreferencesPool.add(signed);
1841
+ await network.publishProposerPreferences(signed);
1842
+ chain.emitter.emit(routes.events.EventType.proposerPreferences, {
1843
+ version: config.getForkName(signed.message.proposalSlot),
1844
+ data: signed,
1845
+ });
1846
+ } catch (e) {
1847
+ const logCtx = {
1848
+ slot: signed.message.proposalSlot,
1849
+ validatorIndex: signed.message.validatorIndex,
1850
+ dependentRoot: toRootHex(signed.message.dependentRoot),
1851
+ };
1852
+
1853
+ if (e instanceof ProposerPreferencesError && e.type.code === ProposerPreferencesErrorCode.ALREADY_KNOWN) {
1854
+ logger.debug("Ignoring known signed proposer preferences", logCtx);
1855
+ return;
1856
+ }
1857
+
1858
+ failures.push({index: i, message: (e as Error).message});
1859
+ logger.verbose(`Error on submitProposerPreferences [${i}]`, logCtx, e as Error);
1860
+ if (e instanceof ProposerPreferencesError && e.action === GossipAction.REJECT) {
1861
+ chain.persistInvalidSszValue(ssz.gloas.SignedProposerPreferences, signed, "api_reject");
1862
+ }
1863
+ }
1864
+ })
1865
+ );
1866
+
1867
+ if (failures.length > 0) {
1868
+ throw new IndexedError("Error processing signed proposer preferences", failures);
1869
+ }
1870
+ },
1871
+
1768
1872
  async getExecutionPayloadEnvelope({slot, beaconBlockRoot}) {
1769
1873
  const fork = config.getForkName(slot);
1770
1874
 
@@ -1772,7 +1876,7 @@ export function getValidatorApi(
1772
1876
  throw new ApiError(400, `getExecutionPayloadEnvelope not supported for pre-gloas fork=${fork}`);
1773
1877
  }
1774
1878
 
1775
- notWhileSyncing();
1879
+ notWhileSyncing(chain, sync.state);
1776
1880
  await waitForSlot(slot);
1777
1881
 
1778
1882
  const blockRootHex = toRootHex(beaconBlockRoot);
@@ -1790,6 +1894,18 @@ export function getValidatorApi(
1790
1894
 
1791
1895
  const {executionPayload, executionRequests, parentBlockRoot} = produceResult as ProduceFullGloas;
1792
1896
 
1897
+ if (executionPayload === undefined) {
1898
+ // Blocks committing to a builder bid are cached as full but without payload data
1899
+ throw new ApiError(404, `No local execution payload cached for block root ${blockRootHex}`);
1900
+ }
1901
+
1902
+ if (executionPayload.slotNumber !== slot) {
1903
+ throw new ApiError(
1904
+ 404,
1905
+ `Cached execution payload is for slot=${executionPayload.slotNumber}, requested slot=${slot}`
1906
+ );
1907
+ }
1908
+
1793
1909
  const envelope: gloas.ExecutionPayloadEnvelope = {
1794
1910
  payload: executionPayload,
1795
1911
  executionRequests: executionRequests,