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

Add a optional json dump state to evm-bin #9706

Merged
merged 16 commits into from
Nov 25, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions ethcore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ unexpected = { path = "../util/unexpected" }
journaldb = { path = "../util/journaldb" }
keccak-hasher = { path = "../util/keccak-hasher" }
kvdb-rocksdb = "0.1.3"
serde = "1.0"
serde_derive = "1.0"
tempdir = {version="0.3", optional = true}

[target.'cfg(any(target_os = "linux", target_os = "macos", target_os = "windows", target_os = "android"))'.dependencies]
Expand Down
20 changes: 19 additions & 1 deletion ethcore/src/client/evm_test_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,15 @@ impl<'a> EvmTestClient<'a> {
return TransactResult::Err {
state_root: *self.state.root(),
error: error.into(),
end_state: if vm_tracer.dump_end_state() {
Some(self.state.to_pod())
} else {
None
},
};
}

let do_dump_state = vm_tracer.dump_end_state();
// Apply transaction
let result = self.state.apply_with_tracing(&env_info, self.spec.engine.machine(), &transaction, tracer, vm_tracer);
let scheme = self.spec.engine.machine().create_address_scheme(env_info.number);
Expand All @@ -249,6 +255,12 @@ impl<'a> EvmTestClient<'a> {
).ok();
self.state.commit().ok();

let end_state = if do_dump_state {
Some(self.state.to_pod())
} else {
None
};

