1use 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
40pub type EthRpcConverterFor<N, NetworkT = Ethereum> = RpcConverter<
42 NetworkT,
43 <N as FullNodeComponents>::Evm,
44 EthReceiptConverter<<<N as FullNodeTypes>::Provider as ChainSpecProvider>::ChainSpec>,
45>;
46
47pub type EthApiFor<N, NetworkT = Ethereum> = EthApi<N, EthRpcConverterFor<N, NetworkT>>;
49
50pub type EthApiBuilderFor<N, NetworkT = Ethereum> =
52 EthApiBuilder<N, EthRpcConverterFor<N, NetworkT>>;
53
54#[derive(Deref)]
69pub struct EthApi<N: RpcNodeCore, Rpc: RpcConvert> {
70 #[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 #[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#[expect(missing_debug_implementations)]
230pub struct EthApiInner<N: RpcNodeCore, Rpc: RpcConvert> {
231 components: N,
233 signers: SignersForRpc<N::Provider, Rpc::Network>,
235 eth_cache: EthStateCache<N::Primitives>,
237 gas_oracle: GasPriceOracle<N::Provider>,
239 settings: EthApiSettings,
241 starting_block: U256,
243 task_spawner: Runtime,
245 pending_block: Mutex<Option<PendingBlock<N::Primitives>>>,
247 blocking_task_pool: BlockingTaskPool,
249 fee_history_cache: FeeHistoryCache<ProviderHeader<N::Provider>>,
251
252 blocking_task_guard: BlockingTaskGuard,
254
255 blocking_io_request_semaphore: Arc<Semaphore>,
257
258 raw_tx_sender: broadcast::Sender<Bytes>,
260
261 raw_tx_forwarder: Option<RpcClient>,
263
264 converter: Rpc,
266
267 next_env_builder: Box<dyn PendingEnvBuilder<N::Evm>>,
269
270 tx_batch_sender:
272 mpsc::UnboundedSender<BatchTxRequest<<N::Pool as TransactionPool>::Transaction>>,
273
274 blob_sidecar_converter: BlobSidecarConverter,
276}
277
278impl<N, Rpc> EthApiInner<N, Rpc>
279where
280 N: RpcNodeCore,
281 Rpc: RpcConvert,
282{
283 #[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 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 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 #[inline]
348 pub fn provider(&self) -> &N::Provider {
349 self.components.provider()
350 }
351
352 #[inline]
354 pub const fn converter(&self) -> &Rpc {
355 &self.converter
356 }
357
358 #[inline]
360 pub const fn cache(&self) -> &EthStateCache<N::Primitives> {
361 &self.eth_cache
362 }
363
364 #[inline]
366 pub const fn pending_block(&self) -> &Mutex<Option<PendingBlock<N::Primitives>>> {
367 &self.pending_block
368 }
369
370 #[inline]
373 pub const fn pending_env_builder(&self) -> &dyn PendingEnvBuilder<N::Evm> {
374 &*self.next_env_builder
375 }
376
377 #[inline]
379 pub const fn task_spawner(&self) -> &Runtime {
380 &self.task_spawner
381 }
382
383 #[inline]
387 pub const fn blocking_task_pool(&self) -> &BlockingTaskPool {
388 &self.blocking_task_pool
389 }
390
391 #[inline]
393 pub fn evm_config(&self) -> &N::Evm {
394 self.components.evm_config()
395 }
396
397 #[inline]
399 pub fn pool(&self) -> &N::Pool {
400 self.components.pool()
401 }
402
403 #[inline]
405 pub const fn eth_api_settings(&self) -> &EthApiSettings {
406 &self.settings
407 }
408
409 #[inline]
411 pub const fn gas_cap(&self) -> u64 {
412 self.settings.gas_cap
413 }
414
415 #[inline]
417 pub const fn max_simulate_blocks(&self) -> u64 {
418 self.settings.max_simulate_blocks
419 }
420
421 #[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 #[inline]
429 pub const fn gas_oracle(&self) -> &GasPriceOracle<N::Provider> {
430 &self.gas_oracle
431 }
432
433 #[inline]
435 pub const fn fee_history_cache(&self) -> &FeeHistoryCache<ProviderHeader<N::Provider>> {
436 &self.fee_history_cache
437 }
438
439 #[inline]
441 pub const fn signers(&self) -> &SignersForRpc<N::Provider, Rpc::Network> {
442 &self.signers
443 }
444
445 #[inline]
447 pub const fn starting_block(&self) -> U256 {
448 self.starting_block
449 }
450
451 #[inline]
453 pub fn network(&self) -> &N::Network {
454 self.components.network()
455 }
456
457 #[inline]
459 pub const fn eth_proof_window(&self) -> u64 {
460 self.settings.eth_proof_window
461 }
462
463 #[inline]
465 pub const fn blocking_task_guard(&self) -> &BlockingTaskGuard {
466 &self.blocking_task_guard
467 }
468
469 #[inline]
471 pub fn subscribe_to_raw_transactions(&self) -> broadcast::Receiver<Bytes> {
472 self.raw_tx_sender.subscribe()
473 }
474
475 #[inline]
477 pub fn broadcast_raw_transaction(&self, raw_tx: Bytes) {
478 let _ = self.raw_tx_sender.send(raw_tx);
479 }
480
481 #[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 #[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 #[inline]
508 pub const fn pending_block_kind(&self) -> PendingBlockKind {
509 self.settings.pending_block_kind
510 }
511
512 #[inline]
514 pub const fn raw_tx_forwarder(&self) -> Option<&RpcClient> {
515 self.raw_tx_forwarder.as_ref()
516 }
517
518 #[inline]
520 pub const fn send_raw_transaction_sync_timeout(&self) -> Duration {
521 self.settings.send_raw_transaction_sync_timeout
522 }
523
524 #[inline]
526 pub const fn blob_sidecar_converter(&self) -> &BlobSidecarConverter {
527 &self.blob_sidecar_converter
528 }
529
530 #[inline]
532 pub const fn evm_memory_limit(&self) -> u64 {
533 self.settings.evm_memory_limit
534 }
535
536 #[inline]
538 pub const fn blocking_io_request_semaphore(&self) -> &Arc<Semaphore> {
539 &self.blocking_io_request_semaphore
540 }
541
542 #[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 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 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 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 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 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 #[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 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 ð_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 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 ð_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 ð_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 ð_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 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 ð_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 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 #[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 let code = Bytes::from_static(&[0x5a, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3]);
1148
1149 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 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}