Skip to content

Commit d564a97

Browse files
g11technflaigensi321
committed
feat: add and support builder_boost_factor query param to produceBlockV3 api (ChainSafe#6236)
* feat: add and support builder_boost_factor query param to produceBlockV3 api lint * add keymanager endpoint and update the test * update builder boost factor to bigint * update the help Co-authored-by: Nico Flaig <nflaig@protonmail.com> * remove comment * validate and use boostfactor ranges as per spec * fix test * correct typo Co-authored-by: NC <adrninistrator1@protonmail.com> * fix the block selection condition * fixes for spec complaince * fix the keymanager routes * comment typo fix Co-authored-by: Nico Flaig <nflaig@protonmail.com> --------- Co-authored-by: Nico Flaig <nflaig@protonmail.com> Co-authored-by: NC <adrninistrator1@protonmail.com>
1 parent bfda794 commit d564a97

File tree

13 files changed

+312
-27
lines changed

13 files changed

+312
-27
lines changed

packages/api/src/beacon/routes/validator.ts

+10
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
Slot,
1414
ssz,
1515
UintNum64,
16+
UintBn64,
1617
ValidatorIndex,
1718
RootHex,
1819
StringType,
@@ -53,6 +54,7 @@ export enum BuilderSelection {
5354
export type ExtraProduceBlockOps = {
5455
feeRecipient?: string;
5556
builderSelection?: BuilderSelection;
57+
builderBoostFactor?: UintBn64;
5658
strictFeeRecipientCheck?: boolean;
5759
blindedLocal?: boolean;
5860
};
@@ -487,6 +489,7 @@ export type ReqTypes = {
487489
skip_randao_verification?: boolean;
488490
fee_recipient?: string;
489491
builder_selection?: string;
492+
builder_boost_factor?: string;
490493
strict_fee_recipient_check?: boolean;
491494
blinded_local?: boolean;
492495
};
@@ -555,6 +558,7 @@ export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
555558
fee_recipient: opts?.feeRecipient,
556559
skip_randao_verification: skipRandaoVerification,
557560
builder_selection: opts?.builderSelection,
561+
builder_boost_factor: opts?.builderBoostFactor?.toString(),
558562
strict_fee_recipient_check: opts?.strictFeeRecipientCheck,
559563
blinded_local: opts?.blindedLocal,
560564
},
@@ -567,6 +571,7 @@ export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
567571
{
568572
feeRecipient: query.fee_recipient,
569573
builderSelection: query.builder_selection as BuilderSelection,
574+
builderBoostFactor: parseBuilderBoostFactor(query.builder_boost_factor),
570575
strictFeeRecipientCheck: query.strict_fee_recipient_check,
571576
blindedLocal: query.blinded_local,
572577
},
@@ -579,6 +584,7 @@ export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
579584
fee_recipient: Schema.String,
580585
skip_randao_verification: Schema.Boolean,
581586
builder_selection: Schema.String,
587+
builder_boost_factor: Schema.String,
582588
strict_fee_recipient_check: Schema.Boolean,
583589
blinded_local: Schema.Boolean,
584590
},
@@ -785,3 +791,7 @@ export function getReturnTypes(): ReturnTypes<Api> {
785791
getLiveness: jsonType("snake"),
786792
};
787793
}
794+
795+
function parseBuilderBoostFactor(builderBoostFactorInput?: string | number | bigint): bigint | undefined {
796+
return builderBoostFactorInput !== undefined ? BigInt(builderBoostFactorInput) : undefined;
797+
}

packages/api/src/keymanager/routes.ts

+69
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ export type GasLimitData = {
7272
pubkey: string;
7373
gasLimit: number;
7474
};
75+
export type BuilderBoostFactorData = {
76+
pubkey: string;
77+
builderBoostFactor: bigint;
78+
};
7579

