diff --git a/Dockerfile b/Dockerfile index 7c68ae1..0c44fff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,6 +17,7 @@ FROM alpine:latest WORKDIR /app COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=builder /build/relayscan /app/relayscan +COPY --from=builder /build/config-* /app/ COPY --from=builder /build/services/website/templates/ /app/services/website/templates/ COPY --from=builder /build/static/ /app/static/ diff --git a/README.md b/README.md index c358641..954e8e8 100644 --- a/README.md +++ b/README.md @@ -44,10 +44,25 @@ https://bidarchive.relayscan.io * Uses PostgreSQL as data store * Configuration: - * Relays in [`/vars/relays.go`](/vars/relays.go) + * Relays and builder addresses in [`config-mainnet.yaml`](/config-mainnet.yaml) * Builder aliases in [`/vars/builder_aliases.go`](/vars/builder_aliases.go) * Version and common env vars in [`/vars/vars.go`](/vars/vars.go) * Some environment variables are required, see [`.env.example`](/.env.example) + +### Config file + +Relay URLs and builder addresses are configured via a YAML config file. By default, `config-mainnet.yaml` is used. + +```bash +# Use default config (config-mainnet.yaml) +./relayscan + +# Use a custom config file +./relayscan --config config-hoodi.yaml + +# Or via environment variable +CONFIG_FILE=config-hoodi.yaml ./relayscan +``` * Saving and checking payloads is split into phases/commands: * [`data-api-backfill`](/cmd/core/data-api-backfill.go) -- queries the data API of all relays and puts that data into the database * [`check-payload-value`](/cmd/core/check-payload-value.go) -- checks all new database entries for payment validity diff --git a/cmd/root.go b/cmd/root.go index 24ef34a..3943dba 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -12,15 +12,27 @@ import ( "github.com/spf13/cobra" ) +var configFile string + var rootCmd = &cobra.Command{ Short: "relayscan", Long: `https://github.com/flashbots/relayscan`, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if configFile == "" { + configFile = "config-mainnet.yaml" + } + return vars.LoadConfig(configFile) + }, Run: func(cmd *cobra.Command, args []string) { fmt.Printf("relayscan %s\n", vars.Version) _ = cmd.Help() }, } +func init() { + rootCmd.PersistentFlags().StringVar(&configFile, "config", os.Getenv("CONFIG_FILE"), "path to config file (default: config-mainnet.yaml)") +} + func Execute() { rootCmd.AddCommand(versionCmd) rootCmd.AddCommand(core.CoreCmd) diff --git a/config-hoodi.yaml b/config-hoodi.yaml new file mode 100644 index 0000000..49a31ae --- /dev/null +++ b/config-hoodi.yaml @@ -0,0 +1,18 @@ +# Hoodi beacon chain genesis timestamp (Mar/17, 2025, 12:10 UTC) - https://github.com/eth-clients/hoodi +genesis: 1742213400 + +relays: + flashbots: "https://0xafa4c6985aa049fb79dd37010438cfebeb0f2bd42b115b89dd678dab0670c1de38da0c4e9138c9290a398ecd9a0b3110@boost-relay-hoodi.flashbots.net" + ultrasound: "https://0xb1559beef7b5ba3127485bbbb090362d9f497ba64e177ee2c8e7db74746306efad687f2cf8574e38d70067d40ef136dc@relay-hoodi.ultrasound.money" + all: + - "https://0xafa4c6985aa049fb79dd37010438cfebeb0f2bd42b115b89dd678dab0670c1de38da0c4e9138c9290a398ecd9a0b3110@boost-relay-hoodi.flashbots.net" + - "https://0xb1559beef7b5ba3127485bbbb090362d9f497ba64e177ee2c8e7db74746306efad687f2cf8574e38d70067d40ef136dc@relay-hoodi.ultrasound.money" + - "https://0x821f2a65afb70e7f2e820a925a9b4c80a159620582c1766b1b09729fec178b11ea22abb3a51f07b288be815a1a2ff516@bloxroute.hoodi.blxrbdn.com" + - "https://0xaa58208899c6105603b74396734a6263cc7d947f444f396a90f7b7d3e65d102aec7e5e5291b27e08d02c50a050825c2f@hoodi.titanrelay.xyz" + - "https://0x98f0ef62f00780cf8eb06701a7d22725b9437d4768bb19b363e882ae87129945ec206ec2dc16933f31d983f8225772b6@hoodi.aestus.live" + - "https://0xb20c3fe59db9c3655088839ef3d972878d182eb745afd8abb1dd2abf6c14f93cd5934ed4446a5fe1ba039e2bc0cf1011@hoodi-relay.ethgas.com" + # haven't had success getting data from TOOL + interstate, but these endpoints are supposed to work... + # - "https://0x9110847c15a7f5c80a9fdd5db989a614cc01104e53bd8c252b6f46a4842c7fdef6b9593336035b5094878deff386804c@hoodi-builder-proxy-alpha.interstate.so:443" + # - "https://0xa0f46566247ceb1f259a7189d5ac8bf2f0f07c135f081b0b5a9f226ef864bf6362c74306fcd02a87b7941f6feac57dc7@relay-hoodi.nuconstruct.xyz" + +builder_addresses: diff --git a/config-mainnet.yaml b/config-mainnet.yaml new file mode 100644 index 0000000..7fbb9c2 --- /dev/null +++ b/config-mainnet.yaml @@ -0,0 +1,32 @@ +# Mainnet beacon chain genesis timestamp (Dec 1, 2020) +genesis: 1606824023 + +relays: + flashbots: "https://0xac6e77dfe25ecd6110b8e780608cce0dab71fdd5ebea22a16c0205200f2f8e2e3ad3b71d3499c54ad14d6c21b41a37ae@boost-relay.flashbots.net" + ultrasound: "https://0xa1559ace749633b997cb3fdacffb890aeebdb0f5a3b6aaa7eeeaf1a38af0a8fe88b9e4b1f61f236d2e64d95733327a62@relay.ultrasound.money" + all: + - "https://0xac6e77dfe25ecd6110b8e780608cce0dab71fdd5ebea22a16c0205200f2f8e2e3ad3b71d3499c54ad14d6c21b41a37ae@boost-relay.flashbots.net" + - "https://0xa1559ace749633b997cb3fdacffb890aeebdb0f5a3b6aaa7eeeaf1a38af0a8fe88b9e4b1f61f236d2e64d95733327a62@relay.ultrasound.money" + - "https://0x8b5d2e73e2a3a55c6c87b8b6eb92e0149a125c852751db1422fa951e42a09b82c142c3ea98d0d9930b056a3bc9896b8f@bloxroute.max-profit.blxrbdn.com" + - "https://0xb0b07cd0abef743db4260b0ed50619cf6ad4d82064cb4fbec9d3ec530f7c5e6793d9f286c4e082c0244ffb9f2658fe88@bloxroute.regulated.blxrbdn.com" + - "https://0xb3ee7afcf27f1f1259ac1787876318c6584ee353097a50ed84f51a1f21a323b3736f271a895c7ce918c038e4265918be@relay.edennetwork.io" + - "https://0xa7ab7a996c8584251c8f925da3170bdfd6ebc75d50f5ddc4050a6fdc77f2a3b5fce2cc750d0865e05d7228af97d69561@agnostic-relay.net" + - "https://0xa15b52576bcbf1072f4a011c0f99f9fb6c66f3e1ff321f11f461d15e31b1cb359caa092c71bbded0bae5b5ea401aab7e@aestus.live" + - "https://0x8c4ed5e24fe5c6ae21018437bde147693f68cda427cd1122cf20819c30eda7ed74f72dece09bb313f2a1855595ab677d@titanrelay.xyz" + - "https://0x88ef3061f598101ca713d556cf757763d9be93d33c3092d3ab6334a36855b6b4a4020528dd533a62d25ea6648251e62e@relay.ethgas.com" + - "https://0xb66921e917a8f4cfc3c52e10c1e5c77b1255693d9e6ed6f5f444b71ca4bb610f2eff4fa98178efbf4dd43a30472c497e@relay.btcs.com" + +builder_addresses: + # Coinbase addresses mapped to their owned addresses + "0xdadb0d80178819f2319190d340ce9a924f783711": + - "0x59cadf9199248b50d40a6891c9e329ea13a88d31" + - "0x75cc09358f100583d66f5277138bfb476345dc1b" + - "0x397b28d85d77fef1576e129bb35b322c2bee1ba1" + "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97": + - "0x9fc3da866e7df3a1c57ade1a97c9f00a70f010c8" + - "0xb29b9fd58cdb2e3bb068bc8560d8c13b2454684d" + "0x1f9090aae28b8a3dceadf281b0f12828e676c326": + - "0x0affb0a96fbefaa97dce488dfd97512346cf3ab8" + "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": + - "0xa83114a443da1cecefc50368531cace9f37fcccb" + - "0x28c74c0f29b686f21ea731bd2a8b88b6954475ba" diff --git a/database/util.go b/database/util.go index caade2d..af4bf7c 100644 --- a/database/util.go +++ b/database/util.go @@ -4,6 +4,7 @@ import ( "net/url" "time" + "github.com/flashbots/relayscan/vars" "github.com/sirupsen/logrus" ) @@ -22,10 +23,10 @@ func MustConnectPostgres(log *logrus.Entry, dsn string) *DatabaseService { } func slotToTime(slot uint64) time.Time { - timestamp := (slot * 12) + 1606824023 // mainnet + timestamp := (slot * 12) + uint64(vars.Genesis) return time.Unix(int64(timestamp), 0).UTC() //nolint:gosec } func timeToSlot(t time.Time) uint64 { - return uint64(t.UTC().Unix()-1606824023) / 12 //nolint:gosec // mainnet + return uint64(t.UTC().Unix()-int64(vars.Genesis)) / 12 //nolint:gosec } diff --git a/go.mod b/go.mod index 2bece80..869c0a2 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,7 @@ require ( go.uber.org/atomic v1.11.0 go.uber.org/zap v1.24.0 golang.org/x/text v0.31.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -93,5 +94,4 @@ require ( golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/vars/builder_addresses.go b/vars/builder_addresses.go index cb35e46..9a93ec4 100644 --- a/vars/builder_addresses.go +++ b/vars/builder_addresses.go @@ -1,20 +1,5 @@ package vars -var BuilderAddresses = map[string]map[string]bool{ // coinbase: owned addresses - "0xdadb0d80178819f2319190d340ce9a924f783711": { - "0x59cadf9199248b50d40a6891c9e329ea13a88d31": true, - "0x75cc09358f100583d66f5277138bfb476345dc1b": true, - "0x397b28d85d77fef1576e129bb35b322c2bee1ba1": true, - }, - "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97": { - "0x9fc3da866e7df3a1c57ade1a97c9f00a70f010c8": true, - "0xb29b9fd58cdb2e3bb068bc8560d8c13b2454684d": true, - }, - "0x1f9090aae28b8a3dceadf281b0f12828e676c326": { - "0x0affb0a96fbefaa97dce488dfd97512346cf3ab8": true, - }, - "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5": { - "0xa83114a443da1cecefc50368531cace9f37fcccb": true, - "0x28c74c0f29b686f21ea731bd2a8b88b6954475ba": true, - }, -} +// BuilderAddresses maps coinbase addresses to their owned addresses +// Populated from config file +var BuilderAddresses map[string]map[string]bool diff --git a/vars/config.go b/vars/config.go new file mode 100644 index 0000000..c2c4517 --- /dev/null +++ b/vars/config.go @@ -0,0 +1,72 @@ +package vars + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v3" +) + +// Config holds the application configuration loaded from YAML +type Config struct { + Genesis int64 `yaml:"genesis"` + Relays RelaysConfig `yaml:"relays"` + BuilderAddresses map[string][]string `yaml:"builder_addresses"` +} + +// RelaysConfig holds relay URL configuration +type RelaysConfig struct { + Flashbots string `yaml:"flashbots"` + Ultrasound string `yaml:"ultrasound"` + All []string `yaml:"all"` +} + +var loadedConfig *Config + +// LoadConfig loads the configuration from a YAML file +func LoadConfig(path string) error { + data, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("failed to read config file %s: %w", path, err) + } + + var cfg Config + if err := yaml.Unmarshal(data, &cfg); err != nil { + return fmt.Errorf("failed to parse config file %s: %w", path, err) + } + + loadedConfig = &cfg + + // Populate package-level variables for backwards compatibility + Genesis = int(cfg.Genesis) + RelayFlashbots = cfg.Relays.Flashbots + RelayUltrasound = cfg.Relays.Ultrasound + RelayURLs = cfg.Relays.All + BuilderAddresses = buildAddressMap(cfg.BuilderAddresses) + + return nil +} + +// MustLoadConfig loads the configuration or panics on error +func MustLoadConfig(path string) { + if err := LoadConfig(path); err != nil { + panic(err) + } +} + +// GetConfig returns the loaded configuration +func GetConfig() *Config { + return loadedConfig +} + +// buildAddressMap converts the config format to the expected map[coinbase]map[address]bool format +func buildAddressMap(addresses map[string][]string) map[string]map[string]bool { + result := make(map[string]map[string]bool) + for coinbase, addrs := range addresses { + result[coinbase] = make(map[string]bool) + for _, addr := range addrs { + result[coinbase][addr] = true + } + } + return result +} diff --git a/vars/relays.go b/vars/relays.go index 45a1289..ddaead6 100644 --- a/vars/relays.go +++ b/vars/relays.go @@ -1,23 +1,8 @@ package vars +// Relay URLs - populated from config file var ( - RelayFlashbots = "https://0xac6e77dfe25ecd6110b8e780608cce0dab71fdd5ebea22a16c0205200f2f8e2e3ad3b71d3499c54ad14d6c21b41a37ae@boost-relay.flashbots.net" - RelayUltrasound = "https://0xa1559ace749633b997cb3fdacffb890aeebdb0f5a3b6aaa7eeeaf1a38af0a8fe88b9e4b1f61f236d2e64d95733327a62@relay.ultrasound.money" - RelayURLs = []string{ - RelayFlashbots, - RelayUltrasound, - "https://0x8b5d2e73e2a3a55c6c87b8b6eb92e0149a125c852751db1422fa951e42a09b82c142c3ea98d0d9930b056a3bc9896b8f@bloxroute.max-profit.blxrbdn.com", - "https://0xb0b07cd0abef743db4260b0ed50619cf6ad4d82064cb4fbec9d3ec530f7c5e6793d9f286c4e082c0244ffb9f2658fe88@bloxroute.regulated.blxrbdn.com", - "https://0xb3ee7afcf27f1f1259ac1787876318c6584ee353097a50ed84f51a1f21a323b3736f271a895c7ce918c038e4265918be@relay.edennetwork.io", - "https://0xa7ab7a996c8584251c8f925da3170bdfd6ebc75d50f5ddc4050a6fdc77f2a3b5fce2cc750d0865e05d7228af97d69561@agnostic-relay.net", - "https://0xa15b52576bcbf1072f4a011c0f99f9fb6c66f3e1ff321f11f461d15e31b1cb359caa092c71bbded0bae5b5ea401aab7e@aestus.live", - "https://0x8c4ed5e24fe5c6ae21018437bde147693f68cda427cd1122cf20819c30eda7ed74f72dece09bb313f2a1855595ab677d@titanrelay.xyz", // added 2024-02-22 - "https://0x88ef3061f598101ca713d556cf757763d9be93d33c3092d3ab6334a36855b6b4a4020528dd533a62d25ea6648251e62e@relay.ethgas.com", // added 2025-09-24 - "https://0xb66921e917a8f4cfc3c52e10c1e5c77b1255693d9e6ed6f5f444b71ca4bb610f2eff4fa98178efbf4dd43a30472c497e@relay.btcs.com", // added 2025-09-24 - // "https://0x95a0a6af2566fa7db732020bb2724be61963ac1eb760aa1046365eb443bd4e3cc0fba0265d40a2d81dd94366643e986a@blockspace.frontier.tech", // data API doesn't work anymore (as of June 1, 2024) - // "https://0xad0a8bb54565c2211cee576363f3a347089d2f07cf72679d16911d740262694cadb62d7fd7483f27afd714ca0f1b9118@bloxroute.ethical.blxrbdn.com", // deactivated aug 2023: https://twitter.com/bloXrouteLabs/status/1690065892778926080 - // "https://0x9000009807ed12c1f08bf4e81c6da3ba8e3fc3d953898ce0102433094e5f22f21102ec057841fcb81978ed1ea0fa8246@builder-relay-mainnet.blocknative.com", // deactivated sept. 27, 2023: https://twitter.com/blocknative/status/1706685103286485364 - // "https://0x98650451ba02064f7b000f5768cf0cf4d4e492317d82871bdc87ef841a0743f69f0f1eea11168503240ac35d101c9135@mainnet-relay.securerpc.com", - // "https://0x8c7d33605ecef85403f8b7289c8058f440cbb6bf72b055dfe2f3e2c6695b6a1ea5a9cd0eb3a7982927a463feb4c3dae2@relay.wenmerge.com", - } + RelayFlashbots string + RelayUltrasound string + RelayURLs []string ) diff --git a/vars/vars.go b/vars/vars.go index 7d4c742..2ed4f72 100644 --- a/vars/vars.go +++ b/vars/vars.go @@ -12,7 +12,7 @@ var ( Version = "dev" // is set during build process LogDebug = os.Getenv("DEBUG") != "" LogJSON = os.Getenv("LOG_JSON") != "" - Genesis = cli.GetEnvInt("GENESIS", 1_606_824_023) + Genesis = 1_606_824_023 // mainnet default, overwritten by config file DefaultBeaconURI = relaycommon.GetEnv("BEACON_URI", "http://localhost:3500") DefaultPostgresDSN = relaycommon.GetEnv("POSTGRES_DSN", "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable")