Add a optional json dump state to evm-bin (#9706)
* Add a dump of the state at the end of transaction for --json-test. Also fixes json-test output on finish, and allow to put both on err or out (--out-only and --err-only). * Dump state resolution from trie, function behind evm-bin feature to avoid misuse. * Rename 'slow' method to 'to_pod_full'. Use cache first in 'to_pod_full', for in between commits case. Change dump activation to use a function pointer instead. * Fix tests. * Query and add storage values to dump. * Switch to use `require` method, even if less efficient it is better in this case to reuse existing code. Reuse of `storage_at` was not easy in this case (could not iterate and use the method at the same time (refcell mutable borrow panics) so keeping code as is. * Switch to returning error. Use 'base_storage_root' instead of 'storage_root'. Added a test, it will only execute with json-test in ci, or when launch with the feature. * Renaming of command line parameters. Comments fixes. Minor code changes. * Fix evmbin cmd parsing test. * README update. * Fix extra space and avoid clone call on copiable address. * Revert test submodule. * Revert wasm-test submodule. * Use map_or instead of map + unwrap_or * restore tests submodule
This commit is contained in:
@@ -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> {
|
||||
@@ -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();
|
||||
/// Takes a `TrieSpec` to set the type of trie.
|
||||
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 given PodState.
|
||||
pub fn from_pod_state(spec: &'a spec::Spec, pod_state: pod_state::PodState) -> Result<Self, EvmTestError> {
|
||||
let factories = Self::factories();
|
||||
/// Creates new EVM test client with an in-memory DB initialized with genesis of given chain Spec.
|
||||
pub fn new(spec: &'a spec::Spec) -> Result<Self, EvmTestError> {
|
||||
Self::new_with_trie(spec, trie::TrieSpec::Secure)
|
||||
}
|
||||
|
||||
/// Creates new EVM test client with an in-memory DB initialized with given PodState.
|
||||
/// Takes a `TrieSpec` to set the type of trie.
|
||||
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 an in-memory DB initialized with given PodState.
|
||||
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(),
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -295,6 +327,8 @@ 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 {
|
||||
@@ -302,5 +336,7 @@ pub enum TransactResult<T, V> {
|
||||
state_root: H256,
|
||||
/// Execution error
|
||||
error: ::error::Error,
|
||||
/// end state if needed
|
||||
end_state: Option<pod_state::PodState>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -109,6 +109,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;
|
||||
|
||||
@@ -134,6 +135,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;
|
||||
@@ -187,3 +190,4 @@ pub use types::*;
|
||||
pub use executive::contract_address;
|
||||
pub use evm::CreateContractAddress;
|
||||
pub use blockchain::{BlockChainDB, BlockChainDBHandler};
|
||||
pub use trie::TrieSpec;
|
||||
|
||||
@@ -32,8 +32,10 @@ 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 {
|
||||
@@ -41,12 +43,19 @@ pub struct PodAccount {
|
||||
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
|
||||
{
|
||||
serializer.collect_str(&format_args!("0x{}",opt_bytes.as_ref().map_or("".to_string(), |b|b.to_hex())))
|
||||
}
|
||||
|
||||
impl PodAccount {
|
||||
/// Convert Account to a PodAccount.
|
||||
/// NOTE: This will silently fail unless the account is fully cached.
|
||||
|
||||
@@ -26,8 +26,8 @@ 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)]
|
||||
pub struct PodState (BTreeMap<Address, PodAccount>);
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct PodState(BTreeMap<Address, PodAccount>);
|
||||
|
||||
impl PodState {
|
||||
/// Contruct a new object from the `m`.
|
||||
|
||||
@@ -624,7 +624,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() {
|
||||
|
||||
@@ -947,20 +947,78 @@ 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.
|
||||
// will need fat db.
|
||||
PodState::from(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.insert(*add, PodAccount::from_account(acc));
|
||||
}
|
||||
m
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(feature="to-pod-full")]
|
||||
/// Populate a PodAccount map from this state.
|
||||
/// 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) -> Result<PodState, Error> {
|
||||
|
||||
assert!(self.checkpoints.borrow().is_empty());
|
||||
assert!(self.factories.trie.is_fat());
|
||||
|
||||
let mut result = BTreeMap::new();
|
||||
|
||||
let trie = self.factories.trie.readonly(self.db.as_hashdb(), &self.root)?;
|
||||
|
||||
// put trie in cache
|
||||
for item in trie.iter()? {
|
||||
if let Ok((addr, _dbval)) = item {
|
||||
let address = Address::from_slice(&addr);
|
||||
let _ = self.require(&address, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve missing part
|
||||
for (add, opt) in self.cache.borrow().iter() {
|
||||
if let Some(ref acc) = opt.account {
|
||||
let pod_account = self.account_to_pod_account(acc, add)?;
|
||||
result.insert(add.clone(), pod_account);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(PodState::from(result))
|
||||
}
|
||||
|
||||
/// Create a PodAccount from an account.
|
||||
/// Differs from existing method by including all storage
|
||||
/// values of the account to the PodAccount.
|
||||
/// This function is only intended for use in small tests or with fresh accounts.
|
||||
/// It requires FatDB.
|
||||
#[cfg(feature="to-pod-full")]
|
||||
fn account_to_pod_account(&self, account: &Account, address: &Address) -> Result<PodAccount, Error> {
|
||||
let mut pod_storage = BTreeMap::new();
|
||||
let addr_hash = account.address_hash(address);
|
||||
let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), addr_hash);
|
||||
let root = account.base_storage_root();
|
||||
|
||||
let trie = self.factories.trie.readonly(accountdb.as_hashdb(), &root)?;
|
||||
for o_kv in trie.iter()? {
|
||||
if let Ok((key, val)) = o_kv {
|
||||
pod_storage.insert(key[..].into(), U256::from(&val[..]).into());
|
||||
}
|
||||
}
|
||||
|
||||
let mut pod_account = PodAccount::from_account(&account);
|
||||
// cached one first
|
||||
pod_storage.append(&mut pod_account.storage);
|
||||
pod_account.storage = pod_storage;
|
||||
Ok(pod_account)
|
||||
}
|
||||
|
||||
|
||||
/// 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`.
|
||||
@@ -1015,7 +1073,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))
|
||||
}
|
||||
@@ -2593,12 +2651,12 @@ mod tests {
|
||||
assert_eq!(diff_map.len(), 1);
|
||||
assert!(diff_map.get(&a).is_some());
|
||||
assert_eq!(diff_map.get(&a),
|
||||
pod_account::diff_pod(Some(&PodAccount {
|
||||
balance: U256::from(100),
|
||||
nonce: U256::zero(),
|
||||
code: Some(Default::default()),
|
||||
storage: Default::default()
|
||||
}), None).as_ref());
|
||||
pod_account::diff_pod(Some(&PodAccount {
|
||||
balance: U256::from(100),
|
||||
nonce: U256::zero(),
|
||||
code: Some(Default::default()),
|
||||
storage: Default::default()
|
||||
}), None).as_ref());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2624,18 +2682,64 @@ mod tests {
|
||||
assert_eq!(diff_map.len(), 1);
|
||||
assert!(diff_map.get(&a).is_some());
|
||||
assert_eq!(diff_map.get(&a),
|
||||
pod_account::diff_pod(Some(&PodAccount {
|
||||
balance: U256::zero(),
|
||||
nonce: U256::zero(),
|
||||
code: Some(Default::default()),
|
||||
storage: vec![(H256::from(&U256::from(1u64)), H256::from(&U256::from(20u64)))]
|
||||
.into_iter().collect(),
|
||||
}), Some(&PodAccount {
|
||||
balance: U256::zero(),
|
||||
nonce: U256::zero(),
|
||||
code: Some(Default::default()),
|
||||
storage: vec![(H256::from(&U256::from(1u64)), H256::from(&U256::from(100u64)))]
|
||||
.into_iter().collect(),
|
||||
})).as_ref());
|
||||
pod_account::diff_pod(Some(&PodAccount {
|
||||
balance: U256::zero(),
|
||||
nonce: U256::zero(),
|
||||
code: Some(Default::default()),
|
||||
storage: vec![(H256::from(&U256::from(1u64)), H256::from(&U256::from(20u64)))]
|
||||
.into_iter().collect(),
|
||||
}), Some(&PodAccount {
|
||||
balance: U256::zero(),
|
||||
nonce: U256::zero(),
|
||||
code: Some(Default::default()),
|
||||
storage: vec![(H256::from(&U256::from(1u64)), H256::from(&U256::from(100u64)))]
|
||||
.into_iter().collect(),
|
||||
})).as_ref());
|
||||
}
|
||||
|
||||
#[cfg(feature="to-pod-full")]
|
||||
#[test]
|
||||
fn should_get_full_pod_storage_values() {
|
||||
use trie::{TrieFactory, TrieSpec};
|
||||
|
||||
let a = 10.into();
|
||||
let db = get_temp_state_db();
|
||||
|
||||
let factories = Factories {
|
||||
vm: Default::default(),
|
||||
trie: TrieFactory::new(TrieSpec::Fat),
|
||||
accountdb: Default::default(),
|
||||
};
|
||||
|
||||
let get_pod_state_val = |pod_state : &PodState, ak, k| {
|
||||
pod_state.get().get(ak).unwrap().storage.get(&k).unwrap().clone()
|
||||
};
|
||||
|
||||
let storage_address = H256::from(&U256::from(1u64));
|
||||
|
||||
let (root, db) = {
|
||||
let mut state = State::new(db, U256::from(0), factories.clone());
|
||||
state.set_storage(&a, storage_address.clone(), H256::from(&U256::from(20u64))).unwrap();
|
||||
let dump = state.to_pod_full().unwrap();
|
||||
assert_eq!(get_pod_state_val(&dump, &a, storage_address.clone()), H256::from(&U256::from(20u64)));
|
||||
state.commit().unwrap();
|
||||
let dump = state.to_pod_full().unwrap();
|
||||
assert_eq!(get_pod_state_val(&dump, &a, storage_address.clone()), H256::from(&U256::from(20u64)));
|
||||
state.drop()
|
||||
};
|
||||
|
||||
let mut state = State::from_existing(db, root, U256::from(0u8), factories).unwrap();
|
||||
let dump = state.to_pod_full().unwrap();
|
||||
assert_eq!(get_pod_state_val(&dump, &a, storage_address.clone()), H256::from(&U256::from(20u64)));
|
||||
state.set_storage(&a, storage_address.clone(), H256::from(&U256::from(21u64))).unwrap();
|
||||
let dump = state.to_pod_full().unwrap();
|
||||
assert_eq!(get_pod_state_val(&dump, &a, storage_address.clone()), H256::from(&U256::from(21u64)));
|
||||
state.commit().unwrap();
|
||||
state.set_storage(&a, storage_address.clone(), H256::from(&U256::from(0u64))).unwrap();
|
||||
let dump = state.to_pod_full().unwrap();
|
||||
assert_eq!(get_pod_state_val(&dump, &a, storage_address.clone()), H256::from(&U256::from(0u64)));
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user