ozzy_replication/recovery/
transfer.rs1use crate::{FrozenLog, Prefix, PreparedOperation, RecoveredState, ReplicationError};
4
5use super::{Recovery, RecoveryError, RecoveryTicket, Transfer};
6
7impl Recovery {
8 pub fn complete_checkpoint(
12 &mut self,
13 ticket: RecoveryTicket,
14 anchor: super::CheckpointAnchor,
15 state_revision: u64,
16 ) -> Result<(), RecoveryError> {
17 self.require_ticket(&ticket)?;
18 if ticket.checkpoint() != Some(anchor) {
19 return Err(RecoveryError::StaleTransfer);
20 }
21 if state_revision != anchor.position.op.0 {
22 return Err(RecoveryError::ApplicationPending);
23 }
24 self.pending
25 .as_mut()
26 .expect("checked transfer")
27 .checkpoint_ready = true;
28 Ok(())
29 }
30
31 pub fn validate_chunk(
38 &mut self,
39 ticket: RecoveryTicket,
40 operations: &[PreparedOperation],
41 ) -> Result<(), RecoveryError> {
42 let pending = self.require_ticket(&ticket)?;
43 if !pending.checkpoint_ready {
44 return Err(RecoveryError::ApplicationPending);
45 }
46 if operations.is_empty() {
47 return Err(ReplicationError::HistoryGap.into());
48 }
49 if operations.len() > self.limits.max_operations {
50 return Err(ReplicationError::Capacity.into());
51 }
52 let mut through = pending.through;
53 let mut bytes = 0_usize;
54 for operation in operations {
55 bytes = bytes
56 .checked_add(operation.body_bytes)
57 .ok_or(ReplicationError::Capacity)?;
58 if bytes > self.limits.max_body_bytes {
59 return Err(ReplicationError::Capacity.into());
60 }
61 if operation.group_id != ticket.scope.group_id
62 || operation.configuration_epoch != ticket.scope.configuration_epoch
63 || operation.original_view > ticket.scope.view
64 {
65 return Err(ReplicationError::ScopeMismatch.into());
66 }
67 if through.op.0.checked_add(1) != Some(operation.prefix.op.0) {
68 return Err(ReplicationError::HistoryGap.into());
69 }
70 if operation.previous_digest != through.digest
71 || operation.prefix.op > ticket.source.accepted.op
72 || (operation.prefix.op == ticket.source.accepted.op
73 && operation.prefix != ticket.source.accepted)
74 || (operation.prefix.op == ticket.committed.op
75 && operation.prefix != ticket.committed)
76 || ticket.checkpoint.is_some_and(|anchor| {
77 operation.prefix.op == anchor.position.op && operation.prefix != anchor.position
78 })
79 {
80 return Err(ReplicationError::ConflictingHistory.into());
81 }
82 through = operation.prefix;
83 }
84 self.pending
85 .as_mut()
86 .expect("checked pending transfer")
87 .through = through;
88 Ok(())
89 }
90
91 pub fn complete(
103 &mut self,
104 ticket: RecoveryTicket,
105 durable: Prefix,
106 applied: Prefix,
107 ) -> Result<RecoveredState, RecoveryError> {
108 let pending = self.require_ticket(&ticket)?;
109 if !pending.checkpoint_ready {
110 return Err(RecoveryError::ApplicationPending);
111 }
112 if pending.through != ticket.source.accepted {
113 return Err(RecoveryError::HistoryMissing);
114 }
115 if durable != ticket.source.accepted {
116 return Err(RecoveryError::StoragePending);
117 }
118 if applied != ticket.committed {
119 return Err(RecoveryError::ApplicationPending);
120 }
121 self.pending = None;
122 self.completed = true;
123 Ok(RecoveredState {
124 scope: ticket.scope,
125 log: FrozenLog {
126 last_normal_view: ticket.scope.view,
127 accepted: ticket.source.accepted,
128 committed: ticket.committed,
129 },
130 })
131 }
132
133 pub fn fail(&mut self, ticket: RecoveryTicket) -> Result<(), RecoveryError> {
137 self.pending_ticket(&ticket)?;
138 self.faulted = true;
139 Ok(())
140 }
141
142 pub fn complete_repair(
147 &mut self,
148 ticket: RecoveryTicket,
149 repaired: RecoveredState,
150 ) -> Result<RecoveredState, RecoveryError> {
151 self.require_ticket(&ticket)?;
152 if repaired.scope
153 != (crate::Scope {
154 view: repaired.scope.view,
155 ..ticket.scope
156 })
157 || repaired.scope.view < ticket.scope.view
158 || repaired.log.last_normal_view > repaired.scope.view
159 || [repaired.log.accepted, repaired.log.committed]
160 .iter()
161 .any(|prefix| (prefix.op.0 == 0) != (prefix.digest == crate::Digest::ZERO))
162 || repaired.log.committed.op > repaired.log.accepted.op
163 || (repaired.log.committed.op == repaired.log.accepted.op
164 && repaired.log.committed != repaired.log.accepted)
165 {
166 return Err(RecoveryError::StoragePending);
167 }
168 self.pending = None;
169 self.completed = true;
170 Ok(repaired)
171 }
172
173 fn pending_ticket(&self, ticket: &RecoveryTicket) -> Result<Transfer, RecoveryError> {
174 self.require_active()?;
175 self.pending
176 .filter(|pending| pending.ticket == *ticket)
177 .ok_or(RecoveryError::StaleTransfer)
178 }
179
180 fn require_ticket(&self, ticket: &RecoveryTicket) -> Result<Transfer, RecoveryError> {
181 let pending = self.pending_ticket(ticket)?;
182 if self.view > ticket.scope.view {
183 return Err(RecoveryError::StaleView);
184 }
185 Ok(pending)
186 }
187}