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:
committed by
Arkadiy Paronyan
parent
abecd80f54
commit
f9a08e285c
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
Reference in New Issue
Block a user