diff --git a/crates/revm/revm-inspectors/src/tracing/arena.rs b/crates/revm/revm-inspectors/src/tracing/arena.rs index 08bfa65bf9..e8e14075d4 100644 --- a/crates/revm/revm-inspectors/src/tracing/arena.rs +++ b/crates/revm/revm-inspectors/src/tracing/arena.rs @@ -1,11 +1,4 @@ use crate::tracing::types::{CallTrace, CallTraceNode, LogCallOrder}; -use reth_primitives::{Address, JsonU256, H256, U256}; -use reth_rpc_types::trace::{ - geth::{DefaultFrame, GethDebugTracingOptions, StructLog}, - parity::{ActionType, TransactionTrace}, -}; -use revm::interpreter::{opcode, InstructionResult}; -use std::collections::{BTreeMap, HashMap}; /// An arena of recorded traces. /// @@ -49,112 +42,4 @@ impl CallTraceArena { ), } } - - /// Returns the traces of the transaction for `trace_transaction` - pub fn parity_traces(&self) -> Vec { - let traces = Vec::with_capacity(self.arena.len()); - for (_idx, node) in self.arena.iter().cloned().enumerate() { - let _action = node.parity_action(); - let _result = node.parity_result(); - - let _action_type = if node.status() == InstructionResult::SelfDestruct { - ActionType::Selfdestruct - } else { - node.kind().into() - }; - - todo!() - - // let trace = TransactionTrace { - // action, - // result: Some(result), - // trace_address: self.info.trace_address(idx), - // subtraces: node.children.len(), - // }; - // traces.push(trace) - } - - traces - } - - /// Recursively fill in the geth trace by going through the traces - /// - /// TODO rewrite this iteratively - fn add_to_geth_trace( - &self, - storage: &mut HashMap>, - trace_node: &CallTraceNode, - struct_logs: &mut Vec, - opts: &GethDebugTracingOptions, - ) { - let mut child_id = 0; - // Iterate over the steps inside the given trace - for step in trace_node.trace.steps.iter() { - let mut log: StructLog = step.into(); - - // Fill in memory and storage depending on the options - if !opts.disable_storage.unwrap_or_default() { - let contract_storage = storage.entry(step.contract).or_default(); - if let Some((key, value)) = step.state_diff { - contract_storage.insert(key.into(), value.into()); - log.storage = Some(contract_storage.clone()); - } - } - if opts.disable_stack.unwrap_or_default() { - log.stack = None; - } - if !opts.enable_memory.unwrap_or_default() { - log.memory = None; - } - - // Add step to geth trace - struct_logs.push(log); - - // If the opcode is a call, the descend into child trace - match step.op.u8() { - opcode::CREATE | - opcode::CREATE2 | - opcode::DELEGATECALL | - opcode::CALL | - opcode::STATICCALL | - opcode::CALLCODE => { - self.add_to_geth_trace( - storage, - &self.arena[trace_node.children[child_id]], - struct_logs, - opts, - ); - child_id += 1; - } - _ => {} - } - } - } - - /// Generate a geth-style trace e.g. for `debug_traceTransaction` - pub fn geth_traces( - &self, - // TODO(mattsse): This should be the total gas used, or gas used by last CallTrace? - receipt_gas_used: U256, - opts: GethDebugTracingOptions, - ) -> DefaultFrame { - if self.arena.is_empty() { - return Default::default() - } - // Fetch top-level trace - let main_trace_node = &self.arena[0]; - let main_trace = &main_trace_node.trace; - - let mut struct_logs = Vec::new(); - let mut storage = HashMap::new(); - self.add_to_geth_trace(&mut storage, main_trace_node, &mut struct_logs, &opts); - - DefaultFrame { - // If the top-level trace succeeded, then it was a success - failed: !main_trace.success, - gas: JsonU256(receipt_gas_used), - return_value: main_trace.output.clone().into(), - struct_logs, - } - } } diff --git a/crates/revm/revm-inspectors/src/tracing/builder/geth.rs b/crates/revm/revm-inspectors/src/tracing/builder/geth.rs new file mode 100644 index 0000000000..305ac18bfa --- /dev/null +++ b/crates/revm/revm-inspectors/src/tracing/builder/geth.rs @@ -0,0 +1,104 @@ +//! Geth trace builder + +use crate::tracing::{types::CallTraceNode, TraceInspectorConfig}; +use reth_primitives::{Address, JsonU256, H256, U256}; +use reth_rpc_types::trace::geth::*; +use revm::interpreter::opcode; +use std::collections::{BTreeMap, HashMap}; + +/// A type for creating geth style traces +#[derive(Clone, Debug)] +pub struct GethTraceBuilder { + /// Recorded trace nodes. + nodes: Vec, + /// How the traces were recorded + _config: TraceInspectorConfig, +} + +impl GethTraceBuilder { + /// Returns a new instance of the builder + pub(crate) fn new(nodes: Vec, _config: TraceInspectorConfig) -> Self { + Self { nodes, _config } + } + + /// Recursively fill in the geth trace by going through the traces + /// + /// TODO rewrite this iteratively + fn add_to_geth_trace( + &self, + storage: &mut HashMap>, + trace_node: &CallTraceNode, + struct_logs: &mut Vec, + opts: &GethDebugTracingOptions, + ) { + let mut child_id = 0; + // Iterate over the steps inside the given trace + for step in trace_node.trace.steps.iter() { + let mut log: StructLog = step.into(); + + // Fill in memory and storage depending on the options + if !opts.disable_storage.unwrap_or_default() { + let contract_storage = storage.entry(step.contract).or_default(); + if let Some((key, value)) = step.state_diff { + contract_storage.insert(key.into(), value.into()); + log.storage = Some(contract_storage.clone()); + } + } + if opts.disable_stack.unwrap_or_default() { + log.stack = None; + } + if !opts.enable_memory.unwrap_or_default() { + log.memory = None; + } + + // Add step to geth trace + struct_logs.push(log); + + // If the opcode is a call, the descend into child trace + match step.op.u8() { + opcode::CREATE | + opcode::CREATE2 | + opcode::DELEGATECALL | + opcode::CALL | + opcode::STATICCALL | + opcode::CALLCODE => { + self.add_to_geth_trace( + storage, + &self.nodes[trace_node.children[child_id]], + struct_logs, + opts, + ); + child_id += 1; + } + _ => {} + } + } + } + + /// Generate a geth-style trace e.g. for `debug_traceTransaction` + pub fn geth_traces( + &self, + // TODO(mattsse): This should be the total gas used, or gas used by last CallTrace? + receipt_gas_used: U256, + opts: GethDebugTracingOptions, + ) -> DefaultFrame { + if self.nodes.is_empty() { + return Default::default() + } + // Fetch top-level trace + let main_trace_node = &self.nodes[0]; + let main_trace = &main_trace_node.trace; + + let mut struct_logs = Vec::new(); + let mut storage = HashMap::new(); + self.add_to_geth_trace(&mut storage, main_trace_node, &mut struct_logs, &opts); + + DefaultFrame { + // If the top-level trace succeeded, then it was a success + failed: !main_trace.success, + gas: JsonU256(receipt_gas_used), + return_value: main_trace.output.clone().into(), + struct_logs, + } + } +} diff --git a/crates/revm/revm-inspectors/src/tracing/builder/mod.rs b/crates/revm/revm-inspectors/src/tracing/builder/mod.rs new file mode 100644 index 0000000000..a39a31913e --- /dev/null +++ b/crates/revm/revm-inspectors/src/tracing/builder/mod.rs @@ -0,0 +1,4 @@ +//! Builder types for building traces + +pub(crate) mod geth; +pub(crate) mod parity; diff --git a/crates/revm/revm-inspectors/src/tracing/builder/parity.rs b/crates/revm/revm-inspectors/src/tracing/builder/parity.rs new file mode 100644 index 0000000000..9694086140 --- /dev/null +++ b/crates/revm/revm-inspectors/src/tracing/builder/parity.rs @@ -0,0 +1,104 @@ +use crate::tracing::{types::CallTraceNode, TraceInspectorConfig}; +use reth_rpc_types::{trace::parity::*, TransactionInfo}; + +/// A type for creating parity style traces +#[derive(Clone, Debug)] +pub struct ParityTraceBuilder { + /// Recorded trace nodes + nodes: Vec, + /// How the traces were recorded + _config: TraceInspectorConfig, +} + +impl ParityTraceBuilder { + /// Returns a new instance of the builder + pub(crate) fn new(nodes: Vec, _config: TraceInspectorConfig) -> Self { + Self { nodes, _config } + } + + /// Returns the trace addresses of all transactions in the set + fn trace_addresses(&self) -> Vec> { + let mut all_addresses = Vec::with_capacity(self.nodes.len()); + for idx in 0..self.nodes.len() { + all_addresses.push(self.trace_address(idx)); + } + all_addresses + } + + /// Returns the `traceAddress` of the node in the arena + /// + /// The `traceAddress` field of all returned traces, gives the exact location in the call trace + /// [index in root, index in first CALL, index in second CALL, …]. + /// + /// # Panics + /// + /// if the `idx` does not belong to a node + fn trace_address(&self, idx: usize) -> Vec { + if idx == 0 { + // root call has empty traceAddress + return vec![] + } + let mut graph = vec![]; + let mut node = &self.nodes[idx]; + while let Some(parent) = node.parent { + // the index of the child call in the arena + let child_idx = node.idx; + node = &self.nodes[parent]; + // find the index of the child call in the parent node + let call_idx = node + .children + .iter() + .position(|child| *child == child_idx) + .expect("child exists in parent"); + graph.push(call_idx); + } + graph.reverse(); + graph + } + + /// Returns an iterator over all recorded traces for `trace_transaction` + pub fn into_localized_transaction_traces_iter( + self, + info: TransactionInfo, + ) -> impl Iterator { + self.into_transaction_traces_iter().map(move |trace| { + let TransactionInfo { hash, index, block_hash, block_number } = info; + LocalizedTransactionTrace { + trace, + transaction_position: index, + transaction_hash: hash, + block_number, + block_hash, + } + }) + } + + /// Returns an iterator over all recorded traces for `trace_transaction` + pub fn into_localized_transaction_traces( + self, + info: TransactionInfo, + ) -> Vec { + self.into_localized_transaction_traces_iter(info).collect() + } + + /// Returns an iterator over all recorded traces for `trace_transaction` + pub fn into_transaction_traces_iter(self) -> impl Iterator { + let trace_addresses = self.trace_addresses(); + + self.nodes.into_iter().zip(trace_addresses).map(|(node, trace_address)| { + let action = node.parity_action(); + let output = TraceResult::parity_success(node.parity_trace_output()); + TransactionTrace { + action, + result: Some(output), + trace_address, + subtraces: node.children.len(), + } + }) + } + + /// Returns the raw traces of the transaction + pub fn into_transaction_traces(self) -> Vec { + self.into_transaction_traces_iter().collect() + } +} diff --git a/crates/revm/revm-inspectors/src/tracing/mod.rs b/crates/revm/revm-inspectors/src/tracing/mod.rs index 958fc2c64c..4dd8b53f4f 100644 --- a/crates/revm/revm-inspectors/src/tracing/mod.rs +++ b/crates/revm/revm-inspectors/src/tracing/mod.rs @@ -18,9 +18,11 @@ use revm::{ use types::{CallTrace, CallTraceStep}; mod arena; +mod builder; mod config; mod types; mod utils; +pub use builder::{geth::GethTraceBuilder, parity::ParityTraceBuilder}; pub use config::TraceInspectorConfig; /// An inspector that collects call traces. @@ -60,9 +62,14 @@ impl TracingInspector { } } - /// Consumes the Inspector and returns the recorded. - pub fn finalize(self) -> CallTraceArena { - self.traces + /// Consumes the Inspector and returns a [ParityTraceBuilder]. + pub fn into_parity_builder(self) -> ParityTraceBuilder { + ParityTraceBuilder::new(self.traces.arena, self.config) + } + + /// Consumes the Inspector and returns a [GethTraceBuilder]. + pub fn into_geth_builder(self) -> GethTraceBuilder { + GethTraceBuilder::new(self.traces.arena, self.config) } /// Configures a [GasInspector] diff --git a/crates/revm/revm-inspectors/src/tracing/types.rs b/crates/revm/revm-inspectors/src/tracing/types.rs index 3c13d29135..963eae53f3 100644 --- a/crates/revm/revm-inspectors/src/tracing/types.rs +++ b/crates/revm/revm-inspectors/src/tracing/types.rs @@ -158,7 +158,7 @@ impl CallTraceNode { } /// Returns the `Output` for a parity trace - pub(crate) fn parity_result(&self) -> TraceOutput { + pub(crate) fn parity_trace_output(&self) -> TraceOutput { match self.kind() { CallKind::Call | CallKind::StaticCall | CallKind::CallCode | CallKind::DelegateCall => { TraceOutput::Call(CallOutput { diff --git a/crates/revm/revm-primitives/src/env.rs b/crates/revm/revm-primitives/src/env.rs index a9ceac12de..1f81ec4c78 100644 --- a/crates/revm/revm-primitives/src/env.rs +++ b/crates/revm/revm-primitives/src/env.rs @@ -1,7 +1,7 @@ use crate::config::revm_spec; use reth_primitives::{ - Address, ChainSpec, Head, Header, Transaction, TransactionKind, TransactionSigned, TxEip1559, - TxEip2930, TxLegacy, U256, + Address, ChainSpec, Head, Header, Transaction, TransactionKind, TransactionSignedEcRecovered, + TxEip1559, TxEip2930, TxLegacy, U256, }; use revm::primitives::{AnalysisKind, BlockEnv, CfgEnv, SpecId, TransactTo, TxEnv}; @@ -57,8 +57,23 @@ pub fn fill_block_env(block_env: &mut BlockEnv, header: &Header, after_merge: bo block_env.gas_limit = U256::from(header.gas_limit); } -/// Fill transaction environment from Transaction. -pub fn fill_tx_env(tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { +/// Returns a new [TxEnv] filled with the transaction's data. +pub fn tx_env_with_recovered(transaction: &TransactionSignedEcRecovered) -> TxEnv { + let mut tx_env = TxEnv::default(); + fill_tx_env(&mut tx_env, transaction.as_ref(), transaction.signer()); + tx_env +} + +/// Fill transaction environment from [TransactionSignedEcRecovered]. +pub fn fill_tx_env_with_recovered(tx_env: &mut TxEnv, transaction: &TransactionSignedEcRecovered) { + fill_tx_env(tx_env, transaction.as_ref(), transaction.signer()) +} + +/// Fill transaction environment from a [Transaction] and the given sender address. +pub fn fill_tx_env(tx_env: &mut TxEnv, transaction: T, sender: Address) +where + T: AsRef, +{ tx_env.caller = sender; match transaction.as_ref() { Transaction::Legacy(TxLegacy { diff --git a/crates/rpc/rpc-types/src/eth/trace/parity.rs b/crates/rpc/rpc-types/src/eth/trace/parity.rs index 14df580898..a6f865a172 100644 --- a/crates/rpc/rpc-types/src/eth/trace/parity.rs +++ b/crates/rpc/rpc-types/src/eth/trace/parity.rs @@ -10,6 +10,20 @@ use std::collections::BTreeMap; /// Result type for parity style transaction trace pub type TraceResult = crate::trace::common::TraceResult; +// === impl TraceResult === + +impl TraceResult { + /// Wraps the result type in a [TraceResult::Success] variant + pub fn parity_success(result: TraceOutput) -> Self { + TraceResult::Success { result } + } + + /// Wraps the result type in a [TraceResult::Error] variant + pub fn parity_error(error: String) -> Self { + TraceResult::Error { error } + } +} + #[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum TraceType { @@ -190,10 +204,18 @@ pub struct TransactionTrace { pub struct LocalizedTransactionTrace { #[serde(flatten)] pub trace: TransactionTrace, + /// Transaction index within the block, None if pending. pub transaction_position: Option, + /// Hash of the transaction pub transaction_hash: Option, - pub block_number: U64, - pub block_hash: H256, + /// Block number the transaction is included in, None if pending. + /// + /// Note: this deviates from which always returns a block number + pub block_number: Option, + /// Hash of the block, if not pending + /// + /// Note: this deviates from which always returns a block number + pub block_hash: Option, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] diff --git a/crates/rpc/rpc-types/src/eth/transaction/common.rs b/crates/rpc/rpc-types/src/eth/transaction/common.rs new file mode 100644 index 0000000000..fd02a66bc1 --- /dev/null +++ b/crates/rpc/rpc-types/src/eth/transaction/common.rs @@ -0,0 +1,17 @@ +//! Commonly used additional types that are not part of the JSON RPC spec but are often required +//! when working with RPC types, such as [Transaction](crate::Transaction) + +use reth_primitives::{TxHash, H256}; + +/// Additional fields in the context of a block that contains this transaction. +#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)] +pub struct TransactionInfo { + /// Hash of the transaction. + pub hash: Option, + /// Index of the transaction in the block + pub index: Option, + /// Hash of the block. + pub block_hash: Option, + /// Number of the block. + pub block_number: Option, +} diff --git a/crates/rpc/rpc-types/src/eth/transaction/mod.rs b/crates/rpc/rpc-types/src/eth/transaction/mod.rs index a5a169bf9b..8aa3e7be1d 100644 --- a/crates/rpc/rpc-types/src/eth/transaction/mod.rs +++ b/crates/rpc/rpc-types/src/eth/transaction/mod.rs @@ -1,8 +1,10 @@ +mod common; mod receipt; mod request; mod signature; mod typed; +pub use common::TransactionInfo; pub use receipt::TransactionReceipt; pub use request::TransactionRequest; pub use signature::Signature; diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs index c603226483..ef92e75b63 100644 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ b/crates/rpc/rpc/src/eth/api/transactions.rs @@ -10,7 +10,7 @@ use reth_primitives::{ }; use reth_provider::{providers::ChainState, BlockProvider, EvmEnvProvider, StateProviderFactory}; use reth_rlp::Decodable; -use reth_rpc_types::{Index, Transaction, TransactionRequest}; +use reth_rpc_types::{Index, Transaction, TransactionInfo, TransactionRequest}; use reth_transaction_pool::{TransactionOrigin, TransactionPool}; use revm::primitives::{BlockEnv, CfgEnv}; @@ -216,6 +216,45 @@ pub enum TransactionSource { }, } +// === impl TransactionSource === + +impl TransactionSource { + /// Consumes the type and returns the wrapped transaction. + pub fn into_recovered(self) -> TransactionSignedEcRecovered { + self.into() + } + + /// Returns the transaction and block related info, if not pending + pub fn split(self) -> (TransactionSignedEcRecovered, TransactionInfo) { + match self { + TransactionSource::Pool(tx) => { + let hash = tx.hash(); + ( + tx, + TransactionInfo { + hash: Some(hash), + index: None, + block_hash: None, + block_number: None, + }, + ) + } + TransactionSource::Database { transaction, index, block_hash, block_number } => { + let hash = transaction.hash(); + ( + transaction, + TransactionInfo { + hash: Some(hash), + index: Some(index), + block_hash: Some(block_hash), + block_number: Some(block_number), + }, + ) + } + } + } +} + impl From for TransactionSignedEcRecovered { fn from(value: TransactionSource) -> Self { match value { diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index b33dbb4119..ec9e2eb704 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -1,16 +1,22 @@ use crate::{ - eth::{cache::EthStateCache, EthTransactions}, + eth::{cache::EthStateCache, revm_utils::inspect, EthTransactions}, result::internal_rpc_err, }; use async_trait::async_trait; use jsonrpsee::core::RpcResult as Result; use reth_primitives::{BlockId, Bytes, H256}; use reth_provider::{BlockProvider, EvmEnvProvider, StateProviderFactory}; +use reth_revm::{ + database::{State, SubState}, + env::tx_env_with_recovered, + tracing::{TraceInspectorConfig, TracingInspector}, +}; use reth_rpc_api::TraceApiServer; use reth_rpc_types::{ trace::{filter::TraceFilter, parity::*}, CallRequest, Index, }; +use revm::primitives::Env; use std::collections::HashSet; /// `trace` API implementation. @@ -115,14 +121,28 @@ where &self, hash: H256, ) -> Result>> { - let (_transaction, at) = match self.eth_api.transaction_by_hash_at(hash).await? { + let (transaction, at) = match self.eth_api.transaction_by_hash_at(hash).await? { None => return Ok(None), Some(res) => res, }; - let (_cfg, _block_env, _at) = self.eth_api.evm_env_at(at).await?; + let (cfg, block, at) = self.eth_api.evm_env_at(at).await?; - Err(internal_rpc_err("unimplemented")) + let (tx, tx_info) = transaction.split(); + + let traces = self.eth_api.with_state_at(at, |state| { + let tx = tx_env_with_recovered(&tx); + let env = Env { cfg, block, tx }; + let db = SubState::new(State::new(state)); + let mut inspector = TracingInspector::new(TraceInspectorConfig::default_parity()); + + inspect(db, env, &mut inspector)?; + + let traces = inspector.into_parity_builder().into_localized_transaction_traces(tx_info); + + Ok(traces) + })?; + Ok(Some(traces)) } }