fix(rpc): replay transactions before tracing (#2532)

This commit is contained in:
Matthias Seitz
2023-05-04 20:07:49 +02:00
committed by GitHub
parent 91d4c890de
commit cf043d9bf5
4 changed files with 147 additions and 29 deletions

View File

@@ -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)
})
}

View File

@@ -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)
}
}

View File

@@ -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.

View File

@@ -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));