diff --git a/Cargo.lock b/Cargo.lock index 98d70bbc7..74acc0e03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,6 +105,11 @@ name = "blastfig" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bloomchain" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "byteorder" version = "0.5.1" @@ -246,6 +251,7 @@ dependencies = [ name = "ethcore" version = "1.1.0" dependencies = [ + "bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "clippy 0.0.63 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index a43092b22..2fdca148c 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -22,6 +22,7 @@ crossbeam = "0.1.5" lazy_static = "0.1" ethcore-devtools = { path = "../devtools" } ethjson = { path = "../json" } +bloomchain = "0.1" [features] jit = ["evmjit"] diff --git a/ethcore/src/block.rs b/ethcore/src/block.rs index b8af3f733..782a20f52 100644 --- a/ethcore/src/block.rs +++ b/ethcore/src/block.rs @@ -22,6 +22,7 @@ use common::*; use engine::*; use state::*; use verification::PreverifiedBlock; +use trace::Trace; /// A block, encoded as it is on the block chain. #[derive(Default, Debug, Clone)] diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index ad74447a1..7e5f896fd 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -55,9 +55,6 @@ impl Default for BlockChainConfig { /// Interface for querying blocks by hash and by number. pub trait BlockProvider { - /// True if we store full tracing information for transactions. - fn have_tracing(&self) -> bool; - /// Returns true if the given block is known /// (though not necessarily a part of the canon chain). fn is_known(&self, hash: &H256) -> bool; @@ -186,9 +183,6 @@ impl BlockProvider for BlockChain { self.extras_db.exists_with_cache(&self.block_details, hash) } - /// We do not store tracing information. - fn have_tracing(&self) -> bool { false } - /// Get raw block data fn block(&self, hash: &H256) -> Option { { @@ -734,9 +728,10 @@ impl BlockChain { self.query_extras(hash, &self.blocks_blooms) } - fn query_extras(&self, hash: &K, cache: &RwLock>) -> Option where + fn query_extras(&self, hash: &K, cache: &RwLock>) -> Option where T: ExtrasIndexable + Clone + Decodable, - K: Key + Eq + Hash + Clone, + K: Key + Eq + Hash + Clone, + R: Deref, H256: From { self.note_used(CacheID::Extras(T::index(), H256::from(hash.clone()))); self.extras_db.read_with_cache(cache, hash) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 72d227a71..964bb7bad 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -42,6 +42,7 @@ use env_info::EnvInfo; use executive::{Executive, Executed, TransactOptions, contract_address}; use receipt::LocalizedReceipt; pub use blockchain::CacheSize as BlockChainCacheSize; +use trace::{TraceDB, Database as TraceDatabase}; /// General block status #[derive(Debug, Eq, PartialEq)] @@ -103,6 +104,7 @@ impl ClientReport { /// Call `import_block()` to import a block asynchronously; `flush_queue()` flushes the queue. pub struct Client where V: Verifier { chain: Arc, + tracedb: Arc>, engine: Arc>, state_db: Mutex>, block_queue: BlockQueue, @@ -150,6 +152,7 @@ impl Client where V: Verifier { let path = get_db_path(path, config.pruning, spec.genesis_header().hash()); let gb = spec.genesis_block(); let chain = Arc::new(BlockChain::new(config.blockchain, &gb, &path)); + let tracedb = Arc::new(TraceDB::new(config.tracing, &path, chain.clone())); let mut state_db = journaldb::new(&append_path(&path, "state"), config.pruning); @@ -165,6 +168,7 @@ impl Client where V: Verifier { Arc::new(Client { chain: chain, + tracedb: tracedb, engine: engine, state_db: Mutex::new(state_db), block_queue: block_queue, @@ -225,7 +229,7 @@ impl Client where V: Verifier { let last_hashes = self.build_last_hashes(header.parent_hash.clone()); let db = self.state_db.lock().unwrap().boxed_clone(); - let enact_result = enact_verified(&block, engine, self.chain.have_tracing(), db, &parent, last_hashes); + let enact_result = enact_verified(&block, engine, self.tracedb.tracing_enabled(), db, &parent, last_hashes); if let Err(e) = enact_result { warn!(target: "client", "Block import failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); return Err(()); diff --git a/ethcore/src/client/config.rs b/ethcore/src/client/config.rs index 89e95ea06..df685f0d1 100644 --- a/ethcore/src/client/config.rs +++ b/ethcore/src/client/config.rs @@ -16,6 +16,7 @@ pub use block_queue::BlockQueueConfig; pub use blockchain::BlockChainConfig; +pub use trace::{Config as TraceConfig, Switch}; use util::journaldb; /// Client configuration. Includes configs for all sub-systems. @@ -25,6 +26,8 @@ pub struct ClientConfig { pub queue: BlockQueueConfig, /// Blockchain configuration. pub blockchain: BlockChainConfig, + /// Trace configuration. + pub tracing: TraceConfig, /// The JournalDB ("pruning") algorithm to use. pub pruning: journaldb::Algorithm, /// The name of the client instance. diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 02885574f..57be070e4 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -20,6 +20,7 @@ mod client; mod config; mod ids; mod test_client; +mod trace; pub use self::client::*; pub use self::config::{ClientConfig, BlockQueueConfig, BlockChainConfig}; diff --git a/ethcore/src/client/trace.rs b/ethcore/src/client/trace.rs new file mode 100644 index 000000000..e7023c83b --- /dev/null +++ b/ethcore/src/client/trace.rs @@ -0,0 +1,38 @@ + +//! Bridge between Tracedb and Blockchain. + +use std::ops::Range; +use util::{Address, H256}; +use header::BlockNumber; +use trace::DatabaseExtras as TraceDatabaseExtras; +use blockchain::{BlockChain, BlockProvider}; +use extras::TransactionAddress; +use super::BlockId; + +impl TraceDatabaseExtras for BlockChain { + fn block_hash(&self, block_number: BlockNumber) -> Option { + (self as &BlockProvider).block_hash(block_number) + } + + fn transaction_hash(&self, block_number: BlockNumber, tx_position: usize) -> Option { + (self as &BlockProvider).block_hash(block_number) + .and_then(|block_hash| { + let tx_address = TransactionAddress { + block_hash: block_hash, + index: tx_position + }; + self.transaction(&tx_address) + }) + .map(|tx| tx.hash()) + } +} + +/// Easy to use trace filter. +pub struct Filter { + /// Range of filtering. + pub range: Range, + /// From address. + pub from_address: Vec
, + /// To address. + pub to_address: Vec
, +} diff --git a/ethcore/src/common.rs b/ethcore/src/common.rs index e91501217..2c66710c0 100644 --- a/ethcore/src/common.rs +++ b/ethcore/src/common.rs @@ -26,4 +26,3 @@ pub use transaction::*; pub use log_entry::*; pub use receipt::*; pub use action_params::*; -pub use trace::*; \ No newline at end of file diff --git a/ethcore/src/db.rs b/ethcore/src/db.rs index 3dca6ca19..2c5f7c73a 100644 --- a/ethcore/src/db.rs +++ b/ethcore/src/db.rs @@ -16,10 +16,11 @@ //! Extras db utils. +use std::ops::Deref; use std::hash::Hash; use std::sync::RwLock; use std::collections::HashMap; -use util::{H264, DBTransaction, Database}; +use util::{DBTransaction, Database}; use util::rlp::{encode, Encodable, decode, Decodable}; #[derive(Clone, Copy)] @@ -30,19 +31,22 @@ pub enum CacheUpdatePolicy { /// Should be used to get database key associated with given value. pub trait Key { + type Target: Deref; + /// Returns db key. - fn key(&self) -> H264; + fn key(&self) -> Self::Target; } /// Should be used to write value into database. pub trait Writable { /// Writes the value into the database. - fn write(&self, key: &Key, value: &T) where T: Encodable; + fn write(&self, key: &Key, value: &T) where T: Encodable, R: Deref; /// Writes the value into the database and updates the cache. - fn write_with_cache(&self, cache: &mut HashMap, key: K, value: T, policy: CacheUpdatePolicy) where - K: Key + Hash + Eq, - T: Encodable { + fn write_with_cache(&self, cache: &mut HashMap, key: K, value: T, policy: CacheUpdatePolicy) where + K: Key + Hash + Eq, + T: Encodable, + R: Deref { self.write(&key, &value); match policy { CacheUpdatePolicy::Overwrite => { @@ -55,8 +59,10 @@ pub trait Writable { } /// Writes the values into the database and updates the cache. - fn extend_with_cache(&self, cache: &mut HashMap, values: HashMap, policy: CacheUpdatePolicy) - where K: Key + Hash + Eq, T: Encodable { + fn extend_with_cache(&self, cache: &mut HashMap, values: HashMap, policy: CacheUpdatePolicy) where + K: Key + Hash + Eq, + T: Encodable, + R: Deref { match policy { CacheUpdatePolicy::Overwrite => { for (key, value) in values.into_iter() { @@ -77,7 +83,9 @@ pub trait Writable { /// Should be used to read values from database. pub trait Readable { /// Returns value for given key. - fn read(&self, key: &Key) -> Option where T: Decodable; + fn read(&self, key: &Key) -> Option where + T: Decodable, + R: Deref; /// Returns value for given key either in cache or in database. fn read_with_cache(&self, cache: &RwLock>, key: &K) -> Option where @@ -98,11 +106,12 @@ pub trait Readable { } /// Returns true if given value exists. - fn exists(&self, key: &Key) -> bool; + fn exists(&self, key: &Key) -> bool where R: Deref; /// Returns true if given value exists either in cache or in database. - fn exists_with_cache(&self, cache: &RwLock>, key: &K) -> bool where - K: Eq + Hash + Key { + fn exists_with_cache(&self, cache: &RwLock>, key: &K) -> bool where + K: Eq + Hash + Key, + R: Deref { { let read = cache.read().unwrap(); if read.get(key).is_some() { @@ -110,38 +119,38 @@ pub trait Readable { } } - self.exists::(key) + self.exists::(key) } } impl Writable for DBTransaction { - fn write(&self, key: &Key, value: &T) where T: Encodable { + fn write(&self, key: &Key, value: &T) where T: Encodable, R: Deref { let result = self.put(&key.key(), &encode(value)); if let Err(err) = result { - panic!("db put failed, key: {:?}, err: {:?}", key.key(), err); + panic!("db put failed, key: {:?}, err: {:?}", &key.key() as &[u8], err); } } } impl Readable for Database { - fn read(&self, key: &Key) -> Option where T: Decodable { + fn read(&self, key: &Key) -> Option where T: Decodable, R: Deref { let result = self.get(&key.key()); match result { Ok(option) => option.map(|v| decode(&v)), Err(err) => { - panic!("db get failed, key: {:?}, err: {:?}", key.key(), err); + panic!("db get failed, key: {:?}, err: {:?}", &key.key() as &[u8], err); } } } - fn exists(&self, key: &Key) -> bool { + fn exists(&self, key: &Key) -> bool where R: Deref { let result = self.get(&key.key()); match result { Ok(v) => v.is_some(), Err(err) => { - panic!("db get failed, key: {:?}, err: {:?}", key.key(), err); + panic!("db get failed, key: {:?}, err: {:?}", &key.key() as &[u8], err); } } } diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index d7753bfca..fdfc68faf 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -21,6 +21,7 @@ use engine::*; use evm::{self, Ext}; use externalities::*; use substate::*; +use trace::{Trace, Tracer, NoopTracer, ExecutiveTracer}; use crossbeam; /// Max depth to avoid stack overflow (when it's reached we start a new thread with VM) @@ -246,22 +247,47 @@ impl<'a> Executive<'a> { } trace!("Executive::call(params={:?}) self.env_info={:?}", params, self.info); + let delegate_call = params.code_address != params.address; + if self.engine.is_builtin(¶ms.code_address) { // if destination is builtin, try to execute it let default = []; let data = if let Some(ref d) = params.data { d as &[u8] } else { &default as &[u8] }; + let trace_info = tracer.prepare_trace_call(¶ms); + let cost = self.engine.cost_of_builtin(¶ms.code_address, data); match cost <= params.gas { true => { self.engine.execute_builtin(¶ms.code_address, data, &mut output); self.state.clear_snapshot(); + + // trace only top level calls to builtins to avoid DDoS attacks + if self.depth == 0 { + let mut trace_output = tracer.prepare_trace_output(); + if let Some(mut out) = trace_output.as_mut() { + *out = output.to_owned(); + } + + tracer.trace_call( + trace_info, + cost, + trace_output, + self.depth, + vec![], + delegate_call + ); + } + Ok(params.gas - cost) }, // just drain the whole gas false => { self.state.revert_snapshot(); + + tracer.trace_failed_call(trace_info, self.depth, vec![], delegate_call); + Err(evm::Error::OutOfGas) } } @@ -269,7 +295,6 @@ impl<'a> Executive<'a> { let trace_info = tracer.prepare_trace_call(¶ms); let mut trace_output = tracer.prepare_trace_output(); let mut subtracer = tracer.subtracer(); - let delegate_call = params.code_address != params.address; let gas = params.gas; if params.code.is_some() { @@ -442,6 +467,8 @@ mod tests { use evm::{Factory, VMType}; use substate::*; use tests::helpers::*; + use trace::trace; + use trace::{Trace, Tracer, NoopTracer, ExecutiveTracer}; #[test] fn test_contract_address() { @@ -591,26 +618,26 @@ mod tests { let expected_trace = vec![ Trace { depth: 0, - action: TraceAction::Call(TraceCall { + action: trace::Action::Call(trace::Call { from: x!("cd1722f3947def4cf144679da39c4c32bdc35681"), to: x!("b010143a42d5980c7e5ef0e4a4416dc098a4fed3"), value: x!(100), gas: x!(100000), input: vec![], }), - result: TraceResult::Call(TraceCallResult { + result: trace::Res::Call(trace::CallResult { gas_used: U256::from(55_248), output: vec![], }), subs: vec![Trace { depth: 1, - action: TraceAction::Create(TraceCreate { + action: trace::Action::Create(trace::Create { from: x!("b010143a42d5980c7e5ef0e4a4416dc098a4fed3"), value: x!(23), gas: x!(67979), init: vec![96, 16, 128, 96, 12, 96, 0, 57, 96, 0, 243, 0, 96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53, 85] }), - result: TraceResult::Create(TraceCreateResult { + result: trace::Res::Create(trace::CreateResult { gas_used: U256::from(3224), address: Address::from_str("c6d80f262ae5e0f164e5fde365044d7ada2bfa34").unwrap(), code: vec![96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53] @@ -662,13 +689,13 @@ mod tests { let expected_trace = vec![Trace { depth: 0, - action: TraceAction::Create(TraceCreate { + action: trace::Action::Create(trace::Create { from: params.sender, value: x!(100), gas: params.gas, init: vec![96, 16, 128, 96, 12, 96, 0, 57, 96, 0, 243, 0, 96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53, 85], }), - result: TraceResult::Create(TraceCreateResult { + result: trace::Res::Create(trace::CreateResult { gas_used: U256::from(3224), address: params.address, code: vec![96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53] diff --git a/ethcore/src/externalities.rs b/ethcore/src/externalities.rs index 1c0bb417f..4d868cba5 100644 --- a/ethcore/src/externalities.rs +++ b/ethcore/src/externalities.rs @@ -21,6 +21,7 @@ use engine::*; use executive::*; use evm::{self, Schedule, Ext, ContractCreateResult, MessageCallResult}; use substate::*; +use trace::Tracer; /// Policy for handling output data on `RETURN` opcode. pub enum OutputPolicy<'a, 'b> { @@ -293,6 +294,7 @@ mod tests { use substate::*; use tests::helpers::*; use super::*; + use trace::{NoopTracer}; fn get_test_origin() -> OriginInfo { OriginInfo { diff --git a/ethcore/src/extras.rs b/ethcore/src/extras.rs index 49d867dcf..6b6bb13b0 100644 --- a/ethcore/src/extras.rs +++ b/ethcore/src/extras.rs @@ -85,36 +85,48 @@ impl ExtrasIndexable for BlockReceipts { } impl Key for BlockNumber { + type Target = H264; + fn key(&self) -> H264 { with_index(&H256::from(*self), ExtrasIndex::BlockHash) } } impl Key for H256 { + type Target = H264; + fn key(&self) -> H264 { with_index(self, ExtrasIndex::BlockDetails) } } impl Key for H256 { + type Target = H264; + fn key(&self) -> H264 { with_index(self, ExtrasIndex::TransactionAddress) } } impl Key for H256 { + type Target = H264; + fn key(&self) -> H264 { with_index(self, ExtrasIndex::BlockLogBlooms) } } impl Key for H256 { + type Target = H264; + fn key(&self) -> H264 { with_index(self, ExtrasIndex::BlocksBlooms) } } impl Key for H256 { + type Target = H264; + fn key(&self) -> H264 { with_index(self, ExtrasIndex::BlockReceipts) } diff --git a/ethcore/src/json_tests/executive.rs b/ethcore/src/json_tests/executive.rs index 427868807..ebffdbb15 100644 --- a/ethcore/src/json_tests/executive.rs +++ b/ethcore/src/json_tests/executive.rs @@ -24,6 +24,7 @@ use externalities::*; use substate::*; use tests::helpers::*; use ethjson; +use trace::{Tracer, NoopTracer}; #[derive(Debug, PartialEq)] struct CallCreate { diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index d267a08ce..a7b9125fc 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -84,6 +84,7 @@ extern crate env_logger; extern crate num_cpus; extern crate crossbeam; extern crate ethjson; +extern crate bloomchain; #[cfg(test)] extern crate ethcore_devtools as devtools; #[cfg(feature = "jit" )] extern crate evmjit; diff --git a/ethcore/src/state.rs b/ethcore/src/state.rs index 6a128003d..c44614550 100644 --- a/ethcore/src/state.rs +++ b/ethcore/src/state.rs @@ -18,6 +18,7 @@ use common::*; use engine::Engine; use executive::{Executive, TransactOptions}; use account_db::*; +use trace::Trace; #[cfg(test)] #[cfg(feature = "json-tests")] use pod_account::*; @@ -367,7 +368,8 @@ use env_info::*; use spec::*; use transaction::*; use util::log::init_log; -use trace::*; +use trace::trace; +use trace::trace::{Trace}; #[test] fn should_apply_create_transaction() { @@ -393,13 +395,13 @@ fn should_apply_create_transaction() { let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = Some(Trace { depth: 0, - action: TraceAction::Create(TraceCreate { + action: trace::Action::Create(trace::Create { from: x!("9cce34f7ab185c7aba1b7c8140d620b4bda941d6"), value: x!(100), gas: x!(77412), init: vec![96, 16, 128, 96, 12, 96, 0, 57, 96, 0, 243, 0, 96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53, 85], }), - result: TraceResult::Create(TraceCreateResult { + result: trace::Res::Create(trace::CreateResult { gas_used: U256::from(3224), address: Address::from_str("8988167e088c87cd314df6d3c2b83da5acb93ace").unwrap(), code: vec![96, 0, 53, 84, 21, 96, 9, 87, 0, 91, 96, 32, 53, 96, 0, 53] @@ -453,13 +455,13 @@ fn should_trace_failed_create_transaction() { let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = Some(Trace { depth: 0, - action: TraceAction::Create(TraceCreate { + action: trace::Action::Create(trace::Create { from: x!("9cce34f7ab185c7aba1b7c8140d620b4bda941d6"), value: x!(100), gas: x!(78792), init: vec![91, 96, 0, 86], }), - result: TraceResult::FailedCreate, + result: trace::Res::FailedCreate, subs: vec![] }); @@ -491,14 +493,14 @@ fn should_trace_call_transaction() { let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = Some(Trace { depth: 0, - action: TraceAction::Call(TraceCall { + action: trace::Action::Call(trace::Call { from: x!("9cce34f7ab185c7aba1b7c8140d620b4bda941d6"), to: x!(0xa), value: x!(100), gas: x!(79000), input: vec![], }), - result: TraceResult::Call(TraceCallResult { + result: trace::Res::Call(trace::CallResult { gas_used: U256::from(3), output: vec![] }), @@ -532,14 +534,14 @@ fn should_trace_basic_call_transaction() { let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = Some(Trace { depth: 0, - action: TraceAction::Call(TraceCall { + action: trace::Action::Call(trace::Call { from: x!("9cce34f7ab185c7aba1b7c8140d620b4bda941d6"), to: x!(0xa), value: x!(100), gas: x!(79000), input: vec![], }), - result: TraceResult::Call(TraceCallResult { + result: trace::Res::Call(trace::CallResult { gas_used: U256::from(0), output: vec![] }), @@ -550,7 +552,7 @@ fn should_trace_basic_call_transaction() { } #[test] -fn should_not_trace_call_transaction_to_builtin() { +fn should_trace_call_transaction_to_builtin() { init_log(); let temp = RandomTempPath::new(); @@ -571,7 +573,21 @@ fn should_not_trace_call_transaction_to_builtin() { let result = state.apply(&info, engine.deref(), &t, true).unwrap(); - assert_eq!(result.trace, None); + assert_eq!(result.trace, Some(Trace { + depth: 0, + action: trace::Action::Call(trace::Call { + from: x!("9cce34f7ab185c7aba1b7c8140d620b4bda941d6"), + to: x!("0000000000000000000000000000000000000001"), + value: x!(0), + gas: x!(79_000), + input: vec![], + }), + result: trace::Res::Call(trace::CallResult { + gas_used: U256::from(3000), + output: vec![] + }), + subs: vec![] + })); } #[test] @@ -599,14 +615,14 @@ fn should_not_trace_subcall_transaction_to_builtin() { let expected_trace = Some(Trace { depth: 0, - action: TraceAction::Call(TraceCall { + action: trace::Action::Call(trace::Call { from: x!("9cce34f7ab185c7aba1b7c8140d620b4bda941d6"), to: x!(0xa), value: x!(0), gas: x!(79000), input: vec![], }), - result: TraceResult::Call(TraceCallResult { + result: trace::Res::Call(trace::CallResult { gas_used: U256::from(28_061), output: vec![] }), @@ -641,14 +657,14 @@ fn should_not_trace_callcode() { let expected_trace = Some(Trace { depth: 0, - action: TraceAction::Call(TraceCall { + action: trace::Action::Call(trace::Call { from: x!("9cce34f7ab185c7aba1b7c8140d620b4bda941d6"), to: x!(0xa), value: x!(0), gas: x!(79000), input: vec![], }), - result: TraceResult::Call(TraceCallResult { + result: trace::Res::Call(trace::CallResult { gas_used: U256::from(64), output: vec![] }), @@ -686,14 +702,14 @@ fn should_not_trace_delegatecall() { let expected_trace = Some(Trace { depth: 0, - action: TraceAction::Call(TraceCall { + action: trace::Action::Call(trace::Call { from: x!("9cce34f7ab185c7aba1b7c8140d620b4bda941d6"), to: x!(0xa), value: x!(0), gas: x!(79000), input: vec![], }), - result: TraceResult::Call(TraceCallResult { + result: trace::Res::Call(trace::CallResult { gas_used: U256::from(61), output: vec![] }), @@ -727,14 +743,14 @@ fn should_trace_failed_call_transaction() { let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = Some(Trace { depth: 0, - action: TraceAction::Call(TraceCall { + action: trace::Action::Call(trace::Call { from: x!("9cce34f7ab185c7aba1b7c8140d620b4bda941d6"), to: x!(0xa), value: x!(100), gas: x!(79000), input: vec![], }), - result: TraceResult::FailedCall, + result: trace::Res::FailedCall, subs: vec![] }); @@ -769,27 +785,27 @@ fn should_trace_call_with_subcall_transaction() { let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = Some(Trace { depth: 0, - action: TraceAction::Call(TraceCall { + action: trace::Action::Call(trace::Call { from: x!("9cce34f7ab185c7aba1b7c8140d620b4bda941d6"), to: x!(0xa), value: x!(100), gas: x!(79000), input: vec![], }), - result: TraceResult::Call(TraceCallResult { + result: trace::Res::Call(trace::CallResult { gas_used: U256::from(69), output: vec![] }), subs: vec![Trace { depth: 1, - action: TraceAction::Call(TraceCall { + action: trace::Action::Call(trace::Call { from: x!(0xa), to: x!(0xb), value: x!(0), gas: x!(78934), input: vec![], }), - result: TraceResult::Call(TraceCallResult { + result: trace::Res::Call(trace::CallResult { gas_used: U256::from(3), output: vec![] }), @@ -825,27 +841,27 @@ fn should_trace_call_with_basic_subcall_transaction() { let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = Some(Trace { depth: 0, - action: TraceAction::Call(TraceCall { + action: trace::Action::Call(trace::Call { from: x!("9cce34f7ab185c7aba1b7c8140d620b4bda941d6"), to: x!(0xa), value: x!(100), gas: x!(79000), input: vec![], }), - result: TraceResult::Call(TraceCallResult { + result: trace::Res::Call(trace::CallResult { gas_used: U256::from(31761), output: vec![] }), subs: vec![Trace { depth: 1, - action: TraceAction::Call(TraceCall { + action: trace::Action::Call(trace::Call { from: x!(0xa), to: x!(0xb), value: x!(69), gas: x!(2300), input: vec![], }), - result: TraceResult::Call(TraceCallResult::default()), + result: trace::Res::Call(trace::CallResult::default()), subs: vec![] }] }); @@ -878,14 +894,14 @@ fn should_not_trace_call_with_invalid_basic_subcall_transaction() { let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = Some(Trace { depth: 0, - action: TraceAction::Call(TraceCall { + action: trace::Action::Call(trace::Call { from: x!("9cce34f7ab185c7aba1b7c8140d620b4bda941d6"), to: x!(0xa), value: x!(100), gas: x!(79000), input: vec![], }), - result: TraceResult::Call(TraceCallResult { + result: trace::Res::Call(trace::CallResult { gas_used: U256::from(31761), output: vec![] }), @@ -921,27 +937,27 @@ fn should_trace_failed_subcall_transaction() { let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = Some(Trace { depth: 0, - action: TraceAction::Call(TraceCall { + action: trace::Action::Call(trace::Call { from: x!("9cce34f7ab185c7aba1b7c8140d620b4bda941d6"), to: x!(0xa), value: x!(100), gas: x!(79000), input: vec![], }), - result: TraceResult::Call(TraceCallResult { + result: trace::Res::Call(trace::CallResult { gas_used: U256::from(79_000), output: vec![] }), subs: vec![Trace { depth: 1, - action: TraceAction::Call(TraceCall { + action: trace::Action::Call(trace::Call { from: x!(0xa), to: x!(0xb), value: x!(0), gas: x!(78934), input: vec![], }), - result: TraceResult::FailedCall, + result: trace::Res::FailedCall, subs: vec![] }] }); @@ -976,40 +992,40 @@ fn should_trace_call_with_subcall_with_subcall_transaction() { let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = Some(Trace { depth: 0, - action: TraceAction::Call(TraceCall { + action: trace::Action::Call(trace::Call { from: x!("9cce34f7ab185c7aba1b7c8140d620b4bda941d6"), to: x!(0xa), value: x!(100), gas: x!(79000), input: vec![], }), - result: TraceResult::Call(TraceCallResult { + result: trace::Res::Call(trace::CallResult { gas_used: U256::from(135), output: vec![] }), subs: vec![Trace { depth: 1, - action: TraceAction::Call(TraceCall { + action: trace::Action::Call(trace::Call { from: x!(0xa), to: x!(0xb), value: x!(0), gas: x!(78934), input: vec![], }), - result: TraceResult::Call(TraceCallResult { + result: trace::Res::Call(trace::CallResult { gas_used: U256::from(69), output: vec![] }), subs: vec![Trace { depth: 2, - action: TraceAction::Call(TraceCall { + action: trace::Action::Call(trace::Call { from: x!(0xb), to: x!(0xc), value: x!(0), gas: x!(78868), input: vec![], }), - result: TraceResult::Call(TraceCallResult { + result: trace::Res::Call(trace::CallResult { gas_used: U256::from(3), output: vec![] }), @@ -1048,37 +1064,37 @@ fn should_trace_failed_subcall_with_subcall_transaction() { let result = state.apply(&info, &engine, &t, true).unwrap(); let expected_trace = Some(Trace { depth: 0, - action: TraceAction::Call(TraceCall { + action: trace::Action::Call(trace::Call { from: x!("9cce34f7ab185c7aba1b7c8140d620b4bda941d6"), to: x!(0xa), value: x!(100), gas: x!(79000), input: vec![], }), - result: TraceResult::Call(TraceCallResult { + result: trace::Res::Call(trace::CallResult { gas_used: U256::from(79_000), output: vec![] }), subs: vec![Trace { depth: 1, - action: TraceAction::Call(TraceCall { + action: trace::Action::Call(trace::Call { from: x!(0xa), to: x!(0xb), value: x!(0), gas: x!(78934), input: vec![], }), - result: TraceResult::FailedCall, + result: trace::Res::FailedCall, subs: vec![Trace { depth: 2, - action: TraceAction::Call(TraceCall { + action: trace::Action::Call(trace::Call { from: x!(0xb), to: x!(0xc), value: x!(0), gas: x!(78868), input: vec![], }), - result: TraceResult::Call(TraceCallResult { + result: trace::Res::Call(trace::CallResult { gas_used: U256::from(3), output: vec![] }), diff --git a/ethcore/src/trace/block.rs b/ethcore/src/trace/block.rs new file mode 100644 index 000000000..c85073d96 --- /dev/null +++ b/ethcore/src/trace/block.rs @@ -0,0 +1,42 @@ +use util::rlp::*; +use basic_types::LogBloom; +use super::Trace; + +/// Traces created by transactions from the same block. +#[derive(Clone)] +pub struct BlockTraces(Vec); + +impl From> for BlockTraces { + fn from(traces: Vec) -> Self { + BlockTraces(traces) + } +} + +impl Into> for BlockTraces { + fn into(self) -> Vec { + self.0 + } +} + +impl Decodable for BlockTraces { + fn decode(decoder: &D) -> Result where D: Decoder { + let traces = try!(Decodable::decode(decoder)); + let block_traces = BlockTraces(traces); + Ok(block_traces) + } +} + +impl Encodable for BlockTraces { + fn rlp_append(&self, s: &mut RlpStream) { + Encodable::rlp_append(&self.0, s) + } +} + +impl BlockTraces { + /// Returns bloom of all traces in given block. + pub fn bloom(&self) -> LogBloom { + self.0.iter() + .fold(LogBloom::default(), |acc, trace| acc | trace.bloom()) + } +} + diff --git a/ethcore/src/trace/bloom.rs b/ethcore/src/trace/bloom.rs new file mode 100644 index 000000000..6f488c84b --- /dev/null +++ b/ethcore/src/trace/bloom.rs @@ -0,0 +1,106 @@ +use bloomchain::Bloom; +use bloomchain::group::{BloomGroup, GroupPosition}; +use util::rlp::*; +use basic_types::LogBloom; + +/// Helper structure representing bloom of the trace. +#[derive(Clone)] +pub struct BlockTracesBloom(LogBloom); + +impl From for BlockTracesBloom { + fn from(bloom: LogBloom) -> BlockTracesBloom { + BlockTracesBloom(bloom) + } +} + +impl From for BlockTracesBloom { + fn from(bloom: Bloom) -> BlockTracesBloom { + let bytes: [u8; 256] = bloom.into(); + BlockTracesBloom(LogBloom::from(bytes)) + } +} + +impl Into for BlockTracesBloom { + fn into(self) -> Bloom { + let log = self.0; + Bloom::from(log.0) + } +} + +/// Represents group of X consecutive blooms. +#[derive(Clone)] +pub struct BlockTracesBloomGroup { + blooms: Vec, +} + +impl From for BlockTracesBloomGroup { + fn from(group: BloomGroup) -> Self { + let blooms = group.blooms + .into_iter() + .map(From::from) + .collect(); + + BlockTracesBloomGroup { + blooms: blooms + } + } +} + +impl Into for BlockTracesBloomGroup { + fn into(self) -> BloomGroup { + let blooms = self.blooms + .into_iter() + .map(Into::into) + .collect(); + + BloomGroup { + blooms: blooms + } + } +} + +impl Decodable for BlockTracesBloom { + fn decode(decoder: &D) -> Result where D: Decoder { + Decodable::decode(decoder).map(BlockTracesBloom) + } +} + +impl Encodable for BlockTracesBloom { + fn rlp_append(&self, s: &mut RlpStream) { + Encodable::rlp_append(&self.0, s) + } +} + +impl Decodable for BlockTracesBloomGroup { + fn decode(decoder: &D) -> Result where D: Decoder { + let blooms = try!(Decodable::decode(decoder)); + let group = BlockTracesBloomGroup { + blooms: blooms + }; + Ok(group) + } +} + +impl Encodable for BlockTracesBloomGroup { + fn rlp_append(&self, s: &mut RlpStream) { + Encodable::rlp_append(&self.blooms, s) + } +} + +/// Represents BloomGroup position in database. +#[derive(PartialEq, Eq, Hash, Clone, Debug)] +pub struct TraceGroupPosition { + /// Bloom level. + pub level: u8, + /// Group index. + pub index: u32, +} + +impl From for TraceGroupPosition { + fn from(p: GroupPosition) -> Self { + TraceGroupPosition { + level: p.level as u8, + index: p.index as u32, + } + } +} diff --git a/ethcore/src/trace/config.rs b/ethcore/src/trace/config.rs new file mode 100644 index 000000000..3390fb2aa --- /dev/null +++ b/ethcore/src/trace/config.rs @@ -0,0 +1,62 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Traces config. +use bloomchain::Config as BloomConfig; + +/// 3-value enum. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Switch { + /// True. + On, + /// False. + Off, + /// Auto. + Auto, +} + +impl Switch { + /// Tries to turn old switch to new value. + pub fn turn_to(&self, to: Switch) -> Result { + match (*self, to) { + (Switch::On, Switch::On) | (Switch::On, Switch::Auto) | (Switch::Auto, Switch::On) => Ok(true), + (Switch::Off, Switch::On) => Err("Tracing can't be enabled"), + _ => Ok(false), + } + } +} + +/// Traces config. +#[derive(Debug, Clone)] +pub struct Config { + /// Indicates if tracing should be enabled or not. + /// If it's None, it will be automatically configured. + pub enabled: Switch, + /// Traces blooms configuration. + pub blooms: BloomConfig, +} + +impl Default for Config { + fn default() -> Self { + Config { + enabled: Switch::Auto, + blooms: BloomConfig { + levels: 3, + elements_per_index: 16, + } + } + } +} diff --git a/ethcore/src/trace/db.rs b/ethcore/src/trace/db.rs new file mode 100644 index 000000000..0d2468859 --- /dev/null +++ b/ethcore/src/trace/db.rs @@ -0,0 +1,582 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Trace database. +use std::ptr; +use std::ops::Deref; +use std::collections::HashMap; +use std::sync::{RwLock, Arc}; +use std::path::Path; +use bloomchain::{Number, Config as BloomConfig}; +use bloomchain::group::{BloomGroupDatabase, BloomGroupChain, GroupPosition, BloomGroup}; +use util::{FixedHash, H256, H264, Database, DBTransaction}; +use header::BlockNumber; +use trace::{BlockTraces, LocalizedTrace, Config, Switch, Filter, Database as TraceDatabase, ImportRequest, +DatabaseExtras}; +use db::{Key, Writable, Readable, CacheUpdatePolicy}; +use super::bloom::{TraceGroupPosition, BlockTracesBloom, BlockTracesBloomGroup}; +use super::flat::{FlatTrace, FlatBlockTraces, FlatTransactionTraces}; + +const TRACE_DB_VER: &'static [u8] = b"1.0"; + +#[derive(Debug, Copy, Clone)] +enum TraceDBIndex { + /// Block traces index. + BlockTraces = 0, + /// Trace bloom group index. + BlockTracesBloomGroups = 1, +} + +impl Key for H256 { + type Target = H264; + + fn key(&self) -> H264 { + let mut result = H264::default(); + result[0] = TraceDBIndex::BlockTraces as u8; + unsafe { + ptr::copy(self.as_ptr(), result.as_mut_ptr().offset(1), 32); + } + result + } +} + +/// Helper data structure created cause [u8; 6] does not implement Deref to &[u8]. +pub struct TraceGroupKey([u8; 6]); + +impl Deref for TraceGroupKey { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Key for TraceGroupPosition { + type Target = TraceGroupKey; + + fn key(&self) -> Self::Target { + let mut result = [0u8; 6]; + result[0] = TraceDBIndex::BlockTracesBloomGroups as u8; + result[1] = self.level; + unsafe { + ptr::copy(&[self.index] as *const u32 as *const u8, result.as_mut_ptr().offset(2), 4); + } + TraceGroupKey(result) + } +} + +/// Trace database. +pub struct TraceDB where T: DatabaseExtras { + // cache + traces: RwLock>, + blooms: RwLock>, + // db + tracesdb: Database, + // config, + bloom_config: BloomConfig, + // tracing enabled + enabled: bool, + // extras + extras: Arc, +} + +impl BloomGroupDatabase for TraceDB where T: DatabaseExtras { + fn blooms_at(&self, position: &GroupPosition) -> Option { + let position = TraceGroupPosition::from(position.clone()); + self.tracesdb.read_with_cache(&self.blooms, &position).map(Into::into) + } +} + +impl TraceDB where T: DatabaseExtras { + /// Creates new instance of `TraceDB`. + pub fn new(config: Config, path: &Path, extras: Arc) -> Self { + let mut tracedb_path = path.to_path_buf(); + tracedb_path.push("tracedb"); + let tracesdb = Database::open_default(tracedb_path.to_str().unwrap()).unwrap(); + + // check if in previously tracing was enabled + let old_tracing = match tracesdb.get(b"enabled").unwrap() { + Some(ref value) if value as &[u8] == &[0x1] => Switch::On, + Some(ref value) if value as &[u8] == &[0x0] => Switch::Off, + Some(_) => { panic!("tracesdb is corrupted") }, + None => Switch::Auto, + }; + + let enabled = old_tracing.turn_to(config.enabled).expect("Tracing can't be enabled. Resync required."); + + let encoded_tracing = match enabled { + true => [0x1], + false => [0x0] + }; + + tracesdb.put(b"enabled", &encoded_tracing).unwrap(); + tracesdb.put(b"version", TRACE_DB_VER).unwrap(); + + TraceDB { + traces: RwLock::new(HashMap::new()), + blooms: RwLock::new(HashMap::new()), + tracesdb: tracesdb, + bloom_config: config.blooms, + enabled: enabled, + extras: extras, + } + } + + /// Returns traces for block with hash. + fn traces(&self, block_hash: &H256) -> Option { + self.tracesdb.read_with_cache(&self.traces, block_hash) + } + + /// Returns vector of transaction traces for given block. + fn transactions_traces(&self, block_hash: &H256) -> Option> { + self.traces(block_hash) + .map(FlatBlockTraces::from) + .map(Into::into) + } + + fn matching_block_traces( + &self, + filter: &Filter, + traces: FlatBlockTraces, + block_hash: H256, + block_number: BlockNumber + ) -> Vec { + let tx_traces: Vec = traces.into(); + tx_traces.into_iter() + .enumerate() + .flat_map(|(tx_number, tx_trace)| { + self.matching_transaction_traces(filter, tx_trace, block_hash.clone(), block_number, tx_number) + }) + .collect() + } + + fn matching_transaction_traces( + &self, + filter: &Filter, + traces: FlatTransactionTraces, + block_hash: H256, + block_number: BlockNumber, + tx_number: usize + ) -> Vec { + let tx_hash = self.extras.transaction_hash(block_number, tx_number) + .expect("Expected to find transaction hash. Database is probably corrupted"); + + let flat_traces: Vec = traces.into(); + flat_traces.into_iter() + .filter_map(|trace| { + match filter.matches(&trace) { + true => Some(LocalizedTrace { + action: trace.action, + result: trace.result, + subtraces: trace.subtraces, + trace_address: trace.trace_address, + transaction_number: tx_number, + transaction_hash: tx_hash.clone(), + block_number: block_number, + block_hash: block_hash + }), + false => None + } + }) + .collect() + } +} + +impl TraceDatabase for TraceDB where T: DatabaseExtras { + fn tracing_enabled(&self) -> bool { + self.enabled + } + + /// Traces of import request's enacted blocks are expected to be already in database + /// or to be the currently inserted trace. + fn import(&self, request: ImportRequest) { + // fast return if tracing is disabled + if !self.tracing_enabled() { + return; + } + + let batch = DBTransaction::new(); + + // at first, let's insert new block traces + { + let mut traces = self.traces.write().unwrap(); + // it's important to use overwrite here, + // cause this value might be queried by hash later + batch.write_with_cache(&mut traces, request.block_hash, request.traces, CacheUpdatePolicy::Overwrite); + } + + // now let's rebuild the blooms + { + let range_start = request.block_number as Number + 1 - request.enacted.len(); + let range_end = range_start + request.retracted; + let replaced_range = range_start..range_end; + let enacted_blooms = request.enacted + .iter() + // all traces are expected to be found here. That's why `expect` has been used + // instead of `filter_map`. If some traces haven't been found, it meens that + // traces database is corrupted or incomplete. + .map(|block_hash| self.traces(block_hash).expect("Traces database is incomplete.")) + .map(|block_traces| block_traces.bloom()) + .map(BlockTracesBloom::from) + .map(Into::into) + .collect(); + + let chain = BloomGroupChain::new(self.bloom_config, self); + let trace_blooms = chain.replace(&replaced_range, enacted_blooms); + let blooms_to_insert = trace_blooms.into_iter() + .map(|p| (From::from(p.0), From::from(p.1))) + .collect::>(); + + let mut blooms = self.blooms.write().unwrap(); + batch.extend_with_cache(&mut blooms, blooms_to_insert, CacheUpdatePolicy::Remove); + } + + self.tracesdb.write(batch).unwrap(); + } + + fn trace(&self, block_number: BlockNumber, tx_position: usize, trace_position: Vec) -> Option { + self.extras.block_hash(block_number) + .and_then(|block_hash| self.transactions_traces(&block_hash) + .and_then(|traces| traces.into_iter().nth(tx_position)) + .map(Into::>::into) + // this may and should be optimized + .and_then(|traces| traces.into_iter().find(|trace| trace.trace_address == trace_position)) + .map(|trace| { + let tx_hash = self.extras.transaction_hash(block_number, tx_position) + .expect("Expected to find transaction hash. Database is probably corrupted"); + + LocalizedTrace { + action: trace.action, + result: trace.result, + subtraces: trace.subtraces, + trace_address: trace.trace_address, + transaction_number: tx_position, + transaction_hash: tx_hash, + block_number: block_number, + block_hash: block_hash, + } + }) + ) + } + + fn transaction_traces(&self, block_number: BlockNumber, tx_position: usize) -> Option> { + self.extras.block_hash(block_number) + .and_then(|block_hash| self.transactions_traces(&block_hash) + .and_then(|traces| traces.into_iter().nth(tx_position)) + .map(Into::>::into) + .map(|traces| { + let tx_hash = self.extras.transaction_hash(block_number, tx_position) + .expect("Expected to find transaction hash. Database is probably corrupted"); + + traces.into_iter() + .map(|trace| LocalizedTrace { + action: trace.action, + result: trace.result, + subtraces: trace.subtraces, + trace_address: trace.trace_address, + transaction_number: tx_position, + transaction_hash: tx_hash.clone(), + block_number: block_number, + block_hash: block_hash + }) + .collect() + }) + ) + } + + fn block_traces(&self, block_number: BlockNumber) -> Option> { + self.extras.block_hash(block_number) + .and_then(|block_hash| self.transactions_traces(&block_hash) + .map(|traces| { + traces.into_iter() + .map(Into::>::into) + .enumerate() + .flat_map(|(tx_position, traces)| { + let tx_hash = self.extras.transaction_hash(block_number, tx_position) + .expect("Expected to find transaction hash. Database is probably corrupted"); + + traces.into_iter() + .map(|trace| LocalizedTrace { + action: trace.action, + result: trace.result, + subtraces: trace.subtraces, + trace_address: trace.trace_address, + transaction_number: tx_position, + transaction_hash: tx_hash.clone(), + block_number: block_number, + block_hash: block_hash, + }) + .collect::>() + }) + .collect::>() + }) + ) + } + + fn filter(&self, filter: &Filter) -> Vec { + let chain = BloomGroupChain::new(self.bloom_config, self); + let numbers = chain.filter(filter); + numbers.into_iter() + .flat_map(|n| { + let number = n as BlockNumber; + let hash = self.extras.block_hash(number) + .expect("Expected to find block hash. Extras db is probably corrupted"); + let traces = self.traces(&hash) + .expect("Expected to find a trace. Db is probably corrupted."); + let flat_block = FlatBlockTraces::from(traces); + self.matching_block_traces(filter, flat_block, hash, number) + }) + .collect() + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + use std::sync::Arc; + use util::{Address, U256, H256}; + use devtools::RandomTempPath; + use header::BlockNumber; + use trace::{Config, Switch, TraceDB, Database, DatabaseExtras, ImportRequest}; + use trace::{BlockTraces, Trace, Filter, LocalizedTrace, AddressesFilter}; + use trace::trace::{Call, Action, Res}; + + struct NoopExtras; + + impl DatabaseExtras for NoopExtras { + fn block_hash(&self, _block_number: BlockNumber) -> Option { + unimplemented!(); + } + + fn transaction_hash(&self, _block_number: BlockNumber, _tx_position: usize) -> Option { + unimplemented!(); + } + } + + struct Extras { + block_hashes: HashMap, + transaction_hashes: HashMap>, + } + + impl Default for Extras { + fn default() -> Self { + Extras { + block_hashes: HashMap::new(), + transaction_hashes: HashMap::new(), + } + } + } + + impl DatabaseExtras for Extras { + fn block_hash(&self, block_number: BlockNumber) -> Option { + self.block_hashes.get(&block_number).cloned() + } + + fn transaction_hash(&self, block_number: BlockNumber, tx_position: usize) -> Option { + self.transaction_hashes.get(&block_number) + .and_then(|hashes| hashes.iter().cloned().nth(tx_position)) + } + } + + #[test] + fn test_reopening_db_with_tracing_off() { + let temp = RandomTempPath::new(); + let mut config = Config::default(); + + // set autotracing + config.enabled = Switch::Auto; + + { + let tracedb = TraceDB::new(config.clone(), temp.as_path(), Arc::new(NoopExtras)); + assert_eq!(tracedb.tracing_enabled(), false); + } + + { + let tracedb = TraceDB::new(config.clone(), temp.as_path(), Arc::new(NoopExtras)); + assert_eq!(tracedb.tracing_enabled(), false); + } + + config.enabled = Switch::Off; + + { + let tracedb = TraceDB::new(config.clone(), temp.as_path(), Arc::new(NoopExtras)); + assert_eq!(tracedb.tracing_enabled(), false); + } + } + + #[test] + fn test_reopening_db_with_tracing_on() { + let temp = RandomTempPath::new(); + let mut config = Config::default(); + + // set tracing on + config.enabled = Switch::On; + + { + let tracedb = TraceDB::new(config.clone(), temp.as_path(), Arc::new(NoopExtras)); + assert_eq!(tracedb.tracing_enabled(), true); + } + + { + let tracedb = TraceDB::new(config.clone(), temp.as_path(), Arc::new(NoopExtras)); + assert_eq!(tracedb.tracing_enabled(), true); + } + + config.enabled = Switch::Auto; + + { + let tracedb = TraceDB::new(config.clone(), temp.as_path(), Arc::new(NoopExtras)); + assert_eq!(tracedb.tracing_enabled(), true); + } + + config.enabled = Switch::Off; + + { + let tracedb = TraceDB::new(config.clone(), temp.as_path(), Arc::new(NoopExtras)); + assert_eq!(tracedb.tracing_enabled(), false); + } + } + + #[test] + #[should_panic] + fn test_invalid_reopening_db() { + let temp = RandomTempPath::new(); + let mut config = Config::default(); + + // set tracing on + config.enabled = Switch::Off; + + { + let tracedb = TraceDB::new(config.clone(), temp.as_path(), Arc::new(NoopExtras)); + assert_eq!(tracedb.tracing_enabled(), true); + } + + config.enabled = Switch::On; + TraceDB::new(config.clone(), temp.as_path(), Arc::new(NoopExtras)); // should panic! + } + + fn create_simple_import_request(block_number: BlockNumber, block_hash: H256) -> ImportRequest { + ImportRequest { + traces: BlockTraces::from(vec![Trace { + depth: 0, + action: Action::Call(Call { + from: Address::from(1), + to: Address::from(2), + value: U256::from(3), + gas: U256::from(4), + input: vec![], + }), + result: Res::FailedCall, + subs: vec![], + }]), + block_hash: block_hash.clone(), + block_number: block_number, + enacted: vec![block_hash], + retracted: 0, + } + } + + fn create_simple_localized_trace(block_number: BlockNumber, block_hash: H256, tx_hash: H256) -> LocalizedTrace { + LocalizedTrace { + action: Action::Call(Call { + from: Address::from(1), + to: Address::from(2), + value: U256::from(3), + gas: U256::from(4), + input: vec![], + }), + result: Res::FailedCall, + trace_address: vec![], + subtraces: 0, + transaction_number: 0, + transaction_hash: tx_hash, + block_number: block_number, + block_hash: block_hash, + } + } + + + #[test] + fn test_import() { + let temp = RandomTempPath::new(); + let mut config = Config::default(); + config.enabled = Switch::On; + let block_0 = H256::from(0xa1); + let block_1 = H256::from(0xa2); + let tx_0 = H256::from(0xff); + let tx_1 = H256::from(0xaf); + + let mut extras = Extras::default(); + extras.block_hashes.insert(0, block_0.clone()); + extras.block_hashes.insert(1, block_1.clone()); + extras.transaction_hashes.insert(0, vec![tx_0.clone()]); + extras.transaction_hashes.insert(1, vec![tx_1.clone()]); + + let tracedb = TraceDB::new(config, temp.as_path(), Arc::new(extras)); + + // import block 0 + let request = create_simple_import_request(0, block_0.clone()); + tracedb.import(request); + + let filter = Filter { + range: (0..0), + from_address: AddressesFilter::from(vec![Address::from(1)]), + to_address: AddressesFilter::from(vec![]), + }; + + let traces = tracedb.filter(&filter); + assert_eq!(traces.len(), 1); + assert_eq!(traces[0], create_simple_localized_trace(0, block_0.clone(), tx_0.clone())); + + // import block 1 + let request = create_simple_import_request(1, block_1.clone()); + tracedb.import(request); + + let filter = Filter { + range: (0..1), + from_address: AddressesFilter::from(vec![Address::from(1)]), + to_address: AddressesFilter::from(vec![]), + }; + + let traces = tracedb.filter(&filter); + assert_eq!(traces.len(), 2); + assert_eq!(traces[0], create_simple_localized_trace(0, block_0.clone(), tx_0.clone())); + assert_eq!(traces[1], create_simple_localized_trace(1, block_1.clone(), tx_1.clone())); + + let traces = tracedb.block_traces(0).unwrap(); + assert_eq!(traces.len(), 1); + assert_eq!(traces[0], create_simple_localized_trace(0, block_0.clone(), tx_0.clone())); + + let traces = tracedb.block_traces(1).unwrap(); + assert_eq!(traces.len(), 1); + assert_eq!(traces[0], create_simple_localized_trace(1, block_1.clone(), tx_1.clone())); + + assert_eq!(None, tracedb.block_traces(2)); + + let traces = tracedb.transaction_traces(0, 0).unwrap(); + assert_eq!(traces.len(), 1); + assert_eq!(traces[0], create_simple_localized_trace(0, block_0.clone(), tx_0.clone())); + + let traces = tracedb.transaction_traces(1, 0).unwrap(); + assert_eq!(traces.len(), 1); + assert_eq!(traces[0], create_simple_localized_trace(1, block_1.clone(), tx_1.clone())); + + assert_eq!(None, tracedb.transaction_traces(1, 1)); + + assert_eq!(tracedb.trace(0, 0, vec![]).unwrap(), create_simple_localized_trace(0, block_0.clone(), tx_0.clone())); + assert_eq!(tracedb.trace(1, 0, vec![]).unwrap(), create_simple_localized_trace(1, block_1.clone(), tx_1.clone())); + } +} diff --git a/ethcore/src/trace/executive_tracer.rs b/ethcore/src/trace/executive_tracer.rs new file mode 100644 index 000000000..a0bff1c72 --- /dev/null +++ b/ethcore/src/trace/executive_tracer.rs @@ -0,0 +1,108 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Simple executive tracer. + +use util::{Bytes, Address, U256}; +use action_params::ActionParams; +use trace::trace::{Trace, Call, Create, Action, Res, CreateResult, CallResult}; +use trace::Tracer; + +/// Simple executive tracer. Traces all calls and creates. Ignores delegatecalls. +#[derive(Default)] +pub struct ExecutiveTracer { + traces: Vec +} + +impl Tracer for ExecutiveTracer { + fn prepare_trace_call(&self, params: &ActionParams) -> Option { + Some(Call::from(params.clone())) + } + + fn prepare_trace_create(&self, params: &ActionParams) -> Option { + Some(Create::from(params.clone())) + } + + fn prepare_trace_output(&self) -> Option { + Some(vec![]) + } + + fn trace_call(&mut self, call: Option, gas_used: U256, output: Option, depth: usize, subs: + Vec, delegate_call: bool) { + // don't trace if it's DELEGATECALL or CALLCODE. + if delegate_call { + return; + } + + let trace = Trace { + depth: depth, + subs: subs, + action: Action::Call(call.expect("self.prepare_trace_call().is_some(): so we must be tracing: qed")), + result: Res::Call(CallResult { + gas_used: gas_used, + output: output.expect("self.prepare_trace_output().is_some(): so we must be tracing: qed") + }) + }; + self.traces.push(trace); + } + + fn trace_create(&mut self, create: Option, gas_used: U256, code: Option, address: Address, depth: usize, subs: Vec) { + let trace = Trace { + depth: depth, + subs: subs, + action: Action::Create(create.expect("self.prepare_trace_create().is_some(): so we must be tracing: qed")), + result: Res::Create(CreateResult { + gas_used: gas_used, + code: code.expect("self.prepare_trace_output.is_some(): so we must be tracing: qed"), + address: address + }) + }; + self.traces.push(trace); + } + + fn trace_failed_call(&mut self, call: Option, depth: usize, subs: Vec, delegate_call: bool) { + // don't trace if it's DELEGATECALL or CALLCODE. + if delegate_call { + return; + } + + let trace = Trace { + depth: depth, + subs: subs, + action: Action::Call(call.expect("self.prepare_trace_call().is_some(): so we must be tracing: qed")), + result: Res::FailedCall, + }; + self.traces.push(trace); + } + + fn trace_failed_create(&mut self, create: Option, depth: usize, subs: Vec) { + let trace = Trace { + depth: depth, + subs: subs, + action: Action::Create(create.expect("self.prepare_trace_create().is_some(): so we must be tracing: qed")), + result: Res::FailedCreate, + }; + self.traces.push(trace); + } + + fn subtracer(&self) -> Self { + ExecutiveTracer::default() + } + + fn traces(self) -> Vec { + self.traces + } +} diff --git a/ethcore/src/trace/filter.rs b/ethcore/src/trace/filter.rs new file mode 100644 index 000000000..d1f699c52 --- /dev/null +++ b/ethcore/src/trace/filter.rs @@ -0,0 +1,283 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use std::ops::Range; +use bloomchain::{Filter as BloomFilter, Bloom, Number}; +use util::{Address, FixedHash}; +use util::sha3::Hashable; +use basic_types::LogBloom; +use super::flat::FlatTrace; +use super::trace::Action; + +/// Addresses filter. +/// +/// Used to create bloom possibilities and match filters. +pub struct AddressesFilter(Vec
); + +impl From> for AddressesFilter { + fn from(addresses: Vec
) -> Self { + AddressesFilter(addresses) + } +} + +impl AddressesFilter { + /// Returns true if address matches one of the searched addresses. + pub fn matches(&self, address: &Address) -> bool { + self.matches_all() || self.0.contains(address) + } + + /// Returns true if this address filter matches everything. + pub fn matches_all(&self) -> bool { + self.0.is_empty() + } + + /// Returns blooms of this addresses filter. + pub fn blooms(&self) -> Vec { + match self.0.is_empty() { + true => vec![LogBloom::new()], + false => self.0.iter() + .map(|address| LogBloom::from_bloomed(&address.sha3())) + .collect() + } + } + + /// Returns vector of blooms zipped with blooms of this addresses filter. + pub fn with_blooms(&self, blooms: Vec) -> Vec { + match self.0.is_empty() { + true => blooms, + false => blooms + .into_iter() + .flat_map(|bloom| self.0.iter() + .map(|address| bloom.with_bloomed(&address.sha3())) + .collect::>()) + .collect() + } + } +} + +/// Traces filter. +pub struct Filter { + /// Block range. + pub range: Range, + + /// From address filter. + pub from_address: AddressesFilter, + + /// To address filter. + pub to_address: AddressesFilter, +} + +impl BloomFilter for Filter { + fn bloom_possibilities(&self) -> Vec { + self.bloom_possibilities() + .into_iter() + .map(|b| Bloom::from(b.0)) + .collect() + } + + fn range(&self) -> Range { + self.range.clone() + } +} + +impl Filter { + /// Returns combinations of each address. + fn bloom_possibilities(&self) -> Vec { + self.to_address.with_blooms(self.from_address.blooms()) + } + + /// Returns true if given trace matches the filter. + pub fn matches(&self, trace: &FlatTrace) -> bool { + match trace.action { + Action::Call(ref call) => { + let from_matches = self.from_address.matches(&call.from); + let to_matches = self.to_address.matches(&call.to); + from_matches && to_matches + }, + Action::Create(ref create) => { + let from_matches = self.from_address.matches(&create.from); + let to_matches = self.to_address.matches_all(); + from_matches && to_matches + } + } + } +} + +#[cfg(test)] +mod tests { + use util::{FixedHash, Address, U256}; + use util::sha3::Hashable; + use trace::trace::{Action, Call, Res}; + use trace::flat::FlatTrace; + use trace::{Filter, AddressesFilter}; + use basic_types::LogBloom; + + #[test] + fn empty_trace_filter_bloom_possibilities() { + let filter = Filter { + range: (0..0), + from_address: AddressesFilter::from(vec![]), + to_address: AddressesFilter::from(vec![]), + }; + + let blooms = filter.bloom_possibilities(); + assert_eq!(blooms, vec![LogBloom::new()]); + } + + #[test] + fn single_trace_filter_bloom_possibility() { + let filter = Filter { + range: (0..0), + from_address: AddressesFilter::from(vec![Address::from(1)]), + to_address: AddressesFilter::from(vec![Address::from(2)]), + }; + + let blooms = filter.bloom_possibilities(); + assert_eq!(blooms.len(), 1); + + assert!(blooms[0].contains_bloomed(&Address::from(1).sha3())); + assert!(blooms[0].contains_bloomed(&Address::from(2).sha3())); + assert!(!blooms[0].contains_bloomed(&Address::from(3).sha3())); + } + + #[test] + fn only_from_trace_filter_bloom_possibility() { + let filter = Filter { + range: (0..0), + from_address: AddressesFilter::from(vec![Address::from(1)]), + to_address: AddressesFilter::from(vec![]), + }; + + let blooms = filter.bloom_possibilities(); + assert_eq!(blooms.len(), 1); + + assert!(blooms[0].contains_bloomed(&Address::from(1).sha3())); + assert!(!blooms[0].contains_bloomed(&Address::from(2).sha3())); + } + + #[test] + fn only_to_trace_filter_bloom_possibility() { + let filter = Filter { + range: (0..0), + from_address: AddressesFilter::from(vec![]), + to_address: AddressesFilter::from(vec![Address::from(1)]), + }; + + let blooms = filter.bloom_possibilities(); + assert_eq!(blooms.len(), 1); + + assert!(blooms[0].contains_bloomed(&Address::from(1).sha3())); + assert!(!blooms[0].contains_bloomed(&Address::from(2).sha3())); + } + + #[test] + fn multiple_trace_filter_bloom_possibility() { + let filter = Filter { + range: (0..0), + from_address: AddressesFilter::from(vec![Address::from(1), Address::from(3)]), + to_address: AddressesFilter::from(vec![Address::from(2), Address::from(4)]), + }; + + let blooms = filter.bloom_possibilities(); + assert_eq!(blooms.len(), 4); + + assert!(blooms[0].contains_bloomed(&Address::from(1).sha3())); + assert!(blooms[0].contains_bloomed(&Address::from(2).sha3())); + assert!(!blooms[0].contains_bloomed(&Address::from(3).sha3())); + assert!(!blooms[0].contains_bloomed(&Address::from(4).sha3())); + + assert!(blooms[1].contains_bloomed(&Address::from(1).sha3())); + assert!(blooms[1].contains_bloomed(&Address::from(4).sha3())); + assert!(!blooms[1].contains_bloomed(&Address::from(2).sha3())); + assert!(!blooms[1].contains_bloomed(&Address::from(3).sha3())); + + assert!(blooms[2].contains_bloomed(&Address::from(2).sha3())); + assert!(blooms[2].contains_bloomed(&Address::from(3).sha3())); + assert!(!blooms[2].contains_bloomed(&Address::from(1).sha3())); + assert!(!blooms[2].contains_bloomed(&Address::from(4).sha3())); + + assert!(blooms[3].contains_bloomed(&Address::from(3).sha3())); + assert!(blooms[3].contains_bloomed(&Address::from(4).sha3())); + assert!(!blooms[3].contains_bloomed(&Address::from(1).sha3())); + assert!(!blooms[3].contains_bloomed(&Address::from(2).sha3())); + } + + #[test] + fn filter_matches() { + let f0 = Filter { + range: (0..0), + from_address: AddressesFilter::from(vec![Address::from(1)]), + to_address: AddressesFilter::from(vec![]), + }; + + let f1 = Filter { + range: (0..0), + from_address: AddressesFilter::from(vec![Address::from(3), Address::from(1)]), + to_address: AddressesFilter::from(vec![]), + }; + + let f2 = Filter { + range: (0..0), + from_address: AddressesFilter::from(vec![]), + to_address: AddressesFilter::from(vec![]), + }; + + let f3 = Filter { + range: (0..0), + from_address: AddressesFilter::from(vec![]), + to_address: AddressesFilter::from(vec![Address::from(2)]), + }; + + let f4 = Filter { + range: (0..0), + from_address: AddressesFilter::from(vec![]), + to_address: AddressesFilter::from(vec![Address::from(2), Address::from(3)]), + }; + + let f5 = Filter { + range: (0..0), + from_address: AddressesFilter::from(vec![Address::from(1)]), + to_address: AddressesFilter::from(vec![Address::from(2), Address::from(3)]), + }; + + let f6 = Filter { + range: (0..0), + from_address: AddressesFilter::from(vec![Address::from(1)]), + to_address: AddressesFilter::from(vec![Address::from(4)]), + }; + + let trace = FlatTrace { + action: Action::Call(Call { + from: Address::from(1), + to: Address::from(2), + value: U256::from(3), + gas: U256::from(4), + input: vec![0x5], + }), + result: Res::FailedCall, + trace_address: vec![0], + subtraces: 0, + }; + + assert!(f0.matches(&trace)); + assert!(f1.matches(&trace)); + assert!(f2.matches(&trace)); + assert!(f3.matches(&trace)); + assert!(f4.matches(&trace)); + assert!(f5.matches(&trace)); + assert!(!f6.matches(&trace)); + } +} diff --git a/ethcore/src/trace/flat.rs b/ethcore/src/trace/flat.rs new file mode 100644 index 000000000..ae3f22050 --- /dev/null +++ b/ethcore/src/trace/flat.rs @@ -0,0 +1,181 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use trace::BlockTraces; +use super::trace::{Trace, Action, Res}; + +/// Trace localized in vector of traces produced by a single transaction. +/// +/// Parent and children indexes refer to positions in this vector. +pub struct FlatTrace { + /// Type of action performed by a transaction. + pub action: Action, + /// Result of this action. + pub result: Res, + /// Number of subtraces. + pub subtraces: usize, + /// Exact location of trace. + /// + /// [index in root, index in first CALL, index in second CALL, ...] + pub trace_address: Vec, +} + +/// Represents all traces produced by a single transaction. +pub struct FlatTransactionTraces(Vec); + +impl Into> for FlatTransactionTraces { + fn into(self) -> Vec { + self.0 + } +} + +/// Represents all traces produced by transactions in a single block. +pub struct FlatBlockTraces(Vec); + +impl From for FlatBlockTraces { + fn from(block_traces: BlockTraces) -> Self { + let traces: Vec = block_traces.into(); + let ordered = traces.into_iter() + .map(|trace| FlatBlockTraces::flatten(vec![], trace)) + .map(FlatTransactionTraces) + .collect(); + FlatBlockTraces(ordered) + } +} + +impl Into> for FlatBlockTraces { + fn into(self) -> Vec { + self.0 + } +} + +impl FlatBlockTraces { + /// Helper function flattening nested tree structure to vector of ordered traces. + fn flatten(address: Vec, trace: Trace) -> Vec { + let subtraces = trace.subs.len(); + let all_subs = trace.subs + .into_iter() + .enumerate() + .flat_map(|(index, subtrace)| { + let mut subtrace_address = address.clone(); + subtrace_address.push(index); + FlatBlockTraces::flatten(subtrace_address, subtrace) + }) + .collect::>(); + + let ordered = FlatTrace { + action: trace.action, + result: trace.result, + subtraces: subtraces, + trace_address: address, + }; + + let mut result = vec![ordered]; + result.extend(all_subs); + result + } +} + +#[cfg(test)] +mod tests { + use super::{FlatBlockTraces, FlatTransactionTraces, FlatTrace}; + use util::{U256, Address}; + use trace::trace::{Action, Res, CallResult, Call, Create, Trace}; + use trace::BlockTraces; + + #[test] + fn test_block_from() { + let trace = Trace { + depth: 2, + action: Action::Call(Call { + from: Address::from(1), + to: Address::from(2), + value: U256::from(3), + gas: U256::from(4), + input: vec![0x5] + }), + subs: vec![ + Trace { + depth: 3, + action: Action::Create(Create { + from: Address::from(6), + value: U256::from(7), + gas: U256::from(8), + init: vec![0x9] + }), + subs: vec![ + Trace { + depth: 3, + action: Action::Create(Create { + from: Address::from(6), + value: U256::from(7), + gas: U256::from(8), + init: vec![0x9] + }), + subs: vec![ + ], + result: Res::FailedCreate + }, + Trace { + depth: 3, + action: Action::Create(Create { + from: Address::from(6), + value: U256::from(7), + gas: U256::from(8), + init: vec![0x9] + }), + subs: vec![ + ], + result: Res::FailedCreate + } + ], + result: Res::FailedCreate + }, + Trace { + depth: 3, + action: Action::Create(Create { + from: Address::from(6), + value: U256::from(7), + gas: U256::from(8), + init: vec![0x9] + }), + subs: vec![], + result: Res::FailedCreate, + } + ], + result: Res::Call(CallResult { + gas_used: U256::from(10), + output: vec![0x11, 0x12] + }) + }; + + let block_traces = FlatBlockTraces::from(BlockTraces::from(vec![trace])); + let transaction_traces: Vec = block_traces.into(); + assert_eq!(transaction_traces.len(), 1); + let ordered_traces: Vec = transaction_traces.into_iter().nth(0).unwrap().into(); + assert_eq!(ordered_traces.len(), 5); + assert_eq!(ordered_traces[0].trace_address, vec![]); + assert_eq!(ordered_traces[0].subtraces, 2); + assert_eq!(ordered_traces[1].trace_address, vec![0]); + assert_eq!(ordered_traces[1].subtraces, 2); + assert_eq!(ordered_traces[2].trace_address, vec![0, 0]); + assert_eq!(ordered_traces[2].subtraces, 0); + assert_eq!(ordered_traces[3].trace_address, vec![0, 1]); + assert_eq!(ordered_traces[3].subtraces, 0); + assert_eq!(ordered_traces[4].trace_address, vec![1]); + assert_eq!(ordered_traces[4].subtraces, 0); + } +} diff --git a/ethcore/src/trace/import.rs b/ethcore/src/trace/import.rs new file mode 100644 index 000000000..a6b4a29bb --- /dev/null +++ b/ethcore/src/trace/import.rs @@ -0,0 +1,36 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Traces import request. +use util::H256; +use header::BlockNumber; +use trace::BlockTraces; + +/// Traces import request. +pub struct ImportRequest { + /// Traces to import. + pub traces: BlockTraces, + /// Hash of traces block. + pub block_hash: H256, + /// Number of traces block. + pub block_number: BlockNumber, + /// Blocks enacted by this import. + /// + /// They should be ordered from oldest to newest. + pub enacted: Vec, + /// Number of blocks retracted by this import. + pub retracted: usize, +} diff --git a/ethcore/src/trace/localized.rs b/ethcore/src/trace/localized.rs new file mode 100644 index 000000000..ef18d6898 --- /dev/null +++ b/ethcore/src/trace/localized.rs @@ -0,0 +1,42 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use util::H256; +use super::trace::{Action, Res}; +use header::BlockNumber; + +/// Localized trace. +#[derive(Debug, PartialEq)] +pub struct LocalizedTrace { + /// Type of action performed by a transaction. + pub action: Action, + /// Result of this action. + pub result: Res, + /// Number of subtraces. + pub subtraces: usize, + /// Exact location of trace. + /// + /// [index in root, index in first CALL, index in second CALL, ...] + pub trace_address: Vec, + /// Transaction number within the block. + pub transaction_number: usize, + /// Signed transaction hash. + pub transaction_hash: H256, + /// Block number. + pub block_number: BlockNumber, + /// Block hash. + pub block_hash: H256, +} diff --git a/ethcore/src/trace/mod.rs b/ethcore/src/trace/mod.rs index 2a5bda6c5..445689310 100644 --- a/ethcore/src/trace/mod.rs +++ b/ethcore/src/trace/mod.rs @@ -16,21 +16,39 @@ //! Tracing -mod trace; +mod block; +mod bloom; +mod config; +mod db; +mod executive_tracer; +mod filter; +mod flat; +mod import; +mod localized; +mod noop_tracer; +pub mod trace; -pub use self::trace::*; -use util::bytes::Bytes; -use util::hash::Address; -use util::numbers::U256; +pub use self::block::BlockTraces; +pub use self::config::{Config, Switch}; +pub use self::db::TraceDB; +pub use self::trace::Trace; +pub use self::noop_tracer::NoopTracer; +pub use self::executive_tracer::ExecutiveTracer; +pub use self::filter::{Filter, AddressesFilter}; +pub use self::import::ImportRequest; +pub use self::localized::LocalizedTrace; +use util::{Bytes, Address, U256, H256}; +use self::trace::{Call, Create}; use action_params::ActionParams; +use header::BlockNumber; /// This trait is used by executive to build traces. pub trait Tracer: Send { /// Prepares call trace for given params. Noop tracer should return None. - fn prepare_trace_call(&self, params: &ActionParams) -> Option; + fn prepare_trace_call(&self, params: &ActionParams) -> Option; /// Prepares create trace for given params. Noop tracer should return None. - fn prepare_trace_create(&self, params: &ActionParams) -> Option; + fn prepare_trace_create(&self, params: &ActionParams) -> Option; /// Prepare trace output. Noop tracer should return None. fn prepare_trace_output(&self) -> Option; @@ -38,7 +56,7 @@ pub trait Tracer: Send { /// Stores trace call info. fn trace_call( &mut self, - call: Option, + call: Option, gas_used: U256, output: Option, depth: usize, @@ -49,7 +67,7 @@ pub trait Tracer: Send { /// Stores trace create info. fn trace_create( &mut self, - create: Option, + create: Option, gas_used: U256, code: Option, address: Address, @@ -58,10 +76,10 @@ pub trait Tracer: Send { ); /// Stores failed call trace. - fn trace_failed_call(&mut self, call: Option, depth: usize, subs: Vec, delegate_call: bool); + fn trace_failed_call(&mut self, call: Option, depth: usize, subs: Vec, delegate_call: bool); /// Stores failed create trace. - fn trace_failed_create(&mut self, create: Option, depth: usize, subs: Vec); + fn trace_failed_create(&mut self, create: Option, depth: usize, subs: Vec); /// Spawn subracer which will be used to trace deeper levels of execution. fn subtracer(&self) -> Self where Self: Sized; @@ -70,132 +88,33 @@ pub trait Tracer: Send { fn traces(self) -> Vec; } -/// Nonoperative tracer. Does not trace anything. -pub struct NoopTracer; +/// DbExtras provides an interface to query extra data which is not stored in tracesdb, +/// but necessary to work correctly. +pub trait DatabaseExtras { + /// Returns hash of given block number. + fn block_hash(&self, block_number: BlockNumber) -> Option; -impl Tracer for NoopTracer { - fn prepare_trace_call(&self, _: &ActionParams) -> Option { - None - } - - fn prepare_trace_create(&self, _: &ActionParams) -> Option { - None - } - - fn prepare_trace_output(&self) -> Option { - None - } - - fn trace_call(&mut self, call: Option, _: U256, output: Option, _: usize, _: Vec, - _: bool) { - assert!(call.is_none()); - assert!(output.is_none()); - } - - fn trace_create(&mut self, create: Option, _: U256, code: Option, _: Address, _: usize, _: Vec) { - assert!(create.is_none()); - assert!(code.is_none()); - } - - fn trace_failed_call(&mut self, call: Option, _: usize, _: Vec, _: bool) { - assert!(call.is_none()); - } - - fn trace_failed_create(&mut self, create: Option, _: usize, _: Vec) { - assert!(create.is_none()); - } - - fn subtracer(&self) -> Self { - NoopTracer - } - - fn traces(self) -> Vec { - vec![] - } + /// Returns hash of transaction at given position. + fn transaction_hash(&self, block_number: BlockNumber, tx_position: usize) -> Option; } -/// Simple executive tracer. Traces all calls and creates. Ignores delegatecalls. -#[derive(Default)] -pub struct ExecutiveTracer { - traces: Vec -} - -impl Tracer for ExecutiveTracer { - fn prepare_trace_call(&self, params: &ActionParams) -> Option { - Some(TraceCall::from(params.clone())) - } - - fn prepare_trace_create(&self, params: &ActionParams) -> Option { - Some(TraceCreate::from(params.clone())) - } - - fn prepare_trace_output(&self) -> Option { - Some(vec![]) - } - - fn trace_call(&mut self, call: Option, gas_used: U256, output: Option, depth: usize, subs: - Vec, delegate_call: bool) { - // don't trace if it's DELEGATECALL or CALLCODE. - if delegate_call { - return; - } - - let trace = Trace { - depth: depth, - subs: subs, - action: TraceAction::Call(call.expect("Trace call expected to be Some.")), - result: TraceResult::Call(TraceCallResult { - gas_used: gas_used, - output: output.expect("Trace call output expected to be Some.") - }) - }; - self.traces.push(trace); - } - - fn trace_create(&mut self, create: Option, gas_used: U256, code: Option, address: Address, depth: usize, subs: Vec) { - let trace = Trace { - depth: depth, - subs: subs, - action: TraceAction::Create(create.expect("Trace create expected to be Some.")), - result: TraceResult::Create(TraceCreateResult { - gas_used: gas_used, - code: code.expect("Trace create code expected to be Some."), - address: address - }) - }; - self.traces.push(trace); - } - - fn trace_failed_call(&mut self, call: Option, depth: usize, subs: Vec, delegate_call: bool) { - // don't trace if it's DELEGATECALL or CALLCODE. - if delegate_call { - return; - } - - let trace = Trace { - depth: depth, - subs: subs, - action: TraceAction::Call(call.expect("Trace call expected to be Some.")), - result: TraceResult::FailedCall, - }; - self.traces.push(trace); - } - - fn trace_failed_create(&mut self, create: Option, depth: usize, subs: Vec) { - let trace = Trace { - depth: depth, - subs: subs, - action: TraceAction::Create(create.expect("Trace create expected to be Some.")), - result: TraceResult::FailedCreate, - }; - self.traces.push(trace); - } - - fn subtracer(&self) -> Self { - ExecutiveTracer::default() - } - - fn traces(self) -> Vec { - self.traces - } +/// Db provides an interface to query tracesdb. +pub trait Database { + /// Returns true if tracing is enabled. Otherwise false. + fn tracing_enabled(&self) -> bool; + + /// Imports new block traces. + fn import(&self, request: ImportRequest); + + /// Returns localized trace at given position. + fn trace(&self, block_number: BlockNumber, tx_position: usize, trace_position: Vec) -> Option; + + /// Returns localized traces created by a single transaction. + fn transaction_traces(&self, block_number: BlockNumber, tx_position: usize) -> Option>; + + /// Returns localized traces created in given block. + fn block_traces(&self, block_number: BlockNumber) -> Option>; + + /// Filter traces matching given filter. + fn filter(&self, filter: &Filter) -> Vec; } diff --git a/ethcore/src/trace/noop_tracer.rs b/ethcore/src/trace/noop_tracer.rs new file mode 100644 index 000000000..2581e692b --- /dev/null +++ b/ethcore/src/trace/noop_tracer.rs @@ -0,0 +1,65 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Nonoperative tracer. + +use util::{Bytes, Address, U256}; +use action_params::ActionParams; +use trace::Tracer; +use trace::trace::{Trace, Call, Create}; + +/// Nonoperative tracer. Does not trace anything. +pub struct NoopTracer; + +impl Tracer for NoopTracer { + fn prepare_trace_call(&self, _: &ActionParams) -> Option { + None + } + + fn prepare_trace_create(&self, _: &ActionParams) -> Option { + None + } + + fn prepare_trace_output(&self) -> Option { + None + } + + fn trace_call(&mut self, call: Option, _: U256, output: Option, _: usize, _: Vec, _: bool) { + assert!(call.is_none(), "self.prepare_trace_call().is_none(): so we can't be tracing: qed"); + assert!(output.is_none(), "self.prepare_trace_output().is_none(): so we can't be tracing: qed"); + } + + fn trace_create(&mut self, create: Option, _: U256, code: Option, _: Address, _: usize, _: Vec) { + assert!(create.is_none(), "self.prepare_trace_create().is_none(): so we can't be tracing: qed"); + assert!(code.is_none(), "self.prepare_trace_output().is_none(): so we can't be tracing: qed"); + } + + fn trace_failed_call(&mut self, call: Option, _: usize, _: Vec, _: bool) { + assert!(call.is_none(), "self.prepare_trace_call().is_none(): so we can't be tracing: qed"); + } + + fn trace_failed_create(&mut self, create: Option, _: usize, _: Vec) { + assert!(create.is_none(), "self.prepare_trace_create().is_none(): so we can't be tracing: qed"); + } + + fn subtracer(&self) -> Self { + NoopTracer + } + + fn traces(self) -> Vec { + vec![] + } +} diff --git a/ethcore/src/trace/trace.rs b/ethcore/src/trace/trace.rs index 6a90aaf8a..f7efe9721 100644 --- a/ethcore/src/trace/trace.rs +++ b/ethcore/src/trace/trace.rs @@ -15,20 +15,44 @@ // along with Parity. If not, see . //! Tracing datatypes. -use common::*; +use util::{U256, Bytes, Address, FixedHash}; +use util::rlp::*; +use util::sha3::Hashable; +use action_params::ActionParams; +use basic_types::LogBloom; -/// `TraceCall` result. +/// `Call` result. #[derive(Debug, Clone, PartialEq, Default)] -pub struct TraceCallResult { +pub struct CallResult { /// Gas used by call. pub gas_used: U256, /// Call Output. pub output: Bytes, } -/// `TraceCreate` result. +impl Encodable for CallResult { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(2); + s.append(&self.gas_used); + s.append(&self.output); + } +} + +impl Decodable for CallResult { + fn decode(decoder: &D) -> Result where D: Decoder { + let d = decoder.as_rlp(); + let res = CallResult { + gas_used: try!(d.val_at(0)), + output: try!(d.val_at(1)), + }; + + Ok(res) + } +} + +/// `Create` result. #[derive(Debug, Clone, PartialEq)] -pub struct TraceCreateResult { +pub struct CreateResult { /// Gas used by create. pub gas_used: U256, /// Code of the newly created contract. @@ -37,9 +61,31 @@ pub struct TraceCreateResult { pub address: Address, } +impl Encodable for CreateResult { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(3); + s.append(&self.gas_used); + s.append(&self.code); + s.append(&self.address); + } +} + +impl Decodable for CreateResult { + fn decode(decoder: &D) -> Result where D: Decoder { + let d = decoder.as_rlp(); + let res = CreateResult { + gas_used: try!(d.val_at(0)), + code: try!(d.val_at(1)), + address: try!(d.val_at(2)), + }; + + Ok(res) + } +} + /// Description of a _call_ action, either a `CALL` operation or a message transction. #[derive(Debug, Clone, PartialEq)] -pub struct TraceCall { +pub struct Call { /// The sending account. pub from: Address, /// The destination account. @@ -52,9 +98,9 @@ pub struct TraceCall { pub input: Bytes, } -impl From for TraceCall { +impl From for Call { fn from(p: ActionParams) -> Self { - TraceCall { + Call { from: p.sender, to: p.address, value: p.value.value(), @@ -64,9 +110,44 @@ impl From for TraceCall { } } +impl Encodable for Call { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(5); + s.append(&self.from); + s.append(&self.to); + s.append(&self.value); + s.append(&self.gas); + s.append(&self.input); + } +} + +impl Decodable for Call { + fn decode(decoder: &D) -> Result where D: Decoder { + let d = decoder.as_rlp(); + let res = Call { + from: try!(d.val_at(0)), + to: try!(d.val_at(1)), + value: try!(d.val_at(2)), + gas: try!(d.val_at(3)), + input: try!(d.val_at(4)), + }; + + Ok(res) + } +} + +impl Call { + /// Returns call action bloom. + /// The bloom contains from and to addresses. + pub fn bloom(&self) -> LogBloom { + LogBloom::from_bloomed(&self.from.sha3()) + .with_bloomed(&self.to.sha3()) + } +} + /// Description of a _create_ action, either a `CREATE` operation or a create transction. #[derive(Debug, Clone, PartialEq)] -pub struct TraceCreate { +pub struct Create { /// The address of the creator. pub from: Address, /// The value with which the new account is endowed. @@ -77,9 +158,9 @@ pub struct TraceCreate { pub init: Bytes, } -impl From for TraceCreate { +impl From for Create { fn from(p: ActionParams) -> Self { - TraceCreate { + Create { from: p.sender, value: p.value.value(), gas: p.gas, @@ -88,28 +169,137 @@ impl From for TraceCreate { } } +impl Encodable for Create { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(4); + s.append(&self.from); + s.append(&self.value); + s.append(&self.gas); + s.append(&self.init); + } +} + +impl Decodable for Create { + fn decode(decoder: &D) -> Result where D: Decoder { + let d = decoder.as_rlp(); + let res = Create { + from: try!(d.val_at(0)), + value: try!(d.val_at(1)), + gas: try!(d.val_at(2)), + init: try!(d.val_at(3)), + }; + + Ok(res) + } +} + +impl Create { + /// Returns bloom create action bloom. + /// The bloom contains only from address. + pub fn bloom(&self) -> LogBloom { + LogBloom::from_bloomed(&self.from.sha3()) + } +} + /// Description of an action that we trace; will be either a call or a create. #[derive(Debug, Clone, PartialEq)] -pub enum TraceAction { +pub enum Action { /// It's a call action. - Call(TraceCall), + Call(Call), /// It's a create action. - Create(TraceCreate), + Create(Create), +} + +impl Encodable for Action { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(2); + match *self { + Action::Call(ref call) => { + s.append(&0u8); + s.append(call); + }, + Action::Create(ref create) => { + s.append(&1u8); + s.append(create); + } + } + } +} + +impl Decodable for Action { + fn decode(decoder: &D) -> Result where D: Decoder { + let d = decoder.as_rlp(); + let action_type: u8 = try!(d.val_at(0)); + match action_type { + 0 => d.val_at(1).map(Action::Call), + 1 => d.val_at(1).map(Action::Create), + _ => Err(DecoderError::Custom("Invalid action type.")), + } + } +} + +impl Action { + /// Returns action bloom. + pub fn bloom(&self) -> LogBloom { + match *self { + Action::Call(ref call) => call.bloom(), + Action::Create(ref create) => create.bloom(), + } + } } /// The result of the performed action. #[derive(Debug, Clone, PartialEq)] -pub enum TraceResult { +pub enum Res { /// Successful call action result. - Call(TraceCallResult), + Call(CallResult), /// Successful create action result. - Create(TraceCreateResult), + Create(CreateResult), /// Failed call. FailedCall, /// Failed create. FailedCreate, } +impl Encodable for Res { + fn rlp_append(&self, s: &mut RlpStream) { + match *self { + Res::Call(ref call) => { + s.begin_list(2); + s.append(&0u8); + s.append(call); + }, + Res::Create(ref create) => { + s.begin_list(2); + s.append(&1u8); + s.append(create); + }, + Res::FailedCall => { + s.begin_list(1); + s.append(&2u8); + }, + Res::FailedCreate => { + s.begin_list(1); + s.append(&3u8); + } + } + } +} + +impl Decodable for Res { + fn decode(decoder: &D) -> Result where D: Decoder { + let d = decoder.as_rlp(); + let action_type: u8 = try!(d.val_at(0)); + match action_type { + 0 => d.val_at(1).map(Res::Call), + 1 => d.val_at(1).map(Res::Create), + 2 => Ok(Res::FailedCall), + 3 => Ok(Res::FailedCreate), + _ => Err(DecoderError::Custom("Invalid result type.")), + } + } +} + #[derive(Debug, Clone, PartialEq)] /// A trace; includes a description of the action being traced and sub traces of each interior action. pub struct Trace { @@ -117,9 +307,122 @@ pub struct Trace { /// the outer action of the transaction. pub depth: usize, /// The action being performed. - pub action: TraceAction, + pub action: Action, /// The sub traces for each interior action performed as part of this call. pub subs: Vec, /// The result of the performed action. - pub result: TraceResult, + pub result: Res, +} + +impl Encodable for Trace { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(4); + s.append(&self.depth); + s.append(&self.action); + s.append(&self.subs); + s.append(&self.result); + } +} + +impl Decodable for Trace { + fn decode(decoder: &D) -> Result where D: Decoder { + let d = decoder.as_rlp(); + let res = Trace { + depth: try!(d.val_at(0)), + action: try!(d.val_at(1)), + subs: try!(d.val_at(2)), + result: try!(d.val_at(3)), + }; + + Ok(res) + } +} + +impl Trace { + /// Returns trace bloom. + pub fn bloom(&self) -> LogBloom { + self.subs.iter().fold(self.action.bloom(), |b, s| b | s.bloom()) + } +} + +#[cfg(test)] +mod tests { + use util::{Address, U256, FixedHash}; + use util::rlp::{encode, decode}; + use util::sha3::Hashable; + use trace::trace::{Call, CallResult, Create, Res, Action, Trace}; + + #[test] + fn traces_rlp() { + let trace = Trace { + depth: 2, + action: Action::Call(Call { + from: Address::from(1), + to: Address::from(2), + value: U256::from(3), + gas: U256::from(4), + input: vec![0x5] + }), + subs: vec![ + Trace { + depth: 3, + action: Action::Create(Create { + from: Address::from(6), + value: U256::from(7), + gas: U256::from(8), + init: vec![0x9] + }), + subs: vec![], + result: Res::FailedCreate + } + ], + result: Res::Call(CallResult { + gas_used: U256::from(10), + output: vec![0x11, 0x12] + }) + }; + + let encoded = encode(&trace); + let decoded: Trace = decode(&encoded); + assert_eq!(trace, decoded); + } + + #[test] + fn traces_bloom() { + let trace = Trace { + depth: 2, + action: Action::Call(Call { + from: Address::from(1), + to: Address::from(2), + value: U256::from(3), + gas: U256::from(4), + input: vec![0x5] + }), + subs: vec![ + Trace { + depth: 3, + action: Action::Create(Create { + from: Address::from(6), + value: U256::from(7), + gas: U256::from(8), + init: vec![0x9] + }), + subs: vec![], + result: Res::FailedCreate + } + ], + result: Res::Call(CallResult { + gas_used: U256::from(10), + output: vec![0x11, 0x12] + }) + }; + + let bloom = trace.bloom(); + + // right now only addresses are bloomed + assert!(bloom.contains_bloomed(&Address::from(1).sha3())); + assert!(bloom.contains_bloomed(&Address::from(2).sha3())); + assert!(!bloom.contains_bloomed(&Address::from(20).sha3())); + assert!(bloom.contains_bloomed(&Address::from(6).sha3())); + } } diff --git a/ethcore/src/verification/verification.rs b/ethcore/src/verification/verification.rs index a3cc21032..ba7ef3bd4 100644 --- a/ethcore/src/verification/verification.rs +++ b/ethcore/src/verification/verification.rs @@ -278,8 +278,6 @@ mod tests { } impl BlockProvider for TestBlockChain { - fn have_tracing(&self) -> bool { false } - fn is_known(&self, hash: &H256) -> bool { self.blocks.contains_key(hash) } diff --git a/util/src/hash.rs b/util/src/hash.rs index 1b894d82f..69ed17c79 100644 --- a/util/src/hash.rs +++ b/util/src/hash.rs @@ -78,6 +78,12 @@ macro_rules! impl_hash { /// Unformatted binary data of fixed length. pub struct $from (pub [u8; $size]); + impl From<[u8; $size]> for $from { + fn from(bytes: [u8; $size]) -> Self { + $from(bytes) + } + } + impl Deref for $from { type Target = [u8]; diff --git a/util/src/rlp/rlperrors.rs b/util/src/rlp/rlperrors.rs index 2d821be5f..93a82187e 100644 --- a/util/src/rlp/rlperrors.rs +++ b/util/src/rlp/rlperrors.rs @@ -40,7 +40,9 @@ pub enum DecoderError { /// Non-canonical (longer than necessary) representation used for data or list. RlpInvalidIndirection, /// Declared length is inconsistent with data specified after. - RlpInconsistentLengthAndData + RlpInconsistentLengthAndData, + /// Custom rlp decoding error. + Custom(&'static str), } impl StdError for DecoderError {