From 78f62dd34c8c6c1b0a045990d4bf0d235a9d32b2 Mon Sep 17 00:00:00 2001 From: Daniel Ramirez Date: Thu, 2 May 2024 10:30:04 -0400 Subject: [PATCH] feat: add spawn_replay_transaction to EthTransactions (#8036) Co-authored-by: Oliver Nordbjerg --- crates/rpc/rpc/src/eth/api/transactions.rs | 55 ++++++++++++++++++++++ crates/rpc/rpc/src/otterscan.rs | 13 ++--- 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs index 15e2b6f565..51bde5bfaa 100644 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ b/crates/rpc/rpc/src/eth/api/transactions.rs @@ -376,6 +376,20 @@ pub trait EthTransactions: Send + Sync { .await } + /// 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. + /// The callback `f` is invoked with the [ResultAndState] after the transaction was executed and + /// the database that points to the beginning of the transaction. + /// + /// Note: Implementers should use a threadpool where blocking is allowed, such as + /// [BlockingTaskPool](reth_tasks::pool::BlockingTaskPool). + async fn spawn_replay_transaction(&self, hash: B256, f: F) -> EthResult> + where + F: FnOnce(TransactionInfo, ResultAndState, StateCacheDB) -> EthResult + Send + 'static, + R: Send + 'static; + /// 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 @@ -1173,6 +1187,47 @@ where Ok(block.map(|block| (transaction, block.seal(block_hash)))) } + async fn spawn_replay_transaction(&self, hash: B256, f: F) -> EthResult> + where + F: FnOnce(TransactionInfo, ResultAndState, StateCacheDB) -> EthResult + Send + 'static, + R: Send + 'static, + { + let (transaction, block) = match self.transaction_and_block(hash).await? { + None => return Ok(None), + Some(res) => res, + }; + let (tx, tx_info) = transaction.split(); + + let (cfg, block_env, _) = self.evm_env_at(block.hash().into()).await?; + + // we need to get the state of the parent block because we're essentially replaying the + // block the transaction is included in + let parent_block = block.parent_hash; + let block_txs = block.into_transactions_ecrecovered(); + + let this = self.clone(); + self.spawn_with_state_at_block(parent_block.into(), move |state| { + let mut db = CacheDB::new(StateProviderDatabase::new(state)); + + // replay all transactions prior to the targeted transaction + this.replay_transactions_until( + &mut db, + cfg.clone(), + block_env.clone(), + block_txs, + tx.hash, + )?; + + let env = + EnvWithHandlerCfg::new_with_cfg_env(cfg, block_env, tx_env_with_recovered(&tx)); + + let (res, _) = this.transact(&mut db, env)?; + f(tx_info, res, db) + }) + .await + .map(Some) + } + async fn spawn_trace_transaction_in_block_with_inspector( &self, hash: B256, diff --git a/crates/rpc/rpc/src/otterscan.rs b/crates/rpc/rpc/src/otterscan.rs index bdfbc1293e..2f62e66a31 100644 --- a/crates/rpc/rpc/src/otterscan.rs +++ b/crates/rpc/rpc/src/otterscan.rs @@ -1,7 +1,6 @@ use alloy_primitives::Bytes; use async_trait::async_trait; use jsonrpsee::core::RpcResult; -use revm::inspectors::NoOpInspector; use revm_inspectors::transfer::{TransferInspector, TransferKind}; use revm_primitives::ExecutionResult; @@ -81,14 +80,10 @@ where async fn get_transaction_error(&self, tx_hash: TxHash) -> RpcResult> { let maybe_revert = self .eth - .spawn_trace_transaction_in_block_with_inspector( - tx_hash, - NoOpInspector, - |_tx_info, _inspector, res, _| match res.result { - ExecutionResult::Revert { output, .. } => Ok(Some(output)), - _ => Ok(None), - }, - ) + .spawn_replay_transaction(tx_hash, |_tx_info, res, _| match res.result { + ExecutionResult::Revert { output, .. } => Ok(Some(output)), + _ => Ok(None), + }) .await .map(Option::flatten)?; Ok(maybe_revert)