Core tracedb functionality. (#996)

* fixed encoding 0u8

* simplified if else stmt

* tracedb core

* more comprehensive tracedb tests

* fixed minor review issues

* addresses filter

* fixed typos

* replace malformed with corrupted

* trace switch

* db key is generic and can be made smaller

* smaller tracedb keys

* tracedb version

* fixed ignored tests

* rename Tracedb -> TraceDB

* fixed typos

* proves

* trace only top level calls to builtins to avoid DDoS attacks

* fixed tracedb config switches

* fix comments fat replaced with trace

* vector-addressing scheme for localized traces

* removed comments

* removed first, redundant 0 from trace address

* updated db.trace method

* additional tests for tracedb.trace()
This commit is contained in:
Marek Kotewicz
2016-04-30 17:41:24 +02:00
committed by Gav Wood
parent e942f86bd7
commit 66477a9476
31 changed files with 2090 additions and 239 deletions

View File

@@ -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<Trace>);
impl From<Vec<Trace>> for BlockTraces {
fn from(traces: Vec<Trace>) -> Self {
BlockTraces(traces)
}
}
impl Into<Vec<Trace>> for BlockTraces {
fn into(self) -> Vec<Trace> {
self.0
}
}
impl Decodable for BlockTraces {
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> 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())
}
}

106
ethcore/src/trace/bloom.rs Normal file
View File

@@ -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<LogBloom> for BlockTracesBloom {
fn from(bloom: LogBloom) -> BlockTracesBloom {
BlockTracesBloom(bloom)
}
}
impl From<Bloom> for BlockTracesBloom {
fn from(bloom: Bloom) -> BlockTracesBloom {
let bytes: [u8; 256] = bloom.into();
BlockTracesBloom(LogBloom::from(bytes))
}
}
impl Into<Bloom> 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<BlockTracesBloom>,
}
impl From<BloomGroup> for BlockTracesBloomGroup {
fn from(group: BloomGroup) -> Self {
let blooms = group.blooms
.into_iter()
.map(From::from)
.collect();
BlockTracesBloomGroup {
blooms: blooms
}
}
}
impl Into<BloomGroup> 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<D>(decoder: &D) -> Result<Self, DecoderError> 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<D>(decoder: &D) -> Result<Self, DecoderError> 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<GroupPosition> for TraceGroupPosition {
fn from(p: GroupPosition) -> Self {
TraceGroupPosition {
level: p.level as u8,
index: p.index as u32,
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
//! 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<bool, &'static str> {
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,
}
}
}
}

582
ethcore/src/trace/db.rs Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
//! 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<BlockTraces> 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<BlockTracesBloomGroup> 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<T> where T: DatabaseExtras {
// cache
traces: RwLock<HashMap<H256, BlockTraces>>,
blooms: RwLock<HashMap<TraceGroupPosition, BlockTracesBloomGroup>>,
// db
tracesdb: Database,
// config,
bloom_config: BloomConfig,
// tracing enabled
enabled: bool,
// extras
extras: Arc<T>,
}
impl<T> BloomGroupDatabase for TraceDB<T> where T: DatabaseExtras {
fn blooms_at(&self, position: &GroupPosition) -> Option<BloomGroup> {
let position = TraceGroupPosition::from(position.clone());
self.tracesdb.read_with_cache(&self.blooms, &position).map(Into::into)
}
}
impl<T> TraceDB<T> where T: DatabaseExtras {
/// Creates new instance of `TraceDB`.
pub fn new(config: Config, path: &Path, extras: Arc<T>) -> 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<BlockTraces> {
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<Vec<FlatTransactionTraces>> {
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<LocalizedTrace> {
let tx_traces: Vec<FlatTransactionTraces> = 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<LocalizedTrace> {
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<FlatTrace> = 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<T> TraceDatabase for TraceDB<T> 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::<HashMap<TraceGroupPosition, BlockTracesBloomGroup>>();
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<usize>) -> Option<LocalizedTrace> {
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::<Vec<FlatTrace>>::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<Vec<LocalizedTrace>> {
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::<Vec<FlatTrace>>::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<Vec<LocalizedTrace>> {
self.extras.block_hash(block_number)
.and_then(|block_hash| self.transactions_traces(&block_hash)
.map(|traces| {
traces.into_iter()
.map(Into::<Vec<FlatTrace>>::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::<Vec<LocalizedTrace>>()
})
.collect::<Vec<LocalizedTrace>>()
})
)
}
fn filter(&self, filter: &Filter) -> Vec<LocalizedTrace> {
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<H256> {
unimplemented!();
}
fn transaction_hash(&self, _block_number: BlockNumber, _tx_position: usize) -> Option<H256> {
unimplemented!();
}
}
struct Extras {
block_hashes: HashMap<BlockNumber, H256>,
transaction_hashes: HashMap<BlockNumber, Vec<H256>>,
}
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<H256> {
self.block_hashes.get(&block_number).cloned()
}
fn transaction_hash(&self, block_number: BlockNumber, tx_position: usize) -> Option<H256> {
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()));
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
//! 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<Trace>
}
impl Tracer for ExecutiveTracer {
fn prepare_trace_call(&self, params: &ActionParams) -> Option<Call> {
Some(Call::from(params.clone()))
}
fn prepare_trace_create(&self, params: &ActionParams) -> Option<Create> {
Some(Create::from(params.clone()))
}
fn prepare_trace_output(&self) -> Option<Bytes> {
Some(vec![])
}
fn trace_call(&mut self, call: Option<Call>, gas_used: U256, output: Option<Bytes>, depth: usize, subs:
Vec<Trace>, 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<Create>, gas_used: U256, code: Option<Bytes>, address: Address, depth: usize, subs: Vec<Trace>) {
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<Call>, depth: usize, subs: Vec<Trace>, 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<Create>, depth: usize, subs: Vec<Trace>) {
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<Trace> {
self.traces
}
}

283
ethcore/src/trace/filter.rs Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
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<Address>);
impl From<Vec<Address>> for AddressesFilter {
fn from(addresses: Vec<Address>) -> 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<LogBloom> {
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<LogBloom>) -> Vec<LogBloom> {
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::<Vec<_>>())
.collect()
}
}
}
/// Traces filter.
pub struct Filter {
/// Block range.
pub range: Range<usize>,
/// From address filter.
pub from_address: AddressesFilter,
/// To address filter.
pub to_address: AddressesFilter,
}
impl BloomFilter for Filter {
fn bloom_possibilities(&self) -> Vec<Bloom> {
self.bloom_possibilities()
.into_iter()
.map(|b| Bloom::from(b.0))
.collect()
}
fn range(&self) -> Range<Number> {
self.range.clone()
}
}
impl Filter {
/// Returns combinations of each address.
fn bloom_possibilities(&self) -> Vec<LogBloom> {
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));
}
}

