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

Running state test using parity-evm #6355

Merged
merged 5 commits into from
Aug 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

60 changes: 38 additions & 22 deletions ethcore/src/client/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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()),
}
}
}

Expand Down Expand Up @@ -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> {
Expand All @@ -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);
}
Expand All @@ -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();
Expand All @@ -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))
};
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
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 vm::{self, ActionParams};
Expand All @@ -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 {
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