Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
325 changes: 325 additions & 0 deletions src/tracing/debug.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
use crate::tracing::{
FourByteInspector, MuxInspector, TracingInspector, TracingInspectorConfig, TransactionContext,
};
#[cfg(feature = "js-tracer")]
use alloc::{boxed::Box, string::String};
use alloy_rpc_types_eth::TransactionInfo;
use alloy_rpc_types_trace::geth::{
mux::MuxConfig, CallConfig, FourByteFrame, GethDebugBuiltInTracerType, GethDebugTracerType,
GethDebugTracingOptions, GethDefaultTracingOptions, GethTrace, NoopFrame, PreStateConfig,
};
use revm::{
context_interface::{
result::{HaltReasonTr, ResultAndState},
Block, ContextTr, Transaction,
},
inspector::JournalExt,
interpreter::{CallInputs, CallOutcome, CreateInputs, CreateOutcome, Interpreter},
primitives::{Address, Log, U256},
DatabaseRef, Inspector,
};
use thiserror::Error;

/// Inspector for the `debug` API
///
/// This inspector is used to trace the execution of a transaction or call and supports all variants
/// of [`GethDebugTracerType`].
///
/// This inspector can be re-used for tracing multiple transactions. This is supported by
/// requiring caller to invoke [`DebugInspector::fuse`] after each transaction. See method
/// documentation for more details.
#[derive(Debug)]
pub enum DebugInspector {
/// FourByte tracer
FourByte(FourByteInspector),
/// CallTracer
CallTracer(TracingInspector, CallConfig),
/// PreStateTracer
PreStateTracer(TracingInspector, PreStateConfig),
/// Noop tracer
Noop(revm::inspector::NoOpInspector),
/// Mux tracer
Mux(MuxInspector, MuxConfig),
/// FlatCallTracer
FlatCallTracer(TracingInspector),
/// Default tracer
Default(TracingInspector, GethDefaultTracingOptions),
#[cfg(feature = "js-tracer")]
/// JS tracer
Js(Box<crate::tracing::js::JsInspector>, String, serde_json::Value),
}

