add Vote generation

This commit is contained in:
keorn 2016-10-05 14:33:07 +01:00
parent cb2c9938a1
commit 096b71feb2
4 changed files with 151 additions and 44 deletions

View File

@ -25,10 +25,12 @@ pub type LogBloom = H2048;
pub static ZERO_LOGBLOOM: LogBloom = H2048([0x00; 256]); pub static ZERO_LOGBLOOM: LogBloom = H2048([0x00; 256]);
#[cfg_attr(feature="dev", allow(enum_variant_names))] #[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 { pub enum Seal {
/// The seal/signature is included. /// The seal/signature is included.
With, With,
/// The seal/signature is not included. /// The seal/signature is not included.
Without, Without,
/// First N fields of seal are included.
WithSome(usize),
} }

View File

@ -19,6 +19,7 @@
mod message; mod message;
mod timeout; mod timeout;
mod params; mod params;
mod vote;
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
use common::*; use common::*;
@ -28,20 +29,22 @@ use account_provider::AccountProvider;
use block::*; use block::*;
use spec::CommonParams; use spec::CommonParams;
use engines::{Engine, EngineError, ProposeCollect}; use engines::{Engine, EngineError, ProposeCollect};
use blockchain::extras::BlockDetails;
use evm::Schedule; use evm::Schedule;
use io::IoService; use io::IoService;
use self::message::ConsensusMessage; use self::message::ConsensusMessage;
use self::timeout::{TimerHandler, NextStep}; use self::timeout::{TimerHandler, NextStep};
use self::params::TendermintParams; use self::params::TendermintParams;
use self::vote::Vote;
#[derive(Debug)] #[derive(Debug)]
enum Step { enum Step {
Propose, Propose,
Prevote(ProposeCollect), Prevote(ProposeCollect),
/// Precommit step storing the precommit vote and accumulating seal. /// Precommit step storing the precommit vote and accumulating seal.
Precommit(ProposeCollect, Seal), Precommit(ProposeCollect, Signatures),
/// Commit step storing a complete valid seal. /// Commit step storing a complete valid seal.
Commit(BlockHash, Seal) Commit(BlockHash, Signatures)
} }
pub type Height = usize; pub type Height = usize;
@ -49,7 +52,7 @@ pub type Round = usize;
pub type BlockHash = H256; pub type BlockHash = H256;
pub type AtomicMs = AtomicUsize; pub type AtomicMs = AtomicUsize;
type Seal = Vec<Bytes>; type Signatures = Vec<Bytes>;
/// Engine using `Tendermint` consensus algorithm, suitable for EVM chain. /// Engine using `Tendermint` consensus algorithm, suitable for EVM chain.
pub struct Tendermint { pub struct Tendermint {
@ -57,6 +60,8 @@ pub struct Tendermint {
our_params: TendermintParams, our_params: TendermintParams,
builtins: BTreeMap<Address, Builtin>, builtins: BTreeMap<Address, Builtin>,
timeout_service: IoService<NextStep>, timeout_service: IoService<NextStep>,
/// Address to be used as authority.
authority: RwLock<Address>,
/// Consensus round. /// Consensus round.
r: AtomicUsize, r: AtomicUsize,
/// Consensus step. /// Consensus step.
@ -77,6 +82,7 @@ impl Tendermint {
our_params: our_params, our_params: our_params,
builtins: builtins, builtins: builtins,
timeout_service: IoService::<NextStep>::start().expect("Error creating engine timeout service"), timeout_service: IoService::<NextStep>::start().expect("Error creating engine timeout service"),
authority: RwLock::new(Address::default()),
r: AtomicUsize::new(0), r: AtomicUsize::new(0),
s: RwLock::new(Step::Propose), s: RwLock::new(Step::Propose),
proposer_nonce: AtomicUsize::new(0) proposer_nonce: AtomicUsize::new(0)
@ -86,22 +92,26 @@ impl Tendermint {
engine 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 { 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 { fn nonce_proposer(&self, proposer_nonce: usize) -> &Address {
self.our_params.validators.contains(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 { fn new_vote(&self, proposal: BlockHash) -> ProposeCollect {
ProposeCollect::new(proposal, ProposeCollect::new(proposal,
self.our_params.validators.iter().cloned().collect(), self.our_params.authorities.iter().cloned().collect(),
self.threshold()) self.threshold())
} }
@ -192,7 +202,7 @@ impl Tendermint {
} }
fn threshold(&self) -> usize { fn threshold(&self) -> usize {
self.our_params.validator_n*2/3 self.our_params.authority_n*2/3
} }
fn next_timeout(&self) -> u64 { fn next_timeout(&self) -> u64 {
@ -203,8 +213,8 @@ impl Tendermint {
impl Engine for Tendermint { impl Engine for Tendermint {
fn name(&self) -> &str { "Tendermint" } fn name(&self) -> &str { "Tendermint" }
fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) } fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) }
/// Possibly signatures of all validators. /// (consensus round, proposal signature, authority signatures)
fn seal_fields(&self) -> usize { 2 } fn seal_fields(&self) -> usize { 3 }
fn params(&self) -> &CommonParams { &self.params } fn params(&self) -> &CommonParams { &self.params }
fn builtins(&self) -> &BTreeMap<Address, Builtin> { &self.builtins } fn builtins(&self) -> &BTreeMap<Address, Builtin> { &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). /// This assumes that all uncles are valid uncles (i.e. of at least one generation before the current).
fn on_close_block(&self, _block: &mut ExecutedBlock) {} fn on_close_block(&self, _block: &mut ExecutedBlock) {}
/// Attempt to seal the block internally using all available signatures. /// Attempt to seal the block internally using all available signatures.
/// ///
/// None is returned if not enough signatures can be collected. /// None is returned if not enough signatures can be collected.
fn generate_seal(&self, block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option<Vec<Bytes>> { fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option<Vec<Bytes>> {
self.s.try_read().and_then(|s| match *s { if let (Some(ap), Some(step)) = (accounts, self.s.try_read()) {
Step::Commit(hash, ref seal) if hash == block.header().bare_hash() => Some(seal.clone()), let header = block.header();
_ => None, 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<Bytes, Error> { fn handle_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result<Bytes, Error> {
let c: ConsensusMessage = try!(message.as_val()); let message: ConsensusMessage = try!(message.as_val());
println!("{:?}", c); try!(Err(EngineError::UnknownStep))
//match message {
// ConsensusMessage::Prevote
//}
// Check if correct round. // Check if correct round.
if self.r.load(AtomicOrdering::Relaxed) != try!(message.val_at(0)) { //if self.r.load(AtomicOrdering::Relaxed) != try!(message.val_at(0)) {
try!(Err(EngineError::WrongRound)) // try!(Err(EngineError::WrongRound))
} //}
// Handle according to step. // Handle according to step.
match try!(message.val_at(1)) { // match try!(message.val_at(1)) {
0u8 if self.is_proposer(&sender) => self.propose_message(try!(message.at(2))), // 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))), // 1 if self.is_authority(&sender) => self.prevote_message(sender, try!(message.at(2))),
2 if self.is_validator(&sender) => self.precommit_message(sender, signature, try!(message.at(2))), // 2 if self.is_authority(&sender) => self.precommit_message(sender, signature, try!(message.at(2))),
_ => try!(Err(EngineError::UnknownStep)), // _ => try!(Err(EngineError::UnknownStep)),
} // }
} }
fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { 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> { fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
let to_address = |b: &Vec<u8>| { let to_address = |b: &Vec<u8>| {
let sig: H520 = try!(UntrustedRlp::new(b.as_slice()).as_val()); let sig: H520 = try!(UntrustedRlp::new(b.as_slice()).as_val());
Ok(public_to_address(&try!(recover(&sig.into(), &header.bare_hash())))) 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 let seal_set = try!(header
.seal() .seal()
.iter() .iter()
.map(to_address) .map(to_address)
.collect::<Result<HashSet<_>, Error>>()); .collect::<Result<HashSet<_>, Error>>());
if seal_set.intersection(&validator_set).count() <= self.threshold() { if seal_set.intersection(&authority_set).count() <= self.threshold() {
try!(Err(BlockError::InvalidSeal)) try!(Err(BlockError::InvalidSeal))
} else { } else {
Ok(()) Ok(())
@ -315,6 +354,10 @@ impl Engine for Tendermint {
fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> {
t.sender().map(|_|()) // Perform EC recovery and cache sender 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)] #[cfg(test)]
@ -465,9 +508,9 @@ mod tests {
let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new()); let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new());
let genesis_header = spec.genesis_header(); 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(); 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 last_hashes = Arc::new(vec![genesis_header.hash()]);
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap();
let b = b.close_and_lock(); let b = b.close_and_lock();
@ -484,8 +527,8 @@ mod tests {
let tap = AccountProvider::transient_provider(); let tap = AccountProvider::transient_provider();
let r = 0; let r = 0;
let not_validator_addr = tap.insert_account("101".sha3(), "101").unwrap(); let not_authority_addr = tap.insert_account("101".sha3(), "101").unwrap();
assert!(propose_default(&engine, r, not_validator_addr).is_err()); assert!(propose_default(&engine, r, not_authority_addr).is_err());
let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap(); let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap();
assert!(propose_default(&engine, r, not_proposer_addr).is_err()); assert!(propose_default(&engine, r, not_proposer_addr).is_err());

View File

@ -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 <http://www.gnu.org/licenses/>.
//! 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<Vote> {
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<Vote> {
accounts.sign(validator, message(&header)).ok().map(Into::into).map(Self::new)
}
}
impl Decodable for Vote {
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> 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);
}
}

View File

@ -226,7 +226,8 @@ impl Header {
// TODO: make these functions traity // TODO: make these functions traity
/// Place this header into an RLP stream `s`, optionally `with_seal`. /// Place this header into an RLP stream `s`, optionally `with_seal`.
pub fn stream_rlp(&self, s: &mut RlpStream, with_seal: 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.parent_hash);
s.append(&self.uncles_hash); s.append(&self.uncles_hash);
s.append(&self.author); s.append(&self.author);
@ -240,10 +241,8 @@ impl Header {
s.append(&self.gas_used); s.append(&self.gas_used);
s.append(&self.timestamp); s.append(&self.timestamp);
s.append(&self.extra_data); s.append(&self.extra_data);
if let Seal::With = with_seal { for b in self.seal.iter().take(seal_n) {
for b in &self.seal { s.append_raw(b, 1);
s.append_raw(b, 1);
}
} }
} }