reth_rpc_eth_api/helpers/
config.rs

1//! Loads chain configuration.
2
3use alloy_consensus::{BlockHeader, 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 revm::precompile::PrecompileId;
16use std::{borrow::Borrow, collections::BTreeMap};
17
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        // Short-circuit if Cancun is not active.
93        if !chain_spec.is_cancun_active_at_timestamp(latest.timestamp()) {
94            return Err(RethError::msg("cancun has not been activated"))
95        }
96
97        let current_precompiles =
98            evm_to_precompiles_map(self.evm_config.evm_for_block(EmptyDB::default(), &latest));
99
100        let mut fork_timestamps =
101            chain_spec.forks_iter().filter_map(|(_, cond)| cond.as_timestamp()).collect::<Vec<_>>();
102        fork_timestamps.dedup();
103
104        let (current_fork_idx, current_fork_timestamp) = fork_timestamps
105            .iter()
106            .position(|ts| &latest.timestamp < ts)
107            .and_then(|idx| idx.checked_sub(1))
108            .or_else(|| fork_timestamps.len().checked_sub(1))
109            .and_then(|idx| fork_timestamps.get(idx).map(|ts| (idx, *ts)))
110            .ok_or_else(|| RethError::msg("no active timestamp fork found"))?;
111
112        let current = self
113            .build_fork_config_at(current_fork_timestamp, current_precompiles)
114            .ok_or_else(|| RethError::msg("no fork config for current fork"))?;
115
116        let mut config = EthConfig { current, next: None, last: None };
117
118        if let Some(last_fork_idx) = current_fork_idx.checked_sub(1) {
119            if let Some(last_fork_timestamp) = fork_timestamps.get(last_fork_idx).copied() {
120                let fake_header = {
121                    let mut header = latest.clone();
122                    header.timestamp = last_fork_timestamp;
123                    header
124                };
125                let last_precompiles = evm_to_precompiles_map(
126                    self.evm_config.evm_for_block(EmptyDB::default(), &fake_header),
127                );
128
129                config.last = self.build_fork_config_at(last_fork_timestamp, last_precompiles);
130            }
131        }
132
133        if let Some(next_fork_timestamp) = fork_timestamps.get(current_fork_idx + 1).copied() {
134            let fake_header = {
135                let mut header = latest;
136                header.timestamp = next_fork_timestamp;
137                header
138            };
139            let next_precompiles = evm_to_precompiles_map(
140                self.evm_config.evm_for_block(EmptyDB::default(), &fake_header),
141            );
142
143            config.next = self.build_fork_config_at(next_fork_timestamp, next_precompiles);
144        }
145
146        Ok(config)
147    }
148}
149
150impl<Provider, Evm> EthConfigApiServer for EthConfigHandler<Provider, Evm>
151where
152    Provider: ChainSpecProvider<ChainSpec: Hardforks + EthereumHardforks>
153        + BlockReaderIdExt<Header = Header>
154        + 'static,
155    Evm: ConfigureEvm<Primitives: NodePrimitives<BlockHeader = Header>> + 'static,
156{
157    fn config(&self) -> RpcResult<EthConfig> {
158        Ok(self.config().map_err(EthApiError::from)?)
159    }
160}
161
162fn evm_to_precompiles_map(
163    evm: impl Evm<Precompiles = PrecompilesMap>,
164) -> BTreeMap<String, Address> {
165    let precompiles = evm.precompiles();
166    precompiles
167        .addresses()
168        .filter_map(|address| {
169            Some((precompile_to_str(precompiles.get(address)?.precompile_id()), *address))
170        })
171        .collect()
172}
173
174// TODO: move
175fn precompile_to_str(id: &PrecompileId) -> String {
176    let str = match id {
177        PrecompileId::EcRec => "ECREC",
178        PrecompileId::Sha256 => "SHA256",
179        PrecompileId::Ripemd160 => "RIPEMD160",
180        PrecompileId::Identity => "ID",
181        PrecompileId::ModExp => "MODEXP",
182        PrecompileId::Bn254Add => "BN254_ADD",
183        PrecompileId::Bn254Mul => "BN254_MUL",
184        PrecompileId::Bn254Pairing => "BN254_PAIRING",
185        PrecompileId::Blake2F => "BLAKE2F",
186        PrecompileId::KzgPointEvaluation => "KZG_POINT_EVALUATION",
187        PrecompileId::Bls12G1Add => "BLS12_G1ADD",
188        PrecompileId::Bls12G1Msm => "BLS12_G1MSM",
189        PrecompileId::Bls12G2Add => "BLS12_G2ADD",
190        PrecompileId::Bls12G2Msm => "BLS12_G2MSM",
191        PrecompileId::Bls12Pairing => "BLS12_PAIRING_CHECK",
192        PrecompileId::Bls12MapFpToGp1 => "BLS12_MAP_FP_TO_G1",
193        PrecompileId::Bls12MapFp2ToGp2 => "BLS12_MAP_FP2_TO_G2",
194        PrecompileId::P256Verify => "P256_VERIFY",
195        PrecompileId::Custom(custom) => custom.borrow(),
196    };
197    str.to_owned()
198}