Add RPC & client call to replay a transaction. (#1734)
* Add RPC & client call to replay a transaction. * Address grumbles
This commit is contained in:
parent
b9a08c36aa
commit
eaa41ea568
@ -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<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.
|
||||
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<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) {
|
||||
if self.mode != Mode::Active {
|
||||
self.wake_up();
|
||||
|
@ -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<Executed, ReplayError> {
|
||||
Ok(self.execution_result.read().clone().unwrap())
|
||||
}
|
||||
|
||||
fn block_total_difficulty(&self, _id: BlockID) -> Option<U256> {
|
||||
Some(U256::zero())
|
||||
}
|
||||
|
@ -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<Executed, ExecutionError>;
|
||||
|
||||
/// Replays a given transaction for inspection.
|
||||
fn replay(&self, t: TransactionID, analytics: CallAnalytics) -> Result<Executed, ReplayError>;
|
||||
|
||||
/// Returns traces matching given filter.
|
||||
fn filter_traces(&self, filter: TraceFilter) -> Option<Vec<LocalizedTrace>>;
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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<Executed, ExecutionError>;
|
||||
|
||||
|
@ -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<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.
|
||||
pub struct TracesClient<C, M> where C: BlockChainClient, M: MinerService {
|
||||
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> {
|
||||
try!(self.active());
|
||||
trace!(target: "jsonrpc", "call: {:?}", params);
|
||||
from_params(params)
|
||||
.and_then(|(request, flags)| {
|
||||
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));
|
||||
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<C, M> Traces for TracesClient<C, M> where C: BlockChainClient + 'static, M:
|
||||
|
||||
fn raw_transaction(&self, params: Params) -> Result<Value, Error> {
|
||||
try!(self.active());
|
||||
trace!(target: "jsonrpc", "call: {:?}", params);
|
||||
from_params::<(Bytes, Vec<String>)>(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<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),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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<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.
|
||||
fn to_delegate(self) -> IoDelegate<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_call", Traces::call);
|
||||
delegate.add_method("trace_rawTransaction", Traces::raw_transaction);
|
||||
delegate.add_method("trace_replayTransaction", Traces::replay_transaction);
|
||||
|
||||
delegate
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user