From 8da38fa98b8c88fee4242e988f405b7657d49d77 Mon Sep 17 00:00:00 2001 From: keorn Date: Fri, 19 Aug 2016 17:18:30 +0200 Subject: [PATCH 001/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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/280] 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 2b34d76b8ca02347e0bb8f2851c42c7805853d70 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Thu, 8 Dec 2016 12:54:13 +0100 Subject: [PATCH 153/280] pull out fetchMeta --- js/src/contracts/badgereg.js | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/js/src/contracts/badgereg.js b/js/src/contracts/badgereg.js index 45760b277..02d08e516 100644 --- a/js/src/contracts/badgereg.js +++ b/js/src/contracts/badgereg.js @@ -38,16 +38,9 @@ export default class BadgeReg { .then((badgeReg) => { return badgeReg.instance.fromName.call({}, [name]) .then(([ id, address ]) => { - return Promise.all([ - badgeReg.instance.meta.call({}, [id, 'TITLE']), - badgeReg.instance.meta.call({}, [id, 'IMG']) - ]) - .then(([ title, img ]) => { - title = bytesToHex(title); - title = title === ZERO ? null : hex2Ascii(title); - if (bytesToHex(img) === ZERO) img = null; - - const data = { address, name, title, icon: img }; + return this.fetchMeta(id) + .then(({ title, icon }) => { + const data = { address, name, title, icon }; this.certifiers[name] = data; return data; }); @@ -55,6 +48,22 @@ export default class BadgeReg { }); } + fetchMeta (id) { + return this._registry.getContract('badgereg') + .then((badgeReg) => { + return Promise.all([ + badgeReg.instance.meta.call({}, [id, 'TITLE']), + badgeReg.instance.meta.call({}, [id, 'IMG']) + ]); + }) + .then(([ title, icon ]) => { + title = bytesToHex(title); + title = title === ZERO ? null : hex2Ascii(title); + if (bytesToHex(icon) === ZERO) icon = null; + return { title, icon }; + }); + } + checkIfCertified (certifier, address) { if (!this.contracts[certifier]) { this.contracts[certifier] = this._api.newContract(ABI, certifier); From b32b636697dac347b2d9de259d6c97ccd6acbd46 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Thu, 8 Dec 2016 13:07:27 +0100 Subject: [PATCH 154/280] fetch certifiers by id --- js/src/contracts/badgereg.js | 44 ++++++++++++------- .../providers/certifications/middleware.js | 29 +++++++++--- 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/js/src/contracts/badgereg.js b/js/src/contracts/badgereg.js index 02d08e516..45f7df315 100644 --- a/js/src/contracts/badgereg.js +++ b/js/src/contracts/badgereg.js @@ -18,7 +18,8 @@ import { bytesToHex, hex2Ascii } from '~/api/util/format'; import ABI from './abi/certifier.json'; -const ZERO = '0x0000000000000000000000000000000000000000000000000000000000000000'; +const ZERO20 = '0x0000000000000000000000000000000000000000'; +const ZERO32 = '0x0000000000000000000000000000000000000000000000000000000000000000'; export default class BadgeReg { constructor (api, registry) { @@ -26,25 +27,36 @@ export default class BadgeReg { this._registry = registry; registry.getContract('badgereg'); - this.certifiers = {}; // by name + this.certifiers = []; // by id this.contracts = {}; // by name } - fetchCertifier (name) { - if (this.certifiers[name]) { - return Promise.resolve(this.certifiers[name]); + nrOfCertifiers () { + return this._registry.getContract('badgereg') + .then((badgeReg) => { + return badgeReg.instance.badgeCount.call({}, []) + .then((count) => count.valueOf()); + }); + } + + fetchCertifier (id) { + if (this.certifiers[id]) { + return Promise.resolve(this.certifiers[id]); } return this._registry.getContract('badgereg') .then((badgeReg) => { - return badgeReg.instance.fromName.call({}, [name]) - .then(([ id, address ]) => { - return this.fetchMeta(id) - .then(({ title, icon }) => { - const data = { address, name, title, icon }; - this.certifiers[name] = data; - return data; - }); - }); + return badgeReg.instance.badge.call({}, [ id ]); + }) + .then(([ address, name ]) => { + if (address === ZERO20) throw new Error(`Certifier ${id} does not exist.`); + name = bytesToHex(name); + name = name === ZERO32 ? null : hex2Ascii(name); + return this.fetchMeta(id) + .then(({ title, icon }) => { + const data = { address, name, title, icon }; + this.certifiers[id] = data; + return data; + }); }); } @@ -58,8 +70,8 @@ export default class BadgeReg { }) .then(([ title, icon ]) => { title = bytesToHex(title); - title = title === ZERO ? null : hex2Ascii(title); - if (bytesToHex(icon) === ZERO) icon = null; + title = title === ZERO32 ? null : hex2Ascii(title); + if (bytesToHex(icon) === ZERO32) icon = null; return { title, icon }; }); } diff --git a/js/src/redux/providers/certifications/middleware.js b/js/src/redux/providers/certifications/middleware.js index a5406051f..6c443cea5 100644 --- a/js/src/redux/providers/certifications/middleware.js +++ b/js/src/redux/providers/certifications/middleware.js @@ -17,20 +17,39 @@ import Contracts from '~/contracts'; import { addCertification } from './actions'; -const knownCertifiers = [ 'smsverification' ]; +const knownCertifiers = [ + 0 // sms verification +]; export default class CertificationsMiddleware { toMiddleware () { return (store) => (next) => (action) => { - if (action.type !== 'fetchCertifications') { + if (action.type === 'fetchCertifiers') { + badgeReg.nrOfCertifiers().then((count) => { + new Array(+count).fill(null).forEach((_, id) => { + badgeReg.fetchCertifier(id) + .then((cert) => { + const { address, name, title, icon } = cert; + store.dispatch(addCertifier(address, name, title, icon)); + }) + .catch((err) => { + if (err) { + console.error(`Failed to fetch certifier ${id}:`, err); + } + }); + }); + }); + } + + else if (action.type !== 'fetchCertifications') { return next(action); } const { address } = action; const badgeReg = Contracts.get().badgeReg; - knownCertifiers.forEach((name) => { - badgeReg.fetchCertifier(name) + knownCertifiers.forEach((id) => { + badgeReg.fetchCertifier(id) .then((cert) => { return badgeReg.checkIfCertified(cert.address, address) .then((isCertified) => { @@ -42,7 +61,7 @@ export default class CertificationsMiddleware { }) .catch((err) => { if (err) { - console.error(`Failed to check if ${address} certified by ${name}:`, err); + console.error(`Failed to check if ${address} certified by ${id}:`, err); } }); }); From 409c4adfbfd2a396f46534d5098a7af738e85607 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Thu, 8 Dec 2016 22:05:16 +0100 Subject: [PATCH 155/280] fetch certifiers from BadgeReg --- .../redux/providers/certifications/actions.js | 4 + .../providers/certifications/middleware.js | 84 +++++++++++-------- 2 files changed, 54 insertions(+), 34 deletions(-) diff --git a/js/src/redux/providers/certifications/actions.js b/js/src/redux/providers/certifications/actions.js index c84f7db55..a8cc43f03 100644 --- a/js/src/redux/providers/certifications/actions.js +++ b/js/src/redux/providers/certifications/actions.js @@ -14,6 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +export const fetchCertifiers = () => ({ + type: 'fetchCertifiers' +}); + export const fetchCertifications = (address) => ({ type: 'fetchCertifications', address }); diff --git a/js/src/redux/providers/certifications/middleware.js b/js/src/redux/providers/certifications/middleware.js index 6c443cea5..715a5cb59 100644 --- a/js/src/redux/providers/certifications/middleware.js +++ b/js/src/redux/providers/certifications/middleware.js @@ -14,57 +14,73 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import { uniq } from 'lodash'; + +import ABI from '~/contracts/abi/certifier.json'; +import Contract from '~/api/contract'; import Contracts from '~/contracts'; import { addCertification } from './actions'; -const knownCertifiers = [ - 0 // sms verification -]; - export default class CertificationsMiddleware { toMiddleware () { + const api = Contracts.get()._api; + const badgeReg = Contracts.get().badgeReg; + const contract = new Contract(api, ABI); + const Confirmed = contract.events.find((e) => e.name === 'Confirmed'); + + let certifiers = []; + let accounts = []; // these are addresses + + const fetchConfirmedEvents = (dispatch) => { + if (certifiers.length === 0 || accounts.length === 0) return; + api.eth.getLogs({ + fromBlock: 0, + toBlock: 'latest', + address: certifiers.map((c) => c.address), + topics: [ Confirmed.signature, accounts ] + }) + .then((logs) => contract.parseEventLogs(logs)) + .then((logs) => { + logs.forEach((log) => { + const certifier = certifiers.find((c) => c.address === log.address); + if (!certifier) throw new Error(`Could not find certifier at ${log.address}.`); + const { name, title, icon } = certifier; + dispatch(addCertification(log.params.who.value, name, title, icon)); + }); + }) + .catch((err) => { + console.error('Failed to fetch Confirmed events:', err); + }); + }; + return (store) => (next) => (action) => { if (action.type === 'fetchCertifiers') { badgeReg.nrOfCertifiers().then((count) => { new Array(+count).fill(null).forEach((_, id) => { badgeReg.fetchCertifier(id) .then((cert) => { - const { address, name, title, icon } = cert; - store.dispatch(addCertifier(address, name, title, icon)); + if (!certifiers.some((c) => c.address === cert.address)) { + certifiers = certifiers.concat(cert); + fetchConfirmedEvents(store.dispatch); + } }) .catch((err) => { - if (err) { - console.error(`Failed to fetch certifier ${id}:`, err); - } + console.warn(`Could not fetch certifier ${id}:`, err); }); }); }); - } + } else if (action.type === 'fetchCertifications') { + const { address } = action; - else if (action.type !== 'fetchCertifications') { - return next(action); - } - - const { address } = action; - const badgeReg = Contracts.get().badgeReg; - - knownCertifiers.forEach((id) => { - badgeReg.fetchCertifier(id) - .then((cert) => { - return badgeReg.checkIfCertified(cert.address, address) - .then((isCertified) => { - if (isCertified) { - const { name, title, icon } = cert; - store.dispatch(addCertification(address, name, title, icon)); - } - }); - }) - .catch((err) => { - if (err) { - console.error(`Failed to check if ${address} certified by ${id}:`, err); - } - }); - }); + if (!accounts.includes(address)) { + accounts = accounts.concat(address); + fetchConfirmedEvents(store.dispatch); + } + } else if (action.type === 'setVisibleAccounts') { + const { addresses } = action; + accounts = uniq(accounts.concat(addresses)); + fetchConfirmedEvents(store.dispatch); + } else return next(action); }; } } From e1c5796a5c43643aab7075ffe2be984a2f4a6d3a Mon Sep 17 00:00:00 2001 From: Jannis R Date: Thu, 8 Dec 2016 22:58:24 +0100 Subject: [PATCH 156/280] fetch certifications in account view --- js/src/ui/Certifications/certifications.js | 16 ++-------------- js/src/views/Account/account.js | 11 +++++++++-- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/js/src/ui/Certifications/certifications.js b/js/src/ui/Certifications/certifications.js index 871b14e9c..b034deda4 100644 --- a/js/src/ui/Certifications/certifications.js +++ b/js/src/ui/Certifications/certifications.js @@ -19,7 +19,6 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { hashToImageUrl } from '~/redux/providers/imagesReducer'; -import { fetchCertifications } from '~/redux/providers/certifications/actions'; import defaultIcon from '../../../assets/images/certifications/unknown.svg'; @@ -29,14 +28,7 @@ class Certifications extends Component { static propTypes = { account: PropTypes.string.isRequired, certifications: PropTypes.array.isRequired, - dappsUrl: PropTypes.string.isRequired, - - fetchCertifications: PropTypes.func.isRequired - } - - componentWillMount () { - const { account, fetchCertifications } = this.props; - fetchCertifications(account); + dappsUrl: PropTypes.string.isRequired } render () { @@ -77,11 +69,7 @@ function mapStateToProps (_, initProps) { }; } -function mapDispatchToProps (dispatch) { - return bindActionCreators({ fetchCertifications }, dispatch); -} - export default connect( mapStateToProps, - mapDispatchToProps + null )(Certifications); diff --git a/js/src/views/Account/account.js b/js/src/views/Account/account.js index 98b0a5e97..ec9551d6b 100644 --- a/js/src/views/Account/account.js +++ b/js/src/views/Account/account.js @@ -31,6 +31,7 @@ import shapeshiftBtn from '../../../assets/images/shapeshift-btn.png'; import Header from './Header'; import Transactions from './Transactions'; import { setVisibleAccounts } from '~/redux/providers/personalActions'; +import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions'; import VerificationStore from '~/modals/SMSVerification/store'; @@ -43,6 +44,8 @@ class Account extends Component { static propTypes = { setVisibleAccounts: PropTypes.func.isRequired, + fetchCertifiers: PropTypes.func.isRequired, + fetchCertifications: PropTypes.func.isRequired, images: PropTypes.object.isRequired, params: PropTypes.object, @@ -62,6 +65,7 @@ class Account extends Component { } componentDidMount () { + this.props.fetchCertifiers(); this.setVisibleAccounts(); } @@ -88,9 +92,10 @@ class Account extends Component { } setVisibleAccounts (props = this.props) { - const { params, setVisibleAccounts } = props; + const { params, setVisibleAccounts, fetchCertifications } = props; const addresses = [ params.address ]; setVisibleAccounts(addresses); + fetchCertifications(params.address); } render () { @@ -344,7 +349,9 @@ function mapStateToProps (state) { function mapDispatchToProps (dispatch) { return bindActionCreators({ - setVisibleAccounts + setVisibleAccounts, + fetchCertifiers, + fetchCertifications }, dispatch); } From 5862f2a9ebd2d72f2d639802de1c2d7b96f5b3b4 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Fri, 9 Dec 2016 00:30:05 +0100 Subject: [PATCH 157/280] Certifications: read dappsUrl from state --- js/src/ui/Certifications/certifications.js | 6 +++--- js/src/views/Account/Header/header.js | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/js/src/ui/Certifications/certifications.js b/js/src/ui/Certifications/certifications.js index b034deda4..47cf36942 100644 --- a/js/src/ui/Certifications/certifications.js +++ b/js/src/ui/Certifications/certifications.js @@ -27,8 +27,7 @@ import styles from './certifications.css'; class Certifications extends Component { static propTypes = { account: PropTypes.string.isRequired, - certifications: PropTypes.array.isRequired, - dappsUrl: PropTypes.string.isRequired + certifications: PropTypes.array.isRequired } render () { @@ -65,7 +64,8 @@ function mapStateToProps (_, initProps) { return (state) => { const certifications = state.certifications[account] || []; - return { certifications }; + const dappsUrl = state.api.dappsUrl; + return { certifications, dappsUrl }; }; } diff --git a/js/src/views/Account/Header/header.js b/js/src/views/Account/Header/header.js index b126951b2..c77c4987d 100644 --- a/js/src/views/Account/Header/header.js +++ b/js/src/views/Account/Header/header.js @@ -78,7 +78,6 @@ export default class Header extends Component { balance={ balance } /> { children } From a84cd9143f014c9bba9a5b2bc6c9790bf9d9f27b Mon Sep 17 00:00:00 2001 From: Jannis R Date: Fri, 9 Dec 2016 00:34:28 +0100 Subject: [PATCH 158/280] show certifications in accounts list --- js/src/views/Accounts/List/list.js | 40 ++++++++++++++++++++++-- js/src/views/Accounts/Summary/summary.js | 17 +++++++++- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/js/src/views/Accounts/List/list.js b/js/src/views/Accounts/List/list.js index 4d54b640f..8c29d17f0 100644 --- a/js/src/views/Accounts/List/list.js +++ b/js/src/views/Accounts/List/list.js @@ -15,13 +15,16 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; import { Container } from '~/ui'; +import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions'; import Summary from '../Summary'; import styles from './list.css'; -export default class List extends Component { +class List extends Component { static propTypes = { accounts: PropTypes.object, walletsOwners: PropTypes.object, @@ -31,7 +34,11 @@ export default class List extends Component { empty: PropTypes.bool, order: PropTypes.string, orderFallback: PropTypes.string, - handleAddSearchToken: PropTypes.func + certifications: PropTypes.object.isRequired, + + handleAddSearchToken: PropTypes.func, + fetchCertifiers: PropTypes.func.isRequired, + fetchCertifications: PropTypes.func.isRequired }; render () { @@ -42,6 +49,14 @@ export default class List extends Component { ); } + componentWillMount () { + const { fetchCertifiers, accounts, fetchCertifications } = this.props; + fetchCertifiers(); + for (let address in accounts) { + fetchCertifications(address); + } + } + renderAccounts () { const { accounts, balances, link, empty, handleAddSearchToken, walletsOwners } = this.props; @@ -72,7 +87,9 @@ export default class List extends Component { account={ account } balance={ balance } owners={ owners } - handleAddSearchToken={ handleAddSearchToken } /> + handleAddSearchToken={ handleAddSearchToken } + showCertifications + /> ); }); @@ -207,3 +224,20 @@ export default class List extends Component { }); } } + +function mapStateToProps (state) { + const { certifications } = state; + return { certifications }; +} + +function mapDispatchToProps (dispatch) { + return bindActionCreators({ + fetchCertifiers, + fetchCertifications + }, dispatch); +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(List); diff --git a/js/src/views/Accounts/Summary/summary.js b/js/src/views/Accounts/Summary/summary.js index 764f24edf..0894e8699 100644 --- a/js/src/views/Accounts/Summary/summary.js +++ b/js/src/views/Accounts/Summary/summary.js @@ -20,6 +20,7 @@ import { isEqual } from 'lodash'; import ReactTooltip from 'react-tooltip'; import { Balance, Container, ContainerTitle, IdentityIcon, IdentityName, Tags, Input } from '~/ui'; +import Certifications from '~/ui/Certifications'; import { nullableProptype } from '~/util/proptypes'; import styles from '../accounts.css'; @@ -35,12 +36,14 @@ export default class Summary extends Component { link: PropTypes.string, name: PropTypes.string, noLink: PropTypes.bool, + showCertifications: PropTypes.bool, handleAddSearchToken: PropTypes.func, owners: nullableProptype(PropTypes.array) }; static defaultProps = { - noLink: false + noLink: false, + showCertifications: false }; shouldComponentUpdate (nextProps) { @@ -107,6 +110,7 @@ export default class Summary extends Component { { this.renderOwners() } { this.renderBalance() } + { this.renderCertifications() } ); } @@ -172,4 +176,15 @@ export default class Summary extends Component { ); } + + renderCertifications () { + const { showCertifications, account } = this.props; + if (!showCertifications) { + return null; + } + + return ( + + ); + } } From e53629089230f23899f73840e8b218f5d4062c8d Mon Sep 17 00:00:00 2001 From: Jannis R Date: Fri, 9 Dec 2016 00:38:44 +0100 Subject: [PATCH 159/280] fix linting issues --- js/src/ui/Certifications/certifications.js | 4 ++-- js/src/views/Account/Header/header.js | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/js/src/ui/Certifications/certifications.js b/js/src/ui/Certifications/certifications.js index 47cf36942..36635ff58 100644 --- a/js/src/ui/Certifications/certifications.js +++ b/js/src/ui/Certifications/certifications.js @@ -16,7 +16,6 @@ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; import { hashToImageUrl } from '~/redux/providers/imagesReducer'; @@ -27,7 +26,8 @@ import styles from './certifications.css'; class Certifications extends Component { static propTypes = { account: PropTypes.string.isRequired, - certifications: PropTypes.array.isRequired + certifications: PropTypes.array.isRequired, + dappsUrl: PropTypes.string.isRequired } render () { diff --git a/js/src/views/Account/Header/header.js b/js/src/views/Account/Header/header.js index c77c4987d..c2b9151e7 100644 --- a/js/src/views/Account/Header/header.js +++ b/js/src/views/Account/Header/header.js @@ -23,10 +23,6 @@ import Certifications from '~/ui/Certifications'; import styles from './header.css'; export default class Header extends Component { - static contextTypes = { - api: PropTypes.object - }; - static propTypes = { account: PropTypes.object, balance: PropTypes.object, @@ -40,7 +36,6 @@ export default class Header extends Component { }; render () { - const { api } = this.context; const { account, balance, className, children } = this.props; const { address, meta, uuid } = account; From 600a7e5ccc84609f8e088d9eb33624cff6ae8bf6 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Tue, 6 Dec 2016 15:09:11 +0100 Subject: [PATCH 160/280] 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 161/280] 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 162/280] 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 163/280] 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 164/280] 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 = (