diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index 4215c50f25..280a0cb344 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -6,8 +6,6 @@ use alloy_primitives::{address, b256, Address, BlockNumber, B256, U256}; use alloy_trie::EMPTY_ROOT_HASH; use derive_more::From; use once_cell::sync::{Lazy, OnceCell}; -#[cfg(feature = "optimism")] -use reth_ethereum_forks::OptimismHardfork; use reth_ethereum_forks::{ ChainHardforks, DisplayHardforks, EthereumHardfork, EthereumHardforks, ForkCondition, ForkFilter, ForkFilterKey, ForkHash, ForkId, Hardfork, Head, DEV_HARDFORKS, @@ -256,7 +254,8 @@ impl ChainSpec { #[inline] #[cfg(feature = "optimism")] pub fn is_optimism(&self) -> bool { - self.chain.is_optimism() || self.hardforks.get(OptimismHardfork::Bedrock).is_some() + self.chain.is_optimism() || + self.hardforks.get(reth_ethereum_forks::OptimismHardfork::Bedrock).is_some() } /// Returns `true` if this chain contains Optimism configuration. @@ -605,119 +604,206 @@ impl ChainSpec { impl From for ChainSpec { fn from(genesis: Genesis) -> Self { - #[cfg(feature = "optimism")] - let optimism_genesis_info = OptimismGenesisInfo::extract_from(&genesis); - #[cfg(feature = "optimism")] - let genesis_info = - optimism_genesis_info.optimism_chain_info.genesis_info.unwrap_or_default(); - - // Block-based hardforks - let hardfork_opts = [ - (EthereumHardfork::Homestead.boxed(), genesis.config.homestead_block), - (EthereumHardfork::Dao.boxed(), genesis.config.dao_fork_block), - (EthereumHardfork::Tangerine.boxed(), genesis.config.eip150_block), - (EthereumHardfork::SpuriousDragon.boxed(), genesis.config.eip155_block), - (EthereumHardfork::Byzantium.boxed(), genesis.config.byzantium_block), - (EthereumHardfork::Constantinople.boxed(), genesis.config.constantinople_block), - (EthereumHardfork::Petersburg.boxed(), genesis.config.petersburg_block), - (EthereumHardfork::Istanbul.boxed(), genesis.config.istanbul_block), - (EthereumHardfork::MuirGlacier.boxed(), genesis.config.muir_glacier_block), - (EthereumHardfork::Berlin.boxed(), genesis.config.berlin_block), - (EthereumHardfork::London.boxed(), genesis.config.london_block), - (EthereumHardfork::ArrowGlacier.boxed(), genesis.config.arrow_glacier_block), - (EthereumHardfork::GrayGlacier.boxed(), genesis.config.gray_glacier_block), - #[cfg(feature = "optimism")] - (OptimismHardfork::Bedrock.boxed(), genesis_info.bedrock_block), - ]; - let mut hardforks = hardfork_opts - .into_iter() - .filter_map(|(hardfork, opt)| opt.map(|block| (hardfork, ForkCondition::Block(block)))) - .collect::>(); - - // Paris - let paris_block_and_final_difficulty = - if let Some(ttd) = genesis.config.terminal_total_difficulty { - hardforks.push(( - EthereumHardfork::Paris.boxed(), - ForkCondition::TTD { - total_difficulty: ttd, - fork_block: genesis.config.merge_netsplit_block, - }, - )); - - genesis.config.merge_netsplit_block.map(|block| (block, ttd)) - } else { - None - }; - - // Time-based hardforks - let time_hardfork_opts = [ - (EthereumHardfork::Shanghai.boxed(), genesis.config.shanghai_time), - (EthereumHardfork::Cancun.boxed(), genesis.config.cancun_time), - (EthereumHardfork::Prague.boxed(), genesis.config.prague_time), - #[cfg(feature = "optimism")] - (OptimismHardfork::Regolith.boxed(), genesis_info.regolith_time), - #[cfg(feature = "optimism")] - (OptimismHardfork::Canyon.boxed(), genesis_info.canyon_time), - #[cfg(feature = "optimism")] - (OptimismHardfork::Ecotone.boxed(), genesis_info.ecotone_time), - #[cfg(feature = "optimism")] - (OptimismHardfork::Fjord.boxed(), genesis_info.fjord_time), - #[cfg(feature = "optimism")] - (OptimismHardfork::Granite.boxed(), genesis_info.granite_time), - ]; - - let time_hardforks = time_hardfork_opts - .into_iter() - .filter_map(|(hardfork, opt)| { - opt.map(|time| (hardfork, ForkCondition::Timestamp(time))) - }) - .collect::>(); - - hardforks.extend(time_hardforks); - - // Uses ethereum or optimism main chains to find proper order #[cfg(not(feature = "optimism"))] - let mainnet_hardforks: ChainHardforks = EthereumHardfork::mainnet().into(); - #[cfg(not(feature = "optimism"))] - let mainnet_order = mainnet_hardforks.forks_iter(); - #[cfg(feature = "optimism")] - let mainnet_hardforks = OptimismHardfork::op_mainnet(); - #[cfg(feature = "optimism")] - let mainnet_order = mainnet_hardforks.forks_iter(); - - let mut ordered_hardforks = Vec::with_capacity(hardforks.len()); - for (hardfork, _) in mainnet_order { - if let Some(pos) = hardforks.iter().position(|(e, _)| **e == *hardfork) { - ordered_hardforks.push(hardforks.remove(pos)); - } + { + into_ethereum_chain_spec(genesis) } - // append the remaining unknown hardforks to ensure we don't filter any out - ordered_hardforks.extend(hardforks); - - // NOTE: in full node, we prune all receipts except the deposit contract's. We do not - // have the deployment block in the genesis file, so we use block zero. We use the same - // deposit topic as the mainnet contract if we have the deposit contract address in the - // genesis json. - let deposit_contract = genesis.config.deposit_contract_address.map(|address| { - DepositContract { address, block: 0, topic: MAINNET_DEPOSIT_CONTRACT.topic } - }); - - Self { - chain: genesis.config.chain_id.into(), - genesis, - genesis_hash: OnceCell::new(), - hardforks: ChainHardforks::new(ordered_hardforks), - paris_block_and_final_difficulty, - deposit_contract, - #[cfg(feature = "optimism")] - base_fee_params: optimism_genesis_info.base_fee_params, - ..Default::default() + #[cfg(feature = "optimism")] + { + into_optimism_chain_spec(genesis) } } } +/// Convert the given [`Genesis`] into an Ethereum [`ChainSpec`]. +#[cfg(not(feature = "optimism"))] +fn into_ethereum_chain_spec(genesis: Genesis) -> ChainSpec { + // Block-based hardforks + let hardfork_opts = [ + (EthereumHardfork::Homestead.boxed(), genesis.config.homestead_block), + (EthereumHardfork::Dao.boxed(), genesis.config.dao_fork_block), + (EthereumHardfork::Tangerine.boxed(), genesis.config.eip150_block), + (EthereumHardfork::SpuriousDragon.boxed(), genesis.config.eip155_block), + (EthereumHardfork::Byzantium.boxed(), genesis.config.byzantium_block), + (EthereumHardfork::Constantinople.boxed(), genesis.config.constantinople_block), + (EthereumHardfork::Petersburg.boxed(), genesis.config.petersburg_block), + (EthereumHardfork::Istanbul.boxed(), genesis.config.istanbul_block), + (EthereumHardfork::MuirGlacier.boxed(), genesis.config.muir_glacier_block), + (EthereumHardfork::Berlin.boxed(), genesis.config.berlin_block), + (EthereumHardfork::London.boxed(), genesis.config.london_block), + (EthereumHardfork::ArrowGlacier.boxed(), genesis.config.arrow_glacier_block), + (EthereumHardfork::GrayGlacier.boxed(), genesis.config.gray_glacier_block), + ]; + let mut hardforks = hardfork_opts + .into_iter() + .filter_map(|(hardfork, opt)| opt.map(|block| (hardfork, ForkCondition::Block(block)))) + .collect::>(); + + // Paris + let paris_block_and_final_difficulty = + if let Some(ttd) = genesis.config.terminal_total_difficulty { + hardforks.push(( + EthereumHardfork::Paris.boxed(), + ForkCondition::TTD { + total_difficulty: ttd, + fork_block: genesis.config.merge_netsplit_block, + }, + )); + + genesis.config.merge_netsplit_block.map(|block| (block, ttd)) + } else { + None + }; + + // Time-based hardforks + let time_hardfork_opts = [ + (EthereumHardfork::Shanghai.boxed(), genesis.config.shanghai_time), + (EthereumHardfork::Cancun.boxed(), genesis.config.cancun_time), + (EthereumHardfork::Prague.boxed(), genesis.config.prague_time), + ]; + + let mut time_hardforks = time_hardfork_opts + .into_iter() + .filter_map(|(hardfork, opt)| opt.map(|time| (hardfork, ForkCondition::Timestamp(time)))) + .collect::>(); + + hardforks.append(&mut time_hardforks); + + // Ordered Hardforks + let mainnet_hardforks: ChainHardforks = EthereumHardfork::mainnet().into(); + let mainnet_order = mainnet_hardforks.forks_iter(); + + let mut ordered_hardforks = Vec::with_capacity(hardforks.len()); + for (hardfork, _) in mainnet_order { + if let Some(pos) = hardforks.iter().position(|(e, _)| **e == *hardfork) { + ordered_hardforks.push(hardforks.remove(pos)); + } + } + + // append the remaining unknown hardforks to ensure we don't filter any out + ordered_hardforks.append(&mut hardforks); + + // NOTE: in full node, we prune all receipts except the deposit contract's. We do not + // have the deployment block in the genesis file, so we use block zero. We use the same + // deposit topic as the mainnet contract if we have the deposit contract address in the + // genesis json. + let deposit_contract = genesis.config.deposit_contract_address.map(|address| DepositContract { + address, + block: 0, + topic: MAINNET_DEPOSIT_CONTRACT.topic, + }); + + ChainSpec { + chain: genesis.config.chain_id.into(), + genesis, + genesis_hash: OnceCell::new(), + hardforks: ChainHardforks::new(ordered_hardforks), + paris_block_and_final_difficulty, + deposit_contract, + ..Default::default() + } +} + +#[cfg(feature = "optimism")] +/// Convert the given [`Genesis`] into an Optimism [`ChainSpec`]. +fn into_optimism_chain_spec(genesis: Genesis) -> ChainSpec { + use reth_ethereum_forks::OptimismHardfork; + let optimism_genesis_info = OptimismGenesisInfo::extract_from(&genesis); + let genesis_info = optimism_genesis_info.optimism_chain_info.genesis_info.unwrap_or_default(); + + // Block-based hardforks + let hardfork_opts = [ + (EthereumHardfork::Homestead.boxed(), genesis.config.homestead_block), + (EthereumHardfork::Tangerine.boxed(), genesis.config.eip150_block), + (EthereumHardfork::SpuriousDragon.boxed(), genesis.config.eip155_block), + (EthereumHardfork::Byzantium.boxed(), genesis.config.byzantium_block), + (EthereumHardfork::Constantinople.boxed(), genesis.config.constantinople_block), + (EthereumHardfork::Petersburg.boxed(), genesis.config.petersburg_block), + (EthereumHardfork::Istanbul.boxed(), genesis.config.istanbul_block), + (EthereumHardfork::MuirGlacier.boxed(), genesis.config.muir_glacier_block), + (EthereumHardfork::Berlin.boxed(), genesis.config.berlin_block), + (EthereumHardfork::London.boxed(), genesis.config.london_block), + (EthereumHardfork::ArrowGlacier.boxed(), genesis.config.arrow_glacier_block), + (EthereumHardfork::GrayGlacier.boxed(), genesis.config.gray_glacier_block), + (OptimismHardfork::Bedrock.boxed(), genesis_info.bedrock_block), + ]; + let mut block_hardforks = hardfork_opts + .into_iter() + .filter_map(|(hardfork, opt)| opt.map(|block| (hardfork, ForkCondition::Block(block)))) + .collect::>(); + + // Paris + let paris_block_and_final_difficulty = + if let Some(ttd) = genesis.config.terminal_total_difficulty { + block_hardforks.push(( + EthereumHardfork::Paris.boxed(), + ForkCondition::TTD { + total_difficulty: ttd, + fork_block: genesis.config.merge_netsplit_block, + }, + )); + + genesis.config.merge_netsplit_block.map(|block| (block, ttd)) + } else { + None + }; + + // Time-based hardforks + let time_hardfork_opts = [ + (EthereumHardfork::Shanghai.boxed(), genesis.config.shanghai_time), + (EthereumHardfork::Cancun.boxed(), genesis.config.cancun_time), + (EthereumHardfork::Prague.boxed(), genesis.config.prague_time), + (OptimismHardfork::Regolith.boxed(), genesis_info.regolith_time), + (OptimismHardfork::Canyon.boxed(), genesis_info.canyon_time), + (OptimismHardfork::Ecotone.boxed(), genesis_info.ecotone_time), + (OptimismHardfork::Fjord.boxed(), genesis_info.fjord_time), + (OptimismHardfork::Granite.boxed(), genesis_info.granite_time), + ]; + + let mut time_hardforks = time_hardfork_opts + .into_iter() + .filter_map(|(hardfork, opt)| opt.map(|time| (hardfork, ForkCondition::Timestamp(time)))) + .collect::>(); + + block_hardforks.append(&mut time_hardforks); + + // Ordered Hardforks + let mainnet_hardforks = OptimismHardfork::op_mainnet(); + let mainnet_order = mainnet_hardforks.forks_iter(); + + let mut ordered_hardforks = Vec::with_capacity(block_hardforks.len()); + for (hardfork, _) in mainnet_order { + if let Some(pos) = block_hardforks.iter().position(|(e, _)| **e == *hardfork) { + ordered_hardforks.push(block_hardforks.remove(pos)); + } + } + + // append the remaining unknown hardforks to ensure we don't filter any out + ordered_hardforks.append(&mut block_hardforks); + + // NOTE: in full node, we prune all receipts except the deposit contract's. We do not + // have the deployment block in the genesis file, so we use block zero. We use the same + // deposit topic as the mainnet contract if we have the deposit contract address in the + // genesis json. + let deposit_contract = genesis.config.deposit_contract_address.map(|address| DepositContract { + address, + block: 0, + topic: MAINNET_DEPOSIT_CONTRACT.topic, + }); + + ChainSpec { + chain: genesis.config.chain_id.into(), + genesis, + genesis_hash: OnceCell::new(), + hardforks: ChainHardforks::new(ordered_hardforks), + paris_block_and_final_difficulty, + deposit_contract, + base_fee_params: optimism_genesis_info.base_fee_params, + ..Default::default() + } +} + /// A trait for reading the current [`ChainSpec`]. #[auto_impl::auto_impl(&, Arc)] pub trait ChainSpecProvider: Send + Sync { @@ -886,7 +972,7 @@ impl ChainSpecBuilder { #[cfg(feature = "optimism")] pub fn bedrock_activated(mut self) -> Self { self = self.paris_activated(); - self.hardforks.insert(OptimismHardfork::Bedrock, ForkCondition::Block(0)); + self.hardforks.insert(crate::OptimismHardfork::Bedrock, ForkCondition::Block(0)); self } @@ -894,7 +980,7 @@ impl ChainSpecBuilder { #[cfg(feature = "optimism")] pub fn regolith_activated(mut self) -> Self { self = self.bedrock_activated(); - self.hardforks.insert(OptimismHardfork::Regolith, ForkCondition::Timestamp(0)); + self.hardforks.insert(crate::OptimismHardfork::Regolith, ForkCondition::Timestamp(0)); self } @@ -904,7 +990,7 @@ impl ChainSpecBuilder { self = self.regolith_activated(); // Canyon also activates changes from L1's Shanghai hardfork self.hardforks.insert(EthereumHardfork::Shanghai, ForkCondition::Timestamp(0)); - self.hardforks.insert(OptimismHardfork::Canyon, ForkCondition::Timestamp(0)); + self.hardforks.insert(crate::OptimismHardfork::Canyon, ForkCondition::Timestamp(0)); self } @@ -913,7 +999,7 @@ impl ChainSpecBuilder { pub fn ecotone_activated(mut self) -> Self { self = self.canyon_activated(); self.hardforks.insert(EthereumHardfork::Cancun, ForkCondition::Timestamp(0)); - self.hardforks.insert(OptimismHardfork::Ecotone, ForkCondition::Timestamp(0)); + self.hardforks.insert(crate::OptimismHardfork::Ecotone, ForkCondition::Timestamp(0)); self } @@ -921,7 +1007,7 @@ impl ChainSpecBuilder { #[cfg(feature = "optimism")] pub fn fjord_activated(mut self) -> Self { self = self.ecotone_activated(); - self.hardforks.insert(OptimismHardfork::Fjord, ForkCondition::Timestamp(0)); + self.hardforks.insert(crate::OptimismHardfork::Fjord, ForkCondition::Timestamp(0)); self } @@ -929,7 +1015,7 @@ impl ChainSpecBuilder { #[cfg(feature = "optimism")] pub fn granite_activated(mut self) -> Self { self = self.fjord_activated(); - self.hardforks.insert(OptimismHardfork::Granite, ForkCondition::Timestamp(0)); + self.hardforks.insert(crate::OptimismHardfork::Granite, ForkCondition::Timestamp(0)); self } @@ -1024,7 +1110,7 @@ impl OptimismGenesisInfo { BaseFeeParams::new(denominator as u128, elasticity as u128), ), ( - OptimismHardfork::Canyon.boxed(), + reth_ethereum_forks::OptimismHardfork::Canyon.boxed(), BaseFeeParams::new(canyon_denominator as u128, elasticity as u128), ), ] @@ -1418,7 +1504,7 @@ Post-merge hard forks (timestamp based): } #[test] - fn mainnet_forkids() { + fn mainnet_fork_ids() { test_fork_ids( &MAINNET, &[ @@ -1494,7 +1580,7 @@ Post-merge hard forks (timestamp based): } #[test] - fn holesky_forkids() { + fn holesky_fork_ids() { test_fork_ids( &HOLESKY, &[ @@ -1532,7 +1618,7 @@ Post-merge hard forks (timestamp based): } #[test] - fn sepolia_forkids() { + fn sepolia_fork_ids() { test_fork_ids( &SEPOLIA, &[ @@ -1572,7 +1658,7 @@ Post-merge hard forks (timestamp based): } #[test] - fn dev_forkids() { + fn dev_fork_ids() { test_fork_ids( &DEV, &[( @@ -2362,4 +2448,139 @@ Post-merge hard forks (timestamp based): MAINNET.latest_fork_id() ) } + + #[test] + #[cfg(not(feature = "optimism"))] + fn test_fork_order_ethereum_mainnet() { + let genesis = Genesis { + config: ChainConfig { + chain_id: 0, + homestead_block: Some(0), + dao_fork_block: Some(0), + dao_fork_support: false, + eip150_block: Some(0), + eip155_block: Some(0), + eip158_block: Some(0), + byzantium_block: Some(0), + constantinople_block: Some(0), + petersburg_block: Some(0), + istanbul_block: Some(0), + muir_glacier_block: Some(0), + berlin_block: Some(0), + london_block: Some(0), + arrow_glacier_block: Some(0), + gray_glacier_block: Some(0), + merge_netsplit_block: Some(0), + shanghai_time: Some(0), + cancun_time: Some(0), + terminal_total_difficulty: Some(U256::ZERO), + ..Default::default() + }, + ..Default::default() + }; + + let chain_spec = into_ethereum_chain_spec(genesis); + + let hardforks: Vec<_> = chain_spec.hardforks.forks_iter().map(|(h, _)| h).collect(); + let expected_hardforks = vec![ + EthereumHardfork::Homestead.boxed(), + EthereumHardfork::Dao.boxed(), + EthereumHardfork::Tangerine.boxed(), + EthereumHardfork::SpuriousDragon.boxed(), + EthereumHardfork::Byzantium.boxed(), + EthereumHardfork::Constantinople.boxed(), + EthereumHardfork::Petersburg.boxed(), + EthereumHardfork::Istanbul.boxed(), + EthereumHardfork::MuirGlacier.boxed(), + EthereumHardfork::Berlin.boxed(), + EthereumHardfork::London.boxed(), + EthereumHardfork::ArrowGlacier.boxed(), + EthereumHardfork::GrayGlacier.boxed(), + EthereumHardfork::Paris.boxed(), + EthereumHardfork::Shanghai.boxed(), + EthereumHardfork::Cancun.boxed(), + ]; + + assert!(expected_hardforks + .iter() + .zip(hardforks.iter()) + .all(|(expected, actual)| &**expected == *actual)); + assert_eq!(expected_hardforks.len(), hardforks.len()); + } + + #[test] + #[cfg(feature = "optimism")] + fn test_fork_order_optimism_mainnet() { + use crate::OptimismHardfork; + + let genesis = Genesis { + config: ChainConfig { + chain_id: 0, + homestead_block: Some(0), + dao_fork_block: Some(0), + dao_fork_support: false, + eip150_block: Some(0), + eip155_block: Some(0), + eip158_block: Some(0), + byzantium_block: Some(0), + constantinople_block: Some(0), + petersburg_block: Some(0), + istanbul_block: Some(0), + muir_glacier_block: Some(0), + berlin_block: Some(0), + london_block: Some(0), + arrow_glacier_block: Some(0), + gray_glacier_block: Some(0), + merge_netsplit_block: Some(0), + shanghai_time: Some(0), + cancun_time: Some(0), + terminal_total_difficulty: Some(U256::ZERO), + extra_fields: [ + (String::from("bedrockBlock"), 0.into()), + (String::from("regolithTime"), 0.into()), + (String::from("canyonTime"), 0.into()), + (String::from("ecotoneTime"), 0.into()), + (String::from("fjordTime"), 0.into()), + (String::from("graniteTime"), 0.into()), + ] + .into_iter() + .collect(), + ..Default::default() + }, + ..Default::default() + }; + + let chain_spec: ChainSpec = into_optimism_chain_spec(genesis); + + let hardforks: Vec<_> = chain_spec.hardforks.forks_iter().map(|(h, _)| h).collect(); + let expected_hardforks = vec![ + EthereumHardfork::Homestead.boxed(), + EthereumHardfork::Tangerine.boxed(), + EthereumHardfork::SpuriousDragon.boxed(), + EthereumHardfork::Byzantium.boxed(), + EthereumHardfork::Constantinople.boxed(), + EthereumHardfork::Petersburg.boxed(), + EthereumHardfork::Istanbul.boxed(), + EthereumHardfork::MuirGlacier.boxed(), + EthereumHardfork::Berlin.boxed(), + EthereumHardfork::London.boxed(), + EthereumHardfork::ArrowGlacier.boxed(), + EthereumHardfork::GrayGlacier.boxed(), + EthereumHardfork::Paris.boxed(), + OptimismHardfork::Bedrock.boxed(), + OptimismHardfork::Regolith.boxed(), + EthereumHardfork::Shanghai.boxed(), + OptimismHardfork::Canyon.boxed(), + EthereumHardfork::Cancun.boxed(), + OptimismHardfork::Ecotone.boxed(), + OptimismHardfork::Fjord.boxed(), + OptimismHardfork::Granite.boxed(), + ]; + + assert!(expected_hardforks + .iter() + .zip(hardforks.iter()) + .all(|(expected, actual)| &**expected == *actual)); + assert_eq!(expected_hardforks.len(), hardforks.len()); + } }