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