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