Skip to main content

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