diff --git a/Code/common/src/lib.rs b/Code/common/src/lib.rs index 7dcc3ec6a..44b33eb84 100644 --- a/Code/common/src/lib.rs +++ b/Code/common/src/lib.rs @@ -1,3 +1,15 @@ +//! Common data types and abstractions + +#![forbid(unsafe_code)] +#![deny(unused_crate_dependencies, trivial_casts, trivial_numeric_casts)] +#![warn( + // missing_docs, + broken_intra_doc_links, + private_intra_doc_links, + variant_size_differences +)] +#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::panic))] + mod height; mod proposal; mod round; diff --git a/Code/common/src/proposal.rs b/Code/common/src/proposal.rs index e7a2c2af1..43b59584a 100644 --- a/Code/common/src/proposal.rs +++ b/Code/common/src/proposal.rs @@ -1,16 +1,18 @@ -use crate::{Round, Value}; +use crate::{Height, Round, Value}; /// A proposal for a value in a round #[derive(Clone, Debug, PartialEq, Eq)] pub struct Proposal { + pub height: Height, pub round: Round, pub value: Value, pub pol_round: Round, } impl Proposal { - pub fn new(round: Round, value: Value, pol_round: Round) -> Self { + pub fn new(height: Height, round: Round, value: Value, pol_round: Round) -> Self { Self { + height, round, value, pol_round, diff --git a/Code/common/src/round.rs b/Code/common/src/round.rs index 5bb28e465..bdb1fe530 100644 --- a/Code/common/src/round.rs +++ b/Code/common/src/round.rs @@ -27,7 +27,18 @@ impl Round { } pub fn is_defined(&self) -> bool { - matches!(self, Round::Some(_)) + matches!(self, Round::Some(r) if *r >= 0) + } + + pub fn is_nil(&self) -> bool { + matches!(self, Round::None) + } + + pub fn is_valid(&self) -> bool { + match self { + Round::None => true, + Round::Some(r) => *r >= 0, + } } pub fn increment(&self) -> Round { diff --git a/Code/common/src/validator_set.rs b/Code/common/src/validator_set.rs index 11c1f1399..e90cda9cd 100644 --- a/Code/common/src/validator_set.rs +++ b/Code/common/src/validator_set.rs @@ -4,6 +4,10 @@ pub struct PublicKey(Vec); impl PublicKey { + pub const fn new(value: Vec) -> Self { + Self(value) + } + pub fn hash(&self) -> u64 { // TODO self.0.iter().fold(0, |acc, x| acc ^ *x as u64) @@ -99,6 +103,10 @@ impl ValidatorSet { self.validators.iter().find(|v| &v.address() == address) } + pub fn get_by_public_key(&self, public_key: &PublicKey) -> Option<&Validator> { + self.validators.iter().find(|v| &v.public_key == public_key) + } + /// In place sort and deduplication of a list of validators fn sort_validators(vals: &mut Vec) { use core::cmp::Reverse; @@ -109,6 +117,14 @@ impl ValidatorSet { vals.dedup(); } + + pub fn get_proposer(&mut self) -> Validator { + // TODO: Proper implementation + assert!(!self.validators.is_empty()); + let proposer = self.validators[0].clone(); + self.validators.rotate_left(1); + proposer + } } #[cfg(test)] diff --git a/Code/common/src/value.rs b/Code/common/src/value.rs index 342414176..d06ea6a53 100644 --- a/Code/common/src/value.rs +++ b/Code/common/src/value.rs @@ -10,4 +10,21 @@ impl Value { pub fn as_u64(&self) -> u64 { self.0 } + + pub fn valid(&self) -> bool { + self.0 > 0 + } + + pub fn id(&self) -> ValueId { + ValueId(self.0) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Copy)] +pub struct ValueId(u64); + +impl ValueId { + pub fn new(id: u64) -> Self { + Self(id) + } } diff --git a/Code/common/src/vote.rs b/Code/common/src/vote.rs index e0a620ff2..9ca3a6eab 100644 --- a/Code/common/src/vote.rs +++ b/Code/common/src/vote.rs @@ -1,4 +1,4 @@ -use crate::{Address, Round, Value}; +use crate::{Address, Round, ValueId}; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum VoteType { @@ -11,12 +11,12 @@ pub enum VoteType { pub struct Vote { pub typ: VoteType, pub round: Round, - pub value: Option, + pub value: Option, pub address: Address, } impl Vote { - pub fn new_prevote(round: Round, value: Option, address: Address) -> Self { + pub fn new_prevote(round: Round, value: Option, address: Address) -> Self { Self { typ: VoteType::Prevote, round, @@ -25,7 +25,7 @@ impl Vote { } } - pub fn new_precommit(round: Round, value: Option, address: Address) -> Self { + pub fn new_precommit(round: Round, value: Option, address: Address) -> Self { Self { typ: VoteType::Precommit, round, diff --git a/Code/consensus/src/executor.rs b/Code/consensus/src/executor.rs index 809325401..d7de326f6 100644 --- a/Code/consensus/src/executor.rs +++ b/Code/consensus/src/executor.rs @@ -1,81 +1,149 @@ use std::collections::BTreeMap; +use std::sync::Arc; -use malachite_common::{Height, Proposal, Round, Timeout, TimeoutStep, ValidatorSet, Vote}; +use malachite_common::{ + Height, Proposal, PublicKey, Round, Timeout, TimeoutStep, ValidatorSet, Value, Vote, VoteType, +}; use malachite_round::events::Event as RoundEvent; use malachite_round::message::Message as RoundMessage; use malachite_round::state::State as RoundState; +use malachite_vote::count::Threshold; use malachite_vote::keeper::VoteKeeper; #[derive(Clone, Debug)] pub struct Executor { height: Height, + key: PublicKey, validator_set: ValidatorSet, - + round: Round, votes: VoteKeeper, round_states: BTreeMap, } #[derive(Clone, Debug, PartialEq, Eq)] pub enum Message { + NewRound(Round), Proposal(Proposal), Vote(Vote), Timeout(Timeout), } impl Executor { - pub fn new(height: Height, validator_set: ValidatorSet) -> Self { + pub fn new(height: Height, validator_set: ValidatorSet, key: PublicKey) -> Self { let votes = VoteKeeper::new(height, Round::INITIAL, validator_set.total_voting_power()); Self { height, + key, validator_set, + round: Round::INITIAL, votes, round_states: BTreeMap::new(), } } - pub fn execute(&mut self, msg: Message) { - let msg = match self.apply(msg) { + pub fn get_value(&self) -> Value { + // TODO - add external interface to get the value + Value::new(9999) + } + pub fn execute(&mut self, msg: Message) -> Option { + let round_msg = match self.apply(msg) { Some(msg) => msg, - None => return, + None => return None, }; - match msg { + match round_msg { RoundMessage::NewRound(round) => { // TODO: check if we are the proposer self.round_states .insert(round, RoundState::new(self.height).new_round(round)); + None } - RoundMessage::Proposal(_) => { + RoundMessage::Proposal(p) => { // sign the proposal + Some(Message::Proposal(p)) } - RoundMessage::Vote(_) => { + RoundMessage::Vote(mut v) => { // sign the vote + // TODO - round message votes should not include address + v.address = self.validator_set.get_by_public_key(&self.key)?.address(); + Some(Message::Vote(v)) } RoundMessage::Timeout(_) => { // schedule the timeout + None } RoundMessage::Decision(_) => { // update the state + None } } } fn apply(&mut self, msg: Message) -> Option { match msg { + Message::NewRound(round) => self.apply_new_round(round), Message::Proposal(proposal) => self.apply_proposal(proposal), Message::Vote(vote) => self.apply_vote(vote), Message::Timeout(timeout) => self.apply_timeout(timeout), } } + fn apply_new_round(&mut self, round: Round) -> Option { + let proposer = self.validator_set.get_proposer(); + let event = if proposer.public_key == self.key { + let value = self.get_value(); + RoundEvent::NewRoundProposer(value) + } else { + RoundEvent::NewRound + }; + self.apply_event(round, event) + } + fn apply_proposal(&mut self, proposal: Proposal) -> Option { // TODO: Check for invalid proposal let round = proposal.round; - let event = RoundEvent::Proposal(proposal); + let event = RoundEvent::Proposal(proposal.clone()); - self.apply_event(round, event) + let Some(round_state) = self.round_states.get(&self.round) else { + // TODO: Add logging + return None; + }; + + if round_state.proposal.is_some() { + return None; + } + + if round_state.height != proposal.height || proposal.round != self.round { + return None; + } + + if !proposal.pol_round.is_valid() + || proposal.pol_round.is_defined() && proposal.pol_round >= round_state.round + { + return None; + } + + // TODO verify proposal signature (make some of these checks part of message validation) + + match proposal.pol_round { + Round::None => { + // Is it possible to get +2/3 prevotes before the proposal? + // Do we wait for our own prevote to check the threshold? + self.apply_event(round, event) + } + Round::Some(_) + if self.votes.check_threshold( + &proposal.pol_round, + VoteType::Prevote, + Threshold::Value(Arc::from(proposal.value.id())), + ) => + { + self.apply_event(round, event) + } + _ => None, + } } fn apply_vote(&mut self, vote: Vote) -> Option { @@ -122,3 +190,201 @@ impl Executor { transition.message } } + +#[cfg(test)] +mod tests { + use super::*; + use malachite_common::{Proposal, Validator, Value}; + use malachite_round::state::{RoundValue, State, Step}; + + #[test] + fn test_executor_steps() { + let value = Value::new(9999); // TODO: get value from external source + let value_id = value.id(); + let v1 = Validator::new(PublicKey::new(vec![1]), 1); + let v2 = Validator::new(PublicKey::new(vec![2]), 1); + let v3 = Validator::new(PublicKey::new(vec![3]), 1); + let my_address = v1.clone().address(); + let key = v1.clone().public_key; // we are proposer + + let vs = ValidatorSet::new(vec![v1, v2.clone(), v3.clone()]); + + let mut executor = Executor::new(Height::new(1), vs, key.clone()); + + let proposal = Proposal::new(Height::new(1), Round::new(0), value.clone(), Round::new(-1)); + struct TestStep { + input_message: Option, + expected_output_message: Option, + new_state: State, + } + let steps: Vec = vec![ + // Start round 0, we are proposer, propose value + TestStep { + input_message: Some(Message::NewRound(Round::new(0))), + expected_output_message: Some(Message::Proposal(proposal.clone())), + new_state: State { + height: Height::new(1), + round: Round::new(0), + step: Step::Propose, + proposal: None, + locked: None, + valid: None, + }, + }, + // Receive our own proposal, prevote for it (v1) + TestStep { + input_message: None, + expected_output_message: Some(Message::Vote(Vote::new_prevote( + Round::new(0), + Some(value_id), + my_address, + ))), + new_state: State { + height: Height::new(1), + round: Round::new(0), + step: Step::Prevote, + proposal: Some(proposal.clone()), + locked: None, + valid: None, + }, + }, + // Receive our own prevote v1 + TestStep { + input_message: None, + expected_output_message: None, + new_state: State { + height: Height::new(1), + round: Round::new(0), + step: Step::Prevote, + proposal: Some(proposal.clone()), + locked: None, + valid: None, + }, + }, + // v2 prevotes for our proposal + TestStep { + input_message: Some(Message::Vote(Vote::new_prevote( + Round::new(0), + Some(value_id), + v2.clone().address(), + ))), + expected_output_message: None, + new_state: State { + height: Height::new(1), + round: Round::new(0), + step: Step::Prevote, + proposal: Some(proposal.clone()), + locked: None, + valid: None, + }, + }, + // v3 prevotes for our proposal, we get +2/3 prevotes, precommit for it (v1) + TestStep { + input_message: Some(Message::Vote(Vote::new_prevote( + Round::new(0), + Some(value_id), + v3.clone().address(), + ))), + expected_output_message: Some(Message::Vote(Vote::new_precommit( + Round::new(0), + Some(value_id), + my_address, + ))), + new_state: State { + height: Height::new(1), + round: Round::new(0), + step: Step::Precommit, + proposal: Some(proposal.clone()), + locked: Some(RoundValue { + value: value.clone(), + round: Round::new(0), + }), + valid: Some(RoundValue { + value: value.clone(), + round: Round::new(0), + }), + }, + }, + // v1 receives its own precommit + TestStep { + input_message: None, + expected_output_message: None, + new_state: State { + height: Height::new(1), + round: Round::new(0), + step: Step::Precommit, + proposal: Some(proposal.clone()), + locked: Some(RoundValue { + value: value.clone(), + round: Round::new(0), + }), + valid: Some(RoundValue { + value: value.clone(), + round: Round::new(0), + }), + }, + }, + // v2 precommits for our proposal + TestStep { + input_message: Some(Message::Vote(Vote::new_precommit( + Round::new(0), + Some(value_id), + v2.clone().address(), + ))), + expected_output_message: None, + new_state: State { + height: Height::new(1), + round: Round::new(0), + step: Step::Precommit, + proposal: Some(proposal.clone()), + locked: Some(RoundValue { + value: value.clone(), + round: Round::new(0), + }), + valid: Some(RoundValue { + value: value.clone(), + round: Round::new(0), + }), + }, + }, + // v3 precommits for our proposal, we get +2/3 precommits, decide it (v1) + TestStep { + input_message: Some(Message::Vote(Vote::new_precommit( + Round::new(0), + Some(value_id), + v2.clone().address(), + ))), + expected_output_message: None, + new_state: State { + height: Height::new(1), + round: Round::new(0), + step: Step::Commit, + proposal: Some(proposal.clone()), + locked: Some(RoundValue { + value: value.clone(), + round: Round::new(0), + }), + valid: Some(RoundValue { + value: value.clone(), + round: Round::new(0), + }), + }, + }, + ]; + + let mut previous_message = None; + for step in steps { + let execute_message = if step.input_message.is_none() { + previous_message.clone() + } else { + step.input_message + } + .unwrap(); + let message = executor.execute(execute_message); + assert_eq!(message, step.expected_output_message); + let new_state = executor.round_states.get(&Round::new(0)).unwrap(); + assert_eq!(new_state, &step.new_state); + previous_message = message; + } + } +} diff --git a/Code/consensus/src/lib.rs b/Code/consensus/src/lib.rs index 0c95fdab2..756b4ca4b 100644 --- a/Code/consensus/src/lib.rs +++ b/Code/consensus/src/lib.rs @@ -1 +1,13 @@ +//! Consensus executor + +#![forbid(unsafe_code)] +#![deny(unused_crate_dependencies, trivial_casts, trivial_numeric_casts)] +#![warn( + // missing_docs, + broken_intra_doc_links, + private_intra_doc_links, + variant_size_differences +)] +#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::panic))] + pub mod executor; diff --git a/Code/round/src/events.rs b/Code/round/src/events.rs index 59a30b07a..aeb9ea8b8 100644 --- a/Code/round/src/events.rs +++ b/Code/round/src/events.rs @@ -1,6 +1,6 @@ use malachite_common::Proposal; -use crate::Value; +use crate::{Value, ValueId}; #[derive(Clone, Debug, PartialEq, Eq)] pub enum Event { @@ -10,9 +10,9 @@ pub enum Event { ProposalInvalid, // Receive an invalid proposal. PolkaAny, // Receive +2/3 prevotes for anything. PolkaNil, // Receive +2/3 prevotes for nil. - PolkaValue(Value), // Receive +2/3 prevotes for Value. + PolkaValue(ValueId), // Receive +2/3 prevotes for Value. PrecommitAny, // Receive +2/3 precommits for anything. - PrecommitValue(Value), // Receive +2/3 precommits for Value. + PrecommitValue(ValueId), // Receive +2/3 precommits for Value. RoundSkip, // Receive +1/3 votes from a higher round. TimeoutPropose, // Timeout waiting for proposal. TimeoutPrevote, // Timeout waiting for prevotes. diff --git a/Code/round/src/lib.rs b/Code/round/src/lib.rs index ee336d3bb..dca5bb0e8 100644 --- a/Code/round/src/lib.rs +++ b/Code/round/src/lib.rs @@ -1,3 +1,15 @@ +//! Per-round consensus state machine + +#![forbid(unsafe_code)] +#![deny(unused_crate_dependencies, trivial_casts, trivial_numeric_casts)] +#![warn( + // missing_docs, + broken_intra_doc_links, + private_intra_doc_links, + variant_size_differences +)] +#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::panic))] + pub use malachite_common::*; pub mod events; diff --git a/Code/round/src/message.rs b/Code/round/src/message.rs index 5e9ecfeda..4c3b62a0e 100644 --- a/Code/round/src/message.rs +++ b/Code/round/src/message.rs @@ -1,6 +1,6 @@ -use malachite_common::Address; +use malachite_common::{Address, Height}; -use crate::{state::RoundValue, Proposal, Round, Timeout, TimeoutStep, Value, Vote}; +use crate::{state::RoundValue, Proposal, Round, Timeout, TimeoutStep, Value, ValueId, Vote}; #[derive(Clone, Debug, PartialEq, Eq)] pub enum Message { @@ -12,20 +12,21 @@ pub enum Message { } impl Message { - pub fn proposal(round: Round, value: Value, pol_round: Round) -> Message { + pub fn proposal(height: Height, round: Round, value: Value, pol_round: Round) -> Message { Message::Proposal(Proposal { + height, round, value, pol_round, }) } - pub fn prevote(round: Round, value: Option, address: Address) -> Message { - Message::Vote(Vote::new_prevote(round, value, address)) + pub fn prevote(round: Round, value_id: Option, address: Address) -> Message { + Message::Vote(Vote::new_prevote(round, value_id, address)) } - pub fn precommit(round: Round, value: Option, address: Address) -> Message { - Message::Vote(Vote::new_precommit(round, value, address)) + pub fn precommit(round: Round, value_id: Option, address: Address) -> Message { + Message::Vote(Vote::new_precommit(round, value_id, address)) } pub fn timeout(round: Round, step: TimeoutStep) -> Message { diff --git a/Code/round/src/state.rs b/Code/round/src/state.rs index ffa082cf4..f77bb053f 100644 --- a/Code/round/src/state.rs +++ b/Code/round/src/state.rs @@ -1,6 +1,7 @@ use crate::events::Event; use crate::state_machine::Transition; use crate::{Height, Round, Value}; +use malachite_common::Proposal; /// A value and its associated round #[derive(Clone, Debug, PartialEq, Eq)] @@ -31,6 +32,7 @@ pub struct State { pub height: Height, pub round: Round, pub step: Step, + pub proposal: Option, pub locked: Option, pub valid: Option, } @@ -41,6 +43,7 @@ impl State { height, round: Round::INITIAL, step: Step::NewRound, + proposal: None, locked: None, valid: None, } diff --git a/Code/round/src/state_machine.rs b/Code/round/src/state_machine.rs index 0fb952d88..fc6c7ad91 100644 --- a/Code/round/src/state_machine.rs +++ b/Code/round/src/state_machine.rs @@ -3,7 +3,7 @@ use malachite_common::Address; use crate::events::Event; use crate::message::Message; use crate::state::{State, Step}; -use crate::{Round, TimeoutStep, Value}; +use crate::{Round, TimeoutStep, Value, ValueId}; // FIXME: Where to get the address/public key from? // IDEA: Add a Context parameter to `apply_state` @@ -39,7 +39,7 @@ impl Transition { } } -/// Check that a proposal has a Proof-Of-Lock +/// Check that a proposal has a valid Proof-Of-Lock round fn is_valid_pol_round(state: &State, pol_round: Round) -> bool { pol_round.is_defined() && pol_round < state.round } @@ -52,7 +52,7 @@ fn is_valid_pol_round(state: &State, pol_round: Round) -> bool { /// Valid transitions result in at least a change to the state and/or an output message. /// /// Commented numbers refer to line numbers in the spec paper. -pub fn apply_event(state: State, round: Round, event: Event) -> Transition { +pub fn apply_event(mut state: State, round: Round, event: Event) -> Transition { let this_round = state.round == round; match (state.step, event) { @@ -61,10 +61,37 @@ pub fn apply_event(state: State, round: Round, event: Event) -> Transition { (Step::NewRound, Event::NewRound) if this_round => schedule_timeout_propose(state), // L11/L20 // From Propose. Event must be for current round. - (Step::Propose, Event::Proposal(proposal)) // L22/L28 + (Step::Propose, Event::Proposal(proposal)) if this_round && proposal.pol_round.is_nil() => { + // L22 + if proposal.value.valid() + && state + .locked + .as_ref() + .map_or(true, |locked| locked.value == proposal.value) + { + state.proposal = Some(proposal.clone()); + prevote(state, proposal.round, proposal.value.id()) + } else { + prevote_nil(state) + } + } + + (Step::Propose, Event::Proposal(proposal)) if this_round && is_valid_pol_round(&state, proposal.pol_round) => { - prevote(state, proposal.pol_round, proposal.value) + // L28 + let Some(locked) = state.locked.as_ref() else { + // TODO: Add logging + return Transition::invalid(state); + }; + + if proposal.value.valid() + && (locked.round <= proposal.pol_round || locked.value == proposal.value) + { + prevote(state, proposal.round, proposal.value.id()) + } else { + prevote_nil(state) + } } (Step::Propose, Event::ProposalInvalid) if this_round => prevote_nil(state), // L22/L25, L28/L31 (Step::Propose, Event::TimeoutPropose) if this_round => prevote_nil(state), // L57 @@ -72,20 +99,22 @@ pub fn apply_event(state: State, round: Round, event: Event) -> Transition { // 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), // L44 - (Step::Prevote, Event::PolkaValue(value)) if this_round => precommit(state, value), // L36/L37 - NOTE: only once? + (Step::Prevote, Event::PolkaValue(value_id)) if this_round => precommit(state, value_id), // L36/L37 - NOTE: only once? (Step::Prevote, Event::TimeoutPrevote) if this_round => precommit_nil(state), // L61 // From Precommit. Event must be for current round. - (Step::Precommit, Event::PolkaValue(value)) if this_round => set_valid_value(state, value), // L36/L42 - NOTE: only once? + (Step::Precommit, Event::PolkaValue(value_id)) if this_round => { + set_valid_value(state, value_id) + } // L36/L42 - NOTE: only once? // From Commit. No more state transitions. (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, round.increment()), // L65 - (_, Event::RoundSkip) if state.round < round => round_skip(state, round), // L55 - (_, Event::PrecommitValue(value)) => commit(state, round, value), // L49 + (_, Event::TimeoutPrecommit) if this_round => round_skip(state, round.increment()), // L65 + (_, Event::RoundSkip) if state.round < round => round_skip(state, round), // L55 + (_, Event::PrecommitValue(value_id)) => commit(state, round, value_id), // L49 // Invalid transition. _ => Transition::invalid(state), @@ -106,7 +135,7 @@ pub fn propose(state: State, value: Value) -> Transition { None => (value, Round::None), }; - let proposal = Message::proposal(state.round, value, pol_round); + let proposal = Message::proposal(state.height, state.round, value, pol_round); Transition::to(state.next_step()).with_message(proposal) } @@ -118,10 +147,10 @@ pub fn propose(state: State, value: Value) -> Transition { /// unless we are locked on something else at a higher round. /// /// Ref: L22/L28 -pub fn prevote(state: State, vr: Round, proposed: Value) -> Transition { +pub fn prevote(state: State, vr: Round, proposed: ValueId) -> Transition { let value = match &state.locked { Some(locked) if locked.round <= vr => Some(proposed), // unlock and prevote - Some(locked) if locked.value == proposed => Some(proposed), // already locked on value + Some(locked) if locked.value.id() == proposed => Some(proposed), // already locked on value Some(_) => None, // we're locked on a higher round with a different value, prevote nil None => Some(proposed), // not locked, prevote the value }; @@ -148,12 +177,19 @@ pub fn prevote_nil(state: State) -> Transition { /// /// NOTE: Only one of this and set_valid_value should be called once in a round /// How do we enforce this? -pub fn precommit(state: State, value: Value) -> Transition { - let message = Message::precommit(state.round, Some(value.clone()), ADDRESS); - let next = state - .set_locked(value.clone()) - .set_valid(value.clone()) - .next_step(); +pub fn precommit(state: State, value_id: ValueId) -> Transition { + let message = Message::precommit(state.round, Some(value_id), ADDRESS); + + let Some(value) = state + .proposal + .as_ref() + .map(|proposal| proposal.value.clone()) + else { + // TODO: Add logging + return Transition::invalid(state); + }; + + let next = state.set_locked(value.clone()).set_valid(value).next_step(); Transition::to(next).with_message(message) } @@ -207,8 +243,20 @@ pub fn schedule_timeout_precommit(state: State) -> Transition { /// Ref: L36/L42 /// /// NOTE: only one of this and precommit should be called once in a round -pub fn set_valid_value(state: State, value: Value) -> Transition { - Transition::to(state.set_valid(value)) +pub fn set_valid_value(state: State, value_id: ValueId) -> Transition { + // 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())) } //--------------------------------------------------------------------- @@ -226,8 +274,19 @@ pub fn round_skip(state: State, round: Round) -> Transition { /// We received +2/3 precommits for a value - commit and decide that value! /// /// Ref: L49 -pub fn commit(state: State, round: Round, value: Value) -> Transition { - let message = Message::decision(round, value); +pub fn commit(state: State, round: Round, value_id: ValueId) -> Transition { + // 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); + } + + let message = Message::decision(round, locked.value.clone()); Transition::to(state.commit_step()).with_message(message) } @@ -240,7 +299,7 @@ mod tests { #[test] fn test_propose() { let value = Value::new(42); - let mut state = State::new(Height::new(1)); + let mut state = State::new(Height::new(10)); let transition = apply_event(state.clone(), Round::new(0), Event::NewRoundProposer(value)); @@ -249,7 +308,7 @@ mod tests { assert_eq!( transition.message.unwrap(), - Message::proposal(Round::new(0), Value::new(42), Round::None) + Message::proposal(Height::new(10), Round::new(0), Value::new(42), Round::None) ); } @@ -274,13 +333,18 @@ mod tests { let transition = apply_event( state, Round::new(1), - Event::Proposal(Proposal::new(Round::new(1), value.clone(), Round::new(0))), + Event::Proposal(Proposal::new( + Height::new(1), + Round::new(1), + value.clone(), + Round::None, + )), ); assert_eq!(transition.state.step, Step::Prevote); assert_eq!( transition.message.unwrap(), - Message::prevote(Round::new(1), Some(value), ADDRESS) + Message::prevote(Round::new(1), Some(value.id()), ADDRESS) ); } } diff --git a/Code/rust-toolchain.toml b/Code/rust-toolchain.toml deleted file mode 100644 index 292fe499e..000000000 --- a/Code/rust-toolchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "stable" diff --git a/Code/vote/src/count.rs b/Code/vote/src/count.rs index 6836ae61f..32e7310f6 100644 --- a/Code/vote/src/count.rs +++ b/Code/vote/src/count.rs @@ -1,14 +1,14 @@ use alloc::collections::BTreeMap; use alloc::sync::Arc; -use malachite_common::{Value, Vote}; +use malachite_common::{ValueId, Vote}; pub type Weight = u64; /// A value and the weight of votes for it. #[derive(Clone, Debug, PartialEq, Eq)] pub struct ValuesWeights { - value_weights: BTreeMap, Weight>, + value_weights: BTreeMap, Weight>, } impl ValuesWeights { @@ -19,14 +19,14 @@ impl ValuesWeights { } /// Add weight to the value and return the new weight. - pub fn add_weight(&mut self, value: Arc, weight: Weight) -> Weight { + pub fn add_weight(&mut self, value: Arc, weight: Weight) -> Weight { let entry = self.value_weights.entry(value).or_insert(0); *entry += weight; *entry } /// Return the value with the highest weight and said weight, if any. - pub fn highest_weighted_value(&self) -> Option<(&Value, Weight)> { + pub fn highest_weighted_value(&self) -> Option<(&ValueId, Weight)> { self.value_weights .iter() .max_by_key(|(_, weight)| *weight) @@ -90,6 +90,14 @@ impl VoteCount { // No quorum Threshold::Init } + pub fn check_threshold(&self, threshold: Threshold) -> bool { + match threshold { + Threshold::Init => false, + Threshold::Any => self.values_weights.highest_weighted_value().is_some(), + Threshold::Nil => self.nil > 0, + Threshold::Value(value) => self.values_weights.value_weights.contains_key(&value), + } + } } //------------------------------------------------------------------------- @@ -106,7 +114,7 @@ pub enum Threshold { /// Quorum for nil Nil, /// Quorum for a value - Value(Arc), + Value(Arc), } /// Returns whether or note `value > (2/3)*total`. @@ -144,8 +152,8 @@ mod tests { #[test] fn add_votes_single_value() { - let v = Value::new(1); - let val = Some(v.clone()); + let v = ValueId::new(1); + let val = Some(v); let total = 4; let weight = 1; @@ -172,10 +180,10 @@ mod tests { #[test] fn add_votes_multi_values() { - let v1 = Value::new(1); - let v2 = Value::new(2); - let val1 = Some(v1.clone()); - let val2 = Some(v2.clone()); + let v1 = ValueId::new(1); + let v2 = ValueId::new(2); + let val1 = Some(v1); + let val2 = Some(v2); let total = 15; let mut round_votes = RoundVotes::new(Height::new(1), Round::new(0), total); diff --git a/Code/vote/src/keeper.rs b/Code/vote/src/keeper.rs index 2f10019da..d24a5b778 100644 --- a/Code/vote/src/keeper.rs +++ b/Code/vote/src/keeper.rs @@ -42,6 +42,23 @@ impl VoteKeeper { Self::to_event(vote_type, threshold) } + pub fn check_threshold( + &self, + round: &Round, + vote_type: VoteType, + threshold: Threshold, + ) -> bool { + let round = match self.rounds.get(round) { + Some(round) => round, + None => return false, + }; + + match vote_type { + VoteType::Prevote => round.prevotes.check_threshold(threshold), + VoteType::Precommit => round.precommits.check_threshold(threshold), + } + } + /// Map a vote type and a threshold to a state machine event. fn to_event(typ: VoteType, threshold: Threshold) -> Option { match (typ, threshold) { @@ -49,20 +66,18 @@ impl VoteKeeper { (VoteType::Prevote, Threshold::Any) => Some(Event::PolkaAny), (VoteType::Prevote, Threshold::Nil) => Some(Event::PolkaNil), - (VoteType::Prevote, Threshold::Value(v)) => Some(Event::PolkaValue(v.as_ref().clone())), + (VoteType::Prevote, Threshold::Value(v)) => Some(Event::PolkaValue(*v.as_ref())), (VoteType::Precommit, Threshold::Any) => Some(Event::PrecommitAny), (VoteType::Precommit, Threshold::Nil) => None, - (VoteType::Precommit, Threshold::Value(v)) => { - Some(Event::PrecommitValue(v.as_ref().clone())) - } + (VoteType::Precommit, Threshold::Value(v)) => Some(Event::PrecommitValue(*v.as_ref())), } } } #[cfg(test)] mod tests { - use malachite_common::{Address, Value}; + use malachite_common::{Address, ValueId}; use super::*; @@ -102,8 +117,8 @@ mod tests { fn prevote_apply_single_value() { let mut keeper = VoteKeeper::new(Height::new(1), Round::INITIAL, 4); - let v = Value::new(1); - let val = Some(v.clone()); + let v = ValueId::new(1); + let val = Some(v); let vote = Vote::new_prevote(Round::new(0), val, Address::new(1)); let event = keeper.apply_vote(vote.clone(), 1); @@ -124,8 +139,8 @@ mod tests { fn precommit_apply_single_value() { let mut keeper = VoteKeeper::new(Height::new(1), Round::INITIAL, 4); - let v = Value::new(1); - let val = Some(v.clone()); + let v = ValueId::new(1); + let val = Some(v); let vote = Vote::new_precommit(Round::new(0), val, Address::new(1)); let event = keeper.apply_vote(vote.clone(), 1); diff --git a/Code/vote/src/lib.rs b/Code/vote/src/lib.rs index 8b9d682a6..b0cd03df0 100644 --- a/Code/vote/src/lib.rs +++ b/Code/vote/src/lib.rs @@ -1,5 +1,15 @@ //! Tally votes of the same type (eg. prevote or precommit) +#![forbid(unsafe_code)] +#![deny(unused_crate_dependencies, trivial_casts, trivial_numeric_casts)] +#![warn( + // missing_docs, + broken_intra_doc_links, + private_intra_doc_links, + variant_size_differences +)] +#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::panic))] + extern crate alloc; pub mod count;