use alloc::vec::Vec;
use crate::algebra::{ResourceVector, WideResourceVector};
use crate::lifecycle::{
ClosureAccounting, OrdinaryProjectionError, ParticipantConversation, RestoredLifecycleParts,
RestoredParticipantLifecycle, RetainedRecordCharge,
};
use crate::wire::CredentialDigest;
use super::LiveFrontierOwner;
pub type CheckpointParticipant = RestoredParticipantLifecycle<
CredentialDigest,
CredentialDigest,
CredentialDigest,
CredentialDigest,
>;
#[derive(Debug)]
#[non_exhaustive]
pub struct RestoredLiveFrontierOwner {
pub conversation: ParticipantConversation,
pub participants: Vec<CheckpointParticipant>,
pub owner: LiveFrontierOwner,
pub cursor_episode: Option<crate::lifecycle::NonzeroDebtCursorEpisode>,
pub(in crate::lifecycle) observer_progress: u64,
}
#[derive(Debug, PartialEq, Eq)]
pub enum LiveFrontierRestoreError {
MissingLifecycle,
MissingClosureAccounting,
MissingRetainedCharges,
MissingCursorEpisode,
UnexpectedCursorEpisode,
AccountingCodec(crate::lifecycle::FrontierAccountingDecodeError),
RetainedChargeCodec(crate::lifecycle::CapsuleDecodeError),
CursorEpisode(crate::lifecycle::StorageRestoreError),
CursorEpisodeEncoding(crate::lifecycle::CursorFactEncodeError),
ForeignConversation,
ClosureMismatch,
RetainedRecordLimit,
Accounting(OrdinaryProjectionError),
}
impl LiveFrontierOwner {
fn restore_checkpoint_parts(
parts: RestoredLifecycleParts,
accounting: ClosureAccounting,
charges: Vec<RetainedRecordCharge>,
cursor_episode: Option<crate::lifecycle::NonzeroDebtCursorEpisode>,
saved: FrontierCheckpointSource<'_>,
) -> Result<RestoredLiveFrontierOwner, LiveFrontierRestoreError> {
let lifecycle = parts
.lifecycle
.ok_or(LiveFrontierRestoreError::MissingLifecycle)?;
if lifecycle.frontiers().conversation_id() != parts.conversation.conversation_id() {
return Err(LiveFrontierRestoreError::ForeignConversation);
}
if lifecycle.closure() != accounting.state() {
return Err(LiveFrontierRestoreError::ClosureMismatch);
}
if lifecycle.frontiers().retained_records().len() as u128
> u128::from(saved.retained_record_limit)
.saturating_add(u128::from(lifecycle.frontiers().identity_slot_limit()))
{
return Err(LiveFrontierRestoreError::RetainedRecordLimit);
}
validate_saved_accounting(
lifecycle.frontiers(),
&accounting,
&charges,
saved.marker_max,
)
.map_err(LiveFrontierRestoreError::Accounting)?;
let (participants, frontiers, _) = lifecycle.into_parts();
Ok(RestoredLiveFrontierOwner {
conversation: parts.conversation,
participants,
cursor_episode,
observer_progress: saved.observer_progress,
owner: Self {
frontiers,
closure_accounting: accounting,
retained_charges: charges,
retained_record_limit: saved.retained_record_limit,
},
})
}
}
fn validate_saved_accounting(
frontiers: &crate::lifecycle::ClaimFrontiers,
accounting: &ClosureAccounting,
charges: &[RetainedRecordCharge],
marker_max: ResourceVector,
) -> Result<(), OrdinaryProjectionError> {
let records = frontiers.retained_records();
let high_end = u128::from(frontiers.sequence().ledger().high_watermark()) + 1;
if high_end.checked_sub(frontiers.retained_floor()) != Some(records.len() as u128)
|| records.len() != charges.len()
{
return Err(OrdinaryProjectionError::RetainedSuffix);
}
let credited: Vec<_> = frontiers
.retained_marker_records()
.iter()
.map(|record| record.delivery_seq)
.collect();
let mut retained = WideResourceVector::default();
for (index, (record, charge)) in records.iter().zip(charges).enumerate() {
if u128::from(record.delivery_seq) != frontiers.retained_floor() + index as u128
|| charge.delivery_seq() != record.delivery_seq
|| charge.admission_order() != record.admission_order
{
return Err(OrdinaryProjectionError::RetainedChargeKey {
index: u64::try_from(index).map_err(|_| OrdinaryProjectionError::RetainedSuffix)?,
});
}
let resource = charge.encoded_charge();
if resource.entries != 1 {
return Err(OrdinaryProjectionError::RetainedEntryCharge {
delivery_seq: charge.delivery_seq(),
entries: resource.entries,
});
}
let resource = if credited.contains(&record.delivery_seq) {
marker_max
} else {
resource
};
retained =
super::super::ordinary_record_projection::checked_add_resource(retained, resource)?;
}
let pending = frontiers.pending_marker_candidate_count();
let retained_credits = u64::try_from(frontiers.retained_marker_records().len())
.map_err(|_| OrdinaryProjectionError::MarkerCredit)?;
let credits = retained_credits
.checked_add(pending)
.ok_or(OrdinaryProjectionError::MarkerCredit)?;
if credits != accounting.marker_capacity_credits() {
return Err(OrdinaryProjectionError::MarkerCreditAccounting {
derived: credits,
stored: accounting.marker_capacity_credits(),
});
}
let anchors = frontiers
.unaccepted_marker_anchor_count()
.checked_add(pending)
.ok_or(OrdinaryProjectionError::MarkerAnchor)?;
if anchors != accounting.marker_anchors() {
return Err(OrdinaryProjectionError::MarkerAnchorAccounting {
derived: anchors,
stored: accounting.marker_anchors(),
});
}
let baseline = super::super::ordinary_record_projection::retained_baseline_wide(
retained,
frontiers.identity_slot_limit(),
retained_credits,
marker_max,
)?;
if baseline != accounting.baseline() {
return Err(OrdinaryProjectionError::BaselineAccounting {
derived: baseline,
stored: accounting.baseline(),
});
}
Ok(())
}
impl LiveFrontierOwner {
pub fn restore_checkpoint(
parts: RestoredLifecycleParts,
saved: FrontierCheckpointSource<'_>,
) -> Result<RestoredLiveFrontierOwner, LiveFrontierRestoreError> {
let lifecycle = parts
.lifecycle
.as_ref()
.ok_or(LiveFrontierRestoreError::MissingLifecycle)?;
let accounting = Self::decode_closure_accounting_source(
lifecycle.closure(),
saved
.closure_accounting
.ok_or(LiveFrontierRestoreError::MissingClosureAccounting)?,
)
.map_err(LiveFrontierRestoreError::AccountingCodec)?;
let charges = Self::decode_retained_charge_source(
saved
.retained_charges
.ok_or(LiveFrontierRestoreError::MissingRetainedCharges)?,
)
.map_err(LiveFrontierRestoreError::RetainedChargeCodec)?;
let episode = match lifecycle.closure() {
crate::lifecycle::ClosureState::Clear => {
if saved.cursor_episode.is_some() {
return Err(LiveFrontierRestoreError::UnexpectedCursorEpisode);
}
None
}
crate::lifecycle::ClosureState::Owed { debt, .. } => {
let bytes = saved
.cursor_episode
.ok_or(LiveFrontierRestoreError::MissingCursorEpisode)?;
Some(
crate::lifecycle::NonzeroDebtCursorEpisode::decode_checkpoint(
bytes,
lifecycle.frontiers(),
debt,
saved.observer_progress,
)
.map_err(LiveFrontierRestoreError::CursorEpisode)?,
)
}
};
Self::restore_checkpoint_parts(parts, accounting, charges, episode, saved)
}
}
#[derive(Clone, Copy, Debug)]
pub struct FrontierCheckpointSource<'a> {
pub closure_accounting: Option<&'a [u8]>,
pub retained_charges: Option<&'a [u8]>,
pub cursor_episode: Option<&'a [u8]>,
pub observer_progress: u64,
pub retained_record_limit: u64,
pub marker_max: ResourceVector,
}