diff --git a/crates/blockchain/tracing.rs b/crates/blockchain/tracing.rs index 1730c3b1db..ac5e6e142c 100644 --- a/crates/blockchain/tracing.rs +++ b/crates/blockchain/tracing.rs @@ -86,6 +86,63 @@ impl Blockchain { Ok(call_traces) } + /// Re-executes the given transaction with tracing disabled (noopTracer). + /// Returns once the tx finishes; nothing is recorded. May need to re-execute ancestor + /// blocks to rebuild the parent state, up to the amount given by `reexec`. + pub async fn trace_transaction_noop( + &self, + tx_hash: H256, + reexec: u32, + timeout: Duration, + ) -> Result<(), ChainError> { + let Some((_, block_hash, tx_index)) = + self.storage.get_transaction_location(tx_hash).await? + else { + return Err(ChainError::Custom("Transaction not Found".to_string())); + }; + let tx_index = tx_index as usize; + let Some(block) = self.storage.get_block_by_hash(block_hash).await? else { + return Err(ChainError::Custom("Block not Found".to_string())); + }; + let mut vm = self + .rebuild_parent_state(block.header.parent_hash, reexec) + .await?; + vm.rerun_block(&block, Some(tx_index))?; + timeout_trace_operation(timeout, move || vm.trace_tx_noop(&block, tx_index)).await + } + + /// Re-executes every transaction in `block` with tracing disabled (noopTracer). + /// Returns one entry per tx (hash + empty unit) so callers can emit a uniform empty result per tx. + /// May need to re-execute ancestor blocks to rebuild the parent state, up to the amount given by `reexec`. + pub async fn trace_block_noop( + &self, + block: Block, + reexec: u32, + timeout: Duration, + ) -> Result, ChainError> { + let mut vm = self + .rebuild_parent_state(block.header.parent_hash, reexec) + .await?; + // Run system calls but stop before tx 0 + vm.rerun_block(&block, Some(0))?; + let vm = Arc::new(Mutex::new(vm)); + let block = Arc::new(block); + let mut results = Vec::with_capacity(block.body.transactions.len()); + for index in 0..block.body.transactions.len() { + let block = block.clone(); + let vm = vm.clone(); + let tx_hash = block.as_ref().body.transactions[index].hash(); + timeout_trace_operation(timeout, move || { + vm.lock() + .map_err(|_| EvmError::Custom("Unexpected Runtime Error".to_string()))? + .trace_tx_noop(block.as_ref(), index) + }) + .await?; + results.push((tx_hash, ())); + } + Ok(results) + } + /// Outputs the prestate trace for the given transaction. /// If `diff_mode` is true, returns both pre and post state; otherwise returns only pre state. /// `include_empty` keeps default-state entries in pre (only valid when `diff_mode` is false). diff --git a/crates/networking/rpc/tracing.rs b/crates/networking/rpc/tracing.rs index 5c23745a45..3b9eff8494 100644 --- a/crates/networking/rpc/tracing.rs +++ b/crates/networking/rpc/tracing.rs @@ -41,10 +41,12 @@ struct TraceConfig { #[derive(Default, Deserialize)] #[serde(rename_all = "camelCase")] +#[allow(clippy::enum_variant_names)] enum TracerType { #[default] CallTracer, PrestateTracer, + NoopTracer, } #[derive(Deserialize, Default)] @@ -171,6 +173,14 @@ impl RpcHandler for TraceTransactionRequest { PrestateResult::Diff(diff) => Ok(serde_json::to_value(diff)?), } } + TracerType::NoopTracer => { + context + .blockchain + .trace_transaction_noop(self.tx_hash, reexec, timeout) + .await + .map_err(|err| RpcErr::Internal(err.to_string()))?; + Ok(serde_json::json!({})) + } } } } @@ -282,6 +292,18 @@ impl RpcHandler for TraceBlockByNumberRequest { .collect::>()?; Ok(serde_json::to_value(block_trace)?) } + TracerType::NoopTracer => { + let traces = context + .blockchain + .trace_block_noop(block, reexec, timeout) + .await + .map_err(|err| RpcErr::Internal(err.to_string()))?; + let block_trace: BlockTrace = traces + .into_iter() + .map(|(hash, ())| (hash, serde_json::json!({})).into()) + .collect(); + Ok(serde_json::to_value(block_trace)?) + } } } } diff --git a/crates/vm/backends/levm/tracing.rs b/crates/vm/backends/levm/tracing.rs index 10de316d90..c2979fd61f 100644 --- a/crates/vm/backends/levm/tracing.rs +++ b/crates/vm/backends/levm/tracing.rs @@ -91,6 +91,25 @@ impl LEVM { } } + /// Executes `tx` with tracing disabled and discards the result. Matches geth's + /// `noopTracer`: the transaction still runs (so execution errors surface and the + /// trace harness overhead is exercised), but nothing is recorded. + pub fn trace_tx_noop( + db: &mut GeneralizedDatabase, + block_header: &BlockHeader, + tx: &Transaction, + vm_type: VMType, + crypto: &dyn Crypto, + ) -> Result<(), EvmError> { + let sender = tx + .sender(crypto) + .map_err(|e| EvmError::Transaction(format!("Couldn't recover sender: {e}")))?; + let env = Self::setup_env(tx, sender, block_header, db, vm_type)?; + let mut vm = VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type, crypto)?; + vm.execute()?; + Ok(()) + } + /// Run transaction with callTracer activated. pub fn trace_tx_calls( db: &mut GeneralizedDatabase, diff --git a/crates/vm/tracing.rs b/crates/vm/tracing.rs index b2255ad71d..9cebf68ca0 100644 --- a/crates/vm/tracing.rs +++ b/crates/vm/tracing.rs @@ -35,6 +35,26 @@ impl Evm { ) } + /// Executes a single tx with tracing disabled (noopTracer). + /// Assumes that the received state already contains changes from previous transactions. + pub fn trace_tx_noop(&mut self, block: &Block, tx_index: usize) -> Result<(), EvmError> { + let tx = block + .body + .transactions + .get(tx_index) + .ok_or(EvmError::Custom( + "Missing Transaction for Trace".to_string(), + ))?; + + LEVM::trace_tx_noop( + &mut self.db, + &block.header, + tx, + self.vm_type, + self.crypto.as_ref(), + ) + } + /// Executes a single tx and captures the pre/post account state (prestateTracer). /// Assumes that the received state already contains changes from previous transactions. pub fn trace_tx_prestate(