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