@lodestar/beacon-node 1.45.0-dev.f2645825a8 → 1.45.0-dev.f6c521a123

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 (214) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +3 -2
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/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 -42
  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 +58 -59
  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/interface.d.ts +2 -0
  68. package/lib/chain/interface.d.ts.map +1 -1
  69. package/lib/chain/interface.js.map +1 -1
  70. package/lib/chain/lightClient/index.d.ts.map +1 -1
  71. package/lib/chain/lightClient/index.js +11 -6
  72. package/lib/chain/lightClient/index.js.map +1 -1
  73. package/lib/chain/lightClient/proofs.d.ts.map +1 -1
  74. package/lib/chain/lightClient/proofs.js +28 -2
  75. package/lib/chain/lightClient/proofs.js.map +1 -1
  76. package/lib/chain/lightClient/types.d.ts +3 -1
  77. package/lib/chain/lightClient/types.d.ts.map +1 -1
  78. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  79. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  80. package/lib/chain/opPools/proposerPreferencesPool.d.ts +1 -1
  81. package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
  82. package/lib/chain/opPools/proposerPreferencesPool.js +1 -1
  83. package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
  84. package/lib/chain/options.d.ts +4 -0
  85. package/lib/chain/options.d.ts.map +1 -1
  86. package/lib/chain/options.js.map +1 -1
  87. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  88. package/lib/chain/prepareNextSlot.js +12 -7
  89. package/lib/chain/prepareNextSlot.js.map +1 -1
  90. package/lib/chain/validation/aggregateAndProof.js +10 -6
  91. package/lib/chain/validation/aggregateAndProof.js.map +1 -1
  92. package/lib/chain/validation/attestation.d.ts.map +1 -1
  93. package/lib/chain/validation/attestation.js +10 -6
  94. package/lib/chain/validation/attestation.js.map +1 -1
  95. package/lib/chain/validation/block.d.ts.map +1 -1
  96. package/lib/chain/validation/block.js +53 -4
  97. package/lib/chain/validation/block.js.map +1 -1
  98. package/lib/chain/validation/executionPayloadBid.js +13 -12
  99. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  100. package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
  101. package/lib/chain/validation/executionPayloadEnvelope.js +29 -0
  102. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  103. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  104. package/lib/chain/validation/proposerPreferences.js +3 -2
  105. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  106. package/lib/chain/validation/signatureSets/aggregateAndProof.d.ts.map +1 -1
  107. package/lib/chain/validation/signatureSets/aggregateAndProof.js +2 -3
  108. package/lib/chain/validation/signatureSets/aggregateAndProof.js.map +1 -1
  109. package/lib/db/repositories/lightclientSyncCommitteeWitness.d.ts.map +1 -1
  110. package/lib/db/repositories/lightclientSyncCommitteeWitness.js +42 -7
  111. package/lib/db/repositories/lightclientSyncCommitteeWitness.js.map +1 -1
  112. package/lib/execution/builder/http.d.ts +18 -0
  113. package/lib/execution/builder/http.d.ts.map +1 -1
  114. package/lib/execution/builder/http.js +20 -11
  115. package/lib/execution/builder/http.js.map +1 -1
  116. package/lib/metrics/metrics/beacon.d.ts +6 -0
  117. package/lib/metrics/metrics/beacon.d.ts.map +1 -1
  118. package/lib/metrics/metrics/beacon.js +18 -0
  119. package/lib/metrics/metrics/beacon.js.map +1 -1
  120. package/lib/network/core/metrics.d.ts +65 -0
  121. package/lib/network/core/metrics.d.ts.map +1 -1
  122. package/lib/network/core/metrics.js +94 -0
  123. package/lib/network/core/metrics.js.map +1 -1
  124. package/lib/network/discv5/utils.d.ts.map +1 -1
  125. package/lib/network/discv5/utils.js +12 -3
  126. package/lib/network/discv5/utils.js.map +1 -1
  127. package/lib/network/gossip/encoding.d.ts +2 -2
  128. package/lib/network/gossip/encoding.d.ts.map +1 -1
  129. package/lib/network/gossip/encoding.js +22 -9
  130. package/lib/network/gossip/encoding.js.map +1 -1
  131. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  132. package/lib/network/gossip/gossipsub.js +4 -3
  133. package/lib/network/gossip/gossipsub.js.map +1 -1
  134. package/lib/network/gossip/topic.d.ts +80 -232
  135. package/lib/network/gossip/topic.d.ts.map +1 -1
  136. package/lib/network/gossip/topic.js +47 -4
  137. package/lib/network/gossip/topic.js.map +1 -1
  138. package/lib/network/peers/peerManager.d.ts.map +1 -1
  139. package/lib/network/peers/peerManager.js +10 -0
  140. package/lib/network/peers/peerManager.js.map +1 -1
  141. package/lib/network/peers/score/score.d.ts +3 -1
  142. package/lib/network/peers/score/score.d.ts.map +1 -1
  143. package/lib/network/peers/score/score.js +6 -1
  144. package/lib/network/peers/score/score.js.map +1 -1
  145. package/lib/network/peers/score/store.d.ts.map +1 -1
  146. package/lib/network/peers/score/store.js +6 -1
  147. package/lib/network/peers/score/store.js.map +1 -1
  148. package/lib/network/peers/utils/prioritizePeers.d.ts.map +1 -1
  149. package/lib/network/peers/utils/prioritizePeers.js +14 -0
  150. package/lib/network/peers/utils/prioritizePeers.js.map +1 -1
  151. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  152. package/lib/network/processor/gossipHandlers.js +18 -8
  153. package/lib/network/processor/gossipHandlers.js.map +1 -1
  154. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
  155. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +4 -2
  156. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
  157. package/lib/sync/sync.d.ts.map +1 -1
  158. package/lib/sync/sync.js +7 -0
  159. package/lib/sync/sync.js.map +1 -1
  160. package/lib/sync/unknownBlock.d.ts.map +1 -1
  161. package/lib/sync/unknownBlock.js +7 -2
  162. package/lib/sync/unknownBlock.js.map +1 -1
  163. package/package.json +23 -21
  164. package/src/api/impl/beacon/blocks/index.ts +3 -2
  165. package/src/api/impl/beacon/index.ts +1 -1
  166. package/src/api/impl/beacon/pool/index.ts +5 -52
  167. package/src/api/impl/beacon/state/index.ts +3 -2
  168. package/src/api/impl/beacon/state/utils.ts +11 -0
  169. package/src/api/impl/debug/index.ts +3 -2
  170. package/src/api/impl/lodestar/index.ts +2 -2
  171. package/src/api/impl/proof/index.ts +2 -2
  172. package/src/api/impl/utils.ts +51 -1
  173. package/src/api/impl/validator/index.ts +66 -64
  174. package/src/api/rest/base.ts +17 -3
  175. package/src/chain/blocks/importBlock.ts +1 -23
  176. package/src/chain/blocks/utils/chainSegment.ts +16 -5
  177. package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +1 -1
  178. package/src/chain/blocks/verifyBlocksSanityChecks.ts +1 -1
  179. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -2
  180. package/src/chain/builderCircuitBreaker.ts +84 -0
  181. package/src/chain/chain.ts +38 -2
  182. package/src/chain/errors/blockError.ts +44 -7
  183. package/src/chain/errors/executionPayloadEnvelope.ts +9 -0
  184. package/src/chain/interface.ts +2 -0
  185. package/src/chain/lightClient/index.ts +12 -7
  186. package/src/chain/lightClient/proofs.ts +36 -1
  187. package/src/chain/lightClient/types.ts +3 -1
  188. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  189. package/src/chain/opPools/proposerPreferencesPool.ts +1 -1
  190. package/src/chain/options.ts +4 -0
  191. package/src/chain/prepareNextSlot.ts +11 -7
  192. package/src/chain/validation/aggregateAndProof.ts +12 -6
  193. package/src/chain/validation/attestation.ts +12 -6
  194. package/src/chain/validation/block.ts +69 -4
  195. package/src/chain/validation/executionPayloadBid.ts +16 -11
  196. package/src/chain/validation/executionPayloadEnvelope.ts +37 -0
  197. package/src/chain/validation/proposerPreferences.ts +3 -2
  198. package/src/chain/validation/signatureSets/aggregateAndProof.ts +3 -3
  199. package/src/db/repositories/lightclientSyncCommitteeWitness.ts +57 -9
  200. package/src/execution/builder/http.ts +30 -17
  201. package/src/metrics/metrics/beacon.ts +19 -0
  202. package/src/network/core/metrics.ts +94 -0
  203. package/src/network/discv5/utils.ts +11 -3
  204. package/src/network/gossip/encoding.ts +20 -7
  205. package/src/network/gossip/gossipsub.ts +9 -3
  206. package/src/network/gossip/topic.ts +58 -4
  207. package/src/network/peers/peerManager.ts +11 -0
  208. package/src/network/peers/score/score.ts +8 -1
  209. package/src/network/peers/score/store.ts +8 -1
  210. package/src/network/peers/utils/prioritizePeers.ts +15 -0
  211. package/src/network/processor/gossipHandlers.ts +18 -7
  212. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +4 -2
  213. package/src/sync/sync.ts +10 -0
  214. package/src/sync/unknownBlock.ts +8 -2
