Skip to main content

reth_rpc/eth/helpers/
state.rs

1//! Contains RPC handler implementations specific to state.
2
3use crate::EthApi;
4use alloy_eips::BlockId;
5use alloy_primitives::{Address, U256};
6use reth_rpc_convert::RpcConvert;
7use reth_rpc_eth_api::{
8    helpers::{EthState, LoadPendingBlock, LoadState, SpawnBlocking},
9    FromEthApiError, RpcNodeCore,
10};
11use reth_rpc_eth_types::EthApiError;
12use reth_storage_api::StateProviderFactory;
13
14impl<N, Rpc> EthState for EthApi<N, Rpc>
15where
16    N: RpcNodeCore,
17    Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
18    Self: LoadPendingBlock,
19{
20}
21
22impl<N, Rpc> LoadState for EthApi<N, Rpc>
23where
24    N: RpcNodeCore,
25    Rpc: RpcConvert<Primitives = N::Primitives>,
26    Self: LoadPendingBlock,
27{
28    /// Reads the nonce from the provider's state instead of a locally built pending block.
29    ///
30    /// A local pending block only applies pool transactions on top of the latest state, and the
31    /// pool lookup for the pending tag already accounts for the sender's transactions, so
32    /// building the block and its state overlay only to read one nonce is redundant.
33    fn transaction_count(
34        &self,
35        address: Address,
36        block_id: Option<BlockId>,
37    ) -> impl Future<Output = Result<U256, Self::Error>> + Send
38    where
39        Self: SpawnBlocking,
40    {
41        let at = block_id.unwrap_or_default();
42        self.spawn_blocking_io(move |this| {
43            let state = if at.is_latest() {
44                this.latest_state()?
45            } else {
46                this.provider().state_by_block_id(at).map_err(Self::Error::from_eth_err)?
47            };
48            this.transaction_count_with_state(address, at, &state)
49        })
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use crate::eth::helpers::types::EthRpcConverter;
56
57    use super::*;
58    use alloy_eips::BlockId;
59    use alloy_primitives::{
60        map::{AddressMap, B256Map},
61        Address, StorageKey, StorageValue, B256, U256,
62    };
63    use alloy_rpc_types_eth::TransactionRequest;
64    use alloy_serde::JsonStorageKey;
65    use reth_chainspec::ChainSpec;
66    use reth_ethereum_primitives::Block;
67    use reth_evm_ethereum::EthEvmConfig;
68    use reth_network_api::noop::NoopNetwork;
69    use reth_provider::{
70        test_utils::{ExtendedAccount, MockEthProvider, NoopProvider},
71        ChainSpecProvider,
72    };
73    use reth_rpc_eth_api::{
74        helpers::{pending_block::PendingEnvBuilder, EthCall, EthState, SpawnBlocking},
75        node::{RpcNodeCoreAdapter, RpcNodeCoreExt},
76        EthApiTypes,
77    };
78    use reth_rpc_eth_types::{EthApiSettings, EthStateCache, PendingBlock};
79    use reth_rpc_server_types::constants::DEFAULT_MAX_PROOF_SLOTS;
80    use reth_storage_api::{StateProviderBox, StateProviderFactory};
81    use reth_tasks::{
82        pool::{BlockingTaskGuard, BlockingTaskPool},
83        Runtime,
84    };
85    use reth_transaction_pool::{
86        test_utils::{testing_pool, MockTransaction, TestPool},
87        TransactionOrigin, TransactionPool,
88    };
89    use std::{future::Future, sync::Arc, time::Duration};
90    use tokio::sync::{Mutex, Semaphore};
91
92    fn noop_eth_api() -> EthApi<
93        RpcNodeCoreAdapter<NoopProvider, TestPool, NoopNetwork, EthEvmConfig>,
94        EthRpcConverter<ChainSpec>,
95    > {
96        let provider = NoopProvider::default();
97        let pool = testing_pool();
98        let evm_config = EthEvmConfig::mainnet();
99
100        EthApi::builder(provider, pool, NoopNetwork::default(), evm_config).build()
101    }
102
103    fn mock_eth_api(
104        accounts: AddressMap<ExtendedAccount>,
105    ) -> EthApi<
106        RpcNodeCoreAdapter<MockEthProvider, TestPool, NoopNetwork, EthEvmConfig>,
107        EthRpcConverter<ChainSpec>,
108    > {
109        let pool = testing_pool();
110        let mock_provider = MockEthProvider::default();
111
112        let evm_config = EthEvmConfig::new(mock_provider.chain_spec());
113        mock_provider.extend_accounts(accounts);
114
115        EthApi::builder(mock_provider, pool, NoopNetwork::default(), evm_config).build()
116    }
117
118    #[tokio::test]
119    async fn test_storage() {
120        // === Noop ===
121        let eth_api = noop_eth_api();
122        let address = Address::random();
123        let storage = eth_api.storage_at(address, U256::ZERO.into(), None).await.unwrap();
124        assert_eq!(storage, U256::ZERO.to_be_bytes());
125
126        // === Mock ===
127        let storage_value = StorageValue::from(1337);
128        let storage_key = StorageKey::random();
129        let storage: B256Map<_> = core::iter::once((storage_key, storage_value)).collect();
130
131        let accounts = AddressMap::from_iter([(
132            address,
133            ExtendedAccount::new(0, U256::ZERO).extend_storage(storage),
134        )]);
135        let eth_api = mock_eth_api(accounts);
136
137        let storage_key: U256 = storage_key.into();
138        let storage = eth_api.storage_at(address, storage_key.into(), None).await.unwrap();
139        assert_eq!(storage, storage_value.to_be_bytes());
140    }
141
142    #[tokio::test]
143    async fn test_get_account_missing() {
144        let eth_api = noop_eth_api();
145        let address = Address::random();
146        let account = eth_api.get_account(address, Default::default()).await.unwrap();
147        assert!(account.is_none());
148    }
149
150    #[test]
151    fn test_get_proof_key_cap() {
152        let eth_api = noop_eth_api();
153        let address = Address::random();
154        let keys = |len| vec![JsonStorageKey::from(B256::ZERO); len];
155
156        assert!(eth_api.get_proof(address, keys(DEFAULT_MAX_PROOF_SLOTS), None).is_ok());
157
158        let Err(err) = eth_api.get_proof(address, keys(DEFAULT_MAX_PROOF_SLOTS + 1), None) else {
159            panic!("expected over-limit request to be rejected");
160        };
161        assert_eq!(err.to_string(), "storage key count 1025 exceeds limit 1024");
162    }
163
164    #[test]
165    fn test_get_multi_proof_cap() {
166        let eth_api = noop_eth_api();
167        let address = Address::random();
168
169        // Exactly at the limit, on both axes.
170        let targets = vec![(address, vec![B256::ZERO; DEFAULT_MAX_PROOF_SLOTS])];
171        assert!(eth_api.get_multi_proof(targets, None).is_ok());
172        let targets = vec![(address, Vec::new()); DEFAULT_MAX_PROOF_SLOTS];
173        assert!(eth_api.get_multi_proof(targets, None).is_ok());
174
175        // Too many slots in a single target.
176        let targets = vec![(address, vec![B256::ZERO; DEFAULT_MAX_PROOF_SLOTS + 1])];
177        let Err(err) = eth_api.get_multi_proof(targets, None) else {
178            panic!("expected over-limit request to be rejected");
179        };
180        assert_eq!(err.to_string(), "total slot count 1025 exceeds limit 1024");
181
182        // Too many slots in total, spread across targets.
183        let targets = vec![
184            (address, vec![B256::ZERO; DEFAULT_MAX_PROOF_SLOTS]),
185            (Address::random(), vec![B256::ZERO]),
186        ];
187        let Err(err) = eth_api.get_multi_proof(targets, None) else {
188            panic!("expected over-limit request to be rejected");
189        };
190        assert_eq!(err.to_string(), "total slot count 1025 exceeds limit 1024");
191
192        // Too many targets without any slots.
193        let targets = vec![(address, Vec::new()); DEFAULT_MAX_PROOF_SLOTS + 1];
194        let Err(err) = eth_api.get_multi_proof(targets, None) else {
195            panic!("expected over-limit request to be rejected");
196        };
197        assert_eq!(err.to_string(), "target count 1025 exceeds limit 1024");
198    }
199
200    #[cfg(feature = "account-ext")]
201    #[tokio::test]
202    async fn test_get_account_extension() {
203        let address = Address::random();
204        let extension = reth_primitives_traits::AccountExtension::copy_from_slice(&[0x01]);
205        let eth_api = mock_eth_api(AddressMap::from_iter([(
206            address,
207            ExtendedAccount::new(0, U256::ZERO).with_extension(extension.clone()),
208        )]));
209        eth_api.provider().add_block(B256::ZERO, Block::default());
210
211        // An account whose only non-default field is its extension still exists.
212        let info = eth_api.get_account_info(address, Default::default()).await.unwrap();
213        assert!(!info.is_empty());
214        assert_eq!(info.extension, extension);
215        let json = serde_json::to_value(&info).unwrap();
216        assert_eq!(json["extension"], "0x01");
217        assert_eq!(serde_json::from_value::<alloy_rpc_types_eth::AccountInfo>(json).unwrap(), info);
218
219        let account = eth_api.get_account(address, Default::default()).await.unwrap().unwrap();
220        assert_eq!(account.extension, extension);
221
222        let missing =
223            eth_api.get_account_info(Address::random(), Default::default()).await.unwrap();
224        assert!(missing.is_empty());
225        assert!(serde_json::to_value(missing).unwrap().get("extension").is_none());
226    }
227
228    #[test]
229    fn pending_state_and_access_list_do_not_deadlock() {
230        let runtime = tokio::runtime::Builder::new_multi_thread()
231            .worker_threads(2)
232            .max_blocking_threads(1)
233            .enable_all()
234            .build()
235            .unwrap();
236
237        let result = runtime.block_on(async {
238            let address = Address::random();
239            let accounts =
240                AddressMap::from_iter([(address, ExtendedAccount::new(0, U256::from(1337)))]);
241            let eth_api = mock_eth_api(accounts);
242            // Use a block after access-list transactions became valid.
243            let mut block = Block::default();
244            block.header.number = 13_000_000;
245            block.header.timestamp = 1_629_000_000;
246            block.header.gas_limit = 30_000_000;
247            block.header.base_fee_per_gas = Some(1_000_000_000);
248            eth_api.provider().add_block(B256::ZERO, block);
249            tokio::time::timeout(Duration::from_secs(10), async {
250                let balance = eth_api.balance(address, Some(BlockId::pending())).await?;
251                eth_api
252                    .create_access_list_at(
253                        TransactionRequest { gas_price: Some(0), ..Default::default() },
254                        Some(BlockId::latest()),
255                        None,
256                    )
257                    .await?;
258                Ok::<_, EthApiError>(balance)
259            })
260            .await
261        });
262        // A deadlocked blocking thread would also block a regular runtime drop.
263        runtime.shutdown_background();
264        assert_eq!(
265            result.expect("RPC timed out on one blocking thread").expect("RPC returned an error"),
266            U256::from(1337)
267        );
268    }
269
270    type MockEthApi = EthApi<
271        RpcNodeCoreAdapter<MockEthProvider, TestPool, NoopNetwork, EthEvmConfig>,
272        EthRpcConverter<ChainSpec>,
273    >;
274
275    #[derive(Clone)]
276    struct CustomPendingState {
277        inner: MockEthApi,
278        pending: MockEthProvider,
279    }
280
281    impl EthApiTypes for CustomPendingState {
282        type Error = EthApiError;
283        type NetworkTypes = <MockEthApi as EthApiTypes>::NetworkTypes;
284        type RpcConvert = <MockEthApi as EthApiTypes>::RpcConvert;
285
286        fn eth_api_settings(&self) -> &EthApiSettings {
287            self.inner.eth_api_settings()
288        }
289
290        fn converter(&self) -> &Self::RpcConvert {
291            self.inner.converter()
292        }
293    }
294
295    impl RpcNodeCore for CustomPendingState {
296        type Primitives = <MockEthApi as RpcNodeCore>::Primitives;
297        type Provider = <MockEthApi as RpcNodeCore>::Provider;
298        type Pool = <MockEthApi as RpcNodeCore>::Pool;
299        type Evm = <MockEthApi as RpcNodeCore>::Evm;
300        type Network = <MockEthApi as RpcNodeCore>::Network;
301
302        fn pool(&self) -> &Self::Pool {
303            self.inner.pool()
304        }
305
306        fn evm_config(&self) -> &Self::Evm {
307            self.inner.evm_config()
308        }
309
310        fn network(&self) -> &Self::Network {
311            self.inner.network()
312        }
313
314        fn provider(&self) -> &Self::Provider {
315            self.inner.provider()
316        }
317    }
318
319    impl RpcNodeCoreExt for CustomPendingState {
320        fn cache(&self) -> &EthStateCache<Self::Primitives> {
321            self.inner.cache()
322        }
323    }
324
325    impl SpawnBlocking for CustomPendingState {
326        fn io_task_spawner(&self) -> &Runtime {
327            self.inner.io_task_spawner()
328        }
329
330        fn tracing_task_pool(&self) -> &BlockingTaskPool {
331            self.inner.tracing_task_pool()
332        }
333
334        fn tracing_task_guard(&self) -> &BlockingTaskGuard {
335            self.inner.tracing_task_guard()
336        }
337
338        fn blocking_io_task_guard(&self) -> &Arc<Semaphore> {
339            self.inner.blocking_io_task_guard()
340        }
341    }
342
343    impl LoadPendingBlock for CustomPendingState {
344        fn pending_block(&self) -> &Mutex<Option<PendingBlock<Self::Primitives>>> {
345            self.inner.pending_block()
346        }
347
348        fn pending_env_builder(&self) -> &dyn PendingEnvBuilder<Self::Evm> {
349            self.inner.pending_env_builder()
350        }
351
352        fn local_pending_state(
353            &self,
354        ) -> impl Future<Output = Result<Option<StateProviderBox>, Self::Error>> + Send {
355            let state = self.pending.latest().map_err(EthApiError::from);
356            async move { state.map(Some) }
357        }
358    }
359
360    impl LoadState for CustomPendingState {}
361
362    impl EthState for CustomPendingState {}
363
364    #[tokio::test]
365    async fn pending_state_reads_use_existing_override() {
366        let address = Address::random();
367        let chain = AddressMap::from_iter([(address, ExtendedAccount::new(0, U256::from(1337)))]);
368        let eth_api = mock_eth_api(chain);
369        eth_api.provider().add_block(B256::ZERO, Block::default());
370
371        let pending = MockEthProvider::default();
372        pending.extend_accounts([(address, ExtendedAccount::new(0, U256::from(42)))]);
373        let eth_api = CustomPendingState { inner: eth_api, pending };
374
375        assert_eq!(
376            eth_api.balance(address, Some(BlockId::pending())).await.unwrap(),
377            U256::from(42)
378        );
379        assert_eq!(eth_api.balance(address, None).await.unwrap(), U256::from(1337));
380    }
381
382    #[tokio::test]
383    async fn pending_transaction_count_skips_local_pending_block() {
384        let address = Address::random();
385        let accounts = AddressMap::from_iter([(address, ExtendedAccount::new(7, U256::ZERO))]);
386        let eth_api = mock_eth_api(accounts);
387        let mut block = Block::default();
388        block.header.number = 13_000_000;
389        block.header.timestamp = 1_629_000_000;
390        block.header.gas_limit = 30_000_000;
391        block.header.base_fee_per_gas = Some(1_000_000_000);
392        eth_api.provider().add_block(B256::ZERO, block);
393
394        let pending = Some(BlockId::pending());
395        let count = EthState::transaction_count(&eth_api, address, pending).await.unwrap();
396        assert_eq!(count, U256::from(7));
397
398        // Nonces 7 and 8 follow the account nonce, nonce 10 is gapped.
399        for nonce in [7, 8, 10] {
400            let tx = MockTransaction::eip1559().with_sender(address).with_nonce(nonce);
401            eth_api.pool().add_transaction(TransactionOrigin::External, tx).await.unwrap();
402        }
403        let count = EthState::transaction_count(&eth_api, address, pending).await.unwrap();
404        assert_eq!(count, U256::from(9));
405        assert_eq!(
406            EthState::transaction_count(&eth_api, address, None).await.unwrap(),
407            U256::from(7)
408        );
409
410        assert!(eth_api.pending_block().lock().await.is_none());
411    }
412}