-
Notifications
You must be signed in to change notification settings - Fork 273
Add timing feature for bid optimization #839
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
0de9893
0cf0bf6
89bc53e
551974a
ebd6330
05fccad
4f7136e
28af7d9
a76e948
1dcb4ee
020d85b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| package cli | ||
|
|
||
| import ( | ||
| "os" | ||
| "path/filepath" | ||
| "strings" | ||
|
|
||
| "github.com/flashbots/mev-boost/server/types" | ||
| "github.com/fsnotify/fsnotify" | ||
| "github.com/sirupsen/logrus" | ||
| "github.com/spf13/viper" | ||
| "gopkg.in/yaml.v3" | ||
| ) | ||
|
|
||
| type RelayConfigYAML struct { | ||
| URL string `yaml:"url"` | ||
| ID string `yaml:"id"` | ||
| EnableTimingGames bool `yaml:"enable_timing_games"` | ||
| TargetFirstRequestMs uint64 `yaml:"target_first_request_ms"` | ||
| FrequencyGetHeaderMs uint64 `yaml:"frequency_getheader_ms"` | ||
| } | ||
|
|
||
| // Config holds all configuration settings from the config file | ||
| type Config struct { | ||
| TimeoutGetHeaderMs uint64 `yaml:"timeout_get_header_ms"` | ||
| LateInSlotTimeMs uint64 `yaml:"late_in_slot_time_ms"` | ||
| Relays []RelayConfigYAML `yaml:"relays"` | ||
| } | ||
|
|
||
| type ConfigResult struct { | ||
| RelayConfigs map[string]types.RelayConfig | ||
| TimeoutGetHeaderMs uint64 | ||
| LateInSlotTimeMs uint64 | ||
| } | ||
|
|
||
| // ConfigWatcher provides hot reloading of config files | ||
| type ConfigWatcher struct { | ||
| v *viper.Viper | ||
| configPath string | ||
| cliRelays []types.RelayEntry | ||
| onConfigChange func(*ConfigResult) | ||
| log *logrus.Entry | ||
| } | ||
|
|
||
| // LoadConfigFile loads configurations from a YAML file | ||
| func LoadConfigFile(configPath string) (*ConfigResult, error) { | ||
| data, err := os.ReadFile(configPath) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| var config Config | ||
| if err := yaml.Unmarshal(data, &config); err != nil { | ||
| return nil, err | ||
| } | ||
| return parseConfig(config) | ||
| } | ||
|
|
||
| // NewConfigWatcher creates a new config file watcher | ||
| func NewConfigWatcher(configPath string, cliRelays []types.RelayEntry, log *logrus.Entry) (*ConfigWatcher, error) { | ||
| v := viper.New() | ||
| absPath, err := filepath.Abs(configPath) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| v.SetConfigFile(absPath) | ||
| v.SetConfigType("yaml") | ||
|
|
||
| if err := v.ReadInConfig(); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return &ConfigWatcher{ | ||
| v: v, | ||
| configPath: absPath, | ||
| cliRelays: cliRelays, | ||
| log: log, | ||
| }, nil | ||
| } | ||
|
|
||
| // Watch starts watching the config file for changes | ||
| func (cw *ConfigWatcher) Watch(onConfigChange func(*ConfigResult)) { | ||
| cw.onConfigChange = onConfigChange | ||
|
|
||
| cw.v.OnConfigChange(func(_ fsnotify.Event) { | ||
| cw.log.Info("config file changed, reloading...") | ||
| var config Config | ||
| if err := cw.v.Unmarshal(&config); err != nil { | ||
| cw.log.WithError(err).Error("failed to unmarshal new config, keeping old config") | ||
| return | ||
| } | ||
| newConfig, err := parseConfig(config) | ||
| if err != nil { | ||
| cw.log.WithError(err).Error("failed to parse new config, keeping old config") | ||
| return | ||
| } | ||
|
|
||
| cw.log.Infof("successfully loaded new config") | ||
|
|
||
| if cw.onConfigChange != nil { | ||
| cw.onConfigChange(newConfig) | ||
| } | ||
| }) | ||
|
|
||
| cw.v.WatchConfig() | ||
| } | ||
|
|
||
| // MergeRelayConfigs merges relays passed via --relays with config file settings. | ||
| // this allows the users to still use --relays if they dont want to provide a config file | ||
| func MergeRelayConfigs(relays []types.RelayEntry, configMap map[string]types.RelayConfig) []types.RelayConfig { | ||
| configs := make([]types.RelayConfig, 0) | ||
| processedURLs := make(map[string]bool) | ||
|
|
||
| for _, entry := range relays { | ||
| urlStr := entry.String() | ||
| if config, exists := configMap[urlStr]; exists { | ||
| config.RelayEntry = entry | ||
| configs = append(configs, config) | ||
| } else { | ||
| configs = append(configs, types.NewRelayConfig(entry)) | ||
| } | ||
| processedURLs[urlStr] = true | ||
| } | ||
|
|
||
| for urlStr, config := range configMap { | ||
| if !processedURLs[urlStr] { | ||
| configs = append(configs, config) | ||
| } | ||
| } | ||
| return configs | ||
| } | ||
|
|
||
| func parseConfig(config Config) (*ConfigResult, error) { | ||
| timeoutGetHeaderMs := config.TimeoutGetHeaderMs | ||
| if timeoutGetHeaderMs == 0 { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. neeed to clearly specify this in a documentaton on what the default value is. you can do it in the config.yaml |
||
| timeoutGetHeaderMs = 900 | ||
| } | ||
|
|
||
| lateInSlotTimeMs := config.LateInSlotTimeMs | ||
| if lateInSlotTimeMs == 0 { | ||
| lateInSlotTimeMs = 1000 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same for this. |
||
| } | ||
|
|
||
| configMap := make(map[string]types.RelayConfig) | ||
| for _, relay := range config.Relays { | ||
| relayEntry, err := types.NewRelayEntry(strings.TrimSpace(relay.URL)) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if relay.ID != "" { | ||
| relayEntry.ID = relay.ID | ||
| } else { | ||
| relayEntry.ID = relayEntry.URL.String() | ||
| } | ||
| relayConfig := types.RelayConfig{ | ||
| RelayEntry: relayEntry, | ||
| EnableTimingGames: relay.EnableTimingGames, | ||
| TargetFirstRequestMs: relay.TargetFirstRequestMs, | ||
| FrequencyGetHeaderMs: relay.FrequencyGetHeaderMs, | ||
| } | ||
| configMap[relayEntry.String()] = relayConfig | ||
| } | ||
|
|
||
| return &ConfigResult{ | ||
| RelayConfigs: configMap, | ||
| TimeoutGetHeaderMs: timeoutGetHeaderMs, | ||
| LateInSlotTimeMs: lateInSlotTimeMs, | ||
| }, nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| # Example configuration for mev-boost | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would recommend to create a |
||
|
|
||
| # timeout settings for get_header requests | ||
| timeout_get_header_ms: 900 # timeout for get_header request in milliseconds | ||
| late_in_slot_time_ms: 1000 # threshold that defines when in a slot is considered "too late" | ||
|
|
||
| # Relay configurations | ||
| relays: | ||
| # relay with timing games enabled | ||
| - url: https://0x9000009807ed12c1f08bf4e81c6da3ba8e3fc3d953898ce0102433094e5f22f21102ec057841fcb81978ed1ea0fa8246@relay.relayer1.net | ||
| # id for identifying the relay. if not provided, the url will be used as the id | ||
| id: relay.relayer1.net | ||
| enable_timing_games: true | ||
| # time in ms from slot start for the first getHeader request | ||
| target_first_request_ms: 200 | ||
| # time in ms between subsequent getHeader requests | ||
| frequency_getheader_ms: 100 | ||
|
|
||
| # relay with timing games disabled (standard behavior) | ||
| - url: https://0x9000009807ed12c1f08bf4e81c6da3ba8e3fc3d953898ce0102433094e5f22f21102ec057841fcb81978ed1ea0fa8246@relay.relayer2.com | ||
| id: relay.relayer2.com | ||
| enable_timing_games: false | ||
| target_first_request_ms: 0 | ||
| frequency_getheader_ms: 0 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do relays need an ID outside the URL?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
optionally if you want. URLs can be too long when being read in metrics so you can essentially give an alias
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in other places, just the hostname was a good enough ID and short one. seems better to use only the hostname for metrics too?