@@ -65,6 +65,8 @@ import {
65
65
  AttestationError,
66
66
  AttestationErrorCode,
67
67
  GossipAction,
68
+ ProposerPreferencesError,
69
+ ProposerPreferencesErrorCode,
68
70
  SyncCommitteeError,
69
71
  SyncCommitteeErrorCode,
70
72
  } from "../../../chain/errors/index.js";
@@ -74,12 +76,12 @@ import {BlockType, ProduceFullDeneb, ProduceFullGloas} from "../../../chain/prod
74
76
  import {RegenCaller} from "../../../chain/regen/index.js";
75
77
  import {CheckpointHex} from "../../../chain/stateCache/types.js";
76
78
  import {validateApiAggregateAndProof} from "../../../chain/validation/index.js";
79
+ import {validateGossipProposerPreferences} from "../../../chain/validation/proposerPreferences.js";
77
80
  import {validateSyncCommitteeGossipContributionAndProof} from "../../../chain/validation/syncCommitteeContributionAndProof.js";
78
81
  import {ZERO_HASH} from "../../../constants/index.js";
79
82
  import {BuilderStatus, NoBidReceived} from "../../../execution/builder/http.js";
80
83
  import {validateGossipFnRetryUnknownRoot} from "../../../network/processor/gossipHandlers.js";
