Skip to content

Commit 1305b46

Browse files
authored
fix: genesis time in custom chain spec (#190)
* add genesis time in path chain loader * fixes
1 parent ca153cc commit 1305b46

File tree

4 files changed

+85
-63
lines changed

4 files changed

+85
-63
lines changed

config.example.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
# Chain spec ID. Supported values:
55
# A network ID. Supported values: Mainnet, Holesky, Sepolia, Helder.
6-
# 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).
6+
# 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).
77
# A custom object, e.g., chain = { genesis_time_secs = 1695902400, slot_time_secs = 12, genesis_fork_version = "0x01017000" }.
88
chain = "Holesky"
99

configs/custom_chain.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# PBS config with a custom chain spec file
2+
3+
# genesis time in seconds needs to be specified
4+
chain = { genesis_time_secs = 100, path = "tests/data/holesky_spec.json" }
5+
6+
[pbs]
7+
port = 18550
8+
9+
[[relays]]
10+
id = "example-relay"
11+
url = "http://0xa1cec75a3f0661e99299274182938151e8433c61a19222347ea1313d839229cb4ce4e3e5aa2bdeb71c8fcf1b084963c2@abc.xyz"

crates/common/src/config/mod.rs

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::path::PathBuf;
33
use eyre::Result;
44
use serde::{Deserialize, Serialize};
55

6-
use crate::types::{load_chain_from_file, Chain, ChainLoader};
6+
use crate::types::{load_chain_from_file, Chain, ChainLoader, ForkVersion};
77

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

60-
CommitBoostConfig {
61-
chain,
62-
relays: rest_config.relays,
63-
pbs: rest_config.pbs,
64-
muxes: rest_config.muxes,
65-
modules: rest_config.modules,
66-
signer: rest_config.signer,
67-
metrics: rest_config.metrics,
68-
logs: rest_config.logs,
57+
let chain = match helper_config.chain {
58+
ChainLoader::Path { path, genesis_time_secs } => {
59+
// check if the file path is overridden by env var
60+
let (slot_time_secs, genesis_fork_version) =
61+
if let Some(path) = load_optional_env_var(CHAIN_SPEC_ENV) {
62+
load_chain_from_file(path.parse()?)?
63+
} else {
64+
load_chain_from_file(path)?
65+
};
66+
Chain::Custom { genesis_time_secs, slot_time_secs, genesis_fork_version }
6967
}
70-
} else {
71-
load_file_from_env(CONFIG_ENV)?
68+
ChainLoader::Known(known) => Chain::from(known),
69+
ChainLoader::Custom { genesis_time_secs, slot_time_secs, genesis_fork_version } => {
70+
let genesis_fork_version: ForkVersion = genesis_fork_version.as_ref().try_into()?;
71+
Chain::Custom { genesis_time_secs, slot_time_secs, genesis_fork_version }
72+
}
73+
};
74+
75+
let config = CommitBoostConfig {
76+
chain,
77+
relays: helper_config.relays,
78+
pbs: helper_config.pbs,
79+
muxes: helper_config.muxes,
80+
modules: helper_config.modules,
81+
signer: helper_config.signer,
82+
metrics: helper_config.metrics,
83+
logs: helper_config.logs,
7284
};
7385

7486
config.validate()?;
@@ -79,8 +91,8 @@ impl CommitBoostConfig {
7991
pub fn chain_spec_file(path: &str) -> Option<PathBuf> {
8092
match load_from_file::<ChainConfig>(path) {
8193
Ok(config) => {
82-
if let ChainLoader::Path(path_buf) = config.chain {
83-
Some(path_buf)
94+
if let ChainLoader::Path { path, genesis_time_secs: _ } = config.chain {
95+
Some(path)
8496
} else {
8597
None
8698
}
@@ -99,6 +111,7 @@ struct ChainConfig {
99111
/// Helper struct to load the rest of the config
100112
#[derive(Deserialize)]
101113
struct HelperConfig {
114+
chain: ChainLoader,
102115
relays: Vec<RelayConfig>,
103116
pbs: StaticPbsConfig,
104117
#[serde(flatten)]

crates/common/src/types.rs

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ pub enum Chain {
2323
Holesky,
2424
Sepolia,
2525
Helder,
26-
Custom { genesis_time_secs: u64, slot_time_secs: u64, genesis_fork_version: [u8; 4] },
26+
Custom { genesis_time_secs: u64, slot_time_secs: u64, genesis_fork_version: ForkVersion },
2727
}
2828

29+
pub type ForkVersion = [u8; 4];
30+
2931
impl std::fmt::Debug for Chain {
3032
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3133
match self {
@@ -54,7 +56,7 @@ impl Chain {
5456
}
5557
}
5658

57-
pub fn genesis_fork_version(&self) -> [u8; 4] {
59+
pub fn genesis_fork_version(&self) -> ForkVersion {
5860
match self {
5961
Chain::Mainnet => KnownChain::Mainnet.genesis_fork_version(),
6062
Chain::Holesky => KnownChain::Holesky.genesis_fork_version(),
@@ -120,7 +122,7 @@ impl KnownChain {
120122
}
121123
}
122124

123-
pub fn genesis_fork_version(&self) -> [u8; 4] {
125+
pub fn genesis_fork_version(&self) -> ForkVersion {
124126
match self {
125127
KnownChain::Mainnet => hex!("00000000"),
126128
KnownChain::Holesky => hex!("01017000"),
@@ -163,8 +165,19 @@ impl From<KnownChain> for Chain {
163165
#[serde(untagged)]
164166
pub enum ChainLoader {
165167
Known(KnownChain),
166-
Path(PathBuf),
167-
Custom { genesis_time_secs: u64, slot_time_secs: u64, genesis_fork_version: Bytes },
168+
Path {
169+
/// Genesis time as returned in /eth/v1/beacon/genesis
170+
genesis_time_secs: u64,
171+
/// Path to the genesis spec, as returned by /eth/v1/config/spec
172+
/// either in JSON or YAML format
173+
path: PathBuf,
174+
},
175+
Custom {
176+
/// Genesis time as returned in /eth/v1/beacon/genesis
177+
genesis_time_secs: u64,
178+
slot_time_secs: u64,
179+
genesis_fork_version: Bytes,
180+
},
168181
}
169182

170183
impl Serialize for Chain {
@@ -199,48 +212,40 @@ impl<'de> Deserialize<'de> for Chain {
199212

200213
match loader {
201214
ChainLoader::Known(known) => Ok(Chain::from(known)),
202-
ChainLoader::Path(path) => load_chain_from_file(path).map_err(serde::de::Error::custom),
215+
ChainLoader::Path { genesis_time_secs, path } => {
216+
let (slot_time_secs, genesis_fork_version) =
217+
load_chain_from_file(path).map_err(serde::de::Error::custom)?;
218+
Ok(Chain::Custom { genesis_time_secs, slot_time_secs, genesis_fork_version })
219+
}
203220
ChainLoader::Custom { genesis_time_secs, slot_time_secs, genesis_fork_version } => {
204-
let genesis_fork_version: [u8; 4] =
221+
let genesis_fork_version: ForkVersion =
205222
genesis_fork_version.as_ref().try_into().map_err(serde::de::Error::custom)?;
206223
Ok(Chain::Custom { genesis_time_secs, slot_time_secs, genesis_fork_version })
207224
}
208225
}
209226
}
210227
}
211228

212-
/// Load a chain config from a spec file, such as returned by
213-
/// /eth/v1/config/spec ref: https://ethereum.github.io/beacon-APIs/#/Config/getSpec
229+
/// Returns seconds_per_slot and genesis_fork_version from a spec, such as
230+
/// returned by /eth/v1/config/spec ref: https://ethereum.github.io/beacon-APIs/#/Config/getSpec
214231
/// Try to load two formats:
215232
/// - JSON as return the getSpec endpoint, either with or without the `data`
216233
/// field
217234
/// - YAML as used e.g. in Kurtosis/Ethereum Package
218-
pub fn load_chain_from_file(path: PathBuf) -> eyre::Result<Chain> {
235+
pub fn load_chain_from_file(path: PathBuf) -> eyre::Result<(u64, ForkVersion)> {
219236
#[derive(Deserialize)]
220237
#[serde(rename_all = "UPPERCASE")]
221238
struct QuotedSpecFile {
222-
#[serde(with = "serde_utils::quoted_u64")]
223-
min_genesis_time: u64,
224-
#[serde(with = "serde_utils::quoted_u64")]
225-
genesis_delay: u64,
226239
#[serde(with = "serde_utils::quoted_u64")]
227240
seconds_per_slot: u64,
228241
genesis_fork_version: Bytes,
229242
}
230243

231244
impl QuotedSpecFile {
232-
fn to_chain(&self) -> eyre::Result<Chain> {
233-
let genesis_fork_version: [u8; 4] = self.genesis_fork_version.as_ref().try_into()?;
234-
235-
Ok(Chain::Custom {
236-
// note that this can be wrong, (e.g. it's wrong in mainnet). The correct
237-
// value should come from /eth/v1/beacon/genesis
238-
// more info here: https://kb.beaconcha.in/ethereum-staking/the-genesis-event
239-
// FIXME
240-
genesis_time_secs: self.min_genesis_time + self.genesis_delay,
241-
slot_time_secs: self.seconds_per_slot,
242-
genesis_fork_version,
243-
})
245+
fn to_chain(&self) -> eyre::Result<(u64, ForkVersion)> {
246+
let genesis_fork_version: ForkVersion =
247+
self.genesis_fork_version.as_ref().try_into()?;
248+
Ok((self.seconds_per_slot, genesis_fork_version))
244249
}
245250
}
246251

@@ -252,21 +257,14 @@ pub fn load_chain_from_file(path: PathBuf) -> eyre::Result<Chain> {
252257
#[derive(Deserialize)]
253258
#[serde(rename_all = "UPPERCASE")]
254259
struct SpecFile {
255-
min_genesis_time: u64,
256-
genesis_delay: u64,
257260
seconds_per_slot: u64,
258261
genesis_fork_version: u32,
259262
}
260263

261264
impl SpecFile {
262-
fn to_chain(&self) -> Chain {
263-
let genesis_fork_version: [u8; 4] = self.genesis_fork_version.to_be_bytes();
264-
265-
Chain::Custom {
266-
genesis_time_secs: self.min_genesis_time + self.genesis_delay,
267-
slot_time_secs: self.seconds_per_slot,
268-
genesis_fork_version,
269-
}
265+
fn to_chain(&self) -> (u64, ForkVersion) {
266+
let genesis_fork_version: ForkVersion = self.genesis_fork_version.to_be_bytes();
267+
(self.seconds_per_slot, genesis_fork_version)
270268
}
271269
}
272270

@@ -320,11 +318,11 @@ mod tests {
320318
path.pop();
321319
path.push("tests/data/mainnet_spec_data.json");
322320

323-
let s = format!("chain = {path:?}");
321+
let s = format!("chain = {{ genesis_time_secs = 1, path = {path:?}}}");
324322

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

327-
// see fixme in load_chain_from_file
325+
assert_eq!(decoded.chain.genesis_time_sec(), 1);
328326
assert_eq!(decoded.chain.slot_time_sec(), KnownChain::Mainnet.slot_time_sec());
329327
assert_eq!(
330328
decoded.chain.genesis_fork_version(),
@@ -341,11 +339,11 @@ mod tests {
341339
path.pop();
342340
path.push("tests/data/holesky_spec.json");
343341

344-
let s = format!("chain = {path:?}");
342+
let s = format!("chain = {{ genesis_time_secs = 1, path = {path:?}}}");
345343

346344
let decoded: MockConfig = toml::from_str(&s).unwrap();
347345
assert_eq!(decoded.chain, Chain::Custom {
348-
genesis_time_secs: KnownChain::Holesky.genesis_time_sec(),
346+
genesis_time_secs: 1,
349347
slot_time_secs: KnownChain::Holesky.slot_time_sec(),
350348
genesis_fork_version: KnownChain::Holesky.genesis_fork_version()
351349
})
@@ -360,11 +358,11 @@ mod tests {
360358
path.pop();
361359
path.push("tests/data/sepolia_spec_data.json");
362360

363-
let s = format!("chain = {path:?}");
361+
let s = format!("chain = {{ genesis_time_secs = 1, path = {path:?}}}");
364362

365363
let decoded: MockConfig = toml::from_str(&s).unwrap();
366364
assert_eq!(decoded.chain, Chain::Custom {
367-
genesis_time_secs: KnownChain::Sepolia.genesis_time_sec(),
365+
genesis_time_secs: 1,
368366
slot_time_secs: KnownChain::Sepolia.slot_time_sec(),
369367
genesis_fork_version: KnownChain::Sepolia.genesis_fork_version()
370368
})
@@ -379,11 +377,11 @@ mod tests {
379377
path.pop();
380378
path.push("tests/data/helder_spec.yml");
381379

382-
let s = format!("chain = {path:?}");
380+
let s = format!("chain = {{ genesis_time_secs = 1, path = {path:?}}}");
383381

384382
let decoded: MockConfig = toml::from_str(&s).unwrap();
385383
assert_eq!(decoded.chain, Chain::Custom {
386-
genesis_time_secs: KnownChain::Helder.genesis_time_sec(),
384+
genesis_time_secs: 1,
387385
slot_time_secs: KnownChain::Helder.slot_time_sec(),
388386
genesis_fork_version: KnownChain::Helder.genesis_fork_version()
389387
})

0 commit comments

Comments
 (0)