use crate::{FrozenLog, Prefix, PreparedOperation, RecoveredState, ReplicationError};
use super::{Recovery, RecoveryError, RecoveryTicket, Transfer};
impl Recovery {
pub fn complete_checkpoint(
&mut self,
ticket: RecoveryTicket,
anchor: super::CheckpointAnchor,
state_revision: u64,
) -> Result<(), RecoveryError> {
let mut pending = self.require_ticket(&ticket)?;
if ticket.checkpoint() != Some(anchor) {
return Err(RecoveryError::StaleTransfer);
}
if state_revision != anchor.position.op.0 {
return Err(RecoveryError::ApplicationPending);
}
pending.checkpoint_ready = true;
self.pending = Some(pending);
Ok(())
}
pub fn validate_chunk(
&mut self,
ticket: RecoveryTicket,
operations: &[PreparedOperation],
) -> Result<(), RecoveryError> {
let mut pending = self.require_ticket(&ticket)?;
if !pending.checkpoint_ready {
return Err(RecoveryError::ApplicationPending);
}
if operations.is_empty() {
return Err(ReplicationError::HistoryGap.into());
}
if operations.len() > self.limits.max_operations {
return Err(ReplicationError::Capacity.into());
}
let mut through = pending.through;
let mut bytes = 0_usize;
for operation in operations {
bytes = bytes
.checked_add(operation.body_bytes)
.ok_or(ReplicationError::Capacity)?;
if bytes > self.limits.max_body_bytes {
return Err(ReplicationError::Capacity.into());
}
if operation.group_id != ticket.scope.group_id
|| operation.configuration_epoch != ticket.scope.configuration_epoch
|| operation.original_view > ticket.scope.view
{
return Err(ReplicationError::ScopeMismatch.into());
}
if through.op.0.checked_add(1) != Some(operation.prefix.op.0) {
return Err(ReplicationError::HistoryGap.into());
}
if operation.previous_digest != through.digest
|| operation.prefix.op > ticket.source.accepted.op
|| (operation.prefix.op == ticket.source.accepted.op
&& operation.prefix != ticket.source.accepted)
|| (operation.prefix.op == ticket.committed.op
&& operation.prefix != ticket.committed)
|| ticket.checkpoint.is_some_and(|anchor| {
operation.prefix.op == anchor.position.op && operation.prefix != anchor.position
})
{
return Err(ReplicationError::ConflictingHistory.into());
}
through = operation.prefix;
}
pending.through = through;
self.pending = Some(pending);
Ok(())
}
pub fn complete(
&mut self,
ticket: RecoveryTicket,
durable: Prefix,
applied: Prefix,
) -> Result<RecoveredState, RecoveryError> {
let pending = self.require_ticket(&ticket)?;
if !pending.checkpoint_ready {
return Err(RecoveryError::ApplicationPending);
}
if pending.through != ticket.source.accepted {
return Err(RecoveryError::HistoryMissing);
}
if durable != ticket.source.accepted {
return Err(RecoveryError::StoragePending);
}
if applied != ticket.committed {
return Err(RecoveryError::ApplicationPending);
}
self.pending = None;
self.completed = true;
Ok(RecoveredState {
scope: ticket.scope,
log: FrozenLog {
last_normal_view: ticket.scope.view,
accepted: ticket.source.accepted,
committed: ticket.committed,
},
})
}
pub fn fail(&mut self, ticket: RecoveryTicket) -> Result<(), RecoveryError> {
self.pending_ticket(&ticket)?;
self.faulted = true;
Ok(())
}
pub fn complete_repair(
&mut self,
ticket: RecoveryTicket,
repaired: RecoveredState,
) -> Result<RecoveredState, RecoveryError> {
self.require_ticket(&ticket)?;
if repaired.scope
!= (crate::Scope {
view: repaired.scope.view,
..ticket.scope
})
|| repaired.scope.view < ticket.scope.view
|| repaired.log.last_normal_view > repaired.scope.view
|| [repaired.log.accepted, repaired.log.committed]
.iter()
.any(|prefix| (prefix.op.0 == 0) != (prefix.digest == crate::Digest::ZERO))
|| repaired.log.committed.op > repaired.log.accepted.op
|| (repaired.log.committed.op == repaired.log.accepted.op
&& repaired.log.committed != repaired.log.accepted)
{
return Err(RecoveryError::StoragePending);
}
self.pending = None;
self.completed = true;
Ok(repaired)
}
fn pending_ticket(&self, ticket: &RecoveryTicket) -> Result<Transfer, RecoveryError> {
self.require_active()?;
self.pending
.filter(|pending| pending.ticket == *ticket)
.ok_or(RecoveryError::StaleTransfer)
}
fn require_ticket(&self, ticket: &RecoveryTicket) -> Result<Transfer, RecoveryError> {
let pending = self.pending_ticket(ticket)?;
if self.view > ticket.scope.view {
return Err(RecoveryError::StaleView);
}
Ok(pending)
}
}