From 8da38fa98b8c88fee4242e988f405b7657d49d77 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 19 Aug 2016 17:18:30 +0200 Subject: [PATCH 001/295] intro simple seal bft engine --- ethcore/res/bft.json | 39 +++++ ethcore/src/engines/bft.rs | 289 +++++++++++++++++++++++++++++++++++++ ethcore/src/engines/mod.rs | 4 + 3 files changed, 332 insertions(+) create mode 100644 ethcore/res/bft.json create mode 100644 ethcore/src/engines/bft.rs diff --git a/ethcore/res/bft.json b/ethcore/res/bft.json new file mode 100644 index 000000000..24bd386b2 --- /dev/null +++ b/ethcore/res/bft.json @@ -0,0 +1,39 @@ +{ + "name": "TestBFT", + "engine": { + "BFT": { + "params": { + "gasLimitBoundDivisor": "0x0400", + "durationLimit": "0x0d", + "validators" : ["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/src/engines/bft.rs b/ethcore/src/engines/bft.rs new file mode 100644 index 000000000..aae6854d2 --- /dev/null +++ b/ethcore/src/engines/bft.rs @@ -0,0 +1,289 @@ +// 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 common::*; +use account_provider::AccountProvider; +use block::*; +use spec::CommonParams; +use engines::Engine; +use evm::Schedule; +use ethjson; + +/// `BFT` params. +#[derive(Debug, PartialEq)] +pub struct BFTParams { + /// Gas limit divisor. + pub gas_limit_bound_divisor: U256, + /// Block duration. + pub duration_limit: u64, + /// Validators. + pub validators: Vec
, + /// Number of validators. + pub validator_n: usize, + /// Precommit step votes. + precommits: HashMap> +} + +impl From for BFTParams { + fn from(p: ethjson::spec::BFTParams) -> Self { + let val = p.validators.into_iter().map(Into::into).collect::>(); + BFTParams { + gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), + duration_limit: p.duration_limit.into(), + validators: auth, + validator_n: val.len() + } + } +} + +/// Engine using `BFT` consensus algorithm, suitable for EVM chain. +pub struct BFT { + params: CommonParams, + our_params: BFTParams, + builtins: BTreeMap, +} + +impl BFT { + /// Create a new instance of BFT engine + pub fn new(params: CommonParams, our_params: BFTParams, builtins: BTreeMap) -> Self { + BFT { + params: params, + our_params: our_params, + builtins: builtins, + } + } + + fn check_precommit(&self, bare_hash: &H256, signature: &H520) -> result::Result<(), Error> { + let signer = Address::from(try!(ec::recover(&sig, bare_hash)).sha3()); + if !self.our_params.validators.contains(&signer) { + return try!(Err(BlockError::InvalidSeal)); + } + Ok(()) + } + + fn supermajority(&self) -> usize { 2*self.our_params.validator_n/3 } + + fn signatures_seal(&self, signatures: &HashSet) -> Vec { + signatures.iter().map(|sig| encode(&(&*sig as &[u8])).to_vec()).collect() + } +} + +impl Engine for BFT { + fn name(&self) -> &str { "BFT" } + fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } + /// Possibly signatures of all validators. + fn seal_fields(&self) -> usize { self.our_params.validator_n } + + 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.difficulty = parent.difficulty; + header.gas_limit = { + let gas_limit = parent.gas_limit; + 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()) + } + }; + header.note_dirty(); + } + + /// 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 using all available signatures. + /// + /// None is returned if not enough signatures can be collected. + fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { + let hash = block.bare_hash(); + let signatures = self.our_params.precommits.entry(hash).or_insert(HashSet::new()); + let threshold = self.supermajority(); + match (signatures.len(), accounts) { + (threshold-1, Some(ap)) => { + // account should be pernamently unlocked, otherwise signing will fail + if let Ok(signature) = ap.sign(*block.header().author(), hash) { + *signatures.insert(signature); + Some(self.signatures_seal(signatures)); + } else { + trace!(target: "bft", "generate_seal: FAIL: secret key unavailable"); + None + } + }, + (0..threshold, _) => None, + (threshold.., _) => Some(block.header().seal), + } + } + + fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { + // check the seal fields. + // TODO: pull this out into common code. + if header.seal.len() != self.seal_fields() { + return Err(From::from(BlockError::InvalidSealArity( + Mismatch { expected: self.seal_fields(), found: header.seal.len() } + ))); + } + Ok(()) + } + + fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { + let hash = header.bare_hash(); + let threshold = self.supermajority(); + let signatures = self.our_params.precommits.entry(hash).or_insert(HashSet::new()); + if signatures.len() > threshold { return Ok(()) } + // Count all valid precommits. + for seal_field in header.seal { + let sig = try!(UntrustedRlp::new(seal_field).as_val::()); + if !signatures.contains(sig) || self.check_precommit(hash, sig).is_ok() { + trace!(target: "bft", "verify_block_unordered: new validator vote found"); + *signatures.insert(sig); + if signatures.len() > threshold { return Ok(()) } + } + } + Err(BlockError::InvalidSeal) + } + + fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { + // we should not 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 - parent.gas_limit / gas_limit_divisor; + let max_gas = parent.gas_limit + parent.gas_limit / 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 }))); + } + 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 block::*; + use tests::helpers::*; + use account_provider::AccountProvider; + use spec::Spec; + + /// Create a new test chain spec with `BFT` consensus engine. + fn new_test_authority() -> Spec { Spec::load(include_bytes!("../../res/test_authority.json")) } + + #[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![rlp::encode(&Signature::zero()).to_vec()]); + + let verify_result = engine.verify_block_unordered(&header, None); + + match verify_result { + Err(Error::Util(UtilError::Crypto(CryptoError::InvalidSignature))) => {}, + Err(_) => { panic!("should be block difficulty error (got {:?})", verify_result); }, + _ => { panic!("Should be error, got Ok"); }, + } + } + + #[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 vm_factory = Default::default(); + let b = OpenBlock::new(engine, &vm_factory, 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 e7738fbaa..2f3c0d189 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -113,6 +113,10 @@ pub trait Engine : Sync + Send { header.note_dirty(); } + /// Handle any potential consensus messages; + /// updating consensus state and potentially issuing a new one. + fn handle_message(&self, sender: Address, message: Bytes) -> Option> { None } + // TODO: builtin contract routing - to do this properly, it will require removing the built-in configuration-reading logic // from Spec into here and removing the Spec::builtins field. /// Determine whether a particular address is a builtin contract. From 74939a43d6a1f12cbe0c80195af4a6911e5d0dbf Mon Sep 17 00:00:00 2001 From: keorn Date: Sun, 21 Aug 2016 15:27:39 +0200 Subject: [PATCH 002/295] fix types and lifetimes --- ethcore/src/engines/bft.rs | 65 +++++++++++++++++++++----------------- ethcore/src/engines/mod.rs | 2 ++ 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/ethcore/src/engines/bft.rs b/ethcore/src/engines/bft.rs index aae6854d2..2131800b7 100644 --- a/ethcore/src/engines/bft.rs +++ b/ethcore/src/engines/bft.rs @@ -25,7 +25,7 @@ use evm::Schedule; use ethjson; /// `BFT` params. -#[derive(Debug, PartialEq)] +#[derive(Debug)] pub struct BFTParams { /// Gas limit divisor. pub gas_limit_bound_divisor: U256, @@ -36,7 +36,7 @@ pub struct BFTParams { /// Number of validators. pub validator_n: usize, /// Precommit step votes. - precommits: HashMap> + precommits: RwLock>> } impl From for BFTParams { @@ -45,8 +45,9 @@ impl From for BFTParams { BFTParams { gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), duration_limit: p.duration_limit.into(), - validators: auth, - validator_n: val.len() + validator_n: val.len(), + validators: val, + precommits: RwLock::new(HashMap::new()) } } } @@ -68,17 +69,27 @@ impl BFT { } } - fn check_precommit(&self, bare_hash: &H256, signature: &H520) -> result::Result<(), Error> { - let signer = Address::from(try!(ec::recover(&sig, bare_hash)).sha3()); - if !self.our_params.validators.contains(&signer) { - return try!(Err(BlockError::InvalidSeal)); + fn add_precommit(&self, bare_hash: &H256, signature: &Signature) { + if let Some(mut precommits) = self.our_params.precommits.write().get_mut(bare_hash) { + precommits.insert(signature.clone()); + } else { + let mut new = HashSet::new(); + new.insert(signature.clone()); + assert!(self.our_params.precommits.write().insert(bare_hash.clone(), new).is_none()); + } + } + + fn check_precommit(&self, bare_hash: &H256, signature: &Signature) -> result::Result<(), Error> { + let signer = Address::from(try!(ec::recover(&signature, bare_hash)).sha3()); + match self.our_params.validators.contains(&signer) { + false => try!(Err(BlockError::InvalidSeal)), + true => Ok(()), } - Ok(()) } fn supermajority(&self) -> usize { 2*self.our_params.validator_n/3 } - fn signatures_seal(&self, signatures: &HashSet) -> Vec { + fn signatures_seal(&self, signatures: &HashSet) -> Vec { signatures.iter().map(|sig| encode(&(&*sig as &[u8])).to_vec()).collect() } } @@ -121,22 +132,23 @@ impl Engine for BFT { /// /// None is returned if not enough signatures can be collected. fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { - let hash = block.bare_hash(); - let signatures = self.our_params.precommits.entry(hash).or_insert(HashSet::new()); + let hash = block.header().bare_hash(); + let guard = self.our_params.precommits.read(); + let signatures = guard.get(&hash).unwrap_or(return None); let threshold = self.supermajority(); match (signatures.len(), accounts) { - (threshold-1, Some(ap)) => { + (v, Some(ap)) if v == threshold-1 => { // account should be pernamently unlocked, otherwise signing will fail if let Ok(signature) = ap.sign(*block.header().author(), hash) { - *signatures.insert(signature); - Some(self.signatures_seal(signatures)); + self.add_precommit(&hash, &signature.into()); + Some(self.signatures_seal(signatures)) } else { trace!(target: "bft", "generate_seal: FAIL: secret key unavailable"); None } }, - (0..threshold, _) => None, - (threshold.., _) => Some(block.header().seal), + (v, _) if v < threshold => None, + _ => Some(block.header().seal.clone()), } } @@ -154,18 +166,19 @@ impl Engine for BFT { fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { let hash = header.bare_hash(); let threshold = self.supermajority(); - let signatures = self.our_params.precommits.entry(hash).or_insert(HashSet::new()); + let guard = self.our_params.precommits.read(); + let mut signatures = guard.get(&hash).unwrap_or(try!(Err(BlockError::InvalidSeal))); if signatures.len() > threshold { return Ok(()) } // Count all valid precommits. - for seal_field in header.seal { - let sig = try!(UntrustedRlp::new(seal_field).as_val::()); - if !signatures.contains(sig) || self.check_precommit(hash, sig).is_ok() { + for seal_field in header.seal() { + let sig = try!(UntrustedRlp::new(&seal_field).as_val::()); + if !signatures.contains(&sig) || self.check_precommit(&hash, &sig).is_ok() { trace!(target: "bft", "verify_block_unordered: new validator vote found"); - *signatures.insert(sig); + self.add_precommit(&hash, &sig); if signatures.len() > threshold { return Ok(()) } } } - Err(BlockError::InvalidSeal) + try!(Err(BlockError::InvalidSeal)) } fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { @@ -197,12 +210,6 @@ impl Engine for BFT { } } -impl Header { - /// Get the none field of the header. - pub fn signature(&self) -> H520 { - decode(&self.seal()[0]) - } -} #[cfg(test)] mod tests { diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 2f3c0d189..e52db90fb 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 bft; pub use self::null_engine::NullEngine; pub use self::instant_seal::InstantSeal; pub use self::basic_authority::BasicAuthority; +pub use self::bft::BFT; use common::*; use account_provider::AccountProvider; From a20a0de48f95fdd884688114e7ed6ee92c6a5786 Mon Sep 17 00:00:00 2001 From: keorn Date: Sun, 21 Aug 2016 15:28:40 +0200 Subject: [PATCH 003/295] add spec --- ethcore/src/spec/spec.rs | 3 +- json/src/spec/bft.rs | 59 ++++++++++++++++++++++++++++++++++++++++ json/src/spec/engine.rs | 3 ++ json/src/spec/mod.rs | 2 ++ 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 json/src/spec/bft.rs diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index a0c32d51a..c3db5d4f1 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, BFT}; use pod_state::*; use account_db::*; use super::genesis::Genesis; @@ -139,6 +139,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::BFT(bft) => Arc::new(BFT::new(params, From::from(bft.params), builtins)), } } diff --git a/json/src/spec/bft.rs b/json/src/spec/bft.rs new file mode 100644 index 000000000..a5a34c550 --- /dev/null +++ b/json/src/spec/bft.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 BFTParams { + /// Gas limit divisor. + #[serde(rename="gasLimitBoundDivisor")] + pub gas_limit_bound_divisor: Uint, + /// Block duration. + #[serde(rename="durationLimit")] + pub duration_limit: Uint, + /// Valid authorities + pub validators: Vec
, +} + +/// Authority engine deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct BFT { + /// Ethash params. + pub params: BFTParams, +} + +#[cfg(test)] +mod tests { + use serde_json; + use spec::bft::BFT; + + #[test] + fn basic_authority_deserialization() { + let s = r#"{ + "params": { + "gasLimitBoundDivisor": "0x0400", + "durationLimit": "0x0d", + "validators" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + } + }"#; + + let _deserialized: BFT = serde_json::from_str(s).unwrap(); + } +} diff --git a/json/src/spec/engine.rs b/json/src/spec/engine.rs index 3813b1756..7b32efa51 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::BFT; /// Engine deserialization. #[derive(Debug, PartialEq, Deserialize)] @@ -30,6 +31,8 @@ pub enum Engine { Ethash(Ethash), /// BasicAuthority engine. BasicAuthority(BasicAuthority), + /// Byzantine Fault Tolerant engine. + BFT(BFT) } #[cfg(test)] diff --git a/json/src/spec/mod.rs b/json/src/spec/mod.rs index f6c856b13..c8a6f8bf5 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 bft; 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::bft::{BFT, BFTParams}; From 2f5aeda44fb9d2b92cb41e3a668d834819366246 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 22 Aug 2016 13:19:23 +0200 Subject: [PATCH 004/295] reusable voting on hashes --- ethcore/src/engines/mod.rs | 4 +- ethcore/src/engines/signed_vote.rs | 101 +++++++++++++++++++++++++++++ ethcore/src/error.rs | 12 ++++ 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 ethcore/src/engines/signed_vote.rs diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index e52db90fb..47a12435d 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -20,13 +20,15 @@ mod null_engine; mod instant_seal; mod basic_authority; mod bft; +mod signed_vote; pub use self::null_engine::NullEngine; pub use self::instant_seal::InstantSeal; pub use self::basic_authority::BasicAuthority; pub use self::bft::BFT; +pub use self::signed_vote::VoteError; -use common::*; +use common::{HashMap, SemanticVersion, Header, EnvInfo, Address, Builtin, BTreeMap, U256, Bytes, SignedTransaction, Error}; use account_provider::AccountProvider; use block::ExecutedBlock; use spec::CommonParams; diff --git a/ethcore/src/engines/signed_vote.rs b/ethcore/src/engines/signed_vote.rs new file mode 100644 index 000000000..694b7cc9b --- /dev/null +++ b/ethcore/src/engines/signed_vote.rs @@ -0,0 +1,101 @@ +// 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 . + +//! Voting on hashes, where each vote has to come from a set of public keys. + +use common::*; +use account_provider::AccountProvider; +use block::*; +use spec::CommonParams; +use engines::Engine; +use evm::Schedule; +use ethjson; + +/// Signed voting on hashes. +#[derive(Debug)] +pub struct SignedVote { + /// Voter public keys. + pub voters: HashSet
, + /// Number of voters. + pub voter_n: usize, + /// Threshold vote number for success. + pub threshold: usize, + /// Votes. + votes: RwLock>>, + /// Winner hash, set after enough votes are reached. + winner: RwLock> +} + +#[derive(Debug)] +pub enum VoteError { + UnauthorisedVoter +} + +impl SignedVote { + /// Create a new instance of BFT engine + pub fn new(voters: HashSet
, threshold: usize) -> Self { + SignedVote { + voter_n: voters.len(), + voters: voters, + threshold: threshold, + votes: RwLock::new(HashMap::new()), + winner: RwLock::new(None) + } + } + + pub fn vote(&self, bare_hash: H256, signature: &Signature) -> bool { + if !self.can_vote(&bare_hash, signature).is_ok() { return false; } + let n = if let Some(mut old) = self.votes.write().get_mut(&bare_hash) { + old.insert(signature.clone()); + old.len() + } else { + let mut new = HashSet::new(); + new.insert(signature.clone()); + assert!(self.votes.write().insert(bare_hash.clone(), new).is_none()); + 1 + }; + if self.is_won(n) { + let mut guard = self.winner.write(); + *guard = Some(bare_hash); + } + true + } + + fn can_vote(&self, bare_hash: &H256, signature: &Signature) -> Result<(), Error> { + let signer = Address::from(try!(ec::recover(&signature, bare_hash)).sha3()); + match self.voters.contains(&signer) { + false => try!(Err(VoteError::UnauthorisedVoter)), + true => Ok(()), + } + } + + fn is_won(&self, valid_votes: usize) -> bool { + valid_votes > self.threshold + } + + pub fn winner(&self) -> Option { self.winner.read().clone() } +} + +#[cfg(test)] +mod tests { + use common::{HashSet, Address}; + use engines::signed_vote::SignedVote; + #[test] + fn simple_vote() { + let voters: HashSet<_> = vec![Address::default()].into_iter().collect(); + let vote = SignedVote::new(voters, 2); + } +} diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index 449303732..aed7773ae 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -24,6 +24,7 @@ use client::Error as ClientError; use ipc::binary::{BinaryConvertError, BinaryConvertable}; use types::block_import_error::BlockImportError; use snapshot::Error as SnapshotError; +use engines::VoteError; pub use types::executed::{ExecutionError, CallError}; @@ -238,6 +239,8 @@ pub enum Error { Snappy(::util::snappy::InvalidInput), /// Snapshot error. Snapshot(SnapshotError), + /// Consensus vote error. + Vote(VoteError), } impl fmt::Display for Error { @@ -258,6 +261,7 @@ impl fmt::Display for Error { Error::StdIo(ref err) => err.fmt(f), Error::Snappy(ref err) => err.fmt(f), Error::Snapshot(ref err) => err.fmt(f), + Error::Vote(ref err) => f.write_str("Bad vote."), } } } @@ -361,6 +365,14 @@ impl From for Error { } } +impl From for Error { + fn from(err: VoteError) -> Error { + match err { + other => Error::Vote(other), + } + } +} + impl From> for Error where Error: From { fn from(err: Box) -> Error { Error::from(*err) From 89011dcc34e5255efd830f808d4e6bcb5f10b3a9 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 22 Aug 2016 17:33:04 +0200 Subject: [PATCH 005/295] fix locking patterns, add simple test --- ethcore/src/engines/signed_vote.rs | 84 ++++++++++++++++++++---------- ethcore/src/error.rs | 3 +- 2 files changed, 59 insertions(+), 28 deletions(-) diff --git a/ethcore/src/engines/signed_vote.rs b/ethcore/src/engines/signed_vote.rs index 694b7cc9b..b6facca36 100644 --- a/ethcore/src/engines/signed_vote.rs +++ b/ethcore/src/engines/signed_vote.rs @@ -16,13 +16,7 @@ //! Voting on hashes, where each vote has to come from a set of public keys. -use common::*; -use account_provider::AccountProvider; -use block::*; -use spec::CommonParams; -use engines::Engine; -use evm::Schedule; -use ethjson; +use common::{HashSet, HashMap, RwLock, H256, Signature, Address, Error, ec, Hashable}; /// Signed voting on hashes. #[derive(Debug)] @@ -39,16 +33,20 @@ pub struct SignedVote { winner: RwLock> } +/// Voting errors. #[derive(Debug)] pub enum VoteError { + /// Voter is not in the voters set. UnauthorisedVoter } impl SignedVote { /// Create a new instance of BFT engine pub fn new(voters: HashSet
, threshold: usize) -> Self { + let voters_n = voters.len(); + assert!(voters_n > threshold); SignedVote { - voter_n: voters.len(), + voter_n: voters_n, voters: voters, threshold: threshold, votes: RwLock::new(HashMap::new()), @@ -56,19 +54,23 @@ impl SignedVote { } } + /// Vote on hash using the signed hash, true if vote counted. pub fn vote(&self, bare_hash: H256, signature: &Signature) -> bool { if !self.can_vote(&bare_hash, signature).is_ok() { return false; } - let n = if let Some(mut old) = self.votes.write().get_mut(&bare_hash) { - old.insert(signature.clone()); - old.len() - } else { - let mut new = HashSet::new(); - new.insert(signature.clone()); - assert!(self.votes.write().insert(bare_hash.clone(), new).is_none()); - 1 - }; - if self.is_won(n) { - let mut guard = self.winner.write(); + let mut guard = self.votes.try_write().unwrap(); + let set = guard.entry(bare_hash.clone()).or_insert_with(|| HashSet::new()); + if !set.insert(signature.clone()) { return false; } +// let n = if let Some(mut old) = guard.get_mut(&bare_hash) { +// if !old.insert(signature.clone()) { return false; } +// old.len() +// } else { +// let mut new = HashSet::new(); +// new.insert(signature.clone()); +// assert!(guard.insert(bare_hash.clone(), new).is_none()); +// 1 +// }; + if set.len() >= self.threshold { + let mut guard = self.winner.try_write().unwrap(); *guard = Some(bare_hash); } true @@ -82,20 +84,48 @@ impl SignedVote { } } - fn is_won(&self, valid_votes: usize) -> bool { - valid_votes > self.threshold - } - - pub fn winner(&self) -> Option { self.winner.read().clone() } + /// Some winner if voting threshold was reached. + pub fn winner(&self) -> Option { self.winner.try_read().unwrap().clone() } } #[cfg(test)] mod tests { - use common::{HashSet, Address}; + use common::*; use engines::signed_vote::SignedVote; + use account_provider::AccountProvider; + #[test] fn simple_vote() { - let voters: HashSet<_> = vec![Address::default()].into_iter().collect(); - let vote = SignedVote::new(voters, 2); + let tap = AccountProvider::transient_provider(); + let addr1 = tap.insert_account("1".sha3(), "1").unwrap(); + tap.unlock_account_permanently(addr1, "1".into()).unwrap(); + + let addr2 = tap.insert_account("2".sha3(), "2").unwrap(); + tap.unlock_account_permanently(addr2, "2".into()).unwrap(); + + let addr3 = tap.insert_account("3".sha3(), "3").unwrap(); + tap.unlock_account_permanently(addr3, "3".into()).unwrap(); + + let voters: HashSet<_> = vec![addr1, addr2].into_iter().map(Into::into).collect(); + let vote = SignedVote::new(voters.into(), 1); + assert!(vote.winner().is_none()); + let header = Header::default(); + let bare_hash = header.bare_hash(); + + // Unapproved voter. + let signature = tap.sign(addr3, bare_hash).unwrap(); + assert!(!vote.vote(bare_hash, &signature.into())); + assert!(vote.winner().is_none()); + // First good vote. + let signature = tap.sign(addr1, bare_hash).unwrap(); + assert!(vote.vote(bare_hash, &signature.into())); + assert_eq!(vote.winner().unwrap(), bare_hash); + // Voting again is ineffective. + let signature = tap.sign(addr1, bare_hash).unwrap(); + assert!(!vote.vote(bare_hash, &signature.into())); + // Second valid vote. + let signature = tap.sign(addr2, bare_hash).unwrap(); + assert!(vote.vote(bare_hash, &signature.into())); + assert_eq!(vote.winner().unwrap(), bare_hash); } } diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index aed7773ae..ccc926ce6 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -261,7 +261,8 @@ impl fmt::Display for Error { Error::StdIo(ref err) => err.fmt(f), Error::Snappy(ref err) => err.fmt(f), Error::Snapshot(ref err) => err.fmt(f), - Error::Vote(ref err) => f.write_str("Bad vote."), + Error::Vote(ref err) => + f.write_fmt(format_args!("Bad vote: {:?}", err)), } } } From 3515a72fa08b64b4d37de61ff18ca64e0d47ee22 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 22 Aug 2016 20:00:41 +0200 Subject: [PATCH 006/295] proposal vote collector --- ethcore/src/engines/propose_collect.rs | 130 +++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 ethcore/src/engines/propose_collect.rs diff --git a/ethcore/src/engines/propose_collect.rs b/ethcore/src/engines/propose_collect.rs new file mode 100644 index 000000000..84dee0bd7 --- /dev/null +++ b/ethcore/src/engines/propose_collect.rs @@ -0,0 +1,130 @@ +// 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 . + +//! Voting on a hash, where each vote has to come from a set of addresses. + +use common::{HashSet, RwLock, H256, Signature, Address, Error, ec, Hashable, AtomicBool}; + +/// Collect votes on a hash. +#[derive(Debug)] +pub struct ProposeCollect { + /// Proposed hash. + pub hash: H256, + /// Allowed voter addresses. + pub voters: HashSet
, + /// Threshold vote number for success. + pub threshold: usize, + /// Votes. + votes: RwLock>, + /// Was enough votes reached. + is_won: AtomicBool +} + +/// Voting errors. +#[derive(Debug)] +pub enum VoteError { + /// Voter is not in the voters set. + UnauthorisedVoter +} + +impl SignedVote { + /// Create a new instance of BFT engine + pub fn new(hash: H256, voters: HashSet
, threshold: usize) -> Self { + assert!(voters.len() > threshold); + SignedVote { + hash: hash, + voters: voters, + threshold: threshold, + votes: RwLock::new(HashSet::new()), + is_won: AtomicBool::new(false) + } + } + + /// Vote on hash using the signed hash, true if vote counted. + pub fn vote(&self, signature: &Signature) -> bool { + if self.votes.contains(signature) { return false; } + if !self.can_vote(signature).is_ok() { return false; } + self.votes.try_write().unwrap().insert(signature); + true + } + + fn can_vote(&self, signature: &Signature) -> Result<(), Error> { + let signer = Address::from(try!(ec::recover(&signature, self.hash)).sha3()); + match self.voters.contains(&signer) { + false => try!(Err(VoteError::UnauthorisedVoter)), + true => Ok(()), + } + } + + /// Some winner if voting threshold was reached. + pub fn winner(&self) -> Option { + let threshold_checker = || match self.votes.len() >= threshold { + true => { self.is_won.store(true, Ordering::Relaxed); true }, + false => false, + }; + match self.is_won || threshold_checker() { + true => Some(self.hash), + false => None, + } + } + + /// Get signatures backing given hash. + pub fn votes(&self) -> HashSet { + self.votes.try_read().unwrap().clone() + } +} + +#[cfg(test)] +mod tests { + use common::*; + use engines::propose_collect::ProposeCollect; + use account_provider::AccountProvider; + + #[test] + fn simple_propose_collect() { + let tap = AccountProvider::transient_provider(); + let addr1 = tap.insert_account("1".sha3(), "1").unwrap(); + tap.unlock_account_permanently(addr1, "1".into()).unwrap(); + + let addr2 = tap.insert_account("2".sha3(), "2").unwrap(); + tap.unlock_account_permanently(addr2, "2".into()).unwrap(); + + let addr3 = tap.insert_account("3".sha3(), "3").unwrap(); + tap.unlock_account_permanently(addr3, "3".into()).unwrap(); + + let header = Header::default(); + let bare_hash = header.bare_hash(); + let voters: HashSet<_> = vec![addr1, addr2].into_iter().map(Into::into).collect(); + let vote = ProposeCollect::new(bare_hash, voters.into(), 1); + assert!(vote.winner().is_none()); + + // Unapproved voter. + let signature = tap.sign(addr3, bare_hash).unwrap(); + assert!(!vote.vote(&signature.into())); + assert!(vote.winner().is_none()); + // First good vote. + let signature = tap.sign(addr1, bare_hash).unwrap(); + assert!(vote.vote(&signature.into())); + assert_eq!(vote.winner().unwrap(), bare_hash); + // Voting again is ineffective. + let signature = tap.sign(addr1, bare_hash).unwrap(); + assert!(!vote.vote(&signature.into())); + // Second valid vote. + let signature = tap.sign(addr2, bare_hash).unwrap(); + assert!(vote.vote(&signature.into())); + assert_eq!(vote.winner().unwrap(), bare_hash); + } +} From 3aa862c9c2609b16a5847ae8485627ad8fc5604a Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 23 Aug 2016 12:58:40 +0200 Subject: [PATCH 007/295] add test, start tendermint --- ethcore/src/engines/mod.rs | 8 +- ethcore/src/engines/propose_collect.rs | 25 +-- ethcore/src/engines/signed_vote.rs | 15 +- ethcore/src/engines/tendermint.rs | 284 +++++++++++++++++++++++++ ethcore/src/spec/spec.rs | 4 +- json/src/spec/engine.rs | 4 +- json/src/spec/mod.rs | 4 +- json/src/spec/tendermint.rs | 59 +++++ 8 files changed, 370 insertions(+), 33 deletions(-) create mode 100644 ethcore/src/engines/tendermint.rs create mode 100644 json/src/spec/tendermint.rs diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 47a12435d..fc45edbc9 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -19,14 +19,16 @@ mod null_engine; mod instant_seal; mod basic_authority; -mod bft; +mod tendermint; mod signed_vote; +mod propose_collect; pub use self::null_engine::NullEngine; pub use self::instant_seal::InstantSeal; pub use self::basic_authority::BasicAuthority; -pub use self::bft::BFT; -pub use self::signed_vote::VoteError; +pub use self::tendermint::Tendermint; +pub use self::signed_vote::{SignedVote, VoteError}; +pub use self::propose_collect::{ProposeCollect}; use common::{HashMap, SemanticVersion, Header, EnvInfo, Address, Builtin, BTreeMap, U256, Bytes, SignedTransaction, Error}; use account_provider::AccountProvider; diff --git a/ethcore/src/engines/propose_collect.rs b/ethcore/src/engines/propose_collect.rs index 84dee0bd7..f09618c50 100644 --- a/ethcore/src/engines/propose_collect.rs +++ b/ethcore/src/engines/propose_collect.rs @@ -16,7 +16,9 @@ //! Voting on a hash, where each vote has to come from a set of addresses. -use common::{HashSet, RwLock, H256, Signature, Address, Error, ec, Hashable, AtomicBool}; +use std::sync::atomic::{AtomicBool, Ordering}; +use common::{HashSet, RwLock, H256, Signature, Address, Error, ec, Hashable}; +use engines::VoteError; /// Collect votes on a hash. #[derive(Debug)] @@ -33,18 +35,11 @@ pub struct ProposeCollect { is_won: AtomicBool } -/// Voting errors. -#[derive(Debug)] -pub enum VoteError { - /// Voter is not in the voters set. - UnauthorisedVoter -} - -impl SignedVote { +impl ProposeCollect { /// Create a new instance of BFT engine pub fn new(hash: H256, voters: HashSet
, threshold: usize) -> Self { assert!(voters.len() > threshold); - SignedVote { + ProposeCollect { hash: hash, voters: voters, threshold: threshold, @@ -55,14 +50,14 @@ impl SignedVote { /// Vote on hash using the signed hash, true if vote counted. pub fn vote(&self, signature: &Signature) -> bool { - if self.votes.contains(signature) { return false; } + if self.votes.try_read().unwrap().contains(signature) { return false; } if !self.can_vote(signature).is_ok() { return false; } - self.votes.try_write().unwrap().insert(signature); + self.votes.try_write().unwrap().insert(signature.clone()); true } fn can_vote(&self, signature: &Signature) -> Result<(), Error> { - let signer = Address::from(try!(ec::recover(&signature, self.hash)).sha3()); + let signer = Address::from(try!(ec::recover(&signature, &self.hash)).sha3()); match self.voters.contains(&signer) { false => try!(Err(VoteError::UnauthorisedVoter)), true => Ok(()), @@ -71,11 +66,11 @@ impl SignedVote { /// Some winner if voting threshold was reached. pub fn winner(&self) -> Option { - let threshold_checker = || match self.votes.len() >= threshold { + let threshold_checker = || match self.votes.try_read().unwrap().len() >= self.threshold { true => { self.is_won.store(true, Ordering::Relaxed); true }, false => false, }; - match self.is_won || threshold_checker() { + match self.is_won.load(Ordering::Relaxed) || threshold_checker() { true => Some(self.hash), false => None, } diff --git a/ethcore/src/engines/signed_vote.rs b/ethcore/src/engines/signed_vote.rs index b6facca36..d3381112a 100644 --- a/ethcore/src/engines/signed_vote.rs +++ b/ethcore/src/engines/signed_vote.rs @@ -60,15 +60,7 @@ impl SignedVote { let mut guard = self.votes.try_write().unwrap(); let set = guard.entry(bare_hash.clone()).or_insert_with(|| HashSet::new()); if !set.insert(signature.clone()) { return false; } -// let n = if let Some(mut old) = guard.get_mut(&bare_hash) { -// if !old.insert(signature.clone()) { return false; } -// old.len() -// } else { -// let mut new = HashSet::new(); -// new.insert(signature.clone()); -// assert!(guard.insert(bare_hash.clone(), new).is_none()); -// 1 -// }; + // Set the winner if threshold is reached. if set.len() >= self.threshold { let mut guard = self.winner.try_write().unwrap(); *guard = Some(bare_hash); @@ -86,6 +78,11 @@ impl SignedVote { /// Some winner if voting threshold was reached. pub fn winner(&self) -> Option { self.winner.try_read().unwrap().clone() } + + /// Get signatures backing given hash. + pub fn votes(&self, bare_hash: &H256) -> Option> { + self.votes.try_read().unwrap().get(bare_hash).cloned() + } } #[cfg(test)] diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs new file mode 100644 index 000000000..cdc0aaf3f --- /dev/null +++ b/ethcore/src/engines/tendermint.rs @@ -0,0 +1,284 @@ +// 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 . + +//! Tendermint BFT consensus engine with round robin proof-of-authority. + +use common::*; +use account_provider::AccountProvider; +use block::*; +use spec::CommonParams; +use engines::{Engine, ProposeCollect}; +use evm::Schedule; +use ethjson; + +/// `Tendermint` params. +#[derive(Debug)] +pub struct TendermintParams { + /// Gas limit divisor. + pub gas_limit_bound_divisor: U256, + /// Block duration. + pub duration_limit: u64, + /// List of validators. + pub validators: Vec
, + /// Number of validators. + pub validator_n: usize, + /// Consensus round. + r: u64, + /// Consensus step. + s: Step, + /// Used to swith proposer. + proposer_nonce: usize +} + +#[derive(Debug)] +enum Step { + Propose, + Prevote(ProposeCollect), + Precommit(ProposeCollect), + Commit +} + +impl From for TendermintParams { + fn from(p: ethjson::spec::TendermintParams) -> Self { + let val: Vec<_> = p.validators.into_iter().map(Into::into).collect(); + let val_n = val.len(); + TendermintParams { + gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), + duration_limit: p.duration_limit.into(), + validators: val, + validator_n: val_n, + r: 0, + s: Step::Propose, + proposer_nonce: 0 + } + } +} + +/// Engine using `Tendermint` consensus algorithm, suitable for EVM chain. +pub struct Tendermint { + params: CommonParams, + our_params: TendermintParams, + builtins: BTreeMap, +} + +impl Tendermint { + /// Create a new instance of Tendermint engine + pub fn new(params: CommonParams, our_params: TendermintParams, builtins: BTreeMap) -> Self { + Tendermint { + params: params, + our_params: our_params, + builtins: builtins, + } + } + + fn proposer(&self) -> Address { + let ref p = self.our_params; + p.validators.get(p.proposer_nonce%p.validator_n).unwrap().clone() + } +} + +impl Engine for Tendermint { + fn name(&self) -> &str { "Tendermint" } + fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } + /// Possibly signatures of all validators. + fn seal_fields(&self) -> usize { self.our_params.validator_n } + + 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.difficulty = parent.difficulty; + header.gas_limit = { + let gas_limit = parent.gas_limit; + 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()) + } + }; + header.note_dirty(); + } + + /// 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 using all available signatures. + /// + /// None is returned if not enough signatures can be collected. + fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { + accounts.and_then(|ap| { + let header = block.header(); + if header.author() == &self.proposer() { + ap.sign(*header.author(), header.bare_hash()) + .ok() + .and_then(|signature| Some(vec![encode(&(&*signature as &[u8])).to_vec()])) + } else { + None + } + }) + } + + fn handle_message(&self, sender: Address, message: Bytes) -> Option> { + match message[0] { + 0 => println!("0"), + _ => println!("unknown"), + } + //let sig: Signature = message.into(); + None + } + + fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { + // check the seal fields. + // TODO: pull this out into common code. + if header.seal.len() != self.seal_fields() { + return Err(From::from(BlockError::InvalidSealArity( + Mismatch { expected: self.seal_fields(), found: header.seal.len() } + ))); + } + Ok(()) + } + + fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { + Ok(()) + } + + fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { + // we should not 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 - parent.gas_limit / gas_limit_divisor; + let max_gas = parent.gas_limit + parent.gas_limit / 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 }))); + } + 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 + } +} + + +#[cfg(test)] +mod tests { + use common::*; + use block::*; + use tests::helpers::*; + use account_provider::AccountProvider; + use spec::Spec; + + /// Create a new test chain spec with `Tendermint` consensus engine. + fn new_test_authority() -> Spec { Spec::load(include_bytes!("../../res/bft.json")) } + + #[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![rlp::encode(&Signature::zero()).to_vec()]); + + let verify_result = engine.verify_block_unordered(&header, None); + + match verify_result { + Err(Error::Util(UtilError::Crypto(CryptoError::InvalidSignature))) => {}, + Err(_) => { panic!("should be block difficulty error (got {:?})", verify_result); }, + _ => { panic!("Should be error, got Ok"); }, + } + } + + #[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 vm_factory = Default::default(); + let b = OpenBlock::new(engine, &vm_factory, 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()); + } + + #[test] + fn handle_message() { + false; + } +} diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index c3db5d4f1..5f7995abb 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, BFT}; +use engines::{Engine, NullEngine, InstantSeal, BasicAuthority, Tendermint}; use pod_state::*; use account_db::*; use super::genesis::Genesis; @@ -139,7 +139,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::BFT(bft) => Arc::new(BFT::new(params, From::from(bft.params), builtins)), + ethjson::spec::Engine::Tendermint(tendermint) => Arc::new(Tendermint::new(params, From::from(tendermint.params), builtins)), } } diff --git a/json/src/spec/engine.rs b/json/src/spec/engine.rs index 7b32efa51..7285f6576 100644 --- a/json/src/spec/engine.rs +++ b/json/src/spec/engine.rs @@ -18,7 +18,7 @@ use spec::Ethash; use spec::BasicAuthority; -use spec::BFT; +use spec::Tendermint; /// Engine deserialization. #[derive(Debug, PartialEq, Deserialize)] @@ -32,7 +32,7 @@ pub enum Engine { /// BasicAuthority engine. BasicAuthority(BasicAuthority), /// Byzantine Fault Tolerant engine. - BFT(BFT) + Tendermint(Tendermint) } #[cfg(test)] diff --git a/json/src/spec/mod.rs b/json/src/spec/mod.rs index c8a6f8bf5..0ad51e63f 100644 --- a/json/src/spec/mod.rs +++ b/json/src/spec/mod.rs @@ -26,7 +26,7 @@ pub mod engine; pub mod state; pub mod ethash; pub mod basic_authority; -pub mod bft; +pub mod tendermint; pub use self::account::Account; pub use self::builtin::{Builtin, Pricing, Linear}; @@ -38,4 +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::bft::{BFT, BFTParams}; +pub use self::tendermint::{Tendermint, TendermintParams}; diff --git a/json/src/spec/tendermint.rs b/json/src/spec/tendermint.rs new file mode 100644 index 000000000..c3294810c --- /dev/null +++ b/json/src/spec/tendermint.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 . + +//! Tendermint params deserialization. + +use uint::Uint; +use hash::Address; + +/// Tendermint params deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct TendermintParams { + /// Gas limit divisor. + #[serde(rename="gasLimitBoundDivisor")] + pub gas_limit_bound_divisor: Uint, + /// Block duration. + #[serde(rename="durationLimit")] + pub duration_limit: Uint, + /// Valid authorities + pub validators: Vec
, +} + +/// Tendermint engine deserialization. +#[derive(Debug, PartialEq, Deserialize)] +pub struct Tendermint { + /// Ethash params. + pub params: TendermintParams, +} + +#[cfg(test)] +mod tests { + use serde_json; + use spec::tendermint::Tendermint; + + #[test] + fn basic_authority_deserialization() { + let s = r#"{ + "params": { + "gasLimitBoundDivisor": "0x0400", + "durationLimit": "0x0d", + "validators" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + } + }"#; + + let _deserialized: Tendermint = serde_json::from_str(s).unwrap(); + } +} From 535c502771437e76db815001c14ea4e266d50d3f Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 23 Aug 2016 15:44:01 +0200 Subject: [PATCH 008/295] delete old test --- ethcore/src/engines/bft.rs | 296 ------------------------------------- json/src/spec/bft.rs | 59 -------- 2 files changed, 355 deletions(-) delete mode 100644 ethcore/src/engines/bft.rs delete mode 100644 json/src/spec/bft.rs diff --git a/ethcore/src/engines/bft.rs b/ethcore/src/engines/bft.rs deleted file mode 100644 index 2131800b7..000000000 --- a/ethcore/src/engines/bft.rs +++ /dev/null @@ -1,296 +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 . - -//! A blockchain engine that supports a basic, non-BFT proof-of-authority. - -use common::*; -use account_provider::AccountProvider; -use block::*; -use spec::CommonParams; -use engines::Engine; -use evm::Schedule; -use ethjson; - -/// `BFT` params. -#[derive(Debug)] -pub struct BFTParams { - /// Gas limit divisor. - pub gas_limit_bound_divisor: U256, - /// Block duration. - pub duration_limit: u64, - /// Validators. - pub validators: Vec
, - /// Number of validators. - pub validator_n: usize, - /// Precommit step votes. - precommits: RwLock>> -} - -impl From for BFTParams { - fn from(p: ethjson::spec::BFTParams) -> Self { - let val = p.validators.into_iter().map(Into::into).collect::>(); - BFTParams { - gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), - duration_limit: p.duration_limit.into(), - validator_n: val.len(), - validators: val, - precommits: RwLock::new(HashMap::new()) - } - } -} - -/// Engine using `BFT` consensus algorithm, suitable for EVM chain. -pub struct BFT { - params: CommonParams, - our_params: BFTParams, - builtins: BTreeMap, -} - -impl BFT { - /// Create a new instance of BFT engine - pub fn new(params: CommonParams, our_params: BFTParams, builtins: BTreeMap) -> Self { - BFT { - params: params, - our_params: our_params, - builtins: builtins, - } - } - - fn add_precommit(&self, bare_hash: &H256, signature: &Signature) { - if let Some(mut precommits) = self.our_params.precommits.write().get_mut(bare_hash) { - precommits.insert(signature.clone()); - } else { - let mut new = HashSet::new(); - new.insert(signature.clone()); - assert!(self.our_params.precommits.write().insert(bare_hash.clone(), new).is_none()); - } - } - - fn check_precommit(&self, bare_hash: &H256, signature: &Signature) -> result::Result<(), Error> { - let signer = Address::from(try!(ec::recover(&signature, bare_hash)).sha3()); - match self.our_params.validators.contains(&signer) { - false => try!(Err(BlockError::InvalidSeal)), - true => Ok(()), - } - } - - fn supermajority(&self) -> usize { 2*self.our_params.validator_n/3 } - - fn signatures_seal(&self, signatures: &HashSet) -> Vec { - signatures.iter().map(|sig| encode(&(&*sig as &[u8])).to_vec()).collect() - } -} - -impl Engine for BFT { - fn name(&self) -> &str { "BFT" } - fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } - /// Possibly signatures of all validators. - fn seal_fields(&self) -> usize { self.our_params.validator_n } - - 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.difficulty = parent.difficulty; - header.gas_limit = { - let gas_limit = parent.gas_limit; - 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()) - } - }; - header.note_dirty(); - } - - /// 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 using all available signatures. - /// - /// None is returned if not enough signatures can be collected. - fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { - let hash = block.header().bare_hash(); - let guard = self.our_params.precommits.read(); - let signatures = guard.get(&hash).unwrap_or(return None); - let threshold = self.supermajority(); - match (signatures.len(), accounts) { - (v, Some(ap)) if v == threshold-1 => { - // account should be pernamently unlocked, otherwise signing will fail - if let Ok(signature) = ap.sign(*block.header().author(), hash) { - self.add_precommit(&hash, &signature.into()); - Some(self.signatures_seal(signatures)) - } else { - trace!(target: "bft", "generate_seal: FAIL: secret key unavailable"); - None - } - }, - (v, _) if v < threshold => None, - _ => Some(block.header().seal.clone()), - } - } - - fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { - // check the seal fields. - // TODO: pull this out into common code. - if header.seal.len() != self.seal_fields() { - return Err(From::from(BlockError::InvalidSealArity( - Mismatch { expected: self.seal_fields(), found: header.seal.len() } - ))); - } - Ok(()) - } - - fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { - let hash = header.bare_hash(); - let threshold = self.supermajority(); - let guard = self.our_params.precommits.read(); - let mut signatures = guard.get(&hash).unwrap_or(try!(Err(BlockError::InvalidSeal))); - if signatures.len() > threshold { return Ok(()) } - // Count all valid precommits. - for seal_field in header.seal() { - let sig = try!(UntrustedRlp::new(&seal_field).as_val::()); - if !signatures.contains(&sig) || self.check_precommit(&hash, &sig).is_ok() { - trace!(target: "bft", "verify_block_unordered: new validator vote found"); - self.add_precommit(&hash, &sig); - if signatures.len() > threshold { return Ok(()) } - } - } - try!(Err(BlockError::InvalidSeal)) - } - - fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { - // we should not 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 - parent.gas_limit / gas_limit_divisor; - let max_gas = parent.gas_limit + parent.gas_limit / 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 }))); - } - 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 - } -} - - -#[cfg(test)] -mod tests { - use common::*; - use block::*; - use tests::helpers::*; - use account_provider::AccountProvider; - use spec::Spec; - - /// Create a new test chain spec with `BFT` consensus engine. - fn new_test_authority() -> Spec { Spec::load(include_bytes!("../../res/test_authority.json")) } - - #[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![rlp::encode(&Signature::zero()).to_vec()]); - - let verify_result = engine.verify_block_unordered(&header, None); - - match verify_result { - Err(Error::Util(UtilError::Crypto(CryptoError::InvalidSignature))) => {}, - Err(_) => { panic!("should be block difficulty error (got {:?})", verify_result); }, - _ => { panic!("Should be error, got Ok"); }, - } - } - - #[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 vm_factory = Default::default(); - let b = OpenBlock::new(engine, &vm_factory, 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/json/src/spec/bft.rs b/json/src/spec/bft.rs deleted file mode 100644 index a5a34c550..000000000 --- a/json/src/spec/bft.rs +++ /dev/null @@ -1,59 +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 . - -//! Authority params deserialization. - -use uint::Uint; -use hash::Address; - -/// Authority params deserialization. -#[derive(Debug, PartialEq, Deserialize)] -pub struct BFTParams { - /// Gas limit divisor. - #[serde(rename="gasLimitBoundDivisor")] - pub gas_limit_bound_divisor: Uint, - /// Block duration. - #[serde(rename="durationLimit")] - pub duration_limit: Uint, - /// Valid authorities - pub validators: Vec
, -} - -/// Authority engine deserialization. -#[derive(Debug, PartialEq, Deserialize)] -pub struct BFT { - /// Ethash params. - pub params: BFTParams, -} - -#[cfg(test)] -mod tests { - use serde_json; - use spec::bft::BFT; - - #[test] - fn basic_authority_deserialization() { - let s = r#"{ - "params": { - "gasLimitBoundDivisor": "0x0400", - "durationLimit": "0x0d", - "validators" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] - } - }"#; - - let _deserialized: BFT = serde_json::from_str(s).unwrap(); - } -} From 207f9d02f25c42b120bad0ea21ad1606a1c3f442 Mon Sep 17 00:00:00 2001 From: arkpar Date: Mon, 15 Aug 2016 14:25:57 +0200 Subject: [PATCH 009/295] Started inf networking --- ethcore/src/client/chain_notify.rs | 4 + ethcore/src/client/client.rs | 4 + ethcore/src/client/test_client.rs | 4 + ethcore/src/client/traits.rs | 3 + sync/src/api.rs | 63 ++++++++-- sync/src/infinity.rs | 190 +++++++++++++++++++++++++++++ sync/src/lib.rs | 1 + 7 files changed, 259 insertions(+), 10 deletions(-) create mode 100644 sync/src/infinity.rs diff --git a/ethcore/src/client/chain_notify.rs b/ethcore/src/client/chain_notify.rs index 897c8cfac..e4638f152 100644 --- a/ethcore/src/client/chain_notify.rs +++ b/ethcore/src/client/chain_notify.rs @@ -40,6 +40,10 @@ pub trait ChainNotify : Send + Sync { fn stop(&self) { // does nothing by default } + + /// fires when chain broadcasts a message + fn broadcast(&self, _data: Vec) { + } } impl IpcConfig for ChainNotify { } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index aced57e4c..bf871e4ad 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1020,6 +1020,10 @@ impl BlockChainClient for Client { fn pending_transactions(&self) -> Vec { self.miner.pending_transactions() } + + fn queue_infinity_message(&self, _message: Bytes) { + //TODO: handle message here + } } impl MiningBlockChainClient for Client { diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 212dead9a..8852448cc 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -554,6 +554,10 @@ impl BlockChainClient for TestBlockChainClient { self.miner.import_external_transactions(self, txs); } + fn queue_infinity_message(&self, _packet: Bytes) { + unimplemented!(); + } + fn pending_transactions(&self) -> Vec { self.miner.pending_transactions() } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 271e95785..da876efa6 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -182,6 +182,9 @@ pub trait BlockChainClient : Sync + Send { /// Queue transactions for importing. fn queue_transactions(&self, transactions: Vec); + /// Queue packet + fn queue_infinity_message(&self, packet: Bytes); + /// list all transactions fn pending_transactions(&self) -> Vec; diff --git a/sync/src/api.rs b/sync/src/api.rs index 608d9d521..cb1c47229 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -23,6 +23,7 @@ use ethcore::client::{BlockChainClient, ChainNotify}; use ethcore::header::BlockNumber; use sync_io::NetSyncIo; use chain::{ChainSync, SyncStatus}; +use infinity::{InfinitySync}; use std::net::{SocketAddr, AddrParseError}; use ipc::{BinaryConvertable, BinaryConvertError, IpcConfig}; use std::str::FromStr; @@ -30,6 +31,8 @@ use parking_lot::RwLock; /// Ethereum sync protocol pub const ETH_PROTOCOL: &'static str = "eth"; +/// Infinity protocol +pub const INF_PROTOCOL: &'static str = "inf"; /// Sync configuration #[derive(Debug, Clone)] @@ -65,18 +68,22 @@ pub trait SyncProvider: Send + Sync { pub struct EthSync { /// Network service network: NetworkService, - /// Protocol handler - handler: Arc, + /// Ethereum Protocol handler + eth_handler: Arc, + /// Infinity Protocol handler + inf_handler: Arc, } impl EthSync { /// Creates and register protocol with the network service pub fn new(config: SyncConfig, chain: Arc, network_config: NetworkConfiguration) -> Result, NetworkError> { + let inf_sync = InfinitySync::new(&config, chain.clone()); let chain_sync = ChainSync::new(config, &*chain); let service = try!(NetworkService::new(try!(network_config.into_basic()))); let sync = Arc::new(EthSync{ network: service, - handler: Arc::new(SyncProtocolHandler { sync: RwLock::new(chain_sync), chain: chain }), + eth_handler: Arc::new(SyncProtocolHandler { sync: RwLock::new(chain_sync), chain: chain.clone() }), + inf_handler: Arc::new(InfProtocolHandler { sync: RwLock::new(inf_sync), chain: chain }), }); Ok(sync) @@ -88,12 +95,12 @@ impl EthSync { impl SyncProvider for EthSync { /// Get sync status fn status(&self) -> SyncStatus { - self.handler.sync.write().status() + self.eth_handler.sync.write().status() } } struct SyncProtocolHandler { - /// Shared blockchain client. TODO: this should evetually become an IPC endpoint + /// Shared blockchain client. chain: Arc, /// Sync strategy sync: RwLock, @@ -122,6 +129,33 @@ impl NetworkProtocolHandler for SyncProtocolHandler { } } +struct InfProtocolHandler { + /// Shared blockchain client. + chain: Arc, + /// Sync strategy + sync: RwLock, +} + +impl NetworkProtocolHandler for InfProtocolHandler { + fn initialize(&self, _io: &NetworkContext) { + } + + fn read(&self, io: &NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) { + InfinitySync::dispatch_packet(&self.sync, &mut NetSyncIo::new(io, &*self.chain), *peer, packet_id, data); + } + + fn connected(&self, io: &NetworkContext, peer: &PeerId) { + self.sync.write().on_peer_connected(&mut NetSyncIo::new(io, &*self.chain), *peer); + } + + fn disconnected(&self, io: &NetworkContext, peer: &PeerId) { + self.sync.write().on_peer_aborting(&mut NetSyncIo::new(io, &*self.chain), *peer); + } + + fn timeout(&self, _io: &NetworkContext, _timer: TimerToken) { + } +} + impl ChainNotify for EthSync { fn new_blocks(&self, imported: Vec, @@ -132,8 +166,8 @@ impl ChainNotify for EthSync { _duration: u64) { self.network.with_context(ETH_PROTOCOL, |context| { - let mut sync_io = NetSyncIo::new(context, &*self.handler.chain); - self.handler.sync.write().chain_new_blocks( + let mut sync_io = NetSyncIo::new(context, &*self.eth_handler.chain); + self.eth_handler.sync.write().chain_new_blocks( &mut sync_io, &imported, &invalid, @@ -145,13 +179,22 @@ impl ChainNotify for EthSync { fn start(&self) { self.network.start().unwrap_or_else(|e| warn!("Error starting network: {:?}", e)); - self.network.register_protocol(self.handler.clone(), ETH_PROTOCOL, &[62u8, 63u8]) + self.network.register_protocol(self.eth_handler.clone(), ETH_PROTOCOL, &[62u8, 63u8]) .unwrap_or_else(|e| warn!("Error registering ethereum protocol: {:?}", e)); + self.network.register_protocol(self.inf_handler.clone(), INF_PROTOCOL, &[1u8]) + .unwrap_or_else(|e| warn!("Error registering infinity protocol: {:?}", e)); } fn stop(&self) { self.network.stop().unwrap_or_else(|e| warn!("Error stopping network: {:?}", e)); } + + fn broadcast(&self, message: Vec) { + self.network.with_context(ETH_PROTOCOL, |context| { + let mut sync_io = NetSyncIo::new(context, &*self.eth_handler.chain); + self.inf_handler.sync.write().propagate_packet(&mut sync_io, message.clone()); + }); + } } impl IpcConfig for ManageNetwork { } @@ -201,8 +244,8 @@ impl ManageNetwork for EthSync { fn stop_network(&self) { self.network.with_context(ETH_PROTOCOL, |context| { - let mut sync_io = NetSyncIo::new(context, &*self.handler.chain); - self.handler.sync.write().abort(&mut sync_io); + let mut sync_io = NetSyncIo::new(context, &*self.eth_handler.chain); + self.eth_handler.sync.write().abort(&mut sync_io); }); self.stop(); } diff --git a/sync/src/infinity.rs b/sync/src/infinity.rs new file mode 100644 index 000000000..23886560e --- /dev/null +++ b/sync/src/infinity.rs @@ -0,0 +1,190 @@ +// 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 . + +/// Infinity networking + +use util::*; +use network::*; +use ethcore::client::{BlockChainClient}; +use sync_io::SyncIo; +use super::SyncConfig; + +known_heap_size!(0, PeerInfo); + +type PacketDecodeError = DecoderError; + +const PROTOCOL_VERSION: u8 = 1u8; + +const STATUS_PACKET: u8 = 0x00; +const GENERIC_PACKET: u8 = 0x01; + +/// Syncing status and statistics +#[derive(Clone)] +pub struct NetworkStatus { + pub protocol_version: u8, + /// The underlying p2p network version. + pub network_id: U256, + /// Total number of connected peers + pub num_peers: usize, + /// Total number of active peers + pub num_active_peers: usize, +} + +#[derive(Clone)] +/// Inf peer information +struct PeerInfo { + /// inf protocol version + protocol_version: u32, + /// Peer chain genesis hash + genesis: H256, + /// Peer network id + network_id: U256, +} + +/// Infinity protocol handler. +pub struct InfinitySync { + chain: Arc, + /// All connected peers + peers: HashMap, + /// Network ID + network_id: U256, +} + +impl InfinitySync { + /// Create a new instance of syncing strategy. + pub fn new(config: &SyncConfig, chain: Arc) -> InfinitySync { + let mut sync = InfinitySync { + chain: chain, + peers: HashMap::new(), + network_id: config.network_id, + }; + sync.reset(); + sync + } + + /// @returns Synchonization status + pub fn _status(&self) -> NetworkStatus { + NetworkStatus { + protocol_version: 1, + network_id: self.network_id, + num_peers: self.peers.len(), + num_active_peers: 0, + } + } + + #[cfg_attr(feature="dev", allow(for_kv_map))] // Because it's not possible to get `values_mut()` + /// Reset sync. Clear all downloaded data but keep the queue + fn reset(&mut self) { + } + + /// Called by peer to report status + fn on_peer_status(&mut self, io: &mut SyncIo, peer_id: PeerId, r: &UntrustedRlp) -> Result<(), PacketDecodeError> { + let peer = PeerInfo { + protocol_version: try!(r.val_at(0)), + network_id: try!(r.val_at(1)), + genesis: try!(r.val_at(2)), + }; + trace!(target: "inf", "New peer {} (protocol: {}, network: {:?}, genesis:{})", peer_id, peer.protocol_version, peer.network_id, peer.genesis); + if self.peers.contains_key(&peer_id) { + debug!(target: "inf", "Unexpected status packet from {}:{}", peer_id, io.peer_info(peer_id)); + return Ok(()); + } + let chain_info = io.chain().chain_info(); + if peer.genesis != chain_info.genesis_hash { + io.disable_peer(peer_id); + trace!(target: "inf", "Peer {} genesis hash mismatch (ours: {}, theirs: {})", peer_id, chain_info.genesis_hash, peer.genesis); + return Ok(()); + } + if peer.network_id != self.network_id { + io.disable_peer(peer_id); + trace!(target: "inf", "Peer {} network id mismatch (ours: {}, theirs: {})", peer_id, self.network_id, peer.network_id); + return Ok(()); + } + + self.peers.insert(peer_id.clone(), peer); + Ok(()) + } + + /// Called when a new peer is connected + pub fn on_peer_connected(&mut self, io: &mut SyncIo, peer: PeerId) { + trace!(target: "inf", "== Connected {}: {}", peer, io.peer_info(peer)); + if let Err(e) = self.send_status(io) { + debug!(target:"inf", "Error sending status request: {:?}", e); + io.disable_peer(peer); + } + } + + /// Generic packet sender + fn send_packet(&mut self, sync: &mut SyncIo, peer_id: PeerId, packet_id: PacketId, packet: Bytes) { + if self.peers.contains_key(&peer_id) { + if let Err(e) = sync.send(peer_id, packet_id, packet) { + debug!(target:"inf", "Error sending request: {:?}", e); + sync.disable_peer(peer_id); + } + } + } + + /// Called when peer sends us new transactions + fn on_peer_packet(&mut self, _io: &mut SyncIo, _peer_id: PeerId, r: &UntrustedRlp) -> Result<(), PacketDecodeError> { + self.chain.queue_infinity_message(r.as_raw().to_vec()); + Ok(()) + } + + /// Called by peer when it is disconnecting + pub fn on_peer_aborting(&mut self, io: &mut SyncIo, peer: PeerId) { + trace!(target: "inf", "== Disconnecting {}: {}", peer, io.peer_info(peer)); + if self.peers.contains_key(&peer) { + debug!(target: "inf", "Disconnected {}", peer); + self.peers.remove(&peer); + } + } + + /// Send Status message + fn send_status(&mut self, io: &mut SyncIo) -> Result<(), NetworkError> { + let mut packet = RlpStream::new_list(5); + let chain = io.chain().chain_info(); + packet.append(&(PROTOCOL_VERSION as u32)); + packet.append(&self.network_id); + packet.append(&chain.total_difficulty); + packet.append(&chain.best_block_hash); + packet.append(&chain.genesis_hash); + io.respond(STATUS_PACKET, packet.out()) + } + + pub fn dispatch_packet(sync: &RwLock, io: &mut SyncIo, peer: PeerId, packet_id: u8, data: &[u8]) { + let rlp = UntrustedRlp::new(data); + match packet_id { + STATUS_PACKET => sync.write().on_peer_status(io, peer, &rlp).unwrap_or_else( + |e| trace!(target: "inf", "Error processing packet: {:?}", e)), + GENERIC_PACKET => sync.write().on_peer_packet(io, peer, &rlp).unwrap_or_else( + |e| warn!(target: "inf", "Error queueing packet: {:?}", e)), + p @ _ => trace!(target: "inf", "Unexpected packet {} from {}", p, peer), + }; + } + + pub fn propagate_packet(&mut self, io: &mut SyncIo, packet: Bytes) { + let lucky_peers: Vec<_> = self.peers.keys().cloned().collect(); + trace!(target: "inf", "Sending packets to {:?}", lucky_peers); + for peer_id in lucky_peers { + self.send_packet(io, peer_id, GENERIC_PACKET, packet.clone()); + } + } +} + +#[cfg(test)] +mod tests { +} + diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 69dd03a2a..29bd50bb2 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -82,6 +82,7 @@ extern crate parking_lot; mod chain; mod blocks; mod sync_io; +mod infinity; #[cfg(test)] mod tests; From 99a143eb37a0989f15a3d509140a18afc918f3c6 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 23 Aug 2016 17:19:23 +0200 Subject: [PATCH 010/295] change broadcast interface, add basic message handling --- ethcore/src/client/chain_notify.rs | 2 +- ethcore/src/client/client.rs | 12 ++++++++++-- ethcore/src/engines/mod.rs | 4 ++-- ethcore/src/engines/tendermint.rs | 14 ++++++++------ sync/src/api.rs | 2 +- 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/ethcore/src/client/chain_notify.rs b/ethcore/src/client/chain_notify.rs index e4638f152..64f525b9f 100644 --- a/ethcore/src/client/chain_notify.rs +++ b/ethcore/src/client/chain_notify.rs @@ -42,7 +42,7 @@ pub trait ChainNotify : Send + Sync { } /// fires when chain broadcasts a message - fn broadcast(&self, _data: Vec) { + fn broadcast(&self, _data: &[u8]) { } } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index bf871e4ad..1dcc23d02 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -25,6 +25,7 @@ use time::precise_time_ns; use util::{journaldb, rlp, Bytes, View, PerfTimer, Itertools, Mutex, RwLock}; use util::journaldb::JournalDB; use util::rlp::{UntrustedRlp}; +use util::ec::recover; use util::{U256, H256, Address, H2048, Uint}; use util::sha3::*; use util::kvdb::*; @@ -1021,8 +1022,15 @@ impl BlockChainClient for Client { self.miner.pending_transactions() } - fn queue_infinity_message(&self, _message: Bytes) { - //TODO: handle message here + fn queue_infinity_message(&self, message: Bytes) { + let full_rlp = UntrustedRlp::new(&message); + let signature = full_rlp.val_at(0).unwrap_or(return); + let message: Vec<_> = full_rlp.val_at(1).unwrap_or(return); + let message_rlp = UntrustedRlp::new(&message); + let pub_key = recover(&signature, &message.sha3()).unwrap_or(return); + if let Some(new_message) = self.engine.handle_message(pub_key.sha3().into(), message_rlp) { + self.notify(|notify| notify.broadcast(new_message.as_raw())); + } } } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index fc45edbc9..59464a85b 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -30,7 +30,7 @@ pub use self::tendermint::Tendermint; pub use self::signed_vote::{SignedVote, VoteError}; pub use self::propose_collect::{ProposeCollect}; -use common::{HashMap, SemanticVersion, Header, EnvInfo, Address, Builtin, BTreeMap, U256, Bytes, SignedTransaction, Error}; +use common::{HashMap, SemanticVersion, Header, EnvInfo, Address, Builtin, BTreeMap, U256, Bytes, SignedTransaction, Error, UntrustedRlp}; use account_provider::AccountProvider; use block::ExecutedBlock; use spec::CommonParams; @@ -121,7 +121,7 @@ pub trait Engine : Sync + Send { /// Handle any potential consensus messages; /// updating consensus state and potentially issuing a new one. - fn handle_message(&self, sender: Address, message: Bytes) -> Option> { None } + fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Option { None } // TODO: builtin contract routing - to do this properly, it will require removing the built-in configuration-reading logic // from Spec into here and removing the Spec::builtins field. diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index cdc0aaf3f..5726ab2ba 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -88,6 +88,10 @@ impl Tendermint { let ref p = self.our_params; p.validators.get(p.proposer_nonce%p.validator_n).unwrap().clone() } + + fn propose_message(&self, message: UntrustedRlp) -> Option { + None + } } impl Engine for Tendermint { @@ -140,13 +144,11 @@ impl Engine for Tendermint { }) } - fn handle_message(&self, sender: Address, message: Bytes) -> Option> { - match message[0] { - 0 => println!("0"), - _ => println!("unknown"), + fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Option { + match message.val_at(0).unwrap_or(return None) { + 0u8 if sender == self.proposer() => self.propose_message(message), + _ => None, } - //let sig: Signature = message.into(); - None } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { diff --git a/sync/src/api.rs b/sync/src/api.rs index cb1c47229..1f88c34bf 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -189,7 +189,7 @@ impl ChainNotify for EthSync { self.network.stop().unwrap_or_else(|e| warn!("Error stopping network: {:?}", e)); } - fn broadcast(&self, message: Vec) { + fn broadcast(&self, message: &[u8]) { self.network.with_context(ETH_PROTOCOL, |context| { let mut sync_io = NetSyncIo::new(context, &*self.eth_handler.chain); self.inf_handler.sync.write().propagate_packet(&mut sync_io, message.clone()); From 1cb3c164da6294ce6b7bb32baa6623b81d55b4c8 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 24 Aug 2016 11:58:49 +0200 Subject: [PATCH 011/295] propose step --- ethcore/src/client/client.rs | 11 ++++++----- ethcore/src/engines/mod.rs | 2 +- ethcore/src/engines/tendermint.rs | 29 ++++++++++++++++++++++++----- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 1dcc23d02..51376fc0a 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1024,12 +1024,13 @@ impl BlockChainClient for Client { fn queue_infinity_message(&self, message: Bytes) { let full_rlp = UntrustedRlp::new(&message); - let signature = full_rlp.val_at(0).unwrap_or(return); - let message: Vec<_> = full_rlp.val_at(1).unwrap_or(return); + let signature = full_rlp.val_at(0).unwrap_or_else(|| return); + let message: Vec<_> = full_rlp.val_at(1).unwrap_or_else(|| return); let message_rlp = UntrustedRlp::new(&message); - let pub_key = recover(&signature, &message.sha3()).unwrap_or(return); - if let Some(new_message) = self.engine.handle_message(pub_key.sha3().into(), message_rlp) { - self.notify(|notify| notify.broadcast(new_message.as_raw())); + let pub_key = recover(&signature, &message.sha3()).unwrap_or_else(|| return); + if let Some(new_message) = self.engine.handle_message(pub_key.sha3().into(), message_rlp) + { + self.notify(|notify| notify.broadcast(&new_message)); } } } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 59464a85b..ef7590540 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -121,7 +121,7 @@ pub trait Engine : Sync + Send { /// Handle any potential consensus messages; /// updating consensus state and potentially issuing a new one. - fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Option { None } + fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Option { None } // TODO: builtin contract routing - to do this properly, it will require removing the built-in configuration-reading logic // from Spec into here and removing the Spec::builtins field. diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index 5726ab2ba..0a6747390 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -38,7 +38,7 @@ pub struct TendermintParams { /// Consensus round. r: u64, /// Consensus step. - s: Step, + s: RwLock, /// Used to swith proposer. proposer_nonce: usize } @@ -61,7 +61,7 @@ impl From for TendermintParams { validators: val, validator_n: val_n, r: 0, - s: Step::Propose, + s: RwLock::new(Step::Propose), proposer_nonce: 0 } } @@ -89,9 +89,27 @@ impl Tendermint { p.validators.get(p.proposer_nonce%p.validator_n).unwrap().clone() } - fn propose_message(&self, message: UntrustedRlp) -> Option { + fn propose_message(&self, message: UntrustedRlp) -> Option { + match *self.our_params.s.try_read().unwrap() { + Step::Propose => (), + _ => return None, + } + let proposal = message.val_at(0).unwrap_or_else(|| return None); + let vote = ProposeCollect::new(proposal, + self.our_params.validators.iter().cloned().collect(), + self.threshold()); + let mut guard = self.our_params.s.try_write().unwrap(); + *guard = Step::Prevote(vote); + Some(message.as_raw().to_vec()) + } + + fn prevote_message(&self, sender: Address, message: UntrustedRlp) -> Option { None } + + fn threshold(&self) -> usize { + self.our_params.validator_n*2/3 + } } impl Engine for Tendermint { @@ -144,9 +162,10 @@ impl Engine for Tendermint { }) } - fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Option { - match message.val_at(0).unwrap_or(return None) { + fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Option { + match message.val_at(0).unwrap_or_else(|| return None) { 0u8 if sender == self.proposer() => self.propose_message(message), + 1 => self.prevote_message(sender, message), _ => None, } } From 77f06be7fbc431e7532c73c2029f1fd9cfaffa6b Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 24 Aug 2016 15:55:47 +0200 Subject: [PATCH 012/295] fix error propagation --- ethcore/res/bft.json | 39 ----------------------- ethcore/src/client/client.rs | 16 +++++----- ethcore/src/client/test_client.rs | 2 +- ethcore/src/client/traits.rs | 2 +- ethcore/src/engines/mod.rs | 19 ++++++++++-- ethcore/src/engines/propose_collect.rs | 4 +-- ethcore/src/engines/signed_vote.rs | 10 ++---- ethcore/src/engines/tendermint.rs | 43 ++++++++++++++++---------- ethcore/src/error.rs | 12 +++---- 9 files changed, 63 insertions(+), 84 deletions(-) delete mode 100644 ethcore/res/bft.json diff --git a/ethcore/res/bft.json b/ethcore/res/bft.json deleted file mode 100644 index 24bd386b2..000000000 --- a/ethcore/res/bft.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "TestBFT", - "engine": { - "BFT": { - "params": { - "gasLimitBoundDivisor": "0x0400", - "durationLimit": "0x0d", - "validators" : ["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/src/client/client.rs b/ethcore/src/client/client.rs index 51376fc0a..87367bf98 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1024,13 +1024,15 @@ impl BlockChainClient for Client { fn queue_infinity_message(&self, message: Bytes) { let full_rlp = UntrustedRlp::new(&message); - let signature = full_rlp.val_at(0).unwrap_or_else(|| return); - let message: Vec<_> = full_rlp.val_at(1).unwrap_or_else(|| return); - let message_rlp = UntrustedRlp::new(&message); - let pub_key = recover(&signature, &message.sha3()).unwrap_or_else(|| return); - if let Some(new_message) = self.engine.handle_message(pub_key.sha3().into(), message_rlp) - { - self.notify(|notify| notify.broadcast(&new_message)); + if let Ok(signature) = full_rlp.val_at(0) { + if let Ok(message) = full_rlp.val_at::>(1) { + let message_rlp = UntrustedRlp::new(&message); + if let Ok(pub_key) = recover(&signature, &message.sha3()) { + if let Ok(new_message) = self.engine.handle_message(pub_key.sha3().into(), message_rlp) { + self.notify(|notify| notify.broadcast(&new_message)); + } + } + } } } } diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 8852448cc..a7d710da3 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -38,7 +38,7 @@ use spec::Spec; use block_queue::BlockQueueInfo; use block::{OpenBlock, SealedBlock}; use executive::Executed; -use error::CallError; +use error::{Error, CallError}; use trace::LocalizedTrace; /// Test client. diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index da876efa6..368bae7d6 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -183,7 +183,7 @@ pub trait BlockChainClient : Sync + Send { fn queue_transactions(&self, transactions: Vec); /// Queue packet - fn queue_infinity_message(&self, packet: Bytes); + fn queue_infinity_message(&self, message: Bytes); /// list all transactions fn pending_transactions(&self) -> Vec; diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index ef7590540..07d17399a 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -27,8 +27,8 @@ pub use self::null_engine::NullEngine; pub use self::instant_seal::InstantSeal; pub use self::basic_authority::BasicAuthority; pub use self::tendermint::Tendermint; -pub use self::signed_vote::{SignedVote, VoteError}; -pub use self::propose_collect::{ProposeCollect}; +pub use self::signed_vote::SignedVote; +pub use self::propose_collect::ProposeCollect; use common::{HashMap, SemanticVersion, Header, EnvInfo, Address, Builtin, BTreeMap, U256, Bytes, SignedTransaction, Error, UntrustedRlp}; use account_provider::AccountProvider; @@ -36,6 +36,19 @@ use block::ExecutedBlock; use spec::CommonParams; use evm::Schedule; +/// Voting errors. +#[derive(Debug)] +pub enum EngineError { + /// Voter is not in the voters set. + UnauthorisedVoter, + /// Message pertaining incorrect consensus step. + WrongStep, + /// Message pertaining unknown consensus step. + UnknownStep, + /// Message was not expected. + UnexpectedMessage +} + /// 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. pub trait Engine : Sync + Send { @@ -121,7 +134,7 @@ pub trait Engine : Sync + Send { /// Handle any potential consensus messages; /// updating consensus state and potentially issuing a new one. - fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Option { None } + fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Result { Err(EngineError::UnexpectedMessage.into()) } // TODO: builtin contract routing - to do this properly, it will require removing the built-in configuration-reading logic // from Spec into here and removing the Spec::builtins field. diff --git a/ethcore/src/engines/propose_collect.rs b/ethcore/src/engines/propose_collect.rs index f09618c50..f94132e7d 100644 --- a/ethcore/src/engines/propose_collect.rs +++ b/ethcore/src/engines/propose_collect.rs @@ -18,7 +18,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use common::{HashSet, RwLock, H256, Signature, Address, Error, ec, Hashable}; -use engines::VoteError; +use super::EngineError; /// Collect votes on a hash. #[derive(Debug)] @@ -59,7 +59,7 @@ impl ProposeCollect { fn can_vote(&self, signature: &Signature) -> Result<(), Error> { let signer = Address::from(try!(ec::recover(&signature, &self.hash)).sha3()); match self.voters.contains(&signer) { - false => try!(Err(VoteError::UnauthorisedVoter)), + false => try!(Err(EngineError::UnauthorisedVoter)), true => Ok(()), } } diff --git a/ethcore/src/engines/signed_vote.rs b/ethcore/src/engines/signed_vote.rs index d3381112a..b7c5082a3 100644 --- a/ethcore/src/engines/signed_vote.rs +++ b/ethcore/src/engines/signed_vote.rs @@ -16,6 +16,7 @@ //! Voting on hashes, where each vote has to come from a set of public keys. +use super::EngineError; use common::{HashSet, HashMap, RwLock, H256, Signature, Address, Error, ec, Hashable}; /// Signed voting on hashes. @@ -33,13 +34,6 @@ pub struct SignedVote { winner: RwLock> } -/// Voting errors. -#[derive(Debug)] -pub enum VoteError { - /// Voter is not in the voters set. - UnauthorisedVoter -} - impl SignedVote { /// Create a new instance of BFT engine pub fn new(voters: HashSet
, threshold: usize) -> Self { @@ -71,7 +65,7 @@ impl SignedVote { fn can_vote(&self, bare_hash: &H256, signature: &Signature) -> Result<(), Error> { let signer = Address::from(try!(ec::recover(&signature, bare_hash)).sha3()); match self.voters.contains(&signer) { - false => try!(Err(VoteError::UnauthorisedVoter)), + false => try!(Err(EngineError::UnauthorisedVoter)), true => Ok(()), } } diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index 0a6747390..bc865ce2d 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -20,7 +20,7 @@ use common::*; use account_provider::AccountProvider; use block::*; use spec::CommonParams; -use engines::{Engine, ProposeCollect}; +use engines::{Engine, EngineError, ProposeCollect}; use evm::Schedule; use ethjson; @@ -89,22 +89,22 @@ impl Tendermint { p.validators.get(p.proposer_nonce%p.validator_n).unwrap().clone() } - fn propose_message(&self, message: UntrustedRlp) -> Option { + fn propose_message(&self, message: UntrustedRlp) -> Result { match *self.our_params.s.try_read().unwrap() { Step::Propose => (), - _ => return None, + _ => try!(Err(EngineError::WrongStep)), } - let proposal = message.val_at(0).unwrap_or_else(|| return None); + let proposal = try!(message.val_at(0)); let vote = ProposeCollect::new(proposal, self.our_params.validators.iter().cloned().collect(), self.threshold()); - let mut guard = self.our_params.s.try_write().unwrap(); + let mut guard = self.our_params.s.write(); *guard = Step::Prevote(vote); - Some(message.as_raw().to_vec()) + Ok(message.as_raw().to_vec()) } - fn prevote_message(&self, sender: Address, message: UntrustedRlp) -> Option { - None + fn prevote_message(&self, sender: Address, message: UntrustedRlp) -> Result { + try!(Err(EngineError::WrongStep)) } fn threshold(&self) -> usize { @@ -162,11 +162,11 @@ impl Engine for Tendermint { }) } - fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Option { - match message.val_at(0).unwrap_or_else(|| return None) { + fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Result { + match try!(message.val_at(0)) { 0u8 if sender == self.proposer() => self.propose_message(message), 1 => self.prevote_message(sender, message), - _ => None, + _ => try!(Err(EngineError::UnknownStep)), } } @@ -224,18 +224,18 @@ mod tests { use spec::Spec; /// Create a new test chain spec with `Tendermint` consensus engine. - fn new_test_authority() -> Spec { Spec::load(include_bytes!("../../res/bft.json")) } + fn new_test_tendermint() -> Spec { Spec::load(include_bytes!("../../res/tendermint.json")) } #[test] fn has_valid_metadata() { - let engine = new_test_authority().engine; + let engine = new_test_tendermint().engine; assert!(!engine.name().is_empty()); assert!(engine.version().major >= 1); } #[test] fn can_return_schedule() { - let engine = new_test_authority().engine; + let engine = new_test_tendermint().engine; let schedule = engine.schedule(&EnvInfo { number: 10000000, author: 0.into(), @@ -251,7 +251,7 @@ mod tests { #[test] fn can_do_seal_verification_fail() { - let engine = new_test_authority().engine; + let engine = new_test_tendermint().engine; let header: Header = Header::default(); let verify_result = engine.verify_block_basic(&header, None); @@ -265,7 +265,7 @@ mod tests { #[test] fn can_do_signature_verification_fail() { - let engine = new_test_authority().engine; + let engine = new_test_tendermint().engine; let mut header: Header = Header::default(); header.set_seal(vec![rlp::encode(&Signature::zero()).to_vec()]); @@ -284,7 +284,7 @@ mod tests { let addr = tap.insert_account("".sha3(), "").unwrap(); tap.unlock_account_permanently(addr, "".into()).unwrap(); - let spec = new_test_authority(); + let spec = new_test_tendermint(); let engine = &*spec.engine; let genesis_header = spec.genesis_header(); let mut db_result = get_temp_journal_db(); @@ -298,6 +298,15 @@ mod tests { assert!(b.try_seal(engine, seal).is_ok()); } + #[test] + fn propose_step(){ + let engine = new_test_tendermint().engine; + let tap = AccountProvider::transient_provider(); + let addr = tap.insert_account("1".sha3(), "1").unwrap(); + println!("{:?}", addr); + false; + } + #[test] fn handle_message() { false; diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index ccc926ce6..45ab99bcc 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -24,7 +24,7 @@ use client::Error as ClientError; use ipc::binary::{BinaryConvertError, BinaryConvertable}; use types::block_import_error::BlockImportError; use snapshot::Error as SnapshotError; -use engines::VoteError; +use engines::EngineError; pub use types::executed::{ExecutionError, CallError}; @@ -240,7 +240,7 @@ pub enum Error { /// Snapshot error. Snapshot(SnapshotError), /// Consensus vote error. - Vote(VoteError), + Engine(EngineError), } impl fmt::Display for Error { @@ -261,7 +261,7 @@ impl fmt::Display for Error { Error::StdIo(ref err) => err.fmt(f), Error::Snappy(ref err) => err.fmt(f), Error::Snapshot(ref err) => err.fmt(f), - Error::Vote(ref err) => + Error::Engine(ref err) => f.write_fmt(format_args!("Bad vote: {:?}", err)), } } @@ -366,10 +366,10 @@ impl From for Error { } } -impl From for Error { - fn from(err: VoteError) -> Error { +impl From for Error { + fn from(err: EngineError) -> Error { match err { - other => Error::Vote(other), + other => Error::Engine(other), } } } From fcae03e55fe0f302b58e949f3839159e6b556f16 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 25 Aug 2016 19:22:10 +0200 Subject: [PATCH 013/295] propose message test --- ethcore/res/tendermint.json | 42 +++++++++++++++++++++++++++++++ ethcore/src/client/client.rs | 1 + ethcore/src/client/test_client.rs | 2 +- ethcore/src/engines/mod.rs | 2 +- ethcore/src/engines/tendermint.rs | 38 ++++++++++++++++++++-------- 5 files changed, 73 insertions(+), 12 deletions(-) create mode 100644 ethcore/res/tendermint.json diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json new file mode 100644 index 000000000..126761233 --- /dev/null +++ b/ethcore/res/tendermint.json @@ -0,0 +1,42 @@ +{ + "name": "TestBFT", + "engine": { + "Tendermint": { + "params": { + "gasLimitBoundDivisor": "0x0400", + "durationLimit": "0x0d", + "validators" : [ + "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", + "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" + ] + } + } + }, + "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/src/client/client.rs b/ethcore/src/client/client.rs index 87367bf98..721a31548 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1022,6 +1022,7 @@ impl BlockChainClient for Client { self.miner.pending_transactions() } + // TODO: Make it an actual queue, return errors. fn queue_infinity_message(&self, message: Bytes) { let full_rlp = UntrustedRlp::new(&message); if let Ok(signature) = full_rlp.val_at(0) { diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index a7d710da3..8852448cc 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -38,7 +38,7 @@ use spec::Spec; use block_queue::BlockQueueInfo; use block::{OpenBlock, SealedBlock}; use executive::Executed; -use error::{Error, CallError}; +use error::CallError; use trace::LocalizedTrace; /// Test client. diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 07d17399a..d27bd97a1 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -134,7 +134,7 @@ pub trait Engine : Sync + Send { /// Handle any potential consensus messages; /// updating consensus state and potentially issuing a new one. - fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Result { Err(EngineError::UnexpectedMessage.into()) } + fn handle_message(&self, _sender: Address, _message: UntrustedRlp) -> Result { Err(EngineError::UnexpectedMessage.into()) } // TODO: builtin contract routing - to do this properly, it will require removing the built-in configuration-reading logic // from Spec into here and removing the Spec::builtins field. diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index bc865ce2d..e5c46c65a 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -16,6 +16,7 @@ //! Tendermint BFT consensus engine with round robin proof-of-authority. +use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use common::*; use account_provider::AccountProvider; use block::*; @@ -40,7 +41,7 @@ pub struct TendermintParams { /// Consensus step. s: RwLock, /// Used to swith proposer. - proposer_nonce: usize + proposer_nonce: AtomicUsize } #[derive(Debug)] @@ -62,7 +63,7 @@ impl From for TendermintParams { validator_n: val_n, r: 0, s: RwLock::new(Step::Propose), - proposer_nonce: 0 + proposer_nonce: AtomicUsize::new(0) } } } @@ -86,7 +87,7 @@ impl Tendermint { fn proposer(&self) -> Address { let ref p = self.our_params; - p.validators.get(p.proposer_nonce%p.validator_n).unwrap().clone() + p.validators.get(p.proposer_nonce.load(AtomicOrdering::Relaxed)%p.validator_n).unwrap().clone() } fn propose_message(&self, message: UntrustedRlp) -> Result { @@ -94,7 +95,8 @@ impl Tendermint { Step::Propose => (), _ => try!(Err(EngineError::WrongStep)), } - let proposal = try!(message.val_at(0)); + let proposal = try!(message.as_val()); + self.our_params.proposer_nonce.fetch_add(1, AtomicOrdering::Relaxed); let vote = ProposeCollect::new(proposal, self.our_params.validators.iter().cloned().collect(), self.threshold()); @@ -164,8 +166,8 @@ impl Engine for Tendermint { fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Result { match try!(message.val_at(0)) { - 0u8 if sender == self.proposer() => self.propose_message(message), - 1 => self.prevote_message(sender, message), + 0u8 if sender == self.proposer() => self.propose_message(try!(message.at(1))), + 1 => self.prevote_message(sender, try!(message.at(1))), _ => try!(Err(EngineError::UnknownStep)), } } @@ -222,8 +224,10 @@ mod tests { use tests::helpers::*; use account_provider::AccountProvider; use spec::Spec; + use super::Step; /// Create a new test chain spec with `Tendermint` consensus engine. + /// Account "0".sha3() and "1".sha3() are a validators. fn new_test_tendermint() -> Spec { Spec::load(include_bytes!("../../res/tendermint.json")) } #[test] @@ -299,12 +303,26 @@ mod tests { } #[test] - fn propose_step(){ + fn propose_step() { let engine = new_test_tendermint().engine; let tap = AccountProvider::transient_provider(); - let addr = tap.insert_account("1".sha3(), "1").unwrap(); - println!("{:?}", addr); - false; + let mut s = RlpStream::new_list(2); + let header = Header::default(); + s.append(&0u8).append(&header.bare_hash()); + let drain = s.out(); + let propose_rlp = UntrustedRlp::new(&drain); + + let not_validator_addr = tap.insert_account("101".sha3(), "101").unwrap(); + assert!(engine.handle_message(not_validator_addr, propose_rlp.clone()).is_err()); + + let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap(); + assert!(engine.handle_message(not_proposer_addr, propose_rlp.clone()).is_err()); + + let proposer_addr = tap.insert_account("1".sha3(), "1").unwrap(); + assert_eq!(vec![160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101], + engine.handle_message(proposer_addr, propose_rlp.clone()).unwrap()); + + assert!(engine.handle_message(not_proposer_addr, propose_rlp).is_err()); } #[test] From 2cc2bd6518628ba74ccd3a3673f9d75dab1cad25 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 26 Aug 2016 10:40:00 +0200 Subject: [PATCH 014/295] impl Hash for Signature --- ethkey/src/signature.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ethkey/src/signature.rs b/ethkey/src/signature.rs index eec0fbf47..b723ee3b0 100644 --- a/ethkey/src/signature.rs +++ b/ethkey/src/signature.rs @@ -18,6 +18,7 @@ use std::ops::{Deref, DerefMut}; use std::cmp::PartialEq; use std::{mem, fmt}; use std::str::FromStr; +use std::hash::{Hash, Hasher}; use secp256k1::{Message as SecpMessage, RecoverableSignature, RecoveryId, Error as SecpError}; use secp256k1::key::{SecretKey, PublicKey}; use rustc_serialize::hex::{ToHex, FromHex}; @@ -114,6 +115,12 @@ impl Default for Signature { } } +impl Hash for Signature { + fn hash(&self, state: &mut H) { + H520::from(self.0).hash(state); + } +} + impl From<[u8; 65]> for Signature { fn from(s: [u8; 65]) -> Self { Signature(s) From e7a9bf4df888031293753712ff5df1ca70919697 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 26 Aug 2016 11:27:54 +0200 Subject: [PATCH 015/295] impl Clone for Signature --- ethkey/src/signature.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ethkey/src/signature.rs b/ethkey/src/signature.rs index b723ee3b0..8733f1245 100644 --- a/ethkey/src/signature.rs +++ b/ethkey/src/signature.rs @@ -121,6 +121,12 @@ impl Hash for Signature { } } +impl Clone for Signature { + fn clone(&self) -> Self { + Signature(self.0) + } +} + impl From<[u8; 65]> for Signature { fn from(s: [u8; 65]) -> Self { Signature(s) From a4ba7262ada81de63d2a0cb29bc6ed200c1de758 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 26 Aug 2016 13:16:56 +0200 Subject: [PATCH 016/295] update Signature and ipc usage --- ethcore/src/client/chain_notify.rs | 2 +- ethcore/src/client/client.rs | 15 +++++++-------- ethcore/src/engines/propose_collect.rs | 13 +++++++------ ethcore/src/engines/signed_vote.rs | 11 ++++++----- ethcore/src/engines/tendermint.rs | 23 ++++++----------------- ethcore/src/error.rs | 7 ++++--- sync/src/api.rs | 2 +- 7 files changed, 32 insertions(+), 41 deletions(-) diff --git a/ethcore/src/client/chain_notify.rs b/ethcore/src/client/chain_notify.rs index 64f525b9f..e4638f152 100644 --- a/ethcore/src/client/chain_notify.rs +++ b/ethcore/src/client/chain_notify.rs @@ -42,7 +42,7 @@ pub trait ChainNotify : Send + Sync { } /// fires when chain broadcasts a message - fn broadcast(&self, _data: &[u8]) { + fn broadcast(&self, _data: Vec) { } } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index c84ebccfc..991e880a8 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -25,12 +25,12 @@ use time::precise_time_ns; use util::{journaldb, rlp, Bytes, View, PerfTimer, Itertools, Mutex, RwLock}; use util::journaldb::JournalDB; use util::rlp::{UntrustedRlp}; -use util::ec::recover; -use util::{U256, H256, Address, H2048, Uint}; +use util::{U256, H256, H520, Address, H2048, Uint}; use util::sha3::*; use util::kvdb::*; // other +use ethkey::recover; use io::*; use views::{BlockView, HeaderView, BodyView}; use error::{ImportError, ExecutionError, CallError, BlockError, ImportResult}; @@ -1031,12 +1031,11 @@ impl BlockChainClient for Client { // TODO: Make it an actual queue, return errors. fn queue_infinity_message(&self, message: Bytes) { let full_rlp = UntrustedRlp::new(&message); - if let Ok(signature) = full_rlp.val_at(0) { - if let Ok(message) = full_rlp.val_at::>(1) { - let message_rlp = UntrustedRlp::new(&message); - if let Ok(pub_key) = recover(&signature, &message.sha3()) { - if let Ok(new_message) = self.engine.handle_message(pub_key.sha3().into(), message_rlp) { - self.notify(|notify| notify.broadcast(&new_message)); + if let Ok(signature) = full_rlp.val_at::(0) { + if let Ok(message) = full_rlp.at(1) { + if let Ok(pub_key) = recover(&signature.into(), &message.as_raw().sha3()) { + if let Ok(new_message) = self.engine.handle_message(pub_key.sha3().into(), message) { + self.notify(|notify| notify.broadcast(new_message.clone())); } } } diff --git a/ethcore/src/engines/propose_collect.rs b/ethcore/src/engines/propose_collect.rs index f94132e7d..c693c71ec 100644 --- a/ethcore/src/engines/propose_collect.rs +++ b/ethcore/src/engines/propose_collect.rs @@ -17,8 +17,9 @@ //! Voting on a hash, where each vote has to come from a set of addresses. use std::sync::atomic::{AtomicBool, Ordering}; -use common::{HashSet, RwLock, H256, Signature, Address, Error, ec, Hashable}; +use common::{HashSet, RwLock, H256, Address, Error, Hashable}; use super::EngineError; +use ethkey::{recover, Signature}; /// Collect votes on a hash. #[derive(Debug)] @@ -57,7 +58,7 @@ impl ProposeCollect { } fn can_vote(&self, signature: &Signature) -> Result<(), Error> { - let signer = Address::from(try!(ec::recover(&signature, &self.hash)).sha3()); + let signer = Address::from(try!(recover(&signature, &self.hash)).sha3()); match self.voters.contains(&signer) { false => try!(Err(EngineError::UnauthorisedVoter)), true => Ok(()), @@ -108,18 +109,18 @@ mod tests { // Unapproved voter. let signature = tap.sign(addr3, bare_hash).unwrap(); - assert!(!vote.vote(&signature.into())); + assert!(!vote.vote(&signature)); assert!(vote.winner().is_none()); // First good vote. let signature = tap.sign(addr1, bare_hash).unwrap(); - assert!(vote.vote(&signature.into())); + assert!(vote.vote(&signature)); assert_eq!(vote.winner().unwrap(), bare_hash); // Voting again is ineffective. let signature = tap.sign(addr1, bare_hash).unwrap(); - assert!(!vote.vote(&signature.into())); + assert!(!vote.vote(&signature)); // Second valid vote. let signature = tap.sign(addr2, bare_hash).unwrap(); - assert!(vote.vote(&signature.into())); + assert!(vote.vote(&signature)); assert_eq!(vote.winner().unwrap(), bare_hash); } } diff --git a/ethcore/src/engines/signed_vote.rs b/ethcore/src/engines/signed_vote.rs index b7c5082a3..e3627e986 100644 --- a/ethcore/src/engines/signed_vote.rs +++ b/ethcore/src/engines/signed_vote.rs @@ -17,7 +17,8 @@ //! Voting on hashes, where each vote has to come from a set of public keys. use super::EngineError; -use common::{HashSet, HashMap, RwLock, H256, Signature, Address, Error, ec, Hashable}; +use common::{HashSet, HashMap, RwLock, H256, Address, Error, Hashable}; +use ethkey::{Signature, recover}; /// Signed voting on hashes. #[derive(Debug)] @@ -49,11 +50,11 @@ impl SignedVote { } /// Vote on hash using the signed hash, true if vote counted. - pub fn vote(&self, bare_hash: H256, signature: &Signature) -> bool { - if !self.can_vote(&bare_hash, signature).is_ok() { return false; } + pub fn vote(&self, bare_hash: H256, signature: Signature) -> bool { + if !self.can_vote(&bare_hash, &signature).is_ok() { return false; } let mut guard = self.votes.try_write().unwrap(); let set = guard.entry(bare_hash.clone()).or_insert_with(|| HashSet::new()); - if !set.insert(signature.clone()) { return false; } + if !set.insert(signature) { return false; } // Set the winner if threshold is reached. if set.len() >= self.threshold { let mut guard = self.winner.try_write().unwrap(); @@ -63,7 +64,7 @@ impl SignedVote { } fn can_vote(&self, bare_hash: &H256, signature: &Signature) -> Result<(), Error> { - let signer = Address::from(try!(ec::recover(&signature, bare_hash)).sha3()); + let signer = Address::from(try!(recover(&signature, bare_hash)).sha3()); match self.voters.contains(&signer) { false => try!(Err(EngineError::UnauthorisedVoter)), true => Ok(()), diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index e5c46c65a..2899c77df 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -106,7 +106,10 @@ impl Tendermint { } fn prevote_message(&self, sender: Address, message: UntrustedRlp) -> Result { - try!(Err(EngineError::WrongStep)) + match *self.our_params.s.try_write().unwrap() { + Step::Prevote(ref mut vote) => try!(Err(EngineError::WrongStep)), + _ => try!(Err(EngineError::WrongStep)), + } } fn threshold(&self) -> usize { @@ -167,7 +170,7 @@ impl Engine for Tendermint { fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Result { match try!(message.val_at(0)) { 0u8 if sender == self.proposer() => self.propose_message(try!(message.at(1))), - 1 => self.prevote_message(sender, try!(message.at(1))), + 1 if self.our_params.validators.contains(&sender) => self.prevote_message(sender, try!(message.at(1))), _ => try!(Err(EngineError::UnknownStep)), } } @@ -225,6 +228,7 @@ mod tests { use account_provider::AccountProvider; use spec::Spec; use super::Step; + use ethkey::Signature; /// Create a new test chain spec with `Tendermint` consensus engine. /// Account "0".sha3() and "1".sha3() are a validators. @@ -267,21 +271,6 @@ mod tests { } } - #[test] - fn can_do_signature_verification_fail() { - let engine = new_test_tendermint().engine; - let mut header: Header = Header::default(); - header.set_seal(vec![rlp::encode(&Signature::zero()).to_vec()]); - - let verify_result = engine.verify_block_unordered(&header, None); - - match verify_result { - Err(Error::Util(UtilError::Crypto(CryptoError::InvalidSignature))) => {}, - Err(_) => { panic!("should be block difficulty error (got {:?})", verify_result); }, - _ => { panic!("Should be error, got Ok"); }, - } - } - #[test] fn can_generate_seal() { let tap = AccountProvider::transient_provider(); diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index 1f854837e..5d6b56c16 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -366,9 +366,10 @@ impl From for Error { impl From for Error { fn from(err: EngineError) -> Error { - match err { - other => Error::Engine(other), - } + Error::Engine(err) + } +} + impl From for Error { fn from(err: EthkeyError) -> Error { Error::Ethkey(err) diff --git a/sync/src/api.rs b/sync/src/api.rs index 59d2723ee..92b276d21 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -189,7 +189,7 @@ impl ChainNotify for EthSync { self.network.stop().unwrap_or_else(|e| warn!("Error stopping network: {:?}", e)); } - fn broadcast(&self, message: &[u8]) { + fn broadcast(&self, message: Vec) { self.network.with_context(ETH_PROTOCOL, |context| { let mut sync_io = NetSyncIo::new(context, &*self.eth_handler.chain); self.inf_handler.sync.write().propagate_packet(&mut sync_io, message.clone()); From f60d4645edeb49bc4d0b52411c0fd9307cd29086 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 26 Aug 2016 19:27:02 +0200 Subject: [PATCH 017/295] move vote with addresses, remove recover check --- ethcore/src/engines/propose_collect.rs | 60 ++++++++++---------------- 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/ethcore/src/engines/propose_collect.rs b/ethcore/src/engines/propose_collect.rs index c693c71ec..ee4aa3810 100644 --- a/ethcore/src/engines/propose_collect.rs +++ b/ethcore/src/engines/propose_collect.rs @@ -17,9 +17,7 @@ //! Voting on a hash, where each vote has to come from a set of addresses. use std::sync::atomic::{AtomicBool, Ordering}; -use common::{HashSet, RwLock, H256, Address, Error, Hashable}; -use super::EngineError; -use ethkey::{recover, Signature}; +use common::{HashSet, RwLock, H256, Address}; /// Collect votes on a hash. #[derive(Debug)] @@ -31,7 +29,7 @@ pub struct ProposeCollect { /// Threshold vote number for success. pub threshold: usize, /// Votes. - votes: RwLock>, + votes: RwLock>, /// Was enough votes reached. is_won: AtomicBool } @@ -50,35 +48,24 @@ impl ProposeCollect { } /// Vote on hash using the signed hash, true if vote counted. - pub fn vote(&self, signature: &Signature) -> bool { - if self.votes.try_read().unwrap().contains(signature) { return false; } - if !self.can_vote(signature).is_ok() { return false; } - self.votes.try_write().unwrap().insert(signature.clone()); + pub fn vote(&self, voter: Address) -> bool { + if self.votes.try_read().unwrap().contains(&voter) { return false; } + if !self.voters.contains(&voter) { return false; } + self.votes.try_write().unwrap().insert(voter); true } - fn can_vote(&self, signature: &Signature) -> Result<(), Error> { - let signer = Address::from(try!(recover(&signature, &self.hash)).sha3()); - match self.voters.contains(&signer) { - false => try!(Err(EngineError::UnauthorisedVoter)), - true => Ok(()), - } - } - /// Some winner if voting threshold was reached. - pub fn winner(&self) -> Option { + pub fn is_won(&self) -> bool { let threshold_checker = || match self.votes.try_read().unwrap().len() >= self.threshold { true => { self.is_won.store(true, Ordering::Relaxed); true }, false => false, }; - match self.is_won.load(Ordering::Relaxed) || threshold_checker() { - true => Some(self.hash), - false => None, - } + self.is_won.load(Ordering::Relaxed) || threshold_checker() } - /// Get signatures backing given hash. - pub fn votes(&self) -> HashSet { + /// Get addresses backing given hash. + pub fn votes(&self) -> HashSet
{ self.votes.try_read().unwrap().clone() } } @@ -103,24 +90,21 @@ mod tests { let header = Header::default(); let bare_hash = header.bare_hash(); - let voters: HashSet<_> = vec![addr1, addr2].into_iter().map(Into::into).collect(); - let vote = ProposeCollect::new(bare_hash, voters.into(), 1); - assert!(vote.winner().is_none()); + let voters: HashSet<_> = vec![addr1.clone(), addr2.clone(), Address::default()].into_iter().map(Into::into).collect(); + let vote = ProposeCollect::new(bare_hash, voters.into(), 2); + assert!(!vote.is_won()); // Unapproved voter. - let signature = tap.sign(addr3, bare_hash).unwrap(); - assert!(!vote.vote(&signature)); - assert!(vote.winner().is_none()); + assert!(!vote.vote(addr3)); + assert!(!vote.is_won()); // First good vote. - let signature = tap.sign(addr1, bare_hash).unwrap(); - assert!(vote.vote(&signature)); - assert_eq!(vote.winner().unwrap(), bare_hash); + assert!(vote.vote(addr1.clone())); + assert!(!vote.is_won()); // Voting again is ineffective. - let signature = tap.sign(addr1, bare_hash).unwrap(); - assert!(!vote.vote(&signature)); - // Second valid vote. - let signature = tap.sign(addr2, bare_hash).unwrap(); - assert!(vote.vote(&signature)); - assert_eq!(vote.winner().unwrap(), bare_hash); + assert!(!vote.vote(addr1)); + assert!(!vote.is_won()); + // Second valid vote thus win. + assert!(vote.vote(addr2)); + assert!(vote.is_won()); } } From a12a764d6c370cecaf7831e68243295ab7c8c127 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 26 Aug 2016 19:27:50 +0200 Subject: [PATCH 018/295] add rounds check, simplify tests --- ethcore/src/engines/mod.rs | 6 +- ethcore/src/engines/signed_vote.rs | 8 +- ethcore/src/engines/tendermint.rs | 116 +++++++++++++++++++++++------ 3 files changed, 101 insertions(+), 29 deletions(-) diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index d27bd97a1..1db8acb0f 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -46,7 +46,11 @@ pub enum EngineError { /// Message pertaining unknown consensus step. UnknownStep, /// Message was not expected. - UnexpectedMessage + UnexpectedMessage, + /// Received a vote for a different proposal. + WrongVote, + /// Received message is from a different consensus round. + WrongRound } /// A consensus mechanism for the chain. Generally either proof-of-work or proof-of-stake-based. diff --git a/ethcore/src/engines/signed_vote.rs b/ethcore/src/engines/signed_vote.rs index e3627e986..323972ed4 100644 --- a/ethcore/src/engines/signed_vote.rs +++ b/ethcore/src/engines/signed_vote.rs @@ -106,18 +106,18 @@ mod tests { // Unapproved voter. let signature = tap.sign(addr3, bare_hash).unwrap(); - assert!(!vote.vote(bare_hash, &signature.into())); + assert!(!vote.vote(bare_hash, signature)); assert!(vote.winner().is_none()); // First good vote. let signature = tap.sign(addr1, bare_hash).unwrap(); - assert!(vote.vote(bare_hash, &signature.into())); + assert!(vote.vote(bare_hash, signature)); assert_eq!(vote.winner().unwrap(), bare_hash); // Voting again is ineffective. let signature = tap.sign(addr1, bare_hash).unwrap(); - assert!(!vote.vote(bare_hash, &signature.into())); + assert!(!vote.vote(bare_hash, signature)); // Second valid vote. let signature = tap.sign(addr2, bare_hash).unwrap(); - assert!(vote.vote(bare_hash, &signature.into())); + assert!(vote.vote(bare_hash, signature)); assert_eq!(vote.winner().unwrap(), bare_hash); } } diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index 2899c77df..76f9938b2 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -41,7 +41,9 @@ pub struct TendermintParams { /// Consensus step. s: RwLock, /// Used to swith proposer. - proposer_nonce: AtomicUsize + proposer_nonce: AtomicUsize, + /// Seal collection. + seal: Vec } #[derive(Debug)] @@ -63,7 +65,8 @@ impl From for TendermintParams { validator_n: val_n, r: 0, s: RwLock::new(Step::Propose), - proposer_nonce: AtomicUsize::new(0) + proposer_nonce: AtomicUsize::new(0), + seal: Vec::new() } } } @@ -90,24 +93,76 @@ impl Tendermint { p.validators.get(p.proposer_nonce.load(AtomicOrdering::Relaxed)%p.validator_n).unwrap().clone() } + fn is_proposer(&self, address: &Address) -> bool { + self.proposer() == *address + } + + fn is_validator(&self, address: &Address) -> bool { + self.our_params.validators.contains(address) + } + + fn new_vote(&self, proposal: H256) -> ProposeCollect { + ProposeCollect::new(proposal, + self.our_params.validators.iter().cloned().collect(), + self.threshold()) + } + fn propose_message(&self, message: UntrustedRlp) -> Result { + // Check if message is for correct step. match *self.our_params.s.try_read().unwrap() { Step::Propose => (), _ => try!(Err(EngineError::WrongStep)), } let proposal = try!(message.as_val()); self.our_params.proposer_nonce.fetch_add(1, AtomicOrdering::Relaxed); - let vote = ProposeCollect::new(proposal, - self.our_params.validators.iter().cloned().collect(), - self.threshold()); let mut guard = self.our_params.s.write(); - *guard = Step::Prevote(vote); + // Proceed to the prevote step. + *guard = Step::Prevote(self.new_vote(proposal)); Ok(message.as_raw().to_vec()) } fn prevote_message(&self, sender: Address, message: UntrustedRlp) -> Result { + // Check if message is for correct step. match *self.our_params.s.try_write().unwrap() { - Step::Prevote(ref mut vote) => try!(Err(EngineError::WrongStep)), + Step::Prevote(ref mut vote) => { + // Vote if message is about the right block. + if vote.hash == try!(message.as_val()) { + vote.vote(sender); + // Move to next step is prevote is won. + if vote.is_won() { + let mut guard = self.our_params.s.write(); + *guard = Step::Precommit(self.new_vote(vote.hash)); + Ok(message.as_raw().to_vec()) + } else { + Ok(message.as_raw().to_vec()) + } + } else { + try!(Err(EngineError::WrongVote)) + } + }, + _ => try!(Err(EngineError::WrongStep)), + } + } + + fn precommit_message(&self, sender: Address, message: UntrustedRlp) -> Result { + // Check if message is for correct step. + match *self.our_params.s.try_write().unwrap() { + Step::Prevote(ref mut vote) => { + // Vote and accumulate seal if message is about the right block. + if vote.hash == try!(message.as_val()) { + vote.vote(sender); + // Commit if precommit is won. + if vote.is_won() { + let mut guard = self.our_params.s.write(); + *guard = Step::Commit; + Ok(message.as_raw().to_vec()) + } else { + Ok(message.as_raw().to_vec()) + } + } else { + try!(Err(EngineError::WrongVote)) + } + }, _ => try!(Err(EngineError::WrongStep)), } } @@ -168,9 +223,13 @@ impl Engine for Tendermint { } fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Result { - match try!(message.val_at(0)) { - 0u8 if sender == self.proposer() => self.propose_message(try!(message.at(1))), - 1 if self.our_params.validators.contains(&sender) => self.prevote_message(sender, try!(message.at(1))), + // Check if correct round. + if self.our_params.r != try!(message.val_at(0)) { try!(Err(EngineError::WrongRound)) } + // Handle according to step. + match try!(message.val_at(1)) { + 0u8 if self.is_proposer(&sender) => self.propose_message(try!(message.at(2))), + 1 if self.is_validator(&sender) => self.prevote_message(sender, try!(message.at(2))), + 2 if self.is_validator(&sender) => self.precommit_message(sender, try!(message.at(2))), _ => try!(Err(EngineError::UnknownStep)), } } @@ -219,7 +278,6 @@ impl Engine for Tendermint { } } - #[cfg(test)] mod tests { use common::*; @@ -227,13 +285,22 @@ mod tests { use tests::helpers::*; use account_provider::AccountProvider; use spec::Spec; - use super::Step; - use ethkey::Signature; + use engines::Engine; /// Create a new test chain spec with `Tendermint` consensus engine. /// Account "0".sha3() and "1".sha3() are a validators. fn new_test_tendermint() -> Spec { Spec::load(include_bytes!("../../res/tendermint.json")) } + fn propose_default(engine: &Arc, proposer: Address) -> Result { + let mut s = RlpStream::new_list(3); + let header = Header::default(); + s.append(&0u8).append(&0u8).append(&header.bare_hash()); + let drain = s.out(); + let propose_rlp = UntrustedRlp::new(&drain); + + engine.handle_message(proposer, propose_rlp) + } + #[test] fn has_valid_metadata() { let engine = new_test_tendermint().engine; @@ -284,8 +351,7 @@ mod tests { 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 vm_factory = Default::default(); - let b = OpenBlock::new(engine, &vm_factory, Default::default(), false, db, &genesis_header, last_hashes, addr, (3141562.into(), 31415620.into()), vec![]).unwrap(); + 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()); @@ -295,23 +361,25 @@ mod tests { fn propose_step() { let engine = new_test_tendermint().engine; let tap = AccountProvider::transient_provider(); - let mut s = RlpStream::new_list(2); - let header = Header::default(); - s.append(&0u8).append(&header.bare_hash()); - let drain = s.out(); - let propose_rlp = UntrustedRlp::new(&drain); let not_validator_addr = tap.insert_account("101".sha3(), "101").unwrap(); - assert!(engine.handle_message(not_validator_addr, propose_rlp.clone()).is_err()); + assert!(propose_default(&engine, not_validator_addr).is_err()); let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap(); - assert!(engine.handle_message(not_proposer_addr, propose_rlp.clone()).is_err()); + assert!(propose_default(&engine, not_proposer_addr).is_err()); let proposer_addr = tap.insert_account("1".sha3(), "1").unwrap(); assert_eq!(vec![160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101], - engine.handle_message(proposer_addr, propose_rlp.clone()).unwrap()); + propose_default(&engine, proposer_addr).unwrap()); - assert!(engine.handle_message(not_proposer_addr, propose_rlp).is_err()); + assert!(propose_default(&engine, proposer_addr).is_err()); + assert!(propose_default(&engine, not_proposer_addr).is_err()); + } + + #[test] + fn prevote_step() { + let engine = new_test_tendermint().engine; + propose_default(&engine, Address::default()); } #[test] From 402564518895d21bc02dab17708910f9424c2b56 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 29 Aug 2016 12:09:51 +0200 Subject: [PATCH 019/295] accumulate seal in precommit --- ethcore/src/client/client.rs | 3 ++- ethcore/src/engines/mod.rs | 4 ++-- ethcore/src/engines/tendermint.rs | 16 ++++++++-------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 991e880a8..9fea1513a 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1034,7 +1034,8 @@ impl BlockChainClient for Client { if let Ok(signature) = full_rlp.val_at::(0) { if let Ok(message) = full_rlp.at(1) { if let Ok(pub_key) = recover(&signature.into(), &message.as_raw().sha3()) { - if let Ok(new_message) = self.engine.handle_message(pub_key.sha3().into(), message) { + if let Ok(new_message) = self.engine.handle_message(pub_key.sha3().into(), signature, message) + { self.notify(|notify| notify.broadcast(new_message.clone())); } } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 1db8acb0f..7dbdaf86b 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -30,7 +30,7 @@ pub use self::tendermint::Tendermint; pub use self::signed_vote::SignedVote; pub use self::propose_collect::ProposeCollect; -use common::{HashMap, SemanticVersion, Header, EnvInfo, Address, Builtin, BTreeMap, U256, Bytes, SignedTransaction, Error, UntrustedRlp}; +use common::{HashMap, SemanticVersion, Header, EnvInfo, Address, Builtin, BTreeMap, U256, Bytes, SignedTransaction, Error, UntrustedRlp, H520}; use account_provider::AccountProvider; use block::ExecutedBlock; use spec::CommonParams; @@ -138,7 +138,7 @@ pub trait Engine : Sync + Send { /// Handle any potential consensus messages; /// updating consensus state and potentially issuing a new one. - fn handle_message(&self, _sender: Address, _message: UntrustedRlp) -> Result { Err(EngineError::UnexpectedMessage.into()) } + fn handle_message(&self, _sender: Address, _signature: H520, _message: UntrustedRlp) -> Result { Err(EngineError::UnexpectedMessage.into()) } // TODO: builtin contract routing - to do this properly, it will require removing the built-in configuration-reading logic // from Spec into here and removing the Spec::builtins field. diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index 76f9938b2..d1bfe26ce 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -50,7 +50,7 @@ pub struct TendermintParams { enum Step { Propose, Prevote(ProposeCollect), - Precommit(ProposeCollect), + Precommit(ProposeCollect, Vec), Commit } @@ -131,7 +131,7 @@ impl Tendermint { // Move to next step is prevote is won. if vote.is_won() { let mut guard = self.our_params.s.write(); - *guard = Step::Precommit(self.new_vote(vote.hash)); + *guard = Step::Precommit(self.new_vote(vote.hash), Vec::new()); Ok(message.as_raw().to_vec()) } else { Ok(message.as_raw().to_vec()) @@ -144,13 +144,13 @@ impl Tendermint { } } - fn precommit_message(&self, sender: Address, message: UntrustedRlp) -> Result { + fn precommit_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { // Check if message is for correct step. match *self.our_params.s.try_write().unwrap() { - Step::Prevote(ref mut vote) => { + Step::Precommit(ref mut vote, ref mut seal) => { // Vote and accumulate seal if message is about the right block. if vote.hash == try!(message.as_val()) { - vote.vote(sender); + if vote.vote(sender) { seal.push(encode(&signature).to_vec()); } // Commit if precommit is won. if vote.is_won() { let mut guard = self.our_params.s.write(); @@ -222,14 +222,14 @@ impl Engine for Tendermint { }) } - fn handle_message(&self, sender: Address, message: UntrustedRlp) -> Result { + fn handle_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { // Check if correct round. if self.our_params.r != try!(message.val_at(0)) { try!(Err(EngineError::WrongRound)) } // Handle according to step. match try!(message.val_at(1)) { 0u8 if self.is_proposer(&sender) => self.propose_message(try!(message.at(2))), 1 if self.is_validator(&sender) => self.prevote_message(sender, try!(message.at(2))), - 2 if self.is_validator(&sender) => self.precommit_message(sender, try!(message.at(2))), + 2 if self.is_validator(&sender) => self.precommit_message(sender, signature, try!(message.at(2))), _ => try!(Err(EngineError::UnknownStep)), } } @@ -298,7 +298,7 @@ mod tests { let drain = s.out(); let propose_rlp = UntrustedRlp::new(&drain); - engine.handle_message(proposer, propose_rlp) + engine.handle_message(proposer, H520::default(), propose_rlp) } #[test] From d7499044e3a3590471720981d4ba606397536d1a Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 29 Aug 2016 14:32:37 +0200 Subject: [PATCH 020/295] move seal into commit --- ethcore/src/engines/tendermint.rs | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index d1bfe26ce..116a0ce16 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -17,6 +17,7 @@ //! Tendermint BFT consensus engine with round robin proof-of-authority. use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; +use std::time::Duration; use common::*; use account_provider::AccountProvider; use block::*; @@ -36,24 +37,36 @@ pub struct TendermintParams { pub validators: Vec
, /// Number of validators. pub validator_n: usize, + /// Timeout durations for different steps. + timeouts: Timeouts, /// Consensus round. r: u64, /// Consensus step. s: RwLock, /// Used to swith proposer. proposer_nonce: AtomicUsize, - /// Seal collection. - seal: Vec } #[derive(Debug)] enum Step { Propose, Prevote(ProposeCollect), - Precommit(ProposeCollect, Vec), - Commit + /// Precommit step storing the precommit vote and accumulating seal. + Precommit(ProposeCollect, Seal), + /// Commit step storing a complete valid seal. + Commit(Seal) } +#[derive(Debug)] +struct Timeouts { + propose: Duration, + prevote: Duration, + precommit: Duration, + commit: Duration +} + +type Seal = Vec; + impl From for TendermintParams { fn from(p: ethjson::spec::TendermintParams) -> Self { let val: Vec<_> = p.validators.into_iter().map(Into::into).collect(); @@ -63,10 +76,10 @@ impl From for TendermintParams { duration_limit: p.duration_limit.into(), validators: val, validator_n: val_n, + timeouts: Timeouts { propose: Duration::from_secs(3), prevote: Duration::from_secs(3), precommit: Duration::from_secs(3), commit: Duration::from_secs(3) }, r: 0, s: RwLock::new(Step::Propose), - proposer_nonce: AtomicUsize::new(0), - seal: Vec::new() + proposer_nonce: AtomicUsize::new(0) } } } @@ -154,7 +167,7 @@ impl Tendermint { // Commit if precommit is won. if vote.is_won() { let mut guard = self.our_params.s.write(); - *guard = Step::Commit; + *guard = Step::Commit(seal.clone()); Ok(message.as_raw().to_vec()) } else { Ok(message.as_raw().to_vec()) @@ -245,7 +258,7 @@ impl Engine for Tendermint { Ok(()) } - fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { + fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { Ok(()) } From e475d0bf4ca97e72deed10781fb2352b0f10ae18 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 31 Aug 2016 18:18:02 +0200 Subject: [PATCH 021/295] initial timeouts --- ethcore/res/tendermint.json | 1 - ethcore/src/engines/propose_collect.rs | 11 +- ethcore/src/engines/tendermint.rs | 162 ++++++++++++++++++++----- ethcore/src/service.rs | 4 + json/src/spec/tendermint.rs | 4 - 5 files changed, 146 insertions(+), 36 deletions(-) diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index 126761233..8aa8f24f7 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -4,7 +4,6 @@ "Tendermint": { "params": { "gasLimitBoundDivisor": "0x0400", - "durationLimit": "0x0d", "validators" : [ "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" diff --git a/ethcore/src/engines/propose_collect.rs b/ethcore/src/engines/propose_collect.rs index ee4aa3810..46defd557 100644 --- a/ethcore/src/engines/propose_collect.rs +++ b/ethcore/src/engines/propose_collect.rs @@ -49,10 +49,13 @@ impl ProposeCollect { /// Vote on hash using the signed hash, true if vote counted. pub fn vote(&self, voter: Address) -> bool { - if self.votes.try_read().unwrap().contains(&voter) { return false; } - if !self.voters.contains(&voter) { return false; } - self.votes.try_write().unwrap().insert(voter); - true + match self.votes.try_read().unwrap().contains(&voter) || !self.voters.contains(&voter) { + true => false, + false => { + self.votes.try_write().unwrap().insert(voter); + true + }, + } } /// Some winner if voting threshold was reached. diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index 116a0ce16..8ccbbf95a 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -17,7 +17,6 @@ //! Tendermint BFT consensus engine with round robin proof-of-authority. use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; -use std::time::Duration; use common::*; use account_provider::AccountProvider; use block::*; @@ -25,28 +24,54 @@ use spec::CommonParams; use engines::{Engine, EngineError, ProposeCollect}; use evm::Schedule; use ethjson; +use io::{IoContext, IoHandler, TimerToken}; +use service::{ClientIoMessage, ENGINE_TIMEOUT_TOKEN}; +use time::get_time; /// `Tendermint` params. #[derive(Debug)] pub struct TendermintParams { /// Gas limit divisor. pub gas_limit_bound_divisor: U256, - /// Block duration. - pub duration_limit: u64, /// List of validators. pub validators: Vec
, /// Number of validators. pub validator_n: usize, /// Timeout durations for different steps. - timeouts: Timeouts, + timeouts: DefaultTimeouts, /// Consensus round. r: u64, /// Consensus step. s: RwLock, + /// Current step timeout in ms. + timeout: AtomicTimerToken, /// Used to swith proposer. proposer_nonce: AtomicUsize, } +impl Default for TendermintParams { + fn default() -> Self { + let validators = vec!["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e".into(), "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1".into()]; + let val_n = validators.len(); + let propose_timeout = 3000; + TendermintParams { + gas_limit_bound_divisor: 0x0400.into(), + validators: validators, + validator_n: val_n, + timeouts: DefaultTimeouts { + propose: propose_timeout, + prevote: 3000, + precommit: 3000, + commit: 3000 + }, + r: 0, + s: RwLock::new(Step::Propose), + timeout: AtomicUsize::new(propose_timeout), + proposer_nonce: AtomicUsize::new(0) + } + } +} + #[derive(Debug)] enum Step { Propose, @@ -58,27 +83,62 @@ enum Step { } #[derive(Debug)] -struct Timeouts { - propose: Duration, - prevote: Duration, - precommit: Duration, - commit: Duration +struct DefaultTimeouts { + propose: TimerToken, + prevote: TimerToken, + precommit: TimerToken, + commit: TimerToken } type Seal = Vec; +type AtomicTimerToken = AtomicUsize; + +impl IoHandler for Tendermint { + fn initialize(&self, io: &IoContext) { + io.register_timer(ENGINE_TIMEOUT_TOKEN, self.our_params.timeout.load(AtomicOrdering::Relaxed) as u64).expect("Error registering engine timeout"); + } + + fn timeout(&self, io: &IoContext, timer: TimerToken) { + if timer == ENGINE_TIMEOUT_TOKEN { + println!("Timeout: {:?}", get_time().sec); + io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to cancel consensus timer."); + match *self.our_params.s.try_read().unwrap() { + Step::Propose => self.to_propose(), + Step::Prevote(ref proposal) => self.to_precommit(proposal.hash.clone()), + Step::Precommit(_, _) => self.to_propose(), + Step::Commit(_) => self.to_propose(), + }; + io.register_timer(ENGINE_TIMEOUT_TOKEN, 3000).expect("Failed to start new consensus timer.") + } + } + + fn message(&self, io: &IoContext, net_message: &ClientIoMessage) { + if let &ClientIoMessage::ConsensusStep(next_timeout) = net_message { + println!("Message: {:?}", get_time().sec); + io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to cancel consensus timer."); + io.register_timer(ENGINE_TIMEOUT_TOKEN, next_timeout).expect("Failed to start new consensus timer.") + } + } +} impl From for TendermintParams { fn from(p: ethjson::spec::TendermintParams) -> Self { let val: Vec<_> = p.validators.into_iter().map(Into::into).collect(); let val_n = val.len(); + let propose_timeout = 3; TendermintParams { gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), - duration_limit: p.duration_limit.into(), validators: val, validator_n: val_n, - timeouts: Timeouts { propose: Duration::from_secs(3), prevote: Duration::from_secs(3), precommit: Duration::from_secs(3), commit: Duration::from_secs(3) }, + timeouts: DefaultTimeouts { + propose: propose_timeout, + prevote: 3, + precommit: 3, + commit: 3 + }, r: 0, s: RwLock::new(Step::Propose), + timeout: AtomicUsize::new(propose_timeout), proposer_nonce: AtomicUsize::new(0) } } @@ -120,6 +180,12 @@ impl Tendermint { self.threshold()) } + fn to_propose(&self) { + self.our_params.proposer_nonce.fetch_add(1, AtomicOrdering::Relaxed); + let mut guard = self.our_params.s.write(); + *guard = Step::Propose; + } + fn propose_message(&self, message: UntrustedRlp) -> Result { // Check if message is for correct step. match *self.our_params.s.try_read().unwrap() { @@ -127,11 +193,14 @@ impl Tendermint { _ => try!(Err(EngineError::WrongStep)), } let proposal = try!(message.as_val()); - self.our_params.proposer_nonce.fetch_add(1, AtomicOrdering::Relaxed); + self.to_prevote(proposal); + Ok(message.as_raw().to_vec()) + } + + fn to_prevote(&self, proposal: H256) { let mut guard = self.our_params.s.write(); // Proceed to the prevote step. *guard = Step::Prevote(self.new_vote(proposal)); - Ok(message.as_raw().to_vec()) } fn prevote_message(&self, sender: Address, message: UntrustedRlp) -> Result { @@ -142,13 +211,8 @@ impl Tendermint { if vote.hash == try!(message.as_val()) { vote.vote(sender); // Move to next step is prevote is won. - if vote.is_won() { - let mut guard = self.our_params.s.write(); - *guard = Step::Precommit(self.new_vote(vote.hash), Vec::new()); - Ok(message.as_raw().to_vec()) - } else { - Ok(message.as_raw().to_vec()) - } + if vote.is_won() { self.to_precommit(vote.hash); } + Ok(message.as_raw().to_vec()) } else { try!(Err(EngineError::WrongVote)) } @@ -157,6 +221,11 @@ impl Tendermint { } } + fn to_precommit(&self, proposal: H256) { + let mut guard = self.our_params.s.write(); + *guard = Step::Precommit(self.new_vote(proposal), Vec::new()); + } + fn precommit_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { // Check if message is for correct step. match *self.our_params.s.try_write().unwrap() { @@ -165,13 +234,8 @@ impl Tendermint { if vote.hash == try!(message.as_val()) { if vote.vote(sender) { seal.push(encode(&signature).to_vec()); } // Commit if precommit is won. - if vote.is_won() { - let mut guard = self.our_params.s.write(); - *guard = Step::Commit(seal.clone()); - Ok(message.as_raw().to_vec()) - } else { - Ok(message.as_raw().to_vec()) - } + if vote.is_won() { self.to_commit(seal.clone()); } + Ok(message.as_raw().to_vec()) } else { try!(Err(EngineError::WrongVote)) } @@ -180,6 +244,11 @@ impl Tendermint { } } + fn to_commit(&self, seal: Seal) { + let mut guard = self.our_params.s.write(); + *guard = Step::Commit(seal); + } + fn threshold(&self) -> usize { self.our_params.validator_n*2/3 } @@ -294,16 +363,40 @@ impl Engine for Tendermint { #[cfg(test)] mod tests { use common::*; + use std::thread::sleep; + use std::time::{Duration, Instant}; use block::*; use tests::helpers::*; + use service::{ClientService, ClientIoMessage}; + use devtools::RandomTempPath; + use client::ClientConfig; + use miner::Miner; use account_provider::AccountProvider; use spec::Spec; use engines::Engine; + use super::{Tendermint, TendermintParams}; /// Create a new test chain spec with `Tendermint` consensus engine. /// Account "0".sha3() and "1".sha3() are a validators. fn new_test_tendermint() -> Spec { Spec::load(include_bytes!("../../res/tendermint.json")) } + fn new_test_client_service() -> ClientService { + let temp_path = RandomTempPath::new(); + let mut path = temp_path.as_path().to_owned(); + path.push("pruning"); + path.push("db"); + + let spec = get_test_spec(); + let service = ClientService::start( + ClientConfig::default(), + &spec, + &path, + &path, + Arc::new(Miner::with_spec(&spec)), + ); + service.unwrap() + } + fn propose_default(engine: &Arc, proposer: Address) -> Result { let mut s = RlpStream::new_list(3); let header = Header::default(); @@ -399,4 +492,19 @@ mod tests { fn handle_message() { false; } + + #[test] + fn timeout_switching() { + let service = new_test_client_service(); + let engine = new_test_tendermint().engine; + let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()); + service.register_io_handler(Arc::new(tender)); + + println!("Waiting for timeout"); + sleep(Duration::from_secs(10)); + + let message_channel = service.io().channel(); + message_channel.send(ClientIoMessage::ConsensusStep(1000)); + sleep(Duration::from_secs(5)); + } } diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index 355c7d580..7a3da2691 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -43,6 +43,8 @@ pub enum ClientIoMessage { FeedStateChunk(H256, Bytes), /// Feed a block chunk to the snapshot service FeedBlockChunk(H256, Bytes), + /// Signal consensus step timeout. + ConsensusStep(u64), } /// Client service setup. Creates and registers client and network services with the IO subsystem. @@ -143,6 +145,8 @@ struct ClientIoHandler { const CLIENT_TICK_TIMER: TimerToken = 0; const CLIENT_TICK_MS: u64 = 5000; +/// Timer token representing the consensus step timeouts. +pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 1; impl IoHandler for ClientIoHandler { fn initialize(&self, io: &IoContext) { diff --git a/json/src/spec/tendermint.rs b/json/src/spec/tendermint.rs index c3294810c..97c30fbb2 100644 --- a/json/src/spec/tendermint.rs +++ b/json/src/spec/tendermint.rs @@ -25,9 +25,6 @@ pub struct TendermintParams { /// Gas limit divisor. #[serde(rename="gasLimitBoundDivisor")] pub gas_limit_bound_divisor: Uint, - /// Block duration. - #[serde(rename="durationLimit")] - pub duration_limit: Uint, /// Valid authorities pub validators: Vec
, } @@ -49,7 +46,6 @@ mod tests { let s = r#"{ "params": { "gasLimitBoundDivisor": "0x0400", - "durationLimit": "0x0d", "validators" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] } }"#; From 0fcbf8d99fd2b3b84dd71a7d47ff4cd4974b5d58 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 31 Aug 2016 18:43:24 +0200 Subject: [PATCH 022/295] fix after merge --- ethcore/src/engines/tendermint.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index 8ccbbf95a..7333cf70d 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -108,7 +108,7 @@ impl IoHandler for Tendermint { Step::Precommit(_, _) => self.to_propose(), Step::Commit(_) => self.to_propose(), }; - io.register_timer(ENGINE_TIMEOUT_TOKEN, 3000).expect("Failed to start new consensus timer.") + //io.register_timer(ENGINE_TIMEOUT_TOKEN, 3000).expect("Failed to start new consensus timer.") } } @@ -271,17 +271,16 @@ impl Engine for Tendermint { } fn populate_from_parent(&self, header: &mut Header, parent: &Header, gas_floor_target: U256, _gas_ceil_target: U256) { - header.difficulty = parent.difficulty; - header.gas_limit = { - let gas_limit = parent.gas_limit; + 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()) } - }; - header.note_dirty(); + }); } /// Apply the block reward on finalisation of the block. @@ -319,9 +318,9 @@ impl Engine for Tendermint { fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { // check the seal fields. // TODO: pull this out into common code. - if header.seal.len() != self.seal_fields() { + if header.seal().len() != self.seal_fields() { return Err(From::from(BlockError::InvalidSealArity( - Mismatch { expected: self.seal_fields(), found: header.seal.len() } + Mismatch { expected: self.seal_fields(), found: header.seal().len() } ))); } Ok(()) @@ -342,10 +341,10 @@ impl Engine for Tendermint { 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 - parent.gas_limit / gas_limit_divisor; - let max_gas = parent.gas_limit + parent.gas_limit / 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 }))); + 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(()) } From 83c371e6d4c04245b009b454a0975adb7148722e Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 1 Sep 2016 14:12:26 +0200 Subject: [PATCH 023/295] 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 8851acec7c76914b07969037fb568afff4a6f0e9 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 5 Sep 2016 17:06:43 +0200 Subject: [PATCH 024/295] fix propose collect locking --- ethcore/src/engines/propose_collect.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/ethcore/src/engines/propose_collect.rs b/ethcore/src/engines/propose_collect.rs index 46defd557..ad245e2cd 100644 --- a/ethcore/src/engines/propose_collect.rs +++ b/ethcore/src/engines/propose_collect.rs @@ -49,12 +49,12 @@ impl ProposeCollect { /// Vote on hash using the signed hash, true if vote counted. pub fn vote(&self, voter: Address) -> bool { - match self.votes.try_read().unwrap().contains(&voter) || !self.voters.contains(&voter) { - true => false, - false => { - self.votes.try_write().unwrap().insert(voter); - true - }, + let is_known = self.votes.try_read().unwrap().contains(&voter); + if !is_known && self.voters.contains(&voter) { + self.votes.try_write().unwrap().insert(voter); + true + } else { + false } } @@ -66,11 +66,6 @@ impl ProposeCollect { }; self.is_won.load(Ordering::Relaxed) || threshold_checker() } - - /// Get addresses backing given hash. - pub fn votes(&self) -> HashSet
{ - self.votes.try_read().unwrap().clone() - } } #[cfg(test)] From 0af4bf23a982ced4b97d39aa5e2684d4738b53ba Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 5 Sep 2016 17:51:29 +0200 Subject: [PATCH 025/295] add internal timeout service, test proposer switching --- ethcore/src/engines/tendermint.rs | 247 ++++++++++++++++-------------- ethcore/src/service.rs | 4 - ethcore/src/spec/spec.rs | 2 +- 3 files changed, 131 insertions(+), 122 deletions(-) diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index 7333cf70d..66b469923 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -17,6 +17,7 @@ //! Tendermint BFT consensus engine with round robin proof-of-authority. use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; +use std::sync::Weak; use common::*; use account_provider::AccountProvider; use block::*; @@ -24,8 +25,7 @@ use spec::CommonParams; use engines::{Engine, EngineError, ProposeCollect}; use evm::Schedule; use ethjson; -use io::{IoContext, IoHandler, TimerToken}; -use service::{ClientIoMessage, ENGINE_TIMEOUT_TOKEN}; +use io::{IoContext, IoHandler, TimerToken, IoService}; use time::get_time; /// `Tendermint` params. @@ -39,14 +39,6 @@ pub struct TendermintParams { pub validator_n: usize, /// Timeout durations for different steps. timeouts: DefaultTimeouts, - /// Consensus round. - r: u64, - /// Consensus step. - s: RwLock, - /// Current step timeout in ms. - timeout: AtomicTimerToken, - /// Used to swith proposer. - proposer_nonce: AtomicUsize, } impl Default for TendermintParams { @@ -64,10 +56,6 @@ impl Default for TendermintParams { precommit: 3000, commit: 3000 }, - r: 0, - s: RwLock::new(Step::Propose), - timeout: AtomicUsize::new(propose_timeout), - proposer_nonce: AtomicUsize::new(0) } } } @@ -82,88 +70,70 @@ enum Step { Commit(Seal) } -#[derive(Debug)] -struct DefaultTimeouts { - propose: TimerToken, - prevote: TimerToken, - precommit: TimerToken, - commit: TimerToken -} - -type Seal = Vec; -type AtomicTimerToken = AtomicUsize; - -impl IoHandler for Tendermint { - fn initialize(&self, io: &IoContext) { - io.register_timer(ENGINE_TIMEOUT_TOKEN, self.our_params.timeout.load(AtomicOrdering::Relaxed) as u64).expect("Error registering engine timeout"); - } - - fn timeout(&self, io: &IoContext, timer: TimerToken) { - if timer == ENGINE_TIMEOUT_TOKEN { - println!("Timeout: {:?}", get_time().sec); - io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to cancel consensus timer."); - match *self.our_params.s.try_read().unwrap() { - Step::Propose => self.to_propose(), - Step::Prevote(ref proposal) => self.to_precommit(proposal.hash.clone()), - Step::Precommit(_, _) => self.to_propose(), - Step::Commit(_) => self.to_propose(), - }; - //io.register_timer(ENGINE_TIMEOUT_TOKEN, 3000).expect("Failed to start new consensus timer.") - } - } - - fn message(&self, io: &IoContext, net_message: &ClientIoMessage) { - if let &ClientIoMessage::ConsensusStep(next_timeout) = net_message { - println!("Message: {:?}", get_time().sec); - io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to cancel consensus timer."); - io.register_timer(ENGINE_TIMEOUT_TOKEN, next_timeout).expect("Failed to start new consensus timer.") - } - } -} - impl From for TendermintParams { fn from(p: ethjson::spec::TendermintParams) -> Self { let val: Vec<_> = p.validators.into_iter().map(Into::into).collect(); let val_n = val.len(); - let propose_timeout = 3; + let propose_timeout = 3000; TendermintParams { gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), validators: val, validator_n: val_n, timeouts: DefaultTimeouts { propose: propose_timeout, - prevote: 3, - precommit: 3, - commit: 3 + prevote: 3000, + precommit: 3000, + commit: 3000 }, - r: 0, - s: RwLock::new(Step::Propose), - timeout: AtomicUsize::new(propose_timeout), - proposer_nonce: AtomicUsize::new(0) } } } +#[derive(Clone)] +struct StepMessage; + /// Engine using `Tendermint` consensus algorithm, suitable for EVM chain. pub struct Tendermint { params: CommonParams, our_params: TendermintParams, builtins: BTreeMap, + timeout_service: IoService, + /// Consensus round. + r: u64, + /// Consensus step. + s: RwLock, + /// Current step timeout in ms. + timeout: AtomicMs, + /// Used to swith proposer. + proposer_nonce: AtomicUsize, +} + +struct TimerHandler { + engine: Weak, } impl Tendermint { /// Create a new instance of Tendermint engine - pub fn new(params: CommonParams, our_params: TendermintParams, builtins: BTreeMap) -> Self { - Tendermint { - params: params, - our_params: our_params, - builtins: builtins, - } + pub fn new(params: CommonParams, our_params: TendermintParams, builtins: BTreeMap) -> Arc { + let engine = Arc::new( + Tendermint { + params: params, + timeout: AtomicUsize::new(our_params.timeouts.propose), + our_params: our_params, + builtins: builtins, + timeout_service: IoService::::start().expect("Error creating engine timeout service"), + r: 0, + s: RwLock::new(Step::Propose), + proposer_nonce: AtomicUsize::new(0) + }); + let handler = TimerHandler { engine: Arc::downgrade(&engine) }; + engine.timeout_service.register_handler(Arc::new(handler)).expect("Error creating engine timeout service"); + engine } fn proposer(&self) -> Address { let ref p = self.our_params; - p.validators.get(p.proposer_nonce.load(AtomicOrdering::Relaxed)%p.validator_n).unwrap().clone() + p.validators.get(self.proposer_nonce.load(AtomicOrdering::Relaxed)%p.validator_n).unwrap().clone() } fn is_proposer(&self, address: &Address) -> bool { @@ -180,15 +150,21 @@ impl Tendermint { self.threshold()) } + fn to_step(&self, step: Step) { + let mut guard = self.s.try_write().unwrap(); + *guard = step; + } + fn to_propose(&self) { - self.our_params.proposer_nonce.fetch_add(1, AtomicOrdering::Relaxed); - let mut guard = self.our_params.s.write(); - *guard = Step::Propose; + trace!(target: "tendermint", "step: entering propose"); + println!("step: entering propose"); + self.proposer_nonce.fetch_add(1, AtomicOrdering::Relaxed); + self.to_step(Step::Propose); } fn propose_message(&self, message: UntrustedRlp) -> Result { // Check if message is for correct step. - match *self.our_params.s.try_read().unwrap() { + match *self.s.try_read().unwrap() { Step::Propose => (), _ => try!(Err(EngineError::WrongStep)), } @@ -198,20 +174,24 @@ impl Tendermint { } fn to_prevote(&self, proposal: H256) { - let mut guard = self.our_params.s.write(); + trace!(target: "tendermint", "step: entering prevote"); + println!("step: entering prevote"); // Proceed to the prevote step. - *guard = Step::Prevote(self.new_vote(proposal)); + self.to_step(Step::Prevote(self.new_vote(proposal))); } fn prevote_message(&self, sender: Address, message: UntrustedRlp) -> Result { // Check if message is for correct step. - match *self.our_params.s.try_write().unwrap() { + match *self.s.try_write().unwrap() { Step::Prevote(ref mut vote) => { // Vote if message is about the right block. if vote.hash == try!(message.as_val()) { vote.vote(sender); // Move to next step is prevote is won. - if vote.is_won() { self.to_precommit(vote.hash); } + if vote.is_won() { + //self.our_params.timeouts.precommit + self.to_precommit(vote.hash); + } Ok(message.as_raw().to_vec()) } else { try!(Err(EngineError::WrongVote)) @@ -222,13 +202,14 @@ impl Tendermint { } fn to_precommit(&self, proposal: H256) { - let mut guard = self.our_params.s.write(); - *guard = Step::Precommit(self.new_vote(proposal), Vec::new()); + trace!(target: "tendermint", "step: entering precommit"); + println!("step: entering precommit"); + self.to_step(Step::Precommit(self.new_vote(proposal), Vec::new())); } fn precommit_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { // Check if message is for correct step. - match *self.our_params.s.try_write().unwrap() { + match *self.s.try_write().unwrap() { Step::Precommit(ref mut vote, ref mut seal) => { // Vote and accumulate seal if message is about the right block. if vote.hash == try!(message.as_val()) { @@ -245,13 +226,18 @@ impl Tendermint { } fn to_commit(&self, seal: Seal) { - let mut guard = self.our_params.s.write(); - *guard = Step::Commit(seal); + trace!(target: "tendermint", "step: entering commit"); + println!("step: entering commit"); + self.to_step(Step::Commit(seal)); } fn threshold(&self) -> usize { self.our_params.validator_n*2/3 } + + fn next_timeout(&self) -> u64 { + self.timeout.load(AtomicOrdering::Relaxed) as u64 + } } impl Engine for Tendermint { @@ -305,7 +291,7 @@ impl Engine for Tendermint { fn handle_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { // Check if correct round. - if self.our_params.r != try!(message.val_at(0)) { try!(Err(EngineError::WrongRound)) } + if self.r != try!(message.val_at(0)) { try!(Err(EngineError::WrongRound)) } // Handle according to step. match try!(message.val_at(1)) { 0u8 if self.is_proposer(&sender) => self.propose_message(try!(message.at(2))), @@ -359,17 +345,55 @@ impl Engine for Tendermint { } } +/// Base timeout of each step in ms. +#[derive(Debug)] +struct DefaultTimeouts { + propose: Ms, + prevote: Ms, + precommit: Ms, + commit: Ms +} + +type Ms = usize; +type Seal = Vec; +type AtomicMs = AtomicUsize; + +/// Timer token representing the consensus step timeouts. +pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 0; + +impl IoHandler for TimerHandler { + fn initialize(&self, io: &IoContext) { + if let Some(engine) = self.engine.upgrade() { + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Error registering engine timeout"); + } + } + + fn timeout(&self, io: &IoContext, timer: TimerToken) { + if timer == ENGINE_TIMEOUT_TOKEN { + if let Some(engine) = self.engine.upgrade() { + println!("Timeout: {:?}", get_time()); + engine.to_propose(); + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Failed to restart consensus step timer.") + } + } + } + + fn message(&self, io: &IoContext, _net_message: &StepMessage) { + if let Some(engine) = self.engine.upgrade() { + println!("Message: {:?}", get_time().sec); + io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to restart consensus step timer."); + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Failed to restart consensus step timer.") + } + } +} + #[cfg(test)] mod tests { use common::*; use std::thread::sleep; - use std::time::{Duration, Instant}; + use std::time::{Duration}; use block::*; use tests::helpers::*; - use service::{ClientService, ClientIoMessage}; - use devtools::RandomTempPath; - use client::ClientConfig; - use miner::Miner; use account_provider::AccountProvider; use spec::Spec; use engines::Engine; @@ -379,23 +403,6 @@ mod tests { /// Account "0".sha3() and "1".sha3() are a validators. fn new_test_tendermint() -> Spec { Spec::load(include_bytes!("../../res/tendermint.json")) } - fn new_test_client_service() -> ClientService { - let temp_path = RandomTempPath::new(); - let mut path = temp_path.as_path().to_owned(); - path.push("pruning"); - path.push("db"); - - let spec = get_test_spec(); - let service = ClientService::start( - ClientConfig::default(), - &spec, - &path, - &path, - Arc::new(Miner::with_spec(&spec)), - ); - service.unwrap() - } - fn propose_default(engine: &Arc, proposer: Address) -> Result { let mut s = RlpStream::new_list(3); let header = Header::default(); @@ -406,6 +413,10 @@ mod tests { engine.handle_message(proposer, H520::default(), propose_rlp) } + fn default_block() -> Vec { + vec![160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101] + } + #[test] fn has_valid_metadata() { let engine = new_test_tendermint().engine; @@ -474,36 +485,38 @@ mod tests { assert!(propose_default(&engine, not_proposer_addr).is_err()); let proposer_addr = tap.insert_account("1".sha3(), "1").unwrap(); - assert_eq!(vec![160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101], - propose_default(&engine, proposer_addr).unwrap()); + assert_eq!(default_block(), propose_default(&engine, proposer_addr).unwrap()); assert!(propose_default(&engine, proposer_addr).is_err()); assert!(propose_default(&engine, not_proposer_addr).is_err()); } + #[test] + fn proposer_switching() { + let engine = new_test_tendermint().engine; + let tap = AccountProvider::transient_provider(); + + let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap(); + assert!(propose_default(&engine, not_proposer_addr).is_err()); + + sleep(Duration::from_secs(3)); + + assert_eq!(default_block(), propose_default(&engine, not_proposer_addr).unwrap()); + } + #[test] fn prevote_step() { let engine = new_test_tendermint().engine; propose_default(&engine, Address::default()); } - #[test] - fn handle_message() { - false; - } - #[test] fn timeout_switching() { - let service = new_test_client_service(); let engine = new_test_tendermint().engine; let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()); - service.register_io_handler(Arc::new(tender)); println!("Waiting for timeout"); - sleep(Duration::from_secs(10)); + sleep(Duration::from_secs(60)); - let message_channel = service.io().channel(); - message_channel.send(ClientIoMessage::ConsensusStep(1000)); - sleep(Duration::from_secs(5)); } } diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index e378e63de..e2e4772a4 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -46,8 +46,6 @@ pub enum ClientIoMessage { FeedStateChunk(H256, Bytes), /// Feed a block chunk to the snapshot service FeedBlockChunk(H256, Bytes), - /// Signal consensus step timeout. - ConsensusStep(u64), } /// Client service setup. Creates and registers client and network services with the IO subsystem. @@ -148,8 +146,6 @@ struct ClientIoHandler { const CLIENT_TICK_TIMER: TimerToken = 0; const CLIENT_TICK_MS: u64 = 5000; -/// Timer token representing the consensus step timeouts. -pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 1; impl IoHandler for ClientIoHandler { fn initialize(&self, io: &IoContext) { diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index d5f357097..96bb5354b 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -137,7 +137,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::Tendermint(tendermint) => Arc::new(Tendermint::new(params, From::from(tendermint.params), builtins)), + ethjson::spec::Engine::Tendermint(tendermint) => Tendermint::new(params, From::from(tendermint.params), builtins), } } From 45e6b4ac9dcda51322ebd3e9c14c623231719f60 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 6 Sep 2016 12:26:06 +0200 Subject: [PATCH 026/295] seal generation and verificatio --- ethcore/src/engines/tendermint.rs | 50 ++++++++++++++++++------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index 1c9ffad41..303b94275 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -20,6 +20,7 @@ use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use std::sync::Weak; use common::*; use rlp::{UntrustedRlp, View, encode}; +use ethkey::{recover, public_to_address}; use account_provider::AccountProvider; use block::*; use spec::CommonParams; @@ -278,15 +279,9 @@ impl Engine for Tendermint { /// /// None is returned if not enough signatures can be collected. fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { - accounts.and_then(|ap| { - let header = block.header(); - if header.author() == &self.proposer() { - ap.sign(*header.author(), header.bare_hash()) - .ok() - .and_then(|signature| Some(vec![encode(&(&*signature as &[u8])).to_vec()])) - } else { - None - } + self.s.try_read().and_then(|s| match *s { + Step::Commit(ref seal) => Some(seal.clone()), + _ => None, }) } @@ -302,22 +297,35 @@ impl Engine for Tendermint { } } - fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { - // check the seal fields. - // TODO: pull this out into common code. - if header.seal().len() != self.seal_fields() { - return Err(From::from(BlockError::InvalidSealArity( - Mismatch { expected: self.seal_fields(), found: header.seal().len() } - ))); + fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + if header.seal().len() < self.threshold() { + Err(From::from(BlockError::InvalidSealArity( + Mismatch { expected: self.threshold(), found: header.seal().len() } + ))) + } else { + Ok(()) } - Ok(()) } - fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { - Ok(()) + fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + let to_address = |b: &Vec| { + let sig: H520 = try!(UntrustedRlp::new(b.as_slice()).as_val()); + Ok(public_to_address(&try!(recover(&sig.into(), &header.bare_hash())))) + }; + let validator_set = self.our_params.validators.iter().cloned().collect(); + let seal_set = try!(header + .seal() + .iter() + .map(to_address) + .collect::, Error>>()); + if self.threshold() < seal_set.intersection(&validator_set).count() { + 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> { // we should not calculate difficulty for genesis blocks if header.number() == 0 { return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))); @@ -336,7 +344,7 @@ impl Engine for Tendermint { 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(()) } From ba21bafd7b0ad5484ef080ff9c5b0c1aaec99a0d Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 7 Sep 2016 16:25:42 +0200 Subject: [PATCH 027/295] tests and fixes --- ethcore/src/engines/tendermint.rs | 270 +++++++++++++++++++++++------- 1 file changed, 210 insertions(+), 60 deletions(-) diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index 303b94275..df8368052 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -31,7 +31,7 @@ use io::{IoContext, IoHandler, TimerToken, IoService}; use time::get_time; /// `Tendermint` params. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TendermintParams { /// Gas limit divisor. pub gas_limit_bound_divisor: U256, @@ -47,17 +47,11 @@ impl Default for TendermintParams { fn default() -> Self { let validators = vec!["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e".into(), "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1".into()]; let val_n = validators.len(); - let propose_timeout = 3000; TendermintParams { gas_limit_bound_divisor: 0x0400.into(), validators: validators, validator_n: val_n, - timeouts: DefaultTimeouts { - propose: propose_timeout, - prevote: 3000, - precommit: 3000, - commit: 3000 - }, + timeouts: DefaultTimeouts::default() } } } @@ -69,24 +63,18 @@ enum Step { /// Precommit step storing the precommit vote and accumulating seal. Precommit(ProposeCollect, Seal), /// Commit step storing a complete valid seal. - Commit(Seal) + Commit(H256, Seal) } impl From for TendermintParams { fn from(p: ethjson::spec::TendermintParams) -> Self { let val: Vec<_> = p.validators.into_iter().map(Into::into).collect(); let val_n = val.len(); - let propose_timeout = 3000; TendermintParams { gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), validators: val, validator_n: val_n, - timeouts: DefaultTimeouts { - propose: propose_timeout, - prevote: 3000, - precommit: 3000, - commit: 3000 - }, + timeouts: DefaultTimeouts::default() } } } @@ -101,7 +89,7 @@ pub struct Tendermint { builtins: BTreeMap, timeout_service: IoService, /// Consensus round. - r: u64, + r: AtomicUsize, /// Consensus step. s: RwLock, /// Current step timeout in ms. @@ -124,7 +112,7 @@ impl Tendermint { our_params: our_params, builtins: builtins, timeout_service: IoService::::start().expect("Error creating engine timeout service"), - r: 0, + r: AtomicUsize::new(0), s: RwLock::new(Step::Propose), proposer_nonce: AtomicUsize::new(0) }); @@ -184,23 +172,27 @@ impl Tendermint { fn prevote_message(&self, sender: Address, message: UntrustedRlp) -> Result { // Check if message is for correct step. - match *self.s.try_write().unwrap() { + let hash = match *self.s.try_write().unwrap() { Step::Prevote(ref mut vote) => { // Vote if message is about the right block. if vote.hash == try!(message.as_val()) { vote.vote(sender); // Move to next step is prevote is won. if vote.is_won() { - //self.our_params.timeouts.precommit - self.to_precommit(vote.hash); + // If won assign a hash used for precommit. + vote.hash.clone() + } else { + // Just propoagate the message if not won yet. + return Ok(message.as_raw().to_vec()); } - Ok(message.as_raw().to_vec()) } else { try!(Err(EngineError::WrongVote)) } }, _ => try!(Err(EngineError::WrongStep)), - } + }; + self.to_precommit(hash); + Ok(message.as_raw().to_vec()) } fn to_precommit(&self, proposal: H256) { @@ -217,7 +209,7 @@ impl Tendermint { if vote.hash == try!(message.as_val()) { if vote.vote(sender) { seal.push(encode(&signature).to_vec()); } // Commit if precommit is won. - if vote.is_won() { self.to_commit(seal.clone()); } + if vote.is_won() { self.to_commit(vote.hash.clone(), seal.clone()); } Ok(message.as_raw().to_vec()) } else { try!(Err(EngineError::WrongVote)) @@ -227,10 +219,11 @@ impl Tendermint { } } - fn to_commit(&self, seal: Seal) { + /// Move to commit step, when valid block is known and being distributed. + pub fn to_commit(&self, block_hash: H256, seal: Vec) { trace!(target: "tendermint", "step: entering commit"); println!("step: entering commit"); - self.to_step(Step::Commit(seal)); + self.to_step(Step::Commit(block_hash, seal)); } fn threshold(&self) -> usize { @@ -278,16 +271,18 @@ impl Engine for Tendermint { /// Attempt to seal the block internally using all available signatures. /// /// None is returned if not enough signatures can be collected. - fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { + fn generate_seal(&self, block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option> { self.s.try_read().and_then(|s| match *s { - Step::Commit(ref seal) => Some(seal.clone()), + Step::Commit(hash, ref seal) if hash == block.header().bare_hash() => Some(seal.clone()), _ => None, }) } fn handle_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { // Check if correct round. - if self.r != try!(message.val_at(0)) { try!(Err(EngineError::WrongRound)) } + if self.r.load(AtomicOrdering::Relaxed) != try!(message.val_at(0)) { + try!(Err(EngineError::WrongRound)) + } // Handle according to step. match try!(message.val_at(1)) { 0u8 if self.is_proposer(&sender) => self.propose_message(try!(message.at(2))), @@ -298,7 +293,7 @@ impl Engine for Tendermint { } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { - if header.seal().len() < self.threshold() { + if header.seal().len() <= self.threshold() { Err(From::from(BlockError::InvalidSealArity( Mismatch { expected: self.threshold(), found: header.seal().len() } ))) @@ -318,10 +313,10 @@ impl Engine for Tendermint { .iter() .map(to_address) .collect::, Error>>()); - if self.threshold() < seal_set.intersection(&validator_set).count() { - Ok(()) - } else { + if seal_set.intersection(&validator_set).count() <= self.threshold() { try!(Err(BlockError::InvalidSeal)) + } else { + Ok(()) } } @@ -355,7 +350,7 @@ impl Engine for Tendermint { } /// Base timeout of each step in ms. -#[derive(Debug)] +#[derive(Debug, Clone)] struct DefaultTimeouts { propose: Ms, prevote: Ms, @@ -363,6 +358,17 @@ struct DefaultTimeouts { commit: Ms } +impl Default for DefaultTimeouts { + fn default() -> Self { + DefaultTimeouts { + propose: 3000, + prevote: 3000, + precommit: 3000, + commit: 3000 + } + } +} + type Ms = usize; type Seal = Vec; type AtomicMs = AtomicUsize; @@ -381,7 +387,20 @@ impl IoHandler for TimerHandler { if timer == ENGINE_TIMEOUT_TOKEN { if let Some(engine) = self.engine.upgrade() { println!("Timeout: {:?}", get_time()); - engine.to_propose(); + // Can you release entering a clause? + let next_step = match *engine.s.try_read().unwrap() { + Step::Propose => Step::Propose, + Step::Prevote(_) => Step::Propose, + Step::Precommit(_, _) => Step::Propose, + Step::Commit(_, _) => { + engine.r.fetch_add(1, AtomicOrdering::Relaxed); + Step::Propose + }, + }; + match next_step { + Step::Propose => engine.to_propose(), + _ => (), + } io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Failed to restart consensus step timer.") } } @@ -401,28 +420,53 @@ mod tests { use common::*; use std::thread::sleep; use std::time::{Duration}; - use rlp::{UntrustedRlp, RlpStream, Stream, View}; + use rlp::{UntrustedRlp, RlpStream, Stream, View, encode}; use block::*; use tests::helpers::*; use account_provider::AccountProvider; use spec::Spec; - use engines::Engine; + use engines::{Engine, EngineError}; use super::{Tendermint, TendermintParams}; /// Create a new test chain spec with `Tendermint` consensus engine. /// Account "0".sha3() and "1".sha3() are a validators. fn new_test_tendermint() -> Spec { Spec::load(include_bytes!("../../res/tendermint.json")) } - fn propose_default(engine: &Arc, proposer: Address) -> Result { + fn propose_default(engine: &Arc, round: u8, proposer: Address) -> Result { let mut s = RlpStream::new_list(3); let header = Header::default(); - s.append(&0u8).append(&0u8).append(&header.bare_hash()); + s.append(&round).append(&0u8).append(&header.bare_hash()); let drain = s.out(); let propose_rlp = UntrustedRlp::new(&drain); engine.handle_message(proposer, H520::default(), propose_rlp) } + fn vote_default(engine: &Arc, round: u8, voter: Address) -> Result { + let mut s = RlpStream::new_list(3); + let header = Header::default(); + s.append(&round).append(&1u8).append(&header.bare_hash()); + let drain = s.out(); + let vote_rlp = UntrustedRlp::new(&drain); + + engine.handle_message(voter, H520::default(), vote_rlp) + } + + fn good_seal(header: &Header) -> Vec { + let tap = AccountProvider::transient_provider(); + + let mut seal = Vec::new(); + + let v0 = tap.insert_account("0".sha3(), "0").unwrap(); + let sig0 = tap.sign_with_password(v0, "0".into(), header.bare_hash()).unwrap(); + seal.push(encode(&(&*sig0 as &[u8])).to_vec()); + + let v1 = tap.insert_account("1".sha3(), "1").unwrap(); + let sig1 = tap.sign_with_password(v1, "1".into(), header.bare_hash()).unwrap(); + seal.push(encode(&(&*sig1 as &[u8])).to_vec()); + seal + } + fn default_block() -> Vec { vec![160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101] } @@ -451,7 +495,7 @@ mod tests { } #[test] - fn can_do_seal_verification_fail() { + fn verification_fails_on_short_seal() { let engine = new_test_tendermint().engine; let header: Header = Header::default(); @@ -465,21 +509,73 @@ mod tests { } #[test] - fn can_generate_seal() { + fn verification_fails_on_wrong_signatures() { + let engine = new_test_tendermint().engine; + let mut header = Header::default(); let tap = AccountProvider::transient_provider(); - let addr = tap.insert_account("".sha3(), "").unwrap(); - tap.unlock_account_permanently(addr, "".into()).unwrap(); + let mut seal = Vec::new(); + + let v1 = tap.insert_account("0".sha3(), "0").unwrap(); + let sig1 = tap.sign_with_password(v1, "0".into(), header.bare_hash()).unwrap(); + seal.push(encode(&(&*sig1 as &[u8])).to_vec()); + + header.set_seal(seal.clone()); + + // Not enough signatures. + assert!(engine.verify_block_basic(&header, None).is_err()); + + let v2 = tap.insert_account("101".sha3(), "101").unwrap(); + let sig2 = tap.sign_with_password(v2, "101".into(), header.bare_hash()).unwrap(); + seal.push(encode(&(&*sig2 as &[u8])).to_vec()); + + header.set_seal(seal); + + // Enough signatures. + assert!(engine.verify_block_basic(&header, None).is_ok()); + + let verify_result = engine.verify_block_unordered(&header, None); + + // But wrong signatures. + match verify_result { + Err(Error::Block(BlockError::InvalidSeal)) => (), + Err(_) => panic!("should be block seal-arity mismatch error (got {:?})", verify_result), + _ => panic!("Should be error, got Ok"), + } + } + + #[test] + fn seal_with_enough_signatures_is_ok() { + let engine = new_test_tendermint().engine; + let mut header = Header::default(); + + let seal = good_seal(&header); + header.set_seal(seal); + + // Enough signatures. + assert!(engine.verify_block_basic(&header, None).is_ok()); + + // And they are ok. + assert!(engine.verify_block_unordered(&header, None).is_ok()); + } + + #[test] + fn can_generate_seal() { let spec = new_test_tendermint(); - let engine = &*spec.engine; + let ref engine = *spec.engine; + let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()); + 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 = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); - let seal = engine.generate_seal(b.block(), Some(&tap)).unwrap(); + + tender.to_commit(b.hash(), good_seal(&b.header())); + + let seal = tender.generate_seal(b.block(), None).unwrap(); assert!(b.try_seal(engine, seal).is_ok()); } @@ -487,18 +583,19 @@ mod tests { fn propose_step() { let engine = new_test_tendermint().engine; let tap = AccountProvider::transient_provider(); + let r = 0; let not_validator_addr = tap.insert_account("101".sha3(), "101").unwrap(); - assert!(propose_default(&engine, not_validator_addr).is_err()); + assert!(propose_default(&engine, r, not_validator_addr).is_err()); let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap(); - assert!(propose_default(&engine, not_proposer_addr).is_err()); + assert!(propose_default(&engine, r, not_proposer_addr).is_err()); let proposer_addr = tap.insert_account("1".sha3(), "1").unwrap(); - assert_eq!(default_block(), propose_default(&engine, proposer_addr).unwrap()); + assert_eq!(default_block(), propose_default(&engine, r, proposer_addr).unwrap()); - assert!(propose_default(&engine, proposer_addr).is_err()); - assert!(propose_default(&engine, not_proposer_addr).is_err()); + assert!(propose_default(&engine, r, proposer_addr).is_err()); + assert!(propose_default(&engine, r, not_proposer_addr).is_err()); } #[test] @@ -506,27 +603,80 @@ mod tests { let engine = new_test_tendermint().engine; let tap = AccountProvider::transient_provider(); + // Currently not a proposer. let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap(); - assert!(propose_default(&engine, not_proposer_addr).is_err()); + assert!(propose_default(&engine, 0, not_proposer_addr).is_err()); - sleep(Duration::from_secs(3)); + sleep(Duration::from_millis(TendermintParams::default().timeouts.propose as u64)); - assert_eq!(default_block(), propose_default(&engine, not_proposer_addr).unwrap()); + // Becomes proposer after timeout. + assert_eq!(default_block(), propose_default(&engine, 0, not_proposer_addr).unwrap()); } #[test] fn prevote_step() { let engine = new_test_tendermint().engine; - propose_default(&engine, Address::default()); + let tap = AccountProvider::transient_provider(); + let r = 0; + + let v0 = tap.insert_account("0".sha3(), "0").unwrap(); + let v1 = tap.insert_account("1".sha3(), "1").unwrap(); + + // Propose. + assert!(propose_default(&engine, r, v1.clone()).is_ok()); + + // Prevote. + assert_eq!(default_block(), vote_default(&engine, r, v0.clone()).unwrap()); + + assert!(vote_default(&engine, r, v0).is_err()); + assert!(vote_default(&engine, r, v1).is_err()); + } + + #[test] + fn precommit_step() { + let engine = new_test_tendermint().engine; + let tap = AccountProvider::transient_provider(); + let r = 0; + + let v0 = tap.insert_account("0".sha3(), "0").unwrap(); + let v1 = tap.insert_account("1".sha3(), "1").unwrap(); + + // Propose. + assert!(propose_default(&engine, r, v1.clone()).is_ok()); + + // Prevote. + assert_eq!(default_block(), vote_default(&engine, r, v0.clone()).unwrap()); + + assert!(vote_default(&engine, r, v0).is_err()); + assert!(vote_default(&engine, r, v1).is_err()); } #[test] fn timeout_switching() { - let engine = new_test_tendermint().engine; - let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()); - - println!("Waiting for timeout"); - sleep(Duration::from_secs(60)); + let tender = { + let engine = new_test_tendermint().engine; + Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()) + }; + println!("Waiting for timeout"); + sleep(Duration::from_secs(10)); + } + + #[test] + fn increments_round() { + let spec = new_test_tendermint(); + let ref engine = *spec.engine; + let def_params = TendermintParams::default(); + let tender = Tendermint::new(engine.params().clone(), def_params.clone(), BTreeMap::new()); + let header = Header::default(); + + tender.to_commit(header.bare_hash(), good_seal(&header)); + + sleep(Duration::from_millis(def_params.timeouts.commit as u64)); + + match propose_default(&(tender as Arc), 0, Address::default()) { + Err(Error::Engine(EngineError::WrongRound)) => {}, + _ => panic!("Should be EngineError::WrongRound"), + } } } From 9fe62d975036b8eb660ee533f25c1d2f0aa84e4f Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 8 Sep 2016 10:31:46 +0200 Subject: [PATCH 028/295] adjust default timeouts --- ethcore/src/engines/tendermint.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint.rs index df8368052..b88874b84 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint.rs @@ -361,10 +361,10 @@ struct DefaultTimeouts { impl Default for DefaultTimeouts { fn default() -> Self { DefaultTimeouts { - propose: 3000, - prevote: 3000, - precommit: 3000, - commit: 3000 + propose: 1000, + prevote: 1000, + precommit: 1000, + commit: 1000 } } } From 6cbb859bd2fb5dd4cb15efbb2036c5c1dce8e388 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 29 Sep 2016 14:44:42 +0100 Subject: [PATCH 029/295] add tendermint message types and deserialization --- ethcore/src/engines/tendermint/message.rs | 81 ++++++++++++++ .../{tendermint.rs => tendermint/mod.rs} | 105 ++++-------------- ethcore/src/engines/tendermint/timeout.rs | 104 +++++++++++++++++ 3 files changed, 206 insertions(+), 84 deletions(-) create mode 100644 ethcore/src/engines/tendermint/message.rs rename ethcore/src/engines/{tendermint.rs => tendermint/mod.rs} (88%) create mode 100644 ethcore/src/engines/tendermint/timeout.rs diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs new file mode 100644 index 000000000..86691b476 --- /dev/null +++ b/ethcore/src/engines/tendermint/message.rs @@ -0,0 +1,81 @@ +// 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 . + +//! Tendermint message handling. + +use super::{Height, Round, BlockHash}; +use rlp::{View, DecoderError, Decodable, Decoder, Encodable, RlpStream, Stream}; + +pub enum ConsensusMessage { + Prevote(Height, Round, BlockHash), + Precommit(Height, Round, BlockHash), + Commit(Height, BlockHash), +} + +/// (height, step, ...) +impl Decodable for ConsensusMessage { + fn decode(decoder: &D) -> Result where D: Decoder { + // Handle according to step. + let rlp = decoder.as_rlp(); + if decoder.as_raw().len() != try!(rlp.payload_info()).total() { + return Err(DecoderError::RlpIsTooBig); + } + let height = try!(rlp.val_at(0)); + Ok(match try!(rlp.val_at(1)) { + 0u8 => ConsensusMessage::Prevote( + height, + try!(rlp.val_at(2)), + try!(rlp.val_at(3)) + ), + 1 => ConsensusMessage::Precommit( + height, + try!(rlp.val_at(2)), + try!(rlp.val_at(3)) + ), + 2 => ConsensusMessage::Commit( + height, + try!(rlp.val_at(2))), + _ => return Err(DecoderError::Custom("Unknown step.")), + }) + } +} + +impl Encodable for ConsensusMessage { + fn rlp_append(&self, s: &mut RlpStream) { + match *self { + ConsensusMessage::Prevote(h, r, hash) => { + s.begin_list(4); + s.append(&h); + s.append(&0u8); + s.append(&r); + s.append(&hash); + }, + ConsensusMessage::Precommit(h, r, hash) => { + s.begin_list(4); + s.append(&h); + s.append(&1u8); + s.append(&r); + s.append(&hash); + }, + ConsensusMessage::Commit(h, hash) => { + s.begin_list(3); + s.append(&h); + s.append(&2u8); + s.append(&hash); + }, + } + } +} diff --git a/ethcore/src/engines/tendermint.rs b/ethcore/src/engines/tendermint/mod.rs similarity index 88% rename from ethcore/src/engines/tendermint.rs rename to ethcore/src/engines/tendermint/mod.rs index b88874b84..4ebaca347 100644 --- a/ethcore/src/engines/tendermint.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -16,8 +16,10 @@ //! Tendermint BFT consensus engine with round robin proof-of-authority. +mod message; +mod timeout; + use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; -use std::sync::Weak; use common::*; use rlp::{UntrustedRlp, View, encode}; use ethkey::{recover, public_to_address}; @@ -27,8 +29,9 @@ use spec::CommonParams; use engines::{Engine, EngineError, ProposeCollect}; use evm::Schedule; use ethjson; -use io::{IoContext, IoHandler, TimerToken, IoService}; -use time::get_time; +use io::IoService; +use self::message::ConsensusMessage; +use self::timeout::{TimerHandler, NextStep, DefaultTimeouts}; /// `Tendermint` params. #[derive(Debug, Clone)] @@ -63,9 +66,16 @@ enum Step { /// Precommit step storing the precommit vote and accumulating seal. Precommit(ProposeCollect, Seal), /// Commit step storing a complete valid seal. - Commit(H256, Seal) + Commit(BlockHash, Seal) } +pub type Height = usize; +pub type Round = usize; +pub type BlockHash = H256; + +pub type AtomicMs = AtomicUsize; +type Seal = Vec; + impl From for TendermintParams { fn from(p: ethjson::spec::TendermintParams) -> Self { let val: Vec<_> = p.validators.into_iter().map(Into::into).collect(); @@ -79,15 +89,12 @@ impl From for TendermintParams { } } -#[derive(Clone)] -struct StepMessage; - /// Engine using `Tendermint` consensus algorithm, suitable for EVM chain. pub struct Tendermint { params: CommonParams, our_params: TendermintParams, builtins: BTreeMap, - timeout_service: IoService, + timeout_service: IoService, /// Consensus round. r: AtomicUsize, /// Consensus step. @@ -98,25 +105,21 @@ pub struct Tendermint { proposer_nonce: AtomicUsize, } -struct TimerHandler { - engine: Weak, -} - impl Tendermint { /// Create a new instance of Tendermint engine pub fn new(params: CommonParams, our_params: TendermintParams, builtins: BTreeMap) -> Arc { let engine = Arc::new( Tendermint { params: params, - timeout: AtomicUsize::new(our_params.timeouts.propose), + timeout: AtomicUsize::new(our_params.timeouts.propose()), our_params: our_params, builtins: builtins, - timeout_service: IoService::::start().expect("Error creating engine timeout service"), + timeout_service: IoService::::start().expect("Error creating engine timeout service"), r: AtomicUsize::new(0), s: RwLock::new(Step::Propose), proposer_nonce: AtomicUsize::new(0) }); - let handler = TimerHandler { engine: Arc::downgrade(&engine) }; + let handler = TimerHandler::new(Arc::downgrade(&engine)); engine.timeout_service.register_handler(Arc::new(handler)).expect("Error creating engine timeout service"); engine } @@ -134,7 +137,7 @@ impl Tendermint { self.our_params.validators.contains(address) } - fn new_vote(&self, proposal: H256) -> ProposeCollect { + fn new_vote(&self, proposal: BlockHash) -> ProposeCollect { ProposeCollect::new(proposal, self.our_params.validators.iter().cloned().collect(), self.threshold()) @@ -163,7 +166,7 @@ impl Tendermint { Ok(message.as_raw().to_vec()) } - fn to_prevote(&self, proposal: H256) { + fn to_prevote(&self, proposal: BlockHash) { trace!(target: "tendermint", "step: entering prevote"); println!("step: entering prevote"); // Proceed to the prevote step. @@ -195,7 +198,7 @@ impl Tendermint { Ok(message.as_raw().to_vec()) } - fn to_precommit(&self, proposal: H256) { + fn to_precommit(&self, proposal: BlockHash) { trace!(target: "tendermint", "step: entering precommit"); println!("step: entering precommit"); self.to_step(Step::Precommit(self.new_vote(proposal), Vec::new())); @@ -349,72 +352,6 @@ impl Engine for Tendermint { } } -/// Base timeout of each step in ms. -#[derive(Debug, Clone)] -struct DefaultTimeouts { - propose: Ms, - prevote: Ms, - precommit: Ms, - commit: Ms -} - -impl Default for DefaultTimeouts { - fn default() -> Self { - DefaultTimeouts { - propose: 1000, - prevote: 1000, - precommit: 1000, - commit: 1000 - } - } -} - -type Ms = usize; -type Seal = Vec; -type AtomicMs = AtomicUsize; - -/// Timer token representing the consensus step timeouts. -pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 0; - -impl IoHandler for TimerHandler { - fn initialize(&self, io: &IoContext) { - if let Some(engine) = self.engine.upgrade() { - io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Error registering engine timeout"); - } - } - - fn timeout(&self, io: &IoContext, timer: TimerToken) { - if timer == ENGINE_TIMEOUT_TOKEN { - if let Some(engine) = self.engine.upgrade() { - println!("Timeout: {:?}", get_time()); - // Can you release entering a clause? - let next_step = match *engine.s.try_read().unwrap() { - Step::Propose => Step::Propose, - Step::Prevote(_) => Step::Propose, - Step::Precommit(_, _) => Step::Propose, - Step::Commit(_, _) => { - engine.r.fetch_add(1, AtomicOrdering::Relaxed); - Step::Propose - }, - }; - match next_step { - Step::Propose => engine.to_propose(), - _ => (), - } - io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Failed to restart consensus step timer.") - } - } - } - - fn message(&self, io: &IoContext, _net_message: &StepMessage) { - if let Some(engine) = self.engine.upgrade() { - println!("Message: {:?}", get_time().sec); - io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to restart consensus step timer."); - io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Failed to restart consensus step timer.") - } - } -} - #[cfg(test)] mod tests { use common::*; diff --git a/ethcore/src/engines/tendermint/timeout.rs b/ethcore/src/engines/tendermint/timeout.rs new file mode 100644 index 000000000..979c08a39 --- /dev/null +++ b/ethcore/src/engines/tendermint/timeout.rs @@ -0,0 +1,104 @@ +// 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 . + +//! Tendermint BFT consensus engine with round robin proof-of-authority. + +use std::sync::atomic::{Ordering as AtomicOrdering}; +use std::sync::Weak; +use io::{IoContext, IoHandler, TimerToken}; +use super::{Tendermint, Step}; +use time::get_time; + +pub struct TimerHandler { + engine: Weak, +} + +impl TimerHandler { + pub fn new(engine: Weak) -> Self { + TimerHandler { engine: engine } + } +} + +/// Base timeout of each step in ms. +#[derive(Debug, Clone)] +pub struct DefaultTimeouts { + propose: Ms, + prevote: Ms, + precommit: Ms, + commit: Ms +} + +impl DefaultTimeouts { + pub fn propose(&self) -> usize { self.propose } +} + +impl Default for DefaultTimeouts { + fn default() -> Self { + DefaultTimeouts { + propose: 1000, + prevote: 1000, + precommit: 1000, + commit: 1000 + } + } +} + +type Ms = usize; + +#[derive(Clone)] +pub struct NextStep; + +/// Timer token representing the consensus step timeouts. +pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 0; + +impl IoHandler for TimerHandler { + fn initialize(&self, io: &IoContext) { + if let Some(engine) = self.engine.upgrade() { + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Error registering engine timeout"); + } + } + + fn timeout(&self, io: &IoContext, timer: TimerToken) { + if timer == ENGINE_TIMEOUT_TOKEN { + if let Some(engine) = self.engine.upgrade() { + println!("Timeout: {:?}", get_time()); + // Can you release entering a clause? + let next_step = match *engine.s.try_read().unwrap() { + Step::Propose => Step::Propose, + Step::Prevote(_) => Step::Propose, + Step::Precommit(_, _) => Step::Propose, + Step::Commit(_, _) => { + engine.r.fetch_add(1, AtomicOrdering::Relaxed); + Step::Propose + }, + }; + match next_step { + Step::Propose => engine.to_propose(), + _ => (), + } + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Failed to restart consensus step timer.") + } + } + } + + fn message(&self, io: &IoContext, _net_message: &NextStep) { + if let Some(engine) = self.engine.upgrade() { + println!("Message: {:?}", get_time().sec); + io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to restart consensus step timer."); + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Failed to restart consensus step timer.") + } + } +} From d0851462543b94c5b9cee5e61abd4302810fc8d4 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 29 Sep 2016 15:32:49 +0100 Subject: [PATCH 030/295] separate params out --- ethcore/src/engines/tendermint/mod.rs | 44 ++--------------- ethcore/src/engines/tendermint/params.rs | 60 ++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 41 deletions(-) create mode 100644 ethcore/src/engines/tendermint/params.rs diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 4ebaca347..e6ee0f387 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -18,6 +18,7 @@ mod message; mod timeout; +mod params; use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use common::*; @@ -28,36 +29,10 @@ use block::*; use spec::CommonParams; use engines::{Engine, EngineError, ProposeCollect}; use evm::Schedule; -use ethjson; use io::IoService; use self::message::ConsensusMessage; -use self::timeout::{TimerHandler, NextStep, DefaultTimeouts}; - -/// `Tendermint` params. -#[derive(Debug, Clone)] -pub struct TendermintParams { - /// Gas limit divisor. - pub gas_limit_bound_divisor: U256, - /// List of validators. - pub validators: Vec
, - /// Number of validators. - pub validator_n: usize, - /// Timeout durations for different steps. - timeouts: DefaultTimeouts, -} - -impl Default for TendermintParams { - fn default() -> Self { - let validators = vec!["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e".into(), "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1".into()]; - let val_n = validators.len(); - TendermintParams { - gas_limit_bound_divisor: 0x0400.into(), - validators: validators, - validator_n: val_n, - timeouts: DefaultTimeouts::default() - } - } -} +use self::timeout::{TimerHandler, NextStep}; +use self::params::TendermintParams; #[derive(Debug)] enum Step { @@ -76,19 +51,6 @@ pub type BlockHash = H256; pub type AtomicMs = AtomicUsize; type Seal = Vec; -impl From for TendermintParams { - fn from(p: ethjson::spec::TendermintParams) -> Self { - let val: Vec<_> = p.validators.into_iter().map(Into::into).collect(); - let val_n = val.len(); - TendermintParams { - gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), - validators: val, - validator_n: val_n, - timeouts: DefaultTimeouts::default() - } - } -} - /// Engine using `Tendermint` consensus algorithm, suitable for EVM chain. pub struct Tendermint { params: CommonParams, diff --git a/ethcore/src/engines/tendermint/params.rs b/ethcore/src/engines/tendermint/params.rs new file mode 100644 index 000000000..2a23cbb27 --- /dev/null +++ b/ethcore/src/engines/tendermint/params.rs @@ -0,0 +1,60 @@ +// 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 . + +//! Tendermint BFT consensus engine with round robin proof-of-authority. + +use common::{Address, U256}; +use ethjson; +use super::timeout::DefaultTimeouts; + +/// `Tendermint` params. +#[derive(Debug, Clone)] +pub struct TendermintParams { + /// Gas limit divisor. + pub gas_limit_bound_divisor: U256, + /// List of validators. + pub validators: Vec
, + /// Number of validators. + pub validator_n: usize, + /// Timeout durations for different steps. + pub timeouts: DefaultTimeouts, +} + +impl Default for TendermintParams { + fn default() -> Self { + let validators = vec!["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e".into(), "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1".into()]; + let val_n = validators.len(); + TendermintParams { + gas_limit_bound_divisor: 0x0400.into(), + validators: validators, + validator_n: val_n, + timeouts: DefaultTimeouts::default() + } + } +} + +impl From for TendermintParams { + fn from(p: ethjson::spec::TendermintParams) -> Self { + let val: Vec<_> = p.validators.into_iter().map(Into::into).collect(); + let val_n = val.len(); + TendermintParams { + gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), + validators: val, + validator_n: val_n, + timeouts: DefaultTimeouts::default() + } + } +} From d59e9e816e84821057bb45bb671b5340f737446a Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 29 Sep 2016 16:57:52 +0100 Subject: [PATCH 031/295] fix tests compilation --- ethcore/src/engines/tendermint/message.rs | 1 + ethcore/src/engines/tendermint/mod.rs | 35 +++++++++++------------ ethcore/src/engines/tendermint/params.rs | 2 +- ethcore/src/engines/tendermint/timeout.rs | 16 ++++------- ethcore/src/spec/spec.rs | 6 ++++ 5 files changed, 31 insertions(+), 29 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 86691b476..b14a07c1e 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -19,6 +19,7 @@ use super::{Height, Round, BlockHash}; use rlp::{View, DecoderError, Decodable, Decoder, Encodable, RlpStream, Stream}; +#[derive(Debug)] pub enum ConsensusMessage { Prevote(Height, Round, BlockHash), Precommit(Height, Round, BlockHash), diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index e6ee0f387..4a9f76ac7 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -73,7 +73,7 @@ impl Tendermint { let engine = Arc::new( Tendermint { params: params, - timeout: AtomicUsize::new(our_params.timeouts.propose()), + timeout: AtomicUsize::new(our_params.timeouts.propose), our_params: our_params, builtins: builtins, timeout_service: IoService::::start().expect("Error creating engine timeout service"), @@ -244,6 +244,8 @@ impl Engine for Tendermint { } fn handle_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { + let c: ConsensusMessage = try!(message.as_val()); + println!("{:?}", c); // Check if correct round. if self.r.load(AtomicOrdering::Relaxed) != try!(message.val_at(0)) { try!(Err(EngineError::WrongRound)) @@ -325,11 +327,8 @@ mod tests { use account_provider::AccountProvider; use spec::Spec; use engines::{Engine, EngineError}; - use super::{Tendermint, TendermintParams}; - - /// Create a new test chain spec with `Tendermint` consensus engine. - /// Account "0".sha3() and "1".sha3() are a validators. - fn new_test_tendermint() -> Spec { Spec::load(include_bytes!("../../res/tendermint.json")) } + use super::Tendermint; + use super::params::TendermintParams; fn propose_default(engine: &Arc, round: u8, proposer: Address) -> Result { let mut s = RlpStream::new_list(3); @@ -372,14 +371,14 @@ mod tests { #[test] fn has_valid_metadata() { - let engine = new_test_tendermint().engine; + let engine = Spec::new_test_tendermint().engine; assert!(!engine.name().is_empty()); assert!(engine.version().major >= 1); } #[test] fn can_return_schedule() { - let engine = new_test_tendermint().engine; + let engine = Spec::new_test_tendermint().engine; let schedule = engine.schedule(&EnvInfo { number: 10000000, author: 0.into(), @@ -395,7 +394,7 @@ mod tests { #[test] fn verification_fails_on_short_seal() { - let engine = new_test_tendermint().engine; + let engine = Spec::new_test_tendermint().engine; let header: Header = Header::default(); let verify_result = engine.verify_block_basic(&header, None); @@ -409,7 +408,7 @@ mod tests { #[test] fn verification_fails_on_wrong_signatures() { - let engine = new_test_tendermint().engine; + let engine = Spec::new_test_tendermint().engine; let mut header = Header::default(); let tap = AccountProvider::transient_provider(); @@ -445,7 +444,7 @@ mod tests { #[test] fn seal_with_enough_signatures_is_ok() { - let engine = new_test_tendermint().engine; + let engine = Spec::new_test_tendermint().engine; let mut header = Header::default(); let seal = good_seal(&header); @@ -460,7 +459,7 @@ mod tests { #[test] fn can_generate_seal() { - let spec = new_test_tendermint(); + let spec = Spec::new_test_tendermint(); let ref engine = *spec.engine; let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()); @@ -480,7 +479,7 @@ mod tests { #[test] fn propose_step() { - let engine = new_test_tendermint().engine; + let engine = Spec::new_test_tendermint().engine; let tap = AccountProvider::transient_provider(); let r = 0; @@ -499,7 +498,7 @@ mod tests { #[test] fn proposer_switching() { - let engine = new_test_tendermint().engine; + let engine = Spec::new_test_tendermint().engine; let tap = AccountProvider::transient_provider(); // Currently not a proposer. @@ -514,7 +513,7 @@ mod tests { #[test] fn prevote_step() { - let engine = new_test_tendermint().engine; + let engine = Spec::new_test_tendermint().engine; let tap = AccountProvider::transient_provider(); let r = 0; @@ -533,7 +532,7 @@ mod tests { #[test] fn precommit_step() { - let engine = new_test_tendermint().engine; + let engine = Spec::new_test_tendermint().engine; let tap = AccountProvider::transient_provider(); let r = 0; @@ -553,7 +552,7 @@ mod tests { #[test] fn timeout_switching() { let tender = { - let engine = new_test_tendermint().engine; + let engine = Spec::new_test_tendermint().engine; Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()) }; @@ -563,7 +562,7 @@ mod tests { #[test] fn increments_round() { - let spec = new_test_tendermint(); + let spec = Spec::new_test_tendermint(); let ref engine = *spec.engine; let def_params = TendermintParams::default(); let tender = Tendermint::new(engine.params().clone(), def_params.clone(), BTreeMap::new()); diff --git a/ethcore/src/engines/tendermint/params.rs b/ethcore/src/engines/tendermint/params.rs index 2a23cbb27..95c6be85d 100644 --- a/ethcore/src/engines/tendermint/params.rs +++ b/ethcore/src/engines/tendermint/params.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//! Tendermint BFT consensus engine with round robin proof-of-authority. +//! Tendermint specific parameters. use common::{Address, U256}; use ethjson; diff --git a/ethcore/src/engines/tendermint/timeout.rs b/ethcore/src/engines/tendermint/timeout.rs index 979c08a39..47840d8b7 100644 --- a/ethcore/src/engines/tendermint/timeout.rs +++ b/ethcore/src/engines/tendermint/timeout.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//! Tendermint BFT consensus engine with round robin proof-of-authority. +//! Tendermint timeout handling. use std::sync::atomic::{Ordering as AtomicOrdering}; use std::sync::Weak; @@ -35,14 +35,10 @@ impl TimerHandler { /// Base timeout of each step in ms. #[derive(Debug, Clone)] pub struct DefaultTimeouts { - propose: Ms, - prevote: Ms, - precommit: Ms, - commit: Ms -} - -impl DefaultTimeouts { - pub fn propose(&self) -> usize { self.propose } + pub propose: Ms, + pub prevote: Ms, + pub precommit: Ms, + pub commit: Ms } impl Default for DefaultTimeouts { @@ -56,7 +52,7 @@ impl Default for DefaultTimeouts { } } -type Ms = usize; +pub type Ms = usize; #[derive(Clone)] pub struct NextStep; diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index f02136e0c..c24849cba 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -266,6 +266,12 @@ 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 Tendermint consensus which does internal sealing (not requiring work). + /// Account "0".sha3() and "1".sha3() are a validators. + pub fn new_test_tendermint() -> Self { + Spec::load(include_bytes!("../../res/tendermint.json") as &[u8]).expect("tendermint.json is invalid") + } } #[cfg(test)] From 8a51ae02aab37d2a85bc47595e36f973904c657a Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 30 Sep 2016 12:22:46 +0100 Subject: [PATCH 032/295] simplify seal --- ethcore/src/engines/tendermint/mod.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 4a9f76ac7..8d92d1828 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -204,7 +204,7 @@ impl Engine for Tendermint { fn name(&self) -> &str { "Tendermint" } fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } /// Possibly signatures of all validators. - fn seal_fields(&self) -> usize { self.our_params.validator_n } + fn seal_fields(&self) -> usize { 2 } fn params(&self) -> &CommonParams { &self.params } fn builtins(&self) -> &BTreeMap { &self.builtins } @@ -260,12 +260,13 @@ impl Engine for Tendermint { } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { - if header.seal().len() <= self.threshold() { - Err(From::from(BlockError::InvalidSealArity( - Mismatch { expected: self.threshold(), found: header.seal().len() } - ))) - } else { + let seal_length = header.seal().len(); + if seal_length == self.seal_fields() { Ok(()) + } else { + Err(From::from(BlockError::InvalidSealArity( + Mismatch { expected: self.seal_fields(), found: seal_length } + ))) } } From 76d7ec84bb7b6748a08dfe936130a82e5ad98850 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 30 Sep 2016 14:43:52 +0100 Subject: [PATCH 033/295] new block ordering engine method --- ethcore/src/engines/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index ff8f13ebd..c29a094d2 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -156,5 +156,8 @@ 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); } + /// Check if new block should be chosen as the one in chain. + fn is_new_best_block(&self, parent_details: BlockDetails, best_header: HeaderView, new_header: HeaderView) -> bool; + // TODO: sealing stuff - though might want to leave this for later. } From 67c24dcb95cda6339666608f7f9949e80329784d Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 5 Oct 2016 14:29:35 +0100 Subject: [PATCH 034/295] use Engine to order blockchain --- ethcore/src/blockchain/blockchain.rs | 48 ++++++++++++++++------------ ethcore/src/client/client.rs | 6 ++-- ethcore/src/snapshot/service.rs | 5 +-- ethcore/src/snapshot/tests/blocks.rs | 16 ++++++---- 4 files changed, 44 insertions(+), 31 deletions(-) diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index 8daf672b9..d599f957f 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -33,6 +33,7 @@ use blockchain::update::ExtrasUpdate; use blockchain::{CacheSize, ImportRoute, Config}; use db::{self, Writable, Readable, CacheUpdatePolicy}; use cache_manager::CacheManager; +use engines::Engine; const LOG_BLOOMS_LEVELS: usize = 3; const LOG_BLOOMS_ELEMENTS_PER_INDEX: usize = 16; @@ -182,6 +183,8 @@ pub struct BlockChain { pending_best_block: RwLock>, pending_block_hashes: RwLock>, pending_transaction_addresses: RwLock>>, + + engine: Arc, } impl BlockProvider for BlockChain { @@ -387,8 +390,8 @@ impl<'a> Iterator for AncestryIter<'a> { } impl BlockChain { - /// Create new instance of blockchain from given Genesis - pub fn new(config: Config, genesis: &[u8], db: Arc) -> BlockChain { + /// Create new instance of blockchain from given Genesis and block picking rules of Engine. + pub fn new(config: Config, genesis: &[u8], db: Arc, engine: Arc) -> BlockChain { // 400 is the avarage size of the key let cache_man = CacheManager::new(config.pref_cache_size, config.max_cache_size, 400); @@ -411,6 +414,7 @@ impl BlockChain { pending_best_block: RwLock::new(None), pending_block_hashes: RwLock::new(HashMap::new()), pending_transaction_addresses: RwLock::new(HashMap::new()), + engine: engine, }; // load best block @@ -799,13 +803,12 @@ impl BlockChain { let number = header.number(); let parent_hash = header.parent_hash(); let parent_details = self.block_details(&parent_hash).unwrap_or_else(|| panic!("Invalid parent hash: {:?}", parent_hash)); - let total_difficulty = parent_details.total_difficulty + header.difficulty(); - let is_new_best = total_difficulty > self.best_block_total_difficulty(); + let is_new_best = self.engine.is_new_best_block(self.best_block_total_difficulty(), HeaderView::new(&self.best_block_header()), &parent_details, header); BlockInfo { hash: hash, number: number, - total_difficulty: total_difficulty, + total_difficulty: parent_details.total_difficulty + header.difficulty(), location: if is_new_best { // on new best block we need to make sure that all ancestors // are moved to "canon chain" @@ -1226,11 +1229,16 @@ mod tests { use views::BlockView; use transaction::{Transaction, Action}; use log_entry::{LogEntry, LocalizedLogEntry}; + use spec::Spec; fn new_db(path: &str) -> Arc { Arc::new(Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), path).unwrap()) } + fn new_chain(genesis: &[u8], db: Arc) -> BlockChain { + BlockChain::new(Config::default(), genesis, db, Spec::new_null().engine) + } + #[test] fn should_cache_best_block() { // given @@ -1241,7 +1249,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); assert_eq!(bc.best_block_number(), 0); // when @@ -1267,7 +1275,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); assert_eq!(bc.genesis_hash(), genesis_hash.clone()); assert_eq!(bc.best_block_hash(), genesis_hash.clone()); @@ -1298,7 +1306,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let mut block_hashes = vec![genesis_hash.clone()]; let mut batch = db.transaction(); @@ -1334,7 +1342,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let mut batch =db.transaction(); for b in &[&b1a, &b1b, &b2a, &b2b, &b3a, &b3b, &b4a, &b4b, &b5a, &b5b] { @@ -1396,7 +1404,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let mut batch = db.transaction(); let _ = bc.insert_block(&mut batch, &b1a, vec![]); @@ -1484,7 +1492,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let mut batch = db.transaction(); let _ = bc.insert_block(&mut batch, &b1a, vec![]); @@ -1546,7 +1554,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let mut batch = db.transaction(); let ir1 = bc.insert_block(&mut batch, &b1, vec![]); @@ -1662,7 +1670,7 @@ mod tests { let temp = RandomTempPath::new(); { let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); assert_eq!(bc.best_block_hash(), genesis_hash); let mut batch =db.transaction(); bc.insert_block(&mut batch, &first, vec![]); @@ -1673,7 +1681,7 @@ mod tests { { let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); assert_eq!(bc.best_block_hash(), first_hash); } @@ -1728,7 +1736,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let mut batch =db.transaction(); bc.insert_block(&mut batch, &b1, vec![]); db.write(batch).unwrap(); @@ -1788,7 +1796,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); insert_block(&db, &bc, &b1, vec![Receipt { state_root: H256::default(), gas_used: 10_000.into(), @@ -1892,7 +1900,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let blocks_b1 = bc.blocks_with_bloom(&bloom_b1, 0, 5); let blocks_b2 = bc.blocks_with_bloom(&bloom_b2, 0, 5); @@ -1949,7 +1957,7 @@ mod tests { { let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let uncle = canon_chain.fork(1).generate(&mut finalizer.fork()).unwrap(); let mut batch =db.transaction(); @@ -1968,7 +1976,7 @@ mod tests { // re-loading the blockchain should load the correct best block. let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); assert_eq!(bc.best_block_number(), 5); } @@ -1985,7 +1993,7 @@ mod tests { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(Config::default(), &genesis, db.clone()); + let bc = new_chain(&genesis, db.clone()); let mut batch =db.transaction(); bc.insert_block(&mut batch, &first, vec![]); diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 541bd7872..8fc13cfb7 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -23,7 +23,7 @@ use time::precise_time_ns; // util use util::{Bytes, PerfTimer, Itertools, Mutex, RwLock}; -use util::journaldb::{self, JournalDB}; +use util::journaldb; use util::{U256, H256, H520, Address, H2048, Uint}; use util::sha3::*; use util::TrieFactory; @@ -170,7 +170,7 @@ impl Client { let gb = spec.genesis_block(); let db = Arc::new(try!(Database::open(&db_config, &path.to_str().unwrap()).map_err(ClientError::Database))); - let chain = Arc::new(BlockChain::new(config.blockchain.clone(), &gb, db.clone())); + let chain = Arc::new(BlockChain::new(config.blockchain.clone(), &gb, db.clone(), spec.engine.clone())); let tracedb = RwLock::new(TraceDB::new(config.tracing.clone(), db.clone(), chain.clone())); let journal_db = journaldb::new(db.clone(), config.pruning, ::db::COL_STATE); @@ -689,7 +689,7 @@ impl snapshot::DatabaseRestore for Client { try!(db.restore(new_db)); *state_db = StateDB::new(journaldb::new(db.clone(), self.pruning, ::db::COL_STATE)); - *chain = Arc::new(BlockChain::new(self.config.blockchain.clone(), &[], db.clone())); + *chain = Arc::new(BlockChain::new(self.config.blockchain.clone(), &[], db.clone(), self.engine.clone())); *tracedb = TraceDB::new(self.config.tracing.clone(), db.clone(), chain.clone()); Ok(()) } diff --git a/ethcore/src/snapshot/service.rs b/ethcore/src/snapshot/service.rs index 5243a4792..303612273 100644 --- a/ethcore/src/snapshot/service.rs +++ b/ethcore/src/snapshot/service.rs @@ -32,6 +32,7 @@ use engines::Engine; use error::Error; use ids::BlockID; use service::ClientIoMessage; +use spec::Spec; use io::IoChannel; @@ -97,7 +98,7 @@ impl Restoration { let raw_db = Arc::new(try!(Database::open(params.db_config, &*params.db_path.to_string_lossy()) .map_err(UtilError::SimpleString))); - let chain = BlockChain::new(Default::default(), params.genesis, raw_db.clone()); + let chain = BlockChain::new(Default::default(), params.genesis, raw_db.clone(), Spec::new_null().engine); let blocks = try!(BlockRebuilder::new(chain, manifest.block_number)); let root = manifest.state_root.clone(); @@ -629,4 +630,4 @@ mod tests { service.restore_state_chunk(Default::default(), vec![]); service.restore_block_chunk(Default::default(), vec![]); } -} \ No newline at end of file +} diff --git a/ethcore/src/snapshot/tests/blocks.rs b/ethcore/src/snapshot/tests/blocks.rs index 6c4344b6e..3c8744bcc 100644 --- a/ethcore/src/snapshot/tests/blocks.rs +++ b/ethcore/src/snapshot/tests/blocks.rs @@ -28,6 +28,8 @@ use util::kvdb::{Database, DatabaseConfig}; use std::sync::Arc; +use spec::Spec; + fn chunk_and_restore(amount: u64) { let mut canon_chain = ChainGenerator::default(); let mut finalizer = BlockFinalizer::default(); @@ -39,8 +41,10 @@ fn chunk_and_restore(amount: u64) { let mut snapshot_path = new_path.as_path().to_owned(); snapshot_path.push("SNAP"); + let new_chain = |db| BlockChain::new(Default::default(), &genesis, db, Spec::new_null().engine); + let old_db = Arc::new(Database::open(&db_cfg, orig_path.as_str()).unwrap()); - let bc = BlockChain::new(Default::default(), &genesis, old_db.clone()); + let bc = new_chain(old_db.clone()); // build the blockchain. let mut batch = old_db.transaction(); @@ -67,9 +71,9 @@ fn chunk_and_restore(amount: u64) { }).unwrap(); // restore it. - let new_db = Arc::new(Database::open(&db_cfg, new_path.as_str()).unwrap()); - let new_chain = BlockChain::new(Default::default(), &genesis, new_db.clone()); - let mut rebuilder = BlockRebuilder::new(new_chain, amount).unwrap(); + let restored_db = Arc::new(Database::open(&db_cfg, new_path.as_str()).unwrap()); + let restored_chain = new_chain(restored_db.clone()); + let mut rebuilder = BlockRebuilder::new(restored_chain, amount).unwrap(); let reader = PackedReader::new(&snapshot_path).unwrap().unwrap(); let engine = ::engines::NullEngine::new(Default::default(), Default::default()); for chunk_hash in &reader.manifest().block_hashes { @@ -81,8 +85,8 @@ fn chunk_and_restore(amount: u64) { rebuilder.glue_chunks(); // and test it. - let new_chain = BlockChain::new(Default::default(), &genesis, new_db); - assert_eq!(new_chain.best_block_hash(), best_hash); + let restored_chain = new_chain(restored_db); + assert_eq!(restored_chain.best_block_hash(), best_hash); } #[test] From a03db2ff29c7e36d4a60e6dec5be5296b59eda7f Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 5 Oct 2016 14:30:44 +0100 Subject: [PATCH 035/295] add is_new_best method to engines --- ethcore/src/engines/mod.rs | 6 +++++- ethcore/src/ethereum/ethash.rs | 12 ++++++++++++ ethcore/src/tests/helpers.rs | 6 +++--- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index c29a094d2..0be507387 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -36,6 +36,8 @@ use account_provider::AccountProvider; use block::ExecutedBlock; use spec::CommonParams; use evm::Schedule; +use ethereum::ethash; +use blockchain::extras::BlockDetails; /// Voting errors. #[derive(Debug)] @@ -157,7 +159,9 @@ pub trait Engine : Sync + Send { fn execute_builtin(&self, a: &Address, input: &[u8], output: &mut BytesRef) { self.builtins().get(a).unwrap().execute(input, output); } /// Check if new block should be chosen as the one in chain. - fn is_new_best_block(&self, parent_details: BlockDetails, best_header: HeaderView, new_header: HeaderView) -> bool; + fn is_new_best_block(&self, best_total_difficulty: U256, _best_header: HeaderView, parent_details: &BlockDetails, new_header: &HeaderView) -> bool { + ethash::is_new_best_block(best_total_difficulty, parent_details, new_header) + } // TODO: sealing stuff - though might want to leave this for later. } diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index 982698a50..a1dfd0a40 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -22,6 +22,7 @@ use engines::Engine; use evm::Schedule; use ethjson; use rlp::{self, UntrustedRlp, View}; +use blockchain::extras::BlockDetails; /// Ethash params. #[derive(Debug, PartialEq)] @@ -273,6 +274,11 @@ impl Engine for Ethash { fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { t.sender().map(|_|()) // Perform EC recovery and cache sender } + + /// Check if new block should be chosen as the one in chain. + fn is_new_best_block(&self, best_total_difficulty: U256, _best_header: HeaderView, parent_details: &BlockDetails, new_header: &HeaderView) -> bool { + is_new_best_block(best_total_difficulty, parent_details, new_header) + } } #[cfg_attr(feature="dev", allow(wrong_self_convention))] // to_ethash should take self @@ -347,6 +353,12 @@ impl Ethash { } } +/// Check if a new block should replace the best blockchain block. +pub fn is_new_best_block(best_total_difficulty: U256, parent_details: &BlockDetails, new_header: &HeaderView) -> bool { + parent_details.total_difficulty + new_header.difficulty() > best_total_difficulty +} + + impl Header { /// Get the none field of the header. pub fn nonce(&self) -> H64 { diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index 6504ef8a9..11cd152dc 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -277,7 +277,7 @@ fn new_db(path: &str) -> Arc { pub fn generate_dummy_blockchain(block_number: u32) -> GuardedTempResult { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone()); + let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone(), Spec::new_null().engine); let mut batch = db.transaction(); for block_order in 1..block_number { @@ -295,7 +295,7 @@ pub fn generate_dummy_blockchain(block_number: u32) -> GuardedTempResult GuardedTempResult { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone()); + let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone(), Spec::new_null().engine); let mut batch = db.transaction(); @@ -314,7 +314,7 @@ pub fn generate_dummy_blockchain_with_extra(block_number: u32) -> GuardedTempRes pub fn generate_dummy_empty_blockchain() -> GuardedTempResult { let temp = RandomTempPath::new(); let db = new_db(temp.as_str()); - let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone()); + let bc = BlockChain::new(BlockChainConfig::default(), &create_unverifiable_block(0, H256::zero()), db.clone(), Spec::new_null().engine); GuardedTempResult:: { _temp: temp, From 64d7bcbd0c325ad8502162f98a54ceee68de70bb Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 5 Oct 2016 14:31:31 +0100 Subject: [PATCH 036/295] validators -> authorities --- ethcore/res/tendermint.json | 2 +- ethcore/src/spec/spec.rs | 2 +- json/src/spec/tendermint.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index 8aa8f24f7..2f40d707b 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -4,7 +4,7 @@ "Tendermint": { "params": { "gasLimitBoundDivisor": "0x0400", - "validators" : [ + "authorities" : [ "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" ] diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index d86f7ec46..5b861c25e 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -279,7 +279,7 @@ impl Spec { } /// Create a new Spec with Tendermint consensus which does internal sealing (not requiring work). - /// Account "0".sha3() and "1".sha3() are a validators. + /// Account "0".sha3() and "1".sha3() are a authorities. pub fn new_test_tendermint() -> Self { Spec::load(include_bytes!("../../res/tendermint.json") as &[u8]).expect("tendermint.json is invalid") } diff --git a/json/src/spec/tendermint.rs b/json/src/spec/tendermint.rs index 97c30fbb2..3d1a5a06d 100644 --- a/json/src/spec/tendermint.rs +++ b/json/src/spec/tendermint.rs @@ -26,7 +26,7 @@ pub struct TendermintParams { #[serde(rename="gasLimitBoundDivisor")] pub gas_limit_bound_divisor: Uint, /// Valid authorities - pub validators: Vec
, + pub authorities: Vec
, } /// Tendermint engine deserialization. @@ -46,7 +46,7 @@ mod tests { let s = r#"{ "params": { "gasLimitBoundDivisor": "0x0400", - "validators" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + "authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] } }"#; From cb2c9938a1467ee3b60efac4f47f75275f7aceca Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 5 Oct 2016 14:32:15 +0100 Subject: [PATCH 037/295] keep author as validator --- ethcore/src/engines/tendermint/params.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ethcore/src/engines/tendermint/params.rs b/ethcore/src/engines/tendermint/params.rs index 95c6be85d..cf7a90931 100644 --- a/ethcore/src/engines/tendermint/params.rs +++ b/ethcore/src/engines/tendermint/params.rs @@ -25,22 +25,22 @@ use super::timeout::DefaultTimeouts; pub struct TendermintParams { /// Gas limit divisor. pub gas_limit_bound_divisor: U256, - /// List of validators. - pub validators: Vec
, - /// Number of validators. - pub validator_n: usize, + /// List of authorities. + pub authorities: Vec
, + /// Number of authorities. + pub authority_n: usize, /// Timeout durations for different steps. pub timeouts: DefaultTimeouts, } impl Default for TendermintParams { fn default() -> Self { - let validators = vec!["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e".into(), "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1".into()]; - let val_n = validators.len(); + let authorities = vec!["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e".into(), "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1".into()]; + let val_n = authorities.len(); TendermintParams { gas_limit_bound_divisor: 0x0400.into(), - validators: validators, - validator_n: val_n, + authorities: authorities, + authority_n: val_n, timeouts: DefaultTimeouts::default() } } @@ -48,12 +48,12 @@ impl Default for TendermintParams { impl From for TendermintParams { fn from(p: ethjson::spec::TendermintParams) -> Self { - let val: Vec<_> = p.validators.into_iter().map(Into::into).collect(); + let val: Vec<_> = p.authorities.into_iter().map(Into::into).collect(); let val_n = val.len(); TendermintParams { gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), - validators: val, - validator_n: val_n, + authorities: val, + authority_n: val_n, timeouts: DefaultTimeouts::default() } } From 096b71feb20cd2731a59cc5193143ebb71ce057e Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 5 Oct 2016 14:33:07 +0100 Subject: [PATCH 038/295] add Vote generation --- ethcore/src/basic_types.rs | 4 +- ethcore/src/engines/tendermint/mod.rs | 119 +++++++++++++++++-------- ethcore/src/engines/tendermint/vote.rs | 63 +++++++++++++ ethcore/src/header.rs | 9 +- 4 files changed, 151 insertions(+), 44 deletions(-) create mode 100644 ethcore/src/engines/tendermint/vote.rs diff --git a/ethcore/src/basic_types.rs b/ethcore/src/basic_types.rs index 5f6515c0d..e2a705dd7 100644 --- a/ethcore/src/basic_types.rs +++ b/ethcore/src/basic_types.rs @@ -25,10 +25,12 @@ pub type LogBloom = H2048; pub static ZERO_LOGBLOOM: LogBloom = H2048([0x00; 256]); #[cfg_attr(feature="dev", allow(enum_variant_names))] -/// Semantic boolean for when a seal/signature is included. +/// Enum for when a seal/signature is included. pub enum Seal { /// The seal/signature is included. With, /// The seal/signature is not included. Without, + /// First N fields of seal are included. + WithSome(usize), } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 8d92d1828..2af0ab781 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -19,6 +19,7 @@ mod message; mod timeout; mod params; +mod vote; use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use common::*; @@ -28,20 +29,22 @@ use account_provider::AccountProvider; use block::*; use spec::CommonParams; use engines::{Engine, EngineError, ProposeCollect}; +use blockchain::extras::BlockDetails; use evm::Schedule; use io::IoService; use self::message::ConsensusMessage; use self::timeout::{TimerHandler, NextStep}; use self::params::TendermintParams; +use self::vote::Vote; #[derive(Debug)] enum Step { Propose, Prevote(ProposeCollect), /// Precommit step storing the precommit vote and accumulating seal. - Precommit(ProposeCollect, Seal), + Precommit(ProposeCollect, Signatures), /// Commit step storing a complete valid seal. - Commit(BlockHash, Seal) + Commit(BlockHash, Signatures) } pub type Height = usize; @@ -49,7 +52,7 @@ pub type Round = usize; pub type BlockHash = H256; pub type AtomicMs = AtomicUsize; -type Seal = Vec; +type Signatures = Vec; /// Engine using `Tendermint` consensus algorithm, suitable for EVM chain. pub struct Tendermint { @@ -57,6 +60,8 @@ pub struct Tendermint { our_params: TendermintParams, builtins: BTreeMap, timeout_service: IoService, + /// Address to be used as authority. + authority: RwLock
, /// Consensus round. r: AtomicUsize, /// Consensus step. @@ -77,6 +82,7 @@ impl Tendermint { our_params: our_params, builtins: builtins, timeout_service: IoService::::start().expect("Error creating engine timeout service"), + authority: RwLock::new(Address::default()), r: AtomicUsize::new(0), s: RwLock::new(Step::Propose), proposer_nonce: AtomicUsize::new(0) @@ -86,22 +92,26 @@ impl Tendermint { engine } - fn proposer(&self) -> Address { - let ref p = self.our_params; - p.validators.get(self.proposer_nonce.load(AtomicOrdering::Relaxed)%p.validator_n).unwrap().clone() - } - fn is_proposer(&self, address: &Address) -> bool { - self.proposer() == *address + self.is_nonce_proposer(self.proposer_nonce.load(AtomicOrdering::SeqCst), address) } - fn is_validator(&self, address: &Address) -> bool { - self.our_params.validators.contains(address) + fn nonce_proposer(&self, proposer_nonce: usize) -> &Address { + let ref p = self.our_params; + p.authorities.get(proposer_nonce%p.authority_n).unwrap() + } + + fn is_nonce_proposer(&self, proposer_nonce: usize, address: &Address) -> bool { + self.nonce_proposer(proposer_nonce) == address + } + + fn is_authority(&self, address: &Address) -> bool { + self.our_params.authorities.contains(address) } fn new_vote(&self, proposal: BlockHash) -> ProposeCollect { ProposeCollect::new(proposal, - self.our_params.validators.iter().cloned().collect(), + self.our_params.authorities.iter().cloned().collect(), self.threshold()) } @@ -192,7 +202,7 @@ impl Tendermint { } fn threshold(&self) -> usize { - self.our_params.validator_n*2/3 + self.our_params.authority_n*2/3 } fn next_timeout(&self) -> u64 { @@ -203,8 +213,8 @@ impl Tendermint { impl Engine for Tendermint { fn name(&self) -> &str { "Tendermint" } fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } - /// Possibly signatures of all validators. - fn seal_fields(&self) -> usize { 2 } + /// (consensus round, proposal signature, authority signatures) + fn seal_fields(&self) -> usize { 3 } fn params(&self) -> &CommonParams { &self.params } fn builtins(&self) -> &BTreeMap { &self.builtins } @@ -229,34 +239,62 @@ impl Engine for Tendermint { }); } - /// Apply the block reward on finalisation of the block. + /// Get the address to be used as authority. + fn on_new_block(&self, block: &mut ExecutedBlock) { + if let Some(mut authority) = self.authority.try_write() { + *authority = *block.header().author() + } + } + + /// Set author to proposer. /// 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 using all available signatures. /// /// None is returned if not enough signatures can be collected. - fn generate_seal(&self, block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option> { - self.s.try_read().and_then(|s| match *s { - Step::Commit(hash, ref seal) if hash == block.header().bare_hash() => Some(seal.clone()), - _ => None, - }) + fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { + if let (Some(ap), Some(step)) = (accounts, self.s.try_read()) { + let header = block.header(); + let author = header.author(); + match *step { + Step::Commit(hash, ref seal) if hash == header.bare_hash() => + // Commit the block using a complete signature set. + return Some(seal.clone()), + Step::Propose if self.is_proposer(header.author()) => + // Seal block with propose signature. + if let Some(proposal) = Vote::propose(header, &ap) { + return Some(vec![::rlp::encode(&proposal).to_vec(), Vec::new()]) + } else { + trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable"); + }, + _ => {}, + } + } else { + trace!(target: "basicauthority", "generate_seal: FAIL: accounts not provided"); + } + None } fn handle_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { - let c: ConsensusMessage = try!(message.as_val()); - println!("{:?}", c); + let message: ConsensusMessage = try!(message.as_val()); + try!(Err(EngineError::UnknownStep)) + //match message { + // ConsensusMessage::Prevote + //} + + // Check if correct round. - if self.r.load(AtomicOrdering::Relaxed) != try!(message.val_at(0)) { - try!(Err(EngineError::WrongRound)) - } + //if self.r.load(AtomicOrdering::Relaxed) != try!(message.val_at(0)) { + // try!(Err(EngineError::WrongRound)) + //} // Handle according to step. - match try!(message.val_at(1)) { - 0u8 if self.is_proposer(&sender) => self.propose_message(try!(message.at(2))), - 1 if self.is_validator(&sender) => self.prevote_message(sender, try!(message.at(2))), - 2 if self.is_validator(&sender) => self.precommit_message(sender, signature, try!(message.at(2))), - _ => try!(Err(EngineError::UnknownStep)), - } +// match try!(message.val_at(1)) { +// 0u8 if self.is_proposer(&sender) => self.propose_message(try!(message.at(2))), +// 1 if self.is_authority(&sender) => self.prevote_message(sender, try!(message.at(2))), +// 2 if self.is_authority(&sender) => self.precommit_message(sender, signature, try!(message.at(2))), +// _ => try!(Err(EngineError::UnknownStep)), +// } } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { @@ -270,18 +308,19 @@ impl Engine for Tendermint { } } + /// Also transitions to Prevote if verifying Proposal. fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { let to_address = |b: &Vec| { let sig: H520 = try!(UntrustedRlp::new(b.as_slice()).as_val()); Ok(public_to_address(&try!(recover(&sig.into(), &header.bare_hash())))) }; - let validator_set = self.our_params.validators.iter().cloned().collect(); + let authority_set = self.our_params.authorities.iter().cloned().collect(); let seal_set = try!(header .seal() .iter() .map(to_address) .collect::, Error>>()); - if seal_set.intersection(&validator_set).count() <= self.threshold() { + if seal_set.intersection(&authority_set).count() <= self.threshold() { try!(Err(BlockError::InvalidSeal)) } else { Ok(()) @@ -315,6 +354,10 @@ impl Engine for Tendermint { fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { t.sender().map(|_|()) // Perform EC recovery and cache sender } + + fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { + new_header.seal().get(1).expect("Tendermint seal should have two elements.").len() > best_header.seal().get(1).expect("Tendermint seal should have two elements.").len() + } } #[cfg(test)] @@ -465,9 +508,9 @@ mod tests { let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()); let genesis_header = spec.genesis_header(); - let mut db_result = get_temp_journal_db(); + let mut db_result = get_temp_state_db(); let mut db = db_result.take(); - spec.ensure_db_good(db.as_hashdb_mut()).unwrap(); + spec.ensure_db_good(&mut db).unwrap(); let last_hashes = Arc::new(vec![genesis_header.hash()]); let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); @@ -484,8 +527,8 @@ mod tests { let tap = AccountProvider::transient_provider(); let r = 0; - let not_validator_addr = tap.insert_account("101".sha3(), "101").unwrap(); - assert!(propose_default(&engine, r, not_validator_addr).is_err()); + let not_authority_addr = tap.insert_account("101".sha3(), "101").unwrap(); + assert!(propose_default(&engine, r, not_authority_addr).is_err()); let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap(); assert!(propose_default(&engine, r, not_proposer_addr).is_err()); diff --git a/ethcore/src/engines/tendermint/vote.rs b/ethcore/src/engines/tendermint/vote.rs new file mode 100644 index 000000000..a11583201 --- /dev/null +++ b/ethcore/src/engines/tendermint/vote.rs @@ -0,0 +1,63 @@ +// 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 . + +//! Tendermint block seal. + +use common::{H256, Address, H520, Header}; +use util::Hashable; +use account_provider::AccountProvider; +use rlp::{View, DecoderError, Decodable, Decoder, Encodable, RlpStream, Stream}; +use basic_types::Seal; + +#[derive(Debug)] +pub struct Vote { + signature: H520 +} + +fn message(header: &Header) -> H256 { + header.rlp(Seal::WithSome(1)).sha3() +} + +impl Vote { + fn new(signature: H520) -> Vote { Vote { signature: signature }} + + /// Try to use the author address to create a vote. + pub fn propose(header: &Header, accounts: &AccountProvider) -> Option { + accounts.sign(*header.author(), message(&header)).ok().map(Into::into).map(Self::new) + } + + /// Use any unlocked validator account to create a vote. + pub fn validate(header: &Header, accounts: &AccountProvider, validator: Address) -> Option { + accounts.sign(validator, message(&header)).ok().map(Into::into).map(Self::new) + } +} + +impl Decodable for Vote { + fn decode(decoder: &D) -> Result where D: Decoder { + let rlp = decoder.as_rlp(); + if decoder.as_raw().len() != try!(rlp.payload_info()).total() { + return Err(DecoderError::RlpIsTooBig); + } + rlp.as_val().map(Self::new) + } +} + +impl Encodable for Vote { + fn rlp_append(&self, s: &mut RlpStream) { + let Vote { ref signature } = *self; + s.append(signature); + } +} diff --git a/ethcore/src/header.rs b/ethcore/src/header.rs index 7d86cfd61..794b9230b 100644 --- a/ethcore/src/header.rs +++ b/ethcore/src/header.rs @@ -226,7 +226,8 @@ impl Header { // TODO: make these functions traity /// Place this header into an RLP stream `s`, optionally `with_seal`. pub fn stream_rlp(&self, s: &mut RlpStream, with_seal: Seal) { - s.begin_list(13 + match with_seal { Seal::With => self.seal.len(), _ => 0 }); + let seal_n = match with_seal { Seal::With => self.seal.len(), Seal::WithSome(n) => n, _ => 0 }; + s.begin_list(13 + seal_n); s.append(&self.parent_hash); s.append(&self.uncles_hash); s.append(&self.author); @@ -240,10 +241,8 @@ impl Header { s.append(&self.gas_used); s.append(&self.timestamp); s.append(&self.extra_data); - if let Seal::With = with_seal { - for b in &self.seal { - s.append_raw(b, 1); - } + for b in self.seal.iter().take(seal_n) { + s.append_raw(b, 1); } } From e343153f06b12ddf626f9ff3a592168817705211 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 11 Oct 2016 18:37:31 +0100 Subject: [PATCH 039/295] mixed merge and changes... --- .gitlab-ci.yml | 23 +- .travis.yml | 1 + Cargo.lock | 34 +- appveyor.yml | 2 +- dapps/src/api/api.rs | 5 +- dapps/src/apps/fetcher.rs | 3 +- ethcore/build.rs | 6 +- ethcore/src/client/client.rs | 125 ++++--- ethcore/src/client/mod.rs | 11 +- ethcore/src/client/test_client.rs | 13 +- ethcore/src/engines/tendermint/mod.rs | 18 +- ethcore/src/engines/tendermint/vote.rs | 23 +- ethcore/src/evm/interpreter/mod.rs | 8 +- ethcore/src/evm/interpreter/shared_cache.rs | 2 +- ethcore/src/executive.rs | 28 +- ethcore/src/miner/miner.rs | 203 ++++++++--- ethcore/src/miner/mod.rs | 13 +- ethcore/src/miner/transaction_queue.rs | 132 ++++++- ethcore/src/snapshot/mod.rs | 8 +- ethcore/src/state/account.rs | 63 +--- ethcore/src/state/mod.rs | 296 ++++++++++------ ethcore/src/state_db.rs | 372 ++++++++++++++++---- ethcore/src/tests/client.rs | 6 +- ethcore/src/tests/mod.rs | 1 + ethcore/src/tests/rpc.rs | 3 +- ethcore/src/trace/db.rs | 27 +- ethcore/src/types/filter.rs | 2 +- ethstore/src/dir/disk.rs | 17 +- ipc/codegen/src/lib.rs | 43 ++- ipc/hypervisor/Cargo.toml | 1 + ipc/hypervisor/src/lib.rs | 28 +- ipc/hypervisor/src/service.rs.in | 5 +- parity/cli/config.full.toml | 3 +- parity/cli/config.toml | 1 + parity/cli/mod.rs | 9 +- parity/cli/usage.txt | 7 +- parity/configuration.rs | 5 +- parity/helpers.rs | 30 +- parity/main.rs | 3 + parity/modules.rs | 5 +- parity/params.rs | 2 +- parity/sync.rs | 5 +- rpc/src/v1/helpers/errors.rs | 23 +- rpc/src/v1/impls/eth.rs | 20 +- rpc/src/v1/impls/eth_filter.rs | 12 +- rpc/src/v1/tests/eth.rs | 3 +- rpc/src/v1/tests/helpers/miner_service.rs | 13 +- rpc/src/v1/types/filter.rs | 41 ++- sync/build.rs | 2 +- sync/src/blocks.rs | 6 +- sync/src/chain.rs | 217 ++++++------ sync/src/lib.rs | 6 + sync/src/tests/chain.rs | 24 +- sync/src/tests/helpers.rs | 39 +- util/bigint/Cargo.toml | 2 +- util/io/src/lib.rs | 2 + util/io/src/worker.rs | 13 +- util/network/src/host.rs | 3 +- 58 files changed, 1397 insertions(+), 621 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 32e8d953f..36bb0650a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,8 +3,9 @@ stages: - test variables: GIT_DEPTH: "3" - SIMPLECOV: "true" + SIMPLECOV: "true" RUST_BACKTRACE: "1" + RUSTFLAGS: "-D warnings" cache: key: "$CI_BUILD_NAME/$CI_BUILD_REF_NAME" untracked: true @@ -20,7 +21,7 @@ linux-stable: - cargo build --release --verbose - strip target/release/parity - md5sum target/release/parity >> checksum - - aws configure set aws_access_key_id $s3_key + - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity --body target/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/checksum --body checksum @@ -43,7 +44,7 @@ linux-stable-14.04: - cargo build --release --verbose - strip target/release/parity - md5sum target/release/parity >> checksum - - aws configure set aws_access_key_id $s3_key + - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-ubuntu_14_04-gnu/parity --body target/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-ubuntu_14_04-gnu/checksum --body checksum @@ -106,7 +107,7 @@ linux-centos: - cargo build --release --verbose - strip target/release/parity - md5sum target/release/parity >> checksum - - aws configure set aws_access_key_id $s3_key + - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity --body target/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/checksum --body checksum @@ -134,7 +135,7 @@ linux-armv7: - cargo build --target armv7-unknown-linux-gnueabihf --release --verbose - arm-linux-gnueabihf-strip target/armv7-unknown-linux-gnueabihf/release/parity - md5sum target/armv7-unknown-linux-gnueabihf/release/parity >> checksum - - aws configure set aws_access_key_id $s3_key + - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/parity --body target/armv7-unknown-linux-gnueabihf/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/checksum --body checksum @@ -163,7 +164,7 @@ linux-arm: - cargo build --target arm-unknown-linux-gnueabihf --release --verbose - arm-linux-gnueabihf-strip target/arm-unknown-linux-gnueabihf/release/parity - md5sum target/arm-unknown-linux-gnueabihf/release/parity >> checksum - - aws configure set aws_access_key_id $s3_key + - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/parity --body target/arm-unknown-linux-gnueabihf/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/checksum --body checksum @@ -191,8 +192,8 @@ linux-armv6: - cat .cargo/config - cargo build --target arm-unknown-linux-gnueabi --release --verbose - arm-linux-gnueabi-strip target/arm-unknown-linux-gnueabi/release/parity - - md5sum target/arm-unknown-linux-gnueabi/release/parity >> checksum - - aws configure set aws_access_key_id $s3_key + - md5sum target/arm-unknown-linux-gnueabi/release/parity >> checksum + - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabi/parity --body target/arm-unknown-linux-gnueabi/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabi/checksum --body checksum @@ -221,7 +222,7 @@ linux-aarch64: - cargo build --target aarch64-unknown-linux-gnu --release --verbose - aarch64-linux-gnu-strip target/aarch64-unknown-linux-gnu/release/parity - md5sum target/aarch64-unknown-linux-gnu/release/parity >> checksum - - aws configure set aws_access_key_id $s3_key + - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/parity --body target/aarch64-unknown-linux-gnu/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/checksum --body checksum @@ -243,7 +244,7 @@ darwin: script: - cargo build --release --verbose - md5sum target/release/parity >> checksum - - aws configure set aws_access_key_id $s3_key + - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/parity --body target/release/parity - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/checksum --body checksum @@ -264,7 +265,7 @@ windows: - set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include;C:\vs2015\VC\include;C:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\ucrt - set LIB=C:\vs2015\VC\lib;C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10240.0\ucrt\x64 - set RUST_BACKTRACE=1 - - set RUSTFLAGS=-Zorbit=off + - set RUSTFLAGS=%RUSTFLAGS% -Zorbit=off - rustup default stable-x86_64-pc-windows-msvc - cargo build --release --verbose - cmd md5sum target\release\parity >> checksum diff --git a/.travis.yml b/.travis.yml index 6428ccecf..d9cda5715 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,7 @@ env: - RUN_COVERAGE="false" - RUN_DOCS="false" - TEST_OPTIONS="" + - RUSTFLAGS="-D warnings" # GH_TOKEN for documentation - secure: bumJASbZSU8bxJ0EyPUJmu16AiV9EXOpyOj86Jlq/Ty9CfwGqsSXt96uDyE+OUJf34RUFQMsw0nk37/zC4lcn6kqk2wpuH3N/o85Zo/cVZY/NusBWLQqtT5VbYWsV+u2Ua4Tmmsw8yVYQhYwU2ZOejNpflL+Cs9XGgORp1L+/gMRMC2y5Se6ZhwnKPQlRJ8LGsG1dzjQULxzADIt3/zuspNBS8a2urJwlHfGMkvHDoUWCviP/GXoSqw3TZR7FmKyxE19I8n9+iSvm9+oZZquvcgfUxMHn8Gq/b44UbPvjtFOg2yam4xdWXF/RyWCHdc/R9EHorSABeCbefIsm+zcUF3/YQxwpSxM4IZEeH2rTiC7dcrsKw3XsO16xFQz5YI5Bay+CT/wTdMmJd7DdYz7Dyf+pOvcM9WOf/zorxYWSBOMYy0uzbusU2iyIghQ82s7E/Ahg+WARtPgkuTLSB5aL1oCTBKHqQscMr7lo5Ti6RpWLxEdTQMBznc+bMr+6dEtkEcG9zqc6cE9XX+ox3wTU6+HVMfQ1ltCntJ4UKcw3A6INEbw9wgocQa812CIASQ2fE+SCAbz6JxBjIAlFUnD1lUB7S8PdMPwn9plfQgKQ2A5YZqg6FnBdf0rQXIJYxQWKHXj/rBHSUCT0tHACDlzTA+EwWggvkP5AGIxRxm8jhw= - KCOV_CMD="./kcov-master/tmp/usr/local/bin/kcov" diff --git a/Cargo.lock b/Cargo.lock index bb91080c2..a092325ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -240,7 +240,7 @@ version = "0.5.4" source = "git+https://github.com/ethcore/rust-secp256k1#a9a0b1be1f39560ca86e8fc8e55e205a753ff25c" dependencies = [ "arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", - "gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -308,7 +308,7 @@ dependencies = [ [[package]] name = "ethcore-bigint" -version = "0.1.0" +version = "0.1.1" dependencies = [ "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", @@ -399,6 +399,7 @@ dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", "semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -538,7 +539,7 @@ dependencies = [ "elastic-array 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)", - "ethcore-bigint 0.1.0", + "ethcore-bigint 0.1.1", "ethcore-bloom-journal 0.1.0", "ethcore-devtools 1.4.0", "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -567,7 +568,7 @@ name = "ethcrypto" version = "0.1.0" dependencies = [ "eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)", - "ethcore-bigint 0.1.0", + "ethcore-bigint 0.1.1", "ethkey 0.2.0", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -590,7 +591,7 @@ version = "0.2.0" dependencies = [ "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)", - "ethcore-bigint 0.1.0", + "ethcore-bigint 0.1.1", "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)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -674,8 +675,11 @@ dependencies = [ [[package]] name = "gcc" -version = "0.3.28" +version = "0.3.35" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rayon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "glob" @@ -936,7 +940,7 @@ name = "miniz-sys" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1022,7 +1026,7 @@ name = "nanomsg-sys" version = "0.5.0" source = "git+https://github.com/ethcore/nanomsg.rs.git#c40fe442c9afaea5b38009a3d992ca044dcceb00" dependencies = [ - "gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1394,7 +1398,7 @@ name = "rlp" version = "0.1.0" dependencies = [ "elastic-array 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-bigint 0.1.0", + "ethcore-bigint 0.1.1", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1402,7 +1406,7 @@ dependencies = [ [[package]] name = "rocksdb" version = "0.4.5" -source = "git+https://github.com/ethcore/rust-rocksdb#485dd747a2c9a9f910fc8ac696fc9edf5fa22aa3" +source = "git+https://github.com/ethcore/rust-rocksdb#ffc7c82380fe8569f85ae6743f7f620af2d4a679" dependencies = [ "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)", @@ -1411,9 +1415,9 @@ dependencies = [ [[package]] name = "rocksdb-sys" version = "0.3.0" -source = "git+https://github.com/ethcore/rust-rocksdb#485dd747a2c9a9f910fc8ac696fc9edf5fa22aa3" +source = "git+https://github.com/ethcore/rust-rocksdb#ffc7c82380fe8569f85ae6743f7f620af2d4a679" dependencies = [ - "gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1444,7 +1448,7 @@ name = "rust-crypto" version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1537,7 +1541,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "sha3" version = "0.1.0" dependencies = [ - "gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1926,7 +1930,7 @@ dependencies = [ "checksum eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)" = "" "checksum ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0c53453517f620847be51943db329276ae52f2e210cfc659e81182864be2f" "checksum flate2 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "3eeb481e957304178d2e782f2da1257f1434dfecbae883bafb61ada2a9fea3bb" -"checksum gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)" = "3da3a2cbaeb01363c8e3704fd9fd0eb2ceb17c6f27abd4c1ef040fb57d20dc79" +"checksum gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "91ecd03771effb0c968fd6950b37e89476a578aaf1c70297d8e92b6516ec3312" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" "checksum hamming 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65043da274378d68241eb9a8f8f8aa54e349136f7b8e12f63e3ef44043cc30e1" "checksum heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "abb306abb8d398e053cfb1b3e7b72c2f580be048b85745c52652954f8ad1439c" diff --git a/appveyor.yml b/appveyor.yml index 3f6bb85ef..75a2da7cb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,7 +6,7 @@ environment: certpass: secure: 0BgXJqxq9Ei34/hZ7121FQ== keyfile: C:\users\appveyor\Certificates.p12 - RUSTFLAGS: -Zorbit=off + RUSTFLAGS: -Zorbit=off -D warnings branches: only: diff --git a/dapps/src/api/api.rs b/dapps/src/api/api.rs index 9a8dfef95..80c5e09de 100644 --- a/dapps/src/api/api.rs +++ b/dapps/src/api/api.rs @@ -92,11 +92,14 @@ impl server::Handler for RestApiRouter { } let url = url.expect("Check for None early-exists above; qed"); - let path = self.path.take().expect("on_request called only once, and path is always defined in new; qed"); + let mut path = self.path.take().expect("on_request called only once, and path is always defined in new; qed"); let control = self.control.take().expect("on_request called only once, and control is always defined in new; qed"); let endpoint = url.path.get(1).map(|v| v.as_str()); let hash = url.path.get(2).map(|v| v.as_str()); + // at this point path.app_id contains 'api', adjust it to the hash properly, otherwise + // we will try and retrieve 'api' as the hash when doing the /api/content route + if let Some(hash) = hash.clone() { path.app_id = hash.to_owned() } let handler = endpoint.and_then(|v| match v { "apps" => Some(as_json(&self.api.list_apps())), diff --git a/dapps/src/apps/fetcher.rs b/dapps/src/apps/fetcher.rs index 8702e4706..2e1328858 100644 --- a/dapps/src/apps/fetcher.rs +++ b/dapps/src/apps/fetcher.rs @@ -122,7 +122,7 @@ impl ContentFetcher { }, // We need to start fetching app None => { - trace!(target: "dapps", "Content unavailable. Fetching..."); + trace!(target: "dapps", "Content unavailable. Fetching... {:?}", content_id); let content_hex = content_id.from_hex().expect("to_handler is called only when `contains` returns true."); let content = self.resolver.resolve(content_hex); @@ -415,4 +415,3 @@ mod tests { assert_eq!(fetcher.contains("test3"), false); } } - diff --git a/ethcore/build.rs b/ethcore/build.rs index b83955708..5a3a3f0ba 100644 --- a/ethcore/build.rs +++ b/ethcore/build.rs @@ -18,7 +18,7 @@ extern crate ethcore_ipc_codegen; fn main() { ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap(); - ethcore_ipc_codegen::derive_ipc("src/client/traits.rs").unwrap(); - ethcore_ipc_codegen::derive_ipc("src/snapshot/snapshot_service_trait.rs").unwrap(); - ethcore_ipc_codegen::derive_ipc("src/client/chain_notify.rs").unwrap(); + ethcore_ipc_codegen::derive_ipc_cond("src/client/traits.rs", cfg!(feature="ipc")).unwrap(); + ethcore_ipc_codegen::derive_ipc_cond("src/snapshot/snapshot_service_trait.rs", cfg!(feature="ipc")).unwrap(); + ethcore_ipc_codegen::derive_ipc_cond("src/client/chain_notify.rs", cfg!(feature="ipc")).unwrap(); } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index ece20dd77..ba00284f1 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -149,13 +149,6 @@ pub struct Client { /// assume finality of a given candidate. pub const HISTORY: u64 = 1200; -/// Append a path element to the given path and return the string. -pub fn append_path

(path: P, item: &str) -> String where P: AsRef { - let mut p = path.as_ref().to_path_buf(); - p.push(item); - p.to_str().unwrap().to_owned() -} - impl Client { /// Create a new client with given spec and DB path and custom verifier. pub fn new( @@ -169,7 +162,7 @@ impl Client { let path = path.to_path_buf(); let gb = spec.genesis_block(); - let db = Arc::new(try!(Database::open(&db_config, &path.to_str().unwrap()).map_err(ClientError::Database))); + let db = Arc::new(try!(Database::open(&db_config, &path.to_str().expect("DB path could not be converted to string.")).map_err(ClientError::Database))); let chain = Arc::new(BlockChain::new(config.blockchain.clone(), &gb, db.clone(), spec.engine.clone())); let tracedb = RwLock::new(TraceDB::new(config.tracing.clone(), db.clone(), chain.clone())); @@ -298,31 +291,27 @@ impl Client { // Check if Parent is in chain let chain_has_parent = chain.block_header(header.parent_hash()); - if let None = chain_has_parent { + if let Some(parent) = chain_has_parent { + // Enact Verified Block + let last_hashes = self.build_last_hashes(header.parent_hash().clone()); + let db = self.state_db.lock().boxed_clone_canon(&header.parent_hash()); + + let enact_result = enact_verified(block, engine, self.tracedb.read().tracing_enabled(), db, &parent, last_hashes, self.factories.clone()); + let locked_block = try!(enact_result.map_err(|e| { + warn!(target: "client", "Block import failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); + })); + + // Final Verification + if let Err(e) = self.verifier.verify_block_final(header, locked_block.block().header()) { + warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); + return Err(()); + } + + Ok(locked_block) + } else { warn!(target: "client", "Block import failed for #{} ({}): Parent not found ({}) ", header.number(), header.hash(), header.parent_hash()); - return Err(()); - }; - - // Enact Verified Block - let parent = chain_has_parent.unwrap(); - let last_hashes = self.build_last_hashes(header.parent_hash().clone()); - let is_canon = header.parent_hash() == &chain.best_block_hash(); - let db = if is_canon { self.state_db.lock().boxed_clone_canon() } else { self.state_db.lock().boxed_clone() }; - - let enact_result = enact_verified(block, engine, self.tracedb.read().tracing_enabled(), db, &parent, last_hashes, self.factories.clone()); - if let Err(e) = enact_result { - warn!(target: "client", "Block import failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); - return Err(()); - }; - - // Final Verification - let locked_block = enact_result.unwrap(); - if let Err(e) = self.verifier.verify_block_final(header, locked_block.block().header()) { - warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); - return Err(()); + Err(()) } - - Ok(locked_block) } fn calculate_enacted_retracted(&self, import_results: &[ImportRoute]) -> (Vec, Vec) { @@ -366,23 +355,21 @@ impl Client { for block in blocks { let header = &block.header; - if invalid_blocks.contains(header.parent_hash()) { + let is_invalid = invalid_blocks.contains(header.parent_hash()); + if is_invalid { invalid_blocks.insert(header.hash()); continue; } - let closed_block = self.check_and_close_block(&block); - if let Err(_) = closed_block { + if let Ok(closed_block) = self.check_and_close_block(&block) { + imported_blocks.push(header.hash()); + + let route = self.commit_block(closed_block, &header.hash(), &block.bytes); + import_results.push(route); + + self.report.write().accrue_block(&block); + } else { invalid_blocks.insert(header.hash()); - continue; } - - let closed_block = closed_block.unwrap(); - imported_blocks.push(header.hash()); - - let route = self.commit_block(closed_block, &header.hash(), &block.bytes); - import_results.push(route); - - self.report.write().accrue_block(&block); } let imported = imported_blocks.len(); @@ -432,7 +419,7 @@ impl Client { // Are we committing an era? let ancient = if number >= HISTORY { let n = number - HISTORY; - Some((n, chain.block_hash(n).unwrap())) + Some((n, chain.block_hash(n).expect("only verified blocks can be commited; verified block has hash; qed"))) } else { None }; @@ -461,6 +448,8 @@ impl Client { enacted: route.enacted.clone(), retracted: route.retracted.len() }); + let is_canon = route.enacted.last().map_or(false, |h| h == hash); + state.sync_cache(&route.enacted, &route.retracted, is_canon); // Final commit to the DB self.db.read().write_buffered(batch); chain.commit(); @@ -535,9 +524,11 @@ impl Client { /// Get a copy of the best block's state. pub fn state(&self) -> State { + let header = self.best_block_header(); + let header = HeaderView::new(&header); State::from_existing( - self.state_db.lock().boxed_clone(), - HeaderView::new(&self.best_block_header()).state_root(), + self.state_db.lock().boxed_clone_canon(&header.hash()), + header.state_root(), self.engine.account_start_nonce(), self.factories.clone()) .expect("State root of best block header always valid.") @@ -899,8 +890,10 @@ impl BlockChainClient for Client { BodyView::new(&block).localized_transaction_at(&address.block_hash, block_number, address.index) }); - match (t, chain.transaction_receipt(&address)) { - (Some(tx), Some(receipt)) => { + let tx_and_sender = t.and_then(|tx| tx.sender().ok().map(|sender| (tx, sender))); + + match (tx_and_sender, chain.transaction_receipt(&address)) { + (Some((tx, sender)), Some(receipt)) => { let block_hash = tx.block_hash.clone(); let block_number = tx.block_number.clone(); let transaction_hash = tx.hash(); @@ -922,7 +915,7 @@ impl BlockChainClient for Client { gas_used: receipt.gas_used - prior_gas_used, contract_address: match tx.action { Action::Call(_) => None, - Action::Create => Some(contract_address(&tx.sender().unwrap(), &tx.nonce)) + Action::Create => Some(contract_address(&sender, &tx.nonce)) }, logs: receipt.logs.into_iter().enumerate().map(|(i, log)| LocalizedLogEntry { entry: log, @@ -1023,17 +1016,18 @@ impl BlockChainClient for Client { let start = self.block_number(filter.range.start); let end = self.block_number(filter.range.end); - if start.is_some() && end.is_some() { - let filter = trace::Filter { - range: start.unwrap() as usize..end.unwrap() as usize, - from_address: From::from(filter.from_address), - to_address: From::from(filter.to_address), - }; + match (start, end) { + (Some(s), Some(e)) => { + let filter = trace::Filter { + range: s as usize..e as usize, + from_address: From::from(filter.from_address), + to_address: From::from(filter.to_address), + }; - let traces = self.tracedb.read().filter(&filter); - Some(traces) - } else { - None + let traces = self.tracedb.read().filter(&filter); + Some(traces) + }, + _ => None, } } @@ -1080,7 +1074,7 @@ impl BlockChainClient for Client { } fn pending_transactions(&self) -> Vec { - self.miner.pending_transactions() + self.miner.pending_transactions(self.chain.read().best_block_number()) } // TODO: Make it an actual queue, return errors. @@ -1109,7 +1103,7 @@ impl MiningBlockChainClient for Client { engine, self.factories.clone(), false, // TODO: this will need to be parameterised once we want to do immediate mining insertion. - self.state_db.lock().boxed_clone(), + self.state_db.lock().boxed_clone_canon(&h), &chain.block_header(&h).expect("h is best block hash: so its header must exist: qed"), self.build_last_hashes(h.clone()), author, @@ -1120,11 +1114,15 @@ impl MiningBlockChainClient for Client { // Add uncles chain .find_uncle_headers(&h, engine.maximum_uncle_age()) - .unwrap() + .unwrap_or_else(Vec::new) .into_iter() .take(engine.maximum_uncle_count()) .foreach(|h| { - open_block.push_uncle(h).unwrap(); + open_block.push_uncle(h).expect("pushing maximum_uncle_count; + open_block was just created; + push_uncle is not ok only if more than maximum_uncle_count is pushed; + so all push_uncle are Ok; + qed"); }); open_block @@ -1145,6 +1143,7 @@ impl MiningBlockChainClient for Client { let block_data = block.rlp_bytes(); let route = self.commit_block(block, &h, &block_data); trace!(target: "client", "Imported sealed block #{} ({})", number, h); + self.state_db.lock().sync_cache(&route.enacted, &route.retracted, false); let (enacted, retracted) = self.calculate_enacted_retracted(&[route]); self.miner.chain_new_blocks(self, &[h.clone()], &[], &enacted, &retracted); diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 3bbf9011b..1e8aa9d72 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -30,13 +30,20 @@ pub use self::test_client::{TestBlockChainClient, EachBlockWith}; pub use types::trace_filter::Filter as TraceFilter; pub use executive::{Executed, Executive, TransactOptions}; pub use env_info::{LastHashes, EnvInfo}; -pub use self::chain_notify::{ChainNotify, ChainNotifyClient}; +pub use self::chain_notify::ChainNotify; pub use types::call_analytics::CallAnalytics; pub use block_import_error::BlockImportError; pub use transaction_import::TransactionImportResult; pub use transaction_import::TransactionImportError; -pub use self::traits::{BlockChainClient, MiningBlockChainClient, RemoteClient}; +pub use self::traits::{BlockChainClient, MiningBlockChainClient}; + +/// IPC interfaces +#[cfg(feature="ipc")] +pub mod remote { + pub use super::traits::RemoteClient; + pub use super::chain_notify::ChainNotifyClient; +} mod traits { #![allow(dead_code, unused_assignments, unused_variables, missing_docs)] // codegen issues diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index c640e76db..6a53582d3 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -55,6 +55,8 @@ pub struct TestBlockChainClient { pub genesis_hash: H256, /// Last block hash. pub last_hash: RwLock, + /// Extra data do set for each block + pub extra_data: Bytes, /// Difficulty. pub difficulty: RwLock, /// Balances. @@ -105,11 +107,17 @@ impl Default for TestBlockChainClient { impl TestBlockChainClient { /// Creates new test client. pub fn new() -> Self { + Self::new_with_extra_data(Bytes::new()) + } + + /// Creates new test client with specified extra data for each block + pub fn new_with_extra_data(extra_data: Bytes) -> Self { let spec = Spec::new_test(); let mut client = TestBlockChainClient { blocks: RwLock::new(HashMap::new()), numbers: RwLock::new(HashMap::new()), genesis_hash: H256::new(), + extra_data: extra_data, last_hash: RwLock::new(H256::new()), difficulty: RwLock::new(From::from(0)), balances: RwLock::new(HashMap::new()), @@ -129,7 +137,7 @@ impl TestBlockChainClient { client.genesis_hash = client.last_hash.read().clone(); client } - + /// Set the transaction receipt result pub fn set_transaction_receipt(&self, id: TransactionID, receipt: LocalizedReceipt) { self.receipts.write().insert(id, receipt); @@ -184,6 +192,7 @@ impl TestBlockChainClient { header.set_parent_hash(self.last_hash.read().clone()); header.set_number(n as BlockNumber); header.set_gas_limit(U256::from(1_000_000)); + header.set_extra_data(self.extra_data.clone()); let uncles = match with { EachBlockWith::Uncle | EachBlockWith::UncleAndTransaction => { let mut uncles = RlpStream::new_list(1); @@ -606,6 +615,6 @@ impl BlockChainClient for TestBlockChainClient { } fn pending_transactions(&self) -> Vec { - self.miner.pending_transactions() + self.miner.pending_transactions(self.chain_info().best_block_number) } } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 2af0ab781..7826f991e 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -20,6 +20,7 @@ mod message; mod timeout; mod params; mod vote; +mod vote_collector; use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use common::*; @@ -246,9 +247,11 @@ impl Engine for Tendermint { } } - /// Set author to proposer. + /// Set author to proposer and set the correct round in the seal. /// 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 on_close_block(&self, _block: &mut ExecutedBlock) { + + } /// Attempt to seal the block internally using all available signatures. /// @@ -278,11 +281,14 @@ impl Engine for Tendermint { fn handle_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { let message: ConsensusMessage = try!(message.as_val()); - try!(Err(EngineError::UnknownStep)) - //match message { - // ConsensusMessage::Prevote - //} + if self.is_authority(&sender) { + //match message { + // ConsensusMessage::Prevote + //} + } + + try!(Err(EngineError::UnknownStep)) // Check if correct round. //if self.r.load(AtomicOrdering::Relaxed) != try!(message.val_at(0)) { diff --git a/ethcore/src/engines/tendermint/vote.rs b/ethcore/src/engines/tendermint/vote.rs index a11583201..a877d324e 100644 --- a/ethcore/src/engines/tendermint/vote.rs +++ b/ethcore/src/engines/tendermint/vote.rs @@ -21,27 +21,35 @@ use util::Hashable; use account_provider::AccountProvider; use rlp::{View, DecoderError, Decodable, Decoder, Encodable, RlpStream, Stream}; use basic_types::Seal; +use super::BlockHash; -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, Hash)] pub struct Vote { + block_hash: BlockHash, signature: H520 } -fn message(header: &Header) -> H256 { +fn block_hash(header: &Header) -> H256 { header.rlp(Seal::WithSome(1)).sha3() } impl Vote { - fn new(signature: H520) -> Vote { Vote { signature: signature }} + fn new(block_hash: BlockHash, signature: H520) -> Vote { + Vote { block_hash: block_hash, signature: signature } + } /// Try to use the author address to create a vote. pub fn propose(header: &Header, accounts: &AccountProvider) -> Option { - accounts.sign(*header.author(), message(&header)).ok().map(Into::into).map(Self::new) + Self::validate(header, accounts, *header.author()) } /// Use any unlocked validator account to create a vote. pub fn validate(header: &Header, accounts: &AccountProvider, validator: Address) -> Option { - accounts.sign(validator, message(&header)).ok().map(Into::into).map(Self::new) + let message = block_hash(&header); + accounts.sign(validator, message) + .ok() + .map(Into::into) + .map(|sig| Self::new(message, sig)) } } @@ -51,13 +59,14 @@ impl Decodable for Vote { if decoder.as_raw().len() != try!(rlp.payload_info()).total() { return Err(DecoderError::RlpIsTooBig); } - rlp.as_val().map(Self::new) + Ok(Self::new(try!(rlp.val_at(0)), try!(rlp.val_at(1)))) } } impl Encodable for Vote { fn rlp_append(&self, s: &mut RlpStream) { - let Vote { ref signature } = *self; + let Vote { ref block_hash, ref signature } = *self; + s.append(block_hash); s.append(signature); } } diff --git a/ethcore/src/evm/interpreter/mod.rs b/ethcore/src/evm/interpreter/mod.rs index fdf99876a..887f37cef 100644 --- a/ethcore/src/evm/interpreter/mod.rs +++ b/ethcore/src/evm/interpreter/mod.rs @@ -116,11 +116,11 @@ impl evm::Evm for Interpreter { let instruction = code[reader.position]; reader.position += 1; - let info = infos[instruction as usize]; - try!(self.verify_instruction(ext, instruction, &info, &stack)); + let info = &infos[instruction as usize]; + try!(self.verify_instruction(ext, instruction, info, &stack)); // Calculate gas cost - let (gas_cost, mem_gas, mem_size) = try!(gasometer.get_gas_cost_mem(ext, instruction, &info, &stack, self.mem.size())); + let (gas_cost, mem_gas, mem_size) = try!(gasometer.get_gas_cost_mem(ext, instruction, info, &stack, self.mem.size())); // TODO: make compile-time removable if too much of a performance hit. let trace_executed = ext.trace_prepare_execute(reader.position - 1, instruction, &gas_cost.as_u256()); @@ -129,7 +129,7 @@ impl evm::Evm for Interpreter { gasometer.current_mem_gas = mem_gas; gasometer.current_gas = gasometer.current_gas - gas_cost; - evm_debug!({ informant.before_instruction(reader.position, instruction, &info, &gasometer.current_gas, &stack) }); + evm_debug!({ informant.before_instruction(reader.position, instruction, info, &gasometer.current_gas, &stack) }); let (mem_written, store_written) = match trace_executed { true => (Self::mem_written(instruction, &stack), Self::store_written(instruction, &stack)), diff --git a/ethcore/src/evm/interpreter/shared_cache.rs b/ethcore/src/evm/interpreter/shared_cache.rs index 76360138b..ce383bae8 100644 --- a/ethcore/src/evm/interpreter/shared_cache.rs +++ b/ethcore/src/evm/interpreter/shared_cache.rs @@ -21,7 +21,7 @@ use util::sha3::*; use bit_set::BitSet; use super::super::instructions; -const CACHE_CODE_ITEMS: usize = 4096; +const CACHE_CODE_ITEMS: usize = 65536; /// GLobal cache for EVM interpreter pub struct SharedCache { diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index b0b0b58c8..f3186d6dd 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -25,10 +25,10 @@ use trace::{FlatTrace, Tracer, NoopTracer, ExecutiveTracer, VMTrace, VMTracer, E use crossbeam; pub use types::executed::{Executed, ExecutionResult}; -/// Max depth to avoid stack overflow (when it's reached we start a new thread with VM) +/// Roughly estimate what stack size each level of evm depth will use /// TODO [todr] We probably need some more sophisticated calculations here (limit on my machine 132) /// Maybe something like here: `https://github.com/ethereum/libethereum/blob/4db169b8504f2b87f7d5a481819cfb959fc65f6c/libethereum/ExtVM.cpp` -const MAX_VM_DEPTH_FOR_THREAD: usize = 64; +const STACK_SIZE_PER_DEPTH: usize = 24*1024; /// Returns new address created from address and given nonce. pub fn contract_address(address: &Address, nonce: &U256) -> Address { @@ -149,12 +149,13 @@ impl<'a> Executive<'a> { // TODO: we might need bigints here, or at least check overflows. let balance = self.state.balance(&sender); - let gas_cost = U512::from(t.gas) * U512::from(t.gas_price); + let gas_cost = t.gas.full_mul(t.gas_price); let total_cost = U512::from(t.value) + gas_cost; // avoid unaffordable transactions - if U512::from(balance) < total_cost { - return Err(From::from(ExecutionError::NotEnoughCash { required: total_cost, got: U512::from(balance) })); + let balance512 = U512::from(balance); + if balance512 < total_cost { + return Err(From::from(ExecutionError::NotEnoughCash { required: total_cost, got: balance512 })); } // NOTE: there can be no invalid transactions from this point. @@ -212,8 +213,11 @@ impl<'a> Executive<'a> { tracer: &mut T, vm_tracer: &mut V ) -> evm::Result where T: Tracer, V: VMTracer { + + let depth_threshold = ::io::LOCAL_STACK_SIZE.with(|sz| sz.get() / STACK_SIZE_PER_DEPTH); + // Ordinary execution - keep VM in same thread - if (self.depth + 1) % MAX_VM_DEPTH_FOR_THREAD != 0 { + if (self.depth + 1) % depth_threshold != 0 { let vm_factory = self.vm_factory; let mut ext = self.as_externalities(OriginInfo::from(¶ms), unconfirmed_substate, output_policy, tracer, vm_tracer); trace!(target: "executive", "ext.schedule.have_delegate_call: {}", ext.schedule().have_delegate_call); @@ -265,7 +269,7 @@ impl<'a> Executive<'a> { let cost = self.engine.cost_of_builtin(¶ms.code_address, data); if cost <= params.gas { self.engine.execute_builtin(¶ms.code_address, data, &mut output); - self.state.clear_snapshot(); + self.state.discard_snapshot(); // trace only top level calls to builtins to avoid DDoS attacks if self.depth == 0 { @@ -285,7 +289,7 @@ impl<'a> Executive<'a> { Ok(params.gas - cost) } else { // just drain the whole gas - self.state.revert_snapshot(); + self.state.revert_to_snapshot(); tracer.trace_failed_call(trace_info, vec![], evm::Error::OutOfGas.into()); @@ -331,7 +335,7 @@ impl<'a> Executive<'a> { res } else { // otherwise it's just a basic transaction, only do tracing, if necessary. - self.state.clear_snapshot(); + self.state.discard_snapshot(); tracer.trace_call(trace_info, U256::zero(), trace_output, vec![]); Ok(params.gas) @@ -413,7 +417,7 @@ impl<'a> Executive<'a> { // real ammount to refund let gas_left_prerefund = match result { Ok(x) => x, _ => 0.into() }; - let refunded = cmp::min(refunds_bound, (t.gas - gas_left_prerefund) / U256::from(2)); + let refunded = cmp::min(refunds_bound, (t.gas - gas_left_prerefund) >> 1); let gas_left = gas_left_prerefund + refunded; let gas_used = t.gas - gas_left; @@ -473,10 +477,10 @@ impl<'a> Executive<'a> { | Err(evm::Error::BadInstruction {.. }) | Err(evm::Error::StackUnderflow {..}) | Err(evm::Error::OutOfStack {..}) => { - self.state.revert_snapshot(); + self.state.revert_to_snapshot(); }, Ok(_) | Err(evm::Error::Internal) => { - self.state.clear_snapshot(); + self.state.discard_snapshot(); substate.accrue(un_substate); } } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 77183d452..a2dab4475 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -48,6 +48,17 @@ pub enum PendingSet { SealingOrElseQueue, } +/// Type of the gas limit to apply to the transaction queue. +#[derive(Debug, PartialEq)] +pub enum GasLimit { + /// Depends on the block gas limit and is updated with every block. + Auto, + /// No limit. + None, + /// Set to a fixed gas value. + Fixed(U256), +} + /// Configures the behaviour of the miner. #[derive(Debug, PartialEq)] pub struct MinerOptions { @@ -71,6 +82,8 @@ pub struct MinerOptions { pub work_queue_size: usize, /// Can we submit two different solutions for the same block and expect both to result in an import? pub enable_resubmission: bool, + /// Global gas limit for all transaction in the queue except for local and retracted. + pub tx_queue_gas_limit: GasLimit, } impl Default for MinerOptions { @@ -81,11 +94,12 @@ impl Default for MinerOptions { reseal_on_external_tx: false, reseal_on_own_tx: true, tx_gas_limit: !U256::zero(), - tx_queue_size: 1024, + tx_queue_size: 2048, pending_set: PendingSet::AlwaysQueue, reseal_min_period: Duration::from_secs(2), work_queue_size: 20, enable_resubmission: true, + tx_queue_gas_limit: GasLimit::Auto, } } } @@ -194,7 +208,11 @@ impl Miner { 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))); + let gas_limit = match options.tx_queue_gas_limit { + GasLimit::Fixed(ref limit) => *limit, + _ => !U256::zero(), + }; + let txq = Arc::new(Mutex::new(TransactionQueue::with_limits(options.tx_queue_size, gas_limit, options.tx_gas_limit))); Miner { transaction_queue: txq, next_allowed_reseal: Mutex::new(Instant::now()), @@ -443,6 +461,10 @@ impl Miner { let gas_limit = HeaderView::new(&chain.best_block_header()).gas_limit(); let mut queue = self.transaction_queue.lock(); queue.set_gas_limit(gas_limit); + if let GasLimit::Auto = self.options.tx_queue_gas_limit { + // Set total tx queue gas limit to be 2x the block gas limit. + queue.set_total_gas_limit(gas_limit << 1); + } } /// Returns true if we had to prepare new pending block. @@ -493,6 +515,21 @@ impl Miner { /// Are we allowed to do a non-mandatory reseal? fn tx_reseal_allowed(&self) -> bool { Instant::now() > *self.next_allowed_reseal.lock() } + + fn from_pending_block(&self, latest_block_number: BlockNumber, from_chain: F, map_block: G) -> H + where F: Fn() -> H, G: Fn(&ClosedBlock) -> H { + let sealing_work = self.sealing_work.lock(); + sealing_work.queue.peek_last_ref().map_or_else( + || from_chain(), + |b| { + if b.block().header().number() > latest_block_number { + map_block(b) + } else { + from_chain() + } + } + ) + } } const SEALING_TIMEOUT_IN_BLOCKS : u64 = 5; @@ -565,29 +602,35 @@ impl MinerService for Miner { } fn balance(&self, chain: &MiningBlockChainClient, address: &Address) -> U256 { - let sealing_work = self.sealing_work.lock(); - sealing_work.queue.peek_last_ref().map_or_else( + self.from_pending_block( + chain.chain_info().best_block_number, || chain.latest_balance(address), |b| b.block().fields().state.balance(address) ) } fn storage_at(&self, chain: &MiningBlockChainClient, address: &Address, position: &H256) -> H256 { - let sealing_work = self.sealing_work.lock(); - sealing_work.queue.peek_last_ref().map_or_else( + self.from_pending_block( + chain.chain_info().best_block_number, || chain.latest_storage_at(address, position), |b| b.block().fields().state.storage_at(address, position) ) } fn nonce(&self, chain: &MiningBlockChainClient, address: &Address) -> U256 { - let sealing_work = self.sealing_work.lock(); - sealing_work.queue.peek_last_ref().map_or_else(|| chain.latest_nonce(address), |b| b.block().fields().state.nonce(address)) + self.from_pending_block( + chain.chain_info().best_block_number, + || chain.latest_nonce(address), + |b| b.block().fields().state.nonce(address) + ) } fn code(&self, chain: &MiningBlockChainClient, address: &Address) -> Option { - let sealing_work = self.sealing_work.lock(); - sealing_work.queue.peek_last_ref().map_or_else(|| chain.latest_code(address), |b| b.block().fields().state.code(address).map(|c| (*c).clone())) + self.from_pending_block( + chain.chain_info().best_block_number, + || chain.latest_code(address), + |b| b.block().fields().state.code(address).map(|c| (*c).clone()) + ) } fn set_author(&self, author: Address) { @@ -737,50 +780,74 @@ impl MinerService for Miner { queue.top_transactions() } - fn pending_transactions(&self) -> Vec { + fn pending_transactions(&self, best_block: BlockNumber) -> Vec { let queue = self.transaction_queue.lock(); - let sw = self.sealing_work.lock(); - // TODO: should only use the sealing_work when it's current (it could be an old block) - let sealing_set = match sw.enabled { - true => sw.queue.peek_last_ref(), - false => None, - }; - match (&self.options.pending_set, sealing_set) { - (&PendingSet::AlwaysQueue, _) | (&PendingSet::SealingOrElseQueue, None) => queue.top_transactions(), - (_, sealing) => sealing.map_or_else(Vec::new, |s| s.transactions().to_owned()), + match self.options.pending_set { + PendingSet::AlwaysQueue => queue.top_transactions(), + PendingSet::SealingOrElseQueue => { + self.from_pending_block( + best_block, + || queue.top_transactions(), + |sealing| sealing.transactions().to_owned() + ) + }, + PendingSet::AlwaysSealing => { + self.from_pending_block( + best_block, + || vec![], + |sealing| sealing.transactions().to_owned() + ) + }, } } - fn pending_transactions_hashes(&self) -> Vec { + fn pending_transactions_hashes(&self, best_block: BlockNumber) -> Vec { let queue = self.transaction_queue.lock(); - let sw = self.sealing_work.lock(); - let sealing_set = match sw.enabled { - true => sw.queue.peek_last_ref(), - false => None, - }; - match (&self.options.pending_set, sealing_set) { - (&PendingSet::AlwaysQueue, _) | (&PendingSet::SealingOrElseQueue, None) => queue.pending_hashes(), - (_, sealing) => sealing.map_or_else(Vec::new, |s| s.transactions().iter().map(|t| t.hash()).collect()), + match self.options.pending_set { + PendingSet::AlwaysQueue => queue.pending_hashes(), + PendingSet::SealingOrElseQueue => { + self.from_pending_block( + best_block, + || queue.pending_hashes(), + |sealing| sealing.transactions().iter().map(|t| t.hash()).collect() + ) + }, + PendingSet::AlwaysSealing => { + self.from_pending_block( + best_block, + || vec![], + |sealing| sealing.transactions().iter().map(|t| t.hash()).collect() + ) + }, } } - fn transaction(&self, hash: &H256) -> Option { + fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option { let queue = self.transaction_queue.lock(); - let sw = self.sealing_work.lock(); - let sealing_set = match sw.enabled { - true => sw.queue.peek_last_ref(), - false => None, - }; - match (&self.options.pending_set, sealing_set) { - (&PendingSet::AlwaysQueue, _) | (&PendingSet::SealingOrElseQueue, None) => queue.find(hash), - (_, sealing) => sealing.and_then(|s| s.transactions().iter().find(|t| &t.hash() == hash).cloned()), + match self.options.pending_set { + PendingSet::AlwaysQueue => queue.find(hash), + PendingSet::SealingOrElseQueue => { + self.from_pending_block( + best_block, + || queue.find(hash), + |sealing| sealing.transactions().iter().find(|t| &t.hash() == hash).cloned() + ) + }, + PendingSet::AlwaysSealing => { + self.from_pending_block( + best_block, + || None, + |sealing| sealing.transactions().iter().find(|t| &t.hash() == hash).cloned() + ) + }, } } - fn pending_receipt(&self, hash: &H256) -> Option { - let sealing_work = self.sealing_work.lock(); - match (sealing_work.enabled, sealing_work.queue.peek_last_ref()) { - (true, Some(pending)) => { + fn pending_receipt(&self, best_block: BlockNumber, hash: &H256) -> Option { + self.from_pending_block( + best_block, + || None, + |pending| { let txs = pending.transactions(); txs.iter() .map(|t| t.hash()) @@ -801,15 +868,15 @@ impl MinerService for Miner { logs: receipt.logs.clone(), } }) - }, - _ => None - } + } + ) } - fn pending_receipts(&self) -> BTreeMap { - let sealing_work = self.sealing_work.lock(); - match (sealing_work.enabled, sealing_work.queue.peek_last_ref()) { - (true, Some(pending)) => { + fn pending_receipts(&self, best_block: BlockNumber) -> BTreeMap { + self.from_pending_block( + best_block, + || BTreeMap::new(), + |pending| { let hashes = pending.transactions() .iter() .map(|t| t.hash()); @@ -817,9 +884,8 @@ impl MinerService for Miner { let receipts = pending.receipts().iter().cloned(); hashes.zip(receipts).collect() - }, - _ => BTreeMap::new() - } + } + ) } fn last_nonce(&self, address: &Address) -> Option { @@ -1016,6 +1082,7 @@ mod tests { reseal_min_period: Duration::from_secs(5), tx_gas_limit: !U256::zero(), tx_queue_size: 1024, + tx_queue_gas_limit: GasLimit::None, pending_set: PendingSet::AlwaysSealing, work_queue_size: 5, enable_resubmission: true, @@ -1044,34 +1111,54 @@ mod tests { let client = TestBlockChainClient::default(); let miner = miner(); let transaction = transaction(); + let best_block = 0; // when let res = miner.import_own_transaction(&client, transaction); // then assert_eq!(res.unwrap(), TransactionImportResult::Current); assert_eq!(miner.all_transactions().len(), 1); - assert_eq!(miner.pending_transactions().len(), 1); - assert_eq!(miner.pending_transactions_hashes().len(), 1); - assert_eq!(miner.pending_receipts().len(), 1); + assert_eq!(miner.pending_transactions(best_block).len(), 1); + assert_eq!(miner.pending_transactions_hashes(best_block).len(), 1); + assert_eq!(miner.pending_receipts(best_block).len(), 1); // This method will let us know if pending block was created (before calling that method) assert!(!miner.prepare_work_sealing(&client)); } + #[test] + fn should_not_use_pending_block_if_best_block_is_higher() { + // given + let client = TestBlockChainClient::default(); + let miner = miner(); + let transaction = transaction(); + let best_block = 10; + // when + let res = miner.import_own_transaction(&client, transaction); + + // then + assert_eq!(res.unwrap(), TransactionImportResult::Current); + assert_eq!(miner.all_transactions().len(), 1); + assert_eq!(miner.pending_transactions(best_block).len(), 0); + assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0); + assert_eq!(miner.pending_receipts(best_block).len(), 0); + } + #[test] fn should_import_external_transaction() { // given let client = TestBlockChainClient::default(); let miner = miner(); let transaction = transaction(); + let best_block = 0; // when let res = miner.import_external_transactions(&client, vec![transaction]).pop().unwrap(); // then assert_eq!(res.unwrap(), TransactionImportResult::Current); assert_eq!(miner.all_transactions().len(), 1); - assert_eq!(miner.pending_transactions_hashes().len(), 0); - assert_eq!(miner.pending_transactions().len(), 0); - assert_eq!(miner.pending_receipts().len(), 0); + assert_eq!(miner.pending_transactions_hashes(best_block).len(), 0); + assert_eq!(miner.pending_transactions(best_block).len(), 0); + assert_eq!(miner.pending_receipts(best_block).len(), 0); // This method will let us know if pending block was created (before calling that method) assert!(miner.prepare_work_sealing(&client)); } diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index e95ce758a..8dfddf483 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -48,7 +48,7 @@ mod work_notify; mod price_info; pub use self::transaction_queue::{TransactionQueue, AccountDetails, TransactionOrigin}; -pub use self::miner::{Miner, MinerOptions, PendingSet, GasPricer, GasPriceCalibratorOptions}; +pub use self::miner::{Miner, MinerOptions, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit}; pub use self::external::{ExternalMiner, ExternalMinerService}; pub use client::TransactionImportResult; @@ -56,6 +56,7 @@ use std::collections::BTreeMap; use util::{H256, U256, Address, Bytes}; use client::{MiningBlockChainClient, Executed, CallAnalytics}; use block::ClosedBlock; +use header::BlockNumber; use receipt::{RichReceipt, Receipt}; use error::{Error, CallError}; use transaction::SignedTransaction; @@ -115,7 +116,7 @@ pub trait MinerService : Send + Sync { Result; /// Returns hashes of transactions currently in pending - fn pending_transactions_hashes(&self) -> Vec; + fn pending_transactions_hashes(&self, best_block: BlockNumber) -> Vec; /// Removes all transactions from the queue and restart mining operation. fn clear_and_reset(&self, chain: &MiningBlockChainClient); @@ -135,19 +136,19 @@ pub trait MinerService : Send + Sync { where F: FnOnce(&ClosedBlock) -> T, Self: Sized; /// Query pending transactions for hash. - fn transaction(&self, hash: &H256) -> Option; + fn transaction(&self, best_block: BlockNumber, hash: &H256) -> Option; /// Get a list of all transactions. fn all_transactions(&self) -> Vec; /// Get a list of all pending transactions. - fn pending_transactions(&self) -> Vec; + fn pending_transactions(&self, best_block: BlockNumber) -> Vec; /// Get a list of all pending receipts. - fn pending_receipts(&self) -> BTreeMap; + fn pending_receipts(&self, best_block: BlockNumber) -> BTreeMap; /// Get a particular reciept. - fn pending_receipt(&self, hash: &H256) -> Option; + fn pending_receipt(&self, best_block: BlockNumber, hash: &H256) -> Option; /// Returns highest transaction nonce for given address. fn last_nonce(&self, address: &Address) -> Option; diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 75a66358a..fdb652780 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -130,6 +130,8 @@ struct TransactionOrder { /// (e.g. Tx(nonce:5), State(nonce:0) -> height: 5) /// High nonce_height = Low priority (processed later) nonce_height: U256, + /// Gas specified in the transaction. + gas: U256, /// Gas Price of the transaction. /// Low gas price = Low priority (processed later) gas_price: U256, @@ -146,6 +148,7 @@ impl TransactionOrder { fn for_transaction(tx: &VerifiedTransaction, base_nonce: U256) -> Self { TransactionOrder { nonce_height: tx.nonce() - base_nonce, + gas: tx.transaction.gas.clone(), gas_price: tx.transaction.gas_price, hash: tx.hash(), origin: tx.origin, @@ -287,6 +290,7 @@ struct TransactionSet { by_address: Table, by_gas_price: GasPriceQueue, limit: usize, + gas_limit: U256, } impl TransactionSet { @@ -317,15 +321,20 @@ impl TransactionSet { /// It drops transactions from this set but also removes associated `VerifiedTransaction`. /// Returns addresses and lowest nonces of transactions removed because of limit. fn enforce_limit(&mut self, by_hash: &mut HashMap) -> Option> { - let len = self.by_priority.len(); - if len <= self.limit { - return None; - } - + let mut count = 0; + let mut gas: U256 = 0.into(); let to_drop : Vec<(Address, U256)> = { self.by_priority .iter() - .skip(self.limit) + .skip_while(|order| { + count = count + 1; + let r = gas.overflowing_add(order.gas); + if r.1 { return false } + gas = r.0; + // Own and retracted transactions are allowed to go above the gas limit, bot not above the count limit. + (gas <= self.gas_limit || order.origin == TransactionOrigin::Local || order.origin == TransactionOrigin::RetractedBlock) && + count <= self.limit + }) .map(|order| by_hash.get(&order.hash) .expect("All transactions in `self.by_priority` and `self.by_address` are kept in sync with `by_hash`.")) .map(|tx| (tx.sender(), tx.nonce())) @@ -432,16 +441,17 @@ impl Default for TransactionQueue { impl TransactionQueue { /// Creates new instance of this Queue pub fn new() -> Self { - Self::with_limits(1024, !U256::zero()) + Self::with_limits(1024, !U256::zero(), !U256::zero()) } /// Create new instance of this Queue with specified limits - pub fn with_limits(limit: usize, tx_gas_limit: U256) -> Self { + pub fn with_limits(limit: usize, gas_limit: U256, tx_gas_limit: U256) -> Self { let current = TransactionSet { by_priority: BTreeSet::new(), by_address: Table::new(), by_gas_price: Default::default(), limit: limit, + gas_limit: gas_limit, }; let future = TransactionSet { @@ -449,6 +459,7 @@ impl TransactionQueue { by_address: Table::new(), by_gas_price: Default::default(), limit: limit, + gas_limit: gas_limit, }; TransactionQueue { @@ -504,6 +515,13 @@ impl TransactionQueue { }; } + /// Sets new total gas limit. + pub fn set_total_gas_limit(&mut self, gas_limit: U256) { + self.future.gas_limit = gas_limit; + self.current.gas_limit = gas_limit; + self.future.enforce_limit(&mut self.by_hash); + } + /// Set the new limit for the amount of gas any individual transaction may have. /// Any transaction already imported to the queue is not affected. pub fn set_tx_gas_limit(&mut self, limit: U256) { @@ -636,7 +654,7 @@ impl TransactionQueue { }; for k in nonces_from_sender { let order = self.future.drop(&sender, &k).unwrap(); - self.current.insert(sender, k, order.penalize()); + self.future.insert(sender, k, order.penalize()); } } @@ -735,6 +753,15 @@ impl TransactionQueue { .collect() } + #[cfg(test)] + fn future_transactions(&self) -> Vec { + self.future.by_priority + .iter() + .map(|t| self.by_hash.get(&t.hash).expect("All transactions in `current` and `future` are always included in `by_hash`")) + .map(|t| t.transaction.clone()) + .collect() + } + /// Returns hashes of all transactions from current, ordered by priority. pub fn pending_hashes(&self) -> Vec { self.current.by_priority @@ -818,6 +845,16 @@ impl TransactionQueue { let nonce = tx.nonce(); let hash = tx.hash(); + { + // Rough size sanity check + let gas = &tx.transaction.gas; + if U256::from(tx.transaction.data.len()) > *gas { + // Droping transaction + trace!(target: "txqueue", "Dropping oversized transaction: {:?} (gas: {} < size {})", hash, gas, tx.transaction.data.len()); + return Err(TransactionError::LimitReached); + } + } + // The transaction might be old, let's check that. // This has to be the first test, otherwise calculating // nonce height would result in overflow. @@ -970,6 +1007,7 @@ mod test { } fn default_nonce() -> U256 { 123.into() } + fn default_gas_val() -> U256 { 100_000.into() } fn default_gas_price() -> U256 { 1.into() } fn new_unsigned_tx(nonce: U256, gas_price: U256) -> Transaction { @@ -977,7 +1015,7 @@ mod test { action: Action::Create, value: U256::from(100), data: "3331600055".from_hex().unwrap(), - gas: U256::from(100_000), + gas: default_gas_val(), gas_price: gas_price, nonce: nonce } @@ -1042,7 +1080,7 @@ mod test { #[test] fn should_return_correct_nonces_when_dropped_because_of_limit() { // given - let mut txq = TransactionQueue::with_limits(2, !U256::zero()); + let mut txq = TransactionQueue::with_limits(2, !U256::zero(), !U256::zero()); let (tx1, tx2) = new_tx_pair(123.into(), 1.into(), 1.into(), 0.into()); let sender = tx1.sender().unwrap(); let nonce = tx1.nonce; @@ -1080,7 +1118,8 @@ mod test { by_priority: BTreeSet::new(), by_address: Table::new(), by_gas_price: Default::default(), - limit: 1 + limit: 1, + gas_limit: !U256::zero(), }; let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); let tx1 = VerifiedTransaction::new(tx1, TransactionOrigin::External).unwrap(); @@ -1120,7 +1159,8 @@ mod test { by_priority: BTreeSet::new(), by_address: Table::new(), by_gas_price: Default::default(), - limit: 1 + limit: 1, + gas_limit: !U256::zero(), }; // Create two transactions with same nonce // (same hash) @@ -1168,7 +1208,8 @@ mod test { by_priority: BTreeSet::new(), by_address: Table::new(), by_gas_price: Default::default(), - limit: 2 + limit: 2, + gas_limit: !U256::zero(), }; let tx = new_tx_default(); let tx1 = VerifiedTransaction::new(tx.clone(), TransactionOrigin::External).unwrap(); @@ -1185,7 +1226,8 @@ mod test { by_priority: BTreeSet::new(), by_address: Table::new(), by_gas_price: Default::default(), - limit: 1 + limit: 1, + gas_limit: !U256::zero(), }; assert_eq!(set.gas_price_entry_limit(), 0.into()); @@ -1463,6 +1505,36 @@ mod test { assert_eq!(top.len(), 2); } + #[test] + fn should_penalize_transactions_from_sender_in_future() { + // given + let prev_nonce = |a: &Address| AccountDetails{ nonce: default_account_details(a).nonce - U256::one(), balance: !U256::zero() }; + let mut txq = TransactionQueue::new(); + // txa, txb - slightly bigger gas price to have consistent ordering + let (txa, txb) = new_tx_pair_default(1.into(), 0.into()); + let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); + + // insert everything + txq.add(txa.clone(), &prev_nonce, TransactionOrigin::External).unwrap(); + txq.add(txb.clone(), &prev_nonce, TransactionOrigin::External).unwrap(); + txq.add(tx1.clone(), &prev_nonce, TransactionOrigin::External).unwrap(); + txq.add(tx2.clone(), &prev_nonce, TransactionOrigin::External).unwrap(); + + assert_eq!(txq.status().future, 4); + + // when + txq.penalize(&tx1.hash()); + + // then + let top = txq.future_transactions(); + assert_eq!(top[0], txa); + assert_eq!(top[1], txb); + assert_eq!(top[2], tx1); + assert_eq!(top[3], tx2); + assert_eq!(top.len(), 4); + } + + #[test] fn should_penalize_transactions_from_sender() { // given @@ -1651,7 +1723,7 @@ mod test { #[test] fn should_drop_old_transactions_when_hitting_the_limit() { // given - let mut txq = TransactionQueue::with_limits(1, !U256::zero()); + let mut txq = TransactionQueue::with_limits(1, !U256::zero(), !U256::zero()); let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); let sender = tx.sender().unwrap(); let nonce = tx.nonce; @@ -1672,7 +1744,7 @@ mod test { #[test] fn should_limit_future_transactions() { - let mut txq = TransactionQueue::with_limits(1, !U256::zero()); + let mut txq = TransactionQueue::with_limits(1, !U256::zero(), !U256::zero()); txq.current.set_limit(10); let (tx1, tx2) = new_tx_pair_default(4.into(), 1.into()); let (tx3, tx4) = new_tx_pair_default(4.into(), 2.into()); @@ -1689,6 +1761,30 @@ mod test { assert_eq!(txq.status().future, 1); } + #[test] + fn should_limit_by_gas() { + let mut txq = TransactionQueue::with_limits(100, default_gas_val() * U256::from(2), !U256::zero()); + let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1)); + let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2)); + txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).ok(); + txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).ok(); + txq.add(tx3.clone(), &default_account_details, TransactionOrigin::External).ok(); + txq.add(tx4.clone(), &default_account_details, TransactionOrigin::External).ok(); + assert_eq!(txq.status().pending, 2); + } + + #[test] + fn should_keep_own_transactions_above_gas_limit() { + let mut txq = TransactionQueue::with_limits(100, default_gas_val() * U256::from(2), !U256::zero()); + let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1)); + let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2)); + txq.add(tx1.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); + txq.add(tx2.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); + txq.add(tx3.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); + txq.add(tx4.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); + assert_eq!(txq.status().pending, 4); + } + #[test] fn should_drop_transactions_with_old_nonces() { let mut txq = TransactionQueue::new(); @@ -1932,7 +2028,7 @@ mod test { #[test] fn should_keep_right_order_in_future() { // given - let mut txq = TransactionQueue::with_limits(1, !U256::zero()); + let mut txq = TransactionQueue::with_limits(1, !U256::zero(), !U256::zero()); let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); let prev_nonce = |a: &Address| AccountDetails { nonce: default_account_details(a).nonce - U256::one(), balance: default_account_details(a).balance }; diff --git a/ethcore/src/snapshot/mod.rs b/ethcore/src/snapshot/mod.rs index 2150ee226..a5e6b58bd 100644 --- a/ethcore/src/snapshot/mod.rs +++ b/ethcore/src/snapshot/mod.rs @@ -51,7 +51,7 @@ use rand::{Rng, OsRng}; pub use self::error::Error; pub use self::service::{Service, DatabaseRestore}; -pub use self::traits::{SnapshotService, RemoteSnapshotService}; +pub use self::traits::SnapshotService; pub use self::watcher::Watcher; pub use types::snapshot_manifest::ManifestData; pub use types::restoration_status::RestorationStatus; @@ -67,6 +67,12 @@ mod watcher; #[cfg(test)] mod tests; +/// IPC interfaces +#[cfg(feature="ipc")] +pub mod remote { + pub use super::traits::RemoteSnapshotService; +} + mod traits { #![allow(dead_code, unused_assignments, unused_variables, missing_docs)] // codegen issues include!(concat!(env!("OUT_DIR"), "/snapshot_service_trait.rs")); diff --git a/ethcore/src/state/account.rs b/ethcore/src/state/account.rs index bd7ed810b..79a9e8ef1 100644 --- a/ethcore/src/state/account.rs +++ b/ethcore/src/state/account.rs @@ -16,7 +16,6 @@ //! Single account in the system. -use std::collections::hash_map::Entry; use util::*; use pod_account::*; use rlp::*; @@ -24,9 +23,11 @@ use lru_cache::LruCache; use std::cell::{RefCell, Cell}; -const STORAGE_CACHE_ITEMS: usize = 4096; +const STORAGE_CACHE_ITEMS: usize = 8192; /// Single account in the system. +/// Keeps track of changes to the code and storage. +/// The changes are applied in `commit_storage` and `commit_code` pub struct Account { // Balance of the account. balance: U256, @@ -46,8 +47,6 @@ pub struct Account { code_size: Option, // Code cache of the account. code_cache: Arc, - // Account is new or has been modified. - filth: Filth, // Account code new or has been modified. code_filth: Filth, // Cached address hash. @@ -67,7 +66,6 @@ impl Account { code_hash: code.sha3(), code_size: Some(code.len()), code_cache: Arc::new(code), - filth: Filth::Dirty, code_filth: Filth::Dirty, address_hash: Cell::new(None), } @@ -89,7 +87,6 @@ impl Account { code_filth: Filth::Dirty, code_size: Some(pod.code.as_ref().map_or(0, |c| c.len())), code_cache: Arc::new(pod.code.map_or_else(|| { warn!("POD account with unknown code is being created! Assuming no code."); vec![] }, |c| c)), - filth: Filth::Dirty, address_hash: Cell::new(None), } } @@ -105,7 +102,6 @@ impl Account { code_hash: SHA3_EMPTY, code_cache: Arc::new(vec![]), code_size: Some(0), - filth: Filth::Dirty, code_filth: Filth::Clean, address_hash: Cell::new(None), } @@ -123,7 +119,6 @@ impl Account { code_hash: r.val_at(3), code_cache: Arc::new(vec![]), code_size: None, - filth: Filth::Clean, code_filth: Filth::Clean, address_hash: Cell::new(None), } @@ -141,7 +136,6 @@ impl Account { code_hash: SHA3_EMPTY, code_cache: Arc::new(vec![]), code_size: None, - filth: Filth::Dirty, code_filth: Filth::Clean, address_hash: Cell::new(None), } @@ -153,7 +147,6 @@ impl Account { self.code_hash = code.sha3(); self.code_cache = Arc::new(code); self.code_size = Some(self.code_cache.len()); - self.filth = Filth::Dirty; self.code_filth = Filth::Dirty; } @@ -164,17 +157,7 @@ impl Account { /// Set (and cache) the contents of the trie's storage at `key` to `value`. pub fn set_storage(&mut self, key: H256, value: H256) { - match self.storage_changes.entry(key) { - Entry::Occupied(ref mut entry) if entry.get() != &value => { - entry.insert(value); - self.filth = Filth::Dirty; - }, - Entry::Vacant(entry) => { - entry.insert(value); - self.filth = Filth::Dirty; - }, - _ => {}, - } + self.storage_changes.insert(key, value); } /// Get (and cache) the contents of the trie's storage at `key`. @@ -263,17 +246,6 @@ impl Account { !self.code_cache.is_empty() || (self.code_cache.is_empty() && self.code_hash == SHA3_EMPTY) } - /// Is this a new or modified account? - pub fn is_dirty(&self) -> bool { - self.filth == Filth::Dirty || self.code_filth == Filth::Dirty || !self.storage_is_clean() - } - - /// Mark account as clean. - pub fn set_clean(&mut self) { - assert!(self.storage_is_clean()); - self.filth = Filth::Clean - } - /// Provide a database to get `code_hash`. Should not be called if it is a contract without code. pub fn cache_code(&mut self, db: &HashDB) -> bool { // TODO: fill out self.code_cache; @@ -326,25 +298,18 @@ impl Account { /// Increment the nonce of the account by one. pub fn inc_nonce(&mut self) { self.nonce = self.nonce + U256::from(1u8); - self.filth = Filth::Dirty; } - /// Increment the nonce of the account by one. + /// Increase account balance. pub fn add_balance(&mut self, x: &U256) { - if !x.is_zero() { - self.balance = self.balance + *x; - self.filth = Filth::Dirty; - } + self.balance = self.balance + *x; } - /// Increment the nonce of the account by one. + /// Decrease account balance. /// Panics if balance is less than `x` pub fn sub_balance(&mut self, x: &U256) { - if !x.is_zero() { - assert!(self.balance >= *x); - self.balance = self.balance - *x; - self.filth = Filth::Dirty; - } + assert!(self.balance >= *x); + self.balance = self.balance - *x; } /// Commit the `storage_changes` to the backing DB and update `storage_root`. @@ -406,7 +371,6 @@ impl Account { code_hash: self.code_hash.clone(), code_size: self.code_size.clone(), code_cache: self.code_cache.clone(), - filth: self.filth, code_filth: self.code_filth, address_hash: self.address_hash.clone(), } @@ -427,10 +391,10 @@ impl Account { account } - /// Replace self with the data from other account merging storage cache - pub fn merge_with(&mut self, other: Account) { - assert!(self.storage_is_clean()); - assert!(other.storage_is_clean()); + /// Replace self with the data from other account merging storage cache. + /// Basic account data and all modifications are overwritten + /// with new values. + pub fn overwrite_with(&mut self, other: Account) { self.balance = other.balance; self.nonce = other.nonce; self.storage_root = other.storage_root; @@ -443,6 +407,7 @@ impl Account { for (k, v) in other.storage_cache.into_inner().into_iter() { cache.insert(k.clone() , v.clone()); //TODO: cloning should not be required here } + self.storage_changes = other.storage_changes; } } diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index a2fe25b91..39c8bbc11 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -15,6 +15,7 @@ // along with Parity. If not, see . use std::cell::{RefCell, RefMut}; +use std::collections::hash_map::Entry; use common::*; use engines::Engine; use executive::{Executive, TransactOptions}; @@ -42,42 +43,93 @@ pub struct ApplyOutcome { /// Result type for the execution ("application") of a transaction. pub type ApplyResult = Result; -#[derive(Debug)] -enum AccountEntry { - /// Contains account data. - Cached(Account), - /// Account has been deleted. - Killed, - /// Account does not exist. - Missing, +#[derive(Eq, PartialEq, Clone, Copy, Debug)] +/// Account modification state. Used to check if the account was +/// Modified in between commits and overall. +enum AccountState { + /// Account was loaded from disk and never modified in this state object. + CleanFresh, + /// Account was loaded from the global cache and never modified. + CleanCached, + /// Account has been modified and is not committed to the trie yet. + /// This is set if any of the account data is changed, including + /// storage and code. + Dirty, + /// Account was modified and committed to the trie. + Committed, } +#[derive(Debug)] +/// In-memory copy of the account data. Holds the optional account +/// and the modification status. +/// Account entry can contain existing (`Some`) or non-existing +/// account (`None`) +struct AccountEntry { + account: Option, + state: AccountState, +} + +// Account cache item. Contains account data and +// modification state impl AccountEntry { fn is_dirty(&self) -> bool { - match *self { - AccountEntry::Cached(ref a) => a.is_dirty(), - AccountEntry::Killed => true, - AccountEntry::Missing => false, - } + self.state == AccountState::Dirty } - /// Clone dirty data into new `AccountEntry`. + /// Clone dirty data into new `AccountEntry`. This includes + /// basic account data and modified storage keys. /// Returns None if clean. - fn clone_dirty(&self) -> Option { - match *self { - AccountEntry::Cached(ref acc) if acc.is_dirty() => Some(AccountEntry::Cached(acc.clone_dirty())), - AccountEntry::Killed => Some(AccountEntry::Killed), - _ => None, + fn clone_if_dirty(&self) -> Option { + match self.is_dirty() { + true => Some(self.clone_dirty()), + false => None, } } - /// Clone account entry data that needs to be saved in the snapshot. - /// This includes basic account information and all locally cached storage keys - fn clone_for_snapshot(&self) -> AccountEntry { - match *self { - AccountEntry::Cached(ref acc) => AccountEntry::Cached(acc.clone_all()), - AccountEntry::Killed => AccountEntry::Killed, - AccountEntry::Missing => AccountEntry::Missing, + /// Clone dirty data into new `AccountEntry`. This includes + /// basic account data and modified storage keys. + fn clone_dirty(&self) -> AccountEntry { + AccountEntry { + account: self.account.as_ref().map(Account::clone_dirty), + state: self.state, + } + } + + // Create a new account entry and mark it as dirty. + fn new_dirty(account: Option) -> AccountEntry { + AccountEntry { + account: account, + state: AccountState::Dirty, + } + } + + // Create a new account entry and mark it as clean. + fn new_clean(account: Option) -> AccountEntry { + AccountEntry { + account: account, + state: AccountState::CleanFresh, + } + } + + // Create a new account entry and mark it as clean and cached. + fn new_clean_cached(account: Option) -> AccountEntry { + AccountEntry { + account: account, + state: AccountState::CleanCached, + } + } + + // Replace data with another entry but preserve storage cache. + fn overwrite_with(&mut self, other: AccountEntry) { + self.state = other.state; + match other.account { + Some(acc) => match self.account { + Some(ref mut ours) => { + ours.overwrite_with(acc); + }, + None => {}, + }, + None => self.account = None, } } } @@ -90,6 +142,9 @@ impl AccountEntry { /// locally from previous commits. Global cache reflects the database /// state and never contains any changes. /// +/// Cache items contains account data, or the flag that account does not exist +/// and modification state (see `AccountState`) +/// /// Account data can be in the following cache states: /// * In global but not local - something that was queried from the database, /// but never modified @@ -103,12 +158,32 @@ impl AccountEntry { /// then global state cache. If data is not found in any of the caches /// it is loaded from the DB to the local cache. /// -/// Upon destruction all the local cache data merged into the global cache. -/// The merge might be rejected if current state is non-canonical. +/// **** IMPORTANT ************************************************************* +/// All the modifications to the account data must set the `Dirty` state in the +/// `AccountEntry`. This is done in `require` and `require_or_from`. So just +/// use that. +/// **************************************************************************** +/// +/// Upon destruction all the local cache data propagated into the global cache. +/// Propagated items might be rejected if current state is non-canonical. +/// +/// State snapshotting. +/// +/// A new snapshot can be created with `snapshot()`. Snapshots can be +/// created in a hierarchy. +/// When a snapshot is active all changes are applied directly into +/// `cache` and the original value is copied into an active snapshot. +/// Reverting a snapshot with `revert_to_snapshot` involves copying +/// original values from the latest snapshot back into `cache`. The code +/// takes care not to overwrite cached storage while doing that. +/// Snapshot can be discateded with `discard_snapshot`. All of the orignal +/// backed-up values are moved into a parent snapshot (if any). +/// pub struct State { db: StateDB, root: H256, cache: RefCell>, + // The original account is preserved in snapshots: RefCell>>>, account_start_nonce: U256, factories: Factories, @@ -162,35 +237,48 @@ impl State { Ok(state) } - /// Create a recoverable snaphot of this state + /// Create a recoverable snaphot of this state. pub fn snapshot(&mut self) { self.snapshots.borrow_mut().push(HashMap::new()); } - /// Merge last snapshot with previous - pub fn clear_snapshot(&mut self) { + /// Merge last snapshot with previous. + pub fn discard_snapshot(&mut self) { // merge with previous snapshot let last = self.snapshots.borrow_mut().pop(); if let Some(mut snapshot) = last { if let Some(ref mut prev) = self.snapshots.borrow_mut().last_mut() { - for (k, v) in snapshot.drain() { - prev.entry(k).or_insert(v); + if prev.is_empty() { + **prev = snapshot; + } else { + for (k, v) in snapshot.drain() { + prev.entry(k).or_insert(v); + } } } } } - /// Revert to snapshot - pub fn revert_snapshot(&mut self) { + /// Revert to the last snapshot and discard it. + pub fn revert_to_snapshot(&mut self) { if let Some(mut snapshot) = self.snapshots.borrow_mut().pop() { for (k, v) in snapshot.drain() { match v { Some(v) => { - self.cache.borrow_mut().insert(k, v); + match self.cache.borrow_mut().entry(k) { + Entry::Occupied(mut e) => { + // Merge snapshotted changes back into the main account + // storage preserving the cache. + e.get_mut().overwrite_with(v); + }, + Entry::Vacant(e) => { + e.insert(v); + } + } }, None => { match self.cache.borrow_mut().entry(k) { - ::std::collections::hash_map::Entry::Occupied(e) => { + Entry::Occupied(e) => { if e.get().is_dirty() { e.remove(); } @@ -204,10 +292,17 @@ impl State { } fn insert_cache(&self, address: &Address, account: AccountEntry) { - if let Some(ref mut snapshot) = self.snapshots.borrow_mut().last_mut() { - if !snapshot.contains_key(address) { - snapshot.insert(address.clone(), self.cache.borrow_mut().insert(address.clone(), account)); - return; + // Dirty account which is not in the cache means this is a new account. + // It goes directly into the snapshot as there's nothing to rever to. + // + // In all other cases account is read as clean first, and after that made + // dirty in and added to the snapshot with `note_cache`. + if account.is_dirty() { + if let Some(ref mut snapshot) = self.snapshots.borrow_mut().last_mut() { + if !snapshot.contains_key(address) { + snapshot.insert(address.clone(), self.cache.borrow_mut().insert(address.clone(), account)); + return; + } } } self.cache.borrow_mut().insert(address.clone(), account); @@ -216,14 +311,14 @@ impl State { fn note_cache(&self, address: &Address) { if let Some(ref mut snapshot) = self.snapshots.borrow_mut().last_mut() { if !snapshot.contains_key(address) { - snapshot.insert(address.clone(), self.cache.borrow().get(address).map(AccountEntry::clone_for_snapshot)); + snapshot.insert(address.clone(), self.cache.borrow().get(address).map(AccountEntry::clone_dirty)); } } } /// Destroy the current object and return root and database. pub fn drop(mut self) -> (H256, StateDB) { - self.commit_cache(); + self.propagate_to_global_cache(); (self.root, self.db) } @@ -235,12 +330,12 @@ impl State { /// Create a new contract at address `contract`. If there is already an account at the address /// it will have its code reset, ready for `init_code()`. pub fn new_contract(&mut self, contract: &Address, balance: U256) { - self.insert_cache(contract, AccountEntry::Cached(Account::new_contract(balance, self.account_start_nonce))); + self.insert_cache(contract, AccountEntry::new_dirty(Some(Account::new_contract(balance, self.account_start_nonce)))); } /// Remove an existing account. pub fn kill_account(&mut self, account: &Address) { - self.insert_cache(account, AccountEntry::Killed); + self.insert_cache(account, AccountEntry::new_dirty(None)); } /// Determine whether an account exists. @@ -272,8 +367,8 @@ impl State { let local_cache = self.cache.borrow_mut(); let mut local_account = None; if let Some(maybe_acc) = local_cache.get(address) { - match *maybe_acc { - AccountEntry::Cached(ref account) => { + match maybe_acc.account { + Some(ref account) => { if let Some(value) = account.cached_storage_at(key) { return value; } else { @@ -292,7 +387,7 @@ impl State { return result; } if let Some(ref mut acc) = local_account { - if let AccountEntry::Cached(ref account) = **acc { + if let Some(ref account) = acc.account { let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), account.address_hash(address)); return account.storage_at(account_db.as_hashdb(), key) } else { @@ -314,10 +409,7 @@ impl State { let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), a.address_hash(address)); a.storage_at(account_db.as_hashdb(), key) }); - match maybe_acc { - Some(account) => self.insert_cache(address, AccountEntry::Cached(account)), - None => self.insert_cache(address, AccountEntry::Missing), - } + self.insert_cache(address, AccountEntry::new_clean(maybe_acc)); r } @@ -341,13 +433,17 @@ impl State { /// Add `incr` to the balance of account `a`. pub fn add_balance(&mut self, a: &Address, incr: &U256) { trace!(target: "state", "add_balance({}, {}): {}", a, incr, self.balance(a)); - self.require(a, false).add_balance(incr); + if !incr.is_zero() || !self.exists(a) { + self.require(a, false).add_balance(incr); + } } /// Subtract `decr` from the balance of account `a`. pub fn sub_balance(&mut self, a: &Address, decr: &U256) { trace!(target: "state", "sub_balance({}, {}): {}", a, decr, self.balance(a)); - self.require(a, false).sub_balance(decr); + if !decr.is_zero() || !self.exists(a) { + self.require(a, false).sub_balance(decr); + } } /// Subtracts `by` from the balance of `from` and adds it to that of `to`. @@ -363,7 +459,9 @@ impl State { /// Mutate storage of account `a` so that it is `value` for `key`. pub fn set_storage(&mut self, a: &Address, key: H256, value: H256) { - self.require(a, false).set_storage(key, value) + if self.storage_at(a, &key) != value { + self.require(a, false).set_storage(key, value) + } } /// Initialise the code of account `a` so that it is `code`. @@ -404,10 +502,9 @@ impl State { accounts: &mut HashMap ) -> Result<(), Error> { // first, commit the sub trees. - // TODO: is this necessary or can we dispense with the `ref mut a` for just `a`? - for (address, ref mut a) in accounts.iter_mut() { - match a { - &mut&mut AccountEntry::Cached(ref mut account) if account.is_dirty() => { + for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) { + match a.account { + Some(ref mut account) => { db.note_account_bloom(&address); let addr_hash = account.address_hash(address); let mut account_db = factories.accountdb.create(db.as_hashdb_mut(), addr_hash); @@ -420,17 +517,15 @@ impl State { { let mut trie = factories.trie.from_existing(db.as_hashdb_mut(), root).unwrap(); - for (address, ref mut a) in accounts.iter_mut() { - match **a { - AccountEntry::Cached(ref mut account) if account.is_dirty() => { - account.set_clean(); + for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) { + a.state = AccountState::Committed; + match a.account { + Some(ref mut account) => { try!(trie.insert(address, &account.rlp())); }, - AccountEntry::Killed => { + None => { try!(trie.remove(address)); - **a = AccountEntry::Missing; }, - _ => {}, } } } @@ -438,20 +533,12 @@ impl State { Ok(()) } - fn commit_cache(&mut self) { + /// Propagate local cache into shared canonical state cache. + fn propagate_to_global_cache(&mut self) { let mut addresses = self.cache.borrow_mut(); - for (address, a) in addresses.drain() { - match a { - AccountEntry::Cached(account) => { - if !account.is_dirty() { - self.db.cache_account(address, Some(account)); - } - }, - AccountEntry::Missing => { - self.db.cache_account(address, None); - }, - _ => {}, - } + trace!("Committing cache {:?} entries", addresses.len()); + for (address, a) in addresses.drain().filter(|&(_, ref a)| a.state == AccountState::Committed || a.state == AccountState::CleanFresh) { + self.db.add_to_account_cache(address, a.account, a.state == AccountState::Committed); } } @@ -473,7 +560,7 @@ impl State { assert!(self.snapshots.borrow().is_empty()); for (add, acc) in accounts.drain().into_iter() { self.db.note_account_bloom(&add); - self.cache.borrow_mut().insert(add, AccountEntry::Cached(Account::from_pod(acc))); + self.cache.borrow_mut().insert(add, AccountEntry::new_dirty(Some(Account::from_pod(acc)))); } } @@ -483,7 +570,7 @@ impl State { // TODO: handle database rather than just the cache. // will need fat db. PodState::from(self.cache.borrow().iter().fold(BTreeMap::new(), |mut m, (add, opt)| { - if let AccountEntry::Cached(ref acc) = *opt { + if let Some(ref acc) = opt.account { m.insert(add.clone(), PodAccount::from_account(acc)); } m @@ -530,7 +617,7 @@ impl State { where F: Fn(Option<&Account>) -> U { // check local cache first if let Some(ref mut maybe_acc) = self.cache.borrow_mut().get_mut(a) { - if let AccountEntry::Cached(ref mut account) = **maybe_acc { + if let Some(ref mut account) = maybe_acc.account { let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), account.address_hash(a)); Self::update_account_cache(require, account, accountdb.as_hashdb()); return f(Some(account)); @@ -562,10 +649,7 @@ impl State { Self::update_account_cache(require, account, accountdb.as_hashdb()); } let r = f(maybe_acc.as_ref()); - match maybe_acc { - Some(account) => self.insert_cache(a, AccountEntry::Cached(account)), - None => self.insert_cache(a, AccountEntry::Missing), - } + self.insert_cache(a, AccountEntry::new_clean(maybe_acc)); r } } @@ -584,36 +668,38 @@ impl State { let contains_key = self.cache.borrow().contains_key(a); if !contains_key { match self.db.get_cached_account(a) { - Some(Some(acc)) => self.insert_cache(a, AccountEntry::Cached(acc)), - Some(None) => self.insert_cache(a, AccountEntry::Missing), + Some(acc) => self.insert_cache(a, AccountEntry::new_clean_cached(acc)), None => { let maybe_acc = if self.db.check_account_bloom(a) { let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR); let maybe_acc = match db.get(a) { - Ok(Some(acc)) => AccountEntry::Cached(Account::from_rlp(acc)), - Ok(None) => AccountEntry::Missing, + Ok(Some(acc)) => AccountEntry::new_clean(Some(Account::from_rlp(acc))), + Ok(None) => AccountEntry::new_clean(None), Err(e) => panic!("Potential DB corruption encountered: {}", e), }; maybe_acc } else { - AccountEntry::Missing + AccountEntry::new_clean(None) }; self.insert_cache(a, maybe_acc); } } - } else { - self.note_cache(a); - } - - match self.cache.borrow_mut().get_mut(a).unwrap() { - &mut AccountEntry::Cached(ref mut acc) => not_default(acc), - slot => *slot = AccountEntry::Cached(default()), + } + self.note_cache(a); + + match &mut self.cache.borrow_mut().get_mut(a).unwrap().account { + &mut Some(ref mut acc) => not_default(acc), + slot => *slot = Some(default()), } + // at this point the account is guaranteed to be in the cache. RefMut::map(self.cache.borrow_mut(), |c| { - match c.get_mut(a).unwrap() { - &mut AccountEntry::Cached(ref mut account) => { + let mut entry = c.get_mut(a).unwrap(); + // set the dirty flag after changing account data. + entry.state = AccountState::Dirty; + match entry.account { + Some(ref mut account) => { if require_code { let addr_hash = account.address_hash(a); let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), addr_hash); @@ -638,7 +724,7 @@ impl Clone for State { let cache = { let mut cache: HashMap = HashMap::new(); for (key, val) in self.cache.borrow().iter() { - if let Some(entry) = val.clone_dirty() { + if let Some(entry) = val.clone_if_dirty() { cache.insert(key.clone(), entry); } } @@ -1679,12 +1765,12 @@ fn snapshot_basic() { state.snapshot(); state.add_balance(&a, &U256::from(69u64)); assert_eq!(state.balance(&a), U256::from(69u64)); - state.clear_snapshot(); + state.discard_snapshot(); assert_eq!(state.balance(&a), U256::from(69u64)); state.snapshot(); state.add_balance(&a, &U256::from(1u64)); assert_eq!(state.balance(&a), U256::from(70u64)); - state.revert_snapshot(); + state.revert_to_snapshot(); assert_eq!(state.balance(&a), U256::from(69u64)); } @@ -1697,9 +1783,9 @@ fn snapshot_nested() { state.snapshot(); state.add_balance(&a, &U256::from(69u64)); assert_eq!(state.balance(&a), U256::from(69u64)); - state.clear_snapshot(); + state.discard_snapshot(); assert_eq!(state.balance(&a), U256::from(69u64)); - state.revert_snapshot(); + state.revert_to_snapshot(); assert_eq!(state.balance(&a), U256::from(0)); } diff --git a/ethcore/src/state_db.rs b/ethcore/src/state_db.rs index 7a1206801..04db274c4 100644 --- a/ethcore/src/state_db.rs +++ b/ethcore/src/state_db.rs @@ -14,56 +14,94 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::collections::{VecDeque, HashSet}; use lru_cache::LruCache; use util::journaldb::JournalDB; use util::hash::{H256}; use util::hashdb::HashDB; use state::Account; +use header::BlockNumber; use util::{Arc, Address, Database, DBTransaction, UtilError, Mutex, Hashable}; use bloom_journal::{Bloom, BloomJournal}; use db::COL_ACCOUNT_BLOOM; use byteorder::{LittleEndian, ByteOrder}; -const STATE_CACHE_ITEMS: usize = 65536; +const STATE_CACHE_ITEMS: usize = 256000; +const STATE_CACHE_BLOCKS: usize = 8; pub const ACCOUNT_BLOOM_SPACE: usize = 1048576; pub const DEFAULT_ACCOUNT_PRESET: usize = 1000000; pub const ACCOUNT_BLOOM_HASHCOUNT_KEY: &'static [u8] = b"account_hash_count"; +/// Shared canonical state cache. struct AccountCache { /// DB Account cache. `None` indicates that account is known to be missing. accounts: LruCache>, + /// Information on the modifications in recently committed blocks; specifically which addresses + /// changed in which block. Ordered by block number. + modifications: VecDeque, +} + +/// Buffered account cache item. +struct CacheQueueItem { + /// Account address. + address: Address, + /// Acccount data or `None` if account does not exist. + account: Option, + /// Indicates that the account was modified before being + /// added to the cache. + modified: bool, +} + +#[derive(Debug)] +/// Accumulates a list of accounts changed in a block. +struct BlockChanges { + /// Block number. + number: BlockNumber, + /// Block hash. + hash: H256, + /// Parent block hash. + parent: H256, + /// A set of modified account addresses. + accounts: HashSet

, + /// Block is part of the canonical chain. + is_canon: bool, } /// State database abstraction. -/// Manages shared global state cache. +/// Manages shared global state cache which reflects the canonical +/// state as it is on the disk. All the entries in the cache are clean. /// A clone of `StateDB` may be created as canonical or not. -/// For canonical clones cache changes are accumulated and applied -/// on commit. -/// For non-canonical clones cache is cleared on commit. +/// For canonical clones local cache is accumulated and applied +/// in `sync_cache` +/// For non-canonical clones local cache is dropped. +/// +/// Global cache propagation. +/// After a `State` object has been committed to the trie it +/// propagates its local cache into the `StateDB` local cache +/// using `add_to_account_cache` function. +/// Then, after the block has been added to the chain the local cache in the +/// `StateDB` is propagated into the global cache. pub struct StateDB { + /// Backing database. db: Box, + /// Shared canonical state cache. account_cache: Arc>, - cache_overlay: Vec<(Address, Option)>, - is_canon: bool, + /// Local dirty cache. + local_cache: Vec, + /// Shared account bloom. Does not handle chain reorganizations. account_bloom: Arc>, + /// Hash of the block on top of which this instance was created or + /// `None` if cache is disabled + parent_hash: Option, + /// Hash of the committing block or `None` if not committed yet. + commit_hash: Option, + /// Number of the committing block or `None` if not committed yet. + commit_number: Option, } impl StateDB { - - /// Create a new instance wrapping `JournalDB` - pub fn new(db: Box) -> StateDB { - let bloom = Self::load_bloom(db.backing()); - StateDB { - db: db, - account_cache: Arc::new(Mutex::new(AccountCache { accounts: LruCache::new(STATE_CACHE_ITEMS) })), - cache_overlay: Vec::new(), - is_canon: false, - account_bloom: Arc::new(Mutex::new(bloom)), - } - } - /// Loads accounts bloom from the database /// This bloom is used to handle request for the non-existant account fast pub fn load_bloom(db: &Database) -> Bloom { @@ -91,6 +129,23 @@ impl StateDB { bloom } + /// Create a new instance wrapping `JournalDB` + pub fn new(db: Box) -> StateDB { + let bloom = Self::load_bloom(db.backing()); + StateDB { + db: db, + account_cache: Arc::new(Mutex::new(AccountCache { + accounts: LruCache::new(STATE_CACHE_ITEMS), + modifications: VecDeque::new(), + })), + local_cache: Vec::new(), + account_bloom: Arc::new(Mutex::new(bloom)), + parent_hash: None, + commit_hash: None, + commit_number: None, + } + } + pub fn check_account_bloom(&self, address: &Address) -> bool { trace!(target: "account_bloom", "Check account bloom: {:?}", address); let bloom = self.account_bloom.lock(); @@ -125,14 +180,107 @@ impl StateDB { try!(Self::commit_bloom(batch, bloom_lock.drain_journal())); } let records = try!(self.db.commit(batch, now, id, end)); - if self.is_canon { - self.commit_cache(); - } else { - self.clear_cache(); - } + self.commit_hash = Some(id.clone()); + self.commit_number = Some(now); Ok(records) } + /// Propagate local cache into the global cache and synchonize + /// the global cache with the best block state. + /// This function updates the global cache by removing entries + /// that are invalidated by chain reorganization. `sync_cache` + /// should be called after the block has been committed and the + /// blockchain route has ben calculated. + pub fn sync_cache(&mut self, enacted: &[H256], retracted: &[H256], is_best: bool) { + trace!("sync_cache id = (#{:?}, {:?}), parent={:?}, best={}", self.commit_number, self.commit_hash, self.parent_hash, is_best); + let mut cache = self.account_cache.lock(); + let mut cache = &mut *cache; + + // Purge changes from re-enacted and retracted blocks. + // Filter out commiting block if any. + let mut clear = false; + for block in enacted.iter().filter(|h| self.commit_hash.as_ref().map_or(true, |p| *h != p)) { + clear = clear || { + if let Some(ref mut m) = cache.modifications.iter_mut().find(|ref m| &m.hash == block) { + trace!("Reverting enacted block {:?}", block); + m.is_canon = true; + for a in &m.accounts { + trace!("Reverting enacted address {:?}", a); + cache.accounts.remove(a); + } + false + } else { + true + } + }; + } + + for block in retracted { + clear = clear || { + if let Some(ref mut m) = cache.modifications.iter_mut().find(|ref m| &m.hash == block) { + trace!("Retracting block {:?}", block); + m.is_canon = false; + for a in &m.accounts { + trace!("Retracted address {:?}", a); + cache.accounts.remove(a); + } + false + } else { + true + } + }; + } + if clear { + // We don't know anything about the block; clear everything + trace!("Wiping cache"); + cache.accounts.clear(); + cache.modifications.clear(); + } + + // Propagate cache only if committing on top of the latest canonical state + // blocks are ordered by number and only one block with a given number is marked as canonical + // (contributed to canonical state cache) + if let (Some(ref number), Some(ref hash), Some(ref parent)) = (self.commit_number, self.commit_hash, self.parent_hash) { + if cache.modifications.len() == STATE_CACHE_BLOCKS { + cache.modifications.pop_back(); + } + let mut modifications = HashSet::new(); + trace!("committing {} cache entries", self.local_cache.len()); + for account in self.local_cache.drain(..) { + if account.modified { + modifications.insert(account.address.clone()); + } + if is_best { + if let Some(&mut Some(ref mut existing)) = cache.accounts.get_mut(&account.address) { + if let Some(new) = account.account { + if account.modified { + existing.overwrite_with(new); + } + continue; + } + } + cache.accounts.insert(account.address, account.account); + } + } + + // Save modified accounts. These are ordered by the block number. + let block_changes = BlockChanges { + accounts: modifications, + number: *number, + hash: hash.clone(), + is_canon: is_best, + parent: parent.clone(), + }; + let insert_at = cache.modifications.iter().enumerate().find(|&(_, ref m)| m.number < *number).map(|(i, _)| i); + trace!("inserting modifications at {:?}", insert_at); + if let Some(insert_at) = insert_at { + cache.modifications.insert(insert_at, block_changes); + } else { + cache.modifications.push_back(block_changes); + } + } + } + /// Returns an interface to HashDB. pub fn as_hashdb(&self) -> &HashDB { self.db.as_hashdb() @@ -148,20 +296,24 @@ impl StateDB { StateDB { db: self.db.boxed_clone(), account_cache: self.account_cache.clone(), - cache_overlay: Vec::new(), - is_canon: false, + local_cache: Vec::new(), account_bloom: self.account_bloom.clone(), + parent_hash: None, + commit_hash: None, + commit_number: None, } } /// Clone the database for a canonical state. - pub fn boxed_clone_canon(&self) -> StateDB { + pub fn boxed_clone_canon(&self, parent: &H256) -> StateDB { StateDB { db: self.db.boxed_clone(), account_cache: self.account_cache.clone(), - cache_overlay: Vec::new(), - is_canon: true, + local_cache: Vec::new(), account_bloom: self.account_bloom.clone(), + parent_hash: Some(parent.clone()), + commit_hash: None, + commit_number: None, } } @@ -180,53 +332,149 @@ impl StateDB { &*self.db } - /// Enqueue cache change. - pub fn cache_account(&mut self, addr: Address, data: Option) { - self.cache_overlay.push((addr, data)); - } - - /// Apply pending cache changes. - fn commit_cache(&mut self) { - let mut cache = self.account_cache.lock(); - for (address, account) in self.cache_overlay.drain(..) { - if let Some(&mut Some(ref mut existing)) = cache.accounts.get_mut(&address) { - if let Some(new) = account { - existing.merge_with(new); - continue; - } - } - cache.accounts.insert(address, account); - } - } - - /// Clear the cache. - pub fn clear_cache(&mut self) { - self.cache_overlay.clear(); - let mut cache = self.account_cache.lock(); - cache.accounts.clear(); + /// Add a local cache entry. + /// The entry will be propagated to the global cache in `sync_cache`. + /// `modified` indicates that the entry was changed since being read from disk or global cache. + /// `data` can be set to an existing (`Some`), or non-existing account (`None`). + pub fn add_to_account_cache(&mut self, addr: Address, data: Option, modified: bool) { + self.local_cache.push(CacheQueueItem { + address: addr, + account: data, + modified: modified, + }) } /// Get basic copy of the cached account. Does not include storage. - /// Returns 'None' if the state is non-canonical and cache is disabled - /// or if the account is not cached. + /// Returns 'None' if cache is disabled or if the account is not cached. pub fn get_cached_account(&self, addr: &Address) -> Option> { - if !self.is_canon { + let mut cache = self.account_cache.lock(); + if !Self::is_allowed(addr, &self.parent_hash, &cache.modifications) { return None; } - let mut cache = self.account_cache.lock(); cache.accounts.get_mut(&addr).map(|a| a.as_ref().map(|a| a.clone_basic())) } /// Get value from a cached account. - /// Returns 'None' if the state is non-canonical and cache is disabled - /// or if the account is not cached. + /// Returns 'None' if cache is disabled or if the account is not cached. pub fn get_cached(&self, a: &Address, f: F) -> Option where F: FnOnce(Option<&mut Account>) -> U { - if !self.is_canon { + let mut cache = self.account_cache.lock(); + if !Self::is_allowed(a, &self.parent_hash, &cache.modifications) { return None; } - let mut cache = self.account_cache.lock(); cache.accounts.get_mut(a).map(|c| f(c.as_mut())) } + + /// Check if the account can be returned from cache by matching current block parent hash against canonical + /// state and filtering out account modified in later blocks. + fn is_allowed(addr: &Address, parent_hash: &Option, modifications: &VecDeque) -> bool { + let mut parent = match *parent_hash { + None => { + trace!("Cache lookup skipped for {:?}: no parent hash", addr); + return false; + } + Some(ref parent) => parent, + }; + if modifications.is_empty() { + return true; + } + // Ignore all accounts modified in later blocks + // Modifications contains block ordered by the number + // We search for our parent in that list first and then for + // all its parent until we hit the canonical block, + // checking against all the intermediate modifications. + let mut iter = modifications.iter(); + while let Some(ref m) = iter.next() { + if &m.hash == parent { + if m.is_canon { + return true; + } + parent = &m.parent; + } + if m.accounts.contains(addr) { + trace!("Cache lookup skipped for {:?}: modified in a later block", addr); + return false; + } + } + trace!("Cache lookup skipped for {:?}: parent hash is unknown", addr); + return false; + } +} + +#[cfg(test)] +mod tests { + +use util::{U256, H256, FixedHash, Address, DBTransaction}; +use tests::helpers::*; +use state::Account; +use util::log::init_log; + +#[test] +fn state_db_smoke() { + init_log(); + + let mut state_db_result = get_temp_state_db(); + let state_db = state_db_result.take(); + let root_parent = H256::random(); + let address = Address::random(); + let h0 = H256::random(); + let h1a = H256::random(); + let h1b = H256::random(); + let h2a = H256::random(); + let h2b = H256::random(); + let h3a = H256::random(); + let h3b = H256::random(); + let mut batch = DBTransaction::new(state_db.journal_db().backing()); + + // blocks [ 3a(c) 2a(c) 2b 1b 1a(c) 0 ] + // balance [ 5 5 4 3 2 2 ] + let mut s = state_db.boxed_clone_canon(&root_parent); + s.add_to_account_cache(address, Some(Account::new_basic(2.into(), 0.into())), false); + s.commit(&mut batch, 0, &h0, None).unwrap(); + s.sync_cache(&[], &[], true); + + let mut s = state_db.boxed_clone_canon(&h0); + s.commit(&mut batch, 1, &h1a, None).unwrap(); + s.sync_cache(&[], &[], true); + + let mut s = state_db.boxed_clone_canon(&h0); + s.add_to_account_cache(address, Some(Account::new_basic(3.into(), 0.into())), true); + s.commit(&mut batch, 1, &h1b, None).unwrap(); + s.sync_cache(&[], &[], false); + + let mut s = state_db.boxed_clone_canon(&h1b); + s.add_to_account_cache(address, Some(Account::new_basic(4.into(), 0.into())), true); + s.commit(&mut batch, 2, &h2b, None).unwrap(); + s.sync_cache(&[], &[], false); + + let mut s = state_db.boxed_clone_canon(&h1a); + s.add_to_account_cache(address, Some(Account::new_basic(5.into(), 0.into())), true); + s.commit(&mut batch, 2, &h2a, None).unwrap(); + s.sync_cache(&[], &[], true); + + let mut s = state_db.boxed_clone_canon(&h2a); + s.commit(&mut batch, 3, &h3a, None).unwrap(); + s.sync_cache(&[], &[], true); + + let s = state_db.boxed_clone_canon(&h3a); + assert_eq!(s.get_cached_account(&address).unwrap().unwrap().balance(), &U256::from(5)); + + let s = state_db.boxed_clone_canon(&h1a); + assert!(s.get_cached_account(&address).is_none()); + + let s = state_db.boxed_clone_canon(&h2b); + assert!(s.get_cached_account(&address).is_none()); + + let s = state_db.boxed_clone_canon(&h1b); + assert!(s.get_cached_account(&address).is_none()); + + // reorg to 3b + // blocks [ 3b(c) 3a 2a 2b(c) 1b 1a 0 ] + let mut s = state_db.boxed_clone_canon(&h2b); + s.commit(&mut batch, 3, &h3b, None).unwrap(); + s.sync_cache(&[h1b.clone(), h2b.clone(), h3b.clone()], &[h1a.clone(), h2a.clone(), h3a.clone()], true); + let s = state_db.boxed_clone_canon(&h3a); + assert!(s.get_cached_account(&address).is_none()); +} } diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index 59e3699ac..067f28d39 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -57,7 +57,11 @@ fn should_return_registrar() { IoChannel::disconnected(), &db_config ).unwrap(); - assert_eq!(client.additional_params().get("registrar"), Some(&"52dff57a8a1532e6afb3dc07e2af58bb9eb05b3d".to_owned())); + let params = client.additional_params(); + let address = params.get("registrar").unwrap(); + + assert_eq!(address.len(), 40); + assert!(U256::from_str(address).is_ok()); } #[test] diff --git a/ethcore/src/tests/mod.rs b/ethcore/src/tests/mod.rs index db36a3762..4157e486d 100644 --- a/ethcore/src/tests/mod.rs +++ b/ethcore/src/tests/mod.rs @@ -16,4 +16,5 @@ pub mod helpers; mod client; +#[cfg(feature="ipc")] mod rpc; diff --git a/ethcore/src/tests/rpc.rs b/ethcore/src/tests/rpc.rs index d5d88c087..b021e750d 100644 --- a/ethcore/src/tests/rpc.rs +++ b/ethcore/src/tests/rpc.rs @@ -19,7 +19,8 @@ use nanoipc; use std::sync::Arc; use std::sync::atomic::{Ordering, AtomicBool}; -use client::{Client, BlockChainClient, ClientConfig, RemoteClient, BlockID}; +use client::{Client, BlockChainClient, ClientConfig, BlockID}; +use client::remote::RemoteClient; use tests::helpers::*; use devtools::*; use miner::Miner; diff --git a/ethcore/src/trace/db.rs b/ethcore/src/trace/db.rs index b608ad685..2cf14828a 100644 --- a/ethcore/src/trace/db.rs +++ b/ethcore/src/trace/db.rs @@ -256,16 +256,6 @@ impl TraceDatabase for TraceDB where T: DatabaseExtras { return; } - // at first, let's insert new block traces - { - let mut traces = self.traces.write(); - // it's important to use overwrite here, - // cause this value might be queried by hash later - batch.write_with_cache(db::COL_TRACE, &mut *traces, request.block_hash, request.traces, CacheUpdatePolicy::Overwrite); - // note_used must be called after locking traces to avoid cache/traces deadlock on garbage collection - self.note_used(CacheID::Trace(request.block_hash.clone())); - } - // now let's rebuild the blooms if !request.enacted.is_empty() { let range_start = request.block_number as Number + 1 - request.enacted.len(); @@ -276,12 +266,25 @@ impl TraceDatabase for TraceDB where T: DatabaseExtras { // all traces are expected to be found here. That's why `expect` has been used // instead of `filter_map`. If some traces haven't been found, it meens that // traces database is corrupted or incomplete. - .map(|block_hash| self.traces(block_hash).expect("Traces database is incomplete.")) - .map(|block_traces| block_traces.bloom()) + .map(|block_hash| if block_hash == &request.block_hash { + request.traces.bloom() + } else { + self.traces(block_hash).expect("Traces database is incomplete.").bloom() + }) .map(blooms::Bloom::from) .map(Into::into) .collect(); + // insert new block traces into the cache and the database + { + let mut traces = self.traces.write(); + // it's important to use overwrite here, + // cause this value might be queried by hash later + batch.write_with_cache(db::COL_TRACE, &mut *traces, request.block_hash, request.traces, CacheUpdatePolicy::Overwrite); + // note_used must be called after locking traces to avoid cache/traces deadlock on garbage collection + self.note_used(CacheID::Trace(request.block_hash.clone())); + } + let chain = BloomGroupChain::new(self.bloom_config, self); let trace_blooms = chain.replace(&replaced_range, enacted_blooms); let blooms_to_insert = trace_blooms.into_iter() diff --git a/ethcore/src/types/filter.rs b/ethcore/src/types/filter.rs index 6274d63f4..e3487e5f6 100644 --- a/ethcore/src/types/filter.rs +++ b/ethcore/src/types/filter.rs @@ -22,7 +22,7 @@ use client::BlockID; use log_entry::LogEntry; /// Blockchain Filter. -#[derive(Binary)] +#[derive(Binary, Debug, PartialEq)] pub struct Filter { /// Blockchain will be searched from this block. pub from_block: BlockID, diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index 3016412eb..e4d3b91c6 100644 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -76,15 +76,14 @@ impl DiskDirectory { .map(|entry| entry.path()) .collect::>(); - let files: Result, _> = paths.iter() - .map(fs::File::open) - .collect(); - - let files = try!(files); - - files.into_iter() - .map(json::KeyFile::load) - .zip(paths.into_iter()) + paths + .iter() + .map(|p| ( + fs::File::open(p) + .map_err(Error::from) + .and_then(|r| json::KeyFile::load(r).map_err(|e| Error::Custom(format!("{:?}", e)))), + p + )) .map(|(file, path)| match file { Ok(file) => Ok((path.clone(), SafeAccount::from_file( file, Some(path.file_name().and_then(|n| n.to_str()).expect("Keys have valid UTF8 names only.").to_owned()) diff --git a/ipc/codegen/src/lib.rs b/ipc/codegen/src/lib.rs index 94959b058..dc58c6a8a 100644 --- a/ipc/codegen/src/lib.rs +++ b/ipc/codegen/src/lib.rs @@ -56,7 +56,7 @@ pub fn expand(src: &std::path::Path, dst: &std::path::Path) { } #[cfg(feature = "with-syntex")] -pub fn register(reg: &mut syntex::Registry) { +pub fn register_cleaner(reg: &mut syntex::Registry) { use syntax::{ast, fold}; #[cfg(feature = "with-syntex")] @@ -66,6 +66,7 @@ pub fn register(reg: &mut syntex::Registry) { fn fold_attribute(&mut self, attr: ast::Attribute) -> Option { match attr.node.value.node { ast::MetaItemKind::List(ref n, _) if n == &"ipc" => { return None; } + ast::MetaItemKind::Word(ref n) if n == &"ipc" => { return None; } _ => {} } @@ -80,13 +81,18 @@ pub fn register(reg: &mut syntex::Registry) { fold::Folder::fold_crate(&mut StripAttributeFolder, krate) } + reg.add_post_expansion_pass(strip_attributes); +} + +#[cfg(feature = "with-syntex")] +pub fn register(reg: &mut syntex::Registry) { reg.add_attr("feature(custom_derive)"); reg.add_attr("feature(custom_attribute)"); reg.add_decorator("ipc", codegen::expand_ipc_implementation); reg.add_decorator("derive_Binary", serialization::expand_serialization_implementation); - reg.add_post_expansion_pass(strip_attributes); + register_cleaner(reg); } #[cfg(not(feature = "with-syntex"))] @@ -104,7 +110,34 @@ pub fn register(reg: &mut rustc_plugin::Registry) { } #[derive(Debug)] -pub enum Error { InvalidFileName, ExpandFailure } +pub enum Error { InvalidFileName, ExpandFailure, Io(std::io::Error) } + +impl std::convert::From for Error { + fn from(err: std::io::Error) -> Self { + Error::Io(err) + } +} + +pub fn derive_ipc_cond(src_path: &str, has_feature: bool) -> Result<(), Error> { + if has_feature { derive_ipc(src_path) } + else { cleanup_ipc(src_path) } +} + +pub fn cleanup_ipc(src_path: &str) -> Result<(), Error> { + use std::env; + use std::path::{Path, PathBuf}; + + let out_dir = env::var_os("OUT_DIR").unwrap(); + let file_name = try!(PathBuf::from(src_path).file_name().ok_or(Error::InvalidFileName).map(|val| val.to_str().unwrap().to_owned())); + let mut registry = syntex::Registry::new(); + register_cleaner(&mut registry); + if let Err(_) = registry.expand("", &Path::new(src_path), &Path::new(&out_dir).join(&file_name)) + { + // will be reported by compiler + return Err(Error::ExpandFailure) + } + Ok(()) +} pub fn derive_ipc(src_path: &str) -> Result<(), Error> { use std::env; @@ -113,11 +146,11 @@ pub fn derive_ipc(src_path: &str) -> Result<(), Error> { let out_dir = env::var_os("OUT_DIR").unwrap(); let file_name = try!(PathBuf::from(src_path).file_name().ok_or(Error::InvalidFileName).map(|val| val.to_str().unwrap().to_owned())); + let final_path = Path::new(&out_dir).join(&file_name); + let mut intermediate_file_name = file_name.clone(); intermediate_file_name.push_str(".rpc.in"); - let intermediate_path = Path::new(&out_dir).join(&intermediate_file_name); - let final_path = Path::new(&out_dir).join(&file_name); { let mut registry = syntex::Registry::new(); diff --git a/ipc/hypervisor/Cargo.toml b/ipc/hypervisor/Cargo.toml index a4c462bd0..d730b9bcf 100644 --- a/ipc/hypervisor/Cargo.toml +++ b/ipc/hypervisor/Cargo.toml @@ -13,6 +13,7 @@ nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git" } ethcore-ipc-nano = { path = "../nano" } semver = "0.2" log = "0.3" +time = "0.1" [build-dependencies] ethcore-ipc-codegen = { path = "../codegen" } diff --git a/ipc/hypervisor/src/lib.rs b/ipc/hypervisor/src/lib.rs index 78b8b04ce..c7543ca91 100644 --- a/ipc/hypervisor/src/lib.rs +++ b/ipc/hypervisor/src/lib.rs @@ -22,6 +22,7 @@ extern crate ethcore_ipc as ipc; extern crate ethcore_ipc_nano as nanoipc; extern crate semver; #[macro_use] extern crate log; +extern crate time; pub mod service; @@ -187,23 +188,40 @@ impl Hypervisor { } /// Waits for every required module to check in - pub fn wait_for_shutdown(&self) { + pub fn wait_for_shutdown(&self) -> bool { + use time::{PreciseTime, Duration}; + let mut worker = self.ipc_worker.write().unwrap(); + let start = PreciseTime::now(); while !self.modules_shutdown() { - worker.poll() + worker.poll(); + if start.to(PreciseTime::now()) > Duration::seconds(30) { + warn!("Some modules failed to shutdown gracefully, they will be terminated."); + break; + } } + self.modules_shutdown() } /// Shutdown the ipc and all managed child processes pub fn shutdown(&self) { let mut childs = self.processes.write().unwrap(); - for (ref mut module, _) in childs.iter_mut() { + for (ref module, _) in childs.iter() { trace!(target: "hypervisor", "Stopping process module: {}", module); self.service.send_shutdown(**module); } trace!(target: "hypervisor", "Waiting for shutdown..."); - self.wait_for_shutdown(); - trace!(target: "hypervisor", "All modules reported shutdown"); + if self.wait_for_shutdown() { + trace!(target: "hypervisor", "All modules reported shutdown"); + return; + } + + for (ref module, ref mut process) in childs.iter_mut() { + if self.service.is_running(**module) { + process.kill().unwrap(); + trace!("Terminated {}", module); + } + } } } diff --git a/ipc/hypervisor/src/service.rs.in b/ipc/hypervisor/src/service.rs.in index 6996765ec..e80a1ec30 100644 --- a/ipc/hypervisor/src/service.rs.in +++ b/ipc/hypervisor/src/service.rs.in @@ -39,7 +39,6 @@ pub struct ModuleState { shutdown: bool, } - #[ipc] pub trait ControlService { fn shutdown(&self) -> bool; @@ -106,6 +105,10 @@ impl HypervisorService { self.modules.read().unwrap().iter().filter(|&(_, module)| module.started && !module.shutdown).count() } + pub fn is_running(&self, id: IpcModuleId) -> bool { + self.modules.read().unwrap().get(&id).map(|module| module.started && !module.shutdown).unwrap_or(false) + } + pub fn send_shutdown(&self, module_id: IpcModuleId) { let modules = self.modules.read().unwrap(); modules.get(&module_id).map(|module| { diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index a411e6767..2363f1740 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -67,7 +67,8 @@ usd_per_eth = "auto" price_update_period = "hourly" gas_floor_target = "4700000" gas_cap = "6283184" -tx_queue_size = 1024 +tx_queue_size = 2048 +tx_queue_gas = "auto" tx_gas_limit = "6283184" extra_data = "Parity" remove_solved = false diff --git a/parity/cli/config.toml b/parity/cli/config.toml index a5ad55d40..4ab691679 100644 --- a/parity/cli/config.toml +++ b/parity/cli/config.toml @@ -41,6 +41,7 @@ reseal_on_txs = "all" reseal_min_period = 4000 price_update_period = "hourly" tx_queue_size = 2048 +tx_queue_gas = "auto" [footprint] tracing = "on" diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 082dbe8e4..10348b21b 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -193,8 +193,10 @@ usage! { or |c: &Config| otry!(c.mining).gas_cap.clone(), flag_extra_data: Option = None, or |c: &Config| otry!(c.mining).extra_data.clone().map(Some), - flag_tx_queue_size: usize = 1024usize, + flag_tx_queue_size: usize = 2048usize, or |c: &Config| otry!(c.mining).tx_queue_size.clone(), + flag_tx_queue_gas: String = "auto", + or |c: &Config| otry!(c.mining).tx_queue_gas.clone(), flag_remove_solved: bool = false, or |c: &Config| otry!(c.mining).remove_solved.clone(), flag_notify_work: Option = None, @@ -348,6 +350,7 @@ struct Mining { gas_cap: Option, extra_data: Option, tx_queue_size: Option, + tx_queue_gas: Option, remove_solved: Option, notify_work: Option>, } @@ -522,7 +525,8 @@ mod tests { flag_gas_floor_target: "4700000".into(), flag_gas_cap: "6283184".into(), flag_extra_data: Some("Parity".into()), - flag_tx_queue_size: 1024usize, + flag_tx_queue_size: 2048usize, + flag_tx_queue_gas: "auto".into(), flag_remove_solved: false, flag_notify_work: Some("http://localhost:3001".into()), @@ -673,6 +677,7 @@ mod tests { gas_floor_target: None, gas_cap: None, tx_queue_size: Some(2048), + tx_queue_gas: Some("auto".into()), tx_gas_limit: None, extra_data: None, remove_solved: None, diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index 861b7dafc..ca75c9ee0 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -44,7 +44,8 @@ Account Options: ACCOUNTS is a comma-delimited list of addresses. Implies --no-signer. (default: {flag_unlock:?}) --password FILE Provide a file containing a password for unlocking - an account. (default: {flag_password:?}) + an account. Leading and trailing whitespace is trimmed. + (default: {flag_password:?}) --keys-iterations NUM Specify the number of iterations to use when deriving key from the password (bigger is more secure) (default: {flag_keys_iterations}). @@ -183,6 +184,10 @@ Sealing/Mining Options: more than 32 characters. (default: {flag_extra_data:?}) --tx-queue-size LIMIT Maximum amount of transactions in the queue (waiting to be included in next block) (default: {flag_tx_queue_size}). + --tx-queue-gas LIMIT Maximum amount of total gas for external transactions in + the queue. LIMIT can be either an amount of gas or + 'auto' or 'off'. 'auto' sets the limit to be 2x + the current block gas limit. (default: {flag_tx_queue_gas}). --remove-solved Move solved blocks from the work package queue instead of cloning them. This gives a slightly faster import speed, but means that extra solutions diff --git a/parity/configuration.rs b/parity/configuration.rs index 811ba6097..54a72fab5 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -30,7 +30,7 @@ use rpc::{IpcConfiguration, HttpConfiguration}; use ethcore_rpc::NetworkSettings; use cache::CacheConfig; use helpers::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_price, replace_home, -geth_ipc_path, parity_ipc_path, to_bootnodes, to_addresses, to_address}; +geth_ipc_path, parity_ipc_path, to_bootnodes, to_addresses, to_address, to_gas_limit}; use params::{ResealPolicy, AccountsConfig, GasPricerConfig, MinerExtras, SpecType}; use ethcore_logger::Config as LogConfig; use dir::Directories; @@ -125,7 +125,7 @@ impl Configuration { ImportFromGethAccounts { to: dirs.keys, testnet: self.args.flag_testnet - } + } ); Cmd::Account(account_cmd) } else if self.args.cmd_wallet { @@ -348,6 +348,7 @@ impl Configuration { None => U256::max_value(), }, tx_queue_size: self.args.flag_tx_queue_size, + tx_queue_gas_limit: try!(to_gas_limit(&self.args.flag_tx_queue_gas)), pending_set: try!(to_pending_set(&self.args.flag_relay_set)), reseal_min_period: Duration::from_millis(self.args.flag_reseal_min_period), work_queue_size: self.args.flag_work_queue_size, diff --git a/parity/helpers.rs b/parity/helpers.rs index abdd5daa5..6f4f90953 100644 --- a/parity/helpers.rs +++ b/parity/helpers.rs @@ -22,7 +22,7 @@ use std::fs::File; use util::{clean_0x, U256, Uint, Address, path, CompactionProfile}; use util::journaldb::Algorithm; use ethcore::client::{Mode, BlockID, VMType, DatabaseCompactionProfile, ClientConfig}; -use ethcore::miner::PendingSet; +use ethcore::miner::{PendingSet, GasLimit}; use cache::CacheConfig; use dir::DatabaseDirectories; use upgrade::upgrade; @@ -93,6 +93,14 @@ pub fn to_pending_set(s: &str) -> Result { } } +pub fn to_gas_limit(s: &str) -> Result { + match s { + "auto" => Ok(GasLimit::Auto), + "off" => Ok(GasLimit::None), + other => Ok(GasLimit::Fixed(try!(to_u256(other)))), + } +} + pub fn to_address(s: Option) -> Result { match s { Some(ref a) => clean_0x(a).parse().map_err(|_| format!("Invalid address: {:?}", a)), @@ -273,9 +281,10 @@ pub fn password_prompt() -> Result { pub fn password_from_file

(path: P) -> Result where P: AsRef { let mut file = try!(File::open(path).map_err(|_| "Unable to open password file.")); let mut file_content = String::new(); - try!(file.read_to_string(&mut file_content).map_err(|_| "Unable to read password file.")); - // remove eof - Ok((&file_content[..file_content.len() - 1]).to_owned()) + match file.read_to_string(&mut file_content) { + Ok(_) => Ok(file_content.trim().into()), + Err(_) => Err("Unable to read password file.".into()), + } } /// Reads passwords from files. Treats each line as a separate password. @@ -294,10 +303,13 @@ pub fn passwords_from_files(files: Vec) -> Result, String> { #[cfg(test)] mod tests { use std::time::Duration; + use std::fs::File; + use std::io::Write; + use devtools::RandomTempPath; use util::{U256}; use ethcore::client::{Mode, BlockID}; use ethcore::miner::PendingSet; - use super::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_address, to_addresses, to_price, geth_ipc_path, to_bootnodes}; + use super::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_address, to_addresses, to_price, geth_ipc_path, to_bootnodes, password_from_file}; #[test] fn test_to_duration() { @@ -380,6 +392,14 @@ mod tests { ); } + #[test] + fn test_password() { + let path = RandomTempPath::new(); + let mut file = File::create(path.as_path()).unwrap(); + file.write_all(b"a bc ").unwrap(); + assert_eq!(password_from_file(path).unwrap().as_bytes(), b"a bc"); + } + #[test] #[cfg_attr(feature = "dev", allow(float_cmp))] fn test_to_price() { diff --git a/parity/main.rs b/parity/main.rs index b74af7b3d..e0d6dfe36 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -196,6 +196,9 @@ fn sync_main() -> bool { } fn main() { + // Always print backtrace on panic. + ::std::env::set_var("RUST_BACKTRACE", "1"); + if sync_main() { return; } diff --git a/parity/modules.rs b/parity/modules.rs index 53cef4741..39e05a293 100644 --- a/parity/modules.rs +++ b/parity/modules.rs @@ -68,8 +68,9 @@ pub type SyncModules = (Arc, Arc, Arc) #[cfg(feature="ipc")] mod ipc_deps { - pub use ethsync::{SyncClient, NetworkManagerClient, ServiceConfiguration}; - pub use ethcore::client::ChainNotifyClient; + pub use ethsync::remote::{SyncClient, NetworkManagerClient}; + pub use ethsync::ServiceConfiguration; + pub use ethcore::client::remote::ChainNotifyClient; pub use hypervisor::{SYNC_MODULE_ID, BootArgs, HYPERVISOR_IPC_URL}; pub use nanoipc::{GuardedSocket, NanoSocket, generic_client, fast_client}; pub use ipc::IpcSocket; diff --git a/parity/params.rs b/parity/params.rs index faba029b2..ee3038ebf 100644 --- a/parity/params.rs +++ b/parity/params.rs @@ -206,7 +206,7 @@ impl Default for MinerExtras { extra_data: version_data(), gas_floor_target: U256::from(4_700_000), gas_ceil_target: U256::from(6_283_184), - transactions_limit: 1024, + transactions_limit: 2048, } } } diff --git a/parity/sync.rs b/parity/sync.rs index 85f771546..25f900b78 100644 --- a/parity/sync.rs +++ b/parity/sync.rs @@ -19,8 +19,9 @@ use std::sync::Arc; use std::sync::atomic::AtomicBool; use hypervisor::{SYNC_MODULE_ID, HYPERVISOR_IPC_URL, ControlService}; -use ethcore::client::{RemoteClient, ChainNotify}; -use ethcore::snapshot::{RemoteSnapshotService}; +use ethcore::client::ChainNotify; +use ethcore::client::remote::RemoteClient; +use ethcore::snapshot::remote::RemoteSnapshotService; use ethsync::{SyncProvider, EthSync, ManageNetwork, ServiceConfiguration}; use modules::service_urls; use boot; diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index 885ec08f0..0d7902897 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -21,7 +21,7 @@ macro_rules! rpc_unimplemented { } use std::fmt; -use ethcore::error::Error as EthcoreError; +use ethcore::error::{Error as EthcoreError, CallError}; use ethcore::account_provider::{Error as AccountError}; use fetch::FetchError; use jsonrpc_core::{Error, ErrorCode, Value}; @@ -34,6 +34,7 @@ mod codes { pub const NO_NEW_WORK: i64 = -32003; pub const UNKNOWN_ERROR: i64 = -32009; pub const TRANSACTION_ERROR: i64 = -32010; + pub const EXECUTION_ERROR: i64 = -32015; pub const ACCOUNT_LOCKED: i64 = -32020; pub const PASSWORD_INVALID: i64 = -32021; pub const ACCOUNT_ERROR: i64 = -32023; @@ -109,6 +110,14 @@ pub fn invalid_params(param: &str, details: T) -> Error { } } +pub fn execution(data: T) -> Error { + Error { + code: ErrorCode::ServerError(codes::EXECUTION_ERROR), + message: "Transaction execution error.".into(), + data: Some(Value::String(format!("{:?}", data))), + } +} + pub fn state_pruned() -> Error { Error { code: ErrorCode::ServerError(codes::UNSUPPORTED_REQUEST), @@ -189,13 +198,13 @@ pub fn from_transaction_error(error: EthcoreError) -> Error { AlreadyImported => "Transaction with the same hash was already imported.".into(), Old => "Transaction nonce is too low. Try incrementing the nonce.".into(), TooCheapToReplace => { - "Transaction fee is too low. There is another transaction with same nonce in the queue. Try increasing the fee or incrementing the nonce.".into() + "Transaction gas price is too low. There is another transaction with same nonce in the queue. Try increasing the gas price or incrementing the nonce.".into() }, LimitReached => { "There are too many transactions in the queue. Your transaction was dropped due to limit. Try increasing the fee.".into() }, InsufficientGasPrice { minimal, got } => { - format!("Transaction fee is too low. It does not satisfy your node's minimal fee (minimal: {}, got: {}). Try increasing the fee.", minimal, got) + format!("Transaction gas price is too low. It does not satisfy your node's minimal gas price (minimal: {}, got: {}). Try increasing the gas price.", minimal, got) }, InsufficientBalance { balance, cost } => { format!("Insufficient funds. Account you try to send transaction from does not have enough funds. Required {} and got: {}.", cost, balance) @@ -219,4 +228,10 @@ pub fn from_transaction_error(error: EthcoreError) -> Error { } } - +pub fn from_call_error(error: CallError) -> Error { + match error { + CallError::StatePruned => state_pruned(), + CallError::Execution(e) => execution(e), + CallError::TransactionNotFound => internal("{}, this should not be the case with eth_call, most likely a bug.", CallError::TransactionNotFound), + } +} diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index b174e406e..c13229222 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -33,7 +33,7 @@ use util::{FromHex, Mutex}; use rlp::{self, UntrustedRlp, View}; use ethcore::account_provider::AccountProvider; use ethcore::client::{MiningBlockChainClient, BlockID, TransactionID, UncleID}; -use ethcore::header::Header as BlockHeader; +use ethcore::header::{Header as BlockHeader, BlockNumber as EthBlockNumber}; use ethcore::block::IsBlock; use ethcore::views::*; use ethcore::ethereum::Ethash; @@ -198,8 +198,8 @@ impl EthClient where } } -pub fn pending_logs(miner: &M, filter: &EthcoreFilter) -> Vec where M: MinerService { - let receipts = miner.pending_receipts(); +pub fn pending_logs(miner: &M, best_block: EthBlockNumber, filter: &EthcoreFilter) -> Vec where M: MinerService { + let receipts = miner.pending_receipts(best_block); let pending_logs = receipts.into_iter() .flat_map(|(hash, r)| r.logs.into_iter().map(|l| (hash.clone(), l)).collect::>()) @@ -426,7 +426,8 @@ impl Eth for EthClient where try!(self.active()); let hash: H256 = hash.into(); let miner = take_weak!(self.miner); - Ok(try!(self.transaction(TransactionID::Hash(hash))).or_else(|| miner.transaction(&hash).map(Into::into))) + let client = take_weak!(self.client); + Ok(try!(self.transaction(TransactionID::Hash(hash))).or_else(|| miner.transaction(client.chain_info().best_block_number, &hash).map(Into::into))) } fn transaction_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> Result, Error> { @@ -445,8 +446,9 @@ impl Eth for EthClient where try!(self.active()); let miner = take_weak!(self.miner); + let best_block = take_weak!(self.client).chain_info().best_block_number; let hash: H256 = hash.into(); - match (miner.pending_receipt(&hash), self.options.allow_pending_receipt_query) { + match (miner.pending_receipt(best_block, &hash), self.options.allow_pending_receipt_query) { (Some(receipt), true) => Ok(Some(receipt.into())), _ => { let client = take_weak!(self.client); @@ -488,7 +490,8 @@ impl Eth for EthClient where .collect::>(); if include_pending { - let pending = pending_logs(&*take_weak!(self.miner), &filter); + let best_block = take_weak!(self.client).chain_info().best_block_number; + let pending = pending_logs(&*take_weak!(self.miner), best_block, &filter); logs.extend(pending); } @@ -590,7 +593,10 @@ impl Eth for EthClient where num => take_weak!(self.client).call(&signed, num.into(), Default::default()), }; - Ok(r.map(|e| Bytes(e.output)).unwrap_or(Bytes::new(vec![]))) + match r { + Ok(b) => Ok(Bytes(b.output)), + Err(e) => Err(errors::from_call_error(e)), + } } fn estimate_gas(&self, request: CallRequest, num: Trailing) -> Result { diff --git a/rpc/src/v1/impls/eth_filter.rs b/rpc/src/v1/impls/eth_filter.rs index 03d9d7215..dd1c937ac 100644 --- a/rpc/src/v1/impls/eth_filter.rs +++ b/rpc/src/v1/impls/eth_filter.rs @@ -81,7 +81,8 @@ impl EthFilter for EthFilterClient try!(self.active()); let mut polls = self.polls.lock(); - let pending_transactions = take_weak!(self.miner).pending_transactions_hashes(); + let best_block = take_weak!(self.client).chain_info().best_block_number; + let pending_transactions = take_weak!(self.miner).pending_transactions_hashes(best_block); let id = polls.create_poll(PollFilter::PendingTransaction(pending_transactions)); Ok(id.into()) } @@ -108,7 +109,8 @@ impl EthFilter for EthFilterClient }, PollFilter::PendingTransaction(ref mut previous_hashes) => { // get hashes of pending transactions - let current_hashes = take_weak!(self.miner).pending_transactions_hashes(); + let best_block = take_weak!(self.client).chain_info().best_block_number; + let current_hashes = take_weak!(self.miner).pending_transactions_hashes(best_block); let new_hashes = { @@ -149,7 +151,8 @@ impl EthFilter for EthFilterClient // additionally retrieve pending logs if include_pending { - let pending_logs = pending_logs(&*take_weak!(self.miner), &filter); + let best_block = take_weak!(self.client).chain_info().best_block_number; + let pending_logs = pending_logs(&*take_weak!(self.miner), best_block, &filter); // remove logs about which client was already notified about let new_pending_logs: Vec<_> = pending_logs.iter() @@ -190,7 +193,8 @@ impl EthFilter for EthFilterClient .collect::>(); if include_pending { - logs.extend(pending_logs(&*take_weak!(self.miner), &filter)); + let best_block = take_weak!(self.client).chain_info().best_block_number; + logs.extend(pending_logs(&*take_weak!(self.miner), best_block, &filter)); } let logs = limit_logs(logs, filter.limit); diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 448fa4734..97e4d3bea 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -24,7 +24,7 @@ use ethcore::spec::{Genesis, Spec}; use ethcore::block::Block; use ethcore::views::BlockView; use ethcore::ethereum; -use ethcore::miner::{MinerOptions, GasPricer, MinerService, ExternalMiner, Miner, PendingSet}; +use ethcore::miner::{MinerOptions, GasPricer, MinerService, ExternalMiner, Miner, PendingSet, GasLimit}; use ethcore::account_provider::AccountProvider; use devtools::RandomTempPath; use util::Hashable; @@ -58,6 +58,7 @@ fn miner_service(spec: &Spec, accounts: Arc) -> Arc { reseal_on_own_tx: true, tx_queue_size: 1024, tx_gas_limit: !U256::zero(), + tx_queue_gas_limit: GasLimit::None, pending_set: PendingSet::SealingOrElseQueue, reseal_min_period: Duration::from_secs(0), work_queue_size: 50, diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index ddc0b057b..0787f2102 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -21,6 +21,7 @@ use util::standard::*; use ethcore::error::{Error, CallError}; use ethcore::client::{MiningBlockChainClient, Executed, CallAnalytics}; use ethcore::block::{ClosedBlock, IsBlock}; +use ethcore::header::BlockNumber; use ethcore::transaction::SignedTransaction; use ethcore::receipt::{Receipt, RichReceipt}; use ethcore::miner::{MinerService, MinerStatus, TransactionImportResult}; @@ -162,7 +163,7 @@ impl MinerService for TestMinerService { } /// Returns hashes of transactions currently in pending - fn pending_transactions_hashes(&self) -> Vec { + fn pending_transactions_hashes(&self, _best_block: BlockNumber) -> Vec { vec![] } @@ -186,7 +187,7 @@ impl MinerService for TestMinerService { Some(f(&open_block.close())) } - fn transaction(&self, hash: &H256) -> Option { + fn transaction(&self, _best_block: BlockNumber, hash: &H256) -> Option { self.pending_transactions.lock().get(hash).cloned() } @@ -194,13 +195,13 @@ impl MinerService for TestMinerService { self.pending_transactions.lock().values().cloned().collect() } - fn pending_transactions(&self) -> Vec { + fn pending_transactions(&self, _best_block: BlockNumber) -> Vec { self.pending_transactions.lock().values().cloned().collect() } - fn pending_receipt(&self, hash: &H256) -> Option { + fn pending_receipt(&self, _best_block: BlockNumber, hash: &H256) -> Option { // Not much point implementing this since the logic is complex and the only thing it relies on is pending_receipts, which is already tested. - self.pending_receipts().get(hash).map(|r| + self.pending_receipts(0).get(hash).map(|r| RichReceipt { transaction_hash: Default::default(), transaction_index: Default::default(), @@ -212,7 +213,7 @@ impl MinerService for TestMinerService { ) } - fn pending_receipts(&self) -> BTreeMap { + fn pending_receipts(&self, _best_block: BlockNumber) -> BTreeMap { self.pending_receipts.lock().clone() } diff --git a/rpc/src/v1/types/filter.rs b/rpc/src/v1/types/filter.rs index b4a45272b..fc163c54b 100644 --- a/rpc/src/v1/types/filter.rs +++ b/rpc/src/v1/types/filter.rs @@ -85,8 +85,14 @@ impl Into for Filter { VariadicValue::Null => None, VariadicValue::Single(t) => Some(vec![t.into()]), VariadicValue::Multiple(t) => Some(t.into_iter().map(Into::into).collect()) - }).filter_map(|m| m).collect()).into_iter(); - vec![iter.next(), iter.next(), iter.next(), iter.next()] + }).collect()).into_iter(); + + vec![ + iter.next().unwrap_or(None), + iter.next().unwrap_or(None), + iter.next().unwrap_or(None), + iter.next().unwrap_or(None) + ] }, limit: self.limit, } @@ -121,6 +127,8 @@ mod tests { use util::hash::*; use super::*; use v1::types::BlockNumber; + use ethcore::filter::Filter as EthFilter; + use ethcore::client::BlockID; #[test] fn topic_deserialization() { @@ -148,4 +156,33 @@ mod tests { limit: None, }); } + + #[test] + fn filter_conversion() { + let filter = Filter { + from_block: Some(BlockNumber::Earliest), + to_block: Some(BlockNumber::Latest), + address: Some(VariadicValue::Multiple(vec![])), + topics: Some(vec![ + VariadicValue::Null, + VariadicValue::Single("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b".into()), + VariadicValue::Null, + ]), + limit: None, + }; + + let eth_filter: EthFilter = filter.into(); + assert_eq!(eth_filter, EthFilter { + from_block: BlockID::Earliest, + to_block: BlockID::Latest, + address: Some(vec![]), + topics: vec![ + None, + Some(vec!["000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b".into()]), + None, + None, + ], + limit: None, + }); + } } diff --git a/sync/build.rs b/sync/build.rs index cdb717e0e..c465d5e34 100644 --- a/sync/build.rs +++ b/sync/build.rs @@ -17,5 +17,5 @@ extern crate ethcore_ipc_codegen; fn main() { - ethcore_ipc_codegen::derive_ipc("src/api.rs").unwrap(); + ethcore_ipc_codegen::derive_ipc_cond("src/api.rs", cfg!(feature="ipc")).unwrap(); } diff --git a/sync/src/blocks.rs b/sync/src/blocks.rs index beaa49c60..ae2092f25 100644 --- a/sync/src/blocks.rs +++ b/sync/src/blocks.rs @@ -184,8 +184,8 @@ impl BlockCollection { { let mut blocks = Vec::new(); let mut head = self.head; - while head.is_some() { - head = self.parents.get(&head.unwrap()).cloned(); + while let Some(h) = head { + head = self.parents.get(&h).cloned(); if let Some(head) = head { match self.blocks.get(&head) { Some(block) if block.body.is_some() => { @@ -201,7 +201,7 @@ impl BlockCollection { for block in blocks.drain(..) { let mut block_rlp = RlpStream::new_list(3); block_rlp.append_raw(&block.header, 1); - let body = Rlp::new(block.body.as_ref().unwrap()); // incomplete blocks are filtered out in the loop above + let body = Rlp::new(block.body.as_ref().expect("blocks contains only full blocks; qed")); block_rlp.append_raw(body.at(0).as_raw(), 1); block_rlp.append_raw(body.at(1).as_raw(), 1); drained.push(block_rlp.out()); diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 565c53827..446fd5499 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -90,7 +90,6 @@ use util::*; use rlp::*; use network::*; -use std::mem::{replace}; use ethcore::views::{HeaderView, BlockView}; use ethcore::header::{BlockNumber, Header as BlockHeader}; use ethcore::client::{BlockChainClient, BlockStatus, BlockID, BlockChainInfo, BlockImportError}; @@ -123,6 +122,7 @@ const MAX_ROUND_PARENTS: usize = 32; const MAX_NEW_HASHES: usize = 64; const MAX_TX_TO_IMPORT: usize = 512; const MAX_NEW_BLOCK_AGE: BlockNumber = 20; +const MAX_TRANSACTION_SIZE: usize = 300*1024; const STATUS_PACKET: u8 = 0x00; const NEW_BLOCK_HASHES_PACKET: u8 = 0x01; @@ -143,7 +143,7 @@ const GET_SNAPSHOT_DATA_PACKET: u8 = 0x13; const SNAPSHOT_DATA_PACKET: u8 = 0x14; const HEADERS_TIMEOUT_SEC: f64 = 15f64; -const BODIES_TIMEOUT_SEC: f64 = 5f64; +const BODIES_TIMEOUT_SEC: f64 = 10f64; const FORK_HEADER_TIMEOUT_SEC: f64 = 3f64; const SNAPSHOT_MANIFEST_TIMEOUT_SEC: f64 = 3f64; const SNAPSHOT_DATA_TIMEOUT_SEC: f64 = 10f64; @@ -249,8 +249,6 @@ struct PeerInfo { network_id: U256, /// Peer best block hash latest_hash: H256, - /// Peer best block number if known - latest_number: Option, /// Peer total difficulty if known difficulty: Option, /// Type of data currenty being requested from peer. @@ -395,6 +393,8 @@ impl ChainSync { } self.syncing_difficulty = From::from(0u64); self.state = SyncState::Idle; + // Reactivate peers only if some progress has been made + // since the last sync round of if starting fresh. self.active_peers = self.peers.keys().cloned().collect(); } @@ -406,7 +406,8 @@ impl ChainSync { self.continue_sync(io); } - /// Remove peer from active peer set + /// Remove peer from active peer set. Peer will be reactivated on the next sync + /// round. fn deactivate_peer(&mut self, io: &mut SyncIo, peer_id: PeerId) { trace!(target: "sync", "Deactivating peer {}", peer_id); self.active_peers.remove(&peer_id); @@ -443,7 +444,6 @@ impl ChainSync { network_id: try!(r.val_at(1)), difficulty: Some(try!(r.val_at(2))), latest_hash: try!(r.val_at(3)), - latest_number: None, genesis: try!(r.val_at(4)), asking: PeerAsking::Nothing, asking_blocks: Vec::new(), @@ -480,7 +480,11 @@ impl ChainSync { } self.peers.insert(peer_id.clone(), peer); - self.active_peers.insert(peer_id.clone()); + // Don't activate peer immediatelly when searching for common block. + // Let the current sync round complete first. + if self.state != SyncState::ChainHead { + self.active_peers.insert(peer_id.clone()); + } debug!(target: "sync", "Connected {}:{}", peer_id, io.peer_info(peer_id)); if let Some((fork_block, _)) = self.fork_block { self.request_headers_by_number(io, peer_id, fork_block, 1, 0, false, PeerAsking::ForkHeader); @@ -496,7 +500,8 @@ impl ChainSync { let confirmed = match self.peers.get_mut(&peer_id) { Some(ref mut peer) if peer.asking == PeerAsking::ForkHeader => { let item_count = r.item_count(); - if item_count == 0 || (item_count == 1 && try!(r.at(0)).as_raw().sha3() == self.fork_block.unwrap().1) { + if item_count == 0 || (item_count == 1 && + try!(r.at(0)).as_raw().sha3() == self.fork_block.expect("ForkHeader state is only entered when fork_block is some; qed").1) { peer.asking = PeerAsking::Nothing; if item_count == 0 { trace!(target: "sync", "{}: Chain is too short to confirm the block", peer_id); @@ -562,7 +567,7 @@ impl ChainSync { continue; } - if self.highest_block == None || number > self.highest_block.unwrap() { + if self.highest_block.as_ref().map_or(true, |n| number > *n) { self.highest_block = Some(number); } let hash = info.hash(); @@ -594,9 +599,9 @@ impl ChainSync { } if headers.is_empty() { - // Peer does not have any new subchain heads, deactivate it nd try with another + // Peer does not have any new subchain heads, deactivate it and try with another. trace!(target: "sync", "{} Disabled for no data", peer_id); - io.disable_peer(peer_id); + self.deactivate_peer(io, peer_id); } match self.state { SyncState::ChainHead => { @@ -675,9 +680,9 @@ impl ChainSync { } let mut unknown = false; { - let peer = self.peers.get_mut(&peer_id).unwrap(); - peer.latest_hash = header.hash(); - peer.latest_number = Some(header.number()); + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + peer.latest_hash = header.hash(); + } } if self.last_imported_block > header.number() && self.last_imported_block - header.number() > MAX_NEW_BLOCK_AGE { trace!(target: "sync", "Ignored ancient new block {:?}", h); @@ -770,9 +775,9 @@ impl ChainSync { new_hashes.push(hash.clone()); if number > max_height { trace!(target: "sync", "New unknown block hash {:?}", hash); - let peer = self.peers.get_mut(&peer_id).unwrap(); - peer.latest_hash = hash.clone(); - peer.latest_number = Some(number); + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + peer.latest_hash = hash.clone(); + } max_height = number; } }, @@ -942,19 +947,22 @@ impl ChainSync { return; } let (peer_latest, peer_difficulty, peer_snapshot_number, peer_snapshot_hash) = { - let peer = self.peers.get_mut(&peer_id).unwrap(); - if peer.asking != PeerAsking::Nothing || !peer.can_sync() { + if let Some(ref peer) = self.peers.get_mut(&peer_id) { + if peer.asking != PeerAsking::Nothing || !peer.can_sync() { + return; + } + if self.state == SyncState::Waiting { + trace!(target: "sync", "Waiting for the block queue"); + return; + } + if self.state == SyncState::SnapshotWaiting { + trace!(target: "sync", "Waiting for the snapshot restoration"); + return; + } + (peer.latest_hash.clone(), peer.difficulty.clone(), peer.snapshot_number.as_ref().cloned(), peer.snapshot_hash.as_ref().cloned()) + } else { return; } - if self.state == SyncState::Waiting { - trace!(target: "sync", "Waiting for the block queue"); - return; - } - if self.state == SyncState::SnapshotWaiting { - trace!(target: "sync", "Waiting for the snapshot restoration"); - return; - } - (peer.latest_hash.clone(), peer.difficulty.clone(), peer.snapshot_number.as_ref().cloned(), peer.snapshot_hash.as_ref().cloned()) }; let chain_info = io.chain().chain_info(); let td = chain_info.pending_total_difficulty; @@ -1042,14 +1050,18 @@ impl ChainSync { // check to see if we need to download any block bodies first let needed_bodies = self.blocks.needed_bodies(MAX_BODIES_TO_REQUEST, ignore_others); if !needed_bodies.is_empty() { - replace(&mut self.peers.get_mut(&peer_id).unwrap().asking_blocks, needed_bodies.clone()); + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + peer.asking_blocks = needed_bodies.clone(); + } self.request_bodies(io, peer_id, needed_bodies); return; } // find subchain to download if let Some((h, count)) = self.blocks.needed_headers(MAX_HEADERS_TO_REQUEST, ignore_others) { - replace(&mut self.peers.get_mut(&peer_id).unwrap().asking_blocks, vec![h.clone()]); + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + peer.asking_blocks = vec![h.clone()]; + } self.request_headers_by_hash(io, peer_id, &h, count, 0, false, PeerAsking::BlockHeaders); } } @@ -1059,34 +1071,37 @@ impl ChainSync { self.clear_peer_download(peer_id); // find chunk data to download if let Some(hash) = self.snapshot.needed_chunk() { - self.peers.get_mut(&peer_id).unwrap().asking_snapshot_data = Some(hash.clone()); + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + peer.asking_snapshot_data = Some(hash.clone()); + } self.request_snapshot_chunk(io, peer_id, &hash); } } /// Clear all blocks/headers marked as being downloaded by a peer. fn clear_peer_download(&mut self, peer_id: PeerId) { - let peer = self.peers.get_mut(&peer_id).unwrap(); - match peer.asking { - PeerAsking::BlockHeaders | PeerAsking::Heads => { - for b in &peer.asking_blocks { - self.blocks.clear_header_download(b); - } - }, - PeerAsking::BlockBodies => { - for b in &peer.asking_blocks { - self.blocks.clear_body_download(b); - } - }, - PeerAsking::SnapshotData => { - if let Some(hash) = peer.asking_snapshot_data { - self.snapshot.clear_chunk_download(&hash); - } - }, - _ => (), + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + match peer.asking { + PeerAsking::BlockHeaders | PeerAsking::Heads => { + for b in &peer.asking_blocks { + self.blocks.clear_header_download(b); + } + }, + PeerAsking::BlockBodies => { + for b in &peer.asking_blocks { + self.blocks.clear_body_download(b); + } + }, + PeerAsking::SnapshotData => { + if let Some(hash) = peer.asking_snapshot_data { + self.snapshot.clear_chunk_download(&hash); + } + }, + _ => (), + } + peer.asking_blocks.clear(); + peer.asking_snapshot_data = None; } - peer.asking_blocks.clear(); - peer.asking_snapshot_data = None; } fn block_imported(&mut self, hash: &H256, number: BlockNumber, parent: &H256) { @@ -1211,30 +1226,34 @@ impl ChainSync { /// Reset peer status after request is complete. fn reset_peer_asking(&mut self, peer_id: PeerId, asking: PeerAsking) -> bool { - let peer = self.peers.get_mut(&peer_id).unwrap(); - peer.expired = false; - if peer.asking != asking { - trace!(target:"sync", "Asking {:?} while expected {:?}", peer.asking, asking); - peer.asking = PeerAsking::Nothing; + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + peer.expired = false; + if peer.asking != asking { + trace!(target:"sync", "Asking {:?} while expected {:?}", peer.asking, asking); + peer.asking = PeerAsking::Nothing; + false + } + else { + peer.asking = PeerAsking::Nothing; + true + } + } else { false } - else { - peer.asking = PeerAsking::Nothing; - true - } } /// Generic request sender fn send_request(&mut self, sync: &mut SyncIo, peer_id: PeerId, asking: PeerAsking, packet_id: PacketId, packet: Bytes) { - let peer = self.peers.get_mut(&peer_id).unwrap(); - if peer.asking != PeerAsking::Nothing { - warn!(target:"sync", "Asking {:?} while requesting {:?}", peer.asking, asking); - } - peer.asking = asking; - peer.ask_time = time::precise_time_s(); - if let Err(e) = sync.send(peer_id, packet_id, packet) { - debug!(target:"sync", "Error sending request: {:?}", e); - sync.disable_peer(peer_id); + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + if peer.asking != PeerAsking::Nothing { + warn!(target:"sync", "Asking {:?} while requesting {:?}", peer.asking, asking); + } + peer.asking = asking; + peer.ask_time = time::precise_time_s(); + if let Err(e) = sync.send(peer_id, packet_id, packet) { + debug!(target:"sync", "Error sending request: {:?}", e); + sync.disable_peer(peer_id); + } } } @@ -1261,7 +1280,12 @@ impl ChainSync { item_count = min(item_count, MAX_TX_TO_IMPORT); let mut transactions = Vec::with_capacity(item_count); for i in 0 .. item_count { - let tx = try!(r.at(i)).as_raw().to_vec(); + let rlp = try!(r.at(i)); + if rlp.as_raw().len() > MAX_TRANSACTION_SIZE { + debug!("Skipped oversized transaction of {} bytes", rlp.as_raw().len()); + continue; + } + let tx = rlp.as_raw().to_vec(); transactions.push(tx); } io.chain().queue_transactions(transactions); @@ -1604,7 +1628,7 @@ impl ChainSync { /// creates latest block rlp for the given client fn create_latest_block_rlp(chain: &BlockChainClient) -> Bytes { let mut rlp_stream = RlpStream::new_list(2); - rlp_stream.append_raw(&chain.block(BlockID::Hash(chain.chain_info().best_block_hash)).unwrap(), 1); + rlp_stream.append_raw(&chain.block(BlockID::Hash(chain.chain_info().best_block_hash)).expect("Best block always exists"), 1); rlp_stream.append(&chain.chain_info().total_difficulty); rlp_stream.out() } @@ -1618,25 +1642,23 @@ impl ChainSync { } /// returns peer ids that have less blocks than our chain - fn get_lagging_peers(&mut self, chain_info: &BlockChainInfo, io: &SyncIo) -> Vec<(PeerId, BlockNumber)> { + fn get_lagging_peers(&mut self, chain_info: &BlockChainInfo, io: &SyncIo) -> Vec { let latest_hash = chain_info.best_block_hash; - let latest_number = chain_info.best_block_number; self.peers.iter_mut().filter_map(|(&id, ref mut peer_info)| match io.chain().block_status(BlockID::Hash(peer_info.latest_hash.clone())) { BlockStatus::InChain => { - if peer_info.latest_number.is_none() { - peer_info.latest_number = Some(HeaderView::new(&io.chain().block_header(BlockID::Hash(peer_info.latest_hash.clone())).unwrap()).number()); + if peer_info.latest_hash != latest_hash { + Some(id) + } else { + None } - if peer_info.latest_hash != latest_hash && latest_number > peer_info.latest_number.unwrap() { - Some((id, peer_info.latest_number.unwrap())) - } else { None } }, _ => None }) .collect::>() } - fn select_random_lagging_peers(&mut self, peers: &[(PeerId, BlockNumber)]) -> Vec<(PeerId, BlockNumber)> { + fn select_random_lagging_peers(&mut self, peers: &[PeerId]) -> Vec { use rand::Rng; // take sqrt(x) peers let mut peers = peers.to_vec(); @@ -1649,46 +1671,42 @@ impl ChainSync { } /// propagates latest block to lagging peers - fn propagate_blocks(&mut self, chain_info: &BlockChainInfo, io: &mut SyncIo, sealed: &[H256], peers: &[(PeerId, BlockNumber)]) -> usize { + fn propagate_blocks(&mut self, chain_info: &BlockChainInfo, io: &mut SyncIo, sealed: &[H256], peers: &[PeerId]) -> usize { trace!(target: "sync", "Sending NewBlocks to {:?}", peers); let mut sent = 0; - for &(peer_id, _) in peers { + for peer_id in peers { if sealed.is_empty() { let rlp = ChainSync::create_latest_block_rlp(io.chain()); - self.send_packet(io, peer_id, NEW_BLOCK_PACKET, rlp); + self.send_packet(io, *peer_id, NEW_BLOCK_PACKET, rlp); } else { for h in sealed { let rlp = ChainSync::create_new_block_rlp(io.chain(), h); - self.send_packet(io, peer_id, NEW_BLOCK_PACKET, rlp); + self.send_packet(io, *peer_id, NEW_BLOCK_PACKET, rlp); } } - self.peers.get_mut(&peer_id).unwrap().latest_hash = chain_info.best_block_hash.clone(); - self.peers.get_mut(&peer_id).unwrap().latest_number = Some(chain_info.best_block_number); + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + peer.latest_hash = chain_info.best_block_hash.clone(); + } sent += 1; } sent } /// propagates new known hashes to all peers - fn propagate_new_hashes(&mut self, chain_info: &BlockChainInfo, io: &mut SyncIo, peers: &[(PeerId, BlockNumber)]) -> usize { + fn propagate_new_hashes(&mut self, chain_info: &BlockChainInfo, io: &mut SyncIo, peers: &[PeerId]) -> usize { trace!(target: "sync", "Sending NewHashes to {:?}", peers); let mut sent = 0; - let last_parent = HeaderView::new(&io.chain().block_header(BlockID::Hash(chain_info.best_block_hash.clone())).unwrap()).parent_hash(); - for &(peer_id, peer_number) in peers { - let peer_best = if chain_info.best_block_number - peer_number > MAX_PEER_LAG_PROPAGATION as BlockNumber { - // If we think peer is too far behind just send one latest hash - last_parent.clone() - } else { - self.peers.get(&peer_id).unwrap().latest_hash.clone() - }; - sent += match ChainSync::create_new_hashes_rlp(io.chain(), &peer_best, &chain_info.best_block_hash) { + let last_parent = HeaderView::new(&io.chain().block_header(BlockID::Hash(chain_info.best_block_hash.clone())) + .expect("Best block always exists")).parent_hash(); + for peer_id in peers { + sent += match ChainSync::create_new_hashes_rlp(io.chain(), &last_parent, &chain_info.best_block_hash) { Some(rlp) => { { - let peer = self.peers.get_mut(&peer_id).unwrap(); - peer.latest_hash = chain_info.best_block_hash.clone(); - peer.latest_number = Some(chain_info.best_block_number); + if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + peer.latest_hash = chain_info.best_block_hash.clone(); + } } - self.send_packet(io, peer_id, NEW_BLOCK_HASHES_PACKET, rlp); + self.send_packet(io, *peer_id, NEW_BLOCK_HASHES_PACKET, rlp); 1 }, None => 0 @@ -2001,7 +2019,6 @@ mod tests { genesis: H256::zero(), network_id: U256::zero(), latest_hash: peer_latest_hash, - latest_number: None, difficulty: None, asking: PeerAsking::Nothing, asking_blocks: Vec::new(), diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 0fa7d9f42..d8dc710f5 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -64,3 +64,9 @@ pub use api::{EthSync, SyncProvider, SyncClient, NetworkManagerClient, ManageNet ServiceConfiguration, NetworkConfiguration}; pub use chain::{SyncStatus, SyncState}; pub use network::{is_valid_node_url, NonReservedPeerMode, NetworkError}; + +/// IPC interfaces +#[cfg(feature="ipc")] +pub mod remote { + pub use api::{SyncClient, NetworkManagerClient}; +} diff --git a/sync/src/tests/chain.rs b/sync/src/tests/chain.rs index d8d3d0711..c54529beb 100644 --- a/sync/src/tests/chain.rs +++ b/sync/src/tests/chain.rs @@ -95,6 +95,27 @@ fn forked() { assert_eq!(&*net.peer(2).chain.numbers.read(), &peer1_chain); } +#[test] +fn forked_with_misbehaving_peer() { + ::env_logger::init().ok(); + let mut net = TestNet::new(3); + // peer 0 is on a totally different chain with higher total difficulty + net.peer_mut(0).chain = TestBlockChainClient::new_with_extra_data(b"fork".to_vec()); + net.peer_mut(0).chain.add_blocks(500, EachBlockWith::Nothing); + net.peer_mut(1).chain.add_blocks(100, EachBlockWith::Nothing); + net.peer_mut(2).chain.add_blocks(100, EachBlockWith::Nothing); + + net.peer_mut(1).chain.add_blocks(100, EachBlockWith::Nothing); + net.peer_mut(2).chain.add_blocks(200, EachBlockWith::Uncle); + // peer 1 should sync to peer 2, others should not change + let peer0_chain = net.peer(0).chain.numbers.read().clone(); + let peer2_chain = net.peer(2).chain.numbers.read().clone(); + net.sync(); + assert_eq!(&*net.peer(0).chain.numbers.read(), &peer0_chain); + assert_eq!(&*net.peer(1).chain.numbers.read(), &peer2_chain); + assert_eq!(&*net.peer(2).chain.numbers.read(), &peer2_chain); +} + #[test] fn net_hard_fork() { ::env_logger::init().ok(); @@ -116,11 +137,12 @@ fn net_hard_fork() { #[test] fn restart() { + ::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_steps(8); + net.sync(); // make sure that sync has actually happened assert!(net.peer(0).chain.chain_info().best_block_number > 100); diff --git a/sync/src/tests/helpers.rs b/sync/src/tests/helpers.rs index cbed49eff..3558e5578 100644 --- a/sync/src/tests/helpers.rs +++ b/sync/src/tests/helpers.rs @@ -29,6 +29,7 @@ pub struct TestIo<'p> { pub snapshot_service: &'p TestSnapshotService, pub queue: &'p mut VecDeque, pub sender: Option, + pub to_disconnect: HashSet, } impl<'p> TestIo<'p> { @@ -37,16 +38,19 @@ impl<'p> TestIo<'p> { chain: chain, snapshot_service: ss, queue: queue, - sender: sender + sender: sender, + to_disconnect: HashSet::new(), } } } impl<'p> SyncIo for TestIo<'p> { - fn disable_peer(&mut self, _peer_id: PeerId) { + fn disable_peer(&mut self, peer_id: PeerId) { + self.disconnect_peer(peer_id); } - fn disconnect_peer(&mut self, _peer_id: PeerId) { + fn disconnect_peer(&mut self, peer_id: PeerId) { + self.to_disconnect.insert(peer_id); } fn is_expired(&self) -> bool { @@ -150,13 +154,30 @@ impl TestNet { 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 disconnecting = { + let mut p = self.peers.get_mut(packet.recipient).unwrap(); + trace!("--- {} -> {} ---", peer, packet.recipient); + let to_disconnect = { + let mut io = TestIo::new(&mut p.chain, &p.snapshot_service, &mut p.queue, Some(peer as PeerId)); + ChainSync::dispatch_packet(&p.sync, &mut io, peer as PeerId, packet.packet_id, &packet.data); + io.to_disconnect + }; + for d in &to_disconnect { + // notify this that disconnecting peers are disconnecting + let mut io = TestIo::new(&mut p.chain, &p.snapshot_service, &mut p.queue, Some(*d)); + p.sync.write().on_peer_aborting(&mut io, *d); + } + to_disconnect + }; + for d in &disconnecting { + // notify other peers that this peer is disconnecting + let mut p = self.peers.get_mut(*d).unwrap(); + let mut io = TestIo::new(&mut p.chain, &p.snapshot_service, &mut p.queue, Some(peer as PeerId)); + p.sync.write().on_peer_aborting(&mut io, peer as PeerId); + } } - 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)); + + self.sync_step_peer(peer); } } diff --git a/util/bigint/Cargo.toml b/util/bigint/Cargo.toml index ee25ce846..c21b5239f 100644 --- a/util/bigint/Cargo.toml +++ b/util/bigint/Cargo.toml @@ -4,7 +4,7 @@ homepage = "http://ethcore.io" repository = "https://github.com/ethcore/parity" license = "GPL-3.0" name = "ethcore-bigint" -version = "0.1.0" +version = "0.1.1" authors = ["Ethcore "] build = "build.rs" diff --git a/util/io/src/lib.rs b/util/io/src/lib.rs index b2a16e19b..082192dfa 100644 --- a/util/io/src/lib.rs +++ b/util/io/src/lib.rs @@ -68,6 +68,8 @@ mod panics; use mio::{EventLoop, Token}; use std::fmt; +pub use worker::LOCAL_STACK_SIZE; + #[derive(Debug)] /// IO Error pub enum IoError { diff --git a/util/io/src/worker.rs b/util/io/src/worker.rs index 0176c467c..f4f63919f 100644 --- a/util/io/src/worker.rs +++ b/util/io/src/worker.rs @@ -22,9 +22,19 @@ use crossbeam::sync::chase_lev; use service::{HandlerId, IoChannel, IoContext}; use IoHandler; use panics::*; +use std::cell::Cell; use std::sync::{Condvar as SCondvar, Mutex as SMutex}; +const STACK_SIZE: usize = 16*1024*1024; + +thread_local! { + /// Stack size + /// Should be modified if it is changed in Rust since it is no way + /// to know or get it + pub static LOCAL_STACK_SIZE: Cell = Cell::new(::std::env::var("RUST_MIN_STACK").ok().and_then(|s| s.parse().ok()).unwrap_or(2 * 1024 * 1024)); +} + pub enum WorkType { Readable, Writable, @@ -66,8 +76,9 @@ impl Worker { deleting: deleting.clone(), wait_mutex: wait_mutex.clone(), }; - worker.thread = Some(thread::Builder::new().name(format!("IO Worker #{}", index)).spawn( + worker.thread = Some(thread::Builder::new().stack_size(STACK_SIZE).name(format!("IO Worker #{}", index)).spawn( move || { + LOCAL_STACK_SIZE.with(|val| val.set(STACK_SIZE)); panic_handler.catch_panic(move || { Worker::work_loop(stealer, channel.clone(), wait, wait_mutex.clone(), deleting) }).unwrap() diff --git a/util/network/src/host.rs b/util/network/src/host.rs index f530503c6..d982481f9 100644 --- a/util/network/src/host.rs +++ b/util/network/src/host.rs @@ -591,7 +591,8 @@ impl Host { } fn handshake_count(&self) -> usize { - self.sessions.read().count() - self.session_count() + // session_count < total_count is possible because of the data race. + self.sessions.read().count().saturating_sub(self.session_count()) } fn keep_alive(&self, io: &IoContext) { From 214916a4141ce5c0da3d0ebb71b8318593d17ab7 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 11 Oct 2016 18:38:05 +0100 Subject: [PATCH 040/295] new vote counter --- .../src/engines/tendermint/vote_collector.rs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 ethcore/src/engines/tendermint/vote_collector.rs diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs new file mode 100644 index 000000000..bcf347053 --- /dev/null +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -0,0 +1,37 @@ +// 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 . + +//! Collects votes on hashes at each height and round. + +use super::vote::Vote; +use super::{Height, Round, BlockHash}; +use common::{HashSet, HashMap, RwLock, H256, Address, Error, Hashable}; +use ethkey::{Signature, recover}; + +/// Signed voting on hashes. +#[derive(Debug)] +pub struct VoteCollector { + /// Structure for storing all votes. + votes: RwLock>>, +} + +impl VoteCollector { + pub fn new() -> VoteCollector { + VoteCollector { votes: RwLock::new(HashMap::new()) } + } + + pub fn vote() {} +} From 4e3655089064e35bb6f8c7ce379dd3a464c489a0 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 8 Nov 2016 18:01:31 +0000 Subject: [PATCH 041/295] message revamp --- ethcore/src/engines/tendermint/message.rs | 120 +++++++++++++--------- 1 file changed, 74 insertions(+), 46 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index b14a07c1e..69f414ec8 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -16,67 +16,95 @@ //! Tendermint message handling. -use super::{Height, Round, BlockHash}; +use std::cmp::Ordering; +use super::{Height, Round, BlockHash, Step}; use rlp::{View, DecoderError, Decodable, Decoder, Encodable, RlpStream, Stream}; -#[derive(Debug)] -pub enum ConsensusMessage { - Prevote(Height, Round, BlockHash), - Precommit(Height, Round, BlockHash), - Commit(Height, BlockHash), +#[derive(Debug, PartialEq, Eq)] +pub enum Step { + Prevote, + Precommit } -/// (height, step, ...) +#[derive(Debug, PartialEq, Eq)] +pub struct ConsensusMessage { + signature: H520, + height: Height, + round: Round, + step: Step, + block_hash: BlockHash +} + +impl PartialOrd for ConsensusMessage { + fn partial_cmp(&self, m: &ConsensusMessage) -> Option { + Some(self.cmp(m)) + } +} + +impl Ord for ConsensusMessage { + fn cmp(&self, m: &ConsensusMessage) -> Ordering { + if self.height != m.height { + self.height.cmp(&m.height) + } else if self.round != m.round { + self.round.cmp(&m.round) + } else if self.step != m.step { + match self.step { + Step::Prevote => Ordering::Less, + Step::Precommit => Ordering::Greater, + } + } else { + self.block_hash.cmp(&m.block_hash) + } + } +} + +impl Decodable for Step { + fn decode(decoder: &D) -> Result where D: Decoder { + match try!(decoder.as_rlp().as_val()) { + 0u8 => Ok(Step::Prevote), + 1 => Ok(Step::Precommit), + _ => Err(DecoderError::Custom("Unknown step.")), + } + } +} + + +impl Encodable for Step { + fn rlp_append(&self, s: &mut RlpStream) { + match *self { + Step::Prevote => s.append(&0u8), + Step::Precommit => s.append(&1u8), + _ => panic!("No encoding needed for other steps"), + }; + } +} + +/// (signature, height, round, step, block_hash) impl Decodable for ConsensusMessage { fn decode(decoder: &D) -> Result where D: Decoder { - // Handle according to step. let rlp = decoder.as_rlp(); if decoder.as_raw().len() != try!(rlp.payload_info()).total() { return Err(DecoderError::RlpIsTooBig); } - let height = try!(rlp.val_at(0)); - Ok(match try!(rlp.val_at(1)) { - 0u8 => ConsensusMessage::Prevote( - height, - try!(rlp.val_at(2)), - try!(rlp.val_at(3)) - ), - 1 => ConsensusMessage::Precommit( - height, - try!(rlp.val_at(2)), - try!(rlp.val_at(3)) - ), - 2 => ConsensusMessage::Commit( - height, - try!(rlp.val_at(2))), - _ => return Err(DecoderError::Custom("Unknown step.")), + let m = rlp.at(1); + Ok(ConsensusMessage { + signature: try!(rlp.val_at(0)), + height: try!(m.val_at(0)), + round: try!(m.val_at(1)), + step: try!(m.val_at(2)), + block_hash: try!(m.val_at(3)) }) } } impl Encodable for ConsensusMessage { fn rlp_append(&self, s: &mut RlpStream) { - match *self { - ConsensusMessage::Prevote(h, r, hash) => { - s.begin_list(4); - s.append(&h); - s.append(&0u8); - s.append(&r); - s.append(&hash); - }, - ConsensusMessage::Precommit(h, r, hash) => { - s.begin_list(4); - s.append(&h); - s.append(&1u8); - s.append(&r); - s.append(&hash); - }, - ConsensusMessage::Commit(h, hash) => { - s.begin_list(3); - s.append(&h); - s.append(&2u8); - s.append(&hash); - }, - } + s.begin_list(2); + s.append(&self.signature); + s.begin_list(4); + s.append(&self.height); + s.append(&self.round); + s.append(&self.step); + s.append(&self.block_hash); } } From 55a5402bf525328693ae1786f87b198a9a3ae844 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 15 Nov 2016 10:20:14 +0000 Subject: [PATCH 042/295] simplify messages --- ethcore/src/client/client.rs | 12 +---- ethcore/src/engines/mod.rs | 2 +- ethcore/src/engines/tendermint/message.rs | 60 ++++++++++++++--------- 3 files changed, 41 insertions(+), 33 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index f7cf5fecc..44bd0a743 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1172,16 +1172,8 @@ impl BlockChainClient for Client { // TODO: Make it an actual queue, return errors. fn queue_infinity_message(&self, message: Bytes) { - let full_rlp = UntrustedRlp::new(&message); - if let Ok(signature) = full_rlp.val_at::(0) { - if let Ok(message) = full_rlp.at(1) { - if let Ok(pub_key) = recover(&signature.into(), &message.as_raw().sha3()) { - if let Ok(new_message) = self.engine.handle_message(pub_key.sha3().into(), signature, message) - { - self.notify(|notify| notify.broadcast(new_message.clone())); - } - } - } + if let Ok(new_message) = self.engine.handle_message(UntrustedRlp::new(&message)) { + self.notify(|notify| notify.broadcast(new_message.clone())); } } } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index f03e765a9..d7f0796a5 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -151,7 +151,7 @@ pub trait Engine : Sync + Send { /// Handle any potential consensus messages; /// updating consensus state and potentially issuing a new one. - fn handle_message(&self, _sender: Address, _signature: H520, _message: UntrustedRlp) -> Result { Err(EngineError::UnexpectedMessage.into()) } + fn handle_message(&self, _message: UntrustedRlp) -> Result { Err(EngineError::UnexpectedMessage.into()) } // TODO: builtin contract routing - to do this properly, it will require removing the built-in configuration-reading logic // from Spec into here and removing the Spec::builtins field. diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 69f414ec8..4563c683e 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -16,23 +16,31 @@ //! Tendermint message handling. -use std::cmp::Ordering; +use util::*; use super::{Height, Round, BlockHash, Step}; use rlp::{View, DecoderError, Decodable, Decoder, Encodable, RlpStream, Stream}; -#[derive(Debug, PartialEq, Eq)] -pub enum Step { - Prevote, - Precommit -} - #[derive(Debug, PartialEq, Eq)] pub struct ConsensusMessage { - signature: H520, + pub signature: H520, height: Height, round: Round, - step: Step, - block_hash: BlockHash + pub step: Step, + block_hash: Option +} + +impl ConsensusMessage { + fn is_round(&self, height: Height, round: Round) -> bool { + self.height == height && self.round == round + } + + fn is_step(&self, height: Height, round: Round, step: Step) -> bool { + self.height == height && self.round == round && self.step == step + } + + pub fn is_aligned(&self, height: Height, round: Round, block_hash: Option) -> bool { + self.height == height && self.round == round && self.block_hash == block_hash + } } impl PartialOrd for ConsensusMessage { @@ -41,6 +49,17 @@ impl PartialOrd for ConsensusMessage { } } +impl Step { + fn number(&self) -> i8 { + match *self { + Step::Propose => -1, + Step::Prevote => 0, + Step::Precommit => 1, + Step::Commit => 2, + } + } +} + impl Ord for ConsensusMessage { fn cmp(&self, m: &ConsensusMessage) -> Ordering { if self.height != m.height { @@ -48,10 +67,7 @@ impl Ord for ConsensusMessage { } else if self.round != m.round { self.round.cmp(&m.round) } else if self.step != m.step { - match self.step { - Step::Prevote => Ordering::Less, - Step::Precommit => Ordering::Greater, - } + self.step.number().cmp(&m.step.number()) } else { self.block_hash.cmp(&m.block_hash) } @@ -71,11 +87,7 @@ impl Decodable for Step { impl Encodable for Step { fn rlp_append(&self, s: &mut RlpStream) { - match *self { - Step::Prevote => s.append(&0u8), - Step::Precommit => s.append(&1u8), - _ => panic!("No encoding needed for other steps"), - }; + s.append(&(self.number() as u8)); } } @@ -86,13 +98,17 @@ impl Decodable for ConsensusMessage { if decoder.as_raw().len() != try!(rlp.payload_info()).total() { return Err(DecoderError::RlpIsTooBig); } - let m = rlp.at(1); + let m = try!(rlp.at(1)); + let block_message: H256 = try!(m.val_at(3)); Ok(ConsensusMessage { signature: try!(rlp.val_at(0)), height: try!(m.val_at(0)), round: try!(m.val_at(1)), step: try!(m.val_at(2)), - block_hash: try!(m.val_at(3)) + block_hash: match block_message.is_zero() { + true => None, + false => Some(block_message), + } }) } } @@ -105,6 +121,6 @@ impl Encodable for ConsensusMessage { s.append(&self.height); s.append(&self.round); s.append(&self.step); - s.append(&self.block_hash); + s.append(&self.block_hash.unwrap_or(H256::zero())); } } From dd8ed42270b3abb973ea888a166363dfcef63f87 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 15 Nov 2016 10:20:42 +0000 Subject: [PATCH 043/295] update timeouts --- ethcore/src/engines/tendermint/timeout.rs | 41 +++++++++++------------ 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/ethcore/src/engines/tendermint/timeout.rs b/ethcore/src/engines/tendermint/timeout.rs index 47840d8b7..52d1bd2d0 100644 --- a/ethcore/src/engines/tendermint/timeout.rs +++ b/ethcore/src/engines/tendermint/timeout.rs @@ -26,12 +26,6 @@ pub struct TimerHandler { engine: Weak, } -impl TimerHandler { - pub fn new(engine: Weak) -> Self { - TimerHandler { engine: engine } - } -} - /// Base timeout of each step in ms. #[derive(Debug, Clone)] pub struct DefaultTimeouts { @@ -61,31 +55,36 @@ pub struct NextStep; pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 0; impl IoHandler for TimerHandler { - fn initialize(&self, io: &IoContext) { + fn initialize(&self, io: &IoContext) { if let Some(engine) = self.engine.upgrade() { - io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Error registering engine timeout"); + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.remaining_step_duration().as_millis()) + .unwrap_or_else(|e| warn!(target: "poa", "Failed to start consensus step timer: {}.", e)) } } - fn timeout(&self, io: &IoContext, timer: TimerToken) { + fn timeout(&self, io: &IoContext, timer: TimerToken) { if timer == ENGINE_TIMEOUT_TOKEN { if let Some(engine) = self.engine.upgrade() { - println!("Timeout: {:?}", get_time()); - // Can you release entering a clause? - let next_step = match *engine.s.try_read().unwrap() { - Step::Propose => Step::Propose, - Step::Prevote(_) => Step::Propose, - Step::Precommit(_, _) => Step::Propose, - Step::Commit(_, _) => { - engine.r.fetch_add(1, AtomicOrdering::Relaxed); + engine.step.fetch_add(1, AtomicOrdering::SeqCst); + engine.proposed.store(false, AtomicOrdering::SeqCst); + let next_step = match *engine.step.try_read().unwrap() { + Step::Propose => Step::Prevote, + Step::Prevote => Step::Precommit, + Step::Precommit => Step::Propose, + Step::Commit => { + engine.round.fetch_add(1, AtomicOrdering::Relaxed); Step::Propose }, }; - match next_step { - Step::Propose => engine.to_propose(), - _ => (), + + if let Some(ref channel) = *engine.message_channel.lock() { + match channel.send(ClientIoMessage::UpdateSealing) { + Ok(_) => trace!(target: "poa", "timeout: UpdateSealing message sent for step {}.", engine.step.load(AtomicOrdering::Relaxed)), + Err(err) => trace!(target: "poa", "timeout: Could not send a sealing message {} for step {}.", err, engine.step.load(AtomicOrdering::Relaxed)), + } } - io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Failed to restart consensus step timer.") + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout().as_millis()) + .unwrap_or_else(|e| warn!(target: "poa", "Failed to restart consensus step timer: {}.", e)) } } } From 54e495634560244f01de2e35df54c43649548e22 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 15 Nov 2016 10:21:18 +0000 Subject: [PATCH 044/295] return errors from constructor --- ethcore/src/engines/tendermint/mod.rs | 226 ++++++++++++++------------ ethcore/src/spec/spec.rs | 2 +- 2 files changed, 126 insertions(+), 102 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 70ba24a09..efaffb501 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -19,11 +19,11 @@ mod message; mod timeout; mod params; -mod vote; mod vote_collector; use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use util::*; +use basic_types::Seal; use error::{Error, BlockError}; use header::Header; use builtin::Builtin; @@ -42,16 +42,14 @@ use io::IoService; use self::message::ConsensusMessage; use self::timeout::{TimerHandler, NextStep}; use self::params::TendermintParams; -use self::vote::Vote; +use self::vote_collector::VoteCollector; -#[derive(Debug)] -enum Step { +#[derive(Debug, PartialEq, Eq)] +pub enum Step { Propose, - Prevote(ProposeCollect), - /// Precommit step storing the precommit vote and accumulating seal. - Precommit(ProposeCollect, Signatures), - /// Commit step storing a complete valid seal. - Commit(BlockHash, Signatures) + Prevote, + Precommit, + Commit } pub type Height = usize; @@ -69,43 +67,45 @@ pub struct Tendermint { timeout_service: IoService, /// Address to be used as authority. authority: RwLock

, + /// Blockchain height. + height: AtomicUsize, /// Consensus round. - r: AtomicUsize, + round: AtomicUsize, /// Consensus step. - s: RwLock, + step: RwLock, /// Current step timeout in ms. timeout: AtomicMs, /// Used to swith proposer. proposer_nonce: AtomicUsize, + /// Vote accumulator. + votes: VoteCollector } impl Tendermint { /// Create a new instance of Tendermint engine - pub fn new(params: CommonParams, our_params: TendermintParams, builtins: BTreeMap) -> Arc { + pub fn new(params: CommonParams, our_params: TendermintParams, builtins: BTreeMap) -> Result, Error> { let engine = Arc::new( Tendermint { params: params, timeout: AtomicUsize::new(our_params.timeouts.propose), our_params: our_params, builtins: builtins, - timeout_service: IoService::::start().expect("Error creating engine timeout service"), + timeout_service: try!(IoService::::start()), authority: RwLock::new(Address::default()), - r: AtomicUsize::new(0), - s: RwLock::new(Step::Propose), - proposer_nonce: AtomicUsize::new(0) + height: AtomicUsize::new(0), + round: AtomicUsize::new(0), + step: RwLock::new(Step::Propose), + proposer_nonce: AtomicUsize::new(0), + votes: VoteCollector::new() }); - let handler = TimerHandler::new(Arc::downgrade(&engine)); - engine.timeout_service.register_handler(Arc::new(handler)).expect("Error creating engine timeout service"); - engine - } - - fn is_proposer(&self, address: &Address) -> bool { - self.is_nonce_proposer(self.proposer_nonce.load(AtomicOrdering::SeqCst), address) + let handler = TimerHandler { engine: Arc::downgrade(&engine) }; + try!(engine.timeout_service.register_handler(Arc::new(handler))); + Ok(engine) } fn nonce_proposer(&self, proposer_nonce: usize) -> &Address { let ref p = self.our_params; - p.authorities.get(proposer_nonce%p.authority_n).unwrap() + p.authorities.get(proposer_nonce % p.authority_n).unwrap() } fn is_nonce_proposer(&self, proposer_nonce: usize, address: &Address) -> bool { @@ -123,12 +123,12 @@ impl Tendermint { } fn to_step(&self, step: Step) { - let mut guard = self.s.try_write().unwrap(); + let mut guard = self.step.try_write().unwrap(); *guard = step; } fn to_propose(&self) { - trace!(target: "tendermint", "step: entering propose"); + trace!(target: "poa", "step: entering propose"); println!("step: entering propose"); self.proposer_nonce.fetch_add(1, AtomicOrdering::Relaxed); self.to_step(Step::Propose); @@ -136,7 +136,7 @@ impl Tendermint { fn propose_message(&self, message: UntrustedRlp) -> Result { // Check if message is for correct step. - match *self.s.try_read().unwrap() { + match *self.step.try_read().unwrap() { Step::Propose => (), _ => try!(Err(EngineError::WrongStep)), } @@ -146,7 +146,7 @@ impl Tendermint { } fn to_prevote(&self, proposal: BlockHash) { - trace!(target: "tendermint", "step: entering prevote"); + trace!(target: "poa", "step: entering prevote"); println!("step: entering prevote"); // Proceed to the prevote step. self.to_step(Step::Prevote(self.new_vote(proposal))); @@ -154,8 +154,8 @@ impl Tendermint { fn prevote_message(&self, sender: Address, message: UntrustedRlp) -> Result { // Check if message is for correct step. - let hash = match *self.s.try_write().unwrap() { - Step::Prevote(ref mut vote) => { + let hash = match *self.step.try_write().unwrap() { + Step::Prevote => { // Vote if message is about the right block. if vote.hash == try!(message.as_val()) { vote.vote(sender); @@ -178,43 +178,39 @@ impl Tendermint { } fn to_precommit(&self, proposal: BlockHash) { - trace!(target: "tendermint", "step: entering precommit"); + trace!(target: "poa", "step: entering precommit"); println!("step: entering precommit"); self.to_step(Step::Precommit(self.new_vote(proposal), Vec::new())); } - fn precommit_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { - // Check if message is for correct step. - match *self.s.try_write().unwrap() { - Step::Precommit(ref mut vote, ref mut seal) => { - // Vote and accumulate seal if message is about the right block. - if vote.hash == try!(message.as_val()) { - if vote.vote(sender) { seal.push(encode(&signature).to_vec()); } - // Commit if precommit is won. - if vote.is_won() { self.to_commit(vote.hash.clone(), seal.clone()); } - Ok(message.as_raw().to_vec()) - } else { - try!(Err(EngineError::WrongVote)) - } - }, - _ => try!(Err(EngineError::WrongStep)), - } - } - /// Move to commit step, when valid block is known and being distributed. pub fn to_commit(&self, block_hash: H256, seal: Vec) { - trace!(target: "tendermint", "step: entering commit"); + trace!(target: "poa", "step: entering commit"); println!("step: entering commit"); self.to_step(Step::Commit(block_hash, seal)); } fn threshold(&self) -> usize { - self.our_params.authority_n*2/3 + self.our_params.authority_n * 2/3 } fn next_timeout(&self) -> u64 { self.timeout.load(AtomicOrdering::Relaxed) as u64 } + + /// Round proposer switching. + fn is_proposer(&self, address: &Address) -> bool { + self.is_nonce_proposer(self.proposer_nonce.load(AtomicOrdering::SeqCst), address) + } + + fn is_current(&self, message: &ConsensusMessage) -> bool { + message.is_step(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), self.step.load(AtomicOrdering::SeqCst)) + } +} + +/// Block hash including the consensus round. +fn block_hash(header: &Header) -> H256 { + header.rlp(Seal::WithSome(1)).sha3() } impl Engine for Tendermint { @@ -253,60 +249,79 @@ impl Engine for Tendermint { } } - /// Set author to proposer and set the correct round in the seal. + /// Set the correct round in the seal. /// 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 on_close_block(&self, block: &mut ExecutedBlock) { + let round = self.round.load(AtomicOrdering::SeqCst); + vec![::rlp::encode(&round).to_vec()] + } + + /// Round proposer switching. + fn is_sealer(&self, address: &Address) -> Option { + Some(self.is_proposer(address)) } /// Attempt to seal the block internally using all available signatures. /// /// None is returned if not enough signatures can be collected. fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { - if let (Some(ap), Some(step)) = (accounts, self.s.try_read()) { + if let (Some(ap), Some(step)) = (accounts, self.step.try_read()) { let header = block.header(); let author = header.author(); match *step { - Step::Commit(hash, ref seal) if hash == header.bare_hash() => + Step::Commit => { // Commit the block using a complete signature set. - return Some(seal.clone()), - Step::Propose if self.is_proposer(header.author()) => + let round = self.round.load(AtomicOrdering::SeqCst); + if let Some((proposer, votes)) = self.votes.seal_signatures(header.number() as Height, round, block_hash(header)).split_first() { + if votes.len() + 1 > self.threshold() { + return Some(vec![ + ::rlp::encode(&round).to_vec(), + ::rlp::encode(proposer).to_vec(), + ::rlp::encode(&votes).to_vec() + ]); + } + } + }, + Step::Propose if self.is_proposer(author) => // Seal block with propose signature. - if let Some(proposal) = Vote::propose(header, &ap) { - return Some(vec![::rlp::encode(&proposal).to_vec(), Vec::new()]) + + if let Ok(signature) = ap.sign(*author, None, block_hash(header)) { + return Some(vec![ + ::rlp::encode(&self.round.load(AtomicOrdering::SeqCst)).to_vec(), + ::rlp::encode(&signature.into()).to_vec(), + Vec::new() + ]) } else { - trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable"); + trace!(target: "poa", "generate_seal: FAIL: accounts secret key unavailable"); }, _ => {}, } } else { - trace!(target: "basicauthority", "generate_seal: FAIL: accounts not provided"); + trace!(target: "poa", "generate_seal: FAIL: accounts not provided"); } None } - fn handle_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { - let message: ConsensusMessage = try!(message.as_val()); - - if self.is_authority(&sender) { - //match message { - // ConsensusMessage::Prevote - //} + fn handle_message(&self, rlp: UntrustedRlp) -> Result { + let message: ConsensusMessage = try!(rlp.as_val()); + let sender = public_to_address(&try!(recover(&message.signature.into(), &try!(rlp.at(1)).as_raw().sha3()))); + // TODO: Do not admit old messages. + if !self.is_authority(&sender) { + try!(Err(BlockError::InvalidSeal)); } - try!(Err(EngineError::UnknownStep)) - - // Check if correct round. - //if self.r.load(AtomicOrdering::Relaxed) != try!(message.val_at(0)) { - // try!(Err(EngineError::WrongRound)) - //} - // Handle according to step. -// match try!(message.val_at(1)) { -// 0u8 if self.is_proposer(&sender) => self.propose_message(try!(message.at(2))), -// 1 if self.is_authority(&sender) => self.prevote_message(sender, try!(message.at(2))), -// 2 if self.is_authority(&sender) => self.precommit_message(sender, signature, try!(message.at(2))), -// _ => try!(Err(EngineError::UnknownStep)), -// } + // Check if the message affects the current step. + if self.is_current(message) { + match self.step.load(AtomicOrdering::SeqCst) { + Step::Prevote => { + let votes = aligned_signatures(message); + if votes.len() > self.threshold() { + } + }, + Step::Precommit => , + } + } + self.votes.vote(message, sender); } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { @@ -322,21 +337,28 @@ impl Engine for Tendermint { /// Also transitions to Prevote if verifying Proposal. fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { - let to_address = |b: &Vec| { - let sig: H520 = try!(UntrustedRlp::new(b.as_slice()).as_val()); - Ok(public_to_address(&try!(recover(&sig.into(), &header.bare_hash())))) - }; - let authority_set = self.our_params.authorities.iter().cloned().collect(); - let seal_set = try!(header - .seal() - .iter() - .map(to_address) - .collect::, Error>>()); - if seal_set.intersection(&authority_set).count() <= self.threshold() { + let proposal_signature: H520 = try!(UntrustedRlp::new(header.seal()[1].as_slice()).as_val()); + let proposer = public_to_address(&try!(recover(&proposal_signature.into(), &block_hash(header)))); + if !self.is_proposer(&proposer) { try!(Err(BlockError::InvalidSeal)) - } else { - Ok(()) } + let proposal = ConsensusMessage { + signature: proposal_signature, + height: header.number() as usize, + round: try!(UntrustedRlp::new(header.seal()[0].as_slice()).as_val()), + step: Step::Propose, + block_hash: Some(block_hash(header)) + }; + self.votes.vote(proposal, proposer); + let votes_rlp = UntrustedRlp::new(&header.seal()[2]); + for rlp in votes_rlp.iter() { + let sig: H520 = try!(rlp.as_val()); + let address = public_to_address(&try!(recover(&sig.into(), &block_hash(header)))); + if !self.our_params.authorities.contains(a) { + try!(Err(BlockError::InvalidSeal)) + } + } + Ok(()) } fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { @@ -368,7 +390,9 @@ impl Engine for Tendermint { } fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { - new_header.seal().get(1).expect("Tendermint seal should have two elements.").len() > best_header.seal().get(1).expect("Tendermint seal should have two elements.").len() + let new_signatures = new_header.seal().get(2).expect("Tendermint seal should have three elements.").len(); + let best_signatures = best_header.seal().get(2).expect("Tendermint seal should have three elements.").len(); + new_signatures > best_signatures } } @@ -377,7 +401,7 @@ mod tests { use std::thread::sleep; use std::time::{Duration}; use util::*; - use rlp::{UntrustedRlp, RlpStream, Stream, View, encode}; + use rlp::{UntrustedRlp, RlpStream, Stream, View}; use block::*; use error::{Error, BlockError}; use header::Header; @@ -416,11 +440,11 @@ mod tests { let v0 = tap.insert_account("0".sha3(), "0").unwrap(); let sig0 = tap.sign(v0, Some("0".into()), header.bare_hash()).unwrap(); - seal.push(encode(&(&*sig0 as &[u8])).to_vec()); + seal.push(::rlp::encode(&(&*sig0 as &[u8])).to_vec()); let v1 = tap.insert_account("1".sha3(), "1").unwrap(); let sig1 = tap.sign(v1, Some("1".into()), header.bare_hash()).unwrap(); - seal.push(encode(&(&*sig1 as &[u8])).to_vec()); + seal.push(::rlp::encode(&(&*sig1 as &[u8])).to_vec()); seal } @@ -475,7 +499,7 @@ mod tests { let v1 = tap.insert_account("0".sha3(), "0").unwrap(); let sig1 = tap.sign(v1, Some("0".into()), header.bare_hash()).unwrap(); - seal.push(encode(&(&*sig1 as &[u8])).to_vec()); + seal.push(::rlp::encode(&(&*sig1 as &[u8])).to_vec()); header.set_seal(seal.clone()); @@ -484,7 +508,7 @@ mod tests { let v2 = tap.insert_account("101".sha3(), "101").unwrap(); let sig2 = tap.sign(v2, Some("101".into()), header.bare_hash()).unwrap(); - seal.push(encode(&(&*sig2 as &[u8])).to_vec()); + seal.push(::rlp::encode(&(&*sig2 as &[u8])).to_vec()); header.set_seal(seal); diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index e6ca8a8c4..b5303e513 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -144,7 +144,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::Tendermint(tendermint) => Tendermint::new(params, From::from(tendermint.params), builtins), + ethjson::spec::Engine::Tendermint(tendermint) => Tendermint::new(params, From::from(tendermint.params), builtins).expect("Failed to start the Tendermint consensus engine."), } } From ff2dc5dd57e7c8d97af9573a4d28d825616ff5f5 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 15 Nov 2016 10:21:49 +0000 Subject: [PATCH 045/295] vote counting --- .../src/engines/tendermint/vote_collector.rs | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 448a4b209..31a43c169 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -16,22 +16,49 @@ //! Collects votes on hashes at each height and round. -use super::vote::Vote; -use super::{Height, Round}; +use util::*; +use super::message::ConsensusMessage; +use super::{Height, Round, Step}; use ethkey::recover; -use util::{RwLock, HashMap, HashSet}; -/// Signed voting on hashes. #[derive(Debug)] pub struct VoteCollector { - /// Structure for storing all votes. - votes: RwLock>>, + /// Storing all Proposals, Prevotes and Precommits. + votes: RwLock> } impl VoteCollector { pub fn new() -> VoteCollector { - VoteCollector { votes: RwLock::new(HashMap::new()) } + VoteCollector { votes: RwLock::new(BTreeMap::new()) } } - pub fn vote() {} + pub fn vote(&self, message: ConsensusMessage, voter: Address) { + if let Some(mut guard) = self.votes.write() { + *guard.insert(message, voter); + } + } + + pub fn seal_signatures(&self, height: Height, round: Round, block_hash: Option) -> Vec { + self.votes + .read() + .keys() + // Get only Propose and Precommits. + .filter(|m| m.is_aligned(height, round, block_hash) && m.step != Step::Prevote) + .map(|m| m.signature) + .collect() + } + + pub fn aligned_signatures(&self, message: &ConsensusMessage) -> Vec { + self.seal_signatures(message.height, message.round, message.block_hash) + } + + pub fn count_signatures(&self, height: Height, round: Round) -> usize { + self.votes + .read() + .keys() + // Get only Propose and Precommits. + .filter(|m| m.is_round(height, round) && m.step != Step::Prevote) + .map(|m| m.signature) + .collect() + } } From 06e5416537cb99eb76b9b87422e68c371f2c62d1 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 15 Nov 2016 11:27:09 +0000 Subject: [PATCH 046/295] header fns, extra_info --- ethcore/src/engines/tendermint/mod.rs | 28 ++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index efaffb501..4a789a088 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -213,6 +213,14 @@ fn block_hash(header: &Header) -> H256 { header.rlp(Seal::WithSome(1)).sha3() } +fn proposer_signature(header: &Header) -> H520 { + try!(UntrustedRlp::new(header.seal()[1].as_slice()).as_val()) +} + +fn consensus_round(header: &Header) -> Round { + try!(UntrustedRlp::new(header.seal()[0].as_slice()).as_val()) +} + impl Engine for Tendermint { fn name(&self) -> &str { "Tendermint" } fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } @@ -223,7 +231,14 @@ impl Engine for Tendermint { 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 extra_info(&self, header: &Header) -> BTreeMap { + map![ + "signature".into() => proposer_signature(header).as_ref().map(ToString::to_string).unwrap_or("".into()), + "height".into() => header.number().to_string(), + "round".into() => consensus_round(header).as_ref().map(ToString::to_string).unwrap_or("".into()), + "block_hash".into() => block_hash(header).as_ref().map(ToString::to_string).unwrap_or("".into()) + ] + } fn schedule(&self, _env_info: &EnvInfo) -> Schedule { Schedule::new_homestead() @@ -253,7 +268,7 @@ impl Engine for Tendermint { /// 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) { let round = self.round.load(AtomicOrdering::SeqCst); - vec![::rlp::encode(&round).to_vec()] + block.header.set_seal(vec![::rlp::encode(&round).to_vec(), Vec::new(), Vec::new()]); } /// Round proposer switching. @@ -272,7 +287,7 @@ impl Engine for Tendermint { Step::Commit => { // Commit the block using a complete signature set. let round = self.round.load(AtomicOrdering::SeqCst); - if let Some((proposer, votes)) = self.votes.seal_signatures(header.number() as Height, round, block_hash(header)).split_first() { + if let Some((proposer, votes)) = self.votes.seal_signatures(header.number() as Height, round, Some(block_hash(header))).split_first() { if votes.len() + 1 > self.threshold() { return Some(vec![ ::rlp::encode(&round).to_vec(), @@ -337,15 +352,14 @@ impl Engine for Tendermint { /// Also transitions to Prevote if verifying Proposal. fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { - let proposal_signature: H520 = try!(UntrustedRlp::new(header.seal()[1].as_slice()).as_val()); - let proposer = public_to_address(&try!(recover(&proposal_signature.into(), &block_hash(header)))); + let proposer = public_to_address(&try!(recover(&proposal_signature(header).into(), &block_hash(header)))); if !self.is_proposer(&proposer) { try!(Err(BlockError::InvalidSeal)) } let proposal = ConsensusMessage { signature: proposal_signature, - height: header.number() as usize, - round: try!(UntrustedRlp::new(header.seal()[0].as_slice()).as_val()), + height: header.number() as Height, + round: consensus_round(header), step: Step::Propose, block_hash: Some(block_hash(header)) }; From 7d0eafd5cde7f244e9695f362d0aa245ee1e392a Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 15 Nov 2016 13:33:11 +0000 Subject: [PATCH 047/295] fix extra_info --- ethcore/src/engines/tendermint/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 4a789a088..e3ccb855b 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -233,10 +233,10 @@ impl Engine for Tendermint { /// Additional engine-specific information for the user/developer concerning `header`. fn extra_info(&self, header: &Header) -> BTreeMap { map![ - "signature".into() => proposer_signature(header).as_ref().map(ToString::to_string).unwrap_or("".into()), + "signature".into() => proposer_signature(header).to_string(), "height".into() => header.number().to_string(), - "round".into() => consensus_round(header).as_ref().map(ToString::to_string).unwrap_or("".into()), - "block_hash".into() => block_hash(header).as_ref().map(ToString::to_string).unwrap_or("".into()) + "round".into() => consensus_round(header).to_string(), + "block_hash".into() => block_hash(header).to_string().unwrap_or("".into()) ] } @@ -268,7 +268,7 @@ impl Engine for Tendermint { /// 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) { let round = self.round.load(AtomicOrdering::SeqCst); - block.header.set_seal(vec![::rlp::encode(&round).to_vec(), Vec::new(), Vec::new()]); + block.fields_mut().header.set_seal(vec![::rlp::encode(&round).to_vec(), Vec::new(), Vec::new()]); } /// Round proposer switching. From 1c958695c3adf77b3da55fc22857be8aab952f14 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 15 Nov 2016 15:25:30 +0000 Subject: [PATCH 048/295] timeout loading --- ethcore/src/engines/tendermint/params.rs | 12 +++-- ethcore/src/engines/tendermint/timeout.rs | 53 ++++++++++++++--------- json/src/spec/tendermint.rs | 12 +++++ 3 files changed, 54 insertions(+), 23 deletions(-) diff --git a/ethcore/src/engines/tendermint/params.rs b/ethcore/src/engines/tendermint/params.rs index 904499c04..4e3996c99 100644 --- a/ethcore/src/engines/tendermint/params.rs +++ b/ethcore/src/engines/tendermint/params.rs @@ -17,7 +17,7 @@ //! Tendermint specific parameters. use ethjson; -use super::timeout::DefaultTimeouts; +use super::timeout::TendermintTimeouts; use util::{Address, U256}; /// `Tendermint` params. @@ -30,7 +30,7 @@ pub struct TendermintParams { /// Number of authorities. pub authority_n: usize, /// Timeout durations for different steps. - pub timeouts: DefaultTimeouts, + pub timeouts: TendermintTimeouts, } impl Default for TendermintParams { @@ -54,7 +54,13 @@ impl From for TendermintParams { gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), authorities: val, authority_n: val_n, - timeouts: DefaultTimeouts::default() + let dt = TendermintTimeouts::default(); + timeouts: TendermintTimeouts { + propose: p.timeout_propose.unwrap_or(dt.propose), + prevote: p.timeout_prevote.unwrap_or(dt.prevote), + precommit: p.timeout_precommit.unwrap_or(dt.precommit), + commit: p.timeout_commit.unwrap_or(dt.commit) + } } } } diff --git a/ethcore/src/engines/tendermint/timeout.rs b/ethcore/src/engines/tendermint/timeout.rs index 52d1bd2d0..f107d06ee 100644 --- a/ethcore/src/engines/tendermint/timeout.rs +++ b/ethcore/src/engines/tendermint/timeout.rs @@ -20,7 +20,8 @@ use std::sync::atomic::{Ordering as AtomicOrdering}; use std::sync::Weak; use io::{IoContext, IoHandler, TimerToken}; use super::{Tendermint, Step}; -use time::get_time; +use time::{get_time, Duration}; +use service::ClientIoMessage; pub struct TimerHandler { engine: Weak, @@ -28,41 +29,50 @@ pub struct TimerHandler { /// Base timeout of each step in ms. #[derive(Debug, Clone)] -pub struct DefaultTimeouts { - pub propose: Ms, - pub prevote: Ms, - pub precommit: Ms, - pub commit: Ms +pub struct TendermintTimeouts { + propose: Duration, + prevote: Duartion, + precommit: Duration, + commit: Duration } -impl Default for DefaultTimeouts { - fn default() -> Self { - DefaultTimeouts { - propose: 1000, - prevote: 1000, - precommit: 1000, - commit: 1000 +impl TendermintTimeouts { + pub fn for_step(step: Step) -> Duration { + match step { + Step::Propose => self.propose, + Step::Prevote => self.prevote, + Step::Precommit => self.precommit, + Step::Commit => self.commit, } } } -pub type Ms = usize; +impl Default for TendermintTimeouts { + fn default() -> Self { + DefaultTimeouts { + propose: Duration::milliseconds(1000), + prevote: Duration::milliseconds(1000), + precommit: Duration::milliseconds(1000), + commit: Duration::milliseconds(1000) + } + } +} #[derive(Clone)] pub struct NextStep; /// Timer token representing the consensus step timeouts. -pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 0; +pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 23; impl IoHandler for TimerHandler { - fn initialize(&self, io: &IoContext) { + fn initialize(&self, io: &IoContext) { if let Some(engine) = self.engine.upgrade() { io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.remaining_step_duration().as_millis()) .unwrap_or_else(|e| warn!(target: "poa", "Failed to start consensus step timer: {}.", e)) } } - fn timeout(&self, io: &IoContext, timer: TimerToken) { + 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::SeqCst); @@ -77,14 +87,17 @@ impl IoHandler for TimerHandler { }, }; + + io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout().as_millis()) + .unwrap_or_else(|e| warn!(target: "poa", "Failed to restart consensus step timer: {}.", e)) + if let Some(ref channel) = *engine.message_channel.lock() { match channel.send(ClientIoMessage::UpdateSealing) { - Ok(_) => trace!(target: "poa", "timeout: UpdateSealing message sent for step {}.", engine.step.load(AtomicOrdering::Relaxed)), + Ok(_) => trace!(target: "poa", "timeout: UpdateSealing message sent for step {}.", engine.step.), Err(err) => trace!(target: "poa", "timeout: Could not send a sealing message {} for step {}.", err, engine.step.load(AtomicOrdering::Relaxed)), } } - io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout().as_millis()) - .unwrap_or_else(|e| warn!(target: "poa", "Failed to restart consensus step timer: {}.", e)) + } } } diff --git a/json/src/spec/tendermint.rs b/json/src/spec/tendermint.rs index 3d1a5a06d..6858602da 100644 --- a/json/src/spec/tendermint.rs +++ b/json/src/spec/tendermint.rs @@ -27,6 +27,18 @@ pub struct TendermintParams { pub gas_limit_bound_divisor: Uint, /// Valid authorities pub authorities: Vec
, + /// Propose step timeout in milliseconds. + #[serde(rename="timeoutPropose")] + pub timeout_propose: Option, + /// Prevote step timeout in milliseconds. + #[serde(rename="timeoutPrevote")] + pub timeout_prevote: Option, + /// Precommit step timeout in milliseconds. + #[serde(rename="timeoutPrecommit")] + pub timeout_precommit: Option, + /// Commit step timeout in milliseconds. + #[serde(rename="timeoutCommit")] + pub timeout_commit: Option, } /// Tendermint engine deserialization. From 2fa34fd6a874f1fc8341f2fb162cebe5083cc0ae Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 16 Nov 2016 12:43:21 +0000 Subject: [PATCH 049/295] step transition messaging --- ethcore/src/engines/tendermint/mod.rs | 24 +++++++++++---- ethcore/src/engines/tendermint/timeout.rs | 37 ++++++++++++----------- json/src/spec/mod.rs | 5 +-- 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 558479ad5..cc3283b5f 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -38,13 +38,14 @@ use engines::{Engine, EngineError, ProposeCollect}; use blockchain::extras::BlockDetails; use views::HeaderView; use evm::Schedule; -use io::IoService; +use io::{IoService, IoChannel}; +use service::ClientIoMessage; use self::message::ConsensusMessage; use self::timeout::{TimerHandler, NextStep}; use self::params::TendermintParams; use self::vote_collector::VoteCollector; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum Step { Propose, Prevote, @@ -64,7 +65,7 @@ pub struct Tendermint { params: CommonParams, our_params: TendermintParams, builtins: BTreeMap, - timeout_service: IoService, + step_service: IoService, /// Address to be used as authority. authority: RwLock
, /// Blockchain height. @@ -77,6 +78,8 @@ pub struct Tendermint { proposer_nonce: AtomicUsize, /// Vote accumulator. votes: VoteCollector, + /// Proposed block held until seal is gathered. + proposed_block: Mutex>, /// Channel for updating the sealing. message_channel: Mutex>> } @@ -89,20 +92,30 @@ impl Tendermint { params: params, our_params: our_params, builtins: builtins, - timeout_service: try!(IoService::::start()), + step_service: try!(IoService::::start()), authority: RwLock::new(Address::default()), height: AtomicUsize::new(0), round: AtomicUsize::new(0), step: RwLock::new(Step::Propose), proposer_nonce: AtomicUsize::new(0), votes: VoteCollector::new(), + proposed_block: Mutex::new(None), message_channel: Mutex::new(None) }); let handler = TimerHandler { engine: Arc::downgrade(&engine) }; - try!(engine.timeout_service.register_handler(Arc::new(handler))); + try!(engine.step_service.register_handler(Arc::new(handler))); Ok(engine) } + fn update_sealing(&self) { + if let Some(ref channel) = *self.message_channel.lock() { + match channel.send(ClientIoMessage::UpdateSealing) { + Ok(_) => trace!(target: "poa", "timeout: UpdateSealing message sent."), + Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), + } + } + } + fn nonce_proposer(&self, proposer_nonce: usize) -> &Address { let ref p = self.our_params; p.authorities.get(proposer_nonce % p.authority_n).unwrap() @@ -191,7 +204,6 @@ impl Engine for Tendermint { } /// Set the correct round in the seal. - /// 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) { let round = self.round.load(AtomicOrdering::SeqCst); block.fields_mut().header.set_seal(vec![::rlp::encode(&round).to_vec(), Vec::new(), Vec::new()]); diff --git a/ethcore/src/engines/tendermint/timeout.rs b/ethcore/src/engines/tendermint/timeout.rs index 9739861f4..64a21a299 100644 --- a/ethcore/src/engines/tendermint/timeout.rs +++ b/ethcore/src/engines/tendermint/timeout.rs @@ -16,9 +16,10 @@ //! Tendermint timeout handling. +use util::Mutex; use std::sync::atomic::{Ordering as AtomicOrdering}; use std::sync::Weak; -use io::{IoContext, IoHandler, TimerToken}; +use io::{IoContext, IoHandler, TimerToken, IoChannel}; use super::{Tendermint, Step}; use time::{get_time, Duration}; use service::ClientIoMessage; @@ -59,7 +60,7 @@ impl Default for TendermintTimeouts { } #[derive(Clone)] -pub struct NextStep; +pub struct NextStep(Step); /// Timer token representing the consensus step timeouts. pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 23; @@ -69,15 +70,6 @@ fn set_timeout(io: &IoContext, timeout: Duration) { .unwrap_or_else(|e| warn!(target: "poa", "Failed to set consensus step timeout: {}.", e)) } -fn update_sealing(io_channel: Mutex>>) { - if let Some(ref channel) = *io_channel.lock() { - match channel.send(ClientIoMessage::UpdateSealing) { - Ok(_) => trace!(target: "poa", "timeout: UpdateSealing message sent for round {}.", engine.round.load(AtomicOrdering::SeqCst)), - Err(err) => trace!(target: "poa", "timeout: Could not send a sealing message {} for round {}.", err, engine.round.load(AtomicOrdering::SeqCst)), - } - } -} - impl IoHandler for TimerHandler { fn initialize(&self, io: &IoContext) { if let Some(engine) = self.engine.upgrade() { @@ -112,18 +104,29 @@ impl IoHandler for TimerHandler { }; if let Some(step) = next_step { - *engine.step.write() = step + *engine.step.write() = step; + if step == Step::Propose { + engine.update_sealing(); + } } - } } } - fn message(&self, io: &IoContext, _net_message: &NextStep) { + fn message(&self, io: &IoContext, message: &NextStep) { if let Some(engine) = self.engine.upgrade() { - println!("Message: {:?}", get_time().sec); - io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to restart consensus step timer."); - io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Failed to restart consensus step timer.") + io.clear_timer(ENGINE_TIMEOUT_TOKEN); + let NextStep(next_step) = *message; + *engine.step.write() = next_step; + match next_step { + Step::Propose => { + engine.update_sealing(); + set_timeout(io, engine.our_params.timeouts.propose) + }, + Step::Prevote => set_timeout(io, engine.our_params.timeouts.prevote), + Step::Precommit => set_timeout(io, engine.our_params.timeouts.precommit), + Step::Commit => set_timeout(io, engine.our_params.timeouts.commit), + }; } } } diff --git a/json/src/spec/mod.rs b/json/src/spec/mod.rs index 741e847ac..7fd123da4 100644 --- a/json/src/spec/mod.rs +++ b/json/src/spec/mod.rs @@ -39,8 +39,5 @@ pub use self::engine::Engine; pub use self::state::State; pub use self::ethash::{Ethash, EthashParams}; pub use self::basic_authority::{BasicAuthority, BasicAuthorityParams}; -<<<<<<< HEAD -pub use self::tendermint::{Tendermint, TendermintParams}; -======= pub use self::authority_round::{AuthorityRound, AuthorityRoundParams}; ->>>>>>> parity/master +pub use self::tendermint::{Tendermint, TendermintParams}; From 3b0d5503b14258c95e39995158f8ee3cec12e043 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 16 Nov 2016 13:13:21 +0000 Subject: [PATCH 050/295] fix compilation --- ethcore/src/engines/tendermint/mod.rs | 10 ++++++---- ethcore/src/engines/tendermint/timeout.rs | 23 ++++++++++++----------- sync/src/api.rs | 2 +- sync/src/infinity.rs | 6 +++--- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index cc3283b5f..47ed8f828 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -41,7 +41,7 @@ use evm::Schedule; use io::{IoService, IoChannel}; use service::ClientIoMessage; use self::message::ConsensusMessage; -use self::timeout::{TimerHandler, NextStep}; +use self::timeout::{TransitionHandler, NextStep}; use self::params::TendermintParams; use self::vote_collector::VoteCollector; @@ -102,7 +102,7 @@ impl Tendermint { proposed_block: Mutex::new(None), message_channel: Mutex::new(None) }); - let handler = TimerHandler { engine: Arc::downgrade(&engine) }; + let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; try!(engine.step_service.register_handler(Arc::new(handler))); Ok(engine) } @@ -241,7 +241,7 @@ impl Engine for Tendermint { if let Ok(signature) = ap.sign(*author, None, block_hash(header)) { return Some(vec![ ::rlp::encode(&self.round.load(AtomicOrdering::SeqCst)).to_vec(), - ::rlp::encode(&signature.into()).to_vec(), + ::rlp::encode(&H520::from(signature)).to_vec(), Vec::new() ]) } else { @@ -271,10 +271,12 @@ impl Engine for Tendermint { if votes.len() > self.threshold() { } }, - Step::Precommit => , + Step::Precommit => {}, + _ => {}, } } self.votes.vote(message, sender); + Err(BlockError::InvalidSeal.into()) } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { diff --git a/ethcore/src/engines/tendermint/timeout.rs b/ethcore/src/engines/tendermint/timeout.rs index 64a21a299..789755077 100644 --- a/ethcore/src/engines/tendermint/timeout.rs +++ b/ethcore/src/engines/tendermint/timeout.rs @@ -16,16 +16,14 @@ //! Tendermint timeout handling. -use util::Mutex; use std::sync::atomic::{Ordering as AtomicOrdering}; use std::sync::Weak; -use io::{IoContext, IoHandler, TimerToken, IoChannel}; +use io::{IoContext, IoHandler, TimerToken}; use super::{Tendermint, Step}; -use time::{get_time, Duration}; -use service::ClientIoMessage; +use time::Duration; -pub struct TimerHandler { - engine: Weak, +pub struct TransitionHandler { + pub engine: Weak, } /// Base timeout of each step in ms. @@ -70,7 +68,7 @@ fn set_timeout(io: &IoContext, timeout: Duration) { .unwrap_or_else(|e| warn!(target: "poa", "Failed to set consensus step timeout: {}.", e)) } -impl IoHandler for TimerHandler { +impl IoHandler for TransitionHandler { fn initialize(&self, io: &IoContext) { if let Some(engine) = self.engine.upgrade() { set_timeout(io, engine.our_params.timeouts.propose) @@ -104,7 +102,7 @@ impl IoHandler for TimerHandler { }; if let Some(step) = next_step { - *engine.step.write() = step; + *engine.step.write() = step.clone(); if step == Step::Propose { engine.update_sealing(); } @@ -115,9 +113,12 @@ impl IoHandler for TimerHandler { fn message(&self, io: &IoContext, message: &NextStep) { if let Some(engine) = self.engine.upgrade() { - io.clear_timer(ENGINE_TIMEOUT_TOKEN); - let NextStep(next_step) = *message; - *engine.step.write() = next_step; + match io.clear_timer(ENGINE_TIMEOUT_TOKEN) { + Ok(_) => {}, + Err(io_err) => warn!(target: "poa", "Could not remove consensus timer {}.", io_err), + }; + let NextStep(next_step) = message.clone(); + *engine.step.write() = next_step.clone(); match next_step { Step::Propose => { engine.update_sealing(); diff --git a/sync/src/api.rs b/sync/src/api.rs index bba26e8cc..078cac74c 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -280,7 +280,7 @@ impl ChainNotify for EthSync { } fn stop(&self) { - self.handler.snapshot_service.abort_restore(); + self.eth_handler.snapshot_service.abort_restore(); self.network.stop().unwrap_or_else(|e| warn!("Error stopping network: {:?}", e)); } diff --git a/sync/src/infinity.rs b/sync/src/infinity.rs index 810db32ed..936060a1d 100644 --- a/sync/src/infinity.rs +++ b/sync/src/infinity.rs @@ -37,7 +37,7 @@ const GENERIC_PACKET: u8 = 0x01; pub struct NetworkStatus { pub protocol_version: u8, /// The underlying p2p network version. - pub network_id: U256, + pub network_id: usize, /// Total number of connected peers pub num_peers: usize, /// Total number of active peers @@ -52,7 +52,7 @@ struct PeerInfo { /// Peer chain genesis hash genesis: H256, /// Peer network id - network_id: U256, + network_id: usize, } /// Infinity protocol handler. @@ -61,7 +61,7 @@ pub struct InfinitySync { /// All connected peers peers: HashMap, /// Network ID - network_id: U256, + network_id: usize, } impl InfinitySync { From 51bbad66d06d4ecc7dbaeba3fe01740d74076866 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 16 Nov 2016 15:56:16 +0000 Subject: [PATCH 051/295] add a path to submit seal from engine --- ethcore/src/client/client.rs | 7 +++++++ ethcore/src/engines/tendermint/mod.rs | 9 +++++++++ ethcore/src/miner/miner.rs | 6 +++--- ethcore/src/service.rs | 8 +++++++- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index e0f33af6a..e7aa716bd 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -569,6 +569,13 @@ impl Client { self.miner.update_sealing(self) } + /// Used by PoA to submit gathered signatures. + pub fn submit_seal(&self, block_hash: H256, seal: Vec) { + if self.miner.submit_seal(self, block_hash, seal).is_err() { + warn!(target: "poa", "Wrong internal seal submission!") + } + } + /// Attempt to get a copy of a specific block's final state. /// /// This will not fail if given BlockID::Latest. diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 47ed8f828..bb87b8ab2 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -116,6 +116,15 @@ impl Tendermint { } } + fn submit_seal(&self, block_hash: H256, seal: Vec) { + if let Some(ref channel) = *self.message_channel.lock() { + match channel.send(ClientIoMessage::SubmitSeal(block_hash, seal)) { + Ok(_) => trace!(target: "poa", "timeout: SubmitSeal message sent."), + Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), + } + } + } + fn nonce_proposer(&self, proposer_nonce: usize) -> &Address { let ref p = self.our_params; p.authorities.get(proposer_nonce % p.authority_n).unwrap() diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 84e29458d..f93203b00 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -1008,7 +1008,7 @@ impl MinerService for Miner { ret.map(f) } - fn submit_seal(&self, chain: &MiningBlockChainClient, pow_hash: H256, seal: Vec) -> Result<(), Error> { + fn submit_seal(&self, chain: &MiningBlockChainClient, block_hash: H256, seal: Vec) -> Result<(), Error> { let result = if let Some(b) = self.sealing_work.lock().queue.get_used_if( if self.options.enable_resubmission { @@ -1016,9 +1016,9 @@ impl MinerService for Miner { } else { GetAction::Take }, - |b| &b.hash() == &pow_hash + |b| &b.hash() == &block_hash ) { - trace!(target: "miner", "Sealing block {}={}={} with seal {:?}", pow_hash, b.hash(), b.header().bare_hash(), seal); + trace!(target: "miner", "Sealing block {}={}={} with seal {:?}", block_hash, b.hash(), b.header().bare_hash(), seal); b.lock().try_seal(&*self.engine, seal).or_else(|(e, _)| { warn!(target: "miner", "Mined solution rejected: {}", e); Err(Error::PowInvalid) diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index 36b5e7157..b19de72e9 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -50,6 +50,8 @@ pub enum ClientIoMessage { TakeSnapshot(u64), /// Trigger sealing update (useful for internal sealing). UpdateSealing, + /// Submit seal (useful for internal sealing). + SubmitSeal(H256, Vec), } /// Client service setup. Creates and registers client and network services with the IO subsystem. @@ -219,9 +221,13 @@ impl IoHandler for ClientIoHandler { } }, ClientIoMessage::UpdateSealing => { - trace!(target: "authorityround", "message: UpdateSealing"); + trace!(target: "poa", "message: UpdateSealing"); self.client.update_sealing() }, + ClientIoMessage::SubmitSeal(ref hash, ref seal) => { + trace!(target: "poa", "message: SubmitSeal"); + self.client.submit_seal(*hash, seal.clone()) + }, _ => {} // ignore other messages } } From 802d5c669d0c2b9e84ba378c9c8b9d1d6d9758da Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 16 Nov 2016 18:01:09 +0000 Subject: [PATCH 052/295] transition rules --- ethcore/src/engines/tendermint/message.rs | 12 ++-- ethcore/src/engines/tendermint/mod.rs | 67 ++++++++++++++----- ethcore/src/engines/tendermint/timeout.rs | 35 ++++------ .../src/engines/tendermint/vote_collector.rs | 2 +- 4 files changed, 72 insertions(+), 44 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 0a7bcb1df..5e9cbb1a3 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -20,7 +20,7 @@ use util::*; use super::{Height, Round, BlockHash, Step}; use rlp::{View, DecoderError, Decodable, Decoder, Encodable, RlpStream, Stream}; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct ConsensusMessage { pub signature: H520, pub height: Height, @@ -30,12 +30,16 @@ pub struct ConsensusMessage { } impl ConsensusMessage { + pub fn is_height(&self, height: Height) -> bool { + self.height == height + } + pub fn is_round(&self, height: Height, round: Round) -> bool { self.height == height && self.round == round } - pub fn is_step(&self, height: Height, round: Round, step: &Step) -> bool { - self.height == height && self.round == round && &self.step == step + pub fn is_step(&self, height: Height, round: Round, step: Step) -> bool { + self.height == height && self.round == round && self.step == step } pub fn is_aligned(&self, height: Height, round: Round, block_hash: Option) -> bool { @@ -79,7 +83,7 @@ impl Decodable for Step { match try!(decoder.as_rlp().as_val()) { 0u8 => Ok(Step::Prevote), 1 => Ok(Step::Precommit), - _ => Err(DecoderError::Custom("Unknown step.")), + _ => Err(DecoderError::Custom("Invalid step.")), } } } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index bb87b8ab2..a42615693 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -41,11 +41,11 @@ use evm::Schedule; use io::{IoService, IoChannel}; use service::ClientIoMessage; use self::message::ConsensusMessage; -use self::timeout::{TransitionHandler, NextStep}; +use self::timeout::TransitionHandler; use self::params::TendermintParams; use self::vote_collector::VoteCollector; -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Step { Propose, Prevote, @@ -65,7 +65,7 @@ pub struct Tendermint { params: CommonParams, our_params: TendermintParams, builtins: BTreeMap, - step_service: IoService, + step_service: IoService, /// Address to be used as authority. authority: RwLock
, /// Blockchain height. @@ -92,7 +92,7 @@ impl Tendermint { params: params, our_params: our_params, builtins: builtins, - step_service: try!(IoService::::start()), + step_service: try!(IoService::::start()), authority: RwLock::new(Address::default()), height: AtomicUsize::new(0), round: AtomicUsize::new(0), @@ -125,6 +125,14 @@ impl Tendermint { } } + fn to_step(&self, step: Step) { + *self.step.write() = step; + match step { + Step::Propose => self.update_sealing(), + _ => {}, + } + } + fn nonce_proposer(&self, proposer_nonce: usize) -> &Address { let ref p = self.our_params; p.authorities.get(proposer_nonce % p.authority_n).unwrap() @@ -148,11 +156,19 @@ impl Tendermint { } fn is_current(&self, message: &ConsensusMessage) -> bool { - message.is_step(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), &self.step.read()) + message.is_height(self.height.load(AtomicOrdering::SeqCst)) } fn has_enough_any_votes(&self) -> bool { - self.votes.count_step_votes(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), &self.step.read()) > self.threshold() + self.votes.count_step_votes(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read()) > self.threshold() + } + + fn has_enough_step_votes(&self, message: &ConsensusMessage) -> bool { + self.votes.count_step_votes(message.height, message.round, message.step) > self.threshold() + } + + fn has_enough_aligned_votes(&self, message: &ConsensusMessage) -> bool { + self.votes.aligned_signatures(&message).len() > self.threshold() } } @@ -272,19 +288,38 @@ impl Engine for Tendermint { try!(Err(BlockError::InvalidSeal)); } - // Check if the message affects the current step. + self.votes.vote(message.clone(), sender); + + // Check if the message should be handled right now. if self.is_current(&message) { - match *self.step.read() { - Step::Prevote => { - let votes = self.votes.aligned_signatures(&message); - if votes.len() > self.threshold() { - } - }, - Step::Precommit => {}, - _ => {}, + let next_step = match *self.step.read() { + Step::Precommit if self.has_enough_aligned_votes(&message) => { + if message.block_hash.is_none() { + self.round.fetch_add(1, AtomicOrdering::SeqCst); + Some(Step::Propose) + } else { + Some(Step::Commit) + } + }, + Step::Precommit if self.has_enough_step_votes(&message) => { + self.round.store(message.round, AtomicOrdering::SeqCst); + Some(Step::Precommit) + }, + Step::Prevote if self.has_enough_aligned_votes(&message) => Some(Step::Precommit), + Step::Prevote if self.has_enough_step_votes(&message) => { + self.round.store(message.round, AtomicOrdering::SeqCst); + Some(Step::Prevote) + }, + _ => None, + }; + + if let Some(step) = next_step { + if let Err(io_err) = self.step_service.send_message(step) { + warn!(target: "poa", "Could not proceed to next step {}.", io_err) } + } } - self.votes.vote(message, sender); + Err(BlockError::InvalidSeal.into()) } diff --git a/ethcore/src/engines/tendermint/timeout.rs b/ethcore/src/engines/tendermint/timeout.rs index 789755077..329145984 100644 --- a/ethcore/src/engines/tendermint/timeout.rs +++ b/ethcore/src/engines/tendermint/timeout.rs @@ -57,25 +57,22 @@ impl Default for TendermintTimeouts { } } -#[derive(Clone)] -pub struct NextStep(Step); - /// Timer token representing the consensus step timeouts. pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 23; -fn set_timeout(io: &IoContext, timeout: Duration) { +fn set_timeout(io: &IoContext, timeout: Duration) { io.register_timer_once(ENGINE_TIMEOUT_TOKEN, timeout.num_milliseconds() as u64) .unwrap_or_else(|e| warn!(target: "poa", "Failed to set consensus step timeout: {}.", e)) } -impl IoHandler for TransitionHandler { - fn initialize(&self, io: &IoContext) { +impl IoHandler for TransitionHandler { + fn initialize(&self, io: &IoContext) { if let Some(engine) = self.engine.upgrade() { set_timeout(io, engine.our_params.timeouts.propose) } } - fn timeout(&self, io: &IoContext, timer: TimerToken) { + fn timeout(&self, io: &IoContext, timer: TimerToken) { if timer == ENGINE_TIMEOUT_TOKEN { if let Some(engine) = self.engine.upgrade() { let next_step = match *engine.step.read() { @@ -102,32 +99,24 @@ impl IoHandler for TransitionHandler { }; if let Some(step) = next_step { - *engine.step.write() = step.clone(); - if step == Step::Propose { - engine.update_sealing(); - } + engine.to_step(step) } } } } - fn message(&self, io: &IoContext, message: &NextStep) { + fn message(&self, io: &IoContext, next_step: &Step) { if let Some(engine) = self.engine.upgrade() { - match io.clear_timer(ENGINE_TIMEOUT_TOKEN) { - Ok(_) => {}, - Err(io_err) => warn!(target: "poa", "Could not remove consensus timer {}.", io_err), - }; - let NextStep(next_step) = message.clone(); - *engine.step.write() = next_step.clone(); - match next_step { - Step::Propose => { - engine.update_sealing(); - set_timeout(io, engine.our_params.timeouts.propose) - }, + if let Err(io_err) = io.clear_timer(ENGINE_TIMEOUT_TOKEN) { + warn!(target: "poa", "Could not remove consensus timer {}.", io_err) + } + match *next_step { + Step::Propose => set_timeout(io, engine.our_params.timeouts.propose), Step::Prevote => set_timeout(io, engine.our_params.timeouts.prevote), Step::Precommit => set_timeout(io, engine.our_params.timeouts.precommit), Step::Commit => set_timeout(io, engine.our_params.timeouts.commit), }; + engine.to_step(*next_step); } } } diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index c5db224b0..29906f999 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -50,7 +50,7 @@ impl VoteCollector { self.seal_signatures(message.height, message.round, message.block_hash) } - pub fn count_step_votes(&self, height: Height, round: Round, step: &Step) -> usize { + pub fn count_step_votes(&self, height: Height, round: Round, step: Step) -> usize { self.votes .read() .keys() From 45027ea3060048de67bfdc8cfe49d88638e69274 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 17 Nov 2016 12:17:48 +0000 Subject: [PATCH 053/295] add new client messaging --- ethcore/src/client/client.rs | 9 ++++++--- ethcore/src/service.rs | 10 ++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index e7aa716bd..6eda89a7c 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -576,6 +576,11 @@ impl Client { } } + /// Used by PoA to communicate with peers. + pub fn broadcast_message(&self, message: Bytes) { + self.notify(|notify| notify.broadcast(message.clone())); + } + /// Attempt to get a copy of a specific block's final state. /// /// This will not fail if given BlockID::Latest. @@ -1231,9 +1236,7 @@ impl BlockChainClient for Client { // TODO: Make it an actual queue, return errors. fn queue_infinity_message(&self, message: Bytes) { - if let Ok(new_message) = self.engine.handle_message(UntrustedRlp::new(&message)) { - self.notify(|notify| notify.broadcast(new_message.clone())); - } + self.engine.handle_message(UntrustedRlp::new(&message)); } fn signing_network_id(&self) -> Option { diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index b19de72e9..1962bec5f 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -52,6 +52,8 @@ pub enum ClientIoMessage { UpdateSealing, /// Submit seal (useful for internal sealing). SubmitSeal(H256, Vec), + /// Broadcast a message to the network. + BroadcastMessage(Bytes) } /// Client service setup. Creates and registers client and network services with the IO subsystem. @@ -222,11 +224,15 @@ impl IoHandler for ClientIoHandler { }, ClientIoMessage::UpdateSealing => { trace!(target: "poa", "message: UpdateSealing"); - self.client.update_sealing() + self.client.update_sealing(); }, ClientIoMessage::SubmitSeal(ref hash, ref seal) => { trace!(target: "poa", "message: SubmitSeal"); - self.client.submit_seal(*hash, seal.clone()) + self.client.submit_seal(*hash, seal.clone()); + }, + ClientIoMessage::BroadcastMessage(ref message) => { + trace!(target: "poa", "message: BroadcastMessage"); + self.client.broadcast_message(message.clone()); }, _ => {} // ignore other messages } From 9563ccfbd299ce6eded385c31f94e96bf96cdfce Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 17 Nov 2016 12:18:20 +0000 Subject: [PATCH 054/295] message broadcasting methods --- ethcore/src/engines/mod.rs | 2 +- ethcore/src/engines/tendermint/mod.rs | 83 ++++++++++--------- .../src/engines/tendermint/vote_collector.rs | 7 +- 3 files changed, 48 insertions(+), 44 deletions(-) diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 64e5cb16d..283014a50 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -159,7 +159,7 @@ pub trait Engine : Sync + Send { /// Handle any potential consensus messages; /// updating consensus state and potentially issuing a new one. - fn handle_message(&self, _message: UntrustedRlp) -> Result { Err(EngineError::UnexpectedMessage.into()) } + fn handle_message(&self, _message: UntrustedRlp) -> Result<(), Error> { Err(EngineError::UnexpectedMessage.into()) } // TODO: builtin contract routing - to do this properly, it will require removing the built-in configuration-reading logic // from Spec into here and removing the Spec::builtins field. diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index a42615693..78e08db1b 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -125,11 +125,37 @@ impl Tendermint { } } + fn broadcast_message(&self, message: Bytes) { + if let Some(ref channel) = *self.message_channel.lock() { + match channel.send(ClientIoMessage::BroadcastMessage(message)) { + Ok(_) => trace!(target: "poa", "timeout: BroadcastMessage message sent."), + Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), + } + } + } + fn to_step(&self, step: Step) { *self.step.write() = step; match step { Step::Propose => self.update_sealing(), - _ => {}, + Step::Prevote => { + self.broadcast_message() + }, + Step::Precommit => { + self.broadcast_message() + }, + Step::Commit => { + // Commit the block using a complete signature set. + let round = self.round.load(AtomicOrdering::SeqCst); + if let Some((proposer, votes)) = self.votes.seal_signatures(header.number() as Height, round, Some(block_hash(header))) { + let seal = vec![ + ::rlp::encode(&round).to_vec(), + ::rlp::encode(proposer).to_vec(), + ::rlp::encode(&votes).to_vec() + ]; + self.submit_seal(seal) + } + }, } } @@ -240,47 +266,27 @@ impl Engine for Tendermint { } /// Attempt to seal the block internally using all available signatures. - /// - /// None is returned if not enough signatures can be collected. fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { - if let (Some(ap), Some(step)) = (accounts, self.step.try_read()) { + if let Some(ap) = accounts { let header = block.header(); let author = header.author(); - match *step { - Step::Commit => { - // Commit the block using a complete signature set. - let round = self.round.load(AtomicOrdering::SeqCst); - if let Some((proposer, votes)) = self.votes.seal_signatures(header.number() as Height, round, Some(block_hash(header))).split_first() { - if votes.len() + 1 > self.threshold() { - return Some(vec![ - ::rlp::encode(&round).to_vec(), - ::rlp::encode(proposer).to_vec(), - ::rlp::encode(&votes).to_vec() - ]); - } - } - }, - Step::Propose if self.is_proposer(author) => - // Seal block with propose signature. - - if let Ok(signature) = ap.sign(*author, None, block_hash(header)) { - return Some(vec![ - ::rlp::encode(&self.round.load(AtomicOrdering::SeqCst)).to_vec(), - ::rlp::encode(&H520::from(signature)).to_vec(), - Vec::new() - ]) - } else { - trace!(target: "poa", "generate_seal: FAIL: accounts secret key unavailable"); - }, - _ => {}, + if let Ok(signature) = ap.sign(*author, None, block_hash(header)) { + Some(vec![ + ::rlp::encode(&self.round.load(AtomicOrdering::SeqCst)).to_vec(), + ::rlp::encode(&H520::from(signature)).to_vec(), + Vec::new() + ]) + } else { + warn!(target: "poa", "generate_seal: FAIL: accounts secret key unavailable"); + None } } else { - trace!(target: "poa", "generate_seal: FAIL: accounts not provided"); + warn!(target: "poa", "generate_seal: FAIL: accounts not provided"); + None } - None } - fn handle_message(&self, rlp: UntrustedRlp) -> Result { + fn handle_message(&self, rlp: UntrustedRlp) -> Result<(), Error> { let message: ConsensusMessage = try!(rlp.as_val()); let sender = public_to_address(&try!(recover(&message.signature.into(), &try!(rlp.at(1)).as_raw().sha3()))); // TODO: Do not admit old messages. @@ -288,10 +294,8 @@ impl Engine for Tendermint { try!(Err(BlockError::InvalidSeal)); } - self.votes.vote(message.clone(), sender); - - // Check if the message should be handled right now. - if self.is_current(&message) { + // Check if the message is known and should be handled right now. + if self.votes.vote(message.clone(), sender).is_none() && self.is_current(&message) { let next_step = match *self.step.read() { Step::Precommit if self.has_enough_aligned_votes(&message) => { if message.block_hash.is_none() { @@ -319,8 +323,7 @@ impl Engine for Tendermint { } } } - - Err(BlockError::InvalidSeal.into()) + Ok(()) } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 29906f999..075fda641 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -32,11 +32,11 @@ impl VoteCollector { VoteCollector { votes: RwLock::new(BTreeMap::new()) } } - pub fn vote(&self, message: ConsensusMessage, voter: Address) { - self.votes.write().insert(message, voter); + pub fn vote(&self, message: ConsensusMessage, voter: Address) -> Option
{ + self.votes.write().insert(message, voter) } - pub fn seal_signatures(&self, height: Height, round: Round, block_hash: Option) -> Vec { + pub fn seal_signatures(&self, height: Height, round: Round, block_hash: Option) -> (H520, Vec) { self.votes .read() .keys() @@ -44,6 +44,7 @@ impl VoteCollector { .filter(|m| m.is_aligned(height, round, block_hash) && m.step != Step::Prevote) .map(|m| m.signature) .collect() + .split_first() } pub fn aligned_signatures(&self, message: &ConsensusMessage) -> Vec { From 51ac38318a8ee9f0979a063e54ecd415d30852dc Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 17 Nov 2016 13:26:57 +0000 Subject: [PATCH 055/295] save proposal hash --- ethcore/src/engines/tendermint/mod.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 78e08db1b..28b67fa8c 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -81,7 +81,11 @@ pub struct Tendermint { /// Proposed block held until seal is gathered. proposed_block: Mutex>, /// Channel for updating the sealing. - message_channel: Mutex>> + message_channel: Mutex>>, + /// Last round when PoLC was seen. + last_lock_round: RwLock, + /// Proposed block. + proposal: RwLock> } impl Tendermint { @@ -100,7 +104,9 @@ impl Tendermint { proposer_nonce: AtomicUsize::new(0), votes: VoteCollector::new(), proposed_block: Mutex::new(None), - message_channel: Mutex::new(None) + message_channel: Mutex::new(None), + last_lock_round: AtomicUsize::new(0), + proposal: RwLock::new(None) }); let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; try!(engine.step_service.register_handler(Arc::new(handler))); @@ -137,7 +143,10 @@ impl Tendermint { fn to_step(&self, step: Step) { *self.step.write() = step; match step { - Step::Propose => self.update_sealing(), + Step::Propose => { + self.proposal.write() = None; + self.update_sealing() + }, Step::Prevote => { self.broadcast_message() }, @@ -153,7 +162,7 @@ impl Tendermint { ::rlp::encode(proposer).to_vec(), ::rlp::encode(&votes).to_vec() ]; - self.submit_seal(seal) + self.submit_seal(self.proposal.read(), seal) } }, } @@ -198,7 +207,7 @@ impl Tendermint { } } -/// Block hash including the consensus round. +/// Block hash including the consensus round, gets signed and included in the seal. fn block_hash(header: &Header) -> H256 { header.rlp(Seal::WithSome(1)).sha3() } @@ -271,6 +280,7 @@ impl Engine for Tendermint { let header = block.header(); let author = header.author(); if let Ok(signature) = ap.sign(*author, None, block_hash(header)) { + self.proposal.write() = Some(block.hash()); Some(vec![ ::rlp::encode(&self.round.load(AtomicOrdering::SeqCst)).to_vec(), ::rlp::encode(&H520::from(signature)).to_vec(), From ce711e321ad97b9cf1ed2b36fe8b315575e0822a Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 17 Nov 2016 13:28:29 +0000 Subject: [PATCH 056/295] remove unused vote accumulators --- ethcore/src/engines/mod.rs | 5 - ethcore/src/engines/propose_collect.rs | 109 --------------------- ethcore/src/engines/signed_vote.rs | 125 ------------------------- 3 files changed, 239 deletions(-) delete mode 100644 ethcore/src/engines/propose_collect.rs delete mode 100644 ethcore/src/engines/signed_vote.rs diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 283014a50..2f43792e5 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -21,17 +21,12 @@ mod instant_seal; mod basic_authority; mod authority_round; mod tendermint; -mod signed_vote; -mod propose_collect; - pub use self::null_engine::NullEngine; pub use self::instant_seal::InstantSeal; pub use self::basic_authority::BasicAuthority; pub use self::authority_round::AuthorityRound; pub use self::tendermint::Tendermint; -pub use self::signed_vote::SignedVote; -pub use self::propose_collect::ProposeCollect; use rlp::UntrustedRlp; use util::*; diff --git a/ethcore/src/engines/propose_collect.rs b/ethcore/src/engines/propose_collect.rs deleted file mode 100644 index c60457c3a..000000000 --- a/ethcore/src/engines/propose_collect.rs +++ /dev/null @@ -1,109 +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 . - -//! Voting on a hash, where each vote has to come from a set of addresses. - -use std::sync::atomic::{AtomicBool, Ordering}; -use util::{HashSet, RwLock, H256, Address}; - -/// Collect votes on a hash. -#[derive(Debug)] -pub struct ProposeCollect { - /// Proposed hash. - pub hash: H256, - /// Allowed voter addresses. - pub voters: HashSet
, - /// Threshold vote number for success. - pub threshold: usize, - /// Votes. - votes: RwLock>, - /// Was enough votes reached. - is_won: AtomicBool -} - -impl ProposeCollect { - /// Create a new instance of BFT engine - pub fn new(hash: H256, voters: HashSet
, threshold: usize) -> Self { - assert!(voters.len() > threshold); - ProposeCollect { - hash: hash, - voters: voters, - threshold: threshold, - votes: RwLock::new(HashSet::new()), - is_won: AtomicBool::new(false) - } - } - - /// Vote on hash using the signed hash, true if vote counted. - pub fn vote(&self, voter: Address) -> bool { - let is_known = self.votes.try_read().unwrap().contains(&voter); - if !is_known && self.voters.contains(&voter) { - self.votes.try_write().unwrap().insert(voter); - true - } else { - false - } - } - - /// Some winner if voting threshold was reached. - pub fn is_won(&self) -> bool { - let threshold_checker = || match self.votes.try_read().unwrap().len() >= self.threshold { - true => { self.is_won.store(true, Ordering::Relaxed); true }, - false => false, - }; - self.is_won.load(Ordering::Relaxed) || threshold_checker() - } -} - -#[cfg(test)] -mod tests { - use engines::propose_collect::ProposeCollect; - use account_provider::AccountProvider; - use util::*; - use header::Header; - - #[test] - fn simple_propose_collect() { - let tap = AccountProvider::transient_provider(); - let addr1 = tap.insert_account("1".sha3(), "1").unwrap(); - tap.unlock_account_permanently(addr1, "1".into()).unwrap(); - - let addr2 = tap.insert_account("2".sha3(), "2").unwrap(); - tap.unlock_account_permanently(addr2, "2".into()).unwrap(); - - let addr3 = tap.insert_account("3".sha3(), "3").unwrap(); - tap.unlock_account_permanently(addr3, "3".into()).unwrap(); - - let header = Header::default(); - let bare_hash = header.bare_hash(); - let voters: HashSet<_> = vec![addr1.clone(), addr2.clone(), Address::default()].into_iter().map(Into::into).collect(); - let vote = ProposeCollect::new(bare_hash, voters.into(), 2); - assert!(!vote.is_won()); - - // Unapproved voter. - assert!(!vote.vote(addr3)); - assert!(!vote.is_won()); - // First good vote. - assert!(vote.vote(addr1.clone())); - assert!(!vote.is_won()); - // Voting again is ineffective. - assert!(!vote.vote(addr1)); - assert!(!vote.is_won()); - // Second valid vote thus win. - assert!(vote.vote(addr2)); - assert!(vote.is_won()); - } -} diff --git a/ethcore/src/engines/signed_vote.rs b/ethcore/src/engines/signed_vote.rs deleted file mode 100644 index 0cef5c214..000000000 --- a/ethcore/src/engines/signed_vote.rs +++ /dev/null @@ -1,125 +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 . - -//! Voting on hashes, where each vote has to come from a set of public keys. - -use super::EngineError; -use util::*; -use error::Error; -use ethkey::{Signature, recover}; - -/// Signed voting on hashes. -#[derive(Debug)] -pub struct SignedVote { - /// Voter public keys. - pub voters: HashSet
, - /// Number of voters. - pub voter_n: usize, - /// Threshold vote number for success. - pub threshold: usize, - /// Votes. - votes: RwLock>>, - /// Winner hash, set after enough votes are reached. - winner: RwLock> -} - -impl SignedVote { - /// Create a new instance of BFT engine - pub fn new(voters: HashSet
, threshold: usize) -> Self { - let voters_n = voters.len(); - assert!(voters_n > threshold); - SignedVote { - voter_n: voters_n, - voters: voters, - threshold: threshold, - votes: RwLock::new(HashMap::new()), - winner: RwLock::new(None) - } - } - - /// Vote on hash using the signed hash, true if vote counted. - pub fn vote(&self, bare_hash: H256, signature: Signature) -> bool { - if !self.can_vote(&bare_hash, &signature).is_ok() { return false; } - let mut guard = self.votes.try_write().unwrap(); - let set = guard.entry(bare_hash.clone()).or_insert_with(|| HashSet::new()); - if !set.insert(signature) { return false; } - // Set the winner if threshold is reached. - if set.len() >= self.threshold { - let mut guard = self.winner.try_write().unwrap(); - *guard = Some(bare_hash); - } - true - } - - fn can_vote(&self, bare_hash: &H256, signature: &Signature) -> Result<(), Error> { - let signer = Address::from(try!(recover(&signature, bare_hash)).sha3()); - match self.voters.contains(&signer) { - false => try!(Err(EngineError::UnauthorisedVoter)), - true => Ok(()), - } - } - - /// Some winner if voting threshold was reached. - pub fn winner(&self) -> Option { self.winner.try_read().unwrap().clone() } - - /// Get signatures backing given hash. - pub fn votes(&self, bare_hash: &H256) -> Option> { - self.votes.try_read().unwrap().get(bare_hash).cloned() - } -} - -#[cfg(test)] -mod tests { - use util::*; - use header::Header; - use engines::signed_vote::SignedVote; - use account_provider::AccountProvider; - - #[test] - fn simple_vote() { - let tap = AccountProvider::transient_provider(); - let addr1 = tap.insert_account("1".sha3(), "1").unwrap(); - tap.unlock_account_permanently(addr1, "1".into()).unwrap(); - - let addr2 = tap.insert_account("2".sha3(), "2").unwrap(); - tap.unlock_account_permanently(addr2, "2".into()).unwrap(); - - let addr3 = tap.insert_account("3".sha3(), "3").unwrap(); - tap.unlock_account_permanently(addr3, "3".into()).unwrap(); - - let voters: HashSet<_> = vec![addr1, addr2].into_iter().map(Into::into).collect(); - let vote = SignedVote::new(voters.into(), 1); - assert!(vote.winner().is_none()); - let header = Header::default(); - let bare_hash = header.bare_hash(); - - // Unapproved voter. - let signature = tap.sign(addr3, None, bare_hash).unwrap(); - assert!(!vote.vote(bare_hash, signature)); - assert!(vote.winner().is_none()); - // First good vote. - let signature = tap.sign(addr1, None, bare_hash).unwrap(); - assert!(vote.vote(bare_hash, signature)); - assert_eq!(vote.winner().unwrap(), bare_hash); - // Voting again is ineffective. - let signature = tap.sign(addr1, None, bare_hash).unwrap(); - assert!(!vote.vote(bare_hash, signature)); - // Second valid vote. - let signature = tap.sign(addr2, None, bare_hash).unwrap(); - assert!(vote.vote(bare_hash, signature)); - assert_eq!(vote.winner().unwrap(), bare_hash); - } -} From 3bac68419ae43d70403f10d6619d9ce876f9ba95 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 17 Nov 2016 17:12:37 +0000 Subject: [PATCH 057/295] last_lock --- ethcore/src/engines/tendermint/message.rs | 2 +- ethcore/src/engines/tendermint/mod.rs | 96 +++++++++++-------- .../src/engines/tendermint/vote_collector.rs | 22 +++-- 3 files changed, 75 insertions(+), 45 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 5e9cbb1a3..f957c7785 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -114,7 +114,7 @@ impl Decodable for ConsensusMessage { false => Some(block_message), } }) - } + } } impl Encodable for ConsensusMessage { diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 28b67fa8c..494e83873 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -34,7 +34,7 @@ use ethkey::{recover, public_to_address}; use account_provider::AccountProvider; use block::*; use spec::CommonParams; -use engines::{Engine, EngineError, ProposeCollect}; +use engines::{Engine, EngineError}; use blockchain::extras::BlockDetails; use views::HeaderView; use evm::Schedule; @@ -82,9 +82,9 @@ pub struct Tendermint { proposed_block: Mutex>, /// Channel for updating the sealing. message_channel: Mutex>>, - /// Last round when PoLC was seen. - last_lock_round: RwLock, - /// Proposed block. + /// Message for the last PoLC. + last_lock: RwLock>, + /// Bare hash of the proposed block, used for seal submission. proposal: RwLock> } @@ -105,7 +105,7 @@ impl Tendermint { votes: VoteCollector::new(), proposed_block: Mutex::new(None), message_channel: Mutex::new(None), - last_lock_round: AtomicUsize::new(0), + last_lock: RwLock::new(None), proposal: RwLock::new(None) }); let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; @@ -140,18 +140,28 @@ impl Tendermint { } } + fn generate_message(&self, block_hash: Option) -> ConsensusMessage { + Ok(signature) = ap.sign(*author, None, block_hash(header)) + ConsensusMessage { signatue + + } + fn to_step(&self, step: Step) { *self.step.write() = step; match step { Step::Propose => { - self.proposal.write() = None; + *self.proposal.write() = None; self.update_sealing() }, Step::Prevote => { self.broadcast_message() }, Step::Precommit => { - self.broadcast_message() + let message = match self.last_lock.read() { + Some(m) => + None => ConsensusMessage { signature: signature, height + } + self.broadcast_message(::rlp::encode(message)) }, Step::Commit => { // Commit the block using a complete signature set. @@ -162,8 +172,11 @@ impl Tendermint { ::rlp::encode(proposer).to_vec(), ::rlp::encode(&votes).to_vec() ]; - self.submit_seal(self.proposal.read(), seal) + if let Some(block_hash) = *self.proposal.read() { + self.submit_seal(block_hash, seal); + } } + *self.last_lock.write() = None; }, } } @@ -203,7 +216,7 @@ impl Tendermint { } fn has_enough_aligned_votes(&self, message: &ConsensusMessage) -> bool { - self.votes.aligned_signatures(&message).len() > self.threshold() + self.votes.aligned_votes(&message).len() > self.threshold() } } @@ -258,9 +271,7 @@ impl Engine for Tendermint { /// Get the address to be used as authority. fn on_new_block(&self, block: &mut ExecutedBlock) { - if let Some(mut authority) = self.authority.try_write() { - *authority = *block.header().author() - } + *self.authority.write() = *block.header().author() } /// Set the correct round in the seal. @@ -280,7 +291,7 @@ impl Engine for Tendermint { let header = block.header(); let author = header.author(); if let Ok(signature) = ap.sign(*author, None, block_hash(header)) { - self.proposal.write() = Some(block.hash()); + *self.proposal.write() = Some(header.bare_hash()); Some(vec![ ::rlp::encode(&self.round.load(AtomicOrdering::SeqCst)).to_vec(), ::rlp::encode(&H520::from(signature)).to_vec(), @@ -304,32 +315,41 @@ impl Engine for Tendermint { try!(Err(BlockError::InvalidSeal)); } - // Check if the message is known and should be handled right now. - if self.votes.vote(message.clone(), sender).is_none() && self.is_current(&message) { - let next_step = match *self.step.read() { - Step::Precommit if self.has_enough_aligned_votes(&message) => { - if message.block_hash.is_none() { - self.round.fetch_add(1, AtomicOrdering::SeqCst); - Some(Step::Propose) - } else { - Some(Step::Commit) - } - }, - Step::Precommit if self.has_enough_step_votes(&message) => { - self.round.store(message.round, AtomicOrdering::SeqCst); - Some(Step::Precommit) - }, - Step::Prevote if self.has_enough_aligned_votes(&message) => Some(Step::Precommit), - Step::Prevote if self.has_enough_step_votes(&message) => { - self.round.store(message.round, AtomicOrdering::SeqCst); - Some(Step::Prevote) - }, - _ => None, - }; + // Check if the message is known. + if self.votes.vote(message.clone(), sender).is_none() { + let is_newer_than_lock = self.last_lock.read().map_or(true, |lock| message > lock); + if is_newer_than_lock + && message.step == Step::Prevote + && self.has_enough_aligned_votes(&message) { + *self.last_lock.write() = Some(message); + } + // Check if it can affect step transition. + if self.is_current(&message) { + let next_step = match *self.step.read() { + Step::Precommit if self.has_enough_aligned_votes(&message) => { + if message.block_hash.is_none() { + self.round.fetch_add(1, AtomicOrdering::SeqCst); + Some(Step::Propose) + } else { + Some(Step::Commit) + } + }, + Step::Precommit if self.has_enough_step_votes(&message) => { + self.round.store(message.round, AtomicOrdering::SeqCst); + Some(Step::Precommit) + }, + Step::Prevote if self.has_enough_aligned_votes(&message) => Some(Step::Precommit), + Step::Prevote if self.has_enough_step_votes(&message) => { + self.round.store(message.round, AtomicOrdering::SeqCst); + Some(Step::Prevote) + }, + _ => None, + }; - if let Some(step) = next_step { - if let Err(io_err) = self.step_service.send_message(step) { - warn!(target: "poa", "Could not proceed to next step {}.", io_err) + if let Some(step) = next_step { + if let Err(io_err) = self.step_service.send_message(step) { + warn!(target: "poa", "Could not proceed to next step {}.", io_err) + } } } } diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 075fda641..e7ea0a5d5 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -36,19 +36,29 @@ impl VoteCollector { self.votes.write().insert(message, voter) } - pub fn seal_signatures(&self, height: Height, round: Round, block_hash: Option) -> (H520, Vec) { + pub fn seal_signatures(&self, height: Height, round: Round, block_hash: Option) -> Option<(&H520, &[H520])> { + self.votes + .read() + .keys() + .cloned() + // Get only Propose and Precommits. + .filter(|m| m.is_aligned(height, round, block_hash) && m.step != Step::Prevote) + .map(|m| m.signature) + .collect::>() + .split_first() + } + + pub fn aligned_votes(&self, message: &ConsensusMessage) -> Vec<&ConsensusMessage> { self.votes .read() .keys() // Get only Propose and Precommits. - .filter(|m| m.is_aligned(height, round, block_hash) && m.step != Step::Prevote) - .map(|m| m.signature) + .filter(|m| m.is_aligned(message.height, message.round, message.block_hash) && m.step == message.step) .collect() - .split_first() } - pub fn aligned_signatures(&self, message: &ConsensusMessage) -> Vec { - self.seal_signatures(message.height, message.round, message.block_hash) + pub fn aligned_signatures(&self, message: &ConsensusMessage) -> &[H520] { + self.seal_signatures(message.height, message.round, message.block_hash).map_or(&[], |s| s.1) } pub fn count_step_votes(&self, height: Height, round: Round, step: Step) -> usize { From 11ccacd6d0a9989d07b941a77f7a4e9f5a6f7bb3 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 17 Nov 2016 18:32:12 +0000 Subject: [PATCH 058/295] dont keep account provider in miner --- ethcore/src/engines/authority_round.rs | 14 ++++++++++---- ethcore/src/engines/basic_authority.rs | 10 ++++++++-- ethcore/src/engines/instant_seal.rs | 3 +-- ethcore/src/engines/mod.rs | 5 +++-- ethcore/src/miner/miner.rs | 24 ++++++------------------ parity/run.rs | 8 ++++---- 6 files changed, 32 insertions(+), 32 deletions(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 9bed99e8b..2d7a5d69b 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -68,6 +68,7 @@ pub struct AuthorityRound { builtins: BTreeMap, transition_service: IoService, message_channel: Mutex>>, + account_provider: Mutex>>, step: AtomicUsize, proposed: AtomicBool, } @@ -101,6 +102,7 @@ impl AuthorityRound { builtins: builtins, transition_service: try!(IoService::::start()), message_channel: Mutex::new(None), + account_provider: Mutex::new(None), step: AtomicUsize::new(initial_step), proposed: AtomicBool::new(false) }); @@ -219,12 +221,12 @@ 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> { + fn generate_seal(&self, block: &ExecutedBlock) -> Option> { if self.proposed.load(AtomicOrdering::SeqCst) { return None; } let header = block.header(); let step = self.step(); if self.is_step_proposer(step, header.author()) { - if let Some(ap) = accounts { + if let Some(ref ap) = *self.account_provider.lock() { // Account should be permanently unlocked, otherwise sealing will fail. if let Ok(signature) = ap.sign(*header.author(), None, header.bare_hash()) { trace!(target: "poa", "generate_seal: Issuing a block for step {}.", step); @@ -307,8 +309,12 @@ impl Engine for AuthorityRound { } fn register_message_channel(&self, message_channel: IoChannel) { - let mut guard = self.message_channel.lock(); - *guard = Some(message_channel); + *self.message_channel.lock() = Some(message_channel); + } + + + fn register_account_provider(&self, account_provider: Arc) { + *self.account_provider.lock() = Some(account_provider); } } diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 23a97967c..82a590b38 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -58,6 +58,7 @@ pub struct BasicAuthority { params: CommonParams, our_params: BasicAuthorityParams, builtins: BTreeMap, + account_provider: Mutex>> } impl BasicAuthority { @@ -67,6 +68,7 @@ impl BasicAuthority { params: params, our_params: our_params, builtins: builtins, + account_provider: Mutex::new(None) } } } @@ -113,8 +115,8 @@ impl Engine for BasicAuthority { /// /// 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 let Some(ap) = accounts { + fn generate_seal(&self, block: &ExecutedBlock) -> Option> { + if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let message = header.bare_hash(); // account should be pernamently unlocked, otherwise sealing will fail @@ -179,6 +181,10 @@ impl Engine for BasicAuthority { fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { t.sender().map(|_|()) // Perform EC recovery and cache sender } + + fn register_account_provider(&self, ap: Arc) { + *self.account_provider.lock() = Some(ap); + } } #[cfg(test)] diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index 3dc78d1a2..7351ac90d 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -23,7 +23,6 @@ use spec::CommonParams; use evm::Schedule; use block::ExecutedBlock; use util::Bytes; -use account_provider::AccountProvider; /// An engine which does not provide any consensus mechanism, just seals blocks internally. pub struct InstantSeal { @@ -60,7 +59,7 @@ impl Engine for InstantSeal { fn is_sealer(&self, _author: &Address) -> Option { Some(true) } - fn generate_seal(&self, _block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option> { + fn generate_seal(&self, _block: &ExecutedBlock) -> Option> { Some(Vec::new()) } } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index c70a19de8..e2e194d3c 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -91,7 +91,7 @@ pub trait Engine : Sync + Send { /// /// This operation is synchronous and may (quite reasonably) not be available, in which None will /// be returned. - fn generate_seal(&self, _block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option> { None } + fn generate_seal(&self, _block: &ExecutedBlock) -> Option> { None } /// Phase 1 quick block verification. Only does checks that are cheap. `block` (the header's full block) /// may be provided for additional checks. Returns either a null `Ok` or a general error detailing the problem with import. @@ -146,5 +146,6 @@ pub trait Engine : Sync + Send { /// 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. + /// Add an account provider useful for Engines that sign stuff. + fn register_account_provider(&self, _account_provider: Arc) {} } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 84e29458d..845d6aeed 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -19,7 +19,6 @@ use std::time::{Instant, Duration}; use util::*; use util::using_queue::{UsingQueue, GetAction}; -use account_provider::AccountProvider; use views::{BlockView, HeaderView}; use state::{State, CleanupMode}; use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics}; @@ -220,14 +219,13 @@ pub struct Miner { extra_data: RwLock, engine: Arc, - accounts: Option>, work_poster: Option, gas_pricer: Mutex, } impl Miner { /// Creates new instance of miner. - fn new_raw(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Miner { + fn new_raw(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec) -> Miner { let work_poster = match options.new_work_notify.is_empty() { true => None, false => Some(WorkPoster::new(&options.new_work_notify)) @@ -261,26 +259,20 @@ impl Miner { author: RwLock::new(Address::default()), extra_data: RwLock::new(Vec::new()), options: options, - accounts: accounts, engine: spec.engine.clone(), work_poster: work_poster, gas_pricer: Mutex::new(gas_pricer), } } - /// 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. + /// Creates new instance of miner 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) + Miner::new_raw(Default::default(), GasPricer::new_fixed(20_000_000_000u64.into()), spec) } /// Creates new instance of a miner Arc. - pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Arc { - Arc::new(Miner::new_raw(options, gas_pricer, spec, accounts)) + pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec) -> Arc { + Arc::new(Miner::new_raw(options, gas_pricer, spec)) } fn forced_sealing(&self) -> bool { @@ -461,10 +453,7 @@ impl Miner { /// 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."); - let s = self.engine.generate_seal(block.block(), match self.accounts { - Some(ref x) => Some(&**x), - None => None, - }); + let s = self.engine.generate_seal(block.block()); if let Some(seal) = s { trace!(target: "miner", "seal_block_internally: managed internal seal. importing..."); block.lock().try_seal(&*self.engine, seal).or_else(|_| { @@ -1170,7 +1159,6 @@ mod tests { }, GasPricer::new_fixed(0u64.into()), &Spec::new_test(), - None, // accounts provider )).ok().expect("Miner was just created.") } diff --git a/parity/run.rs b/parity/run.rs index 2cc791f8c..aae4db748 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -203,11 +203,8 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { sync_config.fork_block = spec.fork_block(); sync_config.warp_sync = cmd.warp_sync; - // prepare account provider - let account_provider = Arc::new(try!(prepare_account_provider(&cmd.dirs, cmd.acc_conf))); - // create miner - let miner = Miner::new(cmd.miner_options, cmd.gas_pricer.into(), &spec, Some(account_provider.clone())); + let miner = Miner::new(cmd.miner_options, cmd.gas_pricer.into(), &spec); miner.set_author(cmd.miner_extras.author); miner.set_gas_floor_target(cmd.miner_extras.gas_floor_target); miner.set_gas_ceil_target(cmd.miner_extras.gas_ceil_target); @@ -241,6 +238,9 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { // create supervisor let mut hypervisor = modules::hypervisor(&cmd.dirs.ipc_path()); + // prepare account provider + let account_provider = Arc::new(try!(prepare_account_provider(&cmd.dirs, cmd.acc_conf))); + // create client service. let service = try!(ClientService::start( client_config, From 11b6578bc324d91b38038e0e035900303da086e9 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 17 Nov 2016 18:46:50 +0000 Subject: [PATCH 059/295] update tests --- ethcore/src/engines/authority_round.rs | 9 +++++---- ethcore/src/engines/basic_authority.rs | 3 ++- ethcore/src/engines/instant_seal.rs | 9 ++------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 2d7a5d69b..ee07b0262 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -388,6 +388,7 @@ mod tests { let spec = Spec::new_test_round(); let engine = &*spec.engine; + engine.register_account_provider(Arc::new(tap)); let genesis_header = spec.genesis_header(); let mut db1 = get_temp_state_db().take(); spec.ensure_db_good(&mut db1).unwrap(); @@ -399,16 +400,16 @@ mod tests { let b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes, addr2, (3141562.into(), 31415620.into()), vec![]).unwrap(); let b2 = b2.close_and_lock(); - if let Some(seal) = engine.generate_seal(b1.block(), Some(&tap)) { + if let Some(seal) = engine.generate_seal(b1.block()) { assert!(b1.clone().try_seal(engine, seal).is_ok()); // Second proposal is forbidden. - assert!(engine.generate_seal(b1.block(), Some(&tap)).is_none()); + assert!(engine.generate_seal(b1.block()).is_none()); } - if let Some(seal) = engine.generate_seal(b2.block(), Some(&tap)) { + if let Some(seal) = engine.generate_seal(b2.block()) { assert!(b2.clone().try_seal(engine, seal).is_ok()); // Second proposal is forbidden. - assert!(engine.generate_seal(b2.block(), Some(&tap)).is_none()); + assert!(engine.generate_seal(b2.block()).is_none()); } } diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 82a590b38..c43495967 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -259,6 +259,7 @@ mod tests { let spec = new_test_authority(); let engine = &*spec.engine; + engine.register_account_provider(Arc::new(tap)); let genesis_header = spec.genesis_header(); let mut db_result = get_temp_state_db(); let mut db = db_result.take(); @@ -266,7 +267,7 @@ mod tests { 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(); + let seal = engine.generate_seal(b.block()).unwrap(); assert!(b.try_seal(engine, seal).is_ok()); } diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index 7351ac90d..7491d47f3 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -68,16 +68,12 @@ impl Engine for InstantSeal { mod tests { use util::*; use tests::helpers::*; - use account_provider::AccountProvider; use spec::Spec; use header::Header; use block::*; #[test] fn instant_can_seal() { - let tap = AccountProvider::transient_provider(); - let addr = tap.insert_account("".sha3(), "").unwrap(); - let spec = Spec::new_instant(); let engine = &*spec.engine; let genesis_header = spec.genesis_header(); @@ -85,10 +81,9 @@ mod tests { let mut db = db_result.take(); spec.ensure_db_good(&mut db).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 = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); - // Seal with empty AccountProvider. - let seal = engine.generate_seal(b.block(), Some(&tap)).unwrap(); + let seal = engine.generate_seal(b.block()).unwrap(); assert!(b.try_seal(engine, seal).is_ok()); } From a521fda24e667e5d8432dfa9a8dd41dfa98e4017 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 17 Nov 2016 19:04:02 +0000 Subject: [PATCH 060/295] update rpc module test --- rpc/src/v1/tests/eth.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 2f5131f32..1ff5e1771 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -50,7 +50,7 @@ fn sync_provider() -> Arc { })) } -fn miner_service(spec: &Spec, accounts: Arc) -> Arc { +fn miner_service(spec: &Spec) -> Arc { Miner::new( MinerOptions { new_work_notify: vec![], @@ -69,7 +69,6 @@ fn miner_service(spec: &Spec, accounts: Arc) -> Arc { }, GasPricer::new_fixed(20_000_000_000u64.into()), &spec, - Some(accounts), ) } @@ -116,7 +115,8 @@ impl EthTester { fn from_spec(spec: Spec) -> Self { let dir = RandomTempPath::new(); let account_provider = account_provider(); - let miner_service = miner_service(&spec, account_provider.clone()); + spec.engine.register_account_provider(account_provider.clone()); + let miner_service = miner_service(&spec); let snapshot_service = snapshot_service(); let db_config = ::util::kvdb::DatabaseConfig::with_columns(::ethcore::db::NUM_COLUMNS); From 9d8ac7a09b49a1e5263209ae1130181c1708c149 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 17 Nov 2016 19:08:00 +0000 Subject: [PATCH 061/295] extra line [ci skip] --- ethcore/src/engines/authority_round.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index ee07b0262..ab2b41aec 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -312,7 +312,6 @@ impl Engine for AuthorityRound { *self.message_channel.lock() = Some(message_channel); } - fn register_account_provider(&self, account_provider: Arc) { *self.account_provider.lock() = Some(account_provider); } From c62795d09beeec17b32615bc05243ae85c97281c Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 17 Nov 2016 23:36:24 +0000 Subject: [PATCH 062/295] ap registration --- ethcore/src/engines/tendermint/message.rs | 12 ++++- ethcore/src/engines/tendermint/mod.rs | 65 +++++++++++++++-------- 2 files changed, 54 insertions(+), 23 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index f957c7785..32faec72b 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -18,7 +18,8 @@ use util::*; use super::{Height, Round, BlockHash, Step}; -use rlp::{View, DecoderError, Decodable, Decoder, Encodable, RlpStream, Stream}; +use rlp::*; +use error::Error; #[derive(Debug, PartialEq, Eq, Clone)] pub struct ConsensusMessage { @@ -30,6 +31,15 @@ pub struct ConsensusMessage { } impl ConsensusMessage { + pub fn new_rlp(signer: F, height: Height, round: Round, step: Step, block_hash: Option) -> Option where F: FnOnce(H256) -> Option { + let mut s = RlpStream::new_list(4); + s.append(&height); + s.append(&round); + s.append(&step); + s.append(&block_hash.unwrap_or(H256::zero())); + Some(s.out()) + } + pub fn is_height(&self, height: Height) -> bool { self.height == height } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 494e83873..8bbed8c84 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -82,6 +82,8 @@ pub struct Tendermint { proposed_block: Mutex>, /// Channel for updating the sealing. message_channel: Mutex>>, + /// Used to sign messages and proposals. + account_provider: Mutex>>, /// Message for the last PoLC. last_lock: RwLock>, /// Bare hash of the proposed block, used for seal submission. @@ -105,6 +107,7 @@ impl Tendermint { votes: VoteCollector::new(), proposed_block: Mutex::new(None), message_channel: Mutex::new(None), + account_provider: Mutex::new(None), last_lock: RwLock::new(None), proposal: RwLock::new(None) }); @@ -131,19 +134,31 @@ impl Tendermint { } } - fn broadcast_message(&self, message: Bytes) { - if let Some(ref channel) = *self.message_channel.lock() { - match channel.send(ClientIoMessage::BroadcastMessage(message)) { - Ok(_) => trace!(target: "poa", "timeout: BroadcastMessage message sent."), - Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), + fn broadcast_message(&self, block_hash: Option) { + if let Some(message) = self.generate_message(block_hash) { + if let Some(ref channel) = *self.message_channel.lock() { + match channel.send(ClientIoMessage::BroadcastMessage(message)) { + Ok(_) => trace!(target: "poa", "timeout: BroadcastMessage message sent."), + Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), + } } + } else { + warn!(target: "poa", "broadcast_message: Message could not be generated."); } } - fn generate_message(&self, block_hash: Option) -> ConsensusMessage { - Ok(signature) = ap.sign(*author, None, block_hash(header)) - ConsensusMessage { signatue - + fn generate_message(&self, block_hash: Option) -> Option { + if let Some(ref ap) = *self.account_provider.lock() { + ConsensusMessage::new_rlp( + |mh| ap.sign(*self.authority.read(), None, mh).ok().map(H520::from), + self.height.load(AtomicOrdering::SeqCst), + self.round.load(AtomicOrdering::SeqCst), + *self.step.read(), + block_hash + ) + } else { + None + } } fn to_step(&self, step: Step) { @@ -154,19 +169,19 @@ impl Tendermint { self.update_sealing() }, Step::Prevote => { - self.broadcast_message() + self.broadcast_message(None) }, Step::Precommit => { - let message = match self.last_lock.read() { - Some(m) => - None => ConsensusMessage { signature: signature, height - } - self.broadcast_message(::rlp::encode(message)) + let block_hash = match *self.last_lock.read() { + Some(ref m) => None, + None => None, + }; + self.broadcast_message(block_hash); }, Step::Commit => { // Commit the block using a complete signature set. let round = self.round.load(AtomicOrdering::SeqCst); - if let Some((proposer, votes)) = self.votes.seal_signatures(header.number() as Height, round, Some(block_hash(header))) { + if let Some((proposer, votes)) = self.votes.seal_signatures(self.height.load(AtomicOrdering::SeqCst), round, *self.proposal.read()) { let seal = vec![ ::rlp::encode(&round).to_vec(), ::rlp::encode(proposer).to_vec(), @@ -286,8 +301,8 @@ impl Engine for Tendermint { } /// Attempt to seal the block internally using all available signatures. - fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { - if let Some(ap) = accounts { + fn generate_seal(&self, block: &ExecutedBlock) -> Option> { + if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let author = header.author(); if let Ok(signature) = ap.sign(*author, None, block_hash(header)) { @@ -317,11 +332,14 @@ impl Engine for Tendermint { // Check if the message is known. if self.votes.vote(message.clone(), sender).is_none() { - let is_newer_than_lock = self.last_lock.read().map_or(true, |lock| message > lock); + let is_newer_than_lock = match *self.last_lock.read() { + Some(ref lock) => &message > lock, + None => true, + }; if is_newer_than_lock && message.step == Step::Prevote && self.has_enough_aligned_votes(&message) { - *self.last_lock.write() = Some(message); + *self.last_lock.write() = Some(message.clone()); } // Check if it can affect step transition. if self.is_current(&message) { @@ -428,8 +446,11 @@ impl Engine for Tendermint { } fn register_message_channel(&self, message_channel: IoChannel) { - let mut guard = self.message_channel.lock(); - *guard = Some(message_channel); + *self.message_channel.lock() = Some(message_channel); + } + + fn register_account_provider(&self, account_provider: Arc) { + *self.account_provider.lock() = Some(account_provider); } } From e90d81419380fa391ed3d365839a368d5877e032 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 18 Nov 2016 12:27:00 +0000 Subject: [PATCH 063/295] lock rounds --- ethcore/src/engines/tendermint/mod.rs | 50 ++++++++++++------- ethcore/src/engines/tendermint/timeout.rs | 1 + .../src/engines/tendermint/vote_collector.rs | 33 ++++++------ 3 files changed, 53 insertions(+), 31 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 8bbed8c84..02cd114b5 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -78,14 +78,14 @@ pub struct Tendermint { proposer_nonce: AtomicUsize, /// Vote accumulator. votes: VoteCollector, - /// Proposed block held until seal is gathered. - proposed_block: Mutex>, /// Channel for updating the sealing. message_channel: Mutex>>, /// Used to sign messages and proposals. account_provider: Mutex>>, /// Message for the last PoLC. - last_lock: RwLock>, + lock_change: RwLock>, + /// Last lock round. + last_lock: AtomicUsize, /// Bare hash of the proposed block, used for seal submission. proposal: RwLock> } @@ -105,10 +105,10 @@ impl Tendermint { step: RwLock::new(Step::Propose), proposer_nonce: AtomicUsize::new(0), votes: VoteCollector::new(), - proposed_block: Mutex::new(None), message_channel: Mutex::new(None), account_provider: Mutex::new(None), - last_lock: RwLock::new(None), + lock_change: RwLock::new(None), + last_lock: AtomicUsize::new(0), proposal: RwLock::new(None) }); let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; @@ -169,29 +169,41 @@ impl Tendermint { self.update_sealing() }, Step::Prevote => { - self.broadcast_message(None) + let should_unlock = |lock_change_round| { + self.last_lock.load(AtomicOrdering::SeqCst) < lock_change_round + && lock_change_round < self.round.load(AtomicOrdering::SeqCst) + }; + let block_hash = match *self.lock_change.read() { + Some(ref m) if should_unlock(m.round) => self.proposal.read().clone(), + Some(ref m) => m.block_hash, + None => None, + }; + self.broadcast_message(block_hash) }, Step::Precommit => { - let block_hash = match *self.last_lock.read() { - Some(ref m) => None, - None => None, + let block_hash = match *self.lock_change.read() { + Some(ref m) if self.is_round(m) => { + self.last_lock.store(m.round, AtomicOrdering::SeqCst); + m.block_hash + }, + _ => None, }; self.broadcast_message(block_hash); }, Step::Commit => { // Commit the block using a complete signature set. let round = self.round.load(AtomicOrdering::SeqCst); - if let Some((proposer, votes)) = self.votes.seal_signatures(self.height.load(AtomicOrdering::SeqCst), round, *self.proposal.read()) { + if let Some(seal) = self.votes.seal_signatures(self.height.load(AtomicOrdering::SeqCst), round, *self.proposal.read()) { let seal = vec![ ::rlp::encode(&round).to_vec(), - ::rlp::encode(proposer).to_vec(), - ::rlp::encode(&votes).to_vec() + ::rlp::encode(&seal.proposal).to_vec(), + ::rlp::encode(&seal.votes).to_vec() ]; if let Some(block_hash) = *self.proposal.read() { self.submit_seal(block_hash, seal); } } - *self.last_lock.write() = None; + *self.lock_change.write() = None; }, } } @@ -218,10 +230,14 @@ impl Tendermint { self.is_nonce_proposer(self.proposer_nonce.load(AtomicOrdering::SeqCst), address) } - fn is_current(&self, message: &ConsensusMessage) -> bool { + fn is__height(&self, message: &ConsensusMessage) -> bool { message.is_height(self.height.load(AtomicOrdering::SeqCst)) } + fn is_round(&self, message: &ConsensusMessage) -> bool { + message.is_round(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst)) + } + fn has_enough_any_votes(&self) -> bool { self.votes.count_step_votes(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read()) > self.threshold() } @@ -332,17 +348,17 @@ impl Engine for Tendermint { // Check if the message is known. if self.votes.vote(message.clone(), sender).is_none() { - let is_newer_than_lock = match *self.last_lock.read() { + let is_newer_than_lock = match *self.lock_change.read() { Some(ref lock) => &message > lock, None => true, }; if is_newer_than_lock && message.step == Step::Prevote && self.has_enough_aligned_votes(&message) { - *self.last_lock.write() = Some(message.clone()); + *self.lock_change.write() = Some(message.clone()); } // Check if it can affect step transition. - if self.is_current(&message) { + if self.is__height(&message) { let next_step = match *self.step.read() { Step::Precommit if self.has_enough_aligned_votes(&message) => { if message.block_hash.is_none() { diff --git a/ethcore/src/engines/tendermint/timeout.rs b/ethcore/src/engines/tendermint/timeout.rs index 329145984..7cd94350b 100644 --- a/ethcore/src/engines/tendermint/timeout.rs +++ b/ethcore/src/engines/tendermint/timeout.rs @@ -91,6 +91,7 @@ impl IoHandler for TransitionHandler { }, Step::Commit => { set_timeout(io, engine.our_params.timeouts.propose); + engine.last_lock.store(0, AtomicOrdering::SeqCst); engine.round.store(0, AtomicOrdering::SeqCst); engine.height.fetch_add(1, AtomicOrdering::SeqCst); Some(Step::Propose) diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index e7ea0a5d5..424759678 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -27,6 +27,11 @@ pub struct VoteCollector { votes: RwLock> } +pub struct SealSignatures { + pub proposal: H520, + pub votes: Vec +} + impl VoteCollector { pub fn new() -> VoteCollector { VoteCollector { votes: RwLock::new(BTreeMap::new()) } @@ -36,29 +41,29 @@ impl VoteCollector { self.votes.write().insert(message, voter) } - pub fn seal_signatures(&self, height: Height, round: Round, block_hash: Option) -> Option<(&H520, &[H520])> { - self.votes - .read() - .keys() - .cloned() + pub fn seal_signatures(&self, height: Height, round: Round, block_hash: Option) -> Option { + let guard = self.votes.read(); // Get only Propose and Precommits. + let mut correct_signatures = guard.keys() .filter(|m| m.is_aligned(height, round, block_hash) && m.step != Step::Prevote) - .map(|m| m.signature) - .collect::>() - .split_first() + .map(|m| m.signature.clone()); + correct_signatures.next().map(|proposal| SealSignatures { + proposal: proposal, + votes: correct_signatures.collect() + }) } - pub fn aligned_votes(&self, message: &ConsensusMessage) -> Vec<&ConsensusMessage> { - self.votes - .read() - .keys() + pub fn aligned_votes(&self, message: &ConsensusMessage) -> Vec { + let guard = self.votes.read(); + guard.keys() // Get only Propose and Precommits. .filter(|m| m.is_aligned(message.height, message.round, message.block_hash) && m.step == message.step) + .cloned() .collect() } - pub fn aligned_signatures(&self, message: &ConsensusMessage) -> &[H520] { - self.seal_signatures(message.height, message.round, message.block_hash).map_or(&[], |s| s.1) + pub fn aligned_signatures(&self, message: &ConsensusMessage) -> Vec { + self.seal_signatures(message.height, message.round, message.block_hash).map_or(Vec::new(), |s| s.votes) } pub fn count_step_votes(&self, height: Height, round: Round, step: Step) -> usize { From 2f3b80129661f0fe87f50b5804dc7cedaf9c2237 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 18 Nov 2016 13:37:47 +0000 Subject: [PATCH 064/295] rename transition --- .../src/engines/tendermint/{timeout.rs => transition.rs} | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) rename ethcore/src/engines/tendermint/{timeout.rs => transition.rs} (92%) diff --git a/ethcore/src/engines/tendermint/timeout.rs b/ethcore/src/engines/tendermint/transition.rs similarity index 92% rename from ethcore/src/engines/tendermint/timeout.rs rename to ethcore/src/engines/tendermint/transition.rs index 7cd94350b..a5cb02763 100644 --- a/ethcore/src/engines/tendermint/timeout.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -16,7 +16,6 @@ //! Tendermint timeout handling. -use std::sync::atomic::{Ordering as AtomicOrdering}; use std::sync::Weak; use io::{IoContext, IoHandler, TimerToken}; use super::{Tendermint, Step}; @@ -86,14 +85,12 @@ impl IoHandler for TransitionHandler { }, Step::Precommit if engine.has_enough_any_votes() => { set_timeout(io, engine.our_params.timeouts.propose); - engine.round.fetch_add(1, AtomicOrdering::SeqCst); + engine.increment_round(1); Some(Step::Propose) }, Step::Commit => { set_timeout(io, engine.our_params.timeouts.propose); - engine.last_lock.store(0, AtomicOrdering::SeqCst); - engine.round.store(0, AtomicOrdering::SeqCst); - engine.height.fetch_add(1, AtomicOrdering::SeqCst); + engine.reset_round(); Some(Step::Propose) }, _ => None, From 49cbd6ef983d50737d8875a1b99010bf4863c2ea Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 18 Nov 2016 13:38:04 +0000 Subject: [PATCH 065/295] unused imports, proposer_nonce --- ethcore/src/client/client.rs | 4 +- ethcore/src/engines/tendermint/message.rs | 9 +++- ethcore/src/engines/tendermint/mod.rs | 54 ++++++++++++------- ethcore/src/engines/tendermint/params.rs | 2 +- .../src/engines/tendermint/vote_collector.rs | 1 - 5 files changed, 43 insertions(+), 27 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 6eda89a7c..d55d884a4 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -24,13 +24,11 @@ use time::precise_time_ns; // util use util::{Bytes, PerfTimer, Itertools, Mutex, RwLock}; use util::{journaldb, TrieFactory, Trie}; -use util::{U256, H256, H520, Address, H2048, Uint, FixedHash}; -use util::sha3::*; +use util::{U256, H256, Address, H2048, Uint, FixedHash}; use util::trie::TrieSpec; use util::kvdb::*; // other -use ethkey::recover; use io::*; use views::{HeaderView, BodyView, BlockView}; use error::{ImportError, ExecutionError, CallError, BlockError, ImportResult, Error as EthcoreError}; diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 32faec72b..4fcbd3e52 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -19,7 +19,6 @@ use util::*; use super::{Height, Round, BlockHash, Step}; use rlp::*; -use error::Error; #[derive(Debug, PartialEq, Eq, Clone)] pub struct ConsensusMessage { @@ -37,7 +36,13 @@ impl ConsensusMessage { s.append(&round); s.append(&step); s.append(&block_hash.unwrap_or(H256::zero())); - Some(s.out()) + let block_info = s.out(); + signer(block_info.sha3()).map(|ref signature| { + let mut s = RlpStream::new_list(2); + s.append(signature); + s.append(&block_info); + s.out() + }) } pub fn is_height(&self, height: Height) -> bool { diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 02cd114b5..1974858e5 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -17,7 +17,7 @@ //! Tendermint BFT consensus engine with round robin proof-of-authority. mod message; -mod timeout; +mod transition; mod params; mod vote_collector; @@ -29,7 +29,7 @@ use header::Header; use builtin::Builtin; use env_info::EnvInfo; use transaction::SignedTransaction; -use rlp::{UntrustedRlp, View, encode}; +use rlp::{UntrustedRlp, View}; use ethkey::{recover, public_to_address}; use account_provider::AccountProvider; use block::*; @@ -41,7 +41,7 @@ use evm::Schedule; use io::{IoService, IoChannel}; use service::ClientIoMessage; use self::message::ConsensusMessage; -use self::timeout::TransitionHandler; +use self::transition::TransitionHandler; use self::params::TendermintParams; use self::vote_collector::VoteCollector; @@ -57,7 +57,6 @@ pub type Height = usize; pub type Round = usize; pub type BlockHash = H256; -pub type AtomicMs = AtomicUsize; type Signatures = Vec; /// Engine using `Tendermint` consensus algorithm, suitable for EVM chain. @@ -169,12 +168,8 @@ impl Tendermint { self.update_sealing() }, Step::Prevote => { - let should_unlock = |lock_change_round| { - self.last_lock.load(AtomicOrdering::SeqCst) < lock_change_round - && lock_change_round < self.round.load(AtomicOrdering::SeqCst) - }; let block_hash = match *self.lock_change.read() { - Some(ref m) if should_unlock(m.round) => self.proposal.read().clone(), + Some(ref m) if self.should_unlock(m.round) => self.proposal.read().clone(), Some(ref m) => m.block_hash, None => None, }; @@ -210,7 +205,7 @@ impl Tendermint { fn nonce_proposer(&self, proposer_nonce: usize) -> &Address { let ref p = self.our_params; - p.authorities.get(proposer_nonce % p.authority_n).unwrap() + p.authorities.get(proposer_nonce % p.authority_n).expect("There are authority_n authorities; taking number modulo authority_n gives number in authority_n range; qed") } fn is_nonce_proposer(&self, proposer_nonce: usize, address: &Address) -> bool { @@ -230,7 +225,7 @@ impl Tendermint { self.is_nonce_proposer(self.proposer_nonce.load(AtomicOrdering::SeqCst), address) } - fn is__height(&self, message: &ConsensusMessage) -> bool { + fn is_height(&self, message: &ConsensusMessage) -> bool { message.is_height(self.height.load(AtomicOrdering::SeqCst)) } @@ -238,12 +233,31 @@ impl Tendermint { message.is_round(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst)) } + fn increment_round(&self, n: Round) { + self.proposer_nonce.fetch_add(n, AtomicOrdering::SeqCst); + self.round.fetch_add(n, AtomicOrdering::SeqCst); + } + + fn reset_round(&self) { + self.last_lock.store(0, AtomicOrdering::SeqCst); + self.proposer_nonce.fetch_add(1, AtomicOrdering::SeqCst); + self.height.fetch_add(1, AtomicOrdering::SeqCst); + self.round.store(0, AtomicOrdering::SeqCst); + } + + fn should_unlock(&self, lock_change_round: Round) -> bool { + self.last_lock.load(AtomicOrdering::SeqCst) < lock_change_round + && lock_change_round < self.round.load(AtomicOrdering::SeqCst) + } + + fn has_enough_any_votes(&self) -> bool { self.votes.count_step_votes(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read()) > self.threshold() } - fn has_enough_step_votes(&self, message: &ConsensusMessage) -> bool { - self.votes.count_step_votes(message.height, message.round, message.step) > self.threshold() + fn has_enough_future_step_votes(&self, message: &ConsensusMessage) -> bool { + message.round > self.round.load(AtomicOrdering::SeqCst) + && self.votes.count_step_votes(message.height, message.round, message.step) > self.threshold() } fn has_enough_aligned_votes(&self, message: &ConsensusMessage) -> bool { @@ -357,24 +371,24 @@ impl Engine for Tendermint { && self.has_enough_aligned_votes(&message) { *self.lock_change.write() = Some(message.clone()); } - // Check if it can affect step transition. - if self.is__height(&message) { + // Check if it can affect the step transition. + if self.is_height(&message) { let next_step = match *self.step.read() { Step::Precommit if self.has_enough_aligned_votes(&message) => { if message.block_hash.is_none() { - self.round.fetch_add(1, AtomicOrdering::SeqCst); + self.increment_round(1); Some(Step::Propose) } else { Some(Step::Commit) } }, - Step::Precommit if self.has_enough_step_votes(&message) => { - self.round.store(message.round, AtomicOrdering::SeqCst); + Step::Precommit if self.has_enough_future_step_votes(&message) => { + self.increment_round(message.round - self.round.load(AtomicOrdering::SeqCst)); Some(Step::Precommit) }, Step::Prevote if self.has_enough_aligned_votes(&message) => Some(Step::Precommit), - Step::Prevote if self.has_enough_step_votes(&message) => { - self.round.store(message.round, AtomicOrdering::SeqCst); + Step::Prevote if self.has_enough_future_step_votes(&message) => { + self.increment_round(message.round - self.round.load(AtomicOrdering::SeqCst)); Some(Step::Prevote) }, _ => None, diff --git a/ethcore/src/engines/tendermint/params.rs b/ethcore/src/engines/tendermint/params.rs index dbed9b541..3752ae3bd 100644 --- a/ethcore/src/engines/tendermint/params.rs +++ b/ethcore/src/engines/tendermint/params.rs @@ -17,7 +17,7 @@ //! Tendermint specific parameters. use ethjson; -use super::timeout::TendermintTimeouts; +use super::transition::TendermintTimeouts; use util::{Address, U256}; use time::Duration; diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 424759678..0f4553502 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -19,7 +19,6 @@ use util::*; use super::message::ConsensusMessage; use super::{Height, Round, Step}; -use ethkey::recover; #[derive(Debug)] pub struct VoteCollector { From e69be670de55187a31e47fb12b8b6f9a1dd15dd0 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 21 Nov 2016 11:36:07 +0000 Subject: [PATCH 066/295] message serialization --- ethcore/src/engines/tendermint/message.rs | 30 +++++++++++------------ ethcore/src/engines/tendermint/mod.rs | 15 ++++++------ 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 4fcbd3e52..26e4716c1 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -30,21 +30,6 @@ pub struct ConsensusMessage { } impl ConsensusMessage { - pub fn new_rlp(signer: F, height: Height, round: Round, step: Step, block_hash: Option) -> Option where F: FnOnce(H256) -> Option { - let mut s = RlpStream::new_list(4); - s.append(&height); - s.append(&round); - s.append(&step); - s.append(&block_hash.unwrap_or(H256::zero())); - let block_info = s.out(); - signer(block_info.sha3()).map(|ref signature| { - let mut s = RlpStream::new_list(2); - s.append(signature); - s.append(&block_info); - s.out() - }) - } - pub fn is_height(&self, height: Height) -> bool { self.height == height } @@ -143,3 +128,18 @@ impl Encodable for ConsensusMessage { s.append(&self.block_hash.unwrap_or(H256::zero())); } } + +pub fn message_info_rlp(height: Height, round: Round, step: Step, block_hash: Option) -> Bytes { + let mut s = RlpStream::new_list(4); + s.append(&height).append(&round).append(&step).append(&block_hash.unwrap_or(H256::zero())); + s.out() +} + +pub fn message_full_rlp(signer: F, height: Height, round: Round, step: Step, block_hash: Option) -> Option where F: FnOnce(H256) -> Option { + let vote_info = message_info_rlp(height, round, step, block_hash); + signer(vote_info.sha3()).map(|ref signature| { + let mut s = RlpStream::new_list(2); + s.append(signature).append(&vote_info); + s.out() + }) +} diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 1974858e5..81b764de7 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -40,7 +40,7 @@ use views::HeaderView; use evm::Schedule; use io::{IoService, IoChannel}; use service::ClientIoMessage; -use self::message::ConsensusMessage; +use self::message::{ConsensusMessage, message_info_rlp, message_full_rlp}; use self::transition::TransitionHandler; use self::params::TendermintParams; use self::vote_collector::VoteCollector; @@ -148,7 +148,7 @@ impl Tendermint { fn generate_message(&self, block_hash: Option) -> Option { if let Some(ref ap) = *self.account_provider.lock() { - ConsensusMessage::new_rlp( + message_full_rlp( |mh| ap.sign(*self.authority.read(), None, mh).ok().map(H520::from), self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), @@ -335,7 +335,8 @@ impl Engine for Tendermint { if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let author = header.author(); - if let Ok(signature) = ap.sign(*author, None, block_hash(header)) { + let vote_info = message_info_rlp(header.number() as Height, self.round.load(AtomicOrdering::SeqCst), Step::Propose, Some(block_hash(header))); + if let Ok(signature) = ap.sign(*author, None, vote_info.sha3()) { *self.proposal.write() = Some(header.bare_hash()); Some(vec![ ::rlp::encode(&self.round.load(AtomicOrdering::SeqCst)).to_vec(), @@ -418,10 +419,6 @@ impl Engine for Tendermint { /// Also transitions to Prevote if verifying Proposal. fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { let signature = try!(proposer_signature(header)); - let proposer = public_to_address(&try!(recover(&signature.into(), &block_hash(header)))); - if !self.is_proposer(&proposer) { - try!(Err(BlockError::InvalidSeal)) - } let proposal = ConsensusMessage { signature: signature, height: header.number() as Height, @@ -429,6 +426,10 @@ impl Engine for Tendermint { step: Step::Propose, block_hash: Some(block_hash(header)) }; + let proposer = public_to_address(&try!(recover(&signature.into(), &::rlp::encode(&proposal)))); + if !self.is_proposer(&proposer) { + try!(Err(BlockError::InvalidSeal)) + } self.votes.vote(proposal, proposer); let votes_rlp = UntrustedRlp::new(&header.seal()[2]); for rlp in votes_rlp.iter() { From 42ef7767dab880654903173298c61eb29ee48083 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 21 Nov 2016 12:23:27 +0000 Subject: [PATCH 067/295] delete unused message type --- ethcore/src/engines/tendermint/vote.rs | 72 -------------------------- 1 file changed, 72 deletions(-) delete mode 100644 ethcore/src/engines/tendermint/vote.rs diff --git a/ethcore/src/engines/tendermint/vote.rs b/ethcore/src/engines/tendermint/vote.rs deleted file mode 100644 index b01e14460..000000000 --- a/ethcore/src/engines/tendermint/vote.rs +++ /dev/null @@ -1,72 +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 . - -//! Tendermint block seal. - -use util::*; -use header::Header; -use account_provider::AccountProvider; -use rlp::{View, DecoderError, Decodable, Decoder, Encodable, RlpStream, Stream}; -use basic_types::Seal; -use super::BlockHash; - -#[derive(Debug, PartialEq, Eq, Hash)] -pub struct Vote { - block_hash: BlockHash, - signature: H520 -} - -fn block_hash(header: &Header) -> H256 { - header.rlp(Seal::WithSome(1)).sha3() -} - -impl Vote { - fn new(block_hash: BlockHash, signature: H520) -> Vote { - Vote { block_hash: block_hash, signature: signature } - } - - /// Try to use the author address to create a vote. - pub fn propose(header: &Header, accounts: &AccountProvider) -> Option { - Self::validate(header, accounts, *header.author()) - } - - /// Use any unlocked validator account to create a vote. - pub fn validate(header: &Header, accounts: &AccountProvider, validator: Address) -> Option { - let message = block_hash(&header); - accounts.sign(validator, None, message) - .ok() - .map(Into::into) - .map(|sig| Self::new(message, sig)) - } -} - -impl Decodable for Vote { - fn decode(decoder: &D) -> Result where D: Decoder { - let rlp = decoder.as_rlp(); - if decoder.as_raw().len() != try!(rlp.payload_info()).total() { - return Err(DecoderError::RlpIsTooBig); - } - Ok(Self::new(try!(rlp.val_at(0)), try!(rlp.val_at(1)))) - } -} - -impl Encodable for Vote { - fn rlp_append(&self, s: &mut RlpStream) { - let Vote { ref block_hash, ref signature } = *self; - s.append(block_hash); - s.append(signature); - } -} From 12dbdc1d6e0c9455bc8dad1bcb78d8fee220033b Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 21 Nov 2016 16:01:34 +0000 Subject: [PATCH 068/295] dont pass ap --- ethcore/src/miner/miner.rs | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 05cc14b64..664b2f4e9 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -19,6 +19,7 @@ use std::time::{Instant, Duration}; use util::*; use util::using_queue::{UsingQueue, GetAction}; +use account_provider::AccountProvider; use views::{BlockView, HeaderView}; use header::Header; use state::{State, CleanupMode}; @@ -221,13 +222,14 @@ pub struct Miner { extra_data: RwLock, engine: Arc, + accounts: Option>, work_poster: Option, gas_pricer: Mutex, } impl Miner { /// Creates new instance of miner. - fn new_raw(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec) -> 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)) @@ -261,20 +263,26 @@ impl Miner { author: RwLock::new(Address::default()), extra_data: RwLock::new(Vec::new()), options: options, + accounts: accounts, engine: spec.engine.clone(), work_poster: work_poster, gas_pricer: Mutex::new(gas_pricer), } } - /// Creates new instance of miner with given spec. + /// 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) + 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) -> Arc { - Arc::new(Miner::new_raw(options, gas_pricer, spec)) + pub fn new(options: MinerOptions, gas_pricer: GasPricer, spec: &Spec, accounts: Option>) -> Arc { + Arc::new(Miner::new_raw(options, gas_pricer, spec, accounts)) } fn forced_sealing(&self) -> bool { @@ -1029,7 +1037,7 @@ impl MinerService for Miner { ret.map(f) } - fn submit_seal(&self, chain: &MiningBlockChainClient, block_hash: H256, seal: Vec) -> Result<(), Error> { + fn submit_seal(&self, chain: &MiningBlockChainClient, pow_hash: H256, seal: Vec) -> Result<(), Error> { let result = if let Some(b) = self.sealing_work.lock().queue.get_used_if( if self.options.enable_resubmission { @@ -1037,9 +1045,9 @@ impl MinerService for Miner { } else { GetAction::Take }, - |b| &b.hash() == &block_hash + |b| &b.hash() == &pow_hash ) { - trace!(target: "miner", "Sealing block {}={}={} with seal {:?}", block_hash, b.hash(), b.header().bare_hash(), seal); + trace!(target: "miner", "Sealing block {}={}={} with seal {:?}", pow_hash, b.hash(), b.header().bare_hash(), seal); b.lock().try_seal(&*self.engine, seal).or_else(|(e, _)| { warn!(target: "miner", "Mined solution rejected: {}", e); Err(Error::PowInvalid) @@ -1191,6 +1199,7 @@ mod tests { }, GasPricer::new_fixed(0u64.into()), &Spec::new_test(), + None, // accounts provider )).ok().expect("Miner was just created.") } From 841d0941e09ad1c1125eadff2969806ef72b02a1 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 21 Nov 2016 16:01:52 +0000 Subject: [PATCH 069/295] remove WithSome block hash --- ethcore/src/basic_types.rs | 4 +--- ethcore/src/header.rs | 9 +++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/ethcore/src/basic_types.rs b/ethcore/src/basic_types.rs index 5c0100de3..79f009fd1 100644 --- a/ethcore/src/basic_types.rs +++ b/ethcore/src/basic_types.rs @@ -25,12 +25,10 @@ pub type LogBloom = H2048; pub static ZERO_LOGBLOOM: LogBloom = H2048([0x00; 256]); #[cfg_attr(feature="dev", allow(enum_variant_names))] -/// Enum for when a seal/signature is included. +/// Semantic boolean for when a seal/signature is included. pub enum Seal { /// The seal/signature is included. With, /// The seal/signature is not included. Without, - /// First N fields of seal are included. - WithSome(usize), } diff --git a/ethcore/src/header.rs b/ethcore/src/header.rs index 0aea4efd7..228933570 100644 --- a/ethcore/src/header.rs +++ b/ethcore/src/header.rs @@ -228,8 +228,7 @@ impl Header { // TODO: make these functions traity /// Place this header into an RLP stream `s`, optionally `with_seal`. pub fn stream_rlp(&self, s: &mut RlpStream, with_seal: Seal) { - let seal_n = match with_seal { Seal::With => self.seal.len(), Seal::WithSome(n) => n, _ => 0 }; - s.begin_list(13 + seal_n); + s.begin_list(13 + match with_seal { Seal::With => self.seal.len(), _ => 0 }); s.append(&self.parent_hash); s.append(&self.uncles_hash); s.append(&self.author); @@ -243,8 +242,10 @@ impl Header { s.append(&self.gas_used); s.append(&self.timestamp); s.append(&self.extra_data); - for b in self.seal.iter().take(seal_n) { - s.append_raw(b, 1); + if let Seal::With = with_seal { + for b in &self.seal { + s.append_raw(b, 1); + } } } From 84fdaf966a6617a4b823ef922a1029377dc3139d Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 21 Nov 2016 16:02:26 +0000 Subject: [PATCH 070/295] correct seal verification --- ethcore/src/engines/tendermint/message.rs | 39 +++++++++++++++-- ethcore/src/engines/tendermint/mod.rs | 52 ++++++----------------- 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 26e4716c1..cdcadb5b5 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -18,7 +18,10 @@ use util::*; use super::{Height, Round, BlockHash, Step}; +use error::Error; +use header::Header; use rlp::*; +use ethkey::{recover, public_to_address}; #[derive(Debug, PartialEq, Eq, Clone)] pub struct ConsensusMessage { @@ -29,7 +32,23 @@ pub struct ConsensusMessage { pub block_hash: Option } + +fn consensus_round(header: &Header) -> Result { + UntrustedRlp::new(header.seal()[0].as_slice()).as_val() +} + impl ConsensusMessage { + pub fn new_proposal(header: &Header) -> Result { + Ok(ConsensusMessage { + signature: try!(UntrustedRlp::new(header.seal()[1].as_slice()).as_val()), + height: header.number() as Height, + round: try!(consensus_round(header)), + step: Step::Propose, + block_hash: Some(header.bare_hash()) + }) + } + + pub fn is_height(&self, height: Height) -> bool { self.height == height } @@ -45,6 +64,13 @@ impl ConsensusMessage { pub fn is_aligned(&self, height: Height, round: Round, block_hash: Option) -> bool { self.height == height && self.round == round && self.block_hash == block_hash } + + pub fn verify(&self) -> Result { + let full_rlp = ::rlp::encode(self); + let block_info = Rlp::new(&full_rlp).at(1); + let public_key = try!(recover(&self.signature.into(), &block_info.as_raw().sha3())); + Ok(public_to_address(&public_key)) + } } impl PartialOrd for ConsensusMessage { @@ -56,10 +82,10 @@ impl PartialOrd for ConsensusMessage { impl Step { fn number(&self) -> i8 { match *self { - Step::Propose => -1, - Step::Prevote => 0, - Step::Precommit => 1, - Step::Commit => 2, + Step::Propose => 0, + Step::Prevote => 1, + Step::Precommit => 2, + Step::Commit => 3, } } } @@ -135,6 +161,11 @@ pub fn message_info_rlp(height: Height, round: Round, step: Step, block_hash: Op s.out() } +pub fn message_info_rlp_from_header(header: &Header) -> Result { + let round = try!(consensus_round(header)); + Ok(message_info_rlp(header.number() as Height, round, Step::Precommit, Some(header.bare_hash()))) +} + pub fn message_full_rlp(signer: F, height: Height, round: Round, step: Step, block_hash: Option) -> Option where F: FnOnce(H256) -> Option { let vote_info = message_info_rlp(height, round, step, block_hash); signer(vote_info.sha3()).map(|ref signature| { diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 81b764de7..4385b8264 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -23,7 +23,6 @@ mod vote_collector; use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use util::*; -use basic_types::Seal; use error::{Error, BlockError}; use header::Header; use builtin::Builtin; @@ -40,7 +39,7 @@ use views::HeaderView; use evm::Schedule; use io::{IoService, IoChannel}; use service::ClientIoMessage; -use self::message::{ConsensusMessage, message_info_rlp, message_full_rlp}; +use self::message::*; use self::transition::TransitionHandler; use self::params::TendermintParams; use self::vote_collector::VoteCollector; @@ -265,19 +264,6 @@ impl Tendermint { } } -/// Block hash including the consensus round, gets signed and included in the seal. -fn block_hash(header: &Header) -> H256 { - header.rlp(Seal::WithSome(1)).sha3() -} - -fn proposer_signature(header: &Header) -> Result { - UntrustedRlp::new(header.seal()[1].as_slice()).as_val() -} - -fn consensus_round(header: &Header) -> Result { - UntrustedRlp::new(header.seal()[0].as_slice()).as_val() -} - impl Engine for Tendermint { fn name(&self) -> &str { "Tendermint" } fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } @@ -289,11 +275,12 @@ impl Engine for Tendermint { /// Additional engine-specific information for the user/developer concerning `header`. fn extra_info(&self, header: &Header) -> BTreeMap { + let message = ConsensusMessage::new_proposal(header).expect("Invalid header."); map![ - "signature".into() => proposer_signature(header).as_ref().map(ToString::to_string).unwrap_or("".into()), - "height".into() => header.number().to_string(), - "round".into() => consensus_round(header).as_ref().map(ToString::to_string).unwrap_or("".into()), - "block_hash".into() => block_hash(header).to_string() + "signature".into() => message.signature.to_string(), + "height".into() => message.height.to_string(), + "round".into() => message.round.to_string(), + "block_hash".into() => message.block_hash.as_ref().map(ToString::to_string).unwrap_or("".into()) ] } @@ -319,12 +306,6 @@ impl Engine for Tendermint { *self.authority.write() = *block.header().author() } - /// Set the correct round in the seal. - fn on_close_block(&self, block: &mut ExecutedBlock) { - let round = self.round.load(AtomicOrdering::SeqCst); - block.fields_mut().header.set_seal(vec![::rlp::encode(&round).to_vec(), Vec::new(), Vec::new()]); - } - /// Round proposer switching. fn is_sealer(&self, address: &Address) -> Option { Some(self.is_proposer(address)) @@ -335,7 +316,7 @@ impl Engine for Tendermint { if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let author = header.author(); - let vote_info = message_info_rlp(header.number() as Height, self.round.load(AtomicOrdering::SeqCst), Step::Propose, Some(block_hash(header))); + let vote_info = message_info_rlp(header.number() as Height, self.round.load(AtomicOrdering::SeqCst), Step::Propose, Some(header.bare_hash())); if let Ok(signature) = ap.sign(*author, None, vote_info.sha3()) { *self.proposal.write() = Some(header.bare_hash()); Some(vec![ @@ -418,23 +399,16 @@ impl Engine for Tendermint { /// Also transitions to Prevote if verifying Proposal. fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { - let signature = try!(proposer_signature(header)); - let proposal = ConsensusMessage { - signature: signature, - height: header.number() as Height, - round: try!(consensus_round(header)), - step: Step::Propose, - block_hash: Some(block_hash(header)) - }; - let proposer = public_to_address(&try!(recover(&signature.into(), &::rlp::encode(&proposal)))); + let proposal = try!(ConsensusMessage::new_proposal(header)); + let proposer = try!(proposal.verify()); if !self.is_proposer(&proposer) { try!(Err(BlockError::InvalidSeal)) } self.votes.vote(proposal, proposer); - let votes_rlp = UntrustedRlp::new(&header.seal()[2]); - for rlp in votes_rlp.iter() { - let sig: H520 = try!(rlp.as_val()); - let address = public_to_address(&try!(recover(&sig.into(), &block_hash(header)))); + let block_info_hash = try!(message_info_rlp_from_header(header)).sha3(); + for rlp in UntrustedRlp::new(&header.seal()[2]).iter() { + let signature: H520 = try!(rlp.as_val()); + let address = public_to_address(&try!(recover(&signature.into(), &block_info_hash))); if !self.our_params.authorities.contains(&address) { try!(Err(BlockError::InvalidSeal)) } From 66526af5a8b379c165dfc02dc8ecd532f84e17ce Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 21 Nov 2016 18:53:53 +0000 Subject: [PATCH 071/295] pass engine in tests --- ethcore/src/engines/null_engine.rs | 6 ++++++ ethcore/src/snapshot/tests/blocks.rs | 22 +++++++++------------- ethcore/src/spec/spec.rs | 3 +-- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/ethcore/src/engines/null_engine.rs b/ethcore/src/engines/null_engine.rs index e0906ce22..bd5a4474a 100644 --- a/ethcore/src/engines/null_engine.rs +++ b/ethcore/src/engines/null_engine.rs @@ -38,6 +38,12 @@ impl NullEngine { } } +impl Default for NullEngine { + fn default() -> Self { + Self::new(Default::default(), Default::default()) + } +} + impl Engine for NullEngine { fn name(&self) -> &str { "NullEngine" diff --git a/ethcore/src/snapshot/tests/blocks.rs b/ethcore/src/snapshot/tests/blocks.rs index 34f1accca..3d9390d2e 100644 --- a/ethcore/src/snapshot/tests/blocks.rs +++ b/ethcore/src/snapshot/tests/blocks.rs @@ -31,23 +31,20 @@ use std::collections::HashMap; use std::sync::Arc; use std::sync::atomic::AtomicBool; -use spec::Spec; - fn chunk_and_restore(amount: u64) { let mut canon_chain = ChainGenerator::default(); let mut finalizer = BlockFinalizer::default(); let genesis = canon_chain.generate(&mut finalizer).unwrap(); let db_cfg = DatabaseConfig::with_columns(::db::NUM_COLUMNS); + let engine = Arc::new(::engines::NullEngine::default()); let orig_path = RandomTempPath::create_dir(); let new_path = RandomTempPath::create_dir(); let mut snapshot_path = new_path.as_path().to_owned(); snapshot_path.push("SNAP"); - let new_chain = |db| BlockChain::new(Default::default(), &genesis, db, Spec::new_null().engine); - let old_db = Arc::new(Database::open(&db_cfg, orig_path.as_str()).unwrap()); - let bc = new_chain(old_db.clone()); + let bc = BlockChain::new(Default::default(), &genesis, old_db.clone(), engine.clone()); // build the blockchain. let mut batch = old_db.transaction(); @@ -77,21 +74,20 @@ fn chunk_and_restore(amount: u64) { // restore it. let new_db = Arc::new(Database::open(&db_cfg, new_path.as_str()).unwrap()); - let new_chain = BlockChain::new(Default::default(), &genesis, new_db.clone(), Spec::new_null().engine); + let new_chain = BlockChain::new(Default::default(), &genesis, new_db.clone(), engine.clone()); let mut rebuilder = BlockRebuilder::new(new_chain, new_db.clone(), &manifest).unwrap(); let reader = PackedReader::new(&snapshot_path).unwrap().unwrap(); - let engine = ::engines::NullEngine::new(Default::default(), Default::default()); let flag = AtomicBool::new(true); for chunk_hash in &reader.manifest().block_hashes { let compressed = reader.chunk(*chunk_hash).unwrap(); let chunk = snappy::decompress(&compressed).unwrap(); - rebuilder.feed(&chunk, &engine, &flag).unwrap(); + rebuilder.feed(&chunk, engine.as_ref(), &flag).unwrap(); } rebuilder.finalize(HashMap::new()).unwrap(); // and test it. - let new_chain = BlockChain::new(Default::default(), &genesis, new_db, Spec::new_null().engine); + let new_chain = BlockChain::new(Default::default(), &genesis, new_db, engine); assert_eq!(new_chain.best_block_hash(), best_hash); } @@ -125,8 +121,8 @@ fn checks_flag() { let db_cfg = DatabaseConfig::with_columns(::db::NUM_COLUMNS); let db = Arc::new(Database::open(&db_cfg, path.as_str()).unwrap()); - let chain = BlockChain::new(Default::default(), &genesis, db.clone()); - let engine = ::engines::NullEngine::new(Default::default(), Default::default()); + let engine = Arc::new(::engines::NullEngine::default()); + let chain = BlockChain::new(Default::default(), &genesis, db.clone(), engine.clone()); let manifest = ::snapshot::ManifestData { state_hashes: Vec::new(), @@ -138,8 +134,8 @@ fn checks_flag() { let mut rebuilder = BlockRebuilder::new(chain, db.clone(), &manifest).unwrap(); - match rebuilder.feed(&chunk, &engine, &AtomicBool::new(false)) { + match rebuilder.feed(&chunk, engine.as_ref(), &AtomicBool::new(false)) { Err(Error::Snapshot(SnapshotError::RestorationAborted)) => {} _ => panic!("Wrong result on abort flag set") } -} \ No newline at end of file +} diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index ca5970ecf..b789df222 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -30,8 +30,7 @@ use ethjson; use rlp::{Rlp, RlpStream, View, Stream}; /// Parameters common to all engines. -#[derive(Debug, PartialEq, Clone)] -#[cfg_attr(test, derive(Default))] +#[derive(Debug, PartialEq, Clone, Default)] pub struct CommonParams { /// Account start nonce. pub account_start_nonce: U256, From 32bcd08b94b1a4648d1b1a29c5ad143da3889dd2 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 21 Nov 2016 18:54:16 +0000 Subject: [PATCH 072/295] test utilities --- ethcore/src/engines/tendermint/mod.rs | 147 ++++++++------------------ 1 file changed, 46 insertions(+), 101 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 4385b8264..48e3b3599 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -461,11 +461,10 @@ impl Engine for Tendermint { #[cfg(test)] mod tests { - use std::thread::sleep; - use std::time::{Duration}; use util::*; use rlp::{UntrustedRlp, RlpStream, Stream, View}; use block::*; + use state_db::StateDB; use error::{Error, BlockError}; use header::Header; use env_info::EnvInfo; @@ -473,46 +472,38 @@ mod tests { use account_provider::AccountProvider; use spec::Spec; use engines::{Engine, EngineError}; - use super::Tendermint; + use super::*; use super::params::TendermintParams; + use super::message::*; - fn propose_default(engine: &Arc, round: u8, proposer: Address) -> Result { - let mut s = RlpStream::new_list(3); - let header = Header::default(); - s.append(&round).append(&0u8).append(&header.bare_hash()); - let drain = s.out(); - let propose_rlp = UntrustedRlp::new(&drain); - - engine.handle_message(proposer, H520::default(), propose_rlp) + fn propose_default(engine: &Arc, db: &StateDB, proposer: Address) -> Option> { + let mut header = Header::default(); + let last_hashes = Arc::new(vec![]); + let b = OpenBlock::new(engine.as_ref(), Default::default(), false, db.boxed_clone(), &header, last_hashes, proposer, (3141562.into(), 31415620.into()), vec![]).unwrap(); + let b = b.close_and_lock(); + engine.generate_seal(b.block()) } - fn vote_default(engine: &Arc, round: u8, voter: Address) -> Result { - let mut s = RlpStream::new_list(3); - let header = Header::default(); - s.append(&round).append(&1u8).append(&header.bare_hash()); - let drain = s.out(); - let vote_rlp = UntrustedRlp::new(&drain); - - engine.handle_message(voter, H520::default(), vote_rlp) + fn vote_default(engine: &Arc, signer: F, height: usize, round: usize, step: Step) where F: FnOnce(H256) -> Option { + let m = message_full_rlp(signer, height, round, step, Some(Default::default())).unwrap(); + engine.handle_message(UntrustedRlp::new(&m)).unwrap(); } - fn good_seal(header: &Header) -> Vec { + fn proposal_seal(header: &Header, round: Round) -> Vec { let tap = AccountProvider::transient_provider(); - let mut seal = Vec::new(); - - let v0 = tap.insert_account("0".sha3(), "0").unwrap(); - let sig0 = tap.sign(v0, Some("0".into()), header.bare_hash()).unwrap(); - seal.push(::rlp::encode(&(&*sig0 as &[u8])).to_vec()); - - let v1 = tap.insert_account("1".sha3(), "1").unwrap(); - let sig1 = tap.sign(v1, Some("1".into()), header.bare_hash()).unwrap(); - seal.push(::rlp::encode(&(&*sig1 as &[u8])).to_vec()); - seal + let author = header.author(); + let vote_info = message_info_rlp(header.number() as Height, round, Step::Propose, Some(header.bare_hash())); + let signature = tap.sign(*author, None, vote_info.sha3()).unwrap(); + vec![ + ::rlp::encode(&round).to_vec(), + ::rlp::encode(&H520::from(signature)).to_vec(), + Vec::new() + ] } - fn default_block() -> Vec { - vec![160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101] + fn default_seal() -> Vec { + vec![vec![160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101]] } #[test] @@ -593,7 +584,7 @@ mod tests { let engine = Spec::new_test_tendermint().engine; let mut header = Header::default(); - let seal = good_seal(&header); + let seal = proposal_seal(&header, 0); header.set_seal(seal); // Enough signatures. @@ -607,7 +598,7 @@ mod tests { fn can_generate_seal() { let spec = Spec::new_test_tendermint(); let ref engine = *spec.engine; - let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()); + let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()).unwrap(); let genesis_header = spec.genesis_header(); let mut db_result = get_temp_state_db(); @@ -617,82 +608,55 @@ mod tests { let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); - tender.to_commit(b.hash(), good_seal(&b.header())); - - let seal = tender.generate_seal(b.block(), None).unwrap(); + let seal = tender.generate_seal(b.block()).unwrap(); assert!(b.try_seal(engine, seal).is_ok()); } #[test] fn propose_step() { - let engine = Spec::new_test_tendermint().engine; + let spec = Spec::new_test_tendermint(); + let engine = spec.engine.clone(); let tap = AccountProvider::transient_provider(); - let r = 0; + let mut db_result = get_temp_state_db(); + let mut db = db_result.take(); + spec.ensure_db_good(&mut db).unwrap(); let not_authority_addr = tap.insert_account("101".sha3(), "101").unwrap(); - assert!(propose_default(&engine, r, not_authority_addr).is_err()); + assert!(propose_default(&engine, &db, not_authority_addr).is_none()); let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap(); - assert!(propose_default(&engine, r, not_proposer_addr).is_err()); + assert!(propose_default(&engine, &db, not_proposer_addr).is_none()); let proposer_addr = tap.insert_account("1".sha3(), "1").unwrap(); - assert_eq!(default_block(), propose_default(&engine, r, proposer_addr).unwrap()); + assert_eq!(default_seal(), propose_default(&engine, &db, proposer_addr).unwrap()); - assert!(propose_default(&engine, r, proposer_addr).is_err()); - assert!(propose_default(&engine, r, not_proposer_addr).is_err()); - } - - #[test] - fn proposer_switching() { - let engine = Spec::new_test_tendermint().engine; - let tap = AccountProvider::transient_provider(); - - // Currently not a proposer. - let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap(); - assert!(propose_default(&engine, 0, not_proposer_addr).is_err()); - - sleep(Duration::from_millis(TendermintParams::default().timeouts.propose as u64)); - - // Becomes proposer after timeout. - assert_eq!(default_block(), propose_default(&engine, 0, not_proposer_addr).unwrap()); + assert!(propose_default(&engine, &db, proposer_addr).is_none()); + assert!(propose_default(&engine, &db, not_proposer_addr).is_none()); } #[test] fn prevote_step() { + let spec = Spec::new_test_tendermint(); let engine = Spec::new_test_tendermint().engine; let tap = AccountProvider::transient_provider(); - let r = 0; + let mut db_result = get_temp_state_db(); + let mut db = db_result.take(); + spec.ensure_db_good(&mut db).unwrap(); let v0 = tap.insert_account("0".sha3(), "0").unwrap(); let v1 = tap.insert_account("1".sha3(), "1").unwrap(); - // Propose. - assert!(propose_default(&engine, r, v1.clone()).is_ok()); + // Propose + assert!(propose_default(&engine, &db, v1.clone()).is_some()); - // Prevote. - assert_eq!(default_block(), vote_default(&engine, r, v0.clone()).unwrap()); - - assert!(vote_default(&engine, r, v0).is_err()); - assert!(vote_default(&engine, r, v1).is_err()); - } - - #[test] - fn precommit_step() { - let engine = Spec::new_test_tendermint().engine; - let tap = AccountProvider::transient_provider(); + let h = 0; let r = 0; - let v0 = tap.insert_account("0".sha3(), "0").unwrap(); - let v1 = tap.insert_account("1".sha3(), "1").unwrap(); - - // Propose. - assert!(propose_default(&engine, r, v1.clone()).is_ok()); - // Prevote. - assert_eq!(default_block(), vote_default(&engine, r, v0.clone()).unwrap()); + vote_default(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Prevote); - assert!(vote_default(&engine, r, v0).is_err()); - assert!(vote_default(&engine, r, v1).is_err()); + vote_default(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Prevote); + vote_default(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Prevote); } #[test] @@ -703,24 +667,5 @@ mod tests { }; println!("Waiting for timeout"); - sleep(Duration::from_secs(10)); - } - - #[test] - fn increments_round() { - let spec = Spec::new_test_tendermint(); - let ref engine = *spec.engine; - let def_params = TendermintParams::default(); - let tender = Tendermint::new(engine.params().clone(), def_params.clone(), BTreeMap::new()); - let header = Header::default(); - - tender.to_commit(header.bare_hash(), good_seal(&header)); - - sleep(Duration::from_millis(def_params.timeouts.commit as u64)); - - match propose_default(&(tender as Arc), 0, Address::default()) { - Err(Error::Engine(EngineError::WrongRound)) => {}, - _ => panic!("Should be EngineError::WrongRound"), - } } } From 340d37793058c47d5095d5e1ff871ed1fb7c4163 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 22 Nov 2016 16:05:27 +0000 Subject: [PATCH 073/295] Revert "dont keep account provider in miner" This reverts commit 11ccacd6d0a9989d07b941a77f7a4e9f5a6f7bb3. --- ethcore/res/tendermint.json | 18 ++--- ethcore/src/engines/authority_round.rs | 6 +- ethcore/src/engines/basic_authority.rs | 2 +- ethcore/src/engines/mod.rs | 6 +- ethcore/src/engines/tendermint/mod.rs | 107 ++++++++++++++++--------- parity/run.rs | 11 ++- 6 files changed, 94 insertions(+), 56 deletions(-) diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index 2f40d707b..ae265aa0b 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -12,16 +12,16 @@ } }, "params": { - "accountStartNonce": "0x0100000", + "accountStartNonce": "0x0", "maximumExtraDataSize": "0x20", "minGasLimit": "0x1388", - "networkID" : "0x69" + "networkID" : "0x2323" }, "genesis": { "seal": { "generic": { - "fields": 1, - "rlp": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa" + "fields": 3, + "rlp": "0x40010" } }, "difficulty": "0x20000", @@ -32,10 +32,10 @@ "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" } + "0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, + "0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, + "0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, + "0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, + "9cce34f7ab185c7aba1b7c8140d620b4bda941d6": { "balance": "1606938044258990275541962092341162602522202993782792835301376" } } } diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index ab2b41aec..e90bc73f2 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -68,9 +68,9 @@ pub struct AuthorityRound { builtins: BTreeMap, transition_service: IoService, message_channel: Mutex>>, - account_provider: Mutex>>, step: AtomicUsize, proposed: AtomicBool, + account_provider: Mutex>>, } fn header_step(header: &Header) -> Result { @@ -102,9 +102,9 @@ impl AuthorityRound { builtins: builtins, transition_service: try!(IoService::::start()), message_channel: Mutex::new(None), - account_provider: Mutex::new(None), step: AtomicUsize::new(initial_step), - proposed: AtomicBool::new(false) + proposed: AtomicBool::new(false), + account_provider: Mutex::new(None), }); let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; try!(engine.transition_service.register_handler(Arc::new(handler))); diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index c43495967..7e0402dab 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -58,7 +58,7 @@ pub struct BasicAuthority { params: CommonParams, our_params: BasicAuthorityParams, builtins: BTreeMap, - account_provider: Mutex>> + account_provider: Mutex>>, } impl BasicAuthority { diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 5aae9ee78..ab50ee9c9 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -174,11 +174,11 @@ pub trait Engine : Sync + Send { /// Add a channel for communication with Client which can be used for sealing. fn register_message_channel(&self, _message_channel: IoChannel) {} + /// Add an account provider useful for Engines that sign stuff. + fn register_account_provider(&self, _account_provider: Arc) {} + /// Check if new block should be chosen as the one in chain. fn is_new_best_block(&self, best_total_difficulty: U256, _best_header: HeaderView, parent_details: &BlockDetails, new_header: &HeaderView) -> bool { ethash::is_new_best_block(best_total_difficulty, parent_details, new_header) } - - /// Add an account provider useful for Engines that sign stuff. - fn register_account_provider(&self, _account_provider: Arc) {} } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 48e3b3599..b110a602d 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -202,26 +202,22 @@ impl Tendermint { } } - fn nonce_proposer(&self, proposer_nonce: usize) -> &Address { - let ref p = self.our_params; - p.authorities.get(proposer_nonce % p.authority_n).expect("There are authority_n authorities; taking number modulo authority_n gives number in authority_n range; qed") - } - - fn is_nonce_proposer(&self, proposer_nonce: usize, address: &Address) -> bool { - self.nonce_proposer(proposer_nonce) == address - } - fn is_authority(&self, address: &Address) -> bool { self.our_params.authorities.contains(address) } - fn threshold(&self) -> usize { - self.our_params.authority_n * 2/3 + fn is_above_threshold(&self, n: usize) -> bool { + n > self.our_params.authority_n * 2/3 } /// Round proposer switching. fn is_proposer(&self, address: &Address) -> bool { - self.is_nonce_proposer(self.proposer_nonce.load(AtomicOrdering::SeqCst), address) + let ref p = self.our_params; + let proposer_nonce = self.proposer_nonce.load(AtomicOrdering::SeqCst); + let proposer = p.authorities.get(proposer_nonce % p.authority_n).expect("There are authority_n authorities; taking number modulo authority_n gives number in authority_n range; qed"); + println!("{:?}", &proposer); + println!("{:?}", &address); + proposer == address } fn is_height(&self, message: &ConsensusMessage) -> bool { @@ -251,16 +247,22 @@ impl Tendermint { fn has_enough_any_votes(&self) -> bool { - self.votes.count_step_votes(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read()) > self.threshold() + let step_votes = self.votes.count_step_votes(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read()); + self.is_above_threshold(step_votes) } fn has_enough_future_step_votes(&self, message: &ConsensusMessage) -> bool { - message.round > self.round.load(AtomicOrdering::SeqCst) - && self.votes.count_step_votes(message.height, message.round, message.step) > self.threshold() + if message.round > self.round.load(AtomicOrdering::SeqCst) { + let step_votes = self.votes.count_step_votes(message.height, message.round, message.step); + self.is_above_threshold(step_votes) + } else { + false + } } fn has_enough_aligned_votes(&self, message: &ConsensusMessage) -> bool { - self.votes.aligned_votes(&message).len() > self.threshold() + let aligned_votes = self.votes.aligned_votes(&message).len(); + self.is_above_threshold(aligned_votes) } } @@ -476,6 +478,13 @@ mod tests { use super::params::TendermintParams; use super::message::*; + fn setup() -> (Spec, Arc) { + let tap = Arc::new(AccountProvider::transient_provider()); + let spec = Spec::new_test_tendermint(); + spec.engine.register_account_provider(tap.clone()); + (spec, tap) + } + fn propose_default(engine: &Arc, db: &StateDB, proposer: Address) -> Option> { let mut header = Header::default(); let last_hashes = Arc::new(vec![]); @@ -489,9 +498,7 @@ mod tests { engine.handle_message(UntrustedRlp::new(&m)).unwrap(); } - fn proposal_seal(header: &Header, round: Round) -> Vec { - let tap = AccountProvider::transient_provider(); - + fn proposal_seal(tap: &Arc, header: &Header, round: Round) -> Vec { let author = header.author(); let vote_info = message_info_rlp(header.number() as Height, round, Step::Propose, Some(header.bare_hash())); let signature = tap.sign(*author, None, vote_info.sha3()).unwrap(); @@ -502,6 +509,12 @@ mod tests { ] } + fn insert_and_unlock(tap: &Arc, acc: &str) -> Address { + let addr = tap.insert_account(acc.sha3(), acc).unwrap(); + tap.unlock_account_permanently(addr, acc.into()).unwrap(); + addr + } + fn default_seal() -> Vec { vec![vec![160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101]] } @@ -544,21 +557,44 @@ mod tests { } #[test] - fn verification_fails_on_wrong_signatures() { - let engine = Spec::new_test_tendermint().engine; - let mut header = Header::default(); - let tap = AccountProvider::transient_provider(); + fn allows_correct_proposer() { + ::env_logger::init().unwrap(); + let (spec, tap) = setup(); + let engine = spec.engine; + + let mut header = Header::default(); + let validator = insert_and_unlock(&tap, "0"); + header.set_author(validator); + let seal = proposal_seal(&tap, &header, 0); + header.set_seal(seal); + // Good proposer. + assert!(engine.verify_block_unordered(&header, None).is_ok()); + + let mut header = Header::default(); + let random = insert_and_unlock(&tap, "101"); + header.set_author(random); + let seal = proposal_seal(&tap, &header, 0); + header.set_seal(seal); + // Bad proposer. + assert!(engine.verify_block_unordered(&header, None).is_err()); + } + + #[test] + fn verification_fails_on_wrong_signatures() { + let (spec, tap) = setup(); + let engine = spec.engine; + let mut header = Header::default(); - let mut seal = Vec::new(); let v1 = tap.insert_account("0".sha3(), "0").unwrap(); - let sig1 = tap.sign(v1, Some("0".into()), header.bare_hash()).unwrap(); - seal.push(::rlp::encode(&(&*sig1 as &[u8])).to_vec()); + + header.set_author(v1); + let mut seal = proposal_seal(&tap, &header, 0); header.set_seal(seal.clone()); // Not enough signatures. - assert!(engine.verify_block_basic(&header, None).is_err()); + assert!(engine.verify_block_unordered(&header, None).is_err()); let v2 = tap.insert_account("101".sha3(), "101").unwrap(); let sig2 = tap.sign(v2, Some("101".into()), header.bare_hash()).unwrap(); @@ -567,7 +603,7 @@ mod tests { header.set_seal(seal); // Enough signatures. - assert!(engine.verify_block_basic(&header, None).is_ok()); + assert!(engine.verify_block_unordered(&header, None).is_ok()); let verify_result = engine.verify_block_unordered(&header, None); @@ -581,10 +617,11 @@ mod tests { #[test] fn seal_with_enough_signatures_is_ok() { - let engine = Spec::new_test_tendermint().engine; + let (spec, tap) = setup(); + let engine = spec.engine; let mut header = Header::default(); - let seal = proposal_seal(&header, 0); + let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Enough signatures. @@ -596,20 +633,18 @@ mod tests { #[test] fn can_generate_seal() { - let spec = Spec::new_test_tendermint(); - let ref engine = *spec.engine; - let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()).unwrap(); + let (spec, _) = setup(); let genesis_header = spec.genesis_header(); let mut db_result = get_temp_state_db(); let mut db = db_result.take(); spec.ensure_db_good(&mut db).unwrap(); let last_hashes = Arc::new(vec![genesis_header.hash()]); - let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); + let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); - let seal = tender.generate_seal(b.block()).unwrap(); - assert!(b.try_seal(engine, seal).is_ok()); + let seal = spec.engine.generate_seal(b.block()).unwrap(); + assert!(b.try_seal(spec.engine.as_ref(), seal).is_ok()); } #[test] diff --git a/parity/run.rs b/parity/run.rs index aae4db748..0f00bba97 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -203,8 +203,14 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { sync_config.fork_block = spec.fork_block(); sync_config.warp_sync = cmd.warp_sync; + // prepare account provider + let account_provider = Arc::new(try!(prepare_account_provider(&cmd.dirs, cmd.acc_conf))); + + // let the Engine access the accounts + spec.engine.register_account_provider(account_provider); + // create miner - let miner = Miner::new(cmd.miner_options, cmd.gas_pricer.into(), &spec); + let miner = Miner::new(cmd.miner_options, cmd.gas_pricer.into(), &spec, Some(account_provider.clone())); miner.set_author(cmd.miner_extras.author); miner.set_gas_floor_target(cmd.miner_extras.gas_floor_target); miner.set_gas_ceil_target(cmd.miner_extras.gas_ceil_target); @@ -238,9 +244,6 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { // create supervisor let mut hypervisor = modules::hypervisor(&cmd.dirs.ipc_path()); - // prepare account provider - let account_provider = Arc::new(try!(prepare_account_provider(&cmd.dirs, cmd.acc_conf))); - // create client service. let service = try!(ClientService::start( client_config, From 8f6a464c5181abc14c60d13dbcdaffca80b2d4b0 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 22 Nov 2016 17:15:22 +0000 Subject: [PATCH 074/295] new error types --- ethcore/src/error.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index 9206447eb..5ba4aa0c2 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -170,6 +170,10 @@ pub enum BlockError { UnknownUncleParent(H256), /// The same author issued different votes at the same step. DoubleVote(H160), + /// The received block is from an incorrect proposer. + NotProposer(H160), + /// Signature does not belong to an authority. + NotAuthority(H160) } impl fmt::Display for BlockError { @@ -204,6 +208,8 @@ impl fmt::Display for BlockError { UnknownParent(ref hash) => format!("Unknown parent: {}", hash), UnknownUncleParent(ref hash) => format!("Unknown uncle parent: {}", hash), DoubleVote(ref address) => format!("Author {} issued too many blocks.", address), + NotProposer(ref address) => format!("Author {} is not a current proposer.", address), + NotAuthority(ref address) => format!("Signer {} is not authorized.", address), }; f.write_fmt(format_args!("Block error ({})", msg)) From d5b15d4560a5d7ed4dc5c3e7e8c3e13212480576 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 22 Nov 2016 17:15:42 +0000 Subject: [PATCH 075/295] change authorities for testing --- ethcore/res/tendermint.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index ae265aa0b..d244b5e69 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -5,8 +5,8 @@ "params": { "gasLimitBoundDivisor": "0x0400", "authorities" : [ - "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", - "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" + "0xff7b8b40a1ec83e2955a0a8a008c73acae282ae7", + "0x4cabfe78ad6c38d87c00046b98c7957f5c523577" ] } } From 207364929c9f82e9cc40bf108ffa28278a2d943b Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 24 Nov 2016 13:55:16 +0000 Subject: [PATCH 076/295] improve error types --- ethcore/src/error.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index 5ba4aa0c2..cadb4fb1f 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -171,9 +171,9 @@ pub enum BlockError { /// The same author issued different votes at the same step. DoubleVote(H160), /// The received block is from an incorrect proposer. - NotProposer(H160), + NotProposer(Mismatch), /// Signature does not belong to an authority. - NotAuthority(H160) + NotAuthorized(H160) } impl fmt::Display for BlockError { @@ -208,8 +208,8 @@ impl fmt::Display for BlockError { UnknownParent(ref hash) => format!("Unknown parent: {}", hash), UnknownUncleParent(ref hash) => format!("Unknown uncle parent: {}", hash), DoubleVote(ref address) => format!("Author {} issued too many blocks.", address), - NotProposer(ref address) => format!("Author {} is not a current proposer.", address), - NotAuthority(ref address) => format!("Signer {} is not authorized.", address), + NotProposer(ref mis) => format!("Author is not a current proposer: {}", mis), + NotAuthorized(ref address) => format!("Signer {} is not authorized.", address), }; f.write_fmt(format_args!("Block error ({})", msg)) From a3730b30421ee0eb6be1a5c66d72e41a6cf47262 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 24 Nov 2016 13:56:27 +0000 Subject: [PATCH 077/295] change proposer address --- ethcore/res/tendermint.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index d244b5e69..94eaa626d 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -5,7 +5,7 @@ "params": { "gasLimitBoundDivisor": "0x0400", "authorities" : [ - "0xff7b8b40a1ec83e2955a0a8a008c73acae282ae7", + "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1", "0x4cabfe78ad6c38d87c00046b98c7957f5c523577" ] } From 38f25fc1952cc186be2b29ffa5c776ea226c0849 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 24 Nov 2016 13:57:04 +0000 Subject: [PATCH 078/295] message tests and fixes --- ethcore/src/engines/tendermint/message.rs | 130 ++++++++++++++++++---- 1 file changed, 109 insertions(+), 21 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index cdcadb5b5..a9a446259 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -18,7 +18,7 @@ use util::*; use super::{Height, Round, BlockHash, Step}; -use error::Error; +use error::{Error, BlockError}; use header::Header; use rlp::*; use ethkey::{recover, public_to_address}; @@ -34,7 +34,8 @@ pub struct ConsensusMessage { fn consensus_round(header: &Header) -> Result { - UntrustedRlp::new(header.seal()[0].as_slice()).as_val() + let round_rlp = header.seal().get(0).expect("seal passed basic verification; seal has 3 fields; qed"); + UntrustedRlp::new(round_rlp.as_slice()).as_val() } impl ConsensusMessage { @@ -80,7 +81,7 @@ impl PartialOrd for ConsensusMessage { } impl Step { - fn number(&self) -> i8 { + fn number(&self) -> u8 { match *self { Step::Propose => 0, Step::Prevote => 1, @@ -107,17 +108,17 @@ impl Ord for ConsensusMessage { impl Decodable for Step { fn decode(decoder: &D) -> Result where D: Decoder { match try!(decoder.as_rlp().as_val()) { - 0u8 => Ok(Step::Prevote), - 1 => Ok(Step::Precommit), + 0u8 => Ok(Step::Propose), + 1 => Ok(Step::Prevote), + 2 => Ok(Step::Precommit), _ => Err(DecoderError::Custom("Invalid step.")), } } } - impl Encodable for Step { fn rlp_append(&self, s: &mut RlpStream) { - s.append(&(self.number() as u8)); + s.append(&self.number()); } } @@ -125,9 +126,6 @@ impl Encodable for Step { impl Decodable for ConsensusMessage { fn decode(decoder: &D) -> Result where D: Decoder { let rlp = decoder.as_rlp(); - if decoder.as_raw().len() != try!(rlp.payload_info()).total() { - return Err(DecoderError::RlpIsTooBig); - } let m = try!(rlp.at(1)); let block_message: H256 = try!(m.val_at(3)); Ok(ConsensusMessage { @@ -141,23 +139,25 @@ impl Decodable for ConsensusMessage { } }) } -} - +} impl Encodable for ConsensusMessage { fn rlp_append(&self, s: &mut RlpStream) { - s.begin_list(2); - s.append(&self.signature); - s.begin_list(4); - s.append(&self.height); - s.append(&self.round); - s.append(&self.step); - s.append(&self.block_hash.unwrap_or(H256::zero())); + s.begin_list(2) + .append(&self.signature) + // TODO: figure out whats wrong with nested list encoding + .begin_list(5) + .append(&self.height) + .append(&self.round) + .append(&self.step) + .append(&self.block_hash.unwrap_or(H256::zero())); } } pub fn message_info_rlp(height: Height, round: Round, step: Step, block_hash: Option) -> Bytes { - let mut s = RlpStream::new_list(4); + // TODO: figure out whats wrong with nested list encoding + let mut s = RlpStream::new_list(5); s.append(&height).append(&round).append(&step).append(&block_hash.unwrap_or(H256::zero())); + println!("{:?}, {:?}, {:?}, {:?}", &height, &round, &step, &block_hash); s.out() } @@ -170,7 +170,95 @@ pub fn message_full_rlp(signer: F, height: Height, round: Round, step: Step, let vote_info = message_info_rlp(height, round, step, block_hash); signer(vote_info.sha3()).map(|ref signature| { let mut s = RlpStream::new_list(2); - s.append(signature).append(&vote_info); + s.append(signature).append_raw(&vote_info, 1); s.out() }) } + +#[cfg(test)] +mod tests { + use util::*; + use rlp::*; + use super::super::Step; + use super::*; + use account_provider::AccountProvider; + use header::Header; + + #[test] + fn encode_decode() { + let message = ConsensusMessage { + signature: H520::default(), + height: 10, + round: 123, + step: Step::Precommit, + block_hash: Some("1".sha3()) + }; + let raw_rlp = ::rlp::encode(&message).to_vec(); + let rlp = Rlp::new(&raw_rlp); + assert_eq!(message, rlp.as_val()); + + let message = ConsensusMessage { + signature: H520::default(), + height: 1314, + round: 0, + step: Step::Prevote, + block_hash: None + }; + let raw_rlp = ::rlp::encode(&message); + let rlp = Rlp::new(&raw_rlp); + assert_eq!(message, rlp.as_val()); + } + + #[test] + fn generate_and_verify() { + let tap = Arc::new(AccountProvider::transient_provider()); + let addr = tap.insert_account("0".sha3(), "0").unwrap(); + tap.unlock_account_permanently(addr, "0".into()).unwrap(); + + let raw_rlp = message_full_rlp( + |mh| tap.sign(addr, None, mh).ok().map(H520::from), + 123, + 2, + Step::Precommit, + Some(H256::default()) + ).unwrap(); + + let rlp = UntrustedRlp::new(&raw_rlp); + let message: ConsensusMessage = rlp.as_val().unwrap(); + match message.verify() { Ok(a) if a == addr => {}, _ => panic!(), }; + } + + #[test] + fn proposal_message() { + let mut header = Header::default(); + let seal = vec![ + ::rlp::encode(&0u8).to_vec(), + ::rlp::encode(&H520::default()).to_vec(), + Vec::new() + ]; + header.set_seal(seal); + let message = ConsensusMessage::new_proposal(&header).unwrap(); + assert_eq!( + message, + ConsensusMessage { + signature: Default::default(), + height: 0, + round: 0, + step: Step::Propose, + block_hash: Some(header.bare_hash()) + } + ); + } + + #[test] + fn message_info_from_header() { + let mut header = Header::default(); + let seal = vec![ + ::rlp::encode(&0u8).to_vec(), + ::rlp::encode(&H520::default()).to_vec(), + Vec::new() + ]; + header.set_seal(seal); + assert_eq!(message_info_rlp_from_header(&header).unwrap().to_vec(), vec![228, 128, 128, 2, 160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101]); + } +} From 8f37807d4ba6a2679cd0002623e0e6d3abd0f588 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 24 Nov 2016 13:57:54 +0000 Subject: [PATCH 079/295] seal checks --- ethcore/src/engines/tendermint/mod.rs | 118 +++++++++++++------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index b110a602d..eb5975d9a 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -211,13 +211,15 @@ impl Tendermint { } /// Round proposer switching. - fn is_proposer(&self, address: &Address) -> bool { + fn is_proposer(&self, address: &Address) -> Result<(), BlockError> { let ref p = self.our_params; let proposer_nonce = self.proposer_nonce.load(AtomicOrdering::SeqCst); let proposer = p.authorities.get(proposer_nonce % p.authority_n).expect("There are authority_n authorities; taking number modulo authority_n gives number in authority_n range; qed"); - println!("{:?}", &proposer); - println!("{:?}", &address); - proposer == address + if proposer == address { + Ok(()) + } else { + Err(BlockError::NotProposer(Mismatch { expected: proposer.clone(), found: address.clone() })) + } } fn is_height(&self, message: &ConsensusMessage) -> bool { @@ -310,7 +312,7 @@ impl Engine for Tendermint { /// Round proposer switching. fn is_sealer(&self, address: &Address) -> Option { - Some(self.is_proposer(address)) + Some(self.is_proposer(address).is_ok()) } /// Attempt to seal the block internally using all available signatures. @@ -318,6 +320,7 @@ impl Engine for Tendermint { if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let author = header.author(); + println!("author: {:?}", author); let vote_info = message_info_rlp(header.number() as Height, self.round.load(AtomicOrdering::SeqCst), Step::Propose, Some(header.bare_hash())); if let Ok(signature) = ap.sign(*author, None, vote_info.sha3()) { *self.proposal.write() = Some(header.bare_hash()); @@ -341,7 +344,7 @@ impl Engine for Tendermint { let sender = public_to_address(&try!(recover(&message.signature.into(), &try!(rlp.at(1)).as_raw().sha3()))); // TODO: Do not admit old messages. if !self.is_authority(&sender) { - try!(Err(BlockError::InvalidSeal)); + try!(Err(BlockError::NotAuthorized(sender))); } // Check if the message is known. @@ -403,17 +406,22 @@ impl Engine for Tendermint { fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { let proposal = try!(ConsensusMessage::new_proposal(header)); let proposer = try!(proposal.verify()); - if !self.is_proposer(&proposer) { - try!(Err(BlockError::InvalidSeal)) - } + try!(self.is_proposer(&proposer)); self.votes.vote(proposal, proposer); let block_info_hash = try!(message_info_rlp_from_header(header)).sha3(); + + let mut signature_count = 0; for rlp in UntrustedRlp::new(&header.seal()[2]).iter() { let signature: H520 = try!(rlp.as_val()); let address = public_to_address(&try!(recover(&signature.into(), &block_info_hash))); if !self.our_params.authorities.contains(&address) { - try!(Err(BlockError::InvalidSeal)) + try!(Err(BlockError::NotAuthorized(address))) } + + signature_count += 1; + } + if signature_count > self.our_params.authority_n { + try!(Err(BlockError::InvalidSealArity(Mismatch { expected: self.our_params.authority_n, found: signature_count }))) } Ok(()) } @@ -478,6 +486,7 @@ mod tests { use super::params::TendermintParams; use super::message::*; + /// Accounts inserted with "1" and "2" are authorities. First proposer is "0". fn setup() -> (Spec, Arc) { let tap = Arc::new(AccountProvider::transient_provider()); let spec = Spec::new_test_tendermint(); @@ -500,6 +509,7 @@ mod tests { fn proposal_seal(tap: &Arc, header: &Header, round: Round) -> Vec { let author = header.author(); + println!("author: {:?}", author); let vote_info = message_info_rlp(header.number() as Height, round, Step::Propose, Some(header.bare_hash())); let signature = tap.sign(*author, None, vote_info.sha3()).unwrap(); vec![ @@ -545,7 +555,7 @@ mod tests { #[test] fn verification_fails_on_short_seal() { let engine = Spec::new_test_tendermint().engine; - let header: Header = Header::default(); + let header = Header::default(); let verify_result = engine.verify_block_basic(&header, None); @@ -558,16 +568,17 @@ mod tests { #[test] fn allows_correct_proposer() { - ::env_logger::init().unwrap(); let (spec, tap) = setup(); let engine = spec.engine; let mut header = Header::default(); let validator = insert_and_unlock(&tap, "0"); + println!("validator: {:?}", &validator); header.set_author(validator); let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Good proposer. + println!("{:?}", engine.verify_block_unordered(&header, None)); assert!(engine.verify_block_unordered(&header, None).is_ok()); let mut header = Header::default(); @@ -576,75 +587,64 @@ mod tests { let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Bad proposer. - assert!(engine.verify_block_unordered(&header, None).is_err()); - } - - #[test] - fn verification_fails_on_wrong_signatures() { - let (spec, tap) = setup(); - let engine = spec.engine; - let mut header = Header::default(); - - - let v1 = tap.insert_account("0".sha3(), "0").unwrap(); - - header.set_author(v1); - let mut seal = proposal_seal(&tap, &header, 0); - - header.set_seal(seal.clone()); - - // Not enough signatures. - assert!(engine.verify_block_unordered(&header, None).is_err()); - - let v2 = tap.insert_account("101".sha3(), "101").unwrap(); - let sig2 = tap.sign(v2, Some("101".into()), header.bare_hash()).unwrap(); - seal.push(::rlp::encode(&(&*sig2 as &[u8])).to_vec()); - - header.set_seal(seal); - - // Enough signatures. - assert!(engine.verify_block_unordered(&header, None).is_ok()); - - let verify_result = engine.verify_block_unordered(&header, None); - - // But wrong signatures. - match verify_result { - Err(Error::Block(BlockError::InvalidSeal)) => (), - Err(_) => panic!("should be block seal-arity mismatch error (got {:?})", verify_result), - _ => panic!("Should be error, got Ok"), + match engine.verify_block_unordered(&header, None) { + Err(Error::Block(BlockError::NotProposer(_))) => {}, + _ => panic!(), } } #[test] - fn seal_with_enough_signatures_is_ok() { + fn seal_signatures_checking() { let (spec, tap) = setup(); let engine = spec.engine; - let mut header = Header::default(); - let seal = proposal_seal(&tap, &header, 0); + let mut header = Header::default(); + let proposer = insert_and_unlock(&tap, "0"); + header.set_author(proposer); + let mut seal = proposal_seal(&tap, &header, 0); + + let voter = insert_and_unlock(&tap, "1"); + let vote_info = message_info_rlp(0, 0, Step::Prevote, Some(header.bare_hash())); + let signature = tap.sign(voter, None, vote_info.sha3()).unwrap(); + + seal[2] = ::rlp::encode(&vec![H520::from(signature)]).to_vec(); + + header.set_seal(seal.clone()); + + // One good signature. + assert!(engine.verify_block_unordered(&header, None).is_ok()); + + let bad_voter = insert_and_unlock(&tap, "101"); + let bad_signature = tap.sign(bad_voter, None, vote_info.sha3()).unwrap(); + seal.push(::rlp::encode(&(&*bad_signature as &[u8])).to_vec()); + header.set_seal(seal); - // Enough signatures. - assert!(engine.verify_block_basic(&header, None).is_ok()); - - // And they are ok. - assert!(engine.verify_block_unordered(&header, None).is_ok()); + // One good and one bad signature. + match engine.verify_block_unordered(&header, None) { + Err(Error::Block(BlockError::NotAuthorized(_))) => {}, + _ => panic!(), + } } #[test] fn can_generate_seal() { - let (spec, _) = setup(); + let (spec, tap) = setup(); let genesis_header = spec.genesis_header(); let mut db_result = get_temp_state_db(); let mut db = db_result.take(); spec.ensure_db_good(&mut db).unwrap(); let last_hashes = Arc::new(vec![genesis_header.hash()]); - let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); + let validator = insert_and_unlock(&tap, "0"); + let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db, &genesis_header, last_hashes, validator, (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); let seal = spec.engine.generate_seal(b.block()).unwrap(); - assert!(b.try_seal(spec.engine.as_ref(), seal).is_ok()); + println!("{:?}", seal.clone()); + if let Err(e) = b.try_seal(spec.engine.as_ref(), seal) { + println!("{:?}", e.0); + } } #[test] From 04acdd6ca0afa7119653487d6673f7020573fb72 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 24 Nov 2016 19:57:58 +0000 Subject: [PATCH 080/295] reuse rlp generation --- ethcore/src/engines/tendermint/message.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index a9a446259..df671f82b 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -18,7 +18,7 @@ use util::*; use super::{Height, Round, BlockHash, Step}; -use error::{Error, BlockError}; +use error::Error; use header::Header; use rlp::*; use ethkey::{recover, public_to_address}; @@ -49,7 +49,6 @@ impl ConsensusMessage { }) } - pub fn is_height(&self, height: Height) -> bool { self.height == height } @@ -140,16 +139,13 @@ impl Decodable for ConsensusMessage { }) } } + impl Encodable for ConsensusMessage { fn rlp_append(&self, s: &mut RlpStream) { + let info = message_info_rlp(self.height, self.round, self.step, self.block_hash); s.begin_list(2) .append(&self.signature) - // TODO: figure out whats wrong with nested list encoding - .begin_list(5) - .append(&self.height) - .append(&self.round) - .append(&self.step) - .append(&self.block_hash.unwrap_or(H256::zero())); + .append_raw(&info, 1); } } From f867372dfee89d4cf6a365f559a86c64f6538e82 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 24 Nov 2016 19:58:29 +0000 Subject: [PATCH 081/295] increase default proposal time --- ethcore/src/engines/tendermint/transition.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index a5cb02763..a3f2e7b46 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -48,7 +48,7 @@ impl TendermintTimeouts { impl Default for TendermintTimeouts { fn default() -> Self { TendermintTimeouts { - propose: Duration::milliseconds(1000), + propose: Duration::milliseconds(2000), prevote: Duration::milliseconds(1000), precommit: Duration::milliseconds(1000), commit: Duration::milliseconds(1000) From da499b0a4ac0f07c8a176267cf6e8b627125db3e Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 24 Nov 2016 19:59:08 +0000 Subject: [PATCH 082/295] self contained test proposal --- ethcore/res/tendermint.json | 2 +- ethcore/src/engines/tendermint/mod.rs | 109 +++++++++++++------------- 2 files changed, 56 insertions(+), 55 deletions(-) diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index 94eaa626d..e411d54e2 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -6,7 +6,7 @@ "gasLimitBoundDivisor": "0x0400", "authorities" : [ "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1", - "0x4cabfe78ad6c38d87c00046b98c7957f5c523577" + "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e" ] } } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index eb5975d9a..d4e756779 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -320,7 +320,6 @@ impl Engine for Tendermint { if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let author = header.author(); - println!("author: {:?}", author); let vote_info = message_info_rlp(header.number() as Height, self.round.load(AtomicOrdering::SeqCst), Step::Propose, Some(header.bare_hash())); if let Ok(signature) = ap.sign(*author, None, vote_info.sha3()) { *self.proposal.write() = Some(header.bare_hash()); @@ -472,7 +471,8 @@ impl Engine for Tendermint { #[cfg(test)] mod tests { use util::*; - use rlp::{UntrustedRlp, RlpStream, Stream, View}; + use rlp::{UntrustedRlp, View}; + use io::{IoContext, IoHandler}; use block::*; use state_db::StateDB; use error::{Error, BlockError}; @@ -480,6 +480,8 @@ mod tests { use env_info::EnvInfo; use tests::helpers::*; use account_provider::AccountProvider; + use io::IoService; + use service::ClientIoMessage; use spec::Spec; use engines::{Engine, EngineError}; use super::*; @@ -494,12 +496,16 @@ mod tests { (spec, tap) } - fn propose_default(engine: &Arc, db: &StateDB, proposer: Address) -> Option> { - let mut header = Header::default(); - let last_hashes = Arc::new(vec![]); - let b = OpenBlock::new(engine.as_ref(), Default::default(), false, db.boxed_clone(), &header, last_hashes, proposer, (3141562.into(), 31415620.into()), vec![]).unwrap(); + fn propose_default(spec: &Spec, proposer: Address) -> (LockedBlock, Vec) { + let mut db_result = get_temp_state_db(); + let mut db = db_result.take(); + spec.ensure_db_good(&mut db).unwrap(); + let genesis_header = spec.genesis_header(); + let last_hashes = Arc::new(vec![genesis_header.hash()]); + let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db.boxed_clone(), &genesis_header, last_hashes, proposer, (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); - engine.generate_seal(b.block()) + let seal = spec.engine.generate_seal(b.block()).unwrap(); + (b, seal) } fn vote_default(engine: &Arc, signer: F, height: usize, round: usize, step: Step) where F: FnOnce(H256) -> Option { @@ -525,8 +531,17 @@ mod tests { addr } - fn default_seal() -> Vec { - vec![vec![160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101]] + struct TestIo; + + impl IoHandler for TestIo { + fn message(&self, _io: &IoContext, net_message: &ClientIoMessage) { + match *net_message { + ClientIoMessage::UpdateSealing => {}, + ClientIoMessage::SubmitSeal(ref block_hash, ref seal) => {}, + ClientIoMessage::BroadcastMessage(ref message) => {}, + _ => {} // ignore other messages + } + } } #[test] @@ -604,19 +619,21 @@ mod tests { let mut seal = proposal_seal(&tap, &header, 0); let voter = insert_and_unlock(&tap, "1"); - let vote_info = message_info_rlp(0, 0, Step::Prevote, Some(header.bare_hash())); + println!("voter: {:?}", &voter); + let vote_info = message_info_rlp(0, 0, Step::Precommit, Some(header.bare_hash())); let signature = tap.sign(voter, None, vote_info.sha3()).unwrap(); - seal[2] = ::rlp::encode(&vec![H520::from(signature)]).to_vec(); + seal[2] = ::rlp::encode(&vec![H520::from(signature.clone())]).to_vec(); header.set_seal(seal.clone()); + println!("{:?}", engine.verify_block_unordered(&header, None)); // One good signature. assert!(engine.verify_block_unordered(&header, None).is_ok()); let bad_voter = insert_and_unlock(&tap, "101"); let bad_signature = tap.sign(bad_voter, None, vote_info.sha3()).unwrap(); - seal.push(::rlp::encode(&(&*bad_signature as &[u8])).to_vec()); + seal[2] = ::rlp::encode(&vec![H520::from(signature), H520::from(bad_signature)]).to_vec(); header.set_seal(seal); @@ -630,59 +647,30 @@ mod tests { #[test] fn can_generate_seal() { let (spec, tap) = setup(); + + let proposer = insert_and_unlock(&tap, "0"); - let genesis_header = spec.genesis_header(); - let mut db_result = get_temp_state_db(); - let mut db = db_result.take(); - spec.ensure_db_good(&mut db).unwrap(); - let last_hashes = Arc::new(vec![genesis_header.hash()]); - let validator = insert_and_unlock(&tap, "0"); - let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db, &genesis_header, last_hashes, validator, (3141562.into(), 31415620.into()), vec![]).unwrap(); - let b = b.close_and_lock(); - - let seal = spec.engine.generate_seal(b.block()).unwrap(); - println!("{:?}", seal.clone()); - if let Err(e) = b.try_seal(spec.engine.as_ref(), seal) { - println!("{:?}", e.0); - } - } - - #[test] - fn propose_step() { - let spec = Spec::new_test_tendermint(); - let engine = spec.engine.clone(); - let tap = AccountProvider::transient_provider(); - let mut db_result = get_temp_state_db(); - let mut db = db_result.take(); - spec.ensure_db_good(&mut db).unwrap(); - - let not_authority_addr = tap.insert_account("101".sha3(), "101").unwrap(); - assert!(propose_default(&engine, &db, not_authority_addr).is_none()); - - let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap(); - assert!(propose_default(&engine, &db, not_proposer_addr).is_none()); - - let proposer_addr = tap.insert_account("1".sha3(), "1").unwrap(); - assert_eq!(default_seal(), propose_default(&engine, &db, proposer_addr).unwrap()); - - assert!(propose_default(&engine, &db, proposer_addr).is_none()); - assert!(propose_default(&engine, &db, not_proposer_addr).is_none()); + let (b, seal) = propose_default(&spec, proposer); + assert!(b.try_seal(spec.engine.as_ref(), seal).is_ok()); } #[test] fn prevote_step() { - let spec = Spec::new_test_tendermint(); - let engine = Spec::new_test_tendermint().engine; - let tap = AccountProvider::transient_provider(); + let (spec, tap) = setup(); + let engine = spec.engine.clone(); let mut db_result = get_temp_state_db(); let mut db = db_result.take(); spec.ensure_db_good(&mut db).unwrap(); + + let io_service = IoService::::start().unwrap(); + io_service.register_handler(Arc::new(TestIo)); + engine.register_message_channel(io_service.channel()); - let v0 = tap.insert_account("0".sha3(), "0").unwrap(); - let v1 = tap.insert_account("1".sha3(), "1").unwrap(); + let v0 = insert_and_unlock(&tap, "0"); + let v1 = insert_and_unlock(&tap, "1"); // Propose - assert!(propose_default(&engine, &db, v1.clone()).is_some()); + propose_default(&spec, v0.clone()); let h = 0; let r = 0; @@ -694,6 +682,19 @@ mod tests { vote_default(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Prevote); } + #[test] + fn precommit_step() { + let (spec, tap) = setup(); + let engine = spec.engine.clone(); + + let not_authority_addr = insert_and_unlock(&tap, "101"); + let proposer_addr = insert_and_unlock(&tap, "0"); + propose_default(&spec, proposer_addr); + + let not_proposer_addr = insert_and_unlock(&tap, "1"); + + } + #[test] fn timeout_switching() { let tender = { From a143da2cb8f3790d9aeaa85f84f83dcc3bf0b7ed Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 25 Nov 2016 11:36:25 +0000 Subject: [PATCH 083/295] fix complete build --- parity/run.rs | 2 +- sync/src/api.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/parity/run.rs b/parity/run.rs index 5ae667bdc..f860e414d 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -209,7 +209,7 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { let account_provider = Arc::new(try!(prepare_account_provider(&cmd.dirs, cmd.acc_conf))); // let the Engine access the accounts - spec.engine.register_account_provider(account_provider); + spec.engine.register_account_provider(account_provider.clone()); // create miner let miner = Miner::new(cmd.miner_options, cmd.gas_pricer.into(), &spec, Some(account_provider.clone())); diff --git a/sync/src/api.rs b/sync/src/api.rs index c85c5ebb0..8d7d08037 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -180,7 +180,7 @@ impl SyncProvider for EthSync { } fn transactions_stats(&self) -> BTreeMap { - let sync = self.handler.sync.read(); + let sync = self.eth_handler.sync.read(); sync.transactions_stats() .iter() .map(|(hash, stats)| (*hash, stats.into())) From d2099d9f13f742408bea20c95a4e9df144d97c3e Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 25 Nov 2016 16:43:26 +0000 Subject: [PATCH 084/295] derive Eq for tests --- ethcore/src/service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index 1962bec5f..20a5587e0 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -32,7 +32,7 @@ use nanoipc; use client::BlockChainClient; /// Message type for external and internal events -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] pub enum ClientIoMessage { /// Best Block Hash in chain has been changed NewChainHead, From f59746b2da3b354e228524a0249aecf456e4bc10 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 25 Nov 2016 16:44:18 +0000 Subject: [PATCH 085/295] order messages by signature --- ethcore/src/engines/tendermint/message.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index df671f82b..c8af7c9d8 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -99,7 +99,7 @@ impl Ord for ConsensusMessage { } else if self.step != m.step { self.step.number().cmp(&m.step.number()) } else { - self.block_hash.cmp(&m.block_hash) + self.signature.cmp(&m.signature) } } } From 8f72017bccf76398669ad4d55a197c99d0c4ccf8 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 25 Nov 2016 16:44:57 +0000 Subject: [PATCH 086/295] add transition tracing --- ethcore/src/engines/tendermint/transition.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index a3f2e7b46..ace5661b6 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -76,19 +76,23 @@ impl IoHandler for TransitionHandler { if let Some(engine) = self.engine.upgrade() { let next_step = match *engine.step.read() { Step::Propose => { + trace!(target: "poa", "timeout: Propose timeout."); set_timeout(io, engine.our_params.timeouts.prevote); Some(Step::Prevote) }, Step::Prevote if engine.has_enough_any_votes() => { + trace!(target: "poa", "timeout: Prevote timeout."); set_timeout(io, engine.our_params.timeouts.precommit); Some(Step::Precommit) }, Step::Precommit if engine.has_enough_any_votes() => { + trace!(target: "poa", "timeout: Precommit timeout."); set_timeout(io, engine.our_params.timeouts.propose); engine.increment_round(1); Some(Step::Propose) }, Step::Commit => { + trace!(target: "poa", "timeout: Commit timeout."); set_timeout(io, engine.our_params.timeouts.propose); engine.reset_round(); Some(Step::Propose) From a7afbf4d25bf76e1a5cfa0dadfee10de0efd3c27 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 25 Nov 2016 16:45:32 +0000 Subject: [PATCH 087/295] tracing and vote test --- ethcore/src/engines/tendermint/mod.rs | 46 ++++++++++++++++----------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index d4e756779..f57c56093 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -140,8 +140,6 @@ impl Tendermint { Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), } } - } else { - warn!(target: "poa", "broadcast_message: Message could not be generated."); } } @@ -155,6 +153,7 @@ impl Tendermint { block_hash ) } else { + warn!(target: "poa", "generate_message: No AccountProvider available."); None } } @@ -163,10 +162,12 @@ impl Tendermint { *self.step.write() = step; match step { Step::Propose => { + trace!(target: "poa", "to_step: Transitioning to Propose."); *self.proposal.write() = None; self.update_sealing() }, Step::Prevote => { + trace!(target: "poa", "to_step: Transitioning to Prevote."); let block_hash = match *self.lock_change.read() { Some(ref m) if self.should_unlock(m.round) => self.proposal.read().clone(), Some(ref m) => m.block_hash, @@ -175,6 +176,7 @@ impl Tendermint { self.broadcast_message(block_hash) }, Step::Precommit => { + trace!(target: "poa", "to_step: Transitioning to Precommit."); let block_hash = match *self.lock_change.read() { Some(ref m) if self.is_round(m) => { self.last_lock.store(m.round, AtomicOrdering::SeqCst); @@ -185,6 +187,7 @@ impl Tendermint { self.broadcast_message(block_hash); }, Step::Commit => { + trace!(target: "poa", "to_step: Transitioning to Commit."); // Commit the block using a complete signature set. let round = self.round.load(AtomicOrdering::SeqCst); if let Some(seal) = self.votes.seal_signatures(self.height.load(AtomicOrdering::SeqCst), round, *self.proposal.read()) { @@ -348,6 +351,7 @@ impl Engine for Tendermint { // Check if the message is known. if self.votes.vote(message.clone(), sender).is_none() { + trace!(target: "poa", "handle_message: Processing new authorized message: {:?}", &message); let is_newer_than_lock = match *self.lock_change.read() { Some(ref lock) => &message > lock, None => true, @@ -355,6 +359,7 @@ impl Engine for Tendermint { if is_newer_than_lock && message.step == Step::Prevote && self.has_enough_aligned_votes(&message) { + trace!(target: "poa", "handle_message: Lock change."); *self.lock_change.write() = Some(message.clone()); } // Check if it can affect the step transition. @@ -409,6 +414,7 @@ impl Engine for Tendermint { self.votes.vote(proposal, proposer); let block_info_hash = try!(message_info_rlp_from_header(header)).sha3(); + // TODO: use addresses recovered during precommit vote let mut signature_count = 0; for rlp in UntrustedRlp::new(&header.seal()[2]).iter() { let signature: H520 = try!(rlp.as_val()); @@ -508,8 +514,8 @@ mod tests { (b, seal) } - fn vote_default(engine: &Arc, signer: F, height: usize, round: usize, step: Step) where F: FnOnce(H256) -> Option { - let m = message_full_rlp(signer, height, round, step, Some(Default::default())).unwrap(); + fn vote(engine: &Arc, signer: F, height: usize, round: usize, step: Step, block_hash: Option) where F: FnOnce(H256) -> Option { + let m = message_full_rlp(signer, height, round, step, block_hash).unwrap(); engine.handle_message(UntrustedRlp::new(&m)).unwrap(); } @@ -531,15 +537,13 @@ mod tests { addr } - struct TestIo; + struct TestIo(ClientIoMessage); impl IoHandler for TestIo { fn message(&self, _io: &IoContext, net_message: &ClientIoMessage) { - match *net_message { - ClientIoMessage::UpdateSealing => {}, - ClientIoMessage::SubmitSeal(ref block_hash, ref seal) => {}, - ClientIoMessage::BroadcastMessage(ref message) => {}, - _ => {} // ignore other messages + let TestIo(ref expected) = *self; + if net_message == expected { + panic!() } } } @@ -655,31 +659,35 @@ mod tests { } #[test] + #[should_panic] fn prevote_step() { + ::env_logger::init().unwrap(); let (spec, tap) = setup(); let engine = spec.engine.clone(); let mut db_result = get_temp_state_db(); let mut db = db_result.take(); spec.ensure_db_good(&mut db).unwrap(); - let io_service = IoService::::start().unwrap(); - io_service.register_handler(Arc::new(TestIo)); - engine.register_message_channel(io_service.channel()); - let v0 = insert_and_unlock(&tap, "0"); let v1 = insert_and_unlock(&tap, "1"); // Propose - propose_default(&spec, v0.clone()); + let (b, seal) = propose_default(&spec, v0.clone()); + let proposal = Some(b.header().bare_hash()); - let h = 0; + // Register IoHandler that panics on correct message. + let io_service = IoService::::start().unwrap(); + io_service.register_handler(Arc::new(TestIo(ClientIoMessage::SubmitSeal(Default::default(), seal)))).unwrap(); + engine.register_message_channel(io_service.channel()); + + let h = 1; let r = 0; // Prevote. - vote_default(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Prevote); + vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Prevote, proposal); - vote_default(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Prevote); - vote_default(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Prevote); + vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Prevote, proposal); + vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Prevote, proposal); } #[test] From 89f0bd714d16533105f180a51962193c5098fc36 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 28 Nov 2016 09:42:50 +0000 Subject: [PATCH 088/295] test whole transitioning --- ethcore/src/engines/tendermint/mod.rs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index f57c56093..2777093eb 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -98,7 +98,7 @@ impl Tendermint { builtins: builtins, step_service: try!(IoService::::start()), authority: RwLock::new(Address::default()), - height: AtomicUsize::new(0), + height: AtomicUsize::new(1), round: AtomicUsize::new(0), step: RwLock::new(Step::Propose), proposer_nonce: AtomicUsize::new(0), @@ -386,6 +386,7 @@ impl Engine for Tendermint { }; if let Some(step) = next_step { + trace!(target: "poa", "handle_message: Transition triggered."); if let Err(io_err) = self.step_service.send_message(step) { warn!(target: "poa", "Could not proceed to next step {}.", io_err) } @@ -531,6 +532,14 @@ mod tests { ] } + fn precommit_signatures(tap: &Arc, height: Height, round: Round, bare_hash: Option) -> Bytes { + let vote_info = message_info_rlp(height, round, Step::Precommit, bare_hash); + ::rlp::encode(&vec![ + tap.sign("0".sha3(), Some("0"), vote_info.sha3()).unwrap(), + tap.sign("1".sha3(), Some("1"), vote_info.sha3()).unwrap() + ]).to_vec() + } + fn insert_and_unlock(tap: &Arc, acc: &str) -> Address { let addr = tap.insert_account(acc.sha3(), acc).unwrap(); tap.unlock_account_permanently(addr, acc.into()).unwrap(); @@ -660,7 +669,7 @@ mod tests { #[test] #[should_panic] - fn prevote_step() { + fn step_transitioning() { ::env_logger::init().unwrap(); let (spec, tap) = setup(); let engine = spec.engine.clone(); @@ -671,23 +680,25 @@ mod tests { let v0 = insert_and_unlock(&tap, "0"); let v1 = insert_and_unlock(&tap, "1"); + let h = 1; + let r = 0; + // Propose - let (b, seal) = propose_default(&spec, v0.clone()); + let (b, mut seal) = propose_default(&spec, v0.clone()); let proposal = Some(b.header().bare_hash()); // Register IoHandler that panics on correct message. + seal[2] = precommit_signatures(&tap, h, r, b.header().bare_hash()); let io_service = IoService::::start().unwrap(); - io_service.register_handler(Arc::new(TestIo(ClientIoMessage::SubmitSeal(Default::default(), seal)))).unwrap(); + io_service.register_handler(Arc::new(TestIo(ClientIoMessage::SubmitSeal(proposal, seal)))).unwrap(); engine.register_message_channel(io_service.channel()); - let h = 1; - let r = 0; - // Prevote. vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Prevote, proposal); vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Prevote, proposal); - vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Prevote, proposal); + vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); + vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); } #[test] From 09c28806d6b4c5539ed2e372d139faf82b72df0e Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 28 Nov 2016 11:47:33 +0000 Subject: [PATCH 089/295] proper test IoHandler --- ethcore/src/engines/tendermint/mod.rs | 29 +++++++++++++++++---------- ethcore/src/service.rs | 2 +- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 2777093eb..04519aac0 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -532,11 +532,11 @@ mod tests { ] } - fn precommit_signatures(tap: &Arc, height: Height, round: Round, bare_hash: Option) -> Bytes { + fn precommit_signatures(tap: &Arc, height: Height, round: Round, bare_hash: Option, v1: H160, v2: H160) -> Bytes { let vote_info = message_info_rlp(height, round, Step::Precommit, bare_hash); ::rlp::encode(&vec![ - tap.sign("0".sha3(), Some("0"), vote_info.sha3()).unwrap(), - tap.sign("1".sha3(), Some("1"), vote_info.sha3()).unwrap() + H520::from(tap.sign(v1, None, vote_info.sha3()).unwrap()), + H520::from(tap.sign(v2, None, vote_info.sha3()).unwrap()) ]).to_vec() } @@ -546,14 +546,17 @@ mod tests { addr } - struct TestIo(ClientIoMessage); + struct TestIo { + received: Mutex> + } + + impl TestIo { + fn new() -> Arc { Arc::new(TestIo { received: Mutex::new(None) }) } + } impl IoHandler for TestIo { fn message(&self, _io: &IoContext, net_message: &ClientIoMessage) { - let TestIo(ref expected) = *self; - if net_message == expected { - panic!() - } + *self.received.lock() = Some(net_message.clone()); } } @@ -687,10 +690,11 @@ mod tests { let (b, mut seal) = propose_default(&spec, v0.clone()); let proposal = Some(b.header().bare_hash()); - // Register IoHandler that panics on correct message. - seal[2] = precommit_signatures(&tap, h, r, b.header().bare_hash()); + // Register IoHandler remembers messages. + seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v0, v1); let io_service = IoService::::start().unwrap(); - io_service.register_handler(Arc::new(TestIo(ClientIoMessage::SubmitSeal(proposal, seal)))).unwrap(); + let test_io = TestIo::new(); + io_service.register_handler(test_io.clone()).unwrap(); engine.register_message_channel(io_service.channel()); // Prevote. @@ -699,6 +703,9 @@ mod tests { vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Prevote, proposal); vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); + + ::std::thread::sleep(::std::time::Duration::from_millis(40)); + assert_eq!(*test_io.received.lock(), Some(ClientIoMessage::SubmitSeal(proposal.unwrap(), seal))); } #[test] diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index 20a5587e0..b3e1e0d51 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -32,7 +32,7 @@ use nanoipc; use client::BlockChainClient; /// Message type for external and internal events -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq, Debug)] pub enum ClientIoMessage { /// Best Block Hash in chain has been changed NewChainHead, From ef4ecce7bfc766abd2f6c9f407d2e7f06258e0ed Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 28 Nov 2016 14:08:38 +0000 Subject: [PATCH 090/295] nicer vote counting + test --- ethcore/src/engines/tendermint/message.rs | 8 +- ethcore/src/engines/tendermint/mod.rs | 6 +- .../src/engines/tendermint/vote_collector.rs | 92 +++++++++++++++---- 3 files changed, 85 insertions(+), 21 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index c8af7c9d8..7f6e60754 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -61,8 +61,12 @@ impl ConsensusMessage { self.height == height && self.round == round && self.step == step } - pub fn is_aligned(&self, height: Height, round: Round, block_hash: Option) -> bool { - self.height == height && self.round == round && self.block_hash == block_hash + pub fn is_block_hash(&self, h: Height, r: Round, s: Step, block_hash: Option) -> bool { + self.height == h && self.round == r && self.step == s && self.block_hash == block_hash + } + + pub fn is_aligned(&self, m: &ConsensusMessage) -> bool { + self.is_block_hash(m.height, m.round, m.step, m.block_hash) } pub fn verify(&self) -> Result { diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 04519aac0..005e76011 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -266,8 +266,8 @@ impl Tendermint { } fn has_enough_aligned_votes(&self, message: &ConsensusMessage) -> bool { - let aligned_votes = self.votes.aligned_votes(&message).len(); - self.is_above_threshold(aligned_votes) + let aligned_count = self.votes.count_aligned_votes(&message); + self.is_above_threshold(aligned_count) } } @@ -709,6 +709,7 @@ mod tests { } #[test] + #[ignore] fn precommit_step() { let (spec, tap) = setup(); let engine = spec.engine.clone(); @@ -722,6 +723,7 @@ mod tests { } #[test] + #[ignore] fn timeout_switching() { let tender = { let engine = Spec::new_test_tendermint().engine; diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 0f4553502..4158398d1 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -42,34 +42,92 @@ impl VoteCollector { pub fn seal_signatures(&self, height: Height, round: Round, block_hash: Option) -> Option { let guard = self.votes.read(); - // Get only Propose and Precommits. - let mut correct_signatures = guard.keys() - .filter(|m| m.is_aligned(height, round, block_hash) && m.step != Step::Prevote) - .map(|m| m.signature.clone()); - correct_signatures.next().map(|proposal| SealSignatures { - proposal: proposal, - votes: correct_signatures.collect() + let mut current_signatures = guard.keys() + .skip_while(|m| !m.is_block_hash(height, round, Step::Propose, block_hash)); + current_signatures.next().map(|proposal| SealSignatures { + proposal: proposal.signature, + votes: current_signatures + .skip_while(|m| !m.is_block_hash(height, round, Step::Precommit, block_hash)) + .take_while(|m| m.is_block_hash(height, round, Step::Precommit, block_hash)) + .map(|m| m.signature.clone()) + .collect() }) } - pub fn aligned_votes(&self, message: &ConsensusMessage) -> Vec { + pub fn count_aligned_votes(&self, message: &ConsensusMessage) -> usize { let guard = self.votes.read(); guard.keys() - // Get only Propose and Precommits. - .filter(|m| m.is_aligned(message.height, message.round, message.block_hash) && m.step == message.step) - .cloned() - .collect() - } - - pub fn aligned_signatures(&self, message: &ConsensusMessage) -> Vec { - self.seal_signatures(message.height, message.round, message.block_hash).map_or(Vec::new(), |s| s.votes) + .skip_while(|m| !m.is_aligned(message)) + // sorted by signature so might not be continuous + .filter(|m| m.is_aligned(message)) + .count() } pub fn count_step_votes(&self, height: Height, round: Round, step: Step) -> usize { self.votes .read() .keys() - .filter(|m| m.is_step(height, round, step)) + .skip_while(|m| !m.is_step(height, round, step)) + .take_while(|m| m.is_step(height, round, step)) .count() } } + +#[cfg(test)] +mod tests { + use util::*; + use super::*; + use super::super::{Height, Round, BlockHash, Step}; + use super::super::message::ConsensusMessage; + + fn simple_vote(collector: &VoteCollector, signature: H520, h: Height, r: Round, step: Step, block_hash: Option) -> Option { + collector.vote(ConsensusMessage { signature: signature, height: h, round: r, step: step, block_hash: block_hash }, H160::default()) + } + + #[test] + fn seal_retrieval() { + let collector = VoteCollector::new(); + let bh = Some("1".sha3()); + let h = 1; + let r = 2; + let proposal = H520::random(); + simple_vote(&collector, proposal.clone(), h, r, Step::Propose, Some("0".sha3())); + let seal = SealSignatures { + proposal: proposal, + votes: Vec::new() + }; + collector.seal_signatures(h, r, bh); + } + + #[test] + fn count_votes() { + let collector = VoteCollector::new(); + // good prevote + simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); + simple_vote(&collector, H520::random(), 3, 1, Step::Prevote, Some("0".sha3())); + // good precommit + simple_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("0".sha3())); + simple_vote(&collector, H520::random(), 3, 3, Step::Precommit, Some("0".sha3())); + // good prevote + simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); + // good prevote + simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); + // good precommit + simple_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("1".sha3())); + // good prevote + simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); + simple_vote(&collector, H520::random(), 2, 2, Step::Precommit, Some("2".sha3())); + + assert_eq!(collector.count_step_votes(3, 2, Step::Prevote), 4); + assert_eq!(collector.count_step_votes(3, 2, Step::Precommit), 2); + + let message = ConsensusMessage { + signature: H520::default(), + height: 3, + round: 2, + step: Step::Prevote, + block_hash: Some("1".sha3()) + }; + assert_eq!(collector.count_aligned_votes(&message), 2); + } +} From 7d97ba5ee01582c1b79e720b744630d06d9fb026 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 28 Nov 2016 15:23:39 +0000 Subject: [PATCH 091/295] seal sigs test --- .../src/engines/tendermint/vote_collector.rs | 45 ++++++++++++++++--- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 4158398d1..afc5427b8 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -26,6 +26,7 @@ pub struct VoteCollector { votes: RwLock> } +#[derive(Debug, PartialEq, Eq)] pub struct SealSignatures { pub proposal: H520, pub votes: Vec @@ -48,7 +49,7 @@ impl VoteCollector { proposal: proposal.signature, votes: current_signatures .skip_while(|m| !m.is_block_hash(height, round, Step::Precommit, block_hash)) - .take_while(|m| m.is_block_hash(height, round, Step::Precommit, block_hash)) + .filter(|m| m.is_block_hash(height, round, Step::Precommit, block_hash)) .map(|m| m.signature.clone()) .collect() }) @@ -90,13 +91,41 @@ mod tests { let bh = Some("1".sha3()); let h = 1; let r = 2; - let proposal = H520::random(); - simple_vote(&collector, proposal.clone(), h, r, Step::Propose, Some("0".sha3())); + let mut signatures = Vec::new(); + for _ in 0..5 { + signatures.push(H520::random()); + } + // Wrong height proposal. + simple_vote(&collector, signatures[4].clone(), h - 1, r, Step::Propose, bh.clone()); + // Good proposal. + simple_vote(&collector, signatures[0].clone(), h, r, Step::Propose, bh.clone()); + // Wrong block proposal. + simple_vote(&collector, signatures[0].clone(), h, r, Step::Propose, Some("0".sha3())); + // Wrong block precommit. + simple_vote(&collector, signatures[3].clone(), h, r, Step::Precommit, Some("0".sha3())); + // Wrong round proposal. + simple_vote(&collector, signatures[0].clone(), h, r - 1, Step::Propose, bh.clone()); + // Prevote. + simple_vote(&collector, signatures[0].clone(), h, r, Step::Prevote, bh.clone()); + // Relevant precommit. + simple_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone()); + // Replcated vote. + simple_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone()); + // Wrong round precommit. + simple_vote(&collector, signatures[4].clone(), h, r + 1, Step::Precommit, bh.clone()); + // Wrong height precommit. + simple_vote(&collector, signatures[3].clone(), h + 1, r, Step::Precommit, bh.clone()); + // Relevant precommit. + simple_vote(&collector, signatures[1].clone(), h, r, Step::Precommit, bh.clone()); + // Wrong round precommit, same signature. + simple_vote(&collector, signatures[1].clone(), h, r + 1, Step::Precommit, bh.clone()); + // Wrong round precommit. + simple_vote(&collector, signatures[4].clone(), h, r - 1, Step::Precommit, bh.clone()); let seal = SealSignatures { - proposal: proposal, - votes: Vec::new() + proposal: signatures[0], + votes: signatures[1..3].to_vec() }; - collector.seal_signatures(h, r, bh); + assert_eq!(seal, collector.seal_signatures(h, r, bh).unwrap()); } #[test] @@ -111,7 +140,9 @@ mod tests { // good prevote simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); // good prevote - simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); + let same_sig = H520::random(); + simple_vote(&collector, same_sig.clone(), 3, 2, Step::Prevote, Some("1".sha3())); + simple_vote(&collector, same_sig, 3, 2, Step::Prevote, Some("1".sha3())); // good precommit simple_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("1".sha3())); // good prevote From 1326c6cf5abc0df932d4eeb0f60dcce6e2b1bd98 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 28 Nov 2016 15:24:22 +0000 Subject: [PATCH 092/295] rebroadcast unseen messages --- ethcore/src/engines/tendermint/mod.rs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 005e76011..5cadf0aef 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -132,13 +132,11 @@ impl Tendermint { } } - fn broadcast_message(&self, block_hash: Option) { - if let Some(message) = self.generate_message(block_hash) { - if let Some(ref channel) = *self.message_channel.lock() { - match channel.send(ClientIoMessage::BroadcastMessage(message)) { - Ok(_) => trace!(target: "poa", "timeout: BroadcastMessage message sent."), - Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), - } + fn broadcast_message(&self, message: Bytes) { + if let Some(ref channel) = *self.message_channel.lock() { + match channel.send(ClientIoMessage::BroadcastMessage(message)) { + Ok(_) => trace!(target: "poa", "timeout: BroadcastMessage message sent."), + Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), } } } @@ -158,6 +156,12 @@ impl Tendermint { } } + fn generate_and_broadcast_message(&self, block_hash: Option) { + if let Some(message) = self.generate_message(block_hash) { + self.broadcast_message(message); + } + } + fn to_step(&self, step: Step) { *self.step.write() = step; match step { @@ -173,7 +177,7 @@ impl Tendermint { Some(ref m) => m.block_hash, None => None, }; - self.broadcast_message(block_hash) + self.generate_and_broadcast_message(block_hash) }, Step::Precommit => { trace!(target: "poa", "to_step: Transitioning to Precommit."); @@ -184,7 +188,7 @@ impl Tendermint { }, _ => None, }; - self.broadcast_message(block_hash); + self.generate_and_broadcast_message(block_hash); }, Step::Commit => { trace!(target: "poa", "to_step: Transitioning to Commit."); @@ -352,6 +356,7 @@ impl Engine for Tendermint { // Check if the message is known. if self.votes.vote(message.clone(), sender).is_none() { trace!(target: "poa", "handle_message: Processing new authorized message: {:?}", &message); + self.broadcast_message(rlp.as_raw().to_vec()); let is_newer_than_lock = match *self.lock_change.read() { Some(ref lock) => &message > lock, None => true, @@ -704,7 +709,7 @@ mod tests { vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); - ::std::thread::sleep(::std::time::Duration::from_millis(40)); + ::std::thread::sleep(::std::time::Duration::from_millis(5)); assert_eq!(*test_io.received.lock(), Some(ClientIoMessage::SubmitSeal(proposal.unwrap(), seal))); } From b454f7e3070d1f3498ee8fdb6dcb09f22ef35bf4 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 28 Nov 2016 15:42:36 +0000 Subject: [PATCH 093/295] use Io queue for messages --- ethcore/src/client/client.rs | 12 ++++++++++-- ethcore/src/service.rs | 7 ++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index e89bc8921..b6976a933 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -562,6 +562,13 @@ impl Client { results.len() } + /// Handle messages from the IO queue + pub fn handle_queued_message(&self, message: &Bytes) { + if let Err(e) = self.engine.handle_message(UntrustedRlp::new(message)) { + trace!(target: "poa", "Invalid message received: {}", e); + } + } + /// Used by PoA to try sealing on period change. pub fn update_sealing(&self) { self.miner.update_sealing(self) @@ -1229,9 +1236,10 @@ impl BlockChainClient for Client { self.miner.pending_transactions(self.chain.read().best_block_number()) } - // TODO: Make it an actual queue, return errors. fn queue_infinity_message(&self, message: Bytes) { - self.engine.handle_message(UntrustedRlp::new(&message)); + if let Err(e) = self.io_channel.lock().send(ClientIoMessage::NewMessage(message)) { + debug!("Ignoring the message, error queueing: {}", e); + } } fn signing_network_id(&self) -> Option { diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index b3e1e0d51..c7dccaa89 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -53,7 +53,9 @@ pub enum ClientIoMessage { /// Submit seal (useful for internal sealing). SubmitSeal(H256, Vec), /// Broadcast a message to the network. - BroadcastMessage(Bytes) + BroadcastMessage(Bytes), + /// New consensus message received. + NewMessage(Bytes) } /// Client service setup. Creates and registers client and network services with the IO subsystem. @@ -234,6 +236,9 @@ impl IoHandler for ClientIoHandler { trace!(target: "poa", "message: BroadcastMessage"); self.client.broadcast_message(message.clone()); }, + ClientIoMessage::NewMessage(ref message) => { + self.client.handle_queued_message(message); + }, _ => {} // ignore other messages } } From e4ff614966df08a2a6d7800bb42103fb80fb0399 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 28 Nov 2016 15:50:55 +0000 Subject: [PATCH 094/295] remove unused tracing --- ethcore/src/engines/tendermint/message.rs | 1 - ethcore/src/engines/tendermint/mod.rs | 19 +++++++------------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 7f6e60754..3366510d2 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -157,7 +157,6 @@ pub fn message_info_rlp(height: Height, round: Round, step: Step, block_hash: Op // TODO: figure out whats wrong with nested list encoding let mut s = RlpStream::new_list(5); s.append(&height).append(&round).append(&step).append(&block_hash.unwrap_or(H256::zero())); - println!("{:?}, {:?}, {:?}, {:?}", &height, &round, &step, &block_hash); s.out() } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 5cadf0aef..f62bafb57 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -117,8 +117,8 @@ impl Tendermint { fn update_sealing(&self) { if let Some(ref channel) = *self.message_channel.lock() { match channel.send(ClientIoMessage::UpdateSealing) { - Ok(_) => trace!(target: "poa", "timeout: UpdateSealing message sent."), - Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), + Ok(_) => trace!(target: "poa", "update_sealing: UpdateSealing message sent."), + Err(err) => warn!(target: "poa", "update_sealing: Could not send a sealing message {}.", err), } } } @@ -126,8 +126,8 @@ impl Tendermint { fn submit_seal(&self, block_hash: H256, seal: Vec) { if let Some(ref channel) = *self.message_channel.lock() { match channel.send(ClientIoMessage::SubmitSeal(block_hash, seal)) { - Ok(_) => trace!(target: "poa", "timeout: SubmitSeal message sent."), - Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), + Ok(_) => trace!(target: "poa", "submit_seal: SubmitSeal message sent."), + Err(err) => warn!(target: "poa", "submit_seal: Could not send a sealing message {}.", err), } } } @@ -135,8 +135,8 @@ impl Tendermint { fn broadcast_message(&self, message: Bytes) { if let Some(ref channel) = *self.message_channel.lock() { match channel.send(ClientIoMessage::BroadcastMessage(message)) { - Ok(_) => trace!(target: "poa", "timeout: BroadcastMessage message sent."), - Err(err) => warn!(target: "poa", "timeout: Could not send a sealing message {}.", err), + Ok(_) => trace!(target: "poa", "broadcast_message: BroadcastMessage message sent."), + Err(err) => warn!(target: "poa", "broadcast_message: Could not send a sealing message {}.", err), } } } @@ -527,7 +527,6 @@ mod tests { fn proposal_seal(tap: &Arc, header: &Header, round: Round) -> Vec { let author = header.author(); - println!("author: {:?}", author); let vote_info = message_info_rlp(header.number() as Height, round, Step::Propose, Some(header.bare_hash())); let signature = tap.sign(*author, None, vote_info.sha3()).unwrap(); vec![ @@ -609,12 +608,10 @@ mod tests { let mut header = Header::default(); let validator = insert_and_unlock(&tap, "0"); - println!("validator: {:?}", &validator); header.set_author(validator); let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Good proposer. - println!("{:?}", engine.verify_block_unordered(&header, None)); assert!(engine.verify_block_unordered(&header, None).is_ok()); let mut header = Header::default(); @@ -640,7 +637,6 @@ mod tests { let mut seal = proposal_seal(&tap, &header, 0); let voter = insert_and_unlock(&tap, "1"); - println!("voter: {:?}", &voter); let vote_info = message_info_rlp(0, 0, Step::Precommit, Some(header.bare_hash())); let signature = tap.sign(voter, None, vote_info.sha3()).unwrap(); @@ -648,7 +644,6 @@ mod tests { header.set_seal(seal.clone()); - println!("{:?}", engine.verify_block_unordered(&header, None)); // One good signature. assert!(engine.verify_block_unordered(&header, None).is_ok()); @@ -709,7 +704,7 @@ mod tests { vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); - ::std::thread::sleep(::std::time::Duration::from_millis(5)); + ::std::thread::sleep(::std::time::Duration::from_millis(50)); assert_eq!(*test_io.received.lock(), Some(ClientIoMessage::SubmitSeal(proposal.unwrap(), seal))); } From 0f1eefc00d24467f5e1b4e15c940b37f9e6f2a6e Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 28 Nov 2016 18:37:00 +0000 Subject: [PATCH 095/295] disallow None seal sigs --- ethcore/src/engines/tendermint/vote_collector.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index afc5427b8..19cc0d1ab 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -41,15 +41,16 @@ impl VoteCollector { self.votes.write().insert(message, voter) } - pub fn seal_signatures(&self, height: Height, round: Round, block_hash: Option) -> Option { + pub fn seal_signatures(&self, height: Height, round: Round, block_hash: H256) -> Option { let guard = self.votes.read(); + let bh = Some(block_hash); let mut current_signatures = guard.keys() - .skip_while(|m| !m.is_block_hash(height, round, Step::Propose, block_hash)); + .skip_while(|m| !m.is_block_hash(height, round, Step::Propose, bh)); current_signatures.next().map(|proposal| SealSignatures { proposal: proposal.signature, votes: current_signatures - .skip_while(|m| !m.is_block_hash(height, round, Step::Precommit, block_hash)) - .filter(|m| m.is_block_hash(height, round, Step::Precommit, block_hash)) + .skip_while(|m| !m.is_block_hash(height, round, Step::Precommit, bh)) + .filter(|m| m.is_block_hash(height, round, Step::Precommit, bh)) .map(|m| m.signature.clone()) .collect() }) @@ -125,7 +126,7 @@ mod tests { proposal: signatures[0], votes: signatures[1..3].to_vec() }; - assert_eq!(seal, collector.seal_signatures(h, r, bh).unwrap()); + assert_eq!(seal, collector.seal_signatures(h, r, bh.unwrap()).unwrap()); } #[test] From 61cf8b8b7e590aa7c24ae14305a18a17cc8f5e7b Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 28 Nov 2016 18:58:15 +0000 Subject: [PATCH 096/295] vote propose --- ethcore/src/engines/tendermint/mod.rs | 37 +++++++++++++++------------ 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index f62bafb57..c5009a7c6 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -194,14 +194,16 @@ impl Tendermint { trace!(target: "poa", "to_step: Transitioning to Commit."); // Commit the block using a complete signature set. let round = self.round.load(AtomicOrdering::SeqCst); - if let Some(seal) = self.votes.seal_signatures(self.height.load(AtomicOrdering::SeqCst), round, *self.proposal.read()) { - let seal = vec![ - ::rlp::encode(&round).to_vec(), - ::rlp::encode(&seal.proposal).to_vec(), - ::rlp::encode(&seal.votes).to_vec() - ]; - if let Some(block_hash) = *self.proposal.read() { + if let Some(block_hash) = *self.proposal.read() { + if let Some(seal) = self.votes.seal_signatures(self.height.load(AtomicOrdering::SeqCst), round, block_hash) { + let seal = vec![ + ::rlp::encode(&round).to_vec(), + ::rlp::encode(&seal.proposal).to_vec(), + ::rlp::encode(&seal.votes).to_vec() + ]; self.submit_seal(block_hash, seal); + } else { + warn!(target: "poa", "Proposal was not found!"); } } *self.lock_change.write() = None; @@ -327,12 +329,16 @@ impl Engine for Tendermint { if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let author = header.author(); - let vote_info = message_info_rlp(header.number() as Height, self.round.load(AtomicOrdering::SeqCst), Step::Propose, Some(header.bare_hash())); - if let Ok(signature) = ap.sign(*author, None, vote_info.sha3()) { + let height = header.number() as Height; + let round = self.round.load(AtomicOrdering::SeqCst); + let bh = Some(header.bare_hash()); + let vote_info = message_info_rlp(height, round, Step::Propose, bh); + if let Ok(signature) = ap.sign(*author, None, vote_info.sha3()).map(H520::from) { + self.votes.vote(ConsensusMessage { signature: signature, height: height, round: round, step: Step::Propose, block_hash: bh }, *author); *self.proposal.write() = Some(header.bare_hash()); Some(vec![ ::rlp::encode(&self.round.load(AtomicOrdering::SeqCst)).to_vec(), - ::rlp::encode(&H520::from(signature)).to_vec(), + ::rlp::encode(&signature).to_vec(), Vec::new() ]) } else { @@ -551,16 +557,16 @@ mod tests { } struct TestIo { - received: Mutex> + received: RwLock> } impl TestIo { - fn new() -> Arc { Arc::new(TestIo { received: Mutex::new(None) }) } + fn new() -> Arc { Arc::new(TestIo { received: RwLock::new(Vec::new()) }) } } impl IoHandler for TestIo { fn message(&self, _io: &IoContext, net_message: &ClientIoMessage) { - *self.received.lock() = Some(net_message.clone()); + self.received.write().push(net_message.clone()); } } @@ -671,7 +677,6 @@ mod tests { } #[test] - #[should_panic] fn step_transitioning() { ::env_logger::init().unwrap(); let (spec, tap) = setup(); @@ -704,8 +709,8 @@ mod tests { vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); - ::std::thread::sleep(::std::time::Duration::from_millis(50)); - assert_eq!(*test_io.received.lock(), Some(ClientIoMessage::SubmitSeal(proposal.unwrap(), seal))); + ::std::thread::sleep(::std::time::Duration::from_millis(500)); + assert_eq!(test_io.received.read()[5], ClientIoMessage::SubmitSeal(proposal.unwrap(), seal)); } #[test] From d0eab4a0d8be724f347a92cd17f4d48921cbd6c5 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 29 Nov 2016 10:55:24 +0000 Subject: [PATCH 097/295] old message removal, avoid too many recoveries --- ethcore/src/engines/tendermint/mod.rs | 16 +++--- .../src/engines/tendermint/vote_collector.rs | 55 ++++++++++++++++++- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index c5009a7c6..70e04e929 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -353,14 +353,14 @@ impl Engine for Tendermint { fn handle_message(&self, rlp: UntrustedRlp) -> Result<(), Error> { let message: ConsensusMessage = try!(rlp.as_val()); - let sender = public_to_address(&try!(recover(&message.signature.into(), &try!(rlp.at(1)).as_raw().sha3()))); - // TODO: Do not admit old messages. - if !self.is_authority(&sender) { - try!(Err(BlockError::NotAuthorized(sender))); - } - // Check if the message is known. - if self.votes.vote(message.clone(), sender).is_none() { + if self.votes.is_known(&message) { + let sender = public_to_address(&try!(recover(&message.signature.into(), &try!(rlp.at(1)).as_raw().sha3()))); + if !self.is_authority(&sender) { + try!(Err(BlockError::NotAuthorized(sender))); + } + self.votes.vote(message.clone(), sender); + trace!(target: "poa", "handle_message: Processing new authorized message: {:?}", &message); self.broadcast_message(rlp.as_raw().to_vec()); let is_newer_than_lock = match *self.lock_change.read() { @@ -381,6 +381,8 @@ impl Engine for Tendermint { self.increment_round(1); Some(Step::Propose) } else { + // Remove old messages. + self.votes.throw_out_old(&message); Some(Step::Commit) } }, diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 19cc0d1ab..758d6bcf5 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -34,11 +34,40 @@ pub struct SealSignatures { impl VoteCollector { pub fn new() -> VoteCollector { - VoteCollector { votes: RwLock::new(BTreeMap::new()) } + let mut collector = BTreeMap::new(); + // Insert dummy message to fulfill invariant: "only messages newer than the oldest are inserted". + collector.insert(ConsensusMessage { + signature: H520::default(), + height: 0, + round: 0, + step: Step::Propose, + block_hash: None + }, + Address::default()); + VoteCollector { votes: RwLock::new(collector) } } + /// Insert vote if it is newer than the oldest one. pub fn vote(&self, message: ConsensusMessage, voter: Address) -> Option
{ - self.votes.write().insert(message, voter) + if { + let guard = self.votes.read(); + guard.keys().next().map_or(true, |oldest| &message > oldest) + } { + self.votes.write().insert(message, voter) + } else { + None + } + } + + pub fn is_known(&self, message: &ConsensusMessage) -> bool { + self.votes.read().contains_key(message) + } + + /// Throws out messages older than message, leaves message as marker for the oldest. + pub fn throw_out_old(&self, message: &ConsensusMessage) { + let mut guard = self.votes.write(); + let new_collector = guard.split_off(message); + *guard = new_collector; } pub fn seal_signatures(&self, height: Height, round: Round, block_hash: H256) -> Option { @@ -162,4 +191,26 @@ mod tests { }; assert_eq!(collector.count_aligned_votes(&message), 2); } + + #[test] + fn remove_old() { + let collector = VoteCollector::new(); + simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); + simple_vote(&collector, H520::random(), 3, 1, Step::Prevote, Some("0".sha3())); + simple_vote(&collector, H520::random(), 3, 3, Step::Precommit, Some("0".sha3())); + simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); + simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); + simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); + simple_vote(&collector, H520::random(), 2, 2, Step::Precommit, Some("2".sha3())); + + let message = ConsensusMessage { + signature: H520::default(), + height: 3, + round: 2, + step: Step::Precommit, + block_hash: Some("1".sha3()) + }; + collector.throw_out_old(&message); + assert_eq!(collector.votes.read().len(), 1); + } } From 49b953a9f488865a774933c59ceb659bacbbb8d7 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 29 Nov 2016 11:18:40 +0000 Subject: [PATCH 098/295] order invariant seal equality --- ethcore/src/engines/tendermint/vote_collector.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 758d6bcf5..51e51d155 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -26,12 +26,21 @@ pub struct VoteCollector { votes: RwLock> } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug)] pub struct SealSignatures { pub proposal: H520, pub votes: Vec } +impl PartialEq for SealSignatures { + fn eq(&self, other: &SealSignatures) -> bool { + self.proposal == other.proposal + && self.votes.iter().collect::>() == other.votes.iter().collect::>() + } +} + +impl Eq for SealSignatures {} + impl VoteCollector { pub fn new() -> VoteCollector { let mut collector = BTreeMap::new(); From e784fa906e6e68637c9c3ef68d6c7c5037c983f6 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 29 Nov 2016 12:20:38 +0000 Subject: [PATCH 099/295] warn on double vote --- .../src/engines/tendermint/vote_collector.rs | 96 +++++++++++-------- 1 file changed, 58 insertions(+), 38 deletions(-) diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 51e51d155..923b3c9a7 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -104,12 +104,20 @@ impl VoteCollector { } pub fn count_step_votes(&self, height: Height, round: Round, step: Step) -> usize { - self.votes - .read() - .keys() - .skip_while(|m| !m.is_step(height, round, step)) - .take_while(|m| m.is_step(height, round, step)) - .count() + let guard = self.votes.read(); + let current = guard.iter().skip_while(|&(m, _)| !m.is_step(height, round, step)); + let mut origins = HashSet::new(); + let mut n = 0; + for (message, origin) in current { + if message.is_step(height, round, step) { + if origins.insert(origin) { + n += 1; + } else { + warn!("count_step_votes: authority {} has cast multiple step votes, this indicates malicious behaviour.", origin) + } + } + } + n } } @@ -120,8 +128,12 @@ mod tests { use super::super::{Height, Round, BlockHash, Step}; use super::super::message::ConsensusMessage; - fn simple_vote(collector: &VoteCollector, signature: H520, h: Height, r: Round, step: Step, block_hash: Option) -> Option { - collector.vote(ConsensusMessage { signature: signature, height: h, round: r, step: step, block_hash: block_hash }, H160::default()) + fn random_vote(collector: &VoteCollector, signature: H520, h: Height, r: Round, step: Step, block_hash: Option) -> Option { + full_vote(collector, signature, h, r, step, block_hash, H160::random()) + } + + fn full_vote(collector: &VoteCollector, signature: H520, h: Height, r: Round, step: Step, block_hash: Option, address: Address) -> Option { + collector.vote(ConsensusMessage { signature: signature, height: h, round: r, step: step, block_hash: block_hash }, address) } #[test] @@ -135,31 +147,31 @@ mod tests { signatures.push(H520::random()); } // Wrong height proposal. - simple_vote(&collector, signatures[4].clone(), h - 1, r, Step::Propose, bh.clone()); + random_vote(&collector, signatures[4].clone(), h - 1, r, Step::Propose, bh.clone()); // Good proposal. - simple_vote(&collector, signatures[0].clone(), h, r, Step::Propose, bh.clone()); + random_vote(&collector, signatures[0].clone(), h, r, Step::Propose, bh.clone()); // Wrong block proposal. - simple_vote(&collector, signatures[0].clone(), h, r, Step::Propose, Some("0".sha3())); + random_vote(&collector, signatures[0].clone(), h, r, Step::Propose, Some("0".sha3())); // Wrong block precommit. - simple_vote(&collector, signatures[3].clone(), h, r, Step::Precommit, Some("0".sha3())); + random_vote(&collector, signatures[3].clone(), h, r, Step::Precommit, Some("0".sha3())); // Wrong round proposal. - simple_vote(&collector, signatures[0].clone(), h, r - 1, Step::Propose, bh.clone()); + random_vote(&collector, signatures[0].clone(), h, r - 1, Step::Propose, bh.clone()); // Prevote. - simple_vote(&collector, signatures[0].clone(), h, r, Step::Prevote, bh.clone()); + random_vote(&collector, signatures[0].clone(), h, r, Step::Prevote, bh.clone()); // Relevant precommit. - simple_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone()); + random_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone()); // Replcated vote. - simple_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone()); + random_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone()); // Wrong round precommit. - simple_vote(&collector, signatures[4].clone(), h, r + 1, Step::Precommit, bh.clone()); + random_vote(&collector, signatures[4].clone(), h, r + 1, Step::Precommit, bh.clone()); // Wrong height precommit. - simple_vote(&collector, signatures[3].clone(), h + 1, r, Step::Precommit, bh.clone()); + random_vote(&collector, signatures[3].clone(), h + 1, r, Step::Precommit, bh.clone()); // Relevant precommit. - simple_vote(&collector, signatures[1].clone(), h, r, Step::Precommit, bh.clone()); + random_vote(&collector, signatures[1].clone(), h, r, Step::Precommit, bh.clone()); // Wrong round precommit, same signature. - simple_vote(&collector, signatures[1].clone(), h, r + 1, Step::Precommit, bh.clone()); + random_vote(&collector, signatures[1].clone(), h, r + 1, Step::Precommit, bh.clone()); // Wrong round precommit. - simple_vote(&collector, signatures[4].clone(), h, r - 1, Step::Precommit, bh.clone()); + random_vote(&collector, signatures[4].clone(), h, r - 1, Step::Precommit, bh.clone()); let seal = SealSignatures { proposal: signatures[0], votes: signatures[1..3].to_vec() @@ -171,22 +183,22 @@ mod tests { fn count_votes() { let collector = VoteCollector::new(); // good prevote - simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); - simple_vote(&collector, H520::random(), 3, 1, Step::Prevote, Some("0".sha3())); + random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); + random_vote(&collector, H520::random(), 3, 1, Step::Prevote, Some("0".sha3())); // good precommit - simple_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("0".sha3())); - simple_vote(&collector, H520::random(), 3, 3, Step::Precommit, Some("0".sha3())); + random_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("0".sha3())); + random_vote(&collector, H520::random(), 3, 3, Step::Precommit, Some("0".sha3())); // good prevote - simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); + random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); // good prevote let same_sig = H520::random(); - simple_vote(&collector, same_sig.clone(), 3, 2, Step::Prevote, Some("1".sha3())); - simple_vote(&collector, same_sig, 3, 2, Step::Prevote, Some("1".sha3())); + random_vote(&collector, same_sig.clone(), 3, 2, Step::Prevote, Some("1".sha3())); + random_vote(&collector, same_sig, 3, 2, Step::Prevote, Some("1".sha3())); // good precommit - simple_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("1".sha3())); + random_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("1".sha3())); // good prevote - simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); - simple_vote(&collector, H520::random(), 2, 2, Step::Precommit, Some("2".sha3())); + random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); + random_vote(&collector, H520::random(), 2, 2, Step::Precommit, Some("2".sha3())); assert_eq!(collector.count_step_votes(3, 2, Step::Prevote), 4); assert_eq!(collector.count_step_votes(3, 2, Step::Precommit), 2); @@ -204,13 +216,13 @@ mod tests { #[test] fn remove_old() { let collector = VoteCollector::new(); - simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); - simple_vote(&collector, H520::random(), 3, 1, Step::Prevote, Some("0".sha3())); - simple_vote(&collector, H520::random(), 3, 3, Step::Precommit, Some("0".sha3())); - simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); - simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); - simple_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); - simple_vote(&collector, H520::random(), 2, 2, Step::Precommit, Some("2".sha3())); + random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); + random_vote(&collector, H520::random(), 3, 1, Step::Prevote, Some("0".sha3())); + random_vote(&collector, H520::random(), 3, 3, Step::Precommit, Some("0".sha3())); + random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); + random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); + random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3())); + random_vote(&collector, H520::random(), 2, 2, Step::Precommit, Some("2".sha3())); let message = ConsensusMessage { signature: H520::default(), @@ -222,4 +234,12 @@ mod tests { collector.throw_out_old(&message); assert_eq!(collector.votes.read().len(), 1); } + + #[test] + fn malicious_authority() { + let collector = VoteCollector::new(); + full_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()), Address::default()); + full_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3()), Address::default()); + assert_eq!(collector.count_step_votes(3, 2, Step::Prevote), 1); + } } From 294e89e5c0269b98d56d5f4847e15815a70c1a55 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 29 Nov 2016 12:51:27 +0000 Subject: [PATCH 100/295] use EngineError instead of BlockError --- ethcore/src/engines/authority_round.rs | 4 +-- ethcore/src/engines/mod.rs | 30 +++++++++++++------- ethcore/src/engines/tendermint/mod.rs | 38 ++++---------------------- ethcore/src/error.rs | 12 +------- 4 files changed, 29 insertions(+), 55 deletions(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index e55f1e5f9..9987ffd10 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -25,7 +25,7 @@ use rlp::{UntrustedRlp, View, encode}; use account_provider::AccountProvider; use block::*; use spec::CommonParams; -use engines::Engine; +use engines::{Engine, EngineError}; use header::Header; use error::{Error, BlockError}; use evm::Schedule; @@ -283,7 +283,7 @@ impl Engine for AuthorityRound { // Check if parent is from a previous step. if step == try!(header_step(parent)) { trace!(target: "poa", "Multiple blocks proposed for step {}.", step); - try!(Err(BlockError::DoubleVote(header.author().clone()))); + try!(Err(EngineError::DoubleVote(header.author().clone()))); } // Check difficulty is correct given the two timestamps. diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index ab50ee9c9..91557f8c3 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -48,18 +48,28 @@ use views::HeaderView; /// Voting errors. #[derive(Debug)] pub enum EngineError { - /// Voter is not in the voters set. - UnauthorisedVoter, - /// Message pertaining incorrect consensus step. - WrongStep, - /// Message pertaining unknown consensus step. - UnknownStep, + /// Signature does not belong to an authority. + NotAuthorized(H160), + /// The same author issued different votes at the same step. + DoubleVote(H160), + /// The received block is from an incorrect proposer. + NotProposer(Mismatch), /// Message was not expected. UnexpectedMessage, - /// Received a vote for a different proposal. - WrongVote, - /// Received message is from a different consensus round. - WrongRound +} + +impl fmt::Display for EngineError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::EngineError::*; + let msg = match *self { + DoubleVote(ref address) => format!("Author {} issued too many blocks.", address), + NotProposer(ref mis) => format!("Author is not a current proposer: {}", mis), + NotAuthorized(ref address) => format!("Signer {} is not authorized.", address), + UnexpectedMessage => "This Engine should not be fed messages.".into(), + }; + + f.write_fmt(format_args!("Engine error ({})", msg)) + } } /// A consensus mechanism for the chain. Generally either proof-of-work or proof-of-stake-based. diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 70e04e929..1168872c1 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -220,14 +220,14 @@ impl Tendermint { } /// Round proposer switching. - fn is_proposer(&self, address: &Address) -> Result<(), BlockError> { + fn is_proposer(&self, address: &Address) -> Result<(), EngineError> { let ref p = self.our_params; let proposer_nonce = self.proposer_nonce.load(AtomicOrdering::SeqCst); let proposer = p.authorities.get(proposer_nonce % p.authority_n).expect("There are authority_n authorities; taking number modulo authority_n gives number in authority_n range; qed"); if proposer == address { Ok(()) } else { - Err(BlockError::NotProposer(Mismatch { expected: proposer.clone(), found: address.clone() })) + Err(EngineError::NotProposer(Mismatch { expected: proposer.clone(), found: address.clone() })) } } @@ -357,7 +357,7 @@ impl Engine for Tendermint { if self.votes.is_known(&message) { let sender = public_to_address(&try!(recover(&message.signature.into(), &try!(rlp.at(1)).as_raw().sha3()))); if !self.is_authority(&sender) { - try!(Err(BlockError::NotAuthorized(sender))); + try!(Err(EngineError::NotAuthorized(sender))); } self.votes.vote(message.clone(), sender); @@ -434,7 +434,7 @@ impl Engine for Tendermint { let signature: H520 = try!(rlp.as_val()); let address = public_to_address(&try!(recover(&signature.into(), &block_info_hash))); if !self.our_params.authorities.contains(&address) { - try!(Err(BlockError::NotAuthorized(address))) + try!(Err(EngineError::NotAuthorized(address))) } signature_count += 1; @@ -494,7 +494,6 @@ mod tests { use rlp::{UntrustedRlp, View}; use io::{IoContext, IoHandler}; use block::*; - use state_db::StateDB; use error::{Error, BlockError}; use header::Header; use env_info::EnvInfo; @@ -629,7 +628,7 @@ mod tests { header.set_seal(seal); // Bad proposer. match engine.verify_block_unordered(&header, None) { - Err(Error::Block(BlockError::NotProposer(_))) => {}, + Err(Error::Engine(EngineError::NotProposer(_))) => {}, _ => panic!(), } } @@ -663,7 +662,7 @@ mod tests { // One good and one bad signature. match engine.verify_block_unordered(&header, None) { - Err(Error::Block(BlockError::NotAuthorized(_))) => {}, + Err(Error::Engine(EngineError::NotAuthorized(_))) => {}, _ => panic!(), } } @@ -714,29 +713,4 @@ mod tests { ::std::thread::sleep(::std::time::Duration::from_millis(500)); assert_eq!(test_io.received.read()[5], ClientIoMessage::SubmitSeal(proposal.unwrap(), seal)); } - - #[test] - #[ignore] - fn precommit_step() { - let (spec, tap) = setup(); - let engine = spec.engine.clone(); - - let not_authority_addr = insert_and_unlock(&tap, "101"); - let proposer_addr = insert_and_unlock(&tap, "0"); - propose_default(&spec, proposer_addr); - - let not_proposer_addr = insert_and_unlock(&tap, "1"); - - } - - #[test] - #[ignore] - fn timeout_switching() { - let tender = { - let engine = Spec::new_test_tendermint().engine; - Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()) - }; - - println!("Waiting for timeout"); - } } diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index cadb4fb1f..5a0fc2167 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -168,12 +168,6 @@ pub enum BlockError { UnknownParent(H256), /// Uncle parent given is unknown. UnknownUncleParent(H256), - /// The same author issued different votes at the same step. - DoubleVote(H160), - /// The received block is from an incorrect proposer. - NotProposer(Mismatch), - /// Signature does not belong to an authority. - NotAuthorized(H160) } impl fmt::Display for BlockError { @@ -207,9 +201,6 @@ impl fmt::Display for BlockError { RidiculousNumber(ref oob) => format!("Implausible block number. {}", oob), UnknownParent(ref hash) => format!("Unknown parent: {}", hash), UnknownUncleParent(ref hash) => format!("Unknown uncle parent: {}", hash), - DoubleVote(ref address) => format!("Author {} issued too many blocks.", address), - NotProposer(ref mis) => format!("Author is not a current proposer: {}", mis), - NotAuthorized(ref address) => format!("Signer {} is not authorized.", address), }; f.write_fmt(format_args!("Block error ({})", msg)) @@ -294,8 +285,7 @@ impl fmt::Display for Error { Error::StdIo(ref err) => err.fmt(f), Error::Snappy(ref err) => err.fmt(f), Error::Snapshot(ref err) => err.fmt(f), - Error::Engine(ref err) => - f.write_fmt(format_args!("Bad vote: {:?}", err)), + Error::Engine(ref err) => err.fmt(f), Error::Ethkey(ref err) => err.fmt(f), } } From 7929a145e7e5d31eeb0d8e3652bf632364385677 Mon Sep 17 00:00:00 2001 From: keorn Date: Tue, 29 Nov 2016 14:55:54 +0000 Subject: [PATCH 101/295] fix deadlock --- ethcore/src/engines/tendermint/mod.rs | 9 +++--- .../src/engines/tendermint/vote_collector.rs | 31 ++++++++++++------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 1168872c1..28aae2b10 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -354,14 +354,15 @@ impl Engine for Tendermint { fn handle_message(&self, rlp: UntrustedRlp) -> Result<(), Error> { let message: ConsensusMessage = try!(rlp.as_val()); // Check if the message is known. - if self.votes.is_known(&message) { + if !self.votes.is_known(&message) { let sender = public_to_address(&try!(recover(&message.signature.into(), &try!(rlp.at(1)).as_raw().sha3()))); if !self.is_authority(&sender) { try!(Err(EngineError::NotAuthorized(sender))); } - self.votes.vote(message.clone(), sender); trace!(target: "poa", "handle_message: Processing new authorized message: {:?}", &message); + self.votes.vote(message.clone(), sender); + self.broadcast_message(rlp.as_raw().to_vec()); let is_newer_than_lock = match *self.lock_change.read() { Some(ref lock) => &message > lock, @@ -381,8 +382,6 @@ impl Engine for Tendermint { self.increment_round(1); Some(Step::Propose) } else { - // Remove old messages. - self.votes.throw_out_old(&message); Some(Step::Commit) } }, @@ -504,7 +503,6 @@ mod tests { use spec::Spec; use engines::{Engine, EngineError}; use super::*; - use super::params::TendermintParams; use super::message::*; /// Accounts inserted with "1" and "2" are authorities. First proposer is "0". @@ -712,5 +710,6 @@ mod tests { ::std::thread::sleep(::std::time::Duration::from_millis(500)); assert_eq!(test_io.received.read()[5], ClientIoMessage::SubmitSeal(proposal.unwrap(), seal)); + println!("{:?}", *test_io.received.read()); } } diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 923b3c9a7..095b0fa37 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -58,12 +58,14 @@ impl VoteCollector { /// Insert vote if it is newer than the oldest one. pub fn vote(&self, message: ConsensusMessage, voter: Address) -> Option
{ - if { + let is_new = { let guard = self.votes.read(); guard.keys().next().map_or(true, |oldest| &message > oldest) - } { + }; + if is_new { self.votes.write().insert(message, voter) } else { + trace!(target: "poa", "vote: Old message ignored {:?}.", message); None } } @@ -80,17 +82,22 @@ impl VoteCollector { } pub fn seal_signatures(&self, height: Height, round: Round, block_hash: H256) -> Option { - let guard = self.votes.read(); let bh = Some(block_hash); - let mut current_signatures = guard.keys() - .skip_while(|m| !m.is_block_hash(height, round, Step::Propose, bh)); - current_signatures.next().map(|proposal| SealSignatures { - proposal: proposal.signature, - votes: current_signatures - .skip_while(|m| !m.is_block_hash(height, round, Step::Precommit, bh)) - .filter(|m| m.is_block_hash(height, round, Step::Precommit, bh)) - .map(|m| m.signature.clone()) - .collect() + let (proposal, votes) = { + let guard = self.votes.read(); + let mut current_signatures = guard.keys().skip_while(|m| !m.is_block_hash(height, round, Step::Propose, bh)); + let proposal = current_signatures.next().cloned(); + let votes = current_signatures + .skip_while(|m| !m.is_block_hash(height, round, Step::Precommit, bh)) + .filter(|m| m.is_block_hash(height, round, Step::Precommit, bh)) + .cloned() + .collect::>(); + (proposal, votes) + }; + votes.last().map(|m| self.throw_out_old(m)); + proposal.map(|p| SealSignatures { + proposal: p.signature, + votes: votes.into_iter().map(|m| m.signature).collect() }) } From 95f81b2a2fcda7f5de812504be5118646790c4f0 Mon Sep 17 00:00:00 2001 From: arkpar Date: Tue, 29 Nov 2016 16:54:30 +0100 Subject: [PATCH 102/295] Moved consensus networking into Parity handler --- ethcore/src/client/client.rs | 2 +- ethcore/src/client/test_client.rs | 2 +- ethcore/src/client/traits.rs | 4 +- sync/src/api.rs | 52 +------- sync/src/chain.rs | 28 ++++- sync/src/infinity.rs | 191 ------------------------------ sync/src/lib.rs | 1 - 7 files changed, 30 insertions(+), 250 deletions(-) delete mode 100644 sync/src/infinity.rs diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index b6976a933..5ed51eee8 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1236,7 +1236,7 @@ impl BlockChainClient for Client { self.miner.pending_transactions(self.chain.read().best_block_number()) } - fn queue_infinity_message(&self, message: Bytes) { + fn queue_consensus_message(&self, message: Bytes) { if let Err(e) = self.io_channel.lock().send(ClientIoMessage::NewMessage(message)) { debug!("Ignoring the message, error queueing: {}", e); } diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index cf713cb4f..6a9ab4b68 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -651,7 +651,7 @@ impl BlockChainClient for TestBlockChainClient { self.miner.import_external_transactions(self, txs); } - fn queue_infinity_message(&self, _packet: Bytes) { + fn queue_consensus_message(&self, _packet: Bytes) { unimplemented!(); } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 61077ceb1..493f623f6 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -189,8 +189,8 @@ pub trait BlockChainClient : Sync + Send { /// Queue transactions for importing. fn queue_transactions(&self, transactions: Vec); - /// Queue packet - fn queue_infinity_message(&self, message: Bytes); + /// Queue conensus engine message. + fn queue_consensus_message(&self, message: Bytes); /// list all transactions fn pending_transactions(&self) -> Vec; diff --git a/sync/src/api.rs b/sync/src/api.rs index 8d7d08037..ee9031d0e 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -28,19 +28,16 @@ use ethcore::snapshot::SnapshotService; use ethcore::header::BlockNumber; use sync_io::NetSyncIo; use chain::{ChainSync, SyncStatus}; -use infinity::{InfinitySync}; use std::net::{SocketAddr, AddrParseError}; use ipc::{BinaryConvertable, BinaryConvertError, IpcConfig}; use std::str::FromStr; use parking_lot::RwLock; use chain::{ETH_PACKET_COUNT, SNAPSHOT_SYNC_PACKET_COUNT}; +/// Parity sync protocol pub const WARP_SYNC_PROTOCOL_ID: ProtocolId = *b"par"; - /// Ethereum sync protocol -pub const ETH_PROTOCOL: [u8; 3] = *b"eth"; -/// Infinity protocol -pub const INF_PROTOCOL: [u8; 3] = *b"inf"; +pub const ETH_PROTOCOL: ProtocolId = *b"eth"; /// Sync configuration #[derive(Debug, Clone, Copy)] @@ -124,8 +121,6 @@ pub struct EthSync { network: NetworkService, /// Ethereum Protocol handler eth_handler: Arc, - /// Infinity Protocol handler - inf_handler: Arc, /// The main subprotocol name subprotocol_name: [u8; 3], /// Configuration @@ -135,7 +130,6 @@ pub struct EthSync { impl EthSync { /// Creates and register protocol with the network service pub fn new(config: SyncConfig, chain: Arc, snapshot_service: Arc, network_config: NetworkConfiguration) -> Result, NetworkError> { - let inf_sync = InfinitySync::new(&config, chain.clone()); let chain_sync = ChainSync::new(config, &*chain); let service = try!(NetworkService::new(try!(network_config.clone().into_basic()))); let sync = Arc::new(EthSync{ @@ -146,12 +140,6 @@ impl EthSync { snapshot_service: snapshot_service.clone(), overlay: RwLock::new(HashMap::new()), }), - inf_handler: Arc::new(InfProtocolHandler { - sync: RwLock::new(inf_sync), - chain: chain, - snapshot_service: snapshot_service, - overlay: RwLock::new(HashMap::new()), - }), subprotocol_name: config.subprotocol_name, config: network_config, }); @@ -232,37 +220,6 @@ impl NetworkProtocolHandler for SyncProtocolHandler { } } -struct InfProtocolHandler { - /// Shared blockchain client. - chain: Arc, - /// Shared snapshot service. - snapshot_service: Arc, - /// Sync strategy - sync: RwLock, - /// Chain overlay used to cache data such as fork block. - overlay: RwLock>, -} - -impl NetworkProtocolHandler for InfProtocolHandler { - fn initialize(&self, _io: &NetworkContext) { - } - - fn read(&self, io: &NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) { - InfinitySync::dispatch_packet(&self.sync, &mut NetSyncIo::new(io, &*self.chain, &*self.snapshot_service, &self.overlay), *peer, packet_id, data); - } - - fn connected(&self, io: &NetworkContext, peer: &PeerId) { - self.sync.write().on_peer_connected(&mut NetSyncIo::new(io, &*self.chain, &*self.snapshot_service, &self.overlay), *peer); - } - - fn disconnected(&self, io: &NetworkContext, peer: &PeerId) { - self.sync.write().on_peer_aborting(&mut NetSyncIo::new(io, &*self.chain, &*self.snapshot_service, &self.overlay), *peer); - } - - fn timeout(&self, _io: &NetworkContext, _timer: TimerToken) { - } -} - impl ChainNotify for EthSync { fn new_blocks(&self, imported: Vec, @@ -295,9 +252,6 @@ impl ChainNotify for EthSync { // register the warp sync subprotocol self.network.register_protocol(self.eth_handler.clone(), WARP_SYNC_PROTOCOL_ID, SNAPSHOT_SYNC_PACKET_COUNT, &[1u8]) .unwrap_or_else(|e| warn!("Error registering snapshot sync protocol: {:?}", e)); - // register the inf sync subprotocol - self.network.register_protocol(self.inf_handler.clone(), INF_PROTOCOL, ETH_PACKET_COUNT, &[1u8]) - .unwrap_or_else(|e| warn!("Error registering infinity protocol: {:?}", e)); } fn stop(&self) { @@ -308,7 +262,7 @@ impl ChainNotify for EthSync { fn broadcast(&self, message: Vec) { self.network.with_context(ETH_PROTOCOL, |context| { let mut sync_io = NetSyncIo::new(context, &*self.eth_handler.chain, &*self.eth_handler.snapshot_service, &self.eth_handler.overlay); - self.inf_handler.sync.write().propagate_packet(&mut sync_io, message.clone()); + self.eth_handler.sync.write().propagate_consensus_packet(&mut sync_io, message.clone()); }); } } diff --git a/sync/src/chain.rs b/sync/src/chain.rs index e36dcffa3..e317232ba 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -112,6 +112,7 @@ type PacketDecodeError = DecoderError; const PROTOCOL_VERSION_63: u8 = 63; const PROTOCOL_VERSION_1: u8 = 1; +const PROTOCOL_VERSION_2: u8 = 2; const MAX_BODIES_TO_SEND: usize = 256; const MAX_HEADERS_TO_SEND: usize = 512; const MAX_NODE_DATA_TO_SEND: usize = 1024; @@ -148,8 +149,9 @@ const GET_SNAPSHOT_MANIFEST_PACKET: u8 = 0x11; const SNAPSHOT_MANIFEST_PACKET: u8 = 0x12; const GET_SNAPSHOT_DATA_PACKET: u8 = 0x13; const SNAPSHOT_DATA_PACKET: u8 = 0x14; +const CONSENSUS_DATA_PACKET: u8 = 0x15; -pub const SNAPSHOT_SYNC_PACKET_COUNT: u8 = 0x15; +pub const SNAPSHOT_SYNC_PACKET_COUNT: u8 = 0x16; const MAX_SNAPSHOT_CHUNKS_DOWNLOAD_AHEAD: usize = 3; @@ -607,7 +609,7 @@ impl ChainSync { trace!(target: "sync", "Peer {} network id mismatch (ours: {}, theirs: {})", peer_id, self.network_id, peer.network_id); return Ok(()); } - if (warp_protocol && peer.protocol_version != PROTOCOL_VERSION_1) || (!warp_protocol && peer.protocol_version != PROTOCOL_VERSION_63) { + if (warp_protocol && peer.protocol_version != PROTOCOL_VERSION_1 && peer.protocol_version != PROTOCOL_VERSION_2) || (!warp_protocol && peer.protocol_version != PROTOCOL_VERSION_63) { io.disable_peer(peer_id); trace!(target: "sync", "Peer {} unsupported eth protocol ({})", peer_id, peer.protocol_version); return Ok(()); @@ -1416,8 +1418,9 @@ impl ChainSync { /// Send Status message fn send_status(&mut self, io: &mut SyncIo, peer: PeerId) -> Result<(), NetworkError> { - let warp_protocol = io.protocol_version(&WARP_SYNC_PROTOCOL_ID, peer) != 0; - let protocol = if warp_protocol { PROTOCOL_VERSION_1 } else { PROTOCOL_VERSION_63 }; + let warp_protocol_version = io.protocol_version(&WARP_SYNC_PROTOCOL_ID, peer); + let warp_protocol = warp_protocol_version != 0; + let protocol = if warp_protocol { warp_protocol_version } else { PROTOCOL_VERSION_63 }; trace!(target: "sync", "Sending status to {}, protocol version {}", peer, protocol); let mut packet = RlpStream::new_list(if warp_protocol { 7 } else { 5 }); let chain = io.chain().chain_info(); @@ -1663,7 +1666,7 @@ impl ChainSync { GET_SNAPSHOT_DATA_PACKET => ChainSync::return_rlp(io, &rlp, peer, ChainSync::return_snapshot_data, |e| format!("Error sending snapshot data: {:?}", e)), - + CONSENSUS_DATA_PACKET => ChainSync::on_consensus_packet(io, peer, &rlp), _ => { sync.write().on_packet(io, peer, packet_id, data); Ok(()) @@ -1996,6 +1999,21 @@ impl ChainSync { self.restart(io); } } + + /// Called when peer sends us new consensus packet + fn on_consensus_packet(io: &mut SyncIo, _peer_id: PeerId, r: &UntrustedRlp) -> Result<(), PacketDecodeError> { + io.chain().queue_consensus_message(r.as_raw().to_vec()); + Ok(()) + } + + /// Broadcast consensus message to peers. + pub fn propagate_consensus_packet(&mut self, io: &mut SyncIo, packet: Bytes) { + let lucky_peers: Vec<_> = self.peers.iter().filter_map(|(id, p)| if p.protocol_version == PROTOCOL_VERSION_2 { Some(*id) } else { None }).collect(); + trace!(target: "sync", "Sending consensus packet to {:?}", lucky_peers); + for peer_id in lucky_peers { + self.send_packet(io, peer_id, CONSENSUS_DATA_PACKET, packet.clone()); + } + } } #[cfg(test)] diff --git a/sync/src/infinity.rs b/sync/src/infinity.rs deleted file mode 100644 index 936060a1d..000000000 --- a/sync/src/infinity.rs +++ /dev/null @@ -1,191 +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 . - -/// Infinity networking - -use util::*; -use network::*; -use rlp::{UntrustedRlp, DecoderError, RlpStream, View, Stream}; -use ethcore::client::{BlockChainClient}; -use sync_io::SyncIo; -use super::SyncConfig; - -known_heap_size!(0, PeerInfo); - -type PacketDecodeError = DecoderError; - -const PROTOCOL_VERSION: u8 = 1u8; - -const STATUS_PACKET: u8 = 0x00; -const GENERIC_PACKET: u8 = 0x01; - -/// Syncing status and statistics -#[derive(Clone)] -pub struct NetworkStatus { - pub protocol_version: u8, - /// The underlying p2p network version. - pub network_id: usize, - /// Total number of connected peers - pub num_peers: usize, - /// Total number of active peers - pub num_active_peers: usize, -} - -#[derive(Clone)] -/// Inf peer information -struct PeerInfo { - /// inf protocol version - protocol_version: u32, - /// Peer chain genesis hash - genesis: H256, - /// Peer network id - network_id: usize, -} - -/// Infinity protocol handler. -pub struct InfinitySync { - chain: Arc, - /// All connected peers - peers: HashMap, - /// Network ID - network_id: usize, -} - -impl InfinitySync { - /// Create a new instance of syncing strategy. - pub fn new(config: &SyncConfig, chain: Arc) -> InfinitySync { - let mut sync = InfinitySync { - chain: chain, - peers: HashMap::new(), - network_id: config.network_id, - }; - sync.reset(); - sync - } - - /// @returns Synchonization status - pub fn _status(&self) -> NetworkStatus { - NetworkStatus { - protocol_version: 1, - network_id: self.network_id, - num_peers: self.peers.len(), - num_active_peers: 0, - } - } - - #[cfg_attr(feature="dev", allow(for_kv_map))] // Because it's not possible to get `values_mut()` - /// Reset sync. Clear all downloaded data but keep the queue - fn reset(&mut self) { - } - - /// Called by peer to report status - fn on_peer_status(&mut self, io: &mut SyncIo, peer_id: PeerId, r: &UntrustedRlp) -> Result<(), PacketDecodeError> { - let peer = PeerInfo { - protocol_version: try!(r.val_at(0)), - network_id: try!(r.val_at(1)), - genesis: try!(r.val_at(2)), - }; - trace!(target: "inf", "New peer {} (protocol: {}, network: {:?}, genesis:{})", peer_id, peer.protocol_version, peer.network_id, peer.genesis); - if self.peers.contains_key(&peer_id) { - debug!(target: "inf", "Unexpected status packet from {}:{}", peer_id, io.peer_info(peer_id)); - return Ok(()); - } - let chain_info = io.chain().chain_info(); - if peer.genesis != chain_info.genesis_hash { - io.disable_peer(peer_id); - trace!(target: "inf", "Peer {} genesis hash mismatch (ours: {}, theirs: {})", peer_id, chain_info.genesis_hash, peer.genesis); - return Ok(()); - } - if peer.network_id != self.network_id { - io.disable_peer(peer_id); - trace!(target: "inf", "Peer {} network id mismatch (ours: {}, theirs: {})", peer_id, self.network_id, peer.network_id); - return Ok(()); - } - - self.peers.insert(peer_id.clone(), peer); - Ok(()) - } - - /// Called when a new peer is connected - pub fn on_peer_connected(&mut self, io: &mut SyncIo, peer: PeerId) { - trace!(target: "inf", "== Connected {}: {}", peer, io.peer_info(peer)); - if let Err(e) = self.send_status(io) { - debug!(target:"inf", "Error sending status request: {:?}", e); - io.disable_peer(peer); - } - } - - /// Generic packet sender - fn send_packet(&mut self, sync: &mut SyncIo, peer_id: PeerId, packet_id: PacketId, packet: Bytes) { - if self.peers.contains_key(&peer_id) { - if let Err(e) = sync.send(peer_id, packet_id, packet) { - debug!(target:"inf", "Error sending request: {:?}", e); - sync.disable_peer(peer_id); - } - } - } - - /// Called when peer sends us new transactions - fn on_peer_packet(&mut self, _io: &mut SyncIo, _peer_id: PeerId, r: &UntrustedRlp) -> Result<(), PacketDecodeError> { - self.chain.queue_infinity_message(r.as_raw().to_vec()); - Ok(()) - } - - /// Called by peer when it is disconnecting - pub fn on_peer_aborting(&mut self, io: &mut SyncIo, peer: PeerId) { - trace!(target: "inf", "== Disconnecting {}: {}", peer, io.peer_info(peer)); - if self.peers.contains_key(&peer) { - debug!(target: "inf", "Disconnected {}", peer); - self.peers.remove(&peer); - } - } - - /// Send Status message - fn send_status(&mut self, io: &mut SyncIo) -> Result<(), NetworkError> { - let mut packet = RlpStream::new_list(5); - let chain = io.chain().chain_info(); - packet.append(&(PROTOCOL_VERSION as u32)); - packet.append(&self.network_id); - packet.append(&chain.total_difficulty); - packet.append(&chain.best_block_hash); - packet.append(&chain.genesis_hash); - io.respond(STATUS_PACKET, packet.out()) - } - - pub fn dispatch_packet(sync: &RwLock, io: &mut SyncIo, peer: PeerId, packet_id: u8, data: &[u8]) { - let rlp = UntrustedRlp::new(data); - match packet_id { - STATUS_PACKET => sync.write().on_peer_status(io, peer, &rlp).unwrap_or_else( - |e| trace!(target: "inf", "Error processing packet: {:?}", e)), - GENERIC_PACKET => sync.write().on_peer_packet(io, peer, &rlp).unwrap_or_else( - |e| warn!(target: "inf", "Error queueing packet: {:?}", e)), - p @ _ => trace!(target: "inf", "Unexpected packet {} from {}", p, peer), - }; - } - - pub fn propagate_packet(&mut self, io: &mut SyncIo, packet: Bytes) { - let lucky_peers: Vec<_> = self.peers.keys().cloned().collect(); - trace!(target: "inf", "Sending packets to {:?}", lucky_peers); - for peer_id in lucky_peers { - self.send_packet(io, peer_id, GENERIC_PACKET, packet.clone()); - } - } -} - -#[cfg(test)] -mod tests { -} - diff --git a/sync/src/lib.rs b/sync/src/lib.rs index d7c208030..2061e4e3a 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -50,7 +50,6 @@ mod chain; mod blocks; mod block_sync; mod sync_io; -mod infinity; mod snapshot; mod transactions_stats; From ad440a12bdad91766657d039d3423a703bed32ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 30 Nov 2016 13:47:14 +0100 Subject: [PATCH 103/295] EthMultiStore --- ethcore/src/account_provider.rs | 27 ++++- ethstore/src/dir/disk.rs | 5 +- ethstore/src/dir/geth.rs | 5 +- ethstore/src/dir/mod.rs | 3 +- ethstore/src/dir/parity.rs | 5 +- ethstore/src/ethstore.rs | 143 +++++++++++++++++++++++++-- ethstore/tests/util/transient_dir.rs | 5 +- 7 files changed, 169 insertions(+), 24 deletions(-) diff --git a/ethcore/src/account_provider.rs b/ethcore/src/account_provider.rs index 917ae8b8b..e2ccd1d83 100644 --- a/ethcore/src/account_provider.rs +++ b/ethcore/src/account_provider.rs @@ -163,12 +163,19 @@ impl AddressBook { } } +fn transient_sstore() -> Box { + Box::new(EthStore::open(Box::new(NullDir::default())).expect("NullDir load always succeeds; qed"))) +} + /// Account management. /// Responsible for unlocking accounts. pub struct AccountProvider { - unlocked: Mutex>, - sstore: Box, address_book: Mutex, + unlocked: Mutex>, + /// Accounts on disk + sstore: Box, + /// Accounts unlocked with rolling tokens + transient_sstore: Box, } impl AccountProvider { @@ -178,6 +185,7 @@ impl AccountProvider { unlocked: Mutex::new(HashMap::new()), address_book: Mutex::new(AddressBook::new(sstore.local_path().into())), sstore: sstore, + transient_sstore: transient_sstore(), } } @@ -186,8 +194,8 @@ impl AccountProvider { AccountProvider { unlocked: Mutex::new(HashMap::new()), address_book: Mutex::new(AddressBook::transient()), - sstore: Box::new(EthStore::open(Box::new(NullDir::default())) - .expect("NullDir load always succeeds; qed")) + sstore: transient_sstore(), + transient_sstore: transient_sstore(), } } @@ -433,4 +441,15 @@ mod tests { ap.unlocked.lock().get_mut(&kp.address()).unwrap().unlock = Unlock::Timed(Instant::now()); assert!(ap.sign(kp.address(), None, Default::default()).is_err()); } + + #[test] + fn should_sign_and_return_token() { + let kp = Random.generate().unwrap(); + // given + let ap = AccountProvider::transient_provider(); + assert!(ap.insert_account(kp.secret().clone(), "test").is_ok()); + + // when + let (_signature, token) = ap.sign_with_token(kp.address(), "test", Default::default()).unwrap(); + } } diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index 56b2c1ccb..22093171e 100644 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -18,7 +18,6 @@ use std::{fs, io}; use std::path::{PathBuf, Path}; use std::collections::HashMap; use time; -use ethkey::Address; use {json, SafeAccount, Error}; use json::UUID; use super::KeyDirectory; @@ -138,12 +137,12 @@ impl KeyDirectory for DiskDirectory { Ok(account) } - fn remove(&self, address: &Address) -> Result<(), Error> { + fn remove(&self, account: &SafeAccount) -> Result<(), Error> { // enumerate all entries in keystore // and find entry with given address let to_remove = try!(self.files()) .into_iter() - .find(|&(_, ref account)| &account.address == address); + .find(|&(_, ref acc)| acc == account); // remove it match to_remove { diff --git a/ethstore/src/dir/geth.rs b/ethstore/src/dir/geth.rs index f63ebbea2..a5367f98d 100644 --- a/ethstore/src/dir/geth.rs +++ b/ethstore/src/dir/geth.rs @@ -16,7 +16,6 @@ use std::env; use std::path::PathBuf; -use ethkey::Address; use {SafeAccount, Error}; use super::{KeyDirectory, DiskDirectory, DirectoryType}; @@ -89,7 +88,7 @@ impl KeyDirectory for GethDirectory { self.dir.insert(account) } - fn remove(&self, address: &Address) -> Result<(), Error> { - self.dir.remove(address) + fn remove(&self, account: &SafeAccount) -> Result<(), Error> { + self.dir.remove(account) } } diff --git a/ethstore/src/dir/mod.rs b/ethstore/src/dir/mod.rs index e29bd1ec4..0da4d71fb 100644 --- a/ethstore/src/dir/mod.rs +++ b/ethstore/src/dir/mod.rs @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use ethkey::Address; use std::path::{PathBuf}; use {SafeAccount, Error}; @@ -30,7 +29,7 @@ pub enum DirectoryType { pub trait KeyDirectory: Send + Sync { fn load(&self) -> Result, Error>; fn insert(&self, account: SafeAccount) -> Result; - fn remove(&self, address: &Address) -> Result<(), Error>; + fn remove(&self, account: &SafeAccount) -> Result<(), Error>; fn path(&self) -> Option<&PathBuf> { None } } diff --git a/ethstore/src/dir/parity.rs b/ethstore/src/dir/parity.rs index 7aa50c80b..c5d0057d8 100644 --- a/ethstore/src/dir/parity.rs +++ b/ethstore/src/dir/parity.rs @@ -16,7 +16,6 @@ use std::env; use std::path::PathBuf; -use ethkey::Address; use {SafeAccount, Error}; use super::{KeyDirectory, DiskDirectory, DirectoryType}; @@ -68,7 +67,7 @@ impl KeyDirectory for ParityDirectory { self.dir.insert(account) } - fn remove(&self, address: &Address) -> Result<(), Error> { - self.dir.remove(address) + fn remove(&self, account: &SafeAccount) -> Result<(), Error> { + self.dir.remove(account) } } diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index 4991c4714..f83e5fd3a 100644 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -53,7 +53,7 @@ impl EthStore { fn save(&self, account: SafeAccount) -> Result<(), Error> { // save to file - let account = try!(self.dir.insert(account.clone())); + let account = try!(self.dir.insert(account)); // update cache let mut cache = self.cache.write(); @@ -124,13 +124,11 @@ impl SecretStore for EthStore { } fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> { - let can_remove = { - let account = try!(self.get(address)); - account.check_password(password) - }; + let account = try!(self.get(address)); + let can_remove = account.check_password(password); if can_remove { - try!(self.dir.remove(address)); + try!(self.dir.remove(&account)); let mut cache = self.cache.write(); cache.remove(address); Ok(()) @@ -197,3 +195,136 @@ impl SecretStore for EthStore { import::import_geth_accounts(&*self.dir, desired.into_iter().collect(), testnet) } } + +/// Similar to `EthStore` but may store many accounts (with different passwords) for the same `Address` +pub struct EthMultiStore { + dir: Box, + iterations: u32, + cache: RwLock>>, +} + +impl EthMultiStore { + + pub fn open(directory: Box) -> Result { + Self::open_with_iterations(directory, KEY_ITERATIONS as u32) + } + + pub fn open_with_iterations(directory: Box, iterations: u32) -> Result { + let mut store = EthMultiStore { + dir: directory, + iterations: iterations, + cache: Default::default(), + }; + try!(store.reload_accounts()); + Ok(store) + } + + fn reload_accounts(&self) -> Result<(), Error> { + let mut cache = self.cache.write(); + let accounts = try!(self.dir.load()); + + let mut new_accounts = BTreeMap::new(); + for account in accounts { + let mut entry = new_accounts.entry(account.address.clone()).or_insert_with(Vec::new); + entry.push(account); + } + mem::replace(&mut *cache, new_accounts); + Ok(()) + } + + fn get(&self, address: &Address) -> Result, Error> { + { + let cache = self.cache.read(); + if let Some(accounts) = cache.get(address) { + if !accounts.is_empty() { + return Ok(accounts.clone()) + } + } + } + + try!(self.reload_accounts()); + let cache = self.cache.read(); + let accounts = try!(cache.get(address).cloned().ok_or(Error::InvalidAccount)); + if accounts.is_empty() { + Err(Error::InvalidAccount) + } else { + Ok(accounts) + } + } + + pub fn insert_account(&self, account: SafeAccount) -> Result<(), Error> { + //save to file + let account = try!(self.dir.insert(account)); + + // update cache + let mut cache = self.cache.write(); + let mut accounts = cache.entry(account.address.clone()).or_insert_with(Vec::new); + accounts.push(account); + Ok(()) + } + + pub fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> { + let accounts = try!(self.get(address)); + + for account in accounts { + // Skip if password is invalid + if !account.check_password(password) { + continue; + } + + // Remove from dir + try!(self.dir.remove(&account)); + + // Remove from cache + let mut cache = self.cache.write(); + let is_empty = { + let mut accounts = cache.get_mut(address).expect("Entry exists, because it was returned by `get`; qed"); + if let Some(position) = accounts.iter().position(|acc| acc == &account) { + accounts.remove(position); + } + accounts.is_empty() + }; + + if is_empty { + cache.remove(address); + } + + return Ok(()); + } + Err(Error::InvalidPassword) + } + + pub fn change_password(&self, address: &Address, old_password: &str, new_password: &str) -> Result<(), Error> { + let accounts = try!(self.get(address)); + for account in accounts { + // First remove + try!(self.remove_account(&address, old_password)); + // Then insert back with new password + let new_account = try!(account.change_password(old_password, new_password, self.iterations)); + try!(self.insert_account(new_account)); + } + Ok(()) + } + + pub fn sign(&self, address: &Address, password: &str, message: &Message) -> Result { + let accounts = try!(self.get(address)); + for account in accounts { + if account.check_password(password) { + return account.sign(password, message); + } + } + + Err(Error::InvalidPassword) + } + + pub fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error> { + let accounts = try!(self.get(account)); + for account in accounts { + if account.check_password(password) { + return account.decrypt(password, shared_mac, message); + } + } + Err(Error::InvalidPassword) + } +} + diff --git a/ethstore/tests/util/transient_dir.rs b/ethstore/tests/util/transient_dir.rs index 23523e48c..76010182e 100644 --- a/ethstore/tests/util/transient_dir.rs +++ b/ethstore/tests/util/transient_dir.rs @@ -18,7 +18,6 @@ use std::path::PathBuf; use std::{env, fs}; use rand::{Rng, OsRng}; use ethstore::dir::{KeyDirectory, DiskDirectory}; -use ethstore::ethkey::Address; use ethstore::{Error, SafeAccount}; pub fn random_dir() -> PathBuf { @@ -68,7 +67,7 @@ impl KeyDirectory for TransientDir { self.dir.insert(account) } - fn remove(&self, address: &Address) -> Result<(), Error> { - self.dir.remove(address) + fn remove(&self, account: &SafeAccount) -> Result<(), Error> { + self.dir.remove(account) } } From 4ef5badceaccfd643bea3a6676bf3d667910c45d Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 30 Nov 2016 12:58:45 +0000 Subject: [PATCH 104/295] fix parity tests merge --- parity/cli/mod.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index e5f66cfbe..3f4f647a0 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -574,16 +574,11 @@ mod tests { flag_gas_floor_target: "4700000".into(), flag_gas_cap: "6283184".into(), flag_extra_data: Some("Parity".into()), -<<<<<<< HEAD - flag_tx_queue_size: 2048usize, - flag_tx_queue_gas: "auto".into(), -======= flag_tx_queue_size: 1024usize, flag_tx_queue_gas: "auto".into(), flag_tx_queue_strategy: "gas_factor".into(), flag_tx_queue_ban_count: 1u16, flag_tx_queue_ban_time: 180u16, ->>>>>>> parity/master flag_remove_solved: false, flag_notify_work: Some("http://localhost:3001".into()), @@ -740,16 +735,11 @@ mod tests { price_update_period: Some("hourly".into()), gas_floor_target: None, gas_cap: None, -<<<<<<< HEAD - tx_queue_size: Some(2048), - tx_queue_gas: Some("auto".into()), -======= tx_queue_size: Some(1024), tx_queue_gas: Some("auto".into()), tx_queue_strategy: None, tx_queue_ban_count: None, tx_queue_ban_time: None, ->>>>>>> parity/master tx_gas_limit: None, tx_time_limit: None, extra_data: None, From 34d5017950b2a9f0609146a516303974b036d9ce Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 30 Nov 2016 12:59:33 +0000 Subject: [PATCH 105/295] hold password in engine, add rpc --- ethcore/src/engines/mod.rs | 3 +++ ethcore/src/engines/tendermint/mod.rs | 21 ++++++++++++++++----- ethcore/src/miner/miner.rs | 15 ++++++++++++++- ethcore/src/miner/mod.rs | 3 +++ rpc/src/v1/impls/parity_set.rs | 6 ++++++ rpc/src/v1/tests/helpers/miner_service.rs | 9 +++++++++ rpc/src/v1/traits/parity_set.rs | 4 ++++ 7 files changed, 55 insertions(+), 6 deletions(-) diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 91557f8c3..67e89b834 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -187,6 +187,9 @@ pub trait Engine : Sync + Send { /// Add an account provider useful for Engines that sign stuff. fn register_account_provider(&self, _account_provider: Arc) {} + /// Register an account which signs consensus messages. + fn set_signer(&self, _address: Address, _password: String) {} + /// Check if new block should be chosen as the one in chain. fn is_new_best_block(&self, best_total_difficulty: U256, _best_header: HeaderView, parent_details: &BlockDetails, new_header: &HeaderView) -> bool { ethash::is_new_best_block(best_total_difficulty, parent_details, new_header) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 28aae2b10..452aed8aa 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -66,6 +66,8 @@ pub struct Tendermint { step_service: IoService, /// Address to be used as authority. authority: RwLock
, + /// Password used for signing messages. + password: RwLock>, /// Blockchain height. height: AtomicUsize, /// Consensus round. @@ -98,6 +100,7 @@ impl Tendermint { builtins: builtins, step_service: try!(IoService::::start()), authority: RwLock::new(Address::default()), + password: RwLock::new(None), height: AtomicUsize::new(1), round: AtomicUsize::new(0), step: RwLock::new(Step::Propose), @@ -144,7 +147,7 @@ impl Tendermint { fn generate_message(&self, block_hash: Option) -> Option { if let Some(ref ap) = *self.account_provider.lock() { message_full_rlp( - |mh| ap.sign(*self.authority.read(), None, mh).ok().map(H520::from), + |mh| ap.sign(*self.authority.read(), self.password.read().clone(), mh).ok().map(H520::from), self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read(), @@ -333,7 +336,7 @@ impl Engine for Tendermint { let round = self.round.load(AtomicOrdering::SeqCst); let bh = Some(header.bare_hash()); let vote_info = message_info_rlp(height, round, Step::Propose, bh); - if let Ok(signature) = ap.sign(*author, None, vote_info.sha3()).map(H520::from) { + if let Ok(signature) = ap.sign(*author, self.password.read().clone(), vote_info.sha3()).map(H520::from) { self.votes.vote(ConsensusMessage { signature: signature, height: height, round: round, step: Step::Propose, block_hash: bh }, *author); *self.proposal.write() = Some(header.bare_hash()); Some(vec![ @@ -472,6 +475,11 @@ impl Engine for Tendermint { t.sender().map(|_|()) // Perform EC recovery and cache sender } + fn set_signer(&self, address: Address, password: String) { + *self.authority.write() = address; + *self.password.write() = Some(password); + } + fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { let new_signatures = new_header.seal().get(2).expect("Tendermint seal should have three elements.").len(); let best_signatures = best_header.seal().get(2).expect("Tendermint seal should have three elements.").len(); @@ -695,7 +703,6 @@ mod tests { let proposal = Some(b.header().bare_hash()); // Register IoHandler remembers messages. - seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v0, v1); let io_service = IoService::::start().unwrap(); let test_io = TestIo::new(); io_service.register_handler(test_io.clone()).unwrap(); @@ -708,8 +715,12 @@ mod tests { vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); + // Wait a bit for async stuff. ::std::thread::sleep(::std::time::Duration::from_millis(500)); - assert_eq!(test_io.received.read()[5], ClientIoMessage::SubmitSeal(proposal.unwrap(), seal)); - println!("{:?}", *test_io.received.read()); + seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v0, v1); + let first = test_io.received.read()[5] == ClientIoMessage::SubmitSeal(proposal.unwrap(), seal.clone()); + seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v1, v0); + let second = test_io.received.read()[5] == ClientIoMessage::SubmitSeal(proposal.unwrap(), seal); + assert!(first ^ second); } } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 5aaa5b479..b1984dd43 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -19,7 +19,7 @@ use std::time::{Instant, Duration}; use util::*; use util::using_queue::{UsingQueue, GetAction}; -use account_provider::AccountProvider; +use account_provider::{AccountProvider, Error as AccountError}; use views::{BlockView, HeaderView}; use header::Header; use state::{State, CleanupMode}; @@ -735,6 +735,19 @@ impl MinerService for Miner { *self.author.write() = author; } + fn set_signer(&self, address: Address, password: String) -> Result<(), AccountError> { + if self.seals_internally { + if let Some(ref ap) = self.accounts { + try!(ap.sign(address.clone(), Some(password.clone()), Default::default())); + } + let mut sealing_work = self.sealing_work.lock(); + sealing_work.enabled = self.engine.is_sealer(&address).unwrap_or(false); + *self.author.write() = address; + self.engine.set_signer(address, password); + } + Ok(()) + } + fn set_extra_data(&self, extra_data: Bytes) { *self.extra_data.write() = extra_data; } diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 1fb2244fd..89937e115 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -76,6 +76,9 @@ pub trait MinerService : Send + Sync { /// Set the author that we will seal blocks as. fn set_author(&self, author: Address); + /// Set info necessary to sign consensus messages. + fn set_signer(&self, address: Address, password: String) -> Result<(), ::account_provider::Error>; + /// Get the extra_data that we will seal blocks with. fn extra_data(&self) -> Bytes; diff --git a/rpc/src/v1/impls/parity_set.rs b/rpc/src/v1/impls/parity_set.rs index 47634d518..11bc48268 100644 --- a/rpc/src/v1/impls/parity_set.rs +++ b/rpc/src/v1/impls/parity_set.rs @@ -116,6 +116,12 @@ impl ParitySet for ParitySetClient where Ok(true) } + fn set_sealer(&self, address: H160, password: String) -> Result { + try!(self.active()); + try!(take_weak!(self.miner).set_signer(address.into(), password).map_err(Into::into).map_err(errors::from_password_error)); + Ok(true) + } + fn set_transactions_limit(&self, limit: usize) -> Result { try!(self.active()); diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index ad55faa7b..87efd2425 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -25,6 +25,7 @@ use ethcore::header::BlockNumber; use ethcore::transaction::SignedTransaction; use ethcore::receipt::{Receipt, RichReceipt}; use ethcore::miner::{MinerService, MinerStatus, TransactionImportResult, LocalTransactionStatus}; +use ethcore::account_provider::Error as AccountError; /// Test miner service. pub struct TestMinerService { @@ -43,6 +44,7 @@ pub struct TestMinerService { min_gas_price: RwLock, gas_range_target: RwLock<(U256, U256)>, + password: RwLock, author: RwLock
, extra_data: RwLock, limit: RwLock, @@ -61,6 +63,7 @@ impl Default for TestMinerService { min_gas_price: RwLock::new(U256::from(20_000_000)), gas_range_target: RwLock::new((U256::from(12345), U256::from(54321))), author: RwLock::new(Address::zero()), + password: RwLock::new(String::new()), extra_data: RwLock::new(vec![1, 2, 3, 4]), limit: RwLock::new(1024), tx_gas_limit: RwLock::new(!U256::zero()), @@ -83,6 +86,12 @@ impl MinerService for TestMinerService { *self.author.write() = author; } + fn set_signer(&self, address: Address, password: String) -> Result<(), AccountError> { + *self.author.write() = address; + *self.password.write() = password; + Ok(()) + } + fn set_extra_data(&self, extra_data: Bytes) { *self.extra_data.write() = extra_data; } diff --git a/rpc/src/v1/traits/parity_set.rs b/rpc/src/v1/traits/parity_set.rs index c83eff022..89d92e043 100644 --- a/rpc/src/v1/traits/parity_set.rs +++ b/rpc/src/v1/traits/parity_set.rs @@ -44,6 +44,10 @@ build_rpc_trait! { #[rpc(name = "parity_setAuthor")] fn set_author(&self, H160) -> Result; + /// Sets account for signing consensus messages. + #[rpc(name = "parity_setSealer")] + fn set_sealer(&self, H160, String) -> Result; + /// Sets the limits for transaction queue. #[rpc(name = "parity_setTransactionsLimit")] fn set_transactions_limit(&self, usize) -> Result; From 61f1699c0eea4fd8b345d2a5f0f8680aed256e7d Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 30 Nov 2016 13:39:28 +0000 Subject: [PATCH 106/295] fix merge --- sync/src/chain.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 609055422..e8c9afb37 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -1419,14 +1419,9 @@ impl ChainSync { /// Send Status message fn send_status(&mut self, io: &mut SyncIo, peer: PeerId) -> Result<(), NetworkError> { -<<<<<<< HEAD let warp_protocol_version = io.protocol_version(&WARP_SYNC_PROTOCOL_ID, peer); let warp_protocol = warp_protocol_version != 0; let protocol = if warp_protocol { warp_protocol_version } else { PROTOCOL_VERSION_63 }; -======= - let warp_protocol = io.protocol_version(&WARP_SYNC_PROTOCOL_ID, peer) != 0; - let protocol = if warp_protocol { PROTOCOL_VERSION_1 } else { io.eth_protocol_version(peer) }; ->>>>>>> master trace!(target: "sync", "Sending status to {}, protocol version {}", peer, protocol); let mut packet = RlpStream::new_list(if warp_protocol { 7 } else { 5 }); let chain = io.chain().chain_info(); From 6397556cbb5f240a2f77dc227d9d02fcf42a0240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 30 Nov 2016 15:08:38 +0100 Subject: [PATCH 107/295] Sign with token support --- ethcore/src/account_provider.rs | 72 ++++++++++---- ethstore/src/ethstore.rs | 168 +++++++++++++++----------------- ethstore/src/lib.rs | 6 +- ethstore/src/random.rs | 8 +- ethstore/src/secret_store.rs | 15 ++- 5 files changed, 152 insertions(+), 117 deletions(-) diff --git a/ethcore/src/account_provider.rs b/ethcore/src/account_provider.rs index e2ccd1d83..311204bb1 100644 --- a/ethcore/src/account_provider.rs +++ b/ethcore/src/account_provider.rs @@ -20,8 +20,8 @@ use std::{fs, fmt}; use std::collections::HashMap; use std::path::PathBuf; use std::time::{Instant, Duration}; -use util::{Mutex, RwLock}; -use ethstore::{SecretStore, Error as SSError, SafeAccount, EthStore}; +use util::{Mutex, RwLock, Itertools}; +use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, SafeAccount, EthStore, EthMultiStore, random_string}; use ethstore::dir::{KeyDirectory}; use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator}; use ethjson::misc::AccountMeta; @@ -72,21 +72,35 @@ impl From for Error { #[derive(Default)] struct NullDir { - accounts: RwLock>, + accounts: RwLock>>, } impl KeyDirectory for NullDir { fn load(&self) -> Result, SSError> { - Ok(self.accounts.read().values().cloned().collect()) + Ok(self.accounts.read().values().cloned().flatten().collect()) } fn insert(&self, account: SafeAccount) -> Result { - self.accounts.write().insert(account.address.clone(), account.clone()); + self.accounts.write() + .entry(account.address.clone()) + .or_insert_with(Vec::new) + .push(account.clone()); Ok(account) } - fn remove(&self, address: &Address) -> Result<(), SSError> { - self.accounts.write().remove(address); + fn remove(&self, account: &SafeAccount) -> Result<(), SSError> { + let mut accounts = self.accounts.write(); + let is_empty = if let Some(mut accounts) = accounts.get_mut(&account.address) { + if let Some(position) = accounts.iter().position(|acc| acc == account) { + accounts.remove(position); + } + accounts.is_empty() + } else { + false + }; + if is_empty { + accounts.remove(&account.address); + } Ok(()) } } @@ -163,10 +177,12 @@ impl AddressBook { } } -fn transient_sstore() -> Box { - Box::new(EthStore::open(Box::new(NullDir::default())).expect("NullDir load always succeeds; qed"))) +fn transient_sstore() -> EthMultiStore { + EthMultiStore::open(Box::new(NullDir::default())).expect("NullDir load always succeeds; qed") } +type AccountToken = String; + /// Account management. /// Responsible for unlocking accounts. pub struct AccountProvider { @@ -175,7 +191,7 @@ pub struct AccountProvider { /// Accounts on disk sstore: Box, /// Accounts unlocked with rolling tokens - transient_sstore: Box, + transient_sstore: EthMultiStore, } impl AccountProvider { @@ -194,7 +210,7 @@ impl AccountProvider { AccountProvider { unlocked: Mutex::new(HashMap::new()), address_book: Mutex::new(AddressBook::transient()), - sstore: transient_sstore(), + sstore: Box::new(EthStore::open(Box::new(NullDir::default())).expect("NullDir load always succeeds; qed")), transient_sstore: transient_sstore(), } } @@ -285,11 +301,8 @@ impl AccountProvider { /// Returns `true` if the password for `account` is `password`. `false` if not. pub fn test_password(&self, account: &Address, password: &str) -> Result { - match self.sstore.sign(account, password, &Default::default()) { - Ok(_) => Ok(true), - Err(SSError::InvalidPassword) => Ok(false), - Err(e) => Err(Error::SStore(e)), - } + self.sstore.test_password(account, password) + .map_err(Into::into) } /// Permanently removes an account. @@ -368,6 +381,26 @@ impl AccountProvider { Ok(try!(self.sstore.sign(&account, &password, &message))) } + /// Signs given message with supplied token. Returns a token to use in next signing within this session. + pub fn sign_with_token(&self, account: Address, token: AccountToken, message: Message) -> Result<(Signature, AccountToken), Error> { + let is_std_password = try!(self.sstore.test_password(&account, &token)); + + let new_token = random_string(16); + let signature = if is_std_password { + // Insert to transient store + try!(self.sstore.copy_account(&self.transient_sstore, &account, &token, &new_token)); + // sign + try!(self.sstore.sign(&account, &token, &message)) + } else { + // check transient store + try!(self.transient_sstore.change_password(&account, &token, &new_token)); + // and sign + try!(self.transient_sstore.sign(&account, &new_token, &message)) + }; + + Ok((signature, new_token)) + } + /// Decrypts a message. If password is not provided the account must be unlocked. pub fn decrypt(&self, account: Address, password: Option, shared_mac: &[u8], message: &[u8]) -> Result, Error> { let password = try!(password.map(Ok).unwrap_or_else(|| self.password(&account))); @@ -450,6 +483,11 @@ mod tests { assert!(ap.insert_account(kp.secret().clone(), "test").is_ok()); // when - let (_signature, token) = ap.sign_with_token(kp.address(), "test", Default::default()).unwrap(); + let (_signature, token) = ap.sign_with_token(kp.address(), "test".into(), Default::default()).unwrap(); + + // then + ap.sign_with_token(kp.address(), token.clone(), Default::default()) + .expect("First usage of token should be correct."); + assert!(ap.sign_with_token(kp.address(), token, Default::default()).is_err(), "Second usage of the same token should fail."); } } diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index f83e5fd3a..158a7c55a 100644 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -22,7 +22,7 @@ use random::Random; use ethkey::{Signature, Address, Message, Secret, Public}; use dir::KeyDirectory; use account::SafeAccount; -use {Error, SecretStore}; +use {Error, SimpleSecretStore, SecretStore}; use json; use json::UUID; use parking_lot::RwLock; @@ -30,9 +30,7 @@ use presale::PresaleWallet; use import; pub struct EthStore { - dir: Box, - iterations: u32, - cache: RwLock>, + store: EthMultiStore, } impl EthStore { @@ -41,57 +39,46 @@ impl EthStore { } pub fn open_with_iterations(directory: Box, iterations: u32) -> Result { - let accounts = try!(directory.load()); - let cache = accounts.into_iter().map(|account| (account.address.clone(), account)).collect(); - let store = EthStore { - dir: directory, - iterations: iterations, - cache: RwLock::new(cache), - }; - Ok(store) - } - - fn save(&self, account: SafeAccount) -> Result<(), Error> { - // save to file - let account = try!(self.dir.insert(account)); - - // update cache - let mut cache = self.cache.write(); - cache.insert(account.address.clone(), account); - Ok(()) - } - - fn reload_accounts(&self) -> Result<(), Error> { - let mut cache = self.cache.write(); - let accounts = try!(self.dir.load()); - let new_accounts: BTreeMap<_, _> = accounts.into_iter().map(|account| (account.address.clone(), account)).collect(); - mem::replace(&mut *cache, new_accounts); - Ok(()) + Ok(EthStore { + store: try!(EthMultiStore::open_with_iterations(directory, iterations)), + }) } fn get(&self, address: &Address) -> Result { - { - let cache = self.cache.read(); - if let Some(account) = cache.get(address) { - return Ok(account.clone()) - } - } - try!(self.reload_accounts()); - let cache = self.cache.read(); - cache.get(address).cloned().ok_or(Error::InvalidAccount) + let mut accounts = try!(self.store.get(address)).into_iter(); + accounts.next().ok_or(Error::InvalidAccount) + } +} + +impl SimpleSecretStore for EthStore { + fn insert_account(&self, secret: Secret, password: &str) -> Result { + self.store.insert_account(secret, password) + } + + fn accounts(&self) -> Result, Error> { + self.store.accounts() + } + + fn change_password(&self, address: &Address, old_password: &str, new_password: &str) -> Result<(), Error> { + self.store.change_password(address, old_password, new_password) + } + + fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> { + self.store.remove_account(address, password) + } + + fn sign(&self, address: &Address, password: &str, message: &Message) -> Result { + let account = try!(self.get(address)); + account.sign(password, message) + } + + fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error> { + let account = try!(self.get(account)); + account.decrypt(password, shared_mac, message) } } impl SecretStore for EthStore { - fn insert_account(&self, secret: Secret, password: &str) -> Result { - let keypair = try!(KeyPair::from_secret(secret).map_err(|_| Error::CreationFailed)); - let id: [u8; 16] = Random::random(); - let account = SafeAccount::create(&keypair, id, password, self.iterations, "".to_owned(), "{}".to_owned()); - let address = account.address.clone(); - try!(self.save(account)); - Ok(address) - } - fn import_presale(&self, json: &[u8], password: &str) -> Result { let json_wallet = try!(json::PresaleWallet::load(json).map_err(|_| Error::InvalidKeyFile("Invalid JSON format".to_owned()))); let wallet = PresaleWallet::from(json_wallet); @@ -105,46 +92,20 @@ impl SecretStore for EthStore { let secret = try!(safe_account.crypto.secret(password).map_err(|_| Error::InvalidPassword)); safe_account.address = try!(KeyPair::from_secret(secret)).address(); let address = safe_account.address.clone(); - try!(self.save(safe_account)); + try!(self.store.save(safe_account)); Ok(address) } - fn accounts(&self) -> Result, Error> { - try!(self.reload_accounts()); - Ok(self.cache.read().keys().cloned().collect()) - } - - fn change_password(&self, address: &Address, old_password: &str, new_password: &str) -> Result<(), Error> { - // change password + fn test_password(&self, address: &Address, password: &str) -> Result { let account = try!(self.get(address)); - let account = try!(account.change_password(old_password, new_password, self.iterations)); - - // save to file - self.save(account) + Ok(account.check_password(password)) } - fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> { + fn copy_account(&self, new_store: &SimpleSecretStore, address: &Address, password: &str, new_password: &str) -> Result<(), Error> { let account = try!(self.get(address)); - let can_remove = account.check_password(password); - - if can_remove { - try!(self.dir.remove(&account)); - let mut cache = self.cache.write(); - cache.remove(address); - Ok(()) - } else { - Err(Error::InvalidPassword) - } - } - - fn sign(&self, address: &Address, password: &str, message: &Message) -> Result { - let account = try!(self.get(address)); - account.sign(password, message) - } - - fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error> { - let account = try!(self.get(account)); - account.decrypt(password, shared_mac, message) + let secret = try!(account.crypto.secret(password)); + try!(new_store.insert_account(secret, new_password)); + Ok(()) } fn public(&self, account: &Address, password: &str) -> Result { @@ -172,7 +133,7 @@ impl SecretStore for EthStore { account.name = name; // save to file - self.save(account) + self.store.save(account) } fn set_meta(&self, address: &Address, meta: String) -> Result<(), Error> { @@ -180,11 +141,11 @@ impl SecretStore for EthStore { account.meta = meta; // save to file - self.save(account) + self.store.save(account) } fn local_path(&self) -> String { - self.dir.path().map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|| String::new()) + self.store.dir.path().map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|| String::new()) } fn list_geth_accounts(&self, testnet: bool) -> Vec
{ @@ -192,7 +153,7 @@ impl SecretStore for EthStore { } fn import_geth_accounts(&self, desired: Vec
, testnet: bool) -> Result, Error> { - import::import_geth_accounts(&*self.dir, desired.into_iter().collect(), testnet) + import::import_geth_accounts(&*self.store.dir, desired.into_iter().collect(), testnet) } } @@ -210,7 +171,7 @@ impl EthMultiStore { } pub fn open_with_iterations(directory: Box, iterations: u32) -> Result { - let mut store = EthMultiStore { + let store = EthMultiStore { dir: directory, iterations: iterations, cache: Default::default(), @@ -252,7 +213,7 @@ impl EthMultiStore { } } - pub fn insert_account(&self, account: SafeAccount) -> Result<(), Error> { + fn save(&self, account: SafeAccount) -> Result<(), Error> { //save to file let account = try!(self.dir.insert(account)); @@ -263,7 +224,24 @@ impl EthMultiStore { Ok(()) } - pub fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> { +} + +impl SimpleSecretStore for EthMultiStore { + fn insert_account(&self, secret: Secret, password: &str) -> Result { + let keypair = try!(KeyPair::from_secret(secret).map_err(|_| Error::CreationFailed)); + let id: [u8; 16] = Random::random(); + let account = SafeAccount::create(&keypair, id, password, self.iterations, "".to_owned(), "{}".to_owned()); + let address = account.address.clone(); + try!(self.save(account)); + Ok(address) + } + + fn accounts(&self) -> Result, Error> { + try!(self.reload_accounts()); + Ok(self.cache.read().keys().cloned().collect()) + } + + fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> { let accounts = try!(self.get(address)); for account in accounts { @@ -294,19 +272,19 @@ impl EthMultiStore { Err(Error::InvalidPassword) } - pub fn change_password(&self, address: &Address, old_password: &str, new_password: &str) -> Result<(), Error> { + fn change_password(&self, address: &Address, old_password: &str, new_password: &str) -> Result<(), Error> { let accounts = try!(self.get(address)); for account in accounts { // First remove try!(self.remove_account(&address, old_password)); // Then insert back with new password let new_account = try!(account.change_password(old_password, new_password, self.iterations)); - try!(self.insert_account(new_account)); + try!(self.save(new_account)); } Ok(()) } - pub fn sign(&self, address: &Address, password: &str, message: &Message) -> Result { + fn sign(&self, address: &Address, password: &str, message: &Message) -> Result { let accounts = try!(self.get(address)); for account in accounts { if account.check_password(password) { @@ -317,7 +295,7 @@ impl EthMultiStore { Err(Error::InvalidPassword) } - pub fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error> { + fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error> { let accounts = try!(self.get(account)); for account in accounts { if account.check_password(password) { @@ -328,3 +306,9 @@ impl EthMultiStore { } } +#[cfg(test)] +mod tests { + fn should_have_some_tests() { + assert_eq!(true, false) + } +} diff --git a/ethstore/src/lib.rs b/ethstore/src/lib.rs index f8619ff19..3fe56b7d3 100644 --- a/ethstore/src/lib.rs +++ b/ethstore/src/lib.rs @@ -50,8 +50,8 @@ mod secret_store; pub use self::account::SafeAccount; pub use self::error::Error; -pub use self::ethstore::EthStore; +pub use self::ethstore::{EthStore, EthMultiStore}; pub use self::import::{import_accounts, read_geth_accounts}; pub use self::presale::PresaleWallet; -pub use self::secret_store::SecretStore; -pub use self::random::random_phrase; +pub use self::secret_store::{SimpleSecretStore, SecretStore}; +pub use self::random::{random_phrase, random_string}; diff --git a/ethstore/src/random.rs b/ethstore/src/random.rs index 954ec500f..d98f2fcdf 100644 --- a/ethstore/src/random.rs +++ b/ethstore/src/random.rs @@ -51,10 +51,16 @@ pub fn random_phrase(words: usize) -> String { .map(|s| s.to_owned()) .collect(); } - let mut rng = OsRng::new().unwrap(); + let mut rng = OsRng::new().expect("Not able to operate without random source."); (0..words).map(|_| rng.choose(&WORDS).unwrap()).join(" ") } +/// Generate a random string of given length. +pub fn random_string(length: usize) -> String { + let mut rng = OsRng::new().expect("Not able to operate without random source."); + rng.gen_ascii_chars().take(length).collect() +} + #[cfg(test)] mod tests { use super::random_phrase; diff --git a/ethstore/src/secret_store.rs b/ethstore/src/secret_store.rs index 06f38922b..b62189aca 100644 --- a/ethstore/src/secret_store.rs +++ b/ethstore/src/secret_store.rs @@ -18,18 +18,25 @@ use ethkey::{Address, Message, Signature, Secret, Public}; use Error; use json::UUID; -pub trait SecretStore: Send + Sync { +pub trait SimpleSecretStore: Send + Sync { fn insert_account(&self, secret: Secret, password: &str) -> Result; - fn import_presale(&self, json: &[u8], password: &str) -> Result; - fn import_wallet(&self, json: &[u8], password: &str) -> Result; fn change_password(&self, account: &Address, old_password: &str, new_password: &str) -> Result<(), Error>; fn remove_account(&self, account: &Address, password: &str) -> Result<(), Error>; fn sign(&self, account: &Address, password: &str, message: &Message) -> Result; fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error>; - fn public(&self, account: &Address, password: &str) -> Result; fn accounts(&self) -> Result, Error>; +} + +pub trait SecretStore: SimpleSecretStore { + fn import_presale(&self, json: &[u8], password: &str) -> Result; + fn import_wallet(&self, json: &[u8], password: &str) -> Result; + fn copy_account(&self, new_store: &SimpleSecretStore, account: &Address, password: &str, new_password: &str) -> Result<(), Error>; + fn test_password(&self, account: &Address, password: &str) -> Result; + + fn public(&self, account: &Address, password: &str) -> Result; + fn uuid(&self, account: &Address) -> Result; fn name(&self, account: &Address) -> Result; fn meta(&self, account: &Address) -> Result; From 73e7908325e011eb1e2cb9afeb572d3ea0647308 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 30 Nov 2016 14:30:21 +0000 Subject: [PATCH 108/295] test password registration --- ethcore/src/engines/tendermint/mod.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 452aed8aa..b70c7a64f 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -498,6 +498,7 @@ impl Engine for Tendermint { #[cfg(test)] mod tests { use util::*; + use util::trie::TrieSpec; use rlp::{UntrustedRlp, View}; use io::{IoContext, IoHandler}; use block::*; @@ -524,7 +525,7 @@ mod tests { fn propose_default(spec: &Spec, proposer: Address) -> (LockedBlock, Vec) { let mut db_result = get_temp_state_db(); let mut db = db_result.take(); - spec.ensure_db_good(&mut db).unwrap(); + spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); let genesis_header = spec.genesis_header(); let last_hashes = Arc::new(vec![genesis_header.hash()]); let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db.boxed_clone(), &genesis_header, last_hashes, proposer, (3141562.into(), 31415620.into()), vec![]).unwrap(); @@ -563,6 +564,12 @@ mod tests { addr } + fn insert_and_register(tap: &Arc, engine: &Arc, acc: &str) -> Address { + let addr = tap.insert_account(acc.sha3(), acc).unwrap(); + engine.set_signer(addr.clone(), acc.into()); + addr + } + struct TestIo { received: RwLock> } @@ -676,8 +683,8 @@ mod tests { #[test] fn can_generate_seal() { let (spec, tap) = setup(); - - let proposer = insert_and_unlock(&tap, "0"); + + let proposer = insert_and_register(&tap, &spec.engine, "0"); let (b, seal) = propose_default(&spec, proposer); assert!(b.try_seal(spec.engine.as_ref(), seal).is_ok()); @@ -685,12 +692,11 @@ mod tests { #[test] fn step_transitioning() { - ::env_logger::init().unwrap(); let (spec, tap) = setup(); let engine = spec.engine.clone(); let mut db_result = get_temp_state_db(); let mut db = db_result.take(); - spec.ensure_db_good(&mut db).unwrap(); + spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); let v0 = insert_and_unlock(&tap, "0"); let v1 = insert_and_unlock(&tap, "1"); From ca87d2cde9dd19a33c980b1e16acd6af0c981798 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 30 Nov 2016 15:01:43 +0000 Subject: [PATCH 109/295] add set_sealer rpc test --- rpc/src/v1/tests/eth.rs | 5 +++-- rpc/src/v1/tests/helpers/miner_service.rs | 3 ++- rpc/src/v1/tests/mocked/parity_set.rs | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 1ff5e1771..b31cc8bdf 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -50,7 +50,7 @@ fn sync_provider() -> Arc { })) } -fn miner_service(spec: &Spec) -> Arc { +fn miner_service(spec: &Spec, accounts: Arc) -> Arc { Miner::new( MinerOptions { new_work_notify: vec![], @@ -69,6 +69,7 @@ fn miner_service(spec: &Spec) -> Arc { }, GasPricer::new_fixed(20_000_000_000u64.into()), &spec, + Some(accounts), ) } @@ -116,7 +117,7 @@ impl EthTester { let dir = RandomTempPath::new(); let account_provider = account_provider(); spec.engine.register_account_provider(account_provider.clone()); - let miner_service = miner_service(&spec); + let miner_service = miner_service(&spec, account_provider.clone()); let snapshot_service = snapshot_service(); let db_config = ::util::kvdb::DatabaseConfig::with_columns(::ethcore::db::NUM_COLUMNS); diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 87efd2425..68caa137b 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -41,10 +41,11 @@ pub struct TestMinerService { pub pending_receipts: Mutex>, /// Last nonces. pub last_nonces: RwLock>, + /// Password held by Engine. + pub password: RwLock, min_gas_price: RwLock, gas_range_target: RwLock<(U256, U256)>, - password: RwLock, author: RwLock
, extra_data: RwLock, limit: RwLock, diff --git a/rpc/src/v1/tests/mocked/parity_set.rs b/rpc/src/v1/tests/mocked/parity_set.rs index 3202374a7..835dc0abe 100644 --- a/rpc/src/v1/tests/mocked/parity_set.rs +++ b/rpc/src/v1/tests/mocked/parity_set.rs @@ -106,6 +106,23 @@ fn rpc_parity_set_author() { assert_eq!(miner.author(), Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap()); } +#[test] +fn rpc_parity_set_sealer() { + let miner = miner_service(); + let client = client_service(); + let network = network_service(); + let io = IoHandler::new(); + io.add_delegate(parity_set_client(&client, &miner, &network).to_delegate()); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_setSealer", "params":["0xcd1722f3947def4cf144679da39c4c32bdc35681", "password"], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; + + assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); + assert_eq!(miner.author(), Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap()); + assert_eq!(*miner.password.read(), "password".to_string()); +} + + #[test] fn rpc_parity_set_transactions_limit() { let miner = miner_service(); From c028f106b1924b6656bbd1f66d7fbdf836e219f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 30 Nov 2016 16:11:41 +0100 Subject: [PATCH 110/295] RPC for confirming with token --- ethcore/src/account_provider.rs | 24 +++++- rpc/src/v1/helpers/dispatch.rs | 114 +++++++++++++++++++++++------ rpc/src/v1/impls/personal.rs | 4 +- rpc/src/v1/impls/signer.rs | 61 +++++++++------ rpc/src/v1/impls/signing.rs | 4 +- rpc/src/v1/impls/signing_unsafe.rs | 3 +- rpc/src/v1/tests/mocked/signer.rs | 46 ++++++++++++ rpc/src/v1/traits/signer.rs | 6 +- rpc/src/v1/types/confirmations.rs | 25 +++++++ rpc/src/v1/types/mod.rs.in | 4 +- 10 files changed, 237 insertions(+), 54 deletions(-) diff --git a/ethcore/src/account_provider.rs b/ethcore/src/account_provider.rs index 311204bb1..637a20401 100644 --- a/ethcore/src/account_provider.rs +++ b/ethcore/src/account_provider.rs @@ -401,6 +401,28 @@ impl AccountProvider { Ok((signature, new_token)) } + /// Decrypts a message with given token. Returns a token to use in next operation for this account. + pub fn decrypt_with_token(&self, account: Address, token: AccountToken, shared_mac: &[u8], message: &[u8]) + -> Result<(Vec, AccountToken), Error> + { + let is_std_password = try!(self.sstore.test_password(&account, &token)); + + let new_token = random_string(16); + let message = if is_std_password { + // Insert to transient store + try!(self.sstore.copy_account(&self.transient_sstore, &account, &token, &new_token)); + // decrypt + try!(self.sstore.decrypt(&account, &token, shared_mac, message)) + } else { + // check transient store + try!(self.transient_sstore.change_password(&account, &token, &new_token)); + // and decrypt + try!(self.transient_sstore.decrypt(&account, &token, shared_mac, message)) + }; + + Ok((message, new_token)) + } + /// Decrypts a message. If password is not provided the account must be unlocked. pub fn decrypt(&self, account: Address, password: Option, shared_mac: &[u8], message: &[u8]) -> Result, Error> { let password = try!(password.map(Ok).unwrap_or_else(|| self.password(&account))); @@ -477,8 +499,8 @@ mod tests { #[test] fn should_sign_and_return_token() { - let kp = Random.generate().unwrap(); // given + let kp = Random.generate().unwrap(); let ap = AccountProvider::transient_provider(); assert!(ap.insert_account(kp.secret().clone(), "test").is_ok()); diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index a66bc816d..3ac310be6 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::fmt::Debug; use rlp; use util::{Address, H256, U256, Uint, Bytes}; use util::bytes::ToPretty; @@ -37,46 +38,101 @@ use v1::types::{ pub const DEFAULT_MAC: [u8; 2] = [0, 0]; -pub fn execute(client: &C, miner: &M, accounts: &AccountProvider, payload: ConfirmationPayload, pass: Option) -> Result +type AccountToken = String; + +#[derive(Debug, Clone, PartialEq)] +pub enum SignWith { + Nothing, + Password(String), + Token(AccountToken), +} + +#[derive(Debug)] +pub enum WithToken { + No(T), + Yes(T, AccountToken), +} + +impl WithToken { + pub fn map(self, f: F) -> WithToken where + S: Debug, + F: FnOnce(T) -> S, + { + match self { + WithToken::No(v) => WithToken::No(f(v)), + WithToken::Yes(v, token) => WithToken::Yes(f(v), token), + } + } + + pub fn into_value(self) -> T { + match self { + WithToken::No(v) => v, + WithToken::Yes(v, ..) => v, + } + } +} + +impl From<(T, AccountToken)> for WithToken { + fn from(tuple: (T, AccountToken)) -> Self { + WithToken::Yes(tuple.0, tuple.1) + } +} + +pub fn execute(client: &C, miner: &M, accounts: &AccountProvider, payload: ConfirmationPayload, pass: SignWith) -> Result, Error> where C: MiningBlockChainClient, M: MinerService { match payload { ConfirmationPayload::SendTransaction(request) => { sign_and_dispatch(client, miner, accounts, request, pass) - .map(RpcH256::from) - .map(ConfirmationResponse::SendTransaction) + .map(|result| result + .map(RpcH256::from) + .map(ConfirmationResponse::SendTransaction) + ) }, ConfirmationPayload::SignTransaction(request) => { sign_no_dispatch(client, miner, accounts, request, pass) - .map(RpcRichRawTransaction::from) - .map(ConfirmationResponse::SignTransaction) + .map(|result| result + .map(RpcRichRawTransaction::from) + .map(ConfirmationResponse::SignTransaction) + ) }, ConfirmationPayload::Signature(address, hash) => { signature(accounts, address, hash, pass) - .map(RpcH520::from) - .map(ConfirmationResponse::Signature) + .map(|result| result + .map(RpcH520::from) + .map(ConfirmationResponse::Signature) + ) }, ConfirmationPayload::Decrypt(address, data) => { decrypt(accounts, address, data, pass) - .map(RpcBytes) - .map(ConfirmationResponse::Decrypt) + .map(|result| result + .map(RpcBytes) + .map(ConfirmationResponse::Decrypt) + ) }, } } -fn signature(accounts: &AccountProvider, address: Address, hash: H256, password: Option) -> Result { - accounts.sign(address, password.clone(), hash).map_err(|e| match password { - Some(_) => errors::from_password_error(e), - None => errors::from_signing_error(e), +fn signature(accounts: &AccountProvider, address: Address, hash: H256, password: SignWith) -> Result, Error> { + match password.clone() { + SignWith::Nothing => accounts.sign(address, None, hash).map(WithToken::No), + SignWith::Password(pass) => accounts.sign(address, Some(pass), hash).map(WithToken::No), + SignWith::Token(token) => accounts.sign_with_token(address, token, hash).map(Into::into), + }.map_err(|e| match password { + SignWith::Nothing => errors::from_signing_error(e), + _ => errors::from_password_error(e), }) } -fn decrypt(accounts: &AccountProvider, address: Address, msg: Bytes, password: Option) -> Result { - accounts.decrypt(address, password.clone(), &DEFAULT_MAC, &msg) - .map_err(|e| match password { - Some(_) => errors::from_password_error(e), - None => errors::from_signing_error(e), - }) +fn decrypt(accounts: &AccountProvider, address: Address, msg: Bytes, password: SignWith) -> Result, Error> { + match password.clone() { + SignWith::Nothing => accounts.decrypt(address, None, &DEFAULT_MAC, &msg).map(WithToken::No), + SignWith::Password(pass) => accounts.decrypt(address, Some(pass), &DEFAULT_MAC, &msg).map(WithToken::No), + SignWith::Token(token) => accounts.decrypt_with_token(address, token, &DEFAULT_MAC, &msg).map(Into::into), + }.map_err(|e| match password { + SignWith::Nothing => errors::from_signing_error(e), + _ => errors::from_password_error(e), + }) } pub fn dispatch_transaction(client: &C, miner: &M, signed_transaction: SignedTransaction) -> Result @@ -88,7 +144,7 @@ pub fn dispatch_transaction(client: &C, miner: &M, signed_transaction: Sig .map(|_| hash) } -pub fn sign_no_dispatch(client: &C, miner: &M, accounts: &AccountProvider, filled: FilledTransactionRequest, password: Option) -> Result +pub fn sign_no_dispatch(client: &C, miner: &M, accounts: &AccountProvider, filled: FilledTransactionRequest, password: SignWith) -> Result, Error> where C: MiningBlockChainClient, M: MinerService { let network_id = client.signing_network_id(); @@ -110,20 +166,32 @@ pub fn sign_no_dispatch(client: &C, miner: &M, accounts: &AccountProvider, let hash = t.hash(network_id); let signature = try!(signature(accounts, address, hash, password)); - t.with_signature(signature, network_id) + signature.map(|sig| { + t.with_signature(sig, network_id) + }) }; Ok(signed_transaction) } -pub fn sign_and_dispatch(client: &C, miner: &M, accounts: &AccountProvider, filled: FilledTransactionRequest, password: Option) -> Result +pub fn sign_and_dispatch(client: &C, miner: &M, accounts: &AccountProvider, filled: FilledTransactionRequest, password: SignWith) -> Result, Error> where C: MiningBlockChainClient, M: MinerService { let network_id = client.signing_network_id(); let signed_transaction = try!(sign_no_dispatch(client, miner, accounts, filled, password)); + let (signed_transaction, token) = match signed_transaction { + WithToken::No(signed_transaction) => (signed_transaction, None), + WithToken::Yes(signed_transaction, token) => (signed_transaction, Some(token)), + }; + trace!(target: "miner", "send_transaction: dispatching tx: {} for network ID {:?}", rlp::encode(&signed_transaction).to_vec().pretty(), network_id); - dispatch_transaction(&*client, &*miner, signed_transaction) + dispatch_transaction(&*client, &*miner, signed_transaction).map(|hash| { + match token { + Some(ref token) => WithToken::Yes(hash, token.clone()), + None => WithToken::No(hash), + } + }) } pub fn fill_optional_fields(request: TransactionRequest, client: &C, miner: &M) -> FilledTransactionRequest diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 1515e3fa1..48ed584cf 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -114,7 +114,7 @@ impl Personal for PersonalClient where C: MiningBl &*miner, &*accounts, request, - Some(password) - ).map(Into::into) + dispatch::SignWith::Password(password) + ).map(|v| v.into_value().into()) } } diff --git a/rpc/src/v1/impls/signer.rs b/rpc/src/v1/impls/signer.rs index 66f46ba01..bdb34dab7 100644 --- a/rpc/src/v1/impls/signer.rs +++ b/rpc/src/v1/impls/signer.rs @@ -26,7 +26,7 @@ use ethcore::miner::MinerService; use jsonrpc_core::Error; use v1::traits::Signer; -use v1::types::{TransactionModification, ConfirmationRequest, ConfirmationResponse, U256, Bytes}; +use v1::types::{TransactionModification, ConfirmationRequest, ConfirmationResponse, ConfirmationResponseWithToken, U256, Bytes}; use v1::helpers::{errors, SignerService, SigningQueue, ConfirmationPayload}; use v1::helpers::dispatch::{self, dispatch_transaction}; @@ -60,6 +60,35 @@ impl SignerClient where C: MiningBlockChainClient, take_weak!(self.client).keep_alive(); Ok(()) } + + fn confirm_internal(&self, id: U256, modification: TransactionModification, f: F) -> Result where + F: FnOnce(&C, &M, &AccountProvider, ConfirmationPayload) -> Result, + { + try!(self.active()); + + let id = id.into(); + let accounts = take_weak!(self.accounts); + let signer = take_weak!(self.signer); + let client = take_weak!(self.client); + let miner = take_weak!(self.miner); + + signer.peek(&id).map(|confirmation| { + let mut payload = confirmation.payload.clone(); + // Modify payload + match (&mut payload, modification.gas_price) { + (&mut ConfirmationPayload::SendTransaction(ref mut request), Some(gas_price)) => { + request.gas_price = gas_price.into(); + }, + _ => {}, + } + let result = f(&*client, &*miner, &*accounts, payload); + // Execute + if let Ok(ref response) = result { + signer.request_confirmed(id, Ok(response.clone())); + } + result + }).unwrap_or_else(|| Err(errors::invalid_params("Unknown RequestID", id))) + } } impl Signer for SignerClient where C: MiningBlockChainClient, M: MinerService { @@ -78,30 +107,14 @@ impl Signer for SignerClient where C: MiningBlockC // TODO [ToDr] TransactionModification is redundant for some calls // might be better to replace it in future fn confirm_request(&self, id: U256, modification: TransactionModification, pass: String) -> Result { - try!(self.active()); + self.confirm_internal(id, modification, move |client, miner, accounts, payload| { + dispatch::execute(client, miner, accounts, payload, dispatch::SignWith::Password(pass)) + .map(|v| v.into_value()) + }) + } - let id = id.into(); - let accounts = take_weak!(self.accounts); - let signer = take_weak!(self.signer); - let client = take_weak!(self.client); - let miner = take_weak!(self.miner); - - signer.peek(&id).map(|confirmation| { - let mut payload = confirmation.payload.clone(); - // Modify payload - match (&mut payload, modification.gas_price) { - (&mut ConfirmationPayload::SendTransaction(ref mut request), Some(gas_price)) => { - request.gas_price = gas_price.into(); - }, - _ => {}, - } - // Execute - let result = dispatch::execute(&*client, &*miner, &*accounts, payload, Some(pass)); - if let Ok(ref response) = result { - signer.request_confirmed(id, Ok(response.clone())); - } - result - }).unwrap_or_else(|| Err(errors::invalid_params("Unknown RequestID", id))) + fn confirm_request_with_token(&self, id: U256, modification: TransactionModification, token: String) -> Result { + unimplemented!() } fn confirm_request_raw(&self, id: U256, bytes: Bytes) -> Result { diff --git a/rpc/src/v1/impls/signing.rs b/rpc/src/v1/impls/signing.rs index 262e04dfb..c055628c0 100644 --- a/rpc/src/v1/impls/signing.rs +++ b/rpc/src/v1/impls/signing.rs @@ -99,7 +99,9 @@ impl SigningQueueClient where let sender = payload.sender(); if accounts.is_unlocked(sender) { - return dispatch::execute(&*client, &*miner, &*accounts, payload, None).map(DispatchResult::Value); + return dispatch::execute(&*client, &*miner, &*accounts, payload, dispatch::SignWith::Nothing) + .map(|v| v.into_value()) + .map(DispatchResult::Value); } take_weak!(self.signer).add_request(payload) diff --git a/rpc/src/v1/impls/signing_unsafe.rs b/rpc/src/v1/impls/signing_unsafe.rs index 46ffe6ded..3f03f7609 100644 --- a/rpc/src/v1/impls/signing_unsafe.rs +++ b/rpc/src/v1/impls/signing_unsafe.rs @@ -75,7 +75,8 @@ impl SigningUnsafeClient where let accounts = take_weak!(self.accounts); let payload = dispatch::from_rpc(payload, &*client, &*miner); - dispatch::execute(&*client, &*miner, &*accounts, payload, None) + dispatch::execute(&*client, &*miner, &*accounts, payload, dispatch::SignWith::Nothing) + .map(|v| v.into_value()) } } diff --git a/rpc/src/v1/tests/mocked/signer.rs b/rpc/src/v1/tests/mocked/signer.rs index e2ba580e0..c4df02606 100644 --- a/rpc/src/v1/tests/mocked/signer.rs +++ b/rpc/src/v1/tests/mocked/signer.rs @@ -209,6 +209,52 @@ fn should_confirm_transaction_and_dispatch() { assert_eq!(tester.miner.imported_transactions.lock().len(), 1); } +#[test] +fn should_confirm_transaction_with_token() { + // given + let tester = signer_tester(); + let address = tester.accounts.new_account("test").unwrap(); + let recipient = Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap(); + tester.signer.add_request(ConfirmationPayload::SendTransaction(FilledTransactionRequest { + from: address, + to: Some(recipient), + gas_price: U256::from(10_000), + gas: U256::from(10_000_000), + value: U256::from(1), + data: vec![], + nonce: None, + })).unwrap(); + + let t = Transaction { + nonce: U256::zero(), + gas_price: U256::from(0x1000), + gas: U256::from(10_000_000), + action: Action::Call(recipient), + value: U256::from(0x1), + data: vec![] + }; + let (signature, token) = tester.accounts.sign_with_token(address, "test".into(), t.hash(None)).unwrap(); + let t = t.with_signature(signature, None); + + assert_eq!(tester.signer.requests().len(), 1); + + // when + let request = r#"{ + "jsonrpc":"2.0", + "method":"signer_confirmRequestWithToken", + "params":["0x1", {"gasPrice":"0x1000"}, ""#.to_owned() + &token + r#""], + "id":1 + }"#; + let response = r#"{"jsonrpc":"2.0","result":{"result":""#.to_owned() + + format!("0x{:?}", t.hash()).as_ref() + + r#""token":""},"id":1}"#; + + // then + assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned())); + assert_eq!(tester.signer.requests().len(), 0); + assert_eq!(tester.miner.imported_transactions.lock().len(), 1); +} + #[test] fn should_confirm_transaction_with_rlp() { // given diff --git a/rpc/src/v1/traits/signer.rs b/rpc/src/v1/traits/signer.rs index eafa520d4..5014dc4a0 100644 --- a/rpc/src/v1/traits/signer.rs +++ b/rpc/src/v1/traits/signer.rs @@ -18,7 +18,7 @@ use jsonrpc_core::Error; use v1::helpers::auto_args::Wrap; -use v1::types::{U256, Bytes, TransactionModification, ConfirmationRequest, ConfirmationResponse}; +use v1::types::{U256, Bytes, TransactionModification, ConfirmationRequest, ConfirmationResponse, ConfirmationResponseWithToken}; build_rpc_trait! { @@ -33,6 +33,10 @@ build_rpc_trait! { #[rpc(name = "signer_confirmRequest")] fn confirm_request(&self, U256, TransactionModification, String) -> Result; + /// Confirm specific request with token. + #[rpc(name = "signer_confirmRequestWithToken")] + fn confirm_request_with_token(&self, U256, TransactionModification, String) -> Result; + /// Confirm specific request with already signed data. #[rpc(name = "signer_confirmRequestRaw")] fn confirm_request_raw(&self, U256, Bytes) -> Result; diff --git a/rpc/src/v1/types/confirmations.rs b/rpc/src/v1/types/confirmations.rs index bbbad83f3..795d24726 100644 --- a/rpc/src/v1/types/confirmations.rs +++ b/rpc/src/v1/types/confirmations.rs @@ -101,6 +101,15 @@ impl Serialize for ConfirmationResponse { } } +/// Confirmation response with additional token for further requests +#[derive(Debug, Clone, PartialEq, Serialize)] +pub struct ConfirmationResponseWithToken { + /// Actual response + pub result: ConfirmationResponse, + /// New token + pub token: String, +} + /// Confirmation payload, i.e. the thing to be confirmed #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)] pub enum ConfirmationPayload { @@ -247,5 +256,21 @@ mod tests { gas_price: None, }); } + + #[test] + fn should_serialize_confirmation_response_with_token() { + // given + let response = ConfirmationResponseWithToken { + result: ConfirmationResponse::SendTransaction(H256::default()), + token: "test-token".into(), + }; + + // when + let res = serde_json::to_string(&response); + let expected = r#"{"result":"0x0000000000000000000000000000000000000000","token":"test-token"}"#; + + // then + assert_eq!(res.unwrap(), expected.to_owned()); + } } diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index 55e8fd27b..6b6f01443 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -38,7 +38,9 @@ pub use self::bytes::Bytes; pub use self::block::{RichBlock, Block, BlockTransactions}; pub use self::block_number::BlockNumber; pub use self::call_request::CallRequest; -pub use self::confirmations::{ConfirmationPayload, ConfirmationRequest, ConfirmationResponse, TransactionModification, SignRequest, DecryptRequest, Either}; +pub use self::confirmations::{ + ConfirmationPayload, ConfirmationRequest, ConfirmationResponse, ConfirmationResponseWithToken, TransactionModification, SignRequest, DecryptRequest, Either +}; pub use self::filter::{Filter, FilterChanges}; pub use self::hash::{H64, H160, H256, H512, H520, H2048}; pub use self::index::Index; From 022ccb5bcef2ef7eab2dafcad52188dcad76f6f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 30 Nov 2016 16:21:57 +0100 Subject: [PATCH 111/295] Fixing tests --- ethcore/src/account_provider.rs | 9 +++++---- ethstore/src/ethstore.rs | 3 +++ rpc/src/v1/types/confirmations.rs | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/ethcore/src/account_provider.rs b/ethcore/src/account_provider.rs index 637a20401..da5992f0c 100644 --- a/ethcore/src/account_provider.rs +++ b/ethcore/src/account_provider.rs @@ -81,10 +81,11 @@ impl KeyDirectory for NullDir { } fn insert(&self, account: SafeAccount) -> Result { - self.accounts.write() - .entry(account.address.clone()) - .or_insert_with(Vec::new) - .push(account.clone()); + let mut lock = self.accounts.write(); + let mut accounts = lock.entry(account.address.clone()).or_insert_with(Vec::new); + // If the filename is the same we just need to replace the entry + accounts.retain(|acc| acc.filename != account.filename); + accounts.push(account.clone()); Ok(account) } diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index 158a7c55a..ec3cc16b9 100644 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -220,6 +220,9 @@ impl EthMultiStore { // update cache let mut cache = self.cache.write(); let mut accounts = cache.entry(account.address.clone()).or_insert_with(Vec::new); + // TODO [ToDr] That is crappy way of overcoming set_name, set_meta, etc. + // Avoid cloning instead! + accounts.retain(|acc| acc.filename != account.filename); accounts.push(account); Ok(()) } diff --git a/rpc/src/v1/types/confirmations.rs b/rpc/src/v1/types/confirmations.rs index 795d24726..a4284fa5c 100644 --- a/rpc/src/v1/types/confirmations.rs +++ b/rpc/src/v1/types/confirmations.rs @@ -267,7 +267,7 @@ mod tests { // when let res = serde_json::to_string(&response); - let expected = r#"{"result":"0x0000000000000000000000000000000000000000","token":"test-token"}"#; + let expected = r#"{"result":"0x0000000000000000000000000000000000000000000000000000000000000000","token":"test-token"}"#; // then assert_eq!(res.unwrap(), expected.to_owned()); From 1d76bb7048739d7d65530425df20bc2aa339cd23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 30 Nov 2016 16:41:37 +0100 Subject: [PATCH 112/295] Fixing ethstore tests --- ethstore/src/ethstore.rs | 1 + ethstore/tests/api.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index ec3cc16b9..8cbce0f1c 100644 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -311,6 +311,7 @@ impl SimpleSecretStore for EthMultiStore { #[cfg(test)] mod tests { + #[test] fn should_have_some_tests() { assert_eq!(true, false) } diff --git a/ethstore/tests/api.rs b/ethstore/tests/api.rs index e1667607b..a26da4132 100644 --- a/ethstore/tests/api.rs +++ b/ethstore/tests/api.rs @@ -19,7 +19,7 @@ extern crate ethstore; mod util; -use ethstore::{SecretStore, EthStore}; +use ethstore::{SecretStore, EthStore, SimpleSecretStore}; use ethstore::ethkey::{Random, Generator, Secret, KeyPair, verify_address}; use ethstore::dir::DiskDirectory; use util::TransientDir; From 84cf27c3efb797a031506a946c1acd5d4941149a Mon Sep 17 00:00:00 2001 From: arkpar Date: Wed, 30 Nov 2016 16:47:20 +0100 Subject: [PATCH 113/295] Advertise protocol version 2 --- sync/src/api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sync/src/api.rs b/sync/src/api.rs index ee9031d0e..1bb8f267e 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -250,7 +250,7 @@ impl ChainNotify for EthSync { self.network.register_protocol(self.eth_handler.clone(), self.subprotocol_name, ETH_PACKET_COUNT, &[62u8, 63u8]) .unwrap_or_else(|e| warn!("Error registering ethereum protocol: {:?}", e)); // register the warp sync subprotocol - self.network.register_protocol(self.eth_handler.clone(), WARP_SYNC_PROTOCOL_ID, SNAPSHOT_SYNC_PACKET_COUNT, &[1u8]) + self.network.register_protocol(self.eth_handler.clone(), WARP_SYNC_PROTOCOL_ID, SNAPSHOT_SYNC_PACKET_COUNT, &[1u8, 2u8]) .unwrap_or_else(|e| warn!("Error registering snapshot sync protocol: {:?}", e)); } From bb8347477a81f40d81cb4bb4a417659d53f1b7ad Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 30 Nov 2016 16:01:20 +0000 Subject: [PATCH 114/295] gossip when not enough votes --- ethcore/src/engines/tendermint/transition.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index ace5661b6..b20b97fba 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -85,12 +85,23 @@ impl IoHandler for TransitionHandler { set_timeout(io, engine.our_params.timeouts.precommit); Some(Step::Precommit) }, + Step::Prevote => { + trace!(target: "poa", "timeout: Prevote timeout without enough votes."); + set_timeout(io, engine.our_params.timeouts.precommit); + Some(Step::Prevote) + }, Step::Precommit if engine.has_enough_any_votes() => { trace!(target: "poa", "timeout: Precommit timeout."); set_timeout(io, engine.our_params.timeouts.propose); engine.increment_round(1); Some(Step::Propose) }, + Step::Precommit => { + trace!(target: "poa", "timeout: Precommit timeout without enough votes."); + set_timeout(io, engine.our_params.timeouts.propose); + engine.increment_round(1); + Some(Step::Propose) + }, Step::Commit => { trace!(target: "poa", "timeout: Commit timeout."); set_timeout(io, engine.our_params.timeouts.propose); From d128c20dc21353b884fd1b8f8f27d4e5425a4c71 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 30 Nov 2016 16:02:05 +0000 Subject: [PATCH 115/295] remove proposer_nonce --- ethcore/src/engines/tendermint/mod.rs | 30 ++++++++++++++++++--------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index b70c7a64f..e7eb486dc 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -74,8 +74,6 @@ pub struct Tendermint { round: AtomicUsize, /// Consensus step. step: RwLock, - /// Used to swith proposer. - proposer_nonce: AtomicUsize, /// Vote accumulator. votes: VoteCollector, /// Channel for updating the sealing. @@ -104,7 +102,6 @@ impl Tendermint { height: AtomicUsize::new(1), round: AtomicUsize::new(0), step: RwLock::new(Step::Propose), - proposer_nonce: AtomicUsize::new(0), votes: VoteCollector::new(), message_channel: Mutex::new(None), account_provider: Mutex::new(None), @@ -180,7 +177,7 @@ impl Tendermint { Some(ref m) => m.block_hash, None => None, }; - self.generate_and_broadcast_message(block_hash) + self.generate_and_broadcast_message(block_hash); }, Step::Precommit => { trace!(target: "poa", "to_step: Transitioning to Precommit."); @@ -225,7 +222,7 @@ impl Tendermint { /// Round proposer switching. fn is_proposer(&self, address: &Address) -> Result<(), EngineError> { let ref p = self.our_params; - let proposer_nonce = self.proposer_nonce.load(AtomicOrdering::SeqCst); + let proposer_nonce = self.height.load(AtomicOrdering::SeqCst) + self.round.load(AtomicOrdering::SeqCst); let proposer = p.authorities.get(proposer_nonce % p.authority_n).expect("There are authority_n authorities; taking number modulo authority_n gives number in authority_n range; qed"); if proposer == address { Ok(()) @@ -243,13 +240,11 @@ impl Tendermint { } fn increment_round(&self, n: Round) { - self.proposer_nonce.fetch_add(n, AtomicOrdering::SeqCst); self.round.fetch_add(n, AtomicOrdering::SeqCst); } fn reset_round(&self) { self.last_lock.store(0, AtomicOrdering::SeqCst); - self.proposer_nonce.fetch_add(1, AtomicOrdering::SeqCst); self.height.fetch_add(1, AtomicOrdering::SeqCst); self.round.store(0, AtomicOrdering::SeqCst); } @@ -627,7 +622,7 @@ mod tests { let engine = spec.engine; let mut header = Header::default(); - let validator = insert_and_unlock(&tap, "0"); + let validator = insert_and_unlock(&tap, "1"); header.set_author(validator); let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); @@ -684,7 +679,7 @@ mod tests { fn can_generate_seal() { let (spec, tap) = setup(); - let proposer = insert_and_register(&tap, &spec.engine, "0"); + let proposer = insert_and_register(&tap, &spec.engine, "1"); let (b, seal) = propose_default(&spec, proposer); assert!(b.try_seal(spec.engine.as_ref(), seal).is_ok()); @@ -705,7 +700,7 @@ mod tests { let r = 0; // Propose - let (b, mut seal) = propose_default(&spec, v0.clone()); + let (b, mut seal) = propose_default(&spec, v1.clone()); let proposal = Some(b.header().bare_hash()); // Register IoHandler remembers messages. @@ -729,4 +724,19 @@ mod tests { let second = test_io.received.read()[5] == ClientIoMessage::SubmitSeal(proposal.unwrap(), seal); assert!(first ^ second); } + + #[test] + fn timeout_transitioning() { + ::env_logger::init().unwrap(); + let (spec, tap) = setup(); + let engine = spec.engine.clone(); + let mut db_result = get_temp_state_db(); + let mut db = db_result.take(); + spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); + + let v = insert_and_register(&tap, &engine, "0"); + + ::std::thread::sleep(::std::time::Duration::from_millis(15000)); + println!("done"); + } } From dcb7e1e6387f1bdccdb47b4ad49a1dc181c57766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 30 Nov 2016 17:05:31 +0100 Subject: [PATCH 116/295] Implementing RPC --- ethstore/tests/api.rs | 2 +- rpc/src/v1/helpers/dispatch.rs | 14 +++++++++++++- rpc/src/v1/impls/signer.rs | 21 ++++++++++++++------- rpc/src/v1/tests/mocked/signer.rs | 5 +++-- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/ethstore/tests/api.rs b/ethstore/tests/api.rs index a26da4132..dd9ec3311 100644 --- a/ethstore/tests/api.rs +++ b/ethstore/tests/api.rs @@ -19,7 +19,7 @@ extern crate ethstore; mod util; -use ethstore::{SecretStore, EthStore, SimpleSecretStore}; +use ethstore::{EthStore, SimpleSecretStore}; use ethstore::ethkey::{Random, Generator, Secret, KeyPair, verify_address}; use ethstore::dir::DiskDirectory; use util::TransientDir; diff --git a/rpc/src/v1/helpers/dispatch.rs b/rpc/src/v1/helpers/dispatch.rs index 3ac310be6..1a70f7e10 100644 --- a/rpc/src/v1/helpers/dispatch.rs +++ b/rpc/src/v1/helpers/dispatch.rs @@ -15,6 +15,7 @@ // along with Parity. If not, see . use std::fmt::Debug; +use std::ops::Deref; use rlp; use util::{Address, H256, U256, Uint, Bytes}; use util::bytes::ToPretty; @@ -53,6 +54,17 @@ pub enum WithToken { Yes(T, AccountToken), } +impl Deref for WithToken { + type Target = T; + + fn deref(&self) -> &Self::Target { + match *self { + WithToken::No(ref v) => v, + WithToken::Yes(ref v, _) => v, + } + } +} + impl WithToken { pub fn map(self, f: F) -> WithToken where S: Debug, @@ -67,7 +79,7 @@ impl WithToken { pub fn into_value(self) -> T { match self { WithToken::No(v) => v, - WithToken::Yes(v, ..) => v, + WithToken::Yes(v, _) => v, } } } diff --git a/rpc/src/v1/impls/signer.rs b/rpc/src/v1/impls/signer.rs index bdb34dab7..97d3de809 100644 --- a/rpc/src/v1/impls/signer.rs +++ b/rpc/src/v1/impls/signer.rs @@ -28,7 +28,7 @@ use jsonrpc_core::Error; use v1::traits::Signer; use v1::types::{TransactionModification, ConfirmationRequest, ConfirmationResponse, ConfirmationResponseWithToken, U256, Bytes}; use v1::helpers::{errors, SignerService, SigningQueue, ConfirmationPayload}; -use v1::helpers::dispatch::{self, dispatch_transaction}; +use v1::helpers::dispatch::{self, dispatch_transaction, WithToken}; /// Transactions confirmation (personal) rpc implementation. pub struct SignerClient where C: MiningBlockChainClient, M: MinerService { @@ -61,8 +61,8 @@ impl SignerClient where C: MiningBlockChainClient, Ok(()) } - fn confirm_internal(&self, id: U256, modification: TransactionModification, f: F) -> Result where - F: FnOnce(&C, &M, &AccountProvider, ConfirmationPayload) -> Result, + fn confirm_internal(&self, id: U256, modification: TransactionModification, f: F) -> Result, Error> where + F: FnOnce(&C, &M, &AccountProvider, ConfirmationPayload) -> Result, Error>, { try!(self.active()); @@ -84,7 +84,7 @@ impl SignerClient where C: MiningBlockChainClient, let result = f(&*client, &*miner, &*accounts, payload); // Execute if let Ok(ref response) = result { - signer.request_confirmed(id, Ok(response.clone())); + signer.request_confirmed(id, Ok((*response).clone())); } result }).unwrap_or_else(|| Err(errors::invalid_params("Unknown RequestID", id))) @@ -109,12 +109,19 @@ impl Signer for SignerClient where C: MiningBlockC fn confirm_request(&self, id: U256, modification: TransactionModification, pass: String) -> Result { self.confirm_internal(id, modification, move |client, miner, accounts, payload| { dispatch::execute(client, miner, accounts, payload, dispatch::SignWith::Password(pass)) - .map(|v| v.into_value()) - }) + }).map(|v| v.into_value()) } fn confirm_request_with_token(&self, id: U256, modification: TransactionModification, token: String) -> Result { - unimplemented!() + self.confirm_internal(id, modification, move |client, miner, accounts, payload| { + dispatch::execute(client, miner, accounts, payload, dispatch::SignWith::Token(token)) + }).and_then(|v| match v { + WithToken::No(_) => Err(errors::internal("Unexpected response without token.", "")), + WithToken::Yes(response, token) => Ok(ConfirmationResponseWithToken { + result: response, + token: token, + }), + }) } fn confirm_request_raw(&self, id: U256, bytes: Bytes) -> Result { diff --git a/rpc/src/v1/tests/mocked/signer.rs b/rpc/src/v1/tests/mocked/signer.rs index c4df02606..eb7fa6c48 100644 --- a/rpc/src/v1/tests/mocked/signer.rs +++ b/rpc/src/v1/tests/mocked/signer.rs @@ -247,10 +247,11 @@ fn should_confirm_transaction_with_token() { }"#; let response = r#"{"jsonrpc":"2.0","result":{"result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + - r#""token":""},"id":1}"#; + r#"","token":""#; // then - assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned())); + let result = tester.io.handle_request_sync(&request).unwrap(); + assert!(result.starts_with(&response), "Should return correct result. Expected: {:?}, Got: {:?}", response, result); assert_eq!(tester.signer.requests().len(), 0); assert_eq!(tester.miner.imported_transactions.lock().len(), 1); } From dbf82c2e98e8ce13d6ef358c93c822a2bd48ef03 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 30 Nov 2016 16:40:16 +0000 Subject: [PATCH 117/295] fix tests --- ethcore/src/engines/tendermint/mod.rs | 9 +++++---- ethcore/src/engines/tendermint/transition.rs | 10 ++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index e7eb486dc..e8b4973fe 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -647,7 +647,7 @@ mod tests { let engine = spec.engine; let mut header = Header::default(); - let proposer = insert_and_unlock(&tap, "0"); + let proposer = insert_and_unlock(&tap, "1"); header.set_author(proposer); let mut seal = proposal_seal(&tap, &header, 0); @@ -687,6 +687,7 @@ mod tests { #[test] fn step_transitioning() { + ::env_logger::init().unwrap(); let (spec, tap) = setup(); let engine = spec.engine.clone(); let mut db_result = get_temp_state_db(); @@ -717,11 +718,11 @@ mod tests { vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); // Wait a bit for async stuff. - ::std::thread::sleep(::std::time::Duration::from_millis(500)); + ::std::thread::sleep(::std::time::Duration::from_millis(50)); seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v0, v1); - let first = test_io.received.read()[5] == ClientIoMessage::SubmitSeal(proposal.unwrap(), seal.clone()); + let first = test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal.clone())); seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v1, v0); - let second = test_io.received.read()[5] == ClientIoMessage::SubmitSeal(proposal.unwrap(), seal); + let second = test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal)); assert!(first ^ second); } diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index b20b97fba..a7aebfd30 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -49,9 +49,9 @@ impl Default for TendermintTimeouts { fn default() -> Self { TendermintTimeouts { propose: Duration::milliseconds(2000), - prevote: Duration::milliseconds(1000), - precommit: Duration::milliseconds(1000), - commit: Duration::milliseconds(1000) + prevote: Duration::milliseconds(2000), + precommit: Duration::milliseconds(2000), + commit: Duration::milliseconds(2000) } } } @@ -99,8 +99,7 @@ impl IoHandler for TransitionHandler { Step::Precommit => { trace!(target: "poa", "timeout: Precommit timeout without enough votes."); set_timeout(io, engine.our_params.timeouts.propose); - engine.increment_round(1); - Some(Step::Propose) + Some(Step::Precommit) }, Step::Commit => { trace!(target: "poa", "timeout: Commit timeout."); @@ -108,7 +107,6 @@ impl IoHandler for TransitionHandler { engine.reset_round(); Some(Step::Propose) }, - _ => None, }; if let Some(step) = next_step { From 66b4f1ac47e12b5c00283893cd3571910ad54587 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 1 Dec 2016 14:10:22 +0000 Subject: [PATCH 118/295] remove unnecessary option --- ethcore/src/engines/tendermint/transition.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index a7aebfd30..a494cfc0f 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -78,40 +78,38 @@ impl IoHandler for TransitionHandler { Step::Propose => { trace!(target: "poa", "timeout: Propose timeout."); set_timeout(io, engine.our_params.timeouts.prevote); - Some(Step::Prevote) + Step::Prevote }, Step::Prevote if engine.has_enough_any_votes() => { trace!(target: "poa", "timeout: Prevote timeout."); set_timeout(io, engine.our_params.timeouts.precommit); - Some(Step::Precommit) + Step::Precommit }, Step::Prevote => { trace!(target: "poa", "timeout: Prevote timeout without enough votes."); - set_timeout(io, engine.our_params.timeouts.precommit); - Some(Step::Prevote) + set_timeout(io, engine.our_params.timeouts.prevote); + Step::Prevote }, Step::Precommit if engine.has_enough_any_votes() => { trace!(target: "poa", "timeout: Precommit timeout."); set_timeout(io, engine.our_params.timeouts.propose); engine.increment_round(1); - Some(Step::Propose) + Step::Propose }, Step::Precommit => { trace!(target: "poa", "timeout: Precommit timeout without enough votes."); - set_timeout(io, engine.our_params.timeouts.propose); - Some(Step::Precommit) + set_timeout(io, engine.our_params.timeouts.precommit); + Step::Precommit }, Step::Commit => { trace!(target: "poa", "timeout: Commit timeout."); set_timeout(io, engine.our_params.timeouts.propose); engine.reset_round(); - Some(Step::Propose) + Step::Propose }, }; - if let Some(step) = next_step { - engine.to_step(step) - } + engine.to_step(next_step) } } } From 344999aaf7f3b7befc3f47ef8f7e7ff464550790 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 1 Dec 2016 14:10:42 +0000 Subject: [PATCH 119/295] return signing failure error --- ethcore/src/engines/tendermint/message.rs | 2 +- ethcore/src/engines/tendermint/mod.rs | 23 ++++++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 3366510d2..c57a194f2 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -165,7 +165,7 @@ pub fn message_info_rlp_from_header(header: &Header) -> Result(signer: F, height: Height, round: Round, step: Step, block_hash: Option) -> Option where F: FnOnce(H256) -> Option { +pub fn message_full_rlp(signer: F, height: Height, round: Round, step: Step, block_hash: Option) -> Result where F: FnOnce(H256) -> Result { let vote_info = message_info_rlp(height, round, step, block_hash); signer(vote_info.sha3()).map(|ref signature| { let mut s = RlpStream::new_list(2); diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index e8b4973fe..cdbf1429a 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -138,18 +138,26 @@ impl Tendermint { Ok(_) => trace!(target: "poa", "broadcast_message: BroadcastMessage message sent."), Err(err) => warn!(target: "poa", "broadcast_message: Could not send a sealing message {}.", err), } + } else { + warn!(target: "poa", "broadcast_message: No IoChannel available."); } } fn generate_message(&self, block_hash: Option) -> Option { if let Some(ref ap) = *self.account_provider.lock() { - message_full_rlp( - |mh| ap.sign(*self.authority.read(), self.password.read().clone(), mh).ok().map(H520::from), + match message_full_rlp( + |mh| ap.sign(*self.authority.read(), self.password.read().clone(), mh).map(H520::from), self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read(), block_hash - ) + ) { + Ok(m) => Some(m), + Err(e) => { + warn!(target: "poa", "generate_message: Could not sign the message {}", e); + None + }, + } } else { warn!(target: "poa", "generate_message: No AccountProvider available."); None @@ -166,12 +174,12 @@ impl Tendermint { *self.step.write() = step; match step { Step::Propose => { - trace!(target: "poa", "to_step: Transitioning to Propose."); + trace!(target: "poa", "to_step: Propose."); *self.proposal.write() = None; self.update_sealing() }, Step::Prevote => { - trace!(target: "poa", "to_step: Transitioning to Prevote."); + trace!(target: "poa", "to_step: Prevote."); let block_hash = match *self.lock_change.read() { Some(ref m) if self.should_unlock(m.round) => self.proposal.read().clone(), Some(ref m) => m.block_hash, @@ -180,7 +188,7 @@ impl Tendermint { self.generate_and_broadcast_message(block_hash); }, Step::Precommit => { - trace!(target: "poa", "to_step: Transitioning to Precommit."); + trace!(target: "poa", "to_step: Precommit."); let block_hash = match *self.lock_change.read() { Some(ref m) if self.is_round(m) => { self.last_lock.store(m.round, AtomicOrdering::SeqCst); @@ -191,7 +199,7 @@ impl Tendermint { self.generate_and_broadcast_message(block_hash); }, Step::Commit => { - trace!(target: "poa", "to_step: Transitioning to Commit."); + trace!(target: "poa", "to_step: Commit."); // Commit the block using a complete signature set. let round = self.round.load(AtomicOrdering::SeqCst); if let Some(block_hash) = *self.proposal.read() { @@ -482,6 +490,7 @@ impl Engine for Tendermint { } fn register_message_channel(&self, message_channel: IoChannel) { + trace!(target: "poa", "register_message_channel: Register the IoChannel."); *self.message_channel.lock() = Some(message_channel); } From 4eca687bbbaf7f87b7c36ce7209ffd73f3ead94e Mon Sep 17 00:00:00 2001 From: arkpar Date: Thu, 1 Dec 2016 15:48:56 +0100 Subject: [PATCH 120/295] Fixed network context --- sync/src/api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sync/src/api.rs b/sync/src/api.rs index 1bb8f267e..b27713203 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -260,7 +260,7 @@ impl ChainNotify for EthSync { } fn broadcast(&self, message: Vec) { - self.network.with_context(ETH_PROTOCOL, |context| { + self.network.with_context(WARP_SYNC_PROTOCOL_ID, |context| { let mut sync_io = NetSyncIo::new(context, &*self.eth_handler.chain, &*self.eth_handler.snapshot_service, &self.eth_handler.overlay); self.eth_handler.sync.write().propagate_consensus_packet(&mut sync_io, message.clone()); }); From e40e398eaa7caf4c9f0078cb58a0bd4d1be28262 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 1 Dec 2016 17:19:40 +0000 Subject: [PATCH 121/295] clean up some tracing --- ethcore/src/service.rs | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index c7dccaa89..ea4a92bed 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -224,21 +224,10 @@ impl IoHandler for ClientIoHandler { debug!(target: "snapshot", "Failed to initialize periodic snapshot thread: {:?}", e); } }, - ClientIoMessage::UpdateSealing => { - trace!(target: "poa", "message: UpdateSealing"); - self.client.update_sealing(); - }, - ClientIoMessage::SubmitSeal(ref hash, ref seal) => { - trace!(target: "poa", "message: SubmitSeal"); - self.client.submit_seal(*hash, seal.clone()); - }, - ClientIoMessage::BroadcastMessage(ref message) => { - trace!(target: "poa", "message: BroadcastMessage"); - self.client.broadcast_message(message.clone()); - }, - ClientIoMessage::NewMessage(ref message) => { - self.client.handle_queued_message(message); - }, + ClientIoMessage::UpdateSealing => self.client.update_sealing(), + ClientIoMessage::SubmitSeal(ref hash, ref seal) => self.client.submit_seal(*hash, seal.clone()), + ClientIoMessage::BroadcastMessage(ref message) => self.client.broadcast_message(message.clone()), + ClientIoMessage::NewMessage(ref message) => self.client.handle_queued_message(message), _ => {} // ignore other messages } } From 498b2fb0b1922a938511e2ed76c4c09bbae3c87a Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 1 Dec 2016 17:20:16 +0000 Subject: [PATCH 122/295] show verification error --- ethcore/src/miner/miner.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 032e15d31..54902839f 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -468,8 +468,8 @@ impl Miner { let s = self.engine.generate_seal(block.block()); if let Some(seal) = s { 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?"); + block.lock().try_seal(&*self.engine, seal).or_else(|(e, _)| { + warn!("prepare_sealing: ERROR: try_seal failed when given internally generated seal: {}", e); Err(None) }) } else { From 9290fdde857df28100e75fd092b904f93134ed49 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 1 Dec 2016 17:21:51 +0000 Subject: [PATCH 123/295] fix tests --- ethcore/src/engines/tendermint/message.rs | 2 +- ethcore/src/engines/tendermint/mod.rs | 23 ++++++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index c57a194f2..5cc37b5f4 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -215,7 +215,7 @@ mod tests { tap.unlock_account_permanently(addr, "0".into()).unwrap(); let raw_rlp = message_full_rlp( - |mh| tap.sign(addr, None, mh).ok().map(H520::from), + |mh| tap.sign(addr, None, mh).map(H520::from), 123, 2, Step::Precommit, diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index cdbf1429a..33c768007 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -134,6 +134,7 @@ impl Tendermint { fn broadcast_message(&self, message: Bytes) { if let Some(ref channel) = *self.message_channel.lock() { + trace!(target: "poa", "broadcast_message: {:?}", &message); match channel.send(ClientIoMessage::BroadcastMessage(message)) { Ok(_) => trace!(target: "poa", "broadcast_message: BroadcastMessage message sent."), Err(err) => warn!(target: "poa", "broadcast_message: Could not send a sealing message {}.", err), @@ -154,7 +155,7 @@ impl Tendermint { ) { Ok(m) => Some(m), Err(e) => { - warn!(target: "poa", "generate_message: Could not sign the message {}", e); + trace!(target: "poa", "generate_message: Could not sign the message {}", e); None }, } @@ -325,9 +326,9 @@ impl Engine for Tendermint { *self.authority.write() = *block.header().author() } - /// Round proposer switching. + /// Should this node participate. fn is_sealer(&self, address: &Address) -> Option { - Some(self.is_proposer(address).is_ok()) + Some(self.is_authority(address)) } /// Attempt to seal the block internally using all available signatures. @@ -335,17 +336,21 @@ impl Engine for Tendermint { if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let author = header.author(); + // Only proposer can generate seal. + if self.is_proposer(author).is_err() { return None; } let height = header.number() as Height; let round = self.round.load(AtomicOrdering::SeqCst); let bh = Some(header.bare_hash()); let vote_info = message_info_rlp(height, round, Step::Propose, bh); if let Ok(signature) = ap.sign(*author, self.password.read().clone(), vote_info.sha3()).map(H520::from) { + // Insert Propose vote. self.votes.vote(ConsensusMessage { signature: signature, height: height, round: round, step: Step::Propose, block_hash: bh }, *author); + // Remember proposal for later seal submission. *self.proposal.write() = Some(header.bare_hash()); Some(vec![ ::rlp::encode(&self.round.load(AtomicOrdering::SeqCst)).to_vec(), ::rlp::encode(&signature).to_vec(), - Vec::new() + ::rlp::EMPTY_LIST_RLP.to_vec() ]) } else { warn!(target: "poa", "generate_seal: FAIL: accounts secret key unavailable"); @@ -538,7 +543,7 @@ mod tests { (b, seal) } - fn vote(engine: &Arc, signer: F, height: usize, round: usize, step: Step, block_hash: Option) where F: FnOnce(H256) -> Option { + fn vote(engine: &Arc, signer: F, height: usize, round: usize, step: Step, block_hash: Option) where F: FnOnce(H256) -> Result { let m = message_full_rlp(signer, height, round, step, block_hash).unwrap(); engine.handle_message(UntrustedRlp::new(&m)).unwrap(); } @@ -720,11 +725,11 @@ mod tests { engine.register_message_channel(io_service.channel()); // Prevote. - vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Prevote, proposal); + vote(&engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Prevote, proposal); - vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Prevote, proposal); - vote(&engine, |mh| tap.sign(v1, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); - vote(&engine, |mh| tap.sign(v0, None, mh).ok().map(H520::from), h, r, Step::Precommit, proposal); + vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal); + vote(&engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Precommit, proposal); + vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal); // Wait a bit for async stuff. ::std::thread::sleep(::std::time::Duration::from_millis(50)); From f1ef4a49355bd516d8587d8dce08f006c36fd88c Mon Sep 17 00:00:00 2001 From: arkpar Date: Thu, 1 Dec 2016 19:11:36 +0100 Subject: [PATCH 124/295] Import sealed block immedtiatelly --- 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 54902839f..1947cc9af 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -482,7 +482,7 @@ impl Miner { fn seal_and_import_block_internally(&self, chain: &MiningBlockChainClient, block: ClosedBlock) -> bool { if !block.transactions().is_empty() || self.forced_sealing() { if let Ok(sealed) = self.seal_block_internally(block) { - if chain.import_block(sealed.rlp_bytes()).is_ok() { + if chain.import_sealed_block(sealed).is_ok() { trace!(target: "miner", "import_block_internally: imported internally sealed block"); return true } From 39ea703c693cc8561ad50504d15a6726cfff8a77 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 1 Dec 2016 20:50:24 +0000 Subject: [PATCH 125/295] vote on message generation --- ethcore/src/engines/authority_round.rs | 7 ++++- ethcore/src/engines/tendermint/message.rs | 33 +++++++++++--------- ethcore/src/engines/tendermint/mod.rs | 37 ++++++++++++++--------- 3 files changed, 46 insertions(+), 31 deletions(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index ade14a28d..dbfcbda31 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -28,6 +28,8 @@ use spec::CommonParams; use engines::{Engine, EngineError}; use header::Header; use error::{Error, BlockError}; +use blockchain::extras::BlockDetails; +use views::HeaderView; use evm::Schedule; use ethjson; use io::{IoContext, IoHandler, TimerToken, IoService, IoChannel}; @@ -196,7 +198,6 @@ impl Engine for AuthorityRound { } 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; @@ -308,6 +309,10 @@ impl Engine for AuthorityRound { t.sender().map(|_|()) // Perform EC recovery and cache sender } + fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { + new_header.number() > best_header.number() + } + fn register_message_channel(&self, message_channel: IoChannel) { *self.message_channel.lock() = Some(message_channel); } diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 5cc37b5f4..06ee551ba 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -39,6 +39,16 @@ fn consensus_round(header: &Header) -> Result { } impl ConsensusMessage { + pub fn new(signature: H520, height: Height, round: Round, step: Step, block_hash: Option) -> Self { + ConsensusMessage { + signature: signature, + height: height, + round: round, + step: step, + block_hash: block_hash + } + } + pub fn new_proposal(header: &Header) -> Result { Ok(ConsensusMessage { signature: try!(UntrustedRlp::new(header.seal()[1].as_slice()).as_val()), @@ -156,7 +166,7 @@ impl Encodable for ConsensusMessage { pub fn message_info_rlp(height: Height, round: Round, step: Step, block_hash: Option) -> Bytes { // TODO: figure out whats wrong with nested list encoding let mut s = RlpStream::new_list(5); - s.append(&height).append(&round).append(&step).append(&block_hash.unwrap_or(H256::zero())); + s.append(&height).append(&round).append(&step).append(&block_hash.unwrap_or_else(H256::zero)); s.out() } @@ -165,13 +175,10 @@ pub fn message_info_rlp_from_header(header: &Header) -> Result(signer: F, height: Height, round: Round, step: Step, block_hash: Option) -> Result where F: FnOnce(H256) -> Result { - let vote_info = message_info_rlp(height, round, step, block_hash); - signer(vote_info.sha3()).map(|ref signature| { - let mut s = RlpStream::new_list(2); - s.append(signature).append_raw(&vote_info, 1); - s.out() - }) +pub fn message_full_rlp(signature: &H520, vote_info: &Bytes) -> Bytes { + let mut s = RlpStream::new_list(2); + s.append(signature).append_raw(vote_info, 1); + s.out() } #[cfg(test)] @@ -214,13 +221,9 @@ mod tests { let addr = tap.insert_account("0".sha3(), "0").unwrap(); tap.unlock_account_permanently(addr, "0".into()).unwrap(); - let raw_rlp = message_full_rlp( - |mh| tap.sign(addr, None, mh).map(H520::from), - 123, - 2, - Step::Precommit, - Some(H256::default()) - ).unwrap(); + let mi = message_info_rlp(123, 2, Step::Precommit, Some(H256::default())); + + let raw_rlp = message_full_rlp(&tap.sign(addr, None, mi.sha3()).unwrap().into(), &mi); let rlp = UntrustedRlp::new(&raw_rlp); let message: ConsensusMessage = rlp.as_val().unwrap(); diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 33c768007..0cc8085d0 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -134,7 +134,6 @@ impl Tendermint { fn broadcast_message(&self, message: Bytes) { if let Some(ref channel) = *self.message_channel.lock() { - trace!(target: "poa", "broadcast_message: {:?}", &message); match channel.send(ClientIoMessage::BroadcastMessage(message)) { Ok(_) => trace!(target: "poa", "broadcast_message: BroadcastMessage message sent."), Err(err) => warn!(target: "poa", "broadcast_message: Could not send a sealing message {}.", err), @@ -146,14 +145,17 @@ impl Tendermint { fn generate_message(&self, block_hash: Option) -> Option { if let Some(ref ap) = *self.account_provider.lock() { - match message_full_rlp( - |mh| ap.sign(*self.authority.read(), self.password.read().clone(), mh).map(H520::from), - self.height.load(AtomicOrdering::SeqCst), - self.round.load(AtomicOrdering::SeqCst), - *self.step.read(), - block_hash - ) { - Ok(m) => Some(m), + let h = self.height.load(AtomicOrdering::SeqCst); + let r = self.round.load(AtomicOrdering::SeqCst); + let s = self.step.read(); + let vote_info = message_info_rlp(h, r, *s, block_hash); + let authority = self.authority.read(); + match ap.sign(*authority, self.password.read().clone(), vote_info.sha3()).map(Into::into) { + Ok(signature) => { + let message_rlp = message_full_rlp(&signature, &vote_info); + self.votes.vote(ConsensusMessage::new(signature, h, r, *s, block_hash), *authority); + Some(message_rlp) + }, Err(e) => { trace!(target: "poa", "generate_message: Could not sign the message {}", e); None @@ -309,7 +311,6 @@ impl Engine for Tendermint { } 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; @@ -344,7 +345,7 @@ impl Engine for Tendermint { let vote_info = message_info_rlp(height, round, Step::Propose, bh); if let Ok(signature) = ap.sign(*author, self.password.read().clone(), vote_info.sha3()).map(H520::from) { // Insert Propose vote. - self.votes.vote(ConsensusMessage { signature: signature, height: height, round: round, step: Step::Propose, block_hash: bh }, *author); + self.votes.vote(ConsensusMessage::new(signature, height, round, Step::Propose, bh), *author); // Remember proposal for later seal submission. *self.proposal.write() = Some(header.bare_hash()); Some(vec![ @@ -489,9 +490,14 @@ impl Engine for Tendermint { } fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { - let new_signatures = new_header.seal().get(2).expect("Tendermint seal should have three elements.").len(); - let best_signatures = best_header.seal().get(2).expect("Tendermint seal should have three elements.").len(); - new_signatures > best_signatures + trace!(target: "poa", "new_header: {}, best_header: {}", new_header.number(), best_header.number()); + if new_header.number() > best_header.number() { + true + } else { + let new_signatures = new_header.seal().get(2).expect("Tendermint seal should have three elements.").len(); + let best_signatures = best_header.seal().get(2).expect("Tendermint seal should have three elements.").len(); + new_signatures > best_signatures + } } fn register_message_channel(&self, message_channel: IoChannel) { @@ -544,7 +550,8 @@ mod tests { } fn vote(engine: &Arc, signer: F, height: usize, round: usize, step: Step, block_hash: Option) where F: FnOnce(H256) -> Result { - let m = message_full_rlp(signer, height, round, step, block_hash).unwrap(); + let mi = message_info_rlp(height, round, step, block_hash); + let m = message_full_rlp(&signer(mi.sha3()).unwrap().into(), &mi); engine.handle_message(UntrustedRlp::new(&m)).unwrap(); } From e76ead40d1e5de93dc8dafdeb1f338c99776b7b4 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 1 Dec 2016 21:55:43 +0000 Subject: [PATCH 126/295] update tracing message --- 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 1947cc9af..c819cef3f 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -464,7 +464,7 @@ impl Miner { /// 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."); + trace!(target: "miner", "seal_block_internally: attempting internal seal."); let s = self.engine.generate_seal(block.block()); if let Some(seal) = s { trace!(target: "miner", "seal_block_internally: managed internal seal. importing..."); From df1cce8e7f649328b93d8d1f186bc902d8698a1a Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 1 Dec 2016 21:56:38 +0000 Subject: [PATCH 127/295] simplify seal verification --- ethcore/src/engines/tendermint/message.rs | 8 ++-- ethcore/src/engines/tendermint/mod.rs | 37 +++++++++++++------ .../src/engines/tendermint/vote_collector.rs | 2 +- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 06ee551ba..40d848679 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -85,6 +85,10 @@ impl ConsensusMessage { let public_key = try!(recover(&self.signature.into(), &block_info.as_raw().sha3())); Ok(public_to_address(&public_key)) } + + pub fn precommit_hash(&self) -> H256 { + message_info_rlp(self.height, self.round, Step::Precommit, self.block_hash).sha3() + } } impl PartialOrd for ConsensusMessage { @@ -170,10 +174,6 @@ pub fn message_info_rlp(height: Height, round: Round, step: Step, block_hash: Op s.out() } -pub fn message_info_rlp_from_header(header: &Header) -> Result { - let round = try!(consensus_round(header)); - Ok(message_info_rlp(header.number() as Height, round, Step::Precommit, Some(header.bare_hash()))) -} pub fn message_full_rlp(signature: &H520, vote_info: &Bytes) -> Bytes { let mut s = RlpStream::new_list(2); diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 0cc8085d0..ae7d804d5 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -324,7 +324,7 @@ impl Engine for Tendermint { /// Get the address to be used as authority. fn on_new_block(&self, block: &mut ExecutedBlock) { - *self.authority.write() = *block.header().author() + *self.authority.write() = *block.header().author(); } /// Should this node participate. @@ -337,8 +337,11 @@ impl Engine for Tendermint { if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let author = header.author(); - // Only proposer can generate seal. - if self.is_proposer(author).is_err() { return None; } + // Only proposer can generate seal if None was generated. + if self.is_proposer(author).is_err() && self.proposal.read().is_none() { + return None; + } + let height = header.number() as Height; let round = self.round.load(AtomicOrdering::SeqCst); let bh = Some(header.bare_hash()); @@ -421,6 +424,7 @@ impl Engine for Tendermint { } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + // TODO: check total length of the last field let seal_length = header.seal().len(); if seal_length == self.seal_fields() { Ok(()) @@ -431,28 +435,37 @@ impl Engine for Tendermint { } } - /// Also transitions to Prevote if verifying Proposal. fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { let proposal = try!(ConsensusMessage::new_proposal(header)); let proposer = try!(proposal.verify()); - try!(self.is_proposer(&proposer)); - self.votes.vote(proposal, proposer); - let block_info_hash = try!(message_info_rlp_from_header(header)).sha3(); - + if !self.is_authority(&proposer) { + try!(Err(EngineError::NotAuthorized(proposer))) + } + let precommit_hash = proposal.precommit_hash(); // TODO: use addresses recovered during precommit vote let mut signature_count = 0; + let mut origins = HashSet::new(); for rlp in UntrustedRlp::new(&header.seal()[2]).iter() { let signature: H520 = try!(rlp.as_val()); - let address = public_to_address(&try!(recover(&signature.into(), &block_info_hash))); + let address = public_to_address(&try!(recover(&signature.into(), &precommit_hash))); if !self.our_params.authorities.contains(&address) { try!(Err(EngineError::NotAuthorized(address))) } - signature_count += 1; + if origins.insert(address) { + signature_count += 1; + } else { + warn!(target: "poa", "verify_block_unordered: Duplicate signature from {} on the seal.", address) + } } - if signature_count > self.our_params.authority_n { - try!(Err(BlockError::InvalidSealArity(Mismatch { expected: self.our_params.authority_n, found: signature_count }))) + + // Check if its just a proposal if there is not enough precommits. + if !self.is_above_threshold(signature_count) { + try!(self.is_proposer(&proposer)); + *self.proposal.write() = proposal.block_hash.clone(); + self.votes.vote(proposal, proposer); } + Ok(()) } diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 095b0fa37..93052a44a 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -120,7 +120,7 @@ impl VoteCollector { if origins.insert(origin) { n += 1; } else { - warn!("count_step_votes: authority {} has cast multiple step votes, this indicates malicious behaviour.", origin) + warn!("count_step_votes: Authority {} has cast multiple step votes, this indicates malicious behaviour.", origin) } } } From f0e9eae244f5d838388adfe4bfc40cd17031c17a Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 1 Dec 2016 22:07:18 +0000 Subject: [PATCH 128/295] remove difficulty check --- ethcore/src/engines/authority_round.rs | 4 ---- ethcore/src/engines/tendermint/mod.rs | 4 ---- 2 files changed, 8 deletions(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index dbfcbda31..6bce4cf83 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -287,10 +287,6 @@ impl Engine for AuthorityRound { try!(Err(EngineError::DoubleVote(header.author().clone()))); } - // 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; diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index ae7d804d5..e35dc6749 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -475,10 +475,6 @@ impl Engine for Tendermint { 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; From 2c8c09059862f740e3eba02e184d51111b8f6a51 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 2 Dec 2016 13:30:43 +0000 Subject: [PATCH 129/295] stricter size verification --- ethcore/src/engines/tendermint/mod.rs | 97 ++++++++++++++++++++------- 1 file changed, 72 insertions(+), 25 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index e35dc6749..c5149f555 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -56,8 +56,6 @@ pub type Height = usize; pub type Round = usize; pub type BlockHash = H256; -type Signatures = Vec; - /// Engine using `Tendermint` consensus algorithm, suitable for EVM chain. pub struct Tendermint { params: CommonParams, @@ -153,6 +151,7 @@ impl Tendermint { match ap.sign(*authority, self.password.read().clone(), vote_info.sha3()).map(Into::into) { Ok(signature) => { let message_rlp = message_full_rlp(&signature, &vote_info); + // TODO: memoize the rlp for consecutive broadcasts self.votes.vote(ConsensusMessage::new(signature, h, r, *s, block_hash), *authority); Some(message_rlp) }, @@ -173,16 +172,22 @@ impl Tendermint { } } + fn broadcast_old_messages(&self) { + if let Some(ref lc) = *self.lock_change.read() { + for m in self.votes.get_older_than(lc).into_iter() { + self.broadcast_message(m); + } + } + } + fn to_step(&self, step: Step) { *self.step.write() = step; match step { Step::Propose => { - trace!(target: "poa", "to_step: Propose."); *self.proposal.write() = None; self.update_sealing() }, Step::Prevote => { - trace!(target: "poa", "to_step: Prevote."); let block_hash = match *self.lock_change.read() { Some(ref m) if self.should_unlock(m.round) => self.proposal.read().clone(), Some(ref m) => m.block_hash, @@ -191,7 +196,6 @@ impl Tendermint { self.generate_and_broadcast_message(block_hash); }, Step::Precommit => { - trace!(target: "poa", "to_step: Precommit."); let block_hash = match *self.lock_change.read() { Some(ref m) if self.is_round(m) => { self.last_lock.store(m.round, AtomicOrdering::SeqCst); @@ -202,7 +206,7 @@ impl Tendermint { self.generate_and_broadcast_message(block_hash); }, Step::Commit => { - trace!(target: "poa", "to_step: Commit."); + debug!(target: "poa", "to_step: Commit."); // Commit the block using a complete signature set. let round = self.round.load(AtomicOrdering::SeqCst); if let Some(block_hash) = *self.proposal.read() { @@ -251,10 +255,12 @@ impl Tendermint { } fn increment_round(&self, n: Round) { + debug!(target: "poa", "increment_round: New round."); self.round.fetch_add(n, AtomicOrdering::SeqCst); } fn reset_round(&self) { + debug!(target: "poa", "reset_round: New height."); self.last_lock.store(0, AtomicOrdering::SeqCst); self.height.fetch_add(1, AtomicOrdering::SeqCst); self.round.store(0, AtomicOrdering::SeqCst); @@ -386,7 +392,7 @@ impl Engine for Tendermint { if is_newer_than_lock && message.step == Step::Prevote && self.has_enough_aligned_votes(&message) { - trace!(target: "poa", "handle_message: Lock change."); + debug!(target: "poa", "handle_message: Lock change."); *self.lock_change.write() = Some(message.clone()); } // Check if it can affect the step transition. @@ -424,15 +430,24 @@ impl Engine for Tendermint { } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { - // TODO: check total length of the last field let seal_length = header.seal().len(); if seal_length == self.seal_fields() { - Ok(()) + let signatures_len = header.seal()[2].len(); + if signatures_len >= 1 { + Ok(()) + } else { + Err(From::from(EngineError::BadSealFieldSize(OutOfBounds { + min: Some(1), + max: None, + found: signatures_len + }))) + } } else { Err(From::from(BlockError::InvalidSealArity( Mismatch { expected: self.seal_fields(), found: seal_length } ))) } + } fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { @@ -441,11 +456,13 @@ impl Engine for Tendermint { if !self.is_authority(&proposer) { try!(Err(EngineError::NotAuthorized(proposer))) } + let precommit_hash = proposal.precommit_hash(); // TODO: use addresses recovered during precommit vote + let ref signatures_field = header.seal()[2]; let mut signature_count = 0; let mut origins = HashSet::new(); - for rlp in UntrustedRlp::new(&header.seal()[2]).iter() { + for rlp in UntrustedRlp::new(signatures_field).iter() { let signature: H520 = try!(rlp.as_val()); let address = public_to_address(&try!(recover(&signature.into(), &precommit_hash))); if !self.our_params.authorities.contains(&address) { @@ -455,12 +472,22 @@ impl Engine for Tendermint { if origins.insert(address) { signature_count += 1; } else { - warn!(target: "poa", "verify_block_unordered: Duplicate signature from {} on the seal.", address) + warn!(target: "poa", "verify_block_unordered: Duplicate signature from {} on the seal.", address); + try!(Err(BlockError::InvalidSeal)); } } - // Check if its just a proposal if there is not enough precommits. + // Check if its a proposal if there is not enough precommits. if !self.is_above_threshold(signature_count) { + let signatures_len = signatures_field.len(); + // Proposal has to have an empty signature list. + if signatures_len != 1 { + try!(Err(EngineError::BadSealFieldSize(OutOfBounds { + min: Some(1), + max: Some(1), + found: signatures_len + }))); + } try!(self.is_proposer(&proposer)); *self.proposal.write() = proposal.block_hash.clone(); self.votes.vote(proposal, proposer); @@ -470,17 +497,28 @@ impl Engine for Tendermint { } fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { - // we should not calculate difficulty for genesis blocks if header.number() == 0 { - return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))); + try!(Err(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))); } 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() }))); + try!(Err(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(max_gas), found: header.gas_limit().clone() }))); } + + // Commit is longer than empty signature list. + let parent_signature_len = parent.seal()[2].len(); + if parent_signature_len < 1 { + try!(Err(EngineError::BadSealFieldSize(OutOfBounds { + // One signature. + min: Some(136), + max: None, + found: parent_signature_len + }))); + } + Ok(()) } @@ -571,7 +609,7 @@ mod tests { vec![ ::rlp::encode(&round).to_vec(), ::rlp::encode(&H520::from(signature)).to_vec(), - Vec::new() + ::rlp::EMPTY_LIST_RLP.to_vec() ] } @@ -681,21 +719,30 @@ mod tests { header.set_author(proposer); let mut seal = proposal_seal(&tap, &header, 0); - let voter = insert_and_unlock(&tap, "1"); let vote_info = message_info_rlp(0, 0, Step::Precommit, Some(header.bare_hash())); - let signature = tap.sign(voter, None, vote_info.sha3()).unwrap(); - - seal[2] = ::rlp::encode(&vec![H520::from(signature.clone())]).to_vec(); + let signature1 = tap.sign(proposer, None, vote_info.sha3()).unwrap(); + seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone())]).to_vec(); + header.set_seal(seal.clone()); + + // One good signature is not enough. + match engine.verify_block_unordered(&header, None) { + Err(Error::Engine(EngineError::BadSealFieldSize(_))) => {}, + _ => panic!(), + } + + let voter = insert_and_unlock(&tap, "0"); + let signature0 = tap.sign(voter, None, vote_info.sha3()).unwrap(); + + seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone()), H520::from(signature0.clone())]).to_vec(); header.set_seal(seal.clone()); - // One good signature. assert!(engine.verify_block_unordered(&header, None).is_ok()); let bad_voter = insert_and_unlock(&tap, "101"); let bad_signature = tap.sign(bad_voter, None, vote_info.sha3()).unwrap(); - seal[2] = ::rlp::encode(&vec![H520::from(signature), H520::from(bad_signature)]).to_vec(); + seal[2] = ::rlp::encode(&vec![H520::from(signature1), H520::from(bad_signature)]).to_vec(); header.set_seal(seal); // One good and one bad signature. @@ -717,7 +764,6 @@ mod tests { #[test] fn step_transitioning() { - ::env_logger::init().unwrap(); let (spec, tap) = setup(); let engine = spec.engine.clone(); let mut db_result = get_temp_state_db(); @@ -758,16 +804,17 @@ mod tests { #[test] fn timeout_transitioning() { - ::env_logger::init().unwrap(); let (spec, tap) = setup(); let engine = spec.engine.clone(); let mut db_result = get_temp_state_db(); let mut db = db_result.take(); spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); + println!("{:?}", ::rlp::EMPTY_LIST_RLP.to_vec().len()); + println!("{:?}", ::rlp::encode(&vec![H520::default()]).to_vec().len()); let v = insert_and_register(&tap, &engine, "0"); - ::std::thread::sleep(::std::time::Duration::from_millis(15000)); println!("done"); + } } From 0eb55cbd4d716dea0f6463f16253fa8d89a210fc Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 2 Dec 2016 13:31:28 +0000 Subject: [PATCH 130/295] update message test --- ethcore/src/engines/tendermint/message.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 40d848679..5eae139d5 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -254,13 +254,16 @@ mod tests { #[test] fn message_info_from_header() { - let mut header = Header::default(); - let seal = vec![ - ::rlp::encode(&0u8).to_vec(), - ::rlp::encode(&H520::default()).to_vec(), - Vec::new() - ]; - header.set_seal(seal); - assert_eq!(message_info_rlp_from_header(&header).unwrap().to_vec(), vec![228, 128, 128, 2, 160, 39, 191, 179, 126, 80, 124, 233, 13, 161, 65, 48, 114, 4, 177, 198, 186, 36, 25, 67, 128, 97, 53, 144, 172, 80, 202, 75, 29, 113, 152, 255, 101]); + let header = Header::default(); + let pro = ConsensusMessage { + signature: Default::default(), + height: 0, + round: 0, + step: Step::Propose, + block_hash: Some(header.bare_hash()) + }; + let pre = message_info_rlp(0, 0, Step::Precommit, Some(header.bare_hash())); + + assert_eq!(pro.precommit_hash(), pre.sha3()); } } From e0f2fac4413822bfa869298b0569f487a79f90f1 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 2 Dec 2016 13:32:00 +0000 Subject: [PATCH 131/295] new error type --- ethcore/src/engines/authority_round.rs | 1 - ethcore/src/engines/mod.rs | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 6bce4cf83..f59fd0b17 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -275,7 +275,6 @@ impl Engine for AuthorityRound { } 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() }))); } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 67e89b834..f157cc505 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -56,6 +56,8 @@ pub enum EngineError { NotProposer(Mismatch), /// Message was not expected. UnexpectedMessage, + /// Seal field has an unexpected size. + BadSealFieldSize(OutOfBounds), } impl fmt::Display for EngineError { @@ -66,6 +68,7 @@ impl fmt::Display for EngineError { NotProposer(ref mis) => format!("Author is not a current proposer: {}", mis), NotAuthorized(ref address) => format!("Signer {} is not authorized.", address), UnexpectedMessage => "This Engine should not be fed messages.".into(), + BadSealFieldSize(ref oob) => format!("Seal field has an unexpected length: {}", oob), }; f.write_fmt(format_args!("Engine error ({})", msg)) From 91099f62c90122f320be44958df2f3f29cbee8ae Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 2 Dec 2016 13:37:49 +0000 Subject: [PATCH 132/295] add more gossip if step is stuck --- ethcore/src/engines/tendermint/transition.rs | 18 +++++++++++------- .../src/engines/tendermint/vote_collector.rs | 5 +++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index a494cfc0f..1ca230b3e 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -78,38 +78,42 @@ impl IoHandler for TransitionHandler { Step::Propose => { trace!(target: "poa", "timeout: Propose timeout."); set_timeout(io, engine.our_params.timeouts.prevote); - Step::Prevote + Some(Step::Prevote) }, Step::Prevote if engine.has_enough_any_votes() => { trace!(target: "poa", "timeout: Prevote timeout."); set_timeout(io, engine.our_params.timeouts.precommit); - Step::Precommit + Some(Step::Precommit) }, Step::Prevote => { trace!(target: "poa", "timeout: Prevote timeout without enough votes."); set_timeout(io, engine.our_params.timeouts.prevote); - Step::Prevote + engine.broadcast_old_messages(); + None }, Step::Precommit if engine.has_enough_any_votes() => { trace!(target: "poa", "timeout: Precommit timeout."); set_timeout(io, engine.our_params.timeouts.propose); engine.increment_round(1); - Step::Propose + Some(Step::Propose) }, Step::Precommit => { trace!(target: "poa", "timeout: Precommit timeout without enough votes."); set_timeout(io, engine.our_params.timeouts.precommit); - Step::Precommit + engine.broadcast_old_messages(); + None }, Step::Commit => { trace!(target: "poa", "timeout: Commit timeout."); set_timeout(io, engine.our_params.timeouts.propose); engine.reset_round(); - Step::Propose + Some(Step::Propose) }, }; - engine.to_step(next_step) + if let Some(s) = next_step { + engine.to_step(s) + } } } } diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 93052a44a..8bb271a35 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -126,6 +126,11 @@ impl VoteCollector { } n } + + pub fn get_older_than(&self, message: &ConsensusMessage) -> Vec { + let guard = self.votes.read(); + guard.keys().take_while(|m| *m <= message).map(|m| ::rlp::encode(m).to_vec()).collect() + } } #[cfg(test)] From c8a3db4c5290410f0244f5c4f2d7245a786032a5 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 2 Dec 2016 13:59:54 +0000 Subject: [PATCH 133/295] new error proposal test --- ethcore/src/engines/tendermint/mod.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index c5149f555..c5a556a62 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -510,10 +510,10 @@ impl Engine for Tendermint { // Commit is longer than empty signature list. let parent_signature_len = parent.seal()[2].len(); - if parent_signature_len < 1 { + if parent_signature_len <= 1 { try!(Err(EngineError::BadSealFieldSize(OutOfBounds { // One signature. - min: Some(136), + min: Some(69), max: None, found: parent_signature_len }))); @@ -695,11 +695,10 @@ mod tests { let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Good proposer. - assert!(engine.verify_block_unordered(&header, None).is_ok()); + assert!(engine.verify_block_unordered(&header.clone(), None).is_ok()); - let mut header = Header::default(); - let random = insert_and_unlock(&tap, "101"); - header.set_author(random); + let validator = insert_and_unlock(&tap, "0"); + header.set_author(validator); let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Bad proposer. @@ -707,6 +706,16 @@ mod tests { Err(Error::Engine(EngineError::NotProposer(_))) => {}, _ => panic!(), } + + let random = insert_and_unlock(&tap, "101"); + header.set_author(random); + let seal = proposal_seal(&tap, &header, 0); + header.set_seal(seal); + // Not authority. + match engine.verify_block_unordered(&header, None) { + Err(Error::Engine(EngineError::NotAuthorized(_))) => {}, + _ => panic!(), + } } #[test] From ff6240eff3851b0172f313636bb6cb06973d0924 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 2 Dec 2016 20:03:15 +0000 Subject: [PATCH 134/295] insert block into queue when sealing --- ethcore/src/miner/miner.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index c819cef3f..cb97f35ba 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -1030,6 +1030,11 @@ impl MinerService for Miner { let (block, original_work_hash) = self.prepare_block(chain); if self.seals_internally { trace!(target: "miner", "update_sealing: engine indicates internal sealing"); + { + let mut sealing_work = self.sealing_work.lock(); + sealing_work.queue.push(block.clone()); + sealing_work.queue.use_last_ref(); + } self.seal_and_import_block_internally(chain, block); } else { trace!(target: "miner", "update_sealing: engine does not seal internally, preparing work"); @@ -1052,7 +1057,7 @@ impl MinerService for Miner { ret.map(f) } - fn submit_seal(&self, chain: &MiningBlockChainClient, pow_hash: H256, seal: Vec) -> Result<(), Error> { + fn submit_seal(&self, chain: &MiningBlockChainClient, block_hash: H256, seal: Vec) -> Result<(), Error> { let result = if let Some(b) = self.sealing_work.lock().queue.get_used_if( if self.options.enable_resubmission { @@ -1060,22 +1065,22 @@ impl MinerService for Miner { } else { GetAction::Take }, - |b| &b.hash() == &pow_hash + |b| { println!("should be {:?}, but is {:?}", b.hash(), &block_hash); &b.hash() == &block_hash } ) { - trace!(target: "miner", "Sealing block {}={}={} with seal {:?}", pow_hash, b.hash(), b.header().bare_hash(), seal); + trace!(target: "miner", "Submitted block {}={}={} with seal {:?}", block_hash, b.hash(), b.header().bare_hash(), seal); b.lock().try_seal(&*self.engine, seal).or_else(|(e, _)| { warn!(target: "miner", "Mined solution rejected: {}", e); Err(Error::PowInvalid) }) } else { - warn!(target: "miner", "Mined solution rejected: Block unknown or out of date."); + warn!(target: "miner", "Submitted solution rejected: Block unknown or out of date."); Err(Error::PowHashInvalid) }; result.and_then(|sealed| { let n = sealed.header().number(); let h = sealed.header().hash(); try!(chain.import_sealed_block(sealed)); - info!(target: "miner", "Mined block imported OK. #{}: {}", Colour::White.bold().paint(format!("{}", n)), Colour::White.bold().paint(h.hex())); + info!(target: "miner", "Submitted block imported OK. #{}: {}", Colour::White.bold().paint(format!("{}", n)), Colour::White.bold().paint(h.hex())); Ok(()) }) } From 9084e6242d4c6c94e836d65b46c8130590b2062e Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 2 Dec 2016 20:04:12 +0000 Subject: [PATCH 135/295] lock ordering --- ethcore/src/engines/tendermint/mod.rs | 104 ++++++++++++++------------ 1 file changed, 57 insertions(+), 47 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index c5a556a62..bc29f78dd 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -152,7 +152,10 @@ impl Tendermint { Ok(signature) => { let message_rlp = message_full_rlp(&signature, &vote_info); // TODO: memoize the rlp for consecutive broadcasts - self.votes.vote(ConsensusMessage::new(signature, h, r, *s, block_hash), *authority); + let message = ConsensusMessage::new(signature, h, r, *s, block_hash); + self.votes.vote(message.clone(), *authority); + self.handle_valid_message(&message); + Some(message_rlp) }, Err(e) => { @@ -189,15 +192,15 @@ impl Tendermint { }, Step::Prevote => { let block_hash = match *self.lock_change.read() { - Some(ref m) if self.should_unlock(m.round) => self.proposal.read().clone(), - Some(ref m) => m.block_hash, - None => None, + Some(ref m) if !self.should_unlock(m.round) => m.block_hash, + _ => self.proposal.read().clone(), }; self.generate_and_broadcast_message(block_hash); }, Step::Precommit => { let block_hash = match *self.lock_change.read() { - Some(ref m) if self.is_round(m) => { + Some(ref m) if self.is_round(m) && m.block_hash.is_some() => { + trace!(target: "poa", "to_step: Setting last lock: {}", m.round); self.last_lock.store(m.round, AtomicOrdering::SeqCst); m.block_hash }, @@ -290,6 +293,51 @@ impl Tendermint { let aligned_count = self.votes.count_aligned_votes(&message); self.is_above_threshold(aligned_count) } + + fn handle_valid_message(&self, message: &ConsensusMessage) { + trace!(target: "poa", "handle_valid_message: Processing valid message: {:?}", message); + let is_newer_than_lock = match *self.lock_change.read() { + Some(ref lock) => message > lock, + None => true, + }; + if is_newer_than_lock + && message.step == Step::Prevote + && message.block_hash.is_some() + && self.has_enough_aligned_votes(message) { + debug!(target: "poa", "handle_valid_message: Lock change."); + *self.lock_change.write() = Some(message.clone()); + } + // Check if it can affect the step transition. + if self.is_height(message) { + let next_step = match *self.step.read() { + Step::Precommit if self.has_enough_aligned_votes(message) => { + if message.block_hash.is_none() { + self.increment_round(1); + Some(Step::Propose) + } else { + Some(Step::Commit) + } + }, + Step::Precommit if self.has_enough_future_step_votes(message) => { + self.increment_round(message.round - self.round.load(AtomicOrdering::SeqCst)); + Some(Step::Precommit) + }, + Step::Prevote if self.has_enough_aligned_votes(message) => Some(Step::Precommit), + Step::Prevote if self.has_enough_future_step_votes(message) => { + self.increment_round(message.round - self.round.load(AtomicOrdering::SeqCst)); + Some(Step::Prevote) + }, + _ => None, + }; + + if let Some(step) = next_step { + trace!(target: "poa", "handle_valid_message: Transition triggered."); + if let Err(io_err) = self.step_service.send_message(step) { + warn!(target: "poa", "Could not proceed to next step {}.", io_err) + } + } + } + } } impl Engine for Tendermint { @@ -381,50 +429,9 @@ impl Engine for Tendermint { try!(Err(EngineError::NotAuthorized(sender))); } - trace!(target: "poa", "handle_message: Processing new authorized message: {:?}", &message); self.votes.vote(message.clone(), sender); - self.broadcast_message(rlp.as_raw().to_vec()); - let is_newer_than_lock = match *self.lock_change.read() { - Some(ref lock) => &message > lock, - None => true, - }; - if is_newer_than_lock - && message.step == Step::Prevote - && self.has_enough_aligned_votes(&message) { - debug!(target: "poa", "handle_message: Lock change."); - *self.lock_change.write() = Some(message.clone()); - } - // Check if it can affect the step transition. - if self.is_height(&message) { - let next_step = match *self.step.read() { - Step::Precommit if self.has_enough_aligned_votes(&message) => { - if message.block_hash.is_none() { - self.increment_round(1); - Some(Step::Propose) - } else { - Some(Step::Commit) - } - }, - Step::Precommit if self.has_enough_future_step_votes(&message) => { - self.increment_round(message.round - self.round.load(AtomicOrdering::SeqCst)); - Some(Step::Precommit) - }, - Step::Prevote if self.has_enough_aligned_votes(&message) => Some(Step::Precommit), - Step::Prevote if self.has_enough_future_step_votes(&message) => { - self.increment_round(message.round - self.round.load(AtomicOrdering::SeqCst)); - Some(Step::Prevote) - }, - _ => None, - }; - - if let Some(step) = next_step { - trace!(target: "poa", "handle_message: Transition triggered."); - if let Err(io_err) = self.step_service.send_message(step) { - warn!(target: "poa", "Could not proceed to next step {}.", io_err) - } - } - } + self.handle_valid_message(&message); } Ok(()) } @@ -534,6 +541,9 @@ impl Engine for Tendermint { fn set_signer(&self, address: Address, password: String) { *self.authority.write() = address; *self.password.write() = Some(password); + if let Err(io_err) = self.step_service.send_message(Step::Propose) { + warn!(target: "poa", "Could not reset the round {}.", io_err); + } } fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { From f1542b56120150f40ca1525c7e3e31a511d02e51 Mon Sep 17 00:00:00 2001 From: keorn Date: Sat, 3 Dec 2016 16:19:51 +0000 Subject: [PATCH 136/295] better genesis seal rlp --- ethcore/res/tendermint.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index e411d54e2..28c0fd43c 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -21,7 +21,7 @@ "seal": { "generic": { "fields": 3, - "rlp": "0x40010" + "rlp": "80b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f843b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" } }, "difficulty": "0x20000", From edef7a185fb34f3d3ae5c63f37c01f629ee9fbae Mon Sep 17 00:00:00 2001 From: keorn Date: Sun, 4 Dec 2016 19:42:53 +0000 Subject: [PATCH 137/295] remove tracing --- 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 cb97f35ba..f32fee413 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -1065,7 +1065,7 @@ impl MinerService for Miner { } else { GetAction::Take }, - |b| { println!("should be {:?}, but is {:?}", b.hash(), &block_hash); &b.hash() == &block_hash } + |b| &b.hash() == &block_hash ) { trace!(target: "miner", "Submitted block {}={}={} with seal {:?}", block_hash, b.hash(), b.header().bare_hash(), seal); b.lock().try_seal(&*self.engine, seal).or_else(|(e, _)| { From f7a01b87b5963ba54cbfdae67c1726f93b1ef05b Mon Sep 17 00:00:00 2001 From: keorn Date: Sun, 4 Dec 2016 19:43:24 +0000 Subject: [PATCH 138/295] better gossip, better proposal collection --- ethcore/src/engines/tendermint/mod.rs | 135 ++++++++++-------- ethcore/src/engines/tendermint/transition.rs | 2 +- .../src/engines/tendermint/vote_collector.rs | 22 ++- 3 files changed, 84 insertions(+), 75 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index bc29f78dd..5244861cf 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -154,6 +154,7 @@ impl Tendermint { // TODO: memoize the rlp for consecutive broadcasts let message = ConsensusMessage::new(signature, h, r, *s, block_hash); self.votes.vote(message.clone(), *authority); + debug!(target: "poa", "Generated a message for height {:?}.", message); self.handle_valid_message(&message); Some(message_rlp) @@ -176,13 +177,20 @@ impl Tendermint { } fn broadcast_old_messages(&self) { - if let Some(ref lc) = *self.lock_change.read() { - for m in self.votes.get_older_than(lc).into_iter() { - self.broadcast_message(m); - } + for m in self.votes.get_up_to(self.height.load(AtomicOrdering::SeqCst)).into_iter() { + self.broadcast_message(m); } } + fn to_height(&self, height: Height) { + debug!(target: "poa", "Transitioning to height {}.", height); + self.last_lock.store(0, AtomicOrdering::SeqCst); + self.height.store(height, AtomicOrdering::SeqCst); + self.round.store(0, AtomicOrdering::SeqCst); + *self.lock_change.write() = None; + } + + /// Use via step_service to transition steps. fn to_step(&self, step: Step) { *self.step.write() = step; match step { @@ -209,22 +217,26 @@ impl Tendermint { self.generate_and_broadcast_message(block_hash); }, Step::Commit => { - debug!(target: "poa", "to_step: Commit."); + trace!(target: "poa", "to_step: Commit."); // Commit the block using a complete signature set. let round = self.round.load(AtomicOrdering::SeqCst); + let height = self.height.load(AtomicOrdering::SeqCst); if let Some(block_hash) = *self.proposal.read() { - if let Some(seal) = self.votes.seal_signatures(self.height.load(AtomicOrdering::SeqCst), round, block_hash) { - let seal = vec![ - ::rlp::encode(&round).to_vec(), - ::rlp::encode(&seal.proposal).to_vec(), - ::rlp::encode(&seal.votes).to_vec() - ]; - self.submit_seal(block_hash, seal); + // Generate seal and remove old votes. + if let Some(seal) = self.votes.seal_signatures(height, round, block_hash) { + trace!(target: "poa", "to_step: Collected seal: {:?}", seal); + if self.is_proposer(&*self.authority.read()).is_ok() { + let seal = vec![ + ::rlp::encode(&round).to_vec(), + ::rlp::encode(&seal.proposal).to_vec(), + ::rlp::encode(&seal.votes).to_vec() + ]; + self.submit_seal(block_hash, seal); + } } else { warn!(target: "poa", "Proposal was not found!"); } } - *self.lock_change.write() = None; }, } } @@ -237,10 +249,11 @@ impl Tendermint { n > self.our_params.authority_n * 2/3 } - /// Round proposer switching. - fn is_proposer(&self, address: &Address) -> Result<(), EngineError> { + /// Check if address is a proposer for given round. + fn is_round_proposer(&self, height: Height, round: Round, address: &Address) -> Result<(), EngineError> { let ref p = self.our_params; - let proposer_nonce = self.height.load(AtomicOrdering::SeqCst) + self.round.load(AtomicOrdering::SeqCst); + let proposer_nonce = height + round; + trace!(target: "poa", "is_proposer: Proposer nonce: {}", proposer_nonce); let proposer = p.authorities.get(proposer_nonce % p.authority_n).expect("There are authority_n authorities; taking number modulo authority_n gives number in authority_n range; qed"); if proposer == address { Ok(()) @@ -249,6 +262,11 @@ impl Tendermint { } } + /// Check if address is the current proposer. + fn is_proposer(&self, address: &Address) -> Result<(), EngineError> { + self.is_round_proposer(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), address) + } + fn is_height(&self, message: &ConsensusMessage) -> bool { message.is_height(self.height.load(AtomicOrdering::SeqCst)) } @@ -258,15 +276,12 @@ impl Tendermint { } fn increment_round(&self, n: Round) { - debug!(target: "poa", "increment_round: New round."); + trace!(target: "poa", "increment_round: New round."); self.round.fetch_add(n, AtomicOrdering::SeqCst); } - fn reset_round(&self) { - debug!(target: "poa", "reset_round: New height."); - self.last_lock.store(0, AtomicOrdering::SeqCst); - self.height.fetch_add(1, AtomicOrdering::SeqCst); - self.round.store(0, AtomicOrdering::SeqCst); + fn new_height(&self) { + self.to_height(self.height.load(AtomicOrdering::SeqCst) + 1); } fn should_unlock(&self, lock_change_round: Round) -> bool { @@ -295,7 +310,6 @@ impl Tendermint { } fn handle_valid_message(&self, message: &ConsensusMessage) { - trace!(target: "poa", "handle_valid_message: Processing valid message: {:?}", message); let is_newer_than_lock = match *self.lock_change.read() { Some(ref lock) => message > lock, None => true, @@ -304,7 +318,7 @@ impl Tendermint { && message.step == Step::Prevote && message.block_hash.is_some() && self.has_enough_aligned_votes(message) { - debug!(target: "poa", "handle_valid_message: Lock change."); + trace!(target: "poa", "handle_valid_message: Lock change."); *self.lock_change.write() = Some(message.clone()); } // Check if it can affect the step transition. @@ -376,11 +390,6 @@ impl Engine for Tendermint { }); } - /// Get the address to be used as authority. - fn on_new_block(&self, block: &mut ExecutedBlock) { - *self.authority.write() = *block.header().author(); - } - /// Should this node participate. fn is_sealer(&self, address: &Address) -> Option { Some(self.is_authority(address)) @@ -399,14 +408,15 @@ impl Engine for Tendermint { let height = header.number() as Height; let round = self.round.load(AtomicOrdering::SeqCst); let bh = Some(header.bare_hash()); - let vote_info = message_info_rlp(height, round, Step::Propose, bh); + let vote_info = message_info_rlp(height, round, Step::Propose, bh.clone()); if let Ok(signature) = ap.sign(*author, self.password.read().clone(), vote_info.sha3()).map(H520::from) { // Insert Propose vote. + debug!(target: "poa", "Submitting proposal {} at height {} round {}.", header.bare_hash(), height, round); self.votes.vote(ConsensusMessage::new(signature, height, round, Step::Propose, bh), *author); // Remember proposal for later seal submission. - *self.proposal.write() = Some(header.bare_hash()); + *self.proposal.write() = bh; Some(vec![ - ::rlp::encode(&self.round.load(AtomicOrdering::SeqCst)).to_vec(), + ::rlp::encode(&round).to_vec(), ::rlp::encode(&signature).to_vec(), ::rlp::EMPTY_LIST_RLP.to_vec() ]) @@ -422,8 +432,7 @@ impl Engine for Tendermint { fn handle_message(&self, rlp: UntrustedRlp) -> Result<(), Error> { let message: ConsensusMessage = try!(rlp.as_val()); - // Check if the message is known. - if !self.votes.is_known(&message) { + if !self.votes.is_old_or_known(&message) { let sender = public_to_address(&try!(recover(&message.signature.into(), &try!(rlp.at(1)).as_raw().sha3()))); if !self.is_authority(&sender) { try!(Err(EngineError::NotAuthorized(sender))); @@ -431,7 +440,10 @@ impl Engine for Tendermint { self.votes.vote(message.clone(), sender); self.broadcast_message(rlp.as_raw().to_vec()); + trace!(target: "poa", "Handling a valid message: {:?}", message); self.handle_valid_message(&message); + } else { + trace!(target: "poa", "handle_message: Old or known message ignored {:?}.", message); } Ok(()) } @@ -483,9 +495,14 @@ impl Engine for Tendermint { try!(Err(BlockError::InvalidSeal)); } } - + + if self.is_above_threshold(signature_count) { + // Skip ahead if block is from the future. + if proposal.height > self.height.load(AtomicOrdering::SeqCst) { + self.to_height(proposal.height); + } // Check if its a proposal if there is not enough precommits. - if !self.is_above_threshold(signature_count) { + } else { let signatures_len = signatures_field.len(); // Proposal has to have an empty signature list. if signatures_len != 1 { @@ -495,11 +512,13 @@ impl Engine for Tendermint { found: signatures_len }))); } - try!(self.is_proposer(&proposer)); - *self.proposal.write() = proposal.block_hash.clone(); - self.votes.vote(proposal, proposer); + try!(self.is_round_proposer(proposal.height, proposal.round, &proposer)); + if self.is_round(&proposal) { + debug!(target: "poa", "Received a new proposal for height {}, round {} from {}.", proposal.height, proposal.round, proposer); + *self.proposal.write() = proposal.block_hash.clone(); + self.votes.vote(proposal, proposer); + } } - Ok(()) } @@ -548,12 +567,22 @@ impl Engine for Tendermint { fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { trace!(target: "poa", "new_header: {}, best_header: {}", new_header.number(), best_header.number()); - if new_header.number() > best_header.number() { - true + let new_number = new_header.number(); + let best_number = best_header.number(); + if new_number != best_number { + new_number > best_number } else { - let new_signatures = new_header.seal().get(2).expect("Tendermint seal should have three elements.").len(); - let best_signatures = best_header.seal().get(2).expect("Tendermint seal should have three elements.").len(); - new_signatures > best_signatures + let new_seal = new_header.seal(); + let best_seal = best_header.seal(); + let new_signatures = new_seal.get(2).expect("Tendermint seal should have three elements.").len(); + let best_signatures = best_seal.get(2).expect("Tendermint seal should have three elements.").len(); + if new_signatures > best_signatures { + true + } else { + let new_round: Round = ::rlp::Rlp::new(&new_seal.get(0).expect("Tendermint seal should have three elements.")).as_val(); + let best_round: Round = ::rlp::Rlp::new(&best_seal.get(0).expect("Tendermint seal should have three elements.")).as_val(); + new_round > best_round + } } } @@ -820,20 +849,4 @@ mod tests { let second = test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal)); assert!(first ^ second); } - - #[test] - fn timeout_transitioning() { - let (spec, tap) = setup(); - let engine = spec.engine.clone(); - let mut db_result = get_temp_state_db(); - let mut db = db_result.take(); - spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); - - println!("{:?}", ::rlp::EMPTY_LIST_RLP.to_vec().len()); - println!("{:?}", ::rlp::encode(&vec![H520::default()]).to_vec().len()); - let v = insert_and_register(&tap, &engine, "0"); - - println!("done"); - - } } diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index 1ca230b3e..22d4d9498 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -106,7 +106,7 @@ impl IoHandler for TransitionHandler { Step::Commit => { trace!(target: "poa", "timeout: Commit timeout."); set_timeout(io, engine.our_params.timeouts.propose); - engine.reset_round(); + engine.new_height(); Some(Step::Propose) }, }; diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 8bb271a35..271ff5f9a 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -58,20 +58,15 @@ impl VoteCollector { /// Insert vote if it is newer than the oldest one. pub fn vote(&self, message: ConsensusMessage, voter: Address) -> Option
{ - let is_new = { - let guard = self.votes.read(); - guard.keys().next().map_or(true, |oldest| &message > oldest) - }; - if is_new { - self.votes.write().insert(message, voter) - } else { - trace!(target: "poa", "vote: Old message ignored {:?}.", message); - None - } + self.votes.write().insert(message, voter) } - pub fn is_known(&self, message: &ConsensusMessage) -> bool { + pub fn is_old_or_known(&self, message: &ConsensusMessage) -> bool { self.votes.read().contains_key(message) + || { + let guard = self.votes.read(); + guard.keys().next().map_or(true, |oldest| message <= oldest) + } } /// Throws out messages older than message, leaves message as marker for the oldest. @@ -94,6 +89,7 @@ impl VoteCollector { .collect::>(); (proposal, votes) }; + // Remove messages that are no longer relevant. votes.last().map(|m| self.throw_out_old(m)); proposal.map(|p| SealSignatures { proposal: p.signature, @@ -127,9 +123,9 @@ impl VoteCollector { n } - pub fn get_older_than(&self, message: &ConsensusMessage) -> Vec { + pub fn get_up_to(&self, height: Height) -> Vec { let guard = self.votes.read(); - guard.keys().take_while(|m| *m <= message).map(|m| ::rlp::encode(m).to_vec()).collect() + guard.keys().take_while(|m| m.height <= height).map(|m| ::rlp::encode(m).to_vec()).collect() } } From b30c1d56020efd34bba4ee3a337a1fdba1527a0d Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 5 Dec 2016 13:24:22 +0000 Subject: [PATCH 139/295] fix tests --- ethcore/res/ethereum/tests | 2 +- ethcore/src/engines/tendermint/mod.rs | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/ethcore/res/ethereum/tests b/ethcore/res/ethereum/tests index e8f4624b7..d509c7593 160000 --- a/ethcore/res/ethereum/tests +++ b/ethcore/res/ethereum/tests @@ -1 +1 @@ -Subproject commit e8f4624b7f1a15c63674eecf577c7ab76c3b16be +Subproject commit d509c75936ec6cbba683ee1916aa0bca436bc376 diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 5244861cf..bec41d37f 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -667,7 +667,7 @@ mod tests { } fn insert_and_register(tap: &Arc, engine: &Arc, acc: &str) -> Address { - let addr = tap.insert_account(acc.sha3(), acc).unwrap(); + let addr = insert_and_unlock(tap, acc); engine.set_signer(addr.clone(), acc.into()); addr } @@ -729,14 +729,14 @@ mod tests { let engine = spec.engine; let mut header = Header::default(); - let validator = insert_and_unlock(&tap, "1"); + let validator = insert_and_unlock(&tap, "0"); header.set_author(validator); let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Good proposer. assert!(engine.verify_block_unordered(&header.clone(), None).is_ok()); - let validator = insert_and_unlock(&tap, "0"); + let validator = insert_and_unlock(&tap, "1"); header.set_author(validator); let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); @@ -812,14 +812,15 @@ mod tests { #[test] fn step_transitioning() { + ::env_logger::init().unwrap(); let (spec, tap) = setup(); let engine = spec.engine.clone(); let mut db_result = get_temp_state_db(); let mut db = db_result.take(); spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); - let v0 = insert_and_unlock(&tap, "0"); - let v1 = insert_and_unlock(&tap, "1"); + let v0 = insert_and_register(&tap, &engine, "0"); + let v1 = insert_and_register(&tap, &engine, "1"); let h = 1; let r = 0; @@ -842,7 +843,7 @@ mod tests { vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal); // Wait a bit for async stuff. - ::std::thread::sleep(::std::time::Duration::from_millis(50)); + ::std::thread::sleep(::std::time::Duration::from_millis(100)); seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v0, v1); let first = test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal.clone())); seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v1, v0); From db59bd8731c1baa3428464a0afd948f5ced9f994 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 5 Dec 2016 14:28:50 +0000 Subject: [PATCH 140/295] update genesis seal --- ethcore/res/tendermint.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index 28c0fd43c..38e5334a3 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -20,8 +20,7 @@ "genesis": { "seal": { "generic": { - "fields": 3, - "rlp": "80b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f843b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "rlp": "f88980b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f843b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" } }, "difficulty": "0x20000", From 4f857642b59586167f37c2dbcd0cc4cf6b8962f5 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 5 Dec 2016 16:28:56 +0000 Subject: [PATCH 141/295] rename set_sealer --- ethcore/src/miner/miner.rs | 2 +- ethcore/src/miner/mod.rs | 2 +- rpc/src/v1/impls/parity_set.rs | 4 ++-- rpc/src/v1/tests/helpers/miner_service.rs | 2 +- rpc/src/v1/tests/mocked/parity_set.rs | 4 ++-- rpc/src/v1/traits/parity_set.rs | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index f32fee413..4b38a7e07 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -737,7 +737,7 @@ impl MinerService for Miner { *self.author.write() = author; } - fn set_signer(&self, address: Address, password: String) -> Result<(), AccountError> { + fn set_consensus_signer(&self, address: Address, password: String) -> Result<(), AccountError> { if self.seals_internally { if let Some(ref ap) = self.accounts { try!(ap.sign(address.clone(), Some(password.clone()), Default::default())); diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 89937e115..26cefb295 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -77,7 +77,7 @@ pub trait MinerService : Send + Sync { fn set_author(&self, author: Address); /// Set info necessary to sign consensus messages. - fn set_signer(&self, address: Address, password: String) -> Result<(), ::account_provider::Error>; + fn set_consensus_signer(&self, address: Address, password: String) -> Result<(), ::account_provider::Error>; /// Get the extra_data that we will seal blocks with. fn extra_data(&self) -> Bytes; diff --git a/rpc/src/v1/impls/parity_set.rs b/rpc/src/v1/impls/parity_set.rs index 11bc48268..e28f9a573 100644 --- a/rpc/src/v1/impls/parity_set.rs +++ b/rpc/src/v1/impls/parity_set.rs @@ -116,9 +116,9 @@ impl ParitySet for ParitySetClient where Ok(true) } - fn set_sealer(&self, address: H160, password: String) -> Result { + fn set_consensus_signer(&self, address: H160, password: String) -> Result { try!(self.active()); - try!(take_weak!(self.miner).set_signer(address.into(), password).map_err(Into::into).map_err(errors::from_password_error)); + try!(take_weak!(self.miner).set_consensus_signer(address.into(), password).map_err(Into::into).map_err(errors::from_password_error)); Ok(true) } diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index 68caa137b..6037cdd4a 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -87,7 +87,7 @@ impl MinerService for TestMinerService { *self.author.write() = author; } - fn set_signer(&self, address: Address, password: String) -> Result<(), AccountError> { + fn set_consensus_signer(&self, address: Address, password: String) -> Result<(), AccountError> { *self.author.write() = address; *self.password.write() = password; Ok(()) diff --git a/rpc/src/v1/tests/mocked/parity_set.rs b/rpc/src/v1/tests/mocked/parity_set.rs index 8ae3976f9..fdf3f2d0f 100644 --- a/rpc/src/v1/tests/mocked/parity_set.rs +++ b/rpc/src/v1/tests/mocked/parity_set.rs @@ -107,14 +107,14 @@ fn rpc_parity_set_author() { } #[test] -fn rpc_parity_set_sealer() { +fn rpc_parity_set_consensus_signer() { let miner = miner_service(); let client = client_service(); let network = network_service(); let io = IoHandler::new(); io.add_delegate(parity_set_client(&client, &miner, &network).to_delegate()); - let request = r#"{"jsonrpc": "2.0", "method": "parity_setSealer", "params":["0xcd1722f3947def4cf144679da39c4c32bdc35681", "password"], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_setConsensusSigner", "params":["0xcd1722f3947def4cf144679da39c4c32bdc35681", "password"], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); diff --git a/rpc/src/v1/traits/parity_set.rs b/rpc/src/v1/traits/parity_set.rs index 89d92e043..e196a7d21 100644 --- a/rpc/src/v1/traits/parity_set.rs +++ b/rpc/src/v1/traits/parity_set.rs @@ -45,8 +45,8 @@ build_rpc_trait! { fn set_author(&self, H160) -> Result; /// Sets account for signing consensus messages. - #[rpc(name = "parity_setSealer")] - fn set_sealer(&self, H160, String) -> Result; + #[rpc(name = "parity_setConsensusSigner")] + fn set_consensus_signer(&self, H160, String) -> Result; /// Sets the limits for transaction queue. #[rpc(name = "parity_setTransactionsLimit")] From d9eb5e7f1dea3c573ddff5a36977025a02d9064a Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 7 Dec 2016 09:32:36 +0100 Subject: [PATCH 142/295] remove uncles --- ethcore/src/engines/tendermint/mod.rs | 3 +++ ethcore/src/snapshot/service.rs | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index bec41d37f..c6c5c1ad4 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -363,6 +363,9 @@ impl Engine for Tendermint { fn params(&self) -> &CommonParams { &self.params } fn builtins(&self) -> &BTreeMap { &self.builtins } + fn maximum_uncle_count(&self) -> usize { 0 } + fn maximum_uncle_age(&self) -> usize { 0 } + /// Additional engine-specific information for the user/developer concerning `header`. fn extra_info(&self, header: &Header) -> BTreeMap { let message = ConsensusMessage::new_proposal(header).expect("Invalid header."); diff --git a/ethcore/src/snapshot/service.rs b/ethcore/src/snapshot/service.rs index a7ab8bcd2..89ee68de0 100644 --- a/ethcore/src/snapshot/service.rs +++ b/ethcore/src/snapshot/service.rs @@ -32,7 +32,6 @@ use engines::Engine; use error::Error; use ids::BlockID; use service::ClientIoMessage; -use spec::Spec; use io::IoChannel; From a296c5e2266e53407fc087bc2fcd2b370356a02f Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 7 Dec 2016 11:38:09 +0100 Subject: [PATCH 143/295] test client message handling --- ethcore/src/client/test_client.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 973772f81..19064264a 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -156,7 +156,7 @@ impl TestBlockChainClient { client.genesis_hash = client.last_hash.read().clone(); client } - + /// Set the transaction receipt result pub fn set_transaction_receipt(&self, id: TransactionID, receipt: LocalizedReceipt) { self.receipts.write().insert(id, receipt); @@ -659,8 +659,8 @@ impl BlockChainClient for TestBlockChainClient { self.miner.import_external_transactions(self, txs); } - fn queue_consensus_message(&self, _packet: Bytes) { - unimplemented!(); + fn queue_consensus_message(&self, message: Bytes) { + self.spec.engine.handle_message(UntrustedRlp::new(&message)).unwrap(); } fn pending_transactions(&self) -> Vec { From 6440ca2f68a1689d9aa89a8b76681a844f0e1abb Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 7 Dec 2016 14:39:37 +0100 Subject: [PATCH 144/295] move stuff around --- ethcore/src/engines/tendermint/mod.rs | 1 + ethcore/src/miner/miner.rs | 9 +++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index c6c5c1ad4..6f7ce2097 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -382,6 +382,7 @@ impl Engine for Tendermint { } 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; diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 806ff6964..892e37f40 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -480,6 +480,9 @@ impl Miner { /// 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 { + let mut sealing_work = self.sealing_work.lock(); + sealing_work.queue.push(block.clone()); + sealing_work.queue.use_last_ref(); if !block.transactions().is_empty() || self.forced_sealing() { if let Ok(sealed) = self.seal_block_internally(block) { if chain.import_sealed_block(sealed).is_ok() { @@ -1015,7 +1018,6 @@ 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) { @@ -1030,11 +1032,6 @@ impl MinerService for Miner { let (block, original_work_hash) = self.prepare_block(chain); if self.seals_internally { trace!(target: "miner", "update_sealing: engine indicates internal sealing"); - { - let mut sealing_work = self.sealing_work.lock(); - sealing_work.queue.push(block.clone()); - sealing_work.queue.use_last_ref(); - } self.seal_and_import_block_internally(chain, block); } else { trace!(target: "miner", "update_sealing: engine does not seal internally, preparing work"); From aa9caac750e80ec7fdcc046a9f48ebcf26a64f9d Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 7 Dec 2016 15:36:20 +0100 Subject: [PATCH 145/295] revert cli default --- parity/cli/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 1c9aa5084..f5e2cbf4a 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -206,7 +206,7 @@ usage! { or |c: &Config| otry!(c.mining).gas_cap.clone(), flag_extra_data: Option = None, or |c: &Config| otry!(c.mining).extra_data.clone().map(Some), - flag_tx_queue_size: usize = 2048usize, + flag_tx_queue_size: usize = 1024usize, or |c: &Config| otry!(c.mining).tx_queue_size.clone(), flag_tx_queue_gas: String = "auto", or |c: &Config| otry!(c.mining).tx_queue_gas.clone(), From 347634ac6c9dfbcd9defb53f434158a6f7853cf9 Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 7 Dec 2016 16:42:58 +0100 Subject: [PATCH 146/295] dont rebroadcast propose --- ethcore/src/engines/tendermint/mod.rs | 9 +++++++++ ethcore/src/engines/tendermint/vote_collector.rs | 7 ++++++- ethcore/src/miner/miner.rs | 8 +++++--- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 6f7ce2097..a1f2fb03a 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -52,6 +52,15 @@ pub enum Step { Commit } +impl Step { + pub fn is_pre(self) -> bool { + match self { + Step::Prevote | Step::Precommit => true, + _ => false, + } + } +} + pub type Height = usize; pub type Round = usize; pub type BlockHash = H256; diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 271ff5f9a..9df2574ec 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -125,7 +125,12 @@ impl VoteCollector { pub fn get_up_to(&self, height: Height) -> Vec { let guard = self.votes.read(); - guard.keys().take_while(|m| m.height <= height).map(|m| ::rlp::encode(m).to_vec()).collect() + guard + .keys() + .filter(|m| m.step.is_pre()) + .take_while(|m| m.height <= height) + .map(|m| ::rlp::encode(m).to_vec()) + .collect() } } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 892e37f40..6e2bbb27b 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -480,9 +480,11 @@ impl Miner { /// 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 { - let mut sealing_work = self.sealing_work.lock(); - sealing_work.queue.push(block.clone()); - sealing_work.queue.use_last_ref(); + { + let mut sealing_work = self.sealing_work.lock(); + sealing_work.queue.push(block.clone()); + sealing_work.queue.use_last_ref(); + } if !block.transactions().is_empty() || self.forced_sealing() { if let Ok(sealed) = self.seal_block_internally(block) { if chain.import_sealed_block(sealed).is_ok() { From 3ebfa1481deb218477639f0d40f7adca6007ad67 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 8 Dec 2016 12:03:34 +0100 Subject: [PATCH 147/295] better proposal block handling --- ethcore/src/client/chain_notify.rs | 4 +- ethcore/src/client/client.rs | 36 +++++++-- ethcore/src/client/test_client.rs | 2 + ethcore/src/client/traits.rs | 5 +- ethcore/src/engines/authority_round.rs | 20 ++--- ethcore/src/engines/basic_authority.rs | 14 ++-- ethcore/src/engines/instant_seal.rs | 13 +-- ethcore/src/engines/mod.rs | 23 +++++- ethcore/src/engines/tendermint/mod.rs | 58 +++++++------- ethcore/src/miner/miner.rs | 68 ++++++++-------- ethcore/src/snapshot/watcher.rs | 4 +- sync/src/api.rs | 4 +- sync/src/chain.rs | 106 +++++++++++++++++-------- sync/src/tests/helpers.rs | 2 +- 14 files changed, 228 insertions(+), 131 deletions(-) diff --git a/ethcore/src/client/chain_notify.rs b/ethcore/src/client/chain_notify.rs index f9c1732e0..628536ff1 100644 --- a/ethcore/src/client/chain_notify.rs +++ b/ethcore/src/client/chain_notify.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . use ipc::IpcConfig; -use util::H256; +use util::{H256, Bytes}; /// Represents what has to be handled by actor listening to chain events #[ipc] @@ -27,6 +27,8 @@ pub trait ChainNotify : Send + Sync { _enacted: Vec, _retracted: Vec, _sealed: Vec, + // Block bytes and total difficulty. + _proposed: Vec, _duration: u64) { // does nothing by default } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index b26b19ad3..a7d65feec 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -391,9 +391,10 @@ impl Client { /// This is triggered by a message coming from a block queue when the block is ready for insertion pub fn import_verified_blocks(&self) -> usize { let max_blocks_to_import = 4; - let (imported_blocks, import_results, invalid_blocks, imported, duration, is_empty) = { + let (imported_blocks, import_results, invalid_blocks, imported, proposed_blocks, duration, is_empty) = { let mut imported_blocks = Vec::with_capacity(max_blocks_to_import); let mut invalid_blocks = HashSet::new(); + let mut proposed_blocks = Vec::with_capacity(max_blocks_to_import); let mut import_results = Vec::with_capacity(max_blocks_to_import); let _import_lock = self.import_lock.lock(); @@ -412,12 +413,17 @@ impl Client { continue; } if let Ok(closed_block) = self.check_and_close_block(&block) { - imported_blocks.push(header.hash()); + if self.engine.is_proposal(&block.header) { + proposed_blocks.push(block.bytes); + invalid_blocks.insert(header.hash()); + } else { + imported_blocks.push(header.hash()); - let route = self.commit_block(closed_block, &header.hash(), &block.bytes); - import_results.push(route); + let route = self.commit_block(closed_block, &header.hash(), &block.bytes); + import_results.push(route); - self.report.write().accrue_block(&block); + self.report.write().accrue_block(&block); + } } else { invalid_blocks.insert(header.hash()); } @@ -431,7 +437,7 @@ impl Client { } let is_empty = self.block_queue.mark_as_good(&imported_blocks); let duration_ns = precise_time_ns() - start; - (imported_blocks, import_results, invalid_blocks, imported, duration_ns, is_empty) + (imported_blocks, import_results, invalid_blocks, imported, proposed_blocks, duration_ns, is_empty) }; { @@ -449,6 +455,7 @@ impl Client { enacted.clone(), retracted.clone(), Vec::new(), + proposed_blocks.clone(), duration, ); }); @@ -1364,6 +1371,20 @@ impl MiningBlockChainClient for Client { &self.factories.vm } + fn broadcast_proposal_block(&self, block: SealedBlock) { + self.notify(|notify| { + notify.new_blocks( + vec![], + vec![], + vec![], + vec![], + vec![], + vec![block.rlp_bytes()], + 0, + ); + }); + } + fn import_sealed_block(&self, block: SealedBlock) -> ImportResult { let h = block.header().hash(); let start = precise_time_ns(); @@ -1388,6 +1409,7 @@ impl MiningBlockChainClient for Client { enacted.clone(), retracted.clone(), vec![h.clone()], + vec![], precise_time_ns() - start, ); }); @@ -1458,4 +1480,4 @@ mod tests { assert!(client.tree_route(&genesis, &new_hash).is_none()); } -} \ No newline at end of file +} diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 19064264a..c8f8cdbf7 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -360,6 +360,8 @@ impl MiningBlockChainClient for TestBlockChainClient { fn import_sealed_block(&self, _block: SealedBlock) -> ImportResult { Ok(H256::default()) } + + fn broadcast_proposal_block(&self, _block: SealedBlock) {} } impl BlockChainClient for TestBlockChainClient { diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 4290831e8..cb5e77499 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -273,6 +273,9 @@ pub trait MiningBlockChainClient: BlockChainClient { /// Returns EvmFactory. fn vm_factory(&self) -> &EvmFactory; + /// Broadcast a block proposal. + fn broadcast_proposal_block(&self, block: SealedBlock); + /// Import sealed block. Skips all verifications. fn import_sealed_block(&self, block: SealedBlock) -> ImportResult; @@ -299,4 +302,4 @@ pub trait ProvingBlockChainClient: BlockChainClient { /// Get code by address hash. fn code_by_hash(&self, account_key: H256, id: BlockID) -> Bytes; -} \ No newline at end of file +} diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 5b61b13fa..807e31c9a 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -25,7 +25,7 @@ use rlp::{UntrustedRlp, Rlp, View, encode}; use account_provider::AccountProvider; use block::*; use spec::CommonParams; -use engines::{Engine, EngineError}; +use engines::{Engine, Seal, EngineError}; use header::Header; use error::{Error, BlockError}; use blockchain::extras::BlockDetails; @@ -218,8 +218,8 @@ 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) -> Option> { - if self.proposed.load(AtomicOrdering::SeqCst) { return None; } + fn generate_seal(&self, block: &ExecutedBlock) -> Seal { + if self.proposed.load(AtomicOrdering::SeqCst) { return Seal::None; } let header = block.header(); let step = self.step(); if self.is_step_proposer(step, header.author()) { @@ -228,7 +228,8 @@ impl Engine for AuthorityRound { if let Ok(signature) = ap.sign(*header.author(), self.password.read().clone(), header.bare_hash()) { trace!(target: "poa", "generate_seal: Issuing a block for step {}.", step); self.proposed.store(true, AtomicOrdering::SeqCst); - return Some(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); + let rlps = vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]; + return Seal::Regular(rlps); } else { warn!(target: "poa", "generate_seal: FAIL: Accounts secret key unavailable."); } @@ -236,7 +237,7 @@ impl Engine for AuthorityRound { warn!(target: "poa", "generate_seal: FAIL: Accounts not provided."); } } - None + Seal::None } /// Check the number of seal fields. @@ -339,6 +340,7 @@ mod tests { use account_provider::AccountProvider; use spec::Spec; use std::time::UNIX_EPOCH; + use engines::Seal; #[test] fn has_valid_metadata() { @@ -408,17 +410,17 @@ mod tests { let b2 = b2.close_and_lock(); engine.set_signer(addr1, "1".into()); - if let Some(seal) = engine.generate_seal(b1.block()) { + if let Seal::Regular(seal) = engine.generate_seal(b1.block()) { assert!(b1.clone().try_seal(engine, seal).is_ok()); // Second proposal is forbidden. - assert!(engine.generate_seal(b1.block()).is_none()); + assert!(engine.generate_seal(b1.block()) == Seal::None); } engine.set_signer(addr2, "2".into()); - if let Some(seal) = engine.generate_seal(b2.block()) { + if let Seal::Regular(seal) = engine.generate_seal(b2.block()) { assert!(b2.clone().try_seal(engine, seal).is_ok()); // Second proposal is forbidden. - assert!(engine.generate_seal(b2.block()).is_none()); + assert!(engine.generate_seal(b2.block()) == Seal::None); } } diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 1070d3a3d..3f99963d9 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -21,7 +21,7 @@ use account_provider::AccountProvider; use block::*; use builtin::Builtin; use spec::CommonParams; -use engines::Engine; +use engines::{Engine, Seal}; use env_info::EnvInfo; use error::{BlockError, Error}; use evm::Schedule; @@ -112,20 +112,20 @@ impl Engine for BasicAuthority { /// /// This operation is synchronous and may (quite reasonably) not be available, in which `false` will /// be returned. - fn generate_seal(&self, block: &ExecutedBlock) -> Option> { + fn generate_seal(&self, block: &ExecutedBlock) -> Seal { if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let message = header.bare_hash(); // account should be pernamently unlocked, otherwise sealing will fail if let Ok(signature) = ap.sign(*block.header().author(), self.password.read().clone(), message) { - return Some(vec![::rlp::encode(&(&*signature as &[u8])).to_vec()]); + return Seal::Regular(vec![::rlp::encode(&(&*signature as &[u8])).to_vec()]); } else { trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable"); } } else { trace!(target: "basicauthority", "generate_seal: FAIL: accounts not provided"); } - None + Seal::None } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { @@ -199,6 +199,7 @@ mod tests { use account_provider::AccountProvider; use header::Header; use spec::Spec; + use engines::Seal; /// Create a new test chain spec with `BasicAuthority` consensus engine. fn new_test_authority() -> Spec { @@ -269,8 +270,9 @@ mod tests { 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()).unwrap(); - assert!(b.try_seal(engine, seal).is_ok()); + if let Seal::Regular(seal) = engine.generate_seal(b.block()) { + assert!(b.try_seal(engine, seal).is_ok()); + } } #[test] diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index 83335fb03..87354f5ff 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -17,12 +17,11 @@ use std::collections::BTreeMap; use util::Address; use builtin::Builtin; -use engines::Engine; +use engines::{Engine, Seal}; use env_info::EnvInfo; use spec::CommonParams; use evm::Schedule; use block::ExecutedBlock; -use util::Bytes; /// An engine which does not provide any consensus mechanism, just seals blocks internally. pub struct InstantSeal { @@ -59,8 +58,8 @@ impl Engine for InstantSeal { fn is_sealer(&self, _author: &Address) -> Option { Some(true) } - fn generate_seal(&self, _block: &ExecutedBlock) -> Option> { - Some(Vec::new()) + fn generate_seal(&self, _block: &ExecutedBlock) -> Seal { + Seal::Regular(Vec::new()) } } @@ -72,6 +71,7 @@ mod tests { use spec::Spec; use header::Header; use block::*; + use engines::Seal; #[test] fn instant_can_seal() { @@ -84,8 +84,9 @@ mod tests { let last_hashes = Arc::new(vec![genesis_header.hash()]); let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); - let seal = engine.generate_seal(b.block()).unwrap(); - assert!(b.try_seal(engine, seal).is_ok()); + if let Seal::Regular(seal) = engine.generate_seal(b.block()) { + assert!(b.try_seal(engine, seal).is_ok()); + } } #[test] diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 17cb46138..31ce746fc 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -49,11 +49,11 @@ use views::HeaderView; #[derive(Debug)] pub enum EngineError { /// Signature does not belong to an authority. - NotAuthorized(H160), + NotAuthorized(Address), /// The same author issued different votes at the same step. - DoubleVote(H160), + DoubleVote(Address), /// The received block is from an incorrect proposer. - NotProposer(Mismatch), + NotProposer(Mismatch
), /// Message was not expected. UnexpectedMessage, /// Seal field has an unexpected size. @@ -75,6 +75,17 @@ impl fmt::Display for EngineError { } } +/// Seal type. +#[derive(Debug, PartialEq, Eq)] +pub enum Seal { + /// Proposal seal; should be broadcasted, but not inserted into blockchain. + Proposal(Vec), + /// Regular block seal; should be part of the blockchain. + Regular(Vec), + /// Engine does generate seal for this block right now. + None, +} + /// 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. pub trait Engine : Sync + Send { @@ -127,7 +138,7 @@ pub trait Engine : Sync + Send { /// /// This operation is synchronous and may (quite reasonably) not be available, in which None will /// be returned. - fn generate_seal(&self, _block: &ExecutedBlock) -> Option> { None } + fn generate_seal(&self, _block: &ExecutedBlock) -> Seal { Seal::None } /// Phase 1 quick block verification. Only does checks that are cheap. `block` (the header's full block) /// may be provided for additional checks. Returns either a null `Ok` or a general error detailing the problem with import. @@ -189,6 +200,10 @@ pub trait Engine : Sync + Send { ethash::is_new_best_block(best_total_difficulty, parent_details, new_header) } + /// Find out if the block is a proposal block and should not be inserted into the DB. + /// Takes a header of a fully verified block. + fn is_proposal(&self, _verified_header: &Header) -> bool { false } + /// Register an account which signs consensus messages. fn set_signer(&self, _address: Address, _password: String) {} diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index a1f2fb03a..efb2f7479 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -33,7 +33,7 @@ use ethkey::{recover, public_to_address}; use account_provider::AccountProvider; use block::*; use spec::CommonParams; -use engines::{Engine, EngineError}; +use engines::{Engine, Seal, EngineError}; use blockchain::extras::BlockDetails; use views::HeaderView; use evm::Schedule; @@ -408,14 +408,14 @@ impl Engine for Tendermint { Some(self.is_authority(address)) } - /// Attempt to seal the block internally using all available signatures. - fn generate_seal(&self, block: &ExecutedBlock) -> Option> { + /// Attempt to seal generate a proposal seal. + fn generate_seal(&self, block: &ExecutedBlock) -> Seal { if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let author = header.author(); // Only proposer can generate seal if None was generated. if self.is_proposer(author).is_err() && self.proposal.read().is_none() { - return None; + return Seal::None; } let height = header.number() as Height; @@ -428,18 +428,18 @@ impl Engine for Tendermint { self.votes.vote(ConsensusMessage::new(signature, height, round, Step::Propose, bh), *author); // Remember proposal for later seal submission. *self.proposal.write() = bh; - Some(vec![ + Seal::Proposal(vec![ ::rlp::encode(&round).to_vec(), ::rlp::encode(&signature).to_vec(), ::rlp::EMPTY_LIST_RLP.to_vec() ]) } else { warn!(target: "poa", "generate_seal: FAIL: accounts secret key unavailable"); - None + Seal::None } } else { warn!(target: "poa", "generate_seal: FAIL: accounts not provided"); - None + Seal::None } } @@ -526,11 +526,6 @@ impl Engine for Tendermint { }))); } try!(self.is_round_proposer(proposal.height, proposal.round, &proposer)); - if self.is_round(&proposal) { - debug!(target: "poa", "Received a new proposal for height {}, round {} from {}.", proposal.height, proposal.round, proposer); - *self.proposal.write() = proposal.block_hash.clone(); - self.votes.vote(proposal, proposer); - } } Ok(()) } @@ -547,17 +542,6 @@ impl Engine for Tendermint { try!(Err(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(max_gas), found: header.gas_limit().clone() }))); } - // Commit is longer than empty signature list. - let parent_signature_len = parent.seal()[2].len(); - if parent_signature_len <= 1 { - try!(Err(EngineError::BadSealFieldSize(OutOfBounds { - // One signature. - min: Some(69), - max: None, - found: parent_signature_len - }))); - } - Ok(()) } @@ -599,6 +583,22 @@ impl Engine for Tendermint { } } + fn is_proposal(&self, header: &Header) -> bool { + let signatures_len = header.seal()[2].len(); + // Signatures have to be an empty list rlp. + if signatures_len != 1 { + return false; + } + let proposal = ConsensusMessage::new_proposal(header).expect("block went through full verification; this Engine verifies new_proposal creation; qed"); + let proposer = proposal.verify().expect("block went through full verification; this Engine tries verify; qed"); + debug!(target: "poa", "Received a new proposal for height {}, round {} from {}.", proposal.height, proposal.round, proposer); + if self.is_round(&proposal) { + *self.proposal.write() = proposal.block_hash.clone(); + } + self.votes.vote(proposal, proposer); + true + } + fn register_message_channel(&self, message_channel: IoChannel) { trace!(target: "poa", "register_message_channel: Register the IoChannel."); *self.message_channel.lock() = Some(message_channel); @@ -624,7 +624,7 @@ mod tests { use io::IoService; use service::ClientIoMessage; use spec::Spec; - use engines::{Engine, EngineError}; + use engines::{Engine, EngineError, Seal}; use super::*; use super::message::*; @@ -644,8 +644,11 @@ mod tests { let last_hashes = Arc::new(vec![genesis_header.hash()]); let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db.boxed_clone(), &genesis_header, last_hashes, proposer, (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); - let seal = spec.engine.generate_seal(b.block()).unwrap(); - (b, seal) + if let Seal::Proposal(seal) = spec.engine.generate_seal(b.block()) { + (b, seal) + } else { + panic!() + } } fn vote(engine: &Arc, signer: F, height: usize, round: usize, step: Step, block_hash: Option) where F: FnOnce(H256) -> Result { @@ -737,6 +740,7 @@ mod tests { } #[test] + #[ignore] fn allows_correct_proposer() { let (spec, tap) = setup(); let engine = spec.engine; @@ -825,7 +829,7 @@ mod tests { #[test] fn step_transitioning() { - ::env_logger::init().unwrap(); + //::env_logger::init().unwrap(); let (spec, tap) = setup(); let engine = spec.engine.clone(); let mut db_result = get_temp_state_db(); diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 6e2bbb27b..2b2661acf 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -26,12 +26,12 @@ use state::{State, CleanupMode}; use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics}; use client::TransactionImportResult; use executive::contract_address; -use block::{ClosedBlock, SealedBlock, IsBlock, Block}; +use block::{ClosedBlock, IsBlock, Block}; use error::*; use transaction::{Action, SignedTransaction}; use receipt::{Receipt, RichReceipt}; use spec::Spec; -use engines::Engine; +use engines::{Engine, Seal}; use miner::{MinerService, MinerStatus, TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin}; use miner::banning_queue::{BanningTransactionQueue, Threshold}; use miner::work_notify::WorkPoster; @@ -461,39 +461,41 @@ impl Miner { } } - /// 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: attempting internal seal."); - let s = self.engine.generate_seal(block.block()); - if let Some(seal) = s { - trace!(target: "miner", "seal_block_internally: managed internal seal. importing..."); - block.lock().try_seal(&*self.engine, seal).or_else(|(e, _)| { - warn!("prepare_sealing: ERROR: try_seal failed when given internally generated seal: {}", e); - Err(None) - }) - } else { - trace!(target: "miner", "seal_block_internally: unable to generate seal internally"); - Err(Some(block)) - } - } - - /// Uses Engine to seal the block internally and then imports it to chain. + /// Attempts to perform internal sealing (one that does not require work) and handles the result depending on the type of Seal. fn seal_and_import_block_internally(&self, chain: &MiningBlockChainClient, block: ClosedBlock) -> bool { - { - let mut sealing_work = self.sealing_work.lock(); - sealing_work.queue.push(block.clone()); - sealing_work.queue.use_last_ref(); - } if !block.transactions().is_empty() || self.forced_sealing() { - if let Ok(sealed) = self.seal_block_internally(block) { - if chain.import_sealed_block(sealed).is_ok() { - trace!(target: "miner", "import_block_internally: imported internally sealed block"); - return true - } + trace!(target: "miner", "seal_block_internally: attempting internal seal."); + match self.engine.generate_seal(block.block()) { + // Save proposal for later seal submission and broadcast it. + Seal::Proposal(seal) => { + trace!(target: "miner", "Received a Proposal seal."); + let mut sealing_work = self.sealing_work.lock(); + sealing_work.queue.push(block.clone()); + sealing_work.queue.use_last_ref(); + block + .lock() + .seal(&*self.engine, seal) + .map(|sealed| { chain.broadcast_proposal_block(sealed); true }) + .unwrap_or_else(|e| { + warn!("ERROR: seal failed when given internally generated seal: {}", e); + false + }) + }, + // Directly import a regular seal. + Seal::Regular(seal) => + block + .lock() + .seal(&*self.engine, seal) + .map(|sealed| chain.import_sealed_block(sealed).is_ok()) + .unwrap_or_else(|e| { + warn!("ERROR: seal failed when given internally generated seal: {}", e); + false + }), + Seal::None => false, } + } else { + false } - false } /// Prepares work which has to be done to seal. @@ -1034,7 +1036,9 @@ impl MinerService for Miner { let (block, original_work_hash) = self.prepare_block(chain); if self.seals_internally { trace!(target: "miner", "update_sealing: engine indicates internal sealing"); - self.seal_and_import_block_internally(chain, block); + if self.seal_and_import_block_internally(chain, block) { + trace!(target: "miner", "update_sealing: imported internally sealed block"); + } } else { trace!(target: "miner", "update_sealing: engine does not seal internally, preparing work"); self.prepare_work(block, original_work_hash); diff --git a/ethcore/src/snapshot/watcher.rs b/ethcore/src/snapshot/watcher.rs index 43439e437..c73e07455 100644 --- a/ethcore/src/snapshot/watcher.rs +++ b/ethcore/src/snapshot/watcher.rs @@ -23,7 +23,7 @@ use service::ClientIoMessage; use views::HeaderView; use io::IoChannel; -use util::hash::H256; +use util::{H256, Bytes}; use std::sync::Arc; @@ -107,6 +107,7 @@ impl ChainNotify for Watcher { _: Vec, _: Vec, _: Vec, + _: Vec, _duration: u64) { if self.oracle.is_major_importing() { return } @@ -174,6 +175,7 @@ mod tests { vec![], vec![], vec![], + vec![], 0, ); } diff --git a/sync/src/api.rs b/sync/src/api.rs index 79e5806e1..233f78087 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -227,6 +227,7 @@ impl ChainNotify for EthSync { enacted: Vec, retracted: Vec, sealed: Vec, + proposed: Vec, _duration: u64) { self.network.with_context(self.subprotocol_name, |context| { @@ -237,7 +238,8 @@ impl ChainNotify for EthSync { &invalid, &enacted, &retracted, - &sealed); + &sealed, + &proposed); }); } diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 319a7f611..ee3a4da3e 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -249,6 +249,15 @@ enum PeerAsking { SnapshotData, } +/// Peer type semantic boolean. +#[derive(Clone)] +enum PeerStatus { + /// Have the same latest_hash as we. + Current, + /// Is lagging in blocks. + Lagging +} + #[derive(PartialEq, Eq, Debug, Clone, Copy)] /// Block downloader channel. enum BlockSet { @@ -1797,32 +1806,42 @@ impl ChainSync { } } + /// creates rlp from block bytes and total difficulty + fn create_block_rlp(bytes: &Bytes, total_difficulty: U256) -> Bytes { + let mut rlp_stream = RlpStream::new_list(2); + rlp_stream.append_raw(bytes, 1); + rlp_stream.append(&total_difficulty); + rlp_stream.out() + } + /// creates latest block rlp for the given client fn create_latest_block_rlp(chain: &BlockChainClient) -> Bytes { - let mut rlp_stream = RlpStream::new_list(2); - rlp_stream.append_raw(&chain.block(BlockID::Hash(chain.chain_info().best_block_hash)).expect("Best block always exists"), 1); - rlp_stream.append(&chain.chain_info().total_difficulty); - rlp_stream.out() + ChainSync::create_block_rlp( + &chain.block(BlockID::Hash(chain.chain_info().best_block_hash)).expect("Best block always exists"), + chain.chain_info().total_difficulty + ) } /// creates latest block rlp for the given client fn create_new_block_rlp(chain: &BlockChainClient, hash: &H256) -> Bytes { - let mut rlp_stream = RlpStream::new_list(2); - rlp_stream.append_raw(&chain.block(BlockID::Hash(hash.clone())).expect("Block has just been sealed; qed"), 1); - rlp_stream.append(&chain.block_total_difficulty(BlockID::Hash(hash.clone())).expect("Block has just been sealed; qed.")); - rlp_stream.out() + ChainSync::create_block_rlp( + &chain.block(BlockID::Hash(hash.clone())).expect("Block has just been sealed; qed"), + chain.block_total_difficulty(BlockID::Hash(hash.clone())).expect("Block has just been sealed; qed.") + ) } - /// returns peer ids that have less blocks than our chain - fn get_lagging_peers(&mut self, chain_info: &BlockChainInfo, io: &SyncIo) -> Vec { + + /// Returns peer ids that either have less blocks than our (Lagging) chain or are Current. + fn get_peers(&mut self, chain_info: &BlockChainInfo, io: &SyncIo, peer_status: PeerStatus) -> Vec { let latest_hash = chain_info.best_block_hash; self.peers.iter_mut().filter_map(|(&id, ref mut peer_info)| match io.chain().block_status(BlockID::Hash(peer_info.latest_hash.clone())) { BlockStatus::InChain => { - if peer_info.latest_hash != latest_hash { - Some(id) - } else { - None + match (peer_info.latest_hash == latest_hash, peer_status.clone()) { + (false, PeerStatus::Lagging) => Some(id), + (true, PeerStatus::Lagging) => None, + (false, PeerStatus::Current) => None, + (true, PeerStatus::Current) => Some(id), } }, _ => None @@ -1830,7 +1849,7 @@ impl ChainSync { .collect::>() } - fn select_random_lagging_peers(&mut self, peers: &[PeerId]) -> Vec { + fn select_random_peers(&mut self, peers: &[PeerId]) -> Vec { use rand::Rng; // take sqrt(x) peers let mut peers = peers.to_vec(); @@ -1842,16 +1861,16 @@ impl ChainSync { peers } - /// propagates latest block to lagging peers - fn propagate_blocks(&mut self, chain_info: &BlockChainInfo, io: &mut SyncIo, sealed: &[H256], peers: &[PeerId]) -> usize { + /// propagates latest block to a set of peers + fn propagate_blocks(&mut self, chain_info: &BlockChainInfo, io: &mut SyncIo, blocks: &[H256], peers: &[PeerId]) -> usize { trace!(target: "sync", "Sending NewBlocks to {:?}", peers); let mut sent = 0; for peer_id in peers { - if sealed.is_empty() { + if blocks.is_empty() { let rlp = ChainSync::create_latest_block_rlp(io.chain()); self.send_packet(io, *peer_id, NEW_BLOCK_PACKET, rlp); } else { - for h in sealed { + for h in blocks { let rlp = ChainSync::create_new_block_rlp(io.chain(), h); self.send_packet(io, *peer_id, NEW_BLOCK_PACKET, rlp); } @@ -1969,10 +1988,10 @@ impl ChainSync { fn propagate_latest_blocks(&mut self, io: &mut SyncIo, sealed: &[H256]) { let chain_info = io.chain().chain_info(); if (((chain_info.best_block_number as i64) - (self.last_sent_block_number as i64)).abs() as BlockNumber) < MAX_PEER_LAG_PROPAGATION { - let mut peers = self.get_lagging_peers(&chain_info, io); + let mut peers = self.get_peers(&chain_info, io, PeerStatus::Lagging); if sealed.is_empty() { let hashes = self.propagate_new_hashes(&chain_info, io, &peers); - peers = self.select_random_lagging_peers(&peers); + peers = self.select_random_peers(&peers); let blocks = self.propagate_blocks(&chain_info, io, sealed, &peers); if blocks != 0 || hashes != 0 { trace!(target: "sync", "Sent latest {} blocks and {} hashes to peers.", blocks, hashes); @@ -1987,6 +2006,22 @@ impl ChainSync { self.last_sent_block_number = chain_info.best_block_number; } + /// Distribute valid proposed blocks to subset of current peers. + fn propagate_proposed_blocks(&mut self, io: &mut SyncIo, proposed: &[Bytes]) { + let chain_info = io.chain().chain_info(); + let mut peers = self.get_peers(&chain_info, io, PeerStatus::Current); + peers = self.select_random_peers(&peers); + for block in proposed { + let rlp = ChainSync::create_block_rlp( + block, + chain_info.total_difficulty + ); + for peer_id in &peers { + self.send_packet(io, *peer_id, NEW_BLOCK_PACKET, rlp.clone()); + } + } + } + /// Maintain other peers. Send out any new blocks and transactions pub fn maintain_sync(&mut self, io: &mut SyncIo) { self.maybe_start_snapshot_sync(io); @@ -1994,9 +2029,10 @@ 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]) { + pub fn chain_new_blocks(&mut self, io: &mut SyncIo, _imported: &[H256], invalid: &[H256], _enacted: &[H256], _retracted: &[H256], sealed: &[H256], proposed: &[Bytes]) { if io.is_chain_queue_empty() { self.propagate_latest_blocks(io, sealed); + self.propagate_proposed_blocks(io, proposed); } if !invalid.is_empty() { trace!(target: "sync", "Bad blocks in the queue, restarting"); @@ -2032,7 +2068,7 @@ mod tests { use rlp::{Rlp, RlpStream, UntrustedRlp, View, Stream}; use super::*; use ::SyncConfig; - use super::{PeerInfo, PeerAsking}; + use super::{PeerInfo, PeerAsking, PeerStatus}; use ethcore::views::BlockView; use ethcore::header::*; use ethcore::client::*; @@ -2250,7 +2286,7 @@ mod tests { let ss = TestSnapshotService::new(); let io = TestIo::new(&mut client, &ss, &mut queue, None); - let lagging_peers = sync.get_lagging_peers(&chain_info, &io); + let lagging_peers = sync.get_peers(&chain_info, &io, PeerStatus::Lagging); assert_eq!(1, lagging_peers.len()) } @@ -2282,7 +2318,7 @@ mod tests { let ss = TestSnapshotService::new(); let mut io = TestIo::new(&mut client, &ss, &mut queue, None); - let peers = sync.get_lagging_peers(&chain_info, &io); + let peers = sync.get_peers(&chain_info, &io, PeerStatus::Lagging); let peer_count = sync.propagate_new_hashes(&chain_info, &mut io, &peers); // 1 message should be send @@ -2302,7 +2338,7 @@ mod tests { let chain_info = client.chain_info(); let ss = TestSnapshotService::new(); let mut io = TestIo::new(&mut client, &ss, &mut queue, None); - let peers = sync.get_lagging_peers(&chain_info, &io); + let peers = sync.get_peers(&chain_info, &io, PeerStatus::Lagging); let peer_count = sync.propagate_blocks(&chain_info, &mut io, &[], &peers); // 1 message should be send @@ -2323,7 +2359,7 @@ mod tests { let chain_info = client.chain_info(); let ss = TestSnapshotService::new(); let mut io = TestIo::new(&mut client, &ss, &mut queue, None); - let peers = sync.get_lagging_peers(&chain_info, &io); + let peers = sync.get_peers(&chain_info, &io, PeerStatus::Lagging); let peer_count = sync.propagate_blocks(&chain_info, &mut io, &[hash.clone()], &peers); // 1 message should be send @@ -2347,7 +2383,7 @@ mod tests { // Try to propagate same transactions for the second time let peer_count2 = sync.propagate_new_transactions(&mut io); // Even after new block transactions should not be propagated twice - sync.chain_new_blocks(&mut io, &[], &[], &[], &[], &[]); + sync.chain_new_blocks(&mut io, &[], &[], &[], &[], &[], &[]); // Try to propagate same transactions for the third time let peer_count3 = sync.propagate_new_transactions(&mut io); @@ -2373,7 +2409,7 @@ mod tests { let peer_count = sync.propagate_new_transactions(&mut io); io.chain.insert_transaction_to_queue(); // New block import should trigger propagation. - sync.chain_new_blocks(&mut io, &[], &[], &[], &[], &[]); + sync.chain_new_blocks(&mut io, &[], &[], &[], &[], &[], &[]); // 2 message should be send assert_eq!(2, io.queue.len()); @@ -2534,7 +2570,7 @@ mod tests { let ss = TestSnapshotService::new(); let mut io = TestIo::new(&mut client, &ss, &mut queue, None); - let peers = sync.get_lagging_peers(&chain_info, &io); + let peers = sync.get_peers(&chain_info, &io, PeerStatus::Lagging); sync.propagate_new_hashes(&chain_info, &mut io, &peers); let data = &io.queue[0].data.clone(); @@ -2554,7 +2590,7 @@ mod tests { let ss = TestSnapshotService::new(); let mut io = TestIo::new(&mut client, &ss, &mut queue, None); - let peers = sync.get_lagging_peers(&chain_info, &io); + let peers = sync.get_peers(&chain_info, &io, PeerStatus::Lagging); sync.propagate_blocks(&chain_info, &mut io, &[], &peers); let data = &io.queue[0].data.clone(); @@ -2589,7 +2625,7 @@ mod tests { let ss = TestSnapshotService::new(); let mut io = TestIo::new(&mut client, &ss, &mut queue, None); io.chain.miner.chain_new_blocks(io.chain, &[], &[], &[], &good_blocks); - sync.chain_new_blocks(&mut io, &[], &[], &[], &good_blocks, &[]); + sync.chain_new_blocks(&mut io, &[], &[], &[], &good_blocks, &[], &[]); assert_eq!(io.chain.miner.status().transactions_in_future_queue, 0); assert_eq!(io.chain.miner.status().transactions_in_pending_queue, 1); } @@ -2604,7 +2640,7 @@ mod tests { let ss = TestSnapshotService::new(); let mut io = TestIo::new(&mut client, &ss, &mut queue, None); io.chain.miner.chain_new_blocks(io.chain, &[], &[], &good_blocks, &retracted_blocks); - sync.chain_new_blocks(&mut io, &[], &[], &good_blocks, &retracted_blocks, &[]); + sync.chain_new_blocks(&mut io, &[], &[], &good_blocks, &retracted_blocks, &[], &[]); } // then @@ -2630,10 +2666,10 @@ mod tests { let mut io = TestIo::new(&mut client, &ss, &mut queue, None); // when - sync.chain_new_blocks(&mut io, &[], &[], &[], &good_blocks, &[]); + sync.chain_new_blocks(&mut io, &[], &[], &[], &good_blocks, &[], &[]); assert_eq!(io.chain.miner.status().transactions_in_future_queue, 0); assert_eq!(io.chain.miner.status().transactions_in_pending_queue, 0); - sync.chain_new_blocks(&mut io, &[], &[], &good_blocks, &retracted_blocks, &[]); + sync.chain_new_blocks(&mut io, &[], &[], &good_blocks, &retracted_blocks, &[], &[]); // then let status = io.chain.miner.status(); diff --git a/sync/src/tests/helpers.rs b/sync/src/tests/helpers.rs index 10c1277a6..f3254fbad 100644 --- a/sync/src/tests/helpers.rs +++ b/sync/src/tests/helpers.rs @@ -244,6 +244,6 @@ impl TestNet { 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), &[], &[], &[], &[], &[]); + peer.sync.write().chain_new_blocks(&mut TestIo::new(&mut peer.chain, &peer.snapshot_service, &mut peer.queue, None), &[], &[], &[], &[], &[], &[]); } } From dca752e9bb2c3dbb1b425b73350491d8197d03ad Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 8 Dec 2016 20:09:30 +0100 Subject: [PATCH 148/295] docs, tweaks --- ethcore/src/engines/tendermint/mod.rs | 45 ++++++++++---------- ethcore/src/engines/tendermint/transition.rs | 1 - ethcore/src/miner/miner.rs | 8 ++-- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index efb2f7479..421ee849a 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -14,7 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//! Tendermint BFT consensus engine with round robin proof-of-authority. +/// Tendermint BFT consensus engine with round robin proof-of-authority. +/// At each blockchain `Height` there can be multiple `Round`s of voting. +/// Block is issued when there is enough `Precommit` votes collected on a particular block at the end of a `Round`. +/// Signatures always sign `Height`, `Round`, `Step` and `BlockHash` which is a block hash without seal. +/// First a block with `Seal::Proposal` is issued by the designated proposer. +/// Once enough votes have been gathered the proposer issues that block in the `Commit` step. mod message; mod transition; @@ -191,10 +196,11 @@ impl Tendermint { } } - fn to_height(&self, height: Height) { - debug!(target: "poa", "Transitioning to height {}.", height); + fn to_next_height(&self, height: Height) { + let new_height = height + 1; + debug!(target: "poa", "Received a Commit, transitioning to height {}.", new_height); self.last_lock.store(0, AtomicOrdering::SeqCst); - self.height.store(height, AtomicOrdering::SeqCst); + self.height.store(new_height, AtomicOrdering::SeqCst); self.round.store(0, AtomicOrdering::SeqCst); *self.lock_change.write() = None; } @@ -232,18 +238,19 @@ impl Tendermint { let height = self.height.load(AtomicOrdering::SeqCst); if let Some(block_hash) = *self.proposal.read() { // Generate seal and remove old votes. - if let Some(seal) = self.votes.seal_signatures(height, round, block_hash) { - trace!(target: "poa", "to_step: Collected seal: {:?}", seal); - if self.is_proposer(&*self.authority.read()).is_ok() { + if self.is_proposer(&*self.authority.read()).is_ok() { + if let Some(seal) = self.votes.seal_signatures(height, round, block_hash) { + trace!(target: "poa", "to_step: Collected seal: {:?}", seal); let seal = vec![ ::rlp::encode(&round).to_vec(), ::rlp::encode(&seal.proposal).to_vec(), ::rlp::encode(&seal.votes).to_vec() ]; self.submit_seal(block_hash, seal); + self.to_next_height(height); + } else { + warn!(target: "poa", "Proposal was not found!"); } - } else { - warn!(target: "poa", "Proposal was not found!"); } } }, @@ -289,10 +296,6 @@ impl Tendermint { self.round.fetch_add(n, AtomicOrdering::SeqCst); } - fn new_height(&self) { - self.to_height(self.height.load(AtomicOrdering::SeqCst) + 1); - } - fn should_unlock(&self, lock_change_round: Round) -> bool { self.last_lock.load(AtomicOrdering::SeqCst) < lock_change_round && lock_change_round < self.round.load(AtomicOrdering::SeqCst) @@ -414,7 +417,7 @@ impl Engine for Tendermint { let header = block.header(); let author = header.author(); // Only proposer can generate seal if None was generated. - if self.is_proposer(author).is_err() && self.proposal.read().is_none() { + if self.is_proposer(author).is_err() || self.proposal.read().is_some() { return Seal::None; } @@ -428,6 +431,7 @@ impl Engine for Tendermint { self.votes.vote(ConsensusMessage::new(signature, height, round, Step::Propose, bh), *author); // Remember proposal for later seal submission. *self.proposal.write() = bh; + assert!(self.is_round_proposer(height, round, author).is_ok()); Seal::Proposal(vec![ ::rlp::encode(&round).to_vec(), ::rlp::encode(&signature).to_vec(), @@ -509,13 +513,8 @@ impl Engine for Tendermint { } } - if self.is_above_threshold(signature_count) { - // Skip ahead if block is from the future. - if proposal.height > self.height.load(AtomicOrdering::SeqCst) { - self.to_height(proposal.height); - } // Check if its a proposal if there is not enough precommits. - } else { + if !self.is_above_threshold(signature_count) { let signatures_len = signatures_field.len(); // Proposal has to have an empty signature list. if signatures_len != 1 { @@ -563,9 +562,9 @@ impl Engine for Tendermint { } fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { - trace!(target: "poa", "new_header: {}, best_header: {}", new_header.number(), best_header.number()); let new_number = new_header.number(); let best_number = best_header.number(); + trace!(target: "poa", "new_header: {}, best_header: {}", new_number, best_number); if new_number != best_number { new_number > best_number } else { @@ -586,10 +585,12 @@ impl Engine for Tendermint { fn is_proposal(&self, header: &Header) -> bool { let signatures_len = header.seal()[2].len(); // Signatures have to be an empty list rlp. + let proposal = ConsensusMessage::new_proposal(header).expect("block went through full verification; this Engine verifies new_proposal creation; qed"); if signatures_len != 1 { + // New Commit received, skip to next height. + self.to_next_height(proposal.height); return false; } - let proposal = ConsensusMessage::new_proposal(header).expect("block went through full verification; this Engine verifies new_proposal creation; qed"); let proposer = proposal.verify().expect("block went through full verification; this Engine tries verify; qed"); debug!(target: "poa", "Received a new proposal for height {}, round {} from {}.", proposal.height, proposal.round, proposer); if self.is_round(&proposal) { diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index 22d4d9498..35f56ac87 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -106,7 +106,6 @@ impl IoHandler for TransitionHandler { Step::Commit => { trace!(target: "poa", "timeout: Commit timeout."); set_timeout(io, engine.our_params.timeouts.propose); - engine.new_height(); Some(Step::Propose) }, }; diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 2b2661acf..72686ab40 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -469,9 +469,11 @@ impl Miner { // Save proposal for later seal submission and broadcast it. Seal::Proposal(seal) => { trace!(target: "miner", "Received a Proposal seal."); - let mut sealing_work = self.sealing_work.lock(); - sealing_work.queue.push(block.clone()); - sealing_work.queue.use_last_ref(); + { + let mut sealing_work = self.sealing_work.lock(); + sealing_work.queue.push(block.clone()); + sealing_work.queue.use_last_ref(); + } block .lock() .seal(&*self.engine, seal) From 9ecb07434f07e30ff166412e4ab01f6a32410eb2 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 8 Dec 2016 21:13:32 +0100 Subject: [PATCH 149/295] fix informant --- ethcore/src/client/client.rs | 2 +- parity/informant.rs | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index a7d65feec..93c666dbf 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -414,8 +414,8 @@ impl Client { } if let Ok(closed_block) = self.check_and_close_block(&block) { if self.engine.is_proposal(&block.header) { + self.block_queue.mark_as_good(&[header.hash()]); proposed_blocks.push(block.bytes); - invalid_blocks.insert(header.hash()); } else { imported_blocks.push(header.hash()); diff --git a/parity/informant.rs b/parity/informant.rs index d3e3c8a20..6b6f51d7b 100644 --- a/parity/informant.rs +++ b/parity/informant.rs @@ -23,7 +23,7 @@ use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use std::time::{Instant, Duration}; use isatty::{stdout_isatty}; use ethsync::{SyncProvider, ManageNetwork}; -use util::{Uint, RwLock, Mutex, H256, Colour}; +use util::{Uint, RwLock, Mutex, H256, Colour, Bytes}; use ethcore::client::*; use ethcore::views::BlockView; use ethcore::snapshot::service::Service as SnapshotService; @@ -176,14 +176,13 @@ impl Informant { } impl ChainNotify for Informant { - fn new_blocks(&self, imported: Vec, _invalid: Vec, _enacted: Vec, _retracted: Vec, _sealed: Vec, duration: u64) { + fn new_blocks(&self, imported: Vec, _invalid: Vec, _enacted: Vec, _retracted: Vec, _sealed: Vec, _proposed: Vec, duration: u64) { let mut last_import = self.last_import.lock(); let sync_state = self.sync.as_ref().map(|s| s.status().state); let importing = is_major_importing(sync_state, self.client.queue_info()); - let ripe = Instant::now() > *last_import + Duration::from_secs(1) && !importing; let txs_imported = imported.iter() - .take(imported.len() - if ripe {1} else {0}) + .take(imported.len().saturating_sub(if ripe { 1 } else { 0 })) .filter_map(|h| self.client.block(BlockID::Hash(*h))) .map(|b| BlockView::new(&b).transactions_count()) .sum(); From 79ef64349c1c3f123a1d9f1d323a5d60b69190df Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 8 Dec 2016 21:27:49 +0100 Subject: [PATCH 150/295] remove assert --- ethcore/src/engines/tendermint/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 421ee849a..bbcf24750 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -431,7 +431,6 @@ impl Engine for Tendermint { self.votes.vote(ConsensusMessage::new(signature, height, round, Step::Propose, bh), *author); // Remember proposal for later seal submission. *self.proposal.write() = bh; - assert!(self.is_round_proposer(height, round, author).is_ok()); Seal::Proposal(vec![ ::rlp::encode(&round).to_vec(), ::rlp::encode(&signature).to_vec(), From 74770e47738b63847ee0a394ac35b4a7deaa6dda Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 8 Dec 2016 21:49:55 +0100 Subject: [PATCH 151/295] better docstrings --- ethcore/src/client/chain_notify.rs | 2 +- ethcore/src/miner/miner.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ethcore/src/client/chain_notify.rs b/ethcore/src/client/chain_notify.rs index 628536ff1..f082ab010 100644 --- a/ethcore/src/client/chain_notify.rs +++ b/ethcore/src/client/chain_notify.rs @@ -27,7 +27,7 @@ pub trait ChainNotify : Send + Sync { _enacted: Vec, _retracted: Vec, _sealed: Vec, - // Block bytes and total difficulty. + // Block bytes. _proposed: Vec, _duration: u64) { // does nothing by default diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 72686ab40..f5072d04b 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -483,7 +483,7 @@ impl Miner { false }) }, - // Directly import a regular seal. + // Directly import a regular sealed block. Seal::Regular(seal) => block .lock() From 7c4224146697645d2346ddda3b7f0ea228fac3c7 Mon Sep 17 00:00:00 2001 From: keorn Date: Thu, 8 Dec 2016 22:00:26 +0100 Subject: [PATCH 152/295] remove merge code --- ethcore/src/miner/transaction_queue.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 41338ed2e..cd2d3ba47 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -1040,16 +1040,6 @@ impl TransactionQueue { let nonce = tx.nonce(); let hash = tx.hash(); - { - // Rough size sanity check - let gas = &tx.transaction.gas; - if U256::from(tx.transaction.data.len()) > *gas { - // Droping transaction - trace!(target: "txqueue", "Dropping oversized transaction: {:?} (gas: {} < size {})", hash, gas, tx.transaction.data.len()); - return Err(TransactionError::LimitReached); - } - } - // The transaction might be old, let's check that. // This has to be the first test, otherwise calculating // nonce height would result in overflow. From 600a7e5ccc84609f8e088d9eb33624cff6ae8bf6 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Tue, 6 Dec 2016 15:09:11 +0100 Subject: [PATCH 153/295] make SMS verification contract general purpose --- js/src/contracts/contracts.js | 7 +++++-- js/src/contracts/{sms-verification.js => verification.js} | 0 js/src/modals/SMSVerification/store.js | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) rename js/src/contracts/{sms-verification.js => verification.js} (100%) diff --git a/js/src/contracts/contracts.js b/js/src/contracts/contracts.js index f61a63690..f30f67efb 100644 --- a/js/src/contracts/contracts.js +++ b/js/src/contracts/contracts.js @@ -19,7 +19,7 @@ import Registry from './registry'; import SignatureReg from './signaturereg'; import TokenReg from './tokenreg'; import GithubHint from './githubhint'; -import * as smsVerification from './sms-verification'; +import * as verification from './verification'; import BadgeReg from './badgereg'; let instance = null; @@ -58,7 +58,10 @@ export default class Contracts { } get smsVerification () { - return smsVerification; + return verification; + } + get emailVerification () { + return verification; } static create (api) { diff --git a/js/src/contracts/sms-verification.js b/js/src/contracts/verification.js similarity index 100% rename from js/src/contracts/sms-verification.js rename to js/src/contracts/verification.js diff --git a/js/src/modals/SMSVerification/store.js b/js/src/modals/SMSVerification/store.js index 49b91fa70..279329ca5 100644 --- a/js/src/modals/SMSVerification/store.js +++ b/js/src/modals/SMSVerification/store.js @@ -20,7 +20,7 @@ import { sha3 } from '~/api/util/sha3'; import Contracts from '~/contracts'; -import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/sms-verification'; +import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/verification'; import { postToServer } from '../../3rdparty/sms-verification'; import checkIfTxFailed from '../../util/check-if-tx-failed'; import waitForConfirmations from '../../util/wait-for-block-confirmations'; From b5b529f8c2442bbeb6675225f46141dd7a583085 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Tue, 6 Dec 2016 15:09:54 +0100 Subject: [PATCH 154/295] modals/SMSVerification -> modals/Verification --- .../modals/{SMSVerification => Verification}/Done/done.css | 0 .../modals/{SMSVerification => Verification}/Done/done.js | 0 .../modals/{SMSVerification => Verification}/Done/index.js | 0 .../GatherData/gatherData.css | 0 .../GatherData/gatherData.js | 0 .../{SMSVerification => Verification}/GatherData/index.js | 0 .../{SMSVerification => Verification}/QueryCode/index.js | 0 .../QueryCode/queryCode.js | 0 .../SendConfirmation/index.js | 0 .../SendConfirmation/sendConfirmation.css | 0 .../SendConfirmation/sendConfirmation.js | 0 .../{SMSVerification => Verification}/SendRequest/index.js | 0 .../SendRequest/sendRequest.css | 0 .../SendRequest/sendRequest.js | 0 js/src/modals/{SMSVerification => Verification}/index.js | 2 +- js/src/modals/{SMSVerification => Verification}/store.js | 0 .../SMSVerification.js => Verification/verification.js} | 4 ++-- js/src/modals/index.js | 4 ++-- js/src/views/Account/account.js | 6 +++--- 19 files changed, 8 insertions(+), 8 deletions(-) rename js/src/modals/{SMSVerification => Verification}/Done/done.css (100%) rename js/src/modals/{SMSVerification => Verification}/Done/done.js (100%) rename js/src/modals/{SMSVerification => Verification}/Done/index.js (100%) rename js/src/modals/{SMSVerification => Verification}/GatherData/gatherData.css (100%) rename js/src/modals/{SMSVerification => Verification}/GatherData/gatherData.js (100%) rename js/src/modals/{SMSVerification => Verification}/GatherData/index.js (100%) rename js/src/modals/{SMSVerification => Verification}/QueryCode/index.js (100%) rename js/src/modals/{SMSVerification => Verification}/QueryCode/queryCode.js (100%) rename js/src/modals/{SMSVerification => Verification}/SendConfirmation/index.js (100%) rename js/src/modals/{SMSVerification => Verification}/SendConfirmation/sendConfirmation.css (100%) rename js/src/modals/{SMSVerification => Verification}/SendConfirmation/sendConfirmation.js (100%) rename js/src/modals/{SMSVerification => Verification}/SendRequest/index.js (100%) rename js/src/modals/{SMSVerification => Verification}/SendRequest/sendRequest.css (100%) rename js/src/modals/{SMSVerification => Verification}/SendRequest/sendRequest.js (100%) rename js/src/modals/{SMSVerification => Verification}/index.js (94%) rename js/src/modals/{SMSVerification => Verification}/store.js (100%) rename js/src/modals/{SMSVerification/SMSVerification.js => Verification/verification.js} (97%) diff --git a/js/src/modals/SMSVerification/Done/done.css b/js/src/modals/Verification/Done/done.css similarity index 100% rename from js/src/modals/SMSVerification/Done/done.css rename to js/src/modals/Verification/Done/done.css diff --git a/js/src/modals/SMSVerification/Done/done.js b/js/src/modals/Verification/Done/done.js similarity index 100% rename from js/src/modals/SMSVerification/Done/done.js rename to js/src/modals/Verification/Done/done.js diff --git a/js/src/modals/SMSVerification/Done/index.js b/js/src/modals/Verification/Done/index.js similarity index 100% rename from js/src/modals/SMSVerification/Done/index.js rename to js/src/modals/Verification/Done/index.js diff --git a/js/src/modals/SMSVerification/GatherData/gatherData.css b/js/src/modals/Verification/GatherData/gatherData.css similarity index 100% rename from js/src/modals/SMSVerification/GatherData/gatherData.css rename to js/src/modals/Verification/GatherData/gatherData.css diff --git a/js/src/modals/SMSVerification/GatherData/gatherData.js b/js/src/modals/Verification/GatherData/gatherData.js similarity index 100% rename from js/src/modals/SMSVerification/GatherData/gatherData.js rename to js/src/modals/Verification/GatherData/gatherData.js diff --git a/js/src/modals/SMSVerification/GatherData/index.js b/js/src/modals/Verification/GatherData/index.js similarity index 100% rename from js/src/modals/SMSVerification/GatherData/index.js rename to js/src/modals/Verification/GatherData/index.js diff --git a/js/src/modals/SMSVerification/QueryCode/index.js b/js/src/modals/Verification/QueryCode/index.js similarity index 100% rename from js/src/modals/SMSVerification/QueryCode/index.js rename to js/src/modals/Verification/QueryCode/index.js diff --git a/js/src/modals/SMSVerification/QueryCode/queryCode.js b/js/src/modals/Verification/QueryCode/queryCode.js similarity index 100% rename from js/src/modals/SMSVerification/QueryCode/queryCode.js rename to js/src/modals/Verification/QueryCode/queryCode.js diff --git a/js/src/modals/SMSVerification/SendConfirmation/index.js b/js/src/modals/Verification/SendConfirmation/index.js similarity index 100% rename from js/src/modals/SMSVerification/SendConfirmation/index.js rename to js/src/modals/Verification/SendConfirmation/index.js diff --git a/js/src/modals/SMSVerification/SendConfirmation/sendConfirmation.css b/js/src/modals/Verification/SendConfirmation/sendConfirmation.css similarity index 100% rename from js/src/modals/SMSVerification/SendConfirmation/sendConfirmation.css rename to js/src/modals/Verification/SendConfirmation/sendConfirmation.css diff --git a/js/src/modals/SMSVerification/SendConfirmation/sendConfirmation.js b/js/src/modals/Verification/SendConfirmation/sendConfirmation.js similarity index 100% rename from js/src/modals/SMSVerification/SendConfirmation/sendConfirmation.js rename to js/src/modals/Verification/SendConfirmation/sendConfirmation.js diff --git a/js/src/modals/SMSVerification/SendRequest/index.js b/js/src/modals/Verification/SendRequest/index.js similarity index 100% rename from js/src/modals/SMSVerification/SendRequest/index.js rename to js/src/modals/Verification/SendRequest/index.js diff --git a/js/src/modals/SMSVerification/SendRequest/sendRequest.css b/js/src/modals/Verification/SendRequest/sendRequest.css similarity index 100% rename from js/src/modals/SMSVerification/SendRequest/sendRequest.css rename to js/src/modals/Verification/SendRequest/sendRequest.css diff --git a/js/src/modals/SMSVerification/SendRequest/sendRequest.js b/js/src/modals/Verification/SendRequest/sendRequest.js similarity index 100% rename from js/src/modals/SMSVerification/SendRequest/sendRequest.js rename to js/src/modals/Verification/SendRequest/sendRequest.js diff --git a/js/src/modals/SMSVerification/index.js b/js/src/modals/Verification/index.js similarity index 94% rename from js/src/modals/SMSVerification/index.js rename to js/src/modals/Verification/index.js index d9b0990db..9c29a7165 100644 --- a/js/src/modals/SMSVerification/index.js +++ b/js/src/modals/Verification/index.js @@ -14,4 +14,4 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -export default from './SMSVerification'; +export default from './verification'; diff --git a/js/src/modals/SMSVerification/store.js b/js/src/modals/Verification/store.js similarity index 100% rename from js/src/modals/SMSVerification/store.js rename to js/src/modals/Verification/store.js diff --git a/js/src/modals/SMSVerification/SMSVerification.js b/js/src/modals/Verification/verification.js similarity index 97% rename from js/src/modals/SMSVerification/SMSVerification.js rename to js/src/modals/Verification/verification.js index 86f027a52..65447c4f9 100644 --- a/js/src/modals/SMSVerification/SMSVerification.js +++ b/js/src/modals/Verification/verification.js @@ -37,7 +37,7 @@ import SendConfirmation from './SendConfirmation'; import Done from './Done'; @observer -export default class SMSVerification extends Component { +export default class Verification extends Component { static propTypes = { store: PropTypes.any.isRequired, account: PropTypes.string.isRequired, @@ -54,7 +54,7 @@ export default class SMSVerification extends Component { } render () { - const phase = SMSVerification.phases[this.props.store.step]; + const phase = Verification.phases[this.props.store.step]; const { error, isStepValid } = this.props.store; return ( diff --git a/js/src/modals/index.js b/js/src/modals/index.js index 0f0844e40..1daee8663 100644 --- a/js/src/modals/index.js +++ b/js/src/modals/index.js @@ -24,7 +24,7 @@ import EditMeta from './EditMeta'; import ExecuteContract from './ExecuteContract'; import FirstRun from './FirstRun'; import Shapeshift from './Shapeshift'; -import SMSVerification from './SMSVerification'; +import Verification from './Verification'; import Transfer from './Transfer'; import PasswordManager from './PasswordManager'; import SaveContract from './SaveContract'; @@ -42,7 +42,7 @@ export { ExecuteContract, FirstRun, Shapeshift, - SMSVerification, + Verification, Transfer, PasswordManager, LoadContract, diff --git a/js/src/views/Account/account.js b/js/src/views/Account/account.js index 98b0a5e97..1fd654fde 100644 --- a/js/src/views/Account/account.js +++ b/js/src/views/Account/account.js @@ -23,7 +23,7 @@ import ContentSend from 'material-ui/svg-icons/content/send'; import LockIcon from 'material-ui/svg-icons/action/lock'; import VerifyIcon from 'material-ui/svg-icons/action/verified-user'; -import { EditMeta, DeleteAccount, Shapeshift, SMSVerification, Transfer, PasswordManager } from '~/modals'; +import { EditMeta, DeleteAccount, Shapeshift, Verification, Transfer, PasswordManager } from '~/modals'; import { Actionbar, Button, Page } from '~/ui'; import shapeshiftBtn from '../../../assets/images/shapeshift-btn.png'; @@ -32,7 +32,7 @@ import Header from './Header'; import Transactions from './Transactions'; import { setVisibleAccounts } from '~/redux/providers/personalActions'; -import VerificationStore from '~/modals/SMSVerification/store'; +import VerificationStore from '~/modals/Verification/store'; import styles from './account.css'; @@ -228,7 +228,7 @@ class Account extends Component { const { address } = this.props.params; return ( - From 1672cbad7be46f49af7e2b0b1eaf66b08f9a95a4 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Tue, 6 Dec 2016 16:09:02 +0100 Subject: [PATCH 155/295] factor out SMS-specific logic --- .../Verification/SendRequest/sendRequest.js | 6 +- js/src/modals/Verification/sms-store.js | 66 +++++++++++++++++++ js/src/modals/Verification/store.js | 55 +++------------- js/src/modals/Verification/verification.js | 8 +-- js/src/views/Account/account.js | 2 +- 5 files changed, 83 insertions(+), 54 deletions(-) create mode 100644 js/src/modals/Verification/sms-store.js diff --git a/js/src/modals/Verification/SendRequest/sendRequest.js b/js/src/modals/Verification/SendRequest/sendRequest.js index 933de9265..41dc7c06c 100644 --- a/js/src/modals/Verification/SendRequest/sendRequest.js +++ b/js/src/modals/Verification/SendRequest/sendRequest.js @@ -19,7 +19,7 @@ import React, { Component, PropTypes } from 'react'; import { nullableProptype } from '~/util/proptypes'; import TxHash from '~/ui/TxHash'; import { - POSTING_REQUEST, POSTED_REQUEST, REQUESTING_SMS + POSTING_REQUEST, POSTED_REQUEST, REQUESTING_CODE } from '../store'; import styles from './sendRequest.css'; @@ -45,9 +45,9 @@ export default class SendRequest extends Component { ); - case REQUESTING_SMS: + case REQUESTING_CODE: return ( -

Requesting an SMS from the Parity server and waiting for the puzzle to be put into the contract.

+

Requesting a code from the Parity server and waiting for the puzzle to be put into the contract.

); default: diff --git a/js/src/modals/Verification/sms-store.js b/js/src/modals/Verification/sms-store.js new file mode 100644 index 000000000..58b79ebfb --- /dev/null +++ b/js/src/modals/Verification/sms-store.js @@ -0,0 +1,66 @@ +// 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 . + +import { observable, computed, action } from 'mobx'; +import phone from 'phoneformat.js'; + +import VerificationStore, { + LOADING, QUERY_DATA, QUERY_CODE, POSTED_CONFIRMATION, DONE +} from './store'; +import { postToServer } from '../../3rdparty/sms-verification'; + +export default class SMSVerificationStore extends VerificationStore { + @observable number = ''; + + @computed get isNumberValid () { + return phone.isValidNumber(this.number); + } + + @computed get isStepValid () { + if (this.step === DONE) { + return true; + } + if (this.error) { + return false; + } + + switch (this.step) { + case LOADING: + return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null; + case QUERY_DATA: + return this.isNumberValid && this.consentGiven; + case QUERY_CODE: + return this.requestTx && this.isCodeValid === true; + case POSTED_CONFIRMATION: + return !!this.confirmationTx; + default: + return false; + } + } + + constructor (api, account, isTestnet) { + return super(api, account, isTestnet, 'smsverification'); + } + + @action setNumber = (number) => { + this.number = number; + } + + requestCode = () => { + const { number, account, isTestnet } = this; + return postToServer({ number, address: account }, isTestnet); + } +} diff --git a/js/src/modals/Verification/store.js b/js/src/modals/Verification/store.js index 279329ca5..f8542786b 100644 --- a/js/src/modals/Verification/store.js +++ b/js/src/modals/Verification/store.js @@ -14,14 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { observable, computed, autorun, action } from 'mobx'; -import phone from 'phoneformat.js'; +import { observable, autorun, action } from 'mobx'; import { sha3 } from '~/api/util/sha3'; - import Contracts from '~/contracts'; import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/verification'; -import { postToServer } from '../../3rdparty/sms-verification'; import checkIfTxFailed from '../../util/check-if-tx-failed'; import waitForConfirmations from '../../util/wait-for-block-confirmations'; @@ -29,7 +26,7 @@ export const LOADING = 'fetching-contract'; export const QUERY_DATA = 'query-data'; export const POSTING_REQUEST = 'posting-request'; export const POSTED_REQUEST = 'posted-request'; -export const REQUESTING_SMS = 'requesting-sms'; +export const REQUESTING_CODE = 'requesting-code'; export const QUERY_CODE = 'query-code'; export const POSTING_CONFIRMATION = 'posting-confirmation'; export const POSTED_CONFIRMATION = 'posted-confirmation'; @@ -44,45 +41,18 @@ export default class VerificationStore { @observable isVerified = null; @observable hasRequested = null; @observable consentGiven = false; - @observable number = ''; @observable requestTx = null; @observable code = ''; @observable isCodeValid = null; @observable confirmationTx = null; - @computed get isNumberValid () { - return phone.isValidNumber(this.number); - } - - @computed get isStepValid () { - if (this.step === DONE) { - return true; - } - if (this.error) { - return false; - } - - switch (this.step) { - case LOADING: - return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null; - case QUERY_DATA: - return this.isNumberValid && this.consentGiven; - case QUERY_CODE: - return this.requestTx && this.isCodeValid === true; - case POSTED_CONFIRMATION: - return !!this.confirmationTx; - default: - return false; - } - } - - constructor (api, account, isTestnet) { + constructor (api, account, isTestnet, name) { this.api = api; this.account = account; this.isTestnet = isTestnet; this.step = LOADING; - Contracts.create(api).registry.getContract('smsverification') + Contracts.create(api).registry.getContract(name) .then((contract) => { this.contract = contract; this.load(); @@ -93,7 +63,7 @@ export default class VerificationStore { autorun(() => { if (this.error) { - console.error('sms verification: ' + this.error); + console.error('verification: ' + this.error); } }); } @@ -136,10 +106,6 @@ export default class VerificationStore { }); } - @action setNumber = (number) => { - this.number = number; - } - @action setConsentGiven = (consentGiven) => { this.consentGiven = consentGiven; } @@ -168,7 +134,7 @@ export default class VerificationStore { } @action sendRequest = () => { - const { api, account, contract, fee, number, hasRequested } = this; + const { api, account, contract, fee, hasRequested } = this; const request = contract.functions.find((fn) => fn.name === 'request'); const options = { from: account, value: fee.toString() }; @@ -201,18 +167,15 @@ export default class VerificationStore { chain .then(() => { - return api.parity.netChain(); - }) - .then((chain) => { - this.step = REQUESTING_SMS; - return postToServer({ number, address: account }, this.isTestnet); + this.step = REQUESTING_CODE; + return this.requestCode(); }) .then(() => awaitPuzzle(api, contract, account)) .then(() => { this.step = QUERY_CODE; }) .catch((err) => { - this.error = 'Failed to request a confirmation SMS: ' + err.message; + this.error = 'Failed to request a confirmation code: ' + err.message; }); } diff --git a/js/src/modals/Verification/verification.js b/js/src/modals/Verification/verification.js index 65447c4f9..b05ed353a 100644 --- a/js/src/modals/Verification/verification.js +++ b/js/src/modals/Verification/verification.js @@ -25,7 +25,7 @@ import { LOADING, QUERY_DATA, POSTING_REQUEST, POSTED_REQUEST, - REQUESTING_SMS, QUERY_CODE, + REQUESTING_CODE, QUERY_CODE, POSTING_CONFIRMATION, POSTED_CONFIRMATION, DONE } from './store'; @@ -47,7 +47,7 @@ export default class Verification extends Component { static phases = { // mapping (store steps -> steps) [LOADING]: 0, [QUERY_DATA]: 1, - [POSTING_REQUEST]: 2, [POSTED_REQUEST]: 2, [REQUESTING_SMS]: 2, + [POSTING_REQUEST]: 2, [POSTED_REQUEST]: 2, [REQUESTING_CODE]: 2, [QUERY_CODE]: 3, [POSTING_CONFIRMATION]: 4, [POSTED_CONFIRMATION]: 4, [DONE]: 5 @@ -60,7 +60,7 @@ export default class Verification extends Component { return ( Loading SMS Verification.

+

Loading Verification.

); case 1: diff --git a/js/src/views/Account/account.js b/js/src/views/Account/account.js index 1fd654fde..240ca2f9b 100644 --- a/js/src/views/Account/account.js +++ b/js/src/views/Account/account.js @@ -32,7 +32,7 @@ import Header from './Header'; import Transactions from './Transactions'; import { setVisibleAccounts } from '~/redux/providers/personalActions'; -import VerificationStore from '~/modals/Verification/store'; +import VerificationStore from '~/modals/Verification/sms-store'; import styles from './account.css'; From 1ac3421f3327778306c21997e9acdcecde32ad6d Mon Sep 17 00:00:00 2001 From: Jannis R Date: Tue, 6 Dec 2016 17:16:54 +0100 Subject: [PATCH 156/295] step to select verification method --- js/src/modals/Verification/verification.js | 109 ++++++++++++++++----- js/src/views/Account/account.js | 29 ++++-- 2 files changed, 105 insertions(+), 33 deletions(-) diff --git a/js/src/modals/Verification/verification.js b/js/src/modals/Verification/verification.js index b05ed353a..5982933a9 100644 --- a/js/src/modals/Verification/verification.js +++ b/js/src/modals/Verification/verification.js @@ -20,6 +20,13 @@ import DoneIcon from 'material-ui/svg-icons/action/done-all'; import CancelIcon from 'material-ui/svg-icons/content/clear'; import { Button, IdentityIcon, Modal } from '~/ui'; +import RadioButtons from '~/ui/Form/RadioButtons'; +import { nullableProptype } from '~/util/proptypes'; + +const methods = { + sms: { label: 'SMS Verification', key: 0, value: 'sms' }, + email: { label: 'E-mail Verification', key: 1, value: 'email' } +}; import { LOADING, @@ -39,23 +46,34 @@ import Done from './Done'; @observer export default class Verification extends Component { static propTypes = { - store: PropTypes.any.isRequired, + store: nullableProptype(PropTypes.object).isRequired, account: PropTypes.string.isRequired, + onSelectMethod: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired } static phases = { // mapping (store steps -> steps) - [LOADING]: 0, - [QUERY_DATA]: 1, - [POSTING_REQUEST]: 2, [POSTED_REQUEST]: 2, [REQUESTING_CODE]: 2, - [QUERY_CODE]: 3, - [POSTING_CONFIRMATION]: 4, [POSTED_CONFIRMATION]: 4, - [DONE]: 5 + [LOADING]: 1, + [QUERY_DATA]: 2, + [POSTING_REQUEST]: 3, [POSTED_REQUEST]: 3, [REQUESTING_CODE]: 3, + [QUERY_CODE]: 4, + [POSTING_CONFIRMATION]: 5, [POSTED_CONFIRMATION]: 5, + [DONE]: 6 } + state = { + method: 'sms' + }; + render () { - const phase = Verification.phases[this.props.store.step]; - const { error, isStepValid } = this.props.store; + const { store } = this.props; + let phase = 0; let error = false; let isStepValid = true; + + if (store) { + phase = Verification.phases[store.step]; + error = store.error; + isStepValid = store.isStepValid; + } return ( { this.renderStep(phase, error) } @@ -85,7 +103,7 @@ export default class Verification extends Component { return (
{ cancel }
); } - if (phase === 5) { + if (phase === 6) { return (
{ cancel } @@ -101,16 +119,23 @@ export default class Verification extends Component { let action = () => {}; switch (phase) { - case 1: - action = store.sendRequest; + case 0: + action = () => { + const { onSelectMethod } = this.props; + const { method } = this.state; + onSelectMethod(method); + }; break; case 2: - action = store.queryCode; + action = store.sendRequest; break; case 3: - action = store.sendConfirmation; + action = store.queryCode; break; case 4: + action = store.sendConfirmation; + break; + case 5: action = store.done; break; } @@ -133,6 +158,19 @@ export default class Verification extends Component { return (

{ error }

); } + if (phase === 0) { + const { method } = this.state; + const values = Object.values(methods); + const value = values.findIndex((v) => v.value === method); + return ( + + ); + } + const { step, fee, number, isNumberValid, isVerified, hasRequested, @@ -141,13 +179,34 @@ export default class Verification extends Component { } = this.props.store; switch (phase) { - case 0: + case 1: return (

Loading Verification.

); - case 1: - const { setNumber, setConsentGiven } = this.props.store; + case 2: + const { method } = this.state; + const { setConsentGiven } = this.props.store; + + const fields = [] + if (method === 'sms') { + fields.push({ + key: 'number', + label: 'phone number in international format', + hint: 'the SMS will be sent to this number', + error: this.props.store.isNumberValid ? null : 'invalid number', + onChange: this.props.store.setNumber + }); + } else if (method === 'email') { + fields.push({ + key: 'email', + label: 'email address', + hint: 'the code will be sent to this address', + error: this.props.store.isEmailValid ? null : 'invalid email', + onChange: this.props.store.setEmail + }); + } + return ( ); - case 2: + case 3: return ( ); - case 3: + case 4: return ( ); - case 4: + case 5: return ( ); - case 5: + case 6: return ( ); @@ -183,4 +242,8 @@ export default class Verification extends Component { return null; } } + + selectMethod = (choice, i) => { + this.setState({ method: choice.value }); + } } diff --git a/js/src/views/Account/account.js b/js/src/views/Account/account.js index 240ca2f9b..3f262bce4 100644 --- a/js/src/views/Account/account.js +++ b/js/src/views/Account/account.js @@ -32,7 +32,8 @@ import Header from './Header'; import Transactions from './Transactions'; import { setVisibleAccounts } from '~/redux/providers/personalActions'; -import VerificationStore from '~/modals/Verification/sms-store'; +import SMSVerificationStore from '~/modals/Verification/sms-store'; +import EmailVerificationStore from '~/modals/Verification/email-store'; import styles from './account.css'; @@ -72,15 +73,6 @@ class Account extends Component { if (prevAddress !== nextAddress) { this.setVisibleAccounts(nextProps); } - - const { isTestnet } = nextProps; - if (typeof isTestnet === 'boolean' && !this.state.verificationStore) { - const { api } = this.context; - const { address } = nextProps.params; - this.setState({ - verificationStore: new VerificationStore(api, address, isTestnet) - }); - } } componentWillUnmount () { @@ -230,6 +222,7 @@ class Account extends Component { return ( ); @@ -303,6 +296,22 @@ class Account extends Component { this.setState({ showVerificationDialog: true }); } + selectVerificationMethod = (name) => { + const { isTestnet } = this.props; + if (typeof isTestnet !== 'boolean' || this.state.verificationStore) return; + + const { api } = this.context; + const { address } = this.props.params; + + let verificationStore = null; + if (name === 'sms') { + verificationStore = new SMSVerificationStore(api, address, isTestnet); + } else if (name === 'email') { + verificationStore = new EmailVerificationStore(api, address, isTestnet); + } + this.setState({ verificationStore }); + } + onVerificationClose = () => { this.setState({ showVerificationDialog: false }); } From d3fd71d9534ac540d72f7d8263e13a5e8461fa53 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Tue, 6 Dec 2016 17:45:34 +0100 Subject: [PATCH 157/295] add email-specific contract, helpers, store --- js/src/3rdparty/email-verification/index.js | 53 +++++++++++++++ js/src/3rdparty/email-verification/styles.css | 20 ++++++ js/src/3rdparty/sms-verification/index.js | 13 ++++ js/src/3rdparty/sms-verification/styles.css | 20 ++++++ js/src/contracts/abi/email-verification.json | 1 + js/src/contracts/abi/index.js | 2 + js/src/modals/Verification/email-store.js | 66 +++++++++++++++++++ 7 files changed, 175 insertions(+) create mode 100644 js/src/3rdparty/email-verification/index.js create mode 100644 js/src/3rdparty/email-verification/styles.css create mode 100644 js/src/3rdparty/sms-verification/styles.css create mode 100644 js/src/contracts/abi/email-verification.json create mode 100644 js/src/modals/Verification/email-store.js diff --git a/js/src/3rdparty/email-verification/index.js b/js/src/3rdparty/email-verification/index.js new file mode 100644 index 000000000..5f4885f3b --- /dev/null +++ b/js/src/3rdparty/email-verification/index.js @@ -0,0 +1,53 @@ +// 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 . + +import { stringify } from 'querystring'; +import React from 'react'; + +import styles from './styles.css'; + +export const howItWorks = ( +
+

The following steps will let you prove that you control both an account and an e-mail address.

+
    +
  1. You send a verification request to a specific contract.
  2. +
  3. Our server puts a puzzle into this contract.
  4. +
  5. The code you receive via e-mail is the solution to this puzzle.
  6. +
+
+); + +export const termsOfService = ( +
    +
  • todo
  • +
+); + +export const postToServer = (query, isTestnet = false) => { + const port = isTestnet ? 28443 : 18443; + query = stringify(query); + return fetch(`https://email-verification.parity.io:${port}/?` + query, { + method: 'POST', mode: 'cors', cache: 'no-store' + }) + .then((res) => { + return res.json().then((data) => { + if (res.ok) { + return data.message; + } + throw new Error(data.message || 'unknown error'); + }); + }); +}; diff --git a/js/src/3rdparty/email-verification/styles.css b/js/src/3rdparty/email-verification/styles.css new file mode 100644 index 000000000..daa4c605c --- /dev/null +++ b/js/src/3rdparty/email-verification/styles.css @@ -0,0 +1,20 @@ +/* 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 . +*/ + +.list li { + padding: .1em 0; +} diff --git a/js/src/3rdparty/sms-verification/index.js b/js/src/3rdparty/sms-verification/index.js index c50b2331a..46faf084c 100644 --- a/js/src/3rdparty/sms-verification/index.js +++ b/js/src/3rdparty/sms-verification/index.js @@ -17,6 +17,19 @@ import { stringify } from 'querystring'; import React from 'react'; +import styles from './styles.css'; + +export const howItWorks = ( +
+

The following steps will let you prove that you control both an account and a phone number.

+
    +
  1. You send a verification request to a specific contract.
  2. +
  3. Our server puts a puzzle into this contract.
  4. +
  5. The code you receive via SMS is the solution to this puzzle.
  6. +
+
+); + export const termsOfService = (
  • This privacy notice relates to your use of the Parity SMS verification service. We take your privacy seriously and deal in an honest, direct and transparent way when it comes to your data.
  • diff --git a/js/src/3rdparty/sms-verification/styles.css b/js/src/3rdparty/sms-verification/styles.css new file mode 100644 index 000000000..daa4c605c --- /dev/null +++ b/js/src/3rdparty/sms-verification/styles.css @@ -0,0 +1,20 @@ +/* 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 . +*/ + +.list li { + padding: .1em 0; +} diff --git a/js/src/contracts/abi/email-verification.json b/js/src/contracts/abi/email-verification.json new file mode 100644 index 000000000..baa6db483 --- /dev/null +++ b/js/src/contracts/abi/email-verification.json @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"reverse","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"},{"name":"_puzzle","type":"bytes32"},{"name":"_emailHash","type":"bytes32"}],"name":"puzzle","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_emailHash","type":"bytes32"}],"name":"request","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_code","type":"bytes32"}],"name":"confirm","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":false,"name":"emailHash","type":"bytes32"}],"name":"Requested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":true,"name":"emailHash","type":"bytes32"},{"indexed":false,"name":"puzzle","type":"bytes32"}],"name":"Puzzled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Confirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Revoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}] \ No newline at end of file diff --git a/js/src/contracts/abi/index.js b/js/src/contracts/abi/index.js index f15765b1a..c8cb851dd 100644 --- a/js/src/contracts/abi/index.js +++ b/js/src/contracts/abi/index.js @@ -19,6 +19,7 @@ import basiccoin from './basiccoin.json'; import basiccoinmanager from './basiccoinmanager.json'; import dappreg from './dappreg.json'; import eip20 from './eip20.json'; +import emailverification from './email-verification.json'; import gavcoin from './gavcoin.json'; import githubhint from './githubhint.json'; import owned from './owned.json'; @@ -34,6 +35,7 @@ export { basiccoinmanager, dappreg, eip20, + emailverification, gavcoin, githubhint, owned, diff --git a/js/src/modals/Verification/email-store.js b/js/src/modals/Verification/email-store.js new file mode 100644 index 000000000..14179cfe2 --- /dev/null +++ b/js/src/modals/Verification/email-store.js @@ -0,0 +1,66 @@ +// 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 . + +import { observable, computed, action } from 'mobx'; + +import VerificationStore, { + LOADING, QUERY_DATA, QUERY_CODE, POSTED_CONFIRMATION, DONE +} from './store'; +import { postToServer } from '../../3rdparty/email-verification'; + +export default class EmailVerificationStore extends VerificationStore { + @observable email = ''; + + @computed get isEmailValid () { + // See https://davidcel.is/posts/stop-validating-email-addresses-with-regex/ + return this.email && this.email.indexOf('@') >= 0; + } + + @computed get isStepValid () { + if (this.step === DONE) { + return true; + } + if (this.error) { + return false; + } + + switch (this.step) { + case LOADING: + return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null; + case QUERY_DATA: + return this.isEmailValid && this.consentGiven; + case QUERY_CODE: + return this.requestTx && this.isCodeValid === true; + case POSTED_CONFIRMATION: + return !!this.confirmationTx; + default: + return false; + } + } + + constructor (api, account, isTestnet) { + return super(api, account, isTestnet, 'emailverification'); + } + + @action setEmail = (email) => { + this.email = email; + } + + requestCode = () => { + const { email, account, isTestnet } = this; + return postToServer({ email, address: account }, isTestnet); + } +} From 052f9258a5f1c596adac6e3b2ab490032303da5b Mon Sep 17 00:00:00 2001 From: Jannis R Date: Wed, 7 Dec 2016 11:53:48 +0100 Subject: [PATCH 158/295] pass fields to query into GatherData --- .../Verification/GatherData/gatherData.css | 4 -- .../Verification/GatherData/gatherData.js | 52 +++++++++++-------- js/src/modals/Verification/verification.js | 8 +-- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/js/src/modals/Verification/GatherData/gatherData.css b/js/src/modals/Verification/GatherData/gatherData.css index 680986981..13563b6a5 100644 --- a/js/src/modals/Verification/GatherData/gatherData.css +++ b/js/src/modals/Verification/GatherData/gatherData.css @@ -15,10 +15,6 @@ /* along with Parity. If not, see . */ -.list li { - padding: .1em 0; -} - .spacing { margin-top: 1.5em; } diff --git a/js/src/modals/Verification/GatherData/gatherData.js b/js/src/modals/Verification/GatherData/gatherData.js index f5f09578e..24f870fc6 100644 --- a/js/src/modals/Verification/GatherData/gatherData.js +++ b/js/src/modals/Verification/GatherData/gatherData.js @@ -25,41 +25,31 @@ import { fromWei } from '~/api/util/wei'; import { Form, Input } from '~/ui'; import { nullableProptype } from '~/util/proptypes'; -import { termsOfService } from '../../../3rdparty/sms-verification'; +import * as sms from '../../../3rdparty/sms-verification'; +import * as email from '../../../3rdparty/email-verification'; import styles from './gatherData.css'; export default class GatherData extends Component { static propTypes = { fee: React.PropTypes.instanceOf(BigNumber), - isNumberValid: PropTypes.bool.isRequired, + method: PropTypes.string.isRequired, + fields: PropTypes.array.isRequired, isVerified: nullableProptype(PropTypes.bool.isRequired), hasRequested: nullableProptype(PropTypes.bool.isRequired), - setNumber: PropTypes.func.isRequired, setConsentGiven: PropTypes.func.isRequired } render () { - const { isNumberValid, isVerified } = this.props; + const { method, isVerified } = this.props; + const { howItWorks, termsOfService } = method === 'email' ? email : sms; return (
    -

    The following steps will let you prove that you control both an account and a phone number.

    -
      -
    1. You send a verification request to a specific contract.
    2. -
    3. Our server puts a puzzle into this contract.
    4. -
    5. The code you receive via SMS is the solution to this puzzle.
    6. -
    + { howItWorks } { this.renderFee() } { this.renderCertified() } { this.renderRequested() } - + { this.renderFields() } { - this.props.setNumber(value); - } + renderFields () { + const { isVerified, fields } = this.props; - numberOnChange = (_, value) => { - this.props.setNumber(value); + const rendered = fields.map((field) => { + const onChange = (_, v) => { + field.onChange(v); + }; + const onSubmit = field.onChange; + return ( + + ); + }); + + return (
    {rendered}
    ); } consentOnChange = (_, consentGiven) => { diff --git a/js/src/modals/Verification/verification.js b/js/src/modals/Verification/verification.js index 5982933a9..276dfbd1c 100644 --- a/js/src/modals/Verification/verification.js +++ b/js/src/modals/Verification/verification.js @@ -46,7 +46,7 @@ import Done from './Done'; @observer export default class Verification extends Component { static propTypes = { - store: nullableProptype(PropTypes.object).isRequired, + store: nullableProptype(PropTypes.object.isRequired), account: PropTypes.string.isRequired, onSelectMethod: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired @@ -209,9 +209,9 @@ export default class Verification extends Component { return ( ); From 0e0f602d5e1423eb1665bc794e03e81a1c17fb61 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Wed, 7 Dec 2016 19:21:29 +0100 Subject: [PATCH 159/295] pass fields to query into QueryCode --- js/src/modals/Verification/QueryCode/queryCode.js | 13 +++++++++---- js/src/modals/Verification/verification.js | 15 ++++++++++++--- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/js/src/modals/Verification/QueryCode/queryCode.js b/js/src/modals/Verification/QueryCode/queryCode.js index 03b228367..db0ae25eb 100644 --- a/js/src/modals/Verification/QueryCode/queryCode.js +++ b/js/src/modals/Verification/QueryCode/queryCode.js @@ -20,20 +20,25 @@ import { Form, Input } from '~/ui'; export default class QueryCode extends Component { static propTypes = { - number: PropTypes.string.isRequired, + receiver: PropTypes.string.isRequired, + hint: PropTypes.string, isCodeValid: PropTypes.bool.isRequired, setCode: PropTypes.func.isRequired } + static defaultProps = { + hint: 'Enter the code you received.' + } + render () { - const { number, isCodeValid } = this.props; + const { receiver, hint, isCodeValid } = this.props; return ( -

    The verification code has been sent to { number }.

    +

    The verification code has been sent to { receiver }.

    { error }

    ); } + const { method } = this.state; if (phase === 0) { - const { method } = this.state; const values = Object.values(methods); const value = values.findIndex((v) => v.value === method); return ( @@ -185,7 +185,6 @@ export default class Verification extends Component { ); case 2: - const { method } = this.state; const { setConsentGiven } = this.props.store; const fields = [] @@ -221,9 +220,19 @@ export default class Verification extends Component { ); case 4: + let receiver, hint; + if (method === 'sms') { + receiver = this.props.store.number; + hint = 'Enter the code you received via SMS.'; + } else if (method === 'email') { + receiver = this.props.store.email; + hint = 'Enter the code you received via e-mail.'; + } return ( ); From 162420f4c2f790808de5c0086e6990ac84368cd4 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Wed, 7 Dec 2016 19:22:13 +0100 Subject: [PATCH 160/295] send emailHash with request, update ABI --- js/src/contracts/abi/email-verification.json | 2 +- js/src/modals/Verification/email-store.js | 5 +++++ js/src/modals/Verification/store.js | 7 +++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/js/src/contracts/abi/email-verification.json b/js/src/contracts/abi/email-verification.json index baa6db483..6a7f5a6d0 100644 --- a/js/src/contracts/abi/email-verification.json +++ b/js/src/contracts/abi/email-verification.json @@ -1 +1 @@ -[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"reverse","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"},{"name":"_puzzle","type":"bytes32"},{"name":"_emailHash","type":"bytes32"}],"name":"puzzle","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_emailHash","type":"bytes32"}],"name":"request","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_code","type":"bytes32"}],"name":"confirm","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":false,"name":"emailHash","type":"bytes32"}],"name":"Requested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":true,"name":"emailHash","type":"bytes32"},{"indexed":false,"name":"puzzle","type":"bytes32"}],"name":"Puzzled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Confirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Revoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}] \ No newline at end of file +[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"reverse","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"},{"name":"_puzzle","type":"bytes32"},{"name":"_emailHash","type":"bytes32"}],"name":"puzzle","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_emailHash","type":"bytes32"}],"name":"request","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_code","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":false,"name":"emailHash","type":"bytes32"}],"name":"Requested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":true,"name":"emailHash","type":"bytes32"},{"indexed":false,"name":"puzzle","type":"bytes32"}],"name":"Puzzled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Confirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Revoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}] \ No newline at end of file diff --git a/js/src/modals/Verification/email-store.js b/js/src/modals/Verification/email-store.js index 14179cfe2..f0a65300f 100644 --- a/js/src/modals/Verification/email-store.js +++ b/js/src/modals/Verification/email-store.js @@ -15,6 +15,7 @@ // along with Parity. If not, see . import { observable, computed, action } from 'mobx'; +import { sha3 } from '~/api/util/sha3'; import VerificationStore, { LOADING, QUERY_DATA, QUERY_CODE, POSTED_CONFIRMATION, DONE @@ -55,10 +56,14 @@ export default class EmailVerificationStore extends VerificationStore { return super(api, account, isTestnet, 'emailverification'); } + requestValues = () => [ sha3(this.email) ] + @action setEmail = (email) => { this.email = email; } + requestValues = () => [ sha3(this.email) ] + requestCode = () => { const { email, account, isTestnet } = this; return postToServer({ email, address: account }, isTestnet); diff --git a/js/src/modals/Verification/store.js b/js/src/modals/Verification/store.js index f8542786b..fc89ad8ac 100644 --- a/js/src/modals/Verification/store.js +++ b/js/src/modals/Verification/store.js @@ -133,19 +133,22 @@ export default class VerificationStore { }); } + requestValues = () => [] + @action sendRequest = () => { const { api, account, contract, fee, hasRequested } = this; const request = contract.functions.find((fn) => fn.name === 'request'); const options = { from: account, value: fee.toString() }; + const values = this.requestValues(); let chain = Promise.resolve(); if (!hasRequested) { this.step = POSTING_REQUEST; - chain = request.estimateGas(options, []) + chain = request.estimateGas(options, values) .then((gas) => { options.gas = gas.mul(1.2).toFixed(0); - return request.postTransaction(options, []); + return request.postTransaction(options, values); }) .then((handle) => { // TODO: The "request rejected" error doesn't have any property to From dfc445b6d66b4752d92e9460dede40df7696b8b2 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Thu, 8 Dec 2016 11:55:51 +0100 Subject: [PATCH 161/295] fix bugs & linting issues --- js/src/modals/Verification/email-store.js | 4 +--- js/src/modals/Verification/sms-store.js | 2 +- js/src/modals/Verification/store.js | 2 +- js/src/modals/Verification/verification.js | 4 ++-- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/js/src/modals/Verification/email-store.js b/js/src/modals/Verification/email-store.js index f0a65300f..5d4ddb4b7 100644 --- a/js/src/modals/Verification/email-store.js +++ b/js/src/modals/Verification/email-store.js @@ -53,7 +53,7 @@ export default class EmailVerificationStore extends VerificationStore { } constructor (api, account, isTestnet) { - return super(api, account, isTestnet, 'emailverification'); + super(api, account, isTestnet, 'emailverification3'); } requestValues = () => [ sha3(this.email) ] @@ -62,8 +62,6 @@ export default class EmailVerificationStore extends VerificationStore { this.email = email; } - requestValues = () => [ sha3(this.email) ] - requestCode = () => { const { email, account, isTestnet } = this; return postToServer({ email, address: account }, isTestnet); diff --git a/js/src/modals/Verification/sms-store.js b/js/src/modals/Verification/sms-store.js index 58b79ebfb..d0f316358 100644 --- a/js/src/modals/Verification/sms-store.js +++ b/js/src/modals/Verification/sms-store.js @@ -52,7 +52,7 @@ export default class SMSVerificationStore extends VerificationStore { } constructor (api, account, isTestnet) { - return super(api, account, isTestnet, 'smsverification'); + super(api, account, isTestnet, 'smsverification'); } @action setNumber = (number) => { diff --git a/js/src/modals/Verification/store.js b/js/src/modals/Verification/store.js index fc89ad8ac..88c86e206 100644 --- a/js/src/modals/Verification/store.js +++ b/js/src/modals/Verification/store.js @@ -52,7 +52,7 @@ export default class VerificationStore { this.isTestnet = isTestnet; this.step = LOADING; - Contracts.create(api).registry.getContract(name) + Contracts.get().registry.getContract(name) .then((contract) => { this.contract = contract; this.load(); diff --git a/js/src/modals/Verification/verification.js b/js/src/modals/Verification/verification.js index 094c4d074..cb40d8351 100644 --- a/js/src/modals/Verification/verification.js +++ b/js/src/modals/Verification/verification.js @@ -173,7 +173,7 @@ export default class Verification extends Component { const { step, - fee, number, isNumberValid, isVerified, hasRequested, + fee, isVerified, hasRequested, requestTx, isCodeValid, confirmationTx, setCode } = this.props.store; @@ -187,7 +187,7 @@ export default class Verification extends Component { case 2: const { setConsentGiven } = this.props.store; - const fields = [] + const fields = []; if (method === 'sms') { fields.push({ key: 'number', From 5418c56b019c934ac850577b0f70aa189b26b240 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Thu, 8 Dec 2016 12:11:17 +0100 Subject: [PATCH 162/295] remove Prepare step The modal got really crowded and the preparation step had only been shown for fractions of a second anyways. The "loading" message is now part of the next step. --- js/src/modals/Verification/verification.js | 40 ++++++++++------------ 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/js/src/modals/Verification/verification.js b/js/src/modals/Verification/verification.js index cb40d8351..82792a706 100644 --- a/js/src/modals/Verification/verification.js +++ b/js/src/modals/Verification/verification.js @@ -53,12 +53,11 @@ export default class Verification extends Component { } static phases = { // mapping (store steps -> steps) - [LOADING]: 1, - [QUERY_DATA]: 2, - [POSTING_REQUEST]: 3, [POSTED_REQUEST]: 3, [REQUESTING_CODE]: 3, - [QUERY_CODE]: 4, - [POSTING_CONFIRMATION]: 5, [POSTED_CONFIRMATION]: 5, - [DONE]: 6 + [LOADING]: 1, [QUERY_DATA]: 1, + [POSTING_REQUEST]: 2, [POSTED_REQUEST]: 2, [REQUESTING_CODE]: 2, + [QUERY_CODE]: 3, + [POSTING_CONFIRMATION]: 4, [POSTED_CONFIRMATION]: 4, + [DONE]: 5 } state = { @@ -81,8 +80,8 @@ export default class Verification extends Component { title='verify your account' visible current={ phase } - steps={ ['Method', 'Prepare', 'Enter Data', 'Request', 'Enter Code', 'Confirm', 'Done!'] } - waiting={ error ? [] : [ 1, 3, 5 ] } + steps={ ['Method', 'Enter Data', 'Request', 'Enter Code', 'Confirm', 'Done!'] } + waiting={ error ? [] : [ 2, 4 ] } > { this.renderStep(phase, error) } @@ -103,7 +102,7 @@ export default class Verification extends Component { return (
    { cancel }
    ); } - if (phase === 6) { + if (phase === 5) { return (
    { cancel } @@ -126,16 +125,16 @@ export default class Verification extends Component { onSelectMethod(method); }; break; - case 2: + case 1: action = store.sendRequest; break; - case 3: + case 2: action = store.queryCode; break; - case 4: + case 3: action = store.sendConfirmation; break; - case 5: + case 4: action = store.done; break; } @@ -180,11 +179,10 @@ export default class Verification extends Component { switch (phase) { case 1: - return ( -

    Loading Verification.

    - ); + if (step === LOADING) { + return (

    Loading verification data.

    ); + } - case 2: const { setConsentGiven } = this.props.store; const fields = []; @@ -214,12 +212,12 @@ export default class Verification extends Component { /> ); - case 3: + case 2: return ( ); - case 4: + case 3: let receiver, hint; if (method === 'sms') { receiver = this.props.store.number; @@ -237,12 +235,12 @@ export default class Verification extends Component { /> ); - case 5: + case 4: return ( ); - case 6: + case 5: return ( ); From 8596134c0f701d9fec07ccde38373b13467e1819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 9 Dec 2016 08:31:58 +0000 Subject: [PATCH 163/295] Clearer updates handling --- ethcore/src/account_provider/mod.rs | 29 +++++++++++-------- ethstore/src/dir/disk.rs | 5 ++++ ethstore/src/dir/geth.rs | 4 +++ ethstore/src/dir/mod.rs | 1 + ethstore/src/dir/parity.rs | 4 +++ ethstore/src/ethstore.rs | 42 ++++++++++++++++++---------- ethstore/tests/util/transient_dir.rs | 4 +++ 7 files changed, 63 insertions(+), 26 deletions(-) diff --git a/ethcore/src/account_provider/mod.rs b/ethcore/src/account_provider/mod.rs index 7e0005c7a..bc7ffdfed 100644 --- a/ethcore/src/account_provider/mod.rs +++ b/ethcore/src/account_provider/mod.rs @@ -23,7 +23,7 @@ use self::stores::{AddressBook, DappsSettingsStore}; use std::fmt; use std::collections::HashMap; use std::time::{Instant, Duration}; -use util::{Mutex, RwLock, Itertools}; +use util::{RwLock, Itertools}; use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, SafeAccount, EthStore, EthMultiStore, random_string}; use ethstore::dir::{KeyDirectory}; use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator}; @@ -83,7 +83,7 @@ impl KeyDirectory for NullDir { Ok(self.accounts.read().values().cloned().flatten().collect()) } - fn insert(&self, account: SafeAccount) -> Result { + fn update(&self, account: SafeAccount) -> Result { let mut lock = self.accounts.write(); let mut accounts = lock.entry(account.address.clone()).or_insert_with(Vec::new); // If the filename is the same we just need to replace the entry @@ -92,6 +92,13 @@ impl KeyDirectory for NullDir { Ok(account) } + fn insert(&self, account: SafeAccount) -> Result { + let mut lock = self.accounts.write(); + let mut accounts = lock.entry(account.address.clone()).or_insert_with(Vec::new); + accounts.push(account.clone()); + Ok(account) + } + fn remove(&self, account: &SafeAccount) -> Result<(), SSError> { let mut accounts = self.accounts.write(); let is_empty = if let Some(mut accounts) = accounts.get_mut(&account.address) { @@ -121,8 +128,8 @@ type AccountToken = String; /// Account management. /// Responsible for unlocking accounts. pub struct AccountProvider { - address_book: Mutex, - unlocked: Mutex>, + unlocked: RwLock>, + address_book: RwLock, dapps_settings: RwLock, /// Accounts on disk sstore: Box, @@ -134,7 +141,7 @@ impl AccountProvider { /// Creates new account provider. pub fn new(sstore: Box) -> Self { AccountProvider { - unlocked: Mutex::new(HashMap::new()), + unlocked: RwLock::new(HashMap::new()), address_book: RwLock::new(AddressBook::new(sstore.local_path().into())), dapps_settings: RwLock::new(DappsSettingsStore::new(sstore.local_path().into())), sstore: sstore, @@ -145,8 +152,8 @@ impl AccountProvider { /// Creates not disk backed provider. pub fn transient_provider() -> Self { AccountProvider { - address_book: Mutex::new(AddressBook::transient()), - unlocked: Mutex::new(HashMap::new()), + unlocked: RwLock::new(HashMap::new()), + address_book: RwLock::new(AddressBook::transient()), dapps_settings: RwLock::new(DappsSettingsStore::transient()), sstore: Box::new(EthStore::open(Box::new(NullDir::default())).expect("NullDir load always succeeds; qed")), transient_sstore: transient_sstore(), @@ -278,7 +285,7 @@ impl AccountProvider { let _ = try!(self.sstore.sign(&account, &password, &Default::default())); // check if account is already unlocked pernamently, if it is, do nothing - let mut unlocked = self.unlocked.lock(); + let mut unlocked = self.unlocked.write(); if let Some(data) = unlocked.get(&account) { if let Unlock::Perm = data.unlock { return Ok(()) @@ -295,7 +302,7 @@ impl AccountProvider { } fn password(&self, account: &Address) -> Result { - let mut unlocked = self.unlocked.lock(); + let mut unlocked = self.unlocked.write(); let data = try!(unlocked.get(account).ok_or(Error::NotUnlocked)).clone(); if let Unlock::Temp = data.unlock { unlocked.remove(account).expect("data exists: so key must exist: qed"); @@ -326,7 +333,7 @@ impl AccountProvider { /// Checks if given account is unlocked pub fn is_unlocked(&self, account: Address) -> bool { - let unlocked = self.unlocked.lock(); + let unlocked = self.unlocked.read(); unlocked.get(&account).is_some() } @@ -434,7 +441,7 @@ mod tests { assert!(ap.unlock_account_timed(kp.address(), "test1".into(), 60000).is_err()); assert!(ap.unlock_account_timed(kp.address(), "test".into(), 60000).is_ok()); assert!(ap.sign(kp.address(), None, Default::default()).is_ok()); - ap.unlocked.lock().get_mut(&kp.address()).unwrap().unlock = Unlock::Timed(Instant::now()); + ap.unlocked.write().get_mut(&kp.address()).unwrap().unlock = Unlock::Timed(Instant::now()); assert!(ap.sign(kp.address(), None, Default::default()).is_err()); } diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index 22093171e..8c1aeb48f 100644 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -105,6 +105,11 @@ impl KeyDirectory for DiskDirectory { Ok(accounts) } + fn update(&self, account: SafeAccount) -> Result { + // Disk store handles updates correctly iff filename is the same + self.insert(account) + } + fn insert(&self, account: SafeAccount) -> Result { // transform account into key file let keyfile: json::KeyFile = account.clone().into(); diff --git a/ethstore/src/dir/geth.rs b/ethstore/src/dir/geth.rs index a5367f98d..60537f045 100644 --- a/ethstore/src/dir/geth.rs +++ b/ethstore/src/dir/geth.rs @@ -88,6 +88,10 @@ impl KeyDirectory for GethDirectory { self.dir.insert(account) } + fn update(&self, account: SafeAccount) -> Result { + self.dir.update(account) + } + fn remove(&self, account: &SafeAccount) -> Result<(), Error> { self.dir.remove(account) } diff --git a/ethstore/src/dir/mod.rs b/ethstore/src/dir/mod.rs index 0da4d71fb..e2c6c1117 100644 --- a/ethstore/src/dir/mod.rs +++ b/ethstore/src/dir/mod.rs @@ -29,6 +29,7 @@ pub enum DirectoryType { pub trait KeyDirectory: Send + Sync { fn load(&self) -> Result, Error>; fn insert(&self, account: SafeAccount) -> Result; + fn update(&self, account: SafeAccount) -> Result; fn remove(&self, account: &SafeAccount) -> Result<(), Error>; fn path(&self) -> Option<&PathBuf> { None } } diff --git a/ethstore/src/dir/parity.rs b/ethstore/src/dir/parity.rs index c5d0057d8..cc3bb5cd3 100644 --- a/ethstore/src/dir/parity.rs +++ b/ethstore/src/dir/parity.rs @@ -67,6 +67,10 @@ impl KeyDirectory for ParityDirectory { self.dir.insert(account) } + fn update(&self, account: SafeAccount) -> Result { + self.dir.update(account) + } + fn remove(&self, account: &SafeAccount) -> Result<(), Error> { self.dir.remove(account) } diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index 8cbce0f1c..158402a60 100644 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -92,7 +92,7 @@ impl SecretStore for EthStore { let secret = try!(safe_account.crypto.secret(password).map_err(|_| Error::InvalidPassword)); safe_account.address = try!(KeyPair::from_secret(secret)).address(); let address = safe_account.address.clone(); - try!(self.store.save(safe_account)); + try!(self.store.import(safe_account)); Ok(address) } @@ -129,19 +129,21 @@ impl SecretStore for EthStore { } fn set_name(&self, address: &Address, name: String) -> Result<(), Error> { - let mut account = try!(self.get(address)); + let old = try!(self.get(address)); + let mut account = old.clone(); account.name = name; // save to file - self.store.save(account) + self.store.update(old, account) } fn set_meta(&self, address: &Address, meta: String) -> Result<(), Error> { - let mut account = try!(self.get(address)); + let old = try!(self.get(address)); + let mut account = old.clone(); account.meta = meta; // save to file - self.store.save(account) + self.store.update(old, account) } fn local_path(&self) -> String { @@ -213,20 +215,32 @@ impl EthMultiStore { } } - fn save(&self, account: SafeAccount) -> Result<(), Error> { - //save to file + fn import(&self, account: SafeAccount) -> Result<(), Error> { + // save to file let account = try!(self.dir.insert(account)); // update cache let mut cache = self.cache.write(); let mut accounts = cache.entry(account.address.clone()).or_insert_with(Vec::new); - // TODO [ToDr] That is crappy way of overcoming set_name, set_meta, etc. - // Avoid cloning instead! - accounts.retain(|acc| acc.filename != account.filename); accounts.push(account); Ok(()) } + fn update(&self, old: SafeAccount, new: SafeAccount) -> Result<(), Error> { + // save to file + let account = try!(self.dir.update(new)); + + // update cache + let mut cache = self.cache.write(); + let mut accounts = cache.entry(account.address.clone()).or_insert_with(Vec::new); + // Remove old account + accounts.retain(|acc| acc != &old); + // And push updated to the end + accounts.push(account); + Ok(()) + + } + } impl SimpleSecretStore for EthMultiStore { @@ -235,7 +249,7 @@ impl SimpleSecretStore for EthMultiStore { let id: [u8; 16] = Random::random(); let account = SafeAccount::create(&keypair, id, password, self.iterations, "".to_owned(), "{}".to_owned()); let address = account.address.clone(); - try!(self.save(account)); + try!(self.import(account)); Ok(address) } @@ -278,11 +292,9 @@ impl SimpleSecretStore for EthMultiStore { fn change_password(&self, address: &Address, old_password: &str, new_password: &str) -> Result<(), Error> { let accounts = try!(self.get(address)); for account in accounts { - // First remove - try!(self.remove_account(&address, old_password)); - // Then insert back with new password + // Change password let new_account = try!(account.change_password(old_password, new_password, self.iterations)); - try!(self.save(new_account)); + try!(self.update(account, new_account)); } Ok(()) } diff --git a/ethstore/tests/util/transient_dir.rs b/ethstore/tests/util/transient_dir.rs index 76010182e..f01c98063 100644 --- a/ethstore/tests/util/transient_dir.rs +++ b/ethstore/tests/util/transient_dir.rs @@ -63,6 +63,10 @@ impl KeyDirectory for TransientDir { self.dir.load() } + fn update(&self, account: SafeAccount) -> Result { + self.dir.update(account) + } + fn insert(&self, account: SafeAccount) -> Result { self.dir.insert(account) } From 930183831b79e28b50d26cc700f9d0b9fe264ab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 9 Dec 2016 09:45:34 +0000 Subject: [PATCH 164/295] Adding tests for ethstore --- ethcore/src/account_provider/mod.rs | 49 +---------- ethstore/src/dir/memory.rs | 67 ++++++++++++++ ethstore/src/dir/mod.rs | 2 + ethstore/src/ethstore.rs | 130 +++++++++++++++++++++++++--- 4 files changed, 192 insertions(+), 56 deletions(-) create mode 100644 ethstore/src/dir/memory.rs diff --git a/ethcore/src/account_provider/mod.rs b/ethcore/src/account_provider/mod.rs index bc7ffdfed..81e246e8c 100644 --- a/ethcore/src/account_provider/mod.rs +++ b/ethcore/src/account_provider/mod.rs @@ -25,7 +25,7 @@ use std::collections::HashMap; use std::time::{Instant, Duration}; use util::{RwLock, Itertools}; use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, SafeAccount, EthStore, EthMultiStore, random_string}; -use ethstore::dir::{KeyDirectory}; +use ethstore::dir::{KeyDirectory, MemoryDirectory}; use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator}; use ethjson::misc::AccountMeta; pub use ethstore::ethkey::Signature; @@ -73,54 +73,11 @@ impl From for Error { } } -#[derive(Default)] -struct NullDir { - accounts: RwLock>>, -} - -impl KeyDirectory for NullDir { - fn load(&self) -> Result, SSError> { - Ok(self.accounts.read().values().cloned().flatten().collect()) - } - - fn update(&self, account: SafeAccount) -> Result { - let mut lock = self.accounts.write(); - let mut accounts = lock.entry(account.address.clone()).or_insert_with(Vec::new); - // If the filename is the same we just need to replace the entry - accounts.retain(|acc| acc.filename != account.filename); - accounts.push(account.clone()); - Ok(account) - } - - fn insert(&self, account: SafeAccount) -> Result { - let mut lock = self.accounts.write(); - let mut accounts = lock.entry(account.address.clone()).or_insert_with(Vec::new); - accounts.push(account.clone()); - Ok(account) - } - - fn remove(&self, account: &SafeAccount) -> Result<(), SSError> { - let mut accounts = self.accounts.write(); - let is_empty = if let Some(mut accounts) = accounts.get_mut(&account.address) { - if let Some(position) = accounts.iter().position(|acc| acc == account) { - accounts.remove(position); - } - accounts.is_empty() - } else { - false - }; - if is_empty { - accounts.remove(&account.address); - } - Ok(()) - } -} - /// Dapp identifier pub type DappId = String; fn transient_sstore() -> EthMultiStore { - EthMultiStore::open(Box::new(NullDir::default())).expect("NullDir load always succeeds; qed") + EthMultiStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory load always succeeds; qed") } type AccountToken = String; @@ -155,7 +112,7 @@ impl AccountProvider { unlocked: RwLock::new(HashMap::new()), address_book: RwLock::new(AddressBook::transient()), dapps_settings: RwLock::new(DappsSettingsStore::transient()), - sstore: Box::new(EthStore::open(Box::new(NullDir::default())).expect("NullDir load always succeeds; qed")), + sstore: Box::new(EthStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory load always succeeds; qed")), transient_sstore: transient_sstore(), } } diff --git a/ethstore/src/dir/memory.rs b/ethstore/src/dir/memory.rs new file mode 100644 index 000000000..c4f20f0e9 --- /dev/null +++ b/ethstore/src/dir/memory.rs @@ -0,0 +1,67 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +use std::collections::HashMap; +use parking_lot::RwLock; +use itertools::Itertools; +use ethkey::Address; + +use {SafeAccount, Error}; +use super::KeyDirectory; + +#[derive(Default)] +pub struct MemoryDirectory { + accounts: RwLock>>, +} + +impl KeyDirectory for MemoryDirectory { + fn load(&self) -> Result, Error> { + Ok(self.accounts.read().values().cloned().flatten().collect()) + } + + fn update(&self, account: SafeAccount) -> Result { + let mut lock = self.accounts.write(); + let mut accounts = lock.entry(account.address.clone()).or_insert_with(Vec::new); + // If the filename is the same we just need to replace the entry + accounts.retain(|acc| acc.filename != account.filename); + accounts.push(account.clone()); + Ok(account) + } + + fn insert(&self, account: SafeAccount) -> Result { + let mut lock = self.accounts.write(); + let mut accounts = lock.entry(account.address.clone()).or_insert_with(Vec::new); + accounts.push(account.clone()); + Ok(account) + } + + fn remove(&self, account: &SafeAccount) -> Result<(), Error> { + let mut accounts = self.accounts.write(); + let is_empty = if let Some(mut accounts) = accounts.get_mut(&account.address) { + if let Some(position) = accounts.iter().position(|acc| acc == account) { + accounts.remove(position); + } + accounts.is_empty() + } else { + false + }; + if is_empty { + accounts.remove(&account.address); + } + Ok(()) + } +} + diff --git a/ethstore/src/dir/mod.rs b/ethstore/src/dir/mod.rs index e2c6c1117..2d7b98d58 100644 --- a/ethstore/src/dir/mod.rs +++ b/ethstore/src/dir/mod.rs @@ -19,6 +19,7 @@ use {SafeAccount, Error}; mod disk; mod geth; +mod memory; mod parity; pub enum DirectoryType { @@ -36,4 +37,5 @@ pub trait KeyDirectory: Send + Sync { pub use self::disk::DiskDirectory; pub use self::geth::GethDirectory; +pub use self::memory::MemoryDirectory; pub use self::parity::ParityDirectory; diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index 158402a60..7540edb69 100644 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -16,18 +16,16 @@ use std::collections::BTreeMap; use std::mem; -use ethkey::KeyPair; +use parking_lot::RwLock; + use crypto::KEY_ITERATIONS; use random::Random; -use ethkey::{Signature, Address, Message, Secret, Public}; +use ethkey::{Signature, Address, Message, Secret, Public, KeyPair}; use dir::KeyDirectory; use account::SafeAccount; -use {Error, SimpleSecretStore, SecretStore}; -use json; -use json::UUID; -use parking_lot::RwLock; +use json::{self, UUID}; use presale::PresaleWallet; -use import; +use {import, Error, SimpleSecretStore, SecretStore}; pub struct EthStore { store: EthMultiStore, @@ -323,8 +321,120 @@ impl SimpleSecretStore for EthMultiStore { #[cfg(test)] mod tests { - #[test] - fn should_have_some_tests() { - assert_eq!(true, false) + + use dir::MemoryDirectory; + use ethkey::{Random, Generator, KeyPair}; + use secret_store::{SimpleSecretStore, SecretStore}; + use super::{EthStore, EthMultiStore}; + + fn keypair() -> KeyPair { + Random.generate().unwrap() } + + fn store() -> EthStore { + EthStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory always load successfuly; qed") + } + + fn multi_store() -> EthMultiStore { + EthMultiStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory always load successfuly; qed") + } + + #[test] + fn should_insert_account_successfully() { + // given + let store = store(); + let keypair = keypair(); + + // when + let address = store.insert_account(keypair.secret().clone(), "test").unwrap(); + + // then + assert_eq!(address, keypair.address()); + assert!(store.get(&address).is_ok(), "Should contain account."); + assert_eq!(store.accounts().unwrap().len(), 1, "Should have one account."); + } + + #[test] + fn should_update_meta_and_name() { + // given + let store = store(); + let keypair = keypair(); + let address = store.insert_account(keypair.secret().clone(), "test").unwrap(); + assert_eq!(&store.meta(&address).unwrap(), "{}"); + assert_eq!(&store.name(&address).unwrap(), ""); + + // when + store.set_meta(&address, "meta".into()).unwrap(); + store.set_name(&address, "name".into()).unwrap(); + + // then + assert_eq!(&store.meta(&address).unwrap(), "meta"); + assert_eq!(&store.name(&address).unwrap(), "name"); + assert_eq!(store.accounts().unwrap().len(), 1); + } + + #[test] + fn should_remove_account() { + // given + let store = store(); + let keypair = keypair(); + let address = store.insert_account(keypair.secret().clone(), "test").unwrap(); + + // when + store.remove_account(&address, "test").unwrap(); + + // then + assert_eq!(store.accounts().unwrap().len(), 0, "Should remove account."); + } + + #[test] + fn should_return_true_if_password_is_correct() { + // given + let store = store(); + let keypair = keypair(); + let address = store.insert_account(keypair.secret().clone(), "test").unwrap(); + + // when + let res1 = store.test_password(&address, "x").unwrap(); + let res2 = store.test_password(&address, "test").unwrap(); + + assert!(!res1, "First password should be invalid."); + assert!(res2, "Second password should be correct."); + } + + #[test] + fn multistore_should_be_able_to_have_the_same_account_twice() { + // given + let store = multi_store(); + let keypair = keypair(); + let address = store.insert_account(keypair.secret().clone(), "test").unwrap(); + let address2 = store.insert_account(keypair.secret().clone(), "xyz").unwrap(); + assert_eq!(address, address2); + + // when + assert!(store.remove_account(&address, "test").is_ok(), "First password should work."); + assert_eq!(store.accounts().unwrap().len(), 1); + + assert!(store.remove_account(&address, "xyz").is_ok(), "Second password should work too."); + assert_eq!(store.accounts().unwrap().len(), 0); + } + + #[test] + fn should_copy_account() { + // given + let store = store(); + let multi_store = multi_store(); + let keypair = keypair(); + let address = store.insert_account(keypair.secret().clone(), "test").unwrap(); + assert_eq!(multi_store.accounts().unwrap().len(), 0); + + // when + store.copy_account(&multi_store, &address, "test", "xyz").unwrap(); + + // then + assert!(store.test_password(&address, "test").unwrap(), "First password should work for store."); + assert!(multi_store.sign(&address, "xyz", &Default::default()).is_ok(), "Second password should work for second store."); + assert_eq!(multi_store.accounts().unwrap().len(), 1); + } + } From 42c34b5c1b83960495d1cf545a735fa752878904 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 9 Dec 2016 10:53:38 +0100 Subject: [PATCH 165/295] ignore flaky test --- ethcore/src/engines/tendermint/message.rs | 6 +++--- ethcore/src/engines/tendermint/mod.rs | 11 ++++++----- ethcore/src/engines/tendermint/params.rs | 6 +++--- ethcore/src/engines/tendermint/transition.rs | 4 ++-- ethcore/src/engines/tendermint/vote_collector.rs | 4 ++-- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs index 5eae139d5..5b88a66ca 100644 --- a/ethcore/src/engines/tendermint/message.rs +++ b/ethcore/src/engines/tendermint/message.rs @@ -29,7 +29,7 @@ pub struct ConsensusMessage { pub height: Height, pub round: Round, pub step: Step, - pub block_hash: Option + pub block_hash: Option, } @@ -45,7 +45,7 @@ impl ConsensusMessage { height: height, round: round, step: step, - block_hash: block_hash + block_hash: block_hash, } } @@ -55,7 +55,7 @@ impl ConsensusMessage { height: header.number() as Height, round: try!(consensus_round(header)), step: Step::Propose, - block_hash: Some(header.bare_hash()) + block_hash: Some(header.bare_hash()), }) } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index bbcf24750..ec6cff37b 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -16,9 +16,10 @@ /// Tendermint BFT consensus engine with round robin proof-of-authority. /// At each blockchain `Height` there can be multiple `Round`s of voting. -/// Block is issued when there is enough `Precommit` votes collected on a particular block at the end of a `Round`. /// Signatures always sign `Height`, `Round`, `Step` and `BlockHash` which is a block hash without seal. /// First a block with `Seal::Proposal` is issued by the designated proposer. +/// Next the `Round` proceeds through `Prevote` and `Precommit` `Step`s. +/// Block is issued when there is enough `Precommit` votes collected on a particular block at the end of a `Round`. /// Once enough votes have been gathered the proposer issues that block in the `Commit` step. mod message; @@ -97,7 +98,7 @@ pub struct Tendermint { /// Last lock round. last_lock: AtomicUsize, /// Bare hash of the proposed block, used for seal submission. - proposal: RwLock> + proposal: RwLock>, } impl Tendermint { @@ -119,7 +120,7 @@ impl Tendermint { account_provider: Mutex::new(None), lock_change: RwLock::new(None), last_lock: AtomicUsize::new(0), - proposal: RwLock::new(None) + proposal: RwLock::new(None), }); let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; try!(engine.step_service.register_handler(Arc::new(handler))); @@ -390,7 +391,7 @@ impl Engine for Tendermint { } fn schedule(&self, _env_info: &EnvInfo) -> Schedule { - Schedule::new_homestead() + Schedule::new_post_eip150(usize::max_value(), true, true, true) } fn populate_from_parent(&self, header: &mut Header, parent: &Header, gas_floor_target: U256, _gas_ceil_target: U256) { @@ -828,8 +829,8 @@ mod tests { } #[test] + #[ignore] fn step_transitioning() { - //::env_logger::init().unwrap(); let (spec, tap) = setup(); let engine = spec.engine.clone(); let mut db_result = get_temp_state_db(); diff --git a/ethcore/src/engines/tendermint/params.rs b/ethcore/src/engines/tendermint/params.rs index 3752ae3bd..cf723713b 100644 --- a/ethcore/src/engines/tendermint/params.rs +++ b/ethcore/src/engines/tendermint/params.rs @@ -42,7 +42,7 @@ impl Default for TendermintParams { gas_limit_bound_divisor: 0x0400.into(), authorities: authorities, authority_n: val_n, - timeouts: TendermintTimeouts::default() + timeouts: TendermintTimeouts::default(), } } } @@ -65,8 +65,8 @@ impl From for TendermintParams { propose: p.timeout_propose.map_or(dt.propose, to_duration), prevote: p.timeout_prevote.map_or(dt.prevote, to_duration), precommit: p.timeout_precommit.map_or(dt.precommit, to_duration), - commit: p.timeout_commit.map_or(dt.commit, to_duration) - } + commit: p.timeout_commit.map_or(dt.commit, to_duration), + }, } } } diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index 35f56ac87..4c54714a5 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -31,7 +31,7 @@ pub struct TendermintTimeouts { pub propose: Duration, pub prevote: Duration, pub precommit: Duration, - pub commit: Duration + pub commit: Duration, } impl TendermintTimeouts { @@ -51,7 +51,7 @@ impl Default for TendermintTimeouts { propose: Duration::milliseconds(2000), prevote: Duration::milliseconds(2000), precommit: Duration::milliseconds(2000), - commit: Duration::milliseconds(2000) + commit: Duration::milliseconds(2000), } } } diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index 9df2574ec..e2f642114 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -23,13 +23,13 @@ use super::{Height, Round, Step}; #[derive(Debug)] pub struct VoteCollector { /// Storing all Proposals, Prevotes and Precommits. - votes: RwLock> + votes: RwLock>, } #[derive(Debug)] pub struct SealSignatures { pub proposal: H520, - pub votes: Vec + pub votes: Vec, } impl PartialEq for SealSignatures { From fb71caffbd018019463d258b9a0dd3f6ac3915da Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 9 Dec 2016 11:05:44 +0100 Subject: [PATCH 166/295] remove double registration --- parity/run.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/parity/run.rs b/parity/run.rs index 12df6b22c..cf39b5528 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -215,9 +215,6 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { // let the Engine access the accounts spec.engine.register_account_provider(account_provider.clone()); - // let the Engine access the accounts - spec.engine.register_account_provider(account_provider.clone()); - // create miner let miner = Miner::new(cmd.miner_options, cmd.gas_pricer.into(), &spec, Some(account_provider.clone())); miner.set_author(cmd.miner_extras.author); From c76b7cf8f8c2cae18e9b15d0a991758140334d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 9 Dec 2016 10:48:46 +0000 Subject: [PATCH 167/295] Fixing tests submodule --- ethcore/res/ethereum/tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/res/ethereum/tests b/ethcore/res/ethereum/tests index d509c7593..e8f4624b7 160000 --- a/ethcore/res/ethereum/tests +++ b/ethcore/res/ethereum/tests @@ -1 +1 @@ -Subproject commit d509c75936ec6cbba683ee1916aa0bca436bc376 +Subproject commit e8f4624b7f1a15c63674eecf577c7ab76c3b16be From 3ccdb7c143cd73c3f051b8a112a443f61032eb68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 9 Dec 2016 10:52:42 +0000 Subject: [PATCH 168/295] Fixing unused imports --- ethcore/src/account_provider/mod.rs | 7 ++++--- rpc/src/v1/types/confirmations.rs | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ethcore/src/account_provider/mod.rs b/ethcore/src/account_provider/mod.rs index 81e246e8c..47a77bc0e 100644 --- a/ethcore/src/account_provider/mod.rs +++ b/ethcore/src/account_provider/mod.rs @@ -23,9 +23,9 @@ use self::stores::{AddressBook, DappsSettingsStore}; use std::fmt; use std::collections::HashMap; use std::time::{Instant, Duration}; -use util::{RwLock, Itertools}; -use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, SafeAccount, EthStore, EthMultiStore, random_string}; -use ethstore::dir::{KeyDirectory, MemoryDirectory}; +use util::RwLock; +use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore, random_string}; +use ethstore::dir::MemoryDirectory; use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator}; use ethjson::misc::AccountMeta; pub use ethstore::ethkey::Signature; @@ -418,6 +418,7 @@ mod tests { assert!(ap.sign_with_token(kp.address(), token, Default::default()).is_err(), "Second usage of the same token should fail."); } + #[test] fn should_set_dapps_addresses() { // given let ap = AccountProvider::transient_provider(); diff --git a/rpc/src/v1/types/confirmations.rs b/rpc/src/v1/types/confirmations.rs index a897af35e..5450d0a9f 100644 --- a/rpc/src/v1/types/confirmations.rs +++ b/rpc/src/v1/types/confirmations.rs @@ -192,7 +192,7 @@ impl Serialize for Either where mod tests { use std::str::FromStr; use serde_json; - use v1::types::U256; + use v1::types::{U256, H256}; use v1::helpers; use super::*; From 762e5f1e3e2474ab67be8a6a501c34e3177bb78d Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 9 Dec 2016 12:05:56 +0100 Subject: [PATCH 169/295] proposed block sync tests --- sync/src/chain.rs | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/sync/src/chain.rs b/sync/src/chain.rs index ee3a4da3e..c3d0434b8 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -2287,8 +2287,27 @@ mod tests { let io = TestIo::new(&mut client, &ss, &mut queue, None); let lagging_peers = sync.get_peers(&chain_info, &io, PeerStatus::Lagging); + let current_peers = sync.get_peers(&chain_info, &io, PeerStatus::Current); - assert_eq!(1, lagging_peers.len()) + assert_eq!(1, lagging_peers.len()); + assert!(current_peers.is_empty()); + } + + #[test] + fn finds_current_peers() { + let mut client = TestBlockChainClient::new(); + client.add_blocks(100, EachBlockWith::Uncle); + let mut queue = VecDeque::new(); + let mut sync = dummy_sync_with_peer(client.block_hash(BlockID::Latest).unwrap(), &client); + let chain_info = client.chain_info(); + let ss = TestSnapshotService::new(); + let io = TestIo::new(&mut client, &ss, &mut queue, None); + + let current_peers = sync.get_peers(&chain_info, &io, PeerStatus::Current); + let lagging_peers = sync.get_peers(&chain_info, &io, PeerStatus::Lagging); + + assert_eq!(1, current_peers.len()); + assert!(lagging_peers.is_empty()); } #[test] @@ -2370,6 +2389,23 @@ mod tests { assert_eq!(0x07, io.queue[0].packet_id); } + #[test] + fn sends_proposed_block() { + let mut client = TestBlockChainClient::new(); + client.add_blocks(100, EachBlockWith::Uncle); + let mut queue = VecDeque::new(); + let block = client.block(BlockID::Latest).unwrap(); + let mut sync = dummy_sync_with_peer(client.block_hash(BlockID::Latest).unwrap(), &client); + let ss = TestSnapshotService::new(); + let mut io = TestIo::new(&mut client, &ss, &mut queue, None); + sync.propagate_proposed_blocks(&mut io, &[block]); + + // 1 message should be send + assert_eq!(1, io.queue.len()); + // NEW_BLOCK_PACKET + assert_eq!(0x07, io.queue[0].packet_id); + } + #[test] fn propagates_transactions() { let mut client = TestBlockChainClient::new(); From a94bbea7a578a7ddc4adaf4342533a039793d6ca Mon Sep 17 00:00:00 2001 From: Jannis R Date: Fri, 9 Dec 2016 12:37:36 +0100 Subject: [PATCH 170/295] more user-friendly method selection --- js/src/modals/Verification/verification.css | 21 +++++++++++++++++++++ js/src/modals/Verification/verification.js | 12 ++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 js/src/modals/Verification/verification.css diff --git a/js/src/modals/Verification/verification.css b/js/src/modals/Verification/verification.css new file mode 100644 index 000000000..f1a39ac4f --- /dev/null +++ b/js/src/modals/Verification/verification.css @@ -0,0 +1,21 @@ +/* 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 . +*/ + +.noSpacing { + margin-top: 0; + margin-bottom: 0; +} diff --git a/js/src/modals/Verification/verification.js b/js/src/modals/Verification/verification.js index 82792a706..af9a3a45e 100644 --- a/js/src/modals/Verification/verification.js +++ b/js/src/modals/Verification/verification.js @@ -23,9 +23,17 @@ import { Button, IdentityIcon, Modal } from '~/ui'; import RadioButtons from '~/ui/Form/RadioButtons'; import { nullableProptype } from '~/util/proptypes'; +import styles from './verification.css'; + const methods = { - sms: { label: 'SMS Verification', key: 0, value: 'sms' }, - email: { label: 'E-mail Verification', key: 1, value: 'email' } + sms: { + label: 'SMS Verification', key: 0, value: 'sms', + description: (

    It will be stored on the blockchain that you control a phone number (not which).

    ) + }, + email: { + label: 'E-mail Verification', key: 1, value: 'email', + description: (

    The hash of the e-mail address you prove control over will be stored on the blockchain.

    ) + } }; import { From 56e9dab9de54a08d44b2a7b68529aabfc8076f90 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 9 Dec 2016 14:52:08 +0100 Subject: [PATCH 171/295] add Engine stop method --- ethcore/src/client/client.rs | 6 ++++++ ethcore/src/engines/mod.rs | 3 +++ ethcore/src/engines/tendermint/mod.rs | 14 +++++++++++--- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 93c666dbf..17d741b0f 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1445,6 +1445,12 @@ impl ProvingBlockChainClient for Client { } } +impl Drop for Client { + fn drop(&mut self) { + self.engine.stop(); + } +} + #[cfg(test)] mod tests { diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 31ce746fc..db53b551d 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -207,6 +207,9 @@ pub trait Engine : Sync + Send { /// Register an account which signs consensus messages. fn set_signer(&self, _address: Address, _password: String) {} + /// Stops any services that the may hold the Engine and makes it safe to drop. + fn stop(&self) {} + /// Add a channel for communication with Client which can be used for sealing. fn register_message_channel(&self, _message_channel: IoChannel) {} diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index ec6cff37b..0b7c09d2f 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -191,6 +191,7 @@ impl Tendermint { } } + /// Broadcast all messages since last issued block to get the peers up to speed. fn broadcast_old_messages(&self) { for m in self.votes.get_up_to(self.height.load(AtomicOrdering::SeqCst)).into_iter() { self.broadcast_message(m); @@ -561,6 +562,10 @@ impl Engine for Tendermint { } } + fn stop(&self) { + self.step_service.stop() + } + fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { let new_number = new_header.number(); let best_number = best_header.number(); @@ -741,7 +746,6 @@ mod tests { } #[test] - #[ignore] fn allows_correct_proposer() { let (spec, tap) = setup(); let engine = spec.engine; @@ -772,7 +776,8 @@ mod tests { match engine.verify_block_unordered(&header, None) { Err(Error::Engine(EngineError::NotAuthorized(_))) => {}, _ => panic!(), - } + }; + engine.stop(); } #[test] @@ -815,7 +820,8 @@ mod tests { match engine.verify_block_unordered(&header, None) { Err(Error::Engine(EngineError::NotAuthorized(_))) => {}, _ => panic!(), - } + }; + engine.stop(); } #[test] @@ -826,6 +832,7 @@ mod tests { let (b, seal) = propose_default(&spec, proposer); assert!(b.try_seal(spec.engine.as_ref(), seal).is_ok()); + spec.engine.stop(); } #[test] @@ -860,6 +867,7 @@ mod tests { vote(&engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Precommit, proposal); vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal); + engine.stop(); // Wait a bit for async stuff. ::std::thread::sleep(::std::time::Duration::from_millis(100)); seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v0, v1); From 5d054f08c33df9ec6d17664f3529cc06758ab625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 9 Dec 2016 15:05:03 +0100 Subject: [PATCH 172/295] Clearing old transactions --- ethcore/src/miner/transaction_queue.rs | 41 +++++++++++++++++++++++++- util/table/src/lib.rs | 6 ++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index cd2d3ba47..84981b893 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -81,6 +81,8 @@ //! 3. `remove_all` is used to inform the queue about client (state) nonce changes. //! - It removes all transactions (either from `current` or `future`) with nonce < client nonce //! - It moves matching `future` transactions to `current` +//! 4. `remove_old` is used periodically to clear the transactions if node goes out of sync +//! (in such case `remove_all` is not invoked because we are syncing the chain) use std::ops::Deref; use std::cmp::Ordering; @@ -765,6 +767,20 @@ impl TransactionQueue { assert_eq!(self.future.by_priority.len() + self.current.by_priority.len(), self.by_hash.len()); } + /// Checks the current nonce for all transactions' senders in the queue and removes the old transactions. + pub fn remove_old(&mut self, fetch_account: &F) where + F: Fn(&Address) -> AccountDetails, + { + let senders = self.current.by_address + .keys() + .map(|key| (*key, fetch_account(key).nonce)) + .collect::>(); + + for (sender, nonce) in senders { + self.remove_all(sender, nonce); + } + } + /// Penalize transactions from sender of transaction with given hash. /// I.e. it should change the priority of the transaction in the queue. /// @@ -2438,7 +2454,7 @@ mod test { } #[test] - fn should_reject_transactions_below_bas_gas() { + fn should_reject_transactions_below_base_gas() { // given let mut txq = TransactionQueue::default(); let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); @@ -2457,4 +2473,27 @@ mod test { } + #[test] + fn should_clear_all_old_transactions() { + // given + let mut txq = TransactionQueue::default(); + let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); + let (tx3, tx4) = new_tx_pair_default(1.into(), 0.into()); + let nonce1 = tx1.nonce; + let new_details = |_a: &Address| AccountDetails { nonce: nonce1 + U256::one(), balance: !U256::zero() }; + + // Insert all transactions + txq.add(tx1, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx3, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx4, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + assert_eq!(txq.top_transactions().len(), 4); + + // when + txq.remove_old(&new_details); + + // then + assert_eq!(txq.top_transactions().len(), 2); + } + } diff --git a/util/table/src/lib.rs b/util/table/src/lib.rs index a13beab11..f65c1e171 100644 --- a/util/table/src/lib.rs +++ b/util/table/src/lib.rs @@ -18,6 +18,7 @@ use std::hash::Hash; use std::collections::HashMap; +use std::collections::hash_map::Keys; /// Structure to hold double-indexed values /// @@ -41,6 +42,11 @@ impl Table } } + /// Returns keys iterator for this Table. + pub fn keys(&self) -> Keys> { + self.map.keys() + } + /// Removes all elements from this Table pub fn clear(&mut self) { self.map.clear(); From cee07fef747cb5146a170e066aeb2ca2b5cc129e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 9 Dec 2016 15:54:13 +0100 Subject: [PATCH 173/295] Trigger remove_old on new block --- ethcore/src/miner/miner.rs | 60 +++++++++----------------- ethcore/src/miner/transaction_queue.rs | 42 ++++++++++++------ 2 files changed, 48 insertions(+), 54 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 8d1f55567..cebd35b09 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -1083,20 +1083,6 @@ impl MinerService for Miner { fn chain_new_blocks(&self, chain: &MiningBlockChainClient, _imported: &[H256], _invalid: &[H256], enacted: &[H256], retracted: &[H256]) { trace!(target: "miner", "chain_new_blocks"); - fn fetch_transactions(chain: &MiningBlockChainClient, hash: &H256) -> Vec { - let block = chain - .block(BlockID::Hash(*hash)) - // Client should send message after commit to db and inserting to chain. - .expect("Expected in-chain blocks."); - let block = BlockView::new(&block); - let txs = block.transactions(); - // populate sender - for tx in &txs { - let _sender = tx.sender(); - } - txs - } - // 1. We ignore blocks that were `imported` (because it means that they are not in canon-chain, and transactions // should be still available in the queue. // 2. We ignore blocks that are `invalid` because it doesn't have any meaning in terms of the transactions that @@ -1107,35 +1093,29 @@ impl MinerService for Miner { // Then import all transactions... { - let out_of_chain = retracted - .par_iter() - .map(|h| fetch_transactions(chain, h)); - out_of_chain.for_each(|txs| { - let mut transaction_queue = self.transaction_queue.lock(); - let _ = self.add_transactions_to_queue( - chain, txs, TransactionOrigin::RetractedBlock, &mut transaction_queue - ); - }); + retracted.par_iter() + .map(|hash| { + let block = chain.block(BlockID::Hash(*hash)) + .expect("Client is sending message after commit to db and inserting to chain; the block is available; qed"); + let block = BlockView::new(&block); + let txs = block.transactions(); + // populate sender + for tx in &txs { + let _sender = tx.sender(); + } + txs + }).for_each(|txs| { + let mut transaction_queue = self.transaction_queue.lock(); + let _ = self.add_transactions_to_queue( + chain, txs, TransactionOrigin::RetractedBlock, &mut transaction_queue + ); + }); } - // ...and at the end remove old ones + // ...and at the end remove the old ones { - let in_chain = enacted - .par_iter() - .map(|h: &H256| fetch_transactions(chain, h)); - - in_chain.for_each(|mut txs| { - let mut transaction_queue = self.transaction_queue.lock(); - - let to_remove = txs.drain(..) - .map(|tx| { - tx.sender().expect("Transaction is in block, so sender has to be defined.") - }) - .collect::>(); - for sender in to_remove { - transaction_queue.remove_all(sender, chain.latest_nonce(&sender)); - } - }); + let mut transaction_queue = self.transaction_queue.lock(); + transaction_queue.remove_old(|sender| chain.latest_nonce(sender)); } if enacted.len() > 0 { diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 84981b893..03a9da8e3 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -79,10 +79,10 @@ //! we check if the transactions should go to `current` (comparing state nonce) //! - When it's removed from `current` - all transactions from this sender (`current` & `future`) are recalculated. //! 3. `remove_all` is used to inform the queue about client (state) nonce changes. -//! - It removes all transactions (either from `current` or `future`) with nonce < client nonce -//! - It moves matching `future` transactions to `current` -//! 4. `remove_old` is used periodically to clear the transactions if node goes out of sync -//! (in such case `remove_all` is not invoked because we are syncing the chain) +//! - It removes all transactions (either from `current` or `future`) with nonce < client nonce +//! - It moves matching `future` transactions to `current` +//! 4. `remove_old` is used as convenient method to update the state nonce for all senders in the queue. +//! - Invokes `remove_all` with latest state nonce for all senders. use std::ops::Deref; use std::cmp::Ordering; @@ -754,6 +754,21 @@ impl TransactionQueue { /// Removes all transactions from particular sender up to (excluding) given client (state) nonce. /// Client (State) Nonce = next valid nonce for this sender. pub fn remove_all(&mut self, sender: Address, client_nonce: U256) { + // Check if there is anything in current... + let should_check_in_current = self.current.by_address.row(&sender) + // If nonce == client_nonce nothing is changed + .and_then(|by_nonce| by_nonce.keys().find(|nonce| *nonce < &client_nonce)) + .map(|_| ()); + // ... or future + let should_check_in_future = self.future.by_address.row(&sender) + // if nonce == client_nonce we need to promote to current + .and_then(|by_nonce| by_nonce.keys().find(|nonce| *nonce <= &client_nonce)) + .map(|_| ()); + + if should_check_in_current.or(should_check_in_future).is_none() { + return; + } + // We will either move transaction to future or remove it completely // so there will be no transactions from this sender in current self.last_nonces.remove(&sender); @@ -768,16 +783,16 @@ impl TransactionQueue { } /// Checks the current nonce for all transactions' senders in the queue and removes the old transactions. - pub fn remove_old(&mut self, fetch_account: &F) where - F: Fn(&Address) -> AccountDetails, + pub fn remove_old(&mut self, fetch_nonce: F) where + F: Fn(&Address) -> U256, { - let senders = self.current.by_address - .keys() - .map(|key| (*key, fetch_account(key).nonce)) - .collect::>(); + let senders = self.current.by_address.keys() + .chain(self.future.by_address.keys()) + .cloned() + .collect::>(); - for (sender, nonce) in senders { - self.remove_all(sender, nonce); + for sender in senders { + self.remove_all(sender, fetch_nonce(&sender)); } } @@ -2480,7 +2495,6 @@ mod test { let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); let (tx3, tx4) = new_tx_pair_default(1.into(), 0.into()); let nonce1 = tx1.nonce; - let new_details = |_a: &Address| AccountDetails { nonce: nonce1 + U256::one(), balance: !U256::zero() }; // Insert all transactions txq.add(tx1, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); @@ -2490,7 +2504,7 @@ mod test { assert_eq!(txq.top_transactions().len(), 4); // when - txq.remove_old(&new_details); + txq.remove_old(|_| nonce1 + U256::one()); // then assert_eq!(txq.top_transactions().len(), 2); From 452b8c9c74735834ea4a0301f60305b1bf3eabde Mon Sep 17 00:00:00 2001 From: Jannis R Date: Fri, 9 Dec 2016 16:20:35 +0100 Subject: [PATCH 174/295] split sms & email verification 3rdparty code We might want to bundle the code in `3rdparty`. React & presentational components don't belong in there. At the same time, the terms of service are strictly related to the use of these external services. We decided to not bundle them, but still keep them in a file called `terms-of-service.js`. The commit also moves the "how it works" section into the presentational part in `modals/Verification`. --- js/src/3rdparty/email-verification/index.js | 20 --------- js/src/3rdparty/email-verification/styles.css | 20 --------- .../email-verification/terms-of-service.js | 23 +++++++++++ js/src/3rdparty/sms-verification/index.js | 24 ----------- js/src/3rdparty/sms-verification/styles.css | 20 --------- .../sms-verification/terms-of-service.js | 27 ++++++++++++ js/src/contracts/contracts.js | 1 + .../Verification/GatherData/gatherData.js | 8 ++-- js/src/modals/Verification/how-it-works.js | 41 +++++++++++++++++++ js/src/modals/Verification/verification.css | 4 ++ 10 files changed, 101 insertions(+), 87 deletions(-) delete mode 100644 js/src/3rdparty/email-verification/styles.css create mode 100644 js/src/3rdparty/email-verification/terms-of-service.js delete mode 100644 js/src/3rdparty/sms-verification/styles.css create mode 100644 js/src/3rdparty/sms-verification/terms-of-service.js create mode 100644 js/src/modals/Verification/how-it-works.js diff --git a/js/src/3rdparty/email-verification/index.js b/js/src/3rdparty/email-verification/index.js index 5f4885f3b..5b81f3f95 100644 --- a/js/src/3rdparty/email-verification/index.js +++ b/js/src/3rdparty/email-verification/index.js @@ -15,26 +15,6 @@ // along with Parity. If not, see . import { stringify } from 'querystring'; -import React from 'react'; - -import styles from './styles.css'; - -export const howItWorks = ( -
    -

    The following steps will let you prove that you control both an account and an e-mail address.

    -
      -
    1. You send a verification request to a specific contract.
    2. -
    3. Our server puts a puzzle into this contract.
    4. -
    5. The code you receive via e-mail is the solution to this puzzle.
    6. -
    -
    -); - -export const termsOfService = ( -
      -
    • todo
    • -
    -); export const postToServer = (query, isTestnet = false) => { const port = isTestnet ? 28443 : 18443; diff --git a/js/src/3rdparty/email-verification/styles.css b/js/src/3rdparty/email-verification/styles.css deleted file mode 100644 index daa4c605c..000000000 --- a/js/src/3rdparty/email-verification/styles.css +++ /dev/null @@ -1,20 +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 . -*/ - -.list li { - padding: .1em 0; -} diff --git a/js/src/3rdparty/email-verification/terms-of-service.js b/js/src/3rdparty/email-verification/terms-of-service.js new file mode 100644 index 000000000..263b7e8f0 --- /dev/null +++ b/js/src/3rdparty/email-verification/terms-of-service.js @@ -0,0 +1,23 @@ +// 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 . + +import React from 'react'; + +export default ( +
      +
    • todo
    • +
    +); diff --git a/js/src/3rdparty/sms-verification/index.js b/js/src/3rdparty/sms-verification/index.js index 46faf084c..65761223b 100644 --- a/js/src/3rdparty/sms-verification/index.js +++ b/js/src/3rdparty/sms-verification/index.js @@ -15,30 +15,6 @@ // along with Parity. If not, see . import { stringify } from 'querystring'; -import React from 'react'; - -import styles from './styles.css'; - -export const howItWorks = ( -
    -

    The following steps will let you prove that you control both an account and a phone number.

    -
      -
    1. You send a verification request to a specific contract.
    2. -
    3. Our server puts a puzzle into this contract.
    4. -
    5. The code you receive via SMS is the solution to this puzzle.
    6. -
    -
    -); - -export const termsOfService = ( -
      -
    • This privacy notice relates to your use of the Parity SMS verification service. We take your privacy seriously and deal in an honest, direct and transparent way when it comes to your data.
    • -
    • We collect your phone number when you use this service. This is temporarily kept in memory, and then encrypted and stored in our EU servers. We only retain the cryptographic hash of the number to prevent duplicated accounts. You consent to this use.
    • -
    • You pay a fee for the cost of this service using the account you want to verify.
    • -
    • Your phone number is transmitted to a third party US SMS verification service Twilio for the sole purpose of the SMS verification. You consent to this use. Twilio’s privacy policy is here: https://www.twilio.com/legal/privacy/developer.
    • -
    • Parity Technology Limited is registered in England and Wales under company number 09760015 and complies with the Data Protection Act 1998 (UK). You may contact us via email at admin@parity.io. Our general privacy policy can be found here: https://ethcore.io/legal.html.
    • -
    -); export const postToServer = (query, isTestnet = false) => { const port = isTestnet ? 8443 : 443; diff --git a/js/src/3rdparty/sms-verification/styles.css b/js/src/3rdparty/sms-verification/styles.css deleted file mode 100644 index daa4c605c..000000000 --- a/js/src/3rdparty/sms-verification/styles.css +++ /dev/null @@ -1,20 +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 . -*/ - -.list li { - padding: .1em 0; -} diff --git a/js/src/3rdparty/sms-verification/terms-of-service.js b/js/src/3rdparty/sms-verification/terms-of-service.js new file mode 100644 index 000000000..f61b3c97d --- /dev/null +++ b/js/src/3rdparty/sms-verification/terms-of-service.js @@ -0,0 +1,27 @@ +// 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 . + +import React from 'react'; + +export default ( +
      +
    • This privacy notice relates to your use of the Parity SMS verification service. We take your privacy seriously and deal in an honest, direct and transparent way when it comes to your data.
    • +
    • We collect your phone number when you use this service. This is temporarily kept in memory, and then encrypted and stored in our EU servers. We only retain the cryptographic hash of the number to prevent duplicated accounts. You consent to this use.
    • +
    • You pay a fee for the cost of this service using the account you want to verify.
    • +
    • Your phone number is transmitted to a third party US SMS verification service Twilio for the sole purpose of the SMS verification. You consent to this use. Twilio’s privacy policy is here: https://www.twilio.com/legal/privacy/developer.
    • +
    • Parity Technology Limited is registered in England and Wales under company number 09760015 and complies with the Data Protection Act 1998 (UK). You may contact us via email at admin@parity.io. Our general privacy policy can be found here: https://ethcore.io/legal.html.
    • +
    +); diff --git a/js/src/contracts/contracts.js b/js/src/contracts/contracts.js index f30f67efb..f21ca9803 100644 --- a/js/src/contracts/contracts.js +++ b/js/src/contracts/contracts.js @@ -60,6 +60,7 @@ export default class Contracts { get smsVerification () { return verification; } + get emailVerification () { return verification; } diff --git a/js/src/modals/Verification/GatherData/gatherData.js b/js/src/modals/Verification/GatherData/gatherData.js index 24f870fc6..ecebd4da8 100644 --- a/js/src/modals/Verification/GatherData/gatherData.js +++ b/js/src/modals/Verification/GatherData/gatherData.js @@ -25,8 +25,9 @@ import { fromWei } from '~/api/util/wei'; import { Form, Input } from '~/ui'; import { nullableProptype } from '~/util/proptypes'; -import * as sms from '../../../3rdparty/sms-verification'; -import * as email from '../../../3rdparty/email-verification'; +import smsTermsOfService from '~/3rdparty/sms-verification/terms-of-service'; +import emailTermsOfService from '~/3rdparty/email-verification/terms-of-service'; +import { howSMSVerificationWorks, howEmailVerificationWorks } from '../how-it-works'; import styles from './gatherData.css'; export default class GatherData extends Component { @@ -41,7 +42,8 @@ export default class GatherData extends Component { render () { const { method, isVerified } = this.props; - const { howItWorks, termsOfService } = method === 'email' ? email : sms; + const termsOfService = method === 'email' ? emailTermsOfService : smsTermsOfService; + const howItWorks = method === 'email' ? howEmailVerificationWorks : howSMSVerificationWorks; return ( diff --git a/js/src/modals/Verification/how-it-works.js b/js/src/modals/Verification/how-it-works.js new file mode 100644 index 000000000..6abdc80dd --- /dev/null +++ b/js/src/modals/Verification/how-it-works.js @@ -0,0 +1,41 @@ +// 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 . + +import React from 'react'; + +import styles from './verification.css'; + +export const howSMSVerificationWorks = ( +
    +

    The following steps will let you prove that you control both an account and a phone number.

    +
      +
    1. You send a verification request to a specific contract.
    2. +
    3. Our server puts a puzzle into this contract.
    4. +
    5. The code you receive via SMS is the solution to this puzzle.
    6. +
    +
    +); + +export const howEmailVerificationWorks = ( +
    +

    The following steps will let you prove that you control both an account and an e-mail address.

    +
      +
    1. You send a verification request to a specific contract.
    2. +
    3. Our server puts a puzzle into this contract.
    4. +
    5. The code you receive via e-mail is the solution to this puzzle.
    6. +
    +
    +); diff --git a/js/src/modals/Verification/verification.css b/js/src/modals/Verification/verification.css index f1a39ac4f..968671e63 100644 --- a/js/src/modals/Verification/verification.css +++ b/js/src/modals/Verification/verification.css @@ -19,3 +19,7 @@ margin-top: 0; margin-bottom: 0; } + +.list li { + padding: .1em 0; +} From c249c51dd3430ba144c7345889f97d863a15d1cb Mon Sep 17 00:00:00 2001 From: Jannis R Date: Fri, 9 Dec 2016 16:40:45 +0100 Subject: [PATCH 175/295] certification: use BadgeReg to load the contract --- js/src/modals/Verification/email-store.js | 3 ++- js/src/modals/Verification/sms-store.js | 3 ++- js/src/modals/Verification/store.js | 10 ++++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/js/src/modals/Verification/email-store.js b/js/src/modals/Verification/email-store.js index 5d4ddb4b7..3d7faa9e1 100644 --- a/js/src/modals/Verification/email-store.js +++ b/js/src/modals/Verification/email-store.js @@ -17,6 +17,7 @@ import { observable, computed, action } from 'mobx'; import { sha3 } from '~/api/util/sha3'; +import EmailVerificationABI from '~/contracts/abi/email-verification.json'; import VerificationStore, { LOADING, QUERY_DATA, QUERY_CODE, POSTED_CONFIRMATION, DONE } from './store'; @@ -53,7 +54,7 @@ export default class EmailVerificationStore extends VerificationStore { } constructor (api, account, isTestnet) { - super(api, account, isTestnet, 'emailverification3'); + super(api, EmailVerificationABI, 'emailverification3', account, isTestnet); } requestValues = () => [ sha3(this.email) ] diff --git a/js/src/modals/Verification/sms-store.js b/js/src/modals/Verification/sms-store.js index d0f316358..44c5aa39c 100644 --- a/js/src/modals/Verification/sms-store.js +++ b/js/src/modals/Verification/sms-store.js @@ -17,6 +17,7 @@ import { observable, computed, action } from 'mobx'; import phone from 'phoneformat.js'; +import SMSVerificationABI from '~/contracts/abi/sms-verification.json'; import VerificationStore, { LOADING, QUERY_DATA, QUERY_CODE, POSTED_CONFIRMATION, DONE } from './store'; @@ -52,7 +53,7 @@ export default class SMSVerificationStore extends VerificationStore { } constructor (api, account, isTestnet) { - super(api, account, isTestnet, 'smsverification'); + super(api, SMSVerificationABI, 'smsverification', account, isTestnet); } @action setNumber = (number) => { diff --git a/js/src/modals/Verification/store.js b/js/src/modals/Verification/store.js index 88c86e206..21213e724 100644 --- a/js/src/modals/Verification/store.js +++ b/js/src/modals/Verification/store.js @@ -16,6 +16,7 @@ import { observable, autorun, action } from 'mobx'; import { sha3 } from '~/api/util/sha3'; +import Contract from '~/api/contract'; import Contracts from '~/contracts'; import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/verification'; @@ -46,18 +47,19 @@ export default class VerificationStore { @observable isCodeValid = null; @observable confirmationTx = null; - constructor (api, account, isTestnet, name) { + constructor (api, abi, name, account, isTestnet) { this.api = api; this.account = account; this.isTestnet = isTestnet; this.step = LOADING; - Contracts.get().registry.getContract(name) - .then((contract) => { - this.contract = contract; + Contracts.get().badgeReg.fetchCertifier(name) + .then(({ address }) => { + this.contract = new Contract(api, abi).at(address); this.load(); }) .catch((err) => { + console.error('error', err); this.error = 'Failed to fetch the contract: ' + err.message; }); From ca7406773fce7eee4f9d2739648cd6858c84bcdc Mon Sep 17 00:00:00 2001 From: arkpar Date: Tue, 6 Dec 2016 19:23:15 +0100 Subject: [PATCH 176/295] AuthorityRound network simulation test --- Cargo.lock | 2 + ethcore/res/authority_round.json | 3 +- ethcore/src/client/client.rs | 10 ++ ethcore/src/client/test_client.rs | 4 +- ethcore/src/engines/authority_round.rs | 27 ++-- ethcore/src/engines/mod.rs | 2 + ethcore/src/spec/spec.rs | 2 +- json/src/spec/authority_round.rs | 7 +- sync/Cargo.toml | 2 + sync/src/lib.rs | 3 + sync/src/tests/chain.rs | 96 ++++++------- sync/src/tests/consensus.rs | 78 +++++++++++ sync/src/tests/helpers.rs | 179 ++++++++++++++++++++----- sync/src/tests/mod.rs | 1 + sync/src/tests/snapshot.rs | 2 +- 15 files changed, 318 insertions(+), 100 deletions(-) create mode 100644 sync/src/tests/consensus.rs diff --git a/Cargo.lock b/Cargo.lock index f603410f0..bedcfebe4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -660,12 +660,14 @@ dependencies = [ "clippy 0.0.103 (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.5.0", + "ethcore-devtools 1.5.0", "ethcore-io 1.5.0", "ethcore-ipc 1.5.0", "ethcore-ipc-codegen 1.5.0", "ethcore-ipc-nano 1.5.0", "ethcore-network 1.5.0", "ethcore-util 1.5.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.3.5 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ethcore/res/authority_round.json b/ethcore/res/authority_round.json index a0f88b85b..85beb51b4 100644 --- a/ethcore/res/authority_round.json +++ b/ethcore/res/authority_round.json @@ -4,7 +4,8 @@ "AuthorityRound": { "params": { "gasLimitBoundDivisor": "0x0400", - "stepDuration": "1", + "stepDuration": 1, + "startStep": 2, "authorities" : [ "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e", "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1" diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 17d741b0f..bad09794a 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -255,6 +255,11 @@ impl Client { self.notify.write().push(Arc::downgrade(&target)); } + /// Returns engine reference. + pub fn engine(&self) -> &Engine { + &*self.engine + } + fn notify(&self, f: F) where F: Fn(&ChainNotify) { for np in self.notify.read().iter() { if let Some(n) = np.upgrade() { @@ -570,6 +575,11 @@ impl Client { results.len() } + /// Get shared miner reference. + pub fn miner(&self) -> Arc { + self.miner.clone() + } + /// Handle messages from the IO queue pub fn handle_queued_message(&self, message: &Bytes) { if let Err(e) = self.engine.handle_message(UntrustedRlp::new(message)) { diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index c8f8cdbf7..01a91fa66 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -255,7 +255,7 @@ impl TestBlockChainClient { } /// Make a bad block by setting invalid extra data. - pub fn corrupt_block(&mut self, n: BlockNumber) { + pub fn corrupt_block(&self, n: BlockNumber) { let hash = self.block_hash(BlockID::Number(n)).unwrap(); let mut header: BlockHeader = decode(&self.block_header(BlockID::Number(n)).unwrap()); header.set_extra_data(b"This extra data is way too long to be considered valid".to_vec()); @@ -267,7 +267,7 @@ impl TestBlockChainClient { } /// Make a bad block by setting invalid parent hash. - pub fn corrupt_block_parent(&mut self, n: BlockNumber) { + pub fn corrupt_block_parent(&self, n: BlockNumber) { let hash = self.block_hash(BlockID::Number(n)).unwrap(); let mut header: BlockHeader = decode(&self.block_header(BlockID::Number(n)).unwrap()); header.set_parent_hash(H256::from(42)); diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 807e31c9a..7df000659 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -49,6 +49,8 @@ pub struct AuthorityRoundParams { pub authorities: Vec
    , /// Number of authorities. pub authority_n: usize, + /// Starting step, + pub start_step: Option, } impl From for AuthorityRoundParams { @@ -58,6 +60,7 @@ impl From for AuthorityRoundParams { step_duration: Duration::from_secs(p.step_duration.into()), authority_n: p.authorities.len(), authorities: p.authorities.into_iter().map(Into::into).collect::>(), + start_step: p.start_step.map(Into::into), } } } @@ -97,7 +100,7 @@ impl AsMillis for Duration { impl AuthorityRound { /// Create a new instance of AuthorityRound engine. pub fn new(params: CommonParams, our_params: AuthorityRoundParams, builtins: BTreeMap) -> Result, Error> { - let initial_step = (unix_now().as_secs() / our_params.step_duration.as_secs()) as usize; + let initial_step = our_params.start_step.unwrap_or_else(|| (unix_now().as_secs() / our_params.step_duration.as_secs())) as usize; let engine = Arc::new( AuthorityRound { params: params, @@ -160,14 +163,7 @@ impl IoHandler<()> for TransitionHandler { 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::SeqCst); - engine.proposed.store(false, AtomicOrdering::SeqCst); - if let Some(ref channel) = *engine.message_channel.lock() { - match channel.send(ClientIoMessage::UpdateSealing) { - Ok(_) => trace!(target: "poa", "timeout: UpdateSealing message sent for step {}.", engine.step.load(AtomicOrdering::Relaxed)), - Err(err) => trace!(target: "poa", "timeout: Could not send a sealing message {} for step {}.", err, engine.step.load(AtomicOrdering::Relaxed)), - } - } + engine.step(); io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.remaining_step_duration().as_millis()) .unwrap_or_else(|e| warn!(target: "poa", "Failed to restart consensus step timer: {}.", e)) } @@ -184,6 +180,17 @@ impl Engine for AuthorityRound { fn params(&self) -> &CommonParams { &self.params } fn builtins(&self) -> &BTreeMap { &self.builtins } + fn step(&self) { + self.step.fetch_add(1, AtomicOrdering::SeqCst); + self.proposed.store(false, AtomicOrdering::SeqCst); + if let Some(ref channel) = *self.message_channel.lock() { + match channel.send(ClientIoMessage::UpdateSealing) { + Ok(_) => trace!(target: "poa", "timeout: UpdateSealing message sent for step {}.", self.step.load(AtomicOrdering::Relaxed)), + Err(err) => trace!(target: "poa", "timeout: Could not send a sealing message {} for step {}.", err, self.step.load(AtomicOrdering::Relaxed)), + } + } + } + /// Additional engine-specific information for the user/developer concerning `header`. fn extra_info(&self, header: &Header) -> BTreeMap { map![ @@ -236,6 +243,8 @@ impl Engine for AuthorityRound { } else { warn!(target: "poa", "generate_seal: FAIL: Accounts not provided."); } + } else { + trace!(target: "poa", "generate_seal: Not a proposer for step {}.", step); } Seal::None } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index db53b551d..7793d636d 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -215,4 +215,6 @@ pub trait Engine : Sync + Send { /// Add an account provider useful for Engines that sign stuff. fn register_account_provider(&self, _account_provider: Arc) {} + /// Trigger next step of the consensus engine. + fn step(&self) {} } diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index cd501cbdf..79314948f 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -274,7 +274,7 @@ impl Spec { pub fn new_instant() -> Spec { load_bundled!("instant_seal") } /// 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. + /// Accounts with secrets "0".sha3() and "1".sha3() are the authorities. pub fn new_test_round() -> Self { load_bundled!("authority_round") } /// Create a new Spec with Tendermint consensus which does internal sealing (not requiring work). diff --git a/json/src/spec/authority_round.rs b/json/src/spec/authority_round.rs index 3d73ef1ef..bae17bb24 100644 --- a/json/src/spec/authority_round.rs +++ b/json/src/spec/authority_round.rs @@ -30,6 +30,10 @@ pub struct AuthorityRoundParams { pub step_duration: Uint, /// Valid authorities pub authorities: Vec
    , + /// Starting step. Determined automatically if not specified. + /// To be used for testing only. + #[serde(rename="startStep")] + pub start_step: Option, } /// Authority engine deserialization. @@ -50,7 +54,8 @@ mod tests { "params": { "gasLimitBoundDivisor": "0x0400", "stepDuration": "0x02", - "authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + "authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"], + "startStep" : 24 } }"#; diff --git a/sync/Cargo.toml b/sync/Cargo.toml index 738f5f55c..d7980f0d9 100644 --- a/sync/Cargo.toml +++ b/sync/Cargo.toml @@ -26,6 +26,8 @@ heapsize = "0.3" ethcore-ipc = { path = "../ipc/rpc" } semver = "0.2" ethcore-ipc-nano = { path = "../ipc/nano" } +ethcore-devtools = { path = "../devtools" } +ethkey = { path = "../ethkey" } parking_lot = "0.3" [features] diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 2061e4e3a..ced4c3f52 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 ethcore_devtools as devtools; +#[cfg(test)] extern crate ethkey; + #[macro_use] extern crate log; #[macro_use] diff --git a/sync/src/tests/chain.rs b/sync/src/tests/chain.rs index 7705215f5..361d53e29 100644 --- a/sync/src/tests/chain.rs +++ b/sync/src/tests/chain.rs @@ -24,8 +24,8 @@ use SyncConfig; 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.peer(1).chain.add_blocks(1000, EachBlockWith::Uncle); + net.peer(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()); @@ -35,7 +35,7 @@ fn two_peers() { fn long_chain() { ::env_logger::init().ok(); let mut net = TestNet::new(2); - net.peer_mut(1).chain.add_blocks(50000, EachBlockWith::Nothing); + net.peer(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()); @@ -45,8 +45,8 @@ fn long_chain() { 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.peer(1).chain.add_blocks(1000, EachBlockWith::Uncle); + net.peer(2).chain.add_blocks(1000, EachBlockWith::Uncle); net.sync(); let status = net.peer(0).sync.read().status(); assert_eq!(status.state, SyncState::Idle); @@ -55,8 +55,8 @@ fn status_after_sync() { #[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); + net.peer(1).chain.add_blocks(100, EachBlockWith::Uncle); + net.peer(2).chain.add_blocks(100, EachBlockWith::Uncle); let total_steps = net.sync(); assert!(total_steps < 20); } @@ -67,8 +67,8 @@ fn empty_blocks() { 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.peer(1).chain.add_blocks(5, with.clone()); + net.peer(2).chain.add_blocks(5, with); } net.sync(); assert!(net.peer(0).chain.block(BlockID::Number(1000)).is_some()); @@ -79,14 +79,14 @@ fn empty_blocks() { fn forked() { ::env_logger::init().ok(); let mut net = TestNet::new(3); - net.peer_mut(0).chain.add_blocks(30, EachBlockWith::Uncle); - net.peer_mut(1).chain.add_blocks(30, EachBlockWith::Uncle); - net.peer_mut(2).chain.add_blocks(30, EachBlockWith::Uncle); - net.peer_mut(0).chain.add_blocks(10, EachBlockWith::Nothing); //fork - net.peer_mut(1).chain.add_blocks(20, EachBlockWith::Uncle); - net.peer_mut(2).chain.add_blocks(20, EachBlockWith::Uncle); - net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Uncle); //fork between 1 and 2 - net.peer_mut(2).chain.add_blocks(1, EachBlockWith::Nothing); + net.peer(0).chain.add_blocks(30, EachBlockWith::Uncle); + net.peer(1).chain.add_blocks(30, EachBlockWith::Uncle); + net.peer(2).chain.add_blocks(30, EachBlockWith::Uncle); + net.peer(0).chain.add_blocks(10, EachBlockWith::Nothing); //fork + net.peer(1).chain.add_blocks(20, EachBlockWith::Uncle); + net.peer(2).chain.add_blocks(20, EachBlockWith::Uncle); + net.peer(1).chain.add_blocks(10, EachBlockWith::Uncle); //fork between 1 and 2 + net.peer(2).chain.add_blocks(1, EachBlockWith::Nothing); // peer 1 has the best chain of 601 blocks let peer1_chain = net.peer(1).chain.numbers.read().clone(); net.sync(); @@ -102,12 +102,12 @@ fn forked_with_misbehaving_peer() { let mut net = TestNet::new(3); // peer 0 is on a totally different chain with higher total difficulty net.peer_mut(0).chain = TestBlockChainClient::new_with_extra_data(b"fork".to_vec()); - net.peer_mut(0).chain.add_blocks(50, EachBlockWith::Nothing); - net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Nothing); - net.peer_mut(2).chain.add_blocks(10, EachBlockWith::Nothing); + net.peer(0).chain.add_blocks(50, EachBlockWith::Nothing); + net.peer(1).chain.add_blocks(10, EachBlockWith::Nothing); + net.peer(2).chain.add_blocks(10, EachBlockWith::Nothing); - net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Nothing); - net.peer_mut(2).chain.add_blocks(20, EachBlockWith::Uncle); + net.peer(1).chain.add_blocks(10, EachBlockWith::Nothing); + net.peer(2).chain.add_blocks(20, EachBlockWith::Uncle); // peer 1 should sync to peer 2, others should not change let peer0_chain = net.peer(0).chain.numbers.read().clone(); let peer2_chain = net.peer(2).chain.numbers.read().clone(); @@ -124,13 +124,13 @@ fn net_hard_fork() { 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.peer(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.peer(0).chain.add_blocks(100, EachBlockWith::Nothing); net.sync(); assert_eq!(net.peer(1).chain.chain_info().best_block_number, 0); } @@ -140,8 +140,8 @@ fn net_hard_fork() { fn restart() { ::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.peer(1).chain.add_blocks(1000, EachBlockWith::Uncle); + net.peer(2).chain.add_blocks(1000, EachBlockWith::Uncle); net.sync(); @@ -166,37 +166,37 @@ fn status_empty() { #[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.peer(0).chain.add_blocks(100, EachBlockWith::Uncle); + net.peer(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); + assert_eq!(1, net.peer(0).queue.read().len()); + assert_eq!(0x00, net.peer(0).queue.read()[0].packet_id); } #[test] fn propagate_hashes() { let mut net = TestNet::new(6); - net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Uncle); + net.peer(1).chain.add_blocks(10, EachBlockWith::Uncle); net.sync(); - net.peer_mut(0).chain.add_blocks(10, EachBlockWith::Uncle); + net.peer(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()); + assert_eq!(9, net.peer(0).queue.read().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 { + for i in 0..net.peer(0).queue.read().len() { + if net.peer(0).queue.read()[i].packet_id == 0x1 { hashes += 1; } - if net.peer(0).queue[i].packet_id == 0x7 { + if net.peer(0).queue.read()[i].packet_id == 0x7 { blocks += 1; } } @@ -207,24 +207,24 @@ fn propagate_hashes() { #[test] fn propagate_blocks() { let mut net = TestNet::new(20); - net.peer_mut(1).chain.add_blocks(10, EachBlockWith::Uncle); + net.peer(1).chain.add_blocks(10, EachBlockWith::Uncle); net.sync(); - net.peer_mut(0).chain.add_blocks(10, EachBlockWith::Uncle); + net.peer(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()); + assert!(!net.peer(0).queue.read().is_empty()); // NEW_BLOCK_PACKET - let blocks = net.peer(0).queue.iter().filter(|p| p.packet_id == 0x7).count(); + let blocks = net.peer(0).queue.read().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.peer(1).chain.add_blocks(10, EachBlockWith::Uncle); + net.peer(1).chain.corrupt_block(6); net.sync_steps(20); assert_eq!(net.peer(0).chain.chain_info().best_block_number, 5); @@ -233,8 +233,8 @@ fn restart_on_malformed_block() { #[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.peer(1).chain.add_blocks(10, EachBlockWith::Uncle); + net.peer(1).chain.corrupt_block_parent(6); net.sync_steps(20); assert_eq!(net.peer(0).chain.chain_info().best_block_number, 5); @@ -243,8 +243,8 @@ fn restart_on_broken_chain() { #[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.peer(1).chain.add_blocks(10, EachBlockWith::Uncle); + net.peer(1).chain.corrupt_block_parent(6); net.sync_steps(20); assert_eq!(net.peer(0).chain.chain_info().best_block_number, 5); @@ -255,8 +255,8 @@ fn high_td_attach() { fn disconnect_on_unrelated_chain() { ::env_logger::init().ok(); let mut net = TestNet::new(2); - net.peer_mut(0).chain.add_blocks(200, EachBlockWith::Uncle); - net.peer_mut(1).chain.add_blocks(100, EachBlockWith::Nothing); + net.peer(0).chain.add_blocks(200, EachBlockWith::Uncle); + net.peer(1).chain.add_blocks(100, EachBlockWith::Nothing); net.sync(); assert_eq!(net.disconnect_events, vec![(0, 0)]); } diff --git a/sync/src/tests/consensus.rs b/sync/src/tests/consensus.rs new file mode 100644 index 000000000..00a036a54 --- /dev/null +++ b/sync/src/tests/consensus.rs @@ -0,0 +1,78 @@ +// 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::BlockChainClient; +use ethcore::spec::Spec; +use ethcore::miner::MinerService; +use ethcore::transaction::*; +use ethcore::account_provider::AccountProvider; +use ethkey::KeyPair; +use super::helpers::*; +use SyncConfig; + +#[test] +fn test_authority_round() { + ::env_logger::init().ok(); + + let s1 = KeyPair::from_secret("1".sha3()).unwrap(); + let s2 = KeyPair::from_secret("0".sha3()).unwrap(); + let spec_factory = || { + let spec = Spec::new_test_round(); + let account_provider = AccountProvider::transient_provider(); + account_provider.insert_account(s1.secret().clone(), "").unwrap(); + account_provider.insert_account(s2.secret().clone(), "").unwrap(); + spec.engine.register_account_provider(Arc::new(account_provider)); + spec + }; + let mut net = TestNet::new_with_spec(2, SyncConfig::default(), spec_factory); + let mut net = &mut *net; + // Push transaction to both clients. Only one of them gets lucky to mine a block. + net.peer(0).chain.miner().set_author(s1.address()); + net.peer(0).chain.engine().set_signer(s1.address(), "".to_owned()); + net.peer(1).chain.miner().set_author(s2.address()); + net.peer(1).chain.engine().set_signer(s2.address(), "".to_owned()); + let tx1 = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 21000.into(), + action: Action::Call(Address::default()), + value: 0.into(), + data: Vec::new(), + }.sign(s1.secret(), None); + // exhange statuses + net.sync_steps(5); + net.peer(0).chain.miner().import_own_transaction(&net.peer(0).chain, tx1).unwrap(); + net.sync(); + assert_eq!(net.peer(0).chain.chain_info().best_block_number, 1); + assert_eq!(net.peer(1).chain.chain_info().best_block_number, 1); + + let tx2 = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 21000.into(), + action: Action::Call(Address::default()), + value: 0.into(), + data: Vec::new(), + }.sign(s2.secret(), None); + net.peer(1).chain.miner().import_own_transaction(&net.peer(1).chain, tx2).unwrap(); + net.peer(1).chain.engine().step(); + net.peer(1).chain.miner().update_sealing(&net.peer(1).chain); + net.sync(); + assert_eq!(net.peer(0).chain.chain_info().best_block_number, 2); + assert_eq!(net.peer(1).chain.chain_info().best_block_number, 2); +} + diff --git a/sync/src/tests/helpers.rs b/sync/src/tests/helpers.rs index f3254fbad..3211f27af 100644 --- a/sync/src/tests/helpers.rs +++ b/sync/src/tests/helpers.rs @@ -17,16 +17,33 @@ use util::*; use network::*; use tests::snapshot::*; -use ethcore::client::{TestBlockChainClient, BlockChainClient}; +use ethcore::client::{TestBlockChainClient, BlockChainClient, Client as EthcoreClient, ClientConfig, ChainNotify}; use ethcore::header::BlockNumber; use ethcore::snapshot::SnapshotService; +use ethcore::spec::Spec; +use ethcore::miner::Miner; +use ethcore::db::NUM_COLUMNS; use sync_io::SyncIo; +use io::IoChannel; use api::WARP_SYNC_PROTOCOL_ID; use chain::ChainSync; use ::SyncConfig; +use devtools::{self, GuardedTempResult}; -pub struct TestIo<'p> { - pub chain: &'p mut TestBlockChainClient, +pub trait FlushingBlockChainClient: BlockChainClient { + fn flush(&self) {} +} + +impl FlushingBlockChainClient for EthcoreClient { + fn flush(&self) { + self.flush_queue(); + } +} + +impl FlushingBlockChainClient for TestBlockChainClient {} + +pub struct TestIo<'p, C> where C: FlushingBlockChainClient, C: 'p { + pub chain: &'p C, pub snapshot_service: &'p TestSnapshotService, pub queue: &'p mut VecDeque, pub sender: Option, @@ -34,8 +51,8 @@ pub struct TestIo<'p> { overlay: RwLock>, } -impl<'p> TestIo<'p> { - pub fn new(chain: &'p mut TestBlockChainClient, ss: &'p TestSnapshotService, queue: &'p mut VecDeque, sender: Option) -> TestIo<'p> { +impl<'p, C> TestIo<'p, C> where C: FlushingBlockChainClient, C: 'p { + pub fn new(chain: &'p C, ss: &'p TestSnapshotService, queue: &'p mut VecDeque, sender: Option) -> TestIo<'p, C> { TestIo { chain: chain, snapshot_service: ss, @@ -47,7 +64,7 @@ impl<'p> TestIo<'p> { } } -impl<'p> SyncIo for TestIo<'p> { +impl<'p, C> SyncIo for TestIo<'p, C> where C: FlushingBlockChainClient, C: 'p { fn disable_peer(&mut self, peer_id: PeerId) { self.disconnect_peer(peer_id); } @@ -99,7 +116,7 @@ impl<'p> SyncIo for TestIo<'p> { } fn protocol_version(&self, protocol: &ProtocolId, peer_id: PeerId) -> u8 { - if protocol == &WARP_SYNC_PROTOCOL_ID { 1 } else { self.eth_protocol_version(peer_id) } + if protocol == &WARP_SYNC_PROTOCOL_ID { 2 } else { self.eth_protocol_version(peer_id) } } fn chain_overlay(&self) -> &RwLock> { @@ -113,31 +130,31 @@ pub struct TestPacket { pub recipient: PeerId, } -pub struct TestPeer { - pub chain: TestBlockChainClient, +pub struct TestPeer where C: FlushingBlockChainClient { + pub chain: C, pub snapshot_service: Arc, pub sync: RwLock, - pub queue: VecDeque, + pub queue: RwLock>, } -pub struct TestNet { - pub peers: Vec, +pub struct TestNet where C: FlushingBlockChainClient { + pub peers: Vec>>, pub started: bool, pub disconnect_events: Vec<(PeerId, PeerId)>, //disconnected (initiated by, to) } -impl TestNet { - pub fn new(n: usize) -> TestNet { +impl TestNet { + pub fn new(n: usize) -> TestNet { Self::new_with_config(n, SyncConfig::default()) } - pub fn new_with_fork(n: usize, fork: Option<(BlockNumber, H256)>) -> TestNet { + pub fn new_with_fork(n: usize, fork: Option<(BlockNumber, H256)>) -> TestNet { let mut config = SyncConfig::default(); config.fork_block = fork; Self::new_with_config(n, config) } - pub fn new_with_config(n: usize, config: SyncConfig) -> TestNet { + pub fn new_with_config(n: usize, config: SyncConfig) -> TestNet { let mut net = TestNet { peers: Vec::new(), started: false, @@ -147,31 +164,77 @@ impl TestNet { let chain = TestBlockChainClient::new(); let ss = Arc::new(TestSnapshotService::new()); let sync = ChainSync::new(config.clone(), &chain); - net.peers.push(TestPeer { + net.peers.push(Arc::new(TestPeer { sync: RwLock::new(sync), snapshot_service: ss, chain: chain, - queue: VecDeque::new(), - }); + queue: RwLock::new(VecDeque::new()), + })); } net } +} - pub fn peer(&self, i: usize) -> &TestPeer { +impl TestNet { + pub fn new_with_spec(n: usize, config: SyncConfig, spec_factory: F) -> GuardedTempResult> + where F: Fn() -> Spec + { + let mut net = TestNet { + peers: Vec::new(), + started: false, + disconnect_events: Vec::new(), + }; + let dir = devtools::RandomTempPath::new(); + for _ in 0..n { + let mut client_dir = dir.as_path().clone(); + client_dir.push(devtools::random_filename()); + + let db_config = DatabaseConfig::with_columns(NUM_COLUMNS); + + let spec = spec_factory(); + let client = Arc::try_unwrap(EthcoreClient::new( + ClientConfig::default(), + &spec, + client_dir.as_path(), + Arc::new(Miner::with_spec(&spec)), + IoChannel::disconnected(), + &db_config + ).unwrap()).ok().unwrap(); + + let ss = Arc::new(TestSnapshotService::new()); + let sync = ChainSync::new(config.clone(), &client); + let peer = Arc::new(TestPeer { + sync: RwLock::new(sync), + snapshot_service: ss, + chain: client, + queue: RwLock::new(VecDeque::new()), + }); + peer.chain.add_notify(peer.clone()); + net.peers.push(peer); + } + GuardedTempResult::> { + _temp: dir, + result: Some(net) + } + } +} + +impl TestNet where C: FlushingBlockChainClient { + pub fn peer(&self, i: usize) -> &TestPeer { &self.peers[i] } - pub fn peer_mut(&mut self, i: usize) -> &mut TestPeer { - &mut self.peers[i] + pub fn peer_mut(&mut self, i: usize) -> &mut TestPeer { + Arc::get_mut(&mut self.peers[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 = &mut self.peers[peer]; + let p = &self.peers[peer]; p.sync.write().update_targets(&p.chain); - 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); + p.sync.write().on_peer_connected(&mut TestIo::new(&p.chain, &p.snapshot_service, &mut p.queue.write(), Some(client as PeerId)), client as PeerId); } } } @@ -179,18 +242,20 @@ impl TestNet { pub fn sync_step(&mut self) { for peer in 0..self.peers.len() { - if let Some(packet) = self.peers[peer].queue.pop_front() { + let packet = self.peers[peer].queue.write().pop_front(); + if let Some(packet) = packet { let disconnecting = { - let mut p = &mut self.peers[packet.recipient]; + let p = &self.peers[packet.recipient]; + let mut queue = p.queue.write(); trace!("--- {} -> {} ---", peer, packet.recipient); let to_disconnect = { - let mut io = TestIo::new(&mut p.chain, &p.snapshot_service, &mut p.queue, Some(peer as PeerId)); + let mut io = TestIo::new(&p.chain, &p.snapshot_service, &mut queue, Some(peer as PeerId)); ChainSync::dispatch_packet(&p.sync, &mut io, peer as PeerId, packet.packet_id, &packet.data); io.to_disconnect }; for d in &to_disconnect { // notify this that disconnecting peers are disconnecting - let mut io = TestIo::new(&mut p.chain, &p.snapshot_service, &mut p.queue, Some(*d)); + let mut io = TestIo::new(&p.chain, &p.snapshot_service, &mut queue, Some(*d)); p.sync.write().on_peer_aborting(&mut io, *d); self.disconnect_events.push((peer, *d)); } @@ -198,8 +263,9 @@ impl TestNet { }; for d in &disconnecting { // notify other peers that this peer is disconnecting - let mut p = &mut self.peers[*d]; - let mut io = TestIo::new(&mut p.chain, &p.snapshot_service, &mut p.queue, Some(peer as PeerId)); + let p = &self.peers[*d]; + let mut queue = p.queue.write(); + let mut io = TestIo::new(&p.chain, &p.snapshot_service, &mut queue, Some(peer as PeerId)); p.sync.write().on_peer_aborting(&mut io, peer as PeerId); } } @@ -209,13 +275,17 @@ impl TestNet { } 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)); + let peer = self.peer(peer_num); + peer.chain.flush(); + let mut queue = peer.queue.write(); + peer.sync.write().maintain_peers(&mut TestIo::new(&peer.chain, &peer.snapshot_service, &mut queue, None)); + peer.sync.write().maintain_sync(&mut TestIo::new(&peer.chain, &peer.snapshot_service, &mut queue, None)); + peer.sync.write().propagate_new_transactions(&mut TestIo::new(&peer.chain, &peer.snapshot_service, &mut 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)); + let peer = self.peer(i); + peer.sync.write().restart(&mut TestIo::new(&peer.chain, &peer.snapshot_service, &mut peer.queue.write(), None)); } pub fn sync(&mut self) -> u32 { @@ -239,11 +309,46 @@ impl TestNet { } pub fn done(&self) -> bool { - self.peers.iter().all(|p| p.queue.is_empty()) + self.peers.iter().all(|p| p.queue.read().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), &[], &[], &[], &[], &[], &[]); + let peer = self.peer(peer_id); + let mut queue = peer.queue.write(); + peer.sync.write().chain_new_blocks(&mut TestIo::new(&peer.chain, &peer.snapshot_service, &mut queue, None), &[], &[], &[], &[], &[], &[]); } } + +impl ChainNotify for TestPeer { + fn new_blocks(&self, + imported: Vec, + invalid: Vec, + enacted: Vec, + retracted: Vec, + sealed: Vec, + proposed: Vec, + _duration: u64) + { + let mut queue = self.queue.write(); + let mut io = TestIo::new(&self.chain, &self.snapshot_service, &mut queue, None); + self.sync.write().chain_new_blocks( + &mut io, + &imported, + &invalid, + &enacted, + &retracted, + &sealed, + &proposed); + } + + fn start(&self) {} + + fn stop(&self) {} + + fn broadcast(&self, message: Vec) { + let mut queue = self.queue.write(); + let mut io = TestIo::new(&self.chain, &self.snapshot_service, &mut queue, None); + self.sync.write().propagate_consensus_packet(&mut io, message.clone()); + } +} + diff --git a/sync/src/tests/mod.rs b/sync/src/tests/mod.rs index bdb4ae4f9..f805f6c24 100644 --- a/sync/src/tests/mod.rs +++ b/sync/src/tests/mod.rs @@ -17,4 +17,5 @@ pub mod helpers; pub mod snapshot; mod chain; +mod consensus; mod rpc; diff --git a/sync/src/tests/snapshot.rs b/sync/src/tests/snapshot.rs index 5d0b21b47..283d59ee3 100644 --- a/sync/src/tests/snapshot.rs +++ b/sync/src/tests/snapshot.rs @@ -129,7 +129,7 @@ fn snapshot_sync() { let snapshot_service = Arc::new(TestSnapshotService::new_with_snapshot(16, H256::new(), 500000)); for i in 0..4 { net.peer_mut(i).snapshot_service = snapshot_service.clone(); - net.peer_mut(i).chain.add_blocks(1, EachBlockWith::Nothing); + net.peer(i).chain.add_blocks(1, EachBlockWith::Nothing); } net.sync_steps(50); assert_eq!(net.peer(4).snapshot_service.state_restoration_chunks.lock().len(), net.peer(0).snapshot_service.manifest.as_ref().unwrap().state_hashes.len()); From c91a614c3dd5503a7a8c52b01671fc9422ee3108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 9 Dec 2016 20:24:33 +0100 Subject: [PATCH 177/295] Fixing tests --- ethcore/src/miner/transaction_queue.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 03a9da8e3..b4cc93a9c 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -769,6 +769,11 @@ impl TransactionQueue { return; } + self.remove_all_internal(sender, client_nonce); + } + + /// Always updates future and moves transactions from current to future. + fn remove_all_internal(&mut self, sender: Address, client_nonce: U256) { // We will either move transaction to future or remove it completely // so there will be no transactions from this sender in current self.last_nonces.remove(&sender); @@ -878,7 +883,7 @@ impl TransactionQueue { if order.is_some() { // This will keep consistency in queue // Moves all to future and then promotes a batch from current: - self.remove_all(sender, current_nonce); + self.remove_all_internal(sender, current_nonce); assert_eq!(self.future.by_priority.len() + self.current.by_priority.len(), self.by_hash.len()); return; } From 08e7e79bfd21e2de9f387a06b9943205ab0df4f6 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 9 Dec 2016 20:48:05 +0100 Subject: [PATCH 178/295] more test --- ethcore/src/engines/tendermint/mod.rs | 107 ++++++++++++++++-- .../src/engines/tendermint/vote_collector.rs | 7 +- 2 files changed, 106 insertions(+), 8 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 0b7c09d2f..ae6e3129a 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -251,7 +251,7 @@ impl Tendermint { self.submit_seal(block_hash, seal); self.to_next_height(height); } else { - warn!(target: "poa", "Proposal was not found!"); + warn!(target: "poa", "Not enough votes found!"); } } } @@ -657,10 +657,11 @@ mod tests { } } - fn vote(engine: &Arc, signer: F, height: usize, round: usize, step: Step, block_hash: Option) where F: FnOnce(H256) -> Result { + fn vote(engine: &Arc, signer: F, height: usize, round: usize, step: Step, block_hash: Option) -> Bytes where F: FnOnce(H256) -> Result { let mi = message_info_rlp(height, round, step, block_hash); let m = message_full_rlp(&signer(mi.sha3()).unwrap().into(), &mi); engine.handle_message(UntrustedRlp::new(&m)).unwrap(); + m } fn proposal_seal(tap: &Arc, header: &Header, round: Round) -> Vec { @@ -835,9 +836,58 @@ mod tests { spec.engine.stop(); } + #[test] + fn can_recognize_proposal() { + let (spec, tap) = setup(); + + let proposer = insert_and_register(&tap, &spec.engine, "1"); + + let (b, seal) = propose_default(&spec, proposer); + let sealed = b.seal(spec.engine.as_ref(), seal).unwrap(); + assert!(spec.engine.is_proposal(sealed.header())); + spec.engine.stop(); + } + + #[test] + fn relays_messages() { + let (spec, tap) = setup(); + let engine = spec.engine.clone(); + let mut db_result = get_temp_state_db(); + let mut db = db_result.take(); + spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); + + let v0 = insert_and_register(&tap, &engine, "0"); + let v1 = insert_and_register(&tap, &engine, "1"); + + let h = 0; + let r = 0; + + // Propose + let (b, _) = propose_default(&spec, v1.clone()); + let proposal = Some(b.header().bare_hash()); + + // Register IoHandler remembers messages. + let io_service = IoService::::start().unwrap(); + let test_io = TestIo::new(); + io_service.register_handler(test_io.clone()).unwrap(); + engine.register_message_channel(io_service.channel()); + + let prevote_current = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal); + + let precommit_current = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal); + + let prevote_future = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h + 1, r, Step::Prevote, proposal); + + engine.stop(); + // Relays all valid present and future messages. + assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(prevote_current))); + assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(precommit_current))); + assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(prevote_future))); + } + #[test] #[ignore] - fn step_transitioning() { + fn seal_submission() { let (spec, tap) = setup(); let engine = spec.engine.clone(); let mut db_result = get_temp_state_db(); @@ -867,13 +917,56 @@ mod tests { vote(&engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Precommit, proposal); vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal); + // Wait a bit for async stuff. engine.stop(); + io_service.stop(); + seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v0, v1); + println!("should {:?}, {:?}", proposal.unwrap(), seal); + println!("{:?}", *test_io.received.read()); + assert!(test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal))); + } + + #[test] + #[ignore] + fn skips_to_future_round() { + let (spec, tap) = setup(); + let engine = spec.engine.clone(); + let mut db_result = get_temp_state_db(); + let mut db = db_result.take(); + spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); + + let v0 = insert_and_register(&tap, &engine, "0"); + let v1 = insert_and_register(&tap, &engine, "1"); + + let h = 1; + let r = 2; + + // Propose + let (b, mut seal) = propose_default(&spec, v1.clone()); + let proposal = Some(b.header().bare_hash()); + + // Register IoHandler remembers messages. + let io_service = IoService::::start().unwrap(); + let test_io = TestIo::new(); + io_service.register_handler(test_io.clone()).unwrap(); + engine.register_message_channel(io_service.channel()); + + // Prevote. + vote(&engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Prevote, proposal); + + vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal); + vote(&engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Precommit, proposal); + vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal); + // Wait a bit for async stuff. ::std::thread::sleep(::std::time::Duration::from_millis(100)); + engine.stop(); + io_service.stop(); seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v0, v1); - let first = test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal.clone())); - seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v1, v0); - let second = test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal)); - assert!(first ^ second); + //assert!(test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal.clone()))); + //seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v1, v0); + println!("have {:?}", *test_io.received.read()); + println!("should {:?}, {:?}", proposal.unwrap(), seal); + assert!(test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal))); } } diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index e2f642114..6b717651b 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -89,11 +89,16 @@ impl VoteCollector { .collect::>(); (proposal, votes) }; + if votes.is_empty() { + return None; + } // Remove messages that are no longer relevant. votes.last().map(|m| self.throw_out_old(m)); + let mut votes_vec: Vec<_> = votes.into_iter().map(|m| m.signature).collect(); + votes_vec.sort(); proposal.map(|p| SealSignatures { proposal: p.signature, - votes: votes.into_iter().map(|m| m.signature).collect() + votes: votes_vec, }) } From 5f1fcf95e015921673c50e5250f8ee3cfcac4751 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 9 Dec 2016 23:01:43 +0100 Subject: [PATCH 179/295] Make *ID names consistent with std Rust (Id) --- ethcore/light/src/client.rs | 6 +- ethcore/light/src/provider.rs | 14 +-- ethcore/src/account_provider/mod.rs | 2 +- ethcore/src/blockchain/blockchain.rs | 40 ++++----- ethcore/src/client/client.rs | 124 +++++++++++++------------- ethcore/src/client/test_client.rs | 92 +++++++++---------- ethcore/src/client/traits.rs | 84 ++++++++--------- ethcore/src/miner/miner.rs | 8 +- ethcore/src/snapshot/error.rs | 4 +- ethcore/src/snapshot/mod.rs | 4 +- ethcore/src/snapshot/service.rs | 4 +- ethcore/src/snapshot/tests/service.rs | 6 +- ethcore/src/snapshot/watcher.rs | 4 +- ethcore/src/tests/client.rs | 20 ++--- ethcore/src/tests/rpc.rs | 4 +- ethcore/src/trace/db.rs | 18 ++-- ethcore/src/types/filter.rs | 28 +++--- ethcore/src/types/ids.rs | 12 +-- ethcore/src/types/trace_filter.rs | 4 +- ethstore/src/dir/disk.rs | 4 +- ethstore/src/ethstore.rs | 4 +- ethstore/src/json/error.rs | 4 +- ethstore/src/json/id.rs | 48 +++++----- ethstore/src/json/key_file.rs | 14 +-- ethstore/src/json/mod.rs.in | 2 +- ethstore/src/secret_store.rs | 4 +- js/src/jsonrpc/interfaces/parity.js | 4 +- json/src/misc/account_meta.rs | 2 +- parity/blockchain.rs | 10 +-- parity/configuration.rs | 12 +-- parity/dapps.rs | 4 +- parity/helpers.rs | 22 ++--- parity/informant.rs | 4 +- parity/snapshot.rs | 4 +- rpc/src/v1/impls/eth.rs | 28 +++--- rpc/src/v1/impls/eth_filter.rs | 10 +-- rpc/src/v1/impls/traces.rs | 8 +- rpc/src/v1/tests/eth.rs | 4 +- rpc/src/v1/tests/mocked/eth.rs | 4 +- rpc/src/v1/types/block_number.rs | 24 ++--- rpc/src/v1/types/filter.rs | 12 +-- rpc/src/v1/types/trace_filter.rs | 6 +- sync/src/block_sync.rs | 6 +- sync/src/blocks.rs | 10 +-- sync/src/chain.rs | 34 +++---- sync/src/tests/chain.rs | 12 +-- 46 files changed, 389 insertions(+), 389 deletions(-) diff --git a/ethcore/light/src/client.rs b/ethcore/light/src/client.rs index 8a5c43c48..0035406dc 100644 --- a/ethcore/light/src/client.rs +++ b/ethcore/light/src/client.rs @@ -20,7 +20,7 @@ use std::sync::Arc; use ethcore::engines::Engine; -use ethcore::ids::BlockID; +use ethcore::ids::BlockId; use ethcore::service::ClientIoMessage; use ethcore::block_import_error::BlockImportError; use ethcore::block_status::BlockStatus; @@ -51,7 +51,7 @@ impl Client { } /// Whether the block is already known (but not necessarily part of the canonical chain) - pub fn is_known(&self, _id: BlockID) -> bool { + pub fn is_known(&self, _id: BlockId) -> bool { false } @@ -61,7 +61,7 @@ impl Client { } /// Inquire about the status of a given block. - pub fn status(&self, _id: BlockID) -> BlockStatus { + pub fn status(&self, _id: BlockId) -> BlockStatus { BlockStatus::Unknown } diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index 264df0397..0feee1cab 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -20,7 +20,7 @@ use ethcore::blockchain_info::BlockChainInfo; use ethcore::client::{BlockChainClient, ProvingBlockChainClient}; use ethcore::transaction::SignedTransaction; -use ethcore::ids::BlockID; +use ethcore::ids::BlockId; use util::{Bytes, H256}; @@ -96,7 +96,7 @@ impl Provider for T { let best_num = self.chain_info().best_block_number; let start_num = req.block_num; - match self.block_hash(BlockID::Number(req.block_num)) { + match self.block_hash(BlockId::Number(req.block_num)) { Some(hash) if hash == req.block_hash => {} _=> { trace!(target: "les_provider", "unknown/non-canonical start block in header request: {:?}", (req.block_num, req.block_hash)); @@ -108,7 +108,7 @@ impl Provider for T { .map(|x: u64| x.saturating_mul(req.skip)) .take_while(|x| if req.reverse { x < &start_num } else { best_num - start_num < *x }) .map(|x| if req.reverse { start_num - x } else { start_num + x }) - .map(|x| self.block_header(BlockID::Number(x))) + .map(|x| self.block_header(BlockId::Number(x))) .take_while(|x| x.is_some()) .flat_map(|x| x) .collect() @@ -116,7 +116,7 @@ impl Provider for T { fn block_bodies(&self, req: request::Bodies) -> Vec { req.block_hashes.into_iter() - .map(|hash| self.block_body(BlockID::Hash(hash))) + .map(|hash| self.block_body(BlockId::Hash(hash))) .map(|body| body.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec())) .collect() } @@ -135,8 +135,8 @@ impl Provider for T { for request in req.requests { let proof = match request.key2 { - Some(key2) => self.prove_storage(request.key1, key2, request.from_level, BlockID::Hash(request.block)), - None => self.prove_account(request.key1, request.from_level, BlockID::Hash(request.block)), + Some(key2) => self.prove_storage(request.key1, key2, request.from_level, BlockId::Hash(request.block)), + None => self.prove_account(request.key1, request.from_level, BlockId::Hash(request.block)), }; let mut stream = RlpStream::new_list(proof.len()); @@ -153,7 +153,7 @@ impl Provider for T { fn contract_code(&self, req: request::ContractCodes) -> Vec { req.code_requests.into_iter() .map(|req| { - self.code_by_hash(req.account_key, BlockID::Hash(req.block_hash)) + self.code_by_hash(req.account_key, BlockId::Hash(req.block_hash)) }) .collect() } diff --git a/ethcore/src/account_provider/mod.rs b/ethcore/src/account_provider/mod.rs index a2c83f1ce..59fbda045 100644 --- a/ethcore/src/account_provider/mod.rs +++ b/ethcore/src/account_provider/mod.rs @@ -213,7 +213,7 @@ impl AccountProvider { Ok(AccountMeta { name: try!(self.sstore.name(&account)), meta: try!(self.sstore.meta(&account)), - uuid: self.sstore.uuid(&account).ok().map(Into::into), // allowed to not have a UUID + uuid: self.sstore.uuid(&account).ok().map(Into::into), // allowed to not have a Uuid }) } diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index 0e8e0a271..c69cf3336 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -146,7 +146,7 @@ pub trait BlockProvider { } #[derive(Debug, Hash, Eq, PartialEq, Clone)] -enum CacheID { +enum CacheId { BlockHeader(H256), BlockBody(H256), BlockDetails(H256), @@ -160,7 +160,7 @@ impl bc::group::BloomGroupDatabase for BlockChain { fn blooms_at(&self, position: &bc::group::GroupPosition) -> Option { let position = LogGroupPosition::from(position.clone()); let result = self.db.read_with_cache(db::COL_EXTRA, &self.blocks_blooms, &position).map(Into::into); - self.cache_man.lock().note_used(CacheID::BlocksBlooms(position)); + self.cache_man.lock().note_used(CacheId::BlocksBlooms(position)); result } } @@ -193,7 +193,7 @@ pub struct BlockChain { db: Arc, - cache_man: Mutex>, + cache_man: Mutex>, pending_best_block: RwLock>, pending_block_hashes: RwLock>, @@ -270,7 +270,7 @@ impl BlockProvider for BlockChain { None => None }; - self.cache_man.lock().note_used(CacheID::BlockHeader(hash.clone())); + self.cache_man.lock().note_used(CacheId::BlockHeader(hash.clone())); result } @@ -306,7 +306,7 @@ impl BlockProvider for BlockChain { None => None }; - self.cache_man.lock().note_used(CacheID::BlockBody(hash.clone())); + self.cache_man.lock().note_used(CacheId::BlockBody(hash.clone())); result } @@ -314,28 +314,28 @@ impl BlockProvider for BlockChain { /// Get the familial details concerning a block. fn block_details(&self, hash: &H256) -> Option { let result = self.db.read_with_cache(db::COL_EXTRA, &self.block_details, hash); - self.cache_man.lock().note_used(CacheID::BlockDetails(hash.clone())); + self.cache_man.lock().note_used(CacheId::BlockDetails(hash.clone())); result } /// Get the hash of given block's number. fn block_hash(&self, index: BlockNumber) -> Option { let result = self.db.read_with_cache(db::COL_EXTRA, &self.block_hashes, &index); - self.cache_man.lock().note_used(CacheID::BlockHashes(index)); + self.cache_man.lock().note_used(CacheId::BlockHashes(index)); result } /// Get the address of transaction with given hash. fn transaction_address(&self, hash: &H256) -> Option { let result = self.db.read_with_cache(db::COL_EXTRA, &self.transaction_addresses, hash); - self.cache_man.lock().note_used(CacheID::TransactionAddresses(hash.clone())); + self.cache_man.lock().note_used(CacheId::TransactionAddresses(hash.clone())); result } /// Get receipts of block with given hash. fn block_receipts(&self, hash: &H256) -> Option { let result = self.db.read_with_cache(db::COL_EXTRA, &self.block_receipts, hash); - self.cache_man.lock().note_used(CacheID::BlockReceipts(hash.clone())); + self.cache_man.lock().note_used(CacheId::BlockReceipts(hash.clone())); result } @@ -809,7 +809,7 @@ impl BlockChain { let mut write_details = self.block_details.write(); batch.extend_with_cache(db::COL_EXTRA, &mut *write_details, update, CacheUpdatePolicy::Overwrite); - self.cache_man.lock().note_used(CacheID::BlockDetails(block_hash)); + self.cache_man.lock().note_used(CacheId::BlockDetails(block_hash)); } #[cfg_attr(feature="dev", allow(similar_names))] @@ -968,15 +968,15 @@ impl BlockChain { let mut cache_man = self.cache_man.lock(); for n in pending_hashes_keys { - cache_man.note_used(CacheID::BlockHashes(n)); + cache_man.note_used(CacheId::BlockHashes(n)); } for hash in enacted_txs_keys { - cache_man.note_used(CacheID::TransactionAddresses(hash)); + cache_man.note_used(CacheId::TransactionAddresses(hash)); } for hash in pending_block_hashes { - cache_man.note_used(CacheID::BlockDetails(hash)); + cache_man.note_used(CacheId::BlockDetails(hash)); } } @@ -1244,13 +1244,13 @@ impl BlockChain { cache_man.collect_garbage(current_size, | ids | { for id in &ids { match *id { - CacheID::BlockHeader(ref h) => { block_headers.remove(h); }, - CacheID::BlockBody(ref h) => { block_bodies.remove(h); }, - CacheID::BlockDetails(ref h) => { block_details.remove(h); } - CacheID::BlockHashes(ref h) => { block_hashes.remove(h); } - CacheID::TransactionAddresses(ref h) => { transaction_addresses.remove(h); } - CacheID::BlocksBlooms(ref h) => { blocks_blooms.remove(h); } - CacheID::BlockReceipts(ref h) => { block_receipts.remove(h); } + CacheId::BlockHeader(ref h) => { block_headers.remove(h); }, + CacheId::BlockBody(ref h) => { block_bodies.remove(h); }, + CacheId::BlockDetails(ref h) => { block_details.remove(h); } + CacheId::BlockHashes(ref h) => { block_hashes.remove(h); } + CacheId::TransactionAddresses(ref h) => { transaction_addresses.remove(h); } + CacheId::BlocksBlooms(ref h) => { blocks_blooms.remove(h); } + CacheId::BlockReceipts(ref h) => { block_receipts.remove(h); } } } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 30b5b4653..acd4984ab 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -50,7 +50,7 @@ use log_entry::LocalizedLogEntry; use verification::queue::BlockQueue; use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute}; use client::{ - BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient, + BlockId, TransactionId, UncleId, TraceId, ClientConfig, BlockChainClient, MiningBlockChainClient, TraceFilter, CallAnalytics, BlockImportError, Mode, ChainNotify, PruningInfo, ProvingBlockChainClient, }; @@ -580,13 +580,13 @@ impl Client { /// Attempt to get a copy of a specific block's final state. /// - /// This will not fail if given BlockID::Latest. + /// This will not fail if given BlockId::Latest. /// Otherwise, this can fail (but may not) if the DB prunes state. - pub fn state_at(&self, id: BlockID) -> Option { + pub fn state_at(&self, id: BlockId) -> Option { // fast path for latest state. match id.clone() { - BlockID::Pending => return self.miner.pending_state().or_else(|| Some(self.state())), - BlockID::Latest => return Some(self.state()), + BlockId::Pending => return self.miner.pending_state().or_else(|| Some(self.state())), + BlockId::Latest => return Some(self.state()), _ => {}, } @@ -611,15 +611,15 @@ impl Client { /// Attempt to get a copy of a specific block's beginning state. /// - /// This will not fail if given BlockID::Latest. + /// This will not fail if given BlockId::Latest. /// Otherwise, this can fail (but may not) if the DB prunes state. - pub fn state_at_beginning(&self, id: BlockID) -> Option { + pub fn state_at_beginning(&self, id: BlockId) -> Option { // fast path for latest state. match id { - BlockID::Pending => self.state_at(BlockID::Latest), + BlockId::Pending => self.state_at(BlockId::Latest), id => match self.block_number(id) { None | Some(0) => None, - Some(n) => self.state_at(BlockID::Number(n - 1)), + Some(n) => self.state_at(BlockId::Number(n - 1)), } } } @@ -689,18 +689,18 @@ impl Client { } /// Look up the block number for the given block ID. - pub fn block_number(&self, id: BlockID) -> Option { + pub fn block_number(&self, id: BlockId) -> Option { match id { - BlockID::Number(number) => Some(number), - BlockID::Hash(ref hash) => self.chain.read().block_number(hash), - BlockID::Earliest => Some(0), - BlockID::Latest | BlockID::Pending => Some(self.chain.read().best_block_number()), + BlockId::Number(number) => Some(number), + BlockId::Hash(ref hash) => self.chain.read().block_number(hash), + BlockId::Earliest => Some(0), + BlockId::Latest | BlockId::Pending => Some(self.chain.read().best_block_number()), } } /// Take a snapshot at the given block. /// If the ID given is "latest", this will default to 1000 blocks behind. - pub fn take_snapshot(&self, writer: W, at: BlockID, p: &snapshot::Progress) -> Result<(), EthcoreError> { + pub fn take_snapshot(&self, writer: W, at: BlockId, p: &snapshot::Progress) -> Result<(), EthcoreError> { let db = self.state_db.lock().journal_db().boxed_clone(); let best_block_number = self.chain_info().best_block_number; let block_number = try!(self.block_number(at).ok_or(snapshot::Error::InvalidStartingBlock(at))); @@ -712,13 +712,13 @@ impl Client { let history = ::std::cmp::min(self.history, 1000); let start_hash = match at { - BlockID::Latest => { + BlockId::Latest => { let start_num = match db.earliest_era() { Some(era) => ::std::cmp::max(era, best_block_number - history), None => best_block_number - history, }; - match self.block_hash(BlockID::Number(start_num)) { + match self.block_hash(BlockId::Number(start_num)) { Some(h) => h, None => return Err(snapshot::Error::InvalidStartingBlock(at).into()), } @@ -739,19 +739,19 @@ impl Client { self.history } - fn block_hash(chain: &BlockChain, id: BlockID) -> Option { + fn block_hash(chain: &BlockChain, id: BlockId) -> Option { match id { - BlockID::Hash(hash) => Some(hash), - BlockID::Number(number) => chain.block_hash(number), - BlockID::Earliest => chain.block_hash(0), - BlockID::Latest | BlockID::Pending => Some(chain.best_block_hash()), + BlockId::Hash(hash) => Some(hash), + BlockId::Number(number) => chain.block_hash(number), + BlockId::Earliest => chain.block_hash(0), + BlockId::Latest | BlockId::Pending => Some(chain.best_block_hash()), } } - fn transaction_address(&self, id: TransactionID) -> Option { + fn transaction_address(&self, id: TransactionId) -> Option { match id { - TransactionID::Hash(ref hash) => self.chain.read().transaction_address(hash), - TransactionID::Location(id, index) => Self::block_hash(&self.chain.read(), id).map(|hash| TransactionAddress { + TransactionId::Hash(ref hash) => self.chain.read().transaction_address(hash), + TransactionId::Location(id, index) => Self::block_hash(&self.chain.read(), id).map(|hash| TransactionAddress { block_hash: hash, index: index, }) @@ -805,7 +805,7 @@ impl snapshot::DatabaseRestore for Client { impl BlockChainClient for Client { - fn call(&self, t: &SignedTransaction, block: BlockID, analytics: CallAnalytics) -> Result { + fn call(&self, t: &SignedTransaction, block: BlockId, analytics: CallAnalytics) -> Result { let header = try!(self.block_header(block).ok_or(CallError::StatePruned)); let view = HeaderView::new(&header); let last_hashes = self.build_last_hashes(view.parent_hash()); @@ -841,11 +841,11 @@ impl BlockChainClient for Client { Ok(ret) } - fn replay(&self, id: TransactionID, analytics: CallAnalytics) -> Result { + fn replay(&self, id: TransactionId, analytics: CallAnalytics) -> Result { let address = try!(self.transaction_address(id).ok_or(CallError::TransactionNotFound)); - let header_data = try!(self.block_header(BlockID::Hash(address.block_hash)).ok_or(CallError::StatePruned)); - let body_data = try!(self.block_body(BlockID::Hash(address.block_hash)).ok_or(CallError::StatePruned)); - let mut state = try!(self.state_at_beginning(BlockID::Hash(address.block_hash)).ok_or(CallError::StatePruned)); + let header_data = try!(self.block_header(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)); + let body_data = try!(self.block_body(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)); + let mut state = try!(self.state_at_beginning(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)); let txs = BodyView::new(&body_data).transactions(); if address.index >= txs.len() { @@ -918,18 +918,18 @@ impl BlockChainClient for Client { self.chain.read().best_block_header() } - fn block_header(&self, id: BlockID) -> Option { + fn block_header(&self, id: BlockId) -> Option { let chain = self.chain.read(); Self::block_hash(&chain, id).and_then(|hash| chain.block_header_data(&hash)) } - fn block_body(&self, id: BlockID) -> Option { + fn block_body(&self, id: BlockId) -> Option { let chain = self.chain.read(); Self::block_hash(&chain, id).and_then(|hash| chain.block_body(&hash)) } - fn block(&self, id: BlockID) -> Option { - if let BlockID::Pending = id { + fn block(&self, id: BlockId) -> Option { + if let BlockId::Pending = id { if let Some(block) = self.miner.pending_block() { return Some(block.rlp_bytes(Seal::Without)); } @@ -940,7 +940,7 @@ impl BlockChainClient for Client { }) } - fn block_status(&self, id: BlockID) -> BlockStatus { + fn block_status(&self, id: BlockId) -> BlockStatus { let chain = self.chain.read(); match Self::block_hash(&chain, id) { Some(ref hash) if chain.is_known(hash) => BlockStatus::InChain, @@ -949,42 +949,42 @@ impl BlockChainClient for Client { } } - fn block_total_difficulty(&self, id: BlockID) -> Option { - if let BlockID::Pending = id { + fn block_total_difficulty(&self, id: BlockId) -> Option { + if let BlockId::Pending = id { if let Some(block) = self.miner.pending_block() { - return Some(*block.header.difficulty() + self.block_total_difficulty(BlockID::Latest).expect("blocks in chain have details; qed")); + return Some(*block.header.difficulty() + self.block_total_difficulty(BlockId::Latest).expect("blocks in chain have details; qed")); } } let chain = self.chain.read(); Self::block_hash(&chain, id).and_then(|hash| chain.block_details(&hash)).map(|d| d.total_difficulty) } - fn nonce(&self, address: &Address, id: BlockID) -> Option { + fn nonce(&self, address: &Address, id: BlockId) -> Option { self.state_at(id).map(|s| s.nonce(address)) } - fn storage_root(&self, address: &Address, id: BlockID) -> Option { + fn storage_root(&self, address: &Address, id: BlockId) -> Option { self.state_at(id).and_then(|s| s.storage_root(address)) } - fn block_hash(&self, id: BlockID) -> Option { + fn block_hash(&self, id: BlockId) -> Option { let chain = self.chain.read(); Self::block_hash(&chain, id) } - fn code(&self, address: &Address, id: BlockID) -> Option> { + fn code(&self, address: &Address, id: BlockId) -> Option> { self.state_at(id).map(|s| s.code(address).map(|c| (*c).clone())) } - fn balance(&self, address: &Address, id: BlockID) -> Option { + fn balance(&self, address: &Address, id: BlockId) -> Option { self.state_at(id).map(|s| s.balance(address)) } - fn storage_at(&self, address: &Address, position: &H256, id: BlockID) -> Option { + fn storage_at(&self, address: &Address, position: &H256, id: BlockId) -> Option { self.state_at(id).map(|s| s.storage_at(address, position)) } - fn list_accounts(&self, id: BlockID, after: Option<&Address>, count: u64) -> Option> { + fn list_accounts(&self, id: BlockId, after: Option<&Address>, count: u64) -> Option> { if !self.factories.trie.is_fat() { trace!(target: "fatdb", "list_accounts: Not a fat DB"); return None; @@ -1022,7 +1022,7 @@ impl BlockChainClient for Client { Some(accounts) } - fn list_storage(&self, id: BlockID, account: &Address, after: Option<&H256>, count: u64) -> Option> { + fn list_storage(&self, id: BlockId, account: &Address, after: Option<&H256>, count: u64) -> Option> { if !self.factories.trie.is_fat() { trace!(target: "fatdb", "list_stroage: Not a fat DB"); return None; @@ -1066,20 +1066,20 @@ impl BlockChainClient for Client { Some(keys) } - fn transaction(&self, id: TransactionID) -> Option { + fn transaction(&self, id: TransactionId) -> Option { self.transaction_address(id).and_then(|address| self.chain.read().transaction(&address)) } - fn transaction_block(&self, id: TransactionID) -> Option { + fn transaction_block(&self, id: TransactionId) -> Option { self.transaction_address(id).map(|addr| addr.block_hash) } - fn uncle(&self, id: UncleID) -> Option { + fn uncle(&self, id: UncleId) -> Option { let index = id.position; self.block_body(id.block).and_then(|body| BodyView::new(&body).uncle_rlp_at(index)) } - fn transaction_receipt(&self, id: TransactionID) -> Option { + fn transaction_receipt(&self, id: TransactionId) -> Option { let chain = self.chain.read(); self.transaction_address(id) .and_then(|address| chain.block_number(&address.block_hash).and_then(|block_number| { @@ -1163,7 +1163,7 @@ impl BlockChainClient for Client { if self.chain.read().is_known(&unverified.hash()) { return Err(BlockImportError::Import(ImportError::AlreadyInChain)); } - if self.block_status(BlockID::Hash(unverified.parent_hash())) == BlockStatus::Unknown { + if self.block_status(BlockId::Hash(unverified.parent_hash())) == BlockStatus::Unknown { return Err(BlockImportError::Block(BlockError::UnknownParent(unverified.parent_hash()))); } } @@ -1177,7 +1177,7 @@ impl BlockChainClient for Client { if self.chain.read().is_known(&header.hash()) { return Err(BlockImportError::Import(ImportError::AlreadyInChain)); } - if self.block_status(BlockID::Hash(header.parent_hash())) == BlockStatus::Unknown { + if self.block_status(BlockId::Hash(header.parent_hash())) == BlockStatus::Unknown { return Err(BlockImportError::Block(BlockError::UnknownParent(header.parent_hash()))); } } @@ -1200,7 +1200,7 @@ impl BlockChainClient for Client { self.engine.additional_params().into_iter().collect() } - fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockID, to_block: BlockID) -> Option> { + fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockId, to_block: BlockId) -> Option> { match (self.block_number(from_block), self.block_number(to_block)) { (Some(from), Some(to)) => Some(self.chain.read().blocks_with_bloom(bloom, from, to)), _ => None @@ -1242,20 +1242,20 @@ impl BlockChainClient for Client { let trace_address = trace.address; self.transaction_address(trace.transaction) .and_then(|tx_address| { - self.block_number(BlockID::Hash(tx_address.block_hash)) + self.block_number(BlockId::Hash(tx_address.block_hash)) .and_then(|number| self.tracedb.read().trace(number, tx_address.index, trace_address)) }) } - fn transaction_traces(&self, transaction: TransactionID) -> Option> { + fn transaction_traces(&self, transaction: TransactionId) -> Option> { self.transaction_address(transaction) .and_then(|tx_address| { - self.block_number(BlockID::Hash(tx_address.block_hash)) + self.block_number(BlockId::Hash(tx_address.block_hash)) .and_then(|number| self.tracedb.read().transaction_traces(number, tx_address.index)) }) } - fn block_traces(&self, block: BlockID) -> Option> { + fn block_traces(&self, block: BlockId) -> Option> { self.block_number(block) .and_then(|number| self.tracedb.read().block_traces(number)) } @@ -1290,13 +1290,13 @@ impl BlockChainClient for Client { self.engine.signing_network_id(&self.latest_env_info()) } - fn block_extra_info(&self, id: BlockID) -> Option> { + fn block_extra_info(&self, id: BlockId) -> Option> { self.block_header(id) .map(|block| decode(&block)) .map(|header| self.engine.extra_info(&header)) } - fn uncle_extra_info(&self, id: UncleID) -> Option> { + fn uncle_extra_info(&self, id: UncleId) -> Option> { self.uncle(id) .map(|header| self.engine.extra_info(&decode(&header))) } @@ -1392,19 +1392,19 @@ impl MayPanic for Client { } impl ProvingBlockChainClient for Client { - fn prove_storage(&self, key1: H256, key2: H256, from_level: u32, id: BlockID) -> Vec { + fn prove_storage(&self, key1: H256, key2: H256, from_level: u32, id: BlockId) -> Vec { self.state_at(id) .and_then(move |state| state.prove_storage(key1, key2, from_level).ok()) .unwrap_or_else(Vec::new) } - fn prove_account(&self, key1: H256, from_level: u32, id: BlockID) -> Vec { + fn prove_account(&self, key1: H256, from_level: u32, id: BlockId) -> Vec { self.state_at(id) .and_then(move |state| state.prove_account(key1, from_level).ok()) .unwrap_or_else(Vec::new) } - fn code_by_hash(&self, account_key: H256, id: BlockID) -> Bytes { + fn code_by_hash(&self, account_key: H256, id: BlockId) -> Bytes { self.state_at(id) .and_then(move |state| state.code_by_address_hash(account_key).ok()) .and_then(|x| x) diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 433a5166f..e61fe729c 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -24,8 +24,8 @@ use devtools::*; use transaction::{Transaction, LocalizedTransaction, SignedTransaction, Action}; use blockchain::TreeRoute; use client::{ - BlockChainClient, MiningBlockChainClient, BlockChainInfo, BlockStatus, BlockID, - TransactionID, UncleID, TraceId, TraceFilter, LastHashes, CallAnalytics, BlockImportError, + BlockChainClient, MiningBlockChainClient, BlockChainInfo, BlockStatus, BlockId, + TransactionId, UncleId, TraceId, TraceFilter, LastHashes, CallAnalytics, BlockImportError, }; use db::{NUM_COLUMNS, COL_STATE}; use header::{Header as BlockHeader, BlockNumber}; @@ -73,7 +73,7 @@ pub struct TestBlockChainClient { /// Execution result. pub execution_result: RwLock>>, /// Transaction receipts. - pub receipts: RwLock>, + pub receipts: RwLock>, /// Logs pub logs: RwLock>, /// Block queue size. @@ -158,7 +158,7 @@ impl TestBlockChainClient { } /// Set the transaction receipt result - pub fn set_transaction_receipt(&self, id: TransactionID, receipt: LocalizedReceipt) { + pub fn set_transaction_receipt(&self, id: TransactionId, receipt: LocalizedReceipt) { self.receipts.write().insert(id, receipt); } @@ -256,8 +256,8 @@ impl TestBlockChainClient { /// Make a bad block by setting invalid extra data. pub fn corrupt_block(&self, n: BlockNumber) { - let hash = self.block_hash(BlockID::Number(n)).unwrap(); - let mut header: BlockHeader = decode(&self.block_header(BlockID::Number(n)).unwrap()); + let hash = self.block_hash(BlockId::Number(n)).unwrap(); + let mut header: BlockHeader = decode(&self.block_header(BlockId::Number(n)).unwrap()); header.set_extra_data(b"This extra data is way too long to be considered valid".to_vec()); let mut rlp = RlpStream::new_list(3); rlp.append(&header); @@ -268,8 +268,8 @@ impl TestBlockChainClient { /// Make a bad block by setting invalid parent hash. pub fn corrupt_block_parent(&self, n: BlockNumber) { - let hash = self.block_hash(BlockID::Number(n)).unwrap(); - let mut header: BlockHeader = decode(&self.block_header(BlockID::Number(n)).unwrap()); + let hash = self.block_hash(BlockId::Number(n)).unwrap(); + let mut header: BlockHeader = decode(&self.block_header(BlockId::Number(n)).unwrap()); header.set_parent_hash(H256::from(42)); let mut rlp = RlpStream::new_list(3); rlp.append(&header); @@ -285,12 +285,12 @@ impl TestBlockChainClient { blocks_read[&index].clone() } - fn block_hash(&self, id: BlockID) -> Option { + fn block_hash(&self, id: BlockId) -> Option { match id { - BlockID::Hash(hash) => Some(hash), - BlockID::Number(n) => self.numbers.read().get(&(n as usize)).cloned(), - BlockID::Earliest => self.numbers.read().get(&0).cloned(), - BlockID::Latest | BlockID::Pending => self.numbers.read().get(&(self.numbers.read().len() - 1)).cloned() + BlockId::Hash(hash) => Some(hash), + BlockId::Number(n) => self.numbers.read().get(&(n as usize)).cloned(), + BlockId::Earliest => self.numbers.read().get(&0).cloned(), + BlockId::Latest | BlockId::Pending => self.numbers.read().get(&(self.numbers.read().len() - 1)).cloned() } } @@ -363,46 +363,46 @@ impl MiningBlockChainClient for TestBlockChainClient { } impl BlockChainClient for TestBlockChainClient { - fn call(&self, _t: &SignedTransaction, _block: BlockID, _analytics: CallAnalytics) -> Result { + fn call(&self, _t: &SignedTransaction, _block: BlockId, _analytics: CallAnalytics) -> Result { self.execution_result.read().clone().unwrap() } - fn replay(&self, _id: TransactionID, _analytics: CallAnalytics) -> Result { + fn replay(&self, _id: TransactionId, _analytics: CallAnalytics) -> Result { self.execution_result.read().clone().unwrap() } - fn block_total_difficulty(&self, _id: BlockID) -> Option { + fn block_total_difficulty(&self, _id: BlockId) -> Option { Some(U256::zero()) } - fn block_hash(&self, id: BlockID) -> Option { + fn block_hash(&self, id: BlockId) -> Option { Self::block_hash(self, id) } - fn nonce(&self, address: &Address, id: BlockID) -> Option { + fn nonce(&self, address: &Address, id: BlockId) -> Option { match id { - BlockID::Latest => Some(self.nonces.read().get(address).cloned().unwrap_or(self.spec.params.account_start_nonce)), + BlockId::Latest => Some(self.nonces.read().get(address).cloned().unwrap_or(self.spec.params.account_start_nonce)), _ => None, } } - fn storage_root(&self, _address: &Address, _id: BlockID) -> Option { + fn storage_root(&self, _address: &Address, _id: BlockId) -> Option { None } fn latest_nonce(&self, address: &Address) -> U256 { - self.nonce(address, BlockID::Latest).unwrap() + self.nonce(address, BlockId::Latest).unwrap() } - fn code(&self, address: &Address, id: BlockID) -> Option> { + fn code(&self, address: &Address, id: BlockId) -> Option> { match id { - BlockID::Latest => Some(self.code.read().get(address).cloned()), + BlockId::Latest => Some(self.code.read().get(address).cloned()), _ => None, } } - fn balance(&self, address: &Address, id: BlockID) -> Option { - if let BlockID::Latest = id { + fn balance(&self, address: &Address, id: BlockId) -> Option { + if let BlockId::Latest = id { Some(self.balances.read().get(address).cloned().unwrap_or_else(U256::zero)) } else { None @@ -410,45 +410,45 @@ impl BlockChainClient for TestBlockChainClient { } fn latest_balance(&self, address: &Address) -> U256 { - self.balance(address, BlockID::Latest).unwrap() + self.balance(address, BlockId::Latest).unwrap() } - fn storage_at(&self, address: &Address, position: &H256, id: BlockID) -> Option { - if let BlockID::Latest = id { + fn storage_at(&self, address: &Address, position: &H256, id: BlockId) -> Option { + if let BlockId::Latest = id { Some(self.storage.read().get(&(address.clone(), position.clone())).cloned().unwrap_or_else(H256::new)) } else { None } } - fn list_accounts(&self, _id: BlockID, _after: Option<&Address>, _count: u64) -> Option> { + fn list_accounts(&self, _id: BlockId, _after: Option<&Address>, _count: u64) -> Option> { None } - fn list_storage(&self, _id: BlockID, _account: &Address, _after: Option<&H256>, _count: u64) -> Option> { + fn list_storage(&self, _id: BlockId, _account: &Address, _after: Option<&H256>, _count: u64) -> Option> { None } - fn transaction(&self, _id: TransactionID) -> Option { + fn transaction(&self, _id: TransactionId) -> Option { None // Simple default. } - fn transaction_block(&self, _id: TransactionID) -> Option { + fn transaction_block(&self, _id: TransactionId) -> Option { None // Simple default. } - fn uncle(&self, _id: UncleID) -> Option { + fn uncle(&self, _id: UncleId) -> Option { None // Simple default. } - fn uncle_extra_info(&self, _id: UncleID) -> Option> { + fn uncle_extra_info(&self, _id: UncleId) -> Option> { None } - fn transaction_receipt(&self, id: TransactionID) -> Option { + fn transaction_receipt(&self, id: TransactionId) -> Option { self.receipts.read().get(&id).cloned() } - fn blocks_with_bloom(&self, _bloom: &H2048, _from_block: BlockID, _to_block: BlockID) -> Option> { + fn blocks_with_bloom(&self, _bloom: &H2048, _from_block: BlockId, _to_block: BlockId) -> Option> { unimplemented!(); } @@ -466,14 +466,14 @@ impl BlockChainClient for TestBlockChainClient { } fn best_block_header(&self) -> Bytes { - self.block_header(BlockID::Hash(self.chain_info().best_block_hash)).expect("Best block always have header.") + self.block_header(BlockId::Hash(self.chain_info().best_block_hash)).expect("Best block always have header.") } - fn block_header(&self, id: BlockID) -> Option { + fn block_header(&self, id: BlockId) -> Option { self.block_hash(id).and_then(|hash| self.blocks.read().get(&hash).map(|r| Rlp::new(r).at(0).as_raw().to_vec())) } - fn block_body(&self, id: BlockID) -> Option { + fn block_body(&self, id: BlockId) -> Option { self.block_hash(id).and_then(|hash| self.blocks.read().get(&hash).map(|r| { let mut stream = RlpStream::new_list(2); stream.append_raw(Rlp::new(r).at(1).as_raw(), 1); @@ -482,21 +482,21 @@ impl BlockChainClient for TestBlockChainClient { })) } - fn block(&self, id: BlockID) -> Option { + fn block(&self, id: BlockId) -> Option { self.block_hash(id).and_then(|hash| self.blocks.read().get(&hash).cloned()) } - fn block_extra_info(&self, id: BlockID) -> Option> { + fn block_extra_info(&self, id: BlockId) -> Option> { self.block(id) .map(|block| BlockView::new(&block).header()) .map(|header| self.spec.engine.extra_info(&header)) } - fn block_status(&self, id: BlockID) -> BlockStatus { + fn block_status(&self, id: BlockId) -> BlockStatus { match id { - BlockID::Number(number) if (number as usize) < self.blocks.read().len() => BlockStatus::InChain, - BlockID::Hash(ref hash) if self.blocks.read().get(hash).is_some() => BlockStatus::InChain, + BlockId::Number(number) if (number as usize) < self.blocks.read().len() => BlockStatus::InChain, + BlockId::Hash(ref hash) if self.blocks.read().get(hash).is_some() => BlockStatus::InChain, _ => BlockStatus::Unknown } } @@ -649,11 +649,11 @@ impl BlockChainClient for TestBlockChainClient { unimplemented!(); } - fn transaction_traces(&self, _trace: TransactionID) -> Option> { + fn transaction_traces(&self, _trace: TransactionId) -> Option> { unimplemented!(); } - fn block_traces(&self, _trace: BlockID) -> Option> { + fn block_traces(&self, _trace: BlockId) -> Option> { unimplemented!(); } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index e23a564d4..ad21d2ba5 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -50,93 +50,93 @@ pub trait BlockChainClient : Sync + Send { fn keep_alive(&self) {} /// Get raw block header data by block id. - fn block_header(&self, id: BlockID) -> Option; + fn block_header(&self, id: BlockId) -> Option; /// Get raw block body data by block id. /// Block body is an RLP list of two items: uncles and transactions. - fn block_body(&self, id: BlockID) -> Option; + fn block_body(&self, id: BlockId) -> Option; /// Get raw block data by block header hash. - fn block(&self, id: BlockID) -> Option; + fn block(&self, id: BlockId) -> Option; /// Get block status by block header hash. - fn block_status(&self, id: BlockID) -> BlockStatus; + fn block_status(&self, id: BlockId) -> BlockStatus; /// Get block total difficulty. - fn block_total_difficulty(&self, id: BlockID) -> Option; + fn block_total_difficulty(&self, id: BlockId) -> Option; /// Attempt to get address nonce at given block. - /// May not fail on BlockID::Latest. - fn nonce(&self, address: &Address, id: BlockID) -> Option; + /// May not fail on BlockId::Latest. + fn nonce(&self, address: &Address, id: BlockId) -> Option; /// Attempt to get address storage root at given block. - /// May not fail on BlockID::Latest. - fn storage_root(&self, address: &Address, id: BlockID) -> Option; + /// May not fail on BlockId::Latest. + fn storage_root(&self, address: &Address, id: BlockId) -> Option; /// Get address nonce at the latest block's state. fn latest_nonce(&self, address: &Address) -> U256 { - self.nonce(address, BlockID::Latest) - .expect("nonce will return Some when given BlockID::Latest. nonce was given BlockID::Latest. \ + self.nonce(address, BlockId::Latest) + .expect("nonce will return Some when given BlockId::Latest. nonce was given BlockId::Latest. \ Therefore nonce has returned Some; qed") } /// Get block hash. - fn block_hash(&self, id: BlockID) -> Option; + fn block_hash(&self, id: BlockId) -> Option; /// Get address code at given block's state. - fn code(&self, address: &Address, id: BlockID) -> Option>; + fn code(&self, address: &Address, id: BlockId) -> Option>; /// Get address code at the latest block's state. fn latest_code(&self, address: &Address) -> Option { - self.code(address, BlockID::Latest) - .expect("code will return Some if given BlockID::Latest; qed") + self.code(address, BlockId::Latest) + .expect("code will return Some if given BlockId::Latest; qed") } /// Get address balance at the given block's state. /// - /// May not return None if given BlockID::Latest. + /// May not return None if given BlockId::Latest. /// Returns None if and only if the block's root hash has been pruned from the DB. - fn balance(&self, address: &Address, id: BlockID) -> Option; + fn balance(&self, address: &Address, id: BlockId) -> Option; /// Get address balance at the latest block's state. fn latest_balance(&self, address: &Address) -> U256 { - self.balance(address, BlockID::Latest) - .expect("balance will return Some if given BlockID::Latest. balance was given BlockID::Latest \ + self.balance(address, BlockId::Latest) + .expect("balance will return Some if given BlockId::Latest. balance was given BlockId::Latest \ Therefore balance has returned Some; qed") } /// Get value of the storage at given position at the given block's state. /// - /// May not return None if given BlockID::Latest. + /// May not return None if given BlockId::Latest. /// Returns None if and only if the block's root hash has been pruned from the DB. - fn storage_at(&self, address: &Address, position: &H256, id: BlockID) -> Option; + fn storage_at(&self, address: &Address, position: &H256, id: BlockId) -> Option; /// Get value of the storage at given position at the latest block's state. fn latest_storage_at(&self, address: &Address, position: &H256) -> H256 { - self.storage_at(address, position, BlockID::Latest) - .expect("storage_at will return Some if given BlockID::Latest. storage_at was given BlockID::Latest. \ + self.storage_at(address, position, BlockId::Latest) + .expect("storage_at will return Some if given BlockId::Latest. storage_at was given BlockId::Latest. \ Therefore storage_at has returned Some; qed") } /// Get a list of all accounts in the block `id`, if fat DB is in operation, otherwise `None`. /// If `after` is set the list starts with the following item. - fn list_accounts(&self, id: BlockID, after: Option<&Address>, count: u64) -> Option>; + fn list_accounts(&self, id: BlockId, after: Option<&Address>, count: u64) -> Option>; /// Get a list of all storage keys in the block `id`, if fat DB is in operation, otherwise `None`. /// If `after` is set the list starts with the following item. - fn list_storage(&self, id: BlockID, account: &Address, after: Option<&H256>, count: u64) -> Option>; + fn list_storage(&self, id: BlockId, account: &Address, after: Option<&H256>, count: u64) -> Option>; /// Get transaction with given hash. - fn transaction(&self, id: TransactionID) -> Option; + fn transaction(&self, id: TransactionId) -> Option; /// Get the hash of block that contains the transaction, if any. - fn transaction_block(&self, id: TransactionID) -> Option; + fn transaction_block(&self, id: TransactionId) -> Option; /// Get uncle with given id. - fn uncle(&self, id: UncleID) -> Option; + fn uncle(&self, id: UncleId) -> Option; /// Get transaction receipt with given hash. - fn transaction_receipt(&self, id: TransactionID) -> Option; + fn transaction_receipt(&self, id: TransactionId) -> Option; /// Get a tree route between `from` and `to`. /// See `BlockChain::tree_route`. @@ -173,16 +173,16 @@ pub trait BlockChainClient : Sync + Send { fn best_block_header(&self) -> Bytes; /// Returns numbers of blocks containing given bloom. - fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockID, to_block: BlockID) -> Option>; + fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockId, to_block: BlockId) -> Option>; /// Returns logs matching given filter. fn logs(&self, filter: Filter) -> Vec; /// Makes a non-persistent transaction call. - fn call(&self, t: &SignedTransaction, block: BlockID, analytics: CallAnalytics) -> Result; + fn call(&self, t: &SignedTransaction, block: BlockId, analytics: CallAnalytics) -> Result; /// Replays a given transaction for inspection. - fn replay(&self, t: TransactionID, analytics: CallAnalytics) -> Result; + fn replay(&self, t: TransactionId, analytics: CallAnalytics) -> Result; /// Returns traces matching given filter. fn filter_traces(&self, filter: TraceFilter) -> Option>; @@ -191,10 +191,10 @@ pub trait BlockChainClient : Sync + Send { fn trace(&self, trace: TraceId) -> Option; /// Returns traces created by transaction. - fn transaction_traces(&self, trace: TransactionID) -> Option>; + fn transaction_traces(&self, trace: TransactionId) -> Option>; /// Returns traces created by transaction from block. - fn block_traces(&self, trace: BlockID) -> Option>; + fn block_traces(&self, trace: BlockId) -> Option>; /// Get last hashes starting from best block. fn last_hashes(&self) -> LastHashes; @@ -211,7 +211,7 @@ pub trait BlockChainClient : Sync + Send { let mut corpus = Vec::new(); while corpus.is_empty() { for _ in 0..sample_size { - let block_bytes = self.block(BlockID::Hash(h)).expect("h is either the best_block_hash or an ancestor; qed"); + let block_bytes = self.block(BlockId::Hash(h)).expect("h is either the best_block_hash or an ancestor; qed"); let block = BlockView::new(&block_bytes); let header = block.header_view(); if header.number() == 0 { @@ -249,11 +249,11 @@ pub trait BlockChainClient : Sync + Send { /// Set the mode. fn set_mode(&self, mode: Mode); - /// Returns engine-related extra info for `BlockID`. - fn block_extra_info(&self, id: BlockID) -> Option>; + /// Returns engine-related extra info for `BlockId`. + fn block_extra_info(&self, id: BlockId) -> Option>; - /// Returns engine-related extra info for `UncleID`. - fn uncle_extra_info(&self, id: UncleID) -> Option>; + /// Returns engine-related extra info for `UncleId`. + fn uncle_extra_info(&self, id: UncleId) -> Option>; /// Returns information about pruning/data availability. fn pruning_info(&self) -> PruningInfo; @@ -288,15 +288,15 @@ pub trait ProvingBlockChainClient: BlockChainClient { /// Returns a vector of raw trie nodes (in order from the root) proving the storage query. /// Nodes after `from_level` may be omitted. /// An empty vector indicates unservable query. - fn prove_storage(&self, key1: H256, key2: H256, from_level: u32, id: BlockID) -> Vec; + fn prove_storage(&self, key1: H256, key2: H256, from_level: u32, id: BlockId) -> Vec; /// Prove account existence at a specific block id. /// The key is the keccak hash of the account's address. /// Returns a vector of raw trie nodes (in order from the root) proving the query. /// Nodes after `from_level` may be omitted. /// An empty vector indicates unservable query. - fn prove_account(&self, key1: H256, from_level: u32, id: BlockID) -> Vec; + fn prove_account(&self, key1: H256, from_level: u32, id: BlockId) -> Vec; /// Get code by address hash. - fn code_by_hash(&self, account_key: H256, id: BlockID) -> Bytes; + fn code_by_hash(&self, account_key: H256, id: BlockId) -> Bytes; } \ No newline at end of file diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index b171856b9..ad541391d 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -23,7 +23,7 @@ use account_provider::{AccountProvider, Error as AccountError}; use views::{BlockView, HeaderView}; use header::Header; use state::{State, CleanupMode}; -use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockID, CallAnalytics, TransactionID}; +use client::{MiningBlockChainClient, Executive, Executed, EnvInfo, TransactOptions, BlockId, CallAnalytics, TransactionId}; use client::TransactionImportResult; use executive::contract_address; use block::{ClosedBlock, SealedBlock, IsBlock, Block}; @@ -585,7 +585,7 @@ impl Miner { let best_block_header: Header = ::rlp::decode(&chain.best_block_header()); transactions.into_iter() .map(|tx| { - if chain.transaction_block(TransactionID::Hash(tx.hash())).is_some() { + if chain.transaction_block(TransactionId::Hash(tx.hash())).is_some() { debug!(target: "miner", "Rejected tx {:?}: already in the blockchain", tx.hash()); return Err(Error::Transaction(TransactionError::AlreadyImported)); } @@ -701,7 +701,7 @@ impl MinerService for Miner { Ok(ret) }, None => { - chain.call(t, BlockID::Latest, analytics) + chain.call(t, BlockId::Latest, analytics) } } } @@ -1094,7 +1094,7 @@ impl MinerService for Miner { fn fetch_transactions(chain: &MiningBlockChainClient, hash: &H256) -> Vec { let block = chain - .block(BlockID::Hash(*hash)) + .block(BlockId::Hash(*hash)) // Client should send message after commit to db and inserting to chain. .expect("Expected in-chain blocks."); let block = BlockView::new(&block); diff --git a/ethcore/src/snapshot/error.rs b/ethcore/src/snapshot/error.rs index d417695f0..cc84d8e48 100644 --- a/ethcore/src/snapshot/error.rs +++ b/ethcore/src/snapshot/error.rs @@ -18,7 +18,7 @@ use std::fmt; -use ids::BlockID; +use ids::BlockId; use util::H256; use util::trie::TrieError; @@ -28,7 +28,7 @@ use rlp::DecoderError; #[derive(Debug)] pub enum Error { /// Invalid starting block for snapshot. - InvalidStartingBlock(BlockID), + InvalidStartingBlock(BlockId), /// Block not found. BlockNotFound(H256), /// Incomplete chain. diff --git a/ethcore/src/snapshot/mod.rs b/ethcore/src/snapshot/mod.rs index 408941309..9b45f2687 100644 --- a/ethcore/src/snapshot/mod.rs +++ b/ethcore/src/snapshot/mod.rs @@ -27,7 +27,7 @@ use account_db::{AccountDB, AccountDBMut}; use blockchain::{BlockChain, BlockProvider}; use engines::Engine; use header::Header; -use ids::BlockID; +use ids::BlockId; use views::BlockView; use util::{Bytes, Hashable, HashDB, DBValue, snappy, U256, Uint}; @@ -129,7 +129,7 @@ pub fn take_snapshot( p: &Progress ) -> Result<(), Error> { let start_header = try!(chain.block_header(&block_at) - .ok_or(Error::InvalidStartingBlock(BlockID::Hash(block_at)))); + .ok_or(Error::InvalidStartingBlock(BlockId::Hash(block_at)))); let state_root = start_header.state_root(); let number = start_header.number(); diff --git a/ethcore/src/snapshot/service.rs b/ethcore/src/snapshot/service.rs index 89ee68de0..05c4c1f9f 100644 --- a/ethcore/src/snapshot/service.rs +++ b/ethcore/src/snapshot/service.rs @@ -30,7 +30,7 @@ use blockchain::BlockChain; use client::{BlockChainClient, Client}; use engines::Engine; use error::Error; -use ids::BlockID; +use ids::BlockId; use service::ClientIoMessage; use io::IoChannel; @@ -354,7 +354,7 @@ impl Service { let writer = try!(LooseWriter::new(temp_dir.clone())); let guard = Guard::new(temp_dir.clone()); - let res = client.take_snapshot(writer, BlockID::Number(num), &self.progress); + let res = client.take_snapshot(writer, BlockId::Number(num), &self.progress); self.taking_snapshot.store(false, Ordering::SeqCst); if let Err(e) = res { diff --git a/ethcore/src/snapshot/tests/service.rs b/ethcore/src/snapshot/tests/service.rs index e136985c6..efdb12323 100644 --- a/ethcore/src/snapshot/tests/service.rs +++ b/ethcore/src/snapshot/tests/service.rs @@ -19,7 +19,7 @@ use std::sync::Arc; use client::{BlockChainClient, Client}; -use ids::BlockID; +use ids::BlockId; use snapshot::service::{Service, ServiceParams}; use snapshot::{self, ManifestData, SnapshotService}; use spec::Spec; @@ -96,8 +96,8 @@ fn restored_is_equivalent() { assert_eq!(service.status(), ::snapshot::RestorationStatus::Inactive); for x in 0..NUM_BLOCKS { - let block1 = client.block(BlockID::Number(x as u64)).unwrap(); - let block2 = client2.block(BlockID::Number(x as u64)).unwrap(); + let block1 = client.block(BlockId::Number(x as u64)).unwrap(); + let block2 = client2.block(BlockId::Number(x as u64)).unwrap(); assert_eq!(block1, block2); } diff --git a/ethcore/src/snapshot/watcher.rs b/ethcore/src/snapshot/watcher.rs index 43439e437..ab4dde134 100644 --- a/ethcore/src/snapshot/watcher.rs +++ b/ethcore/src/snapshot/watcher.rs @@ -18,7 +18,7 @@ use util::Mutex; use client::{BlockChainClient, Client, ChainNotify}; -use ids::BlockID; +use ids::BlockId; use service::ClientIoMessage; use views::HeaderView; @@ -43,7 +43,7 @@ impl Oracle for StandardOracle where F: Send + Sync + Fn() -> bool { fn to_number(&self, hash: H256) -> Option { - self.client.block_header(BlockID::Hash(hash)).map(|h| HeaderView::new(&h).number()) + self.client.block_header(BlockId::Hash(hash)).map(|h| HeaderView::new(&h).number()) } fn is_major_importing(&self) -> bool { diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index 427082823..0b688345d 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . use io::IoChannel; -use client::{BlockChainClient, MiningBlockChainClient, Client, ClientConfig, BlockID}; +use client::{BlockChainClient, MiningBlockChainClient, Client, ClientConfig, BlockId}; use state::CleanupMode; use ethereum; use block::IsBlock; @@ -99,7 +99,7 @@ fn imports_good_block() { client.flush_queue(); client.import_verified_blocks(); - let block = client.block_header(BlockID::Number(1)).unwrap(); + let block = client.block_header(BlockId::Number(1)).unwrap(); assert!(!block.is_empty()); } @@ -117,7 +117,7 @@ fn query_none_block() { IoChannel::disconnected(), &db_config ).unwrap(); - let non_existant = client.block_header(BlockID::Number(188)); + let non_existant = client.block_header(BlockId::Number(188)); assert!(non_existant.is_none()); } @@ -125,7 +125,7 @@ fn query_none_block() { fn query_bad_block() { let client_result = get_test_client_with_blocks(vec![get_bad_state_dummy_block()]); let client = client_result.reference(); - let bad_block:Option = client.block_header(BlockID::Number(1)); + let bad_block:Option = client.block_header(BlockId::Number(1)); assert!(bad_block.is_none()); } @@ -146,8 +146,8 @@ fn returns_logs() { let client_result = get_test_client_with_blocks(vec![dummy_block.clone()]); let client = client_result.reference(); let logs = client.logs(Filter { - from_block: BlockID::Earliest, - to_block: BlockID::Latest, + from_block: BlockId::Earliest, + to_block: BlockId::Latest, address: None, topics: vec![], limit: None, @@ -161,8 +161,8 @@ fn returns_logs_with_limit() { let client_result = get_test_client_with_blocks(vec![dummy_block.clone()]); let client = client_result.reference(); let logs = client.logs(Filter { - from_block: BlockID::Earliest, - to_block: BlockID::Latest, + from_block: BlockId::Earliest, + to_block: BlockId::Latest, address: None, topics: vec![], limit: Some(2), @@ -176,7 +176,7 @@ fn returns_block_body() { let client_result = get_test_client_with_blocks(vec![dummy_block.clone()]); let client = client_result.reference(); let block = BlockView::new(&dummy_block); - let body = client.block_body(BlockID::Hash(block.header().hash())).unwrap(); + let body = client.block_body(BlockId::Hash(block.header().hash())).unwrap(); let body = Rlp::new(&body); assert_eq!(body.item_count(), 2); assert_eq!(body.at(0).as_raw()[..], block.rlp().at(1).as_raw()[..]); @@ -187,7 +187,7 @@ fn returns_block_body() { fn imports_block_sequence() { let client_result = generate_dummy_client(6); let client = client_result.reference(); - let block = client.block_header(BlockID::Number(5)).unwrap(); + let block = client.block_header(BlockId::Number(5)).unwrap(); assert!(!block.is_empty()); } diff --git a/ethcore/src/tests/rpc.rs b/ethcore/src/tests/rpc.rs index b021e750d..2da94b3d0 100644 --- a/ethcore/src/tests/rpc.rs +++ b/ethcore/src/tests/rpc.rs @@ -19,7 +19,7 @@ use nanoipc; use std::sync::Arc; use std::sync::atomic::{Ordering, AtomicBool}; -use client::{Client, BlockChainClient, ClientConfig, BlockID}; +use client::{Client, BlockChainClient, ClientConfig, BlockId}; use client::remote::RemoteClient; use tests::helpers::*; use devtools::*; @@ -71,7 +71,7 @@ fn can_query_block() { run_test_worker(scope, stop_guard.share(), socket_path); let remote_client = nanoipc::generic_client::>(socket_path).unwrap(); - let non_existant_block = remote_client.block_header(BlockID::Number(999)); + let non_existant_block = remote_client.block_header(BlockId::Number(999)); assert!(non_existant_block.is_none()); }) diff --git a/ethcore/src/trace/db.rs b/ethcore/src/trace/db.rs index 14129d4d9..174c9b386 100644 --- a/ethcore/src/trace/db.rs +++ b/ethcore/src/trace/db.rs @@ -94,7 +94,7 @@ impl Key for TraceGroupPosition { } #[derive(Debug, Hash, Eq, PartialEq)] -enum CacheID { +enum CacheId { Trace(H256), Bloom(TraceGroupPosition), } @@ -104,7 +104,7 @@ pub struct TraceDB where T: DatabaseExtras { // cache traces: RwLock>, blooms: RwLock>, - cache_manager: RwLock>, + cache_manager: RwLock>, // db tracesdb: Arc, // config, @@ -119,7 +119,7 @@ impl BloomGroupDatabase for TraceDB where T: DatabaseExtras { fn blooms_at(&self, position: &GroupPosition) -> Option { let position = TraceGroupPosition::from(position.clone()); let result = self.tracesdb.read_with_cache(db::COL_TRACE, &self.blooms, &position).map(Into::into); - self.note_used(CacheID::Bloom(position)); + self.note_used(CacheId::Bloom(position)); result } } @@ -152,7 +152,7 @@ impl TraceDB where T: DatabaseExtras { } /// Let the cache system know that a cacheable item has been used. - fn note_used(&self, id: CacheID) { + fn note_used(&self, id: CacheId) { let mut cache_manager = self.cache_manager.write(); cache_manager.note_used(id); } @@ -168,8 +168,8 @@ impl TraceDB where T: DatabaseExtras { cache_manager.collect_garbage(current_size, | ids | { for id in &ids { match *id { - CacheID::Trace(ref h) => { traces.remove(h); }, - CacheID::Bloom(ref h) => { blooms.remove(h); }, + CacheId::Trace(ref h) => { traces.remove(h); }, + CacheId::Bloom(ref h) => { blooms.remove(h); }, } } traces.shrink_to_fit(); @@ -182,7 +182,7 @@ impl TraceDB where T: DatabaseExtras { /// Returns traces for block with hash. fn traces(&self, block_hash: &H256) -> Option { let result = self.tracesdb.read_with_cache(db::COL_TRACE, &self.traces, block_hash); - self.note_used(CacheID::Trace(block_hash.clone())); + self.note_used(CacheId::Trace(block_hash.clone())); result } @@ -289,7 +289,7 @@ impl TraceDatabase for TraceDB where T: DatabaseExtras { batch.extend_with_cache(db::COL_TRACE, &mut *blooms, blooms_to_insert, CacheUpdatePolicy::Remove); // note_used must be called after locking blooms to avoid cache/traces deadlock on garbage collection for key in blooms_keys { - self.note_used(CacheID::Bloom(key)); + self.note_used(CacheId::Bloom(key)); } } @@ -300,7 +300,7 @@ impl TraceDatabase for TraceDB where T: DatabaseExtras { // cause this value might be queried by hash later batch.write_with_cache(db::COL_TRACE, &mut *traces, request.block_hash, request.traces, CacheUpdatePolicy::Overwrite); // note_used must be called after locking traces to avoid cache/traces deadlock on garbage collection - self.note_used(CacheID::Trace(request.block_hash.clone())); + self.note_used(CacheId::Trace(request.block_hash.clone())); } } diff --git a/ethcore/src/types/filter.rs b/ethcore/src/types/filter.rs index e3487e5f6..ecc997f71 100644 --- a/ethcore/src/types/filter.rs +++ b/ethcore/src/types/filter.rs @@ -18,17 +18,17 @@ use util::{Address, H256, Hashable, H2048}; use util::bloom::Bloomable; -use client::BlockID; +use client::BlockId; use log_entry::LogEntry; /// Blockchain Filter. #[derive(Binary, Debug, PartialEq)] pub struct Filter { /// Blockchain will be searched from this block. - pub from_block: BlockID, + pub from_block: BlockId, /// Till this block. - pub to_block: BlockID, + pub to_block: BlockId, /// Search addresses. /// @@ -114,14 +114,14 @@ impl Filter { mod tests { use util::FixedHash; use filter::Filter; - use client::BlockID; + use client::BlockId; use log_entry::LogEntry; #[test] fn test_bloom_possibilities_none() { let none_filter = Filter { - from_block: BlockID::Earliest, - to_block: BlockID::Latest, + from_block: BlockId::Earliest, + to_block: BlockId::Latest, address: None, topics: vec![None, None, None, None], limit: None, @@ -136,8 +136,8 @@ mod tests { #[test] fn test_bloom_possibilities_single_address_and_topic() { let filter = Filter { - from_block: BlockID::Earliest, - to_block: BlockID::Latest, + from_block: BlockId::Earliest, + to_block: BlockId::Latest, address: Some(vec!["b372018f3be9e171df0581136b59d2faf73a7d5d".into()]), topics: vec![ Some(vec!["ff74e91598aed6ae5d2fdcf8b24cd2c7be49a0808112a305069355b7160f23f9".into()]), @@ -155,8 +155,8 @@ mod tests { #[test] fn test_bloom_possibilities_single_address_and_many_topics() { let filter = Filter { - from_block: BlockID::Earliest, - to_block: BlockID::Latest, + from_block: BlockId::Earliest, + to_block: BlockId::Latest, address: Some(vec!["b372018f3be9e171df0581136b59d2faf73a7d5d".into()]), topics: vec![ Some(vec!["ff74e91598aed6ae5d2fdcf8b24cd2c7be49a0808112a305069355b7160f23f9".into()]), @@ -174,8 +174,8 @@ mod tests { #[test] fn test_bloom_possibilites_multiple_addresses_and_topics() { let filter = Filter { - from_block: BlockID::Earliest, - to_block: BlockID::Latest, + from_block: BlockId::Earliest, + to_block: BlockId::Latest, address: Some(vec![ "b372018f3be9e171df0581136b59d2faf73a7d5d".into(), "b372018f3be9e171df0581136b59d2faf73a7d5d".into(), @@ -204,8 +204,8 @@ mod tests { #[test] fn test_filter_matches() { let filter = Filter { - from_block: BlockID::Earliest, - to_block: BlockID::Latest, + from_block: BlockId::Earliest, + to_block: BlockId::Latest, address: Some(vec!["b372018f3be9e171df0581136b59d2faf73a7d5d".into()]), topics: vec![ Some(vec!["ff74e91598aed6ae5d2fdcf8b24cd2c7be49a0808112a305069355b7160f23f9".into()]), diff --git a/ethcore/src/types/ids.rs b/ethcore/src/types/ids.rs index 1fe81f392..2c3c777b4 100644 --- a/ethcore/src/types/ids.rs +++ b/ethcore/src/types/ids.rs @@ -21,7 +21,7 @@ use header::BlockNumber; /// Uniquely identifies block. #[derive(Debug, PartialEq, Copy, Clone, Hash, Eq, Binary)] -pub enum BlockID { +pub enum BlockId { /// Block's sha3. /// Querying by hash is always faster. Hash(H256), @@ -37,28 +37,28 @@ pub enum BlockID { /// Uniquely identifies transaction. #[derive(Debug, PartialEq, Clone, Hash, Eq, Binary)] -pub enum TransactionID { +pub enum TransactionId { /// Transaction's sha3. Hash(H256), /// Block id and transaction index within this block. /// Querying by block position is always faster. - Location(BlockID, usize) + Location(BlockId, usize) } /// Uniquely identifies Trace. #[derive(Binary)] pub struct TraceId { /// Transaction - pub transaction: TransactionID, + pub transaction: TransactionId, /// Trace address within transaction. pub address: Vec, } /// Uniquely identifies Uncle. #[derive(Debug, PartialEq, Eq, Copy, Clone, Binary)] -pub struct UncleID { +pub struct UncleId { /// Block id. - pub block: BlockID, + pub block: BlockId, /// Position in block. pub position: usize } diff --git a/ethcore/src/types/trace_filter.rs b/ethcore/src/types/trace_filter.rs index c17cc9e85..af9d7c3ee 100644 --- a/ethcore/src/types/trace_filter.rs +++ b/ethcore/src/types/trace_filter.rs @@ -18,13 +18,13 @@ use std::ops::Range; use util::{Address}; -use types::ids::BlockID; +use types::ids::BlockId; /// Easy to use trace filter. #[derive(Binary)] pub struct Filter { /// Range of filtering. - pub range: Range, + pub range: Range, /// From address. pub from_address: Vec
    , /// To address. diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index 56b2c1ccb..c86123d1a 100644 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -20,7 +20,7 @@ use std::collections::HashMap; use time; use ethkey::Address; use {json, SafeAccount, Error}; -use json::UUID; +use json::Uuid; use super::KeyDirectory; const IGNORED_FILES: &'static [&'static str] = &["thumbs.db", "address_book.json"]; @@ -113,7 +113,7 @@ impl KeyDirectory for DiskDirectory { // build file path let filename = account.filename.as_ref().cloned().unwrap_or_else(|| { let timestamp = time::strftime("%Y-%m-%dT%H-%M-%S", &time::now_utc()).expect("Time-format string is valid."); - format!("UTC--{}Z--{}", timestamp, UUID::from(account.id)) + format!("UTC--{}Z--{}", timestamp, Uuid::from(account.id)) }); // update account filename diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index 4991c4714..3747431fb 100644 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -24,7 +24,7 @@ use dir::KeyDirectory; use account::SafeAccount; use {Error, SecretStore}; use json; -use json::UUID; +use json::Uuid; use parking_lot::RwLock; use presale::PresaleWallet; use import; @@ -154,7 +154,7 @@ impl SecretStore for EthStore { account.public(password) } - fn uuid(&self, address: &Address) -> Result { + fn uuid(&self, address: &Address) -> Result { let account = try!(self.get(address)); Ok(account.id.into()) } diff --git a/ethstore/src/json/error.rs b/ethstore/src/json/error.rs index 5e077cfad..a3ea4d326 100644 --- a/ethstore/src/json/error.rs +++ b/ethstore/src/json/error.rs @@ -21,7 +21,7 @@ pub enum Error { UnsupportedCipher, InvalidCipherParams, UnsupportedKdf, - InvalidUUID, + InvalidUuid, UnsupportedVersion, InvalidCiphertext, InvalidH256, @@ -31,7 +31,7 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match *self { - Error::InvalidUUID => write!(f, "Invalid UUID"), + Error::InvalidUuid => write!(f, "Invalid Uuid"), Error::UnsupportedVersion => write!(f, "Unsupported version"), Error::UnsupportedKdf => write!(f, "Unsupported kdf"), Error::InvalidCiphertext => write!(f, "Invalid ciphertext"), diff --git a/ethstore/src/json/id.rs b/ethstore/src/json/id.rs index ff282a9f8..664716d95 100644 --- a/ethstore/src/json/id.rs +++ b/ethstore/src/json/id.rs @@ -23,15 +23,15 @@ use super::Error; /// Universaly unique identifier. #[derive(Debug, PartialEq)] -pub struct UUID([u8; 16]); +pub struct Uuid([u8; 16]); -impl From<[u8; 16]> for UUID { +impl From<[u8; 16]> for Uuid { fn from(uuid: [u8; 16]) -> Self { - UUID(uuid) + Uuid(uuid) } } -impl<'a> Into for &'a UUID { +impl<'a> Into for &'a Uuid { fn into(self) -> String { let d1 = &self.0[0..4]; let d2 = &self.0[4..6]; @@ -42,44 +42,44 @@ impl<'a> Into for &'a UUID { } } -impl Into for UUID { +impl Into for Uuid { fn into(self) -> String { Into::into(&self) } } -impl Into<[u8; 16]> for UUID { +impl Into<[u8; 16]> for Uuid { fn into(self) -> [u8; 16] { self.0 } } -impl fmt::Display for UUID { +impl fmt::Display for Uuid { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - let s: String = (self as &UUID).into(); + let s: String = (self as &Uuid).into(); write!(f, "{}", s) } } fn copy_into(from: &str, into: &mut [u8]) -> Result<(), Error> { - let from = try!(from.from_hex().map_err(|_| Error::InvalidUUID)); + let from = try!(from.from_hex().map_err(|_| Error::InvalidUuid)); if from.len() != into.len() { - return Err(Error::InvalidUUID); + return Err(Error::InvalidUuid); } into.copy_from_slice(&from); Ok(()) } -impl str::FromStr for UUID { +impl str::FromStr for Uuid { type Err = Error; fn from_str(s: &str) -> Result { let parts: Vec<&str> = s.split("-").collect(); if parts.len() != 5 { - return Err(Error::InvalidUUID); + return Err(Error::InvalidUuid); } let mut uuid = [0u8; 16]; @@ -90,17 +90,17 @@ impl str::FromStr for UUID { try!(copy_into(parts[3], &mut uuid[8..10])); try!(copy_into(parts[4], &mut uuid[10..16])); - Ok(UUID(uuid)) + Ok(Uuid(uuid)) } } -impl From<&'static str> for UUID { +impl From<&'static str> for Uuid { fn from(s: &'static str) -> Self { s.parse().expect(&format!("invalid string literal for {}: '{}'", stringify!(Self), s)) } } -impl Serialize for UUID { +impl Serialize for Uuid { fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer { let s: String = self.into(); @@ -108,17 +108,17 @@ impl Serialize for UUID { } } -impl Deserialize for UUID { +impl Deserialize for Uuid { fn deserialize(deserializer: &mut D) -> Result where D: Deserializer { - deserializer.deserialize(UUIDVisitor) + deserializer.deserialize(UuidVisitor) } } -struct UUIDVisitor; +struct UuidVisitor; -impl Visitor for UUIDVisitor { - type Value = UUID; +impl Visitor for UuidVisitor { + type Value = Uuid; fn visit_str(&mut self, value: &str) -> Result where E: SerdeError { value.parse().map_err(SerdeError::custom) @@ -131,18 +131,18 @@ impl Visitor for UUIDVisitor { #[cfg(test)] mod tests { - use super::UUID; + use super::Uuid; #[test] fn uuid_from_str() { - let uuid: UUID = "3198bc9c-6672-5ab3-d995-4942343ae5b6".into(); - assert_eq!(uuid, UUID::from([0x31, 0x98, 0xbc, 0x9c, 0x66, 0x72, 0x5a, 0xb3, 0xd9, 0x95, 0x49, 0x42, 0x34, 0x3a, 0xe5, 0xb6])); + let uuid: Uuid = "3198bc9c-6672-5ab3-d995-4942343ae5b6".into(); + assert_eq!(uuid, Uuid::from([0x31, 0x98, 0xbc, 0x9c, 0x66, 0x72, 0x5a, 0xb3, 0xd9, 0x95, 0x49, 0x42, 0x34, 0x3a, 0xe5, 0xb6])); } #[test] fn uuid_from_and_to_str() { let from = "3198bc9c-6672-5ab3-d995-4942343ae5b6"; - let uuid: UUID = from.into(); + let uuid: Uuid = from.into(); let to: String = uuid.into(); assert_eq!(from, &to); } diff --git a/ethstore/src/json/key_file.rs b/ethstore/src/json/key_file.rs index 6e37c7c89..093479b3a 100644 --- a/ethstore/src/json/key_file.rs +++ b/ethstore/src/json/key_file.rs @@ -18,11 +18,11 @@ use std::io::{Read, Write}; use serde::{Deserialize, Deserializer, Error}; use serde::de::{Visitor, MapVisitor}; use serde_json; -use super::{UUID, Version, Crypto, H160}; +use super::{Uuid, Version, Crypto, H160}; #[derive(Debug, PartialEq, Serialize)] pub struct KeyFile { - pub id: UUID, + pub id: Uuid, pub version: Version, pub crypto: Crypto, pub address: H160, @@ -31,7 +31,7 @@ pub struct KeyFile { } enum KeyFileField { - ID, + Id, Version, Crypto, Address, @@ -56,7 +56,7 @@ impl Visitor for KeyFileFieldVisitor { where E: Error { match value { - "id" => Ok(KeyFileField::ID), + "id" => Ok(KeyFileField::Id), "version" => Ok(KeyFileField::Version), "crypto" => Ok(KeyFileField::Crypto), "Crypto" => Ok(KeyFileField::Crypto), @@ -94,7 +94,7 @@ impl Visitor for KeyFileVisitor { loop { match try!(visitor.visit_key()) { - Some(KeyFileField::ID) => { id = Some(try!(visitor.visit_value())); } + Some(KeyFileField::Id) => { id = Some(try!(visitor.visit_value())); } Some(KeyFileField::Version) => { version = Some(try!(visitor.visit_value())); } Some(KeyFileField::Crypto) => { crypto = Some(try!(visitor.visit_value())); } Some(KeyFileField::Address) => { address = Some(try!(visitor.visit_value())); } @@ -153,7 +153,7 @@ impl KeyFile { mod tests { use std::str::FromStr; use serde_json; - use json::{KeyFile, UUID, Version, Crypto, Cipher, Aes128Ctr, Kdf, Scrypt}; + use json::{KeyFile, Uuid, Version, Crypto, Cipher, Aes128Ctr, Kdf, Scrypt}; #[test] fn basic_keyfile() { @@ -183,7 +183,7 @@ mod tests { }"#; let expected = KeyFile { - id: UUID::from_str("8777d9f6-7860-4b9b-88b7-0b57ee6b3a73").unwrap(), + id: Uuid::from_str("8777d9f6-7860-4b9b-88b7-0b57ee6b3a73").unwrap(), version: Version::V3, address: "6edddfc6349aff20bc6467ccf276c5b52487f7a8".into(), crypto: Crypto { diff --git a/ethstore/src/json/mod.rs.in b/ethstore/src/json/mod.rs.in index 133d9821e..8ad48b994 100644 --- a/ethstore/src/json/mod.rs.in +++ b/ethstore/src/json/mod.rs.in @@ -14,7 +14,7 @@ pub use self::cipher::{Cipher, CipherSer, CipherSerParams, Aes128Ctr}; pub use self::crypto::{Crypto, CipherText}; pub use self::error::Error; pub use self::hash::{H128, H160, H256}; -pub use self::id::UUID; +pub use self::id::Uuid; pub use self::kdf::{Kdf, KdfSer, Prf, Pbkdf2, Scrypt, KdfSerParams}; pub use self::key_file::KeyFile; pub use self::presale::{PresaleWallet, Encseed}; diff --git a/ethstore/src/secret_store.rs b/ethstore/src/secret_store.rs index 06f38922b..bf0a4ec44 100644 --- a/ethstore/src/secret_store.rs +++ b/ethstore/src/secret_store.rs @@ -16,7 +16,7 @@ use ethkey::{Address, Message, Signature, Secret, Public}; use Error; -use json::UUID; +use json::Uuid; pub trait SecretStore: Send + Sync { fn insert_account(&self, secret: Secret, password: &str) -> Result; @@ -30,7 +30,7 @@ pub trait SecretStore: Send + Sync { fn public(&self, account: &Address, password: &str) -> Result; fn accounts(&self) -> Result, Error>; - fn uuid(&self, account: &Address) -> Result; + fn uuid(&self, account: &Address) -> Result; fn name(&self, account: &Address) -> Result; fn meta(&self, account: &Address) -> Result; diff --git a/js/src/jsonrpc/interfaces/parity.js b/js/src/jsonrpc/interfaces/parity.js index 067ced1fc..76886af35 100644 --- a/js/src/jsonrpc/interfaces/parity.js +++ b/js/src/jsonrpc/interfaces/parity.js @@ -43,7 +43,7 @@ export default { }, uuid: { type: String, - desc: 'The account UUID, or null if not available/unknown/not applicable.' + desc: 'The account Uuid, or null if not available/unknown/not applicable.' } } } @@ -66,7 +66,7 @@ export default { }, uuid: { type: String, - desc: 'The account UUID, or null if not available/unknown/not applicable.' + desc: 'The account Uuid, or null if not available/unknown/not applicable.' } } } diff --git a/json/src/misc/account_meta.rs b/json/src/misc/account_meta.rs index 400f9b8df..a347a8cf6 100644 --- a/json/src/misc/account_meta.rs +++ b/json/src/misc/account_meta.rs @@ -29,7 +29,7 @@ pub struct AccountMeta { pub name: String, /// The rest of the metadata of the account. pub meta: String, - /// The 128-bit UUID of the account, if it has one (brain-wallets don't). + /// The 128-bit Uuid of the account, if it has one (brain-wallets don't). pub uuid: Option, } diff --git a/parity/blockchain.rs b/parity/blockchain.rs index 0750d369d..a9d81e5c3 100644 --- a/parity/blockchain.rs +++ b/parity/blockchain.rs @@ -25,7 +25,7 @@ use io::{PanicHandler, ForwardPanic}; use util::{ToPretty, Uint, U256, H256, Address, Hashable}; use rlp::PayloadInfo; use ethcore::service::ClientService; -use ethcore::client::{Mode, DatabaseCompactionProfile, VMType, BlockImportError, BlockChainClient, BlockID}; +use ethcore::client::{Mode, DatabaseCompactionProfile, VMType, BlockImportError, BlockChainClient, BlockId}; use ethcore::error::ImportError; use ethcore::miner::Miner; use ethcore::verification::queue::VerifierSettings; @@ -101,8 +101,8 @@ pub struct ExportBlockchain { pub wal: bool, pub fat_db: Switch, pub tracing: Switch, - pub from_block: BlockID, - pub to_block: BlockID, + pub from_block: BlockId, + pub to_block: BlockId, pub check_seal: bool, } @@ -119,7 +119,7 @@ pub struct ExportState { pub wal: bool, pub fat_db: Switch, pub tracing: Switch, - pub at: BlockID, + pub at: BlockId, pub storage: bool, pub code: bool, pub min_balance: Option, @@ -384,7 +384,7 @@ fn execute_export(cmd: ExportBlockchain) -> Result { if i % 10000 == 0 { info!("#{}", i); } - let b = try!(client.block(BlockID::Number(i)).ok_or("Error exporting incomplete chain")); + let b = try!(client.block(BlockId::Number(i)).ok_or("Error exporting incomplete chain")); match format { DataFormat::Binary => { out.write(&b).expect("Couldn't write to stream."); } DataFormat::Hex => { out.write_fmt(format_args!("{}", b.pretty())).expect("Couldn't write to stream."); } diff --git a/parity/configuration.rs b/parity/configuration.rs index 60116ef99..5b7c34c27 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -729,7 +729,7 @@ mod tests { use super::*; use cli::Args; use ethcore_rpc::NetworkSettings; - use ethcore::client::{VMType, BlockID}; + use ethcore::client::{VMType, BlockId}; use ethcore::miner::{MinerOptions, PrioritizationStrategy}; use helpers::{replace_home, default_network_config}; use run::RunCmd; @@ -838,8 +838,8 @@ mod tests { wal: true, tracing: Default::default(), fat_db: Default::default(), - from_block: BlockID::Number(1), - to_block: BlockID::Latest, + from_block: BlockId::Number(1), + to_block: BlockId::Latest, check_seal: true, }))); } @@ -860,7 +860,7 @@ mod tests { wal: true, tracing: Default::default(), fat_db: Default::default(), - at: BlockID::Latest, + at: BlockId::Latest, storage: true, code: true, min_balance: None, @@ -884,8 +884,8 @@ mod tests { wal: true, tracing: Default::default(), fat_db: Default::default(), - from_block: BlockID::Number(1), - to_block: BlockID::Latest, + from_block: BlockId::Number(1), + to_block: BlockId::Latest, check_seal: true, }))); } diff --git a/parity/dapps.rs b/parity/dapps.rs index 16ae4dd98..ec6fd8846 100644 --- a/parity/dapps.rs +++ b/parity/dapps.rs @@ -106,7 +106,7 @@ mod server { use util::{Bytes, Address, U256}; use ethcore::transaction::{Transaction, Action}; - use ethcore::client::{Client, BlockChainClient, BlockID}; + use ethcore::client::{Client, BlockChainClient, BlockId}; use rpc_apis; use ethcore_rpc::is_major_importing; @@ -182,7 +182,7 @@ mod server { data: data, }.fake_sign(from); - self.client.call(&transaction, BlockID::Latest, Default::default()) + self.client.call(&transaction, BlockId::Latest, Default::default()) .map_err(|e| format!("{:?}", e)) .map(|executed| { executed.output diff --git a/parity/helpers.rs b/parity/helpers.rs index 654270b64..60a04bc45 100644 --- a/parity/helpers.rs +++ b/parity/helpers.rs @@ -20,7 +20,7 @@ use std::time::Duration; use std::fs::File; use util::{clean_0x, U256, Uint, Address, path, CompactionProfile}; use util::journaldb::Algorithm; -use ethcore::client::{Mode, BlockID, VMType, DatabaseCompactionProfile, ClientConfig, VerifierType}; +use ethcore::client::{Mode, BlockId, VMType, DatabaseCompactionProfile, ClientConfig, VerifierType}; use ethcore::miner::{PendingSet, GasLimit, PrioritizationStrategy}; use cache::CacheConfig; use dir::DatabaseDirectories; @@ -62,13 +62,13 @@ pub fn to_mode(s: &str, timeout: u64, alarm: u64) -> Result { } } -pub fn to_block_id(s: &str) -> Result { +pub fn to_block_id(s: &str) -> Result { if s == "latest" { - Ok(BlockID::Latest) + Ok(BlockId::Latest) } else if let Ok(num) = s.parse() { - Ok(BlockID::Number(num)) + Ok(BlockId::Number(num)) } else if let Ok(hash) = s.parse() { - Ok(BlockID::Hash(hash)) + Ok(BlockId::Hash(hash)) } else { Err("Invalid block.".into()) } @@ -327,7 +327,7 @@ mod tests { use std::io::Write; use devtools::RandomTempPath; use util::{U256}; - use ethcore::client::{Mode, BlockID}; + use ethcore::client::{Mode, BlockId}; use ethcore::miner::PendingSet; use super::{to_duration, to_mode, to_block_id, to_u256, to_pending_set, to_address, to_addresses, to_price, geth_ipc_path, to_bootnodes, password_from_file}; @@ -361,13 +361,13 @@ mod tests { #[test] fn test_to_block_id() { - assert_eq!(to_block_id("latest").unwrap(), BlockID::Latest); - assert_eq!(to_block_id("0").unwrap(), BlockID::Number(0)); - assert_eq!(to_block_id("2").unwrap(), BlockID::Number(2)); - assert_eq!(to_block_id("15").unwrap(), BlockID::Number(15)); + assert_eq!(to_block_id("latest").unwrap(), BlockId::Latest); + assert_eq!(to_block_id("0").unwrap(), BlockId::Number(0)); + assert_eq!(to_block_id("2").unwrap(), BlockId::Number(2)); + assert_eq!(to_block_id("15").unwrap(), BlockId::Number(15)); assert_eq!( to_block_id("9fc84d84f6a785dc1bd5abacfcf9cbdd3b6afb80c0f799bfb2fd42c44a0c224e").unwrap(), - BlockID::Hash("9fc84d84f6a785dc1bd5abacfcf9cbdd3b6afb80c0f799bfb2fd42c44a0c224e".parse().unwrap()) + BlockId::Hash("9fc84d84f6a785dc1bd5abacfcf9cbdd3b6afb80c0f799bfb2fd42c44a0c224e".parse().unwrap()) ); } diff --git a/parity/informant.rs b/parity/informant.rs index d3e3c8a20..1caeb1b7c 100644 --- a/parity/informant.rs +++ b/parity/informant.rs @@ -184,12 +184,12 @@ impl ChainNotify for Informant { let ripe = Instant::now() > *last_import + Duration::from_secs(1) && !importing; let txs_imported = imported.iter() .take(imported.len() - if ripe {1} else {0}) - .filter_map(|h| self.client.block(BlockID::Hash(*h))) + .filter_map(|h| self.client.block(BlockId::Hash(*h))) .map(|b| BlockView::new(&b).transactions_count()) .sum(); if ripe { - if let Some(block) = imported.last().and_then(|h| self.client.block(BlockID::Hash(*h))) { + if let Some(block) = imported.last().and_then(|h| self.client.block(BlockId::Hash(*h))) { let view = BlockView::new(&block); let header = view.header(); let tx_count = view.transactions_count(); diff --git a/parity/snapshot.rs b/parity/snapshot.rs index d8323084d..804047596 100644 --- a/parity/snapshot.rs +++ b/parity/snapshot.rs @@ -26,7 +26,7 @@ use ethcore::snapshot::service::Service as SnapshotService; use ethcore::service::ClientService; use ethcore::client::{Mode, DatabaseCompactionProfile, VMType}; use ethcore::miner::Miner; -use ethcore::ids::BlockID; +use ethcore::ids::BlockId; use cache::CacheConfig; use params::{SpecType, Pruning, Switch, tracing_switch_to_bool, fatdb_switch_to_bool}; @@ -60,7 +60,7 @@ pub struct SnapshotCommand { pub file_path: Option, pub wal: bool, pub kind: Kind, - pub block_at: BlockID, + pub block_at: BlockId, } // helper for reading chunks from arbitrary reader and feeding them into the diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 6b9f47de3..d68d59e59 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -32,7 +32,7 @@ use util::sha3::*; use util::{FromHex, Mutex}; use rlp::{self, UntrustedRlp, View}; use ethcore::account_provider::AccountProvider; -use ethcore::client::{MiningBlockChainClient, BlockID, TransactionID, UncleID}; +use ethcore::client::{MiningBlockChainClient, BlockId, TransactionId, UncleId}; use ethcore::header::{Header as BlockHeader, BlockNumber as EthBlockNumber}; use ethcore::block::IsBlock; use ethcore::views::*; @@ -119,7 +119,7 @@ impl EthClient where } } - fn block(&self, id: BlockID, include_txs: bool) -> Result, Error> { + fn block(&self, id: BlockId, include_txs: bool) -> Result, Error> { let client = take_weak!(self.client); match (client.block(id.clone()), client.block_total_difficulty(id)) { (Some(bytes), Some(total_difficulty)) => { @@ -159,20 +159,20 @@ impl EthClient where } } - fn transaction(&self, id: TransactionID) -> Result, Error> { + fn transaction(&self, id: TransactionId) -> Result, Error> { match take_weak!(self.client).transaction(id) { Some(t) => Ok(Some(Transaction::from(t))), None => Ok(None), } } - fn uncle(&self, id: UncleID) -> Result, Error> { + fn uncle(&self, id: UncleId) -> Result, Error> { let client = take_weak!(self.client); let uncle: BlockHeader = match client.uncle(id) { Some(rlp) => rlp::decode(&rlp), None => { return Ok(None); } }; - let parent_difficulty = match client.block_total_difficulty(BlockID::Hash(uncle.parent_hash().clone())) { + let parent_difficulty = match client.block_total_difficulty(BlockId::Hash(uncle.parent_hash().clone())) { Some(difficulty) => difficulty, None => { return Ok(None); } }; @@ -393,7 +393,7 @@ impl Eth for EthClient where fn block_transaction_count_by_hash(&self, hash: RpcH256) -> Result, Error> { try!(self.active()); Ok( - take_weak!(self.client).block(BlockID::Hash(hash.into())) + take_weak!(self.client).block(BlockId::Hash(hash.into())) .map(|bytes| BlockView::new(&bytes).transactions_count().into()) ) } @@ -416,7 +416,7 @@ impl Eth for EthClient where try!(self.active()); Ok( - take_weak!(self.client).block(BlockID::Hash(hash.into())) + take_weak!(self.client).block(BlockId::Hash(hash.into())) .map(|bytes| BlockView::new(&bytes).uncles_count().into()) ) } @@ -449,7 +449,7 @@ impl Eth for EthClient where fn block_by_hash(&self, hash: RpcH256, include_txs: bool) -> Result, Error> { try!(self.active()); - self.block(BlockID::Hash(hash.into()), include_txs) + self.block(BlockId::Hash(hash.into()), include_txs) } fn block_by_number(&self, num: BlockNumber, include_txs: bool) -> Result, Error> { @@ -463,19 +463,19 @@ impl Eth for EthClient where let hash: H256 = hash.into(); let miner = take_weak!(self.miner); let client = take_weak!(self.client); - Ok(try!(self.transaction(TransactionID::Hash(hash))).or_else(|| miner.transaction(client.chain_info().best_block_number, &hash).map(Into::into))) + Ok(try!(self.transaction(TransactionId::Hash(hash))).or_else(|| miner.transaction(client.chain_info().best_block_number, &hash).map(Into::into))) } fn transaction_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> Result, Error> { try!(self.active()); - self.transaction(TransactionID::Location(BlockID::Hash(hash.into()), index.value())) + self.transaction(TransactionId::Location(BlockId::Hash(hash.into()), index.value())) } fn transaction_by_block_number_and_index(&self, num: BlockNumber, index: Index) -> Result, Error> { try!(self.active()); - self.transaction(TransactionID::Location(num.into(), index.value())) + self.transaction(TransactionId::Location(num.into(), index.value())) } fn transaction_receipt(&self, hash: RpcH256) -> Result, Error> { @@ -488,7 +488,7 @@ impl Eth for EthClient where (Some(receipt), true) => Ok(Some(receipt.into())), _ => { let client = take_weak!(self.client); - let receipt = client.transaction_receipt(TransactionID::Hash(hash)); + let receipt = client.transaction_receipt(TransactionId::Hash(hash)); Ok(receipt.map(Into::into)) } } @@ -497,13 +497,13 @@ impl Eth for EthClient where fn uncle_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> Result, Error> { try!(self.active()); - self.uncle(UncleID { block: BlockID::Hash(hash.into()), position: index.value() }) + self.uncle(UncleId { block: BlockId::Hash(hash.into()), position: index.value() }) } fn uncle_by_block_number_and_index(&self, num: BlockNumber, index: Index) -> Result, Error> { try!(self.active()); - self.uncle(UncleID { block: num.into(), position: index.value() }) + self.uncle(UncleId { block: num.into(), position: index.value() }) } fn compilers(&self) -> Result, Error> { diff --git a/rpc/src/v1/impls/eth_filter.rs b/rpc/src/v1/impls/eth_filter.rs index dd1c937ac..695ff5251 100644 --- a/rpc/src/v1/impls/eth_filter.rs +++ b/rpc/src/v1/impls/eth_filter.rs @@ -21,7 +21,7 @@ use std::collections::HashSet; use jsonrpc_core::*; use ethcore::miner::MinerService; use ethcore::filter::Filter as EthcoreFilter; -use ethcore::client::{BlockChainClient, BlockID}; +use ethcore::client::{BlockChainClient, BlockId}; use util::Mutex; use v1::traits::EthFilter; use v1::types::{BlockNumber, Index, Filter, FilterChanges, Log, H256 as RpcH256, U256 as RpcU256}; @@ -98,7 +98,7 @@ impl EthFilter for EthFilterClient // + 1, cause we want to return hashes including current block hash. let current_number = client.chain_info().best_block_number + 1; let hashes = (*block_number..current_number).into_iter() - .map(BlockID::Number) + .map(BlockId::Number) .filter_map(|id| client.block_hash(id)) .map(Into::into) .collect::>(); @@ -140,10 +140,10 @@ impl EthFilter for EthFilterClient // build appropriate filter let mut filter: EthcoreFilter = filter.clone().into(); - filter.from_block = BlockID::Number(*block_number); - filter.to_block = BlockID::Latest; + filter.from_block = BlockId::Number(*block_number); + filter.to_block = BlockId::Latest; - // retrieve logs in range from_block..min(BlockID::Latest..to_block) + // retrieve logs in range from_block..min(BlockId::Latest..to_block) let mut logs = client.logs(filter.clone()) .into_iter() .map(From::from) diff --git a/rpc/src/v1/impls/traces.rs b/rpc/src/v1/impls/traces.rs index e85d961a1..0b287ce29 100644 --- a/rpc/src/v1/impls/traces.rs +++ b/rpc/src/v1/impls/traces.rs @@ -19,7 +19,7 @@ use std::sync::{Weak, Arc}; use jsonrpc_core::*; use rlp::{UntrustedRlp, View}; -use ethcore::client::{BlockChainClient, CallAnalytics, TransactionID, TraceId}; +use ethcore::client::{BlockChainClient, CallAnalytics, TransactionId, TraceId}; use ethcore::miner::MinerService; use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action}; use v1::traits::Traces; @@ -100,7 +100,7 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: from_params::<(H256,)>(params) .and_then(|(transaction_hash,)| { let client = take_weak!(self.client); - let traces = client.transaction_traces(TransactionID::Hash(transaction_hash.into())); + let traces = client.transaction_traces(TransactionId::Hash(transaction_hash.into())); let traces = traces.map_or_else(Vec::new, |traces| traces.into_iter().map(LocalizedTrace::from).collect()); Ok(to_value(&traces)) }) @@ -112,7 +112,7 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: .and_then(|(transaction_hash, address)| { let client = take_weak!(self.client); let id = TraceId { - transaction: TransactionID::Hash(transaction_hash.into()), + transaction: TransactionId::Hash(transaction_hash.into()), address: address.into_iter().map(|i| i.value()).collect() }; let trace = client.trace(id); @@ -153,7 +153,7 @@ impl Traces for TracesClient where C: BlockChainClient + 'static, M: try!(self.active()); from_params::<(H256, _)>(params) .and_then(|(transaction_hash, flags)| { - match take_weak!(self.client).replay(TransactionID::Hash(transaction_hash.into()), to_call_analytics(flags)) { + match take_weak!(self.client).replay(TransactionId::Hash(transaction_hash.into()), to_call_analytics(flags)) { Ok(e) => Ok(to_value(&TraceResults::from(e))), _ => Ok(Value::Null), } diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 3aa83fec9..428cae3e0 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -19,7 +19,7 @@ use std::sync::Arc; use std::time::Duration; use ethcore::client::{BlockChainClient, Client, ClientConfig}; -use ethcore::ids::BlockID; +use ethcore::ids::BlockId; use ethcore::spec::{Genesis, Spec}; use ethcore::block::Block; use ethcore::views::BlockView; @@ -425,7 +425,7 @@ fn verify_transaction_counts(name: String, chain: BlockChain) { assert_eq!(tester.handler.handle_request_sync(&req), Some(res)); // uncles can share block numbers, so skip them. - if tester.client.block_hash(BlockID::Number(number)) == Some(hash) { + if tester.client.block_hash(BlockId::Number(number)) == Some(hash) { let (req, res) = by_number(number, count, &mut id); assert_eq!(tester.handler.handle_request_sync(&req), Some(res)); } diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index 05c95346d..7a7a1f682 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -24,7 +24,7 @@ use rlp; use util::{Uint, U256, Address, H256, FixedHash, Mutex}; use ethcore::account_provider::AccountProvider; -use ethcore::client::{TestBlockChainClient, EachBlockWith, Executed, TransactionID}; +use ethcore::client::{TestBlockChainClient, EachBlockWith, Executed, TransactionId}; use ethcore::log_entry::{LocalizedLogEntry, LogEntry}; use ethcore::receipt::LocalizedReceipt; use ethcore::transaction::{Transaction, Action}; @@ -952,7 +952,7 @@ fn rpc_eth_transaction_receipt() { let hash = H256::from_str("b903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238").unwrap(); let tester = EthTester::default(); - tester.client.set_transaction_receipt(TransactionID::Hash(hash), receipt); + tester.client.set_transaction_receipt(TransactionId::Hash(hash), receipt); let request = r#"{ "jsonrpc": "2.0", diff --git a/rpc/src/v1/types/block_number.rs b/rpc/src/v1/types/block_number.rs index 01625f8ed..0a2b2f305 100644 --- a/rpc/src/v1/types/block_number.rs +++ b/rpc/src/v1/types/block_number.rs @@ -16,7 +16,7 @@ use serde::{Deserialize, Deserializer, Error}; use serde::de::Visitor; -use ethcore::client::BlockID; +use ethcore::client::BlockId; /// Represents rpc api block number param. #[derive(Debug, PartialEq, Clone)] @@ -64,20 +64,20 @@ impl Visitor for BlockNumberVisitor { } } -impl Into for BlockNumber { - fn into(self) -> BlockID { +impl Into for BlockNumber { + fn into(self) -> BlockId { match self { - BlockNumber::Num(n) => BlockID::Number(n), - BlockNumber::Earliest => BlockID::Earliest, - BlockNumber::Latest => BlockID::Latest, - BlockNumber::Pending => BlockID::Pending, + BlockNumber::Num(n) => BlockId::Number(n), + BlockNumber::Earliest => BlockId::Earliest, + BlockNumber::Latest => BlockId::Latest, + BlockNumber::Pending => BlockId::Pending, } } } #[cfg(test)] mod tests { - use ethcore::client::BlockID; + use ethcore::client::BlockId; use super::*; use serde_json; @@ -90,10 +90,10 @@ mod tests { #[test] fn block_number_into() { - assert_eq!(BlockID::Number(100), BlockNumber::Num(100).into()); - assert_eq!(BlockID::Earliest, BlockNumber::Earliest.into()); - assert_eq!(BlockID::Latest, BlockNumber::Latest.into()); - assert_eq!(BlockID::Pending, BlockNumber::Pending.into()); + assert_eq!(BlockId::Number(100), BlockNumber::Num(100).into()); + assert_eq!(BlockId::Earliest, BlockNumber::Earliest.into()); + assert_eq!(BlockId::Latest, BlockNumber::Latest.into()); + assert_eq!(BlockId::Pending, BlockNumber::Pending.into()); } } diff --git a/rpc/src/v1/types/filter.rs b/rpc/src/v1/types/filter.rs index bcc4d2e8d..5254b1602 100644 --- a/rpc/src/v1/types/filter.rs +++ b/rpc/src/v1/types/filter.rs @@ -18,7 +18,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer, Error}; use serde_json::value; use jsonrpc_core::Value; use ethcore::filter::Filter as EthFilter; -use ethcore::client::BlockID; +use ethcore::client::BlockId; use v1::types::{BlockNumber, H160, H256, Log}; /// Variadic value @@ -73,8 +73,8 @@ pub struct Filter { impl Into for Filter { fn into(self) -> EthFilter { EthFilter { - from_block: self.from_block.map_or_else(|| BlockID::Latest, Into::into), - to_block: self.to_block.map_or_else(|| BlockID::Latest, Into::into), + from_block: self.from_block.map_or_else(|| BlockId::Latest, Into::into), + to_block: self.to_block.map_or_else(|| BlockId::Latest, Into::into), address: self.address.and_then(|address| match address { VariadicValue::Null => None, VariadicValue::Single(a) => Some(vec![a.into()]), @@ -128,7 +128,7 @@ mod tests { use super::{VariadicValue, Topic, Filter}; use v1::types::BlockNumber; use ethcore::filter::Filter as EthFilter; - use ethcore::client::BlockID; + use ethcore::client::BlockId; #[test] fn topic_deserialization() { @@ -173,8 +173,8 @@ mod tests { let eth_filter: EthFilter = filter.into(); assert_eq!(eth_filter, EthFilter { - from_block: BlockID::Earliest, - to_block: BlockID::Latest, + from_block: BlockId::Earliest, + to_block: BlockId::Latest, address: Some(vec![]), topics: vec![ None, diff --git a/rpc/src/v1/types/trace_filter.rs b/rpc/src/v1/types/trace_filter.rs index 6c7460f9b..2517b1585 100644 --- a/rpc/src/v1/types/trace_filter.rs +++ b/rpc/src/v1/types/trace_filter.rs @@ -16,7 +16,7 @@ //! Trace filter deserialization. -use ethcore::client::BlockID; +use ethcore::client::BlockId; use ethcore::client; use v1::types::{BlockNumber, H160}; @@ -40,8 +40,8 @@ pub struct TraceFilter { impl Into for TraceFilter { fn into(self) -> client::TraceFilter { - let start = self.from_block.map_or(BlockID::Latest, Into::into); - let end = self.to_block.map_or(BlockID::Latest, Into::into); + let start = self.from_block.map_or(BlockId::Latest, Into::into); + let end = self.to_block.map_or(BlockId::Latest, Into::into); client::TraceFilter { range: start..end, from_address: self.from_address.map_or_else(Vec::new, |x| x.into_iter().map(Into::into).collect()), diff --git a/sync/src/block_sync.rs b/sync/src/block_sync.rs index eea977906..79c7a65a9 100644 --- a/sync/src/block_sync.rs +++ b/sync/src/block_sync.rs @@ -22,7 +22,7 @@ use util::*; use rlp::*; use ethcore::views::{BlockView}; use ethcore::header::{BlockNumber, Header as BlockHeader}; -use ethcore::client::{BlockStatus, BlockID, BlockImportError}; +use ethcore::client::{BlockStatus, BlockId, BlockImportError}; use ethcore::block::Block; use ethcore::error::{ImportError, BlockError}; use sync_io::SyncIo; @@ -225,7 +225,7 @@ impl BlockDownloader { trace!(target: "sync", "Error decoding block header RLP: {:?}", e); BlockDownloaderImportError::Invalid })); - match io.chain().block_status(BlockID::Hash(hash.clone())) { + match io.chain().block_status(BlockId::Hash(hash.clone())) { BlockStatus::InChain | BlockStatus::Queued => { match self.state { State::Blocks => trace!(target: "sync", "Header already in chain {} ({})", number, hash), @@ -353,7 +353,7 @@ impl BlockDownloader { debug!(target: "sync", "Could not revert to previous ancient block, last: {} ({})", self.last_imported_block, self.last_imported_hash); self.reset(); } else { - match io.chain().block_hash(BlockID::Number(self.last_imported_block - 1)) { + match io.chain().block_hash(BlockId::Number(self.last_imported_block - 1)) { Some(h) => { self.last_imported_block -= 1; self.last_imported_hash = h; diff --git a/sync/src/blocks.rs b/sync/src/blocks.rs index bf0c4b244..bcb9973dc 100644 --- a/sync/src/blocks.rs +++ b/sync/src/blocks.rs @@ -475,7 +475,7 @@ impl BlockCollection { #[cfg(test)] mod test { use super::BlockCollection; - use ethcore::client::{TestBlockChainClient, EachBlockWith, BlockID, BlockChainClient}; + use ethcore::client::{TestBlockChainClient, EachBlockWith, BlockId, BlockChainClient}; use ethcore::views::HeaderView; use ethcore::header::BlockNumber; use util::*; @@ -497,7 +497,7 @@ mod test { assert!(is_empty(&bc)); let client = TestBlockChainClient::new(); client.add_blocks(100, EachBlockWith::Nothing); - let hashes = (0 .. 100).map(|i| (&client as &BlockChainClient).block_hash(BlockID::Number(i)).unwrap()).collect(); + let hashes = (0 .. 100).map(|i| (&client as &BlockChainClient).block_hash(BlockId::Number(i)).unwrap()).collect(); bc.reset_to(hashes); assert!(!is_empty(&bc)); bc.clear(); @@ -511,7 +511,7 @@ mod test { let client = TestBlockChainClient::new(); let nblocks = 200; client.add_blocks(nblocks, EachBlockWith::Nothing); - let blocks: Vec<_> = (0 .. nblocks).map(|i| (&client as &BlockChainClient).block(BlockID::Number(i as BlockNumber)).unwrap()).collect(); + let blocks: Vec<_> = (0 .. nblocks).map(|i| (&client as &BlockChainClient).block(BlockId::Number(i as BlockNumber)).unwrap()).collect(); let headers: Vec<_> = blocks.iter().map(|b| Rlp::new(b).at(0).as_raw().to_vec()).collect(); let hashes: Vec<_> = headers.iter().map(|h| HeaderView::new(h).sha3()).collect(); let heads: Vec<_> = hashes.iter().enumerate().filter_map(|(i, h)| if i % 20 == 0 { Some(h.clone()) } else { None }).collect(); @@ -564,7 +564,7 @@ mod test { let client = TestBlockChainClient::new(); let nblocks = 200; client.add_blocks(nblocks, EachBlockWith::Nothing); - let blocks: Vec<_> = (0 .. nblocks).map(|i| (&client as &BlockChainClient).block(BlockID::Number(i as BlockNumber)).unwrap()).collect(); + let blocks: Vec<_> = (0 .. nblocks).map(|i| (&client as &BlockChainClient).block(BlockId::Number(i as BlockNumber)).unwrap()).collect(); let headers: Vec<_> = blocks.iter().map(|b| Rlp::new(b).at(0).as_raw().to_vec()).collect(); let hashes: Vec<_> = headers.iter().map(|h| HeaderView::new(h).sha3()).collect(); let heads: Vec<_> = hashes.iter().enumerate().filter_map(|(i, h)| if i % 20 == 0 { Some(h.clone()) } else { None }).collect(); @@ -586,7 +586,7 @@ mod test { let client = TestBlockChainClient::new(); let nblocks = 200; client.add_blocks(nblocks, EachBlockWith::Nothing); - let blocks: Vec<_> = (0 .. nblocks).map(|i| (&client as &BlockChainClient).block(BlockID::Number(i as BlockNumber)).unwrap()).collect(); + let blocks: Vec<_> = (0 .. nblocks).map(|i| (&client as &BlockChainClient).block(BlockId::Number(i as BlockNumber)).unwrap()).collect(); let headers: Vec<_> = blocks.iter().map(|b| Rlp::new(b).at(0).as_raw().to_vec()).collect(); let hashes: Vec<_> = headers.iter().map(|h| HeaderView::new(h).sha3()).collect(); let heads: Vec<_> = hashes.iter().enumerate().filter_map(|(i, h)| if i % 20 == 0 { Some(h.clone()) } else { None }).collect(); diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 2d53ad5ee..b3bf57158 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -94,7 +94,7 @@ use rlp::*; use network::*; use ethcore::views::{HeaderView}; use ethcore::header::{BlockNumber, Header as BlockHeader}; -use ethcore::client::{BlockChainClient, BlockStatus, BlockID, BlockChainInfo, BlockImportError}; +use ethcore::client::{BlockChainClient, BlockStatus, BlockId, BlockChainInfo, BlockImportError}; use ethcore::error::*; use ethcore::snapshot::{ManifestData, RestorationStatus}; use sync_io::SyncIo; @@ -934,7 +934,7 @@ impl ChainSync { io.disable_peer(peer_id); continue; } - match io.chain().block_status(BlockID::Hash(hash.clone())) { + match io.chain().block_status(BlockId::Hash(hash.clone())) { BlockStatus::InChain => { trace!(target: "sync", "New block hash already in chain {:?}", hash); }, @@ -1155,7 +1155,7 @@ impl ChainSync { return; } - let have_latest = io.chain().block_status(BlockID::Hash(peer_latest)) != BlockStatus::Unknown; + let have_latest = io.chain().block_status(BlockId::Hash(peer_latest)) != BlockStatus::Unknown; if !have_latest && (higher_difficulty || force || self.state == SyncState::NewBlocks) { // check if got new blocks to download trace!(target: "sync", "Syncing with {}, force={}, td={:?}, our td={}, state={:?}", peer_id, force, peer_difficulty, syncing_difficulty, self.state); @@ -1450,11 +1450,11 @@ impl ChainSync { // id is a hash let hash: H256 = try!(r.val_at(0)); trace!(target: "sync", "{} -> GetBlockHeaders (hash: {}, max: {}, skip: {}, reverse:{})", peer_id, hash, max_headers, skip, reverse); - match io.chain().block_header(BlockID::Hash(hash)) { + match io.chain().block_header(BlockId::Hash(hash)) { Some(hdr) => { let number = From::from(HeaderView::new(&hdr).number()); debug_assert_eq!(HeaderView::new(&hdr).sha3(), hash); - if max_headers == 1 || io.chain().block_hash(BlockID::Number(number)) != Some(hash) { + if max_headers == 1 || io.chain().block_hash(BlockId::Number(number)) != Some(hash) { // Non canonical header or single header requested // TODO: handle single-step reverse hashchains of non-canon hashes trace!(target:"sync", "Returning single header: {:?}", hash); @@ -1487,7 +1487,7 @@ impl ChainSync { trace!(target: "sync", "{}: Returning cached fork header", peer_id); data.extend_from_slice(hdr); count += 1; - } else if let Some(mut hdr) = io.chain().block_header(BlockID::Number(number)) { + } else if let Some(mut hdr) = io.chain().block_header(BlockId::Number(number)) { data.append(&mut hdr); count += 1; } else { @@ -1521,7 +1521,7 @@ impl ChainSync { let mut added = 0usize; let mut data = Bytes::new(); for i in 0..count { - if let Some(mut hdr) = io.chain().block_body(BlockID::Hash(try!(r.val_at::(i)))) { + if let Some(mut hdr) = io.chain().block_body(BlockId::Hash(try!(r.val_at::(i)))) { data.append(&mut hdr); added += 1; } @@ -1778,7 +1778,7 @@ impl ChainSync { let mut rlp_stream = RlpStream::new_list(blocks.len()); for block_hash in blocks { let mut hash_rlp = RlpStream::new_list(2); - let number = HeaderView::new(&chain.block_header(BlockID::Hash(block_hash.clone())) + let number = HeaderView::new(&chain.block_header(BlockId::Hash(block_hash.clone())) .expect("chain.tree_route and chain.find_uncles only return hahses of blocks that are in the blockchain. qed.")).number(); hash_rlp.append(&block_hash); hash_rlp.append(&number); @@ -1795,7 +1795,7 @@ impl ChainSync { /// creates latest block rlp for the given client fn create_latest_block_rlp(chain: &BlockChainClient) -> Bytes { let mut rlp_stream = RlpStream::new_list(2); - rlp_stream.append_raw(&chain.block(BlockID::Hash(chain.chain_info().best_block_hash)).expect("Best block always exists"), 1); + rlp_stream.append_raw(&chain.block(BlockId::Hash(chain.chain_info().best_block_hash)).expect("Best block always exists"), 1); rlp_stream.append(&chain.chain_info().total_difficulty); rlp_stream.out() } @@ -1803,8 +1803,8 @@ impl ChainSync { /// creates latest block rlp for the given client fn create_new_block_rlp(chain: &BlockChainClient, hash: &H256) -> Bytes { let mut rlp_stream = RlpStream::new_list(2); - rlp_stream.append_raw(&chain.block(BlockID::Hash(hash.clone())).expect("Block has just been sealed; qed"), 1); - rlp_stream.append(&chain.block_total_difficulty(BlockID::Hash(hash.clone())).expect("Block has just been sealed; qed.")); + rlp_stream.append_raw(&chain.block(BlockId::Hash(hash.clone())).expect("Block has just been sealed; qed"), 1); + rlp_stream.append(&chain.block_total_difficulty(BlockId::Hash(hash.clone())).expect("Block has just been sealed; qed.")); rlp_stream.out() } @@ -1812,7 +1812,7 @@ impl ChainSync { fn get_lagging_peers(&mut self, chain_info: &BlockChainInfo, io: &SyncIo) -> Vec { let latest_hash = chain_info.best_block_hash; self.peers.iter_mut().filter_map(|(&id, ref mut peer_info)| - match io.chain().block_status(BlockID::Hash(peer_info.latest_hash.clone())) { + match io.chain().block_status(BlockId::Hash(peer_info.latest_hash.clone())) { BlockStatus::InChain => { if peer_info.latest_hash != latest_hash { Some(id) @@ -1863,7 +1863,7 @@ impl ChainSync { fn propagate_new_hashes(&mut self, chain_info: &BlockChainInfo, io: &mut SyncIo, peers: &[PeerId]) -> usize { trace!(target: "sync", "Sending NewHashes to {:?}", peers); let mut sent = 0; - let last_parent = HeaderView::new(&io.chain().block_header(BlockID::Hash(chain_info.best_block_hash.clone())) + let last_parent = HeaderView::new(&io.chain().block_header(BlockId::Hash(chain_info.best_block_hash.clone())) .expect("Best block always exists")).parent_hash(); for peer_id in peers { sent += match ChainSync::create_new_hashes_rlp(io.chain(), &last_parent, &chain_info.best_block_hash) { @@ -2125,7 +2125,7 @@ mod tests { let mut client = TestBlockChainClient::new(); client.add_blocks(100, EachBlockWith::Nothing); - let blocks: Vec<_> = (0 .. 100).map(|i| (&client as &BlockChainClient).block(BlockID::Number(i as BlockNumber)).unwrap()).collect(); + let blocks: Vec<_> = (0 .. 100).map(|i| (&client as &BlockChainClient).block(BlockId::Number(i as BlockNumber)).unwrap()).collect(); let headers: Vec<_> = blocks.iter().map(|b| Rlp::new(b).at(0).as_raw().to_vec()).collect(); let hashes: Vec<_> = headers.iter().map(|h| HeaderView::new(h).sha3()).collect(); @@ -2298,7 +2298,7 @@ mod tests { let mut client = TestBlockChainClient::new(); client.add_blocks(100, EachBlockWith::Uncle); let mut queue = VecDeque::new(); - let hash = client.block_hash(BlockID::Number(99)).unwrap(); + let hash = client.block_hash(BlockId::Number(99)).unwrap(); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let chain_info = client.chain_info(); let ss = TestSnapshotService::new(); @@ -2556,7 +2556,7 @@ mod tests { // Add some balance to clients and reset nonces for h in &[good_blocks[0], retracted_blocks[0]] { - let block = client.block(BlockID::Hash(*h)).unwrap(); + let block = client.block(BlockId::Hash(*h)).unwrap(); let view = BlockView::new(&block); client.set_balance(view.transactions()[0].sender().unwrap(), U256::from(1_000_000_000)); client.set_nonce(view.transactions()[0].sender().unwrap(), U256::from(0)); @@ -2575,7 +2575,7 @@ mod tests { } // We need to update nonce status (because we say that the block has been imported) for h in &[good_blocks[0]] { - let block = client.block(BlockID::Hash(*h)).unwrap(); + let block = client.block(BlockId::Hash(*h)).unwrap(); let view = BlockView::new(&block); client.set_nonce(view.transactions()[0].sender().unwrap(), U256::from(1)); } diff --git a/sync/src/tests/chain.rs b/sync/src/tests/chain.rs index 361d53e29..02b9063fa 100644 --- a/sync/src/tests/chain.rs +++ b/sync/src/tests/chain.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . use util::*; -use ethcore::client::{TestBlockChainClient, BlockChainClient, BlockID, EachBlockWith}; +use ethcore::client::{TestBlockChainClient, BlockChainClient, BlockId, EachBlockWith}; use chain::{SyncState}; use super::helpers::*; use SyncConfig; @@ -27,7 +27,7 @@ fn two_peers() { net.peer(1).chain.add_blocks(1000, EachBlockWith::Uncle); net.peer(2).chain.add_blocks(1000, EachBlockWith::Uncle); net.sync(); - assert!(net.peer(0).chain.block(BlockID::Number(1000)).is_some()); + 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()); } @@ -37,7 +37,7 @@ fn long_chain() { let mut net = TestNet::new(2); net.peer(1).chain.add_blocks(50000, EachBlockWith::Nothing); net.sync(); - assert!(net.peer(0).chain.block(BlockID::Number(50000)).is_some()); + 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()); } @@ -71,7 +71,7 @@ fn empty_blocks() { net.peer(2).chain.add_blocks(5, with); } net.sync(); - assert!(net.peer(0).chain.block(BlockID::Number(1000)).is_some()); + 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()); } @@ -123,13 +123,13 @@ fn net_hard_fork() { 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()))); + let mut net = TestNet::new_with_fork(2, Some((50, ref_client.block_hash(BlockId::Number(50)).unwrap()))); net.peer(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()))); + let mut net = TestNet::new_with_fork(2, Some((50, ref_client.block_hash(BlockId::Number(50)).unwrap()))); net.peer(0).chain.add_blocks(100, EachBlockWith::Nothing); net.sync(); assert_eq!(net.peer(1).chain.chain_info().best_block_number, 0); From f6564dcc2f61f892d9a85f057209db5f802cf5b0 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 01:32:39 +0100 Subject: [PATCH 180/295] Fix dapps separation --- js/src/views/Dapps/dapps.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/js/src/views/Dapps/dapps.js b/js/src/views/Dapps/dapps.js index cf847202a..b0b731b93 100644 --- a/js/src/views/Dapps/dapps.js +++ b/js/src/views/Dapps/dapps.js @@ -72,9 +72,17 @@ export default class Dapps extends Component { ] } /> - { this.renderList(this.store.visibleLocal) } - { this.renderList(this.store.visibleBuiltin) } - { this.renderList(this.store.visibleNetwork, externalOverlay) } +
    + { this.renderList(this.store.visibleLocal) } +
    + +
    + { this.renderList(this.store.visibleBuiltin) } +
    + +
    + { this.renderList(this.store.visibleNetwork, externalOverlay) } +
    ); From 0d9b1882a31346ab5ab831ea373767c22723b2f9 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 01:56:38 +0100 Subject: [PATCH 181/295] Treat tabs as real link (enable Ctrl+Click for new Tab) --- js/src/views/Application/TabBar/tabBar.css | 31 +++++++---- js/src/views/Application/TabBar/tabBar.js | 64 +++++----------------- 2 files changed, 33 insertions(+), 62 deletions(-) diff --git a/js/src/views/Application/TabBar/tabBar.css b/js/src/views/Application/TabBar/tabBar.css index b11df4e40..7b74622d3 100644 --- a/js/src/views/Application/TabBar/tabBar.css +++ b/js/src/views/Application/TabBar/tabBar.css @@ -30,24 +30,31 @@ } } -.tabs button, +.tabLink { + display: flex; + + > * { + flex: 1; + } + + &:hover { + background: rgba(0, 0, 0, 0.4) !important; + } + + &.tabactive, &.tabactive:hover { + color: white !important; + background: rgba(0, 0, 0, 0.25) !important; + border-radius: 4px 4px 0 0; + } +} + +.tabLink, .settings, .logo, .last { background: rgba(0, 0, 0, 0.5) !important; /* rgba(0, 0, 0, 0.25) !important; */ } -.tabs button:hover { - background: rgba(0, 0, 0, 0.4) !important; -} - -button.tabactive, -button.tabactive:hover { - color: white !important; - background: rgba(0, 0, 0, 0.25) !important; - border-radius: 4px 4px 0 0; -} - .tabbarTooltip { left: 3.3em; top: 0.5em; diff --git a/js/src/views/Application/TabBar/tabBar.js b/js/src/views/Application/TabBar/tabBar.js index 9d9f55874..ccf1290c5 100644 --- a/js/src/views/Application/TabBar/tabBar.js +++ b/js/src/views/Application/TabBar/tabBar.js @@ -16,6 +16,7 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; +import { Link } from 'react-router'; import { bindActionCreators } from 'redux'; import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar'; import { Tab as MUITab } from 'material-ui/Tabs'; @@ -40,8 +41,7 @@ class Tab extends Component { active: PropTypes.bool, view: PropTypes.object, children: PropTypes.node, - pendings: PropTypes.number, - onChange: PropTypes.func + pendings: PropTypes.number }; shouldComponentUpdate (nextProps) { @@ -60,7 +60,6 @@ class Tab extends Component { selected={ active } icon={ view.icon } label={ label } - onTouchTap={ this.handleClick } > { children } @@ -118,11 +117,6 @@ class Tab extends Component { return this.renderLabel(label, null); } - - handleClick = () => { - const { onChange, view } = this.props; - onChange(view); - } } class TabBar extends Component { @@ -142,34 +136,12 @@ class TabBar extends Component { pending: [] }; - state = { - activeViewId: '' - }; - - setActiveView (props = this.props) { - const { hash, views } = props; - const view = views.find((view) => view.value === hash); - - this.setState({ activeViewId: view.id }); - } - - componentWillMount () { - this.setActiveView(); - } - - componentWillReceiveProps (nextProps) { - if (nextProps.hash !== this.props.hash) { - this.setActiveView(nextProps); - } - } - shouldComponentUpdate (nextProps, nextState) { const prevViews = this.props.views.map((v) => v.id).sort(); const nextViews = nextProps.views.map((v) => v.id).sort(); return (nextProps.hash !== this.props.hash) || (nextProps.pending.length !== this.props.pending.length) || - (nextState.activeViewId !== this.state.activeViewId) || (!isEqual(prevViews, nextViews)); } @@ -206,7 +178,6 @@ class TabBar extends Component { renderTabs () { const { views, pending } = this.props; - const { activeViewId } = this.state; const items = views .map((view, index) => { @@ -216,36 +187,29 @@ class TabBar extends Component { ) : null; - const active = activeViewId === view.id; - return ( - - { body } - + + { body } + + ); }); return ( -
    +
    { items }
    ); } - - onChange = (view) => { - const { router } = this.context; - - router.push(view.route); - this.setState({ activeViewId: view.id }); - } } function mapStateToProps (state) { From 5f09eb9d04e238e9097f37468d68c4dc9ab93bd6 Mon Sep 17 00:00:00 2001 From: keorn Date: Sat, 10 Dec 2016 10:38:10 +0100 Subject: [PATCH 182/295] update tests to new spec (#3790) --- ethcore/src/engines/authority_round.rs | 28 ++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 21a6e4761..98190c1ea 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -347,7 +347,6 @@ mod tests { use tests::helpers::*; use account_provider::AccountProvider; use spec::Spec; - use std::time::UNIX_EPOCH; #[test] fn has_valid_metadata() { @@ -442,13 +441,30 @@ mod tests { let engine = Spec::new_test_round().engine; let signature = tap.sign(addr, Some("0".into()), header.bare_hash()).unwrap(); - let time = UNIX_EPOCH.elapsed().unwrap().as_secs(); // Two authorities. - let mut step = time - time % 2; - header.set_seal(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); + // Spec starts with step 2. + header.set_seal(vec![encode(&2usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); assert!(engine.verify_block_seal(&header).is_err()); - step = step + 1; - header.set_seal(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); + header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); assert!(engine.verify_block_seal(&header).is_ok()); } + + #[test] + fn rejects_future_block() { + 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 engine = Spec::new_test_round().engine; + + let signature = tap.sign(addr, Some("0".into()), header.bare_hash()).unwrap(); + // Two authorities. + // Spec starts with step 2. + header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); + assert!(engine.verify_block_seal(&header).is_ok()); + header.set_seal(vec![encode(&5usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); + assert!(engine.verify_block_seal(&header).is_err()); + } } From 6eb63a7316c3605c107a8288ce36b641ab2ea23d Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Sat, 10 Dec 2016 10:49:39 +0100 Subject: [PATCH 183/295] Update CI builds (#3780) * Only run languages tests in appropriate areas * Drop echo --- .gitlab-ci.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d27b58f9a..6df465035 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -423,12 +423,8 @@ test-rust-stable: before_script: - git submodule update --init --recursive - export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v ^js/ | wc -l) - - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l) - - echo "rust/js modified: $RUST_FILES_MODIFIED / $JS_FILES_MODIFIED" - - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "Skipping JS deps install since no JS files modified."; else ./js/scripts/install-deps.sh;fi script: - export RUST_BACKTRACE=1 - - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "Skipping JS lint since no JS files modified."; else ./js/scripts/lint.sh && ./js/scripts/test.sh && ./js/scripts/build.sh; fi - if [ "$RUST_FILES_MODIFIED" = 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi tags: - rust @@ -439,11 +435,8 @@ js-test: before_script: - git submodule update --init --recursive - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l) - - echo $JS_FILES_MODIFIED - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "Skipping JS deps install since no JS files modified."; else ./js/scripts/install-deps.sh;fi script: - - export RUST_BACKTRACE=1 - - echo $JS_FILES_MODIFIED - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "Skipping JS lint since no JS files modified."; else ./js/scripts/lint.sh && ./js/scripts/test.sh && ./js/scripts/build.sh; fi tags: - rust From 51b9034a5e7a0839945e79e237d154f470c20466 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 12:44:48 +0100 Subject: [PATCH 184/295] Don't show addresses while loading balances (fix flash of unsorted) --- js/src/views/Addresses/addresses.js | 37 +++++++++++++++++++---------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/js/src/views/Addresses/addresses.js b/js/src/views/Addresses/addresses.js index 5e0ed4e18..a3e52ec55 100644 --- a/js/src/views/Addresses/addresses.js +++ b/js/src/views/Addresses/addresses.js @@ -23,7 +23,7 @@ import { uniq, isEqual } from 'lodash'; import List from '../Accounts/List'; import Summary from '../Accounts/Summary'; import { AddAddress } from '~/modals'; -import { Actionbar, ActionbarExport, ActionbarImport, ActionbarSearch, ActionbarSort, Button, Page } from '~/ui'; +import { Actionbar, ActionbarExport, ActionbarImport, ActionbarSearch, ActionbarSort, Button, Page, Loading } from '~/ui'; import { setVisibleAccounts } from '~/redux/providers/personalActions'; import styles from './addresses.css'; @@ -72,27 +72,40 @@ class Addresses extends Component { } render () { - const { balances, contacts, hasContacts } = this.props; - const { searchValues, sortOrder } = this.state; - return (
    { this.renderActionbar() } { this.renderAddAddress() } - + { this.renderAccountsList() }
    ); } + renderAccountsList () { + const { balances, contacts, hasContacts } = this.props; + const { searchValues, sortOrder } = this.state; + + if (hasContacts && Object.keys(balances).length === 0) { + return ( + + ); + } + + return ( + + ); + } + renderSortButton () { const onChange = (sortOrder) => { this.setState({ sortOrder }); From 7eb30b12494a257b1ea35c38d6ba38565d5e1b81 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 10 Dec 2016 13:52:43 +0100 Subject: [PATCH 185/295] Fix build. --- ethcore/light/src/net/tests/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/light/src/net/tests/mod.rs b/ethcore/light/src/net/tests/mod.rs index 6e2bc9f33..29c1a819a 100644 --- a/ethcore/light/src/net/tests/mod.rs +++ b/ethcore/light/src/net/tests/mod.rs @@ -19,7 +19,7 @@ use ethcore::blockchain_info::BlockChainInfo; use ethcore::client::{BlockChainClient, EachBlockWith, TestBlockChainClient}; -use ethcore::ids::BlockID; +use ethcore::ids::BlockId; use ethcore::transaction::SignedTransaction; use network::PeerId; From 0977b82eeb5b647e2c2b5b5a204c1f8620a6105f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 10 Dec 2016 13:54:17 +0100 Subject: [PATCH 186/295] More fixes. --- ethcore/light/src/net/tests/mod.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ethcore/light/src/net/tests/mod.rs b/ethcore/light/src/net/tests/mod.rs index 29c1a819a..876432ce2 100644 --- a/ethcore/light/src/net/tests/mod.rs +++ b/ethcore/light/src/net/tests/mod.rs @@ -94,7 +94,7 @@ impl Provider for TestProvider { let best_num = self.0.client.chain_info().best_block_number; let start_num = req.block_num; - match self.0.client.block_hash(BlockID::Number(req.block_num)) { + match self.0.client.block_hash(BlockId::Number(req.block_num)) { Some(hash) if hash == req.block_hash => {} _=> { trace!(target: "les_provider", "unknown/non-canonical start block in header request: {:?}", (req.block_num, req.block_hash)); @@ -106,7 +106,7 @@ impl Provider for TestProvider { .map(|x: u64| x.saturating_mul(req.skip + 1)) .take_while(|x| if req.reverse { x < &start_num } else { best_num - start_num >= *x }) .map(|x| if req.reverse { start_num - x } else { start_num + x }) - .map(|x| self.0.client.block_header(BlockID::Number(x))) + .map(|x| self.0.client.block_header(BlockId::Number(x))) .take_while(|x| x.is_some()) .flat_map(|x| x) .collect() @@ -114,7 +114,7 @@ impl Provider for TestProvider { fn block_bodies(&self, req: request::Bodies) -> Vec { req.block_hashes.into_iter() - .map(|hash| self.0.client.block_body(BlockID::Hash(hash))) + .map(|hash| self.0.client.block_body(BlockId::Hash(hash))) .map(|body| body.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec())) .collect() } @@ -285,7 +285,7 @@ fn get_block_headers() { let request = Headers { block_num: 1, - block_hash: provider.client.block_hash(BlockID::Number(1)).unwrap(), + block_hash: provider.client.block_hash(BlockId::Number(1)).unwrap(), max: 10, skip: 0, reverse: false, @@ -294,7 +294,7 @@ fn get_block_headers() { let request_body = encode_request(&Request::Headers(request.clone()), req_id); let response = { - let headers: Vec<_> = (0..10).map(|i| provider.client.block_header(BlockID::Number(i + 1)).unwrap()).collect(); + let headers: Vec<_> = (0..10).map(|i| provider.client.block_header(BlockId::Number(i + 1)).unwrap()).collect(); assert_eq!(headers.len(), 10); let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Headers, 10); @@ -334,14 +334,14 @@ fn get_block_bodies() { } let request = request::Bodies { - block_hashes: (0..10).map(|i| provider.client.block_hash(BlockID::Number(i)).unwrap()).collect(), + block_hashes: (0..10).map(|i| provider.client.block_hash(BlockId::Number(i)).unwrap()).collect(), }; let req_id = 111; let request_body = encode_request(&Request::Bodies(request.clone()), req_id); let response = { - let bodies: Vec<_> = (0..10).map(|i| provider.client.block_body(BlockID::Number(i + 1)).unwrap()).collect(); + let bodies: Vec<_> = (0..10).map(|i| provider.client.block_body(BlockId::Number(i + 1)).unwrap()).collect(); assert_eq!(bodies.len(), 10); let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Bodies, 10); @@ -382,7 +382,7 @@ fn get_block_receipts() { // find the first 10 block hashes starting with `f` because receipts are only provided // by the test client in that case. - let block_hashes: Vec<_> = (0..1000).map(|i| provider.client.block_hash(BlockID::Number(i)).unwrap()) + let block_hashes: Vec<_> = (0..1000).map(|i| provider.client.block_hash(BlockId::Number(i)).unwrap()) .filter(|hash| format!("{}", hash).starts_with("f")).take(10).collect(); let request = request::Receipts { From 591d086f42d4ba5e92936df277f14054e42050d2 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 14:19:15 +0100 Subject: [PATCH 187/295] Better use of React-Router (maintaining old routes) --- js/src/main.js | 59 +++++++++++++++++++----- js/src/views/Accounts/Summary/summary.js | 2 +- js/src/views/Addresses/addresses.js | 2 +- js/src/views/Contracts/contracts.js | 2 +- 4 files changed, 51 insertions(+), 14 deletions(-) diff --git a/js/src/main.js b/js/src/main.js index d508c50fc..c1dda9d57 100644 --- a/js/src/main.js +++ b/js/src/main.js @@ -15,7 +15,7 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; -import { Redirect, Router, Route } from 'react-router'; +import { Redirect, Router, Route, IndexRoute } from 'react-router'; import { Accounts, Account, Addresses, Address, Application, Contract, Contracts, WriteContract, Wallet, Dapp, Dapps, Settings, SettingsBackground, SettingsParity, SettingsProxy, SettingsViews, Signer, Status } from '~/views'; @@ -26,6 +26,23 @@ export default class MainApplication extends Component { routerHistory: PropTypes.any.isRequired }; + handleDeprecatedRoute = (nextState, replace) => { + const { address } = nextState.params; + const redirectMap = { + account: 'accounts', + address: 'addresses', + contract: 'contracts' + }; + + const oldRoute = nextState.routes[0].path; + const newRoute = Object.keys(redirectMap).reduce((newRoute, key) => { + return newRoute.replace(new RegExp(`^/${key}`), '/' + redirectMap[key]); + }, oldRoute); + + console.warn(`Route "${oldRoute}" is deprecated. Please use "${newRoute}"`); + replace(newRoute.replace(':address', address)); + } + render () { const { routerHistory } = this.props; @@ -34,26 +51,46 @@ export default class MainApplication extends Component { + + { /** Backward Compatible links */ } + + + + - - - - - + + + + + + + + + + + - - - + + + + + + + + - - + + + + + ); diff --git a/js/src/views/Accounts/Summary/summary.js b/js/src/views/Accounts/Summary/summary.js index 3183a2903..a19b9a9de 100644 --- a/js/src/views/Accounts/Summary/summary.js +++ b/js/src/views/Accounts/Summary/summary.js @@ -153,7 +153,7 @@ export default class Summary extends Component { const { link, noLink, account, name } = this.props; const { address } = account; - const viewLink = `/${link || 'account'}/${address}`; + const viewLink = `/${link || 'accounts'}/${address}`; const content = ( diff --git a/js/src/views/Addresses/addresses.js b/js/src/views/Addresses/addresses.js index a3e52ec55..fd26d94e5 100644 --- a/js/src/views/Addresses/addresses.js +++ b/js/src/views/Addresses/addresses.js @@ -95,7 +95,7 @@ class Addresses extends Component { return ( Date: Sat, 10 Dec 2016 14:20:34 +0100 Subject: [PATCH 188/295] Network connectivity fixes --- ethcore/res/ethereum/frontier.json | 3 --- sync/src/chain.rs | 2 +- sync/src/sync_io.rs | 4 ++++ util/network/src/host.rs | 14 +++++++++----- util/network/src/session.rs | 8 ++++---- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/ethcore/res/ethereum/frontier.json b/ethcore/res/ethereum/frontier.json index 2c520c46a..3a9dce456 100644 --- a/ethcore/res/ethereum/frontier.json +++ b/ethcore/res/ethereum/frontier.json @@ -170,9 +170,6 @@ "enode://cadc6e573b6bc2a9128f2f635ac0db3353e360b56deef239e9be7e7fce039502e0ec670b595f6288c0d2116812516ad6b6ff8d5728ff45eba176989e40dead1e@37.128.191.230:30303", "enode://595a9a06f8b9bc9835c8723b6a82105aea5d55c66b029b6d44f229d6d135ac3ecdd3e9309360a961ea39d7bee7bac5d03564077a4e08823acc723370aace65ec@46.20.235.22:30303", "enode://029178d6d6f9f8026fc0bc17d5d1401aac76ec9d86633bba2320b5eed7b312980c0a210b74b20c4f9a8b0b2bf884b111fa9ea5c5f916bb9bbc0e0c8640a0f56c@216.158.85.185:30303", - "enode://84f5d5957b4880a8b0545e32e05472318898ad9fc8ebe1d56c90c12334a98e12351eccfdf3a2bf72432ac38b57e9d348400d17caa083879ade3822390f89773f@10.1.52.78:30303", - "enode://f90dc9b9bf7b8db97726b7849e175f1eb2707f3d8f281c929336e398dd89b0409fc6aeceb89e846278e9d3ecc3857cebfbe6758ff352ece6fe5d42921ee761db@10.1.173.87:30303", - "enode://6a868ced2dec399c53f730261173638a93a40214cf299ccf4d42a76e3fa54701db410669e8006347a4b3a74fa090bb35af0320e4bc8d04cf5b7f582b1db285f5@10.3.149.199:30303", "enode://fdd1b9bb613cfbc200bba17ce199a9490edc752a833f88d4134bf52bb0d858aa5524cb3ec9366c7a4ef4637754b8b15b5dc913e4ed9fdb6022f7512d7b63f181@212.47.247.103:30303", "enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303", "enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303", diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 2d53ad5ee..9f054bec8 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -1678,7 +1678,7 @@ impl ChainSync { pub fn on_packet(&mut self, io: &mut SyncIo, peer: PeerId, packet_id: u8, data: &[u8]) { if packet_id != STATUS_PACKET && !self.peers.contains_key(&peer) { - debug!(target:"sync", "Unexpected packet from unregistered peer: {}:{}", peer, io.peer_info(peer)); + debug!(target:"sync", "Unexpected packet {} from unregistered peer: {}:{}", packet_id, peer, io.peer_info(peer)); return; } let rlp = UntrustedRlp::new(data); diff --git a/sync/src/sync_io.rs b/sync/src/sync_io.rs index 8dc8c65c0..e9e2d3396 100644 --- a/sync/src/sync_io.rs +++ b/sync/src/sync_io.rs @@ -131,6 +131,10 @@ impl<'s, 'h> SyncIo for NetSyncIo<'s, 'h> { fn protocol_version(&self, protocol: &ProtocolId, peer_id: PeerId) -> u8 { self.network.protocol_version(*protocol, peer_id).unwrap_or(0) } + + fn peer_info(&self, peer_id: PeerId) -> String { + self.network.peer_client_version(peer_id) + } } diff --git a/util/network/src/host.rs b/util/network/src/host.rs index f75158e4a..975fb87b8 100644 --- a/util/network/src/host.rs +++ b/util/network/src/host.rs @@ -22,7 +22,7 @@ use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering as AtomicOrdering}; use std::ops::*; use std::cmp::min; use std::path::{Path, PathBuf}; -use std::io::{Read, Write}; +use std::io::{Read, Write, ErrorKind}; use std::fs; use ethkey::{KeyPair, Secret, Random, Generator}; use mio::*; @@ -381,8 +381,6 @@ pub struct Host { impl Host { /// Create a new instance pub fn new(mut config: NetworkConfiguration, stats: Arc) -> Result { - trace!(target: "host", "Creating new Host object"); - let mut listen_address = match config.listen_address { None => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), DEFAULT_PORT)), Some(addr) => addr, @@ -405,6 +403,7 @@ impl Host { // Setup the server socket let tcp_listener = try!(TcpListener::bind(&listen_address)); listen_address = SocketAddr::new(listen_address.ip(), try!(tcp_listener.local_addr()).port()); + debug!(target: "network", "Listening at {:?}", listen_address); let udp_port = config.udp_port.unwrap_or(listen_address.port()); let local_endpoint = NodeEndpoint { address: listen_address, udp_port: udp_port }; @@ -707,7 +706,10 @@ impl Host { } }; match TcpStream::connect(&address) { - Ok(socket) => socket, + Ok(socket) => { + trace!(target: "network", "Connecting to {:?}", address); + socket + }, Err(e) => { debug!(target: "network", "Can't connect to address {:?}: {:?}", address, e); return; @@ -749,7 +751,9 @@ impl Host { let socket = match self.tcp_listener.lock().accept() { Ok((sock, _addr)) => sock, Err(e) => { - debug!(target: "network", "Error accepting connection: {:?}", e); + if e.kind() != ErrorKind::WouldBlock { + debug!(target: "network", "Error accepting connection: {:?}", e); + } break }, }; diff --git a/util/network/src/session.rs b/util/network/src/session.rs index 9c8bed9da..3aab05d9a 100644 --- a/util/network/src/session.rs +++ b/util/network/src/session.rs @@ -435,16 +435,16 @@ impl Session { // map to protocol let protocol = self.info.capabilities[i].protocol; - let pid = packet_id - self.info.capabilities[i].id_offset; + let protocol_packet_id = packet_id - self.info.capabilities[i].id_offset; match *self.protocol_states.entry(protocol).or_insert_with(|| ProtocolState::Pending(Vec::new())) { ProtocolState::Connected => { - trace!(target: "network", "Packet {} mapped to {:?}:{}, i={}, capabilities={:?}", packet_id, protocol, pid, i, self.info.capabilities); - Ok(SessionData::Packet { data: packet.data, protocol: protocol, packet_id: pid } ) + trace!(target: "network", "Packet {} mapped to {:?}:{}, i={}, capabilities={:?}", packet_id, protocol, protocol_packet_id, i, self.info.capabilities); + Ok(SessionData::Packet { data: packet.data, protocol: protocol, packet_id: protocol_packet_id } ) } ProtocolState::Pending(ref mut pending) => { trace!(target: "network", "Packet {} deferred until protocol connection event completion", packet_id); - pending.push((packet.data, packet_id)); + pending.push((packet.data, protocol_packet_id)); Ok(SessionData::Continue) } From 13607d48be99d52240912138c1cb1479d7d63abb Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 14:26:35 +0100 Subject: [PATCH 189/295] Better use of Tab Bar --- js/src/views/Application/TabBar/tabBar.js | 59 +++++++++++------------ 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/js/src/views/Application/TabBar/tabBar.js b/js/src/views/Application/TabBar/tabBar.js index ccf1290c5..63f569f1f 100644 --- a/js/src/views/Application/TabBar/tabBar.js +++ b/js/src/views/Application/TabBar/tabBar.js @@ -17,7 +17,6 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { Link } from 'react-router'; -import { bindActionCreators } from 'redux'; import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar'; import { Tab as MUITab } from 'material-ui/Tabs'; import { isEqual } from 'lodash'; @@ -27,37 +26,24 @@ import { Badge, Tooltip } from '~/ui'; import styles from './tabBar.css'; import imagesEthcoreBlock from '../../../../assets/images/parity-logo-white-no-text.svg'; -const TABMAP = { - accounts: 'account', - wallet: 'account', - addresses: 'address', - apps: 'app', - contracts: 'contract', - deploy: 'contract' -}; - class Tab extends Component { static propTypes = { - active: PropTypes.bool, view: PropTypes.object, children: PropTypes.node, pendings: PropTypes.number }; shouldComponentUpdate (nextProps) { - return nextProps.active !== this.props.active || - (nextProps.view.id === 'signer' && nextProps.pendings !== this.props.pendings); + return (nextProps.view.id === 'signer' && nextProps.pendings !== this.props.pendings); } render () { - const { active, view, children } = this.props; + const { view, children } = this.props; const label = this.getLabel(view); return ( @@ -126,7 +112,6 @@ class TabBar extends Component { static propTypes = { views: PropTypes.array.isRequired, - hash: PropTypes.string.isRequired, pending: PropTypes.array, isTest: PropTypes.bool, netChain: PropTypes.string @@ -140,8 +125,7 @@ class TabBar extends Component { const prevViews = this.props.views.map((v) => v.id).sort(); const nextViews = nextProps.views.map((v) => v.id).sort(); - return (nextProps.hash !== this.props.hash) || - (nextProps.pending.length !== this.props.pending.length) || + return (nextProps.pending.length !== this.props.pending.length) || (!isEqual(prevViews, nextViews)); } @@ -212,28 +196,41 @@ class TabBar extends Component { } } -function mapStateToProps (state) { - const { views } = state.settings; +function mapStateToProps (initState) { + const { views } = initState.settings; - const filteredViews = Object + let filteredViewIds = Object .keys(views) - .filter((id) => views[id].fixed || views[id].active) + .filter((id) => views[id].fixed || views[id].active); + + let filteredViews = filteredViewIds .map((id) => ({ ...views[id], id })); - const windowHash = (window.location.hash || '').split('?')[0].split('/')[1]; - const hash = TABMAP[windowHash] || windowHash; + return (state) => { + const { views } = state.settings; - return { views: filteredViews, hash }; -} + const viewIds = Object + .keys(views) + .filter((id) => views[id].fixed || views[id].active); -function mapDispatchToProps (dispatch) { - return bindActionCreators({}, dispatch); + if (isEqual(viewIds, filteredViewIds)) { + return { views: filteredViews }; + } + + filteredViewIds = viewIds; + filteredViews = viewIds + .map((id) => ({ + ...views[id], + id + })); + + return { views: filteredViews }; + }; } export default connect( - mapStateToProps, - mapDispatchToProps + mapStateToProps )(TabBar); From 81c5085b35ad05313d9999ff80018e375d5c8413 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 14:31:20 +0100 Subject: [PATCH 190/295] Don't create new Contracts instance if already exists --- js/src/contracts/contracts.js | 4 ++++ js/src/views/Account/account.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/js/src/contracts/contracts.js b/js/src/contracts/contracts.js index f61a63690..a8020b825 100644 --- a/js/src/contracts/contracts.js +++ b/js/src/contracts/contracts.js @@ -62,6 +62,10 @@ export default class Contracts { } static create (api) { + if (instance) { + return instance; + } + return new Contracts(api); } diff --git a/js/src/views/Account/account.js b/js/src/views/Account/account.js index cbacd5280..840de05b9 100644 --- a/js/src/views/Account/account.js +++ b/js/src/views/Account/account.js @@ -26,7 +26,7 @@ import VerifyIcon from 'material-ui/svg-icons/action/verified-user'; import { EditMeta, DeleteAccount, Shapeshift, SMSVerification, Transfer, PasswordManager } from '~/modals'; import { Actionbar, Button, Page } from '~/ui'; -import shapeshiftBtn from '../../../assets/images/shapeshift-btn.png'; +import shapeshiftBtn from '~/../assets/images/shapeshift-btn.png'; import Header from './Header'; import Transactions from './Transactions'; From 65f586ed1478f8dc4fc389b94d9e582319703f92 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 14:32:54 +0100 Subject: [PATCH 191/295] Fix tab bar active style --- js/src/views/Application/TabBar/tabBar.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/js/src/views/Application/TabBar/tabBar.css b/js/src/views/Application/TabBar/tabBar.css index 7b74622d3..172750c8f 100644 --- a/js/src/views/Application/TabBar/tabBar.css +++ b/js/src/views/Application/TabBar/tabBar.css @@ -42,9 +42,12 @@ } &.tabactive, &.tabactive:hover { - color: white !important; background: rgba(0, 0, 0, 0.25) !important; border-radius: 4px 4px 0 0; + + * { + color: white !important; + } } } From e1ade5b3750561101ce8519e3767ae7b63886e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sat, 10 Dec 2016 14:56:41 +0100 Subject: [PATCH 192/295] Maintaining a list of transactions propagated from other peers --- ethcore/src/client/chain_notify.rs | 11 ++++- ethcore/src/client/client.rs | 15 ++++--- ethcore/src/client/test_client.rs | 2 +- ethcore/src/client/traits.rs | 8 ++-- ethcore/src/service.rs | 6 ++- .../dapps/localtx/Transaction/transaction.js | 26 ++++++++++-- .../localtx/Transaction/transaction.spec.js | 2 +- rpc/src/v1/tests/helpers/sync_provider.rs | 10 ++++- rpc/src/v1/types/sync.rs | 16 ++++++-- sync/src/api.rs | 20 +++++++--- sync/src/chain.rs | 10 ++++- sync/src/transactions_stats.rs | 40 +++++++++++++++++++ 12 files changed, 137 insertions(+), 29 deletions(-) diff --git a/ethcore/src/client/chain_notify.rs b/ethcore/src/client/chain_notify.rs index e0282d460..50ff20e38 100644 --- a/ethcore/src/client/chain_notify.rs +++ b/ethcore/src/client/chain_notify.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . use ipc::IpcConfig; -use util::H256; +use util::{H256, H512}; /// Represents what has to be handled by actor listening to chain events #[ipc] @@ -40,6 +40,15 @@ pub trait ChainNotify : Send + Sync { fn stop(&self) { // does nothing by default } + + /// fires when new transactions are imported + fn transactions_imported(&self, + _hashes: Vec, + _peer_id: Option, + _block_num: u64, + ) { + // does nothing by default + } } impl IpcConfig for ChainNotify { } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index dfd899b29..9add41e4f 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -25,7 +25,7 @@ use time::precise_time_ns; use util::{Bytes, PerfTimer, Itertools, Mutex, RwLock, Hashable}; use util::{journaldb, TrieFactory, Trie}; use util::trie::TrieSpec; -use util::{U256, H256, Address, H2048, Uint, FixedHash}; +use util::{U256, H256, H512, Address, H2048, Uint, FixedHash}; use util::kvdb::*; // other @@ -559,11 +559,16 @@ impl Client { } /// Import transactions from the IO queue - pub fn import_queued_transactions(&self, transactions: &[Bytes]) -> usize { + pub fn import_queued_transactions(&self, transactions: &[Bytes], peer_id: Option) -> 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(); + let txs: Vec = transactions.iter().filter_map(|bytes| UntrustedRlp::new(bytes).as_val().ok()).collect(); + let hashes: Vec<_> = txs.iter().map(|tx| tx.hash()).collect(); + let block_number = self.chain_info().best_block_number; + self.notify(|notify| { + notify.transactions_imported(hashes.clone(), peer_id.clone(), block_number); + }); let results = self.miner.import_external_transactions(self, txs); results.len() } @@ -1264,14 +1269,14 @@ impl BlockChainClient for Client { (*self.build_last_hashes(self.chain.read().best_block_hash())).clone() } - fn queue_transactions(&self, transactions: Vec) { + fn queue_transactions(&self, transactions: Vec, node_id: Option) { 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.lock().send(ClientIoMessage::NewTransactions(transactions)) { + match self.io_channel.lock().send(ClientIoMessage::NewTransactions(transactions, node_id)) { Ok(_) => { self.queue_transactions.fetch_add(len, AtomicOrdering::SeqCst); } diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 317a481c7..44efade66 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -657,7 +657,7 @@ impl BlockChainClient for TestBlockChainClient { unimplemented!(); } - fn queue_transactions(&self, transactions: Vec) { + fn queue_transactions(&self, transactions: Vec, _peer_id: Option) { // import right here let txs = transactions.into_iter().filter_map(|bytes| UntrustedRlp::new(&bytes).as_val().ok()).collect(); self.miner.import_external_transactions(self, txs); diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index e23a564d4..c032d4059 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . use std::collections::BTreeMap; -use util::{U256, Address, H256, H2048, Bytes, Itertools}; +use util::{U256, Address, H256, H512, H2048, Bytes, Itertools}; use util::stats::Histogram; use blockchain::TreeRoute; use verification::queue::QueueInfo as BlockQueueInfo; @@ -200,7 +200,7 @@ pub trait BlockChainClient : Sync + Send { fn last_hashes(&self) -> LastHashes; /// Queue transactions for importing. - fn queue_transactions(&self, transactions: Vec); + fn queue_transactions(&self, transactions: Vec, peer_id: Option); /// list all transactions fn pending_transactions(&self) -> Vec; @@ -294,9 +294,9 @@ pub trait ProvingBlockChainClient: BlockChainClient { /// The key is the keccak hash of the account's address. /// Returns a vector of raw trie nodes (in order from the root) proving the query. /// Nodes after `from_level` may be omitted. - /// An empty vector indicates unservable query. + /// An empty vector indicates unservable query. fn prove_account(&self, key1: H256, from_level: u32, id: BlockID) -> Vec; /// Get code by address hash. fn code_by_hash(&self, account_key: H256, id: BlockID) -> Bytes; -} \ No newline at end of file +} diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index 36b5e7157..b595843a8 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -39,7 +39,7 @@ pub enum ClientIoMessage { /// A block is ready BlockVerified, /// New transaction RLPs are ready to be imported - NewTransactions(Vec), + NewTransactions(Vec, Option), /// Begin snapshot restoration BeginRestoration(ManifestData), /// Feed a state chunk to the snapshot service @@ -196,7 +196,9 @@ impl IoHandler for ClientIoHandler { match *net_message { ClientIoMessage::BlockVerified => { self.client.import_verified_blocks(); } - ClientIoMessage::NewTransactions(ref transactions) => { self.client.import_queued_transactions(transactions); } + ClientIoMessage::NewTransactions(ref transactions, ref peer_id) => { + self.client.import_queued_transactions(transactions, peer_id.clone()); + } ClientIoMessage::BeginRestoration(ref manifest) => { if let Err(e) = self.snapshot.init_restore(manifest.clone(), true) { warn!("Failed to initialize snapshot restoration: {}", e); diff --git a/js/src/dapps/localtx/Transaction/transaction.js b/js/src/dapps/localtx/Transaction/transaction.js index 17a45ecd6..c9ca10ba5 100644 --- a/js/src/dapps/localtx/Transaction/transaction.js +++ b/js/src/dapps/localtx/Transaction/transaction.js @@ -48,7 +48,6 @@ class BaseTransaction extends Component { - 0x{ transaction.nonce.toString(16) }
    ); } @@ -87,6 +86,17 @@ class BaseTransaction extends Component { ); } + + renderReceived (stats) { + const noOfPeers = Object.keys(stats.receivedFrom).length; + const noOfPropagations = Object.values(stats.receivedFrom).reduce((sum, val) => sum + val, 0); + + return ( + + { noOfPropagations } ({ noOfPeers } peers) + + ); + } } export class Transaction extends BaseTransaction { @@ -103,7 +113,8 @@ export class Transaction extends BaseTransaction { isLocal: false, stats: { firstSeen: 0, - propagatedTo: {} + propagatedTo: {}, + receivedFrom: {} } }; @@ -129,6 +140,9 @@ export class Transaction extends BaseTransaction { # Propagated + + # Received + ); @@ -165,6 +179,9 @@ export class Transaction extends BaseTransaction { { this.renderPropagation(stats) } + + { this.renderReceived(stats) } + ); } @@ -193,7 +210,8 @@ export class LocalTransaction extends BaseTransaction { static defaultProps = { stats: { - propagatedTo: {} + propagatedTo: {}, + receivedFrom: {} } }; @@ -317,6 +335,8 @@ export class LocalTransaction extends BaseTransaction { { this.renderStatus() }
    { status === 'pending' ? this.renderPropagation(stats) : null } +
    + { status === 'pending' ? this.renderReceived(stats) : null } ); diff --git a/js/src/dapps/localtx/Transaction/transaction.spec.js b/js/src/dapps/localtx/Transaction/transaction.spec.js index 04f2f8de8..2bd3691db 100644 --- a/js/src/dapps/localtx/Transaction/transaction.spec.js +++ b/js/src/dapps/localtx/Transaction/transaction.spec.js @@ -34,7 +34,7 @@ describe('dapps/localtx/Transaction', () => { it('renders without crashing', () => { const transaction = { hash: '0x1234567890', - nonce: 15, + nonce: new BigNumber(15), gasPrice: new BigNumber(10), gas: new BigNumber(10) }; diff --git a/rpc/src/v1/tests/helpers/sync_provider.rs b/rpc/src/v1/tests/helpers/sync_provider.rs index 8800d926a..aa7e8d849 100644 --- a/rpc/src/v1/tests/helpers/sync_provider.rs +++ b/rpc/src/v1/tests/helpers/sync_provider.rs @@ -105,13 +105,19 @@ impl SyncProvider for TestSyncProvider { first_seen: 10, propagated_to: map![ 128.into() => 16 - ] + ], + received_from: map![ + 1.into() => 10 + ], }, 5.into() => TransactionStats { first_seen: 16, propagated_to: map![ 16.into() => 1 - ] + ], + received_from: map![ + 256.into() => 2 + ], } ] } diff --git a/rpc/src/v1/types/sync.rs b/rpc/src/v1/types/sync.rs index 6f8938be9..65d989156 100644 --- a/rpc/src/v1/types/sync.rs +++ b/rpc/src/v1/types/sync.rs @@ -127,6 +127,9 @@ pub struct TransactionStats { /// Peers this transaction was propagated to with count. #[serde(rename="propagatedTo")] pub propagated_to: BTreeMap, + /// Peers that propagated this transaction back. + #[serde(rename="receivedFrom")] + pub received_from: BTreeMap, } impl From for PeerInfo { @@ -157,7 +160,11 @@ impl From for TransactionStats { propagated_to: s.propagated_to .into_iter() .map(|(id, count)| (id.into(), count)) - .collect() + .collect(), + received_from: s.received_from + .into_iter() + .map(|(id, count)| (id.into(), count)) + .collect(), } } } @@ -208,10 +215,13 @@ mod tests { first_seen: 100, propagated_to: map![ 10.into() => 50 - ] + ], + received_from: map![ + 1.into() => 1000 + ], }; let serialized = serde_json::to_string(&stats).unwrap(); - assert_eq!(serialized, r#"{"firstSeen":100,"propagatedTo":{"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a":50}}"#) + assert_eq!(serialized, r#"{"firstSeen":100,"propagatedTo":{"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a":50},"receivedFrom":{"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001":1000}}"#) } } diff --git a/sync/src/api.rs b/sync/src/api.rs index 7c531bf7c..10434ce26 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -99,6 +99,8 @@ pub struct TransactionStats { pub first_seen: u64, /// Peers it was propagated to. pub propagated_to: BTreeMap, + /// Peers that propagated the transaction back. + pub received_from: BTreeMap, } /// Peer connection information @@ -144,7 +146,7 @@ pub struct EthSync { network: NetworkService, /// Main (eth/par) protocol handler sync_handler: Arc, - /// Light (les) protocol handler + /// Light (les) protocol handler light_proto: Option>, /// The main subprotocol name subprotocol_name: [u8; 3], @@ -155,7 +157,7 @@ pub struct EthSync { impl EthSync { /// Creates and register protocol with the network service pub fn new(params: Params) -> Result, NetworkError> { - let pruning_info = params.chain.pruning_info(); + let pruning_info = params.chain.pruning_info(); let light_proto = match params.config.serve_light { false => None, true => Some({ @@ -297,7 +299,7 @@ impl ChainNotify for EthSync { Some(lp) => lp, None => return, }; - + let chain_info = self.sync_handler.chain.chain_info(); light_proto.make_announcement(context, Announcement { head_hash: chain_info.best_block_hash, @@ -323,7 +325,7 @@ impl ChainNotify for EthSync { // register the warp sync subprotocol self.network.register_protocol(self.sync_handler.clone(), WARP_SYNC_PROTOCOL_ID, SNAPSHOT_SYNC_PACKET_COUNT, &[1u8]) .unwrap_or_else(|e| warn!("Error registering snapshot sync protocol: {:?}", e)); - + // register the light protocol. if let Some(light_proto) = self.light_proto.as_ref().map(|x| x.clone()) { self.network.register_protocol(light_proto, self.light_subprotocol_name, ::light::net::PACKET_COUNT, ::light::net::PROTOCOL_VERSIONS) @@ -335,6 +337,11 @@ impl ChainNotify for EthSync { self.sync_handler.snapshot_service.abort_restore(); self.network.stop().unwrap_or_else(|e| warn!("Error stopping network: {:?}", e)); } + + fn transactions_imported(&self, hashes: Vec, peer_id: Option, block_number: u64) { + let mut sync = self.sync_handler.sync.write(); + sync.transactions_imported(hashes, peer_id, block_number); + } } /// LES event handler. @@ -344,7 +351,8 @@ struct TxRelay(Arc); impl LightHandler for TxRelay { fn on_transactions(&self, ctx: &EventContext, relay: &[::ethcore::transaction::SignedTransaction]) { trace!(target: "les", "Relaying {} transactions from peer {}", relay.len(), ctx.peer()); - self.0.queue_transactions(relay.iter().map(|tx| ::rlp::encode(tx).to_vec()).collect()) + // TODO [ToDr] Can we get a peer enode somehow? + self.0.queue_transactions(relay.iter().map(|tx| ::rlp::encode(tx).to_vec()).collect(), None) } } @@ -547,4 +555,4 @@ pub struct ServiceConfiguration { pub net: NetworkConfiguration, /// IPC path. pub io_path: String, -} \ No newline at end of file +} diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 2d53ad5ee..9115ac297 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -432,6 +432,13 @@ impl ChainSync { self.transactions_stats.stats() } + /// Updates statistics for imported transactions. + pub fn transactions_imported(&mut self, hashes: Vec, peer_id: Option, block_number: u64) { + for hash in hashes { + self.transactions_stats.received(hash, peer_id, block_number); + } + } + /// Abort all sync activity pub fn abort(&mut self, io: &mut SyncIo) { self.reset_and_continue(io); @@ -1409,7 +1416,8 @@ impl ChainSync { let tx = rlp.as_raw().to_vec(); transactions.push(tx); } - io.chain().queue_transactions(transactions); + let id = io.peer_session_info(peer_id).and_then(|info| info.id); + io.chain().queue_transactions(transactions, id); Ok(()) } diff --git a/sync/src/transactions_stats.rs b/sync/src/transactions_stats.rs index 8c5eb6dda..a91a860e5 100644 --- a/sync/src/transactions_stats.rs +++ b/sync/src/transactions_stats.rs @@ -26,6 +26,7 @@ type BlockNumber = u64; pub struct Stats { first_seen: BlockNumber, propagated_to: HashMap, + received_from: HashMap, } impl Stats { @@ -33,6 +34,7 @@ impl Stats { Stats { first_seen: number, propagated_to: Default::default(), + received_from: Default::default(), } } } @@ -45,6 +47,10 @@ impl<'a> From<&'a Stats> for TransactionStats { .iter() .map(|(hash, size)| (*hash, *size)) .collect(), + received_from: other.received_from + .iter() + .map(|(hash, size)| (*hash, *size)) + .collect(), } } } @@ -63,6 +69,14 @@ impl TransactionsStats { *count = count.saturating_add(1); } + /// Increase number of back-propagations from given `enodeid`. + pub fn received(&mut self, hash: H256, enode_id: Option, current_block_num: BlockNumber) { + let enode_id = enode_id.unwrap_or_default(); + let mut stats = self.pending_transactions.entry(hash).or_insert_with(|| Stats::new(current_block_num)); + let mut count = stats.received_from.entry(enode_id).or_insert(0); + *count = count.saturating_add(1); + } + /// Returns propagation stats for given hash or `None` if hash is not known. #[cfg(test)] pub fn get(&self, hash: &H256) -> Option<&Stats> { @@ -112,6 +126,32 @@ mod tests { propagated_to: hash_map![ enodeid1 => 2, enodeid2 => 1 + ], + received_from: Default::default(), + })); + } + + #[test] + fn should_keep_track_of_back_propagations() { + // given + let mut stats = TransactionsStats::default(); + let hash = 5.into(); + let enodeid1 = 2.into(); + let enodeid2 = 5.into(); + + // when + stats.received(hash, Some(enodeid1), 5); + stats.received(hash, Some(enodeid1), 10); + stats.received(hash, Some(enodeid2), 15); + + // then + let stats = stats.get(&hash); + assert_eq!(stats, Some(&Stats { + first_seen: 5, + propagated_to: Default::default(), + received_from: hash_map![ + enodeid1 => 2, + enodeid2 => 1 ] })); } From ef93262311c97ef7f1b0f845a45652894c3c5121 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 15:19:05 +0100 Subject: [PATCH 193/295] See addresses outside address book + Save them --- js/src/modals/AddAddress/addAddress.js | 9 ++ js/src/views/Account/Header/header.css | 4 + js/src/views/Account/Header/header.js | 27 ++++-- js/src/views/Address/address.js | 109 +++++++++++++++++++------ 4 files changed, 119 insertions(+), 30 deletions(-) diff --git a/js/src/modals/AddAddress/addAddress.js b/js/src/modals/AddAddress/addAddress.js index e44cb0b3c..a72158cc7 100644 --- a/js/src/modals/AddAddress/addAddress.js +++ b/js/src/modals/AddAddress/addAddress.js @@ -28,6 +28,7 @@ export default class AddAddress extends Component { static propTypes = { contacts: PropTypes.object.isRequired, + address: PropTypes.string, onClose: PropTypes.func }; @@ -39,6 +40,12 @@ export default class AddAddress extends Component { description: '' }; + componentWillMount () { + if (this.props.address) { + this.onEditAddress(null, this.props.address); + } + } + render () { return (
    - } /> -
    + { this.renderName(address) } + +
    { address }
    + { uuidText }
    { meta.description }
    { this.renderTxCount() }
    +
    @@ -89,6 +94,18 @@ export default class Header extends Component { ); } + renderName (address) { + const { hideName } = this.props; + + if (hideName) { + return null; + } + + return ( + } /> + ); + } + renderTxCount () { const { balance, isContract } = this.props; diff --git a/js/src/views/Address/address.js b/js/src/views/Address/address.js index c1427b2be..9c39203ba 100644 --- a/js/src/views/Address/address.js +++ b/js/src/views/Address/address.js @@ -19,8 +19,9 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import ActionDelete from 'material-ui/svg-icons/action/delete'; import ContentCreate from 'material-ui/svg-icons/content/create'; +import ContentAdd from 'material-ui/svg-icons/content/add'; -import { EditMeta } from '~/modals'; +import { EditMeta, AddAddress } from '~/modals'; import { Actionbar, Button, Page } from '~/ui'; import Header from '../Account/Header'; @@ -32,7 +33,7 @@ class Address extends Component { static contextTypes = { api: PropTypes.object.isRequired, router: PropTypes.object.isRequired - } + }; static propTypes = { setVisibleAccounts: PropTypes.func.isRequired, @@ -40,12 +41,13 @@ class Address extends Component { contacts: PropTypes.object, balances: PropTypes.object, params: PropTypes.object - } + }; state = { showDeleteDialog: false, - showEditDialog: false - } + showEditDialog: false, + showAdd: false + }; componentDidMount () { this.setVisibleAccounts(); @@ -73,32 +75,69 @@ class Address extends Component { render () { const { contacts, balances } = this.props; const { address } = this.props.params; - const { showDeleteDialog } = this.state; + + if (Object.keys(contacts).length === 0) { + return null; + } const contact = (contacts || {})[address]; const balance = (balances || {})[address]; - if (!contact) { + return ( +
    + { this.renderAddAddress(contact, address) } + { this.renderEditDialog(contact) } + { this.renderActionbar(contact) } + { this.renderDelete(contact) } + +
    + + +
    + ); + } + + renderAddAddress (contact, address) { + if (contact) { + return null; + } + + const { contacts } = this.props; + const { showAdd } = this.state; + + if (!showAdd) { return null; } return ( -
    - { this.renderEditDialog(contact) } - { this.renderActionbar(contact) } - - -
    - - -
    + + ); + } + + renderDelete (contact) { + if (!contact) { + return null; + } + + const { showDeleteDialog } = this.state; + + return ( + ); } @@ -116,17 +155,27 @@ class Address extends Component { onClick={ this.showDeleteDialog } /> ]; + const addToBook = ( +
    ); diff --git a/js/src/modals/Transfer/store.js b/js/src/modals/Transfer/store.js index 3a8f54f92..e08d7203d 100644 --- a/js/src/modals/Transfer/store.js +++ b/js/src/modals/Transfer/store.js @@ -54,6 +54,7 @@ export default class TransferStore { @observable sender = ''; @observable senderError = null; + @observable sendersBalances = {}; @observable total = '0.0'; @observable totalError = null; @@ -66,8 +67,6 @@ export default class TransferStore { onClose = null; senders = null; - sendersBalances = null; - isWallet = false; wallet = null; diff --git a/js/src/modals/Transfer/transfer.js b/js/src/modals/Transfer/transfer.js index 0c96a1168..57dc569f2 100644 --- a/js/src/modals/Transfer/transfer.js +++ b/js/src/modals/Transfer/transfer.js @@ -155,8 +155,8 @@ class Transfer extends Component { renderDetailsPage () { const { account, balance, images, senders } = this.props; - const { valueAll, extras, recipient, recipientError, sender, senderError } = this.store; - const { tag, total, totalError, value, valueError } = this.store; + const { recipient, recipientError, sender, senderError, sendersBalances } = this.store; + const { valueAll, extras, tag, total, totalError, value, valueError } = this.store; return (
    tok.token.tag === tag); + const tokenIndex = nextTokens.findIndex((tok) => tok.token && tok.token.tag === tag); if (tokenIndex === -1) { nextTokens.push({ diff --git a/js/src/ui/Form/AddressSelect/addressSelect.css b/js/src/ui/Form/AddressSelect/addressSelect.css index 30671db73..01bc8901d 100644 --- a/js/src/ui/Form/AddressSelect/addressSelect.css +++ b/js/src/ui/Form/AddressSelect/addressSelect.css @@ -15,7 +15,9 @@ /* along with Parity. If not, see . */ .account { - padding: 4px 0 0 0; + padding: 0.25em 0; + display: flex; + align-items: center; } .name { @@ -27,6 +29,11 @@ padding: 0 0 0 1em; } +.balance { + color: #aaa; + padding-left: 1em; +} + .image { display: inline-block; height: 32px; diff --git a/js/src/ui/Form/AddressSelect/addressSelect.js b/js/src/ui/Form/AddressSelect/addressSelect.js index d0f331c34..2fbcc80bf 100644 --- a/js/src/ui/Form/AddressSelect/addressSelect.js +++ b/js/src/ui/Form/AddressSelect/addressSelect.js @@ -21,6 +21,8 @@ import AutoComplete from '../AutoComplete'; import IdentityIcon from '../../IdentityIcon'; import IdentityName from '../../IdentityName'; +import { fromWei } from '~/api/util/wei'; + import styles from './addressSelect.css'; export default class AddressSelect extends Component { @@ -40,7 +42,8 @@ export default class AddressSelect extends Component { value: PropTypes.string, tokens: PropTypes.object, onChange: PropTypes.func.isRequired, - allowInput: PropTypes.bool + allowInput: PropTypes.bool, + balances: PropTypes.object } state = { @@ -129,7 +132,34 @@ export default class AddressSelect extends Component { }; } + renderBalance (address) { + const { balances = {} } = this.props; + const balance = balances[address]; + + if (!balance) { + return null; + } + + const ethToken = balance.tokens.find((t) => t.token.tag.toLowerCase() === 'eth'); + + if (!ethToken) { + return null; + } + + const value = fromWei(ethToken.value); + + return ( +
    + { value.toFormat(3) } { 'ETH' } +
    + ); + } + renderMenuItem (address) { + const balance = this.props.balances + ? this.renderBalance(address) + : null; + const item = (
    + + { balance }
    ); @@ -155,11 +187,10 @@ export default class AddressSelect extends Component { getSearchText () { const entry = this.getEntry(); - const { value } = this.state; return entry && entry.name ? entry.name.toUpperCase() - : value; + : this.state.value; } getEntry () { From 239ba61a9951754ca84f42850ca5bf680234c734 Mon Sep 17 00:00:00 2001 From: keorn Date: Sat, 10 Dec 2016 16:50:23 +0100 Subject: [PATCH 197/295] move transition message to to_step --- ethcore/src/engines/tendermint/mod.rs | 101 +++++++++---------- ethcore/src/engines/tendermint/transition.rs | 46 +-------- 2 files changed, 50 insertions(+), 97 deletions(-) diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index ae6e3129a..7da2a9566 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -209,6 +209,9 @@ impl Tendermint { /// Use via step_service to transition steps. fn to_step(&self, step: Step) { + if let Err(io_err) = self.step_service.send_message(step) { + warn!(target: "poa", "Could not proceed to step {}.", io_err) + } *self.step.write() = step; match step { Step::Propose => { @@ -360,9 +363,7 @@ impl Tendermint { if let Some(step) = next_step { trace!(target: "poa", "handle_valid_message: Transition triggered."); - if let Err(io_err) = self.step_service.send_message(step) { - warn!(target: "poa", "Could not proceed to next step {}.", io_err) - } + self.to_step(step); } } } @@ -557,9 +558,7 @@ impl Engine for Tendermint { fn set_signer(&self, address: Address, password: String) { *self.authority.write() = address; *self.password.write() = Some(password); - if let Err(io_err) = self.step_service.send_message(Step::Propose) { - warn!(target: "poa", "Could not reset the round {}.", io_err); - } + self.to_step(Step::Propose); } fn stop(&self) { @@ -605,6 +604,40 @@ impl Engine for Tendermint { true } + /// Equivalent to a timeout: to be used for tests. + fn step(&self) { + let next_step = match *self.step.read() { + Step::Propose => { + trace!(target: "poa", "timeout: Propose timeout."); + Step::Prevote + }, + Step::Prevote if self.has_enough_any_votes() => { + trace!(target: "poa", "timeout: Prevote timeout."); + Step::Precommit + }, + Step::Prevote => { + trace!(target: "poa", "timeout: Prevote timeout without enough votes."); + self.broadcast_old_messages(); + Step::Prevote + }, + Step::Precommit if self.has_enough_any_votes() => { + trace!(target: "poa", "timeout: Precommit timeout."); + self.increment_round(1); + Step::Propose + }, + Step::Precommit => { + trace!(target: "poa", "timeout: Precommit timeout without enough votes."); + self.broadcast_old_messages(); + Step::Precommit + }, + Step::Commit => { + trace!(target: "poa", "timeout: Commit timeout."); + Step::Propose + }, + }; + self.to_step(next_step); + } + fn register_message_channel(&self, message_channel: IoChannel) { trace!(target: "poa", "register_message_channel: Register the IoChannel."); *self.message_channel.lock() = Some(message_channel); @@ -878,16 +911,16 @@ mod tests { let prevote_future = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h + 1, r, Step::Prevote, proposal); - engine.stop(); // Relays all valid present and future messages. assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(prevote_current))); assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(precommit_current))); assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(prevote_future))); + engine.stop(); } #[test] - #[ignore] fn seal_submission() { + ::env_logger::init().unwrap(); let (spec, tap) = setup(); let engine = spec.engine.clone(); let mut db_result = get_temp_state_db(); @@ -903,51 +936,11 @@ mod tests { // Propose let (b, mut seal) = propose_default(&spec, v1.clone()); let proposal = Some(b.header().bare_hash()); - - // Register IoHandler remembers messages. - let io_service = IoService::::start().unwrap(); - let test_io = TestIo::new(); - io_service.register_handler(test_io.clone()).unwrap(); - engine.register_message_channel(io_service.channel()); - - // Prevote. - vote(&engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Prevote, proposal); - - vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal); - vote(&engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Precommit, proposal); - vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal); - - // Wait a bit for async stuff. - engine.stop(); - io_service.stop(); - seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v0, v1); - println!("should {:?}, {:?}", proposal.unwrap(), seal); - println!("{:?}", *test_io.received.read()); - assert!(test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal))); - } - - #[test] - #[ignore] - fn skips_to_future_round() { - let (spec, tap) = setup(); - let engine = spec.engine.clone(); - let mut db_result = get_temp_state_db(); - let mut db = db_result.take(); - spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); - let v0 = insert_and_register(&tap, &engine, "0"); - let v1 = insert_and_register(&tap, &engine, "1"); - - let h = 1; - let r = 2; - - // Propose - let (b, mut seal) = propose_default(&spec, v1.clone()); - let proposal = Some(b.header().bare_hash()); + let test_io = TestIo::new(); // Register IoHandler remembers messages. let io_service = IoService::::start().unwrap(); - let test_io = TestIo::new(); io_service.register_handler(test_io.clone()).unwrap(); engine.register_message_channel(io_service.channel()); @@ -960,13 +953,11 @@ mod tests { // Wait a bit for async stuff. ::std::thread::sleep(::std::time::Duration::from_millis(100)); - engine.stop(); - io_service.stop(); - seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v0, v1); - //assert!(test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal.clone()))); - //seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v1, v0); - println!("have {:?}", *test_io.received.read()); + + seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v1, v0); println!("should {:?}, {:?}", proposal.unwrap(), seal); + println!("{:?}", *test_io.received.read()); assert!(test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal))); + engine.stop(); } } diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs index 4c54714a5..5e50fe6e2 100644 --- a/ethcore/src/engines/tendermint/transition.rs +++ b/ethcore/src/engines/tendermint/transition.rs @@ -17,9 +17,10 @@ //! Tendermint timeout handling. use std::sync::Weak; +use time::Duration; use io::{IoContext, IoHandler, TimerToken}; use super::{Tendermint, Step}; -use time::Duration; +use engines::Engine; pub struct TransitionHandler { pub engine: Weak, @@ -71,48 +72,10 @@ impl IoHandler for TransitionHandler { } } - fn timeout(&self, io: &IoContext, timer: TimerToken) { + fn timeout(&self, _io: &IoContext, timer: TimerToken) { if timer == ENGINE_TIMEOUT_TOKEN { if let Some(engine) = self.engine.upgrade() { - let next_step = match *engine.step.read() { - Step::Propose => { - trace!(target: "poa", "timeout: Propose timeout."); - set_timeout(io, engine.our_params.timeouts.prevote); - Some(Step::Prevote) - }, - Step::Prevote if engine.has_enough_any_votes() => { - trace!(target: "poa", "timeout: Prevote timeout."); - set_timeout(io, engine.our_params.timeouts.precommit); - Some(Step::Precommit) - }, - Step::Prevote => { - trace!(target: "poa", "timeout: Prevote timeout without enough votes."); - set_timeout(io, engine.our_params.timeouts.prevote); - engine.broadcast_old_messages(); - None - }, - Step::Precommit if engine.has_enough_any_votes() => { - trace!(target: "poa", "timeout: Precommit timeout."); - set_timeout(io, engine.our_params.timeouts.propose); - engine.increment_round(1); - Some(Step::Propose) - }, - Step::Precommit => { - trace!(target: "poa", "timeout: Precommit timeout without enough votes."); - set_timeout(io, engine.our_params.timeouts.precommit); - engine.broadcast_old_messages(); - None - }, - Step::Commit => { - trace!(target: "poa", "timeout: Commit timeout."); - set_timeout(io, engine.our_params.timeouts.propose); - Some(Step::Propose) - }, - }; - - if let Some(s) = next_step { - engine.to_step(s) - } + engine.step(); } } } @@ -128,7 +91,6 @@ impl IoHandler for TransitionHandler { Step::Precommit => set_timeout(io, engine.our_params.timeouts.precommit), Step::Commit => set_timeout(io, engine.our_params.timeouts.commit), }; - engine.to_step(*next_step); } } } From aaf6da4c0003aeee8778b7954a4f48f055603351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sat, 10 Dec 2016 16:51:50 +0100 Subject: [PATCH 198/295] Returning persistent node id --- ethcore/light/src/net/context.rs | 29 +++++++--- ethcore/light/src/net/tests/mod.rs | 86 ++++++++++++++++-------------- sync/src/api.rs | 3 +- util/network/src/lib.rs | 2 +- 4 files changed, 70 insertions(+), 50 deletions(-) diff --git a/ethcore/light/src/net/context.rs b/ethcore/light/src/net/context.rs index c05e69b0f..af1f4c677 100644 --- a/ethcore/light/src/net/context.rs +++ b/ethcore/light/src/net/context.rs @@ -16,13 +16,13 @@ //! I/O and event context generalizations. -use network::{NetworkContext, PeerId}; +use network::{NetworkContext, PeerId, NodeId}; use super::{Announcement, LightProtocol, ReqId}; use super::error::Error; use request::Request; -/// An I/O context which allows sending and receiving packets as well as +/// An I/O context which allows sending and receiving packets as well as /// disconnecting peers. This is used as a generalization of the portions /// of a p2p network which the light protocol structure makes use of. pub trait IoContext { @@ -41,6 +41,9 @@ pub trait IoContext { /// Get a peer's protocol version. fn protocol_version(&self, peer: PeerId) -> Option; + + /// Persistent peer id + fn persistent_peer_id(&self, peer: PeerId) -> Option; } impl<'a> IoContext for NetworkContext<'a> { @@ -67,6 +70,10 @@ impl<'a> IoContext for NetworkContext<'a> { fn protocol_version(&self, peer: PeerId) -> Option { self.protocol_version(self.subprotocol_name(), peer) } + + fn persistent_peer_id(&self, peer: PeerId) -> Option { + self.session_info(peer).and_then(|info| info.id) + } } /// Context for a protocol event. @@ -75,6 +82,9 @@ pub trait EventContext { /// disconnected/connected peer. fn peer(&self) -> PeerId; + /// Returns the relevant's peer persistent Id (aka NodeId). + fn persistent_peer_id(&self) -> Option; + /// Make a request from a peer. fn request_from(&self, peer: PeerId, request: Request) -> Result; @@ -89,7 +99,7 @@ pub trait EventContext { fn disable_peer(&self, peer: PeerId); } -/// Concrete implementation of `EventContext` over the light protocol struct and +/// Concrete implementation of `EventContext` over the light protocol struct and /// an io context. pub struct Ctx<'a> { /// Io context to enable immediate response to events. @@ -97,11 +107,18 @@ pub struct Ctx<'a> { /// Protocol implementation. pub proto: &'a LightProtocol, /// Relevant peer for event. - pub peer: PeerId, + pub peer: PeerId, } impl<'a> EventContext for Ctx<'a> { - fn peer(&self) -> PeerId { self.peer } + + fn peer(&self) -> PeerId { + self.peer + } + + fn persistent_peer_id(&self) -> Option { + self.io.persistent_peer_id(self.peer) + } fn request_from(&self, peer: PeerId, request: Request) -> Result { self.proto.request_from(self.io, &peer, request) } @@ -117,4 +134,4 @@ impl<'a> EventContext for Ctx<'a> { fn disable_peer(&self, peer: PeerId) { self.io.disable_peer(peer); } -} \ No newline at end of file +} diff --git a/ethcore/light/src/net/tests/mod.rs b/ethcore/light/src/net/tests/mod.rs index 876432ce2..e2a17a41e 100644 --- a/ethcore/light/src/net/tests/mod.rs +++ b/ethcore/light/src/net/tests/mod.rs @@ -15,13 +15,13 @@ // along with Parity. If not, see . //! Tests for the `LightProtocol` implementation. -//! These don't test of the higher level logic on top of +//! These don't test of the higher level logic on top of use ethcore::blockchain_info::BlockChainInfo; use ethcore::client::{BlockChainClient, EachBlockWith, TestBlockChainClient}; use ethcore::ids::BlockId; use ethcore::transaction::SignedTransaction; -use network::PeerId; +use network::{PeerId, NodeId}; use net::buffer_flow::FlowParams; use net::context::IoContext; @@ -68,6 +68,10 @@ impl IoContext for Expect { fn protocol_version(&self, _peer: PeerId) -> Option { Some(super::MAX_PROTOCOL_VERSION) } + + fn persistent_peer_id(&self, _peer: PeerId) -> Option { + None + } } // can't implement directly for Arc due to cross-crate orphan rules. @@ -106,7 +110,7 @@ impl Provider for TestProvider { .map(|x: u64| x.saturating_mul(req.skip + 1)) .take_while(|x| if req.reverse { x < &start_num } else { best_num - start_num >= *x }) .map(|x| if req.reverse { start_num - x } else { start_num + x }) - .map(|x| self.0.client.block_header(BlockId::Number(x))) + .map(|x| self.0.client.block_header(BlockId::Number(x))) .take_while(|x| x.is_some()) .flat_map(|x| x) .collect() @@ -139,12 +143,12 @@ impl Provider for TestProvider { } } }) - .collect() + .collect() } fn contract_code(&self, req: request::ContractCodes) -> Vec { req.code_requests.into_iter() - .map(|req| { + .map(|req| { req.account_key.iter().chain(req.account_key.iter()).cloned().collect() }) .collect() @@ -202,9 +206,9 @@ fn status(chain_info: BlockChainInfo) -> Status { #[test] fn handshake_expected() { let flow_params = make_flow_params(); - let capabilities = capabilities(); + let capabilities = capabilities(); - let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); + let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); let status = status(provider.client.chain_info()); @@ -217,9 +221,9 @@ fn handshake_expected() { #[should_panic] fn genesis_mismatch() { let flow_params = make_flow_params(); - let capabilities = capabilities(); + let capabilities = capabilities(); - let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); + let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); let mut status = status(provider.client.chain_info()); status.genesis_hash = H256::default(); @@ -232,15 +236,15 @@ fn genesis_mismatch() { #[test] fn buffer_overflow() { let flow_params = make_flow_params(); - let capabilities = capabilities(); + let capabilities = capabilities(); - let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); + let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); let status = status(provider.client.chain_info()); { - let packet_body = write_handshake(&status, &capabilities, Some(&flow_params)); - proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); + let packet_body = write_handshake(&status, &capabilities, Some(&flow_params)); + proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); } { @@ -266,9 +270,9 @@ fn buffer_overflow() { #[test] fn get_block_headers() { let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into()); - let capabilities = capabilities(); + let capabilities = capabilities(); - let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); + let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); let cur_status = status(provider.client.chain_info()); let my_status = write_handshake(&cur_status, &capabilities, Some(&flow_params)); @@ -278,8 +282,8 @@ fn get_block_headers() { let cur_status = status(provider.client.chain_info()); { - let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params)); - proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); + let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params)); + proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &my_status); } @@ -300,7 +304,7 @@ fn get_block_headers() { let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Headers, 10); let mut response_stream = RlpStream::new_list(12); - + response_stream.append(&req_id).append(&new_buf); for header in headers { response_stream.append_raw(&header, 1); @@ -316,9 +320,9 @@ fn get_block_headers() { #[test] fn get_block_bodies() { let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into()); - let capabilities = capabilities(); + let capabilities = capabilities(); - let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); + let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); let cur_status = status(provider.client.chain_info()); let my_status = write_handshake(&cur_status, &capabilities, Some(&flow_params)); @@ -328,8 +332,8 @@ fn get_block_bodies() { let cur_status = status(provider.client.chain_info()); { - let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params)); - proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); + let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params)); + proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &my_status); } @@ -347,7 +351,7 @@ fn get_block_bodies() { let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Bodies, 10); let mut response_stream = RlpStream::new_list(12); - + response_stream.append(&req_id).append(&new_buf); for body in bodies { response_stream.append_raw(&body, 1); @@ -363,9 +367,9 @@ fn get_block_bodies() { #[test] fn get_block_receipts() { let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into()); - let capabilities = capabilities(); + let capabilities = capabilities(); - let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); + let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); let cur_status = status(provider.client.chain_info()); let my_status = write_handshake(&cur_status, &capabilities, Some(&flow_params)); @@ -375,8 +379,8 @@ fn get_block_receipts() { let cur_status = status(provider.client.chain_info()); { - let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params)); - proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); + let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params)); + proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body)); proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &my_status); } @@ -400,7 +404,7 @@ fn get_block_receipts() { let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Receipts, receipts.len()); let mut response_stream = RlpStream::new_list(2 + receipts.len()); - + response_stream.append(&req_id).append(&new_buf); for block_receipts in receipts { response_stream.append_raw(&block_receipts, 1); @@ -416,15 +420,15 @@ fn get_block_receipts() { #[test] fn get_state_proofs() { let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into()); - let capabilities = capabilities(); + let capabilities = capabilities(); - let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); + let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); let cur_status = status(provider.client.chain_info()); { - let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params)); - proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body.clone())); + let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params)); + proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body.clone())); proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &packet_body); } @@ -432,7 +436,7 @@ fn get_state_proofs() { let key1 = U256::from(11223344).into(); let key2 = U256::from(99988887).into(); - let request = Request::StateProofs (request::StateProofs { + let request = Request::StateProofs (request::StateProofs { requests: vec![ request::StateProof { block: H256::default(), key1: key1, key2: None, from_level: 0 }, request::StateProof { block: H256::default(), key1: key1, key2: Some(key2), from_level: 0}, @@ -449,7 +453,7 @@ fn get_state_proofs() { let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::StateProofs, 2); let mut response_stream = RlpStream::new_list(4); - + response_stream.append(&req_id).append(&new_buf); for proof in proofs { response_stream.append_raw(&proof, 1); @@ -465,15 +469,15 @@ fn get_state_proofs() { #[test] fn get_contract_code() { let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into()); - let capabilities = capabilities(); + let capabilities = capabilities(); - let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); + let (provider, proto) = setup(flow_params.clone(), capabilities.clone()); let cur_status = status(provider.client.chain_info()); { - let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params)); - proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body.clone())); + let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params)); + proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body.clone())); proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &packet_body); } @@ -481,7 +485,7 @@ fn get_contract_code() { let key1 = U256::from(11223344).into(); let key2 = U256::from(99988887).into(); - let request = Request::Codes (request::ContractCodes { + let request = Request::Codes (request::ContractCodes { code_requests: vec![ request::ContractCode { block_hash: H256::default(), account_key: key1 }, request::ContractCode { block_hash: H256::default(), account_key: key2 }, @@ -498,7 +502,7 @@ fn get_contract_code() { let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Codes, 2); let mut response_stream = RlpStream::new_list(4); - + response_stream.append(&req_id).append(&new_buf); for code in codes { response_stream.append(&code); @@ -509,4 +513,4 @@ fn get_contract_code() { let expected = Expect::Respond(packet::CONTRACT_CODES, response); proto.handle_packet(&expected, &1, packet::GET_CONTRACT_CODES, &request_body); -} \ No newline at end of file +} diff --git a/sync/src/api.rs b/sync/src/api.rs index 10434ce26..0f3695fe9 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -351,8 +351,7 @@ struct TxRelay(Arc); impl LightHandler for TxRelay { fn on_transactions(&self, ctx: &EventContext, relay: &[::ethcore::transaction::SignedTransaction]) { trace!(target: "les", "Relaying {} transactions from peer {}", relay.len(), ctx.peer()); - // TODO [ToDr] Can we get a peer enode somehow? - self.0.queue_transactions(relay.iter().map(|tx| ::rlp::encode(tx).to_vec()).collect(), None) + self.0.queue_transactions(relay.iter().map(|tx| ::rlp::encode(tx).to_vec()).collect(), ctx.persistent_peer_id()) } } diff --git a/util/network/src/lib.rs b/util/network/src/lib.rs index f21cb498d..a1eef68fa 100644 --- a/util/network/src/lib.rs +++ b/util/network/src/lib.rs @@ -99,7 +99,7 @@ pub use stats::NetworkStats; pub use session::SessionInfo; use io::TimerToken; -pub use node_table::is_valid_node_url; +pub use node_table::{is_valid_node_url, NodeId}; const PROTOCOL_VERSION: u32 = 4; From 84116130f6b93ad6cfaa094818e28d459998174a Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 16:58:03 +0100 Subject: [PATCH 199/295] Add sender balances to contract (exec/deploy) --- .../DeployContract/DetailsStep/detailsStep.js | 3 +++ .../modals/DeployContract/deployContract.js | 22 +++++++++++++++++-- .../DetailsStep/detailsStep.js | 15 ++++++++----- .../modals/ExecuteContract/executeContract.js | 14 +++++++++--- 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/js/src/modals/DeployContract/DetailsStep/detailsStep.js b/js/src/modals/DeployContract/DetailsStep/detailsStep.js index aa0a30e55..9db223793 100644 --- a/js/src/modals/DeployContract/DetailsStep/detailsStep.js +++ b/js/src/modals/DeployContract/DetailsStep/detailsStep.js @@ -37,6 +37,7 @@ export default class DetailsStep extends Component { onParamsChange: PropTypes.func.isRequired, onInputsChange: PropTypes.func.isRequired, + balances: PropTypes.object, fromAddress: PropTypes.string, fromAddressError: PropTypes.string, name: PropTypes.string, @@ -77,6 +78,7 @@ export default class DetailsStep extends Component { render () { const { accounts, + balances, readOnly, fromAddress, fromAddressError, @@ -97,6 +99,7 @@ export default class DetailsStep extends Component { value={ fromAddress } error={ fromAddressError } accounts={ accounts } + balances={ balances } onChange={ this.onFromAddressChange } /> . import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; import ContentClear from 'material-ui/svg-icons/content/clear'; +import { pick } from 'lodash'; import { BusyStep, CompletedStep, CopyToClipboard, Button, IdentityIcon, Modal, TxHash } from '~/ui'; import { ERRORS, validateAbi, validateCode, validateName } from '~/util/validation'; @@ -36,7 +38,7 @@ const STEPS = { COMPLETED: { title: 'completed' } }; -export default class DeployContract extends Component { +class DeployContract extends Component { static contextTypes = { api: PropTypes.object.isRequired, store: PropTypes.object.isRequired @@ -45,6 +47,7 @@ export default class DeployContract extends Component { static propTypes = { accounts: PropTypes.object.isRequired, onClose: PropTypes.func.isRequired, + balances: PropTypes.object, abi: PropTypes.string, code: PropTypes.string, readOnly: PropTypes.bool, @@ -192,7 +195,7 @@ export default class DeployContract extends Component { } renderStep () { - const { accounts, readOnly } = this.props; + const { accounts, readOnly, balances } = this.props; const { address, deployError, step, deployState, txhash, rejected } = this.state; if (deployError) { @@ -216,6 +219,7 @@ export default class DeployContract extends Component { { + const balances = pick(state.balances.balances, fromAddresses); + return { balances }; + }; +} + +export default connect( + mapStateToProps +)(DeployContract); + diff --git a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js index 3ffb929a9..7bbe7be84 100644 --- a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js +++ b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js @@ -35,22 +35,24 @@ export default class DetailsStep extends Component { amount: PropTypes.string, amountError: PropTypes.string, onAmountChange: PropTypes.func.isRequired, + onFromAddressChange: PropTypes.func.isRequired, + onValueChange: PropTypes.func.isRequired, + values: PropTypes.array.isRequired, + valuesError: PropTypes.array.isRequired, + + balances: PropTypes.object, fromAddress: PropTypes.string, fromAddressError: PropTypes.string, gasEdit: PropTypes.bool, - onFromAddressChange: PropTypes.func.isRequired, func: PropTypes.object, funcError: PropTypes.string, onFuncChange: PropTypes.func, onGasEditClick: PropTypes.func, - values: PropTypes.array.isRequired, - valuesError: PropTypes.array.isRequired, - warning: PropTypes.string, - onValueChange: PropTypes.func.isRequired + warning: PropTypes.string } render () { - const { accounts, amount, amountError, fromAddress, fromAddressError, gasEdit, onGasEditClick, onFromAddressChange, onAmountChange } = this.props; + const { accounts, amount, amountError, balances, fromAddress, fromAddressError, gasEdit, onGasEditClick, onFromAddressChange, onAmountChange } = this.props; return ( @@ -61,6 +63,7 @@ export default class DetailsStep extends Component { value={ fromAddress } error={ fromAddressError } accounts={ accounts } + balances={ balances } onChange={ onFromAddressChange } /> { this.renderFunctionSelect() } { this.renderParameters() } diff --git a/js/src/modals/ExecuteContract/executeContract.js b/js/src/modals/ExecuteContract/executeContract.js index 7b4e8ccd2..c3ac96490 100644 --- a/js/src/modals/ExecuteContract/executeContract.js +++ b/js/src/modals/ExecuteContract/executeContract.js @@ -18,6 +18,8 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { observer } from 'mobx-react'; +import { pick } from 'lodash'; + import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; import ContentClear from 'material-ui/svg-icons/content/clear'; import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; @@ -57,6 +59,7 @@ class ExecuteContract extends Component { isTest: PropTypes.bool, fromAddress: PropTypes.string, accounts: PropTypes.object, + balances: PropTypes.object, contract: PropTypes.object, gasLimit: PropTypes.object.isRequired, onClose: PropTypes.func.isRequired, @@ -362,10 +365,15 @@ class ExecuteContract extends Component { } } -function mapStateToProps (state) { - const { gasLimit } = state.nodeStatus; +function mapStateToProps (initState, initProps) { + const fromAddresses = Object.keys(initProps.accounts); - return { gasLimit }; + return (state) => { + const balances = pick(state.balances.balances, fromAddresses); + const { gasLimit } = state.nodeStatus; + + return { gasLimit, balances }; + }; } function mapDispatchToProps (dispatch) { From cd6ab072170bfe30ac04a79008c9024d784141e6 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 17:06:44 +0100 Subject: [PATCH 200/295] Use the new `onClose` autocomplete prop --- js/src/ui/Form/AutoComplete/autocomplete.js | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/js/src/ui/Form/AutoComplete/autocomplete.js b/js/src/ui/Form/AutoComplete/autocomplete.js index c7a5dd141..f5ad43201 100644 --- a/js/src/ui/Form/AutoComplete/autocomplete.js +++ b/js/src/ui/Form/AutoComplete/autocomplete.js @@ -44,7 +44,6 @@ export default class AutoComplete extends Component { lastChangedValue: undefined, entry: null, open: false, - fakeBlur: false, dataSource: [] } @@ -78,7 +77,7 @@ export default class AutoComplete extends Component { onUpdateInput={ onUpdateInput } searchText={ value } onFocus={ this.onFocus } - onBlur={ this.onBlur } + onClose={ this.onClose } animation={ PopoverAnimationVertical } filter={ filter } popoverProps={ { open } } @@ -121,7 +120,6 @@ export default class AutoComplete extends Component { case 'down': const { menu } = muiAutocomplete.refs; menu && menu.handleKeyDown(event); - this.setState({ fakeBlur: true }); break; case 'enter': @@ -155,22 +153,12 @@ export default class AutoComplete extends Component { this.setState({ entry, open: false }); } - onBlur = (event) => { + onClose = (event) => { const { onUpdateInput } = this.props; - // TODO: Handle blur gracefully where we use onUpdateInput (currently replaces - // input where text is allowed with the last selected value from the dropdown) if (!onUpdateInput) { - window.setTimeout(() => { - const { entry, fakeBlur } = this.state; - - if (fakeBlur) { - this.setState({ fakeBlur: false }); - return; - } - - this.handleOnChange(entry); - }, 200); + const { entry } = this.state; + this.handleOnChange(entry); } } From c408861c295ce8beeb51134e154b6dc535b2f08c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sat, 10 Dec 2016 17:36:29 +0100 Subject: [PATCH 201/295] Updating submodules --- ethcore/res/ethereum/tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/res/ethereum/tests b/ethcore/res/ethereum/tests index d509c7593..e8f4624b7 160000 --- a/ethcore/res/ethereum/tests +++ b/ethcore/res/ethereum/tests @@ -1 +1 @@ -Subproject commit d509c75936ec6cbba683ee1916aa0bca436bc376 +Subproject commit e8f4624b7f1a15c63674eecf577c7ab76c3b16be From b9909da8b13cc103dddb8c647d03e579a9acf203 Mon Sep 17 00:00:00 2001 From: keorn Date: Sat, 10 Dec 2016 17:40:20 +0100 Subject: [PATCH 202/295] move Sealing methods to MiningBlockChainClient --- ethcore/src/client/client.rs | 32 ++++++++++++--------------- ethcore/src/client/test_client.rs | 12 ++++++++++ ethcore/src/client/traits.rs | 9 ++++++++ ethcore/src/engines/tendermint/mod.rs | 2 +- ethcore/src/service.rs | 2 +- 5 files changed, 37 insertions(+), 20 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index e685969cc..eb7311975 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -580,23 +580,6 @@ impl Client { self.miner.clone() } - /// Used by PoA to try sealing on period change. - pub fn update_sealing(&self) { - self.miner.update_sealing(self) - } - - /// Used by PoA to submit gathered signatures. - pub fn submit_seal(&self, block_hash: H256, seal: Vec) { - if self.miner.submit_seal(self, block_hash, seal).is_err() { - warn!(target: "poa", "Wrong internal seal submission!") - } - } - - /// Used by PoA to communicate with peers. - pub fn broadcast_message(&self, message: Bytes) { - self.notify(|notify| notify.broadcast(message.clone())); - } - /// Attempt to get a copy of a specific block's final state. /// /// This will not fail if given BlockID::Latest. @@ -1335,7 +1318,6 @@ impl BlockChainClient for Client { } impl MiningBlockChainClient for Client { - fn latest_schedule(&self) -> Schedule { self.engine.schedule(&self.latest_env_info()) } @@ -1378,6 +1360,20 @@ impl MiningBlockChainClient for Client { &self.factories.vm } + fn broadcast_message(&self, message: Bytes) { + self.notify(|notify| notify.broadcast(message.clone())); + } + + fn update_sealing(&self) { + self.miner.update_sealing(self) + } + + fn submit_seal(&self, block_hash: H256, seal: Vec) { + if self.miner.submit_seal(self, block_hash, seal).is_err() { + warn!(target: "poa", "Wrong internal seal submission!") + } + } + fn broadcast_proposal_block(&self, block: SealedBlock) { self.notify(|notify| { notify.new_blocks( diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 43dd366b0..9be4f8ae0 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -362,6 +362,18 @@ impl MiningBlockChainClient for TestBlockChainClient { } fn broadcast_proposal_block(&self, _block: SealedBlock) {} + + fn broadcast_message(&self, _message: Bytes) {} + + fn update_sealing(&self) { + self.miner.update_sealing(self) + } + + fn submit_seal(&self, block_hash: H256, seal: Vec) { + if self.miner.submit_seal(self, block_hash, seal).is_err() { + warn!(target: "poa", "Wrong internal seal submission!") + } + } } impl BlockChainClient for TestBlockChainClient { diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 7488b8baf..69ff7e05a 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -276,6 +276,15 @@ pub trait MiningBlockChainClient: BlockChainClient { /// Returns EvmFactory. fn vm_factory(&self) -> &EvmFactory; + /// Used by PoA to try sealing on period change. + fn update_sealing(&self); + + /// Used by PoA to submit gathered signatures. + fn submit_seal(&self, block_hash: H256, seal: Vec); + + /// Used by PoA to communicate with peers. + fn broadcast_message(&self, message: Bytes); + /// Broadcast a block proposal. fn broadcast_proposal_block(&self, block: SealedBlock); diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 7da2a9566..2b779950a 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -667,7 +667,7 @@ mod tests { use super::*; use super::message::*; - /// Accounts inserted with "1" and "2" are authorities. First proposer is "0". + /// Accounts inserted with "0" and "1" are authorities. First proposer is "0". fn setup() -> (Spec, Arc) { let tap = Arc::new(AccountProvider::transient_provider()); let spec = Spec::new_test_tendermint(); diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index 0429d3539..c2cb1889e 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -21,7 +21,7 @@ use rlp::{UntrustedRlp, View}; use io::*; use spec::Spec; use error::*; -use client::{Client, ClientConfig, ChainNotify}; +use client::{Client, MiningBlockChainClient, ClientConfig, ChainNotify}; use miner::Miner; use snapshot::ManifestData; use snapshot::service::{Service as SnapshotService, ServiceParams as SnapServiceParams}; From 1611d190ba3798904d6c222213ecedb26b323526 Mon Sep 17 00:00:00 2001 From: keorn Date: Sat, 10 Dec 2016 17:40:53 +0100 Subject: [PATCH 203/295] initial tendetmint consensus test --- sync/src/tests/consensus.rs | 67 +++++++++++++++++++++++++++++++++---- sync/src/tests/helpers.rs | 4 --- 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/sync/src/tests/consensus.rs b/sync/src/tests/consensus.rs index 00a036a54..89297ba1f 100644 --- a/sync/src/tests/consensus.rs +++ b/sync/src/tests/consensus.rs @@ -26,8 +26,6 @@ use SyncConfig; #[test] fn test_authority_round() { - ::env_logger::init().ok(); - let s1 = KeyPair::from_secret("1".sha3()).unwrap(); let s2 = KeyPair::from_secret("0".sha3()).unwrap(); let spec_factory = || { @@ -41,10 +39,8 @@ fn test_authority_round() { let mut net = TestNet::new_with_spec(2, SyncConfig::default(), spec_factory); let mut net = &mut *net; // Push transaction to both clients. Only one of them gets lucky to mine a block. - net.peer(0).chain.miner().set_author(s1.address()); - net.peer(0).chain.engine().set_signer(s1.address(), "".to_owned()); - net.peer(1).chain.miner().set_author(s2.address()); - net.peer(1).chain.engine().set_signer(s2.address(), "".to_owned()); + net.peer(0).chain.miner().set_engine_signer(s1.address(), "".to_owned()).unwrap(); + net.peer(1).chain.miner().set_engine_signer(s2.address(), "".to_owned()).unwrap(); let tx1 = Transaction { nonce: 0.into(), gas_price: 0.into(), @@ -76,3 +72,62 @@ fn test_authority_round() { assert_eq!(net.peer(1).chain.chain_info().best_block_number, 2); } +#[test] +fn test_tendermint() { + ::env_logger::init().ok(); + + let s1 = KeyPair::from_secret("1".sha3()).unwrap(); + let s2 = KeyPair::from_secret("0".sha3()).unwrap(); + let spec_factory = || { + let spec = Spec::new_test_tendermint(); + let account_provider = AccountProvider::transient_provider(); + account_provider.insert_account(s1.secret().clone(), "").unwrap(); + account_provider.insert_account(s2.secret().clone(), "").unwrap(); + spec.engine.register_account_provider(Arc::new(account_provider)); + spec + }; + let mut net = TestNet::new_with_spec(2, SyncConfig::default(), spec_factory); + let mut net = &mut *net; + // Push transaction to both clients. Only one of them issues a proposal. + net.peer(0).chain.miner().set_engine_signer(s1.address(), "".to_owned()).unwrap(); + net.peer(1).chain.miner().set_engine_signer(s2.address(), "".to_owned()).unwrap(); + let tx1 = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 21000.into(), + action: Action::Call(Address::default()), + value: 0.into(), + data: Vec::new(), + }.sign(s1.secret(), None); + // exhange statuses + net.sync_steps(5); + net.peer(0).chain.miner().import_own_transaction(&net.peer(0).chain, tx1).unwrap(); + // Propose + net.sync(); + // Propose timeout + net.peer(1).chain.engine().step(); + net.peer(0).chain.engine().step(); + // Precommit + net.sync(); + + net.sync_steps(5); + net.sync(); + ::std::thread::sleep(::std::time::Duration::from_millis(100)); + assert_eq!(net.peer(0).chain.chain_info().best_block_number, 1); + assert_eq!(net.peer(1).chain.chain_info().best_block_number, 1); + + let tx2 = Transaction { + nonce: 0.into(), + gas_price: 0.into(), + gas: 21000.into(), + action: Action::Call(Address::default()), + value: 0.into(), + data: Vec::new(), + }.sign(s2.secret(), None); + net.peer(1).chain.miner().import_own_transaction(&net.peer(1).chain, tx2).unwrap(); + net.peer(1).chain.engine().step(); + net.peer(1).chain.miner().update_sealing(&net.peer(1).chain); + net.sync(); + assert_eq!(net.peer(0).chain.chain_info().best_block_number, 2); + assert_eq!(net.peer(1).chain.chain_info().best_block_number, 2); +} diff --git a/sync/src/tests/helpers.rs b/sync/src/tests/helpers.rs index 096cc5756..9be250ba0 100644 --- a/sync/src/tests/helpers.rs +++ b/sync/src/tests/helpers.rs @@ -317,10 +317,6 @@ impl TestNet where C: FlushingBlockChainClient { let mut queue = peer.queue.write(); peer.sync.write().chain_new_blocks(&mut TestIo::new(&peer.chain, &peer.snapshot_service, &mut queue, None), &[], &[], &[], &[], &[], &[]); } - - fn start(&self) {} - - fn stop(&self) {} } impl ChainNotify for TestPeer { From b6c7ed24b7bc290e4b78f82b7baca5a6cd8e3070 Mon Sep 17 00:00:00 2001 From: keorn Date: Sat, 10 Dec 2016 18:35:29 +0100 Subject: [PATCH 204/295] Client trait reorg --- ethcore/src/client/client.rs | 9 +++++---- ethcore/src/client/test_client.rs | 4 ++-- ethcore/src/client/traits.rs | 6 +++--- ethcore/src/service.rs | 6 ++---- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 8586eec30..f7141248a 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1294,6 +1294,11 @@ impl BlockChainClient for Client { } } + fn broadcast_consensus_message(&self, message: Bytes) { + self.notify(|notify| notify.broadcast(message.clone())); + } + + fn signing_network_id(&self) -> Option { self.engine.signing_network_id(&self.latest_env_info()) } @@ -1360,10 +1365,6 @@ impl MiningBlockChainClient for Client { &self.factories.vm } - fn broadcast_message(&self, message: Bytes) { - self.notify(|notify| notify.broadcast(message.clone())); - } - fn update_sealing(&self) { self.miner.update_sealing(self) } diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 6dd59f43d..485de0d20 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -363,8 +363,6 @@ impl MiningBlockChainClient for TestBlockChainClient { fn broadcast_proposal_block(&self, _block: SealedBlock) {} - fn broadcast_message(&self, _message: Bytes) {} - fn update_sealing(&self) { self.miner.update_sealing(self) } @@ -681,6 +679,8 @@ impl BlockChainClient for TestBlockChainClient { self.spec.engine.handle_message(UntrustedRlp::new(&message)).unwrap(); } + fn broadcast_consensus_message(&self, _message: Bytes) {} + fn pending_transactions(&self) -> Vec { self.miner.pending_transactions(self.chain_info().best_block_number) } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index bf7a180b4..f3c3cd65c 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -205,6 +205,9 @@ pub trait BlockChainClient : Sync + Send { /// Queue conensus engine message. fn queue_consensus_message(&self, message: Bytes); + /// Used by PoA to communicate with peers. + fn broadcast_consensus_message(&self, message: Bytes); + /// list all transactions fn pending_transactions(&self) -> Vec; @@ -282,9 +285,6 @@ pub trait MiningBlockChainClient: BlockChainClient { /// Used by PoA to submit gathered signatures. fn submit_seal(&self, block_hash: H256, seal: Vec); - /// Used by PoA to communicate with peers. - fn broadcast_message(&self, message: Bytes); - /// Broadcast a block proposal. fn broadcast_proposal_block(&self, block: SealedBlock); diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index c2cb1889e..f4b5eae8d 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -21,7 +21,7 @@ use rlp::{UntrustedRlp, View}; use io::*; use spec::Spec; use error::*; -use client::{Client, MiningBlockChainClient, ClientConfig, ChainNotify}; +use client::{Client, BlockChainClient, MiningBlockChainClient, ClientConfig, ChainNotify}; use miner::Miner; use snapshot::ManifestData; use snapshot::service::{Service as SnapshotService, ServiceParams as SnapServiceParams}; @@ -29,8 +29,6 @@ use std::sync::atomic::AtomicBool; #[cfg(feature="ipc")] use nanoipc; -#[cfg(feature="ipc")] -use client::BlockChainClient; /// Message type for external and internal events #[derive(Clone, PartialEq, Eq, Debug)] @@ -227,7 +225,7 @@ impl IoHandler for ClientIoHandler { }, ClientIoMessage::UpdateSealing => self.client.update_sealing(), ClientIoMessage::SubmitSeal(ref hash, ref seal) => self.client.submit_seal(*hash, seal.clone()), - ClientIoMessage::BroadcastMessage(ref message) => self.client.broadcast_message(message.clone()), + ClientIoMessage::BroadcastMessage(ref message) => self.client.broadcast_consensus_message(message.clone()), ClientIoMessage::NewMessage(ref message) => if let Err(e) = self.client.engine().handle_message(UntrustedRlp::new(message)) { trace!(target: "poa", "Invalid message received: {}", e); }, From 2346f29731da1816eab4115c8e130c6ad4745aa3 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 18:35:54 +0100 Subject: [PATCH 205/295] Add dividers to AutoComplete --- js/src/ui/Form/AddressSelect/addressSelect.js | 32 +++++-- js/src/ui/Form/AutoComplete/autocomplete.js | 94 +++++++++++++++---- 2 files changed, 103 insertions(+), 23 deletions(-) diff --git a/js/src/ui/Form/AddressSelect/addressSelect.js b/js/src/ui/Form/AddressSelect/addressSelect.js index 2fbcc80bf..4bd93caa9 100644 --- a/js/src/ui/Form/AddressSelect/addressSelect.js +++ b/js/src/ui/Form/AddressSelect/addressSelect.js @@ -47,23 +47,41 @@ export default class AddressSelect extends Component { } state = { + autocompleteEntries: [], entries: {}, addresses: [], value: '' } entriesFromProps (props = this.props) { - const { accounts, contacts, contracts, wallets } = props; - const entries = Object.assign({}, accounts || {}, wallets || {}, contacts || {}, contracts || {}); - return entries; + const { accounts = {}, contacts = {}, contracts = {}, wallets = {} } = props; + + const autocompleteEntries = [].concat( + Object.values(wallets), + 'divider', + Object.values(accounts), + 'divider', + Object.values(contacts), + 'divider', + Object.values(contracts) + ); + + const entries = { + ...wallets, + ...accounts, + ...contacts, + ...contracts + }; + + return { autocompleteEntries, entries }; } componentWillMount () { const { value } = this.props; - const entries = this.entriesFromProps(); + const { entries, autocompleteEntries } = this.entriesFromProps(); const addresses = Object.keys(entries).sort(); - this.setState({ entries, addresses, value }); + this.setState({ autocompleteEntries, entries, addresses, value }); } componentWillReceiveProps (newProps) { @@ -74,7 +92,7 @@ export default class AddressSelect extends Component { render () { const { allowInput, disabled, error, hint, label } = this.props; - const { entries, value } = this.state; + const { autocompleteEntries, value } = this.state; const searchText = this.getSearchText(); const icon = this.renderIdentityIcon(value); @@ -92,7 +110,7 @@ export default class AddressSelect extends Component { onUpdateInput={ allowInput && this.onUpdateInput } value={ searchText } filter={ this.handleFilter } - entries={ entries } + entries={ autocompleteEntries } entry={ this.getEntry() || {} } renderItem={ this.renderItem } /> diff --git a/js/src/ui/Form/AutoComplete/autocomplete.js b/js/src/ui/Form/AutoComplete/autocomplete.js index f5ad43201..0e2acc98b 100644 --- a/js/src/ui/Form/AutoComplete/autocomplete.js +++ b/js/src/ui/Form/AutoComplete/autocomplete.js @@ -17,9 +17,10 @@ import React, { Component, PropTypes } from 'react'; import keycode from 'keycode'; import { MenuItem, AutoComplete as MUIAutoComplete } from 'material-ui'; +import Divider from 'material-ui/Divider'; import { PopoverAnimationVertical } from 'material-ui/Popover'; -import { isEqual } from 'lodash'; +import { isEqual, range } from 'lodash'; export default class AutoComplete extends Component { static propTypes = { @@ -38,14 +39,17 @@ export default class AutoComplete extends Component { PropTypes.array, PropTypes.object ]) - } + }; state = { lastChangedValue: undefined, entry: null, open: false, - dataSource: [] - } + dataSource: [], + dividerBreaks: [] + }; + + dividersVisibility = {}; componentWillMount () { const dataSource = this.getDataSource(); @@ -63,7 +67,7 @@ export default class AutoComplete extends Component { } render () { - const { disabled, error, hint, label, value, className, filter, onUpdateInput } = this.props; + const { disabled, error, hint, label, value, className, onUpdateInput } = this.props; const { open, dataSource } = this.state; return ( @@ -79,7 +83,7 @@ export default class AutoComplete extends Component { onFocus={ this.onFocus } onClose={ this.onClose } animation={ PopoverAnimationVertical } - filter={ filter } + filter={ this.handleFilter } popoverProps={ { open } } openOnFocus menuCloseDelay={ 0 } @@ -99,18 +103,76 @@ export default class AutoComplete extends Component { ? entries : Object.values(entries); - if (renderItem && typeof renderItem === 'function') { - return entriesArray.map(entry => renderItem(entry)); + let currentDivider = 0; + let firstSet = false; + + const dataSource = entriesArray.map((entry, index) => { + // Render divider + if (typeof entry === 'string' && entry.toLowerCase() === 'divider') { + // Don't add divider if nothing before + if (!firstSet) { + return undefined; + } + + const item = { + text: '', + divider: currentDivider, + isDivider: true, + value: ( + + ) + }; + + currentDivider++; + return item; + } + + let item; + + if (renderItem && typeof renderItem === 'function') { + item = renderItem(entry); + } else { + item = { + text: entry, + value: ( + + ) + }; + } + + if (!firstSet) { + item.first = true; + firstSet = true; + } + + item.divider = currentDivider; + + return item; + }).filter((item) => item !== undefined); + + return dataSource; + } + + handleFilter = (searchText, name, item) => { + if (item.isDivider) { + return this.dividersVisibility[item.divider]; } - return entriesArray.map(entry => ({ - text: entry, - value: ( - - ) - })); + if (item.first) { + this.dividersVisibility = {}; + } + + const { filter } = this.props; + const show = filter(searchText, name, item); + + // Show the related divider + if (show) { + this.dividersVisibility[item.divider] = true; + } + + return show; } onKeyDown = (event) => { From 69c0086ada5b55a0924662b301e0b1fefbb93f4b Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 19:14:29 +0100 Subject: [PATCH 206/295] Better Autocomplete Divider --- js/src/ui/Form/AutoComplete/autocomplete.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/js/src/ui/Form/AutoComplete/autocomplete.js b/js/src/ui/Form/AutoComplete/autocomplete.js index 0e2acc98b..f78ab41dc 100644 --- a/js/src/ui/Form/AutoComplete/autocomplete.js +++ b/js/src/ui/Form/AutoComplete/autocomplete.js @@ -16,11 +16,23 @@ import React, { Component, PropTypes } from 'react'; import keycode from 'keycode'; -import { MenuItem, AutoComplete as MUIAutoComplete } from 'material-ui'; -import Divider from 'material-ui/Divider'; +import { MenuItem, AutoComplete as MUIAutoComplete, Divider as MUIDivider } from 'material-ui'; import { PopoverAnimationVertical } from 'material-ui/Popover'; -import { isEqual, range } from 'lodash'; +import { isEqual } from 'lodash'; + +// Hack to prevent "Unknown prop `disableFocusRipple` on
    tag" error +class Divider extends Component { + static muiName = MUIDivider.muiName; + + render () { + return ( +
    + +
    + ); + } +} export default class AutoComplete extends Component { static propTypes = { From 0f6681d3e819aa1498d28be4c24313c369754f02 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 19:15:45 +0100 Subject: [PATCH 207/295] Linting issue --- js/src/ui/Form/AutoComplete/autocomplete.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/ui/Form/AutoComplete/autocomplete.js b/js/src/ui/Form/AutoComplete/autocomplete.js index f78ab41dc..d11ae7cc5 100644 --- a/js/src/ui/Form/AutoComplete/autocomplete.js +++ b/js/src/ui/Form/AutoComplete/autocomplete.js @@ -28,7 +28,7 @@ class Divider extends Component { render () { return (
    - +
    ); } From 76a93d4eff8a69d4774c5a576c4df588608919df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sat, 10 Dec 2016 20:01:04 +0100 Subject: [PATCH 208/295] eth_sign RPC now hashes given data --- rpc/src/v1/impls/signing.rs | 5 +++-- rpc/src/v1/impls/signing_unsafe.rs | 4 +++- rpc/src/v1/tests/mocked/eth.rs | 8 ++++---- rpc/src/v1/tests/mocked/signing.rs | 9 +++++---- rpc/src/v1/traits/eth_signing.rs | 6 +++--- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/rpc/src/v1/impls/signing.rs b/rpc/src/v1/impls/signing.rs index 262e04dfb..efb7ed782 100644 --- a/rpc/src/v1/impls/signing.rs +++ b/rpc/src/v1/impls/signing.rs @@ -18,7 +18,7 @@ use std::sync::{Arc, Weak}; use transient_hashmap::TransientHashMap; -use util::{U256, Mutex}; +use util::{U256, Mutex, Hashable}; use ethcore::account_provider::AccountProvider; use ethcore::miner::MinerService; @@ -180,7 +180,8 @@ impl EthSigning for SigningQueueClient where C: MiningBlockChainClient, M: MinerService, { - fn sign(&self, ready: Ready, address: RpcH160, hash: RpcH256) { + fn sign(&self, ready: Ready, address: RpcH160, data: RpcBytes) { + let hash = data.0.sha3().into(); let res = self.active().and_then(|_| self.dispatch(RpcConfirmationPayload::Signature((address, hash).into()))); self.handle_dispatch(res, |response| { match response { diff --git a/rpc/src/v1/impls/signing_unsafe.rs b/rpc/src/v1/impls/signing_unsafe.rs index 46ffe6ded..4796cc85d 100644 --- a/rpc/src/v1/impls/signing_unsafe.rs +++ b/rpc/src/v1/impls/signing_unsafe.rs @@ -17,6 +17,7 @@ //! Unsafe Signing RPC implementation. use std::sync::{Arc, Weak}; +use util::Hashable; use ethcore::account_provider::AccountProvider; use ethcore::miner::MinerService; @@ -83,7 +84,8 @@ impl EthSigning for SigningUnsafeClient where C: MiningBlockChainClient, M: MinerService, { - fn sign(&self, ready: Ready, address: RpcH160, hash: RpcH256) { + fn sign(&self, ready: Ready, address: RpcH160, data: RpcBytes) { + let hash = data.0.sha3().into(); let result = match self.handle(RpcConfirmationPayload::Signature((address, hash).into())) { Ok(RpcConfirmationResponse::Signature(signature)) => Ok(signature), Err(e) => Err(e), diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index 7a7a1f682..bc321be5c 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -18,11 +18,11 @@ use std::str::FromStr; use std::collections::HashMap; use std::sync::Arc; use std::time::{Instant, Duration}; -use rustc_serialize::hex::ToHex; +use rustc_serialize::hex::{FromHex, ToHex}; use time::get_time; use rlp; -use util::{Uint, U256, Address, H256, FixedHash, Mutex}; +use util::{Uint, U256, Address, H256, FixedHash, Mutex, Hashable}; use ethcore::account_provider::AccountProvider; use ethcore::client::{TestBlockChainClient, EachBlockWith, Executed, TransactionId}; use ethcore::log_entry::{LocalizedLogEntry, LogEntry}; @@ -294,8 +294,8 @@ fn rpc_eth_sign() { let account = tester.accounts_provider.new_account("abcd").unwrap(); tester.accounts_provider.unlock_account_permanently(account, "abcd".into()).unwrap(); - let message = H256::from("0x0cc175b9c0f1b6a831c399e26977266192eb5ffee6ae2fec3ad71c777531578f"); - let signed = tester.accounts_provider.sign(account, None, message).unwrap(); + let message = "0cc175b9c0f1b6a831c399e26977266192eb5ffee6ae2fec3ad71c777531578f".from_hex().unwrap(); + let signed = tester.accounts_provider.sign(account, None, message.sha3()).unwrap(); let req = r#"{ "jsonrpc": "2.0", diff --git a/rpc/src/v1/tests/mocked/signing.rs b/rpc/src/v1/tests/mocked/signing.rs index 31a700443..27a751701 100644 --- a/rpc/src/v1/tests/mocked/signing.rs +++ b/rpc/src/v1/tests/mocked/signing.rs @@ -26,7 +26,7 @@ use v1::types::ConfirmationResponse; use v1::tests::helpers::TestMinerService; use v1::tests::mocked::parity; -use util::{Address, FixedHash, Uint, U256, H256, ToPretty}; +use util::{Address, FixedHash, Uint, U256, ToPretty, Hashable}; use ethcore::account_provider::AccountProvider; use ethcore::client::TestBlockChainClient; use ethcore::transaction::{Transaction, Action}; @@ -186,11 +186,11 @@ fn should_check_status_of_request_when_its_resolved() { fn should_sign_if_account_is_unlocked() { // given let tester = eth_signing(); - let hash: H256 = 5.into(); + let data = vec![5u8]; let acc = tester.accounts.new_account("test").unwrap(); tester.accounts.unlock_account_permanently(acc, "test".into()).unwrap(); - let signature = tester.accounts.sign(acc, None, hash).unwrap(); + let signature = tester.accounts.sign(acc, None, data.sha3()).unwrap(); // when let request = r#"{ @@ -198,10 +198,11 @@ fn should_sign_if_account_is_unlocked() { "method": "eth_sign", "params": [ ""#.to_owned() + format!("0x{:?}", acc).as_ref() + r#"", - ""# + format!("0x{:?}", hash).as_ref() + r#"" + ""# + format!("0x{}", data.to_hex()).as_ref() + r#"" ], "id": 1 }"#; +println!("{:?}", request); let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{}", signature).as_ref() + r#"","id":1}"#; assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned())); assert_eq!(tester.signer.requests().len(), 0); diff --git a/rpc/src/v1/traits/eth_signing.rs b/rpc/src/v1/traits/eth_signing.rs index 09f8c5e03..1248b4768 100644 --- a/rpc/src/v1/traits/eth_signing.rs +++ b/rpc/src/v1/traits/eth_signing.rs @@ -17,14 +17,14 @@ //! Eth rpc interface. use v1::helpers::auto_args::{WrapAsync, Ready}; -use v1::types::{H160, H256, H520, TransactionRequest, RichRawTransaction}; +use v1::types::{Bytes, H160, H256, H520, TransactionRequest, RichRawTransaction}; build_rpc_trait! { /// Signing methods implementation relying on unlocked accounts. pub trait EthSigning { - /// Signs the data with given address signature. + /// Signs the hash of data with given address signature. #[rpc(async, name = "eth_sign")] - fn sign(&self, Ready, H160, H256); + fn sign(&self, Ready, H160, Bytes); /// Sends transaction; will block waiting for signer to return the /// transaction hash. From 9b5fd932905ce397de29a15ac788cd02ee7fc530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sat, 10 Dec 2016 20:07:12 +0100 Subject: [PATCH 209/295] removing println [ci:skip] --- rpc/src/v1/tests/mocked/signing.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rpc/src/v1/tests/mocked/signing.rs b/rpc/src/v1/tests/mocked/signing.rs index 27a751701..7d79ef59f 100644 --- a/rpc/src/v1/tests/mocked/signing.rs +++ b/rpc/src/v1/tests/mocked/signing.rs @@ -202,7 +202,6 @@ fn should_sign_if_account_is_unlocked() { ], "id": 1 }"#; -println!("{:?}", request); let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{}", signature).as_ref() + r#"","id":1}"#; assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned())); assert_eq!(tester.signer.requests().len(), 0); From 08a47ea2d48c2de543658a50d74dab39caa10b3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sat, 10 Dec 2016 20:18:42 +0100 Subject: [PATCH 210/295] Allow modifications of gas when confirming in signer (#3798) --- rpc/src/v1/impls/signer.rs | 10 ++++++---- rpc/src/v1/tests/mocked/signer.rs | 4 ++-- rpc/src/v1/types/confirmations.rs | 12 +++++++++++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/rpc/src/v1/impls/signer.rs b/rpc/src/v1/impls/signer.rs index 66f46ba01..f13a3d037 100644 --- a/rpc/src/v1/impls/signer.rs +++ b/rpc/src/v1/impls/signer.rs @@ -89,11 +89,13 @@ impl Signer for SignerClient where C: MiningBlockC signer.peek(&id).map(|confirmation| { let mut payload = confirmation.payload.clone(); // Modify payload - match (&mut payload, modification.gas_price) { - (&mut ConfirmationPayload::SendTransaction(ref mut request), Some(gas_price)) => { + if let ConfirmationPayload::SendTransaction(ref mut request) = payload { + if let Some(gas_price) = modification.gas_price { request.gas_price = gas_price.into(); - }, - _ => {}, + } + if let Some(gas) = modification.gas { + request.gas = gas.into(); + } } // Execute let result = dispatch::execute(&*client, &*miner, &*accounts, payload, Some(pass)); diff --git a/rpc/src/v1/tests/mocked/signer.rs b/rpc/src/v1/tests/mocked/signer.rs index ea89e5876..c87abb7eb 100644 --- a/rpc/src/v1/tests/mocked/signer.rs +++ b/rpc/src/v1/tests/mocked/signer.rs @@ -183,7 +183,7 @@ fn should_confirm_transaction_and_dispatch() { let t = Transaction { nonce: U256::zero(), gas_price: U256::from(0x1000), - gas: U256::from(10_000_000), + gas: U256::from(0x50505), action: Action::Call(recipient), value: U256::from(0x1), data: vec![] @@ -198,7 +198,7 @@ fn should_confirm_transaction_and_dispatch() { let request = r#"{ "jsonrpc":"2.0", "method":"signer_confirmRequest", - "params":["0x1", {"gasPrice":"0x1000"}, "test"], + "params":["0x1", {"gasPrice":"0x1000","gas":"0x50505"}, "test"], "id":1 }"#; let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() + format!("0x{:?}", t.hash()).as_ref() + r#"","id":1}"#; diff --git a/rpc/src/v1/types/confirmations.rs b/rpc/src/v1/types/confirmations.rs index d8cfa14d6..f69018422 100644 --- a/rpc/src/v1/types/confirmations.rs +++ b/rpc/src/v1/types/confirmations.rs @@ -142,6 +142,8 @@ pub struct TransactionModification { /// Modified gas price #[serde(rename="gasPrice")] pub gas_price: Option, + /// Modified gas + pub gas: Option, } /// Represents two possible return values. @@ -275,18 +277,26 @@ mod tests { let s1 = r#"{ "gasPrice":"0xba43b7400" }"#; - let s2 = r#"{}"#; + let s2 = r#"{"gas": "0x1233"}"#; + let s3 = r#"{}"#; // when let res1: TransactionModification = serde_json::from_str(s1).unwrap(); let res2: TransactionModification = serde_json::from_str(s2).unwrap(); + let res3: TransactionModification = serde_json::from_str(s3).unwrap(); // then assert_eq!(res1, TransactionModification { gas_price: Some(U256::from_str("0ba43b7400").unwrap()), + gas: None, }); assert_eq!(res2, TransactionModification { gas_price: None, + gas: Some(U256::from_str("1233").unwrap()), + }); + assert_eq!(res3, TransactionModification { + gas_price: None, + gas: None, }); } } From 70eab0da0331c7867c619aa12a6f6a86831aea3f Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 20:29:22 +0100 Subject: [PATCH 211/295] PR grumbles --- .../DeployContract/DetailsStep/detailsStep.js | 22 +++++++++---------- .../DetailsStep/detailsStep.js | 6 ++--- js/src/ui/Form/AddressSelect/addressSelect.js | 3 +-- js/src/views/Addresses/addresses.js | 2 +- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/js/src/modals/DeployContract/DetailsStep/detailsStep.js b/js/src/modals/DeployContract/DetailsStep/detailsStep.js index 9db223793..3de7a8a44 100644 --- a/js/src/modals/DeployContract/DetailsStep/detailsStep.js +++ b/js/src/modals/DeployContract/DetailsStep/detailsStep.js @@ -28,27 +28,25 @@ export default class DetailsStep extends Component { static propTypes = { accounts: PropTypes.object.isRequired, - - onFromAddressChange: PropTypes.func.isRequired, - onNameChange: PropTypes.func.isRequired, - onDescriptionChange: PropTypes.func.isRequired, onAbiChange: PropTypes.func.isRequired, onCodeChange: PropTypes.func.isRequired, - onParamsChange: PropTypes.func.isRequired, + onDescriptionChange: PropTypes.func.isRequired, + onFromAddressChange: PropTypes.func.isRequired, onInputsChange: PropTypes.func.isRequired, + onNameChange: PropTypes.func.isRequired, + onParamsChange: PropTypes.func.isRequired, + abi: PropTypes.string, + abiError: PropTypes.string, balances: PropTypes.object, + code: PropTypes.string, + codeError: PropTypes.string, + description: PropTypes.string, + descriptionError: PropTypes.string, fromAddress: PropTypes.string, fromAddressError: PropTypes.string, name: PropTypes.string, nameError: PropTypes.string, - description: PropTypes.string, - descriptionError: PropTypes.string, - abi: PropTypes.string, - abiError: PropTypes.string, - code: PropTypes.string, - codeError: PropTypes.string, - readOnly: PropTypes.bool }; diff --git a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js index 7bbe7be84..fde7fa1b2 100644 --- a/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js +++ b/js/src/modals/ExecuteContract/DetailsStep/detailsStep.js @@ -32,20 +32,20 @@ export default class DetailsStep extends Component { static propTypes = { accounts: PropTypes.object.isRequired, contract: PropTypes.object.isRequired, - amount: PropTypes.string, - amountError: PropTypes.string, onAmountChange: PropTypes.func.isRequired, onFromAddressChange: PropTypes.func.isRequired, onValueChange: PropTypes.func.isRequired, values: PropTypes.array.isRequired, valuesError: PropTypes.array.isRequired, + amount: PropTypes.string, + amountError: PropTypes.string, balances: PropTypes.object, fromAddress: PropTypes.string, fromAddressError: PropTypes.string, - gasEdit: PropTypes.bool, func: PropTypes.object, funcError: PropTypes.string, + gasEdit: PropTypes.bool, onFuncChange: PropTypes.func, onGasEditClick: PropTypes.func, warning: PropTypes.string diff --git a/js/src/ui/Form/AddressSelect/addressSelect.js b/js/src/ui/Form/AddressSelect/addressSelect.js index 4bd93caa9..0cd92c5c8 100644 --- a/js/src/ui/Form/AddressSelect/addressSelect.js +++ b/js/src/ui/Form/AddressSelect/addressSelect.js @@ -158,7 +158,7 @@ export default class AddressSelect extends Component { return null; } - const ethToken = balance.tokens.find((t) => t.token.tag.toLowerCase() === 'eth'); + const ethToken = balance.tokens.find((tok) => tok.token && tok.token.tag && tok.token.tag.toLowerCase() === 'eth'); if (!ethToken) { return null; @@ -187,7 +187,6 @@ export default class AddressSelect extends Component { - { balance }
); diff --git a/js/src/views/Addresses/addresses.js b/js/src/views/Addresses/addresses.js index fd26d94e5..609f029c7 100644 --- a/js/src/views/Addresses/addresses.js +++ b/js/src/views/Addresses/addresses.js @@ -89,7 +89,7 @@ class Addresses extends Component { if (hasContacts && Object.keys(balances).length === 0) { return ( - + ); } From 19ca9ad460cbf1fa3fced7197420935473580b41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sat, 10 Dec 2016 21:22:19 +0100 Subject: [PATCH 212/295] Prevent broadcasting transactions to peer that send them. --- ethcore/src/client/chain_notify.rs | 7 ++-- ethcore/src/client/client.rs | 9 ++--- ethcore/src/client/test_client.rs | 2 +- ethcore/src/client/traits.rs | 4 +- ethcore/src/service.rs | 6 +-- .../dapps/localtx/Transaction/transaction.js | 25 +----------- rpc/src/v1/tests/helpers/sync_provider.rs | 6 --- rpc/src/v1/tests/mocked/parity.rs | 2 +- rpc/src/v1/types/sync.rs | 12 +----- sync/src/api.rs | 8 ++-- sync/src/chain.rs | 11 +++-- sync/src/transactions_stats.rs | 40 ------------------- 12 files changed, 25 insertions(+), 107 deletions(-) diff --git a/ethcore/src/client/chain_notify.rs b/ethcore/src/client/chain_notify.rs index 50ff20e38..ddab542fb 100644 --- a/ethcore/src/client/chain_notify.rs +++ b/ethcore/src/client/chain_notify.rs @@ -41,11 +41,10 @@ pub trait ChainNotify : Send + Sync { // does nothing by default } - /// fires when new transactions are imported - fn transactions_imported(&self, + /// fires when new transactions are received from a peer + fn transactions_received(&self, _hashes: Vec, - _peer_id: Option, - _block_num: u64, + _peer_id: usize, ) { // does nothing by default } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 1387dad9a..c258ed1eb 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -559,15 +559,14 @@ impl Client { } /// Import transactions from the IO queue - pub fn import_queued_transactions(&self, transactions: &[Bytes], peer_id: Option) -> usize { + pub fn import_queued_transactions(&self, transactions: &[Bytes], peer_id: usize) -> 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: Vec = transactions.iter().filter_map(|bytes| UntrustedRlp::new(bytes).as_val().ok()).collect(); let hashes: Vec<_> = txs.iter().map(|tx| tx.hash()).collect(); - let block_number = self.chain_info().best_block_number; self.notify(|notify| { - notify.transactions_imported(hashes.clone(), peer_id.clone(), block_number); + notify.transactions_received(hashes.clone(), peer_id); }); let results = self.miner.import_external_transactions(self, txs); results.len() @@ -1269,14 +1268,14 @@ impl BlockChainClient for Client { (*self.build_last_hashes(self.chain.read().best_block_hash())).clone() } - fn queue_transactions(&self, transactions: Vec, node_id: Option) { + fn queue_transactions(&self, transactions: Vec, peer_id: usize) { 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.lock().send(ClientIoMessage::NewTransactions(transactions, node_id)) { + match self.io_channel.lock().send(ClientIoMessage::NewTransactions(transactions, peer_id)) { Ok(_) => { self.queue_transactions.fetch_add(len, AtomicOrdering::SeqCst); } diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index a2af13794..7f27c9151 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -657,7 +657,7 @@ impl BlockChainClient for TestBlockChainClient { unimplemented!(); } - fn queue_transactions(&self, transactions: Vec, _peer_id: Option) { + fn queue_transactions(&self, transactions: Vec, _peer_id: usize) { // import right here let txs = transactions.into_iter().filter_map(|bytes| UntrustedRlp::new(&bytes).as_val().ok()).collect(); self.miner.import_external_transactions(self, txs); diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index ccf07ea3f..fed864607 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -15,7 +15,7 @@ // along with Parity. If not, see . use std::collections::BTreeMap; -use util::{U256, Address, H256, H512, H2048, Bytes, Itertools}; +use util::{U256, Address, H256, H2048, Bytes, Itertools}; use util::stats::Histogram; use blockchain::TreeRoute; use verification::queue::QueueInfo as BlockQueueInfo; @@ -200,7 +200,7 @@ pub trait BlockChainClient : Sync + Send { fn last_hashes(&self) -> LastHashes; /// Queue transactions for importing. - fn queue_transactions(&self, transactions: Vec, peer_id: Option); + fn queue_transactions(&self, transactions: Vec, peer_id: usize); /// list all transactions fn pending_transactions(&self) -> Vec; diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index b595843a8..9b96911e4 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -39,7 +39,7 @@ pub enum ClientIoMessage { /// A block is ready BlockVerified, /// New transaction RLPs are ready to be imported - NewTransactions(Vec, Option), + NewTransactions(Vec, usize), /// Begin snapshot restoration BeginRestoration(ManifestData), /// Feed a state chunk to the snapshot service @@ -196,8 +196,8 @@ impl IoHandler for ClientIoHandler { match *net_message { ClientIoMessage::BlockVerified => { self.client.import_verified_blocks(); } - ClientIoMessage::NewTransactions(ref transactions, ref peer_id) => { - self.client.import_queued_transactions(transactions, peer_id.clone()); + ClientIoMessage::NewTransactions(ref transactions, peer_id) => { + self.client.import_queued_transactions(transactions, peer_id); } ClientIoMessage::BeginRestoration(ref manifest) => { if let Err(e) = self.snapshot.init_restore(manifest.clone(), true) { diff --git a/js/src/dapps/localtx/Transaction/transaction.js b/js/src/dapps/localtx/Transaction/transaction.js index c9ca10ba5..d1c98f360 100644 --- a/js/src/dapps/localtx/Transaction/transaction.js +++ b/js/src/dapps/localtx/Transaction/transaction.js @@ -86,17 +86,6 @@ class BaseTransaction extends Component { ); } - - renderReceived (stats) { - const noOfPeers = Object.keys(stats.receivedFrom).length; - const noOfPropagations = Object.values(stats.receivedFrom).reduce((sum, val) => sum + val, 0); - - return ( - - { noOfPropagations } ({ noOfPeers } peers) - - ); - } } export class Transaction extends BaseTransaction { @@ -113,8 +102,7 @@ export class Transaction extends BaseTransaction { isLocal: false, stats: { firstSeen: 0, - propagatedTo: {}, - receivedFrom: {} + propagatedTo: {} } }; @@ -140,9 +128,6 @@ export class Transaction extends BaseTransaction { # Propagated - - # Received - ); @@ -179,9 +164,6 @@ export class Transaction extends BaseTransaction { { this.renderPropagation(stats) } - - { this.renderReceived(stats) } - ); } @@ -210,8 +192,7 @@ export class LocalTransaction extends BaseTransaction { static defaultProps = { stats: { - propagatedTo: {}, - receivedFrom: {} + propagatedTo: {} } }; @@ -335,8 +316,6 @@ export class LocalTransaction extends BaseTransaction { { this.renderStatus() }
{ status === 'pending' ? this.renderPropagation(stats) : null } -
- { status === 'pending' ? this.renderReceived(stats) : null } ); diff --git a/rpc/src/v1/tests/helpers/sync_provider.rs b/rpc/src/v1/tests/helpers/sync_provider.rs index aa7e8d849..2517abd46 100644 --- a/rpc/src/v1/tests/helpers/sync_provider.rs +++ b/rpc/src/v1/tests/helpers/sync_provider.rs @@ -106,18 +106,12 @@ impl SyncProvider for TestSyncProvider { propagated_to: map![ 128.into() => 16 ], - received_from: map![ - 1.into() => 10 - ], }, 5.into() => TransactionStats { first_seen: 16, propagated_to: map![ 16.into() => 1 ], - received_from: map![ - 256.into() => 2 - ], } ] } diff --git a/rpc/src/v1/tests/mocked/parity.rs b/rpc/src/v1/tests/mocked/parity.rs index 45ee4aa75..9b4daaccd 100644 --- a/rpc/src/v1/tests/mocked/parity.rs +++ b/rpc/src/v1/tests/mocked/parity.rs @@ -363,7 +363,7 @@ fn rpc_parity_transactions_stats() { let io = deps.default_client(); let request = r#"{"jsonrpc": "2.0", "method": "parity_pendingTransactionsStats", "params":[], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","result":{"0x0000000000000000000000000000000000000000000000000000000000000001":{"firstSeen":10,"propagatedTo":{"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080":16},"receivedFrom":{"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001":10}},"0x0000000000000000000000000000000000000000000000000000000000000005":{"firstSeen":16,"propagatedTo":{"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010":1},"receivedFrom":{"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100":2}}},"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"0x0000000000000000000000000000000000000000000000000000000000000001":{"firstSeen":10,"propagatedTo":{"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080":16}},"0x0000000000000000000000000000000000000000000000000000000000000005":{"firstSeen":16,"propagatedTo":{"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010":1}}},"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } diff --git a/rpc/src/v1/types/sync.rs b/rpc/src/v1/types/sync.rs index 65d989156..8d3726e7a 100644 --- a/rpc/src/v1/types/sync.rs +++ b/rpc/src/v1/types/sync.rs @@ -127,9 +127,6 @@ pub struct TransactionStats { /// Peers this transaction was propagated to with count. #[serde(rename="propagatedTo")] pub propagated_to: BTreeMap, - /// Peers that propagated this transaction back. - #[serde(rename="receivedFrom")] - pub received_from: BTreeMap, } impl From for PeerInfo { @@ -161,10 +158,6 @@ impl From for TransactionStats { .into_iter() .map(|(id, count)| (id.into(), count)) .collect(), - received_from: s.received_from - .into_iter() - .map(|(id, count)| (id.into(), count)) - .collect(), } } } @@ -216,12 +209,9 @@ mod tests { propagated_to: map![ 10.into() => 50 ], - received_from: map![ - 1.into() => 1000 - ], }; let serialized = serde_json::to_string(&stats).unwrap(); - assert_eq!(serialized, r#"{"firstSeen":100,"propagatedTo":{"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a":50},"receivedFrom":{"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001":1000}}"#) + assert_eq!(serialized, r#"{"firstSeen":100,"propagatedTo":{"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a":50}}"#) } } diff --git a/sync/src/api.rs b/sync/src/api.rs index 0f3695fe9..c675ee6d3 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -99,8 +99,6 @@ pub struct TransactionStats { pub first_seen: u64, /// Peers it was propagated to. pub propagated_to: BTreeMap, - /// Peers that propagated the transaction back. - pub received_from: BTreeMap, } /// Peer connection information @@ -338,9 +336,9 @@ impl ChainNotify for EthSync { self.network.stop().unwrap_or_else(|e| warn!("Error stopping network: {:?}", e)); } - fn transactions_imported(&self, hashes: Vec, peer_id: Option, block_number: u64) { + fn transactions_received(&self, hashes: Vec, peer_id: PeerId) { let mut sync = self.sync_handler.sync.write(); - sync.transactions_imported(hashes, peer_id, block_number); + sync.transactions_received(hashes, peer_id); } } @@ -351,7 +349,7 @@ struct TxRelay(Arc); impl LightHandler for TxRelay { fn on_transactions(&self, ctx: &EventContext, relay: &[::ethcore::transaction::SignedTransaction]) { trace!(target: "les", "Relaying {} transactions from peer {}", relay.len(), ctx.peer()); - self.0.queue_transactions(relay.iter().map(|tx| ::rlp::encode(tx).to_vec()).collect(), ctx.persistent_peer_id()) + self.0.queue_transactions(relay.iter().map(|tx| ::rlp::encode(tx).to_vec()).collect(), ctx.peer()) } } diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 243c6b431..ecd95c68a 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -432,10 +432,10 @@ impl ChainSync { self.transactions_stats.stats() } - /// Updates statistics for imported transactions. - pub fn transactions_imported(&mut self, hashes: Vec, peer_id: Option, block_number: u64) { - for hash in hashes { - self.transactions_stats.received(hash, peer_id, block_number); + /// Updates transactions were received by a peer + pub fn transactions_received(&mut self, hashes: Vec, peer_id: PeerId) { + if let Some(mut peer_info) = self.peers.get_mut(&peer_id) { + peer_info.last_sent_transactions.extend(&hashes); } } @@ -1416,8 +1416,7 @@ impl ChainSync { let tx = rlp.as_raw().to_vec(); transactions.push(tx); } - let id = io.peer_session_info(peer_id).and_then(|info| info.id); - io.chain().queue_transactions(transactions, id); + io.chain().queue_transactions(transactions, peer_id); Ok(()) } diff --git a/sync/src/transactions_stats.rs b/sync/src/transactions_stats.rs index a91a860e5..fa8eb6e82 100644 --- a/sync/src/transactions_stats.rs +++ b/sync/src/transactions_stats.rs @@ -26,7 +26,6 @@ type BlockNumber = u64; pub struct Stats { first_seen: BlockNumber, propagated_to: HashMap, - received_from: HashMap, } impl Stats { @@ -34,7 +33,6 @@ impl Stats { Stats { first_seen: number, propagated_to: Default::default(), - received_from: Default::default(), } } } @@ -47,10 +45,6 @@ impl<'a> From<&'a Stats> for TransactionStats { .iter() .map(|(hash, size)| (*hash, *size)) .collect(), - received_from: other.received_from - .iter() - .map(|(hash, size)| (*hash, *size)) - .collect(), } } } @@ -69,14 +63,6 @@ impl TransactionsStats { *count = count.saturating_add(1); } - /// Increase number of back-propagations from given `enodeid`. - pub fn received(&mut self, hash: H256, enode_id: Option, current_block_num: BlockNumber) { - let enode_id = enode_id.unwrap_or_default(); - let mut stats = self.pending_transactions.entry(hash).or_insert_with(|| Stats::new(current_block_num)); - let mut count = stats.received_from.entry(enode_id).or_insert(0); - *count = count.saturating_add(1); - } - /// Returns propagation stats for given hash or `None` if hash is not known. #[cfg(test)] pub fn get(&self, hash: &H256) -> Option<&Stats> { @@ -127,32 +113,6 @@ mod tests { enodeid1 => 2, enodeid2 => 1 ], - received_from: Default::default(), - })); - } - - #[test] - fn should_keep_track_of_back_propagations() { - // given - let mut stats = TransactionsStats::default(); - let hash = 5.into(); - let enodeid1 = 2.into(); - let enodeid2 = 5.into(); - - // when - stats.received(hash, Some(enodeid1), 5); - stats.received(hash, Some(enodeid1), 10); - stats.received(hash, Some(enodeid2), 15); - - // then - let stats = stats.get(&hash); - assert_eq!(stats, Some(&Stats { - first_seen: 5, - propagated_to: Default::default(), - received_from: hash_map![ - enodeid1 => 2, - enodeid2 => 1 - ] })); } From b5020d3c8d387ed3a51a1779688eb904e1b165d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sat, 10 Dec 2016 21:25:08 +0100 Subject: [PATCH 213/295] Fixing Light context API --- ethcore/light/src/net/context.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ethcore/light/src/net/context.rs b/ethcore/light/src/net/context.rs index af1f4c677..522722bcd 100644 --- a/ethcore/light/src/net/context.rs +++ b/ethcore/light/src/net/context.rs @@ -43,7 +43,7 @@ pub trait IoContext { fn protocol_version(&self, peer: PeerId) -> Option; /// Persistent peer id - fn persistent_peer_id(&self, peer: PeerId) -> Option; + fn persistent_peer_id(&self, peer: &PeerId) -> Option; } impl<'a> IoContext for NetworkContext<'a> { @@ -71,8 +71,8 @@ impl<'a> IoContext for NetworkContext<'a> { self.protocol_version(self.subprotocol_name(), peer) } - fn persistent_peer_id(&self, peer: PeerId) -> Option { - self.session_info(peer).and_then(|info| info.id) + fn persistent_peer_id(&self, peer: &PeerId) -> Option { + self.session_info(*peer).and_then(|info| info.id) } } @@ -83,7 +83,7 @@ pub trait EventContext { fn peer(&self) -> PeerId; /// Returns the relevant's peer persistent Id (aka NodeId). - fn persistent_peer_id(&self) -> Option; + fn persistent_peer_id(&self, id: &PeerId) -> Option; /// Make a request from a peer. fn request_from(&self, peer: PeerId, request: Request) -> Result; @@ -116,8 +116,8 @@ impl<'a> EventContext for Ctx<'a> { self.peer } - fn persistent_peer_id(&self) -> Option { - self.io.persistent_peer_id(self.peer) + fn persistent_peer_id(&self, id: &PeerId) -> Option { + self.io.persistent_peer_id(id) } fn request_from(&self, peer: PeerId, request: Request) -> Result { self.proto.request_from(self.io, &peer, request) From 7401358543f8e4b18f55cebc4cc1d73ae5337ab6 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Sat, 10 Dec 2016 22:15:56 +0100 Subject: [PATCH 214/295] PR grumbles --- .../DeployContract/DetailsStep/detailsStep.js | 23 +++++++++++-------- js/src/views/Contracts/contracts.js | 2 +- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/js/src/modals/DeployContract/DetailsStep/detailsStep.js b/js/src/modals/DeployContract/DetailsStep/detailsStep.js index 3de7a8a44..51c5d3cfb 100644 --- a/js/src/modals/DeployContract/DetailsStep/detailsStep.js +++ b/js/src/modals/DeployContract/DetailsStep/detailsStep.js @@ -94,25 +94,28 @@ export default class DetailsStep extends Component { + error={ fromAddressError } + onChange={ this.onFromAddressChange } + value={ fromAddress } + /> + /> + /> { this.renderContractSelect() } @@ -120,17 +123,19 @@ export default class DetailsStep extends Component { label='abi / solc combined-output' hint='the abi of the contract to deploy or solc combined-output' error={ abiError } - value={ solcOutput } onChange={ this.onSolcChange } onSubmit={ this.onSolcSubmit } - readOnly={ readOnly } /> + readOnly={ readOnly } + value={ solcOutput } + /> + readOnly={ readOnly || solc } + value={ code } + /> ); diff --git a/js/src/views/Contracts/contracts.js b/js/src/views/Contracts/contracts.js index 524954f80..7b74654da 100644 --- a/js/src/views/Contracts/contracts.js +++ b/js/src/views/Contracts/contracts.js @@ -147,7 +147,7 @@ class Contracts extends Component { >