diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json new file mode 100644 index 000000000..38e5334a3 --- /dev/null +++ b/ethcore/res/tendermint.json @@ -0,0 +1,40 @@ +{ + "name": "TestBFT", + "engine": { + "Tendermint": { + "params": { + "gasLimitBoundDivisor": "0x0400", + "authorities" : [ + "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1", + "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e" + ] + } + } + }, + "params": { + "accountStartNonce": "0x0", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID" : "0x2323" + }, + "genesis": { + "seal": { + "generic": { + "rlp": "f88980b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f843b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + }, + "difficulty": "0x20000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x", + "gasLimit": "0x2fefd8" + }, + "accounts": { + "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/client/chain_notify.rs b/ethcore/src/client/chain_notify.rs index a9f8d0204..a6dbbeacc 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. + _proposed: Vec, _duration: u64) { // does nothing by default } @@ -41,6 +43,9 @@ pub trait ChainNotify : Send + Sync { // does nothing by default } + /// fires when chain broadcasts a message + fn broadcast(&self, _data: Vec) {} + /// fires when new transactions are received from a peer fn transactions_received(&self, _hashes: Vec, diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index dce594617..93b850051 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -24,8 +24,8 @@ use time::precise_time_ns; // util use util::{Bytes, PerfTimer, Itertools, Mutex, RwLock, Hashable}; use util::{journaldb, TrieFactory, Trie}; -use util::trie::TrieSpec; use util::{U256, H256, Address, H2048, Uint, FixedHash}; +use util::trie::TrieSpec; use util::kvdb::*; // other @@ -396,9 +396,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(); @@ -417,12 +418,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) { + self.block_queue.mark_as_good(&[header.hash()]); + proposed_blocks.push(block.bytes); + } 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()); } @@ -436,7 +442,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) }; { @@ -454,6 +460,7 @@ impl Client { enacted.clone(), retracted.clone(), Vec::new(), + proposed_blocks.clone(), duration, ); }); @@ -577,9 +584,10 @@ impl Client { self.miner.clone() } - /// Used by PoA to try sealing on period change. - pub fn update_sealing(&self) { - self.miner.update_sealing(self) + + /// Replace io channel. Useful for testing. + pub fn set_io_channel(&self, io_channel: IoChannel) { + *self.io_channel.lock() = io_channel; } /// Attempt to get a copy of a specific block's final state. @@ -1290,6 +1298,18 @@ impl BlockChainClient for Client { self.miner.pending_transactions(self.chain.read().best_block_number()) } + fn queue_consensus_message(&self, message: Bytes) { + let channel = self.io_channel.lock().clone(); + if let Err(e) = channel.send(ClientIoMessage::NewMessage(message)) { + debug!("Ignoring the message, error queueing: {}", e); + } + } + + fn broadcast_consensus_message(&self, message: Bytes) { + self.notify(|notify| notify.broadcast(message.clone())); + } + + fn signing_network_id(&self) -> Option { self.engine.signing_network_id(&self.latest_env_info()) } @@ -1314,7 +1334,6 @@ impl BlockChainClient for Client { } impl MiningBlockChainClient for Client { - fn latest_schedule(&self) -> Schedule { self.engine.schedule(&self.latest_env_info()) } @@ -1357,6 +1376,30 @@ impl MiningBlockChainClient for Client { &self.factories.vm } + fn update_sealing(&self) { + self.miner.update_sealing(self) + } + + fn submit_seal(&self, block_hash: H256, seal: Vec) { + if self.miner.submit_seal(self, block_hash, seal).is_err() { + warn!(target: "poa", "Wrong internal seal submission!") + } + } + + fn broadcast_proposal_block(&self, block: SealedBlock) { + self.notify(|notify| { + notify.new_blocks( + 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(); @@ -1381,6 +1424,7 @@ impl MiningBlockChainClient for Client { enacted.clone(), retracted.clone(), vec![h.clone()], + vec![], precise_time_ns() - start, ); }); @@ -1416,6 +1460,12 @@ impl ::client::ProvingBlockChainClient for Client { } } +impl Drop for Client { + fn drop(&mut self) { + self.engine.stop(); + } +} + #[cfg(test)] mod tests { diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 44954f99d..7d619ffba 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -360,6 +360,18 @@ impl MiningBlockChainClient for TestBlockChainClient { fn import_sealed_block(&self, _block: SealedBlock) -> ImportResult { Ok(H256::default()) } + + fn broadcast_proposal_block(&self, _block: SealedBlock) {} + + fn update_sealing(&self) { + self.miner.update_sealing(self) + } + + fn submit_seal(&self, block_hash: H256, seal: Vec) { + if self.miner.submit_seal(self, block_hash, seal).is_err() { + warn!(target: "poa", "Wrong internal seal submission!") + } + } } impl BlockChainClient for TestBlockChainClient { @@ -663,6 +675,12 @@ impl BlockChainClient for TestBlockChainClient { self.miner.import_external_transactions(self, txs); } + fn queue_consensus_message(&self, message: Bytes) { + self.spec.engine.handle_message(&message).unwrap(); + } + + fn broadcast_consensus_message(&self, _message: Bytes) {} + fn pending_transactions(&self) -> Vec { self.miner.pending_transactions(self.chain_info().best_block_number) } diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index f44a4496f..ad2bed95f 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -202,6 +202,12 @@ pub trait BlockChainClient : Sync + Send { /// Queue transactions for importing. fn queue_transactions(&self, transactions: Vec, peer_id: usize); + /// Queue conensus engine message. + fn queue_consensus_message(&self, message: Bytes); + + /// Used by PoA to communicate with peers. + fn broadcast_consensus_message(&self, message: Bytes); + /// list all transactions fn pending_transactions(&self) -> Vec; @@ -273,6 +279,15 @@ pub trait MiningBlockChainClient: BlockChainClient { /// Returns EvmFactory. fn vm_factory(&self) -> &EvmFactory; + /// Used by PoA to try sealing on period change. + fn update_sealing(&self); + + /// Used by PoA to submit gathered signatures. + fn submit_seal(&self, block_hash: H256, seal: Vec); + + /// 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; diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 9f78d8cec..8d1c004c5 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; +use engines::{Engine, Seal, EngineError}; use header::Header; use error::{Error, BlockError}; use blockchain::extras::BlockDetails; @@ -225,8 +225,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()) { @@ -235,7 +235,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."); } @@ -245,7 +246,7 @@ impl Engine for AuthorityRound { } else { trace!(target: "poa", "generate_seal: Not a proposer for step {}.", step); } - None + Seal::None } /// Check the number of seal fields. @@ -288,7 +289,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()))); } let gas_limit_divisor = self.our_params.gas_limit_bound_divisor; @@ -347,6 +348,7 @@ mod tests { use tests::helpers::*; use account_provider::AccountProvider; use spec::Spec; + use engines::Seal; #[test] fn has_valid_metadata() { @@ -416,17 +418,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 5676365da..37ac4066b 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 ef444cac1..74f71168c 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 5e5c5530f..a3e57dd65 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -20,11 +20,13 @@ mod null_engine; mod instant_seal; mod basic_authority; mod authority_round; +mod tendermint; 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; use util::*; use account_provider::AccountProvider; @@ -42,6 +44,47 @@ use ethereum::ethash; use blockchain::extras::BlockDetails; use views::HeaderView; +/// Voting errors. +#[derive(Debug)] +pub enum EngineError { + /// Signature does not belong to an authority. + NotAuthorized(Address), + /// The same author issued different votes at the same step. + DoubleVote(Address), + /// The received block is from an incorrect proposer. + NotProposer(Mismatch
), + /// Message was not expected. + UnexpectedMessage, + /// Seal field has an unexpected size. + BadSealFieldSize(OutOfBounds), +} + +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(), + BadSealFieldSize(ref oob) => format!("Seal field has an unexpected length: {}", oob), + }; + + f.write_fmt(format_args!("Engine error ({})", msg)) + } +} + +/// 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 { @@ -94,7 +137,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. @@ -133,6 +176,10 @@ pub trait Engine : Sync + Send { header.set_gas_limit(parent.gas_limit().clone()); } + /// Handle any potential consensus messages; + /// updating consensus state and potentially issuing a new one. + fn handle_message(&self, _message: &[u8]) -> 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. /// Determine whether a particular address is a builtin contract. @@ -153,9 +200,16 @@ 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) {} + /// Stops any services that the may hold the Engine and makes it safe to drop. + fn stop(&self) {} + /// Add a channel for communication with Client which can be used for sealing. fn register_message_channel(&self, _message_channel: IoChannel) {} diff --git a/ethcore/src/engines/tendermint/message.rs b/ethcore/src/engines/tendermint/message.rs new file mode 100644 index 000000000..3e5da592d --- /dev/null +++ b/ethcore/src/engines/tendermint/message.rs @@ -0,0 +1,279 @@ +// 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 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 { + pub signature: H520, + pub height: Height, + pub round: Round, + pub step: Step, + pub block_hash: Option, +} + + +fn consensus_round(header: &Header) -> Result { + 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 { + 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().get(1).expect("seal passed basic verification; seal has 3 fields; qed").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 new_commit(proposal: &ConsensusMessage, signature: H520) -> Self { + ConsensusMessage { + signature: signature, + height: proposal.height, + round: proposal.round, + step: Step::Precommit, + block_hash: proposal.block_hash, + } + } + + 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_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 { + 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)) + } + + pub fn precommit_hash(&self) -> H256 { + message_info_rlp(self.height, self.round, Step::Precommit, self.block_hash).sha3() + } +} + +impl PartialOrd for ConsensusMessage { + fn partial_cmp(&self, m: &ConsensusMessage) -> Option { + Some(self.cmp(m)) + } +} + +impl Step { + fn number(&self) -> u8 { + match *self { + Step::Propose => 0, + Step::Prevote => 1, + Step::Precommit => 2, + Step::Commit => 3, + } + } +} + +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 { + self.step.number().cmp(&m.step.number()) + } else { + self.signature.cmp(&m.signature) + } + } +} + +impl Decodable for Step { + fn decode(decoder: &D) -> Result where D: Decoder { + match try!(decoder.as_rlp().as_val()) { + 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()); + } +} + +/// (signature, height, round, step, block_hash) +impl Decodable for ConsensusMessage { + fn decode(decoder: &D) -> Result where D: Decoder { + let rlp = decoder.as_rlp(); + 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: match block_message.is_zero() { + true => None, + false => Some(block_message), + } + }) + } +} + +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) + .append_raw(&info, 1); + } +} + +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_else(H256::zero)); + 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)] +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 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(); + 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 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()); + } +} diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs new file mode 100644 index 000000000..de89658ab --- /dev/null +++ b/ethcore/src/engines/tendermint/mod.rs @@ -0,0 +1,966 @@ +// 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. +/// At each blockchain `Height` there can be multiple `Round`s of voting. +/// Signatures always sign `Height`, `Round`, `Step` and `BlockHash` which is a block hash without seal. +/// First a block with `Seal::Proposal` is issued by the designated proposer. +/// Next the `Round` proceeds through `Prevote` and `Precommit` `Step`s. +/// Block is issued when there is enough `Precommit` votes collected on a particular block at the end of a `Round`. +/// Once enough votes have been gathered the proposer issues that block in the `Commit` step. + +mod message; +mod transition; +mod params; +mod vote_collector; + +use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; +use util::*; +use error::{Error, BlockError}; +use header::Header; +use builtin::Builtin; +use env_info::EnvInfo; +use transaction::SignedTransaction; +use rlp::{UntrustedRlp, View}; +use ethkey::{recover, public_to_address}; +use account_provider::AccountProvider; +use block::*; +use spec::CommonParams; +use engines::{Engine, Seal, EngineError}; +use blockchain::extras::BlockDetails; +use views::HeaderView; +use evm::Schedule; +use io::{IoService, IoChannel}; +use service::ClientIoMessage; +use self::message::*; +use self::transition::TransitionHandler; +use self::params::TendermintParams; +use self::vote_collector::VoteCollector; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Step { + Propose, + Prevote, + Precommit, + 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; + +/// Engine using `Tendermint` consensus algorithm, suitable for EVM chain. +pub struct Tendermint { + params: CommonParams, + our_params: TendermintParams, + builtins: BTreeMap, + step_service: IoService, + /// Address to be used as authority. + authority: RwLock
, + /// Password used for signing messages. + password: RwLock>, + /// Blockchain height. + height: AtomicUsize, + /// Consensus round. + round: AtomicUsize, + /// Consensus step. + step: RwLock, + /// Vote accumulator. + votes: VoteCollector, + /// Channel for updating the sealing. + message_channel: Mutex>>, + /// Used to sign messages and proposals. + account_provider: Mutex>>, + /// Message for the last PoLC. + lock_change: RwLock>, + /// Last lock round. + last_lock: AtomicUsize, + /// Bare hash of the proposed block, used for seal submission. + proposal: RwLock>, +} + +impl Tendermint { + /// Create a new instance of Tendermint engine + pub fn new(params: CommonParams, our_params: TendermintParams, builtins: BTreeMap) -> Result, Error> { + let engine = Arc::new( + Tendermint { + params: params, + our_params: our_params, + 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), + votes: VoteCollector::new(), + message_channel: Mutex::new(None), + account_provider: Mutex::new(None), + lock_change: RwLock::new(None), + last_lock: AtomicUsize::new(0), + proposal: RwLock::new(None), + }); + let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; + 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", "UpdateSealing message sent."), + Err(err) => warn!(target: "poa", "Could not send a sealing message {}.", err), + } + } + } + + 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", "SubmitSeal message sent."), + Err(err) => warn!(target: "poa", "Could not send a sealing message {}.", err), + } + } + } + + fn broadcast_message(&self, message: Bytes) { + let channel = self.message_channel.lock().clone(); + if let Some(ref channel) = channel { + match channel.send(ClientIoMessage::BroadcastMessage(message)) { + Ok(_) => trace!(target: "poa", "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() { + 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); + let message = ConsensusMessage::new(signature, h, r, *s, block_hash); + self.votes.vote(message.clone(), *authority); + debug!(target: "poa", "Generated {:?} as {}.", message, *authority); + self.handle_valid_message(&message); + + Some(message_rlp) + }, + Err(e) => { + trace!(target: "poa", "Could not sign the message {}", e); + None + }, + } + } else { + warn!(target: "poa", "No AccountProvider available."); + None + } + } + + fn generate_and_broadcast_message(&self, block_hash: Option) { + if let Some(message) = self.generate_message(block_hash) { + self.broadcast_message(message); + } + } + + /// Broadcast all messages since last issued block to get the peers up to speed. + fn broadcast_old_messages(&self) { + for m in self.votes.get_up_to(self.height.load(AtomicOrdering::SeqCst)).into_iter() { + self.broadcast_message(m); + } + } + + 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(new_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) { + if let Err(io_err) = self.step_service.send_message(step) { + warn!(target: "poa", "Could not proceed to step {}.", io_err) + } + *self.step.write() = step; + match step { + Step::Propose => { + *self.proposal.write() = None; + self.update_sealing() + }, + Step::Prevote => { + let block_hash = match *self.lock_change.read() { + 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 => { + trace!(target: "poa", "to_step: Precommit."); + let block_hash = match *self.lock_change.read() { + Some(ref m) if self.is_round(m) && m.block_hash.is_some() => { + trace!(target: "poa", "Setting last lock: {}", m.round); + self.last_lock.store(m.round, AtomicOrdering::SeqCst); + m.block_hash + }, + _ => None, + }; + self.generate_and_broadcast_message(block_hash); + }, + 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() { + // Generate seal and remove old votes. + if self.is_proposer(&*self.authority.read()).is_ok() { + if let Some(seal) = self.votes.seal_signatures(height, round, block_hash) { + trace!(target: "poa", "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", "Not enough votes found!"); + } + } + } + }, + } + } + + fn is_authority(&self, address: &Address) -> bool { + self.our_params.authorities.contains(address) + } + + fn is_above_threshold(&self, n: usize) -> bool { + n > self.our_params.authority_n * 2/3 + } + + /// 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 = 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(()) + } else { + Err(EngineError::NotProposer(Mismatch { expected: proposer.clone(), found: address.clone() })) + } + } + + /// 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)) + } + + fn is_round(&self, message: &ConsensusMessage) -> bool { + message.is_round(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst)) + } + + fn increment_round(&self, n: Round) { + trace!(target: "poa", "increment_round: New round."); + self.round.fetch_add(n, 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 { + 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 { + 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 { + let aligned_count = self.votes.count_aligned_votes(&message); + self.is_above_threshold(aligned_count) + } + + fn handle_valid_message(&self, message: &ConsensusMessage) { + let is_newer_than_lock = match *self.lock_change.read() { + Some(ref lock) => message > lock, + None => true, + }; + let lock_change = is_newer_than_lock + && message.step == Step::Prevote + && message.block_hash.is_some() + && self.has_enough_aligned_votes(message); + if 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. + 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) + }, + // Avoid counting twice. + Step::Prevote if lock_change => 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", "Transition to {:?} triggered.", step); + self.to_step(step); + } + } + } +} + +impl Engine for Tendermint { + fn name(&self) -> &str { "Tendermint" } + fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } + /// (consensus round, proposal signature, authority signatures) + fn seal_fields(&self) -> usize { 3 } + + 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."); + map![ + "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()) + ] + } + + fn schedule(&self, _env_info: &EnvInfo) -> Schedule { + Schedule::new_post_eip150(usize::max_value(), true, true, true) + } + + fn populate_from_parent(&self, header: &mut Header, parent: &Header, gas_floor_target: U256, _gas_ceil_target: U256) { + 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()) + } + }); + } + + /// Should this node participate. + fn is_sealer(&self, address: &Address) -> Option { + Some(self.is_authority(address)) + } + + /// 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_some() { + return Seal::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.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() = bh; + 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"); + Seal::None + } + } else { + warn!(target: "poa", "generate_seal: FAIL: accounts not provided"); + Seal::None + } + } + + fn handle_message(&self, rlp: &[u8]) -> Result<(), Error> { + let rlp = UntrustedRlp::new(rlp); + let message: ConsensusMessage = try!(rlp.as_val()); + 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))); + } + self.broadcast_message(rlp.as_raw().to_vec()); + trace!(target: "poa", "Handling a valid {:?} from {}.", message, sender); + self.votes.vote(message.clone(), sender); + self.handle_valid_message(&message); + } + Ok(()) + } + + fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + let seal_length = header.seal().len(); + if seal_length == self.seal_fields() { + 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> { + let proposal = try!(ConsensusMessage::new_proposal(header)); + let proposer = try!(proposal.verify()); + if !self.is_authority(&proposer) { + try!(Err(EngineError::NotAuthorized(proposer))) + } + + let precommit_hash = proposal.precommit_hash(); + let ref signatures_field = header.seal()[2]; + let mut signature_count = 0; + let mut origins = HashSet::new(); + for rlp in UntrustedRlp::new(signatures_field).iter() { + let precommit: ConsensusMessage = ConsensusMessage::new_commit(&proposal, try!(rlp.as_val())); + let address = match self.votes.get(&precommit) { + Some(a) => a, + None => public_to_address(&try!(recover(&precommit.signature.into(), &precommit_hash))), + }; + if !self.our_params.authorities.contains(&address) { + try!(Err(EngineError::NotAuthorized(address.to_owned()))) + } + + if origins.insert(address) { + signature_count += 1; + } else { + warn!(target: "poa", "verify_block_unordered: Duplicate signature from {} on the seal.", address); + try!(Err(BlockError::InvalidSeal)); + } + } + + // 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_round_proposer(proposal.height, proposal.round, &proposer)); + } + Ok(()) + } + + fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + if header.number() == 0 { + 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 { + try!(Err(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas), max: Some(max_gas), found: header.gas_limit().clone() }))); + } + + Ok(()) + } + + fn verify_transaction_basic(&self, t: &SignedTransaction, _header: &Header) -> Result<(), 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 + } + + fn set_signer(&self, address: Address, password: String) { + *self.authority.write() = address; + *self.password.write() = Some(password); + self.to_step(Step::Propose); + } + + fn stop(&self) { + self.step_service.stop() + } + + fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { + let new_number = new_header.number(); + let best_number = best_header.number(); + trace!(target: "poa", "new_header: {}, best_header: {}", new_number, best_number); + if new_number != best_number { + new_number > best_number + } else { + 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 + } + } + } + + 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. + trace!(target: "poa", "Received a commit for height {}, round {}.", proposal.height, proposal.round); + self.to_next_height(proposal.height); + return false; + } + 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 + } + + /// Equivalent to a timeout: to be used for tests. + fn step(&self) { + let next_step = match *self.step.read() { + Step::Propose => { + trace!(target: "poa", "Propose timeout."); + Step::Prevote + }, + Step::Prevote if self.has_enough_any_votes() => { + trace!(target: "poa", "Prevote timeout."); + Step::Precommit + }, + Step::Prevote => { + trace!(target: "poa", "Prevote timeout without enough votes."); + self.broadcast_old_messages(); + Step::Prevote + }, + Step::Precommit if self.has_enough_any_votes() => { + trace!(target: "poa", "Precommit timeout."); + self.increment_round(1); + Step::Propose + }, + Step::Precommit => { + trace!(target: "poa", "Precommit timeout without enough votes."); + self.broadcast_old_messages(); + Step::Precommit + }, + Step::Commit => { + trace!(target: "poa", "Commit timeout."); + Step::Propose + }, + }; + self.to_step(next_step); + } + + fn register_message_channel(&self, message_channel: IoChannel) { + trace!(target: "poa", "Register the IoChannel."); + *self.message_channel.lock() = Some(message_channel); + } + + fn register_account_provider(&self, account_provider: Arc) { + *self.account_provider.lock() = Some(account_provider); + } +} + +#[cfg(test)] +mod tests { + use util::*; + use util::trie::TrieSpec; + use io::{IoContext, IoHandler}; + use block::*; + use error::{Error, BlockError}; + use header::Header; + use env_info::EnvInfo; + use tests::helpers::*; + use account_provider::AccountProvider; + use io::IoService; + use service::ClientIoMessage; + use spec::Spec; + use engines::{Engine, EngineError, Seal}; + use super::*; + use super::message::*; + + /// Accounts inserted with "0" and "1" are authorities. First proposer is "0". + fn setup() -> (Spec, Arc) { + let tap = Arc::new(AccountProvider::transient_provider()); + let spec = Spec::new_test_tendermint(); + spec.engine.register_account_provider(tap.clone()); + (spec, tap) + } + + 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, &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(); + let b = b.close_and_lock(); + 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) -> Bytes where F: FnOnce(H256) -> Result { + let mi = message_info_rlp(height, round, step, block_hash); + let m = message_full_rlp(&signer(mi.sha3()).unwrap().into(), &mi); + engine.handle_message(&m).unwrap(); + m + } + + 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(); + vec![ + ::rlp::encode(&round).to_vec(), + ::rlp::encode(&H520::from(signature)).to_vec(), + ::rlp::EMPTY_LIST_RLP.to_vec() + ] + } + + 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![ + H520::from(tap.sign(v1, None, vote_info.sha3()).unwrap()), + H520::from(tap.sign(v2, None, 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(); + addr + } + + fn insert_and_register(tap: &Arc, engine: &Arc, acc: &str) -> Address { + let addr = insert_and_unlock(tap, acc); + engine.set_signer(addr.clone(), acc.into()); + addr + } + + struct TestIo { + received: RwLock> + } + + impl TestIo { + 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.write().push(net_message.clone()); + } + } + + #[test] + fn has_valid_metadata() { + let engine = Spec::new_test_tendermint().engine; + assert!(!engine.name().is_empty()); + assert!(engine.version().major >= 1); + } + + #[test] + fn can_return_schedule() { + let engine = Spec::new_test_tendermint().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 verification_fails_on_short_seal() { + let engine = Spec::new_test_tendermint().engine; + let 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 allows_correct_proposer() { + 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.clone(), None).is_ok()); + + let validator = insert_and_unlock(&tap, "1"); + header.set_author(validator); + let seal = proposal_seal(&tap, &header, 0); + header.set_seal(seal); + // Bad proposer. + match engine.verify_block_unordered(&header, None) { + 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!(), + }; + engine.stop(); + } + + #[test] + fn seal_signatures_checking() { + let (spec, tap) = setup(); + let engine = spec.engine; + + let mut header = Header::default(); + let proposer = insert_and_unlock(&tap, "1"); + header.set_author(proposer); + let mut seal = proposal_seal(&tap, &header, 0); + + let vote_info = message_info_rlp(0, 0, Step::Precommit, Some(header.bare_hash())); + 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()); + + 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(signature1), H520::from(bad_signature)]).to_vec(); + header.set_seal(seal); + + // One good and one bad signature. + match engine.verify_block_unordered(&header, None) { + Err(Error::Engine(EngineError::NotAuthorized(_))) => {}, + _ => panic!(), + }; + engine.stop(); + } + + #[test] + fn can_generate_seal() { + let (spec, tap) = setup(); + + 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()); + spec.engine.stop(); + } + + #[test] + fn can_recognize_proposal() { + let (spec, tap) = setup(); + + let proposer = insert_and_register(&tap, &spec.engine, "1"); + + let (b, seal) = propose_default(&spec, proposer); + let sealed = b.seal(spec.engine.as_ref(), seal).unwrap(); + assert!(spec.engine.is_proposal(sealed.header())); + spec.engine.stop(); + } + + #[test] + fn relays_messages() { + let (spec, tap) = setup(); + let engine = spec.engine.clone(); + let mut db_result = get_temp_state_db(); + let mut db = db_result.take(); + spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); + + let v0 = insert_and_register(&tap, &engine, "0"); + let v1 = insert_and_register(&tap, &engine, "1"); + + let h = 0; + let r = 0; + + // Propose + let (b, _) = propose_default(&spec, v1.clone()); + let proposal = Some(b.header().bare_hash()); + + // Register IoHandler remembers messages. + let io_service = IoService::::start().unwrap(); + let test_io = TestIo::new(); + io_service.register_handler(test_io.clone()).unwrap(); + engine.register_message_channel(io_service.channel()); + + let prevote_current = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal); + + let precommit_current = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal); + + let prevote_future = vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h + 1, r, Step::Prevote, proposal); + + // Wait a bit for async stuff. + ::std::thread::sleep(::std::time::Duration::from_millis(500)); + + // Relays all valid present and future messages. + assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(prevote_current))); + assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(precommit_current))); + assert!(test_io.received.read().contains(&ClientIoMessage::BroadcastMessage(prevote_future))); + engine.stop(); + } + + #[test] + fn seal_submission() { + let (spec, tap) = setup(); + let engine = spec.engine.clone(); + let mut db_result = get_temp_state_db(); + let mut db = db_result.take(); + spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); + + let v0 = insert_and_register(&tap, &engine, "0"); + let v1 = insert_and_register(&tap, &engine, "1"); + + let h = 1; + let r = 0; + + // Register IoHandler remembers messages. + let test_io = TestIo::new(); + let io_service = IoService::::start().unwrap(); + io_service.register_handler(test_io.clone()).unwrap(); + engine.register_message_channel(io_service.channel()); + + // Propose + let (b, mut seal) = propose_default(&spec, v1.clone()); + let proposal = Some(b.header().bare_hash()); + engine.step(); + + // Prevote. + vote(&engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Prevote, proposal); + vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Prevote, proposal); + vote(&engine, |mh| tap.sign(v1, None, mh).map(H520::from), h, r, Step::Precommit, proposal); + vote(&engine, |mh| tap.sign(v0, None, mh).map(H520::from), h, r, Step::Precommit, proposal); + + // Wait a bit for async stuff. + ::std::thread::sleep(::std::time::Duration::from_millis(500)); + + seal[2] = precommit_signatures(&tap, h, r, Some(b.header().bare_hash()), v1, v0); + assert!(test_io.received.read().contains(&ClientIoMessage::SubmitSeal(proposal.unwrap(), seal))); + engine.stop(); + } +} diff --git a/ethcore/src/engines/tendermint/params.rs b/ethcore/src/engines/tendermint/params.rs new file mode 100644 index 000000000..cf723713b --- /dev/null +++ b/ethcore/src/engines/tendermint/params.rs @@ -0,0 +1,72 @@ +// 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 specific parameters. + +use ethjson; +use super::transition::TendermintTimeouts; +use util::{Address, U256}; +use time::Duration; + +/// `Tendermint` params. +#[derive(Debug, Clone)] +pub struct TendermintParams { + /// Gas limit divisor. + pub gas_limit_bound_divisor: U256, + /// List of authorities. + pub authorities: Vec
, + /// Number of authorities. + pub authority_n: usize, + /// Timeout durations for different steps. + pub timeouts: TendermintTimeouts, +} + +impl Default for TendermintParams { + fn default() -> Self { + let authorities = vec!["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e".into(), "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1".into()]; + let val_n = authorities.len(); + TendermintParams { + gas_limit_bound_divisor: 0x0400.into(), + authorities: authorities, + authority_n: val_n, + timeouts: TendermintTimeouts::default(), + } + } +} + +fn to_duration(ms: ethjson::uint::Uint) -> Duration { + let ms: usize = ms.into(); + Duration::milliseconds(ms as i64) +} + +impl From for TendermintParams { + fn from(p: ethjson::spec::TendermintParams) -> Self { + let val: Vec<_> = p.authorities.into_iter().map(Into::into).collect(); + let val_n = val.len(); + let dt = TendermintTimeouts::default(); + TendermintParams { + gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(), + authorities: val, + authority_n: val_n, + timeouts: TendermintTimeouts { + propose: p.timeout_propose.map_or(dt.propose, to_duration), + prevote: p.timeout_prevote.map_or(dt.prevote, to_duration), + precommit: p.timeout_precommit.map_or(dt.precommit, to_duration), + commit: p.timeout_commit.map_or(dt.commit, to_duration), + }, + } + } +} diff --git a/ethcore/src/engines/tendermint/transition.rs b/ethcore/src/engines/tendermint/transition.rs new file mode 100644 index 000000000..83b390d74 --- /dev/null +++ b/ethcore/src/engines/tendermint/transition.rs @@ -0,0 +1,96 @@ +// 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 timeout handling. + +use std::sync::Weak; +use time::Duration; +use io::{IoContext, IoHandler, TimerToken}; +use super::{Tendermint, Step}; +use engines::Engine; + +pub struct TransitionHandler { + pub engine: Weak, +} + +/// Base timeout of each step in ms. +#[derive(Debug, Clone)] +pub struct TendermintTimeouts { + pub propose: Duration, + pub prevote: Duration, + pub precommit: Duration, + pub commit: Duration, +} + +impl TendermintTimeouts { + pub fn for_step(&self, step: Step) -> Duration { + match step { + Step::Propose => self.propose, + Step::Prevote => self.prevote, + Step::Precommit => self.precommit, + Step::Commit => self.commit, + } + } +} + +impl Default for TendermintTimeouts { + fn default() -> Self { + TendermintTimeouts { + propose: Duration::milliseconds(10000), + prevote: Duration::milliseconds(10000), + precommit: Duration::milliseconds(10000), + commit: Duration::milliseconds(10000), + } + } +} + +/// Timer token representing the consensus step timeouts. +pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 23; + +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) { + if let Some(engine) = self.engine.upgrade() { + set_timeout(io, engine.our_params.timeouts.propose) + } + } + + fn timeout(&self, _io: &IoContext, timer: TimerToken) { + if timer == ENGINE_TIMEOUT_TOKEN { + if let Some(engine) = self.engine.upgrade() { + engine.step(); + } + } + } + + fn message(&self, io: &IoContext, next_step: &Step) { + if let Some(engine) = self.engine.upgrade() { + 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), + }; + } + } +} diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs new file mode 100644 index 000000000..be592bc8f --- /dev/null +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -0,0 +1,272 @@ +// 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 util::*; +use super::message::ConsensusMessage; +use super::{Height, Round, Step}; + +#[derive(Debug)] +pub struct VoteCollector { + /// Storing all Proposals, Prevotes and Precommits. + votes: RwLock>, +} + +#[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(); + // 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) + } + + pub fn is_old_or_known(&self, message: &ConsensusMessage) -> bool { + self.votes.read().get(message).map_or(false, |a| { + trace!(target: "poa", "Known message from {}: {:?}.", a, message); + true + }) || { + let guard = self.votes.read(); + let is_old = guard.keys().next().map_or(true, |oldest| message <= oldest); + if is_old { trace!(target: "poa", "Old message {:?}.", message); } + is_old + } + } + + /// 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 { + let bh = Some(block_hash); + 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) + }; + if votes.is_empty() { + return None; + } + // Remove messages that are no longer relevant. + votes.last().map(|m| self.throw_out_old(m)); + let mut votes_vec: Vec<_> = votes.into_iter().map(|m| m.signature).collect(); + votes_vec.sort(); + proposal.map(|p| SealSignatures { + proposal: p.signature, + votes: votes_vec, + }) + } + + pub fn count_aligned_votes(&self, message: &ConsensusMessage) -> usize { + let guard = self.votes.read(); + guard.keys() + .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 { + 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 + } + + pub fn get_up_to(&self, height: Height) -> Vec { + let guard = self.votes.read(); + guard + .keys() + .filter(|m| m.step.is_pre()) + .take_while(|m| m.height <= height) + .map(|m| ::rlp::encode(m).to_vec()) + .collect() + } + + pub fn get(&self, message: &ConsensusMessage) -> Option
{ + let guard = self.votes.read(); + guard.get(message).cloned() + } +} + +#[cfg(test)] +mod tests { + use util::*; + use super::*; + use super::super::{Height, Round, BlockHash, Step}; + use super::super::message::ConsensusMessage; + + 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] + fn seal_retrieval() { + let collector = VoteCollector::new(); + let bh = Some("1".sha3()); + let h = 1; + let r = 2; + let mut signatures = Vec::new(); + for _ in 0..5 { + signatures.push(H520::random()); + } + // Wrong height proposal. + random_vote(&collector, signatures[4].clone(), h - 1, r, Step::Propose, bh.clone()); + // Good proposal + random_vote(&collector, signatures[0].clone(), h, r, Step::Propose, bh.clone()); + // Wrong block proposal. + random_vote(&collector, signatures[0].clone(), h, r, Step::Propose, Some("0".sha3())); + // Wrong block precommit. + random_vote(&collector, signatures[3].clone(), h, r, Step::Precommit, Some("0".sha3())); + // Wrong round proposal. + random_vote(&collector, signatures[0].clone(), h, r - 1, Step::Propose, bh.clone()); + // Prevote. + random_vote(&collector, signatures[0].clone(), h, r, Step::Prevote, bh.clone()); + // Relevant precommit. + random_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone()); + // Replcated vote. + random_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone()); + // Wrong round precommit. + random_vote(&collector, signatures[4].clone(), h, r + 1, Step::Precommit, bh.clone()); + // Wrong height precommit. + random_vote(&collector, signatures[3].clone(), h + 1, r, Step::Precommit, bh.clone()); + // Relevant precommit. + random_vote(&collector, signatures[1].clone(), h, r, Step::Precommit, bh.clone()); + // Wrong round precommit, same signature. + random_vote(&collector, signatures[1].clone(), h, r + 1, Step::Precommit, bh.clone()); + // Wrong round precommit. + 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() + }; + assert_eq!(seal, collector.seal_signatures(h, r, bh.unwrap()).unwrap()); + } + + #[test] + fn count_votes() { + let collector = VoteCollector::new(); + // good prevote + 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 + 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 + random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3())); + // good prevote + let same_sig = H520::random(); + 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 + random_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("1".sha3())); + // good prevote + 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); + + 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); + } + + #[test] + fn remove_old() { + let collector = VoteCollector::new(); + 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(), + height: 3, + round: 2, + step: Step::Precommit, + block_hash: Some("1".sha3()) + }; + 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); + } +} diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index 4afbe25b8..846972c02 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::EngineError; use ethkey::Error as EthkeyError; pub use types::executed::{ExecutionError, CallError}; @@ -167,8 +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), } impl fmt::Display for BlockError { @@ -202,7 +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), }; f.write_fmt(format_args!("Block error ({})", msg)) @@ -263,6 +261,8 @@ pub enum Error { Snappy(::util::snappy::InvalidInput), /// Snapshot error. Snapshot(SnapshotError), + /// Consensus vote error. + Engine(EngineError), /// Ethkey error. Ethkey(EthkeyError), } @@ -285,6 +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) => err.fmt(f), Error::Ethkey(ref err) => err.fmt(f), } } @@ -383,6 +384,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: EngineError) -> Error { + Error::Engine(err) + } +} + impl From for Error { fn from(err: EthkeyError) -> Error { Error::Ethkey(err) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index a6a63ccaf..eb050f0bd 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, TransactionId}; 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; @@ -466,34 +466,43 @@ 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 { 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 sealed block. + 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. @@ -1024,7 +1033,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) { @@ -1039,7 +1047,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/service.rs b/ethcore/src/service.rs index d809de51a..7a149c528 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -20,7 +20,7 @@ use util::*; use io::*; use spec::Spec; use error::*; -use client::{Client, ClientConfig, ChainNotify}; +use client::{Client, BlockChainClient, MiningBlockChainClient, ClientConfig, ChainNotify}; use miner::Miner; use snapshot::ManifestData; use snapshot::service::{Service as SnapshotService, ServiceParams as SnapServiceParams}; @@ -28,11 +28,9 @@ use std::sync::atomic::AtomicBool; #[cfg(feature="ipc")] use nanoipc; -#[cfg(feature="ipc")] -use client::BlockChainClient; /// Message type for external and internal events -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq, Debug)] pub enum ClientIoMessage { /// Best Block Hash in chain has been changed NewChainHead, @@ -50,6 +48,12 @@ pub enum ClientIoMessage { TakeSnapshot(u64), /// Trigger sealing update (useful for internal sealing). UpdateSealing, + /// Submit seal (useful for internal sealing). + SubmitSeal(H256, Vec), + /// Broadcast a message to the network. + BroadcastMessage(Bytes), + /// New consensus message received. + NewMessage(Bytes) } /// Client service setup. Creates and registers client and network services with the IO subsystem. @@ -220,9 +224,11 @@ impl IoHandler for ClientIoHandler { debug!(target: "snapshot", "Failed to initialize periodic snapshot thread: {:?}", e); } }, - ClientIoMessage::UpdateSealing => { - trace!(target: "authorityround", "message: UpdateSealing"); - self.client.update_sealing() + 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_consensus_message(message.clone()), + ClientIoMessage::NewMessage(ref message) => if let Err(e) = self.client.engine().handle_message(message) { + trace!(target: "poa", "Invalid message received: {}", e); }, _ => {} // ignore other messages } diff --git a/ethcore/src/snapshot/watcher.rs b/ethcore/src/snapshot/watcher.rs index 2ee186020..91d94174e 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/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index bfd2ebd1d..f7a14ad82 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -18,7 +18,7 @@ use util::*; use builtin::Builtin; -use engines::{Engine, NullEngine, InstantSeal, BasicAuthority, AuthorityRound}; +use engines::{Engine, NullEngine, InstantSeal, BasicAuthority, AuthorityRound, Tendermint}; use pod_state::*; use account_db::*; use header::{BlockNumber, Header}; @@ -146,7 +146,8 @@ impl Spec { ethjson::spec::Engine::InstantSeal => Arc::new(InstantSeal::new(params, builtins)), ethjson::spec::Engine::Ethash(ethash) => Arc::new(ethereum::Ethash::new(params, From::from(ethash.params), builtins)), ethjson::spec::Engine::BasicAuthority(basic_authority) => Arc::new(BasicAuthority::new(params, From::from(basic_authority.params), builtins)), - ethjson::spec::Engine::AuthorityRound(authority_round) => AuthorityRound::new(params, From::from(authority_round.params), builtins).expect("Consensus engine could not be started."), + ethjson::spec::Engine::AuthorityRound(authority_round) => AuthorityRound::new(params, From::from(authority_round.params), builtins).expect("Failed to start AuthorityRound consensus engine."), + ethjson::spec::Engine::Tendermint(tendermint) => Tendermint::new(params, From::from(tendermint.params), builtins).expect("Failed to start the Tendermint consensus engine."), } } @@ -275,6 +276,10 @@ impl Spec { /// Create a new Spec with AuthorityRound consensus which does internal sealing (not requiring work). /// Accounts with secrets "0".sha3() and "1".sha3() are the authorities. pub fn new_test_round() -> Self { load_bundled!("authority_round") } + + /// Create a new Spec with Tendermint consensus which does internal sealing (not requiring work). + /// Account "0".sha3() and "1".sha3() are a authorities. + pub fn new_test_tendermint() -> Self { load_bundled!("tendermint") } } #[cfg(test)] diff --git a/ethcore/src/state_db.rs b/ethcore/src/state_db.rs index 79c1a4a2c..d38449f7a 100644 --- a/ethcore/src/state_db.rs +++ b/ethcore/src/state_db.rs @@ -457,7 +457,6 @@ impl StateDB { #[cfg(test)] mod tests { - use util::{U256, H256, FixedHash, Address, DBTransaction}; use tests::helpers::*; use state::Account; @@ -531,4 +530,3 @@ mod tests { assert!(s.get_cached_account(&address).is_none()); } } - diff --git a/ethkey/src/signature.rs b/ethkey/src/signature.rs index 97a2e0715..ad595cfb9 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}; @@ -116,6 +117,18 @@ impl Default for Signature { } } +impl Hash for Signature { + fn hash(&self, state: &mut H) { + H520::from(self.0).hash(state); + } +} + +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) diff --git a/json/src/spec/engine.rs b/json/src/spec/engine.rs index d36327e70..c95693b5e 100644 --- a/json/src/spec/engine.rs +++ b/json/src/spec/engine.rs @@ -19,6 +19,7 @@ use spec::Ethash; use spec::BasicAuthority; use spec::AuthorityRound; +use spec::Tendermint; /// Engine deserialization. #[derive(Debug, PartialEq, Deserialize)] @@ -33,6 +34,8 @@ pub enum Engine { BasicAuthority(BasicAuthority), /// AuthorityRound engine. AuthorityRound(AuthorityRound), + /// Tendermint engine. + Tendermint(Tendermint) } #[cfg(test)] diff --git a/json/src/spec/mod.rs b/json/src/spec/mod.rs index dcd1f3361..f27b13b92 100644 --- a/json/src/spec/mod.rs +++ b/json/src/spec/mod.rs @@ -27,6 +27,7 @@ pub mod state; pub mod ethash; pub mod basic_authority; pub mod authority_round; +pub mod tendermint; pub use self::account::Account; pub use self::builtin::{Builtin, Pricing, Linear}; @@ -39,3 +40,4 @@ pub use self::state::State; pub use self::ethash::{Ethash, EthashParams}; pub use self::basic_authority::{BasicAuthority, BasicAuthorityParams}; pub use self::authority_round::{AuthorityRound, AuthorityRoundParams}; +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..6858602da --- /dev/null +++ b/json/src/spec/tendermint.rs @@ -0,0 +1,67 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! 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, + /// 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. +#[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", + "authorities" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + } + }"#; + + let _deserialized: Tendermint = serde_json::from_str(s).unwrap(); + } +} diff --git a/parity/informant.rs b/parity/informant.rs index 1991146ea..99a3f17fe 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(); diff --git a/sync/src/api.rs b/sync/src/api.rs index 8f03fbac8..45a9c1eb7 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -35,7 +35,12 @@ use parking_lot::RwLock; use chain::{ETH_PACKET_COUNT, SNAPSHOT_SYNC_PACKET_COUNT}; use light::net::{LightProtocol, Params as LightParams, Capabilities, Handler as LightHandler, EventContext}; +/// Parity sync protocol pub const WARP_SYNC_PROTOCOL_ID: ProtocolId = *b"par"; +/// Ethereum sync protocol +pub const ETH_PROTOCOL: ProtocolId = *b"eth"; +/// Ethereum light protocol +pub const LES_PROTOCOL: ProtocolId = *b"les"; /// Sync configuration #[derive(Debug, Clone, Copy)] @@ -64,8 +69,8 @@ impl Default for SyncConfig { max_download_ahead_blocks: 20000, download_old_blocks: true, network_id: 1, - subprotocol_name: *b"eth", - light_subprotocol_name: *b"les", + subprotocol_name: ETH_PROTOCOL, + light_subprotocol_name: LES_PROTOCOL, fork_block: None, warp_sync: false, serve_light: false, @@ -143,7 +148,7 @@ pub struct EthSync { /// Network service network: NetworkService, /// Main (eth/par) protocol handler - sync_handler: Arc, + eth_handler: Arc, /// Light (les) protocol handler light_proto: Option>, /// The main subprotocol name @@ -182,7 +187,7 @@ impl EthSync { let sync = Arc::new(EthSync { network: service, - sync_handler: Arc::new(SyncProtocolHandler { + eth_handler: Arc::new(SyncProtocolHandler { sync: RwLock::new(chain_sync), chain: params.chain, snapshot_service: params.snapshot_service, @@ -201,15 +206,15 @@ impl EthSync { impl SyncProvider for EthSync { /// Get sync status fn status(&self) -> SyncStatus { - self.sync_handler.sync.write().status() + self.eth_handler.sync.write().status() } /// Get sync peers fn peers(&self) -> Vec { // TODO: [rob] LES peers/peer info self.network.with_context_eval(self.subprotocol_name, |context| { - let sync_io = NetSyncIo::new(context, &*self.sync_handler.chain, &*self.sync_handler.snapshot_service, &self.sync_handler.overlay); - self.sync_handler.sync.write().peers(&sync_io) + let sync_io = NetSyncIo::new(context, &*self.eth_handler.chain, &*self.eth_handler.snapshot_service, &self.eth_handler.overlay); + self.eth_handler.sync.write().peers(&sync_io) }).unwrap_or(Vec::new()) } @@ -218,7 +223,7 @@ impl SyncProvider for EthSync { } fn transactions_stats(&self) -> BTreeMap { - let sync = self.sync_handler.sync.read(); + let sync = self.eth_handler.sync.read(); sync.transactions_stats() .iter() .map(|(hash, stats)| (*hash, stats.into())) @@ -277,19 +282,21 @@ impl ChainNotify for EthSync { enacted: Vec, retracted: Vec, sealed: Vec, + proposed: Vec, _duration: u64) { use light::net::Announcement; self.network.with_context(self.subprotocol_name, |context| { - let mut sync_io = NetSyncIo::new(context, &*self.sync_handler.chain, &*self.sync_handler.snapshot_service, &self.sync_handler.overlay); - self.sync_handler.sync.write().chain_new_blocks( + 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().chain_new_blocks( &mut sync_io, &imported, &invalid, &enacted, &retracted, - &sealed); + &sealed, + &proposed); }); self.network.with_context(self.light_subprotocol_name, |context| { @@ -297,8 +304,8 @@ impl ChainNotify for EthSync { Some(lp) => lp, None => return, }; - - let chain_info = self.sync_handler.chain.chain_info(); + + let chain_info = self.eth_handler.chain.chain_info(); light_proto.make_announcement(context, Announcement { head_hash: chain_info.best_block_hash, head_num: chain_info.best_block_number, @@ -318,10 +325,10 @@ impl ChainNotify for EthSync { Err(err) => warn!("Error starting network: {}", err), _ => {}, } - self.network.register_protocol(self.sync_handler.clone(), self.subprotocol_name, ETH_PACKET_COUNT, &[62u8, 63u8]) + 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.sync_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)); // register the light protocol. @@ -332,12 +339,19 @@ impl ChainNotify for EthSync { } fn stop(&self) { - self.sync_handler.snapshot_service.abort_restore(); + self.eth_handler.snapshot_service.abort_restore(); self.network.stop().unwrap_or_else(|e| warn!("Error stopping network: {:?}", e)); } + fn broadcast(&self, message: Vec) { + 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()); + }); + } + fn transactions_received(&self, hashes: Vec, peer_id: PeerId) { - let mut sync = self.sync_handler.sync.write(); + let mut sync = self.eth_handler.sync.write(); sync.transactions_received(hashes, peer_id); } } @@ -399,8 +413,8 @@ impl ManageNetwork for EthSync { fn stop_network(&self) { self.network.with_context(self.subprotocol_name, |context| { - let mut sync_io = NetSyncIo::new(context, &*self.sync_handler.chain, &*self.sync_handler.snapshot_service, &self.sync_handler.overlay); - self.sync_handler.sync.write().abort(&mut sync_io); + 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().abort(&mut sync_io); }); if let Some(light_proto) = self.light_proto.as_ref() { diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 350a42d0e..d3638f35d 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -113,6 +113,7 @@ type PacketDecodeError = DecoderError; const PROTOCOL_VERSION_63: u8 = 63; const PROTOCOL_VERSION_62: u8 = 62; 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; @@ -149,8 +150,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; @@ -615,13 +617,15 @@ 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 && peer.protocol_version != PROTOCOL_VERSION_62) { + if (warp_protocol && peer.protocol_version != PROTOCOL_VERSION_1 && peer.protocol_version != PROTOCOL_VERSION_2) || (!warp_protocol && peer.protocol_version != PROTOCOL_VERSION_63 && peer.protocol_version != PROTOCOL_VERSION_62) { io.disable_peer(peer_id); trace!(target: "sync", "Peer {} unsupported eth protocol ({})", peer_id, peer.protocol_version); return Ok(()); } self.peers.insert(peer_id.clone(), peer); + // Don't activate peer immediatelly when searching for common block. + // Let the current sync round complete first. 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 { @@ -1422,8 +1426,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 { io.eth_protocol_version(peer) }; + 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(); @@ -1672,7 +1677,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(()) @@ -1799,44 +1804,51 @@ 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 + /// creates given hash 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 have different blocks than our chain + fn get_lagging_peers(&mut self, chain_info: &BlockChainInfo) -> 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 - } - }, - _ => None + self + .peers + .iter_mut() + .filter_map(|(&id, ref mut peer_info)| { + trace!(target: "sync", "Checking peer our best {} their best {}", latest_hash, peer_info.latest_hash); + if peer_info.latest_hash != latest_hash { + Some(id) + } else { + None + } }) .collect::>() } - fn select_random_lagging_peers(&mut self, peers: &[PeerId]) -> Vec { - use rand::Rng; + fn select_random_peers(peers: &[PeerId]) -> Vec { // take sqrt(x) peers let mut peers = peers.to_vec(); - let mut count = (self.peers.len() as f64).powf(0.5).round() as usize; + let mut count = (peers.len() as f64).powf(0.5).round() as usize; count = min(count, MAX_PEERS_PROPAGATION); count = max(count, MIN_PEERS_PROPAGATION); ::rand::thread_rng().shuffle(&mut peers); @@ -1844,16 +1856,20 @@ 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 { + fn get_consensus_peers(&self) -> Vec { + self.peers.iter().filter_map(|(id, p)| if p.protocol_version == PROTOCOL_VERSION_2 { Some(*id) } else { None }).collect() + } + + /// 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); } @@ -1971,10 +1987,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_lagging_peers(&chain_info); if sealed.is_empty() { let hashes = self.propagate_new_hashes(&chain_info, io, &peers); - peers = self.select_random_lagging_peers(&peers); + peers = ChainSync::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); @@ -1989,6 +2005,21 @@ 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 peers = self.get_consensus_peers(); + trace!(target: "sync", "Sending proposed blocks to {:?}", peers); + for block in proposed { + let rlp = ChainSync::create_block_rlp( + block, + io.chain().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); @@ -1996,15 +2027,32 @@ 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"); 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> { + trace!(target: "sync", "Received consensus packet from {:?}", peer_id); + 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 = ChainSync::select_random_peers(&self.get_consensus_peers()); + 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)] @@ -2067,9 +2115,9 @@ mod tests { #[test] fn return_receipts_empty() { let mut client = TestBlockChainClient::new(); - let mut queue = VecDeque::new(); + let queue = RwLock::new(VecDeque::new()); let ss = TestSnapshotService::new(); - let io = TestIo::new(&mut client, &ss, &mut queue, None); + let io = TestIo::new(&mut client, &ss, &queue, None); let result = ChainSync::return_receipts(&io, &UntrustedRlp::new(&[0xc0]), 0); @@ -2079,10 +2127,10 @@ mod tests { #[test] fn return_receipts() { let mut client = TestBlockChainClient::new(); - let mut queue = VecDeque::new(); + let queue = RwLock::new(VecDeque::new()); let sync = dummy_sync_with_peer(H256::new(), &client); let ss = TestSnapshotService::new(); - let mut io = TestIo::new(&mut client, &ss, &mut queue, None); + let mut io = TestIo::new(&mut client, &ss, &queue, None); let mut receipt_list = RlpStream::new_list(4); receipt_list.append(&H256::from("0000000000000000000000000000000000000000000000005555555555555555")); @@ -2103,7 +2151,7 @@ mod tests { io.sender = Some(2usize); ChainSync::dispatch_packet(&RwLock::new(sync), &mut io, 0usize, super::GET_RECEIPTS_PACKET, &receipts_request); - assert_eq!(1, io.queue.len()); + assert_eq!(1, io.packets.len()); } #[test] @@ -2136,9 +2184,9 @@ mod tests { let headers: Vec<_> = blocks.iter().map(|b| Rlp::new(b).at(0).as_raw().to_vec()).collect(); let hashes: Vec<_> = headers.iter().map(|h| HeaderView::new(h).sha3()).collect(); - let mut queue = VecDeque::new(); + let queue = RwLock::new(VecDeque::new()); let ss = TestSnapshotService::new(); - let io = TestIo::new(&mut client, &ss, &mut queue, None); + let io = TestIo::new(&mut client, &ss, &queue, None); let unknown: H256 = H256::new(); let result = ChainSync::return_block_headers(&io, &UntrustedRlp::new(&make_hash_req(&unknown, 1, 0, false)), 0); @@ -2174,10 +2222,10 @@ mod tests { #[test] fn return_nodes() { let mut client = TestBlockChainClient::new(); - let mut queue = VecDeque::new(); + let queue = RwLock::new(VecDeque::new()); let sync = dummy_sync_with_peer(H256::new(), &client); let ss = TestSnapshotService::new(); - let mut io = TestIo::new(&mut client, &ss, &mut queue, None); + let mut io = TestIo::new(&mut client, &ss, &queue, None); let mut node_list = RlpStream::new_list(3); node_list.append(&H256::from("0000000000000000000000000000000000000000000000005555555555555555")); @@ -2200,7 +2248,7 @@ mod tests { io.sender = Some(2usize); ChainSync::dispatch_packet(&RwLock::new(sync), &mut io, 0usize, super::GET_NODE_DATA_PACKET, &node_request); - assert_eq!(1, io.queue.len()); + assert_eq!(1, io.packets.len()); } fn dummy_sync_with_peer(peer_latest_hash: H256, client: &BlockChainClient) -> ChainSync { @@ -2231,15 +2279,12 @@ mod tests { fn finds_lagging_peers() { let mut client = TestBlockChainClient::new(); client.add_blocks(100, EachBlockWith::Uncle); - let mut queue = VecDeque::new(); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(10), &client); let chain_info = client.chain_info(); - 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_lagging_peers(&chain_info); - assert_eq!(1, lagging_peers.len()) + assert_eq!(1, lagging_peers.len()); } #[test] @@ -2263,62 +2308,99 @@ mod tests { fn sends_new_hashes_to_lagging_peer() { let mut client = TestBlockChainClient::new(); client.add_blocks(100, EachBlockWith::Uncle); - let mut queue = VecDeque::new(); + let queue = RwLock::new(VecDeque::new()); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let chain_info = client.chain_info(); let ss = TestSnapshotService::new(); - let mut io = TestIo::new(&mut client, &ss, &mut queue, None); + let mut io = TestIo::new(&mut client, &ss, &queue, None); - let peers = sync.get_lagging_peers(&chain_info, &io); + let peers = sync.get_lagging_peers(&chain_info); let peer_count = sync.propagate_new_hashes(&chain_info, &mut io, &peers); // 1 message should be send - assert_eq!(1, io.queue.len()); + assert_eq!(1, io.packets.len()); // 1 peer should be updated assert_eq!(1, peer_count); // NEW_BLOCK_HASHES_PACKET - assert_eq!(0x01, io.queue[0].packet_id); + assert_eq!(0x01, io.packets[0].packet_id); } #[test] fn sends_latest_block_to_lagging_peer() { let mut client = TestBlockChainClient::new(); client.add_blocks(100, EachBlockWith::Uncle); - let mut queue = VecDeque::new(); + let queue = RwLock::new(VecDeque::new()); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); 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 mut io = TestIo::new(&mut client, &ss, &queue, None); + let peers = sync.get_lagging_peers(&chain_info); let peer_count = sync.propagate_blocks(&chain_info, &mut io, &[], &peers); // 1 message should be send - assert_eq!(1, io.queue.len()); + assert_eq!(1, io.packets.len()); // 1 peer should be updated assert_eq!(1, peer_count); // NEW_BLOCK_PACKET - assert_eq!(0x07, io.queue[0].packet_id); + assert_eq!(0x07, io.packets[0].packet_id); } #[test] fn sends_sealed_block() { let mut client = TestBlockChainClient::new(); client.add_blocks(100, EachBlockWith::Uncle); - let mut queue = VecDeque::new(); + let queue = RwLock::new(VecDeque::new()); let hash = client.block_hash(BlockId::Number(99)).unwrap(); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let chain_info = client.chain_info(); let ss = TestSnapshotService::new(); - let mut io = TestIo::new(&mut client, &ss, &mut queue, None); - let peers = sync.get_lagging_peers(&chain_info, &io); + let mut io = TestIo::new(&mut client, &ss, &queue, None); + let peers = sync.get_lagging_peers(&chain_info); let peer_count = sync.propagate_blocks(&chain_info, &mut io, &[hash.clone()], &peers); // 1 message should be send - assert_eq!(1, io.queue.len()); + assert_eq!(1, io.packets.len()); // 1 peer should be updated assert_eq!(1, peer_count); // NEW_BLOCK_PACKET - assert_eq!(0x07, io.queue[0].packet_id); + assert_eq!(0x07, io.packets[0].packet_id); + } + + #[test] + fn sends_proposed_block() { + let mut client = TestBlockChainClient::new(); + client.add_blocks(2, EachBlockWith::Uncle); + let queue = RwLock::new(VecDeque::new()); + let block = client.block(BlockId::Latest).unwrap(); + let mut sync = ChainSync::new(SyncConfig::default(), &client); + sync.peers.insert(0, + PeerInfo { + // Messaging protocol + protocol_version: 2, + genesis: H256::zero(), + network_id: 0, + latest_hash: client.block_hash_delta_minus(1), + difficulty: None, + asking: PeerAsking::Nothing, + asking_blocks: Vec::new(), + asking_hash: None, + ask_time: 0, + last_sent_transactions: HashSet::new(), + expired: false, + confirmation: super::ForkConfirmation::Confirmed, + snapshot_number: None, + snapshot_hash: None, + asking_snapshot_data: None, + block_set: None, + }); + let ss = TestSnapshotService::new(); + let mut io = TestIo::new(&mut client, &ss, &queue, None); + sync.propagate_proposed_blocks(&mut io, &[block]); + + // 1 message should be sent + assert_eq!(1, io.packets.len()); + // NEW_BLOCK_PACKET + assert_eq!(0x07, io.packets[0].packet_id); } #[test] @@ -2327,25 +2409,25 @@ mod tests { client.add_blocks(100, EachBlockWith::Uncle); client.insert_transaction_to_queue(); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(1), &client); - let mut queue = VecDeque::new(); + let queue = RwLock::new(VecDeque::new()); let ss = TestSnapshotService::new(); - let mut io = TestIo::new(&mut client, &ss, &mut queue, None); + let mut io = TestIo::new(&mut client, &ss, &queue, None); let peer_count = sync.propagate_new_transactions(&mut io); // 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); // 1 message should be send - assert_eq!(1, io.queue.len()); + assert_eq!(1, io.packets.len()); // 1 peer should be updated but only once assert_eq!(1, peer_count); assert_eq!(0, peer_count2); assert_eq!(0, peer_count3); // TRANSACTIONS_PACKET - assert_eq!(0x02, io.queue[0].packet_id); + assert_eq!(0x02, io.packets[0].packet_id); } #[test] @@ -2354,21 +2436,21 @@ mod tests { client.add_blocks(100, EachBlockWith::Uncle); client.insert_transaction_to_queue(); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(1), &client); - let mut queue = VecDeque::new(); + let queue = RwLock::new(VecDeque::new()); let ss = TestSnapshotService::new(); - let mut io = TestIo::new(&mut client, &ss, &mut queue, None); + let mut io = TestIo::new(&mut client, &ss, &queue, None); 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()); + assert_eq!(2, io.packets.len()); // 1 peer should receive the message assert_eq!(1, peer_count); // TRANSACTIONS_PACKET - assert_eq!(0x02, io.queue[0].packet_id); - assert_eq!(0x02, io.queue[1].packet_id); + assert_eq!(0x02, io.packets[0].packet_id); + assert_eq!(0x02, io.packets[1].packet_id); } #[test] @@ -2377,31 +2459,34 @@ mod tests { client.add_blocks(100, EachBlockWith::Uncle); client.insert_transaction_to_queue(); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(1), &client); - let mut queue = VecDeque::new(); + let queue = RwLock::new(VecDeque::new()); let ss = TestSnapshotService::new(); // should sent some { - let mut io = TestIo::new(&mut client, &ss, &mut queue, None); + let mut io = TestIo::new(&mut client, &ss, &queue, None); let peer_count = sync.propagate_new_transactions(&mut io); - assert_eq!(1, io.queue.len()); + assert_eq!(1, io.packets.len()); assert_eq!(1, peer_count); } // Insert some more client.insert_transaction_to_queue(); - let mut io = TestIo::new(&mut client, &ss, &mut queue, None); - // Propagate new transactions - let peer_count2 = sync.propagate_new_transactions(&mut io); - // And now the peer should have all transactions - let peer_count3 = sync.propagate_new_transactions(&mut io); + let (peer_count2, peer_count3) = { + let mut io = TestIo::new(&mut client, &ss, &queue, None); + // Propagate new transactions + let peer_count2 = sync.propagate_new_transactions(&mut io); + // And now the peer should have all transactions + let peer_count3 = sync.propagate_new_transactions(&mut io); + (peer_count2, peer_count3) + }; // 2 message should be send (in total) - assert_eq!(2, io.queue.len()); + assert_eq!(2, queue.read().len()); // 1 peer should be updated but only once after inserting new transaction assert_eq!(1, peer_count2); assert_eq!(0, peer_count3); // TRANSACTIONS_PACKET - assert_eq!(0x02, io.queue[0].packet_id); - assert_eq!(0x02, io.queue[1].packet_id); + assert_eq!(0x02, queue.read()[0].packet_id); + assert_eq!(0x02, queue.read()[1].packet_id); } #[test] @@ -2410,9 +2495,9 @@ mod tests { client.add_blocks(100, EachBlockWith::Uncle); client.insert_transaction_to_queue(); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(1), &client); - let mut queue = VecDeque::new(); + let queue = RwLock::new(VecDeque::new()); let ss = TestSnapshotService::new(); - let mut io = TestIo::new(&mut client, &ss, &mut queue, None); + let mut io = TestIo::new(&mut client, &ss, &queue, None); sync.propagate_new_transactions(&mut io); let stats = sync.transactions_stats(); @@ -2426,11 +2511,11 @@ mod tests { let block_data = get_dummy_block(11, client.chain_info().best_block_hash); - let mut queue = VecDeque::new(); + let queue = RwLock::new(VecDeque::new()); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); //sync.have_common_block = true; let ss = TestSnapshotService::new(); - let mut io = TestIo::new(&mut client, &ss, &mut queue, None); + let mut io = TestIo::new(&mut client, &ss, &queue, None); let block = UntrustedRlp::new(&block_data); @@ -2446,10 +2531,10 @@ mod tests { let block_data = get_dummy_blocks(11, client.chain_info().best_block_hash); - let mut queue = VecDeque::new(); + let queue = RwLock::new(VecDeque::new()); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let ss = TestSnapshotService::new(); - let mut io = TestIo::new(&mut client, &ss, &mut queue, None); + let mut io = TestIo::new(&mut client, &ss, &queue, None); let block = UntrustedRlp::new(&block_data); @@ -2462,10 +2547,10 @@ mod tests { fn handles_peer_new_block_empty() { let mut client = TestBlockChainClient::new(); client.add_blocks(10, EachBlockWith::Uncle); - let mut queue = VecDeque::new(); + let queue = RwLock::new(VecDeque::new()); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let ss = TestSnapshotService::new(); - let mut io = TestIo::new(&mut client, &ss, &mut queue, None); + let mut io = TestIo::new(&mut client, &ss, &queue, None); let empty_data = vec![]; let block = UntrustedRlp::new(&empty_data); @@ -2479,10 +2564,10 @@ mod tests { fn handles_peer_new_hashes() { let mut client = TestBlockChainClient::new(); client.add_blocks(10, EachBlockWith::Uncle); - let mut queue = VecDeque::new(); + let queue = RwLock::new(VecDeque::new()); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let ss = TestSnapshotService::new(); - let mut io = TestIo::new(&mut client, &ss, &mut queue, None); + let mut io = TestIo::new(&mut client, &ss, &queue, None); let hashes_data = get_dummy_hashes(); let hashes_rlp = UntrustedRlp::new(&hashes_data); @@ -2496,10 +2581,10 @@ mod tests { fn handles_peer_new_hashes_empty() { let mut client = TestBlockChainClient::new(); client.add_blocks(10, EachBlockWith::Uncle); - let mut queue = VecDeque::new(); + let queue = RwLock::new(VecDeque::new()); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let ss = TestSnapshotService::new(); - let mut io = TestIo::new(&mut client, &ss, &mut queue, None); + let mut io = TestIo::new(&mut client, &ss, &queue, None); let empty_hashes_data = vec![]; let hashes_rlp = UntrustedRlp::new(&empty_hashes_data); @@ -2515,16 +2600,16 @@ mod tests { fn hashes_rlp_mutually_acceptable() { let mut client = TestBlockChainClient::new(); client.add_blocks(100, EachBlockWith::Uncle); - let mut queue = VecDeque::new(); + let queue = RwLock::new(VecDeque::new()); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let chain_info = client.chain_info(); let ss = TestSnapshotService::new(); - let mut io = TestIo::new(&mut client, &ss, &mut queue, None); + let mut io = TestIo::new(&mut client, &ss, &queue, None); - let peers = sync.get_lagging_peers(&chain_info, &io); + let peers = sync.get_lagging_peers(&chain_info); sync.propagate_new_hashes(&chain_info, &mut io, &peers); - let data = &io.queue[0].data.clone(); + let data = &io.packets[0].data.clone(); let result = sync.on_peer_new_hashes(&mut io, 0, &UntrustedRlp::new(data)); assert!(result.is_ok()); } @@ -2535,16 +2620,16 @@ mod tests { fn block_rlp_mutually_acceptable() { let mut client = TestBlockChainClient::new(); client.add_blocks(100, EachBlockWith::Uncle); - let mut queue = VecDeque::new(); + let queue = RwLock::new(VecDeque::new()); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let chain_info = client.chain_info(); let ss = TestSnapshotService::new(); - let mut io = TestIo::new(&mut client, &ss, &mut queue, None); + let mut io = TestIo::new(&mut client, &ss, &queue, None); - let peers = sync.get_lagging_peers(&chain_info, &io); + let peers = sync.get_lagging_peers(&chain_info); sync.propagate_blocks(&chain_info, &mut io, &[], &peers); - let data = &io.queue[0].data.clone(); + let data = &io.packets[0].data.clone(); let result = sync.on_peer_new_block(&mut io, 0, &UntrustedRlp::new(data)); assert!(result.is_ok()); } @@ -2572,11 +2657,11 @@ mod tests { // when { - let mut queue = VecDeque::new(); + let queue = RwLock::new(VecDeque::new()); let ss = TestSnapshotService::new(); - let mut io = TestIo::new(&mut client, &ss, &mut queue, None); + let mut io = TestIo::new(&mut client, &ss, &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); } @@ -2587,11 +2672,11 @@ mod tests { client.set_nonce(view.transactions()[0].sender().unwrap(), U256::from(1)); } { - let mut queue = VecDeque::new(); + let queue = RwLock::new(VecDeque::new()); let ss = TestSnapshotService::new(); - let mut io = TestIo::new(&mut client, &ss, &mut queue, None); + let mut io = TestIo::new(&client, &ss, &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 @@ -2612,15 +2697,15 @@ mod tests { let good_blocks = vec![client.block_hash_delta_minus(2)]; let retracted_blocks = vec![client.block_hash_delta_minus(1)]; - let mut queue = VecDeque::new(); + let queue = RwLock::new(VecDeque::new()); let ss = TestSnapshotService::new(); - let mut io = TestIo::new(&mut client, &ss, &mut queue, None); + let mut io = TestIo::new(&mut client, &ss, &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/chain.rs b/sync/src/tests/chain.rs index 3d0c17fff..6d1ffaf83 100644 --- a/sync/src/tests/chain.rs +++ b/sync/src/tests/chain.rs @@ -101,7 +101,7 @@ 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 = Arc::new(TestBlockChainClient::new_with_extra_data(b"fork".to_vec())); net.peer(0).chain.add_blocks(50, EachBlockWith::Nothing); net.peer(1).chain.add_blocks(10, EachBlockWith::Nothing); net.peer(2).chain.add_blocks(10, EachBlockWith::Nothing); diff --git a/sync/src/tests/consensus.rs b/sync/src/tests/consensus.rs index b96997d1e..c1fa8fce7 100644 --- a/sync/src/tests/consensus.rs +++ b/sync/src/tests/consensus.rs @@ -15,7 +15,9 @@ // along with Parity. If not, see . use util::*; -use ethcore::client::BlockChainClient; +use io::{IoHandler, IoContext, IoChannel}; +use ethcore::client::{BlockChainClient, Client, MiningBlockChainClient}; +use ethcore::service::ClientIoMessage; use ethcore::spec::Spec; use ethcore::miner::MinerService; use ethcore::transaction::*; @@ -24,55 +26,171 @@ use ethkey::KeyPair; use super::helpers::*; use SyncConfig; -#[test] -fn test_authority_round() { - ::env_logger::init().ok(); +struct TestIoHandler { + client: Arc, +} - let s1 = KeyPair::from_secret("1".sha3()).unwrap(); - let s2 = KeyPair::from_secret("0".sha3()).unwrap(); - let spec_factory = || { - let spec = Spec::new_test_round(); - let account_provider = AccountProvider::transient_provider(); - account_provider.insert_account(s1.secret().clone(), "").unwrap(); - account_provider.insert_account(s2.secret().clone(), "").unwrap(); - spec.engine.register_account_provider(Arc::new(account_provider)); - spec - }; - let mut net = TestNet::new_with_spec(2, SyncConfig::default(), spec_factory); - let mut net = &mut *net; - // Push transaction to both clients. Only one of them gets lucky to mine a block. - net.peer(0).chain.miner().set_author(s1.address()); - net.peer(0).chain.engine().set_signer(s1.address(), "".to_owned()); - net.peer(1).chain.miner().set_author(s2.address()); - net.peer(1).chain.engine().set_signer(s2.address(), "".to_owned()); - let tx1 = Transaction { - nonce: 0.into(), +impl IoHandler for TestIoHandler { + fn message(&self, _io: &IoContext, net_message: &ClientIoMessage) { + match *net_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_consensus_message(message.clone()), + ClientIoMessage::NewMessage(ref message) => if let Err(e) = self.client.engine().handle_message(message) { + panic!("Invalid message received: {}", e); + }, + _ => {} // ignore other messages + } + } +} + +fn new_tx(secret: &H256, nonce: U256) -> SignedTransaction { + Transaction { + nonce: nonce.into(), gas_price: 0.into(), gas: 21000.into(), action: Action::Call(Address::default()), value: 0.into(), data: Vec::new(), - }.sign(s1.secret(), None); - // exhange statuses - net.sync_steps(5); - net.peer(0).chain.miner().import_own_transaction(&net.peer(0).chain, tx1).unwrap(); + }.sign(secret, None) +} + +#[test] +fn authority_round() { + let s0 = KeyPair::from_secret("1".sha3()).unwrap(); + let s1 = KeyPair::from_secret("0".sha3()).unwrap(); + let spec_factory = || { + let spec = Spec::new_test_round(); + let account_provider = AccountProvider::transient_provider(); + account_provider.insert_account(s0.secret().clone(), "").unwrap(); + account_provider.insert_account(s1.secret().clone(), "").unwrap(); + spec.engine.register_account_provider(Arc::new(account_provider)); + spec + }; + let mut net = TestNet::with_spec(2, SyncConfig::default(), spec_factory); + let mut net = &mut *net; + let io_handler0: Arc> = Arc::new(TestIoHandler { client: net.peer(0).chain.clone() }); + let io_handler1: Arc> = Arc::new(TestIoHandler { client: net.peer(1).chain.clone() }); + // Push transaction to both clients. Only one of them gets lucky to produce a block. + net.peer(0).chain.miner().set_engine_signer(s0.address(), "".to_owned()).unwrap(); + net.peer(1).chain.miner().set_engine_signer(s1.address(), "".to_owned()).unwrap(); + net.peer(0).chain.engine().register_message_channel(IoChannel::to_handler(Arc::downgrade(&io_handler0))); + net.peer(1).chain.engine().register_message_channel(IoChannel::to_handler(Arc::downgrade(&io_handler1))); + net.peer(0).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler1))); + net.peer(1).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler0))); + // exchange statuses + net.sync(); + // Trigger block proposal + net.peer(0).chain.miner().import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 0.into())).unwrap(); + // Sync a block net.sync(); assert_eq!(net.peer(0).chain.chain_info().best_block_number, 1); assert_eq!(net.peer(1).chain.chain_info().best_block_number, 1); - let tx2 = Transaction { - nonce: 0.into(), - gas_price: 0.into(), - gas: 21000.into(), - action: Action::Call(Address::default()), - value: 0.into(), - data: Vec::new(), - }.sign(s2.secret(), None); - net.peer(1).chain.miner().import_own_transaction(&net.peer(1).chain, tx2).unwrap(); + net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 0.into())).unwrap(); + // Move to next proposer step + net.peer(0).chain.engine().step(); net.peer(1).chain.engine().step(); - net.peer(1).chain.miner().update_sealing(&net.peer(1).chain); net.sync(); assert_eq!(net.peer(0).chain.chain_info().best_block_number, 2); assert_eq!(net.peer(1).chain.chain_info().best_block_number, 2); + + // Fork the network + net.peer(0).chain.miner().import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 1.into())).unwrap(); + net.peer(0).chain.engine().step(); + net.peer(1).chain.engine().step(); + assert_eq!(net.peer(0).chain.chain_info().best_block_number, 3); + net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 1.into())).unwrap(); + net.peer(0).chain.engine().step(); + net.peer(1).chain.engine().step(); + assert_eq!(net.peer(1).chain.chain_info().best_block_number, 3); + // Reorg to the correct one. + net.sync(); + let ci0 = net.peer(0).chain.chain_info(); + let ci1 = net.peer(1).chain.chain_info(); + assert_eq!(ci0.best_block_number, 3); + assert_eq!(ci1.best_block_number, 3); + assert_eq!(ci0.best_block_hash, ci1.best_block_hash); } +#[test] +fn tendermint() { + let s0 = KeyPair::from_secret("1".sha3()).unwrap(); + let s1 = KeyPair::from_secret("0".sha3()).unwrap(); + let spec_factory = || { + let spec = Spec::new_test_tendermint(); + let account_provider = AccountProvider::transient_provider(); + account_provider.insert_account(s0.secret().clone(), "").unwrap(); + account_provider.insert_account(s1.secret().clone(), "").unwrap(); + spec.engine.register_account_provider(Arc::new(account_provider)); + spec + }; + let mut net = TestNet::with_spec(2, SyncConfig::default(), spec_factory); + let mut net = &mut *net; + let io_handler0: Arc> = Arc::new(TestIoHandler { client: net.peer(0).chain.clone() }); + let io_handler1: Arc> = Arc::new(TestIoHandler { client: net.peer(1).chain.clone() }); + // Push transaction to both clients. Only one of them issues a proposal. + net.peer(0).chain.miner().set_engine_signer(s0.address(), "".to_owned()).unwrap(); + trace!(target: "poa", "Peer 0 is {}.", s0.address()); + net.peer(1).chain.miner().set_engine_signer(s1.address(), "".to_owned()).unwrap(); + trace!(target: "poa", "Peer 1 is {}.", s1.address()); + net.peer(0).chain.engine().register_message_channel(IoChannel::to_handler(Arc::downgrade(&io_handler0))); + net.peer(1).chain.engine().register_message_channel(IoChannel::to_handler(Arc::downgrade(&io_handler1))); + net.peer(0).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler0))); + net.peer(1).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler1))); + // Exhange statuses + net.sync(); + // Propose + net.peer(0).chain.miner().import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 0.into())).unwrap(); + net.sync(); + // Propose timeout, synchronous for now + net.peer(0).chain.engine().step(); + net.peer(1).chain.engine().step(); + // Prevote, precommit and commit + net.sync(); + + assert_eq!(net.peer(0).chain.chain_info().best_block_number, 1); + assert_eq!(net.peer(1).chain.chain_info().best_block_number, 1); + + net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 0.into())).unwrap(); + // Commit timeout + net.peer(0).chain.engine().step(); + net.peer(1).chain.engine().step(); + // Propose + net.sync(); + // Propose timeout + net.peer(0).chain.engine().step(); + net.peer(1).chain.engine().step(); + // Prevote, precommit and commit + net.sync(); + assert_eq!(net.peer(0).chain.chain_info().best_block_number, 2); + assert_eq!(net.peer(1).chain.chain_info().best_block_number, 2); + + net.peer(0).chain.miner().import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 1.into())).unwrap(); + net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 1.into())).unwrap(); + // Peers get disconnected. + // Commit + net.peer(0).chain.engine().step(); + net.peer(1).chain.engine().step(); + // Propose + net.peer(0).chain.engine().step(); + net.peer(1).chain.engine().step(); +net.peer(0).chain.miner().import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 2.into())).unwrap(); + net.peer(1).chain.miner().import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 2.into())).unwrap(); + // Send different prevotes + net.sync(); + // Prevote timeout + net.peer(0).chain.engine().step(); + net.peer(1).chain.engine().step(); + // Precommit and commit + net.sync(); + // Propose timeout + net.peer(0).chain.engine().step(); + net.peer(1).chain.engine().step(); + net.sync(); + let ci0 = net.peer(0).chain.chain_info(); + let ci1 = net.peer(1).chain.chain_info(); + assert_eq!(ci0.best_block_number, 3); + assert_eq!(ci1.best_block_number, 3); + assert_eq!(ci0.best_block_hash, ci1.best_block_hash); +} diff --git a/sync/src/tests/helpers.rs b/sync/src/tests/helpers.rs index af9118431..6ad9965cd 100644 --- a/sync/src/tests/helpers.rs +++ b/sync/src/tests/helpers.rs @@ -45,14 +45,15 @@ impl FlushingBlockChainClient for TestBlockChainClient {} pub struct TestIo<'p, C> where C: FlushingBlockChainClient, C: 'p { pub chain: &'p C, pub snapshot_service: &'p TestSnapshotService, - pub queue: &'p mut VecDeque, + pub queue: &'p RwLock>, pub sender: Option, pub to_disconnect: HashSet, + pub packets: Vec, overlay: RwLock>, } impl<'p, C> TestIo<'p, C> where C: FlushingBlockChainClient, C: 'p { - pub fn new(chain: &'p C, ss: &'p TestSnapshotService, queue: &'p mut VecDeque, sender: Option) -> TestIo<'p, C> { + pub fn new(chain: &'p C, ss: &'p TestSnapshotService, queue: &'p RwLock>, sender: Option) -> TestIo<'p, C> { TestIo { chain: chain, snapshot_service: ss, @@ -60,10 +61,17 @@ impl<'p, C> TestIo<'p, C> where C: FlushingBlockChainClient, C: 'p { sender: sender, to_disconnect: HashSet::new(), overlay: RwLock::new(HashMap::new()), + packets: Vec::new(), } } } +impl<'p, C> Drop for TestIo<'p, C> where C: FlushingBlockChainClient, C: 'p { + fn drop(&mut self) { + self.queue.write().extend(self.packets.drain(..)); + } +} + impl<'p, C> SyncIo for TestIo<'p, C> where C: FlushingBlockChainClient, C: 'p { fn disable_peer(&mut self, peer_id: PeerId) { self.disconnect_peer(peer_id); @@ -78,7 +86,7 @@ impl<'p, C> SyncIo for TestIo<'p, C> where C: FlushingBlockChainClient, C: 'p { } fn respond(&mut self, packet_id: PacketId, data: Vec) -> Result<(), NetworkError> { - self.queue.push_back(TestPacket { + self.packets.push(TestPacket { data: data, packet_id: packet_id, recipient: self.sender.unwrap() @@ -87,7 +95,7 @@ impl<'p, C> SyncIo for TestIo<'p, C> where C: FlushingBlockChainClient, C: 'p { } fn send(&mut self, peer_id: PeerId, packet_id: PacketId, data: Vec) -> Result<(), NetworkError> { - self.queue.push_back(TestPacket { + self.packets.push(TestPacket { data: data, packet_id: packet_id, recipient: peer_id, @@ -100,7 +108,7 @@ impl<'p, C> SyncIo for TestIo<'p, C> where C: FlushingBlockChainClient, C: 'p { } fn chain(&self) -> &BlockChainClient { - self.chain + &*self.chain } fn snapshot_service(&self) -> &SnapshotService { @@ -131,7 +139,7 @@ pub struct TestPacket { } pub struct TestPeer where C: FlushingBlockChainClient { - pub chain: C, + pub chain: Arc, pub snapshot_service: Arc, pub sync: RwLock, pub queue: RwLock>, @@ -167,7 +175,7 @@ impl TestNet { net.peers.push(Arc::new(TestPeer { sync: RwLock::new(sync), snapshot_service: ss, - chain: chain, + chain: Arc::new(chain), queue: RwLock::new(VecDeque::new()), })); } @@ -176,7 +184,7 @@ impl TestNet { } impl TestNet { - pub fn new_with_spec(n: usize, config: SyncConfig, spec_factory: F) -> GuardedTempResult> + pub fn with_spec(n: usize, config: SyncConfig, spec_factory: F) -> GuardedTempResult> where F: Fn() -> Spec { let mut net = TestNet { @@ -192,17 +200,17 @@ impl TestNet { let db_config = DatabaseConfig::with_columns(NUM_COLUMNS); let spec = spec_factory(); - let client = Arc::try_unwrap(EthcoreClient::new( + let client = EthcoreClient::new( ClientConfig::default(), &spec, client_dir.as_path(), Arc::new(Miner::with_spec(&spec)), IoChannel::disconnected(), &db_config - ).unwrap()).ok().unwrap(); + ).unwrap(); let ss = Arc::new(TestSnapshotService::new()); - let sync = ChainSync::new(config.clone(), &client); + let sync = ChainSync::new(config.clone(), &*client); let peer = Arc::new(TestPeer { sync: RwLock::new(sync), snapshot_service: ss, @@ -229,33 +237,38 @@ impl TestNet where C: FlushingBlockChainClient { } pub fn start(&mut self) { + if self.started { + return; + } for peer in 0..self.peers.len() { for client in 0..self.peers.len() { if peer != client { let p = &self.peers[peer]; - p.sync.write().update_targets(&p.chain); - p.sync.write().on_peer_connected(&mut TestIo::new(&p.chain, &p.snapshot_service, &mut p.queue.write(), Some(client as PeerId)), client as PeerId); + p.sync.write().update_targets(&*p.chain); + p.sync.write().on_peer_connected(&mut TestIo::new(&*p.chain, &p.snapshot_service, &p.queue, Some(client as PeerId)), client as PeerId); } } } + self.started = true; } pub fn sync_step(&mut self) { for peer in 0..self.peers.len() { + self.peers[peer].chain.flush(); let packet = self.peers[peer].queue.write().pop_front(); if let Some(packet) = packet { let disconnecting = { let p = &self.peers[packet.recipient]; - let mut queue = p.queue.write(); trace!("--- {} -> {} ---", peer, packet.recipient); let to_disconnect = { - let mut io = TestIo::new(&p.chain, &p.snapshot_service, &mut queue, Some(peer as PeerId)); + let mut io = TestIo::new(&*p.chain, &p.snapshot_service, &p.queue, Some(peer as PeerId)); ChainSync::dispatch_packet(&p.sync, &mut io, peer as PeerId, packet.packet_id, &packet.data); - io.to_disconnect + p.chain.flush(); + io.to_disconnect.clone() }; for d in &to_disconnect { // notify this that disconnecting peers are disconnecting - let mut io = TestIo::new(&p.chain, &p.snapshot_service, &mut queue, Some(*d)); + let mut io = TestIo::new(&*p.chain, &p.snapshot_service, &p.queue, Some(*d)); p.sync.write().on_peer_aborting(&mut io, *d); self.disconnect_events.push((peer, *d)); } @@ -264,8 +277,7 @@ impl TestNet where C: FlushingBlockChainClient { for d in &disconnecting { // notify other peers that this peer is disconnecting let p = &self.peers[*d]; - let mut queue = p.queue.write(); - let mut io = TestIo::new(&p.chain, &p.snapshot_service, &mut queue, Some(peer as PeerId)); + let mut io = TestIo::new(&*p.chain, &p.snapshot_service, &p.queue, Some(peer as PeerId)); p.sync.write().on_peer_aborting(&mut io, peer as PeerId); } } @@ -277,15 +289,14 @@ impl TestNet where C: FlushingBlockChainClient { pub fn sync_step_peer(&mut self, peer_num: usize) { let peer = self.peer(peer_num); peer.chain.flush(); - let mut queue = peer.queue.write(); - peer.sync.write().maintain_peers(&mut TestIo::new(&peer.chain, &peer.snapshot_service, &mut queue, None)); - peer.sync.write().maintain_sync(&mut TestIo::new(&peer.chain, &peer.snapshot_service, &mut queue, None)); - peer.sync.write().propagate_new_transactions(&mut TestIo::new(&peer.chain, &peer.snapshot_service, &mut queue, None)); + peer.sync.write().maintain_peers(&mut TestIo::new(&*peer.chain, &peer.snapshot_service, &peer.queue, None)); + peer.sync.write().maintain_sync(&mut TestIo::new(&*peer.chain, &peer.snapshot_service, &peer.queue, None)); + peer.sync.write().propagate_new_transactions(&mut TestIo::new(&*peer.chain, &peer.snapshot_service, &peer.queue, None)); } pub fn restart_peer(&mut self, i: usize) { let peer = self.peer(i); - peer.sync.write().restart(&mut TestIo::new(&peer.chain, &peer.snapshot_service, &mut peer.queue.write(), None)); + peer.sync.write().restart(&mut TestIo::new(&*peer.chain, &peer.snapshot_service, &peer.queue, None)); } pub fn sync(&mut self) -> u32 { @@ -299,10 +310,7 @@ impl TestNet where C: FlushingBlockChainClient { } pub fn sync_steps(&mut self, count: usize) { - if !self.started { - self.start(); - self.started = true; - } + self.start(); for _ in 0..count { self.sync_step(); } @@ -314,8 +322,7 @@ impl TestNet where C: FlushingBlockChainClient { pub fn trigger_chain_new_blocks(&mut self, peer_id: usize) { let peer = self.peer(peer_id); - let mut queue = peer.queue.write(); - peer.sync.write().chain_new_blocks(&mut TestIo::new(&peer.chain, &peer.snapshot_service, &mut queue, None), &[], &[], &[], &[], &[]); + peer.sync.write().chain_new_blocks(&mut TestIo::new(&*peer.chain, &peer.snapshot_service, &peer.queue, None), &[], &[], &[], &[], &[], &[]); } } @@ -326,21 +333,26 @@ impl ChainNotify for TestPeer { enacted: Vec, retracted: Vec, sealed: Vec, + proposed: Vec, _duration: u64) { - let mut queue = self.queue.write(); - let mut io = TestIo::new(&self.chain, &self.snapshot_service, &mut queue, None); + let mut io = TestIo::new(&*self.chain, &self.snapshot_service, &self.queue, None); self.sync.write().chain_new_blocks( &mut io, &imported, &invalid, &enacted, &retracted, - &sealed); + &sealed, + &proposed); } fn start(&self) {} fn stop(&self) {} -} + fn broadcast(&self, message: Vec) { + let mut io = TestIo::new(&*self.chain, &self.snapshot_service, &self.queue, None); + self.sync.write().propagate_consensus_packet(&mut io, message.clone()); + } +} diff --git a/util/io/src/service.rs b/util/io/src/service.rs index d9650a94a..f41cd866b 100644 --- a/util/io/src/service.rs +++ b/util/io/src/service.rs @@ -329,11 +329,18 @@ impl Handler for IoManager where Message: Send + Clone + Sync } } +#[derive(Clone)] +enum Handlers where Message: Send + Clone { + SharedCollection(Weak>, HandlerId>>>), + Single(Weak>), +} + /// Allows sending messages into the event loop. All the IO handlers will get the message /// in the `message` callback. pub struct IoChannel where Message: Send + Clone{ channel: Option>>, - handlers: Weak>, HandlerId>>>, + handlers: Handlers, + } impl Clone for IoChannel where Message: Send + Clone + Sync + 'static { @@ -348,19 +355,29 @@ impl Clone for IoChannel where Message: Send + Clone + Sync + impl IoChannel where Message: Send + Clone + Sync + 'static { /// Send a message through the channel pub fn send(&self, message: Message) -> Result<(), IoError> { - if let Some(ref channel) = self.channel { - try!(channel.send(IoMessage::UserMessage(message))); + match self.channel { + Some(ref channel) => try!(channel.send(IoMessage::UserMessage(message))), + None => try!(self.send_sync(message)) } Ok(()) } /// Send a message through the channel and handle it synchronously pub fn send_sync(&self, message: Message) -> Result<(), IoError> { - if let Some(handlers) = self.handlers.upgrade() { - for id in 0 .. MAX_HANDLERS { - if let Some(h) = handlers.read().get(id) { - let handler = h.clone(); - handler.message(&IoContext::new(self.clone(), id), &message); + match self.handlers { + Handlers::SharedCollection(ref handlers) => { + if let Some(handlers) = handlers.upgrade() { + for id in 0 .. MAX_HANDLERS { + if let Some(h) = handlers.read().get(id) { + let handler = h.clone(); + handler.message(&IoContext::new(self.clone(), id), &message); + } + } + } + }, + Handlers::Single(ref handler) => { + if let Some(handler) = handler.upgrade() { + handler.message(&IoContext::new(self.clone(), 0), &message); } } } @@ -378,14 +395,21 @@ impl IoChannel where Message: Send + Clone + Sync + 'static { pub fn disconnected() -> IoChannel { IoChannel { channel: None, - handlers: Weak::default(), + handlers: Handlers::SharedCollection(Weak::default()), } } + /// Create a new synchronous channel to a given handler. + pub fn to_handler(handler: Weak>) -> IoChannel { + IoChannel { + channel: None, + handlers: Handlers::Single(handler), + } + } fn new(channel: Sender>, handlers: Weak>, HandlerId>>>) -> IoChannel { IoChannel { channel: Some(channel), - handlers: handlers, + handlers: Handlers::SharedCollection(handlers), } } }