From 67233adc171a217dd8641dab5c71705fc724b3e7 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Mon, 23 Oct 2023 11:30:24 +0200 Subject: [PATCH] Attempt to abstract over the various data types needed by the consensus engine --- Code/common/src/consensus.rs | 81 ++++++++ Code/common/src/height.rs | 32 ++- Code/common/src/lib.rs | 27 ++- Code/common/src/proposal.rs | 67 +++++-- Code/common/src/timeout.rs | 2 +- Code/common/src/validator_set.rs | 321 ++++++++++++++++++++----------- Code/common/src/value.rs | 67 +++++-- Code/common/src/vote.rs | 86 +++++++-- Code/consensus/src/executor.rs | 121 +++++++----- Code/round/src/events.rs | 32 ++- Code/round/src/lib.rs | 2 - Code/round/src/message.rs | 59 +++--- Code/round/src/state.rs | 49 +++-- Code/round/src/state_machine.rs | 135 ++++++++----- Code/vote/src/count.rs | 63 +++--- Code/vote/src/keeper.rs | 53 ++--- Code/vote/src/lib.rs | 24 ++- 17 files changed, 822 insertions(+), 399 deletions(-) create mode 100644 Code/common/src/consensus.rs diff --git a/Code/common/src/consensus.rs b/Code/common/src/consensus.rs new file mode 100644 index 000000000..0615d3909 --- /dev/null +++ b/Code/common/src/consensus.rs @@ -0,0 +1,81 @@ +use crate::{ + Address, Height, Proposal, PublicKey, Round, Validator, ValidatorSet, Value, ValueId, Vote, +}; + +pub trait Consensus +where + Self: Sized, +{ + type Address: Address; + type Height: Height; + type Proposal: Proposal; + type PublicKey: PublicKey; + type ValidatorSet: ValidatorSet; + type Validator: Validator; + type Value: Value; + type Vote: Vote; + + // FIXME: Remove this and thread it through where necessary + const DUMMY_ADDRESS: Self::Address; + + // FIXME: Remove + const DUMMY_VALUE: Self::Value; + + fn new_proposal( + height: Self::Height, + round: Round, + value: Self::Value, + pol_round: Round, + ) -> Self::Proposal; + + fn new_prevote( + round: Round, + value_id: Option>, + address: Self::Address, + ) -> Self::Vote; + + fn new_precommit( + round: Round, + value_id: Option>, + address: Self::Address, + ) -> Self::Vote; +} + +pub mod test { + use crate::height::test::*; + use crate::proposal::test::*; + use crate::validator_set::test::*; + use crate::value::test::*; + use crate::vote::test::*; + use crate::Round; + + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + pub struct TestConsensus; + + impl super::Consensus for TestConsensus { + type Address = Address; + type Height = Height; + type Proposal = Proposal; + type PublicKey = PublicKey; + type ValidatorSet = ValidatorSet; + type Validator = Validator; + type Value = Value; + type Vote = Vote; + + const DUMMY_ADDRESS: Address = Address::new(42); + + const DUMMY_VALUE: Self::Value = Value::new(0xdeadbeef); + + fn new_proposal(height: Height, round: Round, value: Value, pol_round: Round) -> Proposal { + Proposal::new(height, round, value, pol_round) + } + + fn new_prevote(round: Round, value_id: Option, address: Address) -> Vote { + Vote::new_prevote(round, value_id, address) + } + + fn new_precommit(round: Round, value_id: Option, address: Address) -> Vote { + Vote::new_precommit(round, value_id, address) + } + } +} diff --git a/Code/common/src/height.rs b/Code/common/src/height.rs index 156cc44be..ab6d211e2 100644 --- a/Code/common/src/height.rs +++ b/Code/common/src/height.rs @@ -1,15 +1,27 @@ -// TODO: Abstract over Height +use core::fmt::Debug; -/// A blockchain height -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] -pub struct Height(u64); +// TODO: Keep the trait or just add the bounds to Consensus::Height? +pub trait Height +where + // TODO: Require Copy as well? + Self: Clone + Debug + PartialEq + Eq + PartialOrd + Ord, +{ +} -impl Height { - pub fn new(height: u64) -> Self { - Self(height) - } +pub mod test { + /// A blockchain height + #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] + pub struct Height(u64); - pub fn as_u64(&self) -> u64 { - self.0 + impl Height { + pub fn new(height: u64) -> Self { + Self(height) + } + + pub fn as_u64(&self) -> u64 { + self.0 + } } + + impl super::Height for Height {} } diff --git a/Code/common/src/lib.rs b/Code/common/src/lib.rs index 44b33eb84..62ff0b269 100644 --- a/Code/common/src/lib.rs +++ b/Code/common/src/lib.rs @@ -10,6 +10,7 @@ )] #![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::panic))] +mod consensus; mod height; mod proposal; mod round; @@ -18,10 +19,22 @@ mod validator_set; mod value; mod vote; -pub use height::*; -pub use proposal::*; -pub use round::*; -pub use timeout::*; -pub use validator_set::*; -pub use value::*; -pub use vote::*; +pub type ValueId = <::Value as Value>::Id; + +pub use consensus::Consensus; +pub use height::Height; +pub use proposal::Proposal; +pub use round::Round; +pub use timeout::{Timeout, TimeoutStep}; +pub use validator_set::{Address, PublicKey, Validator, ValidatorSet}; +pub use value::Value; +pub use vote::{Vote, VoteType}; + +pub mod test { + pub use crate::consensus::test::*; + pub use crate::height::test::*; + pub use crate::proposal::test::*; + pub use crate::validator_set::test::*; + pub use crate::value::test::*; + pub use crate::vote::test::*; +} diff --git a/Code/common/src/proposal.rs b/Code/common/src/proposal.rs index 43b59584a..190a203a9 100644 --- a/Code/common/src/proposal.rs +++ b/Code/common/src/proposal.rs @@ -1,21 +1,56 @@ -use crate::{Height, Round, Value}; - -/// A proposal for a value in a round -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Proposal { - pub height: Height, - pub round: Round, - pub value: Value, - pub pol_round: Round, +use core::fmt::Debug; + +use crate::{Consensus, Round}; + +pub trait Proposal +where + Self: Clone + Debug + PartialEq + Eq, +{ + fn height(&self) -> C::Height; + fn round(&self) -> Round; + fn value(&self) -> &C::Value; + fn pol_round(&self) -> Round; } -impl Proposal { - pub fn new(height: Height, round: Round, value: Value, pol_round: Round) -> Self { - Self { - height, - round, - value, - pol_round, +pub mod test { + use crate::test::{Height, TestConsensus, Value}; + use crate::Round; + + /// A proposal for a value in a round + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct Proposal { + pub height: Height, + pub round: Round, + pub value: Value, + pub pol_round: Round, + } + + impl Proposal { + pub fn new(height: Height, round: Round, value: Value, pol_round: Round) -> Self { + Self { + height, + round, + value, + pol_round, + } + } + } + + impl super::Proposal for Proposal { + fn height(&self) -> Height { + self.height + } + + fn round(&self) -> Round { + self.round + } + + fn value(&self) -> &Value { + &self.value + } + + fn pol_round(&self) -> Round { + self.pol_round } } } diff --git a/Code/common/src/timeout.rs b/Code/common/src/timeout.rs index 56c6e632d..5920f7a5e 100644 --- a/Code/common/src/timeout.rs +++ b/Code/common/src/timeout.rs @@ -7,7 +7,7 @@ pub enum TimeoutStep { Precommit, } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Timeout { pub round: Round, pub step: TimeoutStep, diff --git a/Code/common/src/validator_set.rs b/Code/common/src/validator_set.rs index e90cda9cd..5dde5ed81 100644 --- a/Code/common/src/validator_set.rs +++ b/Code/common/src/validator_set.rs @@ -1,165 +1,252 @@ -// TODO: Abstract over all of this +use core::fmt::Debug; -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct PublicKey(Vec); +use crate::Consensus; -impl PublicKey { - pub const fn new(value: Vec) -> Self { - Self(value) - } +// TODO: Do we need to abstract over this as well? +pub type VotingPower = u64; - pub fn hash(&self) -> u64 { - // TODO - self.0.iter().fold(0, |acc, x| acc ^ *x as u64) - } +pub trait PublicKey +where + Self: Clone + Debug + PartialEq + Eq, +{ + fn hash(&self) -> u64; // FIXME: Make the hash type generic } -pub type VotingPower = u64; - -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Address(u64); +// TODO: Keep this trait or just add the bounds to Consensus::Address? +pub trait Address +where + Self: Clone + Debug + PartialEq + Eq, +{ +} -impl Address { - pub const fn new(value: u64) -> Self { - Self(value) - } +pub trait Validator +where + Self: Clone + Debug + PartialEq + Eq, + C: Consensus, +{ + fn address(&self) -> &C::Address; + fn public_key(&self) -> &C::PublicKey; + fn voting_power(&self) -> VotingPower; } -/// A validator is a public key and voting power -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Validator { - pub public_key: PublicKey, - pub voting_power: VotingPower, +pub trait ValidatorSet +where + C: Consensus, +{ + fn total_voting_power(&self) -> VotingPower; + fn get_proposer(&self) -> C::Validator; + fn get_by_public_key(&self, public_key: &C::PublicKey) -> Option<&C::Validator>; + fn get_by_address(&self, address: &C::Address) -> Option<&C::Validator>; } -impl Validator { - pub fn new(public_key: PublicKey, voting_power: VotingPower) -> Self { - Self { - public_key, - voting_power, - } - } +pub mod test { + use std::sync::atomic::{AtomicUsize, Ordering}; - pub fn hash(&self) -> Vec { - self.public_key.0.clone() // TODO - } + use crate::test::TestConsensus; - pub fn address(&self) -> Address { - Address(self.public_key.hash()) // TODO - } -} + use super::VotingPower; -/// A validator set contains a list of validators sorted by address. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct ValidatorSet { - validators: Vec, -} + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] + pub struct PublicKey(Vec); + + impl PublicKey { + pub const fn new(value: Vec) -> Self { + Self(value) + } -impl ValidatorSet { - pub fn new(validators: impl IntoIterator) -> Self { - let mut validators: Vec<_> = validators.into_iter().collect(); - ValidatorSet::sort_validators(&mut validators); + pub fn hash(&self) -> u64 { + self.0.iter().fold(0, |acc, x| acc ^ *x as u64) + } + } - Self { validators } + impl super::PublicKey for PublicKey { + fn hash(&self) -> u64 { + self.hash() + } } - /// The total voting power of the validator set - pub fn total_voting_power(&self) -> VotingPower { - // TODO: Cache this? - self.validators.iter().map(|v| v.voting_power).sum() + #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] + pub struct Address(u64); + + impl Address { + pub const fn new(value: u64) -> Self { + Self(value) + } } - /// Add a validator to the set - pub fn add(&mut self, validator: Validator) { - self.validators.push(validator); + impl super::Address for Address {} - ValidatorSet::sort_validators(&mut self.validators); + /// A validator is a public key and voting power + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct Validator { + pub address: Address, + pub public_key: PublicKey, + pub voting_power: VotingPower, } - /// Update the voting power of the given validator - pub fn update(&mut self, val: Validator) { - if let Some(v) = self - .validators - .iter_mut() - .find(|v| v.address() == val.address()) - { - v.voting_power = val.voting_power; + impl Validator { + pub fn new(public_key: PublicKey, voting_power: VotingPower) -> Self { + Self { + address: Address(public_key.hash()), + public_key, + voting_power, + } } - dbg!(self.total_voting_power()); - Self::sort_validators(&mut self.validators); - dbg!(self.total_voting_power()); + pub fn hash(&self) -> u64 { + self.public_key.hash() // TODO + } } - /// Remove a validator from the set - pub fn remove(&mut self, address: &Address) { - self.validators.retain(|v| &v.address() != address); + impl super::Validator for Validator { + fn address(&self) -> &Address { + &self.address + } - Self::sort_validators(&mut self.validators); // TODO: Not needed - } + fn public_key(&self) -> &PublicKey { + &self.public_key + } - /// Get a validator by its address - pub fn get_by_address(&self, address: &Address) -> Option<&Validator> { - self.validators.iter().find(|v| &v.address() == address) + fn voting_power(&self) -> VotingPower { + self.voting_power + } } - pub fn get_by_public_key(&self, public_key: &PublicKey) -> Option<&Validator> { - self.validators.iter().find(|v| &v.public_key == public_key) + /// A validator set contains a list of validators sorted by address. + pub struct ValidatorSet { + validators: Vec, + proposer: AtomicUsize, } - /// In place sort and deduplication of a list of validators - fn sort_validators(vals: &mut Vec) { - use core::cmp::Reverse; + impl ValidatorSet { + pub fn new(validators: impl IntoIterator) -> Self { + let mut validators: Vec<_> = validators.into_iter().collect(); + ValidatorSet::sort_validators(&mut validators); - // Sort the validators according to the current Tendermint requirements - // (v. 0.34 -> first by validator power, descending, then by address, ascending) - vals.sort_unstable_by_key(|v| (Reverse(v.voting_power), v.address())); + assert!(!validators.is_empty()); - vals.dedup(); + Self { + validators, + proposer: AtomicUsize::new(0), + } + } + + /// The total voting power of the validator set + pub fn total_voting_power(&self) -> VotingPower { + // TODO: Cache this? + self.validators.iter().map(|v| v.voting_power).sum() + } + + /// Add a validator to the set + pub fn add(&mut self, validator: Validator) { + self.validators.push(validator); + + ValidatorSet::sort_validators(&mut self.validators); + } + + /// Update the voting power of the given validator + pub fn update(&mut self, val: Validator) { + if let Some(v) = self + .validators + .iter_mut() + .find(|v| v.address == val.address) + { + v.voting_power = val.voting_power; + } + + dbg!(self.total_voting_power()); + Self::sort_validators(&mut self.validators); + dbg!(self.total_voting_power()); + } + + /// Remove a validator from the set + pub fn remove(&mut self, address: &Address) { + self.validators.retain(|v| &v.address != address); + + Self::sort_validators(&mut self.validators); // TODO: Not needed + } + + /// Get a validator by its address + pub fn get_by_address(&self, address: &Address) -> Option<&Validator> { + self.validators.iter().find(|v| &v.address == address) + } + + pub fn get_by_public_key(&self, public_key: &PublicKey) -> Option<&Validator> { + self.validators.iter().find(|v| &v.public_key == public_key) + } + + /// In place sort and deduplication of a list of validators + fn sort_validators(vals: &mut Vec) { + use core::cmp::Reverse; + + // Sort the validators according to the current Tendermint requirements + // (v. 0.34 -> first by validator power, descending, then by address, ascending) + vals.sort_unstable_by_key(|v| (Reverse(v.voting_power), v.address)); + + vals.dedup(); + } + + pub fn get_proposer(&self) -> Validator { + // TODO: Proper implementation + assert!(!self.validators.is_empty()); + let proposer = self.validators[self.proposer.load(Ordering::Relaxed)].clone(); + self.proposer.fetch_add(1, Ordering::Relaxed); + proposer + } } - pub fn get_proposer(&mut self) -> Validator { - // TODO: Proper implementation - assert!(!self.validators.is_empty()); - let proposer = self.validators[0].clone(); - self.validators.rotate_left(1); - proposer + impl super::ValidatorSet for ValidatorSet { + fn total_voting_power(&self) -> VotingPower { + self.total_voting_power() + } + + fn get_by_public_key(&self, public_key: &PublicKey) -> Option<&Validator> { + self.get_by_public_key(public_key) + } + + fn get_proposer(&self) -> Validator { + self.get_proposer() + } + + fn get_by_address(&self, address: &Address) -> Option<&Validator> { + self.get_by_address(address) + } } -} -#[cfg(test)] -mod tests { - use super::*; + #[cfg(test)] + mod tests { + use super::*; - #[test] - fn add_update_remove() { - let v1 = Validator::new(PublicKey(vec![1]), 1); - let v2 = Validator::new(PublicKey(vec![2]), 2); - let v3 = Validator::new(PublicKey(vec![3]), 3); + #[test] + fn add_update_remove() { + let v1 = Validator::new(PublicKey(vec![1]), 1); + let v2 = Validator::new(PublicKey(vec![2]), 2); + let v3 = Validator::new(PublicKey(vec![3]), 3); - let mut vs = ValidatorSet::new(vec![v1, v2, v3]); - assert_eq!(vs.total_voting_power(), 6); + let mut vs = ValidatorSet::new(vec![v1, v2, v3]); + assert_eq!(vs.total_voting_power(), 6); - let v4 = Validator::new(PublicKey(vec![4]), 4); - vs.add(v4); - assert_eq!(vs.total_voting_power(), 10); + let v4 = Validator::new(PublicKey(vec![4]), 4); + vs.add(v4); + assert_eq!(vs.total_voting_power(), 10); - let mut v5 = Validator::new(PublicKey(vec![5]), 5); - vs.update(v5.clone()); // no effect - assert_eq!(vs.total_voting_power(), 10); + let mut v5 = Validator::new(PublicKey(vec![5]), 5); + vs.update(v5.clone()); // no effect + assert_eq!(vs.total_voting_power(), 10); - vs.add(v5.clone()); - assert_eq!(vs.total_voting_power(), 15); + vs.add(v5.clone()); + assert_eq!(vs.total_voting_power(), 15); - v5.voting_power = 100; - vs.update(v5.clone()); - assert_eq!(vs.total_voting_power(), 110); + v5.voting_power = 100; + vs.update(v5.clone()); + assert_eq!(vs.total_voting_power(), 110); - vs.remove(&v5.address()); - assert_eq!(vs.total_voting_power(), 10); + vs.remove(&v5.address); + assert_eq!(vs.total_voting_power(), 10); - let v6 = Validator::new(PublicKey(vec![6]), 6); - vs.remove(&v6.address()); // no effect - assert_eq!(vs.total_voting_power(), 10); + let v6 = Validator::new(PublicKey(vec![6]), 6); + vs.remove(&v6.address); // no effect + assert_eq!(vs.total_voting_power(), 10); + } } } diff --git a/Code/common/src/value.rs b/Code/common/src/value.rs index d06ea6a53..a8351853d 100644 --- a/Code/common/src/value.rs +++ b/Code/common/src/value.rs @@ -1,30 +1,57 @@ -/// The value to decide on -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Value(u64); +use core::fmt::Debug; -impl Value { - pub fn new(value: u64) -> Self { - Self(value) - } +pub trait Value +where + Self: Clone + Debug + PartialEq + Eq + PartialOrd + Ord, +{ + type Id: Clone + Debug + PartialEq + Eq + PartialOrd + Ord; - pub fn as_u64(&self) -> u64 { - self.0 - } + fn id(&self) -> Self::Id; + + fn valid(&self) -> bool; +} + +pub mod test { + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Copy)] + pub struct ValueId(u64); - pub fn valid(&self) -> bool { - self.0 > 0 + impl ValueId { + pub const fn new(id: u64) -> Self { + Self(id) + } } - pub fn id(&self) -> ValueId { - ValueId(self.0) + /// The value to decide on + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] + pub struct Value(u64); + + impl Value { + pub const fn new(value: u64) -> Self { + Self(value) + } + + pub const fn as_u64(&self) -> u64 { + self.0 + } + + pub const fn valid(&self) -> bool { + self.0 > 0 + } + + pub const fn id(&self) -> ValueId { + ValueId(self.0) + } } -} -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Copy)] -pub struct ValueId(u64); + impl super::Value for Value { + type Id = ValueId; + + fn valid(&self) -> bool { + self.valid() + } -impl ValueId { - pub fn new(id: u64) -> Self { - Self(id) + fn id(&self) -> ValueId { + self.id() + } } } diff --git a/Code/common/src/vote.rs b/Code/common/src/vote.rs index 9ca3a6eab..4382652c6 100644 --- a/Code/common/src/vote.rs +++ b/Code/common/src/vote.rs @@ -1,4 +1,6 @@ -use crate::{Address, Round, ValueId}; +use core::fmt::Debug; + +use crate::{Consensus, Round, Value}; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum VoteType { @@ -6,31 +8,73 @@ pub enum VoteType { Precommit, } -/// A vote for a value in a round -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Vote { - pub typ: VoteType, - pub round: Round, - pub value: Option, - pub address: Address, +pub trait Vote +where + Self: Clone + Debug + PartialEq + Eq, +{ + fn round(&self) -> Round; + fn value(&self) -> Option<&::Id>; + fn vote_type(&self) -> VoteType; + + // FIXME: round message votes should not include address + fn address(&self) -> &C::Address; + fn set_address(&mut self, address: C::Address); } -impl Vote { - pub fn new_prevote(round: Round, value: Option, address: Address) -> Self { - Self { - typ: VoteType::Prevote, - round, - value, - address, +pub mod test { + use crate::test::{Address, TestConsensus, ValueId}; + use crate::Round; + + use super::VoteType; + + /// A vote for a value in a round + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct Vote { + pub typ: VoteType, + pub round: Round, + pub value: Option, + pub address: Address, + } + + impl Vote { + pub fn new_prevote(round: Round, value: Option, address: Address) -> Self { + Self { + typ: VoteType::Prevote, + round, + value, + address, + } + } + + pub fn new_precommit(round: Round, value: Option, address: Address) -> Self { + Self { + typ: VoteType::Precommit, + round, + value, + address, + } } } - pub fn new_precommit(round: Round, value: Option, address: Address) -> Self { - Self { - typ: VoteType::Precommit, - round, - value, - address, + impl super::Vote for Vote { + fn round(&self) -> Round { + self.round + } + + fn value(&self) -> Option<&ValueId> { + self.value.as_ref() + } + + fn vote_type(&self) -> VoteType { + self.typ + } + + fn address(&self) -> &Address { + &self.address + } + + fn set_address(&mut self, address: Address) { + self.address = address; } } } diff --git a/Code/consensus/src/executor.rs b/Code/consensus/src/executor.rs index d7de326f6..ccc22aebf 100644 --- a/Code/consensus/src/executor.rs +++ b/Code/consensus/src/executor.rs @@ -1,8 +1,8 @@ use std::collections::BTreeMap; -use std::sync::Arc; use malachite_common::{ - Height, Proposal, PublicKey, Round, Timeout, TimeoutStep, ValidatorSet, Value, Vote, VoteType, + Consensus, Proposal, Round, Timeout, TimeoutStep, Validator, ValidatorSet, Value, Vote, + VoteType, }; use malachite_round::events::Event as RoundEvent; use malachite_round::message::Message as RoundMessage; @@ -11,26 +11,39 @@ use malachite_vote::count::Threshold; use malachite_vote::keeper::VoteKeeper; #[derive(Clone, Debug)] -pub struct Executor { - height: Height, - key: PublicKey, - validator_set: ValidatorSet, +pub struct Executor +where + C: Consensus, +{ + height: C::Height, + key: C::PublicKey, + validator_set: C::ValidatorSet, round: Round, - votes: VoteKeeper, - round_states: BTreeMap, + votes: VoteKeeper, + round_states: BTreeMap>, } #[derive(Clone, Debug, PartialEq, Eq)] -pub enum Message { +pub enum Message +where + C: Consensus, +{ NewRound(Round), - Proposal(Proposal), - Vote(Vote), + Proposal(C::Proposal), + Vote(C::Vote), Timeout(Timeout), } -impl Executor { - pub fn new(height: Height, validator_set: ValidatorSet, key: PublicKey) -> Self { - let votes = VoteKeeper::new(height, Round::INITIAL, validator_set.total_voting_power()); +impl Executor +where + C: Consensus, +{ + pub fn new(height: C::Height, validator_set: C::ValidatorSet, key: C::PublicKey) -> Self { + let votes = VoteKeeper::new( + height.clone(), + Round::INITIAL, + validator_set.total_voting_power(), + ); Self { height, @@ -42,11 +55,12 @@ impl Executor { } } - pub fn get_value(&self) -> Value { + pub fn get_value(&self) -> C::Value { // TODO - add external interface to get the value - Value::new(9999) + C::DUMMY_VALUE } - pub fn execute(&mut self, msg: Message) -> Option { + + pub fn execute(&mut self, msg: Message) -> Option> { let round_msg = match self.apply(msg) { Some(msg) => msg, None => return None, @@ -57,7 +71,7 @@ impl Executor { // TODO: check if we are the proposer self.round_states - .insert(round, RoundState::new(self.height).new_round(round)); + .insert(round, RoundState::new(self.height.clone()).new_round(round)); None } RoundMessage::Proposal(p) => { @@ -66,8 +80,16 @@ impl Executor { } RoundMessage::Vote(mut v) => { // sign the vote - // TODO - round message votes should not include address - v.address = self.validator_set.get_by_public_key(&self.key)?.address(); + + // FIXME: round message votes should not include address + let address = self + .validator_set + .get_by_public_key(&self.key)? + .address() + .clone(); + + v.set_address(address); + Some(Message::Vote(v)) } RoundMessage::Timeout(_) => { @@ -81,7 +103,7 @@ impl Executor { } } - fn apply(&mut self, msg: Message) -> Option { + fn apply(&mut self, msg: Message) -> Option> { match msg { Message::NewRound(round) => self.apply_new_round(round), Message::Proposal(proposal) => self.apply_proposal(proposal), @@ -90,9 +112,9 @@ impl Executor { } } - fn apply_new_round(&mut self, round: Round) -> Option { + fn apply_new_round(&mut self, round: Round) -> Option> { let proposer = self.validator_set.get_proposer(); - let event = if proposer.public_key == self.key { + let event = if proposer.public_key() == &self.key { let value = self.get_value(); RoundEvent::NewRoundProposer(value) } else { @@ -101,9 +123,8 @@ impl Executor { self.apply_event(round, event) } - fn apply_proposal(&mut self, proposal: Proposal) -> Option { + fn apply_proposal(&mut self, proposal: C::Proposal) -> Option> { // TODO: Check for invalid proposal - let round = proposal.round; let event = RoundEvent::Proposal(proposal.clone()); let Some(round_state) = self.round_states.get(&self.round) else { @@ -115,46 +136,45 @@ impl Executor { return None; } - if round_state.height != proposal.height || proposal.round != self.round { + if round_state.height != proposal.height() || proposal.round() != self.round { return None; } - if !proposal.pol_round.is_valid() - || proposal.pol_round.is_defined() && proposal.pol_round >= round_state.round + if !proposal.pol_round().is_valid() + || proposal.pol_round().is_defined() && proposal.pol_round() >= round_state.round { return None; } // TODO verify proposal signature (make some of these checks part of message validation) - - match proposal.pol_round { + match proposal.pol_round() { Round::None => { // Is it possible to get +2/3 prevotes before the proposal? // Do we wait for our own prevote to check the threshold? - self.apply_event(round, event) + self.apply_event(proposal.round(), event) } Round::Some(_) if self.votes.check_threshold( - &proposal.pol_round, + &proposal.pol_round(), VoteType::Prevote, - Threshold::Value(Arc::from(proposal.value.id())), + Threshold::Value(proposal.value().id()), ) => { - self.apply_event(round, event) + self.apply_event(proposal.round(), event) } _ => None, } } - fn apply_vote(&mut self, vote: Vote) -> Option { - let Some(validator) = self.validator_set.get_by_address(&vote.address) else { + fn apply_vote(&mut self, vote: C::Vote) -> Option> { + let Some(validator) = self.validator_set.get_by_address(vote.address()) else { // TODO: Is this the correct behavior? How to log such "errors"? return None; }; - let round = vote.round; + let round = vote.round(); - let event = match self.votes.apply_vote(vote, validator.voting_power) { + let event = match self.votes.apply_vote(vote, validator.voting_power()) { Some(event) => event, None => return None, }; @@ -162,7 +182,7 @@ impl Executor { self.apply_event(round, event) } - fn apply_timeout(&mut self, timeout: Timeout) -> Option { + fn apply_timeout(&mut self, timeout: Timeout) -> Option> { let event = match timeout.step { TimeoutStep::Propose => RoundEvent::TimeoutPropose, TimeoutStep::Prevote => RoundEvent::TimeoutPrevote, @@ -173,12 +193,12 @@ impl Executor { } /// Apply the event, update the state. - fn apply_event(&mut self, round: Round, event: RoundEvent) -> Option { + fn apply_event(&mut self, round: Round, event: RoundEvent) -> Option> { // Get the round state, or create a new one let round_state = self .round_states .remove(&round) - .unwrap_or_else(|| RoundState::new(self.height)); + .unwrap_or_else(|| RoundState::new(self.height.clone())); // Apply the event to the round state machine let transition = round_state.apply_event(round, event); @@ -193,10 +213,13 @@ impl Executor { #[cfg(test)] mod tests { - use super::*; - use malachite_common::{Proposal, Validator, Value}; + use malachite_common::test::{ + Height, Proposal, PublicKey, TestConsensus, Validator, ValidatorSet, Value, Vote, + }; use malachite_round::state::{RoundValue, State, Step}; + use super::*; + #[test] fn test_executor_steps() { let value = Value::new(9999); // TODO: get value from external source @@ -204,7 +227,7 @@ mod tests { let v1 = Validator::new(PublicKey::new(vec![1]), 1); let v2 = Validator::new(PublicKey::new(vec![2]), 1); let v3 = Validator::new(PublicKey::new(vec![3]), 1); - let my_address = v1.clone().address(); + let my_address = v1.address; let key = v1.clone().public_key; // we are proposer let vs = ValidatorSet::new(vec![v1, v2.clone(), v3.clone()]); @@ -213,9 +236,9 @@ mod tests { let proposal = Proposal::new(Height::new(1), Round::new(0), value.clone(), Round::new(-1)); struct TestStep { - input_message: Option, - expected_output_message: Option, - new_state: State, + input_message: Option>, + expected_output_message: Option>, + new_state: State, } let steps: Vec = vec![ // Start round 0, we are proposer, propose value @@ -266,7 +289,7 @@ mod tests { input_message: Some(Message::Vote(Vote::new_prevote( Round::new(0), Some(value_id), - v2.clone().address(), + v2.address, ))), expected_output_message: None, new_state: State { @@ -283,7 +306,7 @@ mod tests { input_message: Some(Message::Vote(Vote::new_prevote( Round::new(0), Some(value_id), - v3.clone().address(), + v3.address, ))), expected_output_message: Some(Message::Vote(Vote::new_precommit( Round::new(0), diff --git a/Code/round/src/events.rs b/Code/round/src/events.rs index aeb9ea8b8..0ca8fd944 100644 --- a/Code/round/src/events.rs +++ b/Code/round/src/events.rs @@ -1,20 +1,18 @@ -use malachite_common::Proposal; - -use crate::{Value, ValueId}; +use malachite_common::{Consensus, ValueId}; #[derive(Clone, Debug, PartialEq, Eq)] -pub enum Event { - NewRound, // Start a new round, not as proposer. - NewRoundProposer(Value), // Start a new round and propose the Value. - Proposal(Proposal), // Receive a proposal with possible polka round. - ProposalInvalid, // Receive an invalid proposal. - PolkaAny, // Receive +2/3 prevotes for anything. - PolkaNil, // Receive +2/3 prevotes for nil. - PolkaValue(ValueId), // Receive +2/3 prevotes for Value. - PrecommitAny, // Receive +2/3 precommits for anything. - PrecommitValue(ValueId), // Receive +2/3 precommits for Value. - RoundSkip, // Receive +1/3 votes from a higher round. - TimeoutPropose, // Timeout waiting for proposal. - TimeoutPrevote, // Timeout waiting for prevotes. - TimeoutPrecommit, // Timeout waiting for precommits. +pub enum Event { + NewRound, // Start a new round, not as proposer. + NewRoundProposer(C::Value), // Start a new round and propose the Value. + Proposal(C::Proposal), // Receive a proposal with possible polka round. + ProposalInvalid, // Receive an invalid proposal. + PolkaAny, // Receive +2/3 prevotes for anything. + PolkaNil, // Receive +2/3 prevotes for nil. + PolkaValue(ValueId), // Receive +2/3 prevotes for Value. + PrecommitAny, // Receive +2/3 precommits for anything. + PrecommitValue(ValueId), // Receive +2/3 precommits for Value. + RoundSkip, // Receive +1/3 votes from a higher round. + TimeoutPropose, // Timeout waiting for proposal. + TimeoutPrevote, // Timeout waiting for prevotes. + TimeoutPrecommit, // Timeout waiting for precommits. } diff --git a/Code/round/src/lib.rs b/Code/round/src/lib.rs index dca5bb0e8..5df153bb7 100644 --- a/Code/round/src/lib.rs +++ b/Code/round/src/lib.rs @@ -10,8 +10,6 @@ )] #![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::panic))] -pub use malachite_common::*; - pub mod events; pub mod message; pub mod state; diff --git a/Code/round/src/message.rs b/Code/round/src/message.rs index 4c3b62a0e..2fb422cf1 100644 --- a/Code/round/src/message.rs +++ b/Code/round/src/message.rs @@ -1,39 +1,52 @@ -use malachite_common::{Address, Height}; +use malachite_common::{Consensus, Round, Timeout, TimeoutStep, ValueId}; -use crate::{state::RoundValue, Proposal, Round, Timeout, TimeoutStep, Value, ValueId, Vote}; +use crate::state::RoundValue; -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum Message { - NewRound(Round), // Move to the new round. - Proposal(Proposal), // Broadcast the proposal. - Vote(Vote), // Broadcast the vote. - Timeout(Timeout), // Schedule the timeout. - Decision(RoundValue), // Decide the value. +#[derive(Debug, PartialEq, Eq)] +pub enum Message { + NewRound(Round), // Move to the new round. + Proposal(C::Proposal), // Broadcast the proposal. + Vote(C::Vote), // Broadcast the vote. + Timeout(Timeout), // Schedule the timeout. + Decision(RoundValue), // Decide the value. } -impl Message { - pub fn proposal(height: Height, round: Round, value: Value, pol_round: Round) -> Message { - Message::Proposal(Proposal { - height, - round, - value, - pol_round, - }) +impl Clone for Message +where + C: Consensus, +{ + fn clone(&self) -> Self { + match self { + Message::NewRound(round) => Message::NewRound(*round), + Message::Proposal(proposal) => Message::Proposal(proposal.clone()), + Message::Vote(vote) => Message::Vote(vote.clone()), + Message::Timeout(timeout) => Message::Timeout(*timeout), + Message::Decision(round_value) => Message::Decision(round_value.clone()), + } + } +} + +impl Message +where + C: Consensus, +{ + pub fn proposal(height: C::Height, round: Round, value: C::Value, pol_round: Round) -> Self { + Message::Proposal(C::new_proposal(height, round, value, pol_round)) } - pub fn prevote(round: Round, value_id: Option, address: Address) -> Message { - Message::Vote(Vote::new_prevote(round, value_id, address)) + pub fn prevote(round: Round, value_id: Option>, address: C::Address) -> Self { + Message::Vote(C::new_prevote(round, value_id, address)) } - pub fn precommit(round: Round, value_id: Option, address: Address) -> Message { - Message::Vote(Vote::new_precommit(round, value_id, address)) + pub fn precommit(round: Round, value_id: Option>, address: C::Address) -> Self { + Message::Vote(C::new_precommit(round, value_id, address)) } - pub fn timeout(round: Round, step: TimeoutStep) -> Message { + pub fn timeout(round: Round, step: TimeoutStep) -> Self { Message::Timeout(Timeout { round, step }) } - pub fn decision(round: Round, value: Value) -> Message { + pub fn decision(round: Round, value: C::Value) -> Self { Message::Decision(RoundValue { round, value }) } } diff --git a/Code/round/src/state.rs b/Code/round/src/state.rs index f77bb053f..f543a8854 100644 --- a/Code/round/src/state.rs +++ b/Code/round/src/state.rs @@ -1,16 +1,16 @@ use crate::events::Event; use crate::state_machine::Transition; -use crate::{Height, Round, Value}; -use malachite_common::Proposal; + +use malachite_common::{Consensus, Round}; /// A value and its associated round #[derive(Clone, Debug, PartialEq, Eq)] -pub struct RoundValue { +pub struct RoundValue { pub value: Value, pub round: Round, } -impl RoundValue { +impl RoundValue { pub fn new(value: Value, round: Round) -> Self { Self { value, round } } @@ -27,18 +27,37 @@ pub enum Step { } /// The state of the consensus state machine -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct State { - pub height: Height, +#[derive(Debug, PartialEq, Eq)] +pub struct State { + pub height: C::Height, pub round: Round, pub step: Step, - pub proposal: Option, - pub locked: Option, - pub valid: Option, + pub proposal: Option, + pub locked: Option>, + pub valid: Option>, +} + +impl Clone for State +where + C: Consensus, +{ + fn clone(&self) -> Self { + Self { + height: self.height.clone(), + round: self.round, + step: self.step, + proposal: self.proposal.clone(), + locked: self.locked.clone(), + valid: self.valid.clone(), + } + } } -impl State { - pub fn new(height: Height) -> Self { +impl State +where + C: Consensus, +{ + pub fn new(height: C::Height) -> Self { Self { height, round: Round::INITIAL, @@ -75,21 +94,21 @@ impl State { } } - pub fn set_locked(self, value: Value) -> Self { + pub fn set_locked(self, value: C::Value) -> Self { Self { locked: Some(RoundValue::new(value, self.round)), ..self } } - pub fn set_valid(self, value: Value) -> Self { + pub fn set_valid(self, value: C::Value) -> Self { Self { valid: Some(RoundValue::new(value, self.round)), ..self } } - pub fn apply_event(self, round: Round, event: Event) -> Transition { + pub fn apply_event(self, round: Round, event: Event) -> Transition { crate::state_machine::apply_event(self, round, event) } } diff --git a/Code/round/src/state_machine.rs b/Code/round/src/state_machine.rs index fc6c7ad91..1f05d47a9 100644 --- a/Code/round/src/state_machine.rs +++ b/Code/round/src/state_machine.rs @@ -1,23 +1,25 @@ -use malachite_common::Address; +use malachite_common::{Consensus, Proposal, Round, TimeoutStep, Value, ValueId}; use crate::events::Event; use crate::message::Message; use crate::state::{State, Step}; -use crate::{Round, TimeoutStep, Value, ValueId}; // FIXME: Where to get the address/public key from? // IDEA: Add a Context parameter to `apply_state` -const ADDRESS: Address = Address::new(42); +// const ADDRESS: Address = Address::new(42); #[derive(Clone, Debug, PartialEq, Eq)] -pub struct Transition { - pub state: State, - pub message: Option, +pub struct Transition { + pub state: State, + pub message: Option>, pub valid: bool, } -impl Transition { - pub fn to(state: State) -> Self { +impl Transition +where + C: Consensus, +{ + pub fn to(state: State) -> Self { Self { state, message: None, @@ -25,7 +27,7 @@ impl Transition { } } - pub fn invalid(state: State) -> Self { + pub fn invalid(state: State) -> Self { Self { state, message: None, @@ -33,14 +35,17 @@ impl Transition { } } - pub fn with_message(mut self, message: Message) -> Self { + pub fn with_message(mut self, message: Message) -> Self { self.message = Some(message); self } } /// Check that a proposal has a valid Proof-Of-Lock round -fn is_valid_pol_round(state: &State, pol_round: Round) -> bool { +fn is_valid_pol_round(state: &State, pol_round: Round) -> bool +where + C: Consensus, +{ pol_round.is_defined() && pol_round < state.round } @@ -52,7 +57,10 @@ fn is_valid_pol_round(state: &State, pol_round: Round) -> bool { /// Valid transitions result in at least a change to the state and/or an output message. /// /// Commented numbers refer to line numbers in the spec paper. -pub fn apply_event(mut state: State, round: Round, event: Event) -> Transition { +pub fn apply_event(mut state: State, round: Round, event: Event) -> Transition +where + C: Consensus, +{ let this_round = state.round == round; match (state.step, event) { @@ -61,23 +69,25 @@ pub fn apply_event(mut state: State, round: Round, event: Event) -> Transition { (Step::NewRound, Event::NewRound) if this_round => schedule_timeout_propose(state), // L11/L20 // From Propose. Event must be for current round. - (Step::Propose, Event::Proposal(proposal)) if this_round && proposal.pol_round.is_nil() => { + (Step::Propose, Event::Proposal(proposal)) + if this_round && proposal.pol_round().is_nil() => + { // L22 - if proposal.value.valid() + if proposal.value().valid() && state .locked .as_ref() - .map_or(true, |locked| locked.value == proposal.value) + .map_or(true, |locked| &locked.value == proposal.value()) { state.proposal = Some(proposal.clone()); - prevote(state, proposal.round, proposal.value.id()) + prevote(state, proposal.round(), proposal.value().id()) } else { prevote_nil(state) } } (Step::Propose, Event::Proposal(proposal)) - if this_round && is_valid_pol_round(&state, proposal.pol_round) => + if this_round && is_valid_pol_round(&state, proposal.pol_round()) => { // L28 let Some(locked) = state.locked.as_ref() else { @@ -85,10 +95,10 @@ pub fn apply_event(mut state: State, round: Round, event: Event) -> Transition { return Transition::invalid(state); }; - if proposal.value.valid() - && (locked.round <= proposal.pol_round || locked.value == proposal.value) + if proposal.value().valid() + && (locked.round <= proposal.pol_round() || &locked.value == proposal.value()) { - prevote(state, proposal.round, proposal.value.id()) + prevote(state, proposal.round(), proposal.value().id()) } else { prevote_nil(state) } @@ -129,13 +139,16 @@ pub fn apply_event(mut state: State, round: Round, event: Event) -> Transition { /// otherwise propose the given value. /// /// Ref: L11/L14 -pub fn propose(state: State, value: Value) -> Transition { +pub fn propose(state: State, value: C::Value) -> Transition +where + C: Consensus, +{ let (value, pol_round) = match &state.valid { Some(round_value) => (round_value.value.clone(), round_value.round), None => (value, Round::None), }; - let proposal = Message::proposal(state.height, state.round, value, pol_round); + let proposal = Message::proposal(state.height.clone(), state.round, value, pol_round); Transition::to(state.next_step()).with_message(proposal) } @@ -147,7 +160,10 @@ pub fn propose(state: State, value: Value) -> Transition { /// unless we are locked on something else at a higher round. /// /// Ref: L22/L28 -pub fn prevote(state: State, vr: Round, proposed: ValueId) -> Transition { +pub fn prevote(state: State, vr: Round, proposed: ValueId) -> Transition +where + C: Consensus, +{ let value = match &state.locked { Some(locked) if locked.round <= vr => Some(proposed), // unlock and prevote Some(locked) if locked.value.id() == proposed => Some(proposed), // already locked on value @@ -155,15 +171,18 @@ pub fn prevote(state: State, vr: Round, proposed: ValueId) -> Transition { None => Some(proposed), // not locked, prevote the value }; - let message = Message::prevote(state.round, value, ADDRESS); + let message = Message::prevote(state.round, value, C::DUMMY_ADDRESS); Transition::to(state.next_step()).with_message(message) } /// Received a complete proposal for an empty or invalid value, or timed out; prevote nil. /// /// Ref: L22/L25, L28/L31, L57 -pub fn prevote_nil(state: State) -> Transition { - let message = Message::prevote(state.round, None, ADDRESS); +pub fn prevote_nil(state: State) -> Transition +where + C: Consensus, +{ + let message = Message::prevote(state.round, None, C::DUMMY_ADDRESS); Transition::to(state.next_step()).with_message(message) } @@ -177,13 +196,16 @@ pub fn prevote_nil(state: State) -> Transition { /// /// NOTE: Only one of this and set_valid_value should be called once in a round /// How do we enforce this? -pub fn precommit(state: State, value_id: ValueId) -> Transition { - let message = Message::precommit(state.round, Some(value_id), ADDRESS); +pub fn precommit(state: State, value_id: ValueId) -> Transition +where + C: Consensus, +{ + let message = Message::precommit(state.round, Some(value_id), C::DUMMY_ADDRESS); let Some(value) = state .proposal .as_ref() - .map(|proposal| proposal.value.clone()) + .map(|proposal| proposal.value().clone()) else { // TODO: Add logging return Transition::invalid(state); @@ -197,8 +219,11 @@ pub fn precommit(state: State, value_id: ValueId) -> Transition { /// Received a polka for nil or timed out of prevote; precommit nil. /// /// Ref: L44, L61 -pub fn precommit_nil(state: State) -> Transition { - let message = Message::precommit(state.round, None, ADDRESS); +pub fn precommit_nil(state: State) -> Transition +where + C: Consensus, +{ + let message = Message::precommit(state.round, None, C::DUMMY_ADDRESS); Transition::to(state.next_step()).with_message(message) } @@ -209,7 +234,10 @@ pub fn precommit_nil(state: State) -> Transition { /// We're not the proposer; schedule timeout propose. /// /// Ref: L11, L20 -pub fn schedule_timeout_propose(state: State) -> Transition { +pub fn schedule_timeout_propose(state: State) -> Transition +where + C: Consensus, +{ let timeout = Message::timeout(state.round, TimeoutStep::Propose); Transition::to(state.next_step()).with_message(timeout) } @@ -220,7 +248,10 @@ pub fn schedule_timeout_propose(state: State) -> Transition { /// /// NOTE: This should only be called once in a round, per the spec, /// but it's harmless to schedule more timeouts -pub fn schedule_timeout_prevote(state: State) -> Transition { +pub fn schedule_timeout_prevote(state: State) -> Transition +where + C: Consensus, +{ let message = Message::timeout(state.round, TimeoutStep::Prevote); Transition::to(state.next_step()).with_message(message) } @@ -228,7 +259,10 @@ pub fn schedule_timeout_prevote(state: State) -> Transition { /// We received +2/3 precommits for any; schedule timeout precommit. /// /// Ref: L47 -pub fn schedule_timeout_precommit(state: State) -> Transition { +pub fn schedule_timeout_precommit(state: State) -> Transition +where + C: Consensus, +{ let message = Message::timeout(state.round, TimeoutStep::Precommit); Transition::to(state.next_step()).with_message(message) } @@ -243,9 +277,11 @@ pub fn schedule_timeout_precommit(state: State) -> Transition { /// Ref: L36/L42 /// /// NOTE: only one of this and precommit should be called once in a round -pub fn set_valid_value(state: State, value_id: ValueId) -> Transition { - // check that we're locked on this value - +pub fn set_valid_value(state: State, value_id: ValueId) -> Transition +where + C: Consensus, +{ + // Check that we're locked on this value let Some(locked) = state.locked.as_ref() else { // TODO: Add logging return Transition::invalid(state); @@ -267,14 +303,20 @@ pub fn set_valid_value(state: State, value_id: ValueId) -> Transition { /// from a higher round. Move to the higher round. /// /// Ref: 65 -pub fn round_skip(state: State, round: Round) -> Transition { +pub fn round_skip(state: State, round: Round) -> Transition +where + C: Consensus, +{ Transition::to(state.new_round(round)).with_message(Message::NewRound(round)) } /// We received +2/3 precommits for a value - commit and decide that value! /// /// Ref: L49 -pub fn commit(state: State, round: Round, value_id: ValueId) -> Transition { +pub fn commit(state: State, round: Round, value_id: ValueId) -> Transition +where + C: Consensus, +{ // Check that we're locked on this value let Some(locked) = state.locked.as_ref() else { // TODO: Add logging @@ -292,14 +334,15 @@ pub fn commit(state: State, round: Round, value_id: ValueId) -> Transition { #[cfg(test)] mod tests { - use malachite_common::{Height, Proposal, Timeout}; - use super::*; + use malachite_common::test::{Height, Proposal, TestConsensus, Value}; + use malachite_common::{Timeout, TimeoutStep}; + #[test] fn test_propose() { let value = Value::new(42); - let mut state = State::new(Height::new(10)); + let mut state: State = State::new(Height::new(10)); let transition = apply_event(state.clone(), Round::new(0), Event::NewRoundProposer(value)); @@ -315,7 +358,7 @@ mod tests { #[test] fn test_prevote() { let value = Value::new(42); - let state = State::new(Height::new(1)).new_round(Round::new(1)); + let state: State = State::new(Height::new(1)).new_round(Round::new(1)); let transition = apply_event(state, Round::new(1), Event::NewRound); @@ -344,7 +387,11 @@ mod tests { assert_eq!(transition.state.step, Step::Prevote); assert_eq!( transition.message.unwrap(), - Message::prevote(Round::new(1), Some(value.id()), ADDRESS) + Message::prevote( + Round::new(1), + Some(value.id()), + TestConsensus::DUMMY_ADDRESS + ) ); } } diff --git a/Code/vote/src/count.rs b/Code/vote/src/count.rs index 32e7310f6..f2a22c9bf 100644 --- a/Code/vote/src/count.rs +++ b/Code/vote/src/count.rs @@ -1,25 +1,27 @@ use alloc::collections::BTreeMap; -use alloc::sync::Arc; -use malachite_common::{ValueId, Vote}; +use malachite_common::{Consensus, ValueId, Vote}; pub type Weight = u64; /// A value and the weight of votes for it. #[derive(Clone, Debug, PartialEq, Eq)] -pub struct ValuesWeights { - value_weights: BTreeMap, Weight>, +pub struct ValuesWeights { + value_weights: BTreeMap, } -impl ValuesWeights { - pub fn new() -> ValuesWeights { +impl ValuesWeights { + pub fn new() -> ValuesWeights { ValuesWeights { value_weights: BTreeMap::new(), } } /// Add weight to the value and return the new weight. - pub fn add_weight(&mut self, value: Arc, weight: Weight) -> Weight { + pub fn add_weight(&mut self, value: ValueId, weight: Weight) -> Weight + where + ValueId: Ord, + { let entry = self.value_weights.entry(value).or_insert(0); *entry += weight; *entry @@ -30,11 +32,11 @@ impl ValuesWeights { self.value_weights .iter() .max_by_key(|(_, weight)| *weight) - .map(|(value, weight)| (value.as_ref(), *weight)) + .map(|(value, weight)| (value, *weight)) } } -impl Default for ValuesWeights { +impl Default for ValuesWeights { fn default() -> Self { Self::new() } @@ -43,17 +45,23 @@ impl Default for ValuesWeights { /// VoteCount tallys votes of the same type. /// Votes are for nil or for some value. #[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct VoteCount { +pub struct VoteCount +where + C: Consensus, +{ // Weight of votes for nil pub nil: Weight, /// Weight of votes for the values - pub values_weights: ValuesWeights, + pub values_weights: ValuesWeights>, /// Total weight pub total: Weight, } -impl VoteCount { - pub fn new(total: Weight) -> VoteCount { +impl VoteCount +where + C: Consensus, +{ + pub fn new(total: Weight) -> Self { VoteCount { nil: 0, total, @@ -62,14 +70,13 @@ impl VoteCount { } /// Add vote to internal counters and return the highest threshold. - pub fn add_vote(&mut self, vote: Vote, weight: Weight) -> Threshold { - if let Some(value) = vote.value { - let value = Arc::new(value); + pub fn add_vote(&mut self, vote: C::Vote, weight: Weight) -> Threshold> { + if let Some(value) = vote.value() { let new_weight = self.values_weights.add_weight(value.clone(), weight); // Check if we have a quorum for this value. if is_quorum(new_weight, self.total) { - return Threshold::Value(value); + return Threshold::Value(value.clone()); } } else { self.nil += weight; @@ -90,7 +97,7 @@ impl VoteCount { // No quorum Threshold::Init } - pub fn check_threshold(&self, threshold: Threshold) -> bool { + pub fn check_threshold(&self, threshold: Threshold>) -> bool { match threshold { Threshold::Init => false, Threshold::Any => self.values_weights.highest_weighted_value().is_some(), @@ -106,7 +113,7 @@ impl VoteCount { // Thresh represents the different quorum thresholds. #[derive(Clone, Debug, PartialEq, Eq)] -pub enum Threshold { +pub enum Threshold { /// No quorum Init, // no quorum /// Qorum of votes but not for the same value @@ -114,7 +121,7 @@ pub enum Threshold { /// Quorum for nil Nil, /// Quorum for a value - Value(Arc), + Value(ValueId), } /// Returns whether or note `value > (2/3)*total`. @@ -124,7 +131,8 @@ pub fn is_quorum(value: Weight, total: Weight) -> bool { #[cfg(test)] mod tests { - use malachite_common::{Address, Height, Round}; + use malachite_common::test::{Address, Height, TestConsensus, ValueId, Vote}; + use malachite_common::Round; use crate::RoundVotes; @@ -134,7 +142,8 @@ mod tests { fn add_votes_nil() { let total = 3; - let mut round_votes = RoundVotes::new(Height::new(1), Round::new(0), total); + let mut round_votes: RoundVotes = + RoundVotes::new(Height::new(1), Round::new(0), total); // add a vote for nil. nothing changes. let vote = Vote::new_prevote(Round::new(0), None, Address::new(1)); @@ -157,7 +166,8 @@ mod tests { let total = 4; let weight = 1; - let mut round_votes = RoundVotes::new(Height::new(1), Round::new(0), total); + let mut round_votes: RoundVotes = + RoundVotes::new(Height::new(1), Round::new(0), total); // add a vote. nothing changes. let vote = Vote::new_prevote(Round::new(0), val, Address::new(1)); @@ -175,7 +185,7 @@ mod tests { // add vote for value, get Thresh::Value let thresh = round_votes.add_vote(vote, weight); - assert_eq!(thresh, Threshold::Value(Arc::new(v))); + assert_eq!(thresh, Threshold::Value(v)); } #[test] @@ -186,7 +196,8 @@ mod tests { let val2 = Some(v2); let total = 15; - let mut round_votes = RoundVotes::new(Height::new(1), Round::new(0), total); + let mut round_votes: RoundVotes = + RoundVotes::new(Height::new(1), Round::new(0), total); // add a vote for v1. nothing changes. let vote1 = Vote::new_precommit(Round::new(0), val1, Address::new(1)); @@ -213,6 +224,6 @@ mod tests { // add a big vote for v2. get Value(v2) let thresh = round_votes.add_vote(vote2.clone(), 10); - assert_eq!(thresh, Threshold::Value(Arc::new(v2))); + assert_eq!(thresh, Threshold::Value(v2)); } } diff --git a/Code/vote/src/keeper.rs b/Code/vote/src/keeper.rs index d24a5b778..964eb1f55 100644 --- a/Code/vote/src/keeper.rs +++ b/Code/vote/src/keeper.rs @@ -1,6 +1,6 @@ use alloc::collections::BTreeMap; -use malachite_common::{Height, Round, Vote, VoteType}; +use malachite_common::{Consensus, Round, ValueId, Vote, VoteType}; use malachite_round::events::Event; use crate::{ @@ -10,17 +10,23 @@ use crate::{ /// Keeps track of votes and emits events when thresholds are reached. #[derive(Clone, Debug)] -pub struct VoteKeeper { - height: Height, +pub struct VoteKeeper +where + C: Consensus, +{ + height: C::Height, total_weight: Weight, - rounds: BTreeMap, + rounds: BTreeMap>, } -impl VoteKeeper { - pub fn new(height: Height, round: Round, total_weight: Weight) -> Self { +impl VoteKeeper +where + C: Consensus, +{ + pub fn new(height: C::Height, round: Round, total_weight: Weight) -> Self { let mut rounds = BTreeMap::new(); - rounds.insert(round, RoundVotes::new(height, round, total_weight)); + rounds.insert(round, RoundVotes::new(height.clone(), round, total_weight)); VoteKeeper { height, @@ -30,13 +36,12 @@ impl VoteKeeper { } /// Apply a vote with a given weight, potentially triggering an event. - pub fn apply_vote(&mut self, vote: Vote, weight: Weight) -> Option { - let round = self - .rounds - .entry(vote.round) - .or_insert_with(|| RoundVotes::new(self.height, vote.round, self.total_weight)); + pub fn apply_vote(&mut self, vote: C::Vote, weight: Weight) -> Option> { + let round = self.rounds.entry(vote.round()).or_insert_with(|| { + RoundVotes::new(self.height.clone(), vote.round(), self.total_weight) + }); - let vote_type = vote.typ; + let vote_type = vote.vote_type(); let threshold = round.add_vote(vote, weight); Self::to_event(vote_type, threshold) @@ -46,7 +51,7 @@ impl VoteKeeper { &self, round: &Round, vote_type: VoteType, - threshold: Threshold, + threshold: Threshold>, ) -> bool { let round = match self.rounds.get(round) { Some(round) => round, @@ -60,30 +65,31 @@ impl VoteKeeper { } /// Map a vote type and a threshold to a state machine event. - fn to_event(typ: VoteType, threshold: Threshold) -> Option { + fn to_event(typ: VoteType, threshold: Threshold>) -> Option> { match (typ, threshold) { (_, Threshold::Init) => None, (VoteType::Prevote, Threshold::Any) => Some(Event::PolkaAny), (VoteType::Prevote, Threshold::Nil) => Some(Event::PolkaNil), - (VoteType::Prevote, Threshold::Value(v)) => Some(Event::PolkaValue(*v.as_ref())), + (VoteType::Prevote, Threshold::Value(v)) => Some(Event::PolkaValue(v)), (VoteType::Precommit, Threshold::Any) => Some(Event::PrecommitAny), (VoteType::Precommit, Threshold::Nil) => None, - (VoteType::Precommit, Threshold::Value(v)) => Some(Event::PrecommitValue(*v.as_ref())), + (VoteType::Precommit, Threshold::Value(v)) => Some(Event::PrecommitValue(v)), } } } #[cfg(test)] mod tests { - use malachite_common::{Address, ValueId}; + use malachite_common::test::{Address, Height, TestConsensus, ValueId, Vote}; use super::*; #[test] fn prevote_apply_nil() { - let mut keeper = VoteKeeper::new(Height::new(1), Round::INITIAL, 3); + let mut keeper: VoteKeeper = + VoteKeeper::new(Height::new(1), Round::INITIAL, 3); let vote = Vote::new_prevote(Round::new(0), None, Address::new(1)); @@ -99,7 +105,8 @@ mod tests { #[test] fn precommit_apply_nil() { - let mut keeper = VoteKeeper::new(Height::new(1), Round::INITIAL, 3); + let mut keeper: VoteKeeper = + VoteKeeper::new(Height::new(1), Round::INITIAL, 3); let vote = Vote::new_precommit(Round::new(0), None, Address::new(1)); @@ -115,7 +122,8 @@ mod tests { #[test] fn prevote_apply_single_value() { - let mut keeper = VoteKeeper::new(Height::new(1), Round::INITIAL, 4); + let mut keeper: VoteKeeper = + VoteKeeper::new(Height::new(1), Round::INITIAL, 4); let v = ValueId::new(1); let val = Some(v); @@ -137,7 +145,8 @@ mod tests { #[test] fn precommit_apply_single_value() { - let mut keeper = VoteKeeper::new(Height::new(1), Round::INITIAL, 4); + let mut keeper: VoteKeeper = + VoteKeeper::new(Height::new(1), Round::INITIAL, 4); let v = ValueId::new(1); let val = Some(v); diff --git a/Code/vote/src/lib.rs b/Code/vote/src/lib.rs index b0cd03df0..74b514a05 100644 --- a/Code/vote/src/lib.rs +++ b/Code/vote/src/lib.rs @@ -15,22 +15,28 @@ extern crate alloc; pub mod count; pub mod keeper; -use malachite_common::{Height, Round, Vote, VoteType}; +use malachite_common::{Consensus, Round, ValueId, Vote, VoteType}; use crate::count::{Threshold, VoteCount, Weight}; /// Tracks all the votes for a single round #[derive(Clone, Debug)] -pub struct RoundVotes { - pub height: Height, +pub struct RoundVotes +where + C: Consensus, +{ + pub height: C::Height, pub round: Round, - pub prevotes: VoteCount, - pub precommits: VoteCount, + pub prevotes: VoteCount, + pub precommits: VoteCount, } -impl RoundVotes { - pub fn new(height: Height, round: Round, total: Weight) -> RoundVotes { +impl RoundVotes +where + C: Consensus, +{ + pub fn new(height: C::Height, round: Round, total: Weight) -> Self { RoundVotes { height, round, @@ -39,8 +45,8 @@ impl RoundVotes { } } - pub fn add_vote(&mut self, vote: Vote, weight: Weight) -> Threshold { - match vote.typ { + pub fn add_vote(&mut self, vote: C::Vote, weight: Weight) -> Threshold> { + match vote.vote_type() { VoteType::Prevote => self.prevotes.add_vote(vote, weight), VoteType::Precommit => self.precommits.add_vote(vote, weight), }