feat(rpc): impl debug trace block (#2114)

Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com>
This commit is contained in:
Matthias Seitz
2023-04-04 22:04:00 +02:00
committed by GitHub
parent cf5a1114d0
commit 40b5201bbe
4 changed files with 138 additions and 59 deletions

View File

@@ -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

View File

@@ -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>

View File

@@ -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())
}

View File

@@ -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())
{