reth_rpc/eth/
core.rs

1//! Implementation of the [`jsonrpsee`] generated [`EthApiServer`](crate::EthApi) trait
2//! Handles RPC requests for the `eth_` namespace.
3
4use std::sync::Arc;
5
6use crate::{eth::helpers::types::EthRpcConverter, EthApiBuilder};
7use alloy_consensus::BlockHeader;
8use alloy_eips::BlockNumberOrTag;
9use alloy_network::Ethereum;
10use alloy_primitives::{Bytes, U256};
11use derive_more::Deref;
12use reth_chainspec::{ChainSpec, ChainSpecProvider};
13use reth_evm::ConfigureEvm;
14use reth_evm_ethereum::EthEvmConfig;
15use reth_node_api::{FullNodeComponents, FullNodeTypes};
16use reth_rpc_convert::{RpcConvert, RpcConverter};
17use reth_rpc_eth_api::{
18    helpers::{
19        pending_block::{BasicPendingEnvBuilder, PendingEnvBuilder},
20        spec::SignersForRpc,
21        SpawnBlocking,
22    },
23    node::RpcNodeCoreExt,
24    EthApiTypes, RpcNodeCore,
25};
26use reth_rpc_eth_types::{
27    receipt::EthReceiptConverter, EthApiError, EthStateCache, FeeHistoryCache, GasCap,
28    GasPriceOracle, PendingBlock,
29};
30use reth_storage_api::{
31    noop::NoopProvider, BlockReader, BlockReaderIdExt, NodePrimitivesProvider, ProviderBlock,
32    ProviderHeader, ProviderReceipt,
33};
34use reth_tasks::{
35    pool::{BlockingTaskGuard, BlockingTaskPool},
36    TaskSpawner, TokioTaskExecutor,
37};
38use tokio::sync::{broadcast, Mutex};
39
40const DEFAULT_BROADCAST_CAPACITY: usize = 2000;
41
42/// Helper type alias for [`RpcConverter`] with components from the given [`FullNodeComponents`].
43pub type EthRpcConverterFor<N> = RpcConverter<
44    Ethereum,
45    <N as FullNodeComponents>::Evm,
46    EthReceiptConverter<<<N as FullNodeTypes>::Provider as ChainSpecProvider>::ChainSpec>,
47>;
48
49/// Helper type alias for [`EthApi`] with components from the given [`FullNodeComponents`].
50pub type EthApiFor<N> = EthApi<
51    <N as FullNodeTypes>::Provider,
52    <N as FullNodeComponents>::Pool,
53    <N as FullNodeComponents>::Network,
54    <N as FullNodeComponents>::Evm,
55    EthRpcConverterFor<N>,
56>;
57
58/// Helper type alias for [`EthApi`] with components from the given [`FullNodeComponents`].
59pub type EthApiBuilderFor<N> = EthApiBuilder<
60    <N as FullNodeTypes>::Provider,
61    <N as FullNodeComponents>::Pool,
62    <N as FullNodeComponents>::Network,
63    <N as FullNodeComponents>::Evm,
64    EthRpcConverterFor<N>,
65>;
66
67/// `Eth` API implementation.
68///
69/// This type provides the functionality for handling `eth_` related requests.
70/// These are implemented two-fold: Core functionality is implemented as
71/// [`EthApiSpec`](reth_rpc_eth_api::helpers::EthApiSpec) trait. Additionally, the required server
72/// implementations (e.g. [`EthApiServer`](reth_rpc_eth_api::EthApiServer)) are implemented
73/// separately in submodules. The rpc handler implementation can then delegate to the main impls.
74/// This way [`EthApi`] is not limited to [`jsonrpsee`] and can be used standalone or in other
75/// network handlers (for example ipc).
76///
77/// ## Trait requirements
78///
79/// While this type requires various unrestricted generic components, trait bounds are enforced when
80/// additional traits are implemented for this type.
81#[derive(Deref)]
82pub struct EthApi<Provider: BlockReader, Pool, Network, EvmConfig: ConfigureEvm, Rpc: RpcConvert> {
83    /// All nested fields bundled together.
84    #[deref]
85    pub(super) inner: Arc<EthApiInner<Provider, Pool, Network, EvmConfig, Rpc>>,
86}
87
88impl<Provider, Pool, Network, EvmConfig, Rpc> Clone
89    for EthApi<Provider, Pool, Network, EvmConfig, Rpc>
90where
91    Provider: BlockReader,
92    EvmConfig: ConfigureEvm,
93    Rpc: RpcConvert,
94{
95    fn clone(&self) -> Self {
96        Self { inner: self.inner.clone() }
97    }
98}
99
100impl EthApi<NoopProvider, (), (), EthEvmConfig, EthRpcConverter<ChainSpec>> {
101    /// Convenience fn to obtain a new [`EthApiBuilder`] instance with mandatory components.
102    ///
103    /// Creating an [`EthApi`] requires a few mandatory components:
104    ///  - provider: The type responsible for fetching requested data from disk.
105    ///  - transaction pool: To interact with the pool, submitting new transactions (e.g.
106    ///    `eth_sendRawTransactions`).
107    ///  - network: required to handle requests related to network state (e.g. `eth_syncing`).
108    ///  - evm config: Knows how create a new EVM instance to transact,estimate,call,trace.
109    ///
110    /// # Create an instance with noop ethereum implementations
111    ///
112    /// ```no_run
113    /// use alloy_network::Ethereum;
114    /// use reth_evm_ethereum::EthEvmConfig;
115    /// use reth_network_api::noop::NoopNetwork;
116    /// use reth_provider::noop::NoopProvider;
117    /// use reth_rpc::EthApi;
118    /// use reth_transaction_pool::noop::NoopTransactionPool;
119    /// let eth_api = EthApi::builder(
120    ///     NoopProvider::default(),
121    ///     NoopTransactionPool::default(),
122    ///     NoopNetwork::default(),
123    ///     EthEvmConfig::mainnet(),
124    /// )
125    /// .build();
126    /// ```
127    #[expect(clippy::type_complexity)]
128    pub fn builder<Provider, Pool, Network, EvmConfig>(
129        provider: Provider,
130        pool: Pool,
131        network: Network,
132        evm_config: EvmConfig,
133    ) -> EthApiBuilder<
134        Provider,
135        Pool,
136        Network,
137        EvmConfig,
138        RpcConverter<Ethereum, EvmConfig, EthReceiptConverter<Provider::ChainSpec>>,
139    >
140    where
141        Provider: ChainSpecProvider + BlockReaderIdExt,
142    {
143        EthApiBuilder::new(provider, pool, network, evm_config)
144    }
145}
146
147impl<Provider, Pool, Network, EvmConfig, Rpc> EthApi<Provider, Pool, Network, EvmConfig, Rpc>
148where
149    Provider: BlockReaderIdExt + ChainSpecProvider,
150    EvmConfig: ConfigureEvm,
151    Rpc: RpcConvert,
152    BasicPendingEnvBuilder: PendingEnvBuilder<EvmConfig>,
153{
154    /// Creates a new, shareable instance using the default tokio task spawner.
155    #[expect(clippy::too_many_arguments)]
156    pub fn new(
157        provider: Provider,
158        pool: Pool,
159        network: Network,
160        eth_cache: EthStateCache<Provider::Block, Provider::Receipt>,
161        gas_oracle: GasPriceOracle<Provider>,
162        gas_cap: impl Into<GasCap>,
163        max_simulate_blocks: u64,
164        eth_proof_window: u64,
165        blocking_task_pool: BlockingTaskPool,
166        fee_history_cache: FeeHistoryCache<ProviderHeader<Provider>>,
167        evm_config: EvmConfig,
168        proof_permits: usize,
169        rpc_converter: Rpc,
170    ) -> Self {
171        let inner = EthApiInner::new(
172            provider,
173            pool,
174            network,
175            eth_cache,
176            gas_oracle,
177            gas_cap,
178            max_simulate_blocks,
179            eth_proof_window,
180            blocking_task_pool,
181            fee_history_cache,
182            evm_config,
183            TokioTaskExecutor::default().boxed(),
184            proof_permits,
185            rpc_converter,
186            BasicPendingEnvBuilder::default(),
187        );
188
189        Self { inner: Arc::new(inner) }
190    }
191}
192
193impl<Provider, Pool, Network, EvmConfig, Rpc> EthApiTypes
194    for EthApi<Provider, Pool, Network, EvmConfig, Rpc>
195where
196    Self: Send + Sync,
197    Provider: BlockReader,
198    EvmConfig: ConfigureEvm,
199    Rpc: RpcConvert,
200{
201    type Error = EthApiError;
202    type NetworkTypes = Rpc::Network;
203    type RpcConvert = Rpc;
204
205    fn tx_resp_builder(&self) -> &Self::RpcConvert {
206        &self.tx_resp_builder
207    }
208}
209
210impl<Provider, Pool, Network, EvmConfig, Rpc> RpcNodeCore
211    for EthApi<Provider, Pool, Network, EvmConfig, Rpc>
212where
213    Provider: BlockReader + NodePrimitivesProvider + Clone + Unpin,
214    Pool: Send + Sync + Clone + Unpin,
215    Network: Send + Sync + Clone,
216    EvmConfig: ConfigureEvm,
217    Rpc: RpcConvert,
218{
219    type Primitives = Provider::Primitives;
220    type Provider = Provider;
221    type Pool = Pool;
222    type Evm = EvmConfig;
223    type Network = Network;
224    type PayloadBuilder = ();
225
226    fn pool(&self) -> &Self::Pool {
227        self.inner.pool()
228    }
229
230    fn evm_config(&self) -> &Self::Evm {
231        self.inner.evm_config()
232    }
233
234    fn network(&self) -> &Self::Network {
235        self.inner.network()
236    }
237
238    fn payload_builder(&self) -> &Self::PayloadBuilder {
239        &()
240    }
241
242    fn provider(&self) -> &Self::Provider {
243        self.inner.provider()
244    }
245}
246
247impl<Provider, Pool, Network, EvmConfig, Rpc> RpcNodeCoreExt
248    for EthApi<Provider, Pool, Network, EvmConfig, Rpc>
249where
250    Provider: BlockReader + NodePrimitivesProvider + Clone + Unpin,
251    Pool: Send + Sync + Clone + Unpin,
252    Network: Send + Sync + Clone,
253    EvmConfig: ConfigureEvm,
254    Rpc: RpcConvert,
255{
256    #[inline]
257    fn cache(&self) -> &EthStateCache<ProviderBlock<Provider>, ProviderReceipt<Provider>> {
258        self.inner.cache()
259    }
260}
261
262impl<Provider, Pool, Network, EvmConfig, Rpc> std::fmt::Debug
263    for EthApi<Provider, Pool, Network, EvmConfig, Rpc>
264where
265    Provider: BlockReader,
266    EvmConfig: ConfigureEvm,
267    Rpc: RpcConvert,
268{
269    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270        f.debug_struct("EthApi").finish_non_exhaustive()
271    }
272}
273
274impl<Provider, Pool, Network, EvmConfig, Rpc> SpawnBlocking
275    for EthApi<Provider, Pool, Network, EvmConfig, Rpc>
276where
277    Self: EthApiTypes<NetworkTypes = Rpc::Network> + Clone + Send + Sync + 'static,
278    Provider: BlockReader,
279    EvmConfig: ConfigureEvm,
280    Rpc: RpcConvert,
281{
282    #[inline]
283    fn io_task_spawner(&self) -> impl TaskSpawner {
284        self.inner.task_spawner()
285    }
286
287    #[inline]
288    fn tracing_task_pool(&self) -> &BlockingTaskPool {
289        self.inner.blocking_task_pool()
290    }
291
292    #[inline]
293    fn tracing_task_guard(&self) -> &BlockingTaskGuard {
294        self.inner.blocking_task_guard()
295    }
296}
297
298/// Container type `EthApi`
299#[expect(missing_debug_implementations)]
300pub struct EthApiInner<
301    Provider: BlockReader,
302    Pool,
303    Network,
304    EvmConfig: ConfigureEvm,
305    Rpc: RpcConvert,
306> {
307    /// The transaction pool.
308    pool: Pool,
309    /// The provider that can interact with the chain.
310    provider: Provider,
311    /// An interface to interact with the network
312    network: Network,
313    /// All configured Signers
314    signers: SignersForRpc<Provider, Rpc::Network>,
315    /// The async cache frontend for eth related data
316    eth_cache: EthStateCache<Provider::Block, Provider::Receipt>,
317    /// The async gas oracle frontend for gas price suggestions
318    gas_oracle: GasPriceOracle<Provider>,
319    /// Maximum gas limit for `eth_call` and call tracing RPC methods.
320    gas_cap: u64,
321    /// Maximum number of blocks for `eth_simulateV1`.
322    max_simulate_blocks: u64,
323    /// The maximum number of blocks into the past for generating state proofs.
324    eth_proof_window: u64,
325    /// The block number at which the node started
326    starting_block: U256,
327    /// The type that can spawn tasks which would otherwise block.
328    task_spawner: Box<dyn TaskSpawner>,
329    /// Cached pending block if any
330    pending_block: Mutex<Option<PendingBlock<Provider::Block, Provider::Receipt>>>,
331    /// A pool dedicated to CPU heavy blocking tasks.
332    blocking_task_pool: BlockingTaskPool,
333    /// Cache for block fees history
334    fee_history_cache: FeeHistoryCache<ProviderHeader<Provider>>,
335    /// The type that defines how to configure the EVM
336    evm_config: EvmConfig,
337
338    /// Guard for getproof calls
339    blocking_task_guard: BlockingTaskGuard,
340
341    /// Transaction broadcast channel
342    raw_tx_sender: broadcast::Sender<Bytes>,
343
344    /// Converter for RPC types.
345    tx_resp_builder: Rpc,
346
347    /// Builder for pending block environment.
348    next_env_builder: Box<dyn PendingEnvBuilder<EvmConfig>>,
349}
350
351impl<Provider, Pool, Network, EvmConfig, Rpc> EthApiInner<Provider, Pool, Network, EvmConfig, Rpc>
352where
353    Provider: BlockReaderIdExt,
354    EvmConfig: ConfigureEvm,
355    Rpc: RpcConvert,
356{
357    /// Creates a new, shareable instance using the default tokio task spawner.
358    #[expect(clippy::too_many_arguments)]
359    pub fn new(
360        provider: Provider,
361        pool: Pool,
362        network: Network,
363        eth_cache: EthStateCache<Provider::Block, Provider::Receipt>,
364        gas_oracle: GasPriceOracle<Provider>,
365        gas_cap: impl Into<GasCap>,
366        max_simulate_blocks: u64,
367        eth_proof_window: u64,
368        blocking_task_pool: BlockingTaskPool,
369        fee_history_cache: FeeHistoryCache<ProviderHeader<Provider>>,
370        evm_config: EvmConfig,
371        task_spawner: Box<dyn TaskSpawner + 'static>,
372        proof_permits: usize,
373        tx_resp_builder: Rpc,
374        next_env: impl PendingEnvBuilder<EvmConfig>,
375    ) -> Self {
376        let signers = parking_lot::RwLock::new(Default::default());
377        // get the block number of the latest block
378        let starting_block = U256::from(
379            provider
380                .header_by_number_or_tag(BlockNumberOrTag::Latest)
381                .ok()
382                .flatten()
383                .map(|header| header.number())
384                .unwrap_or_default(),
385        );
386
387        let (raw_tx_sender, _) = broadcast::channel(DEFAULT_BROADCAST_CAPACITY);
388
389        Self {
390            provider,
391            pool,
392            network,
393            signers,
394            eth_cache,
395            gas_oracle,
396            gas_cap: gas_cap.into().into(),
397            max_simulate_blocks,
398            eth_proof_window,
399            starting_block,
400            task_spawner,
401            pending_block: Default::default(),
402            blocking_task_pool,
403            fee_history_cache,
404            evm_config,
405            blocking_task_guard: BlockingTaskGuard::new(proof_permits),
406            raw_tx_sender,
407            tx_resp_builder,
408            next_env_builder: Box::new(next_env),
409        }
410    }
411}
412
413impl<Provider, Pool, Network, EvmConfig, Rpc> EthApiInner<Provider, Pool, Network, EvmConfig, Rpc>
414where
415    Provider: BlockReader,
416    EvmConfig: ConfigureEvm,
417    Rpc: RpcConvert,
418{
419    /// Returns a handle to data on disk.
420    #[inline]
421    pub const fn provider(&self) -> &Provider {
422        &self.provider
423    }
424
425    /// Returns a handle to the transaction response builder.
426    #[inline]
427    pub const fn tx_resp_builder(&self) -> &Rpc {
428        &self.tx_resp_builder
429    }
430
431    /// Returns a handle to data in memory.
432    #[inline]
433    pub const fn cache(&self) -> &EthStateCache<Provider::Block, Provider::Receipt> {
434        &self.eth_cache
435    }
436
437    /// Returns a handle to the pending block.
438    #[inline]
439    pub const fn pending_block(
440        &self,
441    ) -> &Mutex<Option<PendingBlock<Provider::Block, Provider::Receipt>>> {
442        &self.pending_block
443    }
444
445    /// Returns a type that knows how to build a [`ConfigureEvm::NextBlockEnvCtx`] for a pending
446    /// block.
447    #[inline]
448    pub const fn pending_env_builder(&self) -> &dyn PendingEnvBuilder<EvmConfig> {
449        &*self.next_env_builder
450    }
451
452    /// Returns a handle to the task spawner.
453    #[inline]
454    pub const fn task_spawner(&self) -> &dyn TaskSpawner {
455        &*self.task_spawner
456    }
457
458    /// Returns a handle to the blocking thread pool.
459    #[inline]
460    pub const fn blocking_task_pool(&self) -> &BlockingTaskPool {
461        &self.blocking_task_pool
462    }
463
464    /// Returns a handle to the EVM config.
465    #[inline]
466    pub const fn evm_config(&self) -> &EvmConfig {
467        &self.evm_config
468    }
469
470    /// Returns a handle to the transaction pool.
471    #[inline]
472    pub const fn pool(&self) -> &Pool {
473        &self.pool
474    }
475
476    /// Returns the gas cap.
477    #[inline]
478    pub const fn gas_cap(&self) -> u64 {
479        self.gas_cap
480    }
481
482    /// Returns the `max_simulate_blocks`.
483    #[inline]
484    pub const fn max_simulate_blocks(&self) -> u64 {
485        self.max_simulate_blocks
486    }
487
488    /// Returns a handle to the gas oracle.
489    #[inline]
490    pub const fn gas_oracle(&self) -> &GasPriceOracle<Provider> {
491        &self.gas_oracle
492    }
493
494    /// Returns a handle to the fee history cache.
495    #[inline]
496    pub const fn fee_history_cache(&self) -> &FeeHistoryCache<ProviderHeader<Provider>> {
497        &self.fee_history_cache
498    }
499
500    /// Returns a handle to the signers.
501    #[inline]
502    pub const fn signers(&self) -> &SignersForRpc<Provider, Rpc::Network> {
503        &self.signers
504    }
505
506    /// Returns the starting block.
507    #[inline]
508    pub const fn starting_block(&self) -> U256 {
509        self.starting_block
510    }
511
512    /// Returns the inner `Network`
513    #[inline]
514    pub const fn network(&self) -> &Network {
515        &self.network
516    }
517
518    /// The maximum number of blocks into the past for generating state proofs.
519    #[inline]
520    pub const fn eth_proof_window(&self) -> u64 {
521        self.eth_proof_window
522    }
523
524    /// Returns reference to [`BlockingTaskGuard`].
525    #[inline]
526    pub const fn blocking_task_guard(&self) -> &BlockingTaskGuard {
527        &self.blocking_task_guard
528    }
529
530    /// Returns [`broadcast::Receiver`] of new raw transactions
531    #[inline]
532    pub fn subscribe_to_raw_transactions(&self) -> broadcast::Receiver<Bytes> {
533        self.raw_tx_sender.subscribe()
534    }
535
536    /// Broadcasts raw transaction if there are active subscribers.
537    #[inline]
538    pub fn broadcast_raw_transaction(&self, raw_tx: Bytes) {
539        let _ = self.raw_tx_sender.send(raw_tx);
540    }
541}
542
543#[cfg(test)]
544mod tests {
545    use crate::{EthApi, EthApiBuilder};
546    use alloy_consensus::{Block, BlockBody, Header};
547    use alloy_eips::BlockNumberOrTag;
548    use alloy_network::Ethereum;
549    use alloy_primitives::{Signature, B256, U64};
550    use alloy_rpc_types::FeeHistory;
551    use jsonrpsee_types::error::INVALID_PARAMS_CODE;
552    use rand::Rng;
553    use reth_chain_state::CanonStateSubscriptions;
554    use reth_chainspec::{ChainSpec, ChainSpecProvider, EthChainSpec};
555    use reth_ethereum_primitives::TransactionSigned;
556    use reth_evm_ethereum::EthEvmConfig;
557    use reth_network_api::noop::NoopNetwork;
558    use reth_provider::test_utils::{MockEthProvider, NoopProvider};
559    use reth_rpc_convert::RpcConverter;
560    use reth_rpc_eth_api::EthApiServer;
561    use reth_rpc_eth_types::receipt::EthReceiptConverter;
562    use reth_storage_api::{BlockReader, BlockReaderIdExt, StateProviderFactory};
563    use reth_testing_utils::generators;
564    use reth_transaction_pool::test_utils::{testing_pool, TestPool};
565
566    type FakeEthApi<P = MockEthProvider> = EthApi<
567        P,
568        TestPool,
569        NoopNetwork,
570        EthEvmConfig,
571        RpcConverter<Ethereum, EthEvmConfig, EthReceiptConverter<ChainSpec>>,
572    >;
573
574    fn build_test_eth_api<
575        P: BlockReaderIdExt<
576                Block = reth_ethereum_primitives::Block,
577                Receipt = reth_ethereum_primitives::Receipt,
578                Header = alloy_consensus::Header,
579            > + BlockReader
580            + ChainSpecProvider<ChainSpec = ChainSpec>
581            + StateProviderFactory
582            + CanonStateSubscriptions<Primitives = reth_ethereum_primitives::EthPrimitives>
583            + Unpin
584            + Clone
585            + 'static,
586    >(
587        provider: P,
588    ) -> FakeEthApi<P> {
589        EthApiBuilder::new(
590            provider.clone(),
591            testing_pool(),
592            NoopNetwork::default(),
593            EthEvmConfig::new(provider.chain_spec()),
594        )
595        .build()
596    }
597
598    // Function to prepare the EthApi with mock data
599    fn prepare_eth_api(
600        newest_block: u64,
601        mut oldest_block: Option<B256>,
602        block_count: u64,
603        mock_provider: MockEthProvider,
604    ) -> (FakeEthApi, Vec<u128>, Vec<f64>) {
605        let mut rng = generators::rng();
606
607        // Build mock data
608        let mut gas_used_ratios = Vec::with_capacity(block_count as usize);
609        let mut base_fees_per_gas = Vec::with_capacity(block_count as usize);
610        let mut last_header = None;
611        let mut parent_hash = B256::default();
612
613        for i in (0..block_count).rev() {
614            let hash = rng.random();
615            // Note: Generates saner values to avoid invalid overflows later
616            let gas_limit = rng.random::<u32>() as u64;
617            let base_fee_per_gas: Option<u64> =
618                rng.random::<bool>().then(|| rng.random::<u32>() as u64);
619            let gas_used = rng.random::<u32>() as u64;
620
621            let header = Header {
622                number: newest_block - i,
623                gas_limit,
624                gas_used,
625                base_fee_per_gas,
626                parent_hash,
627                ..Default::default()
628            };
629            last_header = Some(header.clone());
630            parent_hash = hash;
631
632            const TOTAL_TRANSACTIONS: usize = 100;
633            let mut transactions = Vec::with_capacity(TOTAL_TRANSACTIONS);
634            for _ in 0..TOTAL_TRANSACTIONS {
635                let random_fee: u128 = rng.random();
636
637                if let Some(base_fee_per_gas) = header.base_fee_per_gas {
638                    let transaction = TransactionSigned::new_unhashed(
639                        reth_ethereum_primitives::Transaction::Eip1559(
640                            alloy_consensus::TxEip1559 {
641                                max_priority_fee_per_gas: random_fee,
642                                max_fee_per_gas: random_fee + base_fee_per_gas as u128,
643                                ..Default::default()
644                            },
645                        ),
646                        Signature::test_signature(),
647                    );
648
649                    transactions.push(transaction);
650                } else {
651                    let transaction = TransactionSigned::new_unhashed(
652                        reth_ethereum_primitives::Transaction::Legacy(Default::default()),
653                        Signature::test_signature(),
654                    );
655
656                    transactions.push(transaction);
657                }
658            }
659
660            mock_provider.add_block(
661                hash,
662                Block {
663                    header: header.clone(),
664                    body: BlockBody { transactions, ..Default::default() },
665                },
666            );
667            mock_provider.add_header(hash, header);
668
669            oldest_block.get_or_insert(hash);
670            gas_used_ratios.push(gas_used as f64 / gas_limit as f64);
671            base_fees_per_gas.push(base_fee_per_gas.map(|fee| fee as u128).unwrap_or_default());
672        }
673
674        // Add final base fee (for the next block outside of the request)
675        let last_header = last_header.unwrap();
676        let spec = mock_provider.chain_spec();
677        base_fees_per_gas.push(
678            spec.next_block_base_fee(&last_header, last_header.timestamp).unwrap_or_default()
679                as u128,
680        );
681
682        let eth_api = build_test_eth_api(mock_provider);
683
684        (eth_api, base_fees_per_gas, gas_used_ratios)
685    }
686
687    /// Invalid block range
688    #[tokio::test]
689    async fn test_fee_history_empty() {
690        let response = <EthApi<_, _, _, _, _> as EthApiServer<_, _, _, _, _>>::fee_history(
691            &build_test_eth_api(NoopProvider::default()),
692            U64::from(1),
693            BlockNumberOrTag::Latest,
694            None,
695        )
696        .await;
697        assert!(response.is_err());
698        let error_object = response.unwrap_err();
699        assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
700    }
701
702    #[tokio::test]
703    /// Invalid block range (request is before genesis)
704    async fn test_fee_history_invalid_block_range_before_genesis() {
705        let block_count = 10;
706        let newest_block = 1337;
707        let oldest_block = None;
708
709        let (eth_api, _, _) =
710            prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
711
712        let response = <EthApi<_, _, _, _, _> as EthApiServer<_, _, _, _, _>>::fee_history(
713            &eth_api,
714            U64::from(newest_block + 1),
715            newest_block.into(),
716            Some(vec![10.0]),
717        )
718        .await;
719
720        assert!(response.is_err());
721        let error_object = response.unwrap_err();
722        assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
723    }
724
725    #[tokio::test]
726    /// Invalid block range (request is in the future)
727    async fn test_fee_history_invalid_block_range_in_future() {
728        let block_count = 10;
729        let newest_block = 1337;
730        let oldest_block = None;
731
732        let (eth_api, _, _) =
733            prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
734
735        let response = <EthApi<_, _, _, _, _> as EthApiServer<_, _, _, _, _>>::fee_history(
736            &eth_api,
737            U64::from(1),
738            (newest_block + 1000).into(),
739            Some(vec![10.0]),
740        )
741        .await;
742
743        assert!(response.is_err());
744        let error_object = response.unwrap_err();
745        assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
746    }
747
748    #[tokio::test]
749    /// Requesting no block should result in a default response
750    async fn test_fee_history_no_block_requested() {
751        let block_count = 10;
752        let newest_block = 1337;
753        let oldest_block = None;
754
755        let (eth_api, _, _) =
756            prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
757
758        let response = <EthApi<_, _, _, _, _> as EthApiServer<_, _, _, _, _>>::fee_history(
759            &eth_api,
760            U64::from(0),
761            newest_block.into(),
762            None,
763        )
764        .await
765        .unwrap();
766        assert_eq!(
767            response,
768            FeeHistory::default(),
769            "none: requesting no block should yield a default response"
770        );
771    }
772
773    #[tokio::test]
774    /// Requesting a single block should return 1 block (+ base fee for the next block over)
775    async fn test_fee_history_single_block() {
776        let block_count = 10;
777        let newest_block = 1337;
778        let oldest_block = None;
779
780        let (eth_api, base_fees_per_gas, gas_used_ratios) =
781            prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
782
783        let fee_history =
784            eth_api.fee_history(U64::from(1), newest_block.into(), None).await.unwrap();
785        assert_eq!(
786            fee_history.base_fee_per_gas,
787            &base_fees_per_gas[base_fees_per_gas.len() - 2..],
788            "one: base fee per gas is incorrect"
789        );
790        assert_eq!(
791            fee_history.base_fee_per_gas.len(),
792            2,
793            "one: should return base fee of the next block as well"
794        );
795        assert_eq!(
796            &fee_history.gas_used_ratio,
797            &gas_used_ratios[gas_used_ratios.len() - 1..],
798            "one: gas used ratio is incorrect"
799        );
800        assert_eq!(fee_history.oldest_block, newest_block, "one: oldest block is incorrect");
801        assert!(
802            fee_history.reward.is_none(),
803            "one: no percentiles were requested, so there should be no rewards result"
804        );
805    }
806
807    /// Requesting all blocks should be ok
808    #[tokio::test]
809    async fn test_fee_history_all_blocks() {
810        let block_count = 10;
811        let newest_block = 1337;
812        let oldest_block = None;
813
814        let (eth_api, base_fees_per_gas, gas_used_ratios) =
815            prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
816
817        let fee_history =
818            eth_api.fee_history(U64::from(block_count), newest_block.into(), None).await.unwrap();
819
820        assert_eq!(
821            &fee_history.base_fee_per_gas, &base_fees_per_gas,
822            "all: base fee per gas is incorrect"
823        );
824        assert_eq!(
825            fee_history.base_fee_per_gas.len() as u64,
826            block_count + 1,
827            "all: should return base fee of the next block as well"
828        );
829        assert_eq!(
830            &fee_history.gas_used_ratio, &gas_used_ratios,
831            "all: gas used ratio is incorrect"
832        );
833        assert_eq!(
834            fee_history.oldest_block,
835            newest_block - block_count + 1,
836            "all: oldest block is incorrect"
837        );
838        assert!(
839            fee_history.reward.is_none(),
840            "all: no percentiles were requested, so there should be no rewards result"
841        );
842    }
843}