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 compute_state_root_for_eth_simulate: bool,
241 eth_proof_window: u64,
243 starting_block: U256,
245 task_spawner: Runtime,
247 pending_block: Mutex<Option<PendingBlock<N::Primitives>>>,
249 blocking_task_pool: BlockingTaskPool,
251 fee_history_cache: FeeHistoryCache<ProviderHeader<N::Provider>>,
253
254 blocking_task_guard: BlockingTaskGuard,
256
257 blocking_io_request_semaphore: Arc<Semaphore>,
259
260 raw_tx_sender: broadcast::Sender<Bytes>,
262
263 raw_tx_forwarder: Option<RpcClient>,
265
266 converter: Rpc,
268
269 next_env_builder: Box<dyn PendingEnvBuilder<N::Evm>>,
271
272 tx_batch_sender:
274 mpsc::UnboundedSender<BatchTxRequest<<N::Pool as TransactionPool>::Transaction>>,
275
276 pending_block_kind: PendingBlockKind,
278
279 send_raw_transaction_sync_timeout: Duration,
281
282 blob_sidecar_converter: BlobSidecarConverter,
284
285 evm_memory_limit: u64,
287
288 force_blob_sidecar_upcasting: bool,
290}
291
292impl<N, Rpc> EthApiInner<N, Rpc>
293where
294 N: RpcNodeCore,
295 Rpc: RpcConvert,
296{
297 #[expect(clippy::too_many_arguments)]
299 pub fn new(
300 components: N,
301 eth_cache: EthStateCache<N::Primitives>,
302 gas_oracle: GasPriceOracle<N::Provider>,
303 gas_cap: impl Into<GasCap>,
304 max_simulate_blocks: u64,
305 compute_state_root_for_eth_simulate: bool,
306 eth_proof_window: u64,
307 blocking_task_pool: BlockingTaskPool,
308 fee_history_cache: FeeHistoryCache<ProviderHeader<N::Provider>>,
309 task_spawner: Runtime,
310 proof_permits: usize,
311 converter: Rpc,
312 next_env: impl PendingEnvBuilder<N::Evm>,
313 max_batch_size: usize,
314 max_blocking_io_requests: usize,
315 pending_block_kind: PendingBlockKind,
316 raw_tx_forwarder: Option<RpcClient>,
317 send_raw_transaction_sync_timeout: Duration,
318 evm_memory_limit: u64,
319 force_blob_sidecar_upcasting: bool,
320 ) -> Self {
321 let signers = parking_lot::RwLock::new(Default::default());
322 let starting_block = U256::from(
324 components
325 .provider()
326 .header_by_number_or_tag(BlockNumberOrTag::Latest)
327 .ok()
328 .flatten()
329 .map(|header| header.number())
330 .unwrap_or_default(),
331 );
332
333 let (raw_tx_sender, _) = broadcast::channel(DEFAULT_BROADCAST_CAPACITY);
334
335 let (processor, tx_batch_sender) =
337 BatchTxProcessor::new(components.pool().clone(), max_batch_size);
338 task_spawner.spawn_critical_task("tx-batcher", processor);
339
340 Self {
341 components,
342 signers,
343 eth_cache,
344 gas_oracle,
345 gas_cap: gas_cap.into().into(),
346 max_simulate_blocks,
347 compute_state_root_for_eth_simulate,
348 eth_proof_window,
349 starting_block,
350 task_spawner,
351 pending_block: Default::default(),
352 blocking_task_pool,
353 fee_history_cache,
354 blocking_task_guard: BlockingTaskGuard::new(proof_permits),
355 blocking_io_request_semaphore: Arc::new(Semaphore::new(max_blocking_io_requests)),
356 raw_tx_sender,
357 raw_tx_forwarder,
358 converter,
359 next_env_builder: Box::new(next_env),
360 tx_batch_sender,
361 pending_block_kind,
362 send_raw_transaction_sync_timeout,
363 blob_sidecar_converter: BlobSidecarConverter::new(),
364 evm_memory_limit,
365 force_blob_sidecar_upcasting,
366 }
367 }
368}
369
370impl<N, Rpc> EthApiInner<N, Rpc>
371where
372 N: RpcNodeCore,
373 Rpc: RpcConvert,
374{
375 #[inline]
377 pub fn provider(&self) -> &N::Provider {
378 self.components.provider()
379 }
380
381 #[inline]
383 pub const fn converter(&self) -> &Rpc {
384 &self.converter
385 }
386
387 #[inline]
389 pub const fn cache(&self) -> &EthStateCache<N::Primitives> {
390 &self.eth_cache
391 }
392
393 #[inline]
395 pub const fn pending_block(&self) -> &Mutex<Option<PendingBlock<N::Primitives>>> {
396 &self.pending_block
397 }
398
399 #[inline]
402 pub const fn pending_env_builder(&self) -> &dyn PendingEnvBuilder<N::Evm> {
403 &*self.next_env_builder
404 }
405
406 #[inline]
408 pub const fn task_spawner(&self) -> &Runtime {
409 &self.task_spawner
410 }
411
412 #[inline]
416 pub const fn blocking_task_pool(&self) -> &BlockingTaskPool {
417 &self.blocking_task_pool
418 }
419
420 #[inline]
422 pub fn evm_config(&self) -> &N::Evm {
423 self.components.evm_config()
424 }
425
426 #[inline]
428 pub fn pool(&self) -> &N::Pool {
429 self.components.pool()
430 }
431
432 #[inline]
434 pub const fn gas_cap(&self) -> u64 {
435 self.gas_cap
436 }
437
438 #[inline]
440 pub const fn max_simulate_blocks(&self) -> u64 {
441 self.max_simulate_blocks
442 }
443
444 #[inline]
446 pub const fn compute_state_root_for_eth_simulate(&self) -> bool {
447 self.compute_state_root_for_eth_simulate
448 }
449
450 #[inline]
452 pub const fn gas_oracle(&self) -> &GasPriceOracle<N::Provider> {
453 &self.gas_oracle
454 }
455
456 #[inline]
458 pub const fn fee_history_cache(&self) -> &FeeHistoryCache<ProviderHeader<N::Provider>> {
459 &self.fee_history_cache
460 }
461
462 #[inline]
464 pub const fn signers(&self) -> &SignersForRpc<N::Provider, Rpc::Network> {
465 &self.signers
466 }
467
468 #[inline]
470 pub const fn starting_block(&self) -> U256 {
471 self.starting_block
472 }
473
474 #[inline]
476 pub fn network(&self) -> &N::Network {
477 self.components.network()
478 }
479
480 #[inline]
482 pub const fn eth_proof_window(&self) -> u64 {
483 self.eth_proof_window
484 }
485
486 #[inline]
488 pub const fn blocking_task_guard(&self) -> &BlockingTaskGuard {
489 &self.blocking_task_guard
490 }
491
492 #[inline]
494 pub fn subscribe_to_raw_transactions(&self) -> broadcast::Receiver<Bytes> {
495 self.raw_tx_sender.subscribe()
496 }
497
498 #[inline]
500 pub fn broadcast_raw_transaction(&self, raw_tx: Bytes) {
501 let _ = self.raw_tx_sender.send(raw_tx);
502 }
503
504 #[inline]
506 pub const fn tx_batch_sender(
507 &self,
508 ) -> &mpsc::UnboundedSender<BatchTxRequest<<N::Pool as TransactionPool>::Transaction>> {
509 &self.tx_batch_sender
510 }
511
512 #[inline]
514 pub async fn add_pool_transaction(
515 &self,
516 origin: reth_transaction_pool::TransactionOrigin,
517 transaction: <N::Pool as TransactionPool>::Transaction,
518 ) -> Result<AddedTransactionOutcome, EthApiError> {
519 let (response_tx, response_rx) = tokio::sync::oneshot::channel();
520 let request = reth_transaction_pool::BatchTxRequest::new(origin, transaction, response_tx);
521
522 self.tx_batch_sender()
523 .send(request)
524 .map_err(|_| reth_rpc_eth_types::EthApiError::BatchTxSendError)?;
525
526 Ok(response_rx.await??)
527 }
528
529 #[inline]
531 pub const fn pending_block_kind(&self) -> PendingBlockKind {
532 self.pending_block_kind
533 }
534
535 #[inline]
537 pub const fn raw_tx_forwarder(&self) -> Option<&RpcClient> {
538 self.raw_tx_forwarder.as_ref()
539 }
540
541 #[inline]
543 pub const fn send_raw_transaction_sync_timeout(&self) -> Duration {
544 self.send_raw_transaction_sync_timeout
545 }
546
547 #[inline]
549 pub const fn blob_sidecar_converter(&self) -> &BlobSidecarConverter {
550 &self.blob_sidecar_converter
551 }
552
553 #[inline]
555 pub const fn evm_memory_limit(&self) -> u64 {
556 self.evm_memory_limit
557 }
558
559 #[inline]
561 pub const fn blocking_io_request_semaphore(&self) -> &Arc<Semaphore> {
562 &self.blocking_io_request_semaphore
563 }
564
565 #[inline]
567 pub const fn force_blob_sidecar_upcasting(&self) -> bool {
568 self.force_blob_sidecar_upcasting
569 }
570}
571
572#[cfg(test)]
573mod tests {
574 use crate::{eth::helpers::types::EthRpcConverter, EthApi, EthApiBuilder};
575 use alloy_consensus::{Block, BlockBody, Header};
576 use alloy_eips::BlockNumberOrTag;
577 use alloy_primitives::{Signature, B256, U64};
578 use alloy_rpc_types::FeeHistory;
579 use alloy_rpc_types_eth::{Bundle, TransactionRequest};
580 use jsonrpsee_types::error::INVALID_PARAMS_CODE;
581 use rand::Rng;
582 use reth_chain_state::CanonStateSubscriptions;
583 use reth_chainspec::{ChainSpec, ChainSpecProvider, EthChainSpec};
584 use reth_ethereum_primitives::TransactionSigned;
585 use reth_evm_ethereum::EthEvmConfig;
586 use reth_network_api::noop::NoopNetwork;
587 use reth_provider::{
588 test_utils::{MockEthProvider, NoopProvider},
589 PruneCheckpointReader, StageCheckpointReader,
590 };
591 use reth_rpc_eth_api::{node::RpcNodeCoreAdapter, EthApiServer};
592 use reth_storage_api::{BalProvider, BlockReader, BlockReaderIdExt, StateProviderFactory};
593 use reth_testing_utils::generators;
594 use reth_transaction_pool::test_utils::{testing_pool, TestPool};
595
596 type FakeEthApi<P = MockEthProvider> = EthApi<
597 RpcNodeCoreAdapter<P, TestPool, NoopNetwork, EthEvmConfig>,
598 EthRpcConverter<ChainSpec>,
599 >;
600
601 fn build_test_eth_api<
602 P: BlockReaderIdExt<
603 Block = reth_ethereum_primitives::Block,
604 Receipt = reth_ethereum_primitives::Receipt,
605 Header = alloy_consensus::Header,
606 Transaction = reth_ethereum_primitives::TransactionSigned,
607 > + BlockReader
608 + ChainSpecProvider<ChainSpec = ChainSpec>
609 + StateProviderFactory
610 + CanonStateSubscriptions<Primitives = reth_ethereum_primitives::EthPrimitives>
611 + StageCheckpointReader
612 + PruneCheckpointReader
613 + BalProvider
614 + Unpin
615 + Clone
616 + 'static,
617 >(
618 provider: P,
619 ) -> FakeEthApi<P> {
620 EthApiBuilder::new(
621 provider.clone(),
622 testing_pool(),
623 NoopNetwork::default(),
624 EthEvmConfig::new(provider.chain_spec()),
625 )
626 .build()
627 }
628
629 fn prepare_eth_api(
631 newest_block: u64,
632 mut oldest_block: Option<B256>,
633 block_count: u64,
634 mock_provider: MockEthProvider,
635 ) -> (FakeEthApi, Vec<u128>, Vec<f64>) {
636 let mut rng = generators::rng();
637
638 let mut gas_used_ratios = Vec::with_capacity(block_count as usize);
640 let mut base_fees_per_gas = Vec::with_capacity(block_count as usize);
641 let mut last_header = None;
642 let mut parent_hash = B256::default();
643
644 for i in (0..block_count).rev() {
645 let hash = rng.random();
646 let gas_limit = rng.random::<u32>() as u64;
648 let base_fee_per_gas: Option<u64> =
649 rng.random::<bool>().then(|| rng.random::<u32>() as u64);
650 let gas_used = rng.random::<u32>() as u64;
651
652 let header = Header {
653 number: newest_block - i,
654 gas_limit,
655 gas_used,
656 base_fee_per_gas,
657 parent_hash,
658 ..Default::default()
659 };
660 last_header = Some(header.clone());
661 parent_hash = hash;
662
663 const TOTAL_TRANSACTIONS: usize = 100;
664 let mut transactions = Vec::with_capacity(TOTAL_TRANSACTIONS);
665 for _ in 0..TOTAL_TRANSACTIONS {
666 let random_fee: u128 = rng.random();
667
668 if let Some(base_fee_per_gas) = header.base_fee_per_gas {
669 let transaction = TransactionSigned::new_unhashed(
670 reth_ethereum_primitives::Transaction::Eip1559(
671 alloy_consensus::TxEip1559 {
672 max_priority_fee_per_gas: random_fee,
673 max_fee_per_gas: random_fee + base_fee_per_gas as u128,
674 ..Default::default()
675 },
676 ),
677 Signature::test_signature(),
678 );
679
680 transactions.push(transaction);
681 } else {
682 let transaction = TransactionSigned::new_unhashed(
683 reth_ethereum_primitives::Transaction::Legacy(Default::default()),
684 Signature::test_signature(),
685 );
686
687 transactions.push(transaction);
688 }
689 }
690
691 mock_provider.add_block(
692 hash,
693 Block {
694 header: header.clone(),
695 body: BlockBody { transactions, ..Default::default() },
696 },
697 );
698 mock_provider.add_header(hash, header);
699
700 oldest_block.get_or_insert(hash);
701 gas_used_ratios.push(gas_used as f64 / gas_limit as f64);
702 base_fees_per_gas.push(base_fee_per_gas.map(|fee| fee as u128).unwrap_or_default());
703 }
704
705 let last_header = last_header.unwrap();
707 let spec = mock_provider.chain_spec();
708 base_fees_per_gas.push(
709 spec.next_block_base_fee(&last_header, last_header.timestamp).unwrap_or_default()
710 as u128,
711 );
712
713 let eth_api = build_test_eth_api(mock_provider);
714
715 (eth_api, base_fees_per_gas, gas_used_ratios)
716 }
717
718 #[tokio::test]
720 async fn test_fee_history_empty() {
721 let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::fee_history(
722 &build_test_eth_api(NoopProvider::default()),
723 U64::from(1),
724 BlockNumberOrTag::Latest,
725 None,
726 )
727 .await;
728 assert!(response.is_err());
729 let error_object = response.unwrap_err();
730 assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
731 }
732
733 #[tokio::test]
734 async fn test_fee_history_invalid_block_range_before_genesis() {
736 let block_count = 10;
737 let newest_block = 1337;
738 let oldest_block = None;
739
740 let (eth_api, _, _) =
741 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
742
743 let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::fee_history(
744 ð_api,
745 U64::from(newest_block + 1),
746 newest_block.into(),
747 Some(vec![10.0]),
748 )
749 .await;
750
751 assert!(response.is_err());
752 let error_object = response.unwrap_err();
753 assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
754 }
755
756 #[tokio::test]
757 async fn test_fee_history_invalid_block_range_in_future() {
759 let block_count = 10;
760 let newest_block = 1337;
761 let oldest_block = None;
762
763 let (eth_api, _, _) =
764 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
765
766 let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::fee_history(
767 ð_api,
768 U64::from(1),
769 (newest_block + 1000).into(),
770 Some(vec![10.0]),
771 )
772 .await;
773
774 assert!(response.is_err());
775 let error_object = response.unwrap_err();
776 assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
777 }
778
779 #[tokio::test]
780 async fn test_call_many_maps_provider_block_lookup_error_with_eth_api_conversion() {
781 let eth_api = build_test_eth_api(MockEthProvider::default());
782 let bundles = vec![Bundle {
783 transactions: vec![TransactionRequest::default()],
784 block_override: None,
785 }];
786
787 let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::call_many(
788 ð_api, bundles, None, None,
789 )
790 .await;
791
792 let err = response.expect_err("call_many should fail when latest block lookup errors");
793 let message = err.message().to_ascii_lowercase();
794 assert!(
795 message.contains("block not found"),
796 "best block lookup should map via EthApiError::from(ProviderError): {message}"
797 );
798 assert!(
799 !message.contains("best block does not exist"),
800 "provider implementation detail should not leak from converted error: {message}"
801 );
802 }
803
804 #[tokio::test]
805 async fn test_call_many_keeps_header_not_found_when_block_hash_absent() {
806 let eth_api = build_test_eth_api(NoopProvider::default());
807 let bundles = vec![Bundle {
808 transactions: vec![TransactionRequest::default()],
809 block_override: None,
810 }];
811
812 let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::call_many(
813 ð_api, bundles, None, None,
814 )
815 .await;
816
817 let err =
818 response.expect_err("call_many should fail when latest block hash is unavailable");
819 let message = err.message().to_ascii_lowercase();
820 assert!(
821 message.contains("block not found"),
822 "missing block hash should still map to block-not-found: {message}"
823 );
824 }
825
826 #[tokio::test]
827 async fn test_fee_history_no_block_requested() {
829 let block_count = 10;
830 let newest_block = 1337;
831 let oldest_block = None;
832
833 let (eth_api, _, _) =
834 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
835
836 let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::fee_history(
837 ð_api,
838 U64::from(0),
839 newest_block.into(),
840 None,
841 )
842 .await
843 .unwrap();
844 assert_eq!(
845 response,
846 FeeHistory::default(),
847 "none: requesting no block should yield a default response"
848 );
849 }
850
851 #[tokio::test]
852 async fn test_fee_history_single_block() {
854 let block_count = 10;
855 let newest_block = 1337;
856 let oldest_block = None;
857
858 let (eth_api, base_fees_per_gas, gas_used_ratios) =
859 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
860
861 let fee_history =
862 eth_api.fee_history(U64::from(1), newest_block.into(), None).await.unwrap();
863 assert_eq!(
864 fee_history.base_fee_per_gas,
865 &base_fees_per_gas[base_fees_per_gas.len() - 2..],
866 "one: base fee per gas is incorrect"
867 );
868 assert_eq!(
869 fee_history.base_fee_per_gas.len(),
870 2,
871 "one: should return base fee of the next block as well"
872 );
873 assert_eq!(
874 &fee_history.gas_used_ratio,
875 &gas_used_ratios[gas_used_ratios.len() - 1..],
876 "one: gas used ratio is incorrect"
877 );
878 assert_eq!(fee_history.oldest_block, newest_block, "one: oldest block is incorrect");
879 assert!(
880 fee_history.reward.is_none(),
881 "one: no percentiles were requested, so there should be no rewards result"
882 );
883 }
884
885 #[tokio::test]
887 async fn test_fee_history_all_blocks() {
888 let block_count = 10;
889 let newest_block = 1337;
890 let oldest_block = None;
891
892 let (eth_api, base_fees_per_gas, gas_used_ratios) =
893 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
894
895 let fee_history =
896 eth_api.fee_history(U64::from(block_count), newest_block.into(), None).await.unwrap();
897
898 assert_eq!(
899 &fee_history.base_fee_per_gas, &base_fees_per_gas,
900 "all: base fee per gas is incorrect"
901 );
902 assert_eq!(
903 fee_history.base_fee_per_gas.len() as u64,
904 block_count + 1,
905 "all: should return base fee of the next block as well"
906 );
907 assert_eq!(
908 &fee_history.gas_used_ratio, &gas_used_ratios,
909 "all: gas used ratio is incorrect"
910 );
911 assert_eq!(
912 fee_history.oldest_block,
913 newest_block - block_count + 1,
914 "all: oldest block is incorrect"
915 );
916 assert!(
917 fee_history.reward.is_none(),
918 "all: no percentiles were requested, so there should be no rewards result"
919 );
920 }
921}