@lodestar/beacon-node 1.45.0-dev.9ed373bf56 → 1.45.0-dev.aadf7a7167

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 (143) 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 +4 -0
  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 +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 +21 -56
  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 +2 -0
  46. package/lib/chain/chain.d.ts.map +1 -1
  47. package/lib/chain/chain.js +3 -0
  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 +2 -0
  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/aggregatedAttestationPool.js +1 -1
  69. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  70. package/lib/chain/options.d.ts +4 -0
  71. package/lib/chain/options.d.ts.map +1 -1
  72. package/lib/chain/options.js.map +1 -1
  73. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  74. package/lib/chain/prepareNextSlot.js +12 -7
  75. package/lib/chain/prepareNextSlot.js.map +1 -1
  76. package/lib/chain/validation/block.d.ts.map +1 -1
  77. package/lib/chain/validation/block.js +52 -1
  78. package/lib/chain/validation/block.js.map +1 -1
  79. package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
  80. package/lib/chain/validation/executionPayloadEnvelope.js +30 -0
  81. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  82. package/lib/chain/validation/signatureSets/aggregateAndProof.d.ts.map +1 -1
  83. package/lib/chain/validation/signatureSets/aggregateAndProof.js +2 -3
  84. package/lib/chain/validation/signatureSets/aggregateAndProof.js.map +1 -1
  85. package/lib/db/repositories/lightclientSyncCommitteeWitness.d.ts.map +1 -1
  86. package/lib/db/repositories/lightclientSyncCommitteeWitness.js +42 -7
  87. package/lib/db/repositories/lightclientSyncCommitteeWitness.js.map +1 -1
  88. package/lib/execution/builder/http.d.ts +18 -0
  89. package/lib/execution/builder/http.d.ts.map +1 -1
  90. package/lib/execution/builder/http.js +20 -11
  91. package/lib/execution/builder/http.js.map +1 -1
  92. package/lib/metrics/metrics/beacon.d.ts +6 -0
  93. package/lib/metrics/metrics/beacon.d.ts.map +1 -1
  94. package/lib/metrics/metrics/beacon.js +18 -0
  95. package/lib/metrics/metrics/beacon.js.map +1 -1
  96. package/lib/network/gossip/encoding.d.ts +2 -2
  97. package/lib/network/gossip/encoding.d.ts.map +1 -1
  98. package/lib/network/gossip/encoding.js +15 -9
  99. package/lib/network/gossip/encoding.js.map +1 -1
  100. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  101. package/lib/network/gossip/gossipsub.js +2 -2
  102. package/lib/network/gossip/gossipsub.js.map +1 -1
  103. package/lib/network/gossip/topic.d.ts +74 -231
  104. package/lib/network/gossip/topic.d.ts.map +1 -1
  105. package/lib/network/gossip/topic.js +24 -1
  106. package/lib/network/gossip/topic.js.map +1 -1
  107. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  108. package/lib/network/processor/gossipHandlers.js +4 -0
  109. package/lib/network/processor/gossipHandlers.js.map +1 -1
  110. package/package.json +19 -19
  111. package/src/api/impl/beacon/index.ts +1 -1
  112. package/src/api/impl/beacon/pool/index.ts +5 -0
  113. package/src/api/impl/beacon/state/index.ts +3 -2
  114. package/src/api/impl/beacon/state/utils.ts +11 -0
  115. package/src/api/impl/debug/index.ts +3 -2
  116. package/src/api/impl/lodestar/index.ts +2 -2
  117. package/src/api/impl/proof/index.ts +2 -2
  118. package/src/api/impl/utils.ts +51 -1
  119. package/src/api/impl/validator/index.ts +21 -62
  120. package/src/api/rest/base.ts +9 -0
  121. package/src/chain/blocks/importBlock.ts +1 -6
  122. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -2
  123. package/src/chain/builderCircuitBreaker.ts +84 -0
  124. package/src/chain/chain.ts +7 -0
  125. package/src/chain/errors/blockError.ts +3 -0
  126. package/src/chain/errors/executionPayloadEnvelope.ts +9 -0
  127. package/src/chain/interface.ts +2 -0
  128. package/src/chain/lightClient/index.ts +12 -7
  129. package/src/chain/lightClient/proofs.ts +36 -1
  130. package/src/chain/lightClient/types.ts +3 -1
  131. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  132. package/src/chain/options.ts +4 -0
  133. package/src/chain/prepareNextSlot.ts +11 -7
  134. package/src/chain/validation/block.ts +69 -1
  135. package/src/chain/validation/executionPayloadEnvelope.ts +39 -0
  136. package/src/chain/validation/signatureSets/aggregateAndProof.ts +3 -3
  137. package/src/db/repositories/lightclientSyncCommitteeWitness.ts +57 -9
  138. package/src/execution/builder/http.ts +30 -17
  139. package/src/metrics/metrics/beacon.ts +19 -0
  140. package/src/network/gossip/encoding.ts +13 -7
  141. package/src/network/gossip/gossipsub.ts +7 -2
  142. package/src/network/gossip/topic.ts +31 -0
  143. package/src/network/processor/gossipHandlers.ts +5 -0
