@lodestar/beacon-node 1.45.0-dev.77f7452c30 → 1.45.0-dev.7a06691aa8

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 (209) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +186 -57
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/index.d.ts +1 -1
  5. package/lib/api/impl/beacon/index.d.ts.map +1 -1
  6. package/lib/api/impl/beacon/index.js.map +1 -1
  7. package/lib/api/impl/beacon/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 +2 -2
  10. package/lib/api/impl/beacon/state/index.js.map +1 -1
  11. package/lib/api/impl/beacon/state/utils.d.ts +2 -1
  12. package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
  13. package/lib/api/impl/beacon/state/utils.js +9 -1
  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 +192 -143
  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 +14 -3
  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 +2 -17
  38. package/lib/chain/blocks/importBlock.js.map +1 -1
  39. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  40. package/lib/chain/blocks/utils/chainSegment.js +13 -5
  41. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  42. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +1 -1
  43. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
  44. package/lib/chain/blocks/verifyBlocksSanityChecks.js +1 -1
  45. package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
  46. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  47. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  48. package/lib/chain/builderCircuitBreaker.d.ts +31 -0
  49. package/lib/chain/builderCircuitBreaker.d.ts.map +1 -0
  50. package/lib/chain/builderCircuitBreaker.js +59 -0
  51. package/lib/chain/builderCircuitBreaker.js.map +1 -0
  52. package/lib/chain/chain.d.ts +2 -0
  53. package/lib/chain/chain.d.ts.map +1 -1
  54. package/lib/chain/chain.js +26 -2
  55. package/lib/chain/chain.js.map +1 -1
  56. package/lib/chain/errors/blockError.d.ts +48 -6
  57. package/lib/chain/errors/blockError.d.ts.map +1 -1
  58. package/lib/chain/errors/blockError.js +25 -4
  59. package/lib/chain/errors/blockError.js.map +1 -1
  60. package/lib/chain/errors/executionPayloadEnvelope.d.ts +11 -0
  61. package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
  62. package/lib/chain/errors/executionPayloadEnvelope.js +2 -0
  63. package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
  64. package/lib/chain/errors/proposerPreferences.d.ts +14 -1
  65. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
  66. package/lib/chain/errors/proposerPreferences.js +2 -0
  67. package/lib/chain/errors/proposerPreferences.js.map +1 -1
  68. package/lib/chain/interface.d.ts +2 -0
  69. package/lib/chain/interface.d.ts.map +1 -1
  70. package/lib/chain/interface.js.map +1 -1
  71. package/lib/chain/lightClient/index.d.ts.map +1 -1
  72. package/lib/chain/lightClient/index.js +11 -6
  73. package/lib/chain/lightClient/index.js.map +1 -1
  74. package/lib/chain/lightClient/proofs.d.ts.map +1 -1
  75. package/lib/chain/lightClient/proofs.js +28 -2
  76. package/lib/chain/lightClient/proofs.js.map +1 -1
  77. package/lib/chain/lightClient/types.d.ts +3 -1
  78. package/lib/chain/lightClient/types.d.ts.map +1 -1
  79. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  80. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  81. package/lib/chain/opPools/proposerPreferencesPool.d.ts +1 -1
  82. package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
  83. package/lib/chain/opPools/proposerPreferencesPool.js +1 -1
  84. package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
  85. package/lib/chain/options.d.ts +4 -0
  86. package/lib/chain/options.d.ts.map +1 -1
  87. package/lib/chain/options.js.map +1 -1
  88. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  89. package/lib/chain/prepareNextSlot.js +12 -7
  90. package/lib/chain/prepareNextSlot.js.map +1 -1
  91. package/lib/chain/validation/aggregateAndProof.js +10 -6
  92. package/lib/chain/validation/aggregateAndProof.js.map +1 -1
  93. package/lib/chain/validation/attestation.d.ts.map +1 -1
  94. package/lib/chain/validation/attestation.js +10 -6
  95. package/lib/chain/validation/attestation.js.map +1 -1
  96. package/lib/chain/validation/block.d.ts.map +1 -1
  97. package/lib/chain/validation/block.js +53 -4
  98. package/lib/chain/validation/block.js.map +1 -1
  99. package/lib/chain/validation/executionPayloadBid.js +2 -2
  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 +9 -2
  104. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  105. package/lib/chain/validation/proposerPreferences.js +61 -9
  106. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  107. package/lib/chain/validation/signatureSets/aggregateAndProof.d.ts.map +1 -1
  108. package/lib/chain/validation/signatureSets/aggregateAndProof.js +2 -3
  109. package/lib/chain/validation/signatureSets/aggregateAndProof.js.map +1 -1
  110. package/lib/db/repositories/lightclientSyncCommitteeWitness.d.ts.map +1 -1
  111. package/lib/db/repositories/lightclientSyncCommitteeWitness.js +42 -7
  112. package/lib/db/repositories/lightclientSyncCommitteeWitness.js.map +1 -1
  113. package/lib/execution/builder/http.d.ts +18 -0
  114. package/lib/execution/builder/http.d.ts.map +1 -1
  115. package/lib/execution/builder/http.js +20 -11
  116. package/lib/execution/builder/http.js.map +1 -1
  117. package/lib/metrics/metrics/beacon.d.ts +6 -0
  118. package/lib/metrics/metrics/beacon.d.ts.map +1 -1
  119. package/lib/metrics/metrics/beacon.js +18 -0
  120. package/lib/metrics/metrics/beacon.js.map +1 -1
  121. package/lib/network/gossip/encoding.d.ts +2 -2
  122. package/lib/network/gossip/encoding.d.ts.map +1 -1
  123. package/lib/network/gossip/encoding.js +15 -9
  124. package/lib/network/gossip/encoding.js.map +1 -1
  125. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  126. package/lib/network/gossip/gossipsub.js +2 -2
  127. package/lib/network/gossip/gossipsub.js.map +1 -1
  128. package/lib/network/gossip/topic.d.ts +74 -231
  129. package/lib/network/gossip/topic.d.ts.map +1 -1
  130. package/lib/network/gossip/topic.js +24 -1
  131. package/lib/network/gossip/topic.js.map +1 -1
  132. package/lib/network/interface.d.ts +4 -1
  133. package/lib/network/interface.d.ts.map +1 -1
  134. package/lib/network/network.d.ts +4 -1
  135. package/lib/network/network.d.ts.map +1 -1
  136. package/lib/network/network.js +19 -11
  137. package/lib/network/network.js.map +1 -1
  138. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  139. package/lib/network/processor/gossipHandlers.js +12 -6
  140. package/lib/network/processor/gossipHandlers.js.map +1 -1
  141. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
  142. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +4 -2
  143. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
  144. package/lib/network/reqresp/handlers/index.d.ts.map +1 -1
  145. package/lib/network/reqresp/handlers/index.js +20 -11
  146. package/lib/network/reqresp/handlers/index.js.map +1 -1
  147. package/lib/network/util.d.ts +1 -0
  148. package/lib/network/util.d.ts.map +1 -1
  149. package/lib/network/util.js +4 -0
  150. package/lib/network/util.js.map +1 -1
  151. package/lib/sync/sync.d.ts.map +1 -1
  152. package/lib/sync/sync.js +7 -0
  153. package/lib/sync/sync.js.map +1 -1
  154. package/lib/sync/unknownBlock.d.ts.map +1 -1
  155. package/lib/sync/unknownBlock.js +7 -2
  156. package/lib/sync/unknownBlock.js.map +1 -1
  157. package/lib/util/execution.js +1 -1
  158. package/lib/util/execution.js.map +1 -1
  159. package/package.json +19 -17
  160. package/src/api/impl/beacon/blocks/index.ts +221 -63
  161. package/src/api/impl/beacon/index.ts +1 -1
  162. package/src/api/impl/beacon/state/index.ts +3 -2
  163. package/src/api/impl/beacon/state/utils.ts +11 -0
  164. package/src/api/impl/debug/index.ts +3 -2
  165. package/src/api/impl/lodestar/index.ts +2 -2
  166. package/src/api/impl/proof/index.ts +2 -2
  167. package/src/api/impl/utils.ts +51 -1
  168. package/src/api/impl/validator/index.ts +224 -157
  169. package/src/api/rest/base.ts +17 -3
  170. package/src/chain/blocks/importBlock.ts +0 -17
  171. package/src/chain/blocks/utils/chainSegment.ts +16 -5
  172. package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +1 -1
  173. package/src/chain/blocks/verifyBlocksSanityChecks.ts +1 -1
  174. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -2
  175. package/src/chain/builderCircuitBreaker.ts +84 -0
  176. package/src/chain/chain.ts +38 -2
  177. package/src/chain/errors/blockError.ts +44 -7
  178. package/src/chain/errors/executionPayloadEnvelope.ts +9 -0
  179. package/src/chain/errors/proposerPreferences.ts +16 -1
  180. package/src/chain/interface.ts +2 -0
  181. package/src/chain/lightClient/index.ts +12 -7
  182. package/src/chain/lightClient/proofs.ts +36 -1
  183. package/src/chain/lightClient/types.ts +3 -1
  184. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  185. package/src/chain/opPools/proposerPreferencesPool.ts +1 -1
  186. package/src/chain/options.ts +4 -0
  187. package/src/chain/prepareNextSlot.ts +11 -7
  188. package/src/chain/validation/aggregateAndProof.ts +12 -6
  189. package/src/chain/validation/attestation.ts +12 -6
  190. package/src/chain/validation/block.ts +69 -4
  191. package/src/chain/validation/executionPayloadBid.ts +2 -2
  192. package/src/chain/validation/executionPayloadEnvelope.ts +37 -0
  193. package/src/chain/validation/proposerPreferences.ts +71 -9
  194. package/src/chain/validation/signatureSets/aggregateAndProof.ts +3 -3
  195. package/src/db/repositories/lightclientSyncCommitteeWitness.ts +57 -9
  196. package/src/execution/builder/http.ts +30 -17
  197. package/src/metrics/metrics/beacon.ts +19 -0
  198. package/src/network/gossip/encoding.ts +13 -7
  199. package/src/network/gossip/gossipsub.ts +7 -2
  200. package/src/network/gossip/topic.ts +30 -0
  201. package/src/network/interface.ts +3 -1
  202. package/src/network/network.ts +24 -15
  203. package/src/network/processor/gossipHandlers.ts +12 -6
  204. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +4 -2
  205. package/src/network/reqresp/handlers/index.ts +21 -12
  206. package/src/network/util.ts +5 -0
  207. package/src/sync/sync.ts +10 -0
  208. package/src/sync/unknownBlock.ts +8 -2
  209. package/src/util/execution.ts +1 -1
