diff --git a/ethcore/res/null_morden_with_reward.json b/ethcore/res/null_morden_with_reward.json new file mode 100644 index 000000000..b7b1c9a0d --- /dev/null +++ b/ethcore/res/null_morden_with_reward.json @@ -0,0 +1,35 @@ +{ + "name": "Morden", + "engine": { + "null": null + }, + "params": { + "gasLimitBoundDivisor": "0x0400", + "accountStartNonce": "0x0", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID" : "0x2", + "blockReward": "0x4563918244F40000" + }, + "genesis": { + "seal": { + "ethereum": { + "nonce": "0x00006d6f7264656e", + "mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578" + } + }, + "difficulty": "0x20000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x", + "gasLimit": "0x2fefd8" + }, + "accounts": { + "0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, + "0000000000000000000000000000000000000002": { "balance": "1", "nonce": "1048576", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, + "0000000000000000000000000000000000000003": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, + "0000000000000000000000000000000000000004": { "balance": "1", "nonce": "1048576", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, + "102e61f5d8f9bc71d0ad4a084df4e65e05ce0e1c": { "balance": "1606938044258990275541962092341162602522202993782792835301376", "nonce": "1048576" } + } +} diff --git a/ethcore/src/block.rs b/ethcore/src/block.rs index 64958700e..341e50061 100644 --- a/ethcore/src/block.rs +++ b/ethcore/src/block.rs @@ -21,7 +21,7 @@ use std::sync::Arc; use std::collections::HashSet; use rlp::{UntrustedRlp, RlpStream, Encodable, Decodable, DecoderError}; -use util::{Bytes, Address, Hashable, U256, H256, ordered_trie_root, SHA3_NULL_RLP}; +use util::{Bytes, Address, Hashable, U256, H256, ordered_trie_root, SHA3_NULL_RLP, SHA3_EMPTY_LIST_RLP}; use util::error::{Mismatch, OutOfBounds}; use basic_types::{LogBloom, Seal}; @@ -107,7 +107,7 @@ pub struct BlockRefMut<'a> { /// State. pub state: &'a mut State, /// Traces. - pub traces: &'a Option>>, + pub traces: &'a mut Option>>, } /// A set of immutable references to `ExecutedBlock` fields that are publicly accessible. @@ -148,7 +148,7 @@ impl ExecutedBlock { uncles: &self.uncles, state: &mut self.state, receipts: &self.receipts, - traces: &self.traces, + traces: &mut self.traces, } } @@ -196,6 +196,9 @@ pub trait IsBlock { /// Get all uncles in this block. fn uncles(&self) -> &[Header] { &self.block().uncles } + + /// Get tracing enabled flag for this block. + fn tracing_enabled(&self) -> bool { self.block().traces.is_some() } } /// Trait for a object that has a state database. @@ -395,6 +398,7 @@ impl<'x> OpenBlock<'x> { if let Err(e) = s.engine.on_close_block(&mut s.block) { warn!("Encountered error on closing the block: {}", e); } + if let Err(e) = s.block.state.commit() { warn!("Encountered error on state commit: {}", e); } @@ -429,7 +433,7 @@ impl<'x> OpenBlock<'x> { s.block.header.set_transactions_root(ordered_trie_root(s.block.transactions.iter().map(|e| e.rlp_bytes().into_vec()))); } let uncle_bytes = s.block.uncles.iter().fold(RlpStream::new_list(s.block.uncles.len()), |mut s, u| {s.append_raw(&u.rlp(Seal::With), 1); s} ).out(); - if s.block.header.uncles_hash().is_zero() { + if s.block.header.uncles_hash().is_zero() || s.block.header.uncles_hash() == &SHA3_EMPTY_LIST_RLP { s.block.header.set_uncles_hash(uncle_bytes.sha3()); } if s.block.header.receipts_root().is_zero() || s.block.header.receipts_root() == &SHA3_NULL_RLP { diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 508cd3e25..63f0abef1 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -31,7 +31,6 @@ use error::{Error, TransactionError, BlockError}; use ethjson; use header::{Header, BlockNumber}; use spec::CommonParams; -use state::CleanupMode; use transaction::UnverifiedTransaction; use super::signer::EngineSigner; @@ -548,17 +547,7 @@ impl Engine for AuthorityRound { /// Apply the block reward on finalisation of the block. fn on_close_block(&self, block: &mut ExecutedBlock) -> Result<(), Error> { - let fields = block.fields_mut(); - // Bestow block reward - let reward = self.params().block_reward; - let res = fields.state.add_balance(fields.header.author(), &reward, CleanupMode::NoEmpty) - .map_err(::error::Error::from) - .and_then(|_| fields.state.commit()); - // Commit state so that we can actually figure out the state root. - if let Err(ref e) = res { - warn!("Encountered error on closing block: {}", e); - } - res + ::engines::common::bestow_block_reward(block, self) } /// Check the number of seal fields. diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 00f322318..cb13c984a 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -399,8 +399,9 @@ pub mod common { use transaction::SYSTEM_ADDRESS; use executive::Executive; use vm::{CallType, ActionParams, ActionValue, EnvInfo, LastHashes}; - use trace::{NoopTracer, NoopVMTracer}; + use trace::{NoopTracer, NoopVMTracer, Tracer, ExecutiveTracer, RewardType}; use state::Substate; + use state::CleanupMode; use util::*; use super::Engine; @@ -469,4 +470,27 @@ pub mod common { } Ok(()) } + + /// Trace rewards on closing block + pub fn bestow_block_reward(block: &mut ExecutedBlock, engine: &E) -> Result<(), Error> { + let fields = block.fields_mut(); + // Bestow block reward + let reward = engine.params().block_reward; + let res = fields.state.add_balance(fields.header.author(), &reward, CleanupMode::NoEmpty) + .map_err(::error::Error::from) + .and_then(|_| fields.state.commit()); + + let block_author = fields.header.author().clone(); + fields.traces.as_mut().map(|mut traces| { + let mut tracer = ExecutiveTracer::default(); + tracer.trace_reward(block_author, engine.params().block_reward, RewardType::Block); + traces.push(tracer.drain()) + }); + + // Commit state so that we can actually figure out the state root. + if let Err(ref e) = res { + warn!("Encountered error on bestowing reward: {}", e); + } + res + } } diff --git a/ethcore/src/engines/null_engine.rs b/ethcore/src/engines/null_engine.rs index 552154580..a52342c59 100644 --- a/ethcore/src/engines/null_engine.rs +++ b/ethcore/src/engines/null_engine.rs @@ -17,10 +17,15 @@ use std::collections::BTreeMap; use util::Address; use builtin::Builtin; +use block::{ExecutedBlock, IsBlock}; +use util::U256; use engines::Engine; use spec::CommonParams; use evm::Schedule; use header::BlockNumber; +use error::Error; +use state::CleanupMode; +use trace::{Tracer, ExecutiveTracer, RewardType}; /// An engine which does not provide any consensus mechanism and does not seal blocks. pub struct NullEngine { @@ -64,4 +69,48 @@ impl Engine for NullEngine { fn snapshot_components(&self) -> Option> { Some(Box::new(::snapshot::PowSnapshot::new(10000, 10000))) } + + fn on_close_block(&self, block: &mut ExecutedBlock) -> Result<(), Error> { + if self.params.block_reward == U256::zero() { + // we don't have to apply reward in this case + return Ok(()) + } + + /// Block reward + let tracing_enabled = block.tracing_enabled(); + let fields = block.fields_mut(); + let mut tracer = ExecutiveTracer::default(); + + let result_block_reward = U256::from(1000000000); + fields.state.add_balance( + fields.header.author(), + &result_block_reward, + CleanupMode::NoEmpty + )?; + + if tracing_enabled { + let block_author = fields.header.author().clone(); + tracer.trace_reward(block_author, result_block_reward, RewardType::Block); + } + + /// Uncle rewards + let result_uncle_reward = U256::from(10000000); + for u in fields.uncles.iter() { + let uncle_author = u.author().clone(); + fields.state.add_balance( + u.author(), + &(result_uncle_reward), + CleanupMode::NoEmpty + )?; + if tracing_enabled { + tracer.trace_reward(uncle_author, result_uncle_reward, RewardType::Uncle); + } + } + + fields.state.commit()?; + if tracing_enabled { + fields.traces.as_mut().map(|mut traces| traces.push(tracer.drain())); + } + Ok(()) + } } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index cc75e99c3..ac5ccd7b2 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -40,7 +40,6 @@ use account_provider::AccountProvider; use block::*; use spec::CommonParams; use engines::{Engine, Seal, EngineError, ConstructedVerifier}; -use state::CleanupMode; use io::IoService; use super::signer::EngineSigner; use super::validator_set::{ValidatorSet, SimpleList}; @@ -541,17 +540,7 @@ impl Engine for Tendermint { /// Apply the block reward on finalisation of the block. fn on_close_block(&self, block: &mut ExecutedBlock) -> Result<(), Error>{ - let fields = block.fields_mut(); - // Bestow block reward - let reward = self.params().block_reward; - let res = fields.state.add_balance(fields.header.author(), &reward, CleanupMode::NoEmpty) - .map_err(::error::Error::from) - .and_then(|_| fields.state.commit()); - // Commit state so that we can actually figure out the state root. - if let Err(ref e) = res { - warn!("Encountered error on closing block: {}", e); - } - res + ::engines::common::bestow_block_reward(block, self) } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index fc7b27403..ef997f5a8 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -24,6 +24,7 @@ use block::*; use builtin::Builtin; use vm::EnvInfo; use error::{BlockError, Error, TransactionError}; +use trace::{Tracer, ExecutiveTracer, RewardType}; use header::{Header, BlockNumber}; use state::CleanupMode; use spec::CommonParams; @@ -285,38 +286,60 @@ impl Engine for Arc { /// Apply the block reward on finalisation of the block. /// This assumes that all uncles are valid uncles (i.e. of at least one generation before the current). fn on_close_block(&self, block: &mut ExecutedBlock) -> Result<(), Error> { + use std::ops::Shr; let reward = self.params().block_reward; + let tracing_enabled = block.tracing_enabled(); let fields = block.fields_mut(); let eras_rounds = self.ethash_params.ecip1017_era_rounds; let (eras, reward) = ecip1017_eras_block_reward(eras_rounds, reward, fields.header.number()); + let mut tracer = ExecutiveTracer::default(); // Bestow block reward + let result_block_reward = reward + reward.shr(5) * U256::from(fields.uncles.len()); fields.state.add_balance( fields.header.author(), - &(reward + reward / U256::from(32) * U256::from(fields.uncles.len())), + &result_block_reward, CleanupMode::NoEmpty )?; + if tracing_enabled { + let block_author = fields.header.author().clone(); + tracer.trace_reward(block_author, result_block_reward, RewardType::Block); + } + // Bestow uncle rewards let current_number = fields.header.number(); for u in fields.uncles.iter() { + let uncle_author = u.author().clone(); + let result_uncle_reward: U256; + if eras == 0 { + result_uncle_reward = (reward * U256::from(8 + u.number() - current_number)).shr(3); fields.state.add_balance( u.author(), - &(reward * U256::from(8 + u.number() - current_number) / U256::from(8)), + &result_uncle_reward, CleanupMode::NoEmpty ) } else { + result_uncle_reward = reward.shr(5); fields.state.add_balance( u.author(), - &(reward / U256::from(32)), + &result_uncle_reward, CleanupMode::NoEmpty ) }?; + + // Trace uncle rewards + if tracing_enabled { + tracer.trace_reward(uncle_author, result_uncle_reward, RewardType::Uncle); + } } // Commit state so that we can actually figure out the state root. fields.state.commit()?; + if tracing_enabled { + fields.traces.as_mut().map(|mut traces| traces.push(tracer.drain())); + } Ok(()) } diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 7d82a470c..3fbfb66ef 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -483,6 +483,9 @@ impl Spec { /// Create a new Spec which conforms to the Frontier-era Morden chain except that it's a NullEngine consensus. pub fn new_test() -> Spec { load_bundled!("null_morden") } + /// Create a new Spec which conforms to the Frontier-era Morden chain except that it's a NullEngine consensus with applying reward on block close. + pub fn new_test_with_reward() -> Spec { load_bundled!("null_morden_with_reward") } + /// Create a new Spec which is a NullEngine consensus with a premine of address whose secret is sha3(''). pub fn new_null() -> Spec { load_bundled!("null") } diff --git a/ethcore/src/tests/mod.rs b/ethcore/src/tests/mod.rs index 31f195725..c86240f33 100644 --- a/ethcore/src/tests/mod.rs +++ b/ethcore/src/tests/mod.rs @@ -17,6 +17,7 @@ pub mod helpers; mod client; mod evm; +mod trace; #[cfg(feature="ipc")] mod rpc; diff --git a/ethcore/src/tests/trace.rs b/ethcore/src/tests/trace.rs new file mode 100644 index 000000000..61377daee --- /dev/null +++ b/ethcore/src/tests/trace.rs @@ -0,0 +1,206 @@ +// Copyright 2015-2017 Parity Technologies (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 . + +//! Client tests of tracing + +use ethkey::KeyPair; +use block::*; +use util::*; +use io::*; +use spec::*; +use client::*; +use tests::helpers::*; +use devtools::RandomTempPath; +use client::{BlockChainClient, Client, ClientConfig}; +use util::kvdb::{Database, DatabaseConfig}; +use std::sync::Arc; +use header::Header; +use miner::Miner; +use transaction::{Action, Transaction}; +use views::BlockView; +use trace::{RewardType, LocalizedTrace}; +use trace::trace::Action::Reward; + +#[test] +fn can_trace_block_and_uncle_reward() { + let dir = RandomTempPath::new(); + let spec = Spec::new_test_with_reward(); + let engine = &*spec.engine; + + // Create client + let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS); + let mut client_config = ClientConfig::default(); + client_config.tracing.enabled = true; + let client_db = Arc::new(Database::open(&db_config, dir.as_path().to_str().unwrap()).unwrap()); + let client = Client::new( + client_config, + &spec, + client_db, + Arc::new(Miner::with_spec(&spec)), + IoChannel::disconnected(), + ).unwrap(); + + // Create test data: + // genesis + // | + // root_block + // | + // parent_block + // | + // block with transaction and uncle + + let genesis_header = spec.genesis_header(); + let mut db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap(); + let mut rolling_timestamp = 40; + let mut last_hashes = vec![]; + let mut last_header = genesis_header.clone(); + last_hashes.push(last_header.hash()); + + let kp = KeyPair::from_secret_slice(&"".sha3()).unwrap(); + let author = kp.address(); + + // Add root block first + let mut root_block = OpenBlock::new( + engine, + Default::default(), + false, + db, + &last_header, + Arc::new(last_hashes.clone()), + author.clone(), + (3141562.into(), 31415620.into()), + vec![], + false, + ).unwrap(); + root_block.set_difficulty(U256::from(0x20000)); + rolling_timestamp += 10; + root_block.set_timestamp(rolling_timestamp); + + let root_block = root_block.close_and_lock().seal(engine, vec![]).unwrap(); + + if let Err(e) = client.import_block(root_block.rlp_bytes()) { + panic!("error importing block which is valid by definition: {:?}", e); + } + + last_header = BlockView::new(&root_block.rlp_bytes()).header(); + let root_header = last_header.clone(); + db = root_block.drain(); + + last_hashes.push(last_header.hash()); + + // Add parent block + let mut parent_block = OpenBlock::new( + engine, + Default::default(), + false, + db, + &last_header, + Arc::new(last_hashes.clone()), + author.clone(), + (3141562.into(), 31415620.into()), + vec![], + false, + ).unwrap(); + parent_block.set_difficulty(U256::from(0x20000)); + rolling_timestamp += 10; + parent_block.set_timestamp(rolling_timestamp); + + let parent_block = parent_block.close_and_lock().seal(engine, vec![]).unwrap(); + + if let Err(e) = client.import_block(parent_block.rlp_bytes()) { + panic!("error importing block which is valid by definition: {:?}", e); + } + + last_header = BlockView::new(&parent_block.rlp_bytes()).header(); + db = parent_block.drain(); + + last_hashes.push(last_header.hash()); + + // Add testing block with transaction and uncle + let mut block = OpenBlock::new( + engine, + Default::default(), + true, + db, + &last_header, + Arc::new(last_hashes.clone()), + author.clone(), + (3141562.into(), 31415620.into()), + vec![], + false + ).unwrap(); + block.set_difficulty(U256::from(0x20000)); + rolling_timestamp += 10; + block.set_timestamp(rolling_timestamp); + + let mut n = 0; + for _ in 0..1 { + block.push_transaction(Transaction { + nonce: n.into(), + gas_price: 10000.into(), + gas: 100000.into(), + action: Action::Create, + data: vec![], + value: U256::zero(), + }.sign(kp.secret(), Some(spec.network_id())), None).unwrap(); + n += 1; + } + + let mut uncle = Header::new(); + let uncle_author: Address = "ef2d6d194084c2de36e0dabfce45d046b37d1106".into(); + uncle.set_author(uncle_author); + uncle.set_parent_hash(root_header.hash()); + uncle.set_gas_limit(U256::from(50_000)); + uncle.set_number(root_header.number() + 1); + uncle.set_timestamp(rolling_timestamp); + block.push_uncle(uncle).unwrap(); + + let block = block.close_and_lock().seal(engine, vec![]).unwrap(); + + let res = client.import_block(block.rlp_bytes()); + if res.is_err() { + panic!("error importing block: {:#?}", res.err().unwrap()); + } + + block.drain(); + client.flush_queue(); + client.import_verified_blocks(); + + // Test0. Check overall filter + let filter = TraceFilter { + range: (BlockId::Number(1)..BlockId::Number(3)), + from_address: vec![], + to_address: vec![], + }; + + let traces = client.filter_traces(filter); + assert!(traces.is_some(), "Filtered traces should be present"); + let traces_vec = traces.unwrap(); + let block_reward_traces: Vec = traces_vec.clone().into_iter().filter(|trace| match (trace).action { + Reward(ref a) => a.reward_type == RewardType::Block, + _ => false, + }).collect(); + assert_eq!(block_reward_traces.len(), 3); + let uncle_reward_traces: Vec = traces_vec.clone().into_iter().filter(|trace| match (trace).action { + Reward(ref a) => a.reward_type == RewardType::Uncle, + _ => false, + }).collect(); + assert_eq!(uncle_reward_traces.len(), 1); + + // Test1. Check block filter + let traces = client.block_traces(BlockId::Number(3)); + assert_eq!(traces.unwrap().len(), 3); +} diff --git a/ethcore/src/trace/db.rs b/ethcore/src/trace/db.rs index fe90ffe41..5199fe0cd 100644 --- a/ethcore/src/trace/db.rs +++ b/ethcore/src/trace/db.rs @@ -215,8 +215,11 @@ impl TraceDB where T: DatabaseExtras { 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 (trace_tx_number, trace_tx_hash) = match self.extras.transaction_hash(block_number, tx_number) { + Some(hash) => (Some(tx_number), Some(hash.clone())), + //None means trace without transaction (reward) + None => (None, None), + }; let flat_traces: Vec = traces.into(); flat_traces.into_iter() @@ -227,8 +230,8 @@ impl TraceDB where T: DatabaseExtras { result: trace.result, subtraces: trace.subtraces, trace_address: trace.trace_address.into_iter().collect(), - transaction_number: tx_number, - transaction_hash: tx_hash.clone(), + transaction_number: trace_tx_number, + transaction_hash: trace_tx_hash, block_number: block_number, block_hash: block_hash }), @@ -321,8 +324,8 @@ impl TraceDatabase for TraceDB where T: DatabaseExtras { result: trace.result, subtraces: trace.subtraces, trace_address: trace.trace_address.into_iter().collect(), - transaction_number: tx_position, - transaction_hash: tx_hash, + transaction_number: Some(tx_position), + transaction_hash: Some(tx_hash), block_number: block_number, block_hash: block_hash, } @@ -345,8 +348,8 @@ impl TraceDatabase for TraceDB where T: DatabaseExtras { result: trace.result, subtraces: trace.subtraces, trace_address: trace.trace_address.into_iter().collect(), - transaction_number: tx_position, - transaction_hash: tx_hash.clone(), + transaction_number: Some(tx_position), + transaction_hash: Some(tx_hash.clone()), block_number: block_number, block_hash: block_hash }) @@ -363,8 +366,11 @@ impl TraceDatabase for TraceDB where T: DatabaseExtras { .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"); + let (trace_tx_number, trace_tx_hash) = match self.extras.transaction_hash(block_number, tx_position) { + Some(hash) => (Some(tx_position), Some(hash.clone())), + //None means trace without transaction (reward) + None => (None, None), + }; traces.into_iter() .map(|trace| LocalizedTrace { @@ -372,8 +378,8 @@ impl TraceDatabase for TraceDB where T: DatabaseExtras { result: trace.result, subtraces: trace.subtraces, trace_address: trace.trace_address.into_iter().collect(), - transaction_number: tx_position, - transaction_hash: tx_hash.clone(), + transaction_number: trace_tx_number, + transaction_hash: trace_tx_hash, block_number: block_number, block_hash: block_hash, }) @@ -543,8 +549,8 @@ mod tests { result: Res::FailedCall(TraceError::OutOfGas), trace_address: vec![], subtraces: 0, - transaction_number: 0, - transaction_hash: tx_hash, + transaction_number: Some(0), + transaction_hash: Some(tx_hash), block_number: block_number, block_hash: block_hash, } diff --git a/ethcore/src/trace/executive_tracer.rs b/ethcore/src/trace/executive_tracer.rs index ba4d0eff9..57e8eabb8 100644 --- a/ethcore/src/trace/executive_tracer.rs +++ b/ethcore/src/trace/executive_tracer.rs @@ -18,7 +18,7 @@ use util::{Bytes, Address, U256}; use vm::ActionParams; -use trace::trace::{Call, Create, Action, Res, CreateResult, CallResult, VMTrace, VMOperation, VMExecutedOperation, MemoryDiff, StorageDiff, Suicide}; +use trace::trace::{Call, Create, Action, Res, CreateResult, CallResult, VMTrace, VMOperation, VMExecutedOperation, MemoryDiff, StorageDiff, Suicide, Reward, RewardType}; use trace::{Tracer, VMTracer, FlatTrace, TraceError}; /// Simple executive tracer. Traces all calls and creates. Ignores delegatecalls. @@ -151,15 +151,22 @@ impl Tracer for ExecutiveTracer { fn trace_suicide(&mut self, address: Address, balance: U256, refund_address: Address) { let trace = FlatTrace { subtraces: 0, - action: Action::Suicide(Suicide { - address: address, - refund_address: refund_address, - balance: balance, - }), + action: Action::Suicide(Suicide { address, refund_address, balance } ), result: Res::None, trace_address: Default::default(), }; - debug!(target: "trace", "Traced failed suicide {:?}", trace); + debug!(target: "trace", "Traced suicide {:?}", trace); + self.traces.push(trace); + } + + fn trace_reward(&mut self, author: Address, value: U256, reward_type: RewardType) { + let trace = FlatTrace { + subtraces: 0, + action: Action::Reward(Reward { author, value, reward_type } ), + result: Res::None, + trace_address: Default::default(), + }; + debug!(target: "trace", "Traced reward {:?}", trace); self.traces.push(trace); } diff --git a/ethcore/src/trace/mod.rs b/ethcore/src/trace/mod.rs index c749bfd82..cbec8a149 100644 --- a/ethcore/src/trace/mod.rs +++ b/ethcore/src/trace/mod.rs @@ -33,7 +33,7 @@ pub use self::localized::LocalizedTrace; pub use self::types::{filter, flat, localized, trace}; pub use self::types::error::Error as TraceError; -pub use self::types::trace::{VMTrace, VMOperation, VMExecutedOperation, MemoryDiff, StorageDiff}; +pub use self::types::trace::{VMTrace, VMOperation, VMExecutedOperation, MemoryDiff, StorageDiff, RewardType}; pub use self::types::flat::{FlatTrace, FlatTransactionTraces, FlatBlockTraces}; pub use self::types::filter::{Filter, AddressesFilter}; @@ -81,6 +81,9 @@ pub trait Tracer: Send { /// Stores suicide info. fn trace_suicide(&mut self, address: Address, balance: U256, refund_address: Address); + /// Stores reward info. + fn trace_reward(&mut self, author: Address, value: U256, reward_type: RewardType); + /// Spawn subtracer which will be used to trace deeper levels of execution. fn subtracer(&self) -> Self where Self: Sized; diff --git a/ethcore/src/trace/noop_tracer.rs b/ethcore/src/trace/noop_tracer.rs index 03d6f57a0..c9bd5f2e8 100644 --- a/ethcore/src/trace/noop_tracer.rs +++ b/ethcore/src/trace/noop_tracer.rs @@ -19,7 +19,7 @@ use util::{Bytes, Address, U256}; use vm::ActionParams; use trace::{Tracer, VMTracer, FlatTrace, TraceError}; -use trace::trace::{Call, Create, VMTrace}; +use trace::trace::{Call, Create, VMTrace, RewardType}; /// Nonoperative tracer. Does not trace anything. pub struct NoopTracer; @@ -58,6 +58,9 @@ impl Tracer for NoopTracer { fn trace_suicide(&mut self, _address: Address, _balance: U256, _refund_address: Address) { } + fn trace_reward(&mut self, _: Address, _: U256, _: RewardType) { + } + fn subtracer(&self) -> Self { NoopTracer } diff --git a/ethcore/src/trace/types/filter.rs b/ethcore/src/trace/types/filter.rs index 1b2e2077a..3abdb7143 100644 --- a/ethcore/src/trace/types/filter.rs +++ b/ethcore/src/trace/types/filter.rs @@ -113,7 +113,7 @@ impl Filter { 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); @@ -128,7 +128,10 @@ impl Filter { let from_matches = self.from_address.matches(&suicide.address); let to_matches = self.to_address.matches(&suicide.refund_address); from_matches && to_matches - } + }, + Action::Reward(ref reward) => { + self.to_address.matches(&reward.author) + }, } } } @@ -138,9 +141,9 @@ mod tests { use util::Address; use util::sha3::Hashable; use bloomable::Bloomable; - use trace::trace::{Action, Call, Res, Create, CreateResult, Suicide}; + use trace::trace::{Action, Call, Res, Create, CreateResult, Suicide, Reward}; use trace::flat::FlatTrace; - use trace::{Filter, AddressesFilter, TraceError}; + use trace::{Filter, AddressesFilter, TraceError, RewardType}; use evm::CallType; #[test] @@ -341,5 +344,24 @@ mod tests { assert!(f4.matches(&trace)); assert!(f5.matches(&trace)); assert!(!f6.matches(&trace)); + + let trace = FlatTrace { + action: Action::Reward(Reward { + author: 2.into(), + value: 100.into(), + reward_type: RewardType::Block, + }), + result: Res::None, + trace_address: vec![].into_iter().collect(), + 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/types/flat.rs b/ethcore/src/trace/types/flat.rs index 8b65f1f4c..138d69f03 100644 --- a/ethcore/src/trace/types/flat.rs +++ b/ethcore/src/trace/types/flat.rs @@ -138,8 +138,9 @@ impl Into> for FlatBlockTraces { mod tests { use rlp::*; use super::{FlatBlockTraces, FlatTransactionTraces, FlatTrace}; - use trace::trace::{Action, Res, CallResult, Call, Suicide}; + use trace::trace::{Action, Res, CallResult, Call, Suicide, Reward}; use evm::CallType; + use trace::RewardType; #[test] fn encode_flat_transaction_traces() { @@ -214,9 +215,32 @@ mod tests { subtraces: 0, }; + let flat_trace3 = FlatTrace { + action: Action::Reward(Reward { + author: "412fda7643b37d436cb40628f6dbbb80a07267ed".parse().unwrap(), + value: 10.into(), + reward_type: RewardType::Uncle, + }), + result: Res::None, + trace_address: vec![0].into_iter().collect(), + subtraces: 0, + }; + + let flat_trace4 = FlatTrace { + action: Action::Reward(Reward { + author: "412fda7643b37d436cb40628f6dbbb80a07267ed".parse().unwrap(), + value: 10.into(), + reward_type: RewardType::Block, + }), + result: Res::None, + trace_address: vec![0].into_iter().collect(), + subtraces: 0, + }; + let block_traces = FlatBlockTraces(vec![ FlatTransactionTraces(vec![flat_trace]), - FlatTransactionTraces(vec![flat_trace1, flat_trace2]) + FlatTransactionTraces(vec![flat_trace1, flat_trace2]), + FlatTransactionTraces(vec![flat_trace3, flat_trace4]) ]); let encoded = ::rlp::encode(&block_traces); diff --git a/ethcore/src/trace/types/localized.rs b/ethcore/src/trace/types/localized.rs index 39a4b08cc..2d4850a8a 100644 --- a/ethcore/src/trace/types/localized.rs +++ b/ethcore/src/trace/types/localized.rs @@ -34,9 +34,9 @@ pub struct LocalizedTrace { /// [index in root, index in first CALL, index in second CALL, ...] pub trace_address: Vec, /// Transaction number within the block. - pub transaction_number: usize, + pub transaction_number: Option, /// Signed transaction hash. - pub transaction_hash: H256, + pub transaction_hash: Option, /// Block number. pub block_number: BlockNumber, /// Block hash. diff --git a/ethcore/src/trace/types/trace.rs b/ethcore/src/trace/types/trace.rs index 5fa0260c6..01c5c4b43 100644 --- a/ethcore/src/trace/types/trace.rs +++ b/ethcore/src/trace/types/trace.rs @@ -128,6 +128,77 @@ impl Create { } } +/// Reward type. +#[derive(Debug, PartialEq, Clone)] +#[cfg_attr(feature = "ipc", binary)] +pub enum RewardType { + /// Block + Block, + /// Uncle + Uncle, +} + +impl Encodable for RewardType { + fn rlp_append(&self, s: &mut RlpStream) { + let v = match *self { + RewardType::Block => 0u32, + RewardType::Uncle => 1, + }; + Encodable::rlp_append(&v, s); + } +} + +impl Decodable for RewardType { + fn decode(rlp: &UntrustedRlp) -> Result { + rlp.as_val().and_then(|v| Ok(match v { + 0u32 => RewardType::Block, + 1 => RewardType::Uncle, + _ => return Err(DecoderError::Custom("Invalid value of RewardType item")), + })) + } +} + +/// Reward action +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "ipc", binary)] +pub struct Reward { + /// Author's address. + pub author: Address, + /// Reward amount. + pub value: U256, + /// Reward type. + pub reward_type: RewardType, +} + +impl Reward { + /// Return reward action bloom. + pub fn bloom(&self) -> LogBloom { + LogBloom::from_bloomed(&self.author.sha3()) + } +} + +impl Encodable for Reward { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(3); + s.append(&self.author); + s.append(&self.value); + s.append(&self.reward_type); + } +} + +impl Decodable for Reward { + fn decode(rlp: &UntrustedRlp) -> Result { + let res = Reward { + author: rlp.val_at(0)?, + value: rlp.val_at(1)?, + reward_type: rlp.val_at(2)?, + }; + + Ok(res) + } +} + + /// Suicide action. #[derive(Debug, Clone, PartialEq, RlpEncodable, RlpDecodable)] #[cfg_attr(feature = "ipc", binary)] @@ -158,6 +229,8 @@ pub enum Action { Create(Create), /// Suicide. Suicide(Suicide), + /// Reward + Reward(Reward), } impl Encodable for Action { @@ -175,7 +248,12 @@ impl Encodable for Action { Action::Suicide(ref suicide) => { s.append(&2u8); s.append(suicide); + }, + Action::Reward(ref reward) => { + s.append(&3u8); + s.append(reward); } + } } } @@ -187,6 +265,7 @@ impl Decodable for Action { 0 => rlp.val_at(1).map(Action::Call), 1 => rlp.val_at(1).map(Action::Create), 2 => rlp.val_at(1).map(Action::Suicide), + 3 => rlp.val_at(1).map(Action::Reward), _ => Err(DecoderError::Custom("Invalid action type.")), } } @@ -199,6 +278,7 @@ impl Action { Action::Call(ref call) => call.bloom(), Action::Create(ref create) => create.bloom(), Action::Suicide(ref suicide) => suicide.bloom(), + Action::Reward(ref reward) => reward.bloom(), } } } diff --git a/json/src/spec/params.rs b/json/src/spec/params.rs index de33039c4..b90eef5ea 100644 --- a/json/src/spec/params.rs +++ b/json/src/spec/params.rs @@ -102,6 +102,9 @@ pub struct Params { pub block_reward: Option, /// See `CommonParams` docs. pub registrar: Option
, + /// Apply reward flag + #[serde(rename="applyReward")] + pub apply_reward: Option, /// Node permission contract address. #[serde(rename="nodePermissionContract")] pub node_permission_contract: Option
, diff --git a/rpc/src/v1/tests/mocked/traces.rs b/rpc/src/v1/tests/mocked/traces.rs index cd3ae3666..71b0ea3bb 100644 --- a/rpc/src/v1/tests/mocked/traces.rs +++ b/rpc/src/v1/tests/mocked/traces.rs @@ -47,8 +47,8 @@ fn io() -> Tester { result: Res::None, subtraces: 0, trace_address: vec![0], - transaction_number: 0, - transaction_hash: 5.into(), + transaction_number: Some(0), + transaction_hash: Some(5.into()), block_number: 10, block_hash: 10.into(), }]); diff --git a/rpc/src/v1/types/trace.rs b/rpc/src/v1/types/trace.rs index f9cf0bb40..e09d95932 100644 --- a/rpc/src/v1/types/trace.rs +++ b/rpc/src/v1/types/trace.rs @@ -299,6 +299,49 @@ impl From for Call { } } +/// Reward type. +#[derive(Debug, Serialize)] +pub enum RewardType { + /// Block + #[serde(rename="block")] + Block, + /// Uncle + #[serde(rename="uncle")] + Uncle, +} + +impl From for RewardType { + fn from(c: trace::RewardType) -> Self { + match c { + trace::RewardType::Block => RewardType::Block, + trace::RewardType::Uncle => RewardType::Uncle, + } + } +} + + +/// Reward action +#[derive(Debug, Serialize)] +pub struct Reward { + /// Author's address. + pub author: H160, + /// Reward amount. + pub value: U256, + /// Reward type. + #[serde(rename="rewardType")] + pub reward_type: RewardType, +} + +impl From for Reward { + fn from(r: trace::Reward) -> Self { + Reward { + author: r.author.into(), + value: r.value.into(), + reward_type: r.reward_type.into(), + } + } +} + /// Suicide #[derive(Debug, Serialize)] pub struct Suicide { @@ -330,6 +373,8 @@ pub enum Action { Create(Create), /// Suicide Suicide(Suicide), + /// Reward + Reward(Reward), } impl From for Action { @@ -338,6 +383,7 @@ impl From for Action { trace::Action::Call(call) => Action::Call(call.into()), trace::Action::Create(create) => Action::Create(create.into()), trace::Action::Suicide(suicide) => Action::Suicide(suicide.into()), + trace::Action::Reward(reward) => Action::Reward(reward.into()), } } } @@ -422,9 +468,9 @@ pub struct LocalizedTrace { /// Subtraces subtraces: usize, /// Transaction position - transaction_position: usize, + transaction_position: Option, /// Transaction hash - transaction_hash: H256, + transaction_hash: Option, /// Block Number block_number: u64, /// Block Hash @@ -449,6 +495,10 @@ impl Serialize for LocalizedTrace { struc.serialize_field("type", "suicide")?; struc.serialize_field("action", suicide)?; }, + Action::Reward(ref reward) => { + struc.serialize_field("type", "reward")?; + struc.serialize_field("action", reward)?; + }, } match self.result { @@ -477,8 +527,8 @@ impl From for LocalizedTrace { result: t.result.into(), trace_address: t.trace_address.into_iter().map(Into::into).collect(), subtraces: t.subtraces.into(), - transaction_position: t.transaction_number.into(), - transaction_hash: t.transaction_hash.into(), + transaction_position: t.transaction_number.map(Into::into), + transaction_hash: t.transaction_hash.map(Into::into), block_number: t.block_number.into(), block_hash: t.block_hash.into(), } @@ -516,6 +566,10 @@ impl Serialize for Trace { struc.serialize_field("type", "suicide")?; struc.serialize_field("action", suicide)?; }, + Action::Reward(ref reward) => { + struc.serialize_field("type", "reward")?; + struc.serialize_field("action", reward)?; + }, } match self.result { @@ -607,8 +661,8 @@ mod tests { }), trace_address: vec![10], subtraces: 1, - transaction_position: 11, - transaction_hash: 12.into(), + transaction_position: Some(11), + transaction_hash: Some(12.into()), block_number: 13, block_hash: 14.into(), }; @@ -630,8 +684,8 @@ mod tests { result: Res::FailedCall(TraceError::OutOfGas), trace_address: vec![10], subtraces: 1, - transaction_position: 11, - transaction_hash: 12.into(), + transaction_position: Some(11), + transaction_hash: Some(12.into()), block_number: 13, block_hash: 14.into(), }; @@ -655,8 +709,8 @@ mod tests { }), trace_address: vec![10], subtraces: 1, - transaction_position: 11, - transaction_hash: 12.into(), + transaction_position: Some(11), + transaction_hash: Some(12.into()), block_number: 13, block_hash: 14.into(), }; @@ -676,8 +730,8 @@ mod tests { result: Res::FailedCreate(TraceError::OutOfGas), trace_address: vec![10], subtraces: 1, - transaction_position: 11, - transaction_hash: 12.into(), + transaction_position: Some(11), + transaction_hash: Some(12.into()), block_number: 13, block_hash: 14.into(), }; @@ -696,8 +750,8 @@ mod tests { result: Res::None, trace_address: vec![10], subtraces: 1, - transaction_position: 11, - transaction_hash: 12.into(), + transaction_position: Some(11), + transaction_hash: Some(12.into()), block_number: 13, block_hash: 14.into(), }; @@ -705,6 +759,26 @@ mod tests { assert_eq!(serialized, r#"{"type":"suicide","action":{"address":"0x0000000000000000000000000000000000000004","refundAddress":"0x0000000000000000000000000000000000000006","balance":"0x7"},"result":null,"traceAddress":[10],"subtraces":1,"transactionPosition":11,"transactionHash":"0x000000000000000000000000000000000000000000000000000000000000000c","blockNumber":13,"blockHash":"0x000000000000000000000000000000000000000000000000000000000000000e"}"#); } + #[test] + fn test_trace_reward_serialize() { + let t = LocalizedTrace { + action: Action::Reward(Reward { + author: 4.into(), + value: 6.into(), + reward_type: RewardType::Block, + }), + result: Res::None, + trace_address: vec![10], + subtraces: 1, + transaction_position: None, + transaction_hash: None, + block_number: 13, + block_hash: 14.into(), + }; + let serialized = serde_json::to_string(&t).unwrap(); + assert_eq!(serialized, r#"{"type":"reward","action":{"author":"0x0000000000000000000000000000000000000004","value":"0x6","rewardType":"block"},"result":null,"traceAddress":[10],"subtraces":1,"transactionPosition":null,"transactionHash":null,"blockNumber":13,"blockHash":"0x000000000000000000000000000000000000000000000000000000000000000e"}"#); + } + #[test] fn test_vmtrace_serialize() { let t = VMTrace {