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

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