big refactor of executive in progress

This commit is contained in:
debris 2016-01-11 02:17:29 +01:00
parent b72da41ea7
commit b273792ef0
5 changed files with 207 additions and 96 deletions

View File

@ -4,15 +4,26 @@ use util::uint::U256;
use util::bytes::Bytes; use util::bytes::Bytes;
use evm::{EvmParams, Ext}; use evm::{EvmParams, Ext};
#[derive(Debug, Eq, PartialEq)] /// Evm errors.
pub enum EvmResult { pub enum EvmError {
Stop { gas_left: U256 }, /// Returned when transaction execution run out of gas.
Return(Bytes), /// The state should be reverted to the state from before the
Suicide, /// transaction execution. But it does not mean that transaction
/// was invalid. Balance still should be transfered and nonce
/// should be increased.
OutOfGas, 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<U256, EvmError>;
/// Evm interface.
pub trait Evm { pub trait Evm {
/// This function should be used to execute transaction.
fn exec(&self, params: &EvmParams, ext: &mut Ext) -> EvmResult; fn exec(&self, params: &EvmParams, ext: &mut Ext) -> EvmResult;
} }

View File

@ -1,5 +1,6 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::cmp; use std::cmp;
use std::ptr;
use util::hash::*; use util::hash::*;
use util::uint::*; use util::uint::*;
use util::rlp::*; use util::rlp::*;
@ -7,9 +8,10 @@ use util::sha3::*;
use util::bytes::*; use util::bytes::*;
use state::*; use state::*;
use env_info::*; use env_info::*;
use evm_schedule::*;
use engine::*; use engine::*;
use transaction::*; 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. /// Returns new address created from address and given nonce.
pub fn contract_address(address: &Address, nonce: &U256) -> Address { pub fn contract_address(address: &Address, nonce: &U256) -> Address {
@ -40,6 +42,7 @@ impl Substate {
} }
} }
// TODO: remove
pub fn logs(&self) -> &[LogEntry] { pub fn logs(&self) -> &[LogEntry] {
&self.logs &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<LogEntry>
}
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. /// Result of executing the transaction.
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub enum ExecutiveResult { pub enum ExecutionError {
Ok, /// 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 }, BlockGasLimitReached { gas_limit: U256, gas_used: U256, gas: U256 },
/// Returned when transaction nonce does not match state nonce.
InvalidNonce { expected: U256, is: U256 }, InvalidNonce { expected: U256, is: U256 },
/// Returned when cost of transaction (value + gas_price * gas) exceeds
/// current sender balance.
NotEnoughCash { required: U256, is: U256 }, NotEnoughCash { required: U256, is: U256 },
OutOfGas, /// Returned when internal evm error occurs.
InternalError Internal
} }
pub type ExecutionResult = Result<Executed, ExecutionError>;
/// Message-call/contract-creation executor; useful for executing transactions. /// Message-call/contract-creation executor; useful for executing transactions.
pub struct Executive<'a> { pub struct Executive<'a> {
state: &'a mut State, state: &'a mut State,
@ -94,24 +139,26 @@ impl<'a> Executive<'a> {
} }
/// This funtion should be used to execute transaction. /// This funtion should be used to execute transaction.
pub fn transact(e: &mut Executive<'a>, t: &Transaction) -> ExecutiveResult { pub fn transact(e: &mut Executive<'a>, t: &Transaction) -> ExecutionResult {
// validate if transaction fits into given block // TODO: validate transaction signature ?/ sender
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
};
}
let sender = t.sender(); let sender = t.sender();
let nonce = e.state.nonce(&sender); let nonce = e.state.nonce(&sender);
// validate transaction nonce // validate transaction nonce
if t.nonce != 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. // TODO: we might need bigints here, or at least check overflows.
let balance = e.state.balance(&sender); let balance = e.state.balance(&sender);
let gas_cost = t.gas * t.gas_price; let gas_cost = t.gas * t.gas_price;
@ -119,9 +166,10 @@ impl<'a> Executive<'a> {
// avoid unaffordable transactions // avoid unaffordable transactions
if balance < total_cost { 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); e.state.inc_nonce(&sender);
let mut substate = Substate::new(); let mut substate = Substate::new();
@ -137,7 +185,7 @@ impl<'a> Executive<'a> {
code: t.data.clone(), code: t.data.clone(),
data: vec![], data: vec![],
}; };
Executive::call(e, &params, &mut substate) Executive::call(e, &params, &mut substate, &mut [])
}, },
TransactionKind::MessageCall => { TransactionKind::MessageCall => {
let params = EvmParams { let params = EvmParams {
@ -156,78 +204,51 @@ impl<'a> Executive<'a> {
// finalize here! // finalize here!
e.finalize(substate, &sender, U256::zero(), U256::zero(), t.gas_price); e.finalize(substate, &sender, U256::zero(), U256::zero(), t.gas_price);
res //res
Ok(Executed::new())
} }
/// Calls contract function with given contract params. /// Calls contract function with given contract params.
/// *Note. It does not finalize the transaction (doesn't do refunds, nor suicides). /// 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 { /// 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 // at first, transfer value to destination
e.state.transfer_balance(&params.sender, &params.address, &params.value); e.state.transfer_balance(&params.sender, &params.address, &params.value);
// if destination is builtin, try to execute it, or quickly return
if e.engine.is_builtin(&params.address) { if e.engine.is_builtin(&params.address) {
return match e.engine.cost_of_builtin(&params.address, &params.data) > params.gas { // if destination is builtin, try to execute it
true => ExecutiveResult::OutOfGas, let cost = e.engine.cost_of_builtin(&params.address, &params.data);
false => { match cost <= params.gas {
// TODO: substract gas for execution true => {
let mut out = vec![]; e.engine.execute_builtin(&params.address, &params.data, output);
e.engine.execute_builtin(&params.address, &params.data, &mut out); Ok(params.gas - cost)
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(&params, &mut ext)
} {
EvmResult::Stop { gas_left } => ExecutiveResult::Ok,
EvmResult::Return(_) => ExecutiveResult::Ok,
EvmResult::Suicide => {
substate.suicides.insert(params.address.clone());
ExecutiveResult::Ok
}, },
EvmResult::OutOfGas => ExecutiveResult::OutOfGas, false => Err(EvmError::OutOfGas)
_err => ExecutiveResult::InternalError
} }
} 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(&params, &mut ext)
} else {
// otherwise, nothing
Ok(params.gas)
} }
ExecutiveResult::Ok
} }
/// Creates contract with given contract params. /// Creates contract with given contract params.
/// *Note. It does not finalize the transaction (doesn't do refunds, nor suicides). /// 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 { /// Modifies the substate.
fn create(e: &mut Executive<'a>, params: &EvmParams, substate: &mut Substate) -> EvmResult {
// at first create new contract // at first create new contract
e.state.new_contract(&params.address); e.state.new_contract(&params.address);
// then transfer value to it // then transfer value to it
e.state.transfer_balance(&params.sender, &params.address, &params.value); e.state.transfer_balance(&params.sender, &params.address, &params.value);
match { let mut ext = Externalities::new(e.state, e.info, e.engine, e.depth, params, substate, OutputPolicy::InitContract);
let mut ext = Externalities::new(e.state, e.info, e.engine, e.depth, params, substate); let evm = VmFactory::create();
let evm = VmFactory::create(); evm.exec(&params, &mut ext)
evm.exec(&params, &mut ext)
} {
EvmResult::Stop { gas_left } => {
ExecutiveResult::Ok
},
EvmResult::Return(output) => {
e.state.init_code(&params.address, output);
ExecutiveResult::Ok
},
EvmResult::Suicide => {
substate.suicides.insert(params.address.clone());
ExecutiveResult::Ok
},
EvmResult::OutOfGas => ExecutiveResult::OutOfGas,
_err => ExecutiveResult::InternalError
}
} }
/// Finalizes the transaction (does refunds and suicides). /// 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. /// Implementation of evm Externalities.
pub struct Externalities<'a> { pub struct Externalities<'a> {
state: &'a mut State, state: &'a mut State,
@ -263,19 +297,29 @@ pub struct Externalities<'a> {
engine: &'a Engine, engine: &'a Engine,
depth: usize, depth: usize,
params: &'a EvmParams, params: &'a EvmParams,
substate: &'a mut Substate substate: &'a mut Substate,
schedule: EvmSchedule,
output: OutputPolicy<'a>
} }
impl<'a> Externalities<'a> { impl<'a> Externalities<'a> {
/// Basic `Externalities` constructor. /// 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 { Externalities {
state: state, state: state,
info: info, info: info,
engine: engine, engine: engine,
depth: depth, depth: depth,
params: params, 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!("gas: {:?}", gas);
println!("call_gas: {:?}", call_gas); println!("call_gas: {:?}", call_gas);
let schedule = self.engine.evm_schedule(self.info);
let mut gas_cost = call_gas; let mut gas_cost = call_gas;
let mut call_gas = call_gas; let mut call_gas = call_gas;
let is_call = receive_address == code_address; let is_call = receive_address == code_address;
if is_call && self.state.code(&code_address).is_none() { 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() { if *value > U256::zero() {
assert!(schedule.call_value_transfer_gas > schedule.call_stipend, "overflow possible"); assert!(self.schedule.call_value_transfer_gas > self.schedule.call_stipend, "overflow possible");
gas_cost = gas_cost + schedule.call_value_transfer_gas as u64; gas_cost = gas_cost + self.schedule.call_value_transfer_gas as u64;
call_gas = call_gas + schedule.call_stipend as u64; call_gas = call_gas + self.schedule.call_stipend as u64;
} }
if gas_cost > gas { if gas_cost > gas {
@ -387,9 +430,11 @@ impl<'a> Ext for Externalities<'a> {
let mut substate = Substate::new(); let mut substate = Substate::new();
{ {
let mut ex = Executive::from_parent(self); let mut ex = Executive::from_parent(self);
Executive::call(&mut ex, &params, &mut substate); // TODO: take output into account
Executive::call(&mut ex, &params, &mut substate, &mut []);
} }
self.substate.accrue(substate);
// TODO: replace call_gas with what's actually left // TODO: replace call_gas with what's actually left
Some((vec![], gas - gas_cost + call_gas)) Some((vec![], gas - gas_cost + call_gas))
} }
@ -398,10 +443,44 @@ impl<'a> Ext for Externalities<'a> {
self.state.code(address).unwrap_or(vec![]) self.state.code(address).unwrap_or(vec![])
} }
fn ret(&mut self, gas: u64, data: &[u8]) -> Option<u64> {
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<H256>, data: Bytes) { fn log(&mut self, topics: Vec<H256>, data: Bytes) {
let address = self.params.address.clone(); let address = self.params.address.clone();
self.substate.logs.push(LogEntry::new(address, topics, data)); 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)] #[cfg(test)]
@ -462,7 +541,7 @@ mod tests {
{ {
let mut ex = Executive::new(&mut state, &info, &engine); let mut ex = Executive::new(&mut state, &info, &engine);
assert_eq!(Executive::create(&mut ex, &params, &mut substate), ExecutiveResult::Ok); assert_eq!(Executive::create(&mut ex, &params, &mut substate), ExecutionResult::Ok);
} }
assert_eq!(state.storage_at(&address, &H256::new()), H256::from(&U256::from(0xf9u64))); 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); let mut ex = Executive::new(&mut state, &info, &engine);
assert_eq!(Executive::create(&mut ex, &params, &mut substate), ExecutiveResult::Ok); assert_eq!(Executive::create(&mut ex, &params, &mut substate), ExecutionResult::Ok);
} }
assert_eq!(state.storage_at(&address, &H256::new()), H256::from(next_address.clone())); 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()); let mut ex = Executive::new(&mut state, &info, engine.deref());
assert_eq!(Executive::call(&mut ex, &params, &mut substate), ExecutiveResult::Ok); assert_eq!(Executive::call(&mut ex, &params, &mut substate), ExecutionResult::Ok);
} }
assert!(false); assert!(false);

