From 096b71feb20cd2731a59cc5193143ebb71ce057e Mon Sep 17 00:00:00 2001 From: keorn Date: Wed, 5 Oct 2016 14:33:07 +0100 Subject: [PATCH] add Vote generation --- ethcore/src/basic_types.rs | 4 +- ethcore/src/engines/tendermint/mod.rs | 119 +++++++++++++++++-------- ethcore/src/engines/tendermint/vote.rs | 63 +++++++++++++ ethcore/src/header.rs | 9 +- 4 files changed, 151 insertions(+), 44 deletions(-) create mode 100644 ethcore/src/engines/tendermint/vote.rs diff --git a/ethcore/src/basic_types.rs b/ethcore/src/basic_types.rs index 5f6515c0d..e2a705dd7 100644 --- a/ethcore/src/basic_types.rs +++ b/ethcore/src/basic_types.rs @@ -25,10 +25,12 @@ pub type LogBloom = H2048; pub static ZERO_LOGBLOOM: LogBloom = H2048([0x00; 256]); #[cfg_attr(feature="dev", allow(enum_variant_names))] -/// Semantic boolean for when a seal/signature is included. +/// Enum for when a seal/signature is included. pub enum Seal { /// The seal/signature is included. With, /// The seal/signature is not included. Without, + /// First N fields of seal are included. + WithSome(usize), } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 8d92d1828..2af0ab781 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -19,6 +19,7 @@ mod message; mod timeout; mod params; +mod vote; use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use common::*; @@ -28,20 +29,22 @@ use account_provider::AccountProvider; use block::*; use spec::CommonParams; use engines::{Engine, EngineError, ProposeCollect}; +use blockchain::extras::BlockDetails; use evm::Schedule; use io::IoService; use self::message::ConsensusMessage; use self::timeout::{TimerHandler, NextStep}; use self::params::TendermintParams; +use self::vote::Vote; #[derive(Debug)] enum Step { Propose, Prevote(ProposeCollect), /// Precommit step storing the precommit vote and accumulating seal. - Precommit(ProposeCollect, Seal), + Precommit(ProposeCollect, Signatures), /// Commit step storing a complete valid seal. - Commit(BlockHash, Seal) + Commit(BlockHash, Signatures) } pub type Height = usize; @@ -49,7 +52,7 @@ pub type Round = usize; pub type BlockHash = H256; pub type AtomicMs = AtomicUsize; -type Seal = Vec; +type Signatures = Vec; /// Engine using `Tendermint` consensus algorithm, suitable for EVM chain. pub struct Tendermint { @@ -57,6 +60,8 @@ pub struct Tendermint { our_params: TendermintParams, builtins: BTreeMap, timeout_service: IoService, + /// Address to be used as authority. + authority: RwLock
, /// Consensus round. r: AtomicUsize, /// Consensus step. @@ -77,6 +82,7 @@ impl Tendermint { our_params: our_params, builtins: builtins, timeout_service: IoService::::start().expect("Error creating engine timeout service"), + authority: RwLock::new(Address::default()), r: AtomicUsize::new(0), s: RwLock::new(Step::Propose), proposer_nonce: AtomicUsize::new(0) @@ -86,22 +92,26 @@ impl Tendermint { engine } - fn proposer(&self) -> Address { - let ref p = self.our_params; - p.validators.get(self.proposer_nonce.load(AtomicOrdering::Relaxed)%p.validator_n).unwrap().clone() - } - fn is_proposer(&self, address: &Address) -> bool { - self.proposer() == *address + self.is_nonce_proposer(self.proposer_nonce.load(AtomicOrdering::SeqCst), address) } - fn is_validator(&self, address: &Address) -> bool { - self.our_params.validators.contains(address) + fn nonce_proposer(&self, proposer_nonce: usize) -> &Address { + let ref p = self.our_params; + p.authorities.get(proposer_nonce%p.authority_n).unwrap() + } + + fn is_nonce_proposer(&self, proposer_nonce: usize, address: &Address) -> bool { + self.nonce_proposer(proposer_nonce) == address + } + + fn is_authority(&self, address: &Address) -> bool { + self.our_params.authorities.contains(address) } fn new_vote(&self, proposal: BlockHash) -> ProposeCollect { ProposeCollect::new(proposal, - self.our_params.validators.iter().cloned().collect(), + self.our_params.authorities.iter().cloned().collect(), self.threshold()) } @@ -192,7 +202,7 @@ impl Tendermint { } fn threshold(&self) -> usize { - self.our_params.validator_n*2/3 + self.our_params.authority_n*2/3 } fn next_timeout(&self) -> u64 { @@ -203,8 +213,8 @@ impl Tendermint { impl Engine for Tendermint { fn name(&self) -> &str { "Tendermint" } fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } - /// Possibly signatures of all validators. - fn seal_fields(&self) -> usize { 2 } + /// (consensus round, proposal signature, authority signatures) + fn seal_fields(&self) -> usize { 3 } fn params(&self) -> &CommonParams { &self.params } fn builtins(&self) -> &BTreeMap { &self.builtins } @@ -229,34 +239,62 @@ impl Engine for Tendermint { }); } - /// Apply the block reward on finalisation of the block. + /// Get the address to be used as authority. + fn on_new_block(&self, block: &mut ExecutedBlock) { + if let Some(mut authority) = self.authority.try_write() { + *authority = *block.header().author() + } + } + + /// Set author to proposer. /// This assumes that all uncles are valid uncles (i.e. of at least one generation before the current). fn on_close_block(&self, _block: &mut ExecutedBlock) {} /// Attempt to seal the block internally using all available signatures. /// /// None is returned if not enough signatures can be collected. - fn generate_seal(&self, block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option> { - self.s.try_read().and_then(|s| match *s { - Step::Commit(hash, ref seal) if hash == block.header().bare_hash() => Some(seal.clone()), - _ => None, - }) + fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { + if let (Some(ap), Some(step)) = (accounts, self.s.try_read()) { + let header = block.header(); + let author = header.author(); + match *step { + Step::Commit(hash, ref seal) if hash == header.bare_hash() => + // Commit the block using a complete signature set. + return Some(seal.clone()), + Step::Propose if self.is_proposer(header.author()) => + // Seal block with propose signature. + if let Some(proposal) = Vote::propose(header, &ap) { + return Some(vec![::rlp::encode(&proposal).to_vec(), Vec::new()]) + } else { + trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable"); + }, + _ => {}, + } + } else { + trace!(target: "basicauthority", "generate_seal: FAIL: accounts not provided"); + } + None } fn handle_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result { - let c: ConsensusMessage = try!(message.as_val()); - println!("{:?}", c); + let message: ConsensusMessage = try!(message.as_val()); + try!(Err(EngineError::UnknownStep)) + //match message { + // ConsensusMessage::Prevote + //} + + // Check if correct round. - if self.r.load(AtomicOrdering::Relaxed) != try!(message.val_at(0)) { - try!(Err(EngineError::WrongRound)) - } + //if self.r.load(AtomicOrdering::Relaxed) != try!(message.val_at(0)) { + // try!(Err(EngineError::WrongRound)) + //} // Handle according to step. - match try!(message.val_at(1)) { - 0u8 if self.is_proposer(&sender) => self.propose_message(try!(message.at(2))), - 1 if self.is_validator(&sender) => self.prevote_message(sender, try!(message.at(2))), - 2 if self.is_validator(&sender) => self.precommit_message(sender, signature, try!(message.at(2))), - _ => try!(Err(EngineError::UnknownStep)), - } +// match try!(message.val_at(1)) { +// 0u8 if self.is_proposer(&sender) => self.propose_message(try!(message.at(2))), +// 1 if self.is_authority(&sender) => self.prevote_message(sender, try!(message.at(2))), +// 2 if self.is_authority(&sender) => self.precommit_message(sender, signature, try!(message.at(2))), +// _ => try!(Err(EngineError::UnknownStep)), +// } } fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { @@ -270,18 +308,19 @@ impl Engine for Tendermint { } } + /// Also transitions to Prevote if verifying Proposal. fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { let to_address = |b: &Vec| { let sig: H520 = try!(UntrustedRlp::new(b.as_slice()).as_val()); Ok(public_to_address(&try!(recover(&sig.into(), &header.bare_hash())))) }; - let validator_set = self.our_params.validators.iter().cloned().collect(); + let authority_set = self.our_params.authorities.iter().cloned().collect(); let seal_set = try!(header .seal() .iter() .map(to_address) .collect::, Error>>()); - if seal_set.intersection(&validator_set).count() <= self.threshold() { + if seal_set.intersection(&authority_set).count() <= self.threshold() { try!(Err(BlockError::InvalidSeal)) } else { Ok(()) @@ -315,6 +354,10 @@ impl Engine for Tendermint { fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { t.sender().map(|_|()) // Perform EC recovery and cache sender } + + fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { + new_header.seal().get(1).expect("Tendermint seal should have two elements.").len() > best_header.seal().get(1).expect("Tendermint seal should have two elements.").len() + } } #[cfg(test)] @@ -465,9 +508,9 @@ mod tests { let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()); let genesis_header = spec.genesis_header(); - let mut db_result = get_temp_journal_db(); + let mut db_result = get_temp_state_db(); let mut db = db_result.take(); - spec.ensure_db_good(db.as_hashdb_mut()).unwrap(); + spec.ensure_db_good(&mut db).unwrap(); let last_hashes = Arc::new(vec![genesis_header.hash()]); let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); @@ -484,8 +527,8 @@ mod tests { let tap = AccountProvider::transient_provider(); let r = 0; - let not_validator_addr = tap.insert_account("101".sha3(), "101").unwrap(); - assert!(propose_default(&engine, r, not_validator_addr).is_err()); + let not_authority_addr = tap.insert_account("101".sha3(), "101").unwrap(); + assert!(propose_default(&engine, r, not_authority_addr).is_err()); let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap(); assert!(propose_default(&engine, r, not_proposer_addr).is_err()); diff --git a/ethcore/src/engines/tendermint/vote.rs b/ethcore/src/engines/tendermint/vote.rs new file mode 100644 index 000000000..a11583201 --- /dev/null +++ b/ethcore/src/engines/tendermint/vote.rs @@ -0,0 +1,63 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Tendermint block seal. + +use common::{H256, Address, H520, Header}; +use util::Hashable; +use account_provider::AccountProvider; +use rlp::{View, DecoderError, Decodable, Decoder, Encodable, RlpStream, Stream}; +use basic_types::Seal; + +#[derive(Debug)] +pub struct Vote { + signature: H520 +} + +fn message(header: &Header) -> H256 { + header.rlp(Seal::WithSome(1)).sha3() +} + +impl Vote { + fn new(signature: H520) -> Vote { Vote { signature: signature }} + + /// Try to use the author address to create a vote. + pub fn propose(header: &Header, accounts: &AccountProvider) -> Option { + accounts.sign(*header.author(), message(&header)).ok().map(Into::into).map(Self::new) + } + + /// Use any unlocked validator account to create a vote. + pub fn validate(header: &Header, accounts: &AccountProvider, validator: Address) -> Option { + accounts.sign(validator, message(&header)).ok().map(Into::into).map(Self::new) + } +} + +impl Decodable for Vote { + fn decode(decoder: &D) -> Result where D: Decoder { + let rlp = decoder.as_rlp(); + if decoder.as_raw().len() != try!(rlp.payload_info()).total() { + return Err(DecoderError::RlpIsTooBig); + } + rlp.as_val().map(Self::new) + } +} + +impl Encodable for Vote { + fn rlp_append(&self, s: &mut RlpStream) { + let Vote { ref signature } = *self; + s.append(signature); + } +} diff --git a/ethcore/src/header.rs b/ethcore/src/header.rs index 7d86cfd61..794b9230b 100644 --- a/ethcore/src/header.rs +++ b/ethcore/src/header.rs @@ -226,7 +226,8 @@ impl Header { // TODO: make these functions traity /// Place this header into an RLP stream `s`, optionally `with_seal`. pub fn stream_rlp(&self, s: &mut RlpStream, with_seal: Seal) { - s.begin_list(13 + match with_seal { Seal::With => self.seal.len(), _ => 0 }); + let seal_n = match with_seal { Seal::With => self.seal.len(), Seal::WithSome(n) => n, _ => 0 }; + s.begin_list(13 + seal_n); s.append(&self.parent_hash); s.append(&self.uncles_hash); s.append(&self.author); @@ -240,10 +241,8 @@ impl Header { s.append(&self.gas_used); s.append(&self.timestamp); s.append(&self.extra_data); - if let Seal::With = with_seal { - for b in &self.seal { - s.append_raw(b, 1); - } + for b in self.seal.iter().take(seal_n) { + s.append_raw(b, 1); } }