@lodestar/beacon-node 1.45.0-dev.905415417a → 1.45.0-dev.9d0edba43c
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.
- package/lib/api/impl/beacon/index.d.ts +1 -1
- package/lib/api/impl/beacon/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/index.js.map +1 -1
- package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/pool/index.js +6 -43
- package/lib/api/impl/beacon/pool/index.js.map +1 -1
- package/lib/api/impl/beacon/state/index.d.ts +1 -1
- package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/state/index.js +48 -5
- package/lib/api/impl/beacon/state/index.js.map +1 -1
- package/lib/api/impl/beacon/state/utils.d.ts +13 -3
- package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
- package/lib/api/impl/beacon/state/utils.js +48 -4
- package/lib/api/impl/beacon/state/utils.js.map +1 -1
- package/lib/api/impl/debug/index.d.ts +1 -1
- package/lib/api/impl/debug/index.d.ts.map +1 -1
- package/lib/api/impl/debug/index.js +2 -2
- package/lib/api/impl/debug/index.js.map +1 -1
- package/lib/api/impl/lodestar/index.js +2 -2
- package/lib/api/impl/lodestar/index.js.map +1 -1
- package/lib/api/impl/proof/index.d.ts +1 -1
- package/lib/api/impl/proof/index.d.ts.map +1 -1
- package/lib/api/impl/proof/index.js +2 -2
- package/lib/api/impl/proof/index.js.map +1 -1
- package/lib/api/impl/utils.d.ts +23 -0
- package/lib/api/impl/utils.d.ts.map +1 -1
- package/lib/api/impl/utils.js +44 -1
- package/lib/api/impl/utils.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts +0 -14
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +58 -59
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +1 -4
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/builderCircuitBreaker.d.ts +31 -0
- package/lib/chain/builderCircuitBreaker.d.ts.map +1 -0
- package/lib/chain/builderCircuitBreaker.js +59 -0
- package/lib/chain/builderCircuitBreaker.js.map +1 -0
- package/lib/chain/chain.d.ts +2 -0
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +3 -0
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/interface.d.ts +2 -0
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/interface.js.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.d.ts +2 -1
- package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.js +3 -0
- package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
- package/lib/chain/options.d.ts +4 -0
- package/lib/chain/options.d.ts.map +1 -1
- package/lib/chain/options.js.map +1 -1
- package/lib/chain/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +12 -7
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +4 -12
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +11 -10
- package/lib/chain/validation/executionPayloadBid.js.map +1 -1
- package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
- package/lib/chain/validation/proposerPreferences.js +14 -3
- package/lib/chain/validation/proposerPreferences.js.map +1 -1
- package/lib/execution/builder/http.d.ts +18 -0
- package/lib/execution/builder/http.d.ts.map +1 -1
- package/lib/execution/builder/http.js +20 -11
- package/lib/execution/builder/http.js.map +1 -1
- package/lib/metrics/metrics/beacon.d.ts +6 -0
- package/lib/metrics/metrics/beacon.d.ts.map +1 -1
- package/lib/metrics/metrics/beacon.js +18 -0
- package/lib/metrics/metrics/beacon.js.map +1 -1
- package/lib/network/core/metrics.d.ts +65 -0
- package/lib/network/core/metrics.d.ts.map +1 -1
- package/lib/network/core/metrics.js +94 -0
- package/lib/network/core/metrics.js.map +1 -1
- package/lib/network/discv5/utils.d.ts.map +1 -1
- package/lib/network/discv5/utils.js +12 -3
- package/lib/network/discv5/utils.js.map +1 -1
- package/lib/network/gossip/encoding.d.ts.map +1 -1
- package/lib/network/gossip/encoding.js +7 -0
- package/lib/network/gossip/encoding.js.map +1 -1
- package/lib/network/gossip/gossipsub.d.ts.map +1 -1
- package/lib/network/gossip/gossipsub.js +2 -1
- package/lib/network/gossip/gossipsub.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +6 -1
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/gossip/topic.js +23 -3
- package/lib/network/gossip/topic.js.map +1 -1
- package/lib/network/peers/peerManager.d.ts.map +1 -1
- package/lib/network/peers/peerManager.js +10 -0
- package/lib/network/peers/peerManager.js.map +1 -1
- package/lib/network/peers/score/score.d.ts +3 -1
- package/lib/network/peers/score/score.d.ts.map +1 -1
- package/lib/network/peers/score/score.js +6 -1
- package/lib/network/peers/score/score.js.map +1 -1
- package/lib/network/peers/score/store.d.ts.map +1 -1
- package/lib/network/peers/score/store.js +6 -1
- package/lib/network/peers/score/store.js.map +1 -1
- package/lib/network/peers/utils/prioritizePeers.d.ts.map +1 -1
- package/lib/network/peers/utils/prioritizePeers.js +14 -0
- package/lib/network/peers/utils/prioritizePeers.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +6 -3
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/package.json +18 -18
- package/src/api/impl/beacon/index.ts +1 -1
- package/src/api/impl/beacon/pool/index.ts +5 -53
- package/src/api/impl/beacon/state/index.ts +55 -3
- package/src/api/impl/beacon/state/utils.ts +68 -4
- package/src/api/impl/debug/index.ts +3 -2
- package/src/api/impl/lodestar/index.ts +2 -2
- package/src/api/impl/proof/index.ts +2 -2
- package/src/api/impl/utils.ts +51 -1
- package/src/api/impl/validator/index.ts +66 -64
- package/src/chain/blocks/importBlock.ts +1 -6
- package/src/chain/builderCircuitBreaker.ts +84 -0
- package/src/chain/chain.ts +7 -0
- package/src/chain/interface.ts +2 -0
- package/src/chain/opPools/proposerPreferencesPool.ts +5 -1
- package/src/chain/options.ts +4 -0
- package/src/chain/prepareNextSlot.ts +11 -7
- package/src/chain/produceBlock/produceBlockBody.ts +9 -17
- package/src/chain/validation/executionPayloadBid.ts +14 -9
- package/src/chain/validation/proposerPreferences.ts +16 -3
- package/src/execution/builder/http.ts +30 -17
- package/src/metrics/metrics/beacon.ts +19 -0
- package/src/network/core/metrics.ts +94 -0
- package/src/network/discv5/utils.ts +11 -3
- package/src/network/gossip/encoding.ts +7 -0
- package/src/network/gossip/gossipsub.ts +2 -1
- package/src/network/gossip/topic.ts +28 -4
- package/src/network/peers/peerManager.ts +11 -0
- package/src/network/peers/score/score.ts +8 -1
- package/src/network/peers/score/store.ts +8 -1
- package/src/network/peers/utils/prioritizePeers.ts +15 -0
- package/src/network/processor/gossipHandlers.ts +6 -2
package/src/api/impl/utils.ts
CHANGED
|
@@ -1,4 +1,54 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {SLOTS_PER_EPOCH} from "@lodestar/params";
|
|
2
|
+
import type {IBeaconChain} from "../../chain/index.js";
|
|
3
|
+
import {SyncState} from "../../sync/index.js";
|
|
4
|
+
import {ApiError, NodeIsSyncing} from "./errors.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* If the node is within this many epochs from the head, we declare it to be synced regardless of
|
|
8
|
+
* the network sync state.
|
|
9
|
+
*
|
|
10
|
+
* This helps prevent attacks where nodes can convince us that we're syncing some non-existent
|
|
11
|
+
* finalized head.
|
|
12
|
+
*
|
|
13
|
+
* TODO: Lighthouse uses 8 for the attack described above. However, 8 kills Lodestar since validators
|
|
14
|
+
* can trigger regen to fast-forward head state 8 epochs to be immediately invalidated as sync sets
|
|
15
|
+
* a new head. Then the checkpoint state cache grows unbounded with very different states (because
|
|
16
|
+
* they are 8 epochs apart) and causes an OOM. Research a proper solution once regen and the state
|
|
17
|
+
* caches are better.
|
|
18
|
+
*/
|
|
19
|
+
export const SYNC_TOLERANCE_EPOCHS = 1;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Reject any request while the node is syncing. Used by endpoints that must not serve while the
|
|
23
|
+
* node is behind — validator duties, and beacon state lookups whose regen could otherwise walk
|
|
24
|
+
* back past the block-root window (`SLOTS_PER_HISTORICAL_ROOT`) and wedge the node. Throws
|
|
25
|
+
* {@link NodeIsSyncing} (503).
|
|
26
|
+
*/
|
|
27
|
+
export function notWhileSyncing(chain: IBeaconChain, syncState: SyncState): void {
|
|
28
|
+
// Consider node synced before or close to genesis
|
|
29
|
+
if (chain.clock.currentSlot < SLOTS_PER_EPOCH) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
switch (syncState) {
|
|
34
|
+
case SyncState.SyncingFinalized:
|
|
35
|
+
case SyncState.SyncingHead: {
|
|
36
|
+
const currentSlot = chain.clock.currentSlot;
|
|
37
|
+
const headSlot = chain.forkChoice.getHead().slot;
|
|
38
|
+
if (currentSlot - headSlot > SYNC_TOLERANCE_EPOCHS * SLOTS_PER_EPOCH) {
|
|
39
|
+
throw new NodeIsSyncing(`headSlot ${headSlot} currentSlot ${currentSlot}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
case SyncState.Synced:
|
|
46
|
+
return;
|
|
47
|
+
|
|
48
|
+
case SyncState.Stalled:
|
|
49
|
+
throw new NodeIsSyncing("waiting for peers");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
2
52
|
|
|
3
53
|
/**
|
|
4
54
|
* Ensures that the array contains unique values, and throws an ApiError
|
|
@@ -65,6 +65,8 @@ import {
|
|
|
65
65
|
AttestationError,
|
|
66
66
|
AttestationErrorCode,
|
|
67
67
|
GossipAction,
|
|
68
|
+
ProposerPreferencesError,
|
|
69
|
+
ProposerPreferencesErrorCode,
|
|
68
70
|
SyncCommitteeError,
|
|
69
71
|
SyncCommitteeErrorCode,
|
|
70
72
|
} from "../../../chain/errors/index.js";
|
|
@@ -74,12 +76,12 @@ import {BlockType, ProduceFullDeneb, ProduceFullGloas} from "../../../chain/prod
|
|
|
74
76
|
import {RegenCaller} from "../../../chain/regen/index.js";
|
|
75
77
|
import {CheckpointHex} from "../../../chain/stateCache/types.js";
|
|
76
78
|
import {validateApiAggregateAndProof} from "../../../chain/validation/index.js";
|
|
79
|
+
import {validateGossipProposerPreferences} from "../../../chain/validation/proposerPreferences.js";
|
|
77
80
|
import {validateSyncCommitteeGossipContributionAndProof} from "../../../chain/validation/syncCommitteeContributionAndProof.js";
|
|
78
81
|
import {ZERO_HASH} from "../../../constants/index.js";
|
|
79
82
|
import {BuilderStatus, NoBidReceived} from "../../../execution/builder/http.js";
|
|
80
83
|
import {validateGossipFnRetryUnknownRoot} from "../../../network/processor/gossipHandlers.js";
|
|
81
84
|
import {CommitteeSubscription} from "../../../network/subnets/index.js";
|
|
82
|
-
import {SyncState} from "../../../sync/index.js";
|
|
83
85
|
import {callInNextEventLoop} from "../../../util/eventLoop.js";
|
|
84
86
|
import {isOptimisticBlock} from "../../../util/forkChoice.js";
|
|
85
87
|
import {getBlockGraffiti, toGraffitiBytes} from "../../../util/graffiti.js";
|
|
@@ -88,23 +90,9 @@ import {ApiOptions} from "../../options.js";
|
|
|
88
90
|
import {getStateResponseWithRegen} from "../beacon/state/utils.js";
|
|
89
91
|
import {ApiError, FailureList, IndexedError, NodeIsSyncing, OnlySupportedByDVT} from "../errors.js";
|
|
90
92
|
import {ApiModules} from "../types.js";
|
|
93
|
+
import {notWhileSyncing} from "../utils.js";
|
|
91
94
|
import {computeSubnetForCommitteesAtSlot, getPubkeysForIndices, selectBlockProductionSource} from "./utils.js";
|
|
92
95
|
|
|
93
|
-
/**
|
|
94
|
-
* If the node is within this many epochs from the head, we declare it to be synced regardless of
|
|
95
|
-
* the network sync state.
|
|
96
|
-
*
|
|
97
|
-
* This helps prevent attacks where nodes can convince us that we're syncing some non-existent
|
|
98
|
-
* finalized head.
|
|
99
|
-
*
|
|
100
|
-
* TODO: Lighthouse uses 8 for the attack described above. However, 8 kills Lodestar since validators
|
|
101
|
-
* can trigger regen to fast-forward head state 8 epochs to be immediately invalidated as sync sets
|
|
102
|
-
* a new head. Then the checkpoint state cache grows unbounded with very different states (because
|
|
103
|
-
* they are 8 epochs apart) and causes an OOM. Research a proper solution once regen and the state
|
|
104
|
-
* caches are better.
|
|
105
|
-
*/
|
|
106
|
-
export const SYNC_TOLERANCE_EPOCHS = 1;
|
|
107
|
-
|
|
108
96
|
/**
|
|
109
97
|
* Cutoff time to wait from start of the slot for execution and builder block production apis to resolve.
|
|
110
98
|
* Post this time, race execution and builder to pick whatever resolves first.
|
|
@@ -338,36 +326,6 @@ export function getValidatorApi(
|
|
|
338
326
|
return null;
|
|
339
327
|
}
|
|
340
328
|
|
|
341
|
-
/**
|
|
342
|
-
* Reject any request while the node is syncing
|
|
343
|
-
*/
|
|
344
|
-
function notWhileSyncing(): void {
|
|
345
|
-
// Consider node synced before or close to genesis
|
|
346
|
-
if (chain.clock.currentSlot < SLOTS_PER_EPOCH) {
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
const syncState = sync.state;
|
|
351
|
-
switch (syncState) {
|
|
352
|
-
case SyncState.SyncingFinalized:
|
|
353
|
-
case SyncState.SyncingHead: {
|
|
354
|
-
const currentSlot = chain.clock.currentSlot;
|
|
355
|
-
const headSlot = chain.forkChoice.getHead().slot;
|
|
356
|
-
if (currentSlot - headSlot > SYNC_TOLERANCE_EPOCHS * SLOTS_PER_EPOCH) {
|
|
357
|
-
throw new NodeIsSyncing(`headSlot ${headSlot} currentSlot ${currentSlot}`);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
case SyncState.Synced:
|
|
364
|
-
return;
|
|
365
|
-
|
|
366
|
-
case SyncState.Stalled:
|
|
367
|
-
throw new NodeIsSyncing("waiting for peers");
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
329
|
/**
|
|
372
330
|
* Post merge, the CL and EL could be out of step in the sync, and could result in
|
|
373
331
|
* Syncing status of the chain head. To be precise:
|
|
@@ -570,7 +528,7 @@ export function getValidatorApi(
|
|
|
570
528
|
builderBoostFactor?: bigint,
|
|
571
529
|
{feeRecipient, builderSelection, strictFeeRecipientCheck}: routes.validator.ExtraProduceBlockOpts = {}
|
|
572
530
|
): Promise<ProduceBlindedBlockOrBlockContentsRes> {
|
|
573
|
-
notWhileSyncing();
|
|
531
|
+
notWhileSyncing(chain, sync.state);
|
|
574
532
|
await waitForSlot(slot); // Must never request for a future slot > currentSlot
|
|
575
533
|
|
|
576
534
|
const parentBlock = chain.getProposerHead(slot);
|
|
@@ -914,7 +872,7 @@ export function getValidatorApi(
|
|
|
914
872
|
throw new ApiError(400, `produceBlockV4 not supported for pre-gloas fork=${fork}`);
|
|
915
873
|
}
|
|
916
874
|
|
|
917
|
-
notWhileSyncing();
|
|
875
|
+
notWhileSyncing(chain, sync.state);
|
|
918
876
|
await waitForSlot(slot);
|
|
919
877
|
|
|
920
878
|
const parentBlock = chain.getProposerHead(slot);
|
|
@@ -935,7 +893,10 @@ export function getValidatorApi(
|
|
|
935
893
|
// support when it is implemented.
|
|
936
894
|
const isBuildingOnFull = chain.forkChoice.shouldBuildOnFull(parentBlock, slot);
|
|
937
895
|
const bidParentBlockHash = isBuildingOnFull ? parentBlock.executionPayloadBlockHash : parentBlock.parentBlockHash;
|
|
938
|
-
const
|
|
896
|
+
const circuitBreakerActive = chain.builderCircuitBreaker.isActive(slot);
|
|
897
|
+
const builderBid = circuitBreakerActive
|
|
898
|
+
? null
|
|
899
|
+
: chain.executionPayloadBidPool.getBestBid(slot, bidParentBlockHash, parentBlockRootHex);
|
|
939
900
|
|
|
940
901
|
const logCtx = {
|
|
941
902
|
slot,
|
|
@@ -943,6 +904,7 @@ export function getValidatorApi(
|
|
|
943
904
|
parentBlockRoot: parentBlockRootHex,
|
|
944
905
|
parentBlockHash: parentBlock.executionPayloadBlockHash,
|
|
945
906
|
fork,
|
|
907
|
+
circuitBreakerActive,
|
|
946
908
|
...(builderBid !== null
|
|
947
909
|
? {
|
|
948
910
|
bidValue: builderBid.message.value,
|
|
@@ -1032,7 +994,7 @@ export function getValidatorApi(
|
|
|
1032
994
|
},
|
|
1033
995
|
|
|
1034
996
|
async produceAttestationData({committeeIndex, slot}) {
|
|
1035
|
-
notWhileSyncing();
|
|
997
|
+
notWhileSyncing(chain, sync.state);
|
|
1036
998
|
|
|
1037
999
|
await waitForSlot(slot); // Must never request for a future slot > currentSlot
|
|
1038
1000
|
|
|
@@ -1113,13 +1075,13 @@ export function getValidatorApi(
|
|
|
1113
1075
|
throw new ApiError(400, `producePayloadAttestationData is not supported before Gloas fork=${fork}`);
|
|
1114
1076
|
}
|
|
1115
1077
|
|
|
1116
|
-
notWhileSyncing();
|
|
1078
|
+
notWhileSyncing(chain, sync.state);
|
|
1117
1079
|
await waitForSlot(slot);
|
|
1118
1080
|
|
|
1119
1081
|
const block = chain.forkChoice.getCanonicalBlockAtSlot(slot);
|
|
1120
1082
|
if (!block) {
|
|
1121
|
-
// No block is seen at slot. Return
|
|
1122
|
-
|
|
1083
|
+
// No canonical block is seen at slot. Return 204 so vc can skip casting payload attestation.
|
|
1084
|
+
return {data: undefined, meta: {version: fork}, status: 204};
|
|
1123
1085
|
}
|
|
1124
1086
|
|
|
1125
1087
|
const payloadInput = chain.seenPayloadEnvelopeInputCache.get(block.blockRoot);
|
|
@@ -1198,7 +1160,7 @@ export function getValidatorApi(
|
|
|
1198
1160
|
},
|
|
1199
1161
|
|
|
1200
1162
|
async getProposerDuties({epoch}, _context, opts?: {v2?: boolean}) {
|
|
1201
|
-
notWhileSyncing();
|
|
1163
|
+
notWhileSyncing(chain, sync.state);
|
|
1202
1164
|
|
|
1203
1165
|
const currentEpoch = currentEpochWithDisparity();
|
|
1204
1166
|
const nextEpoch = currentEpoch + 1;
|
|
@@ -1245,7 +1207,7 @@ export function getValidatorApi(
|
|
|
1245
1207
|
// requested epoch is within that range, we can use the head state at current epoch
|
|
1246
1208
|
state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.getDuties);
|
|
1247
1209
|
} else {
|
|
1248
|
-
const res = await getStateResponseWithRegen(chain, startSlot);
|
|
1210
|
+
const res = await getStateResponseWithRegen(chain, sync, startSlot);
|
|
1249
1211
|
|
|
1250
1212
|
state = res.state instanceof Uint8Array ? chain.getHeadState().loadOtherState(res.state) : res.state;
|
|
1251
1213
|
|
|
@@ -1339,7 +1301,7 @@ export function getValidatorApi(
|
|
|
1339
1301
|
},
|
|
1340
1302
|
|
|
1341
1303
|
async getAttesterDuties({epoch, indices}) {
|
|
1342
|
-
notWhileSyncing();
|
|
1304
|
+
notWhileSyncing(chain, sync.state);
|
|
1343
1305
|
|
|
1344
1306
|
if (indices.length === 0) {
|
|
1345
1307
|
throw new ApiError(400, "No validator to get attester duties");
|
|
@@ -1399,7 +1361,7 @@ export function getValidatorApi(
|
|
|
1399
1361
|
},
|
|
1400
1362
|
|
|
1401
1363
|
async getPtcDuties({epoch, indices}) {
|
|
1402
|
-
notWhileSyncing();
|
|
1364
|
+
notWhileSyncing(chain, sync.state);
|
|
1403
1365
|
|
|
1404
1366
|
if (indices.length === 0) {
|
|
1405
1367
|
throw new ApiError(400, "No validator to get PTC duties");
|
|
@@ -1461,7 +1423,7 @@ export function getValidatorApi(
|
|
|
1461
1423
|
* @param validatorIndices an array of the validator indices for which to obtain the duties.
|
|
1462
1424
|
*/
|
|
1463
1425
|
async getSyncCommitteeDuties({epoch, indices}) {
|
|
1464
|
-
notWhileSyncing();
|
|
1426
|
+
notWhileSyncing(chain, sync.state);
|
|
1465
1427
|
|
|
1466
1428
|
if (indices.length === 0) {
|
|
1467
1429
|
throw new ApiError(400, "No validator to get attester duties");
|
|
@@ -1508,7 +1470,7 @@ export function getValidatorApi(
|
|
|
1508
1470
|
},
|
|
1509
1471
|
|
|
1510
1472
|
async getAggregatedAttestationV2({attestationDataRoot, slot, committeeIndex}) {
|
|
1511
|
-
notWhileSyncing();
|
|
1473
|
+
notWhileSyncing(chain, sync.state);
|
|
1512
1474
|
|
|
1513
1475
|
await waitForSlot(slot); // Must never request for a future slot > currentSlot
|
|
1514
1476
|
|
|
@@ -1531,7 +1493,7 @@ export function getValidatorApi(
|
|
|
1531
1493
|
},
|
|
1532
1494
|
|
|
1533
1495
|
async publishAggregateAndProofsV2({signedAggregateAndProofs}) {
|
|
1534
|
-
notWhileSyncing();
|
|
1496
|
+
notWhileSyncing(chain, sync.state);
|
|
1535
1497
|
|
|
1536
1498
|
const seenTimestampSec = Date.now() / 1000;
|
|
1537
1499
|
const failures: FailureList = [];
|
|
@@ -1592,7 +1554,7 @@ export function getValidatorApi(
|
|
|
1592
1554
|
* https://github.com/ethereum/beacon-APIs/pull/137
|
|
1593
1555
|
*/
|
|
1594
1556
|
async publishContributionAndProofs({contributionAndProofs}) {
|
|
1595
|
-
notWhileSyncing();
|
|
1557
|
+
notWhileSyncing(chain, sync.state);
|
|
1596
1558
|
|
|
1597
1559
|
const failures: FailureList = [];
|
|
1598
1560
|
|
|
@@ -1641,7 +1603,7 @@ export function getValidatorApi(
|
|
|
1641
1603
|
},
|
|
1642
1604
|
|
|
1643
1605
|
async prepareBeaconCommitteeSubnet({subscriptions}) {
|
|
1644
|
-
notWhileSyncing();
|
|
1606
|
+
notWhileSyncing(chain, sync.state);
|
|
1645
1607
|
|
|
1646
1608
|
await network.prepareBeaconCommitteeSubnets(
|
|
1647
1609
|
subscriptions.map(({validatorIndex, slot, isAggregator, committeesAtSlot, committeeIndex}) => ({
|
|
@@ -1674,7 +1636,7 @@ export function getValidatorApi(
|
|
|
1674
1636
|
* https://github.com/ethereum/beacon-APIs/pull/136
|
|
1675
1637
|
*/
|
|
1676
1638
|
async prepareSyncCommitteeSubnets({subscriptions}) {
|
|
1677
|
-
notWhileSyncing();
|
|
1639
|
+
notWhileSyncing(chain, sync.state);
|
|
1678
1640
|
|
|
1679
1641
|
// A `validatorIndex` can be in multiple subnets, so compute the CommitteeSubscription with double for loop
|
|
1680
1642
|
const subs: CommitteeSubscription[] = [];
|
|
@@ -1771,6 +1733,46 @@ export function getValidatorApi(
|
|
|
1771
1733
|
});
|
|
1772
1734
|
},
|
|
1773
1735
|
|
|
1736
|
+
async submitProposerPreferences({signedProposerPreferences}) {
|
|
1737
|
+
const failures: FailureList = [];
|
|
1738
|
+
|
|
1739
|
+
await Promise.all(
|
|
1740
|
+
signedProposerPreferences.map(async (signed, i) => {
|
|
1741
|
+
try {
|
|
1742
|
+
await validateGossipProposerPreferences(chain, signed);
|
|
1743
|
+
|
|
1744
|
+
chain.proposerPreferencesPool.add(signed);
|
|
1745
|
+
await network.publishProposerPreferences(signed);
|
|
1746
|
+
chain.emitter.emit(routes.events.EventType.proposerPreferences, {
|
|
1747
|
+
version: config.getForkName(signed.message.proposalSlot),
|
|
1748
|
+
data: signed,
|
|
1749
|
+
});
|
|
1750
|
+
} catch (e) {
|
|
1751
|
+
const logCtx = {
|
|
1752
|
+
slot: signed.message.proposalSlot,
|
|
1753
|
+
validatorIndex: signed.message.validatorIndex,
|
|
1754
|
+
dependentRoot: toRootHex(signed.message.dependentRoot),
|
|
1755
|
+
};
|
|
1756
|
+
|
|
1757
|
+
if (e instanceof ProposerPreferencesError && e.type.code === ProposerPreferencesErrorCode.ALREADY_KNOWN) {
|
|
1758
|
+
logger.debug("Ignoring known signed proposer preferences", logCtx);
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
failures.push({index: i, message: (e as Error).message});
|
|
1763
|
+
logger.verbose(`Error on submitProposerPreferences [${i}]`, logCtx, e as Error);
|
|
1764
|
+
if (e instanceof ProposerPreferencesError && e.action === GossipAction.REJECT) {
|
|
1765
|
+
chain.persistInvalidSszValue(ssz.gloas.SignedProposerPreferences, signed, "api_reject");
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
})
|
|
1769
|
+
);
|
|
1770
|
+
|
|
1771
|
+
if (failures.length > 0) {
|
|
1772
|
+
throw new IndexedError("Error processing signed proposer preferences", failures);
|
|
1773
|
+
}
|
|
1774
|
+
},
|
|
1775
|
+
|
|
1774
1776
|
async getExecutionPayloadEnvelope({slot, beaconBlockRoot}) {
|
|
1775
1777
|
const fork = config.getForkName(slot);
|
|
1776
1778
|
|
|
@@ -1778,7 +1780,7 @@ export function getValidatorApi(
|
|
|
1778
1780
|
throw new ApiError(400, `getExecutionPayloadEnvelope not supported for pre-gloas fork=${fork}`);
|
|
1779
1781
|
}
|
|
1780
1782
|
|
|
1781
|
-
notWhileSyncing();
|
|
1783
|
+
notWhileSyncing(chain, sync.state);
|
|
1782
1784
|
await waitForSlot(slot);
|
|
1783
1785
|
|
|
1784
1786
|
const blockRootHex = toRootHex(beaconBlockRoot);
|
|
@@ -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
|
|
@@ -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
|
+
}
|
package/src/chain/chain.ts
CHANGED
|
@@ -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,
|
package/src/chain/interface.ts
CHANGED
|
@@ -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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {RootHex, Slot, gloas} from "@lodestar/types";
|
|
1
|
+
import {RootHex, Slot, ValidatorIndex, gloas} from "@lodestar/types";
|
|
2
2
|
import {toRootHex} from "@lodestar/utils";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -22,6 +22,10 @@ export class ProposerPreferencesPool {
|
|
|
22
22
|
return this.bySlot.get(slot)?.get(dependentRootHex) ?? null;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
isKnown(proposalSlot: Slot, dependentRoot: RootHex, validatorIndex: ValidatorIndex): boolean {
|
|
26
|
+
return this.get(proposalSlot, dependentRoot)?.message.validatorIndex === validatorIndex;
|
|
27
|
+
}
|
|
28
|
+
|
|
25
29
|
add(signed: gloas.SignedProposerPreferences): void {
|
|
26
30
|
const {proposalSlot, dependentRoot} = signed.message;
|
|
27
31
|
const rootHex = toRootHex(dependentRoot);
|
package/src/chain/options.ts
CHANGED
|
@@ -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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
155
|
+
if (isForkPostGloas(fork)) {
|
|
156
|
+
this.chain.builderCircuitBreaker.update(clockSlot);
|
|
157
|
+
} else {
|
|
158
|
+
// Update the builder status, if enabled shoot an api call to check status
|
|
159
|
+
this.chain.updateBuilderStatus(clockSlot);
|
|
160
|
+
if (this.chain.executionBuilder?.status === BuilderStatus.enabled) {
|
|
161
|
+
this.chain.executionBuilder.checkStatus().catch((e) => {
|
|
162
|
+
this.logger.error("Builder disabled as the check status api failed", {prepareSlot}, e as Error);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
161
165
|
}
|
|
162
166
|
}
|
|
163
167
|
|
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
ValidatorIndex,
|
|
44
44
|
Wei,
|
|
45
45
|
altair,
|
|
46
|
+
bellatrix,
|
|
46
47
|
capella,
|
|
47
48
|
deneb,
|
|
48
49
|
electra,
|
|
@@ -830,28 +831,19 @@ export function getPayloadAttributesForSSE(
|
|
|
830
831
|
feeRecipient,
|
|
831
832
|
});
|
|
832
833
|
|
|
833
|
-
|
|
834
|
-
if (isForkPostGloas(fork)) {
|
|
835
|
-
const parentBlock = chain.forkChoice.getBlockHexAndBlockHash(
|
|
836
|
-
toRootHex(parentBlockRoot),
|
|
837
|
-
toRootHex(parentBlockHash)
|
|
838
|
-
);
|
|
839
|
-
if (parentBlock?.executionPayloadBlockHash == null) {
|
|
840
|
-
throw Error(`Parent block not found in fork choice root=${toRootHex(parentBlockRoot)}`);
|
|
841
|
-
}
|
|
842
|
-
parentBlockNumber = parentBlock.executionPayloadNumber;
|
|
843
|
-
} else {
|
|
844
|
-
parentBlockNumber = prepareState.payloadBlockNumber;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
const ssePayloadAttributes: SSEPayloadAttributes = {
|
|
834
|
+
const ssePayloadAttributes = {
|
|
848
835
|
proposerIndex: prepareState.getBeaconProposer(prepareSlot),
|
|
849
836
|
proposalSlot: prepareSlot,
|
|
850
|
-
parentBlockNumber,
|
|
851
837
|
parentBlockRoot,
|
|
852
838
|
parentBlockHash,
|
|
853
839
|
payloadAttributes,
|
|
854
|
-
};
|
|
840
|
+
} as SSEPayloadAttributes;
|
|
841
|
+
|
|
842
|
+
if (!isForkPostGloas(fork)) {
|
|
843
|
+
// Removed in Gloas, builders can get the block number from the EL via the block hash if required
|
|
844
|
+
(ssePayloadAttributes as bellatrix.SSEPayloadAttributes).parentBlockNumber = prepareState.payloadBlockNumber;
|
|
845
|
+
}
|
|
846
|
+
|
|
855
847
|
return ssePayloadAttributes;
|
|
856
848
|
}
|
|
857
849
|
|
|
@@ -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
|
-
|
|
42
|
-
|
|
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
|
|
126
|
-
// `
|
|
127
|
-
|
|
128
|
-
try
|
|
129
|
-
|
|
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,
|