Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Commit

Permalink
Resumable EVM and heap-allocated callstack (#9360)
Browse files Browse the repository at this point in the history
* Add new Vm trappable interface

* Exec/Resume interface

* Basic implementation of CallCreateExecutive

* Implement resume_call and resume_create for executive

* Move convertion to call/create result to separate function

* Implement consume that converts resumable to non-resumable

* Use consume for Executive::call/create

* Resumable EVM

* Implement tracing mode without needing subtracers

* Implement vmtracer so it doesn't require extra structs for subtracing

* Use the new tracing mode in executive

* Fix most of the linting errors for cargo build

* Add the concept of stack_depth

* Add back crossbeam

* Fix some test compile

* Fix prefix address test

* Fix evm crate tests

* Fix wasm crate test compile

* Fix wasm runner compile

* Fix jsontests compile

* Fix evmbin compile

* Fix an issue with create nonce and better vm tracing interface

* Fix linting

* Fix evmbin compile

* Fix unconfirmed_substate and static_flag

* Fix an issue in create address logic

* Fix top-level tracing

* Handle builtin tracing

* Fix suicide and reward tracing index stack

* Fix an issue where trap conflicts with tracing

* Fix an issue in parent step vm tracing

* Fix revert tracing

* Fix evmbin tests

* Remove params clone

* Fix TODO proofs

* Fix jsontests compile

* Fix evmbin merge issue

* Fix wasm merge issue

* Fix wasm test

* Fix ethcore merge warnings

* Fix evmbin compile

* Better expect messages and add some trace::skip_one asserts
  • Loading branch information
sorpaas authored Oct 2, 2018
1 parent 61ec361 commit 1e9aebb
Show file tree
Hide file tree
Showing 24 changed files with 1,469 additions and 808 deletions.
2 changes: 1 addition & 1 deletion ethcore/evm/src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ impl Finalize for Error {
}

/// Cost calculation type. For low-gas usage we calculate costs using usize instead of U256
pub trait CostType: Sized + From<usize> + Copy
pub trait CostType: Sized + From<usize> + Copy + Send
+ ops::Mul<Output=Self> + ops::Div<Output=Self> + ops::Add<Output=Self> +ops::Sub<Output=Self>
+ ops::Shr<usize, Output=Self> + ops::Shl<usize, Output=Self>
+ cmp::Ord + fmt::Debug {
Expand Down
4 changes: 2 additions & 2 deletions ethcore/evm/src/factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
//! Evm factory.
//!
use std::sync::Arc;
use vm::{Vm, Schedule};
use vm::{Exec, Schedule};
use ethereum_types::U256;
use super::vm::ActionParams;
use super::interpreter::SharedCache;
Expand All @@ -33,7 +33,7 @@ pub struct Factory {
impl Factory {
/// Create fresh instance of VM
/// Might choose implementation depending on supplied gas.
pub fn create(&self, params: ActionParams, schedule: &Schedule, depth: usize) -> Box<Vm> {
pub fn create(&self, params: ActionParams, schedule: &Schedule, depth: usize) -> Box<Exec> {
match self.evm {
VMType::Interpreter => if Self::can_fit_in_usize(&params.gas) {
Box::new(super::interpreter::Interpreter::<usize>::new(params, self.evm_cache.clone(), schedule, depth))
Expand Down
201 changes: 144 additions & 57 deletions ethcore/evm/src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ use ethereum_types::{U256, U512, H256, Address};

use vm::{
self, ActionParams, ParamsType, ActionValue, CallType, MessageCallResult,
ContractCreateResult, CreateContractAddress, ReturnData, GasLeft, Schedule
ContractCreateResult, CreateContractAddress, ReturnData, GasLeft, Schedule,
TrapKind, TrapError
};

use evm::CostType;
Expand Down Expand Up @@ -103,6 +104,7 @@ enum InstructionResult<Gas> {
apply: bool,
},
StopExecution,
Trap(TrapKind),
}

enum Never {}
Expand Down Expand Up @@ -161,6 +163,7 @@ pub enum InterpreterResult {
Done(vm::Result<GasLeft>),
/// The VM can continue to run.
Continue,
Trap(TrapKind),
}

impl From<vm::Error> for InterpreterResult {
Expand All @@ -182,22 +185,89 @@ pub struct Interpreter<Cost: CostType> {
valid_jump_destinations: Option<Arc<BitSet>>,
gasometer: Option<Gasometer<Cost>>,
stack: VecStack<U256>,
resume_output_range: Option<(U256, U256)>,
resume_result: Option<InstructionResult<Cost>>,
last_stack_ret_len: usize,
_type: PhantomData<Cost>,
}

impl<Cost: CostType> vm::Vm for Interpreter<Cost> {
fn exec(&mut self, ext: &mut vm::Ext) -> vm::Result<GasLeft> {
impl<Cost: 'static + CostType> vm::Exec for Interpreter<Cost> {
fn exec(mut self: Box<Self>, ext: &mut vm::Ext) -> vm::ExecTrapResult<GasLeft> {
loop {
let result = self.step(ext);
match result {
InterpreterResult::Continue => {},
InterpreterResult::Done(value) => return value,
InterpreterResult::Done(value) => return Ok(value),
InterpreterResult::Trap(trap) => match trap {
TrapKind::Call(params) => {
return Err(TrapError::Call(params, self));
},
TrapKind::Create(params, address) => {
return Err(TrapError::Create(params, address, self));
},
},
InterpreterResult::Stopped => panic!("Attempted to execute an already stopped VM.")
}
}
}
}

impl<Cost: 'static + CostType> vm::ResumeCall for Interpreter<Cost> {
fn resume_call(mut self: Box<Self>, result: MessageCallResult) -> Box<vm::Exec> {
{
let this = &mut *self;
let (out_off, out_size) = this.resume_output_range.take().expect("Box<ResumeCall> is obtained from a call opcode; resume_output_range is always set after those opcodes are executed; qed");

match result {
MessageCallResult::Success(gas_left, data) => {
let output = this.mem.writeable_slice(out_off, out_size);
let len = cmp::min(output.len(), data.len());
(&mut output[..len]).copy_from_slice(&data[..len]);

this.return_data = data;
this.stack.push(U256::one());
this.resume_result = Some(InstructionResult::UnusedGas(Cost::from_u256(gas_left).expect("Gas left cannot be greater than current one")));
},
MessageCallResult::Reverted(gas_left, data) => {
let output = this.mem.writeable_slice(out_off, out_size);
let len = cmp::min(output.len(), data.len());
(&mut output[..len]).copy_from_slice(&data[..len]);

this.return_data = data;
this.stack.push(U256::zero());
this.resume_result = Some(InstructionResult::UnusedGas(Cost::from_u256(gas_left).expect("Gas left cannot be greater than current one")));
},
MessageCallResult::Failed => {
this.stack.push(U256::zero());
this.resume_result = Some(InstructionResult::Ok);
},
}
}
self
}
}

impl<Cost: 'static + CostType> vm::ResumeCreate for Interpreter<Cost> {
fn resume_create(mut self: Box<Self>, result: ContractCreateResult) -> Box<vm::Exec> {
match result {
ContractCreateResult::Created(address, gas_left) => {
self.stack.push(address_to_u256(address));
self.resume_result = Some(InstructionResult::UnusedGas(Cost::from_u256(gas_left).expect("Gas left cannot be greater.")));
},
ContractCreateResult::Reverted(gas_left, return_data) => {
self.stack.push(U256::zero());
self.return_data = return_data;
self.resume_result = Some(InstructionResult::UnusedGas(Cost::from_u256(gas_left).expect("Gas left cannot be greater.")));
},
ContractCreateResult::Failed => {
self.stack.push(U256::zero());
self.resume_result = Some(InstructionResult::Ok);
},
}
self
}
}

impl<Cost: CostType> Interpreter<Cost> {
/// Create a new `Interpreter` instance with shared cache.
pub fn new(mut params: ActionParams, cache: Arc<SharedCache>, schedule: &Schedule, depth: usize) -> Interpreter<Cost> {
Expand All @@ -215,6 +285,9 @@ impl<Cost: CostType> Interpreter<Cost> {
do_trace: true,
mem: Vec::new(),
return_data: ReturnData::empty(),
last_stack_ret_len: 0,
resume_output_range: None,
resume_result: None,
_type: PhantomData,
}
}
Expand Down Expand Up @@ -244,50 +317,57 @@ impl<Cost: CostType> Interpreter<Cost> {
/// Inner helper function for step.
#[inline(always)]
fn step_inner(&mut self, ext: &mut vm::Ext) -> Result<Never, InterpreterResult> {
let opcode = self.reader.code[self.reader.position];
let instruction = Instruction::from_u8(opcode);
self.reader.position += 1;

// TODO: make compile-time removable if too much of a performance hit.
self.do_trace = self.do_trace && ext.trace_next_instruction(
self.reader.position - 1, opcode, self.gasometer.as_mut().expect(GASOMETER_PROOF).current_gas.as_u256(),
);

let instruction = match instruction {
Some(i) => i,
None => return Err(InterpreterResult::Done(Err(vm::Error::BadInstruction {
instruction: opcode
}))),
};
let result = match self.resume_result.take() {
Some(result) => result,
None => {
let opcode = self.reader.code[self.reader.position];
let instruction = Instruction::from_u8(opcode);
self.reader.position += 1;

// TODO: make compile-time removable if too much of a performance hit.
self.do_trace = self.do_trace && ext.trace_next_instruction(
self.reader.position - 1, opcode, self.gasometer.as_mut().expect(GASOMETER_PROOF).current_gas.as_u256(),
);

let info = instruction.info();
self.verify_instruction(ext, instruction, info)?;
let instruction = match instruction {
Some(i) => i,
None => return Err(InterpreterResult::Done(Err(vm::Error::BadInstruction {
instruction: opcode
}))),
};

// Calculate gas cost
let requirements = self.gasometer.as_mut().expect(GASOMETER_PROOF).requirements(ext, instruction, info, &self.stack, self.mem.size())?;
if self.do_trace {
ext.trace_prepare_execute(self.reader.position - 1, opcode, requirements.gas_cost.as_u256());
}
let info = instruction.info();
self.last_stack_ret_len = info.ret;
self.verify_instruction(ext, instruction, info)?;

// Calculate gas cost
let requirements = self.gasometer.as_mut().expect(GASOMETER_PROOF).requirements(ext, instruction, info, &self.stack, self.mem.size())?;
if self.do_trace {
ext.trace_prepare_execute(self.reader.position - 1, opcode, requirements.gas_cost.as_u256(), Self::mem_written(instruction, &self.stack), Self::store_written(instruction, &self.stack));
}

self.gasometer.as_mut().expect(GASOMETER_PROOF).verify_gas(&requirements.gas_cost)?;
self.mem.expand(requirements.memory_required_size);
self.gasometer.as_mut().expect(GASOMETER_PROOF).current_mem_gas = requirements.memory_total_gas;
self.gasometer.as_mut().expect(GASOMETER_PROOF).current_gas = self.gasometer.as_mut().expect(GASOMETER_PROOF).current_gas - requirements.gas_cost;
self.gasometer.as_mut().expect(GASOMETER_PROOF).verify_gas(&requirements.gas_cost)?;
self.mem.expand(requirements.memory_required_size);
self.gasometer.as_mut().expect(GASOMETER_PROOF).current_mem_gas = requirements.memory_total_gas;
self.gasometer.as_mut().expect(GASOMETER_PROOF).current_gas = self.gasometer.as_mut().expect(GASOMETER_PROOF).current_gas - requirements.gas_cost;

evm_debug!({ self.informant.before_instruction(self.reader.position, instruction, info, &self.gasometer.as_mut().expect(GASOMETER_PROOF).current_gas, &self.stack) });
evm_debug!({ self.informant.before_instruction(self.reader.position, instruction, info, &self.gasometer.as_mut().expect(GASOMETER_PROOF).current_gas, &self.stack) });

let (mem_written, store_written) = match self.do_trace {
true => (Self::mem_written(instruction, &self.stack), Self::store_written(instruction, &self.stack)),
false => (None, None),
};
// Execute instruction
let current_gas = self.gasometer.as_mut().expect(GASOMETER_PROOF).current_gas;
let result = self.exec_instruction(
current_gas, ext, instruction, requirements.provide_gas
)?;

// Execute instruction
let current_gas = self.gasometer.as_mut().expect(GASOMETER_PROOF).current_gas;
let result = self.exec_instruction(
current_gas, ext, instruction, requirements.provide_gas
)?;
evm_debug!({ self.informant.after_instruction(instruction) });

result
},
};

evm_debug!({ self.informant.after_instruction(instruction) });
if let InstructionResult::Trap(trap) = result {
return Err(InterpreterResult::Trap(trap));
}

if let InstructionResult::UnusedGas(ref gas) = result {
self.gasometer.as_mut().expect(GASOMETER_PROOF).current_gas = self.gasometer.as_mut().expect(GASOMETER_PROOF).current_gas + *gas;
Expand All @@ -296,9 +376,8 @@ impl<Cost: CostType> Interpreter<Cost> {
if self.do_trace {
ext.trace_executed(
self.gasometer.as_mut().expect(GASOMETER_PROOF).current_gas.as_u256(),
self.stack.peek_top(info.ret),
mem_written.map(|(o, s)| (o, &(self.mem[o..o+s]))),
store_written,
self.stack.peek_top(self.last_stack_ret_len),
&self.mem,
);
}

Expand Down Expand Up @@ -451,21 +530,24 @@ impl<Cost: CostType> Interpreter<Cost> {

let contract_code = self.mem.read_slice(init_off, init_size);

let create_result = ext.create(&create_gas.as_u256(), &endowment, contract_code, address_scheme);
let create_result = ext.create(&create_gas.as_u256(), &endowment, contract_code, address_scheme, true);
return match create_result {
ContractCreateResult::Created(address, gas_left) => {
Ok(ContractCreateResult::Created(address, gas_left)) => {
self.stack.push(address_to_u256(address));
Ok(InstructionResult::UnusedGas(Cost::from_u256(gas_left).expect("Gas left cannot be greater.")))
},
ContractCreateResult::Reverted(gas_left, return_data) => {
Ok(ContractCreateResult::Reverted(gas_left, return_data)) => {
self.stack.push(U256::zero());
self.return_data = return_data;
Ok(InstructionResult::UnusedGas(Cost::from_u256(gas_left).expect("Gas left cannot be greater.")))
},
ContractCreateResult::Failed => {
Ok(ContractCreateResult::Failed) => {
self.stack.push(U256::zero());
Ok(InstructionResult::Ok)
},
Err(trap) => {
Ok(InstructionResult::Trap(trap))
},
};
},
instructions::CALL | instructions::CALLCODE | instructions::DELEGATECALL | instructions::STATICCALL => {
Expand Down Expand Up @@ -524,32 +606,37 @@ impl<Cost: CostType> Interpreter<Cost> {

let call_result = {
let input = self.mem.read_slice(in_off, in_size);
ext.call(&call_gas.as_u256(), sender_address, receive_address, value, input, &code_address, call_type)
ext.call(&call_gas.as_u256(), sender_address, receive_address, value, input, &code_address, call_type, true)
};

let output = self.mem.writeable_slice(out_off, out_size);
self.resume_output_range = Some((out_off, out_size));

return match call_result {
MessageCallResult::Success(gas_left, data) => {
Ok(MessageCallResult::Success(gas_left, data)) => {
let output = self.mem.writeable_slice(out_off, out_size);
let len = cmp::min(output.len(), data.len());
(&mut output[..len]).copy_from_slice(&data[..len]);

self.stack.push(U256::one());
self.return_data = data;
Ok(InstructionResult::UnusedGas(Cost::from_u256(gas_left).expect("Gas left cannot be greater than current one")))
},
MessageCallResult::Reverted(gas_left, data) => {
Ok(MessageCallResult::Reverted(gas_left, data)) => {
let output = self.mem.writeable_slice(out_off, out_size);
let len = cmp::min(output.len(), data.len());
(&mut output[..len]).copy_from_slice(&data[..len]);

self.stack.push(U256::zero());
self.return_data = data;
Ok(InstructionResult::UnusedGas(Cost::from_u256(gas_left).expect("Gas left cannot be greater than current one")))
},
MessageCallResult::Failed => {
Ok(MessageCallResult::Failed) => {
self.stack.push(U256::zero());
Ok(InstructionResult::Ok)
},
Err(trap) => {
Ok(InstructionResult::Trap(trap))
},
};
},
instructions::RETURN => {
Expand Down Expand Up @@ -1095,10 +1182,10 @@ mod tests {
use rustc_hex::FromHex;
use vmtype::VMType;
use factory::Factory;
use vm::{self, Vm, ActionParams, ActionValue};
use vm::{self, Exec, ActionParams, ActionValue};
use vm::tests::{FakeExt, test_finalize};

fn interpreter(params: ActionParams, ext: &vm::Ext) -> Box<Vm> {
fn interpreter(params: ActionParams, ext: &vm::Ext) -> Box<Exec> {
Factory::new(VMType::Interpreter, 1).create(params, ext.schedule(), ext.depth())
}

Expand All @@ -1118,7 +1205,7 @@ mod tests {

let gas_left = {
let mut vm = interpreter(params, &ext);
test_finalize(vm.exec(&mut ext)).unwrap()
test_finalize(vm.exec(&mut ext).ok().unwrap()).unwrap()
};

assert_eq!(ext.calls.len(), 1);
Expand All @@ -1140,7 +1227,7 @@ mod tests {

let err = {
let mut vm = interpreter(params, &ext);
test_finalize(vm.exec(&mut ext)).err().unwrap()
test_finalize(vm.exec(&mut ext).ok().unwrap()).err().unwrap()
};

assert_eq!(err, ::vm::Error::OutOfBounds);
Expand Down
Loading

0 comments on commit 1e9aebb

Please sign in to comment.