diff options
Diffstat (limited to 'src/remake/vote.rs')
| -rw-r--r-- | src/remake/vote.rs | 16 |
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), |
