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