Skip to content

Commit

Permalink
feat: add and support builder_boost_factor query param to produceBloc…
Browse files Browse the repository at this point in the history
…kV3 api
  • Loading branch information
g11tech committed Dec 25, 2023
1 parent d8b2318 commit 383859c
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 22 deletions.
8 changes: 8 additions & 0 deletions packages/api/src/beacon/routes/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ export enum BuilderSelection {
export type ExtraProduceBlockOps = {
feeRecipient?: string;
builderSelection?: BuilderSelection;
// precise value isn't required because super high values will be treated as always builder prefered
// and hence UintNum64 is sufficient. If this param is present, builderSelection will be infered to
// be of maxprofit (unless explicity provided) with this %age boost factor applied to the builder values
builderBoostFactor?: UintNum64;
strictFeeRecipientCheck?: boolean;
blindedLocal?: boolean;
};
Expand Down Expand Up @@ -487,6 +491,7 @@ export type ReqTypes = {
skip_randao_verification?: boolean;
fee_recipient?: string;
builder_selection?: string;
builder_boost_factor?: UintNum64;
strict_fee_recipient_check?: boolean;
blinded_local?: boolean;
};
Expand Down Expand Up @@ -555,6 +560,7 @@ export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
fee_recipient: opts?.feeRecipient,
skip_randao_verification: skipRandaoVerification,
builder_selection: opts?.builderSelection,
builder_boost_factor: opts?.builderBoostFactor,
strict_fee_recipient_check: opts?.strictFeeRecipientCheck,
blinded_local: opts?.blindedLocal,
},
Expand All @@ -567,6 +573,7 @@ export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
{
feeRecipient: query.fee_recipient,
builderSelection: query.builder_selection as BuilderSelection,
builderBoostFactor: query.builder_boost_factor,
strictFeeRecipientCheck: query.strict_fee_recipient_check,
blindedLocal: query.blinded_local,
},
Expand All @@ -579,6 +586,7 @@ export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
fee_recipient: Schema.String,
skip_randao_verification: Schema.Boolean,
builder_selection: Schema.String,
builder_boost_factor: Schema.Uint,
strict_fee_recipient_check: Schema.Boolean,
blinded_local: Schema.Boolean,
},
Expand Down
14 changes: 12 additions & 2 deletions packages/beacon-node/src/api/impl/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,12 @@ export function getValidatorApi({
graffiti,
// TODO deneb: skip randao verification
_skipRandaoVerification?: boolean,
{feeRecipient, builderSelection, strictFeeRecipientCheck}: routes.validator.ExtraProduceBlockOps = {}
{
feeRecipient,
builderSelection,
builderBoostFactor,
strictFeeRecipientCheck,
}: routes.validator.ExtraProduceBlockOps = {}
) {
notWhileSyncing();
await waitForSlot(slot); // Must never request for a future slot > currentSlot
Expand All @@ -436,7 +441,10 @@ export function getValidatorApi({

const fork = config.getForkName(slot);
// set some sensible opts
// builderSelection will be deprecated and will run in mode MaxProfit if builder is enabled
// and the actual selection will be determined using builderBoostFactor passed by the validator
builderSelection = builderSelection ?? routes.validator.BuilderSelection.MaxProfit;
builderBoostFactor = builderBoostFactor ?? 100;
const isBuilderEnabled =
ForkSeq[fork] >= ForkSeq.bellatrix &&
chain.executionBuilder !== undefined &&
Expand All @@ -448,6 +456,7 @@ export function getValidatorApi({
slot,
isBuilderEnabled,
strictFeeRecipientCheck,
builderBoostFactor,
});
// Start calls for building execution and builder blocks
const blindedBlockPromise = isBuilderEnabled
Expand Down Expand Up @@ -541,7 +550,7 @@ export function getValidatorApi({
if (fullBlock && blindedBlock) {
switch (builderSelection) {
case routes.validator.BuilderSelection.MaxProfit: {
if (blockValueEngine >= blockValueBuilder) {
if (blockValueEngine >= (blockValueBuilder * BigInt(builderBoostFactor)) / BigInt(100)) {
executionPayloadSource = ProducedBlockSource.engine;
} else {
executionPayloadSource = ProducedBlockSource.builder;
Expand All @@ -561,6 +570,7 @@ export function getValidatorApi({
}
logger.verbose(`Selected executionPayloadSource=${executionPayloadSource} block`, {
builderSelection,
builderBoostFactor,
// winston logger doesn't like bigint
enginePayloadValue: `${enginePayloadValue}`,
builderPayloadValue: `${builderPayloadValue}`,
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/cmds/validator/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ function getProposerConfigFromArgs(
selection: parseBuilderSelection(
args["builder.selection"] ?? (args["builder"] ? defaultOptions.builderAliasSelection : undefined)
),
boostFactor: args["builder.boostFactor"]
},
};

Expand Down
8 changes: 8 additions & 0 deletions packages/cli/src/cmds/validator/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export type IValidatorCliArgs = AccountValidatorArgs &

builder?: boolean;
"builder.selection"?: string;
"builder.boostFactor"?: number,

useProduceBlockV3?: boolean;
broadcastValidation?: string;
Expand Down Expand Up @@ -246,6 +247,13 @@ export const validatorOptions: CliCommandOptions<IValidatorCliArgs> = {
group: "builder",
},

"builder.boostFactor": {
type: "number",
description: "A factor in percentage requested to block producing beacon to boost (>100) or dampen(<100) builder block value for selection against engine, is overriden when `--builder.selection` set to anything other than `maxprofit`",
defaultDescription: `${defaultOptions.builderBoostFactor}`,
group: "builder",
},

useProduceBlockV3: {
type: "boolean",
description: "Enable/disable usage of produceBlockV3 that might not be supported by all beacon clients yet",
Expand Down
60 changes: 43 additions & 17 deletions packages/validator/src/services/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,15 @@ export class BlockProposingService {
const debugLogCtx = {...logCtx, validator: pubkeyHex};

const strictFeeRecipientCheck = this.validatorStore.strictFeeRecipientCheck(pubkeyHex);
const builderSelection = this.validatorStore.getBuilderSelection(pubkeyHex);
const {selection: builderSelection, boostFactor: builderBoostFactor} =
this.validatorStore.getBuilderSelectionParams(pubkeyHex);
const feeRecipient = this.validatorStore.getFeeRecipient(pubkeyHex);
const blindedLocal = this.opts.blindedLocal;

this.logger.debug("Producing block", {
...debugLogCtx,
builderSelection,
builderBoostFactor,
feeRecipient,
strictFeeRecipientCheck,
useProduceBlockV3: this.opts.useProduceBlockV3,
Expand All @@ -139,15 +141,20 @@ export class BlockProposingService {
const produceOpts = {
feeRecipient,
strictFeeRecipientCheck,
builderSelection,
builderBoostFactor,
blindedLocal,
};
const blockContents = await produceBlockFn(this.config, slot, randaoReveal, graffiti, produceOpts).catch(
(e: Error) => {
this.metrics?.blockProposingErrors.inc({error: "produce"});
throw extendError(e, "Failed to produce block");
}
);
const blockContents = await produceBlockFn(
this.config,
slot,
randaoReveal,
graffiti,
produceOpts,
builderSelection
).catch((e: Error) => {
this.metrics?.blockProposingErrors.inc({error: "produce"});
throw extendError(e, "Failed to produce block");
});

this.logger.debug("Produced block", {...debugLogCtx, ...blockContents.debugLogCtx});
this.metrics?.blocksProduced.inc();
Expand Down Expand Up @@ -195,12 +202,14 @@ export class BlockProposingService {
slot: Slot,
randaoReveal: BLSSignature,
graffiti: string,
{feeRecipient, strictFeeRecipientCheck, builderSelection}: routes.validator.ExtraProduceBlockOps
{feeRecipient, strictFeeRecipientCheck, builderBoostFactor}: routes.validator.ExtraProduceBlockOps,
builderSelection: routes.validator.BuilderSelection
): Promise<FullOrBlindedBlockWithContents & DebugLogCtx> => {
const res = await this.api.validator.produceBlockV3(slot, randaoReveal, graffiti, false, {
feeRecipient,
builderSelection,
strictFeeRecipientCheck,
builderBoostFactor,
});
ApiError.assert(res, "Failed to produce block: validator.produceBlockV2");
const {response} = res;
Expand All @@ -221,7 +230,7 @@ export class BlockProposingService {
api: "produceBlockV3",
};

return parseProduceBlockResponse(response, debugLogCtx);
return parseProduceBlockResponse(response, debugLogCtx, builderSelection);
};

/** a wrapper function used for backward compatibility with the clients who don't have v3 implemented yet */
Expand All @@ -230,7 +239,8 @@ export class BlockProposingService {
slot: Slot,
randaoReveal: BLSSignature,
graffiti: string,
{builderSelection}: routes.validator.ExtraProduceBlockOps
_opts: routes.validator.ExtraProduceBlockOps,
builderSelection: routes.validator.BuilderSelection
): Promise<FullOrBlindedBlockWithContents & DebugLogCtx> => {
// other clients have always implemented builder vs execution race in produce blinded block
// so if builderSelection is executiononly then only we call produceBlockV2 else produceBlockV3 always
Expand All @@ -246,7 +256,8 @@ export class BlockProposingService {

return parseProduceBlockResponse(
{executionPayloadBlinded: false, executionPayloadSource, ...response},
debugLogCtx
debugLogCtx,
builderSelection
);
} else {
Object.assign(debugLogCtx, {api: "produceBlindedBlock"});
Expand All @@ -257,23 +268,38 @@ export class BlockProposingService {

return parseProduceBlockResponse(
{executionPayloadBlinded: true, executionPayloadSource, ...response},
debugLogCtx
debugLogCtx,
builderSelection
);
}
};
}

function parseProduceBlockResponse(
response: routes.validator.ProduceFullOrBlindedBlockOrContentsRes,
debugLogCtx: Record<string, string | boolean | undefined>
debugLogCtx: Record<string, string | boolean | undefined>,
builderSelection: routes.validator.BuilderSelection
): FullOrBlindedBlockWithContents & DebugLogCtx {
const executionPayloadSource = response.executionPayloadSource;

if (
(builderSelection === routes.validator.BuilderSelection.BuilderOnly &&
executionPayloadSource === ProducedBlockSource.engine) ||
(builderSelection === routes.validator.BuilderSelection.ExecutionOnly &&
executionPayloadSource === ProducedBlockSource.builder)
) {
throw Error(
`Block not produced as per desired builderSelection=${builderSelection} executionPayloadSource=${executionPayloadSource}`
);
}

if (response.executionPayloadBlinded) {
return {
block: response.data,
contents: null,
version: response.version,
executionPayloadBlinded: true,
executionPayloadSource: response.executionPayloadSource,
executionPayloadSource,
debugLogCtx,
} as FullOrBlindedBlockWithContents & DebugLogCtx;
} else {
Expand All @@ -283,7 +309,7 @@ function parseProduceBlockResponse(
contents: {blobs: response.data.blobs, kzgProofs: response.data.kzgProofs},
version: response.version,
executionPayloadBlinded: false,
executionPayloadSource: response.executionPayloadSource,
executionPayloadSource,
debugLogCtx,
} as FullOrBlindedBlockWithContents & DebugLogCtx;
} else {
Expand All @@ -292,7 +318,7 @@ function parseProduceBlockResponse(
contents: null,
version: response.version,
executionPayloadBlinded: false,
executionPayloadSource: response.executionPayloadSource,
executionPayloadSource,
debugLogCtx,
} as FullOrBlindedBlockWithContents & DebugLogCtx;
}
Expand Down
3 changes: 2 additions & 1 deletion packages/validator/src/services/prepareBeaconProposer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ export function pollBuilderValidatorRegistration(
.filter(
(pubkeyHex): pubkeyHex is string =>
pubkeyHex !== undefined &&
validatorStore.getBuilderSelection(pubkeyHex) !== routes.validator.BuilderSelection.ExecutionOnly
validatorStore.getBuilderSelectionParams(pubkeyHex).selection !==
routes.validator.BuilderSelection.ExecutionOnly
);

if (pubkeyHexes.length > 0) {
Expand Down
29 changes: 27 additions & 2 deletions packages/validator/src/services/validatorStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type DefaultProposerConfig = {
builder: {
gasLimit: number;
selection: routes.validator.BuilderSelection;
boostFactor: number;
};
};

Expand All @@ -79,6 +80,7 @@ export type ProposerConfig = {
builder?: {
gasLimit?: number;
selection?: routes.validator.BuilderSelection;
boostFactor?: number;
};
};

Expand Down Expand Up @@ -123,6 +125,7 @@ export const defaultOptions = {
defaultGasLimit: 30_000_000,
builderSelection: routes.validator.BuilderSelection.ExecutionOnly,
builderAliasSelection: routes.validator.BuilderSelection.MaxProfit,
builderBoostFactor: 100,
// turn it off by default, turn it back on once other clients support v3 api
useProduceBlockV3: false,
// spec asks for gossip validation by default
Expand All @@ -131,6 +134,8 @@ export const defaultOptions = {
blindedLocal: false,
};

const MAX_BUILDER_BOOST_FACTOR = 2 ** 64 - 1;

/**
* Service that sets up and handles validator attester duties.
*/
Expand Down Expand Up @@ -162,6 +167,7 @@ export class ValidatorStore {
builder: {
gasLimit: defaultConfig.builder?.gasLimit ?? defaultOptions.defaultGasLimit,
selection: defaultConfig.builder?.selection ?? defaultOptions.builderSelection,
boostFactor: defaultConfig.builder?.boostFactor ?? defaultOptions.builderBoostFactor,
},
};

Expand Down Expand Up @@ -252,8 +258,27 @@ export class ValidatorStore {
delete validatorData["graffiti"];
}

getBuilderSelection(pubkeyHex: PubkeyHex): routes.validator.BuilderSelection {
return (this.validators.get(pubkeyHex)?.builder || {}).selection ?? this.defaultProposerConfig.builder.selection;
getBuilderSelectionParams(pubkeyHex: PubkeyHex): {selection: routes.validator.BuilderSelection; boostFactor: number} {
const selection =
(this.validators.get(pubkeyHex)?.builder || {}).selection ?? this.defaultProposerConfig.builder.selection;

let boostFactor;
switch (selection) {
case routes.validator.BuilderSelection.MaxProfit:
boostFactor =
(this.validators.get(pubkeyHex)?.builder || {}).boostFactor ?? this.defaultProposerConfig.builder.boostFactor;
break;

case routes.validator.BuilderSelection.BuilderAlways:
case routes.validator.BuilderSelection.BuilderOnly:
boostFactor = MAX_BUILDER_BOOST_FACTOR;
break;

case routes.validator.BuilderSelection.ExecutionOnly:
boostFactor = 0;
}

return {selection, boostFactor};
}

strictFeeRecipientCheck(pubkeyHex: PubkeyHex): boolean {
Expand Down

0 comments on commit 383859c

Please sign in to comment.