VM tracing and JSON RPC endpoint for it. (#1169)

* Groundwork for basic VM tracing.

* RPC endpoint for VM tracing and ser/de types ready.

* Create VMTracer trait.

* Rearchitected VM tracing to reflect existing tracing.

Should more or less work now.

* Integrated VM tracing into JSONRPC.

* Fix ethcore module tests.

* Add tests for VM tracing.

* Fix consensus test code.

* Fix mock tests.

* Added VM trace information for post-execution stuff.

* Fix max-value calls and add "creates" field to getTransaction.

* Tests for VM tracing.

* Don't implement the trait with unimplemented.

* Remove invlaid comment.

* Fix tests.
This commit is contained in:
Gav Wood
2016-06-02 12:40:31 +02:00
parent 7ad9c73c75
commit b17581d7de
36 changed files with 742 additions and 172 deletions

View File

@@ -436,7 +436,7 @@ impl<V> Client<V> where V: Verifier {
}
impl<V> BlockChainClient for Client<V> where V: Verifier {
fn call(&self, t: &SignedTransaction) -> Result<Executed, ExecutionError> {
fn call(&self, t: &SignedTransaction, vm_tracing: bool) -> Result<Executed, ExecutionError> {
let header = self.block_header(BlockID::Latest).unwrap();
let view = HeaderView::new(&header);
let last_hashes = self.build_last_hashes(view.hash());
@@ -456,10 +456,10 @@ impl<V> BlockChainClient for Client<V> where V: Verifier {
ExecutionError::TransactionMalformed(message)
}));
let balance = state.balance(&sender);
// give the sender max balance
// give the sender a decent balance
state.sub_balance(&sender, &balance);
state.add_balance(&sender, &U256::max_value());
let options = TransactOptions { tracing: false, check_nonce: false };
state.add_balance(&sender, &(U256::from(1) << 200));
let options = TransactOptions { tracing: false, vm_tracing: vm_tracing, check_nonce: false };
Executive::new(&mut state, &env_info, self.engine.deref().deref(), &self.vm_factory).transact(t, options)
}

View File

@@ -157,7 +157,8 @@ pub trait BlockChainClient : Sync + Send {
fn logs(&self, filter: Filter) -> Vec<LocalizedLogEntry>;
/// Makes a non-persistent transaction call.
fn call(&self, t: &SignedTransaction) -> Result<Executed, ExecutionError>;
// TODO: should be able to accept blockchain location for call.
fn call(&self, t: &SignedTransaction, vm_tracing: bool) -> Result<Executed, ExecutionError>;
/// Returns EvmFactory.
fn vm_factory(&self) -> &EvmFactory;

View File

@@ -251,7 +251,7 @@ impl MiningBlockChainClient for TestBlockChainClient {
}
impl BlockChainClient for TestBlockChainClient {
fn call(&self, _t: &SignedTransaction) -> Result<Executed, ExecutionError> {
fn call(&self, _t: &SignedTransaction, _vm_tracing: bool) -> Result<Executed, ExecutionError> {
Ok(self.execution_result.read().unwrap().clone().unwrap())
}

View File

@@ -105,4 +105,10 @@ pub trait Ext {
/// Increments sstore refunds count by 1.
fn inc_sstore_clears(&mut self);
/// Prepare to trace an operation. Passthrough for the VM trace.
fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: &U256) -> bool { false }
/// Trace the finalised execution of a single instruction.
fn trace_executed(&mut self, _gas_used: U256, _stack_push: &[U256], _mem_diff: Option<(usize, &[u8])>, _store_diff: Option<(U256, U256)>) {}
}

View File

@@ -124,6 +124,7 @@ pub struct InstructionInfo {
pub side_effects: bool,
pub tier: GasPriceTier
}
impl InstructionInfo {
pub fn new(name: &'static str, additional: usize, args: usize, ret: usize, side_effects: bool, tier: GasPriceTier) -> InstructionInfo {
InstructionInfo {
@@ -139,7 +140,7 @@ impl InstructionInfo {
#[cfg_attr(rustfmt, rustfmt_skip)]
/// Return details about specific instruction
pub fn get_info (instruction: Instruction) -> InstructionInfo {
pub fn get_info(instruction: Instruction) -> InstructionInfo {
match instruction {
STOP => InstructionInfo::new("STOP", 0, 0, 0, true, GasPriceTier::Zero),
ADD => InstructionInfo::new("ADD", 0, 2, 1, false, GasPriceTier::VeryLow),

View File

@@ -17,8 +17,9 @@
///! Rust VM implementation
use common::*;
use trace::VMTracer;
use super::instructions as instructions;
use super::instructions::Instruction;
use super::instructions::{Instruction, get_info};
use std::marker::Copy;
use evm::{self, MessageCallResult, ContractCreateResult};
@@ -69,6 +70,8 @@ trait Stack<T> {
fn push(&mut self, elem: T);
/// Get number of elements on Stack
fn size(&self) -> usize;
/// Returns all data on stack.
fn peek_top(&mut self, no_of_elems: usize) -> &[T];
}
struct VecStack<S> {
@@ -131,6 +134,11 @@ impl<S : fmt::Display> Stack<S> for VecStack<S> {
fn size(&self) -> usize {
self.stack.len()
}
fn peek_top(&mut self, no_from_top: usize) -> &[S] {
assert!(self.stack.len() >= no_from_top, "peek_top asked for more items than exist.");
&self.stack[self.stack.len() - no_from_top .. self.stack.len()]
}
}
trait Memory {
@@ -293,10 +301,15 @@ impl evm::Evm for Interpreter {
while reader.position < code.len() {
let instruction = code[reader.position];
reader.position += 1;
// Calculate gas cost
let (gas_cost, mem_size) = try!(self.get_gas_cost_mem(ext, instruction, &mut mem, &stack));
// TODO: make compile-time removable if too much of a performance hit.
let trace_executed = ext.trace_prepare_execute(reader.position, instruction, &gas_cost);
reader.position += 1;
try!(self.verify_gas(&current_gas, &gas_cost));
mem.expand(mem_size);
current_gas = current_gas - gas_cost; //TODO: use operator -=
@@ -311,10 +324,19 @@ impl evm::Evm for Interpreter {
);
});
let (mem_written, store_written) = match trace_executed {
true => (Self::mem_written(instruction, &stack), Self::store_written(instruction, &stack)),
false => (None, None),
};
// Execute instruction
let result = try!(self.exec_instruction(
current_gas, &params, ext, instruction, &mut reader, &mut mem, &mut stack
));
));
if trace_executed {
ext.trace_executed(current_gas, stack.peek_top(get_info(instruction).ret), mem_written.map(|(o, s)| (o, &(mem[o..(o + s)]))), store_written);
}
// Advance
match result {
@@ -485,6 +507,31 @@ impl Interpreter {
}
}
fn mem_written(
instruction: Instruction,
stack: &Stack<U256>
) -> Option<(usize, usize)> {
match instruction {
instructions::MSTORE | instructions::MLOAD => Some((stack.peek(0).low_u64() as usize, 32)),
instructions::MSTORE8 => Some((stack.peek(0).low_u64() as usize, 1)),
instructions::CALLDATACOPY | instructions::CODECOPY => Some((stack.peek(0).low_u64() as usize, stack.peek(2).low_u64() as usize)),
instructions::EXTCODECOPY => Some((stack.peek(1).low_u64() as usize, stack.peek(3).low_u64() as usize)),
instructions::CALL | instructions::CALLCODE => Some((stack.peek(5).low_u64() as usize, stack.peek(6).low_u64() as usize)),
instructions::DELEGATECALL => Some((stack.peek(4).low_u64() as usize, stack.peek(5).low_u64() as usize)),
_ => None,
}
}
fn store_written(
instruction: Instruction,
stack: &Stack<U256>
) -> Option<(U256, U256)> {
match instruction {
instructions::SSTORE => Some((stack.peek(0).clone(), stack.peek(1).clone())),
_ => None,
}
}
fn mem_gas_cost(&self, schedule: &evm::Schedule, current_mem_size: usize, mem_size: &U256) -> Result<(U256, usize), evm::Error> {
let gas_for_mem = |mem_size: U256| {
let s = mem_size >> 5;
@@ -833,10 +880,12 @@ impl Interpreter {
}
}
fn verify_instructions_requirements(&self,
info: &instructions::InstructionInfo,
stack_limit: usize,
stack: &Stack<U256>) -> Result<(), evm::Error> {
fn verify_instructions_requirements(
&self,
info: &instructions::InstructionInfo,
stack_limit: usize,
stack: &Stack<U256>
) -> Result<(), evm::Error> {
if !stack.has(info.args) {
Err(evm::Error::StackUnderflow {
instruction: info.name,

View File

@@ -16,6 +16,7 @@
//! Just in time compiler execution environment.
use common::*;
use trace::VMTracer;
use evmjit;
use evm;

View File

@@ -33,3 +33,4 @@ pub use self::evm::{Evm, Error, Result};
pub use self::ext::{Ext, ContractCreateResult, MessageCallResult};
pub use self::factory::{Factory, VMType};
pub use self::schedule::Schedule;
pub use self::instructions::get_info;

View File

@@ -21,9 +21,8 @@ use engine::*;
use evm::{self, Ext, Factory};
use externalities::*;
use substate::*;
use trace::{Trace, Tracer, NoopTracer, ExecutiveTracer};
use trace::{Trace, Tracer, NoopTracer, ExecutiveTracer, VMTrace, VMTracer, ExecutiveVMTracer, NoopVMTracer};
use crossbeam;
pub use types::executed::{Executed, ExecutionResult};
/// Max depth to avoid stack overflow (when it's reached we start a new thread with VM)
@@ -43,6 +42,8 @@ pub fn contract_address(address: &Address, nonce: &U256) -> Address {
pub struct TransactOptions {
/// Enable call tracing.
pub tracing: bool,
/// Enable VM tracing.
pub vm_tracing: bool,
/// Check transaction nonce before execution.
pub check_nonce: bool,
}
@@ -80,21 +81,40 @@ impl<'a> Executive<'a> {
}
/// Creates `Externalities` from `Executive`.
pub fn as_externalities<'_, T>(&'_ mut self, origin_info: OriginInfo, substate: &'_ mut Substate, output: OutputPolicy<'_, '_>, tracer: &'_ mut T) -> Externalities<'_, T> where T: Tracer {
Externalities::new(self.state, self.info, self.engine, self.vm_factory, self.depth, origin_info, substate, output, tracer)
pub fn as_externalities<'_, T, V>(
&'_ mut self,
origin_info: OriginInfo,
substate: &'_ mut Substate,
output: OutputPolicy<'_, '_>,
tracer: &'_ mut T,
vm_tracer: &'_ mut V
) -> Externalities<'_, T, V> where T: Tracer, V: VMTracer {
Externalities::new(self.state, self.info, self.engine, self.vm_factory, self.depth, origin_info, substate, output, tracer, vm_tracer)
}
/// This function should be used to execute transaction.
pub fn transact(&'a mut self, t: &SignedTransaction, options: TransactOptions) -> Result<Executed, ExecutionError> {
let check = options.check_nonce;
match options.tracing {
true => self.transact_with_tracer(t, check, ExecutiveTracer::default()),
false => self.transact_with_tracer(t, check, NoopTracer),
true => match options.vm_tracing {
true => self.transact_with_tracer(t, check, ExecutiveTracer::default(), ExecutiveVMTracer::default()),
false => self.transact_with_tracer(t, check, ExecutiveTracer::default(), NoopVMTracer),
},
false => match options.vm_tracing {
true => self.transact_with_tracer(t, check, NoopTracer, ExecutiveVMTracer::default()),
false => self.transact_with_tracer(t, check, NoopTracer, NoopVMTracer),
},
}
}
/// Execute transaction/call with tracing enabled
pub fn transact_with_tracer<T>(&'a mut self, t: &SignedTransaction, check_nonce: bool, mut tracer: T) -> Result<Executed, ExecutionError> where T: Tracer {
pub fn transact_with_tracer<T, V>(
&'a mut self,
t: &SignedTransaction,
check_nonce: bool,
mut tracer: T,
mut vm_tracer: V
) -> Result<Executed, ExecutionError> where T: Tracer, V: VMTracer {
let sender = try!(t.sender().map_err(|e| {
let message = format!("Transaction malformed: {:?}", e);
ExecutionError::TransactionMalformed(message)
@@ -154,7 +174,7 @@ impl<'a> Executive<'a> {
code: Some(t.data.clone()),
data: None,
};
(self.create(params, &mut substate, &mut tracer), vec![])
(self.create(params, &mut substate, &mut tracer, &mut vm_tracer), vec![])
},
Action::Call(ref address) => {
let params = ActionParams {
@@ -170,20 +190,26 @@ impl<'a> Executive<'a> {
};
// TODO: move output upstream
let mut out = vec![];
(self.call(params, &mut substate, BytesRef::Flexible(&mut out), &mut tracer), out)
(self.call(params, &mut substate, BytesRef::Flexible(&mut out), &mut tracer, &mut vm_tracer), out)
}
};
// finalize here!
Ok(try!(self.finalize(t, substate, gas_left, output, tracer.traces().pop())))
Ok(try!(self.finalize(t, substate, gas_left, output, tracer.traces().pop(), vm_tracer.drain())))
}
fn exec_vm<T>(&mut self, params: ActionParams, unconfirmed_substate: &mut Substate, output_policy: OutputPolicy, tracer: &mut T)
-> evm::Result where T: Tracer {
fn exec_vm<T, V>(
&mut self,
params: ActionParams,
unconfirmed_substate: &mut Substate,
output_policy: OutputPolicy,
tracer: &mut T,
vm_tracer: &mut V
) -> evm::Result where T: Tracer, V: VMTracer {
// Ordinary execution - keep VM in same thread
if (self.depth + 1) % MAX_VM_DEPTH_FOR_THREAD != 0 {
let vm_factory = self.vm_factory;
let mut ext = self.as_externalities(OriginInfo::from(&params), unconfirmed_substate, output_policy, tracer);
let mut ext = self.as_externalities(OriginInfo::from(&params), unconfirmed_substate, output_policy, tracer, vm_tracer);
trace!(target: "executive", "ext.schedule.have_delegate_call: {}", ext.schedule().have_delegate_call);
return vm_factory.create().exec(params, &mut ext);
}
@@ -193,7 +219,7 @@ impl<'a> Executive<'a> {
// https://github.com/aturon/crossbeam/issues/16
crossbeam::scope(|scope| {
let vm_factory = self.vm_factory;
let mut ext = self.as_externalities(OriginInfo::from(&params), unconfirmed_substate, output_policy, tracer);
let mut ext = self.as_externalities(OriginInfo::from(&params), unconfirmed_substate, output_policy, tracer, vm_tracer);
scope.spawn(move || {
vm_factory.create().exec(params, &mut ext)
@@ -205,8 +231,14 @@ impl<'a> Executive<'a> {
/// NOTE. It does not finalize the transaction (doesn't do refunds, nor suicides).
/// Modifies the substate and the output.
/// Returns either gas_left or `evm::Error`.
pub fn call<T>(&mut self, params: ActionParams, substate: &mut Substate, mut output: BytesRef, tracer: &mut T)
-> evm::Result where T: Tracer {
pub fn call<T, V>(
&mut self,
params: ActionParams,
substate: &mut Substate,
mut output: BytesRef,
tracer: &mut T,
vm_tracer: &mut V
) -> evm::Result where T: Tracer, V: VMTracer {
// backup used in case of running out of gas
self.state.snapshot();
@@ -264,16 +296,22 @@ impl<'a> Executive<'a> {
let trace_info = tracer.prepare_trace_call(&params);
let mut trace_output = tracer.prepare_trace_output();
let mut subtracer = tracer.subtracer();
let gas = params.gas;
if params.code.is_some() {
// part of substate that may be reverted
let mut unconfirmed_substate = Substate::new();
// TODO: make ActionParams pass by ref then avoid copy altogether.
let mut subvmtracer = vm_tracer.prepare_subtrace(params.code.as_ref().expect("scope is protected by params.code.is_some condition"));
let res = {
self.exec_vm(params, &mut unconfirmed_substate, OutputPolicy::Return(output, trace_output.as_mut()), &mut subtracer)
self.exec_vm(params, &mut unconfirmed_substate, OutputPolicy::Return(output, trace_output.as_mut()), &mut subtracer, &mut subvmtracer)
};
vm_tracer.done_subtrace(subvmtracer);
trace!(target: "executive", "res={:?}", res);
let traces = subtracer.traces();
@@ -307,8 +345,13 @@ impl<'a> Executive<'a> {
/// Creates contract with given contract params.
/// NOTE. It does not finalize the transaction (doesn't do refunds, nor suicides).
/// Modifies the substate.
pub fn create<T>(&mut self, params: ActionParams, substate: &mut Substate, tracer: &mut T) -> evm::Result where T:
Tracer {
pub fn create<T, V>(
&mut self,
params: ActionParams,
substate: &mut Substate,
tracer: &mut T,
vm_tracer: &mut V
) -> evm::Result where T: Tracer, V: VMTracer {
// backup used in case of running out of gas
self.state.snapshot();
@@ -330,10 +373,14 @@ impl<'a> Executive<'a> {
let gas = params.gas;
let created = params.address.clone();
let mut subvmtracer = vm_tracer.prepare_subtrace(&params.code.as_ref().expect("two ways into create (Externalities::create and Executive::transact_with_tracer); both place `Some(...)` `code` in `params`; qed"));
let res = {
self.exec_vm(params, &mut unconfirmed_substate, OutputPolicy::InitContract(trace_output.as_mut()), &mut subtracer)
self.exec_vm(params, &mut unconfirmed_substate, OutputPolicy::InitContract(trace_output.as_mut()), &mut subtracer, &mut subvmtracer)
};
vm_tracer.done_subtrace(subvmtracer);
match res {
Ok(gas_left) => tracer.trace_create(
trace_info,
@@ -351,7 +398,15 @@ impl<'a> Executive<'a> {
}
/// Finalizes the transaction (does refunds and suicides).
fn finalize(&mut self, t: &SignedTransaction, substate: Substate, result: evm::Result, output: Bytes, trace: Option<Trace>) -> ExecutionResult {
fn finalize(
&mut self,
t: &SignedTransaction,
substate: Substate,
result: evm::Result,
output: Bytes,
trace: Option<Trace>,
vm_trace: Option<VMTrace>
) -> ExecutionResult {
let schedule = self.engine.schedule(self.info);
// refunds from SSTORE nonzero -> zero
@@ -394,6 +449,7 @@ impl<'a> Executive<'a> {
contracts_created: vec![],
output: output,
trace: trace,
vm_trace: vm_trace,
})
},
_ => {
@@ -406,6 +462,7 @@ impl<'a> Executive<'a> {
contracts_created: substate.contracts_created,
output: output,
trace: trace,
vm_trace: vm_trace,
})
},
}
@@ -438,6 +495,7 @@ mod tests {
use tests::helpers::*;
use trace::trace;
use trace::{Trace, Tracer, NoopTracer, ExecutiveTracer};
use trace::{VMTrace, VMOperation, VMExecutedOperation, MemoryDiff, StorageDiff, VMTracer, NoopVMTracer, ExecutiveVMTracer};
#[test]
fn test_contract_address() {
@@ -466,7 +524,7 @@ mod tests {
let gas_left = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory);
ex.create(params, &mut substate, &mut NoopTracer).unwrap()
ex.create(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer).unwrap()
};
assert_eq!(gas_left, U256::from(79_975));
@@ -525,7 +583,7 @@ mod tests {
let gas_left = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory);
ex.create(params, &mut substate, &mut NoopTracer).unwrap()
ex.create(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer).unwrap()
};
assert_eq!(gas_left, U256::from(62_976));
@@ -542,7 +600,7 @@ mod tests {
// 52
// 60 1d - push 29
// 60 03 - push 3
// 60 17 - push 17
// 60 17 - push 23
// f0 - create
// 60 00 - push 0
// 55 sstore
@@ -578,13 +636,16 @@ mod tests {
let engine = TestEngine::new(5);
let mut substate = Substate::new();
let mut tracer = ExecutiveTracer::default();
let mut vm_tracer = ExecutiveVMTracer::default();
let gas_left = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory);
let output = BytesRef::Fixed(&mut[0u8;0]);
ex.call(params, &mut substate, output, &mut tracer).unwrap()
ex.call(params, &mut substate, output, &mut tracer, &mut vm_tracer).unwrap()
};
assert_eq!(gas_left, U256::from(44_752));
let expected_trace = vec![ Trace {
depth: 0,
action: trace::Action::Call(trace::Call {
@@ -615,7 +676,39 @@ mod tests {
}]
}];
assert_eq!(tracer.traces(), expected_trace);
assert_eq!(gas_left, U256::from(44_752));
let expected_vm_trace = VMTrace {
parent_step: 0,
code: vec![124, 96, 16, 128, 96, 12, 96, 0, 57, 96, 0, 243, 0, 96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53, 85, 96, 0, 82, 96, 29, 96, 3, 96, 23, 240, 96, 0, 85],
operations: vec![
VMOperation { pc: 0, instruction: 124, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99997.into(), stack_push: vec_into![U256::from_dec_str("2589892687202724018173567190521546555304938078595079151649957320078677").unwrap()], mem_diff: None, store_diff: None }) },
VMOperation { pc: 30, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99994.into(), stack_push: vec_into![0], mem_diff: None, store_diff: None }) },
VMOperation { pc: 32, instruction: 82, gas_cost: 6.into(), executed: Some(VMExecutedOperation { gas_used: 99988.into(), stack_push: vec_into![], mem_diff: Some(MemoryDiff { offset: 0, data: vec![0, 0, 0, 96, 16, 128, 96, 12, 96, 0, 57, 96, 0, 243, 0, 96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53, 85] }), store_diff: None }) },
VMOperation { pc: 33, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99985.into(), stack_push: vec_into![29], mem_diff: None, store_diff: None }) },
VMOperation { pc: 35, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99982.into(), stack_push: vec_into![3], mem_diff: None, store_diff: None }) },
VMOperation { pc: 37, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99979.into(), stack_push: vec_into![23], mem_diff: None, store_diff: None }) },
VMOperation { pc: 39, instruction: 240, gas_cost: 32000.into(), executed: Some(VMExecutedOperation { gas_used: 67979.into(), stack_push: vec_into![U256::from_dec_str("1135198453258042933984631383966629874710669425204").unwrap()], mem_diff: None, store_diff: None }) },
VMOperation { pc: 40, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 64752.into(), stack_push: vec_into![0], mem_diff: None, store_diff: None }) },
VMOperation { pc: 42, instruction: 85, gas_cost: 20000.into(), executed: Some(VMExecutedOperation { gas_used: 44752.into(), stack_push: vec_into![], mem_diff: None, store_diff: Some(StorageDiff { location: 0.into(), value: U256::from_dec_str("1135198453258042933984631383966629874710669425204").unwrap() }) }) }
],
subs: vec![
VMTrace {
parent_step: 7,
code: vec![96, 16, 128, 96, 12, 96, 0, 57, 96, 0, 243, 0, 96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53, 85],
operations: vec![
VMOperation { pc: 0, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 67976.into(), stack_push: vec_into![16], mem_diff: None, store_diff: None }) },
VMOperation { pc: 2, instruction: 128, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 67973.into(), stack_push: vec_into![16, 16], mem_diff: None, store_diff: None }) },
VMOperation { pc: 3, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 67970.into(), stack_push: vec_into![12], mem_diff: None, store_diff: None }) },
VMOperation { pc: 5, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 67967.into(), stack_push: vec_into![0], mem_diff: None, store_diff: None }) },
VMOperation { pc: 7, instruction: 57, gas_cost: 9.into(), executed: Some(VMExecutedOperation { gas_used: 67958.into(), stack_push: vec_into![], mem_diff: Some(MemoryDiff { offset: 0, data: vec![96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53] }), store_diff: None }) },
VMOperation { pc: 8, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 67955.into(), stack_push: vec_into![0], mem_diff: None, store_diff: None }) },
VMOperation { pc: 10, instruction: 243, gas_cost: 0.into(), executed: Some(VMExecutedOperation { gas_used: 67955.into(), stack_push: vec_into![], mem_diff: None, store_diff: None }) }
],
subs: vec![]
}
]
};
assert_eq!(vm_tracer.drain().unwrap(), expected_vm_trace);
}
evm_test!{test_create_contract: test_create_contract_jit, test_create_contract_int}
@@ -650,12 +743,15 @@ mod tests {
let engine = TestEngine::new(5);
let mut substate = Substate::new();
let mut tracer = ExecutiveTracer::default();
let mut vm_tracer = ExecutiveVMTracer::default();
let gas_left = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory);
ex.create(params.clone(), &mut substate, &mut tracer).unwrap()
ex.create(params.clone(), &mut substate, &mut tracer, &mut vm_tracer).unwrap()
};
assert_eq!(gas_left, U256::from(96_776));
let expected_trace = vec![Trace {
depth: 0,
action: trace::Action::Create(trace::Create {
@@ -671,9 +767,23 @@ mod tests {
}),
subs: vec![]
}];
assert_eq!(tracer.traces(), expected_trace);
assert_eq!(gas_left, U256::from(96_776));
let expected_vm_trace = VMTrace {
parent_step: 0,
code: vec![96, 16, 128, 96, 12, 96, 0, 57, 96, 0, 243, 0, 96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53, 85],
operations: vec![
VMOperation { pc: 0, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99997.into(), stack_push: vec_into![16], mem_diff: None, store_diff: None }) },
VMOperation { pc: 2, instruction: 128, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99994.into(), stack_push: vec_into![16, 16], mem_diff: None, store_diff: None }) },
VMOperation { pc: 3, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99991.into(), stack_push: vec_into![12], mem_diff: None, store_diff: None }) },
VMOperation { pc: 5, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99988.into(), stack_push: vec_into![0], mem_diff: None, store_diff: None }) },
VMOperation { pc: 7, instruction: 57, gas_cost: 9.into(), executed: Some(VMExecutedOperation { gas_used: 99979.into(), stack_push: vec_into![], mem_diff: Some(MemoryDiff { offset: 0, data: vec![96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53] }), store_diff: None }) },
VMOperation { pc: 8, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99976.into(), stack_push: vec_into![0], mem_diff: None, store_diff: None }) },
VMOperation { pc: 10, instruction: 243, gas_cost: 0.into(), executed: Some(VMExecutedOperation { gas_used: 99976.into(), stack_push: vec_into![], mem_diff: None, store_diff: None }) }
],
subs: vec![]
};
assert_eq!(vm_tracer.drain().unwrap(), expected_vm_trace);
}
evm_test!{test_create_contract_value_too_high: test_create_contract_value_too_high_jit, test_create_contract_value_too_high_int}
@@ -722,7 +832,7 @@ mod tests {
let gas_left = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory);
ex.create(params, &mut substate, &mut NoopTracer).unwrap()
ex.create(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer).unwrap()
};
assert_eq!(gas_left, U256::from(62_976));
@@ -774,7 +884,7 @@ mod tests {
{
let mut ex = Executive::new(&mut state, &info, &engine, &factory);
ex.create(params, &mut substate, &mut NoopTracer).unwrap();
ex.create(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer).unwrap();
}
assert_eq!(substate.contracts_created.len(), 1);
@@ -835,7 +945,7 @@ mod tests {
let gas_left = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory);
ex.call(params, &mut substate, BytesRef::Fixed(&mut []), &mut NoopTracer).unwrap()
ex.call(params, &mut substate, BytesRef::Fixed(&mut []), &mut NoopTracer, &mut NoopVMTracer).unwrap()
};
assert_eq!(gas_left, U256::from(73_237));
@@ -880,7 +990,7 @@ mod tests {
let gas_left = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory);
ex.call(params, &mut substate, BytesRef::Fixed(&mut []), &mut NoopTracer).unwrap()
ex.call(params, &mut substate, BytesRef::Fixed(&mut []), &mut NoopTracer, &mut NoopVMTracer).unwrap()
};
assert_eq!(gas_left, U256::from(59_870));
@@ -913,7 +1023,7 @@ mod tests {
let executed = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory);
let opts = TransactOptions { check_nonce: true, tracing: false };
let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false };
ex.transact(&t, opts).unwrap()
};
@@ -947,7 +1057,7 @@ mod tests {
let res = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory);
let opts = TransactOptions { check_nonce: true, tracing: false };
let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false };
ex.transact(&t, opts)
};
@@ -979,7 +1089,7 @@ mod tests {
let res = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory);
let opts = TransactOptions { check_nonce: true, tracing: false };
let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false };
ex.transact(&t, opts)
};
@@ -1013,7 +1123,7 @@ mod tests {
let res = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory);
let opts = TransactOptions { check_nonce: true, tracing: false };
let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false };
ex.transact(&t, opts)
};
@@ -1047,7 +1157,7 @@ mod tests {
let res = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory);
let opts = TransactOptions { check_nonce: true, tracing: false };
let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false };
ex.transact(&t, opts)
};
@@ -1082,7 +1192,7 @@ mod tests {
let result = {
let mut ex = Executive::new(&mut state, &info, &engine, &factory);
ex.create(params, &mut substate, &mut NoopTracer)
ex.create(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer)
};
match result {

View File

@@ -21,7 +21,7 @@ use engine::*;
use executive::*;
use evm::{self, Schedule, Ext, ContractCreateResult, MessageCallResult, Factory};
use substate::*;
use trace::Tracer;
use trace::{Tracer, VMTracer};
/// Policy for handling output data on `RETURN` opcode.
pub enum OutputPolicy<'a, 'b> {
@@ -55,7 +55,7 @@ impl OriginInfo {
}
/// Implementation of evm Externalities.
pub struct Externalities<'a, T> where T: 'a + Tracer {
pub struct Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMTracer {
state: &'a mut State,
env_info: &'a EnvInfo,
engine: &'a Engine,
@@ -66,10 +66,10 @@ pub struct Externalities<'a, T> where T: 'a + Tracer {
schedule: Schedule,
output: OutputPolicy<'a, 'a>,
tracer: &'a mut T,
vm_tracer: &'a mut V,
}
impl<'a, T> Externalities<'a, T> where T: 'a + Tracer {
impl<'a, T, V> Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMTracer {
#[cfg_attr(feature="dev", allow(too_many_arguments))]
/// Basic `Externalities` constructor.
pub fn new(state: &'a mut State,
@@ -81,6 +81,7 @@ impl<'a, T> Externalities<'a, T> where T: 'a + Tracer {
substate: &'a mut Substate,
output: OutputPolicy<'a, 'a>,
tracer: &'a mut T,
vm_tracer: &'a mut V,
) -> Self {
Externalities {
state: state,
@@ -93,11 +94,12 @@ impl<'a, T> Externalities<'a, T> where T: 'a + Tracer {
schedule: engine.schedule(env_info),
output: output,
tracer: tracer,
vm_tracer: vm_tracer,
}
}
}
impl<'a, T> Ext for Externalities<'a, T> where T: 'a + Tracer {
impl<'a, T, V> Ext for Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMTracer {
fn storage_at(&self, key: &H256) -> H256 {
self.state.storage_at(&self.origin_info.address, key)
}
@@ -152,7 +154,7 @@ impl<'a, T> Ext for Externalities<'a, T> where T: 'a + Tracer {
let mut ex = Executive::from_parent(self.state, self.env_info, self.engine, self.vm_factory, self.depth);
// TODO: handle internal error separately
match ex.create(params, self.substate, self.tracer) {
match ex.create(params, self.substate, self.tracer, self.vm_tracer) {
Ok(gas_left) => {
self.substate.contracts_created.push(address.clone());
ContractCreateResult::Created(address, gas_left)
@@ -190,7 +192,7 @@ impl<'a, T> Ext for Externalities<'a, T> where T: 'a + Tracer {
let mut ex = Executive::from_parent(self.state, self.env_info, self.engine, self.vm_factory, self.depth);
match ex.call(params, self.substate, BytesRef::Fixed(output), self.tracer) {
match ex.call(params, self.substate, BytesRef::Fixed(output), self.tracer, self.vm_tracer) {
Ok(gas_left) => MessageCallResult::Success(gas_left),
_ => MessageCallResult::Failed
}
@@ -286,6 +288,14 @@ impl<'a, T> Ext for Externalities<'a, T> where T: 'a + Tracer {
fn inc_sstore_clears(&mut self) {
self.substate.sstore_clears_count = self.substate.sstore_clears_count + U256::one();
}
fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: &U256) -> bool {
self.vm_tracer.trace_prepare_execute(pc, instruction, gas_cost)
}
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)
}
}
#[cfg(test)]
@@ -297,7 +307,7 @@ mod tests {
use substate::*;
use tests::helpers::*;
use super::*;
use trace::{NoopTracer};
use trace::{NoopTracer, NoopVMTracer};
fn get_test_origin() -> OriginInfo {
OriginInfo {
@@ -349,9 +359,10 @@ mod tests {
let mut setup = TestSetup::new();
let state = setup.state.reference_mut();
let mut tracer = NoopTracer;
let mut vm_tracer = NoopVMTracer;
let vm_factory = Default::default();
let ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer);
let ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer, &mut vm_tracer);
assert_eq!(ext.env_info().number, 100);
}
@@ -361,9 +372,10 @@ mod tests {
let mut setup = TestSetup::new();
let state = setup.state.reference_mut();
let mut tracer = NoopTracer;
let mut vm_tracer = NoopVMTracer;
let vm_factory = Default::default();
let ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer);
let ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer, &mut vm_tracer);
let hash = ext.blockhash(&U256::from_str("0000000000000000000000000000000000000000000000000000000000120000").unwrap());
@@ -383,9 +395,10 @@ mod tests {
}
let state = setup.state.reference_mut();
let mut tracer = NoopTracer;
let mut vm_tracer = NoopVMTracer;
let vm_factory = Default::default();
let ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer);
let ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer, &mut vm_tracer);
let hash = ext.blockhash(&U256::from_str("0000000000000000000000000000000000000000000000000000000000120000").unwrap());
@@ -398,9 +411,10 @@ mod tests {
let mut setup = TestSetup::new();
let state = setup.state.reference_mut();
let mut tracer = NoopTracer;
let mut vm_tracer = NoopVMTracer;
let vm_factory = Default::default();
let mut ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer);
let mut ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer, &mut vm_tracer);
let mut output = vec![];
@@ -423,10 +437,11 @@ mod tests {
let mut setup = TestSetup::new();
let state = setup.state.reference_mut();
let mut tracer = NoopTracer;
let mut vm_tracer = NoopVMTracer;
{
let vm_factory = Default::default();
let mut ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer);
let mut ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer, &mut vm_tracer);
ext.log(log_topics, &log_data);
}
@@ -440,10 +455,11 @@ mod tests {
let mut setup = TestSetup::new();
let state = setup.state.reference_mut();
let mut tracer = NoopTracer;
let mut vm_tracer = NoopVMTracer;
{
let vm_factory = Default::default();
let mut ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer);
let mut ext = Externalities::new(state, &setup.env_info, &*setup.engine, &vm_factory, 0, get_test_origin(), &mut setup.sub_state, OutputPolicy::InitContract(None), &mut tracer, &mut vm_tracer);
ext.suicide(&refund_account);
}

