1mod recovery;
4mod selection;
5
6use ozzy_proto::NodeId;
7
8use crate::{
9 Configuration, Digest, JournalGeneration, NormalReplica, Prefix, ReplicaSnapshot,
10 ReplicationError, Scope, Status, SyncTicket, VoterIndex, VoterSet, Voters, WriteTicket,
11};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct FrozenLog {
20 pub last_normal_view: u64,
22 pub accepted: Prefix,
24 pub committed: Prefix,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub struct RecoveredState {
36 pub scope: Scope,
38 pub log: FrozenLog,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub struct PromiseTicket {
50 scope: Scope,
51 generation: JournalGeneration,
52 log: FrozenLog,
53}
54
55impl PromiseTicket {
56 pub const fn scope(self) -> Scope {
58 self.scope
59 }
60 pub const fn generation(self) -> JournalGeneration {
62 self.generation
63 }
64 pub const fn log(self) -> FrozenLog {
66 self.log
67 }
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub struct StartViewChange {
73 pub scope: Scope,
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub struct DoViewChange {
80 pub scope: Scope,
82 pub generation: JournalGeneration,
84 pub log: FrozenLog,
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub struct LogSource {
91 pub voter: NodeId,
93 pub generation: JournalGeneration,
95 pub accepted: Prefix,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub struct SelectedView {
105 scope: Scope,
106 source: LogSource,
107 committed: Prefix,
108 voters: VoterSet,
109}
110
111impl SelectedView {
112 pub const fn scope(self) -> Scope {
114 self.scope
115 }
116 pub const fn source(self) -> LogSource {
118 self.source
119 }
120 pub const fn committed(self) -> Prefix {
122 self.committed
123 }
124 pub const fn voter_mask(self) -> u8 {
126 self.voters.mask()
127 }
128}
129
130#[derive(Debug)]
138pub struct ViewChange {
139 pub(crate) normal: NormalReplica,
140 pub(crate) configuration: Configuration,
141 pub(crate) local: VoterIndex,
142 view: u64,
143 promise: Promise,
144 pending: Option<PromiseTicket>,
145 starts: VoterSet,
146 reports: Voters<Option<DoViewChange>>,
147 pub(crate) selected: Option<SelectedView>,
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152enum Promise {
153 Prior(u64),
156 Completed(PromiseTicket),
159}
160
161impl Promise {
162 const fn view(self) -> u64 {
163 match self {
164 Self::Prior(view) => view,
165 Self::Completed(ticket) => ticket.scope.view,
166 }
167 }
168}
169
170impl ViewChange {
171 pub fn history_sources(&self) -> [Option<LogSource>; 3] {
174 VoterIndex::ALL.map(|voter| {
175 self.reports[voter].map(|report| LogSource {
176 voter: self.configuration.voter(voter),
177 generation: report.generation,
178 accepted: report.log.accepted,
179 })
180 })
181 }
182
183 pub(crate) fn begin(
184 mut normal: NormalReplica,
185 configuration: Configuration,
186 local: VoterIndex,
187 view: u64,
188 ) -> Result<Self, ViewChangeError> {
189 normal.fence();
190 if normal.snapshot().status == Status::Faulted {
191 return Err(ViewChangeError::Faulted);
192 }
193 if view <= configuration.scope().view {
194 return Err(ViewChangeError::ViewNotHigher);
195 }
196 Ok(Self {
197 normal,
198 configuration,
199 local,
200 view,
201 promise: Promise::Prior(configuration.scope().view),
202 pending: None,
203 starts: VoterSet::EMPTY,
204 reports: Voters::splat(None),
205 selected: None,
206 })
207 }
208
209 pub fn normal_snapshot(&self) -> ReplicaSnapshot {
211 self.normal.snapshot()
212 }
213
214 pub fn scope(&self) -> Scope {
216 Scope {
217 view: self.view,
218 ..self.configuration.scope()
219 }
220 }
221
222 pub const fn promised_view(&self) -> u64 {
224 self.promise.view()
225 }
226
227 pub fn advance_view(&mut self, view: u64) -> Result<(), ViewChangeError> {
233 self.require_active()?;
234 if view <= self.view {
235 return Err(ViewChangeError::ViewNotHigher);
236 }
237 self.view = view;
238 self.starts = VoterSet::EMPTY;
239 self.reports = Voters::splat(None);
240 self.selected = None;
241 Ok(())
242 }
243
244 pub fn complete_write(&mut self, ticket: WriteTicket) -> Result<(), ViewChangeError> {
246 Ok(self.normal.complete_write(ticket)?)
247 }
248
249 pub fn begin_sync(&self) -> Result<SyncTicket, ViewChangeError> {
251 Ok(self.normal.begin_sync()?)
252 }
253
254 pub fn complete_sync(&mut self, ticket: SyncTicket) -> Result<(), ViewChangeError> {
256 Ok(self.normal.complete_sync(ticket)?)
257 }
258
259 pub fn fail_io(&mut self, generation: JournalGeneration) -> Result<(), ViewChangeError> {
261 Ok(self.normal.fail_io(generation)?)
262 }
263
264 pub fn begin_promise(&mut self) -> Result<PromiseTicket, ViewChangeError> {
269 self.require_active()?;
270 if self.pending.is_some() {
271 return Err(ViewChangeError::PromisePending);
272 }
273 if self.promise.view() == self.view {
274 return Err(ViewChangeError::AlreadyPromised);
275 }
276 let snapshot = self.normal.snapshot();
277 if snapshot.journal.durable != snapshot.accepted.op {
278 return Err(ViewChangeError::StoragePending);
279 }
280 let ticket = PromiseTicket {
281 scope: self.scope(),
282 generation: snapshot.journal.generation,
283 log: FrozenLog {
284 last_normal_view: snapshot.scope.view,
285 accepted: snapshot.accepted,
286 committed: snapshot.committed,
287 },
288 };
289 self.pending = Some(ticket);
290 Ok(ticket)
291 }
292
293 pub fn complete_promise(&mut self, ticket: PromiseTicket) -> Result<(), ViewChangeError> {
295 self.require_active()?;
296 if self.promise == Promise::Completed(ticket) {
297 return Ok(());
298 }
299 self.require_ticket(ticket)?;
300 self.pending = None;
301 self.promise = Promise::Completed(ticket);
302 if ticket.scope.view == self.view {
303 self.starts.insert(self.local);
304 }
305 Ok(())
306 }
307
308 pub fn fail_promise(&mut self, ticket: PromiseTicket) -> Result<(), ViewChangeError> {
310 self.require_active()?;
311 self.require_ticket(ticket)?;
312 self.fail_io(ticket.generation)
313 }
314
315 pub fn start_message(&self) -> Result<StartViewChange, ViewChangeError> {
317 self.require_promised()?;
318 Ok(StartViewChange {
319 scope: self.scope(),
320 })
321 }
322
323 pub fn receive_start(
326 &mut self,
327 from: NodeId,
328 message: StartViewChange,
329 ) -> Result<(), ViewChangeError> {
330 self.require_scope(message.scope)?;
331 let voter = self.configuration.voter_index(from)?;
332 if voter == self.local {
333 return Err(ReplicationError::WrongRole.into());
334 }
335 self.starts.insert(voter);
336 Ok(())
337 }
338
339 pub fn report(&mut self) -> Result<DoViewChange, ViewChangeError> {
342 let promise = self.require_promised()?;
343 if self.starts.len() < 2 {
344 return Err(ViewChangeError::StartQuorumMissing);
345 }
346 let report = DoViewChange {
347 scope: self.scope(),
348 generation: promise.generation,
349 log: promise.log,
350 };
351 self.reports[self.local] = Some(report);
352 Ok(report)
353 }
354
355 pub fn receive_report(
363 &mut self,
364 from: NodeId,
365 report: DoViewChange,
366 ) -> Result<(), ViewChangeError> {
367 self.require_scope(report.scope)?;
368 self.require_primary()?;
369 let voter = self.configuration.voter_index(from)?;
370 if voter == self.local {
371 return Err(ReplicationError::WrongRole.into());
372 }
373 validate_report(report)?;
374 if let Some(previous) = self.reports[voter] {
375 if previous.log != report.log {
376 return Err(self.conflict());
377 }
378 return Ok(());
383 }
384 if self.selected.is_some() {
385 return Err(ViewChangeError::SelectionFrozen);
386 }
387 self.reports[voter] = Some(report);
388 Ok(())
389 }
390
391 fn require_active(&self) -> Result<(), ViewChangeError> {
392 if self.normal.snapshot().status == Status::Faulted {
393 return Err(ViewChangeError::Faulted);
394 }
395 Ok(())
396 }
397
398 pub(crate) fn require_scope(&self, scope: Scope) -> Result<(), ViewChangeError> {
399 self.require_active()?;
400 if scope != self.scope() {
401 return Err(ReplicationError::ScopeMismatch.into());
402 }
403 Ok(())
404 }
405
406 pub(crate) fn require_primary(&self) -> Result<(), ViewChangeError> {
407 if VoterIndex::primary(self.view) != self.local {
408 return Err(ReplicationError::WrongRole.into());
409 }
410 Ok(())
411 }
412
413 pub(crate) fn require_promised(&self) -> Result<PromiseTicket, ViewChangeError> {
415 self.require_active()?;
416 match self.promise {
417 Promise::Completed(ticket) if ticket.scope.view == self.view => Ok(ticket),
418 Promise::Prior(_) | Promise::Completed(_) => Err(ViewChangeError::PromiseRequired),
419 }
420 }
421
422 fn require_ticket(&self, ticket: PromiseTicket) -> Result<(), ViewChangeError> {
423 if self.pending != Some(ticket) {
424 return Err(ViewChangeError::StalePromise);
425 }
426 Ok(())
427 }
428
429 fn conflict(&mut self) -> ViewChangeError {
430 self.normal.fault();
431 ViewChangeError::ConflictingHistory
432 }
433}
434
435pub(crate) fn valid_prefix(prefix: Prefix) -> bool {
436 prefix.op.0 != u64::MAX && ((prefix.op.0 == 0) == (prefix.digest == Digest::ZERO))
437}
438
439fn validate_report(report: DoViewChange) -> Result<(), ViewChangeError> {
440 let log = report.log;
441 if log.last_normal_view >= report.scope.view
442 || !valid_prefix(log.accepted)
443 || !valid_prefix(log.committed)
444 || log.committed.op > log.accepted.op
445 || (log.committed.op == log.accepted.op && log.committed != log.accepted)
446 {
447 return Err(ViewChangeError::InvalidReport);
448 }
449 Ok(())
450}
451
452#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
454pub enum ViewChangeError {
455 #[error(transparent)]
457 Replication(#[from] ReplicationError),
458 #[error("view must increase")]
460 ViewNotHigher,
461 #[error("durable view number exhausted")]
463 ViewExhausted,
464 #[error("old-view journal work has not settled")]
466 StoragePending,
467 #[error("a view promise is already pending")]
469 PromisePending,
470 #[error("requested view already promised")]
472 AlreadyPromised,
473 #[error("stale or foreign view-promise completion")]
475 StalePromise,
476 #[error("requested view promise has not completed")]
478 PromiseRequired,
479 #[error("start-view-change quorum missing")]
481 StartQuorumMissing,
482 #[error("do-view-change quorum missing")]
484 ReportQuorumMissing,
485 #[error("selected lineage evidence missing")]
487 HistoryMissing,
488 #[error("invalid frozen view-change report")]
490 InvalidReport,
491 #[error("conflicting view-change history")]
493 ConflictingHistory,
494 #[error("view selection already frozen")]
496 SelectionFrozen,
497 #[error("view has not been selected")]
499 SelectionRequired,
500 #[error("installation reused the old writer generation")]
502 ReusedGeneration,
503 #[error("stale or foreign installation completion")]
505 StaleInstallation,
506 #[error("installation already completed")]
508 InstallationCompleted,
509 #[error("installed application state is not at the required committed prefix")]
511 ApplicationPending,
512 #[error("view change faulted")]
514 Faulted,
515}