diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 350133e73e..843f2bcb02 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -1,7 +1,7 @@ use crate::{ eth::{ error::{EthApiError, EthResult}, - revm_utils::inspect, + revm_utils::{inspect, replay_transactions_until}, EthTransactions, TransactionSource, }, result::{internal_rpc_err, ToRpcResult}, @@ -95,7 +95,7 @@ where let transactions = transactions.ok_or_else(|| EthApiError::UnknownBlockNumber)?; // replay all transactions of the block - self.eth_api.with_state_at(at, move |state| { + self.eth_api.with_state_at_block(at, move |state| { let mut results = Vec::with_capacity(transactions.len()); let mut db = SubState::new(State::new(state)); @@ -125,13 +125,21 @@ where Some(res) => res, }; - let (cfg, block, at) = self.eth_api.evm_env_at(at).await?; + let block_env = self.eth_api.evm_env_at(at); + let block_txs = self.eth_api.transactions_by_block_id(at); + let (block_env, block_txs) = futures::try_join!(block_env, block_txs)?; + let block_txs = block_txs.unwrap_or_default(); + let (cfg, block_env, at) = block_env; - self.eth_api.with_state_at(at, |state| { + self.eth_api.with_state_at_block(at, |state| { + // configure env for the target transaction let tx = transaction.into_recovered(); - let tx = tx_env_with_recovered(&tx); - let env = Env { cfg, block, tx }; + let mut db = SubState::new(State::new(state)); + // replay all transactions prior to the targeted transaction + replay_transactions_until(&mut db, cfg.clone(), block_env.clone(), block_txs, tx.hash)?; + + let env = Env { cfg, block: block_env, tx: tx_env_with_recovered(&tx) }; trace_transaction(opts, env, &mut db) }) } diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs index 272b5f1f93..467993220b 100644 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ b/crates/rpc/rpc/src/eth/api/transactions.rs @@ -2,7 +2,7 @@ use crate::{ eth::{ error::{EthApiError, EthResult, SignError}, - revm_utils::{inspect, prepare_call_env, transact}, + revm_utils::{inspect, prepare_call_env, replay_transactions_until, transact}, utils::recover_raw_transaction, }, EthApi, EthApiSpec, @@ -41,7 +41,7 @@ pub trait EthTransactions: Send + Sync { fn state_at(&self, at: BlockId) -> EthResult>; /// Executes the closure with the state that corresponds to the given [BlockId]. - fn with_state_at(&self, at: BlockId, f: F) -> EthResult + fn with_state_at_block(&self, at: BlockId, f: F) -> EthResult where F: FnOnce(StateProviderBox<'_>) -> EthResult; @@ -57,6 +57,14 @@ pub trait EthTransactions: Send + Sync { async fn transactions_by_block(&self, block: H256) -> EthResult>>; + /// Get all transactions in the block with the given hash. + /// + /// Returns `None` if block does not exist. + async fn transactions_by_block_id( + &self, + block: BlockId, + ) -> EthResult>>; + /// Returns the transaction by hash. /// /// Checks the pool and state. @@ -116,7 +124,9 @@ pub trait EthTransactions: Send + Sync { where I: for<'r> Inspector>>> + Send; - /// Executes the transaction at the given [BlockId] with a tracer configured by the config. + /// Executes the transaction on top of the given [BlockId] with a tracer configured by the + /// config. + /// /// The callback is then called with the [TracingInspector] and the [ResultAndState] after the /// configured [Env] was inspected. fn trace_at( @@ -129,8 +139,11 @@ pub trait EthTransactions: Send + Sync { where F: FnOnce(TracingInspector, ResultAndState) -> EthResult; - /// Retrieves the transaction if it exists and returns its trace - async fn trace_transaction( + /// Retrieves the transaction if it exists and returns its trace. + /// + /// Before the transaction is traced, all previous transaction in the block are applied to the + /// state by executing them first + async fn trace_transaction_in_block( &self, hash: H256, config: TracingInspectorConfig, @@ -151,7 +164,7 @@ where self.state_at_block_id(at) } - fn with_state_at(&self, at: BlockId, f: F) -> EthResult + fn with_state_at_block(&self, at: BlockId, f: F) -> EthResult where F: FnOnce(StateProviderBox<'_>) -> EthResult, { @@ -185,6 +198,16 @@ where Ok(self.cache().get_block_transactions(block).await?) } + async fn transactions_by_block_id( + &self, + block: BlockId, + ) -> EthResult>> { + match self.client().block_hash_for_id(block)? { + None => Ok(None), + Some(hash) => self.transactions_by_block(hash).await, + } + } + async fn transaction_by_hash(&self, hash: H256) -> EthResult> { if let Some(tx) = self.pool().get(&hash).map(|tx| tx.transaction.to_recovered_transaction()) { @@ -391,7 +414,7 @@ where where F: FnOnce(TracingInspector, ResultAndState) -> EthResult, { - self.with_state_at(at, |state| { + self.with_state_at_block(at, |state| { let db = SubState::new(State::new(state)); let mut inspector = TracingInspector::new(config); @@ -401,7 +424,7 @@ where }) } - async fn trace_transaction( + async fn trace_transaction_in_block( &self, hash: H256, config: TracingInspectorConfig, @@ -414,14 +437,27 @@ where None => return Ok(None), Some(res) => res, }; - - let (cfg, block, at) = self.evm_env_at(at).await?; let (tx, tx_info) = transaction.split(); - let tx = tx_env_with_recovered(&tx); - let env = Env { cfg, block, tx }; - // execute the trace - self.trace_at(env, config, at, move |insp, res| f(tx_info, insp, res)).map(Some) + let block_env = self.evm_env_at(at); + let block_txs = self.transactions_by_block_id(at); + let (block_env, block_txs) = futures::try_join!(block_env, block_txs)?; + let block_txs = block_txs.unwrap_or_default(); + let (cfg, block_env, at) = block_env; + + self.with_state_at_block(at, |state| { + let mut db = SubState::new(State::new(state)); + + // replay all transactions prior to the targeted transaction + replay_transactions_until(&mut db, cfg.clone(), block_env.clone(), block_txs, tx.hash)?; + + let env = Env { cfg, block: block_env, tx: tx_env_with_recovered(&tx) }; + + let mut inspector = TracingInspector::new(config); + let (res, _) = inspect(db, env, &mut inspector)?; + f(tx_info, inspector, res) + }) + .map(Some) } } diff --git a/crates/rpc/rpc/src/eth/revm_utils.rs b/crates/rpc/rpc/src/eth/revm_utils.rs index c01caf2d4e..8d9f6cadad 100644 --- a/crates/rpc/rpc/src/eth/revm_utils.rs +++ b/crates/rpc/rpc/src/eth/revm_utils.rs @@ -1,7 +1,10 @@ //! utilities for working with revm use crate::eth::error::{EthApiError, EthResult, InvalidTransactionError}; -use reth_primitives::{AccessList, Address, U256}; +use reth_primitives::{ + AccessList, Address, TransactionSigned, TransactionSignedEcRecovered, TxHash, H256, U256, +}; +use reth_revm::env::{fill_tx_env, fill_tx_env_with_recovered}; use reth_rpc_types::{ state::{AccountOverride, StateOverride}, CallRequest, @@ -12,9 +15,45 @@ use revm::{ primitives::{BlockEnv, CfgEnv, Env, ResultAndState, SpecId, TransactTo, TxEnv}, Database, Inspector, }; -use revm_primitives::{db::DatabaseRef, Bytecode}; +use revm_primitives::{ + db::{DatabaseCommit, DatabaseRef}, + Bytecode, +}; use tracing::trace; +/// Helper type to work with different transaction types when configuring the EVM env. +/// +/// This makes it easier to handle errors. +pub(crate) trait FillableTransaction { + fn hash(&self) -> TxHash; + + /// Fill the transaction environment with the given transaction. + fn try_fill_tx_env(&self, tx_env: &mut TxEnv) -> EthResult<()>; +} + +impl FillableTransaction for TransactionSignedEcRecovered { + fn hash(&self) -> TxHash { + self.hash + } + + fn try_fill_tx_env(&self, tx_env: &mut TxEnv) -> EthResult<()> { + fill_tx_env_with_recovered(tx_env, self); + Ok(()) + } +} +impl FillableTransaction for TransactionSigned { + fn hash(&self) -> TxHash { + self.hash + } + + fn try_fill_tx_env(&self, tx_env: &mut TxEnv) -> EthResult<()> { + let signer = + self.recover_signer().ok_or_else(|| EthApiError::InvalidTransactionSignature)?; + fill_tx_env(tx_env, self, signer); + Ok(()) + } +} + /// Returns the addresses of the precompiles corresponding to the SpecId. pub(crate) fn get_precompiles(spec_id: &SpecId) -> Vec { let spec = match spec_id { @@ -39,10 +78,10 @@ pub(crate) fn get_precompiles(spec_id: &SpecId) -> Vec { } /// Executes the [Env] against the given [Database] without committing state changes. -pub(crate) fn transact(db: S, env: Env) -> EthResult<(ResultAndState, Env)> +pub(crate) fn transact(db: DB, env: Env) -> EthResult<(ResultAndState, Env)> where - S: Database, - ::Error: Into, + DB: Database, + ::Error: Into, { let mut evm = revm::EVM::with_env(env); evm.database(db); @@ -63,6 +102,41 @@ where Ok((res, evm.env)) } +/// Replays all the transactions until the target transaction is found. +/// +/// All transactions before the target transaction are executed and their changes are written to the +/// _runtime_ db ([CacheDB]). +/// +/// Note: This assumes the target transaction is in the given iterator. +pub(crate) fn replay_transactions_until( + db: &mut CacheDB, + cfg: CfgEnv, + block_env: BlockEnv, + transactions: I, + target_tx_hash: H256, +) -> EthResult<()> +where + DB: DatabaseRef, + EthApiError: From<::Error>, + I: IntoIterator, + Tx: FillableTransaction, +{ + let env = Env { cfg, block: block_env, tx: TxEnv::default() }; + let mut evm = revm::EVM::with_env(env); + evm.database(db); + for tx in transactions.into_iter() { + if tx.hash() == target_tx_hash { + // reached the target transaction + break + } + + tx.try_fill_tx_env(&mut evm.env.tx)?; + let res = evm.transact()?; + evm.db.as_mut().expect("is set").commit(res.state) + } + Ok(()) +} + /// Prepares the [Env] for execution. /// /// Does not commit any changes to the underlying database. diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index fb0e8a8e91..1a4369f1de 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -129,7 +129,7 @@ where let (cfg, block_env, at) = self.eth_api.evm_env_at(at).await?; // execute all transactions on top of each other and record the traces - self.eth_api.with_state_at(at, move |state| { + self.eth_api.with_state_at_block(at, move |state| { let mut results = Vec::with_capacity(calls.len()); let mut db = SubState::new(State::new(state)); @@ -155,7 +155,7 @@ where ) -> EthResult { let config = tracing_config(&trace_types); self.eth_api - .trace_transaction(hash, config, |_, inspector, res| { + .trace_transaction_in_block(hash, config, |_, inspector, res| { let trace_res = inspector.into_parity_builder().into_trace_results(res.result, &trace_types); Ok(trace_res) @@ -191,7 +191,7 @@ where let _permit = self.acquire_trace_permit().await; self.eth_api - .trace_transaction( + .trace_transaction_in_block( hash, TracingInspectorConfig::default_parity(), |tx_info, inspector, _| { @@ -226,7 +226,7 @@ where // replay all transactions of the block self.eth_api - .with_state_at(at, move |state| { + .with_state_at_block(at, move |state| { let mut results = Vec::with_capacity(transactions.len()); let mut db = SubState::new(State::new(state));