diff --git a/packages/api/src/beacon/routes/validator.ts b/packages/api/src/beacon/routes/validator.ts index a7d4c377851a..0fa58f7e9032 100644 --- a/packages/api/src/beacon/routes/validator.ts +++ b/packages/api/src/beacon/routes/validator.ts @@ -13,6 +13,7 @@ import { Slot, ssz, UintNum64, + UintBn64, ValidatorIndex, RootHex, StringType, @@ -56,7 +57,7 @@ export type ExtraProduceBlockOps = { // 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; + builderBoostFactor?: UintBn64; strictFeeRecipientCheck?: boolean; blindedLocal?: boolean; }; @@ -491,7 +492,7 @@ export type ReqTypes = { skip_randao_verification?: boolean; fee_recipient?: string; builder_selection?: string; - builder_boost_factor?: UintNum64; + builder_boost_factor?: string; strict_fee_recipient_check?: boolean; blinded_local?: boolean; }; @@ -560,7 +561,7 @@ export function getReqSerializers(): ReqSerializers { fee_recipient: opts?.feeRecipient, skip_randao_verification: skipRandaoVerification, builder_selection: opts?.builderSelection, - builder_boost_factor: opts?.builderBoostFactor, + builder_boost_factor: opts?.builderBoostFactor?.toString(), strict_fee_recipient_check: opts?.strictFeeRecipientCheck, blinded_local: opts?.blindedLocal, }, @@ -573,7 +574,7 @@ export function getReqSerializers(): ReqSerializers { { feeRecipient: query.fee_recipient, builderSelection: query.builder_selection as BuilderSelection, - builderBoostFactor: query.builder_boost_factor, + builderBoostFactor: parseBuilderBoostFactor(query.builder_boost_factor), strictFeeRecipientCheck: query.strict_fee_recipient_check, blindedLocal: query.blinded_local, }, @@ -586,7 +587,7 @@ export function getReqSerializers(): ReqSerializers { fee_recipient: Schema.String, skip_randao_verification: Schema.Boolean, builder_selection: Schema.String, - builder_boost_factor: Schema.Uint, + builder_boost_factor: Schema.String, strict_fee_recipient_check: Schema.Boolean, blinded_local: Schema.Boolean, }, @@ -793,3 +794,7 @@ export function getReturnTypes(): ReturnTypes { getLiveness: jsonType("snake"), }; } + +function parseBuilderBoostFactor(builderBoostFactorInput?: string | number | bigint): bigint | undefined { + return builderBoostFactorInput !== undefined ? BigInt(builderBoostFactorInput) : undefined; +} diff --git a/packages/api/src/keymanager/routes.ts b/packages/api/src/keymanager/routes.ts index c9a16a52e343..e43f3b6ffee7 100644 --- a/packages/api/src/keymanager/routes.ts +++ b/packages/api/src/keymanager/routes.ts @@ -249,7 +249,7 @@ export type Api = { updateBuilderBoostFactor( pubkey: string, - builderBoostFactor: number + builderBoostFactor: bigint ): Promise< ApiClientResponse< {[HttpStatusCode.OK]: void; [HttpStatusCode.NO_CONTENT]: void}, @@ -442,7 +442,7 @@ export function getReqSerializers(): ReqSerializers { params: {pubkey}, body: {builder_boost_factor: builderBoostFactor.toString(10)}, }), - parseReq: ({params: {pubkey}, body: {builder_boost_factor}}) => [pubkey, parseBoostFactor(builder_boost_factor)], + parseReq: ({params: {pubkey}, body: {builder_boost_factor}}) => [pubkey, BigInt(builder_boost_factor)], schema: { params: {pubkey: Schema.StringRequired}, body: Schema.Object, @@ -494,18 +494,3 @@ function parseGasLimit(gasLimitInput: string | number): number { } return gasLimit; } - -function parseBoostFactor(builderBoostFactorInput: string | number): number { - if ( - (typeof builderBoostFactorInput !== "string" && typeof builderBoostFactorInput !== "number") || - `${builderBoostFactorInput}`.trim() === "" - ) { - throw Error("Not valid Builder Boost Factor"); - } - - const builderBoostFactor = Number(builderBoostFactorInput); - if (Number.isNaN(builderBoostFactor)) { - throw Error(`Builder Boost Factor is not valid builderBoostFactor=${builderBoostFactor}`); - } - return builderBoostFactor; -} diff --git a/packages/api/test/unit/beacon/testData/validator.ts b/packages/api/test/unit/beacon/testData/validator.ts index c10f67fa4095..2688f2080eba 100644 --- a/packages/api/test/unit/beacon/testData/validator.ts +++ b/packages/api/test/unit/beacon/testData/validator.ts @@ -50,7 +50,13 @@ export const testData: GenericServerTestCases = { randaoReveal, graffiti, undefined, - {feeRecipient: undefined, builderSelection: undefined, strictFeeRecipientCheck: undefined}, + { + feeRecipient, + builderSelection: undefined, + strictFeeRecipientCheck: undefined, + blindedLocal: undefined, + builderBoostFactor: 100n, + }, ] as unknown as GenericServerTestCases["produceBlock"]["args"], res: {data: ssz.phase0.BeaconBlock.defaultValue()}, }, @@ -60,7 +66,13 @@ export const testData: GenericServerTestCases = { randaoReveal, graffiti, undefined, - {feeRecipient: undefined, builderSelection: undefined, strictFeeRecipientCheck: undefined}, + { + feeRecipient, + builderSelection: undefined, + strictFeeRecipientCheck: undefined, + blindedLocal: undefined, + builderBoostFactor: 100n, + }, ] as unknown as GenericServerTestCases["produceBlockV2"]["args"], res: { data: ssz.altair.BeaconBlock.defaultValue(), @@ -75,7 +87,13 @@ export const testData: GenericServerTestCases = { randaoReveal, graffiti, true, - {feeRecipient, builderSelection: undefined, strictFeeRecipientCheck: undefined}, + { + feeRecipient, + builderSelection: undefined, + strictFeeRecipientCheck: undefined, + blindedLocal: undefined, + builderBoostFactor: 100n, + }, ], res: { data: ssz.altair.BeaconBlock.defaultValue(), @@ -92,7 +110,13 @@ export const testData: GenericServerTestCases = { randaoReveal, graffiti, undefined, - {feeRecipient: undefined, builderSelection: undefined, strictFeeRecipientCheck: undefined}, + { + feeRecipient, + builderSelection: undefined, + strictFeeRecipientCheck: undefined, + blindedLocal: undefined, + builderBoostFactor: 100n, + }, ] as unknown as GenericServerTestCases["produceBlindedBlock"]["args"], res: { data: ssz.bellatrix.BlindedBeaconBlock.defaultValue(), diff --git a/packages/api/test/unit/keymanager/testData.ts b/packages/api/test/unit/keymanager/testData.ts index a4fc72fc8e2d..c63d974ab5c4 100644 --- a/packages/api/test/unit/keymanager/testData.ts +++ b/packages/api/test/unit/keymanager/testData.ts @@ -13,6 +13,7 @@ const pubkeyRand = "0x84105a985058fc8740a48bf1ede9d223ef09e8c6b1735ba0a55cf4a9ff const ethaddressRand = "0xabcf8e0d4e9587369b2301d0790347320302cc09"; const graffitiRandUtf8 = "636861696e736166652f6c6f64657374"; const gasLimitRand = 30_000_000; +const builderBoostFactor = BigInt(100); export const testData: GenericServerTestCases = { listKeys: { @@ -99,4 +100,8 @@ export const testData: GenericServerTestCases = { args: [pubkeyRand, 1], res: {data: ssz.phase0.SignedVoluntaryExit.defaultValue()}, }, + updateBuilderBoostFactor: { + args: [pubkeyRand, builderBoostFactor], + res: undefined, + }, }; diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index 2f144f42904c..04b1c862d0f8 100644 --- a/packages/beacon-node/src/api/impl/validator/index.ts +++ b/packages/beacon-node/src/api/impl/validator/index.ts @@ -444,7 +444,7 @@ export function getValidatorApi({ // 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; + builderBoostFactor = builderBoostFactor ?? BigInt(100); const isBuilderEnabled = ForkSeq[fork] >= ForkSeq.bellatrix && chain.executionBuilder !== undefined && @@ -456,7 +456,8 @@ export function getValidatorApi({ slot, isBuilderEnabled, strictFeeRecipientCheck, - builderBoostFactor, + // winston logger doesn't like bigint + builderBoostFactor: `${builderBoostFactor}`, }); // Start calls for building execution and builder blocks const blindedBlockPromise = isBuilderEnabled @@ -550,7 +551,7 @@ export function getValidatorApi({ if (fullBlock && blindedBlock) { switch (builderSelection) { case routes.validator.BuilderSelection.MaxProfit: { - if (blockValueEngine >= (blockValueBuilder * BigInt(builderBoostFactor)) / BigInt(100)) { + if (blockValueEngine >= (blockValueBuilder * builderBoostFactor) / BigInt(100)) { executionPayloadSource = ProducedBlockSource.engine; } else { executionPayloadSource = ProducedBlockSource.builder; @@ -570,8 +571,8 @@ export function getValidatorApi({ } logger.verbose(`Selected executionPayloadSource=${executionPayloadSource} block`, { builderSelection, - builderBoostFactor, // winston logger doesn't like bigint + builderBoostFactor: `${builderBoostFactor}`, enginePayloadValue: `${enginePayloadValue}`, builderPayloadValue: `${builderPayloadValue}`, consensusBlockValueEngine: `${consensusBlockValueEngine}`, diff --git a/packages/cli/src/cmds/validator/keymanager/impl.ts b/packages/cli/src/cmds/validator/keymanager/impl.ts index fd57b624f50d..0f94943a706c 100644 --- a/packages/cli/src/cmds/validator/keymanager/impl.ts +++ b/packages/cli/src/cmds/validator/keymanager/impl.ts @@ -390,7 +390,7 @@ export class KeymanagerApi implements Api { }; } - async updateBuilderBoostFactor(pubkeyHex: string, builderBoostFactor: number): Promise { + async updateBuilderBoostFactor(pubkeyHex: string, builderBoostFactor: bigint): Promise { this.checkIfProposerWriteEnabled(); this.validator.validatorStore.updateBuilderBoostFactor(pubkeyHex, builderBoostFactor); this.persistedKeysBackend.writeProposerConfig( diff --git a/packages/cli/src/cmds/validator/options.ts b/packages/cli/src/cmds/validator/options.ts index 2c8862400516..8de2a6002686 100644 --- a/packages/cli/src/cmds/validator/options.ts +++ b/packages/cli/src/cmds/validator/options.ts @@ -45,7 +45,7 @@ export type IValidatorCliArgs = AccountValidatorArgs & builder?: boolean; "builder.selection"?: string; - "builder.boostFactor"?: number; + "builder.boostFactor"?: bigint; useProduceBlockV3?: boolean; broadcastValidation?: string; diff --git a/packages/validator/src/services/validatorStore.ts b/packages/validator/src/services/validatorStore.ts index 18f9dba28529..13fd1543f6ff 100644 --- a/packages/validator/src/services/validatorStore.ts +++ b/packages/validator/src/services/validatorStore.ts @@ -69,7 +69,7 @@ type DefaultProposerConfig = { builder: { gasLimit: number; selection: routes.validator.BuilderSelection; - boostFactor: number; + boostFactor: bigint; }; }; @@ -80,7 +80,7 @@ export type ProposerConfig = { builder?: { gasLimit?: number; selection?: routes.validator.BuilderSelection; - boostFactor?: number; + boostFactor?: bigint; }; }; @@ -125,7 +125,7 @@ export const defaultOptions = { defaultGasLimit: 30_000_000, builderSelection: routes.validator.BuilderSelection.ExecutionOnly, builderAliasSelection: routes.validator.BuilderSelection.MaxProfit, - builderBoostFactor: 100, + builderBoostFactor: BigInt(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 @@ -134,7 +134,7 @@ export const defaultOptions = { blindedLocal: false, }; -const MAX_BUILDER_BOOST_FACTOR = 2 ** 64 - 1; +const MAX_BUILDER_BOOST_FACTOR = BigInt(2 ** 64 - 1); /** * Service that sets up and handles validator attester duties. @@ -258,7 +258,7 @@ export class ValidatorStore { delete validatorData["graffiti"]; } - getBuilderSelectionParams(pubkeyHex: PubkeyHex): {selection: routes.validator.BuilderSelection; boostFactor: number} { + getBuilderSelectionParams(pubkeyHex: PubkeyHex): {selection: routes.validator.BuilderSelection; boostFactor: bigint} { const selection = (this.validators.get(pubkeyHex)?.builder || {}).selection ?? this.defaultProposerConfig.builder.selection; @@ -275,7 +275,7 @@ export class ValidatorStore { break; case routes.validator.BuilderSelection.ExecutionOnly: - boostFactor = 0; + boostFactor = BigInt(0); } return {selection, boostFactor}; @@ -311,7 +311,7 @@ export class ValidatorStore { delete validatorData.builder?.gasLimit; } - updateBuilderBoostFactor(pubkeyHex: PubkeyHex, boostFactor: number): void { + updateBuilderBoostFactor(pubkeyHex: PubkeyHex, boostFactor: bigint): void { const validatorData = this.validators.get(pubkeyHex); if (validatorData === undefined) { throw Error(`Validator pubkey ${pubkeyHex} not known`); diff --git a/packages/validator/test/unit/services/block.test.ts b/packages/validator/test/unit/services/block.test.ts index 7825208ee5a5..3677cdac3a7a 100644 --- a/packages/validator/test/unit/services/block.test.ts +++ b/packages/validator/test/unit/services/block.test.ts @@ -61,7 +61,7 @@ describe("BlockDutiesService", function () { validatorStore.signBlock.callsFake(async (_, block) => ({message: block, signature: signedBlock.signature})); validatorStore.getBuilderSelectionParams.returns({ selection: routes.validator.BuilderSelection.MaxProfit, - boostFactor: 100, + boostFactor: BigInt(100), }); validatorStore.getGraffiti.returns("aaaa"); validatorStore.getFeeRecipient.returns("0x00"); @@ -107,7 +107,7 @@ describe("BlockDutiesService", function () { builderSelection: routes.validator.BuilderSelection.MaxProfit, strictFeeRecipientCheck: false, blindedLocal: false, - builderBoostFactor: 100, + builderBoostFactor: BigInt(100), }, ], "wrong produceBlockV3() args"