Skip to main content

reth_rpc/eth/helpers/
state.rs

1//! Contains RPC handler implementations specific to state.
2
3use crate::EthApi;
4use reth_rpc_convert::RpcConvert;
5use reth_rpc_eth_api::{
6    helpers::{EthState, LoadPendingBlock, LoadState},
7    RpcNodeCore,
8};
9use reth_rpc_eth_types::EthApiError;
10
11impl<N, Rpc> EthState for EthApi<N, Rpc>
12where
13    N: RpcNodeCore,
14    Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
15    Self: LoadPendingBlock,
16{
17}
18
19impl<N, Rpc> LoadState for EthApi<N, Rpc>
20where
21    N: RpcNodeCore,
22    Rpc: RpcConvert<Primitives = N::Primitives>,
23    Self: LoadPendingBlock,
24{
25}
26
27#[cfg(test)]
28mod tests {
29    use crate::eth::helpers::types::EthRpcConverter;
30
31    use super::*;
32    use alloy_eips::BlockId;
33    use alloy_primitives::{
34        map::{AddressMap, B256Map},
35        Address, StorageKey, StorageValue, B256, U256,
36    };
37    use alloy_rpc_types_eth::TransactionRequest;
38    use reth_chainspec::ChainSpec;
39    use reth_ethereum_primitives::Block;
40    use reth_evm_ethereum::EthEvmConfig;
41    use reth_network_api::noop::NoopNetwork;
42    use reth_provider::{
43        test_utils::{ExtendedAccount, MockEthProvider, NoopProvider},
44        ChainSpecProvider,
45    };
46    use reth_rpc_eth_api::{
47        helpers::{pending_block::PendingEnvBuilder, EthCall, EthState, SpawnBlocking},
48        node::{RpcNodeCoreAdapter, RpcNodeCoreExt},
49        EthApiTypes,
50    };
51    use reth_rpc_eth_types::{EthApiSettings, EthStateCache, PendingBlock};
52    use reth_storage_api::{StateProviderBox, StateProviderFactory};
53    use reth_tasks::{
54        pool::{BlockingTaskGuard, BlockingTaskPool},
55        Runtime,
56    };
57    use reth_transaction_pool::test_utils::{testing_pool, TestPool};
58    use std::{future::Future, sync::Arc, time::Duration};
59    use tokio::sync::{Mutex, Semaphore};
60
61    fn noop_eth_api() -> EthApi<
62        RpcNodeCoreAdapter<NoopProvider, TestPool, NoopNetwork, EthEvmConfig>,
63        EthRpcConverter<ChainSpec>,
64    > {
65        let provider = NoopProvider::default();
66        let pool = testing_pool();
67        let evm_config = EthEvmConfig::mainnet();
68
69        EthApi::builder(provider, pool, NoopNetwork::default(), evm_config).build()
70    }
71
72    fn mock_eth_api(
73        accounts: AddressMap<ExtendedAccount>,
74    ) -> EthApi<
75        RpcNodeCoreAdapter<MockEthProvider, TestPool, NoopNetwork, EthEvmConfig>,
76        EthRpcConverter<ChainSpec>,
77    > {
78        let pool = testing_pool();
79        let mock_provider = MockEthProvider::default();
80
81        let evm_config = EthEvmConfig::new(mock_provider.chain_spec());
82        mock_provider.extend_accounts(accounts);
83
84        EthApi::builder(mock_provider, pool, NoopNetwork::default(), evm_config).build()
85    }
86
87    #[tokio::test]
88    async fn test_storage() {
89        // === Noop ===
90        let eth_api = noop_eth_api();
91        let address = Address::random();
92        let storage = eth_api.storage_at(address, U256::ZERO.into(), None).await.unwrap();
93        assert_eq!(storage, U256::ZERO.to_be_bytes());
94
95        // === Mock ===
96        let storage_value = StorageValue::from(1337);
97        let storage_key = StorageKey::random();
98        let storage: B256Map<_> = core::iter::once((storage_key, storage_value)).collect();
99
100        let accounts = AddressMap::from_iter([(
101            address,
102            ExtendedAccount::new(0, U256::ZERO).extend_storage(storage),
103        )]);
104        let eth_api = mock_eth_api(accounts);
105
106        let storage_key: U256 = storage_key.into();
107        let storage = eth_api.storage_at(address, storage_key.into(), None).await.unwrap();
108        assert_eq!(storage, storage_value.to_be_bytes());
109    }
110
111    #[tokio::test]
112    async fn test_get_account_missing() {
113        let eth_api = noop_eth_api();
114        let address = Address::random();
115        let account = eth_api.get_account(address, Default::default()).await.unwrap();
116        assert!(account.is_none());
117    }
118
119    #[cfg(feature = "account-ext")]
120    #[tokio::test]
121    async fn test_get_account_extension() {
122        let address = Address::random();
123        let extension = reth_primitives_traits::AccountExtension::copy_from_slice(&[0x01]);
124        let eth_api = mock_eth_api(AddressMap::from_iter([(
125            address,
126            ExtendedAccount::new(0, U256::ZERO).with_extension(extension.clone()),
127        )]));
128        eth_api.provider().add_block(B256::ZERO, Block::default());
129
130        // An account whose only non-default field is its extension still exists.
131        let info = eth_api.get_account_info(address, Default::default()).await.unwrap();
132        assert!(!info.is_empty());
133        assert_eq!(info.extension, extension);
134        let json = serde_json::to_value(&info).unwrap();
135        assert_eq!(json["extension"], "0x01");
136        assert_eq!(serde_json::from_value::<alloy_rpc_types_eth::AccountInfo>(json).unwrap(), info);
137
138        let account = eth_api.get_account(address, Default::default()).await.unwrap().unwrap();
139        assert_eq!(account.extension, extension);
140
141        let missing =
142            eth_api.get_account_info(Address::random(), Default::default()).await.unwrap();
143        assert!(missing.is_empty());
144        assert!(serde_json::to_value(missing).unwrap().get("extension").is_none());
145    }
146
147    #[test]
148    fn pending_state_and_access_list_do_not_deadlock() {
149        let runtime = tokio::runtime::Builder::new_multi_thread()
150            .worker_threads(2)
151            .max_blocking_threads(1)
152            .enable_all()
153            .build()
154            .unwrap();
155
156        let result = runtime.block_on(async {
157            let address = Address::random();
158            let accounts =
159                AddressMap::from_iter([(address, ExtendedAccount::new(0, U256::from(1337)))]);
160            let eth_api = mock_eth_api(accounts);
161            // Use a block after access-list transactions became valid.
162            let mut block = Block::default();
163            block.header.number = 13_000_000;
164            block.header.timestamp = 1_629_000_000;
165            block.header.gas_limit = 30_000_000;
166            block.header.base_fee_per_gas = Some(1_000_000_000);
167            eth_api.provider().add_block(B256::ZERO, block);
168            tokio::time::timeout(Duration::from_secs(10), async {
169                let balance = eth_api.balance(address, Some(BlockId::pending())).await?;
170                eth_api
171                    .create_access_list_at(
172                        TransactionRequest { gas_price: Some(0), ..Default::default() },
173                        Some(BlockId::latest()),
174                        None,
175                    )
176                    .await?;
177                Ok::<_, EthApiError>(balance)
178            })
179            .await
180        });
181        // A deadlocked blocking thread would also block a regular runtime drop.
182        runtime.shutdown_background();
183        assert_eq!(
184            result.expect("RPC timed out on one blocking thread").expect("RPC returned an error"),
185            U256::from(1337)
186        );
187    }
188
189    type MockEthApi = EthApi<
190        RpcNodeCoreAdapter<MockEthProvider, TestPool, NoopNetwork, EthEvmConfig>,
191        EthRpcConverter<ChainSpec>,
192    >;
193
194    #[derive(Clone)]
195    struct CustomPendingState {
196        inner: MockEthApi,
197        pending: MockEthProvider,
198    }
199
200    impl EthApiTypes for CustomPendingState {
201        type Error = EthApiError;
202        type NetworkTypes = <MockEthApi as EthApiTypes>::NetworkTypes;
203        type RpcConvert = <MockEthApi as EthApiTypes>::RpcConvert;
204
205        fn eth_api_settings(&self) -> &EthApiSettings {
206            self.inner.eth_api_settings()
207        }
208
209        fn converter(&self) -> &Self::RpcConvert {
210            self.inner.converter()
211        }
212    }
213
214    impl RpcNodeCore for CustomPendingState {
215        type Primitives = <MockEthApi as RpcNodeCore>::Primitives;
216        type Provider = <MockEthApi as RpcNodeCore>::Provider;
217        type Pool = <MockEthApi as RpcNodeCore>::Pool;
218        type Evm = <MockEthApi as RpcNodeCore>::Evm;
219        type Network = <MockEthApi as RpcNodeCore>::Network;
220
221        fn pool(&self) -> &Self::Pool {
222            self.inner.pool()
223        }
224
225        fn evm_config(&self) -> &Self::Evm {
226            self.inner.evm_config()
227        }
228
229        fn network(&self) -> &Self::Network {
230            self.inner.network()
231        }
232
233        fn provider(&self) -> &Self::Provider {
234            self.inner.provider()
235        }
236    }
237
238    impl RpcNodeCoreExt for CustomPendingState {
239        fn cache(&self) -> &EthStateCache<Self::Primitives> {
240            self.inner.cache()
241        }
242    }
243
244    impl SpawnBlocking for CustomPendingState {
245        fn io_task_spawner(&self) -> &Runtime {
246            self.inner.io_task_spawner()
247        }
248
249        fn tracing_task_pool(&self) -> &BlockingTaskPool {
250            self.inner.tracing_task_pool()
251        }
252
253        fn tracing_task_guard(&self) -> &BlockingTaskGuard {
254            self.inner.tracing_task_guard()
255        }
256
257        fn blocking_io_task_guard(&self) -> &Arc<Semaphore> {
258            self.inner.blocking_io_task_guard()
259        }
260    }
261
262    impl LoadPendingBlock for CustomPendingState {
263        fn pending_block(&self) -> &Mutex<Option<PendingBlock<Self::Primitives>>> {
264            self.inner.pending_block()
265        }
266
267        fn pending_env_builder(&self) -> &dyn PendingEnvBuilder<Self::Evm> {
268            self.inner.pending_env_builder()
269        }
270
271        fn local_pending_state(
272            &self,
273        ) -> impl Future<Output = Result<Option<StateProviderBox>, Self::Error>> + Send {
274            let state = self.pending.latest().map_err(EthApiError::from);
275            async move { state.map(Some) }
276        }
277    }
278
279    impl LoadState for CustomPendingState {}
280
281    impl EthState for CustomPendingState {}
282
283    #[tokio::test]
284    async fn pending_state_reads_use_existing_override() {
285        let address = Address::random();
286        let chain = AddressMap::from_iter([(address, ExtendedAccount::new(0, U256::from(1337)))]);
287        let eth_api = mock_eth_api(chain);
288        eth_api.provider().add_block(B256::ZERO, Block::default());
289
290        let pending = MockEthProvider::default();
291        pending.extend_accounts([(address, ExtendedAccount::new(0, U256::from(42)))]);
292        let eth_api = CustomPendingState { inner: eth_api, pending };
293
294        assert_eq!(
295            eth_api.balance(address, Some(BlockId::pending())).await.unwrap(),
296            U256::from(42)
297        );
298        assert_eq!(eth_api.balance(address, None).await.unwrap(), U256::from(1337));
299    }
300}