@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
@@ -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
 
@@ -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";
@@ -95,6 +96,7 @@ export interface IBeaconChain {
95
96
  readonly earliestAvailableSlot: Slot;
96
97
  readonly executionEngine: IExecutionEngine;
97
98
  readonly executionBuilder?: IExecutionBuilder;
99
+ readonly builderCircuitBreaker: BuilderCircuitBreaker;
98
100
  // Expose config for convenience in modularized functions
99
101
  readonly config: BeaconConfig;
100
102
  readonly custodyConfig: CustodyConfig;
@@ -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
  };
@@ -355,7 +355,7 @@ export class AggregatedAttestationPool {
355
355
  // after all committees are processed, we have a list of sameAttDataCons
356
356
  for (const consolidation of sameAttDataCons) {
357
357
  // Score attestations by profitability to maximize proposer reward
358
- const flags = getAttestationParticipationStatus(
358
+ const {flags} = getAttestationParticipationStatus(
359
359
  ForkSeq[fork],
360
360
  consolidation.attData,
361
361
  inclusionDistance,
@@ -17,7 +17,7 @@ import {toRootHex} from "@lodestar/utils";
17
17
  export class ProposerPreferencesPool {
18
18
  private readonly bySlot = new Map<Slot, Map<RootHex, gloas.SignedProposerPreferences>>();
19
19
 
20
- /** Lookup for bid validation: matches `(bid.slot, get_proposer_dependent_root(parent_state, ...))`. */
20
+ /** Lookup for bid validation: matches `(bid.slot, get_shuffling_dependent_root(store, bid.parent_block_root, epoch))`. */
21
21
  get(slot: Slot, dependentRootHex: RootHex): gloas.SignedProposerPreferences | null {
22
22
  return this.bySlot.get(slot)?.get(dependentRootHex) ?? null;
23
23
  }
@@ -49,6 +49,10 @@ export type IChainOptions = BlockProcessOpts &
49
49
  archiveDateEpochs?: number;
50
50
  nHistoricalStatesFileDataStore?: boolean;
51
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;
52
56
  };
53
57
 
54
58
  export type BlockProcessOpts = {
@@ -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
 
@@ -90,13 +90,19 @@ async function validateAggregateAndProof(
90
90
  });
91
91
  }
92
92
 
93
- // [REJECT] If `aggregate.data.index == 1` (payload present for a past
94
- // block), the execution payload for `block` passes validation.
93
+ // [REJECT] If `aggregate.data.index == 1` (payload present for a past block)
94
+ // the corresponding execution payload for `block` passes validation.
95
95
  // [IGNORE] When `aggregate.data.index == 1` (payload present for a past block),
96
- // the corresponding execution payload for `block` has been seen (a client MAY queue
97
- // attestations for processing once the payload is retrieved and SHOULD request the
98
- // payload envelope via `ExecutionPayloadEnvelopesByRoot`).
99
- if (block !== null && attData.index === 1 && !chain.seenPayloadEnvelope(toRootHex(attData.beaconBlockRoot))) {
96
+ // the corresponding execution payload for `block` has been fully imported, including its
97
+ // data -- i.e. `is_payload_verified(store, aggregate.data.beacon_block_root)` returns `True`
98
+ // (a client MAY queue attestations for processing until the payload is imported and SHOULD
99
+ // request the payload envelope via `ExecutionPayloadEnvelopesByRoot` using
100
+ // `aggregate.data.beacon_block_root`).
101
+ if (
102
+ block !== null &&
103
+ attData.index === 1 &&
104
+ !chain.forkChoice.hasPayloadHexUnsafe(toRootHex(attData.beaconBlockRoot))
105
+ ) {
100
106
  throw new AttestationError(GossipAction.IGNORE, {
101
107
  code: AttestationErrorCode.EXECUTION_PAYLOAD_NOT_SEEN,
102
108
  beaconBlockRoot: toRootHex(attData.beaconBlockRoot),
@@ -316,13 +316,19 @@ async function validateAttestationNoSignatureCheck(
316
316
  });
317
317
  }
318
318
 
319
- // [REJECT] If `attestation.data.index == 1` (payload present for a past
320
- // block), the execution payload for `block` passes validation.
319
+ // [REJECT] If `attestation.data.index == 1` (payload present for a past block),
320
+ // the execution payload for `block` passes validation.
321
321
  // [IGNORE] When `attestation.data.index == 1` (payload present for a past block),
322
- // the corresponding execution payload for `block` has been seen (a client MAY queue
323
- // attestations for processing once the payload is retrieved and SHOULD request the
324
- // payload envelope via `ExecutionPayloadEnvelopesByRoot`).
325
- if (block !== null && attData.index === 1 && !chain.seenPayloadEnvelope(toRootHex(attData.beaconBlockRoot))) {
322
+ // the execution payload for `block` has been fully imported, including its data -- i.e.
323
+ // `is_payload_verified(store, attestation.data.beacon_block_root)` returns `True`
324
+ // (a client MAY queue attestations for processing until the payload is imported and
325
+ // SHOULD request the payload envelope via `ExecutionPayloadEnvelopesByRoot` using
326
+ // `attestation.data.beacon_block_root`).
327
+ if (
328
+ block !== null &&
329
+ attData.index === 1 &&
330
+ !chain.forkChoice.hasPayloadHexUnsafe(toRootHex(attData.beaconBlockRoot))
331
+ ) {
326
332
  throw new AttestationError(GossipAction.IGNORE, {
327
333
  code: AttestationErrorCode.EXECUTION_PAYLOAD_NOT_SEEN,
328
334
  beaconBlockRoot: toRootHex(attData.beaconBlockRoot),
@@ -1,6 +1,21 @@
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_PAYLOAD_ATTESTATIONS,
12
+ MAX_PROPOSER_SLASHINGS,
13
+ MAX_VOLUNTARY_EXITS,
14
+ MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
15
+ isForkPostBellatrix,
16
+ isForkPostDeneb,
17
+ isForkPostGloas,
18
+ } from "@lodestar/params";
4
19
  import {
5
20
  computeEpochAtSlot,
6
21
  computeStartSlotAtEpoch,
@@ -88,8 +103,8 @@ export async function validateGossipBlock(
88
103
  // 2. The parent is unknown to us, we probably want to download it since it might actually
89
104
  // descend from the finalized root.
90
105
  // (Non-Lighthouse): Since we prune all blocks non-descendant from finalized checking the `db.block` database won't be useful to guard
91
- // against known bad fork blocks, so we throw PARENT_UNKNOWN for cases (1) and (2)
92
- throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_UNKNOWN, parentRoot});
106
+ // against known bad fork blocks, so we throw PARENT_BLOCK_UNKNOWN for cases (1) and (2)
107
+ throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_BLOCK_UNKNOWN, parentRoot});
93
108
  }
94
109
 
95
110
  // [IGNORE] The block's parent (defined by `block.parent_root`) passes all validation
@@ -164,6 +179,56 @@ export async function validateGossipBlock(
164
179
  });
165
180
  }
166
181
 
182
+ // [REJECT] The counts of `block.body.parent_execution_requests` are within
183
+ // their respective limits -- i.e. validate that
184
+ // `len(block.body.parent_execution_requests.withdrawals) <= MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD`,
185
+ // `len(block.body.parent_execution_requests.consolidations) <= MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD`,
186
+ // `len(block.body.parent_execution_requests.builder_deposits) <= MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD`,
187
+ // and
188
+ // `len(block.body.parent_execution_requests.builder_exits) <= MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD`.
189
+ // [REJECT] The counts of the block body operations are within their respective
190
+ // limits -- i.e. validate that
191
+ // `len(block.body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS`,
192
+ // `len(block.body.attester_slashings) <= MAX_ATTESTER_SLASHINGS_ELECTRA`,
193
+ // `len(block.body.attestations) <= MAX_ATTESTATIONS_ELECTRA`,
194
+ // `len(block.body.deposits) == 0`,
195
+ // `len(block.body.voluntary_exits) <= MAX_VOLUNTARY_EXITS`,
196
+ // `len(block.body.bls_to_execution_changes) <= MAX_BLS_TO_EXECUTION_CHANGES`,
197
+ // and `len(block.body.payload_attestations) <= MAX_PAYLOAD_ATTESTATIONS`.
198
+ const body = (block as gloas.BeaconBlock).body;
199
+ const requests = body.parentExecutionRequests;
200
+ const countLimits: [string, number, number][] = [
201
+ ["parentExecutionRequests.withdrawals", requests.withdrawals.length, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD],
202
+ [
203
+ "parentExecutionRequests.consolidations",
204
+ requests.consolidations.length,
205
+ MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
206
+ ],
207
+ [
208
+ "parentExecutionRequests.builderDeposits",
209
+ requests.builderDeposits.length,
210
+ MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
211
+ ],
212
+ ["parentExecutionRequests.builderExits", requests.builderExits.length, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD],
213
+ ["proposerSlashings", body.proposerSlashings.length, MAX_PROPOSER_SLASHINGS],
214
+ ["attesterSlashings", body.attesterSlashings.length, MAX_ATTESTER_SLASHINGS_ELECTRA],
215
+ ["attestations", body.attestations.length, MAX_ATTESTATIONS_ELECTRA],
216
+ ["deposits", body.deposits.length, 0],
217
+ ["voluntaryExits", body.voluntaryExits.length, MAX_VOLUNTARY_EXITS],
218
+ ["blsToExecutionChanges", body.blsToExecutionChanges.length, MAX_BLS_TO_EXECUTION_CHANGES],
219
+ ["payloadAttestations", body.payloadAttestations.length, MAX_PAYLOAD_ATTESTATIONS],
220
+ ];
221
+ for (const [name, count, limit] of countLimits) {
222
+ if (count > limit) {
223
+ throw new BlockGossipError(GossipAction.REJECT, {
224
+ code: BlockErrorCode.TOO_MANY_BLOCK_OPERATIONS,
225
+ name,
226
+ count,
227
+ limit,
228
+ });
229
+ }
230
+ }
231
+
167
232
  // TODO GLOAS: [REJECT] The block's execution payload parent (defined by bid.parent_block_hash) passes all validation
168
233
  // This requires execution engine integration to verify the parent block hash
169
234
  }
@@ -176,7 +241,7 @@ export async function validateGossipBlock(
176
241
  const blockState = await chain.regen
177
242
  .getPreState(block, {dontTransferCache: true}, RegenCaller.validateGossipBlock)
178
243
  .catch(() => {
179
- throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_UNKNOWN, parentRoot});
244
+ throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.PARENT_BLOCK_UNKNOWN, parentRoot});
180
245
  });
