diff --git a/config.example.toml b/config.example.toml index 71c5d964..eba011be 100644 --- a/config.example.toml +++ b/config.example.toml @@ -15,7 +15,11 @@ docker_image = "ghcr.io/commit-boost/pbs:latest" # Whether to enable the PBS module to request signatures from the Signer module (not used in the default PBS image) # OPTIONAL, DEFAULT: false with_signer = false +# Host to receive BuilderAPI calls from beacon node +# OPTIONAL, DEFAULT: 127.0.0.1 +host = "127.0.0.1" # Port to receive BuilderAPI calls from beacon node +# OPTIONAL, DEFAULT: 18550 port = 18550 # Whether to forward `status` calls to relays or skip and return 200 # OPTIONAL, DEFAULT: true @@ -137,6 +141,9 @@ SOME_ENV_VAR = "some_value" # Configuration for how metrics should be collected and scraped # OPTIONAL, skip metrics collection if missing [metrics] +# Host for prometheus, grafana, and cadvisor +# OPTIONAL, DEFAULT: 127.0.0.1 +host = "127.0.0.1" # Path to a `prometheus.yml` file to use in Prometheus. If using a custom config file, be sure to add a # file discovery section as follows: # ```yml diff --git a/crates/cli/src/docker_init.rs b/crates/cli/src/docker_init.rs index e394b5cd..096da141 100644 --- a/crates/cli/src/docker_init.rs +++ b/crates/cli/src/docker_init.rs @@ -1,13 +1,17 @@ -use std::{path::Path, vec}; +use std::{ + net::{Ipv4Addr, SocketAddr}, + path::Path, + vec, +}; use cb_common::{ config::{ CommitBoostConfig, LogsSettings, ModuleKind, BUILDER_PORT_ENV, BUILDER_URLS_ENV, CHAIN_SPEC_ENV, CONFIG_DEFAULT, CONFIG_ENV, JWTS_ENV, LOGS_DIR_DEFAULT, LOGS_DIR_ENV, - METRICS_PORT_ENV, MODULE_ID_ENV, MODULE_JWT_ENV, PBS_MODULE_NAME, PROXY_DIR_DEFAULT, - PROXY_DIR_ENV, SIGNER_DEFAULT, SIGNER_DIR_KEYS_DEFAULT, SIGNER_DIR_KEYS_ENV, - SIGNER_DIR_SECRETS_DEFAULT, SIGNER_DIR_SECRETS_ENV, SIGNER_KEYS_ENV, SIGNER_MODULE_NAME, - SIGNER_PORT_ENV, SIGNER_URL_ENV, + METRICS_PORT_ENV, MODULE_ID_ENV, MODULE_JWT_ENV, PBS_ENDPOINT_ENV, PBS_MODULE_NAME, + PROXY_DIR_DEFAULT, PROXY_DIR_ENV, SIGNER_DEFAULT, SIGNER_DIR_KEYS_DEFAULT, + SIGNER_DIR_KEYS_ENV, SIGNER_DIR_SECRETS_DEFAULT, SIGNER_DIR_SECRETS_ENV, SIGNER_KEYS_ENV, + SIGNER_MODULE_NAME, SIGNER_PORT_ENV, SIGNER_URL_ENV, }, signer::{ProxyStore, SignerLoader}, types::ModuleId, @@ -233,6 +237,19 @@ pub fn handle_docker_init(config_path: String, output_dir: String) -> Result<()> pbs_envs.insert(k, v); } + // ports + let host_endpoint = + SocketAddr::from((cb_config.pbs.pbs_config.host, cb_config.pbs.pbs_config.port)); + let ports = Ports::Short(vec![format!("{}:{}", host_endpoint, cb_config.pbs.pbs_config.port)]); + exposed_ports_warn + .push(format!("pbs has an exported port on {}", cb_config.pbs.pbs_config.port)); + + // inside the container expose on 0.0.0.0 + let container_endpoint = + SocketAddr::from((Ipv4Addr::UNSPECIFIED, cb_config.pbs.pbs_config.port)); + let (key, val) = get_env_val(PBS_ENDPOINT_ENV, &container_endpoint.to_string()); + pbs_envs.insert(key, val); + // volumes let mut pbs_volumes = vec![config_volume.clone()]; pbs_volumes.extend(chain_spec_volume.clone()); @@ -245,16 +262,10 @@ pub fn handle_docker_init(config_path: String, output_dir: String) -> Result<()> Networks::default() }; - exposed_ports_warn - .push(format!("pbs has an exported port on {}", cb_config.pbs.pbs_config.port)); - let pbs_service = Service { container_name: Some("cb_pbs".to_owned()), image: Some(cb_config.pbs.docker_image), - ports: Ports::Short(vec![format!( - "{}:{}", - cb_config.pbs.pbs_config.port, cb_config.pbs.pbs_config.port - )]), + ports, networks: pbs_networs, volumes: pbs_volumes, environment: Environment::KvPair(pbs_envs), @@ -408,7 +419,7 @@ pub fn handle_docker_init(config_path: String, output_dir: String) -> Result<()> image: Some("prom/prometheus:latest".to_owned()), volumes: vec![prom_volume, targets_volume, data_volume], // to inspect prometheus from localhost - ports: Ports::Short(vec!["9090:9090".to_owned()]), + ports: Ports::Short(vec![format!("{}:9090", metrics_config.host)]), networks: Networks::Simple(vec![METRICS_NETWORK.to_owned()]), ..Service::default() }; @@ -435,7 +446,7 @@ pub fn handle_docker_init(config_path: String, output_dir: String) -> Result<()> let grafana_service = Service { container_name: Some("cb_grafana".to_owned()), image: Some("grafana/grafana:latest".to_owned()), - ports: Ports::Short(vec!["3000:3000".to_owned()]), + ports: Ports::Short(vec![format!("{}:3000", metrics_config.host)]), networks: Networks::Simple(vec![METRICS_NETWORK.to_owned()]), depends_on: DependsOnOptions::Simple(vec!["cb_prometheus".to_owned()]), environment: Environment::List(vec!["GF_SECURITY_ADMIN_PASSWORD=admin".to_owned()]), @@ -475,7 +486,7 @@ pub fn handle_docker_init(config_path: String, output_dir: String) -> Result<()> Some(Service { container_name: Some("cb_cadvisor".to_owned()), image: Some("gcr.io/cadvisor/cadvisor".to_owned()), - ports: Ports::Short(vec![format!("{cadvisor_port}:8080")]), + ports: Ports::Short(vec![format!("{}:8080", metrics_config.host)]), networks: Networks::Simple(vec![METRICS_NETWORK.to_owned()]), volumes: vec![ Volumes::Simple("/var/run/docker.sock:/var/run/docker.sock:ro".to_owned()), @@ -540,17 +551,17 @@ pub fn handle_docker_init(config_path: String, output_dir: String) -> Result<()> Ok(()) } -// FOO=${FOO} +/// FOO=${FOO} fn get_env_same(k: &str) -> (String, Option) { get_env_interp(k, k) } -// FOO=${BAR} +/// FOO=${BAR} fn get_env_interp(k: &str, v: &str) -> (String, Option) { get_env_val(k, &format!("${{{v}}}")) } -// FOO=bar +/// FOO=bar fn get_env_val(k: &str, v: &str) -> (String, Option) { (k.into(), Some(SingleValue::String(v.into()))) } diff --git a/crates/common/src/config/constants.rs b/crates/common/src/config/constants.rs index e559b569..8ee68305 100644 --- a/crates/common/src/config/constants.rs +++ b/crates/common/src/config/constants.rs @@ -22,6 +22,9 @@ pub const PBS_MODULE_NAME: &str = "pbs"; /// Urls the pbs modules should post events to (comma separated) pub const BUILDER_URLS_ENV: &str = "CB_BUILDER_URLS"; +/// Where to receive BuilderAPI calls from beacon node +pub const PBS_ENDPOINT_ENV: &str = "CB_PBS_ENDPOINT"; + ///////////////////////// SIGNER ///////////////////////// pub const SIGNER_IMAGE_DEFAULT: &str = "ghcr.io/commit-boost/signer:latest"; diff --git a/crates/common/src/config/metrics.rs b/crates/common/src/config/metrics.rs index 264c45b1..5a812b11 100644 --- a/crates/common/src/config/metrics.rs +++ b/crates/common/src/config/metrics.rs @@ -1,11 +1,16 @@ +use std::net::Ipv4Addr; + use eyre::Result; use serde::{Deserialize, Serialize}; use super::{constants::METRICS_PORT_ENV, load_optional_env_var}; -use crate::utils::default_bool; +use crate::utils::{default_bool, default_host}; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct MetricsConfig { + /// Host for prometheus, grafana, and cadvisor + #[serde(default = "default_host")] + pub host: Ipv4Addr, /// Path to prometheus config file pub prometheus_config: String, /// Whether to start the grafana service diff --git a/crates/common/src/config/mod.rs b/crates/common/src/config/mod.rs index fa3fb560..ac537cbd 100644 --- a/crates/common/src/config/mod.rs +++ b/crates/common/src/config/mod.rs @@ -48,7 +48,7 @@ impl CommitBoostConfig { // When loading the config from the environment, it's important that every path // is replaced with the correct value if the config is loaded inside a container pub fn from_env_path() -> Result { - let config = if let Ok(path) = std::env::var(CHAIN_SPEC_ENV) { + let config = if let Some(path) = load_optional_env_var(CHAIN_SPEC_ENV) { // if the chain spec file is set, load it separately let chain: Chain = load_chain_from_file(path.parse()?)?; let rest_config: HelperConfig = load_file_from_env(CONFIG_ENV)?; diff --git a/crates/common/src/config/pbs.rs b/crates/common/src/config/pbs.rs index 60abb80d..980da29c 100644 --- a/crates/common/src/config/pbs.rs +++ b/crates/common/src/config/pbs.rs @@ -1,19 +1,30 @@ //! Configuration for the PBS module -use std::{collections::HashMap, sync::Arc}; +use std::{ + collections::HashMap, + net::{Ipv4Addr, SocketAddr}, + sync::Arc, +}; use alloy::primitives::{utils::format_ether, U256}; use eyre::{ensure, Result}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use url::Url; -use super::{constants::PBS_IMAGE_DEFAULT, CommitBoostConfig}; +use super::{ + constants::PBS_IMAGE_DEFAULT, load_optional_env_var, CommitBoostConfig, PBS_ENDPOINT_ENV, +}; use crate::{ commit::client::SignerClient, config::{load_env_var, load_file_from_env, CONFIG_ENV, MODULE_JWT_ENV, SIGNER_URL_ENV}, - pbs::{BuilderEventPublisher, DefaultTimeout, RelayClient, RelayEntry, LATE_IN_SLOT_TIME_MS}, + pbs::{ + BuilderEventPublisher, DefaultTimeout, RelayClient, RelayEntry, DEFAULT_PBS_PORT, + LATE_IN_SLOT_TIME_MS, + }, types::Chain, - utils::{as_eth_str, default_bool, default_u256, default_u64, WEI_PER_ETH}, + utils::{ + as_eth_str, default_bool, default_host, default_u16, default_u256, default_u64, WEI_PER_ETH, + }, }; #[derive(Debug, Clone, Deserialize, Serialize)] @@ -36,7 +47,11 @@ pub struct RelayConfig { #[derive(Debug, Clone, Deserialize, Serialize)] pub struct PbsConfig { + /// Host to receive BuilderAPI calls from beacon node + #[serde(default = "default_host")] + pub host: Ipv4Addr, /// Port to receive BuilderAPI calls from beacon node + #[serde(default = "default_u16::")] pub port: u16, /// Whether to forward `get_status` to relays or skip it #[serde(default = "default_bool::")] @@ -112,6 +127,8 @@ pub struct StaticPbsConfig { pub struct PbsModuleConfig { /// Chain spec pub chain: Chain, + /// Endpoint to receive BuilderAPI calls from beacon node + pub endpoint: SocketAddr, /// Pbs default config pub pbs_config: Arc, /// List of relays @@ -130,12 +147,20 @@ fn default_pbs() -> String { pub fn load_pbs_config() -> Result { let config = CommitBoostConfig::from_env_path()?; + // use endpoint from env if set, otherwise use default host and port + let endpoint = if let Some(endpoint) = load_optional_env_var(PBS_ENDPOINT_ENV) { + endpoint.parse()? + } else { + SocketAddr::from((config.pbs.pbs_config.host, config.pbs.pbs_config.port)) + }; + let relay_clients = config.relays.into_iter().map(RelayClient::new).collect::>>()?; let maybe_publiher = BuilderEventPublisher::new_from_env()?; Ok(PbsModuleConfig { chain: config.chain, + endpoint, pbs_config: Arc::new(config.pbs.pbs_config), relays: relay_clients, signer_client: None, @@ -164,6 +189,16 @@ pub fn load_pbs_custom_config() -> Result<(PbsModuleConfig, let cb_config: StubConfig = load_file_from_env(CONFIG_ENV)?; cb_config.pbs.static_config.pbs_config.validate()?; + // use endpoint from env if set, otherwise use default host and port + let endpoint = if let Some(endpoint) = load_optional_env_var(PBS_ENDPOINT_ENV) { + endpoint.parse()? + } else { + SocketAddr::from(( + cb_config.pbs.static_config.pbs_config.host, + cb_config.pbs.static_config.pbs_config.port, + )) + }; + let relay_clients = cb_config.relays.into_iter().map(RelayClient::new).collect::>>()?; let maybe_publiher = BuilderEventPublisher::new_from_env()?; @@ -180,6 +215,7 @@ pub fn load_pbs_custom_config() -> Result<(PbsModuleConfig, Ok(( PbsModuleConfig { chain: cb_config.chain, + endpoint, pbs_config: Arc::new(cb_config.pbs.static_config.pbs_config), relays: relay_clients, signer_client, diff --git a/crates/common/src/pbs/constants.rs b/crates/common/src/pbs/constants.rs index 0a7a79c4..38b93935 100644 --- a/crates/common/src/pbs/constants.rs +++ b/crates/common/src/pbs/constants.rs @@ -17,6 +17,8 @@ pub const HEADER_START_TIME_UNIX_MS: &str = "X-MEVBoost-StartTimeUnixMS"; pub const BUILDER_EVENTS_PATH: &str = "/builder_events"; pub const DEFAULT_PBS_JWT_KEY: &str = "DEFAULT_PBS"; +pub const DEFAULT_PBS_PORT: u16 = 18550; + #[non_exhaustive] pub struct DefaultTimeout; impl DefaultTimeout { diff --git a/crates/common/src/utils.rs b/crates/common/src/utils.rs index 74e7951f..cf250f88 100644 --- a/crates/common/src/utils.rs +++ b/crates/common/src/utils.rs @@ -1,4 +1,7 @@ -use std::time::{SystemTime, UNIX_EPOCH}; +use std::{ + net::Ipv4Addr, + time::{SystemTime, UNIX_EPOCH}, +}; use alloy::{ primitives::U256, @@ -122,10 +125,18 @@ pub const fn default_u64() -> u64 { U } +pub const fn default_u16() -> u16 { + U +} + pub const fn default_bool() -> bool { U } +pub const fn default_host() -> Ipv4Addr { + Ipv4Addr::LOCALHOST +} + pub const fn default_u256() -> U256 { U256::ZERO } diff --git a/crates/pbs/src/service.rs b/crates/pbs/src/service.rs index cd36b5cf..0f88becd 100644 --- a/crates/pbs/src/service.rs +++ b/crates/pbs/src/service.rs @@ -1,5 +1,3 @@ -use std::net::SocketAddr; - use cb_common::constants::COMMIT_BOOST_VERSION; use cb_metrics::provider::MetricsProvider; use eyre::{Context, Result}; @@ -18,7 +16,7 @@ pub struct PbsService; impl PbsService { pub async fn run>(state: PbsState) -> Result<()> { - let address = SocketAddr::from(([0, 0, 0, 0], state.config.pbs_config.port)); + let address = state.config.endpoint; let events_subs = state.config.event_publisher.as_ref().map(|e| e.n_subscribers()).unwrap_or_default(); info!(version = COMMIT_BOOST_VERSION, ?address, events_subs, chain =? state.config.chain, "starting PBS service"); diff --git a/tests/tests/pbs_integration.rs b/tests/tests/pbs_integration.rs index b92f7c26..81bff558 100644 --- a/tests/tests/pbs_integration.rs +++ b/tests/tests/pbs_integration.rs @@ -1,4 +1,9 @@ -use std::{sync::Arc, time::Duration, u64}; +use std::{ + net::{Ipv4Addr, SocketAddr}, + sync::Arc, + time::Duration, + u64, +}; use alloy::primitives::U256; use cb_common::{ @@ -19,6 +24,7 @@ use tracing::info; fn get_pbs_static_config(port: u16) -> PbsConfig { PbsConfig { + host: Ipv4Addr::UNSPECIFIED, port, wait_all_registrations: true, relay_check: true, @@ -35,6 +41,7 @@ fn get_pbs_static_config(port: u16) -> PbsConfig { fn to_pbs_config(chain: Chain, pbs_config: PbsConfig, relays: Vec) -> PbsModuleConfig { PbsModuleConfig { chain, + endpoint: SocketAddr::new(pbs_config.host.into(), pbs_config.port), pbs_config: Arc::new(pbs_config), signer_client: None, event_publisher: None,