mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-01-29 17:18:08 -05:00
feat(rpc): impl debug trace block (#2114)
Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -19,7 +19,7 @@ mod noop;
|
||||
mod pre_state;
|
||||
|
||||
/// Result type for geth style transaction trace
|
||||
pub type TraceResult = crate::trace::common::TraceResult<serde_json::Value, String>;
|
||||
pub type TraceResult = crate::trace::common::TraceResult<GethTraceFrame, String>;
|
||||
|
||||
/// blockTraceResult represents the results of tracing a single block when an entire chain is being
|
||||
/// traced. ref <https://github.com/ethereum/go-ethereum/blob/ee530c0d5aa70d2c00ab5691a89ab431b73f8165/eth/tracers/api.go#L218-L222>
|
||||
|
||||
@@ -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<Vec<TraceResult>> {
|
||||
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<Vec<TraceResult>> {
|
||||
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: <https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers>
|
||||
@@ -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<Vec<TraceResult>> {
|
||||
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<Vec<TraceResult>> {
|
||||
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<Vec<TraceResult>> {
|
||||
Err(internal_rpc_err("unimplemented"))
|
||||
Ok(DebugApi::debug_trace_block(self, block.into(), opts).await?)
|
||||
}
|
||||
|
||||
/// Handler for `debug_traceTransaction`
|
||||
@@ -287,3 +299,54 @@ impl<Client, Eth> std::fmt::Debug for DebugApi<Client, Eth> {
|
||||
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<StateProviderBox<'_>>,
|
||||
) -> EthResult<GethTraceFrame> {
|
||||
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())
|
||||
}
|
||||
|
||||
@@ -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<Option<Vec<TransactionSigned>>>;
|
||||
|
||||
/// Returns the transaction by hash.
|
||||
///
|
||||
/// Checks the pool and state.
|
||||
@@ -172,6 +178,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
async fn transactions_by_block(
|
||||
&self,
|
||||
block: H256,
|
||||
) -> EthResult<Option<Vec<TransactionSigned>>> {
|
||||
Ok(self.cache().get_block_transactions(block).await?)
|
||||
}
|
||||
|
||||
async fn transaction_by_hash(&self, hash: H256) -> EthResult<Option<TransactionSource>> {
|
||||
if let Some(tx) = self.pool().get(&hash).map(|tx| tx.transaction.to_recovered_transaction())
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user