From 3515a72fa08b64b4d37de61ff18ca64e0d47ee22 Mon Sep 17 00:00:00 2001 From: keorn Date: Mon, 22 Aug 2016 20:00:41 +0200 Subject: [PATCH] proposal vote collector --- ethcore/src/engines/propose_collect.rs | 130 +++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 ethcore/src/engines/propose_collect.rs diff --git a/ethcore/src/engines/propose_collect.rs b/ethcore/src/engines/propose_collect.rs new file mode 100644 index 000000000..84dee0bd7 --- /dev/null +++ b/ethcore/src/engines/propose_collect.rs @@ -0,0 +1,130 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Voting on a hash, where each vote has to come from a set of addresses. + +use common::{HashSet, RwLock, H256, Signature, Address, Error, ec, Hashable, AtomicBool}; + +/// Collect votes on a hash. +#[derive(Debug)] +pub struct ProposeCollect { + /// Proposed hash. + pub hash: H256, + /// Allowed voter addresses. + pub voters: HashSet
, + /// Threshold vote number for success. + pub threshold: usize, + /// Votes. + votes: RwLock>, + /// Was enough votes reached. + is_won: AtomicBool +} + +/// Voting errors. +#[derive(Debug)] +pub enum VoteError { + /// Voter is not in the voters set. + UnauthorisedVoter +} + +impl SignedVote { + /// Create a new instance of BFT engine + pub fn new(hash: H256, voters: HashSet
, threshold: usize) -> Self { + assert!(voters.len() > threshold); + SignedVote { + hash: hash, + voters: voters, + threshold: threshold, + votes: RwLock::new(HashSet::new()), + is_won: AtomicBool::new(false) + } + } + + /// Vote on hash using the signed hash, true if vote counted. + pub fn vote(&self, signature: &Signature) -> bool { + if self.votes.contains(signature) { return false; } + if !self.can_vote(signature).is_ok() { return false; } + self.votes.try_write().unwrap().insert(signature); + true + } + + fn can_vote(&self, signature: &Signature) -> Result<(), Error> { + let signer = Address::from(try!(ec::recover(&signature, self.hash)).sha3()); + match self.voters.contains(&signer) { + false => try!(Err(VoteError::UnauthorisedVoter)), + true => Ok(()), + } + } + + /// Some winner if voting threshold was reached. + pub fn winner(&self) -> Option { + let threshold_checker = || match self.votes.len() >= threshold { + true => { self.is_won.store(true, Ordering::Relaxed); true }, + false => false, + }; + match self.is_won || threshold_checker() { + true => Some(self.hash), + false => None, + } + } + + /// Get signatures backing given hash. + pub fn votes(&self) -> HashSet { + self.votes.try_read().unwrap().clone() + } +} + +#[cfg(test)] +mod tests { + use common::*; + use engines::propose_collect::ProposeCollect; + use account_provider::AccountProvider; + + #[test] + fn simple_propose_collect() { + let tap = AccountProvider::transient_provider(); + let addr1 = tap.insert_account("1".sha3(), "1").unwrap(); + tap.unlock_account_permanently(addr1, "1".into()).unwrap(); + + let addr2 = tap.insert_account("2".sha3(), "2").unwrap(); + tap.unlock_account_permanently(addr2, "2".into()).unwrap(); + + let addr3 = tap.insert_account("3".sha3(), "3").unwrap(); + tap.unlock_account_permanently(addr3, "3".into()).unwrap(); + + let header = Header::default(); + let bare_hash = header.bare_hash(); + let voters: HashSet<_> = vec![addr1, addr2].into_iter().map(Into::into).collect(); + let vote = ProposeCollect::new(bare_hash, voters.into(), 1); + assert!(vote.winner().is_none()); + + // Unapproved voter. + let signature = tap.sign(addr3, bare_hash).unwrap(); + assert!(!vote.vote(&signature.into())); + assert!(vote.winner().is_none()); + // First good vote. + let signature = tap.sign(addr1, bare_hash).unwrap(); + assert!(vote.vote(&signature.into())); + assert_eq!(vote.winner().unwrap(), bare_hash); + // Voting again is ineffective. + let signature = tap.sign(addr1, bare_hash).unwrap(); + assert!(!vote.vote(&signature.into())); + // Second valid vote. + let signature = tap.sign(addr2, bare_hash).unwrap(); + assert!(vote.vote(&signature.into())); + assert_eq!(vote.winner().unwrap(), bare_hash); + } +}