181
246
 
182
247
  // in forky condition, make sure to populate ShufflingCache with regened state
@@ -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,
@@ -160,9 +165,9 @@ async function validateExecutionPayloadBid(
160
165
  });
161
166
  }
162
167
 
163
- // [REJECT] `bid.fee_recipient == proposer_preferences.fee_recipient`.
168
+ // [IGNORE] `bid.fee_recipient == proposer_preferences.fee_recipient`.
164
169
  if (!byteArrayEquals(bid.feeRecipient, proposerPreferences.message.feeRecipient)) {
165
- throw new ExecutionPayloadBidError(GossipAction.REJECT, {
170
+ throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
166
171
  code: ExecutionPayloadBidErrorCode.PROPOSER_PREFERENCES_FEE_RECIPIENT_MISMATCH,
167
172
  builderIndex: bid.builderIndex,
168
173
  bidFeeRecipient: toHex(bid.feeRecipient),
@@ -1,4 +1,11 @@
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_WITHDRAWALS_PER_PAYLOAD,
7
+ MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
8
+ } from "@lodestar/params";
2
9
  import {
3
10
  computeStartSlotAtEpoch,
4
11
  getExecutionPayloadEnvelopeSignatureSet,
@@ -115,6 +122,36 @@ async function validateExecutionPayloadEnvelope(
115
122
  });
116
123
  }
117
124
 
125
+ // [REJECT] The counts of `execution_requests` are within their respective limits.
126
+ // New in Gloas:EIP7688 — progressive lists are unbounded at the type level, so bounds
127
+ // are enforced here in gossip validation.
128
+ const {executionRequests} = envelope;
129
+ const requestCountLimits: [string, number, number][] = [
130
+ ["withdrawals", executionRequests.withdrawals.length, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD],
131
+ ["consolidations", executionRequests.consolidations.length, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD],
132
+ ["builderDeposits", executionRequests.builderDeposits.length, MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD],
133
+ ["builderExits", executionRequests.builderExits.length, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD],
134
+ ];
135
+ for (const [name, count, limit] of requestCountLimits) {
136
+ if (count > limit) {
137
+ throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
138
+ code: ExecutionPayloadEnvelopeErrorCode.EXECUTION_REQUESTS_COUNT_EXCEEDED,
139
+ name,
140
+ count,
141
+ limit,
142
+ });
143
+ }
144
+ }
145
+
146
+ // [REJECT] The number of withdrawals is within the limit.
147
+ if (payload.withdrawals.length > MAX_WITHDRAWALS_PER_PAYLOAD) {
148
+ throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
149
+ code: ExecutionPayloadEnvelopeErrorCode.WITHDRAWALS_COUNT_EXCEEDED,
150
+ count: payload.withdrawals.length,
151
+ limit: MAX_WITHDRAWALS_PER_PAYLOAD,
152
+ });
153
+ }
154
+
118
155
  // Get the block state to verify the builder's signature.
