Skip to content

Commit 0be1454

Browse files
authored
feat(mux): add mux file loader (#192)
* add mux file loader * fix
1 parent 93ff3c7 commit 0be1454

File tree

11 files changed

+119
-21
lines changed

11 files changed

+119
-21
lines changed

config.example.toml

+7-1
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,17 @@ frequency_get_header_ms = 300
103103
# - the mux is only used for get header requests
104104
# - if any value is missing from the mux config, the default value from the main config will be used
105105
[[mux]]
106-
# Which validator pubkeys to match against this mux config
106+
# Unique ID for the mux config
107+
id = "test_mux"
108+
# Which validator pubkeys to match against this mux config. This can be empty or omitted if a loader is specified.
109+
# Any keys loaded via the loader will be added to this list.
107110
validator_pubkeys = [
108111
"0x80c7f782b2467c5898c5516a8b6595d75623960b4afc4f71ee07d40985d20e117ba35e7cd352a3e75fb85a8668a3b745",
109112
"0xa119589bb33ef52acbb8116832bec2b58fca590fe5c85eac5d3230b44d5bc09fe73ccd21f88eab31d6de16194d17782e",
110113
]
114+
# Path to a file containing a list of validator pubkeys
115+
# OPTIONAL
116+
loader = "./mux_keys.example.json"
111117
timeout_get_header_ms = 900
112118
late_in_slot_time_ms = 1500
113119
# For each mux, one or more [[pbs_mux.relays]] can be defined, which will be used for the matching validator pubkeys

configs/pbs-mux.toml

+2
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ enable_timing_games = true
1818
target_first_request_ms = 200
1919

2020
[[mux]]
21+
id = "test_mux"
2122
validator_pubkeys = [
2223
"0x80c7f782b2467c5898c5516a8b6595d75623960b4afc4f71ee07d40985d20e117ba35e7cd352a3e75fb85a8668a3b745",
2324
]
25+
loader = "./mux_keys.example.json"
2426
timeout_get_header_ms = 900
2527
late_in_slot_time_ms = 1500
2628

crates/cli/src/docker_init.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,17 @@ pub fn handle_docker_init(config_path: String, output_dir: String) -> Result<()>
219219
}
220220

221221
let mut pbs_envs = IndexMap::from([get_env_val(CONFIG_ENV, CONFIG_DEFAULT)]);
222+
let mut pbs_volumes = vec![config_volume.clone()];
223+
224+
if let Some(mux_config) = cb_config.muxes {
225+
for mux in mux_config.muxes.iter() {
226+
if let Some((env_name, actual_path, internal_path)) = mux.loader_env() {
227+
let (key, val) = get_env_val(&env_name, &internal_path);
228+
pbs_envs.insert(key, val);
229+
pbs_volumes.push(Volumes::Simple(format!("{}:{}:ro", actual_path, internal_path)));
230+
}
231+
}
232+
}
222233

