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:
parent
abecd80f54
commit
f9a08e285c
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -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)",
|
||||
|
@ -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,19 +1113,40 @@ 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)?);
|
||||
}
|
||||
|
||||
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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl snapshot::DatabaseRestore for Client {
|
||||
@ -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 {
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/// Simplified, single-block EVM test client.
|
||||
pub struct EvmTestClient {
|
||||
state_db: state_db::StateDB,
|
||||
factories: Factories,
|
||||
spec: spec::Spec,
|
||||
pub struct EvmTestClient<'a> {
|
||||
state: state::State<state_db::StateDB>,
|
||||
spec: &'a spec::Spec,
|
||||
}
|
||||
|
||||
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)?;
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
@ -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};
|
||||
|
@ -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)
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
||||
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());
|
||||
} else {
|
||||
},
|
||||
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);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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());
|
||||
|
@ -167,7 +167,7 @@ impl Tracer for ExecutiveTracer {
|
||||
ExecutiveTracer::default()
|
||||
}
|
||||
|
||||
fn traces(self) -> Vec<FlatTrace> {
|
||||
fn drain(self) -> Vec<FlatTrace> {
|
||||
self.traces
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -62,7 +62,7 @@ impl Tracer for NoopTracer {
|
||||
NoopTracer
|
||||
}
|
||||
|
||||
fn traces(self) -> Vec<FlatTrace> {
|
||||
fn drain(self) -> Vec<FlatTrace> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
@ -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" }
|
||||
|
@ -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 {
|
||||
|
@ -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());
|
||||
|
@ -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: ðjson::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 {
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
@ -104,7 +104,10 @@ pub enum ForkSpec {
|
||||
EIP158,
|
||||
Frontier,
|
||||
Homestead,
|
||||
// TODO [ToDr] Deprecated
|
||||
Metropolis,
|
||||
Byzantium,
|
||||
Constantinople,
|
||||
}
|
||||
|
||||
/// State test indexes deserialization.
|
||||
|
Loading…
Reference in New Issue
Block a user