Skip to content

Commit

Permalink
fix: genesis time in custom chain spec (#190)
Browse files Browse the repository at this point in the history
* add genesis time in path chain loader

* fixes
  • Loading branch information
ltitanb authored Nov 25, 2024
1 parent ca153cc commit 1305b46
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 63 deletions.
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

0 comments on commit 1305b46

Please sign in to comment.