181
ethcore/src/trace/flat.rs Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
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<usize>,
}
/// Represents all traces produced by a single transaction.
pub struct FlatTransactionTraces(Vec<FlatTrace>);
impl Into<Vec<FlatTrace>> for FlatTransactionTraces {
fn into(self) -> Vec<FlatTrace> {
self.0
}
}
/// Represents all traces produced by transactions in a single block.
pub struct FlatBlockTraces(Vec<FlatTransactionTraces>);
impl From<BlockTraces> for FlatBlockTraces {
fn from(block_traces: BlockTraces) -> Self {
let traces: Vec<Trace> = block_traces.into();
let ordered = traces.into_iter()
.map(|trace| FlatBlockTraces::flatten(vec![], trace))
.map(FlatTransactionTraces)
.collect();
FlatBlockTraces(ordered)
}
}
impl Into<Vec<FlatTransactionTraces>> for FlatBlockTraces {
fn into(self) -> Vec<FlatTransactionTraces> {
self.0
}
}
impl FlatBlockTraces {
/// Helper function flattening nested tree structure to vector of ordered traces.
fn flatten(address: Vec<usize>, trace: Trace) -> Vec<FlatTrace> {
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::<Vec<_>>();
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<FlatTransactionTraces> = block_traces.into();
assert_eq!(transaction_traces.len(), 1);
let ordered_traces: Vec<FlatTrace> = 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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
//! 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<H256>,
/// Number of blocks retracted by this import.
pub retracted: usize,
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
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<usize>,
/// 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,
}

View File

@@ -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<TraceCall>;
fn prepare_trace_call(&self, params: &ActionParams) -> Option<Call>;
/// Prepares create trace for given params. Noop tracer should return None.
fn prepare_trace_create(&self, params: &ActionParams) -> Option<TraceCreate>;
fn prepare_trace_create(&self, params: &ActionParams) -> Option<Create>;
/// Prepare trace output. Noop tracer should return None.
fn prepare_trace_output(&self) -> Option<Bytes>;
@@ -38,7 +56,7 @@ pub trait Tracer: Send {
/// Stores trace call info.
fn trace_call(
&mut self,
call: Option<TraceCall>,
call: Option<Call>,
gas_used: U256,
output: Option<Bytes>,
depth: usize,
@@ -49,7 +67,7 @@ pub trait Tracer: Send {
/// Stores trace create info.
fn trace_create(
&mut self,
create: Option<TraceCreate>,
create: Option<Create>,
gas_used: U256,
code: Option<Bytes>,
address: Address,
@@ -58,10 +76,10 @@ pub trait Tracer: Send {
);
/// Stores failed call trace.
fn trace_failed_call(&mut self, call: Option<TraceCall>, depth: usize, subs: Vec<Trace>, delegate_call: bool);
fn trace_failed_call(&mut self, call: Option<Call>, depth: usize, subs: Vec<Trace>, delegate_call: bool);
/// Stores failed create trace.
fn trace_failed_create(&mut self, create: Option<TraceCreate>, depth: usize, subs: Vec<Trace>);
fn trace_failed_create(&mut self, create: Option<Create>, depth: usize, subs: Vec<Trace>);
/// 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<Trace>;
}
/// 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<H256>;
impl Tracer for NoopTracer {
fn prepare_trace_call(&self, _: &ActionParams) -> Option<TraceCall> {
None
}
fn prepare_trace_create(&self, _: &ActionParams) -> Option<TraceCreate> {
None
}
fn prepare_trace_output(&self) -> Option<Bytes> {
None
}
fn trace_call(&mut self, call: Option<TraceCall>, _: U256, output: Option<Bytes>, _: usize, _: Vec<Trace>,
_: bool) {
assert!(call.is_none());
assert!(output.is_none());
}
fn trace_create(&mut self, create: Option<TraceCreate>, _: U256, code: Option<Bytes>, _: Address, _: usize, _: Vec<Trace>) {
assert!(create.is_none());
assert!(code.is_none());
}
fn trace_failed_call(&mut self, call: Option<TraceCall>, _: usize, _: Vec<Trace>, _: bool) {
assert!(call.is_none());
}
fn trace_failed_create(&mut self, create: Option<TraceCreate>, _: usize, _: Vec<Trace>) {
assert!(create.is_none());
}
fn subtracer(&self) -> Self {
NoopTracer
}
fn traces(self) -> Vec<Trace> {
vec![]
}
/// Returns hash of transaction at given position.
fn transaction_hash(&self, block_number: BlockNumber, tx_position: usize) -> Option<H256>;
}
/// Simple executive tracer. Traces all calls and creates. Ignores delegatecalls.
#[derive(Default)]
pub struct ExecutiveTracer {
traces: Vec<Trace>
}
impl Tracer for ExecutiveTracer {
fn prepare_trace_call(&self, params: &ActionParams) -> Option<TraceCall> {
Some(TraceCall::from(params.clone()))
}
fn prepare_trace_create(&self, params: &ActionParams) -> Option<TraceCreate> {
Some(TraceCreate::from(params.clone()))
}
fn prepare_trace_output(&self) -> Option<Bytes> {
Some(vec![])
}
fn trace_call(&mut self, call: Option<TraceCall>, gas_used: U256, output: Option<Bytes>, depth: usize, subs:
Vec<Trace>, 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<TraceCreate>, gas_used: U256, code: Option<Bytes>, address: Address, depth: usize, subs: Vec<Trace>) {
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<TraceCall>, depth: usize, subs: Vec<Trace>, 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<TraceCreate>, depth: usize, subs: Vec<Trace>) {
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<Trace> {
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<usize>) -> Option<LocalizedTrace>;
/// Returns localized traces created by a single transaction.
fn transaction_traces(&self, block_number: BlockNumber, tx_position: usize) -> Option<Vec<LocalizedTrace>>;
/// Returns localized traces created in given block.
fn block_traces(&self, block_number: BlockNumber) -> Option<Vec<LocalizedTrace>>;
/// Filter traces matching given filter.
fn filter(&self, filter: &Filter) -> Vec<LocalizedTrace>;
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
//! 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<Call> {
None
}
fn prepare_trace_create(&self, _: &ActionParams) -> Option<Create> {
None
}
fn prepare_trace_output(&self) -> Option<Bytes> {
None
}
fn trace_call(&mut self, call: Option<Call>, _: U256, output: Option<Bytes>, _: usize, _: Vec<Trace>, _: 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<Create>, _: U256, code: Option<Bytes>, _: Address, _: usize, _: Vec<Trace>) {
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<Call>, _: usize, _: Vec<Trace>, _: 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<Create>, _: usize, _: Vec<Trace>) {
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<Trace> {
vec![]
}
}

View File

@@ -15,20 +15,44 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! 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<D>(decoder: &D) -> Result<Self, DecoderError> 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<D>(decoder: &D) -> Result<Self, DecoderError> 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<ActionParams> for TraceCall {
impl From<ActionParams> 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<ActionParams> 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<D>(decoder: &D) -> Result<Self, DecoderError> 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<ActionParams> for TraceCreate {
impl From<ActionParams> 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<ActionParams> 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<D>(decoder: &D) -> Result<Self, DecoderError> 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<D>(decoder: &D) -> Result<Self, DecoderError> 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<D>(decoder: &D) -> Result<Self, DecoderError> 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<Trace>,
/// 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<D>(decoder: &D) -> Result<Self, DecoderError> 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()));
}
}