Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions rocketpool-cli/service/config/settings-fallback.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import (

// The page wrapper for the fallback config
type FallbackConfigPage struct {
home *settingsHome
page *page
layout *standardLayout
masterConfig *config.RocketPoolConfig
useFallbackBox *parameterizedFormItem
reconnectDelay *parameterizedFormItem
fallbackNormalItems []*parameterizedFormItem
fallbackPrysmItems []*parameterizedFormItem
home *settingsHome
page *page
layout *standardLayout
masterConfig *config.RocketPoolConfig
useFallbackBox *parameterizedFormItem
reconnectDelay *parameterizedFormItem
fallbackNormalItems []*parameterizedFormItem
fallbackPrysmItems []*parameterizedFormItem
prioritizeValidation *parameterizedFormItem
}

// Creates a new page for the fallback client settings
Expand Down Expand Up @@ -76,11 +77,13 @@ func (configPage *FallbackConfigPage) createContent() {
configPage.reconnectDelay = createParameterizedStringField(&configPage.masterConfig.ReconnectDelay)
configPage.fallbackNormalItems = createParameterizedFormItems(configPage.masterConfig.FallbackNormal.GetParameters(), configPage.layout.descriptionBox)
configPage.fallbackPrysmItems = createParameterizedFormItems(configPage.masterConfig.FallbackPrysm.GetParameters(), configPage.layout.descriptionBox)
configPage.prioritizeValidation = createParameterizedCheckbox(&configPage.masterConfig.PrioritizeValidation)

// Map the parameters to the form items in the layout
configPage.layout.mapParameterizedFormItems(configPage.useFallbackBox, configPage.reconnectDelay)
configPage.layout.mapParameterizedFormItems(configPage.fallbackNormalItems...)
configPage.layout.mapParameterizedFormItems(configPage.fallbackPrysmItems...)
configPage.layout.mapParameterizedFormItems(configPage.prioritizeValidation)

// Set up the setting callbacks
configPage.useFallbackBox.item.(*tview.Checkbox).SetChangedFunc(func(checked bool) {
Expand Down Expand Up @@ -113,6 +116,7 @@ func (configPage *FallbackConfigPage) handleUseFallbackChanged() {
default:
configPage.layout.addFormItems(configPage.fallbackNormalItems)
}
configPage.layout.form.AddFormItem(configPage.prioritizeValidation.item)

configPage.layout.refresh()
}
Expand Down
18 changes: 11 additions & 7 deletions rocketpool-cli/service/config/settings-native-fallback.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import (

// The page wrapper for the fallback config
type NativeFallbackConfigPage struct {
home *settingsNativeHome
page *page
layout *standardLayout
masterConfig *config.RocketPoolConfig
useFallbackBox *parameterizedFormItem
reconnectDelay *parameterizedFormItem
fallbackItems []*parameterizedFormItem
home *settingsNativeHome
page *page
layout *standardLayout
masterConfig *config.RocketPoolConfig
useFallbackBox *parameterizedFormItem
reconnectDelay *parameterizedFormItem
fallbackItems []*parameterizedFormItem
prioritizeValidation *parameterizedFormItem
}

// Creates a new page for the fallback client settings
Expand Down Expand Up @@ -73,10 +74,12 @@ func (configPage *NativeFallbackConfigPage) createContent() {
configPage.useFallbackBox = createParameterizedCheckbox(&configPage.masterConfig.UseFallbackClients)
configPage.reconnectDelay = createParameterizedStringField(&configPage.masterConfig.ReconnectDelay)
configPage.fallbackItems = createParameterizedFormItems(configPage.masterConfig.FallbackNormal.GetParameters(), configPage.layout.descriptionBox)
configPage.fallbackItems = createParameterizedFormItems(configPage.masterConfig.FallbackNormal.GetParameters(), configPage.layout.descriptionBox)

// Map the parameters to the form items in the layout
configPage.layout.mapParameterizedFormItems(configPage.useFallbackBox, configPage.reconnectDelay)
configPage.layout.mapParameterizedFormItems(configPage.fallbackItems...)
configPage.prioritizeValidation = createParameterizedCheckbox(&configPage.masterConfig.PrioritizeValidation)

// Set up the setting callbacks
configPage.useFallbackBox.item.(*tview.Checkbox).SetChangedFunc(func(checked bool) {
Expand All @@ -102,6 +105,7 @@ func (configPage *NativeFallbackConfigPage) handleUseFallbackChanged() {
}
configPage.layout.form.AddFormItem(configPage.reconnectDelay.item)
configPage.layout.addFormItems(configPage.fallbackItems)
configPage.layout.form.AddFormItem(configPage.prioritizeValidation.item)

configPage.layout.refresh()
}
Expand Down
175 changes: 107 additions & 68 deletions shared/services/bc-manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type BeaconClientManager struct {
primaryReady bool
fallbackReady bool
ignoreSyncCheck bool
preferFallback bool
}

// This is a signature for a wrapped Beacon client function that only returns an error
Expand All @@ -36,6 +37,7 @@ type bcFunction2 func(beacon.Client) (interface{}, interface{}, error)

// Creates a new BeaconClientManager instance based on the Rocket Pool config
func NewBeaconClientManager(cfg *config.RocketPoolConfig) (*BeaconClientManager, error) {
var preferFallback bool

// Primary CC
var primaryProvider string
Expand All @@ -62,13 +64,15 @@ func NewBeaconClientManager(cfg *config.RocketPoolConfig) (*BeaconClientManager,
if cfg.UseFallbackClients.Value == true {
if cfg.IsNativeMode {
fallbackProvider = cfg.FallbackNormal.CcHttpUrl.Value.(string)
preferFallback = cfg.PrioritizeValidation.Value.(bool)
} else {
switch selectedCC {
case cfgtypes.ConsensusClient_Prysm:
fallbackProvider = cfg.FallbackPrysm.CcHttpUrl.Value.(string)
default:
fallbackProvider = cfg.FallbackNormal.CcHttpUrl.Value.(string)
}
preferFallback = cfg.PrioritizeValidation.Value.(bool)
}
}

Expand All @@ -80,11 +84,12 @@ func NewBeaconClientManager(cfg *config.RocketPoolConfig) (*BeaconClientManager,
}

return &BeaconClientManager{
primaryBc: primaryBc,
fallbackBc: fallbackBc,
logger: log.NewColorLogger(color.FgHiBlue),
primaryReady: true,
fallbackReady: fallbackBc != nil,
primaryBc: primaryBc,
fallbackBc: fallbackBc,
logger: log.NewColorLogger(color.FgHiBlue),
primaryReady: true,
fallbackReady: fallbackBc != nil,
preferFallback: preferFallback,
}, nil

}
Expand Down Expand Up @@ -403,42 +408,59 @@ func (m *BeaconClientManager) runFunction0(function bcFunction0) error {
return fmt.Errorf("no Beacon clients were ready")
}

// Attempts to run a function progressively through each client until one succeeds or they all fail.
// Attempts to run a function progressively through each client in the preferred order until one succeeds or they all fail.
func (m *BeaconClientManager) runFunction1(function bcFunction1) (interface{}, error) {

// Check if we can use the primary
if m.primaryReady {
// Try to run the function on the primary
result, err := function(m.primaryBc)
if err != nil {
if m.isDisconnected(err) {
// If it's disconnected, log it and try the fallback
m.logger.Printlnf("WARNING: Primary Beacon client disconnected (%s), using fallback...", err.Error())
m.primaryReady = false
return m.runFunction1(function)
}
// If it's a different error, just return it
return nil, err
}
// If there's no error, return the result
return result, nil
const (
primary = iota
fallback = iota
)

var order []int
if !m.preferFallback {
order = []int{primary, fallback}
} else {
order = []int{fallback, primary}
}

if m.fallbackReady {
// Try to run the function on the fallback
result, err := function(m.fallbackBc)
if err != nil {
if m.isDisconnected(err) {
// If it's disconnected, log it and try the fallback
m.logger.Printlnf("WARNING: Fallback Beacon client disconnected (%s)", err.Error())
m.fallbackReady = false
return nil, fmt.Errorf("all Beacon clients failed")
for _, client := range order {
switch client {
case primary:

// Check if we can use the primary
if m.primaryReady {
// Try to run the function on the primary
result, err := function(m.primaryBc)
if err != nil {
if m.isDisconnected(err) {
// If it's disconnected, log it and try the fallback
m.logger.Printlnf("WARNING: Primary Beacon client disconnected (%s), using fallback...", err.Error())
m.primaryReady = false
return m.runFunction1(function)
}
// If it's a different error, just return it
return nil, err
}
// If there's no error, return the result
return result, nil
}
case fallback:

if m.fallbackReady {
// Try to run the function on the fallback
result, err := function(m.fallbackBc)
if err != nil {
if m.isDisconnected(err) {
// If it's disconnected, log it and try the fallback
m.logger.Printlnf("WARNING: Fallback Beacon client disconnected (%s)", err.Error())
m.fallbackReady = false
return m.runFunction1(function)
}
// If it's a different error, just return it
return nil, err
}
// If there's no error, return the result
return result, nil
}
// If it's a different error, just return it
return nil, err
}
// If there's no error, return the result
return result, nil
}

return nil, fmt.Errorf("no Beacon clients were ready")
Expand All @@ -447,40 +469,57 @@ func (m *BeaconClientManager) runFunction1(function bcFunction1) (interface{}, e

// Attempts to run a function progressively through each client until one succeeds or they all fail.
func (m *BeaconClientManager) runFunction2(function bcFunction2) (interface{}, interface{}, error) {

// Check if we can use the primary
if m.primaryReady {
// Try to run the function on the primary
result1, result2, err := function(m.primaryBc)
if err != nil {
if m.isDisconnected(err) {
// If it's disconnected, log it and try the fallback
m.logger.Printlnf("WARNING: Primary Beacon client disconnected (%s), using fallback...", err.Error())
m.primaryReady = false
return m.runFunction2(function)
}
// If it's a different error, just return it
return nil, nil, err
}
// If there's no error, return the result
return result1, result2, nil
const (
primary = iota
fallback = iota
)

var order []int
if !m.preferFallback {
order = []int{primary, fallback}
} else {
order = []int{fallback, primary}
}

if m.fallbackReady {
// Try to run the function on the fallback
result1, result2, err := function(m.fallbackBc)
if err != nil {
if m.isDisconnected(err) {
// If it's disconnected, log it and try the fallback
m.logger.Printlnf("WARNING: Fallback Beacon client disconnected (%s)", err.Error())
m.fallbackReady = false
return nil, nil, fmt.Errorf("all Beacon clients failed")
for _, client := range order {
switch client {
case primary:

// Check if we can use the primary
if m.primaryReady {
// Try to run the function on the primary
result1, result2, err := function(m.primaryBc)
if err != nil {
if m.isDisconnected(err) {
// If it's disconnected, log it and try the fallback
m.logger.Printlnf("WARNING: Primary Beacon client disconnected (%s), using fallback...", err.Error())
m.primaryReady = false
return m.runFunction2(function)
}
// If it's a different error, just return it
return nil, nil, err
}
// If there's no error, return the result
return result1, result2, nil
}
case fallback:

if m.fallbackReady {
// Try to run the function on the fallback
result1, result2, err := function(m.fallbackBc)
if err != nil {
if m.isDisconnected(err) {
// If it's disconnected, log it and try the fallback
m.logger.Printlnf("WARNING: Fallback Beacon client disconnected (%s)", err.Error())
m.fallbackReady = false
return m.runFunction2(function)
}
// If it's a different error, just return it
return nil, nil, err
}
// If there's no error, return the result
return result1, result2, nil
}
// If it's a different error, just return it
return nil, nil, err
}
// If there's no error, return the result
return result1, result2, nil
}

return nil, nil, fmt.Errorf("no Beacon clients were ready")
Expand Down
17 changes: 15 additions & 2 deletions shared/services/config/rocket-pool-config.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ type RocketPoolConfig struct {
ExecutionClient config.Parameter `yaml:"executionClient,omitempty"`

// Fallback settings
UseFallbackClients config.Parameter `yaml:"useFallbackClients,omitempty"`
ReconnectDelay config.Parameter `yaml:"reconnectDelay,omitempty"`
UseFallbackClients config.Parameter `yaml:"useFallbackClients,omitempty"`
ReconnectDelay config.Parameter `yaml:"reconnectDelay,omitempty"`
PrioritizeValidation config.Parameter `yaml:"prioritizeValidation,omitempty"`

// Consensus client settings
ConsensusClientMode config.Parameter `yaml:"consensusClientMode,omitempty"`
Expand Down Expand Up @@ -237,6 +238,17 @@ func NewRocketPoolConfig(rpDir string, isNativeMode bool) *RocketPoolConfig {
OverwriteOnUpgrade: false,
},

PrioritizeValidation: config.Parameter{
ID: "prioritizeValidation",
Name: "Prioritize Validation",
Description: "If enabled, metrics and wallet operations will go through the Fallback to prioritize resources on this node to the validator.",
Type: config.ParameterType_Bool,
Default: map[config.Network]interface{}{config.Network_All: true},
EnvironmentVariables: []string{},
CanBeBlank: false,
OverwriteOnUpgrade: false,
},

ConsensusClientMode: config.Parameter{
ID: "consensusClientMode",
Name: "Consensus Client Mode",
Expand Down Expand Up @@ -525,6 +537,7 @@ func (cfg *RocketPoolConfig) GetParameters() []*config.Parameter {
&cfg.ExecutionClient,
&cfg.UseFallbackClients,
&cfg.ReconnectDelay,
&cfg.PrioritizeValidation,
&cfg.ConsensusClientMode,
&cfg.ConsensusClient,
&cfg.ExternalConsensusClient,
Expand Down
Loading