@lodestar/beacon-node 1.45.0-dev.51a1c44b27 → 1.45.0-dev.7acc183c2b
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/blocks/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/blocks/index.js +3 -2
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- 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 +67 -62
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/api/rest/base.d.ts.map +1 -1
- package/lib/api/rest/base.js +8 -0
- package/lib/api/rest/base.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +2 -5
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts +5 -2
- package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeProcessor.js +17 -17
- package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.js +13 -5
- package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.js +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.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 +3 -2
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +4 -3
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/errors/blockError.d.ts +29 -5
- package/lib/chain/errors/blockError.d.ts.map +1 -1
- package/lib/chain/errors/blockError.js +19 -4
- package/lib/chain/errors/blockError.js.map +1 -1
- package/lib/chain/errors/executionPayloadEnvelope.d.ts +11 -0
- package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/errors/executionPayloadEnvelope.js +2 -0
- package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
- package/lib/chain/interface.d.ts +3 -2
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/interface.js.map +1 -1
- package/lib/chain/lightClient/index.d.ts.map +1 -1
- package/lib/chain/lightClient/index.js +11 -6
- package/lib/chain/lightClient/index.js.map +1 -1
- package/lib/chain/lightClient/proofs.d.ts.map +1 -1
- package/lib/chain/lightClient/proofs.js +28 -2
- package/lib/chain/lightClient/proofs.js.map +1 -1
- package/lib/chain/lightClient/types.d.ts +3 -1
- package/lib/chain/lightClient/types.d.ts.map +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.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 +5 -0
- package/lib/chain/options.d.ts.map +1 -1
- package/lib/chain/options.js +1 -0
- 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/seenCache/index.d.ts +0 -1
- package/lib/chain/seenCache/index.d.ts.map +1 -1
- package/lib/chain/seenCache/index.js +0 -1
- package/lib/chain/seenCache/index.js.map +1 -1
- package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
- package/lib/chain/stateCache/persistentCheckpointsCache.js +3 -2
- package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
- package/lib/chain/validation/block.d.ts.map +1 -1
- package/lib/chain/validation/block.js +55 -4
- package/lib/chain/validation/block.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/executionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.js +30 -0
- package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
- package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
- package/lib/chain/validation/proposerPreferences.js +14 -5
- package/lib/chain/validation/proposerPreferences.js.map +1 -1
- package/lib/chain/validation/signatureSets/aggregateAndProof.d.ts.map +1 -1
- package/lib/chain/validation/signatureSets/aggregateAndProof.js +2 -3
- package/lib/chain/validation/signatureSets/aggregateAndProof.js.map +1 -1
- package/lib/db/repositories/lightclientSyncCommitteeWitness.d.ts.map +1 -1
- package/lib/db/repositories/lightclientSyncCommitteeWitness.js +42 -7
- package/lib/db/repositories/lightclientSyncCommitteeWitness.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 +2 -2
- package/lib/network/gossip/encoding.d.ts.map +1 -1
- package/lib/network/gossip/encoding.js +22 -9
- 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 +4 -3
- package/lib/network/gossip/gossipsub.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +831 -226
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/gossip/topic.js +47 -4
- 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 +12 -8
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/sync/range/chain.d.ts +4 -1
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +3 -3
- package/lib/sync/range/chain.js.map +1 -1
- package/lib/sync/range/range.d.ts.map +1 -1
- package/lib/sync/range/range.js +10 -0
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/unknownBlock.js +1 -1
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRange.js +6 -0
- package/lib/sync/utils/downloadByRange.js.map +1 -1
- package/lib/util/graffiti.d.ts +15 -0
- package/lib/util/graffiti.d.ts.map +1 -1
- package/lib/util/graffiti.js +55 -0
- package/lib/util/graffiti.js.map +1 -1
- package/package.json +19 -19
- package/src/api/impl/beacon/blocks/index.ts +3 -2
- 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 +75 -67
- package/src/api/rest/base.ts +9 -0
- package/src/chain/blocks/importBlock.ts +2 -7
- package/src/chain/blocks/payloadEnvelopeProcessor.ts +17 -17
- package/src/chain/blocks/utils/chainSegment.ts +16 -5
- package/src/chain/blocks/verifyBlocksSanityChecks.ts +1 -1
- package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -2
- package/src/chain/builderCircuitBreaker.ts +84 -0
- package/src/chain/chain.ts +7 -3
- package/src/chain/errors/blockError.ts +23 -6
- package/src/chain/errors/executionPayloadEnvelope.ts +9 -0
- package/src/chain/interface.ts +2 -2
- package/src/chain/lightClient/index.ts +12 -7
- package/src/chain/lightClient/proofs.ts +36 -1
- package/src/chain/lightClient/types.ts +3 -1
- package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
- package/src/chain/opPools/proposerPreferencesPool.ts +5 -1
- package/src/chain/options.ts +6 -0
- package/src/chain/prepareNextSlot.ts +11 -7
- package/src/chain/produceBlock/produceBlockBody.ts +9 -17
- package/src/chain/seenCache/index.ts +0 -1
- package/src/chain/stateCache/persistentCheckpointsCache.ts +3 -2
- package/src/chain/validation/block.ts +72 -4
- package/src/chain/validation/executionPayloadBid.ts +14 -9
- package/src/chain/validation/executionPayloadEnvelope.ts +39 -0
- package/src/chain/validation/proposerPreferences.ts +15 -5
- package/src/chain/validation/signatureSets/aggregateAndProof.ts +3 -3
- package/src/db/repositories/lightclientSyncCommitteeWitness.ts +57 -9
- 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 +20 -7
- package/src/network/gossip/gossipsub.ts +9 -3
- package/src/network/gossip/topic.ts +59 -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 +12 -7
- package/src/sync/range/chain.ts +4 -5
- package/src/sync/range/range.ts +10 -0
- package/src/sync/unknownBlock.ts +1 -1
- package/src/sync/utils/downloadByRange.ts +6 -0
- package/src/util/graffiti.ts +78 -0
- package/lib/chain/seenCache/seenProposerPreferences.d.ts +0 -16
- package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +0 -1
- package/lib/chain/seenCache/seenProposerPreferences.js +0 -26
- package/lib/chain/seenCache/seenProposerPreferences.js.map +0 -1
- package/src/chain/seenCache/seenProposerPreferences.ts +0 -32
|
@@ -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,37 +76,23 @@ 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
|
-
import {
|
|
87
|
+
import {getBlockGraffiti, toGraffitiBytes} from "../../../util/graffiti.js";
|
|
86
88
|
import {getLodestarClientVersion} from "../../../util/metadata.js";
|
|
87
89
|
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);
|
|
@@ -611,7 +569,10 @@ export function getValidatorApi(
|
|
|
611
569
|
}
|
|
612
570
|
|
|
613
571
|
const graffitiBytes = toGraffitiBytes(
|
|
614
|
-
graffiti
|
|
572
|
+
getBlockGraffiti(graffiti, getLodestarClientVersion(opts), chain.executionEngine.clientVersion, {
|
|
573
|
+
private: opts.private,
|
|
574
|
+
graffitiAppend: chain.opts.graffitiAppend,
|
|
575
|
+
})
|
|
615
576
|
);
|
|
616
577
|
|
|
617
578
|
const loggerContext = {
|
|
@@ -911,7 +872,7 @@ export function getValidatorApi(
|
|
|
911
872
|
throw new ApiError(400, `produceBlockV4 not supported for pre-gloas fork=${fork}`);
|
|
912
873
|
}
|
|
913
874
|
|
|
914
|
-
notWhileSyncing();
|
|
875
|
+
notWhileSyncing(chain, sync.state);
|
|
915
876
|
await waitForSlot(slot);
|
|
916
877
|
|
|
917
878
|
const parentBlock = chain.getProposerHead(slot);
|
|
@@ -921,7 +882,10 @@ export function getValidatorApi(
|
|
|
921
882
|
metrics?.blockProductionSlotDelta.set(slot - parentSlot);
|
|
922
883
|
|
|
923
884
|
const graffitiBytes = toGraffitiBytes(
|
|
924
|
-
graffiti
|
|
885
|
+
getBlockGraffiti(graffiti, getLodestarClientVersion(opts), chain.executionEngine.clientVersion, {
|
|
886
|
+
private: opts.private,
|
|
887
|
+
graffitiAppend: chain.opts.graffitiAppend,
|
|
888
|
+
})
|
|
925
889
|
);
|
|
926
890
|
|
|
927
891
|
// TODO GLOAS: respect builderSelection (MaxProfit, BuilderAlways, ExecutionAlways, etc.) to let
|
|
@@ -929,7 +893,10 @@ export function getValidatorApi(
|
|
|
929
893
|
// support when it is implemented.
|
|
930
894
|
const isBuildingOnFull = chain.forkChoice.shouldBuildOnFull(parentBlock, slot);
|
|
931
895
|
const bidParentBlockHash = isBuildingOnFull ? parentBlock.executionPayloadBlockHash : parentBlock.parentBlockHash;
|
|
932
|
-
const
|
|
896
|
+
const circuitBreakerActive = chain.builderCircuitBreaker.isActive(slot);
|
|
897
|
+
const builderBid = circuitBreakerActive
|
|
898
|
+
? null
|
|
899
|
+
: chain.executionPayloadBidPool.getBestBid(slot, bidParentBlockHash, parentBlockRootHex);
|
|
933
900
|
|
|
934
901
|
const logCtx = {
|
|
935
902
|
slot,
|
|
@@ -937,6 +904,7 @@ export function getValidatorApi(
|
|
|
937
904
|
parentBlockRoot: parentBlockRootHex,
|
|
938
905
|
parentBlockHash: parentBlock.executionPayloadBlockHash,
|
|
939
906
|
fork,
|
|
907
|
+
circuitBreakerActive,
|
|
940
908
|
...(builderBid !== null
|
|
941
909
|
? {
|
|
942
910
|
bidValue: builderBid.message.value,
|
|
@@ -1026,7 +994,7 @@ export function getValidatorApi(
|
|
|
1026
994
|
},
|
|
1027
995
|
|
|
1028
996
|
async produceAttestationData({committeeIndex, slot}) {
|
|
1029
|
-
notWhileSyncing();
|
|
997
|
+
notWhileSyncing(chain, sync.state);
|
|
1030
998
|
|
|
1031
999
|
await waitForSlot(slot); // Must never request for a future slot > currentSlot
|
|
1032
1000
|
|
|
@@ -1107,13 +1075,13 @@ export function getValidatorApi(
|
|
|
1107
1075
|
throw new ApiError(400, `producePayloadAttestationData is not supported before Gloas fork=${fork}`);
|
|
1108
1076
|
}
|
|
1109
1077
|
|
|
1110
|
-
notWhileSyncing();
|
|
1078
|
+
notWhileSyncing(chain, sync.state);
|
|
1111
1079
|
await waitForSlot(slot);
|
|
1112
1080
|
|
|
1113
1081
|
const block = chain.forkChoice.getCanonicalBlockAtSlot(slot);
|
|
1114
1082
|
if (!block) {
|
|
1115
|
-
// No block is seen at slot. Return
|
|
1116
|
-
|
|
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};
|
|
1117
1085
|
}
|
|
1118
1086
|
|
|
1119
1087
|
const payloadInput = chain.seenPayloadEnvelopeInputCache.get(block.blockRoot);
|
|
@@ -1192,7 +1160,7 @@ export function getValidatorApi(
|
|
|
1192
1160
|
},
|
|
1193
1161
|
|
|
1194
1162
|
async getProposerDuties({epoch}, _context, opts?: {v2?: boolean}) {
|
|
1195
|
-
notWhileSyncing();
|
|
1163
|
+
notWhileSyncing(chain, sync.state);
|
|
1196
1164
|
|
|
1197
1165
|
const currentEpoch = currentEpochWithDisparity();
|
|
1198
1166
|
const nextEpoch = currentEpoch + 1;
|
|
@@ -1239,7 +1207,7 @@ export function getValidatorApi(
|
|
|
1239
1207
|
// requested epoch is within that range, we can use the head state at current epoch
|
|
1240
1208
|
state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.getDuties);
|
|
1241
1209
|
} else {
|
|
1242
|
-
const res = await getStateResponseWithRegen(chain, startSlot);
|
|
1210
|
+
const res = await getStateResponseWithRegen(chain, sync, startSlot);
|
|
1243
1211
|
|
|
1244
1212
|
state = res.state instanceof Uint8Array ? chain.getHeadState().loadOtherState(res.state) : res.state;
|
|
1245
1213
|
|
|
@@ -1333,7 +1301,7 @@ export function getValidatorApi(
|
|
|
1333
1301
|
},
|
|
1334
1302
|
|
|
1335
1303
|
async getAttesterDuties({epoch, indices}) {
|
|
1336
|
-
notWhileSyncing();
|
|
1304
|
+
notWhileSyncing(chain, sync.state);
|
|
1337
1305
|
|
|
1338
1306
|
if (indices.length === 0) {
|
|
1339
1307
|
throw new ApiError(400, "No validator to get attester duties");
|
|
@@ -1393,7 +1361,7 @@ export function getValidatorApi(
|
|
|
1393
1361
|
},
|
|
1394
1362
|
|
|
1395
1363
|
async getPtcDuties({epoch, indices}) {
|
|
1396
|
-
notWhileSyncing();
|
|
1364
|
+
notWhileSyncing(chain, sync.state);
|
|
1397
1365
|
|
|
1398
1366
|
if (indices.length === 0) {
|
|
1399
1367
|
throw new ApiError(400, "No validator to get PTC duties");
|
|
@@ -1455,7 +1423,7 @@ export function getValidatorApi(
|
|
|
1455
1423
|
* @param validatorIndices an array of the validator indices for which to obtain the duties.
|
|
1456
1424
|
*/
|
|
1457
1425
|
async getSyncCommitteeDuties({epoch, indices}) {
|
|
1458
|
-
notWhileSyncing();
|
|
1426
|
+
notWhileSyncing(chain, sync.state);
|
|
1459
1427
|
|
|
1460
1428
|
if (indices.length === 0) {
|
|
1461
1429
|
throw new ApiError(400, "No validator to get attester duties");
|
|
@@ -1502,7 +1470,7 @@ export function getValidatorApi(
|
|
|
1502
1470
|
},
|
|
1503
1471
|
|
|
1504
1472
|
async getAggregatedAttestationV2({attestationDataRoot, slot, committeeIndex}) {
|
|
1505
|
-
notWhileSyncing();
|
|
1473
|
+
notWhileSyncing(chain, sync.state);
|
|
1506
1474
|
|
|
1507
1475
|
await waitForSlot(slot); // Must never request for a future slot > currentSlot
|
|
1508
1476
|
|
|
@@ -1525,7 +1493,7 @@ export function getValidatorApi(
|
|
|
1525
1493
|
},
|
|
1526
1494
|
|
|
1527
1495
|
async publishAggregateAndProofsV2({signedAggregateAndProofs}) {
|
|
1528
|
-
notWhileSyncing();
|
|
1496
|
+
notWhileSyncing(chain, sync.state);
|
|
1529
1497
|
|
|
1530
1498
|
const seenTimestampSec = Date.now() / 1000;
|
|
1531
1499
|
const failures: FailureList = [];
|
|
@@ -1586,7 +1554,7 @@ export function getValidatorApi(
|
|
|
1586
1554
|
* https://github.com/ethereum/beacon-APIs/pull/137
|
|
1587
1555
|
*/
|
|
1588
1556
|
async publishContributionAndProofs({contributionAndProofs}) {
|
|
1589
|
-
notWhileSyncing();
|
|
1557
|
+
notWhileSyncing(chain, sync.state);
|
|
1590
1558
|
|
|
1591
1559
|
const failures: FailureList = [];
|
|
1592
1560
|
|
|
@@ -1635,7 +1603,7 @@ export function getValidatorApi(
|
|
|
1635
1603
|
},
|
|
1636
1604
|
|
|
1637
1605
|
async prepareBeaconCommitteeSubnet({subscriptions}) {
|
|
1638
|
-
notWhileSyncing();
|
|
1606
|
+
notWhileSyncing(chain, sync.state);
|
|
1639
1607
|
|
|
1640
1608
|
await network.prepareBeaconCommitteeSubnets(
|
|
1641
1609
|
subscriptions.map(({validatorIndex, slot, isAggregator, committeesAtSlot, committeeIndex}) => ({
|
|
@@ -1668,7 +1636,7 @@ export function getValidatorApi(
|
|
|
1668
1636
|
* https://github.com/ethereum/beacon-APIs/pull/136
|
|
1669
1637
|
*/
|
|
1670
1638
|
async prepareSyncCommitteeSubnets({subscriptions}) {
|
|
1671
|
-
notWhileSyncing();
|
|
1639
|
+
notWhileSyncing(chain, sync.state);
|
|
1672
1640
|
|
|
1673
1641
|
// A `validatorIndex` can be in multiple subnets, so compute the CommitteeSubscription with double for loop
|
|
1674
1642
|
const subs: CommitteeSubscription[] = [];
|
|
@@ -1765,6 +1733,46 @@ export function getValidatorApi(
|
|
|
1765
1733
|
});
|
|
1766
1734
|
},
|
|
1767
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
|
+
|
|
1768
1776
|
async getExecutionPayloadEnvelope({slot, beaconBlockRoot}) {
|
|
1769
1777
|
const fork = config.getForkName(slot);
|
|
1770
1778
|
|
|
@@ -1772,7 +1780,7 @@ export function getValidatorApi(
|
|
|
1772
1780
|
throw new ApiError(400, `getExecutionPayloadEnvelope not supported for pre-gloas fork=${fork}`);
|
|
1773
1781
|
}
|
|
1774
1782
|
|
|
1775
|
-
notWhileSyncing();
|
|
1783
|
+
notWhileSyncing(chain, sync.state);
|
|
1776
1784
|
await waitForSlot(slot);
|
|
1777
1785
|
|
|
1778
1786
|
const blockRootHex = toRootHex(beaconBlockRoot);
|
package/src/api/rest/base.ts
CHANGED
|
@@ -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
|
|
@@ -426,7 +421,7 @@ export async function importBlock(
|
|
|
426
421
|
}
|
|
427
422
|
} catch (e) {
|
|
428
423
|
if (isStartSlotOfEpoch(proposalSlot)) {
|
|
429
|
-
notOverrideFcuReason = NotReorgedReason.
|
|
424
|
+
notOverrideFcuReason = NotReorgedReason.AtEpochBoundary;
|
|
430
425
|
} else {
|
|
431
426
|
this.logger.warn("Unable to get beacon proposer. Do not override fcu.", {proposalSlot}, e as Error);
|
|
432
427
|
}
|
|
@@ -8,28 +8,24 @@ import {ImportPayloadOpts} from "./types.js";
|
|
|
8
8
|
// TODO GLOAS: Set to be equal to DEFAULT_MAX_PENDING_UNFINALIZED_PAYLOAD_ENVELOPE_WRITES for now
|
|
9
9
|
const QUEUE_MAX_LENGTH = 16;
|
|
10
10
|
|
|
11
|
-
enum PayloadEnvelopeImportStatus {
|
|
12
|
-
queued = "queued",
|
|
13
|
-
importing = "importing",
|
|
14
|
-
imported = "imported",
|
|
15
|
-
}
|
|
16
|
-
|
|
17
11
|
/**
|
|
18
12
|
* PayloadEnvelopeProcessor processes payload envelope jobs in a queued fashion, one after the other.
|
|
19
13
|
*
|
|
20
14
|
* Jobs are enqueued only on envelope arrival (gossip or API). The envelope may reach us before
|
|
21
15
|
* the sampled data columns; importExecutionPayload awaits `verifyPayloadsDataAvailability`
|
|
22
16
|
* internally, so a queued job can pend for up to `PAYLOAD_DATA_AVAILABILITY_TIMEOUT` while
|
|
23
|
-
* waiting for columns. Duplicate triggers for the same payloadInput are deduped
|
|
17
|
+
* waiting for columns. Duplicate triggers for the same payloadInput are deduped by sharing the
|
|
18
|
+
* in-flight import promise: every caller observes the real import outcome. Resolving duplicates
|
|
19
|
+
* early instead would let BlockInputSync mark a still-pending payload as processed, re-queue it,
|
|
20
|
+
* and spin re-processing the same root until OOM while the import is blocked (e.g. EL syncing).
|
|
24
21
|
*/
|
|
25
22
|
export class PayloadEnvelopeProcessor {
|
|
26
23
|
readonly jobQueue: JobItemQueue<[PayloadEnvelopeInput, ImportPayloadOpts], void>;
|
|
27
|
-
private readonly
|
|
24
|
+
private readonly imports = new WeakMap<PayloadEnvelopeInput, Promise<void>>();
|
|
28
25
|
|
|
29
26
|
constructor(chain: BeaconChain, metrics: Metrics | null, signal: AbortSignal) {
|
|
30
27
|
this.jobQueue = new JobItemQueue<[PayloadEnvelopeInput, ImportPayloadOpts], void>(
|
|
31
28
|
(payloadInput, opts) => {
|
|
32
|
-
this.importStatus.set(payloadInput, PayloadEnvelopeImportStatus.importing);
|
|
33
29
|
return processExecutionPayload.call(chain, payloadInput, signal, opts);
|
|
34
30
|
},
|
|
35
31
|
{maxLength: QUEUE_MAX_LENGTH, noYieldIfOneItem: true, signal},
|
|
@@ -38,24 +34,28 @@ export class PayloadEnvelopeProcessor {
|
|
|
38
34
|
}
|
|
39
35
|
|
|
40
36
|
async processPayloadEnvelopeJob(payloadInput: PayloadEnvelopeInput, opts: ImportPayloadOpts = {}): Promise<void> {
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
const existing = this.imports.get(payloadInput);
|
|
38
|
+
if (existing) {
|
|
39
|
+
return existing;
|
|
43
40
|
}
|
|
44
41
|
|
|
45
42
|
await this.jobQueue.waitForSpace();
|
|
46
43
|
|
|
47
44
|
// Re-check after await, as another call may have queued this payload.
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
const queued = this.imports.get(payloadInput);
|
|
46
|
+
if (queued) {
|
|
47
|
+
return queued;
|
|
50
48
|
}
|
|
51
49
|
|
|
52
|
-
this.
|
|
50
|
+
const importPromise = this.jobQueue.push(payloadInput, opts);
|
|
51
|
+
this.imports.set(payloadInput, importPromise);
|
|
53
52
|
|
|
54
53
|
try {
|
|
55
|
-
await
|
|
56
|
-
this.importStatus.set(payloadInput, PayloadEnvelopeImportStatus.imported);
|
|
54
|
+
await importPromise;
|
|
57
55
|
} catch (e) {
|
|
58
|
-
|
|
56
|
+
// Drop the failed import so a later attempt can retry once the failure cause is resolved
|
|
57
|
+
// (e.g. BLOCK_NOT_IN_FORK_CHOICE after the block lands in fork choice)
|
|
58
|
+
this.imports.delete(payloadInput);
|
|
59
59
|
throw e;
|
|
60
60
|
}
|
|
61
61
|
}
|
|
@@ -86,11 +86,22 @@ export function assertLinearChainSegment(
|
|
|
86
86
|
// Maybe the previous slot's FULL envelope was orphaned — try falling back.
|
|
87
87
|
// If even prevExecHash doesn't match, the segment is non-linear.
|
|
88
88
|
if (bidParentHash !== prevExecHash) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
// i === 0 compares against the seeded fork-choice parent (segment boundary); i > 0
|
|
90
|
+
// compares against the in-segment predecessor's payload (broken link inside the segment).
|
|
91
|
+
throw new BlockError(
|
|
92
|
+
block,
|
|
93
|
+
i === 0
|
|
94
|
+
? {
|
|
95
|
+
code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN,
|
|
96
|
+
parentRoot: toRootHex(block.message.parentRoot),
|
|
97
|
+
parentBlockHash: bidParentHash,
|
|
98
|
+
}
|
|
99
|
+
: {
|
|
100
|
+
code: BlockErrorCode.NON_LINEAR_PAYLOAD_ROOTS,
|
|
101
|
+
parentBlockHash: bidParentHash,
|
|
102
|
+
expectedBlockHash: currentExecHash,
|
|
103
|
+
}
|
|
104
|
+
);
|
|
94
105
|
}
|
|
95
106
|
if (lastFullSlot !== null && payloadEnvelopes !== null) {
|
|
96
107
|
const orphanedInput = payloadEnvelopes.get(lastFullSlot);
|
|
@@ -94,7 +94,7 @@ export function verifyBlocksSanityChecks(
|
|
|
94
94
|
const parentRoot = toRootHex(block.message.parentRoot);
|
|
95
95
|
const parentBlockDefaultStatus = chain.forkChoice.getBlockHexDefaultStatus(parentRoot);
|
|
96
96
|
if (!parentBlockDefaultStatus) {
|
|
97
|
-
throw new BlockError(block, {code: BlockErrorCode.
|
|
97
|
+
throw new BlockError(block, {code: BlockErrorCode.PARENT_BLOCK_UNKNOWN, parentRoot});
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
parentBlock = parentBlockDefaultStatus;
|
|
@@ -100,8 +100,8 @@ export function verifyExecutionPayloadEnvelope(
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
// Verify consistency with expected withdrawals
|
|
103
|
-
const payloadWithdrawalsRoot = ssz.
|
|
104
|
-
const expectedWithdrawalsRoot = ssz.
|
|
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
|
+
}
|
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";
|
|
@@ -107,7 +108,6 @@ import {
|
|
|
107
108
|
SeenExecutionPayloadBids,
|
|
108
109
|
SeenPayloadAttesters,
|
|
109
110
|
SeenPayloadEnvelopeInput,
|
|
110
|
-
SeenProposerPreferences,
|
|
111
111
|
SeenSyncCommitteeMessages,
|
|
112
112
|
} from "./seenCache/index.js";
|
|
113
113
|
import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js";
|
|
@@ -152,6 +152,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
152
152
|
readonly genesisValidatorsRoot: Root;
|
|
153
153
|
readonly executionEngine: IExecutionEngine;
|
|
154
154
|
readonly executionBuilder?: IExecutionBuilder;
|
|
155
|
+
readonly builderCircuitBreaker: BuilderCircuitBreaker;
|
|
155
156
|
// Expose config for convenience in modularized functions
|
|
156
157
|
readonly config: BeaconConfig;
|
|
157
158
|
readonly custodyConfig: CustodyConfig;
|
|
@@ -189,7 +190,6 @@ export class BeaconChain implements IBeaconChain {
|
|
|
189
190
|
readonly seenPayloadAttesters = new SeenPayloadAttesters();
|
|
190
191
|
readonly seenAggregatedAttestations: SeenAggregatedAttestations;
|
|
191
192
|
readonly seenExecutionPayloadBids = new SeenExecutionPayloadBids();
|
|
192
|
-
readonly seenProposerPreferences = new SeenProposerPreferences();
|
|
193
193
|
readonly seenBlockProposers = new SeenBlockProposers();
|
|
194
194
|
readonly seenSyncCommitteeMessages = new SeenSyncCommitteeMessages();
|
|
195
195
|
readonly seenContributionAndProof: SeenContributionAndProof;
|
|
@@ -427,6 +427,11 @@ export class BeaconChain implements IBeaconChain {
|
|
|
427
427
|
|
|
428
428
|
this.forkChoice = forkChoice;
|
|
429
429
|
|
|
430
|
+
this.builderCircuitBreaker = new BuilderCircuitBreaker(
|
|
431
|
+
{faultInspectionWindow: opts.faultInspectionWindow, allowedFaults: opts.allowedFaults},
|
|
432
|
+
{forkChoice, logger, metrics}
|
|
433
|
+
);
|
|
434
|
+
|
|
430
435
|
this.seenPayloadEnvelopeInputCache = new SeenPayloadEnvelopeInput({
|
|
431
436
|
config,
|
|
432
437
|
clock,
|
|
@@ -1473,7 +1478,6 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1473
1478
|
this.payloadAttestationPool.prune(slot);
|
|
1474
1479
|
this.executionPayloadBidPool.prune(slot);
|
|
1475
1480
|
this.seenExecutionPayloadBids.prune(slot);
|
|
1476
|
-
this.seenProposerPreferences.prune(slot);
|
|
1477
1481
|
this.proposerPreferencesPool.prune(slot);
|
|
1478
1482
|
this.seenAttestationDatas.onSlot(slot);
|
|
1479
1483
|
this.reprocessController.onSlot(slot);
|