Skip to content

Commit 558ec2f

Browse files
authored
feat: track block production selection results and payload values (#7203)
* feat: track block production selection source and reason * Rename reason to builder_censorship * Fix metric name * Add metric to track execution payload value * Update metric descriptions * Remove ETH suffix from payload values * Rename metric * Update import path * Return block selection result * Reorder if statements
1 parent e31d535 commit 558ec2f

File tree

4 files changed

+122
-11
lines changed

4 files changed

+122
-11
lines changed

packages/beacon-node/src/api/impl/validator/index.ts

+79-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,15 @@ import {
4747
getValidatorStatus,
4848
} from "@lodestar/types";
4949
import {ExecutionStatus, DataAvailabilityStatus} from "@lodestar/fork-choice";
50-
import {fromHex, toHex, resolveOrRacePromises, prettyWeiToEth, toRootHex} from "@lodestar/utils";
50+
import {
51+
fromHex,
52+
toHex,
53+
resolveOrRacePromises,
54+
prettyWeiToEth,
55+
toRootHex,
56+
TimeoutError,
57+
formatWeiToEth,
58+
} from "@lodestar/utils";
5159
import {
5260
AttestationError,
5361
AttestationErrorCode,
@@ -115,6 +123,41 @@ type ProduceFullOrBlindedBlockOrContentsRes = {executionPayloadSource: ProducedB
115123
| (ProduceBlindedBlockRes & {executionPayloadBlinded: true})
116124
);
117125

126+
/**
127+
* Engine block selection reasons tracked in metrics
128+
*/
129+
export enum EngineBlockSelectionReason {
130+
BuilderDisabled = "builder_disabled",
131+
BuilderError = "builder_error",
132+
BuilderTimeout = "builder_timeout",
133+
BuilderPending = "builder_pending",
134+
BuilderNoBid = "builder_no_bid",
135+
BuilderCensorship = "builder_censorship",
136+
BlockValue = "block_value",
137+
EnginePreferred = "engine_preferred",
138+
}
139+
140+
/**
141+
* Builder block selection reasons tracked in metrics
142+
*/
143+
export enum BuilderBlockSelectionReason {
144+
EngineDisabled = "engine_disabled",
145+
EngineError = "engine_error",
146+
EnginePending = "engine_pending",
147+
BlockValue = "block_value",
148+
BuilderPreferred = "builder_preferred",
149+
}
150+
151+
export type BlockSelectionResult =
152+
| {
153+
source: ProducedBlockSource.engine;
154+
reason: EngineBlockSelectionReason;
155+
}
156+
| {
157+
source: ProducedBlockSource.builder;
158+
reason: BuilderBlockSelectionReason;
159+
};
160+
118161
/**
119162
* Server implementation for handling validator duties.
120163
* See `@lodestar/validator/src/api` for the client implementation).
@@ -417,6 +460,7 @@ export function getValidatorApi(
417460

418461
metrics?.blockProductionSuccess.inc({source});
419462
metrics?.blockProductionNumAggregated.observe({source}, block.body.attestations.length);
463+
metrics?.blockProductionExecutionPayloadValue.observe({source}, Number(formatWeiToEth(executionPayloadValue)));
420464
logger.verbose("Produced blinded block", {
421465
slot,
422466
executionPayloadValue,
@@ -491,6 +535,7 @@ export function getValidatorApi(
491535

492536
metrics?.blockProductionSuccess.inc({source});
493537
metrics?.blockProductionNumAggregated.observe({source}, block.body.attestations.length);
538+
metrics?.blockProductionExecutionPayloadValue.observe({source}, Number(formatWeiToEth(executionPayloadValue)));
494539
logger.verbose("Produced execution block", {
495540
slot,
496541
executionPayloadValue,
@@ -694,6 +739,11 @@ export function getValidatorApi(
694739
...getBlockValueLogInfo(engine.value),
695740
});
696741

742+
metrics?.blockProductionSelectionResults.inc({
743+
source: ProducedBlockSource.engine,
744+
reason: EngineBlockSelectionReason.BuilderCensorship,
745+
});
746+
697747
return {...engine.value, executionPayloadBlinded: false, executionPayloadSource: ProducedBlockSource.engine};
698748
}
699749

@@ -704,6 +754,16 @@ export function getValidatorApi(
704754
...getBlockValueLogInfo(builder.value),
705755
});
706756

757+
metrics?.blockProductionSelectionResults.inc({
758+
source: ProducedBlockSource.builder,
759+
reason:
760+
isEngineEnabled === false
761+
? BuilderBlockSelectionReason.EngineDisabled
762+
: engine.status === "pending"
763+
? BuilderBlockSelectionReason.EnginePending
764+
: BuilderBlockSelectionReason.EngineError,
765+
});
766+
707767
return {...builder.value, executionPayloadBlinded: true, executionPayloadSource: ProducedBlockSource.builder};
708768
}
709769

@@ -714,16 +774,33 @@ export function getValidatorApi(
714774
...getBlockValueLogInfo(engine.value),
715775
});
716776

777+
metrics?.blockProductionSelectionResults.inc({
778+
source: ProducedBlockSource.engine,
779+
reason:
780+
isBuilderEnabled === false
781+
? EngineBlockSelectionReason.BuilderDisabled
782+
: builder.status === "pending"
783+
? EngineBlockSelectionReason.BuilderPending
784+
: builder.reason instanceof NoBidReceived
785+
? EngineBlockSelectionReason.BuilderNoBid
786+
: builder.reason instanceof TimeoutError
787+
? EngineBlockSelectionReason.BuilderTimeout
788+
: EngineBlockSelectionReason.BuilderError,
789+
});
790+
717791
return {...engine.value, executionPayloadBlinded: false, executionPayloadSource: ProducedBlockSource.engine};
718792
}
719793

720794
if (engine.status === "fulfilled" && builder.status === "fulfilled") {
721-
const executionPayloadSource = selectBlockProductionSource({
795+
const result = selectBlockProductionSource({
722796
builderBlockValue: builder.value.executionPayloadValue + builder.value.consensusBlockValue,
723797
engineBlockValue: engine.value.executionPayloadValue + engine.value.consensusBlockValue,
724798
builderBoostFactor,
725799
builderSelection,
726800
});
801+
const executionPayloadSource = result.source;
802+
803+
metrics?.blockProductionSelectionResults.inc(result);
727804

728805
logger.info(`Selected ${executionPayloadSource} block`, {
729806
...loggerContext,

packages/beacon-node/src/api/impl/validator/utils.ts

+19-8
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {ATTESTATION_SUBNET_COUNT} from "@lodestar/params";
33
import {routes} from "@lodestar/api";
44
import {BLSPubkey, CommitteeIndex, ProducedBlockSource, Slot, ValidatorIndex} from "@lodestar/types";
55
import {MAX_BUILDER_BOOST_FACTOR} from "@lodestar/validator";
6+
import {BlockSelectionResult, BuilderBlockSelectionReason, EngineBlockSelectionReason} from "./index.js";
67

78
export function computeSubnetForCommitteesAtSlot(
89
slot: Slot,
@@ -54,21 +55,31 @@ export function selectBlockProductionSource({
5455
engineBlockValue: bigint;
5556
builderBlockValue: bigint;
5657
builderBoostFactor: bigint;
57-
}): ProducedBlockSource {
58+
}): BlockSelectionResult {
5859
switch (builderSelection) {
5960
case routes.validator.BuilderSelection.ExecutionAlways:
6061
case routes.validator.BuilderSelection.ExecutionOnly:
61-
return ProducedBlockSource.engine;
62+
return {source: ProducedBlockSource.engine, reason: EngineBlockSelectionReason.EnginePreferred};
6263

6364
case routes.validator.BuilderSelection.Default:
64-
case routes.validator.BuilderSelection.MaxProfit:
65-
return builderBoostFactor !== MAX_BUILDER_BOOST_FACTOR &&
66-
(builderBoostFactor === BigInt(0) || engineBlockValue >= (builderBlockValue * builderBoostFactor) / BigInt(100))
67-
? ProducedBlockSource.engine
68-
: ProducedBlockSource.builder;
65+
case routes.validator.BuilderSelection.MaxProfit: {
66+
if (builderBoostFactor === BigInt(0)) {
67+
return {source: ProducedBlockSource.engine, reason: EngineBlockSelectionReason.EnginePreferred};
68+
}
69+
70+
if (builderBoostFactor === MAX_BUILDER_BOOST_FACTOR) {
71+
return {source: ProducedBlockSource.builder, reason: BuilderBlockSelectionReason.BuilderPreferred};
72+
}
73+
74+
if (engineBlockValue >= (builderBlockValue * builderBoostFactor) / BigInt(100)) {
75+
return {source: ProducedBlockSource.engine, reason: EngineBlockSelectionReason.BlockValue};
76+
}
77+
78+
return {source: ProducedBlockSource.builder, reason: BuilderBlockSelectionReason.BlockValue};
79+
}
6980

7081
case routes.validator.BuilderSelection.BuilderAlways:
7182
case routes.validator.BuilderSelection.BuilderOnly:
72-
return ProducedBlockSource.builder;
83+
return {source: ProducedBlockSource.builder, reason: BuilderBlockSelectionReason.BuilderPreferred};
7384
}
7485
}

packages/beacon-node/src/metrics/metrics/beacon.ts

+16
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import {NotReorgedReason} from "@lodestar/fork-choice/lib/forkChoice/interface.j
33
import {UpdateHeadOpt} from "@lodestar/fork-choice";
44
import {RegistryMetricCreator} from "../utils/registryMetricCreator.js";
55
import {BlockProductionStep, PayloadPreparationType} from "../../chain/produceBlock/index.js";
6+
import {
7+
BlockSelectionResult,
8+
BuilderBlockSelectionReason,
9+
EngineBlockSelectionReason,
10+
} from "../../api/impl/validator/index.js";
611

712
export type BeaconMetrics = ReturnType<typeof createBeaconMetrics>;
813

@@ -160,12 +165,23 @@ export function createBeaconMetrics(register: RegistryMetricCreator) {
160165
help: "Count of blocks successfully produced",
161166
labelNames: ["source"],
162167
}),
168+
blockProductionSelectionResults: register.gauge<BlockSelectionResult>({
169+
name: "beacon_block_production_selection_results_total",
170+
help: "Count of all block production selection results",
171+
labelNames: ["source", "reason"],
172+
}),
163173
blockProductionNumAggregated: register.histogram<{source: ProducedBlockSource}>({
164174
name: "beacon_block_production_num_aggregated_total",
165175
help: "Count of all aggregated attestations in our produced block",
166176
buckets: [32, 64, 96, 128],
167177
labelNames: ["source"],
168178
}),
179+
blockProductionExecutionPayloadValue: register.histogram<{source: ProducedBlockSource}>({
180+
name: "beacon_block_production_execution_payload_value",
181+
help: "Execution payload value denominated in ETH of produced blocks",
182+
buckets: [0.001, 0.005, 0.01, 0.03, 0.05, 0.07, 0.1, 0.3, 0.5, 1],
183+
labelNames: ["source"],
184+
}),
169185

170186
blockProductionCaches: {
171187
producedBlockRoot: register.gauge({

packages/utils/src/format.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,18 @@ export function formatBigDecimal(numerator: bigint, denominator: bigint, maxDeci
4444
// display upto 5 decimal places
4545
const MAX_DECIMAL_FACTOR = BigInt("100000");
4646

47+
/**
48+
* Format wei as ETH, with up to 5 decimals
49+
*/
50+
export function formatWeiToEth(wei: bigint): string {
51+
return formatBigDecimal(wei, ETH_TO_WEI, MAX_DECIMAL_FACTOR);
52+
}
53+
4754
/**
4855
* Format wei as ETH, with up to 5 decimals and append ' ETH'
4956
*/
5057
export function prettyWeiToEth(wei: bigint): string {
51-
return `${formatBigDecimal(wei, ETH_TO_WEI, MAX_DECIMAL_FACTOR)} ETH`;
58+
return `${formatWeiToEth(wei)} ETH`;
5259
}
5360

5461
/**

0 commit comments

Comments
 (0)