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