diff --git a/src/eth_rpc/servers/debug_rpc.rs b/src/eth_rpc/servers/debug_rpc.rs index 8a536e1fb..970f33bd7 100644 --- a/src/eth_rpc/servers/debug_rpc.rs +++ b/src/eth_rpc/servers/debug_rpc.rs @@ -162,9 +162,14 @@ impl DebugApiServer for DebugRpc

opts: Option, ) -> Result> { let provider = Arc::new(&self.eth_provider); - let tracer = TracerBuilder::new(provider).await?.with_block_id(BlockId::Number(block_number)).await?.build()?; + let tracer = TracerBuilder::new(provider) + .await? + .with_block_id(BlockId::Number(block_number)) + .await? + .with_tracing_options(opts.unwrap_or_default().into()) + .build()?; - Ok(tracer.debug_block(opts.unwrap_or_default())?) + Ok(tracer.debug_block()?) } /// Returns the Geth debug trace for the given block hash. @@ -174,11 +179,14 @@ impl DebugApiServer for DebugRpc

block_hash: B256, opts: Option, ) -> Result> { - let provider = Arc::new(&self.eth_provider); - let tracer = - TracerBuilder::new(provider).await?.with_block_id(BlockId::Hash(block_hash.into())).await?.build()?; + let tracer = TracerBuilder::new(Arc::new(&self.eth_provider)) + .await? + .with_block_id(BlockId::Hash(block_hash.into())) + .await? + .with_tracing_options(opts.unwrap_or_default().into()) + .build()?; - Ok(tracer.debug_block(opts.unwrap_or_default())?) + Ok(tracer.debug_block()?) } /// Returns the Geth debug trace for the given transaction hash. @@ -188,10 +196,13 @@ impl DebugApiServer for DebugRpc

transaction_hash: B256, opts: Option, ) -> Result { - let provider = Arc::new(&self.eth_provider); - let tracer = TracerBuilder::new(provider).await?.with_transaction_hash(transaction_hash).await?.build()?; + let tracer = TracerBuilder::new(Arc::new(&self.eth_provider)) + .await? + .with_transaction_hash(transaction_hash) + .await? + .with_tracing_options(opts.unwrap_or_default().into()) + .build()?; - let trace = tracer.debug_transaction(transaction_hash, opts.unwrap_or_default())?; - Ok(trace) + Ok(tracer.debug_transaction(transaction_hash)?) } } diff --git a/src/eth_rpc/servers/trace_rpc.rs b/src/eth_rpc/servers/trace_rpc.rs index 4d2776502..f38fa95e4 100644 --- a/src/eth_rpc/servers/trace_rpc.rs +++ b/src/eth_rpc/servers/trace_rpc.rs @@ -26,9 +26,13 @@ impl TraceApiServer for TraceRpc

