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, tx_forward::ForwardConfig,
25 EthApiError, EthStateCache, FeeHistoryCache, GasCap, GasPriceOracle, PendingBlock,
26};
27use reth_storage_api::{noop::NoopProvider, BlockReaderIdExt, ProviderHeader};
28use reth_tasks::{
29 pool::{BlockingTaskGuard, BlockingTaskPool},
30 TaskSpawner, TokioTaskExecutor,
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> EthApi<N, Rpc>
136where
137 N: RpcNodeCore,
138 Rpc: RpcConvert,
139 (): PendingEnvBuilder<N::Evm>,
140{
141 #[expect(clippy::too_many_arguments)]
143 pub fn new(
144 components: N,
145 eth_cache: EthStateCache<N::Primitives>,
146 gas_oracle: GasPriceOracle<N::Provider>,
147 gas_cap: impl Into<GasCap>,
148 max_simulate_blocks: u64,
149 eth_proof_window: u64,
150 blocking_task_pool: BlockingTaskPool,
151 fee_history_cache: FeeHistoryCache<ProviderHeader<N::Provider>>,
152 proof_permits: usize,
153 rpc_converter: Rpc,
154 max_batch_size: usize,
155 max_blocking_io_requests: usize,
156 pending_block_kind: PendingBlockKind,
157 raw_tx_forwarder: ForwardConfig,
158 send_raw_transaction_sync_timeout: Duration,
159 evm_memory_limit: u64,
160 force_blob_sidecar_upcasting: bool,
161 ) -> Self {
162 let inner = EthApiInner::new(
163 components,
164 eth_cache,
165 gas_oracle,
166 gas_cap,
167 max_simulate_blocks,
168 eth_proof_window,
169 blocking_task_pool,
170 fee_history_cache,
171 TokioTaskExecutor::default().boxed(),
172 proof_permits,
173 rpc_converter,
174 (),
175 max_batch_size,
176 max_blocking_io_requests,
177 pending_block_kind,
178 raw_tx_forwarder.forwarder_client(),
179 send_raw_transaction_sync_timeout,
180 evm_memory_limit,
181 force_blob_sidecar_upcasting,
182 );
183
184 Self { inner: Arc::new(inner) }
185 }
186}
187
188impl<N, Rpc> EthApiTypes for EthApi<N, Rpc>
189where
190 N: RpcNodeCore,
191 Rpc: RpcConvert<Error = EthApiError>,
192{
193 type Error = EthApiError;
194 type NetworkTypes = Rpc::Network;
195 type RpcConvert = Rpc;
196
197 fn converter(&self) -> &Self::RpcConvert {
198 &self.converter
199 }
200}
201
202impl<N, Rpc> RpcNodeCore for EthApi<N, Rpc>
203where
204 N: RpcNodeCore,
205 Rpc: RpcConvert,
206{
207 type Primitives = N::Primitives;
208 type Provider = N::Provider;
209 type Pool = N::Pool;
210 type Evm = N::Evm;
211 type Network = N::Network;
212
213 fn pool(&self) -> &Self::Pool {
214 self.inner.pool()
215 }
216
217 fn evm_config(&self) -> &Self::Evm {
218 self.inner.evm_config()
219 }
220
221 fn network(&self) -> &Self::Network {
222 self.inner.network()
223 }
224
225 fn provider(&self) -> &Self::Provider {
226 self.inner.provider()
227 }
228}
229
230impl<N, Rpc> RpcNodeCoreExt for EthApi<N, Rpc>
231where
232 N: RpcNodeCore,
233 Rpc: RpcConvert,
234{
235 #[inline]
236 fn cache(&self) -> &EthStateCache<N::Primitives> {
237 self.inner.cache()
238 }
239}
240
241impl<N, Rpc> std::fmt::Debug for EthApi<N, Rpc>
242where
243 N: RpcNodeCore,
244 Rpc: RpcConvert,
245{
246 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247 f.debug_struct("EthApi").finish_non_exhaustive()
248 }
249}
250
251impl<N, Rpc> SpawnBlocking for EthApi<N, Rpc>
252where
253 N: RpcNodeCore,
254 Rpc: RpcConvert<Error = EthApiError>,
255{
256 #[inline]
257 fn io_task_spawner(&self) -> impl TaskSpawner {
258 self.inner.task_spawner()
259 }
260
261 #[inline]
262 fn tracing_task_pool(&self) -> &BlockingTaskPool {
263 self.inner.blocking_task_pool()
264 }
265
266 #[inline]
267 fn tracing_task_guard(&self) -> &BlockingTaskGuard {
268 self.inner.blocking_task_guard()
269 }
270
271 #[inline]
272 fn blocking_io_task_guard(&self) -> &std::sync::Arc<tokio::sync::Semaphore> {
273 self.inner.blocking_io_request_semaphore()
274 }
275}
276
277#[expect(missing_debug_implementations)]
279pub struct EthApiInner<N: RpcNodeCore, Rpc: RpcConvert> {
280 components: N,
282 signers: SignersForRpc<N::Provider, Rpc::Network>,
284 eth_cache: EthStateCache<N::Primitives>,
286 gas_oracle: GasPriceOracle<N::Provider>,
288 gas_cap: u64,
290 max_simulate_blocks: u64,
292 eth_proof_window: u64,
294 starting_block: U256,
296 task_spawner: Box<dyn TaskSpawner>,
298 pending_block: Mutex<Option<PendingBlock<N::Primitives>>>,
300 blocking_task_pool: BlockingTaskPool,
302 fee_history_cache: FeeHistoryCache<ProviderHeader<N::Provider>>,
304
305 blocking_task_guard: BlockingTaskGuard,
307
308 blocking_io_request_semaphore: Arc<Semaphore>,
310
311 raw_tx_sender: broadcast::Sender<Bytes>,
313
314 raw_tx_forwarder: Option<RpcClient>,
316
317 converter: Rpc,
319
320 next_env_builder: Box<dyn PendingEnvBuilder<N::Evm>>,
322
323 tx_batch_sender:
325 mpsc::UnboundedSender<BatchTxRequest<<N::Pool as TransactionPool>::Transaction>>,
326
327 pending_block_kind: PendingBlockKind,
329
330 send_raw_transaction_sync_timeout: Duration,
332
333 blob_sidecar_converter: BlobSidecarConverter,
335
336 evm_memory_limit: u64,
338
339 force_blob_sidecar_upcasting: bool,
341}
342
343impl<N, Rpc> EthApiInner<N, Rpc>
344where
345 N: RpcNodeCore,
346 Rpc: RpcConvert,
347{
348 #[expect(clippy::too_many_arguments)]
350 pub fn new(
351 components: N,
352 eth_cache: EthStateCache<N::Primitives>,
353 gas_oracle: GasPriceOracle<N::Provider>,
354 gas_cap: impl Into<GasCap>,
355 max_simulate_blocks: u64,
356 eth_proof_window: u64,
357 blocking_task_pool: BlockingTaskPool,
358 fee_history_cache: FeeHistoryCache<ProviderHeader<N::Provider>>,
359 task_spawner: Box<dyn TaskSpawner + 'static>,
360 proof_permits: usize,
361 converter: Rpc,
362 next_env: impl PendingEnvBuilder<N::Evm>,
363 max_batch_size: usize,
364 max_blocking_io_requests: usize,
365 pending_block_kind: PendingBlockKind,
366 raw_tx_forwarder: Option<RpcClient>,
367 send_raw_transaction_sync_timeout: Duration,
368 evm_memory_limit: u64,
369 force_blob_sidecar_upcasting: bool,
370 ) -> Self {
371 let signers = parking_lot::RwLock::new(Default::default());
372 let starting_block = U256::from(
374 components
375 .provider()
376 .header_by_number_or_tag(BlockNumberOrTag::Latest)
377 .ok()
378 .flatten()
379 .map(|header| header.number())
380 .unwrap_or_default(),
381 );
382
383 let (raw_tx_sender, _) = broadcast::channel(DEFAULT_BROADCAST_CAPACITY);
384
385 let (processor, tx_batch_sender) =
387 BatchTxProcessor::new(components.pool().clone(), max_batch_size);
388 task_spawner.spawn_critical_task("tx-batcher", Box::pin(processor));
389
390 Self {
391 components,
392 signers,
393 eth_cache,
394 gas_oracle,
395 gas_cap: gas_cap.into().into(),
396 max_simulate_blocks,
397 eth_proof_window,
398 starting_block,
399 task_spawner,
400 pending_block: Default::default(),
401 blocking_task_pool,
402 fee_history_cache,
403 blocking_task_guard: BlockingTaskGuard::new(proof_permits),
404 blocking_io_request_semaphore: Arc::new(Semaphore::new(max_blocking_io_requests)),
405 raw_tx_sender,
406 raw_tx_forwarder,
407 converter,
408 next_env_builder: Box::new(next_env),
409 tx_batch_sender,
410 pending_block_kind,
411 send_raw_transaction_sync_timeout,
412 blob_sidecar_converter: BlobSidecarConverter::new(),
413 evm_memory_limit,
414 force_blob_sidecar_upcasting,
415 }
416 }
417}
418
419impl<N, Rpc> EthApiInner<N, Rpc>
420where
421 N: RpcNodeCore,
422 Rpc: RpcConvert,
423{
424 #[inline]
426 pub fn provider(&self) -> &N::Provider {
427 self.components.provider()
428 }
429
430 #[inline]
432 pub const fn converter(&self) -> &Rpc {
433 &self.converter
434 }
435
436 #[inline]
438 pub const fn cache(&self) -> &EthStateCache<N::Primitives> {
439 &self.eth_cache
440 }
441
442 #[inline]
444 pub const fn pending_block(&self) -> &Mutex<Option<PendingBlock<N::Primitives>>> {
445 &self.pending_block
446 }
447
448 #[inline]
451 pub const fn pending_env_builder(&self) -> &dyn PendingEnvBuilder<N::Evm> {
452 &*self.next_env_builder
453 }
454
455 #[inline]
457 pub const fn task_spawner(&self) -> &dyn TaskSpawner {
458 &*self.task_spawner
459 }
460
461 #[inline]
465 pub const fn blocking_task_pool(&self) -> &BlockingTaskPool {
466 &self.blocking_task_pool
467 }
468
469 #[inline]
471 pub fn evm_config(&self) -> &N::Evm {
472 self.components.evm_config()
473 }
474
475 #[inline]
477 pub fn pool(&self) -> &N::Pool {
478 self.components.pool()
479 }
480
481 #[inline]
483 pub const fn gas_cap(&self) -> u64 {
484 self.gas_cap
485 }
486
487 #[inline]
489 pub const fn max_simulate_blocks(&self) -> u64 {
490 self.max_simulate_blocks
491 }
492
493 #[inline]
495 pub const fn gas_oracle(&self) -> &GasPriceOracle<N::Provider> {
496 &self.gas_oracle
497 }
498
499 #[inline]
501 pub const fn fee_history_cache(&self) -> &FeeHistoryCache<ProviderHeader<N::Provider>> {
502 &self.fee_history_cache
503 }
504
505 #[inline]
507 pub const fn signers(&self) -> &SignersForRpc<N::Provider, Rpc::Network> {
508 &self.signers
509 }
510
511 #[inline]
513 pub const fn starting_block(&self) -> U256 {
514 self.starting_block
515 }
516
517 #[inline]
519 pub fn network(&self) -> &N::Network {
520 self.components.network()
521 }
522
523 #[inline]
525 pub const fn eth_proof_window(&self) -> u64 {
526 self.eth_proof_window
527 }
528
529 #[inline]
531 pub const fn blocking_task_guard(&self) -> &BlockingTaskGuard {
532 &self.blocking_task_guard
533 }
534
535 #[inline]
537 pub fn subscribe_to_raw_transactions(&self) -> broadcast::Receiver<Bytes> {
538 self.raw_tx_sender.subscribe()
539 }
540
541 #[inline]
543 pub fn broadcast_raw_transaction(&self, raw_tx: Bytes) {
544 let _ = self.raw_tx_sender.send(raw_tx);
545 }
546
547 #[inline]
549 pub const fn tx_batch_sender(
550 &self,
551 ) -> &mpsc::UnboundedSender<BatchTxRequest<<N::Pool as TransactionPool>::Transaction>> {
552 &self.tx_batch_sender
553 }
554
555 #[inline]
557 pub async fn add_pool_transaction(
558 &self,
559 origin: reth_transaction_pool::TransactionOrigin,
560 transaction: <N::Pool as TransactionPool>::Transaction,
561 ) -> Result<AddedTransactionOutcome, EthApiError> {
562 let (response_tx, response_rx) = tokio::sync::oneshot::channel();
563 let request = reth_transaction_pool::BatchTxRequest::new(origin, transaction, response_tx);
564
565 self.tx_batch_sender()
566 .send(request)
567 .map_err(|_| reth_rpc_eth_types::EthApiError::BatchTxSendError)?;
568
569 Ok(response_rx.await??)
570 }
571
572 #[inline]
574 pub const fn pending_block_kind(&self) -> PendingBlockKind {
575 self.pending_block_kind
576 }
577
578 #[inline]
580 pub const fn raw_tx_forwarder(&self) -> Option<&RpcClient> {
581 self.raw_tx_forwarder.as_ref()
582 }
583
584 #[inline]
586 pub const fn send_raw_transaction_sync_timeout(&self) -> Duration {
587 self.send_raw_transaction_sync_timeout
588 }
589
590 #[inline]
592 pub const fn blob_sidecar_converter(&self) -> &BlobSidecarConverter {
593 &self.blob_sidecar_converter
594 }
595
596 #[inline]
598 pub const fn evm_memory_limit(&self) -> u64 {
599 self.evm_memory_limit
600 }
601
602 #[inline]
604 pub const fn blocking_io_request_semaphore(&self) -> &Arc<Semaphore> {
605 &self.blocking_io_request_semaphore
606 }
607
608 #[inline]
610 pub const fn force_blob_sidecar_upcasting(&self) -> bool {
611 self.force_blob_sidecar_upcasting
612 }
613}
614
615#[cfg(test)]
616mod tests {
617 use crate::{eth::helpers::types::EthRpcConverter, EthApi, EthApiBuilder};
618 use alloy_consensus::{Block, BlockBody, Header};
619 use alloy_eips::BlockNumberOrTag;
620 use alloy_primitives::{Signature, B256, U64};
621 use alloy_rpc_types::FeeHistory;
622 use jsonrpsee_types::error::INVALID_PARAMS_CODE;
623 use rand::Rng;
624 use reth_chain_state::CanonStateSubscriptions;
625 use reth_chainspec::{ChainSpec, ChainSpecProvider, EthChainSpec};
626 use reth_ethereum_primitives::TransactionSigned;
627 use reth_evm_ethereum::EthEvmConfig;
628 use reth_network_api::noop::NoopNetwork;
629 use reth_provider::{
630 test_utils::{MockEthProvider, NoopProvider},
631 StageCheckpointReader,
632 };
633 use reth_rpc_eth_api::{node::RpcNodeCoreAdapter, EthApiServer};
634 use reth_storage_api::{BlockReader, BlockReaderIdExt, StateProviderFactory};
635 use reth_testing_utils::generators;
636 use reth_transaction_pool::test_utils::{testing_pool, TestPool};
637
638 type FakeEthApi<P = MockEthProvider> = EthApi<
639 RpcNodeCoreAdapter<P, TestPool, NoopNetwork, EthEvmConfig>,
640 EthRpcConverter<ChainSpec>,
641 >;
642
643 fn build_test_eth_api<
644 P: BlockReaderIdExt<
645 Block = reth_ethereum_primitives::Block,
646 Receipt = reth_ethereum_primitives::Receipt,
647 Header = alloy_consensus::Header,
648 Transaction = reth_ethereum_primitives::TransactionSigned,
649 > + BlockReader
650 + ChainSpecProvider<ChainSpec = ChainSpec>
651 + StateProviderFactory
652 + CanonStateSubscriptions<Primitives = reth_ethereum_primitives::EthPrimitives>
653 + StageCheckpointReader
654 + Unpin
655 + Clone
656 + 'static,
657 >(
658 provider: P,
659 ) -> FakeEthApi<P> {
660 EthApiBuilder::new(
661 provider.clone(),
662 testing_pool(),
663 NoopNetwork::default(),
664 EthEvmConfig::new(provider.chain_spec()),
665 )
666 .build()
667 }
668
669 fn prepare_eth_api(
671 newest_block: u64,
672 mut oldest_block: Option<B256>,
673 block_count: u64,
674 mock_provider: MockEthProvider,
675 ) -> (FakeEthApi, Vec<u128>, Vec<f64>) {
676 let mut rng = generators::rng();
677
678 let mut gas_used_ratios = Vec::with_capacity(block_count as usize);
680 let mut base_fees_per_gas = Vec::with_capacity(block_count as usize);
681 let mut last_header = None;
682 let mut parent_hash = B256::default();
683
684 for i in (0..block_count).rev() {
685 let hash = rng.random();
686 let gas_limit = rng.random::<u32>() as u64;
688 let base_fee_per_gas: Option<u64> =
689 rng.random::<bool>().then(|| rng.random::<u32>() as u64);
690 let gas_used = rng.random::<u32>() as u64;
691
692 let header = Header {
693 number: newest_block - i,
694 gas_limit,
695 gas_used,
696 base_fee_per_gas,
697 parent_hash,
698 ..Default::default()
699 };
700 last_header = Some(header.clone());
701 parent_hash = hash;
702
703 const TOTAL_TRANSACTIONS: usize = 100;
704 let mut transactions = Vec::with_capacity(TOTAL_TRANSACTIONS);
705 for _ in 0..TOTAL_TRANSACTIONS {
706 let random_fee: u128 = rng.random();
707
708 if let Some(base_fee_per_gas) = header.base_fee_per_gas {
709 let transaction = TransactionSigned::new_unhashed(
710 reth_ethereum_primitives::Transaction::Eip1559(
711 alloy_consensus::TxEip1559 {
712 max_priority_fee_per_gas: random_fee,
713 max_fee_per_gas: random_fee + base_fee_per_gas as u128,
714 ..Default::default()
715 },
716 ),
717 Signature::test_signature(),
718 );
719
720 transactions.push(transaction);
721 } else {
722 let transaction = TransactionSigned::new_unhashed(
723 reth_ethereum_primitives::Transaction::Legacy(Default::default()),
724 Signature::test_signature(),
725 );
726
727 transactions.push(transaction);
728 }
729 }
730
731 mock_provider.add_block(
732 hash,
733 Block {
734 header: header.clone(),
735 body: BlockBody { transactions, ..Default::default() },
736 },
737 );
738 mock_provider.add_header(hash, header);
739
740 oldest_block.get_or_insert(hash);
741 gas_used_ratios.push(gas_used as f64 / gas_limit as f64);
742 base_fees_per_gas.push(base_fee_per_gas.map(|fee| fee as u128).unwrap_or_default());
743 }
744
745 let last_header = last_header.unwrap();
747 let spec = mock_provider.chain_spec();
748 base_fees_per_gas.push(
749 spec.next_block_base_fee(&last_header, last_header.timestamp).unwrap_or_default()
750 as u128,
751 );
752
753 let eth_api = build_test_eth_api(mock_provider);
754
755 (eth_api, base_fees_per_gas, gas_used_ratios)
756 }
757
758 #[tokio::test]
760 async fn test_fee_history_empty() {
761 let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::fee_history(
762 &build_test_eth_api(NoopProvider::default()),
763 U64::from(1),
764 BlockNumberOrTag::Latest,
765 None,
766 )
767 .await;
768 assert!(response.is_err());
769 let error_object = response.unwrap_err();
770 assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
771 }
772
773 #[tokio::test]
774 async fn test_fee_history_invalid_block_range_before_genesis() {
776 let block_count = 10;
777 let newest_block = 1337;
778 let oldest_block = None;
779
780 let (eth_api, _, _) =
781 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
782
783 let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::fee_history(
784 ð_api,
785 U64::from(newest_block + 1),
786 newest_block.into(),
787 Some(vec![10.0]),
788 )
789 .await;
790
791 assert!(response.is_err());
792 let error_object = response.unwrap_err();
793 assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
794 }
795
796 #[tokio::test]
797 async fn test_fee_history_invalid_block_range_in_future() {
799 let block_count = 10;
800 let newest_block = 1337;
801 let oldest_block = None;
802
803 let (eth_api, _, _) =
804 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
805
806 let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::fee_history(
807 ð_api,
808 U64::from(1),
809 (newest_block + 1000).into(),
810 Some(vec![10.0]),
811 )
812 .await;
813
814 assert!(response.is_err());
815 let error_object = response.unwrap_err();
816 assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
817 }
818
819 #[tokio::test]
820 async fn test_fee_history_no_block_requested() {
822 let block_count = 10;
823 let newest_block = 1337;
824 let oldest_block = None;
825
826 let (eth_api, _, _) =
827 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
828
829 let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::fee_history(
830 ð_api,
831 U64::from(0),
832 newest_block.into(),
833 None,
834 )
835 .await
836 .unwrap();
837 assert_eq!(
838 response,
839 FeeHistory::default(),
840 "none: requesting no block should yield a default response"
841 );
842 }
843
844 #[tokio::test]
845 async fn test_fee_history_single_block() {
847 let block_count = 10;
848 let newest_block = 1337;
849 let oldest_block = None;
850
851 let (eth_api, base_fees_per_gas, gas_used_ratios) =
852 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
853
854 let fee_history =
855 eth_api.fee_history(U64::from(1), newest_block.into(), None).await.unwrap();
856 assert_eq!(
857 fee_history.base_fee_per_gas,
858 &base_fees_per_gas[base_fees_per_gas.len() - 2..],
859 "one: base fee per gas is incorrect"
860 );
861 assert_eq!(
862 fee_history.base_fee_per_gas.len(),
863 2,
864 "one: should return base fee of the next block as well"
865 );
866 assert_eq!(
867 &fee_history.gas_used_ratio,
868 &gas_used_ratios[gas_used_ratios.len() - 1..],
869 "one: gas used ratio is incorrect"
870 );
871 assert_eq!(fee_history.oldest_block, newest_block, "one: oldest block is incorrect");
872 assert!(
873 fee_history.reward.is_none(),
874 "one: no percentiles were requested, so there should be no rewards result"
875 );
876 }
877
878 #[tokio::test]
880 async fn test_fee_history_all_blocks() {
881 let block_count = 10;
882 let newest_block = 1337;
883 let oldest_block = None;
884
885 let (eth_api, base_fees_per_gas, gas_used_ratios) =
886 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
887
888 let fee_history =
889 eth_api.fee_history(U64::from(block_count), newest_block.into(), None).await.unwrap();
890
891 assert_eq!(
892 &fee_history.base_fee_per_gas, &base_fees_per_gas,
893 "all: base fee per gas is incorrect"
894 );
895 assert_eq!(
896 fee_history.base_fee_per_gas.len() as u64,
897 block_count + 1,
898 "all: should return base fee of the next block as well"
899 );
900 assert_eq!(
901 &fee_history.gas_used_ratio, &gas_used_ratios,
902 "all: gas used ratio is incorrect"
903 );
904 assert_eq!(
905 fee_history.oldest_block,
906 newest_block - block_count + 1,
907 "all: oldest block is incorrect"
908 );
909 assert!(
910 fee_history.reward.is_none(),
911 "all: no percentiles were requested, so there should be no rewards result"
912 );
913 }
914}