diff --git a/rocketpool-cli/service/config/settings-builder.go b/rocketpool-cli/service/config/settings-builder.go new file mode 100644 index 000000000..b3a281ae8 --- /dev/null +++ b/rocketpool-cli/service/config/settings-builder.go @@ -0,0 +1,52 @@ +package config + +import ( + cfgtypes "github.com/rocket-pool/smartnode/shared/types/config" +) + +type BuilderConfigPage struct { + epbsPage *EpbsPage + page *page + layout *standardLayout + items map[*cfgtypes.Parameter]*parameterizedFormItem +} + +func NewBuilderConfigPage(epbsPage *EpbsPage) *BuilderConfigPage { + configPage := &BuilderConfigPage{ + epbsPage: epbsPage, + layout: newStandardLayout(), + items: map[*cfgtypes.Parameter]*parameterizedFormItem{}, + } + cfg := epbsPage.home.md.Config + configPage.layout.createForm(&cfg.Smartnode.Network, "Builder Settings") + configPage.layout.setupEscapeReturnHomeHandler(epbsPage.home.md, epbsPage.page) + items := createParameterizedFormItems(cfg.Epbs.GetParameters(), configPage.layout) + configPage.layout.mapParameterizedFormItems(items...) + for _, item := range items { + configPage.items[item.parameter] = item + } + configPage.page = newPage( + epbsPage.page, + "settings-builder", + "Builder Settings", + "Set the minimum bid and maximum execution payment in ETH, and select builders for the ePBS builder list. These settings are shared by all consensus clients.", + configPage.layout.grid, + ) + configPage.handleLayoutChanged() + return configPage +} + +func (configPage *BuilderConfigPage) getPage() *page { + return configPage.page +} + +func (configPage *BuilderConfigPage) handleLayoutChanged() { + cfg := configPage.epbsPage.home.md.Config.Epbs + configPage.layout.form.Clear(true) + configPage.layout.form.AddFormItem(configPage.items[&cfg.MinBid].item) + configPage.layout.form.AddFormItem(configPage.items[&cfg.MaxExecutionPayment].item) + for _, param := range cfg.GetAvailableBuilderParameters() { + configPage.layout.form.AddFormItem(configPage.items[param].item) + } + configPage.layout.refresh() +} diff --git a/rocketpool-cli/service/config/settings-commit-boost.go b/rocketpool-cli/service/config/settings-commit-boost.go index 47b202177..e6fbbfc08 100644 --- a/rocketpool-cli/service/config/settings-commit-boost.go +++ b/rocketpool-cli/service/config/settings-commit-boost.go @@ -9,7 +9,7 @@ import ( // The page wrapper for the Commit-boost config type CommitBoostConfigPage struct { - home *settingsHome + epbsPage *EpbsPage page *page layout *standardLayout masterConfig *config.RocketPoolConfig @@ -26,16 +26,16 @@ type CommitBoostConfigPage struct { } // Creates a new page for the Commit-Boost settings -func NewCommitBoostConfigPage(home *settingsHome) *CommitBoostConfigPage { +func NewCommitBoostConfigPage(epbsPage *EpbsPage) *CommitBoostConfigPage { configPage := &CommitBoostConfigPage{ - home: home, - masterConfig: home.md.Config, + epbsPage: epbsPage, + masterConfig: epbsPage.home.md.Config, } configPage.createContent() configPage.page = newPage( - home.homePage, + epbsPage.page, "settings-commit-boost", "Commit-Boost", "Select this to configure the settings for the Commit-Boost PBS client, the source of blocks with MEV rewards for your validators.\n\nCommit-Boost is a powerful PBS client that offers detailed reporting and transparency around block building, very low latency, and a responsive development team.\n\n", @@ -57,7 +57,7 @@ func (configPage *CommitBoostConfigPage) createContent() { // Create the layout configPage.layout = newStandardLayout() configPage.layout.createForm(&configPage.masterConfig.Smartnode.Network, "Commit-Boost Settings") - configPage.layout.setupEscapeReturnHomeHandler(configPage.home.md, configPage.home.homePage) + configPage.layout.setupEscapeReturnHomeHandler(configPage.epbsPage.home.md, configPage.epbsPage.page) // Set up the form items configPage.enableBox = createParameterizedCheckbox(&configPage.masterConfig.EnableCommitBoost) diff --git a/rocketpool-cli/service/config/settings-epbs.go b/rocketpool-cli/service/config/settings-epbs.go new file mode 100644 index 000000000..077655b63 --- /dev/null +++ b/rocketpool-cli/service/config/settings-epbs.go @@ -0,0 +1,81 @@ +package config + +import ( + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" +) + +// EpbsPage groups shared builder settings and the existing PBS clients. +type EpbsPage struct { + home *settingsHome + page *page + layout *standardLayout + categoryList *tview.List + builderPage *BuilderConfigPage + mevBoostPage *MevBoostConfigPage + commitBoostPage *CommitBoostConfigPage + subpages []settingsPage +} + +func NewEpbsPage(home *settingsHome) *EpbsPage { + epbsPage := &EpbsPage{home: home} + epbsPage.page = newPage( + home.homePage, + "settings-epbs", + "ePBS", + "Configure builder settings shared by all consensus clients, and manage MEV-Boost and Commit-Boost.", + nil, + ) + epbsPage.builderPage = NewBuilderConfigPage(epbsPage) + epbsPage.mevBoostPage = NewMevBoostConfigPage(epbsPage) + epbsPage.commitBoostPage = NewCommitBoostConfigPage(epbsPage) + epbsPage.subpages = []settingsPage{ + epbsPage.builderPage, + epbsPage.mevBoostPage, + epbsPage.commitBoostPage, + } + for _, subpage := range epbsPage.subpages { + home.md.pages.AddPage(subpage.getPage().id, subpage.getPage().content, true, false) + } + epbsPage.createContent() + epbsPage.page.content = epbsPage.layout.grid + return epbsPage +} + +func (epbsPage *EpbsPage) getPage() *page { + return epbsPage.page +} + +func (epbsPage *EpbsPage) createContent() { + epbsPage.layout = newStandardLayout() + epbsPage.layout.createSettingFooter() + list := tview.NewList().SetChangedFunc(func(index int, mainText, secondaryText string, shortcut rune) { + epbsPage.layout.descriptionBox.SetText(epbsPage.subpages[index].getPage().description) + }) + list.SetBackgroundColor(tview.Styles.ContrastBackgroundColor) + list.SetBorderPadding(0, 0, 1, 1) + list.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyEsc { + epbsPage.home.md.setPage(epbsPage.home.homePage) + return nil + } + return event + }) + for _, subpage := range epbsPage.subpages { + list.AddItem(subpage.getPage().title, "", 0, nil) + } + list.SetSelectedFunc(func(index int, mainText, secondaryText string, shortcut rune) { + subpage := epbsPage.subpages[index] + subpage.handleLayoutChanged() + epbsPage.home.md.setPage(subpage.getPage()) + }) + epbsPage.categoryList = list + epbsPage.layout.setContent(list, list.Box, "ePBS Settings") + epbsPage.layout.descriptionBox.SetText(epbsPage.subpages[0].getPage().description) +} + +func (epbsPage *EpbsPage) handleLayoutChanged() { + for _, subpage := range epbsPage.subpages { + subpage.handleLayoutChanged() + } +} diff --git a/rocketpool-cli/service/config/settings-home.go b/rocketpool-cli/service/config/settings-home.go index 4d8a81049..e47b16cd7 100644 --- a/rocketpool-cli/service/config/settings-home.go +++ b/rocketpool-cli/service/config/settings-home.go @@ -19,8 +19,7 @@ type settingsHome struct { ecPage *ExecutionConfigPage fallbackPage *FallbackConfigPage ccPage *ConsensusConfigPage - mevBoostPage *MevBoostConfigPage - commitBoostPage *CommitBoostConfigPage + epbsPage *EpbsPage metricsPage *MetricsConfigPage alertingPage *AlertingConfigPage addonsPage *AddonsPage @@ -47,8 +46,7 @@ func newSettingsHome(md *MainDisplay) *settingsHome { home.ecPage = NewExecutionConfigPage(home) home.ccPage = NewConsensusConfigPage(home) home.fallbackPage = NewFallbackConfigPage(home) - home.mevBoostPage = NewMevBoostConfigPage(home) - home.commitBoostPage = NewCommitBoostConfigPage(home) + home.epbsPage = NewEpbsPage(home) home.metricsPage = NewMetricsConfigPage(home) home.alertingPage = NewAlertingConfigPage(home) home.addonsPage = NewAddonsPage(home) @@ -58,8 +56,7 @@ func newSettingsHome(md *MainDisplay) *settingsHome { home.ecPage, home.ccPage, home.fallbackPage, - home.mevBoostPage, - home.commitBoostPage, + home.epbsPage, home.metricsPage, home.alertingPage, home.addonsPage, @@ -246,12 +243,8 @@ func (home *settingsHome) refresh() { home.fallbackPage.layout.refresh() } - if home.mevBoostPage != nil { - home.mevBoostPage.layout.refresh() - } - - if home.commitBoostPage != nil { - home.commitBoostPage.layout.refresh() + if home.epbsPage != nil { + home.epbsPage.handleLayoutChanged() } if home.metricsPage != nil { diff --git a/rocketpool-cli/service/config/settings-mev-boost.go b/rocketpool-cli/service/config/settings-mev-boost.go index 085ea9519..e249af8a6 100644 --- a/rocketpool-cli/service/config/settings-mev-boost.go +++ b/rocketpool-cli/service/config/settings-mev-boost.go @@ -9,7 +9,7 @@ import ( // The page wrapper for the MEV-boost config type MevBoostConfigPage struct { - home *settingsHome + epbsPage *EpbsPage page *page layout *standardLayout masterConfig *config.RocketPoolConfig @@ -31,16 +31,16 @@ type MevBoostConfigPage struct { } // Creates a new page for the MEV-Boost settings -func NewMevBoostConfigPage(home *settingsHome) *MevBoostConfigPage { +func NewMevBoostConfigPage(epbsPage *EpbsPage) *MevBoostConfigPage { configPage := &MevBoostConfigPage{ - home: home, - masterConfig: home.md.Config, + epbsPage: epbsPage, + masterConfig: epbsPage.home.md.Config, } configPage.createContent() configPage.page = newPage( - home.homePage, + epbsPage.page, "settings-mev-boost", "MEV-Boost", "Select this to configure the settings for the Flashbots MEV-Boost client, the source of blocks with MEV rewards for your minipools.\n\nFor more information on Flashbots, MEV, and MEV-Boost, please see https://writings.flashbots.net/writings/why-run-mevboost/", @@ -62,7 +62,7 @@ func (configPage *MevBoostConfigPage) createContent() { // Create the layout configPage.layout = newStandardLayout() configPage.layout.createForm(&configPage.masterConfig.Smartnode.Network, "MEV-Boost Settings") - configPage.layout.setupEscapeReturnHomeHandler(configPage.home.md, configPage.home.homePage) + configPage.layout.setupEscapeReturnHomeHandler(configPage.epbsPage.home.md, configPage.epbsPage.page) // Set up the form items configPage.enableBox = createParameterizedCheckbox(&configPage.masterConfig.EnableMevBoost) diff --git a/rocketpool-cli/service/config/standard-layout.go b/rocketpool-cli/service/config/standard-layout.go index 4a63e6629..2e8d5e653 100644 --- a/rocketpool-cli/service/config/standard-layout.go +++ b/rocketpool-cli/service/config/standard-layout.go @@ -239,7 +239,7 @@ func (layout *standardLayout) createSettingFooter() { panic(fmt.Errorf("error writing nav string 1: %w", err)) } - navString2 := "Esc: Go Back to Categories (saves changes)" + navString2 := "Esc: Go Back (saves changes)" navTextView2 := tview.NewTextView(). SetDynamicColors(false). SetRegions(false). diff --git a/shared/services/config/epbs-config.go b/shared/services/config/epbs-config.go new file mode 100644 index 000000000..9c98ea02c --- /dev/null +++ b/shared/services/config/epbs-config.go @@ -0,0 +1,188 @@ +package config + +import ( + "fmt" + "net/url" + "regexp" + "strconv" + "strings" + + "github.com/rocket-pool/smartnode/shared/types/config" +) + +var builderAmountPattern = regexp.MustCompile(`^[0-9]+(\.[0-9]+)?$`) + +// EpbsConfig stores builder settings shared by all consensus clients. +type EpbsConfig struct { + Title string `yaml:"-"` + + MinBid config.Parameter `yaml:"minBid,omitempty"` + MaxExecutionPayment config.Parameter `yaml:"maxExecutionPayment,omitempty"` + Aestus config.Parameter `yaml:"aestusEnabled,omitempty"` + Flashbots config.Parameter `yaml:"flashbotsEnabled,omitempty"` + Nflaig config.Parameter `yaml:"nflaigEnabled,omitempty"` + Titan config.Parameter `yaml:"titanEnabled,omitempty"` + Ultrasound config.Parameter `yaml:"ultrasoundEnabled,omitempty"` + Pk910 config.Parameter `yaml:"pk910Enabled,omitempty"` + + parentConfig *RocketPoolConfig `yaml:"-"` +} + +func NewEpbsConfig(cfg *RocketPoolConfig) *EpbsConfig { + epbs := &EpbsConfig{ + Title: "Builder Settings", + parentConfig: cfg, + MinBid: config.Parameter{ + ID: "minBid", + Name: "Minimum Bid (ETH)", + Description: "The minimum payment to accept from an ePBS builder, in ETH. For example, enter 0.05 for 0.05 ETH. Leave blank to use the consensus client's default.", + Type: config.ParameterType_String, + Default: map[config.Network]interface{}{config.Network_All: ""}, + AffectsContainers: []config.ContainerID{config.ContainerID_Validator}, + CanBeBlank: true, + OverwriteOnUpgrade: false, + }, + MaxExecutionPayment: config.Parameter{ + ID: "maxExecutionPayment", + Name: "Max Execution Payment (ETH)", + Description: "The maximum execution-layer payment to count toward an ePBS builder bid, in ETH. These payments rely on the builder's promise and are not backed by staked collateral. Set to 0 to count only trustless payments. For example, enter 0.05 to count up to 0.05 ETH of promised execution-layer payments.", + Type: config.ParameterType_String, + Default: map[config.Network]interface{}{config.Network_All: "0"}, + AffectsContainers: []config.ContainerID{config.ContainerID_Validator}, + CanBeBlank: false, + OverwriteOnUpgrade: false, + }, + } + for _, option := range epbs.builderOptions() { + *option.parameter = config.Parameter{ + ID: option.id, + Name: fmt.Sprintf("Enable %s", option.name), + Description: fmt.Sprintf("Select %s for the ePBS builder list. You can select multiple operators.\n\n%s", option.name, option.url), + Type: config.ParameterType_Bool, + Default: map[config.Network]interface{}{config.Network_All: false}, + AffectsContainers: []config.ContainerID{config.ContainerID_Validator}, + CanBeBlank: false, + OverwriteOnUpgrade: false, + } + } + return epbs +} + +type epbsBuilderOption struct { + id string + name string + url string + parameter *config.Parameter +} + +func (cfg *EpbsConfig) builderOptions() []epbsBuilderOption { + return []epbsBuilderOption{ + {"aestusEnabled", "Aestus", "https://hoodi.aestus.live", &cfg.Aestus}, + {"flashbotsEnabled", "Flashbots", "https://boost-relay-hoodi.flashbots.net", &cfg.Flashbots}, + {"nflaigEnabled", "nflaig", "https://builder-hoodi.nflaig.dev", &cfg.Nflaig}, + {"titanEnabled", "Titan", "https://hoodi.titanrelay.xyz", &cfg.Titan}, + {"ultrasoundEnabled", "Ultra Sound", "https://relay-hoodi.ultrasound.money", &cfg.Ultrasound}, + {"pk910Enabled", "pk910", "https://builder-hoodi.pk910.de", &cfg.Pk910}, + } +} + +func (cfg *EpbsConfig) GetParameters() []*config.Parameter { + params := []*config.Parameter{&cfg.MinBid, &cfg.MaxExecutionPayment} + for _, option := range cfg.builderOptions() { + params = append(params, option.parameter) + } + return params +} + +func (cfg *EpbsConfig) GetConfigTitle() string { + return cfg.Title +} + +func (cfg *EpbsConfig) hoodiBuildersAvailable() bool { + info := cfg.parentConfig.GetNetworkInfo() + return info != nil && info.BeaconNetwork == "hoodi" +} + +// GetAvailableBuilderParameters returns the Hoodi ePBS builder catalog. +func (cfg *EpbsConfig) GetAvailableBuilderParameters() []*config.Parameter { + if !cfg.hoodiBuildersAvailable() { + return nil + } + params := []*config.Parameter{} + for _, option := range cfg.builderOptions() { + params = append(params, option.parameter) + } + return params +} + +// GetBuilderUrls returns the selected endpoints in catalog order as scheme://host. +func (cfg *EpbsConfig) GetBuilderUrls() ([]string, error) { + if !cfg.hoodiBuildersAvailable() { + return nil, nil + } + urls := []string{} + seen := map[string]bool{} + for _, option := range cfg.builderOptions() { + if option.parameter.Value != true { + continue + } + endpoint, err := url.Parse(option.url) + if err != nil || (endpoint.Scheme != "https" && endpoint.Scheme != "http") || endpoint.Hostname() == "" { + return nil, fmt.Errorf("ePBS builder endpoint for %s must be an HTTP or HTTPS URL", option.name) + } + builderURL := (&url.URL{ + Scheme: endpoint.Scheme, + Host: endpoint.Host, + }).String() + if !seen[builderURL] { + urls = append(urls, builderURL) + seen[builderURL] = true + } + } + return urls, nil +} + +// GetBuilderUrlString formats the selected endpoints for client CLI arguments. +func (cfg *EpbsConfig) GetBuilderUrlString() (string, error) { + urls, err := cfg.GetBuilderUrls() + if err != nil { + return "", err + } + return strings.Join(urls, ","), nil +} + +// GetMinBidGwei converts the ETH setting exactly, without floating point or +// rounding. A blank value lets client integrations retain their own defaults. +func (cfg *EpbsConfig) GetMinBidGwei() (string, error) { + return builderAmountToGwei(cfg.MinBid.Value.(string), cfg.MinBid.Name) +} + +// GetMaxExecutionPaymentGwei converts the ETH cap exactly. Blank input uses +// the default of zero, so only trustless payments count toward a builder bid. +func (cfg *EpbsConfig) GetMaxExecutionPaymentGwei() (string, error) { + gwei, err := builderAmountToGwei(cfg.MaxExecutionPayment.Value.(string), cfg.MaxExecutionPayment.Name) + if err == nil && gwei == "" { + return "0", nil + } + return gwei, err +} + +func builderAmountToGwei(value, name string) (string, error) { + value = strings.TrimSpace(value) + if value == "" { + return "", nil + } + if !builderAmountPattern.MatchString(value) { + return "", fmt.Errorf("ePBS %s must be a non-negative decimal amount, for example 0.05", name) + } + whole, fraction, _ := strings.Cut(value, ".") + fraction = strings.TrimRight(fraction, "0") + if len(fraction) > 9 { + return "", fmt.Errorf("ePBS %s must not have more than 9 decimal places", name) + } + gwei, err := strconv.ParseUint(whole+fraction+strings.Repeat("0", 9-len(fraction)), 10, 64) + if err != nil { + return "", fmt.Errorf("ePBS %s must not exceed 18446744073.709551615 ETH", name) + } + return strconv.FormatUint(gwei, 10), nil +} diff --git a/shared/services/config/migration/migration-manager.go b/shared/services/config/migration/migration-manager.go index 13789d1cf..dda9dec47 100644 --- a/shared/services/config/migration/migration-manager.go +++ b/shared/services/config/migration/migration-manager.go @@ -53,6 +53,11 @@ func UpdateConfig(serializedConfig map[string]map[string]string) error { if err != nil { return err } + // Include development configs saved by this release before ePBS was added. + v1245, err := parseVersion("1.24.5") + if err != nil { + return err + } // Create the collection of upgraders upgraders := []ConfigUpgrader{ @@ -85,6 +90,10 @@ func UpdateConfig(serializedConfig map[string]map[string]string) error { Version: v1210, UpgradeFunc: upgradeFromV1210, }, + { + Version: v1245, + UpgradeFunc: upgradeFromV1245, + }, } // Find the index of the provided config's version @@ -92,6 +101,7 @@ func UpdateConfig(serializedConfig map[string]map[string]string) error { for i, upgrader := range upgraders { if configVersion.LessThanOrEqual(upgrader.Version) { targetIndex = i + break } } diff --git a/shared/services/config/migration/v1245-manager.go b/shared/services/config/migration/v1245-manager.go new file mode 100644 index 000000000..f655c68ee --- /dev/null +++ b/shared/services/config/migration/v1245-manager.go @@ -0,0 +1,73 @@ +package migration + +import "strconv" + +// Preserve overlapping MEV-Boost / Commit-Boost operator selections at the time +// ePBS settings were introduced. +var epbsBuilderMigrations = []struct { + builderKey string + mevRelayKeys []string + mevProfileKeys []string + commitBoostKeys []string +}{ + {"aestusEnabled", []string{"aestusEnabled"}, []string{"enableUnregulatedAllMev"}, nil}, + {"flashbotsEnabled", []string{"flashbotsEnabled"}, []string{"enableRegulatedAllMev"}, []string{"cbFlashbotsEnabled"}}, + {"nflaigEnabled", nil, nil, nil}, + {"titanEnabled", []string{"titanGlobalEnabled", "titanRegionalEnabled"}, []string{"enableRegulatedAllMev", "enableUnregulatedAllMev"}, []string{"cbTitanRegionalEnabled"}}, + {"ultrasoundEnabled", []string{"ultrasoundEnabled"}, []string{"enableUnregulatedAllMev"}, nil}, + {"pk910Enabled", nil, nil, nil}, +} + +func upgradeFromV1245(serializedConfig map[string]map[string]string) error { + // An existing section belongs to the user, including blank bids and disabled + // builders. Never re-seed it from settings they may have since changed. + if serializedConfig["epbs"] != nil { + return nil + } + + root := serializedConfig["root"] + mev := serializedConfig["mevBoost"] + cb := serializedConfig["commitBoostConfig"] + + // Missing settings use the same defaults as the legacy configuration. Match + // the existing PBS URL precedence if both services were enabled. + mevEnabled, exists := root["enableMevBoost"] + useMev := !exists || isTrue(mevEnabled) + useCb := !useMev && isTrue(root["enableCommitBoost"]) + useMev = useMev && (mev["mode"] == "" || mev["mode"] == "local") + useCb = useCb && (cb["mode"] == "" || cb["mode"] == "local") + + // There were no shared bid limits to migrate. Default to counting only + // trustless payments. External service URLs and custom relays remain in their + // original sections; they are not entries in the built-in builder catalog. + epbs := map[string]string{"minBid": "", "maxExecutionPayment": "0"} + for _, builder := range epbsBuilderMigrations { + enabled := false + if useMev { + switch mev["selectionMode"] { + case "", "profile": + for _, key := range builder.mevProfileKeys { + enabled = enabled || isTrue(mev[key]) + } + case "relay": + for _, key := range builder.mevRelayKeys { + enabled = enabled || isTrue(mev[key]) + } + } + } else if useCb && len(builder.commitBoostKeys) > 0 { + switch cb["relaySelectionMode"] { + case "", "all": + enabled = true + case "manual": + for _, key := range builder.commitBoostKeys { + enabled = enabled || isTrue(cb[key]) + } + } + } + // Keep selections across networks. The ePBS config only exposes Hoodi + // endpoints for the active network. + epbs[builder.builderKey] = strconv.FormatBool(enabled) + } + serializedConfig["epbs"] = epbs + return nil +} diff --git a/shared/services/config/rocket-pool-config.go b/shared/services/config/rocket-pool-config.go index 9c993eda5..9fc80f695 100644 --- a/shared/services/config/rocket-pool-config.go +++ b/shared/services/config/rocket-pool-config.go @@ -142,6 +142,9 @@ type RocketPoolConfig struct { // Native mode Native *NativeConfig `yaml:"native,omitempty"` + // Builder settings shared by consensus clients + Epbs *EpbsConfig `yaml:"epbs,omitempty"` + // MEV-Boost EnableMevBoost config.Parameter `yaml:"enableMevBoost,omitempty"` MevBoost *MevBoostConfig `yaml:"mevBoost,omitempty"` @@ -620,6 +623,7 @@ func newRocketPoolConfig(rpDir string, isNativeMode bool, networks *NetworksConf cfg.Exporter = NewExporterConfig(cfg) cfg.BitflyNodeMetrics = NewBitflyNodeMetricsConfig(cfg) cfg.Native = NewNativeConfig(cfg) + cfg.Epbs = NewEpbsConfig(cfg) cfg.MevBoost = NewMevBoostConfig(cfg) cfg.CommitBoost = NewCommitBoostConfig(cfg) // Addons @@ -746,6 +750,7 @@ func (cfg *RocketPoolConfig) GetSubconfigs() map[string]config.Config { "exporter": cfg.Exporter, "bitflyNodeMetrics": cfg.BitflyNodeMetrics, "native": cfg.Native, + "epbs": cfg.Epbs, "mevBoost": cfg.MevBoost, "commitBoostConfig": cfg.CommitBoost, "addons-gww": cfg.GraffitiWallWriter.GetConfig(), @@ -1807,6 +1812,15 @@ func (cfg *RocketPoolConfig) GetChanges(oldConfig *RocketPoolConfig) (map[string // Checks to see if the current configuration is valid; if not, returns a list of errors func (cfg *RocketPoolConfig) Validate() []string { errors := []string{} + if _, err := cfg.Epbs.GetMinBidGwei(); err != nil { + errors = append(errors, err.Error()) + } + if _, err := cfg.Epbs.GetMaxExecutionPaymentGwei(); err != nil { + errors = append(errors, err.Error()) + } + if _, err := cfg.Epbs.GetBuilderUrls(); err != nil { + errors = append(errors, err.Error()) + } if err := cfg.ValidateNetworkClients(); err != nil { errors = append(errors, err.Error()) } diff --git a/shared/services/rocketpool/assets/install/scripts/start-vc.sh b/shared/services/rocketpool/assets/install/scripts/start-vc.sh index c162757b8..24233e679 100755 --- a/shared/services/rocketpool/assets/install/scripts/start-vc.sh +++ b/shared/services/rocketpool/assets/install/scripts/start-vc.sh @@ -132,6 +132,21 @@ if [ ! -z "$VC_SUGGESTED_BLOCK_GAS_LIMIT" ]; then CMD="$CMD --monitoring.endpoint $BITFLY_NODE_METRICS_ENDPOINT?apikey=$BITFLY_NODE_METRICS_SECRET&machine=$BITFLY_NODE_METRICS_MACHINE_NAME" fi + # Set up the ePBS builder flags (amounts are in integer Gwei) + if [ ! -z "$EPBS_BUILDER_URLS" ]; then + CMD="$CMD --builder.urls $EPBS_BUILDER_URLS" + fi + if [ ! -z "$EPBS_MIN_BID_GWEI" ]; then + CMD="$CMD --builder.minBid $EPBS_MIN_BID_GWEI" + fi + if [ ! -z "$EPBS_MAX_EXECUTION_PAYMENT_GWEI" ]; then + CMD="$CMD --builder.maxExecutionPayment $EPBS_MAX_EXECUTION_PAYMENT_GWEI" + # Compare as text: a uint64 Gwei amount can exceed shell integer limits. + if [ "$EPBS_MAX_EXECUTION_PAYMENT_GWEI" != "0" ]; then + CMD="$CMD --allowDangerousTrustedPayments" + fi + fi + exec ${CMD} --graffiti "$GRAFFITI" fi @@ -164,7 +179,7 @@ if [ "$CC_CLIENT" = "nimbus" ]; then --block-monitor-type=event \ $VC_ADDITIONAL_FLAGS" - if [ "$ENABLE_PBS" = "true" ]; then + if [ "$ENABLE_PBS" = "true" ] || [ ! -z "$EPBS_BUILDER_URLS" ]; then CMD="$CMD --payload-builder" fi @@ -172,6 +187,23 @@ if [ "$CC_CLIENT" = "nimbus" ]; then CMD="$CMD --metrics --metrics-address=0.0.0.0 --metrics-port=$VC_METRICS_PORT" fi + # Set up the ePBS builder flags (amounts are in integer Gwei) + if [ ! -z "$EPBS_BUILDER_URLS" ]; then + BUILDER_URLS=$(printf '%s' "$EPBS_BUILDER_URLS" | tr ',' ' ') + for BUILDER_URL in $BUILDER_URLS; do + CMD="$CMD --payload-builder-url=$BUILDER_URL" + done + case "$EPBS_BUILDER_URLS" in + *,*) echo "WARNING: Nimbus v26.10.1 uses only the last --payload-builder-url when multiple builders are configured." ;; + esac + fi + if [ ! -z "$EPBS_MIN_BID_GWEI" ]; then + CMD="$CMD --builder-min-bid=$EPBS_MIN_BID_GWEI" + fi + if [ ! -z "$EPBS_MAX_EXECUTION_PAYMENT_GWEI" ]; then + CMD="$CMD --builder-max-execution-payment=$EPBS_MAX_EXECUTION_PAYMENT_GWEI" + fi + # Graffiti breaks if it's in the CMD string instead of here because of spaces exec ${CMD} --graffiti="$GRAFFITI" @@ -228,6 +260,16 @@ if [ "$CC_CLIENT" = "prysm" ]; then CMD="$CMD --disable-monitoring --disable-account-metrics" fi + if [ ! -z "$EPBS_BUILDER_URLS" ]; then + CMD="$CMD --builder-urls=$EPBS_BUILDER_URLS" + fi + if [ ! -z "$EPBS_MIN_BID_GWEI" ]; then + CMD="$CMD --builder-min-bid=$EPBS_MIN_BID_GWEI" + fi + if [ ! -z "$EPBS_MAX_EXECUTION_PAYMENT_GWEI" ]; then + CMD="$CMD --builder-max-execution-payment=$EPBS_MAX_EXECUTION_PAYMENT_GWEI" + fi + if [ "$ADDON_GWW_ENABLED" = "true" ]; then echo "ordered:\n - $GRAFFITI" > $GWW_GRAFFITI_FILE # Default graffiti value for Prysm exec ${CMD} --graffiti-file=$GWW_GRAFFITI_FILE @@ -294,6 +336,17 @@ if [ "$CC_CLIENT" = "teku" ]; then CMD="$CMD --metrics-publish-endpoint=$BITFLY_NODE_METRICS_ENDPOINT?apikey=$BITFLY_NODE_METRICS_SECRET&machine=$BITFLY_NODE_METRICS_MACHINE_NAME" fi + # Set up the ePBS builder flags (amounts are in integer Gwei) + if [ ! -z "$EPBS_BUILDER_URLS" ]; then + CMD="$CMD --Xbuilder-urls=$EPBS_BUILDER_URLS" + fi + if [ ! -z "$EPBS_MIN_BID_GWEI" ]; then + CMD="$CMD --Xbuilder-min-bid=$EPBS_MIN_BID_GWEI" + fi + if [ ! -z "$EPBS_MAX_EXECUTION_PAYMENT_GWEI" ]; then + CMD="$CMD --Xbuilder-max-execution-payment=$EPBS_MAX_EXECUTION_PAYMENT_GWEI" + fi + if [ "$ADDON_GWW_ENABLED" = "true" ]; then echo "$GRAFFITI" > $GWW_GRAFFITI_FILE # Default graffiti value for Teku exec ${CMD} --validators-graffiti-file=$GWW_GRAFFITI_FILE diff --git a/shared/services/rocketpool/assets/install/templates/validator.tmpl b/shared/services/rocketpool/assets/install/templates/validator.tmpl index 1e7ceaa3a..6101c6c41 100644 --- a/shared/services/rocketpool/assets/install/templates/validator.tmpl +++ b/shared/services/rocketpool/assets/install/templates/validator.tmpl @@ -56,6 +56,9 @@ services: - ADDON_GWW_ENABLED={{.GraffitiWallWriter.GetEnabledParameter}} - PBS_URL={{.PbsUrl}} - ENABLE_PBS={{.IsPbsEnabled}} + - EPBS_BUILDER_URLS={{.Epbs.GetBuilderUrlString}} + - EPBS_MIN_BID_GWEI={{.Epbs.GetMinBidGwei}} + - EPBS_MAX_EXECUTION_PAYMENT_GWEI={{.Epbs.GetMaxExecutionPaymentGwei}} {{- if eq .ConsensusClient.String "teku"}} - TEKU_USE_SLASHING_PROTECTION={{.Teku.UseSlashingProtection}} {{- end}} diff --git a/shared/services/rocketpool/assets/install/testnet.env b/shared/services/rocketpool/assets/install/testnet.env index 2e76cee9b..132d6f6ef 100644 --- a/shared/services/rocketpool/assets/install/testnet.env +++ b/shared/services/rocketpool/assets/install/testnet.env @@ -3,4 +3,5 @@ # Keys not listed here inherit the mainnet pin. # # Example: -# RP_IMAGE_GETH=ethereum/client-go:v1.17.4@sha256:... \ No newline at end of file +# RP_IMAGE_GETH=ethereum/client-go:v1.17.4@sha256:... +RP_IMAGE_COMMIT_BOOST=ghcr.io/commit-boost/commit-boost:v0.12.0-rc1@sha256:096000933cd18f8c98cc843e7cdcf7f3a14b0390cc2890bc815d13a35183ba50 \ No newline at end of file