diff --git a/packages/cli/src/cmds/validator/handler.ts b/packages/cli/src/cmds/validator/handler.ts index 305e101f5287..c0f8e6babdc2 100644 --- a/packages/cli/src/cmds/validator/handler.ts +++ b/packages/cli/src/cmds/validator/handler.ts @@ -254,6 +254,8 @@ function parseBuilderSelection(builderSelection?: string): BuilderSelection | un break; case "builderalways": break; + case "builderonly": + break; default: throw Error("Invalid input for builder selection, check help."); } diff --git a/packages/cli/src/cmds/validator/options.ts b/packages/cli/src/cmds/validator/options.ts index 3dd0d7d9505d..f92814cdcb3d 100644 --- a/packages/cli/src/cmds/validator/options.ts +++ b/packages/cli/src/cmds/validator/options.ts @@ -237,7 +237,7 @@ export const validatorOptions: CliCommandOptions = { "builder.selection": { type: "string", - description: "Default builder block selection strategy: maxprofit or builderalways", + description: "Default builder block selection strategy: maxprofit, builderalways, or builderonly", defaultDescription: `${defaultOptions.builderSelection}`, group: "builder", }, diff --git a/packages/validator/src/services/block.ts b/packages/validator/src/services/block.ts index bb2d230e0321..b83c239b62e8 100644 --- a/packages/validator/src/services/block.ts +++ b/packages/validator/src/services/block.ts @@ -109,14 +109,20 @@ export class BlockProposingService { const debugLogCtx = {...logCtx, validator: pubkeyHex}; - this.logger.debug("Producing block", debugLogCtx); - this.metrics?.proposerStepCallProduceBlock.observe(this.clock.secFromSlot(slot)); - const strictFeeRecipientCheck = this.validatorStore.strictFeeRecipientCheck(pubkeyHex); const isBuilderEnabled = this.validatorStore.isBuilderEnabled(pubkeyHex); const builderSelection = this.validatorStore.getBuilderSelection(pubkeyHex); const expectedFeeRecipient = this.validatorStore.getFeeRecipient(pubkeyHex); + this.logger.debug("Producing block", { + ...debugLogCtx, + isBuilderEnabled, + builderSelection, + expectedFeeRecipient, + strictFeeRecipientCheck, + }); + this.metrics?.proposerStepCallProduceBlock.observe(this.clock.secFromSlot(slot)); + const blockContents = await this.produceBlockWrapper(slot, randaoReveal, graffiti, { expectedFeeRecipient, strictFeeRecipientCheck, @@ -190,10 +196,20 @@ export class BlockProposingService { > => { // Start calls for building execution and builder blocks const blindedBlockPromise = isBuilderEnabled ? this.produceBlindedBlock(slot, randaoReveal, graffiti) : null; - const fullBlockPromise = this.produceBlock(slot, randaoReveal, graffiti); + const fullBlockPromise = + // At any point either the builder or execution or both flows should be active. + // + // Ideally such a scenario should be prevented on startup, but proposerSettingsFile or keymanager + // configurations could cause a validator pubkey to have builder disabled with builder selection builder only + // (TODO: independently make sure such an options update is not successful for a validator pubkey) + // + // So if builder is disabled ignore builder selection of builderonly if caused by user mistake + !isBuilderEnabled || builderSelection !== BuilderSelection.BuilderOnly + ? this.produceBlock(slot, randaoReveal, graffiti) + : null; let blindedBlock, fullBlock; - if (blindedBlockPromise !== null) { + if (blindedBlockPromise !== null && fullBlockPromise !== null) { // reference index of promises in the race const promisesOrder = [ProducedBlockSource.builder, ProducedBlockSource.engine]; [blindedBlock, fullBlock] = await racePromisesWithCutoff<{ @@ -225,9 +241,16 @@ export class BlockProposingService { this.logger.error("Failed to produce execution block", {}, fullBlock); fullBlock = null; } - } else { - fullBlock = await fullBlockPromise; + } else if (blindedBlockPromise !== null && fullBlockPromise === null) { + blindedBlock = await blindedBlockPromise; + fullBlock = null; + } else if (blindedBlockPromise === null && fullBlockPromise !== null) { blindedBlock = null; + fullBlock = await fullBlockPromise; + } else { + throw Error( + `Internal Error: Neither builder nor execution proposal flow activated isBuilderEnabled=${isBuilderEnabled} builderSelection=${builderSelection}` + ); } const builderBlockValue = blindedBlock?.blockValue ?? BigInt(0); @@ -252,7 +275,7 @@ export class BlockProposingService { break; } - case BuilderSelection.BuilderAlways: + // For everything else just select the builder default: { selectedSource = ProducedBlockSource.builder; selectedBlock = blindedBlock; diff --git a/packages/validator/src/services/validatorStore.ts b/packages/validator/src/services/validatorStore.ts index 0dbd9077257b..d2fe36b1e214 100644 --- a/packages/validator/src/services/validatorStore.ts +++ b/packages/validator/src/services/validatorStore.ts @@ -67,6 +67,8 @@ export type SignerRemote = { export enum BuilderSelection { BuilderAlways = "builderalways", MaxProfit = "maxprofit", + /** Only activate builder flow for DVT block proposal protocols */ + BuilderOnly = "builderonly", } type DefaultProposerConfig = {