@@ -82,7 +82,6 @@ import {ZERO_HASH} from "../../../constants/index.js";
82
82
  import {BuilderStatus, NoBidReceived} from "../../../execution/builder/http.js";
83
83
  import {validateGossipFnRetryUnknownRoot} from "../../../network/processor/gossipHandlers.js";
84
84
  import {CommitteeSubscription} from "../../../network/subnets/index.js";
85
- import {SyncState} from "../../../sync/index.js";
86
85
  import {callInNextEventLoop} from "../../../util/eventLoop.js";
87
86
  import {isOptimisticBlock} from "../../../util/forkChoice.js";
88
87
  import {getBlockGraffiti, toGraffitiBytes} from "../../../util/graffiti.js";
@@ -91,23 +90,9 @@ import {ApiOptions} from "../../options.js";
91
90
  import {getStateResponseWithRegen} from "../beacon/state/utils.js";
92
91
  import {ApiError, FailureList, IndexedError, NodeIsSyncing, OnlySupportedByDVT} from "../errors.js";
93
92
  import {ApiModules} from "../types.js";
93
+ import {notWhileSyncing} from "../utils.js";
94
94
  import {computeSubnetForCommitteesAtSlot, getPubkeysForIndices, selectBlockProductionSource} from "./utils.js";
95
95
 