223234
if let Some((key, val)) = chain_spec_env.clone() {
224235
pbs_envs.insert(key, val);
@@ -251,7 +262,6 @@ pub fn handle_docker_init(config_path: String, output_dir: String) -> Result<()>
251262
pbs_envs.insert(key, val);
252263

253264
// volumes
254-
let mut pbs_volumes = vec![config_volume.clone()];
255265
pbs_volumes.extend(chain_spec_volume.clone());
256266
pbs_volumes.extend(get_log_volume(&cb_config.logs, PBS_MODULE_NAME));
257267

crates/common/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ publish = false
77

88
[dependencies]
99
# ethereum
10-
alloy = { workspace = true }
10+
alloy.workspace = true
1111
ssz_types.workspace = true
1212
ethereum_serde_utils.workspace = true
1313

crates/common/src/config/constants.rs

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ pub const BUILDER_URLS_ENV: &str = "CB_BUILDER_URLS";
2525
/// Where to receive BuilderAPI calls from beacon node
2626
pub const PBS_ENDPOINT_ENV: &str = "CB_PBS_ENDPOINT";
2727

28+
pub const MUX_PATH_ENV: &str = "CB_MUX_PATH";
29+
2830
///////////////////////// SIGNER /////////////////////////
2931

3032
pub const SIGNER_IMAGE_DEFAULT: &str = "ghcr.io/commit-boost/signer:latest";

crates/common/src/config/mux.rs

+77-9
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
use std::{
22
collections::{HashMap, HashSet},
3+
path::{Path, PathBuf},
34
sync::Arc,
45
};
56

67
use alloy::rpc::types::beacon::BlsPublicKey;
7-
use eyre::{bail, ensure, eyre};
8+
use eyre::{bail, ensure, eyre, Context};
89
use serde::{Deserialize, Serialize};
910

10-
use super::{PbsConfig, RelayConfig};
11+
use super::{load_optional_env_var, PbsConfig, RelayConfig, MUX_PATH_ENV};
1112
use crate::pbs::{RelayClient, RelayEntry};
1213

13-
#[derive(Debug, Clone, Deserialize, Serialize)]
14+
#[derive(Debug, Deserialize, Serialize)]
1415
pub struct PbsMuxes {
1516
/// List of PBS multiplexers
1617
#[serde(rename = "mux")]
@@ -19,6 +20,7 @@ pub struct PbsMuxes {
1920

2021
#[derive(Debug, Clone)]
2122
pub struct RuntimeMuxConfig {
23+
pub id: String,
2224
pub config: Arc<PbsConfig>,
2325
pub relays: Vec<RelayClient>,
2426
}
@@ -29,9 +31,18 @@ impl PbsMuxes {
2931
default_pbs: &PbsConfig,
3032
default_relays: &[RelayConfig],
3133
) -> eyre::Result<HashMap<BlsPublicKey, RuntimeMuxConfig>> {
34+
let mut muxes = self.muxes;
35+
36+
for mux in muxes.iter_mut() {
37+
if let Some(loader) = &mux.loader {
38+
let extra_keys = loader.load(&mux.id)?;
39+
mux.validator_pubkeys.extend(extra_keys);
40+
}
41+
}
42+
3243
// check that validator pubkeys are in disjoint sets
3344
let mut unique_pubkeys = HashSet::new();
34-
for mux in self.muxes.iter() {
45+
for mux in muxes.iter() {
3546
for pubkey in mux.validator_pubkeys.iter() {
3647
if !unique_pubkeys.insert(pubkey) {
3748
bail!("duplicate validator pubkey in muxes: {pubkey}");
@@ -41,11 +52,12 @@ impl PbsMuxes {
4152

4253
let mut configs = HashMap::new();
4354
// 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");
55+
for mux in muxes {
56+
ensure!(!mux.relays.is_empty(), "mux config {} must have at least one relay", mux.id);
4657
ensure!(
4758
!mux.validator_pubkeys.is_empty(),
48-
"mux config must have at least one validator pubkey"
59+
"mux config {} must have at least one validator pubkey",
60+
mux.id
4961
);
5062

5163
let mut relay_clients = Vec::with_capacity(mux.relays.len());
@@ -89,7 +101,7 @@ impl PbsMuxes {
89101
};
90102
let config = Arc::new(config);
91103

92-
let runtime_config = RuntimeMuxConfig { config, relays: relay_clients };
104+
let runtime_config = RuntimeMuxConfig { id: mux.id, config, relays: relay_clients };
93105
for pubkey in mux.validator_pubkeys.iter() {
94106
configs.insert(*pubkey, runtime_config.clone());
95107
}
@@ -100,16 +112,36 @@ impl PbsMuxes {
100112
}
101113

102114
/// Configuration for the PBS Multiplexer
103-
#[derive(Debug, Clone, Deserialize, Serialize)]
115+
#[derive(Debug, Deserialize, Serialize)]
104116
pub struct MuxConfig {
117+
/// Identifier for this mux config
118+
pub id: String,
105119
/// Relays to use for this mux config
106120
pub relays: Vec<PartialRelayConfig>,
107121
/// Which validator pubkeys to match against this mux config
122+
#[serde(default)]
108123
pub validator_pubkeys: Vec<BlsPublicKey>,
124+
/// Loader for extra validator pubkeys
125+
pub loader: Option<MuxKeysLoader>,
109126
pub timeout_get_header_ms: Option<u64>,
110127
pub late_in_slot_time_ms: Option<u64>,
111128
}
112129

130+
impl MuxConfig {
131+
/// Returns the env, actual path, and internal path to use for the loader
132+
pub fn loader_env(&self) -> Option<(String, String, String)> {
133+
self.loader.as_ref().map(|loader| match loader {
134+
MuxKeysLoader::File(path_buf) => {
135+
let path =
136+
path_buf.to_str().unwrap_or_else(|| panic!("invalid path: {:?}", path_buf));
137+
let internal_path = get_mux_path(&self.id);
138+
139+
(get_mux_env(&self.id), path.to_owned(), internal_path)
140+
}
141+
})
142+
}
143+
}
144+
113145
#[derive(Debug, Clone, Deserialize, Serialize)]
114146
/// A relay config with all optional fields. See [`RelayConfig`] for the
115147
/// description of the fields.
@@ -136,3 +168,39 @@ impl PartialRelayConfig {
136168
}
137169
}
138170
}
171+
172+
#[derive(Debug, Clone, Deserialize, Serialize)]
173+
#[serde(untagged)]
174+
pub enum MuxKeysLoader {
175+
/// A file containing a list of validator pubkeys
176+
File(PathBuf),
177+
}
178+
179+
impl MuxKeysLoader {
180+
pub fn load(&self, mux_id: &str) -> eyre::Result<Vec<BlsPublicKey>> {
181+
match self {
182+
Self::File(config_path) => {
183+
// First try loading from env
184+
let path: PathBuf = load_optional_env_var(&get_mux_env(mux_id))
185+
.map(PathBuf::from)
186+
.unwrap_or(config_path.clone());
187+
let file = load_file(path)?;
188+
serde_json::from_str(&file).wrap_err("failed to parse mux keys file")
189+
}
190+
}
191+
}
192+
}
193+
194+
fn load_file<P: AsRef<Path> + std::fmt::Debug>(path: P) -> eyre::Result<String> {
195+
std::fs::read_to_string(&path).wrap_err(format!("Unable to find mux keys file: {path:?}"))
196+
}
197+
198+
/// A different env var for each mux
199+
fn get_mux_env(mux_id: &str) -> String {
200+
format!("{MUX_PATH_ENV}_{mux_id}")
201+
}
202+
203+
/// Path to the mux file
204+
fn get_mux_path(mux_id: &str) -> String {
205+
format!("/{mux_id}-mux_keys.json")
206+
}

crates/pbs/src/mev_boost/get_header.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,12 @@ pub async fn get_header<S: BuilderApiState>(
5151
}
5252

5353
let ms_into_slot = ms_into_slot(params.slot, state.config.chain);
54-
let (pbs_config, relays, is_mux) = state.mux_config_and_relays(&params.pubkey);
54+
let (pbs_config, relays, maybe_mux_id) = state.mux_config_and_relays(&params.pubkey);
5555

56-
if is_mux {
57-
debug!(pubkey = %params.pubkey, relays = relays.len(), "using mux config");
56+
if let Some(mux_id) = maybe_mux_id {
57+
debug!(mux_id, relays = relays.len(), pubkey = %params.pubkey, "using mux config");
5858
} else {
59-
debug!(pubkey = %params.pubkey, relays = relays.len(), "using default config");
59+
debug!(relays = relays.len(), pubkey = %params.pubkey, "using default config");
6060
}
6161

6262
let max_timeout_ms = pbs_config

crates/pbs/src/state.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,10 @@ where
8888
pub fn mux_config_and_relays(
8989
&self,
9090
pubkey: &BlsPublicKey,
91-
) -> (&PbsConfig, &[RelayClient], bool) {
91+
) -> (&PbsConfig, &[RelayClient], Option<&str>) {
9292
match self.config.muxes.as_ref().and_then(|muxes| muxes.get(pubkey)) {
93-
Some(mux) => (&mux.config, mux.relays.as_slice(), true),
94-
None => (self.pbs_config(), self.relays(), false),
93+
Some(mux) => (&mux.config, mux.relays.as_slice(), Some(&mux.id)),
94+
None => (self.pbs_config(), self.relays(), None),
9595
}
9696
}
9797

docs/docs/get_started/running/binary.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ Modules need some environment variables to work correctly.
2222

2323
### PBS Module
2424
- `CB_BUILDER_URLS`: optional, comma-separated list of urls to `events` modules where to post builder events
25-
25+
- `CB_PBS_ENDPOINT`: optional, override the endpoint where the PBS module will open the port for the beacon node
26+
- `CB_MUX_PATH_{ID}`: optional, override where to load mux validator keys for mux with id=\{ID\}
2627
### Signer Module
2728
- `CB_JWTS`: required, comma-separated list of `MODULE_ID=JWT` to process signature requests
2829
- `CB_SIGNER_PORT`: required, port to open the signer server on

mux_keys.example.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[
2+
"0x8160998addda06f2956e5d1945461f33dbc140486e972b96f341ebf2bdb553a0e3feb127451f5332dd9e33469d37ca67",
3+
"0x87b5dc7f78b68a7b5e7f2e8b9c2115f968332cbf6fc2caaaaa2c9dc219a58206b72c924805f2278c58b55790a2c3bf17",
4+
"0x89e2f50fe5cd07ed2ff0a01340b2f717aa65cced6d89a79fdecc1e924be5f4bbe75c11598bb9a53d307bb39b8223bc52"
5+
]

tests/tests/pbs_integration.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,11 @@ async fn test_mux() -> Result<()> {
218218
tokio::spawn(start_mock_relay_service(mock_state.clone(), port + 2));
219219

220220
let mut config = to_pbs_config(chain, get_pbs_static_config(port), relays);
221-
let mux = RuntimeMuxConfig { config: config.pbs_config.clone(), relays: vec![mux_relay] };
221+
let mux = RuntimeMuxConfig {
222+
id: String::from("test"),
223+
config: config.pbs_config.clone(),
224+
relays: vec![mux_relay],
225+
};
222226

223227
let validator_pubkey = blst_pubkey_to_alloy(&random_secret().sk_to_pk());
224228

0 commit comments

Comments
 (0)