summaryrefslogtreecommitdiffstats
path: root/src/remake/vote.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/remake/vote.rs')
-rw-r--r--src/remake/vote.rs33
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,