96
- /**
97
- * If the node is within this many epochs from the head, we declare it to be synced regardless of
98
- * the network sync state.
99
- *
100
- * This helps prevent attacks where nodes can convince us that we're syncing some non-existent
101
- * finalized head.
102
- *
103
- * TODO: Lighthouse uses 8 for the attack described above. However, 8 kills Lodestar since validators
104
- * can trigger regen to fast-forward head state 8 epochs to be immediately invalidated as sync sets
105
- * a new head. Then the checkpoint state cache grows unbounded with very different states (because
106
- * they are 8 epochs apart) and causes an OOM. Research a proper solution once regen and the state
107
- * caches are better.
108
- */
109
- export const SYNC_TOLERANCE_EPOCHS = 1;
110
-
111
96
  /**
112
97
  * Cutoff time to wait from start of the slot for execution and builder block production apis to resolve.
113
98
  * Post this time, race execution and builder to pick whatever resolves first.
@@ -341,36 +326,6 @@ export function getValidatorApi(
341
326
  return null;
342
327
  }
343
328
 
344
- /**
345
- * Reject any request while the node is syncing
346
- */
347
- function notWhileSyncing(): void {
348
- // Consider node synced before or close to genesis
349
- if (chain.clock.currentSlot < SLOTS_PER_EPOCH) {
350
- return;
351
- }
352
-
353
- const syncState = sync.state;
354
- switch (syncState) {
355
- case SyncState.SyncingFinalized:
356
- case SyncState.SyncingHead: {
357
- const currentSlot = chain.clock.currentSlot;
358
- const headSlot = chain.forkChoice.getHead().slot;
359
- if (currentSlot - headSlot > SYNC_TOLERANCE_EPOCHS * SLOTS_PER_EPOCH) {
360
- throw new NodeIsSyncing(`headSlot ${headSlot} currentSlot ${currentSlot}`);
361
- }
362
-
363
- return;
364
- }
365
-
366
- case SyncState.Synced:
367
- return;
368
-
369
- case SyncState.Stalled:
370
- throw new NodeIsSyncing("waiting for peers");
371
- }
372
- }
373
-
374
329
  /**
375
330
  * Post merge, the CL and EL could be out of step in the sync, and could result in
376
331
  * Syncing status of the chain head. To be precise:
@@ -573,7 +528,7 @@ export function getValidatorApi(
573
528
  builderBoostFactor?: bigint,
574
529
  {feeRecipient, builderSelection, strictFeeRecipientCheck}: routes.validator.ExtraProduceBlockOpts = {}
575
530
  ): Promise<ProduceBlindedBlockOrBlockContentsRes> {
576
- notWhileSyncing();
531
+ notWhileSyncing(chain, sync.state);
577
532
  await waitForSlot(slot); // Must never request for a future slot > currentSlot
578
533
 
579
534
  const parentBlock = chain.getProposerHead(slot);
@@ -917,7 +872,7 @@ export function getValidatorApi(
917
872
  throw new ApiError(400, `produceBlockV4 not supported for pre-gloas fork=${fork}`);
918
873
  }
919
874
 
920
- notWhileSyncing();
875
+ notWhileSyncing(chain, sync.state);
921
876
  await waitForSlot(slot);
922
877
 
923
878
  const parentBlock = chain.getProposerHead(slot);
@@ -938,7 +893,10 @@ export function getValidatorApi(
938
893
  // support when it is implemented.
939
894
  const isBuildingOnFull = chain.forkChoice.shouldBuildOnFull(parentBlock, slot);
940
895
  const bidParentBlockHash = isBuildingOnFull ? parentBlock.executionPayloadBlockHash : parentBlock.parentBlockHash;
941
- const builderBid = chain.executionPayloadBidPool.getBestBid(slot, bidParentBlockHash, parentBlockRootHex);
896
+ const circuitBreakerActive = chain.builderCircuitBreaker.isActive(slot);
897
+ const builderBid = circuitBreakerActive
898
+ ? null
899
+ : chain.executionPayloadBidPool.getBestBid(slot, bidParentBlockHash, parentBlockRootHex);
942
900
 
943
901
  const logCtx = {
944
902
  slot,
@@ -946,6 +904,7 @@ export function getValidatorApi(
946
904
  parentBlockRoot: parentBlockRootHex,
947
905
  parentBlockHash: parentBlock.executionPayloadBlockHash,
948
906
  fork,
907
+ circuitBreakerActive,
949
908
  ...(builderBid !== null
950
909
  ? {
951
910
  bidValue: builderBid.message.value,
@@ -1035,7 +994,7 @@ export function getValidatorApi(
1035
994
  },
1036
995
 
1037
996
  async produceAttestationData({committeeIndex, slot}) {
1038
- notWhileSyncing();
997
+ notWhileSyncing(chain, sync.state);
1039
998
 
1040
999
  await waitForSlot(slot); // Must never request for a future slot > currentSlot
1041
1000
 
@@ -1116,7 +1075,7 @@ export function getValidatorApi(
1116
1075
  throw new ApiError(400, `producePayloadAttestationData is not supported before Gloas fork=${fork}`);
1117
1076
  }
1118
1077
 
1119
- notWhileSyncing();
1078
+ notWhileSyncing(chain, sync.state);
1120
1079
  await waitForSlot(slot);
1121
1080
 
1122
1081
  const block = chain.forkChoice.getCanonicalBlockAtSlot(slot);
@@ -1201,7 +1160,7 @@ export function getValidatorApi(
1201
1160
  },
1202
1161
 
1203
1162
  async getProposerDuties({epoch}, _context, opts?: {v2?: boolean}) {
1204
- notWhileSyncing();
1163
+ notWhileSyncing(chain, sync.state);
1205
1164
 
1206
1165
  const currentEpoch = currentEpochWithDisparity();
1207
1166
  const nextEpoch = currentEpoch + 1;
@@ -1248,7 +1207,7 @@ export function getValidatorApi(
1248
1207
  // requested epoch is within that range, we can use the head state at current epoch
1249
1208
  state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.getDuties);
1250
1209
  } else {
1251
- const res = await getStateResponseWithRegen(chain, startSlot);
1210
+ const res = await getStateResponseWithRegen(chain, sync, startSlot);
1252
1211
 
1253
1212
  state = res.state instanceof Uint8Array ? chain.getHeadState().loadOtherState(res.state) : res.state;
1254
1213
 
@@ -1342,7 +1301,7 @@ export function getValidatorApi(
1342
1301
  },
1343
1302
 
1344
1303
  async getAttesterDuties({epoch, indices}) {
1345
- notWhileSyncing();
1304
+ notWhileSyncing(chain, sync.state);
1346
1305
 
1347
1306
  if (indices.length === 0) {
1348
1307
  throw new ApiError(400, "No validator to get attester duties");
@@ -1402,7 +1361,7 @@ export function getValidatorApi(
1402
1361
  },
1403
1362
 
1404
1363
  async getPtcDuties({epoch, indices}) {
1405
- notWhileSyncing();
1364
+ notWhileSyncing(chain, sync.state);
1406
1365
 
1407
1366
  if (indices.length === 0) {
1408
1367
  throw new ApiError(400, "No validator to get PTC duties");
@@ -1464,7 +1423,7 @@ export function getValidatorApi(
1464
1423
  * @param validatorIndices an array of the validator indices for which to obtain the duties.
1465
1424
  */
