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 4 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
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.

4 changes: 4 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 Expand Up @@ -110,3 +112,5 @@ test-heavy = []
benches = []
# Compile test helpers
test-helpers = ["tempdir"]
# Slow method to-pod-full for evmbin or test only.
Copy link
Collaborator

Choose a reason for hiding this comment

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

"Enables slow to_pod_full method for use in tests and evmbin"?

to-pod-full = []
54 changes: 45 additions & 9 deletions ethcore/src/client/evm_test_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ use ethjson::spec::ForkSpec;
pub struct EvmTestClient<'a> {
state: state::State<state_db::StateDB>,
spec: &'a spec::Spec,
dump_state: fn(&state::State<state_db::StateDB>) -> Option<pod_state::PodState>,
}

fn no_dump_state(_: &state::State<state_db::StateDB>) -> Option<pod_state::PodState> {
None
}

impl<'a> fmt::Debug for EvmTestClient<'a> {
Expand All @@ -92,32 +97,51 @@ impl<'a> EvmTestClient<'a> {
}
}

/// Change default function for dump state (default does not dump)
pub fn set_dump_state_fn(&mut self, dump_state: fn(&state::State<state_db::StateDB>) -> Option<pod_state::PodState>) {
self.dump_state = dump_state;
}

/// Creates new EVM test client with in-memory DB initialized with genesis of given Spec.
pub fn new(spec: &'a spec::Spec) -> Result<Self, EvmTestError> {
let factories = Self::factories();
/// You can specify a specific trie kind.
Copy link
Collaborator

Choose a reason for hiding this comment

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

"Takes a TrieSpec to set the type of trie: Generic, Fat, Secure"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am skipping the different type of trie (chance of leading to outdated comment in case of change on the variants).

pub fn new_with_trie(spec: &'a spec::Spec, trie_spec: trie::TrieSpec) -> Result<Self, EvmTestError> {
let factories = Self::factories(trie_spec);
let state = Self::state_from_spec(spec, &factories)?;

Ok(EvmTestClient {
state,
spec,
dump_state: no_dump_state,
})
}

/// Creates new EVM test client with in-memory DB initialized with genesis of given Spec.
Copy link
Collaborator

Choose a reason for hiding this comment

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

s/with in-memory/with an in-memory/

("…of given kind": but the code says it's using the Secure kind so I think the doc needs to change a bit)

pub fn new(spec: &'a spec::Spec) -> Result<Self, EvmTestError> {
Self::new_with_trie(spec, trie::TrieSpec::Secure)
}

/// Creates new EVM test client with in-memory DB initialized with given PodState.
pub fn from_pod_state(spec: &'a spec::Spec, pod_state: pod_state::PodState) -> Result<Self, EvmTestError> {
let factories = Self::factories();
/// You can specify a specific trie kind.
Copy link
Collaborator

Choose a reason for hiding this comment

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

"Takes a TrieSpec to set the type of trie: Generic, Fat, Secure"

pub fn from_pod_state_with_trie(spec: &'a spec::Spec, pod_state: pod_state::PodState, trie_spec: trie::TrieSpec) -> Result<Self, EvmTestError> {
let factories = Self::factories(trie_spec);
let state = Self::state_from_pod(spec, &factories, pod_state)?;

Ok(EvmTestClient {
state,
spec,
dump_state: no_dump_state,
})
}

fn factories() -> Factories {
/// Creates new EVM test client with in-memory DB initialized with given PodState.
Copy link
Collaborator

Choose a reason for hiding this comment

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

s/with in-memory/with an in-memory/

pub fn from_pod_state(spec: &'a spec::Spec, pod_state: pod_state::PodState) -> Result<Self, EvmTestError> {
Self::from_pod_state_with_trie(spec, pod_state, trie::TrieSpec::Secure)
}

fn factories(trie_spec: trie::TrieSpec) -> Factories {
Factories {
vm: factory::VmFactory::new(VMType::Interpreter, 5 * 1024),
trie: trie::TrieFactory::new(trie::TrieSpec::Secure),
trie: trie::TrieFactory::new(trie_spec),
accountdb: Default::default(),
}
}
Expand Down Expand Up @@ -223,6 +247,7 @@ impl<'a> EvmTestClient<'a> {
return TransactResult::Err {
state_root: *self.state.root(),
error: error.into(),
end_state: (self.dump_state)(&self.state),
};
}

Expand All @@ -247,12 +272,17 @@ impl<'a> EvmTestClient<'a> {
&None,
false
).ok();

self.state.commit().ok();

let state_root = *self.state.root();

let end_state = (self.dump_state)(&self.state);

match result {
Ok(result) => {
TransactResult::Ok {
state_root: *self.state.root(),
state_root,
gas_left: initial_gas - result.receipt.gas_used,
outcome: result.receipt.outcome,
output: result.output,
Expand All @@ -263,12 +293,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(),
state_root,
error,
end_state,
},
}
}
Expand All @@ -295,12 +327,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
4 changes: 4 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 Expand Up @@ -185,3 +188,4 @@ pub use types::*;
pub use executive::contract_address;
pub use evm::CreateContractAddress;
pub use blockchain::{BlockChainDB, BlockChainDBHandler};
pub use trie::TrieSpec;
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
61 changes: 56 additions & 5 deletions ethcore/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -940,20 +940,71 @@ impl<B: Backend> State<B> {
}

/// Populate a PodAccount map from this state.
pub fn to_pod(&self) -> PodState {
fn to_pod_cache(&self) -> PodState {
assert!(self.checkpoints.borrow().is_empty());
// TODO: handle database rather than just the cache.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this TODO still relevant after this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, the new method and renaming makes it somehow explicit. (in fact I should have read it, it was a good comment because it spotted the fact that fatdb would be need).

// will need fat db.
PodState::from(self.cache.borrow().iter().fold(BTreeMap::new(), |mut m, (add, opt)| {
PodState::from(self.to_pod_cache_inner())
}

#[inline]
fn to_pod_cache_inner(&self) -> BTreeMap<Address, PodAccount> {
self.cache.borrow().iter().fold(BTreeMap::new(), |mut m, (add, opt)| {
if let Some(ref acc) = opt.account {
m.insert(add.clone(), PodAccount::from_account(acc));
}
m
}))
})
}


#[cfg(feature="to-pod-full")]
/// Populate a PodAccount map from this state.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add docs here that this requires FatDB.

/// Warning this is not for real time use.
/// Use of this method requires FatDB mode to be able
/// to iterate on accounts.
pub fn to_pod_full(&self) -> PodState {
use ethereum_types::H160;

assert!(self.checkpoints.borrow().is_empty());
assert!(self.factories.trie.is_fat());
Copy link
Collaborator

Choose a reason for hiding this comment

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

Seems a bit rough to panic here, why not return an error?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd rather panic here, unless/untill we got coding rules that force error checking (I did not check it currently), forcing some 'clippy' rules in ci could really be an idea.
In the context of this function (very specific use, not in critical context), I think the assert is ok (the function should never be called if not 'fat' and if it is called it is a bug to fix).


let mut result = self.to_pod_cache_inner();

let trie = match self.factories.trie.readonly(self.db.as_hashdb(), &self.root) {
Ok(trie) => trie,
_ => {
trace!(target: "state", "to_pod_full: Couldn't open the DB");
return PodState::from(result);
}
};

let iter = match trie.iter() {
Ok(iter) => iter,
_ => return PodState::from(result),
};

let from_rlp = |b: &[u8]| Account::from_rlp(b).expect("decoding db value failed");

for item in iter {
if let Ok((addr, dbval)) = item {
result.entry(Address::from_slice(&addr)).or_insert_with(|| {
let mut account = from_rlp(&dbval[..]);
if account.code_size().is_none() {
let addr_hash = account.address_hash(&H160::from(&addr[..]));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry I missed this -- this will also not completely work -- for a fresh account object, we would miss all storage items! And I do believe we have plenty of tests that deal with storage items. So I think we would also need to fix this.

We do already have logics in cli::blockchain::execute_export_state that do this. You can refer to its logic, and I would recommend one of the two ways to deal with this.

Refactor Client::list_accounts and Client::list_storage

Currently they only work for real client, but not EVM test client, but I believe it would be possible to extend this interface, and change the count argument to accept Option. With this, we change the signature to to_pod_full(&self, accounts: HashMap<Address, Vec<H256>>), where for the hashmap, keys are account addresses, and values are existing storage items.

Within this function, call State::require for all addresses, and call State::storage_at for all storage items. After that, we can use State::to_pod_cache safely to get all addresses.

Simpler without Refactoring

Simply use the same logic as Client::list_accounts and Client::list_storage, but copy them to State, gather HashMap<Address, Vec<H256>> there. And use State::require, State::storage_at, and State::to_pod_cache.

let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), addr_hash);
let _ = account.cache_code(accountdb.as_hashdb());
}
PodAccount::from_account(&account)
});
}
}

PodState::from(result)
}

/// Populate a PodAccount map from this state, with another state as the account and storage query.
pub fn to_pod_diff<X: Backend>(&mut self, query: &State<X>) -> TrieResult<PodState> {
fn to_pod_diff<X: Backend>(&mut self, query: &State<X>) -> TrieResult<PodState> {
assert!(self.checkpoints.borrow().is_empty());

// Merge PodAccount::to_pod for cache of self and `query`.
Expand Down Expand Up @@ -1008,7 +1059,7 @@ impl<B: Backend> State<B> {
/// Returns a `StateDiff` describing the difference from `orig` to `self`.
/// Consumes self.
pub fn diff_from<X: Backend>(&self, mut orig: State<X>) -> TrieResult<StateDiff> {
let pod_state_post = self.to_pod();
let pod_state_post = self.to_pod_cache();
let pod_state_pre = orig.to_pod_diff(self)?;
Ok(pod_state::diff_pod(&pod_state_pre, &pod_state_post))
}
Expand Down
1 change: 1 addition & 0 deletions ethcore/src/trace/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ pub trait VMTracer: Send {

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

}

/// `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 {
use self::Fail::*;
match *self {
Return { ref expected, ref actual } =>
Expand Down
2 changes: 1 addition & 1 deletion evmbin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ path = "./src/main.rs"
[dependencies]
docopt = "0.8"
env_logger = "0.5"
ethcore = { path = "../ethcore", features = ["test-helpers", "json-tests"] }
ethcore = { path = "../ethcore", features = ["test-helpers", "json-tests", "to-pod-full"] }
ethjson = { path = "../json" }
parity-bytes = "0.1"
ethcore-transaction = { path = "../ethcore/transaction" }
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
Loading