impl DebugInspector {
/// Create a new `DebugInspector` from the given tracing options.
pub fn new(opts: GethDebugTracingOptions) -> Result<Self, DebugInspectorError> {
let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts;

let this = if let Some(tracer) = tracer {
#[allow(unreachable_patterns)]
match tracer {
GethDebugTracerType::BuiltInTracer(tracer) => match tracer {
GethDebugBuiltInTracerType::FourByteTracer => {
Self::FourByte(FourByteInspector::default())
}
GethDebugBuiltInTracerType::CallTracer => {
let config = tracer_config
.into_call_config()
.map_err(|_| DebugInspectorError::InvalidTracerConfig)?;

Self::CallTracer(
TracingInspector::new(TracingInspectorConfig::from_geth_call_config(
&config,
)),
config,
)
}
GethDebugBuiltInTracerType::PreStateTracer => {
let config = tracer_config
.into_pre_state_config()
.map_err(|_| DebugInspectorError::InvalidTracerConfig)?;

Self::PreStateTracer(
TracingInspector::new(
TracingInspectorConfig::from_geth_prestate_config(&config),
),
config,
)
}
GethDebugBuiltInTracerType::NoopTracer => {
Self::Noop(revm::inspector::NoOpInspector)
}
GethDebugBuiltInTracerType::MuxTracer => {
let config = tracer_config
.into_mux_config()
.map_err(|_| DebugInspectorError::InvalidTracerConfig)?;

Self::Mux(MuxInspector::try_from_config(config.clone())?, config)
}
GethDebugBuiltInTracerType::FlatCallTracer => {
let flat_call_config = tracer_config
.into_flat_call_config()
.map_err(|_| DebugInspectorError::InvalidTracerConfig)?;

Self::FlatCallTracer(TracingInspector::new(
TracingInspectorConfig::from_flat_call_config(&flat_call_config),
))
}
_ => {
// Note: this match is non-exhaustive in case we need to add support for
// additional tracers
return Err(DebugInspectorError::UnsupportedTracer);
}
},
#[cfg(not(feature = "js-tracer"))]
GethDebugTracerType::JsTracer(_) => {
return Err(DebugInspectorError::JsTracerNotEnabled);
}
#[cfg(feature = "js-tracer")]
GethDebugTracerType::JsTracer(code) => {
let config = tracer_config.into_json();
Self::Js(
crate::tracing::js::JsInspector::new(code.clone(), config.clone())?.into(),
code,
config,
)
}
_ => {
// Note: this match is non-exhaustive in case we need to add support for
// additional tracers
return Err(DebugInspectorError::UnsupportedTracer);
}
}
} else {
Self::Default(
TracingInspector::new(TracingInspectorConfig::from_geth_config(&config)),
config,
)
};

Ok(this)
}

/// Prepares inspector for executing the next transaction. This will remove any state from
/// previous transactions.
pub fn fuse(&mut self) -> Result<(), DebugInspectorError> {
match self {
Self::FourByte(inspector) => {
core::mem::take(inspector);
}
Self::CallTracer(inspector, _)
| Self::PreStateTracer(inspector, _)
| Self::FlatCallTracer(inspector)
| Self::Default(inspector, _) => inspector.fuse(),
Self::Noop(_) => {}
Self::Mux(inspector, config) => {
*inspector = MuxInspector::try_from_config(config.clone())?;
}
#[cfg(feature = "js-tracer")]
Self::Js(inspector, code, config) => {
*inspector =
crate::tracing::js::JsInspector::new(code.clone(), config.clone())?.into();
}
}

Ok(())
}

/// Should be invoked after each transaction to obtain the resulting [`GethTrace`].
pub fn get_result<DB: DatabaseRef>(
&mut self,
tx_context: Option<TransactionContext>,
tx_env: &impl Transaction,
block_env: &impl Block,
res: &ResultAndState<impl HaltReasonTr>,
db: &mut DB,
) -> Result<GethTrace, DebugInspectorError<DB::Error>> {
let tx_info = TransactionInfo {
hash: tx_context.as_ref().and_then(|c| c.tx_hash),
index: tx_context.as_ref().and_then(|c| c.tx_index.map(|i| i as u64)),
block_hash: tx_context.as_ref().and_then(|c| c.block_hash),
block_number: Some(block_env.number().saturating_to()),
base_fee: Some(block_env.basefee()),
};

let res = match self {
Self::FourByte(inspector) => FourByteFrame::from(&*inspector).into(),
Self::CallTracer(inspector, config) => {
inspector.set_transaction_gas_limit(tx_env.gas_limit());
inspector.geth_builder().geth_call_traces(*config, res.result.gas_used()).into()
}
Self::PreStateTracer(inspector, config) => {
inspector.set_transaction_gas_limit(tx_env.gas_limit());
inspector
.geth_builder()
.geth_prestate_traces(res, config, db)
.map_err(DebugInspectorError::Database)?
.into()
}
Self::Noop(_) => NoopFrame::default().into(),
Self::Mux(inspector, _) => inspector
.try_into_mux_frame(res, db, tx_info)
.map_err(DebugInspectorError::Database)?
.into(),
Self::FlatCallTracer(inspector) => {
inspector.set_transaction_gas_limit(tx_env.gas_limit());
inspector
.clone()
.into_parity_builder()
.into_localized_transaction_traces(tx_info)
.into()
}
Self::Default(inspector, config) => {
inspector.set_transaction_gas_limit(tx_env.gas_limit());
inspector
.geth_builder()
.geth_traces(
res.result.gas_used(),
res.result.output().unwrap_or_default().clone(),
*config,
)
.into()
}
#[cfg(feature = "js-tracer")]
Self::Js(inspector, _, _) => {
inspector.set_transaction_context(tx_context.unwrap_or_default());
let res = inspector
.json_result(res.clone(), tx_env, block_env, db)
.map_err(DebugInspectorError::JsInspector)?;

GethTrace::JS(res)
}
};

Ok(res)
}
}