7680
export type SignerDefinition = {
7781
pubkey: PubkeyHex;
@@ -247,6 +251,27 @@ export type Api = {
247251
>
248252
>;
249253

254+
getBuilderBoostFactor(
255+
pubkey: string
256+
): Promise<ApiClientResponse<{[HttpStatusCode.OK]: {data: BuilderBoostFactorData}}>>;
257+
setBuilderBoostFactor(
258+
pubkey: string,
259+
builderBoostFactor: bigint
260+
): Promise<
261+
ApiClientResponse<
262+
{[HttpStatusCode.OK]: void; [HttpStatusCode.NO_CONTENT]: void},
263+
HttpStatusCode.UNAUTHORIZED | HttpStatusCode.FORBIDDEN | HttpStatusCode.NOT_FOUND
264+
>
265+
>;
266+
deleteBuilderBoostFactor(
267+
pubkey: string
268+
): Promise<
269+
ApiClientResponse<
270+
{[HttpStatusCode.OK]: void; [HttpStatusCode.NO_CONTENT]: void},
271+
HttpStatusCode.UNAUTHORIZED | HttpStatusCode.FORBIDDEN | HttpStatusCode.NOT_FOUND
272+
>
273+
>;
274+
250275
/**
251276
* Create a signed voluntary exit message for an active validator, identified by a public key known to the validator
252277
* client. This endpoint returns a `SignedVoluntaryExit` object, which can be used to initiate voluntary exit via the
@@ -290,6 +315,10 @@ export const routesData: RoutesData<Api> = {
290315
setGasLimit: {url: "/eth/v1/validator/{pubkey}/gas_limit", method: "POST", statusOk: 202},
291316
deleteGasLimit: {url: "/eth/v1/validator/{pubkey}/gas_limit", method: "DELETE", statusOk: 204},
292317

318+
getBuilderBoostFactor: {url: "/eth/v1/validator/{pubkey}/builder_boost_factor", method: "GET"},
319+
setBuilderBoostFactor: {url: "/eth/v1/validator/{pubkey}/builder_boost_factor", method: "POST", statusOk: 202},
320+
deleteBuilderBoostFactor: {url: "/eth/v1/validator/{pubkey}/builder_boost_factor", method: "DELETE", statusOk: 204},
321+
293322
signVoluntaryExit: {url: "/eth/v1/validator/{pubkey}/voluntary_exit", method: "POST"},
294323
};
295324

@@ -326,6 +355,10 @@ export type ReqTypes = {
326355
setGasLimit: {params: {pubkey: string}; body: {gas_limit: string}};
327356
deleteGasLimit: {params: {pubkey: string}};
328357

358+
getBuilderBoostFactor: {params: {pubkey: string}};
359+
setBuilderBoostFactor: {params: {pubkey: string}; body: {builder_boost_factor: string}};
360+
deleteBuilderBoostFactor: {params: {pubkey: string}};
361+
329362
signVoluntaryExit: {params: {pubkey: string}; query: {epoch?: number}};
330363
};
331364

@@ -423,6 +456,33 @@ export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
423456
params: {pubkey: Schema.StringRequired},
424457
},
425458
},
459+
460+
getBuilderBoostFactor: {
461+
writeReq: (pubkey) => ({params: {pubkey}}),
462+
parseReq: ({params: {pubkey}}) => [pubkey],
463+
schema: {
464+
params: {pubkey: Schema.StringRequired},
465+
},
466+
},
467+
setBuilderBoostFactor: {
468+
writeReq: (pubkey, builderBoostFactor) => ({
469+
params: {pubkey},
470+
body: {builder_boost_factor: builderBoostFactor.toString(10)},
471+
}),
472+
parseReq: ({params: {pubkey}, body: {builder_boost_factor}}) => [pubkey, BigInt(builder_boost_factor)],
473+
schema: {
474+
params: {pubkey: Schema.StringRequired},
475+
body: Schema.Object,
476+
},
477+
},
478+
deleteBuilderBoostFactor: {
479+
writeReq: (pubkey) => ({params: {pubkey}}),
480+
parseReq: ({params: {pubkey}}) => [pubkey],
481+
schema: {
482+
params: {pubkey: Schema.StringRequired},
483+
},
484+
},
485+
426486
signVoluntaryExit: {
427487
writeReq: (pubkey, epoch) => ({params: {pubkey}, query: epoch !== undefined ? {epoch} : {}}),
428488
parseReq: ({params: {pubkey}, query: {epoch}}) => [pubkey, epoch],
@@ -455,6 +515,15 @@ export function getReturnTypes(): ReturnTypes<Api> {
455515
{jsonCase: "eth2"}
456516
)
457517
),
518+
getBuilderBoostFactor: ContainerData(
519+
new ContainerType(
520+
{
521+
pubkey: stringType,
522+
builderBoostFactor: ssz.UintBn64,
523+
},
524+
{jsonCase: "eth2"}
525+
)
526+
),
458527
signVoluntaryExit: ContainerData(ssz.phase0.SignedVoluntaryExit),
459528
};
460529
}

packages/api/test/unit/beacon/testData/validator.ts

+28-4
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,13 @@ export const testData: GenericServerTestCases<Api> = {
5050
randaoReveal,
5151
graffiti,
5252
undefined,
53-
{feeRecipient: undefined, builderSelection: undefined, strictFeeRecipientCheck: undefined},
53+
{
54+
feeRecipient,
55+
builderSelection: undefined,
56+
strictFeeRecipientCheck: undefined,
57+
blindedLocal: undefined,
58+
builderBoostFactor: 100n,
59+
},
5460
] as unknown as GenericServerTestCases<Api>["produceBlock"]["args"],
5561
res: {data: ssz.phase0.BeaconBlock.defaultValue()},
5662
},
@@ -60,7 +66,13 @@ export const testData: GenericServerTestCases<Api> = {
6066
randaoReveal,
6167
graffiti,
6268
undefined,
63-
{feeRecipient: undefined, builderSelection: undefined, strictFeeRecipientCheck: undefined},
69+
{
70+
feeRecipient,
71+
builderSelection: undefined,
72+
strictFeeRecipientCheck: undefined,
73+
blindedLocal: undefined,
74+
builderBoostFactor: 100n,
75+
},
6476
] as unknown as GenericServerTestCases<Api>["produceBlockV2"]["args"],
6577
res: {
6678
data: ssz.altair.BeaconBlock.defaultValue(),
@@ -75,7 +87,13 @@ export const testData: GenericServerTestCases<Api> = {
7587
randaoReveal,
7688
graffiti,
7789
true,
78-
{feeRecipient, builderSelection: undefined, strictFeeRecipientCheck: undefined},
90+
{
91+
feeRecipient,
92+
builderSelection: undefined,
93+
strictFeeRecipientCheck: undefined,
94+
blindedLocal: undefined,
95+
builderBoostFactor: 100n,
96+
},
7997
],
8098
res: {
8199
data: ssz.altair.BeaconBlock.defaultValue(),
@@ -92,7 +110,13 @@ export const testData: GenericServerTestCases<Api> = {
92110
randaoReveal,
93111
graffiti,
94112
undefined,
95-
{feeRecipient: undefined, builderSelection: undefined, strictFeeRecipientCheck: undefined},
113+
{
114+
feeRecipient,
115+
builderSelection: undefined,
116+
strictFeeRecipientCheck: undefined,
117+
blindedLocal: undefined,
118+
builderBoostFactor: 100n,
119+
},
96120
] as unknown as GenericServerTestCases<Api>["produceBlindedBlock"]["args"],
97121
res: {
98122
data: ssz.bellatrix.BlindedBeaconBlock.defaultValue(),

packages/api/test/unit/keymanager/testData.ts

+13
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const pubkeyRand = "0x84105a985058fc8740a48bf1ede9d223ef09e8c6b1735ba0a55cf4a9ff
1313
const ethaddressRand = "0xabcf8e0d4e9587369b2301d0790347320302cc09";
1414
const graffitiRandUtf8 = "636861696e736166652f6c6f64657374";
1515
const gasLimitRand = 30_000_000;
16+
const builderBoostFactorRand = BigInt(100);
1617

1718
export const testData: GenericServerTestCases<Api> = {
1819
listKeys: {
@@ -99,4 +100,16 @@ export const testData: GenericServerTestCases<Api> = {
99100
args: [pubkeyRand, 1],
100101
res: {data: ssz.phase0.SignedVoluntaryExit.defaultValue()},
101102
},
103+
getBuilderBoostFactor: {
104+
args: [pubkeyRand],
105+
res: {data: {pubkey: pubkeyRand, builderBoostFactor: builderBoostFactorRand}},
106+
},
107+
setBuilderBoostFactor: {
108+
args: [pubkeyRand, builderBoostFactorRand],
109+
res: undefined,
110+
},
111+
deleteBuilderBoostFactor: {
112+
args: [pubkeyRand],
113+
res: undefined,
114+
},
102115
};

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

+23-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
isForkExecution,
2020
ForkSeq,
2121
} from "@lodestar/params";
22+
import {MAX_BUILDER_BOOST_FACTOR} from "@lodestar/validator";
2223
import {
2324
Root,
2425
Slot,
@@ -423,7 +424,12 @@ export function getValidatorApi({
423424
graffiti,
424425
// TODO deneb: skip randao verification
425426
_skipRandaoVerification?: boolean,
426-
{feeRecipient, builderSelection, strictFeeRecipientCheck}: routes.validator.ExtraProduceBlockOps = {}
427+
{
428+
feeRecipient,
429+
builderSelection,
430+
builderBoostFactor,
431+
strictFeeRecipientCheck,
432+
}: routes.validator.ExtraProduceBlockOps = {}
427433
) {
428434
notWhileSyncing();
429435
await waitForSlot(slot); // Must never request for a future slot > currentSlot
@@ -436,7 +442,14 @@ export function getValidatorApi({
436442

437443
const fork = config.getForkName(slot);
438444
// set some sensible opts
445+
// builderSelection will be deprecated and will run in mode MaxProfit if builder is enabled
446+
// and the actual selection will be determined using builderBoostFactor passed by the validator
439447
builderSelection = builderSelection ?? routes.validator.BuilderSelection.MaxProfit;
448+
builderBoostFactor = builderBoostFactor ?? BigInt(100);
449+
if (builderBoostFactor > MAX_BUILDER_BOOST_FACTOR) {
450+
throw new ApiError(400, `Invalid builderBoostFactor=${builderBoostFactor} > MAX_BUILDER_BOOST_FACTOR`);
451+
}
452+
440453
const isBuilderEnabled =
441454
ForkSeq[fork] >= ForkSeq.bellatrix &&
442455
chain.executionBuilder !== undefined &&
@@ -448,6 +461,8 @@ export function getValidatorApi({
448461
slot,
449462
isBuilderEnabled,
450463
strictFeeRecipientCheck,
464+
// winston logger doesn't like bigint
465+
builderBoostFactor: `${builderBoostFactor}`,
451466
});
452467
// Start calls for building execution and builder blocks
453468
const blindedBlockPromise = isBuilderEnabled
@@ -541,7 +556,12 @@ export function getValidatorApi({
541556
if (fullBlock && blindedBlock) {
542557
switch (builderSelection) {
543558
case routes.validator.BuilderSelection.MaxProfit: {
544-
if (blockValueEngine >= blockValueBuilder) {
559+
if (
560+
// explicitly handle the two special values mentioned in spec for builder preferred / engine preferred
561+
builderBoostFactor !== MAX_BUILDER_BOOST_FACTOR &&
562+
(builderBoostFactor === BigInt(0) ||
563+
blockValueEngine >= (blockValueBuilder * builderBoostFactor) / BigInt(100))
564+
) {
545565
executionPayloadSource = ProducedBlockSource.engine;
546566
} else {
547567
executionPayloadSource = ProducedBlockSource.builder;
@@ -562,6 +582,7 @@ export function getValidatorApi({
562582
logger.verbose(`Selected executionPayloadSource=${executionPayloadSource} block`, {
563583
builderSelection,
564584
// winston logger doesn't like bigint
585+
builderBoostFactor: `${builderBoostFactor}`,
565586
enginePayloadValue: `${enginePayloadValue}`,
566587
builderPayloadValue: `${builderPayloadValue}`,
567588
consensusBlockValueEngine: `${consensusBlockValueEngine}`,

packages/cli/src/cmds/validator/handler.ts

+1
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ function getProposerConfigFromArgs(
227227
selection: parseBuilderSelection(
228228
args["builder.selection"] ?? (args["builder"] ? defaultOptions.builderAliasSelection : undefined)
229229
),
230+
boostFactor: args["builder.boostFactor"],
230231
},
231232
};
232233

packages/cli/src/cmds/validator/keymanager/impl.ts

+23
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,29 @@ export class KeymanagerApi implements Api {
390390
};
391391
}
392392

393+
async getBuilderBoostFactor(pubkeyHex: string): ReturnType<Api["getBuilderBoostFactor"]> {
394+
const builderBoostFactor = this.validator.validatorStore.getBuilderBoostFactor(pubkeyHex);
395+
return {data: {pubkey: pubkeyHex, builderBoostFactor}};
396+
}
397+
398+
async setBuilderBoostFactor(pubkeyHex: string, builderBoostFactor: bigint): Promise<void> {
399+
this.checkIfProposerWriteEnabled();
400+
this.validator.validatorStore.setBuilderBoostFactor(pubkeyHex, builderBoostFactor);
401+
this.persistedKeysBackend.writeProposerConfig(
402+
pubkeyHex,
403+
this.validator.validatorStore.getProposerConfig(pubkeyHex)
404+
);
405+
}
406+
407+
async deleteBuilderBoostFactor(pubkeyHex: string): Promise<void> {
408+
this.checkIfProposerWriteEnabled();
409+
this.validator.validatorStore.deleteBuilderBoostFactor(pubkeyHex);
410+
this.persistedKeysBackend.writeProposerConfig(
411+
pubkeyHex,
412+
this.validator.validatorStore.getProposerConfig(pubkeyHex)
413+
);
414+
}
415+
393416
/**
394417
* Create and sign a voluntary exit message for an active validator
395418
*/

packages/cli/src/cmds/validator/options.ts

+9
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export type IValidatorCliArgs = AccountValidatorArgs &
4545

4646
builder?: boolean;
4747
"builder.selection"?: string;
48+
"builder.boostFactor"?: bigint;
4849

4950
useProduceBlockV3?: boolean;
5051
broadcastValidation?: string;
@@ -246,6 +247,14 @@ export const validatorOptions: CliCommandOptions<IValidatorCliArgs> = {
246247
group: "builder",
247248
},
248249

250+
"builder.boostFactor": {
251+
type: "number",
252+
description:
253+
"Percentage multiplier the block producing beacon node must apply to boost (>100) or dampen (<100) builder block value for selection against execution block. The multiplier is ignored if `--builder.selection` is set to anything other than `maxprofit`",
254+
defaultDescription: `${defaultOptions.builderBoostFactor}`,
255+
group: "builder",
256+
},
257+
249258
useProduceBlockV3: {
250259
type: "boolean",
251260
description: "Enable/disable usage of produceBlockV3 that might not be supported by all beacon clients yet",

packages/validator/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export {Validator, type ValidatorOptions} from "./validator.js";
2-
export {ValidatorStore, SignerType, defaultOptions} from "./services/validatorStore.js";
2+
export {ValidatorStore, SignerType, defaultOptions, MAX_BUILDER_BOOST_FACTOR} from "./services/validatorStore.js";
33
export type {
44
Signer,
55
SignerLocal,

0 commit comments

Comments
 (0)