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, BlockReader, 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            > + BlockReader
598            + ChainSpecProvider<ChainSpec = ChainSpec>
599            + StateProviderFactory<Primitives = reth_ethereum_primitives::EthPrimitives>
600            + NodePrimitivesProvider<Primitives = reth_ethereum_primitives::EthPrimitives>
601            + CanonStateSubscriptions<Primitives = reth_ethereum_primitives::EthPrimitives>
602            + StageCheckpointReader
603            + PruneCheckpointReader
604            + BalProvider
605            + Unpin
606            + Clone
607            + 'static,
608    >(
609        provider: P,
610    ) -> FakeEthApi<P> {
611        EthApiBuilder::new(
612            provider.clone(),
613            testing_pool(),
614            NoopNetwork::default(),
615            EthEvmConfig::new(provider.chain_spec()),
616        )
617        .build()
618    }
619
620    fn build_test_eth_api_with_gas_cap(provider: MockEthProvider, gas_cap: u64) -> FakeEthApi {
621        EthApiBuilder::new(
622            provider.clone(),
623            testing_pool(),
624            NoopNetwork::default(),
625            EthEvmConfig::new(provider.chain_spec()),
626        )
627        .gas_cap(gas_cap.into())
628        .build()
629    }
630
631    #[tokio::test(flavor = "multi_thread")]
632    async fn blocking_task_observes_dropped_request() {
633        let api = build_test_eth_api(MockEthProvider::default());
634        let (started_tx, started_rx) = tokio::sync::oneshot::channel();
635        let (cancelled_tx, cancelled_rx) = tokio::sync::oneshot::channel();
636
637        let request = api.spawn_blocking_io(move |_| {
638            let _ = started_tx.send(());
639            let deadline = Instant::now() + Duration::from_secs(5);
640            while !is_cancelled() && Instant::now() < deadline {
641                std::thread::sleep(Duration::from_millis(1));
642            }
643            let _ = cancelled_tx.send(is_cancelled());
644            Ok(())
645        });
646        // drop the request while its blocking task is running
647        tokio::select! {
648            _ = request => panic!("blocking task completed before the request was dropped"),
649            _ = started_rx => {}
650        }
651
652        assert!(cancelled_rx.await.unwrap());
653    }
654
655    #[tokio::test]
656    async fn test_transaction_by_sender_and_nonce_without_sender_transaction_returns_none() {
657        use reth_rpc_eth_api::helpers::EthTransactions;
658
659        let provider = MockEthProvider::default();
660        let sender = Address::random();
661        provider.add_account(sender, ExtendedAccount::new(1, U256::ZERO));
662
663        let block_hash = B256::random();
664        provider.add_block(
665            block_hash,
666            Block {
667                header: Header { number: 1, ..Default::default() },
668                body: BlockBody::default(),
669            },
670        );
671
672        let api = build_test_eth_api(provider);
673        let transaction =
674            EthTransactions::get_transaction_by_sender_and_nonce(&api, sender, 0, false)
675                .await
676                .expect("existing block without a sender transaction should not be a header error");
677
678        assert!(transaction.is_none());
679    }
680
681    #[tokio::test]
682    async fn test_transaction_by_sender_and_nonce_missing_block_returns_error() {
683        use reth_rpc_eth_api::helpers::EthTransactions;
684        use reth_rpc_eth_types::EthApiError;
685
686        let provider = MockEthProvider::default();
687        let sender = Address::random();
688        provider.add_account(sender, ExtendedAccount::new(1, U256::ZERO));
689        provider.add_header(B256::random(), Header { number: 1, ..Default::default() });
690
691        let api = build_test_eth_api(provider);
692        let error = EthTransactions::get_transaction_by_sender_and_nonce(&api, sender, 0, false)
693            .await
694            .unwrap_err();
695
696        assert!(matches!(error, EthApiError::HeaderNotFound(id) if id == 1.into()));
697    }
698
699    #[tokio::test]
700    async fn test_transaction_by_sender_and_nonce_returns_matching_transaction() {
701        use reth_primitives_traits::SignerRecoverable;
702        use reth_rpc_eth_api::helpers::EthTransactions;
703
704        let provider = MockEthProvider::default();
705        let tx = TransactionSigned::new_unhashed(
706            reth_ethereum_primitives::Transaction::Legacy(Default::default()),
707            Signature::test_signature(),
708        );
709        let sender = tx.recover_signer().unwrap();
710        provider.add_account(sender, ExtendedAccount::new(1, U256::ZERO));
711        let block = Block {
712            header: Header { number: 1, ..Default::default() },
713            body: BlockBody { transactions: vec![tx], ..Default::default() },
714        };
715        let block_hash = block.header.hash_slow();
716        provider.add_block(block_hash, block);
717
718        let api = build_test_eth_api(provider);
719        let transaction =
720            EthTransactions::get_transaction_by_sender_and_nonce(&api, sender, 0, false)
721                .await
722                .unwrap()
723                .expect("matching transaction should be returned");
724
725        assert_eq!(transaction.block_hash, Some(block_hash));
726        assert_eq!(transaction.block_number, Some(1));
727        assert_eq!(transaction.transaction_index, Some(0));
728        assert_eq!(transaction.inner.signer(), sender);
729    }
730
731    #[tokio::test]
732    async fn test_eth_api_settings() {
733        use reth_rpc_eth_api::{
734            helpers::{Call, EthState, EthTransactions, LoadPendingBlock},
735            EthApiTypes,
736        };
737        use reth_rpc_eth_types::{builder::config::PendingBlockKind, EthApiSettings};
738        use std::time::Duration;
739
740        fn settings(api: &impl EthApiTypes) -> &EthApiSettings {
741            api.eth_api_settings()
742        }
743
744        fn assert_settings(actual: &EthApiSettings, expected: &EthApiSettings) {
745            assert_eq!(actual.proof_permits, expected.proof_permits);
746            assert_eq!(actual.max_batch_size, expected.max_batch_size);
747            assert_eq!(actual.max_blocking_io_requests, expected.max_blocking_io_requests);
748            assert_eq!(actual.cache_computed_bals, expected.cache_computed_bals);
749            assert_eq!(actual.gas_cap, expected.gas_cap);
750            assert_eq!(actual.max_simulate_blocks, expected.max_simulate_blocks);
751            assert_eq!(
752                actual.compute_state_root_for_eth_simulate,
753                expected.compute_state_root_for_eth_simulate
754            );
755            assert_eq!(actual.eth_proof_window, expected.eth_proof_window);
756            assert_eq!(actual.pending_block_kind, expected.pending_block_kind);
757            assert_eq!(
758                actual.send_raw_transaction_sync_timeout,
759                expected.send_raw_transaction_sync_timeout
760            );
761            assert_eq!(actual.evm_memory_limit, expected.evm_memory_limit);
762            assert_eq!(actual.force_blob_sidecar_upcasting, expected.force_blob_sidecar_upcasting);
763            assert_eq!(
764                actual.sender_recovery_cache.is_some(),
765                expected.sender_recovery_cache.is_some()
766            );
767        }
768
769        let default_api = build_test_eth_api(MockEthProvider::default());
770        assert_settings(settings(&default_api), &EthApiSettings::default());
771
772        let expected = EthApiSettings {
773            sender_recovery_cache: Some(reth_evm::SenderRecoveryCache::new(16)),
774            proof_permits: 3,
775            max_batch_size: 5,
776            max_blocking_io_requests: 7,
777            cache_computed_bals: true,
778            gas_cap: 123_456,
779            max_simulate_blocks: 7,
780            compute_state_root_for_eth_simulate: true,
781            eth_proof_window: 42,
782            pending_block_kind: PendingBlockKind::Empty,
783            send_raw_transaction_sync_timeout: Duration::from_secs(9),
784            evm_memory_limit: 1 << 20,
785            force_blob_sidecar_upcasting: true,
786        };
787        let api = EthApiBuilder::new(
788            MockEthProvider::default(),
789            testing_pool(),
790            NoopNetwork::default(),
791            EthEvmConfig::mainnet(),
792        )
793        .eth_state_cache_config(reth_rpc_eth_types::EthStateCacheConfig {
794            cache_computed_bals: true,
795            ..Default::default()
796        })
797        .sender_recovery_cache(expected.sender_recovery_cache.clone())
798        .proof_permits(expected.proof_permits)
799        .max_batch_size(expected.max_batch_size)
800        .max_blocking_io_requests(expected.max_blocking_io_requests)
801        .gas_cap(expected.gas_cap.into())
802        .max_simulate_blocks(expected.max_simulate_blocks)
803        .compute_state_root_for_eth_simulate(expected.compute_state_root_for_eth_simulate)
804        .eth_proof_window(expected.eth_proof_window)
805        .pending_block_kind(expected.pending_block_kind)
806        .send_raw_transaction_sync_timeout(expected.send_raw_transaction_sync_timeout)
807        .evm_memory_limit(expected.evm_memory_limit)
808        .force_blob_sidecar_upcasting(expected.force_blob_sidecar_upcasting)
809        .build();
810
811        assert_settings(settings(&api), &expected);
812        assert_eq!(
813            api.inner.blocking_io_request_semaphore().available_permits(),
814            expected.max_blocking_io_requests
815        );
816        assert_eq!(Call::call_gas_limit(&api), expected.gas_cap);
817        assert_eq!(Call::max_simulate_blocks(&api), expected.max_simulate_blocks);
818        assert_eq!(
819            Call::compute_state_root_for_eth_simulate(&api),
820            expected.compute_state_root_for_eth_simulate
821        );
822        assert_eq!(Call::evm_memory_limit(&api), expected.evm_memory_limit);
823        assert_eq!(EthState::max_proof_window(&api), expected.eth_proof_window);
824        assert_eq!(LoadPendingBlock::pending_block_kind(&api), expected.pending_block_kind);
825        assert_eq!(
826            EthTransactions::send_raw_transaction_sync_timeout(&api),
827            expected.send_raw_transaction_sync_timeout
828        );
829        assert_eq!(api.inner.gas_cap(), expected.gas_cap);
830        assert_eq!(api.inner.max_simulate_blocks(), expected.max_simulate_blocks);
831        assert_eq!(
832            api.inner.compute_state_root_for_eth_simulate(),
833            expected.compute_state_root_for_eth_simulate
834        );
835        assert_eq!(api.inner.eth_proof_window(), expected.eth_proof_window);
836        assert_eq!(api.inner.pending_block_kind(), expected.pending_block_kind);
837        assert_eq!(
838            api.inner.send_raw_transaction_sync_timeout(),
839            expected.send_raw_transaction_sync_timeout
840        );
841        assert_eq!(api.inner.evm_memory_limit(), expected.evm_memory_limit);
842        assert_eq!(api.inner.force_blob_sidecar_upcasting(), expected.force_blob_sidecar_upcasting);
843        assert!(std::ptr::eq(settings(&api), settings(&api.clone())));
844    }
845
846    // Function to prepare the EthApi with mock data
847    fn prepare_eth_api(
848        newest_block: u64,
849        mut oldest_block: Option<B256>,
850        block_count: u64,
851        mock_provider: MockEthProvider,
852    ) -> (FakeEthApi, Vec<u128>, Vec<f64>) {
853        let mut rng = generators::rng();
854
855        // Build mock data
856        let mut gas_used_ratios = Vec::with_capacity(block_count as usize);
857        let mut base_fees_per_gas = Vec::with_capacity(block_count as usize);
858        let mut last_header = None;
859        let mut parent_hash = B256::default();
860
861        for i in (0..block_count).rev() {
862            let hash = rng.random();
863            // Note: Generates saner values to avoid invalid overflows later
864            let gas_limit = rng.random::<u32>() as u64;
865            let base_fee_per_gas: Option<u64> =
866                rng.random::<bool>().then(|| rng.random::<u32>() as u64);
867            let gas_used = rng.random::<u32>() as u64;
868
869            let header = Header {
870                number: newest_block - i,
871                gas_limit,
872                gas_used,
873                base_fee_per_gas,
874                parent_hash,
875                ..Default::default()
876            };
877            last_header = Some(header.clone());
878            parent_hash = hash;
879
880            const TOTAL_TRANSACTIONS: usize = 100;
881            let mut transactions = Vec::with_capacity(TOTAL_TRANSACTIONS);
882            for _ in 0..TOTAL_TRANSACTIONS {
883                let random_fee: u128 = rng.random();
884
885                if let Some(base_fee_per_gas) = header.base_fee_per_gas {
886                    let transaction = TransactionSigned::new_unhashed(
887                        reth_ethereum_primitives::Transaction::Eip1559(
888                            alloy_consensus::TxEip1559 {
889                                max_priority_fee_per_gas: random_fee,
890                                max_fee_per_gas: random_fee + base_fee_per_gas as u128,
891                                ..Default::default()
892                            },
893                        ),
894                        Signature::test_signature(),
895                    );
896
897                    transactions.push(transaction);
898                } else {
899                    let transaction = TransactionSigned::new_unhashed(
900                        reth_ethereum_primitives::Transaction::Legacy(Default::default()),
901                        Signature::test_signature(),
902                    );
903
904                    transactions.push(transaction);
905                }
906            }
907
908            mock_provider.add_block(
909                hash,
910                Block {
911                    header: header.clone(),
912                    body: BlockBody { transactions, ..Default::default() },
913                },
914            );
915            mock_provider.add_header(hash, header);
916
917            oldest_block.get_or_insert(hash);
918            gas_used_ratios.push(gas_used as f64 / gas_limit as f64);
919            base_fees_per_gas.push(base_fee_per_gas.map(|fee| fee as u128).unwrap_or_default());
920        }
921
922        // Add final base fee (for the next block outside of the request)
923        let last_header = last_header.unwrap();
924        let spec = mock_provider.chain_spec();
925        base_fees_per_gas.push(
926            spec.next_block_base_fee(&last_header, last_header.timestamp).unwrap_or_default()
927                as u128,
928        );
929
930        let eth_api = build_test_eth_api(mock_provider);
931
932        (eth_api, base_fees_per_gas, gas_used_ratios)
933    }
934
935    /// Invalid block range
936    #[tokio::test]
937    async fn test_fee_history_empty() {
938        let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::fee_history(
939            &build_test_eth_api(NoopProvider::default()),
940            U64::from(1),
941            BlockNumberOrTag::Latest,
942            None,
943        )
944        .await;
945        assert!(response.is_err());
946        let error_object = response.unwrap_err();
947        assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
948    }
949
950    #[tokio::test]
951    /// Invalid block range (request is before genesis)
952    async fn test_fee_history_invalid_block_range_before_genesis() {
953        let block_count = 10;
954        let newest_block = 1337;
955        let oldest_block = None;
956
957        let (eth_api, _, _) =
958            prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
959
960        let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::fee_history(
961            &eth_api,
962            U64::from(newest_block + 1),
963            newest_block.into(),
964            Some(vec![10.0]),
965        )
966        .await;
967
968        assert!(response.is_err());
969        let error_object = response.unwrap_err();
970        assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
971    }
972
973    #[tokio::test]
974    /// Invalid block range (request is in the future)
975    async fn test_fee_history_invalid_block_range_in_future() {
976        let block_count = 10;
977        let newest_block = 1337;
978        let oldest_block = None;
979
980        let (eth_api, _, _) =
981            prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
982
983        let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::fee_history(
984            &eth_api,
985            U64::from(1),
986            (newest_block + 1000).into(),
987            Some(vec![10.0]),
988        )
989        .await;
990
991        assert!(response.is_err());
992        let error_object = response.unwrap_err();
993        assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
994    }
995
996    #[tokio::test]
997    async fn test_call_many_maps_provider_block_lookup_error_with_eth_api_conversion() {
998        let eth_api = build_test_eth_api(MockEthProvider::default());
999        let bundles = vec![Bundle {
1000            transactions: vec![TransactionRequest::default()],
1001            block_override: None,
1002        }];
1003
1004        let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::call_many(
1005            &eth_api, bundles, None, None,
1006        )
1007        .await;
1008
1009        let err = response.expect_err("call_many should fail when latest block lookup errors");
1010        let message = err.message().to_ascii_lowercase();
1011        assert!(
1012            message.contains("block not found"),
1013            "best block lookup should map via EthApiError::from(ProviderError): {message}"
1014        );
1015        assert!(
1016            !message.contains("best block does not exist"),
1017            "provider implementation detail should not leak from converted error: {message}"
1018        );
1019    }
1020
1021    #[tokio::test]
1022    async fn test_call_many_keeps_header_not_found_when_block_hash_absent() {
1023        let eth_api = build_test_eth_api(NoopProvider::default());
1024        let bundles = vec![Bundle {
1025            transactions: vec![TransactionRequest::default()],
1026            block_override: None,
1027        }];
1028
1029        let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::call_many(
1030            &eth_api, bundles, None, None,
1031        )
1032        .await;
1033
1034        let err =
1035            response.expect_err("call_many should fail when latest block hash is unavailable");
1036        let message = err.message().to_ascii_lowercase();
1037        assert!(
1038            message.contains("block not found"),
1039            "missing block hash should still map to block-not-found: {message}"
1040        );
1041    }
1042
1043    #[tokio::test]
1044    /// Requesting no block should result in a default response
1045    async fn test_fee_history_no_block_requested() {
1046        let block_count = 10;
1047        let newest_block = 1337;
1048        let oldest_block = None;
1049
1050        let (eth_api, _, _) =
1051            prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
1052
1053        let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::fee_history(
1054            &eth_api,
1055            U64::from(0),
1056            newest_block.into(),
1057            None,
1058        )
1059        .await
1060        .unwrap();
1061        assert_eq!(
1062            response,
1063            FeeHistory::default(),
1064            "none: requesting no block should yield a default response"
1065        );
1066    }
1067
1068    #[tokio::test]
1069    /// Requesting a single block should return 1 block (+ base fee for the next block over)
1070    async fn test_fee_history_single_block() {
1071        let block_count = 10;
1072        let newest_block = 1337;
1073        let oldest_block = None;
1074
1075        let (eth_api, base_fees_per_gas, gas_used_ratios) =
1076            prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
1077
1078        let fee_history =
1079            eth_api.fee_history(U64::from(1), newest_block.into(), None).await.unwrap();
1080        assert_eq!(
1081            fee_history.base_fee_per_gas,
1082            &base_fees_per_gas[base_fees_per_gas.len() - 2..],
1083            "one: base fee per gas is incorrect"
1084        );
1085        assert_eq!(
1086            fee_history.base_fee_per_gas.len(),
1087            2,
1088            "one: should return base fee of the next block as well"
1089        );
1090        assert_eq!(
1091            &fee_history.gas_used_ratio,
1092            &gas_used_ratios[gas_used_ratios.len() - 1..],
1093            "one: gas used ratio is incorrect"
1094        );
1095        assert_eq!(fee_history.oldest_block, newest_block, "one: oldest block is incorrect");
1096        assert!(
1097            fee_history.reward.is_none(),
1098            "one: no percentiles were requested, so there should be no rewards result"
1099        );
1100    }
1101
1102    /// Requesting all blocks should be ok
1103    #[tokio::test]
1104    async fn test_fee_history_all_blocks() {
1105        let block_count = 10;
1106        let newest_block = 1337;
1107        let oldest_block = None;
1108
1109        let (eth_api, base_fees_per_gas, gas_used_ratios) =
1110            prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
1111
1112        let fee_history =
1113            eth_api.fee_history(U64::from(block_count), newest_block.into(), None).await.unwrap();
1114
1115        assert_eq!(
1116            &fee_history.base_fee_per_gas, &base_fees_per_gas,
1117            "all: base fee per gas is incorrect"
1118        );
1119        assert_eq!(
1120            fee_history.base_fee_per_gas.len() as u64,
1121            block_count + 1,
1122            "all: should return base fee of the next block as well"
1123        );
1124        assert_eq!(
1125            &fee_history.gas_used_ratio, &gas_used_ratios,
1126            "all: gas used ratio is incorrect"
1127        );
1128        assert_eq!(
1129            fee_history.oldest_block,
1130            newest_block - block_count + 1,
1131            "all: oldest block is incorrect"
1132        );
1133        assert!(
1134            fee_history.reward.is_none(),
1135            "all: no percentiles were requested, so there should be no rewards result"
1136        );
1137    }
1138
1139    #[tokio::test]
1140    async fn call_allowance_respects_rpc_gas_cap() {
1141        use alloy_rpc_types_trace::{parity::TraceType, tracerequest::TraceCallRequest};
1142        use reth_rpc_eth_types::EthConfig;
1143        use reth_tasks::pool::BlockingTaskGuard;
1144
1145        let sender = Address::repeat_byte(0x11);
1146        let contract = Address::repeat_byte(0xaa);
1147        // Return the gas remaining after intrinsic gas and the GAS opcode.
1148        let code = Bytes::from_static(&[0x5a, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3]);
1149
1150        // RPC cap, supplied gas, price, balance after value, block limit, expected budget.
1151        for (gas_cap, gas, price, balance, block_limit, budget) in [
1152            (50_000, None, 1, 100_000, 100_000, 50_000),
1153            (50_000, None, 0, 100_000, 100_000, 50_000),
1154            (50_000, None, 1, 40_000, 100_000, 40_000),
1155            (50_000, None, 1, 100_000, 40_000, 40_000),
1156            (50_000, Some(70_000), 1, 100_000, 100_000, 50_000),
1157            (50_000, Some(30_000), 1, 100_000, 100_000, 30_000),
1158            (50_000, Some(70_000), 1, 100_000, 40_000, 50_000),
1159            (0, None, 1, 100_000, 100_000, 100_000),
1160            (0, Some(70_000), 1, 100_000, 40_000, 70_000),
1161        ] {
1162            let provider = MockEthProvider::default()
1163                .with_chain_spec(ChainSpecBuilder::mainnet().cancun_activated().build());
1164            provider.add_block(
1165                B256::repeat_byte(0x42),
1166                Block {
1167                    header: Header {
1168                        number: 1,
1169                        gas_limit: block_limit,
1170                        excess_blob_gas: Some(0),
1171                        ..Default::default()
1172                    },
1173                    body: BlockBody::default(),
1174                },
1175            );
1176            let api = build_test_eth_api_with_gas_cap(provider, gas_cap);
1177            let trace_api =
1178                crate::TraceApi::new(api.clone(), BlockingTaskGuard::new(1), EthConfig::default());
1179            let state_override = StateOverride::from_iter([
1180                (
1181                    sender,
1182                    AccountOverride {
1183                        balance: Some(U256::from(balance + 1_000)),
1184                        ..Default::default()
1185                    },
1186                ),
1187                (contract, AccountOverride { code: Some(code.clone()), ..Default::default() }),
1188            ]);
1189
1190            for dynamic_fee in [false, true] {
1191                let request = TransactionRequest {
1192                    gas,
1193                    gas_price: (!dynamic_fee).then_some(price),
1194                    max_fee_per_gas: dynamic_fee.then_some(price),
1195                    max_priority_fee_per_gas: dynamic_fee.then_some(price),
1196                    value: Some(U256::from(1_000)),
1197                    ..TransactionRequest::default().with_from(sender).with_to(contract)
1198                };
1199                let output = EthCall::call(
1200                    &api,
1201                    request.clone(),
1202                    Some(BlockId::latest()),
1203                    EvmOverrides::state(Some(state_override.clone())),
1204                )
1205                .await
1206                .unwrap();
1207                let traces = trace_api
1208                    .trace_call(TraceCallRequest {
1209                        call: request,
1210                        trace_types: std::iter::once(TraceType::Trace).collect(),
1211                        block_id: Some(BlockId::latest()),
1212                        state_overrides: Some(state_override.clone()),
1213                        block_overrides: None,
1214                    })
1215                    .await
1216                    .unwrap();
1217
1218                assert_eq!(U256::from_be_slice(&output), U256::from(budget - 21_002));
1219                assert_eq!(traces.output, output);
1220            }
1221        }
1222    }
1223
1224    #[tokio::test]
1225    async fn estimate_gas_respects_rpc_gas_cap() {
1226        const LOW_GAS_CAP: u64 = 50_000;
1227        const HIGH_GAS_CAP: u64 = 200_000;
1228        // Revert unless GAS > 60_000. Retrying above the RPC cap would succeed and
1229        // incorrectly turn the revert into an out-of-gas error.
1230        let code = Bytes::from_static(&[
1231            0x5a, 0x61, 0xea, 0x60, 0x10, 0x60, 0x0d, 0x57, 0x60, 0x00, 0x60, 0x00, 0xfd, 0x5b,
1232            0x00,
1233        ]);
1234        let sender = Address::repeat_byte(0x11);
1235        let contract = Address::repeat_byte(0xaa);
1236        let mut state_override = StateOverride::default();
1237        state_override.insert(contract, AccountOverride { code: Some(code), ..Default::default() });
1238        let overrides = EvmOverrides::state(Some(state_override));
1239        let at = BlockId::latest();
1240
1241        for chain_spec in [
1242            ChainSpecBuilder::mainnet().cancun_activated().build(),
1243            ChainSpecBuilder::mainnet().osaka_activated().build(),
1244            ChainSpecBuilder::mainnet().amsterdam_activated().build(),
1245        ] {
1246            let provider = MockEthProvider::default().with_chain_spec(chain_spec);
1247            provider.add_account(sender, ExtendedAccount::new(0, U256::MAX));
1248            provider.add_block(
1249                B256::repeat_byte(0x42),
1250                Block {
1251                    header: Header {
1252                        number: 1,
1253                        gas_limit: 30_000_000,
1254                        excess_blob_gas: Some(0),
1255                        ..Default::default()
1256                    },
1257                    body: BlockBody::default(),
1258                },
1259            );
1260
1261            for gas in [None, Some(1_000_000)] {
1262                let request = TransactionRequest {
1263                    gas,
1264                    ..TransactionRequest::default().with_from(sender).with_to(contract)
1265                };
1266                let capped = build_test_eth_api_with_gas_cap(provider.clone(), LOW_GAS_CAP);
1267                let err = EthCall::estimate_gas_at(&capped, request.clone(), at, overrides.clone())
1268                    .await
1269                    .expect_err("estimation above the RPC gas cap must fail");
1270                assert!(
1271                    matches!(
1272                        err.as_invalid_transaction(),
1273                        Some(RpcInvalidTransactionError::Revert(_))
1274                    ),
1275                    "{err}"
1276                );
1277
1278                for gas_cap in [HIGH_GAS_CAP, 0] {
1279                    let relaxed = build_test_eth_api_with_gas_cap(provider.clone(), gas_cap);
1280                    let estimated =
1281                        EthCall::estimate_gas_at(&relaxed, request.clone(), at, overrides.clone())
1282                            .await
1283                            .expect("same call fits under a higher or unlimited RPC gas cap");
1284                    assert!(estimated > U256::from(LOW_GAS_CAP), "{estimated}");
1285                    assert!(estimated <= U256::from(HIGH_GAS_CAP), "{estimated}");
1286                }
1287            }
1288        }
1289    }
1290
1291    #[tokio::test]
1292    async fn estimate_gas_transfer_respects_rpc_gas_cap() {
1293        let provider = MockEthProvider::default();
1294        provider.add_block(
1295            B256::repeat_byte(0x42),
1296            Block {
1297                header: Header { number: 1, gas_limit: 30_000_000, ..Default::default() },
1298                body: BlockBody::default(),
1299            },
1300        );
1301        let request = TransactionRequest::default().with_to(Address::repeat_byte(0xaa));
1302        let at = BlockId::latest();
1303
1304        let capped = build_test_eth_api_with_gas_cap(provider.clone(), 20_999);
1305        let err = EthCall::estimate_gas_at(&capped, request.clone(), at, EvmOverrides::default())
1306            .await
1307            .expect_err("the transfer shortcut must respect the RPC gas cap");
1308        assert!(
1309            matches!(
1310                err.as_invalid_transaction(),
1311                Some(RpcInvalidTransactionError::GasRequiredExceedsAllowance { gas_limit: 20_999 })
1312            ),
1313            "{err}"
1314        );
1315
1316        let sufficient = build_test_eth_api_with_gas_cap(provider, 21_000);
1317        let estimated = EthCall::estimate_gas_at(&sufficient, request, at, EvmOverrides::default())
1318            .await
1319            .unwrap();
1320        assert_eq!(estimated, U256::from(21_000));
1321    }
1322
1323    #[tokio::test]
1324    async fn header_responses_omit_size_while_blocks_keep_it() {
1325        let provider = MockEthProvider::default();
1326        let block = Block {
1327            header: Header { number: 1, ..Default::default() },
1328            body: BlockBody::default(),
1329        };
1330        let hash = block.header.hash_slow();
1331        let block_size = alloy_rlp::encode(&block).len();
1332        provider.add_block(hash, block);
1333
1334        let api = build_test_eth_api(provider);
1335        for block_id in [BlockId::Number(BlockNumberOrTag::Number(1)), BlockId::Hash(hash.into())] {
1336            let header = EthBlocks::rpc_block_header(&api, block_id).await.unwrap().unwrap();
1337            let response = serde_json::to_value(&header).unwrap();
1338            assert!(response.get("size").is_none());
1339        }
1340
1341        for full in [false, true] {
1342            let block =
1343                EthBlocks::rpc_block(&api, BlockId::Number(BlockNumberOrTag::Number(1)), full)
1344                    .await
1345                    .unwrap()
1346                    .unwrap();
1347            assert_eq!(block.header.size, Some(U256::from(block_size)));
1348        }
1349    }
1350}