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