From c31eab53eabd92169434e9dac3f76d02c8b280cd Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 1 Sep 2016 14:12:26 +0200 Subject: [PATCH 001/106] add non renewing timer --- util/io/src/service.rs | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/util/io/src/service.rs b/util/io/src/service.rs index a47e84e56..c75efbdb6 100644 --- a/util/io/src/service.rs +++ b/util/io/src/service.rs @@ -53,6 +53,7 @@ pub enum IoMessage where Message: Send + Clone + Sized { handler_id: HandlerId, token: TimerToken, delay: u64, + once: bool, }, RemoveTimer { handler_id: HandlerId, @@ -89,12 +90,24 @@ impl IoContext where Message: Send + Clone + 'static { } } - /// Register a new IO timer. 'IoHandler::timeout' will be called with the token. + /// Register a new recurring IO timer. 'IoHandler::timeout' will be called with the token. pub fn register_timer(&self, token: TimerToken, ms: u64) -> Result<(), IoError> { try!(self.channel.send_io(IoMessage::AddTimer { token: token, delay: ms, handler_id: self.handler, + once: false, + })); + Ok(()) + } + + /// Register a new IO timer once. 'IoHandler::timeout' will be called with the token. + pub fn register_timer_once(&self, token: TimerToken, ms: u64) -> Result<(), IoError> { + try!(self.channel.send_io(IoMessage::AddTimer { + token: token, + delay: ms, + handler_id: self.handler, + once: true, })); Ok(()) } @@ -160,6 +173,7 @@ impl IoContext where Message: Send + Clone + 'static { struct UserTimer { delay: u64, timeout: Timeout, + once: bool, } /// Root IO handler. Manages user handlers, messages and IO timers. @@ -228,8 +242,14 @@ impl Handler for IoManager where Message: Send + Clone + Sync let handler_index = token.as_usize() / TOKENS_PER_HANDLER; let token_id = token.as_usize() % TOKENS_PER_HANDLER; if let Some(handler) = self.handlers.get(handler_index) { - if let Some(timer) = self.timers.read().get(&token.as_usize()) { - event_loop.timeout_ms(token, timer.delay).expect("Error re-registering user timer"); + let option = self.timers.read().get(&token.as_usize()).cloned(); + if let Some(timer) = option { + if timer.once { + self.timers.write().remove(&token_id); + event_loop.clear_timeout(timer.timeout); + } else { + event_loop.timeout_ms(token, timer.delay).expect("Error re-registering user timer"); + } self.worker_channel.push(Work { work_type: WorkType::Timeout, token: token_id, handler: handler.clone(), handler_id: handler_index }); self.work_ready.notify_all(); } @@ -257,10 +277,10 @@ impl Handler for IoManager where Message: Send + Clone + Sync event_loop.clear_timeout(timer.timeout); } }, - IoMessage::AddTimer { handler_id, token, delay } => { + IoMessage::AddTimer { handler_id, token, delay, once } => { let timer_id = token + handler_id * TOKENS_PER_HANDLER; let timeout = event_loop.timeout_ms(Token(timer_id), delay).expect("Error registering user timer"); - self.timers.write().insert(timer_id, UserTimer { delay: delay, timeout: timeout }); + self.timers.write().insert(timer_id, UserTimer { delay: delay, timeout: timeout, once: once }); }, IoMessage::RemoveTimer { handler_id, token } => { let timer_id = token + handler_id * TOKENS_PER_HANDLER; From 3a68fab066d1bccfd35bbe9c24b7df83848b6f20 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 8 Sep 2016 12:12:24 +0200 Subject: [PATCH 002/106] new simple authority engine --- Cargo.lock | 2 + ethcore/src/engines/authority_round.rs | 320 +++++++++++++++++++++++++ ethcore/src/engines/mod.rs | 2 + 3 files changed, 324 insertions(+) create mode 100644 ethcore/src/engines/authority_round.rs diff --git a/Cargo.lock b/Cargo.lock index 2ebf9b854..d9e5ced66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -569,6 +569,7 @@ name = "ethkey" version = "0.2.0" dependencies = [ "bigint 0.1.0", + "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", "eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", @@ -580,6 +581,7 @@ dependencies = [ name = "ethstore" version = "0.1.0" dependencies = [ + "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", "ethcrypto 0.1.0", "ethkey 0.2.0", "itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs new file mode 100644 index 000000000..1acc87068 --- /dev/null +++ b/ethcore/src/engines/authority_round.rs @@ -0,0 +1,320 @@ +// 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 . + +//! A blockchain engine that supports a basic, non-BFT proof-of-authority. + +use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; +use std::sync::Weak; +use common::*; +use ethkey::{recover, public_to_address}; +use rlp::{UntrustedRlp, View, encode, decode}; +use account_provider::AccountProvider; +use block::*; +use spec::CommonParams; +use engines::Engine; +use evm::Schedule; +use ethjson; +use io::{IoContext, IoHandler, TimerToken, IoService}; +use time::get_time; + +/// `AuthorityRound` params. +#[derive(Debug, PartialEq)] +pub struct AuthorityRoundParams { + /// Gas limit divisor. + pub gas_limit_bound_divisor: U256, + /// Time to wait before next block or authority switching. + pub step_duration: u64, + /// Valid authorities. + pub authorities: Vec
, + /// Number of authorities. + pub authority_n: usize, +} + +impl From for AuthorityRoundParams { + fn from(p: ethjson::spec::AuthorityRoundParams) -> Self { + AuthorityRoundParams { + gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), + step_duration: p.step_duration.into(), + authority_n: p.authorities.len(), + authorities: p.authorities.into_iter().map(Into::into).collect::>(), + } + } +} + +/// Engine using `AuthorityRound` proof-of-work consensus algorithm, suitable for Ethereum +/// mainnet chains in the Olympic, Frontier and Homestead eras. +pub struct AuthorityRound { + params: CommonParams, + our_params: AuthorityRoundParams, + builtins: BTreeMap, + transistion_service: IoService, + step: AtomicUsize, +} + +impl AuthorityRound { + /// Create a new instance of AuthorityRound engine + pub fn new(params: CommonParams, our_params: AuthorityRoundParams, builtins: BTreeMap) -> Arc { + let engine = Arc::new( + AuthorityRound { + params: params, + our_params: our_params, + builtins: builtins, + transistion_service: IoService::::start().expect("Error creating engine timeout service"), + step: AtomicUsize::new(0), + }); + let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; + engine.transistion_service.register_handler(Arc::new(handler)).expect("Error creating engine timeout service"); + engine + } + + fn proposer(&self) -> &Address { + let ref p = self.our_params; + p.authorities.get(self.step.load(AtomicOrdering::Relaxed)%p.authority_n).unwrap() + } + + fn is_proposer(&self, address: &Address) -> bool { + self.proposer() == address + } +} + +struct TransitionHandler { + engine: Weak, +} + +#[derive(Clone)] +struct BlockArrived; + +const ENGINE_TIMEOUT_TOKEN: TimerToken = 0; + +impl IoHandler for TransitionHandler { + fn initialize(&self, io: &IoContext) { + if let Some(engine) = self.engine.upgrade() { + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.our_params.step_duration).expect("Error registering engine timeout"); + } + } + + fn timeout(&self, io: &IoContext, timer: TimerToken) { + if timer == ENGINE_TIMEOUT_TOKEN { + if let Some(engine) = self.engine.upgrade() { + engine.step.fetch_add(1, AtomicOrdering::Relaxed); + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.our_params.step_duration).expect("Failed to restart consensus step timer.") + } + } + } + + fn message(&self, io: &IoContext, _net_message: &BlockArrived) { + if let Some(engine) = self.engine.upgrade() { + println!("Message: {:?}", get_time().sec); + engine.step.fetch_add(1, AtomicOrdering::Relaxed); + io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to restart consensus step timer."); + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.our_params.step_duration).expect("Failed to restart consensus step timer.") + } + } +} + +impl Engine for AuthorityRound { + fn name(&self) -> &str { "AuthorityRound" } + fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } + // One field - the proposer signature. + fn seal_fields(&self) -> usize { 1 } + + fn params(&self) -> &CommonParams { &self.params } + fn builtins(&self) -> &BTreeMap { &self.builtins } + + /// Additional engine-specific information for the user/developer concerning `header`. + fn extra_info(&self, _header: &Header) -> HashMap { hash_map!["signature".to_owned() => "TODO".to_owned()] } + + fn schedule(&self, _env_info: &EnvInfo) -> Schedule { + Schedule::new_homestead() + } + + fn populate_from_parent(&self, header: &mut Header, parent: &Header, gas_floor_target: U256, _gas_ceil_target: U256) { + header.set_difficulty(parent.difficulty().clone()); + header.set_gas_limit({ + let gas_limit = parent.gas_limit().clone(); + let bound_divisor = self.our_params.gas_limit_bound_divisor; + if gas_limit < gas_floor_target { + min(gas_floor_target, gas_limit + gas_limit / bound_divisor - 1.into()) + } else { + max(gas_floor_target, gas_limit - gas_limit / bound_divisor + 1.into()) + } + }); + } + + /// 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) {} + + /// Attempt to seal the block internally. + /// + /// This operation is synchronous and may (quite reasonably) not be available, in which `false` will + /// be returned. + fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { + if self.is_proposer(block.header().author()) { + if let Some(ap) = accounts { + let header = block.header(); + let message = header.bare_hash(); + // Account should be pernamently unlocked, otherwise sealing will fail. + if let Ok(signature) = ap.sign(*header.author(), message) { + return Some(vec![encode(&(&*signature as &[u8])).to_vec()]); + } else { + trace!(target: "authorityround", "generate_seal: FAIL: accounts secret key unavailable"); + } + } else { + trace!(target: "authorityround", "generate_seal: FAIL: accounts not provided"); + } + } + None + } + + /// Check the number of seal fields. + fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { + if header.seal().len() != self.seal_fields() { + return Err(From::from(BlockError::InvalidSealArity( + Mismatch { expected: self.seal_fields(), found: header.seal().len() } + ))); + } + Ok(()) + } + + /// Check if the signature belongs to the correct proposer. + fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { + let sig = try!(UntrustedRlp::new(&header.seal()[0]).as_val::()); + let signer = public_to_address(&try!(recover(&sig.into(), &header.bare_hash()))); + if self.is_proposer(&signer) { + Ok(()) + } else { + try!(Err(BlockError::InvalidSeal)) + } + } + + fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { + // Don't calculate difficulty for genesis blocks. + if header.number() == 0 { + return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))); + } + + // Check difficulty is correct given the two timestamps. + if header.difficulty() != parent.difficulty() { + return Err(From::from(BlockError::InvalidDifficulty(Mismatch { expected: *parent.difficulty(), found: *header.difficulty() }))) + } + let gas_limit_divisor = self.our_params.gas_limit_bound_divisor; + let min_gas = parent.gas_limit().clone() - parent.gas_limit().clone() / gas_limit_divisor; + let max_gas = parent.gas_limit().clone() + parent.gas_limit().clone() / gas_limit_divisor; + if header.gas_limit() <= &min_gas || header.gas_limit() >= &max_gas { + return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(max_gas), found: header.gas_limit().clone() }))); + } + Ok(()) + } + + fn verify_transaction_basic(&self, t: &SignedTransaction, _header: &Header) -> result::Result<(), Error> { + try!(t.check_low_s()); + Ok(()) + } + + fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { + t.sender().map(|_|()) // Perform EC recovery and cache sender + } +} + +impl Header { + /// Get the none field of the header. + pub fn signature(&self) -> H520 { + decode(&self.seal()[0]) + } +} + +#[cfg(test)] +mod tests { + use common::*; + use rlp::{UntrustedRlp, View, encode, decode}; + use block::*; + use tests::helpers::*; + use account_provider::AccountProvider; + use spec::Spec; + + /// Create a new test chain spec with `AuthorityRound` consensus engine. + fn new_test_authority() -> Spec { + let bytes: &[u8] = include_bytes!("../../res/authority_round.json"); + Spec::load(bytes).expect("invalid chain spec") + } + + #[test] + fn has_valid_metadata() { + let engine = new_test_authority().engine; + assert!(!engine.name().is_empty()); + assert!(engine.version().major >= 1); + } + + #[test] + fn can_return_schedule() { + let engine = new_test_authority().engine; + let schedule = engine.schedule(&EnvInfo { + number: 10000000, + author: 0.into(), + timestamp: 0, + difficulty: 0.into(), + last_hashes: Arc::new(vec![]), + gas_used: 0.into(), + gas_limit: 0.into(), + }); + + assert!(schedule.stack_limit > 0); + } + + #[test] + fn can_do_seal_verification_fail() { + let engine = new_test_authority().engine; + let header: Header = Header::default(); + + let verify_result = engine.verify_block_basic(&header, None); + + match verify_result { + Err(Error::Block(BlockError::InvalidSealArity(_))) => {}, + Err(_) => { panic!("should be block seal-arity mismatch error (got {:?})", verify_result); }, + _ => { panic!("Should be error, got Ok"); }, + } + } + + #[test] + fn can_do_signature_verification_fail() { + let engine = new_test_authority().engine; + let mut header: Header = Header::default(); + header.set_seal(vec![encode(&H520::default()).to_vec()]); + + let verify_result = engine.verify_block_unordered(&header, None); + assert!(verify_result.is_err()); + } + + #[test] + fn can_generate_seal() { + let tap = AccountProvider::transient_provider(); + let addr = tap.insert_account("".sha3(), "").unwrap(); + tap.unlock_account_permanently(addr, "".into()).unwrap(); + + let spec = new_test_authority(); + let engine = &*spec.engine; + let genesis_header = spec.genesis_header(); + let mut db_result = get_temp_journal_db(); + let mut db = db_result.take(); + spec.ensure_db_good(db.as_hashdb_mut()).unwrap(); + let last_hashes = Arc::new(vec![genesis_header.hash()]); + let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, addr, (3141562.into(), 31415620.into()), vec![]).unwrap(); + let b = b.close_and_lock(); + let seal = engine.generate_seal(b.block(), Some(&tap)).unwrap(); + assert!(b.try_seal(engine, seal).is_ok()); + } +} diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 6414ba5e4..0e435543a 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -19,10 +19,12 @@ mod null_engine; mod instant_seal; mod basic_authority; +mod authority_round; pub use self::null_engine::NullEngine; pub use self::instant_seal::InstantSeal; pub use self::basic_authority::BasicAuthority; +pub use self::authority_round::AuthorityRound; use common::*; use account_provider::AccountProvider; From 747898d8e761b5b84583b0cb81d14c59ea683523 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 8 Sep 2016 12:28:59 +0200 Subject: [PATCH 003/106] add new engine to spec --- ethcore/res/authority_round.json | 39 ++++++++++++ ...st_authority.json => basic_authority.json} | 2 +- ethcore/src/engines/authority_round.rs | 2 +- ethcore/src/engines/basic_authority.rs | 7 --- ethcore/src/spec/spec.rs | 3 +- json/src/spec/authority_round.rs | 59 +++++++++++++++++++ json/src/spec/engine.rs | 3 + json/src/spec/mod.rs | 2 + 8 files changed, 107 insertions(+), 10 deletions(-) create mode 100644 ethcore/res/authority_round.json rename ethcore/res/{test_authority.json => basic_authority.json} (98%) create mode 100644 json/src/spec/authority_round.rs diff --git a/ethcore/res/authority_round.json b/ethcore/res/authority_round.json new file mode 100644 index 000000000..bff2c9724 --- /dev/null +++ b/ethcore/res/authority_round.json @@ -0,0 +1,39 @@ +{ + "name": "TestAuthorityRound", + "engine": { + "BasicAuthority": { + "params": { + "gasLimitBoundDivisor": "0x0400", + "stepDuration": "0x0d", + "authorities" : ["0x9cce34f7ab185c7aba1b7c8140d620b4bda941d6"] + } + } + }, + "params": { + "accountStartNonce": "0x0100000", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID" : "0x69" + }, + "genesis": { + "seal": { + "generic": { + "fields": 1, + "rlp": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa" + } + }, + "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 } } } }, + "9cce34f7ab185c7aba1b7c8140d620b4bda941d6": { "balance": "1606938044258990275541962092341162602522202993782792835301376", "nonce": "1048576" } + } +} diff --git a/ethcore/res/test_authority.json b/ethcore/res/basic_authority.json similarity index 98% rename from ethcore/res/test_authority.json rename to ethcore/res/basic_authority.json index 1ab482863..51276d487 100644 --- a/ethcore/res/test_authority.json +++ b/ethcore/res/basic_authority.json @@ -1,5 +1,5 @@ { - "name": "TestAuthority", + "name": "TestBasicAuthority", "engine": { "BasicAuthority": { "params": { diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 1acc87068..dedc2ae3a 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -240,7 +240,7 @@ impl Header { #[cfg(test)] mod tests { use common::*; - use rlp::{UntrustedRlp, View, encode, decode}; + use rlp::encode; use block::*; use tests::helpers::*; use account_provider::AccountProvider; diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 18dfeec46..fad06bb3c 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -171,13 +171,6 @@ impl Engine for BasicAuthority { } } -impl Header { - /// Get the none field of the header. - pub fn signature(&self) -> H520 { - ::rlp::decode(&self.seal()[0]) - } -} - #[cfg(test)] mod tests { use common::*; diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 58317e97b..a94761678 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -17,7 +17,7 @@ //! Parameters for a block chain. use common::*; -use engines::{Engine, NullEngine, InstantSeal, BasicAuthority}; +use engines::{Engine, NullEngine, InstantSeal, BasicAuthority, AuthorityRound}; use pod_state::*; use account_db::*; use super::genesis::Genesis; @@ -138,6 +138,7 @@ impl Spec { ethjson::spec::Engine::InstantSeal => Arc::new(InstantSeal::new(params, builtins)), ethjson::spec::Engine::Ethash(ethash) => Arc::new(ethereum::Ethash::new(params, From::from(ethash.params), builtins)), ethjson::spec::Engine::BasicAuthority(basic_authority) => Arc::new(BasicAuthority::new(params, From::from(basic_authority.params), builtins)), + ethjson::spec::Engine::AuthorityRound(authority_round) => AuthorityRound::new(params, From::from(authority_round.params), builtins), } } diff --git a/json/src/spec/authority_round.rs b/json/src/spec/authority_round.rs new file mode 100644 index 000000000..17d5f74cb --- /dev/null +++ b/json/src/spec/authority_round.rs @@ -0,0 +1,59 @@ +// 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 . + +//! Authority params deserialization. + +use uint::Uint; +use hash::Address; + +/// Authority params deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct AuthorityRoundParams { + /// Gas limit divisor. + #[serde(rename="gasLimitBoundDivisor")] + pub gas_limit_bound_divisor: Uint, + /// Block duration. + #[serde(rename="stepDuration")] + pub step_duration: Uint, + /// Valid authorities + pub authorities: Vec
, +} + +/// Authority engine deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct AuthorityRound { + /// Ethash params. + pub params: AuthorityRoundParams, +} + +#[cfg(test)] +mod tests { + use serde_json; + use spec::basic_authority::AuthorityRound; + + #[test] + fn basic_authority_deserialization() { + let s = r#"{ + "params": { + "gasLimitBoundDivisor": "0x0400", + "stepDuration": "0x02", + "authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + } + }"#; + + let _deserialized: AuthorityRound = serde_json::from_str(s).unwrap(); + } +} diff --git a/json/src/spec/engine.rs b/json/src/spec/engine.rs index 3813b1756..045813374 100644 --- a/json/src/spec/engine.rs +++ b/json/src/spec/engine.rs @@ -18,6 +18,7 @@ use spec::Ethash; use spec::BasicAuthority; +use spec::AuthorityRound; /// Engine deserialization. #[derive(Debug, PartialEq, Deserialize)] @@ -30,6 +31,8 @@ pub enum Engine { Ethash(Ethash), /// BasicAuthority engine. BasicAuthority(BasicAuthority), + /// AuthorityRound engine. + AuthorityRound(AuthorityRound), } #[cfg(test)] diff --git a/json/src/spec/mod.rs b/json/src/spec/mod.rs index f6c856b13..f9de30170 100644 --- a/json/src/spec/mod.rs +++ b/json/src/spec/mod.rs @@ -26,6 +26,7 @@ pub mod engine; pub mod state; pub mod ethash; pub mod basic_authority; +pub mod authority_round; pub use self::account::Account; pub use self::builtin::{Builtin, Pricing, Linear}; @@ -37,3 +38,4 @@ pub use self::engine::Engine; pub use self::state::State; pub use self::ethash::{Ethash, EthashParams}; pub use self::basic_authority::{BasicAuthority, BasicAuthorityParams}; +pub use self::authority_round::{AuthorityRound, AuthorityRoundParams}; From fc3d01ec719ceb9b4fe16229ad58f352952792fc Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 8 Sep 2016 16:27:54 +0200 Subject: [PATCH 004/106] add tests, fixes, simplifications --- ethcore/res/authority_round.json | 9 ++-- ethcore/src/engines/authority_round.rs | 57 +++++++++++++++++++------- ethcore/src/engines/basic_authority.rs | 2 +- json/src/spec/authority_round.rs | 2 +- 4 files changed, 50 insertions(+), 20 deletions(-) diff --git a/ethcore/res/authority_round.json b/ethcore/res/authority_round.json index bff2c9724..eccd5d5eb 100644 --- a/ethcore/res/authority_round.json +++ b/ethcore/res/authority_round.json @@ -1,11 +1,14 @@ { "name": "TestAuthorityRound", "engine": { - "BasicAuthority": { + "AuthorityRound": { "params": { "gasLimitBoundDivisor": "0x0400", - "stepDuration": "0x0d", - "authorities" : ["0x9cce34f7ab185c7aba1b7c8140d620b4bda941d6"] + "stepDuration": "0x03e8", + "authorities" : [ + "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", + "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" + ] } } }, diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index dedc2ae3a..787baccbf 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -19,7 +19,7 @@ use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use std::sync::Weak; use common::*; -use ethkey::{recover, public_to_address}; +use ethkey::verify_address; use rlp::{UntrustedRlp, View, encode, decode}; use account_provider::AccountProvider; use block::*; @@ -108,6 +108,7 @@ impl IoHandler for TransitionHandler { fn timeout(&self, io: &IoContext, timer: TimerToken) { if timer == ENGINE_TIMEOUT_TOKEN { + println!("timeout"); if let Some(engine) = self.engine.upgrade() { engine.step.fetch_add(1, AtomicOrdering::Relaxed); io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.our_params.step_duration).expect("Failed to restart consensus step timer.") @@ -163,12 +164,11 @@ impl Engine for AuthorityRound { /// This operation is synchronous and may (quite reasonably) not be available, in which `false` will /// be returned. fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { - if self.is_proposer(block.header().author()) { + let header = block.header(); + if self.is_proposer(header.author()) { if let Some(ap) = accounts { - let header = block.header(); - let message = header.bare_hash(); - // Account should be pernamently unlocked, otherwise sealing will fail. - if let Ok(signature) = ap.sign(*header.author(), message) { + // Account should be permanently unlocked, otherwise sealing will fail. + if let Ok(signature) = ap.sign(*header.author(), header.bare_hash()) { return Some(vec![encode(&(&*signature as &[u8])).to_vec()]); } else { trace!(target: "authorityround", "generate_seal: FAIL: accounts secret key unavailable"); @@ -181,7 +181,7 @@ impl Engine for AuthorityRound { } /// Check the number of seal fields. - fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { + fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { if header.seal().len() != self.seal_fields() { return Err(From::from(BlockError::InvalidSealArity( Mismatch { expected: self.seal_fields(), found: header.seal().len() } @@ -191,17 +191,16 @@ impl Engine for AuthorityRound { } /// Check if the signature belongs to the correct proposer. - fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { + fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { let sig = try!(UntrustedRlp::new(&header.seal()[0]).as_val::()); - let signer = public_to_address(&try!(recover(&sig.into(), &header.bare_hash()))); - if self.is_proposer(&signer) { + if try!(verify_address(self.proposer(), &sig.into(), &header.bare_hash())) { Ok(()) } else { try!(Err(BlockError::InvalidSeal)) } } - fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { + fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { // Don't calculate difficulty for genesis blocks. if header.number() == 0 { return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))); @@ -220,7 +219,7 @@ impl Engine for AuthorityRound { Ok(()) } - fn verify_transaction_basic(&self, t: &SignedTransaction, _header: &Header) -> result::Result<(), Error> { + fn verify_transaction_basic(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { try!(t.check_low_s()); Ok(()) } @@ -245,6 +244,8 @@ mod tests { use tests::helpers::*; use account_provider::AccountProvider; use spec::Spec; + use std::thread::sleep; + use std::time::Duration; /// Create a new test chain spec with `AuthorityRound` consensus engine. fn new_test_authority() -> Spec { @@ -276,7 +277,7 @@ mod tests { } #[test] - fn can_do_seal_verification_fail() { + fn verification_fails_on_short_seal() { let engine = new_test_authority().engine; let header: Header = Header::default(); @@ -302,8 +303,8 @@ mod tests { #[test] fn can_generate_seal() { let tap = AccountProvider::transient_provider(); - let addr = tap.insert_account("".sha3(), "").unwrap(); - tap.unlock_account_permanently(addr, "".into()).unwrap(); + let addr = tap.insert_account("1".sha3(), "1").unwrap(); + tap.unlock_account_permanently(addr, "1".into()).unwrap(); let spec = new_test_authority(); let engine = &*spec.engine; @@ -317,4 +318,30 @@ mod tests { let seal = engine.generate_seal(b.block(), Some(&tap)).unwrap(); assert!(b.try_seal(engine, seal).is_ok()); } + + #[test] + fn proposer_switching() { + let engine = new_test_authority().engine; + let mut header: Header = Header::default(); + let tap = AccountProvider::transient_provider(); + let addr = tap.insert_account("0".sha3(), "0").unwrap(); + + header.set_author(addr); + + let signature = tap.sign_with_password(addr, "0".into(), header.bare_hash()).unwrap(); + header.set_seal(vec![encode(&(&*signature as &[u8])).to_vec()]); + + // Wrong step. + assert!(engine.verify_block_unordered(&header, None).is_err()); + + sleep(Duration::from_millis(1000)); + + // Right step. + assert!(engine.verify_block_unordered(&header, None).is_ok()); + + sleep(Duration::from_millis(1000)); + + // Wrong step. + assert!(engine.verify_block_unordered(&header, None).is_err()); + } } diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index fad06bb3c..e9a4a6bcd 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -181,7 +181,7 @@ mod tests { /// Create a new test chain spec with `BasicAuthority` consensus engine. fn new_test_authority() -> Spec { - let bytes: &[u8] = include_bytes!("../../res/test_authority.json"); + let bytes: &[u8] = include_bytes!("../../res/basic_authority.json"); Spec::load(bytes).expect("invalid chain spec") } diff --git a/json/src/spec/authority_round.rs b/json/src/spec/authority_round.rs index 17d5f74cb..3d73ef1ef 100644 --- a/json/src/spec/authority_round.rs +++ b/json/src/spec/authority_round.rs @@ -42,7 +42,7 @@ pub struct AuthorityRound { #[cfg(test)] mod tests { use serde_json; - use spec::basic_authority::AuthorityRound; + use spec::authority_round::AuthorityRound; #[test] fn basic_authority_deserialization() { From 965dde822315a2b2555434967f3171b1e0e12b4f Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 9 Sep 2016 11:49:03 +0200 Subject: [PATCH 005/106] enable TestNet with custom spec --- ethcore/src/client/test_client.rs | 5 +++++ sync/src/tests/helpers.rs | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index b235b4b78..33a597745 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -102,6 +102,11 @@ impl TestBlockChainClient { /// Creates new test client. pub fn new() -> Self { let spec = Spec::new_test(); + TestBlockChainClient::new_with_spec(spec) + } + + /// Create test client with custom spec. + pub fn new_with_spec(spec: Spec) -> Self { let mut client = TestBlockChainClient { blocks: RwLock::new(HashMap::new()), numbers: RwLock::new(HashMap::new()), diff --git a/sync/src/tests/helpers.rs b/sync/src/tests/helpers.rs index cbed49eff..dd34ca276 100644 --- a/sync/src/tests/helpers.rs +++ b/sync/src/tests/helpers.rs @@ -19,6 +19,7 @@ use network::*; use tests::snapshot::*; use ethcore::client::{TestBlockChainClient, BlockChainClient}; use ethcore::header::BlockNumber; +use ethcore::spec::Spec; use ethcore::snapshot::SnapshotService; use sync_io::SyncIo; use chain::ChainSync; @@ -128,6 +129,24 @@ impl TestNet { net } + pub fn new_with_spec_file(n: usize, reader: R) -> TestNet where R: Read + Clone { + let mut net = TestNet { + peers: Vec::new(), + started: false, + }; + for _ in 0..n { + let chain = TestBlockChainClient::new_with_spec(Spec::load(reader.clone()).expect("Invalid spec file.")); + let sync = ChainSync::new(SyncConfig::default(), &chain); + net.peers.push(TestPeer { + sync: RwLock::new(sync), + snapshot_service: Arc::new(TestSnapshotService::new()), + chain: chain, + queue: VecDeque::new(), + }); + } + net + } + pub fn peer(&self, i: usize) -> &TestPeer { self.peers.get(i).unwrap() } From 4e75686ef83f45382ffad60288757bc62376508e Mon Sep 17 00:00:00 2001 From: keorn Date: Sat, 10 Sep 2016 14:41:41 +0200 Subject: [PATCH 006/106] separate block preparation methods --- ethcore/src/miner/miner.rs | 88 ++++++++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 28 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index c9d60f075..3bdc73de2 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -24,7 +24,7 @@ use views::{BlockView, HeaderView}; use state::State; use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics}; use executive::contract_address; -use block::{ClosedBlock, IsBlock, Block}; +use block::{ClosedBlock, SealedBlock, IsBlock, Block}; use error::*; use transaction::{Action, SignedTransaction}; use receipt::{Receipt, RichReceipt}; @@ -242,12 +242,7 @@ impl Miner { self.sealing_work.lock().queue.peek_last_ref().map(|b| b.base().clone()) } - /// Prepares new block for sealing including top transactions from queue. - #[cfg_attr(feature="dev", allow(match_same_arms))] - #[cfg_attr(feature="dev", allow(cyclomatic_complexity))] - fn prepare_sealing(&self, chain: &MiningBlockChainClient) { - trace!(target: "miner", "prepare_sealing: entering"); - + fn prepare_block(&self, chain: &MiningBlockChainClient) -> (ClosedBlock, Option) { { trace!(target: "miner", "recalibrating..."); let txq = self.transaction_queue.clone(); @@ -334,31 +329,44 @@ impl Miner { queue.remove_invalid(&hash, &fetch_account); } } + (block, original_work_hash) + } + + /// Attempts to perform internal sealing to return Ok(sealed), + /// Err(Some(block)) returns for unsuccesful sealing while Err(None) indicates misspecified engine. + fn seal_block_internally(&self, block: ClosedBlock) -> Result> { + trace!(target: "miner", "prepare_sealing: block has transaction - attempting internal seal."); + // block with transactions - see if we can seal immediately. + let s = self.engine.generate_seal(block.block(), match self.accounts { + Some(ref x) => Some(&**x), + None => None, + }); + if let Some(seal) = s { + trace!(target: "miner", "prepare_sealing: managed internal seal. importing..."); + block.lock().try_seal(&*self.engine, seal).or_else(|_| { + warn!("prepare_sealing: ERROR: try_seal failed when given internally generated seal. WTF?"); + Err(None) + }) + } else { + trace!(target: "miner", "prepare_sealing: unable to generate seal internally"); + Err(Some(block)) + } + } + + fn seal_and_import_block_internally(&self, chain: &MiningBlockChainClient) -> bool { + let (block, _) = self.prepare_block(chain); if !block.transactions().is_empty() { - trace!(target: "miner", "prepare_sealing: block has transaction - attempting internal seal."); - // block with transactions - see if we can seal immediately. - let s = self.engine.generate_seal(block.block(), match self.accounts { - Some(ref x) => Some(&**x), - None => None, - }); - if let Some(seal) = s { - trace!(target: "miner", "prepare_sealing: managed internal seal. importing..."); - if let Ok(sealed) = block.lock().try_seal(&*self.engine, seal) { - if let Ok(_) = chain.import_block(sealed.rlp_bytes()) { - trace!(target: "miner", "prepare_sealing: sealed internally and imported. leaving."); - } else { - warn!("prepare_sealing: ERROR: could not import internally sealed block. WTF?"); - } - } else { - warn!("prepare_sealing: ERROR: try_seal failed when given internally generated seal. WTF?"); + if let Ok(sealed) = self.seal_block_internally(block) { + if chain.import_block(sealed.rlp_bytes()).is_ok() { + return true; } - return; - } else { - trace!(target: "miner", "prepare_sealing: unable to generate seal internally"); } } + false + } + fn prepare_work(&self, block: ClosedBlock, original_work_hash: Option) { let (work, is_new) = { let mut sealing_work = self.sealing_work.lock(); let last_work_hash = sealing_work.queue.peek_last_ref().map(|pb| pb.block().fields().header.hash()); @@ -386,6 +394,30 @@ impl Miner { } } + /// Prepares new block for sealing including top transactions from queue. + #[cfg_attr(feature="dev", allow(match_same_arms))] + #[cfg_attr(feature="dev", allow(cyclomatic_complexity))] + fn prepare_block_and_work(&self, chain: &MiningBlockChainClient) { + trace!(target: "miner", "prepare_sealing: entering"); + + let (block, original_work_hash) = self.prepare_block(chain); + + let block = if !block.transactions().is_empty() { + let block_opt = self.seal_block_internally(block).map(|sealed| + match chain.import_block(sealed.rlp_bytes()) { + Ok(_) => trace!(target: "miner", "prepare_sealing: sealed internally and imported. leaving."), + _ => warn!("prepare_sealing: ERROR: could not import internally sealed block. WTF?"), + } + ).err().unwrap_or(None); + if block_opt.is_none() { return; } + block_opt.unwrap() + } else { + block + }; + + self.prepare_work(block, original_work_hash); + } + fn update_gas_limit(&self, chain: &MiningBlockChainClient) { let gas_limit = HeaderView::new(&chain.best_block_header()).gas_limit(); let mut queue = self.transaction_queue.lock(); @@ -411,7 +443,7 @@ impl Miner { // | NOTE Code below requires transaction_queue and sealing_work locks. | // | Make sure to release the locks before calling that method. | // -------------------------------------------------------------------------- - self.prepare_sealing(chain); + self.prepare_block_and_work(chain); } let mut sealing_block_last_request = self.sealing_block_last_request.lock(); let best_number = chain.chain_info().best_block_number; @@ -804,7 +836,7 @@ impl MinerService for Miner { // | NOTE Code below requires transaction_queue and sealing_work locks. | // | Make sure to release the locks before calling that method. | // -------------------------------------------------------------------------- - self.prepare_sealing(chain); + self.prepare_block_and_work(chain); } } From cadca6403acbd2d54391dc0e2950603e924262c7 Mon Sep 17 00:00:00 2001 From: keorn Date: Sun, 11 Sep 2016 13:23:32 +0200 Subject: [PATCH 007/106] Split internal sealing from work sealing, add cli option --- ethcore/src/miner/miner.rs | 68 +++++++++++++++++--------------------- parity/cli.rs | 4 +++ parity/configuration.rs | 1 + rpc/src/v1/tests/eth.rs | 1 + 4 files changed, 36 insertions(+), 38 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 3bdc73de2..b79395c0c 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -60,6 +60,8 @@ pub struct MinerOptions { pub reseal_on_own_tx: bool, /// Minimum period between transaction-inspired reseals. pub reseal_min_period: Duration, + /// Seal blocks internally. + pub internal_sealing: bool, /// Maximum amount of gas to bother considering for block insertion. pub tx_gas_limit: U256, /// Maximum size of the transaction queue. @@ -79,6 +81,7 @@ impl Default for MinerOptions { force_sealing: false, reseal_on_external_tx: false, reseal_on_own_tx: true, + internal_sealing: false, tx_gas_limit: !U256::zero(), tx_queue_size: 1024, pending_set: PendingSet::AlwaysQueue, @@ -244,13 +247,13 @@ impl Miner { fn prepare_block(&self, chain: &MiningBlockChainClient) -> (ClosedBlock, Option) { { - trace!(target: "miner", "recalibrating..."); + trace!(target: "miner", "prepare_block: recalibrating..."); let txq = self.transaction_queue.clone(); self.gas_pricer.lock().recalibrate(move |price| { - trace!(target: "miner", "Got gas price! {}", price); + trace!(target: "miner", "prepare_block: Got gas price! {}", price); txq.lock().set_minimal_gas_price(price); }); - trace!(target: "miner", "done recalibration."); + trace!(target: "miner", "prepare_block: done recalibration."); } let (transactions, mut open_block, original_work_hash) = { @@ -268,13 +271,13 @@ impl Miner { */ let open_block = match sealing_work.queue.pop_if(|b| b.block().fields().header.parent_hash() == &best_hash) { Some(old_block) => { - trace!(target: "miner", "Already have previous work; updating and returning"); + trace!(target: "miner", "prepare_block: Already have previous work; updating and returning"); // add transactions to old_block old_block.reopen(&*self.engine) } None => { // block not found - create it. - trace!(target: "miner", "No existing work - making new block"); + trace!(target: "miner", "prepare_block: No existing work - making new block"); chain.prepare_open_block( self.author(), (self.gas_floor_target(), self.gas_ceil_target()), @@ -335,20 +338,19 @@ impl Miner { /// Attempts to perform internal sealing to return Ok(sealed), /// Err(Some(block)) returns for unsuccesful sealing while Err(None) indicates misspecified engine. fn seal_block_internally(&self, block: ClosedBlock) -> Result> { - trace!(target: "miner", "prepare_sealing: block has transaction - attempting internal seal."); - // block with transactions - see if we can seal immediately. + trace!(target: "miner", "seal_block_internally: block has transaction - attempting internal seal."); let s = self.engine.generate_seal(block.block(), match self.accounts { Some(ref x) => Some(&**x), None => None, }); if let Some(seal) = s { - trace!(target: "miner", "prepare_sealing: managed internal seal. importing..."); + trace!(target: "miner", "seal_block_internally: managed internal seal. importing..."); block.lock().try_seal(&*self.engine, seal).or_else(|_| { warn!("prepare_sealing: ERROR: try_seal failed when given internally generated seal. WTF?"); Err(None) }) } else { - trace!(target: "miner", "prepare_sealing: unable to generate seal internally"); + trace!(target: "miner", "seal_block_internally: unable to generate seal internally"); Err(Some(block)) } } @@ -370,9 +372,9 @@ impl Miner { let (work, is_new) = { let mut sealing_work = self.sealing_work.lock(); let last_work_hash = sealing_work.queue.peek_last_ref().map(|pb| pb.block().fields().header.hash()); - trace!(target: "miner", "Checking whether we need to reseal: orig={:?} last={:?}, this={:?}", original_work_hash, last_work_hash, block.block().fields().header.hash()); + trace!(target: "miner", "prepare_work: Checking whether we need to reseal: orig={:?} last={:?}, this={:?}", original_work_hash, last_work_hash, block.block().fields().header.hash()); let (work, is_new) = if last_work_hash.map_or(true, |h| h != block.block().fields().header.hash()) { - trace!(target: "miner", "Pushing a new, refreshed or borrowed pending {}...", block.block().fields().header.hash()); + trace!(target: "miner", "prepare_work: Pushing a new, refreshed or borrowed pending {}...", block.block().fields().header.hash()); let pow_hash = block.block().fields().header.hash(); let number = block.block().fields().header.number(); let difficulty = *block.block().fields().header.difficulty(); @@ -386,7 +388,7 @@ impl Miner { } else { (None, false) }; - trace!(target: "miner", "prepare_sealing: leaving (last={:?})", sealing_work.queue.peek_last_ref().map(|b| b.block().fields().header.hash())); + trace!(target: "miner", "prepare_work: leaving (last={:?})", sealing_work.queue.peek_last_ref().map(|b| b.block().fields().header.hash())); (work, is_new) }; if is_new { @@ -395,26 +397,11 @@ impl Miner { } /// Prepares new block for sealing including top transactions from queue. - #[cfg_attr(feature="dev", allow(match_same_arms))] - #[cfg_attr(feature="dev", allow(cyclomatic_complexity))] fn prepare_block_and_work(&self, chain: &MiningBlockChainClient) { - trace!(target: "miner", "prepare_sealing: entering"); + trace!(target: "miner", "prepare_block_and_work: entering"); let (block, original_work_hash) = self.prepare_block(chain); - let block = if !block.transactions().is_empty() { - let block_opt = self.seal_block_internally(block).map(|sealed| - match chain.import_block(sealed.rlp_bytes()) { - Ok(_) => trace!(target: "miner", "prepare_sealing: sealed internally and imported. leaving."), - _ => warn!("prepare_sealing: ERROR: could not import internally sealed block. WTF?"), - } - ).err().unwrap_or(None); - if block_opt.is_none() { return; } - block_opt.unwrap() - } else { - block - }; - self.prepare_work(block, original_work_hash); } @@ -425,12 +412,12 @@ impl Miner { } /// Returns true if we had to prepare new pending block - fn enable_and_prepare_sealing(&self, chain: &MiningBlockChainClient) -> bool { - trace!(target: "miner", "enable_and_prepare_sealing: entering"); + fn prepare_work_sealing(&self, chain: &MiningBlockChainClient) -> bool { + trace!(target: "miner", "prepare_work_sealing: entering"); let prepare_new = { let mut sealing_work = self.sealing_work.lock(); let have_work = sealing_work.queue.peek_last_ref().is_some(); - trace!(target: "miner", "enable_and_prepare_sealing: have_work={}", have_work); + trace!(target: "miner", "prepare_work_sealing: have_work={}", have_work); if !have_work { sealing_work.enabled = true; true @@ -448,7 +435,7 @@ impl Miner { let mut sealing_block_last_request = self.sealing_block_last_request.lock(); let best_number = chain.chain_info().best_block_number; if *sealing_block_last_request != best_number { - trace!(target: "miner", "enable_and_prepare_sealing: Miner received request (was {}, now {}) - waking up.", *sealing_block_last_request, best_number); + trace!(target: "miner", "prepare_work_sealing: Miner received request (was {}, now {}) - waking up.", *sealing_block_last_request, best_number); *sealing_block_last_request = best_number; } @@ -667,7 +654,7 @@ impl MinerService for Miner { trace!(target: "own_tx", "Importing transaction: {:?}", transaction); let imported = { - // Be sure to release the lock before we call enable_and_prepare_sealing + // Be sure to release the lock before we call prepare_work_sealing let mut transaction_queue = self.transaction_queue.lock(); let import = self.add_transactions_to_queue( chain, vec![transaction], TransactionOrigin::Local, &mut transaction_queue @@ -694,7 +681,7 @@ impl MinerService for Miner { if imported.is_ok() && self.options.reseal_on_own_tx && self.tx_reseal_allowed() { // Make sure to do it after transaction is imported and lock is droped. // We need to create pending block and enable sealing - let prepared = self.enable_and_prepare_sealing(chain); + let prepared = self.prepare_work_sealing(chain); // If new block has not been prepared (means we already had one) // we need to update sealing if !prepared { @@ -836,7 +823,11 @@ impl MinerService for Miner { // | NOTE Code below requires transaction_queue and sealing_work locks. | // | Make sure to release the locks before calling that method. | // -------------------------------------------------------------------------- - self.prepare_block_and_work(chain); + if self.options.internal_sealing { + self.seal_and_import_block_internally(chain); + } else { + self.prepare_block_and_work(chain); + } } } @@ -846,7 +837,7 @@ impl MinerService for Miner { fn map_sealing_work(&self, chain: &MiningBlockChainClient, f: F) -> Option where F: FnOnce(&ClosedBlock) -> T { trace!(target: "miner", "map_sealing_work: entering"); - self.enable_and_prepare_sealing(chain); + self.prepare_work_sealing(chain); trace!(target: "miner", "map_sealing_work: sealing prepared"); let mut sealing_work = self.sealing_work.lock(); let ret = sealing_work.queue.use_last_ref(); @@ -994,6 +985,7 @@ mod tests { force_sealing: false, reseal_on_external_tx: false, reseal_on_own_tx: true, + internal_sealing: false, reseal_min_period: Duration::from_secs(5), tx_gas_limit: !U256::zero(), tx_queue_size: 1024, @@ -1034,7 +1026,7 @@ mod tests { assert_eq!(miner.pending_transactions_hashes().len(), 1); assert_eq!(miner.pending_receipts().len(), 1); // This method will let us know if pending block was created (before calling that method) - assert_eq!(miner.enable_and_prepare_sealing(&client), false); + assert_eq!(miner.prepare_work_sealing(&client), false); } #[test] @@ -1064,6 +1056,6 @@ mod tests { assert_eq!(miner.pending_transactions().len(), 0); assert_eq!(miner.pending_receipts().len(), 0); // This method will let us know if pending block was created (before calling that method) - assert_eq!(miner.enable_and_prepare_sealing(&client), true); + assert_eq!(miner.prepare_work_sealing(&client), true); } } diff --git a/parity/cli.rs b/parity/cli.rs index bb46bda13..652bcec8f 100644 --- a/parity/cli.rs +++ b/parity/cli.rs @@ -163,6 +163,9 @@ Sealing/Mining Options: --reseal-min-period MS Specify the minimum time between reseals from incoming transactions. MS is time measured in milliseconds [default: 2000]. + --internal-sealing Use an internal sealing mechanism, + suitable for PoA or PoS. + --work-queue-size ITEMS Specify the number of historical work packages which are kept cached lest a solution is found for them later. High values take more memory but result @@ -366,6 +369,7 @@ pub struct Args { pub flag_force_sealing: bool, pub flag_reseal_on_txs: String, pub flag_reseal_min_period: u64, + pub flag_internal_sealing: bool, pub flag_work_queue_size: usize, pub flag_remove_solved: bool, pub flag_tx_gas_limit: Option, diff --git a/parity/configuration.rs b/parity/configuration.rs index 51d637580..083b70768 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -326,6 +326,7 @@ impl Configuration { let options = MinerOptions { new_work_notify: self.work_notify(), force_sealing: self.args.flag_force_sealing, + internal_sealing: self.args.flag_internal_sealing, reseal_on_external_tx: reseal.external, reseal_on_own_tx: reseal.own, tx_gas_limit: match self.args.flag_tx_gas_limit { diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 448fa4734..06cc6f065 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -56,6 +56,7 @@ fn miner_service(spec: &Spec, accounts: Arc) -> Arc { force_sealing: true, reseal_on_external_tx: true, reseal_on_own_tx: true, + internal_sealing: false, tx_queue_size: 1024, tx_gas_limit: !U256::zero(), pending_set: PendingSet::SealingOrElseQueue, From c0201bd891d5992a2adeb9a773f4ada2b084d0ba Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 12 Sep 2016 11:07:40 +0200 Subject: [PATCH 008/106] replace cli with engine method, simplify --- ethcore/src/engines/basic_authority.rs | 2 ++ ethcore/src/engines/instant_seal.rs | 2 ++ ethcore/src/engines/mod.rs | 2 ++ ethcore/src/miner/miner.rs | 35 +++++++++++--------------- parity/cli.rs | 4 --- parity/configuration.rs | 1 - rpc/src/v1/tests/eth.rs | 1 - 7 files changed, 20 insertions(+), 27 deletions(-) diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 18dfeec46..5cb2be9ed 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -99,6 +99,8 @@ impl Engine for BasicAuthority { /// 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) {} + fn seals_internally(&self) -> bool { true } + /// Attempt to seal the block internally. /// /// This operation is synchronous and may (quite reasonably) not be available, in which `false` will diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index 3c95f3465..9153d1913 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -58,6 +58,8 @@ impl Engine for InstantSeal { Schedule::new_homestead() } + fn seals_internally(&self) -> bool { true } + fn generate_seal(&self, _block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option> { Some(Vec::new()) } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 6414ba5e4..0394426ce 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -71,6 +71,8 @@ pub trait Engine : Sync + Send { /// Block transformation functions, after the transactions. fn on_close_block(&self, _block: &mut ExecutedBlock) {} + /// If true, generate_seal has to be implemented. + fn seals_internally(&self) -> bool { false } /// Attempt to seal the block internally. /// /// If `Some` is returned, then you get a valid seal. diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index b79395c0c..fdfd43c60 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -60,8 +60,6 @@ pub struct MinerOptions { pub reseal_on_own_tx: bool, /// Minimum period between transaction-inspired reseals. pub reseal_min_period: Duration, - /// Seal blocks internally. - pub internal_sealing: bool, /// Maximum amount of gas to bother considering for block insertion. pub tx_gas_limit: U256, /// Maximum size of the transaction queue. @@ -81,7 +79,6 @@ impl Default for MinerOptions { force_sealing: false, reseal_on_external_tx: false, reseal_on_own_tx: true, - internal_sealing: false, tx_gas_limit: !U256::zero(), tx_queue_size: 1024, pending_set: PendingSet::AlwaysQueue, @@ -245,6 +242,7 @@ impl Miner { self.sealing_work.lock().queue.peek_last_ref().map(|b| b.base().clone()) } + /// Prepares new block for sealing including top transactions from queue. fn prepare_block(&self, chain: &MiningBlockChainClient) -> (ClosedBlock, Option) { { trace!(target: "miner", "prepare_block: recalibrating..."); @@ -355,19 +353,19 @@ impl Miner { } } - fn seal_and_import_block_internally(&self, chain: &MiningBlockChainClient) -> bool { - let (block, _) = self.prepare_block(chain); - + /// Uses engine to seal the block and then imports it to chain. + fn seal_and_import_block_internally(&self, chain: &MiningBlockChainClient, block: ClosedBlock) -> bool { if !block.transactions().is_empty() { if let Ok(sealed) = self.seal_block_internally(block) { if chain.import_block(sealed.rlp_bytes()).is_ok() { - return true; + return true } } } false } + /// Prepares work which has to be done to seal. fn prepare_work(&self, block: ClosedBlock, original_work_hash: Option) { let (work, is_new) = { let mut sealing_work = self.sealing_work.lock(); @@ -396,15 +394,6 @@ impl Miner { } } - /// Prepares new block for sealing including top transactions from queue. - fn prepare_block_and_work(&self, chain: &MiningBlockChainClient) { - trace!(target: "miner", "prepare_block_and_work: entering"); - - let (block, original_work_hash) = self.prepare_block(chain); - - self.prepare_work(block, original_work_hash); - } - fn update_gas_limit(&self, chain: &MiningBlockChainClient) { let gas_limit = HeaderView::new(&chain.best_block_header()).gas_limit(); let mut queue = self.transaction_queue.lock(); @@ -430,7 +419,8 @@ impl Miner { // | NOTE Code below requires transaction_queue and sealing_work locks. | // | Make sure to release the locks before calling that method. | // -------------------------------------------------------------------------- - self.prepare_block_and_work(chain); + let (block, original_work_hash) = self.prepare_block(chain); + self.prepare_work(block, original_work_hash); } let mut sealing_block_last_request = self.sealing_block_last_request.lock(); let best_number = chain.chain_info().best_block_number; @@ -823,10 +813,14 @@ impl MinerService for Miner { // | NOTE Code below requires transaction_queue and sealing_work locks. | // | Make sure to release the locks before calling that method. | // -------------------------------------------------------------------------- - if self.options.internal_sealing { - self.seal_and_import_block_internally(chain); + trace!(target: "miner", "update_sealing: preparing a block"); + let (block, original_work_hash) = self.prepare_block(chain); + if self.engine.seals_internally() { + trace!(target: "miner", "update_sealing: engine indicates internal sealing"); + self.seal_and_import_block_internally(chain, block); } else { - self.prepare_block_and_work(chain); + trace!(target: "miner", "update_sealing: engine does not seal internally, preparing work"); + self.prepare_work(block, original_work_hash); } } } @@ -985,7 +979,6 @@ mod tests { force_sealing: false, reseal_on_external_tx: false, reseal_on_own_tx: true, - internal_sealing: false, reseal_min_period: Duration::from_secs(5), tx_gas_limit: !U256::zero(), tx_queue_size: 1024, diff --git a/parity/cli.rs b/parity/cli.rs index 652bcec8f..bb46bda13 100644 --- a/parity/cli.rs +++ b/parity/cli.rs @@ -163,9 +163,6 @@ Sealing/Mining Options: --reseal-min-period MS Specify the minimum time between reseals from incoming transactions. MS is time measured in milliseconds [default: 2000]. - --internal-sealing Use an internal sealing mechanism, - suitable for PoA or PoS. - --work-queue-size ITEMS Specify the number of historical work packages which are kept cached lest a solution is found for them later. High values take more memory but result @@ -369,7 +366,6 @@ pub struct Args { pub flag_force_sealing: bool, pub flag_reseal_on_txs: String, pub flag_reseal_min_period: u64, - pub flag_internal_sealing: bool, pub flag_work_queue_size: usize, pub flag_remove_solved: bool, pub flag_tx_gas_limit: Option, diff --git a/parity/configuration.rs b/parity/configuration.rs index 083b70768..51d637580 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -326,7 +326,6 @@ impl Configuration { let options = MinerOptions { new_work_notify: self.work_notify(), force_sealing: self.args.flag_force_sealing, - internal_sealing: self.args.flag_internal_sealing, reseal_on_external_tx: reseal.external, reseal_on_own_tx: reseal.own, tx_gas_limit: match self.args.flag_tx_gas_limit { diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 06cc6f065..448fa4734 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -56,7 +56,6 @@ fn miner_service(spec: &Spec, accounts: Arc) -> Arc { force_sealing: true, reseal_on_external_tx: true, reseal_on_own_tx: true, - internal_sealing: false, tx_queue_size: 1024, tx_gas_limit: !U256::zero(), pending_set: PendingSet::SealingOrElseQueue, From 4bfdeea9e508d327f0cf23198ac0a21d67232e03 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 12 Sep 2016 12:46:03 +0200 Subject: [PATCH 009/106] More docs about sealing types. Bypass work in external txs. --- ethcore/src/miner/miner.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index fdfd43c60..abf13fc6a 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -165,6 +165,7 @@ struct SealingWork { } /// Keeps track of transactions using priority queue and holds currently mined block. +/// Handles preparing work for "work sealing" or seals "internally" if Engine does not require work. pub struct Miner { // NOTE [ToDr] When locking always lock in this order! transaction_queue: Arc>, @@ -333,7 +334,7 @@ impl Miner { (block, original_work_hash) } - /// Attempts to perform internal sealing to return Ok(sealed), + /// Attempts to perform internal sealing (one that does not require work) to return Ok(sealed), /// Err(Some(block)) returns for unsuccesful sealing while Err(None) indicates misspecified engine. fn seal_block_internally(&self, block: ClosedBlock) -> Result> { trace!(target: "miner", "seal_block_internally: block has transaction - attempting internal seal."); @@ -353,7 +354,7 @@ impl Miner { } } - /// Uses engine to seal the block and then imports it to chain. + /// Uses Engine to seal the block internally and then imports it to chain. fn seal_and_import_block_internally(&self, chain: &MiningBlockChainClient, block: ClosedBlock) -> bool { if !block.transactions().is_empty() { if let Ok(sealed) = self.seal_block_internally(block) { @@ -400,7 +401,7 @@ impl Miner { queue.set_gas_limit(gas_limit); } - /// Returns true if we had to prepare new pending block + /// Returns true if we had to prepare new pending block. fn prepare_work_sealing(&self, chain: &MiningBlockChainClient) -> bool { trace!(target: "miner", "prepare_work_sealing: entering"); let prepare_new = { @@ -670,11 +671,11 @@ impl MinerService for Miner { // -------------------------------------------------------------------------- if imported.is_ok() && self.options.reseal_on_own_tx && self.tx_reseal_allowed() { // Make sure to do it after transaction is imported and lock is droped. - // We need to create pending block and enable sealing - let prepared = self.prepare_work_sealing(chain); - // If new block has not been prepared (means we already had one) - // we need to update sealing - if !prepared { + // We need to create pending block and enable sealing. + if self.engine.seals_internally() || !self.prepare_work_sealing(chain) { + // If new block has not been prepared (means we already had one) + // or Engine might be able to seal internally, + // we need to update sealing. self.update_sealing(chain); } } @@ -776,6 +777,8 @@ impl MinerService for Miner { self.transaction_queue.lock().last_nonce(address) } + /// Update sealing if required. + /// Prepare the block and work if the Engine does not seal internally. fn update_sealing(&self, chain: &MiningBlockChainClient) { trace!(target: "miner", "update_sealing"); let requires_reseal = { From e41b6c410fe0cfb665ccde0c5f48fbf78fc71776 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 13 Sep 2016 12:52:14 +0200 Subject: [PATCH 010/106] split requires_reseal, add test and new test miner --- ethcore/src/engines/instant_seal.rs | 10 +-- ethcore/src/miner/miner.rs | 133 ++++++++++++++++------------ ethcore/src/spec/spec.rs | 5 ++ 3 files changed, 83 insertions(+), 65 deletions(-) diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index 9153d1913..e88c1d102 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -73,18 +73,12 @@ mod tests { use spec::Spec; use block::*; - /// Create a new test chain spec with `BasicAuthority` consensus engine. - fn new_test_instant() -> Spec { - let bytes: &[u8] = include_bytes!("../../res/instant_seal.json"); - Spec::load(bytes).expect("invalid chain spec") - } - #[test] fn instant_can_seal() { let tap = AccountProvider::transient_provider(); let addr = tap.insert_account("".sha3(), "").unwrap(); - let spec = new_test_instant(); + let spec = Spec::new_test_instant(); let engine = &*spec.engine; let genesis_header = spec.genesis_header(); let mut db_result = get_temp_journal_db(); @@ -100,7 +94,7 @@ mod tests { #[test] fn instant_cant_verify() { - let engine = new_test_instant().engine; + let engine = Spec::new_test_instant().engine; let mut header: Header = Header::default(); assert!(engine.verify_block_basic(&header, None).is_ok()); diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index abf13fc6a..152b0e994 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -34,6 +34,7 @@ use miner::{MinerService, MinerStatus, TransactionQueue, AccountDetails, Transac use miner::work_notify::WorkPoster; use client::TransactionImportResult; use miner::price_info::PriceInfo; +use header::BlockNumber; /// Different possible definitions for pending transaction set. #[derive(Debug, PartialEq)] @@ -334,6 +335,36 @@ impl Miner { (block, original_work_hash) } + /// Check is reseal is allowed and necessary. + fn requires_reseal(&self, best_block: BlockNumber) -> bool { + let has_local_transactions = self.transaction_queue.lock().has_local_pending_transactions(); + let mut sealing_work = self.sealing_work.lock(); + if sealing_work.enabled { + trace!(target: "miner", "requires_reseal: sealing enabled"); + let last_request = *self.sealing_block_last_request.lock(); + let should_disable_sealing = !self.forced_sealing() + && !has_local_transactions + && best_block > last_request + && best_block - last_request > SEALING_TIMEOUT_IN_BLOCKS; + + trace!(target: "miner", "requires_reseal: should_disable_sealing={}; best_block={}, last_request={}", should_disable_sealing, best_block, last_request); + + if should_disable_sealing { + trace!(target: "miner", "Miner sleeping (current {}, last {})", best_block, last_request); + sealing_work.enabled = false; + sealing_work.queue.reset(); + false + } else { + // sealing enabled and we don't want to sleep. + *self.next_allowed_reseal.lock() = Instant::now() + self.options.reseal_min_period; + true + } + } else { + // sealing is disabled. + false + } + } + /// Attempts to perform internal sealing (one that does not require work) to return Ok(sealed), /// Err(Some(block)) returns for unsuccesful sealing while Err(None) indicates misspecified engine. fn seal_block_internally(&self, block: ClosedBlock) -> Result> { @@ -777,41 +808,13 @@ impl MinerService for Miner { self.transaction_queue.lock().last_nonce(address) } + /// Update sealing if required. /// Prepare the block and work if the Engine does not seal internally. fn update_sealing(&self, chain: &MiningBlockChainClient) { trace!(target: "miner", "update_sealing"); - let requires_reseal = { - let has_local_transactions = self.transaction_queue.lock().has_local_pending_transactions(); - let mut sealing_work = self.sealing_work.lock(); - if sealing_work.enabled { - trace!(target: "miner", "update_sealing: sealing enabled"); - let current_no = chain.chain_info().best_block_number; - let last_request = *self.sealing_block_last_request.lock(); - let should_disable_sealing = !self.forced_sealing() - && !has_local_transactions - && current_no > last_request - && current_no - last_request > SEALING_TIMEOUT_IN_BLOCKS; - trace!(target: "miner", "update_sealing: should_disable_sealing={}; current_no={}, last_request={}", should_disable_sealing, current_no, last_request); - - if should_disable_sealing { - trace!(target: "miner", "Miner sleeping (current {}, last {})", current_no, last_request); - sealing_work.enabled = false; - sealing_work.queue.reset(); - false - } else { - // sealing enabled and we don't want to sleep. - *self.next_allowed_reseal.lock() = Instant::now() + self.options.reseal_min_period; - true - } - } else { - // sealing is disabled. - false - } - }; - - if requires_reseal { + if self.requires_reseal(chain.chain_info().best_block_number) { // -------------------------------------------------------------------------- // | NOTE Code below requires transaction_queue and sealing_work locks. | // | Make sure to release the locks before calling that method. | @@ -937,11 +940,12 @@ mod tests { use super::*; use util::*; use ethkey::{Generator, Random}; - use client::{TestBlockChainClient, EachBlockWith}; - use client::{TransactionImportResult}; - use types::transaction::{Transaction, Action}; + use client::{BlockChainClient, TestBlockChainClient, EachBlockWith, TransactionImportResult}; + use header::BlockNumber; + use types::transaction::{Transaction, SignedTransaction, Action}; use block::*; use spec::Spec; + use tests::helpers::{generate_dummy_client}; #[test] fn should_prepare_block_to_seal() { @@ -995,23 +999,24 @@ mod tests { )).ok().expect("Miner was just created.") } + fn transaction() -> SignedTransaction { + let keypair = Random.generate().unwrap(); + Transaction { + action: Action::Create, + value: U256::zero(), + data: "3331600055".from_hex().unwrap(), + gas: U256::from(100_000), + gas_price: U256::zero(), + nonce: U256::zero(), + }.sign(keypair.secret()) + } + #[test] fn should_make_pending_block_when_importing_own_transaction() { // given let client = TestBlockChainClient::default(); let miner = miner(); - let transaction = { - let keypair = Random.generate().unwrap(); - Transaction { - action: Action::Create, - value: U256::zero(), - data: "3331600055".from_hex().unwrap(), - gas: U256::from(100_000), - gas_price: U256::zero(), - nonce: U256::zero(), - }.sign(keypair.secret()) - }; - + let transaction = transaction(); // when let res = miner.import_own_transaction(&client, transaction); @@ -1030,18 +1035,7 @@ mod tests { // given let client = TestBlockChainClient::default(); let miner = miner(); - let transaction = { - let keypair = Random.generate().unwrap(); - Transaction { - action: Action::Create, - value: U256::zero(), - data: "3331600055".from_hex().unwrap(), - gas: U256::from(100_000), - gas_price: U256::zero(), - nonce: U256::zero(), - }.sign(keypair.secret()) - }; - + let transaction = transaction(); // when let res = miner.import_external_transactions(&client, vec![transaction]).pop().unwrap(); @@ -1054,4 +1048,29 @@ mod tests { // This method will let us know if pending block was created (before calling that method) assert_eq!(miner.prepare_work_sealing(&client), true); } + + #[test] + fn internal_seals_without_work() { + let miner = Miner::with_spec(&Spec::new_test_instant()); + { + let mut sealing_work = miner.sealing_work.lock(); + sealing_work.enabled = true; + } + let c = generate_dummy_client(2); + let client = c.reference().as_ref(); + + assert_eq!(miner.import_external_transactions(client, vec![transaction()]).pop().unwrap().unwrap(), TransactionImportResult::Current); + + miner.update_sealing(client); + client.flush_queue(); + assert!(miner.pending_block().is_none()); + assert_eq!(client.chain_info().best_block_number, 3 as BlockNumber); + + assert_eq!(miner.import_own_transaction(client, transaction()).unwrap(), TransactionImportResult::Current); + + miner.update_sealing(client); + client.flush_queue(); + assert!(miner.pending_block().is_none()); + assert_eq!(client.chain_info().best_block_number, 4 as BlockNumber); + } } diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 58317e97b..a6b5ad649 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -260,6 +260,11 @@ impl Spec { pub fn new_null() -> Self { Spec::load(include_bytes!("../../res/null.json") as &[u8]).expect("null.json is invalid") } + + /// Create a new Spec with InstantSeal consensus which does internal sealing (not requiring work). + pub fn new_test_instant() -> Self { + Spec::load(include_bytes!("../../res/instant_seal.json") as &[u8]).expect("instant_seal.json is invalid") + } } #[cfg(test)] From 67601327afc04bcfcbb585213765409ca38fd3d2 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 13 Sep 2016 14:21:12 +0200 Subject: [PATCH 011/106] make test helpers not test, add some docs --- ethcore/src/lib.rs | 5 +++-- ethcore/src/tests/helpers.rs | 20 +++++++++++++++++++- ethcore/src/tests/mod.rs | 2 ++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index 5344381f0..9231f4208 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -152,8 +152,9 @@ mod blockchain; mod types; mod factory; -#[cfg(test)] -mod tests; +//#[cfg(test)] +#[allow(missing_docs)] +pub mod tests; #[cfg(test)] #[cfg(feature="json-tests")] mod json_tests; diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index c1f99f434..3e9839f31 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -36,6 +36,7 @@ pub enum ChainEra { DaoHardfork, } +/// Engine for testing nested calls. pub struct TestEngine { engine: Arc, max_depth: usize @@ -124,7 +125,12 @@ pub fn create_test_block_with_data(header: &Header, transactions: &[SignedTransa } pub fn generate_dummy_client(block_number: u32) -> GuardedTempResult> { - generate_dummy_client_with_spec_and_data(Spec::new_test, block_number, 0, &[]) + generate_dummy_client_with_spec(Spec::new_test, block_number) +} + +pub fn generate_dummy_client_with_spec(get_spec: F, block_number: u32) -> GuardedTempResult> where + F: Fn()->Spec { + generate_dummy_client_with_spec_and_data(get_spec, block_number, 0, &[]) } pub fn generate_dummy_client_with_data(block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> GuardedTempResult> { @@ -266,6 +272,7 @@ pub fn get_test_client_with_blocks(blocks: Vec) -> GuardedTempResult Arc { Arc::new( Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), path) @@ -273,6 +280,7 @@ fn new_db(path: &str) -> Arc { ) } +/// Make blockchain. pub fn generate_dummy_blockchain(block_number: u32) -> GuardedTempResult { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); @@ -291,6 +299,7 @@ pub fn generate_dummy_blockchain(block_number: u32) -> GuardedTempResult GuardedTempResult { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); @@ -310,6 +319,7 @@ pub fn generate_dummy_blockchain_with_extra(block_number: u32) -> GuardedTempRes } } +/// Make blochchain. pub fn generate_dummy_empty_blockchain() -> GuardedTempResult { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); @@ -321,6 +331,7 @@ pub fn generate_dummy_empty_blockchain() -> GuardedTempResult { } } +/// Temporary journal db at random path. pub fn get_temp_journal_db() -> GuardedTempResult> { let temp = RandomTempPath::new(); let journal_db = get_temp_journal_db_in(temp.as_path()); @@ -331,6 +342,7 @@ pub fn get_temp_journal_db() -> GuardedTempResult> { } } +/// Temporary state. pub fn get_temp_state() -> GuardedTempResult { let temp = RandomTempPath::new(); let journal_db = get_temp_journal_db_in(temp.as_path()); @@ -341,21 +353,25 @@ pub fn get_temp_state() -> GuardedTempResult { } } +/// Temporary journal db. pub fn get_temp_journal_db_in(path: &Path) -> Box { let db = new_db(path.to_str().expect("Only valid utf8 paths for tests.")); journaldb::new(db.clone(), journaldb::Algorithm::EarlyMerge, None) } +/// Temporary state db. pub fn get_temp_state_in(path: &Path) -> State { let journal_db = get_temp_journal_db_in(path); State::new(journal_db, U256::from(0), Default::default()) } +/// Sequence of good blocks. pub fn get_good_dummy_block_seq(count: usize) -> Vec { let test_spec = get_test_spec(); get_good_dummy_block_fork_seq(1, count, &test_spec.genesis_header().hash()) } +/// Forked sequence of good blocks. pub fn get_good_dummy_block_fork_seq(start_number: usize, count: usize, parent_hash: &H256) -> Vec { let test_spec = get_test_spec(); let test_engine = &test_spec.engine; @@ -380,6 +396,7 @@ pub fn get_good_dummy_block_fork_seq(start_number: usize, count: usize, parent_h r } +/// Good block. pub fn get_good_dummy_block() -> Bytes { let mut block_header = Header::new(); let test_spec = get_test_spec(); @@ -394,6 +411,7 @@ pub fn get_good_dummy_block() -> Bytes { create_test_block(&block_header) } +/// Block with bad state. pub fn get_bad_state_dummy_block() -> Bytes { let mut block_header = Header::new(); let test_spec = get_test_spec(); diff --git a/ethcore/src/tests/mod.rs b/ethcore/src/tests/mod.rs index db36a3762..73c5777a4 100644 --- a/ethcore/src/tests/mod.rs +++ b/ethcore/src/tests/mod.rs @@ -15,5 +15,7 @@ // along with Parity. If not, see . pub mod helpers; +#[cfg(test)] mod client; +#[cfg(test)] mod rpc; From 6a33f8b369d005bbe94ec0ea5b91cfb8f1c85b5b Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 13 Sep 2016 14:22:44 +0200 Subject: [PATCH 012/106] state constructor used only in tests --- ethcore/src/state/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index 7a3b7b7ee..11c617474 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -56,7 +56,7 @@ const SEC_TRIE_DB_UNWRAP_STR: &'static str = "A state can only be created with v impl State { /// Creates new state with empty state root - #[cfg(test)] + //#[cfg(test)] pub fn new(mut db: Box, account_start_nonce: U256, factories: Factories) -> State { let mut root = H256::new(); { From eee6be1ce33741184ade7a0a6a40a0ecbf7889b3 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 13 Sep 2016 14:23:52 +0200 Subject: [PATCH 013/106] implement new predicate trait --- ethcore/src/engines/authority_round.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 787baccbf..5ae383bbe 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -159,6 +159,7 @@ impl Engine for AuthorityRound { /// 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) {} + fn seals_internally(&self) -> bool { true } /// Attempt to seal the block internally. /// /// This operation is synchronous and may (quite reasonably) not be available, in which `false` will From da2f117aef64aa08af43145055edaea540770b04 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 13 Sep 2016 15:58:32 +0200 Subject: [PATCH 014/106] new test_net holding Client --- ethcore/res/authority_round.json | 2 +- sync/src/tests/consensus.rs | 225 +++++++++++++++++++++++++++++ sync/src/tests/mod.rs | 2 + sync/src/tests/test_net.rs | 234 +++++++++++++++++++++++++++++++ 4 files changed, 462 insertions(+), 1 deletion(-) create mode 100644 sync/src/tests/consensus.rs create mode 100644 sync/src/tests/test_net.rs diff --git a/ethcore/res/authority_round.json b/ethcore/res/authority_round.json index eccd5d5eb..b4e35b29b 100644 --- a/ethcore/res/authority_round.json +++ b/ethcore/res/authority_round.json @@ -13,7 +13,7 @@ } }, "params": { - "accountStartNonce": "0x0100000", + "accountStartNonce": "0x0", "maximumExtraDataSize": "0x20", "minGasLimit": "0x1388", "networkID" : "0x69" diff --git a/sync/src/tests/consensus.rs b/sync/src/tests/consensus.rs new file mode 100644 index 000000000..9835132f7 --- /dev/null +++ b/sync/src/tests/consensus.rs @@ -0,0 +1,225 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use util::*; +use ethcore::client::{Client, BlockChainClient, BlockID, EachBlockWith}; +use chain::{SyncState}; +use super::test_net::*; + +#[test] +fn two_peers() { + ::env_logger::init().ok(); + let mut net = TestNet::new(3); + net.peer_mut(1).chain.add_blocks(1000, EachBlockWith::Uncle); + net.peer_mut(2).chain.add_blocks(1000, EachBlockWith::Uncle); + net.sync(); + assert!(net.peer(0).chain.block(BlockID::Number(1000)).is_some()); + assert_eq!(*net.peer(0).chain.blocks.read(), *net.peer(1).chain.blocks.read()); +} + +#[test] +fn long_chain() { + ::env_logger::init().ok(); + let mut net = TestNet::new(2); + net.peer_mut(1).chain.add_blocks(50000, EachBlockWith::Nothing); + net.sync(); + assert!(net.peer(0).chain.block(BlockID::Number(50000)).is_some()); + assert_eq!(*net.peer(0).chain.blocks.read(), *net.peer(1).chain.blocks.read()); +} + +#[test] +fn status_after_sync() { + ::env_logger::init().ok(); + let mut net = TestNet::new(3); + net.peer_mut(1).chain.add_blocks(1000, EachBlockWith::Uncle); + net.peer_mut(2).chain.add_blocks(1000, EachBlockWith::Uncle); + net.sync(); + let status = net.peer(0).sync.read().status(); + assert_eq!(status.state, SyncState::Idle); +} + +#[test] +fn takes_few_steps() { + let mut net = TestNet::new(3); + net.peer_mut(1).chain.add_blocks(100, EachBlockWith::Uncle); + net.peer_mut(2).chain.add_blocks(100, EachBlockWith::Uncle); + let total_steps = net.sync(); + assert!(total_steps < 20); +} + +#[test] +fn empty_blocks() { + ::env_logger::init().ok(); + let mut net = TestNet::new(3); + for n in 0..200 { + let with = if n % 2 == 0 { EachBlockWith::Nothing } else { EachBlockWith::Uncle }; + net.peer_mut(1).chain.add_blocks(5, with.clone()); + net.peer_mut(2).chain.add_blocks(5, with); + } + net.sync(); + assert!(net.peer(0).chain.block(BlockID::Number(1000)).is_some()); + assert_eq!(*net.peer(0).chain.blocks.read(), *net.peer(1).chain.blocks.read()); +} + +#[test] +fn forked() { + ::env_logger::init().ok(); + let mut net = TestNet::new(3); + net.peer_mut(0).chain.add_blocks(300, EachBlockWith::Uncle); + net.peer_mut(1).chain.add_blocks(300, EachBlockWith::Uncle); + net.peer_mut(2).chain.add_blocks(300, EachBlockWith::Uncle); + net.peer_mut(0).chain.add_blocks(100, EachBlockWith::Nothing); //fork + net.peer_mut(1).chain.add_blocks(200, EachBlockWith::Uncle); + net.peer_mut(2).chain.add_blocks(200, EachBlockWith::Uncle); + net.peer_mut(1).chain.add_blocks(100, EachBlockWith::Uncle); //fork between 1 and 2 + net.peer_mut(2).chain.add_blocks(10, EachBlockWith::Nothing); + // peer 1 has the best chain of 601 blocks + let peer1_chain = net.peer(1).chain.numbers.read().clone(); + net.sync(); + assert_eq!(*net.peer(0).chain.difficulty.read(), *net.peer(1).chain.difficulty.read()); + assert_eq!(&*net.peer(0).chain.numbers.read(), &peer1_chain); + assert_eq!(&*net.peer(1).chain.numbers.read(), &peer1_chain); + assert_eq!(&*net.peer(2).chain.numbers.read(), &peer1_chain); +} + +#[test] +fn net_hard_fork() { + ::env_logger::init().ok(); + let ref_client = TestBlockChainClient::new(); + ref_client.add_blocks(50, EachBlockWith::Uncle); + { + let mut net = TestNet::new_with_fork(2, Some((50, ref_client.block_hash(BlockID::Number(50)).unwrap()))); + net.peer_mut(0).chain.add_blocks(100, EachBlockWith::Uncle); + net.sync(); + assert_eq!(net.peer(1).chain.chain_info().best_block_number, 100); + } + { + let mut net = TestNet::new_with_fork(2, Some((50, ref_client.block_hash(BlockID::Number(50)).unwrap()))); + net.peer_mut(0).chain.add_blocks(100, EachBlockWith::Nothing); + net.sync(); + assert_eq!(net.peer(1).chain.chain_info().best_block_number, 0); + } +} + +#[test] +fn restart() { + let mut net = TestNet::new(3); + net.peer_mut(1).chain.add_blocks(1000, EachBlockWith::Uncle); + net.peer_mut(2).chain.add_blocks(1000, EachBlockWith::Uncle); + + net.sync_steps(8); + + // make sure that sync has actually happened + assert!(net.peer(0).chain.chain_info().best_block_number > 100); + net.restart_peer(0); + + let status = net.peer(0).sync.read().status(); + assert_eq!(status.state, SyncState::Idle); +} + +#[test] +fn status_empty() { + let net = TestNet::new(2); + assert_eq!(net.peer(0).sync.read().status().state, SyncState::Idle); +} + +#[test] +fn status_packet() { + let mut net = TestNet::new(2); + net.peer_mut(0).chain.add_blocks(100, EachBlockWith::Uncle); + net.peer_mut(1).chain.add_blocks(1, EachBlockWith::Uncle); + + net.start(); + + net.sync_step_peer(0); + + assert_eq!(1, net.peer(0).queue.len()); + assert_eq!(0x00, net.peer(0).queue[0].packet_id); +} + +#[test] +fn propagate_hashes() { + let mut net = TestNet::new(6); + net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Uncle); + net.sync(); + + net.peer_mut(0).chain.add_blocks(10, EachBlockWith::Uncle); + net.sync(); + net.trigger_chain_new_blocks(0); //first event just sets the marker + net.trigger_chain_new_blocks(0); + + // 5 peers with NewHahses, 4 with blocks + assert_eq!(9, net.peer(0).queue.len()); + let mut hashes = 0; + let mut blocks = 0; + for i in 0..net.peer(0).queue.len() { + if net.peer(0).queue[i].packet_id == 0x1 { + hashes += 1; + } + if net.peer(0).queue[i].packet_id == 0x7 { + blocks += 1; + } + } + assert_eq!(blocks, 4); + assert_eq!(hashes, 5); +} + +#[test] +fn propagate_blocks() { + let mut net = TestNet::new(20); + net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Uncle); + net.sync(); + + net.peer_mut(0).chain.add_blocks(10, EachBlockWith::Uncle); + net.trigger_chain_new_blocks(0); //first event just sets the marker + net.trigger_chain_new_blocks(0); + + assert!(!net.peer(0).queue.is_empty()); + // NEW_BLOCK_PACKET + let blocks = net.peer(0).queue.iter().filter(|p| p.packet_id == 0x7).count(); + assert!(blocks > 0); +} + +#[test] +fn restart_on_malformed_block() { + let mut net = TestNet::new(2); + net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Uncle); + net.peer_mut(1).chain.corrupt_block(6); + net.sync_steps(20); + + assert_eq!(net.peer(0).chain.chain_info().best_block_number, 5); +} + +#[test] +fn restart_on_broken_chain() { + let mut net = TestNet::new(2); + net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Uncle); + net.peer_mut(1).chain.corrupt_block_parent(6); + net.sync_steps(20); + + assert_eq!(net.peer(0).chain.chain_info().best_block_number, 5); +} + +#[test] +fn high_td_attach() { + let mut net = TestNet::new(2); + net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Uncle); + net.peer_mut(1).chain.corrupt_block_parent(6); + net.sync_steps(20); + + assert_eq!(net.peer(0).chain.chain_info().best_block_number, 5); +} + diff --git a/sync/src/tests/mod.rs b/sync/src/tests/mod.rs index bdb4ae4f9..ffd858e54 100644 --- a/sync/src/tests/mod.rs +++ b/sync/src/tests/mod.rs @@ -16,5 +16,7 @@ pub mod helpers; pub mod snapshot; +pub mod test_net; mod chain; mod rpc; +mod consensus; diff --git a/sync/src/tests/test_net.rs b/sync/src/tests/test_net.rs new file mode 100644 index 000000000..5d06d0697 --- /dev/null +++ b/sync/src/tests/test_net.rs @@ -0,0 +1,234 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use util::*; +use network::*; +use tests::snapshot::*; +use ethcore::client::{Client, BlockChainClient}; +use ethcore::tests::helpers::*; +use ethcore::header::BlockNumber; +use ethcore::spec::Spec; +use ethcore::snapshot::SnapshotService; +use sync_io::SyncIo; +use chain::ChainSync; +use ::SyncConfig; + +pub struct TestIo<'p> { + pub chain: &'p mut Client, + pub snapshot_service: &'p TestSnapshotService, + pub queue: &'p mut VecDeque, + pub sender: Option, +} + +impl<'p> TestIo<'p> { + pub fn new(chain: &'p mut Arc, ss: &'p TestSnapshotService, queue: &'p mut VecDeque, sender: Option) -> TestIo<'p> { + TestIo { + chain: Arc::get_mut(chain).unwrap(), + snapshot_service: ss, + queue: queue, + sender: sender + } + } +} + +impl<'p> SyncIo for TestIo<'p> { + fn disable_peer(&mut self, _peer_id: PeerId) { + } + + fn disconnect_peer(&mut self, _peer_id: PeerId) { + } + + fn is_expired(&self) -> bool { + false + } + + fn respond(&mut self, packet_id: PacketId, data: Vec) -> Result<(), NetworkError> { + self.queue.push_back(TestPacket { + data: data, + packet_id: packet_id, + recipient: self.sender.unwrap() + }); + Ok(()) + } + + fn send(&mut self, peer_id: PeerId, packet_id: PacketId, data: Vec) -> Result<(), NetworkError> { + self.queue.push_back(TestPacket { + data: data, + packet_id: packet_id, + recipient: peer_id, + }); + Ok(()) + } + + fn chain(&self) -> &BlockChainClient { + self.chain + } + + fn snapshot_service(&self) -> &SnapshotService { + self.snapshot_service + } + + fn eth_protocol_version(&self, _peer: PeerId) -> u8 { + 64 + } +} + +pub struct TestPacket { + pub data: Bytes, + pub packet_id: PacketId, + pub recipient: PeerId, +} + +pub struct TestPeer { + pub chain: Arc, + pub snapshot_service: Arc, + pub sync: RwLock, + pub queue: VecDeque, +} + +pub struct TestNet { + pub peers: Vec, + pub started: bool, +} + +impl TestNet { + pub fn new(n: usize) -> TestNet { + Self::new_with_fork(n, None) + } + + pub fn new_with_fork(n: usize, fork: Option<(BlockNumber, H256)>) -> TestNet { + let mut net = TestNet { + peers: Vec::new(), + started: false, + }; + for _ in 0..n { + let mut chain = generate_dummy_client(1); + let mut config = SyncConfig::default(); + config.fork_block = fork; + let ss = Arc::new(TestSnapshotService::new()); + let sync = ChainSync::new(config, chain.reference().as_ref()); + net.peers.push(TestPeer { + sync: RwLock::new(sync), + snapshot_service: ss, + chain: chain.take(), + queue: VecDeque::new(), + }); + } + net + } + + pub fn new_with_spec_file(n: usize, get_spec: &S) -> TestNet where + R: Read + Clone, + S: Fn()->Spec { + let mut net = TestNet { + peers: Vec::new(), + started: false, + }; + for _ in 0..n { + let mut chain = generate_dummy_client_with_spec(get_spec, 1); + let sync = ChainSync::new(SyncConfig::default(), chain.reference().as_ref()); + net.peers.push(TestPeer { + sync: RwLock::new(sync), + snapshot_service: Arc::new(TestSnapshotService::new()), + chain: chain.take(), + queue: VecDeque::new(), + }); + } + net + } + + pub fn peer(&self, i: usize) -> &TestPeer { + self.peers.get(i).unwrap() + } + + pub fn peer_mut(&mut self, i: usize) -> &mut TestPeer { + self.peers.get_mut(i).unwrap() + } + + pub fn start(&mut self) { + for peer in 0..self.peers.len() { + for client in 0..self.peers.len() { + if peer != client { + let mut p = self.peers.get_mut(peer).unwrap(); + p.sync.write().on_peer_connected(&mut TestIo::new(&mut p.chain, + &p.snapshot_service, + &mut p.queue, + Some(client as PeerId)), + client as PeerId); + } + } + } + } + + pub fn sync_step(&mut self) { + for peer in 0..self.peers.len() { + if let Some(packet) = self.peers[peer].queue.pop_front() { + let mut p = self.peers.get_mut(packet.recipient).unwrap(); + trace!("--- {} -> {} ---", peer, packet.recipient); + ChainSync::dispatch_packet(&p.sync, + &mut TestIo::new(&mut p.chain, + &p.snapshot_service, + &mut p.queue, + Some(peer as PeerId)), + peer as PeerId, + packet.packet_id, + &packet.data); + trace!("----------------"); + } + let mut p = self.peers.get_mut(peer).unwrap(); + p.sync.write().maintain_sync(&mut TestIo::new(&mut p.chain, &p.snapshot_service, &mut p.queue, None)); + } + } + + pub fn sync_step_peer(&mut self, peer_num: usize) { + let mut peer = self.peer_mut(peer_num); + peer.sync.write().maintain_sync(&mut TestIo::new(&mut peer.chain, &peer.snapshot_service, &mut peer.queue, None)); + } + + pub fn restart_peer(&mut self, i: usize) { + let peer = self.peer_mut(i); + peer.sync.write().restart(&mut TestIo::new(&mut peer.chain, &peer.snapshot_service, &mut peer.queue, None)); + } + + pub fn sync(&mut self) -> u32 { + self.start(); + let mut total_steps = 0; + while !self.done() { + self.sync_step(); + total_steps += 1; + } + total_steps + } + + pub fn sync_steps(&mut self, count: usize) { + if !self.started { + self.start(); + self.started = true; + } + for _ in 0..count { + self.sync_step(); + } + } + + pub fn done(&self) -> bool { + self.peers.iter().all(|p| p.queue.is_empty()) + } + + pub fn trigger_chain_new_blocks(&mut self, peer_id: usize) { + let mut peer = self.peer_mut(peer_id); + peer.sync.write().chain_new_blocks(&mut TestIo::new(&mut peer.chain, &peer.snapshot_service, &mut peer.queue, None), &[], &[], &[], &[], &[]); + } +} From fef94205e3644cea58852d168aae23ac4a84373b Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 13 Sep 2016 19:59:34 +0200 Subject: [PATCH 015/106] enable internal sealing based on author --- ethcore/src/engines/basic_authority.rs | 2 +- ethcore/src/engines/instant_seal.rs | 2 +- ethcore/src/engines/mod.rs | 2 +- ethcore/src/miner/miner.rs | 9 +++++++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 5cb2be9ed..74b30ff6a 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -99,7 +99,7 @@ impl Engine for BasicAuthority { /// 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) {} - fn seals_internally(&self) -> bool { true } + fn seals_internally(&self, _author: &Address) -> bool { true } /// Attempt to seal the block internally. /// diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index e88c1d102..fc670b839 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -58,7 +58,7 @@ impl Engine for InstantSeal { Schedule::new_homestead() } - fn seals_internally(&self) -> bool { true } + fn seals_internally(&self, _author: &Address) -> bool { true } fn generate_seal(&self, _block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option> { Some(Vec::new()) diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 0394426ce..c2877cfa5 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -72,7 +72,7 @@ pub trait Engine : Sync + Send { fn on_close_block(&self, _block: &mut ExecutedBlock) {} /// If true, generate_seal has to be implemented. - fn seals_internally(&self) -> bool { false } + fn seals_internally(&self, _author: &Address) -> bool { false } /// Attempt to seal the block internally. /// /// If `Some` is returned, then you get a valid seal. diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 152b0e994..fc6fe32ce 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -16,6 +16,7 @@ use rayon::prelude::*; use std::time::{Instant, Duration}; +use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering}; use util::*; use util::using_queue::{UsingQueue, GetAction}; @@ -175,6 +176,7 @@ pub struct Miner { sealing_block_last_request: Mutex, // for sealing... options: MinerOptions, + seals_internally: AtomicBool, gas_range_target: RwLock<(U256, U256)>, author: RwLock
, @@ -195,6 +197,7 @@ impl Miner { next_allowed_reseal: Mutex::new(Instant::now()), sealing_block_last_request: Mutex::new(0), sealing_work: Mutex::new(SealingWork{queue: UsingQueue::new(20), enabled: false}), + seals_internally: AtomicBool::new(spec.engine.seals_internally(&Address::default())), gas_range_target: RwLock::new((U256::zero(), U256::zero())), author: RwLock::new(Address::default()), extra_data: RwLock::new(Vec::new()), @@ -214,6 +217,7 @@ impl Miner { next_allowed_reseal: Mutex::new(Instant::now()), sealing_block_last_request: Mutex::new(0), sealing_work: Mutex::new(SealingWork{queue: UsingQueue::new(options.work_queue_size), enabled: options.force_sealing || !options.new_work_notify.is_empty()}), + seals_internally: AtomicBool::new(spec.engine.seals_internally(&Address::default())), gas_range_target: RwLock::new((U256::zero(), U256::zero())), author: RwLock::new(Address::default()), extra_data: RwLock::new(Vec::new()), @@ -578,6 +582,7 @@ impl MinerService for Miner { } fn set_author(&self, author: Address) { + self.seals_internally.store(self.engine.seals_internally(&author), AtomicOrdering::SeqCst); *self.author.write() = author; } @@ -703,7 +708,7 @@ impl MinerService for Miner { if imported.is_ok() && self.options.reseal_on_own_tx && self.tx_reseal_allowed() { // Make sure to do it after transaction is imported and lock is droped. // We need to create pending block and enable sealing. - if self.engine.seals_internally() || !self.prepare_work_sealing(chain) { + if self.seals_internally.load(AtomicOrdering::SeqCst) || !self.prepare_work_sealing(chain) { // If new block has not been prepared (means we already had one) // or Engine might be able to seal internally, // we need to update sealing. @@ -821,7 +826,7 @@ impl MinerService for Miner { // -------------------------------------------------------------------------- trace!(target: "miner", "update_sealing: preparing a block"); let (block, original_work_hash) = self.prepare_block(chain); - if self.engine.seals_internally() { + if self.seals_internally.load(AtomicOrdering::SeqCst) { trace!(target: "miner", "update_sealing: engine indicates internal sealing"); self.seal_and_import_block_internally(chain, block); } else { From 2bd82269e8396946afa8c056dd87274767df11b6 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 14 Sep 2016 00:00:26 +0200 Subject: [PATCH 016/106] add tests, keep track of engine sealing status --- ethcore/src/engines/basic_authority.rs | 14 +++++- ethcore/src/engines/instant_seal.rs | 2 +- ethcore/src/engines/mod.rs | 5 ++- ethcore/src/miner/miner.rs | 60 ++++++++++++++++++-------- 4 files changed, 59 insertions(+), 22 deletions(-) diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 74b30ff6a..8d852e99e 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -99,7 +99,9 @@ impl Engine for BasicAuthority { /// 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) {} - fn seals_internally(&self, _author: &Address) -> bool { true } + fn is_sealer(&self, author: &Address) -> Option { + Some(self.our_params.authorities.contains(author)) + } /// Attempt to seal the block internally. /// @@ -259,4 +261,14 @@ mod tests { let seal = engine.generate_seal(b.block(), Some(&tap)).unwrap(); assert!(b.try_seal(engine, seal).is_ok()); } + + #[test] + fn seals_internally() { + let tap = AccountProvider::transient_provider(); + let authority = tap.insert_account("".sha3(), "").unwrap(); + + let engine = new_test_authority().engine; + assert!(!engine.is_sealer(&Address::default()).unwrap()); + assert!(engine.is_sealer(&authority).unwrap()); + } } diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index fc670b839..26d2ed5bf 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -58,7 +58,7 @@ impl Engine for InstantSeal { Schedule::new_homestead() } - fn seals_internally(&self, _author: &Address) -> bool { true } + fn is_sealer(&self, _author: &Address) -> Option { Some(true) } fn generate_seal(&self, _block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option> { Some(Vec::new()) diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index c2877cfa5..9cf112bf1 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -71,8 +71,9 @@ pub trait Engine : Sync + Send { /// Block transformation functions, after the transactions. fn on_close_block(&self, _block: &mut ExecutedBlock) {} - /// If true, generate_seal has to be implemented. - fn seals_internally(&self, _author: &Address) -> bool { false } + /// If Some(true) this author is able to generate seals, generate_seal has to be implemented. + /// None indicates that this engine does not seal internally regardless of author (e.g. PoW). + fn is_sealer(&self, _author: &Address) -> Option { None } /// Attempt to seal the block internally. /// /// If `Some` is returned, then you get a valid seal. diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index fc6fe32ce..502c78402 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -16,7 +16,6 @@ use rayon::prelude::*; use std::time::{Instant, Duration}; -use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering}; use util::*; use util::using_queue::{UsingQueue, GetAction}; @@ -176,7 +175,7 @@ pub struct Miner { sealing_block_last_request: Mutex, // for sealing... options: MinerOptions, - seals_internally: AtomicBool, + seals_internally: bool, gas_range_target: RwLock<(U256, U256)>, author: RwLock
, @@ -191,15 +190,20 @@ pub struct Miner { impl Miner { /// Creates new instance of miner without accounts, but with given spec. pub fn with_spec(spec: &Spec) -> Miner { + let author = Address::default(); + let is_sealer = spec.engine.is_sealer(&author); Miner { transaction_queue: Arc::new(Mutex::new(TransactionQueue::new())), options: Default::default(), next_allowed_reseal: Mutex::new(Instant::now()), sealing_block_last_request: Mutex::new(0), - sealing_work: Mutex::new(SealingWork{queue: UsingQueue::new(20), enabled: false}), - seals_internally: AtomicBool::new(spec.engine.seals_internally(&Address::default())), + sealing_work: Mutex::new(SealingWork{ + queue: UsingQueue::new(20), + enabled: is_sealer.unwrap_or(false) + }), + seals_internally: is_sealer.is_some(), gas_range_target: RwLock::new((U256::zero(), U256::zero())), - author: RwLock::new(Address::default()), + author: RwLock::new(author), extra_data: RwLock::new(Vec::new()), accounts: None, engine: spec.engine.clone(), @@ -212,14 +216,21 @@ impl Miner { pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Arc { let work_poster = if !options.new_work_notify.is_empty() { Some(WorkPoster::new(&options.new_work_notify)) } else { None }; let txq = Arc::new(Mutex::new(TransactionQueue::with_limits(options.tx_queue_size, options.tx_gas_limit))); + let author = Address::default(); + let is_sealer = spec.engine.is_sealer(&author); Arc::new(Miner { transaction_queue: txq, next_allowed_reseal: Mutex::new(Instant::now()), sealing_block_last_request: Mutex::new(0), - sealing_work: Mutex::new(SealingWork{queue: UsingQueue::new(options.work_queue_size), enabled: options.force_sealing || !options.new_work_notify.is_empty()}), - seals_internally: AtomicBool::new(spec.engine.seals_internally(&Address::default())), + sealing_work: Mutex::new(SealingWork{ + queue: UsingQueue::new(options.work_queue_size), + enabled: options.force_sealing + || !options.new_work_notify.is_empty() + || is_sealer.unwrap_or(false) + }), + seals_internally: is_sealer.is_some(), gas_range_target: RwLock::new((U256::zero(), U256::zero())), - author: RwLock::new(Address::default()), + author: RwLock::new(author), extra_data: RwLock::new(Vec::new()), options: options, accounts: accounts, @@ -364,7 +375,7 @@ impl Miner { true } } else { - // sealing is disabled. + trace!(target: "miner", "requires_reseal: sealing is disabled"); false } } @@ -582,7 +593,10 @@ impl MinerService for Miner { } fn set_author(&self, author: Address) { - self.seals_internally.store(self.engine.seals_internally(&author), AtomicOrdering::SeqCst); + if self.seals_internally { + let mut sealing_work = self.sealing_work.lock(); + sealing_work.enabled = self.engine.is_sealer(&author).unwrap_or(false); + } *self.author.write() = author; } @@ -708,7 +722,7 @@ impl MinerService for Miner { if imported.is_ok() && self.options.reseal_on_own_tx && self.tx_reseal_allowed() { // Make sure to do it after transaction is imported and lock is droped. // We need to create pending block and enable sealing. - if self.seals_internally.load(AtomicOrdering::SeqCst) || !self.prepare_work_sealing(chain) { + if self.seals_internally || !self.prepare_work_sealing(chain) { // If new block has not been prepared (means we already had one) // or Engine might be able to seal internally, // we need to update sealing. @@ -826,7 +840,7 @@ impl MinerService for Miner { // -------------------------------------------------------------------------- trace!(target: "miner", "update_sealing: preparing a block"); let (block, original_work_hash) = self.prepare_block(chain); - if self.seals_internally.load(AtomicOrdering::SeqCst) { + if self.seals_internally { trace!(target: "miner", "update_sealing: engine indicates internal sealing"); self.seal_and_import_block_internally(chain, block); } else { @@ -1032,7 +1046,7 @@ mod tests { assert_eq!(miner.pending_transactions_hashes().len(), 1); assert_eq!(miner.pending_receipts().len(), 1); // This method will let us know if pending block was created (before calling that method) - assert_eq!(miner.prepare_work_sealing(&client), false); + assert!(!miner.prepare_work_sealing(&client)); } #[test] @@ -1051,16 +1065,26 @@ mod tests { assert_eq!(miner.pending_transactions().len(), 0); assert_eq!(miner.pending_receipts().len(), 0); // This method will let us know if pending block was created (before calling that method) - assert_eq!(miner.prepare_work_sealing(&client), true); + assert!(miner.prepare_work_sealing(&client)); + } + + #[test] + fn should_not_seal_unless_enabled() { + let miner = miner(); + let client = TestBlockChainClient::default(); + // By default resealing is not required. + assert!(!miner.requires_reseal(1u8.into())); + + miner.import_external_transactions(&client, vec![transaction()]).pop().unwrap().unwrap(); + assert!(miner.prepare_work_sealing(&client)); + // Unless asked to prepare work. + assert!(miner.requires_reseal(1u8.into())); } #[test] fn internal_seals_without_work() { let miner = Miner::with_spec(&Spec::new_test_instant()); - { - let mut sealing_work = miner.sealing_work.lock(); - sealing_work.enabled = true; - } + let c = generate_dummy_client(2); let client = c.reference().as_ref(); From 0880d4ad8f35b54c76e79731620936d0c28f6bd6 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 14 Sep 2016 10:49:44 +0200 Subject: [PATCH 017/106] method to check if default address is_sealer --- ethcore/src/engines/mod.rs | 4 +++- ethcore/src/miner/miner.rs | 16 ++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 9cf112bf1..599721f86 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -72,8 +72,10 @@ pub trait Engine : Sync + Send { fn on_close_block(&self, _block: &mut ExecutedBlock) {} /// If Some(true) this author is able to generate seals, generate_seal has to be implemented. - /// None indicates that this engine does not seal internally regardless of author (e.g. PoW). + /// None indicates that this Engine never seals internally regardless of author (e.g. PoW). fn is_sealer(&self, _author: &Address) -> Option { None } + /// Checks if default address is able to seal. + fn is_default_sealer(&self) -> Option { self.is_sealer(&Default::default()) } /// Attempt to seal the block internally. /// /// If `Some` is returned, then you get a valid seal. diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 502c78402..ad963b167 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -190,8 +190,6 @@ pub struct Miner { impl Miner { /// Creates new instance of miner without accounts, but with given spec. pub fn with_spec(spec: &Spec) -> Miner { - let author = Address::default(); - let is_sealer = spec.engine.is_sealer(&author); Miner { transaction_queue: Arc::new(Mutex::new(TransactionQueue::new())), options: Default::default(), @@ -199,11 +197,11 @@ impl Miner { sealing_block_last_request: Mutex::new(0), sealing_work: Mutex::new(SealingWork{ queue: UsingQueue::new(20), - enabled: is_sealer.unwrap_or(false) + enabled: spec.engine.is_default_sealer().unwrap_or(false) }), - seals_internally: is_sealer.is_some(), + seals_internally: spec.engine.is_default_sealer().is_some(), gas_range_target: RwLock::new((U256::zero(), U256::zero())), - author: RwLock::new(author), + author: RwLock::new(Address::default()), extra_data: RwLock::new(Vec::new()), accounts: None, engine: spec.engine.clone(), @@ -216,8 +214,6 @@ impl Miner { pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Arc { let work_poster = if !options.new_work_notify.is_empty() { Some(WorkPoster::new(&options.new_work_notify)) } else { None }; let txq = Arc::new(Mutex::new(TransactionQueue::with_limits(options.tx_queue_size, options.tx_gas_limit))); - let author = Address::default(); - let is_sealer = spec.engine.is_sealer(&author); Arc::new(Miner { transaction_queue: txq, next_allowed_reseal: Mutex::new(Instant::now()), @@ -226,11 +222,11 @@ impl Miner { queue: UsingQueue::new(options.work_queue_size), enabled: options.force_sealing || !options.new_work_notify.is_empty() - || is_sealer.unwrap_or(false) + || spec.engine.is_default_sealer().unwrap_or(false) }), - seals_internally: is_sealer.is_some(), + seals_internally: spec.engine.is_default_sealer().is_some(), gas_range_target: RwLock::new((U256::zero(), U256::zero())), - author: RwLock::new(author), + author: RwLock::new(Address::default()), extra_data: RwLock::new(Vec::new()), options: options, accounts: accounts, From ddb6fec171df98a32bbb57dbfebd436ead779f54 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 14 Sep 2016 11:17:39 +0200 Subject: [PATCH 018/106] add new test specs --- ethcore/src/spec/spec.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 9334a55d6..8d96ebc07 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -266,6 +266,16 @@ impl Spec { pub fn new_test_instant() -> Self { Spec::load(include_bytes!("../../res/instant_seal.json") as &[u8]).expect("instant_seal.json is invalid") } + + /// Create a new Spec with AuthorityRound consensus which does internal sealing (not requiring work). + pub fn new_test_round() -> Self { + Spec::load(include_bytes!("../../res/authority_round.json") as &[u8]).expect("authority_round.json is invalid") + } + + /// Create a new Spec with Tendermint consensus which does internal sealing (not requiring work). + pub fn new_test_tendermint() -> Self { + Spec::load(include_bytes!("../../res/tendermint.json") as &[u8]).expect("tendermint.json is invalid") + } } #[cfg(test)] From bedbe6e65ef5d0cee6df3a6130b6fb70fa131e3d Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 14 Sep 2016 11:20:22 +0200 Subject: [PATCH 019/106] update test spec loading --- ethcore/src/engines/authority_round.rs | 20 +++++++------------- ethcore/src/spec/spec.rs | 5 ----- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 5ae383bbe..8c1cb9ce1 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -159,7 +159,7 @@ impl Engine for AuthorityRound { /// 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) {} - fn seals_internally(&self) -> bool { true } + fn is_sealer(&self, _author: &Address) -> Option { Some(true) } /// Attempt to seal the block internally. /// /// This operation is synchronous and may (quite reasonably) not be available, in which `false` will @@ -248,22 +248,16 @@ mod tests { use std::thread::sleep; use std::time::Duration; - /// Create a new test chain spec with `AuthorityRound` consensus engine. - fn new_test_authority() -> Spec { - let bytes: &[u8] = include_bytes!("../../res/authority_round.json"); - Spec::load(bytes).expect("invalid chain spec") - } - #[test] fn has_valid_metadata() { - let engine = new_test_authority().engine; + let engine = Spec::new_test_round().engine; assert!(!engine.name().is_empty()); assert!(engine.version().major >= 1); } #[test] fn can_return_schedule() { - let engine = new_test_authority().engine; + let engine = Spec::new_test_round().engine; let schedule = engine.schedule(&EnvInfo { number: 10000000, author: 0.into(), @@ -279,7 +273,7 @@ mod tests { #[test] fn verification_fails_on_short_seal() { - let engine = new_test_authority().engine; + let engine = Spec::new_test_round().engine; let header: Header = Header::default(); let verify_result = engine.verify_block_basic(&header, None); @@ -293,7 +287,7 @@ mod tests { #[test] fn can_do_signature_verification_fail() { - let engine = new_test_authority().engine; + let engine = Spec::new_test_round().engine; let mut header: Header = Header::default(); header.set_seal(vec![encode(&H520::default()).to_vec()]); @@ -307,7 +301,7 @@ mod tests { let addr = tap.insert_account("1".sha3(), "1").unwrap(); tap.unlock_account_permanently(addr, "1".into()).unwrap(); - let spec = new_test_authority(); + let spec = Spec::new_test_round(); let engine = &*spec.engine; let genesis_header = spec.genesis_header(); let mut db_result = get_temp_journal_db(); @@ -322,7 +316,7 @@ mod tests { #[test] fn proposer_switching() { - let engine = new_test_authority().engine; + let engine = Spec::new_test_round().engine; let mut header: Header = Header::default(); let tap = AccountProvider::transient_provider(); let addr = tap.insert_account("0".sha3(), "0").unwrap(); diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 8d96ebc07..96fd76cb6 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -271,11 +271,6 @@ impl Spec { pub fn new_test_round() -> Self { Spec::load(include_bytes!("../../res/authority_round.json") as &[u8]).expect("authority_round.json is invalid") } - - /// Create a new Spec with Tendermint consensus which does internal sealing (not requiring work). - pub fn new_test_tendermint() -> Self { - Spec::load(include_bytes!("../../res/tendermint.json") as &[u8]).expect("tendermint.json is invalid") - } } #[cfg(test)] From 28a088eea747cc0e1ca6ac14345acdd9fb328702 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 14 Sep 2016 11:22:43 +0200 Subject: [PATCH 020/106] add client own_tx import through client --- ethcore/src/client/client.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index b6333902b..02dce97df 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -52,7 +52,7 @@ use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute}; use client::{ BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient, MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode, - ChainNotify + ChainNotify, TransactionImportResult }; use client::Error as ClientError; use env_info::EnvInfo; @@ -477,6 +477,11 @@ impl Client { results.len() } + /// Import a locally created transaction. + pub fn import_own_transaction(&self, transaction: SignedTransaction) -> Result { + self.miner.import_own_transaction(self, transaction) + } + /// Attempt to get a copy of a specific block's final state. /// /// This will not fail if given BlockID::Latest. From 7eac946fdb091c705a23f92a6154ddad84ebe29a Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 14 Sep 2016 11:24:38 +0200 Subject: [PATCH 021/106] remove unused original TestNet method --- sync/src/tests/helpers.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/sync/src/tests/helpers.rs b/sync/src/tests/helpers.rs index dd34ca276..2de012490 100644 --- a/sync/src/tests/helpers.rs +++ b/sync/src/tests/helpers.rs @@ -129,24 +129,6 @@ impl TestNet { net } - pub fn new_with_spec_file(n: usize, reader: R) -> TestNet where R: Read + Clone { - let mut net = TestNet { - peers: Vec::new(), - started: false, - }; - for _ in 0..n { - let chain = TestBlockChainClient::new_with_spec(Spec::load(reader.clone()).expect("Invalid spec file.")); - let sync = ChainSync::new(SyncConfig::default(), &chain); - net.peers.push(TestPeer { - sync: RwLock::new(sync), - snapshot_service: Arc::new(TestSnapshotService::new()), - chain: chain, - queue: VecDeque::new(), - }); - } - net - } - pub fn peer(&self, i: usize) -> &TestPeer { self.peers.get(i).unwrap() } From faa9c7fb1b0bb8b335fdf30e4e8f5d46e4c33eed Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 14 Sep 2016 11:25:23 +0200 Subject: [PATCH 022/106] add new TestNet transaction issuing --- sync/src/tests/test_net.rs | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/sync/src/tests/test_net.rs b/sync/src/tests/test_net.rs index 5d06d0697..0a4b01cf1 100644 --- a/sync/src/tests/test_net.rs +++ b/sync/src/tests/test_net.rs @@ -22,6 +22,8 @@ use ethcore::tests::helpers::*; use ethcore::header::BlockNumber; use ethcore::spec::Spec; use ethcore::snapshot::SnapshotService; +use ethcore::transaction::{Transaction, SignedTransaction, Action}; +use ethkey::{Random, Generator}; use sync_io::SyncIo; use chain::ChainSync; use ::SyncConfig; @@ -92,6 +94,19 @@ pub struct TestPacket { pub recipient: PeerId, } + +pub fn random_transaction() -> SignedTransaction { + let keypair = Random.generate().unwrap(); + Transaction { + action: Action::Create, + value: U256::zero(), + data: "3331600055".from_hex().unwrap(), + gas: U256::from(100_000), + gas_price: U256::zero(), + nonce: U256::zero(), + }.sign(keypair.secret()) +} + pub struct TestPeer { pub chain: Arc, pub snapshot_service: Arc, @@ -99,6 +114,16 @@ pub struct TestPeer { pub queue: VecDeque, } +impl TestPeer { + pub fn issue_tx(&self, transaction: SignedTransaction) { + self.chain.import_own_transaction(transaction); + } + + pub fn issue_rand_tx(&self) { + self.issue_tx(random_transaction()) + } +} + pub struct TestNet { pub peers: Vec, pub started: bool, @@ -130,9 +155,7 @@ impl TestNet { net } - pub fn new_with_spec_file(n: usize, get_spec: &S) -> TestNet where - R: Read + Clone, - S: Fn()->Spec { + pub fn new_with_spec(n: usize, get_spec: &S) -> TestNet where S: Fn()->Spec { let mut net = TestNet { peers: Vec::new(), started: false, From b2c0a9d53138728a1320ae5ee96de407584b60ae Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 14 Sep 2016 11:26:39 +0200 Subject: [PATCH 023/106] introduce ethkey dependency to generate txs --- Cargo.lock | 1 + sync/Cargo.toml | 1 + sync/src/lib.rs | 3 +++ 3 files changed, 5 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 13fbcf9ef..fe4926175 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -611,6 +611,7 @@ dependencies = [ "ethcore-ipc-nano 1.4.0", "ethcore-network 1.4.0", "ethcore-util 1.4.0", + "ethkey 0.2.0", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/sync/Cargo.toml b/sync/Cargo.toml index a73077c9c..a6bdf8c8f 100644 --- a/sync/Cargo.toml +++ b/sync/Cargo.toml @@ -13,6 +13,7 @@ ethcore-ipc-codegen = { path = "../ipc/codegen" } [dependencies] ethcore-util = { path = "../util" } +ethkey = { path = "../ethkey" } ethcore-network = { path = "../util/network" } ethcore-io = { path = "../util/io" } ethcore = { path = "../ethcore" } diff --git a/sync/src/lib.rs b/sync/src/lib.rs index d2c6e2583..81f2e7b9d 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -37,6 +37,9 @@ extern crate semver; extern crate parking_lot; extern crate rlp; +#[cfg(test)] +extern crate ethkey; + #[macro_use] extern crate log; #[macro_use] From 68fd8626719e82cbccf8cb65def0410863734601 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 14 Sep 2016 13:56:28 +0200 Subject: [PATCH 024/106] simplify constructors --- ethcore/src/miner/miner.rs | 45 +++++++++++++++----------------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index ad963b167..660bc6294 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -188,33 +188,14 @@ pub struct Miner { } impl Miner { - /// Creates new instance of miner without accounts, but with given spec. - pub fn with_spec(spec: &Spec) -> Miner { - Miner { - transaction_queue: Arc::new(Mutex::new(TransactionQueue::new())), - options: Default::default(), - next_allowed_reseal: Mutex::new(Instant::now()), - sealing_block_last_request: Mutex::new(0), - sealing_work: Mutex::new(SealingWork{ - queue: UsingQueue::new(20), - enabled: spec.engine.is_default_sealer().unwrap_or(false) - }), - seals_internally: spec.engine.is_default_sealer().is_some(), - gas_range_target: RwLock::new((U256::zero(), U256::zero())), - author: RwLock::new(Address::default()), - extra_data: RwLock::new(Vec::new()), - accounts: None, - engine: spec.engine.clone(), - work_poster: None, - gas_pricer: Mutex::new(GasPricer::new_fixed(20_000_000_000u64.into())), - } - } - - /// Creates new instance of miner - pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Arc { - let work_poster = if !options.new_work_notify.is_empty() { Some(WorkPoster::new(&options.new_work_notify)) } else { None }; + /// Creates new instance of miner. + fn new_raw(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Miner { + let work_poster = match options.new_work_notify.is_empty() { + true => None, + false => Some(WorkPoster::new(&options.new_work_notify)) + }; let txq = Arc::new(Mutex::new(TransactionQueue::with_limits(options.tx_queue_size, options.tx_gas_limit))); - Arc::new(Miner { + Miner { transaction_queue: txq, next_allowed_reseal: Mutex::new(Instant::now()), sealing_block_last_request: Mutex::new(0), @@ -233,7 +214,17 @@ impl Miner { engine: spec.engine.clone(), work_poster: work_poster, gas_pricer: Mutex::new(gas_pricer), - }) + } + } + + /// Creates new instance of miner without accounts, but with given spec. + pub fn with_spec(spec: &Spec) -> Miner { + Miner::new_raw(Default::default(), GasPricer::new_fixed(20_000_000_000u64.into()), spec, None) + } + + /// Creates new instance of a miner Arc. + pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Arc { + Arc::new_raw(Miner::new(options, gas_pricer, spec, accounts) } fn forced_sealing(&self) -> bool { From 7c82a10ecc12c2c49db7590c7162fefe65f1ef32 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 14 Sep 2016 14:34:47 +0200 Subject: [PATCH 025/106] fix typo --- ethcore/src/miner/miner.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 660bc6294..1f2d461eb 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -224,7 +224,7 @@ impl Miner { /// Creates new instance of a miner Arc. pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Arc { - Arc::new_raw(Miner::new(options, gas_pricer, spec, accounts) + Arc::new(Miner::new_raw(options, gas_pricer, spec, accounts)) } fn forced_sealing(&self) -> bool { From c482b8ffb66b6f10334bcfad7a3c2e3a654da5de Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 14 Sep 2016 17:28:15 +0200 Subject: [PATCH 026/106] enable sealing only is authority --- ethcore/src/engines/authority_round.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 8c1cb9ce1..e40f8fbf7 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -108,7 +108,7 @@ impl IoHandler for TransitionHandler { fn timeout(&self, io: &IoContext, timer: TimerToken) { if timer == ENGINE_TIMEOUT_TOKEN { - println!("timeout"); + debug!(target: "authorityround", "timeout"); if let Some(engine) = self.engine.upgrade() { engine.step.fetch_add(1, AtomicOrdering::Relaxed); io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.our_params.step_duration).expect("Failed to restart consensus step timer.") @@ -118,7 +118,7 @@ impl IoHandler for TransitionHandler { fn message(&self, io: &IoContext, _net_message: &BlockArrived) { if let Some(engine) = self.engine.upgrade() { - println!("Message: {:?}", get_time().sec); + trace!(target: "authorityround", "Message: {:?}", get_time().sec); engine.step.fetch_add(1, AtomicOrdering::Relaxed); io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to restart consensus step timer."); io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.our_params.step_duration).expect("Failed to restart consensus step timer.") @@ -159,7 +159,11 @@ impl Engine for AuthorityRound { /// 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) {} - fn is_sealer(&self, _author: &Address) -> Option { Some(true) } + fn is_sealer(&self, author: &Address) -> Option { + let ref p = self.our_params; + Some(p.our_params.authorities.contains(author)) + } + /// Attempt to seal the block internally. /// /// This operation is synchronous and may (quite reasonably) not be available, in which `false` will From bb59c2288ee5e33af91aaff365527dff3c55f37d Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 14 Sep 2016 17:28:57 +0200 Subject: [PATCH 027/106] docs on authority spec --- ethcore/src/spec/spec.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 96fd76cb6..09ea03ac7 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -268,6 +268,7 @@ impl Spec { } /// Create a new Spec with AuthorityRound consensus which does internal sealing (not requiring work). + /// Accounts with secrets "1".sha3() and "2".sha3() are the authorities. pub fn new_test_round() -> Self { Spec::load(include_bytes!("../../res/authority_round.json") as &[u8]).expect("authority_round.json is invalid") } From d3ec8588c4b6033b18095adf0ae9d0faa7e57b25 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 14 Sep 2016 17:29:35 +0200 Subject: [PATCH 028/106] new constructor that takes AccountProvider --- ethcore/src/miner/miner.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 1f2d461eb..4e2774da1 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -217,6 +217,11 @@ impl Miner { } } + /// Creates new instance of miner with accounts and with given spec. + pub fn with_spec_and_accounts(spec: &Spec, accounts: Option>) -> Miner { + Miner::new_raw(Default::default(), GasPricer::new_fixed(20_000_000_000u64.into()), spec, accounts) + } + /// Creates new instance of miner without accounts, but with given spec. pub fn with_spec(spec: &Spec) -> Miner { Miner::new_raw(Default::default(), GasPricer::new_fixed(20_000_000_000u64.into()), spec, None) From 44fe864826a84d73613473b0985b7aab37d1f591 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 14 Sep 2016 17:30:09 +0200 Subject: [PATCH 029/106] new highway to miner, set_author --- ethcore/src/client/client.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 02dce97df..24f93ce90 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -477,10 +477,15 @@ impl Client { results.len() } + // TODO: these are only used for tests in sync and only contribute to huge Client /// Import a locally created transaction. pub fn import_own_transaction(&self, transaction: SignedTransaction) -> Result { self.miner.import_own_transaction(self, transaction) } + /// Set miner author. + pub fn set_author(&self, author: Address) { + self.miner.set_author(author) + } /// Attempt to get a copy of a specific block's final state. /// From 3419549c166ccda07c4aaecbaa29ff53b160f6f8 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 14 Sep 2016 17:30:57 +0200 Subject: [PATCH 030/106] revamp dummy_client to take accounts --- ethcore/src/tests/helpers.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index 3e9839f31..05100e39e 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -28,6 +28,7 @@ use ethereum; use devtools::*; use miner::Miner; use rlp::{self, RlpStream, Stream}; +use account_provider::AccountProvider; #[cfg(feature = "json-tests")] pub enum ChainEra { @@ -125,12 +126,12 @@ pub fn create_test_block_with_data(header: &Header, transactions: &[SignedTransa } pub fn generate_dummy_client(block_number: u32) -> GuardedTempResult> { - generate_dummy_client_with_spec(Spec::new_test, block_number) + generate_dummy_client_with_spec_and_data(Spec::new_null, block_number, 0, &[]) } -pub fn generate_dummy_client_with_spec(get_spec: F, block_number: u32) -> GuardedTempResult> where +pub fn dummy_client_with_spec_and_accounts(get_spec: F, accounts: Option>) -> GuardedTempResult> where F: Fn()->Spec { - generate_dummy_client_with_spec_and_data(get_spec, block_number, 0, &[]) + dummy_client_with_spec_and_data_and_accounts(get_spec, 0, 0, &[], accounts) } pub fn generate_dummy_client_with_data(block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> GuardedTempResult> { @@ -138,6 +139,10 @@ pub fn generate_dummy_client_with_data(block_number: u32, txs_per_block: usize, } pub fn generate_dummy_client_with_spec_and_data(get_test_spec: F, block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> GuardedTempResult> where F: Fn()->Spec { + dummy_client_with_spec_and_data_and_accounts(get_test_spec, block_number, txs_per_block, tx_gas_prices, None) +} + +pub fn dummy_client_with_spec_and_data_and_accounts(get_test_spec: F, block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256], accounts: Option>) -> GuardedTempResult> where F: Fn()->Spec { let dir = RandomTempPath::new(); let test_spec = get_test_spec(); let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS); @@ -146,7 +151,7 @@ pub fn generate_dummy_client_with_spec_and_data(get_test_spec: F, block_numbe ClientConfig::default(), &test_spec, dir.as_path(), - Arc::new(Miner::with_spec(&test_spec)), + Arc::new(Miner::with_spec_and_accounts(&test_spec, accounts)), IoChannel::disconnected(), &db_config ).unwrap(); From b144bd2b845a12bfd445b4a38320efae596e6ed8 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 19 Sep 2016 10:38:47 +0200 Subject: [PATCH 031/106] add tracing --- ethcore/src/client/client.rs | 2 ++ ethcore/src/miner/miner.rs | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 24f93ce90..228751069 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -470,6 +470,7 @@ impl Client { /// Import transactions from the IO queue pub fn import_queued_transactions(&self, transactions: &[Bytes]) -> usize { + trace!(target: "external_tx", "Importing queued"); let _timer = PerfTimer::new("import_queued_transactions"); self.queue_transactions.fetch_sub(transactions.len(), AtomicOrdering::SeqCst); let txs = transactions.iter().filter_map(|bytes| UntrustedRlp::new(bytes).as_val().ok()).collect(); @@ -1060,6 +1061,7 @@ impl BlockChainClient for Client { let len = transactions.len(); match self.io_channel.send(ClientIoMessage::NewTransactions(transactions)) { Ok(_) => { + trace!(target: "external_tx", "Sending IoMessage"); self.queue_transactions.fetch_add(len, AtomicOrdering::SeqCst); } Err(e) => { diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 4e2774da1..931246965 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -78,7 +78,7 @@ impl Default for MinerOptions { MinerOptions { new_work_notify: vec![], force_sealing: false, - reseal_on_external_tx: false, + reseal_on_external_tx: true, reseal_on_own_tx: true, tx_gas_limit: !U256::zero(), tx_queue_size: 1024, @@ -397,6 +397,7 @@ impl Miner { if !block.transactions().is_empty() { if let Ok(sealed) = self.seal_block_internally(block) { if chain.import_block(sealed.rlp_bytes()).is_ok() { + trace!(target: "miner", "import_block_internally: imported internally sealed block"); return true } } @@ -659,7 +660,7 @@ impl MinerService for Miner { chain: &MiningBlockChainClient, transactions: Vec ) -> Vec> { - + trace!(target: "external_tx", "Importing external transactions"); let results = { let mut transaction_queue = self.transaction_queue.lock(); self.add_transactions_to_queue( From b31e732ebe85bed34003eee5b52a9083312e843d Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 19 Sep 2016 10:39:57 +0200 Subject: [PATCH 032/106] temporary is_sealer check disable --- ethcore/src/engines/authority_round.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index e40f8fbf7..2984ef263 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -161,7 +161,8 @@ impl Engine for AuthorityRound { fn is_sealer(&self, author: &Address) -> Option { let ref p = self.our_params; - Some(p.our_params.authorities.contains(author)) + Some(p.authorities.contains(author)); + Some(true) } /// Attempt to seal the block internally. From c6c45db1d06c65ec6019dcbd1b4f6cf634b3f98f Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 19 Sep 2016 10:41:01 +0200 Subject: [PATCH 033/106] add devtools dependency --- sync/Cargo.toml | 1 + sync/src/lib.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/sync/Cargo.toml b/sync/Cargo.toml index a6bdf8c8f..500ae0778 100644 --- a/sync/Cargo.toml +++ b/sync/Cargo.toml @@ -18,6 +18,7 @@ ethcore-network = { path = "../util/network" } ethcore-io = { path = "../util/io" } ethcore = { path = "../ethcore" } rlp = { path = "../util/rlp" } +ethcore-devtools = { path = "../devtools" } clippy = { version = "0.0.85", optional = true} log = "0.3" env_logger = "0.3" diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 81f2e7b9d..376ece6d1 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -39,6 +39,8 @@ extern crate rlp; #[cfg(test)] extern crate ethkey; +#[cfg(test)] +extern crate ethcore_devtools as devtools; #[macro_use] extern crate log; From 6af888f9e4b636d69ac91f695826d2f1f41a86fe Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 19 Sep 2016 10:41:35 +0200 Subject: [PATCH 034/106] disable TemporaryPath panic --- Cargo.lock | 1 + devtools/src/random_path.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index fe4926175..10479bde0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -605,6 +605,7 @@ dependencies = [ "clippy 0.0.85 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore 1.4.0", + "ethcore-devtools 1.4.0", "ethcore-io 1.4.0", "ethcore-ipc 1.4.0", "ethcore-ipc-codegen 1.4.0", diff --git a/devtools/src/random_path.rs b/devtools/src/random_path.rs index d58042512..4c2ac189d 100644 --- a/devtools/src/random_path.rs +++ b/devtools/src/random_path.rs @@ -77,7 +77,7 @@ impl Drop for RandomTempPath { fn drop(&mut self) { if let Err(_) = fs::remove_dir_all(&self) { if let Err(e) = fs::remove_file(&self) { - panic!("Failed to remove temp directory. Here's what prevented this from happening: ({})", e); + println!("Failed to remove temp directory. Here's what prevented this from happening: ({})", e); } } } From fdcda41280845aa2734edd7961be20a3225cae56 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 19 Sep 2016 10:42:36 +0200 Subject: [PATCH 035/106] add tracing --- sync/src/chain.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sync/src/chain.rs b/sync/src/chain.rs index ea5e593f3..771099905 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -906,6 +906,7 @@ impl ChainSync { /// Resume downloading fn continue_sync(&mut self, io: &mut SyncIo) { + trace!(target:"sync", "continue_sync"); let mut peers: Vec<(PeerId, U256, u32)> = self.peers.iter().filter_map(|(k, p)| if p.can_sync() { Some((*k, p.difficulty.unwrap_or_else(U256::zero), p.protocol_version)) } else { None }).collect(); thread_rng().shuffle(&mut peers); //TODO: sort by rating @@ -1249,6 +1250,7 @@ impl ChainSync { /// Called when peer sends us new transactions fn on_peer_transactions(&mut self, io: &mut SyncIo, peer_id: PeerId, r: &UntrustedRlp) -> Result<(), PacketDecodeError> { + trace!(target: "sync", "Received tx from {}", peer_id); // accepting transactions once only fully synced if !io.is_chain_queue_empty() { return Ok(()); @@ -1701,7 +1703,6 @@ impl ChainSync { /// propagates new transactions to all peers pub fn propagate_new_transactions(&mut self, io: &mut SyncIo) -> usize { - // Early out of nobody to send to. if self.peers.is_empty() { return 0; @@ -1793,6 +1794,7 @@ impl ChainSync { /// called when block is imported to chain - propagates the blocks and updates transactions sent to peers pub fn chain_new_blocks(&mut self, io: &mut SyncIo, _imported: &[H256], invalid: &[H256], _enacted: &[H256], _retracted: &[H256], sealed: &[H256]) { if io.is_chain_queue_empty() { + trace!(target: "sync", "Chain not empty!"); self.propagate_latest_blocks(io, sealed); } if !invalid.is_empty() { From 49b8e144fd9210212c574f1d59b556fe6fbe62ab Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 19 Sep 2016 10:43:01 +0200 Subject: [PATCH 036/106] initial mocknet with chain notify --- sync/src/tests/consensus.rs | 212 ++----------------------- sync/src/tests/helpers.rs | 1 - sync/src/tests/mocknet.rs | 306 ++++++++++++++++++++++++++++++++++++ sync/src/tests/mod.rs | 2 +- sync/src/tests/test_net.rs | 257 ------------------------------ 5 files changed, 319 insertions(+), 459 deletions(-) create mode 100644 sync/src/tests/mocknet.rs delete mode 100644 sync/src/tests/test_net.rs diff --git a/sync/src/tests/consensus.rs b/sync/src/tests/consensus.rs index 9835132f7..db0b7d646 100644 --- a/sync/src/tests/consensus.rs +++ b/sync/src/tests/consensus.rs @@ -15,211 +15,23 @@ // along with Parity. If not, see . use util::*; -use ethcore::client::{Client, BlockChainClient, BlockID, EachBlockWith}; -use chain::{SyncState}; -use super::test_net::*; +use ethcore::spec::Spec; +use super::mocknet::*; +use std::thread::sleep; +use std::time::Duration; +use ethcore::client::BlockChainClient; #[test] -fn two_peers() { +fn issue_tx() { ::env_logger::init().ok(); - let mut net = TestNet::new(3); - net.peer_mut(1).chain.add_blocks(1000, EachBlockWith::Uncle); - net.peer_mut(2).chain.add_blocks(1000, EachBlockWith::Uncle); + let mut net = MockNet::new_with_spec(2, vec!["1".sha3()], &Spec::new_test_round); + net.peer(1).issue_rand_tx(); + sleep(Duration::from_secs(1)); net.sync(); - assert!(net.peer(0).chain.block(BlockID::Number(1000)).is_some()); - assert_eq!(*net.peer(0).chain.blocks.read(), *net.peer(1).chain.blocks.read()); -} - -#[test] -fn long_chain() { - ::env_logger::init().ok(); - let mut net = TestNet::new(2); - net.peer_mut(1).chain.add_blocks(50000, EachBlockWith::Nothing); + sleep(Duration::from_secs(1)); net.sync(); - assert!(net.peer(0).chain.block(BlockID::Number(50000)).is_some()); - assert_eq!(*net.peer(0).chain.blocks.read(), *net.peer(1).chain.blocks.read()); -} - -#[test] -fn status_after_sync() { - ::env_logger::init().ok(); - let mut net = TestNet::new(3); - net.peer_mut(1).chain.add_blocks(1000, EachBlockWith::Uncle); - net.peer_mut(2).chain.add_blocks(1000, EachBlockWith::Uncle); net.sync(); - let status = net.peer(0).sync.read().status(); - assert_eq!(status.state, SyncState::Idle); -} - -#[test] -fn takes_few_steps() { - let mut net = TestNet::new(3); - net.peer_mut(1).chain.add_blocks(100, EachBlockWith::Uncle); - net.peer_mut(2).chain.add_blocks(100, EachBlockWith::Uncle); - let total_steps = net.sync(); - assert!(total_steps < 20); -} - -#[test] -fn empty_blocks() { - ::env_logger::init().ok(); - let mut net = TestNet::new(3); - for n in 0..200 { - let with = if n % 2 == 0 { EachBlockWith::Nothing } else { EachBlockWith::Uncle }; - net.peer_mut(1).chain.add_blocks(5, with.clone()); - net.peer_mut(2).chain.add_blocks(5, with); - } net.sync(); - assert!(net.peer(0).chain.block(BlockID::Number(1000)).is_some()); - assert_eq!(*net.peer(0).chain.blocks.read(), *net.peer(1).chain.blocks.read()); + println!("{:?}", net.peer(0).client.chain_info()); + println!("{:?}", net.peer(1).client.chain_info()); } - -#[test] -fn forked() { - ::env_logger::init().ok(); - let mut net = TestNet::new(3); - net.peer_mut(0).chain.add_blocks(300, EachBlockWith::Uncle); - net.peer_mut(1).chain.add_blocks(300, EachBlockWith::Uncle); - net.peer_mut(2).chain.add_blocks(300, EachBlockWith::Uncle); - net.peer_mut(0).chain.add_blocks(100, EachBlockWith::Nothing); //fork - net.peer_mut(1).chain.add_blocks(200, EachBlockWith::Uncle); - net.peer_mut(2).chain.add_blocks(200, EachBlockWith::Uncle); - net.peer_mut(1).chain.add_blocks(100, EachBlockWith::Uncle); //fork between 1 and 2 - net.peer_mut(2).chain.add_blocks(10, EachBlockWith::Nothing); - // peer 1 has the best chain of 601 blocks - let peer1_chain = net.peer(1).chain.numbers.read().clone(); - net.sync(); - assert_eq!(*net.peer(0).chain.difficulty.read(), *net.peer(1).chain.difficulty.read()); - assert_eq!(&*net.peer(0).chain.numbers.read(), &peer1_chain); - assert_eq!(&*net.peer(1).chain.numbers.read(), &peer1_chain); - assert_eq!(&*net.peer(2).chain.numbers.read(), &peer1_chain); -} - -#[test] -fn net_hard_fork() { - ::env_logger::init().ok(); - let ref_client = TestBlockChainClient::new(); - ref_client.add_blocks(50, EachBlockWith::Uncle); - { - let mut net = TestNet::new_with_fork(2, Some((50, ref_client.block_hash(BlockID::Number(50)).unwrap()))); - net.peer_mut(0).chain.add_blocks(100, EachBlockWith::Uncle); - net.sync(); - assert_eq!(net.peer(1).chain.chain_info().best_block_number, 100); - } - { - let mut net = TestNet::new_with_fork(2, Some((50, ref_client.block_hash(BlockID::Number(50)).unwrap()))); - net.peer_mut(0).chain.add_blocks(100, EachBlockWith::Nothing); - net.sync(); - assert_eq!(net.peer(1).chain.chain_info().best_block_number, 0); - } -} - -#[test] -fn restart() { - let mut net = TestNet::new(3); - net.peer_mut(1).chain.add_blocks(1000, EachBlockWith::Uncle); - net.peer_mut(2).chain.add_blocks(1000, EachBlockWith::Uncle); - - net.sync_steps(8); - - // make sure that sync has actually happened - assert!(net.peer(0).chain.chain_info().best_block_number > 100); - net.restart_peer(0); - - let status = net.peer(0).sync.read().status(); - assert_eq!(status.state, SyncState::Idle); -} - -#[test] -fn status_empty() { - let net = TestNet::new(2); - assert_eq!(net.peer(0).sync.read().status().state, SyncState::Idle); -} - -#[test] -fn status_packet() { - let mut net = TestNet::new(2); - net.peer_mut(0).chain.add_blocks(100, EachBlockWith::Uncle); - net.peer_mut(1).chain.add_blocks(1, EachBlockWith::Uncle); - - net.start(); - - net.sync_step_peer(0); - - assert_eq!(1, net.peer(0).queue.len()); - assert_eq!(0x00, net.peer(0).queue[0].packet_id); -} - -#[test] -fn propagate_hashes() { - let mut net = TestNet::new(6); - net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Uncle); - net.sync(); - - net.peer_mut(0).chain.add_blocks(10, EachBlockWith::Uncle); - net.sync(); - net.trigger_chain_new_blocks(0); //first event just sets the marker - net.trigger_chain_new_blocks(0); - - // 5 peers with NewHahses, 4 with blocks - assert_eq!(9, net.peer(0).queue.len()); - let mut hashes = 0; - let mut blocks = 0; - for i in 0..net.peer(0).queue.len() { - if net.peer(0).queue[i].packet_id == 0x1 { - hashes += 1; - } - if net.peer(0).queue[i].packet_id == 0x7 { - blocks += 1; - } - } - assert_eq!(blocks, 4); - assert_eq!(hashes, 5); -} - -#[test] -fn propagate_blocks() { - let mut net = TestNet::new(20); - net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Uncle); - net.sync(); - - net.peer_mut(0).chain.add_blocks(10, EachBlockWith::Uncle); - net.trigger_chain_new_blocks(0); //first event just sets the marker - net.trigger_chain_new_blocks(0); - - assert!(!net.peer(0).queue.is_empty()); - // NEW_BLOCK_PACKET - let blocks = net.peer(0).queue.iter().filter(|p| p.packet_id == 0x7).count(); - assert!(blocks > 0); -} - -#[test] -fn restart_on_malformed_block() { - let mut net = TestNet::new(2); - net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Uncle); - net.peer_mut(1).chain.corrupt_block(6); - net.sync_steps(20); - - assert_eq!(net.peer(0).chain.chain_info().best_block_number, 5); -} - -#[test] -fn restart_on_broken_chain() { - let mut net = TestNet::new(2); - net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Uncle); - net.peer_mut(1).chain.corrupt_block_parent(6); - net.sync_steps(20); - - assert_eq!(net.peer(0).chain.chain_info().best_block_number, 5); -} - -#[test] -fn high_td_attach() { - let mut net = TestNet::new(2); - net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Uncle); - net.peer_mut(1).chain.corrupt_block_parent(6); - net.sync_steps(20); - - assert_eq!(net.peer(0).chain.chain_info().best_block_number, 5); -} - diff --git a/sync/src/tests/helpers.rs b/sync/src/tests/helpers.rs index 2de012490..cbed49eff 100644 --- a/sync/src/tests/helpers.rs +++ b/sync/src/tests/helpers.rs @@ -19,7 +19,6 @@ use network::*; use tests::snapshot::*; use ethcore::client::{TestBlockChainClient, BlockChainClient}; use ethcore::header::BlockNumber; -use ethcore::spec::Spec; use ethcore::snapshot::SnapshotService; use sync_io::SyncIo; use chain::ChainSync; diff --git a/sync/src/tests/mocknet.rs b/sync/src/tests/mocknet.rs new file mode 100644 index 000000000..1091e868f --- /dev/null +++ b/sync/src/tests/mocknet.rs @@ -0,0 +1,306 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use util::*; +use network::*; +use tests::snapshot::*; +use ethcore::client::{Client, BlockChainClient, ChainNotify}; +use ethcore::spec::Spec; +use ethcore::snapshot::SnapshotService; +use ethcore::transaction::{Transaction, SignedTransaction, Action}; +use ethcore::account_provider::AccountProvider; +use ethkey::{Random, Generator}; +use sync_io::SyncIo; +use chain::ChainSync; +use ::SyncConfig; +use devtools::RandomTempPath; +use ethcore::miner::Miner; +use ethcore::service::ClientService; +use std::time::Duration; +use std::thread::sleep; + +pub struct TestIo<'p> { + pub client: Arc, + pub snapshot_service: &'p TestSnapshotService, + pub queue: &'p mut VecDeque, + pub sender: Option, +} + +impl<'p> TestIo<'p> { + pub fn new(client: Arc, ss: &'p TestSnapshotService, queue: &'p mut VecDeque, sender: Option) -> TestIo<'p> { + TestIo { + client: client, + snapshot_service: ss, + queue: queue, + sender: sender + } + } +} + +impl<'p> SyncIo for TestIo<'p> { + fn disable_peer(&mut self, _peer_id: PeerId) { + } + + fn disconnect_peer(&mut self, _peer_id: PeerId) { + } + + fn is_expired(&self) -> bool { + false + } + + fn respond(&mut self, packet_id: PacketId, data: Vec) -> Result<(), NetworkError> { + self.queue.push_back(TestPacket { + data: data, + packet_id: packet_id, + recipient: self.sender.unwrap() + }); + Ok(()) + } + + fn send(&mut self, peer_id: PeerId, packet_id: PacketId, data: Vec) -> Result<(), NetworkError> { + self.queue.push_back(TestPacket { + data: data, + packet_id: packet_id, + recipient: peer_id, + }); + Ok(()) + } + + fn chain(&self) -> &BlockChainClient { + self.client.as_ref() + } + + fn snapshot_service(&self) -> &SnapshotService { + self.snapshot_service + } + + fn eth_protocol_version(&self, _peer: PeerId) -> u8 { + 64 + } +} + +pub struct TestPacket { + pub data: Bytes, + pub packet_id: PacketId, + pub recipient: PeerId, +} + +fn transaction() -> Transaction { + Transaction { + action: Action::Create, + value: U256::zero(), + data: "3331600055".from_hex().unwrap(), + gas: U256::from(100_000), + gas_price: U256::zero(), + nonce: U256::zero(), + } +} + +pub fn random_transaction() -> SignedTransaction { + let keypair = Random.generate().unwrap(); + transaction().sign(&keypair.secret()) +} + +pub struct MockPeer { + pub client: Arc, + pub snapshot_service: Arc, + pub sync: RwLock, + pub queue: RwLock>, + _service: ClientService, + _paths: Vec, +} + +impl ChainNotify for MockPeer { + fn new_blocks(&self, + imported: Vec, + invalid: Vec, + enacted: Vec, + retracted: Vec, + sealed: Vec, + _duration: u64) { + println!("New sync blocks"); + let ref mut q = *self.queue.write(); + let mut sync_io = TestIo::new( + self.client.clone(), + &self.snapshot_service, + q, + None); + self.sync.write().chain_new_blocks( + &mut sync_io, + &imported, + &invalid, + &enacted, + &retracted, + &sealed); + } +} + +impl MockPeer { + pub fn new_with_spec(get_spec: &S, author_secret: Option) -> Arc where S: Fn()->Spec { + let (accounts, address) = if let Some(secret) = author_secret { + let tap = AccountProvider::transient_provider(); + let addr = tap.insert_account(secret, "").unwrap(); + tap.unlock_account_permanently(addr, "".into()).unwrap(); + (Some(Arc::new(tap)), Some(addr)) + } else { + (None, None) + }; + + let client_path = RandomTempPath::new(); + let snapshot_path = RandomTempPath::new(); + let ipc_path = RandomTempPath::new(); + let spec = get_spec(); + + let service = ClientService::start( + Default::default(), + &spec, + client_path.as_path(), + snapshot_path.as_path(), + ipc_path.as_path(), + Arc::new(Miner::with_spec_and_accounts(&spec, accounts.clone())), + ).unwrap(); + + let client = service.client(); + if let Some(addr) = address { client.set_author(addr) } + let sync = ChainSync::new(SyncConfig::default(), &*client); + + let peer = Arc::new(MockPeer { + sync: RwLock::new(sync), + snapshot_service: Arc::new(TestSnapshotService::new()), + client: client, + queue: RwLock::new(VecDeque::new()), + _service: service, + _paths: vec![client_path, snapshot_path, ipc_path] + }); + peer.client.add_notify(peer.clone()); + peer + } + + pub fn issue_tx(&self, transaction: SignedTransaction) { + self.client.import_own_transaction(transaction).unwrap(); + } + + pub fn issue_rand_tx(&self) { + self.issue_tx(random_transaction()) + } +} + +pub struct MockNet { + pub peers: Vec>, + pub started: bool, +} + +impl MockNet { + pub fn new_with_spec(nodes: usize, author_secrets: Vec, get_spec: &S) -> MockNet where S: Fn()->Spec { + let mut net = MockNet { + peers: Vec::new(), + started: false, + }; + for secret in author_secrets { + net.peers.push(MockPeer::new_with_spec(get_spec, Some(secret))); + } + for _ in net.peers.len()..nodes { + net.peers.push(MockPeer::new_with_spec(get_spec, None)); + } + net + } + + pub fn peer(&self, i: usize) -> Arc { + self.peers.get(i).unwrap().clone() + } + + pub fn peer_mut(&mut self, i: usize) -> &mut MockPeer { + Arc::get_mut(self.peers.get_mut(i).unwrap()).unwrap() + } + + pub fn start(&mut self) { + for peer in 0..self.peers.len() { + for client in 0..self.peers.len() { + if peer != client { + let p = self.peers.get_mut(peer).unwrap(); + let mut q = p.queue.write(); + p.sync.write().on_peer_connected(&mut TestIo::new(p.client.clone(), + &p.snapshot_service, + &mut *q, + Some(client as PeerId)), + client as PeerId); + } + } + } + } + + pub fn sync_step(&mut self) { + for (i, peer0) in self.peers.iter().enumerate() { + let mut q0 = peer0.queue.write(); + if let Some(packet) = q0.pop_front() { + let p = self.peers.get(packet.recipient).unwrap(); + let mut q1 = p.queue.write(); + trace!(target: "mocknet", "--- {} -> {} ---", i, packet.recipient); + ChainSync::dispatch_packet(&p.sync, + &mut TestIo::new(p.client.clone(), + &p.snapshot_service, + &mut *q1, + Some(i as PeerId)), + i as PeerId, + packet.packet_id, + &packet.data); + trace!(target: "mocknet", "----------------"); + } + let p = self.peers.get(i).unwrap(); + peer0.client.flush_queue(); + let mut io = TestIo::new(peer0.client.clone(), &peer0.snapshot_service, &mut *q0, None); + p.sync.write().maintain_sync(&mut io); + p.sync.write().propagate_new_transactions(&mut io); + sleep(Duration::from_secs(2)); + } + } + + pub fn sync_step_peer(&mut self, peer_num: usize) { + let mut peer = self.peer_mut(peer_num); + let ref mut q = *peer.queue.write(); + peer.sync.write().maintain_sync(&mut TestIo::new(peer.client.clone(), &peer.snapshot_service, q, None)); + } + + pub fn restart_peer(&mut self, i: usize) { + let peer = self.peer_mut(i); + let ref mut q = *peer.queue.write(); + peer.sync.write().restart(&mut TestIo::new(peer.client.clone(), &peer.snapshot_service, q, None)); + } + + pub fn sync(&mut self) -> u32 { + self.start(); + let mut total_steps = 0; + while !self.done() { + self.sync_step(); + total_steps += 1; + } + total_steps + } + + pub fn sync_steps(&mut self, count: usize) { + if !self.started { + self.start(); + self.started = true; + } + for _ in 0..count { + self.sync_step(); + } + } + + pub fn done(&self) -> bool { + self.peers.iter().all(|p| p.queue.try_read().unwrap().is_empty()) + } +} diff --git a/sync/src/tests/mod.rs b/sync/src/tests/mod.rs index ffd858e54..e64792d33 100644 --- a/sync/src/tests/mod.rs +++ b/sync/src/tests/mod.rs @@ -16,7 +16,7 @@ pub mod helpers; pub mod snapshot; -pub mod test_net; +pub mod mocknet; mod chain; mod rpc; mod consensus; diff --git a/sync/src/tests/test_net.rs b/sync/src/tests/test_net.rs deleted file mode 100644 index 0a4b01cf1..000000000 --- a/sync/src/tests/test_net.rs +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. -// This file is part of Parity. - -// Parity is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity. If not, see . - -use util::*; -use network::*; -use tests::snapshot::*; -use ethcore::client::{Client, BlockChainClient}; -use ethcore::tests::helpers::*; -use ethcore::header::BlockNumber; -use ethcore::spec::Spec; -use ethcore::snapshot::SnapshotService; -use ethcore::transaction::{Transaction, SignedTransaction, Action}; -use ethkey::{Random, Generator}; -use sync_io::SyncIo; -use chain::ChainSync; -use ::SyncConfig; - -pub struct TestIo<'p> { - pub chain: &'p mut Client, - pub snapshot_service: &'p TestSnapshotService, - pub queue: &'p mut VecDeque, - pub sender: Option, -} - -impl<'p> TestIo<'p> { - pub fn new(chain: &'p mut Arc, ss: &'p TestSnapshotService, queue: &'p mut VecDeque, sender: Option) -> TestIo<'p> { - TestIo { - chain: Arc::get_mut(chain).unwrap(), - snapshot_service: ss, - queue: queue, - sender: sender - } - } -} - -impl<'p> SyncIo for TestIo<'p> { - fn disable_peer(&mut self, _peer_id: PeerId) { - } - - fn disconnect_peer(&mut self, _peer_id: PeerId) { - } - - fn is_expired(&self) -> bool { - false - } - - fn respond(&mut self, packet_id: PacketId, data: Vec) -> Result<(), NetworkError> { - self.queue.push_back(TestPacket { - data: data, - packet_id: packet_id, - recipient: self.sender.unwrap() - }); - Ok(()) - } - - fn send(&mut self, peer_id: PeerId, packet_id: PacketId, data: Vec) -> Result<(), NetworkError> { - self.queue.push_back(TestPacket { - data: data, - packet_id: packet_id, - recipient: peer_id, - }); - Ok(()) - } - - fn chain(&self) -> &BlockChainClient { - self.chain - } - - fn snapshot_service(&self) -> &SnapshotService { - self.snapshot_service - } - - fn eth_protocol_version(&self, _peer: PeerId) -> u8 { - 64 - } -} - -pub struct TestPacket { - pub data: Bytes, - pub packet_id: PacketId, - pub recipient: PeerId, -} - - -pub fn random_transaction() -> SignedTransaction { - let keypair = Random.generate().unwrap(); - Transaction { - action: Action::Create, - value: U256::zero(), - data: "3331600055".from_hex().unwrap(), - gas: U256::from(100_000), - gas_price: U256::zero(), - nonce: U256::zero(), - }.sign(keypair.secret()) -} - -pub struct TestPeer { - pub chain: Arc, - pub snapshot_service: Arc, - pub sync: RwLock, - pub queue: VecDeque, -} - -impl TestPeer { - pub fn issue_tx(&self, transaction: SignedTransaction) { - self.chain.import_own_transaction(transaction); - } - - pub fn issue_rand_tx(&self) { - self.issue_tx(random_transaction()) - } -} - -pub struct TestNet { - pub peers: Vec, - pub started: bool, -} - -impl TestNet { - pub fn new(n: usize) -> TestNet { - Self::new_with_fork(n, None) - } - - pub fn new_with_fork(n: usize, fork: Option<(BlockNumber, H256)>) -> TestNet { - let mut net = TestNet { - peers: Vec::new(), - started: false, - }; - for _ in 0..n { - let mut chain = generate_dummy_client(1); - let mut config = SyncConfig::default(); - config.fork_block = fork; - let ss = Arc::new(TestSnapshotService::new()); - let sync = ChainSync::new(config, chain.reference().as_ref()); - net.peers.push(TestPeer { - sync: RwLock::new(sync), - snapshot_service: ss, - chain: chain.take(), - queue: VecDeque::new(), - }); - } - net - } - - pub fn new_with_spec(n: usize, get_spec: &S) -> TestNet where S: Fn()->Spec { - let mut net = TestNet { - peers: Vec::new(), - started: false, - }; - for _ in 0..n { - let mut chain = generate_dummy_client_with_spec(get_spec, 1); - let sync = ChainSync::new(SyncConfig::default(), chain.reference().as_ref()); - net.peers.push(TestPeer { - sync: RwLock::new(sync), - snapshot_service: Arc::new(TestSnapshotService::new()), - chain: chain.take(), - queue: VecDeque::new(), - }); - } - net - } - - pub fn peer(&self, i: usize) -> &TestPeer { - self.peers.get(i).unwrap() - } - - pub fn peer_mut(&mut self, i: usize) -> &mut TestPeer { - self.peers.get_mut(i).unwrap() - } - - pub fn start(&mut self) { - for peer in 0..self.peers.len() { - for client in 0..self.peers.len() { - if peer != client { - let mut p = self.peers.get_mut(peer).unwrap(); - p.sync.write().on_peer_connected(&mut TestIo::new(&mut p.chain, - &p.snapshot_service, - &mut p.queue, - Some(client as PeerId)), - client as PeerId); - } - } - } - } - - pub fn sync_step(&mut self) { - for peer in 0..self.peers.len() { - if let Some(packet) = self.peers[peer].queue.pop_front() { - let mut p = self.peers.get_mut(packet.recipient).unwrap(); - trace!("--- {} -> {} ---", peer, packet.recipient); - ChainSync::dispatch_packet(&p.sync, - &mut TestIo::new(&mut p.chain, - &p.snapshot_service, - &mut p.queue, - Some(peer as PeerId)), - peer as PeerId, - packet.packet_id, - &packet.data); - trace!("----------------"); - } - let mut p = self.peers.get_mut(peer).unwrap(); - p.sync.write().maintain_sync(&mut TestIo::new(&mut p.chain, &p.snapshot_service, &mut p.queue, None)); - } - } - - pub fn sync_step_peer(&mut self, peer_num: usize) { - let mut peer = self.peer_mut(peer_num); - peer.sync.write().maintain_sync(&mut TestIo::new(&mut peer.chain, &peer.snapshot_service, &mut peer.queue, None)); - } - - pub fn restart_peer(&mut self, i: usize) { - let peer = self.peer_mut(i); - peer.sync.write().restart(&mut TestIo::new(&mut peer.chain, &peer.snapshot_service, &mut peer.queue, None)); - } - - pub fn sync(&mut self) -> u32 { - self.start(); - let mut total_steps = 0; - while !self.done() { - self.sync_step(); - total_steps += 1; - } - total_steps - } - - pub fn sync_steps(&mut self, count: usize) { - if !self.started { - self.start(); - self.started = true; - } - for _ in 0..count { - self.sync_step(); - } - } - - pub fn done(&self) -> bool { - self.peers.iter().all(|p| p.queue.is_empty()) - } - - pub fn trigger_chain_new_blocks(&mut self, peer_id: usize) { - let mut peer = self.peer_mut(peer_id); - peer.sync.write().chain_new_blocks(&mut TestIo::new(&mut peer.chain, &peer.snapshot_service, &mut peer.queue, None), &[], &[], &[], &[], &[]); - } -} From ce3e8750c97e7654ce2cbe4fa092b9b5f7a82653 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 19 Sep 2016 14:33:11 +0200 Subject: [PATCH 037/106] additional consensus test --- ethcore/src/engines/authority_round.rs | 3 +-- sync/src/tests/consensus.rs | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 2984ef263..dc388da6b 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -161,8 +161,7 @@ impl Engine for AuthorityRound { fn is_sealer(&self, author: &Address) -> Option { let ref p = self.our_params; - Some(p.authorities.contains(author)); - Some(true) + Some(p.authorities.contains(author)) } /// Attempt to seal the block internally. diff --git a/sync/src/tests/consensus.rs b/sync/src/tests/consensus.rs index db0b7d646..72109893b 100644 --- a/sync/src/tests/consensus.rs +++ b/sync/src/tests/consensus.rs @@ -28,10 +28,32 @@ fn issue_tx() { net.peer(1).issue_rand_tx(); sleep(Duration::from_secs(1)); net.sync(); - sleep(Duration::from_secs(1)); net.sync(); net.sync(); net.sync(); println!("{:?}", net.peer(0).client.chain_info()); println!("{:?}", net.peer(1).client.chain_info()); } + +#[test] +fn issue_many() { + ::env_logger::init().ok(); + let mut net = MockNet::new_with_spec(2, vec!["1".sha3()], &Spec::new_test_round); + net.peer(1).issue_rand_tx(); + net.peer(1).issue_rand_tx(); + net.peer(1).issue_rand_tx(); + net.peer(1).issue_rand_tx(); + net.peer(1).issue_rand_tx(); + sleep(Duration::from_secs(1)); + net.sync(); + net.sync(); + net.peer(0).issue_rand_tx(); + net.peer(0).issue_rand_tx(); + net.peer(0).issue_rand_tx(); + net.peer(0).issue_rand_tx(); + net.peer(0).issue_rand_tx(); + net.sync(); + net.sync(); + //println!("{:?}", net.peer(0).client.chain_info()); + //println!("{:?}", net.peer(1).client.chain_info()); +} From 7f177f27d811468a998acd3f4b5deb42c8f12688 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 19 Sep 2016 14:52:43 +0200 Subject: [PATCH 038/106] revert dummy_client changes --- ethcore/src/tests/helpers.rs | 27 ++------------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index 05100e39e..c1f99f434 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -28,7 +28,6 @@ use ethereum; use devtools::*; use miner::Miner; use rlp::{self, RlpStream, Stream}; -use account_provider::AccountProvider; #[cfg(feature = "json-tests")] pub enum ChainEra { @@ -37,7 +36,6 @@ pub enum ChainEra { DaoHardfork, } -/// Engine for testing nested calls. pub struct TestEngine { engine: Arc, max_depth: usize @@ -126,12 +124,7 @@ pub fn create_test_block_with_data(header: &Header, transactions: &[SignedTransa } pub fn generate_dummy_client(block_number: u32) -> GuardedTempResult> { - generate_dummy_client_with_spec_and_data(Spec::new_null, block_number, 0, &[]) -} - -pub fn dummy_client_with_spec_and_accounts(get_spec: F, accounts: Option>) -> GuardedTempResult> where - F: Fn()->Spec { - dummy_client_with_spec_and_data_and_accounts(get_spec, 0, 0, &[], accounts) + generate_dummy_client_with_spec_and_data(Spec::new_test, block_number, 0, &[]) } pub fn generate_dummy_client_with_data(block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> GuardedTempResult> { @@ -139,10 +132,6 @@ pub fn generate_dummy_client_with_data(block_number: u32, txs_per_block: usize, } pub fn generate_dummy_client_with_spec_and_data(get_test_spec: F, block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> GuardedTempResult> where F: Fn()->Spec { - dummy_client_with_spec_and_data_and_accounts(get_test_spec, block_number, txs_per_block, tx_gas_prices, None) -} - -pub fn dummy_client_with_spec_and_data_and_accounts(get_test_spec: F, block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256], accounts: Option>) -> GuardedTempResult> where F: Fn()->Spec { let dir = RandomTempPath::new(); let test_spec = get_test_spec(); let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS); @@ -151,7 +140,7 @@ pub fn dummy_client_with_spec_and_data_and_accounts(get_test_spec: F, block_n ClientConfig::default(), &test_spec, dir.as_path(), - Arc::new(Miner::with_spec_and_accounts(&test_spec, accounts)), + Arc::new(Miner::with_spec(&test_spec)), IoChannel::disconnected(), &db_config ).unwrap(); @@ -277,7 +266,6 @@ pub fn get_test_client_with_blocks(blocks: Vec) -> GuardedTempResult Arc { Arc::new( Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), path) @@ -285,7 +273,6 @@ fn new_db(path: &str) -> Arc { ) } -/// Make blockchain. pub fn generate_dummy_blockchain(block_number: u32) -> GuardedTempResult { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); @@ -304,7 +291,6 @@ pub fn generate_dummy_blockchain(block_number: u32) -> GuardedTempResult GuardedTempResult { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); @@ -324,7 +310,6 @@ pub fn generate_dummy_blockchain_with_extra(block_number: u32) -> GuardedTempRes } } -/// Make blochchain. pub fn generate_dummy_empty_blockchain() -> GuardedTempResult { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); @@ -336,7 +321,6 @@ pub fn generate_dummy_empty_blockchain() -> GuardedTempResult { } } -/// Temporary journal db at random path. pub fn get_temp_journal_db() -> GuardedTempResult> { let temp = RandomTempPath::new(); let journal_db = get_temp_journal_db_in(temp.as_path()); @@ -347,7 +331,6 @@ pub fn get_temp_journal_db() -> GuardedTempResult> { } } -/// Temporary state. pub fn get_temp_state() -> GuardedTempResult { let temp = RandomTempPath::new(); let journal_db = get_temp_journal_db_in(temp.as_path()); @@ -358,25 +341,21 @@ pub fn get_temp_state() -> GuardedTempResult { } } -/// Temporary journal db. pub fn get_temp_journal_db_in(path: &Path) -> Box { let db = new_db(path.to_str().expect("Only valid utf8 paths for tests.")); journaldb::new(db.clone(), journaldb::Algorithm::EarlyMerge, None) } -/// Temporary state db. pub fn get_temp_state_in(path: &Path) -> State { let journal_db = get_temp_journal_db_in(path); State::new(journal_db, U256::from(0), Default::default()) } -/// Sequence of good blocks. pub fn get_good_dummy_block_seq(count: usize) -> Vec { let test_spec = get_test_spec(); get_good_dummy_block_fork_seq(1, count, &test_spec.genesis_header().hash()) } -/// Forked sequence of good blocks. pub fn get_good_dummy_block_fork_seq(start_number: usize, count: usize, parent_hash: &H256) -> Vec { let test_spec = get_test_spec(); let test_engine = &test_spec.engine; @@ -401,7 +380,6 @@ pub fn get_good_dummy_block_fork_seq(start_number: usize, count: usize, parent_h r } -/// Good block. pub fn get_good_dummy_block() -> Bytes { let mut block_header = Header::new(); let test_spec = get_test_spec(); @@ -416,7 +394,6 @@ pub fn get_good_dummy_block() -> Bytes { create_test_block(&block_header) } -/// Block with bad state. pub fn get_bad_state_dummy_block() -> Bytes { let mut block_header = Header::new(); let test_spec = get_test_spec(); From d84f94975f2fb1ebd9d69ec7e522473705e76a4f Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 19 Sep 2016 18:00:39 +0200 Subject: [PATCH 039/106] add proposer step to seal --- ethcore/src/engines/authority_round.rs | 63 ++++++++++++++++---------- sync/src/tests/consensus.rs | 2 +- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index dc388da6b..54d236a0c 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -80,13 +80,17 @@ impl AuthorityRound { engine } - fn proposer(&self) -> &Address { - let ref p = self.our_params; - p.authorities.get(self.step.load(AtomicOrdering::Relaxed)%p.authority_n).unwrap() + fn step(&self) -> usize { + self.step.load(AtomicOrdering::SeqCst) } - fn is_proposer(&self, address: &Address) -> bool { - self.proposer() == address + fn step_proposer(&self, step: usize) -> &Address { + let ref p = self.our_params; + p.authorities.get(step%p.authority_n).unwrap() + } + + fn is_step_proposer(&self, step: usize, address: &Address) -> bool { + self.step_proposer(step) == address } } @@ -110,7 +114,7 @@ impl IoHandler for TransitionHandler { if timer == ENGINE_TIMEOUT_TOKEN { debug!(target: "authorityround", "timeout"); if let Some(engine) = self.engine.upgrade() { - engine.step.fetch_add(1, AtomicOrdering::Relaxed); + engine.step.fetch_add(1, AtomicOrdering::SeqCst); io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.our_params.step_duration).expect("Failed to restart consensus step timer.") } } @@ -119,7 +123,7 @@ impl IoHandler for TransitionHandler { fn message(&self, io: &IoContext, _net_message: &BlockArrived) { if let Some(engine) = self.engine.upgrade() { trace!(target: "authorityround", "Message: {:?}", get_time().sec); - engine.step.fetch_add(1, AtomicOrdering::Relaxed); + engine.step.fetch_add(1, AtomicOrdering::SeqCst); io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to restart consensus step timer."); io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.our_params.step_duration).expect("Failed to restart consensus step timer.") } @@ -130,7 +134,7 @@ impl Engine for AuthorityRound { fn name(&self) -> &str { "AuthorityRound" } fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } // One field - the proposer signature. - fn seal_fields(&self) -> usize { 1 } + fn seal_fields(&self) -> usize { 2 } fn params(&self) -> &CommonParams { &self.params } fn builtins(&self) -> &BTreeMap { &self.builtins } @@ -170,11 +174,12 @@ impl Engine for AuthorityRound { /// be returned. fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { let header = block.header(); - if self.is_proposer(header.author()) { + let step = self.step(); + if self.is_step_proposer(step, header.author()) { if let Some(ap) = accounts { // Account should be permanently unlocked, otherwise sealing will fail. if let Ok(signature) = ap.sign(*header.author(), header.bare_hash()) { - return Some(vec![encode(&(&*signature as &[u8])).to_vec()]); + return Some(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); } else { trace!(target: "authorityround", "generate_seal: FAIL: accounts secret key unavailable"); } @@ -188,19 +193,30 @@ impl Engine for AuthorityRound { /// Check the number of seal fields. fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { if header.seal().len() != self.seal_fields() { - return Err(From::from(BlockError::InvalidSealArity( + trace!(target: "authorityround", "verify_block_basic: wrong number of seal fields"); + Err(From::from(BlockError::InvalidSealArity( Mismatch { expected: self.seal_fields(), found: header.seal().len() } - ))); + ))) + } else { + Ok(()) } - Ok(()) } /// Check if the signature belongs to the correct proposer. + /// TODO: Keep track of BlockNumber to step relationship fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { - let sig = try!(UntrustedRlp::new(&header.seal()[0]).as_val::()); - if try!(verify_address(self.proposer(), &sig.into(), &header.bare_hash())) { - Ok(()) + let step = try!(UntrustedRlp::new(&header.seal()[0]).as_val::()); + if step <= self.step() { + let sig = try!(UntrustedRlp::new(&header.seal()[1]).as_val::()); + let ok_sig = try!(verify_address(self.step_proposer(step), &sig.into(), &header.bare_hash())); + if ok_sig { + Ok(()) + } else { + trace!(target: "authorityround", "verify_block_unordered: invalid seal signature"); + try!(Err(BlockError::InvalidSeal)) + } } else { + trace!(target: "authorityround", "verify_block_unordered: block from the future"); try!(Err(BlockError::InvalidSeal)) } } @@ -320,7 +336,6 @@ mod tests { #[test] fn proposer_switching() { - let engine = Spec::new_test_round().engine; let mut header: Header = Header::default(); let tap = AccountProvider::transient_provider(); let addr = tap.insert_account("0".sha3(), "0").unwrap(); @@ -328,19 +343,21 @@ mod tests { header.set_author(addr); let signature = tap.sign_with_password(addr, "0".into(), header.bare_hash()).unwrap(); - header.set_seal(vec![encode(&(&*signature as &[u8])).to_vec()]); + header.set_seal(vec![vec![1u8], encode(&(&*signature as &[u8])).to_vec()]); - // Wrong step. - assert!(engine.verify_block_unordered(&header, None).is_err()); + let engine = Spec::new_test_round().engine; + + // Too early. + assert!(engine.verify_block_seal(&header).is_err()); sleep(Duration::from_millis(1000)); // Right step. - assert!(engine.verify_block_unordered(&header, None).is_ok()); + assert!(engine.verify_block_seal(&header).is_ok()); sleep(Duration::from_millis(1000)); - // Wrong step. - assert!(engine.verify_block_unordered(&header, None).is_err()); + // Future step. + assert!(engine.verify_block_seal(&header).is_ok()); } } diff --git a/sync/src/tests/consensus.rs b/sync/src/tests/consensus.rs index 72109893b..cafade39c 100644 --- a/sync/src/tests/consensus.rs +++ b/sync/src/tests/consensus.rs @@ -22,7 +22,7 @@ use std::time::Duration; use ethcore::client::BlockChainClient; #[test] -fn issue_tx() { +fn 2_peer_1_tx_seal() { ::env_logger::init().ok(); let mut net = MockNet::new_with_spec(2, vec!["1".sha3()], &Spec::new_test_round); net.peer(1).issue_rand_tx(); From 44c4845d844b5808ee11cd925fdf8e8146eba6e4 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 19 Sep 2016 18:40:25 +0200 Subject: [PATCH 040/106] fix up step switching test --- ethcore/src/engines/authority_round.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 54d236a0c..79acbe0c9 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -350,14 +350,9 @@ mod tests { // Too early. assert!(engine.verify_block_seal(&header).is_err()); - sleep(Duration::from_millis(1000)); + sleep(Duration::from_millis(2000)); // Right step. assert!(engine.verify_block_seal(&header).is_ok()); - - sleep(Duration::from_millis(1000)); - - // Future step. - assert!(engine.verify_block_seal(&header).is_ok()); } } From 9d23915caf2d9c125e0148ad282b3205e6975647 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 20 Sep 2016 15:48:17 +0200 Subject: [PATCH 041/106] more simulation methods --- sync/src/tests/consensus.rs | 34 ++++++++++++++++------------------ sync/src/tests/mocknet.rs | 20 +++++++++++++++++++- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/sync/src/tests/consensus.rs b/sync/src/tests/consensus.rs index cafade39c..55b0bb0f7 100644 --- a/sync/src/tests/consensus.rs +++ b/sync/src/tests/consensus.rs @@ -22,15 +22,12 @@ use std::time::Duration; use ethcore::client::BlockChainClient; #[test] -fn 2_peer_1_tx_seal() { +fn two_peer_tx_seal() { ::env_logger::init().ok(); let mut net = MockNet::new_with_spec(2, vec!["1".sha3()], &Spec::new_test_round); net.peer(1).issue_rand_tx(); sleep(Duration::from_secs(1)); net.sync(); - net.sync(); - net.sync(); - net.sync(); println!("{:?}", net.peer(0).client.chain_info()); println!("{:?}", net.peer(1).client.chain_info()); } @@ -39,21 +36,22 @@ fn 2_peer_1_tx_seal() { fn issue_many() { ::env_logger::init().ok(); let mut net = MockNet::new_with_spec(2, vec!["1".sha3()], &Spec::new_test_round); - net.peer(1).issue_rand_tx(); - net.peer(1).issue_rand_tx(); - net.peer(1).issue_rand_tx(); - net.peer(1).issue_rand_tx(); - net.peer(1).issue_rand_tx(); + net.peer(1).issue_rand_txs(5); sleep(Duration::from_secs(1)); net.sync(); + net.peer(0).issue_rand_txs(5); net.sync(); - net.peer(0).issue_rand_tx(); - net.peer(0).issue_rand_tx(); - net.peer(0).issue_rand_tx(); - net.peer(0).issue_rand_tx(); - net.peer(0).issue_rand_tx(); - net.sync(); - net.sync(); - //println!("{:?}", net.peer(0).client.chain_info()); - //println!("{:?}", net.peer(1).client.chain_info()); + println!("{:?}", net.peer(0).client.chain_info()); + println!("{:?}", net.peer(1).client.chain_info()); +} + +#[test] +fn rand_simulation() { + ::env_logger::init().ok(); + let mut net = MockNet::new_with_spec(2, vec!["1".sha3()], &Spec::new_test_round); + + net.rand_simulation(10); + + println!("{:?}", net.peer(0).client.chain_info()); + println!("{:?}", net.peer(1).client.chain_info()); } diff --git a/sync/src/tests/mocknet.rs b/sync/src/tests/mocknet.rs index 1091e868f..088d55fbe 100644 --- a/sync/src/tests/mocknet.rs +++ b/sync/src/tests/mocknet.rs @@ -31,6 +31,7 @@ use ethcore::miner::Miner; use ethcore::service::ClientService; use std::time::Duration; use std::thread::sleep; +use rand::{thread_rng, Rng}; pub struct TestIo<'p> { pub client: Arc, @@ -196,6 +197,12 @@ impl MockPeer { pub fn issue_rand_tx(&self) { self.issue_tx(random_transaction()) } + + pub fn issue_rand_txs(&self, n: usize) { + for _ in 0..n { + self.issue_rand_tx(); + } + } } pub struct MockNet { @@ -264,7 +271,6 @@ impl MockNet { let mut io = TestIo::new(peer0.client.clone(), &peer0.snapshot_service, &mut *q0, None); p.sync.write().maintain_sync(&mut io); p.sync.write().propagate_new_transactions(&mut io); - sleep(Duration::from_secs(2)); } } @@ -303,4 +309,16 @@ impl MockNet { pub fn done(&self) -> bool { self.peers.iter().all(|p| p.queue.try_read().unwrap().is_empty()) } + + pub fn rand_peer(&self) -> Arc { + thread_rng().choose(&self.peers).unwrap().clone() + } + + pub fn rand_simulation(&mut self, steps: usize) { + for _ in 0..steps { + self.rand_peer().issue_rand_tx(); + sleep(Duration::from_millis(100)); + self.sync(); + } + } } From c57e3cefe4a820dab9a15b13298972b0a9cd3f62 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 21 Sep 2016 10:29:44 +0200 Subject: [PATCH 042/106] sync check method --- ethcore/src/engines/authority_round.rs | 2 +- sync/src/tests/{consensus.rs => auth_round.rs} | 13 ++++++++++--- sync/src/tests/mocknet.rs | 14 +++++++++++++- sync/src/tests/mod.rs | 2 +- 4 files changed, 25 insertions(+), 6 deletions(-) rename sync/src/tests/{consensus.rs => auth_round.rs} (82%) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 79acbe0c9..c2fa89e90 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -112,8 +112,8 @@ impl IoHandler for TransitionHandler { fn timeout(&self, io: &IoContext, timer: TimerToken) { if timer == ENGINE_TIMEOUT_TOKEN { - debug!(target: "authorityround", "timeout"); if let Some(engine) = self.engine.upgrade() { + debug!(target: "authorityround", "Timeout step: {}", engine.step.load(AtomicOrdering::Relaxed)); engine.step.fetch_add(1, AtomicOrdering::SeqCst); io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.our_params.step_duration).expect("Failed to restart consensus step timer.") } diff --git a/sync/src/tests/consensus.rs b/sync/src/tests/auth_round.rs similarity index 82% rename from sync/src/tests/consensus.rs rename to sync/src/tests/auth_round.rs index 55b0bb0f7..4f8a8cbc4 100644 --- a/sync/src/tests/consensus.rs +++ b/sync/src/tests/auth_round.rs @@ -21,15 +21,20 @@ use std::thread::sleep; use std::time::Duration; use ethcore::client::BlockChainClient; +fn authorities() -> Vec { vec!["1".sha3(), "2".sha3()] } + #[test] -fn two_peer_tx_seal() { +fn three_peer_tx_seal() { ::env_logger::init().ok(); - let mut net = MockNet::new_with_spec(2, vec!["1".sha3()], &Spec::new_test_round); + let mut net = MockNet::new_with_spec(2, authorities(), &Spec::new_test_round); net.peer(1).issue_rand_tx(); sleep(Duration::from_secs(1)); net.sync(); + sleep(Duration::from_secs(1)); + net.sync(); println!("{:?}", net.peer(0).client.chain_info()); println!("{:?}", net.peer(1).client.chain_info()); + net.is_synced(1); } #[test] @@ -43,15 +48,17 @@ fn issue_many() { net.sync(); println!("{:?}", net.peer(0).client.chain_info()); println!("{:?}", net.peer(1).client.chain_info()); + net.is_synced(10); } #[test] fn rand_simulation() { ::env_logger::init().ok(); - let mut net = MockNet::new_with_spec(2, vec!["1".sha3()], &Spec::new_test_round); + let mut net = MockNet::new_with_spec(3, authorities(), &Spec::new_test_round); net.rand_simulation(10); println!("{:?}", net.peer(0).client.chain_info()); println!("{:?}", net.peer(1).client.chain_info()); + net.is_synced(10); } diff --git a/sync/src/tests/mocknet.rs b/sync/src/tests/mocknet.rs index 088d55fbe..2ed204243 100644 --- a/sync/src/tests/mocknet.rs +++ b/sync/src/tests/mocknet.rs @@ -29,6 +29,7 @@ use ::SyncConfig; use devtools::RandomTempPath; use ethcore::miner::Miner; use ethcore::service::ClientService; +use ethcore::header::BlockNumber; use std::time::Duration; use std::thread::sleep; use rand::{thread_rng, Rng}; @@ -271,6 +272,7 @@ impl MockNet { let mut io = TestIo::new(peer0.client.clone(), &peer0.snapshot_service, &mut *q0, None); p.sync.write().maintain_sync(&mut io); p.sync.write().propagate_new_transactions(&mut io); + sleep(Duration::from_millis(10)); } } @@ -317,8 +319,18 @@ impl MockNet { pub fn rand_simulation(&mut self, steps: usize) { for _ in 0..steps { self.rand_peer().issue_rand_tx(); - sleep(Duration::from_millis(100)); + sleep(Duration::from_millis(500)); self.sync(); } } + + pub fn is_synced(&self, block: BlockNumber) { + println!("Is block {:?}", &block); + let hash = self.peer(0).client.chain_info().best_block_hash; + for p in &self.peers { + let ci = p.client.chain_info(); + assert_eq!(ci.best_block_number, block); + assert_eq!(ci.best_block_hash, hash); + } + } } diff --git a/sync/src/tests/mod.rs b/sync/src/tests/mod.rs index e64792d33..394e2bf20 100644 --- a/sync/src/tests/mod.rs +++ b/sync/src/tests/mod.rs @@ -19,4 +19,4 @@ pub mod snapshot; pub mod mocknet; mod chain; mod rpc; -mod consensus; +mod auth_round; From ec058cdb50875ef28c3799bf7052ec05e3b617ae Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 27 Sep 2016 12:12:18 +0200 Subject: [PATCH 043/106] reseal on timeout --- ethcore/src/client/client.rs | 11 ++++++++-- ethcore/src/engines/authority_round.rs | 29 ++++++++++++++++++-------- ethcore/src/engines/mod.rs | 4 ++++ ethcore/src/service.rs | 12 +++++++++-- 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index ad2c3fc8b..e90f92be6 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -488,6 +488,11 @@ impl Client { self.miner.set_author(author) } + /// Used by PoA to try sealing on period change. + pub fn update_sealing(&self) { + self.miner.update_sealing(self) + } + /// Attempt to get a copy of a specific block's final state. /// /// This will not fail if given BlockID::Latest. @@ -1025,13 +1030,15 @@ impl BlockChainClient for Client { } fn queue_transactions(&self, transactions: Vec) { - if self.queue_transactions.load(AtomicOrdering::Relaxed) > MAX_TX_QUEUE_SIZE { + let queue_size = self.queue_transactions.load(AtomicOrdering::Relaxed); + trace!(target: "external_tx", "Queue size: {}", queue_size); + if queue_size > MAX_TX_QUEUE_SIZE { debug!("Ignoring {} transactions: queue is full", transactions.len()); } else { let len = transactions.len(); match self.io_channel.send(ClientIoMessage::NewTransactions(transactions)) { Ok(_) => { - trace!(target: "external_tx", "Sending IoMessage"); + trace!(target: "external_tx", "Sent IoMessage"); self.queue_transactions.fetch_add(len, AtomicOrdering::SeqCst); } Err(e) => { diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index c2fa89e90..951f7f06f 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -27,7 +27,8 @@ use spec::CommonParams; use engines::Engine; use evm::Schedule; use ethjson; -use io::{IoContext, IoHandler, TimerToken, IoService}; +use io::{IoContext, IoHandler, TimerToken, IoService, IoChannel}; +use service::ClientIoMessage; use time::get_time; /// `AuthorityRound` params. @@ -61,6 +62,7 @@ pub struct AuthorityRound { our_params: AuthorityRoundParams, builtins: BTreeMap, transistion_service: IoService, + message_channel: Mutex>>, step: AtomicUsize, } @@ -73,6 +75,7 @@ impl AuthorityRound { our_params: our_params, builtins: builtins, transistion_service: IoService::::start().expect("Error creating engine timeout service"), + message_channel: Mutex::new(None), step: AtomicUsize::new(0), }); let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; @@ -115,19 +118,22 @@ impl IoHandler for TransitionHandler { if let Some(engine) = self.engine.upgrade() { debug!(target: "authorityround", "Timeout step: {}", engine.step.load(AtomicOrdering::Relaxed)); engine.step.fetch_add(1, AtomicOrdering::SeqCst); + if let Some(ref channel) = *engine.message_channel.try_lock().unwrap() { + channel.send(ClientIoMessage::UpdateSealing); + } io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.our_params.step_duration).expect("Failed to restart consensus step timer.") } } } - fn message(&self, io: &IoContext, _net_message: &BlockArrived) { - if let Some(engine) = self.engine.upgrade() { - trace!(target: "authorityround", "Message: {:?}", get_time().sec); - engine.step.fetch_add(1, AtomicOrdering::SeqCst); - io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to restart consensus step timer."); - io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.our_params.step_duration).expect("Failed to restart consensus step timer.") - } - } +// fn message(&self, io: &IoContext, _net_message: &BlockArrived) { +// if let Some(engine) = self.engine.upgrade() { +// trace!(target: "authorityround", "Message: {:?}", get_time().sec); +// engine.step.fetch_add(1, AtomicOrdering::SeqCst); +// io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to restart consensus step timer."); +// io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.our_params.step_duration).expect("Failed to restart consensus step timer.") +// } +// } } impl Engine for AuthorityRound { @@ -248,6 +254,11 @@ impl Engine for AuthorityRound { fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { t.sender().map(|_|()) // Perform EC recovery and cache sender } + + fn register_message_channel(&self, message_channel: IoChannel) { + let mut guard = self.message_channel.try_lock().unwrap(); + *guard = Some(message_channel); + } } impl Header { diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index ec27a2fe2..518eab943 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -31,6 +31,8 @@ use account_provider::AccountProvider; use block::ExecutedBlock; use spec::CommonParams; use evm::Schedule; +use io::IoChannel; +use service::ClientIoMessage; /// A consensus mechanism for the chain. Generally either proof-of-work or proof-of-stake-based. /// Provides hooks into each of the major parts of block import. @@ -130,5 +132,7 @@ pub trait Engine : Sync + Send { /// Panics if `is_builtin(a)` is not true. fn execute_builtin(&self, a: &Address, input: &[u8], output: &mut [u8]) { self.builtins().get(a).unwrap().execute(input, output); } + /// Add a channel for communication with Client. + fn register_message_channel(&self, message_channel: IoChannel) {} // TODO: sealing stuff - though might want to leave this for later. } diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index 9fa126cc7..8b1048393 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -21,7 +21,7 @@ use io::*; use spec::Spec; use error::*; use client::{Client, ClientConfig, ChainNotify}; -use miner::Miner; +use miner::{Miner, MinerService}; use snapshot::ManifestData; use snapshot::service::{Service as SnapshotService, ServiceParams as SnapServiceParams}; use std::sync::atomic::AtomicBool; @@ -48,6 +48,8 @@ pub enum ClientIoMessage { FeedBlockChunk(H256, Bytes), /// Take a snapshot for the block with given number. TakeSnapshot(u64), + /// Trigger sealing update (useful for internal sealing). + UpdateSealing, } /// Client service setup. Creates and registers client and network services with the IO subsystem. @@ -105,6 +107,8 @@ impl ClientService { }); try!(io_service.register_handler(client_io)); + spec.engine.register_message_channel(io_service.channel()); + let stop_guard = ::devtools::StopGuard::new(); run_ipc(ipc_path, client.clone(), snapshot.clone(), stop_guard.share()); @@ -196,7 +200,11 @@ impl IoHandler for ClientIoHandler { if let Err(e) = self.snapshot.take_snapshot(&*self.client, num) { warn!("Failed to take snapshot at block #{}: {}", num, e); } - } + }, + ClientIoMessage::UpdateSealing => { + println!("Message received!"); + self.client.update_sealing() + }, _ => {} // ignore other messages } } From cf88641f998d2aab4fa26d129e2e58e7711d8539 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 27 Sep 2016 12:13:21 +0200 Subject: [PATCH 044/106] more mocknet tests --- sync/src/tests/auth_round.rs | 15 ++++++++++++++- sync/src/tests/mocknet.rs | 1 - 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/sync/src/tests/auth_round.rs b/sync/src/tests/auth_round.rs index 4f8a8cbc4..dbca507f8 100644 --- a/sync/src/tests/auth_round.rs +++ b/sync/src/tests/auth_round.rs @@ -24,10 +24,23 @@ use ethcore::client::BlockChainClient; fn authorities() -> Vec { vec!["1".sha3(), "2".sha3()] } #[test] -fn three_peer_tx_seal() { +fn two_auth() { ::env_logger::init().ok(); let mut net = MockNet::new_with_spec(2, authorities(), &Spec::new_test_round); net.peer(1).issue_rand_tx(); + net.sync(); + sleep(Duration::from_secs(1)); + net.sync(); + println!("{:?}", net.peer(0).client.chain_info()); + println!("{:?}", net.peer(1).client.chain_info()); + net.is_synced(1); +} + +#[test] +fn one_auth_missing() { + ::env_logger::init().ok(); + let mut net = MockNet::new_with_spec(2, vec!["1".sha3()], &Spec::new_test_round); + net.peer(1).issue_rand_tx(); sleep(Duration::from_secs(1)); net.sync(); sleep(Duration::from_secs(1)); diff --git a/sync/src/tests/mocknet.rs b/sync/src/tests/mocknet.rs index 2ed204243..2febdb4b8 100644 --- a/sync/src/tests/mocknet.rs +++ b/sync/src/tests/mocknet.rs @@ -325,7 +325,6 @@ impl MockNet { } pub fn is_synced(&self, block: BlockNumber) { - println!("Is block {:?}", &block); let hash = self.peer(0).client.chain_info().best_block_hash; for p in &self.peers { let ci = p.client.chain_info(); From 28cf91c7a556975c62c8682131eeef64faf94261 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 27 Sep 2016 15:50:16 +0100 Subject: [PATCH 045/106] remove mocknet stuff, clean up debug --- Cargo.lock | 2 - ethcore/src/client/client.rs | 21 +- ethcore/src/engines/authority_round.rs | 6 +- ethcore/src/engines/mod.rs | 4 +- ethcore/src/miner/miner.rs | 2 +- ethcore/src/service.rs | 4 +- sync/Cargo.toml | 2 - sync/src/chain.rs | 4 +- sync/src/lib.rs | 5 - sync/src/tests/auth_round.rs | 77 ------ sync/src/tests/mocknet.rs | 335 ------------------------- sync/src/tests/mod.rs | 2 - 12 files changed, 13 insertions(+), 451 deletions(-) delete mode 100644 sync/src/tests/auth_round.rs delete mode 100644 sync/src/tests/mocknet.rs diff --git a/Cargo.lock b/Cargo.lock index b600570ab..52bdee494 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -610,14 +610,12 @@ dependencies = [ "clippy 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore 1.4.0", - "ethcore-devtools 1.4.0", "ethcore-io 1.4.0", "ethcore-ipc 1.4.0", "ethcore-ipc-codegen 1.4.0", "ethcore-ipc-nano 1.4.0", "ethcore-network 1.4.0", "ethcore-util 1.4.0", - "ethkey 0.2.0", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 4d07e2725..fcf79cf71 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -52,7 +52,7 @@ use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute}; use client::{ BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient, MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode, - ChainNotify, TransactionImportResult + ChainNotify }; use client::Error as ClientError; use env_info::EnvInfo; @@ -480,16 +480,6 @@ impl Client { results.len() } - // TODO: these are only used for tests in sync and only contribute to huge Client - /// Import a locally created transaction. - pub fn import_own_transaction(&self, transaction: SignedTransaction) -> Result { - self.miner.import_own_transaction(self, transaction) - } - /// Set miner author. - pub fn set_author(&self, author: Address) { - self.miner.set_author(author) - } - /// Used by PoA to try sealing on period change. pub fn update_sealing(&self) { self.miner.update_sealing(self) @@ -1039,13 +1029,8 @@ impl BlockChainClient for Client { } else { let len = transactions.len(); match self.io_channel.send(ClientIoMessage::NewTransactions(transactions)) { - Ok(_) => { - trace!(target: "external_tx", "Sent IoMessage"); - self.queue_transactions.fetch_add(len, AtomicOrdering::SeqCst); - } - Err(e) => { - debug!("Ignoring {} transactions: error queueing: {}", len, e); - } + Ok(_) => { self.queue_transactions.fetch_add(len, AtomicOrdering::SeqCst); }, + Err(e) => debug!("Ignoring {} transactions: error queueing: {}", len, e), } } } diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 951f7f06f..824a84ecb 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -29,7 +29,6 @@ use evm::Schedule; use ethjson; use io::{IoContext, IoHandler, TimerToken, IoService, IoChannel}; use service::ClientIoMessage; -use time::get_time; /// `AuthorityRound` params. #[derive(Debug, PartialEq)] @@ -119,7 +118,10 @@ impl IoHandler for TransitionHandler { debug!(target: "authorityround", "Timeout step: {}", engine.step.load(AtomicOrdering::Relaxed)); engine.step.fetch_add(1, AtomicOrdering::SeqCst); if let Some(ref channel) = *engine.message_channel.try_lock().unwrap() { - channel.send(ClientIoMessage::UpdateSealing); + match channel.send(ClientIoMessage::UpdateSealing) { + Ok(_) => trace!(target: "authorityround", "timeout: UpdateSealing message sent."), + Err(_) => trace!(target: "authorityround", "timeout: Could not send a sealing message."), + } } io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.our_params.step_duration).expect("Failed to restart consensus step timer.") } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 05af84c2b..098825f84 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -132,7 +132,7 @@ pub trait Engine : Sync + Send { /// Panics if `is_builtin(a)` is not true. fn execute_builtin(&self, a: &Address, input: &[u8], output: &mut BytesRef) { self.builtins().get(a).unwrap().execute(input, output); } - /// Add a channel for communication with Client. - fn register_message_channel(&self, message_channel: IoChannel) {} + /// Add a channel for communication with Client which can be used for sealing. + fn register_message_channel(&self, _message_channel: IoChannel) {} // TODO: sealing stuff - though might want to leave this for later. } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 6de5674b0..8a6e607cc 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -78,7 +78,7 @@ impl Default for MinerOptions { MinerOptions { new_work_notify: vec![], force_sealing: false, - reseal_on_external_tx: true, + reseal_on_external_tx: false, reseal_on_own_tx: true, tx_gas_limit: !U256::zero(), tx_queue_size: 1024, diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index 8b1048393..c9c9b3f1b 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -21,7 +21,7 @@ use io::*; use spec::Spec; use error::*; use client::{Client, ClientConfig, ChainNotify}; -use miner::{Miner, MinerService}; +use miner::Miner; use snapshot::ManifestData; use snapshot::service::{Service as SnapshotService, ServiceParams as SnapServiceParams}; use std::sync::atomic::AtomicBool; @@ -202,7 +202,7 @@ impl IoHandler for ClientIoHandler { } }, ClientIoMessage::UpdateSealing => { - println!("Message received!"); + trace!(target: "authorityround", "message: UpdateSealing"); self.client.update_sealing() }, _ => {} // ignore other messages diff --git a/sync/Cargo.toml b/sync/Cargo.toml index 0fa6462cc..99c522075 100644 --- a/sync/Cargo.toml +++ b/sync/Cargo.toml @@ -13,12 +13,10 @@ ethcore-ipc-codegen = { path = "../ipc/codegen" } [dependencies] ethcore-util = { path = "../util" } -ethkey = { path = "../ethkey" } ethcore-network = { path = "../util/network" } ethcore-io = { path = "../util/io" } ethcore = { path = "../ethcore" } rlp = { path = "../util/rlp" } -ethcore-devtools = { path = "../devtools" } clippy = { version = "0.0.90", optional = true} log = "0.3" env_logger = "0.3" diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 4ccc4141e..565c53827 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -905,7 +905,6 @@ impl ChainSync { /// Resume downloading fn continue_sync(&mut self, io: &mut SyncIo) { - trace!(target:"sync", "continue_sync"); let mut peers: Vec<(PeerId, U256, u32)> = self.peers.iter().filter_map(|(k, p)| if p.can_sync() { Some((*k, p.difficulty.unwrap_or_else(U256::zero), p.protocol_version)) } else { None }).collect(); thread_rng().shuffle(&mut peers); //TODO: sort by rating @@ -1249,7 +1248,6 @@ impl ChainSync { /// Called when peer sends us new transactions fn on_peer_transactions(&mut self, io: &mut SyncIo, peer_id: PeerId, r: &UntrustedRlp) -> Result<(), PacketDecodeError> { - trace!(target: "sync", "Received tx from {}", peer_id); // accepting transactions once only fully synced if !io.is_chain_queue_empty() { return Ok(()); @@ -1701,6 +1699,7 @@ impl ChainSync { /// propagates new transactions to all peers pub fn propagate_new_transactions(&mut self, io: &mut SyncIo) -> usize { + // Early out of nobody to send to. if self.peers.is_empty() { return 0; @@ -1792,7 +1791,6 @@ impl ChainSync { /// called when block is imported to chain - propagates the blocks and updates transactions sent to peers pub fn chain_new_blocks(&mut self, io: &mut SyncIo, _imported: &[H256], invalid: &[H256], _enacted: &[H256], _retracted: &[H256], sealed: &[H256]) { if io.is_chain_queue_empty() { - trace!(target: "sync", "Chain not empty!"); self.propagate_latest_blocks(io, sealed); } if !invalid.is_empty() { diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 376ece6d1..d2c6e2583 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -37,11 +37,6 @@ extern crate semver; extern crate parking_lot; extern crate rlp; -#[cfg(test)] -extern crate ethkey; -#[cfg(test)] -extern crate ethcore_devtools as devtools; - #[macro_use] extern crate log; #[macro_use] diff --git a/sync/src/tests/auth_round.rs b/sync/src/tests/auth_round.rs deleted file mode 100644 index dbca507f8..000000000 --- a/sync/src/tests/auth_round.rs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. -// This file is part of Parity. - -// Parity is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity. If not, see . - -use util::*; -use ethcore::spec::Spec; -use super::mocknet::*; -use std::thread::sleep; -use std::time::Duration; -use ethcore::client::BlockChainClient; - -fn authorities() -> Vec { vec!["1".sha3(), "2".sha3()] } - -#[test] -fn two_auth() { - ::env_logger::init().ok(); - let mut net = MockNet::new_with_spec(2, authorities(), &Spec::new_test_round); - net.peer(1).issue_rand_tx(); - net.sync(); - sleep(Duration::from_secs(1)); - net.sync(); - println!("{:?}", net.peer(0).client.chain_info()); - println!("{:?}", net.peer(1).client.chain_info()); - net.is_synced(1); -} - -#[test] -fn one_auth_missing() { - ::env_logger::init().ok(); - let mut net = MockNet::new_with_spec(2, vec!["1".sha3()], &Spec::new_test_round); - net.peer(1).issue_rand_tx(); - sleep(Duration::from_secs(1)); - net.sync(); - sleep(Duration::from_secs(1)); - net.sync(); - println!("{:?}", net.peer(0).client.chain_info()); - println!("{:?}", net.peer(1).client.chain_info()); - net.is_synced(1); -} - -#[test] -fn issue_many() { - ::env_logger::init().ok(); - let mut net = MockNet::new_with_spec(2, vec!["1".sha3()], &Spec::new_test_round); - net.peer(1).issue_rand_txs(5); - sleep(Duration::from_secs(1)); - net.sync(); - net.peer(0).issue_rand_txs(5); - net.sync(); - println!("{:?}", net.peer(0).client.chain_info()); - println!("{:?}", net.peer(1).client.chain_info()); - net.is_synced(10); -} - -#[test] -fn rand_simulation() { - ::env_logger::init().ok(); - let mut net = MockNet::new_with_spec(3, authorities(), &Spec::new_test_round); - - net.rand_simulation(10); - - println!("{:?}", net.peer(0).client.chain_info()); - println!("{:?}", net.peer(1).client.chain_info()); - net.is_synced(10); -} diff --git a/sync/src/tests/mocknet.rs b/sync/src/tests/mocknet.rs deleted file mode 100644 index 2febdb4b8..000000000 --- a/sync/src/tests/mocknet.rs +++ /dev/null @@ -1,335 +0,0 @@ -// Copyright 2015, 2016 Ethcore (UK) Ltd. -// This file is part of Parity. - -// Parity is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity. If not, see . - -use util::*; -use network::*; -use tests::snapshot::*; -use ethcore::client::{Client, BlockChainClient, ChainNotify}; -use ethcore::spec::Spec; -use ethcore::snapshot::SnapshotService; -use ethcore::transaction::{Transaction, SignedTransaction, Action}; -use ethcore::account_provider::AccountProvider; -use ethkey::{Random, Generator}; -use sync_io::SyncIo; -use chain::ChainSync; -use ::SyncConfig; -use devtools::RandomTempPath; -use ethcore::miner::Miner; -use ethcore::service::ClientService; -use ethcore::header::BlockNumber; -use std::time::Duration; -use std::thread::sleep; -use rand::{thread_rng, Rng}; - -pub struct TestIo<'p> { - pub client: Arc, - pub snapshot_service: &'p TestSnapshotService, - pub queue: &'p mut VecDeque, - pub sender: Option, -} - -impl<'p> TestIo<'p> { - pub fn new(client: Arc, ss: &'p TestSnapshotService, queue: &'p mut VecDeque, sender: Option) -> TestIo<'p> { - TestIo { - client: client, - snapshot_service: ss, - queue: queue, - sender: sender - } - } -} - -impl<'p> SyncIo for TestIo<'p> { - fn disable_peer(&mut self, _peer_id: PeerId) { - } - - fn disconnect_peer(&mut self, _peer_id: PeerId) { - } - - fn is_expired(&self) -> bool { - false - } - - fn respond(&mut self, packet_id: PacketId, data: Vec) -> Result<(), NetworkError> { - self.queue.push_back(TestPacket { - data: data, - packet_id: packet_id, - recipient: self.sender.unwrap() - }); - Ok(()) - } - - fn send(&mut self, peer_id: PeerId, packet_id: PacketId, data: Vec) -> Result<(), NetworkError> { - self.queue.push_back(TestPacket { - data: data, - packet_id: packet_id, - recipient: peer_id, - }); - Ok(()) - } - - fn chain(&self) -> &BlockChainClient { - self.client.as_ref() - } - - fn snapshot_service(&self) -> &SnapshotService { - self.snapshot_service - } - - fn eth_protocol_version(&self, _peer: PeerId) -> u8 { - 64 - } -} - -pub struct TestPacket { - pub data: Bytes, - pub packet_id: PacketId, - pub recipient: PeerId, -} - -fn transaction() -> Transaction { - Transaction { - action: Action::Create, - value: U256::zero(), - data: "3331600055".from_hex().unwrap(), - gas: U256::from(100_000), - gas_price: U256::zero(), - nonce: U256::zero(), - } -} - -pub fn random_transaction() -> SignedTransaction { - let keypair = Random.generate().unwrap(); - transaction().sign(&keypair.secret()) -} - -pub struct MockPeer { - pub client: Arc, - pub snapshot_service: Arc, - pub sync: RwLock, - pub queue: RwLock>, - _service: ClientService, - _paths: Vec, -} - -impl ChainNotify for MockPeer { - fn new_blocks(&self, - imported: Vec, - invalid: Vec, - enacted: Vec, - retracted: Vec, - sealed: Vec, - _duration: u64) { - println!("New sync blocks"); - let ref mut q = *self.queue.write(); - let mut sync_io = TestIo::new( - self.client.clone(), - &self.snapshot_service, - q, - None); - self.sync.write().chain_new_blocks( - &mut sync_io, - &imported, - &invalid, - &enacted, - &retracted, - &sealed); - } -} - -impl MockPeer { - pub fn new_with_spec(get_spec: &S, author_secret: Option) -> Arc where S: Fn()->Spec { - let (accounts, address) = if let Some(secret) = author_secret { - let tap = AccountProvider::transient_provider(); - let addr = tap.insert_account(secret, "").unwrap(); - tap.unlock_account_permanently(addr, "".into()).unwrap(); - (Some(Arc::new(tap)), Some(addr)) - } else { - (None, None) - }; - - let client_path = RandomTempPath::new(); - let snapshot_path = RandomTempPath::new(); - let ipc_path = RandomTempPath::new(); - let spec = get_spec(); - - let service = ClientService::start( - Default::default(), - &spec, - client_path.as_path(), - snapshot_path.as_path(), - ipc_path.as_path(), - Arc::new(Miner::with_spec_and_accounts(&spec, accounts.clone())), - ).unwrap(); - - let client = service.client(); - if let Some(addr) = address { client.set_author(addr) } - let sync = ChainSync::new(SyncConfig::default(), &*client); - - let peer = Arc::new(MockPeer { - sync: RwLock::new(sync), - snapshot_service: Arc::new(TestSnapshotService::new()), - client: client, - queue: RwLock::new(VecDeque::new()), - _service: service, - _paths: vec![client_path, snapshot_path, ipc_path] - }); - peer.client.add_notify(peer.clone()); - peer - } - - pub fn issue_tx(&self, transaction: SignedTransaction) { - self.client.import_own_transaction(transaction).unwrap(); - } - - pub fn issue_rand_tx(&self) { - self.issue_tx(random_transaction()) - } - - pub fn issue_rand_txs(&self, n: usize) { - for _ in 0..n { - self.issue_rand_tx(); - } - } -} - -pub struct MockNet { - pub peers: Vec>, - pub started: bool, -} - -impl MockNet { - pub fn new_with_spec(nodes: usize, author_secrets: Vec, get_spec: &S) -> MockNet where S: Fn()->Spec { - let mut net = MockNet { - peers: Vec::new(), - started: false, - }; - for secret in author_secrets { - net.peers.push(MockPeer::new_with_spec(get_spec, Some(secret))); - } - for _ in net.peers.len()..nodes { - net.peers.push(MockPeer::new_with_spec(get_spec, None)); - } - net - } - - pub fn peer(&self, i: usize) -> Arc { - self.peers.get(i).unwrap().clone() - } - - pub fn peer_mut(&mut self, i: usize) -> &mut MockPeer { - Arc::get_mut(self.peers.get_mut(i).unwrap()).unwrap() - } - - pub fn start(&mut self) { - for peer in 0..self.peers.len() { - for client in 0..self.peers.len() { - if peer != client { - let p = self.peers.get_mut(peer).unwrap(); - let mut q = p.queue.write(); - p.sync.write().on_peer_connected(&mut TestIo::new(p.client.clone(), - &p.snapshot_service, - &mut *q, - Some(client as PeerId)), - client as PeerId); - } - } - } - } - - pub fn sync_step(&mut self) { - for (i, peer0) in self.peers.iter().enumerate() { - let mut q0 = peer0.queue.write(); - if let Some(packet) = q0.pop_front() { - let p = self.peers.get(packet.recipient).unwrap(); - let mut q1 = p.queue.write(); - trace!(target: "mocknet", "--- {} -> {} ---", i, packet.recipient); - ChainSync::dispatch_packet(&p.sync, - &mut TestIo::new(p.client.clone(), - &p.snapshot_service, - &mut *q1, - Some(i as PeerId)), - i as PeerId, - packet.packet_id, - &packet.data); - trace!(target: "mocknet", "----------------"); - } - let p = self.peers.get(i).unwrap(); - peer0.client.flush_queue(); - let mut io = TestIo::new(peer0.client.clone(), &peer0.snapshot_service, &mut *q0, None); - p.sync.write().maintain_sync(&mut io); - p.sync.write().propagate_new_transactions(&mut io); - sleep(Duration::from_millis(10)); - } - } - - pub fn sync_step_peer(&mut self, peer_num: usize) { - let mut peer = self.peer_mut(peer_num); - let ref mut q = *peer.queue.write(); - peer.sync.write().maintain_sync(&mut TestIo::new(peer.client.clone(), &peer.snapshot_service, q, None)); - } - - pub fn restart_peer(&mut self, i: usize) { - let peer = self.peer_mut(i); - let ref mut q = *peer.queue.write(); - peer.sync.write().restart(&mut TestIo::new(peer.client.clone(), &peer.snapshot_service, q, None)); - } - - pub fn sync(&mut self) -> u32 { - self.start(); - let mut total_steps = 0; - while !self.done() { - self.sync_step(); - total_steps += 1; - } - total_steps - } - - pub fn sync_steps(&mut self, count: usize) { - if !self.started { - self.start(); - self.started = true; - } - for _ in 0..count { - self.sync_step(); - } - } - - pub fn done(&self) -> bool { - self.peers.iter().all(|p| p.queue.try_read().unwrap().is_empty()) - } - - pub fn rand_peer(&self) -> Arc { - thread_rng().choose(&self.peers).unwrap().clone() - } - - pub fn rand_simulation(&mut self, steps: usize) { - for _ in 0..steps { - self.rand_peer().issue_rand_tx(); - sleep(Duration::from_millis(500)); - self.sync(); - } - } - - pub fn is_synced(&self, block: BlockNumber) { - let hash = self.peer(0).client.chain_info().best_block_hash; - for p in &self.peers { - let ci = p.client.chain_info(); - assert_eq!(ci.best_block_number, block); - assert_eq!(ci.best_block_hash, hash); - } - } -} diff --git a/sync/src/tests/mod.rs b/sync/src/tests/mod.rs index 394e2bf20..bdb4ae4f9 100644 --- a/sync/src/tests/mod.rs +++ b/sync/src/tests/mod.rs @@ -16,7 +16,5 @@ pub mod helpers; pub mod snapshot; -pub mod mocknet; mod chain; mod rpc; -mod auth_round; From 1c801c99af0d654cf6d9dfe4c7d791639b11dcbb Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 27 Sep 2016 15:54:10 +0100 Subject: [PATCH 046/106] revert random path drop panic --- devtools/src/random_path.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/devtools/src/random_path.rs b/devtools/src/random_path.rs index 6b3649276..9c6c261a2 100644 --- a/devtools/src/random_path.rs +++ b/devtools/src/random_path.rs @@ -86,7 +86,9 @@ impl Drop for RandomTempPath { fn drop(&mut self) { if let Err(_) = fs::remove_dir_all(&self) { if let Err(e) = fs::remove_file(&self) { - println!("Failed to remove temp directory. Here's what prevented this from happening: ({})", e); + if self.panic_on_drop_failure { + panic!("Failed to remove temp directory. Here's what prevented this from happening: ({})", e); + } } } } From 7e1d7148c71650540726ae6846bc565a1d912ad7 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 27 Sep 2016 16:06:13 +0100 Subject: [PATCH 047/106] block time in decimal --- ethcore/res/authority_round.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/res/authority_round.json b/ethcore/res/authority_round.json index b4e35b29b..30df3c2be 100644 --- a/ethcore/res/authority_round.json +++ b/ethcore/res/authority_round.json @@ -4,7 +4,7 @@ "AuthorityRound": { "params": { "gasLimitBoundDivisor": "0x0400", - "stepDuration": "0x03e8", + "stepDuration": "1000", "authorities" : [ "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" From 303f922ebd610df58c8228998bdfd1f3016475c6 Mon Sep 17 00:00:00 2001 From: keorn Date: Sat, 15 Oct 2016 13:55:10 +0100 Subject: [PATCH 048/106] steps based on unix epoch --- ethcore/res/authority_round.json | 2 +- ethcore/src/engines/authority_round.rs | 55 ++++++++++++++++---------- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/ethcore/res/authority_round.json b/ethcore/res/authority_round.json index 30df3c2be..ad23b461f 100644 --- a/ethcore/res/authority_round.json +++ b/ethcore/res/authority_round.json @@ -4,7 +4,7 @@ "AuthorityRound": { "params": { "gasLimitBoundDivisor": "0x0400", - "stepDuration": "1000", + "stepDuration": "1", "authorities" : [ "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 6730c2aac..57276d354 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -18,6 +18,7 @@ use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use std::sync::Weak; +use std::time::{UNIX_EPOCH, Duration}; use common::*; use ethkey::verify_address; use rlp::{UntrustedRlp, View, encode, decode}; @@ -36,7 +37,7 @@ pub struct AuthorityRoundParams { /// Gas limit divisor. pub gas_limit_bound_divisor: U256, /// Time to wait before next block or authority switching. - pub step_duration: u64, + pub step_duration: Duration, /// Valid authorities. pub authorities: Vec
, /// Number of authorities. @@ -47,7 +48,7 @@ impl From for AuthorityRoundParams { fn from(p: ethjson::spec::AuthorityRoundParams) -> Self { AuthorityRoundParams { gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), - step_duration: p.step_duration.into(), + step_duration: Duration::from_secs(p.step_duration.into()), authority_n: p.authorities.len(), authorities: p.authorities.into_iter().map(Into::into).collect::>(), } @@ -62,12 +63,23 @@ pub struct AuthorityRound { builtins: BTreeMap, transistion_service: IoService, message_channel: Mutex>>, - step: AtomicUsize, + step: AtomicUsize +} + +trait AsMillis { + fn as_millis(&self) -> u64; +} + +impl AsMillis for Duration { + fn as_millis(&self) -> u64 { + self.as_secs()*1_000 + (self.subsec_nanos()/1_000_000) as u64 + } } impl AuthorityRound { - /// Create a new instance of AuthorityRound engine + /// Create a new instance of AuthorityRound engine. pub fn new(params: CommonParams, our_params: AuthorityRoundParams, builtins: BTreeMap) -> Arc { + let initial_step = (unix_now().as_secs() / our_params.step_duration.as_secs()) as usize; let engine = Arc::new( AuthorityRound { params: params, @@ -75,10 +87,10 @@ impl AuthorityRound { builtins: builtins, transistion_service: IoService::::start().expect("Error creating engine timeout service"), message_channel: Mutex::new(None), - step: AtomicUsize::new(0), + step: AtomicUsize::new(initial_step), }); let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; - engine.transistion_service.register_handler(Arc::new(handler)).expect("Error creating engine timeout service"); + engine.transistion_service.register_handler(Arc::new(handler)).expect("Error registering engine timeout service"); engine } @@ -86,6 +98,10 @@ impl AuthorityRound { self.step.load(AtomicOrdering::SeqCst) } + fn remaining_step_millis(&self) -> u64 { + unix_now().as_millis() % self.our_params.step_duration.as_millis() + } + fn step_proposer(&self, step: usize) -> &Address { let ref p = self.our_params; p.authorities.get(step%p.authority_n).unwrap() @@ -96,6 +112,10 @@ impl AuthorityRound { } } +fn unix_now() -> Duration { + UNIX_EPOCH.elapsed().expect("Valid time has to be set in your system.") +} + struct TransitionHandler { engine: Weak, } @@ -108,7 +128,7 @@ const ENGINE_TIMEOUT_TOKEN: TimerToken = 0; impl IoHandler for TransitionHandler { fn initialize(&self, io: &IoContext) { if let Some(engine) = self.engine.upgrade() { - io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.our_params.step_duration).expect("Error registering engine timeout"); + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.remaining_step_millis()).expect("Error registering engine timeout"); } } @@ -117,31 +137,22 @@ impl IoHandler for TransitionHandler { if let Some(engine) = self.engine.upgrade() { debug!(target: "authorityround", "Timeout step: {}", engine.step.load(AtomicOrdering::Relaxed)); engine.step.fetch_add(1, AtomicOrdering::SeqCst); - if let Some(ref channel) = *engine.message_channel.try_lock().unwrap() { + if let Some(ref channel) = *engine.message_channel.try_lock().expect("Could not acquire message channel work.") { match channel.send(ClientIoMessage::UpdateSealing) { Ok(_) => trace!(target: "authorityround", "timeout: UpdateSealing message sent."), Err(_) => trace!(target: "authorityround", "timeout: Could not send a sealing message."), } } - io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.our_params.step_duration).expect("Failed to restart consensus step timer.") + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.remaining_step_millis()).expect("Failed to restart consensus step timer.") } } } - -// fn message(&self, io: &IoContext, _net_message: &BlockArrived) { -// if let Some(engine) = self.engine.upgrade() { -// trace!(target: "authorityround", "Message: {:?}", get_time().sec); -// engine.step.fetch_add(1, AtomicOrdering::SeqCst); -// io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to restart consensus step timer."); -// io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.our_params.step_duration).expect("Failed to restart consensus step timer.") -// } -// } } impl Engine for AuthorityRound { fn name(&self) -> &str { "AuthorityRound" } fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } - // One field - the proposer signature. + /// Two fields - consensus step and the corresponding proposer signature. fn seal_fields(&self) -> usize { 2 } fn params(&self) -> &CommonParams { &self.params } @@ -279,7 +290,7 @@ mod tests { use account_provider::AccountProvider; use spec::Spec; use std::thread::sleep; - use std::time::Duration; + use std::time::{Duration, UNIX_EPOCH}; #[test] fn has_valid_metadata() { @@ -356,7 +367,9 @@ mod tests { header.set_author(addr); let signature = tap.sign_with_password(addr, "0".into(), header.bare_hash()).unwrap(); - header.set_seal(vec![vec![1u8], encode(&(&*signature as &[u8])).to_vec()]); + let timestamp = UNIX_EPOCH.elapsed().unwrap().as_secs(); + let step = timestamp + timestamp % 2 + 1; + header.set_seal(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); let engine = Spec::new_test_round().engine; From 92bdfb12348e4c52224314d20da864f3bac238b5 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Tue, 25 Oct 2016 18:06:05 +0200 Subject: [PATCH 049/106] Add Export button to Accounts (#2147) --- js/src/views/Accounts/accounts.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/js/src/views/Accounts/accounts.js b/js/src/views/Accounts/accounts.js index 2cf2c3adf..821d493da 100644 --- a/js/src/views/Accounts/accounts.js +++ b/js/src/views/Accounts/accounts.js @@ -22,7 +22,7 @@ import { uniq } from 'lodash'; import List from './List'; import { CreateAccount } from '../../modals'; -import { Actionbar, ActionbarSearch, ActionbarSort, Button, Page, Tooltip } from '../../ui'; +import { Actionbar, ActionbarExport, ActionbarSearch, ActionbarSort, Button, Page, Tooltip } from '../../ui'; import styles from './accounts.css'; @@ -96,6 +96,8 @@ class Accounts extends Component { } renderActionbar () { + const { accounts } = this.props; + const buttons = [