@lodestar/beacon-node 1.45.0-dev.85227cfbe5 → 1.45.0-dev.88c017b12c

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 (218) 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 +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 +21 -56
  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 +831 -226
  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 +16 -6
  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/network/reqresp/handlers/index.d.ts.map +1 -1
  158. package/lib/network/reqresp/handlers/index.js +20 -11
  159. package/lib/network/reqresp/handlers/index.js.map +1 -1
  160. package/lib/sync/sync.d.ts.map +1 -1
  161. package/lib/sync/sync.js +7 -0
  162. package/lib/sync/sync.js.map +1 -1
  163. package/lib/sync/unknownBlock.d.ts.map +1 -1
  164. package/lib/sync/unknownBlock.js +7 -2
  165. package/lib/sync/unknownBlock.js.map +1 -1
  166. package/package.json +23 -21
  167. package/src/api/impl/beacon/blocks/index.ts +3 -2
  168. package/src/api/impl/beacon/index.ts +1 -1
  169. package/src/api/impl/beacon/pool/index.ts +5 -0
  170. package/src/api/impl/beacon/state/index.ts +3 -2
  171. package/src/api/impl/beacon/state/utils.ts +11 -0
  172. package/src/api/impl/debug/index.ts +3 -2
  173. package/src/api/impl/lodestar/index.ts +2 -2
  174. package/src/api/impl/proof/index.ts +2 -2
  175. package/src/api/impl/utils.ts +51 -1
  176. package/src/api/impl/validator/index.ts +21 -62
  177. package/src/api/rest/base.ts +17 -3
  178. package/src/chain/blocks/importBlock.ts +1 -23
  179. package/src/chain/blocks/utils/chainSegment.ts +16 -5
  180. package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +1 -1
  181. package/src/chain/blocks/verifyBlocksSanityChecks.ts +1 -1
  182. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -2
  183. package/src/chain/builderCircuitBreaker.ts +84 -0
  184. package/src/chain/chain.ts +38 -2
  185. package/src/chain/errors/blockError.ts +44 -7
  186. package/src/chain/errors/executionPayloadEnvelope.ts +9 -0
  187. package/src/chain/interface.ts +2 -0
  188. package/src/chain/lightClient/index.ts +12 -7
  189. package/src/chain/lightClient/proofs.ts +36 -1
  190. package/src/chain/lightClient/types.ts +3 -1
  191. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  192. package/src/chain/opPools/proposerPreferencesPool.ts +1 -1
  193. package/src/chain/options.ts +4 -0
  194. package/src/chain/prepareNextSlot.ts +11 -7
  195. package/src/chain/validation/aggregateAndProof.ts +12 -6
  196. package/src/chain/validation/attestation.ts +12 -6
  197. package/src/chain/validation/block.ts +69 -4
  198. package/src/chain/validation/executionPayloadBid.ts +16 -11
  199. package/src/chain/validation/executionPayloadEnvelope.ts +37 -0
  200. package/src/chain/validation/proposerPreferences.ts +3 -2
  201. package/src/chain/validation/signatureSets/aggregateAndProof.ts +3 -3
  202. package/src/db/repositories/lightclientSyncCommitteeWitness.ts +57 -9
  203. package/src/execution/builder/http.ts +30 -17
  204. package/src/metrics/metrics/beacon.ts +19 -0
  205. package/src/network/core/metrics.ts +94 -0
  206. package/src/network/discv5/utils.ts +11 -3
  207. package/src/network/gossip/encoding.ts +20 -7
  208. package/src/network/gossip/gossipsub.ts +9 -3
  209. package/src/network/gossip/topic.ts +58 -4
  210. package/src/network/peers/peerManager.ts +11 -0
  211. package/src/network/peers/score/score.ts +8 -1
  212. package/src/network/peers/score/store.ts +8 -1
  213. package/src/network/peers/utils/prioritizePeers.ts +15 -0
  214. package/src/network/processor/gossipHandlers.ts +17 -6
  215. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +4 -2
  216. package/src/network/reqresp/handlers/index.ts +21 -12
  217. package/src/sync/sync.ts +10 -0
  218. package/src/sync/unknownBlock.ts +8 -2
