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_rpc_convert::{RpcConvert, RpcConverter};
17use reth_rpc_eth_api::{
18 helpers::{pending_block::PendingEnvBuilder, spec::SignersForRpc, SpawnBlocking},
19 node::{RpcNodeCoreAdapter, RpcNodeCoreExt},
20 EthApiTypes, RpcNodeCore,
21};
22use reth_rpc_eth_types::{
23 builder::config::PendingBlockKind, receipt::EthReceiptConverter, EthApiError, EthApiSettings,
24 EthStateCache, FeeHistoryCache, GasPriceOracle, PendingBlock,
25};
26use reth_storage_api::{noop::NoopProvider, BlockReaderIdExt, ProviderHeader};
27use reth_tasks::{
28 pool::{BlockingTaskGuard, BlockingTaskPool},
29 Runtime,
30};
31use reth_transaction_pool::{
32 blobstore::BlobSidecarConverter, noop::NoopTransactionPool, AddedTransactionOutcome,
33 BatchTxProcessor, BatchTxRequest, TransactionPool,
34};
35use tokio::sync::{broadcast, mpsc, Mutex, Semaphore};
36
37const DEFAULT_BROADCAST_CAPACITY: usize = 2000;
38
39#[derive(Deref)]
54pub struct EthApi<N: RpcNodeCore, Rpc: RpcConvert> {
55 #[deref]
57 pub(super) inner: Arc<EthApiInner<N, Rpc>>,
58}
59
60impl<N, Rpc> Clone for EthApi<N, Rpc>
61where
62 N: RpcNodeCore,
63 Rpc: RpcConvert,
64{
65 fn clone(&self) -> Self {
66 Self { inner: self.inner.clone() }
67 }
68}
69
70impl
71 EthApi<
72 RpcNodeCoreAdapter<NoopProvider, NoopTransactionPool, NoopNetwork, EthEvmConfig>,
73 EthRpcConverter<ChainSpec>,
74 >
75{
76 #[expect(clippy::type_complexity)]
103 pub fn builder<Provider, Pool, Network, EvmConfig, ChainSpec>(
104 provider: Provider,
105 pool: Pool,
106 network: Network,
107 evm_config: EvmConfig,
108 ) -> EthApiBuilder<
109 RpcNodeCoreAdapter<Provider, Pool, Network, EvmConfig>,
110 RpcConverter<Ethereum, EvmConfig, EthReceiptConverter<ChainSpec>>,
111 >
112 where
113 RpcNodeCoreAdapter<Provider, Pool, Network, EvmConfig>:
114 RpcNodeCore<Provider: ChainSpecProvider<ChainSpec = ChainSpec>, Evm = EvmConfig>,
115 {
116 EthApiBuilder::new(provider, pool, network, evm_config)
117 }
118}
119
120impl<N, Rpc> EthApiTypes for EthApi<N, Rpc>
121where
122 N: RpcNodeCore,
123 Rpc: RpcConvert<Error = EthApiError>,
124{
125 type Error = EthApiError;
126 type NetworkTypes = Rpc::Network;
127 type RpcConvert = Rpc;
128
129 fn eth_api_settings(&self) -> &EthApiSettings {
130 self.inner.eth_api_settings()
131 }
132
133 fn converter(&self) -> &Self::RpcConvert {
134 &self.converter
135 }
136}
137
138impl<N, Rpc> RpcNodeCore for EthApi<N, Rpc>
139where
140 N: RpcNodeCore,
141 Rpc: RpcConvert,
142{
143 type Primitives = N::Primitives;
144 type Provider = N::Provider;
145 type Pool = N::Pool;
146 type Evm = N::Evm;
147 type Network = N::Network;
148
149 fn pool(&self) -> &Self::Pool {
150 self.inner.pool()
151 }
152
153 fn evm_config(&self) -> &Self::Evm {
154 self.inner.evm_config()
155 }
156
157 fn network(&self) -> &Self::Network {
158 self.inner.network()
159 }
160
161 fn provider(&self) -> &Self::Provider {
162 self.inner.provider()
163 }
164}
165
166impl<N, Rpc> RpcNodeCoreExt for EthApi<N, Rpc>
167where
168 N: RpcNodeCore,
169 Rpc: RpcConvert,
170{
171 #[inline]
172 fn cache(&self) -> &EthStateCache<N::Primitives> {
173 self.inner.cache()
174 }
175}
176
177impl<N, Rpc> std::fmt::Debug for EthApi<N, Rpc>
178where
179 N: RpcNodeCore,
180 Rpc: RpcConvert,
181{
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 f.debug_struct("EthApi").finish_non_exhaustive()
184 }
185}
186
187impl<N, Rpc> SpawnBlocking for EthApi<N, Rpc>
188where
189 N: RpcNodeCore,
190 Rpc: RpcConvert<Error = EthApiError>,
191{
192 #[inline]
193 fn io_task_spawner(&self) -> &Runtime {
194 self.inner.task_spawner()
195 }
196
197 #[inline]
198 fn tracing_task_pool(&self) -> &BlockingTaskPool {
199 self.inner.blocking_task_pool()
200 }
201
202 #[inline]
203 fn tracing_task_guard(&self) -> &BlockingTaskGuard {
204 self.inner.blocking_task_guard()
205 }
206
207 #[inline]
208 fn blocking_io_task_guard(&self) -> &std::sync::Arc<tokio::sync::Semaphore> {
209 self.inner.blocking_io_request_semaphore()
210 }
211}
212
213#[expect(missing_debug_implementations)]
215pub struct EthApiInner<N: RpcNodeCore, Rpc: RpcConvert> {
216 components: N,
218 signers: SignersForRpc<N::Provider, Rpc::Network>,
220 eth_cache: EthStateCache<N::Primitives>,
222 gas_oracle: GasPriceOracle<N::Provider>,
224 settings: EthApiSettings,
226 starting_block: U256,
228 task_spawner: Runtime,
230 pending_block: Mutex<Option<PendingBlock<N::Primitives>>>,
232 blocking_task_pool: BlockingTaskPool,
234 fee_history_cache: FeeHistoryCache<ProviderHeader<N::Provider>>,
236
237 blocking_task_guard: BlockingTaskGuard,
239
240 blocking_io_request_semaphore: Arc<Semaphore>,
242
243 raw_tx_sender: broadcast::Sender<Bytes>,
245
246 raw_tx_forwarder: Option<RpcClient>,
248
249 converter: Rpc,
251
252 next_env_builder: Box<dyn PendingEnvBuilder<N::Evm>>,
254
255 tx_batch_sender:
257 mpsc::UnboundedSender<BatchTxRequest<<N::Pool as TransactionPool>::Transaction>>,
258
259 blob_sidecar_converter: BlobSidecarConverter,
261}
262
263impl<N, Rpc> EthApiInner<N, Rpc>
264where
265 N: RpcNodeCore,
266 Rpc: RpcConvert,
267{
268 #[expect(clippy::too_many_arguments)]
270 pub fn new(
271 components: N,
272 eth_cache: EthStateCache<N::Primitives>,
273 gas_oracle: GasPriceOracle<N::Provider>,
274 settings: EthApiSettings,
275 blocking_task_pool: BlockingTaskPool,
276 fee_history_cache: FeeHistoryCache<ProviderHeader<N::Provider>>,
277 task_spawner: Runtime,
278 converter: Rpc,
279 next_env: impl PendingEnvBuilder<N::Evm>,
280 raw_tx_forwarder: Option<RpcClient>,
281 ) -> Self {
282 let signers = parking_lot::RwLock::new(Default::default());
283 let starting_block = U256::from(
285 components
286 .provider()
287 .header_by_number_or_tag(BlockNumberOrTag::Latest)
288 .ok()
289 .flatten()
290 .map(|header| header.number())
291 .unwrap_or_default(),
292 );
293
294 let (raw_tx_sender, _) = broadcast::channel(DEFAULT_BROADCAST_CAPACITY);
295
296 let (processor, tx_batch_sender) =
298 BatchTxProcessor::new(components.pool().clone(), settings.max_batch_size);
299 task_spawner.spawn_critical_task("tx-batcher", processor);
300
301 Self {
302 components,
303 signers,
304 eth_cache,
305 gas_oracle,
306 starting_block,
307 task_spawner,
308 pending_block: Default::default(),
309 blocking_task_pool,
310 fee_history_cache,
311 blocking_task_guard: BlockingTaskGuard::new(settings.proof_permits),
312 blocking_io_request_semaphore: Arc::new(Semaphore::new(
313 settings.max_blocking_io_requests,
314 )),
315 settings,
316 raw_tx_sender,
317 raw_tx_forwarder,
318 converter,
319 next_env_builder: Box::new(next_env),
320 tx_batch_sender,
321 blob_sidecar_converter: BlobSidecarConverter::new(),
322 }
323 }
324}
325
326impl<N, Rpc> EthApiInner<N, Rpc>
327where
328 N: RpcNodeCore,
329 Rpc: RpcConvert,
330{
331 #[inline]
333 pub fn provider(&self) -> &N::Provider {
334 self.components.provider()
335 }
336
337 #[inline]
339 pub const fn converter(&self) -> &Rpc {
340 &self.converter
341 }
342
343 #[inline]
345 pub const fn cache(&self) -> &EthStateCache<N::Primitives> {
346 &self.eth_cache
347 }
348
349 #[inline]
351 pub const fn pending_block(&self) -> &Mutex<Option<PendingBlock<N::Primitives>>> {
352 &self.pending_block
353 }
354
355 #[inline]
358 pub const fn pending_env_builder(&self) -> &dyn PendingEnvBuilder<N::Evm> {
359 &*self.next_env_builder
360 }
361
362 #[inline]
364 pub const fn task_spawner(&self) -> &Runtime {
365 &self.task_spawner
366 }
367
368 #[inline]
372 pub const fn blocking_task_pool(&self) -> &BlockingTaskPool {
373 &self.blocking_task_pool
374 }
375
376 #[inline]
378 pub fn evm_config(&self) -> &N::Evm {
379 self.components.evm_config()
380 }
381
382 #[inline]
384 pub fn pool(&self) -> &N::Pool {
385 self.components.pool()
386 }
387
388 #[inline]
390 pub const fn eth_api_settings(&self) -> &EthApiSettings {
391 &self.settings
392 }
393
394 #[inline]
396 pub const fn gas_cap(&self) -> u64 {
397 self.settings.gas_cap
398 }
399
400 #[inline]
402 pub const fn max_simulate_blocks(&self) -> u64 {
403 self.settings.max_simulate_blocks
404 }
405
406 #[inline]
408 pub const fn compute_state_root_for_eth_simulate(&self) -> bool {
409 self.settings.compute_state_root_for_eth_simulate
410 }
411
412 #[inline]
414 pub const fn gas_oracle(&self) -> &GasPriceOracle<N::Provider> {
415 &self.gas_oracle
416 }
417
418 #[inline]
420 pub const fn fee_history_cache(&self) -> &FeeHistoryCache<ProviderHeader<N::Provider>> {
421 &self.fee_history_cache
422 }
423
424 #[inline]
426 pub const fn signers(&self) -> &SignersForRpc<N::Provider, Rpc::Network> {
427 &self.signers
428 }
429
430 #[inline]
432 pub const fn starting_block(&self) -> U256 {
433 self.starting_block
434 }
435
436 #[inline]
438 pub fn network(&self) -> &N::Network {
439 self.components.network()
440 }
441
442 #[inline]
444 pub const fn eth_proof_window(&self) -> u64 {
445 self.settings.eth_proof_window
446 }
447
448 #[inline]
450 pub const fn blocking_task_guard(&self) -> &BlockingTaskGuard {
451 &self.blocking_task_guard
452 }
453
454 #[inline]
456 pub fn subscribe_to_raw_transactions(&self) -> broadcast::Receiver<Bytes> {
457 self.raw_tx_sender.subscribe()
458 }
459
460 #[inline]
462 pub fn broadcast_raw_transaction(&self, raw_tx: Bytes) {
463 let _ = self.raw_tx_sender.send(raw_tx);
464 }
465
466 #[inline]
468 pub const fn tx_batch_sender(
469 &self,
470 ) -> &mpsc::UnboundedSender<BatchTxRequest<<N::Pool as TransactionPool>::Transaction>> {
471 &self.tx_batch_sender
472 }
473
474 #[inline]
476 pub async fn add_pool_transaction(
477 &self,
478 origin: reth_transaction_pool::TransactionOrigin,
479 transaction: <N::Pool as TransactionPool>::Transaction,
480 ) -> Result<AddedTransactionOutcome, EthApiError> {
481 let (response_tx, response_rx) = tokio::sync::oneshot::channel();
482 let request = reth_transaction_pool::BatchTxRequest::new(origin, transaction, response_tx);
483
484 self.tx_batch_sender()
485 .send(request)
486 .map_err(|_| reth_rpc_eth_types::EthApiError::BatchTxSendError)?;
487
488 Ok(response_rx.await??)
489 }
490
491 #[inline]
493 pub const fn pending_block_kind(&self) -> PendingBlockKind {
494 self.settings.pending_block_kind
495 }
496
497 #[inline]
499 pub const fn raw_tx_forwarder(&self) -> Option<&RpcClient> {
500 self.raw_tx_forwarder.as_ref()
501 }
502
503 #[inline]
505 pub const fn send_raw_transaction_sync_timeout(&self) -> Duration {
506 self.settings.send_raw_transaction_sync_timeout
507 }
508
509 #[inline]
511 pub const fn blob_sidecar_converter(&self) -> &BlobSidecarConverter {
512 &self.blob_sidecar_converter
513 }
514
515 #[inline]
517 pub const fn evm_memory_limit(&self) -> u64 {
518 self.settings.evm_memory_limit
519 }
520
521 #[inline]
523 pub const fn blocking_io_request_semaphore(&self) -> &Arc<Semaphore> {
524 &self.blocking_io_request_semaphore
525 }
526
527 #[inline]
529 pub const fn force_blob_sidecar_upcasting(&self) -> bool {
530 self.settings.force_blob_sidecar_upcasting
531 }
532}
533
534#[cfg(test)]
535mod tests {
536 use super::*;
537 use alloy_consensus::{Block, BlockBody, Header};
538 use alloy_eips::{
539 eip4844::DATA_GAS_PER_BLOB, eip7702::Authorization, BlockId, BlockNumberOrTag,
540 };
541 use alloy_genesis::{Genesis, GenesisAccount};
542 use alloy_network::TransactionBuilder;
543 use alloy_primitives::{Address, Bytes, Signature, B256, U256, U64};
544 use alloy_rpc_types::FeeHistory;
545 use alloy_rpc_types_eth::{
546 simulate::{SimBlock, SimulatePayload},
547 state::{AccountOverride, EvmOverrides, StateOverride},
548 BlockOverrides, Bundle, TransactionRequest,
549 };
550 use alloy_rpc_types_trace::{parity::TraceType, tracerequest::TraceCallRequest};
551 use evm2::{evm::CacheDB, AccountInfo};
552 use jsonrpsee_types::error::INVALID_PARAMS_CODE;
553 use rand::Rng;
554 use reth_chain_state::CanonStateSubscriptions;
555 use reth_chainspec::{ChainSpec, ChainSpecBuilder, ChainSpecProvider, EthChainSpec};
556 use reth_db_common::init::init_genesis;
557 use reth_ethereum_primitives::TransactionSigned;
558 use reth_evm::{ConfigureEvm, EvmEnvFor, TxEnvFor};
559 use reth_evm_ethereum::EthEvmConfig;
560 use reth_network_api::noop::NoopNetwork;
561 use reth_primitives_traits::{
562 crypto::secp256k1::sign_message, SealedHeader, SignerRecoverable,
563 };
564 use reth_provider::{
565 providers::BlockchainProvider,
566 test_utils::{
567 create_test_provider_factory_with_chain_spec, ExtendedAccount, MockEthProvider,
568 NoopProvider,
569 },
570 PruneCheckpointReader, StageCheckpointReader,
571 };
572 use reth_rpc_convert::{EthTxEnvError, TryIntoTxEnv};
573 use reth_rpc_eth_api::{
574 helpers::{Call, EthBlocks, EthCall, EthTransactions, LoadPendingBlock, SpawnBlocking},
575 node::RpcNodeCoreAdapter,
576 EthApiServer,
577 };
578 use reth_rpc_eth_types::{receipt::EthReceiptConverter, EthConfig, RpcInvalidTransactionError};
579 use reth_rpc_server_types::constants::DEFAULT_MAX_CALL_MANY_CALLS;
580 use reth_storage_api::{
581 BalProvider, BlockReaderIdExt, NodePrimitivesProvider, StateProviderFactory,
582 };
583 use reth_tasks::cancel::{is_cancelled, CancelOnDrop};
584 use reth_testing_utils::generators;
585 use reth_transaction_pool::test_utils::{testing_pool, MockTransaction, TestPool};
586 use std::time::{Duration, Instant};
587
588 type FakeEthApi<P = MockEthProvider> = EthApi<
589 RpcNodeCoreAdapter<P, TestPool, NoopNetwork, EthEvmConfig>,
590 EthRpcConverter<ChainSpec>,
591 >;
592
593 fn build_test_eth_api<
594 P: BlockReaderIdExt<
595 Block = reth_ethereum_primitives::Block,
596 Receipt = reth_ethereum_primitives::Receipt,
597 Header = alloy_consensus::Header,
598 Transaction = reth_ethereum_primitives::TransactionSigned,
599 > + ChainSpecProvider<ChainSpec = ChainSpec>
600 + StateProviderFactory<Primitives = reth_ethereum_primitives::EthPrimitives>
601 + NodePrimitivesProvider<Primitives = reth_ethereum_primitives::EthPrimitives>
602 + CanonStateSubscriptions<Primitives = reth_ethereum_primitives::EthPrimitives>
603 + StageCheckpointReader
604 + PruneCheckpointReader
605 + BalProvider
606 + Unpin
607 + Clone
608 + 'static,
609 >(
610 provider: P,
611 ) -> FakeEthApi<P> {
612 EthApiBuilder::new(
613 provider.clone(),
614 testing_pool(),
615 NoopNetwork::default(),
616 EthEvmConfig::new(provider.chain_spec()),
617 )
618 .build()
619 }
620
621 fn build_test_eth_api_with_gas_cap(provider: MockEthProvider, gas_cap: u64) -> FakeEthApi {
622 EthApiBuilder::new(
623 provider.clone(),
624 testing_pool(),
625 NoopNetwork::default(),
626 EthEvmConfig::new(provider.chain_spec()),
627 )
628 .gas_cap(gas_cap.into())
629 .build()
630 }
631
632 #[tokio::test(flavor = "multi_thread")]
633 async fn blocking_task_observes_dropped_request() {
634 let api = build_test_eth_api(MockEthProvider::default());
635 let (started_tx, started_rx) = tokio::sync::oneshot::channel();
636 let (cancelled_tx, cancelled_rx) = tokio::sync::oneshot::channel();
637
638 let request = api.spawn_blocking_io(move |_| {
639 let _ = started_tx.send(());
640 let deadline = Instant::now() + Duration::from_secs(5);
641 while !is_cancelled() && Instant::now() < deadline {
642 std::thread::sleep(Duration::from_millis(1));
643 }
644 let _ = cancelled_tx.send(is_cancelled());
645 Ok(())
646 });
647 tokio::select! {
649 _ = request => panic!("blocking task completed before the request was dropped"),
650 _ = started_rx => {}
651 }
652
653 assert!(cancelled_rx.await.unwrap());
654 }
655
656 #[tokio::test(flavor = "multi_thread")]
657 async fn pending_block_build_stops_when_cancelled() {
658 let provider = MockEthProvider::default();
659 let parent =
660 Header { gas_limit: 30_000_000, base_fee_per_gas: Some(1), ..Default::default() };
661 let parent_hash = B256::random();
662 provider.add_header(parent_hash, parent.clone());
663 let api = build_test_eth_api(provider);
664 api.pool().add_external_transaction(MockTransaction::eip1559()).await.unwrap();
665
666 let request = CancelOnDrop::default();
668 let scope = request.clone();
669 drop(request);
670
671 let parent = SealedHeader::new(parent, parent_hash);
672 let err = scope.scope(|| api.build_block(&parent)).unwrap_err();
673 assert!(matches!(err, EthApiError::InternalEthError));
674 }
675
676 #[tokio::test]
677 async fn test_transaction_by_sender_and_nonce_without_sender_transaction_returns_none() {
678 let provider = MockEthProvider::default();
679 let sender = Address::random();
680 provider.add_account(sender, ExtendedAccount::new(1, U256::ZERO));
681
682 let block_hash = B256::random();
683 provider.add_block(
684 block_hash,
685 Block {
686 header: Header { number: 1, ..Default::default() },
687 body: BlockBody::default(),
688 },
689 );
690
691 let api = build_test_eth_api(provider);
692 let transaction =
693 EthTransactions::get_transaction_by_sender_and_nonce(&api, sender, 0, false)
694 .await
695 .expect("existing block without a sender transaction should not be a header error");
696
697 assert!(transaction.is_none());
698 }
699
700 #[tokio::test]
701 async fn test_transaction_by_sender_and_nonce_missing_block_returns_error() {
702 use reth_rpc_eth_types::EthApiError;
703
704 let provider = MockEthProvider::default();
705 let sender = Address::random();
706 provider.add_account(sender, ExtendedAccount::new(1, U256::ZERO));
707 provider.add_header(B256::random(), Header { number: 1, ..Default::default() });
708
709 let api = build_test_eth_api(provider);
710 let error = EthTransactions::get_transaction_by_sender_and_nonce(&api, sender, 0, false)
711 .await
712 .unwrap_err();
713
714 assert!(matches!(error, EthApiError::HeaderNotFound(id) if id == 1.into()));
715 }
716
717 #[tokio::test]
718 async fn test_transaction_by_sender_and_nonce_returns_matching_transaction() {
719 let provider = MockEthProvider::default();
720 let tx = TransactionSigned::new_unhashed(
721 reth_ethereum_primitives::Transaction::Legacy(Default::default()),
722 Signature::test_signature(),
723 );
724 let sender = tx.recover_signer().unwrap();
725 provider.add_account(sender, ExtendedAccount::new(1, U256::ZERO));
726 let block = Block {
727 header: Header { number: 1, ..Default::default() },
728 body: BlockBody { transactions: vec![tx], ..Default::default() },
729 };
730 let block_hash = block.header.hash_slow();
731 provider.add_block(block_hash, block);
732
733 let api = build_test_eth_api(provider);
734 let transaction =
735 EthTransactions::get_transaction_by_sender_and_nonce(&api, sender, 0, false)
736 .await
737 .unwrap()
738 .expect("matching transaction should be returned");
739
740 assert_eq!(transaction.block_hash, Some(block_hash));
741 assert_eq!(transaction.block_number, Some(1));
742 assert_eq!(transaction.transaction_index, Some(0));
743 assert_eq!(transaction.inner.signer(), sender);
744 }
745
746 #[tokio::test]
747 async fn test_eth_api_settings() {
748 use reth_rpc_eth_api::{
749 helpers::{Call, EthState, EthTransactions, LoadPendingBlock},
750 EthApiTypes,
751 };
752 use reth_rpc_eth_types::{builder::config::PendingBlockKind, EthApiSettings};
753 use std::time::Duration;
754
755 fn settings(api: &impl EthApiTypes) -> &EthApiSettings {
756 api.eth_api_settings()
757 }
758
759 fn assert_settings(actual: &EthApiSettings, expected: &EthApiSettings) {
760 assert_eq!(actual.proof_permits, expected.proof_permits);
761 assert_eq!(actual.max_batch_size, expected.max_batch_size);
762 assert_eq!(actual.max_blocking_io_requests, expected.max_blocking_io_requests);
763 assert_eq!(actual.cache_computed_bals, expected.cache_computed_bals);
764 assert_eq!(actual.gas_cap, expected.gas_cap);
765 assert_eq!(actual.max_simulate_blocks, expected.max_simulate_blocks);
766 assert_eq!(
767 actual.compute_state_root_for_eth_simulate,
768 expected.compute_state_root_for_eth_simulate
769 );
770 assert_eq!(actual.eth_proof_window, expected.eth_proof_window);
771 assert_eq!(actual.pending_block_kind, expected.pending_block_kind);
772 assert_eq!(
773 actual.send_raw_transaction_sync_timeout,
774 expected.send_raw_transaction_sync_timeout
775 );
776 assert_eq!(actual.evm_memory_limit, expected.evm_memory_limit);
777 assert_eq!(actual.force_blob_sidecar_upcasting, expected.force_blob_sidecar_upcasting);
778 assert_eq!(
779 actual.sender_recovery_cache.is_some(),
780 expected.sender_recovery_cache.is_some()
781 );
782 }
783
784 let default_api = build_test_eth_api(MockEthProvider::default());
785 assert_settings(settings(&default_api), &EthApiSettings::default());
786
787 let expected = EthApiSettings {
788 sender_recovery_cache: Some(reth_evm::SenderRecoveryCache::new(16)),
789 proof_permits: 3,
790 max_batch_size: 5,
791 max_blocking_io_requests: 7,
792 cache_computed_bals: true,
793 gas_cap: 123_456,
794 max_simulate_blocks: 7,
795 compute_state_root_for_eth_simulate: true,
796 eth_proof_window: 42,
797 pending_block_kind: PendingBlockKind::Empty,
798 send_raw_transaction_sync_timeout: Duration::from_secs(9),
799 evm_memory_limit: 1 << 20,
800 force_blob_sidecar_upcasting: true,
801 };
802 let api = EthApiBuilder::new(
803 MockEthProvider::default(),
804 testing_pool(),
805 NoopNetwork::default(),
806 EthEvmConfig::mainnet(),
807 )
808 .eth_state_cache_config(reth_rpc_eth_types::EthStateCacheConfig {
809 cache_computed_bals: true,
810 ..Default::default()
811 })
812 .sender_recovery_cache(expected.sender_recovery_cache.clone())
813 .proof_permits(expected.proof_permits)
814 .max_batch_size(expected.max_batch_size)
815 .max_blocking_io_requests(expected.max_blocking_io_requests)
816 .gas_cap(expected.gas_cap.into())
817 .max_simulate_blocks(expected.max_simulate_blocks)
818 .compute_state_root_for_eth_simulate(expected.compute_state_root_for_eth_simulate)
819 .eth_proof_window(expected.eth_proof_window)
820 .pending_block_kind(expected.pending_block_kind)
821 .send_raw_transaction_sync_timeout(expected.send_raw_transaction_sync_timeout)
822 .evm_memory_limit(expected.evm_memory_limit)
823 .force_blob_sidecar_upcasting(expected.force_blob_sidecar_upcasting)
824 .build();
825
826 assert_settings(settings(&api), &expected);
827 assert_eq!(
828 api.inner.blocking_io_request_semaphore().available_permits(),
829 expected.max_blocking_io_requests
830 );
831 assert_eq!(Call::call_gas_limit(&api), expected.gas_cap);
832 assert_eq!(Call::max_simulate_blocks(&api), expected.max_simulate_blocks);
833 assert_eq!(
834 Call::compute_state_root_for_eth_simulate(&api),
835 expected.compute_state_root_for_eth_simulate
836 );
837 assert_eq!(Call::evm_memory_limit(&api), expected.evm_memory_limit);
838 assert_eq!(EthState::max_proof_window(&api), expected.eth_proof_window);
839 assert_eq!(LoadPendingBlock::pending_block_kind(&api), expected.pending_block_kind);
840 assert_eq!(
841 EthTransactions::send_raw_transaction_sync_timeout(&api),
842 expected.send_raw_transaction_sync_timeout
843 );
844 assert_eq!(api.inner.gas_cap(), expected.gas_cap);
845 assert_eq!(api.inner.max_simulate_blocks(), expected.max_simulate_blocks);
846 assert_eq!(
847 api.inner.compute_state_root_for_eth_simulate(),
848 expected.compute_state_root_for_eth_simulate
849 );
850 assert_eq!(api.inner.eth_proof_window(), expected.eth_proof_window);
851 assert_eq!(api.inner.pending_block_kind(), expected.pending_block_kind);
852 assert_eq!(
853 api.inner.send_raw_transaction_sync_timeout(),
854 expected.send_raw_transaction_sync_timeout
855 );
856 assert_eq!(api.inner.evm_memory_limit(), expected.evm_memory_limit);
857 assert_eq!(api.inner.force_blob_sidecar_upcasting(), expected.force_blob_sidecar_upcasting);
858 assert!(std::ptr::eq(settings(&api), settings(&api.clone())));
859 }
860
861 fn prepare_eth_api(
863 newest_block: u64,
864 mut oldest_block: Option<B256>,
865 block_count: u64,
866 mock_provider: MockEthProvider,
867 ) -> (FakeEthApi, Vec<u128>, Vec<f64>) {
868 let mut rng = generators::rng();
869
870 let mut gas_used_ratios = Vec::with_capacity(block_count as usize);
872 let mut base_fees_per_gas = Vec::with_capacity(block_count as usize);
873 let mut last_header = None;
874 let mut parent_hash = B256::default();
875
876 for i in (0..block_count).rev() {
877 let hash = rng.random();
878 let gas_limit = rng.random::<u32>() as u64;
880 let base_fee_per_gas: Option<u64> =
881 rng.random::<bool>().then(|| rng.random::<u32>() as u64);
882 let gas_used = rng.random::<u32>() as u64;
883
884 let header = Header {
885 number: newest_block - i,
886 gas_limit,
887 gas_used,
888 base_fee_per_gas,
889 parent_hash,
890 ..Default::default()
891 };
892 last_header = Some(header.clone());
893 parent_hash = hash;
894
895 const TOTAL_TRANSACTIONS: usize = 100;
896 let mut transactions = Vec::with_capacity(TOTAL_TRANSACTIONS);
897 for _ in 0..TOTAL_TRANSACTIONS {
898 let random_fee: u128 = rng.random();
899
900 if let Some(base_fee_per_gas) = header.base_fee_per_gas {
901 let transaction = TransactionSigned::new_unhashed(
902 reth_ethereum_primitives::Transaction::Eip1559(
903 alloy_consensus::TxEip1559 {
904 max_priority_fee_per_gas: random_fee,
905 max_fee_per_gas: random_fee + base_fee_per_gas as u128,
906 ..Default::default()
907 },
908 ),
909 Signature::test_signature(),
910 );
911
912 transactions.push(transaction);
913 } else {
914 let transaction = TransactionSigned::new_unhashed(
915 reth_ethereum_primitives::Transaction::Legacy(Default::default()),
916 Signature::test_signature(),
917 );
918
919 transactions.push(transaction);
920 }
921 }
922
923 mock_provider.add_block(
924 hash,
925 Block {
926 header: header.clone(),
927 body: BlockBody { transactions, ..Default::default() },
928 },
929 );
930 mock_provider.add_header(hash, header);
931
932 oldest_block.get_or_insert(hash);
933 gas_used_ratios.push(gas_used as f64 / gas_limit as f64);
934 base_fees_per_gas.push(base_fee_per_gas.map(|fee| fee as u128).unwrap_or_default());
935 }
936
937 let last_header = last_header.unwrap();
939 let spec = mock_provider.chain_spec();
940 base_fees_per_gas.push(
941 spec.next_block_base_fee(&last_header, last_header.timestamp).unwrap_or_default()
942 as u128,
943 );
944
945 let eth_api = build_test_eth_api(mock_provider);
946
947 (eth_api, base_fees_per_gas, gas_used_ratios)
948 }
949
950 #[tokio::test]
952 async fn test_fee_history_empty() {
953 let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::fee_history(
954 &build_test_eth_api(NoopProvider::default()),
955 U64::from(1),
956 BlockNumberOrTag::Latest,
957 None,
958 )
959 .await;
960 assert!(response.is_err());
961 let error_object = response.unwrap_err();
962 assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
963 }
964
965 #[tokio::test]
966 async fn test_fee_history_invalid_block_range_before_genesis() {
968 let block_count = 10;
969 let newest_block = 1337;
970 let oldest_block = None;
971
972 let (eth_api, _, _) =
973 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
974
975 let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::fee_history(
976 ð_api,
977 U64::from(newest_block + 1),
978 newest_block.into(),
979 Some(vec![10.0]),
980 )
981 .await;
982
983 assert!(response.is_err());
984 let error_object = response.unwrap_err();
985 assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
986 }
987
988 #[tokio::test]
989 async fn test_fee_history_invalid_block_range_in_future() {
991 let block_count = 10;
992 let newest_block = 1337;
993 let oldest_block = None;
994
995 let (eth_api, _, _) =
996 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
997
998 let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::fee_history(
999 ð_api,
1000 U64::from(1),
1001 (newest_block + 1000).into(),
1002 Some(vec![10.0]),
1003 )
1004 .await;
1005
1006 assert!(response.is_err());
1007 let error_object = response.unwrap_err();
1008 assert_eq!(error_object.code(), INVALID_PARAMS_CODE);
1009 }
1010
1011 #[tokio::test]
1012 async fn test_call_many_maps_provider_block_lookup_error_with_eth_api_conversion() {
1013 let eth_api = build_test_eth_api(MockEthProvider::default());
1014 let bundles = vec![Bundle {
1015 transactions: vec![TransactionRequest::default()],
1016 block_override: None,
1017 }];
1018
1019 let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::call_many(
1020 ð_api, bundles, None, None,
1021 )
1022 .await;
1023
1024 let err = response.expect_err("call_many should fail when latest block lookup errors");
1025 let message = err.message().to_ascii_lowercase();
1026 assert!(
1027 message.contains("block not found"),
1028 "best block lookup should map via EthApiError::from(ProviderError): {message}"
1029 );
1030 assert!(
1031 !message.contains("best block does not exist"),
1032 "provider implementation detail should not leak from converted error: {message}"
1033 );
1034 }
1035
1036 #[tokio::test]
1037 async fn test_call_many_keeps_header_not_found_when_block_hash_absent() {
1038 let eth_api = build_test_eth_api(NoopProvider::default());
1039 let bundles = vec![Bundle {
1040 transactions: vec![TransactionRequest::default()],
1041 block_override: None,
1042 }];
1043
1044 let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::call_many(
1045 ð_api, bundles, None, None,
1046 )
1047 .await;
1048
1049 let err =
1050 response.expect_err("call_many should fail when latest block hash is unavailable");
1051 let message = err.message().to_ascii_lowercase();
1052 assert!(
1053 message.contains("block not found"),
1054 "missing block hash should still map to block-not-found: {message}"
1055 );
1056 }
1057
1058 #[tokio::test]
1059 async fn call_many_methods_limit_calls_and_share_gas_cap() {
1060 let provider = MockEthProvider::default();
1061 let header = Header { number: 1, gas_limit: 30_000_000, ..Default::default() };
1062 provider.add_block(header.hash_slow(), Block { header, body: BlockBody::default() });
1063 let looping = Address::with_last_byte(0x43);
1065 provider.add_account(
1066 looping,
1067 ExtendedAccount::new(0, U256::ZERO).with_bytecode(alloy_primitives::bytes!("5b600056")),
1068 );
1069 let api = build_test_eth_api_with_gas_cap(provider, 50_000);
1072 let trace_api =
1073 crate::TraceApi::new(api.clone(), BlockingTaskGuard::new(1), EthConfig::default());
1074 let debug_api = crate::DebugApi::new(
1075 api.clone(),
1076 BlockingTaskGuard::new(1),
1077 &Runtime::test(),
1078 futures::stream::empty(),
1079 );
1080 let transfer = TransactionRequest::default().to(Address::with_last_byte(0x42));
1081 let transfers = |calls| vec![transfer.clone(); calls];
1082 let transfer_and_loop = vec![transfer.clone(), TransactionRequest::default().to(looping)];
1083 let bundles = |calls: Vec<TransactionRequest>| {
1084 vec![Bundle { transactions: calls, block_override: None }]
1085 };
1086 let trace_calls = |calls: Vec<TransactionRequest>| {
1087 calls
1088 .into_iter()
1089 .map(|call| (call, std::iter::once(TraceType::Trace).collect()))
1090 .collect::<Vec<_>>()
1091 };
1092
1093 assert_eq!(
1094 EthCall::call_many(&api, bundles(transfers(2)), None, None).await.unwrap()[0].len(),
1095 2
1096 );
1097 assert_eq!(
1098 trace_api.trace_call_many(trace_calls(transfers(2)), None).await.unwrap().len(),
1099 2
1100 );
1101 assert_eq!(
1102 debug_api.debug_trace_call_many(bundles(transfers(2)), None, None).await.unwrap()[0]
1103 .len(),
1104 2
1105 );
1106
1107 let gas_cap_err = "the request's calls exceed the RPC gas cap of 50000";
1108 for (calls, tx_index) in [(transfers(3), 2), (transfer_and_loop, 1)] {
1109 assert_eq!(
1110 EthCall::call_many(&api, bundles(calls.clone()), None, None)
1111 .await
1112 .unwrap_err()
1113 .to_string(),
1114 format!("call_many error in bundle 0 and transaction {tx_index}: {gas_cap_err}")
1115 );
1116 assert_eq!(
1117 trace_api
1118 .trace_call_many(trace_calls(calls.clone()), None)
1119 .await
1120 .unwrap_err()
1121 .to_string(),
1122 gas_cap_err
1123 );
1124 assert_eq!(
1125 debug_api
1126 .debug_trace_call_many(bundles(calls), None, None)
1127 .await
1128 .unwrap_err()
1129 .to_string(),
1130 gas_cap_err
1131 );
1132 }
1133
1134 let too_many = transfers(DEFAULT_MAX_CALL_MANY_CALLS + 1);
1135 let limit_err = "call count 1025 exceeds limit 1024";
1136 assert_eq!(
1137 EthCall::call_many(&api, bundles(too_many.clone()), None, None)
1138 .await
1139 .unwrap_err()
1140 .to_string(),
1141 limit_err
1142 );
1143 assert_eq!(
1144 trace_api
1145 .trace_call_many(trace_calls(too_many.clone()), None)
1146 .await
1147 .unwrap_err()
1148 .to_string(),
1149 limit_err
1150 );
1151 assert_eq!(
1152 debug_api
1153 .debug_trace_call_many(bundles(too_many), None, None)
1154 .await
1155 .unwrap_err()
1156 .to_string(),
1157 limit_err
1158 );
1159 }
1160
1161 #[tokio::test]
1162 async fn test_fee_history_no_block_requested() {
1164 let block_count = 10;
1165 let newest_block = 1337;
1166 let oldest_block = None;
1167
1168 let (eth_api, _, _) =
1169 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
1170
1171 let response = <EthApi<_, _> as EthApiServer<_, _, _, _, _, _>>::fee_history(
1172 ð_api,
1173 U64::from(0),
1174 newest_block.into(),
1175 None,
1176 )
1177 .await
1178 .unwrap();
1179 assert_eq!(
1180 response,
1181 FeeHistory::default(),
1182 "none: requesting no block should yield a default response"
1183 );
1184 }
1185
1186 #[tokio::test]
1187 async fn test_fee_history_single_block() {
1189 let block_count = 10;
1190 let newest_block = 1337;
1191 let oldest_block = None;
1192
1193 let (eth_api, base_fees_per_gas, gas_used_ratios) =
1194 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
1195
1196 let fee_history =
1197 eth_api.fee_history(U64::from(1), newest_block.into(), None).await.unwrap();
1198 assert_eq!(
1199 fee_history.base_fee_per_gas,
1200 &base_fees_per_gas[base_fees_per_gas.len() - 2..],
1201 "one: base fee per gas is incorrect"
1202 );
1203 assert_eq!(
1204 fee_history.base_fee_per_gas.len(),
1205 2,
1206 "one: should return base fee of the next block as well"
1207 );
1208 assert_eq!(
1209 &fee_history.gas_used_ratio,
1210 &gas_used_ratios[gas_used_ratios.len() - 1..],
1211 "one: gas used ratio is incorrect"
1212 );
1213 assert_eq!(fee_history.oldest_block, newest_block, "one: oldest block is incorrect");
1214 assert!(
1215 fee_history.reward.is_none(),
1216 "one: no percentiles were requested, so there should be no rewards result"
1217 );
1218 }
1219
1220 #[tokio::test]
1222 async fn test_fee_history_all_blocks() {
1223 let block_count = 10;
1224 let newest_block = 1337;
1225 let oldest_block = None;
1226
1227 let (eth_api, base_fees_per_gas, gas_used_ratios) =
1228 prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
1229
1230 let fee_history =
1231 eth_api.fee_history(U64::from(block_count), newest_block.into(), None).await.unwrap();
1232
1233 assert_eq!(
1234 &fee_history.base_fee_per_gas, &base_fees_per_gas,
1235 "all: base fee per gas is incorrect"
1236 );
1237 assert_eq!(
1238 fee_history.base_fee_per_gas.len() as u64,
1239 block_count + 1,
1240 "all: should return base fee of the next block as well"
1241 );
1242 assert_eq!(
1243 &fee_history.gas_used_ratio, &gas_used_ratios,
1244 "all: gas used ratio is incorrect"
1245 );
1246 assert_eq!(
1247 fee_history.oldest_block,
1248 newest_block - block_count + 1,
1249 "all: oldest block is incorrect"
1250 );
1251 assert!(
1252 fee_history.reward.is_none(),
1253 "all: no percentiles were requested, so there should be no rewards result"
1254 );
1255 }
1256
1257 #[tokio::test]
1258 async fn call_allowance_respects_rpc_gas_cap() {
1259 use reth_tasks::pool::BlockingTaskGuard;
1260
1261 let sender = Address::repeat_byte(0x11);
1262 let contract = Address::repeat_byte(0xaa);
1263 let code = Bytes::from_static(&[
1265 0x5a, 0x60, 0x00, 0x52, 0x33, 0x31, 0x60, 0x20, 0x52, 0x60, 0x40, 0x60, 0x00, 0xf3,
1266 ]);
1267
1268 for (gas_cap, gas, price, balance, block_limit, budget) in [
1270 (50_000, None, 1, 100_000, 100_000, 50_000),
1271 (50_000, None, 0, 100_000, 100_000, 50_000),
1272 (50_000, None, 1, 40_000, 100_000, 40_000),
1273 (50_000, None, 1, 100_000, 40_000, 40_000),
1274 (50_000, Some(70_000), 1, 100_000, 100_000, 50_000),
1275 (50_000, Some(30_000), 1, 100_000, 100_000, 30_000),
1276 (50_000, Some(70_000), 1, 100_000, 40_000, 50_000),
1277 (50_000, Some(30_000), 1, 0, 100_000, 30_000),
1278 (50_000, Some(30_000), 0, 0, 100_000, 30_000),
1279 (0, None, 1, 100_000, 100_000, 100_000),
1280 (0, Some(70_000), 1, 100_000, 40_000, 70_000),
1281 ] {
1282 let provider = MockEthProvider::default()
1283 .with_chain_spec(ChainSpecBuilder::mainnet().cancun_activated().build());
1284 provider.add_block(
1285 B256::repeat_byte(0x42),
1286 Block {
1287 header: Header {
1288 number: 1,
1289 gas_limit: block_limit,
1290 excess_blob_gas: Some(0),
1291 ..Default::default()
1292 },
1293 body: BlockBody::default(),
1294 },
1295 );
1296 let api = build_test_eth_api_with_gas_cap(provider, gas_cap);
1297 let trace_api =
1298 crate::TraceApi::new(api.clone(), BlockingTaskGuard::new(1), EthConfig::default());
1299 let state_override = StateOverride::from_iter([
1300 (
1301 sender,
1302 AccountOverride {
1303 balance: Some(U256::from(balance + 1_000)),
1304 ..Default::default()
1305 },
1306 ),
1307 (contract, AccountOverride { code: Some(code.clone()), ..Default::default() }),
1308 ]);
1309
1310 for dynamic_fee in [false, true] {
1311 let request = TransactionRequest {
1312 gas,
1313 gas_price: (!dynamic_fee).then_some(price),
1314 max_fee_per_gas: dynamic_fee.then_some(price),
1315 max_priority_fee_per_gas: dynamic_fee.then_some(price),
1316 value: Some(U256::from(1_000)),
1317 ..TransactionRequest::default().with_from(sender).with_to(contract)
1318 };
1319 let output = EthCall::call(
1320 &api,
1321 request.clone(),
1322 Some(BlockId::latest()),
1323 EvmOverrides::state(Some(state_override.clone())),
1324 )
1325 .await
1326 .unwrap();
1327 let traces = trace_api
1328 .trace_call(TraceCallRequest {
1329 call: request,
1330 trace_types: std::iter::once(TraceType::Trace).collect(),
1331 block_id: Some(BlockId::latest()),
1332 state_overrides: Some(state_override.clone()),
1333 block_overrides: None,
1334 })
1335 .await
1336 .unwrap();
1337
1338 assert_eq!(U256::from_be_slice(&output[..32]), U256::from(budget - 21_002));
1339 assert_eq!(U256::from_be_slice(&output[32..]), U256::from(balance));
1340 assert_eq!(traces.output, output);
1341 }
1342 }
1343 }
1344
1345 #[tokio::test]
1346 async fn call_converts_request_once_as_sent() {
1347 static CONVERTED_GAS: std::sync::Mutex<Vec<Option<u64>>> =
1348 std::sync::Mutex::new(Vec::new());
1349
1350 fn count_conversion(
1353 request: TransactionRequest,
1354 evm_env: &EvmEnvFor<EthEvmConfig>,
1355 ) -> Result<TxEnvFor<EthEvmConfig>, EthTxEnvError> {
1356 CONVERTED_GAS.lock().unwrap().push(request.gas);
1357 request.try_into_tx_env(evm_env)
1358 }
1359
1360 let sender = Address::repeat_byte(0x11);
1361 let contract = Address::repeat_byte(0xaa);
1362 let code = Bytes::from_static(&[0x5a, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3]);
1364 let provider = MockEthProvider::default()
1365 .with_chain_spec(ChainSpecBuilder::mainnet().cancun_activated().build());
1366 provider.add_block(
1367 B256::repeat_byte(0x42),
1368 Block {
1369 header: Header {
1370 number: 1,
1371 gas_limit: 100_000,
1372 excess_blob_gas: Some(0),
1373 ..Default::default()
1374 },
1375 body: BlockBody::default(),
1376 },
1377 );
1378 let api = EthApiBuilder::new(
1379 provider.clone(),
1380 testing_pool(),
1381 NoopNetwork::default(),
1382 EthEvmConfig::new(provider.chain_spec()),
1383 )
1384 .with_rpc_converter(
1385 EthRpcConverter::new(EthReceiptConverter::new(provider.chain_spec()))
1386 .with_tx_env_converter(
1387 count_conversion
1388 as fn(
1389 TransactionRequest,
1390 &EvmEnvFor<EthEvmConfig>,
1391 )
1392 -> Result<TxEnvFor<EthEvmConfig>, EthTxEnvError>,
1393 ),
1394 )
1395 .gas_cap(50_000u64.into())
1396 .build();
1397 let state_override = StateOverride::from_iter([
1398 (sender, AccountOverride { balance: Some(U256::from(101_000)), ..Default::default() }),
1399 (contract, AccountOverride { code: Some(code), ..Default::default() }),
1400 ]);
1401 for (gas, expected_gas) in [(None, 50_000), (Some(70_000), 50_000), (Some(30_000), 30_000)]
1403 {
1404 let request = TransactionRequest {
1405 gas,
1406 gas_price: Some(1),
1407 value: Some(U256::from(1_000)),
1408 ..TransactionRequest::default().with_from(sender).with_to(contract)
1409 };
1410 CONVERTED_GAS.lock().unwrap().clear();
1411
1412 let output = EthCall::call(
1413 &api,
1414 request,
1415 Some(BlockId::latest()),
1416 EvmOverrides::state(Some(state_override.clone())),
1417 )
1418 .await
1419 .unwrap();
1420
1421 assert_eq!(U256::from_be_slice(&output), U256::from(expected_gas - 21_002));
1422 assert_eq!(*CONVERTED_GAS.lock().unwrap(), [gas]);
1423 }
1424 }
1425
1426 #[test]
1427 fn execution_errors_distinguish_reverts_from_failed_calls() {
1428 for stop in [
1429 evm2::interpreter::InstrStop::OutOfFunds,
1430 evm2::interpreter::InstrStop::CallTooDeep,
1431 evm2::interpreter::InstrStop::Revert,
1432 ] {
1433 let output = Bytes::from_static(b"revert payload");
1434 let result = evm2::TxResult::<evm2::BaseEvmTypes> {
1435 status: false,
1436 stop,
1437 output: output.clone(),
1438 ..Default::default()
1439 };
1440 let err = <EthApiError as reth_rpc_eth_types::error::FromEvmError<EthEvmConfig>>::ensure_success::<evm2::BaseEvmTypes>(result)
1441 .unwrap_err();
1442 match err.as_invalid_transaction().unwrap() {
1443 RpcInvalidTransactionError::Revert(_) => {
1444 assert_eq!(stop, evm2::interpreter::InstrStop::Revert);
1445 }
1446 RpcInvalidTransactionError::EvmHalt(reason) => {
1447 assert_ne!(stop, evm2::interpreter::InstrStop::Revert);
1448 assert_eq!(reason, &format!("{stop:?}"));
1449 }
1450 other => panic!("unexpected error: {other}"),
1451 }
1452 let rpc: jsonrpsee::types::ErrorObjectOwned = err.into();
1453 if stop == evm2::interpreter::InstrStop::Revert {
1454 assert_eq!(rpc.code(), 3);
1455 assert_eq!(
1456 serde_json::from_str::<Bytes>(rpc.data().unwrap().get()).unwrap(),
1457 output
1458 );
1459 } else {
1460 assert_eq!(rpc.code(), -32003);
1461 assert!(rpc.data().is_none());
1462 }
1463 }
1464 }
1465
1466 #[tokio::test]
1467 async fn estimate_gas_respects_rpc_gas_cap() {
1468 const LOW_GAS_CAP: u64 = 50_000;
1469 const HIGH_GAS_CAP: u64 = 200_000;
1470 let code = Bytes::from_static(&[
1473 0x5a, 0x61, 0xea, 0x60, 0x10, 0x60, 0x0d, 0x57, 0x60, 0x00, 0x60, 0x00, 0xfd, 0x5b,
1474 0x00,
1475 ]);
1476 let sender = Address::repeat_byte(0x11);
1477 let contract = Address::repeat_byte(0xaa);
1478 let mut state_override = StateOverride::default();
1479 state_override.insert(contract, AccountOverride { code: Some(code), ..Default::default() });
1480 let overrides = EvmOverrides::state(Some(state_override));
1481 let at = BlockId::latest();
1482
1483 for chain_spec in [
1484 ChainSpecBuilder::mainnet().cancun_activated().build(),
1485 ChainSpecBuilder::mainnet().osaka_activated().build(),
1486 ChainSpecBuilder::mainnet().amsterdam_activated().build(),
1487 ] {
1488 let provider = MockEthProvider::default().with_chain_spec(chain_spec);
1489 provider.add_account(sender, ExtendedAccount::new(0, U256::MAX));
1490 provider.add_block(
1491 B256::repeat_byte(0x42),
1492 Block {
1493 header: Header {
1494 number: 1,
1495 gas_limit: 30_000_000,
1496 excess_blob_gas: Some(0),
1497 ..Default::default()
1498 },
1499 body: BlockBody::default(),
1500 },
1501 );
1502
1503 for gas in [None, Some(1_000_000)] {
1504 let request = TransactionRequest {
1505 gas,
1506 ..TransactionRequest::default().with_from(sender).with_to(contract)
1507 };
1508 let capped = build_test_eth_api_with_gas_cap(provider.clone(), LOW_GAS_CAP);
1509 let err = EthCall::estimate_gas_at(&capped, request.clone(), at, overrides.clone())
1510 .await
1511 .expect_err("estimation above the RPC gas cap must fail");
1512 assert!(
1513 matches!(
1514 err.as_invalid_transaction(),
1515 Some(RpcInvalidTransactionError::Revert(_))
1516 ),
1517 "{err}"
1518 );
1519
1520 for gas_cap in [HIGH_GAS_CAP, 0] {
1521 let relaxed = build_test_eth_api_with_gas_cap(provider.clone(), gas_cap);
1522 let estimated =
1523 EthCall::estimate_gas_at(&relaxed, request.clone(), at, overrides.clone())
1524 .await
1525 .expect("same call fits under a higher or unlimited RPC gas cap");
1526 assert!(estimated > U256::from(LOW_GAS_CAP), "{estimated}");
1527 assert!(estimated <= U256::from(HIGH_GAS_CAP), "{estimated}");
1528 }
1529 }
1530 }
1531 }
1532
1533 #[tokio::test]
1534 async fn estimate_gas_fee_free_retries_and_transfer() {
1535 let sender = Address::repeat_byte(0x11);
1536 let target = Address::repeat_byte(0xaa);
1537 let provider = MockEthProvider::default()
1538 .with_chain_spec(ChainSpecBuilder::mainnet().cancun_activated().build());
1539 provider.add_block(
1540 B256::repeat_byte(0x42),
1541 Block {
1542 header: Header {
1543 number: 1,
1544 gas_limit: 100_000,
1545 excess_blob_gas: Some(0),
1546 ..Default::default()
1547 },
1548 body: BlockBody::default(),
1549 },
1550 );
1551 let api = build_test_eth_api_with_gas_cap(provider, 100_000);
1552 for dynamic_fee in [false, true] {
1553 for transfer in [false, true] {
1554 let request = TransactionRequest {
1555 gas: Some(30_000),
1556 gas_price: (!dynamic_fee).then_some(1),
1557 max_fee_per_gas: dynamic_fee.then_some(1),
1558 max_priority_fee_per_gas: dynamic_fee.then_some(1),
1559 ..TransactionRequest::default().with_from(sender).with_to(target)
1560 };
1561 let overrides = StateOverride::from_iter([
1562 (
1563 sender,
1564 AccountOverride {
1565 balance: Some(U256::from(if transfer { 20_000 } else { 50_000 })),
1566 ..Default::default()
1567 },
1568 ),
1569 (
1570 target,
1571 AccountOverride {
1572 code: Some(if transfer {
1573 Bytes::new()
1574 } else {
1575 Bytes::from_static(&[0x60, 0x00, 0x60, 0x00, 0xfd])
1576 }),
1577 ..Default::default()
1578 },
1579 ),
1580 ]);
1581 let result = EthCall::estimate_gas_at(
1582 &api,
1583 request,
1584 BlockId::latest(),
1585 EvmOverrides::state(Some(overrides)),
1586 )
1587 .await;
1588 if transfer {
1589 assert_eq!(result.unwrap(), U256::from(21_000));
1590 } else {
1591 let err = result.unwrap_err();
1592 assert!(
1593 matches!(
1594 err.as_invalid_transaction(),
1595 Some(RpcInvalidTransactionError::Revert(_))
1596 ),
1597 "{err}"
1598 );
1599 }
1600 }
1601 }
1602 }
1603
1604 #[tokio::test]
1605 async fn estimate_gas_transfer_respects_rpc_gas_cap() {
1606 let provider = MockEthProvider::default();
1607 provider.add_block(
1608 B256::repeat_byte(0x42),
1609 Block {
1610 header: Header { number: 1, gas_limit: 30_000_000, ..Default::default() },
1611 body: BlockBody::default(),
1612 },
1613 );
1614 let request = TransactionRequest::default().with_to(Address::repeat_byte(0xaa));
1615 let at = BlockId::latest();
1616
1617 let capped = build_test_eth_api_with_gas_cap(provider.clone(), 20_999);
1618 let err = EthCall::estimate_gas_at(&capped, request.clone(), at, EvmOverrides::default())
1619 .await
1620 .expect_err("the transfer shortcut must respect the RPC gas cap");
1621 assert!(
1622 matches!(
1623 err.as_invalid_transaction(),
1624 Some(RpcInvalidTransactionError::GasRequiredExceedsAllowance { gas_limit: 20_999 })
1625 ),
1626 "{err}"
1627 );
1628
1629 let sufficient = build_test_eth_api_with_gas_cap(provider, 21_000);
1630 let estimated = EthCall::estimate_gas_at(&sufficient, request, at, EvmOverrides::default())
1631 .await
1632 .unwrap();
1633 assert_eq!(estimated, U256::from(21_000));
1634 }
1635
1636 #[tokio::test]
1637 async fn header_responses_omit_size_while_blocks_keep_it() {
1638 let provider = MockEthProvider::default();
1639 let block = Block {
1640 header: Header { number: 1, ..Default::default() },
1641 body: BlockBody::default(),
1642 };
1643 let hash = block.header.hash_slow();
1644 let block_size = alloy_rlp::encode(&block).len();
1645 provider.add_block(hash, block);
1646
1647 let api = build_test_eth_api(provider);
1648 for block_id in [BlockId::number(1), BlockId::hash(hash)] {
1649 let header = EthBlocks::rpc_block_header(&api, block_id).await.unwrap().unwrap();
1650 let response = serde_json::to_value(&header).unwrap();
1651 assert!(response.get("size").is_none());
1652 }
1653
1654 for full in [false, true] {
1655 let block =
1656 EthBlocks::rpc_block(&api, BlockId::number(1), full).await.unwrap().unwrap();
1657 assert_eq!(block.header.size, Some(U256::from(block_size)));
1658 }
1659 }
1660
1661 #[tokio::test]
1662 async fn simulate_preserves_state_between_blocks() {
1663 let sender = Address::repeat_byte(0x11);
1664 let contract = Address::repeat_byte(0xaa);
1665 let code = alloy_primitives::bytes!("60005460010180600055600052303160205260406000f3");
1667 let genesis =
1668 Genesis::default().with_gas_limit(30_000_000).with_base_fee(Some(0)).extend_accounts([
1669 (sender, GenesisAccount::default().with_balance(U256::from(1_000_000))),
1670 (contract, GenesisAccount::default().with_nonce(Some(1)).with_code(Some(code))),
1671 ]);
1672 let chain_spec =
1673 Arc::new(ChainSpecBuilder::mainnet().cancun_activated().genesis(genesis).build());
1674 let factory = create_test_provider_factory_with_chain_spec(chain_spec);
1675 init_genesis(&factory).unwrap();
1676 let provider = BlockchainProvider::new(factory).unwrap();
1677 let api = EthApiBuilder::new(
1678 provider.clone(),
1679 testing_pool(),
1680 NoopNetwork::default(),
1681 EthEvmConfig::new(provider.chain_spec()),
1682 )
1683 .compute_state_root_for_eth_simulate(true)
1684 .build();
1685 for trace_transfers in [false, true] {
1686 let payload = SimulatePayload {
1687 validation: true,
1688 trace_transfers,
1689 block_state_calls: vec![
1690 SimBlock::default()
1691 .call(
1692 TransactionRequest::default()
1693 .with_from(sender)
1694 .with_to(contract)
1695 .with_nonce(0)
1696 .with_gas_limit(100_000)
1697 .with_value(U256::from(1)),
1698 )
1699 .call(
1700 TransactionRequest::default()
1702 .with_from(sender)
1703 .with_to(Address::repeat_byte(0xbb))
1704 .with_nonce(1)
1705 .with_gas_limit(21_000)
1706 .with_value(U256::from(1)),
1707 ),
1708 SimBlock::default().call(
1709 TransactionRequest::default()
1710 .with_from(sender)
1711 .with_to(contract)
1712 .with_nonce(2)
1713 .with_gas_limit(100_000),
1714 ),
1715 ],
1716 ..Default::default()
1717 };
1718 let combined = SimulatePayload {
1719 block_state_calls: vec![SimBlock::default().extend_calls(
1720 payload.block_state_calls.iter().flat_map(|block| block.calls.clone()),
1721 )],
1722 ..payload.clone()
1723 };
1724 let expected = EthCall::simulate_v1(&api, combined, None).await.unwrap();
1725 let blocks = EthCall::simulate_v1(&api, payload, None).await.unwrap();
1726 assert_eq!(blocks[1].inner.header.state_root, expected[0].inner.header.state_root);
1727 assert_eq!(blocks.len(), 2);
1728 for (index, block) in blocks.iter().enumerate() {
1729 let result = &block.calls[0];
1730 assert!(result.status, "{result:?}");
1731 assert_eq!(U256::from_be_slice(&result.return_data[..32]), U256::from(index + 1));
1732 assert_eq!(U256::from_be_slice(&result.return_data[32..]), U256::from(1));
1733 }
1734 }
1735 }
1736
1737 #[tokio::test]
1738 async fn call_preserves_block_number_and_saturates_fee_overrides() {
1739 let contract = Address::repeat_byte(0xaa);
1740 let sender = Address::repeat_byte(0x11);
1741 let provider = MockEthProvider::default()
1742 .with_chain_spec(ChainSpecBuilder::mainnet().cancun_activated().build());
1743 provider.add_account(sender, ExtendedAccount::new(0, U256::MAX));
1744 provider.add_block(
1745 B256::repeat_byte(0x42),
1746 Block {
1747 header: Header {
1748 number: 1,
1749 gas_limit: 30_000_000,
1750 excess_blob_gas: Some(0),
1751 ..Default::default()
1752 },
1753 body: BlockBody::default(),
1754 },
1755 );
1756 let api = build_test_eth_api(provider);
1757 let overrides = EvmOverrides::state(Some(StateOverride::from_iter([(
1759 contract,
1760 AccountOverride {
1761 code: Some(alloy_primitives::bytes!("43600052486020524a60405260606000f3")),
1762 ..Default::default()
1763 },
1764 )])))
1765 .with_block(Box::new(BlockOverrides {
1766 number: Some(U256::MAX),
1767 base_fee: Some(U256::MAX),
1768 blob_base_fee: Some(U256::MAX),
1769 ..Default::default()
1770 }));
1771 let result = EthCall::call(
1772 &api,
1773 TransactionRequest {
1774 gas_price: Some(1),
1775 ..TransactionRequest::default()
1776 .with_from(sender)
1777 .with_to(contract)
1778 .with_gas_limit(100_000)
1779 },
1780 None,
1781 overrides,
1782 )
1783 .await
1784 .unwrap();
1785 assert_eq!(result.len(), 96);
1786 assert_eq!(U256::from_be_slice(&result[..32]), U256::MAX);
1787 assert_eq!(U256::from_be_slice(&result[32..64]), U256::from(u64::MAX));
1788 assert_eq!(U256::from_be_slice(&result[64..]), U256::from(u128::MAX));
1789 }
1790
1791 #[tokio::test]
1792 async fn create_access_list_gas_includes_generated_list() {
1793 let provider = MockEthProvider::default()
1794 .with_chain_spec(ChainSpecBuilder::mainnet().berlin_activated().build());
1795 provider.add_block(
1796 B256::repeat_byte(0x42),
1797 Block {
1798 header: Header { number: 1, gas_limit: 30_000_000, ..Default::default() },
1799 body: BlockBody::default(),
1800 },
1801 );
1802 let sender = Address::repeat_byte(0x11);
1803 provider.add_account(sender, ExtendedAccount::new(7, U256::MAX));
1804 let contract = Address::repeat_byte(0xaa);
1805 let state_override = StateOverride::from_iter([(
1806 contract,
1807 AccountOverride {
1808 code: Some(Bytes::from_static(&[0x60, 0x00, 0x54, 0x00])),
1810 ..Default::default()
1811 },
1812 )]);
1813 let api = build_test_eth_api_with_gas_cap(provider, 30_000);
1814 let mut request = TransactionRequest::default()
1815 .with_from(sender)
1816 .with_to(contract)
1817 .with_gas_limit(100_000);
1818 let at = BlockId::latest();
1819 let result = EthCall::create_access_list_at(
1820 &api,
1821 request.clone(),
1822 Some(at),
1823 Some(state_override.clone()),
1824 )
1825 .await
1826 .unwrap();
1827
1828 assert_eq!(result.error, None);
1829 assert_eq!(result.access_list.0.len(), 1);
1830 assert_eq!(result.access_list.0[0].address, contract);
1831 assert_eq!(result.access_list.0[0].storage_keys, vec![B256::ZERO]);
1832 assert_eq!(result.gas_used, U256::from(21_000 + 2_400 + 1_900 + 3 + 100));
1834
1835 request.set_access_list(result.access_list.clone());
1836 let repeated =
1837 EthCall::create_access_list_at(&api, request, Some(at), Some(state_override))
1838 .await
1839 .unwrap();
1840 assert_eq!(repeated, result);
1841 }
1842
1843 #[tokio::test]
1844 async fn create_access_list_excludes_authorization_authorities() {
1845 let provider = MockEthProvider::default()
1846 .with_chain_spec(ChainSpecBuilder::mainnet().prague_activated().build());
1847 provider.add_block(
1848 B256::repeat_byte(0x42),
1849 Block {
1850 header: Header { number: 1, gas_limit: 30_000_000, ..Default::default() },
1851 body: BlockBody::default(),
1852 },
1853 );
1854 let sender = Address::repeat_byte(0x11);
1855 provider.add_account(sender, ExtendedAccount::new(0, U256::MAX));
1856
1857 let authorization = Authorization {
1858 chain_id: U256::from(1),
1859 address: Address::repeat_byte(0xbb),
1860 nonce: 0,
1861 };
1862 let signature =
1863 sign_message(B256::repeat_byte(0x22), authorization.signature_hash()).unwrap();
1864 let authorization = authorization.into_signed(signature);
1865 let authority = authorization.recover_authority().unwrap();
1866
1867 let contract = Address::repeat_byte(0xaa);
1868 let state_override = StateOverride::from_iter([(
1869 contract,
1870 AccountOverride {
1871 code: Some([&[0x73][..], authority.as_slice(), &[0x31, 0x00]].concat().into()),
1873 ..Default::default()
1874 },
1875 )]);
1876 let api = build_test_eth_api(provider);
1877 let request = TransactionRequest {
1878 authorization_list: Some(vec![authorization]),
1879 ..TransactionRequest::default()
1880 .with_from(sender)
1881 .with_to(contract)
1882 .with_gas_limit(100_000)
1883 };
1884 let result = EthCall::create_access_list_at(
1885 &api,
1886 request,
1887 Some(BlockId::latest()),
1888 Some(state_override),
1889 )
1890 .await
1891 .unwrap();
1892
1893 assert_eq!(result.error, None);
1894 assert!(result.access_list.0.is_empty(), "{:?}", result.access_list);
1896 }
1897
1898 #[tokio::test]
1899 async fn simulate_roots_include_overrides_across_blocks() {
1900 let sender = Address::repeat_byte(0x11);
1901 let contract = Address::repeat_byte(0xaa);
1902 let added = Address::repeat_byte(0xbb);
1903 let absent = Address::repeat_byte(0xcc);
1904 let slot = |n: u64| B256::from(U256::from(n));
1905 let genesis =
1906 Genesis::default().with_gas_limit(30_000_000).with_base_fee(Some(0)).extend_accounts([
1907 (sender, GenesisAccount::default().with_balance(U256::from(1_000_000))),
1908 (
1909 contract,
1910 GenesisAccount::default()
1911 .with_nonce(Some(1))
1912 .with_storage(Some([(slot(0), slot(1)), (slot(1), slot(2))].into())),
1913 ),
1914 ]);
1915 let chain_spec = Arc::new(
1916 ChainSpecBuilder::mainnet().cancun_activated().genesis(genesis.clone()).build(),
1917 );
1918 let factory = create_test_provider_factory_with_chain_spec(chain_spec);
1919 init_genesis(&factory).unwrap();
1920 let provider = BlockchainProvider::new(factory).unwrap();
1921 let api = EthApiBuilder::new(
1922 provider.clone(),
1923 testing_pool(),
1924 NoopNetwork::default(),
1925 EthEvmConfig::new(provider.chain_spec()),
1926 )
1927 .compute_state_root_for_eth_simulate(true)
1928 .build();
1929 let code = alloy_primitives::bytes!("600960005500");
1933 let first = SimBlock {
1934 state_overrides: Some(StateOverride::from_iter([
1935 (
1936 contract,
1937 AccountOverride {
1938 balance: Some(U256::from(7)),
1939 nonce: Some(2),
1940 code: Some(code.clone()),
1941 state_diff: Some(std::iter::once((slot(2), slot(3))).collect()),
1942 ..Default::default()
1943 },
1944 ),
1945 (added, AccountOverride { balance: Some(U256::from(4)), ..Default::default() }),
1946 (absent, AccountOverride::default()),
1949 ])),
1950 ..Default::default()
1951 };
1952 let second = SimBlock {
1953 state_overrides: Some(StateOverride::from_iter([(
1954 contract,
1955 AccountOverride {
1956 state: Some(std::iter::once((slot(0), slot(5))).collect()),
1957 ..Default::default()
1958 },
1959 )])),
1960 ..Default::default()
1961 }
1962 .call(
1963 TransactionRequest::default()
1964 .with_from(sender)
1965 .with_to(contract)
1966 .with_nonce(0)
1967 .with_gas_limit(100_000)
1968 .with_gas_price(0),
1969 );
1970 let third = SimBlock::default().call(
1971 TransactionRequest::default()
1972 .with_from(sender)
1973 .with_to(contract)
1974 .with_nonce(1)
1975 .with_gas_limit(100_000)
1976 .with_gas_price(0),
1977 );
1978 let blocks = EthCall::simulate_v1(
1979 &api,
1980 SimulatePayload {
1981 validation: true,
1982 block_state_calls: vec![first, second, third],
1983 ..Default::default()
1984 },
1985 None,
1986 )
1987 .await
1988 .unwrap();
1989 assert!(blocks[1].calls[0].status);
1990 assert!(blocks[2].calls[0].status);
1991
1992 for (index, storage) in [
1994 [(slot(0), slot(1)), (slot(1), slot(2)), (slot(2), slot(3))].into_iter().collect(),
1995 std::iter::once((slot(0), slot(9))).collect(),
1996 std::iter::once((slot(0), slot(9))).collect(),
1997 ]
1998 .into_iter()
1999 .enumerate()
2000 {
2001 let expected = genesis.clone().extend_accounts([
2002 (
2003 sender,
2004 GenesisAccount::default()
2005 .with_balance(U256::from(1_000_000))
2006 .with_nonce(Some(index as u64)),
2007 ),
2008 (
2009 contract,
2010 GenesisAccount::default()
2011 .with_balance(U256::from(7))
2012 .with_nonce(Some(2))
2013 .with_code(Some(code.clone()))
2014 .with_storage(Some(storage)),
2015 ),
2016 (added, GenesisAccount::default().with_balance(U256::from(4))),
2017 ]);
2018 let expected = ChainSpecBuilder::mainnet().cancun_activated().genesis(expected).build();
2019 assert_eq!(blocks[index].inner.header.state_root, expected.genesis_header().state_root);
2020 }
2021 }
2022
2023 #[tokio::test]
2024 async fn blob_call_allowance_reserves_maximum_fee() {
2025 let sender = Address::repeat_byte(0x11);
2026 let provider = MockEthProvider::default()
2027 .with_chain_spec(ChainSpecBuilder::mainnet().cancun_activated().build());
2028 let api = build_test_eth_api(provider);
2029 let env = api.evm_config().evm_env(&Header::default()).unwrap();
2030 let allowance = |balance, value| {
2031 let mut db = CacheDB::default();
2032 db.insert_account_info(&sender, AccountInfo { balance, ..Default::default() });
2033 let request = TransactionRequest {
2034 max_fee_per_gas: Some(10),
2035 max_priority_fee_per_gas: Some(10),
2036 max_fee_per_blob_gas: Some(3),
2037 blob_versioned_hashes: Some(vec![B256::repeat_byte(1)]),
2038 value: Some(value),
2039 ..TransactionRequest::default()
2040 .with_from(sender)
2041 .with_to(Address::repeat_byte(0xaa))
2042 };
2043 let tx = api.create_txn_env(&env, request, &mut db).unwrap();
2044 api.caller_gas_allowance(&mut db, &env, &tx)
2045 };
2046 let value = U256::from(1_000);
2047 let blob_fee = U256::from(DATA_GAS_PER_BLOB * 3);
2048
2049 assert_eq!(
2050 allowance(U256::from(1_000_000), value).unwrap(),
2051 (1_000_000 - 1_000 - DATA_GAS_PER_BLOB * 3) / 10
2052 );
2053 for (balance, value) in [(value + blob_fee - U256::ONE, value), (U256::MAX, U256::MAX)] {
2054 let err = allowance(balance, value).unwrap_err();
2055 assert!(
2056 matches!(
2057 err.as_invalid_transaction(),
2058 Some(RpcInvalidTransactionError::InsufficientFunds { cost, balance: actual })
2059 if *cost == value.saturating_add(blob_fee) && *actual == balance
2060 ),
2061 "{err}"
2062 );
2063 }
2064 }
2065}