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 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<U256, EvmError>;
/// Evm interface.
pub trait Evm {
/// This function should be used to execute transaction.
fn exec(&self, params: &EvmParams, ext: &mut Ext) -> EvmResult;
}

View File

@ -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<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.
#[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<Executed, ExecutionError>;
/// Message-call/contract-creation executor; useful for executing transactions.
pub struct Executive<'a> {
state: &'a mut State,
@ -94,22 +139,24 @@ 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.
@ -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, &params, &mut substate)
Executive::call(e, &params, &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(&params.sender, &params.address, &params.value);
// if destination is builtin, try to execute it, or quickly return
if e.engine.is_builtin(&params.address) {
return match e.engine.cost_of_builtin(&params.address, &params.data) > params.gas {
true => ExecutiveResult::OutOfGas,
false => {
// TODO: substract gas for execution
let mut out = vec![];
e.engine.execute_builtin(&params.address, &params.data, &mut out);
ExecutiveResult::Ok
// if destination is builtin, try to execute it
let cost = e.engine.cost_of_builtin(&params.address, &params.data);
match cost <= params.gas {
true => {
e.engine.execute_builtin(&params.address, &params.data, output);
Ok(params.gas - cost)
},
false => Err(EvmError::OutOfGas)
}
}
}
// 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);
} 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)
} {
EvmResult::Stop { gas_left } => ExecutiveResult::Ok,
EvmResult::Return(_) => ExecutiveResult::Ok,
EvmResult::Suicide => {
substate.suicides.insert(params.address.clone());
ExecutiveResult::Ok
},
EvmResult::OutOfGas => ExecutiveResult::OutOfGas,
_err => ExecutiveResult::InternalError
} 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(&params.address);
// then transfer value to it
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);
let mut ext = Externalities::new(e.state, e.info, e.engine, e.depth, params, substate, OutputPolicy::InitContract);
let evm = VmFactory::create();
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).
@ -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, &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
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<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) {
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, &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)));
@ -489,7 +568,7 @@ mod tests {
{
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()));
@ -537,7 +616,7 @@ mod tests {
{
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);

View File

@ -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<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) };
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)
}
}
}

View File

@ -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;