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