@@ -3,6 +3,7 @@ use std::path::PathBuf;
33use alloy:: primitives:: { B256 , Bytes , b256, hex} ;
44use derive_more:: { Deref , Display , From , Into } ;
55use eyre:: { Context , bail} ;
6+ use lh_types:: ForkName ;
67use serde:: { Deserialize , Serialize } ;
78
89use crate :: { constants:: APPLICATION_BUILDER_DOMAIN , signature:: compute_domain} ;
@@ -35,7 +36,12 @@ pub enum Chain {
3536 Sepolia ,
3637 Helder ,
3738 Hoodi ,
38- Custom { genesis_time_secs : u64 , slot_time_secs : u64 , genesis_fork_version : ForkVersion } ,
39+ Custom {
40+ genesis_time_secs : u64 ,
41+ slot_time_secs : u64 ,
42+ genesis_fork_version : ForkVersion ,
43+ fulu_fork_slot : u64 ,
44+ } ,
3945}
4046
4147pub type ForkVersion = [ u8 ; 4 ] ;
@@ -59,11 +65,17 @@ impl std::fmt::Debug for Chain {
5965 Self :: Sepolia => write ! ( f, "Sepolia" ) ,
6066 Self :: Helder => write ! ( f, "Helder" ) ,
6167 Self :: Hoodi => write ! ( f, "Hoodi" ) ,
62- Self :: Custom { genesis_time_secs, slot_time_secs, genesis_fork_version } => f
68+ Self :: Custom {
69+ genesis_time_secs,
70+ slot_time_secs,
71+ genesis_fork_version,
72+ fulu_fork_slot,
73+ } => f
6374 . debug_struct ( "Custom" )
6475 . field ( "genesis_time_secs" , genesis_time_secs)
6576 . field ( "slot_time_secs" , slot_time_secs)
6677 . field ( "genesis_fork_version" , & hex:: encode_prefixed ( genesis_fork_version) )
78+ . field ( "fulu_fork_slot" , fulu_fork_slot)
6779 . finish ( ) ,
6880 }
6981 }
@@ -126,6 +138,21 @@ impl Chain {
126138 Chain :: Custom { slot_time_secs, .. } => * slot_time_secs,
127139 }
128140 }
141+
142+ pub fn fulu_fork_slot ( & self ) -> u64 {
143+ match self {
144+ Chain :: Mainnet => KnownChain :: Mainnet . fulu_fork_slot ( ) ,
145+ Chain :: Holesky => KnownChain :: Holesky . fulu_fork_slot ( ) ,
146+ Chain :: Sepolia => KnownChain :: Sepolia . fulu_fork_slot ( ) ,
147+ Chain :: Helder => KnownChain :: Helder . fulu_fork_slot ( ) ,
148+ Chain :: Hoodi => KnownChain :: Hoodi . fulu_fork_slot ( ) ,
149+ Chain :: Custom { slot_time_secs, .. } => * slot_time_secs,
150+ }
151+ }
152+
153+ pub fn fork_by_slot ( & self , slot : u64 ) -> ForkName {
154+ if slot >= self . fulu_fork_slot ( ) { ForkName :: Fulu } else { ForkName :: Electra }
155+ }
129156}
130157
131158#[ derive( Debug , Clone , Copy , Serialize , Deserialize ) ]
@@ -203,6 +230,15 @@ impl KnownChain {
203230 KnownChain :: Hoodi => 12 ,
204231 }
205232 }
233+
234+ pub fn fulu_fork_slot ( & self ) -> u64 {
235+ match self {
236+ KnownChain :: Mainnet | KnownChain :: Helder => u64:: MAX ,
237+ KnownChain :: Holesky => 5283840 ,
238+ KnownChain :: Sepolia => 8724480 ,
239+ KnownChain :: Hoodi => 1622016 ,
240+ }
241+ }
206242}
207243
208244impl From < KnownChain > for Chain {
@@ -233,6 +269,7 @@ pub enum ChainLoader {
233269 genesis_time_secs : u64 ,
234270 slot_time_secs : u64 ,
235271 genesis_fork_version : Bytes ,
272+ fulu_fork_slot : u64 ,
236273 } ,
237274}
238275
@@ -247,13 +284,17 @@ impl Serialize for Chain {
247284 Chain :: Sepolia => ChainLoader :: Known ( KnownChain :: Sepolia ) ,
248285 Chain :: Helder => ChainLoader :: Known ( KnownChain :: Helder ) ,
249286 Chain :: Hoodi => ChainLoader :: Known ( KnownChain :: Hoodi ) ,
250- Chain :: Custom { genesis_time_secs, slot_time_secs, genesis_fork_version } => {
251- ChainLoader :: Custom {
252- genesis_time_secs : * genesis_time_secs,
253- slot_time_secs : * slot_time_secs,
254- genesis_fork_version : Bytes :: from ( * genesis_fork_version) ,
255- }
256- }
287+ Chain :: Custom {
288+ genesis_time_secs,
289+ slot_time_secs,
290+ genesis_fork_version,
291+ fulu_fork_slot,
292+ } => ChainLoader :: Custom {
293+ genesis_time_secs : * genesis_time_secs,
294+ slot_time_secs : * slot_time_secs,
295+ genesis_fork_version : Bytes :: from ( * genesis_fork_version) ,
296+ fulu_fork_slot : * fulu_fork_slot,
297+ } ,
257298 } ;
258299
259300 loader. serialize ( serializer)
@@ -270,14 +311,29 @@ impl<'de> Deserialize<'de> for Chain {
270311 match loader {
271312 ChainLoader :: Known ( known) => Ok ( Chain :: from ( known) ) ,
272313 ChainLoader :: Path { genesis_time_secs, path } => {
273- let ( slot_time_secs, genesis_fork_version) =
314+ let ( slot_time_secs, genesis_fork_version, fulu_fork_slot ) =
274315 load_chain_from_file ( path) . map_err ( serde:: de:: Error :: custom) ?;
275- Ok ( Chain :: Custom { genesis_time_secs, slot_time_secs, genesis_fork_version } )
316+ Ok ( Chain :: Custom {
317+ genesis_time_secs,
318+ slot_time_secs,
319+ genesis_fork_version,
320+ fulu_fork_slot,
321+ } )
276322 }
277- ChainLoader :: Custom { genesis_time_secs, slot_time_secs, genesis_fork_version } => {
323+ ChainLoader :: Custom {
324+ genesis_time_secs,
325+ slot_time_secs,
326+ genesis_fork_version,
327+ fulu_fork_slot,
328+ } => {
278329 let genesis_fork_version: ForkVersion =
279330 genesis_fork_version. as_ref ( ) . try_into ( ) . map_err ( serde:: de:: Error :: custom) ?;
280- Ok ( Chain :: Custom { genesis_time_secs, slot_time_secs, genesis_fork_version } )
331+ Ok ( Chain :: Custom {
332+ genesis_time_secs,
333+ slot_time_secs,
334+ genesis_fork_version,
335+ fulu_fork_slot,
336+ } )
281337 }
282338 }
283339 }
@@ -289,20 +345,25 @@ impl<'de> Deserialize<'de> for Chain {
289345/// - JSON as return the getSpec endpoint, either with or without the `data`
290346/// field
291347/// - YAML as used e.g. in Kurtosis/Ethereum Package
292- pub fn load_chain_from_file ( path : PathBuf ) -> eyre:: Result < ( u64 , ForkVersion ) > {
348+ pub fn load_chain_from_file ( path : PathBuf ) -> eyre:: Result < ( u64 , ForkVersion , u64 ) > {
293349 #[ derive( Deserialize ) ]
294350 #[ serde( rename_all = "UPPERCASE" ) ]
295351 struct QuotedSpecFile {
296352 #[ serde( with = "serde_utils::quoted_u64" ) ]
297353 seconds_per_slot : u64 ,
298354 genesis_fork_version : Bytes ,
355+ #[ serde( with = "serde_utils::quoted_u64" ) ]
356+ slots_per_epoch : u64 ,
357+ #[ serde( with = "serde_utils::quoted_u64" ) ]
358+ fulu_fork_epoch : u64 ,
299359 }
300360
301361 impl QuotedSpecFile {
302- fn to_chain ( & self ) -> eyre:: Result < ( u64 , ForkVersion ) > {
362+ fn to_chain ( & self ) -> eyre:: Result < ( u64 , ForkVersion , u64 ) > {
303363 let genesis_fork_version: ForkVersion =
304364 self . genesis_fork_version . as_ref ( ) . try_into ( ) ?;
305- Ok ( ( self . seconds_per_slot , genesis_fork_version) )
365+ let fulu_fork_slot = self . fulu_fork_epoch . saturating_mul ( self . slots_per_epoch ) ;
366+ Ok ( ( self . seconds_per_slot , genesis_fork_version, fulu_fork_slot) )
306367 }
307368 }
308369
@@ -316,12 +377,16 @@ pub fn load_chain_from_file(path: PathBuf) -> eyre::Result<(u64, ForkVersion)> {
316377 struct SpecFile {
317378 seconds_per_slot : u64 ,
318379 genesis_fork_version : u32 ,
380+ slots_per_epoch : Option < u64 > ,
381+ fulu_fork_epoch : u64 ,
319382 }
320383
321384 impl SpecFile {
322- fn to_chain ( & self ) -> ( u64 , ForkVersion ) {
385+ fn to_chain ( & self ) -> ( u64 , ForkVersion , u64 ) {
323386 let genesis_fork_version: ForkVersion = self . genesis_fork_version . to_be_bytes ( ) ;
324- ( self . seconds_per_slot , genesis_fork_version)
387+ let fulu_fork_slot =
388+ self . fulu_fork_epoch . saturating_mul ( self . slots_per_epoch . unwrap_or ( 32 ) ) ;
389+ ( self . seconds_per_slot , genesis_fork_version, fulu_fork_slot)
325390 }
326391 }
327392
@@ -357,12 +422,13 @@ mod tests {
357422
358423 #[ test]
359424 fn test_load_custom ( ) {
360- let s = r#"chain = { genesis_time_secs = 1, slot_time_secs = 2, genesis_fork_version = "0x01000000" }"# ;
425+ let s = r#"chain = { genesis_time_secs = 1, slot_time_secs = 2, genesis_fork_version = "0x01000000", fulu_fork_slot = 1 }"# ;
361426 let decoded: MockConfig = toml:: from_str ( s) . unwrap ( ) ;
362427 assert_eq ! ( decoded. chain, Chain :: Custom {
363428 genesis_time_secs: 1 ,
364429 slot_time_secs: 2 ,
365- genesis_fork_version: [ 1 , 0 , 0 , 0 ]
430+ genesis_fork_version: [ 1 , 0 , 0 , 0 ] ,
431+ fulu_fork_slot: 1
366432 } )
367433 }
368434
@@ -403,7 +469,8 @@ mod tests {
403469 assert_eq ! ( decoded. chain, Chain :: Custom {
404470 genesis_time_secs: 1 ,
405471 slot_time_secs: KnownChain :: Holesky . slot_time_sec( ) ,
406- genesis_fork_version: KnownChain :: Holesky . genesis_fork_version( )
472+ genesis_fork_version: KnownChain :: Holesky . genesis_fork_version( ) ,
473+ fulu_fork_slot: KnownChain :: Holesky . fulu_fork_slot( ) ,
407474 } )
408475 }
409476
@@ -423,7 +490,8 @@ mod tests {
423490 assert_eq ! ( decoded. chain, Chain :: Custom {
424491 genesis_time_secs: 1 ,
425492 slot_time_secs: KnownChain :: Sepolia . slot_time_sec( ) ,
426- genesis_fork_version: KnownChain :: Sepolia . genesis_fork_version( )
493+ genesis_fork_version: KnownChain :: Sepolia . genesis_fork_version( ) ,
494+ fulu_fork_slot: KnownChain :: Sepolia . fulu_fork_slot( ) ,
427495 } )
428496 }
429497
@@ -443,7 +511,8 @@ mod tests {
443511 assert_eq ! ( decoded. chain, Chain :: Custom {
444512 genesis_time_secs: 1 ,
445513 slot_time_secs: KnownChain :: Hoodi . slot_time_sec( ) ,
446- genesis_fork_version: KnownChain :: Hoodi . genesis_fork_version( )
514+ genesis_fork_version: KnownChain :: Hoodi . genesis_fork_version( ) ,
515+ fulu_fork_slot: KnownChain :: Hoodi . fulu_fork_slot( ) ,
447516 } )
448517 }
449518
@@ -463,7 +532,8 @@ mod tests {
463532 assert_eq ! ( decoded. chain, Chain :: Custom {
464533 genesis_time_secs: 1 ,
465534 slot_time_secs: KnownChain :: Helder . slot_time_sec( ) ,
466- genesis_fork_version: KnownChain :: Helder . genesis_fork_version( )
535+ genesis_fork_version: KnownChain :: Helder . genesis_fork_version( ) ,
536+ fulu_fork_slot: KnownChain :: Helder . fulu_fork_slot( ) ,
467537 } )
468538 }
469539}
0 commit comments