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
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();

View File

@@ -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())
}

View File

@@ -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>>;

View File

@@ -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.

View File

@@ -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,

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")
})
};
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);
}

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.
pub type ExecutionResult = Result<Executed, ExecutionError>;