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