diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index e52db90fb..47a12435d 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -20,13 +20,15 @@ mod null_engine; mod instant_seal; mod basic_authority; mod bft; +mod signed_vote; pub use self::null_engine::NullEngine; pub use self::instant_seal::InstantSeal; pub use self::basic_authority::BasicAuthority; pub use self::bft::BFT; +pub use self::signed_vote::VoteError; -use common::*; +use common::{HashMap, SemanticVersion, Header, EnvInfo, Address, Builtin, BTreeMap, U256, Bytes, SignedTransaction, Error}; use account_provider::AccountProvider; use block::ExecutedBlock; use spec::CommonParams; diff --git a/ethcore/src/engines/signed_vote.rs b/ethcore/src/engines/signed_vote.rs new file mode 100644 index 000000000..694b7cc9b --- /dev/null +++ b/ethcore/src/engines/signed_vote.rs @@ -0,0 +1,101 @@ +// 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 hashes, where each vote has to come from a set of public keys. + +use common::*; +use account_provider::AccountProvider; +use block::*; +use spec::CommonParams; +use engines::Engine; +use evm::Schedule; +use ethjson; + +/// Signed voting on hashes. +#[derive(Debug)] +pub struct SignedVote { + /// Voter public keys. + pub voters: HashSet
, + /// Number of voters. + pub voter_n: usize, + /// Threshold vote number for success. + pub threshold: usize, + /// Votes. + votes: RwLock>>, + /// Winner hash, set after enough votes are reached. + winner: RwLock> +} + +#[derive(Debug)] +pub enum VoteError { + UnauthorisedVoter +} + +impl SignedVote { + /// Create a new instance of BFT engine + pub fn new(voters: HashSet
, threshold: usize) -> Self { + SignedVote { + voter_n: voters.len(), + voters: voters, + threshold: threshold, + votes: RwLock::new(HashMap::new()), + winner: RwLock::new(None) + } + } + + pub fn vote(&self, bare_hash: H256, signature: &Signature) -> bool { + if !self.can_vote(&bare_hash, signature).is_ok() { return false; } + let n = if let Some(mut old) = self.votes.write().get_mut(&bare_hash) { + old.insert(signature.clone()); + old.len() + } else { + let mut new = HashSet::new(); + new.insert(signature.clone()); + assert!(self.votes.write().insert(bare_hash.clone(), new).is_none()); + 1 + }; + if self.is_won(n) { + let mut guard = self.winner.write(); + *guard = Some(bare_hash); + } + true + } + + fn can_vote(&self, bare_hash: &H256, signature: &Signature) -> Result<(), Error> { + let signer = Address::from(try!(ec::recover(&signature, bare_hash)).sha3()); + match self.voters.contains(&signer) { + false => try!(Err(VoteError::UnauthorisedVoter)), + true => Ok(()), + } + } + + fn is_won(&self, valid_votes: usize) -> bool { + valid_votes > self.threshold + } + + pub fn winner(&self) -> Option { self.winner.read().clone() } +} + +#[cfg(test)] +mod tests { + use common::{HashSet, Address}; + use engines::signed_vote::SignedVote; + #[test] + fn simple_vote() { + let voters: HashSet<_> = vec![Address::default()].into_iter().collect(); + let vote = SignedVote::new(voters, 2); + } +} diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index 449303732..aed7773ae 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::VoteError; pub use types::executed::{ExecutionError, CallError}; @@ -238,6 +239,8 @@ pub enum Error { Snappy(::util::snappy::InvalidInput), /// Snapshot error. Snapshot(SnapshotError), + /// Consensus vote error. + Vote(VoteError), } impl fmt::Display for Error { @@ -258,6 +261,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::Vote(ref err) => f.write_str("Bad vote."), } } } @@ -361,6 +365,14 @@ impl From for Error { } } +impl From for Error { + fn from(err: VoteError) -> Error { + match err { + other => Error::Vote(other), + } + } +} + impl From> for Error where Error: From { fn from(err: Box) -> Error { Error::from(*err)