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 InventoryAttribution, InventorySettlementIdentity, InventorySettlementMismatch,
21 admitted_outcome, validate_pair,
22 },
23 snapshot_inventory_delta::SnapshotInventoryDeltaError,
24 },
25};
26use thiserror::Error;
27
28#[derive(Debug)]
30pub struct IcSnapshotUploadSettlementView<'a> {
31 observation: IcObservationResponseView<'a>,
32 baseline: &'a IcSnapshotReply<'a>,
33 settlement: &'a IcSnapshotUploadSettlement,
34 allocated_snapshot: Option<usize>,
35 outcome: ObservationOutcomeRecord,
36}
37impl<'a> IcSnapshotUploadSettlementView<'a> {
38 #[must_use]
40 pub const fn outcome(&self) -> ObservationOutcomeRecord {
41 self.outcome
42 }
43 #[must_use]
45 pub const fn baseline(&self) -> &'a IcSnapshotReply<'a> {
46 self.baseline
47 }
48 #[must_use]
50 pub const fn observation(&self) -> &IcObservationResponseView<'a> {
51 &self.observation
52 }
53 #[must_use]
55 pub const fn settlement(&self) -> &'a IcSnapshotUploadSettlement {
56 self.settlement
57 }
58 #[must_use]
60 pub fn allocated_snapshot(&self) -> Option<&IcSnapshotInfo> {
61 match self.observation.reply() {
62 IcObservationReplyView::Inventory(reply) => self
63 .allocated_snapshot
64 .map(|index| &reply.snapshots()[index]),
65 IcObservationReplyView::Status(_) => None,
66 }
67 }
68}
69
70pub fn validate_settlement<'a>(
83 request: &IcSnapshotUploadObservationRequest<'a, '_>,
84 journal: &AttemptJournalRecord,
85 baseline: &'a IcSnapshotReply<'a>,
86 response: &'a IcObservationResponse,
87 challenge: &ArtifactChecksumRecord,
88 settlement: &'a IcSnapshotUploadSettlement,
89) -> Result<IcSnapshotUploadSettlementView<'a>, IcSnapshotUploadSettlementError> {
90 let observation = validate_response(request, journal, response)?;
91 let IcObservationReplyView::Inventory(inventory) = observation.reply() else {
92 return Err(IcSnapshotUploadSettlementError::WrongInventory);
93 };
94 let authority = request.authority().digest();
95 let baseline_digest = baseline.digest();
96 let inventory_digest = inventory.digest();
97 let candidates = validate_pair::<IcSnapshotUploadSettlementError>(
98 request.payload().target(),
99 baseline,
100 inventory,
101 &InventorySettlementIdentity {
102 authority: &settlement.authority,
103 mutation_attempt: settlement.mutation_attempt,
104 observation_attempt: settlement.observation_attempt,
105 challenge: &settlement.challenge,
106 baseline: &settlement.baseline,
107 inventory: &settlement.inventory,
108 observation_evidence: &settlement.observation_evidence,
109 },
110 &InventorySettlementIdentity {
111 authority: &authority,
112 mutation_attempt: request.mutation_attempt(),
113 observation_attempt: request.observation_attempt(),
114 challenge,
115 baseline: &baseline_digest,
116 inventory: &inventory_digest,
117 observation_evidence: &response.input().evidence,
118 },
119 )?;
120 if let IcSnapshotUploadAttribution::Applied { snapshot_id, .. } = &settlement.attribution {
121 request.mutation().validate_data_destination(snapshot_id)?;
122 }
123 let (outcome, allocated_snapshot) = admitted_outcome(
124 inventory,
125 &candidates,
126 InventoryAttribution::from(&settlement.attribution),
127 )
128 .map_err(|()| IcSnapshotUploadSettlementError::NotNewCandidate)?;
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;