diff --git a/crates/rpc/rpc-types/src/eth/block.rs b/crates/rpc/rpc-types/src/eth/block.rs index d84a8e3dee..5c9de8ab4f 100644 --- a/crates/rpc/rpc-types/src/eth/block.rs +++ b/crates/rpc/rpc-types/src/eth/block.rs @@ -55,6 +55,9 @@ pub enum BlockError { /// A transaction failed sender recovery #[error("transaction failed sender recovery")] InvalidSignature, + /// A raw block failed to decode + #[error("failed to decode raw block {0}")] + RlpDecodeRawBlock(reth_rlp::DecodeError), } /// Block representation 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 e2d0776c06..71d6c9077d 100644 --- a/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs +++ b/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs @@ -19,7 +19,7 @@ mod noop; mod pre_state; /// Result type for geth style transaction trace -pub type TraceResult = crate::trace::common::TraceResult; +pub type TraceResult = crate::trace::common::TraceResult; /// blockTraceResult represents the results of tracing a single block when an entire chain is being /// traced. ref diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 606cf82446..8b6876fc5d 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -9,21 +9,21 @@ use crate::{ }; use async_trait::async_trait; use jsonrpsee::core::RpcResult; -use reth_primitives::{BlockId, BlockNumberOrTag, Bytes, H256, U256}; -use reth_provider::{BlockProvider, HeaderProvider}; +use reth_primitives::{Block, BlockId, BlockNumberOrTag, Bytes, H256, U256}; +use reth_provider::{BlockProvider, HeaderProvider, StateProviderBox}; use reth_revm::{ database::{State, SubState}, env::tx_env_with_recovered, tracing::{FourByteInspector, TracingInspector, TracingInspectorConfig}, }; -use reth_rlp::Encodable; +use reth_rlp::{Decodable, Encodable}; use reth_rpc_api::DebugApiServer; use reth_rpc_types::{ trace::geth::{ BlockTraceResult, FourByteFrame, GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingOptions, GethTraceFrame, NoopFrame, TraceResult, }, - CallRequest, RichBlock, + BlockError, CallRequest, RichBlock, }; use revm::primitives::Env; @@ -57,6 +57,60 @@ where Client: BlockProvider + HeaderProvider + 'static, Eth: EthTransactions + 'static, { + /// Replays the given block and returns the trace of each transaction. + /// + /// This expects a rlp encoded block + /// + /// Note, the parent of this block must be present, or it will fail. + pub async fn debug_trace_raw_block( + &self, + rlp_block: Bytes, + _opts: GethDebugTracingOptions, + ) -> EthResult> { + let block = + Block::decode(&mut rlp_block.as_ref()).map_err(BlockError::RlpDecodeRawBlock)?; + let _parent = block.parent_hash; + + // TODO we need the state after the parent block + + todo!() + } + + /// Replays a block and returns the trace of each transaction. + pub async fn debug_trace_block( + &self, + block_id: BlockId, + opts: GethDebugTracingOptions, + ) -> EthResult> { + let block_hash = self + .client + .block_hash_for_id(block_id)? + .ok_or_else(|| EthApiError::UnknownBlockNumber)?; + + let ((cfg, block_env, at), transactions) = futures::try_join!( + self.eth_api.evm_env_at(block_hash.into()), + self.eth_api.transactions_by_block(block_hash), + )?; + let transactions = transactions.ok_or_else(|| EthApiError::UnknownBlockNumber)?; + + // replay all transactions of the block + self.eth_api.with_state_at(at, move |state| { + let mut results = Vec::with_capacity(transactions.len()); + let mut db = SubState::new(State::new(state)); + + for tx in transactions { + let tx = tx.into_ecrecovered().ok_or(BlockError::InvalidSignature)?; + let tx = tx_env_with_recovered(&tx); + let env = Env { cfg: cfg.clone(), block: block_env.clone(), tx }; + // TODO(mattsse): get rid of clone by extracting necessary opts fields into a struct + let result = trace_transaction(opts.clone(), env, &mut db)?; + results.push(TraceResult::Success { result }); + } + + Ok(results) + }) + } + /// Trace the transaction according to the provided options. /// /// Ref: @@ -72,54 +126,12 @@ where 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 = transaction.into_recovered(); 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 => { - let mut inspector = FourByteInspector::default(); - let _ = inspect(db, env, &mut inspector)?; - return Ok(FourByteFrame::from(inspector).into()) - } - 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::from_geth_config(&config); - - 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()) + let mut db = SubState::new(State::new(state)); + trace_transaction(opts, env, &mut db) }) } @@ -238,28 +250,28 @@ where /// Handler for `debug_traceBlock` async fn debug_trace_block( &self, - _rlp_block: Bytes, - _opts: GethDebugTracingOptions, + rlp_block: Bytes, + opts: GethDebugTracingOptions, ) -> RpcResult> { - Err(internal_rpc_err("unimplemented")) + Ok(DebugApi::debug_trace_raw_block(self, rlp_block, opts).await?) } /// Handler for `debug_traceBlockByHash` async fn debug_trace_block_by_hash( &self, - _block: H256, - _opts: GethDebugTracingOptions, + block: H256, + opts: GethDebugTracingOptions, ) -> RpcResult> { - Err(internal_rpc_err("unimplemented")) + Ok(DebugApi::debug_trace_block(self, block.into(), opts).await?) } /// Handler for `debug_traceBlockByNumber` async fn debug_trace_block_by_number( &self, - _block: BlockNumberOrTag, - _opts: GethDebugTracingOptions, + block: BlockNumberOrTag, + opts: GethDebugTracingOptions, ) -> RpcResult> { - Err(internal_rpc_err("unimplemented")) + Ok(DebugApi::debug_trace_block(self, block.into(), opts).await?) } /// Handler for `debug_traceTransaction` @@ -287,3 +299,54 @@ impl std::fmt::Debug for DebugApi { f.debug_struct("DebugApi").finish_non_exhaustive() } } + +/// Executes the configured transaction in the environment on the given database. +/// +/// Note: this does not apply any state overrides if they're configured in the `opts`. +fn trace_transaction( + opts: GethDebugTracingOptions, + env: Env, + db: &mut SubState>, +) -> EthResult { + 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 => { + let mut inspector = FourByteInspector::default(); + let _ = inspect(db, env, &mut inspector)?; + return Ok(FourByteFrame::from(inspector).into()) + } + 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::from_geth_config(&config); + + 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()) +} diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs index c7f35e1601..2c2cd65952 100644 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ b/crates/rpc/rpc/src/eth/api/transactions.rs @@ -51,6 +51,12 @@ pub trait EthTransactions: Send + Sync { /// for. async fn evm_env_at(&self, at: BlockId) -> EthResult<(CfgEnv, BlockEnv, BlockId)>; + /// Get all transactions in the block with the given hash. + /// + /// Returns `None` if block does not exist. + async fn transactions_by_block(&self, block: H256) + -> EthResult>>; + /// Returns the transaction by hash. /// /// Checks the pool and state. @@ -172,6 +178,13 @@ where } } + async fn transactions_by_block( + &self, + block: H256, + ) -> EthResult>> { + Ok(self.cache().get_block_transactions(block).await?) + } + async fn transaction_by_hash(&self, hash: H256) -> EthResult> { if let Some(tx) = self.pool().get(&hash).map(|tx| tx.transaction.to_recovered_transaction()) {