diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index f75df22bf..217464500 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -35,7 +35,7 @@ use util::kvdb::*; // other use views::BlockView; -use error::{ImportError, ExecutionError, BlockError, ImportResult}; +use error::{ImportError, ExecutionError, ReplayError, BlockError, ImportResult}; use header::BlockNumber; use state::State; use spec::Spec; @@ -473,7 +473,7 @@ impl Client { results.len() } - /// Attempt to get a copy of a specific block's state. + /// Attempt to get a copy of a specific block's final state. /// /// This will not fail if given BlockID::Latest. /// Otherwise, this can fail (but may not) if the DB prunes state. @@ -504,6 +504,21 @@ impl Client { }) } + /// Attempt to get a copy of a specific block's beginning state. + /// + /// This will not fail if given BlockID::Latest. + /// Otherwise, this can fail (but may not) if the DB prunes state. + pub fn state_at_beginning(&self, id: BlockID) -> Option { + // fast path for latest state. + match id { + BlockID::Pending => self.state_at(BlockID::Latest), + id => match self.block_number(id) { + None | Some(0) => None, + Some(n) => self.state_at(BlockID::Number(n - 1)), + } + } + } + /// Get a copy of the best block's state. pub fn state(&self) -> State { State::from_existing( @@ -660,6 +675,46 @@ impl BlockChainClient for Client { ret } + fn replay(&self, id: TransactionID, analytics: CallAnalytics) -> Result { + let address = try!(self.transaction_address(id).ok_or(ReplayError::TransactionNotFound)); + let block_data = try!(self.block(BlockID::Hash(address.block_hash)).ok_or(ReplayError::StatePruned)); + let mut state = try!(self.state_at_beginning(BlockID::Hash(address.block_hash)).ok_or(ReplayError::StatePruned)); + let block = BlockView::new(&block_data); + let txs = block.transactions(); + + if address.index >= txs.len() { + return Err(ReplayError::TransactionNotFound); + } + + let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false }; + let view = block.header_view(); + let last_hashes = self.build_last_hashes(view.hash()); + let mut env_info = EnvInfo { + number: view.number(), + author: view.author(), + timestamp: view.timestamp(), + difficulty: view.difficulty(), + last_hashes: last_hashes, + gas_used: U256::zero(), + gas_limit: view.gas_limit(), + }; + for t in txs.iter().take(address.index) { + match Executive::new(&mut state, &env_info, self.engine.deref().deref(), &self.vm_factory).transact(t, Default::default()) { + Ok(x) => { env_info.gas_used = env_info.gas_used + x.gas_used; } + Err(ee) => { return Err(ReplayError::Execution(ee)) } + } + } + let t = &txs[address.index]; + let orig = state.clone(); + let mut ret = Executive::new(&mut state, &env_info, self.engine.deref().deref(), &self.vm_factory).transact(t, options); + if analytics.state_diffing { + if let Ok(ref mut x) = ret { + x.state_diff = Some(state.diff_from(orig)); + } + } + ret.map_err(|ee| ReplayError::Execution(ee)) + } + fn keep_alive(&self) { if self.mode != Mode::Active { self.wake_up(); diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 926777222..9b13d7be3 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -37,7 +37,7 @@ use spec::Spec; use block_queue::BlockQueueInfo; use block::{OpenBlock, SealedBlock}; use executive::Executed; -use error::ExecutionError; +use error::{ExecutionError, ReplayError}; use trace::LocalizedTrace; /// Test client. @@ -292,6 +292,10 @@ impl BlockChainClient for TestBlockChainClient { Ok(self.execution_result.read().clone().unwrap()) } + fn replay(&self, _id: TransactionID, _analytics: CallAnalytics) -> Result { + Ok(self.execution_result.read().clone().unwrap()) + } + fn block_total_difficulty(&self, _id: BlockID) -> Option { Some(U256::zero()) } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index ad4c4a193..7d13dd23d 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -26,7 +26,7 @@ use transaction::{LocalizedTransaction, SignedTransaction}; use log_entry::LocalizedLogEntry; use filter::Filter; use views::{BlockView}; -use error::{ImportResult, ExecutionError}; +use error::{ImportResult, ExecutionError, ReplayError}; use receipt::LocalizedReceipt; use trace::LocalizedTrace; use evm::Factory as EvmFactory; @@ -160,6 +160,9 @@ pub trait BlockChainClient : Sync + Send { // TODO: should be able to accept blockchain location for call. fn call(&self, t: &SignedTransaction, analytics: CallAnalytics) -> Result; + /// Replays a given transaction for inspection. + fn replay(&self, t: TransactionID, analytics: CallAnalytics) -> Result; + /// Returns traces matching given filter. fn filter_traces(&self, filter: TraceFilter) -> Option>; diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index 26a645322..efcbd65e2 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -22,7 +22,7 @@ use basic_types::LogBloom; use client::Error as ClientError; use ipc::binary::{BinaryConvertError, BinaryConvertable}; use types::block_import_error::BlockImportError; -pub use types::executed::ExecutionError; +pub use types::executed::{ExecutionError, ReplayError}; #[derive(Debug, PartialEq, Clone)] /// Errors concerning transaction processing. diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index 811087616..0fffb3d1d 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -40,6 +40,7 @@ pub fn contract_address(address: &Address, nonce: &U256) -> Address { } /// Transaction execution options. +#[derive(Default)] pub struct TransactOptions { /// Enable call tracing. pub tracing: bool, diff --git a/ethcore/src/trace/executive_tracer.rs b/ethcore/src/trace/executive_tracer.rs index 83761ea23..7eb7f3489 100644 --- a/ethcore/src/trace/executive_tracer.rs +++ b/ethcore/src/trace/executive_tracer.rs @@ -50,6 +50,7 @@ impl Tracer for ExecutiveTracer { output: output.expect("self.prepare_trace_output().is_some(): so we must be tracing: qed") }) }; + debug!(target: "trace", "Traced call {:?}", trace); self.traces.push(trace); } @@ -64,6 +65,7 @@ impl Tracer for ExecutiveTracer { address: address }) }; + debug!(target: "trace", "Traced create {:?}", trace); self.traces.push(trace); } @@ -74,6 +76,7 @@ impl Tracer for ExecutiveTracer { action: Action::Call(call.expect("self.prepare_trace_call().is_some(): so we must be tracing: qed")), result: Res::FailedCall, }; + debug!(target: "trace", "Traced failed call {:?}", trace); self.traces.push(trace); } @@ -84,6 +87,7 @@ impl Tracer for ExecutiveTracer { action: Action::Create(create.expect("self.prepare_trace_create().is_some(): so we must be tracing: qed")), result: Res::FailedCreate, }; + debug!(target: "trace", "Traced failed create {:?}", trace); self.traces.push(trace); } @@ -98,6 +102,7 @@ impl Tracer for ExecutiveTracer { }), result: Res::None, }; + debug!(target: "trace", "Traced failed suicide {:?}", trace); self.traces.push(trace); } diff --git a/ethcore/src/types/executed.rs b/ethcore/src/types/executed.rs index 977c2ac04..abf71627e 100644 --- a/ethcore/src/types/executed.rs +++ b/ethcore/src/types/executed.rs @@ -171,6 +171,31 @@ impl fmt::Display for ExecutionError { } } +/// Result of executing the transaction. +#[derive(PartialEq, Debug, Binary)] +pub enum ReplayError { + /// Couldn't find the transaction in the chain. + TransactionNotFound, + /// Couldn't find the transaction block's state in the chain. + StatePruned, + /// Error executing. + Execution(ExecutionError), +} + +impl fmt::Display for ReplayError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::ReplayError::*; + + let msg = match *self { + TransactionNotFound => "Transaction couldn't be found in the chain".into(), + StatePruned => "Couldn't find the transaction block's state in the chain".into(), + Execution(ref e) => format!("{}", e), + }; + + f.write_fmt(format_args!("Transaction replay error ({}).", msg)) + } +} + /// Transaction execution result. pub type ExecutionResult = Result; diff --git a/rpc/src/v1/impls/traces.rs b/rpc/src/v1/impls/traces.rs index 24713bd61..9ea045b0f 100644 --- a/rpc/src/v1/impls/traces.rs +++ b/rpc/src/v1/impls/traces.rs @@ -26,6 +26,14 @@ use v1::traits::Traces; use v1::helpers::CallRequest as CRequest; use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, H256}; +fn to_call_analytics(flags: Vec) -> CallAnalytics { + CallAnalytics { + transaction_tracing: flags.contains(&("trace".to_owned())), + vm_tracing: flags.contains(&("vmTrace".to_owned())), + state_diffing: flags.contains(&("stateDiff".to_owned())), + } +} + /// Traces api implementation. pub struct TracesClient where C: BlockChainClient, M: MinerService { client: Weak, @@ -114,18 +122,11 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: fn call(&self, params: Params) -> Result { try!(self.active()); - trace!(target: "jsonrpc", "call: {:?}", params); from_params(params) .and_then(|(request, flags)| { let request = CallRequest::into(request); - let flags: Vec = flags; - let analytics = CallAnalytics { - transaction_tracing: flags.contains(&("trace".to_owned())), - vm_tracing: flags.contains(&("vmTrace".to_owned())), - state_diffing: flags.contains(&("stateDiff".to_owned())), - }; let signed = try!(self.sign_call(request)); - match take_weak!(self.client).call(&signed, analytics) { + match take_weak!(self.client).call(&signed, to_call_analytics(flags)) { Ok(e) => to_value(&TraceResults::from(e)), _ => Ok(Value::Null), } @@ -134,17 +135,11 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: fn raw_transaction(&self, params: Params) -> Result { try!(self.active()); - trace!(target: "jsonrpc", "call: {:?}", params); - from_params::<(Bytes, Vec)>(params) + from_params::<(Bytes, _)>(params) .and_then(|(raw_transaction, flags)| { let raw_transaction = raw_transaction.to_vec(); - let analytics = CallAnalytics { - transaction_tracing: flags.contains(&("trace".to_owned())), - vm_tracing: flags.contains(&("vmTrace".to_owned())), - state_diffing: flags.contains(&("stateDiff".to_owned())), - }; match UntrustedRlp::new(&raw_transaction).as_val() { - Ok(signed) => match take_weak!(self.client).call(&signed, analytics) { + Ok(signed) => match take_weak!(self.client).call(&signed, to_call_analytics(flags)) { Ok(e) => to_value(&TraceResults::from(e)), _ => Ok(Value::Null), }, @@ -152,4 +147,15 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: } }) } + + fn replay_transaction(&self, params: Params) -> Result { + try!(self.active()); + from_params::<(H256, _)>(params) + .and_then(|(transaction_hash, flags)| { + match take_weak!(self.client).replay(TransactionID::Hash(transaction_hash.into()), to_call_analytics(flags)) { + Ok(e) => to_value(&TraceResults::from(e)), + _ => Ok(Value::Null), + } + }) + } } diff --git a/rpc/src/v1/traits/traces.rs b/rpc/src/v1/traits/traces.rs index a05b2de01..64d16c5b4 100644 --- a/rpc/src/v1/traits/traces.rs +++ b/rpc/src/v1/traits/traces.rs @@ -38,6 +38,9 @@ pub trait Traces: Sized + Send + Sync + 'static { /// Executes the given raw transaction and returns a number of possible traces for it. fn raw_transaction(&self, _: Params) -> Result; + /// Executes the transaction with the given hash and returns a number of possible traces for it. + fn replay_transaction(&self, _: Params) -> Result; + /// Should be used to convert object to io delegate. fn to_delegate(self) -> IoDelegate { let mut delegate = IoDelegate::new(Arc::new(self)); @@ -47,6 +50,7 @@ pub trait Traces: Sized + Send + Sync + 'static { delegate.add_method("trace_block", Traces::block_traces); delegate.add_method("trace_call", Traces::call); delegate.add_method("trace_rawTransaction", Traces::raw_transaction); + delegate.add_method("trace_replayTransaction", Traces::replay_transaction); delegate }