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