From 56394eec2ce44440d99e9f532b77e63ea4920cdc Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Mon, 27 Feb 2023 08:21:34 -0700 Subject: [PATCH] feat: Hook on Execution (#1567) --- Cargo.lock | 1 + crates/executor/Cargo.toml | 1 + crates/executor/src/executor.rs | 98 +++++---- crates/revm/revm-inspectors/src/lib.rs | 5 + crates/revm/revm-inspectors/src/stack.rs | 240 +++++++++++++++++++++++ 5 files changed, 312 insertions(+), 33 deletions(-) create mode 100644 crates/revm/revm-inspectors/src/stack.rs diff --git a/Cargo.lock b/Cargo.lock index 196a865ed..6cabf34e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4598,6 +4598,7 @@ dependencies = [ "reth-primitives", "reth-provider", "reth-revm", + "reth-revm-inspectors", "reth-rlp", "revm", "rlp", diff --git a/crates/executor/Cargo.toml b/crates/executor/Cargo.toml index 18ff91ecf..41e42af20 100644 --- a/crates/executor/Cargo.toml +++ b/crates/executor/Cargo.toml @@ -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" } diff --git a/crates/executor/src/executor.rs b/crates/executor/src/executor.rs index 24125d555..5a7cd6977 100644 --- a/crates/executor/src/executor.rs +++ b/crates/executor/src/executor.rs @@ -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>, - /// 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) -> Self { + /// Creates a new executor from the given chain spec and database. + pub fn new(chain_spec: &'a ChainSpec, db: &'a mut SubState) -> 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 { @@ -327,18 +334,44 @@ where } } } -} -impl<'a, DB> BlockExecutor 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 { + // 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>, - ) -> Result { + ) -> Result<(Vec, 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 for Executor<'a, DB> +where + DB: StateProvider, +{ + fn execute( + &mut self, + block: &Block, + total_difficulty: U256, + senders: Option>, + ) -> Result { + 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( chain_spec: &ChainSpec, db: &mut SubState, ) -> Result { - 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) } diff --git a/crates/revm/revm-inspectors/src/lib.rs b/crates/revm/revm-inspectors/src/lib.rs index cec3a9d09..b4d79c406 100644 --- a/crates/revm/revm-inspectors/src/lib.rs +++ b/crates/revm/revm-inspectors/src/lib.rs @@ -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; diff --git a/crates/revm/revm-inspectors/src/stack.rs b/crates/revm/revm-inspectors/src/stack.rs new file mode 100644 index 000000000..12e3df405 --- /dev/null +++ b/crates/revm/revm-inspectors/src/stack.rs @@ -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, + /// 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::() == 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 Inspector 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
, 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
, + remaining_gas: Gas, + out: Bytes, + ) -> (InstructionResult, Option
, 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::::selfdestruct(inspector); + }); + } +}