use std::time::Duration;
use ethrex_common::H256;
use ethrex_common::{
serde_utils,
tracing::{CallTraceFrame, PrestateResult, StructLoggerEmit, StructLoggerResult},
};
use ethrex_vm::tracing::OpcodeTracerConfig;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::{rpc::RpcHandler, types::block_identifier::BlockIdentifier, utils::RpcErr};
const DEFAULT_REEXEC: u32 = 128;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5);
pub struct TraceTransactionRequest {
tx_hash: H256,
trace_config: TraceConfig,
}
pub struct TraceBlockByNumberRequest {
block: BlockIdentifier,
trace_config: TraceConfig,
}
#[derive(Deserialize, Default)]
#[serde(rename_all = "camelCase")]
struct TraceConfig {
#[serde(default)]
tracer: TracerType,
#[serde(default)]
tracer_config: Option<Value>,
#[serde(default, with = "serde_utils::duration::opt")]
timeout: Option<Duration>,
#[serde(default)]
reexec: Option<u32>,
}
#[derive(Default, Deserialize)]
#[serde(rename_all = "camelCase")]
#[allow(clippy::enum_variant_names)]
enum TracerType {
#[default]
CallTracer,
PrestateTracer,
OpcodeTracer,
}
#[derive(Deserialize, Default)]
#[serde(rename_all = "camelCase")]
struct CallTracerConfig {
#[serde(default)]
only_top_call: bool,
#[serde(default)]
with_log: bool,
}
#[derive(Deserialize, Default)]
#[serde(rename_all = "camelCase")]
struct PrestateTracerConfig {
#[serde(default)]
diff_mode: bool,
#[serde(default)]
include_empty: bool,
}
impl PrestateTracerConfig {
fn validate(&self) -> Result<(), RpcErr> {
if self.diff_mode && self.include_empty {
return Err(RpcErr::BadParams(
"cannot use diffMode with includeEmpty".to_string(),
));
}
Ok(())
}
}
type BlockTrace<TxTrace> = Vec<BlockTraceComponent<TxTrace>>;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct BlockTraceComponent<TxTrace: Serialize> {
tx_hash: H256,
result: TxTrace,
}
impl<TxTrace: Serialize> From<(H256, TxTrace)> for BlockTraceComponent<TxTrace> {
fn from(value: (H256, TxTrace)) -> Self {
BlockTraceComponent {
tx_hash: value.0,
result: value.1,
}
}
}
impl RpcHandler for TraceTransactionRequest {
fn parse(params: &Option<Vec<serde_json::Value>>) -> Result<Self, RpcErr> {
let params = params
.as_ref()
.ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
if params.len() != 1 && params.len() != 2 {
return Err(RpcErr::BadParams("Expected 1 or 2 params".to_owned()));
};
let trace_config = if params.len() == 2 {
serde_json::from_value(params[1].clone())?
} else {
TraceConfig::default()
};
Ok(TraceTransactionRequest {
tx_hash: serde_json::from_value(params[0].clone())?,
trace_config,
})
}
async fn handle(
&self,
context: crate::rpc::RpcApiContext,
) -> Result<serde_json::Value, crate::utils::RpcErr> {
let reexec = self.trace_config.reexec.unwrap_or(DEFAULT_REEXEC);
let timeout = self.trace_config.timeout.unwrap_or(DEFAULT_TIMEOUT);
match self.trace_config.tracer {
TracerType::CallTracer => {
let config = if let Some(value) = &self.trace_config.tracer_config {
serde_json::from_value(value.clone())?
} else {
CallTracerConfig::default()
};
let call_trace = context
.blockchain
.trace_transaction_calls(
self.tx_hash,
reexec,
timeout,
config.only_top_call,
config.with_log,
)
.await
.map_err(|err| RpcErr::Internal(err.to_string()))?;
let top_frame = call_trace
.into_iter()
.next()
.ok_or(RpcErr::Internal("Empty call trace".to_string()))?;
Ok(serde_json::to_value(top_frame)?)
}
TracerType::PrestateTracer => {
let config: PrestateTracerConfig =
if let Some(value) = &self.trace_config.tracer_config {
serde_json::from_value(value.clone())?
} else {
PrestateTracerConfig::default()
};
config.validate()?;
let result = context
.blockchain
.trace_transaction_prestate(
self.tx_hash,
reexec,
timeout,
config.diff_mode,
config.include_empty,
)
.await
.map_err(|err| RpcErr::Internal(err.to_string()))?;
match result {
PrestateResult::Prestate(trace) => Ok(serde_json::to_value(trace)?),
PrestateResult::Diff(diff) => Ok(serde_json::to_value(diff)?),
}
}
TracerType::OpcodeTracer => {
let cfg: OpcodeTracerConfig = self
.trace_config
.tracer_config
.as_ref()
.map(|v| serde_json::from_value(v.clone()))
.transpose()?
.unwrap_or_default();
let emit = StructLoggerEmit {
mem_size: cfg.enable_memory,
return_data: cfg.enable_return_data,
refund: false,
};
let result = context
.blockchain
.trace_transaction_opcodes(self.tx_hash, reexec, timeout, cfg)
.await
.map_err(|err| RpcErr::Internal(err.to_string()))?;
Ok(serde_json::to_value(StructLoggerResult {
result: &result,
emit,
})?)
}
}
}
}
impl RpcHandler for TraceBlockByNumberRequest {
fn parse(params: &Option<Vec<serde_json::Value>>) -> Result<Self, RpcErr> {
let params = params
.as_ref()
.ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
if params.len() != 1 && params.len() != 2 {
return Err(RpcErr::BadParams("Expected 1 or 2 params".to_owned()));
};
let trace_config = if params.len() == 2 {
serde_json::from_value(params[1].clone())?
} else {
TraceConfig::default()
};
let block = BlockIdentifier::parse(params[0].clone(), 0)?;
Ok(TraceBlockByNumberRequest {
block,
trace_config,
})
}
async fn handle(
&self,
context: crate::rpc::RpcApiContext,
) -> Result<serde_json::Value, crate::utils::RpcErr> {
let block_number = self
.block
.resolve_block_number(&context.storage)
.await?
.ok_or(RpcErr::Internal("Block not Found".to_string()))?;
let block = context
.storage
.get_block_by_number(block_number)
.await?
.ok_or(RpcErr::Internal("Block not Found".to_string()))?;
let reexec = self.trace_config.reexec.unwrap_or(DEFAULT_REEXEC);
let timeout = self.trace_config.timeout.unwrap_or(DEFAULT_TIMEOUT);
match self.trace_config.tracer {
TracerType::CallTracer => {
let config = if let Some(value) = &self.trace_config.tracer_config {
serde_json::from_value(value.clone())?
} else {
CallTracerConfig::default()
};
let call_traces = context
.blockchain
.trace_block_calls(
block,
reexec,
timeout,
config.only_top_call,
config.with_log,
)
.await
.map_err(|err| RpcErr::Internal(err.to_string()))?;
let block_trace: BlockTrace<CallTraceFrame> = call_traces
.into_iter()
.map(|(hash, trace)| {
let frame = trace
.into_iter()
.next()
.ok_or_else(|| RpcErr::Internal("Empty call trace".to_string()))?;
Ok((hash, frame).into())
})
.collect::<Result<_, RpcErr>>()?;
Ok(serde_json::to_value(block_trace)?)
}
TracerType::PrestateTracer => {
let config: PrestateTracerConfig =
if let Some(value) = &self.trace_config.tracer_config {
serde_json::from_value(value.clone())?
} else {
PrestateTracerConfig::default()
};
config.validate()?;
let prestate_traces = context
.blockchain
.trace_block_prestate(
block,
reexec,
timeout,
config.diff_mode,
config.include_empty,
)
.await
.map_err(|err| RpcErr::Internal(err.to_string()))?;
let block_trace: Vec<serde_json::Value> = prestate_traces
.into_iter()
.map(|(hash, result)| {
let trace_value = match result {
PrestateResult::Prestate(trace) => serde_json::to_value(trace)?,
PrestateResult::Diff(diff) => serde_json::to_value(diff)?,
};
serde_json::to_value(BlockTraceComponent {
tx_hash: hash,
result: trace_value,
})
})
.collect::<Result<_, serde_json::Error>>()?;
Ok(serde_json::to_value(block_trace)?)
}
TracerType::OpcodeTracer => {
let cfg: OpcodeTracerConfig = self
.trace_config
.tracer_config
.as_ref()
.map(|v| serde_json::from_value(v.clone()))
.transpose()?
.unwrap_or_default();
let emit = StructLoggerEmit {
mem_size: cfg.enable_memory,
return_data: cfg.enable_return_data,
refund: false,
};
let opcode_traces = context
.blockchain
.trace_block_opcodes(block, reexec, timeout, cfg)
.await
.map_err(|err| RpcErr::Internal(err.to_string()))?;
let block_trace: Vec<serde_json::Value> = opcode_traces
.into_iter()
.map(|(hash, result)| {
let wrapped = serde_json::to_value(StructLoggerResult {
result: &result,
emit,
})?;
serde_json::to_value(BlockTraceComponent {
tx_hash: hash,
result: wrapped,
})
})
.collect::<Result<_, serde_json::Error>>()?;
Ok(serde_json::to_value(block_trace)?)
}
}
}
}