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 let mut pending = 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 pending.checkpoint_ready = true;
25 self.pending = Some(pending);
26 Ok(())
27 }
28
29 pub fn validate_chunk(
36 &mut self,
37 ticket: RecoveryTicket,
38 operations: &[PreparedOperation],
39 ) -> Result<(), RecoveryError> {
40 let mut pending = self.require_ticket(&ticket)?;
41 if !pending.checkpoint_ready {
42 return Err(RecoveryError::ApplicationPending);
43 }
44 if operations.is_empty() {
45 return Err(ReplicationError::HistoryGap.into());
46 }
47 if operations.len() > self.limits.max_operations {
48 return Err(ReplicationError::Capacity.into());
49 }
50 let mut through = pending.through;
51 let mut bytes = 0_usize;
52 for operation in operations {
53 bytes = bytes
54 .checked_add(operation.body_bytes)
55 .ok_or(ReplicationError::Capacity)?;
56 if bytes > self.limits.max_body_bytes {
57 return Err(ReplicationError::Capacity.into());
58 }
59 if operation.group_id != ticket.scope.group_id
60 || operation.configuration_epoch != ticket.scope.configuration_epoch
61 || operation.original_view > ticket.scope.view
62 {
63 return Err(ReplicationError::ScopeMismatch.into());
64 }
65 if through.op.0.checked_add(1) != Some(operation.prefix.op.0) {
66 return Err(ReplicationError::HistoryGap.into());
67 }
68 if operation.previous_digest != through.digest
69 || operation.prefix.op > ticket.source.accepted.op
70 || (operation.prefix.op == ticket.source.accepted.op
71 && operation.prefix != ticket.source.accepted)
72 || (operation.prefix.op == ticket.committed.op
73 && operation.prefix != ticket.committed)
74 || ticket.checkpoint.is_some_and(|anchor| {
75 operation.prefix.op == anchor.position.op && operation.prefix != anchor.position
76 })
77 {
78 return Err(ReplicationError::ConflictingHistory.into());
79 }
80 through = operation.prefix;
81 }
82 pending.through = through;
83 self.pending = Some(pending);
84 Ok(())
85 }
86
87 pub fn complete(
99 &mut self,
100 ticket: RecoveryTicket,
101 durable: Prefix,
102 applied: Prefix,
103 ) -> Result<RecoveredState, RecoveryError> {
104 let pending = self.require_ticket(&ticket)?;
105 if !pending.checkpoint_ready {
106 return Err(RecoveryError::ApplicationPending);
107 }
108 if pending.through != ticket.source.accepted {
109 return Err(RecoveryError::HistoryMissing);
110 }
111 if durable != ticket.source.accepted {
112 return Err(RecoveryError::StoragePending);
113 }
114 if applied != ticket.committed {
115 return Err(RecoveryError::ApplicationPending);
116 }
117 self.pending = None;
118 self.completed = true;
119 Ok(RecoveredState {
120 scope: ticket.scope,
121 log: FrozenLog {
122 last_normal_view: ticket.scope.view,
123 accepted: ticket.source.accepted,
124 committed: ticket.committed,
125 },
126 })
127 }
128
129 pub fn fail(&mut self, ticket: RecoveryTicket) -> Result<(), RecoveryError> {
133 self.pending_ticket(&ticket)?;
134 self.faulted = true;
135 Ok(())
136 }
137
138 pub fn complete_repair(
143 &mut self,
144 ticket: RecoveryTicket,
145 repaired: RecoveredState,
146 ) -> Result<RecoveredState, RecoveryError> {
147 self.require_ticket(&ticket)?;
148 if repaired.scope
149 != (crate::Scope {
150 view: repaired.scope.view,
151 ..ticket.scope
152 })
153 || repaired.scope.view < ticket.scope.view
154 || repaired.log.last_normal_view > repaired.scope.view
155 || [repaired.log.accepted, repaired.log.committed]
156 .iter()
157 .any(|prefix| (prefix.op.0 == 0) != (prefix.digest == crate::Digest::ZERO))
158 || repaired.log.committed.op > repaired.log.accepted.op
159 || (repaired.log.committed.op == repaired.log.accepted.op
160 && repaired.log.committed != repaired.log.accepted)
161 {
162 return Err(RecoveryError::StoragePending);
163 }
164 self.pending = None;
165 self.completed = true;
166 Ok(repaired)
167 }
168
169 fn pending_ticket(&self, ticket: &RecoveryTicket) -> Result<Transfer, RecoveryError> {
170 self.require_active()?;
171 self.pending
172 .filter(|pending| pending.ticket == *ticket)
173 .ok_or(RecoveryError::StaleTransfer)
174 }
175
176 fn require_ticket(&self, ticket: &RecoveryTicket) -> Result<Transfer, RecoveryError> {
177 let pending = self.pending_ticket(ticket)?;
178 if self.view > ticket.scope.view {
179 return Err(RecoveryError::StaleView);
180 }
181 Ok(pending)
182 }
183}