119
156
  const blockState = await chain.regen
120
157
  .getState(block.stateRoot, RegenCaller.validateGossipPayloadEnvelope)
@@ -32,8 +32,9 @@ export async function validateGossipProposerPreferences(
32
32
  });
33
33
  }
34
34
 
35
- // [IGNORE] `preferences.proposal_slot` has not already passed.
36
- const currentSlot = chain.clock.currentSlot;
35
+ // [IGNORE] `preferences.proposal_slot` has not already passed, i.e. `proposal_slot > current_slot`,
36
+ // allowing for `MAXIMUM_GOSSIP_CLOCK_DISPARITY`.
37
+ const currentSlot = chain.clock.currentSlotWithGossipDisparity;
37
38
  if (proposalSlot <= currentSlot) {
38
39
  throw new ProposerPreferencesError(GossipAction.IGNORE, {
39
40
  code: ProposerPreferencesErrorCode.PROPOSAL_SLOT_PASSED,
@@ -1,7 +1,7 @@
1
1
  import {BeaconConfig} from "@lodestar/config";
2
- import {DOMAIN_AGGREGATE_AND_PROOF, ForkSeq} from "@lodestar/params";
2
+ import {DOMAIN_AGGREGATE_AND_PROOF} from "@lodestar/params";
3
3
  import {ISignatureSet, SignatureSetType, computeSigningRoot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
4
- import {Epoch, SignedAggregateAndProof, ValidatorIndex, ssz} from "@lodestar/types";
4
+ import {Epoch, SignedAggregateAndProof, ValidatorIndex} from "@lodestar/types";
5
5
 
6
6
  export function getAggregateAndProofSigningRoot(
7
7
  config: BeaconConfig,
@@ -14,7 +14,7 @@ export function getAggregateAndProofSigningRoot(
14
14
  const slot = computeStartSlotAtEpoch(epoch);
15
15
  const fork = config.getForkName(slot);
16
16
  const aggregatorDomain = config.getDomainAtFork(fork, DOMAIN_AGGREGATE_AND_PROOF);
17
- const sszType = ForkSeq[fork] >= ForkSeq.electra ? ssz.electra.AggregateAndProof : ssz.phase0.AggregateAndProof;
17
+ const sszType = config.getForkTypes(slot).AggregateAndProof;
18
18
  return computeSigningRoot(sszType, aggregateAndProof.message, aggregatorDomain);
19
19
  }
20
20