Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
target
12 changes: 9 additions & 3 deletions config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@ chain = "Holesky"

[pbs]
port = 18550
relays = []
relay_check = true
timeout_get_header_ms = 950
timeout_get_payload_ms = 4000
timeout_register_validator_ms = 3000
skip_sigverify = true
min_bid_eth = 0.0

[headers]
X-MyCustomHeader = "MyCustomValue"
late_in_slot_time_ms = 2000

[[relays]]
id = "example-relay"
url = "http://0xa1cec75a3f0661e99299274182938151e8433c61a19222347ea1313d839229cb4ce4e3e5aa2bdeb71c8fcf1b084963c2@abc.xyz"
headers = { X-MyCustomHeader = "MyCustomValue" }
enable_timing_games = false
target_first_request_ms = 200
frequency_get_header_ms = 300

[signer]
[signer.loader]
Expand Down
2 changes: 1 addition & 1 deletion crates/common/src/commit/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub struct GetPubkeysResponse {
#[derive(Debug, Clone)]
pub struct SignerClient {
/// Url endpoint of the Signer Module
url: Arc<str>,
url: Arc<String>,
client: reqwest::Client,
}

Expand Down
57 changes: 18 additions & 39 deletions crates/common/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use std::{collections::HashMap, sync::Arc};

use alloy::primitives::U256;
use eyre::{eyre, ContextCompat};
use serde::{de::DeserializeOwned, Deserialize, Serialize};

use super::utils::as_eth_str;
use crate::{commit::client::SignerClient, loader::SignerLoader, pbs::RelayEntry, types::Chain};
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";
Expand All @@ -32,6 +36,7 @@ pub const SIGNER_IMAGE: &str = "commitboost_signer";
pub struct CommitBoostConfig {
// TODO: generalize this with a spec file
pub chain: Chain,
pub relays: Vec<RelayConfig>,
pub pbs: StaticPbsConfig,
pub modules: Option<Vec<StaticModuleConfig>>,
pub signer: Option<SignerConfig>,
Expand Down Expand Up @@ -136,65 +141,36 @@ pub struct StaticPbsConfig {
pub with_signer: bool,
}

#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct PbsConfig {
/// Port to receive BuilderAPI calls from CL
pub port: u16,
/// Which relay to register/subscribe to
pub relays: Vec<RelayEntry>,
/// Whether to forward getStatus to relays or skip it
pub relay_check: bool,
#[serde(default = "default_u64::<950>")]
pub timeout_get_header_ms: u64,
#[serde(default = "default_u64::<4000>")]
pub timeout_get_payload_ms: u64,
#[serde(default = "default_u64::<3000>")]
pub timeout_register_validator_ms: u64,
/// Whether to skip the relay signature verification
#[serde(default = "default_bool::<false>")]
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,
/// Custom headers to send to relays
pub headers: Option<HashMap<String, String>>,
}

/// 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<T = ()> {
/// Chain spec
pub chain: Chain,
/// Pbs default config
pub pbs_config: Arc<PbsConfig>,
/// List of relays
pub relays: Vec<RelayClient>,
/// Signer client to call Signer API
pub signer_client: Option<SignerClient>,
/// Opaque module config
pub extra: T,
}

const fn default_u64<const U: u64>() -> u64 {
U
}

const fn default_bool<const U: bool>() -> bool {
U
}

const fn default_u256() -> U256 {
U256::ZERO
}

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() -> eyre::Result<PbsModuleConfig<()>> {
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: (),
})
Expand All @@ -213,11 +189,13 @@ pub fn load_pbs_custom_config<T: DeserializeOwned>() -> eyre::Result<PbsModuleCo
#[derive(Deserialize, Debug)]
struct StubConfig<U> {
chain: Chain,
relays: Vec<RelayConfig>,
pbs: CustomPbsConfig<U>,
}

// load module config including the extra data (if any)
let cb_config: StubConfig<T> = 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
Expand All @@ -231,6 +209,7 @@ pub fn load_pbs_custom_config<T: DeserializeOwned>() -> eyre::Result<PbsModuleCo
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,
})
Expand Down
56 changes: 56 additions & 0 deletions crates/common/src/pbs/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//! 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<String>,
/// Relay in the form of pubkey@url
#[serde(rename = "url")]
pub entry: RelayEntry,
/// Optional headers to send with each request
pub headers: Option<HashMap<String, String>>,
/// Whether to enable timing games
#[serde(default = "default_bool::<false>")]
pub enable_timing_games: bool,
/// Target time in slot when to send the first header request
pub target_first_request_ms: Option<u64>,
/// Frequency in ms to send get_header requests
pub frequency_get_header_ms: Option<u64>,
}

#[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::<false>")]
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::<LATE_IN_SLOT_TIME_MS>")]
pub late_in_slot_time_ms: u64,
}
10 changes: 10 additions & 0 deletions crates/common/src/pbs/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,13 @@ pub const HEADER_START_TIME_UNIX_MS: &str = "X-MEVBoost-StartTimeUnixMS";

