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::{
5    pending_block::PendingStateSource, EthApiSpec, LoadBlock, LoadPendingBlock, SpawnBlocking,
6};
7use crate::{EthApiTypes, FromEthApiError, RpcNodeCore, RpcNodeCoreExt};
8use alloy_consensus::{constants::KECCAK_EMPTY, BlockHeader};
9use alloy_eips::BlockId;
10use alloy_primitives::{keccak256, Address, Bytes, B256, U256};
11use alloy_rpc_types_eth::{Account, AccountInfo, EIP1186AccountProofResponse};
12use alloy_serde::JsonStorageKey;
13use futures::Future;
14use reth_errors::RethError;
15use reth_evm::{ConfigureEvm, EvmEnvFor};
16use reth_primitives_traits::{BlockTy, RecoveredBlock, SealedHeaderFor};
17use reth_rpc_convert::{RpcConvert, RpcTxReq};
18use reth_rpc_eth_types::{
19    error::{FromEvmError, IntoEthApiError},
20    EthApiError, PendingBlockEnv, RpcInvalidTransactionError, SignError,
21};
22use reth_rpc_server_types::constants::{DEFAULT_MAX_PROOF_SLOTS, DEFAULT_MAX_STORAGE_VALUES_SLOTS};
23use reth_storage_api::{
24    BlockIdReader, BlockReaderIdExt, StateProvider, StateProviderBox, StateProviderFactory,
25};
26use reth_transaction_pool::TransactionPool;
27use reth_trie_common::{MultiProofTargetsV2, ProofV2Target};
28use std::{collections::HashMap, sync::Arc};
29
30/// Helper methods for `eth_` methods relating to state (accounts).
31pub trait EthState: LoadState + SpawnBlocking {
32    /// Returns the maximum number of blocks into the past for generating state proofs.
33    fn max_proof_window(&self) -> u64 {
34        self.eth_api_settings().eth_proof_window
35    }
36
37    /// Validates that the given block is within the configured proof window.
38    ///
39    /// Returns an error if the distance between the chain tip and the requested block exceeds
40    /// [`Self::max_proof_window`].
41    fn ensure_within_proof_window(&self, block_id: BlockId) -> Result<(), Self::Error>
42    where
43        Self: EthApiSpec,
44    {
45        let chain_info = self.chain_info().map_err(Self::Error::from_eth_err)?;
46        let block_number = self
47            .provider()
48            .block_number_for_id(block_id)
49            .map_err(Self::Error::from_eth_err)?
50            .ok_or(EthApiError::HeaderNotFound(block_id))?;
51        if chain_info.best_number.saturating_sub(block_number) > self.max_proof_window() {
52            return Err(EthApiError::ExceedsMaxProofWindow.into())
53        }
54        Ok(())
55    }
56
57    /// Returns the number of transactions sent from an address at the given block identifier.
58    ///
59    /// If this is [`BlockNumberOrTag::Pending`](alloy_eips::BlockNumberOrTag) then this will
60    /// look up the highest transaction in pool and return the next nonce (highest + 1).
61    fn transaction_count(
62        &self,
63        address: Address,
64        block_id: Option<BlockId>,
65    ) -> impl Future<Output = Result<U256, Self::Error>> + Send {
66        LoadState::transaction_count(self, address, block_id)
67    }
68
69    /// Returns code of given account, at given blocknumber.
70    fn get_code(
71        &self,
72        address: Address,
73        block_id: Option<BlockId>,
74    ) -> impl Future<Output = Result<Bytes, Self::Error>> + Send {
75        LoadState::get_code(self, address, block_id)
76    }
77
78    /// Returns balance of given account, at given blocknumber.
79    fn balance(
80        &self,
81        address: Address,
82        block_id: Option<BlockId>,
83    ) -> impl Future<Output = Result<U256, Self::Error>> + Send {
84        self.spawn_blocking_io_with_state(block_id.unwrap_or_default(), move |_, state| {
85            Ok(state
86                .account_balance(&address)
87                .map_err(Self::Error::from_eth_err)?
88                .unwrap_or_default())
89        })
90    }
91
92    /// Returns values stored of given account, at given blocknumber.
93    fn storage_at(
94        &self,
95        address: Address,
96        index: JsonStorageKey,
97        block_id: Option<BlockId>,
98    ) -> impl Future<Output = Result<B256, Self::Error>> + Send {
99        self.spawn_blocking_io_with_state(block_id.unwrap_or_default(), move |_, state| {
100            Ok(B256::from(
101                state
102                    .storage(address, index.as_b256())
103                    .map_err(Self::Error::from_eth_err)?
104                    .unwrap_or_default(),
105            ))
106        })
107    }
108
109    /// Returns values from multiple storage positions across multiple addresses.
110    ///
111    /// Enforces a cap on total slot count (sum of all slot arrays) and returns an error if
112    /// exceeded.
113    fn storage_values(
114        &self,
115        requests: HashMap<Address, Vec<JsonStorageKey>>,
116        block_id: Option<BlockId>,
117    ) -> impl Future<Output = Result<HashMap<Address, Vec<B256>>, Self::Error>> + Send {
118        async move {
119            if requests.is_empty() {
120                return Err(Self::Error::from_eth_err(EthApiError::InvalidParams(
121                    "empty request".to_string(),
122                )));
123            }
124            let total_slots: usize = requests.values().map(|slots| slots.len()).sum();
125            if total_slots > DEFAULT_MAX_STORAGE_VALUES_SLOTS {
126                return Err(Self::Error::from_eth_err(EthApiError::InvalidParams(
127                    format!(
128                        "total slot count {total_slots} exceeds limit {DEFAULT_MAX_STORAGE_VALUES_SLOTS}",
129                    ),
130                )));
131            }
132
133            self.spawn_blocking_io_with_state(block_id.unwrap_or_default(), move |_, state| {
134                let mut result = HashMap::with_capacity(requests.len());
135                for (address, slots) in requests {
136                    let mut values = Vec::with_capacity(slots.len());
137                    for slot in &slots {
138                        let value = state
139                            .storage(address, slot.as_b256())
140                            .map_err(Self::Error::from_eth_err)?
141                            .unwrap_or_default();
142                        values.push(B256::from(value));
143                    }
144                    result.insert(address, values);
145                }
146
147                Ok(result)
148            })
149            .await
150        }
151    }
152
153    /// Returns values stored of given account, with Merkle-proof, at given blocknumber.
154    ///
155    /// Enforces a cap of [`DEFAULT_MAX_PROOF_SLOTS`] on the number of requested keys, before a
156    /// proof permit is acquired.
157    fn get_proof(
158        &self,
159        address: Address,
160        keys: Vec<JsonStorageKey>,
161        block_id: Option<BlockId>,
162    ) -> Result<
163        impl Future<Output = Result<EIP1186AccountProofResponse, Self::Error>> + Send,
164        Self::Error,
165    >
166    where
167        Self: EthApiSpec,
168    {
169        if keys.len() > DEFAULT_MAX_PROOF_SLOTS {
170            return Err(Self::Error::from_eth_err(EthApiError::InvalidParams(format!(
171                "storage key count {} exceeds limit {DEFAULT_MAX_PROOF_SLOTS}",
172                keys.len(),
173            ))));
174        }
175
176        Ok(async move {
177            let permit = self
178                .acquire_owned_tracing()
179                .await
180                .map_err(RethError::other)
181                .map_err(EthApiError::Internal)?;
182
183            let block_id = block_id.unwrap_or_default();
184            self.ensure_within_proof_window(block_id)?;
185
186            self.spawn_blocking_io_with_state(block_id, move |_, state| {
187                let _permit = permit;
188                let storage_keys = keys.iter().map(|key| key.as_b256()).collect::<Vec<_>>();
189                let proof = state
190                    .proof(Default::default(), address, &storage_keys)
191                    .map_err(Self::Error::from_eth_err)?;
192                Ok(proof.into_eip1186_response_with(keys, true))
193            })
194            .await
195        })
196    }
197
198    /// Returns account and storage proofs for multiple targets at the given block number.
199    ///
200    /// Enforces a cap of [`DEFAULT_MAX_PROOF_SLOTS`] on both the number of targets and the total
201    /// slot count across all targets, before a proof permit is acquired.
202    fn get_multi_proof(
203        &self,
204        targets: Vec<(Address, Vec<B256>)>,
205        block_id: Option<BlockId>,
206    ) -> Result<
207        impl Future<Output = Result<Vec<EIP1186AccountProofResponse>, Self::Error>> + Send,
208        Self::Error,
209    >
210    where
211        Self: EthApiSpec,
212    {
213        if targets.len() > DEFAULT_MAX_PROOF_SLOTS {
214            return Err(Self::Error::from_eth_err(EthApiError::InvalidParams(format!(
215                "target count {} exceeds limit {DEFAULT_MAX_PROOF_SLOTS}",
216                targets.len(),
217            ))));
218        }
219        let total_slots: usize = targets.iter().map(|(_, slots)| slots.len()).sum();
220        if total_slots > DEFAULT_MAX_PROOF_SLOTS {
221            return Err(Self::Error::from_eth_err(EthApiError::InvalidParams(format!(
222                "total slot count {total_slots} exceeds limit {DEFAULT_MAX_PROOF_SLOTS}",
223            ))));
224        }
225
226        Ok(async move {
227            let permit = self
228                .acquire_owned_tracing()
229                .await
230                .map_err(RethError::other)
231                .map_err(EthApiError::Internal)?;
232
233            let block_id = block_id.unwrap_or_default();
234            self.ensure_within_proof_window(block_id)?;
235
236            self.spawn_blocking_io_with_state(block_id, move |_, state| {
237                let _permit = permit;
238                let mut proof_targets = MultiProofTargetsV2::default();
239                proof_targets.account_targets.reserve(targets.len());
240                proof_targets.storage_targets.reserve(targets.len());
241                for (address, slots) in &targets {
242                    let hashed_address = keccak256(address);
243                    proof_targets.account_targets.push(ProofV2Target::new(hashed_address));
244                    if !slots.is_empty() {
245                        proof_targets
246                            .storage_targets
247                            .entry(hashed_address)
248                            .or_default()
249                            .extend(slots.iter().map(|slot| ProofV2Target::new(keccak256(slot))));
250                    }
251                }
252
253                let multiproof =
254                    state.multiproof_v2(proof_targets, false).map_err(Self::Error::from_eth_err)?;
255
256                targets
257                    .into_iter()
258                    .map(|(address, slots)| {
259                        let proof = multiproof
260                            .account_proof(address, &slots)
261                            .map_err(RethError::other)
262                            .map_err(Self::Error::from_eth_err)?;
263                        let storage_keys =
264                            slots.into_iter().map(JsonStorageKey::from).collect::<Vec<_>>();
265                        Ok(proof.into_eip1186_response_with(storage_keys, true))
266                    })
267                    .collect::<Result<Vec<_>, Self::Error>>()
268            })
269            .await
270        })
271    }
272
273    /// Returns the account at the given address for the provided block identifier.
274    fn get_account(
275        &self,
276        address: Address,
277        block_id: BlockId,
278    ) -> impl Future<Output = Result<Option<Account>, Self::Error>> + Send
279    where
280        Self: EthApiSpec,
281    {
282        async move {
283            self.ensure_within_proof_window(block_id)?;
284
285            self.spawn_blocking_io_with_state(block_id, move |_, state| {
286                let account = state.basic_account(&address).map_err(Self::Error::from_eth_err)?;
287                let Some(account) = account else { return Ok(None) };
288
289                // Provide a default `HashedStorage` value in order to
290                // get the storage root hash of the current state.
291                let storage_root = state
292                    .storage_root(address, Default::default())
293                    .map_err(Self::Error::from_eth_err)?;
294
295                Ok(Some(account.into_trie_account(storage_root)))
296            })
297            .await
298        }
299    }
300
301    /// Retrieves the account's balance, nonce, and code for a given address.
302    #[allow(clippy::needless_update)]
303    fn get_account_info(
304        &self,
305        address: Address,
306        block_id: BlockId,
307    ) -> impl Future<Output = Result<AccountInfo, Self::Error>> + Send {
308        self.spawn_blocking_io_with_state(block_id, move |_, state| {
309            let account = state
310                .basic_account(&address)
311                .map_err(Self::Error::from_eth_err)?
312                .unwrap_or_default();
313
314            let balance = account.balance;
315            let nonce = account.nonce;
316            let code = if account.get_bytecode_hash() == KECCAK_EMPTY {
317                Default::default()
318            } else {
319                state
320                    .account_code(&address)
321                    .map_err(Self::Error::from_eth_err)?
322                    .unwrap_or_default()
323                    .original_bytes()
324            };
325
326            Ok(AccountInfo {
327                balance,
328                nonce,
329                code,
330                #[cfg(feature = "account-ext")]
331                extension: account.extension,
332                ..Default::default()
333            })
334        })
335    }
336}
337
338/// Loads state from database.
339///
340/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` state RPC methods.
341pub trait LoadState:
342    LoadPendingBlock
343    + EthApiTypes<
344        Error: FromEvmError<Self::Evm> + FromEthApiError,
345        RpcConvert: RpcConvert<Network = Self::NetworkTypes>,
346    > + RpcNodeCoreExt
347{
348    /// Returns the state at the given block number
349    fn state_at_hash(&self, block_hash: B256) -> Result<StateProviderBox, Self::Error> {
350        self.provider().history_by_block_hash(block_hash).map_err(Self::Error::from_eth_err)
351    }
352
353    /// Returns the state at the given [`BlockId`], preferring locally built state for `pending`.
354    ///
355    /// If local pending state is unavailable or fails to build, falls back to the provider's
356    /// pending state. Other block IDs resolve only canonical state. See
357    /// <https://github.com/paradigmxyz/reth/issues/4515>.
358    ///
359    /// Pending block construction may spawn blocking work, so await this outside a blocking task.
360    /// The fallback provider lookup is synchronous on the calling task; RPC handlers should use
361    /// [`Self::spawn_blocking_io_with_state`] to keep that read on the blocking pool.
362    fn state_at_block_id(
363        &self,
364        at: BlockId,
365    ) -> impl Future<Output = Result<StateProviderBox, Self::Error>> + Send
366    where
367        Self: SpawnBlocking,
368    {
369        async move {
370            if at.is_pending() &&
371                let Ok(Some(state)) = self.local_pending_state().await
372            {
373                return Ok(state)
374            }
375
376            self.provider().state_by_block_id(at).map_err(Self::Error::from_eth_err)
377        }
378    }
379
380    /// Returns the _latest_ state
381    fn latest_state(&self) -> Result<StateProviderBox, Self::Error> {
382        self.provider().latest().map_err(Self::Error::from_eth_err)
383    }
384
385    /// Returns the state at the given [`BlockId`] enum or the latest.
386    ///
387    /// Convenience function to interprets `None` as `BlockId::Number(BlockNumberOrTag::Latest)`
388    fn state_at_block_id_or_latest(
389        &self,
390        block_id: Option<BlockId>,
391    ) -> impl Future<Output = Result<StateProviderBox, Self::Error>> + Send
392    where
393        Self: SpawnBlocking,
394    {
395        async move {
396            if let Some(block_id) = block_id {
397                self.state_at_block_id(block_id).await
398            } else {
399                Ok(self.latest_state()?)
400            }
401        }
402    }
403
404    /// Executes `f` with the state at the given [`BlockId`] on a blocking IO task.
405    ///
406    /// Resolves the chain's pending-state source before occupying a blocking thread, since
407    /// building a pending block may need a blocking thread of its own.
408    fn spawn_blocking_io_with_state<F, R>(
409        &self,
410        at: BlockId,
411        f: F,
412    ) -> impl Future<Output = Result<R, Self::Error>> + Send
413    where
414        Self: SpawnBlocking,
415        F: FnOnce(Self, StateProviderBox) -> Result<R, Self::Error> + Send + 'static,
416        R: Send + 'static,
417    {
418        async move {
419            let pending = if at.is_pending() {
420                self.local_pending_block_or_state().await.ok().flatten()
421            } else {
422                None
423            };
424
425            self.spawn_blocking_io(move |this| {
426                let state = match pending {
427                    Some(PendingStateSource::Block(pending)) => this
428                        .provider()
429                        .state_with_block_appended(
430                            pending.block().parent_hash(),
431                            pending.executed_block,
432                        )
433                        .or_else(|_| this.provider().state_by_block_id(BlockId::pending()))
434                        .map_err(Self::Error::from_eth_err)?,
435                    Some(PendingStateSource::State(state)) => state,
436                    None if at.is_latest() => this.latest_state()?,
437                    None => {
438                        this.provider().state_by_block_id(at).map_err(Self::Error::from_eth_err)?
439                    }
440                };
441                f(this, state)
442            })
443            .await
444        }
445    }
446
447    /// Returns the EVM environment for the given sealed header.
448    fn evm_env_for_header(
449        &self,
450        header: &SealedHeaderFor<Self::Primitives>,
451    ) -> Result<EvmEnvFor<Self::Evm>, Self::Error> {
452        self.evm_config()
453            .evm_env(header)
454            .map_err(RethError::other)
455            .map_err(Self::Error::from_eth_err)
456    }
457
458    /// Returns the EVM environment for the requested [`BlockId`]
459    ///
460    /// If the [`BlockId`] this will return the [`BlockId`] of the block the env was configured
461    /// for.
462    /// If the [`BlockId`] is pending, this will return the "Pending" tag, otherwise this returns
463    /// the hash of the exact block.
464    fn evm_env_at(
465        &self,
466        at: BlockId,
467    ) -> impl Future<Output = Result<(EvmEnvFor<Self::Evm>, BlockId), Self::Error>> + Send
468    where
469        Self: SpawnBlocking,
470    {
471        async move {
472            if at.is_pending() {
473                let PendingBlockEnv { evm_env, origin } = self.pending_block_env_and_cfg()?;
474                Ok((evm_env, origin.state_block_id()))
475            } else {
476                // we can assume that the blockid will be predominantly `Latest` (e.g. for
477                // `eth_call`) and if requested by number or hash we can quickly fetch just the
478                // header
479                let header = RpcNodeCore::provider(self)
480                    .sealed_header_by_id(at)
481                    .map_err(Self::Error::from_eth_err)?
482                    .ok_or_else(|| EthApiError::HeaderNotFound(at))?;
483                let evm_env = self.evm_env_for_header(&header)?;
484
485                Ok((evm_env, header.hash().into()))
486            }
487        }
488    }
489
490    /// Returns the recovered block, EVM environment, and state block id for the requested
491    /// [`BlockId`].
492    ///
493    /// For pending blocks, this preserves the state id returned by [`Self::evm_env_at`], which can
494    /// be the pending tag for an actual pending block or the latest block hash when the pending env
495    /// is derived from latest.
496    #[expect(clippy::type_complexity)]
497    fn evm_env_and_recovered_block_at(
498        &self,
499        at: BlockId,
500    ) -> impl Future<
501        Output = Result<
502            (Arc<RecoveredBlock<BlockTy<Self::Primitives>>>, EvmEnvFor<Self::Evm>, BlockId),
503            Self::Error,
504        >,
505    > + Send
506    where
507        Self: LoadBlock,
508    {
509        async move {
510            if at.is_pending() {
511                let (evm_env, block_id) = self.evm_env_at(at).await?;
512                let block = self
513                    .recovered_block(block_id)
514                    .await?
515                    .ok_or_else(|| EthApiError::HeaderNotFound(at))?;
516
517                Ok((block, evm_env, block_id))
518            } else {
519                let block = self
520                    .recovered_block(at)
521                    .await?
522                    .ok_or_else(|| EthApiError::HeaderNotFound(at))?;
523                let evm_env = self.evm_env_for_header(block.sealed_block().sealed_header())?;
524                let block_id = block.hash().into();
525
526                Ok((block, evm_env, block_id))
527            }
528        }
529    }
530
531    /// Returns the next available nonce without gaps for the given address
532    /// Next available nonce is either the on chain nonce of the account or the highest consecutive
533    /// nonce in the pool + 1
534    ///
535    /// The provided request must have a from address set.
536    fn next_available_nonce_for(
537        &self,
538        request: &RpcTxReq<Self::NetworkTypes>,
539    ) -> impl Future<Output = Result<u64, Self::Error>> + Send
540    where
541        Self: SpawnBlocking,
542    {
543        let address = request.as_ref().from;
544        self.spawn_blocking_io(move |this| {
545            let address = match address {
546                Some(address) => address,
547                None => return Err(SignError::NoAccount.into_eth_err()),
548            };
549
550            // first fetch the on chain nonce of the account
551            let mut next_nonce = this
552                .latest_state()?
553                .account_nonce(&address)
554                .map_err(Self::Error::from_eth_err)?
555                .unwrap_or_default();
556
557            // Retrieve the highest consecutive transaction for the sender from the transaction pool
558            if let Some(highest_tx) =
559                this.pool().get_highest_consecutive_transaction_by_sender(address, next_nonce)
560            {
561                // Return the nonce of the highest consecutive transaction + 1
562                next_nonce = highest_tx.nonce().checked_add(1).ok_or_else(|| {
563                    Self::Error::from(EthApiError::InvalidTransaction(
564                        RpcInvalidTransactionError::NonceMaxValue,
565                    ))
566                })?;
567            }
568
569            Ok(next_nonce)
570        })
571    }
572
573    /// Returns the number of transactions sent from an address at the given block identifier.
574    ///
575    /// If this is [`BlockNumberOrTag::Pending`](alloy_eips::BlockNumberOrTag) then this will
576    /// look up the highest transaction in pool and return the next nonce (highest + 1).
577    fn transaction_count(
578        &self,
579        address: Address,
580        block_id: Option<BlockId>,
581    ) -> impl Future<Output = Result<U256, Self::Error>> + Send
582    where
583        Self: SpawnBlocking,
584    {
585        let at = block_id.unwrap_or_default();
586        self.spawn_blocking_io_with_state(at, move |this, state| {
587            this.transaction_count_with_state(address, at, &state)
588        })
589    }
590
591    /// Returns the number of transactions sent from an address, given the state at `at`.
592    ///
593    /// If `at` is the pending tag, this accounts for the sender's pool transactions that follow
594    /// the account's nonce in `state` without a gap. Called by [`Self::transaction_count`] once
595    /// the state is open, so implementations can choose the state the nonce is read from.
596    fn transaction_count_with_state(
597        &self,
598        address: Address,
599        at: BlockId,
600        state: &StateProviderBox,
601    ) -> Result<U256, Self::Error> {
602        // first fetch the on chain nonce of the account
603        let on_chain_account_nonce =
604            state.account_nonce(&address).map_err(Self::Error::from_eth_err)?.unwrap_or_default();
605
606        if at.is_pending() {
607            // for pending tag we need to find the highest nonce of txn in the pending state.
608            if let Some(highest_pool_tx) = self
609                .pool()
610                .get_highest_consecutive_transaction_by_sender(address, on_chain_account_nonce)
611            {
612                {
613                    // and the corresponding txcount is nonce + 1 of the highest tx in the pool
614                    // (on chain nonce is increased after tx)
615                    let next_tx_nonce =
616                        highest_pool_tx.nonce().checked_add(1).ok_or_else(|| {
617                            Self::Error::from(EthApiError::InvalidTransaction(
618                                RpcInvalidTransactionError::NonceMaxValue,
619                            ))
620                        })?;
621
622                    // guard against drifts in the pool
623                    let next_tx_nonce = on_chain_account_nonce.max(next_tx_nonce);
624
625                    let tx_count = on_chain_account_nonce.max(next_tx_nonce);
626                    return Ok(U256::from(tx_count));
627                }
628            }
629        }
630        Ok(U256::from(on_chain_account_nonce))
631    }
632
633    /// Returns code of given account, at the given identifier.
634    fn get_code(
635        &self,
636        address: Address,
637        block_id: Option<BlockId>,
638    ) -> impl Future<Output = Result<Bytes, Self::Error>> + Send
639    where
640        Self: SpawnBlocking,
641    {
642        self.spawn_blocking_io_with_state(block_id.unwrap_or_default(), move |_, state| {
643            Ok(state
644                .account_code(&address)
645                .map_err(Self::Error::from_eth_err)?
646                .unwrap_or_default()
647                .original_bytes())
648        })
649    }
650}