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