1466
1425
  async getSyncCommitteeDuties({epoch, indices}) {
1467
- notWhileSyncing();
1426
+ notWhileSyncing(chain, sync.state);
1468
1427
 
1469
1428
  if (indices.length === 0) {
1470
1429
  throw new ApiError(400, "No validator to get attester duties");
@@ -1511,7 +1470,7 @@ export function getValidatorApi(
1511
1470
  },
1512
1471
 
1513
1472
  async getAggregatedAttestationV2({attestationDataRoot, slot, committeeIndex}) {
1514
- notWhileSyncing();
1473
+ notWhileSyncing(chain, sync.state);
1515
1474
 
1516
1475
  await waitForSlot(slot); // Must never request for a future slot > currentSlot
1517
1476
 
@@ -1534,7 +1493,7 @@ export function getValidatorApi(
1534
1493
  },
1535
1494
 
1536
1495
  async publishAggregateAndProofsV2({signedAggregateAndProofs}) {
1537
- notWhileSyncing();
1496
+ notWhileSyncing(chain, sync.state);
1538
1497
 
1539
1498
  const seenTimestampSec = Date.now() / 1000;
1540
1499
  const failures: FailureList = [];
@@ -1595,7 +1554,7 @@ export function getValidatorApi(
1595
1554
  * https://github.com/ethereum/beacon-APIs/pull/137
1596
1555
  */
1597
1556
  async publishContributionAndProofs({contributionAndProofs}) {
1598
- notWhileSyncing();
1557
+ notWhileSyncing(chain, sync.state);
1599
1558
 
1600
1559
  const failures: FailureList = [];
1601
1560
 
@@ -1644,7 +1603,7 @@ export function getValidatorApi(
1644
1603
  },
1645
1604
 
1646
1605
  async prepareBeaconCommitteeSubnet({subscriptions}) {
1647
- notWhileSyncing();
1606
+ notWhileSyncing(chain, sync.state);
1648
1607
 
1649
1608
  await network.prepareBeaconCommitteeSubnets(
1650
1609
  subscriptions.map(({validatorIndex, slot, isAggregator, committeesAtSlot, committeeIndex}) => ({
@@ -1677,7 +1636,7 @@ export function getValidatorApi(
1677
1636
  * https://github.com/ethereum/beacon-APIs/pull/136
1678
1637
  */
1679
1638
  async prepareSyncCommitteeSubnets({subscriptions}) {
1680
- notWhileSyncing();
1639
+ notWhileSyncing(chain, sync.state);
1681
1640
 
1682
1641
  // A `validatorIndex` can be in multiple subnets, so compute the CommitteeSubscription with double for loop
1683
1642
  const subs: CommitteeSubscription[] = [];
@@ -1821,7 +1780,7 @@ export function getValidatorApi(
1821
1780
  throw new ApiError(400, `getExecutionPayloadEnvelope not supported for pre-gloas fork=${fork}`);
1822
1781
  }
1823
1782
 
1824
- notWhileSyncing();
1783
+ notWhileSyncing(chain, sync.state);
1825
1784
  await waitForSlot(slot);
1826
1785
 
1827
1786
  const blockRootHex = toRootHex(beaconBlockRoot);
@@ -3,6 +3,7 @@ import {fastifyCors} from "@fastify/cors";
3
3
  import {FastifyError, FastifyInstance, FastifyRequest, errorCodes, fastify} from "fastify";
4
4
  import {parse as parseQueryString} from "qs";
5
5
  import {addSszContentTypeParser} from "@lodestar/api/server";
6
+ import {NUMBER_OF_COLUMNS} from "@lodestar/params";
6
7
  import {ErrorAborted, Gauge, Histogram, Logger} from "@lodestar/utils";
7
8
  import {isLocalhostIP} from "../../util/ip.js";
8
9
  import {ApiError, FailureList, IndexedError, NodeIsSyncing} from "../impl/errors.js";
@@ -55,6 +56,13 @@ const INVALID_MEDIA_TYPE_CODE = errorCodes.FST_ERR_CTP_INVALID_MEDIA_TYPE().code
55
56
  */
56
57
  const SCHEMA_VALIDATION_ERROR_CODE = errorCodes.FST_ERR_VALIDATION().code;
57
58
 
59
+ /**
60
+ * Cap for array query params, set to the largest array any beacon-API query can carry:
61
+ * a full data-column custody set (`getDebugDataColumnSidecars` `indices`). `qs` turns
62
+ * longer arrays into an object, which then fails schema validation.
63
+ */
64
+ const QUERY_STRING_ARRAY_LIMIT = NUMBER_OF_COLUMNS;
65
+
58
66
  /**
59
67
  * REST API powered by `fastify` server.
60
68
  */
@@ -82,6 +90,7 @@ export class RestApiServer {
82
90
  // be OpenAPI spec compliant and results are inconsistent, see https://github.com/ljharb/qs/issues/331.
83
91
  // The schema validation will catch this and throw an error as parsed query string results in an object.
84
92
  parseArrays: false,
93
+ arrayLimit: QUERY_STRING_ARRAY_LIMIT,
85
94
  }),
86
95
  },
87
96
  bodyLimit: opts.bodyLimit,
@@ -124,18 +124,13 @@ export async function importBlock(
124
124
  executionStatus = parentBlock.executionStatus;
125
125
  }
126
126
 
127
- // getBeaconProposerOrNull will return null if head state is more than one epoch away
128
- // from block slot. We skip proposer boost canonical check as we cannot determine the canonical proposer
129
- const expectedProposerIndex: number | null = this.getHeadState().getBeaconProposerOrNull(blockSlot);
130
-
131
127
  const blockSummary = this.forkChoice.onBlock(
132
128
  block.message,
133
129
  postState,
134
130
  blockDelaySec,
135
131
  currentSlot,
136
132
  executionStatus,
137
- dataAvailabilityStatus,
138
- expectedProposerIndex
133
+ dataAvailabilityStatus
139
134
  );
140
135
 
141
136
  // This adds the state necessary to process the next block
@@ -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";
@@ -151,6 +152,7 @@ export class BeaconChain implements IBeaconChain {
151
152
  readonly genesisValidatorsRoot: Root;
152
153
  readonly executionEngine: IExecutionEngine;
153
154
  readonly executionBuilder?: IExecutionBuilder;
155
+ readonly builderCircuitBreaker: BuilderCircuitBreaker;
154
156
  // Expose config for convenience in modularized functions
155
157
  readonly config: BeaconConfig;
156
158
  readonly custodyConfig: CustodyConfig;
@@ -425,6 +427,11 @@ export class BeaconChain implements IBeaconChain {
425
427
 
426
428
  this.forkChoice = forkChoice;
427
429
 
430
+ this.builderCircuitBreaker = new BuilderCircuitBreaker(
431
+ {faultInspectionWindow: opts.faultInspectionWindow, allowedFaults: opts.allowedFaults},
432
+ {forkChoice, logger, metrics}
433
+ );
434
+
428
435
  this.seenPayloadEnvelopeInputCache = new SeenPayloadEnvelopeInput({
429
436
  config,
430
437
  clock,
@@ -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";
@@ -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,
@@ -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