macro_rules! delegate {
($self:expr => $insp:ident.$method:ident($($arg:expr),*)) => {
match $self {
Self::FourByte($insp) => Inspector::<CTX>::$method($insp, $($arg),*),
Self::CallTracer($insp, _) => Inspector::<CTX>::$method($insp, $($arg),*),
Self::PreStateTracer($insp, _) => Inspector::<CTX>::$method($insp, $($arg),*),
Self::FlatCallTracer($insp) => Inspector::<CTX>::$method($insp, $($arg),*),
Self::Default($insp, _) => Inspector::<CTX>::$method($insp, $($arg),*),
Self::Noop($insp) => Inspector::<CTX>::$method($insp, $($arg),*),
Self::Mux($insp, _) => Inspector::<CTX>::$method($insp, $($arg),*),
#[cfg(feature = "js-tracer")]
Self::Js($insp, _, _) => Inspector::<CTX>::$method($insp, $($arg),*),
}
};
}

impl<CTX> Inspector<CTX> for DebugInspector
where
CTX: ContextTr<Journal: JournalExt, Db: DatabaseRef>,
{
fn initialize_interp(&mut self, interp: &mut Interpreter, context: &mut CTX) {
delegate!(self => inspector.initialize_interp(interp, context))
}

fn step(&mut self, interp: &mut Interpreter, context: &mut CTX) {
delegate!(self => inspector.step(interp, context))
}

fn step_end(&mut self, interp: &mut Interpreter, context: &mut CTX) {
delegate!(self => inspector.step_end(interp, context))
}

fn log(&mut self, context: &mut CTX, log: Log) {
delegate!(self => inspector.log(context, log))
}

fn log_full(&mut self, interp: &mut Interpreter, context: &mut CTX, log: Log) {
delegate!(self => inspector.log_full(interp, context, log))
}

fn call(&mut self, context: &mut CTX, inputs: &mut CallInputs) -> Option<CallOutcome> {
delegate!(self => inspector.call(context, inputs))
}

fn call_end(&mut self, context: &mut CTX, inputs: &CallInputs, outcome: &mut CallOutcome) {
delegate!(self => inspector.call_end(context, inputs, outcome))
}

fn create(&mut self, context: &mut CTX, inputs: &mut CreateInputs) -> Option<CreateOutcome> {
delegate!(self => inspector.create(context, inputs))
}

fn create_end(
&mut self,
context: &mut CTX,
inputs: &CreateInputs,
outcome: &mut CreateOutcome,
) {
delegate!(self => inspector.create_end(context, inputs, outcome))
}

fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
delegate!(self => inspector.selfdestruct(contract, target, value))
}
}

/// Error type for [DebugInspector]
#[derive(Debug, Error)]
pub enum DebugInspectorError<DBError = core::convert::Infallible> {
/// Invalid tracer configuration
#[error("invalid tracer config")]
InvalidTracerConfig,
/// Unsupported tracer
#[error("unsupported tracer")]
UnsupportedTracer,
/// JS tracer is not enabled
#[error("JS Tracer is not enabled")]
JsTracerNotEnabled,
/// Error from MuxInspector
#[error(transparent)]
MuxInspector(#[from] crate::tracing::MuxError),
/// Error from JS inspector
#[cfg(feature = "js-tracer")]
#[error(transparent)]
JsInspector(#[from] crate::tracing::js::JsInspectorError),
/// Database error
#[error("database error: {0}")]
Database(DBError),
}
3 changes: 3 additions & 0 deletions src/tracing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ pub mod js;
mod mux;
pub use mux::{Error as MuxError, MuxInspector};

mod debug;
pub use debug::{DebugInspector, DebugInspectorError};

/// An inspector that collects call traces.
///
/// This [Inspector] can be hooked into revm's EVM which then calls the inspector
Expand Down