Skip to content

Commit

Permalink
feat: debug transaction (#1081)
Browse files Browse the repository at this point in the history
* add debug_traceTransaction API

* update tracer builder

* update EvmBuilder

* add debug transaction to the tracer

* add testing for debug transaction

* cleaning

* fix comments
  • Loading branch information
greged93 authored May 17, 2024
1 parent dea1025 commit 53496d8
Show file tree
Hide file tree
Showing 9 changed files with 338 additions and 184 deletions.
2 changes: 1 addition & 1 deletion .trunk/trunk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ lint:
- [email protected]
- [email protected]
- [email protected]
- trufflehog@3.75.1
- trufflehog@3.76.0
- [email protected]
ignore:
- linters: [ALL]
Expand Down
7 changes: 6 additions & 1 deletion src/eth_provider/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ pub enum EthRpcErrorCode {
impl From<EthApiError> for EthRpcErrorCode {
fn from(error: EthApiError) -> Self {
match error {
EthApiError::UnknownBlock | EthApiError::UnknownBlockNumber => EthRpcErrorCode::ResourceNotFound,
EthApiError::UnknownBlock | EthApiError::UnknownBlockNumber | EthApiError::TransactionNotFound => {
EthRpcErrorCode::ResourceNotFound
}
EthApiError::InvalidBlockRange
| EthApiError::Signature(_)
| EthApiError::EthereumDataFormat(_)
Expand All @@ -48,6 +50,9 @@ pub enum EthApiError {
/// When an unknown block number is encountered
#[error("unknown block number")]
UnknownBlockNumber,
/// When a transaction is not found
#[error("transaction not found")]
TransactionNotFound,
/// When an invalid block range is provided
#[error("invalid block range")]
InvalidBlockRange,
Expand Down
14 changes: 11 additions & 3 deletions src/eth_rpc/api/debug_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use jsonrpsee::core::RpcResult as Result;
use jsonrpsee::proc_macros::rpc;
use reth_primitives::{Bytes, B256};
use reth_rpc_types::{
trace::geth::{GethDebugTracingOptions, TraceResult},
trace::geth::{GethDebugTracingOptions, GethTrace, TraceResult},
BlockId, BlockNumberOrTag,
};

Expand Down Expand Up @@ -40,13 +40,21 @@ pub trait DebugApi {
&self,
block_number: BlockNumberOrTag,
opts: Option<GethDebugTracingOptions>,
) -> Result<Option<Vec<TraceResult>>>;
) -> Result<Vec<TraceResult>>;

/// Returns the Geth debug trace for the given block hash.
#[method(name = "traceBlockByHash")]
async fn trace_block_by_hash(
&self,
block_hash: B256,
opts: Option<GethDebugTracingOptions>,
) -> Result<Option<Vec<TraceResult>>>;
) -> Result<Vec<TraceResult>>;

/// Returns the Geth debug trace for the given transaction hash.
#[method(name = "traceTransaction")]
async fn trace_transaction(
&self,
transaction_hash: B256,
opts: Option<GethDebugTracingOptions>,
) -> Result<GethTrace>;
}
34 changes: 20 additions & 14 deletions src/eth_rpc/servers/debug_rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::sync::Arc;
use alloy_rlp::Encodable;
use jsonrpsee::core::{async_trait, RpcResult as Result};
use reth_primitives::{Block, Bytes, Header, Log, Receipt, ReceiptWithBloom, TransactionSigned, B256};
use reth_rpc_types::trace::geth::{GethDebugTracingOptions, TraceResult};
use reth_rpc_types::trace::geth::{GethDebugTracingOptions, GethTrace, TraceResult};
use reth_rpc_types::{BlockId, BlockNumberOrTag};

use crate::eth_provider::error::{EthApiError, EthereumDataFormatError, SignatureError};
Expand Down Expand Up @@ -153,14 +153,10 @@ impl<P: EthereumProvider + Send + Sync + 'static> DebugApiServer for DebugRpc<P>
&self,
block_number: BlockNumberOrTag,
opts: Option<GethDebugTracingOptions>,
) -> Result<Option<Vec<TraceResult>>> {
) -> Result<Vec<TraceResult>> {
let provider = Arc::new(&self.eth_provider);
let maybe_tracer =
TracerBuilder::new(provider).await?.with_block_id(BlockId::Number(block_number)).await?.build()?;
if maybe_tracer.is_none() {
return Ok(None);
}
let tracer = maybe_tracer.unwrap();
let tracer = TracerBuilder::new(provider).await?.with_block_id(BlockId::Number(block_number)).await?.build()?;

let traces = tracer.debug_block(opts.unwrap_or_default())?;
Ok(traces)
}
Expand All @@ -170,15 +166,25 @@ impl<P: EthereumProvider + Send + Sync + 'static> DebugApiServer for DebugRpc<P>
&self,
block_hash: B256,
opts: Option<GethDebugTracingOptions>,
) -> Result<Option<Vec<TraceResult>>> {
) -> Result<Vec<TraceResult>> {
let provider = Arc::new(&self.eth_provider);
let maybe_tracer =
let tracer =
TracerBuilder::new(provider).await?.with_block_id(BlockId::Hash(block_hash.into())).await?.build()?;
if maybe_tracer.is_none() {
return Ok(None);
}
let tracer = maybe_tracer.unwrap();

let traces = tracer.debug_block(opts.unwrap_or_default())?;
Ok(traces)
}

/// Returns the Geth debug trace for the given transaction hash.
async fn trace_transaction(
&self,
transaction_hash: B256,
opts: Option<GethDebugTracingOptions>,
) -> Result<GethTrace> {
let provider = Arc::new(&self.eth_provider);
let tracer = TracerBuilder::new(provider).await?.with_transaction_hash(transaction_hash).await?.build()?;

let trace = tracer.debug_transaction(transaction_hash, opts.unwrap_or_default())?;
Ok(trace)
}
}
7 changes: 2 additions & 5 deletions src/eth_rpc/servers/trace_rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,8 @@ impl<P: EthereumProvider + Send + Sync + 'static> TraceApiServer for TraceRpc<P>
/// Returns the parity traces for the given block.
async fn trace_block(&self, block_id: BlockId) -> Result<Option<Vec<LocalizedTransactionTrace>>> {
let provider = Arc::new(&self.eth_provider);
let maybe_tracer = TracerBuilder::new(provider).await?.with_block_id(block_id).await?.build()?;
if maybe_tracer.is_none() {
return Ok(None);
}
let tracer = maybe_tracer.unwrap();
let tracer = TracerBuilder::new(provider).await?.with_block_id(block_id).await?.build()?;

let traces = tracer.trace_block(TracingInspectorConfig::default_parity())?;
Ok(traces)
}
Expand Down
54 changes: 28 additions & 26 deletions src/tracing/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::eth_provider::{
provider::EthereumProvider,
};

