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,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}; | ||||
|  | ||||
| @ -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); | ||||
| 						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); | ||||
| 						}, | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| @ -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.
 | ||||
| @ -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", | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user