Add RPC & client call to replay a transaction. (#1734)

* Add RPC & client call to replay a transaction.

* Address grumbles
This commit is contained in:
Gav Wood 2016-07-27 21:34:32 +02:00 committed by GitHub
parent b9a08c36aa
commit eaa41ea568
9 changed files with 124 additions and 21 deletions

View File

@ -35,7 +35,7 @@ use util::kvdb::*;
// other // other
use views::BlockView; use views::BlockView;
use error::{ImportError, ExecutionError, BlockError, ImportResult}; use error::{ImportError, ExecutionError, ReplayError, BlockError, ImportResult};
use header::BlockNumber; use header::BlockNumber;
use state::State; use state::State;
use spec::Spec; use spec::Spec;
@ -473,7 +473,7 @@ impl Client {
results.len() 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. /// This will not fail if given BlockID::Latest.
/// Otherwise, this can fail (but may not) if the DB prunes state. /// 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<State> {
// 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. /// Get a copy of the best block's state.
pub fn state(&self) -> State { pub fn state(&self) -> State {
State::from_existing( State::from_existing(
@ -660,6 +675,46 @@ impl BlockChainClient for Client {
ret ret
} }
fn replay(&self, id: TransactionID, analytics: CallAnalytics) -> Result<Executed, ReplayError> {
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) { fn keep_alive(&self) {
if self.mode != Mode::Active { if self.mode != Mode::Active {
self.wake_up(); self.wake_up();

View File

@ -37,7 +37,7 @@ use spec::Spec;
use block_queue::BlockQueueInfo; use block_queue::BlockQueueInfo;
use block::{OpenBlock, SealedBlock}; use block::{OpenBlock, SealedBlock};
use executive::Executed; use executive::Executed;
use error::ExecutionError; use error::{ExecutionError, ReplayError};
use trace::LocalizedTrace; use trace::LocalizedTrace;
/// Test client. /// Test client.
@ -292,6 +292,10 @@ impl BlockChainClient for TestBlockChainClient {
Ok(self.execution_result.read().clone().unwrap()) Ok(self.execution_result.read().clone().unwrap())
} }
fn replay(&self, _id: TransactionID, _analytics: CallAnalytics) -> Result<Executed, ReplayError> {
Ok(self.execution_result.read().clone().unwrap())
}
fn block_total_difficulty(&self, _id: BlockID) -> Option<U256> { fn block_total_difficulty(&self, _id: BlockID) -> Option<U256> {
Some(U256::zero()) Some(U256::zero())
} }

View File

@ -26,7 +26,7 @@ use transaction::{LocalizedTransaction, SignedTransaction};
use log_entry::LocalizedLogEntry; use log_entry::LocalizedLogEntry;
use filter::Filter; use filter::Filter;
use views::{BlockView}; use views::{BlockView};
use error::{ImportResult, ExecutionError}; use error::{ImportResult, ExecutionError, ReplayError};
use receipt::LocalizedReceipt; use receipt::LocalizedReceipt;
use trace::LocalizedTrace; use trace::LocalizedTrace;
use evm::Factory as EvmFactory; use evm::Factory as EvmFactory;
@ -160,6 +160,9 @@ pub trait BlockChainClient : Sync + Send {
// TODO: should be able to accept blockchain location for call. // TODO: should be able to accept blockchain location for call.
fn call(&self, t: &SignedTransaction, analytics: CallAnalytics) -> Result<Executed, ExecutionError>; fn call(&self, t: &SignedTransaction, analytics: CallAnalytics) -> Result<Executed, ExecutionError>;
/// Replays a given transaction for inspection.
fn replay(&self, t: TransactionID, analytics: CallAnalytics) -> Result<Executed, ReplayError>;
/// Returns traces matching given filter. /// Returns traces matching given filter.
fn filter_traces(&self, filter: TraceFilter) -> Option<Vec<LocalizedTrace>>; fn filter_traces(&self, filter: TraceFilter) -> Option<Vec<LocalizedTrace>>;

View File

@ -22,7 +22,7 @@ use basic_types::LogBloom;
use client::Error as ClientError; use client::Error as ClientError;
use ipc::binary::{BinaryConvertError, BinaryConvertable}; use ipc::binary::{BinaryConvertError, BinaryConvertable};
use types::block_import_error::BlockImportError; use types::block_import_error::BlockImportError;
pub use types::executed::ExecutionError; pub use types::executed::{ExecutionError, ReplayError};
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
/// Errors concerning transaction processing. /// Errors concerning transaction processing.

View File

@ -40,6 +40,7 @@ pub fn contract_address(address: &Address, nonce: &U256) -> Address {
} }
/// Transaction execution options. /// Transaction execution options.
#[derive(Default)]
pub struct TransactOptions { pub struct TransactOptions {
/// Enable call tracing. /// Enable call tracing.
pub tracing: bool, pub tracing: bool,

View File

@ -50,6 +50,7 @@ impl Tracer for ExecutiveTracer {
output: output.expect("self.prepare_trace_output().is_some(): so we must be tracing: qed") 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); self.traces.push(trace);
} }
@ -64,6 +65,7 @@ impl Tracer for ExecutiveTracer {
address: address address: address
}) })
}; };
debug!(target: "trace", "Traced create {:?}", trace);
self.traces.push(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")), action: Action::Call(call.expect("self.prepare_trace_call().is_some(): so we must be tracing: qed")),
result: Res::FailedCall, result: Res::FailedCall,
}; };
debug!(target: "trace", "Traced failed call {:?}", trace);
self.traces.push(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")), action: Action::Create(create.expect("self.prepare_trace_create().is_some(): so we must be tracing: qed")),
result: Res::FailedCreate, result: Res::FailedCreate,
}; };
debug!(target: "trace", "Traced failed create {:?}", trace);
self.traces.push(trace); self.traces.push(trace);
} }
@ -98,6 +102,7 @@ impl Tracer for ExecutiveTracer {
}), }),
result: Res::None, result: Res::None,
}; };
debug!(target: "trace", "Traced failed suicide {:?}", trace);
self.traces.push(trace); self.traces.push(trace);
} }

