Skip to main content

ozzy_replication/view_change/
selection.rs

1//! History selection from a quorum of reports, verified against the chosen
2//! source's pinned history.
3
4use super::{LogSource, SelectedView, ViewChange, ViewChangeError};
5use crate::{Digest, OpNumber, Prefix, VoterSet};
6
7impl ViewChange {
8    /// Select the highest last-normal-view, then longest accepted lineage.
9    ///
10    /// `lookup` must be a nonblocking lookup into already verified, pinned
11    /// canonical history anchored by the supplied source's exact tail digest.
12    /// An isolated matching hash or a chunk from another generation is not such
13    /// evidence. Missing bytes return `None`; fetch/verify them outside the core.
14    /// At most six protected-prefix lookups are needed for three voters.
15    ///
16    /// Selection includes this primary's report and another distinct voter. It
17    /// protects every reported commit and same-normal-view accepted prefix.
18    /// Higher-normal-view histories may discard only older uncertain suffixes.
19    pub fn select(
20        &mut self,
21        mut lookup: impl FnMut(LogSource, OpNumber) -> Option<Digest>,
22    ) -> Result<SelectedView, ViewChangeError> {
23        self.require_promised()?;
24        self.require_primary()?;
25        if let Some(selected) = self.selected {
26            return Ok(selected);
27        }
28        let reports = self.reports;
29        let Some(mut report) = reports[self.local] else {
30            return Err(ViewChangeError::ReportQuorumMissing);
31        };
32        let mut chosen = self.configuration.voter(self.local);
33        let voters: VoterSet = reports
34            .iter()
35            .filter_map(|(voter, candidate)| candidate.map(|_| voter))
36            .collect();
37        if voters.len() < 2 {
38            return Err(ViewChangeError::ReportQuorumMissing);
39        }
40        for (voter, candidate) in reports.iter() {
41            let Some(candidate) = candidate else {
42                continue;
43            };
44            let rank = (candidate.log.last_normal_view, candidate.log.accepted.op);
45            let best = (report.log.last_normal_view, report.log.accepted.op);
46            if rank == best && candidate.log.accepted != report.log.accepted {
47                return Err(self.conflict());
48            }
49            if rank > best {
50                report = *candidate;
51                chosen = self.configuration.voter(voter);
52            }
53        }
54        let source = LogSource {
55            voter: chosen,
56            generation: report.generation,
57            accepted: report.log.accepted,
58        };
59        let mut committed = Prefix::GENESIS;
60        for candidate in reports.values().flatten() {
61            if candidate.log.committed.op > committed.op {
62                committed = candidate.log.committed;
63            }
64            self.verify_prefix(source, candidate.log.committed, &mut lookup)?;
65            if candidate.log.last_normal_view == report.log.last_normal_view {
66                self.verify_prefix(source, candidate.log.accepted, &mut lookup)?;
67            }
68        }
69        let selected = SelectedView {
70            scope: self.scope(),
71            source,
72            committed,
73            voters,
74        };
75        self.selected = Some(selected);
76        Ok(selected)
77    }
78
79    pub(crate) fn verify_prefix(
80        &mut self,
81        source: LogSource,
82        prefix: Prefix,
83        lookup: &mut impl FnMut(LogSource, OpNumber) -> Option<Digest>,
84    ) -> Result<(), ViewChangeError> {
85        if prefix.op > source.accepted.op {
86            return Err(self.conflict());
87        }
88        let digest = if prefix.op == OpNumber(0) {
89            Digest::ZERO
90        } else if prefix.op == source.accepted.op {
91            source.accepted.digest
92        } else {
93            lookup(source, prefix.op).ok_or(ViewChangeError::HistoryMissing)?
94        };
95        if digest != prefix.digest {
96            return Err(self.conflict());
97        }
98        Ok(())
99    }
100}