use super::{config::KakarotEvmConfig, database::EthDatabaseSnapshot, Tracer, TracerResult};
use super::{database::EthDatabaseSnapshot, Tracer, TracerResult};

#[derive(Debug)]
pub struct Floating;
Expand All @@ -18,7 +18,7 @@ pub struct Pinned;
pub struct TracerBuilder<P: EthereumProvider + Send + Sync, Status = Floating> {
eth_provider: P,
env: Env,
block: Option<reth_rpc_types::Block>,
block: reth_rpc_types::Block,
_phantom: std::marker::PhantomData<Status>,
}

Expand All @@ -29,66 +29,68 @@ impl<P: EthereumProvider + Send + Sync + Clone> TracerBuilder<P, Floating> {

let env = Env { cfg, ..Default::default() };

Ok(Self { eth_provider, env, block: None, _phantom: std::marker::PhantomData })
Ok(Self { eth_provider, env, block: Default::default(), _phantom: std::marker::PhantomData })
}

/// Sets the block to trace
pub async fn with_block_id(self, block_id: BlockId) -> TracerResult<TracerBuilder<P, Pinned>> {
let maybe_block = self.block(block_id).await?;
let block = self.block(block_id).await?;

Ok(TracerBuilder {
eth_provider: self.eth_provider.clone(),
env: self.env.clone(),
block: maybe_block,
block,
_phantom: std::marker::PhantomData,
})
}

/// Sets the block to trace given the transaction hash
pub async fn with_transaction_hash(self, transaction_hash: B256) -> TracerResult<TracerBuilder<P, Pinned>> {
let transaction =
self.eth_provider.transaction_by_hash(transaction_hash).await?.ok_or(EthApiError::TransactionNotFound)?;

// we can't trace a pending transaction
if transaction.block_number.is_none() {
return Err(EthApiError::UnknownBlock);
}

self.with_block_id(BlockId::Number(transaction.block_number.unwrap().into())).await
}

