ic_backup/policy/ic_snapshot_upload_observation/settlement/
mod.rs1use super::validate_response;
3use crate::{
4 model::{
5 artifacts::ArtifactChecksumRecord,
6 attempt_journal::{AttemptJournalRecord, ObservationOutcomeRecord},
7 ic_observation::IcObservationResponse,
8 ic_snapshot_reply::{IcSnapshotInfo, IcSnapshotReply},
9 ic_snapshot_upload::IcSnapshotUploadError,
10 ic_snapshot_upload_observation::{
11 IcSnapshotUploadAttribution, IcSnapshotUploadObservationRequest,
12 IcSnapshotUploadSettlement,
13 },
14 },
15 policy::{
16 ic_observation::{
17 IcObservationAssociationError, IcObservationReplyView, IcObservationResponseView,
18 },
19 inventory_settlement::{
20 InventorySettlementIdentity, InventorySettlementMismatch, new_candidate_index,
21 },
22 snapshot_inventory_delta::{SnapshotInventoryDeltaError, compare_inventories},
23 },
24};
25use thiserror::Error;
26
27#[derive(Debug)]
29pub struct IcSnapshotUploadSettlementView<'a> {
30 observation: IcObservationResponseView<'a>,
31 baseline: &'a IcSnapshotReply<'a>,
32 settlement: &'a IcSnapshotUploadSettlement,
33 allocated_snapshot: Option<usize>,
34 outcome: ObservationOutcomeRecord,
35}
36impl<'a> IcSnapshotUploadSettlementView<'a> {
37 #[must_use]
39 pub const fn outcome(&self) -> ObservationOutcomeRecord {
40 self.outcome
41 }
42 #[must_use]
44 pub const fn baseline(&self) -> &'a IcSnapshotReply<'a> {
45 self.baseline
46 }
47 #[must_use]
49 pub const fn observation(&self) -> &IcObservationResponseView<'a> {
50 &self.observation
51 }
52 #[must_use]
54 pub const fn settlement(&self) -> &'a IcSnapshotUploadSettlement {
55 self.settlement
56 }
57 #[must_use]
59 pub fn allocated_snapshot(&self) -> Option<&IcSnapshotInfo> {
60 match self.observation.reply() {
61 IcObservationReplyView::Inventory(reply) => self
62 .allocated_snapshot
63 .map(|index| &reply.snapshots()[index]),
64 IcObservationReplyView::Status(_) => None,
65 }
66 }
67}
68
69pub fn validate_settlement<'a>(
82 request: &IcSnapshotUploadObservationRequest<'a, '_>,
83 journal: &AttemptJournalRecord,
84 baseline: &'a IcSnapshotReply<'a>,
85 response: &'a IcObservationResponse,
86 challenge: &ArtifactChecksumRecord,
87 settlement: &'a IcSnapshotUploadSettlement,
88) -> Result<IcSnapshotUploadSettlementView<'a>, IcSnapshotUploadSettlementError> {
89 let observation = validate_response(request, journal, response)?;
90 let IcObservationReplyView::Inventory(inventory) = observation.reply() else {
91 return Err(IcSnapshotUploadSettlementError::WrongInventory);
92 };
93 let candidates = compare_inventories(request.payload().target(), baseline, inventory)?;
94 let authority = request.authority().digest();
95 let baseline_digest = baseline.digest();
96 let inventory_digest = inventory.digest();
97 InventorySettlementIdentity {
98 authority: &settlement.authority,
99 mutation_attempt: settlement.mutation_attempt,
100 observation_attempt: settlement.observation_attempt,
101 challenge: &settlement.challenge,
102 baseline: &settlement.baseline,
103 inventory: &settlement.inventory,
104 observation_evidence: &settlement.observation_evidence,
105 }
106 .compare(&InventorySettlementIdentity {
107 authority: &authority,
108 mutation_attempt: request.mutation_attempt(),
109 observation_attempt: request.observation_attempt(),
110 challenge,
111 baseline: &baseline_digest,
112 inventory: &inventory_digest,
113 observation_evidence: &response.input().evidence,
114 })?;
115 let (outcome, allocated_snapshot) = match &settlement.attribution {
116 IcSnapshotUploadAttribution::Applied { snapshot_id, .. } => {
117 request.mutation().validate_data_destination(snapshot_id)?;
118 let index = new_candidate_index(inventory, &candidates, snapshot_id)
119 .ok_or(IcSnapshotUploadSettlementError::NotNewCandidate)?;
120 (ObservationOutcomeRecord::Applied, Some(index))
121 }
122 IcSnapshotUploadAttribution::NotApplied { .. } => {
123 (ObservationOutcomeRecord::NotApplied, None)
124 }
125 IcSnapshotUploadAttribution::Unresolved { .. } => {
126 (ObservationOutcomeRecord::Uncertain, None)
127 }
128 };
129 Ok(IcSnapshotUploadSettlementView {
130 observation,
131 baseline,
132 settlement,
133 allocated_snapshot,
134 outcome,
135 })
136}
137
138#[derive(Debug, Error)]
140pub enum IcSnapshotUploadSettlementError {
141 #[error(transparent)]
143 Observation(#[from] IcObservationAssociationError),
144 #[error(transparent)]
146 Baseline(#[from] SnapshotInventoryDeltaError),
147 #[error(transparent)]
149 Destination(#[from] IcSnapshotUploadError),
150 #[error("metadata settlement requires snapshot inventory")]
152 WrongInventory,
153 #[error("metadata settlement authority differs")]
155 AuthorityMismatch,
156 #[error("metadata settlement attempts differ")]
158 AttemptMismatch,
159 #[error("metadata settlement challenge differs")]
161 ChallengeMismatch,
162 #[error("metadata settlement baseline differs")]
164 BaselineMismatch,
165 #[error("metadata settlement inventory differs")]
167 InventoryMismatch,
168 #[error("metadata settlement observation evidence differs")]
170 ObservationEvidenceMismatch,
171 #[error("metadata settlement ID is not a new candidate")]
173 NotNewCandidate,
174}
175
176impl From<InventorySettlementMismatch> for IcSnapshotUploadSettlementError {
177 fn from(value: InventorySettlementMismatch) -> Self {
178 match value {
179 InventorySettlementMismatch::Authority => Self::AuthorityMismatch,
180 InventorySettlementMismatch::Attempt => Self::AttemptMismatch,
181 InventorySettlementMismatch::Challenge => Self::ChallengeMismatch,
182 InventorySettlementMismatch::Baseline => Self::BaselineMismatch,
183 InventorySettlementMismatch::Inventory => Self::InventoryMismatch,
184 InventorySettlementMismatch::ObservationEvidence => Self::ObservationEvidenceMismatch,
185 }
186 }
187}
188
189#[cfg(test)]
190mod tests;