Skip to content

Commit

Permalink
Add error handling during EVM transaction execution (#717)
Browse files Browse the repository at this point in the history
* handle EVM errors during transaction execution

* add new generic execute_transaction function

* add new NotEnoughGas error

* add comment

* fix clippy

* Update chain-evm/src/machine.rs

Co-authored-by: Enzo Cioppettini <48031343+enzoc4@users.noreply.github.com>

* fix comments

* update evm error messages

* Update chain-evm/src/machine.rs

Co-authored-by: Yevhenii Babichenko <eugene.babichenko@iohk.io>

* fix suggestions

Co-authored-by: Enzo Cioppettini <48031343+enzoc4@users.noreply.github.com>
Co-authored-by: Yevhenii Babichenko <eugene.babichenko@iohk.io>
  • Loading branch information
3 people committed Jan 12, 2022
1 parent 3f4aba8 commit 9f60ec1
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 98 deletions.
1 change: 1 addition & 0 deletions chain-evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ logos = { version = "0.12", default-features = false, features = ["export_derive
ethabi = { version = "16.0", default-features = false }
hex = { version = "0.4", default-features = false, features = ["alloc"] }
byte-slice-cast = { version = "1.0", default-features = false }
thiserror = "1.0"

[dev-dependencies]
serde = { version = "1", features = ["derive"] }
Expand Down
185 changes: 107 additions & 78 deletions chain-evm/src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ use std::rc::Rc;
use evm::{
backend::{Apply, ApplyBackend, Backend, Basic},
executor::stack::{MemoryStackState, StackExecutor, StackSubstateMetadata},
Context, Runtime,
Context, ExitError, ExitFatal, ExitRevert, Runtime,
};
use primitive_types::{H160, H256, U256};

use thiserror::Error;

use crate::{
precompiles::Precompiles,
state::{AccountTrie, ByteCode, Key, LogsState},
Expand Down Expand Up @@ -74,6 +76,16 @@ pub type Value = U256;
/// The context of the EVM runtime
pub type RuntimeContext = Context;

#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum Error {
#[error("transaction error: machine returned a normal EVM error")]
TransactionError(ExitError),
#[error("transaction fatal error: machine encountered an error that is not supposed to be normal EVM errors, such as requiring too much memory to execute")]
TransactionFatalError(ExitFatal),
#[error("transaction has been reverted: machine encountered an explict revert")]
TransactionRevertError(ExitRevert),
}

/// Top-level abstraction for the EVM with the
/// necessary types used to get the runtime going.
pub struct VirtualMachine<'runtime> {
Expand All @@ -98,6 +110,56 @@ fn precompiles(fork: HardFork) -> Precompiles {
}
}

impl<'runtime> VirtualMachine<'runtime> {
fn execute_transaction<F, T>(
&mut self,
gas_limit: u64,
delete_empty: bool,
f: F,
) -> Result<(&AccountTrie, &LogsState, T), Error>
where
F: Fn(
&mut StackExecutor<
'runtime,
'_,
MemoryStackState<'_, 'runtime, VirtualMachine>,
Precompiles,
>,
u64,
) -> (ExitReason, T),
{
let executable = |gas_limit| {
let metadata = StackSubstateMetadata::new(gas_limit, self.config);
let memory_stack_state = MemoryStackState::new(metadata, self);
let mut executor = StackExecutor::new_with_precompiles(
memory_stack_state,
self.config,
&self.precompiles,
);
(f(&mut executor, gas_limit), executor)
};

let ((exit_reason, val), executor) = executable(gas_limit);
match exit_reason {
ExitReason::Succeed(_) => {
// apply and return state
// apply changes to the state, this consumes the executor
let state = executor.into_state();
// Next, we consume the stack state and extract the values and logs
// used to modify the accounts trie in the VirtualMachine.
let (values, logs) = state.deconstruct();

self.apply(values, logs, delete_empty);
// exit_reason
Ok((&self.state, &self.logs, val))
}
ExitReason::Revert(err) => Err(Error::TransactionRevertError(err)),
ExitReason::Error(err) => Err(Error::TransactionError(err)),
ExitReason::Fatal(err) => Err(Error::TransactionFatalError(err)),
}
}
}

impl<'runtime> VirtualMachine<'runtime> {
/// Creates a new `VirtualMachine` given configuration parameters.
pub fn new(config: &'runtime Config, environment: &'runtime Environment) -> Self {
Expand Down Expand Up @@ -138,34 +200,21 @@ impl<'runtime> VirtualMachine<'runtime> {
gas_limit: u64,
access_list: Vec<(Address, Vec<Key>)>,
delete_empty: bool,
) -> Option<(&AccountTrie, &LogsState)> {
{
let metadata = StackSubstateMetadata::new(gas_limit, self.config);
let memory_stack_state = MemoryStackState::new(metadata, self);
let mut executor = StackExecutor::new_with_precompiles(
memory_stack_state,
self.config,
&self.precompiles,
);

let exit_reason =
executor.transact_create(caller, value, init_code, gas_limit, access_list);
match exit_reason {
ExitReason::Succeed(_succeded) => {
// apply and return state
// apply changes to the state, this consumes the executor
let state = executor.into_state();
// Next, we consume the stack state and extract the values and logs
// used to modify the accounts trie in the VirtualMachine.
let (values, logs) = state.deconstruct();

self.apply(values, logs, delete_empty);
//_exit_reason
Some((&self.state, &self.logs))
}
_ => None,
}
}
) -> Result<(&AccountTrie, &LogsState), Error> {
let (account_trie, logs_state, _) =
self.execute_transaction(gas_limit, delete_empty, |executor, gas_limit| {
(
executor.transact_create(
caller,
value,
init_code.to_vec(),
gas_limit,
access_list.clone(),
),
(),
)
})?;
Ok((account_trie, logs_state))
}

/// Execute a CREATE2 transaction
Expand All @@ -179,33 +228,22 @@ impl<'runtime> VirtualMachine<'runtime> {
gas_limit: u64,
access_list: Vec<(Address, Vec<Key>)>,
delete_empty: bool,
) -> Option<(&AccountTrie, &LogsState)> {
{
let metadata = StackSubstateMetadata::new(gas_limit, self.config);
let memory_stack_state = MemoryStackState::new(metadata, self);
let mut executor = StackExecutor::new_with_precompiles(
memory_stack_state,
self.config,
&self.precompiles,
);
let exit_reason =
executor.transact_create2(caller, value, init_code, salt, gas_limit, access_list);
match exit_reason {
ExitReason::Succeed(_succeded) => {
// apply and return state
// apply changes to the state, this consumes the executor
let state = executor.into_state();
// Next, we consume the stack state and extract the values and logs
// used to modify the accounts trie in the VirtualMachine.
let (values, logs) = state.deconstruct();

self.apply(values, logs, delete_empty);
//_exit_reason
Some((&self.state, &self.logs))
}
_ => None,
}
}
) -> Result<(&AccountTrie, &LogsState), Error> {
let (account_trie, logs_state, _) =
self.execute_transaction(gas_limit, delete_empty, |executor, gas_limit| {
(
executor.transact_create2(
caller,
value,
init_code.to_vec(),
salt,
gas_limit,
access_list.clone(),
),
(),
)
})?;
Ok((account_trie, logs_state))
}

/// Execute a CALL transaction
Expand All @@ -219,28 +257,19 @@ impl<'runtime> VirtualMachine<'runtime> {
gas_limit: u64,
access_list: Vec<(Address, Vec<Key>)>,
delete_empty: bool,
) -> Option<(&AccountTrie, &LogsState, ByteCode)> {
let metadata = StackSubstateMetadata::new(gas_limit, self.config);
let memory_stack_state = MemoryStackState::new(metadata, self);
let mut executor =
StackExecutor::new_with_precompiles(memory_stack_state, self.config, &self.precompiles);
let (exit_reason, byte_output) =
executor.transact_call(caller, address, value, data, gas_limit, access_list);
match exit_reason {
ExitReason::Succeed(_succeded) => {
// apply and return state
// apply changes to the state, this consumes the executor
let state = executor.into_state();
// Next, we consume the stack state and extract the values and logs
// used to modify the accounts trie in the VirtualMachine.
let (values, logs) = state.deconstruct();

self.apply(values, logs, delete_empty);
//_exit_reason
Some((&self.state, &self.logs, byte_output))
}
_ => None,
}
) -> Result<(&AccountTrie, &LogsState, ByteCode), Error> {
let (account_trie, logs_state, byte_output) =
self.execute_transaction(gas_limit, delete_empty, |executor, gas_limit| {
executor.transact_call(
caller,
address,
value,
data.to_vec(),
gas_limit,
access_list.clone(),
)
})?;
Ok((account_trie, logs_state, byte_output))
}
}

Expand Down
35 changes: 15 additions & 20 deletions chain-impl-mockchain/src/ledger/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,11 @@ impl Ledger {
access_list,
} => {
//
if let Some((new_state, new_logs)) =
vm.transact_create(caller, value, init_code, gas_limit, access_list, true)
{
// update ledger state
self.accounts = new_state.clone();
self.logs = new_logs.clone();
}
let (new_state, new_logs) =
vm.transact_create(caller, value, init_code, gas_limit, access_list, true)?;
// update ledger state
self.accounts = new_state.clone();
self.logs = new_logs.clone();
Ok(())
}
EvmTransaction::Create2 {
Expand All @@ -51,19 +49,18 @@ impl Ledger {
gas_limit,
access_list,
} => {
if let Some((new_state, new_logs)) = vm.transact_create2(
let (new_state, new_logs) = vm.transact_create2(
caller,
value,
init_code,
salt,
gas_limit,
access_list,
true,
) {
// update ledger state
self.accounts = new_state.clone();
self.logs = new_logs.clone();
}
)?;
// update ledger state
self.accounts = new_state.clone();
self.logs = new_logs.clone();
Ok(())
}
EvmTransaction::Call {
Expand All @@ -74,13 +71,11 @@ impl Ledger {
gas_limit,
access_list,
} => {
if let Some((new_state, new_logs, _byte_code_msg)) =
vm.transact_call(caller, address, value, data, gas_limit, access_list, true)
{
// update ledger state
self.accounts = new_state.clone();
self.logs = new_logs.clone();
}
let (new_state, new_logs, _byte_code_msg) =
vm.transact_call(caller, address, value, data, gas_limit, access_list, true)?;
// update ledger state
self.accounts = new_state.clone();
self.logs = new_logs.clone();
Ok(())
}
}
Expand Down
3 changes: 3 additions & 0 deletions chain-impl-mockchain/src/ledger/ledger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,9 @@ pub enum Error {
MintingPolicyViolation(#[from] MintingPolicyViolation),
#[error("evm transactions are disabled, the node was built without the 'evm' feature")]
DisabledEvmTransactions,
#[cfg(feature = "evm")]
#[error("evm transaction error")]
EvmTransactionError(#[from] chain_evm::machine::Error),
}

impl LedgerParameters {
Expand Down

0 comments on commit 9f60ec1

Please sign in to comment.