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