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, 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 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 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 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 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 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 #[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 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 ð_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 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 ð_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 ð_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 ð_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 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 ð_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 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 #[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 let code = Bytes::from_static(&[0x5a, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3]);
1149
1150 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 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}