Running state test using parity-evm (#6355)

* Initial version of state tests.

* Refactor state to support tracing.

* Unify TransactResult.

* Add test.
This commit is contained in:
Tomasz Drwięga 2017-08-28 14:25:16 +02:00 committed by Arkadiy Paronyan
parent abecd80f54
commit f9a08e285c
17 changed files with 538 additions and 159 deletions

1
Cargo.lock generated
View File

@ -935,6 +935,7 @@ dependencies = [
"docopt 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore 1.8.0",
"ethcore-util 1.8.0",
"ethjson 0.1.0",
"evm 0.1.0",
"panic_hook 0.1.0",
"rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -748,7 +748,7 @@ impl Client {
self.factories.clone(),
).expect("state known to be available for just-imported block; qed");
let options = TransactOptions { tracing: false, vm_tracing: false, check_nonce: false };
let options = TransactOptions::with_no_tracing().dont_check_nonce();
let res = Executive::new(&mut state, &env_info, &*self.engine)
.transact(&transaction, options);
@ -1113,18 +1113,39 @@ impl Client {
}.fake_sign(from)
}
fn do_call(&self, env_info: &EnvInfo, state: &mut State<StateDB>, t: &SignedTransaction, analytics: CallAnalytics) -> Result<Executed, CallError> {
let original_state = if analytics.state_diffing { Some(state.clone()) } else { None };
fn do_virtual_call(&self, env_info: &EnvInfo, state: &mut State<StateDB>, t: &SignedTransaction, analytics: CallAnalytics) -> Result<Executed, CallError> {
fn call<E, V, T>(
state: &mut State<StateDB>,
env_info: &EnvInfo,
engine: &E,
state_diff: bool,
transaction: &SignedTransaction,
options: TransactOptions<T, V>,
) -> Result<Executed, CallError> where
E: Engine + ?Sized,
T: trace::Tracer,
V: trace::VMTracer,
{
let options = options.dont_check_nonce();
let original_state = if state_diff { Some(state.clone()) } else { None };
let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false };
let mut ret = Executive::new(state, env_info, &*self.engine).transact_virtual(t, options)?;
let mut ret = Executive::new(state, env_info, engine).transact_virtual(transaction, options)?;
// TODO gav move this into Executive.
if let Some(original) = original_state {
ret.state_diff = Some(state.diff_from(original).map_err(ExecutionError::from)?);
if let Some(original) = original_state {
ret.state_diff = Some(state.diff_from(original).map_err(ExecutionError::from)?);
}
Ok(ret)
}
Ok(ret)
let state_diff = analytics.state_diffing;
let engine = &*self.engine;
match (analytics.transaction_tracing, analytics.vm_tracing) {
(true, true) => call(state, env_info, engine, state_diff, t, TransactOptions::with_tracing_and_vm_tracing()),
(true, false) => call(state, env_info, engine, state_diff, t, TransactOptions::with_tracing()),
(false, true) => call(state, env_info, engine, state_diff, t, TransactOptions::with_vm_tracing()),
(false, false) => call(state, env_info, engine, state_diff, t, TransactOptions::with_no_tracing()),
}
}
}
@ -1157,7 +1178,7 @@ impl BlockChainClient for Client {
// that's just a copy of the state.
let mut state = self.state_at(block).ok_or(CallError::StatePruned)?;
self.do_call(&env_info, &mut state, transaction, analytics)
self.do_virtual_call(&env_info, &mut state, transaction, analytics)
}
fn call_many(&self, transactions: &[(SignedTransaction, CallAnalytics)], block: BlockId) -> Result<Vec<Executed>, CallError> {
@ -1169,7 +1190,7 @@ impl BlockChainClient for Client {
let mut results = Vec::with_capacity(transactions.len());
for &(ref t, analytics) in transactions {
let ret = self.do_call(&env_info, &mut state, t, analytics)?;
let ret = self.do_virtual_call(&env_info, &mut state, t, analytics)?;
env_info.gas_used = ret.cumulative_gas_used;
results.push(ret);
}
@ -1189,7 +1210,7 @@ impl BlockChainClient for Client {
// that's just a copy of the state.
let original_state = self.state_at(block).ok_or(CallError::StatePruned)?;
let sender = t.sender();
let options = TransactOptions { tracing: true, vm_tracing: false, check_nonce: false };
let options = || TransactOptions::with_tracing();
let cond = |gas| {
let mut tx = t.as_unsigned().clone();
@ -1198,7 +1219,7 @@ impl BlockChainClient for Client {
let mut state = original_state.clone();
Ok(Executive::new(&mut state, &env_info, &*self.engine)
.transact_virtual(&tx, options.clone())
.transact_virtual(&tx, options())
.map(|r| r.exception.is_none())
.unwrap_or(false))
};
@ -1254,22 +1275,17 @@ impl BlockChainClient for Client {
return Err(CallError::TransactionNotFound);
}
let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false };
const PROOF: &'static str = "Transactions fetched from blockchain; blockchain transactions are valid; qed";
let rest = txs.split_off(address.index);
for t in txs {
let t = SignedTransaction::new(t).expect(PROOF);
let x = Executive::new(&mut state, &env_info, &*self.engine).transact(&t, Default::default())?;
let x = Executive::new(&mut state, &env_info, &*self.engine).transact(&t, TransactOptions::with_no_tracing())?;
env_info.gas_used = env_info.gas_used + x.gas_used;
}
let first = rest.into_iter().next().expect("We split off < `address.index`; Length is checked earlier; qed");
let t = SignedTransaction::new(first).expect(PROOF);
let original_state = if analytics.state_diffing { Some(state.clone()) } else { None };
let mut ret = Executive::new(&mut state, &env_info, &*self.engine).transact(&t, options)?;
if let Some(original) = original_state {
ret.state_diff = Some(state.diff_from(original).map_err(ExecutionError::from)?)
}
Ok(ret)
self.do_virtual_call(&env_info, &mut state, &t, analytics)
}
fn mode(&self) -> IpcMode {
@ -1951,7 +1967,7 @@ impl ProvingBlockChainClient for Client {
let backend = state::backend::Proving::new(jdb.as_hashdb_mut());
let mut state = state.replace_backend(backend);
let options = TransactOptions { tracing: false, vm_tracing: false, check_nonce: false };
let options = TransactOptions::with_no_tracing().dont_check_nonce();
let res = Executive::new(&mut state, &env_info, &*self.engine).transact(&transaction, options);
match res {

View File

@ -18,9 +18,9 @@
use std::fmt;
use std::sync::Arc;
use util::{self, U256, journaldb, trie};
use util::{self, U256, H256, journaldb, trie};
use util::kvdb::{self, KeyValueDB};
use {state, state_db, client, executive, trace, db, spec};
use {state, state_db, client, executive, trace, transaction, db, spec, pod_state};
use factory::Factories;
use evm::{self, VMType};
use vm::{self, ActionParams};
@ -33,9 +33,17 @@ pub enum EvmTestError {
/// EVM error.
Evm(vm::Error),
/// Initialization error.
Initialization(::error::Error),
ClientError(::error::Error),
/// Low-level database error.
Database(String),
/// Post-condition failure,
PostCondition(String),
}
impl<E: Into<::error::Error>> From<E> for EvmTestError {
fn from(err: E) -> Self {
EvmTestError::ClientError(err.into())
}
}
impl fmt::Display for EvmTestError {
@ -45,52 +53,114 @@ impl fmt::Display for EvmTestError {
match *self {
Trie(ref err) => write!(fmt, "Trie: {}", err),
Evm(ref err) => write!(fmt, "EVM: {}", err),
Initialization(ref err) => write!(fmt, "Initialization: {}", err),
ClientError(ref err) => write!(fmt, "{}", err),
Database(ref err) => write!(fmt, "DB: {}", err),
PostCondition(ref err) => write!(fmt, "{}", err),
}
}
}
/// Simplified, single-block EVM test client.
pub struct EvmTestClient {
state_db: state_db::StateDB,
factories: Factories,
spec: spec::Spec,
use ethereum;
use ethjson::state::test::ForkSpec;
lazy_static! {
pub static ref FRONTIER: spec::Spec = ethereum::new_frontier_test();
pub static ref HOMESTEAD: spec::Spec = ethereum::new_homestead_test();
pub static ref EIP150: spec::Spec = ethereum::new_eip150_test();
pub static ref EIP161: spec::Spec = ethereum::new_eip161_test();
pub static ref _METROPOLIS: spec::Spec = ethereum::new_metropolis_test();
}
impl EvmTestClient {
/// Creates new EVM test client with in-memory DB initialized with genesis of given Spec.
pub fn new(spec: spec::Spec) -> Result<Self, EvmTestError> {
let factories = Factories {
vm: evm::Factory::new(VMType::Interpreter, 5 * 1024),
trie: trie::TrieFactory::new(trie::TrieSpec::Secure),
accountdb: Default::default(),
};
let db = Arc::new(kvdb::in_memory(db::NUM_COLUMNS.expect("We use column-based DB; qed")));
let journal_db = journaldb::new(db.clone(), journaldb::Algorithm::EarlyMerge, db::COL_STATE);
let mut state_db = state_db::StateDB::new(journal_db, 5 * 1024 * 1024);
state_db = spec.ensure_db_good(state_db, &factories).map_err(EvmTestError::Initialization)?;
// Write DB
{
let mut batch = kvdb::DBTransaction::new();
state_db.journal_under(&mut batch, 0, &spec.genesis_header().hash()).map_err(|e| EvmTestError::Initialization(e.into()))?;
db.write(batch).map_err(EvmTestError::Database)?;
/// Simplified, single-block EVM test client.
pub struct EvmTestClient<'a> {
state: state::State<state_db::StateDB>,
spec: &'a spec::Spec,
}
impl<'a> EvmTestClient<'a> {
/// Converts a json spec definition into spec.
pub fn spec_from_json(spec: &ForkSpec) -> Option<&'static spec::Spec> {
match *spec {
ForkSpec::Frontier => Some(&*FRONTIER),
ForkSpec::Homestead => Some(&*HOMESTEAD),
ForkSpec::EIP150 => Some(&*EIP150),
ForkSpec::EIP158 => Some(&*EIP161),
ForkSpec::Metropolis | ForkSpec::Byzantium | ForkSpec::Constantinople => None,
}
}
/// 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();
let state = Self::state_from_spec(spec, &factories)?;
Ok(EvmTestClient {
state_db,
factories,
state,
spec,
})
}
/// Call given contract.
/// 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();
let state = Self::state_from_pod(spec, &factories, pod_state)?;
Ok(EvmTestClient {
state,
spec,
})
}
fn factories() -> Factories {
Factories {
vm: evm::Factory::new(VMType::Interpreter, 5 * 1024),
trie: trie::TrieFactory::new(trie::TrieSpec::Secure),
accountdb: Default::default(),
}
}
fn state_from_spec(spec: &'a spec::Spec, factories: &Factories) -> Result<state::State<state_db::StateDB>, EvmTestError> {
let db = Arc::new(kvdb::in_memory(db::NUM_COLUMNS.expect("We use column-based DB; qed")));
let journal_db = journaldb::new(db.clone(), journaldb::Algorithm::EarlyMerge, db::COL_STATE);
let mut state_db = state_db::StateDB::new(journal_db, 5 * 1024 * 1024);
state_db = spec.ensure_db_good(state_db, factories)?;
let genesis = spec.genesis_header();
// Write DB
{
let mut batch = kvdb::DBTransaction::new();
state_db.journal_under(&mut batch, 0, &genesis.hash())?;
db.write(batch).map_err(EvmTestError::Database)?;
}
state::State::from_existing(
state_db,
*genesis.state_root(),
spec.engine.account_start_nonce(0),
factories.clone()
).map_err(EvmTestError::Trie)
}
fn state_from_pod(spec: &'a spec::Spec, factories: &Factories, pod_state: pod_state::PodState) -> Result<state::State<state_db::StateDB>, EvmTestError> {
let db = Arc::new(kvdb::in_memory(db::NUM_COLUMNS.expect("We use column-based DB; qed")));
let journal_db = journaldb::new(db.clone(), journaldb::Algorithm::EarlyMerge, db::COL_STATE);
let state_db = state_db::StateDB::new(journal_db, 5 * 1024 * 1024);
let mut state = state::State::new(
state_db,
spec.engine.account_start_nonce(0),
factories.clone(),
);
state.populate_from(pod_state);
state.commit()?;
Ok(state)
}
/// Execute the VM given ActionParams and tracer.
/// Returns amount of gas left and the output.
pub fn call<T: trace::VMTracer>(&mut self, params: ActionParams, vm_tracer: &mut T)
-> Result<(U256, Vec<u8>), EvmTestError>
{
let genesis = self.spec.genesis_header();
let mut state = state::State::from_existing(self.state_db.boxed_clone(), *genesis.state_root(), self.spec.engine.account_start_nonce(0), self.factories.clone())
.map_err(EvmTestError::Trie)?;
let info = client::EnvInfo {
number: genesis.number(),
author: *genesis.author(),
@ -103,7 +173,7 @@ impl EvmTestClient {
let mut substate = state::Substate::new();
let mut tracer = trace::NoopTracer;
let mut output = vec![];
let mut executive = executive::Executive::new(&mut state, &info, &*self.spec.engine);
let mut executive = executive::Executive::new(&mut self.state, &info, &*self.spec.engine);
let (gas_left, _) = executive.call(
params,
&mut substate,
@ -114,4 +184,59 @@ impl EvmTestClient {
Ok((gas_left, output))
}
/// Executes a SignedTransaction within context of the provided state and `EnvInfo`.
/// Returns the state root, gas left and the output.
pub fn transact<T: trace::VMTracer>(
&mut self,
env_info: &client::EnvInfo,
transaction: transaction::SignedTransaction,
vm_tracer: T,
) -> TransactResult {
let initial_gas = transaction.gas;
// Verify transaction
let is_ok = transaction.verify_basic(true, None, env_info.number >= self.spec.engine.params().eip86_transition);
if let Err(error) = is_ok {
return TransactResult::Err {
state_root: *self.state.root(),
error,
};
}
// Apply transaction
let tracer = trace::NoopTracer;
let result = self.state.apply_with_tracing(&env_info, &*self.spec.engine, &transaction, tracer, vm_tracer);
match result {
Ok(result) => TransactResult::Ok {
state_root: *self.state.root(),
gas_left: initial_gas - result.receipt.gas_used,
output: result.output
},
Err(error) => TransactResult::Err {
state_root: *self.state.root(),
error,
},
}
}
}
/// A result of applying transaction to the state.
pub enum TransactResult {
/// Successful execution
Ok {
/// State root
state_root: H256,
/// Amount of gas left
gas_left: U256,
/// Output
output: Vec<u8>,
},
/// Transaction failed to run
Err {
/// State root
state_root: H256,
/// Execution error
error: ::error::Error,
},
}

View File

@ -27,7 +27,7 @@ mod client;
pub use self::client::*;
pub use self::config::{Mode, ClientConfig, DatabaseCompactionProfile, BlockChainConfig, VMType};
pub use self::error::Error;
pub use self::evm_test_client::{EvmTestClient, EvmTestError};
pub use self::evm_test_client::{EvmTestClient, EvmTestError, TransactResult};
pub use self::test_client::{TestBlockChainClient, EachBlockWith};
pub use self::chain_notify::ChainNotify;
pub use self::traits::{BlockChainClient, MiningBlockChainClient, EngineClient};

View File

@ -26,7 +26,7 @@ use evm::{CallType, Factory, Finalize, FinalizationResult};
use vm::{self, Ext, CreateContractAddress, ReturnData, CleanDustMode, ActionParams, ActionValue};
use wasm;
use externalities::*;
use trace::{FlatTrace, Tracer, NoopTracer, ExecutiveTracer, VMTrace, VMTracer, ExecutiveVMTracer, NoopVMTracer};
use trace::{self, FlatTrace, VMTrace, Tracer, VMTracer};
use transaction::{Action, SignedTransaction};
use crossbeam;
pub use executed::{Executed, ExecutionResult};
@ -66,16 +66,77 @@ pub fn contract_address(address_scheme: CreateContractAddress, sender: &Address,
}
/// Transaction execution options.
#[derive(Default, Copy, Clone, PartialEq)]
pub struct TransactOptions {
#[derive(Copy, Clone, PartialEq)]
pub struct TransactOptions<T, V> {
/// Enable call tracing.
pub tracing: bool,
pub tracer: T,
/// Enable VM tracing.
pub vm_tracing: bool,
pub vm_tracer: V,
/// Check transaction nonce before execution.
pub check_nonce: bool,
}
impl<T, V> TransactOptions<T, V> {
/// Create new `TransactOptions` with given tracer and VM tracer.
pub fn new(tracer: T, vm_tracer: V) -> Self {
TransactOptions {
tracer,
vm_tracer,
check_nonce: true,
}
}
/// Disables the nonce check
pub fn dont_check_nonce(mut self) -> Self {
self.check_nonce = false;
self
}
}
impl TransactOptions<trace::ExecutiveTracer, trace::ExecutiveVMTracer> {
/// Creates new `TransactOptions` with default tracing and VM tracing.
pub fn with_tracing_and_vm_tracing() -> Self {
TransactOptions {
tracer: trace::ExecutiveTracer::default(),
vm_tracer: trace::ExecutiveVMTracer::toplevel(),
check_nonce: true,
}
}
}
impl TransactOptions<trace::ExecutiveTracer, trace::NoopVMTracer> {
/// Creates new `TransactOptions` with default tracing and no VM tracing.
pub fn with_tracing() -> Self {
TransactOptions {
tracer: trace::ExecutiveTracer::default(),
vm_tracer: trace::NoopVMTracer,
check_nonce: true,
}
}
}
impl TransactOptions<trace::NoopTracer, trace::ExecutiveVMTracer> {
/// Creates new `TransactOptions` with no tracing and default VM tracing.
pub fn with_vm_tracing() -> Self {
TransactOptions {
tracer: trace::NoopTracer,
vm_tracer: trace::ExecutiveVMTracer::toplevel(),
check_nonce: true,
}
}
}
impl TransactOptions<trace::NoopTracer, trace::NoopVMTracer> {
/// Creates new `TransactOptions` without any tracing.
pub fn with_no_tracing() -> Self {
TransactOptions {
tracer: trace::NoopTracer,
vm_tracer: trace::NoopVMTracer,
check_nonce: true,
}
}
}
pub fn executor<E>(engine: &E, vm_factory: &Factory, params: &ActionParams)
-> Box<vm::Vm> where E: Engine + ?Sized
{
@ -137,24 +198,18 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> {
}
/// This function should be used to execute transaction.
pub fn transact(&'a mut self, t: &SignedTransaction, options: TransactOptions) -> Result<Executed, ExecutionError> {
let check = options.check_nonce;
match options.tracing {
true => match options.vm_tracing {
true => self.transact_with_tracer(t, check, ExecutiveTracer::default(), ExecutiveVMTracer::toplevel()),
false => self.transact_with_tracer(t, check, ExecutiveTracer::default(), NoopVMTracer),
},
false => match options.vm_tracing {
true => self.transact_with_tracer(t, check, NoopTracer, ExecutiveVMTracer::toplevel()),
false => self.transact_with_tracer(t, check, NoopTracer, NoopVMTracer),
},
}
pub fn transact<T, V>(&'a mut self, t: &SignedTransaction, options: TransactOptions<T, V>)
-> Result<Executed, ExecutionError> where T: Tracer, V: VMTracer,
{
self.transact_with_tracer(t, options.check_nonce, options.tracer, options.vm_tracer)
}
/// Execute a transaction in a "virtual" context.
/// This will ensure the caller has enough balance to execute the desired transaction.
/// Used for extra-block executions for things like consensus contracts and RPCs
pub fn transact_virtual(&'a mut self, t: &SignedTransaction, options: TransactOptions) -> Result<Executed, ExecutionError> {
pub fn transact_virtual<T, V>(&'a mut self, t: &SignedTransaction, options: TransactOptions<T, V>)
-> Result<Executed, ExecutionError> where T: Tracer, V: VMTracer,
{
let sender = t.sender();
let balance = self.state.balance(&sender)?;
let needed_balance = t.value.saturating_add(t.gas.saturating_mul(t.gas_price));
@ -167,7 +222,7 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> {
}
/// Execute transaction/call with tracing enabled
pub fn transact_with_tracer<T, V>(
fn transact_with_tracer<T, V>(
&'a mut self,
t: &SignedTransaction,
check_nonce: bool,
@ -261,7 +316,7 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> {
};
// finalize here!
Ok(self.finalize(t, substate, result, output, tracer.traces(), vm_tracer.drain())?)
Ok(self.finalize(t, substate, result, output, tracer.drain(), vm_tracer.drain())?)
}
fn exec_vm<T, V>(
@ -399,7 +454,7 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> {
trace!(target: "executive", "res={:?}", res);
let traces = subtracer.traces();
let traces = subtracer.drain();
match res {
Ok(ref res) => tracer.trace_call(
trace_info,
@ -484,9 +539,9 @@ impl<'a, B: 'a + StateBackend, E: Engine + ?Sized> Executive<'a, B, E> {
gas - res.gas_left,
trace_output,
created,
subtracer.traces()
subtracer.drain()
),
Err(ref e) => tracer.trace_failed_create(trace_info, subtracer.traces(), e.into())
Err(ref e) => tracer.trace_failed_create(trace_info, subtracer.drain(), e.into())
};
self.enact_result(&res, substate, unconfirmed_substate);
@ -794,7 +849,7 @@ mod tests {
}),
}];
assert_eq!(tracer.traces(), expected_trace);
assert_eq!(tracer.drain(), expected_trace);
let expected_vm_trace = VMTrace {
parent_step: 0,
@ -887,7 +942,7 @@ mod tests {
}),
}];
assert_eq!(tracer.traces(), expected_trace);
assert_eq!(tracer.drain(), expected_trace);
let expected_vm_trace = VMTrace {
parent_step: 0,
@ -1138,7 +1193,7 @@ mod tests {
let executed = {
let mut ex = Executive::new(&mut state, &info, &engine);
let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false };
let opts = TransactOptions::with_no_tracing();
ex.transact(&t, opts).unwrap()
};
@ -1175,7 +1230,7 @@ mod tests {
let res = {
let mut ex = Executive::new(&mut state, &info, &engine);
let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false };
let opts = TransactOptions::with_no_tracing();
ex.transact(&t, opts)
};
@ -1208,7 +1263,7 @@ mod tests {
let res = {
let mut ex = Executive::new(&mut state, &info, &engine);
let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false };
let opts = TransactOptions::with_no_tracing();
ex.transact(&t, opts)
};
@ -1241,7 +1296,7 @@ mod tests {
let res = {
let mut ex = Executive::new(&mut state, &info, &engine);
let opts = TransactOptions { check_nonce: true, tracing: false, vm_tracing: false };
let opts = TransactOptions::with_no_tracing();
ex.transact(&t, opts)
};

View File

@ -15,23 +15,13 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use super::test_common::*;
use tests::helpers::*;
use pod_state::PodState;
use ethereum;
use spec::Spec;
use trace;
use client::{EvmTestClient, EvmTestError, TransactResult};
use ethjson;
use ethjson::state::test::ForkSpec;
use transaction::SignedTransaction;
use vm::EnvInfo;
lazy_static! {
pub static ref FRONTIER: Spec = ethereum::new_frontier_test();
pub static ref HOMESTEAD: Spec = ethereum::new_homestead_test();
pub static ref EIP150: Spec = ethereum::new_eip150_test();
pub static ref EIP161: Spec = ethereum::new_eip161_test();
pub static ref _METROPOLIS: Spec = ethereum::new_metropolis_test();
}
pub fn json_chain_test(json_data: &[u8]) -> Vec<String> {
::ethcore_logger::init_log();
let tests = ethjson::state::test::Test::load(json_data).unwrap();
@ -43,35 +33,49 @@ pub fn json_chain_test(json_data: &[u8]) -> Vec<String> {
let env: EnvInfo = test.env.into();
let pre: PodState = test.pre_state.into();
for (spec, states) in test.post_states {
for (spec_name, states) in test.post_states {
let total = states.len();
let engine = match spec {
ForkSpec::Frontier => &FRONTIER.engine,
ForkSpec::Homestead => &HOMESTEAD.engine,
ForkSpec::EIP150 => &EIP150.engine,
ForkSpec::EIP158 => &EIP161.engine,
ForkSpec::Metropolis => continue,
let spec = match EvmTestClient::spec_from_json(&spec_name) {
Some(spec) => spec,
None => {
println!(" - {} | {:?} Ignoring tests because of missing spec", name, spec_name);
continue;
}
};
for (i, state) in states.into_iter().enumerate() {
let info = format!(" - {} | {:?} ({}/{}) ...", name, spec, i + 1, total);
let info = format!(" - {} | {:?} ({}/{}) ...", name, spec_name, i + 1, total);
let post_root: H256 = state.hash.into();
let transaction: SignedTransaction = multitransaction.select(&state.indexes).into();
let mut state = get_temp_state();
state.populate_from(pre.clone());
if transaction.verify_basic(true, None, env.number >= engine.params().eip86_transition).is_ok() {
state.commit().expect(&format!("State test {} failed due to internal error.", name));
let _res = state.apply(&env, &**engine, &transaction, false);
} else {
let _rest = state.commit();
}
if state.root() != &post_root {
println!("{} !!! State mismatch (got: {}, expect: {}", info, state.root(), post_root);
flushln!("{} fail", info);
failed.push(name.clone());
} else {
flushln!("{} ok", info);
let result = || -> Result<_, EvmTestError> {
Ok(EvmTestClient::from_pod_state(spec, pre.clone())?
.transact(&env, transaction, trace::NoopVMTracer))
};
match result() {
Err(err) => {
println!("{} !!! Unexpected internal error: {:?}", info, err);
flushln!("{} fail", info);
failed.push(name.clone());
},
Ok(TransactResult::Ok { state_root, .. }) if state_root != post_root => {
println!("{} !!! State mismatch (got: {}, expect: {}", info, state_root, post_root);
flushln!("{} fail", info);
failed.push(name.clone());
},
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);
failed.push(name.clone());
},
Ok(TransactResult::Err { error, .. }) => {
flushln!("{} ok ({:?})", info, error);
},
Ok(_) => {
flushln!("{} ok", info);
},
}
}
}

View File

@ -31,7 +31,7 @@ use vm::EnvInfo;
use error::Error;
use executive::{Executive, TransactOptions};
use factory::Factories;
use trace::FlatTrace;
use trace::{self, FlatTrace, VMTrace};
use pod_account::*;
use pod_state::{self, PodState};
use types::basic_account::BasicAccount;
@ -59,8 +59,12 @@ pub use self::substate::Substate;
pub struct ApplyOutcome {
/// The receipt for the applied transaction.
pub receipt: Receipt,
/// The trace for the applied transaction, if None if tracing is disabled.
/// The output of the applied transaction.
pub output: Bytes,
/// The trace for the applied transaction, empty if tracing was not produced.
pub trace: Vec<FlatTrace>,
/// The VM trace for the applied transaction, None if tracing was not produced.
pub vm_trace: Option<VMTrace>
}
/// Result type for the execution ("application") of a transaction.
@ -205,7 +209,7 @@ pub fn check_proof(
Err(_) => return ProvedExecution::BadProof,
};
match state.execute(env_info, engine, transaction, false, true) {
match state.execute(env_info, engine, transaction, TransactOptions::with_no_tracing(), true) {
Ok(executed) => ProvedExecution::Complete(executed),
Err(ExecutionError::Internal(_)) => ProvedExecution::BadProof,
Err(e) => ProvedExecution::Failed(e),
@ -290,7 +294,7 @@ const SEC_TRIE_DB_UNWRAP_STR: &'static str = "A state can only be created with v
impl<B: Backend> State<B> {
/// Creates new state with empty state root
#[cfg(test)]
/// Used for tests.
pub fn new(mut db: B, account_start_nonce: U256, factories: Factories) -> State<B> {
let mut root = H256::new();
{
@ -623,29 +627,57 @@ impl<B: Backend> State<B> {
/// Execute a given transaction, producing a receipt and an optional trace.
/// This will change the state accordingly.
pub fn apply(&mut self, env_info: &EnvInfo, engine: &Engine, t: &SignedTransaction, tracing: bool) -> ApplyResult {
// let old = self.to_pod();
if tracing {
let options = TransactOptions::with_tracing();
self.apply_with_tracing(env_info, engine, t, options.tracer, options.vm_tracer)
} else {
let options = TransactOptions::with_no_tracing();
self.apply_with_tracing(env_info, engine, t, options.tracer, options.vm_tracer)
}
}
/// Execute a given transaction with given tracer and VM tracer producing a receipt and an optional trace.
/// This will change the state accordingly.
pub fn apply_with_tracing<V, T>(
&mut self,
env_info: &EnvInfo,
engine: &Engine,
t: &SignedTransaction,
tracer: T,
vm_tracer: V,
) -> ApplyResult where
T: trace::Tracer,
V: trace::VMTracer,
{
let options = TransactOptions::new(tracer, vm_tracer);
let e = self.execute(env_info, engine, t, options, false)?;
let e = self.execute(env_info, engine, t, tracing, false)?;
// trace!("Applied transaction. Diff:\n{}\n", state_diff::diff_pod(&old, &self.to_pod()));
let state_root = if env_info.number < engine.params().eip98_transition || env_info.number < engine.params().validate_receipts_transition {
self.commit()?;
Some(self.root().clone())
} else {
None
};
let output = e.output;
let receipt = Receipt::new(state_root, e.cumulative_gas_used, e.logs);
trace!(target: "state", "Transaction receipt: {:?}", receipt);
Ok(ApplyOutcome{receipt: receipt, trace: e.trace})
Ok(ApplyOutcome {
receipt,
output,
trace: e.trace,
vm_trace: e.vm_trace,
})
}
// Execute a given transaction without committing changes.
//
// `virt` signals that we are executing outside of a block set and restrictions like
// gas limits and gas costs should be lifted.
fn execute(&mut self, env_info: &EnvInfo, engine: &Engine, t: &SignedTransaction, tracing: bool, virt: bool)
-> Result<Executed, ExecutionError>
fn execute<T, V>(&mut self, env_info: &EnvInfo, engine: &Engine, t: &SignedTransaction, options: TransactOptions<T, V>, virt: bool)
-> Result<Executed, ExecutionError> where T: trace::Tracer, V: trace::VMTracer,
{
let options = TransactOptions { tracing: tracing, vm_tracing: false, check_nonce: true };
let mut e = Executive::new(self, env_info, engine);
match virt {
@ -730,9 +762,8 @@ impl<B: Backend> State<B> {
Ok(())
}
#[cfg(test)]
#[cfg(feature = "json-tests")]
/// Populate the state from `accounts`.
/// Used for tests.
pub fn populate_from(&mut self, accounts: PodState) {
assert!(self.checkpoints.borrow().is_empty());
for (add, acc) in accounts.drain().into_iter() {

View File

@ -19,7 +19,7 @@ use std::sync::Arc;
use io::IoChannel;
use client::{BlockChainClient, MiningBlockChainClient, Client, ClientConfig, BlockId};
use state::{self, State, CleanupMode};
use executive::Executive;
use executive::{Executive, TransactOptions};
use ethereum;
use block::IsBlock;
use tests::helpers::*;
@ -361,7 +361,7 @@ fn transaction_proof() {
let mut state = State::from_existing(backend, root, 0.into(), factories.clone()).unwrap();
Executive::new(&mut state, &client.latest_env_info(), &*test_spec.engine)
.transact(&transaction, Default::default()).unwrap();
.transact(&transaction, TransactOptions::with_no_tracing().dont_check_nonce()).unwrap();
assert_eq!(state.balance(&Address::default()).unwrap(), 5.into());
assert_eq!(state.balance(&address).unwrap(), 95.into());

View File

@ -167,7 +167,7 @@ impl Tracer for ExecutiveTracer {
ExecutiveTracer::default()
}
fn traces(self) -> Vec<FlatTrace> {
fn drain(self) -> Vec<FlatTrace> {
self.traces
}
}

View File

@ -85,7 +85,7 @@ pub trait Tracer: Send {
fn subtracer(&self) -> Self where Self: Sized;
/// Consumes self and returns all traces.
fn traces(self) -> Vec<FlatTrace>;
fn drain(self) -> Vec<FlatTrace>;
}
/// Used by executive to build VM traces.

View File

@ -62,7 +62,7 @@ impl Tracer for NoopTracer {
NoopTracer
}
fn traces(self) -> Vec<FlatTrace> {
fn drain(self) -> Vec<FlatTrace> {
vec![]
}
}

View File

@ -14,6 +14,7 @@ docopt = "0.8"
serde = "1.0"
serde_derive = "1.0"
ethcore = { path = "../ethcore" }
ethjson = { path = "../json" }
ethcore-util = { path = "../util" }
evm = { path = "../ethcore/evm" }
vm = { path = "../ethcore/vm" }

View File

@ -30,10 +30,8 @@ pub struct Informant {
depth: usize,
pc: usize,
instruction: u8,
name: &'static str,
gas_cost: U256,
gas_used: U256,
stack_pop: usize,
stack: Vec<U256>,
memory: Vec<u8>,
storage: HashMap<H256, H256>,
@ -58,11 +56,19 @@ impl Informant {
}
impl vm::Informant for Informant {
fn before_test(&self, name: &str, action: &str) {
println!(
"{{\"test\":\"{name}\",\"action\":\"{action}\"}}",
name = name,
action = action,
);
}
fn set_gas(&mut self, gas: U256) {
self.gas_used = gas;
}
fn finish(&mut self, result: Result<vm::Success, vm::Failure>) {
fn finish(result: Result<vm::Success, vm::Failure>) {
match result {
Ok(success) => println!(
"{{\"output\":\"0x{output}\",\"gasUsed\":\"{gas:x}\",\"time\":{time}}}",
@ -112,7 +118,7 @@ impl trace::VMTracer for Informant {
self.gas_used = gas_used;
let len = self.stack.len();
self.stack.truncate(len - info.args);
self.stack.truncate(if len > info.args { len - info.args } else { 0 });
self.stack.extend_from_slice(stack_push);
if let Some((pos, data)) = mem_diff {

View File

@ -27,7 +27,11 @@ use info as vm;
pub struct Informant;
impl vm::Informant for Informant {
fn finish(&mut self, result: Result<vm::Success, vm::Failure>) {
fn before_test(&self, name: &str, action: &str) {
println!("Test: {} ({})", name, action);
}
fn finish(result: Result<vm::Success, vm::Failure>) {
match result {
Ok(success) => {
println!("Output: 0x{}", success.output.to_hex());

View File

@ -17,17 +17,19 @@
//! VM runner.
use std::time::{Instant, Duration};
use util::U256;
use ethcore::{trace, spec};
use ethcore::client::{EvmTestClient, EvmTestError};
use vm::ActionParams;
use util::{U256, H256};
use ethcore::{trace, spec, transaction, pod_state};
use ethcore::client::{self, EvmTestClient, EvmTestError, TransactResult};
use ethjson;
/// VM execution informant
pub trait Informant: trace::VMTracer {
/// Display a single run init message
fn before_test(&self, test: &str, action: &str);
/// Set initial gas.
fn set_gas(&mut self, _gas: U256) {}
/// Display final result.
fn finish(&mut self, result: Result<Success, Failure>);
fn finish(result: Result<Success, Failure>);
}
/// Execution finished correctly
@ -50,17 +52,71 @@ pub struct Failure {
pub time: Duration,
}
/// Execute given Transaction and verify resulting state root.
pub fn run_transaction<T: Informant>(
name: &str,
idx: usize,
spec: &ethjson::state::test::ForkSpec,
pre_state: &pod_state::PodState,
post_root: H256,
env_info: &client::EnvInfo,
transaction: transaction::SignedTransaction,
mut informant: T,
) {
let spec_name = format!("{:?}", spec).to_lowercase();
let spec = match EvmTestClient::spec_from_json(spec) {
Some(spec) => {
informant.before_test(&format!("{}:{}:{}", name, spec_name, idx), "starting");
spec
},
None => {
informant.before_test(&format!("{}:{}:{}", name, spec_name, idx), "skipping because of missing spec");
return;
},
};
informant.set_gas(env_info.gas_limit);
let result = run(spec, env_info.gas_limit, pre_state, |mut client| {
let result = client.transact(env_info, transaction, informant);
match result {
TransactResult::Ok { state_root, .. } if state_root != post_root => {
Err(EvmTestError::PostCondition(format!(
"State root mismatch (got: {}, expected: {})",
state_root,
post_root,
)))
},
TransactResult::Ok { gas_left, output, .. } => {
Ok((gas_left, output))
},
TransactResult::Err { error, .. } => {
Err(EvmTestError::PostCondition(format!(
"Unexpected execution error: {:?}", error
)))
},
}
});
T::finish(result)
}
/// Execute VM with given `ActionParams`
pub fn run<T: trace::VMTracer>(vm_tracer: &mut T, spec: spec::Spec, params: ActionParams) -> Result<Success, Failure> {
let mut test_client = EvmTestClient::new(spec).map_err(|error| Failure {
pub fn run<'a, F, T>(spec: &'a spec::Spec, initial_gas: U256, pre_state: T, run: F) -> Result<Success, Failure> where
F: FnOnce(EvmTestClient) -> Result<(U256, Vec<u8>), EvmTestError>,
T: Into<Option<&'a pod_state::PodState>>,
{
let test_client = match pre_state.into() {
Some(pre_state) => EvmTestClient::from_pod_state(spec, pre_state.clone()),
None => EvmTestClient::new(spec),
}.map_err(|error| Failure {
gas_used: 0.into(),
error,
time: Duration::from_secs(0)
})?;
let initial_gas = params.gas;
let start = Instant::now();
let result = test_client.call(params, vm_tracer);
let result = run(test_client);
let duration = start.elapsed();
match result {

View File

@ -17,8 +17,9 @@
//! Parity EVM interpreter binary.
#![warn(missing_docs)]
#![allow(dead_code)]
extern crate ethcore;
extern crate ethjson;
extern crate rustc_hex;
extern crate serde;
#[macro_use]
@ -31,6 +32,7 @@ extern crate panic_hook;
use std::sync::Arc;
use std::{fmt, fs};
use std::path::PathBuf;
use docopt::Docopt;
use rustc_hex::FromHex;
use util::{U256, Bytes, Address};
@ -47,6 +49,7 @@ EVM implementation for Parity.
Copyright 2016, 2017 Parity Technologies (UK) Ltd
Usage:
parity-evm state-test <file> [--json --only NAME --chain CHAIN]
parity-evm stats [options]
parity-evm [options]
parity-evm [-h | --help]
@ -59,6 +62,10 @@ Transaction options:
--gas GAS Supplied gas as hex (without 0x).
--gas-price WEI Supplied gas price as hex (without 0x).
State test options:
--only NAME Runs only a single test matching the name.
--chain CHAIN Run only tests from specific chain.
General options:
--json Display verbose results in JSON.
--chain CHAIN Chain spec file path.
@ -71,20 +78,67 @@ fn main() {
let args: Args = Docopt::new(USAGE).and_then(|d| d.deserialize()).unwrap_or_else(|e| e.exit());
if args.flag_json {
run(args, display::json::Informant::default())
if args.cmd_state_test {
run_state_test(args)
} else if args.flag_json {
run_call(args, display::json::Informant::default())
} else {
run(args, display::simple::Informant::default())
run_call(args, display::simple::Informant::default())
}
}
fn run<T: Informant>(args: Args, mut informant: T) {
fn run_state_test(args: Args) {
use ethjson::state::test::Test;
let file = args.arg_file.expect("FILE is required");
let mut file = match fs::File::open(&file) {
Err(err) => die(format!("Unable to open: {:?}: {}", file, err)),
Ok(file) => file,
};
let state_test = match Test::load(&mut file) {
Err(err) => die(format!("Unable to load the test file: {}", err)),
Ok(test) => test,
};
let only_test = args.flag_only.map(|s| s.to_lowercase());
let only_chain = args.flag_chain.map(|s| s.to_lowercase());
for (name, test) in state_test {
if let Some(false) = only_test.as_ref().map(|only_test| &name.to_lowercase() == only_test) {
continue;
}
let multitransaction = test.transaction;
let env_info = test.env.into();
let pre = test.pre_state.into();
for (spec, states) in test.post_states {
if let Some(false) = only_chain.as_ref().map(|only_chain| &format!("{:?}", spec).to_lowercase() == only_chain) {
continue;
}
for (idx, state) in states.into_iter().enumerate() {
let post_root = state.hash.into();
let transaction = multitransaction.select(&state.indexes).into();
if args.flag_json {
let i = display::json::Informant::default();
info::run_transaction(&name, idx, &spec, &pre, post_root, &env_info, transaction, i)
} else {
let i = display::simple::Informant::default();
info::run_transaction(&name, idx, &spec, &pre, post_root, &env_info, transaction, i)
}
}
}
}
}
fn run_call<T: Informant>(args: Args, mut informant: T) {
let from = arg(args.from(), "--from");
let to = arg(args.to(), "--to");
let code = arg(args.code(), "--code");
let spec = arg(args.spec(), "--chain");
let gas = arg(args.gas(), "--gas");
let gas_price = arg(args.gas(), "--gas-price");
let gas_price = arg(args.gas_price(), "--gas-price");
let data = arg(args.data(), "--input");
if code.is_none() && to == Address::default() {
@ -103,13 +157,18 @@ fn run<T: Informant>(args: Args, mut informant: T) {
params.data = data;
informant.set_gas(gas);
let result = info::run(&mut informant, spec, params);
informant.finish(result);
let result = info::run(&spec, gas, None, |mut client| {
client.call(params, &mut informant)
});
T::finish(result);
}
#[derive(Debug, Deserialize)]
struct Args {
cmd_stats: bool,
cmd_state_test: bool,
arg_file: Option<PathBuf>,
flag_only: Option<String>,
flag_from: Option<String>,
flag_to: Option<String>,
flag_code: Option<String>,
@ -221,4 +280,22 @@ mod tests {
assert_eq!(args.data(), Ok(Some(vec![06])));
assert_eq!(args.flag_chain, Some("./testfile".to_owned()));
}
#[test]
fn should_parse_state_test_command() {
let args = run(&[
"parity-evm",
"state-test",
"./file.json",
"--chain", "homestead",
"--only=add11",
"--json",
]);
assert_eq!(args.cmd_state_test, true);
assert!(args.arg_file.is_some());
assert_eq!(args.flag_json, true);
assert_eq!(args.flag_chain, Some("homestead".to_owned()));
assert_eq!(args.flag_only, Some("add11".to_owned()));
}
}

View File

@ -104,7 +104,10 @@ pub enum ForkSpec {
EIP158,
Frontier,
Homestead,
// TODO [ToDr] Deprecated
Metropolis,
Byzantium,
Constantinople,
}
/// State test indexes deserialization.
@ -161,7 +164,7 @@ mod tests {
"EIP150" : [
{
"hash" : "3e6dacc1575c6a8c76422255eca03529bbf4c0dda75dfc110b22d6dc4152396f",
"indexes" : { "data" : 0, "gas" : 0, "value" : 0 }
"indexes" : { "data" : 0, "gas" : 0, "value" : 0 }
},
{
"hash" : "99a450d8ce5b987a71346d8a0a1203711f770745c7ef326912e46761f14cd764",