Skip to main content

quorum_set/progress/
vec_progress.rs

1use std::collections::BTreeMap;
2use std::collections::BTreeSet;
3use std::error::Error;
4use std::fmt::Debug;
5use std::fmt::Display;
6use std::fmt::Formatter;
7use std::slice::Iter;
8use std::slice::IterMut;
9
10use validit::Validate;
11
12use super::VecProgressEntry;
13use super::VecProgressEntryData;
14use super::display_vec_progress::DisplayVecProgress;
15use super::progress_stats::ProgressStats;
16use crate::quorum::QuorumSet;
17
18/// Tracks per-node progress and the greatest value accepted by a quorum.
19///
20/// `Entry` stores a node ID, an ordered progress value, and optional
21/// application-owned data. `QS` decides which node IDs constitute a quorum. In
22/// Raft terms, this is a compact map from node ID to replicated log ID plus any
23/// follower state the application keeps beside it.
24///
25/// Internally this type uses a vector and keeps only the voter prefix above the
26/// current quorum-accepted value sorted. Normal updates may only keep or
27/// increase progress; explicit resets may move an entry backward without
28/// lowering the recorded quorum-accepted value. This makes the type a good fit
29/// for small consensus memberships.
30#[derive(Clone, Debug)]
31pub struct VecProgress<Entry, QS>
32where
33    Entry: VecProgressEntry,
34    QS: QuorumSet<Id = Entry::Id>,
35{
36    /// Quorum set used to decide whether candidate IDs constitute a quorum.
37    quorum_set: QS,
38
39    /// The greatest value accepted by a quorum.
40    quorum_accepted: Entry::Progress,
41
42    /// Number of voter entries.
43    voter_count: usize,
44
45    /// Progress data.
46    ///
47    /// Elements with values greater than `quorum_accepted` are sorted in descending order.
48    /// Others are unsorted.
49    ///
50    /// The first `voter_count` entries are voters; the rest are learners.
51    /// Learners are not reordered by progress updates.
52    /// Voters may move within the voter range to maintain the sorted prefix.
53    entries: Vec<Entry>,
54
55    /// Statistics of how it runs.
56    stat: ProgressStats,
57}
58
59impl<Entry, QS> Display for VecProgress<Entry, QS>
60where
61    Entry: VecProgressEntry + Display,
62    QS: QuorumSet<Id = Entry::Id> + 'static,
63{
64    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
65        write!(f, "{{")?;
66        for (i, item) in self.entries.iter().enumerate() {
67            if i > 0 {
68                write!(f, ", ")?;
69            }
70            write!(f, "{}", item)?
71        }
72        write!(f, "}}")?;
73
74        Ok(())
75    }
76}
77
78impl<Entry, QS> VecProgress<Entry, QS>
79where
80    Entry: VecProgressEntry,
81    Entry::Id: Ord + Clone + Debug,
82    Entry::Progress: Debug,
83    QS: QuorumSet<Id = Entry::Id>,
84{
85    /// Create a progress tracker from a quorum set and learner IDs.
86    ///
87    /// Voters are created from `quorum_set.ids()`. Learners are tracked after
88    /// voters and never contribute to quorum acceptance. `default_entry` builds
89    /// the initial entry for every voter and learner ID.
90    pub fn new(
91        quorum_set: QS,
92        learner_ids: impl IntoIterator<Item = Entry::Id>,
93        mut default_entry: impl FnMut(Entry::Id) -> Entry,
94    ) -> Self {
95        let mut entries = quorum_set.ids().map(&mut default_entry).collect::<Vec<_>>();
96
97        let voter_count = entries.len();
98
99        entries.extend(learner_ids.into_iter().map(default_entry));
100
101        let this = Self {
102            quorum_set,
103            quorum_accepted: Default::default(),
104            voter_count,
105            entries,
106            stat: Default::default(),
107        };
108        this.validate_initial_state().expect("VecProgress construction invariant violation");
109        this
110    }
111
112    /// Find the index of the specified id.
113    #[inline(always)]
114    fn index(&self, target: &Entry::Id) -> Option<usize> {
115        self.entries.iter().position(|item| item.id() == target)
116    }
117
118    /// Move an element at `index` up so that voters stay sorted.
119    #[inline(always)]
120    fn move_up(&mut self, index: usize) -> usize {
121        self.stat.move_count += 1;
122        for i in (0..index).rev() {
123            if self.entries[i].progress() < self.entries[i + 1].progress() {
124                self.entries.swap(i, i + 1);
125            } else {
126                return i + 1;
127            }
128        }
129
130        0
131    }
132
133    /// Move a voter element at `index` down so that voters stay sorted
134    /// after its progress value is lowered.
135    ///
136    /// It is the counterpart of [`Self::move_up`], used by [`Self::reset_entry_with()`].
137    fn move_down(&mut self, index: usize) -> usize {
138        self.stat.move_count += 1;
139        let mut i = index;
140        while i + 1 < self.voter_count
141            && self.entries[i].progress() < self.entries[i + 1].progress()
142        {
143            self.entries.swap(i, i + 1);
144            i += 1;
145        }
146
147        i
148    }
149
150    /// Return mutable entries without maintaining the progress ordering.
151    ///
152    /// Mutating progress values through this iterator can leave the internal
153    /// ordering and quorum-accepted value stale. Normal progress updates must
154    /// use [`Self::update_progress()`] or [`Self::update_entry_with()`] instead.
155    /// Mutating entry IDs can corrupt membership lookup.
156    pub fn iter_mut_without_reorder(&mut self) -> IterMut<'_, Entry> {
157        self.entries.iter_mut()
158    }
159
160    #[cfg(test)]
161    pub(crate) fn stat(&self) -> &ProgressStats {
162        &self.stat
163    }
164
165    /// Return a display adapter that formats entries with a caller-provided formatter.
166    pub fn display_with<Fmt>(&self, f: Fmt) -> DisplayVecProgress<'_, Entry, QS, Fmt>
167    where Fmt: Fn(&mut Formatter<'_>, &Entry) -> std::fmt::Result {
168        DisplayVecProgress { inner: self, f }
169    }
170
171    /// Validates progress-update invariants in debug builds.
172    fn debug_assert_progress_valid(&self) {
173        #[cfg(debug_assertions)]
174        self.validate().expect("VecProgress progress invariant violation");
175    }
176}
177
178impl<Entry, QS> VecProgress<Entry, QS>
179where
180    Entry: VecProgressEntry,
181    Entry::Id: Ord + Clone + Debug,
182    Entry::Progress: Debug,
183    QS: QuorumSet<Id = Entry::Id>,
184{
185    /// Update one progress value monotonically and recalculate the quorum-accepted value.
186    ///
187    /// It returns `None` if the `id` is not found.
188    /// Otherwise, it returns the current quorum-accepted value.
189    /// Updating with the same value leaves the state unchanged.
190    ///
191    /// # Algorithm
192    ///
193    /// Only one case can increase the quorum-accepted value: the **previous value**
194    /// is less than or equal to the current quorum-accepted value, and the **new
195    /// value** is greater than it.
196    ///
197    /// This avoids many unnecessary quorum recalculations and sorts. Progress
198    /// entries above the quorum-accepted value are kept in descending order, and
199    /// entries at or below it do not need to be sorted.
200    ///
201    /// E.g., given 3 ids with values `1,3,5`, as shown in the figure below:
202    ///
203    /// ```text
204    /// a -----------+-------->
205    /// b -------+------------>
206    /// c ---+---------------->
207    /// ------------------------------
208    ///      1   3   5
209    /// ```
210    ///
211    /// the quorum-accepted is `3` and assumes a majority quorum set is used.
212    /// Then:
213    /// - update_progress(a, 6): nothing to do: quorum-accepted is still 3;
214    /// - update_progress(b, 4): re-calc:       quorum-accepted becomes 4;
215    /// - update_progress(b, 6): re-calc:       quorum-accepted becomes 5;
216    /// - update_progress(c, 2): nothing to do: quorum-accepted is still 3;
217    /// - update_progress(c, 3): nothing to do: quorum-accepted is still 3;
218    /// - update_progress(c, 4): re-calc:       quorum-accepted becomes 4;
219    /// - update_progress(c, 6): re-calc:       quorum-accepted becomes 5;
220    fn update_progress_with<F>(&mut self, id: &Entry::Id, f: F) -> Option<&Entry::Progress>
221    where F: FnOnce(&mut Entry::Progress) {
222        self.update_entry_with(id, |entry| f(entry.progress_mut()))
223    }
224
225    /// Update an entry and recalculate the quorum-accepted value.
226    ///
227    /// Use this when application-owned fields must change together with the
228    /// progress value. The progress update must not lower progress, and the
229    /// entry ID must not change.
230    ///
231    /// It returns `None` if the `id` is not found.
232    /// Otherwise, it returns the current quorum-accepted value.
233    pub fn update_entry_with<F>(&mut self, id: &Entry::Id, f: F) -> Option<&Entry::Progress>
234    where F: FnOnce(&mut Entry) {
235        self.stat.update_count += 1;
236
237        let index = self.index(id)?;
238
239        let prev_progress = self.entries[index].progress().clone();
240
241        f(&mut self.entries[index]);
242
243        debug_assert!(self.entries[index].id() == id);
244
245        Some(self.update_at(index, prev_progress))
246    }
247
248    /// Update application-owned data without recalculating quorum-accepted progress.
249    ///
250    /// This method only exposes [`VecProgressEntryData::Data`], so it cannot
251    /// change progress or invalidate the ordering maintained by [`VecProgress`].
252    ///
253    /// Returns the updated data when `id` is found, otherwise returns `None`.
254    pub fn update_data_with<F>(&mut self, id: &Entry::Id, f: F) -> Option<&Entry::Data>
255    where
256        Entry: VecProgressEntryData,
257        F: FnOnce(&mut Entry::Data),
258    {
259        let index = self.index(id)?;
260
261        f(self.entries[index].data_mut());
262
263        Some(self.entries[index].data())
264    }
265
266    /// Update an entry whose progress value may move backward, for example when
267    /// replication progress is reset upon log reversion.
268    ///
269    /// If the progress value is lowered, the entry is moved down to keep the
270    /// values greater than `quorum_accepted` sorted. The recorded
271    /// quorum-accepted value is deliberately not recalculated: a value accepted
272    /// by a quorum must never be withdrawn.
273    /// The entry ID must not be changed.
274    ///
275    /// It returns the updated entry if the `id` is found, otherwise returns `None`.
276    pub fn reset_entry_with<F>(&mut self, id: &Entry::Id, f: F) -> Option<&Entry>
277    where F: FnOnce(&mut Entry) {
278        let index = self.index(id)?;
279
280        let prev_progress = self.entries[index].progress().clone();
281
282        f(&mut self.entries[index]);
283
284        debug_assert!(self.entries[index].id() == id);
285        debug_assert!(self.entries[index].progress() <= &prev_progress);
286
287        // Learners are never reordered.
288        let new_index =
289            if index < self.voter_count && self.entries[index].progress() < &prev_progress {
290                self.move_down(index)
291            } else {
292                index
293            };
294
295        self.debug_assert_progress_valid();
296        Some(&self.entries[new_index])
297    }
298
299    fn update_at(&mut self, index: usize, prev_progress: Entry::Progress) -> &Entry::Progress {
300        debug_assert!(self.entries[index].progress() >= &prev_progress,);
301
302        // No change, return early
303        if &prev_progress == self.entries[index].progress() {
304            self.debug_assert_progress_valid();
305            return &self.quorum_accepted;
306        }
307
308        // Learner does not grant a value.
309        // And it won't be moved up to adjust the order.
310        if index >= self.voter_count {
311            self.debug_assert_progress_valid();
312            return &self.quorum_accepted;
313        }
314
315        let prev_le_qa = prev_progress <= self.quorum_accepted;
316        let new_gt_qa = self.entries[index].progress() > &self.quorum_accepted;
317
318        // Sort and find the greatest value accepted by a quorum set.
319
320        if new_gt_qa {
321            let new_index = self.move_up(index);
322
323            if prev_le_qa {
324                // From high to low, find the max value that has constituted a quorum.
325                for i in new_index..self.voter_count {
326                    let prog = self.entries[i].progress();
327
328                    // No need to recalculate an already quorum-accepted value.
329                    if prog <= &self.quorum_accepted {
330                        break;
331                    }
332
333                    // Ids of the target that has value GE `entries[i]`
334                    let it = self.entries[0..=i].iter().map(|item| item.id());
335
336                    self.stat.is_quorum_count += 1;
337
338                    if self.quorum_set.is_quorum(it) {
339                        self.quorum_accepted = prog.clone();
340                        break;
341                    }
342                }
343            }
344        }
345
346        self.debug_assert_progress_valid();
347        &self.quorum_accepted
348    }
349
350    /// Set one node's progress and recalculate the quorum-accepted value.
351    ///
352    /// The new value must be greater than or equal to the current progress. Use
353    /// [`Self::reset_entry_with()`] for an explicit backward move.
354    ///
355    /// It returns `None` if the `id` is not found.
356    /// Otherwise, it returns the current quorum-accepted value.
357    pub fn update_progress(
358        &mut self,
359        id: &Entry::Id,
360        value: Entry::Progress,
361    ) -> Option<&Entry::Progress> {
362        self.update_progress_with(id, |x| *x = value)
363    }
364
365    /// Increase one node's progress if `value` is greater than its current value.
366    ///
367    /// It returns `None` if the `id` is not found.
368    /// Otherwise, it returns the current quorum-accepted value.
369    pub fn increase_to(
370        &mut self,
371        id: &Entry::Id,
372        value: Entry::Progress,
373    ) -> Option<&Entry::Progress> {
374        self.update_progress_with(id, |x| {
375            if value > *x {
376                *x = value;
377            }
378        })
379    }
380
381    /// Return the tracked entry for `id`.
382    pub fn get(&self, id: &Entry::Id) -> Option<&Entry> {
383        let index = self.index(id)?;
384        Some(&self.entries[index])
385    }
386
387    /// Return the greatest progress value accepted by the quorum set.
388    ///
389    /// In Raft, this is the replication progress reached by enough voters to be
390    /// considered committed once the term-specific commit rule also allows it.
391    pub fn quorum_accepted(&self) -> &Entry::Progress {
392        &self.quorum_accepted
393    }
394
395    /// Iterate over all entries, with voters first and learners after them.
396    pub fn iter(&self) -> Iter<'_, Entry> {
397        self.entries.as_slice().iter()
398    }
399
400    /// Map every entry and collect the mapped values.
401    pub fn collect_mapped<F, T, C>(&self, f: F) -> C
402    where
403        F: Fn(&Entry) -> T,
404        C: FromIterator<T>,
405    {
406        self.iter().map(f).collect()
407    }
408
409    /// Build a tracker for a new quorum set while preserving progress for shared IDs.
410    ///
411    /// Entries whose IDs still exist in the new voter or learner set keep their
412    /// previous progress and application data. New IDs are initialized through
413    /// `default_entry`.
414    pub fn upgrade_quorum_set(
415        self,
416        quorum_set: QS,
417        learner_ids: impl IntoIterator<Item = Entry::Id>,
418        default_entry: impl FnMut(Entry::Id) -> Entry,
419    ) -> Self {
420        let mut new_prog = Self::new(quorum_set, learner_ids, default_entry);
421
422        new_prog.stat = self.stat.clone();
423
424        for item in self.into_iter() {
425            new_prog.replace(item);
426        }
427        new_prog.debug_assert_progress_valid();
428        new_prog
429    }
430
431    /// Replace the entry for the same ID and update quorum-accepted progress.
432    fn replace(&mut self, entry: Entry) -> Option<&Entry::Progress> {
433        self.stat.update_count += 1;
434
435        let index = self.index(entry.id())?;
436
437        let prev_progress = self.entries[index].progress().clone();
438
439        self.entries[index] = entry;
440
441        Some(self.update_at(index, prev_progress))
442    }
443
444    /// Return whether the given ID is a voter.
445    ///
446    /// A voter is a node in the quorum set that can grant a value.
447    /// A learner's progress is also tracked, but it will never grant a value.
448    ///
449    /// If the given id is not in this [`VecProgress`], it returns `None`.
450    pub fn is_voter(&self, id: &Entry::Id) -> Option<bool> {
451        let index = self.index(id)?;
452        Some(index < self.voter_count)
453    }
454}
455
456impl<Entry, QS> IntoIterator for VecProgress<Entry, QS>
457where
458    Entry: VecProgressEntry,
459    QS: QuorumSet<Id = Entry::Id>,
460{
461    type Item = Entry;
462    type IntoIter = std::vec::IntoIter<Entry>;
463
464    fn into_iter(self) -> Self::IntoIter {
465        self.entries.into_iter()
466    }
467}
468
469impl<Entry, QS> Validate for VecProgress<Entry, QS>
470where
471    Entry: VecProgressEntry,
472    Entry::Id: Ord + Clone + Debug,
473    Entry::Progress: Debug,
474    QS: QuorumSet<Id = Entry::Id>,
475{
476    /// Validates the voter-order invariant maintained after progress updates.
477    fn validate(&self) -> Result<(), Box<dyn Error>> {
478        self.validate_progress()
479    }
480}
481
482impl<Entry, QS> VecProgress<Entry, QS>
483where
484    Entry: VecProgressEntry,
485    Entry::Id: Ord + Clone + Debug,
486    Entry::Progress: Debug,
487    QS: QuorumSet<Id = Entry::Id>,
488{
489    /// Validates all construction-time invariants for a new [`VecProgress`].
490    fn validate_initial_state(&self) -> Result<(), Box<dyn Error>> {
491        self.validate_voter_count()?;
492        self.validate_unique_entry_ids()?;
493        self.validate_quorum_membership()?;
494        self.validate_progress()?;
495
496        Ok(())
497    }
498
499    /// Validates the voter-order invariant affected by progress updates.
500    fn validate_progress(&self) -> Result<(), Box<dyn Error>> {
501        self.validate_voter_order()
502    }
503
504    /// Validates that `voter_count` partitions `entries` into a valid voter
505    /// prefix and learner suffix.
506    fn validate_voter_count(&self) -> Result<(), Box<dyn Error>> {
507        if self.voter_count > self.entries.len() {
508            return Err(invalid(format!(
509                "voter_count {} exceeds entry count {}",
510                self.voter_count,
511                self.entries.len()
512            )));
513        }
514
515        Ok(())
516    }
517
518    /// Validates that every tracked entry ID is unique across both voters and
519    /// learners.
520    fn validate_unique_entry_ids(&self) -> Result<(), Box<dyn Error>> {
521        let mut positions = BTreeMap::<Entry::Id, Vec<usize>>::new();
522        for (i, entry) in self.entries.iter().enumerate() {
523            positions.entry(entry.id().clone()).or_default().push(i);
524        }
525
526        let duplicates = positions
527            .into_iter()
528            .filter(|(_, positions)| positions.len() > 1)
529            .collect::<BTreeMap<_, _>>();
530
531        if !duplicates.is_empty() {
532            return Err(invalid(format!("duplicate entry ids: {duplicates:?}")));
533        }
534
535        Ok(())
536    }
537
538    /// Validates that the quorum-set IDs exactly match the voter-entry IDs and
539    /// do not appear in the learner suffix.
540    fn validate_quorum_membership(&self) -> Result<(), Box<dyn Error>> {
541        let quorum_ids = self.quorum_set.ids().collect::<BTreeSet<_>>();
542        let voter_entry_ids = self.entries[..self.voter_count]
543            .iter()
544            .map(|entry| entry.id().clone())
545            .collect::<BTreeSet<_>>();
546        let learner_entry_ids = self.entries[self.voter_count..]
547            .iter()
548            .map(|entry| entry.id().clone())
549            .collect::<BTreeSet<_>>();
550
551        let missing_voter_ids =
552            quorum_ids.difference(&voter_entry_ids).cloned().collect::<BTreeSet<_>>();
553        let extra_voter_ids =
554            voter_entry_ids.difference(&quorum_ids).cloned().collect::<BTreeSet<_>>();
555        let learner_voter_ids =
556            learner_entry_ids.intersection(&quorum_ids).cloned().collect::<BTreeSet<_>>();
557
558        if quorum_ids.len() == self.voter_count
559            && missing_voter_ids.is_empty()
560            && extra_voter_ids.is_empty()
561            && learner_voter_ids.is_empty()
562        {
563            return Ok(());
564        }
565
566        Err(invalid(format!(
567            "quorum membership mismatch: quorum_ids={quorum_ids:?}, voter_entry_ids={voter_entry_ids:?}, learner_entry_ids={learner_entry_ids:?}, missing_voter_ids={missing_voter_ids:?}, extra_voter_ids={extra_voter_ids:?}, learner_voter_ids={learner_voter_ids:?}, voter_count={}",
568            self.voter_count
569        )))
570    }
571
572    /// Validates that voter entries whose progress is greater than
573    /// `quorum_accepted` form a descending prefix, reporting the first
574    /// out-of-order entry with the current voter and learner progress state.
575    fn validate_voter_order(&self) -> Result<(), Box<dyn Error>> {
576        let voters = &self.entries[..self.voter_count];
577        let progress_state = || {
578            let voter_progress = voters
579                .iter()
580                .map(|entry| (entry.id().clone(), entry.progress().clone()))
581                .collect::<Vec<_>>();
582            let learner_progress = self.entries[self.voter_count..]
583                .iter()
584                .map(|entry| (entry.id().clone(), entry.progress().clone()))
585                .collect::<Vec<_>>();
586
587            (voter_progress, learner_progress)
588        };
589
590        let suffix_start = voters
591            .iter()
592            .position(|entry| entry.progress() <= &self.quorum_accepted)
593            .unwrap_or(voters.len());
594
595        for (previous_index, pair) in voters[..suffix_start].windows(2).enumerate() {
596            let previous = &pair[0];
597            let item = &pair[1];
598            if previous.progress() < item.progress() {
599                let (voter_progress, learner_progress) = progress_state();
600                return Err(invalid(format!(
601                    "voter progress above quorum_accepted is not descending: quorum_accepted={:?}, previous_entry={:?}, out_of_order_entry={:?}, voter_progress={voter_progress:?}, learner_progress={learner_progress:?}",
602                    self.quorum_accepted,
603                    (previous_index, previous.id(), previous.progress()),
604                    (previous_index + 1, item.id(), item.progress())
605                )));
606            }
607        }
608
609        for (suffix_offset, item) in voters[suffix_start..].iter().enumerate() {
610            if item.progress() <= &self.quorum_accepted {
611                continue;
612            }
613
614            let index = suffix_start + suffix_offset;
615            let (voter_progress, learner_progress) = progress_state();
616            return Err(invalid(format!(
617                "voter progress above quorum_accepted appears after the unsorted suffix: quorum_accepted={:?}, out_of_order_entry={:?}, voter_progress={voter_progress:?}, learner_progress={learner_progress:?}",
618                self.quorum_accepted,
619                (index, item.id(), item.progress())
620            )));
621        }
622
623        Ok(())
624    }
625}
626
627fn invalid(message: impl Into<String>) -> Box<dyn Error> {
628    Box::new(std::io::Error::new(
629        std::io::ErrorKind::InvalidData,
630        message.into(),
631    ))
632}
633
634#[cfg(test)]
635mod tests {
636    use std::collections::BTreeMap;
637    use std::collections::BTreeSet;
638
639    use maplit::btreeset;
640    use validit::Validate;
641
642    use super::VecProgress;
643    use crate::Node;
644    use crate::QuorumTree;
645    use crate::progress::VecProgressEntry;
646    use crate::progress::VecProgressEntryData;
647    use crate::quorum::QuorumSet;
648
649    const LCG_A: u64 = 6364136223846793005;
650    const LCG_C: u64 = 1442695040888963407;
651
652    #[derive(Clone, Debug, PartialEq, Eq)]
653    struct IdValData<ID, Val, Data> {
654        id: ID,
655        val: Val,
656        data: Data,
657    }
658
659    impl<ID, Val, Data> IdValData<ID, Val, Data> {
660        fn new(id: ID, val: Val, data: Data) -> Self {
661            Self { id, val, data }
662        }
663    }
664
665    impl<ID, Val, Data> VecProgressEntry for IdValData<ID, Val, Data>
666    where
667        ID: 'static + PartialEq,
668        Val: Clone + Default + Ord,
669    {
670        type Id = ID;
671        type Progress = Val;
672
673        fn id(&self) -> &Self::Id {
674            &self.id
675        }
676
677        fn progress(&self) -> &Self::Progress {
678            &self.val
679        }
680
681        fn progress_mut(&mut self) -> &mut Self::Progress {
682            &mut self.val
683        }
684    }
685
686    impl<ID, Val, Data> VecProgressEntryData for IdValData<ID, Val, Data>
687    where
688        ID: 'static + PartialEq,
689        Val: Clone + Default + Ord,
690    {
691        type Data = Data;
692
693        fn data(&self) -> &Self::Data {
694            &self.data
695        }
696
697        fn data_mut(&mut self) -> &mut Self::Data {
698            &mut self.data
699        }
700    }
701
702    #[derive(Clone, Debug)]
703    struct RequiredSetQuorum {
704        ids: BTreeSet<u64>,
705        required: BTreeSet<u64>,
706    }
707
708    impl RequiredSetQuorum {
709        /// Build a quorum set that grants only when every required ID is present.
710        ///
711        /// This intentionally does not implement majority semantics, so it can
712        /// catch accidental assumptions in `VecProgress`.
713        fn new(
714            ids: impl IntoIterator<Item = u64>,
715            required: impl IntoIterator<Item = u64>,
716        ) -> Self {
717            Self {
718                ids: ids.into_iter().collect(),
719                required: required.into_iter().collect(),
720            }
721        }
722    }
723
724    impl QuorumSet for RequiredSetQuorum {
725        type Id = u64;
726
727        type Iter = std::collections::btree_set::IntoIter<u64>;
728
729        fn is_quorum<'a, I: Iterator<Item = &'a Self::Id> + Clone>(&self, ids: I) -> bool {
730            let granted = ids.copied().collect::<BTreeSet<_>>();
731            self.required.is_subset(&granted)
732        }
733
734        fn ids(&self) -> Self::Iter {
735            self.ids.clone().into_iter()
736        }
737    }
738
739    /// Advance a deterministic pseudo-random sequence for model-based tests.
740    ///
741    /// The generator is intentionally tiny and reproducible; it is not used for
742    /// randomness quality, only for broadening the monotonic update cases.
743    fn next_random(seed: &mut u64) -> u64 {
744        *seed = seed.wrapping_mul(LCG_A).wrapping_add(LCG_C);
745        *seed
746    }
747
748    /// Build learner IDs as the complement of the current quorum IDs.
749    ///
750    /// Randomized upgrade tests use this to preserve progress for all known
751    /// nodes when switching between simple, joint, and shrunk quorum sets.
752    fn learner_ids_for<QS>(quorum_set: &QS, known_ids: impl IntoIterator<Item = u64>) -> Vec<u64>
753    where QS: QuorumSet<Id = u64> {
754        let voter_ids = quorum_set.ids().collect::<BTreeSet<_>>();
755        known_ids.into_iter().filter(|id| !voter_ids.contains(id)).collect()
756    }
757
758    /// Copy the `Option<&u64>` returned by progress updates.
759    fn copy_option(res: Option<&u64>) -> Option<u64> {
760        res.copied()
761    }
762
763    /// Compute quorum-accepted progress with a straightforward reference model.
764    ///
765    /// This intentionally ignores `VecProgress`'s internal ordering optimization:
766    /// it tries candidate progress values from high to low and returns the first
767    /// value whose reached node IDs form a quorum.
768    fn model_quorum_accepted<QS>(quorum_set: &QS, entries: &[(u64, u64)]) -> u64
769    where QS: QuorumSet<Id = u64> {
770        let values = entries.iter().map(|item| (item.0, item.1)).collect::<BTreeMap<_, _>>();
771        let mut candidates = quorum_set.ids().map(|id| values[&id]).collect::<Vec<_>>();
772
773        candidates.sort_unstable_by(|a, b| b.cmp(a));
774        candidates.dedup();
775
776        for candidate in candidates {
777            let ids = values.iter().filter_map(|(id, val)| (*val >= candidate).then_some(id));
778            if quorum_set.is_quorum(ids) {
779                return candidate;
780            }
781        }
782
783        0
784    }
785
786    /// Assert that `VecProgress` agrees with the reference model.
787    ///
788    /// This checks both the externally visible quorum-accepted value and the
789    /// internal ordering invariant relied on by the optimized update algorithm.
790    fn assert_matches_model<QS>(progress: &VecProgress<(u64, u64), QS>, context: &str)
791    where QS: QuorumSet<Id = u64> {
792        let want = model_quorum_accepted(&progress.quorum_set, &progress.entries);
793        assert_eq!(
794            &want,
795            progress.quorum_accepted(),
796            "{}: entries: {:?}",
797            context,
798            progress.entries
799        );
800        assert_voter_prefix_is_sorted(progress, context);
801    }
802
803    /// Assert the voter ordering invariant maintained by `update_progress()`.
804    ///
805    /// Voter entries above `quorum_accepted` must form a descending prefix.
806    /// Learners are excluded because learner progress does not grant quorum.
807    fn assert_voter_prefix_is_sorted<QS>(progress: &VecProgress<(u64, u64), QS>, context: &str)
808    where QS: QuorumSet<Id = u64> {
809        let quorum_accepted = *progress.quorum_accepted();
810        let mut previous = None;
811        let mut seen_unsorted_suffix = false;
812
813        for item in &progress.entries[..progress.voter_count] {
814            if item.1 <= quorum_accepted {
815                seen_unsorted_suffix = true;
816                continue;
817            }
818
819            assert!(
820                !seen_unsorted_suffix,
821                "{}: non-prefix above-quorum entry: {:?}",
822                context, progress.entries
823            );
824            if let Some(prev) = previous {
825                assert!(
826                    prev >= item.1,
827                    "{}: unsorted voters: {:?}",
828                    context,
829                    progress.entries
830                );
831            }
832            previous = Some(item.1);
833        }
834    }
835
836    fn assert_initial_invalid_contains<QS>(progress: &VecProgress<(u64, u64), QS>, want: &str)
837    where QS: QuorumSet<Id = u64> {
838        let err = progress.validate_initial_state().unwrap_err().to_string();
839        assert!(err.contains(want), "error: {err}");
840    }
841
842    fn assert_err_contains(err: Box<dyn std::error::Error>, want: &str) {
843        let err = err.to_string();
844        assert!(err.contains(want), "error: {err}");
845    }
846
847    #[test]
848    fn vec_progress_new() {
849        let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
850        let progress = VecProgress::<(u64, u64), _>::new(quorum_set, [6, 7], |id| (id, 0));
851
852        assert_eq!(
853            vec![(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (6, 0), (7, 0),],
854            progress.entries
855        );
856        assert_eq!(5, progress.voter_count);
857    }
858
859    #[test]
860    fn vec_progress_new_with_tree() {
861        let group_a = QuorumTree::new(2, [Node::Id(1), Node::Id(2), Node::Id(3)]).unwrap();
862        let group_b = QuorumTree::new(2, [Node::Id(4), Node::Id(5), Node::Id(6)]).unwrap();
863        let quorum_set =
864            QuorumTree::new(2, [Node::Subtree(group_a), Node::Subtree(group_b)]).unwrap();
865        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [], |id| (id, 0));
866
867        assert_eq!(
868            vec![(1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (6, 0)],
869            progress.entries
870        );
871        assert_eq!(6, progress.voter_count);
872
873        assert_eq!(Some(&0), progress.update_progress(&1, 10));
874        assert_eq!(Some(&0), progress.update_progress(&2, 10));
875        assert_eq!(Some(&0), progress.update_progress(&4, 10));
876        assert_eq!(Some(&10), progress.update_progress(&5, 10));
877    }
878
879    #[test]
880    fn vec_progress_validate_rejects_duplicate_entry_ids() {
881        let quorum_set = vec![btreeset! {0, 1, 2}];
882        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [3], |id| (id, 0));
883
884        progress.entries[3].0 = 1;
885
886        assert!(progress.validate().is_ok());
887        assert_initial_invalid_contains(&progress, "duplicate entry id");
888        assert_initial_invalid_contains(&progress, "1: [1, 3]");
889    }
890
891    #[test]
892    fn vec_progress_validate_reports_membership_mismatches() {
893        let quorum_set = vec![btreeset! {0, 1, 2}];
894        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [3, 4], |id| (id, 0));
895
896        progress.entries[0].0 = 9;
897        progress.entries[3].0 = 2;
898
899        let err = progress.validate_quorum_membership().unwrap_err();
900        assert_err_contains(err, "missing_voter_ids={0}");
901
902        let err = progress.validate_quorum_membership().unwrap_err();
903        assert_err_contains(err, "extra_voter_ids={9}");
904
905        let err = progress.validate_quorum_membership().unwrap_err();
906        assert_err_contains(err, "learner_voter_ids={2}");
907    }
908
909    #[test]
910    fn vec_progress_validate_reports_voter_order_mismatches() {
911        let quorum_set = vec![btreeset! {0, 1, 2}];
912        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [3], |id| (id, 0));
913
914        progress.quorum_accepted = 4;
915        progress.entries[0].1 = 7;
916        progress.entries[1].1 = 3;
917        progress.entries[2].1 = 6;
918        progress.entries[3].1 = 9;
919
920        let err = progress.validate_voter_order().unwrap_err().to_string();
921        assert!(
922            err.contains("appears after the unsorted suffix"),
923            "error: {err}"
924        );
925        assert!(err.contains("out_of_order_entry=(2, 2, 6)"), "error: {err}");
926        assert!(
927            err.contains("voter_progress=[(0, 7), (1, 3), (2, 6)]"),
928            "error: {err}"
929        );
930        assert!(err.contains("learner_progress=[(3, 9)]"), "error: {err}");
931
932        let quorum_set = vec![btreeset! {0, 1, 2}];
933        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [3], |id| (id, 0));
934
935        progress.quorum_accepted = 4;
936        progress.entries[0].1 = 5;
937        progress.entries[1].1 = 6;
938        progress.entries[3].1 = 9;
939
940        let err = progress.validate_voter_order().unwrap_err().to_string();
941        assert!(err.contains("previous_entry=(0, 0, 5)"), "error: {err}");
942        assert!(err.contains("out_of_order_entry=(1, 1, 6)"), "error: {err}");
943        assert!(
944            err.contains("voter_progress=[(0, 5), (1, 6), (2, 0)]"),
945            "error: {err}"
946        );
947        assert!(err.contains("learner_progress=[(3, 9)]"), "error: {err}");
948    }
949
950    #[test]
951    fn vec_progress_validate_accepts_reset_below_quorum_accepted() {
952        let quorum_set = vec![btreeset! {0, 1, 2}];
953        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [], |id| (id, 0));
954
955        progress.update_progress(&0, 10).unwrap();
956        progress.update_progress(&1, 10).unwrap();
957        progress.reset_entry_with(&0, |entry| entry.1 = 0).unwrap();
958
959        assert!(progress.validate().is_ok());
960    }
961
962    #[test]
963    fn vec_progress_tuple_entry() {
964        let quorum_set = vec![btreeset! {0, 1, 2}];
965        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [3], |id| (id, 0));
966
967        assert_eq!(
968            vec![(0, 0), (1, 0), (2, 0), (3, 0)],
969            progress.iter().cloned().collect::<Vec<_>>()
970        );
971        assert_eq!(Some(&0), progress.update_progress(&0, 5));
972        assert_eq!(Some(&5), progress.update_progress(&1, 5));
973        assert_eq!(Some(&(0, 5)), progress.get(&0));
974        assert_eq!(Some(&(1, 5)), progress.get(&1));
975        assert_eq!(&5, progress.quorum_accepted());
976    }
977
978    #[test]
979    fn vec_progress_index() {
980        let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
981        let progress = VecProgress::<(u64, u64), _>::new(quorum_set, [6, 7], |id| (id, 0));
982
983        assert_eq!(Some(0), progress.index(&0));
984        assert_eq!(Some(1), progress.index(&1));
985        assert_eq!(Some(4), progress.index(&4));
986        assert_eq!(Some(5), progress.index(&6));
987        assert_eq!(Some(6), progress.index(&7));
988        assert_eq!(None, progress.index(&9));
989        assert_eq!(None, progress.index(&100));
990    }
991
992    #[test]
993    fn vec_progress_get() {
994        let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
995        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [6, 7], |id| (id, 0));
996
997        progress.update_progress(&6, 5);
998        assert_eq!(Some(&(6, 5)), progress.get(&6));
999        assert_eq!(Some(&5), progress.get(&6).map(|x| &x.1));
1000        assert_eq!(None, progress.get(&9));
1001
1002        progress.update_progress(&6, 10);
1003        assert_eq!(Some(&10), progress.get(&6).map(|x| &x.1));
1004    }
1005
1006    #[test]
1007    fn vec_progress_iter() {
1008        let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
1009        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [6, 7], |id| (id, 0));
1010
1011        progress.update_progress(&7, 7);
1012        progress.update_progress(&3, 3);
1013        progress.update_progress(&1, 1);
1014
1015        assert_eq!(
1016            vec![(3, 3), (1, 1), (0, 0), (2, 0), (4, 0), (6, 0), (7, 7),],
1017            progress.iter().cloned().collect::<Vec<_>>(),
1018            "iter() returns voter first, followed by learners"
1019        );
1020    }
1021
1022    #[test]
1023    fn vec_progress_move_up() {
1024        let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
1025        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [6], |id| (id, 0));
1026
1027        // initial: 0-0, 1-0, 2-0, 3-0, 4-0
1028        let cases = [
1029            (
1030                (1, 2),
1031                vec![(1, 2), (0, 0), (2, 0), (3, 0), (4, 0), (6, 0)],
1032                0,
1033            ),
1034            (
1035                (2, 3),
1036                vec![(2, 3), (1, 2), (0, 0), (3, 0), (4, 0), (6, 0)],
1037                0,
1038            ),
1039            (
1040                (1, 3),
1041                vec![(2, 3), (1, 3), (0, 0), (3, 0), (4, 0), (6, 0)],
1042                1,
1043            ), // no move
1044            (
1045                (4, 8),
1046                vec![(4, 8), (2, 3), (1, 3), (0, 0), (3, 0), (6, 0)],
1047                0,
1048            ),
1049            (
1050                (0, 5),
1051                vec![(4, 8), (0, 5), (2, 3), (1, 3), (3, 0), (6, 0)],
1052                1,
1053            ), // move to 1st
1054        ];
1055        for (ith, ((id, v), want_vec, want_new_index)) in cases.iter().enumerate() {
1056            // Update a value and move it up to keep the order.
1057            let index = progress.index(id).unwrap();
1058            progress.entries[index].1 = *v;
1059            let got = progress.move_up(index);
1060
1061            assert_eq!(
1062                want_vec, &progress.entries,
1063                "{}-th case: idx:{}, v:{}",
1064                ith, *id, *v
1065            );
1066            assert_eq!(
1067                *want_new_index, got,
1068                "{}-th case: idx:{}, v:{}",
1069                ith, *id, *v
1070            );
1071        }
1072    }
1073
1074    #[test]
1075    fn vec_progress_update_progress() {
1076        let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
1077        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [6], |id| (id, 0));
1078
1079        // initial: 0,0,0,0,0
1080        let cases = vec![
1081            ((6, 9), Some(&0)), // 0,0,0,0,0,9 // learner won't affect quorum-accepted
1082            ((1, 2), Some(&0)), // 0,2,0,0,0,0
1083            ((2, 3), Some(&0)), // 0,2,3,0,0,0
1084            ((3, 1), Some(&1)), // 0,2,3,1,0,0
1085            ((4, 5), Some(&2)), // 0,2,3,1,5,0
1086            ((0, 4), Some(&3)), // 4,2,3,1,5,0
1087            ((3, 2), Some(&3)), // 4,2,3,2,5,0
1088            ((3, 3), Some(&3)), // 4,2,3,2,5,0
1089            ((1, 4), Some(&4)), // 4,4,3,2,5,0
1090            ((9, 1), None),     // nonexistent id, ignore.
1091        ];
1092
1093        for (ith, ((id, v), want_quorum_accepted)) in cases.iter().enumerate() {
1094            let got = progress.update_progress_with(id, |x| *x = *v);
1095            assert_eq!(
1096                want_quorum_accepted.clone(),
1097                got,
1098                "{}-th case: id:{}, v:{}",
1099                ith,
1100                id,
1101                v
1102            );
1103        }
1104    }
1105
1106    #[test]
1107    fn vec_progress_matches_reference_model_for_monotonic_updates() {
1108        let cases = [
1109            (vec![btreeset! {0, 1, 2, 3, 4}], vec![5, 6]),
1110            (vec![btreeset! {0, 1, 2}, btreeset! {2, 3, 4}], vec![5, 6]),
1111        ];
1112
1113        for (case_id, (quorum_set, learners)) in cases.into_iter().enumerate() {
1114            for seed in 0..32 {
1115                let mut seed = seed + 1;
1116                let mut progress =
1117                    VecProgress::<(u64, u64), _>::new(quorum_set.clone(), learners.clone(), |id| {
1118                        (id, 0)
1119                    });
1120
1121                assert_matches_model(&progress, &format!("case-{case_id} seed-{seed} initial"));
1122
1123                for step in 0..128 {
1124                    let id = next_random(&mut seed) % 8;
1125                    let value = progress.get(&id).map(|entry| entry.1).unwrap_or_default()
1126                        + next_random(&mut seed) % 7
1127                        + 1;
1128                    let got = copy_option(progress.update_progress(&id, value));
1129                    let want = model_quorum_accepted(&progress.quorum_set, &progress.entries);
1130                    let want_result = progress.get(&id).map(|_| want);
1131                    let context =
1132                        format!("case-{case_id} seed-{seed} step-{step} update-{id}-{value}");
1133
1134                    assert_eq!(
1135                        want_result, got,
1136                        "{context}: entries: {:?}",
1137                        progress.entries
1138                    );
1139                    assert_matches_model(&progress, &context);
1140                }
1141            }
1142        }
1143    }
1144
1145    #[test]
1146    fn vec_progress_matches_reference_model_after_random_quorum_upgrades() {
1147        let quorum_sets = [
1148            vec![btreeset! {0, 1, 2, 3, 4}],
1149            vec![btreeset! {0, 1, 2}, btreeset! {2, 3, 4}],
1150            vec![btreeset! {2, 3, 4}],
1151            vec![btreeset! {1, 3, 5}, btreeset! {3, 4, 5, 6}],
1152            vec![btreeset! {0, 5, 6}],
1153        ];
1154        let known_ids = (0..=8).collect::<Vec<_>>();
1155
1156        for seed in 0..16 {
1157            let mut seed = seed + 11;
1158            let quorum_set = quorum_sets[0].clone();
1159            let learner_ids = learner_ids_for(&quorum_set, known_ids.clone());
1160            let mut progress =
1161                VecProgress::<(u64, u64), _>::new(quorum_set, learner_ids, |id| (id, 0));
1162
1163            assert_matches_model(&progress, &format!("seed-{seed} initial"));
1164
1165            for round in 0..24 {
1166                for step in 0..16 {
1167                    let id = next_random(&mut seed) % 10;
1168                    let value = progress.get(&id).map(|entry| entry.1).unwrap_or_default()
1169                        + next_random(&mut seed) % 11
1170                        + 1;
1171                    progress.update_progress(&id, value);
1172                    assert_matches_model(
1173                        &progress,
1174                        &format!("seed-{seed} round-{round} step-{step} update"),
1175                    );
1176                }
1177
1178                let quorum_index = next_random(&mut seed) as usize % quorum_sets.len();
1179                let quorum_set = quorum_sets[quorum_index].clone();
1180                let learner_ids = learner_ids_for(&quorum_set, known_ids.clone());
1181
1182                progress = progress.upgrade_quorum_set(quorum_set, learner_ids, |id| (id, 0));
1183                assert_matches_model(
1184                    &progress,
1185                    &format!("seed-{seed} round-{round} upgrade-{quorum_index}"),
1186                );
1187            }
1188        }
1189    }
1190
1191    #[test]
1192    fn vec_progress_joint_quorum_update_progress() {
1193        let quorum_set = vec![btreeset! {0, 1, 2}, btreeset! {2, 3, 4}];
1194        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [5, 6], |id| (id, 0));
1195
1196        let cases = [
1197            (0, 5, 0),
1198            (1, 5, 0),
1199            (2, 4, 0),
1200            (3, 4, 4),
1201            (4, 6, 4),
1202            (3, 6, 5),
1203            (2, 7, 5),
1204            (0, 7, 6),
1205        ];
1206
1207        for (ith, (id, value, want_quorum_accepted)) in cases.iter().enumerate() {
1208            let got = copy_option(progress.update_progress(id, *value));
1209            let context = format!("{ith}-th case: id:{id}, value:{value}");
1210
1211            assert_eq!(
1212                Some(*want_quorum_accepted),
1213                got,
1214                "{context}: entries: {:?}",
1215                progress.entries
1216            );
1217            assert_matches_model(&progress, &context);
1218        }
1219
1220        let entries: Vec<_> = progress.collect_mapped(|item| (item.0, item.1));
1221        assert_eq!(
1222            vec![(2, 7), (0, 7), (4, 6), (3, 6), (1, 5), (5, 0), (6, 0)],
1223            entries
1224        );
1225    }
1226
1227    #[test]
1228    fn vec_progress_non_member_and_learner_edge_cases() {
1229        let quorum_set = vec![btreeset! {0, 1, 2}];
1230        assert!(
1231            std::panic::catch_unwind(|| {
1232                VecProgress::<(u64, u64), _>::new(quorum_set.clone(), [1, 3, 3], |id| (id, 0))
1233            })
1234            .is_err(),
1235            "duplicate IDs are invalid"
1236        );
1237
1238        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [3], |id| (id, 0));
1239
1240        assert_eq!(vec![(0, 0), (1, 0), (2, 0), (3, 0)], progress.entries);
1241        assert_eq!(3, progress.voter_count);
1242        assert_eq!(Some(true), progress.is_voter(&1));
1243        assert_eq!(Some(false), progress.is_voter(&3));
1244        assert_eq!(None, progress.is_voter(&9));
1245
1246        assert_eq!(Some(0), copy_option(progress.update_progress(&3, 7)));
1247        assert_eq!(vec![(0, 0), (1, 0), (2, 0), (3, 7)], progress.entries);
1248
1249        assert_eq!(Some(0), copy_option(progress.update_progress(&1, 5)));
1250        assert_eq!(vec![(1, 5), (0, 0), (2, 0), (3, 7)], progress.entries);
1251
1252        assert_eq!(Some(4), copy_option(progress.update_progress(&2, 4)));
1253        assert_eq!(vec![(1, 5), (2, 4), (0, 0), (3, 7)], progress.entries);
1254
1255        let quorum_set = vec![btreeset! {0, 1, 2}];
1256        let mut no_learners = VecProgress::<(u64, u64), _>::new(quorum_set, [], |id| (id, 0));
1257
1258        assert_eq!(vec![(0, 0), (1, 0), (2, 0)], no_learners.entries);
1259        assert_eq!(3, no_learners.voter_count);
1260        assert_eq!(None, copy_option(no_learners.update_progress(&9, 5)));
1261        assert_eq!(vec![(0, 0), (1, 0), (2, 0)], no_learners.entries);
1262    }
1263
1264    #[test]
1265    fn vec_progress_custom_quorum_set() {
1266        let quorum_set = RequiredSetQuorum::new([0, 1, 2, 3], [0, 3]);
1267        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [], |id| (id, 0));
1268
1269        assert_eq!(Some(0), copy_option(progress.update_progress(&1, 10)));
1270        assert_eq!(Some(0), copy_option(progress.update_progress(&2, 9)));
1271        assert_eq!(Some(0), copy_option(progress.update_progress(&0, 8)));
1272        assert_matches_model(&progress, "custom quorum before required set is reached");
1273
1274        assert_eq!(vec![(1, 10), (2, 9), (0, 8), (3, 0)], progress.entries);
1275
1276        assert_eq!(Some(7), copy_option(progress.update_progress(&3, 7)));
1277        assert_eq!(&7, progress.quorum_accepted());
1278        assert_matches_model(&progress, "custom quorum reaches required set");
1279
1280        assert_eq!(Some(8), copy_option(progress.update_progress(&3, 11)));
1281        assert_eq!(vec![(3, 11), (1, 10), (2, 9), (0, 8)], progress.entries);
1282        assert_matches_model(&progress, "custom quorum follows required set threshold");
1283    }
1284
1285    #[test]
1286    fn vec_progress_update_progress_with() {
1287        let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
1288        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [6], |id| (id, 0));
1289
1290        // Test that update_progress_with can use closures to modify values
1291        // Case 0: 0,2,0,0,0,0
1292        let got = progress.update_progress_with(&1, |x| *x += 2);
1293        assert_eq!(Some(&0), got, "case 0: id:1, +=2");
1294
1295        // Case 1: 0,2,3,0,0,0
1296        let got = progress.update_progress_with(&2, |x| *x += 3);
1297        assert_eq!(Some(&0), got, "case 1: id:2, +=3");
1298
1299        // Case 2: 0,2,3,1,0,0
1300        let got = progress.update_progress_with(&3, |x| *x = 1);
1301        assert_eq!(Some(&1), got, "case 2: id:3, =1");
1302
1303        // Case 3: 0,2,3,1,5,0
1304        let got = progress.update_progress_with(&4, |x| *x += 5);
1305        assert_eq!(Some(&2), got, "case 3: id:4, +5");
1306
1307        // Case 4: 4,2,3,1,5,0 - closure can see updated value
1308        let got = progress.update_progress_with(&0, |x| {
1309            *x += 4;
1310            assert_eq!(4, *x, "closure sees the updated value");
1311        });
1312        assert_eq!(Some(&3), got, "case 4: id:0, +=4");
1313
1314        // Case 5: 4,2,3,2,5,0 - using max
1315        let got = progress.update_progress_with(&3, |x| *x = (*x).max(2));
1316        assert_eq!(Some(&3), got, "case 5: id:3, max(2)");
1317
1318        // Case 6: 4,4,3,2,5,0
1319        let got = progress.update_progress_with(&1, |x| *x *= 2);
1320        assert_eq!(Some(&4), got, "case 6: id:1, *=2");
1321
1322        // Verify final values
1323        assert_eq!(Some(&(0, 4)), progress.get(&0));
1324        assert_eq!(Some(&(1, 4)), progress.get(&1));
1325        assert_eq!(Some(&(2, 3)), progress.get(&2));
1326        assert_eq!(Some(&(3, 2)), progress.get(&3));
1327        assert_eq!(Some(&(4, 5)), progress.get(&4));
1328        assert_eq!(Some(&(6, 0)), progress.get(&6));
1329
1330        // Test nonexistent id returns None
1331        let got = progress.update_progress_with(&9, |x| *x = 10);
1332        assert_eq!(None, got, "nonexistent id returns None");
1333    }
1334
1335    #[test]
1336    fn vec_progress_update_data_with() {
1337        let quorum_set = vec![btreeset! {0, 1, 2}];
1338        let mut progress =
1339            VecProgress::<IdValData<u64, u64, &'static str>, _>::new(quorum_set, [3], |id| {
1340                IdValData::new(id, 0, "foo")
1341            });
1342
1343        assert_eq!(Some(&0), progress.update_progress(&1, 2));
1344
1345        let stats_before = (
1346            progress.stat().update_count,
1347            progress.stat().move_count,
1348            progress.stat().is_quorum_count,
1349        );
1350
1351        assert_eq!(
1352            Some(&"bar"),
1353            progress.update_data_with(&1, |data| *data = "bar")
1354        );
1355        assert_eq!(
1356            None,
1357            progress.update_data_with(&9, |data| *data = "unknown")
1358        );
1359
1360        assert_eq!(
1361            vec![
1362                IdValData::new(1, 2, "bar"),
1363                IdValData::new(0, 0, "foo"),
1364                IdValData::new(2, 0, "foo"),
1365                IdValData::new(3, 0, "foo"),
1366            ],
1367            progress.iter().cloned().collect::<Vec<_>>()
1368        );
1369        assert_eq!(&0, progress.quorum_accepted());
1370        assert_eq!(
1371            stats_before,
1372            (
1373                progress.stat().update_count,
1374                progress.stat().move_count,
1375                progress.stat().is_quorum_count,
1376            )
1377        );
1378    }
1379
1380    #[test]
1381    fn vec_progress_update_does_not_move_learner_elt() {
1382        let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
1383        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [6], |id| (id, 0));
1384
1385        assert_eq!(Some(5), progress.index(&6));
1386
1387        progress.update_progress(&6, 6);
1388        assert_eq!(Some(5), progress.index(&6), "learner is not moved");
1389
1390        progress.update_progress(&4, 4);
1391        assert_eq!(Some(0), progress.index(&4), "voter is not moved");
1392    }
1393
1394    #[test]
1395    fn vec_progress_upgrade_quorum_set() {
1396        let qs012 = vec![btreeset! {0, 1, 2}];
1397        let qs012_345 = vec![btreeset! {0, 1, 2}, btreeset! {3, 4, 5}];
1398        let qs345 = vec![btreeset! {3, 4, 5}];
1399
1400        // Initially, quorum-accepted is 5
1401
1402        let mut p012 = VecProgress::<(u64, u64), _>::new(qs012, [5], |id| (id, 0));
1403
1404        p012.update_progress(&0, 5);
1405        p012.update_progress(&1, 6);
1406        p012.update_progress(&5, 9);
1407        assert_eq!(&5, p012.quorum_accepted());
1408
1409        // After upgrading to a bigger quorum set, quorum-accepted fall back to 0
1410
1411        let mut p012_345 = p012.upgrade_quorum_set(qs012_345, [6], |id| (id, 0));
1412        assert_eq!(
1413            &0,
1414            p012_345.quorum_accepted(),
1415            "quorum extended from 012 to 012_345, quorum-accepted falls back"
1416        );
1417        assert_eq!(Some(&(5, 9)), p012_345.get(&5), "inherit learner progress");
1418
1419        // When quorum set shrinks, quorum-accepted becomes greater.
1420
1421        p012_345.update_progress(&3, 7);
1422        p012_345.update_progress(&4, 8);
1423        assert_eq!(&5, p012_345.quorum_accepted());
1424
1425        let p345 = p012_345.upgrade_quorum_set(qs345, [1], |id| (id, 0));
1426
1427        assert_eq!(
1428            &8,
1429            p345.quorum_accepted(),
1430            "shrink quorum set, greater value becomes quorum-accepted"
1431        );
1432        assert_eq!(Some(&(1, 6)), p345.get(&1), "inherit voter progress");
1433    }
1434
1435    #[test]
1436    fn vec_progress_upgrade_joint_quorum_set() {
1437        let qs01234 = vec![btreeset! {0, 1, 2, 3, 4}];
1438        let qs012_234 = vec![btreeset! {0, 1, 2}, btreeset! {2, 3, 4}];
1439        let qs345 = vec![btreeset! {3, 4, 5}];
1440
1441        let mut p = VecProgress::<(u64, u64), _>::new(qs01234, [5], |id| (id, 0));
1442
1443        for (id, value) in [(0, 9), (1, 8), (2, 7), (3, 2), (4, 1), (5, 10)] {
1444            p.update_progress(&id, value);
1445        }
1446
1447        assert_eq!(&7, p.quorum_accepted());
1448
1449        let mut joint = p.upgrade_quorum_set(qs012_234, [5, 6], |id| (id, 0));
1450
1451        assert_eq!(
1452            &2,
1453            joint.quorum_accepted(),
1454            "joint quorum lowers the accepted value"
1455        );
1456        let entries: Vec<_> = joint.collect_mapped(|item| (item.0, item.1));
1457        assert_eq!(
1458            vec![(0, 9), (1, 8), (2, 7), (3, 2), (4, 1), (5, 10), (6, 0)],
1459            entries
1460        );
1461        assert_matches_model(&joint, "after upgrade to joint quorum");
1462
1463        joint.update_progress(&3, 8);
1464        joint.update_progress(&4, 8);
1465        assert_eq!(&8, joint.quorum_accepted());
1466        assert_matches_model(&joint, "after joint quorum catches up");
1467
1468        let shrunk = joint.upgrade_quorum_set(qs345, [0], |id| (id, 0));
1469
1470        assert_eq!(&8, shrunk.quorum_accepted());
1471        let entries: Vec<_> = shrunk.collect_mapped(|item| (item.0, item.1));
1472        assert_eq!(vec![(5, 10), (3, 8), (4, 8), (0, 9)], entries);
1473        assert_matches_model(&shrunk, "after shrinking joint quorum");
1474    }
1475
1476    #[test]
1477    fn vec_progress_is_voter() {
1478        let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
1479        let progress = VecProgress::<(u64, u64), _>::new(quorum_set, [6, 7], |id| (id, 0));
1480
1481        assert_eq!(Some(true), progress.is_voter(&1));
1482        assert_eq!(Some(true), progress.is_voter(&3));
1483        assert_eq!(Some(false), progress.is_voter(&7));
1484        assert_eq!(None, progress.is_voter(&8));
1485    }
1486
1487    #[test]
1488    fn vec_progress_display() {
1489        let quorum_set = vec![btreeset! {0, 1, 2}];
1490        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [3], |id| (id, 0));
1491
1492        progress.update_progress(&1, 5);
1493        progress.update_progress(&2, 3);
1494
1495        let display = format!(
1496            "{}",
1497            progress.display_with(|f, item| write!(f, "{}: {}", item.0, item.1))
1498        );
1499        assert_eq!("{1: 5, 2: 3, 0: 0, 3: 0}", display);
1500    }
1501
1502    #[test]
1503    fn vec_progress_iter_mut_without_reorder() {
1504        let quorum_set = vec![btreeset! {0, 1, 2}];
1505        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [3], |id| (id, 0));
1506
1507        // Mutate values through iter_mut_without_reorder
1508        for item in progress.iter_mut_without_reorder() {
1509            if item.0 == 1 {
1510                item.1 = 10;
1511            }
1512        }
1513
1514        assert_eq!(Some(&(1, 10)), progress.get(&1));
1515        assert_eq!(Some(&(0, 0)), progress.get(&0));
1516        assert_eq!(Some(&(2, 0)), progress.get(&2));
1517    }
1518
1519    #[test]
1520    fn vec_progress_stat() {
1521        let quorum_set = vec![btreeset! {0, 1, 2}];
1522        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [3], |id| (id, 0));
1523
1524        assert_eq!(
1525            (0, 0, 0),
1526            (
1527                progress.stat().update_count,
1528                progress.stat().move_count,
1529                progress.stat().is_quorum_count,
1530            )
1531        );
1532
1533        progress.update_progress(&3, 10);
1534        assert_eq!(
1535            (1, 0, 0),
1536            (
1537                progress.stat().update_count,
1538                progress.stat().move_count,
1539                progress.stat().is_quorum_count,
1540            )
1541        );
1542
1543        progress.update_progress(&1, 5);
1544        assert_eq!(
1545            (2, 1, 1),
1546            (
1547                progress.stat().update_count,
1548                progress.stat().move_count,
1549                progress.stat().is_quorum_count,
1550            )
1551        );
1552
1553        progress.update_progress(&2, 4);
1554        assert_eq!(
1555            (3, 2, 2),
1556            (
1557                progress.stat().update_count,
1558                progress.stat().move_count,
1559                progress.stat().is_quorum_count,
1560            )
1561        );
1562
1563        progress.update_progress(&1, 6);
1564        assert_eq!(
1565            (4, 3, 2),
1566            (
1567                progress.stat().update_count,
1568                progress.stat().move_count,
1569                progress.stat().is_quorum_count,
1570            )
1571        );
1572
1573        progress.update_progress(&9, 7);
1574        assert_eq!(
1575            (5, 3, 2),
1576            (
1577                progress.stat().update_count,
1578                progress.stat().move_count,
1579                progress.stat().is_quorum_count,
1580            )
1581        );
1582    }
1583
1584    #[test]
1585    fn vec_progress_display_with() {
1586        let quorum_set = vec![btreeset! {0, 1, 2}];
1587        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [3], |id| (id, 0));
1588
1589        progress.update_progress(&1, 5);
1590        progress.update_progress(&2, 3);
1591
1592        let display = progress.display_with(|f, item| write!(f, "{}={}", item.0, item.1));
1593
1594        let output = format!("{}", display);
1595        assert_eq!("{1=5, 2=3, 0=0, 3=0}", output);
1596    }
1597
1598    #[test]
1599    fn vec_progress_increase_to() {
1600        let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
1601        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [6], |id| (id, 0));
1602
1603        // Increase from 0 to 5
1604        progress.increase_to(&1, 5);
1605        assert_eq!(Some(&(1, 5)), progress.get(&1));
1606
1607        // Try to decrease from 5 to 3 - should not change
1608        progress.increase_to(&1, 3);
1609        assert_eq!(Some(&(1, 5)), progress.get(&1));
1610
1611        // Increase from 5 to 7
1612        progress.increase_to(&1, 7);
1613        assert_eq!(Some(&(1, 7)), progress.get(&1));
1614
1615        // Try with nonexistent id
1616        let result = progress.increase_to(&9, 10);
1617        assert!(result.is_none());
1618    }
1619
1620    #[test]
1621    fn vec_progress_collect_mapped() {
1622        let quorum_set = vec![btreeset! {0, 1, 2}];
1623        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [3], |id| (id, 0));
1624
1625        progress.update_progress(&1, 5);
1626        progress.update_progress(&2, 3);
1627
1628        // Collect ids as Vec - order matters after updates (sorted by value descending)
1629        let ids: Vec<u64> = progress.collect_mapped(|item| item.0);
1630        assert_eq!(vec![1, 2, 0, 3], ids);
1631
1632        // Collect values as Vec - order matters after updates (sorted descending)
1633        let values: Vec<u64> = progress.collect_mapped(|item| item.1);
1634        assert_eq!(vec![5, 3, 0, 0], values);
1635
1636        // Collect as Vec of tuples - order matters after updates
1637        let pairs: Vec<(u64, u64)> = progress.collect_mapped(|item| (item.0, item.1));
1638        assert_eq!(vec![(1, 5), (2, 3), (0, 0), (3, 0)], pairs);
1639    }
1640
1641    #[test]
1642    fn vec_progress_reset_entry_with() {
1643        // 7 voters, majority = 4.
1644        let quorum_set = vec![btreeset! {0, 1, 2, 3, 4, 5, 6}];
1645        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [7], |id| (id, 0));
1646
1647        progress.update_progress(&0, 12);
1648        progress.update_progress(&1, 11);
1649        progress.update_progress(&2, 10);
1650        progress.update_progress(&3, 9);
1651        assert_eq!(&9, progress.quorum_accepted());
1652
1653        // Node 1 log-reverts: its progress falls back to the default value and the
1654        // entry is moved down, while the quorum-accepted value must be kept.
1655        let entry = progress.reset_entry_with(&1, |entry| entry.1 = 0);
1656        assert_eq!(Some(&(1, 0)), entry);
1657        assert_eq!(
1658            &9,
1659            progress.quorum_accepted(),
1660            "reset never lowers quorum-accepted"
1661        );
1662        assert_eq!(
1663            vec![
1664                (0, 12),
1665                (2, 10),
1666                (3, 9),
1667                (1, 0),
1668                (4, 0),
1669                (5, 0),
1670                (6, 0),
1671                (7, 0)
1672            ],
1673            progress.entries
1674        );
1675        assert_voter_prefix_is_sorted(&progress, "after reset");
1676
1677        // Node 4 catches up to exactly 10: without the move-down, the reverted node 1
1678        // would be counted spuriously and 10 would be accepted with only 3 real grants.
1679        assert_eq!(Some(9), copy_option(progress.update_progress(&4, 10)));
1680        assert_matches_model(&progress, "after catching up to 10");
1681
1682        // A real quorum at 10: {0, 2, 4, 5}.
1683        assert_eq!(Some(10), copy_option(progress.update_progress(&5, 10)));
1684        assert_matches_model(&progress, "after a real quorum at 10");
1685
1686        // Resetting a learner or a nonexistent id does not reorder anything.
1687        assert_eq!(
1688            Some(&(7, 0)),
1689            progress.reset_entry_with(&7, |entry| entry.1 = 0)
1690        );
1691        assert_eq!(None, progress.reset_entry_with(&9, |entry| entry.1 = 0));
1692    }
1693
1694    #[test]
1695    fn vec_progress_matches_reference_model_with_resets() {
1696        let cases = [
1697            (vec![btreeset! {0, 1, 2, 3, 4, 5, 6}], vec![7]),
1698            (vec![btreeset! {0, 1, 2}, btreeset! {2, 3, 4}], vec![5, 6]),
1699        ];
1700
1701        for (case_id, (quorum_set, learners)) in cases.into_iter().enumerate() {
1702            for seed in 0..32 {
1703                let mut seed = seed + 3;
1704                let mut progress =
1705                    VecProgress::<(u64, u64), _>::new(quorum_set.clone(), learners.clone(), |id| {
1706                        (id, 0)
1707                    });
1708
1709                // The quorum-accepted value never moves backward, so the reference
1710                // is the running max of the instantaneous model value.
1711                let mut want = 0;
1712
1713                for step in 0..128 {
1714                    // Use high bits: the low bits of this LCG have a short period,
1715                    // which makes power-of-two moduli cycle in lock-step.
1716                    let id = (next_random(&mut seed) >> 32) % 8;
1717                    let context = format!("case-{case_id} seed-{seed} step-{step} id-{id}");
1718
1719                    if (next_random(&mut seed) >> 32).is_multiple_of(8) {
1720                        progress.reset_entry_with(&id, |entry| entry.1 = 0);
1721                    } else {
1722                        let value = progress.get(&id).map(|entry| entry.1).unwrap_or_default()
1723                            + next_random(&mut seed) % 7
1724                            + 1;
1725                        progress.update_progress(&id, value);
1726                    }
1727
1728                    want = want.max(model_quorum_accepted(
1729                        &progress.quorum_set,
1730                        &progress.entries,
1731                    ));
1732                    assert_eq!(
1733                        &want,
1734                        progress.quorum_accepted(),
1735                        "{context}: entries: {:?}",
1736                        progress.entries
1737                    );
1738                    assert_voter_prefix_is_sorted(&progress, &context);
1739                }
1740            }
1741        }
1742    }
1743
1744    #[test]
1745    fn vec_progress_sub_quorum_commit_regression() {
1746        let quorum_set = vec![btreeset! {0, 1, 2, 3, 4}];
1747        let mut progress = VecProgress::<(u64, u64), _>::new(quorum_set, [], |id| (id, 0));
1748
1749        progress.update_progress(&0, 5); // qa = 0
1750        progress.update_progress(&1, 3); // qa = 0
1751        progress.update_progress(&2, 4); // qa = 3 ; above-qa region = [0:5, 2:4] (descending, ok)
1752        progress.update_progress(&2, 10); // voter 2 was already > qa(3) and advances further:
1753        //   move_up must still run; if it were skipped, the region would become
1754        //   [0:5, 2:10] (NOT descending) and the next update would falsely accept 6.
1755        let qa = progress.update_progress(&3, 6);
1756
1757        // True match values: {0:5, 1:3, 2:10, 3:6, 4:0}; sorted desc = 10,6,5,3,0 -> 3rd-largest =
1758        // 5. Only voters {2,3} reached 6 -> 2 voters -> NOT a majority.
1759        // Expected quorum_accepted = 5.
1760        assert_eq!(Some(&5), qa);
1761    }
1762}