From 1c3d844a88c33d0035351ec6ac07d86301b1fbd5 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Mon, 27 Nov 2023 12:55:27 +0100 Subject: [PATCH 1/7] spec: Align VoteKeeper spec with the code (#84) --- Scripts/quint-forall.sh | 2 +- Specs/Quint/executor.qnt | 14 +++-- Specs/Quint/voteBookkeeper.qnt | 5 +- Specs/Quint/voteBookkeeperTest.qnt | 86 +++++++++++++++--------------- 4 files changed, 52 insertions(+), 55 deletions(-) mode change 100644 => 100755 Scripts/quint-forall.sh diff --git a/Scripts/quint-forall.sh b/Scripts/quint-forall.sh old mode 100644 new mode 100755 index dc0fb1bd7..6ab2fe003 --- a/Scripts/quint-forall.sh +++ b/Scripts/quint-forall.sh @@ -1,4 +1,4 @@ -#!/bin/env bash +#!/usr/bin/env bash BLUE=$(tput setaf 4) RED=$(tput setaf 1) diff --git a/Specs/Quint/executor.qnt b/Specs/Quint/executor.qnt index 0984c4014..1a671d071 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 (currentRound: Round, totalVotingPower: int): Bookkeeper = - { height: 0, currentRound: currentRound, totalWeight: totalVotingPower, rounds: Map() } +pure def initBookKeeper(totalVotingPower: int): Bookkeeper = + { height: 0, 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(0, tvp), + bk: initBookKeeper(tvp), cs: initConsensusState(v), proposals: Set(), valset: vs, @@ -155,12 +155,10 @@ pure def callConsensus (es: ExecutorState, bk: Bookkeeper, ev: Event) : (Executo else // Go to consensus val res = consensus(es.cs, ev) - // 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) + ({ ...es, bk: bk, cs: res.cs, executedEvents: events }, res.out) } @@ -483,7 +481,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)) + val res = applyVote(es.bk, toVote(input.vote), es.valset.get(input.vote.src), es.cs.round) val newES = { ...es, bk: res.bookkeeper, applyvotesResult: res.event} // only a commit event can come here. val cons_res = Precommit(newES, input, res.event) @@ -495,7 +493,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)) + val res = applyVote(es.bk, toVote(input.vote), es.valset.get(input.vote.src), es.cs.round) 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 25b085a3f..cde151a75 100644 --- a/Specs/Quint/voteBookkeeper.qnt +++ b/Specs/Quint/voteBookkeeper.qnt @@ -62,7 +62,6 @@ module voteBookkeeper { type Bookkeeper = { height: Height, - currentRound: Round, totalWeight: Weight, rounds: Round -> RoundVotes } @@ -158,7 +157,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): { bookkeeper: Bookkeeper, event: ExecutorEvent } = + pure def applyVote(keeper: Bookkeeper, vote: Vote, weight: Weight, currentRound: Round): { bookkeeper: Bookkeeper, event: ExecutorEvent } = val height = keeper.height val total = keeper.totalWeight @@ -194,7 +193,7 @@ module voteBookkeeper { val combinedWeight = updatedVotesAddressesWeights.mapSumValues() val finalEvent = - if (vote.round > keeper.currentRound and isSkip(combinedWeight, total)) + if (vote.round > currentRound and isSkip(combinedWeight, total)) { round: vote.round, name: "Skip", value: "null" } else val threshold = computeThreshold(updatedVoteCount, vote.value) diff --git a/Specs/Quint/voteBookkeeperTest.qnt b/Specs/Quint/voteBookkeeperTest.qnt index ae635f823..4541bb798 100644 --- a/Specs/Quint/voteBookkeeperTest.qnt +++ b/Specs/Quint/voteBookkeeperTest.qnt @@ -20,13 +20,13 @@ module voteBookkeeperTest { lastEmitted' = lastEmitted, } - action initWith(round: Round, totalWeight: Weight): bool = all { - bookkeeper' = { height: 10, currentRound: round, totalWeight: totalWeight, rounds: Map() }, + action initWith(totalWeight: Weight): bool = all { + bookkeeper' = { height: 10, totalWeight: totalWeight, rounds: Map() }, lastEmitted' = { round: -1, name: "", value: "null" }, } - action applyVoteAction(vote: Vote, weight: Weight): bool = - val result = applyVote(bookkeeper, vote, weight) + action applyVoteAction(vote: Vote, weight: Weight, currentRound: Round): bool = + val result = applyVote(bookkeeper, vote, weight, currentRound) all { bookkeeper' = result.bookkeeper, lastEmitted' = result.event, @@ -44,100 +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 = - initWith(1, 100) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 60)) + initWith(100) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 60, 1)) .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "john"}, 10)) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "john"}, 10, 1)) .then(_assert(lastEmitted == {round: 1, name: "PolkaValue", value: "proposal"})) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "bob"}, 30)) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "bob"}, 30, 1)) .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Precommit", round: 1, value: "proposal", address: "bob"}, 30)) + .then(applyVoteAction({typ: "Precommit", round: 1, value: "proposal", address: "bob"}, 30, 1)) .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Precommit", round: 1, value: "proposal", address: "john"}, 10)) + .then(applyVoteAction({typ: "Precommit", round: 1, value: "proposal", address: "john"}, 10, 1)) .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Precommit", round: 1, value: "proposal", address: "alice"}, 60)) + .then(applyVoteAction({typ: "Precommit", round: 1, value: "proposal", address: "alice"}, 60, 1)) .then(_assert(lastEmitted == {round: 1, name: "PrecommitValue", value: "proposal"})) // Reaching PolkaAny run polkaAnyTest = - initWith(1, 100) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "val1", address: "alice"}, 60)) + initWith(100) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "val1", address: "alice"}, 60, 1)) .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "nil", address: "john"}, 10)) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "nil", address: "john"}, 10, 1)) .then(_assert(lastEmitted == {round: 1, name: "PolkaAny", value: "null"})) // Reaching PolkaNil run polkaNilTest = - initWith(1, 100) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "nil", address: "alice"}, 60)) + initWith(100) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "nil", address: "alice"}, 60, 1)) .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "nil", address: "john"}, 10)) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "nil", address: "john"}, 10, 1)) .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)) + initWith(100) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 60, 1)) .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 10)) + .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 10, 1)) .then(_assert(lastEmitted == {round: 2, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "bob"}, 30)) + .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "bob"}, 30, 1)) .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)) + initWith(90) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 10, 1)) .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 20)) + .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 20, 1)) .then(_assert(lastEmitted == {round: 2, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Precommit", round: 2, value: "proposal", address: "john"}, 20)) + .then(applyVoteAction({typ: "Precommit", round: 2, value: "proposal", address: "john"}, 20, 1)) .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)) + initWith(80) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 50, 1)) .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 10)) + .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 10, 1)) .then(_assert(lastEmitted == {round: 2, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Precommit", round: 2, value: "proposal", address: "bob"}, 20)) + .then(applyVoteAction({typ: "Precommit", round: 2, value: "proposal", address: "bob"}, 20, 1)) .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)) + initWith(100) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 10, 1)) .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 60)) + .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 60, 1)) .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)) + initWith(100) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 10, 1)) .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Precommit", round: 2, value: "proposal", address: "john"}, 60)) + .then(applyVoteAction({typ: "Precommit", round: 2, value: "proposal", address: "john"}, 60, 1)) .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)) + initWith(100) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 10, 1)) .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 30)) + .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 30, 1)) .then(_assert(lastEmitted == {round: 2, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Precommit", round: 2, value: "proposal", address: "john"}, 30)) + .then(applyVoteAction({typ: "Precommit", round: 2, value: "proposal", address: "john"}, 30, 1)) .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)) + initWith(80) + .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 20, 1)) .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 10)) + .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 10, 1)) .then(_assert(lastEmitted == {round: 2, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Precommit", round: 2, value: "proposal", address: "bob"}, 50)) + .then(applyVoteAction({typ: "Precommit", round: 2, value: "proposal", address: "bob"}, 50, 1)) .then(_assert(lastEmitted == {round: 2, name: "Skip", value: "null"})) } From 60cbc96b9b25f10a72f90add56534cf7eaaf628a Mon Sep 17 00:00:00 2001 From: Anca Zamfir Date: Mon, 27 Nov 2023 23:05:51 +0100 Subject: [PATCH 2/7] feat: Handle polka previous and commit quorum decisions (#85) * Add tests for L49 and L36 * Clean previous var names in existing driver tests * Fix extra tests * Add comments and try better names * Fix clippy warnings * Move utils into test crate * Remove `utils::` prefix * Move proposer selectors into `utils` * Cut down on boilerplate for making validator set * Add test for proposal for another height * Add test for invalid proposal and polka previous * Remove `Round::NIL` constant * Add test for receiving a proposal at another round * Cleanup unused code in extra tests * Add `run_steps` function --------- Co-authored-by: Romain Ruetschi --- Code/common/src/round.rs | 1 - Code/driver/src/driver.rs | 121 +++++--- Code/round/src/events.rs | 9 +- Code/round/src/state.rs | 2 +- Code/round/src/state_machine.rs | 62 ++-- Code/test/src/lib.rs | 2 + Code/test/src/utils.rs | 386 +++++++++++++++++++++++ Code/test/tests/driver.rs | 455 ++++++++++++++------------- Code/test/tests/driver_extra.rs | 534 ++++++++++++++++++++++++++++++++ 9 files changed, 1255 insertions(+), 317 deletions(-) create mode 100644 Code/test/src/utils.rs create mode 100644 Code/test/tests/driver_extra.rs diff --git a/Code/common/src/round.rs b/Code/common/src/round.rs index fb45993c9..6bb5cd3d6 100644 --- a/Code/common/src/round.rs +++ b/Code/common/src/round.rs @@ -17,7 +17,6 @@ pub enum Round { impl Round { /// The initial, zero round. pub const INITIAL: Round = Round::new(0); - pub const NIL: Round = Round::new(-1); /// Create a new round. /// diff --git a/Code/driver/src/driver.rs b/Code/driver/src/driver.rs index 0a6419d99..3c93f780e 100644 --- a/Code/driver/src/driver.rs +++ b/Code/driver/src/driver.rs @@ -6,7 +6,7 @@ use malachite_common::{ }; use malachite_round::events::Event as RoundEvent; use malachite_round::message::Message as RoundMessage; -use malachite_round::state::State as RoundState; +use malachite_round::state::{State as RoundState, Step}; use malachite_vote::keeper::Message as VoteMessage; use malachite_vote::keeper::VoteKeeper; use malachite_vote::Threshold; @@ -131,8 +131,12 @@ where height: Ctx::Height, round: Round, ) -> Result>, Error> { - self.round_state = RoundState::new(height, round); - + if self.height() == &height { + // If it's a new round for same height, just reset the round, keep the valid and locked values + self.round_state.round = round; + } else { + self.round_state = RoundState::new(height, round); + } self.apply_event(round, RoundEvent::NewRound) } @@ -150,60 +154,89 @@ where validity: Validity, ) -> Result>, Error> { // Check that there is an ongoing round - if self.round_state.round == Round::NIL { + if self.round_state.round == Round::Nil { return Ok(None); } - // Only process the proposal if there is no other proposal - if self.round_state.proposal.is_some() { + // Check that the proposal is for the current height + if self.round_state.height != proposal.height() { return Ok(None); } - // Check that the proposal is for the current height and round - if self.round_state.height != proposal.height() - || self.round_state.round != proposal.round() - { - return Ok(None); + 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_event(proposal.round(), RoundEvent::InvalidProposal); + } else if polka_previous { + // L32 + return self.apply_event( + proposal.round(), + RoundEvent::InvalidProposalAndPolkaPrevious(proposal.clone()), + ); + } else { + return Ok(None); + } + } else { + return Ok(None); + } } - // TODO: Document - if proposal.pol_round().is_defined() && proposal.pol_round() >= self.round_state.round { - return 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_event( + proposal.round(), + RoundEvent::ProposalAndPrecommitValue(proposal.clone()), + ); } - // TODO: Verify proposal signature (make some of these checks part of message validation) - - match proposal.pol_round() { - Round::Nil => { - // Is it possible to get +2/3 prevotes before the proposal? - // Do we wait for our own prevote to check the threshold? - let round = proposal.round(); - let event = if validity.is_valid() { - RoundEvent::Proposal(proposal) - } else { - RoundEvent::ProposalInvalid - }; + // If the proposal is for a different round drop the proposal + // TODO - this check is also done in the round state machine, decide where to do it + if self.round_state.round != proposal.round() { + return Ok(None); + } - self.apply_event(round, event) - } - Round::Some(_) - if self.votes.is_threshold_met( - &proposal.pol_round(), - VoteType::Prevote, - Threshold::Value(proposal.value().id()), - ) => - { - let round = proposal.round(); - let event = if validity.is_valid() { - RoundEvent::Proposal(proposal) - } else { - RoundEvent::ProposalInvalid - }; + 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_event( + proposal.round(), + RoundEvent::ProposalAndPolkaCurrent(proposal.clone()), + ); + } - self.apply_event(round, event) - } - _ => Ok(None), + // L28 + if polka_previous { + return self.apply_event( + proposal.round(), + RoundEvent::ProposalAndPolkaPrevious(proposal.clone()), + ); } + + // TODO - Caller needs to store the proposal (valid or not) as the quorum (polka or commits) may be met later + self.apply_event(proposal.round(), RoundEvent::Proposal(proposal.clone())) } fn apply_vote( diff --git a/Code/round/src/events.rs b/Code/round/src/events.rs index 77c6fff40..7954ac59c 100644 --- a/Code/round/src/events.rs +++ b/Code/round/src/events.rs @@ -8,11 +8,12 @@ where NewRound, // Start a new round, either as proposer or not. L14/L20 ProposeValue(Ctx::Value), // Propose a value.L14 Proposal(Ctx::Proposal), // Receive a proposal. L22 + L23 (valid) + InvalidProposal, // Receive an invalid proposal. L26 + L32 (invalid) ProposalAndPolkaPrevious(Ctx::Proposal), // Recieved a proposal and a polka value from a previous round. L28 + L29 (valid) - ProposalInvalid, // Receive an invalid proposal. L26 + L32 (invalid) - PolkaValue(ValueId), // Receive +2/3 prevotes for valueId. L44 - PolkaAny, // Receive +2/3 prevotes for anything. L34 - PolkaNil, // Receive +2/3 prevotes for nil. L44 + InvalidProposalAndPolkaPrevious(Ctx::Proposal), // Recieved a proposal and a polka value from a previous round. L28 + L29 (invalid) + PolkaValue(ValueId), // Receive +2/3 prevotes for valueId. L44 + PolkaAny, // Receive +2/3 prevotes for anything. L34 + PolkaNil, // Receive +2/3 prevotes for nil. L44 ProposalAndPolkaCurrent(Ctx::Proposal), // Receive +2/3 prevotes for Value in current round. L36 PrecommitAny, // Receive +2/3 precommits for anything. L47 ProposalAndPrecommitValue(Ctx::Proposal), // Receive +2/3 precommits for Value. L49 diff --git a/Code/round/src/state.rs b/Code/round/src/state.rs index e2fb6f56e..200c70450 100644 --- a/Code/round/src/state.rs +++ b/Code/round/src/state.rs @@ -90,7 +90,7 @@ where Ctx: Context, { fn default() -> Self { - Self::new(Ctx::Height::default(), Round::NIL) + Self::new(Ctx::Height::default(), Round::Nil) } } diff --git a/Code/round/src/state_machine.rs b/Code/round/src/state_machine.rs index aa5bb1ec8..f525c1303 100644 --- a/Code/round/src/state_machine.rs +++ b/Code/round/src/state_machine.rs @@ -51,11 +51,7 @@ where /// Valid transitions result in at least a change to the state and/or an output message. /// /// Commented numbers refer to line numbers in the spec paper. -pub fn apply_event( - mut state: State, - info: &Info, - event: Event, -) -> Transition +pub fn apply_event(state: State, info: &Info, event: Event) -> Transition where Ctx: Context, { @@ -77,26 +73,25 @@ where propose(state, value) // L11/L14 } + // L22 with valid proposal (Step::Propose, Event::Proposal(proposal)) if this_round && proposal.pol_round().is_nil() => { - // L22 if state .locked .as_ref() .map_or(true, |locked| &locked.value == proposal.value()) { - state.proposal = Some(proposal.clone()); prevote(state, info.address, &proposal) } else { prevote_nil(state, info.address) } } + // L28 with valid proposal (Step::Propose, Event::ProposalAndPolkaPrevious(proposal)) if this_round && is_valid_pol_round(&state, proposal.pol_round()) => { - // L28 let Some(locked) = state.locked.as_ref() else { return prevote_nil(state, info.address); }; @@ -108,7 +103,14 @@ where } } - (Step::Propose, Event::ProposalInvalid) if this_round => prevote_nil(state, info.address), // L22/L25, L28/L31 + // L28 with invalid proposal + (Step::Propose, Event::InvalidProposalAndPolkaPrevious(proposal)) + if this_round && is_valid_pol_round(&state, proposal.pol_round()) => + { + prevote_nil(state, info.address) + } + + (Step::Propose, Event::InvalidProposal) if this_round => prevote_nil(state, info.address), // L22/L25, L28/L31 // We are the proposer. (Step::Propose, Event::TimeoutPropose) if this_round && info.is_proposer() => { @@ -128,7 +130,7 @@ where // From Precommit. Event must be for current round. (Step::Precommit, Event::ProposalAndPolkaCurrent(proposal)) if this_round => { - set_valid_value(state, proposal.value().clone()) // L36/L42 - NOTE: only once? + set_valid_value(state, &proposal) // L36/L42 - NOTE: only once? } // From Commit. No more state transitions. @@ -201,7 +203,7 @@ where /// /// Ref: L22/L28 pub fn prevote( - state: State, + mut state: State, address: &Ctx::Address, proposal: &Ctx::Proposal, ) -> Transition @@ -218,6 +220,7 @@ where }; let message = Message::prevote(state.height.clone(), state.round, value, address.clone()); + state.proposal = Some(proposal.clone()); Transition::to(state.with_step(Step::Prevote)).with_message(message) } @@ -345,22 +348,12 @@ where /// Ref: L36/L42 /// /// NOTE: only one of this and precommit should be called once in a round -pub fn set_valid_value(state: State, value: Ctx::Value) -> Transition +pub fn set_valid_value(mut state: State, proposal: &Ctx::Proposal) -> Transition where Ctx: Context, { - // Check that we're locked on this value - let Some(locked) = state.locked.as_ref() else { - // TODO: Add logging - return Transition::invalid(state); - }; - - if locked.value.id() != value.id() { - // TODO: Add logging - return Transition::invalid(state); - } - - Transition::to(state.clone().set_valid(locked.value.clone())) + state.proposal = Some(proposal.clone()); + Transition::to(state.clone().set_valid(proposal.value().clone())) } //--------------------------------------------------------------------- @@ -375,7 +368,13 @@ pub fn round_skip(state: State, round: Round) -> Transition where Ctx: Context, { - Transition::to(State::new(state.height.clone(), round)).with_message(Message::NewRound(round)) + let new_state = State { + round, + step: Step::NewRound, + ..state + }; + + Transition::to(new_state).with_message(Message::NewRound(round)) } /// We received +2/3 precommits for a value - commit and decide that value! @@ -385,17 +384,6 @@ pub fn commit(state: State, round: Round, proposal: Ctx::Proposal) -> where Ctx: Context, { - // Check that we're locked on this value - let Some(locked) = state.locked.as_ref() else { - // TODO: Add logging - return Transition::invalid(state); - }; - - if locked.value.id() != proposal.value().id() { - // TODO: Add logging - return Transition::invalid(state); - } - - let message = Message::decision(round, locked.value.clone()); + let message = Message::decision(round, proposal.value().clone()); Transition::to(state.with_step(Step::Commit)).with_message(message) } diff --git a/Code/test/src/lib.rs b/Code/test/src/lib.rs index 181ccd7ce..6c24bcd72 100644 --- a/Code/test/src/lib.rs +++ b/Code/test/src/lib.rs @@ -10,6 +10,8 @@ mod validator_set; mod value; mod vote; +pub mod utils; + pub use crate::context::*; pub use crate::height::*; pub use crate::proposal::*; diff --git a/Code/test/src/utils.rs b/Code/test/src/utils.rs new file mode 100644 index 000000000..e94d9458b --- /dev/null +++ b/Code/test/src/utils.rs @@ -0,0 +1,386 @@ +use rand::rngs::StdRng; +use rand::SeedableRng; + +use malachite_common::{Round, Timeout, VotingPower}; +use malachite_driver::{Event, Message, ProposerSelector, Validity}; +use malachite_round::state::{RoundValue, State, Step}; + +use crate::{ + Address, Height, PrivateKey, Proposal, TestContext, Validator, ValidatorSet, Value, Vote, +}; + +#[derive(Copy, Clone, Debug, Default)] +pub struct RotateProposer; + +impl ProposerSelector for RotateProposer { + fn select_proposer(&self, round: Round, validator_set: &ValidatorSet) -> Address { + let proposer_index = round.as_i64() as usize % validator_set.validators.len(); + validator_set.validators[proposer_index].address + } +} + +#[derive(Copy, Clone, Debug)] +pub struct FixedProposer { + proposer: Address, +} + +impl FixedProposer { + pub fn new(proposer: Address) -> Self { + Self { proposer } + } +} + +impl ProposerSelector for FixedProposer { + fn select_proposer(&self, _round: Round, _validator_set: &ValidatorSet) -> Address { + self.proposer + } +} + +pub fn make_validators( + voting_powers: [VotingPower; N], +) -> [(Validator, PrivateKey); N] { + let mut rng = StdRng::seed_from_u64(0x42); + + let mut validators = Vec::with_capacity(N); + + for vp in voting_powers { + let sk = PrivateKey::generate(&mut rng); + let val = Validator::new(sk.public_key(), vp); + validators.push((val, sk)); + } + + validators.try_into().expect("N validators") +} + +pub fn new_round_event(round: Round) -> Event { + Event::NewRound(Height::new(1), round) +} + +pub fn new_round_msg(round: Round) -> Option> { + Some(Message::NewRound(Height::new(1), round)) +} + +pub fn proposal_msg( + round: Round, + value: Value, + locked_round: Round, +) -> Option> { + let proposal = Proposal::new(Height::new(1), round, value, locked_round); + Some(Message::Propose(proposal)) +} + +pub fn proposal_event( + round: Round, + value: Value, + locked_round: Round, + validity: Validity, +) -> Event { + let proposal = Proposal::new(Height::new(1), round, value, locked_round); + Event::Proposal(proposal, validity) +} + +pub fn prevote_msg(round: Round, addr: &Address, sk: &PrivateKey) -> Option> { + let value = Value::new(9999); + + Some(Message::Vote( + Vote::new_prevote(Height::new(1), round, Some(value.id()), *addr).signed(sk), + )) +} + +pub fn prevote_nil_msg( + round: Round, + addr: &Address, + sk: &PrivateKey, +) -> Option> { + Some(Message::Vote( + Vote::new_prevote(Height::new(1), round, None, *addr).signed(sk), + )) +} + +pub fn prevote_event(addr: &Address, sk: &PrivateKey) -> Event { + let value = Value::new(9999); + + Event::Vote( + Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), *addr).signed(sk), + ) +} + +pub fn prevote_event_at(round: Round, addr: &Address, sk: &PrivateKey) -> Event { + let value = Value::new(9999); + + Event::Vote(Vote::new_prevote(Height::new(1), round, Some(value.id()), *addr).signed(sk)) +} + +pub fn precommit_msg( + round: Round, + value: Value, + addr: &Address, + sk: &PrivateKey, +) -> Option> { + Some(Message::Vote( + Vote::new_precommit(Height::new(1), round, Some(value.id()), *addr).signed(sk), + )) +} + +pub fn precommit_nil_msg(addr: &Address, sk: &PrivateKey) -> Option> { + Some(Message::Vote( + Vote::new_precommit(Height::new(1), Round::new(0), None, *addr).signed(sk), + )) +} + +pub fn precommit_event( + round: Round, + value: Value, + addr: &Address, + sk: &PrivateKey, +) -> Event { + Event::Vote(Vote::new_precommit(Height::new(1), round, Some(value.id()), *addr).signed(sk)) +} + +pub fn decide_message(round: Round, value: Value) -> Option> { + Some(Message::Decide(round, value)) +} + +pub fn start_propose_timer_msg(round: Round) -> Option> { + Some(Message::ScheduleTimeout(Timeout::propose(round))) +} + +pub fn timeout_propose_event(round: Round) -> Event { + Event::TimeoutElapsed(Timeout::propose(round)) +} + +pub fn start_prevote_timer_msg(round: Round) -> Option> { + Some(Message::ScheduleTimeout(Timeout::prevote(round))) +} + +pub fn timeout_prevote_event(round: Round) -> Event { + Event::TimeoutElapsed(Timeout::prevote(round)) +} + +pub fn start_precommit_timer_msg(round: Round) -> Option> { + Some(Message::ScheduleTimeout(Timeout::precommit(round))) +} + +pub fn timeout_precommit_event(round: Round) -> Event { + Event::TimeoutElapsed(Timeout::precommit(round)) +} + +pub fn propose_state(round: Round) -> State { + State { + height: Height::new(1), + round, + step: Step::Propose, + proposal: None, + locked: None, + valid: None, + } +} + +pub fn propose_state_with_proposal_and_valid( + state_round: Round, + valid_round: Round, + proposal: Proposal, +) -> State { + // TODO - set_valid doesn't work because the valid round is set to state round + // we need to set it to something different. + // propose_state(round) + // .set_proposal(proposal.clone()) + // .set_valid(proposal.value) + State { + height: Height::new(1), + round: state_round, + step: Step::Propose, + proposal: Some(proposal.clone()), + valid: Some(RoundValue { + value: proposal.clone().value, + round: valid_round, + }), + locked: None, + } +} + +pub fn propose_state_with_proposal_and_locked_and_valid( + round: Round, + proposal: Proposal, +) -> State { + State { + height: Height::new(1), + round, + step: Step::Propose, + proposal: Some(proposal.clone()), + valid: Some(RoundValue { + value: proposal.clone().value, + round: Round::new(0), + }), + locked: Some(RoundValue { + value: proposal.clone().value, + round: Round::new(0), + }), + } +} + +pub fn prevote_state(round: Round) -> State { + State { + height: Height::new(1), + round, + step: Step::Prevote, + proposal: None, + locked: None, + valid: None, + } +} + +pub fn prevote_state_with_proposal(round: Round, proposal: Proposal) -> State { + State { + height: Height::new(1), + round, + step: Step::Prevote, + proposal: Some(proposal.clone()), + valid: None, + locked: None, + } +} + +pub fn prevote_state_with_proposal_and_valid( + state_round: Round, + valid_round: Round, + proposal: Proposal, +) -> State { + State { + height: Height::new(1), + round: state_round, + step: Step::Prevote, + proposal: Some(proposal.clone()), + valid: Some(RoundValue { + value: proposal.clone().value, + round: valid_round, + }), + locked: None, + } +} + +pub fn prevote_state_with_proposal_and_locked_and_valid( + round: Round, + proposal: Proposal, +) -> State { + State { + height: Height::new(1), + round, + step: Step::Prevote, + proposal: Some(proposal.clone()), + valid: Some(RoundValue { + value: proposal.clone().value, + round: Round::new(0), + }), + locked: Some(RoundValue { + value: proposal.clone().value, + round: Round::new(0), + }), + } +} + +pub fn precommit_state_with_proposal_and_locked_and_valid( + round: Round, + proposal: Proposal, +) -> State { + State { + height: Height::new(1), + round, + step: Step::Precommit, + proposal: Some(proposal.clone()), + valid: Some(RoundValue { + value: proposal.clone().value, + round: Round::new(0), + }), + locked: Some(RoundValue { + value: proposal.clone().value, + round: Round::new(0), + }), + } +} + +pub fn precommit_state(round: Round) -> State { + State { + height: Height::new(1), + round, + step: Step::Precommit, + proposal: None, + locked: None, + valid: None, + } +} + +pub fn precommit_state_with_proposal_and_valid( + state_round: Round, + valid_round: Round, + proposal: Proposal, +) -> State { + State { + height: Height::new(1), + round: state_round, + step: Step::Precommit, + proposal: Some(proposal.clone()), + valid: Some(RoundValue { + value: proposal.clone().value, + round: valid_round, + }), + locked: None, + } +} + +pub fn new_round(round: Round) -> State { + State { + height: Height::new(1), + round, + step: Step::NewRound, + proposal: None, + valid: None, + locked: None, + } +} + +pub fn new_round_with_proposal_and_valid(round: Round, proposal: Proposal) -> State { + State { + height: Height::new(1), + round, + step: Step::NewRound, + proposal: Some(proposal.clone()), + valid: Some(RoundValue { + value: proposal.clone().value, + round: Round::new(0), + }), + locked: None, + } +} + +pub fn new_round_with_proposal_and_locked_and_valid( + round: Round, + proposal: Proposal, +) -> State { + State { + height: Height::new(1), + round, + step: Step::NewRound, + proposal: Some(proposal.clone()), + valid: Some(RoundValue { + value: proposal.clone().value, + round: Round::new(0), + }), + locked: Some(RoundValue { + value: proposal.clone().value, + round: Round::new(0), + }), + } +} + +pub fn decided_state(round: Round, _value: Value) -> State { + State { + // TODO add decided, remove proposal + height: Height::new(1), + round, + step: Step::Commit, + proposal: None, + valid: None, + locked: None, + } +} diff --git a/Code/test/tests/driver.rs b/Code/test/tests/driver.rs index 40396e986..c230b4350 100644 --- a/Code/test/tests/driver.rs +++ b/Code/test/tests/driver.rs @@ -1,15 +1,12 @@ use futures::executor::block_on; -use rand::rngs::StdRng; -use rand::SeedableRng; +use malachite_test::utils::{make_validators, FixedProposer, RotateProposer}; use malachite_common::{Round, Timeout, TimeoutStep}; -use malachite_driver::{Driver, Error, Event, Message, ProposerSelector, Validity}; +use malachite_driver::{Driver, Error, Event, Message, Validity}; use malachite_round::state::{RoundValue, State, Step}; -use malachite_test::{ - Address, Height, PrivateKey, Proposal, TestContext, Validator, ValidatorSet, Value, Vote, -}; +use malachite_test::{Height, Proposal, TestContext, ValidatorSet, Value, Vote}; -struct TestStep { +pub struct TestStep { desc: &'static str, input_event: Option>, expected_output: Option>, @@ -17,7 +14,7 @@ struct TestStep { new_state: State, } -fn to_input_msg(output: Message) -> Option> { +pub fn msg_to_event(output: Message) -> Option> { match output { Message::NewRound(height, round) => Some(Event::NewRound(height, round)), // Let's consider our own proposal to always be valid @@ -29,52 +26,12 @@ fn to_input_msg(output: Message) -> Option> { } } -#[derive(Copy, Clone, Debug, Default)] -pub struct RotateProposer; - -impl ProposerSelector for RotateProposer { - fn select_proposer(&self, round: Round, validator_set: &ValidatorSet) -> Address { - let proposer_index = round.as_i64() as usize % validator_set.validators.len(); - validator_set.validators[proposer_index].address - } -} - -#[derive(Copy, Clone, Debug)] -pub struct FixedProposer { - proposer: Address, -} - -impl FixedProposer { - pub fn new(proposer: Address) -> Self { - Self { proposer } - } -} - -impl ProposerSelector for FixedProposer { - fn select_proposer(&self, _round: Round, _validator_set: &ValidatorSet) -> Address { - self.proposer - } -} - #[test] fn driver_steps_proposer() { let value = Value::new(9999); - 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(), 3); - - let (my_sk, my_addr) = (sk1, addr1); + let [(v1, sk1), (v2, sk2), (v3, sk3)] = make_validators([1, 2, 3]); + let (my_sk, my_addr) = (sk1, v1.address); let ctx = TestContext::new(my_sk.clone()); let sel = FixedProposer::new(my_addr); @@ -150,7 +107,7 @@ fn driver_steps_proposer() { TestStep { desc: "v2 prevotes for our proposal", input_event: Some(Event::Vote( - Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), addr2) + Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), v2.address) .signed(&sk2), )), expected_output: None, @@ -167,7 +124,7 @@ 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(Height::new(1), Round::new(0), Some(value.id()), addr3) + Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), v3.address) .signed(&sk3), )), expected_output: Some(Message::Vote( @@ -213,7 +170,7 @@ fn driver_steps_proposer() { TestStep { desc: "v2 precommits for our proposal", input_event: Some(Event::Vote( - Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), addr2) + Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), v2.address) .signed(&sk2), )), expected_output: None, @@ -236,7 +193,7 @@ 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(Height::new(1), Round::new(0), Some(value.id()), addr3) + Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), v3.address) .signed(&sk3), )), expected_output: Some(Message::Decide(Round::new(0), value)), @@ -258,16 +215,16 @@ fn driver_steps_proposer() { }, ]; - let mut previous_message = None; + let mut event_from_previous_msg = None; for step in steps { println!("Step: {}", step.desc); - let execute_message = step + let execute_event = step .input_event - .unwrap_or_else(|| previous_message.unwrap()); + .unwrap_or_else(|| event_from_previous_msg.unwrap()); - let output = block_on(driver.execute(execute_message)).expect("execute succeeded"); + let output = block_on(driver.execute(execute_event)).expect("execute succeeded"); assert_eq!(output, step.expected_output, "expected output message"); assert_eq!( @@ -277,25 +234,14 @@ fn driver_steps_proposer() { assert_eq!(driver.round_state, step.new_state, "expected state"); - previous_message = output.and_then(to_input_msg); + event_from_previous_msg = output.and_then(msg_to_event); } } #[test] fn driver_steps_proposer_timeout_get_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 v1 = Validator::new(sk1.public_key(), 1); - let v2 = Validator::new(sk2.public_key(), 2); - let v3 = Validator::new(sk3.public_key(), 3); - - let (my_sk, my_addr) = (sk1, addr1); + let [(v1, sk1), (v2, _sk2), (v3, _sk3)] = make_validators([1, 2, 3]); + let (my_sk, my_addr) = (sk1, v1.address); let ctx = TestContext::new(my_sk.clone()); let sel = FixedProposer::new(my_addr); @@ -339,16 +285,16 @@ fn driver_steps_proposer_timeout_get_value() { }, ]; - let mut previous_message = None; + let mut event_from_previous_msg = None; for step in steps { println!("Step: {}", step.desc); - let execute_message = step + let execute_event = step .input_event - .unwrap_or_else(|| previous_message.unwrap()); + .unwrap_or_else(|| event_from_previous_msg.unwrap()); - let output = block_on(driver.execute(execute_message)).expect("execute succeeded"); + let output = block_on(driver.execute(execute_event)).expect("execute succeeded"); assert_eq!(output, step.expected_output, "expected output message"); assert_eq!( @@ -358,7 +304,7 @@ fn driver_steps_proposer_timeout_get_value() { assert_eq!(driver.round_state, step.new_state, "expected state"); - previous_message = output.and_then(to_input_msg); + event_from_previous_msg = output.and_then(msg_to_event); } } @@ -366,25 +312,13 @@ fn driver_steps_proposer_timeout_get_value() { fn driver_steps_not_proposer_valid() { let value = Value::new(9999); - 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(), 3); + let [(v1, sk1), (v2, sk2), (v3, sk3)] = make_validators([1, 2, 3]); // Proposer is v1, so we are not the proposer - let (my_sk, my_addr) = (sk2, addr2); + let (my_sk, my_addr) = (sk2, v2.address); let ctx = TestContext::new(my_sk.clone()); - let sel = FixedProposer::new(addr1); + let sel = FixedProposer::new(v1.address); let vs = ValidatorSet::new(vec![v1.clone(), v2.clone(), v3.clone()]); let mut driver = Driver::new(ctx, sel, vs, my_addr); @@ -440,7 +374,7 @@ fn driver_steps_not_proposer_valid() { TestStep { desc: "v1 prevotes for its own proposal", input_event: Some(Event::Vote( - Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), addr1) + Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), v1.address) .signed(&sk1), )), expected_output: None, @@ -457,7 +391,7 @@ 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(Height::new(1), Round::new(0), Some(value.id()), addr3) + Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), v3.address) .signed(&sk3), )), expected_output: Some(Message::Vote( @@ -503,7 +437,7 @@ fn driver_steps_not_proposer_valid() { TestStep { desc: "v1 precommits its proposal", input_event: Some(Event::Vote( - Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), addr1) + Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), v1.address) .signed(&sk1), )), expected_output: None, @@ -526,7 +460,7 @@ 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(Height::new(1), Round::new(0), Some(value.id()), addr3) + Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), v3.address) .signed(&sk3), )), expected_output: Some(Message::Decide(Round::new(0), value)), @@ -548,16 +482,16 @@ fn driver_steps_not_proposer_valid() { }, ]; - let mut previous_message = None; + let mut event_from_previous_msg = None; for step in steps { println!("Step: {}", step.desc); - let execute_message = step + let execute_event = step .input_event - .unwrap_or_else(|| previous_message.unwrap()); + .unwrap_or_else(|| event_from_previous_msg.unwrap()); - let output = block_on(driver.execute(execute_message)).expect("execute succeeded"); + let output = block_on(driver.execute(execute_event)).expect("execute succeeded"); assert_eq!(output, step.expected_output, "expected output message"); assert_eq!( @@ -567,7 +501,7 @@ fn driver_steps_not_proposer_valid() { assert_eq!(driver.round_state, step.new_state, "expected state"); - previous_message = output.and_then(to_input_msg); + event_from_previous_msg = output.and_then(msg_to_event); } } @@ -575,25 +509,13 @@ fn driver_steps_not_proposer_valid() { fn driver_steps_not_proposer_invalid() { let value = Value::new(9999); - 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(), 3); + let [(v1, sk1), (v2, sk2), (v3, sk3)] = make_validators([1, 2, 3]); // Proposer is v1, so we are not the proposer - let (my_sk, my_addr) = (sk2, addr2); + let (my_sk, my_addr) = (sk2, v2.address); let ctx = TestContext::new(my_sk.clone()); - let sel = FixedProposer::new(addr1); + let sel = FixedProposer::new(v1.address); let vs = ValidatorSet::new(vec![v1.clone(), v2.clone(), v3.clone()]); let mut driver = Driver::new(ctx, sel, vs, my_addr); @@ -648,7 +570,7 @@ fn driver_steps_not_proposer_invalid() { TestStep { desc: "v1 prevotes for its own proposal", input_event: Some(Event::Vote( - Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), addr1).signed(&sk1), + Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), v1.address).signed(&sk1), )), expected_output: None, expected_round: Round::new(0), @@ -664,7 +586,7 @@ 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(Height::new(1), Round::new(0), Some(value.id()), addr3).signed(&sk3), + Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), v3.address).signed(&sk3), )), expected_output: Some(Message::ScheduleTimeout(Timeout::prevote(Round::new(0)))), expected_round: Round::new(0), @@ -695,16 +617,16 @@ fn driver_steps_not_proposer_invalid() { }, ]; - let mut previous_message = None; + let mut event_from_previous_msg = None; for step in steps { println!("Step: {}", step.desc); - let execute_message = step + let execute_event = step .input_event - .unwrap_or_else(|| previous_message.unwrap()); + .unwrap_or_else(|| event_from_previous_msg.unwrap()); - let output = block_on(driver.execute(execute_message)).expect("execute succeeded"); + let output = block_on(driver.execute(execute_event)).expect("execute succeeded"); assert_eq!(output, step.expected_output, "expected output"); assert_eq!( @@ -714,33 +636,165 @@ fn driver_steps_not_proposer_invalid() { assert_eq!(driver.round_state, step.new_state, "expected state"); - previous_message = output.and_then(to_input_msg); + event_from_previous_msg = output.and_then(msg_to_event); } } #[test] -fn driver_steps_not_proposer_timeout_multiple_rounds() { +fn driver_steps_not_proposer_other_height() { + let value = Value::new(9999); + + let [(v1, _sk1), (v2, sk2)] = make_validators([1, 2]); + + // Proposer is v1, so we are not the proposer + let (my_sk, my_addr) = (sk2, v2.address); + + let ctx = TestContext::new(my_sk.clone()); + let sel = FixedProposer::new(v1.address); + let vs = ValidatorSet::new(vec![v1.clone(), v2.clone()]); + + let mut driver = Driver::new(ctx, sel, vs, my_addr); + + // Proposal is for another height + let proposal = Proposal::new(Height::new(2), 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(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, + locked: None, + valid: None, + }, + }, + TestStep { + desc: "Receive a proposal for another height, ignore it (v2)", + input_event: Some(Event::Proposal(proposal.clone(), Validity::Invalid)), + expected_output: None, + expected_round: Round::new(0), + new_state: State { + height: Height::new(1), + round: Round::new(0), + step: Step::Propose, + proposal: None, + locked: None, + valid: None, + }, + }, + ]; + + let mut event_from_previous_msg = None; + + for step in steps { + println!("Step: {}", step.desc); + + let execute_event = step + .input_event + .unwrap_or_else(|| event_from_previous_msg.unwrap()); + + let output = block_on(driver.execute(execute_event)).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"); + + event_from_previous_msg = output.and_then(msg_to_event); + } +} + +#[test] +fn driver_steps_not_proposer_other_round() { let value = Value::new(9999); - let mut rng = StdRng::seed_from_u64(0x42); + let [(v1, _sk1), (v2, sk2)] = make_validators([1, 2]); - let sk1 = PrivateKey::generate(&mut rng); - let sk2 = PrivateKey::generate(&mut rng); - let sk3 = PrivateKey::generate(&mut rng); + // Proposer is v1, so we are not the proposer + let (my_sk, my_addr) = (sk2, v2.address); - 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 ctx = TestContext::new(my_sk.clone()); + let sel = FixedProposer::new(v1.address); + let vs = ValidatorSet::new(vec![v1.clone(), v2.clone()]); - let v1 = Validator::new(sk1.public_key(), 1); - let v2 = Validator::new(sk2.public_key(), 3); - let v3 = Validator::new(sk3.public_key(), 1); + let mut driver = Driver::new(ctx, sel, vs, my_addr); + + // Proposal is for another round + let proposal = Proposal::new(Height::new(1), Round::new(1), value, Round::new(-1)); + + let steps = vec![ + TestStep { + desc: "Start round 0, we are not the proposer", + 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, + locked: None, + valid: None, + }, + }, + TestStep { + desc: "Receive a proposal for another round, ignore it (v2)", + input_event: Some(Event::Proposal(proposal.clone(), Validity::Invalid)), + expected_output: None, + expected_round: Round::new(0), + new_state: State { + height: Height::new(1), + round: Round::new(0), + step: Step::Propose, + proposal: None, + locked: None, + valid: None, + }, + }, + ]; + + let mut event_from_previous_msg = None; + + for step in steps { + println!("Step: {}", step.desc); + + let execute_event = step + .input_event + .unwrap_or_else(|| event_from_previous_msg.unwrap()); + + let output = block_on(driver.execute(execute_event)).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"); + + event_from_previous_msg = output.and_then(msg_to_event); + } +} + +#[test] +fn driver_steps_not_proposer_timeout_multiple_rounds() { + let value = Value::new(9999); + + let [(v1, sk1), (v2, sk2), (v3, sk3)] = make_validators([1, 3, 1]); // Proposer is v1, so we, v3, are not the proposer - let (my_sk, my_addr) = (sk3, addr3); + let (my_sk, my_addr) = (sk3, v3.address); let ctx = TestContext::new(my_sk.clone()); - let sel = FixedProposer::new(addr1); + let sel = FixedProposer::new(v1.address); let vs = ValidatorSet::new(vec![v1.clone(), v2.clone(), v3.clone()]); let mut driver = Driver::new(ctx, sel, vs, my_addr); @@ -797,7 +851,7 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { TestStep { desc: "v1 prevotes for its own proposal", input_event: Some(Event::Vote( - Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), addr1) + Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), v1.address) .signed(&sk1), )), expected_output: None, @@ -815,7 +869,7 @@ 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(Height::new(1), Round::new(0), None, addr2).signed(&sk2), + Vote::new_prevote(Height::new(1), Round::new(0), None, v2.address).signed(&sk2), )), expected_output: Some(Message::Vote( Vote::new_precommit(Height::new(1), Round::new(0), None, my_addr).signed(&my_sk), @@ -849,7 +903,7 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { TestStep { desc: "v1 precommits its proposal", input_event: Some(Event::Vote( - Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), addr1) + Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), v1.address) .signed(&sk1), )), expected_output: None, @@ -867,7 +921,7 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { TestStep { desc: "v2 precommits for nil", input_event: Some(Event::Vote( - Vote::new_precommit(Height::new(1), Round::new(0), None, addr2).signed(&sk2), + Vote::new_precommit(Height::new(1), Round::new(0), None, v2.address).signed(&sk2), )), expected_output: Some(Message::ScheduleTimeout(Timeout::precommit(Round::new(0)))), expected_round: Round::new(0), @@ -911,38 +965,28 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { }, ]; - let mut previous_message = None; + let mut event_from_previous_msg = None; for step in steps { println!("Step: {}", step.desc); - let execute_message = step + let execute_event = step .input_event - .unwrap_or_else(|| previous_message.unwrap()); + .unwrap_or_else(|| event_from_previous_msg.unwrap()); - let output = block_on(driver.execute(execute_message)).expect("execute succeeded"); + let output = block_on(driver.execute(execute_event)).expect("execute succeeded"); assert_eq!(output, step.expected_output, "expected output message"); assert_eq!(driver.round_state, step.new_state, "new state"); - previous_message = output.and_then(to_input_msg); + event_from_previous_msg = output.and_then(msg_to_event); } } +// No value to propose #[test] fn driver_steps_no_value_to_propose() { - // No value to propose - - 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 v1 = Validator::new(sk1.public_key(), 1); - let v2 = Validator::new(sk2.public_key(), 2); - let v3 = Validator::new(sk3.public_key(), 3); - + let [(v1, sk1), (v2, _sk2), (v3, _sk3)] = make_validators([1, 2, 3]); let (my_sk, my_addr) = (sk1, v1.address); let ctx = TestContext::new(my_sk.clone()); @@ -966,19 +1010,10 @@ fn driver_steps_no_value_to_propose() { #[test] fn driver_steps_proposer_not_found() { - 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 addr2 = Address::from_public_key(&sk2.public_key()); + let [(v1, _sk1), (v2, sk2), (v3, _sk3)] = make_validators([1, 2, 3]); - let v1 = Validator::new(sk1.public_key(), 1); - let v2 = Validator::new(sk2.public_key(), 2); - let v3 = Validator::new(sk3.public_key(), 3); + let (my_sk, my_addr) = (sk2, v2.address); - let (my_sk, my_addr) = (sk2, addr2); let ctx = TestContext::new(my_sk.clone()); // Proposer is v1, which is not in the validator set @@ -995,15 +1030,7 @@ fn driver_steps_proposer_not_found() { fn driver_steps_validator_not_found() { let value = Value::new(9999); - 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 v1 = Validator::new(sk1.public_key(), 1); - let v2 = Validator::new(sk2.public_key(), 2); - let v3 = Validator::new(sk3.public_key(), 3); + let [(v1, _sk1), (v2, sk2), (v3, sk3)] = make_validators([1, 2, 3]); let (my_sk, my_addr) = (sk3.clone(), v3.address); let ctx = TestContext::new(my_sk.clone()); @@ -1031,15 +1058,7 @@ fn driver_steps_validator_not_found() { fn driver_steps_invalid_signature() { let value = Value::new(9999); - 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 v1 = Validator::new(sk1.public_key(), 1); - let v2 = Validator::new(sk2.public_key(), 2); - let v3 = Validator::new(sk3.public_key(), 3); + let [(v1, sk1), (v2, _sk2), (v3, sk3)] = make_validators([1, 2, 3]); let (my_sk, my_addr) = (sk3.clone(), v3.address); let ctx = TestContext::new(my_sk.clone()); @@ -1066,24 +1085,12 @@ fn driver_steps_invalid_signature() { fn driver_steps_skip_round_skip_threshold() { let value = Value::new(9999); - let sel = RotateProposer::default(); - - let mut rng = StdRng::seed_from_u64(0x42); + let sel = RotateProposer; - 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); + let [(v1, sk1), (v2, sk2), (v3, sk3)] = make_validators([1, 1, 1]); // Proposer is v1, so we, v3, are not the proposer - let (my_sk, my_addr) = (sk3, addr3); + let (my_sk, my_addr) = (sk3, v3.address); let ctx = TestContext::new(my_sk.clone()); let height = Height::new(1); @@ -1143,7 +1150,7 @@ fn driver_steps_skip_round_skip_threshold() { 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), + Vote::new_prevote(height, Round::new(1), Some(value.id()), v1.address).signed(&sk1), )), expected_output: None, expected_round: Round::new(0), @@ -1160,7 +1167,7 @@ fn driver_steps_skip_round_skip_threshold() { 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), + Vote::new_prevote(height, Round::new(1), Some(value.id()), v2.address).signed(&sk2), )), expected_output: Some(Message::NewRound(height, Round::new(1))), expected_round: Round::new(1), @@ -1175,22 +1182,22 @@ fn driver_steps_skip_round_skip_threshold() { }, ]; - let mut previous_message = None; + let mut event_from_previous_msg = None; for step in steps { println!("Step: {}", step.desc); - let execute_message = step + let execute_event = step .input_event - .unwrap_or_else(|| previous_message.unwrap()); + .unwrap_or_else(|| event_from_previous_msg.unwrap()); - let output = block_on(driver.execute(execute_message)).expect("execute succeeded"); + let output = block_on(driver.execute(execute_event)).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); + event_from_previous_msg = output.and_then(msg_to_event); } } @@ -1198,24 +1205,12 @@ fn driver_steps_skip_round_skip_threshold() { fn driver_steps_skip_round_quorum_threshold() { let value = Value::new(9999); - let sel = RotateProposer::default(); - - 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 sel = RotateProposer; - let v1 = Validator::new(sk1.public_key(), 1); - let v2 = Validator::new(sk2.public_key(), 2); - let v3 = Validator::new(sk3.public_key(), 1); + let [(v1, sk1), (v2, sk2), (v3, sk3)] = make_validators([1, 2, 1]); // Proposer is v1, so we, v3, are not the proposer - let (my_sk, my_addr) = (sk3, addr3); + let (my_sk, my_addr) = (sk3, v3.address); let ctx = TestContext::new(my_sk.clone()); let height = Height::new(1); @@ -1275,7 +1270,7 @@ fn driver_steps_skip_round_quorum_threshold() { 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), + Vote::new_prevote(height, Round::new(1), Some(value.id()), v1.address).signed(&sk1), )), expected_output: None, expected_round: Round::new(0), @@ -1292,7 +1287,7 @@ fn driver_steps_skip_round_quorum_threshold() { 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), + Vote::new_prevote(height, Round::new(1), Some(value.id()), v2.address).signed(&sk2), )), expected_output: Some(Message::NewRound(height, Round::new(1))), expected_round: Round::new(1), @@ -1307,22 +1302,22 @@ fn driver_steps_skip_round_quorum_threshold() { }, ]; - let mut previous_message = None; + let mut event_from_previous_msg = None; for step in steps { println!("Step: {}", step.desc); - let execute_message = step + let execute_event = step .input_event - .unwrap_or_else(|| previous_message.unwrap()); + .unwrap_or_else(|| event_from_previous_msg.unwrap()); - let output = block_on(driver.execute(execute_message)).expect("execute succeeded"); + let output = block_on(driver.execute(execute_event)).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); + event_from_previous_msg = output.and_then(msg_to_event); } } diff --git a/Code/test/tests/driver_extra.rs b/Code/test/tests/driver_extra.rs new file mode 100644 index 000000000..cadbb145f --- /dev/null +++ b/Code/test/tests/driver_extra.rs @@ -0,0 +1,534 @@ +use futures::executor::block_on; + +use malachite_common::Round; +use malachite_driver::{Driver, Event, Message, ProposerSelector, Validity}; +use malachite_round::state::State; + +use malachite_test::{Height, Proposal, TestContext, ValidatorSet, Value}; + +use malachite_test::utils::*; + +// TODO - move all below to utils? +struct TestStep { + desc: &'static str, + input_event: Event, + expected_output: Option>, + expected_round: Round, + new_state: State, +} + +pub fn msg_to_event(output: Message) -> Option> { + match output { + Message::NewRound(height, round) => Some(Event::NewRound(height, round)), + // Let's consider our own proposal to always be valid + Message::Propose(p) => Some(Event::Proposal(p, Validity::Valid)), + Message::Vote(v) => Some(Event::Vote(v)), + Message::Decide(_, _) => None, + Message::ScheduleTimeout(_) => None, + Message::GetValueAndScheduleTimeout(_, _) => None, + } +} + +// Arrive at L49 with commits from current rounds, no locked value, no valid value +// +// Ev: NewRound Proposal +// State: NewRound -------------------> Propose --------------------> Propose -------> Commit +// Msg: start_propose_timer start_precommit_timer decide +// Alg: L21 L48 L49 +// +// v1=2, v2=3, v3=2, we are v3 +// +// L21 - v3 is not proposer starts propose timer (step propose) +// L46 - v3 gets +2/3 precommits (from v1 and v2), starts precommit timer (step propose) +// L49 - v3 receives proposal and has already +2/3 precommit(id(v), round=0) (step decided) +#[test] +fn driver_steps_decide_current_with_no_locked_no_valid() { + 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, v2, are not the proposer, start timeout propose", + input_event: new_round_event(Round::new(0)), + expected_output: start_propose_timer_msg(Round::new(0)), + expected_round: Round::new(0), + new_state: propose_state(Round::new(0)), + }, + TestStep { + desc: "v1 precommits a proposal", + input_event: precommit_event(Round::new(0), value, &v1.address, &sk1), + expected_output: None, + expected_round: Round::new(0), + new_state: propose_state(Round::new(0)), + }, + TestStep { + desc: "v2 precommits for same proposal, we get +2/3 precommit, start precommit timer", + input_event: precommit_event(Round::new(0), value, &v2.address, &sk2), + expected_output: start_precommit_timer_msg(Round::new(0)), + expected_round: Round::new(0), + new_state: propose_state(Round::new(0)), + }, + TestStep { + desc: "Receive proposal", + input_event: proposal_event(Round::new(0), value, Round::Nil, Validity::Valid), + expected_output: decide_message(Round::new(0), value), + expected_round: Round::new(0), + new_state: decided_state( + Round::new(0), + // Proposal::new(Height::new(1), Round::new(1), value, Round::new(0)), <--- TODO - should be this? + value, + ), + }, + ]; + + run_steps(&mut driver, steps) +} + +// Arrive at L49 with commits from previous rounds, no locked value, no valid value +// +// Ev: NewRound(0) Timeout(propose) Timeout(prevote) +// State: NewRound ------------> Propose ----------------> Prevote ------------> Prevote ---------------> Precommit --> +// 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 +// +// v1=2, v2=3, v3=2, we are v3 +// L21 - v3 is not proposer starts propose timer (step propose) +// L57 - v3 receives timeout propose, prevote for nil (step prevote) +// L34 - v3 gets +2/3 prevotes (from v1 and v2), starts prevote timer (step prevote) +// L61 - v3 receives timeout prevote, precommit nil (step precommit) +// L46 - v3 gets +2/3 precommits (from v1 and v2), starts precommit timer (step precommit) +// L65 - v3 receives timeout precommit, starts new round (step new_round) +// L21 - v3 receives new round, is not the proposer, starts propose timer +// L49 - v3 receives proposal(v, round=0) and has already +2/3 precommit(id(v), round=0) (step decided) +#[test] +fn driver_steps_decide_previous_with_no_locked_no_valid() { + 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, v2, are not the proposer, start timeout propose", + input_event: new_round_event(Round::new(0)), + expected_output: start_propose_timer_msg(Round::new(0)), + expected_round: Round::new(0), + new_state: propose_state(Round::new(0)), + }, + TestStep { + desc: "Timeout propopse, prevote for nil (v2)", + input_event: timeout_propose_event(Round::new(0)), + expected_output: prevote_nil_msg(Round::new(0), &my_addr, &my_sk), + expected_round: Round::new(0), + new_state: prevote_state(Round::new(0)), + }, + TestStep { + desc: "v1 prevotes a proposal", + input_event: prevote_event(&v1.address, &sk1), + expected_output: None, + expected_round: Round::new(0), + new_state: prevote_state(Round::new(0)), + }, + TestStep { + desc: "v2 prevotes for same proposal, we get +2/3 prevotes, start prevote timer", + input_event: prevote_event(&v2.address, &sk2), + expected_output: start_prevote_timer_msg(Round::new(0)), + expected_round: Round::new(0), + new_state: prevote_state(Round::new(0)), + }, + TestStep { + desc: "v1 precommits a proposal", + input_event: precommit_event(Round::new(0), value, &v1.address, &sk1), + expected_output: None, + expected_round: Round::new(0), + new_state: prevote_state(Round::new(0)), + }, + TestStep { + desc: "v2 precommits for same proposal, we get +2/3 precommit, start precommit timer", + input_event: precommit_event(Round::new(0), value, &v2.address, &sk2), + expected_output: start_precommit_timer_msg(Round::new(0)), + expected_round: Round::new(0), + new_state: prevote_state(Round::new(0)), + }, + TestStep { + desc: "Timeout precommit, start new round", + input_event: timeout_precommit_event(Round::new(0)), + expected_output: new_round_msg(Round::new(1)), + expected_round: Round::new(1), + new_state: new_round(Round::new(1)), + }, + TestStep { + desc: "Receive proposal", + input_event: proposal_event(Round::new(0), value, Round::Nil, Validity::Valid), + expected_output: decide_message(Round::new(0), value), + expected_round: Round::new(1), + new_state: decided_state( + Round::new(1), + // Proposal::new(Height::new(1), Round::new(1), value, Round::new(0)), <--- TODO - should be this + value, + ), + }, + ]; + + run_steps(&mut driver, steps); +} + +// Arrive at L36 in round 0, with step prevote and then L28 in round 1, with locked value v. +// +// Ev: NewRound(0) Proposal +// State: NewRound ------------> Propose ---------> Prevote -----------> Precommit ---------------------------> NewRound --> +// 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 +// +// v1=2, v2=2, v3=3, we are v2 +// Trying to arrive at L36 with step prevote and then L28 +// L21 - v2 is not proposer starts timeout propose (step propose) +// L24 - v2 receives proposal(v) from v1, prevotes for v (step prevote) +// L37 - v1 and v3 prevote for v, v2 gets +2/3 prevotes, locked_value=v, valid_value=v, sends precommit(v) (step precommit) +// L56 - v2 receives a precommit(id(v), round=1) from v3, starts new round (step new_round) +// Note - this doesn't seem correct v2 behaviour (??) +// L16, L19 - v2 is the proposer and has both a locked and valid value from round 0, propose(round=1, value=v, valid_round=0) (step propose) +// L28 - v2 receives its proposal and has 2f+1 prevotes from round 0 and: +// L29 - locked_round(0) <= valid_round(0) and valid_round(0) < round(1) +// L30 - v2 sends prevote(id(v), round=1) (step prevote) +#[test] +fn driver_steps_polka_previous_with_locked() { + let value = Value::new(9999); + + let [(v1, sk1), (v2, sk2), (v3, sk3)] = make_validators([2, 2, 3]); + let (my_sk, my_addr) = (sk2, v2.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, v2, are not the proposer, start timeout propose", + input_event: new_round_event(Round::new(0)), + expected_output: start_propose_timer_msg(Round::new(0)), + expected_round: Round::new(0), + new_state: propose_state(Round::new(0)), + }, + TestStep { + desc: "receive a proposal from v1 - L22 send prevote", + input_event: proposal_event(Round::new(0), value, Round::Nil, Validity::Valid), + expected_output: prevote_msg(Round::new(0), &my_addr, &my_sk), + expected_round: Round::new(0), + new_state: prevote_state_with_proposal( + Round::new(0), + Proposal::new(Height::new(1), Round::new(0), value, Round::Nil), + ), + }, + TestStep { + desc: "v3 prevotes the proposal", + input_event: prevote_event(&v3.address, &sk3), + expected_output: None, + expected_round: Round::new(0), + new_state: prevote_state_with_proposal( + Round::new(0), + Proposal::new(Height::new(1), Round::new(0), value, Round::Nil), + ), + }, + TestStep { + desc: "v1 prevotes for same proposal, we get +2/3 prevotes, precommit", + input_event: prevote_event(&v1.address, &sk1), + expected_output: precommit_msg(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), + ), + }, + TestStep { + desc: "Receive f+1 vote for round 1 from v3", + input_event: precommit_event(Round::new(1), Value::new(8888), &v3.address, &sk3), + expected_output: new_round_msg(Round::new(1)), + expected_round: Round::new(1), + new_state: new_round_with_proposal_and_locked_and_valid( + Round::new(1), + Proposal::new(Height::new(1), Round::new(0), value, Round::Nil), + ), + }, + TestStep { + desc: "start round 1, we are proposer with a valid value, propose it", + input_event: new_round_event(Round::new(1)), + expected_output: proposal_msg(Round::new(1), value, Round::new(0)), + expected_round: Round::new(1), + new_state: propose_state_with_proposal_and_locked_and_valid( + Round::new(1), + Proposal::new(Height::new(1), Round::new(0), value, Round::Nil), + ), + }, + TestStep { + desc: "Receive our own proposal", + input_event: proposal_event(Round::new(1), value, Round::new(0), Validity::Valid), + expected_output: prevote_msg(Round::new(1), &my_addr, &my_sk), + expected_round: Round::new(1), + new_state: prevote_state_with_proposal_and_locked_and_valid( + Round::new(1), + Proposal::new(Height::new(1), Round::new(1), value, Round::new(0)), + ), + }, + ]; + + run_steps(&mut driver, steps) +} + +#[test] +fn driver_steps_polka_previous_invalid_proposal_with_locked() { + let value = Value::new(9999); + + let [(v1, sk1), (v2, sk2), (v3, sk3)] = make_validators([2, 2, 3]); + let (my_sk, my_addr) = (sk2, v2.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, v2, are not the proposer, start timeout propose", + input_event: new_round_event(Round::new(0)), + expected_output: start_propose_timer_msg(Round::new(0)), + expected_round: Round::new(0), + new_state: propose_state(Round::new(0)), + }, + TestStep { + desc: "receive a proposal from v1 - L22 send prevote", + input_event: proposal_event(Round::new(0), value, Round::Nil, Validity::Valid), + expected_output: prevote_msg(Round::new(0), &my_addr, &my_sk), + expected_round: Round::new(0), + new_state: prevote_state_with_proposal( + Round::new(0), + Proposal::new(Height::new(1), Round::new(0), value, Round::Nil), + ), + }, + TestStep { + desc: "v3 prevotes the proposal", + input_event: prevote_event(&v3.address, &sk3), + expected_output: None, + expected_round: Round::new(0), + new_state: prevote_state_with_proposal( + Round::new(0), + Proposal::new(Height::new(1), Round::new(0), value, Round::Nil), + ), + }, + TestStep { + desc: "v1 prevotes for same proposal, we get +2/3 prevotes, precommit", + input_event: prevote_event(&v1.address, &sk1), + expected_output: precommit_msg(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), + ), + }, + TestStep { + desc: "Receive f+1 vote for round 1 from v3", + input_event: precommit_event(Round::new(1), Value::new(8888), &v3.address, &sk3), + expected_output: new_round_msg(Round::new(1)), + expected_round: Round::new(1), + new_state: new_round_with_proposal_and_locked_and_valid( + Round::new(1), + Proposal::new(Height::new(1), Round::new(0), value, Round::Nil), + ), + }, + TestStep { + desc: "start round 1, we are proposer with a valid value, propose it", + input_event: new_round_event(Round::new(1)), + expected_output: proposal_msg(Round::new(1), value, Round::new(0)), + expected_round: Round::new(1), + new_state: propose_state_with_proposal_and_locked_and_valid( + Round::new(1), + Proposal::new(Height::new(1), Round::new(0), value, Round::Nil), + ), + }, + TestStep { + desc: "Receive our own proposal", + input_event: proposal_event(Round::new(1), value, Round::new(0), Validity::Invalid), + expected_output: prevote_nil_msg(Round::new(1), &my_addr, &my_sk), + expected_round: Round::new(1), + new_state: prevote_state_with_proposal_and_locked_and_valid( + Round::new(1), + Proposal::new(Height::new(1), Round::new(0), value, Round::Nil), + ), + }, + ]; + + run_steps(&mut driver, steps); +} + +// Arrive at L36 in round 0, with step precommit and then L28 in round 1 with no locked value. +// +// Ev: NewRound(0) Timeout(propose) Timeout(prevote) +// State: NewRound ------------> Propose ---------------> Prevote -----------> Prevote -----------------> Precommit --> +// 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: 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 +// L21 - v2 is not proposer starts timeout propose (step propose) +// L59 - v2 receives timeout propose, prevotes for nil (step prevote) +// L35 - v1 and v3 prevote for some proposal(v), v2 gets +2/3 prevotes, starts timeout prevote (step prevote) +// L63 - v2 receives timeout prevote, prevotes for nil (step precommit) +// L36 - v2 receives the proposal(v) from v1, sets valid = v (L42, L43) but does NOT lock (L37-L41 not executed) (step precommit) +// L56 - v2 receives a prevote(id(v), round=1) from v3, starts new round (step new_round) +// Note - this doesn't seem correct v2 behaviour +// L16, L19 - v2 is the proposer and has a valid value from round 0, propose(round=1, value=v, valid_round=0) (step propose) +// L28 - v2 receives its proposal and has 2f+1 prevotes from round 0 and: +// L29 - locked_round(-1) < valid_round(0) and valid_round(0) < round(1) BUT locked_value is nil +// L32 - v2 sends prevote(nil, round=1) (step prevote) +#[test] +fn driver_steps_polka_previous_with_no_locked() { + let value = Value::new(9999); + + let [(v1, sk1), (v2, sk2), (v3, sk3)] = make_validators([2, 2, 3]); + let (my_sk, my_addr) = (sk2, v2.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 v2 are not the proposer, start timeout propose", + input_event: new_round_event(Round::new(0)), + expected_output: start_propose_timer_msg(Round::new(0)), + expected_round: Round::new(0), + new_state: propose_state(Round::new(0)), + }, + TestStep { + desc: "Timeout propopse, prevote for nil (v2)", + input_event: timeout_propose_event(Round::new(0)), + expected_output: prevote_nil_msg(Round::new(0), &my_addr, &my_sk), + expected_round: Round::new(0), + new_state: prevote_state(Round::new(0)), + }, + TestStep { + desc: "v3 prevotes for some proposal", + input_event: prevote_event(&v3.address, &sk3), + expected_output: None, + expected_round: Round::new(0), + new_state: prevote_state(Round::new(0)), + }, + TestStep { + desc: "v1 prevotes for same proposal, we get +2/3 prevotes, start timeout prevote", + input_event: prevote_event(&v1.address, &sk1), + expected_output: start_prevote_timer_msg(Round::new(0)), + expected_round: Round::new(0), + new_state: prevote_state(Round::new(0)), + }, + TestStep { + desc: "timeout prevote, prevote for nil (v2)", + input_event: timeout_prevote_event(Round::new(0)), + expected_output: precommit_nil_msg(&my_addr, &my_sk), + expected_round: Round::new(0), + new_state: precommit_state(Round::new(0)), + }, + TestStep { + desc: "receive a proposal - L36, we don't lock, we set valid", + input_event: proposal_event(Round::new(0), value, Round::Nil, Validity::Valid), + expected_output: None, + expected_round: Round::new(0), + new_state: precommit_state_with_proposal_and_valid( + Round::new(0), + Round::new(0), + Proposal::new(Height::new(1), Round::new(0), value, Round::Nil), + ), + }, + TestStep { + desc: "Receive f+1 vote for round 1 from v3", + input_event: prevote_event_at(Round::new(1), &v3.address, &sk3), + expected_output: new_round_msg(Round::new(1)), + expected_round: Round::new(1), + new_state: new_round_with_proposal_and_valid( + Round::new(1), + Proposal::new(Height::new(1), Round::new(0), value, Round::Nil), + ), + }, + TestStep { + desc: "start round 1, we are proposer with a valid value from round 0, propose it", + input_event: new_round_event(Round::new(1)), + expected_output: proposal_msg(Round::new(1), value, Round::new(0)), + expected_round: Round::new(1), + new_state: propose_state_with_proposal_and_valid( + Round::new(1), + Round::new(0), + Proposal::new(Height::new(1), Round::new(0), value, Round::Nil), + ), + }, + TestStep { + desc: "Receive our own proposal, prevote nil as we are not locked on the value", + input_event: proposal_event(Round::new(1), value, Round::new(0), Validity::Valid), + expected_output: prevote_nil_msg(Round::new(1), &my_addr, &my_sk), + expected_round: Round::new(1), + new_state: prevote_state_with_proposal_and_valid( + Round::new(1), + Round::new(0), + Proposal::new(Height::new(1), Round::new(0), value, Round::Nil), + ), + }, + ]; + + run_steps(&mut driver, steps); +} + +fn run_steps

(driver: &mut Driver, steps: Vec) +where + P: ProposerSelector, +{ + for step in steps { + println!("Step: {}", step.desc); + + let output = block_on(driver.execute(step.input_event)).expect("execute succeeded"); + assert_eq!(output, step.expected_output, "expected output message"); + + assert_eq!( + driver.round_state.round, step.expected_round, + "expected round" + ); + + assert_eq!(driver.round_state, step.new_state, "expected state"); + } +} From 952c4e3d8d4baed5c549f6b13c4c2ae364cc2640 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Tue, 28 Nov 2023 10:20:16 +0100 Subject: [PATCH 3/7] chore: Hide proposer selector type from `Driver` struct type (#90) * chore: Hide proposer selector type from `Driver` struct type * chore: Manually derive `Clone` and `Debug` where appropriate * chore: Exclude Clone and Debug instances from code coverage * chore: Cleanup --- Code/common/src/validator_set.rs | 1 + Code/driver/src/driver.rs | 32 ++++++++++++----- Code/test/src/validator_set.rs | 1 + Code/test/tests/driver_extra.rs | 7 ++-- Code/vote/src/keeper.rs | 60 ++++++++++++++++++++++++++++++-- 5 files changed, 85 insertions(+), 16 deletions(-) diff --git a/Code/common/src/validator_set.rs b/Code/common/src/validator_set.rs index e632830c1..b0288c033 100644 --- a/Code/common/src/validator_set.rs +++ b/Code/common/src/validator_set.rs @@ -37,6 +37,7 @@ where /// A validator set is a collection of validators. pub trait ValidatorSet where + Self: Clone + Debug, Ctx: Context, { /// The total voting power of the validator set. diff --git a/Code/driver/src/driver.rs b/Code/driver/src/driver.rs index 3c93f780e..3fd8ced40 100644 --- a/Code/driver/src/driver.rs +++ b/Code/driver/src/driver.rs @@ -1,4 +1,5 @@ -use malachite_round::state_machine::Info; +use alloc::boxed::Box; +use core::fmt; use malachite_common::{ Context, Proposal, Round, SignedVote, Timeout, TimeoutStep, Validator, ValidatorSet, Value, @@ -7,6 +8,7 @@ use malachite_common::{ use malachite_round::events::Event as RoundEvent; use malachite_round::message::Message as RoundMessage; use malachite_round::state::{State as RoundState, Step}; +use malachite_round::state_machine::Info; use malachite_vote::keeper::Message as VoteMessage; use malachite_vote::keeper::VoteKeeper; use malachite_vote::Threshold; @@ -19,14 +21,12 @@ use crate::ProposerSelector; use crate::Validity; /// Driver for the state machine of the Malachite consensus engine at a given height. -#[derive(Clone, Debug)] -pub struct Driver +pub struct Driver where Ctx: Context, - PSel: ProposerSelector, { pub ctx: Ctx, - pub proposer_selector: PSel, + pub proposer_selector: Box>, pub address: Ctx::Address, pub validator_set: Ctx::ValidatorSet, @@ -35,14 +35,13 @@ where pub round_state: RoundState, } -impl Driver +impl Driver where Ctx: Context, - PSel: ProposerSelector, { pub fn new( ctx: Ctx, - proposer_selector: PSel, + proposer_selector: impl ProposerSelector + 'static, validator_set: Ctx::ValidatorSet, address: Ctx::Address, ) -> Self { @@ -53,7 +52,7 @@ where Self { ctx, - proposer_selector, + proposer_selector: Box::new(proposer_selector), address, validator_set, votes, @@ -329,3 +328,18 @@ where Ok(transition.message) } } + +impl fmt::Debug for Driver +where + Ctx: Context, +{ + #[cfg_attr(coverage_nightly, coverage(off))] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Driver") + .field("address", &self.address) + .field("validator_set", &self.validator_set) + .field("votes", &self.votes) + .field("round_state", &self.round_state) + .finish() + } +} diff --git a/Code/test/src/validator_set.rs b/Code/test/src/validator_set.rs index 36c1fee52..35b8462f5 100644 --- a/Code/test/src/validator_set.rs +++ b/Code/test/src/validator_set.rs @@ -70,6 +70,7 @@ impl malachite_common::Validator for Validator { } /// A validator set contains a list of validators sorted by address. +#[derive(Clone, Debug, PartialEq, Eq)] pub struct ValidatorSet { pub validators: Vec, } diff --git a/Code/test/tests/driver_extra.rs b/Code/test/tests/driver_extra.rs index cadbb145f..199f66aa8 100644 --- a/Code/test/tests/driver_extra.rs +++ b/Code/test/tests/driver_extra.rs @@ -1,7 +1,7 @@ use futures::executor::block_on; use malachite_common::Round; -use malachite_driver::{Driver, Event, Message, ProposerSelector, Validity}; +use malachite_driver::{Driver, Event, Message, Validity}; use malachite_round::state::State; use malachite_test::{Height, Proposal, TestContext, ValidatorSet, Value}; @@ -514,10 +514,7 @@ fn driver_steps_polka_previous_with_no_locked() { run_steps(&mut driver, steps); } -fn run_steps

(driver: &mut Driver, steps: Vec) -where - P: ProposerSelector, -{ +fn run_steps(driver: &mut Driver, steps: Vec) { for step in steps { println!("Step: {}", step.desc); diff --git a/Code/vote/src/keeper.rs b/Code/vote/src/keeper.rs index b1668f288..a0e21fe25 100644 --- a/Code/vote/src/keeper.rs +++ b/Code/vote/src/keeper.rs @@ -1,3 +1,5 @@ +use core::fmt; + use alloc::collections::{BTreeMap, BTreeSet}; use malachite_common::{Context, Round, ValueId, Vote, VoteType}; @@ -17,7 +19,6 @@ pub enum Message { SkipRound(Round), } -#[derive(Clone, Debug)] struct PerRound where Ctx: Context, @@ -40,8 +41,35 @@ where } } +impl Clone for PerRound +where + Ctx: Context, +{ + #[cfg_attr(coverage_nightly, coverage(off))] + fn clone(&self) -> Self { + Self { + votes: self.votes.clone(), + addresses_weights: self.addresses_weights.clone(), + emitted_msgs: self.emitted_msgs.clone(), + } + } +} + +impl fmt::Debug for PerRound +where + Ctx: Context, +{ + #[cfg_attr(coverage_nightly, coverage(off))] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PerRound") + .field("votes", &self.votes) + .field("addresses_weights", &self.addresses_weights) + .field("emitted_msgs", &self.emitted_msgs) + .finish() + } +} + /// Keeps track of votes and emits messages when thresholds are reached. -#[derive(Clone, Debug)] pub struct VoteKeeper where Ctx: Context, @@ -188,3 +216,31 @@ fn threshold_to_message( (VoteType::Precommit, Threshold::Value(v)) => Some(Message::PrecommitValue(v)), } } + +impl Clone for VoteKeeper +where + Ctx: Context, +{ + #[cfg_attr(coverage_nightly, coverage(off))] + fn clone(&self) -> Self { + Self { + total_weight: self.total_weight, + threshold_params: self.threshold_params, + per_round: self.per_round.clone(), + } + } +} + +impl fmt::Debug for VoteKeeper +where + Ctx: Context, +{ + #[cfg_attr(coverage_nightly, coverage(off))] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("VoteKeeper") + .field("total_weight", &self.total_weight) + .field("threshold_params", &self.threshold_params) + .field("per_round", &self.per_round) + .finish() + } +} From 1dafd49c0e511b4662654c7956de54ecd47aa900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A1n=20Vanzetto?= <15466498+hvanz@users.noreply.github.com> Date: Tue, 28 Nov 2023 11:11:34 +0100 Subject: [PATCH 4/7] tests: MBT for votekeeper (#63) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * executor start * PreCommit function * TODOs * line 28 is in the books * prevote, precommit, and timeout done. proposal missing * starting to put things together for state machine * a somewhat complete version of the executor logic * state machine, but needs to be debugged * moving statemachine. problem with chooseSome * it moves * more things moving * some problem with bookkeeper * more things move * I have seen a Polka * cleanup * cleaning * successful test of statemachine * before consensus return refactor * first pending event added * cleaned consensus * commit to merge with Manuel's updated votekeeper * to merge Daniel's comment * executor start * PreCommit function * TODOs * line 28 is in the books * prevote, precommit, and timeout done. proposal missing * starting to put things together for state machine * a somewhat complete version of the executor logic * state machine, but needs to be debugged * moving statemachine. problem with chooseSome * it moves * more things moving * some problem with bookkeeper * more things move * I have seen a Polka * cleanup * cleaning * successful test of statemachine * before consensus return refactor * first pending event added * cleaned consensus * commit to merge with Manuel's updated votekeeper * to merge Daniel's comment * addressed Daniel's comments * addressed Daniel's comments and run tests * completed the timeout test * clean up and comments * added checks for increasing round numbers * added hash function checks * valset error thrown in test fixed * added action and logic to get value from the outside into the system * comments following the discussion on where to put the reponsibility for getValue * transformed that executed events into list * added an asynchronous execution environment * added round number checks to ProposalMsg * test for disagreement in asynchronous setting * Parameterization of the Asynchronous model * Typecheck all Quint specs on CI and run test for `consensustest.qnt` * Update Specs/Quint/AsyncModels.qnt Co-authored-by: Romain Ruetschi * added a type invariant * updated syntax to Quint 0.14.4 * WIP: Deserialize ITF traces emitted by consensus Quint spec * Use fixtures from `Specs/Quint` directory * Use workspace dependencies and common settings * Add `step` action to vote keeper spec * Parse traces for the vote keeper spec * Add test fixtures for vote keeper * Cleanup * Use BigInts * moved bookkeeper statemachine out for modularity * Update Quint test job * commented a line that failed a test. Need to discuss with Manu * Run test on all Quint files * Use script to run all tests even in the presence of failures * fixed the logic and the test for Precommit,Nil * Update Specs/Quint/voteBookkeeperTest.qnt Signed-off-by: Josef Widder Co-authored-by: Hernán Vanzetto <15466498+hvanz@users.noreply.github.com> * rename files for test CI * Update .github/workflows/quint.yml * renamed one more file with a test * module renamed * Register step taken in spec with weightedVote variable * First implementation of trace player for votekeeper * Add valid trace; remove old traces * Failing test for skip round * Fix expected_output and add test for skip with quorum * fix(votekeeper): Compute threshold directly in `VoteKeeper` to account for `Skip` threshold * test: Re-enable VoteCount tests * test: Re-enable RoundVotes tests * test: Remove duplicate tests * test: Re-enable VoteKeeper tests * Re-remove invalid test cases * Remove duplicate commented test * fix(spec/votekeeper): Fix the VoteKeeper spec to account for skip threshold from higher round * fix: Fix a bug where we would double count the weight of validators which both prevoted and precommited * Fix executor spec * fix(votekeeper): Do not count validator weights twice if they prevoted and precommited when checking for Skip threshold * More refactoring; move tests near their function definitions * Add voteBookkeeperSM.qnt with consts; move state and actions to voteBookkeeper * Add Makefile and gen-traces.sh * use rstest for testdata files and fixtures * nits for Cargo.toml * fix clippy warn for items_after_test_module * use .into() to be succinct * use imports * refactor util methods * reorder imports * rename json fixture var * avoid redundant unwrap * use unimplemented!() * rm redundant type annotation * add TraceRunner trait * refactor test using TraceRunner * add Heights in Votes in ITF trace * add Height in Vote in test * pass Height in Vote creation * Update bookkeepers currentRound after calling into consensus * Rename `init` to `initWith` * Add vote invariants * Rename value in test * Make nil always one of the values to pick * Fix merge * Fix trace runner after merge * spec: Add height to vote * spec: add one more round to model * spec: add emitSkip property + a few small improvements * spec: add gitignore * Add new trace files; remove old one * add field current_round to Bookkeeper; fix tests * Fix formatting * spec: Fix executor.qnt * comment out failing test in driver.rs * mv Runner trait to itf crate * override itf crate til next release * update test * deserialize itf to native types * rm itf::Itf usages * add getters for testing * update test * fix consensus trace parsing * depend on informalsystems repo * update types for new itf-rs * refactor test module structure * use Trace::run_on * use rust i64 over BigInt * Add to quint workflow a step to generate traces, including Script/trace-remove-stuttering-steps.sh * fix consensus trace parsing * fix failing trace generation * Fix github workflow env variables * Update task name * Add mbt workflow * mbt workflow: remove branch requirement; simplify env variables * Rename workflow, task * mbt workflow: change trigger * update jq filter * update jq filter * workflow: store traces as artifact * workflow: fix env variable * update workflow * workflow: fix paths * workflow: add fixture dir variable * spec: update Makefile and gen-traces.sh * remove trace files * update Makefile * pick test fixtures at runtime * remove rstest and add glob dependency * format workflow ymls * run coverage with generated traces from MBT workflow * trigger mbt workflow at changes on the workflow yml * store round in vote * update votekeeper rust types and runner * add current_round in step log * add init step log * util method to generate itf traces * update votekeeper mbt * update mbt workflow * revert coverage workflow * set QUINT_SEED in coverage workflow * setup quint to generate traces on ci * disable output capturing in cargo test * nits * spec nits * Add back test on applyVote * Update comment in script * fix typos * use default seed in coverage workflow * update mbt workflow env vars * update rust workflow trigger paths * rm serde_with dep * Remove unused script * Use workspace dependencies --------- Co-authored-by: Josef Widder Co-authored-by: Romain Ruetschi Co-authored-by: Anca Zamfir Co-authored-by: Ranadeep Biswas --- .github/workflows/coverage.yml | 8 + .github/workflows/mbt.yml | 45 ++ .github/workflows/quint.yml | 1 - .github/workflows/rust.yml | 10 +- Code/Cargo.toml | 7 +- Code/common/src/round.rs | 4 +- Code/driver/src/util.rs | 2 +- Code/itf/Cargo.toml | 14 +- Code/itf/src/consensus.rs | 19 +- Code/itf/src/deserializers.rs | 8 +- Code/itf/src/lib.rs | 1 + Code/itf/src/utils.rs | 91 ++++ Code/itf/src/votekeeper.rs | 47 ++- Code/itf/tests/consensus.rs | 21 +- .../voteBookkeeper_polkaAnyTest_6.itf.json | 1 - .../voteBookkeeper_polkaNilTest_7.itf.json | 1 - ...keeper_synchronousConsensusTest_5.itf.json | 1 - Code/itf/tests/votekeeper.rs | 66 ++- Code/itf/tests/votekeeper/runner.rs | 183 ++++++++ Code/itf/tests/votekeeper/utils.rs | 55 +++ Code/round/src/events.rs | 4 +- Code/round/src/state_machine.rs | 2 +- Code/vote/src/keeper.rs | 22 +- Code/vote/src/round_votes.rs | 8 + Code/vote/src/round_weights.rs | 4 + Docs/architecture/adr-template.md | 2 +- Specs/Quint/.gitignore | 2 + Specs/Quint/Makefile | 43 ++ Specs/Quint/executor.qnt | 2 +- Specs/Quint/extraSpells.qnt | 11 +- Specs/Quint/scripts/gen-traces.sh | 62 +++ Specs/Quint/voteBookkeeper.qnt | 397 +++++++++++------- Specs/Quint/voteBookkeeperSM.qnt | 36 ++ Specs/Quint/voteBookkeeperTest.qnt | 195 ++++----- 34 files changed, 1048 insertions(+), 327 deletions(-) create mode 100644 .github/workflows/mbt.yml create mode 100644 Code/itf/src/utils.rs delete mode 100644 Code/itf/tests/fixtures/votekeeper/voteBookkeeper_polkaAnyTest_6.itf.json delete mode 100644 Code/itf/tests/fixtures/votekeeper/voteBookkeeper_polkaNilTest_7.itf.json delete mode 100644 Code/itf/tests/fixtures/votekeeper/voteBookkeeper_synchronousConsensusTest_5.itf.json create mode 100644 Code/itf/tests/votekeeper/runner.rs create mode 100644 Code/itf/tests/votekeeper/utils.rs create mode 100644 Specs/Quint/.gitignore create mode 100644 Specs/Quint/Makefile create mode 100755 Specs/Quint/scripts/gen-traces.sh create mode 100644 Specs/Quint/voteBookkeeperSM.qnt diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index c141726f3..86bf4fede 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -5,9 +5,13 @@ on: branches: main paths: - Code/** + - Specs/Quint/** + - .github/workflows/coverage.yml pull_request: paths: - Code/** + - Specs/Quint/** + - .github/workflows/coverage.yml jobs: coverage: @@ -20,6 +24,10 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + - uses: actions/setup-node@v3 + with: + node-version: "18" + - run: npm install -g @informalsystems/quint - name: Setup Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 with: diff --git a/.github/workflows/mbt.yml b/.github/workflows/mbt.yml new file mode 100644 index 000000000..639213c65 --- /dev/null +++ b/.github/workflows/mbt.yml @@ -0,0 +1,45 @@ +name: MBT +on: + push: + branches: + - main + paths: + - Specs/Quint/** + - Code/** + - .github/workflows/mbt.yml + pull_request: + paths: + - Specs/Quint/** + - Code/** + - .github/workflows/mbt.yml + +jobs: + gen-and-exec-traces: + name: Generate and execute random traces + runs-on: ubuntu-latest + env: + CARGO_INCREMENTAL: 0 + CARGO_PROFILE_DEV_DEBUG: 1 + CARGO_PROFILE_RELEASE_DEBUG: 1 + RUST_BACKTRACE: short + CARGO_NET_RETRY: 10 + RUSTUP_MAX_RETRIES: 10 + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: actions/setup-node@v3 + with: + node-version: "18" + - run: npm install -g @informalsystems/quint + - name: Setup Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + - name: Install cargo-nextest + uses: taiki-e/install-action@cargo-nextest + - name: Build code + working-directory: Code/itf + run: cargo nextest run --workspace --all-features --no-run + - name: Current time as random seed for Quint + run: echo "QUINT_SEED=$(date +%s)" >> $GITHUB_ENV + - name: Run tests from traces (with random seed) + working-directory: Code/itf + run: cargo nextest run --workspace --all-features test_itf diff --git a/.github/workflows/quint.yml b/.github/workflows/quint.yml index 186268d6f..26099622f 100644 --- a/.github/workflows/quint.yml +++ b/.github/workflows/quint.yml @@ -33,4 +33,3 @@ jobs: node-version: "18" - run: npm install -g @informalsystems/quint - run: bash Scripts/quint-forall.sh test Specs/Quint/*Test.qnt - diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index df6b8d3ee..272dfcf45 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -5,9 +5,13 @@ on: branches: main paths: - Code/** + - Specs/Quint/** + - .github/workflows/rust.yml pull_request: paths: - Code/** + - Specs/Quint/** + - .github/workflows/rust.yml env: CARGO_INCREMENTAL: 0 @@ -27,6 +31,10 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + - uses: actions/setup-node@v3 + with: + node-version: "18" + - run: npm install -g @informalsystems/quint - name: Setup Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 - name: Install cargo-nextest @@ -51,7 +59,7 @@ jobs: with: token: ${{secrets.GITHUB_TOKEN}} args: --all-features --all-targets --manifest-path Code/Cargo.toml - + fmt: name: Formatting runs-on: ubuntu-latest diff --git a/Code/Cargo.toml b/Code/Cargo.toml index 6ab8f0f25..705510c26 100644 --- a/Code/Cargo.toml +++ b/Code/Cargo.toml @@ -19,10 +19,13 @@ publish = false [workspace.dependencies] async-trait = "0.1" -futures = "0.3" ed25519-consensus = "2.1.0" -itf = "0.1.2" +futures = "0.3" +glob = "0.3.0" +itf = "0.2.1" +num-bigint = "0.4.4" rand = { version = "0.8.5", features = ["std_rng"] } serde = "1.0" +serde_json = "1.0" sha2 = "0.10.8" signature = "2.1.0" diff --git a/Code/common/src/round.rs b/Code/common/src/round.rs index 6bb5cd3d6..db71c456e 100644 --- a/Code/common/src/round.rs +++ b/Code/common/src/round.rs @@ -41,12 +41,12 @@ impl Round { } } - /// Wether the round is defined, ie. `Round::Some(r)` where `r >= 0`. + /// Whether the round is defined, ie. `Round::Some(r)` where `r >= 0`. pub fn is_defined(&self) -> bool { matches!(self, Round::Some(r) if *r >= 0) } - /// Wether the round is `Round::Nil`. + /// Whether the round is `Round::Nil`. pub fn is_nil(&self) -> bool { matches!(self, Round::Nil) } diff --git a/Code/driver/src/util.rs b/Code/driver/src/util.rs index fd71a5915..9428d6f2c 100644 --- a/Code/driver/src/util.rs +++ b/Code/driver/src/util.rs @@ -1,4 +1,4 @@ -/// Wether or not a proposal is valid. +/// Whether or not a proposal is valid. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Validity { /// The proposal is valid. diff --git a/Code/itf/Cargo.toml b/Code/itf/Cargo.toml index 316394356..b8a977cb8 100644 --- a/Code/itf/Cargo.toml +++ b/Code/itf/Cargo.toml @@ -9,5 +9,15 @@ license.workspace = true publish.workspace = true [dependencies] -itf.workspace = true -serde = { workspace = true, features = ["derive"] } +itf = { workspace = true } +rand = { workspace = true } +malachite-common = { version = "0.1.0", path = "../common" } +malachite-vote = { version = "0.1.0", path = "../vote" } +malachite-test = { version = "0.1.0", path = "../test" } +num-bigint = { workspace = true, features = ["serde"] } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +glob = { workspace = true } + +[dev-dependencies] +tempfile = { version = "3.8.1" } diff --git a/Code/itf/src/consensus.rs b/Code/itf/src/consensus.rs index f631e4f45..2bb430b6a 100644 --- a/Code/itf/src/consensus.rs +++ b/Code/itf/src/consensus.rs @@ -1,13 +1,14 @@ -use itf::{ItfBigInt, ItfMap}; +use num_bigint::BigInt; use serde::Deserialize; +use std::collections::HashMap; use crate::deserializers as de; pub type Address = String; pub type Value = String; pub type Step = String; -pub type Round = ItfBigInt; -pub type Height = ItfBigInt; +pub type Round = BigInt; +pub type Height = BigInt; #[derive(Clone, Debug, Deserialize)] pub enum Timeout { @@ -33,7 +34,7 @@ pub struct State { } #[derive(Clone, Debug, Deserialize)] -pub struct System(ItfMap); +pub struct System(HashMap); #[derive(Clone, Debug, Deserialize)] #[serde(tag = "name")] @@ -132,9 +133,9 @@ impl Proposal { pub fn is_empty(&self) -> bool { self.src.is_empty() && self.proposal.is_empty() - && self.height == ItfBigInt::from(-1) - && self.round == ItfBigInt::from(-1) - && self.valid_round == ItfBigInt::from(-1) + && self.height == BigInt::from(-1) + && self.round == BigInt::from(-1) + && self.valid_round == BigInt::from(-1) } } @@ -152,8 +153,8 @@ impl VoteMessage { pub fn is_empty(&self) -> bool { self.src.is_empty() && self.id.is_empty() - && self.height == ItfBigInt::from(-1) - && self.round == ItfBigInt::from(-1) + && self.height == BigInt::from(-1) + && self.round == BigInt::from(-1) && self.step.is_empty() } } diff --git a/Code/itf/src/deserializers.rs b/Code/itf/src/deserializers.rs index 192e39401..53231f40d 100644 --- a/Code/itf/src/deserializers.rs +++ b/Code/itf/src/deserializers.rs @@ -1,4 +1,4 @@ -use itf::ItfBigInt; +use num_bigint::BigInt; use serde::de::IntoDeserializer; use serde::Deserialize; @@ -16,14 +16,14 @@ where } } -pub(crate) fn minus_one_as_none<'de, D>(de: D) -> Result, D::Error> +pub(crate) fn minus_one_as_none<'de, D>(de: D) -> Result, D::Error> where D: serde::Deserializer<'de>, { - let opt = Option::::deserialize(de)?; + let opt = Option::::deserialize(de)?; match opt { None => Ok(None), - Some(i) if i == ItfBigInt::from(-1) => Ok(None), + Some(i) if i == BigInt::from(-1) => Ok(None), Some(i) => Ok(Some(i)), } } diff --git a/Code/itf/src/lib.rs b/Code/itf/src/lib.rs index ce877e0f6..d8bacfb93 100644 --- a/Code/itf/src/lib.rs +++ b/Code/itf/src/lib.rs @@ -2,3 +2,4 @@ pub mod consensus; pub mod votekeeper; mod deserializers; +pub mod utils; diff --git a/Code/itf/src/utils.rs b/Code/itf/src/utils.rs new file mode 100644 index 000000000..c77f99c47 --- /dev/null +++ b/Code/itf/src/utils.rs @@ -0,0 +1,91 @@ +use glob::glob; +use std::path::Path; + +// TODO(rano): simplify this function once quint is fixed +pub fn generate_traces(spec_rel_path: &str, gen_dir: &str, quint_seed: u64) { + let spec_abs_path = format!( + "{}/../../Specs/Quint/{}", + env!("CARGO_MANIFEST_DIR"), + spec_rel_path + ); + + let spec_path = Path::new(&spec_abs_path); + + std::process::Command::new("quint") + .arg("test") + .arg("--output") + .arg(format!("{}/{{}}.itf.json", gen_dir)) + .arg("--seed") + .arg(quint_seed.to_string()) + .arg(spec_path) + .current_dir(spec_path.parent().unwrap()) + .output() + .expect("Failed to run quint test"); + + // Remove traces from imported modules + for redundant_itf in glob(&format!( + "{}/*{}::*.*", + gen_dir, + spec_path.file_stem().unwrap().to_str().unwrap() + )) + .expect("Failed to read glob pattern") + .flatten() + { + std::fs::remove_file(&redundant_itf).unwrap(); + } + + // Rerun quint per tests + // https://github.com/informalsystems/quint/issues/1263 + for itf_json in glob(&format!("{}/*.itf.json", gen_dir,)) + .expect("Failed to read glob pattern") + .flatten() + { + std::fs::remove_file(&itf_json).unwrap(); + + std::process::Command::new("quint") + .arg("test") + .arg("--output") + .arg(format!( + "{}/{}_{{}}.itf.json", + gen_dir, + spec_path.file_stem().unwrap().to_str().unwrap() + )) + .arg("--match") + .arg( + itf_json + .file_name() + .unwrap() + .to_string_lossy() + .strip_suffix(".itf.json") + .unwrap(), + ) + .arg("--seed") + .arg(quint_seed.to_string()) + .arg(spec_path) + .current_dir(spec_path.parent().unwrap()) + .output() + .expect("Failed to run quint test"); + } + + // Remove duplicate states + // https://github.com/informalsystems/quint/issues/1252 + for itf_json in glob(&format!("{}/*.itf.json", gen_dir,)) + .expect("Failed to read glob pattern") + .flatten() + { + let mut json: serde_json::Value = + serde_json::from_reader(std::fs::File::open(&itf_json).unwrap()).unwrap(); + + let states = json["states"].as_array_mut().unwrap(); + states.retain(|state| { + let index = state["#meta"]["index"].as_u64().unwrap(); + index % 2 == 0 + }); + states.iter_mut().enumerate().for_each(|(i, state)| { + state["#meta"]["index"] = serde_json::Value::from(i as u64); + }); + + let mut json_file = std::fs::File::create(&itf_json).unwrap(); + serde_json::to_writer_pretty(&mut json_file, &json).unwrap(); + } +} diff --git a/Code/itf/src/votekeeper.rs b/Code/itf/src/votekeeper.rs index be3fb73ae..933de3137 100644 --- a/Code/itf/src/votekeeper.rs +++ b/Code/itf/src/votekeeper.rs @@ -1,49 +1,76 @@ -use itf::{ItfBigInt, ItfMap, ItfSet}; +use itf::de::{As, Integer, Same}; +use std::collections::{HashMap, HashSet}; + use serde::Deserialize; -pub type Height = ItfBigInt; -pub type Weight = ItfBigInt; -pub type Round = ItfBigInt; +pub type Height = i64; +pub type Weight = i64; +pub type Round = i64; pub type Address = String; pub type Value = String; +pub type VoteType = String; #[derive(Clone, Debug, PartialEq, Eq, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Bookkeeper { + #[serde(with = "As::")] pub height: Height, + #[serde(with = "As::")] pub total_weight: Weight, - pub rounds: ItfMap, + #[serde(with = "As::>")] + pub rounds: HashMap, +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize)] +pub struct Vote { + pub typ: VoteType, + #[serde(with = "As::")] + pub height: Height, + #[serde(with = "As::")] + pub round: Round, + pub value: Value, + pub address: Address, } #[derive(Clone, Debug, PartialEq, Eq, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RoundVotes { + #[serde(with = "As::")] pub height: Height, + #[serde(with = "As::")] pub round: Round, pub prevotes: VoteCount, pub precommits: VoteCount, - pub emitted_events: ItfSet, - pub votes_addresses_weights: ItfMap, + pub emitted_events: HashSet, + #[serde(with = "As::>")] + pub votes_addresses_weights: HashMap, } #[derive(Clone, Debug, PartialEq, Eq, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VoteCount { + #[serde(with = "As::")] pub total_weight: Weight, - pub values_weights: ItfMap, - pub votes_addresses: ItfSet

, + #[serde(with = "As::>")] + pub values_weights: HashMap, + pub votes_addresses: HashSet
, } #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Hash)] pub struct ExecutorEvent { + #[serde(with = "As::")] pub round: Round, pub name: String, pub value: Value, } #[derive(Clone, Debug, PartialEq, Eq, Deserialize)] -#[serde(rename_all = "camelCase")] pub struct State { + #[serde(rename = "voteBookkeeperTest::voteBookkeeperSM::bookkeeper")] pub bookkeeper: Bookkeeper, + #[serde(rename = "voteBookkeeperTest::voteBookkeeperSM::lastEmitted")] pub last_emitted: ExecutorEvent, + #[serde(rename = "voteBookkeeperTest::voteBookkeeperSM::weightedVote")] + #[serde(with = "As::<(Same, Integer, Integer)>")] + pub weighted_vote: (Vote, Weight, Round), } diff --git a/Code/itf/tests/consensus.rs b/Code/itf/tests/consensus.rs index 435ec6481..9eac81275 100644 --- a/Code/itf/tests/consensus.rs +++ b/Code/itf/tests/consensus.rs @@ -1,19 +1,16 @@ +use glob::glob; + use malachite_itf::consensus::State; #[test] -fn parse_fixtures() { - let folder = format!("{}/tests/fixtures/consensus", env!("CARGO_MANIFEST_DIR")); - - let fixtures = std::fs::read_dir(folder) - .unwrap() - .map(|entry| entry.unwrap().path()) - .filter(|path| path.extension().map_or(false, |ext| ext == "json")) - .collect::>(); - - for fixture in fixtures { - println!("Parsing '{}'", fixture.display()); +fn test_itf() { + for json_fixture in glob("tests/fixtures/consensus/*.json") + .expect("Failed to read glob pattern") + .flatten() + { + println!("Parsing {json_fixture:?}"); - let json = std::fs::read_to_string(&fixture).unwrap(); + let json = std::fs::read_to_string(&json_fixture).unwrap(); let state = itf::trace_from_str::(&json).unwrap(); dbg!(state); diff --git a/Code/itf/tests/fixtures/votekeeper/voteBookkeeper_polkaAnyTest_6.itf.json b/Code/itf/tests/fixtures/votekeeper/voteBookkeeper_polkaAnyTest_6.itf.json deleted file mode 100644 index e766285a1..000000000 --- a/Code/itf/tests/fixtures/votekeeper/voteBookkeeper_polkaAnyTest_6.itf.json +++ /dev/null @@ -1 +0,0 @@ -{"#meta":{"format":"ITF","format-description":"https://apalache.informal.systems/docs/adr/015adr-trace.html","source":"voteBookkeeper.qnt","status":"passed","description":"Created by Quint on Fri Nov 10 2023 14:26:21 GMT+0100 (GMT+01:00)","timestamp":1699622781761},"vars":["lastEmitted","bookkeeper"],"states":[{"#meta":{"index":0},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"","round":{"#bigint":"-1"},"value":"null"}},{"#meta":{"index":1},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[]},"votesAddresses":{"#set":[]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"60"}]]},"votesAddresses":{"#set":["alice"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":2},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[]},"votesAddresses":{"#set":[]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"60"}]]},"votesAddresses":{"#set":["alice"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":3},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[]},"votesAddresses":{"#set":[]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"70"}]]},"votesAddresses":{"#set":["alice","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}},{"#meta":{"index":4},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[]},"votesAddresses":{"#set":[]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"70"}]]},"votesAddresses":{"#set":["alice","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}},{"#meta":{"index":5},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[]},"votesAddresses":{"#set":[]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alice","bob","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["bob",{"#bigint":"30"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":6},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[]},"votesAddresses":{"#set":[]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alice","bob","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["bob",{"#bigint":"30"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":7},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"30"}]]},"votesAddresses":{"#set":["bob"]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alice","bob","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["bob",{"#bigint":"30"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":8},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"30"}]]},"votesAddresses":{"#set":["bob"]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alice","bob","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["bob",{"#bigint":"30"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":9},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"40"}]]},"votesAddresses":{"#set":["bob","john"]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alice","bob","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["bob",{"#bigint":"30"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":10},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"40"}]]},"votesAddresses":{"#set":["bob","john"]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alice","bob","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["bob",{"#bigint":"30"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":11},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"},{"name":"PrecommitValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alive","bob","john"]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alice","bob","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["alive",{"#bigint":"60"}],["bob",{"#bigint":"30"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"PrecommitValue","round":{"#bigint":"1"},"value":"proposal"}}]} \ No newline at end of file diff --git a/Code/itf/tests/fixtures/votekeeper/voteBookkeeper_polkaNilTest_7.itf.json b/Code/itf/tests/fixtures/votekeeper/voteBookkeeper_polkaNilTest_7.itf.json deleted file mode 100644 index b5bf98cda..000000000 --- a/Code/itf/tests/fixtures/votekeeper/voteBookkeeper_polkaNilTest_7.itf.json +++ /dev/null @@ -1 +0,0 @@ -{"#meta":{"format":"ITF","format-description":"https://apalache.informal.systems/docs/adr/015adr-trace.html","source":"voteBookkeeper.qnt","status":"passed","description":"Created by Quint on Fri Nov 10 2023 14:26:21 GMT+0100 (GMT+01:00)","timestamp":1699622781765},"vars":["lastEmitted","bookkeeper"],"states":[{"#meta":{"index":0},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"","round":{"#bigint":"-1"},"value":"null"}},{"#meta":{"index":1},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[]},"votesAddresses":{"#set":[]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"60"}]]},"votesAddresses":{"#set":["alice"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":2},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[]},"votesAddresses":{"#set":[]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"60"}]]},"votesAddresses":{"#set":["alice"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":3},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[]},"votesAddresses":{"#set":[]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"70"}]]},"votesAddresses":{"#set":["alice","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}},{"#meta":{"index":4},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[]},"votesAddresses":{"#set":[]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"70"}]]},"votesAddresses":{"#set":["alice","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}},{"#meta":{"index":5},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[]},"votesAddresses":{"#set":[]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alice","bob","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["bob",{"#bigint":"30"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":6},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[]},"votesAddresses":{"#set":[]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alice","bob","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["bob",{"#bigint":"30"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":7},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"30"}]]},"votesAddresses":{"#set":["bob"]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alice","bob","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["bob",{"#bigint":"30"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":8},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"30"}]]},"votesAddresses":{"#set":["bob"]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alice","bob","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["bob",{"#bigint":"30"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":9},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"40"}]]},"votesAddresses":{"#set":["bob","john"]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alice","bob","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["bob",{"#bigint":"30"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":10},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"40"}]]},"votesAddresses":{"#set":["bob","john"]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alice","bob","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["bob",{"#bigint":"30"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":11},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"},{"name":"PrecommitValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alive","bob","john"]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alice","bob","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["alive",{"#bigint":"60"}],["bob",{"#bigint":"30"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"PrecommitValue","round":{"#bigint":"1"},"value":"proposal"}}]} \ No newline at end of file diff --git a/Code/itf/tests/fixtures/votekeeper/voteBookkeeper_synchronousConsensusTest_5.itf.json b/Code/itf/tests/fixtures/votekeeper/voteBookkeeper_synchronousConsensusTest_5.itf.json deleted file mode 100644 index 75bc07e59..000000000 --- a/Code/itf/tests/fixtures/votekeeper/voteBookkeeper_synchronousConsensusTest_5.itf.json +++ /dev/null @@ -1 +0,0 @@ -{"#meta":{"format":"ITF","format-description":"https://apalache.informal.systems/docs/adr/015adr-trace.html","source":"voteBookkeeper.qnt","status":"passed","description":"Created by Quint on Fri Nov 10 2023 14:26:21 GMT+0100 (GMT+01:00)","timestamp":1699622781758},"vars":["lastEmitted","bookkeeper"],"states":[{"#meta":{"index":0},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"","round":{"#bigint":"-1"},"value":"null"}},{"#meta":{"index":1},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[]},"votesAddresses":{"#set":[]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"60"}]]},"votesAddresses":{"#set":["alice"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":2},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[]},"votesAddresses":{"#set":[]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"60"}]]},"votesAddresses":{"#set":["alice"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":3},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[]},"votesAddresses":{"#set":[]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"70"}]]},"votesAddresses":{"#set":["alice","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}},{"#meta":{"index":4},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[]},"votesAddresses":{"#set":[]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"70"}]]},"votesAddresses":{"#set":["alice","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}},{"#meta":{"index":5},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[]},"votesAddresses":{"#set":[]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alice","bob","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["bob",{"#bigint":"30"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":6},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[]},"votesAddresses":{"#set":[]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alice","bob","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["bob",{"#bigint":"30"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":7},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"30"}]]},"votesAddresses":{"#set":["bob"]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alice","bob","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["bob",{"#bigint":"30"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":8},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"30"}]]},"votesAddresses":{"#set":["bob"]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alice","bob","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["bob",{"#bigint":"30"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":9},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"40"}]]},"votesAddresses":{"#set":["bob","john"]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alice","bob","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["bob",{"#bigint":"30"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":10},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"40"}]]},"votesAddresses":{"#set":["bob","john"]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alice","bob","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["bob",{"#bigint":"30"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"None","round":{"#bigint":"1"},"value":"null"}},{"#meta":{"index":11},"bookkeeper":{"height":{"#bigint":"10"},"rounds":{"#map":[[{"#bigint":"1"},{"emittedEvents":{"#set":[{"name":"PolkaValue","round":{"#bigint":"1"},"value":"proposal"},{"name":"PrecommitValue","round":{"#bigint":"1"},"value":"proposal"}]},"height":{"#bigint":"10"},"precommits":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alive","bob","john"]}},"prevotes":{"totalWeight":{"#bigint":"100"},"valuesWeights":{"#map":[["proposal",{"#bigint":"100"}]]},"votesAddresses":{"#set":["alice","bob","john"]}},"round":{"#bigint":"1"},"votesAddressesWeights":{"#map":[["alice",{"#bigint":"60"}],["alive",{"#bigint":"60"}],["bob",{"#bigint":"30"}],["john",{"#bigint":"10"}]]}}]]},"totalWeight":{"#bigint":"100"}},"lastEmitted":{"name":"PrecommitValue","round":{"#bigint":"1"},"value":"proposal"}}]} \ No newline at end of file diff --git a/Code/itf/tests/votekeeper.rs b/Code/itf/tests/votekeeper.rs index 1515d18ec..757bcf7fc 100644 --- a/Code/itf/tests/votekeeper.rs +++ b/Code/itf/tests/votekeeper.rs @@ -1,22 +1,64 @@ +#[path = "votekeeper/runner.rs"] +pub mod runner; +#[path = "votekeeper/utils.rs"] +pub mod utils; + +use glob::glob; +use rand::rngs::StdRng; +use rand::SeedableRng; + +use malachite_itf::utils::generate_traces; use malachite_itf::votekeeper::State; +use malachite_test::{Address, PrivateKey}; + +use runner::VoteKeeperRunner; +use utils::ADDRESSES; + +const RANDOM_SEED: u64 = 0x42; #[test] -fn parse_fixtures() { - // read fixtures files in test/fixtures/votekeeper/ - let folder = format!("{}/tests/fixtures/votekeeper", env!("CARGO_MANIFEST_DIR")); +fn test_itf() { + let temp_dir = tempfile::tempdir().expect("Failed to create temp dir"); - let fixtures = std::fs::read_dir(folder) - .unwrap() - .map(|entry| entry.unwrap().path()) - .filter(|path| path.extension().map_or(false, |ext| ext == "json")) - .collect::>(); + let quint_seed = option_env!("QUINT_SEED") + // use inspect when stabilized + .map(|x| { + println!("using QUINT_SEED={}", x); + x + }) + .or(Some("118")) + .and_then(|x| x.parse::().ok()) + .filter(|&x| x != 0) + .expect("invalid random seed for quint"); - for fixture in fixtures { - println!("Parsing '{}'", fixture.display()); + generate_traces( + "voteBookkeeperTest.qnt", + &temp_dir.path().to_string_lossy(), + quint_seed, + ); - let json = std::fs::read_to_string(&fixture).unwrap(); + for json_fixture in glob(&format!("{}/*.itf.json", temp_dir.path().display())) + .expect("Failed to read glob pattern") + .flatten() + { + println!("Parsing {json_fixture:?}"); + + let json = std::fs::read_to_string(&json_fixture).unwrap(); let trace = itf::trace_from_str::(&json).unwrap(); - dbg!(trace); + let mut rng = StdRng::seed_from_u64(RANDOM_SEED); + + // build mapping from model addresses to real addresses + let vote_keeper_runner = VoteKeeperRunner { + address_map: ADDRESSES + .iter() + .map(|&name| { + let pk = PrivateKey::generate(&mut rng).public_key(); + (name.into(), Address::from_public_key(&pk)) + }) + .collect(), + }; + + trace.run_on(vote_keeper_runner).unwrap(); } } diff --git a/Code/itf/tests/votekeeper/runner.rs b/Code/itf/tests/votekeeper/runner.rs new file mode 100644 index 000000000..482186b7f --- /dev/null +++ b/Code/itf/tests/votekeeper/runner.rs @@ -0,0 +1,183 @@ +use std::collections::HashMap; + +use malachite_common::{Context, Round, Value}; +use malachite_itf::votekeeper::State; +use malachite_test::{Address, Height, TestContext, Vote}; +use malachite_vote::{ + keeper::{Message, VoteKeeper}, + ThresholdParams, +}; + +use itf::Runner as ItfRunner; + +use super::utils::{check_votes, value_from_model}; + +pub struct VoteKeeperRunner { + pub address_map: HashMap, +} + +impl ItfRunner for VoteKeeperRunner { + type ActualState = VoteKeeper; + type Result = Option::Value as Value>::Id>>; + type ExpectedState = State; + type Error = (); + + fn init(&mut self, expected: &Self::ExpectedState) -> Result { + // Initialize VoteKeeper from the initial total_weight from the first state in the model. + let (input_vote, weight, current_round) = &expected.weighted_vote; + let round = Round::new(input_vote.round); + println!( + "🔵 init: vote={:?}, round={:?}, value={:?}, address={:?}, weight={:?}, current_round={:?}", + input_vote.typ, round, input_vote.value, input_vote.address, weight, current_round + ); + Ok(VoteKeeper::new( + expected.bookkeeper.total_weight as u64, + ThresholdParams::default(), + )) + } + + fn step( + &mut self, + actual: &mut Self::ActualState, + expected: &Self::ExpectedState, + ) -> Result { + // Build step to execute. + let (input_vote, weight, current_round) = &expected.weighted_vote; + let round = Round::new(input_vote.round); + let height = Height::new(input_vote.height as u64); + let value = value_from_model(&input_vote.value); + let address = self.address_map.get(input_vote.address.as_str()).unwrap(); + let vote = match input_vote.typ.as_str() { + "Prevote" => Vote::new_prevote(height, round, value, *address), + "Precommit" => Vote::new_precommit(height, round, value, *address), + _ => unreachable!(), + }; + println!( + "🔵 step: vote={:?}, round={:?}, value={:?}, address={:?}, weight={:?}, current_round={:?}", + input_vote.typ, round, value, input_vote.address, weight, current_round + ); + + // Execute step. + Ok(actual.apply_vote(vote, *weight as u64, Round::new(*current_round))) + } + + fn result_invariant( + &self, + result: &Self::Result, + expected: &Self::ExpectedState, + ) -> Result { + // Get expected result. + let expected_result = &expected.last_emitted; + println!( + "🟣 result: model={:?}({:?},{:?}), code={:?}", + expected_result.name, expected_result.value, expected_result.round, result + ); + // Check result against expected result. + match result { + Some(result) => match result { + Message::PolkaValue(value) => { + assert_eq!(expected_result.name, "PolkaValue"); + assert_eq!( + value_from_model(&expected_result.value).as_ref(), + Some(value) + ); + } + Message::PrecommitValue(value) => { + assert_eq!(expected_result.name, "PrecommitValue"); + assert_eq!( + value_from_model(&expected_result.value).as_ref(), + Some(value) + ); + } + Message::SkipRound(round) => { + assert_eq!(expected_result.name, "Skip"); + assert_eq!(&Round::new(expected_result.round), round); + } + msg => assert_eq!(expected_result.name, format!("{msg:?}")), + }, + None => assert_eq!(expected_result.name, "None"), + } + Ok(true) + } + + fn state_invariant( + &self, + actual: &Self::ActualState, + expected: &Self::ExpectedState, + ) -> Result { + // doesn't check for current Height and Round + + let actual_state = actual; + let expected_state = &expected.bookkeeper; + + assert_eq!( + actual_state.total_weight(), + &(expected_state.total_weight as u64), + "total_weight for the current height" + ); + + assert_eq!(actual_state.per_round().len(), expected_state.rounds.len()); + + for (&round, expected_round) in &expected_state.rounds { + // doesn't check for current Height and Round + + let actual_round = actual_state.per_round().get(&Round::new(round)).unwrap(); + + let expected_events = &expected_round.emitted_events; + let actual_events = actual_round.emitted_msgs(); + + assert_eq!( + actual_events.len(), + expected_events.len(), + "number of emitted events" + ); + + let mut event_count = HashMap::new(); + + for event in expected_events { + let count = event_count.entry(event.name.clone()).or_insert(0); + *count += 1; + } + + for event in actual_events { + let event_name = match event { + Message::PolkaValue(_) => "PolkaValue".into(), + Message::PrecommitValue(_) => "PrecommitValue".into(), + Message::SkipRound(_) => "Skip".into(), + _ => format!("{event:?}"), + }; + let count = event_count.entry(event_name.clone()).or_insert(0); + *count -= 1; + } + + for (event_name, count) in event_count { + assert_eq!(count, 0, "event {event_name:?} not matched"); + } + + let expected_addresses_weights = &expected_round.votes_addresses_weights; + let actual_addresses_weights = &actual_round.addresses_weights().get_inner(); + for address in expected_addresses_weights.keys() { + assert_eq!( + actual_addresses_weights.get(self.address_map.get(address).unwrap()), + expected_addresses_weights + .get(address) + .map(|&w| w as u64) + .as_ref(), + "weight for address {address:?}" + ); + } + + let actual_votes = &actual_round.votes(); + + let expected_prevotes = &expected_round.prevotes; + let actual_prevotes = actual_votes.prevotes(); + check_votes(expected_prevotes, actual_prevotes, &self.address_map); + + let expected_precommits = &expected_round.precommits; + let actual_precommits = actual_votes.precommits(); + check_votes(expected_precommits, actual_precommits, &self.address_map); + } + + Ok(true) + } +} diff --git a/Code/itf/tests/votekeeper/utils.rs b/Code/itf/tests/votekeeper/utils.rs new file mode 100644 index 000000000..91504697d --- /dev/null +++ b/Code/itf/tests/votekeeper/utils.rs @@ -0,0 +1,55 @@ +use std::collections::HashMap; + +use malachite_itf::votekeeper::Value; +use malachite_test::{Address, ValueId}; + +pub const ADDRESSES: [&str; 3] = ["alice", "bob", "john"]; +pub const NIL_VALUE: &str = "nil"; + +pub fn value_from_model(value: &Value) -> Option { + match value.as_str() { + NIL_VALUE => None, + "proposal" => Some(0.into()), + "val1" => Some(1.into()), + "val2" => Some(2.into()), + "val3" => Some(3.into()), + _ => unimplemented!("unknown value {value:?}"), + } +} + +pub fn check_votes( + expected: &malachite_itf::votekeeper::VoteCount, + actual: &malachite_vote::count::VoteCount, + address_map: &HashMap, +) { + // expected has `total_weight` which is not present in actual + + let expected_values_weights = &expected.values_weights; + let actual_values_weights = &actual.values_weights; + + // should check length too + + for value in expected_values_weights.keys() { + assert_eq!( + actual_values_weights.get(&value_from_model(value)), + *expected_values_weights.get(value).unwrap() as u64, + "weight for value {value:?}" + ); + } + + let expected_votes_addresses = &expected.votes_addresses; + let actual_votes_addresses = &actual.validator_addresses; + + assert_eq!( + actual_votes_addresses.len(), + expected_votes_addresses.len(), + "number of voted addresses" + ); + + for address in expected_votes_addresses { + assert!( + actual_votes_addresses.contains(address_map.get(address).unwrap()), + "address {address:?} not voted" + ); + } +} diff --git a/Code/round/src/events.rs b/Code/round/src/events.rs index 7954ac59c..0ea71c90f 100644 --- a/Code/round/src/events.rs +++ b/Code/round/src/events.rs @@ -9,8 +9,8 @@ where ProposeValue(Ctx::Value), // Propose a value.L14 Proposal(Ctx::Proposal), // Receive a proposal. L22 + L23 (valid) InvalidProposal, // Receive an invalid proposal. L26 + L32 (invalid) - ProposalAndPolkaPrevious(Ctx::Proposal), // Recieved a proposal and a polka value from a previous round. L28 + L29 (valid) - InvalidProposalAndPolkaPrevious(Ctx::Proposal), // Recieved a proposal and a polka value from a previous round. L28 + L29 (invalid) + ProposalAndPolkaPrevious(Ctx::Proposal), // Received a proposal and a polka value from a previous round. L28 + L29 (valid) + InvalidProposalAndPolkaPrevious(Ctx::Proposal), // Received a proposal and a polka value from a previous round. L28 + L29 (invalid) PolkaValue(ValueId), // Receive +2/3 prevotes for valueId. L44 PolkaAny, // Receive +2/3 prevotes for anything. L34 PolkaNil, // Receive +2/3 prevotes for nil. L44 diff --git a/Code/round/src/state_machine.rs b/Code/round/src/state_machine.rs index f525c1303..a34346f60 100644 --- a/Code/round/src/state_machine.rs +++ b/Code/round/src/state_machine.rs @@ -114,7 +114,7 @@ where // We are the proposer. (Step::Propose, Event::TimeoutPropose) if this_round && info.is_proposer() => { - // TOOD: Do we need to do something else here? + // TODO: Do we need to do something else here? prevote_nil(state, info.address) // L57 } // We are not the proposer. diff --git a/Code/vote/src/keeper.rs b/Code/vote/src/keeper.rs index a0e21fe25..16864e06d 100644 --- a/Code/vote/src/keeper.rs +++ b/Code/vote/src/keeper.rs @@ -19,7 +19,7 @@ pub enum Message { SkipRound(Round), } -struct PerRound +pub struct PerRound where Ctx: Context, { @@ -39,6 +39,18 @@ where emitted_msgs: BTreeSet::new(), } } + + pub fn votes(&self) -> &RoundVotes> { + &self.votes + } + + pub fn addresses_weights(&self) -> &RoundWeights { + &self.addresses_weights + } + + pub fn emitted_msgs(&self) -> &BTreeSet>> { + &self.emitted_msgs + } } impl Clone for PerRound @@ -91,6 +103,14 @@ where } } + pub fn total_weight(&self) -> &Weight { + &self.total_weight + } + + pub fn per_round(&self) -> &BTreeMap> { + &self.per_round + } + /// Apply a vote with a given weight, potentially triggering an event. pub fn apply_vote( &mut self, diff --git a/Code/vote/src/round_votes.rs b/Code/vote/src/round_votes.rs index 58307b597..c54aab7f0 100644 --- a/Code/vote/src/round_votes.rs +++ b/Code/vote/src/round_votes.rs @@ -18,6 +18,14 @@ impl RoundVotes { } } + pub fn prevotes(&self) -> &VoteCount { + &self.prevotes + } + + pub fn precommits(&self) -> &VoteCount { + &self.precommits + } + pub fn add_vote( &mut self, vote_type: VoteType, diff --git a/Code/vote/src/round_weights.rs b/Code/vote/src/round_weights.rs index 63213782d..77c3396aa 100644 --- a/Code/vote/src/round_weights.rs +++ b/Code/vote/src/round_weights.rs @@ -14,6 +14,10 @@ impl
RoundWeights
{ } } + pub fn get_inner(&self) -> &BTreeMap { + &self.map + } + pub fn set_once(&mut self, address: Address, weight: Weight) where Address: Ord, diff --git a/Docs/architecture/adr-template.md b/Docs/architecture/adr-template.md index 28a5ecfbb..83d323eee 100644 --- a/Docs/architecture/adr-template.md +++ b/Docs/architecture/adr-template.md @@ -31,6 +31,6 @@ If the proposed change will be large, please also indicate a way to do the chang ## References -> Are there any relevant PR comments, issues that led up to this, or articles referrenced for why we made the given design choice? If so link them here! +> Are there any relevant PR comments, issues that led up to this, or articles referenced for why we made the given design choice? If so link them here! * {reference link} diff --git a/Specs/Quint/.gitignore b/Specs/Quint/.gitignore new file mode 100644 index 000000000..efaa2423b --- /dev/null +++ b/Specs/Quint/.gitignore @@ -0,0 +1,2 @@ +traces/ +_apalache-out/ diff --git a/Specs/Quint/Makefile b/Specs/Quint/Makefile new file mode 100644 index 000000000..2b9227986 --- /dev/null +++ b/Specs/Quint/Makefile @@ -0,0 +1,43 @@ +parse: + npx @informalsystems/quint parse asyncModelsTest.qnt + npx @informalsystems/quint parse consensusTest.qnt + npx @informalsystems/quint parse executor.qnt + npx @informalsystems/quint parse statemachineTest.qnt + npx @informalsystems/quint parse voteBookkeeperTest.qnt +.PHONY: parse + +test-con: + npx @informalsystems/quint run consensusTest.qnt +.PHONY: test-con + +test-sm: + npx @informalsystems/quint test statemachineTest.qnt +.PHONY: test-sm + +test-vk: + npx @informalsystems/quint test voteBookkeeperTest.qnt +.PHONY: test-vk + +test: test-con test-sm test-vk +.PHONY: test + +verify: + npx @informalsystems/quint verify --max-steps 10 --invariant Inv voteBookkeeperTest.qnt +.PHONY: verify + +# Generate traces by random simulation from properties describing the last desired state. +traces-sim: + ./scripts/gen-traces.sh voteBookkeeperTest.qnt emitPrecommitValue 30 + ./scripts/gen-traces.sh voteBookkeeperTest.qnt emitPolkaAny 30 + ./scripts/gen-traces.sh voteBookkeeperTest.qnt emitPolkaNil 30 + ./scripts/gen-traces.sh voteBookkeeperTest.qnt emitSkip 30 +.PHONY: traces-sim + +# Generate traces from `run` statements. +traces-run: + mkdir -p traces/votekeeper + npx @informalsystems/quint test --output traces/votekeeper/{}.itf.json voteBookkeeperTest.qnt +.PHONY: traces-run + +traces: traces-sim traces-run +.PHONY: traces diff --git a/Specs/Quint/executor.qnt b/Specs/Quint/executor.qnt index 1a671d071..6aeb71a6a 100644 --- a/Specs/Quint/executor.qnt +++ b/Specs/Quint/executor.qnt @@ -78,7 +78,7 @@ val defaultInput: ExecutorInput = { // this is to match the type from the bookkeeper. When we add heights there we should // unify pure def toVote (v: VoteMsg_t) : Vote = - { typ: v.step, round: v.round, value: v.id, address: v.src } + { typ: v.step, height: v.height, round: v.round, value: v.id, address: v.src } val defaultEvent : Event = { name : "", height : 0, round: 0, value: "", vr: 0 } diff --git a/Specs/Quint/extraSpells.qnt b/Specs/Quint/extraSpells.qnt index a0fb9b7cc..0400dfda0 100644 --- a/Specs/Quint/extraSpells.qnt +++ b/Specs/Quint/extraSpells.qnt @@ -188,7 +188,7 @@ module extraSpells { /// /// - @param __map a map with values of type int /// - @returns the sum of the values of all entries in __map - pure def mapSumValues(__map: a -> int): int = + pure def mapSumValues(__map: a -> int): int = __map.keys().fold(0, (__acc, __val) => __acc + __map.get(__val)) run mapSumValuesTest = all { @@ -196,4 +196,13 @@ module extraSpells { assert(6 == Map("a" -> 0, "b" -> 1, "c" -> 2, "d" -> 3).mapSumValues()), } + /// True iff all (key, value) pairs in the map satisfy the given predicate. + pure def forallEntries(__map: a -> b, __f: (a, b) => bool): bool = + __map.keys().forall(__key => __f(__key, __map.get(__key))) + + + /// True iff all values in the map satisfy the given predicate. + pure def forallValues(__map: a -> b, __f: b => bool): bool = + __map.keys().forall(__key => __f(__map.get(__key))) + } \ No newline at end of file diff --git a/Specs/Quint/scripts/gen-traces.sh b/Specs/Quint/scripts/gen-traces.sh new file mode 100755 index 000000000..3f8debe4b --- /dev/null +++ b/Specs/Quint/scripts/gen-traces.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +BLUE=$(tput setaf 4) +RED=$(tput setaf 1) +RESET=$(tput sgr0) + +# [INFO] message in blue +info() +{ + echo "${BLUE}[INFO] $*${RESET}" +} + +# [ERROR] message in red +error() +{ + echo "${RED}[ERROR] $*${RESET} " +} + +FILEPATH=$1 +PROP=$2 +MAX_STEPS=${3:-100} +[[ ! -f "$FILEPATH" ]] && error "file $FILEPATH does not exist" && exit 1 +[[ -z "$PROP" ]] && error "property name required" && exit 1 + +MODULE=$(basename ${FILEPATH%".qnt"}) +TRACES_DIR="traces/$MODULE" +mkdir -p "$TRACES_DIR" + +# Given dir, name and ext, if "dir/name-N.ext" exists, it will return +# "dir/name-M.ext" with M=N+1, otherwise it will return "dir/name-1.ext". +function nextFilename() { + local dir=$1 + local name=$2 + local ext=$3 + local result="$dir/$name.$ext" + if [ -f $result ]; then + i=1 + result="$dir/$name-$i.$ext" + while [[ -e "$result" || -L "$result" ]] ; do + result="$dir/$name-$((i+1)).$ext" + done + fi + echo "$result" +} + +TRACE_PATH=$(nextFilename "$TRACES_DIR" "$PROP" "itf.json") +OUTPUT=$(npx @informalsystems/quint run \ + --max-steps=$MAX_STEPS \ + --max-samples=1 \ + --invariant "$PROP" \ + --out-itf "$TRACE_PATH" \ + "$FILEPATH" 2>&1) +case $OUTPUT in + "error: Invariant violated") + # info "Success: reached a state that violates $FILEPATH::$PROP" + info "Generated trace: $TRACE_PATH" + ;; + *) + error "Failed: did not find a state that violates $FILEPATH::$PROP in $MAX_STEPS steps" + [ -f $TRACE_PATH ] && info "Generated trace: $TRACE_PATH" + ;; +esac diff --git a/Specs/Quint/voteBookkeeper.qnt b/Specs/Quint/voteBookkeeper.qnt index cde151a75..3e9bca343 100644 --- a/Specs/Quint/voteBookkeeper.qnt +++ b/Specs/Quint/voteBookkeeper.qnt @@ -9,34 +9,40 @@ module voteBookkeeper { // Types // **************************************************************************** - // An address is an string + // Node address type Address = str - // A round is an integer + // Round in the Tendermint algorithm type Round = int - // A height is an integer + // Height in the Tendermint algorithm type Height = int - // A value is a string + // Value proposed and voted to be included in the chain type Value = str + val Nil: Value = "nil" // a special value of type Value - // Weight is an integer + // The stake of a node type Weight = int type VoteType = str + val VoteTypes: Set[VoteType] = Set("Prevote", "Precommit") + def isPrevote(voteType) = voteType == "Prevote" + def isPrecommit(voteType) = voteType == "Precommit" type Vote = { typ: VoteType, + height: Height, round: Round, value: Value, address: Address } + type WeightedVote = (Vote, Weight, Round) + type VoteCount = { totalWeight: Weight, - // includes nil - valuesWeights: Value -> Weight, + valuesWeights: Value -> Weight, // including the "nil" value votesAddresses: Set[Address] } @@ -53,12 +59,25 @@ module voteBookkeeper { name: str, value: Value } + val unreachedThreshold = { name: "Unreached", value: "null" } + val nilThreshold = { name: "Nil", value: "null" } + val anyThreshold = { name: "Any", value: "null" } + val skipThreshold = { name: "Skip", value: "null" } + pure def valueThreshold(value) = { name: "Value", value: value } + pure def isThresholdOnValue(t: Threshold): bool = t.name == "Value" type ExecutorEvent = { round: Round, name: str, value: Value } + pure def noEvent(round) = { round: round, name: "None", value: "null" } + pure def polkaValueEvent(round, value) = { round: round, name: "PolkaValue", value: value } + pure def polkaNilEvent(round) = { round: round, name: "PolkaNil", value: "null" } + pure def polkaAnyEvent(round) = { round: round, name: "PolkaAny", value: "null" } + pure def precommitValueEvent(round, value) = { round: round, name: "PrecommitValue", value: value } + pure def precommitAnyEvent(round) = { round: round, name: "PrecommitAny", value: "null" } + pure def skipEvent(round) = { round: round, name: "Skip", value: "null" } type Bookkeeper = { height: Height, @@ -72,6 +91,15 @@ module voteBookkeeper { // Internal functions + pure def newRoundVotes(height: Height, round: Round, totalWeight: Weight): RoundVotes = { + height: height, + round: round, + prevotes: newVoteCount(totalWeight), + precommits: newVoteCount(totalWeight), + emittedEvents: Set(), + votesAddressesWeights: Map() + } + // creates a new voteCount pure def newVoteCount(total: Weight): VoteCount = { totalWeight: total, valuesWeights: Map(), votesAddresses: Set() } @@ -80,10 +108,21 @@ module voteBookkeeper { pure def isQuorum(weight: Weight, total: Weight): bool = 3 * weight > 2 * total + run isQuorumTest = all { + assert(isQuorum(0,0) == false), + assert(isQuorum(2,6) == false), + assert(isQuorum(4,6) == false), + assert(isQuorum(5,6) == true), + } + // True iff the vote count has a quorum on a specific value. pure def hasQuorumOnValue(voteCount: VoteCount, value: Value): bool = isQuorum(voteCount.valuesWeights.getOrElse(value, 0), voteCount.totalWeight) + // True iff the vote count has a quorum on value nil. + pure def hasQuorumOnNil(voteCount: VoteCount): bool = + hasQuorumOnValue(voteCount, Nil) + // True iff the vote count has a quorum on any value. pure def hasQuorumOnAny(voteCount: VoteCount): bool = isQuorum(voteCount.valuesWeights.mapSumValues(), voteCount.totalWeight) @@ -92,7 +131,13 @@ module voteBookkeeper { pure def isSkip(weight: Weight, total: Weight): bool = 3 * weight > total - // Adds a vote of weight weigth to a voteCount if there is not vote registered for the voter. + run isSkipTest = all { + assert(isSkip(0,0) == false), + assert(isSkip(2,6) == false), + assert(isSkip(3,6) == true), + } + + // Adds a weighted vote to a voteCount if there is no vote registered for the voter. pure def addVote(voteCount: VoteCount, vote: Vote, weight: Weight): VoteCount = if (vote.address.in(voteCount.votesAddresses)) // Do not count vote if address has already voted. @@ -103,76 +148,109 @@ module voteBookkeeper { .with("valuesWeights", voteCount.valuesWeights.mapSafeSet(vote.value, newWeight)) .with("votesAddresses", voteCount.votesAddresses.setAdd(vote.address)) + run addVoteTest = + val voteCount = { + totalWeight: 100, + valuesWeights: Map("val1" -> 30, "val2" -> 20), + votesAddresses: Set("alice", "bob") + } + val vote = { typ: "Precommit", height: 1, round: 10, value: "val3", address: "john" } + all { + // new voter, new value + assert(addVote(voteCount, vote, 10) == { + totalWeight: 100, + valuesWeights: Map("val1" -> 30, "val2" -> 20, "val3" -> 10), + votesAddresses: Set("alice", "bob", "john") + }), + // new voter, existing value + assert(addVote(voteCount, vote.with("value", "val2"), 10) == { + totalWeight: 100, + valuesWeights: Map("val1" -> 30, "val2" -> 30), + votesAddresses: Set("alice", "bob", "john") + }), + // existing voter + assert(addVote(voteCount, vote.with("address", "alice"), 10) == voteCount), + } + // Given a voteCount and a value, the function returns: // - A threshold Value if there is a quorum for the given value; // - A threshold Nil if there is a quorum for the nil and no quorum for the value; - // - A threshold Any if there is no quorum for the value or nil and there is a quroum for any (including nil); + // - A threshold Any if there is no quorum for the value or nil and there is a quorum for any (including nil); // - A threshold Unreached otherwise indicating that no quorum has been yet reached. pure def computeThreshold(voteCount: VoteCount, value: Value): Threshold = if (voteCount.hasQuorumOnValue(value)) { - if (value == "nil") - { name: "Nil", value: "null" } + if (value == Nil) + nilThreshold else - { name: "Value", value: value } + valueThreshold(value) } else if (voteCount.hasQuorumOnAny()) { - { name: "Any", value: "null" } + anyThreshold } else - { name: "Unreached", value: "null" } + unreachedThreshold // Given a round, voteType and threshold, return the corresponding ExecutorEvent pure def toEvent(round: Round, voteType: VoteType, threshold: Threshold): ExecutorEvent = - if (threshold.name == "Unreached") - { round: round, name: "None", value: "null" } + if (threshold == unreachedThreshold) + noEvent(round) // prevote - else if (voteType == "Prevote" and threshold.name == "Value") - { round: round, name: "PolkaValue", value: threshold.value } - else if (voteType == "Prevote" and threshold.name == "Nil") - { round: round, name: "PolkaNil", value: "null" } - else if (voteType == "Prevote" and threshold.name == "Any") - { round: round, name: "PolkaAny", value: "null" } + else if (voteType.isPrevote() and threshold.isThresholdOnValue()) + polkaValueEvent(round, threshold.value) + else if (voteType.isPrevote() and threshold == nilThreshold) + polkaNilEvent(round) + else if (voteType.isPrevote() and threshold == anyThreshold) + polkaAnyEvent(round) // precommit - else if (voteType == "Precommit" and threshold.name == "Value") - { round: round, name: "PrecommitValue", value: threshold.value } - else if (voteType == "Precommit" and threshold.name == "Any") - { round: round, name: "PrecommitAny", value: "null" } - else if (voteType == "Precommit" and threshold.name == "Nil") - { round: round, name: "PrecommitAny", value: "null" } - - else if (threshold.name == "Skip") - { round: round, name: "Skip", value: "null" } + else if (voteType.isPrecommit() and threshold.isThresholdOnValue()) + precommitValueEvent(round, threshold.value) + else if (voteType.isPrecommit() and threshold.in(Set(anyThreshold, nilThreshold))) + precommitAnyEvent(round) + + else if (threshold == skipThreshold) + skipEvent(round) else - { round: round, name: "None", value: "null" } + noEvent(round) + + run toEventTest = + val round = 10 + all { + assert(toEvent(round, "Prevote", unreachedThreshold) == noEvent(round)), + assert(toEvent(round, "Precommit", unreachedThreshold) == noEvent(round)), + + assert(toEvent(round, "Prevote", anyThreshold) == polkaAnyEvent(round)), + assert(toEvent(round, "Prevote", nilThreshold) == polkaNilEvent(round)), + assert(toEvent(round, "Prevote", valueThreshold("val1")) == polkaValueEvent(round, "val1")), + + assert(toEvent(round, "Precommit", anyThreshold) == precommitAnyEvent(round)), + assert(toEvent(round, "Precommit", nilThreshold) == precommitAnyEvent(round)), + assert(toEvent(round, "Precommit", valueThreshold("val1")) == precommitValueEvent(round, "val1")), + + assert(toEvent(round, "Prevote", skipThreshold) == skipEvent(round)), + assert(toEvent(round, "Precommit", skipThreshold) == skipEvent(round)), + + assert(toEvent(round, "Precommit", { name: "Error", value: "null" }) == noEvent(round)), + assert(toEvent(round, "Error", anyThreshold) == noEvent(round)), + } // Executor interface type BKResult = { bookkeeper: Bookkeeper, event: ExecutorEvent } // Called by the executor when it receives a vote. The function takes the following steps: // - It first adds the vote and then computes a threshold. - // - If there exist a threshold and has not emitted before, the function returns the corresponsing ExecutorEvent. + // - If there exist a threshold and has not emitted before, the function returns the corresponding ExecutorEvent. // - Otherwise, the function returns a no-threshold event. // - Note that if there is no threshold after adding the vote, the function checks if there is a skip threshold. // 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 } = - val height = keeper.height - val total = keeper.totalWeight - - val defaultRoundVotes = { - height: height, - round: vote.round, - prevotes: newVoteCount(total), - precommits: newVoteCount(total), - emittedEvents: Set(), - votesAddressesWeights: Map() - } - val roundVotes = keeper.rounds.getOrElse(vote.round, defaultRoundVotes) + val round = vote.round + val roundVotes = keeper.rounds.getOrElse(round, newRoundVotes(keeper.height, round, keeper.totalWeight)) val updatedVoteCount = - if (vote.typ == "Prevote") + if (vote.typ.isPrevote()) roundVotes.prevotes.addVote(vote, weight) else roundVotes.precommits.addVote(vote, weight) @@ -183,40 +261,57 @@ module voteBookkeeper { else roundVotes.votesAddressesWeights.mapSafeSet(vote.address, weight) - 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 > currentRound and isSkip(combinedWeight, total)) - { round: vote.round, name: "Skip", value: "null" } + if (vote.round > currentRound and isSkip(combinedWeight, keeper.totalWeight)) + skipEvent(vote.round) 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") + noEvent(vote.round) + val updatedRoundVotes = + if (vote.typ.isPrevote()) + roundVotes.with("prevotes", updatedVoteCount) + else + roundVotes.with("precommits", updatedVoteCount) val updatedRoundVotes2 = updatedRoundVotes - .with("votesAddressesWeights", updatedVotesAddressesWeights) - .with("emittedEvents", updatedEmittedEvents) - - val newBookkeeper = - keeper.with("rounds", keeper.rounds.mapSafeSet(vote.round, updatedRoundVotes2)) - - { - bookkeeper: newBookkeeper, - event: finalEvent + .with("votesAddressesWeights", updatedVotesAddressesWeights) + .with("emittedEvents", roundVotes.emittedEvents.setAddIf(finalEvent, finalEvent.name != "None")) + + val updatedBookkeeper = keeper + .with("rounds", keeper.rounds.mapSafeSet(vote.round, updatedRoundVotes2)) + + { bookkeeper: updatedBookkeeper, event: finalEvent } + + run applyVoteTest = + val roundVotes: RoundVotes = { + height: 0, + round: 0, + prevotes: { totalWeight: 4, votesAddresses: Set(), valuesWeights: Map("v1" -> 1, Nil -> 3) }, + precommits: { totalWeight: 4, votesAddresses: Set(), valuesWeights: Map() }, + emittedEvents: Set(), + votesAddressesWeights: Map(), } - + val vk: Bookkeeper = { height: 0, totalWeight: 4, rounds: Map(0 -> roundVotes) } + val o1 = applyVote(vk, { height: 0, round: 0, address: "a0", typ: "Precommit", value: Nil }, 1, 0) + val o2 = applyVote(o1.bookkeeper, { height: 0, round: 0, address: "a1", typ: "Precommit", value: Nil }, 1, 0) + val o3 = applyVote(o2.bookkeeper, { height: 0, round: 0, address: "a2", typ: "Precommit", value: Nil }, 1, 0) + val o4 = applyVote(o3.bookkeeper, { height: 0, round: 0, address: "a3", typ: "Precommit", value: Nil }, 1, 0) + val o5 = applyVote(o4.bookkeeper, { height: 0, round: 1, address: "a4", typ: "Precommit", value: Nil }, 3, 0) + all { + assert(o1.event.name == "None"), + assert(o2.event.name == "None"), + assert(o3.event.name == "PrecommitAny"), + assert(o4.event.name == "None"), + assert(o5.event.name == "Skip"), + } + // Called by the executor to check if there is a specific threshold for a given round and voteType. // TO DISCUSS: // - The function does not consider Skip threshold. This because if the executor receives a Skip event @@ -225,108 +320,92 @@ module voteBookkeeper { pure def checkThreshold(keeper: Bookkeeper, round: Round, voteType: VoteType, threshold: Threshold): bool = if (keeper.rounds.has(round)) { val roundVotes = keeper.rounds.get(round) - val voteCount = if (voteType == "Prevote") roundVotes.prevotes else roundVotes.precommits - if (threshold.name == "Value") { - voteCount.hasQuorumOnValue(threshold.value) - } else if (threshold.name == "Nil") { - voteCount.hasQuorumOnValue("nil") - } else if (threshold.name == "Any") { - voteCount.hasQuorumOnAny() - } else false + val voteCount = if (voteType.isPrevote()) roundVotes.prevotes else roundVotes.precommits + checkThresholdOnVoteCount(threshold, voteCount) } else false -// run PrecommitTest = all { -// val bin : Bookkeeper = { -// height: 0, -// rounds: -// Map( -// 0 -> -// { -// height: 0, -// precommits: { total: 4, valuesAddresses: Map(), valuesWeights: Map() }, -// prevotes: { total: 4, valuesAddresses: Map(), valuesWeights: Map("a block" -> 1, "nil" -> 3) }, -// round: 0 -// } -// ), -// totalWeight: 4 -// } -// val o1 = applyVote(bin, {value: "nil", round: 0, address: "v0", typ: "Precommit" }, 1) -// val o2 = applyVote(o1.bookkeeper, {value: "nil", round: 0, address: "v1", typ: "Precommit" }, 1) -// val o3 = applyVote(o2.bookkeeper, {value: "nil", round: 0, address: "v2", typ: "Precommit" }, 1) -// val o4 = applyVote(o3.bookkeeper, {value: "nil", round: 0, address: "v3", typ: "Precommit" }, 1) -// all{ -// assert(o1.event.name == "None"), -// assert(o2.event.name == "None"), -// assert(o3.event.name == "PrecommitAny"), -// assert(o4.event.name == "PrecommitAny") -// } -// } + pure def checkThresholdOnVoteCount(threshold: Threshold, voteCount: VoteCount): bool = + if (threshold.isThresholdOnValue()) + voteCount.hasQuorumOnValue(threshold.value) + else if (threshold == nilThreshold) + voteCount.hasQuorumOnNil() + else if (threshold == anyThreshold) + voteCount.hasQuorumOnAny() + else false - // **************************************************************************** - // Unit tests - // **************************************************************************** + run computeThresholdTest = + val voteCount = { totalWeight: 100, valuesWeights: Map(), votesAddresses: Set("alice", "bob") } + val mapValueReached = Map("val1" -> 67, "val2" -> 20) + val mapNilReached = Map(Nil -> 70, "val2" -> 20) + val mapNoneReached = Map(Nil -> 20, "val2" -> 20) + all { + assert(computeThreshold(voteCount, "val3") == unreachedThreshold), + assert(computeThreshold(voteCount.with("valuesWeights", mapValueReached), "val1") == valueThreshold("val1")), + assert(computeThreshold(voteCount.with("valuesWeights", mapValueReached), "val2") == anyThreshold), + assert(computeThreshold(voteCount.with("valuesWeights", mapNilReached), Nil) == nilThreshold), + assert(computeThreshold(voteCount.with("valuesWeights", mapNilReached), "val2") == anyThreshold), + assert(computeThreshold(voteCount.with("valuesWeights", mapNoneReached), "val1") == unreachedThreshold), + assert(computeThreshold(voteCount.with("valuesWeights", mapNoneReached), Nil) == unreachedThreshold), + } - run isQuorumTest = all { - assert(isQuorum(0,0) == false), - assert(isQuorum(2,6) == false), - assert(isQuorum(4,6) == false), - assert(isQuorum(5,6) == true) - } + // ************************************************************************ + // Properties/Invariants + // ************************************************************************ - run isSkipTest = all { - assert(isSkip(0,0) == false), - assert(isSkip(2,6) == false), - assert(isSkip(3,6) == true) + // Each weight in a voteCount is less or equal than the total weight. + def voteValidWeightInv(voteCount) = + voteCount.valuesWeights.forallValues(weight => weight <= voteCount.totalWeight) + + // The sum of all weights is less or equal than the total weight. + def voteValidWeightSumInv(voteCount: VoteCount): bool = + voteCount.valuesWeights.mapSumValues() <= voteCount.totalWeight + + def roundVotesInv(rounds: Round -> RoundVotes): bool = + rounds.forallEntries((round, roundVotes) => all { + voteValidWeightInv(roundVotes.prevotes), + voteValidWeightInv(roundVotes.precommits), + voteValidWeightSumInv(roundVotes.prevotes), + voteValidWeightSumInv(roundVotes.precommits), + }) + + def Inv = all { + roundVotesInv(bookkeeper.rounds) } - run addVoteTest = - val voteCount = {totalWeight: 100, valuesWeights: Map("val1" -> 30, "val2" -> 20), votesAddresses: Set("alice", "bob")} - val vote = {typ: "precommit", round: 10, value: "val3", address: "john"} - all { - // new voter, new value - assert(addVote(voteCount, vote, 10) == {totalWeight: 100, valuesWeights: Map("val1" -> 30, "val2" -> 20, "val3" -> 10), votesAddresses: Set("alice", "bob", "john")}), - // new voter, existing value - assert(addVote(voteCount, vote.with("value", "val2"), 10) == {totalWeight: 100, valuesWeights: Map("val1" -> 30, "val2" -> 30), votesAddresses: Set("alice", "bob", "john")}), - // existing voter - assert(addVote(voteCount, vote.with("address", "alice"), 10) == voteCount), - } + // ************************************************************************ + // State + // ************************************************************************ + + // The value used as parameter on each action taken. + var weightedVote: WeightedVote + // The state of the Bookkeeper. + var bookkeeper: Bookkeeper + // The event resulting from applying a weighted vote to the bookkeeper. + var lastEmitted: ExecutorEvent + + // ************************************************************************ + // Actions + // ************************************************************************ + + action allUnchanged: bool = all { + weightedVote' = weightedVote, + bookkeeper' = bookkeeper, + lastEmitted' = lastEmitted, + } - run computeThresholdTest = - val voteCount = {totalWeight: 100, valuesWeights: Map(), votesAddresses: Set("alice", "bob")} - val mapValueReached = Map("val1" -> 67, "val2" -> 20) - val mapNilReached = Map("nil" -> 70, "val2" -> 20) - val mapNoneReached = Map("nil" -> 20, "val2" -> 20) - all { - assert(computeThreshold(voteCount, "val3") == {name: "Unreached", value: "null"}), - assert(computeThreshold(voteCount.with("valuesWeights", mapValueReached), "val1") == {name: "Value", value: "val1"}), - assert(computeThreshold(voteCount.with("valuesWeights", mapValueReached), "val2") == {name: "Any", value: "null"}), - assert(computeThreshold(voteCount.with("valuesWeights", mapNilReached), "nil") == {name: "Nil", value: "null"}), - assert(computeThreshold(voteCount.with("valuesWeights", mapNilReached), "val2") == {name: "Any", value: "null"}), - assert(computeThreshold(voteCount.with("valuesWeights", mapNoneReached), "val1") == {name: "Unreached", value: "null"}), - assert(computeThreshold(voteCount.with("valuesWeights", mapNoneReached), "nil") == {name: "Unreached", value: "null"}), - } + action initWith(initialHeight: Height, totalWeight: Weight): bool = all { + weightedVote' = ({ typ: "", height: initialHeight, round: -1, value: "", address: "" }, -1, -1), + bookkeeper' = { height: initialHeight, totalWeight: totalWeight, rounds: Map() }, + lastEmitted' = { round: -1, name: "", value: "null" } + } - run toEventTest = - val thresholdUnreached = {name: "Unreached", value: "null"} - val thresholdAny = {name: "Any", value: "null"} - val thresholdNil = {name: "Nil", value: "null"} - val thresholdValue = {name: "Value", value: "val1"} - val thresholdSkip = {name: "Skip", value: "null"} - val round = 10 + // The vote keeper receives a weighted vote and produces an event. + action applyVoteAction(vote: Vote, weight: Weight, currentRound: Round): bool = + val result = applyVote(bookkeeper, vote, weight, currentRound) all { - assert(toEvent(round, "Prevote", thresholdUnreached) == {round: round, name: "None", value: "null"}), - assert(toEvent(round, "Precommit", thresholdUnreached) == {round: round, name: "None", value: "null"}), - assert(toEvent(round, "Prevote", thresholdAny) == {round: round, name: "PolkaAny", value: "null"}), - assert(toEvent(round, "Prevote", thresholdNil) == {round: round, name: "PolkaNil", value: "null"}), - assert(toEvent(round, "Prevote", thresholdValue) == {round: round, name: "PolkaValue", value: "val1"}), - assert(toEvent(round, "Precommit", thresholdAny) == {round: round, name: "PrecommitAny", value: "null"}), - assert(toEvent(round, "Precommit", thresholdNil) == {round: round, name: "PrecommitAny", value: "null"}), - assert(toEvent(round, "Precommit", thresholdValue) == {round: round, name: "PrecommitValue", value: "val1"}), - assert(toEvent(round, "Prevote", thresholdSkip) == {round: round, name: "Skip", value: "null"}), - assert(toEvent(round, "Precommit", thresholdSkip) == {round: round, name: "Skip", value: "null"}), - assert(toEvent(round, "Precommit", {name: "Error", value: "null"}) == {round: round, name: "None", value: "null"}), - assert(toEvent(round, "Error", thresholdAny) == {round: round, name: "None", value: "null"}), + weightedVote' = (vote, weight, currentRound), + bookkeeper' = result.bookkeeper, + lastEmitted' = result.event } } - diff --git a/Specs/Quint/voteBookkeeperSM.qnt b/Specs/Quint/voteBookkeeperSM.qnt new file mode 100644 index 000000000..e52921c48 --- /dev/null +++ b/Specs/Quint/voteBookkeeperSM.qnt @@ -0,0 +1,36 @@ +// **************************************************************************** +// Vote Bookkeeper State machine +// **************************************************************************** + +module voteBookkeeperSM { + + import voteBookkeeper.* from "./voteBookkeeper" + export voteBookkeeper.* + + // ************************************************************************ + // Model parameters + // ************************************************************************ + + const INITIAL_HEIGHT: Height + const INITIAL_TOTAL_WEIGHT: Weight + const ADDRESS_WEIGHTS: Address -> Weight // an address has a constant weight during a height + const ROUNDS: Set[Round] + const VALUES: Set[Value] + + // ************************************************************************ + // State machine + // ************************************************************************ + + action init = + initWith(INITIAL_HEIGHT, INITIAL_TOTAL_WEIGHT) + + action step = + nondet voteType = oneOf(VoteTypes) + nondet round = oneOf(ROUNDS) + nondet value = oneOf(VALUES.union(Set(Nil))) + nondet address = oneOf(ADDRESS_WEIGHTS.keys()) + val height = INITIAL_HEIGHT + val vote: Vote = { typ: voteType, height: height, round: round, value: value, address: address } + applyVoteAction(vote, ADDRESS_WEIGHTS.get(address), 1) + +} diff --git a/Specs/Quint/voteBookkeeperTest.qnt b/Specs/Quint/voteBookkeeperTest.qnt index 4541bb798..44419ca27 100644 --- a/Specs/Quint/voteBookkeeperTest.qnt +++ b/Specs/Quint/voteBookkeeperTest.qnt @@ -1,143 +1,134 @@ module voteBookkeeperTest { - import voteBookkeeper.* from "./voteBookkeeper" + import voteBookkeeperSM( + INITIAL_HEIGHT = 1, + INITIAL_TOTAL_WEIGHT = 100, + ADDRESS_WEIGHTS = Map("alice" -> 10, "bob" -> 30, "john" -> 60), + ROUNDS = 1.to(2), + VALUES = Set("val1", "val2") + ).* from "./voteBookkeeperSM" // **************************************************************************** - // State machine state - // **************************************************************************** - - // Bookkeeper state - var bookkeeper: Bookkeeper - // Last emitted event - var lastEmitted: ExecutorEvent - - // **************************************************************************** - // Execution - // **************************************************************************** - - action allUnchanged: bool = all { - bookkeeper' = bookkeeper, - lastEmitted' = lastEmitted, - } - - action initWith(totalWeight: Weight): bool = all { - bookkeeper' = { height: 10, 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) - all { - bookkeeper' = result.bookkeeper, - lastEmitted' = result.event, - } - - // **************************************************************************** - // Test traces + // Tests // **************************************************************************** // auxiliary action for tests action _assert(predicate: bool): bool = - all { predicate, allUnchanged } + all { assert(predicate), allUnchanged } // Consensus full execution with all honest validators (including the leader) and a synchronous network: - // all messages are received in order. We assume three validators in the validator set wtih 60%, 30% and 10% + // all messages are received in order. We assume three validators in the validator set with 60%, 30% and 10% // each of the total voting power run synchronousConsensusTest = - initWith(100) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 60, 1)) - .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "john"}, 10, 1)) - .then(_assert(lastEmitted == {round: 1, name: "PolkaValue", value: "proposal"})) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "bob"}, 30, 1)) - .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Precommit", round: 1, value: "proposal", address: "bob"}, 30, 1)) - .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Precommit", round: 1, value: "proposal", address: "john"}, 10, 1)) - .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Precommit", round: 1, value: "proposal", address: "alice"}, 60, 1)) - .then(_assert(lastEmitted == {round: 1, name: "PrecommitValue", value: "proposal"})) + initWith(1, 100) + .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "val1", address: "alice"}, 60, 1)) + .then(_assert(lastEmitted == noEvent(1))) + .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "val1", address: "john"}, 10, 1)) + .then(_assert(lastEmitted == polkaValueEvent(1, "val1"))) + .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "val1", address: "bob"}, 30, 1)) + .then(_assert(lastEmitted == noEvent(1))) + .then(applyVoteAction({typ: "Precommit", height: 1, round: 1, value: "val1", address: "bob"}, 30, 1)) + .then(_assert(lastEmitted == noEvent(1))) + .then(applyVoteAction({typ: "Precommit", height: 1, round: 1, value: "val1", address: "john"}, 10, 1)) + .then(_assert(lastEmitted == noEvent(1))) + .then(applyVoteAction({typ: "Precommit", height: 1, round: 1, value: "val1", address: "alice"}, 60, 1)) + .then(_assert(lastEmitted == precommitValueEvent(1, "val1"))) // Reaching PolkaAny run polkaAnyTest = - initWith(100) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "val1", address: "alice"}, 60, 1)) - .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "nil", address: "john"}, 10, 1)) - .then(_assert(lastEmitted == {round: 1, name: "PolkaAny", value: "null"})) + initWith(1, 100) + .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "val1", address: "alice"}, 60, 1)) + .then(_assert(lastEmitted == noEvent(1))) + .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "nil", address: "john"}, 10, 1)) + .then(_assert(lastEmitted == polkaAnyEvent(1))) // Reaching PolkaNil run polkaNilTest = - initWith(100) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "nil", address: "alice"}, 60, 1)) - .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "nil", address: "john"}, 10, 1)) - .then(_assert(lastEmitted == {round: 1, name: "PolkaNil", value: "null"})) + initWith(1, 100) + .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "nil", address: "alice"}, 60, 1)) + .then(_assert(lastEmitted == noEvent(1))) + .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "nil", address: "john"}, 10, 1)) + .then(_assert(lastEmitted == polkaNilEvent(1))) // Reaching Skip via n+1 threshold with prevotes from two validators at a future round run skipSmallQuorumAllPrevotesTest = - initWith(100) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 60, 1)) - .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 10, 1)) - .then(_assert(lastEmitted == {round: 2, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "bob"}, 30, 1)) - .then(_assert(lastEmitted == {round: 2, name: "Skip", value: "null"})) + initWith(1, 100) + .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "val1", address: "alice"}, 60, 1)) + .then(_assert(lastEmitted == noEvent(1))) + .then(applyVoteAction({typ: "Prevote", height: 1, round: 2, value: "val1", address: "john"}, 10, 1)) + .then(_assert(lastEmitted == noEvent(2))) + .then(applyVoteAction({typ: "Prevote", height: 1, round: 2, value: "val1", address: "bob"}, 30, 1)) + .then(_assert(lastEmitted == skipEvent(2))) // Cannot reach Skip via f+1 threshold with one prevote and one precommit from the same validator at a future round run noSkipSmallQuorumMixedVotesSameValTest = - initWith(90) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 10, 1)) - .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 20, 1)) - .then(_assert(lastEmitted == {round: 2, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Precommit", round: 2, value: "proposal", address: "john"}, 20, 1)) - .then(_assert(lastEmitted != {round: 2, name: "Skip", value: "null"})) + initWith(1, 90) + .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "val1", address: "alice"}, 10, 1)) + .then(_assert(lastEmitted == noEvent(1))) + .then(applyVoteAction({typ: "Prevote", height: 1, round: 2, value: "val1", address: "john"}, 20, 1)) + .then(_assert(lastEmitted == noEvent(2))) + .then(applyVoteAction({typ: "Precommit", height: 1, round: 2, value: "val1", address: "john"}, 20, 1)) + .then(_assert(lastEmitted != skipEvent(2))) // Reaching Skip via f+1 threshold with one prevote and one precommit from two validators at a future round run skipSmallQuorumMixedVotesTwoValsTest = - initWith(80) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 50, 1)) - .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 10, 1)) - .then(_assert(lastEmitted == {round: 2, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Precommit", round: 2, value: "proposal", address: "bob"}, 20, 1)) - .then(_assert(lastEmitted == {round: 2, name: "Skip", value: "null"})) - + initWith(1, 80) + .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "val1", address: "alice"}, 50, 1)) + .then(_assert(lastEmitted == noEvent(1))) + .then(applyVoteAction({typ: "Prevote", height: 1, round: 2, value: "val1", address: "john"}, 10, 1)) + .then(_assert(lastEmitted == noEvent(2))) + .then(applyVoteAction({typ: "Precommit", height: 1, round: 2, value: "val1", address: "bob"}, 20, 1)) + .then(_assert(lastEmitted == skipEvent(2))) + // Reaching Skip via 2f+1 threshold with a single prevote from a single validator at a future round run skipQuorumSinglePrevoteTest = - initWith(100) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 10, 1)) - .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 60, 1)) - .then(_assert(lastEmitted == {round: 2, name: "Skip", value: "null"})) + initWith(1, 100) + .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "val1", address: "alice"}, 10, 1)) + .then(all { assert(lastEmitted == noEvent(1)), allUnchanged }) + .then(applyVoteAction({typ: "Prevote", height: 1, round: 2, value: "val1", address: "john"}, 60, 1)) + .then(all { assert(lastEmitted == skipEvent(2)), allUnchanged }) // Reaching Skip via 2f+1 threshold with a single precommit from a single validator at a future round run skipQuorumSinglePrecommitTest = - initWith(100) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 10, 1)) + initWith(1, 100) + .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "val1", address: "alice"}, 10, 1)) .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Precommit", round: 2, value: "proposal", address: "john"}, 60, 1)) - .then(_assert(lastEmitted == {round: 2, name: "Skip", value: "null"})) + .then(applyVoteAction({typ: "Precommit", height: 1, round: 2, value: "val1", address: "john"}, 60, 1)) + .then(_assert(lastEmitted == skipEvent(2))) // Cannot reach Skip via 2f+1 threshold with one prevote and one precommit from the same validator at a future round run noSkipQuorumMixedVotesSameValTest = - initWith(100) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 10, 1)) - .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 30, 1)) - .then(_assert(lastEmitted == {round: 2, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Precommit", round: 2, value: "proposal", address: "john"}, 30, 1)) - .then(_assert(lastEmitted != {round: 2, name: "Skip", value: "null"})) + initWith(1, 100) + .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "val1", address: "alice"}, 10, 1)) + .then(_assert(lastEmitted == noEvent(1))) + .then(applyVoteAction({typ: "Prevote", height: 1, round: 2, value: "val1", address: "john"}, 30, 1)) + .then(_assert(lastEmitted == noEvent(2))) + .then(applyVoteAction({typ: "Precommit", height: 1, round: 2, value: "val1", address: "john"}, 30, 1)) + .then(_assert(lastEmitted != skipEvent(2))) // Reaching Skip via 2f+1 threshold with one prevote and one precommit from two validators at a future round run skipQuorumMixedVotesTwoValsTest = - initWith(80) - .then(applyVoteAction({typ: "Prevote", round: 1, value: "proposal", address: "alice"}, 20, 1)) - .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Prevote", round: 2, value: "proposal", address: "john"}, 10, 1)) - .then(_assert(lastEmitted == {round: 2, name: "None", value: "null"})) - .then(applyVoteAction({typ: "Precommit", round: 2, value: "proposal", address: "bob"}, 50, 1)) - .then(_assert(lastEmitted == {round: 2, name: "Skip", value: "null"})) + initWith(1, 80) + .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "val1", address: "alice"}, 20, 1)) + .then(_assert(lastEmitted == noEvent(1))) + .then(applyVoteAction({typ: "Prevote", height: 1, round: 2, value: "val1", address: "john"}, 10, 1)) + .then(_assert(lastEmitted == noEvent(2))) + .then(applyVoteAction({typ: "Precommit", height: 1, round: 2, value: "val1", address: "bob"}, 50, 1)) + .then(_assert(lastEmitted == skipEvent(2))) + + // **************************************************************************** + // Properties that define an expected final state (for generating traces) + // **************************************************************************** + + val emitPrecommitValueState = lastEmitted.name == "PrecommitValue" + val emitPrecommitValue = not(emitPrecommitValueState) + + val emitPolkaAnyState = lastEmitted.name == "PolkaAny" + val emitPolkaAny = not(emitPolkaAnyState) + + val emitPolkaNilState = lastEmitted.name == "PolkaNil" + val emitPolkaNil = not(emitPolkaNilState) + val emitSkipState = lastEmitted.name == "Skip" + val emitSkip = not(emitSkipState) } From 7563350374518c863f736164230d1ec79aa08ab0 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Tue, 28 Nov 2023 11:14:35 +0100 Subject: [PATCH 5/7] Create dependabot.yml --- .github/dependabot.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..3884971d8 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "/Code" + schedule: + interval: "weekly" From 4c5a1a741738589fca3ecb604291294a1aade382 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 30 Nov 2023 09:03:22 +0100 Subject: [PATCH 6/7] code: Rename `Event` to `Input` and `Message` to `Output` (#93) * Rename `Message` to `Output` across the project * Cleanup doc comments * Rename `Event` to `Input` across the project * Cleanup --- Code/QUESTIONS.md | 1 - Code/TODO.md | 11 - Code/driver/src/driver.rs | 72 +++--- Code/driver/src/{event.rs => input.rs} | 2 +- Code/driver/src/lib.rs | 8 +- Code/driver/src/message.rs | 93 ------- Code/driver/src/output.rs | 93 +++++++ Code/itf/tests/votekeeper/runner.rs | 16 +- Code/round/src/events.rs | 25 -- Code/round/src/input.rs | 75 ++++++ Code/round/src/lib.rs | 4 +- Code/round/src/{message.rs => output.rs} | 64 ++--- Code/round/src/state.rs | 6 +- Code/round/src/state_machine.rs | 121 +++++---- Code/round/src/transition.rs | 12 +- Code/test/src/utils.rs | 88 ++++--- Code/test/tests/driver.rs | 310 +++++++++++------------ Code/test/tests/driver_extra.rs | 150 +++++------ Code/test/tests/round.rs | 32 +-- Code/test/tests/vote_keeper.rs | 20 +- Code/vote/src/keeper.rs | 34 +-- 21 files changed, 639 insertions(+), 598 deletions(-) delete mode 100644 Code/QUESTIONS.md delete mode 100644 Code/TODO.md rename Code/driver/src/{event.rs => input.rs} (96%) delete mode 100644 Code/driver/src/message.rs create mode 100644 Code/driver/src/output.rs delete mode 100644 Code/round/src/events.rs create mode 100644 Code/round/src/input.rs rename Code/round/src/{message.rs => output.rs} (50%) diff --git a/Code/QUESTIONS.md b/Code/QUESTIONS.md deleted file mode 100644 index 700c9fae4..000000000 --- a/Code/QUESTIONS.md +++ /dev/null @@ -1 +0,0 @@ -- How do we deal with errors? diff --git a/Code/TODO.md b/Code/TODO.md deleted file mode 100644 index 8a7878e9e..000000000 --- a/Code/TODO.md +++ /dev/null @@ -1,11 +0,0 @@ -is proposal complete -if polka not nil, then need to see proof of lock (2f+1 votes) -then send proposal - -count votes for cur, prev, 1/2 next round - -if complete proposal from a past round => to current one -if we have some threshold (f+1) of votes for a future round => skip to that round - -context (get proposer, get value) -signing context diff --git a/Code/driver/src/driver.rs b/Code/driver/src/driver.rs index 3fd8ced40..2873e77fa 100644 --- a/Code/driver/src/driver.rs +++ b/Code/driver/src/driver.rs @@ -5,17 +5,17 @@ use malachite_common::{ Context, Proposal, Round, SignedVote, Timeout, TimeoutStep, Validator, ValidatorSet, Value, Vote, VoteType, }; -use malachite_round::events::Event as RoundEvent; -use malachite_round::message::Message as RoundMessage; +use malachite_round::input::Input as RoundEvent; +use malachite_round::output::Output as RoundOutput; use malachite_round::state::{State as RoundState, Step}; use malachite_round::state_machine::Info; -use malachite_vote::keeper::Message as VoteMessage; +use malachite_vote::keeper::Output as VoteMessage; use malachite_vote::keeper::VoteKeeper; use malachite_vote::Threshold; use malachite_vote::ThresholdParams; -use crate::event::Event; -use crate::message::Message; +use crate::input::Input; +use crate::output::Output; use crate::Error; use crate::ProposerSelector; use crate::Validity; @@ -81,47 +81,47 @@ where Ok(proposer) } - pub async fn execute(&mut self, msg: Event) -> Result>, Error> { - let round_msg = match self.apply(msg).await? { + pub async fn execute(&mut self, msg: Input) -> Result>, Error> { + let round_output = match self.apply(msg).await? { Some(msg) => msg, None => return Ok(None), }; - let msg = match round_msg { - RoundMessage::NewRound(round) => Message::NewRound(self.height().clone(), round), + let output = match round_output { + RoundOutput::NewRound(round) => Output::NewRound(self.height().clone(), round), - RoundMessage::Proposal(proposal) => { - // sign the proposal - Message::Propose(proposal) + RoundOutput::Proposal(proposal) => { + // TODO: sign the proposal + Output::Propose(proposal) } - RoundMessage::Vote(vote) => { + RoundOutput::Vote(vote) => { let signed_vote = self.ctx.sign_vote(vote); - Message::Vote(signed_vote) + Output::Vote(signed_vote) } - RoundMessage::ScheduleTimeout(timeout) => Message::ScheduleTimeout(timeout), + RoundOutput::ScheduleTimeout(timeout) => Output::ScheduleTimeout(timeout), - RoundMessage::GetValueAndScheduleTimeout(round, timeout) => { - Message::GetValueAndScheduleTimeout(round, timeout) + RoundOutput::GetValueAndScheduleTimeout(round, timeout) => { + Output::GetValueAndScheduleTimeout(round, timeout) } - RoundMessage::Decision(value) => { + RoundOutput::Decision(value) => { // TODO: update the state - Message::Decide(value.round, value.value) + Output::Decide(value.round, value.value) } }; - Ok(Some(msg)) + Ok(Some(output)) } - async fn apply(&mut self, event: Event) -> Result>, Error> { - match event { - Event::NewRound(height, round) => self.apply_new_round(height, round).await, - Event::ProposeValue(round, value) => self.apply_propose_value(round, value).await, - Event::Proposal(proposal, validity) => self.apply_proposal(proposal, validity).await, - Event::Vote(signed_vote) => self.apply_vote(signed_vote), - Event::TimeoutElapsed(timeout) => self.apply_timeout(timeout), + async fn apply(&mut self, input: Input) -> Result>, Error> { + match input { + Input::NewRound(height, round) => self.apply_new_round(height, round).await, + Input::ProposeValue(round, value) => self.apply_propose_value(round, value).await, + Input::Proposal(proposal, validity) => self.apply_proposal(proposal, validity).await, + Input::Vote(signed_vote) => self.apply_vote(signed_vote), + Input::TimeoutElapsed(timeout) => self.apply_timeout(timeout), } } @@ -129,7 +129,7 @@ where &mut self, height: Ctx::Height, round: Round, - ) -> Result>, Error> { + ) -> Result>, Error> { if self.height() == &height { // If it's a new round for same height, just reset the round, keep the valid and locked values self.round_state.round = round; @@ -143,7 +143,7 @@ where &mut self, round: Round, value: Ctx::Value, - ) -> Result>, Error> { + ) -> Result>, Error> { self.apply_event(round, RoundEvent::ProposeValue(value)) } @@ -151,7 +151,7 @@ where &mut self, proposal: Ctx::Proposal, validity: Validity, - ) -> Result>, Error> { + ) -> Result>, Error> { // Check that there is an ongoing round if self.round_state.round == Round::Nil { return Ok(None); @@ -241,7 +241,7 @@ where fn apply_vote( &mut self, signed_vote: SignedVote, - ) -> Result>, Error> { + ) -> Result>, Error> { let validator = self .validator_set .get_by_address(signed_vote.validator_address()) @@ -279,7 +279,7 @@ where self.apply_event(vote_round, round_event) } - fn apply_timeout(&mut self, timeout: Timeout) -> Result>, Error> { + fn apply_timeout(&mut self, timeout: Timeout) -> Result>, Error> { let event = match timeout.step { TimeoutStep::Propose => RoundEvent::TimeoutPropose, TimeoutStep::Prevote => RoundEvent::TimeoutPrevote, @@ -294,7 +294,7 @@ where &mut self, event_round: Round, event: RoundEvent, - ) -> Result>, Error> { + ) -> Result>, Error> { let round_state = core::mem::take(&mut self.round_state); let proposer = self.get_proposer(round_state.round)?; @@ -319,13 +319,13 @@ where }; // Apply the event to the round state machine - let transition = round_state.apply_event(&data, mux_event); + let transition = round_state.apply(&data, mux_event); // Update state self.round_state = transition.next_state; - // Return message, if any - Ok(transition.message) + // Return output, if any + Ok(transition.output) } } diff --git a/Code/driver/src/event.rs b/Code/driver/src/input.rs similarity index 96% rename from Code/driver/src/event.rs rename to Code/driver/src/input.rs index b2cf5f726..eda6d7944 100644 --- a/Code/driver/src/event.rs +++ b/Code/driver/src/input.rs @@ -4,7 +4,7 @@ use crate::Validity; /// Events that can be received by the [`Driver`](crate::Driver). #[derive(Clone, Debug, PartialEq, Eq)] -pub enum Event +pub enum Input where Ctx: Context, { diff --git a/Code/driver/src/lib.rs b/Code/driver/src/lib.rs index 6fba7865f..95c0f560f 100644 --- a/Code/driver/src/lib.rs +++ b/Code/driver/src/lib.rs @@ -16,15 +16,15 @@ extern crate alloc; mod driver; mod error; -mod event; -mod message; +mod input; +mod output; mod proposer; mod util; pub use driver::Driver; pub use error::Error; -pub use event::Event; -pub use message::Message; +pub use input::Input; +pub use output::Output; pub use proposer::ProposerSelector; pub use util::Validity; diff --git a/Code/driver/src/message.rs b/Code/driver/src/message.rs deleted file mode 100644 index 52b354d80..000000000 --- a/Code/driver/src/message.rs +++ /dev/null @@ -1,93 +0,0 @@ -use core::fmt; - -use malachite_common::{Context, Round, SignedVote, Timeout}; - -/// Messages emitted by the [`Driver`](crate::Driver) -pub enum Message -where - Ctx: Context, -{ - /// Start a new round - NewRound(Ctx::Height, Round), - - /// Broadcast a proposal - Propose(Ctx::Proposal), - - /// Broadcast a vote for a value - Vote(SignedVote), - - /// Decide on a value - Decide(Round, Ctx::Value), - - /// Schedule a timeout - ScheduleTimeout(Timeout), - - /// Ask for a value to propose and schedule a timeout - GetValueAndScheduleTimeout(Round, Timeout), -} - -// NOTE: We have to derive these instances manually, otherwise -// the compiler would infer a Clone/Debug/PartialEq/Eq bound on `Ctx`, -// which may not hold for all contexts. - -impl Clone for Message { - #[cfg_attr(coverage_nightly, coverage(off))] - fn clone(&self) -> Self { - match self { - Message::NewRound(height, round) => Message::NewRound(height.clone(), *round), - Message::Propose(proposal) => Message::Propose(proposal.clone()), - 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::GetValueAndScheduleTimeout(round, timeout) => { - Message::GetValueAndScheduleTimeout(*round, *timeout) - } - } - } -} - -impl fmt::Debug for Message { - #[cfg_attr(coverage_nightly, coverage(off))] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Message::NewRound(height, round) => write!(f, "NewRound({:?}, {:?})", height, round), - Message::Propose(proposal) => write!(f, "Propose({:?})", proposal), - 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::GetValueAndScheduleTimeout(round, timeout) => { - write!(f, "GetValueAndScheduleTimeout({:?}, {:?})", round, timeout) - } - } - } -} - -impl PartialEq for Message { - #[cfg_attr(coverage_nightly, coverage(off))] - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Message::NewRound(height, round), Message::NewRound(other_height, other_round)) => { - height == other_height && round == other_round - } - (Message::Propose(proposal), Message::Propose(other_proposal)) => { - proposal == other_proposal - } - (Message::Vote(signed_vote), Message::Vote(other_signed_vote)) => { - signed_vote == other_signed_vote - } - (Message::Decide(round, value), Message::Decide(other_round, other_value)) => { - round == other_round && value == other_value - } - (Message::ScheduleTimeout(timeout), Message::ScheduleTimeout(other_timeout)) => { - timeout == other_timeout - } - ( - Message::GetValueAndScheduleTimeout(round, timeout), - Message::GetValueAndScheduleTimeout(other_round, other_timeout), - ) => round == other_round && timeout == other_timeout, - _ => false, - } - } -} - -impl Eq for Message {} diff --git a/Code/driver/src/output.rs b/Code/driver/src/output.rs new file mode 100644 index 000000000..d0565f484 --- /dev/null +++ b/Code/driver/src/output.rs @@ -0,0 +1,93 @@ +use core::fmt; + +use malachite_common::{Context, Round, SignedVote, Timeout}; + +/// Messages emitted by the [`Driver`](crate::Driver) +pub enum Output +where + Ctx: Context, +{ + /// Start a new round + NewRound(Ctx::Height, Round), + + /// Broadcast a proposal + Propose(Ctx::Proposal), + + /// Broadcast a vote for a value + Vote(SignedVote), + + /// Decide on a value + Decide(Round, Ctx::Value), + + /// Schedule a timeout + ScheduleTimeout(Timeout), + + /// Ask for a value to propose and schedule a timeout + GetValueAndScheduleTimeout(Round, Timeout), +} + +// NOTE: We have to derive these instances manually, otherwise +// the compiler would infer a Clone/Debug/PartialEq/Eq bound on `Ctx`, +// which may not hold for all contexts. + +impl Clone for Output { + #[cfg_attr(coverage_nightly, coverage(off))] + fn clone(&self) -> Self { + match self { + Output::NewRound(height, round) => Output::NewRound(height.clone(), *round), + Output::Propose(proposal) => Output::Propose(proposal.clone()), + Output::Vote(signed_vote) => Output::Vote(signed_vote.clone()), + Output::Decide(round, value) => Output::Decide(*round, value.clone()), + Output::ScheduleTimeout(timeout) => Output::ScheduleTimeout(*timeout), + Output::GetValueAndScheduleTimeout(round, timeout) => { + Output::GetValueAndScheduleTimeout(*round, *timeout) + } + } + } +} + +impl fmt::Debug for Output { + #[cfg_attr(coverage_nightly, coverage(off))] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Output::NewRound(height, round) => write!(f, "NewRound({:?}, {:?})", height, round), + Output::Propose(proposal) => write!(f, "Propose({:?})", proposal), + Output::Vote(signed_vote) => write!(f, "Vote({:?})", signed_vote), + Output::Decide(round, value) => write!(f, "Decide({:?}, {:?})", round, value), + Output::ScheduleTimeout(timeout) => write!(f, "ScheduleTimeout({:?})", timeout), + Output::GetValueAndScheduleTimeout(round, timeout) => { + write!(f, "GetValueAndScheduleTimeout({:?}, {:?})", round, timeout) + } + } + } +} + +impl PartialEq for Output { + #[cfg_attr(coverage_nightly, coverage(off))] + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Output::NewRound(height, round), Output::NewRound(other_height, other_round)) => { + height == other_height && round == other_round + } + (Output::Propose(proposal), Output::Propose(other_proposal)) => { + proposal == other_proposal + } + (Output::Vote(signed_vote), Output::Vote(other_signed_vote)) => { + signed_vote == other_signed_vote + } + (Output::Decide(round, value), Output::Decide(other_round, other_value)) => { + round == other_round && value == other_value + } + (Output::ScheduleTimeout(timeout), Output::ScheduleTimeout(other_timeout)) => { + timeout == other_timeout + } + ( + Output::GetValueAndScheduleTimeout(round, timeout), + Output::GetValueAndScheduleTimeout(other_round, other_timeout), + ) => round == other_round && timeout == other_timeout, + _ => false, + } + } +} + +impl Eq for Output {} diff --git a/Code/itf/tests/votekeeper/runner.rs b/Code/itf/tests/votekeeper/runner.rs index 482186b7f..b5c876e06 100644 --- a/Code/itf/tests/votekeeper/runner.rs +++ b/Code/itf/tests/votekeeper/runner.rs @@ -4,7 +4,7 @@ use malachite_common::{Context, Round, Value}; use malachite_itf::votekeeper::State; use malachite_test::{Address, Height, TestContext, Vote}; use malachite_vote::{ - keeper::{Message, VoteKeeper}, + keeper::{Output, VoteKeeper}, ThresholdParams, }; @@ -18,7 +18,7 @@ pub struct VoteKeeperRunner { impl ItfRunner for VoteKeeperRunner { type ActualState = VoteKeeper; - type Result = Option::Value as Value>::Id>>; + type Result = Option::Value as Value>::Id>>; type ExpectedState = State; type Error = (); @@ -75,21 +75,21 @@ impl ItfRunner for VoteKeeperRunner { // Check result against expected result. match result { Some(result) => match result { - Message::PolkaValue(value) => { + Output::PolkaValue(value) => { assert_eq!(expected_result.name, "PolkaValue"); assert_eq!( value_from_model(&expected_result.value).as_ref(), Some(value) ); } - Message::PrecommitValue(value) => { + Output::PrecommitValue(value) => { assert_eq!(expected_result.name, "PrecommitValue"); assert_eq!( value_from_model(&expected_result.value).as_ref(), Some(value) ); } - Message::SkipRound(round) => { + Output::SkipRound(round) => { assert_eq!(expected_result.name, "Skip"); assert_eq!(&Round::new(expected_result.round), round); } @@ -141,9 +141,9 @@ impl ItfRunner for VoteKeeperRunner { for event in actual_events { let event_name = match event { - Message::PolkaValue(_) => "PolkaValue".into(), - Message::PrecommitValue(_) => "PrecommitValue".into(), - Message::SkipRound(_) => "Skip".into(), + Output::PolkaValue(_) => "PolkaValue".into(), + Output::PrecommitValue(_) => "PrecommitValue".into(), + Output::SkipRound(_) => "Skip".into(), _ => format!("{event:?}"), }; let count = event_count.entry(event_name.clone()).or_insert(0); diff --git a/Code/round/src/events.rs b/Code/round/src/events.rs deleted file mode 100644 index 0ea71c90f..000000000 --- a/Code/round/src/events.rs +++ /dev/null @@ -1,25 +0,0 @@ -use malachite_common::{Context, Round, ValueId}; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum Event -where - Ctx: Context, -{ - NewRound, // Start a new round, either as proposer or not. L14/L20 - ProposeValue(Ctx::Value), // Propose a value.L14 - Proposal(Ctx::Proposal), // Receive a proposal. L22 + L23 (valid) - InvalidProposal, // Receive an invalid proposal. L26 + L32 (invalid) - ProposalAndPolkaPrevious(Ctx::Proposal), // Received a proposal and a polka value from a previous round. L28 + L29 (valid) - InvalidProposalAndPolkaPrevious(Ctx::Proposal), // Received a proposal and a polka value from a previous round. L28 + L29 (invalid) - PolkaValue(ValueId), // Receive +2/3 prevotes for valueId. L44 - PolkaAny, // Receive +2/3 prevotes for anything. L34 - PolkaNil, // Receive +2/3 prevotes for nil. L44 - ProposalAndPolkaCurrent(Ctx::Proposal), // Receive +2/3 prevotes for Value in current round. L36 - PrecommitAny, // Receive +2/3 precommits for anything. L47 - ProposalAndPrecommitValue(Ctx::Proposal), // Receive +2/3 precommits for Value. L49 - PrecommitValue(ValueId), // Receive +2/3 precommits for ValueId. L51 - SkipRound(Round), // Receive +1/3 messages from a higher round. OneCorrectProcessInHigherRound, L55 - TimeoutPropose, // Timeout waiting for proposal. L57 - TimeoutPrevote, // Timeout waiting for prevotes. L61 - TimeoutPrecommit, // Timeout waiting for precommits. L65 -} diff --git a/Code/round/src/input.rs b/Code/round/src/input.rs new file mode 100644 index 000000000..c095f4dcd --- /dev/null +++ b/Code/round/src/input.rs @@ -0,0 +1,75 @@ +use malachite_common::{Context, Round, ValueId}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Input +where + Ctx: Context, +{ + /// Start a new round, either as proposer or not. + /// L14/L20 + NewRound, + + /// Propose a value. + /// L14 + ProposeValue(Ctx::Value), + + /// Receive a proposal. + /// L22 + L23 (valid) + Proposal(Ctx::Proposal), + + /// Receive an invalid proposal. + /// L26 + L32 (invalid) + InvalidProposal, + + /// Received a proposal and a polka value from a previous round. + /// L28 + L29 (valid) + ProposalAndPolkaPrevious(Ctx::Proposal), + + /// Received a proposal and a polka value from a previous round. + /// L28 + L29 (invalid) + InvalidProposalAndPolkaPrevious(Ctx::Proposal), + + /// Receive +2/3 prevotes for a value. + /// L44 + PolkaValue(ValueId), + + /// Receive +2/3 prevotes for anything. + /// L34 + PolkaAny, + + /// Receive +2/3 prevotes for nil. + /// L44 + PolkaNil, + + /// Receive +2/3 prevotes for a value in current round. + /// L36 + ProposalAndPolkaCurrent(Ctx::Proposal), + + /// Receive +2/3 precommits for anything. + /// L47 + PrecommitAny, + + /// Receive +2/3 precommits for a value. + /// L49 + ProposalAndPrecommitValue(Ctx::Proposal), + + /// Receive +2/3 precommits for a value. + /// L51 + PrecommitValue(ValueId), + + /// Receive +1/3 messages from a higher round. OneCorrectProcessInHigherRound. + /// L55 + SkipRound(Round), + + /// Timeout waiting for proposal. + /// L57 + TimeoutPropose, + + /// Timeout waiting for prevotes. + /// L61 + TimeoutPrevote, + + /// Timeout waiting for precommits. + /// L65 + TimeoutPrecommit, +} diff --git a/Code/round/src/lib.rs b/Code/round/src/lib.rs index d13887058..33b43bcf6 100644 --- a/Code/round/src/lib.rs +++ b/Code/round/src/lib.rs @@ -14,8 +14,8 @@ extern crate alloc; -pub mod events; -pub mod message; +pub mod input; +pub mod output; pub mod state; pub mod state_machine; pub mod transition; diff --git a/Code/round/src/message.rs b/Code/round/src/output.rs similarity index 50% rename from Code/round/src/message.rs rename to Code/round/src/output.rs index fc5d8f8f5..a7f586015 100644 --- a/Code/round/src/message.rs +++ b/Code/round/src/output.rs @@ -4,7 +4,7 @@ use malachite_common::{Context, Round, Timeout, TimeoutStep, ValueId}; use crate::state::RoundValue; -pub enum Message +pub enum Output where Ctx: Context, { @@ -16,14 +16,14 @@ where Decision(RoundValue), // Decide the value. } -impl Message { +impl Output { pub fn proposal( height: Ctx::Height, round: Round, value: Ctx::Value, pol_round: Round, ) -> Self { - Message::Proposal(Ctx::new_proposal(height, round, value, pol_round)) + Output::Proposal(Ctx::new_proposal(height, round, value, pol_round)) } pub fn prevote( @@ -32,7 +32,7 @@ impl Message { value_id: Option>, address: Ctx::Address, ) -> Self { - Message::Vote(Ctx::new_prevote(height, round, value_id, address)) + Output::Vote(Ctx::new_prevote(height, round, value_id, address)) } pub fn precommit( @@ -41,19 +41,19 @@ impl Message { value_id: Option>, address: Ctx::Address, ) -> Self { - Message::Vote(Ctx::new_precommit(height, round, value_id, address)) + Output::Vote(Ctx::new_precommit(height, round, value_id, address)) } pub fn schedule_timeout(round: Round, step: TimeoutStep) -> Self { - Message::ScheduleTimeout(Timeout { round, step }) + Output::ScheduleTimeout(Timeout { round, step }) } pub fn get_value_and_schedule_timeout(round: Round, step: TimeoutStep) -> Self { - Message::GetValueAndScheduleTimeout(round, Timeout { round, step }) + Output::GetValueAndScheduleTimeout(round, Timeout { round, step }) } pub fn decision(round: Round, value: Ctx::Value) -> Self { - Message::Decision(RoundValue { round, value }) + Output::Decision(RoundValue { round, value }) } } @@ -61,55 +61,55 @@ impl Message { // the compiler would infer a Clone/Debug/PartialEq/Eq bound on `Ctx`, // which may not hold for all contexts. -impl Clone for Message { +impl Clone for Output { #[cfg_attr(coverage_nightly, coverage(off))] fn clone(&self) -> Self { match self { - Message::NewRound(round) => Message::NewRound(*round), - Message::Proposal(proposal) => Message::Proposal(proposal.clone()), - Message::Vote(vote) => Message::Vote(vote.clone()), - Message::ScheduleTimeout(timeout) => Message::ScheduleTimeout(*timeout), - Message::GetValueAndScheduleTimeout(round, timeout) => { - Message::GetValueAndScheduleTimeout(*round, *timeout) + Output::NewRound(round) => Output::NewRound(*round), + Output::Proposal(proposal) => Output::Proposal(proposal.clone()), + Output::Vote(vote) => Output::Vote(vote.clone()), + Output::ScheduleTimeout(timeout) => Output::ScheduleTimeout(*timeout), + Output::GetValueAndScheduleTimeout(round, timeout) => { + Output::GetValueAndScheduleTimeout(*round, *timeout) } - Message::Decision(round_value) => Message::Decision(round_value.clone()), + Output::Decision(round_value) => Output::Decision(round_value.clone()), } } } -impl fmt::Debug for Message { +impl fmt::Debug for Output { #[cfg_attr(coverage_nightly, coverage(off))] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Message::NewRound(round) => write!(f, "NewRound({:?})", round), - Message::Proposal(proposal) => write!(f, "Proposal({:?})", proposal), - Message::Vote(vote) => write!(f, "Vote({:?})", vote), - Message::ScheduleTimeout(timeout) => write!(f, "ScheduleTimeout({:?})", timeout), - Message::GetValueAndScheduleTimeout(round, timeout) => { + Output::NewRound(round) => write!(f, "NewRound({:?})", round), + Output::Proposal(proposal) => write!(f, "Proposal({:?})", proposal), + Output::Vote(vote) => write!(f, "Vote({:?})", vote), + Output::ScheduleTimeout(timeout) => write!(f, "ScheduleTimeout({:?})", timeout), + Output::GetValueAndScheduleTimeout(round, timeout) => { write!(f, "GetValueAndScheduleTimeout({:?}, {:?})", round, timeout) } - Message::Decision(round_value) => write!(f, "Decision({:?})", round_value), + Output::Decision(round_value) => write!(f, "Decision({:?})", round_value), } } } -impl PartialEq for Message { +impl PartialEq for Output { #[cfg_attr(coverage_nightly, coverage(off))] fn eq(&self, other: &Self) -> bool { match (self, other) { - (Message::NewRound(round), Message::NewRound(other_round)) => round == other_round, - (Message::Proposal(proposal), Message::Proposal(other_proposal)) => { + (Output::NewRound(round), Output::NewRound(other_round)) => round == other_round, + (Output::Proposal(proposal), Output::Proposal(other_proposal)) => { proposal == other_proposal } - (Message::Vote(vote), Message::Vote(other_vote)) => vote == other_vote, - (Message::ScheduleTimeout(timeout), Message::ScheduleTimeout(other_timeout)) => { + (Output::Vote(vote), Output::Vote(other_vote)) => vote == other_vote, + (Output::ScheduleTimeout(timeout), Output::ScheduleTimeout(other_timeout)) => { timeout == other_timeout } ( - Message::GetValueAndScheduleTimeout(round, timeout), - Message::GetValueAndScheduleTimeout(other_round, other_timeout), + Output::GetValueAndScheduleTimeout(round, timeout), + Output::GetValueAndScheduleTimeout(other_round, other_timeout), ) => round == other_round && timeout == other_timeout, - (Message::Decision(round_value), Message::Decision(other_round_value)) => { + (Output::Decision(round_value), Output::Decision(other_round_value)) => { round_value == other_round_value } _ => false, @@ -117,4 +117,4 @@ impl PartialEq for Message { } } -impl Eq for Message {} +impl Eq for Output {} diff --git a/Code/round/src/state.rs b/Code/round/src/state.rs index 200c70450..27501cebd 100644 --- a/Code/round/src/state.rs +++ b/Code/round/src/state.rs @@ -1,6 +1,6 @@ use core::fmt; -use crate::events::Event; +use crate::input::Input; use crate::state_machine::Info; use crate::transition::Transition; @@ -76,8 +76,8 @@ where } } - pub fn apply_event(self, data: &Info, event: Event) -> Transition { - crate::state_machine::apply_event(self, data, event) + pub fn apply(self, data: &Info, input: Input) -> Transition { + crate::state_machine::apply(self, data, input) } } diff --git a/Code/round/src/state_machine.rs b/Code/round/src/state_machine.rs index a34346f60..77b7b6162 100644 --- a/Code/round/src/state_machine.rs +++ b/Code/round/src/state_machine.rs @@ -1,19 +1,19 @@ use malachite_common::{Context, Proposal, Round, TimeoutStep, Value}; -use crate::events::Event; -use crate::message::Message; +use crate::input::Input; +use crate::output::Output; use crate::state::{State, Step}; use crate::transition::Transition; -/// Immutable information about the event and our node: +/// Immutable information about the input and our node: /// - Address of our node /// - Proposer for the round we are at -/// - Round for which the event is for, can be different than the round we are at +/// - Round for which the input is for, can be different than the round we are at pub struct Info<'a, Ctx> where Ctx: Context, { - pub event_round: Round, + pub input_round: Round, pub address: &'a Ctx::Address, pub proposer: &'a Ctx::Address, } @@ -22,9 +22,9 @@ impl<'a, Ctx> Info<'a, Ctx> where Ctx: Context, { - pub fn new(event_round: Round, address: &'a Ctx::Address, proposer: &'a Ctx::Address) -> Self { + pub fn new(input_round: Round, address: &'a Ctx::Address, proposer: &'a Ctx::Address) -> Self { Self { - event_round, + input_round, address, proposer, } @@ -43,38 +43,38 @@ where pol_round.is_defined() && pol_round < state.round } -/// Apply an event to the current state at the current round. +/// Apply an input to the current state at the current round. /// -/// This function takes the current state and round, and an event, +/// This function takes the current state and round, and an input, /// and returns the next state and an optional message for the driver to act on. /// -/// Valid transitions result in at least a change to the state and/or an output message. +/// Valid transitions result in at least a change to the state and/or an output. /// /// Commented numbers refer to line numbers in the spec paper. -pub fn apply_event(state: State, info: &Info, event: Event) -> Transition +pub fn apply(state: State, info: &Info, input: Input) -> Transition where Ctx: Context, { - let this_round = state.round == info.event_round; + let this_round = state.round == info.input_round; - match (state.step, event) { - // From NewRound. Event must be for current round. + match (state.step, input) { + // From NewRound. Input must be for current round. // We are the proposer - (Step::NewRound, Event::NewRound) if this_round && info.is_proposer() => { + (Step::NewRound, Input::NewRound) if this_round && info.is_proposer() => { propose_valid_or_get_value(state) // L18 } // We are not the proposer - (Step::NewRound, Event::NewRound) if this_round => schedule_timeout_propose(state), // L11/L20 + (Step::NewRound, Input::NewRound) if this_round => schedule_timeout_propose(state), // L11/L20 - // From Propose. Event must be for current round. - (Step::Propose, Event::ProposeValue(value)) if this_round => { + // From Propose. Input must be for current round. + (Step::Propose, Input::ProposeValue(value)) if this_round => { debug_assert!(info.is_proposer()); propose(state, value) // L11/L14 } // L22 with valid proposal - (Step::Propose, Event::Proposal(proposal)) + (Step::Propose, Input::Proposal(proposal)) if this_round && proposal.pol_round().is_nil() => { if state @@ -89,7 +89,7 @@ where } // L28 with valid proposal - (Step::Propose, Event::ProposalAndPolkaPrevious(proposal)) + (Step::Propose, Input::ProposalAndPolkaPrevious(proposal)) if this_round && is_valid_pol_round(&state, proposal.pol_round()) => { let Some(locked) = state.locked.as_ref() else { @@ -104,32 +104,32 @@ where } // L28 with invalid proposal - (Step::Propose, Event::InvalidProposalAndPolkaPrevious(proposal)) + (Step::Propose, Input::InvalidProposalAndPolkaPrevious(proposal)) if this_round && is_valid_pol_round(&state, proposal.pol_round()) => { prevote_nil(state, info.address) } - (Step::Propose, Event::InvalidProposal) if this_round => prevote_nil(state, info.address), // L22/L25, L28/L31 + (Step::Propose, Input::InvalidProposal) if this_round => prevote_nil(state, info.address), // L22/L25, L28/L31 // We are the proposer. - (Step::Propose, Event::TimeoutPropose) if this_round && info.is_proposer() => { + (Step::Propose, Input::TimeoutPropose) if this_round && info.is_proposer() => { // TODO: Do we need to do something else here? prevote_nil(state, info.address) // L57 } // We are not the proposer. - (Step::Propose, Event::TimeoutPropose) if this_round => prevote_nil(state, info.address), // L57 + (Step::Propose, Input::TimeoutPropose) if this_round => prevote_nil(state, info.address), // L57 - // From Prevote. Event must be for current round. - (Step::Prevote, Event::PolkaAny) if this_round => schedule_timeout_prevote(state), // L34 - (Step::Prevote, Event::PolkaNil) if this_round => precommit_nil(state, info.address), // L44 - (Step::Prevote, Event::ProposalAndPolkaCurrent(proposal)) if this_round => { + // From Prevote. Input must be for current round. + (Step::Prevote, Input::PolkaAny) if this_round => schedule_timeout_prevote(state), // L34 + (Step::Prevote, Input::PolkaNil) if this_round => precommit_nil(state, info.address), // L44 + (Step::Prevote, Input::ProposalAndPolkaCurrent(proposal)) if this_round => { precommit(state, info.address, proposal) // L36/L37 - NOTE: only once? } - (Step::Prevote, Event::TimeoutPrevote) if this_round => precommit_nil(state, info.address), // L61 + (Step::Prevote, Input::TimeoutPrevote) if this_round => precommit_nil(state, info.address), // L61 - // From Precommit. Event must be for current round. - (Step::Precommit, Event::ProposalAndPolkaCurrent(proposal)) if this_round => { + // From Precommit. Input must be for current round. + (Step::Precommit, Input::ProposalAndPolkaCurrent(proposal)) if this_round => { set_valid_value(state, &proposal) // L36/L42 - NOTE: only once? } @@ -137,13 +137,13 @@ where (Step::Commit, _) => Transition::invalid(state), // From all (except Commit). Various round guards. - (_, Event::PrecommitAny) if this_round => schedule_timeout_precommit(state), // L47 - (_, Event::TimeoutPrecommit) if this_round => { - round_skip(state, info.event_round.increment()) + (_, Input::PrecommitAny) if this_round => schedule_timeout_precommit(state), // L47 + (_, Input::TimeoutPrecommit) if this_round => { + round_skip(state, info.input_round.increment()) } // L65 - (_, Event::SkipRound(round)) if state.round < round => round_skip(state, round), // L55 - (_, Event::ProposalAndPrecommitValue(proposal)) => { - commit(state, info.event_round, proposal) + (_, Input::SkipRound(round)) if state.round < round => round_skip(state, round), // L55 + (_, Input::ProposalAndPrecommitValue(proposal)) => { + commit(state, info.input_round, proposal) } // L49 // Invalid transition. @@ -166,18 +166,17 @@ where match &state.valid { Some(round_value) => { let pol_round = round_value.round; - let proposal = Message::proposal( + let proposal = Output::proposal( state.height.clone(), state.round, round_value.value.clone(), pol_round, ); - Transition::to(state.with_step(Step::Propose)).with_message(proposal) + Transition::to(state.with_step(Step::Propose)).with_output(proposal) } None => { - let timeout = - Message::get_value_and_schedule_timeout(state.round, TimeoutStep::Propose); - Transition::to(state.with_step(Step::Propose)).with_message(timeout) + let timeout = Output::get_value_and_schedule_timeout(state.round, TimeoutStep::Propose); + Transition::to(state.with_step(Step::Propose)).with_output(timeout) } } } @@ -190,8 +189,8 @@ pub fn propose(state: State, value: Ctx::Value) -> Transition where Ctx: Context, { - let proposal = Message::proposal(state.height.clone(), state.round, value, Round::Nil); - Transition::to(state.with_step(Step::Propose)).with_message(proposal) + let proposal = Output::proposal(state.height.clone(), state.round, value, Round::Nil); + Transition::to(state.with_step(Step::Propose)).with_output(proposal) } //--------------------------------------------------------------------- @@ -219,9 +218,9 @@ where None => Some(proposed), // not locked, prevote the value }; - let message = Message::prevote(state.height.clone(), state.round, value, address.clone()); + let output = Output::prevote(state.height.clone(), state.round, value, address.clone()); state.proposal = Some(proposal.clone()); - Transition::to(state.with_step(Step::Prevote)).with_message(message) + Transition::to(state.with_step(Step::Prevote)).with_output(output) } /// Received a complete proposal for an empty or invalid value, or timed out; prevote nil. @@ -231,8 +230,8 @@ pub fn prevote_nil(state: State, address: &Ctx::Address) -> Transition where Ctx: Context, { - let message = Message::prevote(state.height.clone(), state.round, None, address.clone()); - Transition::to(state.with_step(Step::Prevote)).with_message(message) + let output = Output::prevote(state.height.clone(), state.round, None, address.clone()); + Transition::to(state.with_step(Step::Prevote)).with_output(output) } // --------------------------------------------------------------------- @@ -258,7 +257,7 @@ where } let value = proposal.value(); - let message = Message::precommit( + let output = Output::precommit( state.height.clone(), state.round, Some(value.id()), @@ -280,7 +279,7 @@ where .set_valid(value.clone()) .with_step(Step::Precommit); - Transition::to(next).with_message(message) + Transition::to(next).with_output(output) } /// Received a polka for nil or timed out of prevote; precommit nil. @@ -290,8 +289,8 @@ pub fn precommit_nil(state: State, address: &Ctx::Address) -> Transiti where Ctx: Context, { - let message = Message::precommit(state.height.clone(), state.round, None, address.clone()); - Transition::to(state.with_step(Step::Precommit)).with_message(message) + let output = Output::precommit(state.height.clone(), state.round, None, address.clone()); + Transition::to(state.with_step(Step::Precommit)).with_output(output) } // --------------------------------------------------------------------- @@ -305,8 +304,8 @@ pub fn schedule_timeout_propose(state: State) -> Transition where Ctx: Context, { - let timeout = Message::schedule_timeout(state.round, TimeoutStep::Propose); - Transition::to(state.with_step(Step::Propose)).with_message(timeout) + let timeout = Output::schedule_timeout(state.round, TimeoutStep::Propose); + Transition::to(state.with_step(Step::Propose)).with_output(timeout) } /// We received a polka for any; schedule timeout prevote. @@ -320,8 +319,8 @@ where Ctx: Context, { if state.step == Step::Prevote { - let message = Message::schedule_timeout(state.round, TimeoutStep::Prevote); - Transition::to(state).with_message(message) + let output = Output::schedule_timeout(state.round, TimeoutStep::Prevote); + Transition::to(state).with_output(output) } else { Transition::to(state) } @@ -334,8 +333,8 @@ pub fn schedule_timeout_precommit(state: State) -> Transition where Ctx: Context, { - let message = Message::schedule_timeout(state.round, TimeoutStep::Precommit); - Transition::to(state).with_message(message) + let output = Output::schedule_timeout(state.round, TimeoutStep::Precommit); + Transition::to(state).with_output(output) } //--------------------------------------------------------------------- @@ -374,7 +373,7 @@ where ..state }; - Transition::to(new_state).with_message(Message::NewRound(round)) + Transition::to(new_state).with_output(Output::NewRound(round)) } /// We received +2/3 precommits for a value - commit and decide that value! @@ -384,6 +383,6 @@ pub fn commit(state: State, round: Round, proposal: Ctx::Proposal) -> where Ctx: Context, { - let message = Message::decision(round, proposal.value().clone()); - Transition::to(state.with_step(Step::Commit)).with_message(message) + let output = Output::decision(round, proposal.value().clone()); + Transition::to(state.with_step(Step::Commit)).with_output(output) } diff --git a/Code/round/src/transition.rs b/Code/round/src/transition.rs index 33bed24fb..5d28df5b0 100644 --- a/Code/round/src/transition.rs +++ b/Code/round/src/transition.rs @@ -1,6 +1,6 @@ use malachite_common::Context; -use crate::message::Message; +use crate::output::Output; use crate::state::State; #[derive(Clone, Debug, PartialEq, Eq)] @@ -9,7 +9,7 @@ where Ctx: Context, { pub next_state: State, - pub message: Option>, + pub output: Option>, pub valid: bool, } @@ -20,7 +20,7 @@ where pub fn to(next_state: State) -> Self { Self { next_state, - message: None, + output: None, valid: true, } } @@ -28,13 +28,13 @@ where pub fn invalid(next_state: State) -> Self { Self { next_state, - message: None, + output: None, valid: false, } } - pub fn with_message(mut self, message: Message) -> Self { - self.message = Some(message); + pub fn with_output(mut self, output: Output) -> Self { + self.output = Some(output); self } } diff --git a/Code/test/src/utils.rs b/Code/test/src/utils.rs index e94d9458b..d4450f206 100644 --- a/Code/test/src/utils.rs +++ b/Code/test/src/utils.rs @@ -2,7 +2,7 @@ use rand::rngs::StdRng; use rand::SeedableRng; use malachite_common::{Round, Timeout, VotingPower}; -use malachite_driver::{Event, Message, ProposerSelector, Validity}; +use malachite_driver::{Input, Output, ProposerSelector, Validity}; use malachite_round::state::{RoundValue, State, Step}; use crate::{ @@ -52,117 +52,121 @@ pub fn make_validators( validators.try_into().expect("N validators") } -pub fn new_round_event(round: Round) -> Event { - Event::NewRound(Height::new(1), round) +pub fn new_round_input(round: Round) -> Input { + Input::NewRound(Height::new(1), round) } -pub fn new_round_msg(round: Round) -> Option> { - Some(Message::NewRound(Height::new(1), round)) +pub fn new_round_output(round: Round) -> Option> { + Some(Output::NewRound(Height::new(1), round)) } -pub fn proposal_msg( +pub fn proposal_output( round: Round, value: Value, locked_round: Round, -) -> Option> { +) -> Option> { let proposal = Proposal::new(Height::new(1), round, value, locked_round); - Some(Message::Propose(proposal)) + Some(Output::Propose(proposal)) } -pub fn proposal_event( +pub fn proposal_input( round: Round, value: Value, locked_round: Round, validity: Validity, -) -> Event { +) -> Input { let proposal = Proposal::new(Height::new(1), round, value, locked_round); - Event::Proposal(proposal, validity) + Input::Proposal(proposal, validity) } -pub fn prevote_msg(round: Round, addr: &Address, sk: &PrivateKey) -> Option> { +pub fn prevote_output( + round: Round, + addr: &Address, + sk: &PrivateKey, +) -> Option> { let value = Value::new(9999); - Some(Message::Vote( + Some(Output::Vote( Vote::new_prevote(Height::new(1), round, Some(value.id()), *addr).signed(sk), )) } -pub fn prevote_nil_msg( +pub fn prevote_nil_output( round: Round, addr: &Address, sk: &PrivateKey, -) -> Option> { - Some(Message::Vote( +) -> Option> { + Some(Output::Vote( Vote::new_prevote(Height::new(1), round, None, *addr).signed(sk), )) } -pub fn prevote_event(addr: &Address, sk: &PrivateKey) -> Event { +pub fn prevote_input(addr: &Address, sk: &PrivateKey) -> Input { let value = Value::new(9999); - Event::Vote( + Input::Vote( Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), *addr).signed(sk), ) } -pub fn prevote_event_at(round: Round, addr: &Address, sk: &PrivateKey) -> Event { +pub fn prevote_input_at(round: Round, addr: &Address, sk: &PrivateKey) -> Input { let value = Value::new(9999); - Event::Vote(Vote::new_prevote(Height::new(1), round, Some(value.id()), *addr).signed(sk)) + Input::Vote(Vote::new_prevote(Height::new(1), round, Some(value.id()), *addr).signed(sk)) } -pub fn precommit_msg( +pub fn precommit_output( round: Round, value: Value, addr: &Address, sk: &PrivateKey, -) -> Option> { - Some(Message::Vote( +) -> Option> { + Some(Output::Vote( Vote::new_precommit(Height::new(1), round, Some(value.id()), *addr).signed(sk), )) } -pub fn precommit_nil_msg(addr: &Address, sk: &PrivateKey) -> Option> { - Some(Message::Vote( +pub fn precommit_nil_output(addr: &Address, sk: &PrivateKey) -> Option> { + Some(Output::Vote( Vote::new_precommit(Height::new(1), Round::new(0), None, *addr).signed(sk), )) } -pub fn precommit_event( +pub fn precommit_input( round: Round, value: Value, addr: &Address, sk: &PrivateKey, -) -> Event { - Event::Vote(Vote::new_precommit(Height::new(1), round, Some(value.id()), *addr).signed(sk)) +) -> Input { + Input::Vote(Vote::new_precommit(Height::new(1), round, Some(value.id()), *addr).signed(sk)) } -pub fn decide_message(round: Round, value: Value) -> Option> { - Some(Message::Decide(round, value)) +pub fn decide_output(round: Round, value: Value) -> Option> { + Some(Output::Decide(round, value)) } -pub fn start_propose_timer_msg(round: Round) -> Option> { - Some(Message::ScheduleTimeout(Timeout::propose(round))) +pub fn start_propose_timer_output(round: Round) -> Option> { + Some(Output::ScheduleTimeout(Timeout::propose(round))) } -pub fn timeout_propose_event(round: Round) -> Event { - Event::TimeoutElapsed(Timeout::propose(round)) +pub fn timeout_propose_input(round: Round) -> Input { + Input::TimeoutElapsed(Timeout::propose(round)) } -pub fn start_prevote_timer_msg(round: Round) -> Option> { - Some(Message::ScheduleTimeout(Timeout::prevote(round))) +pub fn start_prevote_timer_output(round: Round) -> Option> { + Some(Output::ScheduleTimeout(Timeout::prevote(round))) } -pub fn timeout_prevote_event(round: Round) -> Event { - Event::TimeoutElapsed(Timeout::prevote(round)) +pub fn timeout_prevote_input(round: Round) -> Input { + Input::TimeoutElapsed(Timeout::prevote(round)) } -pub fn start_precommit_timer_msg(round: Round) -> Option> { - Some(Message::ScheduleTimeout(Timeout::precommit(round))) +pub fn start_precommit_timer_output(round: Round) -> Option> { + Some(Output::ScheduleTimeout(Timeout::precommit(round))) } -pub fn timeout_precommit_event(round: Round) -> Event { - Event::TimeoutElapsed(Timeout::precommit(round)) +pub fn timeout_precommit_input(round: Round) -> Input { + Input::TimeoutElapsed(Timeout::precommit(round)) } pub fn propose_state(round: Round) -> State { diff --git a/Code/test/tests/driver.rs b/Code/test/tests/driver.rs index c230b4350..62d4ff454 100644 --- a/Code/test/tests/driver.rs +++ b/Code/test/tests/driver.rs @@ -2,27 +2,27 @@ use futures::executor::block_on; use malachite_test::utils::{make_validators, FixedProposer, RotateProposer}; use malachite_common::{Round, Timeout, TimeoutStep}; -use malachite_driver::{Driver, Error, Event, Message, Validity}; +use malachite_driver::{Driver, Error, Input, Output, Validity}; use malachite_round::state::{RoundValue, State, Step}; use malachite_test::{Height, Proposal, TestContext, ValidatorSet, Value, Vote}; pub struct TestStep { desc: &'static str, - input_event: Option>, - expected_output: Option>, + input: Option>, + expected_output: Option>, expected_round: Round, new_state: State, } -pub fn msg_to_event(output: Message) -> Option> { +pub fn output_to_input(output: Output) -> Option> { match output { - Message::NewRound(height, round) => Some(Event::NewRound(height, round)), + Output::NewRound(height, round) => Some(Input::NewRound(height, round)), // Let's consider our own proposal to always be valid - Message::Propose(p) => Some(Event::Proposal(p, Validity::Valid)), - Message::Vote(v) => Some(Event::Vote(v)), - Message::Decide(_, _) => None, - Message::ScheduleTimeout(_) => None, - Message::GetValueAndScheduleTimeout(_, _) => None, + Output::Propose(p) => Some(Input::Proposal(p, Validity::Valid)), + Output::Vote(v) => Some(Input::Vote(v)), + Output::Decide(_, _) => None, + Output::ScheduleTimeout(_) => None, + Output::GetValueAndScheduleTimeout(_, _) => None, } } @@ -44,8 +44,8 @@ fn driver_steps_proposer() { let steps = vec![ TestStep { desc: "Start round 0, we are proposer, ask for a value to propose", - input_event: Some(Event::NewRound(Height::new(1), Round::new(0))), - expected_output: Some(Message::GetValueAndScheduleTimeout( + input: Some(Input::NewRound(Height::new(1), Round::new(0))), + expected_output: Some(Output::GetValueAndScheduleTimeout( Round::new(0), Timeout::new(Round::new(0), TimeoutStep::Propose), )), @@ -61,8 +61,8 @@ fn driver_steps_proposer() { }, TestStep { desc: "Feed a value to propose, propose that value", - input_event: Some(Event::ProposeValue(Round::new(0), value)), - expected_output: Some(Message::Propose(proposal.clone())), + input: Some(Input::ProposeValue(Round::new(0), value)), + expected_output: Some(Output::Propose(proposal.clone())), expected_round: Round::new(0), new_state: State { height: Height::new(1), @@ -75,8 +75,8 @@ fn driver_steps_proposer() { }, TestStep { desc: "Receive our own proposal, prevote for it (v1)", - input_event: None, - expected_output: Some(Message::Vote( + input: None, + expected_output: Some(Output::Vote( Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), my_addr) .signed(&my_sk), )), @@ -92,7 +92,7 @@ fn driver_steps_proposer() { }, TestStep { desc: "Receive our own prevote v1", - input_event: None, + input: None, expected_output: None, expected_round: Round::new(0), new_state: State { @@ -106,7 +106,7 @@ fn driver_steps_proposer() { }, TestStep { desc: "v2 prevotes for our proposal", - input_event: Some(Event::Vote( + input: Some(Input::Vote( Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), v2.address) .signed(&sk2), )), @@ -123,11 +123,11 @@ 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( + input: Some(Input::Vote( Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), v3.address) .signed(&sk3), )), - expected_output: Some(Message::Vote( + expected_output: Some(Output::Vote( Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), my_addr) .signed(&my_sk), )), @@ -149,7 +149,7 @@ fn driver_steps_proposer() { }, TestStep { desc: "v1 receives its own precommit", - input_event: None, + input: None, expected_output: None, expected_round: Round::new(0), new_state: State { @@ -169,7 +169,7 @@ fn driver_steps_proposer() { }, TestStep { desc: "v2 precommits for our proposal", - input_event: Some(Event::Vote( + input: Some(Input::Vote( Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), v2.address) .signed(&sk2), )), @@ -192,11 +192,11 @@ fn driver_steps_proposer() { }, TestStep { desc: "v3 precommits for our proposal, we get +2/3 precommits, decide it (v1)", - input_event: Some(Event::Vote( + input: Some(Input::Vote( Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), v3.address) .signed(&sk3), )), - expected_output: Some(Message::Decide(Round::new(0), value)), + expected_output: Some(Output::Decide(Round::new(0), value)), expected_round: Round::new(0), new_state: State { height: Height::new(1), @@ -215,17 +215,17 @@ fn driver_steps_proposer() { }, ]; - let mut event_from_previous_msg = None; + let mut output_from_prev_input = None; for step in steps { println!("Step: {}", step.desc); - let execute_event = step - .input_event - .unwrap_or_else(|| event_from_previous_msg.unwrap()); + let input = step + .input + .unwrap_or_else(|| output_from_prev_input.unwrap()); - let output = block_on(driver.execute(execute_event)).expect("execute succeeded"); - assert_eq!(output, step.expected_output, "expected output message"); + 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, @@ -234,7 +234,7 @@ fn driver_steps_proposer() { assert_eq!(driver.round_state, step.new_state, "expected state"); - event_from_previous_msg = output.and_then(msg_to_event); + output_from_prev_input = output.and_then(output_to_input); } } @@ -252,8 +252,8 @@ fn driver_steps_proposer_timeout_get_value() { let steps = vec![ TestStep { desc: "Start round 0, we are proposer, ask for a value to propose", - input_event: Some(Event::NewRound(Height::new(1), Round::new(0))), - expected_output: Some(Message::GetValueAndScheduleTimeout( + input: Some(Input::NewRound(Height::new(1), Round::new(0))), + expected_output: Some(Output::GetValueAndScheduleTimeout( Round::new(0), Timeout::new(Round::new(0), TimeoutStep::Propose), )), @@ -269,8 +269,8 @@ fn driver_steps_proposer_timeout_get_value() { }, TestStep { desc: "Receive a propose timeout", - input_event: Some(Event::TimeoutElapsed(Timeout::propose(Round::new(0)))), - expected_output: Some(Message::Vote( + input: Some(Input::TimeoutElapsed(Timeout::propose(Round::new(0)))), + expected_output: Some(Output::Vote( Vote::new_prevote(Height::new(1), Round::new(0), None, my_addr).signed(&my_sk), )), expected_round: Round::new(0), @@ -285,17 +285,17 @@ fn driver_steps_proposer_timeout_get_value() { }, ]; - let mut event_from_previous_msg = None; + let mut output_from_prev_input = None; for step in steps { println!("Step: {}", step.desc); - let execute_event = step - .input_event - .unwrap_or_else(|| event_from_previous_msg.unwrap()); + let input = step + .input + .unwrap_or_else(|| output_from_prev_input.unwrap()); - let output = block_on(driver.execute(execute_event)).expect("execute succeeded"); - assert_eq!(output, step.expected_output, "expected output message"); + 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, @@ -304,7 +304,7 @@ fn driver_steps_proposer_timeout_get_value() { assert_eq!(driver.round_state, step.new_state, "expected state"); - event_from_previous_msg = output.and_then(msg_to_event); + output_from_prev_input = output.and_then(output_to_input); } } @@ -328,8 +328,8 @@ fn driver_steps_not_proposer_valid() { let steps = vec![ TestStep { desc: "Start round 0, we are not the proposer", - input_event: Some(Event::NewRound(Height::new(1), Round::new(0))), - expected_output: Some(Message::ScheduleTimeout(Timeout::propose(Round::new(0)))), + input: Some(Input::NewRound(Height::new(1), Round::new(0))), + expected_output: Some(Output::ScheduleTimeout(Timeout::propose(Round::new(0)))), expected_round: Round::new(0), new_state: State { height: Height::new(1), @@ -342,8 +342,8 @@ fn driver_steps_not_proposer_valid() { }, TestStep { desc: "Receive a proposal, prevote for it (v2)", - input_event: Some(Event::Proposal(proposal.clone(), Validity::Valid)), - expected_output: Some(Message::Vote( + input: Some(Input::Proposal(proposal.clone(), Validity::Valid)), + expected_output: Some(Output::Vote( Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), my_addr) .signed(&my_sk), )), @@ -359,7 +359,7 @@ fn driver_steps_not_proposer_valid() { }, TestStep { desc: "Receive our own prevote (v2)", - input_event: None, + input: None, expected_output: None, expected_round: Round::new(0), new_state: State { @@ -373,7 +373,7 @@ fn driver_steps_not_proposer_valid() { }, TestStep { desc: "v1 prevotes for its own proposal", - input_event: Some(Event::Vote( + input: Some(Input::Vote( Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), v1.address) .signed(&sk1), )), @@ -390,11 +390,11 @@ 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( + input: Some(Input::Vote( Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), v3.address) .signed(&sk3), )), - expected_output: Some(Message::Vote( + expected_output: Some(Output::Vote( Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), my_addr) .signed(&my_sk), )), @@ -416,7 +416,7 @@ fn driver_steps_not_proposer_valid() { }, TestStep { desc: "we receive our own precommit", - input_event: None, + input: None, expected_output: None, expected_round: Round::new(0), new_state: State { @@ -436,7 +436,7 @@ fn driver_steps_not_proposer_valid() { }, TestStep { desc: "v1 precommits its proposal", - input_event: Some(Event::Vote( + input: Some(Input::Vote( Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), v1.address) .signed(&sk1), )), @@ -459,11 +459,11 @@ 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( + input: Some(Input::Vote( Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), v3.address) .signed(&sk3), )), - expected_output: Some(Message::Decide(Round::new(0), value)), + expected_output: Some(Output::Decide(Round::new(0), value)), expected_round: Round::new(0), new_state: State { height: Height::new(1), @@ -482,17 +482,17 @@ fn driver_steps_not_proposer_valid() { }, ]; - let mut event_from_previous_msg = None; + let mut output_from_prev_input = None; for step in steps { println!("Step: {}", step.desc); - let execute_event = step - .input_event - .unwrap_or_else(|| event_from_previous_msg.unwrap()); + let input = step + .input + .unwrap_or_else(|| output_from_prev_input.unwrap()); - let output = block_on(driver.execute(execute_event)).expect("execute succeeded"); - assert_eq!(output, step.expected_output, "expected output message"); + 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, @@ -501,7 +501,7 @@ fn driver_steps_not_proposer_valid() { assert_eq!(driver.round_state, step.new_state, "expected state"); - event_from_previous_msg = output.and_then(msg_to_event); + output_from_prev_input = output.and_then(output_to_input); } } @@ -525,8 +525,8 @@ fn driver_steps_not_proposer_invalid() { let steps = vec![ TestStep { desc: "Start round 0, we are not the proposer", - input_event: Some(Event::NewRound(Height::new(1), Round::new(0))), - expected_output: Some(Message::ScheduleTimeout(Timeout::propose(Round::new(0)))), + input: Some(Input::NewRound(Height::new(1), Round::new(0))), + expected_output: Some(Output::ScheduleTimeout(Timeout::propose(Round::new(0)))), expected_round: Round::new(0), new_state: State { height: Height::new(1), @@ -539,8 +539,8 @@ fn driver_steps_not_proposer_invalid() { }, TestStep { desc: "Receive an invalid proposal, prevote for nil (v2)", - input_event: Some(Event::Proposal(proposal.clone(), Validity::Invalid)), - expected_output: Some(Message::Vote( + input: Some(Input::Proposal(proposal.clone(), Validity::Invalid)), + expected_output: Some(Output::Vote( Vote::new_prevote(Height::new(1),Round::new(0), None, my_addr).signed(&my_sk), )), expected_round: Round::new(0), @@ -555,7 +555,7 @@ fn driver_steps_not_proposer_invalid() { }, TestStep { desc: "Receive our own prevote (v2)", - input_event: None, + input: None, expected_output: None, expected_round: Round::new(0), new_state: State { @@ -569,7 +569,7 @@ fn driver_steps_not_proposer_invalid() { }, TestStep { desc: "v1 prevotes for its own proposal", - input_event: Some(Event::Vote( + input: Some(Input::Vote( Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), v1.address).signed(&sk1), )), expected_output: None, @@ -585,10 +585,10 @@ 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( + input: Some(Input::Vote( Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), v3.address).signed(&sk3), )), - expected_output: Some(Message::ScheduleTimeout(Timeout::prevote(Round::new(0)))), + expected_output: Some(Output::ScheduleTimeout(Timeout::prevote(Round::new(0)))), expected_round: Round::new(0), new_state: State { height: Height::new(1), @@ -601,8 +601,8 @@ fn driver_steps_not_proposer_invalid() { }, TestStep { desc: "prevote timeout elapses, we precommit for nil (v2)", - input_event: Some(Event::TimeoutElapsed(Timeout::prevote(Round::new(0)))), - expected_output: Some(Message::Vote( + input: Some(Input::TimeoutElapsed(Timeout::prevote(Round::new(0)))), + expected_output: Some(Output::Vote( Vote::new_precommit(Height::new(1), Round::new(0), None, my_addr).signed(&my_sk), )), expected_round: Round::new(0), @@ -617,16 +617,16 @@ fn driver_steps_not_proposer_invalid() { }, ]; - let mut event_from_previous_msg = None; + let mut output_from_prev_input = None; for step in steps { println!("Step: {}", step.desc); - let execute_event = step - .input_event - .unwrap_or_else(|| event_from_previous_msg.unwrap()); + let input = step + .input + .unwrap_or_else(|| output_from_prev_input.unwrap()); - let output = block_on(driver.execute(execute_event)).expect("execute succeeded"); + let output = block_on(driver.execute(input)).expect("execute succeeded"); assert_eq!(output, step.expected_output, "expected output"); assert_eq!( @@ -636,7 +636,7 @@ fn driver_steps_not_proposer_invalid() { assert_eq!(driver.round_state, step.new_state, "expected state"); - event_from_previous_msg = output.and_then(msg_to_event); + output_from_prev_input = output.and_then(output_to_input); } } @@ -661,8 +661,8 @@ fn driver_steps_not_proposer_other_height() { let steps = vec![ TestStep { desc: "Start round 0, we are not the proposer", - input_event: Some(Event::NewRound(Height::new(1), Round::new(0))), - expected_output: Some(Message::ScheduleTimeout(Timeout::propose(Round::new(0)))), + input: Some(Input::NewRound(Height::new(1), Round::new(0))), + expected_output: Some(Output::ScheduleTimeout(Timeout::propose(Round::new(0)))), expected_round: Round::new(0), new_state: State { height: Height::new(1), @@ -675,7 +675,7 @@ fn driver_steps_not_proposer_other_height() { }, TestStep { desc: "Receive a proposal for another height, ignore it (v2)", - input_event: Some(Event::Proposal(proposal.clone(), Validity::Invalid)), + input: Some(Input::Proposal(proposal.clone(), Validity::Invalid)), expected_output: None, expected_round: Round::new(0), new_state: State { @@ -689,16 +689,16 @@ fn driver_steps_not_proposer_other_height() { }, ]; - let mut event_from_previous_msg = None; + let mut output_from_prev_input = None; for step in steps { println!("Step: {}", step.desc); - let execute_event = step - .input_event - .unwrap_or_else(|| event_from_previous_msg.unwrap()); + let input = step + .input + .unwrap_or_else(|| output_from_prev_input.unwrap()); - let output = block_on(driver.execute(execute_event)).expect("execute succeeded"); + let output = block_on(driver.execute(input)).expect("execute succeeded"); assert_eq!(output, step.expected_output, "expected output"); assert_eq!( @@ -708,7 +708,7 @@ fn driver_steps_not_proposer_other_height() { assert_eq!(driver.round_state, step.new_state, "expected state"); - event_from_previous_msg = output.and_then(msg_to_event); + output_from_prev_input = output.and_then(output_to_input); } } @@ -733,8 +733,8 @@ fn driver_steps_not_proposer_other_round() { let steps = vec![ TestStep { desc: "Start round 0, we are not the proposer", - input_event: Some(Event::NewRound(Height::new(1), Round::new(0))), - expected_output: Some(Message::ScheduleTimeout(Timeout::propose(Round::new(0)))), + input: Some(Input::NewRound(Height::new(1), Round::new(0))), + expected_output: Some(Output::ScheduleTimeout(Timeout::propose(Round::new(0)))), expected_round: Round::new(0), new_state: State { height: Height::new(1), @@ -747,7 +747,7 @@ fn driver_steps_not_proposer_other_round() { }, TestStep { desc: "Receive a proposal for another round, ignore it (v2)", - input_event: Some(Event::Proposal(proposal.clone(), Validity::Invalid)), + input: Some(Input::Proposal(proposal.clone(), Validity::Invalid)), expected_output: None, expected_round: Round::new(0), new_state: State { @@ -761,16 +761,16 @@ fn driver_steps_not_proposer_other_round() { }, ]; - let mut event_from_previous_msg = None; + let mut output_from_prev_input = None; for step in steps { println!("Step: {}", step.desc); - let execute_event = step - .input_event - .unwrap_or_else(|| event_from_previous_msg.unwrap()); + let input = step + .input + .unwrap_or_else(|| output_from_prev_input.unwrap()); - let output = block_on(driver.execute(execute_event)).expect("execute succeeded"); + let output = block_on(driver.execute(input)).expect("execute succeeded"); assert_eq!(output, step.expected_output, "expected output"); assert_eq!( @@ -780,7 +780,7 @@ fn driver_steps_not_proposer_other_round() { assert_eq!(driver.round_state, step.new_state, "expected state"); - event_from_previous_msg = output.and_then(msg_to_event); + output_from_prev_input = output.and_then(output_to_input); } } @@ -803,8 +803,8 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { // 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::new(1), Round::new(0))), - expected_output: Some(Message::ScheduleTimeout(Timeout::propose(Round::new(0)))), + input: Some(Input::NewRound(Height::new(1), Round::new(0))), + expected_output: Some(Output::ScheduleTimeout(Timeout::propose(Round::new(0)))), expected_round: Round::new(0), new_state: State { height: Height::new(1), @@ -818,8 +818,8 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { // 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( + input: Some(Input::TimeoutElapsed(Timeout::propose(Round::new(0)))), + expected_output: Some(Output::Vote( Vote::new_prevote(Height::new(1), Round::new(0), None, my_addr).signed(&my_sk), )), expected_round: Round::new(0), @@ -835,7 +835,7 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { // Receive our own prevote v3 TestStep { desc: "Receive our own prevote v3", - input_event: None, + input: None, expected_output: None, expected_round: Round::new(0), new_state: State { @@ -850,7 +850,7 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { // v1 prevotes for its own proposal TestStep { desc: "v1 prevotes for its own proposal", - input_event: Some(Event::Vote( + input: Some(Input::Vote( Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), v1.address) .signed(&sk1), )), @@ -868,10 +868,10 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { // v2 prevotes for nil, we get +2/3 nil prevotes and precommit for nil TestStep { desc: "v2 prevotes for nil, we get +2/3 prevotes, precommit for nil", - input_event: Some(Event::Vote( + input: Some(Input::Vote( Vote::new_prevote(Height::new(1), Round::new(0), None, v2.address).signed(&sk2), )), - expected_output: Some(Message::Vote( + expected_output: Some(Output::Vote( Vote::new_precommit(Height::new(1), Round::new(0), None, my_addr).signed(&my_sk), )), expected_round: Round::new(0), @@ -887,7 +887,7 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { // v3 receives its own precommit TestStep { desc: "v3 receives its own precommit", - input_event: None, + input: None, expected_output: None, expected_round: Round::new(0), new_state: State { @@ -902,7 +902,7 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { // v1 precommits its proposal TestStep { desc: "v1 precommits its proposal", - input_event: Some(Event::Vote( + input: Some(Input::Vote( Vote::new_precommit(Height::new(1), Round::new(0), Some(value.id()), v1.address) .signed(&sk1), )), @@ -920,10 +920,10 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { // v2 precommits for nil TestStep { desc: "v2 precommits for nil", - input_event: Some(Event::Vote( + input: Some(Input::Vote( Vote::new_precommit(Height::new(1), Round::new(0), None, v2.address).signed(&sk2), )), - expected_output: Some(Message::ScheduleTimeout(Timeout::precommit(Round::new(0)))), + expected_output: Some(Output::ScheduleTimeout(Timeout::precommit(Round::new(0)))), expected_round: Round::new(0), new_state: State { height: Height::new(1), @@ -937,8 +937,8 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { // we receive a precommit timeout, start a new round 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(Height::new(1), Round::new(1))), + 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), new_state: State { height: Height::new(1), @@ -951,8 +951,8 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { }, TestStep { desc: "Start round 1, we are not the proposer", - input_event: Some(Event::NewRound(Height::new(1), Round::new(1))), - expected_output: Some(Message::ScheduleTimeout(Timeout::propose(Round::new(1)))), + input: Some(Input::NewRound(Height::new(1), Round::new(1))), + expected_output: Some(Output::ScheduleTimeout(Timeout::propose(Round::new(1)))), expected_round: Round::new(1), new_state: State { height: Height::new(1), @@ -965,21 +965,21 @@ fn driver_steps_not_proposer_timeout_multiple_rounds() { }, ]; - let mut event_from_previous_msg = None; + let mut output_from_prev_input = None; for step in steps { println!("Step: {}", step.desc); - let execute_event = step - .input_event - .unwrap_or_else(|| event_from_previous_msg.unwrap()); + let input = step + .input + .unwrap_or_else(|| output_from_prev_input.unwrap()); - let output = block_on(driver.execute(execute_event)).expect("execute succeeded"); - assert_eq!(output, step.expected_output, "expected output message"); + 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"); - event_from_previous_msg = output.and_then(msg_to_event); + output_from_prev_input = output.and_then(output_to_input); } } @@ -996,12 +996,12 @@ fn driver_steps_no_value_to_propose() { let mut driver = Driver::new(ctx, sel, vs, my_addr); - let output = block_on(driver.execute(Event::NewRound(Height::new(1), Round::new(0)))) + let output = block_on(driver.execute(Input::NewRound(Height::new(1), Round::new(0)))) .expect("execute succeeded"); assert_eq!( output, - Some(Message::GetValueAndScheduleTimeout( + Some(Output::GetValueAndScheduleTimeout( Round::new(0), Timeout::propose(Round::new(0)) )) @@ -1022,7 +1022,7 @@ fn driver_steps_proposer_not_found() { let mut driver = Driver::new(ctx, sel, vs, my_addr); - let output = block_on(driver.execute(Event::NewRound(Height::new(1), Round::new(0)))); + let output = block_on(driver.execute(Input::NewRound(Height::new(1), Round::new(0)))); assert_eq!(output, Err(Error::ProposerNotFound(v1.address))); } @@ -1043,11 +1043,11 @@ fn driver_steps_validator_not_found() { let mut driver = Driver::new(ctx, sel, vs, my_addr); // Start new height - block_on(driver.execute(Event::NewRound(Height::new(1), Round::new(0)))) + block_on(driver.execute(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(Event::Vote( + let output = block_on(driver.execute(Input::Vote( Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), v2.address).signed(&sk2), ))); @@ -1069,12 +1069,12 @@ fn driver_steps_invalid_signature() { let mut driver = Driver::new(ctx, sel, vs, my_addr); // Start new round - block_on(driver.execute(Event::NewRound(Height::new(1), Round::new(0)))) + block_on(driver.execute(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(Event::Vote( + let output = block_on(driver.execute(Input::Vote( Vote::new_prevote(Height::new(1), Round::new(0), Some(value.id()), v2.address).signed(&sk1), ))); @@ -1102,8 +1102,8 @@ fn driver_steps_skip_round_skip_threshold() { // 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)))), + input: Some(Input::NewRound(height, Round::new(0))), + expected_output: Some(Output::ScheduleTimeout(Timeout::propose(Round::new(0)))), expected_round: Round::new(0), new_state: State { height, @@ -1117,8 +1117,8 @@ fn driver_steps_skip_round_skip_threshold() { // 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( + input: Some(Input::TimeoutElapsed(Timeout::propose(Round::new(0)))), + expected_output: Some(Output::Vote( Vote::new_prevote(height, Round::new(0), None, my_addr).signed(&my_sk), )), expected_round: Round::new(0), @@ -1134,7 +1134,7 @@ fn driver_steps_skip_round_skip_threshold() { // Receive our own prevote v3 TestStep { desc: "Receive our own prevote v3", - input_event: None, + input: None, expected_output: None, expected_round: Round::new(0), new_state: State { @@ -1149,7 +1149,7 @@ fn driver_steps_skip_round_skip_threshold() { // v1 prevotes for its own proposal TestStep { desc: "v1 prevotes for its own proposal in round 1", - input_event: Some(Event::Vote( + input: Some(Input::Vote( Vote::new_prevote(height, Round::new(1), Some(value.id()), v1.address).signed(&sk1), )), expected_output: None, @@ -1166,10 +1166,10 @@ fn driver_steps_skip_round_skip_threshold() { // 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( + input: Some(Input::Vote( Vote::new_prevote(height, Round::new(1), Some(value.id()), v2.address).signed(&sk2), )), - expected_output: Some(Message::NewRound(height, Round::new(1))), + expected_output: Some(Output::NewRound(height, Round::new(1))), expected_round: Round::new(1), new_state: State { height, @@ -1182,22 +1182,22 @@ fn driver_steps_skip_round_skip_threshold() { }, ]; - let mut event_from_previous_msg = None; + let mut output_from_prev_input = None; for step in steps { println!("Step: {}", step.desc); - let execute_event = step - .input_event - .unwrap_or_else(|| event_from_previous_msg.unwrap()); + let input = step + .input + .unwrap_or_else(|| output_from_prev_input.unwrap()); - let output = block_on(driver.execute(execute_event)).expect("execute succeeded"); - assert_eq!(output, step.expected_output, "expected output message"); + 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"); - event_from_previous_msg = output.and_then(msg_to_event); + output_from_prev_input = output.and_then(output_to_input); } } @@ -1222,8 +1222,8 @@ fn driver_steps_skip_round_quorum_threshold() { // 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)))), + input: Some(Input::NewRound(height, Round::new(0))), + expected_output: Some(Output::ScheduleTimeout(Timeout::propose(Round::new(0)))), expected_round: Round::new(0), new_state: State { height, @@ -1237,8 +1237,8 @@ fn driver_steps_skip_round_quorum_threshold() { // 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( + input: Some(Input::TimeoutElapsed(Timeout::propose(Round::new(0)))), + expected_output: Some(Output::Vote( Vote::new_prevote(height, Round::new(0), None, my_addr).signed(&my_sk), )), expected_round: Round::new(0), @@ -1254,7 +1254,7 @@ fn driver_steps_skip_round_quorum_threshold() { // Receive our own prevote v3 TestStep { desc: "Receive our own prevote v3", - input_event: None, + input: None, expected_output: None, expected_round: Round::new(0), new_state: State { @@ -1269,7 +1269,7 @@ fn driver_steps_skip_round_quorum_threshold() { // v1 prevotes for its own proposal TestStep { desc: "v1 prevotes for its own proposal in round 1", - input_event: Some(Event::Vote( + input: Some(Input::Vote( Vote::new_prevote(height, Round::new(1), Some(value.id()), v1.address).signed(&sk1), )), expected_output: None, @@ -1286,10 +1286,10 @@ fn driver_steps_skip_round_quorum_threshold() { // 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( + input: Some(Input::Vote( Vote::new_prevote(height, Round::new(1), Some(value.id()), v2.address).signed(&sk2), )), - expected_output: Some(Message::NewRound(height, Round::new(1))), + expected_output: Some(Output::NewRound(height, Round::new(1))), expected_round: Round::new(1), new_state: State { height, @@ -1302,22 +1302,22 @@ fn driver_steps_skip_round_quorum_threshold() { }, ]; - let mut event_from_previous_msg = None; + let mut input_from_prev_output = None; for step in steps { println!("Step: {}", step.desc); - let execute_event = step - .input_event - .unwrap_or_else(|| event_from_previous_msg.unwrap()); + let input = step + .input + .unwrap_or_else(|| input_from_prev_output.unwrap()); - let output = block_on(driver.execute(execute_event)).expect("execute succeeded"); - assert_eq!(output, step.expected_output, "expected output message"); + 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"); - event_from_previous_msg = output.and_then(msg_to_event); + 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 199f66aa8..ff9a3b9a7 100644 --- a/Code/test/tests/driver_extra.rs +++ b/Code/test/tests/driver_extra.rs @@ -1,7 +1,7 @@ use futures::executor::block_on; use malachite_common::Round; -use malachite_driver::{Driver, Event, Message, Validity}; +use malachite_driver::{Driver, Input, Output, Validity}; use malachite_round::state::State; use malachite_test::{Height, Proposal, TestContext, ValidatorSet, Value}; @@ -11,21 +11,21 @@ use malachite_test::utils::*; // TODO - move all below to utils? struct TestStep { desc: &'static str, - input_event: Event, - expected_output: Option>, + input: Input, + expected_output: Option>, expected_round: Round, new_state: State, } -pub fn msg_to_event(output: Message) -> Option> { +pub fn output_to_input(output: Output) -> Option> { match output { - Message::NewRound(height, round) => Some(Event::NewRound(height, round)), + Output::NewRound(height, round) => Some(Input::NewRound(height, round)), // Let's consider our own proposal to always be valid - Message::Propose(p) => Some(Event::Proposal(p, Validity::Valid)), - Message::Vote(v) => Some(Event::Vote(v)), - Message::Decide(_, _) => None, - Message::ScheduleTimeout(_) => None, - Message::GetValueAndScheduleTimeout(_, _) => None, + Output::Propose(p) => Some(Input::Proposal(p, Validity::Valid)), + Output::Vote(v) => Some(Input::Vote(v)), + Output::Decide(_, _) => None, + Output::ScheduleTimeout(_) => None, + Output::GetValueAndScheduleTimeout(_, _) => None, } } @@ -57,29 +57,29 @@ 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", - input_event: new_round_event(Round::new(0)), - expected_output: start_propose_timer_msg(Round::new(0)), + 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 precommits a proposal", - input_event: precommit_event(Round::new(0), value, &v1.address, &sk1), + input: precommit_input(Round::new(0), value, &v1.address, &sk1), expected_output: None, expected_round: Round::new(0), new_state: propose_state(Round::new(0)), }, TestStep { desc: "v2 precommits for same proposal, we get +2/3 precommit, start precommit timer", - input_event: precommit_event(Round::new(0), value, &v2.address, &sk2), - expected_output: start_precommit_timer_msg(Round::new(0)), + input: precommit_input(Round::new(0), value, &v2.address, &sk2), + expected_output: start_precommit_timer_output(Round::new(0)), expected_round: Round::new(0), new_state: propose_state(Round::new(0)), }, TestStep { desc: "Receive proposal", - input_event: proposal_event(Round::new(0), value, Round::Nil, Validity::Valid), - expected_output: decide_message(Round::new(0), value), + input: proposal_input(Round::new(0), value, Round::Nil, Validity::Valid), + expected_output: decide_output(Round::new(0), value), expected_round: Round::new(0), new_state: decided_state( Round::new(0), @@ -129,57 +129,57 @@ 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", - input_event: new_round_event(Round::new(0)), - expected_output: start_propose_timer_msg(Round::new(0)), + 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)", - input_event: timeout_propose_event(Round::new(0)), - expected_output: prevote_nil_msg(Round::new(0), &my_addr, &my_sk), + input: timeout_propose_input(Round::new(0)), + expected_output: prevote_nil_output(Round::new(0), &my_addr, &my_sk), expected_round: Round::new(0), new_state: prevote_state(Round::new(0)), }, TestStep { desc: "v1 prevotes a proposal", - input_event: prevote_event(&v1.address, &sk1), + input: prevote_input(&v1.address, &sk1), expected_output: None, expected_round: Round::new(0), new_state: prevote_state(Round::new(0)), }, TestStep { desc: "v2 prevotes for same proposal, we get +2/3 prevotes, start prevote timer", - input_event: prevote_event(&v2.address, &sk2), - expected_output: start_prevote_timer_msg(Round::new(0)), + input: prevote_input(&v2.address, &sk2), + expected_output: start_prevote_timer_output(Round::new(0)), expected_round: Round::new(0), new_state: prevote_state(Round::new(0)), }, TestStep { desc: "v1 precommits a proposal", - input_event: precommit_event(Round::new(0), value, &v1.address, &sk1), + input: precommit_input(Round::new(0), value, &v1.address, &sk1), expected_output: None, expected_round: Round::new(0), new_state: prevote_state(Round::new(0)), }, TestStep { desc: "v2 precommits for same proposal, we get +2/3 precommit, start precommit timer", - input_event: precommit_event(Round::new(0), value, &v2.address, &sk2), - expected_output: start_precommit_timer_msg(Round::new(0)), + input: precommit_input(Round::new(0), value, &v2.address, &sk2), + expected_output: start_precommit_timer_output(Round::new(0)), expected_round: Round::new(0), new_state: prevote_state(Round::new(0)), }, TestStep { desc: "Timeout precommit, start new round", - input_event: timeout_precommit_event(Round::new(0)), - expected_output: new_round_msg(Round::new(1)), + input: timeout_precommit_input(Round::new(0)), + expected_output: new_round_output(Round::new(1)), expected_round: Round::new(1), new_state: new_round(Round::new(1)), }, TestStep { desc: "Receive proposal", - input_event: proposal_event(Round::new(0), value, Round::Nil, Validity::Valid), - expected_output: decide_message(Round::new(0), value), + input: proposal_input(Round::new(0), value, Round::Nil, Validity::Valid), + expected_output: decide_output(Round::new(0), value), expected_round: Round::new(1), new_state: decided_state( Round::new(1), @@ -231,15 +231,15 @@ fn driver_steps_polka_previous_with_locked() { let steps = vec![ TestStep { desc: "Start round 0, we, v2, are not the proposer, start timeout propose", - input_event: new_round_event(Round::new(0)), - expected_output: start_propose_timer_msg(Round::new(0)), + 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: "receive a proposal from v1 - L22 send prevote", - input_event: proposal_event(Round::new(0), value, Round::Nil, Validity::Valid), - expected_output: prevote_msg(Round::new(0), &my_addr, &my_sk), + input: proposal_input(Round::new(0), value, Round::Nil, Validity::Valid), + expected_output: prevote_output(Round::new(0), &my_addr, &my_sk), expected_round: Round::new(0), new_state: prevote_state_with_proposal( Round::new(0), @@ -248,7 +248,7 @@ fn driver_steps_polka_previous_with_locked() { }, TestStep { desc: "v3 prevotes the proposal", - input_event: prevote_event(&v3.address, &sk3), + input: prevote_input(&v3.address, &sk3), expected_output: None, expected_round: Round::new(0), new_state: prevote_state_with_proposal( @@ -258,8 +258,8 @@ fn driver_steps_polka_previous_with_locked() { }, TestStep { desc: "v1 prevotes for same proposal, we get +2/3 prevotes, precommit", - input_event: prevote_event(&v1.address, &sk1), - expected_output: precommit_msg(Round::new(0), value, &my_addr, &my_sk), + input: prevote_input(&v1.address, &sk1), + 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), @@ -268,8 +268,8 @@ fn driver_steps_polka_previous_with_locked() { }, TestStep { desc: "Receive f+1 vote for round 1 from v3", - input_event: precommit_event(Round::new(1), Value::new(8888), &v3.address, &sk3), - expected_output: new_round_msg(Round::new(1)), + input: precommit_input(Round::new(1), Value::new(8888), &v3.address, &sk3), + expected_output: new_round_output(Round::new(1)), expected_round: Round::new(1), new_state: new_round_with_proposal_and_locked_and_valid( Round::new(1), @@ -278,8 +278,8 @@ fn driver_steps_polka_previous_with_locked() { }, TestStep { desc: "start round 1, we are proposer with a valid value, propose it", - input_event: new_round_event(Round::new(1)), - expected_output: proposal_msg(Round::new(1), value, Round::new(0)), + input: new_round_input(Round::new(1)), + expected_output: proposal_output(Round::new(1), value, Round::new(0)), expected_round: Round::new(1), new_state: propose_state_with_proposal_and_locked_and_valid( Round::new(1), @@ -288,8 +288,8 @@ fn driver_steps_polka_previous_with_locked() { }, TestStep { desc: "Receive our own proposal", - input_event: proposal_event(Round::new(1), value, Round::new(0), Validity::Valid), - expected_output: prevote_msg(Round::new(1), &my_addr, &my_sk), + input: proposal_input(Round::new(1), value, Round::new(0), Validity::Valid), + expected_output: prevote_output(Round::new(1), &my_addr, &my_sk), expected_round: Round::new(1), new_state: prevote_state_with_proposal_and_locked_and_valid( Round::new(1), @@ -317,15 +317,15 @@ fn driver_steps_polka_previous_invalid_proposal_with_locked() { let steps = vec![ TestStep { desc: "Start round 0, we, v2, are not the proposer, start timeout propose", - input_event: new_round_event(Round::new(0)), - expected_output: start_propose_timer_msg(Round::new(0)), + 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: "receive a proposal from v1 - L22 send prevote", - input_event: proposal_event(Round::new(0), value, Round::Nil, Validity::Valid), - expected_output: prevote_msg(Round::new(0), &my_addr, &my_sk), + input: proposal_input(Round::new(0), value, Round::Nil, Validity::Valid), + expected_output: prevote_output(Round::new(0), &my_addr, &my_sk), expected_round: Round::new(0), new_state: prevote_state_with_proposal( Round::new(0), @@ -334,7 +334,7 @@ fn driver_steps_polka_previous_invalid_proposal_with_locked() { }, TestStep { desc: "v3 prevotes the proposal", - input_event: prevote_event(&v3.address, &sk3), + input: prevote_input(&v3.address, &sk3), expected_output: None, expected_round: Round::new(0), new_state: prevote_state_with_proposal( @@ -344,8 +344,8 @@ fn driver_steps_polka_previous_invalid_proposal_with_locked() { }, TestStep { desc: "v1 prevotes for same proposal, we get +2/3 prevotes, precommit", - input_event: prevote_event(&v1.address, &sk1), - expected_output: precommit_msg(Round::new(0), value, &my_addr, &my_sk), + input: prevote_input(&v1.address, &sk1), + 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), @@ -354,8 +354,8 @@ fn driver_steps_polka_previous_invalid_proposal_with_locked() { }, TestStep { desc: "Receive f+1 vote for round 1 from v3", - input_event: precommit_event(Round::new(1), Value::new(8888), &v3.address, &sk3), - expected_output: new_round_msg(Round::new(1)), + input: precommit_input(Round::new(1), Value::new(8888), &v3.address, &sk3), + expected_output: new_round_output(Round::new(1)), expected_round: Round::new(1), new_state: new_round_with_proposal_and_locked_and_valid( Round::new(1), @@ -364,8 +364,8 @@ fn driver_steps_polka_previous_invalid_proposal_with_locked() { }, TestStep { desc: "start round 1, we are proposer with a valid value, propose it", - input_event: new_round_event(Round::new(1)), - expected_output: proposal_msg(Round::new(1), value, Round::new(0)), + input: new_round_input(Round::new(1)), + expected_output: proposal_output(Round::new(1), value, Round::new(0)), expected_round: Round::new(1), new_state: propose_state_with_proposal_and_locked_and_valid( Round::new(1), @@ -374,8 +374,8 @@ fn driver_steps_polka_previous_invalid_proposal_with_locked() { }, TestStep { desc: "Receive our own proposal", - input_event: proposal_event(Round::new(1), value, Round::new(0), Validity::Invalid), - expected_output: prevote_nil_msg(Round::new(1), &my_addr, &my_sk), + input: proposal_input(Round::new(1), value, Round::new(0), Validity::Invalid), + expected_output: prevote_nil_output(Round::new(1), &my_addr, &my_sk), expected_round: Round::new(1), new_state: prevote_state_with_proposal_and_locked_and_valid( Round::new(1), @@ -433,42 +433,42 @@ 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", - input_event: new_round_event(Round::new(0)), - expected_output: start_propose_timer_msg(Round::new(0)), + 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)", - input_event: timeout_propose_event(Round::new(0)), - expected_output: prevote_nil_msg(Round::new(0), &my_addr, &my_sk), + input: timeout_propose_input(Round::new(0)), + expected_output: prevote_nil_output(Round::new(0), &my_addr, &my_sk), expected_round: Round::new(0), new_state: prevote_state(Round::new(0)), }, TestStep { desc: "v3 prevotes for some proposal", - input_event: prevote_event(&v3.address, &sk3), + input: prevote_input(&v3.address, &sk3), expected_output: None, expected_round: Round::new(0), new_state: prevote_state(Round::new(0)), }, TestStep { desc: "v1 prevotes for same proposal, we get +2/3 prevotes, start timeout prevote", - input_event: prevote_event(&v1.address, &sk1), - expected_output: start_prevote_timer_msg(Round::new(0)), + input: prevote_input(&v1.address, &sk1), + expected_output: start_prevote_timer_output(Round::new(0)), expected_round: Round::new(0), new_state: prevote_state(Round::new(0)), }, TestStep { desc: "timeout prevote, prevote for nil (v2)", - input_event: timeout_prevote_event(Round::new(0)), - expected_output: precommit_nil_msg(&my_addr, &my_sk), + input: timeout_prevote_input(Round::new(0)), + expected_output: precommit_nil_output(&my_addr, &my_sk), expected_round: Round::new(0), new_state: precommit_state(Round::new(0)), }, TestStep { desc: "receive a proposal - L36, we don't lock, we set valid", - input_event: proposal_event(Round::new(0), value, Round::Nil, Validity::Valid), + input: proposal_input(Round::new(0), value, Round::Nil, Validity::Valid), expected_output: None, expected_round: Round::new(0), new_state: precommit_state_with_proposal_and_valid( @@ -479,8 +479,8 @@ fn driver_steps_polka_previous_with_no_locked() { }, TestStep { desc: "Receive f+1 vote for round 1 from v3", - input_event: prevote_event_at(Round::new(1), &v3.address, &sk3), - expected_output: new_round_msg(Round::new(1)), + input: prevote_input_at(Round::new(1), &v3.address, &sk3), + expected_output: new_round_output(Round::new(1)), expected_round: Round::new(1), new_state: new_round_with_proposal_and_valid( Round::new(1), @@ -489,8 +489,8 @@ fn driver_steps_polka_previous_with_no_locked() { }, TestStep { desc: "start round 1, we are proposer with a valid value from round 0, propose it", - input_event: new_round_event(Round::new(1)), - expected_output: proposal_msg(Round::new(1), value, Round::new(0)), + input: new_round_input(Round::new(1)), + expected_output: proposal_output(Round::new(1), value, Round::new(0)), expected_round: Round::new(1), new_state: propose_state_with_proposal_and_valid( Round::new(1), @@ -500,8 +500,8 @@ fn driver_steps_polka_previous_with_no_locked() { }, TestStep { desc: "Receive our own proposal, prevote nil as we are not locked on the value", - input_event: proposal_event(Round::new(1), value, Round::new(0), Validity::Valid), - expected_output: prevote_nil_msg(Round::new(1), &my_addr, &my_sk), + input: proposal_input(Round::new(1), value, Round::new(0), Validity::Valid), + expected_output: prevote_nil_output(Round::new(1), &my_addr, &my_sk), expected_round: Round::new(1), new_state: prevote_state_with_proposal_and_valid( Round::new(1), @@ -518,8 +518,8 @@ fn run_steps(driver: &mut Driver, steps: Vec) { for step in steps { println!("Step: {}", step.desc); - let output = block_on(driver.execute(step.input_event)).expect("execute succeeded"); - assert_eq!(output, step.expected_output, "expected output message"); + let output = block_on(driver.execute(step.input)).expect("execute succeeded"); + assert_eq!(output, step.expected_output, "expected output"); assert_eq!( driver.round_state.round, step.expected_round, diff --git a/Code/test/tests/round.rs b/Code/test/tests/round.rs index 0952ff747..246194a7a 100644 --- a/Code/test/tests/round.rs +++ b/Code/test/tests/round.rs @@ -1,10 +1,10 @@ use malachite_test::{Address, Height, Proposal, TestContext, Value}; use malachite_common::{Round, Timeout, TimeoutStep}; -use malachite_round::events::Event; -use malachite_round::message::Message; +use malachite_round::input::Input; +use malachite_round::output::Output; use malachite_round::state::{State, Step}; -use malachite_round::state_machine::{apply_event, Info}; +use malachite_round::state_machine::{apply, Info}; const ADDRESS: Address = Address::new([42; 20]); const OTHER_ADDRESS: Address = Address::new([21; 20]); @@ -24,22 +24,22 @@ fn test_propose() { // We are the proposer let data = Info::new(round, &ADDRESS, &ADDRESS); - let transition = apply_event(state.clone(), &data, Event::NewRound); + let transition = apply(state.clone(), &data, Input::NewRound); state.step = Step::Propose; assert_eq!(transition.next_state, state); assert_eq!( - transition.message.unwrap(), - Message::get_value_and_schedule_timeout(round, TimeoutStep::Propose) + transition.output.unwrap(), + Output::get_value_and_schedule_timeout(round, TimeoutStep::Propose) ); - let transition = apply_event(transition.next_state, &data, Event::ProposeValue(value)); + let transition = apply(transition.next_state, &data, Input::ProposeValue(value)); state.step = Step::Propose; assert_eq!(transition.next_state, state); assert_eq!( - transition.message.unwrap(), - Message::proposal(Height::new(10), Round::new(0), Value::new(42), Round::Nil) + transition.output.unwrap(), + Output::proposal(Height::new(10), Round::new(0), Value::new(42), Round::Nil) ); } @@ -58,12 +58,12 @@ fn test_prevote() { // We are not the proposer let data = Info::new(Round::new(1), &ADDRESS, &OTHER_ADDRESS); - let transition = apply_event(state, &data, Event::NewRound); + let transition = apply(state, &data, Input::NewRound); assert_eq!(transition.next_state.step, Step::Propose); assert_eq!( - transition.message.unwrap(), - Message::ScheduleTimeout(Timeout { + transition.output.unwrap(), + Output::ScheduleTimeout(Timeout { round: Round::new(1), step: TimeoutStep::Propose }) @@ -71,10 +71,10 @@ fn test_prevote() { let state = transition.next_state; - let transition = apply_event( + let transition = apply( state, &data, - Event::Proposal(Proposal::new( + Input::Proposal(Proposal::new( Height::new(1), Round::new(1), value, @@ -84,7 +84,7 @@ fn test_prevote() { assert_eq!(transition.next_state.step, Step::Prevote); assert_eq!( - transition.message.unwrap(), - Message::prevote(Height::new(1), Round::new(1), Some(value.id()), ADDRESS) + transition.output.unwrap(), + Output::prevote(Height::new(1), Round::new(1), Some(value.id()), ADDRESS) ); } diff --git a/Code/test/tests/vote_keeper.rs b/Code/test/tests/vote_keeper.rs index f1d6d7849..f3cd9df3f 100644 --- a/Code/test/tests/vote_keeper.rs +++ b/Code/test/tests/vote_keeper.rs @@ -1,5 +1,5 @@ use malachite_common::Round; -use malachite_vote::keeper::{Message, VoteKeeper}; +use malachite_vote::keeper::{Output, VoteKeeper}; use malachite_test::{Address, Height, TestContext, ValueId, Vote}; @@ -24,7 +24,7 @@ fn prevote_apply_nil() { let vote = Vote::new_prevote(height, round, None, ADDRESS3); let msg = keeper.apply_vote(vote, 1, round); - assert_eq!(msg, Some(Message::PolkaNil)); + assert_eq!(msg, Some(Output::PolkaNil)); } #[test] @@ -43,7 +43,7 @@ fn precommit_apply_nil() { 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)); + assert_eq!(msg, Some(Output::PrecommitAny)); } #[test] @@ -65,11 +65,11 @@ fn prevote_apply_single_value() { 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)); + assert_eq!(msg, Some(Output::PolkaAny)); 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))); + assert_eq!(msg, Some(Output::PolkaValue(id))); } #[test] @@ -91,11 +91,11 @@ fn precommit_apply_single_value() { 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)); + assert_eq!(msg, Some(Output::PrecommitAny)); 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))); + assert_eq!(msg, Some(Output::PrecommitValue(id))); } #[test] @@ -118,7 +118,7 @@ fn skip_round_small_quorum_prevotes_two_vals() { 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)))); + assert_eq!(msg, Some(Output::SkipRound(Round::new(1)))); } #[test] @@ -141,7 +141,7 @@ fn skip_round_small_quorum_with_prevote_precommit_two_vals() { 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)))); + assert_eq!(msg, Some(Output::SkipRound(Round::new(1)))); } #[test] @@ -164,7 +164,7 @@ fn skip_round_full_quorum_with_prevote_precommit_two_vals() { 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)))); + assert_eq!(msg, Some(Output::SkipRound(Round::new(1)))); } #[test] diff --git a/Code/vote/src/keeper.rs b/Code/vote/src/keeper.rs index 16864e06d..fc4371f00 100644 --- a/Code/vote/src/keeper.rs +++ b/Code/vote/src/keeper.rs @@ -10,7 +10,7 @@ use crate::{Threshold, ThresholdParam, ThresholdParams, Weight}; /// Messages emitted by the vote keeper #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Message { +pub enum Output { PolkaAny, PolkaNil, PolkaValue(Value), @@ -25,7 +25,7 @@ where { votes: RoundVotes>, addresses_weights: RoundWeights, - emitted_msgs: BTreeSet>>, + emitted_msgs: BTreeSet>>, } impl PerRound @@ -48,7 +48,7 @@ where &self.addresses_weights } - pub fn emitted_msgs(&self) -> &BTreeSet>> { + pub fn emitted_msgs(&self) -> &BTreeSet>> { &self.emitted_msgs } } @@ -111,13 +111,13 @@ where &self.per_round } - /// Apply a vote with a given weight, potentially triggering an event. + /// Apply a vote with a given weight, potentially triggering an output. pub fn apply_vote( &mut self, vote: Ctx::Vote, weight: Weight, current_round: Round, - ) -> Option>> { + ) -> Option>> { let round = self .per_round .entry(vote.round()) @@ -143,7 +143,7 @@ where .is_met(combined_weight, self.total_weight); if skip_round { - let msg = Message::SkipRound(vote.round()); + let msg = Output::SkipRound(vote.round()); round.emitted_msgs.insert(msg.clone()); return Some(msg); } @@ -157,7 +157,7 @@ where self.total_weight, ); - let msg = threshold_to_message(vote.vote_type(), vote.round(), threshold); + let msg = threshold_to_output(vote.vote_type(), vote.round(), threshold); match msg { Some(msg) if !round.emitted_msgs.contains(&msg) => { @@ -217,23 +217,23 @@ where } } -/// Map a vote type and a threshold to a state machine event. -fn threshold_to_message( +/// Map a vote type and a threshold to a state machine output. +fn threshold_to_output( typ: VoteType, round: Round, threshold: Threshold, -) -> Option> { +) -> Option> { match (typ, threshold) { (_, Threshold::Unreached) => None, - (_, Threshold::Skip) => Some(Message::SkipRound(round)), + (_, Threshold::Skip) => Some(Output::SkipRound(round)), - (VoteType::Prevote, Threshold::Any) => Some(Message::PolkaAny), - (VoteType::Prevote, Threshold::Nil) => Some(Message::PolkaNil), - (VoteType::Prevote, Threshold::Value(v)) => Some(Message::PolkaValue(v)), + (VoteType::Prevote, Threshold::Any) => Some(Output::PolkaAny), + (VoteType::Prevote, Threshold::Nil) => Some(Output::PolkaNil), + (VoteType::Prevote, Threshold::Value(v)) => Some(Output::PolkaValue(v)), - (VoteType::Precommit, Threshold::Any) => Some(Message::PrecommitAny), - (VoteType::Precommit, Threshold::Nil) => Some(Message::PrecommitAny), - (VoteType::Precommit, Threshold::Value(v)) => Some(Message::PrecommitValue(v)), + (VoteType::Precommit, Threshold::Any) => Some(Output::PrecommitAny), + (VoteType::Precommit, Threshold::Nil) => Some(Output::PrecommitAny), + (VoteType::Precommit, Threshold::Value(v)) => Some(Output::PrecommitValue(v)), } } From d4090cb9835b05c5c3aa8e0c8a1b348073b8b881 Mon Sep 17 00:00:00 2001 From: Romain Ruetschi Date: Thu, 30 Nov 2023 11:20:05 +0100 Subject: [PATCH 7/7] spec: Renaming and cleanup (#94) * Remove ITF trace * Rename executor to driver * Rename output of votekeeper to `VoteKeeperOutput` * Rename `Event` to `ConsensusInput` * Rename `ProposeMsg_t` to `Proposal_t` and `VoteMsg_t` to `Vote_t` * Cleanup * Rename `PendingEvent` to `PendingInput` * Fix last remaining instances of `result and `event` * Fix ITF structs * Cleanup in Rust code --- Code/itf/src/votekeeper.rs | 6 +- Code/itf/tests/votekeeper/runner.rs | 12 +- Code/vote/src/keeper.rs | 18 +- Specs/Quint/0DecideNonProposerTest.itf.json | 1 - Specs/Quint/Makefile | 2 +- Specs/Quint/consensus.qnt | 248 ++++++++++---------- Specs/Quint/consensusTest.qnt | 94 ++++---- Specs/Quint/{executor.qnt => driver.qnt} | 216 ++++++++--------- Specs/Quint/statemachineAsync.qnt | 38 +-- Specs/Quint/statemachineTest.qnt | 26 +- Specs/Quint/voteBookkeeper.qnt | 122 +++++----- Specs/Quint/voteBookkeeperTest.qnt | 56 ++--- 12 files changed, 418 insertions(+), 421 deletions(-) delete mode 100644 Specs/Quint/0DecideNonProposerTest.itf.json rename Specs/Quint/{executor.qnt => driver.qnt} (78%) diff --git a/Code/itf/src/votekeeper.rs b/Code/itf/src/votekeeper.rs index 933de3137..41e8d181e 100644 --- a/Code/itf/src/votekeeper.rs +++ b/Code/itf/src/votekeeper.rs @@ -41,7 +41,7 @@ pub struct RoundVotes { pub round: Round, pub prevotes: VoteCount, pub precommits: VoteCount, - pub emitted_events: HashSet, + pub emitted_outputs: HashSet, #[serde(with = "As::>")] pub votes_addresses_weights: HashMap, } @@ -57,7 +57,7 @@ pub struct VoteCount { } #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Hash)] -pub struct ExecutorEvent { +pub struct VoteKeeperOutput { #[serde(with = "As::")] pub round: Round, pub name: String, @@ -69,7 +69,7 @@ pub struct State { #[serde(rename = "voteBookkeeperTest::voteBookkeeperSM::bookkeeper")] pub bookkeeper: Bookkeeper, #[serde(rename = "voteBookkeeperTest::voteBookkeeperSM::lastEmitted")] - pub last_emitted: ExecutorEvent, + pub last_emitted: VoteKeeperOutput, #[serde(rename = "voteBookkeeperTest::voteBookkeeperSM::weightedVote")] #[serde(with = "As::<(Same, Integer, Integer)>")] pub weighted_vote: (Vote, Weight, Round), diff --git a/Code/itf/tests/votekeeper/runner.rs b/Code/itf/tests/votekeeper/runner.rs index b5c876e06..781629d1d 100644 --- a/Code/itf/tests/votekeeper/runner.rs +++ b/Code/itf/tests/votekeeper/runner.rs @@ -123,23 +123,23 @@ impl ItfRunner for VoteKeeperRunner { let actual_round = actual_state.per_round().get(&Round::new(round)).unwrap(); - let expected_events = &expected_round.emitted_events; - let actual_events = actual_round.emitted_msgs(); + let expected_outputs = &expected_round.emitted_outputs; + let actual_outputs = actual_round.emitted_outputs(); assert_eq!( - actual_events.len(), - expected_events.len(), + actual_outputs.len(), + expected_outputs.len(), "number of emitted events" ); let mut event_count = HashMap::new(); - for event in expected_events { + for event in expected_outputs { let count = event_count.entry(event.name.clone()).or_insert(0); *count += 1; } - for event in actual_events { + for event in actual_outputs { let event_name = match event { Output::PolkaValue(_) => "PolkaValue".into(), Output::PrecommitValue(_) => "PrecommitValue".into(), diff --git a/Code/vote/src/keeper.rs b/Code/vote/src/keeper.rs index fc4371f00..e81001f6d 100644 --- a/Code/vote/src/keeper.rs +++ b/Code/vote/src/keeper.rs @@ -25,7 +25,7 @@ where { votes: RoundVotes>, addresses_weights: RoundWeights, - emitted_msgs: BTreeSet>>, + emitted_outputs: BTreeSet>>, } impl PerRound @@ -36,7 +36,7 @@ where Self { votes: RoundVotes::new(), addresses_weights: RoundWeights::new(), - emitted_msgs: BTreeSet::new(), + emitted_outputs: BTreeSet::new(), } } @@ -48,8 +48,8 @@ where &self.addresses_weights } - pub fn emitted_msgs(&self) -> &BTreeSet>> { - &self.emitted_msgs + pub fn emitted_outputs(&self) -> &BTreeSet>> { + &self.emitted_outputs } } @@ -62,7 +62,7 @@ where Self { votes: self.votes.clone(), addresses_weights: self.addresses_weights.clone(), - emitted_msgs: self.emitted_msgs.clone(), + emitted_outputs: self.emitted_outputs.clone(), } } } @@ -76,7 +76,7 @@ where f.debug_struct("PerRound") .field("votes", &self.votes) .field("addresses_weights", &self.addresses_weights) - .field("emitted_msgs", &self.emitted_msgs) + .field("emitted_outputs", &self.emitted_outputs) .finish() } } @@ -144,7 +144,7 @@ where if skip_round { let msg = Output::SkipRound(vote.round()); - round.emitted_msgs.insert(msg.clone()); + round.emitted_outputs.insert(msg.clone()); return Some(msg); } } @@ -160,8 +160,8 @@ where let msg = threshold_to_output(vote.vote_type(), vote.round(), threshold); match msg { - Some(msg) if !round.emitted_msgs.contains(&msg) => { - round.emitted_msgs.insert(msg.clone()); + Some(msg) if !round.emitted_outputs.contains(&msg) => { + round.emitted_outputs.insert(msg.clone()); Some(msg) } _ => None, diff --git a/Specs/Quint/0DecideNonProposerTest.itf.json b/Specs/Quint/0DecideNonProposerTest.itf.json deleted file mode 100644 index 5d30f6911..000000000 --- a/Specs/Quint/0DecideNonProposerTest.itf.json +++ /dev/null @@ -1 +0,0 @@ -{"#meta":{"format":"ITF","format-description":"https://apalache.informal.systems/docs/adr/015adr-trace.html","source":"consensus.qnt","status":"passed","description":"Created by Quint on Wed Oct 25 2023 15:38:28 GMT+0200 (Central European Summer Time)","timestamp":1698241108633},"vars":["system","_Event","_Result"],"states":[{"#meta":{"index":0},"_Event":{"height":-1,"name":"Initial","round":-1,"value":"","vr":-1},"_Result":{"decided":"","name":"","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":1,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":0,"step":"newRound","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":1},"_Event":{"height":1,"name":"NewRound","round":0,"value":"","vr":-1},"_Result":{"decided":"","name":"timeout","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"timeoutPropose","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":1,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":0,"step":"propose","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":2},"_Event":{"height":1,"name":"NewRound","round":0,"value":"","vr":-1},"_Result":{"decided":"","name":"timeout","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"timeoutPropose","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":1,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":0,"step":"propose","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":3},"_Event":{"height":1,"name":"Proposal","round":0,"value":"block","vr":-1},"_Result":{"decided":"","name":"votemessage","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"","voteMessage":{"height":1,"id":"block","round":0,"src":"Josef","step":"prevote"}},"system":{"#map":[["Josef",{"height":1,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":0,"step":"prevote","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":4},"_Event":{"height":1,"name":"Proposal","round":0,"value":"block","vr":-1},"_Result":{"decided":"","name":"votemessage","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"","voteMessage":{"height":1,"id":"block","round":0,"src":"Josef","step":"prevote"}},"system":{"#map":[["Josef",{"height":1,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":0,"step":"prevote","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":5},"_Event":{"height":1,"name":"ProposalAndPolkaAndValid","round":0,"value":"block","vr":-1},"_Result":{"decided":"","name":"votemessage","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"","voteMessage":{"height":1,"id":"block","round":0,"src":"Josef","step":"precommit"}},"system":{"#map":[["Josef",{"height":1,"lockedRound":0,"lockedValue":"block","p":"Josef","round":0,"step":"precommit","validRound":0,"validValue":"block"}]]}},{"#meta":{"index":6},"_Event":{"height":1,"name":"ProposalAndPolkaAndValid","round":0,"value":"block","vr":-1},"_Result":{"decided":"","name":"votemessage","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"","voteMessage":{"height":1,"id":"block","round":0,"src":"Josef","step":"precommit"}},"system":{"#map":[["Josef",{"height":1,"lockedRound":0,"lockedValue":"block","p":"Josef","round":0,"step":"precommit","validRound":0,"validValue":"block"}]]}},{"#meta":{"index":7},"_Event":{"height":1,"name":"ProposalAndCommitAndValid","round":0,"value":"block","vr":-1},"_Result":{"decided":"block","name":"decided","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":1,"lockedRound":0,"lockedValue":"block","p":"Josef","round":0,"step":"decided","validRound":0,"validValue":"block"}]]}},{"#meta":{"index":8},"_Event":{"height":1,"name":"ProposalAndCommitAndValid","round":0,"value":"block","vr":-1},"_Result":{"decided":"block","name":"decided","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":1,"lockedRound":0,"lockedValue":"block","p":"Josef","round":0,"step":"decided","validRound":0,"validValue":"block"}]]}},{"#meta":{"index":9},"_Event":{"height":2,"name":"NewHeight","round":0,"value":"","vr":-1},"_Result":{"decided":"","name":"","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":0,"step":"newRound","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":10},"_Event":{"height":2,"name":"NewHeight","round":0,"value":"","vr":-1},"_Result":{"decided":"","name":"","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":0,"step":"newRound","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":11},"_Event":{"height":2,"name":"NewRoundProposer","round":0,"value":"nextBlock","vr":-1},"_Result":{"decided":"","name":"proposal","proposal":{"height":2,"proposal":"nextBlock","round":0,"src":"Josef","validRound":-1},"skipRound":-1,"timeout":"","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":0,"step":"propose","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":12},"_Event":{"height":2,"name":"NewRoundProposer","round":0,"value":"nextBlock","vr":-1},"_Result":{"decided":"","name":"proposal","proposal":{"height":2,"proposal":"nextBlock","round":0,"src":"Josef","validRound":-1},"skipRound":-1,"timeout":"","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":0,"step":"propose","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":13},"_Event":{"height":2,"name":"Proposal","round":0,"value":"nextBlock","vr":-1},"_Result":{"decided":"","name":"votemessage","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"","voteMessage":{"height":2,"id":"nextBlock","round":0,"src":"Josef","step":"prevote"}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":0,"step":"prevote","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":14},"_Event":{"height":2,"name":"Proposal","round":0,"value":"nextBlock","vr":-1},"_Result":{"decided":"","name":"votemessage","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"","voteMessage":{"height":2,"id":"nextBlock","round":0,"src":"Josef","step":"prevote"}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":0,"step":"prevote","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":15},"_Event":{"height":2,"name":"PolkaAny","round":0,"value":"nextBlock","vr":-1},"_Result":{"decided":"","name":"timeout","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"timeoutPrevote","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":0,"step":"prevote","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":16},"_Event":{"height":2,"name":"PolkaAny","round":0,"value":"nextBlock","vr":-1},"_Result":{"decided":"","name":"timeout","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"timeoutPrevote","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":0,"step":"prevote","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":17},"_Event":{"height":2,"name":"TimeoutPrevote","round":0,"value":"nextBlock","vr":-1},"_Result":{"decided":"","name":"votemessage","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"","voteMessage":{"height":2,"id":"nil","round":0,"src":"Josef","step":"precommit"}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":0,"step":"precommit","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":18},"_Event":{"height":2,"name":"TimeoutPrevote","round":0,"value":"nextBlock","vr":-1},"_Result":{"decided":"","name":"votemessage","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"","voteMessage":{"height":2,"id":"nil","round":0,"src":"Josef","step":"precommit"}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":0,"step":"precommit","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":19},"_Event":{"height":2,"name":"PrecommitAny","round":0,"value":"nextBlock","vr":-1},"_Result":{"decided":"","name":"timeout","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"timeoutPrecommit","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":0,"step":"precommit","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":20},"_Event":{"height":2,"name":"PrecommitAny","round":0,"value":"nextBlock","vr":-1},"_Result":{"decided":"","name":"timeout","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"timeoutPrecommit","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":0,"step":"precommit","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":21},"_Event":{"height":2,"name":"TimeoutPrecommit","round":0,"value":"nextBlock","vr":-1},"_Result":{"decided":"","name":"skipRound","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":1,"timeout":"","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":0,"step":"precommit","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":22},"_Event":{"height":2,"name":"TimeoutPrecommit","round":0,"value":"nextBlock","vr":-1},"_Result":{"decided":"","name":"skipRound","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":1,"timeout":"","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":0,"step":"precommit","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":23},"_Event":{"height":2,"name":"NewRound","round":1,"value":"","vr":-1},"_Result":{"decided":"","name":"timeout","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"timeoutPropose","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":1,"step":"propose","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":24},"_Event":{"height":2,"name":"NewRound","round":1,"value":"","vr":-1},"_Result":{"decided":"","name":"timeout","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"timeoutPropose","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":1,"step":"propose","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":25},"_Event":{"height":2,"name":"TimeoutPropose","round":1,"value":"nextBlock","vr":-1},"_Result":{"decided":"","name":"votemessage","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"","voteMessage":{"height":2,"id":"nil","round":1,"src":"Josef","step":"prevote"}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":1,"step":"prevote","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":26},"_Event":{"height":2,"name":"TimeoutPropose","round":1,"value":"nextBlock","vr":-1},"_Result":{"decided":"","name":"votemessage","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"","voteMessage":{"height":2,"id":"nil","round":1,"src":"Josef","step":"prevote"}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":1,"step":"prevote","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":27},"_Event":{"height":2,"name":"PolkaNil","round":1,"value":"nextBlock","vr":-1},"_Result":{"decided":"","name":"votemessage","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"","voteMessage":{"height":2,"id":"nil","round":1,"src":"Josef","step":"precommit"}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":1,"step":"precommit","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":28},"_Event":{"height":2,"name":"PolkaNil","round":1,"value":"nextBlock","vr":-1},"_Result":{"decided":"","name":"votemessage","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"","voteMessage":{"height":2,"id":"nil","round":1,"src":"Josef","step":"precommit"}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":1,"step":"precommit","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":29},"_Event":{"height":2,"name":"PrecommitAny","round":1,"value":"nextBlock","vr":-1},"_Result":{"decided":"","name":"timeout","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"timeoutPrecommit","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":1,"step":"precommit","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":30},"_Event":{"height":2,"name":"PrecommitAny","round":1,"value":"nextBlock","vr":-1},"_Result":{"decided":"","name":"timeout","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"timeoutPrecommit","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":1,"step":"precommit","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":31},"_Event":{"height":2,"name":"TimeoutPrecommit","round":1,"value":"nextBlock","vr":-1},"_Result":{"decided":"","name":"skipRound","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":2,"timeout":"","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":1,"step":"precommit","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":32},"_Event":{"height":2,"name":"TimeoutPrecommit","round":1,"value":"nextBlock","vr":-1},"_Result":{"decided":"","name":"skipRound","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":2,"timeout":"","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":1,"step":"precommit","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":33},"_Event":{"height":2,"name":"NewRound","round":2,"value":"","vr":-1},"_Result":{"decided":"","name":"timeout","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"timeoutPropose","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":2,"step":"propose","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":34},"_Event":{"height":2,"name":"NewRound","round":2,"value":"","vr":-1},"_Result":{"decided":"","name":"timeout","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"timeoutPropose","voteMessage":{"height":-1,"id":"","round":-1,"src":"","step":""}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":2,"step":"propose","validRound":-1,"validValue":"nil"}]]}},{"#meta":{"index":35},"_Event":{"height":2,"name":"ProposalInvalid","round":2,"value":"","vr":-1},"_Result":{"decided":"","name":"votemessage","proposal":{"height":-1,"proposal":"","round":-1,"src":"","validRound":-1},"skipRound":-1,"timeout":"","voteMessage":{"height":2,"id":"nil","round":2,"src":"Josef","step":"prevote"}},"system":{"#map":[["Josef",{"height":2,"lockedRound":-1,"lockedValue":"nil","p":"Josef","round":2,"step":"prevote","validRound":-1,"validValue":"nil"}]]}}]} \ No newline at end of file diff --git a/Specs/Quint/Makefile b/Specs/Quint/Makefile index 2b9227986..a7477d691 100644 --- a/Specs/Quint/Makefile +++ b/Specs/Quint/Makefile @@ -1,7 +1,7 @@ parse: npx @informalsystems/quint parse asyncModelsTest.qnt npx @informalsystems/quint parse consensusTest.qnt - npx @informalsystems/quint parse executor.qnt + npx @informalsystems/quint parse driver.qnt npx @informalsystems/quint parse statemachineTest.qnt npx @informalsystems/quint parse voteBookkeeperTest.qnt .PHONY: parse diff --git a/Specs/Quint/consensus.qnt b/Specs/Quint/consensus.qnt index 3e282a873..e7cf66ece 100644 --- a/Specs/Quint/consensus.qnt +++ b/Specs/Quint/consensus.qnt @@ -3,14 +3,14 @@ /* TODO: check - whether we have "step" checks in place everywhere -- "the first time": checks here or in executor +- "the first time": checks here or in driver - check against arXiv - tests - types (e.g., heights in the messages) -- discuss "decision tree" in executor +- discuss "decision tree" in driver - Should we think again about the components and the boundaries (especially between - voteBookkeeper and executor) -- Do we need tests for executor and bookkeeping + voteBookkeeper and driver) +- Do we need tests for driver and bookkeeping - test id(v): most likely we need to change the type of Value_t as Quint doesn't have string operators. Perhaps we make Value_t = int and then id(v) = -v */ @@ -32,7 +32,7 @@ module consensus { type Timeout_t = str // the type of propose messages -type ProposeMsg_t = { +type Proposal_t = { src: Address_t, height: Height_t, round: Round_t, @@ -41,7 +41,7 @@ type ProposeMsg_t = { } // the type of Prevote and Precommit messages -type VoteMsg_t = { +type Vote_t = { src: Address_t, height: Height_t, round: Round_t, @@ -51,7 +51,7 @@ type VoteMsg_t = { type ConsensusState = { p: Address_t, - height : Height_t, + height: Height_t, round: Round_t, step: Step_t, // "newRound", propose, Prevote, Precommit, decided lockedRound: Round_t, @@ -65,15 +65,15 @@ pure def initConsensusState (v: Address_t) : ConsensusState = { p: v, round: -1, step: "newRound", - height : 0, + height: 0, lockedRound: -1, lockedValue: "nil", validRound: -1, validValue: "nil" } -type Event = { - name : str, +type ConsensusInput = { + name: str, height : Height_t, round: Round_t, value: Value_t, @@ -82,9 +82,9 @@ type Event = { // what is a good way to encode optionals? I do with default values type ConsensusOutput = { - name : str, - proposal: ProposeMsg_t, - voteMessage: VoteMsg_t, + name: str, + proposal: Proposal_t, + voteMessage: Vote_t, timeout: Timeout_t, decided: Value_t, skipRound: Round_t @@ -104,9 +104,7 @@ type ConsResult = { // pending: Set[ConsensusOutput], // TODO: not sure we need this } -type ConsensusEvent = str - -val ConsensusEvents = Set( +val ConsensusInputNames = Set( "NewHeight", // Setups the state-machine for a single-height execution "NewRound", // Start a new round, not as proposer. "NewRoundProposer", // Start a new round as proposer with the proposed Value. @@ -131,12 +129,12 @@ val ConsensusEvents = Set( "PrecommitValue(ValueId)", // Receive +2/3 Precommits for Value. */ -val noProp : ProposeMsg_t = {src: "", height: -1, round: -1, proposal: "", validRound: -1} -val noVote : VoteMsg_t = {src: "", height: -1, round: -1, step: "", id: ""} +val noProp : Proposal_t = {src: "", height: -1, round: -1, proposal: "", validRound: -1} +val noVote : Vote_t = {src: "", height: -1, round: -1, step: "", id: ""} val noTimeout : Timeout_t = "" val noDecided = "" val noSkipRound : Round_t = -1 -val defaultResult : ConsensusOutput = { +val defaultOutput : ConsensusOutput = { name: "", proposal: noProp, voteMessage: noVote, @@ -146,73 +144,73 @@ val defaultResult : ConsensusOutput = { -pure def NewHeight (state: ConsensusState, ev: Event) : ConsResult = { - if (ev.height > state.height) +pure def NewHeight (state: ConsensusState, input: ConsensusInput) : ConsResult = { + if (input.height > state.height) val newstate = { p: state.p, round: -1, // must be -1, as nothing should be done before a NewRound // function is called step: "newRound", - height : ev.height, + height : input.height, lockedRound: -1, lockedValue: "nil", validRound: -1, validValue: "nil" } - {cs: newstate, out: defaultResult } + {cs: newstate, out: defaultOutput } else - {cs: state, out: defaultResult } + {cs: state, out: defaultOutput } } // line 11.14 -pure def NewRoundProposer (state: ConsensusState, ev: Event) : ConsResult = { - if (ev.round > state.round) - val newstate = { ...state, round: ev.round, step: "propose"} +pure def NewRoundProposer (state: ConsensusState, input: ConsensusInput) : ConsResult = { + if (input.round > state.round) + val newstate = { ...state, round: input.round, step: "propose"} val proposal = if (state.validValue != "nil") state.validValue - else ev.value - val result = { ...defaultResult, name: "proposal", + else input.value + val result = { ...defaultOutput, name: "proposal", proposal: { src: state.p, height: state.height, - round: ev.round, + round: input.round, proposal: proposal, validRound: state.validRound}} {cs: newstate, out: result } else - {cs: state, out: defaultResult } + {cs: state, out: defaultOutput } } // line 11.20 -pure def NewRound (state: ConsensusState, ev: Event) : ConsResult = { - // TODO: discuss comment "ev.round must match state.round" - if (ev.round > state.round) - val newstate = { ...state, round: ev.round, step: "propose" } - val result = { ...defaultResult, name: "timeout", timeout: "TimeoutPropose"} - // We just report that a timeout should be started. The executor must take care +pure def NewRound (state: ConsensusState, input: ConsensusInput) : ConsResult = { + // TODO: discuss comment "input.round must match state.round" + if (input.round > state.round) + val newstate = { ...state, round: input.round, step: "propose" } + val result = { ...defaultOutput, name: "timeout", timeout: "TimeoutPropose"} + // We just report that a timeout should be started. The driver must take care // of figuring out whether it needs to record the round number and height per // timeout {cs: newstate, out: result} else - {cs: state, out: defaultResult } + {cs: state, out: defaultOutput } } // line 22 // Here it is assumed that // - the value has been checked to be valid // - it is for the current round -// The executor checks this upon receiving a propose message "ProposalMsg" -pure def Proposal (state: ConsensusState, ev: Event) : ConsResult = { +// The driver checks this upon receiving a propose message "ProposalMsg" +pure def Proposal (state: ConsensusState, input: ConsensusInput) : ConsResult = { if (state.step == "propose") val newstate = { ...state, step: "Prevote" } - if (state.lockedRound == -1 or state.lockedValue == ev.value) - val result = { ...defaultResult, name: "votemessage", + if (state.lockedRound == -1 or state.lockedValue == input.value) + val result = { ...defaultOutput, name: "votemessage", voteMessage: { src: state.p, height: state.height, round: state.round, step: "Prevote", - id: ev.value}} + id: input.value}} {cs: newstate, out: result} else - val result = { ...defaultResult, name: "votemessage", + val result = { ...defaultOutput, name: "votemessage", voteMessage: { src: state.p, height: state.height, round: state.round, @@ -220,15 +218,15 @@ pure def Proposal (state: ConsensusState, ev: Event) : ConsResult = { id: "nil"}} {cs: newstate, out: result} else - {cs: state, out: defaultResult} - // This should be dead code as the executor checks the step + {cs: state, out: defaultOutput} + // This should be dead code as the driver checks the step } // line 26 -pure def ProposalInvalid (state: ConsensusState, ev: Event) : ConsResult = { +pure def ProposalInvalid (state: ConsensusState, input: ConsensusInput) : ConsResult = { if (state.step == "propose") val newstate = state.with("step", "Prevote") - val result = { ...defaultResult, name: "votemessage", + val result = { ...defaultOutput, name: "votemessage", voteMessage: { src: state.p, height: state.height, round: state.round, @@ -236,23 +234,23 @@ pure def ProposalInvalid (state: ConsensusState, ev: Event) : ConsResult = { id: "nil"}} {cs: newstate, out: result} else - {cs: state, out: defaultResult} + {cs: state, out: defaultOutput} } // line 28 -pure def ProposalAndPolkaPreviousAndValid (state: ConsensusState, ev: Event) : ConsResult = { - if (state.step == "propose" and ev.vr >= 0 and ev.vr < state.round) +pure def ProposalAndPolkaPreviousAndValid (state: ConsensusState, input: ConsensusInput) : ConsResult = { + if (state.step == "propose" and input.vr >= 0 and input.vr < state.round) val newstate = state.with("step", "Prevote") - if (state.lockedRound <= ev.vr or state.lockedValue == ev.value) - val result = { ...defaultResult, name: "votemessage", + if (state.lockedRound <= input.vr or state.lockedValue == input.value) + val result = { ...defaultOutput, name: "votemessage", voteMessage: { src: state.p, height: state.height, round: state.round, step: "Prevote", - id: ev.value}} + id: input.value}} {cs: newstate, out: result} else - val result = { ...defaultResult, name: "votemessage", + val result = { ...defaultOutput, name: "votemessage", voteMessage: { src: state.p, height: state.height, round: state.round, @@ -260,53 +258,53 @@ pure def ProposalAndPolkaPreviousAndValid (state: ConsensusState, ev: Event) : C id: "nil"}} {cs: newstate, out: result} else - {cs: state, out: defaultResult} + {cs: state, out: defaultOutput} // TODO: should we add the event to pending in this case. We would need to - // do this in the executor + // do this in the driver } // line 34 -pure def PolkaAny (state: ConsensusState, ev: Event) : ConsResult = { +pure def PolkaAny (state: ConsensusState, input: ConsensusInput) : ConsResult = { if (state.step == "Prevote") - val result = { ...defaultResult, name: "timeout", timeout: "TimeoutPrevote" } - // We just report that a timeout should be started. The executor must take care + val result = { ...defaultOutput, name: "timeout", timeout: "TimeoutPrevote" } + // We just report that a timeout should be started. The driver must take care // of figuring out whether it needs to record the round number and height per // timeout {cs: state, out: result} else - {cs: state, out: defaultResult} + {cs: state, out: defaultOutput} } // line 36 -pure def ProposalAndPolkaAndValid (state: ConsensusState, ev: Event) : ConsResult = { - val auxState = { ...state, validValue: ev.value, validRound: state.round } +pure def ProposalAndPolkaAndValid (state: ConsensusState, input: ConsensusInput) : ConsResult = { + val auxState = { ...state, validValue: input.value, validRound: state.round } if (state.step == "Prevote") - val newstate = { ...auxState, lockedValue: ev.value, + val newstate = { ...auxState, lockedValue: input.value, lockedRound: state.round, step: "Precommit" } - val result = { ...defaultResult, name: "votemessage", + val result = { ...defaultOutput, name: "votemessage", voteMessage: { src: state.p, height: state.height, round: state.round, step: "Precommit", - id: ev.value}} + id: input.value}} {cs: newstate, out: result} else if (state.step == "Precommit") // TODO: check whether Daniel's comment // "if state > prevote, we should update the valid round!" // was properly addressed - {cs: auxState, out: defaultResult} + {cs: auxState, out: defaultOutput} else - {cs: state, out: defaultResult} + {cs: state, out: defaultOutput} // TODO: should we add the event to pending in this case. We would need to - // do this in the executor + // do this in the driver } // line 44 -pure def PolkaNil (state: ConsensusState, ev: Event) : ConsResult = { +pure def PolkaNil (state: ConsensusState, input: ConsensusInput) : ConsResult = { if (state.step == "Prevote") val newstate = { ...state, step: "Precommit"} - val result = { ...defaultResult, name: "votemessage", + val result = { ...defaultOutput, name: "votemessage", voteMessage: { src: state.p, height: state.height, round: state.round, @@ -314,39 +312,39 @@ pure def PolkaNil (state: ConsensusState, ev: Event) : ConsResult = { id: "nil"}} {cs: newstate, out: result} else - {cs: state, out: defaultResult} + {cs: state, out: defaultOutput} } // line 47 -pure def PrecommitAny (state: ConsensusState, ev: Event) : ConsResult = { - val result = { ...defaultResult, name: "timeout", timeout: "TimeoutPrecommit" } +pure def PrecommitAny (state: ConsensusState, input: ConsensusInput) : ConsResult = { + val result = { ...defaultOutput, name: "timeout", timeout: "TimeoutPrecommit" } {cs: state, out: result} } // line 49 -pure def ProposalAndCommitAndValid (state: ConsensusState, ev: Event) : ConsResult = { +pure def ProposalAndCommitAndValid (state: ConsensusState, input: ConsensusInput) : ConsResult = { if (state.step != "decided") { val newstate = { ...state, step: "decided"} - val result = { ...defaultResult, name: "decided", decided: ev.value} + val result = { ...defaultOutput, name: "decided", decided: input.value} {cs: newstate, out: result} } else - {cs: state, out: defaultResult} + {cs: state, out: defaultOutput} } // line 55 -pure def RoundSkip (state: ConsensusState, ev: Event) : ConsResult = { - if (ev.round > state.round) - val result = { ...defaultResult, name: "skipRound", skipRound: ev.round } +pure def RoundSkip (state: ConsensusState, input: ConsensusInput) : ConsResult = { + if (input.round > state.round) + val result = { ...defaultOutput, name: "skipRound", skipRound: input.round } {cs: state, out: result} else - {cs: state, out: defaultResult} + {cs: state, out: defaultOutput} } -pure def TimeoutPropose (state: ConsensusState, ev: Event) : ConsResult = { - if (ev.height == state.height and ev.round == state.round and state.step == "propose") +pure def TimeoutPropose (state: ConsensusState, input: ConsensusInput) : ConsResult = { + if (input.height == state.height and input.round == state.round and state.step == "propose") val newstate = { ...state, step: "Prevote"} - val result = { ...defaultResult, name: "votemessage", + val result = { ...defaultOutput, name: "votemessage", voteMessage: { src: state.p, height: state.height, round: state.round, @@ -354,14 +352,14 @@ pure def TimeoutPropose (state: ConsensusState, ev: Event) : ConsResult = { id: "nil"}} {cs: newstate, out: result} else - {cs: state, out: defaultResult} + {cs: state, out: defaultOutput} } -pure def TimeoutPrevote (state: ConsensusState, ev: Event) : ConsResult = { - if (ev.height == state.height and ev.round == state.round and state.step == "Prevote") +pure def TimeoutPrevote (state: ConsensusState, input: ConsensusInput) : ConsResult = { + if (input.height == state.height and input.round == state.round and state.step == "Prevote") val newstate = { ...state, step: "Precommit"} // TODO: should we send precommit nil again ? - val result = { ...defaultResult, name: "votemessage", + val result = { ...defaultOutput, name: "votemessage", voteMessage: { src: state.p, height: state.height, round: state.round, @@ -369,17 +367,17 @@ pure def TimeoutPrevote (state: ConsensusState, ev: Event) : ConsResult = { id: "nil"}} {cs: newstate, out: result} else - {cs: state, out: defaultResult} + {cs: state, out: defaultOutput} } -pure def TimeoutPrecommit (state: ConsensusState, ev: Event) : ConsResult = { - if (ev.height == state.height and ev.round == state.round) +pure def TimeoutPrecommit (state: ConsensusState, input: ConsensusInput) : ConsResult = { + if (input.height == state.height and input.round == state.round) // TODO: here we should call newRound. For this we would need to know whether // we are proposer for next round. - val result = {...defaultResult, name: "skipRound", skipRound: state.round + 1} + val result = {...defaultOutput, name: "skipRound", skipRound: state.round + 1} {cs: state, out: result} else - {cs: state, out: defaultResult} + {cs: state, out: defaultOutput} } @@ -387,39 +385,39 @@ pure def TimeoutPrecommit (state: ConsensusState, ev: Event) : ConsResult = { * Main entry point * ********************************************************/ -pure def consensus (state: ConsensusState, ev: Event) : ConsResult = { - if (ev.name == "NewHeight") - NewHeight (state, ev) - else if (ev.name == "NewRoundProposer") - NewRoundProposer(state, ev) - else if (ev.name == "NewRound") - NewRound(state, ev) - else if (ev.name == "Proposal") - Proposal(state, ev) - else if (ev.name == "ProposalAndPolkaPreviousAndValid") - ProposalAndPolkaPreviousAndValid(state, ev) - else if (ev.name == "ProposalInvalid") - ProposalInvalid(state, ev) - else if (ev.name == "PolkaAny") - PolkaAny(state, ev) - else if (ev.name == "ProposalAndPolkaAndValid") - ProposalAndPolkaAndValid(state, ev) - else if (ev.name == "PolkaNil") - PolkaNil(state, ev) - else if (ev.name == "PrecommitAny") - PrecommitAny(state, ev) - else if (ev.name == "ProposalAndCommitAndValid") - ProposalAndCommitAndValid(state, ev) - else if (ev.name == "RoundSkip") - RoundSkip (state, ev) - else if (ev.name == "TimeoutPropose") - TimeoutPropose (state, ev) - else if (ev.name == "TimeoutPrevote") - TimeoutPrevote (state, ev) - else if (ev.name == "TimeoutPrecommit") - TimeoutPrecommit (state, ev) +pure def consensus (state: ConsensusState, input: ConsensusInput) : ConsResult = { + if (input.name == "NewHeight") + NewHeight (state, input) + else if (input.name == "NewRoundProposer") + NewRoundProposer(state, input) + else if (input.name == "NewRound") + NewRound(state, input) + else if (input.name == "Proposal") + Proposal(state, input) + else if (input.name == "ProposalAndPolkaPreviousAndValid") + ProposalAndPolkaPreviousAndValid(state, input) + else if (input.name == "ProposalInvalid") + ProposalInvalid(state, input) + else if (input.name == "PolkaAny") + PolkaAny(state, input) + else if (input.name == "ProposalAndPolkaAndValid") + ProposalAndPolkaAndValid(state, input) + else if (input.name == "PolkaNil") + PolkaNil(state, input) + else if (input.name == "PrecommitAny") + PrecommitAny(state, input) + else if (input.name == "ProposalAndCommitAndValid") + ProposalAndCommitAndValid(state, input) + else if (input.name == "RoundSkip") + RoundSkip (state, input) + else if (input.name == "TimeoutPropose") + TimeoutPropose (state, input) + else if (input.name == "TimeoutPrevote") + TimeoutPrevote (state, input) + else if (input.name == "TimeoutPrecommit") + TimeoutPrecommit (state, input) else - {cs: state, out: defaultResult} + {cs: state, out: defaultOutput} } } diff --git a/Specs/Quint/consensusTest.qnt b/Specs/Quint/consensusTest.qnt index fb7a2df24..2f2a79d45 100644 --- a/Specs/Quint/consensusTest.qnt +++ b/Specs/Quint/consensusTest.qnt @@ -8,9 +8,9 @@ import consensus.* from "./consensus" * Global state * ************************************************************************* */ -var system : Address_t -> ConsensusState -var _Result : ConsensusOutput -var _Event : Event +var system: Address_t -> ConsensusState +var _output: ConsensusOutput +var _input: ConsensusInput pure def initialProcess (name: Address_t) : ConsensusState = { @@ -19,8 +19,8 @@ pure def initialProcess (name: Address_t) : ConsensusState = { action init = all { system' = Map ("Josef" -> initialProcess("Josef")), - _Result' = defaultResult, - _Event' = { name : "Initial", + _output' = defaultOutput, + _input' = { name : "Initial", height : 0, round: -1, value: "", @@ -30,7 +30,7 @@ action init = all { // just to write a test. -action FireEvent(eventName: str, proc: Address_t, h: Height_t, r: Round_t, value: Value_t, vr: Round_t) : bool = all { +action FireInput(eventName: str, proc: Address_t, h: Height_t, r: Round_t, value: Value_t, vr: Round_t) : bool = all { val event = { name : eventName, height : h, round: r, @@ -39,86 +39,86 @@ action FireEvent(eventName: str, proc: Address_t, h: Height_t, r: Round_t, value val res = consensus(system.get(proc), event ) all { system' = system.put(proc, res.cs), - _Result' = res.out, - _Event' = event + _output' = res.out, + _input' = event } } action step = any { - nondet name = oneOf(ConsensusEvents) + nondet name = oneOf(ConsensusInputNames) nondet height = 1//oneOf(1.to(4)) nondet round = 0//oneOf(1.to(4)) nondet value = oneOf(Set("block 1", "block 2", "block 3")) nondet vr = oneOf(Set(-1, 1, 2, 3, 4)) - FireEvent(name, "Josef", height, round, value, vr) + FireInput(name, "Josef", height, round, value, vr) } action unchangedAll = all { system' = system, - _Result' = _Result, - _Event' = _Event, + _output' = _output, + _input' = _input, } // This test should call each event at least once run DecideNonProposerTest = { init - .then(FireEvent("NewRound", "Josef", 1, 0, "", -1)) + .then(FireInput("NewRound", "Josef", 1, 0, "", -1)) .then(all{ - assert(_Result.timeout == "TimeoutPropose"), - FireEvent("Proposal", "Josef", 1, 0, "block", -1)}) + assert(_output.timeout == "TimeoutPropose"), + FireInput("Proposal", "Josef", 1, 0, "block", -1)}) .then(all{ - assert(_Result.voteMessage.step == "Prevote" and _Result.voteMessage.id == "block"), - FireEvent("ProposalAndPolkaAndValid", "Josef", 1, 0, "block", -1)}) + assert(_output.voteMessage.step == "Prevote" and _output.voteMessage.id == "block"), + FireInput("ProposalAndPolkaAndValid", "Josef", 1, 0, "block", -1)}) .then(all{ - assert(_Result.voteMessage.step == "Precommit" and _Result.voteMessage.id == "block"), - FireEvent("ProposalAndCommitAndValid", "Josef", 1, 0, "block", -1)}) + assert(_output.voteMessage.step == "Precommit" and _output.voteMessage.id == "block"), + FireInput("ProposalAndCommitAndValid", "Josef", 1, 0, "block", -1)}) .then(all{ - assert(_Result.decided == "block"), - FireEvent("NewHeight", "Josef", system.get("Josef").height + 1, 0, "", -1)}) + assert(_output.decided == "block"), + FireInput("NewHeight", "Josef", system.get("Josef").height + 1, 0, "", -1)}) .then(all{ assert(system.get("Josef").height == 2), - FireEvent("NewRoundProposer", "Josef", 2, 0, "nextBlock", -1)}) + FireInput("NewRoundProposer", "Josef", 2, 0, "nextBlock", -1)}) .then(all{ - assert(_Result.timeout != "TimeoutPropose" and _Result.proposal.proposal == "nextBlock"), - FireEvent("Proposal", "Josef", 2, 0, "nextBlock", -1)}) // it is assumed that the proposer receives its own message + assert(_output.timeout != "TimeoutPropose" and _output.proposal.proposal == "nextBlock"), + FireInput("Proposal", "Josef", 2, 0, "nextBlock", -1)}) // it is assumed that the proposer receives its own message .then(all{ - assert(_Result.voteMessage.step == "Prevote" and system.get("Josef").step == "Prevote"), - FireEvent("PolkaAny", "Josef", 2, 0, "nextBlock", -1)}) + assert(_output.voteMessage.step == "Prevote" and system.get("Josef").step == "Prevote"), + FireInput("PolkaAny", "Josef", 2, 0, "nextBlock", -1)}) .then(all{ - assert(_Result.timeout == "TimeoutPrevote"), - FireEvent("TimeoutPrevote", "Josef", 2, 0, "nextBlock", -1)}) + assert(_output.timeout == "TimeoutPrevote"), + FireInput("TimeoutPrevote", "Josef", 2, 0, "nextBlock", -1)}) .then(all{ - assert(_Result.voteMessage.step == "Precommit" and _Result.voteMessage.id == "nil" and + assert(_output.voteMessage.step == "Precommit" and _output.voteMessage.id == "nil" and system.get("Josef").step == "Precommit"), - FireEvent("PrecommitAny", "Josef", 2, 0, "nextBlock", -1)}) + FireInput("PrecommitAny", "Josef", 2, 0, "nextBlock", -1)}) .then(all{ - assert(_Result.timeout == "TimeoutPrecommit"), - FireEvent("TimeoutPrecommit", "Josef", 2, 0, "nextBlock", -1)}) + assert(_output.timeout == "TimeoutPrecommit"), + FireInput("TimeoutPrecommit", "Josef", 2, 0, "nextBlock", -1)}) .then(all{ - assert(_Result.skipRound == 1), - FireEvent("NewRound", "Josef", 2, 1, "", -1)}) + assert(_output.skipRound == 1), + FireInput("NewRound", "Josef", 2, 1, "", -1)}) .then(all{ - assert(_Result.timeout == "TimeoutPropose"), - FireEvent("TimeoutPropose", "Josef", 2, 1, "nextBlock", -1)}) + assert(_output.timeout == "TimeoutPropose"), + FireInput("TimeoutPropose", "Josef", 2, 1, "nextBlock", -1)}) .then(all{ - assert(_Result.voteMessage.step == "Prevote" and _Result.voteMessage.id == "nil" and + assert(_output.voteMessage.step == "Prevote" and _output.voteMessage.id == "nil" and system.get("Josef").step == "Prevote"), - FireEvent("PolkaNil", "Josef", 2, 1, "nextBlock", -1)}) + FireInput("PolkaNil", "Josef", 2, 1, "nextBlock", -1)}) .then(all{ - assert(_Result.voteMessage.step == "Precommit" and _Result.voteMessage.id == "nil" and + assert(_output.voteMessage.step == "Precommit" and _output.voteMessage.id == "nil" and system.get("Josef").step == "Precommit"), - FireEvent("PrecommitAny", "Josef", 2, 1, "nextBlock", -1)}) + FireInput("PrecommitAny", "Josef", 2, 1, "nextBlock", -1)}) .then(all{ - assert(_Result.timeout == "TimeoutPrecommit"), - FireEvent("TimeoutPrecommit", "Josef", 2, 1, "nextBlock", -1)}) + assert(_output.timeout == "TimeoutPrecommit"), + FireInput("TimeoutPrecommit", "Josef", 2, 1, "nextBlock", -1)}) .then(all{ - assert(_Result.skipRound == 2), - FireEvent("NewRound", "Josef", 2, 2, "", -1)}) + assert(_output.skipRound == 2), + FireInput("NewRound", "Josef", 2, 2, "", -1)}) .then(all{ - assert(_Result.timeout == "TimeoutPropose"), - FireEvent("ProposalInvalid", "Josef", 2, 2, "", -1)}) + assert(_output.timeout == "TimeoutPropose"), + FireInput("ProposalInvalid", "Josef", 2, 2, "", -1)}) .then(all{ - assert(_Result.voteMessage.step == "Prevote" and _Result.voteMessage.id == "nil" and + assert(_output.voteMessage.step == "Prevote" and _output.voteMessage.id == "nil" and system.get("Josef").step == "Prevote"), unchangedAll }) diff --git a/Specs/Quint/executor.qnt b/Specs/Quint/driver.qnt similarity index 78% rename from Specs/Quint/executor.qnt rename to Specs/Quint/driver.qnt index 6aeb71a6a..c5326d9db 100644 --- a/Specs/Quint/executor.qnt +++ b/Specs/Quint/driver.qnt @@ -1,11 +1,11 @@ // -*- mode: Bluespec; -*- /* - TODO: round switch upon f+1 messages from the future is not done yet. We need to catch - the event from the bookkeeper + TODO: round switch upon f+1 messages from the future is not done yet. + We need to catch the event from the bookkeeper */ -module executor { +module driver { import consensus.* from "./consensus" import voteBookkeeper.* from "./voteBookkeeper" @@ -14,73 +14,73 @@ pure def initBookKeeper(totalVotingPower: int): Bookkeeper = { height: 0, totalWeight: totalVotingPower, rounds: Map() } -type ExecutorState = { +type DriverState = { bk : Bookkeeper, cs : ConsensusState, - proposals: Set[ProposeMsg_t], + proposals: Set[Proposal_t], valset: Address_t -> int, - executedEvents: List[(Event, Height_t, Round_t)], // We record that to have the information in the trace - pendingEvents: Set[(Event, Height_t, Round_t)], + executedInputs: List[(ConsensusInput, Height_t, Round_t)], // We record that to have the information in the trace + pendingInputs: Set[(ConsensusInput, Height_t, Round_t)], started: bool, - applyvotesResult: ExecutorEvent, //debug TODO + voteKeeperOutput: VoteKeeperOutput, //debug TODO chain : List[Value_t], nextValueToPropose: Value_t, } -pure def initExecutor (v: Address_t, vs: Address_t -> int) : ExecutorState = { +pure def initDriver (v: Address_t, vs: Address_t -> int) : DriverState = { val tvp = vs.keys().fold(0, (sum, key) => sum + vs.get(key)) { bk: initBookKeeper(tvp), cs: initConsensusState(v), proposals: Set(), valset: vs, - executedEvents: List(), - pendingEvents: Set(), + executedInputs: List(), + pendingInputs: Set(), started: false, - applyvotesResult: toEvent(0, "", {name: "", value: ""}), // debug + voteKeeperOutput: toVoteKeeperOutput(0, "", {name: "", value: ""}), // debug chain : List(), nextValueToPropose: "", } } type NodeState = { - es: ExecutorState, + es: DriverState, timeout: Set[(Timeout_t, Height_t, Round_t)], - incomingVotes: Set[VoteMsg_t], - incomingProposals: Set[ProposeMsg_t], + incomingVotes: Set[Vote_t], + incomingProposals: Set[Proposal_t], } pure def initNode (v: Address_t, vs: Address_t -> int) : NodeState = { - es: initExecutor(v,vs), + es: initDriver(v,vs), timeout: Set(), incomingVotes: Set(), incomingProposals: Set(), } -type ExecutorInput = { +type DriverInput = { name: str, // TODO: make a set of values. - proposal: ProposeMsg_t, - vote: VoteMsg_t, + proposal: Proposal_t, + vote: Vote_t, timeout: str, - event: Event, + csInput: ConsensusInput, nextValueToPropose: Value_t } -val defaultInput: ExecutorInput = { +val defaultInput: DriverInput = { name: "", proposal: { src: "", height: 0, round: 0, proposal: "", validRound: 0 }, vote: { src: "", height: 0, round: 0, step: "", id: "", }, timeout: "", - event: defaultEvent, + csInput: defaultConsensusInput, nextValueToPropose: "" } // this is to match the type from the bookkeeper. When we add heights there we should // unify -pure def toVote (v: VoteMsg_t) : Vote = +pure def toVote (v: Vote_t) : Vote = { typ: v.step, height: v.height, round: v.round, value: v.id, address: v.src } -val defaultEvent : Event = { name : "", height : 0, round: 0, value: "", vr: 0 } +val defaultConsensusInput : ConsensusInput = { name : "", height : 0, round: 0, value: "", vr: 0 } /* encode the following decision tree @@ -104,7 +104,7 @@ Prevote -> feed all events to Consensus */ -val emptyProposal : ProposeMsg_t= +val emptyProposal : Proposal_t= { src: "none", height: 0, round: 0, proposal: "none", validRound: 0 } val emptyVote = @@ -125,9 +125,9 @@ pure def Proposer(valset: Address_t -> int, height: Height_t, round: Round_t) : else "v4" } -pure def getValue(es: ExecutorState) : Value_t = es.nextValueToPropose +pure def getValue(es: DriverState) : Value_t = es.nextValueToPropose -pure def valid(p: ProposeMsg_t) :bool = { +pure def valid(p: Proposal_t) :bool = { // for simulation, if the value is "invalid", so is the proposal // if this is to become "non-deterministic", we must push it // and its use into the state machine @@ -139,8 +139,8 @@ pure def id(v) = v type ConsensusCall = { - es: ExecutorState, - event: Event, + es: DriverState, + csInput: ConsensusInput, out: ConsensusOutput } @@ -148,24 +148,24 @@ pure def ListContains(list, value) = list.foldl(false, (s,x) => s or x == 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) = { +pure def callConsensus (es: DriverState, bk: Bookkeeper, csInput: ConsensusInput) : (DriverState, ConsensusOutput) = { // 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) + if (es.executedInputs.ListContains((csInput, es.cs.height, es.cs.round))) + ({ ...es, bk: bk, cs: es.cs }, defaultOutput) else // Go to consensus - val res = consensus(es.cs, ev) + val res = consensus(es.cs, csInput) // Record that we executed the event - val events = es.executedEvents.append((ev, res.cs.height, res.cs.round)) + val csInputs = es.executedInputs.append((csInput, res.cs.height, res.cs.round)) - ({ ...es, bk: bk, cs: res.cs, executedEvents: events }, res.out) + ({ ...es, bk: bk, cs: res.cs, executedInputs: csInputs }, res.out) } -// We do this if the executor receives a Precommit -pure def Precommit (es: ExecutorState, input: ExecutorInput, eev: ExecutorEvent) : (ExecutorState, ConsensusOutput) = { - if (eev.name == "PrecommitValue") - if (es.proposals.exists(x => eev.round == x.round and eev.value == id(x.proposal))) { +// We do this if the driver receives a Precommit +pure def Precommit (es: DriverState, input: DriverInput, vkOutput: VoteKeeperOutput) : (DriverState, ConsensusOutput) = { + if (vkOutput.name == "PrecommitValue") + if (es.proposals.exists(x => vkOutput.round == x.round and vkOutput.value == id(x.proposal))) { callConsensus(es, es.bk, { name : "ProposalAndCommitAndValid", height : input.vote.height, round: input.vote.round, @@ -173,14 +173,14 @@ pure def Precommit (es: ExecutorState, input: ExecutorInput, eev: ExecutorEvent) vr: -1}) } else { - if (eev.round == es.cs.round) { + if (vkOutput.round == es.cs.round) { callConsensus(es, es.bk, { name: "PrecommitAny", height: input.vote.height, round: input.vote.round, value: input.vote.id, vr: -1}) } - else if (eev.round > es.cs.round) { + else if (vkOutput.round > es.cs.round) { // if it is for a future round I can trigger skipround // TODO: should we really do this. It is dead code as the f+1 event already happened callConsensus(es, es.bk, { name: "RoundSkip", @@ -191,17 +191,17 @@ pure def Precommit (es: ExecutorState, input: ExecutorInput, eev: ExecutorEvent) } else { // messages from past round -> ignore - (es, defaultResult) + (es, defaultOutput) } } - else if (eev.name == "PrecommitAny" and eev.round == es.cs.round) { + else if (vkOutput.name == "PrecommitAny" and vkOutput.round == es.cs.round) { callConsensus(es, es.bk, { name: "PrecommitAny", height: input.vote.height, round: input.vote.round, value: input.vote.id, vr: -1}) } - else if (eev.name == "Skip" and eev.round > es.cs.round) + else if (vkOutput.name == "Skip" and vkOutput.round > es.cs.round) callConsensus(es, es.bk, { name: "RoundSkip", height: input.vote.height, round: input.vote.round, @@ -209,84 +209,84 @@ pure def Precommit (es: ExecutorState, input: ExecutorInput, eev: ExecutorEvent) vr: -1}) else // none of the supported Precommit events. Do nothing - (es, defaultResult) + (es, defaultOutput) } -// We do this if the executor receives a Prevote -pure def Prevote (es: ExecutorState, input: ExecutorInput, eev: ExecutorEvent) : (ExecutorState, ConsensusOutput) = { +// We do this if the driver receives a Prevote +pure def Prevote (es: DriverState, input: DriverInput, vkOutput: VoteKeeperOutput) : (DriverState, ConsensusOutput) = { // TODO: events do not have heights now. // TODO: Polka implications missing. - if (eev.name == "PolkaValue") - if (eev.round < es.cs.round and + if (vkOutput.name == "PolkaValue") + if (vkOutput.round < es.cs.round and es.proposals.exists(p => p.round == es.cs.round and - eev.round == p.validRound and id(p.proposal) == eev.value)) + vkOutput.round == p.validRound and id(p.proposal) == vkOutput.value)) callConsensus(es, es.bk, { name: "ProposalAndPolkaPreviousAndValid", height: es.cs.height, round: es.cs.round, - value: eev.value, - vr: eev.round}) + value: vkOutput.value, + vr: vkOutput.round}) // TODO: the value should come from the proposal - else if (eev.round == es.cs.round and + else if (vkOutput.round == es.cs.round and es.proposals.exists(p => p.round == es.cs.round and - id(p.proposal) == eev.value)) + id(p.proposal) == vkOutput.value)) callConsensus(es, es.bk, { name: "ProposalAndPolkaAndValid", height: es.cs.height, round: es.cs.round, - value: eev.value, - vr: eev.round}) + value: vkOutput.value, + vr: vkOutput.round}) else // we don't have a matching proposal so we do nothing // TODO: we might check whether it is for a future round and jump - (es, defaultResult) - else if (eev.name == "PolkaAny") - if (eev.round == es.cs.round) + (es, defaultOutput) + else if (vkOutput.name == "PolkaAny") + if (vkOutput.round == es.cs.round) // call consensus and remember that we did it callConsensus(es, es.bk, { name: "PolkaAny", height: es.cs.height, round: es.cs.round, - value: eev.value, - vr: eev.round}) + value: vkOutput.value, + vr: vkOutput.round}) else // TODO: we might check whether it is for a future round and jump - (es, defaultResult) - else if (eev.name == "PolkaNil" and eev.round == es.cs.round) + (es, defaultOutput) + else if (vkOutput.name == "PolkaNil" and vkOutput.round == es.cs.round) callConsensus(es, es.bk, { name: "PolkaNil", height: es.cs.height, round: es.cs.round, - value: eev.value, - vr: eev.round}) - else if (eev.name == "Skip" and eev.round > es.cs.round) + value: vkOutput.value, + vr: vkOutput.round}) + else if (vkOutput.name == "Skip" and vkOutput.round > es.cs.round) callConsensus(es, es.bk, { name: "RoundSkip", height: input.vote.height, round: input.vote.round, value: input.vote.id, vr: -1}) else - (es, defaultResult) + (es, defaultOutput) } -// We do this if a timeout expires at the executor -pure def Timeout (es: ExecutorState, input: ExecutorInput) : (ExecutorState, ConsensusOutput) = { +// We do this if a timeout expires at the driver +pure def Timeout (es: DriverState, input: DriverInput) : (DriverState, ConsensusOutput) = { // TODO: We assume that the timeout event is always for the current round. If this is not // the case, we need to encode it in the input to which round the timeout belongs - val event: Event = {name: input.timeout, + val csInput: ConsensusInput = {name: input.timeout, height: es.cs.height, round: es.cs.round, value: "", vr: 0} - callConsensus(es, es.bk, event) + callConsensus(es, es.bk, csInput) } -// We do this if the executor receives a proposal -pure def ProposalMsg (es: ExecutorState, input: ExecutorInput) : (ExecutorState, ConsensusOutput) = { +// We do this if the driver receives a proposal +pure def ProposalMsg (es: DriverState, input: DriverInput) : (DriverState, ConsensusOutput) = { val newES = { ...es, proposals: es.proposals.union(Set(input.proposal))} if (input.proposal.src != Proposer(es.valset, input.proposal.height, input.proposal.round)) // proposer does not match the height/round of the proposal // keep ES (don't use newES here), that is, drop proposal - (es, defaultResult) + (es, defaultOutput) else if (valid(input.proposal)) val receivedCommit = checkThreshold( newES.bk, input.proposal.round, @@ -307,7 +307,7 @@ pure def ProposalMsg (es: ExecutorState, input: ExecutorInput) : (ExecutorState, else if (input.proposal.round != es.cs.round or input.proposal.height != es.cs.height) // the proposal is from the right proposer and valid, but not for this round // keep the proposal, do nothing else - (newES, defaultResult) + (newES, defaultOutput) else // for current round and q, valid, and from right proposer val receivedPolkaValidRoundVal = checkThreshold(newES.bk, @@ -352,7 +352,7 @@ pure def ProposalMsg (es: ExecutorState, input: ExecutorInput) : (ExecutorState, value: id(input.proposal.proposal), vr: input.proposal.validRound}) else - (newES, defaultResult) + (newES, defaultOutput) else if (newES.cs.step == "Prevote" or newES.cs.step == "Precommit") if (receivedCommitCurrentVal) // here we need to call both, Commit and Polka. @@ -364,7 +364,7 @@ pure def ProposalMsg (es: ExecutorState, input: ExecutorInput) : (ExecutorState, vr: input.proposal.validRound}, newES.cs.height, newES.cs.round) - callConsensus( { ...newES, pendingEvents: newES.pendingEvents.union(Set(pend))}, + callConsensus( { ...newES, pendingInputs: newES.pendingInputs.union(Set(pend))}, newES.bk, { name: "ProposalAndCommitAndValid", height: newES.cs.height, @@ -380,34 +380,34 @@ pure def ProposalMsg (es: ExecutorState, input: ExecutorInput) : (ExecutorState, value: id(input.proposal.proposal), vr: input.proposal.validRound}) else - (newES, defaultResult) + (newES, defaultOutput) else - (newES, defaultResult) + (newES, defaultOutput) else // (not(valid(input.proposal))) // keep ES (don't use newES here), that is, drop proposal if (es.cs.step == "propose" and es.cs.round == input.proposal.round and es.cs.height == input.proposal.height) if (checkThreshold(es.bk, es.cs.round, "Prevote", {name: "Value", value: id(input.proposal.proposal)})) - val event: Event = {name: "ProposalAndPolkaAndInValid", + val csInput: ConsensusInput = {name: "ProposalAndPolkaAndInValid", height: es.cs.height, round: es.cs.round, value: id(input.proposal.proposal), vr: input.proposal.validRound} - callConsensus(es, es.bk, event) + callConsensus(es, es.bk, csInput) else - val event: Event = {name: "ProposalInvalid", + val csInput: ConsensusInput = {name: "ProposalInvalid", height: es.cs.height, round: es.cs.round, value: id(input.proposal.proposal), vr: input.proposal.validRound} - callConsensus(es, es.bk, event) + callConsensus(es, es.bk, csInput) else - (es, defaultResult) + (es, defaultOutput) } // We do this when we need to jump to a new round -pure def skip (es: ExecutorState, r: int) : (ExecutorState, ConsensusOutput) = { +pure def skip (es: DriverState, r: int) : (DriverState, ConsensusOutput) = { // line 15 val prop = if (es.cs.validValue != "nil") es.cs.validValue else getValue(es) @@ -430,15 +430,15 @@ pure def skip (es: ExecutorState, r: int) : (ExecutorState, ConsensusOutput) = { // We do this when we have decided -pure def decided (es: ExecutorState, res: ConsensusOutput) : (ExecutorState, ConsensusOutput) = { +pure def decided (es: DriverState, res: ConsensusOutput) : (DriverState, ConsensusOutput) = { // here we call consensus to set a new height, that is, to initialize the state machine // and then we call skip to start round 0 /* // The following can be used to get to the next height. For now this // function does nothing - // If we choose to move getValue out of the executor logic into the environment (gossip) + // If we choose to move getValue out of the driver logic into the environment (gossip) // then, we would not do this here, but expect the environment to create a (to be defined) - // ExecutorInput + // DriverInput val s1 = callConsensus(es, es.bk, {name : "NewHeight", height : es.cs.height + 1, round: -1, @@ -451,15 +451,15 @@ pure def decided (es: ExecutorState, res: ConsensusOutput) : (ExecutorState, Con // take input out of pending events and then call consensus with that event -// We do this when the executor is asked to work on pending events -pure def PendingEvent (es: ExecutorState, input: ExecutorInput) : (ExecutorState, ConsensusOutput) = { - val newState = { ...es, pendingEvents: es.pendingEvents.exclude(Set((input.event, es.cs.height, es.cs.round)))} - callConsensus(newState, es.bk, input.event) +// We do this when the driver is asked to work on pending events +pure def PendingInput (es: DriverState, input: DriverInput) : (DriverState, ConsensusOutput) = { + val newState = { ...es, pendingInputs: es.pendingInputs.exclude(Set((input.csInput, es.cs.height, es.cs.round)))} + callConsensus(newState, es.bk, input.csInput) } -pure def setValue(es: ExecutorState, value: Value_t) : (ExecutorState, ConsensusOutput) = - ({ ...es, nextValueToPropose: value }, defaultResult) +pure def setValue(es: DriverState, value: Value_t) : (DriverState, ConsensusOutput) = + ({ ...es, nextValueToPropose: value }, defaultOutput) @@ -468,8 +468,8 @@ pure def setValue(es: ExecutorState, value: Value_t) : (ExecutorState, Consensus * Main entry point * ********************************************************/ -// TODO: return ConsensusEvent so that we know from outside what event was fired. -pure def executor (es: ExecutorState, input: ExecutorInput) : (ExecutorState, ConsensusOutput) = { +// TODO: return ConsensusInput so that we know from outside what event was fired. +pure def driver (es: DriverState, input: DriverInput) : (DriverState, ConsensusOutput) = { // TODO: shall we check whether the sender actually is in the validator set if (input.name == "proposal") { val res = ProposalMsg(es, input) @@ -482,9 +482,9 @@ pure def executor (es: ExecutorState, input: ExecutorInput) : (ExecutorState, Co } 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 newES = { ...es, bk: res.bookkeeper, applyvotesResult: res.event} + val newES = { ...es, bk: res.bookkeeper, voteKeeperOutput: res.output } // only a commit event can come here. - val cons_res = Precommit(newES, input, res.event) + val cons_res = Precommit(newES, input, res.output) if (cons_res._2.name == "decided") decided (cons_res._1, cons_res._2) else if (cons_res._2.name == "skipRound") @@ -494,9 +494,9 @@ pure def executor (es: ExecutorState, input: ExecutorInput) : (ExecutorState, Co } 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 newES = { ...es, bk: res.bookkeeper, applyvotesResult: res.event} + val newES = { ...es, bk: res.bookkeeper, voteKeeperOutput: res.output} // only a commit event can come here. - val cons_res = Prevote(newES, input, res.event) + val cons_res = Prevote(newES, input, res.output) if (cons_res._2.name == "decided") // TODO: dead branch. But we should put this after consensus call logic into a function decided (cons_res._1, cons_res._2) @@ -511,8 +511,8 @@ pure def executor (es: ExecutorState, input: ExecutorInput) : (ExecutorState, Co if (res._2.name == "skipRound") skip (res._1, res._2.skipRound) // skip starts a new round. This may involve getValue. If we choose to move the getValue - // logic out of the executor, we wouldn't call skip here but add a (to be defined) - // ExecutorInput + // logic out of the driver, we wouldn't call skip here but add a (to be defined) + // DriverInput else res } @@ -522,12 +522,12 @@ pure def executor (es: ExecutorState, input: ExecutorInput) : (ExecutorState, Co skip (new, 0) } else if (input.name == "pending") { - PendingEvent(es, input) + PendingInput(es, input) } else if (input.name == "SetNextProposedValue") setValue(es, input.nextValueToPropose) else - (es, defaultResult) + (es, defaultOutput) } @@ -535,7 +535,7 @@ pure def executor (es: ExecutorState, input: ExecutorInput) : (ExecutorState, Co // timeouts, etc.) the node should act. // currently this is linked in via the state machine. But we can move it into // the functional layer/ -pure def nextAction (state: NodeState) : (NodeState, ExecutorInput) = { +pure def nextAction (state: NodeState) : (NodeState, DriverInput) = { if (not(state.es.started)) (state, { ...defaultInput, name: "start" }) @@ -563,7 +563,7 @@ pure def nextAction (state: NodeState) : (NodeState, ExecutorInput) = { val newstate = { ...state, timeout: state.timeout.exclude(Set(timeout))} (newstate, { ...defaultInput, name: "timeout", timeout: timeout._1}) - else if (state.es.pendingEvents != Set()) + else if (state.es.pendingInputs != Set()) // this might be cheating as we look into the "es" (state, { ...defaultInput, name: "pending" }) // TODO: In the "starkBFT Spec" Google doc, it is written that pending events @@ -574,7 +574,7 @@ pure def nextAction (state: NodeState) : (NodeState, ExecutorInput) = { } // This function can be used to control test runs better. -pure def nextActionCommand (state: NodeState, command: str) : (NodeState, ExecutorInput) = { +pure def nextActionCommand (state: NodeState, command: str) : (NodeState, DriverInput) = { if (command == "start" and not(state.es.started)) (state, { ...defaultInput, name: "start" }) @@ -602,7 +602,7 @@ pure def nextActionCommand (state: NodeState, command: str) : (NodeState, Execut val newstate = { ...state, timeout: state.timeout.exclude(Set(timeout))} (newstate, { ...defaultInput, name: "timeout", timeout: timeout._1}) - else if (command == "pending" and state.es.pendingEvents != Set()) + else if (command == "pending" and state.es.pendingInputs != Set()) // this might be cheating as we look into the "es" (state, { ...defaultInput, name: "pending" }) diff --git a/Specs/Quint/statemachineAsync.qnt b/Specs/Quint/statemachineAsync.qnt index f714141d8..5192b5ef6 100644 --- a/Specs/Quint/statemachineAsync.qnt +++ b/Specs/Quint/statemachineAsync.qnt @@ -16,8 +16,8 @@ module statemachineAsync { -import executor.* from "./executor" -export executor.* +import driver.* from "./driver" +export driver.* import consensus.* from "./consensus" export consensus.* import voteBookkeeper.* from "./voteBookkeeper" @@ -38,26 +38,26 @@ const Heights : Set[Height_t] val RoundsOrNil = Rounds.union(Set(-1)) val Steps = Set("Prevote", "Precommit") -val AllFaultyVotes : Set[VoteMsg_t] = +val AllFaultyVotes : Set[Vote_t] = tuples(Faulty, Heights, Rounds, Values, Steps) .map(t => { src: t._1, height: t._2, round: t._3, id: t._4, step: t._5 }) -// val AllFaultyVotes : Set[VoteMsg_t] = +// val AllFaultyVotes : Set[Vote_t] = // tuples(Faulty, Heights, Rounds, Values, Steps) // .map(((p, h, r, v, s)) => { src: p, height: h, round: r, id: v, step: s }) -val AllFaultyProposals : Set[ProposeMsg_t] = +val AllFaultyProposals : Set[Proposal_t] = tuples(Faulty, Heights, Rounds, Values, RoundsOrNil) .map(t => { src: t._1, height: t._2, round: t._3, proposal: t._4, validRound: t._5 }) // Global State var system : Address_t -> NodeState -var propBuffer : Address_t -> Set[ProposeMsg_t] -var voteBuffer : Address_t -> Set[VoteMsg_t] -var _hist: { validator: Address_t, input: ExecutorInput, output: ConsensusOutput } +var propBuffer : Address_t -> Set[Proposal_t] +var voteBuffer : Address_t -> Set[Vote_t] +var _hist: { validator: Address_t, input: DriverInput, output: ConsensusOutput } -val ConsensusOutputInv = consensusOutputs.union(Set(defaultResult.name)).contains(_hist.output.name) +val ConsensusOutputInv = consensusOutputs.union(Set(defaultOutput.name)).contains(_hist.output.name) action unchangedAll = all { @@ -77,17 +77,17 @@ action init = all { system' = Correct.mapBy(v => initNode(v, validatorSet)), propBuffer' = Correct.mapBy(v => Set()), voteBuffer' = Correct.mapBy(v => Set()), - _hist' = { validator: "INIT", input: defaultInput, output: defaultResult } + _hist' = { validator: "INIT", input: defaultInput, output: defaultOutput } } // Put the proposal into the buffers of all validators -pure def sendProposal (buffer: Address_t -> Set[ProposeMsg_t], prop: ProposeMsg_t) : Address_t -> Set[ProposeMsg_t] = { +pure def sendProposal (buffer: Address_t -> Set[Proposal_t], prop: Proposal_t) : Address_t -> Set[Proposal_t] = { buffer.keys().mapBy(x => { buffer.get(x).union(Set(prop)) }) } // Put the vote into the inbuffers of all validators -pure def sendVote (sys: Address_t -> Set[VoteMsg_t], vote: VoteMsg_t) : Address_t -> Set[VoteMsg_t] = { +pure def sendVote (sys: Address_t -> Set[Vote_t], vote: Vote_t) : Address_t -> Set[Vote_t] = { sys.keys().mapBy(x => { ...sys.get(x).union(Set(vote)) }) } @@ -101,11 +101,11 @@ pure def startTimeout (sys: Address_t -> NodeState, v: Address_t, toName: str) : action valStep(v: Address_t) : bool = { // pick action - val input = system.get(v).nextAction() // TODO: nextAction could go within executor boundary + val input = system.get(v).nextAction() // TODO: nextAction could go within driver boundary // remove action from v val sys1 = system.put(v, input._1) - // call executor - val res = executor(sys1.get(v).es, input._2) + // call driver + val res = driver(sys1.get(v).es, input._2) all { // update v's state after the step val sys = sys1.put(v, { ...sys1.get(v), es: res._1}) @@ -130,7 +130,7 @@ action valStep(v: Address_t) : bool = { else if (res._2.name == "skipRound") all { propBuffer' = propBuffer, voteBuffer' = voteBuffer, - //skipRound should never leave the executor + //skipRound should never leave the driver system' = sys, } else all { @@ -144,7 +144,7 @@ action valStep(v: Address_t) : bool = { } action setNextValueToPropose(v: Address_t, value: Value_t) : bool = all { - val res = executor(system.get(v).es, { ...defaultInput, name: "SetNextProposedValue", nextValueToPropose: value}) + val res = driver(system.get(v).es, { ...defaultInput, name: "SetNextProposedValue", nextValueToPropose: value}) val newNS = { ...system.get(v), es: res._1} system' = system.put(v, newNS), _hist' = _hist, @@ -152,7 +152,7 @@ action setNextValueToPropose(v: Address_t, value: Value_t) : bool = all { voteBuffer' = voteBuffer, } -action deliverProposal(v: Address_t, p: ProposeMsg_t) : bool = all { +action deliverProposal(v: Address_t, p: Proposal_t) : bool = all { propBuffer.get(v).union(AllFaultyProposals).contains(p), // the proposal must be sent or from a faulty node propBuffer' = propBuffer.put(v, propBuffer.get(v).exclude(Set(p))), system' = system.put(v, { ...system.get(v), incomingProposals: system.get(v).incomingProposals.union(Set(p)) }), @@ -160,7 +160,7 @@ action deliverProposal(v: Address_t, p: ProposeMsg_t) : bool = all { voteBuffer' = voteBuffer, } -action deliverVote(v: Address_t, vote: VoteMsg_t) : bool = all { +action deliverVote(v: Address_t, vote: Vote_t) : bool = all { voteBuffer.get(v).union(AllFaultyVotes).contains(vote), // the vote must be sent or from a faulty node voteBuffer' = voteBuffer.put(v, voteBuffer.get(v).exclude(Set(vote))), system' = system.put(v, { ...system.get(v), incomingVotes: system.get(v).incomingVotes.union(Set(vote)) }), diff --git a/Specs/Quint/statemachineTest.qnt b/Specs/Quint/statemachineTest.qnt index 1e2115ae8..da367de2d 100644 --- a/Specs/Quint/statemachineTest.qnt +++ b/Specs/Quint/statemachineTest.qnt @@ -12,7 +12,7 @@ module statemachineTest { -import executor.* from "./executor" +import driver.* from "./driver" import consensus.* from "./consensus" import voteBookkeeper.* from "./voteBookkeeper" @@ -20,23 +20,23 @@ val validators = Set("v1", "v2", "v3", "v4") val validatorSet = validators.mapBy(x => 1) var system : Address_t -> NodeState -var _hist: (str, ExecutorInput, ConsensusOutput) +var _hist: (str, DriverInput, ConsensusOutput) //var _histSimple: (str, str, str) action init = all { system' = validators.mapBy(v => initNode(v, validatorSet)), - _hist' = ("INIT", defaultInput, defaultResult) + _hist' = ("INIT", defaultInput, defaultOutput) // _histSimple' = ("INIT", "", "") } // Put the proposal into the inbuffers of all validators -pure def deliverProposal (sys: Address_t -> NodeState, prop: ProposeMsg_t) : Address_t -> NodeState = { +pure def deliverProposal (sys: Address_t -> NodeState, prop: Proposal_t) : Address_t -> NodeState = { sys.keys().mapBy(x => { ...sys.get(x), incomingProposals: sys.get(x).incomingProposals.union(Set(prop)) }) } // Put the vote into the inbuffers of all validators -pure def deliverVote (sys: Address_t -> NodeState, vote: VoteMsg_t) : Address_t -> NodeState = { +pure def deliverVote (sys: Address_t -> NodeState, vote: Vote_t) : Address_t -> NodeState = { sys.keys().mapBy(x => { ...sys.get(x), incomingVotes: sys.get(x).incomingVotes.union(Set(vote)) }) } @@ -53,11 +53,11 @@ pure def startTimeout (sys: Address_t -> NodeState, v: Address_t, toName: str) : // hardly ever are fired action valStep(v: Address_t) : bool = { // pick action - val input = system.get(v).nextAction() // TODO: nextAction could go within executor boundary + val input = system.get(v).nextAction() // TODO: nextAction could go within driver boundary // remove action from v val sys1 = system.put(v, input._1) - // call executor - val res = executor(sys1.get(v).es, input._2) + // call driver + val res = driver(sys1.get(v).es, input._2) all { // update v's state after the step val sys = sys1.put(v, { ...sys1.get(v), es: res._1}) @@ -69,7 +69,7 @@ action valStep(v: Address_t) : bool = { else if (res._2.name == "timeout") system' = startTimeout(sys, v, res._2.timeout) else if (res._2.name == "skipRound") - //skipRound should never leave the executor + //skipRound should never leave the driver system' = sys else system' = sys, @@ -79,7 +79,7 @@ action valStep(v: Address_t) : bool = { } action setNextValueToPropose(v: Address_t, value: Value_t) : bool = all { - val res = executor(system.get(v).es, { ...defaultInput, name: "SetNextProposedValue", nextValueToPropose: value}) + val res = driver(system.get(v).es, { ...defaultInput, name: "SetNextProposedValue", nextValueToPropose: value}) val newNS = { ...system.get(v), es: res._1} system' = system.put(v, newNS), _hist' = _hist @@ -101,8 +101,8 @@ action valStepCommand(v: Address_t, command: str) : bool = { val input = system.get(v).nextActionCommand(command) // remove action from v val sys1 = system.put(v, input._1) - // call executor - val res = executor(sys1.get(v).es, input._2) + // call driver + val res = driver(sys1.get(v).es, input._2) all { // update v's state after the step val sys = sys1.put(v, { ...sys1.get(v), es: res._1}) @@ -114,7 +114,7 @@ action valStepCommand(v: Address_t, command: str) : bool = { else if (res._2.name == "timeout") system' = startTimeout(sys, v, res._2.timeout) else if (res._2.name == "skipRound") - //skipRound should never leave the executor + //skipRound should never leave the driver system' = sys else system' = sys, diff --git a/Specs/Quint/voteBookkeeper.qnt b/Specs/Quint/voteBookkeeper.qnt index 3e9bca343..1e1c1676f 100644 --- a/Specs/Quint/voteBookkeeper.qnt +++ b/Specs/Quint/voteBookkeeper.qnt @@ -51,7 +51,7 @@ module voteBookkeeper { round: Round, prevotes: VoteCount, precommits: VoteCount, - emittedEvents: Set[ExecutorEvent], + emittedOutputs: Set[VoteKeeperOutput], votesAddressesWeights: Address -> Weight } @@ -66,18 +66,18 @@ module voteBookkeeper { pure def valueThreshold(value) = { name: "Value", value: value } pure def isThresholdOnValue(t: Threshold): bool = t.name == "Value" - type ExecutorEvent = { + type VoteKeeperOutput = { round: Round, name: str, value: Value } - pure def noEvent(round) = { round: round, name: "None", value: "null" } - pure def polkaValueEvent(round, value) = { round: round, name: "PolkaValue", value: value } - pure def polkaNilEvent(round) = { round: round, name: "PolkaNil", value: "null" } - pure def polkaAnyEvent(round) = { round: round, name: "PolkaAny", value: "null" } - pure def precommitValueEvent(round, value) = { round: round, name: "PrecommitValue", value: value } - pure def precommitAnyEvent(round) = { round: round, name: "PrecommitAny", value: "null" } - pure def skipEvent(round) = { round: round, name: "Skip", value: "null" } + pure def noOutput(round) = { round: round, name: "None", value: "null" } + pure def polkaValueOutput(round, value) = { round: round, name: "PolkaValue", value: value } + pure def polkaNilOutput(round) = { round: round, name: "PolkaNil", value: "null" } + pure def polkaAnyOutput(round) = { round: round, name: "PolkaAny", value: "null" } + pure def precommitValueOutput(round, value) = { round: round, name: "PrecommitValue", value: value } + pure def precommitAnyOutput(round) = { round: round, name: "PrecommitAny", value: "null" } + pure def skipOutput(round) = { round: round, name: "Skip", value: "null" } type Bookkeeper = { height: Height, @@ -96,7 +96,7 @@ module voteBookkeeper { round: round, prevotes: newVoteCount(totalWeight), precommits: newVoteCount(totalWeight), - emittedEvents: Set(), + emittedOutputs: Set(), votesAddressesWeights: Map() } @@ -188,64 +188,64 @@ module voteBookkeeper { } else unreachedThreshold - // Given a round, voteType and threshold, return the corresponding ExecutorEvent - pure def toEvent(round: Round, voteType: VoteType, threshold: Threshold): ExecutorEvent = + // Given a round, voteType and threshold, return the corresponding VoteKeeperOutput + pure def toVoteKeeperOutput(round: Round, voteType: VoteType, threshold: Threshold): VoteKeeperOutput = if (threshold == unreachedThreshold) - noEvent(round) + noOutput(round) // prevote else if (voteType.isPrevote() and threshold.isThresholdOnValue()) - polkaValueEvent(round, threshold.value) + polkaValueOutput(round, threshold.value) else if (voteType.isPrevote() and threshold == nilThreshold) - polkaNilEvent(round) + polkaNilOutput(round) else if (voteType.isPrevote() and threshold == anyThreshold) - polkaAnyEvent(round) + polkaAnyOutput(round) // precommit else if (voteType.isPrecommit() and threshold.isThresholdOnValue()) - precommitValueEvent(round, threshold.value) + precommitValueOutput(round, threshold.value) else if (voteType.isPrecommit() and threshold.in(Set(anyThreshold, nilThreshold))) - precommitAnyEvent(round) + precommitAnyOutput(round) else if (threshold == skipThreshold) - skipEvent(round) + skipOutput(round) else - noEvent(round) + noOutput(round) - run toEventTest = + run toVoteKeeperOutputTest = val round = 10 all { - assert(toEvent(round, "Prevote", unreachedThreshold) == noEvent(round)), - assert(toEvent(round, "Precommit", unreachedThreshold) == noEvent(round)), + assert(toVoteKeeperOutput(round, "Prevote", unreachedThreshold) == noOutput(round)), + assert(toVoteKeeperOutput(round, "Precommit", unreachedThreshold) == noOutput(round)), - assert(toEvent(round, "Prevote", anyThreshold) == polkaAnyEvent(round)), - assert(toEvent(round, "Prevote", nilThreshold) == polkaNilEvent(round)), - assert(toEvent(round, "Prevote", valueThreshold("val1")) == polkaValueEvent(round, "val1")), + assert(toVoteKeeperOutput(round, "Prevote", anyThreshold) == polkaAnyOutput(round)), + assert(toVoteKeeperOutput(round, "Prevote", nilThreshold) == polkaNilOutput(round)), + assert(toVoteKeeperOutput(round, "Prevote", valueThreshold("val1")) == polkaValueOutput(round, "val1")), - assert(toEvent(round, "Precommit", anyThreshold) == precommitAnyEvent(round)), - assert(toEvent(round, "Precommit", nilThreshold) == precommitAnyEvent(round)), - assert(toEvent(round, "Precommit", valueThreshold("val1")) == precommitValueEvent(round, "val1")), + assert(toVoteKeeperOutput(round, "Precommit", anyThreshold) == precommitAnyOutput(round)), + assert(toVoteKeeperOutput(round, "Precommit", nilThreshold) == precommitAnyOutput(round)), + assert(toVoteKeeperOutput(round, "Precommit", valueThreshold("val1")) == precommitValueOutput(round, "val1")), - assert(toEvent(round, "Prevote", skipThreshold) == skipEvent(round)), - assert(toEvent(round, "Precommit", skipThreshold) == skipEvent(round)), + assert(toVoteKeeperOutput(round, "Prevote", skipThreshold) == skipOutput(round)), + assert(toVoteKeeperOutput(round, "Precommit", skipThreshold) == skipOutput(round)), - assert(toEvent(round, "Precommit", { name: "Error", value: "null" }) == noEvent(round)), - assert(toEvent(round, "Error", anyThreshold) == noEvent(round)), + assert(toVoteKeeperOutput(round, "Precommit", { name: "Error", value: "null" }) == noOutput(round)), + assert(toVoteKeeperOutput(round, "Error", anyThreshold) == noOutput(round)), } - // Executor interface - type BKResult = { bookkeeper: Bookkeeper, event: ExecutorEvent } + // Driver interface + type BKOutput = { bookkeeper: Bookkeeper, output: VoteKeeperOutput } - // Called by the executor when it receives a vote. The function takes the following steps: + // Called by the driver when it receives a vote. The function takes the following steps: // - It first adds the vote and then computes a threshold. - // - If there exist a threshold and has not emitted before, the function returns the corresponding ExecutorEvent. - // - Otherwise, the function returns a no-threshold event. + // - If there exist a threshold and has not emitted before, the function returns the corresponding VoteKeeperOutput. + // - Otherwise, the function returns a no-threshold output. // - Note that if there is no threshold after adding the vote, the function checks if there is a skip threshold. // 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, currentRound: Round): { bookkeeper: Bookkeeper, output: VoteKeeperOutput } = val round = vote.round val roundVotes = keeper.rounds.getOrElse(round, newRoundVotes(keeper.height, round, keeper.totalWeight)) @@ -264,16 +264,16 @@ module voteBookkeeper { // Combined weight of all validators at this height val combinedWeight = updatedVotesAddressesWeights.mapSumValues() - val finalEvent = + val finalOutput = if (vote.round > currentRound and isSkip(combinedWeight, keeper.totalWeight)) - skipEvent(vote.round) + skipOutput(vote.round) else val threshold = computeThreshold(updatedVoteCount, vote.value) - val event = toEvent(vote.round, vote.typ, threshold) - if (not(event.in(roundVotes.emittedEvents))) - event + val output = toVoteKeeperOutput(vote.round, vote.typ, threshold) + if (not(output.in(roundVotes.emittedOutputs))) + output else - noEvent(vote.round) + noOutput(vote.round) val updatedRoundVotes = if (vote.typ.isPrevote()) @@ -282,12 +282,12 @@ module voteBookkeeper { roundVotes.with("precommits", updatedVoteCount) val updatedRoundVotes2 = updatedRoundVotes .with("votesAddressesWeights", updatedVotesAddressesWeights) - .with("emittedEvents", roundVotes.emittedEvents.setAddIf(finalEvent, finalEvent.name != "None")) + .with("emittedOutputs", roundVotes.emittedOutputs.setAddIf(finalOutput, finalOutput.name != "None")) val updatedBookkeeper = keeper .with("rounds", keeper.rounds.mapSafeSet(vote.round, updatedRoundVotes2)) - { bookkeeper: updatedBookkeeper, event: finalEvent } + { bookkeeper: updatedBookkeeper, output: finalOutput } run applyVoteTest = val roundVotes: RoundVotes = { @@ -295,7 +295,7 @@ module voteBookkeeper { round: 0, prevotes: { totalWeight: 4, votesAddresses: Set(), valuesWeights: Map("v1" -> 1, Nil -> 3) }, precommits: { totalWeight: 4, votesAddresses: Set(), valuesWeights: Map() }, - emittedEvents: Set(), + emittedOutputs: Set(), votesAddressesWeights: Map(), } val vk: Bookkeeper = { height: 0, totalWeight: 4, rounds: Map(0 -> roundVotes) } @@ -305,16 +305,16 @@ module voteBookkeeper { val o4 = applyVote(o3.bookkeeper, { height: 0, round: 0, address: "a3", typ: "Precommit", value: Nil }, 1, 0) val o5 = applyVote(o4.bookkeeper, { height: 0, round: 1, address: "a4", typ: "Precommit", value: Nil }, 3, 0) all { - assert(o1.event.name == "None"), - assert(o2.event.name == "None"), - assert(o3.event.name == "PrecommitAny"), - assert(o4.event.name == "None"), - assert(o5.event.name == "Skip"), + assert(o1.output.name == "None"), + assert(o2.output.name == "None"), + assert(o3.output.name == "PrecommitAny"), + assert(o4.output.name == "None"), + assert(o5.output.name == "Skip"), } - // Called by the executor to check if there is a specific threshold for a given round and voteType. + // Called by the driver to check if there is a specific threshold for a given round and voteType. // TO DISCUSS: - // - The function does not consider Skip threshold. This because if the executor receives a Skip event + // - The function does not consider Skip threshold. This because if the driver receives a Skip output // and do not act on it, this means that it will never do it in the future. We should discuss that this // is the case. pure def checkThreshold(keeper: Bookkeeper, round: Round, voteType: VoteType, threshold: Threshold): bool = @@ -380,8 +380,8 @@ module voteBookkeeper { var weightedVote: WeightedVote // The state of the Bookkeeper. var bookkeeper: Bookkeeper - // The event resulting from applying a weighted vote to the bookkeeper. - var lastEmitted: ExecutorEvent + // The output outputing from applying a weighted vote to the bookkeeper. + var lastEmitted: VoteKeeperOutput // ************************************************************************ // Actions @@ -399,13 +399,13 @@ module voteBookkeeper { lastEmitted' = { round: -1, name: "", value: "null" } } - // The vote keeper receives a weighted vote and produces an event. + // The vote keeper receives a weighted vote and produces an output. action applyVoteAction(vote: Vote, weight: Weight, currentRound: Round): bool = - val result = applyVote(bookkeeper, vote, weight, currentRound) + val output = applyVote(bookkeeper, vote, weight, currentRound) all { weightedVote' = (vote, weight, currentRound), - bookkeeper' = result.bookkeeper, - lastEmitted' = result.event + bookkeeper' = output.bookkeeper, + lastEmitted' = output.output } } diff --git a/Specs/Quint/voteBookkeeperTest.qnt b/Specs/Quint/voteBookkeeperTest.qnt index 44419ca27..95c369e70 100644 --- a/Specs/Quint/voteBookkeeperTest.qnt +++ b/Specs/Quint/voteBookkeeperTest.qnt @@ -22,71 +22,71 @@ module voteBookkeeperTest { run synchronousConsensusTest = initWith(1, 100) .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "val1", address: "alice"}, 60, 1)) - .then(_assert(lastEmitted == noEvent(1))) + .then(_assert(lastEmitted == noOutput(1))) .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "val1", address: "john"}, 10, 1)) - .then(_assert(lastEmitted == polkaValueEvent(1, "val1"))) + .then(_assert(lastEmitted == polkaValueOutput(1, "val1"))) .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "val1", address: "bob"}, 30, 1)) - .then(_assert(lastEmitted == noEvent(1))) + .then(_assert(lastEmitted == noOutput(1))) .then(applyVoteAction({typ: "Precommit", height: 1, round: 1, value: "val1", address: "bob"}, 30, 1)) - .then(_assert(lastEmitted == noEvent(1))) + .then(_assert(lastEmitted == noOutput(1))) .then(applyVoteAction({typ: "Precommit", height: 1, round: 1, value: "val1", address: "john"}, 10, 1)) - .then(_assert(lastEmitted == noEvent(1))) + .then(_assert(lastEmitted == noOutput(1))) .then(applyVoteAction({typ: "Precommit", height: 1, round: 1, value: "val1", address: "alice"}, 60, 1)) - .then(_assert(lastEmitted == precommitValueEvent(1, "val1"))) + .then(_assert(lastEmitted == precommitValueOutput(1, "val1"))) // Reaching PolkaAny run polkaAnyTest = initWith(1, 100) .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "val1", address: "alice"}, 60, 1)) - .then(_assert(lastEmitted == noEvent(1))) + .then(_assert(lastEmitted == noOutput(1))) .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "nil", address: "john"}, 10, 1)) - .then(_assert(lastEmitted == polkaAnyEvent(1))) + .then(_assert(lastEmitted == polkaAnyOutput(1))) // Reaching PolkaNil run polkaNilTest = initWith(1, 100) .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "nil", address: "alice"}, 60, 1)) - .then(_assert(lastEmitted == noEvent(1))) + .then(_assert(lastEmitted == noOutput(1))) .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "nil", address: "john"}, 10, 1)) - .then(_assert(lastEmitted == polkaNilEvent(1))) + .then(_assert(lastEmitted == polkaNilOutput(1))) // Reaching Skip via n+1 threshold with prevotes from two validators at a future round run skipSmallQuorumAllPrevotesTest = initWith(1, 100) .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "val1", address: "alice"}, 60, 1)) - .then(_assert(lastEmitted == noEvent(1))) + .then(_assert(lastEmitted == noOutput(1))) .then(applyVoteAction({typ: "Prevote", height: 1, round: 2, value: "val1", address: "john"}, 10, 1)) - .then(_assert(lastEmitted == noEvent(2))) + .then(_assert(lastEmitted == noOutput(2))) .then(applyVoteAction({typ: "Prevote", height: 1, round: 2, value: "val1", address: "bob"}, 30, 1)) - .then(_assert(lastEmitted == skipEvent(2))) + .then(_assert(lastEmitted == skipOutput(2))) // 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", height: 1, round: 1, value: "val1", address: "alice"}, 10, 1)) - .then(_assert(lastEmitted == noEvent(1))) + .then(_assert(lastEmitted == noOutput(1))) .then(applyVoteAction({typ: "Prevote", height: 1, round: 2, value: "val1", address: "john"}, 20, 1)) - .then(_assert(lastEmitted == noEvent(2))) + .then(_assert(lastEmitted == noOutput(2))) .then(applyVoteAction({typ: "Precommit", height: 1, round: 2, value: "val1", address: "john"}, 20, 1)) - .then(_assert(lastEmitted != skipEvent(2))) + .then(_assert(lastEmitted != skipOutput(2))) // 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", height: 1, round: 1, value: "val1", address: "alice"}, 50, 1)) - .then(_assert(lastEmitted == noEvent(1))) + .then(_assert(lastEmitted == noOutput(1))) .then(applyVoteAction({typ: "Prevote", height: 1, round: 2, value: "val1", address: "john"}, 10, 1)) - .then(_assert(lastEmitted == noEvent(2))) + .then(_assert(lastEmitted == noOutput(2))) .then(applyVoteAction({typ: "Precommit", height: 1, round: 2, value: "val1", address: "bob"}, 20, 1)) - .then(_assert(lastEmitted == skipEvent(2))) + .then(_assert(lastEmitted == skipOutput(2))) // 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", height: 1, round: 1, value: "val1", address: "alice"}, 10, 1)) - .then(all { assert(lastEmitted == noEvent(1)), allUnchanged }) + .then(all { assert(lastEmitted == noOutput(1)), allUnchanged }) .then(applyVoteAction({typ: "Prevote", height: 1, round: 2, value: "val1", address: "john"}, 60, 1)) - .then(all { assert(lastEmitted == skipEvent(2)), allUnchanged }) + .then(all { assert(lastEmitted == skipOutput(2)), allUnchanged }) // Reaching Skip via 2f+1 threshold with a single precommit from a single validator at a future round run skipQuorumSinglePrecommitTest = @@ -94,27 +94,27 @@ module voteBookkeeperTest { .then(applyVoteAction({typ: "Prevote", height: 1, round: 1, value: "val1", address: "alice"}, 10, 1)) .then(_assert(lastEmitted == {round: 1, name: "None", value: "null"})) .then(applyVoteAction({typ: "Precommit", height: 1, round: 2, value: "val1", address: "john"}, 60, 1)) - .then(_assert(lastEmitted == skipEvent(2))) + .then(_assert(lastEmitted == skipOutput(2))) // 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", height: 1, round: 1, value: "val1", address: "alice"}, 10, 1)) - .then(_assert(lastEmitted == noEvent(1))) + .then(_assert(lastEmitted == noOutput(1))) .then(applyVoteAction({typ: "Prevote", height: 1, round: 2, value: "val1", address: "john"}, 30, 1)) - .then(_assert(lastEmitted == noEvent(2))) + .then(_assert(lastEmitted == noOutput(2))) .then(applyVoteAction({typ: "Precommit", height: 1, round: 2, value: "val1", address: "john"}, 30, 1)) - .then(_assert(lastEmitted != skipEvent(2))) + .then(_assert(lastEmitted != skipOutput(2))) // 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", height: 1, round: 1, value: "val1", address: "alice"}, 20, 1)) - .then(_assert(lastEmitted == noEvent(1))) + .then(_assert(lastEmitted == noOutput(1))) .then(applyVoteAction({typ: "Prevote", height: 1, round: 2, value: "val1", address: "john"}, 10, 1)) - .then(_assert(lastEmitted == noEvent(2))) + .then(_assert(lastEmitted == noOutput(2))) .then(applyVoteAction({typ: "Precommit", height: 1, round: 2, value: "val1", address: "bob"}, 50, 1)) - .then(_assert(lastEmitted == skipEvent(2))) + .then(_assert(lastEmitted == skipOutput(2))) // **************************************************************************** // Properties that define an expected final state (for generating traces)