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

Commit

Permalink
Running state test using parity-evm (#6355)
Browse files Browse the repository at this point in the history
* Initial version of state tests.

* Refactor state to support tracing.

* Unify TransactResult.

* Add test.
  • Loading branch information
tomusdrw authored and arkpar committed Sep 15, 2017
1 parent 084ddd1 commit d6d0152
Show file tree
Hide file tree
Showing 18 changed files with 530 additions and 165 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

65 changes: 41 additions & 24 deletions ethcore/src/client/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,41 @@ impl Client {
BlockId::Latest | BlockId::Pending => Some(self.chain.read().best_block_number()),
}
}

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 mut ret = Executive::new(state, env_info, engine).transact_virtual(transaction, options)?;

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 {
Expand Down Expand Up @@ -1150,19 +1185,8 @@ impl BlockChainClient for Client {

// that's just a copy of the state.
let mut state = self.state_at(block).ok_or(CallError::StatePruned)?;
let original_state = if analytics.state_diffing { Some(state.clone()) } else { None };

let options = TransactOptions::new(analytics.transaction_tracing, analytics.vm_tracing)
.dont_check_nonce()
.save_output_from_contract();
let mut ret = Executive::new(&mut state, &env_info, &*self.engine).transact_virtual(t, 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)
self.do_virtual_call(&env_info, &mut state, t, analytics)
}

fn estimate_gas(&self, t: &SignedTransaction, block: BlockId) -> Result<U256, CallError> {
Expand All @@ -1177,7 +1201,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::with_tracing().dont_check_nonce();
let options = || TransactOptions::with_tracing();

let cond = |gas| {
let mut tx = t.as_unsigned().clone();
Expand All @@ -1186,7 +1210,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))
};
Expand Down Expand Up @@ -1242,24 +1266,17 @@ impl BlockChainClient for Client {
return Err(CallError::TransactionNotFound);
}

let options = TransactOptions::new(analytics.transaction_tracing, analytics.vm_tracing)
.dont_check_nonce()
.save_output_from_contract();
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 {
Expand Down
169 changes: 147 additions & 22 deletions ethcore/src/client/evm_test_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 evm::action_params::ActionParams;
Expand All @@ -33,9 +33,17 @@ pub enum EvmTestError {
/// EVM error.
Evm(evm::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 {
Expand All @@ -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 {
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: spec::Spec) -> Result<Self, EvmTestError> {
let factories = Factories {
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,
spec,
})
}

/// 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).map_err(EvmTestError::Initialization)?;
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, &spec.genesis_header().hash()).map_err(|e| EvmTestError::Initialization(e.into()))?;
state_db.journal_under(&mut batch, 0, &genesis.hash())?;
db.write(batch).map_err(EvmTestError::Database)?;
}

Ok(EvmTestClient {
state::State::from_existing(
state_db,
factories,
spec,
})
*genesis.state_root(),
spec.engine.account_start_nonce(0),
factories.clone()
).map_err(EvmTestError::Trie)
}

/// Call given contract.
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(),
Expand All @@ -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,
Expand All @@ -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,
},
}
2 changes: 1 addition & 1 deletion ethcore/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
Loading

0 comments on commit d6d0152

Please sign in to comment.