@lodestar/beacon-node 1.46.0 → 1.47.0-dev.09607ce6d4
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 -6
- package/lib/api/impl/beacon/blocks/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 +4 -0
- package/lib/api/impl/beacon/pool/index.js.map +1 -1
- package/lib/api/impl/lodestar/index.d.ts.map +1 -1
- package/lib/api/impl/lodestar/index.js +11 -12
- package/lib/api/impl/lodestar/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +12 -22
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/api/impl/validator/utils.d.ts +5 -0
- package/lib/api/impl/validator/utils.d.ts.map +1 -1
- package/lib/api/impl/validator/utils.js +18 -12
- package/lib/api/impl/validator/utils.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/metrics.d.ts.map +1 -1
- package/lib/chain/archiveStore/historicalState/metrics.js +21 -0
- package/lib/chain/archiveStore/historicalState/metrics.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +3 -8
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +1 -1
- package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
- package/lib/chain/blocks/index.d.ts.map +1 -1
- package/lib/chain/blocks/index.js +3 -5
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.js +11 -16
- package/lib/chain/blocks/verifyBlock.js.map +1 -1
- package/lib/chain/builderCircuitBreaker.d.ts +6 -5
- package/lib/chain/builderCircuitBreaker.d.ts.map +1 -1
- package/lib/chain/builderCircuitBreaker.js +28 -15
- package/lib/chain/builderCircuitBreaker.js.map +1 -1
- package/lib/chain/chain.d.ts +1 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +8 -2
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/errors/blockError.d.ts +1 -0
- package/lib/chain/errors/blockError.d.ts.map +1 -1
- package/lib/chain/options.d.ts +1 -3
- package/lib/chain/options.d.ts.map +1 -1
- package/lib/chain/options.js.map +1 -1
- package/lib/chain/prepareNextSlot.js +2 -2
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts +2 -0
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +8 -3
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/seenCache/seenBlockProposers.d.ts +5 -1
- package/lib/chain/seenCache/seenBlockProposers.d.ts.map +1 -1
- package/lib/chain/seenCache/seenBlockProposers.js +17 -7
- package/lib/chain/seenCache/seenBlockProposers.js.map +1 -1
- package/lib/chain/validation/block.d.ts.map +1 -1
- package/lib/chain/validation/block.js +21 -7
- package/lib/chain/validation/block.js.map +1 -1
- package/lib/execution/builder/http.d.ts +10 -7
- package/lib/execution/builder/http.d.ts.map +1 -1
- package/lib/execution/builder/http.js +11 -8
- package/lib/execution/builder/http.js.map +1 -1
- package/lib/metrics/metrics/beacon.d.ts +3 -3
- package/lib/metrics/metrics/beacon.d.ts.map +1 -1
- package/lib/metrics/metrics/beacon.js +9 -9
- package/lib/metrics/metrics/beacon.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +10 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +16 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +32 -5
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/sync/range/batch.js +1 -1
- package/lib/sync/range/batch.js.map +1 -1
- package/lib/sync/range/utils/hashBlocks.d.ts +16 -4
- package/lib/sync/range/utils/hashBlocks.d.ts.map +1 -1
- package/lib/sync/range/utils/hashBlocks.js +49 -18
- package/lib/sync/range/utils/hashBlocks.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +17 -5
- package/lib/sync/unknownBlock.js.map +1 -1
- package/package.json +16 -16
- package/src/api/impl/beacon/blocks/index.ts +4 -6
- package/src/api/impl/beacon/pool/index.ts +5 -0
- package/src/api/impl/lodestar/index.ts +11 -12
- package/src/api/impl/validator/index.ts +22 -27
- package/src/api/impl/validator/utils.ts +30 -15
- package/src/chain/archiveStore/historicalState/metrics.ts +22 -0
- package/src/chain/blocks/importBlock.ts +2 -7
- package/src/chain/blocks/importExecutionPayload.ts +1 -1
- package/src/chain/blocks/index.ts +3 -5
- package/src/chain/blocks/verifyBlock.ts +21 -25
- package/src/chain/builderCircuitBreaker.ts +31 -17
- package/src/chain/chain.ts +8 -1
- package/src/chain/errors/blockError.ts +1 -1
- package/src/chain/options.ts +1 -3
- package/src/chain/prepareNextSlot.ts +2 -2
- package/src/chain/produceBlock/produceBlockBody.ts +15 -2
- package/src/chain/seenCache/seenBlockProposers.ts +24 -7
- package/src/chain/validation/block.ts +21 -7
- package/src/execution/builder/http.ts +11 -11
- package/src/metrics/metrics/beacon.ts +9 -9
- package/src/metrics/metrics/lodestar.ts +16 -0
- package/src/network/processor/gossipHandlers.ts +35 -3
- package/src/sync/range/batch.ts +1 -1
- package/src/sync/range/utils/hashBlocks.ts +56 -21
- package/src/sync/unknownBlock.ts +18 -5
|
@@ -220,12 +220,6 @@ export function getBeaconBlockApi({
|
|
|
220
220
|
// Block has already been seen, e.g. via gossip racing the publish API. Benign.
|
|
221
221
|
chain.logger.debug("Ignoring already-known block during publishing", valLogMeta);
|
|
222
222
|
return;
|
|
223
|
-
case BlockErrorCode.REPEAT_PROPOSAL:
|
|
224
|
-
// The proposer already produced a block for this slot. For a solo setup this is a
|
|
225
|
-
// notable signal (duplicate-proposal attempt). For fallback / DVT setups it is
|
|
226
|
-
// expected on every block where another node published first.
|
|
227
|
-
chain.logger.warn("Ignoring repeat-proposal block during publishing", valLogMeta);
|
|
228
|
-
return;
|
|
229
223
|
}
|
|
230
224
|
}
|
|
231
225
|
|
|
@@ -1032,6 +1026,7 @@ export function getBeaconBlockApi({
|
|
|
1032
1026
|
},
|
|
1033
1027
|
|
|
1034
1028
|
async publishExecutionPayloadBid({signedExecutionPayloadBid}) {
|
|
1029
|
+
const seenTimestampSec = Date.now() / 1000;
|
|
1035
1030
|
const bid = signedExecutionPayloadBid.message;
|
|
1036
1031
|
const slot = bid.slot;
|
|
1037
1032
|
const fork = config.getForkName(slot);
|
|
@@ -1042,6 +1037,9 @@ export function getBeaconBlockApi({
|
|
|
1042
1037
|
|
|
1043
1038
|
await validateApiExecutionPayloadBid(chain, signedExecutionPayloadBid);
|
|
1044
1039
|
|
|
1040
|
+
const elapsedSec = chain.clock.secFromSlot(slot, seenTimestampSec);
|
|
1041
|
+
metrics?.gossipExecutionPayloadBid.elapsedTimeTillReceived.observe({source: OpSource.api}, elapsedSec);
|
|
1042
|
+
|
|
1045
1043
|
try {
|
|
1046
1044
|
const insertOutcome = chain.executionPayloadBidPool.add(signedExecutionPayloadBid);
|
|
1047
1045
|
metrics?.opPool.executionPayloadBidPool.apiInsertOutcome.inc({insertOutcome});
|
|
@@ -25,6 +25,7 @@ import {validateApiPayloadAttestationMessage} from "../../../../chain/validation
|
|
|
25
25
|
import {validateApiProposerSlashing} from "../../../../chain/validation/proposerSlashing.js";
|
|
26
26
|
import {validateApiSyncCommittee} from "../../../../chain/validation/syncCommittee.js";
|
|
27
27
|
import {validateApiVoluntaryExit} from "../../../../chain/validation/voluntaryExit.js";
|
|
28
|
+
import {OpSource} from "../../../../chain/validatorMonitor.js";
|
|
28
29
|
import {validateGossipFnRetryUnknownRoot} from "../../../../network/processor/gossipHandlers.js";
|
|
29
30
|
import {ApiError, FailureList, IndexedError} from "../../errors.js";
|
|
30
31
|
import {ApiModules} from "../../types.js";
|
|
@@ -211,6 +212,7 @@ export function getBeaconPoolApi({
|
|
|
211
212
|
},
|
|
212
213
|
|
|
213
214
|
async submitPayloadAttestationMessages({payloadAttestationMessages}) {
|
|
215
|
+
const seenTimestampSec = Date.now() / 1000;
|
|
214
216
|
const failures: FailureList = [];
|
|
215
217
|
|
|
216
218
|
await Promise.all(
|
|
@@ -226,6 +228,9 @@ export function getBeaconPoolApi({
|
|
|
226
228
|
beaconBlockRoot
|
|
227
229
|
);
|
|
228
230
|
|
|
231
|
+
const delaySec = chain.clock.secFromSlot(slot, seenTimestampSec);
|
|
232
|
+
metrics?.gossipPayloadAttestationMessage.elapsedTimeTillReceived.observe({source: OpSource.api}, delaySec);
|
|
233
|
+
|
|
229
234
|
const insertOutcome = chain.payloadAttestationPool.add(
|
|
230
235
|
payloadAttestationMessage,
|
|
231
236
|
attDataRootHex,
|
|
@@ -280,7 +280,7 @@ export function getLodestarApi({
|
|
|
280
280
|
},
|
|
281
281
|
|
|
282
282
|
async getFastConfirmationInfo() {
|
|
283
|
-
const
|
|
283
|
+
const fcrStore = chain.forkChoice.getFastConfirmationStore();
|
|
284
284
|
const confirmedBlock = chain.forkChoice.getConfirmedBlock();
|
|
285
285
|
const justifiedCheckpoint = chain.forkChoice.getJustifiedCheckpoint();
|
|
286
286
|
const finalizedCheckpoint = chain.forkChoice.getFinalizedCheckpoint();
|
|
@@ -290,21 +290,20 @@ export function getLodestarApi({
|
|
|
290
290
|
return {
|
|
291
291
|
data: {
|
|
292
292
|
confirmed: {
|
|
293
|
-
|
|
294
|
-
slot: confirmedBlock?.slot ??
|
|
293
|
+
root: fromHex(fcrStore.confirmedRoot),
|
|
294
|
+
slot: confirmedBlock?.slot ?? 0,
|
|
295
295
|
},
|
|
296
296
|
head: {
|
|
297
|
-
|
|
297
|
+
root: fromHex(headRoot),
|
|
298
298
|
slot: head.slot,
|
|
299
299
|
},
|
|
300
|
-
justifiedCheckpoint
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
},
|
|
300
|
+
justifiedCheckpoint,
|
|
301
|
+
finalizedCheckpoint,
|
|
302
|
+
previousEpochObservedJustifiedCheckpoint: fcrStore.previousEpochObservedJustifiedCheckpoint,
|
|
303
|
+
currentEpochObservedJustifiedCheckpoint: fcrStore.currentEpochObservedJustifiedCheckpoint,
|
|
304
|
+
previousEpochGreatestUnrealizedCheckpoint: fcrStore.previousEpochGreatestUnrealizedCheckpoint,
|
|
305
|
+
previousSlotHead: fromHex(fcrStore.previousSlotHead),
|
|
306
|
+
currentSlotHead: fromHex(fcrStore.currentSlotHead),
|
|
308
307
|
},
|
|
309
308
|
};
|
|
310
309
|
},
|
|
@@ -92,7 +92,12 @@ import {getStateResponseWithRegen} from "../beacon/state/utils.js";
|
|
|
92
92
|
import {ApiError, FailureList, IndexedError, NodeIsSyncing, OnlySupportedByDVT} from "../errors.js";
|
|
93
93
|
import {ApiModules} from "../types.js";
|
|
94
94
|
import {notWhileSyncing} from "../utils.js";
|
|
95
|
-
import {
|
|
95
|
+
import {
|
|
96
|
+
computeSubnetForCommitteesAtSlot,
|
|
97
|
+
getPubkeysForIndices,
|
|
98
|
+
selectBlockProductionSource,
|
|
99
|
+
selectBlockProductionSourceByBoostFactor,
|
|
100
|
+
} from "./utils.js";
|
|
96
101
|
|
|
97
102
|
/**
|
|
98
103
|
* Cutoff time to wait from start of the slot for execution and builder block production apis to resolve.
|
|
@@ -577,8 +582,7 @@ export function getValidatorApi(
|
|
|
577
582
|
builderSelection,
|
|
578
583
|
isBuilderEnabled,
|
|
579
584
|
strictFeeRecipientCheck,
|
|
580
|
-
|
|
581
|
-
builderBoostFactor: `${builderBoostFactor}`,
|
|
585
|
+
builderBoostFactor,
|
|
582
586
|
};
|
|
583
587
|
|
|
584
588
|
logger.verbose("Assembling block with produceEngineOrBuilderBlock", loggerContext);
|
|
@@ -851,8 +855,8 @@ export function getValidatorApi(
|
|
|
851
855
|
randaoReveal,
|
|
852
856
|
graffiti,
|
|
853
857
|
feeRecipient,
|
|
858
|
+
strictFeeRecipientCheck,
|
|
854
859
|
includePayload,
|
|
855
|
-
builderSelection,
|
|
856
860
|
builderBoostFactor,
|
|
857
861
|
}) {
|
|
858
862
|
const fork = config.getForkName(slot);
|
|
@@ -861,11 +865,6 @@ export function getValidatorApi(
|
|
|
861
865
|
throw new ApiError(400, `produceBlockV4 not supported for pre-gloas fork=${fork}`);
|
|
862
866
|
}
|
|
863
867
|
|
|
864
|
-
builderSelection = builderSelection ?? routes.validator.BuilderSelection.MaxProfit;
|
|
865
|
-
if (builderSelection === routes.validator.BuilderSelection.BuilderOnly) {
|
|
866
|
-
logger.warn("Builder selection builderonly is no longer supported, treating as builderalways");
|
|
867
|
-
builderSelection = routes.validator.BuilderSelection.BuilderAlways;
|
|
868
|
-
}
|
|
869
868
|
builderBoostFactor = builderBoostFactor ?? BigInt(100);
|
|
870
869
|
if (builderBoostFactor > MAX_BUILDER_BOOST_FACTOR) {
|
|
871
870
|
throw new ApiError(400, `Invalid builderBoostFactor=${builderBoostFactor} > MAX_BUILDER_BOOST_FACTOR`);
|
|
@@ -896,14 +895,11 @@ export function getValidatorApi(
|
|
|
896
895
|
// TODO GLOAS: add external builder api support when it is implemented
|
|
897
896
|
const isBuildingOnFull = chain.forkChoice.shouldBuildOnFull(parentBlock, slot);
|
|
898
897
|
const bidParentBlockHash = isBuildingOnFull ? parentBlock.executionPayloadBlockHash : parentBlock.parentBlockHash;
|
|
899
|
-
|
|
900
|
-
//
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
builderSelection === routes.validator.BuilderSelection.ExecutionOnly || circuitBreakerActive
|
|
905
|
-
? null
|
|
906
|
-
: chain.executionPayloadBidPool.getBestBid(slot, bidParentBlockHash, parentBlockRootHex);
|
|
898
|
+
const circuitBreakerActive = chain.builderCircuitBreaker.isActive(slot, parentBlock);
|
|
899
|
+
// Keep a builder bid as fallback unless the circuit breaker is active
|
|
900
|
+
const builderBid = circuitBreakerActive
|
|
901
|
+
? null
|
|
902
|
+
: chain.executionPayloadBidPool.getBestBid(slot, bidParentBlockHash, parentBlockRootHex);
|
|
907
903
|
|
|
908
904
|
const logCtx = {
|
|
909
905
|
slot,
|
|
@@ -911,8 +907,8 @@ export function getValidatorApi(
|
|
|
911
907
|
parentBlockRoot: parentBlockRootHex,
|
|
912
908
|
parentBlockHash: parentBlock.executionPayloadBlockHash,
|
|
913
909
|
fork,
|
|
914
|
-
builderSelection,
|
|
915
910
|
builderBoostFactor,
|
|
911
|
+
strictFeeRecipientCheck,
|
|
916
912
|
circuitBreakerActive,
|
|
917
913
|
...(builderBid !== null
|
|
918
914
|
? {
|
|
@@ -936,6 +932,7 @@ export function getValidatorApi(
|
|
|
936
932
|
randaoReveal,
|
|
937
933
|
graffiti: graffitiBytes,
|
|
938
934
|
feeRecipient,
|
|
935
|
+
strictFeeRecipientCheck,
|
|
939
936
|
commonBlockBodyPromise,
|
|
940
937
|
};
|
|
941
938
|
|
|
@@ -960,12 +957,8 @@ export function getValidatorApi(
|
|
|
960
957
|
chain.produceBlock(baseAttrs)
|
|
961
958
|
).then((engineBlock) => {
|
|
962
959
|
// No need to wait for the bid block if the engine block will always be selected due to
|
|
963
|
-
// suspected builder censorship
|
|
964
|
-
if (
|
|
965
|
-
engineBlock.shouldOverrideBuilder ||
|
|
966
|
-
builderBoostFactor === BigInt(0) ||
|
|
967
|
-
builderSelection === routes.validator.BuilderSelection.ExecutionAlways
|
|
968
|
-
) {
|
|
960
|
+
// suspected builder censorship or a builder boost factor of 0
|
|
961
|
+
if (engineBlock.shouldOverrideBuilder || builderBoostFactor === BigInt(0)) {
|
|
969
962
|
controller.abort();
|
|
970
963
|
}
|
|
971
964
|
return engineBlock;
|
|
@@ -994,8 +987,7 @@ export function getValidatorApi(
|
|
|
994
987
|
});
|
|
995
988
|
logger.warn("Selected local block: censorship suspected in builder bid", logCtx);
|
|
996
989
|
} else if (engineResult.status === "fulfilled" && bidResult.status === "fulfilled") {
|
|
997
|
-
const result =
|
|
998
|
-
builderSelection,
|
|
990
|
+
const result = selectBlockProductionSourceByBoostFactor({
|
|
999
991
|
builderBoostFactor,
|
|
1000
992
|
engineExecutionPayloadValue: engineResult.value.executionPayloadValue,
|
|
1001
993
|
// The bid value is the payment to the proposer, in Gwei
|
|
@@ -1061,7 +1053,10 @@ export function getValidatorApi(
|
|
|
1061
1053
|
root: blockRoot,
|
|
1062
1054
|
});
|
|
1063
1055
|
if (chain.opts.persistProducedBlocks) {
|
|
1064
|
-
void chain.persistBlock(
|
|
1056
|
+
void chain.persistBlock(
|
|
1057
|
+
block,
|
|
1058
|
+
source === ProducedBlockSource.builder ? "produced_builder_block" : "produced_engine_block"
|
|
1059
|
+
);
|
|
1065
1060
|
}
|
|
1066
1061
|
|
|
1067
1062
|
// Include the payload for self-builds unless disabled (stateless flow)
|
|
@@ -59,24 +59,39 @@ export function selectBlockProductionSource({
|
|
|
59
59
|
return {source: ProducedBlockSource.engine, reason: EngineBlockSelectionReason.EnginePreferred};
|
|
60
60
|
|
|
61
61
|
case routes.validator.BuilderSelection.Default:
|
|
62
|
-
case routes.validator.BuilderSelection.MaxProfit:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return {source: ProducedBlockSource.builder, reason: BuilderBlockSelectionReason.BuilderPreferred};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (engineExecutionPayloadValue >= (builderExecutionPayloadValue * builderBoostFactor) / BigInt(100)) {
|
|
72
|
-
return {source: ProducedBlockSource.engine, reason: EngineBlockSelectionReason.BlockValue};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return {source: ProducedBlockSource.builder, reason: BuilderBlockSelectionReason.BlockValue};
|
|
76
|
-
}
|
|
62
|
+
case routes.validator.BuilderSelection.MaxProfit:
|
|
63
|
+
return selectBlockProductionSourceByBoostFactor({
|
|
64
|
+
engineExecutionPayloadValue,
|
|
65
|
+
builderExecutionPayloadValue,
|
|
66
|
+
builderBoostFactor,
|
|
67
|
+
});
|
|
77
68
|
|
|
78
69
|
case routes.validator.BuilderSelection.BuilderAlways:
|
|
79
70
|
case routes.validator.BuilderSelection.BuilderOnly:
|
|
80
71
|
return {source: ProducedBlockSource.builder, reason: BuilderBlockSelectionReason.BuilderPreferred};
|
|
81
72
|
}
|
|
82
73
|
}
|
|
74
|
+
|
|
75
|
+
export function selectBlockProductionSourceByBoostFactor({
|
|
76
|
+
engineExecutionPayloadValue,
|
|
77
|
+
builderExecutionPayloadValue,
|
|
78
|
+
builderBoostFactor,
|
|
79
|
+
}: {
|
|
80
|
+
engineExecutionPayloadValue: bigint;
|
|
81
|
+
builderExecutionPayloadValue: bigint;
|
|
82
|
+
builderBoostFactor: bigint;
|
|
83
|
+
}): BlockSelectionResult {
|
|
84
|
+
if (builderBoostFactor === BigInt(0)) {
|
|
85
|
+
return {source: ProducedBlockSource.engine, reason: EngineBlockSelectionReason.EnginePreferred};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (builderBoostFactor === MAX_BUILDER_BOOST_FACTOR) {
|
|
89
|
+
return {source: ProducedBlockSource.builder, reason: BuilderBlockSelectionReason.BuilderPreferred};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (engineExecutionPayloadValue >= (builderExecutionPayloadValue * builderBoostFactor) / BigInt(100)) {
|
|
93
|
+
return {source: ProducedBlockSource.engine, reason: EngineBlockSelectionReason.BlockValue};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {source: ProducedBlockSource.builder, reason: BuilderBlockSelectionReason.BlockValue};
|
|
97
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {ForkName} from "@lodestar/params";
|
|
1
2
|
import {
|
|
2
3
|
BeaconStateTransitionMetrics,
|
|
3
4
|
EpochTransitionStep,
|
|
@@ -45,6 +46,27 @@ export function createHistoricalStateTransitionMetrics(
|
|
|
45
46
|
labelNames: ["step"],
|
|
46
47
|
buckets: [0.01, 0.05, 0.1, 0.2, 0.5, 0.75, 1],
|
|
47
48
|
}),
|
|
49
|
+
forkUpgradeTime: metricsRegister.histogram<{fork: ForkName}>({
|
|
50
|
+
name: "lodestar_historical_state_stfn_fork_upgrade_seconds",
|
|
51
|
+
help: "Time to upgrade the state at a fork boundary in seconds",
|
|
52
|
+
labelNames: ["fork"],
|
|
53
|
+
buckets: [0.05, 0.1, 0.25, 0.5, 1, 2, 5, 10, 30],
|
|
54
|
+
}),
|
|
55
|
+
onboardBuildersTime: metricsRegister.histogram({
|
|
56
|
+
name: "lodestar_historical_state_stfn_gloas_onboard_builders_seconds",
|
|
57
|
+
help: "Time spent in onboardBuildersFromPendingDeposits at the gloas fork transition",
|
|
58
|
+
buckets: [0.05, 0.1, 0.25, 0.5, 1, 2, 5, 10, 30],
|
|
59
|
+
}),
|
|
60
|
+
onboardBuildersDeposits: metricsRegister.gauge<{outcome: "onboarded" | "topup" | "kept" | "dropped"}>({
|
|
61
|
+
name: "lodestar_historical_state_stfn_gloas_onboard_builders_deposits",
|
|
62
|
+
help: "Pending deposits handled at the gloas fork transition, by outcome",
|
|
63
|
+
labelNames: ["outcome"],
|
|
64
|
+
}),
|
|
65
|
+
onboardBuildersSignatureChecks: metricsRegister.gauge<{source: "cache" | "verified"}>({
|
|
66
|
+
name: "lodestar_historical_state_stfn_gloas_onboard_builders_signature_checks",
|
|
67
|
+
help: "Builder deposit signature checks at the gloas fork transition, by whether the pre-verify cache served them",
|
|
68
|
+
labelNames: ["source"],
|
|
69
|
+
}),
|
|
48
70
|
processBlockTime: metricsRegister.histogram({
|
|
49
71
|
name: "lodestar_historical_state_stfn_process_block_seconds",
|
|
50
72
|
help: "Time to process a single block in seconds",
|
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
RootCache,
|
|
23
23
|
computeEpochAtSlot,
|
|
24
24
|
computeTimeAtSlot,
|
|
25
|
-
isStartSlotOfEpoch,
|
|
26
25
|
isStatePostAltair,
|
|
27
26
|
isStatePostBellatrix,
|
|
28
27
|
} from "@lodestar/state-transition";
|
|
@@ -404,11 +403,7 @@ export async function importBlock(
|
|
|
404
403
|
notOverrideFcuReason = NotReorgedReason.NotProposerOfNextSlot;
|
|
405
404
|
}
|
|
406
405
|
} catch (e) {
|
|
407
|
-
|
|
408
|
-
notOverrideFcuReason = NotReorgedReason.AtEpochBoundary;
|
|
409
|
-
} else {
|
|
410
|
-
this.logger.warn("Unable to get beacon proposer. Do not override fcu.", {proposalSlot}, e as Error);
|
|
411
|
-
}
|
|
406
|
+
this.logger.warn("Unable to get beacon proposer. Do not override fcu.", {proposalSlot}, e as Error);
|
|
412
407
|
}
|
|
413
408
|
|
|
414
409
|
if (shouldOverrideFcu) {
|
|
@@ -445,7 +440,7 @@ export async function importBlock(
|
|
|
445
440
|
* zero block hash (pre TTD)
|
|
446
441
|
*/
|
|
447
442
|
const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice, this.logger);
|
|
448
|
-
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.forkChoice
|
|
443
|
+
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.forkChoice);
|
|
449
444
|
if (headBlockHash !== ZERO_HASH_HEX) {
|
|
450
445
|
this.executionEngine
|
|
451
446
|
.notifyForkchoiceUpdate(
|
|
@@ -250,7 +250,7 @@ export async function importExecutionPayload(
|
|
|
250
250
|
const head = this.forkChoice.getHead();
|
|
251
251
|
if (!this.opts.disableImportExecutionFcU && blockRootHex === head.blockRoot) {
|
|
252
252
|
const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice, this.logger);
|
|
253
|
-
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.forkChoice
|
|
253
|
+
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.forkChoice);
|
|
254
254
|
this.executionEngine.notifyForkchoiceUpdate(fork, blockHashHex, safeBlockHash, finalizedBlockHash).catch((e) => {
|
|
255
255
|
if (!isErrorAborted(e) && !isQueueErrorAborted(e)) {
|
|
256
256
|
this.logger.error("Error pushing notifyForkchoiceUpdate()", {blockHashHex, finalizedBlockHash}, e);
|
|
@@ -108,11 +108,9 @@ export async function processBlocks(
|
|
|
108
108
|
throw segmentExecStatus.execAborted.execError;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
this.seenBlockProposers.add(block.slot, block.proposerIndex);
|
|
115
|
-
}
|
|
111
|
+
for (const blockInput of relevantBlocks) {
|
|
112
|
+
const block = blockInput.getBlock().message;
|
|
113
|
+
this.seenBlockProposers.add(block.slot, block.proposerIndex, blockInput.blockRootHex);
|
|
116
114
|
}
|
|
117
115
|
|
|
118
116
|
const {executionStatuses} = segmentExecStatus;
|
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
computeEpochAtSlot,
|
|
7
7
|
signedBlockToSignedHeader,
|
|
8
8
|
} from "@lodestar/state-transition";
|
|
9
|
-
import {IndexedAttestation, Slot, deneb
|
|
10
|
-
import {toRootHex} from "@lodestar/utils";
|
|
9
|
+
import {IndexedAttestation, Slot, deneb} from "@lodestar/types";
|
|
11
10
|
import {getBlobKzgCommitments} from "../../util/dataColumns.js";
|
|
11
|
+
import {callInNextEventLoop} from "../../util/eventLoop.js";
|
|
12
12
|
import type {BeaconChain} from "../chain.js";
|
|
13
13
|
import {BlockError, BlockErrorCode} from "../errors/index.js";
|
|
14
14
|
import {BlockProcessOpts} from "../options.js";
|
|
@@ -193,34 +193,30 @@ export async function verifyBlocksInEpoch(
|
|
|
193
193
|
),
|
|
194
194
|
|
|
195
195
|
// All signatures at once
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
)
|
|
207
|
-
: Promise.resolve({verifySignaturesTime: Date.now()}),
|
|
196
|
+
verifyBlocksSignatures(
|
|
197
|
+
this.config,
|
|
198
|
+
this.bls,
|
|
199
|
+
this.logger,
|
|
200
|
+
this.metrics,
|
|
201
|
+
preState0,
|
|
202
|
+
blocks,
|
|
203
|
+
indexedAttestationsByBlock,
|
|
204
|
+
opts
|
|
205
|
+
),
|
|
208
206
|
|
|
209
207
|
// TODO GLOAS: can verify payload signatures in batch too
|
|
210
208
|
// maybe chain with the above verifyBlocksSignatures()
|
|
211
209
|
]);
|
|
212
210
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
this.processProposerEquivocation(slot, proposerIndex);
|
|
223
|
-
}
|
|
211
|
+
for (const blockInput of blockInputs) {
|
|
212
|
+
const block = blockInput.getBlock();
|
|
213
|
+
const {slot, proposerIndex} = block.message;
|
|
214
|
+
const signedBlockHeader = signedBlockToSignedHeader(this.config, block);
|
|
215
|
+
this.seenBlockProposers.observeBlockRoot(slot, proposerIndex, blockInput.blockRootHex, signedBlockHeader);
|
|
216
|
+
// Only produce a slashing while importing the block. A block that is verified before it is published
|
|
217
|
+
// must not be treated as equivocation evidence since it may never be seen by the network
|
|
218
|
+
if (opts.verifyOnly !== true && this.seenBlockProposers.isEquivocating(slot, proposerIndex)) {
|
|
219
|
+
callInNextEventLoop(() => this.processProposerEquivocation(slot, proposerIndex));
|
|
224
220
|
}
|
|
225
221
|
}
|
|
226
222
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {IForkChoice} from "@lodestar/fork-choice";
|
|
1
|
+
import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
|
|
2
2
|
import {Slot} from "@lodestar/types";
|
|
3
3
|
import {Logger} from "@lodestar/utils";
|
|
4
4
|
import {getFaultInspectionParams} from "../execution/builder/http.js";
|
|
@@ -15,11 +15,15 @@ export type BuilderCircuitBreakerModules = {
|
|
|
15
15
|
metrics: Metrics | null;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
/** Four observations is the minimum useful recovery sample for the default ~25% fault budget */
|
|
19
|
+
const MIN_BLOCKS_TO_DEACTIVATE = 4;
|
|
20
|
+
|
|
18
21
|
/**
|
|
19
22
|
* Post-gloas circuit breaker for builder bids. The beacon block is produced by the proposer
|
|
20
23
|
* regardless of bid source, so missed blocks are not a useful builder health signal. Instead
|
|
21
|
-
* count blocks
|
|
22
|
-
*
|
|
24
|
+
* count canonical blocks selected as EMPTY. Activate when the proportion of EMPTY blocks exceeds
|
|
25
|
+
* the fault budget, and resume selecting builder bids only when the observed blocks are within
|
|
26
|
+
* budget and meet the minimum recovery sample size.
|
|
23
27
|
*/
|
|
24
28
|
export class BuilderCircuitBreaker {
|
|
25
29
|
readonly faultInspectionWindow: number;
|
|
@@ -35,39 +39,49 @@ export class BuilderCircuitBreaker {
|
|
|
35
39
|
const {faultInspectionWindow, allowedFaults} = getFaultInspectionParams(opts);
|
|
36
40
|
this.faultInspectionWindow = faultInspectionWindow;
|
|
37
41
|
this.allowedFaults = allowedFaults;
|
|
42
|
+
this.modules.logger.info("Builder circuit breaker initialized", {faultInspectionWindow, allowedFaults});
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
/** Whether builder bids must be ignored for a block produced at clockSlot */
|
|
41
|
-
isActive(clockSlot: Slot): boolean {
|
|
42
|
-
this.update(clockSlot);
|
|
46
|
+
isActive(clockSlot: Slot, head: ProtoBlock): boolean {
|
|
47
|
+
this.update(clockSlot, head);
|
|
43
48
|
return this.active;
|
|
44
49
|
}
|
|
45
50
|
|
|
46
|
-
update(clockSlot: Slot): void {
|
|
51
|
+
update(clockSlot: Slot, head: ProtoBlock): void {
|
|
47
52
|
if (clockSlot <= this.lastUpdatedSlot) {
|
|
48
53
|
return;
|
|
49
54
|
}
|
|
50
55
|
this.lastUpdatedSlot = clockSlot;
|
|
51
56
|
|
|
52
|
-
// Exclude clockSlot itself, its payload
|
|
53
|
-
const {
|
|
57
|
+
// Exclude clockSlot itself, its payload status may still be unresolved
|
|
58
|
+
const {full, empty} = this.modules.forkChoice.getCanonicalPayloadCounts(
|
|
54
59
|
Math.max(clockSlot - this.faultInspectionWindow, 0),
|
|
55
|
-
clockSlot - 1
|
|
60
|
+
clockSlot - 1,
|
|
61
|
+
head
|
|
56
62
|
);
|
|
57
|
-
const
|
|
63
|
+
const canonicalBlocks = full + empty;
|
|
58
64
|
|
|
59
65
|
const wasActive = this.active;
|
|
60
|
-
// Scale the fault budget by blocks
|
|
61
|
-
|
|
66
|
+
// Scale the fault budget by canonical blocks so sparse windows still trigger on high EMPTY rates
|
|
67
|
+
const exceedsFaultBudget = empty * this.faultInspectionWindow > this.allowedFaults * canonicalBlocks;
|
|
68
|
+
if (exceedsFaultBudget) {
|
|
69
|
+
this.active = true;
|
|
70
|
+
} else if (canonicalBlocks >= MIN_BLOCKS_TO_DEACTIVATE) {
|
|
71
|
+
// Require a minimum sample within the fault budget before accepting builder bids again
|
|
72
|
+
this.active = false;
|
|
73
|
+
}
|
|
62
74
|
|
|
63
75
|
this.modules.metrics?.builderCircuitBreaker.active.set(this.active ? 1 : 0);
|
|
64
|
-
this.modules.metrics?.builderCircuitBreaker.
|
|
65
|
-
this.modules.metrics?.builderCircuitBreaker.
|
|
66
|
-
this.modules.metrics?.builderCircuitBreaker.
|
|
76
|
+
this.modules.metrics?.builderCircuitBreaker.canonicalBlocks.set(canonicalBlocks);
|
|
77
|
+
this.modules.metrics?.builderCircuitBreaker.fullBlocks.set(full);
|
|
78
|
+
this.modules.metrics?.builderCircuitBreaker.emptyBlocks.set(empty);
|
|
67
79
|
|
|
68
80
|
const logCtx = {
|
|
69
|
-
|
|
70
|
-
|
|
81
|
+
clockSlot,
|
|
82
|
+
canonicalBlocks,
|
|
83
|
+
full,
|
|
84
|
+
empty,
|
|
71
85
|
faultInspectionWindow: this.faultInspectionWindow,
|
|
72
86
|
allowedFaults: this.allowedFaults,
|
|
73
87
|
};
|
package/src/chain/chain.ts
CHANGED
|
@@ -1072,6 +1072,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1072
1072
|
graffiti,
|
|
1073
1073
|
slot,
|
|
1074
1074
|
feeRecipient,
|
|
1075
|
+
strictFeeRecipientCheck,
|
|
1075
1076
|
commonBlockBodyPromise,
|
|
1076
1077
|
parentBlock,
|
|
1077
1078
|
builderBid,
|
|
@@ -1100,6 +1101,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1100
1101
|
graffiti,
|
|
1101
1102
|
slot,
|
|
1102
1103
|
feeRecipient,
|
|
1104
|
+
strictFeeRecipientCheck,
|
|
1103
1105
|
parentBlock,
|
|
1104
1106
|
proposerIndex,
|
|
1105
1107
|
proposerPubKey,
|
|
@@ -1217,7 +1219,12 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1217
1219
|
this.emitter.emit(routes.events.EventType.proposerSlashing, proposerSlashing);
|
|
1218
1220
|
this.emitter.emit(ChainEvent.publishProposerSlashing, proposerSlashing);
|
|
1219
1221
|
this.metrics?.opPool.proposerSlashingsProduced.inc();
|
|
1220
|
-
this.logger.info("Produced proposer slashing from observed equivocation", {
|
|
1222
|
+
this.logger.info("Produced proposer slashing from observed equivocation", {
|
|
1223
|
+
slot: blockSlot,
|
|
1224
|
+
proposerIndex,
|
|
1225
|
+
header1Root: toRootHex(ssz.phase0.BeaconBlockHeader.hashTreeRoot(header1.message)),
|
|
1226
|
+
header2Root: toRootHex(ssz.phase0.BeaconBlockHeader.hashTreeRoot(header2.message)),
|
|
1227
|
+
});
|
|
1221
1228
|
} finally {
|
|
1222
1229
|
this.producingProposerSlashing.delete(proposerIndex);
|
|
1223
1230
|
}
|
|
@@ -118,7 +118,7 @@ export type BlockErrorType =
|
|
|
118
118
|
| {code: BlockErrorCode.GENESIS_BLOCK}
|
|
119
119
|
| {code: BlockErrorCode.WOULD_REVERT_FINALIZED_SLOT; blockSlot: Slot; finalizedSlot: Slot}
|
|
120
120
|
| {code: BlockErrorCode.ALREADY_KNOWN; root: RootHex}
|
|
121
|
-
| {code: BlockErrorCode.REPEAT_PROPOSAL; proposerIndex: ValidatorIndex}
|
|
121
|
+
| {code: BlockErrorCode.REPEAT_PROPOSAL; proposerIndex: ValidatorIndex; root: RootHex}
|
|
122
122
|
| {code: BlockErrorCode.BLOCK_SLOT_LIMIT_REACHED}
|
|
123
123
|
| {code: BlockErrorCode.INCORRECT_PROPOSER; proposerIndex: ValidatorIndex}
|
|
124
124
|
| {code: BlockErrorCode.PROPOSAL_SIGNATURE_INVALID; blockSlot: Slot}
|
package/src/chain/options.ts
CHANGED
|
@@ -53,7 +53,7 @@ export type IChainOptions = BlockProcessOpts &
|
|
|
53
53
|
nativeStateView?: boolean;
|
|
54
54
|
/** Builder circuit breaker fault inspection window in slots */
|
|
55
55
|
faultInspectionWindow?: number;
|
|
56
|
-
/**
|
|
56
|
+
/** Canonical EMPTY blocks allowed per `faultInspectionWindow` observed blocks */
|
|
57
57
|
allowedFaults?: number;
|
|
58
58
|
};
|
|
59
59
|
|
|
@@ -86,8 +86,6 @@ export type BlockProcessOpts = {
|
|
|
86
86
|
verifyOnly?: boolean;
|
|
87
87
|
/** Used to specify to skip execution payload validation */
|
|
88
88
|
skipVerifyExecutionPayload?: boolean;
|
|
89
|
-
/** Used to specify to skip block signatures validation */
|
|
90
|
-
skipVerifyBlockSignatures?: boolean;
|
|
91
89
|
};
|
|
92
90
|
|
|
93
91
|
export type PoolOpts = {
|
|
@@ -169,7 +169,7 @@ export class PrepareNextSlotScheduler {
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
if (isForkPostGloas(fork)) {
|
|
172
|
-
this.chain.builderCircuitBreaker.update(clockSlot);
|
|
172
|
+
this.chain.builderCircuitBreaker.update(clockSlot, updatedHead);
|
|
173
173
|
} else {
|
|
174
174
|
// Update the builder status, if enabled shoot an api call to check status
|
|
175
175
|
this.chain.updateBuilderStatus(clockSlot);
|
|
@@ -224,7 +224,7 @@ export class PrepareNextSlotScheduler {
|
|
|
224
224
|
this.metrics?.blockPayload.payloadAdvancePrepTime.observe(preparationTime);
|
|
225
225
|
|
|
226
226
|
const safeBlockHash = getSafeExecutionBlockHash(this.chain.forkChoice, this.logger);
|
|
227
|
-
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.chain.forkChoice
|
|
227
|
+
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.chain.forkChoice);
|
|
228
228
|
|
|
229
229
|
// awaiting here instead of throwing an async call because there is no other task
|
|
230
230
|
// left for scheduler and this gives nice semantics to catch and log errors in the
|
|
@@ -100,6 +100,8 @@ export type BlockAttributes = {
|
|
|
100
100
|
slot: Slot;
|
|
101
101
|
parentBlock: ProtoBlock;
|
|
102
102
|
feeRecipient?: string;
|
|
103
|
+
/** Verify that a locally produced execution payload uses `feeRecipient`. */
|
|
104
|
+
strictFeeRecipientCheck?: boolean;
|
|
103
105
|
/** When provided, build block with this builder bid instead of a self-build bid */
|
|
104
106
|
builderBid?: gloas.SignedExecutionPayloadBid;
|
|
105
107
|
};
|
|
@@ -201,6 +203,7 @@ export async function produceBlockBody<T extends BlockType>(
|
|
|
201
203
|
const {
|
|
202
204
|
slot: blockSlot,
|
|
203
205
|
feeRecipient: requestedFeeRecipient,
|
|
206
|
+
strictFeeRecipientCheck,
|
|
204
207
|
parentBlock,
|
|
205
208
|
proposerIndex,
|
|
206
209
|
proposerPubKey,
|
|
@@ -271,7 +274,7 @@ export async function produceBlockBody<T extends BlockType>(
|
|
|
271
274
|
// full and blinded no longer makes sense in gloas, it might be a good idea to move
|
|
272
275
|
// this into a completely separate function and have pre/post gloas more separated
|
|
273
276
|
const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice, this.logger);
|
|
274
|
-
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.forkChoice
|
|
277
|
+
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.forkChoice);
|
|
275
278
|
// TODO GLOAS: post-Gloas, proposer feeRecipient is also carried (signed) in
|
|
276
279
|
// ProposerPreferencesPool. Consider using this unified cache instead
|
|
277
280
|
// see https://github.com/ChainSafe/lodestar/issues/9379
|
|
@@ -334,6 +337,16 @@ export async function produceBlockBody<T extends BlockType>(
|
|
|
334
337
|
executionPayloadValue = payloadRes.executionPayloadValue;
|
|
335
338
|
shouldOverrideBuilder = payloadRes.shouldOverrideBuilder;
|
|
336
339
|
|
|
340
|
+
if (
|
|
341
|
+
strictFeeRecipientCheck &&
|
|
342
|
+
requestedFeeRecipient &&
|
|
343
|
+
!byteArrayEquals(executionPayload.feeRecipient, fromHex(requestedFeeRecipient))
|
|
344
|
+
) {
|
|
345
|
+
throw Error(
|
|
346
|
+
`Invalid feeRecipient set in engine payload expected=${requestedFeeRecipient} actual=${toHex(executionPayload.feeRecipient)}`
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
337
350
|
if (blobsBundle === undefined) {
|
|
338
351
|
throw Error(`Missing blobsBundle response from getPayload at fork=${fork}`);
|
|
339
352
|
}
|
|
@@ -417,7 +430,7 @@ export async function produceBlockBody<T extends BlockType>(
|
|
|
417
430
|
}
|
|
418
431
|
|
|
419
432
|
const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice, this.logger);
|
|
420
|
-
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.forkChoice
|
|
433
|
+
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.forkChoice);
|
|
421
434
|
const feeRecipient = requestedFeeRecipient ?? this.beaconProposerCache.getOrDefault(proposerIndex);
|
|
422
435
|
const feeRecipientType = requestedFeeRecipient
|
|
423
436
|
? "requested"
|