diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix.go index c7baa85b3fb8..be4ec6734e89 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix.go @@ -92,15 +92,16 @@ func setExecutionData(ctx context.Context, blk interfaces.SignedBeaconBlock, loc // Use builder payload if the following in true: // builder_bid_value * builderBoostFactor(default 100) > local_block_value * (local-block-value-boost + 100) boost := params.BeaconConfig().LocalBlockValueBoost - higherValueBuilder := builderValueGwei*builderBoostFactor > localValueGwei*(100+boost) - if boost > 0 && builderBoostFactor != defaultBuilderBoostFactor { + if boost > 0 && builderBoostFactor > defaultBuilderBoostFactor { log.WithFields(logrus.Fields{ "localGweiValue": localValueGwei, "localBoostPercentage": boost, "builderGweiValue": builderValueGwei, "builderBoostFactor": builderBoostFactor, - }).Warn("Proposer: both local boost and builder boost are using non default values") + }).Warn("Proposer: builder boost will be ignored, because local boost is activated and builder boost is above the default") + builderBoostFactor = defaultBuilderBoostFactor } + higherValueBuilder := builderValueGwei*builderBoostFactor > localValueGwei*(100+boost) builderValueGweiGauge.Set(float64(builderValueGwei)) localValueGweiGauge.Set(float64(localValueGwei)) diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix_test.go index 7dae54bfaa89..e1ff3b0e5502 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix_test.go @@ -290,7 +290,7 @@ func TestServer_setExecutionData(t *testing.T) { require.NoError(t, err) require.Equal(t, uint64(2), e.BlockNumber()) // builder block }) - t.Run("Builder builder has higher value but forced to local payload with builder boost factor", func(t *testing.T) { + t.Run("Builder has higher value but forced to local payload with builder boost factor at 0", func(t *testing.T) { blk, err := blocks.NewSignedBeaconBlock(util.NewBlindedBeaconBlockCapella()) require.NoError(t, err) require.NoError(t, vs.BeaconDB.SaveRegistrationsByValidatorIDs(ctx, []primitives.ValidatorIndex{blk.Block().ProposerIndex()}, @@ -370,6 +370,23 @@ func TestServer_setExecutionData(t *testing.T) { require.LogsContain(t, hook, "builderGweiValue=1 localBoostPercentage=0 localGweiValue=2") }) + t.Run("Builder configured. Local block has higher value even with higher builder boost factor", func(t *testing.T) { + blk, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlockCapella()) + require.NoError(t, err) + vs.ExecutionEngineCaller = &powtesting.EngineClient{PayloadIDBytes: id, ExecutionPayloadCapella: &v1.ExecutionPayloadCapella{BlockNumber: 3}, BlockValue: 2 * 1e9} + b := blk.Block() + localPayload, _, err := vs.getLocalPayload(ctx, b, capellaTransitionState) + require.NoError(t, err) + builderPayload, builderKzgCommitments, err := vs.getBuilderPayloadAndBlobs(ctx, b.Slot(), b.ProposerIndex()) + require.NoError(t, err) + require.DeepEqual(t, [][]uint8(nil), builderKzgCommitments) + require.NoError(t, setExecutionData(context.Background(), blk, localPayload, builderPayload, builderKzgCommitments, 99999)) + e, err := blk.Block().Body().Execution() + require.NoError(t, err) + require.Equal(t, uint64(3), e.BlockNumber()) // Local block + + require.LogsContain(t, hook, "builderGweiValue=1 localBoostPercentage=0 localGweiValue=2") + }) t.Run("Builder configured. Local block and local boost has higher value", func(t *testing.T) { cfg := params.BeaconConfig().Copy() cfg.LocalBlockValueBoost = 1 // Boost 1%. diff --git a/cmd/beacon-chain/flags/base.go b/cmd/beacon-chain/flags/base.go index 5b26a7480315..f02c7a864723 100644 --- a/cmd/beacon-chain/flags/base.go +++ b/cmd/beacon-chain/flags/base.go @@ -27,8 +27,8 @@ var ( // LocalBlockValueBoost sets a percentage boost for local block construction while using a custom builder. LocalBlockValueBoost = &cli.Uint64Flag{ Name: "local-block-value-boost", - Usage: "A percentage boost for local block construction as a Uint64. This is used to prioritize local block construction over relay/builder block construction" + - "Boost is an additional percentage to multiple local block value. Use builder block if: builder_bid_value * 100 > local_block_value * (local-block-value-boost + 100)", + Usage: "A percentage boost for local block construction as a Uint64. This is used to prioritize local block construction over relay/builder block construction. " + + "Boost is an additional percentage to multiple local block value. Use builder block if: builder_value * builder-boost-factor > local_value * (local-block-value-boost + 100)", Value: 10, } // ExecutionEngineEndpoint provides an HTTP access endpoint to connect to an execution client on the execution layer diff --git a/cmd/validator/flags/flags.go b/cmd/validator/flags/flags.go index 554d5eab8803..69c3bd30562f 100644 --- a/cmd/validator/flags/flags.go +++ b/cmd/validator/flags/flags.go @@ -366,6 +366,14 @@ var ( Value: fmt.Sprint(params.BeaconConfig().DefaultBuilderGasLimit), } + // BuilderBoostFactorFlag sets a multiplier for builder block construction as a Uint64. + BuilderBoostFactorFlag = &cli.Uint64Flag{ + Name: "builder-boost-factor", + Usage: "A multiplier for builder block construction as a Uint64. This is used to prioritize relay/builder block construction over local block construction. " + + "Boost factor is a multiplier against the builder value. Use builder block if: builder_value * builder-boost-factor > local_value * (local-block-value-boost + 100)", + Value: 100, + } + // ValidatorsRegistrationBatchSizeFlag sets the maximum size for one batch of validator registrations. Use a non-positive value to disable batching. ValidatorsRegistrationBatchSizeFlag = &cli.IntFlag{ Name: "validators-registration-batch-size", diff --git a/cmd/validator/main.go b/cmd/validator/main.go index 5692f9ec64f5..1fedb1bf299c 100644 --- a/cmd/validator/main.go +++ b/cmd/validator/main.go @@ -83,6 +83,7 @@ var appFlags = []cli.Flag{ flags.ProposerSettingsFlag, flags.EnableBuilderFlag, flags.BuilderGasLimitFlag, + flags.BuilderBoostFactorFlag, flags.ValidatorsRegistrationBatchSizeFlag, //////////////////// cmd.DisableMonitoringFlag, diff --git a/cmd/validator/usage.go b/cmd/validator/usage.go index ccc76d2617b9..399aa696d50f 100644 --- a/cmd/validator/usage.go +++ b/cmd/validator/usage.go @@ -121,6 +121,7 @@ var appHelpFlagGroups = []flagGroup{ flags.SuggestedFeeRecipientFlag, flags.EnableBuilderFlag, flags.BuilderGasLimitFlag, + flags.BuilderBoostFactorFlag, flags.ValidatorsRegistrationBatchSizeFlag, flags.EnableDistributed, }, diff --git a/config/proposer/loader/loader.go b/config/proposer/loader/loader.go index 9831a9e06b3b..35d3a0e63ded 100644 --- a/config/proposer/loader/loader.go +++ b/config/proposer/loader/loader.go @@ -35,8 +35,9 @@ type settingsLoader struct { } type flagOptions struct { - builderConfig *proposer.BuilderConfig - gasLimit *validator.Uint64 + builderConfig *proposer.BuilderConfig + gasLimit *validator.Uint64 + builderBoostFactor *uint64 } // SettingsLoaderOption sets additional options that affect the proposer settings @@ -74,6 +75,17 @@ func WithGasLimit() SettingsLoaderOption { } } +// WithBuilderBoostFactor applies the --builder-boost-factor flag to proposer settings +func WithBuilderBoostFactor() SettingsLoaderOption { + return func(cliCtx *cli.Context, psl *settingsLoader) error { + if cliCtx.IsSet(flags.BuilderBoostFactorFlag.Name) { + bbf := cliCtx.Uint64(flags.BuilderBoostFactorFlag.Name) + psl.options.builderBoostFactor = &bbf + } + return nil + } +} + // NewProposerSettingsLoader returns a new proposer settings loader that can process the proposer settings based on flag options func NewProposerSettingsLoader(cliCtx *cli.Context, db iface.ValidatorDB, opts ...SettingsLoaderOption) (*settingsLoader, error) { if cliCtx.IsSet(flags.ProposerSettingsFlag.Name) && cliCtx.IsSet(flags.ProposerSettingsURLFlag.Name) { @@ -117,8 +129,11 @@ func (psl *settingsLoader) Load(cliCtx *cli.Context) (*proposer.Settings, error) loadConfig := &validatorpb.ProposerSettingsPayload{} // override settings based on other options - if psl.options.builderConfig != nil && psl.options.gasLimit != nil { - psl.options.builderConfig.GasLimit = *psl.options.gasLimit + if psl.options != nil && psl.options.builderConfig != nil { + if psl.options.gasLimit != nil { + psl.options.builderConfig.GasLimit = *psl.options.gasLimit + } + psl.options.builderConfig.BuilderBoostFactor = psl.options.builderBoostFactor } // check if database has settings already @@ -205,20 +220,8 @@ func (psl *settingsLoader) processProposerSettings(loadedSettings, dbSettings *v // loaded settings have higher priority than db settings newSettings := &validatorpb.ProposerSettingsPayload{} - var builderConfig *validatorpb.BuilderConfig - var gasLimitOnly *validator.Uint64 - - if psl.options != nil { - if psl.options.builderConfig != nil { - builderConfig = psl.options.builderConfig.ToConsensus() - } - if psl.options.gasLimit != nil { - gasLimitOnly = psl.options.gasLimit - } - } - if dbSettings != nil && dbSettings.DefaultConfig != nil { - if builderConfig == nil { + if psl.options == nil || psl.options.builderConfig == nil { dbSettings.DefaultConfig.Builder = nil } newSettings.DefaultConfig = dbSettings.DefaultConfig @@ -229,12 +232,12 @@ func (psl *settingsLoader) processProposerSettings(loadedSettings, dbSettings *v // process any builder overrides on defaults if newSettings.DefaultConfig != nil { - newSettings.DefaultConfig.Builder = processBuilderConfig(newSettings.DefaultConfig.Builder, builderConfig, gasLimitOnly) + newSettings.DefaultConfig.Builder = processBuilderConfig(newSettings.DefaultConfig.Builder, psl.options) } if dbSettings != nil && len(dbSettings.ProposerConfig) != 0 { for _, option := range dbSettings.ProposerConfig { - if builderConfig == nil { + if psl.options == nil || psl.options.builderConfig == nil { option.Builder = nil } } @@ -247,7 +250,7 @@ func (psl *settingsLoader) processProposerSettings(loadedSettings, dbSettings *v // process any overrides for proposer config for _, option := range newSettings.ProposerConfig { if option != nil { - option.Builder = processBuilderConfig(option.Builder, builderConfig, gasLimitOnly) + option.Builder = processBuilderConfig(option.Builder, psl.options) } } @@ -259,18 +262,38 @@ func (psl *settingsLoader) processProposerSettings(loadedSettings, dbSettings *v return newSettings } -func processBuilderConfig(current *validatorpb.BuilderConfig, override *validatorpb.BuilderConfig, gasLimitOnly *validator.Uint64) *validatorpb.BuilderConfig { - if current != nil { - current.GasLimit = reviewGasLimit(current.GasLimit) - if override != nil { - current.Enabled = override.Enabled - } - if gasLimitOnly != nil { - current.GasLimit = *gasLimitOnly - } +func processBuilderConfig(current *validatorpb.BuilderConfig, options *flagOptions) *validatorpb.BuilderConfig { + // If there are no options, return what was passed in + if options == nil { return current } - return override + + // Initialize an override variable + var override *validatorpb.BuilderConfig + if options.builderConfig != nil { + // Convert the builder config to consensus form if it exists + override = options.builderConfig.ToConsensus() + } + // If there's nothing to process further, return the override or current based on what's available + if current == nil { + return override + } + + if override != nil { + current.Enabled = override.Enabled + } + + if options.gasLimit != nil { + current.GasLimit = *options.gasLimit + } + + current.GasLimit = reviewGasLimit(current.GasLimit) + + if options.builderBoostFactor != nil { + current.BuilderBoostFactor = options.builderBoostFactor + } + + return current } func reviewGasLimit(gasLimit validator.Uint64) validator.Uint64 { diff --git a/config/proposer/loader/loader_test.go b/config/proposer/loader/loader_test.go index aaa0592f15b1..091edbbd1c18 100644 --- a/config/proposer/loader/loader_test.go +++ b/config/proposer/loader/loader_test.go @@ -26,12 +26,14 @@ import ( ) func TestProposerSettingsLoader(t *testing.T) { + bbf := uint64(100) hook := logtest.NewGlobal() type proposerSettingsFlag struct { - dir string - url string - defaultfee string - defaultgas string + dir string + url string + defaultfee string + defaultgas string + builderBoostFactor *uint64 } type args struct { @@ -49,6 +51,200 @@ func TestProposerSettingsLoader(t *testing.T) { validatorRegistrationEnabled bool skipDBSavedCheck bool }{ + { + name: "builder boost factor set in proposer settings", + args: args{ + proposerSettingsFlagValues: &proposerSettingsFlag{ + dir: "./testdata/good-prepare-beacon-proposer-with-builder-boost-config.yaml", + url: "", + defaultfee: "", + }, + }, + want: func() *proposer.Settings { + key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a") + require.NoError(t, err) + tempBBF := uint64(90) + return &proposer.Settings{ + ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{ + bytesutil.ToBytes48(key1): { + FeeRecipientConfig: &proposer.FeeRecipientConfig{ + FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"), + }, + BuilderConfig: &proposer.BuilderConfig{ + Enabled: true, + GasLimit: 40000000, + BuilderBoostFactor: &tempBBF, + }, + }, + }, + DefaultConfig: &proposer.Option{ + FeeRecipientConfig: &proposer.FeeRecipientConfig{ + FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"), + }, + BuilderConfig: &proposer.BuilderConfig{ + Enabled: false, + GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit), + }, + }, + } + }, + withdb: func(db iface.ValidatorDB) error { + key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a") + require.NoError(t, err) + tempBBF := uint64(90) + settings := &proposer.Settings{ + ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{ + bytesutil.ToBytes48(key1): { + FeeRecipientConfig: &proposer.FeeRecipientConfig{ + FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"), + }, + BuilderConfig: &proposer.BuilderConfig{ + Enabled: true, + GasLimit: 40000000, + BuilderBoostFactor: &tempBBF, + }, + }, + }, + DefaultConfig: &proposer.Option{ + FeeRecipientConfig: &proposer.FeeRecipientConfig{ + FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"), + }, + BuilderConfig: &proposer.BuilderConfig{ + Enabled: false, + GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit), + }, + }, + } + return db.SaveProposerSettings(context.Background(), settings) + }, + }, + { + name: "builder boost factor flag set with proposer settings file", + args: args{ + proposerSettingsFlagValues: &proposerSettingsFlag{ + dir: "./testdata/good-prepare-beacon-proposer-config.yaml", + url: "", + defaultfee: "", + builderBoostFactor: &bbf, + }, + }, + want: func() *proposer.Settings { + key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a") + require.NoError(t, err) + return &proposer.Settings{ + ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{ + bytesutil.ToBytes48(key1): { + FeeRecipientConfig: &proposer.FeeRecipientConfig{ + FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"), + }, + BuilderConfig: &proposer.BuilderConfig{ + Enabled: true, + GasLimit: 40000000, + BuilderBoostFactor: &bbf, + }, + }, + }, + DefaultConfig: &proposer.Option{ + FeeRecipientConfig: &proposer.FeeRecipientConfig{ + FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"), + }, + BuilderConfig: &proposer.BuilderConfig{ + Enabled: false, + GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit), + BuilderBoostFactor: &bbf, + }, + }, + } + }, + withdb: func(db iface.ValidatorDB) error { + key1, err := hexutil.Decode("0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a") + require.NoError(t, err) + settings := &proposer.Settings{ + ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option{ + bytesutil.ToBytes48(key1): { + FeeRecipientConfig: &proposer.FeeRecipientConfig{ + FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"), + }, + BuilderConfig: &proposer.BuilderConfig{ + Enabled: true, + GasLimit: 40000000, + BuilderBoostFactor: &bbf, + }, + }, + }, + DefaultConfig: &proposer.Option{ + FeeRecipientConfig: &proposer.FeeRecipientConfig{ + FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"), + }, + BuilderConfig: &proposer.BuilderConfig{ + Enabled: false, + GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit), + BuilderBoostFactor: &bbf, + }, + }, + } + return db.SaveProposerSettings(context.Background(), settings) + }, + }, + { + name: "builder boost factor set from flag happy path", + args: args{ + proposerSettingsFlagValues: &proposerSettingsFlag{ + dir: "", + url: "", + defaultfee: "0x6e35733c5af9B61374A128e6F85f553aF09ff89A", + builderBoostFactor: &bbf, + }, + }, + want: func() *proposer.Settings { + return &proposer.Settings{ + ProposeConfig: nil, + DefaultConfig: &proposer.Option{ + FeeRecipientConfig: &proposer.FeeRecipientConfig{ + FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"), + }, + BuilderConfig: &proposer.BuilderConfig{ + Enabled: true, + GasLimit: validator.Uint64(30000000), + BuilderBoostFactor: &bbf, + }, + }, + } + }, + withdb: func(db iface.ValidatorDB) error { + settings := &proposer.Settings{ + ProposeConfig: nil, + DefaultConfig: &proposer.Option{ + FeeRecipientConfig: &proposer.FeeRecipientConfig{ + FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"), + }, + BuilderConfig: &proposer.BuilderConfig{ + Enabled: true, + GasLimit: validator.Uint64(30000000), + BuilderBoostFactor: &bbf, + }, + }, + } + return db.SaveProposerSettings(context.Background(), settings) + }, + validatorRegistrationEnabled: true, + }, + { + name: "builder boost factor set without builder enabled", + args: args{ + proposerSettingsFlagValues: &proposerSettingsFlag{ + dir: "", + url: "", + defaultfee: "", + builderBoostFactor: &bbf, + }, + }, + want: func() *proposer.Settings { + return nil + }, + wantLog: "No proposer settings were provided", + skipDBSavedCheck: true, + }, { name: "graffiti in db without fee recipient", args: args{ @@ -926,6 +1122,10 @@ func TestProposerSettingsLoader(t *testing.T) { set.String(flags.BuilderGasLimitFlag.Name, tt.args.proposerSettingsFlagValues.defaultgas, "") require.NoError(t, set.Set(flags.BuilderGasLimitFlag.Name, tt.args.proposerSettingsFlagValues.defaultgas)) } + if tt.args.proposerSettingsFlagValues.builderBoostFactor != nil { + set.Uint64(flags.BuilderBoostFactorFlag.Name, uint64(*tt.args.proposerSettingsFlagValues.builderBoostFactor), "") + require.NoError(t, set.Set(flags.BuilderBoostFactorFlag.Name, fmt.Sprintf("%d", *tt.args.proposerSettingsFlagValues.builderBoostFactor))) + } if tt.validatorRegistrationEnabled { set.Bool(flags.EnableBuilderFlag.Name, true, "") } @@ -940,6 +1140,7 @@ func TestProposerSettingsLoader(t *testing.T) { validatorDB, WithBuilderConfig(), WithGasLimit(), + WithBuilderBoostFactor(), ) if tt.wantInitErr != "" { require.ErrorContains(t, tt.wantInitErr, err) diff --git a/config/proposer/loader/testdata/good-prepare-beacon-proposer-with-builder-boost-config.yaml b/config/proposer/loader/testdata/good-prepare-beacon-proposer-with-builder-boost-config.yaml new file mode 100644 index 000000000000..e5833de8cf90 --- /dev/null +++ b/config/proposer/loader/testdata/good-prepare-beacon-proposer-with-builder-boost-config.yaml @@ -0,0 +1,13 @@ +--- +proposer_config: + '0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a': + fee_recipient: '0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3' + builder: + enabled: true + gas_limit: 40000000 + builder_boost_factor: 90 +default_config: + fee_recipient: '0x6e35733c5af9B61374A128e6F85f553aF09ff89A' + builder: + enabled: false + gas_limit: '30000000' \ No newline at end of file diff --git a/config/proposer/settings.go b/config/proposer/settings.go index eea44066c973..76525124da90 100644 --- a/config/proposer/settings.go +++ b/config/proposer/settings.go @@ -79,9 +79,10 @@ func verifyOption(key string, option *validatorpb.ProposerOptionPayload) error { // BuilderConfig is the struct representation of the JSON config file set in the validator through the CLI. // GasLimit is a number set to help the network decide on the maximum gas in each block. type BuilderConfig struct { - Enabled bool `json:"enabled" yaml:"enabled"` - GasLimit validator.Uint64 `json:"gas_limit,omitempty" yaml:"gas_limit,omitempty"` - Relays []string `json:"relays,omitempty" yaml:"relays,omitempty"` + Enabled bool `json:"enabled" yaml:"enabled"` + GasLimit validator.Uint64 `json:"gas_limit,omitempty" yaml:"gas_limit,omitempty"` + Relays []string `json:"relays,omitempty" yaml:"relays,omitempty"` + BuilderBoostFactor *uint64 `json:"builder_boost_factor,omitempty" yaml:"builder_boost_factor,omitempty"` } // BuilderConfigFromConsensus converts protobuf to a builder config used in in-memory storage @@ -90,8 +91,9 @@ func BuilderConfigFromConsensus(from *validatorpb.BuilderConfig) *BuilderConfig return nil } c := &BuilderConfig{ - Enabled: from.Enabled, - GasLimit: from.GasLimit, + Enabled: from.Enabled, + GasLimit: from.GasLimit, + BuilderBoostFactor: from.BuilderBoostFactor, } if from.Relays != nil { relays := make([]string, len(from.Relays)) @@ -221,16 +223,18 @@ func (bc *BuilderConfig) Clone() *BuilderConfig { if bc == nil { return nil } - c := &BuilderConfig{} - c.Enabled = bc.Enabled - c.GasLimit = bc.GasLimit var relays []string if bc.Relays != nil { relays = make([]string, len(bc.Relays)) copy(relays, bc.Relays) - c.Relays = relays } - return c + + return &BuilderConfig{ + Enabled: bc.Enabled, + GasLimit: bc.GasLimit, + BuilderBoostFactor: bc.BuilderBoostFactor, + Relays: relays, + } } // Clone creates a deep copy of graffiti config @@ -246,14 +250,17 @@ func (bc *BuilderConfig) ToConsensus() *validatorpb.BuilderConfig { if bc == nil { return nil } - c := &validatorpb.BuilderConfig{} - c.Enabled = bc.Enabled + var relays []string if bc.Relays != nil { relays = make([]string, len(bc.Relays)) copy(relays, bc.Relays) - c.Relays = relays } - c.GasLimit = bc.GasLimit - return c + + return &validatorpb.BuilderConfig{ + Enabled: bc.Enabled, + GasLimit: bc.GasLimit, + Relays: relays, + BuilderBoostFactor: bc.BuilderBoostFactor, + } } diff --git a/config/proposer/settings_test.go b/config/proposer/settings_test.go index 0a5853a72a91..c5dc2e731638 100644 --- a/config/proposer/settings_test.go +++ b/config/proposer/settings_test.go @@ -16,6 +16,7 @@ func Test_Proposer_Setting_Cloning(t *testing.T) { key1hex := "0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a" key1, err := hexutil.Decode(key1hex) require.NoError(t, err) + bbf := uint64(100) settings := &Settings{ ProposeConfig: map[[fieldparams.BLSPubkeyLength]byte]*Option{ bytesutil.ToBytes48(key1): { @@ -23,9 +24,10 @@ func Test_Proposer_Setting_Cloning(t *testing.T) { FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"), }, BuilderConfig: &BuilderConfig{ - Enabled: true, - GasLimit: validator.Uint64(40000000), - Relays: []string{"https://example-relay.com"}, + Enabled: true, + GasLimit: validator.Uint64(40000000), + Relays: []string{"https://example-relay.com"}, + BuilderBoostFactor: &bbf, }, }, }, @@ -34,9 +36,10 @@ func Test_Proposer_Setting_Cloning(t *testing.T) { FeeRecipient: common.HexToAddress("0x6e35733c5af9B61374A128e6F85f553aF09ff89A"), }, BuilderConfig: &BuilderConfig{ - Enabled: false, - GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit), - Relays: []string{"https://example-relay.com"}, + Enabled: false, + GasLimit: validator.Uint64(params.BeaconConfig().DefaultBuilderGasLimit), + Relays: []string{"https://example-relay.com"}, + BuilderBoostFactor: &bbf, }, }, } @@ -57,6 +60,8 @@ func Test_Proposer_Setting_Cloning(t *testing.T) { require.DeepEqual(t, settings.DefaultConfig.BuilderConfig, clone) settings.DefaultConfig.BuilderConfig.GasLimit = 1 require.NotEqual(t, settings.DefaultConfig.BuilderConfig.GasLimit, clone.GasLimit) + settings.DefaultConfig.BuilderConfig.BuilderBoostFactor = nil + require.NotEqual(t, settings.DefaultConfig.BuilderConfig.BuilderBoostFactor, clone.BuilderBoostFactor) }) t.Run("Happy Path BuilderConfigFromConsensus", func(t *testing.T) { @@ -84,6 +89,7 @@ func Test_Proposer_Setting_Cloning(t *testing.T) { require.Equal(t, option.FeeRecipientConfig.FeeRecipient.Hex(), noption.FeeRecipientConfig.FeeRecipient.Hex()) require.Equal(t, option.BuilderConfig.GasLimit, option.BuilderConfig.GasLimit) require.Equal(t, option.BuilderConfig.Enabled, option.BuilderConfig.Enabled) + require.Equal(t, option.BuilderConfig.BuilderBoostFactor, option.BuilderConfig.BuilderBoostFactor) }) } @@ -91,6 +97,7 @@ func TestProposerSettings_ShouldBeSaved(t *testing.T) { key1hex := "0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a" key1, err := hexutil.Decode(key1hex) require.NoError(t, err) + bbf := uint64(100) type fields struct { ProposeConfig map[[fieldparams.BLSPubkeyLength]byte]*Option DefaultConfig *Option @@ -109,9 +116,10 @@ func TestProposerSettings_ShouldBeSaved(t *testing.T) { FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"), }, BuilderConfig: &BuilderConfig{ - Enabled: true, - GasLimit: validator.Uint64(40000000), - Relays: []string{"https://example-relay.com"}, + Enabled: true, + GasLimit: validator.Uint64(40000000), + Relays: []string{"https://example-relay.com"}, + BuilderBoostFactor: &bbf, }, }, }, @@ -128,9 +136,10 @@ func TestProposerSettings_ShouldBeSaved(t *testing.T) { FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"), }, BuilderConfig: &BuilderConfig{ - Enabled: true, - GasLimit: validator.Uint64(40000000), - Relays: []string{"https://example-relay.com"}, + Enabled: true, + GasLimit: validator.Uint64(40000000), + Relays: []string{"https://example-relay.com"}, + BuilderBoostFactor: &bbf, }, }, }, @@ -145,9 +154,10 @@ func TestProposerSettings_ShouldBeSaved(t *testing.T) { FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"), }, BuilderConfig: &BuilderConfig{ - Enabled: true, - GasLimit: validator.Uint64(40000000), - Relays: []string{"https://example-relay.com"}, + Enabled: true, + GasLimit: validator.Uint64(40000000), + Relays: []string{"https://example-relay.com"}, + BuilderBoostFactor: &bbf, }, }, }, @@ -156,9 +166,10 @@ func TestProposerSettings_ShouldBeSaved(t *testing.T) { FeeRecipient: common.HexToAddress("0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3"), }, BuilderConfig: &BuilderConfig{ - Enabled: true, - GasLimit: validator.Uint64(40000000), - Relays: []string{"https://example-relay.com"}, + Enabled: true, + GasLimit: validator.Uint64(40000000), + Relays: []string{"https://example-relay.com"}, + BuilderBoostFactor: &bbf, }, }, }, @@ -179,9 +190,10 @@ func TestProposerSettings_ShouldBeSaved(t *testing.T) { ProposeConfig: nil, DefaultConfig: &Option{ BuilderConfig: &BuilderConfig{ - Enabled: true, - GasLimit: validator.Uint64(40000000), - Relays: []string{"https://example-relay.com"}, + Enabled: true, + GasLimit: validator.Uint64(40000000), + Relays: []string{"https://example-relay.com"}, + BuilderBoostFactor: &bbf, }, }, }, diff --git a/proto/prysm/v1alpha1/validator-client/keymanager.pb.go b/proto/prysm/v1alpha1/validator-client/keymanager.pb.go index 267ca72f5ab7..b6475d353ab9 100755 --- a/proto/prysm/v1alpha1/validator-client/keymanager.pb.go +++ b/proto/prysm/v1alpha1/validator-client/keymanager.pb.go @@ -16,7 +16,6 @@ import ( v1alpha1 "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" - _ "google.golang.org/protobuf/types/known/wrapperspb" ) const ( @@ -524,9 +523,10 @@ type BuilderConfig struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` - GasLimit github_com_prysmaticlabs_prysm_v5_consensus_types_validator.Uint64 `protobuf:"varint,2,opt,name=gas_limit,json=gasLimit,proto3" json:"gas_limit,omitempty" cast-type:"github.com/prysmaticlabs/prysm/v5/consensus-types/validator.Uint64"` - Relays []string `protobuf:"bytes,3,rep,name=relays,proto3" json:"relays,omitempty"` + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + GasLimit github_com_prysmaticlabs_prysm_v5_consensus_types_validator.Uint64 `protobuf:"varint,2,opt,name=gas_limit,json=gasLimit,proto3" json:"gas_limit,omitempty" cast-type:"github.com/prysmaticlabs/prysm/v5/consensus-types/validator.Uint64"` + Relays []string `protobuf:"bytes,3,rep,name=relays,proto3" json:"relays,omitempty"` + BuilderBoostFactor *uint64 `protobuf:"varint,4,opt,name=builder_boost_factor,json=builderBoostFactor,proto3,oneof" json:"builder_boost_factor,omitempty"` } func (x *BuilderConfig) Reset() { @@ -582,6 +582,13 @@ func (x *BuilderConfig) GetRelays() []string { return nil } +func (x *BuilderConfig) GetBuilderBoostFactor() uint64 { + if x != nil && x.BuilderBoostFactor != nil { + return *x.BuilderBoostFactor + } + return 0 +} + type ProposerSettingsPayload struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -645,9 +652,7 @@ var file_proto_prysm_v1alpha1_validator_client_keymanager_proto_rawDesc = []byte 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x6b, 0x65, 0x79, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x61, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x32, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, - 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x32, 0x1a, 0x1b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x65, 0x78, 0x74, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x26, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x61, 0x74, 0x74, 0x65, @@ -793,7 +798,7 @@ var file_proto_prysm_v1alpha1_validator_client_keymanager_proto_rawDesc = []byte 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x08, 0x67, 0x72, 0x61, 0x66, 0x66, 0x69, 0x74, 0x69, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x67, 0x72, 0x61, 0x66, 0x66, 0x69, 0x74, 0x69, 0x88, 0x01, 0x01, 0x42, 0x0b, - 0x0a, 0x09, 0x5f, 0x67, 0x72, 0x61, 0x66, 0x66, 0x69, 0x74, 0x69, 0x22, 0xa6, 0x01, 0x0a, 0x0d, + 0x0a, 0x09, 0x5f, 0x67, 0x72, 0x61, 0x66, 0x66, 0x69, 0x74, 0x69, 0x22, 0xf6, 0x01, 0x0a, 0x0d, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x63, 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, 0x6c, @@ -804,7 +809,12 @@ var file_proto_prysm_v1alpha1_validator_client_keymanager_proto_rawDesc = []byte 0x73, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x55, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x52, 0x08, 0x67, 0x61, 0x73, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, - 0x6c, 0x61, 0x79, 0x73, 0x22, 0xe7, 0x02, 0x0a, 0x17, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, + 0x6c, 0x61, 0x79, 0x73, 0x12, 0x35, 0x0a, 0x14, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x5f, + 0x62, 0x6f, 0x6f, 0x73, 0x74, 0x5f, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x04, 0x48, 0x00, 0x52, 0x12, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x42, 0x6f, 0x6f, + 0x73, 0x74, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x42, 0x17, 0x0a, 0x15, 0x5f, + 0x62, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x5f, 0x62, 0x6f, 0x6f, 0x73, 0x74, 0x5f, 0x66, 0x61, + 0x63, 0x74, 0x6f, 0x72, 0x22, 0xe7, 0x02, 0x0a, 0x17, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x50, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x74, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4b, 0x2e, 0x65, 0x74, 0x68, 0x65, @@ -994,6 +1004,7 @@ func file_proto_prysm_v1alpha1_validator_client_keymanager_proto_init() { (*SignRequest_BlindedBlockDeneb)(nil), } file_proto_prysm_v1alpha1_validator_client_keymanager_proto_msgTypes[2].OneofWrappers = []interface{}{} + file_proto_prysm_v1alpha1_validator_client_keymanager_proto_msgTypes[3].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/proto/prysm/v1alpha1/validator-client/keymanager.proto b/proto/prysm/v1alpha1/validator-client/keymanager.proto index 85500ceb840b..a4453bd1c161 100644 --- a/proto/prysm/v1alpha1/validator-client/keymanager.proto +++ b/proto/prysm/v1alpha1/validator-client/keymanager.proto @@ -1,7 +1,6 @@ syntax = "proto3"; package ethereum.validator.accounts.v2; -import "google/protobuf/wrappers.proto"; import "proto/eth/ext/options.proto"; import "proto/prysm/v1alpha1/attestation.proto"; import "proto/prysm/v1alpha1/beacon_block.proto"; @@ -96,6 +95,7 @@ message BuilderConfig { bool enabled = 1; uint64 gas_limit = 2 [(ethereum.eth.ext.cast_type) = "github.com/prysmaticlabs/prysm/v5/consensus-types/validator.Uint64"]; repeated string relays = 3; + optional uint64 builder_boost_factor = 4; } // ProposerSettingsPayload is used to unmarshal files sent from the validator flag as well as safe to bolt db bucket diff --git a/validator/client/BUILD.bazel b/validator/client/BUILD.bazel index bbcab6f56385..87e40dd15394 100644 --- a/validator/client/BUILD.bazel +++ b/validator/client/BUILD.bazel @@ -94,6 +94,7 @@ go_library( "@org_golang_google_grpc//status:go_default_library", "@org_golang_google_protobuf//proto:go_default_library", "@org_golang_google_protobuf//types/known/emptypb:go_default_library", + "@org_golang_google_protobuf//types/known/wrapperspb:go_default_library", ], ) @@ -176,6 +177,7 @@ go_test( "@org_golang_google_grpc//metadata:go_default_library", "@org_golang_google_grpc//status:go_default_library", "@org_golang_google_protobuf//types/known/emptypb:go_default_library", + "@org_golang_google_protobuf//types/known/wrapperspb:go_default_library", "@org_uber_go_mock//gomock:go_default_library", ], ) diff --git a/validator/client/beacon-api/BUILD.bazel b/validator/client/beacon-api/BUILD.bazel index dfce02f40667..3bed8b1030a9 100644 --- a/validator/client/beacon-api/BUILD.bazel +++ b/validator/client/beacon-api/BUILD.bazel @@ -66,6 +66,7 @@ go_library( "@com_github_sirupsen_logrus//:go_default_library", "@org_golang_google_grpc//:go_default_library", "@org_golang_google_protobuf//types/known/timestamppb:go_default_library", + "@org_golang_google_protobuf//types/known/wrapperspb:go_default_library", "@org_golang_x_sync//errgroup:go_default_library", ], ) @@ -139,6 +140,7 @@ go_test( "@com_github_pkg_errors//:go_default_library", "@org_golang_google_protobuf//types/known/emptypb:go_default_library", "@org_golang_google_protobuf//types/known/timestamppb:go_default_library", + "@org_golang_google_protobuf//types/known/wrapperspb:go_default_library", "@org_uber_go_mock//gomock:go_default_library", ], ) diff --git a/validator/client/beacon-api/beacon_api_validator_client.go b/validator/client/beacon-api/beacon_api_validator_client.go index 7e59bd0ab77d..fa277c5bc76a 100644 --- a/validator/client/beacon-api/beacon_api_validator_client.go +++ b/validator/client/beacon-api/beacon_api_validator_client.go @@ -77,7 +77,7 @@ func (c *beaconApiValidatorClient) GetAttestationData(ctx context.Context, in *e func (c *beaconApiValidatorClient) GetBeaconBlock(ctx context.Context, in *ethpb.BlockRequest) (*ethpb.GenericBeaconBlock, error) { return wrapInMetrics[*ethpb.GenericBeaconBlock]("GetBeaconBlock", func() (*ethpb.GenericBeaconBlock, error) { - return c.getBeaconBlock(ctx, in.Slot, in.RandaoReveal, in.Graffiti) + return c.getBeaconBlock(ctx, in.Slot, in.RandaoReveal, in.Graffiti, in.BuilderBoostFactor) }) } diff --git a/validator/client/beacon-api/get_beacon_block.go b/validator/client/beacon-api/get_beacon_block.go index 4e0b7fd20324..4a27ffa70d98 100644 --- a/validator/client/beacon-api/get_beacon_block.go +++ b/validator/client/beacon-api/get_beacon_block.go @@ -15,6 +15,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/network/httputil" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/runtime/version" + "google.golang.org/protobuf/types/known/wrapperspb" ) type abstractProduceBlockResponseJson struct { @@ -22,20 +23,26 @@ type abstractProduceBlockResponseJson struct { Data json.RawMessage `json:"data"` } -func (c beaconApiValidatorClient) getBeaconBlock(ctx context.Context, slot primitives.Slot, randaoReveal []byte, graffiti []byte) (*ethpb.GenericBeaconBlock, error) { +func buildGetBeaconBlockUrlValues(randaoReveal []byte, graffiti []byte, builderBoostFactor *wrapperspb.UInt64Value) neturl.Values { queryParams := neturl.Values{} + if builderBoostFactor != nil { + queryParams.Add("builder_boost_factor", fmt.Sprint(builderBoostFactor.Value)) + } queryParams.Add("randao_reveal", hexutil.Encode(randaoReveal)) if len(graffiti) > 0 { queryParams.Add("graffiti", hexutil.Encode(graffiti)) } + return queryParams +} +func (c beaconApiValidatorClient) getBeaconBlock(ctx context.Context, slot primitives.Slot, randaoReveal []byte, graffiti []byte, builderBoostFactor *wrapperspb.UInt64Value) (*ethpb.GenericBeaconBlock, error) { var ver string var blinded bool var decoder *json.Decoder // Try v3 endpoint first. If it's not supported, then we fall back to older endpoints. // We try the blinded block endpoint first. If it fails, we assume that we got a full block and try the full block endpoint. - queryUrl := buildURL(fmt.Sprintf("/eth/v3/validator/blocks/%d", slot), queryParams) + queryUrl := buildURL(fmt.Sprintf("/eth/v3/validator/blocks/%d", slot), buildGetBeaconBlockUrlValues(randaoReveal, graffiti, builderBoostFactor)) produceBlockV3ResponseJson := structs.ProduceBlockV3Response{} err := c.jsonRestHandler.Get(ctx, queryUrl, &produceBlockV3ResponseJson) errJson := &httputil.DefaultJsonError{} @@ -47,14 +54,14 @@ func (c beaconApiValidatorClient) getBeaconBlock(ctx context.Context, slot primi return nil, errJson } log.Debug("Endpoint /eth/v3/validator/blocks is not supported, falling back to older endpoints for block proposal.") - fallbackResp, err := c.fallBackToBlinded(ctx, slot, queryParams) + fallbackResp, err := c.fallBackToBlinded(ctx, slot, buildGetBeaconBlockUrlValues(randaoReveal, graffiti, nil)) errJson = &httputil.DefaultJsonError{} if err != nil { if !errors.As(err, &errJson) { return nil, err } log.Debug("Endpoint /eth/v1/validator/blinded_blocks failed to produce a blinded block, trying /eth/v2/validator/blocks.") - fallbackResp, err = c.fallBackToFull(ctx, slot, queryParams) + fallbackResp, err = c.fallBackToFull(ctx, slot, buildGetBeaconBlockUrlValues(randaoReveal, graffiti, nil)) if err != nil { return nil, err } diff --git a/validator/client/beacon-api/get_beacon_block_test.go b/validator/client/beacon-api/get_beacon_block_test.go index cce3e2ae3c1e..d267956629e0 100644 --- a/validator/client/beacon-api/get_beacon_block_test.go +++ b/validator/client/beacon-api/get_beacon_block_test.go @@ -18,6 +18,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/validator/client/beacon-api/mock" test_helpers "github.com/prysmaticlabs/prysm/v5/validator/client/beacon-api/test-helpers" "go.uber.org/mock/gomock" + "google.golang.org/protobuf/types/known/wrapperspb" ) func TestGetBeaconBlock_RequestFailed(t *testing.T) { @@ -36,7 +37,7 @@ func TestGetBeaconBlock_RequestFailed(t *testing.T) { ).Times(1) validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - _, err := validatorClient.getBeaconBlock(ctx, 1, []byte{1}, []byte{2}) + _, err := validatorClient.getBeaconBlock(ctx, 1, []byte{1}, []byte{2}, nil) assert.ErrorContains(t, "foo error", err) } @@ -139,7 +140,7 @@ func TestGetBeaconBlock_Error(t *testing.T) { ).Times(1) validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - _, err := validatorClient.getBeaconBlock(ctx, 1, []byte{1}, []byte{2}) + _, err := validatorClient.getBeaconBlock(ctx, 1, []byte{1}, []byte{2}, nil) assert.ErrorContains(t, testCase.expectedErrorMessage, err) }) } @@ -175,7 +176,7 @@ func TestGetBeaconBlock_Phase0Valid(t *testing.T) { ).Times(1) validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti) + beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti, nil) require.NoError(t, err) expectedBeaconBlock := ðpb.GenericBeaconBlock{ @@ -218,7 +219,7 @@ func TestGetBeaconBlock_AltairValid(t *testing.T) { ).Times(1) validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti) + beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti, nil) require.NoError(t, err) expectedBeaconBlock := ðpb.GenericBeaconBlock{ @@ -262,7 +263,7 @@ func TestGetBeaconBlock_BellatrixValid(t *testing.T) { ).Times(1) validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti) + beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti, nil) require.NoError(t, err) expectedBeaconBlock := ðpb.GenericBeaconBlock{ @@ -307,7 +308,7 @@ func TestGetBeaconBlock_BlindedBellatrixValid(t *testing.T) { ).Times(1) validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti) + beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti, nil) require.NoError(t, err) expectedBeaconBlock := ðpb.GenericBeaconBlock{ @@ -352,7 +353,7 @@ func TestGetBeaconBlock_CapellaValid(t *testing.T) { ).Times(1) validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti) + beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti, nil) require.NoError(t, err) expectedBeaconBlock := ðpb.GenericBeaconBlock{ @@ -397,7 +398,7 @@ func TestGetBeaconBlock_BlindedCapellaValid(t *testing.T) { ).Times(1) validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti) + beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti, nil) require.NoError(t, err) expectedBeaconBlock := ðpb.GenericBeaconBlock{ @@ -442,7 +443,7 @@ func TestGetBeaconBlock_DenebValid(t *testing.T) { ).Times(1) validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti) + beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti, nil) require.NoError(t, err) expectedBeaconBlock := ðpb.GenericBeaconBlock{ @@ -473,7 +474,7 @@ func TestGetBeaconBlock_BlindedDenebValid(t *testing.T) { jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) jsonRestHandler.EXPECT().Get( ctx, - fmt.Sprintf("/eth/v3/validator/blocks/%d?graffiti=%s&randao_reveal=%s", slot, hexutil.Encode(graffiti), hexutil.Encode(randaoReveal)), + fmt.Sprintf("/eth/v3/validator/blocks/%d?builder_boost_factor=%s&graffiti=%s&randao_reveal=%s", slot, "100", hexutil.Encode(graffiti), hexutil.Encode(randaoReveal)), &structs.ProduceBlockV3Response{}, ).SetArg( 2, @@ -487,7 +488,7 @@ func TestGetBeaconBlock_BlindedDenebValid(t *testing.T) { ).Times(1) validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti) + beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti, &wrapperspb.UInt64Value{Value: 100}) require.NoError(t, err) expectedBeaconBlock := ðpb.GenericBeaconBlock{ @@ -518,7 +519,7 @@ func TestGetBeaconBlock_FallbackToBlindedBlock(t *testing.T) { jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) jsonRestHandler.EXPECT().Get( ctx, - fmt.Sprintf("/eth/v3/validator/blocks/%d?graffiti=%s&randao_reveal=%s", slot, hexutil.Encode(graffiti), hexutil.Encode(randaoReveal)), + fmt.Sprintf("/eth/v3/validator/blocks/%d?builder_boost_factor=%s&graffiti=%s&randao_reveal=%s", slot, "100", hexutil.Encode(graffiti), hexutil.Encode(randaoReveal)), &structs.ProduceBlockV3Response{}, ).Return( &httputil.DefaultJsonError{Code: http.StatusNotFound}, @@ -538,7 +539,7 @@ func TestGetBeaconBlock_FallbackToBlindedBlock(t *testing.T) { ).Times(1) validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti) + beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti, &wrapperspb.UInt64Value{Value: 100}) require.NoError(t, err) expectedBeaconBlock := ðpb.GenericBeaconBlock{ @@ -569,7 +570,7 @@ func TestGetBeaconBlock_FallbackToFullBlock(t *testing.T) { jsonRestHandler := mock.NewMockJsonRestHandler(ctrl) jsonRestHandler.EXPECT().Get( ctx, - fmt.Sprintf("/eth/v3/validator/blocks/%d?graffiti=%s&randao_reveal=%s", slot, hexutil.Encode(graffiti), hexutil.Encode(randaoReveal)), + fmt.Sprintf("/eth/v3/validator/blocks/%d?builder_boost_factor=%s&graffiti=%s&randao_reveal=%s", slot, "100", hexutil.Encode(graffiti), hexutil.Encode(randaoReveal)), &structs.ProduceBlockV3Response{}, ).Return( &httputil.DefaultJsonError{Code: http.StatusNotFound}, @@ -596,7 +597,7 @@ func TestGetBeaconBlock_FallbackToFullBlock(t *testing.T) { ).Times(1) validatorClient := &beaconApiValidatorClient{jsonRestHandler: jsonRestHandler} - beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti) + beaconBlock, err := validatorClient.getBeaconBlock(ctx, slot, randaoReveal, graffiti, &wrapperspb.UInt64Value{Value: 100}) require.NoError(t, err) expectedBeaconBlock := ðpb.GenericBeaconBlock{ diff --git a/validator/client/propose.go b/validator/client/propose.go index 12779137f0ad..756f7844443e 100644 --- a/validator/client/propose.go +++ b/validator/client/propose.go @@ -28,6 +28,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/validator/client/iface" "github.com/sirupsen/logrus" "go.opencensus.io/trace" + "google.golang.org/protobuf/types/known/wrapperspb" ) const ( @@ -79,9 +80,10 @@ func (v *validator) ProposeBlock(ctx context.Context, slot primitives.Slot, pubK // Request block from beacon node b, err := v.validatorClient.GetBeaconBlock(ctx, ðpb.BlockRequest{ - Slot: slot, - RandaoReveal: randaoReveal, - Graffiti: g, + Slot: slot, + RandaoReveal: randaoReveal, + Graffiti: g, + BuilderBoostFactor: findBuilderBoost(pubKey, v.proposerSettings), }) if err != nil { log.WithField("slot", slot).WithError(err).Error("Failed to request block from beacon node") @@ -225,6 +227,31 @@ func (v *validator) ProposeBlock(ctx context.Context, slot primitives.Slot, pubK } } +func findBuilderBoost(pubKey [fieldparams.BLSPubkeyLength]byte, proposerSettings *proposer.Settings) *wrapperspb.UInt64Value { + if proposerSettings == nil { + return nil + } + + if proposerSettings.ProposeConfig != nil { + option, ok := proposerSettings.ProposeConfig[pubKey] + if ok && option.BuilderConfig != nil && option.BuilderConfig.BuilderBoostFactor != nil { + return &wrapperspb.UInt64Value{ + Value: *option.BuilderConfig.BuilderBoostFactor, + } + } + } + + if proposerSettings.DefaultConfig != nil && + proposerSettings.DefaultConfig.BuilderConfig != nil && + proposerSettings.DefaultConfig.BuilderConfig.BuilderBoostFactor != nil { + return &wrapperspb.UInt64Value{ + Value: *proposerSettings.DefaultConfig.BuilderConfig.BuilderBoostFactor, + } + } + + return nil +} + // ProposeExit performs a voluntary exit on a validator. // The exit is signed by the validator before being sent to the beacon node for broadcasting. func ProposeExit( diff --git a/validator/client/propose_test.go b/validator/client/propose_test.go index 463c1b1ccd18..8449be89da06 100644 --- a/validator/client/propose_test.go +++ b/validator/client/propose_test.go @@ -5,9 +5,11 @@ import ( "encoding/hex" "errors" "fmt" + "reflect" "strings" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing" lruwrpr "github.com/prysmaticlabs/prysm/v5/cache/lru" @@ -31,6 +33,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/validator/graffiti" logTest "github.com/sirupsen/logrus/hooks/test" "go.uber.org/mock/gomock" + "google.golang.org/protobuf/types/known/wrapperspb" ) type mocks struct { @@ -1260,3 +1263,156 @@ func Test_validator_SetGraffiti(t *testing.T) { }) } } + +func Test_findBuilderBoost(t *testing.T) { + pubKey := [fieldparams.BLSPubkeyLength]byte{'a'} + + type args struct { + proposerSettings *proposer.Settings + } + tests := []struct { + name string + args args + want *wrapperspb.UInt64Value + }{ + { + name: "no proposer settings", + args: args{ + proposerSettings: nil, + }, + want: nil, + }, + { + name: "Proposer settings without builder settings", + args: args{ + proposerSettings: &proposer.Settings{ + ProposeConfig: func() map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option { + config := make(map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option) + config[pubKey] = &proposer.Option{ + FeeRecipientConfig: &proposer.FeeRecipientConfig{ + FeeRecipient: common.HexToAddress("a"), + }, + } + return config + }(), + }, + }, + want: nil, + }, + { + name: "Proposer settings with builder settings but without builder boost factor", + args: args{ + proposerSettings: &proposer.Settings{ + ProposeConfig: func() map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option { + config := make(map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option) + config[pubKey] = &proposer.Option{ + FeeRecipientConfig: &proposer.FeeRecipientConfig{ + FeeRecipient: common.HexToAddress("a"), + }, + BuilderConfig: &proposer.BuilderConfig{ + Enabled: true, + }, + } + return config + }(), + }, + }, + want: nil, + }, + { + name: "Proposer settings with builder settings and specific propose config", + args: args{ + proposerSettings: &proposer.Settings{ + ProposeConfig: func() map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option { + config := make(map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option) + bb := uint64(123) + config[pubKey] = &proposer.Option{ + FeeRecipientConfig: &proposer.FeeRecipientConfig{ + FeeRecipient: common.HexToAddress("a"), + }, + BuilderConfig: &proposer.BuilderConfig{ + Enabled: true, + BuilderBoostFactor: &bb, + }, + } + return config + }(), + }, + }, + want: &wrapperspb.UInt64Value{ + Value: 123, + }, + }, + { + name: "Proposer settings with builder settings and specific propose config but wrong pubkey", + args: args{ + proposerSettings: &proposer.Settings{ + ProposeConfig: func() map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option { + config := make(map[[fieldparams.BLSPubkeyLength]byte]*proposer.Option) + bb := uint64(123) + config[[fieldparams.BLSPubkeyLength]byte{'z'}] = &proposer.Option{ + FeeRecipientConfig: &proposer.FeeRecipientConfig{ + FeeRecipient: common.HexToAddress("a"), + }, + BuilderConfig: &proposer.BuilderConfig{ + Enabled: true, + BuilderBoostFactor: &bb, + }, + } + return config + }(), + }, + }, + want: nil, + }, + { + name: "Proposer settings with builder settings and default config", + args: args{ + proposerSettings: &proposer.Settings{ + DefaultConfig: func() *proposer.Option { + bb := uint64(123) + return &proposer.Option{ + FeeRecipientConfig: &proposer.FeeRecipientConfig{ + FeeRecipient: common.HexToAddress("a"), + }, + BuilderConfig: &proposer.BuilderConfig{ + Enabled: true, + BuilderBoostFactor: &bb, + }, + } + }(), + }, + }, + want: &wrapperspb.UInt64Value{ + Value: 123, + }, + }, + { + name: "Proposer settings with nil boost settings", + args: args{ + proposerSettings: &proposer.Settings{ + DefaultConfig: func() *proposer.Option { + var bb *uint64 + return &proposer.Option{ + FeeRecipientConfig: &proposer.FeeRecipientConfig{ + FeeRecipient: common.HexToAddress("a"), + }, + BuilderConfig: &proposer.BuilderConfig{ + Enabled: true, + BuilderBoostFactor: bb, + }, + } + }(), + }, + }, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := findBuilderBoost(pubKey, tt.args.proposerSettings); !reflect.DeepEqual(got, tt.want) { + t.Errorf("findBuilderBoost() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/validator/node/node.go b/validator/node/node.go index 67d92e32bf63..bfa9f3647e76 100644 --- a/validator/node/node.go +++ b/validator/node/node.go @@ -613,6 +613,7 @@ func proposerSettings(cliCtx *cli.Context, db iface.ValidatorDB) (*proposer.Sett db, loader.WithBuilderConfig(), loader.WithGasLimit(), + loader.WithBuilderBoostFactor(), ) if err != nil { return nil, err