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, EthApiSettings,
25    EthStateCache, FeeHistoryCache, 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 eth_api_settings(&self) -> &EthApiSettings {
145        self.inner.eth_api_settings()
146    }
147
148    fn converter(&self) -> &Self::RpcConvert {
149        &self.converter
150    }
151}
152
153impl<N, Rpc> RpcNodeCore for EthApi<N, Rpc>
154where
155    N: RpcNodeCore,
156    Rpc: RpcConvert,
157{
158    type Primitives = N::Primitives;
159    type Provider = N::Provider;
160    type Pool = N::Pool;
161    type Evm = N::Evm;
162    type Network = N::Network;
163
164    fn pool(&self) -> &Self::Pool {
165        self.inner.pool()
166    }
167
168    fn evm_config(&self) -> &Self::Evm {
169        self.inner.evm_config()
170    }
171
172    fn network(&self) -> &Self::Network {
173        self.inner.network()
174    }
175
176    fn provider(&self) -> &Self::Provider {
177        self.inner.provider()
178    }
179}
180
181impl<N, Rpc> RpcNodeCoreExt for EthApi<N, Rpc>
182where
183    N: RpcNodeCore,
184    Rpc: RpcConvert,
185{
186    #[inline]
187    fn cache(&self) -> &EthStateCache<N::Primitives> {
188        self.inner.cache()
189    }
190}
191
192impl<N, Rpc> std::fmt::Debug for EthApi<N, Rpc>
193where
194    N: RpcNodeCore,
195    Rpc: RpcConvert,
196{
197    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198        f.debug_struct("EthApi").finish_non_exhaustive()
199    }
200}
201
202impl<N, Rpc> SpawnBlocking for EthApi<N, Rpc>
203where
204    N: RpcNodeCore,
205    Rpc: RpcConvert<Error = EthApiError>,
206{
207    #[inline]
208    fn io_task_spawner(&self) -> &Runtime {
209        self.inner.task_spawner()
210    }
211
212    #[inline]
213    fn tracing_task_pool(&self) -> &BlockingTaskPool {
214        self.inner.blocking_task_pool()
215    }
216
217    #[inline]
218    fn tracing_task_guard(&self) -> &BlockingTaskGuard {
219        self.inner.blocking_task_guard()
220    }
221
222    #[inline]
223    fn blocking_io_task_guard(&self) -> &std::sync::Arc<tokio::sync::Semaphore> {
224        self.inner.blocking_io_request_semaphore()
225    }
226}
227
228/// Container type `EthApi`
229#[expect(missing_debug_implementations)]
230pub struct EthApiInner<N: RpcNodeCore, Rpc: RpcConvert> {
231    /// The components of the node.
232    components: N,
233    /// All configured Signers
234    signers: SignersForRpc<N::Provider, Rpc::Network>,
235    /// The async cache frontend for eth related data
236    eth_cache: EthStateCache<N::Primitives>,
237    /// The async gas oracle frontend for gas price suggestions
238    gas_oracle: GasPriceOracle<N::Provider>,
239    /// Settings shared by the `eth` RPC helpers.
240    settings: EthApiSettings,
241    /// The block number at which the node started
242    starting_block: U256,
243    /// The type that can spawn tasks which would otherwise block.
244    task_spawner: Runtime,
245    /// Cached pending block if any
246    pending_block: Mutex<Option<PendingBlock<N::Primitives>>>,
247    /// A pool dedicated to CPU heavy blocking tasks.
248    blocking_task_pool: BlockingTaskPool,
249    /// Cache for block fees history
250    fee_history_cache: FeeHistoryCache<ProviderHeader<N::Provider>>,
251
252    /// Guard for getproof calls
253    blocking_task_guard: BlockingTaskGuard,
254
255    /// Semaphore to limit concurrent blocking IO requests (`eth_call`, `eth_estimateGas`, etc.)
256    blocking_io_request_semaphore: Arc<Semaphore>,
257
258    /// Transaction broadcast channel
259    raw_tx_sender: broadcast::Sender<Bytes>,
260
261    /// Raw transaction forwarder
262    raw_tx_forwarder: Option<RpcClient>,
263
264    /// Converter for RPC types.
265    converter: Rpc,
266
267    /// Builder for pending block environment.
268    next_env_builder: Box<dyn PendingEnvBuilder<N::Evm>>,
269
270    /// Transaction batch sender for batching tx insertions
271    tx_batch_sender:
272        mpsc::UnboundedSender<BatchTxRequest<<N::Pool as TransactionPool>::Transaction>>,
273
274    /// Blob sidecar converter
275    blob_sidecar_converter: BlobSidecarConverter,
276}
277
278impl<N, Rpc> EthApiInner<N, Rpc>
279where
280    N: RpcNodeCore,
281    Rpc: RpcConvert,
282{
283    /// Creates a new, shareable instance using the default tokio task spawner.
284    #[expect(clippy::too_many_arguments)]
285    pub fn new(
286        components: N,
287        eth_cache: EthStateCache<N::Primitives>,
288        gas_oracle: GasPriceOracle<N::Provider>,
289        settings: EthApiSettings,
290        blocking_task_pool: BlockingTaskPool,
291        fee_history_cache: FeeHistoryCache<ProviderHeader<N::Provider>>,
292        task_spawner: Runtime,
293        converter: Rpc,
294        next_env: impl PendingEnvBuilder<N::Evm>,
295        raw_tx_forwarder: Option<RpcClient>,
296    ) -> Self {
297        let signers = parking_lot::RwLock::new(Default::default());
298        // get the block number of the latest block
299        let starting_block = U256::from(
300            components
301                .provider()
302                .header_by_number_or_tag(BlockNumberOrTag::Latest)
303                .ok()
304                .flatten()
305                .map(|header| header.number())
306                .unwrap_or_default(),
307        );
308
309        let (raw_tx_sender, _) = broadcast::channel(DEFAULT_BROADCAST_CAPACITY);
310
311        // Create tx pool insertion batcher
312        let (processor, tx_batch_sender) =
313            BatchTxProcessor::new(components.pool().clone(), settings.max_batch_size);
314        task_spawner.spawn_critical_task("tx-batcher", processor);
315
316        Self {
317            components,
318            signers,
319            eth_cache,
320            gas_oracle,
321            starting_block,
322            task_spawner,
323            pending_block: Default::default(),
324            blocking_task_pool,
325            fee_history_cache,
326            blocking_task_guard: BlockingTaskGuard::new(settings.proof_permits),
327            blocking_io_request_semaphore: Arc::new(Semaphore::new(
328                settings.max_blocking_io_requests,
329            )),
330            settings,
331            raw_tx_sender,
332            raw_tx_forwarder,
333            converter,
334            next_env_builder: Box::new(next_env),
335            tx_batch_sender,
336            blob_sidecar_converter: BlobSidecarConverter::new(),
337        }
338    }
339}
340
341impl<N, Rpc> EthApiInner<N, Rpc>
342where
343    N: RpcNodeCore,
344    Rpc: RpcConvert,
345{
346    /// Returns a handle to data on disk.
347    #[inline]
348    pub fn provider(&self) -> &N::Provider {
349        self.components.provider()
350    }
351
352    /// Returns a handle to the transaction response builder.
353    #[inline]
354    pub const fn converter(&self) -> &Rpc {
355        &self.converter
356    }
357
358    /// Returns a handle to data in memory.
359    #[inline]
360    pub const fn cache(&self) -> &EthStateCache<N::Primitives> {
361        &self.eth_cache
362    }
363
364    /// Returns a handle to the pending block.
365    #[inline]
366    pub const fn pending_block(&self) -> &Mutex<Option<PendingBlock<N::Primitives>>> {
367        &self.pending_block
368    }
369
370    /// Returns a type that knows how to build a [`reth_evm::ConfigureEvm::NextBlockEnvCtx`] for a
371    /// pending block.
372    #[inline]
373    pub const fn pending_env_builder(&self) -> &dyn PendingEnvBuilder<N::Evm> {
374        &*self.next_env_builder
375    }
376
377    /// Returns a handle to the task spawner.
378    #[inline]
379    pub const fn task_spawner(&self) -> &Runtime {
380        &self.task_spawner
381    }
382
383    /// Returns a handle to the blocking thread pool.
384    ///
385    /// This is intended for tasks that are CPU bound.
386    #[inline]
387    pub const fn blocking_task_pool(&self) -> &BlockingTaskPool {
388        &self.blocking_task_pool
389    }
390
391    /// Returns a handle to the EVM config.
392    #[inline]
393    pub fn evm_config(&self) -> &N::Evm {
394        self.components.evm_config()
395    }
396
397    /// Returns a handle to the transaction pool.
398    #[inline]
399    pub fn pool(&self) -> &N::Pool {
400        self.components.pool()
401    }
402
403    /// Returns the settings shared by the `eth` RPC helpers.
404    #[inline]
405    pub const fn eth_api_settings(&self) -> &EthApiSettings {
406        &self.settings
407    }
408
409    /// Returns the gas cap.
410    #[inline]
411    pub const fn gas_cap(&self) -> u64 {
412        self.settings.gas_cap
413    }
414
415    /// Returns the `max_simulate_blocks`.
416    #[inline]
417    pub const fn max_simulate_blocks(&self) -> u64 {
418        self.settings.max_simulate_blocks
419    }
420
421    /// Returns whether state roots are computed for `eth_simulateV1`.
422    #[inline]
423    pub const fn compute_state_root_for_eth_simulate(&self) -> bool {
424        self.settings.compute_state_root_for_eth_simulate
425    }
426
427    /// Returns a handle to the gas oracle.
428    #[inline]
429    pub const fn gas_oracle(&self) -> &GasPriceOracle<N::Provider> {
430        &self.gas_oracle
431    }
432
433    /// Returns a handle to the fee history cache.
434    #[inline]
435    pub const fn fee_history_cache(&self) -> &FeeHistoryCache<ProviderHeader<N::Provider>> {
436        &self.fee_history_cache
437    }
438
439    /// Returns a handle to the signers.
440    #[inline]
441    pub const fn signers(&self) -> &SignersForRpc<N::Provider, Rpc::Network> {
442        &self.signers
443    }
444
445    /// Returns the starting block.
446    #[inline]
447    pub const fn starting_block(&self) -> U256 {
448        self.starting_block
449    }
450
451    /// Returns the inner `Network`
452    #[inline]
453    pub fn network(&self) -> &N::Network {
454        self.components.network()
455    }
456
457    /// The maximum number of blocks into the past for generating state proofs.
458    #[inline]
459    pub const fn eth_proof_window(&self) -> u64 {
460        self.settings.eth_proof_window
461    }
462
463    /// Returns reference to [`BlockingTaskGuard`].
464    #[inline]
465    pub const fn blocking_task_guard(&self) -> &BlockingTaskGuard {
466        &self.blocking_task_guard
467    }
468
469    /// Returns [`broadcast::Receiver`] of new raw transactions
470    #[inline]
471    pub fn subscribe_to_raw_transactions(&self) -> broadcast::Receiver<Bytes> {
472        self.raw_tx_sender.subscribe()
473    }
474
475    /// Broadcasts raw transaction if there are active subscribers.
476    #[inline]
477    pub fn broadcast_raw_transaction(&self, raw_tx: Bytes) {
478        let _ = self.raw_tx_sender.send(raw_tx);
479    }
480
481    /// Returns the transaction batch sender
482    #[inline]
483    pub const fn tx_batch_sender(
484        &self,
485    ) -> &mpsc::UnboundedSender<BatchTxRequest<<N::Pool as TransactionPool>::Transaction>> {
486        &self.tx_batch_sender
487    }
488
489    /// Adds an _unvalidated_ transaction into the pool via the transaction batch sender.
490    #[inline]
491    pub async fn add_pool_transaction(
492        &self,
493        origin: reth_transaction_pool::TransactionOrigin,
494        transaction: <N::Pool as TransactionPool>::Transaction,
495    ) -> Result<AddedTransactionOutcome, EthApiError> {
496        let (response_tx, response_rx) = tokio::sync::oneshot::channel();
497        let request = reth_transaction_pool::BatchTxRequest::new(origin, transaction, response_tx);
498
499        self.tx_batch_sender()
500            .send(request)
501            .map_err(|_| reth_rpc_eth_types::EthApiError::BatchTxSendError)?;
502
503        Ok(response_rx.await??)
504    }
505
506    /// Returns the pending block kind
507    #[inline]
508    pub const fn pending_block_kind(&self) -> PendingBlockKind {
509        self.settings.pending_block_kind
510    }
511
512    /// Returns a handle to the raw transaction forwarder.
513    #[inline]
514    pub const fn raw_tx_forwarder(&self) -> Option<&RpcClient> {
515        self.raw_tx_forwarder.as_ref()
516    }
517
518    /// Returns the timeout duration for `send_raw_transaction_sync` RPC method.
519    #[inline]
520    pub const fn send_raw_transaction_sync_timeout(&self) -> Duration {
521        self.settings.send_raw_transaction_sync_timeout
522    }
523
524    /// Returns a handle to the blob sidecar converter.
525    #[inline]
526    pub const fn blob_sidecar_converter(&self) -> &BlobSidecarConverter {
527        &self.blob_sidecar_converter
528    }
529
530    /// Returns the EVM memory limit.
531    #[inline]
532    pub const fn evm_memory_limit(&self) -> u64 {
533        self.settings.evm_memory_limit
534    }
535
536    /// Returns a reference to the blocking IO request semaphore.
537    #[inline]
538    pub const fn blocking_io_request_semaphore(&self) -> &Arc<Semaphore> {
539        &self.blocking_io_request_semaphore
540    }
541
542    /// Returns whether to force upcasting EIP-4844 blob sidecars to EIP-7594 format.
543    #[inline]
544    pub const fn force_blob_sidecar_upcasting(&self) -> bool {
545        self.settings.force_blob_sidecar_upcasting
546    }
547}
548
549#[cfg(test)]
550mod tests {
551    use crate::{eth::helpers::types::EthRpcConverter, EthApi, EthApiBuilder};
552    use alloy_consensus::{Block, BlockBody, Header};
553    use alloy_eips::{BlockId, BlockNumberOrTag};
554    use alloy_network::TransactionBuilder;
555    use alloy_primitives::{Address, Bytes, Signature, B256, U256, U64};
556    use alloy_rpc_types::FeeHistory;
557    use alloy_rpc_types_eth::{
558        state::{AccountOverride, EvmOverrides, StateOverride},
559        Bundle, TransactionRequest,
560    };
561    use jsonrpsee_types::error::INVALID_PARAMS_CODE;
562    use rand::Rng;
563    use reth_chain_state::CanonStateSubscriptions;
564    use reth_chainspec::{ChainSpec, ChainSpecBuilder, ChainSpecProvider, EthChainSpec};
565    use reth_ethereum_primitives::TransactionSigned;
566    use reth_evm_ethereum::EthEvmConfig;
567    use reth_network_api::noop::NoopNetwork;
568    use reth_provider::{
569        test_utils::{ExtendedAccount, MockEthProvider, NoopProvider},
570        PruneCheckpointReader, StageCheckpointReader,
571    };
572    use reth_rpc_eth_api::{
573        helpers::{EthBlocks, EthCall, SpawnBlocking},
574        node::RpcNodeCoreAdapter,
575        EthApiServer,
576    };
577    use reth_rpc_eth_types::RpcInvalidTransactionError;
578    use reth_storage_api::{
579        BalProvider, BlockReaderIdExt, NodePrimitivesProvider, StateProviderFactory,
580    };
581    use reth_tasks::cancel::is_cancelled;
582    use reth_testing_utils::generators;
583    use reth_transaction_pool::test_utils::{testing_pool, TestPool};
584    use std::time::{Duration, Instant};
585
586    type FakeEthApi<P = MockEthProvider> = EthApi<
587        RpcNodeCoreAdapter<P, TestPool, NoopNetwork, EthEvmConfig>,
588        EthRpcConverter<ChainSpec>,
589    >;
590
591    fn build_test_eth_api<
592        P: BlockReaderIdExt<
593                Block = reth_ethereum_primitives::Block,
594                Receipt = reth_ethereum_primitives::Receipt,
595                Header = alloy_consensus::Header,
596                Transaction = reth_ethereum_primitives::TransactionSigned,
597            > + ChainSpecProvider<ChainSpec = ChainSpec>
598            + StateProviderFactory<Primitives = reth_ethereum_primitives::EthPrimitives>
599            + NodePrimitivesProvider<Primitives = reth_ethereum_primitives::EthPrimitives>
600            + CanonStateSubscriptions<Primitives = reth_ethereum_primitives::EthPrimitives>
601            + StageCheckpointReader
602            + PruneCheckpointReader
603            + BalProvider
604            + Unpin
605            + Clone
606            + 'static,
607    >(
608        provider: P,
609    ) -> FakeEthApi<P> {
610        EthApiBuilder::new(
611            provider.clone(),
612            testing_pool(),
613            NoopNetwork::default(),
614            EthEvmConfig::new(provider.chain_spec()),
615        )
616        .build()
617    }
618
619    fn build_test_eth_api_with_gas_cap(provider: MockEthProvider, gas_cap: u64) -> FakeEthApi {
620        EthApiBuilder::new(
621            provider.clone(),
622            testing_pool(),
623            NoopNetwork::default(),
624            EthEvmConfig::new(provider.chain_spec()),
625        )
626        .gas_cap(gas_cap.into())
627        .build()
628    }
629
630    #[tokio::test(flavor = "multi_thread")]
631    async fn blocking_task_observes_dropped_request() {
632        let api = build_test_eth_api(MockEthProvider::default());
633        let (started_tx, started_rx) = tokio::sync::oneshot::channel();
634        let (cancelled_tx, cancelled_rx) = tokio::sync::oneshot::channel();
635
636        let request = api.spawn_blocking_io(move |_| {
637            let _ = started_tx.send(());
638            let deadline = Instant::now() + Duration::from_secs(5);
639            while !is_cancelled() && Instant::now() < deadline {
640                std::thread::sleep(Duration::from_millis(1));
641            }
642            let _ = cancelled_tx.send(is_cancelled());
643            Ok(())
644        });
645        // drop the request while its blocking task is running
646        tokio::select! {
647            _ = request => panic!("blocking task completed before the request was dropped"),
648            _ = started_rx => {}
649        }
650
651        assert!(cancelled_rx.await.unwrap());
652    }
653
654    #[tokio::test]
655    async fn test_transaction_by_sender_and_nonce_without_sender_transaction_returns_none() {
656        use reth_rpc_eth_api::helpers::EthTransactions;
657
658        let provider = MockEthProvider::default();
659        let sender = Address::random();
660        provider.add_account(sender, ExtendedAccount::new(1, U256::ZERO));
661
662        let block_hash = B256::random();
663        provider.add_block(
664            block_hash,
665            Block {
666                header: Header { number: 1, ..Default::default() },
667                body: BlockBody::default(),
668            },
669        );
670
671        let api = build_test_eth_api(provider);
672        let transaction =
673            EthTransactions::get_transaction_by_sender_and_nonce(&api, sender, 0, false)
674                .await
675                .expect("existing block without a sender transaction should not be a header error");
676
677        assert!(transaction.is_none());
678    }
679
680    #[tokio::test]
681    async fn test_transaction_by_sender_and_nonce_missing_block_returns_error() {
682        use reth_rpc_eth_api::helpers::EthTransactions;
683        use reth_rpc_eth_types::EthApiError;
684
685        let provider = MockEthProvider::default();
686        let sender = Address::random();
687        provider.add_account(sender, ExtendedAccount::new(1, U256::ZERO));
688        provider.add_header(B256::random(), Header { number: 1, ..Default::default() });
689
690        let api = build_test_eth_api(provider);
691        let error = EthTransactions::get_transaction_by_sender_and_nonce(&api, sender, 0, false)
692            .await
693            .unwrap_err();
694
695        assert!(matches!(error, EthApiError::HeaderNotFound(id) if id == 1.into()));
696    }
697
698    #[tokio::test]
699    async fn test_transaction_by_sender_and_nonce_returns_matching_transaction() {
700        use reth_primitives_traits::SignerRecoverable;
701        use reth_rpc_eth_api::helpers::EthTransactions;
702
703        let provider = MockEthProvider::default();
704        let tx = TransactionSigned::new_unhashed(
705            reth_ethereum_primitives::Transaction::Legacy(Default::default()),
706            Signature::test_signature(),
707        );
708        let sender = tx.recover_signer().unwrap();
709        provider.add_account(sender, ExtendedAccount::new(1, U256::ZERO));
710        let block = Block {
711            header: Header { number: 1, ..Default::default() },
712            body: BlockBody { transactions: vec![tx], ..Default::default() },
713        };
714        let block_hash = block.header.hash_slow();
715        provider.add_block(block_hash, block);
716
717        let api = build_test_eth_api(provider);
718        let transaction =
719            EthTransactions::get_transaction_by_sender_and_nonce(&api, sender, 0, false)
720                .await
721                .unwrap()
722                .expect("matching transaction should be returned");
723
724        assert_eq!(transaction.block_hash, Some(block_hash));
725        assert_eq!(transaction.block_number, Some(1));
726        assert_eq!(transaction.transaction_index, Some(0));
727        assert_eq!(transaction.inner.signer(), sender);
728    }
729
730    #[tokio::test]
731    async fn test_eth_api_settings() {
732        use reth_rpc_eth_api::{
733            helpers::{Call, EthState, EthTransactions, LoadPendingBlock},
734            EthApiTypes,
735        };
736        use reth_rpc_eth_types::{builder::config::PendingBlockKind, EthApiSettings};
737        use std::time::Duration;
738
739        fn settings(api: &impl EthApiTypes) -> &EthApiSettings {
740            api.eth_api_settings()
741        }
742
743        fn assert_settings(actual: &EthApiSettings, expected: &EthApiSettings) {
744            assert_eq!(actual.proof_permits, expected.proof_permits);
745            assert_eq!(actual.max_batch_size, expected.max_batch_size);
746            assert_eq!(actual.max_blocking_io_requests, expected.max_blocking_io_requests);
747            assert_eq!(actual.cache_computed_bals, expected.cache_computed_bals);
748            assert_eq!(actual.gas_cap, expected.gas_cap);
749            assert_eq!(actual.max_simulate_blocks, expected.max_simulate_blocks);
750            assert_eq!(
751                actual.compute_state_root_for_eth_simulate,
752                expected.compute_state_root_for_eth_simulate
753            );
754            assert_eq!(actual.eth_proof_window, expected.eth_proof_window);
755            assert_eq!(actual.pending_block_kind, expected.pending_block_kind);
756            assert_eq!(
757                actual.send_raw_transaction_sync_timeout,
758                expected.send_raw_transaction_sync_timeout
759            );
760            assert_eq!(actual.evm_memory_limit, expected.evm_memory_limit);
761            assert_eq!(actual.force_blob_sidecar_upcasting, expected.force_blob_sidecar_upcasting);
762            assert_eq!(
763                actual.sender_recovery_cache.is_some(),
764                expected.sender_recovery_cache.is_some()
765            );
766        }
767
768        let default_api = build_test_eth_api(MockEthProvider::default());
769        assert_settings(settings(&default_api), &EthApiSettings::default());
770
771        let expected = EthApiSettings {
772            sender_recovery_cache: Some(reth_evm::SenderRecoveryCache::new(16)),
773            proof_permits: 3,
774            max_batch_size: 5,
775            max_blocking_io_requests: 7,
776            cache_computed_bals: true,
777            gas_cap: 123_456,
778            max_simulate_blocks: 7,
779            compute_state_root_for_eth_simulate: true,
780            eth_proof_window: 42,
781            pending_block_kind: PendingBlockKind::Empty,
782            send_raw_transaction_sync_timeout: Duration::from_secs(9),
783            evm_memory_limit: 1 << 20,
784            force_blob_sidecar_upcasting: true,
785        };
786        let api = EthApiBuilder::new(
787            MockEthProvider::default(),
788            testing_pool(),
789            NoopNetwork::default(),
790            EthEvmConfig::mainnet(),
791        )
792        .eth_state_cache_config(reth_rpc_eth_types::EthStateCacheConfig {
793            cache_computed_bals: true,
794            ..Default::default()
795        })
796        .sender_recovery_cache(expected.sender_recovery_cache.clone())
797        .proof_permits(expected.proof_permits)
798        .max_batch_size(expected.max_batch_size)
799        .max_blocking_io_requests(expected.max_blocking_io_requests)
800        .gas_cap(expected.gas_cap.into())
801        .max_simulate_blocks(expected.max_simulate_blocks)
802        .compute_state_root_for_eth_simulate(expected.compute_state_root_for_eth_simulate)
803        .eth_proof_window(expected.eth_proof_window)
804        .pending_block_kind(expected.pending_block_kind)
805        .send_raw_transaction_sync_timeout(expected.send_raw_transaction_sync_timeout)
806        .evm_memory_limit(expected.evm_memory_limit)
807        .force_blob_sidecar_upcasting(expected.force_blob_sidecar_upcasting)
808        .build();
809
810        assert_settings(settings(&api), &expected);
811        assert_eq!(
812            api.inner.blocking_io_request_semaphore().available_permits(),
813            expected.max_blocking_io_requests
814        );
815        assert_eq!(Call::call_gas_limit(&api), expected.gas_cap);
816        assert_eq!(Call::max_simulate_blocks(&api), expected.max_simulate_blocks);
817        assert_eq!(
818            Call::compute_state_root_for_eth_simulate(&api),
819            expected.compute_state_root_for_eth_simulate
820        );
821        assert_eq!(Call::evm_memory_limit(&api), expected.evm_memory_limit);
822        assert_eq!(EthState::max_proof_window(&api), expected.eth_proof_window);
823        assert_eq!(LoadPendingBlock::pending_block_kind(&api), expected.pending_block_kind);
824        assert_eq!(
825            EthTransactions::send_raw_transaction_sync_timeout(&api),
826            expected.send_raw_transaction_sync_timeout
827        );
828        assert_eq!(api.inner.gas_cap(), expected.gas_cap);
829        assert_eq!(api.inner.max_simulate_blocks(), expected.max_simulate_blocks);
830        assert_eq!(
831            api.inner.compute_state_root_for_eth_simulate(),
832            expected.compute_state_root_for_eth_simulate
833        );
834        assert_eq!(api.inner.eth_proof_window(), expected.eth_proof_window);
835        assert_eq!(api.inner.pending_block_kind(), expected.pending_block_kind);
836        assert_eq!(
837            api.inner.send_raw_transaction_sync_timeout(),
838            expected.send_raw_transaction_sync_timeout
839        );
840        assert_eq!(api.inner.evm_memory_limit(), expected.evm_memory_limit);
841        assert_eq!(api.inner.force_blob_sidecar_upcasting(), expected.force_blob_sidecar_upcasting);
842        assert!(std::ptr::eq(settings(&api), settings(&api.clone())));
843    }
844
845    // Function to prepare the EthApi with mock data
846    fn prepare_eth_api(
847        newest_block: u64,
848        mut oldest_block: Option<B256>,
849        block_count: u64,
850        mock_provider: MockEthProvider,
851    ) -> (FakeEthApi, Vec<u128>, Vec<f64>) {
852        let mut rng = generators::rng();
853
854        // Build mock data
855        let mut gas_used_ratios = Vec::with_capacity(block_count as usize);
856        let mut base_fees_per_gas = Vec::with_capacity(block_count as usize);
857        let mut last_header = None;
858        let mut parent_hash = B256::default();
859
860        for i in (0..block_count).rev() {
861            let hash = rng.random();
862            // Note: Generates saner values to avoid invalid overflows later
863            let gas_limit = rng.random::<u32>() as u64;
864            let base_fee_per_gas: Option<u64> =
865                rng.random::<bool>().then(|| rng.random::<u32>() as u64);
866            let gas_used = rng.random::<u32>() as u64;
867
868            let header = Header {
869                number: newest_block - i,
870                gas_limit,
871                gas_used,
872                base_fee_per_gas,
873                parent_hash,
874                ..Default::default()
875            };
876            last_header = Some(header.clone());
877            parent_hash = hash;
878
879            const TOTAL_TRANSACTIONS: usize = 100;
880            let mut transactions = Vec::with_capacity(TOTAL_TRANSACTIONS);
881            for _ in 0..TOTAL_TRANSACTIONS {
882                let random_fee: u128 = rng.random();
883
884                if let Some(base_fee_per_gas) = header.base_fee_per_gas {
885                    let transaction = TransactionSigned::new_unhashed(
886                        reth_ethereum_primitives::Transaction::Eip1559(
887                            alloy_consensus::TxEip1559 {
888                                max_priority_fee_per_gas: random_fee,
889                                max_fee_per_gas: random_fee + base_fee_per_gas as u128,
890                                ..Default::default()
891                            },
892                        ),
893                        Signature::test_signature(),
894                    );
895
896                    transactions.push(transaction);
897                } else {
898                    let transaction = TransactionSigned::new_unhashed(
899                        reth_ethereum_primitives::Transaction::Legacy(Default::default()),
900                        Signature::test_signature(),
901                    );
902
903                    transactions.push(transaction);
904                }
905            }
906
907            mock_provider.add_block(
908                hash,
909                Block {
910                    header: header.clone(),
911                    body: BlockBody { transactions, ..Default::default() },
912                },
913            );
914            mock_provider.add_header(hash, header);
915
916            oldest_block.get_or_insert(hash);
917            gas_used_ratios.push(gas_used as f64 / gas_limit as f64);
918            base_fees_per_gas.push(base_fee_per_gas.map(|fee| fee as u128).unwrap_or_default());
919        }
920
921        // Add final base fee (for the next block outside of the request)
922        let last_header = last_header.unwrap();
923        let spec = mock_provider.chain_spec();
924        base_fees_per_gas.push(
925            spec.next_block_base_fee(&last_header, last_header.timestamp).unwrap_or_default()
926                as u128,
927        );
928
929        let eth_api = build_test_eth_api(mock_provider);
930
931        (eth_api, base_fees_per_gas, gas_used_ratios)
932    }
933
934    /// Invalid block range
935    #[tokio::test]
936    async fn test_fee_history_empty() {
937        let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::fee_history(
938            &build_test_eth_api(NoopProvider::default()),
939            U64::from(1),
940            BlockNumberOrTag::Latest,
941            None,
942        )
943        .await;
944        assert!(response.is_err());
945        let error_object = response.unwrap_err();
946        assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
947    }
948
949    #[tokio::test]
950    /// Invalid block range (request is before genesis)
951    async fn test_fee_history_invalid_block_range_before_genesis() {
952        let block_count = 10;
953        let newest_block = 1337;
954        let oldest_block = None;
955
956        let (eth_api, _, _) =
957            prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
958
959        let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::fee_history(
960            &eth_api,
961            U64::from(newest_block + 1),
962            newest_block.into(),
963            Some(vec![10.0]),
964        )
965        .await;
966
967        assert!(response.is_err());
968        let error_object = response.unwrap_err();
969        assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
970    }
971
972    #[tokio::test]
973    /// Invalid block range (request is in the future)
974    async fn test_fee_history_invalid_block_range_in_future() {
975        let block_count = 10;
976        let newest_block = 1337;
977        let oldest_block = None;
978
979        let (eth_api, _, _) =
980            prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
981
982        let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::fee_history(
983            &eth_api,
984            U64::from(1),
985            (newest_block + 1000).into(),
986            Some(vec![10.0]),
987        )
988        .await;
989
990        assert!(response.is_err());
991        let error_object = response.unwrap_err();
992        assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
993    }
994
995    #[tokio::test]
996    async fn test_call_many_maps_provider_block_lookup_error_with_eth_api_conversion() {
997        let eth_api = build_test_eth_api(MockEthProvider::default());
998        let bundles = vec![Bundle {
999            transactions: vec![TransactionRequest::default()],
1000            block_override: None,
1001        }];
1002
1003        let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::call_many(
1004            &eth_api, bundles, None, None,
1005        )
1006        .await;
1007
1008        let err = response.expect_err("call_many should fail when latest block lookup errors");
1009        let message = err.message().to_ascii_lowercase();
1010        assert!(
1011            message.contains("block not found"),
1012            "best block lookup should map via EthApiError::from(ProviderError): {message}"
1013        );
1014        assert!(
1015            !message.contains("best block does not exist"),
1016            "provider implementation detail should not leak from converted error: {message}"
1017        );
1018    }
1019
1020    #[tokio::test]
1021    async fn test_call_many_keeps_header_not_found_when_block_hash_absent() {
1022        let eth_api = build_test_eth_api(NoopProvider::default());
1023        let bundles = vec![Bundle {
1024            transactions: vec![TransactionRequest::default()],
1025            block_override: None,
1026        }];
1027
1028        let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::call_many(
1029            &eth_api, bundles, None, None,
1030        )
1031        .await;
1032
1033        let err =
1034            response.expect_err("call_many should fail when latest block hash is unavailable");
1035        let message = err.message().to_ascii_lowercase();
1036        assert!(
1037            message.contains("block not found"),
1038            "missing block hash should still map to block-not-found: {message}"
1039        );
1040    }
1041
1042    #[tokio::test]
1043    /// Requesting no block should result in a default response
1044    async fn test_fee_history_no_block_requested() {
1045        let block_count = 10;
1046        let newest_block = 1337;
1047        let oldest_block = None;
1048
1049        let (eth_api, _, _) =
1050            prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
1051
1052        let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::fee_history(
1053            &eth_api,
1054            U64::from(0),
1055            newest_block.into(),
1056            None,
1057        )
1058        .await
1059        .unwrap();
1060        assert_eq!(
1061            response,
1062            FeeHistory::default(),
1063            "none: requesting no block should yield a default response"
1064        );
1065    }
1066
1067    #[tokio::test]
1068    /// Requesting a single block should return 1 block (+ base fee for the next block over)
1069    async fn test_fee_history_single_block() {
1070        let block_count = 10;
1071        let newest_block = 1337;
1072        let oldest_block = None;
1073
1074        let (eth_api, base_fees_per_gas, gas_used_ratios) =
1075            prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
1076
1077        let fee_history =
1078            eth_api.fee_history(U64::from(1), newest_block.into(), None).await.unwrap();
1079        assert_eq!(
1080            fee_history.base_fee_per_gas,
1081            &base_fees_per_gas[base_fees_per_gas.len() - 2..],
1082            "one: base fee per gas is incorrect"
1083        );
1084        assert_eq!(
1085            fee_history.base_fee_per_gas.len(),
1086            2,
1087            "one: should return base fee of the next block as well"
1088        );
1089        assert_eq!(
1090            &fee_history.gas_used_ratio,
1091            &gas_used_ratios[gas_used_ratios.len() - 1..],
1092            "one: gas used ratio is incorrect"
1093        );
1094        assert_eq!(fee_history.oldest_block, newest_block, "one: oldest block is incorrect");
1095        assert!(
1096            fee_history.reward.is_none(),
1097            "one: no percentiles were requested, so there should be no rewards result"
1098        );
1099    }
1100
1101    /// Requesting all blocks should be ok
1102    #[tokio::test]
1103    async fn test_fee_history_all_blocks() {
1104        let block_count = 10;
1105        let newest_block = 1337;
1106        let oldest_block = None;
1107
1108        let (eth_api, base_fees_per_gas, gas_used_ratios) =
1109            prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
1110
1111        let fee_history =
1112            eth_api.fee_history(U64::from(block_count), newest_block.into(), None).await.unwrap();
1113
1114        assert_eq!(
1115            &fee_history.base_fee_per_gas, &base_fees_per_gas,
1116            "all: base fee per gas is incorrect"
1117        );
1118        assert_eq!(
1119            fee_history.base_fee_per_gas.len() as u64,
1120            block_count + 1,
1121            "all: should return base fee of the next block as well"
1122        );
1123        assert_eq!(
1124            &fee_history.gas_used_ratio, &gas_used_ratios,
1125            "all: gas used ratio is incorrect"
1126        );
1127        assert_eq!(
1128            fee_history.oldest_block,
1129            newest_block - block_count + 1,
1130            "all: oldest block is incorrect"
1131        );
1132        assert!(
1133            fee_history.reward.is_none(),
1134            "all: no percentiles were requested, so there should be no rewards result"
1135        );
1136    }
1137
1138    #[tokio::test]
1139    async fn call_allowance_respects_rpc_gas_cap() {
1140        use alloy_rpc_types_trace::{parity::TraceType, tracerequest::TraceCallRequest};
1141        use reth_rpc_eth_types::EthConfig;
1142        use reth_tasks::pool::BlockingTaskGuard;
1143
1144        let sender = Address::repeat_byte(0x11);
1145        let contract = Address::repeat_byte(0xaa);
1146        // Return the gas remaining after intrinsic gas and the GAS opcode.
1147        let code = Bytes::from_static(&[0x5a, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3]);
1148
1149        // RPC cap, supplied gas, price, balance after value, block limit, expected budget.
1150        for (gas_cap, gas, price, balance, block_limit, budget) in [
1151            (50_000, None, 1, 100_000, 100_000, 50_000),
1152            (50_000, None, 0, 100_000, 100_000, 50_000),
1153            (50_000, None, 1, 40_000, 100_000, 40_000),
1154            (50_000, None, 1, 100_000, 40_000, 40_000),
1155            (50_000, Some(70_000), 1, 100_000, 100_000, 50_000),
1156            (50_000, Some(30_000), 1, 100_000, 100_000, 30_000),
1157            (50_000, Some(70_000), 1, 100_000, 40_000, 50_000),
1158            (0, None, 1, 100_000, 100_000, 100_000),
1159            (0, Some(70_000), 1, 100_000, 40_000, 70_000),
1160        ] {
1161            let provider = MockEthProvider::default()
1162                .with_chain_spec(ChainSpecBuilder::mainnet().cancun_activated().build());
1163            provider.add_block(
1164                B256::repeat_byte(0x42),
1165                Block {
1166                    header: Header {
1167                        number: 1,
1168                        gas_limit: block_limit,
1169                        excess_blob_gas: Some(0),
1170                        ..Default::default()
1171                    },
1172                    body: BlockBody::default(),
1173                },
1174            );
1175            let api = build_test_eth_api_with_gas_cap(provider, gas_cap);
1176            let trace_api =
1177                crate::TraceApi::new(api.clone(), BlockingTaskGuard::new(1), EthConfig::default());
1178            let state_override = StateOverride::from_iter([
1179                (
1180                    sender,
1181                    AccountOverride {
1182                        balance: Some(U256::from(balance + 1_000)),
1183                        ..Default::default()
1184                    },
1185                ),
1186                (contract, AccountOverride { code: Some(code.clone()), ..Default::default() }),
1187            ]);
1188
1189            for dynamic_fee in [false, true] {
1190                let request = TransactionRequest {
1191                    gas,
1192                    gas_price: (!dynamic_fee).then_some(price),
1193                    max_fee_per_gas: dynamic_fee.then_some(price),
1194                    max_priority_fee_per_gas: dynamic_fee.then_some(price),
1195                    value: Some(U256::from(1_000)),
1196                    ..TransactionRequest::default().with_from(sender).with_to(contract)
1197                };
1198                let output = EthCall::call(
1199                    &api,
1200                    request.clone(),
1201                    Some(BlockId::latest()),
1202                    EvmOverrides::state(Some(state_override.clone())),
1203                )
1204                .await
1205                .unwrap();
1206                let traces = trace_api
1207                    .trace_call(TraceCallRequest {
1208                        call: request,
1209                        trace_types: std::iter::once(TraceType::Trace).collect(),
1210                        block_id: Some(BlockId::latest()),
1211                        state_overrides: Some(state_override.clone()),
1212                        block_overrides: None,
1213                    })
1214                    .await
1215                    .unwrap();
1216
1217                assert_eq!(U256::from_be_slice(&output), U256::from(budget - 21_002));
1218                assert_eq!(traces.output, output);
1219            }
1220        }
1221    }
1222
1223    #[tokio::test]
1224    async fn estimate_gas_respects_rpc_gas_cap() {
1225        const LOW_GAS_CAP: u64 = 50_000;
1226        const HIGH_GAS_CAP: u64 = 200_000;
1227        // Revert unless GAS > 60_000. Retrying above the RPC cap would succeed and
1228        // incorrectly turn the revert into an out-of-gas error.
1229        let code = Bytes::from_static(&[
1230            0x5a, 0x61, 0xea, 0x60, 0x10, 0x60, 0x0d, 0x57, 0x60, 0x00, 0x60, 0x00, 0xfd, 0x5b,
1231            0x00,
1232        ]);
1233        let sender = Address::repeat_byte(0x11);
1234        let contract = Address::repeat_byte(0xaa);
1235        let mut state_override = StateOverride::default();
1236        state_override.insert(contract, AccountOverride { code: Some(code), ..Default::default() });
1237        let overrides = EvmOverrides::state(Some(state_override));
1238        let at = BlockId::latest();
1239
1240        for chain_spec in [
1241            ChainSpecBuilder::mainnet().cancun_activated().build(),
1242            ChainSpecBuilder::mainnet().osaka_activated().build(),
1243            ChainSpecBuilder::mainnet().amsterdam_activated().build(),
1244        ] {
1245            let provider = MockEthProvider::default().with_chain_spec(chain_spec);
1246            provider.add_account(sender, ExtendedAccount::new(0, U256::MAX));
1247            provider.add_block(
1248                B256::repeat_byte(0x42),
1249                Block {
1250                    header: Header {
1251                        number: 1,
1252                        gas_limit: 30_000_000,
1253                        excess_blob_gas: Some(0),
1254                        ..Default::default()
1255                    },
1256                    body: BlockBody::default(),
1257                },
1258            );
1259
1260            for gas in [None, Some(1_000_000)] {
1261                let request = TransactionRequest {
1262                    gas,
1263                    ..TransactionRequest::default().with_from(sender).with_to(contract)
1264                };
1265                let capped = build_test_eth_api_with_gas_cap(provider.clone(), LOW_GAS_CAP);
1266                let err = EthCall::estimate_gas_at(&capped, request.clone(), at, overrides.clone())
1267                    .await
1268                    .expect_err("estimation above the RPC gas cap must fail");
1269                assert!(
1270                    matches!(
1271                        err.as_invalid_transaction(),
1272                        Some(RpcInvalidTransactionError::Revert(_))
1273                    ),
1274                    "{err}"
1275                );
1276
1277                for gas_cap in [HIGH_GAS_CAP, 0] {
1278                    let relaxed = build_test_eth_api_with_gas_cap(provider.clone(), gas_cap);
1279                    let estimated =
1280                        EthCall::estimate_gas_at(&relaxed, request.clone(), at, overrides.clone())
1281                            .await
1282                            .expect("same call fits under a higher or unlimited RPC gas cap");
1283                    assert!(estimated > U256::from(LOW_GAS_CAP), "{estimated}");
1284                    assert!(estimated <= U256::from(HIGH_GAS_CAP), "{estimated}");
1285                }
1286            }
1287        }
1288    }
1289
1290    #[tokio::test]
1291    async fn estimate_gas_transfer_respects_rpc_gas_cap() {
1292        let provider = MockEthProvider::default();
1293        provider.add_block(
1294            B256::repeat_byte(0x42),
1295            Block {
1296                header: Header { number: 1, gas_limit: 30_000_000, ..Default::default() },
1297                body: BlockBody::default(),
1298            },
1299        );
1300        let request = TransactionRequest::default().with_to(Address::repeat_byte(0xaa));
1301        let at = BlockId::latest();
1302
1303        let capped = build_test_eth_api_with_gas_cap(provider.clone(), 20_999);
1304        let err = EthCall::estimate_gas_at(&capped, request.clone(), at, EvmOverrides::default())
1305            .await
1306            .expect_err("the transfer shortcut must respect the RPC gas cap");
1307        assert!(
1308            matches!(
1309                err.as_invalid_transaction(),
1310                Some(RpcInvalidTransactionError::GasRequiredExceedsAllowance { gas_limit: 20_999 })
1311            ),
1312            "{err}"
1313        );
1314
1315        let sufficient = build_test_eth_api_with_gas_cap(provider, 21_000);
1316        let estimated = EthCall::estimate_gas_at(&sufficient, request, at, EvmOverrides::default())
1317            .await
1318            .unwrap();
1319        assert_eq!(estimated, U256::from(21_000));
1320    }
1321
1322    #[tokio::test]
1323    async fn header_responses_omit_size_while_blocks_keep_it() {
1324        let provider = MockEthProvider::default();
1325        let block = Block {
1326            header: Header { number: 1, ..Default::default() },
1327            body: BlockBody::default(),
1328        };
1329        let hash = block.header.hash_slow();
1330        let block_size = alloy_rlp::encode(&block).len();
1331        provider.add_block(hash, block);
1332
1333        let api = build_test_eth_api(provider);
1334        for block_id in [BlockId::number(1), BlockId::hash(hash)] {
1335            let header = EthBlocks::rpc_block_header(&api, block_id).await.unwrap().unwrap();
1336            let response = serde_json::to_value(&header).unwrap();
1337            assert!(response.get("size").is_none());
1338        }
1339
1340        for full in [false, true] {
1341            let block =
1342                EthBlocks::rpc_block(&api, BlockId::number(1), full).await.unwrap().unwrap();
1343            assert_eq!(block.header.size, Some(U256::from(block_size)));
1344        }
1345    }
1346}