mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-04-30 03:01:58 -04:00
481 lines
18 KiB
Rust
481 lines
18 KiB
Rust
//! Loads a pending block from database. Helper trait for `eth_` call and trace RPC methods.
|
|
|
|
use super::{Call, LoadBlock, LoadPendingBlock, LoadState, LoadTransaction};
|
|
use crate::FromEvmError;
|
|
use alloy_consensus::BlockHeader;
|
|
use alloy_primitives::B256;
|
|
use alloy_rpc_types_eth::{BlockId, TransactionInfo};
|
|
use futures::Future;
|
|
use reth_chainspec::ChainSpecProvider;
|
|
use reth_errors::ProviderError;
|
|
use reth_evm::{
|
|
system_calls::SystemCaller, ConfigureEvm, Database, Evm, EvmEnvFor, HaltReasonFor,
|
|
InspectorFor, TxEnvFor,
|
|
};
|
|
use reth_node_api::NodePrimitives;
|
|
use reth_primitives_traits::{BlockBody, RecoveredBlock, SignedTransaction};
|
|
use reth_provider::{BlockReader, ProviderBlock, ProviderHeader, ProviderTx};
|
|
use reth_revm::{database::StateProviderDatabase, db::CacheDB};
|
|
use reth_rpc_eth_types::{
|
|
cache::db::{StateCacheDb, StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper},
|
|
EthApiError,
|
|
};
|
|
use revm::{
|
|
context_interface::result::{ExecutionResult, ResultAndState},
|
|
state::EvmState,
|
|
DatabaseCommit,
|
|
};
|
|
use revm_inspectors::tracing::{TracingInspector, TracingInspectorConfig};
|
|
use std::sync::Arc;
|
|
|
|
/// Executes CPU heavy tasks.
|
|
pub trait Trace:
|
|
LoadState<
|
|
Provider: BlockReader,
|
|
Evm: ConfigureEvm<
|
|
Primitives: NodePrimitives<
|
|
BlockHeader = ProviderHeader<Self::Provider>,
|
|
SignedTx = ProviderTx<Self::Provider>,
|
|
>,
|
|
>,
|
|
Error: FromEvmError<Self::Evm>,
|
|
>
|
|
{
|
|
/// Executes the [`reth_evm::EvmEnv`] against the given [Database] without committing state
|
|
/// changes.
|
|
#[expect(clippy::type_complexity)]
|
|
fn inspect<DB, I>(
|
|
&self,
|
|
db: DB,
|
|
evm_env: EvmEnvFor<Self::Evm>,
|
|
tx_env: TxEnvFor<Self::Evm>,
|
|
inspector: I,
|
|
) -> Result<
|
|
(ResultAndState<HaltReasonFor<Self::Evm>>, (EvmEnvFor<Self::Evm>, TxEnvFor<Self::Evm>)),
|
|
Self::Error,
|
|
>
|
|
where
|
|
DB: Database<Error = ProviderError>,
|
|
I: InspectorFor<Self::Evm, DB>,
|
|
{
|
|
let mut evm = self.evm_config().evm_with_env_and_inspector(db, evm_env.clone(), inspector);
|
|
let res = evm.transact(tx_env.clone()).map_err(Self::Error::from_evm_err)?;
|
|
Ok((res, (evm_env, tx_env)))
|
|
}
|
|
|
|
/// Executes the transaction on top of the given [`BlockId`] with a tracer configured by the
|
|
/// config.
|
|
///
|
|
/// The callback is then called with the [`TracingInspector`] and the [`ResultAndState`] after
|
|
/// the configured [`reth_evm::EvmEnv`] was inspected.
|
|
///
|
|
/// Caution: this is blocking
|
|
fn trace_at<F, R>(
|
|
&self,
|
|
evm_env: EvmEnvFor<Self::Evm>,
|
|
tx_env: TxEnvFor<Self::Evm>,
|
|
config: TracingInspectorConfig,
|
|
at: BlockId,
|
|
f: F,
|
|
) -> Result<R, Self::Error>
|
|
where
|
|
Self: Call,
|
|
F: FnOnce(
|
|
TracingInspector,
|
|
ResultAndState<HaltReasonFor<Self::Evm>>,
|
|
) -> Result<R, Self::Error>,
|
|
{
|
|
self.with_state_at_block(at, |state| {
|
|
let mut db = CacheDB::new(StateProviderDatabase::new(state));
|
|
let mut inspector = TracingInspector::new(config);
|
|
let (res, _) = self.inspect(&mut db, evm_env, tx_env, &mut inspector)?;
|
|
f(inspector, res)
|
|
})
|
|
}
|
|
|
|
/// Same as [`trace_at`](Self::trace_at) but also provides the used database to the callback.
|
|
///
|
|
/// Executes the transaction on top of the given [`BlockId`] with a tracer configured by the
|
|
/// config.
|
|
///
|
|
/// The callback is then called with the [`TracingInspector`] and the [`ResultAndState`] after
|
|
/// the configured [`reth_evm::EvmEnv`] was inspected.
|
|
fn spawn_trace_at_with_state<F, R>(
|
|
&self,
|
|
evm_env: EvmEnvFor<Self::Evm>,
|
|
tx_env: TxEnvFor<Self::Evm>,
|
|
config: TracingInspectorConfig,
|
|
at: BlockId,
|
|
f: F,
|
|
) -> impl Future<Output = Result<R, Self::Error>> + Send
|
|
where
|
|
Self: LoadPendingBlock + Call,
|
|
F: FnOnce(
|
|
TracingInspector,
|
|
ResultAndState<HaltReasonFor<Self::Evm>>,
|
|
StateCacheDb<'_>,
|
|
) -> Result<R, Self::Error>
|
|
+ Send
|
|
+ 'static,
|
|
R: Send + 'static,
|
|
{
|
|
let this = self.clone();
|
|
self.spawn_with_state_at_block(at, move |state| {
|
|
let mut db = CacheDB::new(StateProviderDatabase::new(state));
|
|
let mut inspector = TracingInspector::new(config);
|
|
let (res, _) = this.inspect(&mut db, evm_env, tx_env, &mut inspector)?;
|
|
f(inspector, res, db)
|
|
})
|
|
}
|
|
|
|
/// Retrieves the transaction if it exists and returns its trace.
|
|
///
|
|
/// Before the transaction is traced, all previous transaction in the block are applied to the
|
|
/// state by executing them first.
|
|
/// The callback `f` is invoked with the [`ResultAndState`] after the transaction was executed
|
|
/// and the database that points to the beginning of the transaction.
|
|
///
|
|
/// Note: Implementers should use a threadpool where blocking is allowed, such as
|
|
/// [`BlockingTaskPool`](reth_tasks::pool::BlockingTaskPool).
|
|
fn spawn_trace_transaction_in_block<F, R>(
|
|
&self,
|
|
hash: B256,
|
|
config: TracingInspectorConfig,
|
|
f: F,
|
|
) -> impl Future<Output = Result<Option<R>, Self::Error>> + Send
|
|
where
|
|
Self: LoadPendingBlock + LoadTransaction + Call,
|
|
F: FnOnce(
|
|
TransactionInfo,
|
|
TracingInspector,
|
|
ResultAndState<HaltReasonFor<Self::Evm>>,
|
|
StateCacheDb<'_>,
|
|
) -> Result<R, Self::Error>
|
|
+ Send
|
|
+ 'static,
|
|
R: Send + 'static,
|
|
{
|
|
self.spawn_trace_transaction_in_block_with_inspector(hash, TracingInspector::new(config), f)
|
|
}
|
|
|
|
/// Retrieves the transaction if it exists and returns its trace.
|
|
///
|
|
/// Before the transaction is traced, all previous transaction in the block are applied to the
|
|
/// state by executing them first.
|
|
/// The callback `f` is invoked with the [`ResultAndState`] after the transaction was executed
|
|
/// and the database that points to the beginning of the transaction.
|
|
///
|
|
/// Note: Implementers should use a threadpool where blocking is allowed, such as
|
|
/// [`BlockingTaskPool`](reth_tasks::pool::BlockingTaskPool).
|
|
fn spawn_trace_transaction_in_block_with_inspector<Insp, F, R>(
|
|
&self,
|
|
hash: B256,
|
|
mut inspector: Insp,
|
|
f: F,
|
|
) -> impl Future<Output = Result<Option<R>, Self::Error>> + Send
|
|
where
|
|
Self: LoadPendingBlock + LoadTransaction + Call,
|
|
F: FnOnce(
|
|
TransactionInfo,
|
|
Insp,
|
|
ResultAndState<HaltReasonFor<Self::Evm>>,
|
|
StateCacheDb<'_>,
|
|
) -> Result<R, Self::Error>
|
|
+ Send
|
|
+ 'static,
|
|
Insp:
|
|
for<'a, 'b> InspectorFor<Self::Evm, StateCacheDbRefMutWrapper<'a, 'b>> + Send + 'static,
|
|
R: Send + 'static,
|
|
{
|
|
async move {
|
|
let (transaction, block) = match self.transaction_and_block(hash).await? {
|
|
None => return Ok(None),
|
|
Some(res) => res,
|
|
};
|
|
let (tx, tx_info) = transaction.split();
|
|
|
|
let (evm_env, _) = self.evm_env_at(block.hash().into()).await?;
|
|
|
|
// we need to get the state of the parent block because we're essentially replaying the
|
|
// block the transaction is included in
|
|
let parent_block = block.parent_hash();
|
|
|
|
let this = self.clone();
|
|
self.spawn_with_state_at_block(parent_block.into(), move |state| {
|
|
let mut db = CacheDB::new(StateProviderDatabase::new(state));
|
|
let block_txs = block.transactions_recovered();
|
|
|
|
this.apply_pre_execution_changes(&block, &mut db, &evm_env)?;
|
|
|
|
// replay all transactions prior to the targeted transaction
|
|
this.replay_transactions_until(&mut db, evm_env.clone(), block_txs, *tx.tx_hash())?;
|
|
|
|
let tx_env = this.evm_config().tx_env(tx);
|
|
let (res, _) = this.inspect(
|
|
StateCacheDbRefMutWrapper(&mut db),
|
|
evm_env,
|
|
tx_env,
|
|
&mut inspector,
|
|
)?;
|
|
f(tx_info, inspector, res, db)
|
|
})
|
|
.await
|
|
.map(Some)
|
|
}
|
|
}
|
|
|
|
/// Executes all transactions of a block up to a given index.
|
|
///
|
|
/// If a `highest_index` is given, this will only execute the first `highest_index`
|
|
/// transactions, in other words, it will stop executing transactions after the
|
|
/// `highest_index`th transaction. If `highest_index` is `None`, all transactions
|
|
/// are executed.
|
|
fn trace_block_until<F, R>(
|
|
&self,
|
|
block_id: BlockId,
|
|
block: Option<Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>>,
|
|
highest_index: Option<u64>,
|
|
config: TracingInspectorConfig,
|
|
f: F,
|
|
) -> impl Future<Output = Result<Option<Vec<R>>, Self::Error>> + Send
|
|
where
|
|
Self: LoadBlock,
|
|
F: Fn(
|
|
TransactionInfo,
|
|
TracingInspector,
|
|
ExecutionResult<HaltReasonFor<Self::Evm>>,
|
|
&EvmState,
|
|
&StateCacheDb<'_>,
|
|
) -> Result<R, Self::Error>
|
|
+ Send
|
|
+ 'static,
|
|
R: Send + 'static,
|
|
{
|
|
self.trace_block_until_with_inspector(
|
|
block_id,
|
|
block,
|
|
highest_index,
|
|
move || TracingInspector::new(config),
|
|
f,
|
|
)
|
|
}
|
|
|
|
/// Executes all transactions of a block.
|
|
///
|
|
/// If a `highest_index` is given, this will only execute the first `highest_index`
|
|
/// transactions, in other words, it will stop executing transactions after the
|
|
/// `highest_index`th transaction.
|
|
///
|
|
/// Note: This expect tx index to be 0-indexed, so the first transaction is at index 0.
|
|
///
|
|
/// This accepts a `inspector_setup` closure that returns the inspector to be used for tracing
|
|
/// the transactions.
|
|
fn trace_block_until_with_inspector<Setup, Insp, F, R>(
|
|
&self,
|
|
block_id: BlockId,
|
|
block: Option<Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>>,
|
|
highest_index: Option<u64>,
|
|
mut inspector_setup: Setup,
|
|
f: F,
|
|
) -> impl Future<Output = Result<Option<Vec<R>>, Self::Error>> + Send
|
|
where
|
|
Self: LoadBlock,
|
|
F: Fn(
|
|
TransactionInfo,
|
|
Insp,
|
|
ExecutionResult<HaltReasonFor<Self::Evm>>,
|
|
&EvmState,
|
|
&StateCacheDb<'_>,
|
|
) -> Result<R, Self::Error>
|
|
+ Send
|
|
+ 'static,
|
|
Setup: FnMut() -> Insp + Send + 'static,
|
|
Insp:
|
|
for<'a, 'b> InspectorFor<Self::Evm, StateCacheDbRefMutWrapper<'a, 'b>> + Send + 'static,
|
|
R: Send + 'static,
|
|
{
|
|
async move {
|
|
let block = async {
|
|
if block.is_some() {
|
|
return Ok(block)
|
|
}
|
|
self.recovered_block(block_id).await
|
|
};
|
|
|
|
let ((evm_env, _), block) = futures::try_join!(self.evm_env_at(block_id), block)?;
|
|
|
|
let Some(block) = block else { return Ok(None) };
|
|
|
|
if block.body().transactions().is_empty() {
|
|
// nothing to trace
|
|
return Ok(Some(Vec::new()))
|
|
}
|
|
|
|
// replay all transactions of the block
|
|
self.spawn_tracing(move |this| {
|
|
// we need to get the state of the parent block because we're replaying this block
|
|
// on top of its parent block's state
|
|
let state_at = block.parent_hash();
|
|
let block_hash = block.hash();
|
|
|
|
let block_number = evm_env.block_env.number;
|
|
let base_fee = evm_env.block_env.basefee;
|
|
|
|
// now get the state
|
|
let state = this.state_at_block_id(state_at.into())?;
|
|
let mut db =
|
|
CacheDB::new(StateProviderDatabase::new(StateProviderTraitObjWrapper(&state)));
|
|
|
|
this.apply_pre_execution_changes(&block, &mut db, &evm_env)?;
|
|
|
|
// prepare transactions, we do everything upfront to reduce time spent with open
|
|
// state
|
|
let max_transactions =
|
|
highest_index.map_or(block.body().transaction_count(), |highest| {
|
|
// we need + 1 because the index is 0-based
|
|
highest as usize + 1
|
|
});
|
|
let mut results = Vec::with_capacity(max_transactions);
|
|
|
|
let mut transactions = block
|
|
.transactions_recovered()
|
|
.take(max_transactions)
|
|
.enumerate()
|
|
.map(|(idx, tx)| {
|
|
let tx_info = TransactionInfo {
|
|
hash: Some(*tx.tx_hash()),
|
|
index: Some(idx as u64),
|
|
block_hash: Some(block_hash),
|
|
block_number: Some(block_number),
|
|
base_fee: Some(base_fee),
|
|
};
|
|
let tx_env = this.evm_config().tx_env(tx);
|
|
(tx_info, tx_env)
|
|
})
|
|
.peekable();
|
|
|
|
while let Some((tx_info, tx)) = transactions.next() {
|
|
let mut inspector = inspector_setup();
|
|
let (res, _) = this.inspect(
|
|
StateCacheDbRefMutWrapper(&mut db),
|
|
evm_env.clone(),
|
|
tx,
|
|
&mut inspector,
|
|
)?;
|
|
let ResultAndState { result, state } = res;
|
|
results.push(f(tx_info, inspector, result, &state, &db)?);
|
|
|
|
// need to apply the state changes of this transaction before executing the
|
|
// next transaction, but only if there's a next transaction
|
|
if transactions.peek().is_some() {
|
|
// commit the state changes to the DB
|
|
db.commit(state)
|
|
}
|
|
}
|
|
|
|
Ok(Some(results))
|
|
})
|
|
.await
|
|
}
|
|
}
|
|
|
|
/// Executes all transactions of a block and returns a list of callback results invoked for each
|
|
/// transaction in the block.
|
|
///
|
|
/// This
|
|
/// 1. fetches all transactions of the block
|
|
/// 2. configures the EVM evn
|
|
/// 3. loops over all transactions and executes them
|
|
/// 4. calls the callback with the transaction info, the execution result, the changed state
|
|
/// _after_ the transaction [`StateProviderDatabase`] and the database that points to the
|
|
/// state right _before_ the transaction.
|
|
fn trace_block_with<F, R>(
|
|
&self,
|
|
block_id: BlockId,
|
|
block: Option<Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>>,
|
|
config: TracingInspectorConfig,
|
|
f: F,
|
|
) -> impl Future<Output = Result<Option<Vec<R>>, Self::Error>> + Send
|
|
where
|
|
Self: LoadBlock,
|
|
// This is the callback that's invoked for each transaction with the inspector, the result,
|
|
// state and db
|
|
F: Fn(
|
|
TransactionInfo,
|
|
TracingInspector,
|
|
ExecutionResult<HaltReasonFor<Self::Evm>>,
|
|
&EvmState,
|
|
&StateCacheDb<'_>,
|
|
) -> Result<R, Self::Error>
|
|
+ Send
|
|
+ 'static,
|
|
R: Send + 'static,
|
|
{
|
|
self.trace_block_until(block_id, block, None, config, f)
|
|
}
|
|
|
|
/// Executes all transactions of a block and returns a list of callback results invoked for each
|
|
/// transaction in the block.
|
|
///
|
|
/// This
|
|
/// 1. fetches all transactions of the block
|
|
/// 2. configures the EVM evn
|
|
/// 3. loops over all transactions and executes them
|
|
/// 4. calls the callback with the transaction info, the execution result, the changed state
|
|
/// _after_ the transaction [`EvmState`] and the database that points to the state right
|
|
/// _before_ the transaction, in other words the state the transaction was executed on:
|
|
/// `changed_state = tx(cached_state)`
|
|
///
|
|
/// This accepts a `inspector_setup` closure that returns the inspector to be used for tracing
|
|
/// a transaction. This is invoked for each transaction.
|
|
fn trace_block_inspector<Setup, Insp, F, R>(
|
|
&self,
|
|
block_id: BlockId,
|
|
block: Option<Arc<RecoveredBlock<ProviderBlock<Self::Provider>>>>,
|
|
insp_setup: Setup,
|
|
f: F,
|
|
) -> impl Future<Output = Result<Option<Vec<R>>, Self::Error>> + Send
|
|
where
|
|
Self: LoadBlock,
|
|
// This is the callback that's invoked for each transaction with the inspector, the result,
|
|
// state and db
|
|
F: Fn(
|
|
TransactionInfo,
|
|
Insp,
|
|
ExecutionResult<HaltReasonFor<Self::Evm>>,
|
|
&EvmState,
|
|
&StateCacheDb<'_>,
|
|
) -> Result<R, Self::Error>
|
|
+ Send
|
|
+ 'static,
|
|
Setup: FnMut() -> Insp + Send + 'static,
|
|
Insp:
|
|
for<'a, 'b> InspectorFor<Self::Evm, StateCacheDbRefMutWrapper<'a, 'b>> + Send + 'static,
|
|
R: Send + 'static,
|
|
{
|
|
self.trace_block_until_with_inspector(block_id, block, None, insp_setup, f)
|
|
}
|
|
|
|
/// Applies chain-specific state transitions required before executing a block.
|
|
///
|
|
/// Note: This should only be called when tracing an entire block vs individual transactions.
|
|
/// When tracing transaction on top of an already committed block state, those transitions are
|
|
/// already applied.
|
|
fn apply_pre_execution_changes<DB: Send + Database + DatabaseCommit>(
|
|
&self,
|
|
block: &RecoveredBlock<ProviderBlock<Self::Provider>>,
|
|
db: &mut DB,
|
|
evm_env: &EvmEnvFor<Self::Evm>,
|
|
) -> Result<(), Self::Error> {
|
|
let mut system_caller = SystemCaller::new(self.provider().chain_spec());
|
|
|
|
// apply relevant system calls
|
|
let mut evm = self.evm_config().evm_with_env(db, evm_env.clone());
|
|
system_caller.apply_pre_execution_changes(block.header(), &mut evm).map_err(|err| {
|
|
EthApiError::EvmCustom(format!("failed to apply 4788 system call {err}"))
|
|
})?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|