|
| 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 | +} |
0 commit comments