pub const BUILDER_EVENTS_PATH: &str = "/events";
pub const DEFAULT_PBS_JWT_KEY: &str = "DEFAULT_PBS";

#[non_exhaustive]
pub struct DefaultTimeout;
impl DefaultTimeout {
pub const GET_HEADER_MS: u64 = 950;
pub const GET_PAYLOAD_MS: u64 = 4000;
pub const REGISTER_VALIDATOR_MS: u64 = 3000;
}

pub const LATE_IN_SLOT_TIME_MS: u64 = 2000;
4 changes: 3 additions & 1 deletion crates/common/src/pbs/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mod config;
mod constants;
mod types;

pub use config::*;
pub use constants::*;
pub use types::RelayEntry;
pub use types::*;
110 changes: 81 additions & 29 deletions crates/common/src/pbs/types.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,99 @@
use std::{str::FromStr, sync::Arc};

use alloy::{
primitives::{hex::FromHex, B256},
rpc::types::beacon::BlsPublicKey,
};
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,
use super::{
constants::{BULDER_API_PATH, GET_STATUS_PATH, REGISTER_VALIDATOR_PATH, SUBMIT_BLOCK_PATH},
RelayConfig, HEADER_VERSION_KEY, HEAVER_VERSION_VALUE,
};

use crate::DEFAULT_REQUEST_TIMEOUT;
/// A parsed entry of the relay url in the format: scheme://pubkey@host
#[derive(Debug, Default, Clone)]
pub struct RelayEntry {
/// Default if of the relay, the hostname of the url
pub id: String,
/// Public key of the relay
pub pubkey: BlsPublicKey,
/// Full url of the relay
pub url: String,
}

impl RelayEntry {
fn get_url(&self, path: &str) -> String {
format!("{}{path}", &self.url)
impl Serialize for RelayEntry {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.url)
}
}

impl<'de> Deserialize<'de> for RelayEntry {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let str = String::deserialize(deserializer)?;
let url = Url::parse(&str).map_err(serde::de::Error::custom)?;
let pubkey = BlsPublicKey::from_hex(url.username()).map_err(serde::de::Error::custom)?;
let id = url.host().ok_or(serde::de::Error::custom("missing host"))?.to_string();

Ok(RelayEntry { pubkey, url: str, id })
}
}

/// A client to interact with a relay, safe to share across threads
#[derive(Debug, Clone)]
pub struct RelayClient {
/// ID of the relay
pub id: Arc<String>,
/// HTTP client to send requests
pub client: reqwest::Client,
/// Configuration of the relay
pub config: Arc<RelayConfig>,
}

impl RelayClient {
pub fn new(config: RelayConfig) -> Self {
let mut headers = HeaderMap::new();
headers.insert(HEADER_VERSION_KEY, HeaderValue::from_static(HEAVER_VERSION_VALUE));

if let Some(custom_headers) = &config.headers {
for (key, value) in custom_headers {
headers.insert(
HeaderName::from_str(key)
.unwrap_or_else(|_| panic!("{key} is an invalid header name")),
HeaderValue::from_str(value)
.unwrap_or_else(|_| panic!("{key} has an invalid header value")),
);
}
}

let client = reqwest::Client::builder()
.default_headers(headers)
.timeout(DEFAULT_REQUEST_TIMEOUT)
.build()
.expect("failed to build relay client");

Self {
id: Arc::new(config.id.clone().unwrap_or(config.entry.id.clone())),
client,
config: Arc::new(config),
}
}

pub fn pubkey(&self) -> BlsPublicKey {
self.config.entry.pubkey
}

// URL builders
pub fn get_url(&self, path: &str) -> String {
format!("{}{path}", &self.config.entry.url)
}

pub fn get_header_url(
Expand All @@ -43,29 +118,6 @@ impl RelayEntry {
}
}

impl Serialize for RelayEntry {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.url)
}
}

impl<'de> Deserialize<'de> for RelayEntry {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let str = String::deserialize(deserializer)?;
let url = Url::parse(&str).map_err(serde::de::Error::custom)?;
let pubkey = BlsPublicKey::from_hex(url.username()).map_err(serde::de::Error::custom)?;
let id = url.host().ok_or(serde::de::Error::custom("missing host"))?.to_string();

Ok(RelayEntry { pubkey, url: str, id })
}
}

#[cfg(test)]
mod tests {
use alloy::{primitives::hex::FromHex, rpc::types::beacon::BlsPublicKey};
Expand Down
2 changes: 1 addition & 1 deletion crates/common/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::constants::{
RHEA_BUILDER_DOMAIN, RHEA_FORK_VERSION, RHEA_GENESIS_TIME_SECONDS,
};

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum Chain {
Mainnet,
Holesky,
Expand Down
Loading