diff --git a/Code/common/src/context.rs b/Code/common/src/context.rs index 3f848e3c0..565f6b7c4 100644 --- a/Code/common/src/context.rs +++ b/Code/common/src/context.rs @@ -40,6 +40,7 @@ where /// Build a new prevote vote by the validator with the given address, /// for the value identified by the given value id, at the given round. fn new_prevote( + height: Self::Height, round: Round, value_id: Option>, address: Self::Address, @@ -48,6 +49,7 @@ where /// Build a new precommit vote by the validator with the given address, /// for the value identified by the given value id, at the given round. fn new_precommit( + height: Self::Height, round: Round, value_id: Option>, address: Self::Address, diff --git a/Code/common/src/height.rs b/Code/common/src/height.rs index 511f69503..973ee3b61 100644 --- a/Code/common/src/height.rs +++ b/Code/common/src/height.rs @@ -9,6 +9,6 @@ use core::fmt::Debug; pub trait Height where // TODO: Require Copy as well? - Self: Clone + Debug + PartialEq + Eq + PartialOrd + Ord, + Self: Default + Clone + Debug + PartialEq + Eq + PartialOrd + Ord, { } diff --git a/Code/common/src/vote.rs b/Code/common/src/vote.rs index a67190a26..84bd6969b 100644 --- a/Code/common/src/vote.rs +++ b/Code/common/src/vote.rs @@ -21,11 +21,14 @@ where Self: Clone + Debug + Eq, Ctx: Context, { + /// The height for which the vote is for. + fn height(&self) -> Ctx::Height; + /// The round for which the vote is for. fn round(&self) -> Round; /// Get a reference to the value being voted for. - fn value(&self) -> Option<&::Id>; + fn value(&self) -> &Option<::Id>; /// Take ownership of the value being voted for. fn take_value(self) -> Option<::Id>; diff --git a/Code/driver/src/driver.rs b/Code/driver/src/driver.rs index 116862ecb..7b7cbc484 100644 --- a/Code/driver/src/driver.rs +++ b/Code/driver/src/driver.rs @@ -1,5 +1,3 @@ -use alloc::collections::BTreeMap; - use malachite_round::state_machine::RoundData; use malachite_common::{ @@ -12,6 +10,7 @@ use malachite_round::state::State as RoundState; use malachite_vote::keeper::Message as VoteMessage; use malachite_vote::keeper::VoteKeeper; use malachite_vote::Threshold; +use malachite_vote::ThresholdParams; use crate::env::Env as DriverEnv; use crate::event::Event; @@ -32,13 +31,11 @@ where pub env: Env, pub proposer_selector: PSel, - pub height: Ctx::Height, pub address: Ctx::Address, pub validator_set: Ctx::ValidatorSet, - pub round: Round, pub votes: VoteKeeper, - pub round_states: BTreeMap>, + pub round_state: RoundState, } impl Driver @@ -51,27 +48,37 @@ where ctx: Ctx, env: Env, proposer_selector: PSel, - height: Ctx::Height, validator_set: Ctx::ValidatorSet, address: Ctx::Address, ) -> Self { - let votes = VoteKeeper::new(validator_set.total_voting_power()); + let votes = VoteKeeper::new( + validator_set.total_voting_power(), + ThresholdParams::default(), // TODO: Make this configurable + ); Self { ctx, env, proposer_selector, - height, address, validator_set, - round: Round::NIL, votes, - round_states: BTreeMap::new(), + round_state: RoundState::default(), } } - async fn get_value(&self, round: Round) -> Option { - self.env.get_value(self.height.clone(), round).await + pub fn height(&self) -> &Ctx::Height { + &self.round_state.height + } + + pub fn round(&self) -> Round { + self.round_state.round + } + + async fn get_value(&self) -> Option { + self.env + .get_value(self.height().clone(), self.round()) + .await } pub async fn execute(&mut self, msg: Event) -> Result>, Error> { @@ -81,11 +88,7 @@ where }; let msg = match round_msg { - RoundMessage::NewRound(round) => { - // XXX: Check if there is an existing state? - assert!(self.round < round); - Message::NewRound(round) - } + RoundMessage::NewRound(round) => Message::NewRound(self.height().clone(), round), RoundMessage::Proposal(proposal) => { // sign the proposal @@ -108,21 +111,27 @@ where Ok(Some(msg)) } - async fn apply(&mut self, msg: Event) -> Result>, Error> { - match msg { - Event::NewRound(round) => self.apply_new_round(round).await, + async fn apply(&mut self, event: Event) -> Result>, Error> { + match event { + Event::NewRound(height, round) => self.apply_new_round(height, round).await, + Event::Proposal(proposal, validity) => { Ok(self.apply_proposal(proposal, validity).await) } + Event::Vote(signed_vote) => self.apply_vote(signed_vote), + Event::TimeoutElapsed(timeout) => Ok(self.apply_timeout(timeout)), } } async fn apply_new_round( &mut self, + height: Ctx::Height, round: Round, ) -> Result>, Error> { + self.round_state = RoundState::new(height, round); + let proposer_address = self .proposer_selector .select_proposer(round, &self.validator_set); @@ -136,7 +145,7 @@ where // We are the proposer // TODO: Schedule propose timeout - let Some(value) = self.get_value(round).await else { + let Some(value) = self.get_value().await else { return Err(Error::NoValueToPropose); }; @@ -145,11 +154,6 @@ where RoundEvent::NewRound }; - assert!(self.round < round); - self.round_states - .insert(round, RoundState::default().new_round(round)); - self.round = round; - Ok(self.apply_event(round, event)) } @@ -159,23 +163,24 @@ where validity: Validity, ) -> Option> { // Check that there is an ongoing round - let Some(round_state) = self.round_states.get(&self.round) else { - // TODO: Add logging + if self.round_state.round == Round::NIL { return None; - }; + } // Only process the proposal if there is no other proposal - if round_state.proposal.is_some() { + if self.round_state.proposal.is_some() { return None; } // Check that the proposal is for the current height and round - if self.height != proposal.height() || self.round != proposal.round() { + if self.round_state.height != proposal.height() + || self.round_state.round != proposal.round() + { return None; } // TODO: Document - if proposal.pol_round().is_defined() && proposal.pol_round() >= round_state.round { + if proposal.pol_round().is_defined() && proposal.pol_round() >= self.round_state.round { return None; } @@ -233,11 +238,12 @@ where )); } - let round = signed_vote.vote.round(); + let vote_round = signed_vote.vote.round(); + let current_round = self.round(); - let Some(vote_msg) = self - .votes - .apply_vote(signed_vote.vote, validator.voting_power()) + let Some(vote_msg) = + self.votes + .apply_vote(signed_vote.vote, validator.voting_power(), current_round) else { return Ok(None); }; @@ -251,7 +257,7 @@ where VoteMessage::SkipRound(r) => RoundEvent::SkipRound(r), }; - Ok(self.apply_event(round, round_event)) + Ok(self.apply_event(vote_round, round_event)) } fn apply_timeout(&mut self, timeout: Timeout) -> Option> { @@ -266,10 +272,9 @@ where /// Apply the event, update the state. 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_default(); + let round_state = core::mem::take(&mut self.round_state); - let data = RoundData::new(round, &self.height, &self.address); + let data = RoundData::new(round, round_state.height.clone(), &self.address); // Multiplex the event with the round state. let mux_event = match event { @@ -293,13 +298,9 @@ where let transition = round_state.apply_event(&data, mux_event); // Update state - self.round_states.insert(round, transition.next_state); + self.round_state = transition.next_state; // Return message, if any transition.message } - - pub fn round_state(&self, round: Round) -> Option<&RoundState> { - self.round_states.get(&round) - } } diff --git a/Code/driver/src/event.rs b/Code/driver/src/event.rs index d75dfab5c..d0a3381e1 100644 --- a/Code/driver/src/event.rs +++ b/Code/driver/src/event.rs @@ -8,7 +8,7 @@ pub enum Event where Ctx: Context, { - NewRound(Round), + NewRound(Ctx::Height, Round), Proposal(Ctx::Proposal, Validity), Vote(SignedVote), TimeoutElapsed(Timeout), diff --git a/Code/driver/src/message.rs b/Code/driver/src/message.rs index 89a56333b..b5f4d7073 100644 --- a/Code/driver/src/message.rs +++ b/Code/driver/src/message.rs @@ -11,7 +11,7 @@ where Vote(SignedVote), Decide(Round, Ctx::Value), ScheduleTimeout(Timeout), - NewRound(Round), + NewRound(Ctx::Height, Round), } // NOTE: We have to derive these instances manually, otherwise @@ -26,7 +26,7 @@ impl Clone for Message { Message::Vote(signed_vote) => Message::Vote(signed_vote.clone()), Message::Decide(round, value) => Message::Decide(*round, value.clone()), Message::ScheduleTimeout(timeout) => Message::ScheduleTimeout(*timeout), - Message::NewRound(round) => Message::NewRound(*round), + Message::NewRound(height, round) => Message::NewRound(height.clone(), *round), } } } @@ -39,7 +39,7 @@ impl fmt::Debug for Message { Message::Vote(signed_vote) => write!(f, "Vote({:?})", signed_vote), Message::Decide(round, value) => write!(f, "Decide({:?}, {:?})", round, value), Message::ScheduleTimeout(timeout) => write!(f, "ScheduleTimeout({:?})", timeout), - Message::NewRound(round) => write!(f, "NewRound({:?})", round), + Message::NewRound(height, round) => write!(f, "NewRound({:?}, {:?})", height, round), } } } @@ -60,7 +60,9 @@ impl PartialEq for Message { (Message::ScheduleTimeout(timeout), Message::ScheduleTimeout(other_timeout)) => { timeout == other_timeout } - (Message::NewRound(round), Message::NewRound(other_round)) => round == other_round, + (Message::NewRound(height, round), Message::NewRound(other_height, other_round)) => { + height == other_height && round == other_round + } _ => false, } } diff --git a/Code/round/src/message.rs b/Code/round/src/message.rs index 5e40369dd..877677f9e 100644 --- a/Code/round/src/message.rs +++ b/Code/round/src/message.rs @@ -25,12 +25,22 @@ impl Message { Message::Proposal(Ctx::new_proposal(height, round, value, pol_round)) } - pub fn prevote(round: Round, value_id: Option>, address: Ctx::Address) -> Self { - Message::Vote(Ctx::new_prevote(round, value_id, address)) + pub fn prevote( + height: Ctx::Height, + round: Round, + value_id: Option>, + address: Ctx::Address, + ) -> Self { + Message::Vote(Ctx::new_prevote(height, round, value_id, address)) } - pub fn precommit(round: Round, value_id: Option>, address: Ctx::Address) -> Self { - Message::Vote(Ctx::new_precommit(round, value_id, address)) + pub fn precommit( + height: Ctx::Height, + round: Round, + value_id: Option>, + address: Ctx::Address, + ) -> Self { + Message::Vote(Ctx::new_precommit(height, round, value_id, address)) } pub fn schedule_timeout(round: Round, step: TimeoutStep) -> Self { diff --git a/Code/round/src/state.rs b/Code/round/src/state.rs index d6d63ae5d..c7edc6e5e 100644 --- a/Code/round/src/state.rs +++ b/Code/round/src/state.rs @@ -34,6 +34,7 @@ pub struct State where Ctx: Context, { + pub height: Ctx::Height, pub round: Round, pub step: Step, pub proposal: Option, @@ -45,9 +46,10 @@ impl State where Ctx: Context, { - pub fn new() -> Self { + pub fn new(height: Ctx::Height, round: Round) -> Self { Self { - round: Round::INITIAL, + height, + round, step: Step::NewRound, proposal: None, locked: None, @@ -55,13 +57,6 @@ where } } - pub fn new_round(self, round: Round) -> Self { - Self { - round, - step: Step::NewRound, - ..self - } - } pub fn with_step(self, step: Step) -> Self { Self { step, ..self } } @@ -94,7 +89,7 @@ where Ctx: Context, { fn default() -> Self { - Self::new() + Self::new(Ctx::Height::default(), Round::NIL) } } @@ -105,6 +100,7 @@ where #[cfg_attr(coverage_nightly, coverage(off))] fn clone(&self) -> Self { Self { + height: self.height.clone(), round: self.round, step: self.step, proposal: self.proposal.clone(), diff --git a/Code/round/src/state_machine.rs b/Code/round/src/state_machine.rs index 595af5bc0..62c257fe6 100644 --- a/Code/round/src/state_machine.rs +++ b/Code/round/src/state_machine.rs @@ -16,7 +16,7 @@ where Ctx: Context, { pub round: Round, - pub height: &'a Ctx::Height, + pub height: Ctx::Height, pub address: &'a Ctx::Address, } @@ -24,7 +24,7 @@ impl<'a, Ctx> RoundData<'a, Ctx> where Ctx: Context, { - pub fn new(round: Round, height: &'a Ctx::Height, address: &'a Ctx::Address) -> Self { + pub fn new(round: Round, height: Ctx::Height, address: &'a Ctx::Address) -> Self { Self { round, height, @@ -62,7 +62,7 @@ where match (state.step, event) { // From NewRound. Event must be for current round. (Step::NewRound, Event::NewRoundProposer(value)) if this_round => { - propose(state, data.height, value) // L11/L14 + propose(state, &data.height, value) // L11/L14 } (Step::NewRound, Event::NewRound) if this_round => schedule_timeout_propose(state), // L11/L20 @@ -173,7 +173,7 @@ where None => Some(proposed), // not locked, prevote the value }; - let message = Message::prevote(state.round, value, address.clone()); + let message = Message::prevote(state.height.clone(), state.round, value, address.clone()); Transition::to(state.with_step(Step::Prevote)).with_message(message) } @@ -184,7 +184,7 @@ pub fn prevote_nil(state: State, address: &Ctx::Address) -> Transition where Ctx: Context, { - let message = Message::prevote(state.round, None, address.clone()); + let message = Message::prevote(state.height.clone(), state.round, None, address.clone()); Transition::to(state.with_step(Step::Prevote)).with_message(message) } @@ -211,7 +211,12 @@ where } let value = proposal.value(); - let message = Message::precommit(state.round, Some(value.id()), address.clone()); + let message = Message::precommit( + state.height.clone(), + state.round, + Some(value.id()), + address.clone(), + ); let current_value = match state.proposal { Some(ref proposal) => proposal.value().clone(), @@ -238,7 +243,7 @@ pub fn precommit_nil(state: State, address: &Ctx::Address) -> Transiti where Ctx: Context, { - let message = Message::precommit(state.round, None, address.clone()); + let message = Message::precommit(state.height.clone(), state.round, None, address.clone()); Transition::to(state.with_step(Step::Precommit)).with_message(message) } @@ -326,7 +331,7 @@ pub fn round_skip(state: State, round: Round) -> Transition where Ctx: Context, { - Transition::to(state.new_round(round)).with_message(Message::NewRound(round)) + Transition::to(State::new(state.height.clone(), round)).with_message(Message::NewRound(round)) } /// We received +2/3 precommits for a value - commit and decide that value! diff --git a/Code/test/src/context.rs b/Code/test/src/context.rs index f61eba295..28ff263be 100644 --- a/Code/test/src/context.rs +++ b/Code/test/src/context.rs @@ -47,11 +47,21 @@ impl Context for TestContext { 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_prevote( + height: Height, + round: Round, + value_id: Option, + address: Address, + ) -> Vote { + Vote::new_prevote(height, round, value_id, address) } - fn new_precommit(round: Round, value_id: Option, address: Address) -> Vote { - Vote::new_precommit(round, value_id, address) + fn new_precommit( + height: Height, + round: Round, + value_id: Option, + address: Address, + ) -> Vote { + Vote::new_precommit(height, round, value_id, address) } } diff --git a/Code/test/src/vote.rs b/Code/test/src/vote.rs index d8a1a9aeb..35ae1117f 100644 --- a/Code/test/src/vote.rs +++ b/Code/test/src/vote.rs @@ -2,30 +2,43 @@ use signature::Signer; use malachite_common::{Round, SignedVote, VoteType}; -use crate::{Address, PrivateKey, TestContext, ValueId}; +use crate::{Address, Height, PrivateKey, TestContext, ValueId}; /// A vote for a value in a round #[derive(Clone, Debug, PartialEq, Eq)] pub struct Vote { pub typ: VoteType, + pub height: Height, pub round: Round, pub value: Option, pub validator_address: Address, } impl Vote { - pub fn new_prevote(round: Round, value: Option, validator_address: Address) -> Self { + pub fn new_prevote( + height: Height, + round: Round, + value: Option, + validator_address: Address, + ) -> Self { Self { typ: VoteType::Prevote, + height, round, value, validator_address, } } - pub fn new_precommit(round: Round, value: Option, address: Address) -> Self { + pub fn new_precommit( + height: Height, + round: Round, + value: Option, + address: Address, + ) -> Self { Self { typ: VoteType::Precommit, + height, round, value, validator_address: address, @@ -61,12 +74,16 @@ impl Vote { } impl malachite_common::Vote for Vote { + fn height(&self) -> Height { + self.height + } + fn round(&self) -> Round { self.round } - fn value(&self) -> Option<&ValueId> { - self.value.as_ref() + fn value(&self) -> &Option { + &self.value } fn take_value(self) -> Option { diff --git a/Code/test/tests/driver.rs b/Code/test/tests/driver.rs index bec1bbb13..2e031e0a0 100644 --- a/Code/test/tests/driver.rs +++ b/Code/test/tests/driver.rs @@ -25,7 +25,7 @@ fn to_input_msg(output: Message) -> Option> { Message::Vote(v) => Some(Event::Vote(v)), Message::Decide(_, _) => None, Message::ScheduleTimeout(_) => None, - Message::NewRound(round) => Some(Event::NewRound(round)), + Message::NewRound(height, round) => Some(Event::NewRound(height, round)), } } @@ -85,17 +85,18 @@ fn driver_steps_proposer() { let ctx = TestContext::new(my_sk.clone()); let vs = ValidatorSet::new(vec![v1, v2.clone(), v3.clone()]); - let mut driver = Driver::new(ctx, env, sel, Height::new(1), vs, my_addr); + let mut driver = Driver::new(ctx, env, sel, vs, my_addr); let proposal = Proposal::new(Height::new(1), Round::new(0), value, Round::new(-1)); let steps = vec![ TestStep { desc: "Start round 0, we are proposer, propose value", - input_event: Some(Event::NewRound(Round::new(0))), + input_event: Some(Event::NewRound(Height::new(1), Round::new(0))), expected_output: Some(Message::Propose(proposal.clone())), expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Propose, proposal: None, @@ -107,10 +108,12 @@ fn driver_steps_proposer() { desc: "Receive our own proposal, prevote for it (v1)", input_event: None, expected_output: Some(Message::Vote( - Vote::new_prevote(Round::new(0), Some(value.id()), my_addr).signed(&my_sk), + Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), my_addr) + .signed(&my_sk), )), expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Prevote, proposal: Some(proposal.clone()), @@ -124,6 +127,7 @@ fn driver_steps_proposer() { expected_output: None, expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Prevote, proposal: Some(proposal.clone()), @@ -134,11 +138,13 @@ fn driver_steps_proposer() { TestStep { desc: "v2 prevotes for our proposal", input_event: Some(Event::Vote( - Vote::new_prevote(Round::new(0), Some(value.id()), addr2).signed(&sk2), + Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), addr2) + .signed(&sk2), )), expected_output: None, expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Prevote, proposal: Some(proposal.clone()), @@ -149,13 +155,16 @@ fn driver_steps_proposer() { TestStep { desc: "v3 prevotes for our proposal, we get +2/3 prevotes, precommit for it (v1)", input_event: Some(Event::Vote( - Vote::new_prevote(Round::new(0), Some(value.id()), addr3).signed(&sk3), + Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), addr3) + .signed(&sk3), )), expected_output: Some(Message::Vote( - Vote::new_precommit(Round::new(0), Some(value.id()), my_addr).signed(&my_sk), + Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), my_addr) + .signed(&my_sk), )), expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Precommit, proposal: Some(proposal.clone()), @@ -175,6 +184,7 @@ fn driver_steps_proposer() { expected_output: None, expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Precommit, proposal: Some(proposal.clone()), @@ -191,11 +201,13 @@ fn driver_steps_proposer() { TestStep { desc: "v2 precommits for our proposal", input_event: Some(Event::Vote( - Vote::new_precommit(Round::new(0), Some(value.id()), addr2).signed(&sk2), + Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), addr2) + .signed(&sk2), )), expected_output: None, expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Precommit, proposal: Some(proposal.clone()), @@ -212,11 +224,13 @@ fn driver_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()), addr3).signed(&sk3), + Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), addr3) + .signed(&sk3), )), expected_output: Some(Message::Decide(Round::new(0), value)), expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Commit, proposal: Some(proposal.clone()), @@ -244,10 +258,12 @@ fn driver_steps_proposer() { let output = block_on(driver.execute(execute_message)).expect("execute succeeded"); assert_eq!(output, step.expected_output, "expected output message"); - assert_eq!(driver.round, step.expected_round, "expected round"); + assert_eq!( + driver.round_state.round, step.expected_round, + "expected round" + ); - let new_state = driver.round_state(Round::new(0)).unwrap(); - assert_eq!(new_state, &step.new_state, "expected state"); + assert_eq!(driver.round_state, step.new_state, "expected state"); previous_message = output.and_then(to_input_msg); } @@ -256,7 +272,6 @@ fn driver_steps_proposer() { #[test] fn driver_steps_not_proposer_valid() { let value = Value::new(9999); - let value_id = value.id(); let sel = RotateProposer::default(); let env = TestEnv::new(move |_, _| Some(value)); @@ -281,17 +296,18 @@ fn driver_steps_not_proposer_valid() { let ctx = TestContext::new(my_sk.clone()); let vs = ValidatorSet::new(vec![v1.clone(), v2.clone(), v3.clone()]); - let mut driver = Driver::new(ctx, env, sel, Height::new(1), vs, my_addr); + let mut driver = Driver::new(ctx, env, sel, vs, my_addr); let proposal = Proposal::new(Height::new(1), Round::new(0), value, Round::new(-1)); let steps = vec![ TestStep { desc: "Start round 0, we are not the proposer", - input_event: Some(Event::NewRound(Round::new(0))), + input_event: Some(Event::NewRound(Height::new(1), Round::new(0))), expected_output: Some(Message::ScheduleTimeout(Timeout::propose(Round::new(0)))), expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Propose, proposal: None, @@ -303,10 +319,12 @@ fn driver_steps_not_proposer_valid() { desc: "Receive a proposal, prevote for it (v2)", input_event: Some(Event::Proposal(proposal.clone(), Validity::Valid)), expected_output: Some(Message::Vote( - Vote::new_prevote(Round::new(0), Some(value_id), my_addr).signed(&my_sk), + Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), my_addr) + .signed(&my_sk), )), expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Prevote, proposal: Some(proposal.clone()), @@ -320,6 +338,7 @@ fn driver_steps_not_proposer_valid() { expected_output: None, expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Prevote, proposal: Some(proposal.clone()), @@ -330,11 +349,13 @@ fn driver_steps_not_proposer_valid() { TestStep { desc: "v1 prevotes for its own proposal", input_event: Some(Event::Vote( - Vote::new_prevote(Round::new(0), Some(value_id), addr1).signed(&sk1), + Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), addr1) + .signed(&sk1), )), expected_output: None, expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Prevote, proposal: Some(proposal.clone()), @@ -345,13 +366,16 @@ fn driver_steps_not_proposer_valid() { TestStep { desc: "v3 prevotes for v1's proposal, it gets +2/3 prevotes, precommit for it (v2)", input_event: Some(Event::Vote( - Vote::new_prevote(Round::new(0), Some(value_id), addr3).signed(&sk3), + Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), addr3) + .signed(&sk3), )), expected_output: Some(Message::Vote( - Vote::new_precommit(Round::new(0), Some(value_id), my_addr).signed(&my_sk), + Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), my_addr) + .signed(&my_sk), )), expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Precommit, proposal: Some(proposal.clone()), @@ -371,6 +395,7 @@ fn driver_steps_not_proposer_valid() { expected_output: None, expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Precommit, proposal: Some(proposal.clone()), @@ -387,11 +412,13 @@ fn driver_steps_not_proposer_valid() { TestStep { desc: "v1 precommits its proposal", input_event: Some(Event::Vote( - Vote::new_precommit(Round::new(0), Some(value_id), addr1).signed(&sk1), + Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), addr1) + .signed(&sk1), )), expected_output: None, expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Precommit, proposal: Some(proposal.clone()), @@ -408,11 +435,13 @@ fn driver_steps_not_proposer_valid() { TestStep { desc: "v3 precommits for v1's proposal, it gets +2/3 precommits, decide it", input_event: Some(Event::Vote( - Vote::new_precommit(Round::new(0), Some(value_id), addr3).signed(&sk3), + Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), addr3) + .signed(&sk3), )), expected_output: Some(Message::Decide(Round::new(0), value)), expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Commit, proposal: Some(proposal.clone()), @@ -440,10 +469,12 @@ fn driver_steps_not_proposer_valid() { let output = block_on(driver.execute(execute_message)).expect("execute succeeded"); assert_eq!(output, step.expected_output, "expected output message"); - assert_eq!(driver.round, step.expected_round, "expected round"); + assert_eq!( + driver.round_state.round, step.expected_round, + "expected round" + ); - let new_state = driver.round_state(Round::new(0)).unwrap(); - assert_eq!(new_state, &step.new_state, "expected state"); + assert_eq!(driver.round_state, step.new_state, "expected state"); previous_message = output.and_then(to_input_msg); } @@ -452,7 +483,6 @@ fn driver_steps_not_proposer_valid() { #[test] fn driver_steps_not_proposer_invalid() { let value = Value::new(9999); - let value_id = value.id(); let sel = RotateProposer::default(); let env = TestEnv::new(move |_, _| Some(value)); @@ -477,17 +507,18 @@ fn driver_steps_not_proposer_invalid() { let ctx = TestContext::new(my_sk.clone()); let vs = ValidatorSet::new(vec![v1.clone(), v2.clone(), v3.clone()]); - let mut driver = Driver::new(ctx, env, sel, Height::new(1), vs, my_addr); + let mut driver = Driver::new(ctx, env, sel, vs, my_addr); let proposal = Proposal::new(Height::new(1), Round::new(0), value, Round::new(-1)); let steps = vec![ TestStep { desc: "Start round 0, we are not the proposer", - input_event: Some(Event::NewRound(Round::new(0))), + input_event: Some(Event::NewRound(Height::new(1), Round::new(0))), expected_output: Some(Message::ScheduleTimeout(Timeout::propose(Round::new(0)))), expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Propose, proposal: None, @@ -499,10 +530,11 @@ fn driver_steps_not_proposer_invalid() { desc: "Receive an invalid proposal, prevote for nil (v2)", input_event: Some(Event::Proposal(proposal.clone(), Validity::Invalid)), expected_output: Some(Message::Vote( - Vote::new_prevote(Round::new(0), None, my_addr).signed(&my_sk), + Vote::new_prevote(Height::new(1),Round::new(0), None, my_addr).signed(&my_sk), )), expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Prevote, proposal: None, @@ -516,6 +548,7 @@ fn driver_steps_not_proposer_invalid() { expected_output: None, expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Prevote, proposal: None, @@ -526,11 +559,12 @@ fn driver_steps_not_proposer_invalid() { TestStep { desc: "v1 prevotes for its own proposal", input_event: Some(Event::Vote( - Vote::new_prevote(Round::new(0), Some(value_id), addr1).signed(&sk1), + Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), addr1).signed(&sk1), )), expected_output: None, expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Prevote, proposal: None, @@ -541,11 +575,12 @@ fn driver_steps_not_proposer_invalid() { TestStep { desc: "v3 prevotes for v1's proposal, we have polka for any, schedule prevote timeout (v2)", input_event: Some(Event::Vote( - Vote::new_prevote(Round::new(0), Some(value_id), addr3).signed(&sk3), + Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), addr3).signed(&sk3), )), expected_output: Some(Message::ScheduleTimeout(Timeout::prevote(Round::new(0)))), expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Prevote, proposal: None, @@ -557,10 +592,11 @@ fn driver_steps_not_proposer_invalid() { desc: "prevote timeout elapses, we precommit for nil (v2)", input_event: Some(Event::TimeoutElapsed(Timeout::prevote(Round::new(0)))), expected_output: Some(Message::Vote( - Vote::new_precommit(Round::new(0), None, my_addr).signed(&my_sk), + Vote::new_precommit(Height::new(1), Round::new(0), None, my_addr).signed(&my_sk), )), expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Precommit, proposal: None, @@ -582,10 +618,12 @@ fn driver_steps_not_proposer_invalid() { let output = block_on(driver.execute(execute_message)).expect("execute succeeded"); assert_eq!(output, step.expected_output, "expected output"); - assert_eq!(driver.round, step.expected_round, "expected round"); + assert_eq!( + driver.round_state.round, step.expected_round, + "expected round" + ); - let new_state = driver.round_state(driver.round).unwrap(); - assert_eq!(new_state, &step.new_state, "expected state"); + assert_eq!(driver.round_state, step.new_state, "expected state"); previous_message = output.and_then(to_input_msg); } @@ -594,7 +632,6 @@ fn driver_steps_not_proposer_invalid() { #[test] fn driver_steps_not_proposer_timeout_multiple_rounds() { let value = Value::new(9999); - let value_id = value.id(); let sel = RotateProposer::default(); let env = TestEnv::new(move |_, _| Some(value)); @@ -619,16 +656,17 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { let ctx = TestContext::new(my_sk.clone()); let vs = ValidatorSet::new(vec![v1.clone(), v2.clone(), v3.clone()]); - let mut driver = Driver::new(ctx, env, sel, Height::new(1), vs, my_addr); + let mut driver = Driver::new(ctx, env, sel, vs, my_addr); let steps = vec![ // Start round 0, we, v3, are not the proposer TestStep { desc: "Start round 0, we, v3, are not the proposer", - input_event: Some(Event::NewRound(Round::new(0))), + input_event: Some(Event::NewRound(Height::new(1), Round::new(0))), expected_output: Some(Message::ScheduleTimeout(Timeout::propose(Round::new(0)))), expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Propose, proposal: None, @@ -641,11 +679,12 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { desc: "Receive a propose timeout, prevote for nil (v3)", input_event: Some(Event::TimeoutElapsed(Timeout::propose(Round::new(0)))), expected_output: Some(Message::Vote( - Vote::new_prevote(Round::new(0), None, my_addr).signed(&my_sk), + Vote::new_prevote(Height::new(1), Round::new(0), None, my_addr).signed(&my_sk), )), expected_round: Round::new(0), new_state: State { round: Round::new(0), + height: Height::new(1), step: Step::Prevote, proposal: None, locked: None, @@ -659,6 +698,7 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { expected_output: None, expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Prevote, proposal: None, @@ -670,11 +710,13 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { TestStep { desc: "v1 prevotes for its own proposal", input_event: Some(Event::Vote( - Vote::new_prevote(Round::new(0), Some(value_id), addr1).signed(&sk1), + Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), addr1) + .signed(&sk1), )), expected_output: None, expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Prevote, proposal: None, @@ -686,13 +728,14 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { TestStep { desc: "v2 prevotes for nil, we get +2/3 prevotes, precommit for nil", input_event: Some(Event::Vote( - Vote::new_prevote(Round::new(0), None, addr2).signed(&sk2), + Vote::new_prevote(Height::new(1), Round::new(0), None, addr2).signed(&sk2), )), expected_output: Some(Message::Vote( - Vote::new_precommit(Round::new(0), None, my_addr).signed(&my_sk), + Vote::new_precommit(Height::new(1), Round::new(0), None, my_addr).signed(&my_sk), )), expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Precommit, proposal: None, @@ -707,6 +750,7 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { expected_output: None, expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Precommit, proposal: None, @@ -718,11 +762,13 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { TestStep { desc: "v1 precommits its proposal", input_event: Some(Event::Vote( - Vote::new_precommit(Round::new(0), Some(value_id), addr1).signed(&sk1), + Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), addr1) + .signed(&sk1), )), expected_output: None, expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Precommit, proposal: None, @@ -734,11 +780,12 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { TestStep { desc: "v2 precommits for nil", input_event: Some(Event::Vote( - Vote::new_precommit(Round::new(0), None, addr2).signed(&sk2), + Vote::new_precommit(Height::new(1), Round::new(0), None, addr2).signed(&sk2), )), expected_output: Some(Message::ScheduleTimeout(Timeout::precommit(Round::new(0)))), expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(0), step: Step::Precommit, proposal: None, @@ -750,9 +797,10 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { TestStep { desc: "we receive a precommit timeout, start a new round", input_event: Some(Event::TimeoutElapsed(Timeout::precommit(Round::new(0)))), - expected_output: Some(Message::NewRound(Round::new(1))), + expected_output: Some(Message::NewRound(Height::new(1), Round::new(1))), expected_round: Round::new(0), new_state: State { + height: Height::new(1), round: Round::new(1), step: Step::NewRound, proposal: None, @@ -762,10 +810,11 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { }, TestStep { desc: "Start round 1, we are not the proposer", - input_event: Some(Event::NewRound(Round::new(1))), + input_event: Some(Event::NewRound(Height::new(1), Round::new(1))), expected_output: Some(Message::ScheduleTimeout(Timeout::propose(Round::new(1)))), expected_round: Round::new(1), new_state: State { + height: Height::new(1), round: Round::new(1), step: Step::Propose, proposal: None, @@ -787,10 +836,7 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { let output = block_on(driver.execute(execute_message)).expect("execute succeeded"); assert_eq!(output, step.expected_output, "expected output message"); - assert_eq!(driver.round, step.expected_round, "expected round"); - - let new_state = driver.round_state(driver.round).unwrap(); - assert_eq!(new_state, &step.new_state, "new state"); + assert_eq!(driver.round_state, step.new_state, "new state"); previous_message = output.and_then(to_input_msg); } @@ -818,9 +864,9 @@ fn driver_steps_no_value_to_propose() { let sel = FixedProposer::new(v1.address); let vs = ValidatorSet::new(vec![v1.clone(), v2.clone(), v3.clone()]); - let mut driver = Driver::new(ctx, env, sel, Height::new(1), vs, my_addr); + let mut driver = Driver::new(ctx, env, sel, vs, my_addr); - let output = block_on(driver.execute(Event::NewRound(Round::new(0)))); + let output = block_on(driver.execute(Event::NewRound(Height::new(1), Round::new(0)))); assert_eq!(output, Err(Error::NoValueToPropose)); } @@ -849,9 +895,9 @@ fn driver_steps_proposer_not_found() { let sel = FixedProposer::new(v1.address); let vs = ValidatorSet::new(vec![v2.clone(), v3.clone()]); - let mut driver = Driver::new(ctx, env, sel, Height::new(1), vs, my_addr); + let mut driver = Driver::new(ctx, env, sel, vs, my_addr); - let output = block_on(driver.execute(Event::NewRound(Round::new(0)))); + let output = block_on(driver.execute(Event::NewRound(Height::new(1), Round::new(0)))); assert_eq!(output, Err(Error::ProposerNotFound(v1.address))); } @@ -879,14 +925,15 @@ fn driver_steps_validator_not_found() { // We omit v2 from the validator set let vs = ValidatorSet::new(vec![v1.clone(), v3.clone()]); - let mut driver = Driver::new(ctx, env, sel, Height::new(1), vs, my_addr); + let mut driver = Driver::new(ctx, env, sel, vs, my_addr); - // Start new round - block_on(driver.execute(Event::NewRound(Round::new(0)))).expect("execute succeeded"); + // Start new height + block_on(driver.execute(Event::NewRound(Height::new(1), Round::new(0)))) + .expect("execute succeeded"); // v2 prevotes for some proposal, we cannot find it in the validator set => error let output = block_on(driver.execute(Event::Vote( - Vote::new_prevote(Round::new(0), Some(value.id()), v2.address).signed(&sk2), + Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), v2.address).signed(&sk2), ))); assert_eq!(output, Err(Error::ValidatorNotFound(v2.address))); @@ -914,16 +961,284 @@ fn driver_steps_invalid_signature() { let sel = FixedProposer::new(v1.address); let vs = ValidatorSet::new(vec![v1.clone(), v2.clone(), v3.clone()]); - let mut driver = Driver::new(ctx, env, sel, Height::new(1), vs, my_addr); + let mut driver = Driver::new(ctx, env, sel, vs, my_addr); // Start new round - block_on(driver.execute(Event::NewRound(Round::new(0)))).expect("execute succeeded"); + block_on(driver.execute(Event::NewRound(Height::new(1), Round::new(0)))) + .expect("execute succeeded"); // v2 prevotes for some proposal, with an invalid signature, // ie. signed by v1 instead of v2, just a way of forging an invalid signature let output = block_on(driver.execute(Event::Vote( - Vote::new_prevote(Round::new(0), Some(value.id()), v2.address).signed(&sk1), + Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), v2.address).signed(&sk1), ))); assert!(matches!(output, Err(Error::InvalidVoteSignature(_, _)))); } + +#[test] +fn driver_steps_skip_round_skip_threshold() { + let value = Value::new(9999); + + let sel = RotateProposer::default(); + let env = TestEnv::new(move |_, _| Some(value)); + + let mut rng = StdRng::seed_from_u64(0x42); + + let sk1 = PrivateKey::generate(&mut rng); + let sk2 = PrivateKey::generate(&mut rng); + let sk3 = PrivateKey::generate(&mut rng); + + let addr1 = Address::from_public_key(&sk1.public_key()); + let addr2 = Address::from_public_key(&sk2.public_key()); + let addr3 = Address::from_public_key(&sk3.public_key()); + + let v1 = Validator::new(sk1.public_key(), 1); + let v2 = Validator::new(sk2.public_key(), 1); + let v3 = Validator::new(sk3.public_key(), 1); + + // Proposer is v1, so we, v3, are not the proposer + let (my_sk, my_addr) = (sk3, addr3); + + let ctx = TestContext::new(my_sk.clone()); + let height = Height::new(1); + + let vs = ValidatorSet::new(vec![v1.clone(), v2.clone(), v3.clone()]); + let mut driver = Driver::new(ctx, env, sel, vs, my_addr); + + let steps = vec![ + // Start round 0, we, v3, are not the proposer + TestStep { + desc: "Start round 0, we, v3, are not the proposer", + input_event: Some(Event::NewRound(height, Round::new(0))), + expected_output: Some(Message::ScheduleTimeout(Timeout::propose(Round::new(0)))), + expected_round: Round::new(0), + new_state: State { + height, + round: Round::new(0), + step: Step::Propose, + proposal: None, + locked: None, + valid: None, + }, + }, + // Receive a propose timeout, prevote for nil (from v3) + TestStep { + desc: "Receive a propose timeout, prevote for nil (v3)", + input_event: Some(Event::TimeoutElapsed(Timeout::propose(Round::new(0)))), + expected_output: Some(Message::Vote( + Vote::new_prevote(height, Round::new(0), None, my_addr).signed(&my_sk), + )), + expected_round: Round::new(0), + new_state: State { + height, + round: Round::new(0), + step: Step::Prevote, + proposal: None, + locked: None, + valid: None, + }, + }, + // Receive our own prevote v3 + TestStep { + desc: "Receive our own prevote v3", + input_event: None, + expected_output: None, + expected_round: Round::new(0), + new_state: State { + height, + round: Round::new(0), + step: Step::Prevote, + proposal: None, + locked: None, + valid: None, + }, + }, + // v1 prevotes for its own proposal + TestStep { + desc: "v1 prevotes for its own proposal in round 1", + input_event: Some(Event::Vote( + Vote::new_prevote(height, Round::new(1), Some(value.id()), addr1).signed(&sk1), + )), + expected_output: None, + expected_round: Round::new(0), + new_state: State { + height, + round: Round::new(0), + step: Step::Prevote, + proposal: None, + locked: None, + valid: None, + }, + }, + // v2 prevotes for v1 proposal in round 1, expected output is to move to next round + TestStep { + desc: "v2 prevotes for v1 proposal, we get +1/3 messages from future round", + input_event: Some(Event::Vote( + Vote::new_prevote(height, Round::new(1), Some(value.id()), addr2).signed(&sk2), + )), + expected_output: Some(Message::NewRound(height, Round::new(1))), + expected_round: Round::new(1), + new_state: State { + height, + round: Round::new(1), + step: Step::NewRound, + proposal: None, + locked: None, + valid: None, + }, + }, + ]; + + let mut previous_message = None; + + for step in steps { + println!("Step: {}", step.desc); + + let execute_message = step + .input_event + .unwrap_or_else(|| previous_message.unwrap()); + + let output = block_on(driver.execute(execute_message)).expect("execute succeeded"); + assert_eq!(output, step.expected_output, "expected output message"); + + assert_eq!(driver.round(), step.expected_round, "expected round"); + assert_eq!(driver.round_state, step.new_state, "new state"); + + previous_message = output.and_then(to_input_msg); + } +} + +#[test] +fn driver_steps_skip_round_quorum_threshold() { + let value = Value::new(9999); + + let sel = RotateProposer::default(); + let env = TestEnv::new(move |_, _| Some(value)); + + let mut rng = StdRng::seed_from_u64(0x42); + + let sk1 = PrivateKey::generate(&mut rng); + let sk2 = PrivateKey::generate(&mut rng); + let sk3 = PrivateKey::generate(&mut rng); + + let addr1 = Address::from_public_key(&sk1.public_key()); + let addr2 = Address::from_public_key(&sk2.public_key()); + let addr3 = Address::from_public_key(&sk3.public_key()); + + let v1 = Validator::new(sk1.public_key(), 1); + let v2 = Validator::new(sk2.public_key(), 2); + let v3 = Validator::new(sk3.public_key(), 1); + + // Proposer is v1, so we, v3, are not the proposer + let (my_sk, my_addr) = (sk3, addr3); + + let ctx = TestContext::new(my_sk.clone()); + let height = Height::new(1); + + let vs = ValidatorSet::new(vec![v1.clone(), v2.clone(), v3.clone()]); + let mut driver = Driver::new(ctx, env, sel, vs, my_addr); + + let steps = vec![ + // Start round 0, we, v3, are not the proposer + TestStep { + desc: "Start round 0, we, v3, are not the proposer", + input_event: Some(Event::NewRound(height, Round::new(0))), + expected_output: Some(Message::ScheduleTimeout(Timeout::propose(Round::new(0)))), + expected_round: Round::new(0), + new_state: State { + height, + round: Round::new(0), + step: Step::Propose, + proposal: None, + locked: None, + valid: None, + }, + }, + // Receive a propose timeout, prevote for nil (from v3) + TestStep { + desc: "Receive a propose timeout, prevote for nil (v3)", + input_event: Some(Event::TimeoutElapsed(Timeout::propose(Round::new(0)))), + expected_output: Some(Message::Vote( + Vote::new_prevote(height, Round::new(0), None, my_addr).signed(&my_sk), + )), + expected_round: Round::new(0), + new_state: State { + height, + round: Round::new(0), + step: Step::Prevote, + proposal: None, + locked: None, + valid: None, + }, + }, + // Receive our own prevote v3 + TestStep { + desc: "Receive our own prevote v3", + input_event: None, + expected_output: None, + expected_round: Round::new(0), + new_state: State { + height, + round: Round::new(0), + step: Step::Prevote, + proposal: None, + locked: None, + valid: None, + }, + }, + // v1 prevotes for its own proposal + TestStep { + desc: "v1 prevotes for its own proposal in round 1", + input_event: Some(Event::Vote( + Vote::new_prevote(height, Round::new(1), Some(value.id()), addr1).signed(&sk1), + )), + expected_output: None, + expected_round: Round::new(0), + new_state: State { + height, + round: Round::new(0), + step: Step::Prevote, + proposal: None, + locked: None, + valid: None, + }, + }, + // v2 prevotes for v1 proposal in round 1, expected output is to move to next round + TestStep { + desc: "v2 prevotes for v1 proposal, we get +1/3 messages from future round", + input_event: Some(Event::Vote( + Vote::new_prevote(height, Round::new(1), Some(value.id()), addr2).signed(&sk2), + )), + expected_output: Some(Message::NewRound(height, Round::new(1))), + expected_round: Round::new(1), + new_state: State { + height, + round: Round::new(1), + step: Step::NewRound, + proposal: None, + locked: None, + valid: None, + }, + }, + ]; + + let mut previous_message = None; + + for step in steps { + println!("Step: {}", step.desc); + + let execute_message = step + .input_event + .unwrap_or_else(|| previous_message.unwrap()); + + let output = block_on(driver.execute(execute_message)).expect("execute succeeded"); + assert_eq!(output, step.expected_output, "expected output message"); + + assert_eq!(driver.round(), step.expected_round, "expected round"); + + assert_eq!(driver.round_state, step.new_state, "new state"); + + previous_message = output.and_then(to_input_msg); + } +} diff --git a/Code/test/tests/round.rs b/Code/test/tests/round.rs index 819d5fa3d..cd6d2b5b9 100644 --- a/Code/test/tests/round.rs +++ b/Code/test/tests/round.rs @@ -19,7 +19,7 @@ fn test_propose() { ..Default::default() }; - let data = RoundData::new(round, &height, &ADDRESS); + let data = RoundData::new(round, height, &ADDRESS); let transition = apply_event(state.clone(), &data, Event::NewRoundProposer(value)); @@ -36,9 +36,15 @@ fn test_propose() { fn test_prevote() { let value = Value::new(42); let height = Height::new(1); + let round = Round::new(1); - let state: State = State::default().new_round(Round::new(1)); - let data = RoundData::new(Round::new(1), &height, &ADDRESS); + let state: State = State { + height, + round, + ..Default::default() + }; + + let data = RoundData::new(Round::new(1), height, &ADDRESS); let transition = apply_event(state, &data, Event::NewRound); @@ -67,6 +73,6 @@ fn test_prevote() { assert_eq!(transition.next_state.step, Step::Prevote); assert_eq!( transition.message.unwrap(), - Message::prevote(Round::new(1), Some(value.id()), ADDRESS) + Message::prevote(Height::new(1), Round::new(1), Some(value.id()), ADDRESS) ); } diff --git a/Code/test/tests/round_votes.rs b/Code/test/tests/round_votes.rs index 8be3f46e4..6132734c9 100644 --- a/Code/test/tests/round_votes.rs +++ b/Code/test/tests/round_votes.rs @@ -1,6 +1,5 @@ use malachite_common::VoteType; use malachite_vote::round_votes::RoundVotes; -use malachite_vote::Threshold; use malachite_test::{Address, ValueId}; @@ -13,47 +12,41 @@ const ADDRESS6: Address = Address::new([46; 20]); #[test] fn add_votes_nil() { - let total = 3; + let mut round_votes: RoundVotes<_, ValueId> = RoundVotes::new(); - let mut round_votes: RoundVotes<_, ValueId> = RoundVotes::new(total, Default::default()); + let w = round_votes.add_vote(VoteType::Prevote, ADDRESS1, None, 1); + assert_eq!(w, 1); - // add a vote for nil. nothing changes. - let thresh = round_votes.add_vote(VoteType::Prevote, ADDRESS1, None, 1); - assert_eq!(thresh, Threshold::Unreached); + let w = round_votes.add_vote(VoteType::Prevote, ADDRESS2, None, 1); + assert_eq!(w, 2); - // add it again, nothing changes. - 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(VoteType::Prevote, ADDRESS3, None, 1); - assert_eq!(thresh, Threshold::Nil); + let w = round_votes.add_vote(VoteType::Prevote, ADDRESS3, None, 1); + assert_eq!(w, 3); } #[test] fn add_votes_single_value() { let v = ValueId::new(1); let val = Some(v); - let total = 4; let weight = 1; - let mut round_votes: RoundVotes<_, ValueId> = RoundVotes::new(total, Default::default()); + let mut round_votes: RoundVotes<_, ValueId> = RoundVotes::new(); // add a vote. nothing changes. - let thresh = round_votes.add_vote(VoteType::Prevote, ADDRESS1, val, weight); - assert_eq!(thresh, Threshold::Unreached); + let w = round_votes.add_vote(VoteType::Prevote, ADDRESS1, val, weight); + assert_eq!(w, 1); // add it again, nothing changes. - let thresh = round_votes.add_vote(VoteType::Prevote, ADDRESS2, val, weight); - assert_eq!(thresh, Threshold::Unreached); + let w = round_votes.add_vote(VoteType::Prevote, ADDRESS2, val, weight); + assert_eq!(w, 2); - // add a vote for nil, get Thresh::Any - let thresh = round_votes.add_vote(VoteType::Prevote, ADDRESS3, None, weight); - assert_eq!(thresh, Threshold::Any); + // add a vote for nil, get w::Any + let w = round_votes.add_vote(VoteType::Prevote, ADDRESS3, None, weight); + assert_eq!(w, 1); - // add vote for value, get Thresh::Value - let thresh = round_votes.add_vote(VoteType::Prevote, ADDRESS4, val, weight); - assert_eq!(thresh, Threshold::Value(v)); + // add vote for value, get w::Value + let w = round_votes.add_vote(VoteType::Prevote, ADDRESS4, val, weight); + assert_eq!(w, 3); } #[test] @@ -62,31 +55,24 @@ fn add_votes_multi_values() { let v2 = ValueId::new(2); let val1 = Some(v1); let val2 = Some(v2); - let total = 15; - let mut round_votes: RoundVotes<_, ValueId> = RoundVotes::new(total, Default::default()); + let mut round_votes: RoundVotes<_, ValueId> = RoundVotes::new(); - // add a vote for v1. nothing changes. - let thresh = round_votes.add_vote(VoteType::Precommit, ADDRESS1, val1, 1); - assert_eq!(thresh, Threshold::Unreached); + let w = round_votes.add_vote(VoteType::Precommit, ADDRESS1, val1, 1); + assert_eq!(w, 1); - // add a vote for v2. nothing changes. - let thresh = round_votes.add_vote(VoteType::Precommit, ADDRESS2, val2, 1); - assert_eq!(thresh, Threshold::Unreached); + let w = round_votes.add_vote(VoteType::Precommit, ADDRESS2, val2, 1); + assert_eq!(w, 1); - // add a vote for nil. nothing changes. - let thresh = round_votes.add_vote(VoteType::Precommit, ADDRESS3, None, 1); - assert_eq!(thresh, Threshold::Unreached); + let w = round_votes.add_vote(VoteType::Precommit, ADDRESS3, None, 1); + assert_eq!(w, 1); - // add a vote for v1. nothing changes - let thresh = round_votes.add_vote(VoteType::Precommit, ADDRESS4, val1, 1); - assert_eq!(thresh, Threshold::Unreached); + let w = round_votes.add_vote(VoteType::Precommit, ADDRESS4, val1, 1); + assert_eq!(w, 2); - // add a vote for v2. nothing changes - let thresh = round_votes.add_vote(VoteType::Precommit, ADDRESS5, val2, 1); - assert_eq!(thresh, Threshold::Unreached); + let w = round_votes.add_vote(VoteType::Precommit, ADDRESS5, val2, 1); + assert_eq!(w, 2); - // add a big vote for v2. get Value(v2) - let thresh = round_votes.add_vote(VoteType::Precommit, ADDRESS6, val2, 10); - assert_eq!(thresh, Threshold::Value(v2)); + let w = round_votes.add_vote(VoteType::Precommit, ADDRESS6, val2, 10); + assert_eq!(w, 12); } diff --git a/Code/test/tests/vote_count.rs b/Code/test/tests/vote_count.rs deleted file mode 100644 index 179222dda..000000000 --- a/Code/test/tests/vote_count.rs +++ /dev/null @@ -1,155 +0,0 @@ -#![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 8dedc2b3c..f1d6d7849 100644 --- a/Code/test/tests/vote_keeper.rs +++ b/Code/test/tests/vote_keeper.rs @@ -1,7 +1,7 @@ use malachite_common::Round; use malachite_vote::keeper::{Message, VoteKeeper}; -use malachite_test::{Address, TestContext, ValueId, Vote}; +use malachite_test::{Address, Height, TestContext, ValueId, Vote}; const ADDRESS1: Address = Address::new([41; 20]); const ADDRESS2: Address = Address::new([42; 20]); @@ -10,82 +10,205 @@ const ADDRESS4: Address = Address::new([44; 20]); #[test] fn prevote_apply_nil() { - let mut keeper: VoteKeeper = VoteKeeper::new(3); + let mut keeper: VoteKeeper = VoteKeeper::new(3, Default::default()); + let height = Height::new(1); + let round = Round::new(0); - let vote = Vote::new_prevote(Round::new(0), None, ADDRESS1); - let msg = keeper.apply_vote(vote.clone(), 1); + let vote = Vote::new_prevote(height, round, None, ADDRESS1); + let msg = keeper.apply_vote(vote.clone(), 1, round); assert_eq!(msg, None); - let vote = Vote::new_prevote(Round::new(0), None, ADDRESS2); - let msg = keeper.apply_vote(vote.clone(), 1); + let vote = Vote::new_prevote(height, round, None, ADDRESS2); + let msg = keeper.apply_vote(vote.clone(), 1, round); assert_eq!(msg, None); - let vote = Vote::new_prevote(Round::new(0), None, ADDRESS3); - let msg = keeper.apply_vote(vote, 1); + let vote = Vote::new_prevote(height, round, None, ADDRESS3); + let msg = keeper.apply_vote(vote, 1, round); assert_eq!(msg, Some(Message::PolkaNil)); } #[test] fn precommit_apply_nil() { - let mut keeper: VoteKeeper = VoteKeeper::new(3); + let mut keeper: VoteKeeper = VoteKeeper::new(3, Default::default()); + let height = Height::new(1); + let round = Round::new(0); - let vote = Vote::new_precommit(Round::new(0), None, ADDRESS1); - let msg = keeper.apply_vote(vote.clone(), 1); + let vote = Vote::new_precommit(height, round, None, ADDRESS1); + let msg = keeper.apply_vote(vote.clone(), 1, round); assert_eq!(msg, None); - let vote = Vote::new_precommit(Round::new(0), None, ADDRESS2); - let msg = keeper.apply_vote(vote.clone(), 1); + let vote = Vote::new_precommit(height, Round::new(0), None, ADDRESS2); + let msg = keeper.apply_vote(vote.clone(), 1, round); assert_eq!(msg, None); - let vote = Vote::new_precommit(Round::new(0), None, ADDRESS3); - let msg = keeper.apply_vote(vote, 1); + let vote = Vote::new_precommit(height, Round::new(0), None, ADDRESS3); + let msg = keeper.apply_vote(vote, 1, round); assert_eq!(msg, Some(Message::PrecommitAny)); } #[test] fn prevote_apply_single_value() { - let mut keeper: VoteKeeper = VoteKeeper::new(4); + let mut keeper: VoteKeeper = VoteKeeper::new(4, Default::default()); - let v = ValueId::new(1); - let val = Some(v); + let id = ValueId::new(1); + let val = Some(id); + let height = Height::new(1); + let round = Round::new(0); - let vote = Vote::new_prevote(Round::new(0), val, ADDRESS1); - let msg = keeper.apply_vote(vote.clone(), 1); + let vote = Vote::new_prevote(height, Round::new(0), val, ADDRESS1); + let msg = keeper.apply_vote(vote.clone(), 1, round); assert_eq!(msg, None); - let vote = Vote::new_prevote(Round::new(0), val, ADDRESS2); - let msg = keeper.apply_vote(vote.clone(), 1); + let vote = Vote::new_prevote(height, Round::new(0), val, ADDRESS2); + let msg = keeper.apply_vote(vote.clone(), 1, round); assert_eq!(msg, None); - let vote_nil = Vote::new_prevote(Round::new(0), None, ADDRESS3); - let msg = keeper.apply_vote(vote_nil, 1); + let vote_nil = Vote::new_prevote(height, Round::new(0), None, ADDRESS3); + let msg = keeper.apply_vote(vote_nil, 1, round); 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))); + let vote = Vote::new_prevote(height, Round::new(0), val, ADDRESS4); + let msg = keeper.apply_vote(vote, 1, round); + assert_eq!(msg, Some(Message::PolkaValue(id))); } #[test] fn precommit_apply_single_value() { - let mut keeper: VoteKeeper = VoteKeeper::new(4); + let mut keeper: VoteKeeper = VoteKeeper::new(4, Default::default()); - let v = ValueId::new(1); - let val = Some(v); + let id = ValueId::new(1); + let val = Some(id); + let height = Height::new(1); + let round = Round::new(0); - let vote = Vote::new_precommit(Round::new(0), val, ADDRESS1); - let msg = keeper.apply_vote(vote.clone(), 1); + let vote = Vote::new_precommit(height, Round::new(0), val, ADDRESS1); + let msg = keeper.apply_vote(vote.clone(), 1, round); assert_eq!(msg, None); - let vote = Vote::new_precommit(Round::new(0), val, ADDRESS2); - let msg = keeper.apply_vote(vote.clone(), 1); + let vote = Vote::new_precommit(height, Round::new(0), val, ADDRESS2); + let msg = keeper.apply_vote(vote.clone(), 1, round); assert_eq!(msg, None); - let vote_nil = Vote::new_precommit(Round::new(0), None, ADDRESS3); - let msg = keeper.apply_vote(vote_nil, 1); + let vote_nil = Vote::new_precommit(height, Round::new(0), None, ADDRESS3); + let msg = keeper.apply_vote(vote_nil, 1, round); 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))); + let vote = Vote::new_precommit(height, Round::new(0), val, ADDRESS4); + let msg = keeper.apply_vote(vote, 1, round); + assert_eq!(msg, Some(Message::PrecommitValue(id))); +} + +#[test] +fn skip_round_small_quorum_prevotes_two_vals() { + let mut keeper: VoteKeeper = VoteKeeper::new(4, Default::default()); + + let id = ValueId::new(1); + let val = Some(id); + let height = Height::new(1); + let cur_round = Round::new(0); + let fut_round = Round::new(1); + + let vote = Vote::new_prevote(height, cur_round, val, ADDRESS1); + let msg = keeper.apply_vote(vote.clone(), 1, cur_round); + assert_eq!(msg, None); + + let vote = Vote::new_prevote(height, fut_round, val, ADDRESS2); + let msg = keeper.apply_vote(vote.clone(), 1, cur_round); + assert_eq!(msg, None); + + let vote = Vote::new_prevote(height, fut_round, val, ADDRESS3); + let msg = keeper.apply_vote(vote, 1, cur_round); + assert_eq!(msg, Some(Message::SkipRound(Round::new(1)))); +} + +#[test] +fn skip_round_small_quorum_with_prevote_precommit_two_vals() { + let mut keeper: VoteKeeper = VoteKeeper::new(4, Default::default()); + + let id = ValueId::new(1); + let val = Some(id); + let height = Height::new(1); + let cur_round = Round::new(0); + let fut_round = Round::new(1); + + let vote = Vote::new_prevote(height, cur_round, val, ADDRESS1); + let msg = keeper.apply_vote(vote.clone(), 1, cur_round); + assert_eq!(msg, None); + + let vote = Vote::new_prevote(height, fut_round, val, ADDRESS2); + let msg = keeper.apply_vote(vote.clone(), 1, cur_round); + assert_eq!(msg, None); + + let vote = Vote::new_precommit(height, fut_round, val, ADDRESS3); + let msg = keeper.apply_vote(vote, 1, cur_round); + assert_eq!(msg, Some(Message::SkipRound(Round::new(1)))); +} + +#[test] +fn skip_round_full_quorum_with_prevote_precommit_two_vals() { + let mut keeper: VoteKeeper = VoteKeeper::new(5, Default::default()); + + let id = ValueId::new(1); + let val = Some(id); + let height = Height::new(1); + let cur_round = Round::new(0); + let fut_round = Round::new(1); + + let vote = Vote::new_prevote(height, cur_round, val, ADDRESS1); + let msg = keeper.apply_vote(vote.clone(), 1, cur_round); + assert_eq!(msg, None); + + let vote = Vote::new_prevote(height, fut_round, val, ADDRESS2); + let msg = keeper.apply_vote(vote.clone(), 1, cur_round); + assert_eq!(msg, None); + + let vote = Vote::new_precommit(height, fut_round, val, ADDRESS3); + let msg = keeper.apply_vote(vote, 2, cur_round); + assert_eq!(msg, Some(Message::SkipRound(Round::new(1)))); +} + +#[test] +fn no_skip_round_small_quorum_with_same_val() { + let mut keeper: VoteKeeper = VoteKeeper::new(4, Default::default()); + + let id = ValueId::new(1); + let val = Some(id); + let height = Height::new(1); + let cur_round = Round::new(0); + let fut_round = Round::new(1); + + let vote = Vote::new_prevote(height, cur_round, val, ADDRESS1); + let msg = keeper.apply_vote(vote.clone(), 1, cur_round); + assert_eq!(msg, None); + + let vote = Vote::new_prevote(height, fut_round, val, ADDRESS2); + let msg = keeper.apply_vote(vote.clone(), 1, cur_round); + assert_eq!(msg, None); + + let vote = Vote::new_precommit(height, fut_round, val, ADDRESS2); + let msg = keeper.apply_vote(vote, 1, cur_round); + assert_eq!(msg, None); +} + +#[test] +fn no_skip_round_full_quorum_with_same_val() { + let mut keeper: VoteKeeper = VoteKeeper::new(5, Default::default()); + + let id = ValueId::new(1); + let val = Some(id); + let height = Height::new(1); + let cur_round = Round::new(0); + let fut_round = Round::new(1); + + let vote = Vote::new_prevote(height, cur_round, val, ADDRESS1); + let msg = keeper.apply_vote(vote.clone(), 1, cur_round); + assert_eq!(msg, None); + + let vote = Vote::new_prevote(height, fut_round, val, ADDRESS2); + let msg = keeper.apply_vote(vote.clone(), 1, cur_round); + assert_eq!(msg, None); + + let vote = Vote::new_precommit(height, fut_round, val, ADDRESS2); + let msg = keeper.apply_vote(vote, 2, cur_round); + assert_eq!(msg, None); } diff --git a/Code/vote/src/count.rs b/Code/vote/src/count.rs index 5c7b66dcc..dfb970773 100644 --- a/Code/vote/src/count.rs +++ b/Code/vote/src/count.rs @@ -1,18 +1,12 @@ use alloc::collections::BTreeSet; use crate::value_weights::ValuesWeights; -use crate::{Threshold, ThresholdParams, Weight}; +use crate::{Threshold, ThresholdParam, 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 { - /// 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>, @@ -21,10 +15,8 @@ pub struct VoteCount { } impl VoteCount { - pub fn new(total_weight: Weight, threshold_params: ThresholdParams) -> Self { + pub fn new() -> Self { VoteCount { - total_weight, - threshold_params, values_weights: ValuesWeights::new(), validator_addresses: BTreeSet::new(), } @@ -32,12 +24,7 @@ impl VoteCount { /// 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 + pub fn add(&mut self, address: Address, value: Option, weight: Weight) -> Weight where Address: Clone + Ord, Value: Clone + Ord, @@ -45,77 +32,52 @@ impl VoteCount { let already_voted = !self.validator_addresses.insert(address); if !already_voted { - self.values_weights.add(value.clone(), weight); + self.values_weights.add(value, weight) + } else { + self.values_weights.get(&value) } - - 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 + pub fn get(&self, value: &Option) -> Weight where - Address: Ord, Value: Ord, { - let weight = self.values_weights.get(&value); - - match value { - Some(value) if self.is_quorum(weight, self.total_weight) => Threshold::Value(value), - - None if self.is_quorum(weight, self.total_weight) => Threshold::Nil, - - _ => { - let sum_weight = self.values_weights.sum(); + self.values_weights.get(value) + } - if self.is_quorum(sum_weight, self.total_weight) { - Threshold::Any - } else { - Threshold::Unreached - } - } - } + pub fn sum(&self) -> Weight { + self.values_weights.sum() } /// 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 + pub fn is_threshold_met( + &self, + threshold: Threshold, + param: ThresholdParam, + total_weight: Weight, + ) -> bool where Value: Ord, { match threshold { Threshold::Value(value) => { let weight = self.values_weights.get(&Some(value)); - self.is_quorum(weight, self.total_weight) + param.is_met(weight, total_weight) } Threshold::Nil => { let weight = self.values_weights.get(&None); - self.is_quorum(weight, self.total_weight) + param.is_met(weight, total_weight) } Threshold::Any => { let sum_weight = self.values_weights.sum(); - self.is_quorum(sum_weight, self.total_weight) + param.is_met(sum_weight, total_weight) } Threshold::Skip | Threshold::Unreached => false, } } - - pub fn get(&self, value: &Option) -> Weight - where - Value: Ord, - { - self.values_weights.get(value) - } - - pub fn total_weight(&self) -> Weight { - self.total_weight - } - - fn is_quorum(&self, sum: Weight, total: Weight) -> bool { - self.threshold_params.quorum.is_met(sum, total) - } } #[cfg(test)] @@ -125,7 +87,10 @@ mod tests { #[test] fn vote_count_nil() { - let mut vc = VoteCount::new(4, Default::default()); + let t = 4; + let q = ThresholdParam::TWO_F_PLUS_ONE; + + let mut vc = VoteCount::new(); let addr1 = [1]; let addr2 = [2]; @@ -134,62 +99,65 @@ mod tests { 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::Unreached, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Any, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Nil, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1), q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2), q, t), false); - assert_eq!(vc.add(addr1, None, 1), Threshold::Unreached); + assert_eq!(vc.add(addr1, None, 1), 1); 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.is_threshold_met(Threshold::Unreached, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Any, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Nil, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1), q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2), q, t), false); - assert_eq!(vc.add(addr2, None, 1), Threshold::Unreached); + assert_eq!(vc.add(addr2, None, 1), 2); 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.is_threshold_met(Threshold::Unreached, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Any, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Nil, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1), q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2), q, t), false); // addr1 votes again, is ignored - assert_eq!(vc.add(addr1, None, 1), Threshold::Unreached); + assert_eq!(vc.add(addr1, None, 1), 2); 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.is_threshold_met(Threshold::Unreached, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Any, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Nil, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1), q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2), q, t), false); - assert_eq!(vc.add(addr3, None, 1), Threshold::Nil); + assert_eq!(vc.add(addr3, None, 1), 3); 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.is_threshold_met(Threshold::Unreached, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Any, q, t), true); + assert_eq!(vc.is_threshold_met(Threshold::Nil, q, t), true); + assert_eq!(vc.is_threshold_met(Threshold::Value(1), q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2), q, t), false); - assert_eq!(vc.add(addr4, Some(1), 1), Threshold::Any); + assert_eq!(vc.add(addr4, Some(1), 1), 1); 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); + assert_eq!(vc.is_threshold_met(Threshold::Unreached, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Any, q, t), true); + assert_eq!(vc.is_threshold_met(Threshold::Nil, q, t), true); + assert_eq!(vc.is_threshold_met(Threshold::Value(1), q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2), q, t), false); } #[test] fn vote_count_value() { - let mut vc = VoteCount::new(4, Default::default()); + let t = 4; + let q = ThresholdParam::TWO_F_PLUS_ONE; + + let mut vc = VoteCount::new(); let addr1 = [1]; let addr2 = [2]; @@ -198,79 +166,79 @@ mod tests { 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::Unreached, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Any, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Nil, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1), q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2), q, t), false); - assert_eq!(vc.add(addr1, Some(1), 1), Threshold::Unreached); + assert_eq!(vc.add(addr1, Some(1), 1), 1); 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.is_threshold_met(Threshold::Unreached, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Any, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Nil, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1), q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2), q, t), false); - assert_eq!(vc.add(addr2, Some(1), 1), Threshold::Unreached); + assert_eq!(vc.add(addr2, Some(1), 1), 2); 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.is_threshold_met(Threshold::Unreached, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Any, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Nil, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1), q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2), q, t), false); // addr1 votes again, for nil this time, is ignored - assert_eq!(vc.add(addr1, None, 1), Threshold::Unreached); + assert_eq!(vc.add(addr1, None, 1), 0); 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.is_threshold_met(Threshold::Unreached, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Any, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Nil, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1), q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(2), q, t), false); - assert_eq!(vc.add(addr3, Some(1), 1), Threshold::Value(1)); + assert_eq!(vc.add(addr3, Some(1), 1), 3); 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.is_threshold_met(Threshold::Unreached, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Any, q, t), true); + assert_eq!(vc.is_threshold_met(Threshold::Nil, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1), q, t), true); + assert_eq!(vc.is_threshold_met(Threshold::Value(2), q, t), false); // addr2 votes again, for the same value, is ignored - assert_eq!(vc.add(addr2, Some(1), 1), Threshold::Value(1)); + assert_eq!(vc.add(addr2, Some(1), 1), 3); 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.is_threshold_met(Threshold::Unreached, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Any, q, t), true); + assert_eq!(vc.is_threshold_met(Threshold::Nil, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1), q, t), true); + assert_eq!(vc.is_threshold_met(Threshold::Value(2), q, t), false); - assert_eq!(vc.add(addr4, Some(2), 1), Threshold::Any); + assert_eq!(vc.add(addr4, Some(2), 1), 1); 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.is_threshold_met(Threshold::Unreached, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Any, q, t), true); + assert_eq!(vc.is_threshold_met(Threshold::Nil, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1), q, t), true); + assert_eq!(vc.is_threshold_met(Threshold::Value(2), q, t), false); // addr4 votes again, for a different value, is ignored - assert_eq!(vc.add(addr4, Some(3), 1), Threshold::Any); + assert_eq!(vc.add(addr4, Some(3), 1), 0); 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); + assert_eq!(vc.is_threshold_met(Threshold::Unreached, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Any, q, t), true); + assert_eq!(vc.is_threshold_met(Threshold::Nil, q, t), false); + assert_eq!(vc.is_threshold_met(Threshold::Value(1), q, t), true); + assert_eq!(vc.is_threshold_met(Threshold::Value(2), q, t), false); } } diff --git a/Code/vote/src/keeper.rs b/Code/vote/src/keeper.rs index 5694ec2d7..b1668f288 100644 --- a/Code/vote/src/keeper.rs +++ b/Code/vote/src/keeper.rs @@ -31,9 +31,9 @@ impl PerRound where Ctx: Context, { - fn new(total_weight: Weight, threshold_params: ThresholdParams) -> Self { + fn new() -> Self { Self { - votes: RoundVotes::new(total_weight, threshold_params), + votes: RoundVotes::new(), addresses_weights: RoundWeights::new(), emitted_msgs: BTreeSet::new(), } @@ -46,8 +46,8 @@ pub struct VoteKeeper where Ctx: Context, { - threshold_params: ThresholdParams, total_weight: Weight, + threshold_params: ThresholdParams, per_round: BTreeMap>, } @@ -55,27 +55,30 @@ impl VoteKeeper where Ctx: Context, { - pub fn new(total_weight: Weight) -> Self { + pub fn new(total_weight: Weight, threshold_params: ThresholdParams) -> Self { VoteKeeper { - // TODO: Make these configurable - threshold_params: ThresholdParams::default(), - total_weight, + threshold_params, 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>> { + pub fn apply_vote( + &mut self, + vote: Ctx::Vote, + weight: Weight, + current_round: Round, + ) -> Option>> { let round = self .per_round .entry(vote.round()) - .or_insert_with(|| PerRound::new(self.total_weight, self.threshold_params)); + .or_insert_with(PerRound::new); - let threshold = round.votes.add_vote( + round.votes.add_vote( vote.vote_type(), vote.validator_address().clone(), - vote.value().cloned(), + vote.value().clone(), weight, ); @@ -83,21 +86,38 @@ where .addresses_weights .set_once(vote.validator_address().clone(), weight); - let msg = threshold_to_message(vote.vote_type(), vote.round(), threshold)?; + if vote.round() > current_round { + let combined_weight = round.addresses_weights.sum(); - 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 - }; + let skip_round = self + .threshold_params + .honest + .is_met(combined_weight, self.total_weight); - if let Some(final_msg) = &final_msg { - round.emitted_msgs.insert(final_msg.clone()); + if skip_round { + let msg = Message::SkipRound(vote.round()); + round.emitted_msgs.insert(msg.clone()); + return Some(msg); + } } - final_msg + let threshold = compute_threshold( + vote.vote_type(), + round, + vote.value(), + self.threshold_params.quorum, + self.total_weight, + ); + + let msg = threshold_to_message(vote.vote_type(), vote.round(), threshold); + + match msg { + Some(msg) if !round.emitted_msgs.contains(&msg) => { + round.emitted_msgs.insert(msg.clone()); + Some(msg) + } + _ => None, + } } /// Check if a threshold is met, ie. if we have a quorum for that threshold. @@ -108,19 +128,44 @@ where threshold: Threshold>, ) -> bool { self.per_round.get(round).map_or(false, |round| { - round.votes.is_threshold_met(vote_type, threshold) + round.votes.is_threshold_met( + vote_type, + threshold, + self.threshold_params.quorum, + self.total_weight, + ) }) } +} - /// 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) +/// Compute whether or not we have reached a threshold for the given value, +/// and return that threshold. +fn compute_threshold( + vote_type: VoteType, + round: &PerRound, + value: &Option>, + quorum: ThresholdParam, + total_weight: Weight, +) -> Threshold> +where + Ctx: Context, +{ + let weight = round.votes.get_weight(vote_type, value); + + match value { + Some(value) if quorum.is_met(weight, total_weight) => Threshold::Value(value.clone()), + + None if quorum.is_met(weight, total_weight) => Threshold::Nil, + + _ => { + let weight_sum = round.votes.weight_sum(vote_type); + + if quorum.is_met(weight_sum, total_weight) { + Threshold::Any + } else { + Threshold::Unreached + } + } } } diff --git a/Code/vote/src/round_votes.rs b/Code/vote/src/round_votes.rs index 21a14f80b..58307b597 100644 --- a/Code/vote/src/round_votes.rs +++ b/Code/vote/src/round_votes.rs @@ -1,7 +1,7 @@ use malachite_common::VoteType; use crate::count::VoteCount; -use crate::{Threshold, ThresholdParams, Weight}; +use crate::{Threshold, ThresholdParam, Weight}; /// Tracks all the votes for a single round #[derive(Clone, Debug)] @@ -11,10 +11,10 @@ pub struct RoundVotes { } impl RoundVotes { - pub fn new(total_weight: Weight, threshold_params: ThresholdParams) -> Self { + pub fn new() -> Self { RoundVotes { - prevotes: VoteCount::new(total_weight, threshold_params), - precommits: VoteCount::new(total_weight, threshold_params), + prevotes: VoteCount::new(), + precommits: VoteCount::new(), } } @@ -24,7 +24,7 @@ impl RoundVotes { address: Address, value: Option, weight: Weight, - ) -> Threshold + ) -> Weight where Address: Clone + Ord, Value: Clone + Ord, @@ -35,13 +35,55 @@ impl RoundVotes { } } - pub fn is_threshold_met(&self, vote_type: VoteType, threshold: Threshold) -> bool + pub fn get_weight(&self, vote_type: VoteType, value: &Option) -> Weight where Value: Ord, { match vote_type { - VoteType::Prevote => self.prevotes.is_threshold_met(threshold), - VoteType::Precommit => self.precommits.is_threshold_met(threshold), + VoteType::Prevote => self.prevotes.get(value), + VoteType::Precommit => self.precommits.get(value), } } + + pub fn weight_sum(&self, vote_type: VoteType) -> Weight { + match vote_type { + VoteType::Prevote => self.prevotes.sum(), + VoteType::Precommit => self.precommits.sum(), + } + } + + pub fn combined_weight(&self, value: &Option) -> Weight + where + Value: Ord, + { + self.prevotes.get(value) + self.precommits.get(value) + } + + /// Return whether or not the threshold is met, ie. if we have a quorum for that threshold. + pub fn is_threshold_met( + &self, + vote_type: VoteType, + threshold: Threshold, + param: ThresholdParam, + total_weight: Weight, + ) -> bool + where + Value: Ord, + { + match vote_type { + VoteType::Prevote => self + .prevotes + .is_threshold_met(threshold, param, total_weight), + + VoteType::Precommit => self + .precommits + .is_threshold_met(threshold, param, total_weight), + } + } +} + +impl Default for RoundVotes { + fn default() -> Self { + Self::new() + } } diff --git a/Specs/Quint/executor.qnt b/Specs/Quint/executor.qnt index 9c01323ab..0984c4014 100644 --- a/Specs/Quint/executor.qnt +++ b/Specs/Quint/executor.qnt @@ -10,8 +10,8 @@ module executor { import consensus.* from "./consensus" import voteBookkeeper.* from "./voteBookkeeper" -pure def initBookKeeper (totalVotingPower: int): Bookkeeper = - { height: 0, totalWeight: totalVotingPower, rounds: Map() } +pure def initBookKeeper (currentRound: Round, totalVotingPower: int): Bookkeeper = + { height: 0, currentRound: currentRound, totalWeight: totalVotingPower, rounds: Map() } type ExecutorState = { @@ -30,7 +30,7 @@ type ExecutorState = { pure def initExecutor (v: Address_t, vs: Address_t -> int) : ExecutorState = { val tvp = vs.keys().fold(0, (sum, key) => sum + vs.get(key)) { - bk: initBookKeeper(tvp), + bk: initBookKeeper(0, tvp), cs: initConsensusState(v), proposals: Set(), valset: vs, @@ -149,14 +149,18 @@ pure def ListContains(list, value) = // check whether the event has been already sent to consensus. If not, do so. pure def callConsensus (es: ExecutorState, bk: Bookkeeper, ev: Event) : (ExecutorState, ConsensusOutput) = { - // check whether we already executed the event already + // Check whether we already executed the event already if (es.executedEvents.ListContains((ev, es.cs.height, es.cs.round))) - ( { ...es, bk:bk, cs: es.cs}, - defaultResult ) + ({ ...es, bk: bk, cs: es.cs }, defaultResult) else + // Go to consensus val res = consensus(es.cs, ev) - ( { ...es, bk: bk, cs: res.cs, executedEvents: es.executedEvents.append((ev, res.cs.height, res.cs.round))}, - res.out ) + // Update the round in the vote keeper, in case we moved to a new round + val newBk = { ...bk, currentRound: res.cs.round } + // Record that we executed the event + val events = es.executedEvents.append((ev, res.cs.height, res.cs.round)) + + ({ ...es, bk: newBk, cs: res.cs, executedEvents: events }, res.out) } @@ -479,7 +483,7 @@ pure def executor (es: ExecutorState, input: ExecutorInput) : (ExecutorState, Co res } else if (input.name == "votemessage" and input.vote.step == "Precommit") { - val res = applyVote(es.bk, toVote(input.vote), es.valset.get(input.vote.src), es.cs.round) + val res = applyVote(es.bk, toVote(input.vote), es.valset.get(input.vote.src)) val newES = { ...es, bk: res.bookkeeper, applyvotesResult: res.event} // only a commit event can come here. val cons_res = Precommit(newES, input, res.event) @@ -491,7 +495,7 @@ pure def executor (es: ExecutorState, input: ExecutorInput) : (ExecutorState, Co cons_res } else if (input.name == "votemessage" and input.vote.step == "Prevote") { - val res = applyVote(es.bk, toVote(input.vote), es.valset.get(input.vote.src), es.cs.round) + val res = applyVote(es.bk, toVote(input.vote), es.valset.get(input.vote.src)) val newES = { ...es, bk: res.bookkeeper, applyvotesResult: res.event} // only a commit event can come here. val cons_res = Prevote(newES, input, res.event) diff --git a/Specs/Quint/voteBookkeeper.qnt b/Specs/Quint/voteBookkeeper.qnt index 539af2cb6..25b085a3f 100644 --- a/Specs/Quint/voteBookkeeper.qnt +++ b/Specs/Quint/voteBookkeeper.qnt @@ -62,6 +62,7 @@ module voteBookkeeper { type Bookkeeper = { height: Height, + currentRound: Round, totalWeight: Weight, rounds: Round -> RoundVotes } @@ -157,7 +158,7 @@ module voteBookkeeper { // TO DISCUSS: // - There might be a problem if we generalize from single-shot to multi-shot: the keeper only keeps the totalWeight // of the current height; I wonder if we need to keep the totalWeight for every Height that we may receive a vote for. - pure def applyVote(keeper: Bookkeeper, vote: Vote, weight: Weight, currentRound: Round): { bookkeeper: Bookkeeper, event: ExecutorEvent } = + pure def applyVote(keeper: Bookkeeper, vote: Vote, weight: Weight): { bookkeeper: Bookkeeper, event: ExecutorEvent } = val height = keeper.height val total = keeper.totalWeight @@ -183,29 +184,37 @@ module voteBookkeeper { else roundVotes.votesAddressesWeights.mapSafeSet(vote.address, weight) - val threshold = computeThreshold(updatedVoteCount, vote.value) - val event = toEvent(vote.round, vote.typ, threshold) - val finalEvent = - if (event.name != "None" and not(event.in(roundVotes.emittedEvents))) - event - else if (vote.round > currentRound and roundVotes.emittedEvents == Set() and isSkip(updatedVotesAddressesWeights.mapSumValues(), total)) - { round: vote.round, name: "Skip", value: "null" } - else - { round: vote.round, name: "None", value: "null" } - - val updatedEmmittedEvents = roundVotes.emittedEvents.setAddIf(finalEvent, finalEvent.name != "None") - val updatedRoundVotes = if (vote.typ == "Prevote") roundVotes.with("prevotes", updatedVoteCount) else roundVotes.with("precommits", updatedVoteCount) + + // Combined weight of all validators at this height + val combinedWeight = updatedVotesAddressesWeights.mapSumValues() + + val finalEvent = + if (vote.round > keeper.currentRound and isSkip(combinedWeight, total)) + { round: vote.round, name: "Skip", value: "null" } + else + val threshold = computeThreshold(updatedVoteCount, vote.value) + val event = toEvent(vote.round, vote.typ, threshold) + if (not(event.in(roundVotes.emittedEvents))) + event + else + { round: vote.round, name: "None", value: "null" } + + val updatedEmittedEvents = roundVotes.emittedEvents.setAddIf(finalEvent, finalEvent.name != "None") + val updatedRoundVotes2 = updatedRoundVotes .with("votesAddressesWeights", updatedVotesAddressesWeights) - .with("emittedEvents", updatedEmmittedEvents) + .with("emittedEvents", updatedEmittedEvents) + val newBookkeeper = + keeper.with("rounds", keeper.rounds.mapSafeSet(vote.round, updatedRoundVotes2)) + { - bookkeeper: keeper.with("rounds", keeper.rounds.mapSafeSet(vote.round, updatedRoundVotes2)), + bookkeeper: newBookkeeper, event: finalEvent } diff --git a/Specs/Quint/voteBookkeeperTest.qnt b/Specs/Quint/voteBookkeeperTest.qnt index a4ccbf2a7..ae635f823 100644 --- a/Specs/Quint/voteBookkeeperTest.qnt +++ b/Specs/Quint/voteBookkeeperTest.qnt @@ -20,13 +20,13 @@ module voteBookkeeperTest { lastEmitted' = lastEmitted, } - action init(totalWeight: Weight): bool = all { - bookkeeper' = { height: 10, totalWeight: totalWeight, rounds: Map() }, + action initWith(round: Round, totalWeight: Weight): bool = all { + bookkeeper' = { height: 10, currentRound: round, totalWeight: totalWeight, rounds: Map() }, lastEmitted' = { round: -1, name: "", value: "null" }, } - action applyVoteAction(vote: Vote, weight: Weight, currentRound: Round): bool = - val result = applyVote(bookkeeper, vote, weight, currentRound) + action applyVoteAction(vote: Vote, weight: Weight): bool = + val result = applyVote(bookkeeper, vote, weight) all { bookkeeper' = result.bookkeeper, lastEmitted' = result.event, @@ -44,34 +44,100 @@ module voteBookkeeperTest { // all messages are received in order. We assume three validators in the validator set wtih 60%, 30% and 10% // each of the total voting power run synchronousConsensusTest = - init(100) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 60, 1)) + initWith(1, 100) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 60)) .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "john"}, 10, 1)) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "john"}, 10)) .then(_assert(lastEmitted == {round: 1, name: "PolkaValue", value: "proposal"})) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "bob"}, 30, 1)) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "bob"}, 30)) .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Precommit", round: 1, value: "proposal", address: "bob"}, 30, 1)) + .then(applyVoteAction({typ: "Precommit", round: 1, value: "proposal", address: "bob"}, 30)) .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Precommit", round: 1, value: "proposal", address: "john"}, 10, 1)) + .then(applyVoteAction({typ: "Precommit", round: 1, value: "proposal", address: "john"}, 10)) .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Precommit", round: 1, value: "proposal", address: "alice"}, 60, 1)) + .then(applyVoteAction({typ: "Precommit", round: 1, value: "proposal", address: "alice"}, 60)) .then(_assert(lastEmitted == {round: 1, name: "PrecommitValue", value: "proposal"})) // Reaching PolkaAny run polkaAnyTest = - init(100) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "val1", address: "alice"}, 60, 1)) + initWith(1, 100) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "val1", address: "alice"}, 60)) .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "nil", address: "john"}, 10, 1)) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "nil", address: "john"}, 10)) .then(_assert(lastEmitted == {round: 1, name: "PolkaAny", value: "null"})) // Reaching PolkaNil run polkaNilTest = - init(100) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "nil", address: "alice"}, 60, 1)) + initWith(1, 100) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "nil", address: "alice"}, 60)) .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "nil", address: "john"}, 10, 1)) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "nil", address: "john"}, 10)) .then(_assert(lastEmitted == {round: 1, name: "PolkaNil", value: "null"})) + // Reaching Skip via n+1 threshold with prevotes from two validators at a future round + run skipSmallQuorumAllPrevotesTest = + initWith(1, 100) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 60)) + .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) + .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 10)) + .then(_assert(lastEmitted == {round: 2, name: "None", value: "null"})) + .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "bob"}, 30)) + .then(_assert(lastEmitted == {round: 2, name: "Skip", value: "null"})) + + // Cannot reach Skip via f+1 threshold with one prevote and one precommit from the same validator at a future round + run noSkipSmallQuorumMixedVotesSameValTest = + initWith(1, 90) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 10)) + .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) + .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 20)) + .then(_assert(lastEmitted == {round: 2, name: "None", value: "null"})) + .then(applyVoteAction({typ: "Precommit", round: 2, value: "proposal", address: "john"}, 20)) + .then(_assert(lastEmitted != {round: 2, name: "Skip", value: "null"})) + + // Reaching Skip via f+1 threshold with one prevote and one precommit from two validators at a future round + run skipSmallQuorumMixedVotesTwoValsTest = + initWith(1, 80) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 50)) + .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) + .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 10)) + .then(_assert(lastEmitted == {round: 2, name: "None", value: "null"})) + .then(applyVoteAction({typ: "Precommit", round: 2, value: "proposal", address: "bob"}, 20)) + .then(_assert(lastEmitted == {round: 2, name: "Skip", value: "null"})) + + // Reaching Skip via 2f+1 threshold with a single prevote from a single validator at a future round + run skipQuorumSinglePrevoteTest = + initWith(1, 100) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 10)) + .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) + .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 60)) + .then(_assert(lastEmitted == {round: 2, name: "Skip", value: "null"})) + + // Reaching Skip via 2f+1 threshold with a single precommit from a single validator at a future round + run skipQuorumSinglePrecommitTest = + initWith(1, 100) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 10)) + .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) + .then(applyVoteAction({typ: "Precommit", round: 2, value: "proposal", address: "john"}, 60)) + .then(_assert(lastEmitted == {round: 2, name: "Skip", value: "null"})) + + // Cannot reach Skip via 2f+1 threshold with one prevote and one precommit from the same validator at a future round + run noSkipQuorumMixedVotesSameValTest = + initWith(1, 100) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 10)) + .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) + .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 30)) + .then(_assert(lastEmitted == {round: 2, name: "None", value: "null"})) + .then(applyVoteAction({typ: "Precommit", round: 2, value: "proposal", address: "john"}, 30)) + .then(_assert(lastEmitted != {round: 2, name: "Skip", value: "null"})) + + // Reaching Skip via 2f+1 threshold with one prevote and one precommit from two validators at a future round + run skipQuorumMixedVotesTwoValsTest = + initWith(1, 80) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 20)) + .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) + .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 10)) + .then(_assert(lastEmitted == {round: 2, name: "None", value: "null"})) + .then(applyVoteAction({typ: "Precommit", round: 2, value: "proposal", address: "bob"}, 50)) + .then(_assert(lastEmitted == {round: 2, name: "Skip", value: "null"})) + }