@@ -82,7 +82,6 @@ import {ZERO_HASH} from "../../../constants/index.js";
82
82
  import {BuilderStatus, NoBidReceived} from "../../../execution/builder/http.js";
83
83
  import {validateGossipFnRetryUnknownRoot} from "../../../network/processor/gossipHandlers.js";
84
84
  import {CommitteeSubscription} from "../../../network/subnets/index.js";
85
- import {SyncState} from "../../../sync/index.js";
86
85
  import {callInNextEventLoop} from "../../../util/eventLoop.js";
87
86
  import {isOptimisticBlock} from "../../../util/forkChoice.js";
88
87
  import {getBlockGraffiti, toGraffitiBytes} from "../../../util/graffiti.js";
@@ -91,23 +90,9 @@ import {ApiOptions} from "../../options.js";
91
90
  import {getStateResponseWithRegen} from "../beacon/state/utils.js";
92
91
  import {ApiError, FailureList, IndexedError, NodeIsSyncing, OnlySupportedByDVT} from "../errors.js";
93
92
  import {ApiModules} from "../types.js";
93
+ import {notWhileSyncing} from "../utils.js";
94
94
  import {computeSubnetForCommitteesAtSlot, getPubkeysForIndices, selectBlockProductionSource} from "./utils.js";
95
95
 