/// Fetches a block from the Ethereum provider given a block id
///
/// # Returns
///
/// Returns the block if it exists, otherwise returns None
async fn block(&self, block_id: BlockId) -> TracerResult<Option<reth_rpc_types::Block>> {
let maybe_block = match block_id {
async fn block(&self, block_id: BlockId) -> TracerResult<reth_rpc_types::Block> {
let block = match block_id {
BlockId::Hash(hash) => self.eth_provider.block_by_hash(hash.block_hash, true).await?,
BlockId::Number(number) => self.eth_provider.block_by_number(number, true).await?,
};

if maybe_block.is_none() {
return Ok(None);
}
.ok_or(EthApiError::UnknownBlock)?;

// we can't trace a pending block
let block = maybe_block.unwrap();
if block.header.hash.unwrap_or_default().is_zero() {
return Err(EthApiError::UnknownBlock);
}

Ok(Some(block.inner))
Ok(block.inner)
}
}

impl<P: EthereumProvider + Send + Sync + Clone> TracerBuilder<P, Pinned> {
/// Builds the tracer. Returns None if the block was not found during the call to
/// `with_block_id`.
pub fn build(self) -> TracerResult<Option<Tracer<P>>> {
// If block is None, it means that the block was not found
if self.block.is_none() {
return Ok(None);
}
let block = self.block.as_ref().unwrap();

let transactions = match &block.transactions {
/// Builds the tracer.
pub fn build(self) -> TracerResult<Tracer<P>> {
let transactions = match &self.block.transactions {
BlockTransactions::Full(transactions) => transactions.clone(),
_ => return Err(TransactionError::ExpectedFullTransactions.into()),
};

let env = self.init_env_with_handler_config();
// DB should use the state of the parent block
let db = EthDatabaseSnapshot::new(self.eth_provider, BlockId::Hash(block.header.parent_hash.into()));
let db = EthDatabaseSnapshot::new(self.eth_provider, BlockId::Hash(self.block.header.parent_hash.into()));

Ok(Some(Tracer { env, transactions, cfg: KakarotEvmConfig, db }))
Ok(Tracer { env, transactions, db })
}

/// Init an EnvWithHandlerCfg.
Expand All @@ -102,7 +104,7 @@ impl<P: EthereumProvider + Send + Sync + Clone> TracerBuilder<P, Pinned> {
let mut env = self.env.clone();

let Header { number, timestamp, gas_limit, miner, base_fee_per_gas, difficulty, .. } =
self.block.clone().expect("Block not set").header;
self.block.header.clone();
let block_env = BlockEnv {
number: U256::from(number.unwrap_or_default()),
timestamp: U256::from(timestamp),
Expand Down
65 changes: 40 additions & 25 deletions src/tracing/config.rs
Original file line number Diff line number Diff line change
@@ -1,46 +1,61 @@
use std::sync::Arc;

use alphanet_instructions::{context::InstructionsContext, eip3074};
use reth_revm::{inspector_handle_register, primitives::EnvWithHandlerCfg, Database, Evm, EvmBuilder};
use reth_revm::{
handler::register::HandleRegisterBox, inspector_handle_register, primitives::EnvWithHandlerCfg, Database, Evm,
};

#[derive(Debug, Clone)]
pub(super) struct KakarotEvmConfig;
pub(super) struct EvmBuilder;

impl KakarotEvmConfig {
/// Returns new EVM with the given database and env. Similar to the implementation of [reth_evm::ConfigureEvmEnv]
impl EvmBuilder {
/// Returns new EVM with the given database, env and inspector. Similar to the implementation of [reth_evm::ConfigureEvmEnv]
/// but only keeping the necessary API.
pub(super) fn evm_with_env_and_inspector<'a, DB: Database + 'a, I: reth_revm::Inspector<DB>>(
&self,
db: DB,
env: EnvWithHandlerCfg,
inspector: I,
) -> Evm<'a, I, DB> {
let instructions_context = InstructionsContext::default();
let to_capture_instructions = instructions_context.clone();

let mut evm = EvmBuilder::default()
let mut evm = reth_revm::EvmBuilder::default()
.with_db(db)
.with_external_context(inspector)
.append_handler_register_box(Box::new(move |handler| {
if let Some(ref mut table) = handler.instruction_table {
for boxed_instruction_with_opcode in eip3074::boxed_instructions(to_capture_instructions.clone()) {
table.insert_boxed(
boxed_instruction_with_opcode.opcode,
boxed_instruction_with_opcode.boxed_instruction,
);
}
}
let post_execution_context = instructions_context.clone();
handler.post_execution.end = Arc::new(move |_, outcome: _| {
// at the end of the transaction execution we clear the instructions
post_execution_context.clear();
outcome
});
}))
.append_handler_register_box(eip3074_handle_register())
.append_handler_register(inspector_handle_register)
.build();
evm.modify_spec_id(env.spec_id());
evm.context.evm.env = env.env;
evm
}

/// Returns new EVM with the given database and env. Similar to the implementation of [reth_evm::ConfigureEvmEnv]
/// but only keeping the necessary API.
pub(super) fn evm_with_env<'a, DB: Database + 'a>(db: DB, env: EnvWithHandlerCfg) -> Evm<'a, (), DB> {
let mut evm =
reth_revm::EvmBuilder::default().with_db(db).append_handler_register_box(eip3074_handle_register()).build();
evm.modify_spec_id(env.spec_id());
evm.context.evm.env = env.env;
evm
}
}

fn eip3074_handle_register<EXT, DB: Database>() -> HandleRegisterBox<EXT, DB> {
let instructions_context = InstructionsContext::default();
let to_capture_instructions = instructions_context.clone();

Box::new(move |handler| {
if let Some(ref mut table) = handler.instruction_table {
for boxed_instruction_with_opcode in eip3074::boxed_instructions(to_capture_instructions.clone()) {
table.insert_boxed(
boxed_instruction_with_opcode.opcode,
boxed_instruction_with_opcode.boxed_instruction,
);
}
}
let post_execution_context = instructions_context.clone();
handler.post_execution.end = Arc::new(move |_, outcome: _| {
// at the end of the transaction execution we clear the instructions
post_execution_context.clear();
outcome
});
})
}
Loading

0 comments on commit 53496d8

Please sign in to comment.