1use std::sync::Arc;
5
6use crate::{eth::EthTxBuilder, EthApiBuilder};
7use alloy_consensus::BlockHeader;
8use alloy_eips::BlockNumberOrTag;
9use alloy_network::Ethereum;
10use alloy_primitives::{Bytes, U256};
11use derive_more::Deref;
12use reth_node_api::{FullNodeComponents, FullNodeTypes};
13use reth_rpc_eth_api::{
14 helpers::{EthSigner, SpawnBlocking},
15 node::RpcNodeCoreExt,
16 EthApiTypes, RpcNodeCore,
17};
18use reth_rpc_eth_types::{
19 EthApiError, EthStateCache, FeeHistoryCache, GasCap, GasPriceOracle, PendingBlock,
20};
21use reth_storage_api::{
22 BlockReader, BlockReaderIdExt, NodePrimitivesProvider, ProviderBlock, ProviderReceipt,
23};
24use reth_tasks::{
25 pool::{BlockingTaskGuard, BlockingTaskPool},
26 TaskSpawner, TokioTaskExecutor,
27};
28use tokio::sync::{broadcast, Mutex};
29
30const DEFAULT_BROADCAST_CAPACITY: usize = 2000;
31
32pub type EthApiFor<N> = EthApi<
34 <N as FullNodeTypes>::Provider,
35 <N as FullNodeComponents>::Pool,
36 <N as FullNodeComponents>::Network,
37 <N as FullNodeComponents>::Evm,
38>;
39
40pub type EthApiBuilderFor<N> = EthApiBuilder<
42 <N as FullNodeTypes>::Provider,
43 <N as FullNodeComponents>::Pool,
44 <N as FullNodeComponents>::Network,
45 <N as FullNodeComponents>::Evm,
46>;
47
48#[derive(Deref)]
63pub struct EthApi<Provider: BlockReader, Pool, Network, EvmConfig> {
64 #[deref]
66 pub(super) inner: Arc<EthApiInner<Provider, Pool, Network, EvmConfig>>,
67 pub tx_resp_builder: EthTxBuilder,
69}
70
71impl<Provider, Pool, Network, EvmConfig> Clone for EthApi<Provider, Pool, Network, EvmConfig>
72where
73 Provider: BlockReader,
74{
75 fn clone(&self) -> Self {
76 Self { inner: self.inner.clone(), tx_resp_builder: EthTxBuilder }
77 }
78}
79
80impl<Provider, Pool, Network, EvmConfig> EthApi<Provider, Pool, Network, EvmConfig>
81where
82 Provider: BlockReaderIdExt,
83{
84 pub fn builder(
110 provider: Provider,
111 pool: Pool,
112 network: Network,
113 evm_config: EvmConfig,
114 ) -> EthApiBuilder<Provider, Pool, Network, EvmConfig> {
115 EthApiBuilder::new(provider, pool, network, evm_config)
116 }
117
118 #[expect(clippy::too_many_arguments)]
120 pub fn new(
121 provider: Provider,
122 pool: Pool,
123 network: Network,
124 eth_cache: EthStateCache<Provider::Block, Provider::Receipt>,
125 gas_oracle: GasPriceOracle<Provider>,
126 gas_cap: impl Into<GasCap>,
127 max_simulate_blocks: u64,
128 eth_proof_window: u64,
129 blocking_task_pool: BlockingTaskPool,
130 fee_history_cache: FeeHistoryCache,
131 evm_config: EvmConfig,
132 proof_permits: usize,
133 ) -> Self {
134 let inner = EthApiInner::new(
135 provider,
136 pool,
137 network,
138 eth_cache,
139 gas_oracle,
140 gas_cap,
141 max_simulate_blocks,
142 eth_proof_window,
143 blocking_task_pool,
144 fee_history_cache,
145 evm_config,
146 TokioTaskExecutor::default().boxed(),
147 proof_permits,
148 );
149
150 Self { inner: Arc::new(inner), tx_resp_builder: EthTxBuilder }
151 }
152}
153
154impl<Provider, Pool, Network, EvmConfig> EthApiTypes for EthApi<Provider, Pool, Network, EvmConfig>
155where
156 Self: Send + Sync,
157 Provider: BlockReader,
158{
159 type Error = EthApiError;
160 type NetworkTypes = Ethereum;
161 type TransactionCompat = EthTxBuilder;
162
163 fn tx_resp_builder(&self) -> &Self::TransactionCompat {
164 &self.tx_resp_builder
165 }
166}
167
168impl<Provider, Pool, Network, EvmConfig> RpcNodeCore for EthApi<Provider, Pool, Network, EvmConfig>
169where
170 Provider: BlockReader + NodePrimitivesProvider + Clone + Unpin,
171 Pool: Send + Sync + Clone + Unpin,
172 Network: Send + Sync + Clone,
173 EvmConfig: Send + Sync + Clone + Unpin,
174{
175 type Primitives = Provider::Primitives;
176 type Provider = Provider;
177 type Pool = Pool;
178 type Evm = EvmConfig;
179 type Network = Network;
180 type PayloadBuilder = ();
181
182 fn pool(&self) -> &Self::Pool {
183 self.inner.pool()
184 }
185
186 fn evm_config(&self) -> &Self::Evm {
187 self.inner.evm_config()
188 }
189
190 fn network(&self) -> &Self::Network {
191 self.inner.network()
192 }
193
194 fn payload_builder(&self) -> &Self::PayloadBuilder {
195 &()
196 }
197
198 fn provider(&self) -> &Self::Provider {
199 self.inner.provider()
200 }
201}
202
203impl<Provider, Pool, Network, EvmConfig> RpcNodeCoreExt
204 for EthApi<Provider, Pool, Network, EvmConfig>
205where
206 Provider: BlockReader + NodePrimitivesProvider + Clone + Unpin,
207 Pool: Send + Sync + Clone + Unpin,
208 Network: Send + Sync + Clone,
209 EvmConfig: Send + Sync + Clone + Unpin,
210{
211 #[inline]
212 fn cache(&self) -> &EthStateCache<ProviderBlock<Provider>, ProviderReceipt<Provider>> {
213 self.inner.cache()
214 }
215}
216
217impl<Provider, Pool, Network, EvmConfig> std::fmt::Debug
218 for EthApi<Provider, Pool, Network, EvmConfig>
219where
220 Provider: BlockReader,
221{
222 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223 f.debug_struct("EthApi").finish_non_exhaustive()
224 }
225}
226
227impl<Provider, Pool, Network, EvmConfig> SpawnBlocking
228 for EthApi<Provider, Pool, Network, EvmConfig>
229where
230 Self: Clone + Send + Sync + 'static,
231 Provider: BlockReader,
232{
233 #[inline]
234 fn io_task_spawner(&self) -> impl TaskSpawner {
235 self.inner.task_spawner()
236 }
237
238 #[inline]
239 fn tracing_task_pool(&self) -> &BlockingTaskPool {
240 self.inner.blocking_task_pool()
241 }
242
243 #[inline]
244 fn tracing_task_guard(&self) -> &BlockingTaskGuard {
245 self.inner.blocking_task_guard()
246 }
247}
248
249#[expect(missing_debug_implementations)]
251pub struct EthApiInner<Provider: BlockReader, Pool, Network, EvmConfig> {
252 pool: Pool,
254 provider: Provider,
256 network: Network,
258 signers: parking_lot::RwLock<Vec<Box<dyn EthSigner<Provider::Transaction>>>>,
260 eth_cache: EthStateCache<Provider::Block, Provider::Receipt>,
262 gas_oracle: GasPriceOracle<Provider>,
264 gas_cap: u64,
266 max_simulate_blocks: u64,
268 eth_proof_window: u64,
270 starting_block: U256,
272 task_spawner: Box<dyn TaskSpawner>,
274 pending_block: Mutex<Option<PendingBlock<Provider::Block, Provider::Receipt>>>,
276 blocking_task_pool: BlockingTaskPool,
278 fee_history_cache: FeeHistoryCache,
280 evm_config: EvmConfig,
282
283 blocking_task_guard: BlockingTaskGuard,
285
286 raw_tx_sender: broadcast::Sender<Bytes>,
288}
289
290impl<Provider, Pool, Network, EvmConfig> EthApiInner<Provider, Pool, Network, EvmConfig>
291where
292 Provider: BlockReaderIdExt,
293{
294 #[expect(clippy::too_many_arguments)]
296 pub fn new(
297 provider: Provider,
298 pool: Pool,
299 network: Network,
300 eth_cache: EthStateCache<Provider::Block, Provider::Receipt>,
301 gas_oracle: GasPriceOracle<Provider>,
302 gas_cap: impl Into<GasCap>,
303 max_simulate_blocks: u64,
304 eth_proof_window: u64,
305 blocking_task_pool: BlockingTaskPool,
306 fee_history_cache: FeeHistoryCache,
307 evm_config: EvmConfig,
308 task_spawner: Box<dyn TaskSpawner + 'static>,
309 proof_permits: usize,
310 ) -> Self {
311 let signers = parking_lot::RwLock::new(Default::default());
312 let starting_block = U256::from(
314 provider
315 .header_by_number_or_tag(BlockNumberOrTag::Latest)
316 .ok()
317 .flatten()
318 .map(|header| header.number())
319 .unwrap_or_default(),
320 );
321
322 let (raw_tx_sender, _) = broadcast::channel(DEFAULT_BROADCAST_CAPACITY);
323
324 Self {
325 provider,
326 pool,
327 network,
328 signers,
329 eth_cache,
330 gas_oracle,
331 gas_cap: gas_cap.into().into(),
332 max_simulate_blocks,
333 eth_proof_window,
334 starting_block,
335 task_spawner,
336 pending_block: Default::default(),
337 blocking_task_pool,
338 fee_history_cache,
339 evm_config,
340 blocking_task_guard: BlockingTaskGuard::new(proof_permits),
341 raw_tx_sender,
342 }
343 }
344}
345
346impl<Provider, Pool, Network, EvmConfig> EthApiInner<Provider, Pool, Network, EvmConfig>
347where
348 Provider: BlockReader,
349{
350 #[inline]
352 pub const fn provider(&self) -> &Provider {
353 &self.provider
354 }
355
356 #[inline]
358 pub const fn cache(&self) -> &EthStateCache<Provider::Block, Provider::Receipt> {
359 &self.eth_cache
360 }
361
362 #[inline]
364 pub const fn pending_block(
365 &self,
366 ) -> &Mutex<Option<PendingBlock<Provider::Block, Provider::Receipt>>> {
367 &self.pending_block
368 }
369
370 #[inline]
372 pub const fn task_spawner(&self) -> &dyn TaskSpawner {
373 &*self.task_spawner
374 }
375
376 #[inline]
378 pub const fn blocking_task_pool(&self) -> &BlockingTaskPool {
379 &self.blocking_task_pool
380 }
381
382 #[inline]
384 pub const fn evm_config(&self) -> &EvmConfig {
385 &self.evm_config
386 }
387
388 #[inline]
390 pub const fn pool(&self) -> &Pool {
391 &self.pool
392 }
393
394 #[inline]
396 pub const fn gas_cap(&self) -> u64 {
397 self.gas_cap
398 }
399
400 #[inline]
402 pub const fn max_simulate_blocks(&self) -> u64 {
403 self.max_simulate_blocks
404 }
405
406 #[inline]
408 pub const fn gas_oracle(&self) -> &GasPriceOracle<Provider> {
409 &self.gas_oracle
410 }
411
412 #[inline]
414 pub const fn fee_history_cache(&self) -> &FeeHistoryCache {
415 &self.fee_history_cache
416 }
417
418 #[inline]
420 pub const fn signers(
421 &self,
422 ) -> &parking_lot::RwLock<Vec<Box<dyn EthSigner<Provider::Transaction>>>> {
423 &self.signers
424 }
425
426 #[inline]
428 pub const fn starting_block(&self) -> U256 {
429 self.starting_block
430 }
431
432 #[inline]
434 pub const fn network(&self) -> &Network {
435 &self.network
436 }
437
438 #[inline]
440 pub const fn eth_proof_window(&self) -> u64 {
441 self.eth_proof_window
442 }
443
444 #[inline]
446 pub const fn blocking_task_guard(&self) -> &BlockingTaskGuard {
447 &self.blocking_task_guard
448 }
449
450 #[inline]
452 pub fn subscribe_to_raw_transactions(&self) -> broadcast::Receiver<Bytes> {
453 self.raw_tx_sender.subscribe()
454 }
455
456 #[inline]
458 pub fn broadcast_raw_transaction(&self, raw_tx: Bytes) {
459 let _ = self.raw_tx_sender.send(raw_tx);
460 }
461}
462
463#[cfg(test)]
464mod tests {
465 use crate::{EthApi, EthApiBuilder};
466 use alloy_consensus::{Block, BlockBody, Header};
467 use alloy_eips::BlockNumberOrTag;
468 use alloy_primitives::{Signature, B256, U64};
469 use alloy_rpc_types::FeeHistory;
470 use jsonrpsee_types::error::INVALID_PARAMS_CODE;
471 use rand::Rng;
472 use reth_chain_state::CanonStateSubscriptions;
473 use reth_chainspec::{BaseFeeParams, ChainSpec, ChainSpecProvider};
474 use reth_ethereum_primitives::TransactionSigned;
475 use reth_evm_ethereum::EthEvmConfig;
476 use reth_network_api::noop::NoopNetwork;
477 use reth_provider::test_utils::{MockEthProvider, NoopProvider};
478 use reth_rpc_eth_api::EthApiServer;
479 use reth_storage_api::{BlockReader, BlockReaderIdExt, StateProviderFactory};
480 use reth_testing_utils::generators;
481 use reth_transaction_pool::test_utils::{testing_pool, TestPool};
482
483 fn build_test_eth_api<
484 P: BlockReaderIdExt<
485 Block = reth_ethereum_primitives::Block,
486 Receipt = reth_ethereum_primitives::Receipt,
487 Header = alloy_consensus::Header,
488 > + BlockReader
489 + ChainSpecProvider<ChainSpec = ChainSpec>
490 + StateProviderFactory
491 + CanonStateSubscriptions<Primitives = reth_ethereum_primitives::EthPrimitives>
492 + Unpin
493 + Clone
494 + 'static,
495 >(
496 provider: P,
497 ) -> EthApi<P, TestPool, NoopNetwork, EthEvmConfig> {
498 EthApiBuilder::new(
499 provider.clone(),
500 testing_pool(),
501 NoopNetwork::default(),
502 EthEvmConfig::new(provider.chain_spec()),
503 )
504 .build()
505 }
506
507 fn prepare_eth_api(
509 newest_block: u64,
510 mut oldest_block: Option<B256>,
511 block_count: u64,
512 mock_provider: MockEthProvider,
513 ) -> (EthApi<MockEthProvider, TestPool, NoopNetwork, EthEvmConfig>, Vec<u128>, Vec<f64>) {
514 let mut rng = generators::rng();
515
516 let mut gas_used_ratios = Vec::with_capacity(block_count as usize);
518 let mut base_fees_per_gas = Vec::with_capacity(block_count as usize);
519 let mut last_header = None;
520 let mut parent_hash = B256::default();
521
522 for i in (0..block_count).rev() {
523 let hash = rng.random();
524 let gas_limit = rng.random::<u32>() as u64;
526 let base_fee_per_gas: Option<u64> =
527 rng.random::<bool>().then(|| rng.random::<u32>() as u64);
528 let gas_used = rng.random::<u32>() as u64;
529
530 let header = Header {
531 number: newest_block - i,
532 gas_limit,
533 gas_used,
534 base_fee_per_gas,
535 parent_hash,
536 ..Default::default()
537 };
538 last_header = Some(header.clone());
539 parent_hash = hash;
540
541 const TOTAL_TRANSACTIONS: usize = 100;
542 let mut transactions = Vec::with_capacity(TOTAL_TRANSACTIONS);
543 for _ in 0..TOTAL_TRANSACTIONS {
544 let random_fee: u128 = rng.random();
545
546 if let Some(base_fee_per_gas) = header.base_fee_per_gas {
547 let transaction = TransactionSigned::new_unhashed(
548 reth_ethereum_primitives::Transaction::Eip1559(
549 alloy_consensus::TxEip1559 {
550 max_priority_fee_per_gas: random_fee,
551 max_fee_per_gas: random_fee + base_fee_per_gas as u128,
552 ..Default::default()
553 },
554 ),
555 Signature::test_signature(),
556 );
557
558 transactions.push(transaction);
559 } else {
560 let transaction = TransactionSigned::new_unhashed(
561 reth_ethereum_primitives::Transaction::Legacy(Default::default()),
562 Signature::test_signature(),
563 );
564
565 transactions.push(transaction);
566 }
567 }
568
569 mock_provider.add_block(
570 hash,
571 Block {
572 header: header.clone(),
573 body: BlockBody { transactions, ..Default::default() },
574 },
575 );
576 mock_provider.add_header(hash, header);
577
578 oldest_block.get_or_insert(hash);
579 gas_used_ratios.push(gas_used as f64 / gas_limit as f64);
580 base_fees_per_gas.push(base_fee_per_gas.map(|fee| fee as u128).unwrap_or_default());
581 }
582
583 let last_header = last_header.unwrap();
585 base_fees_per_gas.push(BaseFeeParams::ethereum().next_block_base_fee(
586 last_header.gas_used,
587 last_header.gas_limit,
588 last_header.base_fee_per_gas.unwrap_or_default(),
589 ) as u128);
590
591 let eth_api = build_test_eth_api(mock_provider);
592
593 (eth_api, base_fees_per_gas, gas_used_ratios)
594 }
595
596 #[tokio::test]
598 async fn test_fee_history_empty() {
599 let response = <EthApi<_, _, _, _> as EthApiServer<_, _, _, _>>::fee_history(
600 &build_test_eth_api(NoopProvider::default()),
601 U64::from(1),
602 BlockNumberOrTag::Latest,
603 None,
604 )
605 .await;
606 assert!(response.is_err());
607 let error_object = response.unwrap_err();
608 assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
609 }
610
611 #[tokio::test]
612 async fn test_fee_history_invalid_block_range_before_genesis() {
614 let block_count = 10;
615 let newest_block = 1337;
616 let oldest_block = None;
617
618 let (eth_api, _, _) =
619 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
620
621 let response = <EthApi<_, _, _, _> as EthApiServer<_, _, _, _>>::fee_history(
622 ð_api,
623 U64::from(newest_block + 1),
624 newest_block.into(),
625 Some(vec![10.0]),
626 )
627 .await;
628
629 assert!(response.is_err());
630 let error_object = response.unwrap_err();
631 assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
632 }
633
634 #[tokio::test]
635 async fn test_fee_history_invalid_block_range_in_future() {
637 let block_count = 10;
638 let newest_block = 1337;
639 let oldest_block = None;
640
641 let (eth_api, _, _) =
642 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
643
644 let response = <EthApi<_, _, _, _> as EthApiServer<_, _, _, _>>::fee_history(
645 ð_api,
646 U64::from(1),
647 (newest_block + 1000).into(),
648 Some(vec![10.0]),
649 )
650 .await;
651
652 assert!(response.is_err());
653 let error_object = response.unwrap_err();
654 assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
655 }
656
657 #[tokio::test]
658 async fn test_fee_history_no_block_requested() {
660 let block_count = 10;
661 let newest_block = 1337;
662 let oldest_block = None;
663
664 let (eth_api, _, _) =
665 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
666
667 let response = <EthApi<_, _, _, _> as EthApiServer<_, _, _, _>>::fee_history(
668 ð_api,
669 U64::from(0),
670 newest_block.into(),
671 None,
672 )
673 .await
674 .unwrap();
675 assert_eq!(
676 response,
677 FeeHistory::default(),
678 "none: requesting no block should yield a default response"
679 );
680 }
681
682 #[tokio::test]
683 async fn test_fee_history_single_block() {
685 let block_count = 10;
686 let newest_block = 1337;
687 let oldest_block = None;
688
689 let (eth_api, base_fees_per_gas, gas_used_ratios) =
690 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
691
692 let fee_history =
693 eth_api.fee_history(U64::from(1), newest_block.into(), None).await.unwrap();
694 assert_eq!(
695 fee_history.base_fee_per_gas,
696 &base_fees_per_gas[base_fees_per_gas.len() - 2..],
697 "one: base fee per gas is incorrect"
698 );
699 assert_eq!(
700 fee_history.base_fee_per_gas.len(),
701 2,
702 "one: should return base fee of the next block as well"
703 );
704 assert_eq!(
705 &fee_history.gas_used_ratio,
706 &gas_used_ratios[gas_used_ratios.len() - 1..],
707 "one: gas used ratio is incorrect"
708 );
709 assert_eq!(fee_history.oldest_block, newest_block, "one: oldest block is incorrect");
710 assert!(
711 fee_history.reward.is_none(),
712 "one: no percentiles were requested, so there should be no rewards result"
713 );
714 }
715
716 #[tokio::test]
718 async fn test_fee_history_all_blocks() {
719 let block_count = 10;
720 let newest_block = 1337;
721 let oldest_block = None;
722
723 let (eth_api, base_fees_per_gas, gas_used_ratios) =
724 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
725
726 let fee_history =
727 eth_api.fee_history(U64::from(block_count), newest_block.into(), None).await.unwrap();
728
729 assert_eq!(
730 &fee_history.base_fee_per_gas, &base_fees_per_gas,
731 "all: base fee per gas is incorrect"
732 );
733 assert_eq!(
734 fee_history.base_fee_per_gas.len() as u64,
735 block_count + 1,
736 "all: should return base fee of the next block as well"
737 );
738 assert_eq!(
739 &fee_history.gas_used_ratio, &gas_used_ratios,
740 "all: gas used ratio is incorrect"
741 );
742 assert_eq!(
743 fee_history.oldest_block,
744 newest_block - block_count + 1,
745 "all: oldest block is incorrect"
746 );
747 assert!(
748 fee_history.reward.is_none(),
749 "all: no percentiles were requested, so there should be no rewards result"
750 );
751 }
752}