Skip to content

Commit 7072085

Browse files
authored
feat: upstream DebugInspector from reth (#384)
Moves `DebugInspector` from reth to revm-inspectors as requested in paradigmxyz/reth#19932. This was introduced in paradigmxyz/reth#19925 to unify debug tracing across all Geth tracer types.
1 parent fd4cf14 commit 7072085

File tree

2 files changed

+328
-0
lines changed

2 files changed

+328
-0
lines changed

src/tracing/debug.rs

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
use crate::tracing::{
2+
FourByteInspector, MuxInspector, TracingInspector, TracingInspectorConfig, TransactionContext,
3+
};
4+
#[cfg(feature = "js-tracer")]
5+
use alloc::{boxed::Box, string::String};
6+
use alloy_rpc_types_eth::TransactionInfo;
7+
use alloy_rpc_types_trace::geth::{
8+
mux::MuxConfig, CallConfig, FourByteFrame, GethDebugBuiltInTracerType, GethDebugTracerType,
9+
GethDebugTracingOptions, GethDefaultTracingOptions, GethTrace, NoopFrame, PreStateConfig,
10+
};
11+
use revm::{
12+
context_interface::{
13+
result::{HaltReasonTr, ResultAndState},
14+
Block, ContextTr, Transaction,
15+
},
16+
inspector::JournalExt,
17+
interpreter::{CallInputs, CallOutcome, CreateInputs, CreateOutcome, Interpreter},
18+
primitives::{Address, Log, U256},
19+
DatabaseRef, Inspector,
20+
};
21+
use thiserror::Error;
22+
23+
/// Inspector for the `debug` API
24+
///
25+
/// This inspector is used to trace the execution of a transaction or call and supports all variants
26+
/// of [`GethDebugTracerType`].
27+
///
28+
/// This inspector can be re-used for tracing multiple transactions. This is supported by
29+
/// requiring caller to invoke [`DebugInspector::fuse`] after each transaction. See method
30+
/// documentation for more details.
31+
#[derive(Debug)]
32+
pub enum DebugInspector {
33+
/// FourByte tracer
34+
FourByte(FourByteInspector),
35+
/// CallTracer
36+
CallTracer(TracingInspector, CallConfig),
37+
/// PreStateTracer
38+
PreStateTracer(TracingInspector, PreStateConfig),
39+
/// Noop tracer
40+
Noop(revm::inspector::NoOpInspector),
41+
/// Mux tracer
42+
Mux(MuxInspector, MuxConfig),
43+
/// FlatCallTracer
44+
FlatCallTracer(TracingInspector),
45+
/// Default tracer
46+
Default(TracingInspector, GethDefaultTracingOptions),
47+
#[cfg(feature = "js-tracer")]
48+
/// JS tracer
49+
Js(Box<crate::tracing::js::JsInspector>, String, serde_json::Value),
50+
}
51+
52+
impl DebugInspector {
53+
/// Create a new `DebugInspector` from the given tracing options.
54+
pub fn new(opts: GethDebugTracingOptions) -> Result<Self, DebugInspectorError> {
55+
let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts;
56+
57+
let this = if let Some(tracer) = tracer {
58+
#[allow(unreachable_patterns)]
59+
match tracer {
60+
GethDebugTracerType::BuiltInTracer(tracer) => match tracer {
61+
GethDebugBuiltInTracerType::FourByteTracer => {
62+
Self::FourByte(FourByteInspector::default())
63+
}
64+
GethDebugBuiltInTracerType::CallTracer => {
65+
let config = tracer_config
66+
.into_call_config()
67+
.map_err(|_| DebugInspectorError::InvalidTracerConfig)?;
68+
69+
Self::CallTracer(
70+
TracingInspector::new(TracingInspectorConfig::from_geth_call_config(
71+
&config,
72+
)),
73+
config,
74+
)
75+
}
76+
GethDebugBuiltInTracerType::PreStateTracer => {
77+
let config = tracer_config
78+
.into_pre_state_config()
79+
.map_err(|_| DebugInspectorError::InvalidTracerConfig)?;
80+
81+
Self::PreStateTracer(
82+
TracingInspector::new(
83+
TracingInspectorConfig::from_geth_prestate_config(&config),
84+
),
85+
config,
86+
)
87+
}
88+
GethDebugBuiltInTracerType::NoopTracer => {
89+
Self::Noop(revm::inspector::NoOpInspector)
90+
}
91+
GethDebugBuiltInTracerType::MuxTracer => {
92+
let config = tracer_config
93+
.into_mux_config()
94+
.map_err(|_| DebugInspectorError::InvalidTracerConfig)?;
95+
96+
Self::Mux(MuxInspector::try_from_config(config.clone())?, config)
97+
}
98+
GethDebugBuiltInTracerType::FlatCallTracer => {
99+
let flat_call_config = tracer_config
100+
.into_flat_call_config()
101+
.map_err(|_| DebugInspectorError::InvalidTracerConfig)?;
102+
103+
Self::FlatCallTracer(TracingInspector::new(
104+
TracingInspectorConfig::from_flat_call_config(&flat_call_config),
105+
))
106+
}
107+
_ => {
108+
// Note: this match is non-exhaustive in case we need to add support for
109+
// additional tracers
110+
return Err(DebugInspectorError::UnsupportedTracer);
111+
}
112+
},
113+
#[cfg(not(feature = "js-tracer"))]
114+
GethDebugTracerType::JsTracer(_) => {
115+
return Err(DebugInspectorError::JsTracerNotEnabled);
116+
}
117+
#[cfg(feature = "js-tracer")]
118+
GethDebugTracerType::JsTracer(code) => {
119+
let config = tracer_config.into_json();
120+
Self::Js(
121+
crate::tracing::js::JsInspector::new(code.clone(), config.clone())?.into(),
122+
code,
123+
config,
124+
)
125+
}
126+
_ => {
127+
// Note: this match is non-exhaustive in case we need to add support for
128+
// additional tracers
129+
return Err(DebugInspectorError::UnsupportedTracer);
130+
}
131+
}
132+
} else {
133+
Self::Default(
134+
TracingInspector::new(TracingInspectorConfig::from_geth_config(&config)),
135+
config,
136+
)
137+
};
138+
139+
Ok(this)
140+
}
141+
142+
/// Prepares inspector for executing the next transaction. This will remove any state from
143+
/// previous transactions.
144+
pub fn fuse(&mut self) -> Result<(), DebugInspectorError> {
145+
match self {
146+
Self::FourByte(inspector) => {
147+
core::mem::take(inspector);
148+
}
149+
Self::CallTracer(inspector, _)
150+
| Self::PreStateTracer(inspector, _)
151+
| Self::FlatCallTracer(inspector)
152+
| Self::Default(inspector, _) => inspector.fuse(),
153+
Self::Noop(_) => {}
154+
Self::Mux(inspector, config) => {
155+
*inspector = MuxInspector::try_from_config(config.clone())?;
156+
}
157+
#[cfg(feature = "js-tracer")]
158+
Self::Js(inspector, code, config) => {
159+
*inspector =
160+
crate::tracing::js::JsInspector::new(code.clone(), config.clone())?.into();
161+
}
162+
}
163+
164+
Ok(())
165+
}
166+
167+
/// Should be invoked after each transaction to obtain the resulting [`GethTrace`].
168+
pub fn get_result<DB: DatabaseRef>(
169+
&mut self,
170+
tx_context: Option<TransactionContext>,
171+
tx_env: &impl Transaction,
172+
block_env: &impl Block,
173+
res: &ResultAndState<impl HaltReasonTr>,
174+
db: &mut DB,
175+
) -> Result<GethTrace, DebugInspectorError<DB::Error>> {
176+
let tx_info = TransactionInfo {
177+
hash: tx_context.as_ref().and_then(|c| c.tx_hash),
178+
index: tx_context.as_ref().and_then(|c| c.tx_index.map(|i| i as u64)),
179+
block_hash: tx_context.as_ref().and_then(|c| c.block_hash),
180+
block_number: Some(block_env.number().saturating_to()),
181+
base_fee: Some(block_env.basefee()),
182+
};
183+
184+
let res = match self {
185+
Self::FourByte(inspector) => FourByteFrame::from(&*inspector).into(),
186+
Self::CallTracer(inspector, config) => {
187+
inspector.set_transaction_gas_limit(tx_env.gas_limit());
188+
inspector.geth_builder().geth_call_traces(*config, res.result.gas_used()).into()
189+
}
190+
Self::PreStateTracer(inspector, config) => {
191+
inspector.set_transaction_gas_limit(tx_env.gas_limit());
192+
inspector
193+
.geth_builder()
194+
.geth_prestate_traces(res, config, db)
195+
.map_err(DebugInspectorError::Database)?
196+
.into()
197+
}
198+
Self::Noop(_) => NoopFrame::default().into(),
199+
Self::Mux(inspector, _) => inspector
200+
.try_into_mux_frame(res, db, tx_info)
201+
.map_err(DebugInspectorError::Database)?
202+
.into(),
203+
Self::FlatCallTracer(inspector) => {
204+
inspector.set_transaction_gas_limit(tx_env.gas_limit());
205+
inspector
206+
.clone()
207+
.into_parity_builder()
208+
.into_localized_transaction_traces(tx_info)
209+
.into()
210+
}
211+
Self::Default(inspector, config) => {
212+
inspector.set_transaction_gas_limit(tx_env.gas_limit());
213+
inspector
214+
.geth_builder()
215+
.geth_traces(
216+
res.result.gas_used(),
217+
res.result.output().unwrap_or_default().clone(),
218+
*config,
219+
)
220+
.into()
221+
}
222+
#[cfg(feature = "js-tracer")]
223+
Self::Js(inspector, _, _) => {
224+
inspector.set_transaction_context(tx_context.unwrap_or_default());
225+
let res = inspector
226+
.json_result(res.clone(), tx_env, block_env, db)
227+
.map_err(DebugInspectorError::JsInspector)?;
228+
229+
GethTrace::JS(res)
230+
}
231+
};
232+
233+
Ok(res)
234+
}
235+
}
236+
237+
macro_rules! delegate {
238+
($self:expr => $insp:ident.$method:ident($($arg:expr),*)) => {
239+
match $self {
240+
Self::FourByte($insp) => Inspector::<CTX>::$method($insp, $($arg),*),
241+
Self::CallTracer($insp, _) => Inspector::<CTX>::$method($insp, $($arg),*),
242+
Self::PreStateTracer($insp, _) => Inspector::<CTX>::$method($insp, $($arg),*),
243+
Self::FlatCallTracer($insp) => Inspector::<CTX>::$method($insp, $($arg),*),
244+
Self::Default($insp, _) => Inspector::<CTX>::$method($insp, $($arg),*),
245+
Self::Noop($insp) => Inspector::<CTX>::$method($insp, $($arg),*),
246+
Self::Mux($insp, _) => Inspector::<CTX>::$method($insp, $($arg),*),
247+
#[cfg(feature = "js-tracer")]
248+
Self::Js($insp, _, _) => Inspector::<CTX>::$method($insp, $($arg),*),
249+
}
250+
};
251+
}
252+
253+
impl<CTX> Inspector<CTX> for DebugInspector
254+
where
255+
CTX: ContextTr<Journal: JournalExt, Db: DatabaseRef>,
256+
{
257+
fn initialize_interp(&mut self, interp: &mut Interpreter, context: &mut CTX) {
258+
delegate!(self => inspector.initialize_interp(interp, context))
259+
}
260+
261+
fn step(&mut self, interp: &mut Interpreter, context: &mut CTX) {
262+
delegate!(self => inspector.step(interp, context))
263+
}
264+
265+
fn step_end(&mut self, interp: &mut Interpreter, context: &mut CTX) {
266+
delegate!(self => inspector.step_end(interp, context))
267+
}
268+
269+
fn log(&mut self, context: &mut CTX, log: Log) {
270+
delegate!(self => inspector.log(context, log))
271+
}
272+
273+
fn log_full(&mut self, interp: &mut Interpreter, context: &mut CTX, log: Log) {
274+
delegate!(self => inspector.log_full(interp, context, log))
275+
}
276+
277+
fn call(&mut self, context: &mut CTX, inputs: &mut CallInputs) -> Option<CallOutcome> {
278+
delegate!(self => inspector.call(context, inputs))
279+
}
280+
281+
fn call_end(&mut self, context: &mut CTX, inputs: &CallInputs, outcome: &mut CallOutcome) {
282+
delegate!(self => inspector.call_end(context, inputs, outcome))
283+
}
284+
285+
fn create(&mut self, context: &mut CTX, inputs: &mut CreateInputs) -> Option<CreateOutcome> {
286+
delegate!(self => inspector.create(context, inputs))
287+
}
288+
289+
fn create_end(
290+
&mut self,
291+
context: &mut CTX,
292+
inputs: &CreateInputs,
293+
outcome: &mut CreateOutcome,
294+
) {
295+
delegate!(self => inspector.create_end(context, inputs, outcome))
296+
}
297+
298+
fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
299+
delegate!(self => inspector.selfdestruct(contract, target, value))
300+
}
301+
}
302+
303+
/// Error type for [DebugInspector]
304+
#[derive(Debug, Error)]
305+
pub enum DebugInspectorError<DBError = core::convert::Infallible> {
306+
/// Invalid tracer configuration
307+
#[error("invalid tracer config")]
308+
InvalidTracerConfig,
309+
/// Unsupported tracer
310+
#[error("unsupported tracer")]
311+
UnsupportedTracer,
312+
/// JS tracer is not enabled
313+
#[error("JS Tracer is not enabled")]
314+
JsTracerNotEnabled,
315+
/// Error from MuxInspector
316+
#[error(transparent)]
317+
MuxInspector(#[from] crate::tracing::MuxError),
318+
/// Error from JS inspector
319+
#[cfg(feature = "js-tracer")]
320+
#[error(transparent)]
321+
JsInspector(#[from] crate::tracing::js::JsInspectorError),
322+
/// Database error
323+
#[error("database error: {0}")]
324+
Database(DBError),
325+
}

src/tracing/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ pub mod js;
5959
mod mux;
6060
pub use mux::{Error as MuxError, MuxInspector};
6161

62+
mod debug;
63+
pub use debug::{DebugInspector, DebugInspectorError};
64+
6265
/// An inspector that collects call traces.
6366
///
6467
/// This [Inspector] can be hooked into revm's EVM which then calls the inspector

0 commit comments

Comments
 (0)