Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

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

Merged
merged 2 commits into from Jul 27, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
61 changes: 59 additions & 2 deletions ethcore/src/client/client.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -504,6 +504,18 @@ 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 => self.block_number(id).and_then(|n| self.state_at(BlockID::Number(n - 1))),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can overflow for genesis?

}
}

/// Get a copy of the best block's state.
pub fn state(&self) -> State {
State::from_existing(
Expand Down Expand Up @@ -660,6 +672,51 @@ impl BlockChainClient for Client {
ret
}

fn replay(&self, id: TransactionID, analytics: CallAnalytics) -> Result<Executed, ReplayError> {
if let Some(address) = self.transaction_address(id) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about this:

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

if address.index >= txs.len() {
  return Err(ReplayError::TransactionNotFound);
}
...

to avoid nesting?

if let (Some(block_data), Some(mut state)) = (self.block(BlockID::Hash(address.block_hash)), self.state_at_beginning(BlockID::Hash(address.block_hash))) {
let block = BlockView::new(&block_data);
let txs = block.transactions();
if txs.len() > address.index {
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))
} else {
// really weird. shouldn't ever actually happen.
Err(ReplayError::TransactionNotFound)
}
} else {
Err(ReplayError::StatePruned)
}
} else {
Err(ReplayError::TransactionNotFound)
}
}

fn keep_alive(&self) {
if self.mode != Mode::Active {
self.wake_up();
Expand Down
6 changes: 5 additions & 1 deletion ethcore/src/client/test_client.rs
Expand 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.
Expand Down Expand Up @@ -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())
}
Expand Down
5 changes: 4 additions & 1 deletion ethcore/src/client/traits.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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>>;

Expand Down
2 changes: 1 addition & 1 deletion ethcore/src/error.rs
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions ethcore/src/executive.rs
Expand Up @@ -39,6 +39,7 @@ pub fn contract_address(address: &Address, nonce: &U256) -> Address {
}

/// Transaction execution options.
#[derive(Default)]
pub struct TransactOptions {
/// Enable call tracing.
pub tracing: bool,
Expand Down
5 changes: 5 additions & 0 deletions ethcore/src/trace/executive_tracer.rs
Expand Up @@ -55,6 +55,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);
}

Expand All @@ -69,6 +70,7 @@ impl Tracer for ExecutiveTracer {
address: address
})
};
debug!(target: "trace", "Traced create {:?}", trace);
self.traces.push(trace);
}

Expand All @@ -84,6 +86,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);
}

Expand All @@ -94,6 +97,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);
}

Expand All @@ -108,6 +112,7 @@ impl Tracer for ExecutiveTracer {
}),
result: Res::None,
};
debug!(target: "trace", "Traced failed suicide {:?}", trace);
self.traces.push(trace);
}

Expand Down
25 changes: 25 additions & 0 deletions ethcore/src/types/executed.rs
Expand Up @@ -133,5 +133,30 @@ 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>;
38 changes: 22 additions & 16 deletions rpc/src/v1/impls/traces.rs
Expand Up @@ -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>,
Expand Down Expand Up @@ -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),
}
Expand All @@ -134,22 +135,27 @@ 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),
},
Err(_) => Err(Error::invalid_params()),
}
})
}

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),
}
})
}
}
4 changes: 4 additions & 0 deletions rpc/src/v1/traits/traces.rs
Expand Up @@ -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));
Expand All @@ -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
}
Expand Down