Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions crates/revm/src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,29 @@ impl<DB: Database + DatabaseCommit> EVM<DB> {
}

impl<DB: Database> EVM<DB> {
/// Do checks that could make transaction fail before call/create
pub fn preverify_transaction(&mut self) -> Result<(), EVMError<DB::Error>> {
if let Some(db) = self.db.as_mut() {
let mut noop = NoOpInspector {};
let out = evm_inner::<DB, false>(&mut self.env, db, &mut noop).preverify_transaction();
out
} else {
panic!("Database needs to be set");
}
}

/// Skip preverification steps and execute transaction
/// without writing to DB, return change state.
pub fn transact_preverified(&mut self) -> EVMResult<DB::Error> {
if let Some(db) = self.db.as_mut() {
let mut noop = NoOpInspector {};
let out = evm_inner::<DB, false>(&mut self.env, db, &mut noop).transact_preverified();
out
} else {
panic!("Database needs to be set");
}
}

/// Execute transaction without writing to DB, return change state.
pub fn transact(&mut self) -> EVMResult<DB::Error> {
if let Some(db) = self.db.as_mut() {
Expand All @@ -94,6 +117,37 @@ impl<DB: Database> EVM<DB> {
}

impl<'a, DB: DatabaseRef> EVM<DB> {
/// Do checks that could make transaction fail before call/create
pub fn preverify_transaction_ref(&self) -> Result<(), EVMError<DB::Error>> {
if let Some(db) = self.db.as_ref() {
let mut noop = NoOpInspector {};
let mut db = RefDBWrapper::new(db);
let db = &mut db;
let out =
evm_inner::<RefDBWrapper<DB::Error>, false>(&mut self.env.clone(), db, &mut noop)
.preverify_transaction();
out
} else {
panic!("Database needs to be set");
}
}

/// Skip preverification steps and execute transaction
/// without writing to DB, return change state.
pub fn transact_preverified_ref(&self) -> EVMResult<DB::Error> {
if let Some(db) = self.db.as_ref() {
let mut noop = NoOpInspector {};
let mut db = RefDBWrapper::new(db);
let db = &mut db;
let out =
evm_inner::<RefDBWrapper<DB::Error>, false>(&mut self.env.clone(), db, &mut noop)
.transact_preverified();
out
} else {
panic!("Database needs to be set");
}
}

/// Execute transaction without writing to DB, return change state.
pub fn transact_ref(&self) -> EVMResult<DB::Error> {
if let Some(db) = self.db.as_ref() {
Expand Down
49 changes: 39 additions & 10 deletions crates/revm/src/evm_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ struct CallResult {
}

pub trait Transact<DBError> {
/// Do checks that could make transaction fail before call/create
fn preverify_transaction(&mut self) -> Result<(), EVMError<DBError>>;

/// Skip preverification steps and do transaction
fn transact_preverified(&mut self) -> EVMResult<DBError>;

/// Do transaction.
/// InstructionResult InstructionResult, Output for call or Address if we are creating
/// contract, gas spend, gas refunded, State that needs to be applied.
Expand All @@ -85,10 +91,35 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB,
impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact<DB::Error>
for EVMImpl<'a, GSPEC, DB, INSPECT>
{
fn transact(&mut self) -> EVMResult<DB::Error> {
self.env().validate_block_env::<GSPEC, DB::Error>()?;
self.env().validate_tx::<GSPEC>()?;
fn preverify_transaction(&mut self) -> Result<(), EVMError<DB::Error>> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting - I wonder how much time we spend doing these checks, and whether we could potentially run all these checks in parallel at the beginning of each block in Reth?

I have seen similar separations to be valuable in the context of account abstraction where you'd want to verify e.g. all signatures in parallel (we do that alrd) and then execute.

Copy link
Member

@rakita rakita Aug 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is negligible, check these measurments #499 (comment)
It is 250ms for 100k blocks that took 56s (on sepolia). Although tx num varies it is under 0.5% of overall the time spent.

let env = self.env();

env.validate_block_env::<GSPEC, DB::Error>()?;
env.validate_tx::<GSPEC>()?;

let tx_caller = env.tx.caller;
let tx_data = &env.tx.data;
let tx_is_create = env.tx.transact_to.is_create();

let initial_gas_spend = initial_tx_gas::<GSPEC>(tx_data, tx_is_create, &env.tx.access_list);

// Additonal check to see if limit is big enought to cover initial gas.
if env.tx.gas_limit < initial_gas_spend {
return Err(InvalidTransaction::CallGasCostMoreThanGasLimit.into());
}

// load acc
let journal = &mut self.data.journaled_state;
let (caller_account, _) = journal
.load_account(tx_caller, self.data.db)
.map_err(EVMError::Database)?;

self.data.env.validate_tx_against_state(caller_account)?;

Ok(())
}

fn transact_preverified(&mut self) -> EVMResult<DB::Error> {
let env = &self.data.env;
let tx_caller = env.tx.caller;
let tx_value = env.tx.value;
Expand All @@ -100,11 +131,6 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact<DB::Error>
let initial_gas_spend =
initial_tx_gas::<GSPEC>(&tx_data, tx_is_create, &env.tx.access_list);

// Additional check to see if limit is big enough to cover initial gas.
if env.tx.gas_limit < initial_gas_spend {
return Err(InvalidTransaction::CallGasCostMoreThanGasLimit.into());
}

// load coinbase
// EIP-3651: Warm COINBASE. Starts the `COINBASE` address warm
if GSPEC::enabled(SHANGHAI) {
Expand All @@ -121,8 +147,6 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact<DB::Error>
.load_account(tx_caller, self.data.db)
.map_err(EVMError::Database)?;

self.data.env.validate_tx_against_state(caller_account)?;

// Reduce gas_limit*gas_price amount of caller account.
// unwrap_or can only occur if disable_balance_check is enabled
caller_account.info.balance = caller_account
Expand Down Expand Up @@ -221,6 +245,11 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact<DB::Error>

Ok(ResultAndState { result, state })
}

fn transact(&mut self) -> EVMResult<DB::Error> {
self.preverify_transaction()
.and_then(|_| self.transact_preverified())
}
}

impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, INSPECT> {
Expand Down
6 changes: 2 additions & 4 deletions documentation/src/crates/revm/evm.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ This document provides the documentation for the `EVM` module.

The primary struct in this module is `EVM`. The EVM struct is generic over a type DB. This means that when you use the EVM struct, you can specify the type that DB should represent. It adds flexibility to the struct, allowing it to store different types of databases or data structures in the db field depending on the use case. The `EVM` struct enables `transact` to update the state directly to the database. Additionally, it allows the user to set all environment parameters.


The parameters that can be set are divided between `Config`, `Block`, and `Transaction` (tx). For transacting on the EVM, you can call `transact_commit` that will automatically apply changes to the database.

## Database Abstractions

You can implement the traits Database, DatabaseRef or Database + DatabaseCommit depending on the desired handling of the struct.

- `Database`: Has mutable `self` in its functions. It's useful if you want to modify your cache or update some statistics on `get` calls. This trait enables `transact` and `inspect` functions.
- `DatabaseRef`: Takes a reference on the object, this is useful if you only have a reference on the state and don't want to update anything on it. It enables `transact_ref` and `inspect_ref` functions.
- `Database`: Has mutable `self` in its functions. It's useful if you want to modify your cache or update some statistics on `get` calls. This trait enables `preverify_transaction`, `transact_preverified`, `transact` and `inspect` functions.
- `DatabaseRef`: Takes a reference on the object, this is useful if you only have a reference on the state and don't want to update anything on it. It enables `previerify_transaction`, `transact_preverified_ref`, `transact_ref` and `inspect_ref` functions.
- `Database + DatabaseCommit`: Allows directly committing changes of a transaction. It enables `transact_commit` and `inspect_commit` functions.