reth_rpc_eth_api/helpers/
config.rs

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