Skip to content

Commit ca153cc

Browse files
authored
feat: add pbs mux (#172)
* add pbs mux * comments
1 parent 75e8129 commit ca153cc

File tree

11 files changed

+311
-19
lines changed

11 files changed

+311
-19
lines changed

config.example.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,26 @@ target_first_request_ms = 200
9797
# OPTIONAL
9898
frequency_get_header_ms = 300
9999

100+
# Configuration for the PBS multiplexers, which enable different configs to be used for get header requests, depending on validator pubkey
101+
# Note that:
102+
# - multiple sets of keys can be defined by adding multiple [[mux]] sections. The validator pubkey sets need to be disjoint
103+
# - the mux is only used for get header requests
104+
# - if any value is missing from the mux config, the default value from the main config will be used
105+
[[mux]]
106+
# Which validator pubkeys to match against this mux config
107+
validator_pubkeys = [
108+
"0x80c7f782b2467c5898c5516a8b6595d75623960b4afc4f71ee07d40985d20e117ba35e7cd352a3e75fb85a8668a3b745",
109+
"0xa119589bb33ef52acbb8116832bec2b58fca590fe5c85eac5d3230b44d5bc09fe73ccd21f88eab31d6de16194d17782e",
110+
]
111+
timeout_get_header_ms = 900
112+
late_in_slot_time_ms = 1500
113+
# For each mux, one or more [[pbs_mux.relays]] can be defined, which will be used for the matching validator pubkeys
114+
# Only the relays defined here will be used, and the rest of the relays defined in the main config will be ignored
115+
# Any field defined here will override the default value from the relay config with the same id in [[relays]]
116+
[[mux.relays]]
117+
id = "example-relay"
118+
headers = { X-MyCustomHeader = "ADifferentCustomValue" }
119+
100120
# Configuration for the Signer Module, only required if any `commit` module is present, or if `pbs.with_signer = true`
101121
# OPTIONAL
102122
[signer]

configs/pbs-mux.toml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# PBS config with a mux for a single validator
2+
3+
chain = "Holesky"
4+
5+
[pbs]
6+
port = 18550
7+
timeout_get_header_ms = 950
8+
late_in_slot_time_ms = 2000
9+
10+
[[relays]]
11+
id = "relay-1"
12+
url = "http://0xa1cec75a3f0661e99299274182938151e8433c61a19222347ea1313d839229cb4ce4e3e5aa2bdeb71c8fcf1b084963c2@abc.xyz"
13+
14+
[[relays]]
15+
id = "relay-2"
16+
url = "http://0xa119589bb33ef52acbb8116832bec2b58fca590fe5c85eac5d3230b44d5bc09fe73ccd21f88eab31d6de16194d17782e@def.xyz"
17+
enable_timing_games = true
18+
target_first_request_ms = 200
19+
20+
[[mux]]
21+
validator_pubkeys = [
22+
"0x80c7f782b2467c5898c5516a8b6595d75623960b4afc4f71ee07d40985d20e117ba35e7cd352a3e75fb85a8668a3b745",
23+
]
24+
timeout_get_header_ms = 900
25+
late_in_slot_time_ms = 1500
26+
27+
[[mux.relays]]
28+
id = "relay-2"
29+
enable_timing_games = false

crates/common/src/config/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod constants;
99
mod log;
1010
mod metrics;
1111
mod module;
12+
mod mux;
1213
mod pbs;
1314
mod signer;
1415
mod utils;
@@ -17,6 +18,7 @@ pub use constants::*;
1718
pub use log::*;
1819
pub use metrics::*;
1920
pub use module::*;
21+
pub use mux::*;
2022
pub use pbs::*;
2123
pub use signer::*;
2224
pub use utils::*;
@@ -26,6 +28,8 @@ pub struct CommitBoostConfig {
2628
pub chain: Chain,
2729
pub relays: Vec<RelayConfig>,
2830
pub pbs: StaticPbsConfig,
31+
#[serde(flatten)]
32+
pub muxes: Option<PbsMuxes>,
2933
pub modules: Option<Vec<StaticModuleConfig>>,
3034
pub signer: Option<SignerConfig>,
3135
pub metrics: Option<MetricsConfig>,
@@ -57,6 +61,7 @@ impl CommitBoostConfig {
5761
chain,
5862
relays: rest_config.relays,
5963
pbs: rest_config.pbs,
64+
muxes: rest_config.muxes,
6065
modules: rest_config.modules,
6166
signer: rest_config.signer,
6267
metrics: rest_config.metrics,
@@ -96,6 +101,8 @@ struct ChainConfig {
96101
struct HelperConfig {
97102
relays: Vec<RelayConfig>,
98103
pbs: StaticPbsConfig,
104+
#[serde(flatten)]
105+
muxes: Option<PbsMuxes>,
99106
modules: Option<Vec<StaticModuleConfig>>,
100107
signer: Option<SignerConfig>,
101108
metrics: Option<MetricsConfig>,

crates/common/src/config/mux.rs

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
use std::{
2+
collections::{HashMap, HashSet},
3+
sync::Arc,
4+
};
5+
6+
use alloy::rpc::types::beacon::BlsPublicKey;
7+
use eyre::{bail, ensure, eyre};
8+
use serde::{Deserialize, Serialize};
9+
10+
use super::{PbsConfig, RelayConfig};
11+
use crate::pbs::{RelayClient, RelayEntry};
12+
13+
#[derive(Debug, Clone, Deserialize, Serialize)]
14+
pub struct PbsMuxes {
15+
/// List of PBS multiplexers
16+
#[serde(rename = "mux")]
17+
pub muxes: Vec<MuxConfig>,
18+
}
19+
20+
#[derive(Debug, Clone)]
21+
pub struct RuntimeMuxConfig {
22+
pub config: Arc<PbsConfig>,
23+
pub relays: Vec<RelayClient>,
24+
}
25+
26+
impl PbsMuxes {
27+
pub fn validate_and_fill(
28+
self,
29+
default_pbs: &PbsConfig,
30+
default_relays: &[RelayConfig],
31+
) -> eyre::Result<HashMap<BlsPublicKey, RuntimeMuxConfig>> {
32+
// check that validator pubkeys are in disjoint sets
33+
let mut unique_pubkeys = HashSet::new();
34+
for mux in self.muxes.iter() {
35+
for pubkey in mux.validator_pubkeys.iter() {
36+
if !unique_pubkeys.insert(pubkey) {
37+
bail!("duplicate validator pubkey in muxes: {pubkey}");
38+
}
39+
}
40+
}
41+
42+
let mut configs = HashMap::new();
43+
// fill the configs using the default pbs config and relay entries
44+
for mux in self.muxes {
45+
ensure!(!mux.relays.is_empty(), "mux config must have at least one relay");
46+
ensure!(
47+
!mux.validator_pubkeys.is_empty(),
48+
"mux config must have at least one validator pubkey"
49+
);
50+
51+
let mut relay_clients = Vec::with_capacity(mux.relays.len());
52+
for partial_relay in mux.relays.into_iter() {
53+
// create a new config overriding only the missing fields
54+
let partial_id = partial_relay.id()?;
55+
// assume that there is always a relay defined in the default config. If this
56+
// becomes too much of a burden, we can change this to allow defining relays
57+
// that are exclusively used by a mux
58+
let default_relay = default_relays
59+
.iter()
60+
.find(|r| r.id() == partial_id)
61+
.ok_or_else(|| eyre!("default relay config not found for: {}", partial_id))?;
62+
63+
let full_config = RelayConfig {
64+
id: Some(partial_id.to_string()),
65+
entry: partial_relay.entry.unwrap_or(default_relay.entry.clone()),
66+
headers: partial_relay.headers.or(default_relay.headers.clone()),
67+
enable_timing_games: partial_relay
68+
.enable_timing_games
69+
.unwrap_or(default_relay.enable_timing_games),
70+
target_first_request_ms: partial_relay
71+
.target_first_request_ms
72+
.or(default_relay.target_first_request_ms),
73+
frequency_get_header_ms: partial_relay
74+
.frequency_get_header_ms
75+
.or(default_relay.frequency_get_header_ms),
76+
};
77+
78+
relay_clients.push(RelayClient::new(full_config)?);
79+
}
80+
81+
let config = PbsConfig {
82+
timeout_get_header_ms: mux
83+
.timeout_get_header_ms
84+
.unwrap_or(default_pbs.timeout_get_header_ms),
85+
late_in_slot_time_ms: mux
86+
.late_in_slot_time_ms
87+
.unwrap_or(default_pbs.late_in_slot_time_ms),
88+
..default_pbs.clone()
89+
};
90+
let config = Arc::new(config);
91+
92+
let runtime_config = RuntimeMuxConfig { config, relays: relay_clients };
93+
for pubkey in mux.validator_pubkeys.iter() {
94+
configs.insert(*pubkey, runtime_config.clone());
95+
}
96+
}
97+
98+
Ok(configs)
99+
}
100+
}
101+
102+
/// Configuration for the PBS Multiplexer
103+
#[derive(Debug, Clone, Deserialize, Serialize)]
104+
pub struct MuxConfig {
105+
/// Relays to use for this mux config
106+
pub relays: Vec<PartialRelayConfig>,
107+
/// Which validator pubkeys to match against this mux config
108+
pub validator_pubkeys: Vec<BlsPublicKey>,
109+
pub timeout_get_header_ms: Option<u64>,
110+
pub late_in_slot_time_ms: Option<u64>,
111+
}
112+
113+
#[derive(Debug, Clone, Deserialize, Serialize)]
114+
/// A relay config with all optional fields. See [`RelayConfig`] for the
115+
/// description of the fields.
116+
pub struct PartialRelayConfig {
117+
pub id: Option<String>,
118+
#[serde(rename = "url")]
119+
pub entry: Option<RelayEntry>,
120+
pub headers: Option<HashMap<String, String>>,
121+
pub enable_timing_games: Option<bool>,
122+
pub target_first_request_ms: Option<u64>,
123+
pub frequency_get_header_ms: Option<u64>,
124+
}
125+
126+
impl PartialRelayConfig {
127+
pub fn id(&self) -> eyre::Result<&str> {
128+
match &self.id {
129+
Some(id) => Ok(id.as_str()),
130+
None => {
131+
let entry = self.entry.as_ref().ok_or_else(|| {
132+
eyre!("relays in [[mux]] need to specifify either an `id` or a `url`")
133+
})?;
134+
Ok(entry.id.as_str())
135+
}
136+
}
137+
}
138+
}

crates/common/src/config/pbs.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,23 @@ use std::{
66
sync::Arc,
77
};
88

9-
use alloy::primitives::{utils::format_ether, U256};
9+
use alloy::{
10+
primitives::{utils::format_ether, U256},
11+
rpc::types::beacon::BlsPublicKey,
12+
};
1013
use eyre::{ensure, Result};
1114
use serde::{de::DeserializeOwned, Deserialize, Serialize};
1215
use url::Url;
1316

1417
use super::{
15-
constants::PBS_IMAGE_DEFAULT, load_optional_env_var, CommitBoostConfig, PBS_ENDPOINT_ENV,
18+
constants::PBS_IMAGE_DEFAULT, load_optional_env_var, CommitBoostConfig, RuntimeMuxConfig,
19+
PBS_ENDPOINT_ENV,
1620
};
1721
use crate::{
1822
commit::client::SignerClient,
19-
config::{load_env_var, load_file_from_env, CONFIG_ENV, MODULE_JWT_ENV, SIGNER_URL_ENV},
23+
config::{
24+
load_env_var, load_file_from_env, PbsMuxes, CONFIG_ENV, MODULE_JWT_ENV, SIGNER_URL_ENV,
25+
},
2026
pbs::{
2127
BuilderEventPublisher, DefaultTimeout, RelayClient, RelayEntry, DEFAULT_PBS_PORT,
2228
LATE_IN_SLOT_TIME_MS,
@@ -45,6 +51,12 @@ pub struct RelayConfig {
4551
pub frequency_get_header_ms: Option<u64>,
4652
}
4753

54+
impl RelayConfig {
55+
pub fn id(&self) -> &str {
56+
self.id.as_deref().unwrap_or(self.entry.id.as_str())
57+
}
58+
}
59+
4860
#[derive(Debug, Clone, Deserialize, Serialize)]
4961
pub struct PbsConfig {
5062
/// Host to receive BuilderAPI calls from beacon node
@@ -149,6 +161,8 @@ pub struct PbsModuleConfig {
149161
pub signer_client: Option<SignerClient>,
150162
/// Event publisher
151163
pub event_publisher: Option<BuilderEventPublisher>,
164+
/// Muxes config
165+
pub muxes: Option<HashMap<BlsPublicKey, RuntimeMuxConfig>>,
152166
}
153167

154168
fn default_pbs() -> String {
@@ -158,6 +172,7 @@ fn default_pbs() -> String {
158172
/// Loads the default pbs config, i.e. with no signer client or custom data
159173
pub fn load_pbs_config() -> Result<PbsModuleConfig> {
160174
let config = CommitBoostConfig::from_env_path()?;
175+
config.validate()?;
161176

162177
// use endpoint from env if set, otherwise use default host and port
163178
let endpoint = if let Some(endpoint) = load_optional_env_var(PBS_ENDPOINT_ENV) {
@@ -166,6 +181,11 @@ pub fn load_pbs_config() -> Result<PbsModuleConfig> {
166181
SocketAddr::from((config.pbs.pbs_config.host, config.pbs.pbs_config.port))
167182
};
168183

184+
let muxes = config
185+
.muxes
186+
.map(|muxes| muxes.validate_and_fill(&config.pbs.pbs_config, &config.relays))
187+
.transpose()?;
188+
169189
let relay_clients =
170190
config.relays.into_iter().map(RelayClient::new).collect::<Result<Vec<_>>>()?;
171191
let maybe_publiher = BuilderEventPublisher::new_from_env()?;
@@ -177,6 +197,7 @@ pub fn load_pbs_config() -> Result<PbsModuleConfig> {
177197
relays: relay_clients,
178198
signer_client: None,
179199
event_publisher: maybe_publiher,
200+
muxes,
180201
})
181202
}
182203

@@ -195,6 +216,7 @@ pub fn load_pbs_custom_config<T: DeserializeOwned>() -> Result<(PbsModuleConfig,
195216
chain: Chain,
196217
relays: Vec<RelayConfig>,
197218
pbs: CustomPbsConfig<U>,
219+
muxes: Option<PbsMuxes>,
198220
}
199221

200222
// load module config including the extra data (if any)
@@ -211,6 +233,13 @@ pub fn load_pbs_custom_config<T: DeserializeOwned>() -> Result<(PbsModuleConfig,
211233
))
212234
};
213235

236+
let muxes = match cb_config.muxes {
237+
Some(muxes) => Some(
238+
muxes.validate_and_fill(&cb_config.pbs.static_config.pbs_config, &cb_config.relays)?,
239+
),
240+
None => None,
241+
};
242+
214243
let relay_clients =
215244
cb_config.relays.into_iter().map(RelayClient::new).collect::<Result<Vec<_>>>()?;
216245
let maybe_publiher = BuilderEventPublisher::new_from_env()?;
@@ -232,6 +261,7 @@ pub fn load_pbs_custom_config<T: DeserializeOwned>() -> Result<(PbsModuleConfig,
232261
relays: relay_clients,
233262
signer_client,
234263
event_publisher: maybe_publiher,
264+
muxes,
235265
},
236266
cb_config.pbs.extra,
237267
))

crates/common/src/pbs/relay.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use crate::{config::RelayConfig, DEFAULT_REQUEST_TIMEOUT};
1919
/// A parsed entry of the relay url in the format: scheme://pubkey@host
2020
#[derive(Debug, Clone)]
2121
pub struct RelayEntry {
22-
/// Default if of the relay, the hostname of the url
22+
/// Default ID of the relay, the hostname of the url
2323
pub id: String,
2424
/// Public key of the relay
2525
pub pubkey: BlsPublicKey,
@@ -42,8 +42,9 @@ impl<'de> Deserialize<'de> for RelayEntry {
4242
D: serde::Deserializer<'de>,
4343
{
4444
let url = Url::deserialize(deserializer)?;
45-
let pubkey = BlsPublicKey::from_hex(url.username()).map_err(serde::de::Error::custom)?;
4645
let id = url.host().ok_or(serde::de::Error::custom("missing host"))?.to_string();
46+
let pubkey = BlsPublicKey::from_hex(url.username())
47+
.map_err(|_| serde::de::Error::custom("invalid BLS pubkey"))?;
4748

4849
Ok(RelayEntry { pubkey, url, id })
4950
}
@@ -79,11 +80,7 @@ impl RelayClient {
7980
.timeout(DEFAULT_REQUEST_TIMEOUT)
8081
.build()?;
8182

82-
Ok(Self {
83-
id: Arc::new(config.id.clone().unwrap_or(config.entry.id.clone())),
84-
client,
85-
config: Arc::new(config),
86-
})
83+
Ok(Self { id: Arc::new(config.id().to_owned()), client, config: Arc::new(config) })
8784
}
8885

8986
pub fn pubkey(&self) -> BlsPublicKey {

crates/common/src/pbs/types/get_header.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ use super::{
1212

1313
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
1414
pub struct GetHeaderParams {
15+
/// The slot to request the header for
1516
pub slot: u64,
17+
/// The parent hash of the block to request the header for
1618
pub parent_hash: B256,
19+
/// The pubkey of the validator that is requesting the header
1720
pub pubkey: BlsPublicKey,
1821
}
1922

0 commit comments

Comments
 (0)