ozzy_replication/view_change/
selection.rs1use super::{LogSource, SelectedView, ViewChange, ViewChangeError};
5use crate::{Digest, OpNumber, Prefix, VoterSet};
6
7impl ViewChange {
8 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}