Skip to main content

reth_rpc_eth_api/helpers/
state.rs

1//! Loads a pending block from database. Helper trait for `eth_` block, transaction, call and trace
2//! RPC methods.
3
4use super::{EthApiSpec, LoadPendingBlock, SpawnBlocking};
5use crate::{EthApiTypes, FromEthApiError, RpcNodeCore, RpcNodeCoreExt};
6use alloy_consensus::constants::KECCAK_EMPTY;
7use alloy_eips::BlockId;
8use alloy_primitives::{Address, Bytes, B256, U256};
9use alloy_rpc_types_eth::{Account, AccountInfo, EIP1186AccountProofResponse};
10use alloy_serde::JsonStorageKey;
11use futures::Future;
12use reth_errors::RethError;
13use reth_evm::{ConfigureEvm, EvmEnvFor};
14use reth_primitives_traits::SealedHeaderFor;
15use reth_rpc_convert::{RpcConvert, RpcTxReq};
16use reth_rpc_eth_types::{
17    error::{FromEvmError, IntoEthApiError},
18    EthApiError, PendingBlockEnv, RpcInvalidTransactionError, SignError,
19};
20use reth_rpc_server_types::constants::DEFAULT_MAX_STORAGE_VALUES_SLOTS;
21use reth_storage_api::{
22    BlockIdReader, BlockReaderIdExt, StateProvider, StateProviderBox, StateProviderFactory,
23};
24use reth_transaction_pool::TransactionPool;
25use std::collections::HashMap;
26
27/// Helper methods for `eth_` methods relating to state (accounts).
28pub trait EthState: LoadState + SpawnBlocking {
29    /// Returns the maximum number of blocks into the past for generating state proofs.
30    fn max_proof_window(&self) -> u64;
31
32    /// Validates that the given block is within the configured proof window.
33    ///
34    /// Returns an error if the distance between the chain tip and the requested block exceeds
35    /// [`Self::max_proof_window`].
36    fn ensure_within_proof_window(&self, block_id: BlockId) -> Result<(), Self::Error>
37    where
38        Self: EthApiSpec,
39    {
40        let chain_info = self.chain_info().map_err(Self::Error::from_eth_err)?;
41        let block_number = self
42            .provider()
43            .block_number_for_id(block_id)
44            .map_err(Self::Error::from_eth_err)?
45            .ok_or(EthApiError::HeaderNotFound(block_id))?;
46        if chain_info.best_number.saturating_sub(block_number) > self.max_proof_window() {
47            return Err(EthApiError::ExceedsMaxProofWindow.into())
48        }
49        Ok(())
50    }
51
52    /// Returns the number of transactions sent from an address at the given block identifier.
53    ///
54    /// If this is [`BlockNumberOrTag::Pending`](alloy_eips::BlockNumberOrTag) then this will
55    /// look up the highest transaction in pool and return the next nonce (highest + 1).
56    fn transaction_count(
57        &self,
58        address: Address,
59        block_id: Option<BlockId>,
60    ) -> impl Future<Output = Result<U256, Self::Error>> + Send {
61        LoadState::transaction_count(self, address, block_id)
62    }
63
64    /// Returns code of given account, at given blocknumber.
65    fn get_code(
66        &self,
67        address: Address,
68        block_id: Option<BlockId>,
69    ) -> impl Future<Output = Result<Bytes, Self::Error>> + Send {
70        LoadState::get_code(self, address, block_id)
71    }
72
73    /// Returns balance of given account, at given blocknumber.
74    fn balance(
75        &self,
76        address: Address,
77        block_id: Option<BlockId>,
78    ) -> impl Future<Output = Result<U256, Self::Error>> + Send {
79        self.spawn_blocking_io_fut(async move |this| {
80            Ok(this
81                .state_at_block_id_or_latest(block_id)
82                .await?
83                .account_balance(&address)
84                .map_err(Self::Error::from_eth_err)?
85                .unwrap_or_default())
86        })
87    }
88
89    /// Returns values stored of given account, at given blocknumber.
90    fn storage_at(
91        &self,
92        address: Address,
93        index: JsonStorageKey,
94        block_id: Option<BlockId>,
95    ) -> impl Future<Output = Result<B256, Self::Error>> + Send {
96        self.spawn_blocking_io_fut(async move |this| {
97            Ok(B256::new(
98                this.state_at_block_id_or_latest(block_id)
99                    .await?
100                    .storage(address, index.as_b256())
101                    .map_err(Self::Error::from_eth_err)?
102                    .unwrap_or_default()
103                    .to_be_bytes(),
104            ))
105        })
106    }
107
108    /// Returns values from multiple storage positions across multiple addresses.
109    ///
110    /// Enforces a cap on total slot count (sum of all slot arrays) and returns an error if
111    /// exceeded.
112    fn storage_values(
113        &self,
114        requests: HashMap<Address, Vec<JsonStorageKey>>,
115        block_id: Option<BlockId>,
116    ) -> impl Future<Output = Result<HashMap<Address, Vec<B256>>, Self::Error>> + Send {
117        async move {
118            let total_slots: usize = requests.values().map(|slots| slots.len()).sum();
119            if total_slots > DEFAULT_MAX_STORAGE_VALUES_SLOTS {
120                return Err(Self::Error::from_eth_err(EthApiError::InvalidParams(
121                    format!(
122                        "total slot count {total_slots} exceeds limit {DEFAULT_MAX_STORAGE_VALUES_SLOTS}",
123                    ),
124                )));
125            }
126
127            self.spawn_blocking_io_fut(async move |this| {
128                let state = this.state_at_block_id_or_latest(block_id).await?;
129
130                let mut result = HashMap::with_capacity(requests.len());
131                for (address, slots) in requests {
132                    let mut values = Vec::with_capacity(slots.len());
133                    for slot in &slots {
134                        let value = state
135                            .storage(address, slot.as_b256())
136                            .map_err(Self::Error::from_eth_err)?
137                            .unwrap_or_default();
138                        values.push(B256::new(value.to_be_bytes()));
139                    }
140                    result.insert(address, values);
141                }
142
143                Ok(result)
144            })
145            .await
146        }
147    }
148
149    /// Returns values stored of given account, with Merkle-proof, at given blocknumber.
150    fn get_proof(
151        &self,
152        address: Address,
153        keys: Vec<JsonStorageKey>,
154        block_id: Option<BlockId>,
155    ) -> Result<
156        impl Future<Output = Result<EIP1186AccountProofResponse, Self::Error>> + Send,
157        Self::Error,
158    >
159    where
160        Self: EthApiSpec,
161    {
162        Ok(async move {
163            let _permit = self
164                .acquire_owned_tracing()
165                .await
166                .map_err(RethError::other)
167                .map_err(EthApiError::Internal)?;
168
169            let block_id = block_id.unwrap_or_default();
170            self.ensure_within_proof_window(block_id)?;
171
172            self.spawn_blocking_io_fut(async move |this| {
173                let state = this.state_at_block_id(block_id).await?;
174                let storage_keys = keys.iter().map(|key| key.as_b256()).collect::<Vec<_>>();
175                let proof = state
176                    .proof(Default::default(), address, &storage_keys)
177                    .map_err(Self::Error::from_eth_err)?;
178                Ok(proof.into_eip1186_response(keys))
179            })
180            .await
181        })
182    }
183
184    /// Returns the account at the given address for the provided block identifier.
185    fn get_account(
186        &self,
187        address: Address,
188        block_id: BlockId,
189    ) -> impl Future<Output = Result<Option<Account>, Self::Error>> + Send
190    where
191        Self: EthApiSpec,
192    {
193        async move {
194            self.ensure_within_proof_window(block_id)?;
195
196            self.spawn_blocking_io_fut(async move |this| {
197                let state = this.state_at_block_id(block_id).await?;
198                let account = state.basic_account(&address).map_err(Self::Error::from_eth_err)?;
199                let Some(account) = account else { return Ok(None) };
200
201                let balance = account.balance;
202                let nonce = account.nonce;
203                let code_hash = account.bytecode_hash.unwrap_or(KECCAK_EMPTY);
204
205                // Provide a default `HashedStorage` value in order to
206                // get the storage root hash of the current state.
207                let storage_root = state
208                    .storage_root(address, Default::default())
209                    .map_err(Self::Error::from_eth_err)?;
210
211                Ok(Some(Account { balance, nonce, code_hash, storage_root }))
212            })
213            .await
214        }
215    }
216
217    /// Retrieves the account's balance, nonce, and code for a given address.
218    fn get_account_info(
219        &self,
220        address: Address,
221        block_id: BlockId,
222    ) -> impl Future<Output = Result<AccountInfo, Self::Error>> + Send {
223        self.spawn_blocking_io_fut(async move |this| {
224            let state = this.state_at_block_id(block_id).await?;
225            let account = state
226                .basic_account(&address)
227                .map_err(Self::Error::from_eth_err)?
228                .unwrap_or_default();
229
230            let balance = account.balance;
231            let nonce = account.nonce;
232            let code = if account.get_bytecode_hash() == KECCAK_EMPTY {
233                Default::default()
234            } else {
235                state
236                    .account_code(&address)
237                    .map_err(Self::Error::from_eth_err)?
238                    .unwrap_or_default()
239                    .original_bytes()
240            };
241
242            Ok(AccountInfo { balance, nonce, code })
243        })
244    }
245}
246
247/// Loads state from database.
248///
249/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` state RPC methods.
250pub trait LoadState:
251    LoadPendingBlock
252    + EthApiTypes<
253        Error: FromEvmError<Self::Evm> + FromEthApiError,
254        RpcConvert: RpcConvert<Network = Self::NetworkTypes>,
255    > + RpcNodeCoreExt
256{
257    /// Returns the state at the given block number
258    fn state_at_hash(&self, block_hash: B256) -> Result<StateProviderBox, Self::Error> {
259        self.provider().history_by_block_hash(block_hash).map_err(Self::Error::from_eth_err)
260    }
261
262    /// Returns the state at the given [`BlockId`] enum.
263    ///
264    /// Note: if not [`BlockNumberOrTag::Pending`](alloy_eips::BlockNumberOrTag) then this
265    /// will only return canonical state. See also <https://github.com/paradigmxyz/reth/issues/4515>
266    fn state_at_block_id(
267        &self,
268        at: BlockId,
269    ) -> impl Future<Output = Result<StateProviderBox, Self::Error>> + Send
270    where
271        Self: SpawnBlocking,
272    {
273        async move {
274            if at.is_pending() &&
275                let Ok(Some(state)) = self.local_pending_state().await
276            {
277                return Ok(state)
278            }
279
280            self.provider().state_by_block_id(at).map_err(Self::Error::from_eth_err)
281        }
282    }
283
284    /// Returns the _latest_ state
285    fn latest_state(&self) -> Result<StateProviderBox, Self::Error> {
286        self.provider().latest().map_err(Self::Error::from_eth_err)
287    }
288
289    /// Returns the state at the given [`BlockId`] enum or the latest.
290    ///
291    /// Convenience function to interprets `None` as `BlockId::Number(BlockNumberOrTag::Latest)`
292    fn state_at_block_id_or_latest(
293        &self,
294        block_id: Option<BlockId>,
295    ) -> impl Future<Output = Result<StateProviderBox, Self::Error>> + Send
296    where
297        Self: SpawnBlocking,
298    {
299        async move {
300            if let Some(block_id) = block_id {
301                self.state_at_block_id(block_id).await
302            } else {
303                Ok(self.latest_state()?)
304            }
305        }
306    }
307
308    /// Returns the revm evm env for the given sealed header.
309    fn evm_env_for_header(
310        &self,
311        header: &SealedHeaderFor<Self::Primitives>,
312    ) -> Result<EvmEnvFor<Self::Evm>, Self::Error> {
313        self.evm_config()
314            .evm_env(header)
315            .map_err(RethError::other)
316            .map_err(Self::Error::from_eth_err)
317    }
318
319    /// Returns the revm evm env for the requested [`BlockId`]
320    ///
321    /// If the [`BlockId`] this will return the [`BlockId`] of the block the env was configured
322    /// for.
323    /// If the [`BlockId`] is pending, this will return the "Pending" tag, otherwise this returns
324    /// the hash of the exact block.
325    fn evm_env_at(
326        &self,
327        at: BlockId,
328    ) -> impl Future<Output = Result<(EvmEnvFor<Self::Evm>, BlockId), Self::Error>> + Send
329    where
330        Self: SpawnBlocking,
331    {
332        async move {
333            if at.is_pending() {
334                let PendingBlockEnv { evm_env, origin } = self.pending_block_env_and_cfg()?;
335                Ok((evm_env, origin.state_block_id()))
336            } else {
337                // we can assume that the blockid will be predominantly `Latest` (e.g. for
338                // `eth_call`) and if requested by number or hash we can quickly fetch just the
339                // header
340                let header = RpcNodeCore::provider(self)
341                    .sealed_header_by_id(at)
342                    .map_err(Self::Error::from_eth_err)?
343                    .ok_or_else(|| EthApiError::HeaderNotFound(at))?;
344                let evm_env = self.evm_env_for_header(&header)?;
345
346                Ok((evm_env, header.hash().into()))
347            }
348        }
349    }
350
351    /// Returns the next available nonce without gaps for the given address
352    /// Next available nonce is either the on chain nonce of the account or the highest consecutive
353    /// nonce in the pool + 1
354    ///
355    /// The provided request must have a from address set.
356    fn next_available_nonce_for(
357        &self,
358        request: &RpcTxReq<Self::NetworkTypes>,
359    ) -> impl Future<Output = Result<u64, Self::Error>> + Send
360    where
361        Self: SpawnBlocking,
362    {
363        let address = request.as_ref().from;
364        self.spawn_blocking_io(move |this| {
365            let address = match address {
366                Some(address) => address,
367                None => return Err(SignError::NoAccount.into_eth_err()),
368            };
369
370            // first fetch the on chain nonce of the account
371            let mut next_nonce = this
372                .latest_state()?
373                .account_nonce(&address)
374                .map_err(Self::Error::from_eth_err)?
375                .unwrap_or_default();
376
377            // Retrieve the highest consecutive transaction for the sender from the transaction pool
378            if let Some(highest_tx) =
379                this.pool().get_highest_consecutive_transaction_by_sender(address, next_nonce)
380            {
381                // Return the nonce of the highest consecutive transaction + 1
382                next_nonce = highest_tx.nonce().checked_add(1).ok_or_else(|| {
383                    Self::Error::from(EthApiError::InvalidTransaction(
384                        RpcInvalidTransactionError::NonceMaxValue,
385                    ))
386                })?;
387            }
388
389            Ok(next_nonce)
390        })
391    }
392
393    /// Returns the number of transactions sent from an address at the given block identifier.
394    ///
395    /// If this is [`BlockNumberOrTag::Pending`](alloy_eips::BlockNumberOrTag) then this will
396    /// look up the highest transaction in pool and return the next nonce (highest + 1).
397    fn transaction_count(
398        &self,
399        address: Address,
400        block_id: Option<BlockId>,
401    ) -> impl Future<Output = Result<U256, Self::Error>> + Send
402    where
403        Self: SpawnBlocking,
404    {
405        self.spawn_blocking_io_fut(async move |this| {
406            // first fetch the on chain nonce of the account
407            let on_chain_account_nonce = this
408                .state_at_block_id_or_latest(block_id)
409                .await?
410                .account_nonce(&address)
411                .map_err(Self::Error::from_eth_err)?
412                .unwrap_or_default();
413
414            if block_id == Some(BlockId::pending()) {
415                // for pending tag we need to find the highest nonce of txn in the pending state.
416                if let Some(highest_pool_tx) = this
417                    .pool()
418                    .get_highest_consecutive_transaction_by_sender(address, on_chain_account_nonce)
419                {
420                    {
421                        // and the corresponding txcount is nonce + 1 of the highest tx in the pool
422                        // (on chain nonce is increased after tx)
423                        let next_tx_nonce =
424                            highest_pool_tx.nonce().checked_add(1).ok_or_else(|| {
425                                Self::Error::from(EthApiError::InvalidTransaction(
426                                    RpcInvalidTransactionError::NonceMaxValue,
427                                ))
428                            })?;
429
430                        // guard against drifts in the pool
431                        let next_tx_nonce = on_chain_account_nonce.max(next_tx_nonce);
432
433                        let tx_count = on_chain_account_nonce.max(next_tx_nonce);
434                        return Ok(U256::from(tx_count));
435                    }
436                }
437            }
438            Ok(U256::from(on_chain_account_nonce))
439        })
440    }
441
442    /// Returns code of given account, at the given identifier.
443    fn get_code(
444        &self,
445        address: Address,
446        block_id: Option<BlockId>,
447    ) -> impl Future<Output = Result<Bytes, Self::Error>> + Send
448    where
449        Self: SpawnBlocking,
450    {
451        self.spawn_blocking_io_fut(async move |this| {
452            Ok(this
453                .state_at_block_id_or_latest(block_id)
454                .await?
455                .account_code(&address)
456                .map_err(Self::Error::from_eth_err)?
457                .unwrap_or_default()
458                .original_bytes())
459        })
460    }
461}