Skip to main content

reth_rpc_eth_api/helpers/
config.rs

1//! Loads chain configuration.
2
3use alloy_consensus::BlockHeader;
4use alloy_eips::{
5    eip7840::BlobParams,
6    eip7910::{EthConfig, EthForkConfig, SystemContract},
7};
8use alloy_evm::precompiles::Precompile;
9use alloy_primitives::{address, Address};
10use jsonrpsee::{core::RpcResult, proc_macros::rpc};
11use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks, Hardforks, Head};
12use reth_errors::{ProviderError, RethError};
13use reth_evm::{precompiles::PrecompilesMap, ConfigureEvm, Evm};
14use reth_node_api::NodePrimitives;
15use reth_primitives_traits::header::HeaderMut;
16use reth_revm::db::EmptyDB;
17use reth_rpc_eth_types::EthApiError;
18use reth_storage_api::BlockReaderIdExt;
19use std::collections::BTreeMap;
20
21/// RPC endpoint support for [EIP-7910](https://eips.ethereum.org/EIPS/eip-7910)
22#[cfg_attr(not(feature = "client"), rpc(server, namespace = "eth"))]
23#[cfg_attr(feature = "client", rpc(server, client, namespace = "eth"))]
24pub trait EthConfigApi {
25    /// Returns an object with data about recent and upcoming fork configurations.
26    #[method(name = "config")]
27    fn config(&self) -> RpcResult<EthConfig>;
28}
29
30/// Handler for the `eth_config` RPC endpoint.
31///
32/// Ref: <https://eips.ethereum.org/EIPS/eip-7910>
33#[derive(Debug, Clone)]
34pub struct EthConfigHandler<Provider, Evm> {
35    provider: Provider,
36    evm_config: Evm,
37}
38
39impl<Provider, Evm> EthConfigHandler<Provider, Evm>
40where
41    Provider: ChainSpecProvider<ChainSpec: Hardforks + EthereumHardforks>
42        + BlockReaderIdExt<Header: HeaderMut>
43        + 'static,
44    Evm: ConfigureEvm<Primitives: NodePrimitives<BlockHeader = Provider::Header>> + 'static,
45{
46    /// Creates a new [`EthConfigHandler`].
47    pub const fn new(provider: Provider, evm_config: Evm) -> Self {
48        Self { provider, evm_config }
49    }
50
51    /// Returns fork config for specific timestamp.
52    fn build_fork_config_at(
53        &self,
54        timestamp: u64,
55        precompiles: BTreeMap<String, Address>,
56    ) -> EthForkConfig {
57        let chain_spec = self.provider.chain_spec();
58
59        let mut system_contracts = BTreeMap::<SystemContract, Address>::default();
60
61        if chain_spec.is_cancun_active_at_timestamp(timestamp) {
62            system_contracts.extend(SystemContract::cancun());
63        }
64
65        if chain_spec.is_prague_active_at_timestamp(timestamp) {
66            system_contracts
67                .extend(SystemContract::prague(chain_spec.deposit_contract().map(|c| c.address)));
68        }
69
70        if chain_spec.is_amsterdam_active_at_timestamp(timestamp) {
71            system_contracts.extend(amsterdam_system_contracts());
72        }
73
74        // Fork config only exists for timestamp-based hardforks.
75        let fork_id = chain_spec
76            .fork_id(&Head { timestamp, number: u64::MAX, ..Default::default() })
77            .hash
78            .0
79            .into();
80
81        EthForkConfig {
82            activation_time: timestamp,
83            blob_schedule: chain_spec
84                .blob_params_at_timestamp(timestamp)
85                // no blob support, so we set this to original cancun values as defined in eip-4844
86                .unwrap_or_else(BlobParams::cancun),
87            chain_id: chain_spec.chain().id(),
88            fork_id,
89            precompiles,
90            system_contracts,
91        }
92    }
93
94    fn config(&self) -> Result<EthConfig, RethError> {
95        let chain_spec = self.provider.chain_spec();
96        let latest = self
97            .provider
98            .latest_header()?
99            .ok_or_else(|| ProviderError::BestBlockNotFound)?
100            .into_header();
101
102        let current_precompiles = evm_to_precompiles_map(
103            self.evm_config.evm_for_block(EmptyDB::default(), &latest).map_err(RethError::other)?,
104        );
105
106        let mut fork_timestamps =
107            chain_spec.forks_iter().filter_map(|(_, cond)| cond.as_timestamp()).collect::<Vec<_>>();
108        fork_timestamps.sort_unstable();
109        fork_timestamps.dedup();
110
111        let current_fork_idx = match fork_timestamps.iter().position(|ts| &latest.timestamp() < ts)
112        {
113            // All forks are in the past, use the last one.
114            None => fork_timestamps.len().checked_sub(1),
115            // First fork hasn't activated yet — no active timestamp fork.
116            Some(0) => None,
117            // Found a future fork; current is the one right before it.
118            Some(idx) => Some(idx - 1),
119        };
120        let (current_fork_idx, current_fork_timestamp) = current_fork_idx
121            .and_then(|idx| fork_timestamps.get(idx).map(|ts| (idx, *ts)))
122            .ok_or_else(|| RethError::msg("no active timestamp fork found"))?;
123
124        let current = self.build_fork_config_at(current_fork_timestamp, current_precompiles);
125
126        let mut config = EthConfig { current, next: None, last: None };
127
128        if let Some(next_fork_timestamp) = fork_timestamps.get(current_fork_idx + 1).copied() {
129            let fake_header = {
130                let mut header = latest.clone();
131                header.set_timestamp(next_fork_timestamp);
132                header
133            };
134            let next_precompiles = evm_to_precompiles_map(
135                self.evm_config
136                    .evm_for_block(EmptyDB::default(), &fake_header)
137                    .map_err(RethError::other)?,
138            );
139
140            config.next = Some(self.build_fork_config_at(next_fork_timestamp, next_precompiles));
141        } else {
142            // If there is no fork scheduled, there is no "last" or "final" fork scheduled.
143            return Ok(config);
144        }
145
146        let last_fork_timestamp = fork_timestamps.last().copied().unwrap();
147        let fake_header = {
148            let mut header = latest;
149            header.set_timestamp(last_fork_timestamp);
150            header
151        };
152        let last_precompiles = evm_to_precompiles_map(
153            self.evm_config
154                .evm_for_block(EmptyDB::default(), &fake_header)
155                .map_err(RethError::other)?,
156        );
157
158        config.last = Some(self.build_fork_config_at(last_fork_timestamp, last_precompiles));
159
160        Ok(config)
161    }
162}
163
164impl<Provider, Evm> EthConfigApiServer for EthConfigHandler<Provider, Evm>
165where
166    Provider: ChainSpecProvider<ChainSpec: Hardforks + EthereumHardforks>
167        + BlockReaderIdExt<Header: HeaderMut>
168        + 'static,
169    Evm: ConfigureEvm<Primitives: NodePrimitives<BlockHeader = Provider::Header>> + 'static,
170{
171    fn config(&self) -> RpcResult<EthConfig> {
172        Ok(self.config().map_err(EthApiError::from)?)
173    }
174}
175
176/// Address of the builder deposit system contract introduced in Amsterdam
177/// ([EIP-8282](https://eips.ethereum.org/EIPS/eip-8282)).
178const BUILDER_DEPOSIT_CONTRACT_ADDRESS: Address =
179    address!("0x0000BFF46984E3725691FA540A8C7589300D8282");
180
181/// Address of the builder exit system contract introduced in Amsterdam
182/// ([EIP-8282](https://eips.ethereum.org/EIPS/eip-8282)).
183const BUILDER_EXIT_CONTRACT_ADDRESS: Address =
184    address!("0x000064D678505AD48F8CCB093BC65613800E8282");
185
186/// Returns the system contracts introduced in the Amsterdam hardfork
187/// ([EIP-8282](https://eips.ethereum.org/EIPS/eip-8282) builder execution requests).
188fn amsterdam_system_contracts() -> [(SystemContract, Address); 2] {
189    [
190        (
191            SystemContract::Other("BUILDER_DEPOSIT_CONTRACT_ADDRESS".to_string()),
192            BUILDER_DEPOSIT_CONTRACT_ADDRESS,
193        ),
194        (
195            SystemContract::Other("BUILDER_EXIT_CONTRACT_ADDRESS".to_string()),
196            BUILDER_EXIT_CONTRACT_ADDRESS,
197        ),
198    ]
199}
200
201fn evm_to_precompiles_map(
202    evm: impl Evm<Precompiles = PrecompilesMap>,
203) -> BTreeMap<String, Address> {
204    let precompiles = evm.precompiles();
205    precompiles
206        .addresses()
207        .filter_map(|address| {
208            Some((precompiles.get(address)?.precompile_id().name().to_string(), *address))
209        })
210        .collect()
211}