From 997e11ec90213cfe1ee37395a93d067c8bc4b6a9 Mon Sep 17 00:00:00 2001 From: ltitanb Date: Thu, 1 Aug 2024 14:05:22 +0100 Subject: [PATCH 1/3] add builder events module --- Cargo.toml | 2 +- bin/src/lib.rs | 3 +- config.example.toml | 6 + crates/cli/src/docker_init.rs | 218 ++++++++----- crates/common/Cargo.toml | 7 + crates/common/src/config.rs | 305 ------------------ crates/common/src/config/constants.rs | 21 ++ crates/common/src/config/metrics.rs | 25 ++ crates/common/src/config/mod.rs | 39 +++ crates/common/src/config/module.rs | 174 ++++++++++ crates/common/src/config/pbs.rs | 156 +++++++++ crates/common/src/config/signer.rs | 48 +++ crates/common/src/config/utils.rs | 27 ++ crates/common/src/pbs/config.rs | 56 ---- crates/common/src/pbs/constants.rs | 2 +- crates/common/src/pbs/event.rs | 125 +++++++ crates/common/src/pbs/mod.rs | 6 +- crates/common/src/pbs/{types.rs => relay.rs} | 8 +- .../src/pbs}/types/beacon_block.rs | 0 .../src/pbs}/types/blinded_block_body.rs | 6 +- .../src/pbs}/types/blobs_bundle.rs | 0 .../src/pbs}/types/execution_payload.rs | 5 +- .../src/pbs}/types/get_header.rs | 4 +- .../{pbs/src => common/src/pbs}/types/kzg.rs | 0 .../{pbs/src => common/src/pbs}/types/mod.rs | 0 .../{pbs/src => common/src/pbs}/types/spec.rs | 1 + .../src => common/src/pbs}/types/utils.rs | 3 +- crates/pbs/Cargo.toml | 5 - crates/pbs/src/{boost.rs => api.rs} | 21 +- crates/pbs/src/lib.rs | 11 +- crates/pbs/src/mev_boost/get_header.rs | 19 +- crates/pbs/src/mev_boost/submit_block.rs | 6 +- crates/pbs/src/routes/get_header.rs | 8 +- crates/pbs/src/routes/register_validator.rs | 5 +- crates/pbs/src/routes/router.rs | 2 +- crates/pbs/src/routes/status.rs | 5 +- crates/pbs/src/routes/submit_block.rs | 9 +- crates/pbs/src/service.rs | 6 +- crates/pbs/src/state.rs | 23 +- docker/pbs.Dockerfile | 2 +- docker/signer.Dockerfile | 2 +- examples/builder_log/Cargo.toml | 21 ++ examples/builder_log/Dockerfile | 27 ++ examples/builder_log/src/main.rs | 33 ++ examples/da_commit/Dockerfile | 2 +- examples/da_commit/src/main.rs | 4 +- ...local_module.sh => build_local_modules.sh} | 3 +- tests/src/mock_relay.rs | 5 +- tests/src/mock_validator.rs | 3 +- tests/src/utils.rs | 5 +- tests/tests/payloads.rs | 2 +- tests/tests/pbs_integration.rs | 5 +- 52 files changed, 930 insertions(+), 551 deletions(-) delete mode 100644 crates/common/src/config.rs create mode 100644 crates/common/src/config/constants.rs create mode 100644 crates/common/src/config/metrics.rs create mode 100644 crates/common/src/config/mod.rs create mode 100644 crates/common/src/config/module.rs create mode 100644 crates/common/src/config/pbs.rs create mode 100644 crates/common/src/config/signer.rs create mode 100644 crates/common/src/config/utils.rs delete mode 100644 crates/common/src/pbs/config.rs create mode 100644 crates/common/src/pbs/event.rs rename crates/common/src/pbs/{types.rs => relay.rs} (95%) rename crates/{pbs/src => common/src/pbs}/types/beacon_block.rs (100%) rename crates/{pbs/src => common/src/pbs}/types/blinded_block_body.rs (97%) rename crates/{pbs/src => common/src/pbs}/types/blobs_bundle.rs (100%) rename crates/{pbs/src => common/src/pbs}/types/execution_payload.rs (96%) rename crates/{pbs/src => common/src/pbs}/types/get_header.rs (97%) rename crates/{pbs/src => common/src/pbs}/types/kzg.rs (100%) rename crates/{pbs/src => common/src/pbs}/types/mod.rs (100%) rename crates/{pbs/src => common/src/pbs}/types/spec.rs (98%) rename crates/{pbs/src => common/src/pbs}/types/utils.rs (96%) rename crates/pbs/src/{boost.rs => api.rs} (75%) create mode 100644 examples/builder_log/Cargo.toml create mode 100644 examples/builder_log/Dockerfile create mode 100644 examples/builder_log/src/main.rs rename scripts/{build_local_module.sh => build_local_modules.sh} (57%) diff --git a/Cargo.toml b/Cargo.toml index 908dcf9..990d853 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ members = [ "crates/metrics", "tests", - "examples/da_commit", + "examples/*", ] resolver = "2" diff --git a/bin/src/lib.rs b/bin/src/lib.rs index aee402a..9aab7bc 100644 --- a/bin/src/lib.rs +++ b/bin/src/lib.rs @@ -2,7 +2,8 @@ pub mod prelude { pub use cb_common::{ commit, commit::request::SignRequest, - config::{load_module_config, StartModuleConfig}, + config::{load_builder_module_config, load_commit_module_config, StartCommitModuleConfig}, + pbs::{BuilderEvent, BuilderEventClient, OnBuilderApiEvent}, utils::{initialize_tracing_log, utcnow_ms, utcnow_ns, utcnow_sec, utcnow_us}, }; pub use cb_metrics::provider::MetricsProvider; diff --git a/config.example.toml b/config.example.toml index bfd1add..d3ed18e 100644 --- a/config.example.toml +++ b/config.example.toml @@ -31,5 +31,11 @@ use_grafana = true [[modules]] id = "DA_COMMIT" +type = "commit" docker_image = "test_da_commit" sleep_secs = 5 + +[[modules]] +id = "BUILDER_LOG" +type = "events" +docker_image = "test_builder_log" diff --git a/crates/cli/src/docker_init.rs b/crates/cli/src/docker_init.rs index fc9636f..06fbbe9 100644 --- a/crates/cli/src/docker_init.rs +++ b/crates/cli/src/docker_init.rs @@ -2,9 +2,10 @@ use std::{path::Path, vec}; use cb_common::{ config::{ - CommitBoostConfig, CB_CONFIG_ENV, CB_CONFIG_NAME, JWTS_ENV, METRICS_SERVER_ENV, - MODULE_ID_ENV, MODULE_JWT_ENV, SIGNER_DIR_KEYS, SIGNER_DIR_KEYS_ENV, SIGNER_DIR_SECRETS, - SIGNER_DIR_SECRETS_ENV, SIGNER_KEYS, SIGNER_KEYS_ENV, SIGNER_SERVER_ENV, + CommitBoostConfig, ModuleKind, BUILDER_SERVER_ENV, CB_CONFIG_ENV, CB_CONFIG_NAME, JWTS_ENV, + METRICS_SERVER_ENV, MODULE_ID_ENV, MODULE_JWT_ENV, SIGNER_DIR_KEYS, SIGNER_DIR_KEYS_ENV, + SIGNER_DIR_SECRETS, SIGNER_DIR_SECRETS_ENV, SIGNER_KEYS, SIGNER_KEYS_ENV, + SIGNER_SERVER_ENV, }, loader::SignerLoader, utils::random_jwt, @@ -51,17 +52,101 @@ pub fn handle_docker_init(config_path: String, output_dir: String) -> Result<()> let signer_port = 20000; let signer_server = format!("cb_signer:{signer_port}"); + let builder_events_port = 30000; + let mut builder_events_modules = Vec::new(); + // setup pbs service targets.push(PrometheusTargetConfig { targets: vec![format!("cb_pbs:{metrics_port}")], labels: PrometheusLabelsConfig { job: "pbs".to_owned() }, }); - let pbs_envs = IndexMap::from([ + let mut pbs_envs = IndexMap::from([ get_env_same(CB_CONFIG_ENV), get_env_val(METRICS_SERVER_ENV, &metrics_port.to_string()), ]); + let mut needs_signer_module = cb_config.pbs.with_signer; + + // setup modules + if let Some(modules_config) = cb_config.modules { + for module in modules_config { + // TODO: support modules volumes and network + let module_cid = format!("cb_{}", module.id.to_lowercase()); + + targets.push(PrometheusTargetConfig { + targets: vec![format!("{module_cid}:{metrics_port}")], + labels: PrometheusLabelsConfig { job: module_cid.clone() }, + }); + + let module_service = match module.kind { + // a commit module needs a JWT and access to the signer network + ModuleKind::Commit => { + needs_signer_module = true; + + let jwt = random_jwt(); + let jwt_name = format!("CB_JWT_{}", module.id.to_uppercase()); + + // module ids are assumed unique, so envs dont override each other + let module_envs = IndexMap::from([ + get_env_val(MODULE_ID_ENV, &module.id), + get_env_same(CB_CONFIG_ENV), + get_env_interp(MODULE_JWT_ENV, &jwt_name), + get_env_val(METRICS_SERVER_ENV, &metrics_port.to_string()), + get_env_val(SIGNER_SERVER_ENV, &signer_server), + ]); + + envs.insert(jwt_name.clone(), jwt.clone()); + jwts.insert(module.id.clone(), jwt); + + Service { + container_name: Some(module_cid.clone()), + image: Some(module.docker_image), + // TODO: allow service to open ports here + networks: Networks::Simple(vec![ + METRICS_NETWORK.to_owned(), + SIGNER_NETWORK.to_owned(), + ]), + volumes: vec![config_volume.clone()], + environment: Environment::KvPair(module_envs), + depends_on: DependsOnOptions::Simple(vec!["cb_signer".to_owned()]), + ..Service::default() + } + } + // an event module just needs a port to listen on + ModuleKind::Events => { + // module ids are assumed unique, so envs dont override each other + let module_envs = IndexMap::from([ + get_env_val(MODULE_ID_ENV, &module.id), + get_env_same(CB_CONFIG_ENV), + get_env_val(METRICS_SERVER_ENV, &metrics_port.to_string()), + get_env_val(BUILDER_SERVER_ENV, &builder_events_port.to_string()), + ]); + + builder_events_modules.push(format!("{module_cid}:{builder_events_port}")); + + Service { + container_name: Some(module_cid.clone()), + image: Some(module.docker_image), + networks: Networks::Simple(vec![METRICS_NETWORK.to_owned()]), + volumes: vec![config_volume.clone()], + environment: Environment::KvPair(module_envs), + depends_on: DependsOnOptions::Simple(vec!["cb_pbs".to_owned()]), + ..Service::default() + } + } + }; + + services.insert(module_cid, Some(module_service)); + } + }; + + if !builder_events_modules.is_empty() { + let env = builder_events_modules.join(","); + let (k, v) = get_env_val(BUILDER_SERVER_ENV, &env); + pbs_envs.insert(k, v); + } + let pbs_service = Service { container_name: Some("cb_pbs".to_owned()), image: Some(cb_config.pbs.docker_image), @@ -77,102 +162,68 @@ pub fn handle_docker_init(config_path: String, output_dir: String) -> Result<()> services.insert("cb_pbs".to_owned(), Some(pbs_service)); - // setup modules - if let Some(modules_config) = cb_config.modules { - for module in modules_config { - // TODO: support modules volumes and network - let module_cid = format!("cb_{}", module.id.to_lowercase()); + // TODO: validate if we have signer modules but not signer config + + // setup signer service + + if let Some(signer_config) = cb_config.signer { + if needs_signer_module { + let mut volumes = vec![config_volume.clone()]; targets.push(PrometheusTargetConfig { - targets: vec![format!("{module_cid}:{metrics_port}")], - labels: PrometheusLabelsConfig { job: module_cid.clone() }, + targets: vec![format!("cb_signer:{metrics_port}")], + labels: PrometheusLabelsConfig { job: "signer".into() }, }); - let jwt = random_jwt(); - let jwt_name = format!("CB_JWT_{}", module.id.to_uppercase()); - - // module ids are assumed unique, so envs dont override each other - let module_envs = IndexMap::from([ - get_env_val(MODULE_ID_ENV, &module.id), + let mut signer_envs = IndexMap::from([ get_env_same(CB_CONFIG_ENV), - get_env_interp(MODULE_JWT_ENV, &jwt_name), + get_env_same(JWTS_ENV), get_env_val(METRICS_SERVER_ENV, &metrics_port.to_string()), - get_env_val(SIGNER_SERVER_ENV, &signer_server), + get_env_val(SIGNER_SERVER_ENV, &signer_port.to_string()), ]); - envs.insert(jwt_name.clone(), jwt.clone()); - jwts.insert(module.id.clone(), jwt); + // TODO: generalize this, different loaders may not need volumes but eg ports + match signer_config.loader { + SignerLoader::File { key_path } => { + volumes.push(Volumes::Simple(format!("./{}:{}:ro", key_path, SIGNER_KEYS))); + let (k, v) = get_env_val(SIGNER_KEYS_ENV, SIGNER_KEYS); + signer_envs.insert(k, v); + } + SignerLoader::ValidatorsDir { keys_path, secrets_path } => { + volumes.push(Volumes::Simple(format!("{}:{}:ro", keys_path, SIGNER_DIR_KEYS))); + let (k, v) = get_env_val(SIGNER_DIR_KEYS_ENV, SIGNER_DIR_KEYS); + signer_envs.insert(k, v); + + volumes.push(Volumes::Simple(format!( + "{}:{}:ro", + secrets_path, SIGNER_DIR_SECRETS + ))); + let (k, v) = get_env_val(SIGNER_DIR_SECRETS_ENV, SIGNER_DIR_SECRETS); + signer_envs.insert(k, v); + } + }; + + // write jwts to env + let jwts_json = serde_json::to_string(&jwts).unwrap().clone(); + envs.insert(JWTS_ENV.into(), format!("{jwts_json:?}")); - let module_service = Service { - container_name: Some(module_cid.clone()), - image: Some(module.docker_image), - // TODO: allow service to open ports here + let signer_service = Service { + container_name: Some("cb_signer".to_owned()), + image: Some(signer_config.docker_image), networks: Networks::Simple(vec![ METRICS_NETWORK.to_owned(), SIGNER_NETWORK.to_owned(), ]), - volumes: vec![config_volume.clone()], - environment: Environment::KvPair(module_envs), - depends_on: DependsOnOptions::Simple(vec!["cb_signer".to_owned()]), + volumes, + environment: Environment::KvPair(signer_envs), ..Service::default() }; - services.insert(module_cid, Some(module_service)); + services.insert("cb_signer".to_owned(), Some(signer_service)); } - }; - - // TODO: validate if we have signer modules but not signer config - - // setup signer service - if let Some(signer_config) = cb_config.signer { - let mut volumes = vec![config_volume.clone()]; - - targets.push(PrometheusTargetConfig { - targets: vec![format!("cb_signer:{metrics_port}")], - labels: PrometheusLabelsConfig { job: "signer".into() }, - }); - - let mut signer_envs = IndexMap::from([ - get_env_same(CB_CONFIG_ENV), - get_env_same(JWTS_ENV), - get_env_val(METRICS_SERVER_ENV, &metrics_port.to_string()), - get_env_val(SIGNER_SERVER_ENV, &signer_port.to_string()), - ]); - - // TODO: generalize this, different loaders may not need volumes but eg ports - match signer_config.loader { - SignerLoader::File { key_path } => { - volumes.push(Volumes::Simple(format!("./{}:{}:ro", key_path, SIGNER_KEYS))); - let (k, v) = get_env_val(SIGNER_KEYS_ENV, SIGNER_KEYS); - signer_envs.insert(k, v); - } - SignerLoader::ValidatorsDir { keys_path, secrets_path } => { - volumes.push(Volumes::Simple(format!("{}:{}:ro", keys_path, SIGNER_DIR_KEYS))); - let (k, v) = get_env_val(SIGNER_DIR_KEYS_ENV, SIGNER_DIR_KEYS); - signer_envs.insert(k, v); - - volumes - .push(Volumes::Simple(format!("{}:{}:ro", secrets_path, SIGNER_DIR_SECRETS))); - let (k, v) = get_env_val(SIGNER_DIR_SECRETS_ENV, SIGNER_DIR_SECRETS); - signer_envs.insert(k, v); - } - }; - - // write jwts to env - let jwts_json = serde_json::to_string(&jwts)?.clone(); - envs.insert(JWTS_ENV.into(), format!("{jwts_json:?}")); - - let signer_service = Service { - container_name: Some("cb_signer".to_owned()), - image: Some(signer_config.docker_image), - networks: Networks::Simple(vec![METRICS_NETWORK.to_owned(), SIGNER_NETWORK.to_owned()]), - volumes, - environment: Environment::KvPair(signer_envs), - ..Service::default() - }; - - services.insert("cb_signer".to_owned(), Some(signer_service)); - }; + } else if needs_signer_module { + panic!("Signer module required but no signer config provided"); + } // setup metrics services // TODO: make this metrics optional? @@ -274,6 +325,7 @@ fn get_env_interp(k: &str, v: &str) -> (String, Option) { get_env_val(k, &format!("${{{v}}}")) } +// FOO=bar fn get_env_val(k: &str, v: &str) -> (String, Option) { (k.into(), Some(SingleValue::String(v.into()))) } diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 03b09e8..f85a66e 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -8,11 +8,18 @@ rust-version.workspace = true # ethereum alloy = { workspace = true, features = ["ssz"] } ethereum_ssz.workspace = true +ssz_types.workspace = true ethereum_ssz_derive.workspace = true +ethereum_serde_utils.workspace = true +ethereum-types.workspace = true # networking +axum.workspace = true reqwest.workspace = true +# async / threads +tokio.workspace = true + # serialization toml.workspace = true serde.workspace = true diff --git a/crates/common/src/config.rs b/crates/common/src/config.rs deleted file mode 100644 index 24c54f4..0000000 --- a/crates/common/src/config.rs +++ /dev/null @@ -1,305 +0,0 @@ -use std::{collections::HashMap, sync::Arc}; - -use eyre::{eyre, ContextCompat, Result, WrapErr}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; - -use crate::{ - commit::client::SignerClient, - loader::SignerLoader, - pbs::{PbsConfig, RelayClient, RelayConfig}, - types::Chain, - utils::default_bool, -}; - -pub const MODULE_ID_ENV: &str = "CB_MODULE_ID"; -pub const MODULE_JWT_ENV: &str = "CB_SIGNER_JWT"; -pub const METRICS_SERVER_ENV: &str = "METRICS_SERVER"; -pub const SIGNER_SERVER_ENV: &str = "SIGNER_SERVER"; - -pub const CB_CONFIG_ENV: &str = "CB_CONFIG"; -pub const CB_CONFIG_NAME: &str = "/cb-config.toml"; - -pub const SIGNER_KEYS_ENV: &str = "CB_SIGNER_FILE"; -pub const SIGNER_KEYS: &str = "/keys.json"; -pub const SIGNER_DIR_KEYS_ENV: &str = "SIGNER_LOADER_DIR_KEYS"; -pub const SIGNER_DIR_KEYS: &str = "/keys"; -pub const SIGNER_DIR_SECRETS_ENV: &str = "SIGNER_LOADER_DIR_SECRETS"; -pub const SIGNER_DIR_SECRETS: &str = "/secrets"; - -pub const JWTS_ENV: &str = "CB_JWTS"; - -// TODO: replace these with an actual image in the registry -pub const PBS_DEFAULT_IMAGE: &str = "commitboost_pbs_default"; -pub const SIGNER_IMAGE: &str = "commitboost_signer"; - -#[derive(Debug, Deserialize, Serialize)] -pub struct CommitBoostConfig { - // TODO: generalize this with a spec file - pub chain: Chain, - pub relays: Vec, - pub pbs: StaticPbsConfig, - pub modules: Option>, - pub signer: Option, - pub metrics: MetricsConfig, -} - -fn load_from_file(path: &str) -> Result { - let config_file = - std::fs::read_to_string(path).wrap_err(format!("Unable to find config file: {path}"))?; - toml::from_str(&config_file).wrap_err("could not deserialize toml from string") -} - -fn load_file_from_env(env: &str) -> Result { - let path = std::env::var(env).wrap_err(format!("{env} is not set"))?; - load_from_file(&path) -} - -/// Loads a map of module id -> jwt token from a json env -fn load_jwts() -> Result> { - let jwts = std::env::var(JWTS_ENV).wrap_err(format!("{JWTS_ENV} is not set"))?; - serde_json::from_str(&jwts).wrap_err("could not deserialize json from string") -} - -impl CommitBoostConfig { - pub fn from_file(path: &str) -> Result { - load_from_file(path) - } - - pub fn from_env_path() -> Result { - load_file_from_env(CB_CONFIG_ENV) - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct SignerConfig { - /// Docker image of the module - #[serde(default = "default_signer")] - pub docker_image: String, - /// Which keys to load - pub loader: SignerLoader, -} - -fn default_signer() -> String { - SIGNER_IMAGE.to_string() -} - -#[derive(Debug)] -pub struct StartSignerConfig { - pub chain: Chain, - pub loader: SignerLoader, - pub server_port: u16, - pub jwts: HashMap, -} - -impl StartSignerConfig { - pub fn load_from_env() -> Result { - let config = CommitBoostConfig::from_env_path()?; - - let jwts = load_jwts()?; - let server_port = load_env_var(SIGNER_SERVER_ENV)?.parse()?; - - Ok(StartSignerConfig { - chain: config.chain, - loader: config.signer.expect("Signer config is missing").loader, - server_port, - jwts, - }) - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct MetricsConfig { - /// Path to prometheus config file - pub prometheus_config: String, - /// Whether to start a grafana service - pub use_grafana: bool, -} - -pub struct ModuleMetricsConfig { - /// Where to open metrics server - pub server_port: u16, -} - -impl ModuleMetricsConfig { - pub fn load_from_env() -> Result { - let server_port = load_env_var(METRICS_SERVER_ENV)?.parse()?; - Ok(ModuleMetricsConfig { server_port }) - } -} - -/// Static pbs config from config file -#[derive(Debug, Default, Deserialize, Serialize)] -pub struct StaticPbsConfig { - /// Docker image of the module - #[serde(default = "default_pbs")] - pub docker_image: String, - /// Config of pbs module - #[serde(flatten)] - pub pbs_config: PbsConfig, - /// Whether to enable the signer client - #[serde(default = "default_bool::")] - pub with_signer: bool, -} - -/// Runtime config for the pbs module with support for custom extra config -/// This will be shared across threads, so the `extra` should be thread safe, -/// e.g. wrapped in an Arc -#[derive(Debug, Clone)] -pub struct PbsModuleConfig { - /// Chain spec - pub chain: Chain, - /// Pbs default config - pub pbs_config: Arc, - /// List of relays - pub relays: Vec, - /// Signer client to call Signer API - pub signer_client: Option, - /// Opaque module config - pub extra: T, -} - -fn default_pbs() -> String { - PBS_DEFAULT_IMAGE.to_string() -} - -/// Loads the default pbs config, i.e. with no signer client or custom data -pub fn load_pbs_config() -> Result> { - let config = CommitBoostConfig::from_env_path()?; - let relay_clients = - config.relays.into_iter().map(RelayClient::new).collect::>>()?; - - Ok(PbsModuleConfig { - chain: config.chain, - pbs_config: Arc::new(config.pbs.pbs_config), - relays: relay_clients, - signer_client: None, - extra: (), - }) -} - -/// Loads a custom pbs config, i.e. with signer client and/or custom data -pub fn load_pbs_custom_config() -> Result> { - #[derive(Debug, Deserialize)] - struct CustomPbsConfig { - #[serde(flatten)] - static_config: StaticPbsConfig, - #[serde(flatten)] - extra: U, - } - - #[derive(Deserialize, Debug)] - struct StubConfig { - chain: Chain, - relays: Vec, - pbs: CustomPbsConfig, - } - - // load module config including the extra data (if any) - let cb_config: StubConfig = load_file_from_env(CB_CONFIG_ENV)?; - let relay_clients = - cb_config.relays.into_iter().map(RelayClient::new).collect::>>()?; - - let signer_client = if cb_config.pbs.static_config.with_signer { - // if custom pbs requires a signer client, load jwt - let module_jwt = load_env_var(MODULE_JWT_ENV)?; - let signer_server_address = load_env_var(SIGNER_SERVER_ENV)?; - Some(SignerClient::new(signer_server_address, &module_jwt)) - } else { - None - } - .transpose()?; - - Ok(PbsModuleConfig { - chain: cb_config.chain, - pbs_config: Arc::new(cb_config.pbs.static_config.pbs_config), - relays: relay_clients, - signer_client, - extra: cb_config.pbs.extra, - }) -} - -/// Static module config from config file -#[derive(Debug, Deserialize, Serialize)] -pub struct StaticModuleConfig { - /// Unique id of the module - pub id: String, - /// Docker image of the module - pub docker_image: String, -} - -/// Runtime config to start a module -#[derive(Debug)] -pub struct StartModuleConfig { - /// Unique id of the module - pub id: String, - /// Chain spec - pub chain: Chain, - /// Signer client to call Signer API - pub signer_client: SignerClient, - /// Opaque module config - pub extra: T, -} - -/// Loads a module config from the environment and config file: -/// - [MODULE_ID_ENV] - the id of the module to load -/// - [CB_CONFIG_ENV] - the path to the config file -/// - [MODULE_JWT_ENV] - the jwt token for the module -// TODO: add metrics url here -pub fn load_module_config() -> Result> { - let module_id = load_env_var(MODULE_ID_ENV)?; - let module_jwt = load_env_var(MODULE_JWT_ENV)?; - let signer_server_address = load_env_var(SIGNER_SERVER_ENV)?; - - #[derive(Debug, Deserialize)] - struct ThisModuleConfig { - #[serde(flatten)] - static_config: StaticModuleConfig, - #[serde(flatten)] - extra: U, - } - - #[derive(Debug, Deserialize)] - #[serde(untagged)] - enum ThisModule { - Target(ThisModuleConfig), - Other, - } - - #[derive(Deserialize, Debug)] - struct StubConfig { - chain: Chain, - modules: Vec>, - } - - // load module config including the extra data (if any) - let cb_config: StubConfig = load_file_from_env(CB_CONFIG_ENV)?; - - // find all matching modules config - let matches: Vec> = cb_config - .modules - .into_iter() - .filter_map(|m| if let ThisModule::Target(config) = m { Some(config) } else { None }) - .collect(); - - if matches.is_empty() { - Err(eyre!("Failed to find matching config type")) - } else { - let module_config = matches - .into_iter() - .find(|m| m.static_config.id == module_id) - .wrap_err(format!("failed to find module for {module_id}"))?; - - let signer_client = SignerClient::new(signer_server_address, &module_jwt)?; - - Ok(StartModuleConfig { - id: module_config.static_config.id, - chain: cb_config.chain, - signer_client, - extra: module_config.extra, - }) - } -} - -pub fn load_env_var(env: &str) -> Result { - std::env::var(env).wrap_err("{env} is not set") -} diff --git a/crates/common/src/config/constants.rs b/crates/common/src/config/constants.rs new file mode 100644 index 0000000..4d96d80 --- /dev/null +++ b/crates/common/src/config/constants.rs @@ -0,0 +1,21 @@ +pub const MODULE_ID_ENV: &str = "CB_MODULE_ID"; +pub const MODULE_JWT_ENV: &str = "CB_SIGNER_JWT"; +pub const METRICS_SERVER_ENV: &str = "METRICS_SERVER"; +pub const SIGNER_SERVER_ENV: &str = "SIGNER_SERVER"; +pub const BUILDER_SERVER_ENV: &str = "BUILDER_SERVER"; + +pub const CB_CONFIG_ENV: &str = "CB_CONFIG"; +pub const CB_CONFIG_NAME: &str = "/cb-config.toml"; + +pub const SIGNER_KEYS_ENV: &str = "CB_SIGNER_FILE"; +pub const SIGNER_KEYS: &str = "/keys.json"; +pub const SIGNER_DIR_KEYS_ENV: &str = "SIGNER_LOADER_DIR_KEYS"; +pub const SIGNER_DIR_KEYS: &str = "/keys"; +pub const SIGNER_DIR_SECRETS_ENV: &str = "SIGNER_LOADER_DIR_SECRETS"; +pub const SIGNER_DIR_SECRETS: &str = "/secrets"; + +pub const JWTS_ENV: &str = "CB_JWTS"; + +// TODO: replace these with an actual image in the registry +pub const PBS_DEFAULT_IMAGE: &str = "commitboost_pbs_default"; +pub const SIGNER_IMAGE: &str = "commitboost_signer"; diff --git a/crates/common/src/config/metrics.rs b/crates/common/src/config/metrics.rs new file mode 100644 index 0000000..e0865aa --- /dev/null +++ b/crates/common/src/config/metrics.rs @@ -0,0 +1,25 @@ +use eyre::Result; +use serde::{Deserialize, Serialize}; + +use super::{constants::METRICS_SERVER_ENV, load_env_var}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct MetricsConfig { + /// Path to prometheus config file + pub prometheus_config: String, + /// Whether to start a grafana service + pub use_grafana: bool, +} + +/// Module runtime config set after init +pub struct ModuleMetricsConfig { + /// Where to open metrics server + pub server_port: u16, +} + +impl ModuleMetricsConfig { + pub fn load_from_env() -> Result { + let server_port = load_env_var(METRICS_SERVER_ENV)?.parse()?; + Ok(ModuleMetricsConfig { server_port }) + } +} diff --git a/crates/common/src/config/mod.rs b/crates/common/src/config/mod.rs new file mode 100644 index 0000000..ce1ce18 --- /dev/null +++ b/crates/common/src/config/mod.rs @@ -0,0 +1,39 @@ +use eyre::Result; +use serde::{Deserialize, Serialize}; + +use crate::types::Chain; + +mod constants; +mod metrics; +mod module; +mod pbs; +mod signer; +mod utils; + +pub use constants::*; +pub use metrics::*; +pub use module::*; +pub use pbs::*; +pub use signer::*; +pub use utils::*; + +#[derive(Debug, Deserialize, Serialize)] +pub struct CommitBoostConfig { + // TODO: generalize this with a spec file + pub chain: Chain, + pub relays: Vec, + pub pbs: StaticPbsConfig, + pub modules: Option>, + pub signer: Option, + pub metrics: MetricsConfig, +} + +impl CommitBoostConfig { + pub fn from_file(path: &str) -> Result { + load_from_file(path) + } + + pub fn from_env_path() -> Result { + load_file_from_env(CB_CONFIG_ENV) + } +} diff --git a/crates/common/src/config/module.rs b/crates/common/src/config/module.rs new file mode 100644 index 0000000..953d098 --- /dev/null +++ b/crates/common/src/config/module.rs @@ -0,0 +1,174 @@ +use eyre::{eyre, ContextCompat, Result}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use toml::Table; + +use crate::{ + commit::client::SignerClient, + config::{ + constants::{CB_CONFIG_ENV, MODULE_ID_ENV, MODULE_JWT_ENV, SIGNER_SERVER_ENV}, + load_env_var, + utils::load_file_from_env, + BUILDER_SERVER_ENV, + }, + types::Chain, +}; + +#[derive(Debug, Deserialize, Serialize)] +pub enum ModuleKind { + #[serde(alias = "commit")] + Commit, + #[serde(alias = "events")] + Events, +} + +/// Static module config from config file +#[derive(Debug, Deserialize, Serialize)] +pub struct StaticModuleConfig { + /// Unique id of the module + pub id: String, + /// Docker image of the module + pub docker_image: String, + /// Type of the module + #[serde(rename = "type")] + pub kind: ModuleKind, +} + +/// Runtime config to start a module +#[derive(Debug)] +pub struct StartCommitModuleConfig { + /// Unique id of the module + pub id: String, + /// Chain spec + pub chain: Chain, + /// Signer client to call Signer API + pub signer_client: SignerClient, + /// Opaque module config + pub extra: T, +} + +/// Loads a module config from the environment and config file: +/// - [MODULE_ID_ENV] - the id of the module to load +/// - [CB_CONFIG_ENV] - the path to the config file +/// - [MODULE_JWT_ENV] - the jwt token for the module +// TODO: add metrics url here +pub fn load_commit_module_config() -> Result> { + let module_id = load_env_var(MODULE_ID_ENV)?; + let module_jwt = load_env_var(MODULE_JWT_ENV)?; + let signer_server_address = load_env_var(SIGNER_SERVER_ENV)?; + + #[derive(Debug, Deserialize)] + struct ThisModuleConfig { + #[serde(flatten)] + static_config: StaticModuleConfig, + #[serde(flatten)] + extra: U, + } + + #[derive(Debug, Deserialize)] + #[serde(untagged)] + enum ThisModule { + Target(ThisModuleConfig), + #[allow(dead_code)] + Other(Table), + } + + #[derive(Deserialize, Debug)] + struct StubConfig { + chain: Chain, + modules: Vec>, + } + + // load module config including the extra data (if any) + let cb_config: StubConfig = load_file_from_env(CB_CONFIG_ENV)?; + + // find all matching modules config + let matches: Vec> = cb_config + .modules + .into_iter() + .filter_map(|m| if let ThisModule::Target(config) = m { Some(config) } else { None }) + .collect(); + + if matches.is_empty() { + Err(eyre!("Failed to find matching config type")) + } else { + let module_config = matches + .into_iter() + .find(|m| m.static_config.id == module_id) + .wrap_err(format!("failed to find module for {module_id}"))?; + + let signer_client = SignerClient::new(signer_server_address, &module_jwt)?; + + Ok(StartCommitModuleConfig { + id: module_config.static_config.id, + chain: cb_config.chain, + signer_client, + extra: module_config.extra, + }) + } +} + +#[derive(Debug)] +pub struct StartBuilderModuleConfig { + /// Unique id of the module + pub id: String, + /// Chain spec + pub chain: Chain, + /// Where to listen for Builder events + pub server_port: u16, + /// Opaque module config + pub extra: T, +} + +pub fn load_builder_module_config() -> eyre::Result> +{ + let module_id = load_env_var(MODULE_ID_ENV)?; + let builder_events_port: u16 = load_env_var(BUILDER_SERVER_ENV)?.parse()?; + + #[derive(Debug, Deserialize)] + struct ThisModuleConfig { + #[serde(flatten)] + static_config: StaticModuleConfig, + #[serde(flatten)] + extra: U, + } + + #[derive(Debug, Deserialize)] + #[serde(untagged)] + enum ThisModule { + Target(ThisModuleConfig), + #[allow(dead_code)] + Other(Table), + } + + #[derive(Deserialize, Debug)] + struct StubConfig { + chain: Chain, + modules: Vec>, + } + + // load module config including the extra data (if any) + let cb_config: StubConfig = load_file_from_env(CB_CONFIG_ENV)?; + + // find all matching modules config + let matches: Vec> = cb_config + .modules + .into_iter() + .filter_map(|m| if let ThisModule::Target(config) = m { Some(config) } else { None }) + .collect(); + + if matches.is_empty() { + Err(eyre!("Failed to find matching config type")) + } else { + let module_config = matches + .into_iter() + .find(|m| m.static_config.id == module_id) + .wrap_err(format!("failed to find module for {module_id}"))?; + + Ok(StartBuilderModuleConfig { + id: module_config.static_config.id, + chain: cb_config.chain, + server_port: builder_events_port, + extra: module_config.extra, + }) + } +} diff --git a/crates/common/src/config/pbs.rs b/crates/common/src/config/pbs.rs new file mode 100644 index 0000000..7c46cdd --- /dev/null +++ b/crates/common/src/config/pbs.rs @@ -0,0 +1,156 @@ +//! Configuration for the PBS module + +use std::{collections::HashMap, sync::Arc}; + +use alloy::primitives::U256; +use eyre::Result; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +use super::{constants::PBS_DEFAULT_IMAGE, CommitBoostConfig}; +use crate::{ + commit::client::SignerClient, + config::{load_env_var, load_file_from_env, CB_CONFIG_ENV, MODULE_JWT_ENV, SIGNER_SERVER_ENV}, + pbs::{BuilderEventPublisher, DefaultTimeout, RelayClient, RelayEntry, LATE_IN_SLOT_TIME_MS}, + types::Chain, + utils::{as_eth_str, default_bool, default_u256, default_u64}, +}; + +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +pub struct RelayConfig { + /// Relay ID, if missing will default to the URL hostname from the entry + pub id: Option, + /// Relay in the form of pubkey@url + #[serde(rename = "url")] + pub entry: RelayEntry, + /// Optional headers to send with each request + pub headers: Option>, + /// Whether to enable timing games + #[serde(default = "default_bool::")] + pub enable_timing_games: bool, + /// Target time in slot when to send the first header request + pub target_first_request_ms: Option, + /// Frequency in ms to send get_header requests + pub frequency_get_header_ms: Option, +} + +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +pub struct PbsConfig { + /// Port to receive BuilderAPI calls from beacon node + pub port: u16, + /// Whether to forward `get_status`` to relays or skip it + pub relay_check: bool, + /// Timeout for get_header request in milliseconds + #[serde(default = "default_u64::<{ DefaultTimeout::GET_HEADER_MS }>")] + pub timeout_get_header_ms: u64, + /// Timeout for get_payload request in milliseconds + #[serde(default = "default_u64::<{ DefaultTimeout::GET_PAYLOAD_MS }>")] + pub timeout_get_payload_ms: u64, + /// Timeout for register_validator request in milliseconds + #[serde(default = "default_u64::<{ DefaultTimeout::REGISTER_VALIDATOR_MS }>")] + pub timeout_register_validator_ms: u64, + /// Whether to skip the relay signature verification + #[serde(default = "default_bool::")] + pub skip_sigverify: bool, + /// Minimum bid that will be accepted from get_header + #[serde(rename = "min_bid_eth", with = "as_eth_str", default = "default_u256")] + pub min_bid_wei: U256, + /// How late in the slot we consider to be "late" + #[serde(default = "default_u64::")] + pub late_in_slot_time_ms: u64, +} + +/// Static pbs config from config file +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct StaticPbsConfig { + /// Docker image of the module + #[serde(default = "default_pbs")] + pub docker_image: String, + /// Config of pbs module + #[serde(flatten)] + pub pbs_config: PbsConfig, + /// Whether to enable the signer client + #[serde(default = "default_bool::")] + pub with_signer: bool, +} + +/// Runtime config for the pbs module with support for custom extra config +/// This will be shared across threads, so the `extra` should be thread safe, +/// e.g. wrapped in an Arc +#[derive(Debug, Clone)] +pub struct PbsModuleConfig { + /// Chain spec + pub chain: Chain, + /// Pbs default config + pub pbs_config: Arc, + /// List of relays + pub relays: Vec, + /// Signer client to call Signer API + pub signer_client: Option, + /// Event publisher + pub event_publiher: Option, + /// Opaque module config + pub extra: T, +} + +fn default_pbs() -> String { + PBS_DEFAULT_IMAGE.to_string() +} + +/// Loads the default pbs config, i.e. with no signer client or custom data +pub fn load_pbs_config() -> Result> { + let config = CommitBoostConfig::from_env_path()?; + let relay_clients = + config.relays.into_iter().map(RelayClient::new).collect::>>()?; + let maybe_publiher = BuilderEventPublisher::new_from_env(); + + Ok(PbsModuleConfig { + chain: config.chain, + pbs_config: Arc::new(config.pbs.pbs_config), + relays: relay_clients, + signer_client: None, + event_publiher: maybe_publiher, + extra: (), + }) +} + +/// Loads a custom pbs config, i.e. with signer client and/or custom data +pub fn load_pbs_custom_config() -> Result> { + #[derive(Debug, Deserialize)] + struct CustomPbsConfig { + #[serde(flatten)] + static_config: StaticPbsConfig, + #[serde(flatten)] + extra: U, + } + + #[derive(Deserialize, Debug)] + struct StubConfig { + chain: Chain, + relays: Vec, + pbs: CustomPbsConfig, + } + + // load module config including the extra data (if any) + let cb_config: StubConfig = load_file_from_env(CB_CONFIG_ENV)?; + let relay_clients = + cb_config.relays.into_iter().map(RelayClient::new).collect::>>()?; + let maybe_publiher = BuilderEventPublisher::new_from_env(); + + let signer_client = if cb_config.pbs.static_config.with_signer { + // if custom pbs requires a signer client, load jwt + let module_jwt = load_env_var(MODULE_JWT_ENV)?; + let signer_server_address = load_env_var(SIGNER_SERVER_ENV)?; + Some(SignerClient::new(signer_server_address, &module_jwt)?) + } else { + None + }; + + Ok(PbsModuleConfig { + chain: cb_config.chain, + pbs_config: Arc::new(cb_config.pbs.static_config.pbs_config), + relays: relay_clients, + signer_client, + event_publiher: maybe_publiher, + extra: cb_config.pbs.extra, + }) +} diff --git a/crates/common/src/config/signer.rs b/crates/common/src/config/signer.rs new file mode 100644 index 0000000..171a9da --- /dev/null +++ b/crates/common/src/config/signer.rs @@ -0,0 +1,48 @@ +use std::collections::HashMap; + +use eyre::Result; +use serde::{Deserialize, Serialize}; + +use super::{ + constants::{SIGNER_IMAGE, SIGNER_SERVER_ENV}, + utils::{load_env_var, load_jwts}, + CommitBoostConfig, +}; +use crate::{loader::SignerLoader, types::Chain}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct SignerConfig { + /// Docker image of the module + #[serde(default = "default_signer")] + pub docker_image: String, + /// Which keys to load + pub loader: SignerLoader, +} + +fn default_signer() -> String { + SIGNER_IMAGE.to_string() +} + +#[derive(Debug)] +pub struct StartSignerConfig { + pub chain: Chain, + pub loader: SignerLoader, + pub server_port: u16, + pub jwts: HashMap, +} + +impl StartSignerConfig { + pub fn load_from_env() -> Result { + let config = CommitBoostConfig::from_env_path()?; + + let jwts = load_jwts()?; + let server_port = load_env_var(SIGNER_SERVER_ENV)?.parse()?; + + Ok(StartSignerConfig { + chain: config.chain, + loader: config.signer.expect("Signer config is missing").loader, + server_port, + jwts, + }) + } +} diff --git a/crates/common/src/config/utils.rs b/crates/common/src/config/utils.rs new file mode 100644 index 0000000..74daf63 --- /dev/null +++ b/crates/common/src/config/utils.rs @@ -0,0 +1,27 @@ +use std::collections::HashMap; + +use eyre::{Context, Result}; +use serde::de::DeserializeOwned; + +use super::constants::JWTS_ENV; + +pub fn load_env_var(env: &str) -> Result { + std::env::var(env).wrap_err("{env} is not set") +} + +pub fn load_from_file(path: &str) -> Result { + let config_file = + std::fs::read_to_string(path).wrap_err(format!("Unable to find config file: {path}"))?; + toml::from_str(&config_file).wrap_err("could not deserialize toml from string") +} + +pub fn load_file_from_env(env: &str) -> Result { + let path = std::env::var(env).wrap_err(format!("{env} is not set"))?; + load_from_file(&path) +} + +/// Loads a map of module id -> jwt token from a json env +pub fn load_jwts() -> Result> { + let jwts = std::env::var(JWTS_ENV).wrap_err(format!("{JWTS_ENV} is not set"))?; + serde_json::from_str(&jwts).wrap_err("could not deserialize json from string") +} diff --git a/crates/common/src/pbs/config.rs b/crates/common/src/pbs/config.rs deleted file mode 100644 index d690f40..0000000 --- a/crates/common/src/pbs/config.rs +++ /dev/null @@ -1,56 +0,0 @@ -//! Configuration for the PBS module - -use std::collections::HashMap; - -use alloy::primitives::U256; -use serde::{Deserialize, Serialize}; - -use super::{ - constants::{DefaultTimeout, LATE_IN_SLOT_TIME_MS}, - RelayEntry, -}; -use crate::utils::{as_eth_str, default_bool, default_u256, default_u64}; - -#[derive(Debug, Clone, Default, Deserialize, Serialize)] -pub struct RelayConfig { - /// Relay ID, if missing will default to the URL hostname from the entry - pub id: Option, - /// Relay in the form of pubkey@url - #[serde(rename = "url")] - pub entry: RelayEntry, - /// Optional headers to send with each request - pub headers: Option>, - /// Whether to enable timing games - #[serde(default = "default_bool::")] - pub enable_timing_games: bool, - /// Target time in slot when to send the first header request - pub target_first_request_ms: Option, - /// Frequency in ms to send get_header requests - pub frequency_get_header_ms: Option, -} - -#[derive(Debug, Clone, Default, Deserialize, Serialize)] -pub struct PbsConfig { - /// Port to receive BuilderAPI calls from beacon node - pub port: u16, - /// Whether to forward `get_status`` to relays or skip it - pub relay_check: bool, - /// Timeout for get_header request in milliseconds - #[serde(default = "default_u64::<{ DefaultTimeout::GET_HEADER_MS }>")] - pub timeout_get_header_ms: u64, - /// Timeout for get_payload request in milliseconds - #[serde(default = "default_u64::<{ DefaultTimeout::GET_PAYLOAD_MS }>")] - pub timeout_get_payload_ms: u64, - /// Timeout for register_validator request in milliseconds - #[serde(default = "default_u64::<{ DefaultTimeout::REGISTER_VALIDATOR_MS }>")] - pub timeout_register_validator_ms: u64, - /// Whether to skip the relay signature verification - #[serde(default = "default_bool::")] - pub skip_sigverify: bool, - /// Minimum bid that will be accepted from get_header - #[serde(rename = "min_bid_eth", with = "as_eth_str", default = "default_u256")] - pub min_bid_wei: U256, - /// How late in the slot we consider to be "late" - #[serde(default = "default_u64::")] - pub late_in_slot_time_ms: u64, -} diff --git a/crates/common/src/pbs/constants.rs b/crates/common/src/pbs/constants.rs index 80f68a1..5a5437b 100644 --- a/crates/common/src/pbs/constants.rs +++ b/crates/common/src/pbs/constants.rs @@ -12,7 +12,7 @@ pub const HEADER_VERSION_KEY: &str = "X-CommitBoost-Version"; pub const HEAVER_VERSION_VALUE: &str = "0.1.0"; pub const HEADER_START_TIME_UNIX_MS: &str = "X-MEVBoost-StartTimeUnixMS"; -pub const BUILDER_EVENTS_PATH: &str = "/events"; +pub const BUILDER_EVENTS_PATH: &str = "/builder_events"; pub const DEFAULT_PBS_JWT_KEY: &str = "DEFAULT_PBS"; #[non_exhaustive] diff --git a/crates/common/src/pbs/event.rs b/crates/common/src/pbs/event.rs new file mode 100644 index 0000000..0285235 --- /dev/null +++ b/crates/common/src/pbs/event.rs @@ -0,0 +1,125 @@ +use std::net::SocketAddr; + +use alloy::{primitives::B256, rpc::types::beacon::relay::ValidatorRegistration}; +use axum::{ + async_trait, + extract::State, + response::{IntoResponse, Response}, + routing::post, + Json, +}; +use eyre::bail; +use reqwest::StatusCode; +use serde::{Deserialize, Serialize}; +use tokio::net::TcpListener; +use tracing::{error, info, trace}; + +use super::{ + GetHeaderParams, GetHeaderReponse, SignedBlindedBeaconBlock, SubmitBlindedBlockResponse, +}; +use crate::{ + config::{load_env_var, BUILDER_SERVER_ENV}, + pbs::BUILDER_EVENTS_PATH, +}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum BuilderEvent { + GetHeaderRequest(GetHeaderParams), + GetHeaderResponse(Box>), + GetStatusEvent, + GetStatusResponse, + SubmitBlockRequest(Box), + SubmitBlockResponse(Box), + MissedPayload { block_hash: B256, relays: String }, + RegisterValidatorRequest(Vec), + RegisterValidatorResponse, +} + +#[derive(Debug, Clone)] +pub struct BuilderEventPublisher { + client: reqwest::Client, + endpoints: Vec, +} + +impl BuilderEventPublisher { + pub fn new(endpoints: Vec) -> Self { + Self { client: reqwest::Client::new(), endpoints } + } + + pub fn new_from_env() -> Option { + load_env_var(BUILDER_SERVER_ENV) + .map(|joined| { + let endpoints = joined + .split(',') + .map(|s| format!("http://{}{}", s, BUILDER_EVENTS_PATH)) + .collect(); + + Self::new(endpoints) + }) + .ok() + } + + pub fn publish(&self, event: BuilderEvent) { + for endpoint in self.endpoints.clone() { + let client = self.client.clone(); + let event = event.clone(); + + tokio::spawn(async move { + trace!("Sending events to {}", endpoint); + if let Err(err) = client + .post(endpoint) + .json(&event) + .send() + .await + .and_then(|res| res.error_for_status()) + { + error!("Failed to publish event: {:?}", err) + }; + }); + } + } + + pub fn n_subscribers(&self) -> usize { + self.endpoints.len() + } +} + +pub struct BuilderEventClient { + pub port: u16, + pub processor: T, +} + +impl BuilderEventClient { + pub fn new(port: u16, processor: T) -> Self { + Self { port, processor } + } + + pub async fn run(self) -> eyre::Result<()> { + info!("Starting builder events server on port {}", self.port); + + let router = axum::Router::new() + .route(BUILDER_EVENTS_PATH, post(handle_builder_event::)) + .with_state(self.processor); + let address = SocketAddr::from(([0, 0, 0, 0], self.port)); + let listener = TcpListener::bind(&address).await?; + + axum::serve(listener, router).await?; + + bail!("Builder events stopped") + } +} + +async fn handle_builder_event( + State(processor): State, + Json(event): Json, +) -> Response { + trace!("Handling builder event"); + processor.on_builder_api_event(event).await; + StatusCode::OK.into_response() +} + +#[async_trait] +/// This is what modules are expected to implement to process BuilderApi events +pub trait OnBuilderApiEvent { + async fn on_builder_api_event(&self, event: BuilderEvent); +} diff --git a/crates/common/src/pbs/mod.rs b/crates/common/src/pbs/mod.rs index d4b0a90..6ec4261 100644 --- a/crates/common/src/pbs/mod.rs +++ b/crates/common/src/pbs/mod.rs @@ -1,7 +1,9 @@ -mod config; mod constants; +mod event; +mod relay; mod types; -pub use config::*; pub use constants::*; +pub use event::*; +pub use relay::*; pub use types::*; diff --git a/crates/common/src/pbs/types.rs b/crates/common/src/pbs/relay.rs similarity index 95% rename from crates/common/src/pbs/types.rs rename to crates/common/src/pbs/relay.rs index eade572..2f06c7c 100644 --- a/crates/common/src/pbs/types.rs +++ b/crates/common/src/pbs/relay.rs @@ -4,16 +4,16 @@ use alloy::{ primitives::{hex::FromHex, B256}, rpc::types::beacon::BlsPublicKey, }; -use eyre::WrapErr; +use eyre::{Result, WrapErr}; use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; use serde::{Deserialize, Serialize}; use url::Url; use super::{ constants::{BULDER_API_PATH, GET_STATUS_PATH, REGISTER_VALIDATOR_PATH, SUBMIT_BLOCK_PATH}, - RelayConfig, HEADER_VERSION_KEY, HEAVER_VERSION_VALUE, + HEADER_VERSION_KEY, HEAVER_VERSION_VALUE, }; -use crate::DEFAULT_REQUEST_TIMEOUT; +use crate::{config::RelayConfig, DEFAULT_REQUEST_TIMEOUT}; /// A parsed entry of the relay url in the format: scheme://pubkey@host #[derive(Debug, Default, Clone)] pub struct RelayEntry { @@ -60,7 +60,7 @@ pub struct RelayClient { } impl RelayClient { - pub fn new(config: RelayConfig) -> eyre::Result { + pub fn new(config: RelayConfig) -> Result { let mut headers = HeaderMap::new(); headers.insert(HEADER_VERSION_KEY, HeaderValue::from_static(HEAVER_VERSION_VALUE)); diff --git a/crates/pbs/src/types/beacon_block.rs b/crates/common/src/pbs/types/beacon_block.rs similarity index 100% rename from crates/pbs/src/types/beacon_block.rs rename to crates/common/src/pbs/types/beacon_block.rs diff --git a/crates/pbs/src/types/blinded_block_body.rs b/crates/common/src/pbs/types/blinded_block_body.rs similarity index 97% rename from crates/pbs/src/types/blinded_block_body.rs rename to crates/common/src/pbs/types/blinded_block_body.rs index 353ed54..536c254 100644 --- a/crates/pbs/src/types/blinded_block_body.rs +++ b/crates/common/src/pbs/types/blinded_block_body.rs @@ -1,15 +1,17 @@ +use std::usize; + use alloy::{ primitives::{Address, B256}, rpc::types::beacon::{BlsPublicKey, BlsSignature}, }; -use cb_common::utils::as_str; use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; -use ssz_types::{BitList, BitVector, FixedVector, VariableList}; +use ssz_types::{typenum, BitList, BitVector, FixedVector, VariableList}; use super::{ execution_payload::ExecutionPayloadHeader, kzg::KzgCommitments, spec::EthSpec, utils::*, }; +use crate::utils::as_str; #[derive(Debug, Default, Clone, Serialize, Deserialize, Encode, Decode)] pub struct BlindedBeaconBlockBody { diff --git a/crates/pbs/src/types/blobs_bundle.rs b/crates/common/src/pbs/types/blobs_bundle.rs similarity index 100% rename from crates/pbs/src/types/blobs_bundle.rs rename to crates/common/src/pbs/types/blobs_bundle.rs diff --git a/crates/pbs/src/types/execution_payload.rs b/crates/common/src/pbs/types/execution_payload.rs similarity index 96% rename from crates/pbs/src/types/execution_payload.rs rename to crates/common/src/pbs/types/execution_payload.rs index 92626ec..8c75ef9 100644 --- a/crates/pbs/src/types/execution_payload.rs +++ b/crates/common/src/pbs/types/execution_payload.rs @@ -1,5 +1,4 @@ use alloy::primitives::{Address, B256, U256}; -use cb_common::utils::as_str; use ethereum_types::{Address as EAddress, U256 as EU256}; use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; @@ -7,6 +6,7 @@ use ssz_types::{FixedVector, VariableList}; use tree_hash_derive::TreeHash; use super::{spec::EthSpec, utils::*}; +use crate::utils::as_str; pub const EMPTY_TX_ROOT_HASH: [u8; 32] = [ 127, 254, 36, 30, 166, 1, 135, 253, 176, 24, 123, 250, 34, 222, 53, 209, 249, 190, 215, 171, 6, @@ -96,8 +96,7 @@ mod tests { use ssz_types::VariableList; use tree_hash::TreeHash; - use super::Transactions; - use crate::types::{execution_payload::EMPTY_TX_ROOT_HASH, spec::DenebSpec}; + use crate::pbs::types::{execution_payload::Transactions, spec::DenebSpec, EMPTY_TX_ROOT_HASH}; #[test] fn test_empty_tx_root_hash() { diff --git a/crates/pbs/src/types/get_header.rs b/crates/common/src/pbs/types/get_header.rs similarity index 97% rename from crates/pbs/src/types/get_header.rs rename to crates/common/src/pbs/types/get_header.rs index d6942c2..531e2dd 100644 --- a/crates/pbs/src/types/get_header.rs +++ b/crates/common/src/pbs/types/get_header.rs @@ -14,7 +14,7 @@ use super::{ utils::{as_dec_str, VersionedResponse}, }; -#[derive(Debug, Deserialize, Clone, Copy)] +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] pub struct GetHeaderParams { pub slot: u64, pub parent_hash: B256, @@ -67,9 +67,9 @@ impl ExecutionPayloadHeaderMessage { #[cfg(test)] mod tests { use alloy::primitives::U256; - use cb_common::{signature::verify_signed_builder_message, types::Chain}; use super::GetHeaderReponse; + use crate::{signature::verify_signed_builder_message, types::Chain}; #[test] fn test_get_header() { diff --git a/crates/pbs/src/types/kzg.rs b/crates/common/src/pbs/types/kzg.rs similarity index 100% rename from crates/pbs/src/types/kzg.rs rename to crates/common/src/pbs/types/kzg.rs diff --git a/crates/pbs/src/types/mod.rs b/crates/common/src/pbs/types/mod.rs similarity index 100% rename from crates/pbs/src/types/mod.rs rename to crates/common/src/pbs/types/mod.rs diff --git a/crates/pbs/src/types/spec.rs b/crates/common/src/pbs/types/spec.rs similarity index 98% rename from crates/pbs/src/types/spec.rs rename to crates/common/src/pbs/types/spec.rs index 88f4ba3..73e7ad0 100644 --- a/crates/pbs/src/types/spec.rs +++ b/crates/common/src/pbs/types/spec.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; +use ssz_types::typenum; pub trait EthSpec { type MaxProposerSlashings: typenum::Unsigned + std::fmt::Debug; diff --git a/crates/pbs/src/types/utils.rs b/crates/common/src/pbs/types/utils.rs similarity index 96% rename from crates/pbs/src/types/utils.rs rename to crates/common/src/pbs/types/utils.rs index 3bcdc02..4ec53b6 100644 --- a/crates/pbs/src/types/utils.rs +++ b/crates/common/src/pbs/types/utils.rs @@ -3,8 +3,7 @@ use serde::{Deserialize, Serialize}; pub mod quoted_variable_list_u64 { use serde::{ser::SerializeSeq, Deserializer, Serializer}; use serde_utils::quoted_u64_vec::{QuotedIntVecVisitor, QuotedIntWrapper}; - use ssz_types::VariableList; - use typenum::Unsigned; + use ssz_types::{typenum::Unsigned, VariableList}; pub fn serialize(value: &VariableList, serializer: S) -> Result where diff --git a/crates/pbs/Cargo.toml b/crates/pbs/Cargo.toml index ddf1c34..c07f40c 100644 --- a/crates/pbs/Cargo.toml +++ b/crates/pbs/Cargo.toml @@ -11,11 +11,6 @@ cb-signer.workspace = true # ethereum alloy.workspace = true -ethereum_ssz.workspace = true -ethereum_ssz_derive.workspace = true -ssz_types.workspace = true -ethereum_serde_utils.workspace = true -ethereum-types.workspace = true # networking axum.workspace = true diff --git a/crates/pbs/src/boost.rs b/crates/pbs/src/api.rs similarity index 75% rename from crates/pbs/src/boost.rs rename to crates/pbs/src/api.rs index c737cf7..9418db3 100644 --- a/crates/pbs/src/boost.rs +++ b/crates/pbs/src/api.rs @@ -1,13 +1,13 @@ -use std::fmt::Debug; - -use alloy::{primitives::B256, rpc::types::beacon::relay::ValidatorRegistration}; +use alloy::rpc::types::beacon::relay::ValidatorRegistration; use async_trait::async_trait; use axum::{http::HeaderMap, Router}; +use cb_common::pbs::{ + GetHeaderParams, GetHeaderReponse, SignedBlindedBeaconBlock, SubmitBlindedBlockResponse, +}; use crate::{ mev_boost, state::{BuilderApiState, PbsState}, - GetHeaderParams, GetHeaderReponse, SignedBlindedBeaconBlock, SubmitBlindedBlockResponse, }; #[async_trait] @@ -52,16 +52,3 @@ pub trait BuilderApi: 'static { pub struct DefaultBuilderApi; impl BuilderApi<()> for DefaultBuilderApi {} - -#[derive(Debug, Clone)] -pub enum BuilderEvent { - GetHeaderRequest(GetHeaderParams), - GetHeaderResponse(Box>), - GetStatusEvent, - GetStatusResponse, - SubmitBlockRequest(Box), - SubmitBlockResponse(Box), - MissedPayload { block_hash: B256, relays: String }, - RegisterValidatorRequest(Vec), - RegisterValidatorResponse, -} diff --git a/crates/pbs/src/lib.rs b/crates/pbs/src/lib.rs index 6acb002..47eeee6 100644 --- a/crates/pbs/src/lib.rs +++ b/crates/pbs/src/lib.rs @@ -1,6 +1,6 @@ // implements https://github.com/ethereum/builder-specs and multiplexes to multiple builderAPI compatible clients (ie MEV Boost relays) -mod boost; +mod api; mod constants; mod error; mod metrics; @@ -8,12 +8,7 @@ mod mev_boost; mod routes; mod service; mod state; -mod types; -pub use boost::*; +pub use api::*; pub use service::PbsService; -pub use state::{BuilderApiState, BuilderEventReceiver, PbsState}; -// FIXME only used in tests -pub use types::{ - GetHeaderParams, GetHeaderReponse, SignedBlindedBeaconBlock, SubmitBlindedBlockResponse, -}; +pub use state::{BuilderApiState, PbsState}; diff --git a/crates/pbs/src/mev_boost/get_header.rs b/crates/pbs/src/mev_boost/get_header.rs index b6bd23f..be45384 100644 --- a/crates/pbs/src/mev_boost/get_header.rs +++ b/crates/pbs/src/mev_boost/get_header.rs @@ -6,7 +6,11 @@ use alloy::{ }; use axum::http::{HeaderMap, HeaderValue}; use cb_common::{ - pbs::{PbsConfig, RelayClient, HEADER_SLOT_UUID_KEY, HEADER_START_TIME_UNIX_MS}, + config::PbsConfig, + pbs::{ + GetHeaderParams, GetHeaderReponse, RelayClient, SignedExecutionPayloadHeader, + EMPTY_TX_ROOT_HASH, HEADER_SLOT_UUID_KEY, HEADER_START_TIME_UNIX_MS, + }, signature::verify_signed_builder_message, types::Chain, utils::{get_user_agent, ms_into_slot, utcnow_ms}, @@ -21,8 +25,6 @@ use crate::{ error::{PbsError, ValidationError}, metrics::{RELAY_LATENCY, RELAY_STATUS_CODE}, state::{BuilderApiState, PbsState}, - types::{SignedExecutionPayloadHeader, EMPTY_TX_ROOT_HASH}, - GetHeaderParams, GetHeaderReponse, }; /// Implements https://ethereum.github.io/builder-specs/#/Builder/getHeader @@ -336,13 +338,14 @@ mod tests { rpc::types::beacon::BlsPublicKey, }; use blst::min_pk; - use cb_common::{signature::sign_builder_message, types::Chain}; + use cb_common::{ + pbs::{SignedExecutionPayloadHeader, EMPTY_TX_ROOT_HASH}, + signature::sign_builder_message, + types::Chain, + }; use super::validate_header; - use crate::{ - error::ValidationError, - types::{SignedExecutionPayloadHeader, EMPTY_TX_ROOT_HASH}, - }; + use crate::error::ValidationError; #[test] fn test_validate_header() { diff --git a/crates/pbs/src/mev_boost/submit_block.rs b/crates/pbs/src/mev_boost/submit_block.rs index 853eccb..8eb3894 100644 --- a/crates/pbs/src/mev_boost/submit_block.rs +++ b/crates/pbs/src/mev_boost/submit_block.rs @@ -2,7 +2,10 @@ use std::time::{Duration, Instant}; use axum::http::{HeaderMap, HeaderValue}; use cb_common::{ - pbs::{RelayClient, HEADER_SLOT_UUID_KEY, HEADER_START_TIME_UNIX_MS}, + pbs::{ + RelayClient, SignedBlindedBeaconBlock, SubmitBlindedBlockResponse, HEADER_SLOT_UUID_KEY, + HEADER_START_TIME_UNIX_MS, + }, utils::{get_user_agent, utcnow_ms}, }; use futures::future::select_ok; @@ -14,7 +17,6 @@ use crate::{ error::{PbsError, ValidationError}, metrics::{RELAY_LATENCY, RELAY_STATUS_CODE}, state::{BuilderApiState, PbsState}, - types::{SignedBlindedBeaconBlock, SubmitBlindedBlockResponse}, }; /// Implements https://ethereum.github.io/builder-specs/#/Builder/submitBlindedBlock diff --git a/crates/pbs/src/routes/get_header.rs b/crates/pbs/src/routes/get_header.rs index 1691a6b..7c61b84 100644 --- a/crates/pbs/src/routes/get_header.rs +++ b/crates/pbs/src/routes/get_header.rs @@ -4,18 +4,20 @@ use axum::{ http::HeaderMap, response::IntoResponse, }; -use cb_common::utils::{get_user_agent, ms_into_slot}; +use cb_common::{ + pbs::{BuilderEvent, GetHeaderParams}, + utils::{get_user_agent, ms_into_slot}, +}; use reqwest::StatusCode; use tracing::{error, info}; use uuid::Uuid; use crate::{ - boost::BuilderApi, + api::BuilderApi, constants::GET_HEADER_ENDPOINT_TAG, error::PbsClientError, metrics::BEACON_NODE_STATUS, state::{BuilderApiState, PbsState}, - BuilderEvent, GetHeaderParams, }; #[tracing::instrument(skip_all, name = "get_header", fields(req_id = %Uuid::new_v4(), slot = params.slot))] diff --git a/crates/pbs/src/routes/register_validator.rs b/crates/pbs/src/routes/register_validator.rs index 9bab3ae..bcf90dd 100644 --- a/crates/pbs/src/routes/register_validator.rs +++ b/crates/pbs/src/routes/register_validator.rs @@ -1,17 +1,16 @@ use alloy::rpc::types::beacon::relay::ValidatorRegistration; use axum::{extract::State, http::HeaderMap, response::IntoResponse, Json}; -use cb_common::utils::get_user_agent; +use cb_common::{pbs::BuilderEvent, utils::get_user_agent}; use reqwest::StatusCode; use tracing::{error, info, trace}; use uuid::Uuid; use crate::{ - boost::BuilderApi, + api::BuilderApi, constants::REGISTER_VALIDATOR_ENDPOINT_TAG, error::PbsClientError, metrics::BEACON_NODE_STATUS, state::{BuilderApiState, PbsState}, - BuilderEvent, }; #[tracing::instrument(skip_all, name = "register_validators", fields(req_id = %Uuid::new_v4()))] diff --git a/crates/pbs/src/routes/router.rs b/crates/pbs/src/routes/router.rs index 13af21c..305362a 100644 --- a/crates/pbs/src/routes/router.rs +++ b/crates/pbs/src/routes/router.rs @@ -8,7 +8,7 @@ use cb_common::pbs::{ use super::{handle_get_header, handle_get_status, handle_register_validator, handle_submit_block}; use crate::{ - boost::BuilderApi, + api::BuilderApi, state::{BuilderApiState, PbsState}, }; diff --git a/crates/pbs/src/routes/status.rs b/crates/pbs/src/routes/status.rs index bd575e9..4c2031f 100644 --- a/crates/pbs/src/routes/status.rs +++ b/crates/pbs/src/routes/status.rs @@ -1,16 +1,15 @@ use axum::{extract::State, http::HeaderMap, response::IntoResponse}; -use cb_common::utils::get_user_agent; +use cb_common::{pbs::BuilderEvent, utils::get_user_agent}; use reqwest::StatusCode; use tracing::{error, info}; use uuid::Uuid; use crate::{ - boost::BuilderApi, + api::BuilderApi, constants::STATUS_ENDPOINT_TAG, error::PbsClientError, metrics::BEACON_NODE_STATUS, state::{BuilderApiState, PbsState}, - BuilderEvent, }; #[tracing::instrument(skip_all, name = "status", fields(req_id = %Uuid::new_v4()))] diff --git a/crates/pbs/src/routes/submit_block.rs b/crates/pbs/src/routes/submit_block.rs index 4936082..0515fa9 100644 --- a/crates/pbs/src/routes/submit_block.rs +++ b/crates/pbs/src/routes/submit_block.rs @@ -1,17 +1,18 @@ use axum::{extract::State, http::HeaderMap, response::IntoResponse, Json}; -use cb_common::utils::{get_user_agent, timestamp_of_slot_start_millis, utcnow_ms}; +use cb_common::{ + pbs::{BuilderEvent, SignedBlindedBeaconBlock}, + utils::{get_user_agent, timestamp_of_slot_start_millis, utcnow_ms}, +}; use reqwest::StatusCode; use tracing::{error, info, trace, warn}; use uuid::Uuid; use crate::{ - boost::BuilderApi, + api::BuilderApi, constants::SUBMIT_BLINDED_BLOCK_ENDPOINT_TAG, error::PbsClientError, metrics::BEACON_NODE_STATUS, state::{BuilderApiState, PbsState}, - types::SignedBlindedBeaconBlock, - BuilderEvent, }; #[tracing::instrument(skip_all, name = "submit_blinded_block", fields(req_id = %Uuid::new_v4(), slot = signed_blinded_block.message.slot))] diff --git a/crates/pbs/src/service.rs b/crates/pbs/src/service.rs index 3c0bba1..b23063b 100644 --- a/crates/pbs/src/service.rs +++ b/crates/pbs/src/service.rs @@ -7,7 +7,7 @@ use tokio::net::TcpListener; use tracing::{error, info}; use crate::{ - boost::BuilderApi, + api::BuilderApi, metrics::PBS_METRICS_REGISTRY, routes::create_app_router, state::{BuilderApiState, PbsState}, @@ -24,9 +24,11 @@ impl PbsService { // } let address = SocketAddr::from(([0, 0, 0, 0], state.config.pbs_config.port)); + let events_subs = + state.config.event_publiher.as_ref().map(|e| e.n_subscribers()).unwrap_or_default(); let app = create_app_router::(state); - info!(?address, "Starting PBS service"); + info!(?address, events_subs, "Starting PBS service"); let listener = TcpListener::bind(address).await.expect("failed tcp binding"); diff --git a/crates/pbs/src/state.rs b/crates/pbs/src/state.rs index 9daa1dc..6f0616c 100644 --- a/crates/pbs/src/state.rs +++ b/crates/pbs/src/state.rs @@ -6,20 +6,15 @@ use std::{ use alloy::{primitives::B256, rpc::types::beacon::BlsPublicKey}; use cb_common::{ - config::PbsModuleConfig, - pbs::{PbsConfig, RelayClient}, + config::{PbsConfig, PbsModuleConfig}, + pbs::{BuilderEvent, GetHeaderReponse, RelayClient}, }; use dashmap::DashMap; -use tokio::sync::broadcast; use uuid::Uuid; -use crate::{types::GetHeaderReponse, BuilderEvent}; - pub trait BuilderApiState: fmt::Debug + Default + Clone + Sync + Send + 'static {} impl BuilderApiState for () {} -pub type BuilderEventReceiver = broadcast::Receiver; - /// State for the Pbs module. It can be extended in two ways: /// - By adding extra configs to be loaded at startup /// - By adding extra data to the state @@ -29,8 +24,6 @@ pub struct PbsState { pub config: PbsModuleConfig, /// Opaque extra data for library use pub data: S, - /// Pubsliher for builder events - event_publisher: broadcast::Sender, /// Info about the latest slot and its uuid current_slot_info: Arc>, /// Keeps track of which relays delivered which block for which slot @@ -42,12 +35,9 @@ where S: BuilderApiState, { pub fn new(config: PbsModuleConfig) -> Self { - let (tx, _) = broadcast::channel(10); - Self { config, data: S::default(), - event_publisher: tx, current_slot_info: Arc::new(Mutex::new((0, Uuid::default()))), bid_cache: Arc::new(DashMap::new()), } @@ -58,12 +48,9 @@ where } pub fn publish_event(&self, e: BuilderEvent) { - // ignore client errors - let _ = self.event_publisher.send(e); - } - - pub fn subscribe_events(&self) -> BuilderEventReceiver { - self.event_publisher.subscribe() + if let Some(publisher) = self.config.event_publiher.as_ref() { + publisher.publish(e); + } } pub fn get_or_update_slot_uuid(&self, last_slot: u64) -> Uuid { diff --git a/docker/pbs.Dockerfile b/docker/pbs.Dockerfile index db27136..5219750 100644 --- a/docker/pbs.Dockerfile +++ b/docker/pbs.Dockerfile @@ -11,7 +11,7 @@ COPY --from=planner /app/recipe.json recipe.json RUN cargo chef cook --release --recipe-path recipe.json COPY . . -RUN cargo build --release +RUN cargo build --release --bin default-pbs FROM ubuntu AS runtime diff --git a/docker/signer.Dockerfile b/docker/signer.Dockerfile index 99b153d..d38917e 100644 --- a/docker/signer.Dockerfile +++ b/docker/signer.Dockerfile @@ -11,7 +11,7 @@ COPY --from=planner /app/recipe.json recipe.json RUN cargo chef cook --release --recipe-path recipe.json COPY . . -RUN cargo build --release +RUN cargo build --release --bin signer-module FROM ubuntu AS runtime diff --git a/examples/builder_log/Cargo.toml b/examples/builder_log/Cargo.toml new file mode 100644 index 0000000..ee5b278 --- /dev/null +++ b/examples/builder_log/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "builder_log" +version.workspace = true +edition.workspace = true +rust-version.workspace = true + +[dependencies] +commit-boost = { path = "../../bin" } + +# networking +reqwest.workspace = true + +# async / threads +tokio.workspace = true +async-trait.workspace = true + +# serialization +serde.workspace = true + +# telemetry +tracing.workspace = true diff --git a/examples/builder_log/Dockerfile b/examples/builder_log/Dockerfile new file mode 100644 index 0000000..2c1353a --- /dev/null +++ b/examples/builder_log/Dockerfile @@ -0,0 +1,27 @@ +FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef +WORKDIR /app + +FROM chef AS planner +COPY . . +RUN cargo chef prepare --recipe-path recipe.json + +FROM chef AS builder +COPY --from=planner /app/recipe.json recipe.json + +RUN cargo chef cook --release --recipe-path recipe.json + +COPY . . +RUN cargo build --release --bin builder_log + + +FROM ubuntu AS runtime +WORKDIR /app + +RUN apt-get update +RUN apt-get install -y openssl ca-certificates libssl3 libssl-dev + +COPY --from=builder /app/target/release/builder_log /usr/local/bin +ENTRYPOINT ["/usr/local/bin/builder_log"] + + + diff --git a/examples/builder_log/src/main.rs b/examples/builder_log/src/main.rs new file mode 100644 index 0000000..6bad4d0 --- /dev/null +++ b/examples/builder_log/src/main.rs @@ -0,0 +1,33 @@ +use async_trait::async_trait; +use commit_boost::prelude::*; +use tracing::{error, info}; + +#[derive(Debug, Clone)] +struct LogProcessor; + +#[async_trait] +impl OnBuilderApiEvent for LogProcessor { + async fn on_builder_api_event(&self, event: BuilderEvent) { + info!(?event, "Received builder event"); + } +} + +#[tokio::main] +async fn main() { + initialize_tracing_log(); + + match load_builder_module_config::<()>() { + Ok(config) => { + info!(module_id = config.id, "Starting module"); + + let client = BuilderEventClient::new(config.server_port, LogProcessor); + + if let Err(err) = client.run().await { + error!(?err, "Service failed"); + } + } + Err(err) => { + error!(?err, "Failed to load module config"); + } + } +} diff --git a/examples/da_commit/Dockerfile b/examples/da_commit/Dockerfile index 57bf67e..25c88dc 100644 --- a/examples/da_commit/Dockerfile +++ b/examples/da_commit/Dockerfile @@ -11,7 +11,7 @@ COPY --from=planner /app/recipe.json recipe.json RUN cargo chef cook --release --recipe-path recipe.json COPY . . -RUN cargo build --release +RUN cargo build --release --bin da_commit FROM ubuntu AS runtime diff --git a/examples/da_commit/src/main.rs b/examples/da_commit/src/main.rs index 10dda09..77ae251 100644 --- a/examples/da_commit/src/main.rs +++ b/examples/da_commit/src/main.rs @@ -24,7 +24,7 @@ struct Datagram { } struct DaCommitService { - config: StartModuleConfig, + config: StartCommitModuleConfig, } // Extra configurations parameters can be set here and will be automatically @@ -78,7 +78,7 @@ async fn main() -> Result<()> { // Spin up a server that exposes the /metrics endpoint to Prometheus MetricsProvider::load_and_run(MY_CUSTOM_REGISTRY.clone())?; - match load_module_config::() { + match load_commit_module_config::() { Ok(config) => { info!( module_id = config.id, diff --git a/scripts/build_local_module.sh b/scripts/build_local_modules.sh similarity index 57% rename from scripts/build_local_module.sh rename to scripts/build_local_modules.sh index a92543d..ddbad51 100644 --- a/scripts/build_local_module.sh +++ b/scripts/build_local_modules.sh @@ -2,4 +2,5 @@ set -euo pipefail -docker build -t test_da_commit . -f examples/da_commit/Dockerfile \ No newline at end of file +docker build -t test_da_commit . -f examples/da_commit/Dockerfile +docker build -t test_builder_log . -f examples/builder_log/Dockerfile \ No newline at end of file diff --git a/tests/src/mock_relay.rs b/tests/src/mock_relay.rs index 9ed39e4..b43a5b5 100644 --- a/tests/src/mock_relay.rs +++ b/tests/src/mock_relay.rs @@ -13,13 +13,12 @@ use axum::{ }; use cb_common::{ pbs::{ - BULDER_API_PATH, GET_HEADER_PATH, GET_STATUS_PATH, REGISTER_VALIDATOR_PATH, - SUBMIT_BLOCK_PATH, + GetHeaderParams, GetHeaderReponse, SubmitBlindedBlockResponse, BULDER_API_PATH, + GET_HEADER_PATH, GET_STATUS_PATH, REGISTER_VALIDATOR_PATH, SUBMIT_BLOCK_PATH, }, signer::Signer, types::Chain, }; -use cb_pbs::{GetHeaderParams, GetHeaderReponse, SubmitBlindedBlockResponse}; use tracing::debug; use tree_hash::TreeHash; diff --git a/tests/src/mock_validator.rs b/tests/src/mock_validator.rs index 9a4ae41..ac0a734 100644 --- a/tests/src/mock_validator.rs +++ b/tests/src/mock_validator.rs @@ -2,8 +2,7 @@ use alloy::{ primitives::B256, rpc::types::beacon::{relay::ValidatorRegistration, BlsPublicKey}, }; -use cb_common::pbs::RelayClient; -use cb_pbs::{GetHeaderReponse, SignedBlindedBeaconBlock}; +use cb_common::pbs::{GetHeaderReponse, RelayClient, SignedBlindedBeaconBlock}; use reqwest::Error; use crate::utils::generate_mock_relay; diff --git a/tests/src/utils.rs b/tests/src/utils.rs index 08307c8..1072ee0 100644 --- a/tests/src/utils.rs +++ b/tests/src/utils.rs @@ -1,7 +1,10 @@ use std::sync::Once; use alloy::rpc::types::beacon::BlsPublicKey; -use cb_common::pbs::{RelayClient, RelayConfig, RelayEntry}; +use cb_common::{ + config::RelayConfig, + pbs::{RelayClient, RelayEntry}, +}; use eyre::Result; pub fn get_local_address(port: u16) -> String { diff --git a/tests/tests/payloads.rs b/tests/tests/payloads.rs index f1ddce7..38cd716 100644 --- a/tests/tests/payloads.rs +++ b/tests/tests/payloads.rs @@ -1,7 +1,7 @@ use std::fs; use alloy::rpc::types::beacon::relay::ValidatorRegistration; -use cb_pbs::{SignedBlindedBeaconBlock, SubmitBlindedBlockResponse}; +use cb_common::pbs::{SignedBlindedBeaconBlock, SubmitBlindedBlockResponse}; #[test] fn test_registrations() { let file = fs::read("data/registration_holesky.json").unwrap(); diff --git a/tests/tests/pbs_integration.rs b/tests/tests/pbs_integration.rs index a3d089e..a0d452d 100644 --- a/tests/tests/pbs_integration.rs +++ b/tests/tests/pbs_integration.rs @@ -2,8 +2,8 @@ use std::{net::SocketAddr, sync::Arc, time::Duration, u64}; use alloy::primitives::U256; use cb_common::{ - config::PbsModuleConfig, - pbs::{PbsConfig, RelayClient}, + config::{PbsConfig, PbsModuleConfig}, + pbs::RelayClient, signer::Signer, types::Chain, }; @@ -50,6 +50,7 @@ fn to_pbs_config( chain, pbs_config: Arc::new(pbs_config), signer_client: None, + event_publiher: None, extra: (), relays, } From 3aa8dc816887332a2a2d660e0ca40435dbef8142 Mon Sep 17 00:00:00 2001 From: ltitanb Date: Thu, 1 Aug 2024 14:18:44 +0100 Subject: [PATCH 2/3] clippy --- crates/common/src/pbs/types/blinded_block_body.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/common/src/pbs/types/blinded_block_body.rs b/crates/common/src/pbs/types/blinded_block_body.rs index 536c254..ebad367 100644 --- a/crates/common/src/pbs/types/blinded_block_body.rs +++ b/crates/common/src/pbs/types/blinded_block_body.rs @@ -1,5 +1,3 @@ -use std::usize; - use alloy::{ primitives::{Address, B256}, rpc::types::beacon::{BlsPublicKey, BlsSignature}, From 43ced3d1721ceac4c6b38582345d2065d45b8150 Mon Sep 17 00:00:00 2001 From: ltitanb Date: Thu, 1 Aug 2024 18:04:29 +0100 Subject: [PATCH 3/3] comments --- crates/common/src/config/module.rs | 72 +++++++++++++++--------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/crates/common/src/config/module.rs b/crates/common/src/config/module.rs index 953d098..508c6b5 100644 --- a/crates/common/src/config/module.rs +++ b/crates/common/src/config/module.rs @@ -1,4 +1,4 @@ -use eyre::{eyre, ContextCompat, Result}; +use eyre::{ContextCompat, Result}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use toml::Table; @@ -85,26 +85,27 @@ pub fn load_commit_module_config() -> Result> = cb_config .modules .into_iter() - .filter_map(|m| if let ThisModule::Target(config) = m { Some(config) } else { None }) + .filter_map(|m| match m { + ThisModule::Target(config) => Some(config), + _ => None, + }) .collect(); - if matches.is_empty() { - Err(eyre!("Failed to find matching config type")) - } else { - let module_config = matches - .into_iter() - .find(|m| m.static_config.id == module_id) - .wrap_err(format!("failed to find module for {module_id}"))?; - - let signer_client = SignerClient::new(signer_server_address, &module_jwt)?; - - Ok(StartCommitModuleConfig { - id: module_config.static_config.id, - chain: cb_config.chain, - signer_client, - extra: module_config.extra, - }) - } + eyre::ensure!(!matches.is_empty(), "Failed to find matching config type"); + + let module_config = matches + .into_iter() + .find(|m| m.static_config.id == module_id) + .wrap_err(format!("failed to find module for {module_id}"))?; + + let signer_client = SignerClient::new(signer_server_address, &module_jwt)?; + + Ok(StartCommitModuleConfig { + id: module_config.static_config.id, + chain: cb_config.chain, + signer_client, + extra: module_config.extra, + }) } #[derive(Debug)] @@ -153,22 +154,23 @@ pub fn load_builder_module_config() -> eyre::Result> = cb_config .modules .into_iter() - .filter_map(|m| if let ThisModule::Target(config) = m { Some(config) } else { None }) + .filter_map(|m| match m { + ThisModule::Target(config) => Some(config), + _ => None, + }) .collect(); - if matches.is_empty() { - Err(eyre!("Failed to find matching config type")) - } else { - let module_config = matches - .into_iter() - .find(|m| m.static_config.id == module_id) - .wrap_err(format!("failed to find module for {module_id}"))?; - - Ok(StartBuilderModuleConfig { - id: module_config.static_config.id, - chain: cb_config.chain, - server_port: builder_events_port, - extra: module_config.extra, - }) - } + eyre::ensure!(!matches.is_empty(), "Failed to find matching config type"); + + let module_config = matches + .into_iter() + .find(|m| m.static_config.id == module_id) + .wrap_err(format!("failed to find module for {module_id}"))?; + + Ok(StartBuilderModuleConfig { + id: module_config.static_config.id, + chain: cb_config.chain, + server_port: builder_events_port, + extra: module_config.extra, + }) }