#[allow(clippy::blocks_in_conditions)] #[tracing::instrument(skip(self), err, fields(block_id = ?block_id))] async fn trace_block(&self, block_id: BlockId) -> Result>> { - let provider = Arc::new(&self.eth_provider); - let tracer = TracerBuilder::new(provider).await?.with_block_id(block_id).await?.build()?; + let tracer = TracerBuilder::new(Arc::new(&self.eth_provider)) + .await? + .with_block_id(block_id) + .await? + .with_tracing_options(TracingInspectorConfig::default_parity().into()) + .build()?; - Ok(tracer.trace_block(TracingInspectorConfig::default_parity())?) + Ok(tracer.trace_block()?) } } diff --git a/src/tracing/builder.rs b/src/tracing/builder.rs index eae8f914a..7a9135c3b 100644 --- a/src/tracing/builder.rs +++ b/src/tracing/builder.rs @@ -1,11 +1,12 @@ -use reth_primitives::{B256, U256}; -use reth_revm::primitives::{BlockEnv, CfgEnv, Env, EnvWithHandlerCfg, HandlerCfg, SpecId}; -use reth_rpc_types::{Block, BlockHashOrNumber, BlockId, BlockTransactions, Header}; - use crate::eth_provider::{ error::{EthApiError, TransactionError}, provider::EthereumProvider, }; +use reth_primitives::{B256, U256}; +use reth_revm::primitives::{BlockEnv, CfgEnv, Env, EnvWithHandlerCfg, HandlerCfg, SpecId}; +use reth_rpc_types::trace::geth::GethDebugTracingOptions; +use reth_rpc_types::{Block, BlockHashOrNumber, BlockId, BlockTransactions, Header}; +use revm_inspectors::tracing::TracingInspectorConfig; use super::{database::EthDatabaseSnapshot, Tracer, TracerResult}; @@ -14,11 +15,39 @@ pub struct Floating; #[derive(Debug)] pub struct Pinned; +/// Representing different tracing options for transactions. +#[derive(Clone, Debug)] +pub enum TracingOptions { + /// Geth debug tracing options. + Geth(GethDebugTracingOptions), + /// Parity tracing options. + Parity(TracingInspectorConfig), +} + +impl Default for TracingOptions { + fn default() -> Self { + GethDebugTracingOptions::default().into() + } +} + +impl From for TracingOptions { + fn from(options: GethDebugTracingOptions) -> Self { + Self::Geth(options) + } +} + +impl From for TracingOptions { + fn from(config: TracingInspectorConfig) -> Self { + Self::Parity(config) + } +} + #[derive(Debug)] pub struct TracerBuilder { eth_provider: P, env: Env, block: Block, + tracing_options: TracingOptions, _phantom: std::marker::PhantomData, } @@ -34,7 +63,13 @@ impl TracerBuilder { let env = Env { cfg, ..Default::default() }; - Ok(Self { eth_provider, env, block: Default::default(), _phantom: std::marker::PhantomData }) + Ok(Self { + eth_provider, + env, + block: Default::default(), + tracing_options: Default::default(), + _phantom: std::marker::PhantomData, + }) } /// Sets the block to trace @@ -45,6 +80,7 @@ impl TracerBuilder { eth_provider: self.eth_provider.clone(), env: self.env.clone(), block, + tracing_options: self.tracing_options.clone(), _phantom: std::marker::PhantomData, }) } @@ -92,6 +128,13 @@ impl TracerBuilder { } impl TracerBuilder { + /// Sets the tracing options + #[must_use] + pub fn with_tracing_options(mut self, tracing_options: TracingOptions) -> Self { + self.tracing_options = tracing_options; + self + } + /// Builds the tracer. pub fn build(self) -> TracerResult> { let transactions = match &self.block.transactions { @@ -101,9 +144,11 @@ impl TracerBuilder { let env = self.init_env_with_handler_config(); // DB should use the state of the parent block - let db = EthDatabaseSnapshot::new(self.eth_provider, BlockId::Hash(self.block.header.parent_hash.into())); + let db = EthDatabaseSnapshot::new(self.eth_provider, self.block.header.parent_hash.into()); + + let tracing_options = self.tracing_options; - Ok(Tracer { transactions, env, db }) + Ok(Tracer { transactions, env, db, tracing_options }) } /// Init an `EnvWithHandlerCfg`. diff --git a/src/tracing/mod.rs b/src/tracing/mod.rs index bf7f74333..51662ae52 100644 --- a/src/tracing/mod.rs +++ b/src/tracing/mod.rs @@ -24,72 +24,173 @@ use crate::eth_provider::{ error::{EthApiError, EthereumDataFormatError, TransactionError}, provider::EthereumProvider, }; +use crate::tracing::builder::TracingOptions; pub type TracerResult = Result; +/// Represents the result of tracing a transaction. +type TracingStateResult = TracerResult<(TracingResult, reth_revm::primitives::State)>; + +/// Representing the result of tracing transactions. +#[derive(Clone, Debug)] +enum TracingResult { + /// Geth trace results. + Geth(Vec), + /// Parity trace results. + Parity(Vec), +} + +impl TracingResult { + /// Converts the tracing result into Geth traces. + fn into_geth(self) -> Option> { + if let Self::Geth(traces) = self { + Some(traces) + } else { + None + } + } + + /// Converts the tracing result into Parity traces. + fn into_parity(self) -> Option> { + if let Self::Parity(traces) = self { + Some(traces) + } else { + None + } + } +} + #[derive(Debug)] pub struct Tracer { transactions: Vec, env: EnvWithHandlerCfg, db: EthDatabaseSnapshot

, + tracing_options: TracingOptions, } impl Tracer

