feat(rpc): impl traceCall (#2029)

This commit is contained in:
Matthias Seitz
2023-03-31 20:19:56 +02:00
committed by GitHub
parent b1643f4ca6
commit 00712d642e
5 changed files with 126 additions and 66 deletions

View File

@ -1,5 +1,6 @@
use crate::tracing::{types::CallTraceNode, TracingInspectorConfig};
use reth_rpc_types::{trace::parity::*, TransactionInfo};
use revm::primitives::ExecutionResult;
use std::collections::HashSet;
/// A type for creating parity style traces
@ -82,6 +83,24 @@ impl ParityTraceBuilder {
self.into_localized_transaction_traces_iter(info).collect()
}
/// Consumes the inspector and returns the trace results according to the configured trace
/// types.
pub fn into_trace_results(
self,
res: ExecutionResult,
trace_types: &HashSet<TraceType>,
) -> TraceResults {
let output = match res {
ExecutionResult::Success { output, .. } => output.into_data(),
ExecutionResult::Revert { output, .. } => output,
ExecutionResult::Halt { .. } => Default::default(),
};
let (trace, vm_trace, state_diff) = self.into_trace_type_traces(trace_types);
TraceResults { output: output.into(), trace, vm_trace, state_diff }
}
/// Returns the tracing types that are configured in the set
pub fn into_trace_type_traces(
self,

View File

@ -1,9 +1,6 @@
use crate::{
stack::MaybeOwnedInspector,
tracing::{
types::{CallKind, LogCallOrder, RawLog},
utils::{gas_used, get_create_address},
},
use crate::tracing::{
types::{CallKind, LogCallOrder, RawLog},
utils::{gas_used, get_create_address},
};
pub use arena::CallTraceArena;
use reth_primitives::{bytes::Bytes, Address, H256, U256};
@ -46,10 +43,7 @@ pub struct TracingInspector {
/// Tracks the return value of the last call
last_call_return_data: Option<Bytes>,
/// The gas inspector used to track remaining gas.
///
/// This is either owned by this inspector directly or part of a stack of inspectors, in which
/// case all delegated functions are no-ops.
gas_inspector: MaybeOwnedInspector<GasInspector>,
gas_inspector: GasInspector,
}
// === impl TracingInspector ===
@ -77,20 +71,6 @@ impl TracingInspector {
GethTraceBuilder::new(self.traces.arena, self.config)
}
/// Configures a [GasInspector]
///
/// If this [TracingInspector] is part of a stack [InspectorStack](crate::stack::InspectorStack)
/// which already uses a [GasInspector], it can be reused as [MaybeOwnedInspector::Stacked] in
/// which case the `gas_inspector`'s usage will be a no-op within the context of this
/// [TracingInspector].
pub fn with_stacked_gas_inspector(
mut self,
gas_inspector: MaybeOwnedInspector<GasInspector>,
) -> Self {
self.gas_inspector = gas_inspector;
self
}
/// Returns the last trace [CallTrace] index from the stack.
///
/// # Panics
@ -201,7 +181,7 @@ impl TracingInspector {
stack,
memory,
memory_size: interp.memory.len(),
gas: self.gas_inspector.as_ref().gas_remaining(),
gas: self.gas_inspector.gas_remaining(),
gas_refund_counter: interp.gas.refunded() as u64,
// fields will be populated end of call
@ -251,7 +231,7 @@ impl TracingInspector {
};
}
step.gas_cost = step.gas - self.gas_inspector.as_ref().gas_remaining();
step.gas_cost = step.gas - self.gas_inspector.gas_remaining();
}
// set the status

View File

@ -5,7 +5,7 @@ use crate::{
error::{EthApiError, EthResult, InvalidTransactionError, RevertError},
revm_utils::{
build_call_evm_env, cap_tx_gas_limit_with_caller_allowance, get_precompiles, inspect,
prepare_call_env, transact,
transact,
},
EthTransactions,
},
@ -18,11 +18,11 @@ use reth_revm::{
access_list::AccessListInspector,
database::{State, SubState},
};
use reth_rpc_types::{state::StateOverride, CallRequest};
use reth_rpc_types::CallRequest;
use reth_transaction_pool::TransactionPool;
use revm::{
db::{CacheDB, DatabaseRef},
primitives::{BlockEnv, CfgEnv, Env, ExecutionResult, Halt, ResultAndState, TransactTo},
primitives::{BlockEnv, CfgEnv, Env, ExecutionResult, Halt, TransactTo},
};
use tracing::trace;
@ -36,22 +36,6 @@ where
Client: BlockProvider + StateProviderFactory + EvmEnvProvider + 'static,
Network: Send + Sync + 'static,
{
/// Executes the call request at the given [BlockId]
pub(crate) async fn transact_call_at(
&self,
request: CallRequest,
at: BlockId,
state_overrides: Option<StateOverride>,
) -> EthResult<(ResultAndState, Env)> {
let (cfg, block_env, at) = self.evm_env_at(at).await?;
let state = self.state_at(at)?;
let mut db = SubState::new(State::new(state));
let env = prepare_call_env(cfg, block_env, request, &mut db, state_overrides)?;
trace!(target: "rpc::eth::call", ?env, "Executing call");
transact(&mut db, env)
}
/// Estimate gas needed for execution of the `request` at the [BlockId].
pub(crate) async fn estimate_gas_at(
&self,

View File

@ -2,6 +2,7 @@
use crate::{
eth::{
error::{EthApiError, EthResult, SignError},
revm_utils::{inspect, prepare_call_env, transact},
utils::recover_raw_transaction,
},
EthApi,
@ -15,13 +16,18 @@ use reth_primitives::{
TxLegacy, H256, U128, U256, U64,
};
use reth_provider::{BlockProvider, EvmEnvProvider, StateProviderBox, StateProviderFactory};
use reth_revm::database::{State, SubState};
use reth_rpc_types::{
Index, Log, Transaction, TransactionInfo, TransactionReceipt, TransactionRequest,
TypedTransactionRequest,
state::StateOverride, CallRequest, Index, Log, Transaction, TransactionInfo,
TransactionReceipt, TransactionRequest, TypedTransactionRequest,
};
use reth_transaction_pool::{TransactionOrigin, TransactionPool};
use revm::primitives::{BlockEnv, CfgEnv};
use revm_primitives::utilities::create_address;
use revm::{
db::CacheDB,
primitives::{BlockEnv, CfgEnv},
Inspector,
};
use revm_primitives::{utilities::create_address, Env, ResultAndState};
/// Commonly used transaction related functions for the [EthApi] type in the `eth_` namespace
#[async_trait::async_trait]
@ -67,6 +73,37 @@ pub trait EthTransactions: Send + Sync {
/// Signs transaction with a matching signer, if any and submits the transaction to the pool.
/// Returns the hash of the signed transaction.
async fn send_transaction(&self, request: TransactionRequest) -> EthResult<H256>;
/// Prepares the state and env for the given [CallRequest] at the given [BlockId] and executes
/// the closure.
async fn with_call_at<F, R>(
&self,
request: CallRequest,
at: BlockId,
state_overrides: Option<StateOverride>,
f: F,
) -> EthResult<R>
where
F: for<'r> FnOnce(CacheDB<State<StateProviderBox<'r>>>, Env) -> EthResult<R> + Send;
/// Executes the call request at the given [BlockId].
async fn transact_call_at(
&self,
request: CallRequest,
at: BlockId,
state_overrides: Option<StateOverride>,
) -> EthResult<(ResultAndState, Env)>;
/// Executes the call request at the given [BlockId]
async fn inspect_call_at<I>(
&self,
request: CallRequest,
at: BlockId,
state_overrides: Option<StateOverride>,
inspector: I,
) -> EthResult<(ResultAndState, Env)>
where
I: for<'r> Inspector<CacheDB<State<StateProviderBox<'r>>>> + Send;
}
#[async_trait]
@ -212,6 +249,46 @@ where
Ok(hash)
}
async fn with_call_at<F, R>(
&self,
request: CallRequest,
at: BlockId,
state_overrides: Option<StateOverride>,
f: F,
) -> EthResult<R>
where
F: for<'r> FnOnce(CacheDB<State<StateProviderBox<'r>>>, Env) -> EthResult<R> + Send,
{
let (cfg, block_env, at) = self.evm_env_at(at).await?;
let state = self.state_at(at)?;
let mut db = SubState::new(State::new(state));
let env = prepare_call_env(cfg, block_env, request, &mut db, state_overrides)?;
f(db, env)
}
async fn transact_call_at(
&self,
request: CallRequest,
at: BlockId,
state_overrides: Option<StateOverride>,
) -> EthResult<(ResultAndState, Env)> {
self.with_call_at(request, at, state_overrides, |mut db, env| transact(&mut db, env)).await
}
async fn inspect_call_at<I>(
&self,
request: CallRequest,
at: BlockId,
state_overrides: Option<StateOverride>,
inspector: I,
) -> EthResult<(ResultAndState, Env)>
where
I: for<'r> Inspector<CacheDB<State<StateProviderBox<'r>>>> + Send,
{
self.with_call_at(request, at, state_overrides, |db, env| inspect(db, env, inspector)).await
}
}
// === impl EthApi ===

View File

@ -20,7 +20,7 @@ use reth_rpc_types::{
trace::{filter::TraceFilter, parity::*},
CallRequest, Index,
};
use revm::primitives::{Env, ExecutionResult, ResultAndState};
use revm::primitives::{Env, ResultAndState};
use std::collections::HashSet;
use tokio::sync::{AcquireError, OwnedSemaphorePermit};
@ -92,11 +92,20 @@ where
/// Executes the given call and returns a number of possible traces for it.
pub async fn trace_call(
&self,
_call: CallRequest,
_trace_types: HashSet<TraceType>,
_block_id: Option<BlockId>,
call: CallRequest,
trace_types: HashSet<TraceType>,
block_id: Option<BlockId>,
) -> EthResult<TraceResults> {
todo!()
let _permit = self.acquire_trace_permit().await;
let at = block_id.unwrap_or(BlockId::Number(BlockNumberOrTag::Latest));
let config = tracing_config(&trace_types);
let mut inspector = TracingInspector::new(config);
let (res, _) = self.eth_api.inspect_call_at(call, at, None, &mut inspector).await?;
let trace_res =
inspector.into_parity_builder().into_trace_results(res.result, &trace_types);
Ok(trace_res)
}
/// Traces a call to `eth_sendRawTransaction` without making the call, returning the traces.
@ -119,18 +128,9 @@ where
let config = tracing_config(&trace_types);
self.trace_at(env, config, at, |inspector, res| {
let output = match res.result {
ExecutionResult::Success { output, .. } => output.into_data(),
ExecutionResult::Revert { output, .. } => output,
ExecutionResult::Halt { .. } => Default::default(),
};
let (trace, vm_trace, state_diff) =
inspector.into_parity_builder().into_trace_type_traces(&trace_types);
let res = TraceResults { output: output.into(), trace, vm_trace, state_diff };
Ok(res)
let trace_res =
inspector.into_parity_builder().into_trace_results(res.result, &trace_types);
Ok(trace_res)
})
}