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
2 changes: 1 addition & 1 deletion config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# Chain spec ID. Supported values:
# A network ID. Supported values: Mainnet, Holesky, Sepolia, Helder.
# A path to a chain spec file, either in .json format (e.g., as returned by the beacon endpoint /eth/v1/config/spec), or in .yml format (see examples in tests/data).
# A custom object, e.g., chain = { genesis_time_secs = 1695902400, path = "/path/to/spec.json" }, with a path to a chain spec file, either in .json format (e.g., as returned by the beacon endpoint /eth/v1/config/spec), or in .yml format (see examples in tests/data).
# A custom object, e.g., chain = { genesis_time_secs = 1695902400, slot_time_secs = 12, genesis_fork_version = "0x01017000" }.
chain = "Holesky"

Expand Down
11 changes: 11 additions & 0 deletions configs/custom_chain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# PBS config with a custom chain spec file

# genesis time in seconds needs to be specified
chain = { genesis_time_secs = 100, path = "tests/data/holesky_spec.json" }

[pbs]
port = 18550

[[relays]]
id = "example-relay"
url = "http://0xa1cec75a3f0661e99299274182938151e8433c61a19222347ea1313d839229cb4ce4e3e5aa2bdeb71c8fcf1b084963c2@abc.xyz"
49 changes: 31 additions & 18 deletions crates/common/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::path::PathBuf;
use eyre::Result;
use serde::{Deserialize, Serialize};

use crate::types::{load_chain_from_file, Chain, ChainLoader};
use crate::types::{load_chain_from_file, Chain, ChainLoader, ForkVersion};

