mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-01-29 09:08:05 -05:00
feat: Hook on Execution (#1567)
This commit is contained in:
committed by
GitHub
parent
7275f8d922
commit
56394eec2c
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -4598,6 +4598,7 @@ dependencies = [
|
||||
"reth-primitives",
|
||||
"reth-provider",
|
||||
"reth-revm",
|
||||
"reth-revm-inspectors",
|
||||
"reth-rlp",
|
||||
"revm",
|
||||
"rlp",
|
||||
|
||||
@@ -11,6 +11,7 @@ readme = "README.md"
|
||||
reth-primitives = { path = "../primitives" }
|
||||
reth-interfaces = { path = "../interfaces" }
|
||||
reth-revm = { path = "../revm" }
|
||||
reth-revm-inspectors = { path = "../revm/revm-inspectors" }
|
||||
reth-rlp = { path = "../rlp" }
|
||||
reth-db = { path = "../storage/db" }
|
||||
reth-provider = { path = "../storage/provider" }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::execution_result::{
|
||||
AccountChangeSet, AccountInfoChangeSet, ExecutionResult, TransactionChangeSet,
|
||||
};
|
||||
|
||||
use hashbrown::hash_map::Entry;
|
||||
use reth_interfaces::executor::{BlockExecutor, Error};
|
||||
use reth_primitives::{
|
||||
@@ -14,6 +15,7 @@ use reth_revm::{
|
||||
env::{fill_cfg_and_block_env, fill_tx_env},
|
||||
into_reth_log, to_reth_acc,
|
||||
};
|
||||
use reth_revm_inspectors::stack::{InspectorStack, InspectorStackConfig};
|
||||
use revm::{
|
||||
db::AccountState,
|
||||
primitives::{Account as RevmAccount, AccountInfo, Bytecode, ResultAndState},
|
||||
@@ -28,19 +30,24 @@ where
|
||||
{
|
||||
chain_spec: &'a ChainSpec,
|
||||
evm: EVM<&'a mut SubState<DB>>,
|
||||
/// Enable revm inspector printer.
|
||||
/// In execution this will print opcode level traces directly to console.
|
||||
pub use_printer_tracer: bool,
|
||||
stack: InspectorStack,
|
||||
}
|
||||
|
||||
impl<'a, DB> Executor<'a, DB>
|
||||
where
|
||||
DB: StateProvider,
|
||||
{
|
||||
fn new(chain_spec: &'a ChainSpec, db: &'a mut SubState<DB>) -> Self {
|
||||
/// Creates a new executor from the given chain spec and database.
|
||||
pub fn new(chain_spec: &'a ChainSpec, db: &'a mut SubState<DB>) -> Self {
|
||||
let mut evm = EVM::new();
|
||||
evm.database(db);
|
||||
Executor { chain_spec, evm, use_printer_tracer: false }
|
||||
Executor { chain_spec, evm, stack: InspectorStack::new(InspectorStackConfig::default()) }
|
||||
}
|
||||
|
||||
/// Configures the executor with the given inspectors.
|
||||
pub fn with_stack(mut self, stack: InspectorStack) -> Self {
|
||||
self.stack = stack;
|
||||
self
|
||||
}
|
||||
|
||||
fn db(&mut self) -> &mut SubState<DB> {
|
||||
@@ -327,18 +334,44 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, DB> BlockExecutor<ExecutionResult> for Executor<'a, DB>
|
||||
where
|
||||
DB: StateProvider,
|
||||
{
|
||||
fn execute(
|
||||
/// Runs a single transaction in the configured environment and proceeds
|
||||
/// to return the result and state diff (without applying it).
|
||||
///
|
||||
/// Assumes the rest of the block environment has been filled via `init_block_env`.
|
||||
pub fn transact(
|
||||
&mut self,
|
||||
transaction: &TransactionSigned,
|
||||
sender: Address,
|
||||
) -> Result<ResultAndState, Error> {
|
||||
// Fill revm structure.
|
||||
fill_tx_env(&mut self.evm.env.tx, transaction, sender);
|
||||
|
||||
let out = if self.stack.should_inspect(&self.evm.env, transaction.hash()) {
|
||||
// execution with inspector.
|
||||
let output = self.evm.inspect(&mut self.stack);
|
||||
tracing::trace!(
|
||||
target: "evm",
|
||||
hash = ?transaction.hash(), ?output, ?transaction, env = ?self.evm.env,
|
||||
"Executed transaction"
|
||||
);
|
||||
output
|
||||
} else {
|
||||
// main execution.
|
||||
self.evm.transact()
|
||||
};
|
||||
out.map_err(|e| Error::EVM(format!("{e:?}")))
|
||||
}
|
||||
|
||||
/// Runs the provided transactions and commits their state. Will proceed
|
||||
/// to return the total gas used by this batch of transaction as well as the
|
||||
/// changesets generated by each tx.
|
||||
pub fn execute_transactions(
|
||||
&mut self,
|
||||
block: &Block,
|
||||
total_difficulty: U256,
|
||||
senders: Option<Vec<Address>>,
|
||||
) -> Result<ExecutionResult, Error> {
|
||||
) -> Result<(Vec<TransactionChangeSet>, u64), Error> {
|
||||
let senders = self.recover_senders(&block.body, senders)?;
|
||||
|
||||
self.init_env(&block.header, total_difficulty);
|
||||
@@ -357,27 +390,8 @@ where
|
||||
block_available_gas,
|
||||
})
|
||||
}
|
||||
|
||||
// Fill revm structure.
|
||||
fill_tx_env(&mut self.evm.env.tx, transaction, sender);
|
||||
|
||||
// Execute transaction.
|
||||
let out = if self.use_printer_tracer {
|
||||
// execution with inspector.
|
||||
let output = self.evm.inspect(revm::inspectors::CustomPrintTracer::default());
|
||||
tracing::trace!(
|
||||
target: "evm",
|
||||
hash = ?transaction.hash(), ?output, ?transaction, env = ?self.evm.env,
|
||||
"Executed transaction"
|
||||
);
|
||||
output
|
||||
} else {
|
||||
// main execution.
|
||||
self.evm.transact()
|
||||
};
|
||||
|
||||
// cast the error and extract returnables.
|
||||
let ResultAndState { result, state } = out.map_err(|e| Error::EVM(format!("{e:?}")))?;
|
||||
let ResultAndState { result, state } = self.transact(transaction, sender)?;
|
||||
|
||||
// commit changes
|
||||
let (changeset, new_bytecodes) = self.commit_changes(state);
|
||||
@@ -404,6 +418,23 @@ where
|
||||
});
|
||||
}
|
||||
|
||||
Ok((tx_changesets, cumulative_gas_used))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, DB> BlockExecutor<ExecutionResult> for Executor<'a, DB>
|
||||
where
|
||||
DB: StateProvider,
|
||||
{
|
||||
fn execute(
|
||||
&mut self,
|
||||
block: &Block,
|
||||
total_difficulty: U256,
|
||||
senders: Option<Vec<Address>>,
|
||||
) -> Result<ExecutionResult, Error> {
|
||||
let (tx_changesets, cumulative_gas_used) =
|
||||
self.execute_transactions(block, total_difficulty, senders)?;
|
||||
|
||||
// Check if gas used matches the value set in header.
|
||||
if block.gas_used != cumulative_gas_used {
|
||||
return Err(Error::BlockGasUsed { got: cumulative_gas_used, expected: block.gas_used })
|
||||
@@ -485,7 +516,8 @@ pub fn execute<DB: StateProvider>(
|
||||
chain_spec: &ChainSpec,
|
||||
db: &mut SubState<DB>,
|
||||
) -> Result<ExecutionResult, Error> {
|
||||
let mut executor = Executor::new(chain_spec, db);
|
||||
let mut executor = Executor::new(chain_spec, db)
|
||||
.with_stack(InspectorStack::new(InspectorStackConfig::default()));
|
||||
executor.execute(block, total_difficulty, senders)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,3 +9,8 @@
|
||||
|
||||
/// An inspector implementation for an EIP2930 Accesslist
|
||||
pub mod access_list;
|
||||
|
||||
/// An inspector stack abstracting the implementation details of
|
||||
/// each inspector and allowing to hook on block/transaciton execution,
|
||||
/// used in the main RETH executor.
|
||||
pub mod stack;
|
||||
|
||||
240
crates/revm/revm-inspectors/src/stack.rs
Normal file
240
crates/revm/revm-inspectors/src/stack.rs
Normal file
@@ -0,0 +1,240 @@
|
||||
use reth_primitives::{bytes::Bytes, Address, TxHash, H256};
|
||||
use revm::{
|
||||
inspectors::CustomPrintTracer,
|
||||
interpreter::{CallInputs, CreateInputs, Gas, InstructionResult, Interpreter},
|
||||
primitives::Env,
|
||||
Database, EVMData, Inspector,
|
||||
};
|
||||
|
||||
/// One can hook on inspector execution in 3 ways:
|
||||
/// - Block: Hook on block execution
|
||||
/// - BlockWithIndex: Hook on block execution transaction index
|
||||
/// - Transaction: Hook on a specific transaction hash
|
||||
#[derive(Default)]
|
||||
pub enum Hook {
|
||||
#[default]
|
||||
/// No hook.
|
||||
None,
|
||||
/// Hook on a specific block.
|
||||
Block(u64),
|
||||
/// Hook on a specific transaction hash.
|
||||
Transaction(TxHash),
|
||||
/// Hooks on every transaction in a block.
|
||||
All,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
/// An inspector that calls multiple inspectors in sequence.
|
||||
///
|
||||
/// If a call to an inspector returns a value other than [InstructionResult::Continue] (or
|
||||
/// equivalent) the remaining inspectors are not called.
|
||||
pub struct InspectorStack {
|
||||
/// An inspector that prints the opcode traces to the console.
|
||||
pub custom_print_tracer: Option<CustomPrintTracer>,
|
||||
/// The provided hook
|
||||
pub hook: Hook,
|
||||
}
|
||||
|
||||
impl InspectorStack {
|
||||
/// Create a new inspector stack.
|
||||
pub fn new(config: InspectorStackConfig) -> Self {
|
||||
let mut stack = InspectorStack { hook: config.hook, ..Default::default() };
|
||||
|
||||
if config.use_printer_tracer {
|
||||
stack.custom_print_tracer = Some(CustomPrintTracer::default());
|
||||
}
|
||||
|
||||
stack
|
||||
}
|
||||
|
||||
/// Check if the inspector should be used.
|
||||
pub fn should_inspect(&self, env: &Env, tx_hash: TxHash) -> bool {
|
||||
match self.hook {
|
||||
Hook::None => false,
|
||||
Hook::Block(block) => env.block.number.to::<u64>() == block,
|
||||
Hook::Transaction(hash) => hash == tx_hash,
|
||||
Hook::All => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
/// Configuration for the inspectors.
|
||||
pub struct InspectorStackConfig {
|
||||
/// Enable revm inspector printer.
|
||||
/// In execution this will print opcode level traces directly to console.
|
||||
pub use_printer_tracer: bool,
|
||||
|
||||
/// Hook on a specific block or transaction.
|
||||
pub hook: Hook,
|
||||
}
|
||||
|
||||
/// Helper macro to call the same method on multiple inspectors without resorting to dynamic
|
||||
/// dispatch
|
||||
#[macro_export]
|
||||
macro_rules! call_inspectors {
|
||||
($id:ident, [ $($inspector:expr),+ ], $call:block) => {
|
||||
$({
|
||||
if let Some($id) = $inspector {
|
||||
$call;
|
||||
}
|
||||
})+
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB> Inspector<DB> for InspectorStack
|
||||
where
|
||||
DB: Database,
|
||||
{
|
||||
fn initialize_interp(
|
||||
&mut self,
|
||||
interpreter: &mut Interpreter,
|
||||
data: &mut EVMData<'_, DB>,
|
||||
is_static: bool,
|
||||
) -> InstructionResult {
|
||||
call_inspectors!(inspector, [&mut self.custom_print_tracer], {
|
||||
let status = inspector.initialize_interp(interpreter, data, is_static);
|
||||
|
||||
// Allow inspectors to exit early
|
||||
if status != InstructionResult::Continue {
|
||||
return status
|
||||
}
|
||||
});
|
||||
|
||||
InstructionResult::Continue
|
||||
}
|
||||
|
||||
fn step(
|
||||
&mut self,
|
||||
interpreter: &mut Interpreter,
|
||||
data: &mut EVMData<'_, DB>,
|
||||
is_static: bool,
|
||||
) -> InstructionResult {
|
||||
call_inspectors!(inspector, [&mut self.custom_print_tracer], {
|
||||
let status = inspector.step(interpreter, data, is_static);
|
||||
|
||||
// Allow inspectors to exit early
|
||||
if status != InstructionResult::Continue {
|
||||
return status
|
||||
}
|
||||
});
|
||||
|
||||
InstructionResult::Continue
|
||||
}
|
||||
|
||||
fn log(
|
||||
&mut self,
|
||||
evm_data: &mut EVMData<'_, DB>,
|
||||
address: &Address,
|
||||
topics: &[H256],
|
||||
data: &Bytes,
|
||||
) {
|
||||
call_inspectors!(inspector, [&mut self.custom_print_tracer], {
|
||||
inspector.log(evm_data, address, topics, data);
|
||||
});
|
||||
}
|
||||
|
||||
fn step_end(
|
||||
&mut self,
|
||||
interpreter: &mut Interpreter,
|
||||
data: &mut EVMData<'_, DB>,
|
||||
is_static: bool,
|
||||
eval: InstructionResult,
|
||||
) -> InstructionResult {
|
||||
call_inspectors!(inspector, [&mut self.custom_print_tracer], {
|
||||
let status = inspector.step_end(interpreter, data, is_static, eval);
|
||||
|
||||
// Allow inspectors to exit early
|
||||
if status != InstructionResult::Continue {
|
||||
return status
|
||||
}
|
||||
});
|
||||
|
||||
InstructionResult::Continue
|
||||
}
|
||||
|
||||
fn call(
|
||||
&mut self,
|
||||
data: &mut EVMData<'_, DB>,
|
||||
inputs: &mut CallInputs,
|
||||
is_static: bool,
|
||||
) -> (InstructionResult, Gas, Bytes) {
|
||||
call_inspectors!(inspector, [&mut self.custom_print_tracer], {
|
||||
let (status, gas, retdata) = inspector.call(data, inputs, is_static);
|
||||
|
||||
// Allow inspectors to exit early
|
||||
if status != InstructionResult::Continue {
|
||||
return (status, gas, retdata)
|
||||
}
|
||||
});
|
||||
|
||||
(InstructionResult::Continue, Gas::new(inputs.gas_limit), Bytes::new())
|
||||
}
|
||||
|
||||
fn call_end(
|
||||
&mut self,
|
||||
data: &mut EVMData<'_, DB>,
|
||||
inputs: &CallInputs,
|
||||
remaining_gas: Gas,
|
||||
ret: InstructionResult,
|
||||
out: Bytes,
|
||||
is_static: bool,
|
||||
) -> (InstructionResult, Gas, Bytes) {
|
||||
call_inspectors!(inspector, [&mut self.custom_print_tracer], {
|
||||
let (new_ret, new_gas, new_out) =
|
||||
inspector.call_end(data, inputs, remaining_gas, ret, out.clone(), is_static);
|
||||
|
||||
// If the inspector returns a different ret or a revert with a non-empty message,
|
||||
// we assume it wants to tell us something
|
||||
if new_ret != ret || (new_ret == InstructionResult::Revert && new_out != out) {
|
||||
return (new_ret, new_gas, new_out)
|
||||
}
|
||||
});
|
||||
|
||||
(ret, remaining_gas, out)
|
||||
}
|
||||
|
||||
fn create(
|
||||
&mut self,
|
||||
data: &mut EVMData<'_, DB>,
|
||||
inputs: &mut CreateInputs,
|
||||
) -> (InstructionResult, Option<Address>, Gas, Bytes) {
|
||||
call_inspectors!(inspector, [&mut self.custom_print_tracer], {
|
||||
let (status, addr, gas, retdata) = inspector.create(data, inputs);
|
||||
|
||||
// Allow inspectors to exit early
|
||||
if status != InstructionResult::Continue {
|
||||
return (status, addr, gas, retdata)
|
||||
}
|
||||
});
|
||||
|
||||
(InstructionResult::Continue, None, Gas::new(inputs.gas_limit), Bytes::new())
|
||||
}
|
||||
|
||||
fn create_end(
|
||||
&mut self,
|
||||
data: &mut EVMData<'_, DB>,
|
||||
inputs: &CreateInputs,
|
||||
ret: InstructionResult,
|
||||
address: Option<Address>,
|
||||
remaining_gas: Gas,
|
||||
out: Bytes,
|
||||
) -> (InstructionResult, Option<Address>, Gas, Bytes) {
|
||||
call_inspectors!(inspector, [&mut self.custom_print_tracer], {
|
||||
let (new_ret, new_address, new_gas, new_retdata) =
|
||||
inspector.create_end(data, inputs, ret, address, remaining_gas, out.clone());
|
||||
|
||||
if new_ret != ret {
|
||||
return (new_ret, new_address, new_gas, new_retdata)
|
||||
}
|
||||
});
|
||||
|
||||
(ret, address, remaining_gas, out)
|
||||
}
|
||||
|
||||
fn selfdestruct(&mut self) {
|
||||
call_inspectors!(inspector, [&mut self.custom_print_tracer], {
|
||||
Inspector::<DB>::selfdestruct(inspector);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user