use crate::model::inventory_settlement::{IcInventoryAttribution, IcInventorySettlement};
use super::ic_observation::IcObservationResponseView;
use super::{
settlement_identity::{SettlementIdentity, SettlementIdentityMismatch},
snapshot_inventory_delta::{SnapshotInventoryDeltaError, compare_inventories},
};
use crate::model::{
artifacts::ArtifactChecksumRecord,
attempt_journal::ObservationOutcomeRecord,
ic_snapshot_reply::{IcSnapshotInfo, IcSnapshotReply},
};
fn compare_identity(
claimed: &IcInventorySettlement,
operation: &SettlementIdentity<'_>,
baseline: &ArtifactChecksumRecord,
inventory: &ArtifactChecksumRecord,
observation_evidence: &ArtifactChecksumRecord,
) -> Result<(), InventorySettlementMismatch> {
SettlementIdentity::from(&claimed.context).compare(operation)?;
if &claimed.baseline != baseline {
return Err(InventorySettlementMismatch::Baseline);
}
if &claimed.inventory != inventory {
return Err(InventorySettlementMismatch::Inventory);
}
if &claimed.context.observation_evidence != observation_evidence {
return Err(InventorySettlementMismatch::ObservationEvidence);
}
Ok(())
}
#[derive(Debug, thiserror::Error, Eq, PartialEq)]
pub enum InventorySettlementMismatch {
#[error("inventory settlement authority differs")]
Authority,
#[error("inventory settlement attempts differ")]
Attempt,
#[error("inventory settlement challenge differs")]
Challenge,
#[error("inventory settlement baseline differs")]
Baseline,
#[error("inventory settlement inventory differs")]
Inventory,
#[error("inventory settlement observation evidence differs")]
ObservationEvidence,
}
impl From<SettlementIdentityMismatch> for InventorySettlementMismatch {
fn from(value: SettlementIdentityMismatch) -> Self {
match value {
SettlementIdentityMismatch::Authority => Self::Authority,
SettlementIdentityMismatch::Attempt => Self::Attempt,
SettlementIdentityMismatch::Challenge => Self::Challenge,
}
}
}
pub(crate) fn validate_pair<'a, E>(
target: &str,
baseline: &IcSnapshotReply<'_>,
inventory: &'a IcSnapshotReply<'_>,
claimed: &IcInventorySettlement,
operation: &SettlementIdentity<'_>,
observation_evidence: &ArtifactChecksumRecord,
) -> Result<Vec<&'a IcSnapshotInfo>, E>
where
E: From<SnapshotInventoryDeltaError> + From<InventorySettlementMismatch>,
{
let candidates = compare_inventories(target, baseline, inventory)?;
compare_identity(
claimed,
operation,
&baseline.digest(),
&inventory.digest(),
observation_evidence,
)?;
Ok(candidates)
}
#[derive(Debug)]
pub(crate) enum InventorySettlementOutcome {
Applied(usize),
NotApplied,
Uncertain,
}
impl InventorySettlementOutcome {
pub const fn outcome(&self) -> ObservationOutcomeRecord {
match self {
Self::Applied(_) => ObservationOutcomeRecord::Applied,
Self::NotApplied => ObservationOutcomeRecord::NotApplied,
Self::Uncertain => ObservationOutcomeRecord::Uncertain,
}
}
pub const fn snapshot_index(&self) -> Option<usize> {
match self {
Self::Applied(index) => Some(*index),
Self::NotApplied | Self::Uncertain => None,
}
}
}
pub(crate) fn admitted_outcome(
inventory: &IcSnapshotReply<'_>,
candidates: &[&IcSnapshotInfo],
attribution: &IcInventoryAttribution,
) -> Result<InventorySettlementOutcome, ()> {
match attribution {
IcInventoryAttribution::Applied { snapshot_id, .. } => {
new_candidate_index(inventory, candidates, snapshot_id)
.map(InventorySettlementOutcome::Applied)
.ok_or(())
}
IcInventoryAttribution::NotApplied { .. } => Ok(InventorySettlementOutcome::NotApplied),
IcInventoryAttribution::Unresolved { .. } => Ok(InventorySettlementOutcome::Uncertain),
}
}
pub(crate) fn new_candidate_index(
inventory: &IcSnapshotReply<'_>,
candidates: &[&IcSnapshotInfo],
snapshot_id: &[u8],
) -> Option<usize> {
if !candidates
.iter()
.any(|snapshot| snapshot.id() == snapshot_id)
{
return None;
}
inventory
.snapshots()
.binary_search_by(|snapshot| snapshot.id().cmp(snapshot_id))
.ok()
}
#[derive(Debug)]
pub struct IcInventorySettlementView<'a> {
pub(crate) observation: IcObservationResponseView<'a>,
pub(crate) baseline: &'a IcSnapshotReply<'a>,
pub(crate) settlement: &'a IcInventorySettlement,
pub(crate) resolution: InventorySettlementOutcome,
}
impl<'a> IcInventorySettlementView<'a> {
#[must_use]
pub const fn outcome(&self) -> ObservationOutcomeRecord {
self.resolution.outcome()
}
#[must_use]
pub const fn baseline(&self) -> &'a IcSnapshotReply<'a> {
self.baseline
}
#[must_use]
pub const fn observation(&self) -> &IcObservationResponseView<'a> {
&self.observation
}
#[must_use]
pub const fn settlement(&self) -> &'a IcInventorySettlement {
self.settlement
}
#[must_use]
pub fn snapshot(&self) -> Option<&IcSnapshotInfo> {
self.observation
.inventory_snapshot(self.resolution.snapshot_index()?)
}
}
#[cfg(test)]
mod tests;