81
84
  import {CommitteeSubscription} from "../../../network/subnets/index.js";
82
- import {SyncState} from "../../../sync/index.js";
83
85
  import {callInNextEventLoop} from "../../../util/eventLoop.js";
84
86
  import {isOptimisticBlock} from "../../../util/forkChoice.js";
85
87
  import {getBlockGraffiti, toGraffitiBytes} from "../../../util/graffiti.js";
@@ -88,23 +90,9 @@ import {ApiOptions} from "../../options.js";
88
90
  import {getStateResponseWithRegen} from "../beacon/state/utils.js";
89
91
  import {ApiError, FailureList, IndexedError, NodeIsSyncing, OnlySupportedByDVT} from "../errors.js";
90
92
  import {ApiModules} from "../types.js";
93
+ import {notWhileSyncing} from "../utils.js";
91
94
  import {computeSubnetForCommitteesAtSlot, getPubkeysForIndices, selectBlockProductionSource} from "./utils.js";
92
95
 
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
96
  /**
109
97
  * Cutoff time to wait from start of the slot for execution and builder block production apis to resolve.
110
98
  * Post this time, race execution and builder to pick whatever resolves first.
@@ -338,36 +326,6 @@ export function getValidatorApi(
338
326
  return null;
339
327
  }
340
328
 
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
329
  /**
372
330
  * Post merge, the CL and EL could be out of step in the sync, and could result in
373
331
  * Syncing status of the chain head. To be precise:
@@ -570,7 +528,7 @@ export function getValidatorApi(
570
528
  builderBoostFactor?: bigint,
571
529
  {feeRecipient, builderSelection, strictFeeRecipientCheck}: routes.validator.ExtraProduceBlockOpts = {}
572
530
  ): Promise<ProduceBlindedBlockOrBlockContentsRes> {
573
- notWhileSyncing();
531
+ notWhileSyncing(chain, sync.state);
574
532
  await waitForSlot(slot); // Must never request for a future slot > currentSlot
575
533
 
576
534
  const parentBlock = chain.getProposerHead(slot);
@@ -914,7 +872,7 @@ export function getValidatorApi(
914
872
  throw new ApiError(400, `produceBlockV4 not supported for pre-gloas fork=${fork}`);
915
873
  }
916
874
 
917
- notWhileSyncing();
875
+ notWhileSyncing(chain, sync.state);
918
876
  await waitForSlot(slot);
919
877
 
920
878
  const parentBlock = chain.getProposerHead(slot);
@@ -935,7 +893,10 @@ export function getValidatorApi(
935
893
  // support when it is implemented.
936
894
  const isBuildingOnFull = chain.forkChoice.shouldBuildOnFull(parentBlock, slot);
937
895
  const bidParentBlockHash = isBuildingOnFull ? parentBlock.executionPayloadBlockHash : parentBlock.parentBlockHash;
938
- const builderBid = chain.executionPayloadBidPool.getBestBid(slot, bidParentBlockHash, parentBlockRootHex);
896
+ const circuitBreakerActive = chain.builderCircuitBreaker.isActive(slot);
897
+ const builderBid = circuitBreakerActive
898
+ ? null
899
+ : chain.executionPayloadBidPool.getBestBid(slot, bidParentBlockHash, parentBlockRootHex);
939
900
 
940
901
  const logCtx = {
941
902
  slot,
@@ -943,6 +904,7 @@ export function getValidatorApi(
943
904
  parentBlockRoot: parentBlockRootHex,
944
905
  parentBlockHash: parentBlock.executionPayloadBlockHash,
945
906
  fork,
907
+ circuitBreakerActive,
946
908
  ...(builderBid !== null
947
909
  ? {
948
910
  bidValue: builderBid.message.value,
@@ -1032,7 +994,7 @@ export function getValidatorApi(
1032
994
  },
1033
995
 
1034
996
  async produceAttestationData({committeeIndex, slot}) {
1035
- notWhileSyncing();
997
+ notWhileSyncing(chain, sync.state);
1036
998
 
1037
999
  await waitForSlot(slot); // Must never request for a future slot > currentSlot
1038
1000
 
@@ -1113,13 +1075,13 @@ export function getValidatorApi(
1113
1075
  throw new ApiError(400, `producePayloadAttestationData is not supported before Gloas fork=${fork}`);
1114
1076
  }
1115
1077
 
1116
- notWhileSyncing();
1078
+ notWhileSyncing(chain, sync.state);
1117
1079
  await waitForSlot(slot);
1118
1080
 
1119
1081
  const block = chain.forkChoice.getCanonicalBlockAtSlot(slot);
1120
1082
  if (!block) {
1121
- // No block is seen at slot. Return 404 so vc can skip casting payload attestation.
1122
- throw new ApiError(404, `No canonical block found at slot=${slot}`);
1083
+ // No canonical block is seen at slot. Return 204 so vc can skip casting payload attestation.
1084
+ return {data: undefined, meta: {version: fork}, status: 204};
1123
1085
  }
1124
1086
 
1125
1087
  const payloadInput = chain.seenPayloadEnvelopeInputCache.get(block.blockRoot);
@@ -1198,7 +1160,7 @@ export function getValidatorApi(
1198
1160
  },
1199
1161
 
1200
1162
  async getProposerDuties({epoch}, _context, opts?: {v2?: boolean}) {
1201
- notWhileSyncing();
1163
+ notWhileSyncing(chain, sync.state);
1202
1164
 
1203
1165
  const currentEpoch = currentEpochWithDisparity();
1204
1166
  const nextEpoch = currentEpoch + 1;
@@ -1245,7 +1207,7 @@ export function getValidatorApi(
1245
1207
  // requested epoch is within that range, we can use the head state at current epoch
1246
1208
  state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.getDuties);
1247
1209
  } else {
1248
- const res = await getStateResponseWithRegen(chain, startSlot);
1210
+ const res = await getStateResponseWithRegen(chain, sync, startSlot);
1249
1211
 
1250
1212
  state = res.state instanceof Uint8Array ? chain.getHeadState().loadOtherState(res.state) : res.state;
1251
1213
 
@@ -1339,7 +1301,7 @@ export function getValidatorApi(
1339
1301
  },
1340
1302
 
1341
1303
  async getAttesterDuties({epoch, indices}) {
1342
- notWhileSyncing();
1304
+ notWhileSyncing(chain, sync.state);
1343
1305
 
1344
1306
  if (indices.length === 0) {
1345
1307
  throw new ApiError(400, "No validator to get attester duties");
@@ -1399,7 +1361,7 @@ export function getValidatorApi(
1399
1361
  },
1400
1362
 
1401
1363
  async getPtcDuties({epoch, indices}) {
1402
- notWhileSyncing();
1364
+ notWhileSyncing(chain, sync.state);
1403
1365
 
1404
1366
  if (indices.length === 0) {
1405
1367
  throw new ApiError(400, "No validator to get PTC duties");
@@ -1461,7 +1423,7 @@ export function getValidatorApi(
1461
1423
  * @param validatorIndices an array of the validator indices for which to obtain the duties.
1462
1424
  */