View File

@ -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. /// Transaction execution result.
pub type ExecutionResult = Result<Executed, ExecutionError>; pub type ExecutionResult = Result<Executed, ExecutionError>;

View File

@ -26,6 +26,14 @@ use v1::traits::Traces;
use v1::helpers::CallRequest as CRequest; use v1::helpers::CallRequest as CRequest;
use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, H256}; use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, H256};
fn to_call_analytics(flags: Vec<String>) -> 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. /// Traces api implementation.
pub struct TracesClient<C, M> where C: BlockChainClient, M: MinerService { pub struct TracesClient<C, M> where C: BlockChainClient, M: MinerService {
client: Weak<C>, client: Weak<C>,
@ -114,18 +122,11 @@ impl<C, M> Traces for TracesClient<C, M> where C: BlockChainClient + 'static, M:
fn call(&self, params: Params) -> Result<Value, Error> { fn call(&self, params: Params) -> Result<Value, Error> {
try!(self.active()); try!(self.active());
trace!(target: "jsonrpc", "call: {:?}", params);
from_params(params) from_params(params)
.and_then(|(request, flags)| { .and_then(|(request, flags)| {
let request = CallRequest::into(request); let request = CallRequest::into(request);
let flags: Vec<String> = 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)); 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(e) => to_value(&TraceResults::from(e)),
_ => Ok(Value::Null), _ => Ok(Value::Null),
} }
@ -134,17 +135,11 @@ impl<C, M> Traces for TracesClient<C, M> where C: BlockChainClient + 'static, M:
fn raw_transaction(&self, params: Params) -> Result<Value, Error> { fn raw_transaction(&self, params: Params) -> Result<Value, Error> {
try!(self.active()); try!(self.active());
trace!(target: "jsonrpc", "call: {:?}", params); from_params::<(Bytes, _)>(params)
from_params::<(Bytes, Vec<String>)>(params)
.and_then(|(raw_transaction, flags)| { .and_then(|(raw_transaction, flags)| {
let raw_transaction = raw_transaction.to_vec(); 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() { 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(e) => to_value(&TraceResults::from(e)),
_ => Ok(Value::Null), _ => Ok(Value::Null),
}, },
@ -152,4 +147,15 @@ impl<C, M> Traces for TracesClient<C, M> where C: BlockChainClient + 'static, M:
} }
}) })
} }
fn replay_transaction(&self, params: Params) -> Result<Value, Error> {
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),
}
})
}
} }

View File

@ -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. /// Executes the given raw transaction and returns a number of possible traces for it.
fn raw_transaction(&self, _: Params) -> Result<Value, Error>; fn raw_transaction(&self, _: Params) -> Result<Value, Error>;
/// Executes the transaction with the given hash and returns a number of possible traces for it.
fn replay_transaction(&self, _: Params) -> Result<Value, Error>;
/// Should be used to convert object to io delegate. /// Should be used to convert object to io delegate.
fn to_delegate(self) -> IoDelegate<Self> { fn to_delegate(self) -> IoDelegate<Self> {
let mut delegate = IoDelegate::new(Arc::new(self)); 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_block", Traces::block_traces);
delegate.add_method("trace_call", Traces::call); delegate.add_method("trace_call", Traces::call);
delegate.add_method("trace_rawTransaction", Traces::raw_transaction); delegate.add_method("trace_rawTransaction", Traces::raw_transaction);
delegate.add_method("trace_replayTransaction", Traces::replay_transaction);
delegate delegate
} }