Skip to main content

reth_rpc/
chain_config.rs

1//! Builds the [`ChainConfig`] reported by `debug_chainConfig` and `admin_nodeInfo`.
2
3use alloy_genesis::ChainConfig;
4use reth_chainspec::{EthChainSpec, EthereumHardfork, EthereumHardforks, ForkCondition};
5
6/// Returns the chain config of the given spec.
7///
8/// The config starts from the genesis file, which is empty for the bundled specs and can be
9/// outdated for custom ones, and is completed with the activations and blob params of the spec's
10/// hardforks.
11pub(crate) fn chain_config<C>(spec: &C) -> ChainConfig
12where
13    C: EthChainSpec + EthereumHardforks + ?Sized,
14{
15    let mut config = ChainConfig {
16        chain_id: spec.chain().id(),
17        terminal_total_difficulty_passed: spec.final_paris_total_difficulty().is_some(),
18        terminal_total_difficulty: spec.ethereum_fork_activation(EthereumHardfork::Paris).ttd(),
19        deposit_contract_address: spec.deposit_contract().map(|dc| dc.address),
20        ..spec.genesis().config.clone()
21    };
22
23    let activation = |fork: EthereumHardfork| match spec.ethereum_fork_activation(fork) {
24        ForkCondition::Block(block) => Some(block),
25        ForkCondition::TTD { fork_block, .. } => fork_block,
26        ForkCondition::Timestamp(ts) => Some(ts),
27        ForkCondition::Never => None,
28    };
29
30    macro_rules! set_block_or_time {
31        ($( $field:ident => $fork:ident,)*) => {
32            $(
33                // the hardforks are the source of truth, but keep the genesis value if the fork
34                // is unknown to the spec
35                config.$field = activation(EthereumHardfork::$fork).or(config.$field);
36            )*
37        };
38    }
39
40    set_block_or_time!(
41        homestead_block => Homestead,
42        dao_fork_block => Dao,
43        eip150_block => Tangerine,
44        eip155_block => SpuriousDragon,
45        eip158_block => SpuriousDragon,
46        byzantium_block => Byzantium,
47        constantinople_block => Constantinople,
48        petersburg_block => Petersburg,
49        istanbul_block => Istanbul,
50        muir_glacier_block => MuirGlacier,
51        berlin_block => Berlin,
52        london_block => London,
53        arrow_glacier_block => ArrowGlacier,
54        gray_glacier_block => GrayGlacier,
55        shanghai_time => Shanghai,
56        cancun_time => Cancun,
57        prague_time => Prague,
58        osaka_time => Osaka,
59        amsterdam_time => Amsterdam,
60        bogota_time => Bogota,
61        bpo1_time => Bpo1,
62        bpo2_time => Bpo2,
63        bpo3_time => Bpo3,
64        bpo4_time => Bpo4,
65        bpo5_time => Bpo5,
66    );
67
68    if config.dao_fork_block.is_some() {
69        config.dao_fork_support = true;
70    }
71
72    let blob_forks = [EthereumHardfork::Cancun, EthereumHardfork::Prague, EthereumHardfork::Osaka]
73        .iter()
74        .chain(EthereumHardfork::bpo_variants());
75    for fork in blob_forks {
76        if let ForkCondition::Timestamp(ts) = spec.ethereum_fork_activation(*fork) &&
77            let Some(params) = spec.blob_params_at_timestamp(ts)
78        {
79            // Forks can share a timestamp, so preserve explicitly configured per-fork params.
80            config.blob_schedule.entry(fork.name().to_lowercase()).or_insert(params);
81        }
82    }
83
84    config
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use alloy_eips::eip7840::BlobParams;
91    use reth_chainspec::{ChainSpec, HOODI, MAINNET, SEPOLIA};
92
93    #[test]
94    fn mainnet_config_is_derived_from_hardforks() {
95        let config = chain_config(&*MAINNET);
96
97        assert_eq!(config.chain_id, 1);
98        assert_eq!(config.homestead_block, Some(1_150_000));
99        assert_eq!(config.dao_fork_block, Some(1_920_000));
100        assert!(config.dao_fork_support);
101        assert_eq!(config.london_block, Some(12_965_000));
102        assert_eq!(config.shanghai_time, Some(1_681_338_455));
103        assert_eq!(config.cancun_time, Some(1_710_338_135));
104        assert_eq!(config.prague_time, Some(1_746_612_311));
105        assert_eq!(
106            config.osaka_time,
107            MAINNET.ethereum_fork_activation(EthereumHardfork::Osaka).as_timestamp()
108        );
109        assert_eq!(
110            config.bpo1_time,
111            MAINNET.ethereum_fork_activation(EthereumHardfork::Bpo1).as_timestamp()
112        );
113        assert!(config.terminal_total_difficulty_passed);
114        assert!(config.terminal_total_difficulty.is_some());
115        assert_eq!(
116            config.deposit_contract_address,
117            MAINNET.deposit_contract().map(|dc| dc.address)
118        );
119        assert_eq!(
120            config.blob_schedule.get("cancun").map(|p| p.max_blob_count),
121            MAINNET.blob_params_at_timestamp(config.cancun_time.unwrap()).map(|p| p.max_blob_count)
122        );
123        assert!(config.blob_schedule.contains_key("prague"));
124        assert!(config.blob_schedule.contains_key("osaka"));
125        assert_eq!(config.blob_schedule.contains_key("bpo1"), config.bpo1_time.is_some());
126    }
127
128    #[test]
129    fn sepolia_config_reports_its_chain_id() {
130        let config = chain_config(&*SEPOLIA);
131        assert_eq!(config.chain_id, SEPOLIA.chain().id());
132        assert_eq!(config.shanghai_time, Some(1_677_557_088));
133        assert!(config.blob_schedule.contains_key("cancun"));
134    }
135
136    #[test]
137    fn hoodi_config_includes_forks_missing_from_genesis() {
138        let config = chain_config(&*HOODI);
139        let osaka = HOODI.ethereum_fork_activation(EthereumHardfork::Osaka).as_timestamp();
140        assert!(osaka.is_some());
141        assert_eq!(config.osaka_time, osaka);
142        assert!(config.blob_schedule.contains_key("osaka"));
143    }
144
145    #[test]
146    fn genesis_config_fields_are_kept() {
147        let mut genesis = MAINNET.genesis().clone();
148        genesis.config.chain_id = 1337;
149        genesis.config.extra_fields.insert("custom".to_string(), serde_json::json!(true));
150        let spec = ChainSpec::from(genesis);
151
152        let config = chain_config(&spec);
153        assert_eq!(config.chain_id, 1337);
154        assert_eq!(config.extra_fields.get("custom"), Some(&serde_json::json!(true)));
155    }
156
157    #[test]
158    fn genesis_blob_schedule_is_kept_when_forks_share_a_timestamp() {
159        let mut genesis = MAINNET.genesis().clone();
160        genesis.config.cancun_time = Some(0);
161        genesis.config.prague_time = Some(0);
162        genesis.config.blob_schedule.insert("cancun".to_string(), BlobParams::cancun());
163        genesis.config.blob_schedule.insert("prague".to_string(), BlobParams::prague());
164        let expected = genesis.config.blob_schedule.clone();
165        let spec = ChainSpec::from(genesis);
166
167        assert_eq!(chain_config(&spec).blob_schedule, expected);
168    }
169}