use alloy_consensus::BlockHeader as _;
use alloy_eips::BlockId;
use alloy_primitives::{map::HashSet, Bytes, B256, U256};
use alloy_rpc_types_eth::{
state::{EvmOverrides, StateOverride},
transaction::TransactionRequest,
BlockOverrides, Index,
};
use alloy_rpc_types_trace::{
filter::TraceFilter,
opcode::{BlockOpcodeGas, TransactionOpcodeGas},
parity::*,
tracerequest::TraceCallRequest,
};
use async_trait::async_trait;
use jsonrpsee::core::RpcResult;
use reth_chainspec::{EthChainSpec, EthereumHardfork, MAINNET, SEPOLIA};
use reth_consensus_common::calc::{base_block_reward_pre_merge, block_reward, ommer_reward};
use reth_evm::{env::EvmEnv, ConfigureEvmEnv};
use reth_primitives_traits::{BlockBody, BlockHeader};
use reth_provider::{BlockNumReader, BlockReader, ChainSpecProvider};
use reth_revm::database::StateProviderDatabase;
use reth_rpc_api::TraceApiServer;
use reth_rpc_eth_api::{helpers::TraceExt, FromEthApiError, RpcNodeCore};
use reth_rpc_eth_types::{error::EthApiError, utils::recover_raw_transaction};
use reth_tasks::pool::BlockingTaskGuard;
use reth_transaction_pool::{PoolPooledTx, PoolTransaction, TransactionPool};
use revm::{
db::{CacheDB, DatabaseCommit},
primitives::EnvWithHandlerCfg,
};
use revm_inspectors::{
opcode::OpcodeGasInspector,
tracing::{parity::populate_state_diff, TracingInspector, TracingInspectorConfig},
};
use std::sync::Arc;
use tokio::sync::{AcquireError, OwnedSemaphorePermit};
pub struct TraceApi<Eth> {
inner: Arc<TraceApiInner<Eth>>,
}
impl<Eth> TraceApi<Eth> {
pub fn new(eth_api: Eth, blocking_task_guard: BlockingTaskGuard) -> Self {
let inner = Arc::new(TraceApiInner { eth_api, blocking_task_guard });
Self { inner }
}
async fn acquire_trace_permit(
&self,
) -> std::result::Result<OwnedSemaphorePermit, AcquireError> {
self.inner.blocking_task_guard.clone().acquire_owned().await
}
pub fn eth_api(&self) -> &Eth {
&self.inner.eth_api
}
}
impl<Eth: RpcNodeCore> TraceApi<Eth> {
pub fn provider(&self) -> &Eth::Provider {
self.inner.eth_api.provider()
}
}
impl<Eth> TraceApi<Eth>
where
Eth: TraceExt + 'static,
{
pub async fn trace_call(
&self,
trace_request: TraceCallRequest,
) -> Result<TraceResults, Eth::Error> {
let at = trace_request.block_id.unwrap_or_default();
let config = TracingInspectorConfig::from_parity_config(&trace_request.trace_types);
let overrides =
EvmOverrides::new(trace_request.state_overrides, trace_request.block_overrides);
let mut inspector = TracingInspector::new(config);
let this = self.clone();
self.eth_api()
.spawn_with_call_at(trace_request.call, at, overrides, move |db, env| {
let db = db.0;
let (res, _) = this.eth_api().inspect(&mut *db, env, &mut inspector)?;
let trace_res = inspector
.into_parity_builder()
.into_trace_results_with_state(&res, &trace_request.trace_types, &db)
.map_err(Eth::Error::from_eth_err)?;
Ok(trace_res)
})
.await
}
pub async fn trace_raw_transaction(
&self,
tx: Bytes,
trace_types: HashSet<TraceType>,
block_id: Option<BlockId>,
) -> Result<TraceResults, Eth::Error> {
let tx = recover_raw_transaction::<PoolPooledTx<Eth::Pool>>(&tx)?
.map_transaction(<Eth::Pool as TransactionPool>::Transaction::pooled_into_consensus);
let (evm_env, at) = self.eth_api().evm_env_at(block_id.unwrap_or_default()).await?;
let EvmEnv { cfg_env_with_handler_cfg, block_env } = evm_env;
let env = EnvWithHandlerCfg::new_with_cfg_env(
cfg_env_with_handler_cfg,
block_env,
self.eth_api().evm_config().tx_env(tx.as_signed(), tx.signer()),
);
let config = TracingInspectorConfig::from_parity_config(&trace_types);
self.eth_api()
.spawn_trace_at_with_state(env, config, at, move |inspector, res, db| {
inspector
.into_parity_builder()
.into_trace_results_with_state(&res, &trace_types, &db)
.map_err(Eth::Error::from_eth_err)
})
.await
}
pub async fn trace_call_many(
&self,
calls: Vec<(TransactionRequest, HashSet<TraceType>)>,
block_id: Option<BlockId>,
) -> Result<Vec<TraceResults>, Eth::Error> {
let at = block_id.unwrap_or(BlockId::pending());
let (evm_env, at) = self.eth_api().evm_env_at(at).await?;
let EvmEnv { cfg_env_with_handler_cfg, block_env } = evm_env;
let this = self.clone();
self.eth_api()
.spawn_with_state_at_block(at, move |state| {
let mut results = Vec::with_capacity(calls.len());
let mut db = CacheDB::new(StateProviderDatabase::new(state));
let mut calls = calls.into_iter().peekable();
while let Some((call, trace_types)) = calls.next() {
let env = this.eth_api().prepare_call_env(
cfg_env_with_handler_cfg.clone(),
block_env.clone(),
call,
&mut db,
Default::default(),
)?;
let config = TracingInspectorConfig::from_parity_config(&trace_types);
let mut inspector = TracingInspector::new(config);
let (res, _) = this.eth_api().inspect(&mut db, env, &mut inspector)?;
let trace_res = inspector
.into_parity_builder()
.into_trace_results_with_state(&res, &trace_types, &db)
.map_err(Eth::Error::from_eth_err)?;
results.push(trace_res);
if calls.peek().is_some() {
db.commit(res.state)
}
}
Ok(results)
})
.await
}
pub async fn replay_transaction(
&self,
hash: B256,
trace_types: HashSet<TraceType>,
) -> Result<TraceResults, Eth::Error> {
let config = TracingInspectorConfig::from_parity_config(&trace_types);
self.eth_api()
.spawn_trace_transaction_in_block(hash, config, move |_, inspector, res, db| {
let trace_res = inspector
.into_parity_builder()
.into_trace_results_with_state(&res, &trace_types, &db)
.map_err(Eth::Error::from_eth_err)?;
Ok(trace_res)
})
.await
.transpose()
.ok_or(EthApiError::TransactionNotFound)?
}
pub async fn trace_get(
&self,
hash: B256,
indices: Vec<usize>,
) -> Result<Option<LocalizedTransactionTrace>, Eth::Error> {
if indices.len() != 1 {
return Ok(None)
}
self.trace_get_index(hash, indices[0]).await
}
pub async fn trace_get_index(
&self,
hash: B256,
index: usize,
) -> Result<Option<LocalizedTransactionTrace>, Eth::Error> {
Ok(self.trace_transaction(hash).await?.and_then(|traces| traces.into_iter().nth(index)))
}
pub async fn trace_filter(
&self,
filter: TraceFilter,
) -> Result<Vec<LocalizedTransactionTrace>, Eth::Error> {
let matcher = Arc::new(filter.matcher());
let TraceFilter { from_block, to_block, after, count, .. } = filter;
let start = from_block.unwrap_or(0);
let end = if let Some(to_block) = to_block {
to_block
} else {
self.provider().best_block_number().map_err(Eth::Error::from_eth_err)?
};
if start > end {
return Err(EthApiError::InvalidParams(
"invalid parameters: fromBlock cannot be greater than toBlock".to_string(),
)
.into())
}
let distance = end.saturating_sub(start);
if distance > 100 {
return Err(EthApiError::InvalidParams(
"Block range too large; currently limited to 100 blocks".to_string(),
)
.into())
}
let blocks = self
.provider()
.sealed_block_with_senders_range(start..=end)
.map_err(Eth::Error::from_eth_err)?
.into_iter()
.map(Arc::new)
.collect::<Vec<_>>();
let mut block_traces = Vec::with_capacity(blocks.len());
for block in &blocks {
let matcher = matcher.clone();
let traces = self.eth_api().trace_block_until(
block.hash().into(),
Some(block.clone()),
None,
TracingInspectorConfig::default_parity(),
move |tx_info, inspector, _, _, _| {
let mut traces =
inspector.into_parity_builder().into_localized_transaction_traces(tx_info);
traces.retain(|trace| matcher.matches(&trace.trace));
Ok(Some(traces))
},
);
block_traces.push(traces);
}
let block_traces = futures::future::try_join_all(block_traces).await?;
let mut all_traces = block_traces
.into_iter()
.flatten()
.flat_map(|traces| traces.into_iter().flatten().flat_map(|traces| traces.into_iter()))
.collect::<Vec<_>>();
for block in &blocks {
if let Some(base_block_reward) = self.calculate_base_block_reward(block.header())? {
all_traces.extend(
self.extract_reward_traces(
block.header.header(),
block.body.ommers(),
base_block_reward,
)
.into_iter()
.filter(|trace| matcher.matches(&trace.trace)),
);
} else {
break
}
}
if let Some(after) = after.map(|a| a as usize) {
if after < all_traces.len() {
all_traces.drain(..after);
} else {
return Ok(vec![])
}
}
if let Some(count) = count {
let count = count as usize;
if count < all_traces.len() {
all_traces.truncate(count);
}
};
Ok(all_traces)
}
pub async fn trace_transaction(
&self,
hash: B256,
) -> Result<Option<Vec<LocalizedTransactionTrace>>, Eth::Error> {
self.eth_api()
.spawn_trace_transaction_in_block(
hash,
TracingInspectorConfig::default_parity(),
move |tx_info, inspector, _, _| {
let traces =
inspector.into_parity_builder().into_localized_transaction_traces(tx_info);
Ok(traces)
},
)
.await
}
pub async fn trace_block(
&self,
block_id: BlockId,
) -> Result<Option<Vec<LocalizedTransactionTrace>>, Eth::Error> {
let traces = self.eth_api().trace_block_with(
block_id,
None,
TracingInspectorConfig::default_parity(),
|tx_info, inspector, _, _, _| {
let traces =
inspector.into_parity_builder().into_localized_transaction_traces(tx_info);
Ok(traces)
},
);
let block = self.eth_api().block_with_senders(block_id);
let (maybe_traces, maybe_block) = futures::try_join!(traces, block)?;
let mut maybe_traces =
maybe_traces.map(|traces| traces.into_iter().flatten().collect::<Vec<_>>());
if let (Some(block), Some(traces)) = (maybe_block, maybe_traces.as_mut()) {
if let Some(base_block_reward) =
self.calculate_base_block_reward(block.header.header())?
{
traces.extend(self.extract_reward_traces(
block.block.header(),
block.body.ommers(),
base_block_reward,
));
}
}
Ok(maybe_traces)
}
pub async fn replay_block_transactions(
&self,
block_id: BlockId,
trace_types: HashSet<TraceType>,
) -> Result<Option<Vec<TraceResultsWithTransactionHash>>, Eth::Error> {
self.eth_api()
.trace_block_with(
block_id,
None,
TracingInspectorConfig::from_parity_config(&trace_types),
move |tx_info, inspector, res, state, db| {
let mut full_trace =
inspector.into_parity_builder().into_trace_results(&res, &trace_types);
if let Some(ref mut state_diff) = full_trace.state_diff {
populate_state_diff(state_diff, db, state.iter())
.map_err(Eth::Error::from_eth_err)?;
}
let trace = TraceResultsWithTransactionHash {
transaction_hash: tx_info.hash.expect("tx hash is set"),
full_trace,
};
Ok(trace)
},
)
.await
}
pub async fn trace_transaction_opcode_gas(
&self,
tx_hash: B256,
) -> Result<Option<TransactionOpcodeGas>, Eth::Error> {
self.eth_api()
.spawn_trace_transaction_in_block_with_inspector(
tx_hash,
OpcodeGasInspector::default(),
move |_tx_info, inspector, _res, _| {
let trace = TransactionOpcodeGas {
transaction_hash: tx_hash,
opcode_gas: inspector.opcode_gas_iter().collect(),
};
Ok(trace)
},
)
.await
}
pub async fn trace_block_opcode_gas(
&self,
block_id: BlockId,
) -> Result<Option<BlockOpcodeGas>, Eth::Error> {
let res = self
.eth_api()
.trace_block_inspector(
block_id,
None,
OpcodeGasInspector::default,
move |tx_info, inspector, _res, _, _| {
let trace = TransactionOpcodeGas {
transaction_hash: tx_info.hash.expect("tx hash is set"),
opcode_gas: inspector.opcode_gas_iter().collect(),
};
Ok(trace)
},
)
.await?;
let Some(transactions) = res else { return Ok(None) };
let Some(block) = self.eth_api().block_with_senders(block_id).await? else {
return Ok(None)
};
Ok(Some(BlockOpcodeGas {
block_hash: block.hash(),
block_number: block.header.number(),
transactions,
}))
}
fn calculate_base_block_reward<H: BlockHeader>(
&self,
header: &H,
) -> Result<Option<u128>, Eth::Error> {
let chain_spec = self.provider().chain_spec();
let is_paris_activated = if chain_spec.chain() == MAINNET.chain() {
Some(header.number()) >= EthereumHardfork::Paris.mainnet_activation_block()
} else if chain_spec.chain() == SEPOLIA.chain() {
Some(header.number()) >= EthereumHardfork::Paris.sepolia_activation_block()
} else {
true
};
if is_paris_activated {
return Ok(None)
}
Ok(Some(base_block_reward_pre_merge(&chain_spec, header.number())))
}
fn extract_reward_traces<H: BlockHeader>(
&self,
header: &H,
ommers: Option<&[H]>,
base_block_reward: u128,
) -> Vec<LocalizedTransactionTrace> {
let ommers_cnt = ommers.map(|o| o.len()).unwrap_or_default();
let mut traces = Vec::with_capacity(ommers_cnt + 1);
let block_reward = block_reward(base_block_reward, ommers_cnt);
traces.push(reward_trace(
header,
RewardAction {
author: header.beneficiary(),
reward_type: RewardType::Block,
value: U256::from(block_reward),
},
));
let Some(ommers) = ommers else { return traces };
for uncle in ommers {
let uncle_reward = ommer_reward(base_block_reward, header.number(), uncle.number());
traces.push(reward_trace(
header,
RewardAction {
author: uncle.beneficiary(),
reward_type: RewardType::Uncle,
value: U256::from(uncle_reward),
},
));
}
traces
}
}
#[async_trait]
impl<Eth> TraceApiServer for TraceApi<Eth>
where
Eth: TraceExt + 'static,
{
async fn trace_call(
&self,
call: TransactionRequest,
trace_types: HashSet<TraceType>,
block_id: Option<BlockId>,
state_overrides: Option<StateOverride>,
block_overrides: Option<Box<BlockOverrides>>,
) -> RpcResult<TraceResults> {
let _permit = self.acquire_trace_permit().await;
let request =
TraceCallRequest { call, trace_types, block_id, state_overrides, block_overrides };
Ok(Self::trace_call(self, request).await.map_err(Into::into)?)
}
async fn trace_call_many(
&self,
calls: Vec<(TransactionRequest, HashSet<TraceType>)>,
block_id: Option<BlockId>,
) -> RpcResult<Vec<TraceResults>> {
let _permit = self.acquire_trace_permit().await;
Ok(Self::trace_call_many(self, calls, block_id).await.map_err(Into::into)?)
}
async fn trace_raw_transaction(
&self,
data: Bytes,
trace_types: HashSet<TraceType>,
block_id: Option<BlockId>,
) -> RpcResult<TraceResults> {
let _permit = self.acquire_trace_permit().await;
Ok(Self::trace_raw_transaction(self, data, trace_types, block_id)
.await
.map_err(Into::into)?)
}
async fn replay_block_transactions(
&self,
block_id: BlockId,
trace_types: HashSet<TraceType>,
) -> RpcResult<Option<Vec<TraceResultsWithTransactionHash>>> {
let _permit = self.acquire_trace_permit().await;
Ok(Self::replay_block_transactions(self, block_id, trace_types)
.await
.map_err(Into::into)?)
}
async fn replay_transaction(
&self,
transaction: B256,
trace_types: HashSet<TraceType>,
) -> RpcResult<TraceResults> {
let _permit = self.acquire_trace_permit().await;
Ok(Self::replay_transaction(self, transaction, trace_types).await.map_err(Into::into)?)
}
async fn trace_block(
&self,
block_id: BlockId,
) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
let _permit = self.acquire_trace_permit().await;
Ok(Self::trace_block(self, block_id).await.map_err(Into::into)?)
}
async fn trace_filter(&self, filter: TraceFilter) -> RpcResult<Vec<LocalizedTransactionTrace>> {
Ok(Self::trace_filter(self, filter).await.map_err(Into::into)?)
}
async fn trace_get(
&self,
hash: B256,
indices: Vec<Index>,
) -> RpcResult<Option<LocalizedTransactionTrace>> {
let _permit = self.acquire_trace_permit().await;
Ok(Self::trace_get(self, hash, indices.into_iter().map(Into::into).collect())
.await
.map_err(Into::into)?)
}
async fn trace_transaction(
&self,
hash: B256,
) -> RpcResult<Option<Vec<LocalizedTransactionTrace>>> {
let _permit = self.acquire_trace_permit().await;
Ok(Self::trace_transaction(self, hash).await.map_err(Into::into)?)
}
async fn trace_transaction_opcode_gas(
&self,
tx_hash: B256,
) -> RpcResult<Option<TransactionOpcodeGas>> {
let _permit = self.acquire_trace_permit().await;
Ok(Self::trace_transaction_opcode_gas(self, tx_hash).await.map_err(Into::into)?)
}
async fn trace_block_opcode_gas(&self, block_id: BlockId) -> RpcResult<Option<BlockOpcodeGas>> {
let _permit = self.acquire_trace_permit().await;
Ok(Self::trace_block_opcode_gas(self, block_id).await.map_err(Into::into)?)
}
}
impl<Eth> std::fmt::Debug for TraceApi<Eth> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TraceApi").finish_non_exhaustive()
}
}
impl<Eth> Clone for TraceApi<Eth> {
fn clone(&self) -> Self {
Self { inner: Arc::clone(&self.inner) }
}
}
struct TraceApiInner<Eth> {
eth_api: Eth,
blocking_task_guard: BlockingTaskGuard,
}
fn reward_trace<H: BlockHeader>(header: &H, reward: RewardAction) -> LocalizedTransactionTrace {
LocalizedTransactionTrace {
block_hash: Some(header.hash_slow()),
block_number: Some(header.number()),
transaction_hash: None,
transaction_position: None,
trace: TransactionTrace {
trace_address: vec![],
subtraces: 0,
action: Action::Reward(reward),
error: None,
result: None,
},
}
}