{ - /// Trace the block in the parity format. - pub fn trace_block( - self, + /// Traces the transaction with Geth tracing options and returns the resulting traces and state. + fn trace_geth( + env: EnvWithHandlerCfg, + db: &mut EthDatabaseSnapshot

, + tx: &reth_rpc_types::Transaction, + opts: GethDebugTracingOptions, + ) -> TracingStateResult { + // Extract options + let GethDebugTracingOptions { tracer_config, config, tracer, .. } = opts; + + // Check if tracer is provided + if let Some(tracer) = tracer { + match tracer { + // Only support CallTracer for now + GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::CallTracer) => { + // Convert tracer config to call config + let call_config = tracer_config + .clone() + .into_call_config() + .map_err(|err| EthApiError::Transaction(TransactionError::Tracing(err.into())))?; + + // Initialize tracing inspector with call config + let mut inspector = + TracingInspector::new(TracingInspectorConfig::from_geth_call_config(&call_config)); + + // Build EVM with environment and inspector + let evm = EvmBuilder::evm_with_env_and_inspector(db, env, &mut inspector); + + // Execute transaction + let res = transact_in_place(evm)?; + + // Get call traces + let call_frame = inspector.into_geth_builder().geth_call_traces( + tracer_config.into_call_config().map_err(|err| TransactionError::Tracing(err.into()))?, + res.result.gas_used(), + ); + + // Return success trace result + return Ok(( + TracingResult::Geth(vec![TraceResult::Success { + result: call_frame.into(), + tx_hash: Some(tx.hash), + }]), + res.state, + )); + } + // Return error for unsupported tracers + _ => { + return Err(EthApiError::Transaction(TransactionError::Tracing( + eyre!("only call tracer is currently supported").into(), + ))) + } + } + } + + // Use default tracer + let mut inspector = TracingInspector::new(TracingInspectorConfig::from_geth_config(&config)); + let evm = EvmBuilder::evm_with_env_and_inspector(db, env, &mut inspector); + let res = transact_in_place(evm)?; + let gas_used = res.result.gas_used(); + let return_value = res.result.into_output().unwrap_or_default(); + let frame = inspector.into_geth_builder().geth_traces(gas_used, return_value, config); + Ok(( + TracingResult::Geth(vec![TraceResult::Success { result: frame.into(), tx_hash: Some(tx.hash) }]), + res.state, + )) + } + + /// Traces the transaction with Parity tracing options and returns the resulting traces and state. + fn trace_parity( + env: EnvWithHandlerCfg, + db: &mut EthDatabaseSnapshot

, + tx: &reth_rpc_types::Transaction, tracing_config: TracingInspectorConfig, - ) -> TracerResult>> { + ) -> TracingStateResult { + // Get block base fee + let block_base_fee = env + .env + .block + .basefee + .try_into() + .map_err(|err: FromUintError| TransactionError::Tracing(err.into()))?; + + // Initialize tracing inspector with given config + let mut inspector = TracingInspector::new(tracing_config); + + // Build EVM with environment and inspector + let evm = EvmBuilder::evm_with_env_and_inspector(db, env, &mut inspector); + + // Execute transaction + let res = transact_in_place(evm)?; + + // Create transaction info + let mut transaction_info = TransactionInfo::from(tx); + transaction_info.base_fee = Some(block_base_fee); + + // Return Parity trace result + Ok(( + TracingResult::Parity(inspector.into_parity_builder().into_localized_transaction_traces(transaction_info)), + res.state, + )) + } + + /// Trace the block in the parity format. + pub fn trace_block(self) -> TracerResult>> { let txs = self.transactions.clone(); - let traces = self.trace_transactions( - |env: EnvWithHandlerCfg, - db: &mut EthDatabaseSnapshot

, - tx: &reth_rpc_types::Transaction| - -> TracerResult<(Vec, reth_revm::primitives::State)> { - let block_base_fee = env - .env - .block - .basefee - .try_into() - .map_err(|err: FromUintError| TransactionError::Tracing(err.into()))?; - - // Set up the inspector and transact the transaction - let mut inspector = TracingInspector::new(tracing_config); - let evm = EvmBuilder::evm_with_env_and_inspector(db, env, &mut inspector); - let res = transact_in_place(evm)?; - - let parity_builder = inspector.into_parity_builder(); - - let mut transaction_info = TransactionInfo::from(tx); - transaction_info.base_fee = Some(block_base_fee); - - Ok((parity_builder.into_localized_transaction_traces(transaction_info), res.state)) - }, - &txs, - )?; - - Ok(Some(traces)) + Ok(Some(self.trace_transactions(TracingResult::into_parity, &txs)?)) } /// Returns the debug trace in the Geth. /// Currently only supports the call tracer or the default tracer. - pub fn debug_block(self, opts: GethDebugTracingOptions) -> TracerResult> { + pub fn debug_block(self) -> TracerResult> { let txs = self.transactions.clone(); - let traces = self.trace_transactions(transact_and_get_traces_geth(opts), &txs)?; - - Ok(traces) + self.trace_transactions(TracingResult::into_geth, &txs) } - pub fn debug_transaction( - mut self, - transaction_hash: B256, - opts: GethDebugTracingOptions, - ) -> TracerResult { + pub fn debug_transaction(mut self, transaction_hash: B256) -> TracerResult { for tx in self.transactions.clone() { if tx.hash == transaction_hash { // We only want to trace the transaction with the given hash. let trace = self - .trace_transactions(transact_and_get_traces_geth::

(opts), &[tx])? + .trace_transactions(TracingResult::into_geth, &[tx])? .first() .cloned() .ok_or(TransactionError::Tracing(eyre!("No trace found").into()))?; @@ -100,8 +201,7 @@ impl Tracer

{ } let env = env_with_tx(&self.env, tx.clone())?; - let evm = EvmBuilder::evm_with_env(&mut self.db, env); - transact_commit_in_place(evm)?; + transact_commit_in_place(EvmBuilder::evm_with_env(&mut self.db, env))?; } Err(EthApiError::TransactionNotFound(transaction_hash)) @@ -110,18 +210,11 @@ impl Tracer

