From 7cf807d1b4c7922b00be920453148721027e7305 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 26 Jul 2016 16:48:50 +0200 Subject: [PATCH] Various improvements to tracing & diagnostics. (#1707) * Various improvements to tracing & diagnostics. - Manage possibility of `Account` not having code for `PodAccount` - New RPC: `trace_sendRawTransaction` - See raw transaction dump when inspecting over RPC * Fix test * Remove one of the dupe error messages * Remove unneeded `&`s * Reformat and extremely minor optimisation * Minor optimisation * Remove unneeded let * Fix tests. * Additional fix. * Minor rename. [ci:skip] * Bowing to the pressure. --- ethcore/src/account.rs | 4 +-- ethcore/src/pod_account.rs | 50 ++++++++++++++++++++------------- rpc/src/v1/impls/traces.rs | 44 +++++++++++++++++------------ rpc/src/v1/tests/mocked/eth.rs | 2 +- rpc/src/v1/traits/traces.rs | 4 +++ rpc/src/v1/types/block.rs | 2 +- rpc/src/v1/types/mod.rs.in | 2 +- rpc/src/v1/types/trace.rs | 40 ++++++++++++++++++++++++++ rpc/src/v1/types/transaction.rs | 7 ++++- 9 files changed, 111 insertions(+), 44 deletions(-) diff --git a/ethcore/src/account.rs b/ethcore/src/account.rs index ff7bfe70d..f57817cb8 100644 --- a/ethcore/src/account.rs +++ b/ethcore/src/account.rs @@ -61,8 +61,8 @@ impl Account { nonce: pod.nonce, storage_root: SHA3_NULL_RLP, storage_overlay: RefCell::new(pod.storage.into_iter().map(|(k, v)| (k, (Filth::Dirty, v))).collect()), - code_hash: Some(pod.code.sha3()), - code_cache: pod.code, + code_hash: pod.code.as_ref().map(|c| c.sha3()), + code_cache: pod.code.as_ref().map_or_else(|| { warn!("POD account with unknown code is being created! Assuming no code."); vec![] }, |c| c.clone()), filth: Filth::Dirty, } } diff --git a/ethcore/src/pod_account.rs b/ethcore/src/pod_account.rs index d04833cab..9b6d953b2 100644 --- a/ethcore/src/pod_account.rs +++ b/ethcore/src/pod_account.rs @@ -28,8 +28,8 @@ pub struct PodAccount { pub balance: U256, /// The nonce of the account. pub nonce: U256, - /// The code of the account. - pub code: Bytes, + /// The code of the account or `None` in the special case that it is unknown. + pub code: Option, /// The storage of the account. pub storage: BTreeMap, } @@ -38,7 +38,7 @@ impl PodAccount { /// Construct new object. #[cfg(test)] pub fn new(balance: U256, nonce: U256, code: Bytes, storage: BTreeMap) -> PodAccount { - PodAccount { balance: balance, nonce: nonce, code: code, storage: storage } + PodAccount { balance: balance, nonce: nonce, code: Some(code), storage: storage } } /// Convert Account to a PodAccount. @@ -48,7 +48,7 @@ impl PodAccount { balance: *acc.balance(), nonce: *acc.nonce(), storage: acc.storage_overlay().iter().fold(BTreeMap::new(), |mut m, (k, &(_, ref v))| {m.insert(k.clone(), v.clone()); m}), - code: acc.code().unwrap().to_vec(), + code: acc.code().map(|x| x.to_vec()), } } @@ -58,14 +58,15 @@ impl PodAccount { stream.append(&self.nonce); stream.append(&self.balance); stream.append(&sec_trie_root(self.storage.iter().map(|(k, v)| (k.to_vec(), encode(&U256::from(v.as_slice())).to_vec())).collect())); - stream.append(&self.code.sha3()); + stream.append(&self.code.as_ref().unwrap_or(&vec![]).sha3()); stream.out() } /// Place additional data into given hash DB. pub fn insert_additional(&self, db: &mut AccountDBMut) { - if !self.code.is_empty() { - db.insert(&self.code); + match self.code { + Some(ref c) if !c.is_empty() => { db.insert(c); } + _ => {} } let mut r = H256::new(); let mut t = SecTrieDBMut::new(db, &mut r); @@ -80,7 +81,7 @@ impl From for PodAccount { PodAccount { balance: a.balance.into(), nonce: a.nonce.into(), - code: a.code.into(), + code: Some(a.code.into()), storage: a.storage.into_iter().map(|(key, value)| { let key: U256 = key.into(); let value: U256 = value.into(); @@ -95,7 +96,7 @@ impl From for PodAccount { PodAccount { balance: a.balance.map_or_else(U256::zero, Into::into), nonce: a.nonce.map_or_else(U256::zero, Into::into), - code: vec![], + code: Some(vec![]), storage: BTreeMap::new() } } @@ -103,7 +104,13 @@ impl From for PodAccount { impl fmt::Display for PodAccount { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "(bal={}; nonce={}; code={} bytes, #{}; storage={} items)", self.balance, self.nonce, self.code.len(), self.code.sha3(), self.storage.len()) + write!(f, "(bal={}; nonce={}; code={} bytes, #{}; storage={} items)", + self.balance, + self.nonce, + self.code.as_ref().map_or(0, |c| c.len()), + self.code.as_ref().map_or_else(H256::new, |c| c.sha3()), + self.storage.len() + ) } } @@ -114,13 +121,13 @@ pub fn diff_pod(pre: Option<&PodAccount>, post: Option<&PodAccount>) -> Option Some(AccountDiff { balance: Diff::Born(x.balance), nonce: Diff::Born(x.nonce), - code: Diff::Born(x.code.clone()), + code: Diff::Born(x.code.as_ref().expect("account is newly created; newly created accounts must be given code; all caches should remain in place; qed").clone()), storage: x.storage.iter().map(|(k, v)| (k.clone(), Diff::Born(v.clone()))).collect(), }), (Some(x), None) => Some(AccountDiff { balance: Diff::Died(x.balance), nonce: Diff::Died(x.nonce), - code: Diff::Died(x.code.clone()), + code: Diff::Died(x.code.as_ref().expect("account is deleted; only way to delete account is running SUICIDE; account must have had own code cached to make operation; all caches should remain in place; qed").clone()), storage: x.storage.iter().map(|(k, v)| (k.clone(), Diff::Died(v.clone()))).collect(), }), (Some(pre), Some(post)) => { @@ -130,7 +137,10 @@ pub fn diff_pod(pre: Option<&PodAccount>, post: Option<&PodAccount>) -> Option Diff::new(pre_code, post_code), + _ => Diff::Same, + }, storage: storage.into_iter().map(|k| (k.clone(), Diff::new( pre.storage.get(&k).cloned().unwrap_or_else(H256::new), @@ -156,7 +166,7 @@ mod test { #[test] fn existence() { - let a = PodAccount{balance: 69.into(), nonce: 0.into(), code: vec![], storage: map![]}; + let a = PodAccount{balance: 69.into(), nonce: 0.into(), code: Some(vec![]), storage: map![]}; assert_eq!(diff_pod(Some(&a), Some(&a)), None); assert_eq!(diff_pod(None, Some(&a)), Some(AccountDiff{ balance: Diff::Born(69.into()), @@ -168,8 +178,8 @@ mod test { #[test] fn basic() { - let a = PodAccount{balance: 69.into(), nonce: 0.into(), code: vec![], storage: map![]}; - let b = PodAccount{balance: 42.into(), nonce: 1.into(), code: vec![], storage: map![]}; + let a = PodAccount{balance: 69.into(), nonce: 0.into(), code: Some(vec![]), storage: map![]}; + let b = PodAccount{balance: 42.into(), nonce: 1.into(), code: Some(vec![]), storage: map![]}; assert_eq!(diff_pod(Some(&a), Some(&b)), Some(AccountDiff { balance: Diff::Changed(69.into(), 42.into()), nonce: Diff::Changed(0.into(), 1.into()), @@ -180,8 +190,8 @@ mod test { #[test] fn code() { - let a = PodAccount{balance: 0.into(), nonce: 0.into(), code: vec![], storage: map![]}; - let b = PodAccount{balance: 0.into(), nonce: 1.into(), code: vec![0], storage: map![]}; + let a = PodAccount{balance: 0.into(), nonce: 0.into(), code: Some(vec![]), storage: map![]}; + let b = PodAccount{balance: 0.into(), nonce: 1.into(), code: Some(vec![0]), storage: map![]}; assert_eq!(diff_pod(Some(&a), Some(&b)), Some(AccountDiff { balance: Diff::Same, nonce: Diff::Changed(0.into(), 1.into()), @@ -195,13 +205,13 @@ mod test { let a = PodAccount { balance: 0.into(), nonce: 0.into(), - code: vec![], + code: Some(vec![]), storage: map_into![1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 0, 6 => 0, 7 => 0] }; let b = PodAccount { balance: 0.into(), nonce: 0.into(), - code: vec![], + code: Some(vec![]), storage: map_into![1 => 1, 2 => 3, 3 => 0, 5 => 0, 7 => 7, 8 => 0, 9 => 9] }; assert_eq!(diff_pod(Some(&a), Some(&b)), Some(AccountDiff { diff --git a/rpc/src/v1/impls/traces.rs b/rpc/src/v1/impls/traces.rs index 71176df27..24713bd61 100644 --- a/rpc/src/v1/impls/traces.rs +++ b/rpc/src/v1/impls/traces.rs @@ -18,14 +18,13 @@ use std::sync::{Weak, Arc}; use jsonrpc_core::*; -use std::collections::BTreeMap; -//use util::H256; +use util::rlp::{UntrustedRlp, View}; use ethcore::client::{BlockChainClient, CallAnalytics, TransactionID, TraceId}; use ethcore::miner::MinerService; use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action}; use v1::traits::Traces; use v1::helpers::CallRequest as CRequest; -use v1::types::{TraceFilter, LocalizedTrace, Trace, BlockNumber, Index, CallRequest, Bytes, StateDiff, VMTrace, H256}; +use v1::types::{TraceFilter, LocalizedTrace, BlockNumber, Index, CallRequest, Bytes, TraceResults, H256}; /// Traces api implementation. pub struct TracesClient where C: BlockChainClient, M: MinerService { @@ -126,22 +125,31 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: state_diffing: flags.contains(&("stateDiff".to_owned())), }; let signed = try!(self.sign_call(request)); - 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 { - ret.insert("vmTrace".to_owned(), to_value(&VMTrace::from(vm_trace)).unwrap()); - } - if let Some(state_diff) = executed.state_diff { - ret.insert("stateDiff".to_owned(), to_value(&StateDiff::from(state_diff)).unwrap()); - } - return Ok(Value::Object(ret)) + match take_weak!(self.client).call(&signed, analytics) { + Ok(e) => to_value(&TraceResults::from(e)), + _ => Ok(Value::Null), + } + }) + } + + fn raw_transaction(&self, params: Params) -> Result { + try!(self.active()); + trace!(target: "jsonrpc", "call: {:?}", params); + from_params::<(Bytes, Vec)>(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(e) => to_value(&TraceResults::from(e)), + _ => Ok(Value::Null), + }, + Err(_) => Err(Error::invalid_params()), } - Ok(Value::Null) }) } } diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index b27bff574..85452691d 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -367,7 +367,7 @@ fn rpc_eth_pending_transaction_by_hash() { tester.miner.pending_transactions.lock().insert(H256::zero(), tx); } - let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"creates":null,"from":"0x0f65fe9276bc9a24ae7083ae28e2660ef72df99e","gas":"0x5208","gasPrice":"0x01","hash":"0x41df922fd0d4766fcc02e161f8295ec28522f329ae487f14d811e4b64c8d6e31","input":"0x","nonce":"0x00","to":"0x095e7baea6a6c7c4c2dfeb977efac326af552d87","transactionIndex":null,"value":"0x0a"},"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"creates":null,"from":"0x0f65fe9276bc9a24ae7083ae28e2660ef72df99e","gas":"0x5208","gasPrice":"0x01","hash":"0x41df922fd0d4766fcc02e161f8295ec28522f329ae487f14d811e4b64c8d6e31","input":"0x","nonce":"0x00","raw":"0xf85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804","to":"0x095e7baea6a6c7c4c2dfeb977efac326af552d87","transactionIndex":null,"value":"0x0a"},"id":1}"#; let request = r#"{ "jsonrpc": "2.0", "method": "eth_getTransactionByHash", diff --git a/rpc/src/v1/traits/traces.rs b/rpc/src/v1/traits/traces.rs index 45fa916be..a05b2de01 100644 --- a/rpc/src/v1/traits/traces.rs +++ b/rpc/src/v1/traits/traces.rs @@ -35,6 +35,9 @@ pub trait Traces: Sized + Send + Sync + 'static { /// Executes the given call and returns a number of possible traces for it. fn call(&self, _: Params) -> Result; + /// Executes the given raw transaction and returns a number of possible traces for it. + fn raw_transaction(&self, _: Params) -> Result; + /// Should be used to convert object to io delegate. fn to_delegate(self) -> IoDelegate { let mut delegate = IoDelegate::new(Arc::new(self)); @@ -43,6 +46,7 @@ pub trait Traces: Sized + Send + Sync + 'static { delegate.add_method("trace_transaction", Traces::transaction_traces); delegate.add_method("trace_block", Traces::block_traces); delegate.add_method("trace_call", Traces::call); + delegate.add_method("trace_rawTransaction", Traces::raw_transaction); delegate } diff --git a/rpc/src/v1/types/block.rs b/rpc/src/v1/types/block.rs index c1de9a276..08fe37c61 100644 --- a/rpc/src/v1/types/block.rs +++ b/rpc/src/v1/types/block.rs @@ -103,7 +103,7 @@ mod tests { fn test_serialize_block_transactions() { let t = BlockTransactions::Full(vec![Transaction::default()]); let serialized = serde_json::to_string(&t).unwrap(); - assert_eq!(serialized, r#"[{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x00","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x00","gasPrice":"0x00","gas":"0x00","input":"0x","creates":null}]"#); + assert_eq!(serialized, r#"[{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x00","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x00","gasPrice":"0x00","gas":"0x00","input":"0x","creates":null,"raw":"0x"}]"#); let t = BlockTransactions::Hashes(vec![H256::default().into()]); let serialized = serde_json::to_string(&t).unwrap(); diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index ecbe6d9a0..75f78906b 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -42,6 +42,6 @@ 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, LocalizedTrace, StateDiff, VMTrace}; +pub use self::trace::{LocalizedTrace, TraceResults}; pub use self::trace_filter::TraceFilter; pub use self::uint::U256; diff --git a/rpc/src/v1/types/trace.rs b/rpc/src/v1/types/trace.rs index 187a9a9b0..e32fb0e57 100644 --- a/rpc/src/v1/types/trace.rs +++ b/rpc/src/v1/types/trace.rs @@ -21,6 +21,7 @@ use ethcore::trace::{Trace as EthTrace, LocalizedTrace as EthLocalizedTrace}; use ethcore::trace as et; use ethcore::state_diff; use ethcore::account_diff; +use ethcore::client::Executed; use util::Uint; use v1::types::{Bytes, H160, H256, U256}; @@ -193,6 +194,7 @@ impl From for AccountDiff { } } +#[derive(Debug)] /// Serde-friendly `StateDiff` shadow. pub struct StateDiff(BTreeMap); @@ -444,6 +446,32 @@ impl From for Trace { } } +#[derive(Debug, Serialize)] +/// A diff of some chunk of memory. +pub struct TraceResults { + /// The output of the call/create + pub output: Vec, + /// The transaction trace. + pub trace: Option, + /// The transaction trace. + #[serde(rename="vmTrace")] + pub vm_trace: Option, + /// The transaction trace. + #[serde(rename="stateDiff")] + pub state_diff: Option, +} + +impl From for TraceResults { + fn from(t: Executed) -> Self { + TraceResults { + output: t.output.into(), + trace: t.trace.map(Into::into), + vm_trace: t.vm_trace.map(Into::into), + state_diff: t.state_diff.map(Into::into), + } + } +} + #[cfg(test)] mod tests { use serde_json; @@ -451,6 +479,18 @@ mod tests { use v1::types::{Bytes, U256, H256, H160}; use super::*; + #[test] + fn should_serialize_trace_results() { + let r = TraceResults { + output: vec![0x60], + trace: None, + vm_trace: None, + state_diff: None, + }; + let serialized = serde_json::to_string(&r).unwrap(); + assert_eq!(serialized, r#"{"output":[96],"trace":null,"vmTrace":null,"stateDiff":null}"#); + } + #[test] fn test_trace_serialize() { let t = LocalizedTrace { diff --git a/rpc/src/v1/types/transaction.rs b/rpc/src/v1/types/transaction.rs index cb554b172..812c006e4 100644 --- a/rpc/src/v1/types/transaction.rs +++ b/rpc/src/v1/types/transaction.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use util::rlp::encode; use ethcore::contract_address; use ethcore::transaction::{LocalizedTransaction, Action, SignedTransaction}; use v1::types::{Bytes, H160, H256, U256}; @@ -49,6 +50,8 @@ pub struct Transaction { pub input: Bytes, /// Creates contract pub creates: Option, + /// Raw transaction data + pub raw: Bytes, } impl From for Transaction { @@ -72,6 +75,7 @@ impl From for Transaction { Action::Create => Some(contract_address(&t.sender().unwrap(), &t.nonce).into()), Action::Call(_) => None, }, + raw: encode(&t.signed).to_vec().into(), } } } @@ -97,6 +101,7 @@ impl From for Transaction { Action::Create => Some(contract_address(&t.sender().unwrap(), &t.nonce).into()), Action::Call(_) => None, }, + raw: encode(&t).to_vec().into(), } } } @@ -110,7 +115,7 @@ mod tests { fn test_transaction_serialize() { let t = Transaction::default(); let serialized = serde_json::to_string(&t).unwrap(); - assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x00","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x00","gasPrice":"0x00","gas":"0x00","input":"0x","creates":null}"#); + assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x00","blockHash":null,"blockNumber":null,"transactionIndex":null,"from":"0x0000000000000000000000000000000000000000","to":null,"value":"0x00","gasPrice":"0x00","gas":"0x00","input":"0x","creates":null,"raw":"0x"}"#); } }