diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 5adf373a0..7c24b03f5 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -39,7 +39,7 @@ jobs: - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - name: Generate code coverage - run: cargo llvm-cov nextest --workspace --exclude malachite-itf --all-features --lcov --output-path lcov.info + run: cargo llvm-cov nextest --workspace --exclude malachite-itf --all-features --ignore-run-fail --lcov --output-path lcov.info - name: Generate text report run: cargo llvm-cov report - name: Upload coverage to Codecov diff --git a/Code/.cargo/config.toml b/Code/.cargo/config.toml index e914d4462..77d5cd78e 100644 --- a/Code/.cargo/config.toml +++ b/Code/.cargo/config.toml @@ -1,3 +1,3 @@ [alias] -mbt = "nextest run -p malachite-itf --all-features" -integration = "nextest run --workspace --exclude malachite-itf" +mbt = "nextest run -p malachite-itf --all-features --no-fail-fast" +integration = "nextest run --workspace --exclude malachite-itf --no-fail-fast" diff --git a/Code/driver/src/driver.rs b/Code/driver/src/driver.rs index 137bd2bb7..c6c960fff 100644 --- a/Code/driver/src/driver.rs +++ b/Code/driver/src/driver.rs @@ -1,22 +1,20 @@ use alloc::boxed::Box; +use alloc::vec; +use alloc::vec::Vec; use core::fmt; use malachite_common::{ - Context, Proposal, Round, SignedVote, Timeout, TimeoutStep, Validator, ValidatorSet, Value, - Vote, VoteType, + Context, Proposal, Round, SignedVote, Timeout, TimeoutStep, Validator, ValidatorSet, Vote, }; use malachite_round::input::Input as RoundInput; use malachite_round::output::Output as RoundOutput; -use malachite_round::state::{State as RoundState, Step}; +use malachite_round::state::State as RoundState; use malachite_round::state_machine::Info; -use malachite_vote::keeper::Output as VoteKeeperOutput; use malachite_vote::keeper::VoteKeeper; -use malachite_vote::Threshold; use malachite_vote::ThresholdParams; use crate::input::Input; use crate::output::Output; -use crate::proposals::Proposals; use crate::Error; use crate::ProposerSelector; use crate::Validity; @@ -32,9 +30,10 @@ where pub address: Ctx::Address, pub validator_set: Ctx::ValidatorSet, - pub votes: VoteKeeper, + pub vote_keeper: VoteKeeper, pub round_state: RoundState, - pub proposals: Proposals, + pub proposal: Option, + pub pending_input: Option<(Round, RoundInput)>, } impl Driver @@ -57,9 +56,10 @@ where proposer_selector: Box::new(proposer_selector), address, validator_set, - votes, + vote_keeper: votes, round_state: RoundState::default(), - proposals: Proposals::new(), + proposal: None, + pending_input: None, } } @@ -84,13 +84,33 @@ where Ok(proposer) } - pub async fn execute(&mut self, msg: Input) -> Result>, Error> { + pub async fn process(&mut self, msg: Input) -> Result>, Error> { let round_output = match self.apply(msg).await? { Some(msg) => msg, - None => return Ok(None), + None => return Ok(Vec::new()), }; - let output = match round_output { + let output = self.lift_output(round_output); + let mut outputs = vec![output]; + + self.process_pending(&mut outputs)?; + + Ok(outputs) + } + + fn process_pending(&mut self, outputs: &mut Vec>) -> Result<(), Error> { + while let Some((round, input)) = self.pending_input.take() { + if let Some(round_output) = self.apply_input(round, input)? { + let output = self.lift_output(round_output); + outputs.push(output); + }; + } + + Ok(()) + } + + fn lift_output(&mut self, round_output: RoundOutput) -> Output { + match round_output { RoundOutput::NewRound(round) => Output::NewRound(self.height().clone(), round), RoundOutput::Proposal(proposal) => { @@ -113,9 +133,7 @@ where // TODO: update the state Output::Decide(value.round, value.value) } - }; - - Ok(Some(output)) + } } async fn apply(&mut self, input: Input) -> Result>, Error> { @@ -139,6 +157,7 @@ where } else { self.round_state = RoundState::new(height, round); } + self.apply_input(round, RoundInput::NewRound) } @@ -155,94 +174,12 @@ where proposal: Ctx::Proposal, validity: Validity, ) -> Result>, Error> { - // Check that there is an ongoing round - if self.round_state.round == Round::Nil { - return Ok(None); - } - - // Check that the proposal is for the current height - if self.round_state.height != proposal.height() { - return Ok(None); - } - - self.proposals.insert(proposal.clone()); + let round = proposal.round(); - let polka_for_pol = self.votes.is_threshold_met( - &proposal.pol_round(), - VoteType::Prevote, - Threshold::Value(proposal.value().id()), - ); - - let polka_previous = proposal.pol_round().is_defined() - && polka_for_pol - && proposal.pol_round() < self.round_state.round; - - // Handle invalid proposal - if !validity.is_valid() { - if self.round_state.step == Step::Propose { - if proposal.pol_round().is_nil() { - // L26 - return self.apply_input(proposal.round(), RoundInput::InvalidProposal); - } else if polka_previous { - // L32 - return self.apply_input( - proposal.round(), - RoundInput::InvalidProposalAndPolkaPrevious(proposal), - ); - } else { - return Ok(None); - } - } else { - return Ok(None); - } + match self.multiplex_proposal(proposal, validity) { + Some(round_input) => self.apply_input(round, round_input), + None => Ok(None), } - - // We have a valid proposal. - // L49 - // TODO - check if not already decided - if self.votes.is_threshold_met( - &proposal.round(), - VoteType::Precommit, - Threshold::Value(proposal.value().id()), - ) { - return self.apply_input( - proposal.round(), - RoundInput::ProposalAndPrecommitValue(proposal), - ); - } - - // If the proposal is for a different round, drop the proposal - if self.round() != proposal.round() { - return Ok(None); - } - - let polka_for_current = self.votes.is_threshold_met( - &proposal.round(), - VoteType::Prevote, - Threshold::Value(proposal.value().id()), - ); - - let polka_current = polka_for_current && self.round_state.step >= Step::Prevote; - - // L36 - if polka_current { - return self.apply_input( - proposal.round(), - RoundInput::ProposalAndPolkaCurrent(proposal), - ); - } - - // L28 - if self.round_state.step == Step::Propose && polka_previous { - // TODO: Check proposal vr is equal to threshold vr - return self.apply_input( - proposal.round(), - RoundInput::ProposalAndPolkaPrevious(proposal), - ); - } - - // TODO - Caller needs to store the proposal (valid or not) as the quorum (polka or commits) may be met later - self.apply_input(proposal.round(), RoundInput::Proposal(proposal)) } fn apply_vote( @@ -267,23 +204,20 @@ where let vote_round = signed_vote.vote.round(); let current_round = self.round(); - let Some(vote_output) = - self.votes - .apply_vote(signed_vote.vote, validator.voting_power(), current_round) - else { + let vote_output = + self.vote_keeper + .apply_vote(signed_vote.vote, validator.voting_power(), current_round); + + let Some(vote_output) = vote_output else { return Ok(None); }; - let round_input = match vote_output { - VoteKeeperOutput::PolkaAny => RoundInput::PolkaAny, - VoteKeeperOutput::PolkaNil => RoundInput::PolkaNil, - VoteKeeperOutput::PolkaValue(v) => RoundInput::PolkaValue(v), - VoteKeeperOutput::PrecommitAny => RoundInput::PrecommitAny, - VoteKeeperOutput::PrecommitValue(v) => RoundInput::PrecommitValue(v), - VoteKeeperOutput::SkipRound(r) => RoundInput::SkipRound(r), - }; + let round_input = self.multiplex_vote_threshold(vote_output); - self.apply_input(vote_round, round_input) + match round_input { + Some(input) => self.apply_input(vote_round, input), + None => Ok(None), + } } fn apply_timeout(&mut self, timeout: Timeout) -> Result>, Error> { @@ -303,39 +237,21 @@ where input: RoundInput, ) -> Result>, Error> { let round_state = core::mem::take(&mut self.round_state); - let proposer = self.get_proposer(round_state.round)?; - - let data = Info::new(input_round, &self.address, proposer.address()); + let current_step = round_state.step; - // Multiplex the event with the round state. - let mux_input = match input { - RoundInput::PolkaValue(value_id) => { - let proposal = self.proposals.find(&value_id, |p| p.round() == input_round); - - if let Some(proposal) = proposal { - assert_eq!(proposal.value().id(), value_id); - RoundInput::ProposalAndPolkaCurrent(proposal.clone()) - } else { - RoundInput::PolkaAny - } - } + let proposer = self.get_proposer(round_state.round)?; + let info = Info::new(input_round, &self.address, proposer.address()); - RoundInput::PrecommitValue(value_id) => { - let proposal = self.proposals.find(&value_id, |p| p.round() == input_round); + // Apply the input to the round state machine + let transition = round_state.apply(&info, input); - if let Some(proposal) = proposal { - assert_eq!(proposal.value().id(), value_id); - RoundInput::ProposalAndPrecommitValue(proposal.clone()) - } else { - RoundInput::PrecommitAny - } - } + let pending_step = transition.next_state.step; - _ => input, - }; + if current_step != pending_step { + let pending_input = self.multiplex_step_change(pending_step, input_round); - // Apply the input to the round state machine - let transition = round_state.apply(&data, mux_input); + self.pending_input = pending_input.map(|input| (input_round, input)); + } // Update state self.round_state = transition.next_state; @@ -354,8 +270,8 @@ where f.debug_struct("Driver") .field("address", &self.address) .field("validator_set", &self.validator_set) - .field("votes", &self.votes) - .field("proposals", &self.proposals.proposals) + .field("votes", &self.vote_keeper) + .field("proposal", &self.proposal) .field("round_state", &self.round_state) .finish() } diff --git a/Code/driver/src/lib.rs b/Code/driver/src/lib.rs index fd77260a5..caddfa65e 100644 --- a/Code/driver/src/lib.rs +++ b/Code/driver/src/lib.rs @@ -17,8 +17,8 @@ extern crate alloc; mod driver; mod error; mod input; +mod mux; mod output; -mod proposals; mod proposer; mod util; diff --git a/Code/driver/src/mux.rs b/Code/driver/src/mux.rs new file mode 100644 index 000000000..cb5e31d4d --- /dev/null +++ b/Code/driver/src/mux.rs @@ -0,0 +1,197 @@ +use malachite_common::ValueId; +use malachite_common::{Context, Proposal, Round, Value, VoteType}; +use malachite_round::input::Input as RoundInput; +use malachite_round::state::Step; +use malachite_vote::keeper::Output as VoteKeeperOutput; +use malachite_vote::keeper::VoteKeeper; +use malachite_vote::Threshold; + +use crate::{Driver, Validity}; + +impl Driver +where + Ctx: Context, +{ + pub fn multiplex_proposal( + &mut self, + proposal: Ctx::Proposal, + validity: Validity, + ) -> Option> { + // Check that there is an ongoing round + if self.round_state.round == Round::Nil { + return None; + } + + // Check that the proposal is for the current height + if self.round_state.height != proposal.height() { + return None; + } + + // Store the proposal + self.proposal = Some(proposal.clone()); + + let polka_for_pol = self.vote_keeper.is_threshold_met( + &proposal.pol_round(), + VoteType::Prevote, + Threshold::Value(proposal.value().id()), + ); + + let polka_previous = proposal.pol_round().is_defined() + && polka_for_pol + && proposal.pol_round() < self.round_state.round; + + // Handle invalid proposal + if !validity.is_valid() { + if self.round_state.step == Step::Propose { + if proposal.pol_round().is_nil() { + // L26 + return Some(RoundInput::InvalidProposal); + } else if polka_previous { + // L32 + return Some(RoundInput::InvalidProposalAndPolkaPrevious( + proposal.clone(), + )); + } else { + return None; + } + } else { + return None; + } + } + + // We have a valid proposal. + // L49 + // TODO - check if not already decided + if self.vote_keeper.is_threshold_met( + &proposal.round(), + VoteType::Precommit, + Threshold::Value(proposal.value().id()), + ) { + return Some(RoundInput::ProposalAndPrecommitValue(proposal.clone())); + } + + // If the proposal is for a different round, drop the proposal + if self.round_state.round != proposal.round() { + return None; + } + + let polka_for_current = self.vote_keeper.is_threshold_met( + &proposal.round(), + VoteType::Prevote, + Threshold::Value(proposal.value().id()), + ); + + let polka_current = polka_for_current && self.round_state.step >= Step::Prevote; + + // L36 + if polka_current { + return Some(RoundInput::ProposalAndPolkaCurrent(proposal)); + } + + // L28 + if self.round_state.step == Step::Propose && polka_previous { + // TODO: Check proposal vr is equal to threshold vr + return Some(RoundInput::ProposalAndPolkaPrevious(proposal)); + } + + Some(RoundInput::Proposal(proposal)) + } + + pub fn multiplex_vote_threshold( + &self, + new_threshold: VoteKeeperOutput>, + ) -> Option> { + if let Some(proposal) = &self.proposal { + match new_threshold { + VoteKeeperOutput::PolkaAny => Some(RoundInput::PolkaAny), + VoteKeeperOutput::PolkaNil => Some(RoundInput::PolkaNil), + VoteKeeperOutput::PolkaValue(v) => { + if v == proposal.value().id() { + Some(RoundInput::ProposalAndPolkaCurrent(proposal.clone())) + } else { + Some(RoundInput::PolkaAny) + } + } + VoteKeeperOutput::PrecommitAny => Some(RoundInput::PrecommitAny), + VoteKeeperOutput::PrecommitValue(v) => { + if v == proposal.value().id() { + Some(RoundInput::ProposalAndPrecommitValue(proposal.clone())) + } else { + Some(RoundInput::PrecommitAny) + } + } + VoteKeeperOutput::SkipRound(r) => Some(RoundInput::SkipRound(r)), + } + } else { + match new_threshold { + VoteKeeperOutput::PolkaAny => Some(RoundInput::PolkaAny), + VoteKeeperOutput::PolkaNil => Some(RoundInput::PolkaNil), + VoteKeeperOutput::PolkaValue(_) => Some(RoundInput::PolkaAny), + VoteKeeperOutput::PrecommitAny => Some(RoundInput::PrecommitAny), + VoteKeeperOutput::PrecommitValue(_) => Some(RoundInput::PrecommitAny), + VoteKeeperOutput::SkipRound(r) => Some(RoundInput::SkipRound(r)), + } + } + } + + pub fn multiplex_step_change( + &self, + pending_step: Step, + round: Round, + ) -> Option> { + match pending_step { + Step::NewRound => None, // Some(RoundInput::NewRound), + + Step::Prevote => { + if has_polka_nil(&self.vote_keeper, round) { + Some(RoundInput::PolkaNil) + } else if let Some(proposal) = + has_polka_value(&self.vote_keeper, round, self.proposal.as_ref()) + { + Some(RoundInput::ProposalAndPolkaCurrent(proposal.clone())) + } else if has_polka_any(&self.vote_keeper, round) { + Some(RoundInput::PolkaAny) + } else { + None + } + } + + Step::Propose => None, + Step::Precommit => None, + Step::Commit => None, + } + } +} + +fn has_polka_nil(votekeeper: &VoteKeeper, round: Round) -> bool +where + Ctx: Context, +{ + votekeeper.is_threshold_met(&round, VoteType::Prevote, Threshold::Nil) +} + +fn has_polka_value<'p, Ctx>( + votekeeper: &VoteKeeper, + round: Round, + proposal: Option<&'p Ctx::Proposal>, +) -> Option<&'p Ctx::Proposal> +where + Ctx: Context, +{ + let proposal = proposal?; + + votekeeper + .is_threshold_met( + &round, + VoteType::Prevote, + Threshold::Value(proposal.value().id()), + ) + .then_some(proposal) +} + +fn has_polka_any(votekeeper: &VoteKeeper, round: Round) -> bool +where + Ctx: Context, +{ + votekeeper.is_threshold_met(&round, VoteType::Prevote, Threshold::Any) +} diff --git a/Code/driver/src/proposals.rs b/Code/driver/src/proposals.rs deleted file mode 100644 index cf0173efe..000000000 --- a/Code/driver/src/proposals.rs +++ /dev/null @@ -1,48 +0,0 @@ -use alloc::collections::BTreeMap; -use alloc::vec::Vec; - -use malachite_common::ValueId; -use malachite_common::{Context, Proposal, Value}; - -/// Stores proposals at each round, indexed by their value id. -pub struct Proposals -where - Ctx: Context, -{ - pub(crate) proposals: BTreeMap, Vec>, -} - -impl Proposals -where - Ctx: Context, -{ - pub fn new() -> Self { - Self { - proposals: BTreeMap::new(), - } - } - - pub fn insert(&mut self, proposal: Ctx::Proposal) { - let value_id = proposal.value().id(); - self.proposals.entry(value_id).or_default().push(proposal); - } - - pub fn find( - &self, - value_id: &ValueId, - p: impl Fn(&Ctx::Proposal) -> bool, - ) -> Option<&Ctx::Proposal> { - self.proposals - .get(value_id) - .and_then(|proposals| proposals.iter().find(|proposal| p(proposal))) - } -} - -impl Default for Proposals -where - Ctx: Context, -{ - fn default() -> Self { - Self::new() - } -} diff --git a/Code/test/src/utils.rs b/Code/test/src/utils.rs index 4dfbe5092..a77367496 100644 --- a/Code/test/src/utils.rs +++ b/Code/test/src/utils.rs @@ -109,6 +109,10 @@ pub fn prevote_input(addr: &Address, sk: &PrivateKey) -> Input { ) } +pub fn prevote_nil_input(addr: &Address, sk: &PrivateKey) -> Input { + Input::Vote(Vote::new_prevote(Height::new(1), Round::new(0), None, *addr).signed(sk)) +} + pub fn prevote_input_at(round: Round, addr: &Address, sk: &PrivateKey) -> Input { let value = Value::new(9999); @@ -126,9 +130,13 @@ pub fn precommit_output( )) } -pub fn precommit_nil_output(addr: &Address, sk: &PrivateKey) -> Option> { +pub fn precommit_nil_output( + round: Round, + addr: &Address, + sk: &PrivateKey, +) -> Option> { Some(Output::Vote( - Vote::new_precommit(Height::new(1), Round::new(0), None, *addr).signed(sk), + Vote::new_precommit(Height::new(1), round, None, *addr).signed(sk), )) } diff --git a/Code/test/tests/driver.rs b/Code/test/tests/driver.rs index beec2c586..69cf3cb7e 100644 --- a/Code/test/tests/driver.rs +++ b/Code/test/tests/driver.rs @@ -83,7 +83,6 @@ fn driver_steps_proposer() { height: Height::new(1), round: Round::new(0), step: Step::Prevote, - // proposal: Some(proposal.clone()), locked: None, valid: None, }, @@ -97,7 +96,6 @@ fn driver_steps_proposer() { height: Height::new(1), round: Round::new(0), step: Step::Prevote, - // proposal: Some(proposal.clone()), locked: None, valid: None, }, @@ -114,7 +112,6 @@ fn driver_steps_proposer() { height: Height::new(1), round: Round::new(0), step: Step::Prevote, - // proposal: Some(proposal.clone()), locked: None, valid: None, }, @@ -134,7 +131,6 @@ fn driver_steps_proposer() { height: Height::new(1), round: Round::new(0), step: Step::Precommit, - // proposal: Some(proposal.clone()), locked: Some(RoundValue { value, round: Round::new(0), @@ -154,7 +150,6 @@ fn driver_steps_proposer() { height: Height::new(1), round: Round::new(0), step: Step::Precommit, - // proposal: Some(proposal.clone()), locked: Some(RoundValue { value, round: Round::new(0), @@ -177,7 +172,6 @@ fn driver_steps_proposer() { height: Height::new(1), round: Round::new(0), step: Step::Precommit, - // proposal: Some(proposal.clone()), locked: Some(RoundValue { value, round: Round::new(0), @@ -200,7 +194,6 @@ fn driver_steps_proposer() { height: Height::new(1), round: Round::new(0), step: Step::Commit, - // proposal: Some(proposal.clone()), locked: Some(RoundValue { value, round: Round::new(0), @@ -213,27 +206,7 @@ fn driver_steps_proposer() { }, ]; - let mut output_from_prev_input = None; - - for step in steps { - println!("Step: {}", step.desc); - - let input = step - .input - .unwrap_or_else(|| output_from_prev_input.unwrap()); - - let output = block_on(driver.execute(input)).expect("execute succeeded"); - assert_eq!(output, step.expected_output, "expected output"); - - assert_eq!( - driver.round_state.round, step.expected_round, - "expected round" - ); - - assert_eq!(driver.round_state, step.new_state, "expected state"); - - output_from_prev_input = output.and_then(output_to_input); - } + run_steps(&mut driver, steps); } #[test] @@ -281,27 +254,7 @@ fn driver_steps_proposer_timeout_get_value() { }, ]; - let mut output_from_prev_input = None; - - for step in steps { - println!("Step: {}", step.desc); - - let input = step - .input - .unwrap_or_else(|| output_from_prev_input.unwrap()); - - let output = block_on(driver.execute(input)).expect("execute succeeded"); - assert_eq!(output, step.expected_output, "expected output"); - - assert_eq!( - driver.round_state.round, step.expected_round, - "expected round" - ); - - assert_eq!(driver.round_state, step.new_state, "expected state"); - - output_from_prev_input = output.and_then(output_to_input); - } + run_steps(&mut driver, steps); } #[test] @@ -347,7 +300,6 @@ fn driver_steps_not_proposer_valid() { height: Height::new(1), round: Round::new(0), step: Step::Prevote, - // proposal: Some(proposal.clone()), locked: None, valid: None, }, @@ -361,7 +313,6 @@ fn driver_steps_not_proposer_valid() { height: Height::new(1), round: Round::new(0), step: Step::Prevote, - // proposal: Some(proposal.clone()), locked: None, valid: None, }, @@ -378,7 +329,6 @@ fn driver_steps_not_proposer_valid() { height: Height::new(1), round: Round::new(0), step: Step::Prevote, - // proposal: Some(proposal.clone()), locked: None, valid: None, }, @@ -398,7 +348,6 @@ fn driver_steps_not_proposer_valid() { height: Height::new(1), round: Round::new(0), step: Step::Precommit, - // proposal: Some(proposal.clone()), locked: Some(RoundValue { value, round: Round::new(0), @@ -418,7 +367,6 @@ fn driver_steps_not_proposer_valid() { height: Height::new(1), round: Round::new(0), step: Step::Precommit, - // proposal: Some(proposal.clone()), locked: Some(RoundValue { value, round: Round::new(0), @@ -441,7 +389,6 @@ fn driver_steps_not_proposer_valid() { height: Height::new(1), round: Round::new(0), step: Step::Precommit, - // proposal: Some(proposal.clone()), locked: Some(RoundValue { value, round: Round::new(0), @@ -464,7 +411,6 @@ fn driver_steps_not_proposer_valid() { height: Height::new(1), round: Round::new(0), step: Step::Commit, - // proposal: Some(proposal.clone()), locked: Some(RoundValue { value, round: Round::new(0), @@ -477,27 +423,7 @@ fn driver_steps_not_proposer_valid() { }, ]; - let mut output_from_prev_input = None; - - for step in steps { - println!("Step: {}", step.desc); - - let input = step - .input - .unwrap_or_else(|| output_from_prev_input.unwrap()); - - let output = block_on(driver.execute(input)).expect("execute succeeded"); - assert_eq!(output, step.expected_output, "expected output"); - - assert_eq!( - driver.round_state.round, step.expected_round, - "expected round" - ); - - assert_eq!(driver.round_state, step.new_state, "expected state"); - - output_from_prev_input = output.and_then(output_to_input); - } + run_steps(&mut driver, steps); } #[test] @@ -606,27 +532,7 @@ fn driver_steps_not_proposer_invalid() { }, ]; - let mut output_from_prev_input = None; - - for step in steps { - println!("Step: {}", step.desc); - - let input = step - .input - .unwrap_or_else(|| output_from_prev_input.unwrap()); - - let output = block_on(driver.execute(input)).expect("execute succeeded"); - assert_eq!(output, step.expected_output, "expected output"); - - assert_eq!( - driver.round_state.round, step.expected_round, - "expected round" - ); - - assert_eq!(driver.round_state, step.new_state, "expected state"); - - output_from_prev_input = output.and_then(output_to_input); - } + run_steps(&mut driver, steps); } #[test] @@ -676,27 +582,7 @@ fn driver_steps_not_proposer_other_height() { }, ]; - let mut output_from_prev_input = None; - - for step in steps { - println!("Step: {}", step.desc); - - let input = step - .input - .unwrap_or_else(|| output_from_prev_input.unwrap()); - - let output = block_on(driver.execute(input)).expect("execute succeeded"); - assert_eq!(output, step.expected_output, "expected output"); - - assert_eq!( - driver.round_state.round, step.expected_round, - "expected round" - ); - - assert_eq!(driver.round_state, step.new_state, "expected state"); - - output_from_prev_input = output.and_then(output_to_input); - } + run_steps(&mut driver, steps); } #[test] @@ -746,27 +632,7 @@ fn driver_steps_not_proposer_other_round() { }, ]; - let mut output_from_prev_input = None; - - for step in steps { - println!("Step: {}", step.desc); - - let input = step - .input - .unwrap_or_else(|| output_from_prev_input.unwrap()); - - let output = block_on(driver.execute(input)).expect("execute succeeded"); - assert_eq!(output, step.expected_output, "expected output"); - - assert_eq!( - driver.round_state.round, step.expected_round, - "expected round" - ); - - assert_eq!(driver.round_state, step.new_state, "expected state"); - - output_from_prev_input = output.and_then(output_to_input); - } + run_steps(&mut driver, steps); } #[test] @@ -916,7 +782,7 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { desc: "we receive a precommit timeout, start a new round", input: Some(Input::TimeoutElapsed(Timeout::precommit(Round::new(0)))), expected_output: Some(Output::NewRound(Height::new(1), Round::new(1))), - expected_round: Round::new(0), + expected_round: Round::new(1), new_state: State { height: Height::new(1), round: Round::new(1), @@ -940,22 +806,7 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { }, ]; - let mut output_from_prev_input = None; - - for step in steps { - println!("Step: {}", step.desc); - - let input = step - .input - .unwrap_or_else(|| output_from_prev_input.unwrap()); - - let output = block_on(driver.execute(input)).expect("execute succeeded"); - assert_eq!(output, step.expected_output, "expected output"); - - assert_eq!(driver.round_state, step.new_state, "new state"); - - output_from_prev_input = output.and_then(output_to_input); - } + run_steps(&mut driver, steps); } // No value to propose @@ -971,9 +822,11 @@ fn driver_steps_no_value_to_propose() { let mut driver = Driver::new(ctx, sel, vs, my_addr); - let output = block_on(driver.execute(Input::NewRound(Height::new(1), Round::new(0)))) + let mut outputs = block_on(driver.process(Input::NewRound(Height::new(1), Round::new(0)))) .expect("execute succeeded"); + let output = outputs.pop(); + assert_eq!( output, Some(Output::GetValueAndScheduleTimeout( @@ -997,7 +850,7 @@ fn driver_steps_proposer_not_found() { let mut driver = Driver::new(ctx, sel, vs, my_addr); - let output = block_on(driver.execute(Input::NewRound(Height::new(1), Round::new(0)))); + let output = block_on(driver.process(Input::NewRound(Height::new(1), Round::new(0)))); assert_eq!(output, Err(Error::ProposerNotFound(v1.address))); } @@ -1018,11 +871,11 @@ fn driver_steps_validator_not_found() { let mut driver = Driver::new(ctx, sel, vs, my_addr); // Start new height - block_on(driver.execute(Input::NewRound(Height::new(1), Round::new(0)))) + block_on(driver.process(Input::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(Input::Vote( + let output = block_on(driver.process(Input::Vote( Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), v2.address).signed(&sk2), ))); @@ -1044,12 +897,12 @@ fn driver_steps_invalid_signature() { let mut driver = Driver::new(ctx, sel, vs, my_addr); // Start new round - block_on(driver.execute(Input::NewRound(Height::new(1), Round::new(0)))) + block_on(driver.process(Input::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(Input::Vote( + let output = block_on(driver.process(Input::Vote( Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), v2.address).signed(&sk1), ))); @@ -1152,23 +1005,7 @@ fn driver_steps_skip_round_skip_threshold() { }, ]; - let mut output_from_prev_input = None; - - for step in steps { - println!("Step: {}", step.desc); - - let input = step - .input - .unwrap_or_else(|| output_from_prev_input.unwrap()); - - let output = block_on(driver.execute(input)).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, step.new_state, "new state"); - - output_from_prev_input = output.and_then(output_to_input); - } + run_steps(&mut driver, steps); } #[test] @@ -1267,6 +1104,10 @@ fn driver_steps_skip_round_quorum_threshold() { }, ]; + run_steps(&mut driver, steps); +} + +fn run_steps(driver: &mut Driver, steps: Vec) { let mut input_from_prev_output = None; for step in steps { @@ -1276,11 +1117,11 @@ fn driver_steps_skip_round_quorum_threshold() { .input .unwrap_or_else(|| input_from_prev_output.unwrap()); - let output = block_on(driver.execute(input)).expect("execute succeeded"); - assert_eq!(output, step.expected_output, "expected output"); + let mut outputs = block_on(driver.process(input)).expect("execute succeeded"); + let output = outputs.pop(); + assert_eq!(output, step.expected_output, "expected output"); assert_eq!(driver.round(), step.expected_round, "expected round"); - assert_eq!(driver.round_state, step.new_state, "new state"); input_from_prev_output = output.and_then(output_to_input); diff --git a/Code/test/tests/driver_extra.rs b/Code/test/tests/driver_extra.rs index 7397c6569..afcfe841f 100644 --- a/Code/test/tests/driver_extra.rs +++ b/Code/test/tests/driver_extra.rs @@ -26,6 +26,15 @@ use malachite_test::utils::*; // - L28 in round 1 with locked value, via L36 in round 0 with step prevote. // `driver_steps_polka_previous_with_locked() // +// - L44 with previously received polkaNil and entering prevote (due to timeoutPropose) +// `driver_steps_polka_nil_and_timout_propose()` +// +// - L36 with previoustly received polkaValue and proposal, and entering prevote (due to received proposal) +// `driver_steps_polka_value_then_proposal()` +// +// - L34 with previously received polkaAny and entering prevote (due to received poposal) +// `driver_steps_polka_any_then_proposal_other()` + // TODO - move all below to utils? struct TestStep { desc: &'static str, @@ -74,7 +83,7 @@ fn driver_steps_decide_current_with_no_locked_no_valid() { let steps = vec![ TestStep { - desc: "Start round 0, we, v2, are not the proposer, start timeout propose", + desc: "Start round 0, we, v3, are not the proposer, start timeout propose", input: new_round_input(Round::new(0)), expected_output: start_propose_timer_output(Round::new(0)), expected_round: Round::new(0), @@ -117,10 +126,10 @@ fn driver_steps_decide_current_with_no_locked_no_valid() { // Msg: propose_timer Prevote(nil) prevote_timer Precommit(nil) // Alg: L21 L57 L34 L61 // -// Ev: Timeout(precommit) NewRound(1) Proposal+ -// State: --> Precommit ----------> Precommit ---------------> NewRound -----------> Propose -----------------> Decided -// Msg: precommit_timer new_round(1) propose_timer decide -// Alg: L46 L65 L21 L49 +// Ev: Timeout(precommit) NewRound(1) Proposal+ +// State: Precommit ----------> Precommit ---------------> NewRound -----------> Propose -----------------> Decided +// Msg: precommit_timer new_round(1) propose_timer decide +// Alg: L46 L65 L21 L49 // // v1=2, v2=3, v3=2, we are v3 // L21 - v3 is not proposer starts propose timer (step propose) @@ -146,14 +155,14 @@ fn driver_steps_decide_previous_with_no_locked_no_valid() { let steps = vec![ TestStep { - desc: "Start round 0, we, v2, are not the proposer, start timeout propose", + desc: "Start round 0, we, v3, are not the proposer, start timeout propose", input: new_round_input(Round::new(0)), expected_output: start_propose_timer_output(Round::new(0)), expected_round: Round::new(0), new_state: propose_state(Round::new(0)), }, TestStep { - desc: "Timeout propopse, prevote for nil (v2)", + desc: "Timeout propopse, prevote for nil (v3)", input: timeout_propose_input(Round::new(0)), expected_output: prevote_nil_output(Round::new(0), &my_addr, &my_sk), expected_round: Round::new(0), @@ -217,10 +226,10 @@ fn driver_steps_decide_previous_with_no_locked_no_valid() { // Msg: propose_timer Prevote(nil) prevote_timer Precommit(value) // Alg: L21 L57 L34 L40 // -// Ev: NewRound(1) -// State: --> Precommit ---------------------------> NewRound -----------> Propose --------------------> Commit -// Msg: new_round(1) propose_timer decide(v, round=1) -// Alg: L56 L21 L49-L54 +// Ev: NewRound(1) +// State: Precommit ---------------------------> NewRound -----------> Propose --------------------> Commit +// Msg: new_round(1) propose_timer decide(v, round=1) +// Alg: L56 L21 L49-L54 // // v1=2, v2=3, v3=2, we are v3 // L21 - start round 0, we, v3, are not the proposer, start timeout propose (step propose) @@ -335,10 +344,10 @@ fn driver_steps_decide_previous_with_locked_and_valid() { // Msg: propose_timer prevote(v) precommit(v) new_round(1) // Alg: L21 L24 L37 L56 // -// Ev: NewRound(1) Proposal(+polka) -// State: --> NewRound ---------------> Propose ------------------> Prevote -// Msg: propose(v, pol) prevote(v,round=1) -// Alg: L16, L19 L28-L30 +// Ev: NewRound(1) Proposal(+polka) +// State: NewRound --------------> Propose -----------------> Prevote +// Msg: propose(v, pol) prevote(v,round=1) +// Alg: L16, L19 L28-L30 // // v1=2, v2=2, v3=3, we are v2 // Trying to arrive at L36 with step prevote and then L28 @@ -444,10 +453,10 @@ fn driver_steps_polka_previous_with_locked() { // Msg: propose_timer prevote(nil) prevote_timer new_round(1) // Alg: L21 L59 L35 L56 // -// Ev: NewRound(1) InvalidProposal(round=0) -// State: --> NewRound ---------------> Propose -------------------------> Prevote -// Msg: propose_timer prevote(nil,round=1) -// Alg: L21 L28-L32 +// Ev: NewRound(1) InvalidProposal(round=0) +// State: NewRound ---------------> Propose -----------------------> Prevote +// Msg: propose_timer prevote(nil,round=1) +// Alg: L21 L28-L32 // // v1=2, v2=3, v3=2, we are v3 // L21 - v3 is not proposer starts timeout propose (step propose) @@ -533,15 +542,15 @@ fn driver_steps_polka_previous_invalid_proposal() { // Msg: propose_timer Prevote(nil) prevote_timer Precommit(nil) // Alg: L21 L59 L34 L63 // -// Ev: Proposal(v) -// State: --> Precommit ---------------> Precommit ---------------------------> NewRound -// Msg: none new_round(1) -// Alg: L42, L43 L56 +// Ev: Proposal(v) +// State: Precommit ----------> Precommit --------------------------> NewRound --> +// Msg: none new_round(1) +// Alg: L42, L43 L56 // -// Ev: NewRound(1) Proposal(+polka) -// State: --> NewRound ---------------> Propose -------------------> Prevote -// Msg: propose(v, pol) prevote(nil,round=1) -// Alg: L16, L19 L28, L32 (not locked on v) +// Ev: NewRound(1) Proposal(+polka) +// State: NewRound --------------> Propose -------------------------> Prevote +// Msg: propose(v, pol) prevote(nil,round=1) +// Alg: L16, L19 L28, L32 (not locked on v) // // v1=2, v2=2, v3=3, we are v2 // Trying to be at L36 with step precommit @@ -571,7 +580,7 @@ fn driver_steps_polka_previous_with_no_locked() { let steps = vec![ TestStep { - desc: "Start round 0, we v2 are not the proposer, start timeout propose", + desc: "Start round 0, we, v2, are not the proposer, start timeout propose", input: new_round_input(Round::new(0)), expected_output: start_propose_timer_output(Round::new(0)), expected_round: Round::new(0), @@ -601,7 +610,7 @@ fn driver_steps_polka_previous_with_no_locked() { TestStep { desc: "timeout prevote, prevote for nil (v2)", input: timeout_prevote_input(Round::new(0)), - expected_output: precommit_nil_output(&my_addr, &my_sk), + expected_output: precommit_nil_output(Round::new(0), &my_addr, &my_sk), expected_round: Round::new(0), new_state: precommit_state(Round::new(0)), }, @@ -653,11 +662,194 @@ fn driver_steps_polka_previous_with_no_locked() { run_steps(&mut driver, steps); } +// Arrive at L44 with previously received polkaNil and entering prevote (due to timeoutPropose) +// +// Ev: NewRound(0) Timeout(propose) + replay +// State: NewRound ------------> Propose --------> Propose ---------------> Prevote -------------------> Precommit +// Msg: propose_timer None prevote_nil precommit_nil +// Alg: L21 L34 L44 +// +// +// v1=2, v2=3, v3=2, we are v3 +// L21 - v3 is not proposer starts propose timer (step propose) +// L34 - v3 gets +2/3 prevotes for nil (from v1 and v2), event ignored (step propose) +// L57 - v3 receives timeout propose, prevotes for nil (step prevote) +// L44 - polkaNil is replayed and v3 precommits for nil (step precommit) +#[test] +fn driver_steps_polka_nil_and_timout_propose() { + let [(v1, sk1), (v2, sk2), (v3, sk3)] = make_validators([2, 3, 2]); + let (my_sk, my_addr) = (sk3.clone(), v3.address); + + let ctx = TestContext::new(my_sk.clone()); + let sel = RotateProposer; + let vs = ValidatorSet::new(vec![v1.clone(), v2.clone(), v3.clone()]); + + let mut driver = Driver::new(ctx, sel, vs, my_addr); + + let steps = vec![ + TestStep { + desc: "Start round 0, we, v3, are not the proposer, start timeout propose", + input: new_round_input(Round::new(0)), + expected_output: start_propose_timer_output(Round::new(0)), + expected_round: Round::new(0), + new_state: propose_state(Round::new(0)), + }, + TestStep { + desc: "v1 prevotes nil", + input: prevote_nil_input(&v1.address, &sk1), + expected_output: None, + expected_round: Round::new(0), + new_state: propose_state(Round::new(0)), + }, + TestStep { + desc: "v2 prevotes for for nil, we get polkaNil, but we are in Propose step", + input: prevote_nil_input(&v2.address, &sk2), + expected_output: None, + expected_round: Round::new(0), + new_state: propose_state(Round::new(0)), + }, + TestStep { + desc: "Timeout propose, prevote for nil then precommit for nil", + input: timeout_propose_input(Round::new(0)), + expected_output: precommit_nil_output(Round::new(0), &my_addr, &my_sk), + expected_round: Round::new(0), + new_state: precommit_state(Round::new(0)), + }, + ]; + + run_steps(&mut driver, steps); +} + +// Arrive at L36 with previoustly received polkaValue and proposal, and entering prevote (due to received proposal) +// +// Ev: NewRound(0) Proposal + replay +// State: NewRound ------------> Propose -----------> Propose ---------> Prevote --------------------> Precommit +// Msg: propose_timer None prevote(v) precommit(v) +// Alg: L21 L24 L37, L37-L43 +// +// +// v1=2, v2=3, v3=2, we are v3 +// L21 - v3 is not proposer starts propose timer (step propose) +// L34 - v3 gets +2/3 prevotes (from v1 and v2), events ignored (step propose) +// L57 - v3 receives proposal, prevotes for value (step prevote) +// L36 - polka is replayed and v3 precommits for value (step precommit) +#[test] +fn driver_steps_polka_value_then_proposal() { + let value = Value::new(9999); + + let [(v1, sk1), (v2, sk2), (v3, sk3)] = make_validators([2, 3, 2]); + let (my_sk, my_addr) = (sk3.clone(), v3.address); + + let ctx = TestContext::new(my_sk.clone()); + let sel = RotateProposer; + let vs = ValidatorSet::new(vec![v1.clone(), v2.clone(), v3.clone()]); + + let mut driver = Driver::new(ctx, sel, vs, my_addr); + + let steps = vec![ + TestStep { + desc: "Start round 0, we, v3, are not the proposer, start timeout propose", + input: new_round_input(Round::new(0)), + expected_output: start_propose_timer_output(Round::new(0)), + expected_round: Round::new(0), + new_state: propose_state(Round::new(0)), + }, + TestStep { + desc: "v1 prevotes a proposal", + input: prevote_input(&v1.address, &sk1), + expected_output: None, + expected_round: Round::new(0), + new_state: propose_state(Round::new(0)), + }, + TestStep { + desc: "v2 prevotes for same proposal, we get +2/3 prevotes, but we are in Propose step", + input: prevote_input(&v2.address, &sk2), + expected_output: None, + expected_round: Round::new(0), + new_state: propose_state(Round::new(0)), + }, + TestStep { + desc: "receive a proposal from v1 - L22 send prevote", + input: proposal_input(Round::new(0), value, Round::Nil, Validity::Valid), + expected_output: precommit_output(Round::new(0), value, &my_addr, &my_sk), + expected_round: Round::new(0), + new_state: precommit_state_with_proposal_and_locked_and_valid( + Round::new(0), + Proposal::new(Height::new(1), Round::new(0), value, Round::Nil), + ), + }, + ]; + + run_steps(&mut driver, steps); +} + +// Arrive at L34 with previously received polkaAny and entering prevote (due to received poposal) +// +// Ev: NewRound(0) Proposal(v') + replay +// State: NewRound ------------> Propose -------------> Propose -----------> Prevote -------------------------> Prevote +// Msg: propose_timer None prevote(v) schedule_timeout(prevote) +// Alg: L21 L24 L34 +// +// +// v1=2, v2=3, v3=2, we are v3 +// L21 - v3 is not proposer starts propose timer (step propose) +// L34 - v3 gets +2/3 prevotes for v (from v1 and v2), events ignored (step propose) +// L57 - v3 receives proposal for v', prevotes for v' (step prevote) +// L34 - polka any is replayed and prevote timer is started (step prevote) +#[test] +fn driver_steps_polka_any_then_proposal_other() { + let value = Value::new(9999); + + let [(v1, sk1), (v2, sk2), (v3, sk3)] = make_validators([2, 3, 2]); + let (my_sk, my_addr) = (sk3.clone(), v3.address); + + let ctx = TestContext::new(my_sk.clone()); + let sel = RotateProposer; + let vs = ValidatorSet::new(vec![v1.clone(), v2.clone(), v3.clone()]); + + let mut driver = Driver::new(ctx, sel, vs, my_addr); + + let steps = vec![ + TestStep { + desc: "Start round 0, we, v3, are not the proposer, start timeout propose", + input: new_round_input(Round::new(0)), + expected_output: start_propose_timer_output(Round::new(0)), + expected_round: Round::new(0), + new_state: propose_state(Round::new(0)), + }, + TestStep { + desc: "v1 prevotes for nil", + input: prevote_nil_input(&v1.address, &sk1), + expected_output: None, + expected_round: Round::new(0), + new_state: propose_state(Round::new(0)), + }, + TestStep { + desc: "v2 prevotes for same proposal, we get polkaAny, but we are in Propose step", + input: prevote_input(&v2.address, &sk2), + expected_output: None, + expected_round: Round::new(0), + new_state: propose_state(Round::new(0)), + }, + TestStep { + desc: "receive a proposal from v1 - L22 send prevote, replay polkaAny, start timeout prevote", + input: proposal_input(Round::new(0), value, Round::Nil, Validity::Valid), + expected_output: start_prevote_timer_output(Round::new(0)), + expected_round: Round::new(0), + new_state: prevote_state(Round::new(0)), + }, + ]; + + run_steps(&mut driver, steps); +} + fn run_steps(driver: &mut Driver, steps: Vec) { for step in steps { println!("Step: {}", step.desc); - let output = block_on(driver.execute(step.input)).expect("execute succeeded"); + let mut outputs = block_on(driver.process(step.input)).expect("execute succeeded"); + let output = outputs.pop(); + assert_eq!(output, step.expected_output, "expected output"); assert_eq!( diff --git a/Code/vote/src/keeper.rs b/Code/vote/src/keeper.rs index a0e500712..15b3fdab1 100644 --- a/Code/vote/src/keeper.rs +++ b/Code/vote/src/keeper.rs @@ -175,8 +175,8 @@ where vote_type: VoteType, threshold: Threshold>, ) -> bool { - self.per_round.get(round).map_or(false, |round| { - round.votes.is_threshold_met( + self.per_round.get(round).map_or(false, |per_round| { + per_round.votes.is_threshold_met( vote_type, threshold, self.threshold_params.quorum,