1463
1425
  async getSyncCommitteeDuties({epoch, indices}) {
1464
- notWhileSyncing();
1426
+ notWhileSyncing(chain, sync.state);
1465
1427
 
1466
1428
  if (indices.length === 0) {
1467
1429
  throw new ApiError(400, "No validator to get attester duties");
@@ -1508,7 +1470,7 @@ export function getValidatorApi(
1508
1470
  },
1509
1471
 
1510
1472
  async getAggregatedAttestationV2({attestationDataRoot, slot, committeeIndex}) {
1511
- notWhileSyncing();
1473
+ notWhileSyncing(chain, sync.state);
1512
1474
 
1513
1475
  await waitForSlot(slot); // Must never request for a future slot > currentSlot
1514
1476
 
@@ -1531,7 +1493,7 @@ export function getValidatorApi(
1531
1493
  },
1532
1494
 
1533
1495
  async publishAggregateAndProofsV2({signedAggregateAndProofs}) {
1534
- notWhileSyncing();
1496
+ notWhileSyncing(chain, sync.state);
1535
1497
 
1536
1498
  const seenTimestampSec = Date.now() / 1000;
1537
1499
  const failures: FailureList = [];
@@ -1592,7 +1554,7 @@ export function getValidatorApi(
1592
1554
  * https://github.com/ethereum/beacon-APIs/pull/137
1593
1555
  */
1594
1556
  async publishContributionAndProofs({contributionAndProofs}) {
1595
- notWhileSyncing();
1557
+ notWhileSyncing(chain, sync.state);
1596
1558
 
1597
1559
  const failures: FailureList = [];
1598
1560
 
@@ -1641,7 +1603,7 @@ export function getValidatorApi(
1641
1603
  },
1642
1604
 
1643
1605
  async prepareBeaconCommitteeSubnet({subscriptions}) {
1644
- notWhileSyncing();
1606
+ notWhileSyncing(chain, sync.state);
1645
1607
 
1646
1608
  await network.prepareBeaconCommitteeSubnets(
1647
1609
  subscriptions.map(({validatorIndex, slot, isAggregator, committeesAtSlot, committeeIndex}) => ({
@@ -1674,7 +1636,7 @@ export function getValidatorApi(
1674
1636
  * https://github.com/ethereum/beacon-APIs/pull/136
1675
1637
  */
1676
1638
  async prepareSyncCommitteeSubnets({subscriptions}) {
1677
- notWhileSyncing();
1639
+ notWhileSyncing(chain, sync.state);
1678
1640
 
1679
1641
  // A `validatorIndex` can be in multiple subnets, so compute the CommitteeSubscription with double for loop
1680
1642
  const subs: CommitteeSubscription[] = [];
@@ -1771,6 +1733,46 @@ export function getValidatorApi(
1771
1733
  });
1772
1734
  },
1773
1735
 
1736
+ async submitProposerPreferences({signedProposerPreferences}) {
1737
+ const failures: FailureList = [];
1738
+
1739
+ await Promise.all(
1740
+ signedProposerPreferences.map(async (signed, i) => {
1741
+ try {
1742
+ await validateGossipProposerPreferences(chain, signed);
1743
+
1744
+ chain.proposerPreferencesPool.add(signed);
1745
+ await network.publishProposerPreferences(signed);
1746
+ chain.emitter.emit(routes.events.EventType.proposerPreferences, {
1747
+ version: config.getForkName(signed.message.proposalSlot),
1748
+ data: signed,
1749
+ });
1750
+ } catch (e) {
1751
+ const logCtx = {
1752
+ slot: signed.message.proposalSlot,
1753
+ validatorIndex: signed.message.validatorIndex,
1754
+ dependentRoot: toRootHex(signed.message.dependentRoot),
1755
+ };
1756
+
1757
+ if (e instanceof ProposerPreferencesError && e.type.code === ProposerPreferencesErrorCode.ALREADY_KNOWN) {
1758
+ logger.debug("Ignoring known signed proposer preferences", logCtx);
1759
+ return;
1760
+ }
1761
+
1762
+ failures.push({index: i, message: (e as Error).message});
1763
+ logger.verbose(`Error on submitProposerPreferences [${i}]`, logCtx, e as Error);
1764
+ if (e instanceof ProposerPreferencesError && e.action === GossipAction.REJECT) {
1765
+ chain.persistInvalidSszValue(ssz.gloas.SignedProposerPreferences, signed, "api_reject");
1766
+ }
1767
+ }
1768
+ })
1769
+ );
1770
+
1771
+ if (failures.length > 0) {
1772
+ throw new IndexedError("Error processing signed proposer preferences", failures);
1773
+ }
1774
+ },
1775
+
1774
1776
  async getExecutionPayloadEnvelope({slot, beaconBlockRoot}) {
1775
1777
  const fork = config.getForkName(slot);
1776
1778
 
@@ -1778,7 +1780,7 @@ export function getValidatorApi(
1778
1780
  throw new ApiError(400, `getExecutionPayloadEnvelope not supported for pre-gloas fork=${fork}`);
1779
1781
  }
1780
1782
 
1781
- notWhileSyncing();
1783
+ notWhileSyncing(chain, sync.state);
1782
1784
  await waitForSlot(slot);
1783
1785
 
1784
1786
  const blockRootHex = toRootHex(beaconBlockRoot);
@@ -3,7 +3,9 @@ import {fastifyCors} from "@fastify/cors";
3
3
  import {FastifyError, FastifyInstance, FastifyRequest, errorCodes, fastify} from "fastify";
4
4
  import {parse as parseQueryString} from "qs";
5
5
  import {addSszContentTypeParser} from "@lodestar/api/server";
6
+ import {NUMBER_OF_COLUMNS} from "@lodestar/params";
6
7
  import {ErrorAborted, Gauge, Histogram, Logger} from "@lodestar/utils";
8
+ import {GossipActionError} from "../../chain/errors/gossipValidation.js";
7
9
  import {isLocalhostIP} from "../../util/ip.js";
8
10
  import {ApiError, FailureList, IndexedError, NodeIsSyncing} from "../impl/errors.js";
9
11
  import {HttpActiveSocketsTracker, SocketMetrics} from "./activeSockets.js";
@@ -55,6 +57,13 @@ const INVALID_MEDIA_TYPE_CODE = errorCodes.FST_ERR_CTP_INVALID_MEDIA_TYPE().code
55
57
  */
56
58
  const SCHEMA_VALIDATION_ERROR_CODE = errorCodes.FST_ERR_VALIDATION().code;
57
59
 
60
+ /**
61
+ * Cap for array query params, set to the largest array any beacon-API query can carry:
62
+ * a full data-column custody set (`getDebugDataColumnSidecars` `indices`). `qs` turns
63
+ * longer arrays into an object, which then fails schema validation.
64
+ */
65
+ const QUERY_STRING_ARRAY_LIMIT = NUMBER_OF_COLUMNS;
66
+
58
67
  /**
59
68
  * REST API powered by `fastify` server.
60
69
  */
@@ -82,6 +91,7 @@ export class RestApiServer {
82
91
  // be OpenAPI spec compliant and results are inconsistent, see https://github.com/ljharb/qs/issues/331.
83
92
  // The schema validation will catch this and throw an error as parsed query string results in an object.
84
93
  parseArrays: false,
94
+ arrayLimit: QUERY_STRING_ARRAY_LIMIT,
85
95
  }),
86
96
  },
87
97
  bodyLimit: opts.bodyLimit,
@@ -112,8 +122,8 @@ export class RestApiServer {
112
122
  };
113
123
  void res.status(err.statusCode).send(payload);
114
124
  } else {
115
- // Convert our custom ApiError into status code
116
- const statusCode = err instanceof ApiError ? err.statusCode : 500;
125
+ // Convert known request errors into status codes
126
+ const statusCode = err instanceof ApiError ? err.statusCode : err instanceof GossipActionError ? 400 : 500;
117
127
  const payload: ErrorResponse = {code: statusCode, message: err.message, stacktraces};
118
128
  void res.status(statusCode).send(payload);
119
129
  }
@@ -161,7 +171,11 @@ export class RestApiServer {
161
171
 
162
172
  const operationId = getOperationId(req);
163
173
 
164
- if (err instanceof ApiError || [INVALID_MEDIA_TYPE_CODE, SCHEMA_VALIDATION_ERROR_CODE].includes(err.code)) {
174
+ if (
175
+ err instanceof ApiError ||
176
+ err instanceof GossipActionError ||
177
+ [INVALID_MEDIA_TYPE_CODE, SCHEMA_VALIDATION_ERROR_CODE].includes(err.code)
178
+ ) {
165
179
  this.logger.warn(`Req ${req.id} ${operationId} failed`, {reason: err.message});
166
180
  } else {
167
181
  this.logger.error(`Req ${req.id} ${operationId} error`, {}, err);
@@ -2,7 +2,6 @@ import {BitArray} from "@chainsafe/ssz";
2
2
  import {routes} from "@lodestar/api";
3
3
  import {
4
4
  AncestorStatus,
5
- EpochDifference,
6
5
  ExecutionStatus,
7
6
  ForkChoiceError,
8
7
  ForkChoiceErrorCode,
@@ -21,7 +20,6 @@ import {
21
20
  IBeaconStateView,
22
21
  RootCache,
23
22
  computeEpochAtSlot,
24
- computeStartSlotAtEpoch,
25
23
  computeTimeAtSlot,
26
24
  isStartSlotOfEpoch,
27
25
  isStatePostAltair,
@@ -124,18 +122,13 @@ export async function importBlock(
124
122
  executionStatus = parentBlock.executionStatus;
125
123
  }
126
124
 
127
- // getBeaconProposerOrNull will return null if head state is more than one epoch away
128
- // from block slot. We skip proposer boost canonical check as we cannot determine the canonical proposer
129
- const expectedProposerIndex: number | null = this.getHeadState().getBeaconProposerOrNull(blockSlot);
130
-
131
125
  const blockSummary = this.forkChoice.onBlock(
132
126
  block.message,
133
127
  postState,
134
128
  blockDelaySec,
135
129
  currentSlot,
136
130
  executionStatus,
137
- dataAvailabilityStatus,
138
- expectedProposerIndex
131
+ dataAvailabilityStatus
139
132
  );
140
133
 
141
134
  // This adds the state necessary to process the next block
@@ -306,21 +299,6 @@ export async function importBlock(
306
299
  // Set head state as strong reference
307
300
  this.regen.updateHeadState(newHead, postState);
308
301
 
309
- try {
310
- this.emitter.emit(routes.events.EventType.head, {
311
- block: newHead.blockRoot,
312
- epochTransition: computeStartSlotAtEpoch(computeEpochAtSlot(newHead.slot)) === newHead.slot,
313
- slot: newHead.slot,
314
- state: newHead.stateRoot,
315
- previousDutyDependentRoot: this.forkChoice.getDependentRoot(newHead, EpochDifference.previous),
316
- currentDutyDependentRoot: this.forkChoice.getDependentRoot(newHead, EpochDifference.current),
317
- executionOptimistic: isOptimisticBlock(newHead),
318
- });
319
- } catch (e) {
320
- // getDependentRoot() may fail with error: "No block for root" as we can see in holesky non-finality issue
321
- this.logger.debug("Error emitting head event", {slot: newHead.slot, root: newHead.blockRoot}, e as Error);
322
- }
323
-
324
302
  const delaySec = this.clock.secFromSlot(newHead.slot);
325
303
  this.logger.verbose("New chain head", {
326
304
  slot: newHead.slot,
@@ -86,11 +86,22 @@ export function assertLinearChainSegment(
86
86
  // Maybe the previous slot's FULL envelope was orphaned — try falling back.
87
87
  // If even prevExecHash doesn't match, the segment is non-linear.
88
88
  if (bidParentHash !== prevExecHash) {
89
- throw new BlockError(block, {
90
- code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN,
91
- parentRoot: toRootHex(block.message.parentRoot),
92
- parentBlockHash: bidParentHash,
93
- });
89
+ // i === 0 compares against the seeded fork-choice parent (segment boundary); i > 0
90
+ // compares against the in-segment predecessor's payload (broken link inside the segment).
91
+ throw new BlockError(
92
+ block,
93
+ i === 0
94
+ ? {
95
+ code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN,
96
+ parentRoot: toRootHex(block.message.parentRoot),
97
+ parentBlockHash: bidParentHash,
98
+ }
99
+ : {
100
+ code: BlockErrorCode.NON_LINEAR_PAYLOAD_ROOTS,
101
+ parentBlockHash: bidParentHash,
102
+ expectedBlockHash: currentExecHash,
103
+ }
104
+ );
94
105
  }
95
106
  if (lastFullSlot !== null && payloadEnvelopes !== null) {
96
107
  const orphanedInput = payloadEnvelopes.get(lastFullSlot);
@@ -201,7 +201,7 @@ export async function verifyBlockExecutionPayload(
201
201
  invalidateFromParentBlockHash: toRootHex(executionPayloadEnabled.parentHash),
202
202
  };
203
203
  const execError = new BlockError(block, {
204
- code: BlockErrorCode.EXECUTION_ENGINE_ERROR,
204
+ code: BlockErrorCode.EXECUTION_ENGINE_INVALID,
205
205
  execStatus: execResult.status,
206
206
  errorMessage: execResult.validationError ?? "",
207
207
  });
@@ -94,7 +94,7 @@ export function verifyBlocksSanityChecks(
94
94
  const parentRoot = toRootHex(block.message.parentRoot);
95
95
  const parentBlockDefaultStatus = chain.forkChoice.getBlockHexDefaultStatus(parentRoot);
96
96
  if (!parentBlockDefaultStatus) {
97
- throw new BlockError(block, {code: BlockErrorCode.PARENT_UNKNOWN, parentRoot});
97
+ throw new BlockError(block, {code: BlockErrorCode.PARENT_BLOCK_UNKNOWN, parentRoot});
98
98
  }
99
99
 
100
100
  parentBlock = parentBlockDefaultStatus;
@@ -100,8 +100,8 @@ export function verifyExecutionPayloadEnvelope(
100
100
  }
101
101
 
102
102
  // Verify consistency with expected withdrawals
103
- const payloadWithdrawalsRoot = ssz.capella.Withdrawals.hashTreeRoot(payload.withdrawals);
104
- const expectedWithdrawalsRoot = ssz.capella.Withdrawals.hashTreeRoot(state.payloadExpectedWithdrawals);
103
+ const payloadWithdrawalsRoot = ssz.gloas.Withdrawals.hashTreeRoot(payload.withdrawals);
104
+ const expectedWithdrawalsRoot = ssz.gloas.Withdrawals.hashTreeRoot(state.payloadExpectedWithdrawals);
105
105
  if (!byteArrayEquals(payloadWithdrawalsRoot, expectedWithdrawalsRoot)) {
106
106
  throw new Error(
107
107
  `Withdrawals mismatch between payload and expected payload=${toRootHex(payloadWithdrawalsRoot)} expected=${toRootHex(expectedWithdrawalsRoot)}`
@@ -0,0 +1,84 @@
1
+ import {IForkChoice} from "@lodestar/fork-choice";
2
+ import {Slot} from "@lodestar/types";
3
+ import {Logger} from "@lodestar/utils";
4
+ import {getFaultInspectionParams} from "../execution/builder/http.js";
5
+ import {Metrics} from "../metrics/index.js";
6
+
7
+ export type BuilderCircuitBreakerOpts = {
8
+ faultInspectionWindow?: number;
9
+ allowedFaults?: number;
10
+ };
11
+
12
+ export type BuilderCircuitBreakerModules = {
13
+ forkChoice: IForkChoice;
14
+ logger: Logger;
15
+ metrics: Metrics | null;
16
+ };
17
+
18
+ /**
19
+ * Post-gloas circuit breaker for builder bids. The beacon block is produced by the proposer
20
+ * regardless of bid source, so missed blocks are not a useful builder health signal. Instead
21
+ * count blocks whose payload was never revealed and stop selecting builder bids while the
22
+ * non-reveal rate in the fault inspection window is too high.
23
+ */
24
+ export class BuilderCircuitBreaker {
25
+ readonly faultInspectionWindow: number;
26
+ readonly allowedFaults: number;
27
+
28
+ private active = false;
29
+ private lastUpdatedSlot = -1;
30
+
31
+ constructor(
32
+ opts: BuilderCircuitBreakerOpts,
33
+ private readonly modules: BuilderCircuitBreakerModules
34
+ ) {
35
+ const {faultInspectionWindow, allowedFaults} = getFaultInspectionParams(opts);
36
+ this.faultInspectionWindow = faultInspectionWindow;
37
+ this.allowedFaults = allowedFaults;
38
+ }
39
+
40
+ /** Whether builder bids must be ignored for a block produced at clockSlot */
41
+ isActive(clockSlot: Slot): boolean {
42
+ this.update(clockSlot);
43
+ return this.active;
44
+ }
45
+
46
+ update(clockSlot: Slot): void {
47
+ if (clockSlot <= this.lastUpdatedSlot) {
48
+ return;
49
+ }
50
+ this.lastUpdatedSlot = clockSlot;
51
+
52
+ // Exclude clockSlot itself, its payload reveal may still be in flight
53
+ const {blocksPresent, payloadsRevealed} = this.modules.forkChoice.getPayloadRevealCounts(
54
+ Math.max(clockSlot - this.faultInspectionWindow, 0),
55
+ clockSlot - 1
56
+ );
57
+ const faults = blocksPresent - payloadsRevealed;
58
+
59
+ const wasActive = this.active;
60
+ // Scale the fault budget by blocks present so sparse windows still trigger on high non-reveal rates
61
+ this.active = faults * this.faultInspectionWindow > this.allowedFaults * blocksPresent;
62
+
63
+ this.modules.metrics?.builderCircuitBreaker.active.set(this.active ? 1 : 0);
64
+ this.modules.metrics?.builderCircuitBreaker.faults.set(faults);
65
+ this.modules.metrics?.builderCircuitBreaker.blocksPresent.set(blocksPresent);
66
+ this.modules.metrics?.builderCircuitBreaker.payloadsRevealed.set(payloadsRevealed);
67
+
68
+ const logCtx = {
69
+ blocksPresent,
70
+ faults,
71
+ faultInspectionWindow: this.faultInspectionWindow,
72
+ allowedFaults: this.allowedFaults,
73
+ };
74
+ if (this.active !== wasActive) {
75
+ if (this.active) {
76
+ this.modules.logger.warn("Builder circuit breaker activated, ignoring builder bids", logCtx);
77
+ } else {
78
+ this.modules.logger.info("Builder circuit breaker deactivated", logCtx);
79
+ }
80
+ } else {
81
+ this.modules.logger.verbose("Builder circuit breaker status", {active: this.active, ...logCtx});
82
+ }
83
+ }
84
+ }
@@ -1,8 +1,16 @@
1
1
  import path from "node:path";
2
2
  import {PrivateKey} from "@libp2p/interface";
3
3
  import {Type} from "@chainsafe/ssz";
4
+ import {routes} from "@lodestar/api";
4
5
  import {BeaconConfig} from "@lodestar/config";
5
- import {CheckpointWithHex, ForkChoiceStateGetter, IForkChoice, ProtoBlock, UpdateHeadOpt} from "@lodestar/fork-choice";
6
+ import {
7
+ CheckpointWithHex,
8
+ EpochDifference,
9
+ ForkChoiceStateGetter,
10
+ IForkChoice,
11
+ ProtoBlock,
12
+ UpdateHeadOpt,
13
+ } from "@lodestar/fork-choice";
6
14
  import {LoggerNode} from "@lodestar/logger/node";
7
15
  import {
8
16
  EFFECTIVE_BALANCE_INCREMENT,
@@ -75,6 +83,7 @@ import {ImportPayloadOpts} from "./blocks/types.js";
75
83
  import {persistBlockInput} from "./blocks/writeBlockInputToDb.js";
76
84
  import {persistPayloadEnvelopeInput} from "./blocks/writePayloadEnvelopeInputToDb.js";
77
85
  import {BlsMultiThreadWorkerPool, BlsSingleThreadVerifier, IBlsVerifier} from "./bls/index.js";
86
+ import {BuilderCircuitBreaker} from "./builderCircuitBreaker.js";
78
87
  import {ColumnReconstructionTracker} from "./ColumnReconstructionTracker.js";
79
88
  import {ChainEvent, ChainEventEmitter} from "./emitter.js";
80
89
  import {ForkchoiceCaller, initializeForkChoice} from "./forkChoice/index.js";
@@ -151,6 +160,7 @@ export class BeaconChain implements IBeaconChain {
151
160
  readonly genesisValidatorsRoot: Root;
152
161
  readonly executionEngine: IExecutionEngine;
153
162
  readonly executionBuilder?: IExecutionBuilder;
163
+ readonly builderCircuitBreaker: BuilderCircuitBreaker;
154
164
  // Expose config for convenience in modularized functions
155
165
  readonly config: BeaconConfig;
156
166
  readonly custodyConfig: CustodyConfig;
@@ -425,6 +435,11 @@ export class BeaconChain implements IBeaconChain {
425
435
 
426
436
  this.forkChoice = forkChoice;
427
437
 
438
+ this.builderCircuitBreaker = new BuilderCircuitBreaker(
439
+ {faultInspectionWindow: opts.faultInspectionWindow, allowedFaults: opts.allowedFaults},
440
+ {forkChoice, logger, metrics}
441
+ );
442
+
428
443
  this.seenPayloadEnvelopeInputCache = new SeenPayloadEnvelopeInput({
429
444
  config,
430
445
  clock,
@@ -1172,7 +1187,27 @@ export class BeaconChain implements IBeaconChain {
1172
1187
  const timer = this.metrics?.forkChoice.findHead.startTimer({caller});
1173
1188
 
1174
1189
  try {
1175
- return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetCanonicalHead}).head;
1190
+ const prevHead = this.forkChoice.getHead();
1191
+ const head = this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetCanonicalHead}).head;
1192
+
1193
+ if (head.blockRoot !== prevHead.blockRoot) {
1194
+ try {
1195
+ this.emitter.emit(routes.events.EventType.head, {
1196
+ block: head.blockRoot,
1197
+ epochTransition: computeStartSlotAtEpoch(computeEpochAtSlot(head.slot)) === head.slot,
1198
+ slot: head.slot,
1199
+ state: head.stateRoot,
1200
+ previousDutyDependentRoot: this.forkChoice.getDependentRoot(head, EpochDifference.previous),
1201
+ currentDutyDependentRoot: this.forkChoice.getDependentRoot(head, EpochDifference.current),
1202
+ executionOptimistic: isOptimisticBlock(head),
1203
+ });
1204
+ } catch (e) {
1205
+ // getDependentRoot() may fail with error: "No block for root" as we can see in holesky non-finality issue
1206
+ this.logger.debug("Error emitting head event", {slot: head.slot, root: head.blockRoot}, e as Error);
1207
+ }
1208
+ }
1209
+
1210
+ return head;
1176
1211
  } catch (e) {
1177
1212
  this.metrics?.forkChoice.errors.inc({entrypoint: UpdateHeadOpt.GetCanonicalHead});
1178
1213
  throw e;
@@ -1202,6 +1237,7 @@ export class BeaconChain implements IBeaconChain {
1202
1237
  const secFromSlot = this.clock.secFromSlot(slot);
1203
1238
 
1204
1239
  try {
1240
+ // Do not emit head event here, when proposing we rely on the one emitted when importing our own block
1205
1241
  const {head, isHeadTimely, notReorgedReason} = this.forkChoice.updateAndGetHead({
1206
1242
  mode: UpdateHeadOpt.GetProposerHead,
1207
1243
  secFromSlot,