96
- /**
97
- * If the node is within this many epochs from the head, we declare it to be synced regardless of
98
- * the network sync state.
99
- *
100
- * This helps prevent attacks where nodes can convince us that we're syncing some non-existent
101
- * finalized head.
102
- *
103
- * TODO: Lighthouse uses 8 for the attack described above. However, 8 kills Lodestar since validators
104
- * can trigger regen to fast-forward head state 8 epochs to be immediately invalidated as sync sets
105
- * a new head. Then the checkpoint state cache grows unbounded with very different states (because
106
- * they are 8 epochs apart) and causes an OOM. Research a proper solution once regen and the state
107
- * caches are better.
108
- */
109
- export const SYNC_TOLERANCE_EPOCHS = 1;
110
-
111
96
  /**
112
97
  * Cutoff time to wait from start of the slot for execution and builder block production apis to resolve.
113
98
  * Post this time, race execution and builder to pick whatever resolves first.
@@ -341,36 +326,6 @@ export function getValidatorApi(
341
326
  return null;
342
327
  }
343
328
 
344
- /**
345
- * Reject any request while the node is syncing
346
- */
347
- function notWhileSyncing(): void {
348
- // Consider node synced before or close to genesis
349
- if (chain.clock.currentSlot < SLOTS_PER_EPOCH) {
350
- return;
351
- }
352
-
353
- const syncState = sync.state;
354
- switch (syncState) {
355
- case SyncState.SyncingFinalized:
356
- case SyncState.SyncingHead: {
357
- const currentSlot = chain.clock.currentSlot;
358
- const headSlot = chain.forkChoice.getHead().slot;
359
- if (currentSlot - headSlot > SYNC_TOLERANCE_EPOCHS * SLOTS_PER_EPOCH) {
360
- throw new NodeIsSyncing(`headSlot ${headSlot} currentSlot ${currentSlot}`);
361
- }
362
-
363
- return;
364
- }
365
-
366
- case SyncState.Synced:
367
- return;
368
-
369
- case SyncState.Stalled:
370
- throw new NodeIsSyncing("waiting for peers");
371
- }
372
- }
373
-
374
329
  /**
375
330
  * Post merge, the CL and EL could be out of step in the sync, and could result in
376
331
  * Syncing status of the chain head. To be precise:
@@ -573,7 +528,7 @@ export function getValidatorApi(
573
528
  builderBoostFactor?: bigint,
574
529
  {feeRecipient, builderSelection, strictFeeRecipientCheck}: routes.validator.ExtraProduceBlockOpts = {}
575
530
  ): Promise<ProduceBlindedBlockOrBlockContentsRes> {
576
- notWhileSyncing();
531
+ notWhileSyncing(chain, sync.state);
577
532
  await waitForSlot(slot); // Must never request for a future slot > currentSlot
578
533
 
579
534
  const parentBlock = chain.getProposerHead(slot);
@@ -917,7 +872,7 @@ export function getValidatorApi(
917
872
  throw new ApiError(400, `produceBlockV4 not supported for pre-gloas fork=${fork}`);
918
873
  }
919
874
 
920
- notWhileSyncing();
875
+ notWhileSyncing(chain, sync.state);
921
876
  await waitForSlot(slot);
922
877
 
923
878
  const parentBlock = chain.getProposerHead(slot);
@@ -938,7 +893,10 @@ export function getValidatorApi(
938
893
  // support when it is implemented.
939
894
  const isBuildingOnFull = chain.forkChoice.shouldBuildOnFull(parentBlock, slot);
940
895
  const bidParentBlockHash = isBuildingOnFull ? parentBlock.executionPayloadBlockHash : parentBlock.parentBlockHash;
941
- 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);
942
900
 
943
901
  const logCtx = {
944
902
  slot,
@@ -946,6 +904,7 @@ export function getValidatorApi(
946
904
  parentBlockRoot: parentBlockRootHex,
947
905
  parentBlockHash: parentBlock.executionPayloadBlockHash,
948
906
  fork,
907
+ circuitBreakerActive,
949
908
  ...(builderBid !== null
950
909
  ? {
951
910
  bidValue: builderBid.message.value,
@@ -1035,7 +994,7 @@ export function getValidatorApi(
1035
994
  },
1036
995
 
1037
996
  async produceAttestationData({committeeIndex, slot}) {
1038
- notWhileSyncing();
997
+ notWhileSyncing(chain, sync.state);
1039
998
 
1040
999
  await waitForSlot(slot); // Must never request for a future slot > currentSlot
1041
1000
 
@@ -1116,7 +1075,7 @@ export function getValidatorApi(
1116
1075
  throw new ApiError(400, `producePayloadAttestationData is not supported before Gloas fork=${fork}`);
1117
1076
  }
1118
1077
 
1119
- notWhileSyncing();
1078
+ notWhileSyncing(chain, sync.state);
1120
1079
  await waitForSlot(slot);
1121
1080
 
1122
1081
  const block = chain.forkChoice.getCanonicalBlockAtSlot(slot);
@@ -1201,7 +1160,7 @@ export function getValidatorApi(
1201
1160
  },
1202
1161
 
1203
1162
  async getProposerDuties({epoch}, _context, opts?: {v2?: boolean}) {
1204
- notWhileSyncing();
1163
+ notWhileSyncing(chain, sync.state);
1205
1164
 
1206
1165
  const currentEpoch = currentEpochWithDisparity();
1207
1166
  const nextEpoch = currentEpoch + 1;
@@ -1248,7 +1207,7 @@ export function getValidatorApi(
1248
1207
  // requested epoch is within that range, we can use the head state at current epoch
1249
1208
  state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.getDuties);
1250
1209
  } else {
1251
- const res = await getStateResponseWithRegen(chain, startSlot);
1210
+ const res = await getStateResponseWithRegen(chain, sync, startSlot);
1252
1211
 
1253
1212
  state = res.state instanceof Uint8Array ? chain.getHeadState().loadOtherState(res.state) : res.state;
1254
1213
 
@@ -1342,7 +1301,7 @@ export function getValidatorApi(
1342
1301
  },
1343
1302
 
1344
1303
  async getAttesterDuties({epoch, indices}) {
1345
- notWhileSyncing();
1304
+ notWhileSyncing(chain, sync.state);
1346
1305
 
1347
1306
  if (indices.length === 0) {
1348
1307
  throw new ApiError(400, "No validator to get attester duties");
@@ -1402,7 +1361,7 @@ export function getValidatorApi(
1402
1361
  },
1403
1362
 
1404
1363
  async getPtcDuties({epoch, indices}) {
1405
- notWhileSyncing();
1364
+ notWhileSyncing(chain, sync.state);
1406
1365
 
1407
1366
  if (indices.length === 0) {
1408
1367
  throw new ApiError(400, "No validator to get PTC duties");
@@ -1464,7 +1423,7 @@ export function getValidatorApi(
1464
1423
  * @param validatorIndices an array of the validator indices for which to obtain the duties.
1465
1424
  */