mod constants;
mod log;
Expand Down Expand Up @@ -52,23 +52,35 @@ impl CommitBoostConfig {
// When loading the config from the environment, it's important that every path
// is replaced with the correct value if the config is loaded inside a container
pub fn from_env_path() -> Result<Self> {
let config = if let Some(path) = load_optional_env_var(CHAIN_SPEC_ENV) {
// if the chain spec file is set, load it separately
let chain: Chain = load_chain_from_file(path.parse()?)?;
let rest_config: HelperConfig = load_file_from_env(CONFIG_ENV)?;
let helper_config: HelperConfig = load_file_from_env(CONFIG_ENV)?;

CommitBoostConfig {
chain,
relays: rest_config.relays,
pbs: rest_config.pbs,
muxes: rest_config.muxes,
modules: rest_config.modules,
signer: rest_config.signer,
metrics: rest_config.metrics,
logs: rest_config.logs,
let chain = match helper_config.chain {
ChainLoader::Path { path, genesis_time_secs } => {
// check if the file path is overridden by env var
let (slot_time_secs, genesis_fork_version) =
if let Some(path) = load_optional_env_var(CHAIN_SPEC_ENV) {
load_chain_from_file(path.parse()?)?
} else {
load_chain_from_file(path)?
};
Chain::Custom { genesis_time_secs, slot_time_secs, genesis_fork_version }
}
} else {
load_file_from_env(CONFIG_ENV)?
ChainLoader::Known(known) => Chain::from(known),
ChainLoader::Custom { genesis_time_secs, slot_time_secs, genesis_fork_version } => {
let genesis_fork_version: ForkVersion = genesis_fork_version.as_ref().try_into()?;
Chain::Custom { genesis_time_secs, slot_time_secs, genesis_fork_version }
}
};

let config = CommitBoostConfig {
chain,
relays: helper_config.relays,
pbs: helper_config.pbs,
muxes: helper_config.muxes,
modules: helper_config.modules,
signer: helper_config.signer,
metrics: helper_config.metrics,
logs: helper_config.logs,
};

config.validate()?;
Expand All @@ -79,8 +91,8 @@ impl CommitBoostConfig {
pub fn chain_spec_file(path: &str) -> Option<PathBuf> {
match load_from_file::<ChainConfig>(path) {
Ok(config) => {
if let ChainLoader::Path(path_buf) = config.chain {
Some(path_buf)
if let ChainLoader::Path { path, genesis_time_secs: _ } = config.chain {
Some(path)
} else {
None
}
Expand All @@ -99,6 +111,7 @@ struct ChainConfig {
/// Helper struct to load the rest of the config
#[derive(Deserialize)]
struct HelperConfig {
chain: ChainLoader,
relays: Vec<RelayConfig>,
pbs: StaticPbsConfig,
#[serde(flatten)]
Expand Down
86 changes: 42 additions & 44 deletions crates/common/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ pub enum Chain {
Holesky,
Sepolia,
Helder,
Custom { genesis_time_secs: u64, slot_time_secs: u64, genesis_fork_version: [u8; 4] },
Custom { genesis_time_secs: u64, slot_time_secs: u64, genesis_fork_version: ForkVersion },
}

pub type ForkVersion = [u8; 4];

impl std::fmt::Debug for Chain {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Expand Down Expand Up @@ -54,7 +56,7 @@ impl Chain {
}
}

pub fn genesis_fork_version(&self) -> [u8; 4] {
pub fn genesis_fork_version(&self) -> ForkVersion {
match self {
Chain::Mainnet => KnownChain::Mainnet.genesis_fork_version(),
Chain::Holesky => KnownChain::Holesky.genesis_fork_version(),
Expand Down Expand Up @@ -120,7 +122,7 @@ impl KnownChain {
}
}

pub fn genesis_fork_version(&self) -> [u8; 4] {
pub fn genesis_fork_version(&self) -> ForkVersion {
match self {
KnownChain::Mainnet => hex!("00000000"),
KnownChain::Holesky => hex!("01017000"),
Expand Down Expand Up @@ -163,8 +165,19 @@ impl From<KnownChain> for Chain {
#[serde(untagged)]
pub enum ChainLoader {
Known(KnownChain),
Path(PathBuf),
Custom { genesis_time_secs: u64, slot_time_secs: u64, genesis_fork_version: Bytes },
Path {
/// Genesis time as returned in /eth/v1/beacon/genesis
genesis_time_secs: u64,
/// Path to the genesis spec, as returned by /eth/v1/config/spec
/// either in JSON or YAML format
path: PathBuf,
},
Custom {
/// Genesis time as returned in /eth/v1/beacon/genesis
genesis_time_secs: u64,
slot_time_secs: u64,
genesis_fork_version: Bytes,
},
}

impl Serialize for Chain {
Expand Down Expand Up @@ -199,48 +212,40 @@ impl<'de> Deserialize<'de> for Chain {

match loader {
ChainLoader::Known(known) => Ok(Chain::from(known)),
ChainLoader::Path(path) => load_chain_from_file(path).map_err(serde::de::Error::custom),
ChainLoader::Path { genesis_time_secs, path } => {
let (slot_time_secs, genesis_fork_version) =
load_chain_from_file(path).map_err(serde::de::Error::custom)?;
Ok(Chain::Custom { genesis_time_secs, slot_time_secs, genesis_fork_version })
}
ChainLoader::Custom { genesis_time_secs, slot_time_secs, genesis_fork_version } => {
let genesis_fork_version: [u8; 4] =
let genesis_fork_version: ForkVersion =
genesis_fork_version.as_ref().try_into().map_err(serde::de::Error::custom)?;
Ok(Chain::Custom { genesis_time_secs, slot_time_secs, genesis_fork_version })
}
}
}
}

/// Load a chain config from a spec file, such as returned by
/// /eth/v1/config/spec ref: https://ethereum.github.io/beacon-APIs/#/Config/getSpec
/// Returns seconds_per_slot and genesis_fork_version from a spec, such as
/// returned by /eth/v1/config/spec ref: https://ethereum.github.io/beacon-APIs/#/Config/getSpec
/// Try to load two formats:
/// - JSON as return the getSpec endpoint, either with or without the `data`
/// field
/// - YAML as used e.g. in Kurtosis/Ethereum Package
pub fn load_chain_from_file(path: PathBuf) -> eyre::Result<Chain> {
pub fn load_chain_from_file(path: PathBuf) -> eyre::Result<(u64, ForkVersion)> {
#[derive(Deserialize)]
#[serde(rename_all = "UPPERCASE")]
struct QuotedSpecFile {
#[serde(with = "serde_utils::quoted_u64")]
min_genesis_time: u64,
#[serde(with = "serde_utils::quoted_u64")]
genesis_delay: u64,
#[serde(with = "serde_utils::quoted_u64")]
seconds_per_slot: u64,
genesis_fork_version: Bytes,
}

impl QuotedSpecFile {
fn to_chain(&self) -> eyre::Result<Chain> {
let genesis_fork_version: [u8; 4] = self.genesis_fork_version.as_ref().try_into()?;

Ok(Chain::Custom {
// note that this can be wrong, (e.g. it's wrong in mainnet). The correct
// value should come from /eth/v1/beacon/genesis
// more info here: https://kb.beaconcha.in/ethereum-staking/the-genesis-event
// FIXME
genesis_time_secs: self.min_genesis_time + self.genesis_delay,
slot_time_secs: self.seconds_per_slot,
genesis_fork_version,
})
fn to_chain(&self) -> eyre::Result<(u64, ForkVersion)> {
let genesis_fork_version: ForkVersion =
self.genesis_fork_version.as_ref().try_into()?;
Ok((self.seconds_per_slot, genesis_fork_version))
}
}

Expand All @@ -252,21 +257,14 @@ pub fn load_chain_from_file(path: PathBuf) -> eyre::Result<Chain> {
#[derive(Deserialize)]
#[serde(rename_all = "UPPERCASE")]
struct SpecFile {
min_genesis_time: u64,
genesis_delay: u64,
seconds_per_slot: u64,
genesis_fork_version: u32,
}

impl SpecFile {
fn to_chain(&self) -> Chain {
let genesis_fork_version: [u8; 4] = self.genesis_fork_version.to_be_bytes();

Chain::Custom {
genesis_time_secs: self.min_genesis_time + self.genesis_delay,
slot_time_secs: self.seconds_per_slot,
genesis_fork_version,
}
fn to_chain(&self) -> (u64, ForkVersion) {
let genesis_fork_version: ForkVersion = self.genesis_fork_version.to_be_bytes();
(self.seconds_per_slot, genesis_fork_version)
}
}

Expand Down Expand Up @@ -320,11 +318,11 @@ mod tests {
path.pop();
path.push("tests/data/mainnet_spec_data.json");

let s = format!("chain = {path:?}");
let s = format!("chain = {{ genesis_time_secs = 1, path = {path:?}}}");

let decoded: MockConfig = toml::from_str(&s).unwrap();

// see fixme in load_chain_from_file
assert_eq!(decoded.chain.genesis_time_sec(), 1);
assert_eq!(decoded.chain.slot_time_sec(), KnownChain::Mainnet.slot_time_sec());
assert_eq!(
decoded.chain.genesis_fork_version(),
Expand All @@ -341,11 +339,11 @@ mod tests {
path.pop();
path.push("tests/data/holesky_spec.json");

let s = format!("chain = {path:?}");
let s = format!("chain = {{ genesis_time_secs = 1, path = {path:?}}}");

let decoded: MockConfig = toml::from_str(&s).unwrap();
assert_eq!(decoded.chain, Chain::Custom {
genesis_time_secs: KnownChain::Holesky.genesis_time_sec(),
genesis_time_secs: 1,
slot_time_secs: KnownChain::Holesky.slot_time_sec(),
genesis_fork_version: KnownChain::Holesky.genesis_fork_version()
})
Expand All @@ -360,11 +358,11 @@ mod tests {
path.pop();
path.push("tests/data/sepolia_spec_data.json");

let s = format!("chain = {path:?}");
let s = format!("chain = {{ genesis_time_secs = 1, path = {path:?}}}");

let decoded: MockConfig = toml::from_str(&s).unwrap();
assert_eq!(decoded.chain, Chain::Custom {
genesis_time_secs: KnownChain::Sepolia.genesis_time_sec(),
genesis_time_secs: 1,
slot_time_secs: KnownChain::Sepolia.slot_time_sec(),
genesis_fork_version: KnownChain::Sepolia.genesis_fork_version()
})
Expand All @@ -379,11 +377,11 @@ mod tests {
path.pop();
path.push("tests/data/helder_spec.yml");

let s = format!("chain = {path:?}");
let s = format!("chain = {{ genesis_time_secs = 1, path = {path:?}}}");

let decoded: MockConfig = toml::from_str(&s).unwrap();
assert_eq!(decoded.chain, Chain::Custom {
genesis_time_secs: KnownChain::Helder.genesis_time_sec(),
genesis_time_secs: 1,
slot_time_secs: KnownChain::Helder.slot_time_sec(),
genesis_fork_version: KnownChain::Helder.genesis_fork_version()
})
Expand Down