summaryrefslogtreecommitdiffstats
path: root/src/remake
diff options
context:
space:
mode:
Diffstat (limited to 'src/remake')
-rw-r--r--src/remake/vote.rs16
1 files changed, 14 insertions, 2 deletions
diff --git a/src/remake/vote.rs b/src/remake/vote.rs
index 669cab8..b4ba24e 100644
--- a/src/remake/vote.rs
+++ b/src/remake/vote.rs
@@ -24,7 +24,10 @@
//! resyncs from nodes that never saw it. Should it vote again for a competitor, its
//! entry ends up with two concurrent values, and it counts for no remake. To avoid
//! that, a node doesn't vote while more than one undecided remake it could have voted
-//! for is older than its start, and with one, votes only for that one.
+//! for is older than its start, and with one, votes only for that one; and nobody votes
+//! once a remake of the epoch is committed (a lost vote for it must not help a
+//! competitor). This holds as long as a restarted node, once ready, holds the remake
+//! records it held before; a node with `--document-file` keeps its votes too.
use std::collections::HashSet;
use std::time::Duration;
@@ -200,7 +203,9 @@ pub(crate) fn freedom(
}
/// The remake `me` should vote for now, if any: the oldest proposed one of `epoch`
-/// whose electorate includes it, as far as it is free to vote (`freedom`).
+/// whose electorate includes it, as far as it is free to vote (`freedom`). None once a
+/// remake of `epoch` is committed: the epoch is over, and a vote whose predecessor was
+/// lost in a restart must not help a competitor to a second commit.
pub(crate) fn choice(
me: &str,
epoch: &EpochRecord,
@@ -210,6 +215,13 @@ pub(crate) fn choice(
now: UnixMillis,
grace: u64,
) -> Option<String> {
+ let committed = all.iter().any(|(_, record)| {
+ &record.proposal.from_epoch == epoch
+ && matches!(record.decision, Some(Decision::Committed { .. }))
+ });
+ if committed {
+ return None;
+ }
let only = match freedom(me, epoch, all, register, started) {
Freedom::Any => None,
Freedom::Only(id) => Some(id),