1use std::sync::Arc;
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 derive_more::Deref;
12use reth_chainspec::{ChainSpec, ChainSpecProvider};
13use reth_evm::ConfigureEvm;
14use reth_evm_ethereum::EthEvmConfig;
15use reth_node_api::{FullNodeComponents, FullNodeTypes};
16use reth_rpc_convert::{RpcConvert, RpcConverter};
17use reth_rpc_eth_api::{
18 helpers::{
19 pending_block::{BasicPendingEnvBuilder, PendingEnvBuilder},
20 spec::SignersForRpc,
21 SpawnBlocking,
22 },
23 node::RpcNodeCoreExt,
24 EthApiTypes, RpcNodeCore,
25};
26use reth_rpc_eth_types::{
27 receipt::EthReceiptConverter, EthApiError, EthStateCache, FeeHistoryCache, GasCap,
28 GasPriceOracle, PendingBlock,
29};
30use reth_storage_api::{
31 noop::NoopProvider, BlockReader, BlockReaderIdExt, NodePrimitivesProvider, ProviderBlock,
32 ProviderHeader, ProviderReceipt,
33};
34use reth_tasks::{
35 pool::{BlockingTaskGuard, BlockingTaskPool},
36 TaskSpawner, TokioTaskExecutor,
37};
38use tokio::sync::{broadcast, Mutex};
39
40const DEFAULT_BROADCAST_CAPACITY: usize = 2000;
41
42pub type EthRpcConverterFor<N> = RpcConverter<
44 Ethereum,
45 <N as FullNodeComponents>::Evm,
46 EthReceiptConverter<<<N as FullNodeTypes>::Provider as ChainSpecProvider>::ChainSpec>,
47>;
48
49pub type EthApiFor<N> = EthApi<
51 <N as FullNodeTypes>::Provider,
52 <N as FullNodeComponents>::Pool,
53 <N as FullNodeComponents>::Network,
54 <N as FullNodeComponents>::Evm,
55 EthRpcConverterFor<N>,
56>;
57
58pub type EthApiBuilderFor<N> = EthApiBuilder<
60 <N as FullNodeTypes>::Provider,
61 <N as FullNodeComponents>::Pool,
62 <N as FullNodeComponents>::Network,
63 <N as FullNodeComponents>::Evm,
64 EthRpcConverterFor<N>,
65>;
66
67#[derive(Deref)]
82pub struct EthApi<Provider: BlockReader, Pool, Network, EvmConfig: ConfigureEvm, Rpc: RpcConvert> {
83 #[deref]
85 pub(super) inner: Arc<EthApiInner<Provider, Pool, Network, EvmConfig, Rpc>>,
86}
87
88impl<Provider, Pool, Network, EvmConfig, Rpc> Clone
89 for EthApi<Provider, Pool, Network, EvmConfig, Rpc>
90where
91 Provider: BlockReader,
92 EvmConfig: ConfigureEvm,
93 Rpc: RpcConvert,
94{
95 fn clone(&self) -> Self {
96 Self { inner: self.inner.clone() }
97 }
98}
99
100impl EthApi<NoopProvider, (), (), EthEvmConfig, EthRpcConverter<ChainSpec>> {
101 #[expect(clippy::type_complexity)]
128 pub fn builder<Provider, Pool, Network, EvmConfig>(
129 provider: Provider,
130 pool: Pool,
131 network: Network,
132 evm_config: EvmConfig,
133 ) -> EthApiBuilder<
134 Provider,
135 Pool,
136 Network,
137 EvmConfig,
138 RpcConverter<Ethereum, EvmConfig, EthReceiptConverter<Provider::ChainSpec>>,
139 >
140 where
141 Provider: ChainSpecProvider + BlockReaderIdExt,
142 {
143 EthApiBuilder::new(provider, pool, network, evm_config)
144 }
145}
146
147impl<Provider, Pool, Network, EvmConfig, Rpc> EthApi<Provider, Pool, Network, EvmConfig, Rpc>
148where
149 Provider: BlockReaderIdExt + ChainSpecProvider,
150 EvmConfig: ConfigureEvm,
151 Rpc: RpcConvert,
152 BasicPendingEnvBuilder: PendingEnvBuilder<EvmConfig>,
153{
154 #[expect(clippy::too_many_arguments)]
156 pub fn new(
157 provider: Provider,
158 pool: Pool,
159 network: Network,
160 eth_cache: EthStateCache<Provider::Block, Provider::Receipt>,
161 gas_oracle: GasPriceOracle<Provider>,
162 gas_cap: impl Into<GasCap>,
163 max_simulate_blocks: u64,
164 eth_proof_window: u64,
165 blocking_task_pool: BlockingTaskPool,
166 fee_history_cache: FeeHistoryCache<ProviderHeader<Provider>>,
167 evm_config: EvmConfig,
168 proof_permits: usize,
169 rpc_converter: Rpc,
170 ) -> Self {
171 let inner = EthApiInner::new(
172 provider,
173 pool,
174 network,
175 eth_cache,
176 gas_oracle,
177 gas_cap,
178 max_simulate_blocks,
179 eth_proof_window,
180 blocking_task_pool,
181 fee_history_cache,
182 evm_config,
183 TokioTaskExecutor::default().boxed(),
184 proof_permits,
185 rpc_converter,
186 BasicPendingEnvBuilder::default(),
187 );
188
189 Self { inner: Arc::new(inner) }
190 }
191}
192
193impl<Provider, Pool, Network, EvmConfig, Rpc> EthApiTypes
194 for EthApi<Provider, Pool, Network, EvmConfig, Rpc>
195where
196 Self: Send + Sync,
197 Provider: BlockReader,
198 EvmConfig: ConfigureEvm,
199 Rpc: RpcConvert,
200{
201 type Error = EthApiError;
202 type NetworkTypes = Rpc::Network;
203 type RpcConvert = Rpc;
204
205 fn tx_resp_builder(&self) -> &Self::RpcConvert {
206 &self.tx_resp_builder
207 }
208}
209
210impl<Provider, Pool, Network, EvmConfig, Rpc> RpcNodeCore
211 for EthApi<Provider, Pool, Network, EvmConfig, Rpc>
212where
213 Provider: BlockReader + NodePrimitivesProvider + Clone + Unpin,
214 Pool: Send + Sync + Clone + Unpin,
215 Network: Send + Sync + Clone,
216 EvmConfig: ConfigureEvm,
217 Rpc: RpcConvert,
218{
219 type Primitives = Provider::Primitives;
220 type Provider = Provider;
221 type Pool = Pool;
222 type Evm = EvmConfig;
223 type Network = Network;
224 type PayloadBuilder = ();
225
226 fn pool(&self) -> &Self::Pool {
227 self.inner.pool()
228 }
229
230 fn evm_config(&self) -> &Self::Evm {
231 self.inner.evm_config()
232 }
233
234 fn network(&self) -> &Self::Network {
235 self.inner.network()
236 }
237
238 fn payload_builder(&self) -> &Self::PayloadBuilder {
239 &()
240 }
241
242 fn provider(&self) -> &Self::Provider {
243 self.inner.provider()
244 }
245}
246
247impl<Provider, Pool, Network, EvmConfig, Rpc> RpcNodeCoreExt
248 for EthApi<Provider, Pool, Network, EvmConfig, Rpc>
249where
250 Provider: BlockReader + NodePrimitivesProvider + Clone + Unpin,
251 Pool: Send + Sync + Clone + Unpin,
252 Network: Send + Sync + Clone,
253 EvmConfig: ConfigureEvm,
254 Rpc: RpcConvert,
255{
256 #[inline]
257 fn cache(&self) -> &EthStateCache<ProviderBlock<Provider>, ProviderReceipt<Provider>> {
258 self.inner.cache()
259 }
260}
261
262impl<Provider, Pool, Network, EvmConfig, Rpc> std::fmt::Debug
263 for EthApi<Provider, Pool, Network, EvmConfig, Rpc>
264where
265 Provider: BlockReader,
266 EvmConfig: ConfigureEvm,
267 Rpc: RpcConvert,
268{
269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270 f.debug_struct("EthApi").finish_non_exhaustive()
271 }
272}
273
274impl<Provider, Pool, Network, EvmConfig, Rpc> SpawnBlocking
275 for EthApi<Provider, Pool, Network, EvmConfig, Rpc>
276where
277 Self: EthApiTypes<NetworkTypes = Rpc::Network> + Clone + Send + Sync + 'static,
278 Provider: BlockReader,
279 EvmConfig: ConfigureEvm,
280 Rpc: RpcConvert,
281{
282 #[inline]
283 fn io_task_spawner(&self) -> impl TaskSpawner {
284 self.inner.task_spawner()
285 }
286
287 #[inline]
288 fn tracing_task_pool(&self) -> &BlockingTaskPool {
289 self.inner.blocking_task_pool()
290 }
291
292 #[inline]
293 fn tracing_task_guard(&self) -> &BlockingTaskGuard {
294 self.inner.blocking_task_guard()
295 }
296}
297
298#[expect(missing_debug_implementations)]
300pub struct EthApiInner<
301 Provider: BlockReader,
302 Pool,
303 Network,
304 EvmConfig: ConfigureEvm,
305 Rpc: RpcConvert,
306> {
307 pool: Pool,
309 provider: Provider,
311 network: Network,
313 signers: SignersForRpc<Provider, Rpc::Network>,
315 eth_cache: EthStateCache<Provider::Block, Provider::Receipt>,
317 gas_oracle: GasPriceOracle<Provider>,
319 gas_cap: u64,
321 max_simulate_blocks: u64,
323 eth_proof_window: u64,
325 starting_block: U256,
327 task_spawner: Box<dyn TaskSpawner>,
329 pending_block: Mutex<Option<PendingBlock<Provider::Block, Provider::Receipt>>>,
331 blocking_task_pool: BlockingTaskPool,
333 fee_history_cache: FeeHistoryCache<ProviderHeader<Provider>>,
335 evm_config: EvmConfig,
337
338 blocking_task_guard: BlockingTaskGuard,
340
341 raw_tx_sender: broadcast::Sender<Bytes>,
343
344 tx_resp_builder: Rpc,
346
347 next_env_builder: Box<dyn PendingEnvBuilder<EvmConfig>>,
349}
350
351impl<Provider, Pool, Network, EvmConfig, Rpc> EthApiInner<Provider, Pool, Network, EvmConfig, Rpc>
352where
353 Provider: BlockReaderIdExt,
354 EvmConfig: ConfigureEvm,
355 Rpc: RpcConvert,
356{
357 #[expect(clippy::too_many_arguments)]
359 pub fn new(
360 provider: Provider,
361 pool: Pool,
362 network: Network,
363 eth_cache: EthStateCache<Provider::Block, Provider::Receipt>,
364 gas_oracle: GasPriceOracle<Provider>,
365 gas_cap: impl Into<GasCap>,
366 max_simulate_blocks: u64,
367 eth_proof_window: u64,
368 blocking_task_pool: BlockingTaskPool,
369 fee_history_cache: FeeHistoryCache<ProviderHeader<Provider>>,
370 evm_config: EvmConfig,
371 task_spawner: Box<dyn TaskSpawner + 'static>,
372 proof_permits: usize,
373 tx_resp_builder: Rpc,
374 next_env: impl PendingEnvBuilder<EvmConfig>,
375 ) -> Self {
376 let signers = parking_lot::RwLock::new(Default::default());
377 let starting_block = U256::from(
379 provider
380 .header_by_number_or_tag(BlockNumberOrTag::Latest)
381 .ok()
382 .flatten()
383 .map(|header| header.number())
384 .unwrap_or_default(),
385 );
386
387 let (raw_tx_sender, _) = broadcast::channel(DEFAULT_BROADCAST_CAPACITY);
388
389 Self {
390 provider,
391 pool,
392 network,
393 signers,
394 eth_cache,
395 gas_oracle,
396 gas_cap: gas_cap.into().into(),
397 max_simulate_blocks,
398 eth_proof_window,
399 starting_block,
400 task_spawner,
401 pending_block: Default::default(),
402 blocking_task_pool,
403 fee_history_cache,
404 evm_config,
405 blocking_task_guard: BlockingTaskGuard::new(proof_permits),
406 raw_tx_sender,
407 tx_resp_builder,
408 next_env_builder: Box::new(next_env),
409 }
410 }
411}
412
413impl<Provider, Pool, Network, EvmConfig, Rpc> EthApiInner<Provider, Pool, Network, EvmConfig, Rpc>
414where
415 Provider: BlockReader,
416 EvmConfig: ConfigureEvm,
417 Rpc: RpcConvert,
418{
419 #[inline]
421 pub const fn provider(&self) -> &Provider {
422 &self.provider
423 }
424
425 #[inline]
427 pub const fn tx_resp_builder(&self) -> &Rpc {
428 &self.tx_resp_builder
429 }
430
431 #[inline]
433 pub const fn cache(&self) -> &EthStateCache<Provider::Block, Provider::Receipt> {
434 &self.eth_cache
435 }
436
437 #[inline]
439 pub const fn pending_block(
440 &self,
441 ) -> &Mutex<Option<PendingBlock<Provider::Block, Provider::Receipt>>> {
442 &self.pending_block
443 }
444
445 #[inline]
448 pub const fn pending_env_builder(&self) -> &dyn PendingEnvBuilder<EvmConfig> {
449 &*self.next_env_builder
450 }
451
452 #[inline]
454 pub const fn task_spawner(&self) -> &dyn TaskSpawner {
455 &*self.task_spawner
456 }
457
458 #[inline]
460 pub const fn blocking_task_pool(&self) -> &BlockingTaskPool {
461 &self.blocking_task_pool
462 }
463
464 #[inline]
466 pub const fn evm_config(&self) -> &EvmConfig {
467 &self.evm_config
468 }
469
470 #[inline]
472 pub const fn pool(&self) -> &Pool {
473 &self.pool
474 }
475
476 #[inline]
478 pub const fn gas_cap(&self) -> u64 {
479 self.gas_cap
480 }
481
482 #[inline]
484 pub const fn max_simulate_blocks(&self) -> u64 {
485 self.max_simulate_blocks
486 }
487
488 #[inline]
490 pub const fn gas_oracle(&self) -> &GasPriceOracle<Provider> {
491 &self.gas_oracle
492 }
493
494 #[inline]
496 pub const fn fee_history_cache(&self) -> &FeeHistoryCache<ProviderHeader<Provider>> {
497 &self.fee_history_cache
498 }
499
500 #[inline]
502 pub const fn signers(&self) -> &SignersForRpc<Provider, Rpc::Network> {
503 &self.signers
504 }
505
506 #[inline]
508 pub const fn starting_block(&self) -> U256 {
509 self.starting_block
510 }
511
512 #[inline]
514 pub const fn network(&self) -> &Network {
515 &self.network
516 }
517
518 #[inline]
520 pub const fn eth_proof_window(&self) -> u64 {
521 self.eth_proof_window
522 }
523
524 #[inline]
526 pub const fn blocking_task_guard(&self) -> &BlockingTaskGuard {
527 &self.blocking_task_guard
528 }
529
530 #[inline]
532 pub fn subscribe_to_raw_transactions(&self) -> broadcast::Receiver<Bytes> {
533 self.raw_tx_sender.subscribe()
534 }
535
536 #[inline]
538 pub fn broadcast_raw_transaction(&self, raw_tx: Bytes) {
539 let _ = self.raw_tx_sender.send(raw_tx);
540 }
541}
542
543#[cfg(test)]
544mod tests {
545 use crate::{EthApi, EthApiBuilder};
546 use alloy_consensus::{Block, BlockBody, Header};
547 use alloy_eips::BlockNumberOrTag;
548 use alloy_network::Ethereum;
549 use alloy_primitives::{Signature, B256, U64};
550 use alloy_rpc_types::FeeHistory;
551 use jsonrpsee_types::error::INVALID_PARAMS_CODE;
552 use rand::Rng;
553 use reth_chain_state::CanonStateSubscriptions;
554 use reth_chainspec::{ChainSpec, ChainSpecProvider, EthChainSpec};
555 use reth_ethereum_primitives::TransactionSigned;
556 use reth_evm_ethereum::EthEvmConfig;
557 use reth_network_api::noop::NoopNetwork;
558 use reth_provider::test_utils::{MockEthProvider, NoopProvider};
559 use reth_rpc_convert::RpcConverter;
560 use reth_rpc_eth_api::EthApiServer;
561 use reth_rpc_eth_types::receipt::EthReceiptConverter;
562 use reth_storage_api::{BlockReader, BlockReaderIdExt, StateProviderFactory};
563 use reth_testing_utils::generators;
564 use reth_transaction_pool::test_utils::{testing_pool, TestPool};
565
566 type FakeEthApi<P = MockEthProvider> = EthApi<
567 P,
568 TestPool,
569 NoopNetwork,
570 EthEvmConfig,
571 RpcConverter<Ethereum, EthEvmConfig, EthReceiptConverter<ChainSpec>>,
572 >;
573
574 fn build_test_eth_api<
575 P: BlockReaderIdExt<
576 Block = reth_ethereum_primitives::Block,
577 Receipt = reth_ethereum_primitives::Receipt,
578 Header = alloy_consensus::Header,
579 > + BlockReader
580 + ChainSpecProvider<ChainSpec = ChainSpec>
581 + StateProviderFactory
582 + CanonStateSubscriptions<Primitives = reth_ethereum_primitives::EthPrimitives>
583 + Unpin
584 + Clone
585 + 'static,
586 >(
587 provider: P,
588 ) -> FakeEthApi<P> {
589 EthApiBuilder::new(
590 provider.clone(),
591 testing_pool(),
592 NoopNetwork::default(),
593 EthEvmConfig::new(provider.chain_spec()),
594 )
595 .build()
596 }
597
598 fn prepare_eth_api(
600 newest_block: u64,
601 mut oldest_block: Option<B256>,
602 block_count: u64,
603 mock_provider: MockEthProvider,
604 ) -> (FakeEthApi, Vec<u128>, Vec<f64>) {
605 let mut rng = generators::rng();
606
607 let mut gas_used_ratios = Vec::with_capacity(block_count as usize);
609 let mut base_fees_per_gas = Vec::with_capacity(block_count as usize);
610 let mut last_header = None;
611 let mut parent_hash = B256::default();
612
613 for i in (0..block_count).rev() {
614 let hash = rng.random();
615 let gas_limit = rng.random::<u32>() as u64;
617 let base_fee_per_gas: Option<u64> =
618 rng.random::<bool>().then(|| rng.random::<u32>() as u64);
619 let gas_used = rng.random::<u32>() as u64;
620
621 let header = Header {
622 number: newest_block - i,
623 gas_limit,
624 gas_used,
625 base_fee_per_gas,
626 parent_hash,
627 ..Default::default()
628 };
629 last_header = Some(header.clone());
630 parent_hash = hash;
631
632 const TOTAL_TRANSACTIONS: usize = 100;
633 let mut transactions = Vec::with_capacity(TOTAL_TRANSACTIONS);
634 for _ in 0..TOTAL_TRANSACTIONS {
635 let random_fee: u128 = rng.random();
636
637 if let Some(base_fee_per_gas) = header.base_fee_per_gas {
638 let transaction = TransactionSigned::new_unhashed(
639 reth_ethereum_primitives::Transaction::Eip1559(
640 alloy_consensus::TxEip1559 {
641 max_priority_fee_per_gas: random_fee,
642 max_fee_per_gas: random_fee + base_fee_per_gas as u128,
643 ..Default::default()
644 },
645 ),
646 Signature::test_signature(),
647 );
648
649 transactions.push(transaction);
650 } else {
651 let transaction = TransactionSigned::new_unhashed(
652 reth_ethereum_primitives::Transaction::Legacy(Default::default()),
653 Signature::test_signature(),
654 );
655
656 transactions.push(transaction);
657 }
658 }
659
660 mock_provider.add_block(
661 hash,
662 Block {
663 header: header.clone(),
664 body: BlockBody { transactions, ..Default::default() },
665 },
666 );
667 mock_provider.add_header(hash, header);
668
669 oldest_block.get_or_insert(hash);
670 gas_used_ratios.push(gas_used as f64 / gas_limit as f64);
671 base_fees_per_gas.push(base_fee_per_gas.map(|fee| fee as u128).unwrap_or_default());
672 }
673
674 let last_header = last_header.unwrap();
676 let spec = mock_provider.chain_spec();
677 base_fees_per_gas.push(
678 spec.next_block_base_fee(&last_header, last_header.timestamp).unwrap_or_default()
679 as u128,
680 );
681
682 let eth_api = build_test_eth_api(mock_provider);
683
684 (eth_api, base_fees_per_gas, gas_used_ratios)
685 }
686
687 #[tokio::test]
689 async fn test_fee_history_empty() {
690 let response = <EthApi<_, _, _, _, _> as EthApiServer<_, _, _, _, _>>::fee_history(
691 &build_test_eth_api(NoopProvider::default()),
692 U64::from(1),
693 BlockNumberOrTag::Latest,
694 None,
695 )
696 .await;
697 assert!(response.is_err());
698 let error_object = response.unwrap_err();
699 assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
700 }
701
702 #[tokio::test]
703 async fn test_fee_history_invalid_block_range_before_genesis() {
705 let block_count = 10;
706 let newest_block = 1337;
707 let oldest_block = None;
708
709 let (eth_api, _, _) =
710 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
711
712 let response = <EthApi<_, _, _, _, _> as EthApiServer<_, _, _, _, _>>::fee_history(
713 ð_api,
714 U64::from(newest_block + 1),
715 newest_block.into(),
716 Some(vec![10.0]),
717 )
718 .await;
719
720 assert!(response.is_err());
721 let error_object = response.unwrap_err();
722 assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
723 }
724
725 #[tokio::test]
726 async fn test_fee_history_invalid_block_range_in_future() {
728 let block_count = 10;
729 let newest_block = 1337;
730 let oldest_block = None;
731
732 let (eth_api, _, _) =
733 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
734
735 let response = <EthApi<_, _, _, _, _> as EthApiServer<_, _, _, _, _>>::fee_history(
736 ð_api,
737 U64::from(1),
738 (newest_block + 1000).into(),
739 Some(vec![10.0]),
740 )
741 .await;
742
743 assert!(response.is_err());
744 let error_object = response.unwrap_err();
745 assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
746 }
747
748 #[tokio::test]
749 async fn test_fee_history_no_block_requested() {
751 let block_count = 10;
752 let newest_block = 1337;
753 let oldest_block = None;
754
755 let (eth_api, _, _) =
756 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
757
758 let response = <EthApi<_, _, _, _, _> as EthApiServer<_, _, _, _, _>>::fee_history(
759 ð_api,
760 U64::from(0),
761 newest_block.into(),
762 None,
763 )
764 .await
765 .unwrap();
766 assert_eq!(
767 response,
768 FeeHistory::default(),
769 "none: requesting no block should yield a default response"
770 );
771 }
772
773 #[tokio::test]
774 async fn test_fee_history_single_block() {
776 let block_count = 10;
777 let newest_block = 1337;
778 let oldest_block = None;
779
780 let (eth_api, base_fees_per_gas, gas_used_ratios) =
781 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
782
783 let fee_history =
784 eth_api.fee_history(U64::from(1), newest_block.into(), None).await.unwrap();
785 assert_eq!(
786 fee_history.base_fee_per_gas,
787 &base_fees_per_gas[base_fees_per_gas.len() - 2..],
788 "one: base fee per gas is incorrect"
789 );
790 assert_eq!(
791 fee_history.base_fee_per_gas.len(),
792 2,
793 "one: should return base fee of the next block as well"
794 );
795 assert_eq!(
796 &fee_history.gas_used_ratio,
797 &gas_used_ratios[gas_used_ratios.len() - 1..],
798 "one: gas used ratio is incorrect"
799 );
800 assert_eq!(fee_history.oldest_block, newest_block, "one: oldest block is incorrect");
801 assert!(
802 fee_history.reward.is_none(),
803 "one: no percentiles were requested, so there should be no rewards result"
804 );
805 }
806
807 #[tokio::test]
809 async fn test_fee_history_all_blocks() {
810 let block_count = 10;
811 let newest_block = 1337;
812 let oldest_block = None;
813
814 let (eth_api, base_fees_per_gas, gas_used_ratios) =
815 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
816
817 let fee_history =
818 eth_api.fee_history(U64::from(block_count), newest_block.into(), None).await.unwrap();
819
820 assert_eq!(
821 &fee_history.base_fee_per_gas, &base_fees_per_gas,
822 "all: base fee per gas is incorrect"
823 );
824 assert_eq!(
825 fee_history.base_fee_per_gas.len() as u64,
826 block_count + 1,
827 "all: should return base fee of the next block as well"
828 );
829 assert_eq!(
830 &fee_history.gas_used_ratio, &gas_used_ratios,
831 "all: gas used ratio is incorrect"
832 );
833 assert_eq!(
834 fee_history.oldest_block,
835 newest_block - block_count + 1,
836 "all: oldest block is incorrect"
837 );
838 assert!(
839 fee_history.reward.is_none(),
840 "all: no percentiles were requested, so there should be no rewards result"
841 );
842 }
843}