From 1305b463981dec87dd19f2a94f714c394cd5dd03 Mon Sep 17 00:00:00 2001 From: ltitanb <163874448+ltitanb@users.noreply.github.com> Date: Mon, 25 Nov 2024 11:55:48 +0000 Subject: [PATCH] fix: genesis time in custom chain spec (#190) * add genesis time in path chain loader * fixes --- config.example.toml | 2 +- configs/custom_chain.toml | 11 +++++ crates/common/src/config/mod.rs | 49 ++++++++++++------- crates/common/src/types.rs | 86 ++++++++++++++++----------------- 4 files changed, 85 insertions(+), 63 deletions(-) create mode 100644 configs/custom_chain.toml diff --git a/config.example.toml b/config.example.toml index a58c335d..86110b5b 100644 --- a/config.example.toml +++ b/config.example.toml @@ -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" diff --git a/configs/custom_chain.toml b/configs/custom_chain.toml new file mode 100644 index 00000000..58c516ad --- /dev/null +++ b/configs/custom_chain.toml @@ -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" diff --git a/crates/common/src/config/mod.rs b/crates/common/src/config/mod.rs index 94c5e368..70acfadd 100644 --- a/crates/common/src/config/mod.rs +++ b/crates/common/src/config/mod.rs @@ -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; @@ -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 { - 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()?; @@ -79,8 +91,8 @@ impl CommitBoostConfig { pub fn chain_spec_file(path: &str) -> Option { match load_from_file::(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 } @@ -99,6 +111,7 @@ struct ChainConfig { /// Helper struct to load the rest of the config #[derive(Deserialize)] struct HelperConfig { + chain: ChainLoader, relays: Vec, pbs: StaticPbsConfig, #[serde(flatten)] diff --git a/crates/common/src/types.rs b/crates/common/src/types.rs index c49323a6..c7684520 100644 --- a/crates/common/src/types.rs +++ b/crates/common/src/types.rs @@ -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 { @@ -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(), @@ -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"), @@ -163,8 +165,19 @@ impl From 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 { @@ -199,9 +212,13 @@ 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 }) } @@ -209,38 +226,26 @@ impl<'de> Deserialize<'de> for Chain { } } -/// 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 { +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 { - 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)) } } @@ -252,21 +257,14 @@ pub fn load_chain_from_file(path: PathBuf) -> eyre::Result { #[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) } } @@ -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(), @@ -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() }) @@ -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() }) @@ -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() })