@@ -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
@@ -165,9 +165,9 @@ async function validateExecutionPayloadBid(
165
165
  });
166
166
  }
167
167
 
168
- // [REJECT] `bid.fee_recipient == proposer_preferences.fee_recipient`.
168
+ // [IGNORE] `bid.fee_recipient == proposer_preferences.fee_recipient`.
169
169
  if (!byteArrayEquals(bid.feeRecipient, proposerPreferences.message.feeRecipient)) {
170
- throw new ExecutionPayloadBidError(GossipAction.REJECT, {
170
+ throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
171
171
  code: ExecutionPayloadBidErrorCode.PROPOSER_PREFERENCES_FEE_RECIPIENT_MISMATCH,
172
172
  builderIndex: bid.builderIndex,
173
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)
@@ -1,17 +1,19 @@
1
- import {SLOTS_PER_EPOCH} from "@lodestar/params";
1
+ import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
2
+ import {GENESIS_SLOT, MIN_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params";
2
3
  import {
3
4
  computeEpochAtSlot,
5
+ computeStartSlotAtEpoch,
4
6
  createSingleSignatureSetFromComponents,
5
7
  getProposerPreferencesSigningRoot,
6
8
  } from "@lodestar/state-transition";
7
- import {ValidatorIndex, gloas} from "@lodestar/types";
9
+ import {Epoch, Slot, ValidatorIndex, gloas} from "@lodestar/types";
8
10
  import {toRootHex} from "@lodestar/utils";
9
11
  import {GossipAction, ProposerPreferencesError, ProposerPreferencesErrorCode} from "../errors/index.js";
10
12
  import {IBeaconChain} from "../index.js";
11
13
 
12
14
  /**
13
15
  * Validates a gossiped `SignedProposerPreferences` per
14
- * https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/p2p-interface.md#proposer_preferences
16
+ * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/gloas/p2p-interface.md#new-proposer_preferences
15
17
  */
16
18
  export async function validateGossipProposerPreferences(
17
19
  chain: IBeaconChain,
@@ -22,9 +24,9 @@ export async function validateGossipProposerPreferences(
22
24
  const dependentRootHex = toRootHex(dependentRoot);
23
25
  const proposalEpoch = computeEpochAtSlot(proposalSlot);
24
26
 
25
- // [IGNORE] `preferences.proposal_slot` is in the current or next epoch.
27
+ // [IGNORE] `preferences.proposal_slot` is within the proposer lookahead.
26
28
  const currentEpoch = chain.clock.currentEpoch;
27
- if (proposalEpoch < currentEpoch || proposalEpoch > currentEpoch + 1) {
29
+ if (proposalEpoch < currentEpoch || proposalEpoch > currentEpoch + MIN_SEED_LOOKAHEAD) {
28
30
  throw new ProposerPreferencesError(GossipAction.IGNORE, {
29
31
  code: ProposerPreferencesErrorCode.INVALID_EPOCH,
30
32
  proposalSlot,
@@ -44,21 +46,56 @@ export async function validateGossipProposerPreferences(
44
46
  }
45
47
 
46
48
  // [IGNORE] The block with root `dependent_root` has been seen by the node.
49
+ const dependentBlock = chain.forkChoice.getBlockHexDefaultStatus(dependentRootHex);
50
+ if (dependentBlock === null) {
51
+ throw new ProposerPreferencesError(GossipAction.IGNORE, {
52
+ code: ProposerPreferencesErrorCode.UNKNOWN_DEPENDENT_ROOT,
53
+ proposalSlot,
54
+ dependentRoot: dependentRootHex,
55
+ });
56
+ }
57
+
58
+ const dependentEpoch = proposalEpoch - MIN_SEED_LOOKAHEAD;
59
+ const dependentRootSlot = getDependentRootSlot(dependentEpoch);
60
+
61
+ // [REJECT] The slot of the block with root `preferences.dependent_root` is strictly less than
62
+ // `compute_start_slot_at_epoch(compute_epoch_at_slot(preferences.proposal_slot) - MIN_SEED_LOOKAHEAD)`.
63
+ if (dependentBlock.slot > dependentRootSlot) {
64
+ throw new ProposerPreferencesError(GossipAction.REJECT, {
65
+ code: ProposerPreferencesErrorCode.INVALID_DEPENDENT_ROOT_SLOT,
66
+ dependentRoot: dependentRootHex,
67
+ dependentBlockSlot: dependentBlock.slot,
68
+ dependentRootSlot,
69
+ dependentEpoch,
70
+ });
71
+ }
72
+
73
+ // [IGNORE] `is_valid_dependent_root(store, preferences.dependent_root, epoch)` returns `True`,
74
+ // where `epoch` is `compute_epoch_at_slot(preferences.proposal_slot) - MIN_SEED_LOOKAHEAD`.
75
+ if (!isValidDependentRoot(chain.forkChoice, dependentBlock, dependentEpoch)) {
76
+ throw new ProposerPreferencesError(GossipAction.IGNORE, {
77
+ code: ProposerPreferencesErrorCode.INVALID_DEPENDENT_ROOT,
78
+ dependentRoot: dependentRootHex,
79
+ dependentBlockSlot: dependentBlock.slot,
80
+ dependentEpoch,
81
+ });
82
+ }
83
+
47
84
  // Resolve the proposer lookahead for the message's branch via head state (fast path) or
48
85
  // the previous-root checkpoint state (populated by `processSlotsToNearestCheckpoint` for
49
- // any imported branch crossing into `proposalEpoch - 1`). The head-state path also handles
86
+ // any imported branch crossing into `dependentEpoch`). The head-state path also handles
50
87
  // narrow timing windows where the checkpoint state isn't yet populated.
51
88
  const headState = chain.getHeadState();
52
89
  let proposers: ValidatorIndex[] | null = null;
53
90
  if (headState.epoch === proposalEpoch && headState.currentDecisionRoot === dependentRootHex) {
54
91
  proposers = headState.currentProposers;
55
- } else if (headState.epoch === proposalEpoch - 1 && headState.nextDecisionRoot === dependentRootHex) {
92
+ } else if (headState.epoch === dependentEpoch && headState.nextDecisionRoot === dependentRootHex) {
56
93
  proposers = headState.nextProposers;
57
94
  } else {
58
95
  // Sync lookup only to not trigger disk reload from gossip input.
59
- const checkpointState = chain.regen.getCheckpointStateSync({epoch: proposalEpoch - 1, rootHex: dependentRootHex});
96
+ const checkpointState = chain.regen.getCheckpointStateSync({epoch: dependentEpoch, rootHex: dependentRootHex});
60
97
  if (checkpointState !== null) {
61
- // State is at `proposalEpoch - 1`, so proposers for `proposalSlot` (next epoch from
98
+ // State is at `dependentEpoch`, so proposers for `proposalSlot` (next epoch from
62
99
  // the state's perspective) live in `nextProposers`.
63
100
  proposers = checkpointState.nextProposers;
64
101
  }
@@ -118,3 +155,28 @@ export async function validateGossipProposerPreferences(
118
155
 
119
156
  chain.proposerPreferencesPool.add(signedProposerPreferences);
120
157
  }
158
+
159
+ /**
160
+ * Check whether `dependentBlock` is a viable shuffling-dependent root for `epoch`:
161
+ * it is before the epoch boundary and either has a direct child at or after
162
+ * the boundary, or is the current fork-choice head.
163
+ */
164
+ export function isValidDependentRoot(forkChoice: IForkChoice, dependentBlock: ProtoBlock, epoch: Epoch): boolean {
165
+ const epochStartSlot = computeStartSlotAtEpoch(epoch);
166
+ if (dependentBlock.slot > getDependentRootSlot(epoch)) {
167
+ return false;
168
+ }
169
+
170
+ for (const block of forkChoice.getBlockSummariesByParentRoot(dependentBlock.blockRoot)) {
171
+ if (block.slot >= epochStartSlot) {
172
+ return true;
173
+ }
174
+ }
175
+
176
+ return dependentBlock.blockRoot === forkChoice.getHeadRoot();
177
+ }
178
+
179
+ /** Return the slot used to select a dependent root for `epoch`, clamped to genesis. */
180
+ function getDependentRootSlot(epoch: Epoch): Slot {
181
+ return Math.max(GENESIS_SLOT, computeStartSlotAtEpoch(epoch) - 1);
182
+ }
@@ -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
 
@@ -1,14 +1,16 @@
1
1
  import {ContainerType, VectorCompositeType} from "@chainsafe/ssz";
2
2
  import {ChainForkConfig} from "@lodestar/config";
3
3
  import {DatabaseController, Repository} from "@lodestar/db";
4
+ import {CURRENT_SYNC_COMMITTEE_DEPTH_GLOAS, NEXT_SYNC_COMMITTEE_DEPTH_GLOAS} from "@lodestar/params";
4
5
  import {ssz} from "@lodestar/types";
5
6
  import {SyncCommitteeWitness} from "../../chain/lightClient/types.js";
6
7
  import {Bucket, getBucketNameByValue} from "../buckets.js";
7
8
 
8
- // We add a 1-byte prefix where 0 means pre-electra and 1 means post-electra
9
+ // We add a 1-byte prefix where 0 means pre-Electra, 1 means post-Electra, and 2 means post-Gloas.
9
10
  enum PrefixByte {
10
11
  PRE_ELECTRA = 0,
11
12
  POST_ELECTRA = 1,
13
+ POST_GLOAS = 2,
12
14
  }
13
15
 
14
16
  export const NUM_WITNESS = 4;
@@ -35,6 +37,35 @@ export class SyncCommitteeWitnessRepository extends Repository<Uint8Array, SyncC
35
37
  // Overrides for multi-fork
36
38
  encodeValue(value: SyncCommitteeWitness): Uint8Array {
37
39
  const numWitness = value.witness.length;
40
+ const hasGloasBranches =
41
+ value.currentSyncCommitteeBranch !== undefined || value.nextSyncCommitteeBranch !== undefined;
42
+
43
+ if (hasGloasBranches) {
44
+ if (
45
+ value.currentSyncCommitteeBranch?.length !== CURRENT_SYNC_COMMITTEE_DEPTH_GLOAS ||
46
+ value.nextSyncCommitteeBranch?.length !== NEXT_SYNC_COMMITTEE_DEPTH_GLOAS
47
+ ) {
48
+ throw Error(
49
+ `Invalid post-Gloas sync committee branch lengths current=${value.currentSyncCommitteeBranch?.length} next=${value.nextSyncCommitteeBranch?.length}`
50
+ );
51
+ }
52
+
53
+ const type = new ContainerType({
54
+ currentSyncCommitteeBranch: new VectorCompositeType(ssz.Root, CURRENT_SYNC_COMMITTEE_DEPTH_GLOAS),
55
+ nextSyncCommitteeBranch: new VectorCompositeType(ssz.Root, NEXT_SYNC_COMMITTEE_DEPTH_GLOAS),
56
+ currentSyncCommitteeRoot: ssz.Root,
57
+ nextSyncCommitteeRoot: ssz.Root,
58
+ });
59
+
60
+ const valueBytes = type.serialize({
61
+ currentSyncCommitteeBranch: value.currentSyncCommitteeBranch,
62
+ nextSyncCommitteeBranch: value.nextSyncCommitteeBranch,
63
+ currentSyncCommitteeRoot: value.currentSyncCommitteeRoot,
64
+ nextSyncCommitteeRoot: value.nextSyncCommitteeRoot,
65
+ });
66
+
67
+ return prefixData(PrefixByte.POST_GLOAS, valueBytes);
68
+ }
38
69
 
39
70
  if (numWitness !== NUM_WITNESS && numWitness !== NUM_WITNESS_ELECTRA) {
40
71
  throw Error(`Number of witness can only be 4 pre-electra or 5 post-electra numWitness=${numWitness}`);
@@ -51,19 +82,25 @@ export class SyncCommitteeWitnessRepository extends Repository<Uint8Array, SyncC
51
82
  // We need to differentiate between post-electra and pre-electra witness
52
83
  // such that we can deserialize correctly
53
84
  const isPostElectra = numWitness === NUM_WITNESS_ELECTRA;
54
- const prefixByte = new Uint8Array(1);
55
- prefixByte[0] = isPostElectra ? PrefixByte.POST_ELECTRA : PrefixByte.PRE_ELECTRA;
56
-
57
- const prefixedData = new Uint8Array(1 + valueBytes.length);
58
- prefixedData.set(prefixByte, 0);
59
- prefixedData.set(valueBytes, 1);
60
-
61
- return prefixedData;
85
+ return prefixData(isPostElectra ? PrefixByte.POST_ELECTRA : PrefixByte.PRE_ELECTRA, valueBytes);
62
86
  }
63
87
 
64
88
  decodeValue(data: Uint8Array): SyncCommitteeWitness {
65
89
  // First byte is written
66
90
  const prefix = data.subarray(0, 1);
91
+ const isPostGloas = prefix[0] === PrefixByte.POST_GLOAS;
92
+
93
+ if (isPostGloas) {
94
+ const type = new ContainerType({
95
+ currentSyncCommitteeBranch: new VectorCompositeType(ssz.Root, CURRENT_SYNC_COMMITTEE_DEPTH_GLOAS),
96
+ nextSyncCommitteeBranch: new VectorCompositeType(ssz.Root, NEXT_SYNC_COMMITTEE_DEPTH_GLOAS),
97
+ currentSyncCommitteeRoot: ssz.Root,
98
+ nextSyncCommitteeRoot: ssz.Root,
99
+ });
100
+
101
+ return {witness: [], ...type.deserialize(data.subarray(1))};
102
+ }
103
+
67
104
  const isPostElectra = prefix[0] === PrefixByte.POST_ELECTRA;
68
105
 
69
106
  const type = new ContainerType({
@@ -75,3 +112,14 @@ export class SyncCommitteeWitnessRepository extends Repository<Uint8Array, SyncC
75
112
  return type.deserialize(data.subarray(1));
76
113
  }
77
114
  }
115
+
116
+ function prefixData(prefix: PrefixByte, valueBytes: Uint8Array): Uint8Array {
117
+ const prefixByte = new Uint8Array(1);
118
+ prefixByte[0] = prefix;
119
+
120
+ const prefixedData = new Uint8Array(1 + valueBytes.length);
121
+ prefixedData.set(prefixByte, 0);
122
+ prefixedData.set(valueBytes, 1);
123
+
124
+ return prefixedData;
125
+ }
@@ -70,6 +70,33 @@ export class NoBidReceived extends Error {
70
70
  }
71
71
  }
72
72
 
73
+ /**
74
+ * Beacon clients select randomized values from the following ranges when initializing
75
+ * the circuit breaker (so at boot time and once for each unique boot).
76
+ *
77
+ * ALLOWED_FAULTS: between 1 and SLOTS_PER_EPOCH // 4
78
+ * FAULT_INSPECTION_WINDOW: between SLOTS_PER_EPOCH and 2 * SLOTS_PER_EPOCH
79
+ *
80
+ * The values are randomized per node so builders cannot predict when a given proposer will
81
+ * fall back to local blocks. With fixed thresholds a builder could withhold payloads right up
82
+ * to the limit without ever tripping the breaker.
83
+ */
84
+ export function getFaultInspectionParams(opts: {faultInspectionWindow?: number; allowedFaults?: number}): {
85
+ faultInspectionWindow: number;
86
+ allowedFaults: number;
87
+ } {
88
+ const faultInspectionWindow = Math.max(
89
+ opts.faultInspectionWindow ?? SLOTS_PER_EPOCH + Math.floor(Math.random() * SLOTS_PER_EPOCH),
90
+ SLOTS_PER_EPOCH
91
+ );
92
+ // allowedFaults should be < faultInspectionWindow, limiting them to faultInspectionWindow/4
93
+ const allowedFaults = Math.min(
94
+ opts.allowedFaults ?? Math.floor(faultInspectionWindow / 4),
95
+ Math.floor(faultInspectionWindow / 4)
96
+ );
97
+ return {faultInspectionWindow, allowedFaults};
98
+ }
99
+
73
100
  /**
74
101
  * Additional duration to account for potential event loop lag which causes
75
102
  * builder blocks to be rejected even though the response was sent in time.
@@ -122,23 +149,9 @@ export class ExecutionBuilderHttp implements IExecutionBuilder {
122
149
  this.registrations = new ValidatorRegistrationCache();
123
150
  this.issueLocalFcUWithFeeRecipient = opts.issueLocalFcUWithFeeRecipient;
124
151
 
125
- /**
126
- * Beacon clients select randomized values from the following ranges when initializing
127
- * the circuit breaker (so at boot time and once for each unique boot).
128
- *
129
- * ALLOWED_FAULTS: between 1 and SLOTS_PER_EPOCH // 4
130
- * FAULT_INSPECTION_WINDOW: between SLOTS_PER_EPOCH and 2 * SLOTS_PER_EPOCH
131
- *
132
- */
133
- this.faultInspectionWindow = Math.max(
134
- opts.faultInspectionWindow ?? SLOTS_PER_EPOCH + Math.floor(Math.random() * SLOTS_PER_EPOCH),
135
- SLOTS_PER_EPOCH
136
- );
137
- // allowedFaults should be < faultInspectionWindow, limiting them to faultInspectionWindow/4
138
- this.allowedFaults = Math.min(
139
- opts.allowedFaults ?? Math.floor(this.faultInspectionWindow / 4),
140
- Math.floor(this.faultInspectionWindow / 4)
141
- );
152
+ const {faultInspectionWindow, allowedFaults} = getFaultInspectionParams(opts);
153
+ this.faultInspectionWindow = faultInspectionWindow;
154
+ this.allowedFaults = allowedFaults;
142
155
  }
143
156
 
144
157
  updateStatus(status: BuilderStatus): void {
@@ -138,6 +138,25 @@ export function createBeaconMetrics(register: RegistryMetricCreator) {
138
138
  help: "Count of cached produced results",
139
139
  }),
140
140
 
141
+ builderCircuitBreaker: {
142
+ active: register.gauge({
143
+ name: "beacon_builder_circuit_breaker_active",
144
+ help: "Whether the builder circuit breaker is active (1) causing builder bids to be ignored",
145
+ }),
146
+ faults: register.gauge({
147
+ name: "beacon_builder_circuit_breaker_faults",
148
+ help: "Count of blocks with unrevealed payloads in the fault inspection window",
149
+ }),
150
+ blocksPresent: register.gauge({
151
+ name: "beacon_builder_circuit_breaker_blocks_present",
152
+ help: "Count of blocks present in the fault inspection window",
153
+ }),
154
+ payloadsRevealed: register.gauge({
155
+ name: "beacon_builder_circuit_breaker_payloads_revealed",
156
+ help: "Count of blocks with revealed payloads in the fault inspection window",
157
+ }),
158
+ },
159
+
141
160
  blockPayload: {
142
161
  payloadAdvancePrepTime: register.histogram({
143
162
  name: "beacon_block_payload_prepare_time",
@@ -9,7 +9,7 @@ import {ForkName} from "@lodestar/params";
9
9
  import {intToBytes} from "@lodestar/utils";
10
10
  import {MESSAGE_DOMAIN_VALID_SNAPPY} from "./constants.js";
11
11
  import {Eth2GossipsubMetrics} from "./metrics.js";
12
- import {GossipTopicCache, getGossipSSZType} from "./topic.js";
12
+ import {GossipTopicCache, getGossipSSZMaxSize, getGossipSSZType} from "./topic.js";
13
13
 
14
14
  // Load WASM
15
15
  const xxhash = await xxhashFactory();
@@ -98,7 +98,7 @@ export function msgIdFn(gossipTopicCache: GossipTopicCache, msg: Message): Uint8
98
98
  export class DataTransformSnappy implements DataTransform {
99
99
  constructor(
100
100
  private readonly gossipTopicCache: GossipTopicCache,
101
- private readonly maxSizePerMessage: number,
101
+ private readonly maxPayloadSize: number,
102
102
  private readonly metrics: Eth2GossipsubMetrics | null
103
103
  ) {}
104
104
 
@@ -111,17 +111,18 @@ export class DataTransformSnappy implements DataTransform {
111
111
  inboundTransform(topicStr: string, data: Uint8Array): Uint8Array {
112
112
  // check uncompressed data length before we actually decompress
113
113
  const uncompressedDataLength = snappyWasm.decompress_len(data);
114
- if (uncompressedDataLength > this.maxSizePerMessage) {
115
- throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} > ${this.maxSizePerMessage}`);
116
- }
117
114
 
118
115
  const topic = this.gossipTopicCache.getTopic(topicStr);
119
116
  const sszType = getGossipSSZType(topic);
117
+ const maxSize = getGossipSSZMaxSize(topic, this.maxPayloadSize, sszType);
120
118
  this.metrics?.dataTransform.inbound.inc({type: topic.type});
121
119
 
122
120
  if (uncompressedDataLength < sszType.minSize) {
123
121
  throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} < ${sszType.minSize}`);
124
122
  }
123
+ if (uncompressedDataLength > maxSize) {
124
+ throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} > ${maxSize}`);
125
+ }
125
126
  if (uncompressedDataLength > sszType.maxSize) {
126
127
  throw Error(`ssz_snappy decoded data length ${uncompressedDataLength} > ${sszType.maxSize}`);
127
128
  }
@@ -139,9 +140,14 @@ export class DataTransformSnappy implements DataTransform {
139
140
  */
140
141
  outboundTransform(topicStr: string, data: Uint8Array): Uint8Array {
141
142
  const topic = this.gossipTopicCache.getTopic(topicStr);
143
+ const sszType = getGossipSSZType(topic);
144
+ const maxSize = getGossipSSZMaxSize(topic, this.maxPayloadSize, sszType);
142
145
  this.metrics?.dataTransform.outbound.inc({type: topic.type});
143
- if (data.length > this.maxSizePerMessage) {
144
- throw Error(`ssz_snappy encoded data length ${data.length} > ${this.maxSizePerMessage}`);
146
+ if (data.length > maxSize) {
147
+ throw Error(`ssz_snappy encoded data length ${data.length} > ${maxSize}`);
148
+ }
149
+ if (data.length > sszType.maxSize) {
150
+ throw Error(`ssz_snappy encoded data length ${data.length} > ${sszType.maxSize}`);
145
151
  }
146
152
 
147
153
  // Using Buffer.alloc() instead of Buffer.allocUnsafe() to mitigate high GC pressure observed in some environments