1466
1425
  async getSyncCommitteeDuties({epoch, indices}) {
1467
- notWhileSyncing();
1426
+ notWhileSyncing(chain, sync.state);
1468
1427
 
1469
1428
  if (indices.length === 0) {
1470
1429
  throw new ApiError(400, "No validator to get attester duties");
@@ -1511,7 +1470,7 @@ export function getValidatorApi(
1511
1470
  },
1512
1471
 
1513
1472
  async getAggregatedAttestationV2({attestationDataRoot, slot, committeeIndex}) {
1514
- notWhileSyncing();
1473
+ notWhileSyncing(chain, sync.state);
1515
1474
 
1516
1475
  await waitForSlot(slot); // Must never request for a future slot > currentSlot
1517
1476
 
@@ -1534,7 +1493,7 @@ export function getValidatorApi(
1534
1493
  },
1535
1494
 
1536
1495
  async publishAggregateAndProofsV2({signedAggregateAndProofs}) {
1537
- notWhileSyncing();
1496
+ notWhileSyncing(chain, sync.state);
1538
1497
 
1539
1498
  const seenTimestampSec = Date.now() / 1000;
1540
1499
  const failures: FailureList = [];
@@ -1595,7 +1554,7 @@ export function getValidatorApi(
1595
1554
  * https://github.com/ethereum/beacon-APIs/pull/137
1596
1555
  */
1597
1556
  async publishContributionAndProofs({contributionAndProofs}) {
1598
- notWhileSyncing();
1557
+ notWhileSyncing(chain, sync.state);
1599
1558
 
1600
1559
  const failures: FailureList = [];
1601
1560
 
@@ -1644,7 +1603,7 @@ export function getValidatorApi(
1644
1603
  },
1645
1604
 
1646
1605
  async prepareBeaconCommitteeSubnet({subscriptions}) {
1647
- notWhileSyncing();
1606
+ notWhileSyncing(chain, sync.state);
1648
1607
 
1649
1608
  await network.prepareBeaconCommitteeSubnets(
1650
1609
  subscriptions.map(({validatorIndex, slot, isAggregator, committeesAtSlot, committeeIndex}) => ({
@@ -1677,7 +1636,7 @@ export function getValidatorApi(
1677
1636
  * https://github.com/ethereum/beacon-APIs/pull/136
1678
1637
  */
1679
1638
  async prepareSyncCommitteeSubnets({subscriptions}) {
1680
- notWhileSyncing();
1639
+ notWhileSyncing(chain, sync.state);
1681
1640
 
1682
1641
  // A `validatorIndex` can be in multiple subnets, so compute the CommitteeSubscription with double for loop
1683
1642
  const subs: CommitteeSubscription[] = [];
@@ -1821,7 +1780,7 @@ export function getValidatorApi(
1821
1780
  throw new ApiError(400, `getExecutionPayloadEnvelope not supported for pre-gloas fork=${fork}`);
1822
1781
  }
1823
1782
 
1824
- notWhileSyncing();
1783
+ notWhileSyncing(chain, sync.state);
1825
1784
  await waitForSlot(slot);
1826
1785
 
1827
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,
@@ -8,8 +8,10 @@ import {GossipActionError} from "./gossipValidation.js";
8
8
  export enum BlockErrorCode {
9
9
  /** The prestate cannot be fetched */
10
10
  PRESTATE_MISSING = "BLOCK_ERROR_PRESTATE_MISSING",
11
- /** The parent block was unknown. */
12
- PARENT_UNKNOWN = "BLOCK_ERROR_PARENT_UNKNOWN",
11
+ /**
12
+ * The first block of a chain segment references a parentRoot unknown to fork-choice.
13
+ */
14
+ PARENT_BLOCK_UNKNOWN = "BLOCK_ERROR_PARENT_BLOCK_UNKNOWN",
13
15
  /** The block slot is greater than the present slot. */
14
16
  FUTURE_SLOT = "BLOCK_ERROR_FUTURE_SLOT",
15
17
  /** The block state_root does not match the generated state. */
@@ -38,7 +40,10 @@ export enum BlockErrorCode {
38
40
  NOT_FINALIZED_DESCENDANT = "BLOCK_ERROR_NOT_FINALIZED_DESCENDANT",
39
41
  /** The provided block is from an later slot than its parent. */
40
42
  NOT_LATER_THAN_PARENT = "BLOCK_ERROR_NOT_LATER_THAN_PARENT",
41
- /** At least one block in the chain segment did not have it's parent root set to the root of the prior block. */
43
+ /**
44
+ * A block in the middle of a chain segment does not set its parentRoot to the previous block's root
45
+ * (broken parent-root link inside the segment).
46
+ */
42
47
  NON_LINEAR_PARENT_ROOTS = "BLOCK_ERROR_NON_LINEAR_PARENT_ROOTS",
43
48
  /** The slots of the blocks in the chain segment were not strictly increasing. */
44
49
  NON_LINEAR_SLOTS = "BLOCK_ERROR_NON_LINEAR_SLOTS",
@@ -61,16 +66,32 @@ export enum BlockErrorCode {
61
66
  TRANSACTIONS_TOO_BIG = "BLOCK_ERROR_TRANSACTIONS_TOO_BIG",
62
67
  /** Execution engine is unavailable, syncing, or api call errored. Peers must not be downscored on this code */
63
68
  EXECUTION_ENGINE_ERROR = "BLOCK_ERROR_EXECUTION_ERROR",
69
+ /**
70
+ * Execution engine returned a definitive INVALID verdict on the payload. Unlike
71
+ * EXECUTION_ENGINE_ERROR this is evidence about the data itself, not a local malfunction, so
72
+ * whoever served the block MAY be downscored. Mirrors PayloadErrorCode.EXECUTION_ENGINE_INVALID.
73
+ */
74
+ EXECUTION_ENGINE_INVALID = "BLOCK_ERROR_EXECUTION_INVALID",
64
75
  /** The blobs are unavailable */
65
76
  DATA_UNAVAILABLE = "BLOCK_ERROR_DATA_UNAVAILABLE",
66
77
  /** Block contains too many kzg commitments */
67
78
  TOO_MANY_KZG_COMMITMENTS = "BLOCK_ERROR_TOO_MANY_KZG_COMMITMENTS",
68
79
  /** Bid parent block root does not match block parent root */
69
80
  BID_PARENT_ROOT_MISMATCH = "BLOCK_ERROR_BID_PARENT_ROOT_MISMATCH",
81
+ /** A block body operation or parent execution request list exceeds its per-block limit */
82
+ TOO_MANY_BLOCK_OPERATIONS = "BLOCK_ERROR_TOO_MANY_BLOCK_OPERATIONS",
70
83
  /** The parent block's execution payload has been verified as invalid */
71
84
  PARENT_EXECUTION_INVALID = "BLOCK_ERROR_PARENT_EXECUTION_INVALID",
72
- /** The block's parent execution payload (defined by bid.parent_block_hash) has not been seen */
85
+ /**
86
+ * [gloas] The first block of a chain segment references a parent execution payload
87
+ * (bid.parent_block_hash) unknown to fork-choice (segment boundary).
88
+ */
73
89
  PARENT_PAYLOAD_UNKNOWN = "BLOCK_ERROR_PARENT_PAYLOAD_UNKNOWN",
90
+ /**
91
+ * [gloas] A block in the middle of a chain segment has a bid.parent_block_hash that does not chain onto the
92
+ * previous block's execution payload (broken payload link inside the segment).
93
+ */
94
+ NON_LINEAR_PAYLOAD_ROOTS = "BLOCK_ERROR_NON_LINEAR_PAYLOAD_ROOTS",
74
95
  /** An execution payload envelope in the chain segment references a block root that does not match its slot's block */
75
96
  ENVELOPE_BLOCK_ROOT_MISMATCH = "BLOCK_ERROR_ENVELOPE_BLOCK_ROOT_MISMATCH",
76
97
  }
@@ -80,9 +101,18 @@ type ExecutionErrorStatus = Exclude<
80
101
  ExecutionPayloadStatus.VALID | ExecutionPayloadStatus.ACCEPTED | ExecutionPayloadStatus.SYNCING
81
102
  >;
82
103
 
104
+ /**
105
+ * Statuses where the execution engine could NOT render a verdict on the payload: it is unreachable
106
+ * (UNAVAILABLE), errored (ELERROR), or returned a malformed/client-specific response
107
+ * (INVALID_BLOCK_HASH, dropped from engine_newPayloadV2+). None of these are evidence about the
108
+ * data, so peers must not be downscored on them. A definitive INVALID carries
109
+ * BlockErrorCode.EXECUTION_ENGINE_INVALID instead.
110
+ */
111
+ type ExecutionEngineErrorStatus = Exclude<ExecutionErrorStatus, ExecutionPayloadStatus.INVALID>;
112
+
83
113
  export type BlockErrorType =
84
114
  | {code: BlockErrorCode.PRESTATE_MISSING; error: Error}
85
- | {code: BlockErrorCode.PARENT_UNKNOWN; parentRoot: RootHex}
115
+ | {code: BlockErrorCode.PARENT_BLOCK_UNKNOWN; parentRoot: RootHex}
86
116
  | {code: BlockErrorCode.FUTURE_SLOT; blockSlot: Slot; currentSlot: Slot}
87
117
  | {code: BlockErrorCode.STATE_ROOT_MISMATCH}
88
118
  | {code: BlockErrorCode.GENESIS_BLOCK}
@@ -114,12 +144,19 @@ export type BlockErrorType =
114
144
  | {code: BlockErrorCode.TOO_MUCH_GAS_USED; gasUsed: number; gasLimit: number}
115
145
  | {code: BlockErrorCode.SAME_PARENT_HASH; blockHash: RootHex}
116
146
  | {code: BlockErrorCode.TRANSACTIONS_TOO_BIG; size: number; max: number}
117
- | {code: BlockErrorCode.EXECUTION_ENGINE_ERROR; execStatus: ExecutionErrorStatus; errorMessage: string}
147
+ | {code: BlockErrorCode.EXECUTION_ENGINE_ERROR; execStatus: ExecutionEngineErrorStatus; errorMessage: string}
148
+ | {
149
+ code: BlockErrorCode.EXECUTION_ENGINE_INVALID;
150
+ execStatus: ExecutionPayloadStatus.INVALID;
151
+ errorMessage: string;
152
+ }
118
153
  | {code: BlockErrorCode.DATA_UNAVAILABLE}
119
154
  | {code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS; blobKzgCommitmentsLen: number; commitmentLimit: number}
120
155
  | {code: BlockErrorCode.BID_PARENT_ROOT_MISMATCH; bidParentRoot: RootHex; blockParentRoot: RootHex}
156
+ | {code: BlockErrorCode.TOO_MANY_BLOCK_OPERATIONS; name: string; count: number; limit: number}
121
157
  | {code: BlockErrorCode.PARENT_EXECUTION_INVALID; parentRoot: RootHex}
122
- | {code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN; parentRoot: RootHex; parentBlockHash: RootHex};
158
+ | {code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN; parentRoot: RootHex; parentBlockHash: RootHex}
159
+ | {code: BlockErrorCode.NON_LINEAR_PAYLOAD_ROOTS; parentBlockHash: RootHex; expectedBlockHash: RootHex};
123
160
 
124
161
  export class BlockGossipError extends GossipActionError<BlockErrorType> {}
125
162