diff options
Diffstat (limited to 'src/remake/vote.rs')
| -rw-r--r-- | src/remake/vote.rs | 33 |
1 files changed, 21 insertions, 12 deletions
diff --git a/src/remake/vote.rs b/src/remake/vote.rs index d73326d..4416293 100644 --- a/src/remake/vote.rs +++ b/src/remake/vote.rs @@ -10,7 +10,9 @@ //! may vote again. //! //! Votes live in the record (`votes/<pk>`, written only by that member). A node votes -//! once it is ready, on its current epoch, and only if it is in the electorate. +//! once it is ready, on its current epoch, only if it is in the electorate, and only +//! once it holds the remake's content (`content`), so a committed remake has its +//! content on a majority. //! //! Gap: votes are not persisted locally. A node whose vote reached only the coordinator //! and that then restarts and resyncs from nodes that never saw the vote may vote again @@ -21,6 +23,7 @@ use std::collections::HashSet; use tracing::{info, warn}; use super::Host; +use super::content; use super::records::{self, EpochRecord, Phase, Record, Vote, unix_millis}; use crate::document::store::StoreError; @@ -147,18 +150,23 @@ pub(super) async fn cast<H: Host>(host: &H) { let (voter, wanted) = (me.clone(), epoch.clone()); let due = host .read_doc(move |doc| { - choice( - &voter, - &wanted, - &records::load_all(doc), - unix_millis(), - grace, - ) - .is_some() + let all = records::load_all(doc); + let id = choice(&voter, &wanted, &all, unix_millis(), grace)?; + let content = all + .into_iter() + .find(|(other, _)| *other == id) + .and_then(|(_, record)| record.proposal.content); + Some((id, content)) }) .await; - if !due { + let Some((chosen, content)) = due else { return; + }; + // Only with the content in hand: a committed remake must have it on a majority. + if let Some(reference) = &content { + if content::available_or_fetch(host, reference).is_none() { + return; + } } // Decided again under the write lock, so two passes can't both vote. @@ -167,7 +175,8 @@ pub(super) async fn cast<H: Host>(host: &H) { .commit_doc(move |doc| -> Result<Option<String>, StoreError> { let now = unix_millis(); let all = records::load_all(doc); - let Some(id) = choice(&voter, &epoch, &all, now, grace) else { + let Some(id) = choice(&voter, &epoch, &all, now, grace).filter(|id| *id == chosen) + else { return Ok(None); }; records::write_vote(doc, &id, &voter, &Vote { at: now })?; @@ -206,7 +215,7 @@ mod tests { electorate: electorate.iter().map(|m| (*m).to_owned()).collect(), quorum: quorum(electorate.len()), kind: "compact".to_owned(), - content: JsonValue::Null, + content: None, translation: JsonValue::Null, }, decision: None, |