View File

@ -3,6 +3,7 @@
use util::hash::*; use util::hash::*;
use util::uint::*; use util::uint::*;
use util::bytes::*; use util::bytes::*;
use evm_schedule::*;
pub trait Ext { pub trait Ext {
/// Returns a value for given key. /// Returns a value for given key.
@ -33,4 +34,14 @@ pub trait Ext {
/// Creates log entry with given topics and data /// Creates log entry with given topics and data
fn log(&mut self, topics: Vec<H256>, data: Bytes); fn log(&mut self, topics: Vec<H256>, 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<u64>;
/// Should be called when contract commits suicide.
fn suicide(&mut self);
/// Returns schedule.
fn schedule(&self) -> &EvmSchedule;
} }

View File

@ -316,11 +316,21 @@ impl evm::Evm for JitEvm {
let mut context = unsafe { evmjit::ContextHandle::new(data.into_jit(), &mut ext_handle) }; let mut context = unsafe { evmjit::ContextHandle::new(data.into_jit(), &mut ext_handle) };
match context.exec() { match context.exec() {
evmjit::ReturnCode::Stop => evm::EvmResult::Stop { gas_left: U256::from(context.gas_left()) }, evmjit::ReturnCode::Stop => Ok(evm::EvmOutput::new(U256::from(context.gas_left()), None)),
evmjit::ReturnCode::Return => evm::EvmResult::Return(context.output_data().to_vec()), evmjit::ReturnCode::Return => {
evmjit::ReturnCode::Suicide => evm::EvmResult::Suicide, if context.output_data().len() as u64 * ext.schedule().create_data_gas as u64 > context.gas_left() {
evmjit::ReturnCode::OutOfGas => evm::EvmResult::OutOfGas, return Err(evm::EvmError::OutOfGas);
_ => evm::EvmResult::InternalError }
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)
} }
} }
} }

View File

@ -9,9 +9,9 @@ pub mod params;
#[cfg(feature = "jit" )] #[cfg(feature = "jit" )]
mod jit; mod jit;
pub use self::evm::{Evm, EvmResult}; pub use self::evm::{Evm, EvmError, EvmResult};
pub use self::ext::{Ext}; pub use self::ext::{Ext};
pub use self::logentry::LogEntry; pub use self::logentry::LogEntry;
pub use self::vmfactory::VmFactory; 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; pub use self::params::EvmParams;