From b273792ef0a42c24c6a098c96534696ad3c939c1 Mon Sep 17 00:00:00 2001 From: debris Date: Mon, 11 Jan 2016 02:17:29 +0100 Subject: [PATCH] big refactor of executive in progress --- src/evm/evm.rs | 23 ++-- src/evm/executive.rs | 245 ++++++++++++++++++++++++++++--------------- src/evm/ext.rs | 11 ++ src/evm/jit.rs | 20 +++- src/evm/mod.rs | 4 +- 5 files changed, 207 insertions(+), 96 deletions(-) diff --git a/src/evm/evm.rs b/src/evm/evm.rs index 25b3edd38..45b48a206 100644 --- a/src/evm/evm.rs +++ b/src/evm/evm.rs @@ -4,15 +4,26 @@ use util::uint::U256; use util::bytes::Bytes; use evm::{EvmParams, Ext}; -#[derive(Debug, Eq, PartialEq)] -pub enum EvmResult { - Stop { gas_left: U256 }, - Return(Bytes), - Suicide, +/// Evm errors. +pub enum EvmError { + /// Returned when transaction execution run out of gas. + /// The state should be reverted to the state from before the + /// transaction execution. But it does not mean that transaction + /// was invalid. Balance still should be transfered and nonce + /// should be increased. OutOfGas, - InternalError + /// Returned on evm internal error. Should never be ignored during development. + /// Likely to cause consensus issues. + Internal, } +/// Evm result. +/// +/// Returns gas_left if execution is successfull, otherwise error. +pub type EvmResult = Result; + +/// Evm interface. pub trait Evm { + /// This function should be used to execute transaction. fn exec(&self, params: &EvmParams, ext: &mut Ext) -> EvmResult; } diff --git a/src/evm/executive.rs b/src/evm/executive.rs index 82d821212..a983aff9c 100644 --- a/src/evm/executive.rs +++ b/src/evm/executive.rs @@ -1,5 +1,6 @@ use std::collections::HashSet; use std::cmp; +use std::ptr; use util::hash::*; use util::uint::*; use util::rlp::*; @@ -7,9 +8,10 @@ use util::sha3::*; use util::bytes::*; use state::*; use env_info::*; +use evm_schedule::*; use engine::*; use transaction::*; -use evm::{VmFactory, Ext, LogEntry, EvmParams, EvmResult}; +use evm::{VmFactory, Ext, LogEntry, EvmParams, EvmResult, EvmError}; /// Returns new address created from address and given nonce. pub fn contract_address(address: &Address, nonce: &U256) -> Address { @@ -40,6 +42,7 @@ impl Substate { } } + // TODO: remove pub fn logs(&self) -> &[LogEntry] { &self.logs } @@ -52,17 +55,59 @@ impl Substate { } } +/// Transaction execution result. +pub struct Executed { + /// Gas paid up front for execution of transaction. + pub gas: U256, + /// Gas used during execution of transaction. + pub gas_used: U256, + /// Gas refunded after the execution of transaction. + /// To get gas that was required up front, add `refunded` and `gas_used`. + pub refunded: U256, + /// Cumulative gas used in current block so far. + /// + /// cumulative_gas_used = gas_used(t0) + gas_used(t1) + ... gas_used(tn) + /// + /// where `tn` is current transaction. + pub cumulative_gas_used: U256, + /// Transaction output. + pub output: Bytes, + /// Vector of logs generated by transaction. + pub logs: Vec +} + +impl Executed { + fn new() -> Executed { + Executed { + gas: U256::zero(), + gas_used: U256::zero(), + refunded: U256::zero(), + cumulative_gas_used: U256::zero(), + output: vec![], + logs: vec![] + } + } +} + /// Result of executing the transaction. #[derive(PartialEq, Debug)] -pub enum ExecutiveResult { - Ok, +pub enum ExecutionError { + /// Returned when block (gas_used + gas) > gas_limit. + /// + /// If gas =< gas_limit, upstream may try to execute the transaction + /// in next block. BlockGasLimitReached { gas_limit: U256, gas_used: U256, gas: U256 }, + /// Returned when transaction nonce does not match state nonce. InvalidNonce { expected: U256, is: U256 }, + /// Returned when cost of transaction (value + gas_price * gas) exceeds + /// current sender balance. NotEnoughCash { required: U256, is: U256 }, - OutOfGas, - InternalError + /// Returned when internal evm error occurs. + Internal } +pub type ExecutionResult = Result; + /// Message-call/contract-creation executor; useful for executing transactions. pub struct Executive<'a> { state: &'a mut State, @@ -94,24 +139,26 @@ impl<'a> Executive<'a> { } /// This funtion should be used to execute transaction. - pub fn transact(e: &mut Executive<'a>, t: &Transaction) -> ExecutiveResult { - // validate if transaction fits into given block - if e.info.gas_used + t.gas > e.info.gas_limit { - return ExecutiveResult::BlockGasLimitReached { - gas_limit: e.info.gas_limit, - gas_used: e.info.gas_used, - gas: t.gas - }; - } + pub fn transact(e: &mut Executive<'a>, t: &Transaction) -> ExecutionResult { + // TODO: validate transaction signature ?/ sender let sender = t.sender(); let nonce = e.state.nonce(&sender); // validate transaction nonce if t.nonce != nonce { - return ExecutiveResult::InvalidNonce { expected: nonce, is: t.nonce }; + return Err(ExecutionError::InvalidNonce { expected: nonce, is: t.nonce }); } + // validate if transaction fits into given block + if e.info.gas_used + t.gas > e.info.gas_limit { + return Err(ExecutionError::BlockGasLimitReached { + gas_limit: e.info.gas_limit, + gas_used: e.info.gas_used, + gas: t.gas + }); + } + // TODO: we might need bigints here, or at least check overflows. let balance = e.state.balance(&sender); let gas_cost = t.gas * t.gas_price; @@ -119,9 +166,10 @@ impl<'a> Executive<'a> { // avoid unaffordable transactions if balance < total_cost { - return ExecutiveResult::NotEnoughCash { required: total_cost, is: balance }; + return Err(ExecutionError::NotEnoughCash { required: total_cost, is: balance }); } + // NOTE: there can be no invalid transactions from this point. e.state.inc_nonce(&sender); let mut substate = Substate::new(); @@ -137,7 +185,7 @@ impl<'a> Executive<'a> { code: t.data.clone(), data: vec![], }; - Executive::call(e, ¶ms, &mut substate) + Executive::call(e, ¶ms, &mut substate, &mut []) }, TransactionKind::MessageCall => { let params = EvmParams { @@ -156,78 +204,51 @@ impl<'a> Executive<'a> { // finalize here! e.finalize(substate, &sender, U256::zero(), U256::zero(), t.gas_price); - res + //res + Ok(Executed::new()) } /// Calls contract function with given contract params. - /// *Note. It does not finalize the transaction (doesn't do refunds, nor suicides). - fn call(e: &mut Executive<'a>, params: &EvmParams, substate: &mut Substate) -> ExecutiveResult { + /// NOTE. It does not finalize the transaction (doesn't do refunds, nor suicides). + /// Modifies the substate and the output. + /// Returns either gas_left or `EvmError`. + fn call(e: &mut Executive<'a>, params: &EvmParams, substate: &mut Substate, output: &mut [u8]) -> EvmResult { // at first, transfer value to destination e.state.transfer_balance(¶ms.sender, ¶ms.address, ¶ms.value); - // if destination is builtin, try to execute it, or quickly return if e.engine.is_builtin(¶ms.address) { - return match e.engine.cost_of_builtin(¶ms.address, ¶ms.data) > params.gas { - true => ExecutiveResult::OutOfGas, - false => { - // TODO: substract gas for execution - let mut out = vec![]; - e.engine.execute_builtin(¶ms.address, ¶ms.data, &mut out); - ExecutiveResult::Ok - } - } - } - - // otherwise do `normal` execution if destination is a contract - // TODO: is executing contract with no code different from not executing contract at all? - // if yes, there is a logic issue here. mk - if params.code.len() > 0 { - return match { - let mut ext = Externalities::new(e.state, e.info, e.engine, e.depth, params, substate); - let evm = VmFactory::create(); - evm.exec(¶ms, &mut ext) - } { - EvmResult::Stop { gas_left } => ExecutiveResult::Ok, - EvmResult::Return(_) => ExecutiveResult::Ok, - EvmResult::Suicide => { - substate.suicides.insert(params.address.clone()); - ExecutiveResult::Ok + // if destination is builtin, try to execute it + let cost = e.engine.cost_of_builtin(¶ms.address, ¶ms.data); + match cost <= params.gas { + true => { + e.engine.execute_builtin(¶ms.address, ¶ms.data, output); + Ok(params.gas - cost) }, - EvmResult::OutOfGas => ExecutiveResult::OutOfGas, - _err => ExecutiveResult::InternalError + false => Err(EvmError::OutOfGas) } + } else if params.code.len() > 0 { + // if destination is a contract, do normal message call + let mut ext = Externalities::new(e.state, e.info, e.engine, e.depth, params, substate, OutputPolicy::Return(output)); + let evm = VmFactory::create(); + evm.exec(¶ms, &mut ext) + } else { + // otherwise, nothing + Ok(params.gas) } - - ExecutiveResult::Ok } /// Creates contract with given contract params. - /// *Note. It does not finalize the transaction (doesn't do refunds, nor suicides). - fn create(e: &mut Executive<'a>, params: &EvmParams, substate: &mut Substate) -> ExecutiveResult { + /// NOTE. It does not finalize the transaction (doesn't do refunds, nor suicides). + /// Modifies the substate. + fn create(e: &mut Executive<'a>, params: &EvmParams, substate: &mut Substate) -> EvmResult { // at first create new contract e.state.new_contract(¶ms.address); // then transfer value to it e.state.transfer_balance(¶ms.sender, ¶ms.address, ¶ms.value); - match { - let mut ext = Externalities::new(e.state, e.info, e.engine, e.depth, params, substate); - let evm = VmFactory::create(); - evm.exec(¶ms, &mut ext) - } { - EvmResult::Stop { gas_left } => { - ExecutiveResult::Ok - }, - EvmResult::Return(output) => { - e.state.init_code(¶ms.address, output); - ExecutiveResult::Ok - }, - EvmResult::Suicide => { - substate.suicides.insert(params.address.clone()); - ExecutiveResult::Ok - }, - EvmResult::OutOfGas => ExecutiveResult::OutOfGas, - _err => ExecutiveResult::InternalError - } + let mut ext = Externalities::new(e.state, e.info, e.engine, e.depth, params, substate, OutputPolicy::InitContract); + let evm = VmFactory::create(); + evm.exec(¶ms, &mut ext) } /// Finalizes the transaction (does refunds and suicides). @@ -256,6 +277,19 @@ impl<'a> Executive<'a> { } } +pub enum ExtMode { + Call, + Create +} + +/// Wrapper structure for evm return data to avoid unnecessary copying. +pub enum OutputPolicy<'a> { + /// Reference to fixed sized output of a message call. + Return(&'a mut [u8]), + /// Use it, if you want return code to initialize contract. + InitContract +} + /// Implementation of evm Externalities. pub struct Externalities<'a> { state: &'a mut State, @@ -263,19 +297,29 @@ pub struct Externalities<'a> { engine: &'a Engine, depth: usize, params: &'a EvmParams, - substate: &'a mut Substate + substate: &'a mut Substate, + schedule: EvmSchedule, + output: OutputPolicy<'a> } impl<'a> Externalities<'a> { /// Basic `Externalities` constructor. - pub fn new(state: &'a mut State, info: &'a EnvInfo, engine: &'a Engine, depth: usize, params: &'a EvmParams, substate: &'a mut Substate) -> Self { + pub fn new(state: &'a mut State, + info: &'a EnvInfo, + engine: &'a Engine, + depth: usize, + params: &'a EvmParams, + substate: &'a mut Substate, + output: OutputPolicy<'a>) -> Self { Externalities { state: state, info: info, engine: engine, depth: depth, params: params, - substate: substate + substate: substate, + schedule: engine.evm_schedule(info), + output: output } } } @@ -345,19 +389,18 @@ impl<'a> Ext for Externalities<'a> { println!("gas: {:?}", gas); println!("call_gas: {:?}", call_gas); - let schedule = self.engine.evm_schedule(self.info); let mut gas_cost = call_gas; let mut call_gas = call_gas; let is_call = receive_address == code_address; if is_call && self.state.code(&code_address).is_none() { - gas_cost = gas_cost + schedule.call_new_account_gas as u64; + gas_cost = gas_cost + self.schedule.call_new_account_gas as u64; } if *value > U256::zero() { - assert!(schedule.call_value_transfer_gas > schedule.call_stipend, "overflow possible"); - gas_cost = gas_cost + schedule.call_value_transfer_gas as u64; - call_gas = call_gas + schedule.call_stipend as u64; + assert!(self.schedule.call_value_transfer_gas > self.schedule.call_stipend, "overflow possible"); + gas_cost = gas_cost + self.schedule.call_value_transfer_gas as u64; + call_gas = call_gas + self.schedule.call_stipend as u64; } if gas_cost > gas { @@ -387,9 +430,11 @@ impl<'a> Ext for Externalities<'a> { let mut substate = Substate::new(); { let mut ex = Executive::from_parent(self); - Executive::call(&mut ex, ¶ms, &mut substate); + // TODO: take output into account + Executive::call(&mut ex, ¶ms, &mut substate, &mut []); } + self.substate.accrue(substate); // TODO: replace call_gas with what's actually left Some((vec![], gas - gas_cost + call_gas)) } @@ -398,10 +443,44 @@ impl<'a> Ext for Externalities<'a> { self.state.code(address).unwrap_or(vec![]) } + fn ret(&mut self, gas: u64, data: &[u8]) -> Option { + match &mut self.output { + &mut OutputPolicy::Return(ref mut slice) => unsafe { + let len = cmp::min(slice.len(), data.len()); + ptr::copy(data.as_ptr(), slice.as_mut_ptr(), len); + Some(gas) + }, + &mut OutputPolicy::InitContract => { + let return_cost = data.len() as u64 * self.schedule.create_data_gas as u64; + if return_cost > gas { + return None; + } + let mut code = vec![]; + code.reserve(data.len()); + unsafe { + ptr::copy(data.as_ptr(), code.as_mut_ptr(), data.len()); + code.set_len(data.len()); + } + let address = &self.params.address; + self.state.init_code(address, code); + Some(gas - return_cost) + } + } + } + fn log(&mut self, topics: Vec, data: Bytes) { let address = self.params.address.clone(); self.substate.logs.push(LogEntry::new(address, topics, data)); } + + fn suicide(&mut self) { + let address = self.params.address.clone(); + self.substate.suicides.insert(address); + } + + fn schedule(&self) -> &EvmSchedule { + &self.schedule + } } #[cfg(test)] @@ -462,7 +541,7 @@ mod tests { { let mut ex = Executive::new(&mut state, &info, &engine); - assert_eq!(Executive::create(&mut ex, ¶ms, &mut substate), ExecutiveResult::Ok); + assert_eq!(Executive::create(&mut ex, ¶ms, &mut substate), ExecutionResult::Ok); } assert_eq!(state.storage_at(&address, &H256::new()), H256::from(&U256::from(0xf9u64))); @@ -489,7 +568,7 @@ mod tests { { let mut ex = Executive::new(&mut state, &info, &engine); - assert_eq!(Executive::create(&mut ex, ¶ms, &mut substate), ExecutiveResult::Ok); + assert_eq!(Executive::create(&mut ex, ¶ms, &mut substate), ExecutionResult::Ok); } assert_eq!(state.storage_at(&address, &H256::new()), H256::from(next_address.clone())); @@ -537,7 +616,7 @@ mod tests { { let mut ex = Executive::new(&mut state, &info, engine.deref()); - assert_eq!(Executive::call(&mut ex, ¶ms, &mut substate), ExecutiveResult::Ok); + assert_eq!(Executive::call(&mut ex, ¶ms, &mut substate), ExecutionResult::Ok); } assert!(false); diff --git a/src/evm/ext.rs b/src/evm/ext.rs index 8f693f049..d9fb0b659 100644 --- a/src/evm/ext.rs +++ b/src/evm/ext.rs @@ -3,6 +3,7 @@ use util::hash::*; use util::uint::*; use util::bytes::*; +use evm_schedule::*; pub trait Ext { /// Returns a value for given key. @@ -33,4 +34,14 @@ pub trait Ext { /// Creates log entry with given topics and data fn log(&mut self, topics: Vec, data: Bytes); + + /// Should be called when transaction calls `RETURN` opcode. + /// Returns gas_left if cost of returning the data is not too high. + fn ret(&mut self, gas: u64, data: &[u8]) -> Option; + + /// Should be called when contract commits suicide. + fn suicide(&mut self); + + /// Returns schedule. + fn schedule(&self) -> &EvmSchedule; } diff --git a/src/evm/jit.rs b/src/evm/jit.rs index 4e239a27b..2ac756ddc 100644 --- a/src/evm/jit.rs +++ b/src/evm/jit.rs @@ -316,11 +316,21 @@ impl evm::Evm for JitEvm { let mut context = unsafe { evmjit::ContextHandle::new(data.into_jit(), &mut ext_handle) }; match context.exec() { - evmjit::ReturnCode::Stop => evm::EvmResult::Stop { gas_left: U256::from(context.gas_left()) }, - evmjit::ReturnCode::Return => evm::EvmResult::Return(context.output_data().to_vec()), - evmjit::ReturnCode::Suicide => evm::EvmResult::Suicide, - evmjit::ReturnCode::OutOfGas => evm::EvmResult::OutOfGas, - _ => evm::EvmResult::InternalError + evmjit::ReturnCode::Stop => Ok(evm::EvmOutput::new(U256::from(context.gas_left()), None)), + evmjit::ReturnCode::Return => { + if context.output_data().len() as u64 * ext.schedule().create_data_gas as u64 > context.gas_left() { + return Err(evm::EvmError::OutOfGas); + } + + Ok(evm::EvmOutput::new(U256::from(context.gas_left()), Some(context.output_data().to_vec()))) + }, + evmjit::ReturnCode::Suicide => { + // what if there is a suicide and we run out of gas just after? + ext.suicide(); + Ok(evm::EvmOutput::new(U256::from(context.gas_left()), None)) + }, + evmjit::ReturnCode::OutOfGas => Err(evm::EvmError::OutOfGas), + _err => Err(evm::EvmError::Internal) } } } diff --git a/src/evm/mod.rs b/src/evm/mod.rs index 42643740e..3e3b7ffcb 100644 --- a/src/evm/mod.rs +++ b/src/evm/mod.rs @@ -9,9 +9,9 @@ pub mod params; #[cfg(feature = "jit" )] mod jit; -pub use self::evm::{Evm, EvmResult}; +pub use self::evm::{Evm, EvmError, EvmResult}; pub use self::ext::{Ext}; pub use self::logentry::LogEntry; pub use self::vmfactory::VmFactory; -pub use self::executive::{Executive, ExecutiveResult, Externalities, Substate}; +pub use self::executive::{Executive, ExecutionResult, Externalities, Substate}; pub use self::params::EvmParams;