@lodestar/beacon-node 1.45.0-dev.1588281853 → 1.45.0-dev.17a48050e6

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 (198) hide show
  1. package/lib/api/impl/beacon/index.d.ts +1 -1
  2. package/lib/api/impl/beacon/index.d.ts.map +1 -1
  3. package/lib/api/impl/beacon/index.js.map +1 -1
  4. package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/pool/index.js +6 -43
  6. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  7. package/lib/api/impl/beacon/state/index.d.ts +1 -1
  8. package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
  9. package/lib/api/impl/beacon/state/index.js +48 -5
  10. package/lib/api/impl/beacon/state/index.js.map +1 -1
  11. package/lib/api/impl/beacon/state/utils.d.ts +13 -3
  12. package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
  13. package/lib/api/impl/beacon/state/utils.js +48 -4
  14. package/lib/api/impl/beacon/state/utils.js.map +1 -1
  15. package/lib/api/impl/debug/index.d.ts +1 -1
  16. package/lib/api/impl/debug/index.d.ts.map +1 -1
  17. package/lib/api/impl/debug/index.js +2 -2
  18. package/lib/api/impl/debug/index.js.map +1 -1
  19. package/lib/api/impl/lodestar/index.js +2 -2
  20. package/lib/api/impl/lodestar/index.js.map +1 -1
  21. package/lib/api/impl/proof/index.d.ts +1 -1
  22. package/lib/api/impl/proof/index.d.ts.map +1 -1
  23. package/lib/api/impl/proof/index.js +2 -2
  24. package/lib/api/impl/proof/index.js.map +1 -1
  25. package/lib/api/impl/utils.d.ts +23 -0
  26. package/lib/api/impl/utils.d.ts.map +1 -1
  27. package/lib/api/impl/utils.js +44 -1
  28. package/lib/api/impl/utils.js.map +1 -1
  29. package/lib/api/impl/validator/index.d.ts +0 -14
  30. package/lib/api/impl/validator/index.d.ts.map +1 -1
  31. package/lib/api/impl/validator/index.js +67 -62
  32. package/lib/api/impl/validator/index.js.map +1 -1
  33. package/lib/api/rest/base.d.ts.map +1 -1
  34. package/lib/api/rest/base.js +8 -0
  35. package/lib/api/rest/base.js.map +1 -1
  36. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  37. package/lib/chain/blocks/importBlock.js +1 -4
  38. package/lib/chain/blocks/importBlock.js.map +1 -1
  39. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  40. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  41. package/lib/chain/builderCircuitBreaker.d.ts +31 -0
  42. package/lib/chain/builderCircuitBreaker.d.ts.map +1 -0
  43. package/lib/chain/builderCircuitBreaker.js +59 -0
  44. package/lib/chain/builderCircuitBreaker.js.map +1 -0
  45. package/lib/chain/chain.d.ts +3 -2
  46. package/lib/chain/chain.d.ts.map +1 -1
  47. package/lib/chain/chain.js +4 -3
  48. package/lib/chain/chain.js.map +1 -1
  49. package/lib/chain/errors/blockError.d.ts +7 -0
  50. package/lib/chain/errors/blockError.d.ts.map +1 -1
  51. package/lib/chain/errors/blockError.js +2 -0
  52. package/lib/chain/errors/blockError.js.map +1 -1
  53. package/lib/chain/errors/executionPayloadEnvelope.d.ts +11 -0
  54. package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
  55. package/lib/chain/errors/executionPayloadEnvelope.js +2 -0
  56. package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
  57. package/lib/chain/interface.d.ts +3 -2
  58. package/lib/chain/interface.d.ts.map +1 -1
  59. package/lib/chain/interface.js.map +1 -1
  60. package/lib/chain/lightClient/index.d.ts.map +1 -1
  61. package/lib/chain/lightClient/index.js +11 -6
  62. package/lib/chain/lightClient/index.js.map +1 -1
  63. package/lib/chain/lightClient/proofs.d.ts.map +1 -1
  64. package/lib/chain/lightClient/proofs.js +28 -2
  65. package/lib/chain/lightClient/proofs.js.map +1 -1
  66. package/lib/chain/lightClient/types.d.ts +3 -1
  67. package/lib/chain/lightClient/types.d.ts.map +1 -1
  68. package/lib/chain/opPools/proposerPreferencesPool.d.ts +2 -1
  69. package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
  70. package/lib/chain/opPools/proposerPreferencesPool.js +3 -0
  71. package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
  72. package/lib/chain/options.d.ts +5 -0
  73. package/lib/chain/options.d.ts.map +1 -1
  74. package/lib/chain/options.js +1 -0
  75. package/lib/chain/options.js.map +1 -1
  76. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  77. package/lib/chain/prepareNextSlot.js +12 -7
  78. package/lib/chain/prepareNextSlot.js.map +1 -1
  79. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  80. package/lib/chain/produceBlock/produceBlockBody.js +4 -12
  81. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  82. package/lib/chain/seenCache/index.d.ts +0 -1
  83. package/lib/chain/seenCache/index.d.ts.map +1 -1
  84. package/lib/chain/seenCache/index.js +0 -1
  85. package/lib/chain/seenCache/index.js.map +1 -1
  86. package/lib/chain/validation/block.d.ts.map +1 -1
  87. package/lib/chain/validation/block.js +52 -1
  88. package/lib/chain/validation/block.js.map +1 -1
  89. package/lib/chain/validation/executionPayloadBid.js +11 -10
  90. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  91. package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
  92. package/lib/chain/validation/executionPayloadEnvelope.js +30 -0
  93. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  94. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  95. package/lib/chain/validation/proposerPreferences.js +14 -5
  96. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  97. package/lib/chain/validation/signatureSets/aggregateAndProof.d.ts.map +1 -1
  98. package/lib/chain/validation/signatureSets/aggregateAndProof.js +2 -3
  99. package/lib/chain/validation/signatureSets/aggregateAndProof.js.map +1 -1
  100. package/lib/db/repositories/lightclientSyncCommitteeWitness.d.ts.map +1 -1
  101. package/lib/db/repositories/lightclientSyncCommitteeWitness.js +42 -7
  102. package/lib/db/repositories/lightclientSyncCommitteeWitness.js.map +1 -1
  103. package/lib/execution/builder/http.d.ts +18 -0
  104. package/lib/execution/builder/http.d.ts.map +1 -1
  105. package/lib/execution/builder/http.js +20 -11
  106. package/lib/execution/builder/http.js.map +1 -1
  107. package/lib/metrics/metrics/beacon.d.ts +6 -0
  108. package/lib/metrics/metrics/beacon.d.ts.map +1 -1
  109. package/lib/metrics/metrics/beacon.js +18 -0
  110. package/lib/metrics/metrics/beacon.js.map +1 -1
  111. package/lib/network/core/metrics.d.ts +65 -0
  112. package/lib/network/core/metrics.d.ts.map +1 -1
  113. package/lib/network/core/metrics.js +94 -0
  114. package/lib/network/core/metrics.js.map +1 -1
  115. package/lib/network/discv5/utils.d.ts.map +1 -1
  116. package/lib/network/discv5/utils.js +12 -3
  117. package/lib/network/discv5/utils.js.map +1 -1
  118. package/lib/network/gossip/encoding.d.ts +2 -2
  119. package/lib/network/gossip/encoding.d.ts.map +1 -1
  120. package/lib/network/gossip/encoding.js +22 -9
  121. package/lib/network/gossip/encoding.js.map +1 -1
  122. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  123. package/lib/network/gossip/gossipsub.js +4 -3
  124. package/lib/network/gossip/gossipsub.js.map +1 -1
  125. package/lib/network/gossip/topic.d.ts +831 -226
  126. package/lib/network/gossip/topic.d.ts.map +1 -1
  127. package/lib/network/gossip/topic.js +47 -4
  128. package/lib/network/gossip/topic.js.map +1 -1
  129. package/lib/network/peers/peerManager.d.ts.map +1 -1
  130. package/lib/network/peers/peerManager.js +10 -0
  131. package/lib/network/peers/peerManager.js.map +1 -1
  132. package/lib/network/peers/score/score.d.ts +3 -1
  133. package/lib/network/peers/score/score.d.ts.map +1 -1
  134. package/lib/network/peers/score/score.js +6 -1
  135. package/lib/network/peers/score/score.js.map +1 -1
  136. package/lib/network/peers/score/store.d.ts.map +1 -1
  137. package/lib/network/peers/score/store.js +6 -1
  138. package/lib/network/peers/score/store.js.map +1 -1
  139. package/lib/network/peers/utils/prioritizePeers.d.ts.map +1 -1
  140. package/lib/network/peers/utils/prioritizePeers.js +14 -0
  141. package/lib/network/peers/utils/prioritizePeers.js.map +1 -1
  142. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  143. package/lib/network/processor/gossipHandlers.js +6 -3
  144. package/lib/network/processor/gossipHandlers.js.map +1 -1
  145. package/lib/util/graffiti.d.ts +15 -0
  146. package/lib/util/graffiti.d.ts.map +1 -1
  147. package/lib/util/graffiti.js +55 -0
  148. package/lib/util/graffiti.js.map +1 -1
  149. package/package.json +19 -19
  150. package/src/api/impl/beacon/index.ts +1 -1
  151. package/src/api/impl/beacon/pool/index.ts +5 -53
  152. package/src/api/impl/beacon/state/index.ts +55 -3
  153. package/src/api/impl/beacon/state/utils.ts +68 -4
  154. package/src/api/impl/debug/index.ts +3 -2
  155. package/src/api/impl/lodestar/index.ts +2 -2
  156. package/src/api/impl/proof/index.ts +2 -2
  157. package/src/api/impl/utils.ts +51 -1
  158. package/src/api/impl/validator/index.ts +75 -67
  159. package/src/api/rest/base.ts +9 -0
  160. package/src/chain/blocks/importBlock.ts +1 -6
  161. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -2
  162. package/src/chain/builderCircuitBreaker.ts +84 -0
  163. package/src/chain/chain.ts +7 -3
  164. package/src/chain/errors/blockError.ts +3 -0
  165. package/src/chain/errors/executionPayloadEnvelope.ts +9 -0
  166. package/src/chain/interface.ts +2 -2
  167. package/src/chain/lightClient/index.ts +12 -7
  168. package/src/chain/lightClient/proofs.ts +36 -1
  169. package/src/chain/lightClient/types.ts +3 -1
  170. package/src/chain/opPools/proposerPreferencesPool.ts +5 -1
  171. package/src/chain/options.ts +6 -0
  172. package/src/chain/prepareNextSlot.ts +11 -7
  173. package/src/chain/produceBlock/produceBlockBody.ts +9 -17
  174. package/src/chain/seenCache/index.ts +0 -1
  175. package/src/chain/validation/block.ts +69 -1
  176. package/src/chain/validation/executionPayloadBid.ts +14 -9
  177. package/src/chain/validation/executionPayloadEnvelope.ts +39 -0
  178. package/src/chain/validation/proposerPreferences.ts +15 -5
  179. package/src/chain/validation/signatureSets/aggregateAndProof.ts +3 -3
  180. package/src/db/repositories/lightclientSyncCommitteeWitness.ts +57 -9
  181. package/src/execution/builder/http.ts +30 -17
  182. package/src/metrics/metrics/beacon.ts +19 -0
  183. package/src/network/core/metrics.ts +94 -0
  184. package/src/network/discv5/utils.ts +11 -3
  185. package/src/network/gossip/encoding.ts +20 -7
  186. package/src/network/gossip/gossipsub.ts +9 -3
  187. package/src/network/gossip/topic.ts +59 -4
  188. package/src/network/peers/peerManager.ts +11 -0
  189. package/src/network/peers/score/score.ts +8 -1
  190. package/src/network/peers/score/store.ts +8 -1
  191. package/src/network/peers/utils/prioritizePeers.ts +15 -0
  192. package/src/network/processor/gossipHandlers.ts +6 -2
  193. package/src/util/graffiti.ts +78 -0
  194. package/lib/chain/seenCache/seenProposerPreferences.d.ts +0 -16
  195. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +0 -1
  196. package/lib/chain/seenCache/seenProposerPreferences.js +0 -26
  197. package/lib/chain/seenCache/seenProposerPreferences.js.map +0 -1
  198. package/src/chain/seenCache/seenProposerPreferences.ts +0 -32
