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

Provide read-only access to storage during tracing #9244

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion ethcore/evm/src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ pub trait Finalize {
}

impl Finalize for Result<GasLeft> {
fn finalize<E: Ext>(self, ext: E) -> Result<FinalizationResult> {
fn finalize<E: Ext>(self, mut ext: E) -> Result<FinalizationResult> {
ext.trace_finalized();

match self {
Ok(GasLeft::Known(gas_left)) => Ok(FinalizationResult { gas_left: gas_left, apply_state: true, return_data: ReturnData::empty() }),
Ok(GasLeft::NeedsReturn { gas_left, data, apply_state }) => ext.ret(&gas_left, &data, apply_state).map(|gas_left| FinalizationResult {
Expand Down
21 changes: 20 additions & 1 deletion ethcore/src/externalities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use vm::{
};
use transaction::UNSIGNED_SENDER;
use trace::{Tracer, VMTracer};
use storage::{StorageAccess, StorageError};

/// Policy for handling output data on `RETURN` opcode.
pub enum OutputPolicy {
Expand Down Expand Up @@ -411,7 +412,25 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for Externalities<'a, T, V, B>
}

fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem: &[u8]) {
self.vm_tracer.trace_executed(gas_used, stack_push, mem)
let Externalities { ref state, ref mut vm_tracer, .. } = *self;

let access = ExtStorageAccess(*state, &self.origin_info.address);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to change to a design where if NoopTracer is used, we don't create this struct? For example, this can be done by directly passing State reference into tracer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The benefit is pairing the storage access with the address of the contract so that we don't have to do this, making the storage access API more convenient.

What are the worries about from setting up this struct? The overhead should be negligible since we are mostly dealing with pointers.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah looks like the overhead is negligible. However, I wonder about other tracer usage for ethcore-as-a-library. People may want to build other tracer implementation that access to other state information (like other account's storage slot, account code, nonce, etc). So I'm just thinking maybe directly passing State would be more generalized and potentially fit more usage?

vm_tracer.trace_executed(gas_used, stack_push, mem, &access)
}

fn trace_finalized(&mut self) {
let Externalities { ref state, ref mut vm_tracer, .. } = *self;

let access = ExtStorageAccess(*state, &self.origin_info.address);
vm_tracer.trace_finalized(&access);
}
}

struct ExtStorageAccess<'a, 'b, B: 'a>(&'a State<B>, &'b Address);

impl<'a, 'b, B: 'a> StorageAccess for ExtStorageAccess<'a, 'b, B> where B: StateBackend {
fn storage_at(&self, key: &H256) -> Result<H256, StorageError> {
self.0.storage_at(self.1, key).map_err(Into::into)
}
}

Expand Down
1 change: 1 addition & 0 deletions ethcore/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ pub mod state;
pub mod state_db;
pub mod trace;
pub mod verification;
pub mod storage;

mod cache_manager;
mod account_db;
Expand Down
53 changes: 53 additions & 0 deletions ethcore/src/storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Parity.

// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.

//! Traits for accessing contract storage.

use ethereum_types::H256;
use ethtrie;
use std::fmt;

/// Errors from accessing storage.
#[derive(Debug, Clone, PartialEq)]
pub enum StorageError {
/// Trie lookup error.
TrieError {
/// Underlying error.
error: Box<ethtrie::TrieError>,
},
}

impl fmt::Display for StorageError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::StorageError::*;

match *self {
TrieError { ref error } => write!(f, "trie error: {}", error),
}
}
}

impl From<Box<ethtrie::TrieError>> for StorageError {
fn from(error: Box<ethtrie::TrieError>) -> Self {
StorageError::TrieError { error }
}
}

/// Storage access for the current VM.
pub trait StorageAccess {
/// Access storage at the given location.
fn storage_at(&self, key: &H256) -> Result<H256, StorageError>;
}
3 changes: 2 additions & 1 deletion ethcore/src/trace/executive_tracer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use ethereum_types::{U256, Address};
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};
use storage::StorageAccess;

/// Simple executive tracer. Traces all calls and creates. Ignores delegatecalls.
#[derive(Default)]
Expand Down Expand Up @@ -244,7 +245,7 @@ impl VMTracer for ExecutiveVMTracer {
self.last_store_written = store_written;
}

fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem: &[u8]) {
fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem: &[u8], _storage: &StorageAccess) {
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| {
Expand Down
8 changes: 7 additions & 1 deletion ethcore/src/trace/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ use ethereum_types::{H256, U256, Address};
use kvdb::DBTransaction;
use vm::{Error as VmError, ActionParams};
use header::BlockNumber;
use storage::StorageAccess;

/// This trait is used by executive to build traces.
pub trait Tracer: Send {
Expand Down Expand Up @@ -86,7 +87,12 @@ pub trait VMTracer: Send {
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: &[u8]) {}
fn trace_executed(&mut self, _gas_used: U256, _stack_push: &[U256], _mem: &[u8], _storage: &StorageAccess) {}

/// Trace the end of a subtrace.
///
/// After this, no more instructions will be executed for this subtrace.
fn trace_finalized(&mut self, _storage: &StorageAccess) {}

/// Spawn subtracer which will be used to trace deeper levels of execution.
fn prepare_subtrace(&mut self, _code: &[u8]) {}
Expand Down
3 changes: 3 additions & 0 deletions ethcore/vm/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ pub trait Ext {
/// Trace the finalised execution of a single instruction.
fn trace_executed(&mut self, _gas_used: U256, _stack_push: &[U256], _mem: &[u8]) {}

/// Trace that the subtrace is done executing.
fn trace_finalized(&mut self) {}

/// Check if running in static context.
fn is_static(&self) -> bool;
}
20 changes: 12 additions & 8 deletions evmbin/src/display/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use std::mem;

use ethereum_types::{U256, H256};
use bytes::ToPretty;
use ethcore::trace;
use ethcore::{trace, storage};

use display;
use info as vm;
Expand Down Expand Up @@ -122,7 +122,7 @@ impl trace::VMTracer for Informant {
});
}

fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem: &[u8]) {
fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem: &[u8], _storage: &storage::StorageAccess) {
let subdepth = self.subdepth;
Self::with_informant_in_depth(self, subdepth, |informant: &mut Informant| {
let mem_diff = informant.mem_written.clone().map(|(o, s)| (o, &(mem[o..o+s])));
Expand Down Expand Up @@ -168,6 +168,15 @@ impl trace::VMTracer for Informant {
});
}

fn trace_finalized(&mut self, storage: &storage::StorageAccess) {
if self.unmatched {
// print last line with final state:
self.gas_cost = 0.into();
let gas_used = self.gas_used;
self.trace_executed(gas_used, &[], &[], storage);
}
}

fn prepare_subtrace(&mut self, code: &[u8]) {
let subdepth = self.subdepth;
Self::with_informant_in_depth(self, subdepth, |informant: &mut Informant| {
Expand All @@ -191,12 +200,7 @@ impl trace::VMTracer for Informant {
}

fn drain(mut self) -> Option<Self::Output> {
if self.unmatched {
// print last line with final state:
self.gas_cost = 0.into();
let gas_used = self.gas_used;
self.trace_executed(gas_used, &[], &[]);
} else if !self.subtraces.is_empty() {
if !self.subtraces.is_empty() {
self.traces.extend(mem::replace(&mut self.subtraces, vec![]));
}
Some(self.traces)
Expand Down
4 changes: 2 additions & 2 deletions evmbin/src/display/std_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use std::io;

use ethereum_types::{H256, U256};
use bytes::ToPretty;
use ethcore::trace;
use ethcore::{trace, storage};

use display;
use info as vm;
Expand Down Expand Up @@ -168,7 +168,7 @@ impl<Trace: Writer, Out: Writer> trace::VMTracer for Informant<Trace, Out> {
});
}

fn trace_executed(&mut self, _gas_used: U256, stack_push: &[U256], _mem: &[u8]) {
fn trace_executed(&mut self, _gas_used: U256, stack_push: &[U256], _mem: &[u8], _storage: &storage::StorageAccess) {
let subdepth = self.subdepth;
Self::with_informant_in_depth(self, subdepth, |informant: &mut Informant<Trace, Out>| {
let info = ::evm::Instruction::from_u8(informant.instruction).map(|i| i.info());
Expand Down