ic_backup/policy/inventory_settlement/
mod.rs1use crate::model::inventory_settlement::{IcInventoryAttribution, IcInventorySettlement};
4
5use super::ic_observation::IcObservationResponseView;
6
7use super::{
8 settlement_identity::{SettlementIdentity, SettlementIdentityMismatch},
9 snapshot_inventory_delta::{SnapshotInventoryDeltaError, compare_inventories},
10};
11use crate::model::{
12 artifacts::ArtifactChecksumRecord,
13 attempt_journal::ObservationOutcomeRecord,
14 ic_snapshot_reply::{IcSnapshotInfo, IcSnapshotReply},
15};
16
17fn compare_identity(
18 claimed: &IcInventorySettlement,
19 operation: &SettlementIdentity<'_>,
20 baseline: &ArtifactChecksumRecord,
21 inventory: &ArtifactChecksumRecord,
22 observation_evidence: &ArtifactChecksumRecord,
23) -> Result<(), InventorySettlementMismatch> {
24 SettlementIdentity::from(&claimed.context).compare(operation)?;
25 if &claimed.baseline != baseline {
26 return Err(InventorySettlementMismatch::Baseline);
27 }
28 if &claimed.inventory != inventory {
29 return Err(InventorySettlementMismatch::Inventory);
30 }
31 if &claimed.context.observation_evidence != observation_evidence {
32 return Err(InventorySettlementMismatch::ObservationEvidence);
33 }
34 Ok(())
35}
36
37#[derive(Debug, thiserror::Error, Eq, PartialEq)]
39pub enum InventorySettlementMismatch {
40 #[error("inventory settlement authority differs")]
42 Authority,
43 #[error("inventory settlement attempts differ")]
45 Attempt,
46 #[error("inventory settlement challenge differs")]
48 Challenge,
49 #[error("inventory settlement baseline differs")]
51 Baseline,
52 #[error("inventory settlement inventory differs")]
54 Inventory,
55 #[error("inventory settlement observation evidence differs")]
57 ObservationEvidence,
58}
59
60impl From<SettlementIdentityMismatch> for InventorySettlementMismatch {
61 fn from(value: SettlementIdentityMismatch) -> Self {
62 match value {
63 SettlementIdentityMismatch::Authority => Self::Authority,
64 SettlementIdentityMismatch::Attempt => Self::Attempt,
65 SettlementIdentityMismatch::Challenge => Self::Challenge,
66 }
67 }
68}
69
70pub(crate) fn validate_pair<'a, E>(
71 target: &str,
72 baseline: &IcSnapshotReply<'_>,
73 inventory: &'a IcSnapshotReply<'_>,
74 claimed: &IcInventorySettlement,
75 operation: &SettlementIdentity<'_>,
76 observation_evidence: &ArtifactChecksumRecord,
77) -> Result<Vec<&'a IcSnapshotInfo>, E>
78where
79 E: From<SnapshotInventoryDeltaError> + From<InventorySettlementMismatch>,
80{
81 let candidates = compare_inventories(target, baseline, inventory)?;
82 compare_identity(
83 claimed,
84 operation,
85 &baseline.digest(),
86 &inventory.digest(),
87 observation_evidence,
88 )?;
89 Ok(candidates)
90}
91
92#[derive(Debug)]
94pub(crate) enum InventorySettlementOutcome {
95 Applied(usize),
96 NotApplied,
97 Uncertain,
98}
99
100impl InventorySettlementOutcome {
101 pub const fn outcome(&self) -> ObservationOutcomeRecord {
102 match self {
103 Self::Applied(_) => ObservationOutcomeRecord::Applied,
104 Self::NotApplied => ObservationOutcomeRecord::NotApplied,
105 Self::Uncertain => ObservationOutcomeRecord::Uncertain,
106 }
107 }
108
109 pub const fn snapshot_index(&self) -> Option<usize> {
110 match self {
111 Self::Applied(index) => Some(*index),
112 Self::NotApplied | Self::Uncertain => None,
113 }
114 }
115}
116
117pub(crate) fn admitted_outcome(
118 inventory: &IcSnapshotReply<'_>,
119 candidates: &[&IcSnapshotInfo],
120 attribution: &IcInventoryAttribution,
121) -> Result<InventorySettlementOutcome, ()> {
122 match attribution {
123 IcInventoryAttribution::Applied { snapshot_id, .. } => {
124 new_candidate_index(inventory, candidates, snapshot_id)
125 .map(InventorySettlementOutcome::Applied)
126 .ok_or(())
127 }
128 IcInventoryAttribution::NotApplied { .. } => Ok(InventorySettlementOutcome::NotApplied),
129 IcInventoryAttribution::Unresolved { .. } => Ok(InventorySettlementOutcome::Uncertain),
130 }
131}
132
133pub(crate) fn new_candidate_index(
134 inventory: &IcSnapshotReply<'_>,
135 candidates: &[&IcSnapshotInfo],
136 snapshot_id: &[u8],
137) -> Option<usize> {
138 if !candidates
139 .iter()
140 .any(|snapshot| snapshot.id() == snapshot_id)
141 {
142 return None;
143 }
144 inventory
145 .snapshots()
146 .binary_search_by(|snapshot| snapshot.id().cmp(snapshot_id))
147 .ok()
148}
149
150#[derive(Debug)]
152pub struct IcInventorySettlementView<'a> {
153 pub(crate) observation: IcObservationResponseView<'a>,
154 pub(crate) baseline: &'a IcSnapshotReply<'a>,
155 pub(crate) settlement: &'a IcInventorySettlement,
156 pub(crate) resolution: InventorySettlementOutcome,
157}
158impl<'a> IcInventorySettlementView<'a> {
159 #[must_use]
161 pub const fn outcome(&self) -> ObservationOutcomeRecord {
162 self.resolution.outcome()
163 }
164 #[must_use]
166 pub const fn baseline(&self) -> &'a IcSnapshotReply<'a> {
167 self.baseline
168 }
169 #[must_use]
171 pub const fn observation(&self) -> &IcObservationResponseView<'a> {
172 &self.observation
173 }
174 #[must_use]
176 pub const fn settlement(&self) -> &'a IcInventorySettlement {
177 self.settlement
178 }
179 #[must_use]
181 pub fn snapshot(&self) -> Option<&IcSnapshotInfo> {
182 self.observation
183 .inventory_snapshot(self.resolution.snapshot_index()?)
184 }
185}
186
187#[cfg(test)]
188mod tests;