@@ -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
+ }
@@ -75,6 +75,7 @@ import {ImportPayloadOpts} from "./blocks/types.js";
75
75
  import {persistBlockInput} from "./blocks/writeBlockInputToDb.js";
76
76
  import {persistPayloadEnvelopeInput} from "./blocks/writePayloadEnvelopeInputToDb.js";
77
77
  import {BlsMultiThreadWorkerPool, BlsSingleThreadVerifier, IBlsVerifier} from "./bls/index.js";
78
+ import {BuilderCircuitBreaker} from "./builderCircuitBreaker.js";
78
79
  import {ColumnReconstructionTracker} from "./ColumnReconstructionTracker.js";
79
80
  import {ChainEvent, ChainEventEmitter} from "./emitter.js";
80
81
  import {ForkchoiceCaller, initializeForkChoice} from "./forkChoice/index.js";
@@ -107,7 +108,6 @@ import {
107
108
  SeenExecutionPayloadBids,
108
109
  SeenPayloadAttesters,
109
110
  SeenPayloadEnvelopeInput,
110
- SeenProposerPreferences,
111
111
  SeenSyncCommitteeMessages,
112
112
  } from "./seenCache/index.js";
113
113
  import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
@@ -152,6 +152,7 @@ export class BeaconChain implements IBeaconChain {
152
152
  readonly genesisValidatorsRoot: Root;
153
153
  readonly executionEngine: IExecutionEngine;
154
154
  readonly executionBuilder?: IExecutionBuilder;
155
+ readonly builderCircuitBreaker: BuilderCircuitBreaker;
155
156
  // Expose config for convenience in modularized functions
156
157
  readonly config: BeaconConfig;
157
158
  readonly custodyConfig: CustodyConfig;
@@ -189,7 +190,6 @@ export class BeaconChain implements IBeaconChain {
189
190
  readonly seenPayloadAttesters = new SeenPayloadAttesters();
190
191
  readonly seenAggregatedAttestations: SeenAggregatedAttestations;
191
192
  readonly seenExecutionPayloadBids = new SeenExecutionPayloadBids();
192
- readonly seenProposerPreferences = new SeenProposerPreferences();
193
193
  readonly seenBlockProposers = new SeenBlockProposers();
194
194
  readonly seenSyncCommitteeMessages = new SeenSyncCommitteeMessages();
195
195
  readonly seenContributionAndProof: SeenContributionAndProof;
@@ -427,6 +427,11 @@ export class BeaconChain implements IBeaconChain {
427
427
 
428
428
  this.forkChoice = forkChoice;
429
429
 
430
+ this.builderCircuitBreaker = new BuilderCircuitBreaker(
431
+ {faultInspectionWindow: opts.faultInspectionWindow, allowedFaults: opts.allowedFaults},
432
+ {forkChoice, logger, metrics}
433
+ );
434
+
430
435
  this.seenPayloadEnvelopeInputCache = new SeenPayloadEnvelopeInput({
431
436
  config,
432
437
  clock,
@@ -1473,7 +1478,6 @@ export class BeaconChain implements IBeaconChain {
1473
1478
  this.payloadAttestationPool.prune(slot);
1474
1479
  this.executionPayloadBidPool.prune(slot);
1475
1480
  this.seenExecutionPayloadBids.prune(slot);
1476
- this.seenProposerPreferences.prune(slot);
1477
1481
  this.proposerPreferencesPool.prune(slot);
1478
1482
  this.seenAttestationDatas.onSlot(slot);
1479
1483
  this.reprocessController.onSlot(slot);
@@ -67,6 +67,8 @@ export enum BlockErrorCode {
67
67
  TOO_MANY_KZG_COMMITMENTS = "BLOCK_ERROR_TOO_MANY_KZG_COMMITMENTS",
68
68
  /** Bid parent block root does not match block parent root */
69
69
  BID_PARENT_ROOT_MISMATCH = "BLOCK_ERROR_BID_PARENT_ROOT_MISMATCH",
70
+ /** A block body operation or parent execution request list exceeds its per-block limit */
71
+ TOO_MANY_BLOCK_OPERATIONS = "BLOCK_ERROR_TOO_MANY_BLOCK_OPERATIONS",
70
72
  /** The parent block's execution payload has been verified as invalid */
71
73
  PARENT_EXECUTION_INVALID = "BLOCK_ERROR_PARENT_EXECUTION_INVALID",
72
74
  /** The block's parent execution payload (defined by bid.parent_block_hash) has not been seen */
@@ -118,6 +120,7 @@ export type BlockErrorType =
118
120
  | {code: BlockErrorCode.DATA_UNAVAILABLE}
119
121
  | {code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS; blobKzgCommitmentsLen: number; commitmentLimit: number}
120
122
  | {code: BlockErrorCode.BID_PARENT_ROOT_MISMATCH; bidParentRoot: RootHex; blockParentRoot: RootHex}
123
+ | {code: BlockErrorCode.TOO_MANY_BLOCK_OPERATIONS; name: string; count: number; limit: number}
121
124
  | {code: BlockErrorCode.PARENT_EXECUTION_INVALID; parentRoot: RootHex}
122
125
  | {code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN; parentRoot: RootHex; parentBlockHash: RootHex};
123
126
 
@@ -12,6 +12,8 @@ export enum ExecutionPayloadEnvelopeErrorCode {
12
12
  BUILDER_INDEX_MISMATCH = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_BUILDER_INDEX_MISMATCH",
13
13
  BLOCK_HASH_MISMATCH = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_BLOCK_HASH_MISMATCH",
14
14
  EXECUTION_REQUESTS_ROOT_MISMATCH = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_EXECUTION_REQUESTS_ROOT_MISMATCH",
15
+ EXECUTION_REQUESTS_COUNT_EXCEEDED = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_EXECUTION_REQUESTS_COUNT_EXCEEDED",
16
+ WITHDRAWALS_COUNT_EXCEEDED = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_WITHDRAWALS_COUNT_EXCEEDED",
15
17
  INVALID_SIGNATURE = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_INVALID_SIGNATURE",
16
18
  PAYLOAD_ENVELOPE_INPUT_MISSING = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_PAYLOAD_ENVELOPE_INPUT_MISSING",
17
19
  }
@@ -42,6 +44,13 @@ export type ExecutionPayloadEnvelopeErrorType =
42
44
  envelopeRequestsRoot: RootHex;
43
45
  bidRequestsRoot: RootHex;
44
46
  }
47
+ | {
48
+ code: ExecutionPayloadEnvelopeErrorCode.EXECUTION_REQUESTS_COUNT_EXCEEDED;
49
+ name: string;
50
+ count: number;
51
+ limit: number;
52
+ }
53
+ | {code: ExecutionPayloadEnvelopeErrorCode.WITHDRAWALS_COUNT_EXCEEDED; count: number; limit: number}
45
54
  | {code: ExecutionPayloadEnvelopeErrorCode.INVALID_SIGNATURE}
46
55
  | {code: ExecutionPayloadEnvelopeErrorCode.PAYLOAD_ENVELOPE_INPUT_MISSING; blockRoot: RootHex};
47
56
 
@@ -35,6 +35,7 @@ import {BeaconProposerCache, ProposerPreparationData} from "./beaconProposerCach
35
35
  import {IBlockInput} from "./blocks/blockInput/index.js";
36
36
  import {ImportBlockOpts, ImportPayloadOpts} from "./blocks/types.js";
37
37
  import {IBlsVerifier} from "./bls/index.js";
38
+ import {BuilderCircuitBreaker} from "./builderCircuitBreaker.js";
38
39
  import {ColumnReconstructionTracker} from "./ColumnReconstructionTracker.js";
39
40
  import {ChainEventEmitter} from "./emitter.js";
40
41
  import {ForkchoiceCaller} from "./forkChoice/index.js";
@@ -61,7 +62,6 @@ import {
61
62
  SeenContributionAndProof,
62
63
  SeenExecutionPayloadBids,
63
64
  SeenPayloadAttesters,
64
- SeenProposerPreferences,
65
65
  SeenSyncCommitteeMessages,
66
66
  } from "./seenCache/index.js";
67
67
  import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
@@ -96,6 +96,7 @@ export interface IBeaconChain {
96
96
  readonly earliestAvailableSlot: Slot;
97
97
  readonly executionEngine: IExecutionEngine;
98
98
  readonly executionBuilder?: IExecutionBuilder;
99
+ readonly builderCircuitBreaker: BuilderCircuitBreaker;
99
100
  // Expose config for convenience in modularized functions
100
101
  readonly config: BeaconConfig;
101
102
  readonly custodyConfig: CustodyConfig;
@@ -133,7 +134,6 @@ export interface IBeaconChain {
133
134
  readonly seenPayloadAttesters: SeenPayloadAttesters;
134
135
  readonly seenAggregatedAttestations: SeenAggregatedAttestations;
135
136
  readonly seenExecutionPayloadBids: SeenExecutionPayloadBids;
136
- readonly seenProposerPreferences: SeenProposerPreferences;
137
137
  readonly seenBlockProposers: SeenBlockProposers;
138
138
  readonly seenSyncCommitteeMessages: SeenSyncCommitteeMessages;
139
139
  readonly seenContributionAndProof: SeenContributionAndProof;
@@ -10,9 +10,8 @@ import {
10
10
  MIN_SYNC_COMMITTEE_PARTICIPANTS,
11
11
  SLOTS_PER_EPOCH,
12
12
  SYNC_COMMITTEE_SIZE,
13
- forkPostAltair,
14
- highestFork,
15
13
  isForkPostElectra,
14
+ isForkPostGloas,
16
15
  } from "@lodestar/params";
17
16
  import {
18
17
  type IBeaconStateViewAltair,
@@ -44,7 +43,6 @@ import {
44
43
  electra,
45
44
  phase0,
46
45
  ssz,
47
- sszTypesFor,
48
46
  } from "@lodestar/types";
49
47
  import {Logger, MapDef, byteArrayEquals, pruneSetToMax, toRootHex} from "@lodestar/utils";
50
48
  import {ZERO_HASH} from "../../constants/index.js";
@@ -230,8 +228,8 @@ export class LightClientServer {
230
228
  this.signal = signal;
231
229
 
232
230
  this.zero = {
233
- // Assign the hightest fork's default value because it can always be typecasted down to correct fork
234
- finalizedHeader: sszTypesFor(highestFork(forkPostAltair)).LightClientHeader.defaultValue(),
231
+ // Assign the highest pre-Gloas light-client header because post-Gloas light-client updates are skipped for now.
232
+ finalizedHeader: ssz.electra.LightClientHeader.defaultValue(),
235
233
  // Electra finalityBranch has fixed length of 5 whereas altair has 4. The fifth element will be ignored
236
234
  // when serializing as altair LightClientUpdate
237
235
  finalityBranch: ssz.electra.LightClientUpdate.fields.finalityBranch.defaultValue(),
@@ -658,10 +656,17 @@ export class LightClientServer {
658
656
 
659
657
  const attestedFork = this.config.getForkName(attestedHeader.beacon.slot);
660
658
  const numWitness = syncCommitteeWitness.witness.length;
661
- if (isForkPostElectra(attestedFork) && numWitness !== NUM_WITNESS_ELECTRA) {
659
+ if (
660
+ isForkPostGloas(attestedFork) &&
661
+ (syncCommitteeWitness.currentSyncCommitteeBranch === undefined ||
662
+ syncCommitteeWitness.nextSyncCommitteeBranch === undefined)
663
+ ) {
664
+ throw Error("Expected post-Gloas sync committee branches");
665
+ }
666
+ if (!isForkPostGloas(attestedFork) && isForkPostElectra(attestedFork) && numWitness !== NUM_WITNESS_ELECTRA) {
662
667
  throw Error(`Expected ${NUM_WITNESS_ELECTRA} witnesses in post-Electra numWitness=${numWitness}`);
663
668
  }
664
- if (!isForkPostElectra(attestedFork) && numWitness !== NUM_WITNESS) {
669
+ if (!isForkPostGloas(attestedFork) && !isForkPostElectra(attestedFork) && numWitness !== NUM_WITNESS) {
665
670
  throw Error(`Expected ${NUM_WITNESS} witnesses in pre-Electra numWitness=${numWitness}`);
666
671
  }
667
672
 
@@ -1,11 +1,15 @@
1
1
  import {Tree} from "@chainsafe/persistent-merkle-tree";
2
2
  import {
3
3
  BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX,
4
+ CURRENT_SYNC_COMMITTEE_GINDEX_GLOAS,
4
5
  FINALIZED_ROOT_GINDEX,
5
6
  FINALIZED_ROOT_GINDEX_ELECTRA,
7
+ FINALIZED_ROOT_GINDEX_GLOAS,
6
8
  ForkName,
7
9
  ForkPostBellatrix,
10
+ NEXT_SYNC_COMMITTEE_GINDEX_GLOAS,
8
11
  isForkPostElectra,
12
+ isForkPostGloas,
9
13
  } from "@lodestar/params";
10
14
  import {BeaconStateAllForks, CachedBeaconStateAllForks} from "@lodestar/state-transition";
11
15
  import {BeaconBlockBody, SSZTypesFor, ssz} from "@lodestar/types";
@@ -17,6 +21,24 @@ export function getSyncCommitteesWitness(fork: ForkName, state: BeaconStateAllFo
17
21
  let currentSyncCommitteeRoot: Uint8Array;
18
22
  let nextSyncCommitteeRoot: Uint8Array;
19
23
 
24
+ if (isForkPostGloas(fork)) {
25
+ const tree = new Tree(state.node);
26
+ const currentSyncCommitteeGindex = BigInt(CURRENT_SYNC_COMMITTEE_GINDEX_GLOAS);
27
+ const nextSyncCommitteeGindex = BigInt(NEXT_SYNC_COMMITTEE_GINDEX_GLOAS);
28
+
29
+ currentSyncCommitteeRoot = tree.getRoot(currentSyncCommitteeGindex);
30
+ nextSyncCommitteeRoot = tree.getRoot(nextSyncCommitteeGindex);
31
+ witness = [];
32
+
33
+ return {
34
+ witness,
35
+ currentSyncCommitteeRoot,
36
+ nextSyncCommitteeRoot,
37
+ currentSyncCommitteeBranch: tree.getSingleProof(currentSyncCommitteeGindex),
38
+ nextSyncCommitteeBranch: tree.getSingleProof(nextSyncCommitteeGindex),
39
+ };
40
+ }
41
+
20
42
  if (isForkPostElectra(fork)) {
21
43
  const n2 = n1.left;
22
44
  const n5 = n2.right;
@@ -60,17 +82,30 @@ export function getSyncCommitteesWitness(fork: ForkName, state: BeaconStateAllFo
60
82
  }
61
83
 
62
84
  export function getNextSyncCommitteeBranch(syncCommitteesWitness: SyncCommitteeWitness): Uint8Array[] {
85
+ if (syncCommitteesWitness.nextSyncCommitteeBranch) {
86
+ return syncCommitteesWitness.nextSyncCommitteeBranch;
87
+ }
88
+
63
89
  // Witness branch is sorted by descending gindex
64
90
  return [syncCommitteesWitness.currentSyncCommitteeRoot, ...syncCommitteesWitness.witness];
65
91
  }
66
92
 
67
93
  export function getCurrentSyncCommitteeBranch(syncCommitteesWitness: SyncCommitteeWitness): Uint8Array[] {
94
+ if (syncCommitteesWitness.currentSyncCommitteeBranch) {
95
+ return syncCommitteesWitness.currentSyncCommitteeBranch;
96
+ }
97
+
68
98
  // Witness branch is sorted by descending gindex
69
99
  return [syncCommitteesWitness.nextSyncCommitteeRoot, ...syncCommitteesWitness.witness];
70
100
  }
71
101
 
72
102
  export function getFinalizedRootProof(state: CachedBeaconStateAllForks): Uint8Array[] {
73
- const finalizedRootGindex = state.epochCtx.isPostElectra() ? FINALIZED_ROOT_GINDEX_ELECTRA : FINALIZED_ROOT_GINDEX;
103
+ const fork = state.config.getForkName(state.slot);
104
+ const finalizedRootGindex = isForkPostGloas(fork)
105
+ ? FINALIZED_ROOT_GINDEX_GLOAS
106
+ : state.epochCtx.isPostElectra()
107
+ ? FINALIZED_ROOT_GINDEX_ELECTRA
108
+ : FINALIZED_ROOT_GINDEX;
74
109
  return new Tree(state.node).getSingleProof(BigInt(finalizedRootGindex));
75
110
  }
76
111
 
@@ -26,8 +26,10 @@
26
26
  * ```
27
27
  */
28
28
  export type SyncCommitteeWitness = {
29
- /** Vector[Bytes32, 4] or Vector[Bytes32, 5] depending on the fork */
29
+ /** Shared witness for pre-Gloas forks where current and next sync committees are siblings. */
30
30
  witness: Uint8Array[];
31
31
  currentSyncCommitteeRoot: Uint8Array;
32
32
  nextSyncCommitteeRoot: Uint8Array;
33
+ currentSyncCommitteeBranch?: Uint8Array[];
34
+ nextSyncCommitteeBranch?: Uint8Array[];
33
35
  };
@@ -1,4 +1,4 @@
1
- import {RootHex, Slot, gloas} from "@lodestar/types";
1
+ import {RootHex, Slot, ValidatorIndex, gloas} from "@lodestar/types";
2
2
  import {toRootHex} from "@lodestar/utils";
3
3
 
4
4
  /**
@@ -22,6 +22,10 @@ export class ProposerPreferencesPool {
22
22
  return this.bySlot.get(slot)?.get(dependentRootHex) ?? null;
23
23
  }
24
24
 
25
+ isKnown(proposalSlot: Slot, dependentRoot: RootHex, validatorIndex: ValidatorIndex): boolean {
26
+ return this.get(proposalSlot, dependentRoot)?.message.validatorIndex === validatorIndex;
27
+ }
28
+
25
29
  add(signed: gloas.SignedProposerPreferences): void {
26
30
  const {proposalSlot, dependentRoot} = signed.message;
27
31
  const rootHex = toRootHex(dependentRoot);
@@ -35,6 +35,7 @@ export type IChainOptions = BlockProcessOpts &
35
35
  persistOrphanedBlocksDir?: string;
36
36
  skipCreateStateCacheIfAvailable?: boolean;
37
37
  suggestedFeeRecipient: string;
38
+ graffitiAppend?: boolean;
38
39
  maxSkipSlots?: number;
39
40
  /** Ensure blobs returned by the execution engine are valid */
40
41
  sanityCheckExecutionEngineBlobs?: boolean;
@@ -48,6 +49,10 @@ export type IChainOptions = BlockProcessOpts &
48
49
  archiveDateEpochs?: number;
49
50
  nHistoricalStatesFileDataStore?: boolean;
50
51
  nativeStateView?: boolean;
52
+ /** Builder circuit breaker fault inspection window in slots */
53
+ faultInspectionWindow?: number;
54
+ /** Allowed unrevealed payloads within the fault inspection window */
55
+ allowedFaults?: number;
51
56
  };
52
57
 
53
58
  export type BlockProcessOpts = {
@@ -107,6 +112,7 @@ export const defaultChainOptions: IChainOptions = {
107
112
  computeUnrealized: true,
108
113
  fastConfirmation: false,
109
114
  suggestedFeeRecipient: defaultValidatorOptions.suggestedFeeRecipient,
115
+ graffitiAppend: true,
110
116
  serveHistoricalState: false,
111
117
  assertCorrectProgressiveBalances: false,
112
118
  archiveStateEpochFrequency: 1024,
@@ -1,7 +1,7 @@
1
1
  import {routes} from "@lodestar/api";
2
2
  import {ChainForkConfig} from "@lodestar/config";
3
3
  import {getSafeExecutionBlockHash} from "@lodestar/fork-choice";
4
- import {ForkPostBellatrix, ForkSeq, SLOTS_PER_EPOCH, isForkPostBellatrix} from "@lodestar/params";
4
+ import {ForkPostBellatrix, ForkSeq, SLOTS_PER_EPOCH, isForkPostBellatrix, isForkPostGloas} from "@lodestar/params";
5
5
  import {
6
6
  IBeaconStateView,
7
7
  IBeaconStateViewBellatrix,
@@ -152,12 +152,16 @@ export class PrepareNextSlotScheduler {
152
152
  updatedHead = proposerHead;
153
153
  }
154
154
 
155
- // Update the builder status, if enabled shoot an api call to check status
156
- this.chain.updateBuilderStatus(clockSlot);
157
- if (this.chain.executionBuilder?.status === BuilderStatus.enabled) {
158
- this.chain.executionBuilder.checkStatus().catch((e) => {
159
- this.logger.error("Builder disabled as the check status api failed", {prepareSlot}, e as Error);
160
- });
155
+ if (isForkPostGloas(fork)) {
156
+ this.chain.builderCircuitBreaker.update(clockSlot);
157
+ } else {
158
+ // Update the builder status, if enabled shoot an api call to check status
159
+ this.chain.updateBuilderStatus(clockSlot);
160
+ if (this.chain.executionBuilder?.status === BuilderStatus.enabled) {
161
+ this.chain.executionBuilder.checkStatus().catch((e) => {
162
+ this.logger.error("Builder disabled as the check status api failed", {prepareSlot}, e as Error);
163
+ });
164
+ }
161
165
  }
162
166
  }
163
167
 
@@ -43,6 +43,7 @@ import {
43
43
  ValidatorIndex,
44
44
  Wei,
45
45
  altair,
46
+ bellatrix,
46
47
  capella,
47
48
  deneb,
48
49
  electra,
@@ -830,28 +831,19 @@ export function getPayloadAttributesForSSE(
830
831
  feeRecipient,
831
832
  });
832
833
 
833
- let parentBlockNumber: number;
834
- if (isForkPostGloas(fork)) {
835
- const parentBlock = chain.forkChoice.getBlockHexAndBlockHash(
836
- toRootHex(parentBlockRoot),
837
- toRootHex(parentBlockHash)
838
- );
839
- if (parentBlock?.executionPayloadBlockHash == null) {
840
- throw Error(`Parent block not found in fork choice root=${toRootHex(parentBlockRoot)}`);
841
- }
842
- parentBlockNumber = parentBlock.executionPayloadNumber;
843
- } else {
844
- parentBlockNumber = prepareState.payloadBlockNumber;
845
- }
846
-
847
- const ssePayloadAttributes: SSEPayloadAttributes = {
834
+ const ssePayloadAttributes = {
848
835
  proposerIndex: prepareState.getBeaconProposer(prepareSlot),
849
836
  proposalSlot: prepareSlot,
850
- parentBlockNumber,
851
837
  parentBlockRoot,
852
838
  parentBlockHash,
853
839
  payloadAttributes,
854
- };
840
+ } as SSEPayloadAttributes;
841
+
842
+ if (!isForkPostGloas(fork)) {
843
+ // Removed in Gloas, builders can get the block number from the EL via the block hash if required
844
+ (ssePayloadAttributes as bellatrix.SSEPayloadAttributes).parentBlockNumber = prepareState.payloadBlockNumber;
845
+ }
846
+
855
847
  return ssePayloadAttributes;
856
848
  }
857
849
 
@@ -5,4 +5,3 @@ export {SeenContributionAndProof} from "./seenCommitteeContribution.js";
5
5
  export {SeenExecutionPayloadBids} from "./seenExecutionPayloadBids.js";
6
6
  export {SeenBlockInput} from "./seenGossipBlockInput.js";
7
7
  export {PayloadEnvelopeInput, SeenPayloadEnvelopeInput} from "./seenPayloadEnvelopeInput.js";
8
- export {SeenProposerPreferences} from "./seenProposerPreferences.js";
@@ -1,6 +1,22 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
2
  import {ExecutionStatus} from "@lodestar/fork-choice";
3
- import {ForkName, isForkPostBellatrix, isForkPostDeneb, isForkPostGloas} from "@lodestar/params";
3
+ import {
4
+ ForkName,
5
+ MAX_ATTESTATIONS_ELECTRA,
6
+ MAX_ATTESTER_SLASHINGS_ELECTRA,
7
+ MAX_BLS_TO_EXECUTION_CHANGES,
8
+ MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
9
+ MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD,
10
+ MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
11
+ MAX_DEPOSIT_REQUESTS_PER_PAYLOAD,
12
+ MAX_PAYLOAD_ATTESTATIONS,
13
+ MAX_PROPOSER_SLASHINGS,
14
+ MAX_VOLUNTARY_EXITS,
15
+ MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
16
+ isForkPostBellatrix,
17
+ isForkPostDeneb,
18
+ isForkPostGloas,
19
+ } from "@lodestar/params";
4
20
  import {
5
21
  computeEpochAtSlot,
6
22
  computeStartSlotAtEpoch,
@@ -164,6 +180,58 @@ export async function validateGossipBlock(
164
180
  });
165
181
  }
166
182
 
183
+ // [REJECT] The counts of `block.body.parent_execution_requests` are within
184
+ // their respective limits -- i.e. validate that
185
+ // `len(block.body.parent_execution_requests.deposits) <= MAX_DEPOSIT_REQUESTS_PER_PAYLOAD`,
186
+ // `len(block.body.parent_execution_requests.withdrawals) <= MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD`,
187
+ // `len(block.body.parent_execution_requests.consolidations) <= MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD`,
188
+ // `len(block.body.parent_execution_requests.builder_deposits) <= MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD`,
189
+ // and
190
+ // `len(block.body.parent_execution_requests.builder_exits) <= MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD`.
191
+ // [REJECT] The counts of the block body operations are within their respective
192
+ // limits -- i.e. validate that
193
+ // `len(block.body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS`,
194
+ // `len(block.body.attester_slashings) <= MAX_ATTESTER_SLASHINGS_ELECTRA`,
195
+ // `len(block.body.attestations) <= MAX_ATTESTATIONS_ELECTRA`,
196
+ // `len(block.body.deposits) == 0`,
197
+ // `len(block.body.voluntary_exits) <= MAX_VOLUNTARY_EXITS`,
198
+ // `len(block.body.bls_to_execution_changes) <= MAX_BLS_TO_EXECUTION_CHANGES`,
199
+ // and `len(block.body.payload_attestations) <= MAX_PAYLOAD_ATTESTATIONS`.
200
+ const body = (block as gloas.BeaconBlock).body;
201
+ const requests = body.parentExecutionRequests;
202
+ const countLimits: [string, number, number][] = [
203
+ ["parentExecutionRequests.deposits", requests.deposits.length, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD],
204
+ ["parentExecutionRequests.withdrawals", requests.withdrawals.length, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD],
205
+ [
206
+ "parentExecutionRequests.consolidations",
207
+ requests.consolidations.length,
208
+ MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
209
+ ],
210
+ [
211
+ "parentExecutionRequests.builderDeposits",
212
+ requests.builderDeposits.length,
213
+ MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
214
+ ],
215
+ ["parentExecutionRequests.builderExits", requests.builderExits.length, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD],
216
+ ["proposerSlashings", body.proposerSlashings.length, MAX_PROPOSER_SLASHINGS],
217
+ ["attesterSlashings", body.attesterSlashings.length, MAX_ATTESTER_SLASHINGS_ELECTRA],
218
+ ["attestations", body.attestations.length, MAX_ATTESTATIONS_ELECTRA],
219
+ ["deposits", body.deposits.length, 0],
220
+ ["voluntaryExits", body.voluntaryExits.length, MAX_VOLUNTARY_EXITS],
221
+ ["blsToExecutionChanges", body.blsToExecutionChanges.length, MAX_BLS_TO_EXECUTION_CHANGES],
222
+ ["payloadAttestations", body.payloadAttestations.length, MAX_PAYLOAD_ATTESTATIONS],
223
+ ];
224
+ for (const [name, count, limit] of countLimits) {
225
+ if (count > limit) {
226
+ throw new BlockGossipError(GossipAction.REJECT, {
227
+ code: BlockErrorCode.TOO_MANY_BLOCK_OPERATIONS,
228
+ name,
229
+ count,
230
+ limit,
231
+ });
232
+ }
233
+ }
234
+
167
235
  // TODO GLOAS: [REJECT] The block's execution payload parent (defined by bid.parent_block_hash) passes all validation
168
236
  // This requires execution engine integration to verify the parent block hash
169
237
  }
@@ -37,9 +37,11 @@ async function validateExecutionPayloadBid(
37
37
  const parentBlockRootHex = toRootHex(bid.parentBlockRoot);
38
38
  const parentBlockHashHex = toRootHex(bid.parentBlockHash);
39
39
 
40
- // [IGNORE] `bid.slot` is the current slot or the next slot.
41
- const currentSlot = chain.clock.currentSlot;
42
- if (bid.slot !== currentSlot && bid.slot !== currentSlot + 1) {
40
+ // [IGNORE] `bid.slot` is the current slot, or the next slot (`bid.slot - 1` is current), allowing for `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
41
+ if (
42
+ !chain.clock.isCurrentSlotGivenGossipDisparity(bid.slot) &&
43
+ !chain.clock.isCurrentSlotGivenGossipDisparity(bid.slot - 1)
44
+ ) {
43
45
  throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
44
46
  code: ExecutionPayloadBidErrorCode.INVALID_SLOT,
45
47
  builderIndex: bid.builderIndex,
@@ -122,17 +124,20 @@ async function validateExecutionPayloadBid(
122
124
  throw new Error(`Expected gloas+ state for execution payload bid validation, got fork=${state.forkName}`);
123
125
  }
124
126
 
125
- // [REJECT] `bid.builder_index` is a valid/active builder index -- i.e.
126
- // `is_active_builder(state, bid.builder_index)` returns `True`.
127
- let builder: gloas.Builder;
128
- try {
129
- builder = state.getBuilder(bid.builderIndex);
130
- } catch {
127
+ // [REJECT] `bid.builder_index` is within bounds -- i.e. `bid.builder_index < len(state.builders)`.
128
+ // `state.getBuilder` returns a lazy SSZ `getReadonly` view that is not bounds-checked eagerly; an
129
+ // out-of-range index only throws (`LeafNode has no right node`) on deferred field access (e.g. inside
130
+ // `isActiveBuilder`), escaping a try/catch around `getBuilder`. Check the length explicitly instead.
131
+ if (bid.builderIndex >= state.getBuildersLength()) {
131
132
  throw new ExecutionPayloadBidError(GossipAction.REJECT, {
132
133
  code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE,
133
134
  builderIndex: bid.builderIndex,
134
135
  });
135
136
  }
137
+
138
+ // [REJECT] `bid.builder_index` is a valid/active builder index -- i.e.
139
+ // `is_active_builder(state, bid.builder_index)` returns `True`.
140
+ const builder = state.getBuilder(bid.builderIndex);
136
141
  if (!isActiveBuilder(builder, state.finalizedCheckpoint.epoch)) {
137
142
  throw new ExecutionPayloadBidError(GossipAction.REJECT, {
138
143
  code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE,
@@ -1,4 +1,12 @@
1
1
  import {PayloadStatus} from "@lodestar/fork-choice";
2
+ import {
3
+ MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
4
+ MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD,
5
+ MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
6
+ MAX_DEPOSIT_REQUESTS_PER_PAYLOAD,
7
+ MAX_WITHDRAWALS_PER_PAYLOAD,
8
+ MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
9
+ } from "@lodestar/params";
2
10
  import {
3
11
  computeStartSlotAtEpoch,
4
12
  getExecutionPayloadEnvelopeSignatureSet,
@@ -115,6 +123,37 @@ async function validateExecutionPayloadEnvelope(
115
123
  });
116
124
  }
117
125
 
126
+ // [REJECT] The counts of `execution_requests` are within their respective limits.
127
+ // New in Gloas:EIP7688 — progressive lists are unbounded at the type level, so bounds
128
+ // are enforced here in gossip validation.
129
+ const {executionRequests} = envelope;
130
+ const requestCountLimits: [string, number, number][] = [
131
+ ["deposits", executionRequests.deposits.length, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD],
132
+ ["withdrawals", executionRequests.withdrawals.length, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD],
133
+ ["consolidations", executionRequests.consolidations.length, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD],
134
+ ["builderDeposits", executionRequests.builderDeposits.length, MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD],
135
+ ["builderExits", executionRequests.builderExits.length, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD],
136
+ ];
137
+ for (const [name, count, limit] of requestCountLimits) {
138
+ if (count > limit) {
139
+ throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
140
+ code: ExecutionPayloadEnvelopeErrorCode.EXECUTION_REQUESTS_COUNT_EXCEEDED,
141
+ name,
142
+ count,
143
+ limit,
144
+ });
145
+ }
146
+ }
147
+
148
+ // [REJECT] The number of withdrawals is within the limit.
149
+ if (payload.withdrawals.length > MAX_WITHDRAWALS_PER_PAYLOAD) {
150
+ throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
151
+ code: ExecutionPayloadEnvelopeErrorCode.WITHDRAWALS_COUNT_EXCEEDED,
152
+ count: payload.withdrawals.length,
153
+ limit: MAX_WITHDRAWALS_PER_PAYLOAD,
154
+ });
155
+ }
156
+
118
157
  // Get the block state to verify the builder's signature.
119
158
  const blockState = await chain.regen
120
159
  .getState(block.stateRoot, RegenCaller.validateGossipPayloadEnvelope)