diff --git a/bin/default_pbs.rs b/bin/default_pbs.rs index 8c3a2266..caa72ab7 100644 --- a/bin/default_pbs.rs +++ b/bin/default_pbs.rs @@ -1,6 +1,4 @@ -use cb_common::{ - config::load_pbs_config, module_names::PBS_MODULE_NAME, utils::initialize_tracing_log, -}; +use cb_common::{config::load_pbs_config, utils::initialize_pbs_tracing_log}; use cb_pbs::{DefaultBuilderApi, PbsService, PbsState}; use eyre::Result; @@ -13,10 +11,9 @@ async fn main() -> Result<()> { std::env::set_var("RUST_BACKTRACE", "1"); } - // TODO: handle errors - let pbs_config = load_pbs_config().expect("failed to load pbs config"); - let _guard = initialize_tracing_log(PBS_MODULE_NAME); - let state = PbsState::<()>::new(pbs_config); + let pbs_config = load_pbs_config()?; + let _guard = initialize_pbs_tracing_log(); + let state = PbsState::new(pbs_config); PbsService::init_metrics()?; - PbsService::run::<(), DefaultBuilderApi>(state).await + PbsService::run::<_, DefaultBuilderApi>(state).await } diff --git a/bin/signer_module.rs b/bin/signer_module.rs index 6d0f328b..ca838e59 100644 --- a/bin/signer_module.rs +++ b/bin/signer_module.rs @@ -1,5 +1,6 @@ use cb_common::{ - config::StartSignerConfig, module_names::SIGNER_MODULE_NAME, utils::initialize_tracing_log, + config::{StartSignerConfig, SIGNER_MODULE_NAME}, + utils::initialize_tracing_log, }; use cb_signer::service::SigningService; use eyre::Result; diff --git a/bin/src/lib.rs b/bin/src/lib.rs index 12e90d7a..81be3e30 100644 --- a/bin/src/lib.rs +++ b/bin/src/lib.rs @@ -2,14 +2,20 @@ pub mod prelude { pub use cb_common::{ commit, commit::request::{SignRequest, SignedProxyDelegation}, - config::{load_builder_module_config, load_commit_module_config, StartCommitModuleConfig}, + config::{ + load_builder_module_config, load_commit_module_config, load_pbs_config, + load_pbs_custom_config, StartCommitModuleConfig, + }, pbs::{BuilderEvent, BuilderEventClient, OnBuilderApiEvent}, - utils::{initialize_tracing_log, utcnow_ms, utcnow_ns, utcnow_sec, utcnow_us}, + utils::{ + initialize_pbs_tracing_log, initialize_tracing_log, utcnow_ms, utcnow_ns, utcnow_sec, + utcnow_us, + }, }; pub use cb_metrics::provider::MetricsProvider; pub use cb_pbs::{ get_header, get_status, register_validator, submit_block, BuilderApi, BuilderApiState, - PbsState, + DefaultBuilderApi, PbsService, PbsState, }; // The TreeHash derive macro requires tree_hash:: as import pub mod tree_hash { diff --git a/crates/common/src/config/constants.rs b/crates/common/src/config/constants.rs index f5c565aa..8cbf136d 100644 --- a/crates/common/src/config/constants.rs +++ b/crates/common/src/config/constants.rs @@ -21,3 +21,7 @@ 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"; + +// Module names +pub const PBS_MODULE_NAME: &str = "pbs"; +pub const SIGNER_MODULE_NAME: &str = "signer"; diff --git a/crates/common/src/config/pbs.rs b/crates/common/src/config/pbs.rs index 1014ce74..be85e21d 100644 --- a/crates/common/src/config/pbs.rs +++ b/crates/common/src/config/pbs.rs @@ -74,11 +74,9 @@ pub struct StaticPbsConfig { 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 +/// Runtime config for the pbs module #[derive(Debug, Clone)] -pub struct PbsModuleConfig { +pub struct PbsModuleConfig { /// Chain spec pub chain: Chain, /// Pbs default config @@ -89,8 +87,6 @@ pub struct PbsModuleConfig { pub signer_client: Option, /// Event publisher pub event_publiher: Option, - /// Opaque module config - pub extra: T, } fn default_pbs() -> String { @@ -98,7 +94,7 @@ fn default_pbs() -> String { } /// Loads the default pbs config, i.e. with no signer client or custom data -pub fn load_pbs_config() -> Result> { +pub fn load_pbs_config() -> Result { let config = CommitBoostConfig::from_env_path()?; let relay_clients = config.relays.into_iter().map(RelayClient::new).collect::>>()?; @@ -110,12 +106,11 @@ pub fn load_pbs_config() -> Result> { 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> { +pub fn load_pbs_custom_config() -> Result<(PbsModuleConfig, T)> { #[derive(Debug, Deserialize)] struct CustomPbsConfig { #[serde(flatten)] @@ -146,12 +141,14 @@ pub fn load_pbs_custom_config() -> Result Result { let mut headers = HeaderMap::new(); - headers.insert(HEADER_VERSION_KEY, HeaderValue::from_static(HEAVER_VERSION_VALUE)); + headers.insert(HEADER_VERSION_KEY, HeaderValue::from_static(HEADER_VERSION_VALUE)); if let Some(custom_headers) = &config.headers { for (key, value) in custom_headers { diff --git a/crates/common/src/utils.rs b/crates/common/src/utils.rs index 0966a665..1b727967 100644 --- a/crates/common/src/utils.rs +++ b/crates/common/src/utils.rs @@ -16,8 +16,8 @@ use tracing_appender::{non_blocking::WorkerGuard, rolling::Rotation}; use tracing_subscriber::{fmt::Layer, prelude::*, EnvFilter}; use crate::{ - config::{default_log_level, RollingDuration, CB_BASE_LOG_PATH}, - pbs::HEAVER_VERSION_VALUE, + config::{default_log_level, RollingDuration, CB_BASE_LOG_PATH, PBS_MODULE_NAME}, + pbs::HEADER_VERSION_VALUE, types::Chain, }; @@ -182,6 +182,10 @@ pub fn initialize_tracing_log(module_id: &str) -> WorkerGuard { guard } +pub fn initialize_pbs_tracing_log() -> WorkerGuard { + initialize_tracing_log(PBS_MODULE_NAME) +} + // all commit boost crates // TODO: this can probably done without unwrap fn format_crates_filter(default_level: &str, crates_level: &str) -> EnvFilter { @@ -233,5 +237,5 @@ pub fn get_user_agent(req_headers: &HeaderMap) -> String { /// Adds the commit boost version to the existing user agent pub fn get_user_agent_with_version(req_headers: &HeaderMap) -> eyre::Result { let ua = get_user_agent(req_headers); - Ok(HeaderValue::from_str(&format!("commit-boost/{HEAVER_VERSION_VALUE} {}", ua))?) + Ok(HeaderValue::from_str(&format!("commit-boost/{HEADER_VERSION_VALUE} {}", ua))?) } diff --git a/crates/pbs/src/routes/get_header.rs b/crates/pbs/src/routes/get_header.rs index 57d343ae..e2396a30 100644 --- a/crates/pbs/src/routes/get_header.rs +++ b/crates/pbs/src/routes/get_header.rs @@ -21,7 +21,7 @@ use crate::{ }; #[tracing::instrument(skip_all, name = "get_header", fields(req_id = %Uuid::new_v4(), slot = params.slot))] -pub async fn handle_get_header>( +pub async fn handle_get_header>( State(state): State>, req_headers: HeaderMap, Path(params): Path, @@ -34,7 +34,7 @@ pub async fn handle_get_header>( info!(ua, parent_hash=%params.parent_hash, validator_pubkey=%params.pubkey, ms_into_slot); - match T::get_header(params, req_headers, state.clone()).await { + match A::get_header(params, req_headers, state.clone()).await { Ok(res) => { state.publish_event(BuilderEvent::GetHeaderResponse(Box::new(res.clone()))); diff --git a/crates/pbs/src/routes/register_validator.rs b/crates/pbs/src/routes/register_validator.rs index 098dbe6c..d826762e 100644 --- a/crates/pbs/src/routes/register_validator.rs +++ b/crates/pbs/src/routes/register_validator.rs @@ -14,7 +14,7 @@ use crate::{ }; #[tracing::instrument(skip_all, name = "register_validators", fields(req_id = %Uuid::new_v4()))] -pub async fn handle_register_validator>( +pub async fn handle_register_validator>( State(state): State>, req_headers: HeaderMap, Json(registrations): Json>, @@ -26,7 +26,7 @@ pub async fn handle_register_validator>( info!(ua, num_registrations = registrations.len()); - if let Err(err) = T::register_validator(registrations, req_headers, state.clone()).await { + if let Err(err) = A::register_validator(registrations, req_headers, state.clone()).await { state.publish_event(BuilderEvent::RegisterValidatorResponse); error!(?err, "all relays failed registration"); diff --git a/crates/pbs/src/routes/router.rs b/crates/pbs/src/routes/router.rs index 305362a5..e035702b 100644 --- a/crates/pbs/src/routes/router.rs +++ b/crates/pbs/src/routes/router.rs @@ -12,16 +12,16 @@ use crate::{ state::{BuilderApiState, PbsState}, }; -pub fn create_app_router>(state: PbsState) -> Router { +pub fn create_app_router>(state: PbsState) -> Router { let builder_routes = Router::new() - .route(GET_HEADER_PATH, get(handle_get_header::)) - .route(GET_STATUS_PATH, get(handle_get_status::)) - .route(REGISTER_VALIDATOR_PATH, post(handle_register_validator::)) - .route(SUBMIT_BLOCK_PATH, post(handle_submit_block::)); + .route(GET_HEADER_PATH, get(handle_get_header::)) + .route(GET_STATUS_PATH, get(handle_get_status::)) + .route(REGISTER_VALIDATOR_PATH, post(handle_register_validator::)) + .route(SUBMIT_BLOCK_PATH, post(handle_submit_block::)); let builder_api = Router::new().nest(BULDER_API_PATH, builder_routes); - let app = if let Some(extra_routes) = T::extra_routes() { + let app = if let Some(extra_routes) = A::extra_routes() { builder_api.merge(extra_routes) } else { builder_api diff --git a/crates/pbs/src/routes/status.rs b/crates/pbs/src/routes/status.rs index e59a8dbd..096a8163 100644 --- a/crates/pbs/src/routes/status.rs +++ b/crates/pbs/src/routes/status.rs @@ -13,7 +13,7 @@ use crate::{ }; #[tracing::instrument(skip_all, name = "status", fields(req_id = %Uuid::new_v4()))] -pub async fn handle_get_status>( +pub async fn handle_get_status>( req_headers: HeaderMap, State(state): State>, ) -> Result { @@ -23,7 +23,7 @@ pub async fn handle_get_status>( info!(ua, relay_check = state.config.pbs_config.relay_check); - match T::get_status(req_headers, state.clone()).await { + match A::get_status(req_headers, state.clone()).await { Ok(_) => { state.publish_event(BuilderEvent::GetStatusResponse); info!("relay check successful"); diff --git a/crates/pbs/src/routes/submit_block.rs b/crates/pbs/src/routes/submit_block.rs index e439837f..491c93ea 100644 --- a/crates/pbs/src/routes/submit_block.rs +++ b/crates/pbs/src/routes/submit_block.rs @@ -16,7 +16,7 @@ use crate::{ }; #[tracing::instrument(skip_all, name = "submit_blinded_block", fields(req_id = %Uuid::new_v4(), slot = signed_blinded_block.message.slot))] -pub async fn handle_submit_block>( +pub async fn handle_submit_block>( State(state): State>, req_headers: HeaderMap, Json(signed_blinded_block): Json, @@ -37,7 +37,7 @@ pub async fn handle_submit_block>( warn!(expected = curr_slot, got = slot, "blinded beacon slot mismatch") } - match T::submit_block(signed_blinded_block, req_headers, state.clone()).await { + match A::submit_block(signed_blinded_block, req_headers, state.clone()).await { Ok(res) => { trace!(?res); state.publish_event(BuilderEvent::SubmitBlockResponse(Box::new(res.clone()))); diff --git a/crates/pbs/src/service.rs b/crates/pbs/src/service.rs index d3f1f7f5..6d1f1804 100644 --- a/crates/pbs/src/service.rs +++ b/crates/pbs/src/service.rs @@ -18,15 +18,11 @@ pub struct PbsService; // TODO: add ServerMaxHeaderBytes impl PbsService { - pub async fn run>(state: PbsState) -> Result<()> { - // if state.pbs_config().relay_check { - // PbsService::relay_check(state.relays()).await; - // } - + pub async fn run>(state: PbsState) -> Result<()> { 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); + let app = create_app_router::(state); info!(?address, events_subs, "Starting PBS service"); @@ -42,32 +38,4 @@ impl PbsService { pub fn init_metrics() -> Result<()> { MetricsProvider::load_and_run(PBS_METRICS_REGISTRY.clone()) } - - // TODO: before starting, send a sanity check to relay - // pub async fn relay_check(relays: &[RelayEntry]) { - // info!("Sending initial relay checks"); - - // let mut handles = Vec::with_capacity(relays.len()); - - // for relay in relays { - // handles.push(Box::pin(send_relay_check(relay.clone()))) - // } - - // let results = join_all(handles).await; - - // if !results.iter().any(|r| r.is_ok()) { - // error!("No relay passed check successfully"); - // return; - // } - - // for (i, res) in results.into_iter().enumerate() { - // let relay_id = relays[i].id.as_str(); - - // if let Err(err) = res { - // error!(?err, "Failed to get status from {relay_id}"); - // } else { - // info!(relay_id, "Initial check successful") - // } - // } - // } } diff --git a/crates/pbs/src/state.rs b/crates/pbs/src/state.rs index 3641dffa..6b095291 100644 --- a/crates/pbs/src/state.rs +++ b/crates/pbs/src/state.rs @@ -14,13 +14,12 @@ use uuid::Uuid; pub trait BuilderApiState: Clone + Sync + Send + 'static {} impl BuilderApiState for () {} -/// 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 +/// State for the Pbs module. It can be extended by adding extra data to the +/// state #[derive(Clone)] -pub struct PbsState { +pub struct PbsState { /// Config data for the Pbs service - pub config: PbsModuleConfig, + pub config: PbsModuleConfig, /// Opaque extra data for library use pub data: S, /// Info about the latest slot and its uuid @@ -29,8 +28,8 @@ pub struct PbsState { bid_cache: Arc>>, } -impl PbsState { - pub fn new(config: PbsModuleConfig) -> Self { +impl PbsState<()> { + pub fn new(config: PbsModuleConfig) -> Self { Self { config, data: (), @@ -39,7 +38,7 @@ impl PbsState { } } - pub fn with_data(self, data: S) -> PbsState { + pub fn with_data(self, data: S) -> PbsState { PbsState { data, config: self.config, @@ -49,7 +48,7 @@ impl PbsState { } } -impl PbsState +impl PbsState where S: BuilderApiState, { diff --git a/examples/custom_boost.rs b/examples/custom_boost.rs deleted file mode 100644 index 3b5b504f..00000000 --- a/examples/custom_boost.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::sync::{ - atomic::{AtomicU64, Ordering}, - Arc, -}; - -use async_trait::async_trait; -use axum::{ - extract::State, - http::StatusCode, - response::{IntoResponse, Response}, - routing::get, - Router, -}; -use cb_common::{ - config::{load_pbs_config, CommitBoostConfig, JWT_ENV}, - utils::initialize_tracing_log, -}; -use cb_pbs::{BuilderApi, BuilderApiState, BuilderState, PbsService}; -use tracing::info; - -// You can provide extra state to the Pbs server by implementing the `BuilderApiState` trait -#[derive(Debug, Default, Clone)] -struct StatusCounter(Arc); - -impl BuilderApiState for StatusCounter {} -impl StatusCounter { - fn inc(&self) { - self.0.fetch_add(1, Ordering::Relaxed); - } - - fn log(&self) -> String { - let count = self.0.load(Ordering::Relaxed); - format!("Received {count} status requests!") - } -} - -// Any method that is not overriden will default to the normal MEV boost flow -struct MyBuilderApi; -#[async_trait] -impl BuilderApi for MyBuilderApi { - async fn get_status(state: BuilderState) -> eyre::Result<()> { - let count = state.data.0.load(Ordering::Relaxed); - info!("THIS IS A CUSTOM LOG. Count: {count}"); - state.data.inc(); - Ok(()) - } - - fn extra_routes() -> Option>> { - let router = Router::new().route("/custom/stats", get(handle_stats)); - Some(router) - } -} -async fn handle_stats(State(state): State>) -> Response { - (StatusCode::OK, state.data.log()).into_response() -} - -#[tokio::main] -async fn main() { - - color_eyre::install()?; - - - let (chain, config) = load_pbs_config(); - let _guard = initialize_tracing_log(config.id); - - info!("Starting custom pbs module"); - - let jwt = std::env::var(JWT_ENV).expect(&format!("{JWT_ENV} not set")); - let address = CommitBoostConfig::from_env_path().signer.expect("Signer config missing").address; - - let state = BuilderState::new(chain, config, address, &jwt); - - PbsService::run::(state).await; -} diff --git a/examples/status_api/Cargo.toml b/examples/status_api/Cargo.toml new file mode 100644 index 00000000..e87f5e33 --- /dev/null +++ b/examples/status_api/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "status_api" +version.workspace = true +edition.workspace = true +rust-version.workspace = true + +[dependencies] +commit-boost = { path = "../../bin" } + +# networking +reqwest.workspace = true +axum.workspace = true + +# async / threads +tokio.workspace = true +async-trait.workspace = true + +# serialization +serde.workspace = true + +# telemetry +tracing.workspace = true +prometheus.workspace = true + +# misc +eyre.workspace = true +color-eyre.workspace = true +lazy_static.workspace = true diff --git a/examples/status_api/Dockerfile b/examples/status_api/Dockerfile new file mode 100644 index 00000000..f6f5568f --- /dev/null +++ b/examples/status_api/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 status_api + + +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/status_api /usr/local/bin +ENTRYPOINT ["/usr/local/bin/status_api"] + + + diff --git a/examples/status_api/src/main.rs b/examples/status_api/src/main.rs new file mode 100644 index 00000000..6879c795 --- /dev/null +++ b/examples/status_api/src/main.rs @@ -0,0 +1,94 @@ +use std::sync::{ + atomic::{AtomicU64, Ordering}, + Arc, +}; + +use async_trait::async_trait; +use axum::{ + extract::State, + response::{IntoResponse, Response}, + routing::get, + Router, +}; +use commit_boost::prelude::*; +use eyre::Result; +use lazy_static::lazy_static; +use prometheus::IntCounter; +use reqwest::{header::HeaderMap, StatusCode}; +use serde::Deserialize; +use tracing::info; + +lazy_static! { + pub static ref CHECK_RECEIVED_COUNTER: IntCounter = + IntCounter::new("checks", "successful /check requests received").unwrap(); +} + +/// Extra config loaded from the config file +/// You should add an `inc_amount` field to the config file in the `pbs` +/// section. Be sure also to change the `pbs.docker_image` field, +/// `test_status_api` in this case (from scripts/build_local_modules.sh). +#[derive(Debug, Deserialize)] +struct ExtraConfig { + inc_amount: u64, +} + +// Extra state available at runtime +#[derive(Clone)] +struct MyBuilderState { + inc_amount: u64, + counter: Arc, +} + +impl BuilderApiState for MyBuilderState {} + +impl MyBuilderState { + fn from_config(extra: ExtraConfig) -> Self { + Self { inc_amount: extra.inc_amount, counter: Arc::new(AtomicU64::new(0)) } + } + + fn inc(&self) { + self.counter.fetch_add(self.inc_amount, Ordering::Relaxed); + } + fn get(&self) -> u64 { + self.counter.load(Ordering::Relaxed) + } +} + +struct MyBuilderApi; + +#[async_trait] +impl BuilderApi for MyBuilderApi { + async fn get_status(req_headers: HeaderMap, state: PbsState) -> Result<()> { + state.data.inc(); + info!("THIS IS A CUSTOM LOG"); + CHECK_RECEIVED_COUNTER.inc(); + get_status(req_headers, state).await + } + + fn extra_routes() -> Option>> { + let mut router = Router::new(); + router = router.route("/check", get(handle_check)); + Some(router) + } +} + +async fn handle_check(State(state): State>) -> Response { + (StatusCode::OK, format!("Received {count} status requests!", count = state.data.get())) + .into_response() +} + +#[tokio::main] +async fn main() -> Result<()> { + color_eyre::install()?; + + let (pbs_config, extra) = load_pbs_custom_config::()?; + let _guard = initialize_pbs_tracing_log(); + + let custom_state = MyBuilderState::from_config(extra); + let state = PbsState::new(pbs_config).with_data(custom_state); + + PbsService::register_metric(Box::new(CHECK_RECEIVED_COUNTER.clone())); + PbsService::init_metrics()?; + + PbsService::run::(state).await +} diff --git a/scripts/build_local_modules.sh b/scripts/build_local_modules.sh index ddbad51c..2a6045fd 100755 --- a/scripts/build_local_modules.sh +++ b/scripts/build_local_modules.sh @@ -3,4 +3,5 @@ set -euo pipefail 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 +docker build -t test_builder_log . -f examples/builder_log/Dockerfile +docker build -t test_status_api . -f examples/status_api/Dockerfile \ No newline at end of file diff --git a/tests/tests/pbs_integration.rs b/tests/tests/pbs_integration.rs index d777c5aa..d5fb9f71 100644 --- a/tests/tests/pbs_integration.rs +++ b/tests/tests/pbs_integration.rs @@ -41,17 +41,12 @@ fn get_pbs_static_config(port: u16) -> PbsConfig { } } -fn to_pbs_config( - chain: Chain, - pbs_config: PbsConfig, - relays: Vec, -) -> PbsModuleConfig<()> { +fn to_pbs_config(chain: Chain, pbs_config: PbsConfig, relays: Vec) -> PbsModuleConfig { PbsModuleConfig { chain, pbs_config: Arc::new(pbs_config), signer_client: None, event_publiher: None, - extra: (), relays, } }