diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 6f44dd5bb..03f38891a 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -479,7 +479,7 @@ impl BlockChainClient for Client where V: Verifier { // give the sender a sufficient balance state.add_balance(&sender, &(needed_balance - balance)); } - let options = TransactOptions { tracing: false, vm_tracing: analytics.vm_tracing, check_nonce: false }; + let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false }; let mut ret = Executive::new(&mut state, &env_info, self.engine.deref().deref(), &self.vm_factory).transact(t, options); // TODO gav move this into Executive. diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 0dffb1a1c..947da4904 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -52,6 +52,8 @@ use error::Error as EthError; /// Options concerning what analytics we run on the call. #[derive(Eq, PartialEq, Default, Clone, Copy, Debug)] pub struct CallAnalytics { + /// Make a transaction trace. + pub transaction_tracing: bool, /// Make a VM trace. pub vm_tracing: bool, /// Make a diff. diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index d35601970..9a183fc6e 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -280,7 +280,7 @@ impl MinerService for Miner { // give the sender a sufficient balance state.add_balance(&sender, &(needed_balance - balance)); } - let options = TransactOptions { tracing: false, vm_tracing: analytics.vm_tracing, check_nonce: false }; + let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false }; let mut ret = Executive::new(&mut state, &env_info, self.engine(), chain.vm_factory()).transact(t, options); // TODO gav move this into Executive. diff --git a/rpc/src/v1/impls/traces.rs b/rpc/src/v1/impls/traces.rs index 4cf8b8dfa..9e5ca0093 100644 --- a/rpc/src/v1/impls/traces.rs +++ b/rpc/src/v1/impls/traces.rs @@ -20,15 +20,11 @@ use std::sync::{Weak, Arc}; use jsonrpc_core::*; use std::collections::BTreeMap; use util::{H256, U256, Uint}; -use serde; use ethcore::client::{BlockChainClient, CallAnalytics, TransactionID, TraceId}; -use ethcore::trace::VMTrace; use ethcore::miner::MinerService; -use ethcore::state_diff::StateDiff; -use ethcore::account_diff::{Diff, Existance}; use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action}; use v1::traits::Traces; -use v1::types::{TraceFilter, Trace, BlockNumber, Index, CallRequest}; +use v1::types::{TraceFilter, LocalizedTrace, Trace, BlockNumber, Index, CallRequest, Bytes, StateDiff, VMTrace}; /// Traces api implementation. pub struct TracesClient where C: BlockChainClient, M: MinerService { @@ -61,102 +57,13 @@ impl TracesClient where C: BlockChainClient, M: MinerService { } } -fn vm_trace_to_object(t: &VMTrace) -> Value { - let mut ret = BTreeMap::new(); - ret.insert("code".to_owned(), to_value(&t.code).unwrap()); - - let mut subs = t.subs.iter(); - let mut next_sub = subs.next(); - - let ops = t.operations - .iter() - .enumerate() - .map(|(i, op)| { - let mut m = map![ - "pc".to_owned() => to_value(&op.pc).unwrap(), - "cost".to_owned() => match op.gas_cost <= U256::from(!0u64) { - true => to_value(&op.gas_cost.low_u64()), - false => to_value(&op.gas_cost), - }.unwrap() - ]; - if let Some(ref ex) = op.executed { - let mut em = map![ - "used".to_owned() => to_value(&ex.gas_used.low_u64()).unwrap(), - "push".to_owned() => to_value(&ex.stack_push).unwrap() - ]; - if let Some(ref md) = ex.mem_diff { - em.insert("mem".to_owned(), Value::Object(map![ - "off".to_owned() => to_value(&md.offset).unwrap(), - "data".to_owned() => to_value(&md.data).unwrap() - ])); - } - if let Some(ref sd) = ex.store_diff { - em.insert("store".to_owned(), Value::Object(map![ - "key".to_owned() => to_value(&sd.location).unwrap(), - "val".to_owned() => to_value(&sd.value).unwrap() - ])); - } - m.insert("ex".to_owned(), Value::Object(em)); - } - if next_sub.is_some() && next_sub.unwrap().parent_step == i { - m.insert("sub".to_owned(), vm_trace_to_object(next_sub.unwrap())); - next_sub = subs.next(); - } - Value::Object(m) - }) - .collect::>(); - ret.insert("ops".to_owned(), Value::Array(ops)); - Value::Object(ret) -} - -fn diff_to_object(d: &Diff) -> Value where T: serde::Serialize + Eq { - let mut ret = BTreeMap::new(); - match *d { - Diff::Same => { - ret.insert("diff".to_owned(), Value::String("=".to_owned())); - } - Diff::Born(ref x) => { - ret.insert("diff".to_owned(), Value::String("+".to_owned())); - ret.insert("+".to_owned(), to_value(x).unwrap()); - } - Diff::Died(ref x) => { - ret.insert("diff".to_owned(), Value::String("-".to_owned())); - ret.insert("-".to_owned(), to_value(x).unwrap()); - } - Diff::Changed(ref from, ref to) => { - ret.insert("diff".to_owned(), Value::String("*".to_owned())); - ret.insert("-".to_owned(), to_value(from).unwrap()); - ret.insert("+".to_owned(), to_value(to).unwrap()); - } - }; - Value::Object(ret) -} - -fn state_diff_to_object(t: &StateDiff) -> Value { - Value::Object(t.iter().map(|(address, account)| { - (address.hex(), Value::Object(map![ - "existance".to_owned() => Value::String(match account.existance() { - Existance::Born => "+", - Existance::Alive => ".", - Existance::Died => "-", - }.to_owned()), - "balance".to_owned() => diff_to_object(&account.balance), - "nonce".to_owned() => diff_to_object(&account.nonce), - "code".to_owned() => diff_to_object(&account.code), - "storage".to_owned() => Value::Object(account.storage.iter().map(|(key, val)| { - (key.hex(), diff_to_object(&val)) - }).collect::>()) - ])) - }).collect::>()) -} - impl Traces for TracesClient where C: BlockChainClient + 'static, M: MinerService + 'static { fn filter(&self, params: Params) -> Result { from_params::<(TraceFilter,)>(params) .and_then(|(filter, )| { let client = take_weak!(self.client); let traces = client.filter_traces(filter.into()); - let traces = traces.map_or_else(Vec::new, |traces| traces.into_iter().map(Trace::from).collect()); + let traces = traces.map_or_else(Vec::new, |traces| traces.into_iter().map(LocalizedTrace::from).collect()); to_value(&traces) }) } @@ -166,7 +73,7 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: .and_then(|(block_number,)| { let client = take_weak!(self.client); let traces = client.block_traces(block_number.into()); - let traces = traces.map_or_else(Vec::new, |traces| traces.into_iter().map(Trace::from).collect()); + let traces = traces.map_or_else(Vec::new, |traces| traces.into_iter().map(LocalizedTrace::from).collect()); to_value(&traces) }) } @@ -176,7 +83,7 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: .and_then(|(transaction_hash,)| { let client = take_weak!(self.client); let traces = client.transaction_traces(TransactionID::Hash(transaction_hash)); - let traces = traces.map_or_else(Vec::new, |traces| traces.into_iter().map(Trace::from).collect()); + let traces = traces.map_or_else(Vec::new, |traces| traces.into_iter().map(LocalizedTrace::from).collect()); to_value(&traces) }) } @@ -190,36 +97,36 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: address: address.into_iter().map(|i| i.value()).collect() }; let trace = client.trace(id); - let trace = trace.map(Trace::from); + let trace = trace.map(LocalizedTrace::from); to_value(&trace) }) } - fn vm_trace_call(&self, params: Params) -> Result { - trace!(target: "jsonrpc", "vm_trace_call: {:?}", params); + fn call(&self, params: Params) -> Result { + trace!(target: "jsonrpc", "call: {:?}", params); from_params(params) - .and_then(|(request,)| { + .and_then(|(request, flags)| { + let flags: Vec = 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 r = take_weak!(self.client).call(&signed, CallAnalytics{ vm_tracing: true, state_diffing: false }); + let r = take_weak!(self.client).call(&signed, analytics); if let Ok(executed) = r { + // TODO maybe add other stuff to this? + let mut ret = map!["output".to_owned() => to_value(&Bytes(executed.output)).unwrap()]; + if let Some(trace) = executed.trace { + ret.insert("trace".to_owned(), to_value(&Trace::from(trace)).unwrap()); + } if let Some(vm_trace) = executed.vm_trace { - return Ok(vm_trace_to_object(&vm_trace)); + ret.insert("vmTrace".to_owned(), to_value(&VMTrace::from(vm_trace)).unwrap()); } - } - Ok(Value::Null) - }) - } - - fn state_diff_call(&self, params: Params) -> Result { - trace!(target: "jsonrpc", "state_diff_call: {:?}", params); - from_params(params) - .and_then(|(request,)| { - let signed = try!(self.sign_call(request)); - let r = take_weak!(self.client).call(&signed, CallAnalytics{ vm_tracing: false, state_diffing: true }); - if let Ok(executed) = r { if let Some(state_diff) = executed.state_diff { - return Ok(state_diff_to_object(&state_diff)); + ret.insert("stateDiff".to_owned(), to_value(&StateDiff::from(state_diff)).unwrap()); } + return Ok(Value::Object(ret)) } Ok(Value::Null) }) diff --git a/rpc/src/v1/traits/traces.rs b/rpc/src/v1/traits/traces.rs index 4b58b12ac..45fa916be 100644 --- a/rpc/src/v1/traits/traces.rs +++ b/rpc/src/v1/traits/traces.rs @@ -32,11 +32,8 @@ pub trait Traces: Sized + Send + Sync + 'static { /// Returns all traces produced at given block. fn block_traces(&self, _: Params) -> Result; - /// Executes the given call and returns the VM trace for it. - fn vm_trace_call(&self, _: Params) -> Result; - - /// Executes the given call and returns the diff for it. - fn state_diff_call(&self, params: Params) -> Result; + /// Executes the given call and returns a number of possible traces for it. + fn call(&self, _: Params) -> Result; /// Should be used to convert object to io delegate. fn to_delegate(self) -> IoDelegate { @@ -45,9 +42,7 @@ pub trait Traces: Sized + Send + Sync + 'static { delegate.add_method("trace_get", Traces::trace); delegate.add_method("trace_transaction", Traces::transaction_traces); delegate.add_method("trace_block", Traces::block_traces); - - delegate.add_method("trace_vmTraceCall", Traces::vm_trace_call); - delegate.add_method("trace_stateDiffCall", Traces::state_diff_call); + delegate.add_method("trace_call", Traces::call); delegate } diff --git a/rpc/src/v1/types/bytes.rs b/rpc/src/v1/types/bytes.rs index 76d84d0dd..1bf5deb75 100644 --- a/rpc/src/v1/types/bytes.rs +++ b/rpc/src/v1/types/bytes.rs @@ -36,6 +36,12 @@ impl Bytes { } } +impl From> for Bytes { + fn from(bytes: Vec) -> Bytes { + Bytes(bytes) + } +} + impl Serialize for Bytes { fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer { diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index b4e82a28b..3f07bfb31 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -41,5 +41,5 @@ pub use self::transaction::Transaction; pub use self::transaction_request::{TransactionRequest, TransactionConfirmation, TransactionModification}; pub use self::call_request::CallRequest; pub use self::receipt::Receipt; -pub use self::trace::Trace; +pub use self::trace::{Trace, LocalizedTrace, StateDiff, VMTrace}; pub use self::trace_filter::TraceFilter; diff --git a/rpc/src/v1/types/trace.rs b/rpc/src/v1/types/trace.rs index 6ea58543a..5b4192886 100644 --- a/rpc/src/v1/types/trace.rs +++ b/rpc/src/v1/types/trace.rs @@ -14,11 +14,201 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use util::{Address, U256, H256}; +use std::collections::BTreeMap; +use util::{Address, U256, H256, Uint}; +use serde::{Serialize, Serializer}; use ethcore::trace::trace; -use ethcore::trace::LocalizedTrace; +use ethcore::trace::{Trace as EthTrace, LocalizedTrace as EthLocalizedTrace}; +use ethcore::trace as et; +use ethcore::state_diff; +use ethcore::account_diff; use v1::types::Bytes; +#[derive(Debug, Serialize)] +/// A diff of some chunk of memory. +pub struct MemoryDiff { + /// Offset into memory the change begins. + pub off: usize, + /// The changed data. + pub data: Vec, +} + +impl From for MemoryDiff { + fn from(c: et::MemoryDiff) -> Self { + MemoryDiff { + off: c.offset, + data: c.data, + } + } +} + +#[derive(Debug, Serialize)] +/// A diff of some storage value. +pub struct StorageDiff { + /// Which key in storage is changed. + pub key: U256, + /// What the value has been changed to. + pub val: U256, +} + +impl From for StorageDiff { + fn from(c: et::StorageDiff) -> Self { + StorageDiff { + key: c.location, + val: c.value, + } + } +} + +#[derive(Debug, Serialize)] +/// A record of an executed VM operation. +pub struct VMExecutedOperation { + /// The total gas used. + #[serde(rename="used")] + pub used: u64, + /// The stack item placed, if any. + pub push: Vec, + /// If altered, the memory delta. + #[serde(rename="mem")] + pub mem: Option, + /// The altered storage value, if any. + #[serde(rename="store")] + pub store: Option, +} + +impl From for VMExecutedOperation { + fn from(c: et::VMExecutedOperation) -> Self { + VMExecutedOperation { + used: c.gas_used.low_u64(), + push: c.stack_push, + mem: c.mem_diff.map(From::from), + store: c.store_diff.map(From::from), + } + } +} + +#[derive(Debug, Serialize)] +/// A record of the execution of a single VM operation. +pub struct VMOperation { + /// The program counter. + pub pc: usize, + /// The gas cost for this instruction. + pub cost: u64, + /// Information concerning the execution of the operation. + pub ex: Option, + /// Subordinate trace of the CALL/CREATE if applicable. + pub sub: Option, +} + +impl From<(et::VMOperation, Option)> for VMOperation { + fn from(c: (et::VMOperation, Option)) -> Self { + VMOperation { + pc: c.0.pc, + cost: c.0.gas_cost.low_u64(), + ex: c.0.executed.map(From::from), + sub: c.1.map(From::from), + } + } +} + +#[derive(Debug, Serialize)] +/// A record of a full VM trace for a CALL/CREATE. +pub struct VMTrace { + /// The code to be executed. + pub code: Vec, + /// The operations executed. + pub ops: Vec, +} + +impl From for VMTrace { + fn from(c: et::VMTrace) -> Self { + let mut subs = c.subs.into_iter(); + let mut next_sub = subs.next(); + VMTrace { + code: c.code, + ops: c.operations + .into_iter() + .enumerate() + .map(|(i, op)| (op, { + let have_sub = next_sub.is_some() && next_sub.as_ref().unwrap().parent_step == i; + if have_sub { + let r = next_sub.clone(); + next_sub = subs.next(); + r + } else { None } + }).into()) + .collect(), + } + } +} + +#[derive(Debug, Serialize)] +/// Aux type for Diff::Changed. +pub struct ChangedType where T: Serialize { + from: T, + to: T, +} + +#[derive(Debug, Serialize)] +/// Serde-friendly `Diff` shadow. +pub enum Diff where T: Serialize { + #[serde(rename="=")] + Same, + #[serde(rename="+")] + Born(T), + #[serde(rename="-")] + Died(T), + #[serde(rename="*")] + Changed(ChangedType), +} + +impl From> for Diff where T: Eq, U: Serialize + From { + fn from(c: account_diff::Diff) -> Self { + match c { + account_diff::Diff::Same => Diff::Same, + account_diff::Diff::Born(t) => Diff::Born(t.into()), + account_diff::Diff::Died(t) => Diff::Died(t.into()), + account_diff::Diff::Changed(t, u) => Diff::Changed(ChangedType{from: t.into(), to: u.into()}), + } + } +} + +#[derive(Debug, Serialize)] +/// Serde-friendly `AccountDiff` shadow. +pub struct AccountDiff { + pub balance: Diff, + pub nonce: Diff, + pub code: Diff, + pub storage: BTreeMap>, +} + +impl From for AccountDiff { + fn from(c: account_diff::AccountDiff) -> Self { + AccountDiff { + balance: c.balance.into(), + nonce: c.nonce.into(), + code: c.code.into(), + storage: c.storage.into_iter().map(|(k, v)| (k, v.into())).collect(), + } + } +} + +/// Serde-friendly `StateDiff` shadow. +pub struct StateDiff(BTreeMap); + +impl Serialize for StateDiff { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> + where S: Serializer { + Serialize::serialize(&self.0, serializer) + } +} + +impl From for StateDiff { + fn from(c: state_diff::StateDiff) -> Self { + StateDiff(c.0.into_iter().map(|(k, v)| (k, v.into())).collect()) + } +} + /// Create response #[derive(Debug, Serialize)] pub struct Create { @@ -161,7 +351,7 @@ impl From for Res { /// Trace #[derive(Debug, Serialize)] -pub struct Trace { +pub struct LocalizedTrace { /// Action action: Action, /// Result @@ -185,9 +375,9 @@ pub struct Trace { block_hash: H256, } -impl From for Trace { - fn from(t: LocalizedTrace) -> Self { - Trace { +impl From for LocalizedTrace { + fn from(t: EthLocalizedTrace) -> Self { + LocalizedTrace { action: From::from(t.action), result: From::from(t.result), trace_address: t.trace_address.into_iter().map(From::from).collect(), @@ -200,6 +390,30 @@ impl From for Trace { } } +/// Trace +#[derive(Debug, Serialize)] +pub struct Trace { + /// Depth within the call trace tree. + depth: usize, + /// Action + action: Action, + /// Result + result: Res, + /// Subtraces + subtraces: Vec, +} + +impl From for Trace { + fn from(t: EthTrace) -> Self { + Trace { + depth: t.depth.into(), + action: t.action.into(), + result: t.result.into(), + subtraces: t.subs.into_iter().map(From::from).collect(), + } + } +} + #[cfg(test)] mod tests { use serde_json; @@ -209,7 +423,7 @@ mod tests { #[test] fn test_trace_serialize() { - let t = Trace { + let t = LocalizedTrace { action: Action::Call(Call { from: Address::from(4), to: Address::from(5),