{ /// Traces the provided transactions using the given closure. /// The function `transact_and_get_traces` closure uses the `env` and `db` to create an evm /// which is then used to transact and trace the transaction. - fn trace_transactions( + fn trace_transactions( self, - transact_and_get_traces: F, + convert_result: fn(TracingResult) -> Option>, transactions: &[reth_rpc_types::Transaction], - ) -> TracerResult> - where - F: Fn( - EnvWithHandlerCfg, - &mut EthDatabaseSnapshot

, - &reth_rpc_types::Transaction, - ) -> TracerResult<(Vec, reth_revm::primitives::State)>, - { + ) -> TracerResult> { let mut traces = Vec::with_capacity(self.transactions.len()); let mut transactions = transactions.iter().peekable(); let mut db = self.db; @@ -129,8 +222,12 @@ impl Tracer

{ while let Some(tx) = transactions.next() { let env = env_with_tx(&self.env, tx.clone())?; - let (res, state_changes) = transact_and_get_traces(env, &mut db, tx)?; - traces.extend(res); + let (res, state_changes) = match &self.tracing_options { + TracingOptions::Geth(opts) => Self::trace_geth(env, &mut db, tx, opts.clone())?, + TracingOptions::Parity(tracing_config) => Self::trace_parity(env, &mut db, tx, *tracing_config)?, + }; + + traces.extend(convert_result(res).unwrap_or_default()); // Only commit to the database if there are more transactions to process. if transactions.peek().is_some() { @@ -142,57 +239,6 @@ impl Tracer

{ } } -/// Returns a closure that transacts and gets the geth traces for the given transaction. Captures the -/// `opts` in order to use it in the closure. -fn transact_and_get_traces_geth( - opts: GethDebugTracingOptions, -) -> impl Fn( - EnvWithHandlerCfg, - &mut EthDatabaseSnapshot

, - &reth_rpc_types::Transaction, -) -> TracerResult<(Vec, reth_revm::primitives::State)> { - move |env: EnvWithHandlerCfg, - db: &mut EthDatabaseSnapshot

, - tx: &reth_rpc_types::Transaction| - -> TracerResult<(Vec, reth_revm::primitives::State)> { - let GethDebugTracingOptions { tracer_config, config, tracer, .. } = opts.clone(); - - if let Some(tracer) = tracer { - return match tracer { - GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::CallTracer) => { - let call_config = tracer_config - .clone() - .into_call_config() - .map_err(|err| EthApiError::Transaction(TransactionError::Tracing(err.into())))?; - let mut inspector = - TracingInspector::new(TracingInspectorConfig::from_geth_call_config(&call_config)); - let evm = EvmBuilder::evm_with_env_and_inspector(db, env, &mut inspector); - - let res = transact_in_place(evm)?; - let call_frame = inspector.into_geth_builder().geth_call_traces( - tracer_config.into_call_config().map_err(|err| TransactionError::Tracing(err.into()))?, - res.result.gas_used(), - ); - Ok((vec![TraceResult::Success { result: call_frame.into(), tx_hash: Some(tx.hash) }], res.state)) - } - _ => Err(EthApiError::Transaction(TransactionError::Tracing( - eyre!("only call tracer is currently supported").into(), - ))), - }; - } - - // default tracer - let mut inspector = TracingInspector::new(TracingInspectorConfig::from_geth_config(&config)); - let evm = EvmBuilder::evm_with_env_and_inspector(db, env, &mut inspector); - - let res = transact_in_place(evm)?; - let gas_used = res.result.gas_used(); - let return_value = res.result.into_output().unwrap_or_default(); - let frame = inspector.into_geth_builder().geth_traces(gas_used, return_value, config); - Ok((vec![TraceResult::Success { result: frame.into(), tx_hash: Some(tx.hash) }], res.state)) - } -} - /// Returns the environment with the transaction env updated to the given transaction. fn env_with_tx(env: &EnvWithHandlerCfg, tx: reth_rpc_types::Transaction) -> TracerResult { // Convert the transaction to an ec recovered transaction and update the env with it. @@ -283,10 +329,11 @@ mod tests { .with_transaction_hash(B256::from_hex("INSERT THE TRANSACTION HASH YOU WISH TO DEBUG").unwrap()) .await .unwrap() + .with_tracing_options(TracingInspectorConfig::default_parity().into()) .build() .unwrap(); // When - let _ = tracer.trace_block(TracingInspectorConfig::default_parity()).unwrap(); + let _ = tracer.trace_block().unwrap(); } }