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;
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        // Fork config only exists for timestamp-based hardforks.
71        let fork_id = chain_spec
72            .fork_id(&Head { timestamp, number: u64::MAX, ..Default::default() })
73            .hash
74            .0
75            .into();
76
77        EthForkConfig {
78            activation_time: timestamp,
79            blob_schedule: chain_spec
80                .blob_params_at_timestamp(timestamp)
81                // no blob support, so we set this to original cancun values as defined in eip-4844
82                .unwrap_or(BlobParams::cancun()),
83            chain_id: chain_spec.chain().id(),
84            fork_id,
85            precompiles,
86            system_contracts,
87        }
88    }
89
90    fn config(&self) -> Result<EthConfig, RethError> {
91        let chain_spec = self.provider.chain_spec();
92        let latest = self
93            .provider
94            .latest_header()?
95            .ok_or_else(|| ProviderError::BestBlockNotFound)?
96            .into_header();
97
98        let current_precompiles = evm_to_precompiles_map(
99            self.evm_config.evm_for_block(EmptyDB::default(), &latest).map_err(RethError::other)?,
100        );
101
102        let mut fork_timestamps =
103            chain_spec.forks_iter().filter_map(|(_, cond)| cond.as_timestamp()).collect::<Vec<_>>();
104        fork_timestamps.sort_unstable();
105        fork_timestamps.dedup();
106
107        let (current_fork_idx, current_fork_timestamp) = fork_timestamps
108            .iter()
109            .position(|ts| &latest.timestamp() < ts)
110            .and_then(|idx| idx.checked_sub(1))
111            .or_else(|| fork_timestamps.len().checked_sub(1))
112            .and_then(|idx| fork_timestamps.get(idx).map(|ts| (idx, *ts)))
113            .ok_or_else(|| RethError::msg("no active timestamp fork found"))?;
114
115        let current = self.build_fork_config_at(current_fork_timestamp, current_precompiles);
116
117        let mut config = EthConfig { current, next: None, last: None };
118
119        if let Some(next_fork_timestamp) = fork_timestamps.get(current_fork_idx + 1).copied() {
120            let fake_header = {
121                let mut header = latest.clone();
122                header.set_timestamp(next_fork_timestamp);
123                header
124            };
125            let next_precompiles = evm_to_precompiles_map(
126                self.evm_config
127                    .evm_for_block(EmptyDB::default(), &fake_header)
128                    .map_err(RethError::other)?,
129            );
130
131            config.next = Some(self.build_fork_config_at(next_fork_timestamp, next_precompiles));
132        } else {
133            // If there is no fork scheduled, there is no "last" or "final" fork scheduled.
134            return Ok(config);
135        }
136
137        let last_fork_timestamp = fork_timestamps.last().copied().unwrap();
138        let fake_header = {
139            let mut header = latest;
140            header.set_timestamp(last_fork_timestamp);
141            header
142        };
143        let last_precompiles = evm_to_precompiles_map(
144            self.evm_config
145                .evm_for_block(EmptyDB::default(), &fake_header)
146                .map_err(RethError::other)?,
147        );
148
149        config.last = Some(self.build_fork_config_at(last_fork_timestamp, last_precompiles));
150
151        Ok(config)
152    }
153}
154
155impl<Provider, Evm> EthConfigApiServer for EthConfigHandler<Provider, Evm>
156where
157    Provider: ChainSpecProvider<ChainSpec: Hardforks + EthereumHardforks>
158        + BlockReaderIdExt<Header: HeaderMut>
159        + 'static,
160    Evm: ConfigureEvm<Primitives: NodePrimitives<BlockHeader = Provider::Header>> + 'static,
161{
162    fn config(&self) -> RpcResult<EthConfig> {
163        Ok(self.config().map_err(EthApiError::from)?)
164    }
165}
166
167fn evm_to_precompiles_map(
168    evm: impl Evm<Precompiles = PrecompilesMap>,
169) -> BTreeMap<String, Address> {
170    let precompiles = evm.precompiles();
171    precompiles
172        .addresses()
173        .filter_map(|address| {
174            Some((precompiles.get(address)?.precompile_id().name().to_string(), *address))
175        })
176        .collect()
177}