mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-01-29 09:08:05 -05:00
fix(rpc): replay transactions before tracing (#2532)
This commit is contained in:
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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<StateProviderBox<'_>>;
|
||||
|
||||
/// Executes the closure with the state that corresponds to the given [BlockId].
|
||||
fn with_state_at<F, T>(&self, at: BlockId, f: F) -> EthResult<T>
|
||||
fn with_state_at_block<F, T>(&self, at: BlockId, f: F) -> EthResult<T>
|
||||
where
|
||||
F: FnOnce(StateProviderBox<'_>) -> EthResult<T>;
|
||||
|
||||
@@ -57,6 +57,14 @@ pub trait EthTransactions: Send + Sync {
|
||||
async fn transactions_by_block(&self, block: H256)
|
||||
-> EthResult<Option<Vec<TransactionSigned>>>;
|
||||
|
||||
/// 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<Option<Vec<TransactionSigned>>>;
|
||||
|
||||
/// Returns the transaction by hash.
|
||||
///
|
||||
/// Checks the pool and state.
|
||||
@@ -116,7 +124,9 @@ pub trait EthTransactions: Send + Sync {
|
||||
where
|
||||
I: for<'r> Inspector<CacheDB<State<StateProviderBox<'r>>>> + 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<F, R>(
|
||||
@@ -129,8 +139,11 @@ pub trait EthTransactions: Send + Sync {
|
||||
where
|
||||
F: FnOnce(TracingInspector, ResultAndState) -> EthResult<R>;
|
||||
|
||||
/// Retrieves the transaction if it exists and returns its trace
|
||||
async fn trace_transaction<F, R>(
|
||||
/// 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<F, R>(
|
||||
&self,
|
||||
hash: H256,
|
||||
config: TracingInspectorConfig,
|
||||
@@ -151,7 +164,7 @@ where
|
||||
self.state_at_block_id(at)
|
||||
}
|
||||
|
||||
fn with_state_at<F, T>(&self, at: BlockId, f: F) -> EthResult<T>
|
||||
fn with_state_at_block<F, T>(&self, at: BlockId, f: F) -> EthResult<T>
|
||||
where
|
||||
F: FnOnce(StateProviderBox<'_>) -> EthResult<T>,
|
||||
{
|
||||
@@ -185,6 +198,16 @@ where
|
||||
Ok(self.cache().get_block_transactions(block).await?)
|
||||
}
|
||||
|
||||
async fn transactions_by_block_id(
|
||||
&self,
|
||||
block: BlockId,
|
||||
) -> EthResult<Option<Vec<TransactionSigned>>> {
|
||||
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<Option<TransactionSource>> {
|
||||
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<R>,
|
||||
{
|
||||
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<F, R>(
|
||||
async fn trace_transaction_in_block<F, R>(
|
||||
&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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<reth_primitives::H160> {
|
||||
let spec = match spec_id {
|
||||
@@ -39,10 +78,10 @@ pub(crate) fn get_precompiles(spec_id: &SpecId) -> Vec<reth_primitives::H160> {
|
||||
}
|
||||
|
||||
/// Executes the [Env] against the given [Database] without committing state changes.
|
||||
pub(crate) fn transact<S>(db: S, env: Env) -> EthResult<(ResultAndState, Env)>
|
||||
pub(crate) fn transact<DB>(db: DB, env: Env) -> EthResult<(ResultAndState, Env)>
|
||||
where
|
||||
S: Database,
|
||||
<S as Database>::Error: Into<EthApiError>,
|
||||
DB: Database,
|
||||
<DB as Database>::Error: Into<EthApiError>,
|
||||
{
|
||||
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, I, Tx>(
|
||||
db: &mut CacheDB<DB>,
|
||||
cfg: CfgEnv,
|
||||
block_env: BlockEnv,
|
||||
transactions: I,
|
||||
target_tx_hash: H256,
|
||||
) -> EthResult<()>
|
||||
where
|
||||
DB: DatabaseRef,
|
||||
EthApiError: From<<DB as DatabaseRef>::Error>,
|
||||
I: IntoIterator<Item = Tx>,
|
||||
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.
|
||||
|
||||
@@ -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<TraceResults> {
|
||||
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));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user