diff --git a/crates/revm/revm-inspectors/src/tracing/builder/geth.rs b/crates/revm/revm-inspectors/src/tracing/builder/geth.rs index 305ac18bfa2..b1434bae183 100644 --- a/crates/revm/revm-inspectors/src/tracing/builder/geth.rs +++ b/crates/revm/revm-inspectors/src/tracing/builder/geth.rs @@ -1,6 +1,6 @@ //! Geth trace builder -use crate::tracing::{types::CallTraceNode, TraceInspectorConfig}; +use crate::tracing::{types::CallTraceNode, TracingInspectorConfig}; use reth_primitives::{Address, JsonU256, H256, U256}; use reth_rpc_types::trace::geth::*; use revm::interpreter::opcode; @@ -12,12 +12,12 @@ pub struct GethTraceBuilder { /// Recorded trace nodes. nodes: Vec, /// How the traces were recorded - _config: TraceInspectorConfig, + _config: TracingInspectorConfig, } impl GethTraceBuilder { /// Returns a new instance of the builder - pub(crate) fn new(nodes: Vec, _config: TraceInspectorConfig) -> Self { + pub(crate) fn new(nodes: Vec, _config: TracingInspectorConfig) -> Self { Self { nodes, _config } } @@ -29,7 +29,7 @@ impl GethTraceBuilder { storage: &mut HashMap>, trace_node: &CallTraceNode, struct_logs: &mut Vec, - opts: &GethDebugTracingOptions, + opts: &GethDefaultTracingOptions, ) { let mut child_id = 0; // Iterate over the steps inside the given trace @@ -80,7 +80,7 @@ impl GethTraceBuilder { &self, // TODO(mattsse): This should be the total gas used, or gas used by last CallTrace? receipt_gas_used: U256, - opts: GethDebugTracingOptions, + opts: GethDefaultTracingOptions, ) -> DefaultFrame { if self.nodes.is_empty() { return Default::default() diff --git a/crates/revm/revm-inspectors/src/tracing/builder/parity.rs b/crates/revm/revm-inspectors/src/tracing/builder/parity.rs index 96940861402..fc62ef5b172 100644 --- a/crates/revm/revm-inspectors/src/tracing/builder/parity.rs +++ b/crates/revm/revm-inspectors/src/tracing/builder/parity.rs @@ -1,4 +1,4 @@ -use crate::tracing::{types::CallTraceNode, TraceInspectorConfig}; +use crate::tracing::{types::CallTraceNode, TracingInspectorConfig}; use reth_rpc_types::{trace::parity::*, TransactionInfo}; /// A type for creating parity style traces @@ -7,12 +7,12 @@ pub struct ParityTraceBuilder { /// Recorded trace nodes nodes: Vec, /// How the traces were recorded - _config: TraceInspectorConfig, + _config: TracingInspectorConfig, } impl ParityTraceBuilder { /// Returns a new instance of the builder - pub(crate) fn new(nodes: Vec, _config: TraceInspectorConfig) -> Self { + pub(crate) fn new(nodes: Vec, _config: TracingInspectorConfig) -> Self { Self { nodes, _config } } diff --git a/crates/revm/revm-inspectors/src/tracing/config.rs b/crates/revm/revm-inspectors/src/tracing/config.rs index 14abb668a29..c731eb85dc8 100644 --- a/crates/revm/revm-inspectors/src/tracing/config.rs +++ b/crates/revm/revm-inspectors/src/tracing/config.rs @@ -1,9 +1,9 @@ /// Gives guidance to the [TracingInspector](crate::tracing::TracingInspector). /// -/// Use [TraceInspectorConfig::default_parity] or [TraceInspectorConfig::default_geth] to get the -/// default configs for specific styles of traces. +/// Use [TracingInspectorConfig::default_parity] or [TracingInspectorConfig::default_geth] to get +/// the default configs for specific styles of traces. #[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub struct TraceInspectorConfig { +pub struct TracingInspectorConfig { /// Whether to record every individual opcode level step. pub record_steps: bool, /// Whether to record individual memory snapshots. @@ -14,7 +14,7 @@ pub struct TraceInspectorConfig { pub record_state_diff: bool, } -impl TraceInspectorConfig { +impl TracingInspectorConfig { /// Returns a config with everything enabled. pub const fn all() -> Self { Self { diff --git a/crates/revm/revm-inspectors/src/tracing/mod.rs b/crates/revm/revm-inspectors/src/tracing/mod.rs index 4dd8b53f4fa..4775fbd5ab0 100644 --- a/crates/revm/revm-inspectors/src/tracing/mod.rs +++ b/crates/revm/revm-inspectors/src/tracing/mod.rs @@ -23,7 +23,7 @@ mod config; mod types; mod utils; pub use builder::{geth::GethTraceBuilder, parity::ParityTraceBuilder}; -pub use config::TraceInspectorConfig; +pub use config::TracingInspectorConfig; /// An inspector that collects call traces. /// @@ -36,7 +36,7 @@ pub use config::TraceInspectorConfig; #[derive(Debug, Clone)] pub struct TracingInspector { /// Configures what and how the inspector records traces. - config: TraceInspectorConfig, + config: TracingInspectorConfig, /// Records all call traces traces: CallTraceArena, trace_stack: Vec, @@ -52,7 +52,7 @@ pub struct TracingInspector { impl TracingInspector { /// Returns a new instance for the given config - pub fn new(config: TraceInspectorConfig) -> Self { + pub fn new(config: TracingInspectorConfig) -> Self { Self { config, traces: Default::default(), diff --git a/crates/rpc/rpc-types/src/eth/trace/geth/call.rs b/crates/rpc/rpc-types/src/eth/trace/geth/call.rs index 06d5874f1e5..3723929a59a 100644 --- a/crates/rpc/rpc-types/src/eth/trace/geth/call.rs +++ b/crates/rpc/rpc-types/src/eth/trace/geth/call.rs @@ -39,6 +39,8 @@ pub struct CallLogFrame { #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CallConfig { + /// When set to true, this will only trace the primary (top-level) call and not any sub-calls. + /// It eliminates the additional processing for each call frame #[serde(default, skip_serializing_if = "Option::is_none")] pub only_top_call: Option, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -60,7 +62,7 @@ mod tests { #[test] fn test_serialize_call_trace() { let mut opts = GethDebugTracingCallOptions::default(); - opts.tracing_options.disable_storage = Some(false); + opts.tracing_options.config.disable_storage = Some(false); opts.tracing_options.tracer = Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::CallTracer)); opts.tracing_options.tracer_config = diff --git a/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs b/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs index 6d804926341..1d74fe3af87 100644 --- a/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs +++ b/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs @@ -134,12 +134,28 @@ impl From for GethTrace { /// See #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] pub enum GethDebugBuiltInTracerType { + /// The 4byteTracer collects the function selectors of every function executed in the lifetime + /// of a transaction, along with the size of the supplied call data. The result is a + /// [FourByteFrame] where the keys are SELECTOR-CALLDATASIZE and the values are number of + /// occurrences of this key. #[serde(rename = "4byteTracer")] FourByteTracer, + /// The callTracer tracks all the call frames executed during a transaction, including depth 0. + /// The result will be a nested list of call frames, resembling how EVM works. They form a tree + /// with the top-level call at root and sub-calls as children of the higher levels. #[serde(rename = "callTracer")] CallTracer, + /// The prestate tracer has two modes: prestate and diff. The prestate mode returns the + /// accounts necessary to execute a given transaction. diff mode returns the differences + /// between the transaction's pre and post-state (i.e. what changed because the transaction + /// happened). The prestateTracer defaults to prestate mode. It reexecutes the given + /// transaction and tracks every part of state that is touched. This is similar to the concept + /// of a stateless witness, the difference being this tracer doesn't return any cryptographic + /// proof, rather only the trie leaves. The result is an object. The keys are addresses of + /// accounts. #[serde(rename = "prestateTracer")] PreStateTracer, + /// This tracer is noop. It returns an empty object and is only meant for testing the setup. #[serde(rename = "noopTracer")] NoopTracer, } @@ -152,6 +168,22 @@ pub enum GethDebugBuiltInTracerConfig { PreStateTracer(PreStateConfig), } +// === impl GethDebugBuiltInTracerConfig === + +impl GethDebugBuiltInTracerConfig { + /// Returns true if the config matches the given tracer + pub fn matches_tracer(&self, tracer: &GethDebugBuiltInTracerType) -> bool { + matches!( + (self, tracer), + (GethDebugBuiltInTracerConfig::CallTracer(_), GethDebugBuiltInTracerType::CallTracer,) | + ( + GethDebugBuiltInTracerConfig::PreStateTracer(_), + GethDebugBuiltInTracerType::PreStateTracer, + ) + ) + } +} + /// Available tracers /// /// See and @@ -174,28 +206,95 @@ pub enum GethDebugTracerConfig { JsTracer(serde_json::Value), } +// === impl GethDebugTracerConfig === + +impl GethDebugTracerConfig { + /// Returns the [CallConfig] if it is a call config. + pub fn into_call_config(self) -> Option { + match self { + GethDebugTracerConfig::BuiltInTracer(GethDebugBuiltInTracerConfig::CallTracer(cfg)) => { + Some(cfg) + } + _ => None, + } + } + + /// Returns the [PreStateConfig] if it is a call config. + pub fn into_pre_state_config(self) -> Option { + match self { + GethDebugTracerConfig::BuiltInTracer(GethDebugBuiltInTracerConfig::PreStateTracer( + cfg, + )) => Some(cfg), + _ => None, + } + } + + /// Returns true if the config matches the given tracer + pub fn matches_tracer(&self, tracer: &GethDebugTracerType) -> bool { + match (self, tracer) { + (_, GethDebugTracerType::BuiltInTracer(tracer)) => self.matches_builtin_tracer(tracer), + (GethDebugTracerConfig::JsTracer(_), GethDebugTracerType::JsTracer(_)) => true, + _ => false, + } + } + + /// Returns true if the config matches the given tracer + pub fn matches_builtin_tracer(&self, tracer: &GethDebugBuiltInTracerType) -> bool { + match (self, tracer) { + (GethDebugTracerConfig::BuiltInTracer(config), tracer) => config.matches_tracer(tracer), + (GethDebugTracerConfig::JsTracer(_), _) => false, + } + } +} + /// Bindings for additional `debug_traceTransaction` options /// /// See #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct GethDebugTracingOptions { + #[serde(default, flatten)] + pub config: GethDefaultTracingOptions, + /// The custom tracer to use. + /// + /// If `None` then the default structlog tracer is used. #[serde(default, skip_serializing_if = "Option::is_none")] - pub disable_storage: Option, + pub tracer: Option, + /// tracerConfig is slated for Geth v1.11.0 + /// See #[serde(default, skip_serializing_if = "Option::is_none")] - pub disable_stack: Option, + pub tracer_config: Option, + /// A string of decimal integers that overrides the JavaScript-based tracing calls default + /// timeout of 5 seconds. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub timeout: Option, +} + +/// Default tracing options for the struct looger +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct GethDefaultTracingOptions { + /// enable memory capture #[serde(default, skip_serializing_if = "Option::is_none")] pub enable_memory: Option, + /// disable stack capture + #[serde(default, skip_serializing_if = "Option::is_none")] + pub disable_stack: Option, + /// disable storage capture + #[serde(default, skip_serializing_if = "Option::is_none")] + pub disable_storage: Option, + /// enable return data capture #[serde(default, skip_serializing_if = "Option::is_none")] pub enable_return_data: Option, + /// print output during capture end #[serde(default, skip_serializing_if = "Option::is_none")] - pub tracer: Option, - /// tracerConfig is slated for Geth v1.11.0 - /// See + pub debug: Option, + /// maximum length of output, but zero means unlimited #[serde(default, skip_serializing_if = "Option::is_none")] - pub tracer_config: Option, + pub limit: Option, + /// Chain overrides, can be used to execute a trace using future fork rules #[serde(default, skip_serializing_if = "Option::is_none")] - pub timeout: Option, + pub overrides: Option, } /// Bindings for additional `debug_traceCall` options diff --git a/crates/rpc/rpc-types/src/eth/trace/geth/pre_state.rs b/crates/rpc/rpc-types/src/eth/trace/geth/pre_state.rs index e03461f2ff9..e8bde9fbd83 100644 --- a/crates/rpc/rpc-types/src/eth/trace/geth/pre_state.rs +++ b/crates/rpc/rpc-types/src/eth/trace/geth/pre_state.rs @@ -59,7 +59,7 @@ mod tests { #[test] fn test_serialize_pre_state_trace() { let mut opts = GethDebugTracingCallOptions::default(); - opts.tracing_options.disable_storage = Some(false); + opts.tracing_options.config.disable_storage = Some(false); opts.tracing_options.tracer = Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::PreStateTracer)); opts.tracing_options.tracer_config = Some(GethDebugTracerConfig::BuiltInTracer( diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 8554072a1e5..b4130513529 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -1,12 +1,29 @@ -use crate::{result::internal_rpc_err, EthApiSpec}; +use crate::{ + eth::{ + error::{EthApiError, EthResult}, + revm_utils::inspect, + EthTransactions, + }, + result::internal_rpc_err, + EthApiSpec, +}; use async_trait::async_trait; use jsonrpsee::core::RpcResult; -use reth_primitives::{BlockId, BlockNumberOrTag, Bytes, H256}; +use reth_primitives::{BlockId, BlockNumberOrTag, Bytes, H256, U256}; +use reth_revm::{ + database::{State, SubState}, + env::tx_env_with_recovered, + tracing::{TracingInspector, TracingInspectorConfig}, +}; use reth_rpc_api::DebugApiServer; use reth_rpc_types::{ - trace::geth::{BlockTraceResult, GethDebugTracingOptions, GethTraceFrame, TraceResult}, + trace::geth::{ + BlockTraceResult, GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingOptions, + GethTraceFrame, NoopFrame, TraceResult, + }, CallRequest, RichBlock, }; +use revm::primitives::Env; /// `debug` API implementation. /// @@ -14,7 +31,7 @@ use reth_rpc_types::{ #[non_exhaustive] pub struct DebugApi { /// The implementation of `eth` API - eth: Eth, + eth_api: Eth, } // === impl DebugApi === @@ -22,7 +39,81 @@ pub struct DebugApi { impl DebugApi { /// Create a new instance of the [DebugApi] pub fn new(eth: Eth) -> Self { - Self { eth } + Self { eth_api: eth } + } +} + +// === impl DebugApi === + +impl DebugApi +where + Eth: EthTransactions + 'static, +{ + /// Trace the transaction according to the provided options. + /// + /// Ref: + pub async fn debug_trace_transaction( + &self, + tx_hash: H256, + opts: GethDebugTracingOptions, + ) -> EthResult { + let (transaction, at) = match self.eth_api.transaction_by_hash_at(tx_hash).await? { + None => return Err(EthApiError::TransactionNotFound), + Some(res) => res, + }; + + let (cfg, block, at) = self.eth_api.evm_env_at(at).await?; + + let tx = transaction.into_recovered(); + + 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 GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts; + if let Some(tracer) = tracer { + // valid matching config + if let Some(ref config) = tracer_config { + if !config.matches_tracer(&tracer) { + return Err(EthApiError::InvalidTracerConfig) + } + } + + return match tracer { + GethDebugTracerType::BuiltInTracer(tracer) => match tracer { + GethDebugBuiltInTracerType::FourByteTracer => { + todo!() + } + GethDebugBuiltInTracerType::CallTracer => { + todo!() + } + GethDebugBuiltInTracerType::PreStateTracer => { + todo!() + } + GethDebugBuiltInTracerType::NoopTracer => Ok(NoopFrame::default().into()), + }, + GethDebugTracerType::JsTracer(_) => { + Err(EthApiError::Unsupported("javascript tracers are unsupported.")) + } + } + } + + // default structlog tracer + let inspector_config = TracingInspectorConfig::default_geth() + .set_memory_snapshots(config.enable_memory.unwrap_or_default()) + .set_stack_snapshots(!config.disable_stack.unwrap_or_default()) + .set_state_diffs(!config.disable_storage.unwrap_or_default()); + + let mut inspector = TracingInspector::new(inspector_config); + + let (res, _) = inspect(db, env, &mut inspector)?; + let gas_used = res.result.gas_used(); + + let frame = inspector.into_geth_builder().geth_traces(U256::from(gas_used), config); + + Ok(frame.into()) + }) } } @@ -96,10 +187,10 @@ where /// Handler for `debug_traceTransaction` async fn debug_trace_transaction( &self, - _tx_hash: H256, - _opts: GethDebugTracingOptions, + tx_hash: H256, + opts: GethDebugTracingOptions, ) -> RpcResult { - Err(internal_rpc_err("unimplemented")) + Ok(DebugApi::debug_trace_transaction(self, tx_hash, opts).await?) } /// Handler for `debug_traceCall` diff --git a/crates/rpc/rpc/src/eth/api/server.rs b/crates/rpc/rpc/src/eth/api/server.rs index fcb8ffa95ee..e39db79ef46 100644 --- a/crates/rpc/rpc/src/eth/api/server.rs +++ b/crates/rpc/rpc/src/eth/api/server.rs @@ -446,7 +446,7 @@ mod tests { let hash = H256::random(); let gas_limit: u64 = random(); let gas_used: u64 = random(); - let base_fee_per_gas: Option = random::().then(|| random()); + let base_fee_per_gas: Option = random::().then(random); let header = Header { number: newest_block - i, diff --git a/crates/rpc/rpc/src/eth/error.rs b/crates/rpc/rpc/src/eth/error.rs index 0615a39df07..51f43061f77 100644 --- a/crates/rpc/rpc/src/eth/error.rs +++ b/crates/rpc/rpc/src/eth/error.rs @@ -1,7 +1,7 @@ //! Implementation specific Errors for the `eth_` namespace. -use crate::result::{internal_rpc_err, rpc_err}; -use jsonrpsee::{core::Error as RpcError, types::error::INVALID_PARAMS_CODE}; +use crate::result::{internal_rpc_err, invalid_params_rpc_err, rpc_err}; +use jsonrpsee::core::Error as RpcError; use reth_primitives::{constants::SELECTOR_LEN, Address, Bytes, U256}; use reth_rpc_types::{error::EthRpcErrorCode, BlockError}; use reth_transaction_pool::error::{InvalidPoolTransactionError, PoolError}; @@ -55,6 +55,15 @@ pub enum EthApiError { /// Error related to signing #[error(transparent)] Signing(#[from] SignError), + /// Thrown when a transaction was requested but not matching transaction exists + #[error("transaction not found")] + TransactionNotFound, + /// Some feature is unsupported + #[error("unsupported")] + Unsupported(&'static str), + /// When tracer config does not match the tracer + #[error("invalid tracer config")] + InvalidTracerConfig, } impl From for RpcError { @@ -69,14 +78,15 @@ impl From for RpcError { EthApiError::ConflictingRequestGasPriceAndTipSet { .. } | EthApiError::RequestLegacyGasPriceAndTipSet { .. } | EthApiError::Signing(_) | - EthApiError::BothStateAndStateDiffInOverride(_) => { - rpc_err(INVALID_PARAMS_CODE, error.to_string(), None) - } + EthApiError::BothStateAndStateDiffInOverride(_) | + EthApiError::InvalidTracerConfig => invalid_params_rpc_err(error.to_string()), EthApiError::InvalidTransaction(err) => err.into(), EthApiError::PoolError(_) | EthApiError::PrevrandaoNotSet | EthApiError::InvalidBlockData(_) | - EthApiError::Internal(_) => internal_rpc_err(error.to_string()), + EthApiError::Internal(_) | + EthApiError::TransactionNotFound => internal_rpc_err(error.to_string()), + EthApiError::Unsupported(msg) => internal_rpc_err(msg), } } } diff --git a/crates/rpc/rpc/src/result.rs b/crates/rpc/rpc/src/result.rs index ea9e25de816..7ca707ab074 100644 --- a/crates/rpc/rpc/src/result.rs +++ b/crates/rpc/rpc/src/result.rs @@ -125,6 +125,11 @@ impl ToRpcResultExt for RethResult> { } } +/// Constructs an invalid params JSON-RPC error. +pub(crate) fn invalid_params_rpc_err(msg: impl Into) -> jsonrpsee::core::Error { + rpc_err(jsonrpsee::types::error::INVALID_PARAMS_CODE, msg, None) +} + /// Constructs an internal JSON-RPC error. pub(crate) fn internal_rpc_err(msg: impl Into) -> jsonrpsee::core::Error { rpc_err(jsonrpsee::types::error::INTERNAL_ERROR_CODE, msg, None) diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index 4e871a6e0f8..c1474d8c687 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -9,7 +9,7 @@ use reth_provider::{BlockProvider, EvmEnvProvider, StateProviderFactory}; use reth_revm::{ database::{State, SubState}, env::tx_env_with_recovered, - tracing::{TraceInspectorConfig, TracingInspector}, + tracing::{TracingInspector, TracingInspectorConfig}, }; use reth_rpc_api::TraceApiServer; use reth_rpc_types::{ @@ -82,7 +82,7 @@ where 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()); + let mut inspector = TracingInspector::new(TracingInspectorConfig::default_parity()); inspect(db, env, &mut inspector)?;