diff --git a/Code/common/src/round.rs b/Code/common/src/round.rs index e3c4d7735..7b7e6e879 100644 --- a/Code/common/src/round.rs +++ b/Code/common/src/round.rs @@ -5,7 +5,7 @@ use core::cmp; /// Can be either: /// - `Round::Nil` (ie. `-1`) /// - `Round::Some(r)` where `r >= 0` -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum Round { /// No round, ie. `-1` Nil, diff --git a/Code/common/src/validator_set.rs b/Code/common/src/validator_set.rs index 092ac4dc8..0c21dccf9 100644 --- a/Code/common/src/validator_set.rs +++ b/Code/common/src/validator_set.rs @@ -12,7 +12,7 @@ pub type VotingPower = u64; /// TODO: Keep this trait or just add the bounds to Consensus::Address? pub trait Address where - Self: Clone + Debug + PartialEq + Eq, + Self: Clone + Debug + Eq + Ord, { } diff --git a/Code/consensus/src/executor.rs b/Code/consensus/src/executor.rs index af3a9b0de..029dd77c4 100644 --- a/Code/consensus/src/executor.rs +++ b/Code/consensus/src/executor.rs @@ -11,9 +11,9 @@ use malachite_common::{ use malachite_round::events::Event as RoundEvent; use malachite_round::message::Message as RoundMessage; use malachite_round::state::State as RoundState; -use malachite_vote::count::Threshold; use malachite_vote::keeper::Message as VoteMessage; use malachite_vote::keeper::VoteKeeper; +use malachite_vote::Threshold; /// Messages that can be received and broadcast by the consensus executor. #[derive(Clone, Debug, PartialEq, Eq)] @@ -63,11 +63,7 @@ where private_key: PrivateKey, address: Ctx::Address, ) -> Self { - let votes = VoteKeeper::new( - height.clone(), - Round::NIL, - validator_set.total_voting_power(), - ); + let votes = VoteKeeper::new(validator_set.total_voting_power()); Self { height, @@ -219,6 +215,7 @@ where VoteMessage::PolkaValue(v) => RoundEvent::PolkaValue(v), VoteMessage::PrecommitAny => RoundEvent::PrecommitAny, VoteMessage::PrecommitValue(v) => RoundEvent::PrecommitValue(v), + VoteMessage::SkipRound(r) => RoundEvent::SkipRound(r), }; self.apply_event(round, round_event) diff --git a/Code/round/src/events.rs b/Code/round/src/events.rs index 8ac2374ec..a0e1e4bba 100644 --- a/Code/round/src/events.rs +++ b/Code/round/src/events.rs @@ -1,4 +1,4 @@ -use malachite_common::{Context, ValueId}; +use malachite_common::{Context, Round, ValueId}; #[derive(Clone, Debug, PartialEq, Eq)] pub enum Event @@ -17,8 +17,8 @@ where PrecommitAny, // Receive +2/3 precommits for anything. L47 ProposalAndPrecommitValue(Ctx::Proposal), // Receive +2/3 precommits for Value. L49 PrecommitValue(ValueId), // Receive +2/3 precommits for ValueId. L51 - RoundSkip, // Receive +1/3 messages from a higher round. OneCorrectProcessInHigherRound, L55 - TimeoutPropose, // Timeout waiting for proposal. L57 - TimeoutPrevote, // Timeout waiting for prevotes. L61 + SkipRound(Round), // Receive +1/3 messages from a higher round. OneCorrectProcessInHigherRound, L55 + TimeoutPropose, // Timeout waiting for proposal. L57 + TimeoutPrevote, // Timeout waiting for prevotes. L61 TimeoutPrecommit, // Timeout waiting for precommits. L65 } diff --git a/Code/round/src/state_machine.rs b/Code/round/src/state_machine.rs index dd9350b1c..ade9d53e0 100644 --- a/Code/round/src/state_machine.rs +++ b/Code/round/src/state_machine.rs @@ -120,7 +120,7 @@ where // From all (except Commit). Various round guards. (_, Event::PrecommitAny) if this_round => schedule_timeout_precommit(state), // L47 (_, Event::TimeoutPrecommit) if this_round => round_skip(state, data.round.increment()), // L65 - (_, Event::RoundSkip) if state.round < data.round => round_skip(state, data.round), // L55 + (_, Event::SkipRound(round)) if state.round < round => round_skip(state, round), // L55 (_, Event::ProposalAndPrecommitValue(proposal)) => commit(state, data.round, proposal), // L49 // Invalid transition. diff --git a/Code/test/src/value.rs b/Code/test/src/value.rs index 3954d2868..47b5736bc 100644 --- a/Code/test/src/value.rs +++ b/Code/test/src/value.rs @@ -11,6 +11,12 @@ impl ValueId { } } +impl From for ValueId { + fn from(value: u64) -> Self { + Self::new(value) + } +} + /// The value to decide on #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Value(u64); @@ -44,3 +50,9 @@ impl malachite_common::Value for Value { self.id() } } + +impl From for Value { + fn from(value: u64) -> Self { + Self::new(value) + } +} diff --git a/Code/test/tests/consensus_executor.rs b/Code/test/tests/consensus_executor.rs index 821eb0fb7..b17cbca88 100644 --- a/Code/test/tests/consensus_executor.rs +++ b/Code/test/tests/consensus_executor.rs @@ -168,7 +168,7 @@ fn executor_steps_proposer() { TestStep { desc: "v3 precommits for our proposal, we get +2/3 precommits, decide it (v1)", input_event: Some(Event::Vote( - Vote::new_precommit(Round::new(0), Some(value_id), addr2).signed(&sk2), + Vote::new_precommit(Round::new(0), Some(value_id), addr3).signed(&sk3), )), expected_output: Some(Message::Decide(Round::new(0), value.clone())), new_state: State { @@ -571,6 +571,7 @@ fn executor_steps_not_proposer_timeout_multiple_rounds() { // TODO - add expected executor round to test and assert before the new_state check below let new_state = executor.round_state(executor.round).unwrap(); assert_eq!(new_state, &step.new_state, "new state"); + assert_eq!(output, step.expected_output, "expected output message"); previous_message = output.and_then(to_input_msg); } diff --git a/Code/test/tests/round.rs b/Code/test/tests/round.rs index 4f0dff9e0..9f9b606fc 100644 --- a/Code/test/tests/round.rs +++ b/Code/test/tests/round.rs @@ -14,8 +14,10 @@ fn test_propose() { let height = Height::new(10); let round = Round::new(0); - let mut state: State = State::default(); - state.round = round; + let mut state: State = State { + round, + ..Default::default() + }; let data = RoundData::new(round, &height, &ADDRESS); diff --git a/Code/test/tests/round_votes.rs b/Code/test/tests/round_votes.rs index fb83ca498..8be3f46e4 100644 --- a/Code/test/tests/round_votes.rs +++ b/Code/test/tests/round_votes.rs @@ -1,29 +1,32 @@ -use malachite_common::Round; -use malachite_vote::count::Threshold; -use malachite_vote::RoundVotes; +use malachite_common::VoteType; +use malachite_vote::round_votes::RoundVotes; +use malachite_vote::Threshold; -use malachite_test::{Address, Height, TestContext, ValueId, Vote}; +use malachite_test::{Address, ValueId}; -const ADDRESS: Address = Address::new([42; 20]); +const ADDRESS1: Address = Address::new([41; 20]); +const ADDRESS2: Address = Address::new([42; 20]); +const ADDRESS3: Address = Address::new([43; 20]); +const ADDRESS4: Address = Address::new([44; 20]); +const ADDRESS5: Address = Address::new([45; 20]); +const ADDRESS6: Address = Address::new([46; 20]); #[test] fn add_votes_nil() { let total = 3; - let mut round_votes: RoundVotes = - RoundVotes::new(Height::new(1), Round::new(0), total); + let mut round_votes: RoundVotes<_, ValueId> = RoundVotes::new(total, Default::default()); // add a vote for nil. nothing changes. - let vote = Vote::new_prevote(Round::new(0), None, ADDRESS); - let thresh = round_votes.add_vote(vote.clone(), 1); - assert_eq!(thresh, Threshold::Init); + let thresh = round_votes.add_vote(VoteType::Prevote, ADDRESS1, None, 1); + assert_eq!(thresh, Threshold::Unreached); // add it again, nothing changes. - let thresh = round_votes.add_vote(vote.clone(), 1); - assert_eq!(thresh, Threshold::Init); + let thresh = round_votes.add_vote(VoteType::Prevote, ADDRESS2, None, 1); + assert_eq!(thresh, Threshold::Unreached); // add it again, get Nil - let thresh = round_votes.add_vote(vote.clone(), 1); + let thresh = round_votes.add_vote(VoteType::Prevote, ADDRESS3, None, 1); assert_eq!(thresh, Threshold::Nil); } @@ -34,25 +37,22 @@ fn add_votes_single_value() { let total = 4; let weight = 1; - let mut round_votes: RoundVotes = - RoundVotes::new(Height::new(1), Round::new(0), total); + let mut round_votes: RoundVotes<_, ValueId> = RoundVotes::new(total, Default::default()); // add a vote. nothing changes. - let vote = Vote::new_prevote(Round::new(0), val, ADDRESS); - let thresh = round_votes.add_vote(vote.clone(), weight); - assert_eq!(thresh, Threshold::Init); + let thresh = round_votes.add_vote(VoteType::Prevote, ADDRESS1, val, weight); + assert_eq!(thresh, Threshold::Unreached); // add it again, nothing changes. - let thresh = round_votes.add_vote(vote.clone(), weight); - assert_eq!(thresh, Threshold::Init); + let thresh = round_votes.add_vote(VoteType::Prevote, ADDRESS2, val, weight); + assert_eq!(thresh, Threshold::Unreached); // add a vote for nil, get Thresh::Any - let vote_nil = Vote::new_prevote(Round::new(0), None, ADDRESS); - let thresh = round_votes.add_vote(vote_nil, weight); + let thresh = round_votes.add_vote(VoteType::Prevote, ADDRESS3, None, weight); assert_eq!(thresh, Threshold::Any); // add vote for value, get Thresh::Value - let thresh = round_votes.add_vote(vote, weight); + let thresh = round_votes.add_vote(VoteType::Prevote, ADDRESS4, val, weight); assert_eq!(thresh, Threshold::Value(v)); } @@ -64,33 +64,29 @@ fn add_votes_multi_values() { let val2 = Some(v2); let total = 15; - let mut round_votes: RoundVotes = - RoundVotes::new(Height::new(1), Round::new(0), total); + let mut round_votes: RoundVotes<_, ValueId> = RoundVotes::new(total, Default::default()); // add a vote for v1. nothing changes. - let vote1 = Vote::new_precommit(Round::new(0), val1, ADDRESS); - let thresh = round_votes.add_vote(vote1.clone(), 1); - assert_eq!(thresh, Threshold::Init); + let thresh = round_votes.add_vote(VoteType::Precommit, ADDRESS1, val1, 1); + assert_eq!(thresh, Threshold::Unreached); // add a vote for v2. nothing changes. - let vote2 = Vote::new_precommit(Round::new(0), val2, ADDRESS); - let thresh = round_votes.add_vote(vote2.clone(), 1); - assert_eq!(thresh, Threshold::Init); + let thresh = round_votes.add_vote(VoteType::Precommit, ADDRESS2, val2, 1); + assert_eq!(thresh, Threshold::Unreached); // add a vote for nil. nothing changes. - let vote_nil = Vote::new_precommit(Round::new(0), None, ADDRESS); - let thresh = round_votes.add_vote(vote_nil.clone(), 1); - assert_eq!(thresh, Threshold::Init); + let thresh = round_votes.add_vote(VoteType::Precommit, ADDRESS3, None, 1); + assert_eq!(thresh, Threshold::Unreached); // add a vote for v1. nothing changes - let thresh = round_votes.add_vote(vote1.clone(), 1); - assert_eq!(thresh, Threshold::Init); + let thresh = round_votes.add_vote(VoteType::Precommit, ADDRESS4, val1, 1); + assert_eq!(thresh, Threshold::Unreached); // add a vote for v2. nothing changes - let thresh = round_votes.add_vote(vote2.clone(), 1); - assert_eq!(thresh, Threshold::Init); + let thresh = round_votes.add_vote(VoteType::Precommit, ADDRESS5, val2, 1); + assert_eq!(thresh, Threshold::Unreached); // add a big vote for v2. get Value(v2) - let thresh = round_votes.add_vote(vote2.clone(), 10); + let thresh = round_votes.add_vote(VoteType::Precommit, ADDRESS6, val2, 10); assert_eq!(thresh, Threshold::Value(v2)); } diff --git a/Code/test/tests/vote_count.rs b/Code/test/tests/vote_count.rs new file mode 100644 index 000000000..179222dda --- /dev/null +++ b/Code/test/tests/vote_count.rs @@ -0,0 +1,155 @@ +#![allow(clippy::bool_assert_comparison)] + +use malachite_vote::count::VoteCount; +use malachite_vote::Threshold; + +#[test] +fn vote_count_nil() { + let mut vc = VoteCount::new(4, Default::default()); + + let addr1 = [1]; + let addr2 = [2]; + let addr3 = [3]; + let addr4 = [4]; + + assert_eq!(vc.get(&None), 0); + assert_eq!(vc.get(&Some(1)), 0); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); + assert_eq!(vc.is_threshold_met(Threshold::Any), false); + assert_eq!(vc.is_threshold_met(Threshold::Nil), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1)), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); + + assert_eq!(vc.add(addr1, None, 1), Threshold::Unreached); + assert_eq!(vc.get(&None), 1); + assert_eq!(vc.get(&Some(1)), 0); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); + assert_eq!(vc.is_threshold_met(Threshold::Any), false); + assert_eq!(vc.is_threshold_met(Threshold::Nil), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1)), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); + + assert_eq!(vc.add(addr2, None, 1), Threshold::Unreached); + assert_eq!(vc.get(&None), 2); + assert_eq!(vc.get(&Some(1)), 0); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); + assert_eq!(vc.is_threshold_met(Threshold::Any), false); + assert_eq!(vc.is_threshold_met(Threshold::Nil), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1)), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); + + // addr1 votes again, is ignored + assert_eq!(vc.add(addr1, None, 1), Threshold::Unreached); + assert_eq!(vc.get(&None), 2); + assert_eq!(vc.get(&Some(1)), 0); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); + assert_eq!(vc.is_threshold_met(Threshold::Any), false); + assert_eq!(vc.is_threshold_met(Threshold::Nil), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1)), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); + + assert_eq!(vc.add(addr3, None, 1), Threshold::Nil); + assert_eq!(vc.get(&None), 3); + assert_eq!(vc.get(&Some(1)), 0); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); + assert_eq!(vc.is_threshold_met(Threshold::Any), true); + assert_eq!(vc.is_threshold_met(Threshold::Nil), true); + assert_eq!(vc.is_threshold_met(Threshold::Value(1)), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); + + assert_eq!(vc.add(addr4, Some(1), 1), Threshold::Any); + assert_eq!(vc.get(&None), 3); + assert_eq!(vc.get(&Some(1)), 1); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); + assert_eq!(vc.is_threshold_met(Threshold::Any), true); + assert_eq!(vc.is_threshold_met(Threshold::Nil), true); + assert_eq!(vc.is_threshold_met(Threshold::Value(1)), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); +} + +#[test] +fn vote_count_value() { + let mut vc = VoteCount::new(4, Default::default()); + + let addr1 = [1]; + let addr2 = [2]; + let addr3 = [3]; + let addr4 = [4]; + + assert_eq!(vc.get(&None), 0); + assert_eq!(vc.get(&Some(1)), 0); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); + assert_eq!(vc.is_threshold_met(Threshold::Any), false); + assert_eq!(vc.is_threshold_met(Threshold::Nil), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1)), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); + + assert_eq!(vc.add(addr1, Some(1), 1), Threshold::Unreached); + assert_eq!(vc.get(&None), 0); + assert_eq!(vc.get(&Some(1)), 1); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); + assert_eq!(vc.is_threshold_met(Threshold::Any), false); + assert_eq!(vc.is_threshold_met(Threshold::Nil), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1)), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); + + assert_eq!(vc.add(addr2, Some(1), 1), Threshold::Unreached); + assert_eq!(vc.get(&None), 0); + assert_eq!(vc.get(&Some(1)), 2); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); + assert_eq!(vc.is_threshold_met(Threshold::Any), false); + assert_eq!(vc.is_threshold_met(Threshold::Nil), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1)), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); + + // addr1 votes again, for nil this time, is ignored + assert_eq!(vc.add(addr1, None, 1), Threshold::Unreached); + assert_eq!(vc.get(&None), 0); + assert_eq!(vc.get(&Some(1)), 2); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); + assert_eq!(vc.is_threshold_met(Threshold::Any), false); + assert_eq!(vc.is_threshold_met(Threshold::Nil), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1)), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); + + assert_eq!(vc.add(addr3, Some(1), 1), Threshold::Value(1)); + assert_eq!(vc.get(&None), 0); + assert_eq!(vc.get(&Some(1)), 3); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); + assert_eq!(vc.is_threshold_met(Threshold::Any), true); + assert_eq!(vc.is_threshold_met(Threshold::Nil), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1)), true); + assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); + + // addr2 votes again, for the same value, is ignored + assert_eq!(vc.add(addr2, Some(1), 1), Threshold::Value(1)); + assert_eq!(vc.get(&None), 0); + assert_eq!(vc.get(&Some(1)), 3); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); + assert_eq!(vc.is_threshold_met(Threshold::Any), true); + assert_eq!(vc.is_threshold_met(Threshold::Nil), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1)), true); + assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); + + assert_eq!(vc.add(addr4, Some(2), 1), Threshold::Any); + assert_eq!(vc.get(&None), 0); + assert_eq!(vc.get(&Some(1)), 3); + assert_eq!(vc.get(&Some(2)), 1); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); + assert_eq!(vc.is_threshold_met(Threshold::Any), true); + assert_eq!(vc.is_threshold_met(Threshold::Nil), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1)), true); + assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); + + // addr4 votes again, for a different value, is ignored + assert_eq!(vc.add(addr4, Some(3), 1), Threshold::Any); + assert_eq!(vc.get(&None), 0); + assert_eq!(vc.get(&Some(1)), 3); + assert_eq!(vc.get(&Some(2)), 1); + assert_eq!(vc.get(&Some(3)), 0); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); + assert_eq!(vc.is_threshold_met(Threshold::Any), true); + assert_eq!(vc.is_threshold_met(Threshold::Nil), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1)), true); + assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); +} diff --git a/Code/test/tests/vote_keeper.rs b/Code/test/tests/vote_keeper.rs index 2eed7ad6a..8dedc2b3c 100644 --- a/Code/test/tests/vote_keeper.rs +++ b/Code/test/tests/vote_keeper.rs @@ -1,82 +1,91 @@ use malachite_common::Round; use malachite_vote::keeper::{Message, VoteKeeper}; -use malachite_test::{Address, Height, TestContext, ValueId, Vote}; +use malachite_test::{Address, TestContext, ValueId, Vote}; -const ADDRESS: Address = Address::new([42; 20]); +const ADDRESS1: Address = Address::new([41; 20]); +const ADDRESS2: Address = Address::new([42; 20]); +const ADDRESS3: Address = Address::new([43; 20]); +const ADDRESS4: Address = Address::new([44; 20]); #[test] fn prevote_apply_nil() { - let mut keeper: VoteKeeper = VoteKeeper::new(Height::new(1), Round::INITIAL, 3); - - let vote = Vote::new_prevote(Round::new(0), None, ADDRESS); + let mut keeper: VoteKeeper = VoteKeeper::new(3); + let vote = Vote::new_prevote(Round::new(0), None, ADDRESS1); let msg = keeper.apply_vote(vote.clone(), 1); assert_eq!(msg, None); + let vote = Vote::new_prevote(Round::new(0), None, ADDRESS2); let msg = keeper.apply_vote(vote.clone(), 1); assert_eq!(msg, None); + let vote = Vote::new_prevote(Round::new(0), None, ADDRESS3); let msg = keeper.apply_vote(vote, 1); assert_eq!(msg, Some(Message::PolkaNil)); } #[test] fn precommit_apply_nil() { - let mut keeper: VoteKeeper = VoteKeeper::new(Height::new(1), Round::INITIAL, 3); - - let vote = Vote::new_precommit(Round::new(0), None, ADDRESS); + let mut keeper: VoteKeeper = VoteKeeper::new(3); + let vote = Vote::new_precommit(Round::new(0), None, ADDRESS1); let msg = keeper.apply_vote(vote.clone(), 1); assert_eq!(msg, None); + let vote = Vote::new_precommit(Round::new(0), None, ADDRESS2); let msg = keeper.apply_vote(vote.clone(), 1); assert_eq!(msg, None); + let vote = Vote::new_precommit(Round::new(0), None, ADDRESS3); let msg = keeper.apply_vote(vote, 1); assert_eq!(msg, Some(Message::PrecommitAny)); } #[test] fn prevote_apply_single_value() { - let mut keeper: VoteKeeper = VoteKeeper::new(Height::new(1), Round::INITIAL, 4); + let mut keeper: VoteKeeper = VoteKeeper::new(4); let v = ValueId::new(1); let val = Some(v); - let vote = Vote::new_prevote(Round::new(0), val, ADDRESS); + let vote = Vote::new_prevote(Round::new(0), val, ADDRESS1); let msg = keeper.apply_vote(vote.clone(), 1); assert_eq!(msg, None); + let vote = Vote::new_prevote(Round::new(0), val, ADDRESS2); let msg = keeper.apply_vote(vote.clone(), 1); assert_eq!(msg, None); - let vote_nil = Vote::new_prevote(Round::new(0), None, ADDRESS); + let vote_nil = Vote::new_prevote(Round::new(0), None, ADDRESS3); let msg = keeper.apply_vote(vote_nil, 1); assert_eq!(msg, Some(Message::PolkaAny)); + let vote = Vote::new_prevote(Round::new(0), val, ADDRESS4); let msg = keeper.apply_vote(vote, 1); assert_eq!(msg, Some(Message::PolkaValue(v))); } #[test] fn precommit_apply_single_value() { - let mut keeper: VoteKeeper = VoteKeeper::new(Height::new(1), Round::INITIAL, 4); + let mut keeper: VoteKeeper = VoteKeeper::new(4); let v = ValueId::new(1); let val = Some(v); - let vote = Vote::new_precommit(Round::new(0), val, ADDRESS); + let vote = Vote::new_precommit(Round::new(0), val, ADDRESS1); let msg = keeper.apply_vote(vote.clone(), 1); assert_eq!(msg, None); + let vote = Vote::new_precommit(Round::new(0), val, ADDRESS2); let msg = keeper.apply_vote(vote.clone(), 1); assert_eq!(msg, None); - let vote_nil = Vote::new_precommit(Round::new(0), None, ADDRESS); + let vote_nil = Vote::new_precommit(Round::new(0), None, ADDRESS3); let msg = keeper.apply_vote(vote_nil, 1); assert_eq!(msg, Some(Message::PrecommitAny)); + let vote = Vote::new_precommit(Round::new(0), val, ADDRESS4); let msg = keeper.apply_vote(vote, 1); assert_eq!(msg, Some(Message::PrecommitValue(v))); } diff --git a/Code/vote/src/count.rs b/Code/vote/src/count.rs index b124e0b26..5c7b66dcc 100644 --- a/Code/vote/src/count.rs +++ b/Code/vote/src/count.rs @@ -1,90 +1,77 @@ -use alloc::collections::BTreeMap; +use alloc::collections::BTreeSet; -// TODO: Introduce newtype -// QUESTION: Over what type? i64? -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>, -} - -impl ValuesWeights { - pub fn new() -> ValuesWeights { - ValuesWeights { - value_weights: BTreeMap::new(), - } - } - - /// Add weight to the value and return the new weight. - pub fn add(&mut self, value: Option, weight: Weight) -> Weight - where - ValueId: Ord, - { - let entry = self.value_weights.entry(value).or_insert(0); - *entry += weight; // FIXME: Deal with overflows - *entry - } - - /// Return the weight of the value, or 0 if it is not present. - pub fn get(&self, value: &Option) -> Weight - where - ValueId: Ord, - { - self.value_weights.get(value).cloned().unwrap_or(0) - } - - /// Return the sum of the weights of all values. - pub fn sum(&self) -> Weight { - self.value_weights.values().sum() // FIXME: Deal with overflows - } -} - -impl Default for ValuesWeights { - fn default() -> Self { - Self::new() - } -} +use crate::value_weights::ValuesWeights; +use crate::{Threshold, ThresholdParams, Weight}; /// 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 { + /// Total weight + pub total_weight: Weight, + + /// The threshold parameters + pub threshold_params: ThresholdParams, + /// Weight of votes for the values, including nil - pub values_weights: ValuesWeights, + pub values_weights: ValuesWeights>, - /// Total weight - pub total: Weight, + /// Addresses of validators who voted for the values + pub validator_addresses: BTreeSet
, } -impl VoteCount { - pub fn new(total: Weight) -> Self { +impl VoteCount { + pub fn new(total_weight: Weight, threshold_params: ThresholdParams) -> Self { VoteCount { - total, + total_weight, + threshold_params, values_weights: ValuesWeights::new(), + validator_addresses: BTreeSet::new(), } } - /// Add vote for a vlaue to internal counters and return the highest threshold. - pub fn add_vote(&mut self, value: Option, weight: Weight) -> Threshold + /// Add vote for a value (or nil) to internal counters, but only if we haven't seen + /// a vote from that particular validator yet. + pub fn add( + &mut self, + address: Address, + value: Option, + weight: Weight, + ) -> Threshold where + Address: Clone + Ord, Value: Clone + Ord, { - let new_weight = self.values_weights.add(value.clone(), weight); + let already_voted = !self.validator_addresses.insert(address); + + if !already_voted { + self.values_weights.add(value.clone(), weight); + } + + self.compute_threshold(value) + } + + /// Compute whether or not we have reached a threshold for the given value, + /// and return that threshold. + pub fn compute_threshold(&self, value: Option) -> Threshold + where + Address: Ord, + Value: Ord, + { + let weight = self.values_weights.get(&value); match value { - Some(value) if is_quorum(new_weight, self.total) => Threshold::Value(value), + Some(value) if self.is_quorum(weight, self.total_weight) => Threshold::Value(value), - None if is_quorum(new_weight, self.total) => Threshold::Nil, + None if self.is_quorum(weight, self.total_weight) => Threshold::Nil, _ => { let sum_weight = self.values_weights.sum(); - if is_quorum(sum_weight, self.total) { + if self.is_quorum(sum_weight, self.total_weight) { Threshold::Any } else { - Threshold::Init + Threshold::Unreached } } } @@ -93,120 +80,107 @@ impl VoteCount { /// Return whether or not the threshold is met, ie. if we have a quorum for that threshold. pub fn is_threshold_met(&self, threshold: Threshold) -> bool where - Value: Clone + Ord, + Value: Ord, { match threshold { Threshold::Value(value) => { let weight = self.values_weights.get(&Some(value)); - is_quorum(weight, self.total) + self.is_quorum(weight, self.total_weight) } Threshold::Nil => { let weight = self.values_weights.get(&None); - is_quorum(weight, self.total) + self.is_quorum(weight, self.total_weight) } Threshold::Any => { let sum_weight = self.values_weights.sum(); - is_quorum(sum_weight, self.total) + self.is_quorum(sum_weight, self.total_weight) } - Threshold::Init => false, + Threshold::Skip | Threshold::Unreached => false, } } -} -//------------------------------------------------------------------------- -// Round votes -//------------------------------------------------------------------------- - -// Thresh represents the different quorum thresholds. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum Threshold { - /// No quorum - Init, // no quorum - /// Qorum of votes but not for the same value - Any, - /// Quorum for nil - Nil, - /// Quorum for a value - Value(ValueId), -} + pub fn get(&self, value: &Option) -> Weight + where + Value: Ord, + { + self.values_weights.get(value) + } + + pub fn total_weight(&self) -> Weight { + self.total_weight + } -/// Returns whether or note `value > (2/3)*total`. -fn is_quorum(value: Weight, total: Weight) -> bool { - 3 * value > 2 * total + fn is_quorum(&self, sum: Weight, total: Weight) -> bool { + self.threshold_params.quorum.is_met(sum, total) + } } #[cfg(test)] +#[allow(clippy::bool_assert_comparison)] mod tests { use super::*; #[test] - fn values_weights() { - let mut vw = ValuesWeights::new(); - - assert_eq!(vw.get(&None), 0); - assert_eq!(vw.get(&Some(1)), 0); - - assert_eq!(vw.add(None, 1), 1); - assert_eq!(vw.get(&None), 1); - assert_eq!(vw.get(&Some(1)), 0); - - assert_eq!(vw.add(Some(1), 1), 1); - assert_eq!(vw.get(&None), 1); - assert_eq!(vw.get(&Some(1)), 1); - - assert_eq!(vw.add(None, 1), 2); - assert_eq!(vw.get(&None), 2); - assert_eq!(vw.get(&Some(1)), 1); - - assert_eq!(vw.add(Some(1), 1), 2); - assert_eq!(vw.get(&None), 2); - assert_eq!(vw.get(&Some(1)), 2); - - assert_eq!(vw.add(Some(2), 1), 1); - assert_eq!(vw.get(&None), 2); - assert_eq!(vw.get(&Some(1)), 2); - assert_eq!(vw.get(&Some(2)), 1); + fn vote_count_nil() { + let mut vc = VoteCount::new(4, Default::default()); - // FIXME: Test for and deal with overflows - } + let addr1 = [1]; + let addr2 = [2]; + let addr3 = [3]; + let addr4 = [4]; - #[test] - #[allow(clippy::bool_assert_comparison)] - fn vote_count_nil() { - let mut vc = VoteCount::new(4); + assert_eq!(vc.get(&None), 0); + assert_eq!(vc.get(&Some(1)), 0); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); + assert_eq!(vc.is_threshold_met(Threshold::Any), false); + assert_eq!(vc.is_threshold_met(Threshold::Nil), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1)), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); - assert_eq!(vc.is_threshold_met(Threshold::Init), false); + assert_eq!(vc.add(addr1, None, 1), Threshold::Unreached); + assert_eq!(vc.get(&None), 1); + assert_eq!(vc.get(&Some(1)), 0); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); assert_eq!(vc.is_threshold_met(Threshold::Any), false); assert_eq!(vc.is_threshold_met(Threshold::Nil), false); assert_eq!(vc.is_threshold_met(Threshold::Value(1)), false); assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); - assert_eq!(vc.add_vote(None, 1), Threshold::Init); - assert_eq!(vc.is_threshold_met(Threshold::Init), false); + assert_eq!(vc.add(addr2, None, 1), Threshold::Unreached); + assert_eq!(vc.get(&None), 2); + assert_eq!(vc.get(&Some(1)), 0); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); assert_eq!(vc.is_threshold_met(Threshold::Any), false); assert_eq!(vc.is_threshold_met(Threshold::Nil), false); assert_eq!(vc.is_threshold_met(Threshold::Value(1)), false); assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); - assert_eq!(vc.add_vote(None, 1), Threshold::Init); - assert_eq!(vc.is_threshold_met(Threshold::Init), false); + // addr1 votes again, is ignored + assert_eq!(vc.add(addr1, None, 1), Threshold::Unreached); + assert_eq!(vc.get(&None), 2); + assert_eq!(vc.get(&Some(1)), 0); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); assert_eq!(vc.is_threshold_met(Threshold::Any), false); assert_eq!(vc.is_threshold_met(Threshold::Nil), false); assert_eq!(vc.is_threshold_met(Threshold::Value(1)), false); assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); - assert_eq!(vc.add_vote(None, 1), Threshold::Nil); - assert_eq!(vc.is_threshold_met(Threshold::Init), false); + assert_eq!(vc.add(addr3, None, 1), Threshold::Nil); + assert_eq!(vc.get(&None), 3); + assert_eq!(vc.get(&Some(1)), 0); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); assert_eq!(vc.is_threshold_met(Threshold::Any), true); assert_eq!(vc.is_threshold_met(Threshold::Nil), true); assert_eq!(vc.is_threshold_met(Threshold::Value(1)), false); assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); - assert_eq!(vc.add_vote(Some(1), 1), Threshold::Any); - assert_eq!(vc.is_threshold_met(Threshold::Init), false); + assert_eq!(vc.add(addr4, Some(1), 1), Threshold::Any); + assert_eq!(vc.get(&None), 3); + assert_eq!(vc.get(&Some(1)), 1); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); assert_eq!(vc.is_threshold_met(Threshold::Any), true); assert_eq!(vc.is_threshold_met(Threshold::Nil), true); assert_eq!(vc.is_threshold_met(Threshold::Value(1)), false); @@ -214,39 +188,86 @@ mod tests { } #[test] - #[allow(clippy::bool_assert_comparison)] fn vote_count_value() { - let mut vc = VoteCount::new(4); + let mut vc = VoteCount::new(4, Default::default()); + + let addr1 = [1]; + let addr2 = [2]; + let addr3 = [3]; + let addr4 = [4]; - assert_eq!(vc.is_threshold_met(Threshold::Init), false); + assert_eq!(vc.get(&None), 0); + assert_eq!(vc.get(&Some(1)), 0); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); assert_eq!(vc.is_threshold_met(Threshold::Any), false); assert_eq!(vc.is_threshold_met(Threshold::Nil), false); assert_eq!(vc.is_threshold_met(Threshold::Value(1)), false); assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); - assert_eq!(vc.add_vote(Some(1), 1), Threshold::Init); - assert_eq!(vc.is_threshold_met(Threshold::Init), false); + assert_eq!(vc.add(addr1, Some(1), 1), Threshold::Unreached); + assert_eq!(vc.get(&None), 0); + assert_eq!(vc.get(&Some(1)), 1); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); assert_eq!(vc.is_threshold_met(Threshold::Any), false); assert_eq!(vc.is_threshold_met(Threshold::Nil), false); assert_eq!(vc.is_threshold_met(Threshold::Value(1)), false); assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); - assert_eq!(vc.add_vote(Some(1), 1), Threshold::Init); - assert_eq!(vc.is_threshold_met(Threshold::Init), false); + assert_eq!(vc.add(addr2, Some(1), 1), Threshold::Unreached); + assert_eq!(vc.get(&None), 0); + assert_eq!(vc.get(&Some(1)), 2); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); assert_eq!(vc.is_threshold_met(Threshold::Any), false); assert_eq!(vc.is_threshold_met(Threshold::Nil), false); assert_eq!(vc.is_threshold_met(Threshold::Value(1)), false); assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); - assert_eq!(vc.add_vote(Some(1), 1), Threshold::Value(1)); - assert_eq!(vc.is_threshold_met(Threshold::Init), false); + // addr1 votes again, for nil this time, is ignored + assert_eq!(vc.add(addr1, None, 1), Threshold::Unreached); + assert_eq!(vc.get(&None), 0); + assert_eq!(vc.get(&Some(1)), 2); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); + assert_eq!(vc.is_threshold_met(Threshold::Any), false); + assert_eq!(vc.is_threshold_met(Threshold::Nil), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1)), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); + + assert_eq!(vc.add(addr3, Some(1), 1), Threshold::Value(1)); + assert_eq!(vc.get(&None), 0); + assert_eq!(vc.get(&Some(1)), 3); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); + assert_eq!(vc.is_threshold_met(Threshold::Any), true); + assert_eq!(vc.is_threshold_met(Threshold::Nil), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1)), true); + assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); + + // addr2 votes again, for the same value, is ignored + assert_eq!(vc.add(addr2, Some(1), 1), Threshold::Value(1)); + assert_eq!(vc.get(&None), 0); + assert_eq!(vc.get(&Some(1)), 3); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); + assert_eq!(vc.is_threshold_met(Threshold::Any), true); + assert_eq!(vc.is_threshold_met(Threshold::Nil), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1)), true); + assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); + + assert_eq!(vc.add(addr4, Some(2), 1), Threshold::Any); + assert_eq!(vc.get(&None), 0); + assert_eq!(vc.get(&Some(1)), 3); + assert_eq!(vc.get(&Some(2)), 1); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); assert_eq!(vc.is_threshold_met(Threshold::Any), true); assert_eq!(vc.is_threshold_met(Threshold::Nil), false); assert_eq!(vc.is_threshold_met(Threshold::Value(1)), true); assert_eq!(vc.is_threshold_met(Threshold::Value(2)), false); - assert_eq!(vc.add_vote(Some(2), 1), Threshold::Any); - assert_eq!(vc.is_threshold_met(Threshold::Init), false); + // addr4 votes again, for a different value, is ignored + assert_eq!(vc.add(addr4, Some(3), 1), Threshold::Any); + assert_eq!(vc.get(&None), 0); + assert_eq!(vc.get(&Some(1)), 3); + assert_eq!(vc.get(&Some(2)), 1); + assert_eq!(vc.get(&Some(3)), 0); + assert_eq!(vc.is_threshold_met(Threshold::Unreached), false); assert_eq!(vc.is_threshold_met(Threshold::Any), true); assert_eq!(vc.is_threshold_met(Threshold::Nil), false); assert_eq!(vc.is_threshold_met(Threshold::Value(1)), true); diff --git a/Code/vote/src/keeper.rs b/Code/vote/src/keeper.rs index 1240dcc70..5694ec2d7 100644 --- a/Code/vote/src/keeper.rs +++ b/Code/vote/src/keeper.rs @@ -1,20 +1,43 @@ -use alloc::collections::BTreeMap; +use alloc::collections::{BTreeMap, BTreeSet}; use malachite_common::{Context, Round, ValueId, Vote, VoteType}; -use crate::{ - count::{Threshold, Weight}, - RoundVotes, -}; +use crate::round_votes::RoundVotes; +use crate::round_weights::RoundWeights; +use crate::{Threshold, ThresholdParam, ThresholdParams, Weight}; /// Messages emitted by the vote keeper -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Message { PolkaAny, PolkaNil, PolkaValue(Value), PrecommitAny, PrecommitValue(Value), + SkipRound(Round), +} + +#[derive(Clone, Debug)] +struct PerRound +where + Ctx: Context, +{ + votes: RoundVotes>, + addresses_weights: RoundWeights, + emitted_msgs: BTreeSet>>, +} + +impl PerRound +where + Ctx: Context, +{ + fn new(total_weight: Weight, threshold_params: ThresholdParams) -> Self { + Self { + votes: RoundVotes::new(total_weight, threshold_params), + addresses_weights: RoundWeights::new(), + emitted_msgs: BTreeSet::new(), + } + } } /// Keeps track of votes and emits messages when thresholds are reached. @@ -23,39 +46,58 @@ pub struct VoteKeeper where Ctx: Context, { - height: Ctx::Height, + threshold_params: ThresholdParams, total_weight: Weight, - rounds: BTreeMap>, + per_round: BTreeMap>, } impl VoteKeeper where Ctx: Context, { - pub fn new(height: Ctx::Height, round: Round, total_weight: Weight) -> Self { - let mut rounds = BTreeMap::new(); - - if round != Round::NIL { - rounds.insert(round, RoundVotes::new(height.clone(), round, total_weight)); - } - + pub fn new(total_weight: Weight) -> Self { VoteKeeper { - height, + // TODO: Make these configurable + threshold_params: ThresholdParams::default(), + total_weight, - rounds, + per_round: BTreeMap::new(), } } /// Apply a vote with a given weight, potentially triggering an event. pub fn apply_vote(&mut self, vote: Ctx::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 round = self + .per_round + .entry(vote.round()) + .or_insert_with(|| PerRound::new(self.total_weight, self.threshold_params)); + + let threshold = round.votes.add_vote( + vote.vote_type(), + vote.validator_address().clone(), + vote.value().cloned(), + weight, + ); + + round + .addresses_weights + .set_once(vote.validator_address().clone(), weight); - let vote_type = vote.vote_type(); - let threshold = round.add_vote(vote, weight); + let msg = threshold_to_message(vote.vote_type(), vote.round(), threshold)?; + + let final_msg = if !round.emitted_msgs.contains(&msg) { + Some(msg) + } else if Self::skip_round(round, self.total_weight, self.threshold_params.honest) { + Some(Message::SkipRound(vote.round())) + } else { + None + }; + + if let Some(final_msg) = &final_msg { + round.emitted_msgs.insert(final_msg.clone()); + } - Self::to_message(vote_type, threshold) + final_msg } /// Check if a threshold is met, ie. if we have a quorum for that threshold. @@ -65,32 +107,39 @@ where vote_type: VoteType, threshold: Threshold>, ) -> bool { - let round = match self.rounds.get(round) { - Some(round) => round, - None => return false, - }; + self.per_round.get(round).map_or(false, |round| { + round.votes.is_threshold_met(vote_type, threshold) + }) + } - match vote_type { - VoteType::Prevote => round.prevotes.is_threshold_met(threshold), - VoteType::Precommit => round.precommits.is_threshold_met(threshold), - } + /// Check whether or not we should skip this round, in case we haven't emitted any messages + /// yet, and we have reached an honest threshold for the round. + fn skip_round( + round: &PerRound, + total_weight: Weight, + threshold_param: ThresholdParam, + ) -> bool { + round.emitted_msgs.is_empty() + && threshold_param.is_met(round.addresses_weights.sum(), total_weight) } +} - /// Map a vote type and a threshold to a state machine event. - fn to_message( - typ: VoteType, - threshold: Threshold>, - ) -> Option>> { - match (typ, threshold) { - (_, Threshold::Init) => None, +/// Map a vote type and a threshold to a state machine event. +fn threshold_to_message( + typ: VoteType, + round: Round, + threshold: Threshold, +) -> Option> { + match (typ, threshold) { + (_, Threshold::Unreached) => None, + (_, Threshold::Skip) => Some(Message::SkipRound(round)), - (VoteType::Prevote, Threshold::Any) => Some(Message::PolkaAny), - (VoteType::Prevote, Threshold::Nil) => Some(Message::PolkaNil), - (VoteType::Prevote, Threshold::Value(v)) => Some(Message::PolkaValue(v)), + (VoteType::Prevote, Threshold::Any) => Some(Message::PolkaAny), + (VoteType::Prevote, Threshold::Nil) => Some(Message::PolkaNil), + (VoteType::Prevote, Threshold::Value(v)) => Some(Message::PolkaValue(v)), - (VoteType::Precommit, Threshold::Any) => Some(Message::PrecommitAny), - (VoteType::Precommit, Threshold::Nil) => Some(Message::PrecommitAny), - (VoteType::Precommit, Threshold::Value(v)) => Some(Message::PrecommitValue(v)), - } + (VoteType::Precommit, Threshold::Any) => Some(Message::PrecommitAny), + (VoteType::Precommit, Threshold::Nil) => Some(Message::PrecommitAny), + (VoteType::Precommit, Threshold::Value(v)) => Some(Message::PrecommitValue(v)), } } diff --git a/Code/vote/src/lib.rs b/Code/vote/src/lib.rs index 6d66b9448..cc27fe823 100644 --- a/Code/vote/src/lib.rs +++ b/Code/vote/src/lib.rs @@ -14,41 +14,78 @@ extern crate alloc; pub mod count; pub mod keeper; +pub mod round_votes; +pub mod round_weights; +pub mod value_weights; -use malachite_common::{Context, Round, ValueId, Vote, VoteType}; +// TODO: Introduce newtype +// QUESTION: Over what type? i64? +pub type Weight = u64; -use crate::count::{Threshold, VoteCount, Weight}; +/// Represents the different quorum thresholds. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Threshold { + /// No quorum has been reached yet + Unreached, -/// Tracks all the votes for a single round -#[derive(Clone, Debug)] -pub struct RoundVotes -where - Ctx: Context, -{ - pub height: Ctx::Height, - pub round: Round, + /// Minimum number of votes correct processes, + /// if at a round higher than current then skip to that round. + Skip, - pub prevotes: VoteCount>, - pub precommits: VoteCount>, + /// Quorum of votes but not for the same value + Any, + + /// Quorum of votes for nil + Nil, + + /// Quorum (+2/3) of votes for a value + Value(ValueId), +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct ThresholdParams { + /// Threshold for a quorum (default: 2f+1) + pub quorum: ThresholdParam, + + /// Threshold for the minimum number of honest nodes (default: f+1) + pub honest: ThresholdParam, } -impl RoundVotes -where - Ctx: Context, -{ - pub fn new(height: Ctx::Height, round: Round, total: Weight) -> Self { - RoundVotes { - height, - round, - prevotes: VoteCount::new(total), - precommits: VoteCount::new(total), +impl Default for ThresholdParams { + fn default() -> Self { + Self { + quorum: ThresholdParam::TWO_F_PLUS_ONE, + honest: ThresholdParam::F_PLUS_ONE, } } +} - pub fn add_vote(&mut self, vote: Ctx::Vote, weight: Weight) -> Threshold> { - match vote.vote_type() { - VoteType::Prevote => self.prevotes.add_vote(vote.take_value(), weight), - VoteType::Precommit => self.precommits.add_vote(vote.take_value(), weight), +/// Represents the different quorum thresholds. +/// +/// TODO: Distinguish between quorum and honest thresholds at the type-level +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct ThresholdParam { + pub numerator: u64, + pub denominator: u64, +} + +impl ThresholdParam { + /// 2f+1 + pub const TWO_F_PLUS_ONE: Self = Self::new(2, 3); + + /// f+1 + pub const F_PLUS_ONE: Self = Self::new(1, 3); + + pub const fn new(numerator: u64, denominator: u64) -> Self { + Self { + numerator, + denominator, } } + + /// Check whether the threshold is met. + pub const fn is_met(&self, weight: Weight, total: Weight) -> bool { + // FIXME: Deal with overflows + weight * self.denominator > total * self.numerator + } } diff --git a/Code/vote/src/round_votes.rs b/Code/vote/src/round_votes.rs new file mode 100644 index 000000000..21a14f80b --- /dev/null +++ b/Code/vote/src/round_votes.rs @@ -0,0 +1,47 @@ +use malachite_common::VoteType; + +use crate::count::VoteCount; +use crate::{Threshold, ThresholdParams, Weight}; + +/// Tracks all the votes for a single round +#[derive(Clone, Debug)] +pub struct RoundVotes { + prevotes: VoteCount, + precommits: VoteCount, +} + +impl RoundVotes { + pub fn new(total_weight: Weight, threshold_params: ThresholdParams) -> Self { + RoundVotes { + prevotes: VoteCount::new(total_weight, threshold_params), + precommits: VoteCount::new(total_weight, threshold_params), + } + } + + pub fn add_vote( + &mut self, + vote_type: VoteType, + address: Address, + value: Option, + weight: Weight, + ) -> Threshold + where + Address: Clone + Ord, + Value: Clone + Ord, + { + match vote_type { + VoteType::Prevote => self.prevotes.add(address, value, weight), + VoteType::Precommit => self.precommits.add(address, value, weight), + } + } + + pub fn is_threshold_met(&self, vote_type: VoteType, threshold: Threshold) -> bool + where + Value: Ord, + { + match vote_type { + VoteType::Prevote => self.prevotes.is_threshold_met(threshold), + VoteType::Precommit => self.precommits.is_threshold_met(threshold), + } + } +} diff --git a/Code/vote/src/round_weights.rs b/Code/vote/src/round_weights.rs new file mode 100644 index 000000000..63213782d --- /dev/null +++ b/Code/vote/src/round_weights.rs @@ -0,0 +1,40 @@ +use alloc::collections::BTreeMap; + +use crate::Weight; + +#[derive(Clone, Debug)] +pub struct RoundWeights
{ + map: BTreeMap, +} + +impl
RoundWeights
{ + pub fn new() -> Self { + RoundWeights { + map: BTreeMap::new(), + } + } + + pub fn set_once(&mut self, address: Address, weight: Weight) + where + Address: Ord, + { + self.map.entry(address).or_insert(weight); + } + + pub fn get(&self, address: &Address) -> Weight + where + Address: Ord, + { + *self.map.get(address).unwrap_or(&0) + } + + pub fn sum(&self) -> Weight { + self.map.values().sum() + } +} + +impl
Default for RoundWeights
{ + fn default() -> Self { + Self::new() + } +} diff --git a/Code/vote/src/value_weights.rs b/Code/vote/src/value_weights.rs new file mode 100644 index 000000000..7b1e355af --- /dev/null +++ b/Code/vote/src/value_weights.rs @@ -0,0 +1,82 @@ +use alloc::collections::BTreeMap; + +use crate::Weight; + +/// A value and the weight of votes for it. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ValuesWeights { + value_weights: BTreeMap, +} + +impl ValuesWeights { + pub fn new() -> ValuesWeights { + ValuesWeights { + value_weights: BTreeMap::new(), + } + } + + /// Add weight to the value and return the new weight. + pub fn add(&mut self, value: Value, weight: Weight) -> Weight + where + Value: Ord, + { + let entry = self.value_weights.entry(value).or_insert(0); + *entry += weight; // FIXME: Deal with overflows + *entry + } + + /// Return the weight of the value, or 0 if it is not present. + pub fn get(&self, value: &Value) -> Weight + where + Value: Ord, + { + self.value_weights.get(value).copied().unwrap_or(0) + } + + /// Return the sum of the weights of all values. + pub fn sum(&self) -> Weight { + self.value_weights.values().sum() // FIXME: Deal with overflows + } +} + +impl Default for ValuesWeights { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn values_weights() { + let mut vw = ValuesWeights::new(); + + assert_eq!(vw.get(&None), 0); + assert_eq!(vw.get(&Some(1)), 0); + + assert_eq!(vw.add(None, 1), 1); + assert_eq!(vw.get(&None), 1); + assert_eq!(vw.get(&Some(1)), 0); + + assert_eq!(vw.add(Some(1), 1), 1); + assert_eq!(vw.get(&None), 1); + assert_eq!(vw.get(&Some(1)), 1); + + assert_eq!(vw.add(None, 1), 2); + assert_eq!(vw.get(&None), 2); + assert_eq!(vw.get(&Some(1)), 1); + + assert_eq!(vw.add(Some(1), 1), 2); + assert_eq!(vw.get(&None), 2); + assert_eq!(vw.get(&Some(1)), 2); + + assert_eq!(vw.add(Some(2), 1), 1); + assert_eq!(vw.get(&None), 2); + assert_eq!(vw.get(&Some(1)), 2); + assert_eq!(vw.get(&Some(2)), 1); + + // FIXME: Test for and deal with overflows + } +}