View File

@@ -25,6 +25,7 @@ use substate::*;
use tests::helpers::*;
use ethjson;
use trace::{Tracer, NoopTracer};
use trace::{VMTracer, NoopVMTracer};
#[derive(Debug, PartialEq)]
struct CallCreate {
@@ -48,32 +49,35 @@ impl From<ethjson::vm::Call> for CallCreate {
/// Tiny wrapper around executive externalities.
/// Stores callcreates.
struct TestExt<'a, T> where T: 'a + Tracer {
ext: Externalities<'a, T>,
struct TestExt<'a, T, V> where T: 'a + Tracer, V: 'a + VMTracer {
ext: Externalities<'a, T, V>,
callcreates: Vec<CallCreate>,
contract_address: Address
}
impl<'a, T> TestExt<'a, T> where T: 'a + Tracer {
fn new(state: &'a mut State,
info: &'a EnvInfo,
engine: &'a Engine,
vm_factory: &'a Factory,
depth: usize,
origin_info: OriginInfo,
substate: &'a mut Substate,
output: OutputPolicy<'a, 'a>,
address: Address,
tracer: &'a mut T) -> Self {
impl<'a, T, V> TestExt<'a, T, V> where T: 'a + Tracer, V: 'a + VMTracer {
fn new(
state: &'a mut State,
info: &'a EnvInfo,
engine: &'a Engine,
vm_factory: &'a Factory,
depth: usize,
origin_info: OriginInfo,
substate: &'a mut Substate,
output: OutputPolicy<'a, 'a>,
address: Address,
tracer: &'a mut T,
vm_tracer: &'a mut V,
) -> Self {
TestExt {
contract_address: contract_address(&address, &state.nonce(&address)),
ext: Externalities::new(state, info, engine, vm_factory, depth, origin_info, substate, output, tracer),
ext: Externalities::new(state, info, engine, vm_factory, depth, origin_info, substate, output, tracer, vm_tracer),
callcreates: vec![]
}
}
}
impl<'a, T> Ext for TestExt<'a, T> where T: Tracer {
impl<'a, T, V> Ext for TestExt<'a, T, V> where T: Tracer, V: VMTracer {
fn storage_at(&self, key: &H256) -> H256 {
self.ext.storage_at(key)
}
@@ -186,6 +190,7 @@ fn do_json_test_for(vm_type: &VMType, json_data: &[u8]) -> Vec<String> {
let mut substate = Substate::new();
let mut tracer = NoopTracer;
let mut vm_tracer = NoopVMTracer;
let mut output = vec![];
// execute
@@ -201,6 +206,7 @@ fn do_json_test_for(vm_type: &VMType, json_data: &[u8]) -> Vec<String> {
OutputPolicy::Return(BytesRef::Flexible(&mut output), None),
params.address.clone(),
&mut tracer,
&mut vm_tracer,
);
let evm = vm_factory.create();
let res = evm.exec(params, &mut ex);

View File

@@ -141,3 +141,5 @@ mod tests;
mod json_tests;
pub use types::*;
pub use evm::get_info;
pub use executive::contract_address;

View File

@@ -251,7 +251,7 @@ impl MinerService for Miner {
}
}
fn call(&self, chain: &MiningBlockChainClient, t: &SignedTransaction) -> Result<Executed, ExecutionError> {
fn call(&self, chain: &MiningBlockChainClient, t: &SignedTransaction, vm_tracing: bool) -> Result<Executed, ExecutionError> {
let sealing_work = self.sealing_work.lock().unwrap();
match sealing_work.peek_last_ref() {
Some(work) => {
@@ -277,12 +277,13 @@ impl MinerService for Miner {
// give the sender max balance
state.sub_balance(&sender, &balance);
state.add_balance(&sender, &U256::max_value());
let options = TransactOptions { tracing: false, check_nonce: false };
let options = TransactOptions { tracing: false, vm_tracing: vm_tracing, check_nonce: false };
// TODO: use vm_trace here.
Executive::new(&mut state, &env_info, self.engine(), chain.vm_factory()).transact(t, options)
},
None => {
chain.call(t)
chain.call(t, vm_tracing)
}
}
}

View File

@@ -148,7 +148,7 @@ pub trait MinerService : Send + Sync {
fn balance(&self, chain: &MiningBlockChainClient, address: &Address) -> U256;
/// Call into contract code using pending state.
fn call(&self, chain: &MiningBlockChainClient, t: &SignedTransaction) -> Result<Executed, ExecutionError>;
fn call(&self, chain: &MiningBlockChainClient, t: &SignedTransaction, vm_tracing: bool) -> Result<Executed, ExecutionError>;
/// Get storage value in pending state.
fn storage_at(&self, chain: &MiningBlockChainClient, address: &Address, position: &H256) -> H256;

View File

@@ -183,16 +183,14 @@ impl State {
/// Add `incr` to the balance of account `a`.
pub fn add_balance(&mut self, a: &Address, incr: &U256) {
let old = self.balance(a);
trace!(target: "state", "add_balance({}, {}): {}", a, incr, self.balance(a));
self.require(a, false).add_balance(incr);
trace!("state: add_balance({}, {}): {} -> {}\n", a, incr, old, self.balance(a));
}
/// Subtract `decr` from the balance of account `a`.
pub fn sub_balance(&mut self, a: &Address, decr: &U256) {
let old = self.balance(a);
trace!(target: "state", "sub_balance({}, {}): {}", a, decr, self.balance(a));
self.require(a, false).sub_balance(decr);
trace!("state: sub_balance({}, {}): {} -> {}\n", a, decr, old, self.balance(a));
}
/// Subtracts `by` from the balance of `from` and adds it to that of `to`.
@@ -222,7 +220,7 @@ impl State {
pub fn apply(&mut self, env_info: &EnvInfo, engine: &Engine, vm_factory: &EvmFactory, t: &SignedTransaction, tracing: bool) -> ApplyResult {
// let old = self.to_pod();
let options = TransactOptions { tracing: tracing, check_nonce: true };
let options = TransactOptions { tracing: tracing, vm_tracing: false, check_nonce: true };
let e = try!(Executive::new(self, env_info, engine, vm_factory).transact(t, options));
// TODO uncomment once to_pod() works correctly.

View File

@@ -18,13 +18,13 @@
use util::{Bytes, Address, U256};
use action_params::ActionParams;
use trace::trace::{Trace, Call, Create, Action, Res, CreateResult, CallResult};
use trace::Tracer;
use trace::trace::{Trace, Call, Create, Action, Res, CreateResult, CallResult, VMTrace, VMOperation, VMExecutedOperation, MemoryDiff, StorageDiff};
use trace::{Tracer, VMTracer};
/// Simple executive tracer. Traces all calls and creates. Ignores delegatecalls.
#[derive(Default)]
pub struct ExecutiveTracer {
traces: Vec<Trace>
traces: Vec<Trace>,
}
impl Tracer for ExecutiveTracer {
@@ -40,8 +40,7 @@ impl Tracer for ExecutiveTracer {
Some(vec![])
}
fn trace_call(&mut self, call: Option<Call>, gas_used: U256, output: Option<Bytes>, depth: usize, subs:
Vec<Trace>, delegate_call: bool) {
fn trace_call(&mut self, call: Option<Call>, gas_used: U256, output: Option<Bytes>, depth: usize, subs: Vec<Trace>, delegate_call: bool) {
// don't trace if it's DELEGATECALL or CALLCODE.
if delegate_call {
return;
@@ -106,3 +105,46 @@ impl Tracer for ExecutiveTracer {
self.traces
}
}
/// Simple VM tracer. Traces all operations.
#[derive(Default)]
pub struct ExecutiveVMTracer {
data: VMTrace,
}
impl VMTracer for ExecutiveVMTracer {
fn trace_prepare_execute(&mut self, pc: usize, instruction: u8, gas_cost: &U256) -> bool {
self.data.operations.push(VMOperation {
pc: pc,
instruction: instruction,
gas_cost: gas_cost.clone(),
executed: None,
});
true
}
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(&self, code: &Bytes) -> Self {
ExecutiveVMTracer { data: VMTrace {
parent_step: self.data.operations.len(),
code: code.clone(),
operations: vec![],
subs: vec![],
}}
}
fn done_subtrace(&mut self, sub: Self) {
self.data.subs.push(sub.data);
}
fn drain(mut self) -> Option<VMTrace> { self.data.subs.pop() }
}

View File

@@ -31,9 +31,9 @@ pub use self::block::BlockTraces;
pub use self::config::{Config, Switch};
pub use self::db::TraceDB;
pub use self::error::Error;
pub use types::trace_types::trace::Trace;
pub use self::noop_tracer::NoopTracer;
pub use self::executive_tracer::ExecutiveTracer;
pub use types::trace_types::trace::{Trace, VMTrace, VMOperation, VMExecutedOperation, MemoryDiff, StorageDiff};
pub use self::noop_tracer::{NoopTracer, NoopVMTracer};
pub use self::executive_tracer::{ExecutiveTracer, ExecutiveVMTracer};
pub use types::trace_types::filter::{Filter, AddressesFilter};
pub use self::import::ImportRequest;
pub use self::localized::LocalizedTrace;
@@ -81,13 +81,32 @@ pub trait Tracer: Send {
/// Stores failed create trace.
fn trace_failed_create(&mut self, create: Option<Create>, depth: usize, subs: Vec<Trace>);
/// Spawn subracer which will be used to trace deeper levels of execution.
/// 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 traces(self) -> Vec<Trace>;
}
/// Used by executive to build VM traces.
pub trait VMTracer: Send {
/// Trace the preparation to execute a single instruction.
/// @returns true if `trace_executed` should be called.
fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: &U256) -> bool { false }
/// Trace the finalised execution of a single instruction.
fn trace_executed(&mut self, _gas_used: U256, _stack_push: &[U256], _mem_diff: Option<(usize, &[u8])>, _store_diff: Option<(U256, U256)>) {}
/// Spawn subtracer which will be used to trace deeper levels of execution.
fn prepare_subtrace(&self, code: &Bytes) -> Self where Self: Sized;
/// Spawn subtracer which will be used to trace deeper levels of execution.
fn done_subtrace(&mut self, sub: Self) where Self: Sized;
/// Consumes self and returns the VM trace.
fn drain(self) -> Option<VMTrace>;
}
/// `DbExtras` provides an interface to query extra data which is not stored in tracesdb,
/// but necessary to work correctly.
pub trait DatabaseExtras {

View File

@@ -18,8 +18,8 @@
use util::{Bytes, Address, U256};
use action_params::ActionParams;
use trace::Tracer;
use trace::trace::{Trace, Call, Create};
use trace::{Tracer, VMTracer};
use trace::trace::{Trace, Call, Create, VMTrace};
/// Nonoperative tracer. Does not trace anything.
pub struct NoopTracer;
@@ -63,3 +63,23 @@ impl Tracer for NoopTracer {
vec![]
}
}
/// Nonoperative VM tracer. Does not trace anything.
pub struct NoopVMTracer;
impl VMTracer for NoopVMTracer {
/// Trace the preparation to execute a single instruction.
fn trace_prepare_execute(&mut self, _pc: usize, _instruction: u8, _gas_cost: &U256) -> bool { false }
/// Trace the finalised execution of a single instruction.
fn trace_executed(&mut self, _gas_used: U256, _stack_push: &[U256], _mem_diff: Option<(usize, &[u8])>, _store_diff: Option<(U256, U256)>) {}
/// Spawn subtracer which will be used to trace deeper levels of execution.
fn prepare_subtrace(&self, _code: &Bytes) -> Self { NoopVMTracer }
/// Spawn subtracer which will be used to trace deeper levels of execution.
fn done_subtrace(&mut self, _sub: Self) {}
/// Consumes self and returns all VM traces.
fn drain(self) -> Option<VMTrace> { None }
}

View File

@@ -18,7 +18,7 @@
use util::numbers::*;
use util::Bytes;
use trace::Trace;
use trace::{Trace, VMTrace};
use types::log_entry::LogEntry;
use ipc::binary::BinaryConvertError;
use std::fmt;
@@ -59,6 +59,8 @@ pub struct Executed {
pub output: Bytes,
/// The trace of this transaction.
pub trace: Option<Trace>,
/// The VM trace of this transaction.
pub vm_trace: Option<VMTrace>,
}
/// Result of executing the transaction.

View File

@@ -349,6 +349,170 @@ impl Trace {
}
}
#[derive(Debug, Clone, PartialEq, Binary)]
/// A diff of some chunk of memory.
pub struct MemoryDiff {
/// Offset into memory the change begins.
pub offset: usize,
/// The changed data.
pub data: Bytes,
}
impl Encodable for MemoryDiff {
fn rlp_append(&self, s: &mut RlpStream) {
s.begin_list(2);
s.append(&self.offset);
s.append(&self.data);
}
}
impl Decodable for MemoryDiff {
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
let d = decoder.as_rlp();
Ok(MemoryDiff {
offset: try!(d.val_at(0)),
data: try!(d.val_at(1)),
})
}
}
#[derive(Debug, Clone, PartialEq, Binary)]
/// A diff of some storage value.
pub struct StorageDiff {
/// Which key in storage is changed.
pub location: U256,
/// What the value has been changed to.
pub value: U256,
}
impl Encodable for StorageDiff {
fn rlp_append(&self, s: &mut RlpStream) {
s.begin_list(2);
s.append(&self.location);
s.append(&self.value);
}
}
impl Decodable for StorageDiff {
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
let d = decoder.as_rlp();
Ok(StorageDiff {
location: try!(d.val_at(0)),
value: try!(d.val_at(1)),
})
}
}
#[derive(Debug, Clone, PartialEq, Binary)]
/// A record of an executed VM operation.
pub struct VMExecutedOperation {
/// The total gas used.
pub gas_used: U256,
/// The stack item placed, if any.
pub stack_push: Vec<U256>,
/// If altered, the memory delta.
pub mem_diff: Option<MemoryDiff>,
/// The altered storage value, if any.
pub store_diff: Option<StorageDiff>,
}
impl Encodable for VMExecutedOperation {
fn rlp_append(&self, s: &mut RlpStream) {
s.begin_list(4);
s.append(&self.gas_used);
s.append(&self.stack_push);
s.append(&self.mem_diff);
s.append(&self.store_diff);
}
}
impl Decodable for VMExecutedOperation {
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
let d = decoder.as_rlp();
Ok(VMExecutedOperation {
gas_used: try!(d.val_at(0)),
stack_push: try!(d.val_at(1)),
mem_diff: try!(d.val_at(2)),
store_diff: try!(d.val_at(3)),
})
}
}
#[derive(Debug, Clone, PartialEq, Binary)]
/// A record of the execution of a single VM operation.
pub struct VMOperation {
/// The program counter.
pub pc: usize,
/// The instruction executed.
pub instruction: u8,
/// The gas cost for this instruction.
pub gas_cost: U256,
/// Information concerning the execution of the operation.
pub executed: Option<VMExecutedOperation>,
}
impl Encodable for VMOperation {
fn rlp_append(&self, s: &mut RlpStream) {
s.begin_list(4);
s.append(&self.pc);
s.append(&self.instruction);
s.append(&self.gas_cost);
s.append(&self.executed);
}
}
impl Decodable for VMOperation {
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
let d = decoder.as_rlp();
let res = VMOperation {
pc: try!(d.val_at(0)),
instruction: try!(d.val_at(1)),
gas_cost: try!(d.val_at(2)),
executed: try!(d.val_at(3)),
};
Ok(res)
}
}
#[derive(Debug, Clone, PartialEq, Binary, Default)]
/// A record of a full VM trace for a CALL/CREATE.
pub struct VMTrace {
/// The step (i.e. index into operations) at which this trace corresponds.
pub parent_step: usize,
/// The code to be executed.
pub code: Bytes,
/// The operations executed.
pub operations: Vec<VMOperation>,
/// The sub traces for each interior action performed as part of this call/create.
/// Thre is a 1:1 correspondance between these and a CALL/CREATE/CALLCODE/DELEGATECALL instruction.
pub subs: Vec<VMTrace>,
}
impl Encodable for VMTrace {
fn rlp_append(&self, s: &mut RlpStream) {
s.begin_list(4);
s.append(&self.parent_step);
s.append(&self.code);
s.append(&self.operations);
s.append(&self.subs);
}
}
impl Decodable for VMTrace {
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
let d = decoder.as_rlp();
let res = VMTrace {
parent_step: try!(d.val_at(0)),
code: try!(d.val_at(1)),
operations: try!(d.val_at(2)),
subs: try!(d.val_at(3)),
};
Ok(res)
}
}
#[cfg(test)]
mod tests {
use util::{Address, U256, FixedHash};