Resumable EVM and heap-allocated callstack (#9360)

* 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
This commit is contained in:
Wei Tang
2018-10-02 22:33:19 +08:00
committed by GitHub
parent 61ec361182
commit 1e9aebbc86
24 changed files with 1465 additions and 804 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -25,9 +25,8 @@ use executive::*;
use vm::{
self, ActionParams, ActionValue, EnvInfo, CallType, Schedule,
Ext, ContractCreateResult, MessageCallResult, CreateContractAddress,
ReturnData
ReturnData, TrapKind
};
use evm::FinalizationResult;
use transaction::UNSIGNED_SENDER;
use trace::{Tracer, VMTracer};
@@ -67,7 +66,8 @@ pub struct Externalities<'a, T: 'a, V: 'a, B: 'a> {
state: &'a mut State<B>,
env_info: &'a EnvInfo,
depth: usize,
origin_info: OriginInfo,
stack_depth: usize,
origin_info: &'a OriginInfo,
substate: &'a mut Substate,
machine: &'a Machine,
schedule: &'a Schedule,
@@ -87,7 +87,8 @@ impl<'a, T: 'a, V: 'a, B: 'a> Externalities<'a, T, V, B>
machine: &'a Machine,
schedule: &'a Schedule,
depth: usize,
origin_info: OriginInfo,
stack_depth: usize,
origin_info: &'a OriginInfo,
substate: &'a mut Substate,
output: OutputPolicy,
tracer: &'a mut T,
@@ -98,6 +99,7 @@ impl<'a, T: 'a, V: 'a, B: 'a> Externalities<'a, T, V, B>
state: state,
env_info: env_info,
depth: depth,
stack_depth: stack_depth,
origin_info: origin_info,
substate: substate,
machine: machine,
@@ -176,7 +178,7 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for Externalities<'a, T, V, B>
};
let mut ex = Executive::new(self.state, self.env_info, self.machine, self.schedule);
let r = ex.call(params, self.substate, self.tracer, self.vm_tracer);
let r = ex.call_with_crossbeam(params, self.substate, self.stack_depth + 1, self.tracer, self.vm_tracer);
let output = match &r {
Ok(ref r) => H256::from(&r.return_data[..32]),
_ => H256::new(),
@@ -206,14 +208,15 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for Externalities<'a, T, V, B>
gas: &U256,
value: &U256,
code: &[u8],
address_scheme: CreateContractAddress
) -> ContractCreateResult {
address_scheme: CreateContractAddress,
trap: bool,
) -> ::std::result::Result<ContractCreateResult, TrapKind> {
// create new contract address
let (address, code_hash) = match self.state.nonce(&self.origin_info.address) {
Ok(nonce) => contract_address(address_scheme, &self.origin_info.address, &nonce, &code),
Err(e) => {
debug!(target: "ext", "Database corruption encountered: {:?}", e);
return ContractCreateResult::Failed
return Ok(ContractCreateResult::Failed)
}
};
@@ -237,23 +240,19 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for Externalities<'a, T, V, B>
if !self.schedule.keep_unsigned_nonce || params.sender != UNSIGNED_SENDER {
if let Err(e) = self.state.inc_nonce(&self.origin_info.address) {
debug!(target: "ext", "Database corruption encountered: {:?}", e);
return ContractCreateResult::Failed
return Ok(ContractCreateResult::Failed)
}
}
}
let mut ex = Executive::from_parent(self.state, self.env_info, self.machine, self.schedule, self.depth, self.static_flag);
if trap {
return Err(TrapKind::Create(params, address));
}
// TODO: handle internal error separately
match ex.create(params, self.substate, self.tracer, self.vm_tracer) {
Ok(FinalizationResult{ gas_left, apply_state: true, .. }) => {
self.substate.contracts_created.push(address.clone());
ContractCreateResult::Created(address, gas_left)
},
Ok(FinalizationResult{ gas_left, apply_state: false, return_data }) => {
ContractCreateResult::Reverted(gas_left, return_data)
},
_ => ContractCreateResult::Failed,
}
let mut ex = Executive::from_parent(self.state, self.env_info, self.machine, self.schedule, self.depth, self.static_flag);
let out = ex.create_with_crossbeam(params, self.substate, self.stack_depth + 1, self.tracer, self.vm_tracer);
Ok(into_contract_create_result(out, &address, self.substate))
}
fn call(
@@ -264,8 +263,9 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for Externalities<'a, T, V, B>
value: Option<U256>,
data: &[u8],
code_address: &Address,
call_type: CallType
) -> MessageCallResult {
call_type: CallType,
trap: bool,
) -> ::std::result::Result<MessageCallResult, TrapKind> {
trace!(target: "externalities", "call");
let code_res = self.state.code(code_address)
@@ -273,7 +273,7 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for Externalities<'a, T, V, B>
let (code, code_hash) = match code_res {
Ok((code, hash)) => (code, hash),
Err(_) => return MessageCallResult::Failed,
Err(_) => return Ok(MessageCallResult::Failed),
};
let mut params = ActionParams {
@@ -295,13 +295,13 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for Externalities<'a, T, V, B>
params.value = ActionValue::Transfer(value);
}
let mut ex = Executive::from_parent(self.state, self.env_info, self.machine, self.schedule, self.depth, self.static_flag);
match ex.call(params, self.substate, self.tracer, self.vm_tracer) {
Ok(FinalizationResult{ gas_left, return_data, apply_state: true }) => MessageCallResult::Success(gas_left, return_data),
Ok(FinalizationResult{ gas_left, return_data, apply_state: false }) => MessageCallResult::Reverted(gas_left, return_data),
_ => MessageCallResult::Failed
if trap {
return Err(TrapKind::Call(params));
}
let mut ex = Executive::from_parent(self.state, self.env_info, self.machine, self.schedule, self.depth, self.static_flag);
let out = ex.call_with_crossbeam(params, self.substate, self.stack_depth + 1, self.tracer, self.vm_tracer);
Ok(into_message_call_result(out))
}
fn extcode(&self, address: &Address) -> vm::Result<Option<Arc<Bytes>>> {
@@ -406,12 +406,12 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for Externalities<'a, T, V, B>
self.vm_tracer.trace_next_instruction(pc, instruction, current_gas)
}
fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: U256) {
self.vm_tracer.trace_prepare_execute(pc, instruction, gas_cost)
fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: U256, mem_written: Option<(usize, usize)>, store_written: Option<(U256, U256)>) {
self.vm_tracer.trace_prepare_execute(pc, instruction, gas_cost, mem_written, store_written)
}
fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem_diff: Option<(usize, &[u8])>, store_diff: Option<(U256, U256)>) {
self.vm_tracer.trace_executed(gas_used, stack_push, mem_diff, store_diff)
fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem: &[u8]) {
self.vm_tracer.trace_executed(gas_used, stack_push, mem)
}
}
@@ -480,8 +480,9 @@ mod tests {
let state = &mut setup.state;
let mut tracer = NoopTracer;
let mut vm_tracer = NoopVMTracer;
let origin_info = get_test_origin();
let ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
let ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, 0, &origin_info, &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
assert_eq!(ext.env_info().number, 100);
}
@@ -492,8 +493,9 @@ mod tests {
let state = &mut setup.state;
let mut tracer = NoopTracer;
let mut vm_tracer = NoopVMTracer;
let origin_info = get_test_origin();
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, 0, &origin_info, &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
let hash = ext.blockhash(&"0000000000000000000000000000000000000000000000000000000000120000".parse::<U256>().unwrap());
@@ -516,8 +518,9 @@ mod tests {
let state = &mut setup.state;
let mut tracer = NoopTracer;
let mut vm_tracer = NoopVMTracer;
let origin_info = get_test_origin();
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, 0, &origin_info, &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
let hash = ext.blockhash(&"0000000000000000000000000000000000000000000000000000000000120000".parse::<U256>().unwrap());
@@ -531,8 +534,9 @@ mod tests {
let state = &mut setup.state;
let mut tracer = NoopTracer;
let mut vm_tracer = NoopVMTracer;
let origin_info = get_test_origin();
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, 0, &origin_info, &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
// this should panic because we have no balance on any account
ext.call(
@@ -542,8 +546,9 @@ mod tests {
Some("0000000000000000000000000000000000000000000000000000000000150000".parse::<U256>().unwrap()),
&[],
&Address::new(),
CallType::Call
);
CallType::Call,
false,
).ok().unwrap();
}
#[test]
@@ -555,9 +560,10 @@ mod tests {
let state = &mut setup.state;
let mut tracer = NoopTracer;
let mut vm_tracer = NoopVMTracer;
let origin_info = get_test_origin();
{
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, 0, &origin_info, &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
ext.log(log_topics, &log_data).unwrap();
}
@@ -572,9 +578,10 @@ mod tests {
let state = &mut setup.state;
let mut tracer = NoopTracer;
let mut vm_tracer = NoopVMTracer;
let origin_info = get_test_origin();
{
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, 0, &origin_info, &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
ext.suicide(refund_account).unwrap();
}
@@ -589,11 +596,12 @@ mod tests {
let state = &mut setup.state;
let mut tracer = NoopTracer;
let mut vm_tracer = NoopVMTracer;
let origin_info = get_test_origin();
let address = {
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
match ext.create(&U256::max_value(), &U256::zero(), &[], CreateContractAddress::FromSenderAndNonce) {
ContractCreateResult::Created(address, _) => address,
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, 0, &origin_info, &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
match ext.create(&U256::max_value(), &U256::zero(), &[], CreateContractAddress::FromSenderAndNonce, false) {
Ok(ContractCreateResult::Created(address, _)) => address,
_ => panic!("Test create failed; expected Created, got Failed/Reverted."),
}
};
@@ -609,12 +617,13 @@ mod tests {
let state = &mut setup.state;
let mut tracer = NoopTracer;
let mut vm_tracer = NoopVMTracer;
let origin_info = get_test_origin();
let address = {
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
let mut ext = Externalities::new(state, &setup.env_info, &setup.machine, &setup.schedule, 0, 0, &origin_info, &mut setup.sub_state, OutputPolicy::InitContract, &mut tracer, &mut vm_tracer, false);
match ext.create(&U256::max_value(), &U256::zero(), &[], CreateContractAddress::FromSenderSaltAndCodeHash(H256::default())) {
ContractCreateResult::Created(address, _) => address,
match ext.create(&U256::max_value(), &U256::zero(), &[], CreateContractAddress::FromSenderSaltAndCodeHash(H256::default()), false) {
Ok(ContractCreateResult::Created(address, _)) => address,
_ => panic!("Test create failed; expected Created, got Failed/Reverted."),
}
};

View File

@@ -18,7 +18,7 @@ use trie::TrieFactory;
use ethtrie::RlpCodec;
use account_db::Factory as AccountFactory;
use evm::{Factory as EvmFactory, VMType};
use vm::{Vm, ActionParams, Schedule};
use vm::{Exec, ActionParams, Schedule};
use wasm::WasmInterpreter;
use keccak_hasher::KeccakHasher;
@@ -31,7 +31,7 @@ pub struct VmFactory {
}
impl VmFactory {
pub fn create(&self, params: ActionParams, schedule: &Schedule, depth: usize) -> Box<Vm> {
pub fn create(&self, params: ActionParams, schedule: &Schedule, depth: usize) -> Box<Exec> {
if schedule.wasm.is_some() && params.code.as_ref().map_or(false, |code| code.len() > 4 && &code[0..4] == WASM_MAGIC_NUMBER) {
Box::new(WasmInterpreter::new(params))
} else {

View File

@@ -88,7 +88,7 @@ impl<'a, T: 'a, V: 'a, B: 'a> TestExt<'a, T, V, B>
machine: &'a Machine,
schedule: &'a Schedule,
depth: usize,
origin_info: OriginInfo,
origin_info: &'a OriginInfo,
substate: &'a mut Substate,
output: OutputPolicy,
address: Address,
@@ -98,7 +98,7 @@ impl<'a, T: 'a, V: 'a, B: 'a> TestExt<'a, T, V, B>
let static_call = false;
Ok(TestExt {
nonce: state.nonce(&address)?,
ext: Externalities::new(state, info, machine, schedule, depth, origin_info, substate, output, tracer, vm_tracer, static_call),
ext: Externalities::new(state, info, machine, schedule, depth, 0, origin_info, substate, output, tracer, vm_tracer, static_call),
callcreates: vec![],
sender: address,
})
@@ -140,7 +140,14 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for TestExt<'a, T, V, B>
self.ext.blockhash(number)
}
fn create(&mut self, gas: &U256, value: &U256, code: &[u8], address: CreateContractAddress) -> ContractCreateResult {
fn create(
&mut self,
gas: &U256,
value: &U256,
code: &[u8],
address: CreateContractAddress,
_trap: bool
) -> Result<ContractCreateResult, vm::TrapKind> {
self.callcreates.push(CallCreate {
data: code.to_vec(),
destination: None,
@@ -148,25 +155,27 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for TestExt<'a, T, V, B>
value: *value
});
let contract_address = contract_address(address, &self.sender, &self.nonce, &code).0;
ContractCreateResult::Created(contract_address, *gas)
Ok(ContractCreateResult::Created(contract_address, *gas))
}
fn call(&mut self,
fn call(
&mut self,
gas: &U256,
_sender_address: &Address,
receive_address: &Address,
value: Option<U256>,
data: &[u8],
_code_address: &Address,
_call_type: CallType
) -> MessageCallResult {
_call_type: CallType,
_trap: bool
) -> Result<MessageCallResult, vm::TrapKind> {
self.callcreates.push(CallCreate {
data: data.to_vec(),
destination: Some(receive_address.clone()),
gas_limit: *gas,
value: value.unwrap()
});
MessageCallResult::Success(*gas, ReturnData::empty())
Ok(MessageCallResult::Success(*gas, ReturnData::empty()))
}
fn extcode(&self, address: &Address) -> vm::Result<Option<Arc<Bytes>>> {
@@ -270,6 +279,7 @@ fn do_json_test_for<H: FnMut(&str, HookType)>(vm_type: &VMType, json_data: &[u8]
let mut tracer = NoopTracer;
let mut vm_tracer = NoopVMTracer;
let vm_factory = state.vm_factory();
let origin_info = OriginInfo::from(&params);
// execute
let (res, callcreates) = {
@@ -280,7 +290,7 @@ fn do_json_test_for<H: FnMut(&str, HookType)>(vm_type: &VMType, json_data: &[u8]
&machine,
&schedule,
0,
OriginInfo::from(&params),
&origin_info,
&mut substate,
OutputPolicy::Return,
params.address.clone(),
@@ -288,7 +298,7 @@ fn do_json_test_for<H: FnMut(&str, HookType)>(vm_type: &VMType, json_data: &[u8]
&mut vm_tracer,
));
let mut evm = vm_factory.create(params, &schedule, 0);
let res = evm.exec(&mut ex);
let res = evm.exec(&mut ex).ok().expect("TestExt never trap; resume error never happens; qed");
// a return in finalize will not alter callcreates
let callcreates = ex.callcreates.clone();
(res.finalize(ex), callcreates)

View File

@@ -15,7 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Trace database.
use std::collections::{HashMap, VecDeque};
use std::collections::HashMap;
use std::sync::Arc;
use blockchain::{BlockChainDB};
use heapsize::HeapSizeOf;
@@ -227,13 +227,12 @@ impl<T> TraceDatabase for TraceDB<T> where T: DatabaseExtras {
}
fn trace(&self, block_number: BlockNumber, tx_position: usize, trace_position: Vec<usize>) -> Option<LocalizedTrace> {
let trace_position_deq = VecDeque::from(trace_position);
self.extras.block_hash(block_number)
.and_then(|block_hash| self.transactions_traces(&block_hash)
.and_then(|traces| traces.into_iter().nth(tx_position))
.map(Into::<Vec<FlatTrace>>::into)
// this may and should be optimized
.and_then(|traces| traces.into_iter().find(|trace| trace.trace_address == trace_position_deq))
.and_then(|traces| traces.into_iter().find(|trace| trace.trace_address == trace_position))
.map(|trace| {
let tx_hash = self.extras.transaction_hash(block_number, tx_position)
.expect("Expected to find transaction hash. Database is probably corrupted");

View File

@@ -17,159 +17,175 @@
//! Simple executive tracer.
use ethereum_types::{U256, Address};
use vm::ActionParams;
use vm::{Error as VmError, ActionParams};
use trace::trace::{Call, Create, Action, Res, CreateResult, CallResult, VMTrace, VMOperation, VMExecutedOperation, MemoryDiff, StorageDiff, Suicide, Reward, RewardType};
use trace::{Tracer, VMTracer, FlatTrace, TraceError};
use trace::{Tracer, VMTracer, FlatTrace};
/// Simple executive tracer. Traces all calls and creates. Ignores delegatecalls.
#[derive(Default)]
pub struct ExecutiveTracer {
traces: Vec<FlatTrace>,
}
fn top_level_subtraces(traces: &[FlatTrace]) -> usize {
traces.iter().filter(|t| t.trace_address.is_empty()).count()
}
fn prefix_subtrace_addresses(mut traces: Vec<FlatTrace>) -> Vec<FlatTrace> {
// input traces are expected to be ordered like
// []
// [0]
// [0, 0]
// [0, 1]
// []
// [0]
//
// so they can be transformed to
//
// [0]
// [0, 0]
// [0, 0, 0]
// [0, 0, 1]
// [1]
// [1, 0]
let mut current_subtrace_index = 0;
let mut first = true;
for trace in &mut traces {
match (first, trace.trace_address.is_empty()) {
(true, _) => first = false,
(_, true) => current_subtrace_index += 1,
_ => {}
}
trace.trace_address.push_front(current_subtrace_index);
}
traces
}
#[test]
fn should_prefix_address_properly() {
use super::trace::{Action, Res, Suicide};
let f = |v: Vec<usize>| FlatTrace {
action: Action::Suicide(Suicide {
address: Default::default(),
balance: Default::default(),
refund_address: Default::default(),
}),
result: Res::None,
subtraces: 0,
trace_address: v.into_iter().collect(),
};
let t = vec![vec![], vec![0], vec![0, 0], vec![0], vec![], vec![], vec![0], vec![]].into_iter().map(&f).collect();
let t = prefix_subtrace_addresses(t);
assert_eq!(t, vec![vec![0], vec![0, 0], vec![0, 0, 0], vec![0, 0], vec![1], vec![2], vec![2, 0], vec![3]].into_iter().map(&f).collect::<Vec<_>>());
index_stack: Vec<usize>,
vecindex_stack: Vec<usize>,
sublen_stack: Vec<usize>,
skip_one: bool,
}
impl Tracer for ExecutiveTracer {
type Output = FlatTrace;
fn prepare_trace_call(&self, params: &ActionParams) -> Option<Call> {
Some(Call::from(params.clone()))
}
fn prepare_trace_call(&mut self, params: &ActionParams, depth: usize, is_builtin: bool) {
assert!(!self.skip_one, "skip_one is used only for builtin contracts that do not have subsequent calls; in prepare_trace_call it cannot be true; qed");
fn prepare_trace_create(&self, params: &ActionParams) -> Option<Create> {
Some(Create::from(params.clone()))
}
if depth != 0 && is_builtin && params.value.value() == U256::zero() {
self.skip_one = true;
return;
}
if let Some(parentlen) = self.sublen_stack.last_mut() {
*parentlen += 1;
}
fn trace_call(&mut self, call: Option<Call>, gas_used: U256, output: &[u8], subs: Vec<FlatTrace>) {
let trace = FlatTrace {
trace_address: Default::default(),
subtraces: top_level_subtraces(&subs),
action: Action::Call(call.expect("self.prepare_trace_call().is_some(): so we must be tracing: qed")),
trace_address: self.index_stack.clone(),
subtraces: self.sublen_stack.last().cloned().unwrap_or(0),
action: Action::Call(Call::from(params.clone())),
result: Res::Call(CallResult {
gas_used: gas_used,
output: output.into()
gas_used: U256::zero(),
output: Vec::new()
}),
};
debug!(target: "trace", "Traced call {:?}", trace);
self.vecindex_stack.push(self.traces.len());
self.traces.push(trace);
self.traces.extend(prefix_subtrace_addresses(subs));
self.index_stack.push(0);
self.sublen_stack.push(0);
}
fn trace_create(&mut self, create: Option<Create>, gas_used: U256, code: &[u8], address: Address, subs: Vec<FlatTrace>) {
fn prepare_trace_create(&mut self, params: &ActionParams) {
assert!(!self.skip_one, "skip_one is used only for builtin contracts that do not have subsequent calls; in prepare_trace_create it cannot be true; qed");
if let Some(parentlen) = self.sublen_stack.last_mut() {
*parentlen += 1;
}
let trace = FlatTrace {
subtraces: top_level_subtraces(&subs),
action: Action::Create(create.expect("self.prepare_trace_create().is_some(): so we must be tracing: qed")),
trace_address: self.index_stack.clone(),
subtraces: self.sublen_stack.last().cloned().unwrap_or(0),
action: Action::Create(Create::from(params.clone())),
result: Res::Create(CreateResult {
gas_used: gas_used,
code: code.into(),
address: address
gas_used: U256::zero(),
code: Vec::new(),
address: Address::default(),
}),
trace_address: Default::default(),
};
debug!(target: "trace", "Traced create {:?}", trace);
self.vecindex_stack.push(self.traces.len());
self.traces.push(trace);
self.traces.extend(prefix_subtrace_addresses(subs));
self.index_stack.push(0);
self.sublen_stack.push(0);
}
fn trace_failed_call(&mut self, call: Option<Call>, subs: Vec<FlatTrace>, error: TraceError) {
let trace = FlatTrace {
trace_address: Default::default(),
subtraces: top_level_subtraces(&subs),
action: Action::Call(call.expect("self.prepare_trace_call().is_some(): so we must be tracing: qed")),
result: Res::FailedCall(error),
};
debug!(target: "trace", "Traced failed call {:?}", trace);
self.traces.push(trace);
self.traces.extend(prefix_subtrace_addresses(subs));
fn done_trace_call(&mut self, gas_used: U256, output: &[u8]) {
if self.skip_one {
self.skip_one = false;
return;
}
let vecindex = self.vecindex_stack.pop().expect("Executive invoked prepare_trace_call before this function; vecindex_stack is never empty; qed");
let sublen = self.sublen_stack.pop().expect("Executive invoked prepare_trace_call before this function; sublen_stack is never empty; qed");
self.index_stack.pop();
self.traces[vecindex].result = Res::Call(CallResult {
gas_used,
output: output.into(),
});
self.traces[vecindex].subtraces = sublen;
if let Some(index) = self.index_stack.last_mut() {
*index += 1;
}
}
fn trace_failed_create(&mut self, create: Option<Create>, subs: Vec<FlatTrace>, error: TraceError) {
let trace = FlatTrace {
subtraces: top_level_subtraces(&subs),
action: Action::Create(create.expect("self.prepare_trace_create().is_some(): so we must be tracing: qed")),
result: Res::FailedCreate(error),
trace_address: Default::default(),
fn done_trace_create(&mut self, gas_used: U256, code: &[u8], address: Address) {
assert!(!self.skip_one, "skip_one is only set with prepare_trace_call for builtin contracts with no subsequent calls; skip_one cannot be true after the same level prepare_trace_create; qed");
let vecindex = self.vecindex_stack.pop().expect("Executive invoked prepare_trace_create before this function; vecindex_stack is never empty; qed");
let sublen = self.sublen_stack.pop().expect("Executive invoked prepare_trace_create before this function; sublen_stack is never empty; qed");
self.index_stack.pop();
self.traces[vecindex].result = Res::Create(CreateResult {
gas_used, address,
code: code.into(),
});
self.traces[vecindex].subtraces = sublen;
if let Some(index) = self.index_stack.last_mut() {
*index += 1;
}
}
fn done_trace_failed(&mut self, error: &VmError) {
if self.skip_one {
self.skip_one = false;
return;
}
let vecindex = self.vecindex_stack.pop().expect("Executive invoked prepare_trace_create/call before this function; vecindex_stack is never empty; qed");
let sublen = self.sublen_stack.pop().expect("Executive invoked prepare_trace_create/call before this function; vecindex_stack is never empty; qed");
self.index_stack.pop();
let is_create = match self.traces[vecindex].action {
Action::Create(_) => true,
_ => false,
};
debug!(target: "trace", "Traced failed create {:?}", trace);
self.traces.push(trace);
self.traces.extend(prefix_subtrace_addresses(subs));
if is_create {
self.traces[vecindex].result = Res::FailedCreate(error.into());
} else {
self.traces[vecindex].result = Res::FailedCall(error.into());
}
self.traces[vecindex].subtraces = sublen;
if let Some(index) = self.index_stack.last_mut() {
*index += 1;
}
}
fn trace_suicide(&mut self, address: Address, balance: U256, refund_address: Address) {
if let Some(parentlen) = self.sublen_stack.last_mut() {
*parentlen += 1;
}
let trace = FlatTrace {
subtraces: 0,
action: Action::Suicide(Suicide { address, refund_address, balance } ),
result: Res::None,
trace_address: Default::default(),
trace_address: self.index_stack.clone(),
};
debug!(target: "trace", "Traced suicide {:?}", trace);
self.traces.push(trace);
if let Some(index) = self.index_stack.last_mut() {
*index += 1;
}
}
fn trace_reward(&mut self, author: Address, value: U256, reward_type: RewardType) {
if let Some(parentlen) = self.sublen_stack.last_mut() {
*parentlen += 1;
}
let trace = FlatTrace {
subtraces: 0,
action: Action::Reward(Reward { author, value, reward_type } ),
result: Res::None,
trace_address: Default::default(),
trace_address: self.index_stack.clone(),
};
debug!(target: "trace", "Traced reward {:?}", trace);
self.traces.push(trace);
}
fn subtracer(&self) -> Self {
ExecutiveTracer::default()
if let Some(index) = self.index_stack.last_mut() {
*index += 1;
}
}
fn drain(self) -> Vec<FlatTrace> {
@@ -180,6 +196,9 @@ impl Tracer for ExecutiveTracer {
/// Simple VM tracer. Traces all operations.
pub struct ExecutiveVMTracer {
data: VMTrace,
depth: usize,
last_mem_written: Option<(usize, usize)>,
last_store_written: Option<(U256, U256)>,
}
impl ExecutiveVMTracer {
@@ -191,7 +210,18 @@ impl ExecutiveVMTracer {
code: vec![],
operations: vec![Default::default()], // prefill with a single entry so that prepare_subtrace can get the parent_step
subs: vec![],
}
},
depth: 0,
last_mem_written: None,
last_store_written: None,
}
}
fn with_trace_in_depth<F: Fn(&mut VMTrace)>(trace: &mut VMTrace, depth: usize, f: F) {
if depth == 0 {
f(trace);
} else {
Self::with_trace_in_depth(trace.subs.last_mut().expect("self.depth is incremented with prepare_subtrace; a subtrace is always pushed; self.depth cannot be greater than subtrace stack; qed"), depth - 1, f);
}
}
}
@@ -201,37 +231,77 @@ impl VMTracer for ExecutiveVMTracer {
fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8, _current_gas: U256) -> bool { true }
fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: U256) {
self.data.operations.push(VMOperation {
pc: pc,
instruction: instruction,
gas_cost: gas_cost,
executed: None,
fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: U256, mem_written: Option<(usize, usize)>, store_written: Option<(U256, U256)>) {
Self::with_trace_in_depth(&mut self.data, self.depth, move |trace| {
trace.operations.push(VMOperation {
pc: pc,
instruction: instruction,
gas_cost: gas_cost,
executed: None,
});
});
self.last_mem_written = mem_written;
self.last_store_written = store_written;
}
fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem: &[u8]) {
let mem_diff = self.last_mem_written.take().map(|(o, s)| (o, &(mem[o..o+s])));
let store_diff = self.last_store_written.take();
Self::with_trace_in_depth(&mut self.data, self.depth, move |trace| {
let ex = VMExecutedOperation {
gas_used: gas_used,
stack_push: stack_push.iter().cloned().collect(),
mem_diff: mem_diff.map(|(s, r)| MemoryDiff { offset: s, data: r.iter().cloned().collect() }),
store_diff: store_diff.map(|(l, v)| StorageDiff { location: l, value: v }),
};
trace.operations.last_mut().expect("trace_executed is always called after a trace_prepare_execute; trace.operations cannot be empty; qed").executed = Some(ex);
});
}
fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem_diff: Option<(usize, &[u8])>, store_diff: Option<(U256, U256)>) {
let ex = VMExecutedOperation {
gas_used: gas_used,
stack_push: stack_push.iter().cloned().collect(),
mem_diff: mem_diff.map(|(s, r)| MemoryDiff{ offset: s, data: r.iter().cloned().collect() }),
store_diff: store_diff.map(|(l, v)| StorageDiff{ location: l, value: v }),
};
self.data.operations.last_mut().expect("trace_executed is always called after a trace_prepare_execute").executed = Some(ex);
fn prepare_subtrace(&mut self, code: &[u8]) {
Self::with_trace_in_depth(&mut self.data, self.depth, move |trace| {
let parent_step = trace.operations.len() - 1; // won't overflow since we must already have pushed an operation in trace_prepare_execute.
trace.subs.push(VMTrace {
parent_step,
code: code.to_vec(),
operations: vec![],
subs: vec![],
});
});
self.depth += 1;
}
fn prepare_subtrace(&self, code: &[u8]) -> Self {
ExecutiveVMTracer { data: VMTrace {
parent_step: self.data.operations.len() - 1, // won't overflow since we must already have pushed an operation in trace_prepare_execute.
code: code.to_vec(),
operations: vec![],
subs: vec![],
}}
}
fn done_subtrace(&mut self, sub: Self) {
self.data.subs.push(sub.data);
fn done_subtrace(&mut self) {
self.depth -= 1;
}
fn drain(mut self) -> Option<VMTrace> { self.data.subs.pop() }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_prefix_address_properly() {
let mut tracer = ExecutiveTracer::default();
tracer.prepare_trace_call(&ActionParams::default(), 0, false);
tracer.prepare_trace_call(&ActionParams::default(), 1, false);
tracer.prepare_trace_call(&ActionParams::default(), 2, false);
tracer.done_trace_call(U256::zero(), &[]);
tracer.prepare_trace_call(&ActionParams::default(), 2, false);
tracer.done_trace_call(U256::zero(), &[]);
tracer.prepare_trace_call(&ActionParams::default(), 2, false);
tracer.done_trace_call(U256::zero(), &[]);
tracer.done_trace_call(U256::zero(), &[]);
tracer.done_trace_call(U256::zero(), &[]);
let drained = tracer.drain();
assert!(drained[0].trace_address.len() == 0);
assert_eq!(&drained[1].trace_address, &[0]);
assert_eq!(&drained[2].trace_address, &[0, 0]);
assert_eq!(&drained[3].trace_address, &[0, 1]);
assert_eq!(&drained[4].trace_address, &[0, 2]);
}
}

View File

@@ -38,8 +38,7 @@ pub use self::types::filter::{Filter, AddressesFilter};
use ethereum_types::{H256, U256, Address};
use kvdb::DBTransaction;
use self::trace::{Call, Create};
use vm::ActionParams;
use vm::{Error as VmError, ActionParams};
use header::BlockNumber;
/// This trait is used by executive to build traces.
@@ -47,48 +46,20 @@ pub trait Tracer: Send {
/// Data returned when draining the Tracer.
type Output;
/// Prepares call trace for given params. Noop tracer should return None.
///
/// This is called before a call has been executed.
fn prepare_trace_call(&self, params: &ActionParams) -> Option<Call>;
/// Prepares call trace for given params. Would panic if prepare/done_trace are not balanced.
fn prepare_trace_call(&mut self, params: &ActionParams, depth: usize, is_builtin: bool);
/// Prepares create trace for given params. Noop tracer should return None.
///
/// This is called before a create has been executed.
fn prepare_trace_create(&self, params: &ActionParams) -> Option<Create>;
/// Prepares create trace for given params. Would panic if prepare/done_trace are not balanced.
fn prepare_trace_create(&mut self, params: &ActionParams);
/// Stores trace call info.
///
/// This is called after a call has completed successfully.
fn trace_call(
&mut self,
call: Option<Call>,
gas_used: U256,
output: &[u8],
subs: Vec<Self::Output>,
);
/// Finishes a successful call trace. Would panic if prepare/done_trace are not balanced.
fn done_trace_call(&mut self, gas_used: U256, output: &[u8]);
/// Stores trace create info.
///
/// This is called after a create has completed successfully.
fn trace_create(
&mut self,
create: Option<Create>,
gas_used: U256,
code: &[u8],
address: Address,
subs: Vec<Self::Output>
);
/// Finishes a successful create trace. Would panic if prepare/done_trace are not balanced.
fn done_trace_create(&mut self, gas_used: U256, code: &[u8], address: Address);
/// Stores failed call trace.
///
/// This is called after a call has completed erroneously.
fn trace_failed_call(&mut self, call: Option<Call>, subs: Vec<Self::Output>, error: TraceError);
/// Stores failed create trace.
///
/// This is called after a create has completed erroneously.
fn trace_failed_create(&mut self, create: Option<Create>, subs: Vec<Self::Output>, error: TraceError);
/// Finishes a failed trace. Would panic if prepare/done_trace are not balanced.
fn done_trace_failed(&mut self, error: &VmError);
/// Stores suicide info.
fn trace_suicide(&mut self, address: Address, balance: U256, refund_address: Address);
@@ -96,9 +67,6 @@ pub trait Tracer: Send {
/// Stores reward info.
fn trace_reward(&mut self, author: Address, value: U256, reward_type: RewardType);
/// Spawn subtracer which will be used to trace deeper levels of execution.
fn subtracer(&self) -> Self where Self: Sized;
/// Consumes self and returns all traces.
fn drain(self) -> Vec<Self::Output>;
}
@@ -115,16 +83,16 @@ pub trait VMTracer: Send {
fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8, _current_gas: U256) -> bool { false }
/// Trace the preparation to execute a single valid instruction.
fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: U256) {}
fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: U256, _mem_written: Option<(usize, usize)>, _store_written: Option<(U256, U256)>) {}
/// Trace the finalised execution of a single valid instruction.
fn trace_executed(&mut self, _gas_used: U256, _stack_push: &[U256], _mem_diff: Option<(usize, &[u8])>, _store_diff: Option<(U256, U256)>) {}
fn trace_executed(&mut self, _gas_used: U256, _stack_push: &[U256], _mem: &[u8]) {}
/// Spawn subtracer which will be used to trace deeper levels of execution.
fn prepare_subtrace(&self, code: &[u8]) -> Self where Self: Sized;
fn prepare_subtrace(&mut self, _code: &[u8]) {}
/// Finalize subtracer.
fn done_subtrace(&mut self, sub: Self) where Self: Sized;
fn done_subtrace(&mut self) {}
/// Consumes self and returns the VM trace.
fn drain(self) -> Option<Self::Output>;

View File

@@ -17,9 +17,9 @@
//! Nonoperative tracer.
use ethereum_types::{U256, Address};
use vm::ActionParams;
use trace::{Tracer, VMTracer, FlatTrace, TraceError};
use trace::trace::{Call, Create, VMTrace, RewardType};
use vm::{Error as VmError, ActionParams};
use trace::{Tracer, VMTracer, FlatTrace};
use trace::trace::{VMTrace, RewardType};
/// Nonoperative tracer. Does not trace anything.
pub struct NoopTracer;
@@ -27,43 +27,14 @@ pub struct NoopTracer;
impl Tracer for NoopTracer {
type Output = FlatTrace;
fn prepare_trace_call(&self, _: &ActionParams) -> Option<Call> {
None
}
fn prepare_trace_create(&self, _: &ActionParams) -> Option<Create> {
None
}
fn trace_call(&mut self, call: Option<Call>, _: U256, _: &[u8], _: Vec<FlatTrace>) {
assert!(call.is_none(), "self.prepare_trace_call().is_none(): so we can't be tracing: qed");
}
fn trace_create(&mut self, create: Option<Create>, _: U256, _: &[u8], _: Address, _: Vec<FlatTrace>) {
assert!(create.is_none(), "self.prepare_trace_create().is_none(): so we can't be tracing: qed");
}
fn trace_failed_call(&mut self, call: Option<Call>, _: Vec<FlatTrace>, _: TraceError) {
assert!(call.is_none(), "self.prepare_trace_call().is_none(): so we can't be tracing: qed");
}
fn trace_failed_create(&mut self, create: Option<Create>, _: Vec<FlatTrace>, _: TraceError) {
assert!(create.is_none(), "self.prepare_trace_create().is_none(): so we can't be tracing: qed");
}
fn trace_suicide(&mut self, _address: Address, _balance: U256, _refund_address: Address) {
}
fn trace_reward(&mut self, _: Address, _: U256, _: RewardType) {
}
fn subtracer(&self) -> Self {
NoopTracer
}
fn drain(self) -> Vec<FlatTrace> {
vec![]
}
fn prepare_trace_call(&mut self, _: &ActionParams, _: usize, _: bool) { }
fn prepare_trace_create(&mut self, _: &ActionParams) { }
fn done_trace_call(&mut self, _: U256, _: &[u8]) { }
fn done_trace_create(&mut self, _: U256, _: &[u8], _: Address) { }
fn done_trace_failed(&mut self, _: &VmError) { }
fn trace_suicide(&mut self, _: Address, _: U256, _: Address) { }
fn trace_reward(&mut self, _: Address, _: U256, _: RewardType) { }
fn drain(self) -> Vec<FlatTrace> { vec![] }
}
/// Nonoperative VM tracer. Does not trace anything.
@@ -72,15 +43,5 @@ pub struct NoopVMTracer;
impl VMTracer for NoopVMTracer {
type Output = VMTrace;
fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8, _current_gas: U256) -> bool { false }
fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: U256) {}
fn trace_executed(&mut self, _gas_used: U256, _stack_push: &[U256], _mem_diff: Option<(usize, &[u8])>, _store_diff: Option<(U256, U256)>) {}
fn prepare_subtrace(&self, _code: &[u8]) -> Self { NoopVMTracer }
fn done_subtrace(&mut self, _sub: Self) {}
fn drain(self) -> Option<VMTrace> { None }
}

View File

@@ -16,7 +16,6 @@
//! Flat trace module
use std::collections::VecDeque;
use rlp::{Rlp, RlpStream, Decodable, Encodable, DecoderError};
use heapsize::HeapSizeOf;
use ethereum_types::Bloom;
@@ -36,7 +35,7 @@ pub struct FlatTrace {
/// Exact location of trace.
///
/// [index in root, index in first CALL, index in second CALL, ...]
pub trace_address: VecDeque<usize>,
pub trace_address: Vec<usize>,
}
impl FlatTrace {