match result {
Ok(result) => {
TransactResult::Ok {
Expand All @@ -263,12 +275,14 @@ impl<'a> EvmTestClient<'a> {
Some(executive::contract_address(scheme, &transaction.sender(), &transaction.nonce, &transaction.data).0)
} else {
None
}
},
end_state,
}
},
Err(error) => TransactResult::Err {
state_root: *self.state.root(),
error,
end_state,
},
}
}
Expand All @@ -295,12 +309,16 @@ pub enum TransactResult<T, V> {
logs: Vec<log_entry::LogEntry>,
/// outcome
outcome: receipt::TransactionOutcome,
/// end state if needed
end_state: Option<pod_state::PodState>,
},
/// Transaction failed to run
Err {
/// State root
state_root: H256,
/// Execution error
error: ::error::Error,
/// end state if needed
end_state: Option<pod_state::PodState>,
},
}
2 changes: 1 addition & 1 deletion ethcore/src/json_tests/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ pub fn json_chain_test<H: FnMut(&str, HookType)>(json_data: &[u8], start_stop_ho
flushln!("{} fail", info);
failed.push(name.clone());
},
Ok(TransactResult::Err { state_root, ref error }) if state_root != post_root => {
Ok(TransactResult::Err { state_root, ref error, .. }) if state_root != post_root => {
println!("{} !!! State mismatch (got: {}, expect: {}", info, state_root, post_root);
println!("{} !!! Execution error: {:?}", info, error);
flushln!("{} fail", info);
Expand Down
3 changes: 3 additions & 0 deletions ethcore/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ extern crate vm;
extern crate wasm;
extern crate memory_cache;
extern crate journaldb;
extern crate serde;
#[cfg(any(test, feature = "json-tests", feature = "test-helpers"))]
extern crate tempdir;

Expand All @@ -135,6 +136,8 @@ extern crate macros;
extern crate rlp_derive;
#[macro_use]
extern crate trace_time;
#[macro_use]
extern crate serde_derive;

#[cfg_attr(test, macro_use)]
extern crate evm;
Expand Down
10 changes: 9 additions & 1 deletion ethcore/src/pod_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,29 @@ use state::Account;
use ethjson;
use types::account_diff::*;
use rlp::{self, RlpStream};
use serde::Serializer;
use rustc_hex::ToHex;

#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
/// An account, expressed as Plain-Old-Data (hence the name).
/// Does not have a DB overlay cache, code hash or anything like that.
pub struct PodAccount {
/// The balance of the account.
pub balance: U256,
/// The nonce of the account.
pub nonce: U256,
#[serde(serialize_with="opt_bytes_to_hex")]
/// The code of the account or `None` in the special case that it is unknown.
pub code: Option<Bytes>,
/// The storage of the account.
pub storage: BTreeMap<H256, H256>,
}

fn opt_bytes_to_hex<S>(opt_bytes: &Option<Bytes>, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer {
Copy link
Collaborator

Choose a reason for hiding this comment

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

odd whitespace here, should be

where
    S: Serializer
{

serializer.collect_str(&format_args!("0x{}",opt_bytes.as_ref().map(|b|b.to_hex()).unwrap_or("".to_string())))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Bytes does not impl ToLowerHex (so you can use {:#x})? If not that's something we should fix.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It does not implement it, alas Bytes is only a type alias for Vec<u8>, so it should be quite a refact to fix it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Consider to replace map().unwrap_or() with map_or_else!

}

impl PodAccount {
/// Convert Account to a PodAccount.
/// NOTE: This will silently fail unless the account is fully cached.
Expand Down
2 changes: 1 addition & 1 deletion ethcore/src/pod_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use types::state_diff::StateDiff;
use ethjson;

/// State of all accounts in the system expressed in Plain Old Data.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
pub struct PodState (BTreeMap<Address, PodAccount>);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not related to this PR, but there's an extra space between PodState and (BTreeMap.


impl PodState {
Expand Down
2 changes: 1 addition & 1 deletion ethcore/src/state/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ mod tests {
assert!(raw.len() > compact_vec.len());
let again_raw = decompress(&compact_vec, snapshot_swapper());
assert_eq!(raw, again_raw.into_vec());
}
}

#[test]
fn storage_at() {
Expand Down
4 changes: 4 additions & 0 deletions ethcore/src/trace/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ pub trait VMTracer: Send {

/// Consumes self and returns the VM trace.
fn drain(self) -> Option<Self::Output>;

/// Indicate if state dump is needed.
fn dump_end_state(&self) -> bool { false }

}

/// `DbExtras` provides an interface to query extra data which is not stored in tracesdb,
Expand Down
2 changes: 1 addition & 1 deletion ethcore/wasm/run/src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ impl Fail {
}

impl fmt::Display for Fail {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Copy link
Collaborator

Choose a reason for hiding this comment

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

typo -- looks like incorrect indentation.

use self::Fail::*;
match *self {
Return { ref expected, ref actual } =>
Expand Down
6 changes: 5 additions & 1 deletion evmbin/src/display/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ impl Informant {
}

impl vm::Informant for Informant {
type Sink = ();

fn before_test(&mut self, name: &str, action: &str) {
println!("{}", json!({"action": action, "test": name}));
}
Expand All @@ -66,7 +68,9 @@ impl vm::Informant for Informant {
self.gas_used = gas;
}

fn finish(result: vm::RunResult<Self::Output>) {
fn clone_sink(&self) -> Self::Sink { () }

fn finish(result: vm::RunResult<Self::Output>, _sink: &mut Self::Sink) {
match result {
Ok(success) => {
for trace in success.traces.unwrap_or_else(Vec::new) {
Expand Down
7 changes: 6 additions & 1 deletion evmbin/src/display/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,16 @@ use info as vm;
pub struct Informant;

impl vm::Informant for Informant {

type Sink = ();

fn before_test(&mut self, name: &str, action: &str) {
println!("Test: {} ({})", name, action);
}

fn finish(result: vm::RunResult<Self::Output>) {
fn clone_sink(&self) -> Self::Sink { () }

fn finish(result: vm::RunResult<Self::Output>, _sink: &mut Self::Sink) {
match result {
Ok(success) => {
println!("Output: 0x{}", success.output.to_hex());
Expand Down
67 changes: 58 additions & 9 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, pod_state};

use display;
use info as vm;
Expand Down Expand Up @@ -52,10 +52,11 @@ impl Writer for io::Stderr {
}

/// JSON formatting informant.
pub struct Informant<Trace = io::Stderr, Out = io::Stdout> {
pub struct Informant<Trace, Out> {
code: Vec<u8>,
instruction: u8,
depth: usize,
dump_state: bool,
stack: Vec<U256>,
storage: HashMap<H256, H256>,
subinfos: Vec<Informant<Trace, Out>>,
Expand All @@ -64,18 +65,41 @@ pub struct Informant<Trace = io::Stderr, Out = io::Stdout> {
out_sink: Out,
}

impl Default for Informant {
impl<Trace, Out> Informant<Trace, Out> {
/// Activate or deactivate final dump state
pub fn dump_state(&mut self, do_dump : bool) {
self.dump_state = do_dump;
}
}

impl Default for Informant<io::Stderr, io::Stdout> {
fn default() -> Self {
Self::new(io::stderr(), io::stdout())
}
}

impl Informant<io::Stdout, io::Stdout> {
/// std json informant using out only.
pub fn out_only() -> Self {
Self::new(io::stdout(), io::stdout())
}
}

impl Informant<io::Stderr, io::Stderr> {
/// std json informant using err only.
pub fn err_only() -> Self {
Self::new(io::stderr(), io::stderr())
}
}

impl<Trace: Writer, Out: Writer> Informant<Trace, Out> {

pub fn new(trace_sink: Trace, out_sink: Out) -> Self {
Informant {
code: Default::default(),
instruction: Default::default(),
depth: Default::default(),
dump_state: false,
stack: Default::default(),
storage: Default::default(),
subinfos: Default::default(),
Expand All @@ -91,9 +115,24 @@ impl<Trace: Writer, Out: Writer> Informant<Trace, Out> {
Self::with_informant_in_depth(informant.subinfos.last_mut().expect("prepare/done_trace are not balanced"), depth - 1, f);
}
}

fn dump_state_into(trace_sink: &mut Trace, root: H256, end_state: &Option<pod_state::PodState>) {
if let Some(ref end_state) = end_state {
let dump_data = json!({
"root": root,
"accounts": end_state,
});
writeln!(trace_sink, "{}", dump_data).expect("The sink must be writeable.");
}
}


}

impl<Trace: Writer, Out: Writer> vm::Informant for Informant<Trace, Out> {

type Sink = (Trace, Out);

fn before_test(&mut self, name: &str, action: &str) {
let out_data = json!({
"action": action,
Expand All @@ -105,23 +144,26 @@ impl<Trace: Writer, Out: Writer> vm::Informant for Informant<Trace, Out> {

fn set_gas(&mut self, _gas: U256) {}

fn finish(result: vm::RunResult<<Self as trace::VMTracer>::Output>) {
let mut trace_sink = Trace::default();
let mut out_sink = Out::default();
fn clone_sink(&self) -> Self::Sink {
(self.trace_sink.clone(), self.out_sink.clone())
}
fn finish(result: vm::RunResult<<Self as trace::VMTracer>::Output>, (ref mut trace_sink, ref mut out_sink): &mut Self::Sink) {

match result {
Ok(success) => {
let trace_data = json!({"stateRoot": success.state_root});
writeln!(&mut trace_sink, "{}", trace_data)
writeln!(trace_sink, "{}", trace_data)
.expect("The sink must be writeable.");

Self::dump_state_into(trace_sink, success.state_root, &success.end_state);

let out_data = json!({
"output": format!("0x{}", success.output.to_hex()),
"gasUsed": format!("{:#x}", success.gas_used),
"time": display::as_micros(&success.time),
});

writeln!(&mut out_sink, "{}", out_data).expect("The sink must be writeable.");
writeln!(out_sink, "{}", out_data).expect("The sink must be writeable.");
},
Err(failure) => {
let out_data = json!({
Expand All @@ -130,7 +172,9 @@ impl<Trace: Writer, Out: Writer> vm::Informant for Informant<Trace, Out> {
"time": display::as_micros(&failure.time),
});

writeln!(&mut out_sink, "{}", out_data).expect("The sink must be writeable.");
Self::dump_state_into(trace_sink, failure.state_root, &failure.end_state);

writeln!(out_sink, "{}", out_data).expect("The sink must be writeable.");
},
}
}
Expand Down Expand Up @@ -200,6 +244,11 @@ impl<Trace: Writer, Out: Writer> trace::VMTracer for Informant<Trace, Out> {
}

fn drain(self) -> Option<Self::Output> { None }

fn dump_end_state(&self) -> bool {
self.dump_state
}

}

#[cfg(test)]
Expand Down
Loading