1use ozzy_proto::NodeId;
4
5use crate::{
6 Configuration, Digest, JournalGeneration, NormalReplica, OpNumber, PipelineLimits, Prefix,
7 ReplicaSnapshot, ReplicationError, Scope, Status, SyncTicket, WriteTicket,
8};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct FrozenLog {
17 pub last_normal_view: u64,
19 pub accepted: Prefix,
21 pub committed: Prefix,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct RecoveredState {
33 pub scope: Scope,
35 pub log: FrozenLog,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub struct PromiseTicket {
47 scope: Scope,
48 generation: JournalGeneration,
49 log: FrozenLog,
50}
51
52impl PromiseTicket {
53 pub const fn scope(self) -> Scope {
55 self.scope
56 }
57 pub const fn generation(self) -> JournalGeneration {
59 self.generation
60 }
61 pub const fn log(self) -> FrozenLog {
63 self.log
64 }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub struct StartViewChange {
70 pub scope: Scope,
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub struct DoViewChange {
77 pub scope: Scope,
79 pub generation: JournalGeneration,
81 pub log: FrozenLog,
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub struct LogSource {
88 pub voter: NodeId,
90 pub generation: JournalGeneration,
92 pub accepted: Prefix,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub struct SelectedView {
102 scope: Scope,
103 source: LogSource,
104 committed: Prefix,
105 voters: u8,
106}
107
108impl SelectedView {
109 pub const fn scope(self) -> Scope {
111 self.scope
112 }
113 pub const fn source(self) -> LogSource {
115 self.source
116 }
117 pub const fn committed(self) -> Prefix {
119 self.committed
120 }
121 pub const fn voter_mask(self) -> u8 {
123 self.voters
124 }
125}
126
127#[derive(Debug)]
135pub struct ViewChange {
136 pub(crate) normal: NormalReplica,
137 pub(crate) configuration: Configuration,
138 pub(crate) local: usize,
139 view: u64,
140 promised: u64,
141 pending: Option<PromiseTicket>,
142 pub(crate) completed: Option<PromiseTicket>,
143 starts: u8,
144 reports: [Option<DoViewChange>; 3],
145 pub(crate) selected: Option<SelectedView>,
146}
147
148impl ViewChange {
149 pub(crate) fn after_abandon(
150 mut normal: NormalReplica,
151 previous: PromiseTicket,
152 requested_view: u64,
153 ) -> Result<Self, ViewChangeError> {
154 normal.restore_fenced(previous.generation(), previous.log());
155 let mut changing = normal.into_view_change(requested_view)?;
156 changing.promised = previous.scope().view;
157 changing.completed = Some(previous);
158 Ok(changing)
159 }
160
161 pub fn recover_intact(
175 configuration: Configuration,
176 local: NodeId,
177 generation: JournalGeneration,
178 recovered: RecoveredState,
179 limits: PipelineLimits,
180 ) -> Result<Self, ViewChangeError> {
181 if configuration.policy() == crate::QuorumPolicy::Replicated {
182 return Err(ViewChangeError::VolatileRestart);
183 }
184 Self::recover_verified(configuration, local, generation, recovered, limits)
185 }
186
187 pub fn recover_drained(
195 configuration: Configuration,
196 local: NodeId,
197 generation: JournalGeneration,
198 recovered: RecoveredState,
199 limits: PipelineLimits,
200 ) -> Result<Self, ViewChangeError> {
201 if configuration.policy() != crate::QuorumPolicy::Replicated {
202 return Err(ReplicationError::PolicyMismatch.into());
203 }
204 Self::recover_verified(configuration, local, generation, recovered, limits)
205 }
206
207 fn recover_verified(
208 configuration: Configuration,
209 local: NodeId,
210 generation: JournalGeneration,
211 recovered: RecoveredState,
212 limits: PipelineLimits,
213 ) -> Result<Self, ViewChangeError> {
214 if recovered.scope
215 != (Scope {
216 view: recovered.scope.view,
217 ..configuration.scope()
218 })
219 {
220 return Err(ReplicationError::ScopeMismatch.into());
221 }
222 if recovered.log.last_normal_view > recovered.scope.view {
223 return Err(ViewChangeError::InvalidReport);
224 }
225 let view = if recovered.scope.view > recovered.log.last_normal_view {
226 recovered.scope.view
227 } else {
228 recovered
229 .scope
230 .view
231 .checked_add(1)
232 .ok_or(ViewChangeError::ViewExhausted)?
233 };
234 validate_report(DoViewChange {
235 scope: Scope {
236 view,
237 ..recovered.scope
238 },
239 generation,
240 log: recovered.log,
241 })?;
242 let normal =
243 NormalReplica::recover_fenced(configuration, local, generation, recovered.log, limits)?;
244 let mut changing = normal.into_view_change(view)?;
245 changing.promised = recovered.scope.view;
246 if changing.promised == view {
247 changing.starts |= 1 << changing.local; changing.completed = Some(PromiseTicket {
249 scope: changing.scope(),
250 generation,
251 log: recovered.log,
252 });
253 }
254 Ok(changing)
255 }
256
257 pub(crate) fn begin(
258 mut normal: NormalReplica,
259 configuration: Configuration,
260 local: usize,
261 view: u64,
262 ) -> Result<Self, ViewChangeError> {
263 normal.fence();
264 if normal.snapshot().status == Status::Faulted {
265 return Err(ViewChangeError::Faulted);
266 }
267 if view <= configuration.scope().view {
268 return Err(ViewChangeError::ViewNotHigher);
269 }
270 Ok(Self {
271 normal,
272 configuration,
273 local,
274 view,
275 promised: configuration.scope().view,
276 pending: None,
277 completed: None,
278 starts: 0,
279 reports: [None; 3],
280 selected: None,
281 })
282 }
283
284 pub fn normal_snapshot(&self) -> ReplicaSnapshot {
286 self.normal.snapshot()
287 }
288
289 pub fn scope(&self) -> Scope {
291 Scope {
292 view: self.view,
293 ..self.configuration.scope()
294 }
295 }
296
297 pub const fn promised_view(&self) -> u64 {
299 self.promised
300 }
301
302 pub fn advance_view(&mut self, view: u64) -> Result<(), ViewChangeError> {
308 self.require_active()?;
309 if view <= self.view {
310 return Err(ViewChangeError::ViewNotHigher);
311 }
312 self.view = view;
313 self.starts = 0;
314 self.reports = [None; 3];
315 self.selected = None;
316 Ok(())
317 }
318
319 pub fn complete_write(&mut self, ticket: WriteTicket) -> Result<(), ViewChangeError> {
321 Ok(self.normal.complete_write(ticket)?)
322 }
323
324 pub fn begin_sync(&self) -> Result<SyncTicket, ViewChangeError> {
326 Ok(self.normal.begin_sync()?)
327 }
328
329 pub fn complete_sync(&mut self, ticket: SyncTicket) -> Result<(), ViewChangeError> {
331 Ok(self.normal.complete_sync(ticket)?)
332 }
333
334 pub fn fail_io(&mut self, generation: JournalGeneration) -> Result<(), ViewChangeError> {
336 Ok(self.normal.fail_io(generation)?)
337 }
338
339 pub fn begin_promise(&mut self) -> Result<PromiseTicket, ViewChangeError> {
344 self.require_active()?;
345 if self.pending.is_some() {
346 return Err(ViewChangeError::PromisePending);
347 }
348 if self.promised == self.view {
349 return Err(ViewChangeError::AlreadyPromised);
350 }
351 let snapshot = self.normal.snapshot();
352 if snapshot.journal.durable != snapshot.accepted.op {
353 return Err(ViewChangeError::StoragePending);
354 }
355 let ticket = PromiseTicket {
356 scope: self.scope(),
357 generation: snapshot.journal.generation,
358 log: FrozenLog {
359 last_normal_view: snapshot.scope.view,
360 accepted: snapshot.accepted,
361 committed: snapshot.committed,
362 },
363 };
364 self.pending = Some(ticket);
365 Ok(ticket)
366 }
367
368 pub fn complete_promise(&mut self, ticket: PromiseTicket) -> Result<(), ViewChangeError> {
370 self.require_active()?;
371 if self.completed == Some(ticket) {
372 return Ok(());
373 }
374 self.require_ticket(ticket)?;
375 self.pending = None;
376 self.completed = Some(ticket);
377 self.promised = ticket.scope.view;
378 if self.promised == self.view {
379 self.starts |= 1 << self.local;
380 }
381 Ok(())
382 }
383
384 pub fn fail_promise(&mut self, ticket: PromiseTicket) -> Result<(), ViewChangeError> {
386 self.require_active()?;
387 self.require_ticket(ticket)?;
388 self.fail_io(ticket.generation)
389 }
390
391 pub fn start_message(&self) -> Result<StartViewChange, ViewChangeError> {
393 self.require_promised()?;
394 Ok(StartViewChange {
395 scope: self.scope(),
396 })
397 }
398
399 pub fn receive_start(
402 &mut self,
403 from: NodeId,
404 message: StartViewChange,
405 ) -> Result<(), ViewChangeError> {
406 self.require_scope(message.scope)?;
407 let voter = self.configuration.voter_index(from)?;
408 if voter == self.local {
409 return Err(ReplicationError::WrongRole.into());
410 }
411 self.starts |= 1 << voter;
412 Ok(())
413 }
414
415 pub fn report(&mut self) -> Result<DoViewChange, ViewChangeError> {
418 self.require_promised()?;
419 if self.starts.count_ones() < 2 {
420 return Err(ViewChangeError::StartQuorumMissing);
421 }
422 let promise = self.completed.expect("requested view promised");
423 let report = DoViewChange {
424 scope: self.scope(),
425 generation: promise.generation,
426 log: promise.log,
427 };
428 self.reports[self.local] = Some(report);
429 Ok(report)
430 }
431
432 pub fn receive_report(
440 &mut self,
441 from: NodeId,
442 report: DoViewChange,
443 ) -> Result<(), ViewChangeError> {
444 self.require_scope(report.scope)?;
445 self.require_primary()?;
446 let voter = self.configuration.voter_index(from)?;
447 if voter == self.local {
448 return Err(ReplicationError::WrongRole.into());
449 }
450 validate_report(report)?;
451 if let Some(previous) = self.reports[voter] {
452 if previous.log != report.log {
453 return Err(self.conflict());
454 }
455 return Ok(());
460 }
461 if self.selected.is_some() {
462 return Err(ViewChangeError::SelectionFrozen);
463 }
464 self.reports[voter] = Some(report);
465 Ok(())
466 }
467
468 pub fn select(
480 &mut self,
481 mut lookup: impl FnMut(LogSource, OpNumber) -> Option<Digest>,
482 ) -> Result<SelectedView, ViewChangeError> {
483 self.require_promised()?;
484 self.require_primary()?;
485 if let Some(selected) = self.selected {
486 return Ok(selected);
487 }
488 let reports = self.reports;
489 if reports[self.local].is_none() || reports.iter().flatten().count() < 2 {
490 return Err(ViewChangeError::ReportQuorumMissing);
491 }
492 let mut chosen = self.local;
493 let mut voters = 0;
494 for (index, report) in reports.iter().enumerate() {
495 let Some(report) = report else {
496 continue;
497 };
498 voters |= 1 << index;
499 let current = reports[chosen].expect("selected report");
500 let rank = (report.log.last_normal_view, report.log.accepted.op);
501 let best = (current.log.last_normal_view, current.log.accepted.op);
502 if rank == best && report.log.accepted != current.log.accepted {
503 return Err(self.conflict());
504 }
505 if rank > best {
506 chosen = index;
507 }
508 }
509 let report = reports[chosen].expect("selected report");
510 let source = LogSource {
511 voter: self.configuration.voters()[chosen],
512 generation: report.generation,
513 accepted: report.log.accepted,
514 };
515 let mut committed = Prefix::GENESIS;
516 for candidate in reports.iter().flatten() {
517 if candidate.log.committed.op > committed.op {
518 committed = candidate.log.committed;
519 }
520 self.verify_prefix(source, candidate.log.committed, &mut lookup)?;
521 if candidate.log.last_normal_view == report.log.last_normal_view {
522 self.verify_prefix(source, candidate.log.accepted, &mut lookup)?;
523 }
524 }
525 let selected = SelectedView {
526 scope: self.scope(),
527 source,
528 committed,
529 voters,
530 };
531 self.selected = Some(selected);
532 Ok(selected)
533 }
534
535 pub(crate) fn verify_prefix(
536 &mut self,
537 source: LogSource,
538 prefix: Prefix,
539 lookup: &mut impl FnMut(LogSource, OpNumber) -> Option<Digest>,
540 ) -> Result<(), ViewChangeError> {
541 if prefix.op > source.accepted.op {
542 return Err(self.conflict());
543 }
544 let digest = if prefix.op == OpNumber(0) {
545 Digest::ZERO
546 } else if prefix.op == source.accepted.op {
547 source.accepted.digest
548 } else {
549 lookup(source, prefix.op).ok_or(ViewChangeError::HistoryMissing)?
550 };
551 if digest != prefix.digest {
552 return Err(self.conflict());
553 }
554 Ok(())
555 }
556
557 fn require_active(&self) -> Result<(), ViewChangeError> {
558 if self.normal.snapshot().status == Status::Faulted {
559 return Err(ViewChangeError::Faulted);
560 }
561 Ok(())
562 }
563
564 pub(crate) fn require_scope(&self, scope: Scope) -> Result<(), ViewChangeError> {
565 self.require_active()?;
566 if scope != self.scope() {
567 return Err(ReplicationError::ScopeMismatch.into());
568 }
569 Ok(())
570 }
571
572 pub(crate) fn require_primary(&self) -> Result<(), ViewChangeError> {
573 if self.configuration.primary(self.view) != self.configuration.voters()[self.local] {
574 return Err(ReplicationError::WrongRole.into());
575 }
576 Ok(())
577 }
578
579 pub(crate) fn require_promised(&self) -> Result<(), ViewChangeError> {
580 self.require_active()?;
581 if self.promised != self.view {
582 return Err(ViewChangeError::PromiseRequired);
583 }
584 Ok(())
585 }
586
587 fn require_ticket(&self, ticket: PromiseTicket) -> Result<(), ViewChangeError> {
588 if self.pending != Some(ticket) {
589 return Err(ViewChangeError::StalePromise);
590 }
591 Ok(())
592 }
593
594 fn conflict(&mut self) -> ViewChangeError {
595 self.normal
596 .fail_io(self.normal.snapshot().journal.generation)
597 .expect("current generation");
598 ViewChangeError::ConflictingHistory
599 }
600}
601
602pub(crate) fn valid_prefix(prefix: Prefix) -> bool {
603 prefix.op.0 != u64::MAX && ((prefix.op.0 == 0) == (prefix.digest == Digest::ZERO))
604}
605
606fn validate_report(report: DoViewChange) -> Result<(), ViewChangeError> {
607 let log = report.log;
608 if log.last_normal_view >= report.scope.view
609 || !valid_prefix(log.accepted)
610 || !valid_prefix(log.committed)
611 || log.committed.op > log.accepted.op
612 || (log.committed.op == log.accepted.op && log.committed != log.accepted)
613 {
614 return Err(ViewChangeError::InvalidReport);
615 }
616 Ok(())
617}
618
619#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
621pub enum ViewChangeError {
622 #[error("retained-memory voter restart requires fresh recovery or drained-state evidence")]
624 VolatileRestart,
625 #[error(transparent)]
627 Replication(#[from] ReplicationError),
628 #[error("view must increase")]
630 ViewNotHigher,
631 #[error("durable view number exhausted")]
633 ViewExhausted,
634 #[error("old-view journal work has not settled")]
636 StoragePending,
637 #[error("a view promise is already pending")]
639 PromisePending,
640 #[error("requested view already promised")]
642 AlreadyPromised,
643 #[error("stale or foreign view-promise completion")]
645 StalePromise,
646 #[error("requested view promise has not completed")]
648 PromiseRequired,
649 #[error("start-view-change quorum missing")]
651 StartQuorumMissing,
652 #[error("do-view-change quorum missing")]
654 ReportQuorumMissing,
655 #[error("selected lineage evidence missing")]
657 HistoryMissing,
658 #[error("invalid frozen view-change report")]
660 InvalidReport,
661 #[error("conflicting view-change history")]
663 ConflictingHistory,
664 #[error("view selection already frozen")]
666 SelectionFrozen,
667 #[error("view has not been selected")]
669 SelectionRequired,
670 #[error("installation reused the old writer generation")]
672 ReusedGeneration,
673 #[error("stale or foreign installation completion")]
675 StaleInstallation,
676 #[error("installation already completed")]
678 InstallationCompleted,
679 #[error("installed application state is not at the required committed prefix")]
681 ApplicationPending,
682 #[error("view change faulted")]
684 Faulted,
685}