diff --git a/fixtures/dev-peers.example.json b/fixtures/dev-peers.example.json new file mode 100644 index 00000000..dd9f85ad --- /dev/null +++ b/fixtures/dev-peers.example.json @@ -0,0 +1,22 @@ +[ + { + "name": "proxy_us", + "ip": "127.0.0.1:5544", + "dns_name": "", + "orderflow_proxy": { + "ecdsa_pubkey_address": "0xFCAd0B19bB29D4674531d6f115237E16AfCE377c", + "region": "us" + }, + "instance": { "tls_cert": "" } + }, + { + "name": "proxy_eu", + "ip": "127.0.0.2:5544", + "dns_name": "", + "orderflow_proxy": { + "ecdsa_pubkey_address": "0x6A9296CEb89D12e1F53b2Dd5Df45d3ADB3A814c2", + "region": "eu" + }, + "instance": { "tls_cert": "" } + } +] diff --git a/src/builderhub/mod.rs b/src/builderhub/mod.rs index 5c9abe63..b3b7da29 100644 --- a/src/builderhub/mod.rs +++ b/src/builderhub/mod.rs @@ -19,8 +19,15 @@ use openssl::{ x509::X509, }; use std::{ - convert::Infallible, fmt::Debug, future::Future, io, net::SocketAddr, num::NonZero, - path::PathBuf, sync::Arc, time::Duration, + convert::Infallible, + fmt::Debug, + future::Future, + io, + net::SocketAddr, + num::NonZero, + path::{Path, PathBuf}, + sync::Arc, + time::Duration, }; use tokio::net::{lookup_host, ToSocketAddrs}; @@ -165,6 +172,32 @@ impl LocalPeerStore { } } +impl LocalPeerStore { + /// Load peers from a JSON file and insert them into the store, keyed by their `name`. + /// Returns the number of peers loaded. + /// + /// Used by dev mode (`--dev-peers `) to seed the local peer store from a static + /// list instead of using BuilderHub. + pub fn load_from_file(&self, path: &Path) -> Result { + let bytes = std::fs::read(path).map_err(LocalPeerStoreLoadError::Io)?; + let peers: Vec = + serde_json::from_slice(&bytes).map_err(LocalPeerStoreLoadError::Parse)?; + let count = peers.len(); + for peer in peers { + self.builders.insert(peer.name.clone(), peer); + } + Ok(count) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum LocalPeerStoreLoadError { + #[error("failed to read dev peers file: {0}")] + Io(#[source] io::Error), + #[error("failed to parse dev peers file: {0}")] + Parse(#[source] serde_json::Error), +} + impl PeerStore for LocalPeerStore { type Error = Infallible; diff --git a/src/cli.rs b/src/cli.rs index 05b5dd00..27ff0796 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -238,6 +238,18 @@ pub struct OrderflowIngressArgs { #[clap(long, value_hint = ValueHint::Url, env = "BUILDERHUB_ENDPOINT", id = "BUILDERHUB_ENDPOINT")] pub builder_hub_url: Option, + /// Path to a JSON file containing a static list of peers for development/testing. + /// When set, peers are loaded from this file once at startup into the local peer store. + /// Conflicts with `--builder-hub-url`. + #[clap( + long, + value_hint = ValueHint::FilePath, + env = "DEV_PEERS", + id = "DEV_PEERS", + conflicts_with = "BUILDERHUB_ENDPOINT" + )] + pub dev_peers: Option, + /// Enable Prometheus metrics. /// The metrics will be served at the given interface and port. #[arg(long, env = "METRICS_ADDR", id = "METRICS_ADDR")] @@ -376,6 +388,7 @@ impl Default for OrderflowIngressArgs { builder_name: String::from("buildernet"), builder_region: Region::US, builder_hub_url: None, + dev_peers: None, flashbots_signer: None, max_txs_per_bundle: 100, enable_rate_limiting: false, diff --git a/src/lib.rs b/src/lib.rs index d0478470..5aa4c8a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -168,9 +168,14 @@ pub async fn run_with_listeners( task_executor .spawn_critical("run_update_peers", peer_updater.run(args.peer_update_interval_s)); } else { - tracing::warn!("No BuilderHub URL provided, running with local peer store"); let local_peer_store = LOCAL_PEER_STORE.clone(); + let static_peer_count = match args.dev_peers.as_ref() { + Some(path) => local_peer_store.load_from_file(path)?, + None => 0, + }; + tracing::warn!(static_peer_count, "running without builderhub"); + let peer_store = local_peer_store.register( local_signer, Some(system_listener.local_addr().expect("bound").port()),