ic_backup/model/fence_reconciliation/
mod.rs1use crate::model::{
4 artifacts::ArtifactChecksumRecord,
5 attempt_journal::{AttemptAuthorityRecord, AttemptJournalRecord, MAX_OPERATION_ATTEMPTS},
6 consistency::ApplicationFenceEvidence,
7 evidence_digest::EvidenceDigest,
8 fence_obligation::{FenceObligationError, FenceObligationRecord},
9 inventory::{InventoryRecord, InventoryRecordError, MAX_INVENTORY_TARGETS},
10 operation_plan::{OperationPlanError, OperationPlanRecord, PlanContextRecord},
11 restore_safety::RestoreFenceEvidence,
12};
13use thiserror::Error;
14
15pub const MAX_FENCE_RECONCILIATION_REMOTE_OBSERVATIONS: u32 = 1;
20
21#[derive(Clone, Debug)]
27pub struct FenceReconciliationIntent<'a> {
28 plan: &'a OperationPlanRecord,
29 obligation: &'a FenceObligationRecord,
30 authority: AttemptAuthorityRecord,
31 mutation_attempt: u32,
32 challenge: ArtifactChecksumRecord,
33}
34impl<'a> FenceReconciliationIntent<'a> {
35 pub fn new(
42 plan: &'a OperationPlanRecord,
43 obligation: &'a FenceObligationRecord,
44 journal: &AttemptJournalRecord,
45 challenge: ArtifactChecksumRecord,
46 ) -> Result<Self, FenceReconciliationRequestError> {
47 obligation.validate_plan(plan)?;
48 let authority = plan.attempt_authority_for_journal(
49 obligation.acquisition_operation(),
50 journal,
51 FenceReconciliationRequestError::AuthorityMismatch,
52 )?;
53 let mutation_attempt = journal
54 .pending_mutation()
55 .ok_or(FenceReconciliationRequestError::NoPendingMutation)?;
56 Ok(Self {
57 plan,
58 obligation,
59 authority,
60 mutation_attempt,
61 challenge,
62 })
63 }
64 #[must_use]
66 pub const fn obligation(&self) -> &FenceObligationRecord {
67 self.obligation
68 }
69 #[must_use]
71 pub const fn plan(&self) -> &OperationPlanRecord {
72 self.plan
73 }
74 #[must_use]
76 pub const fn authority(&self) -> &AttemptAuthorityRecord {
77 &self.authority
78 }
79 #[must_use]
81 pub const fn mutation_attempt(&self) -> u32 {
82 self.mutation_attempt
83 }
84 #[must_use]
86 pub const fn challenge(&self) -> &ArtifactChecksumRecord {
87 &self.challenge
88 }
89 #[must_use]
95 pub fn digest(&self) -> ArtifactChecksumRecord {
96 observation_digest(
97 &self.obligation.digest(),
98 &self.authority.digest(),
99 self.mutation_attempt,
100 &self.challenge,
101 )
102 }
103 fn validate_mutation(
104 &self,
105 journal: &AttemptJournalRecord,
106 ) -> Result<(), FenceReconciliationRequestError> {
107 if journal.authority() != &self.authority {
108 return Err(FenceReconciliationRequestError::AuthorityMismatch);
109 }
110 if journal.pending_mutation() != Some(self.mutation_attempt) {
111 return Err(FenceReconciliationRequestError::MutationMismatch);
112 }
113 Ok(())
114 }
115}
116fn observation_digest(
117 obligation: &ArtifactChecksumRecord,
118 authority: &ArtifactChecksumRecord,
119 mutation: u32,
120 challenge: &ArtifactChecksumRecord,
121) -> ArtifactChecksumRecord {
122 let mut framing = EvidenceDigest::new(b"ic-backup/fence-reconciliation/v1\0");
123 framing.checksum(obligation);
124 framing.checksum(authority);
125 framing.u32_be(mutation);
126 framing.checksum(challenge);
127 framing.finish()
128}
129
130#[derive(Clone, Debug)]
136pub struct FenceReconciliationRequest<'a, 'plan> {
137 intent: &'a FenceReconciliationIntent<'plan>,
138 observation_attempt: u32,
139}
140impl<'a, 'plan> FenceReconciliationRequest<'a, 'plan> {
141 pub fn new(
145 intent: &'a FenceReconciliationIntent<'plan>,
146 journal: &AttemptJournalRecord,
147 ) -> Result<Self, FenceReconciliationRequestError> {
148 intent.validate_mutation(journal)?;
149 let observation_attempt = journal
150 .pending_observation()
151 .ok_or(FenceReconciliationRequestError::NoPendingObservation)?;
152 let request = Self {
153 intent,
154 observation_attempt,
155 };
156 request.validate_journal(journal)?;
157 Ok(request)
158 }
159 #[must_use]
161 pub const fn intent(&self) -> &FenceReconciliationIntent<'plan> {
162 self.intent
163 }
164 #[must_use]
166 pub const fn observation_attempt(&self) -> u32 {
167 self.observation_attempt
168 }
169 #[must_use]
171 pub fn digest(&self) -> ArtifactChecksumRecord {
172 self.intent.digest()
173 }
174 pub fn validate_journal(
180 &self,
181 journal: &AttemptJournalRecord,
182 ) -> Result<(), FenceReconciliationRequestError> {
183 crate::model::attempt_journal::ObservationReservation::validate_identity(
184 journal,
185 self.intent.authority(),
186 self.intent.mutation_attempt(),
187 self.observation_attempt,
188 self.digest().hash(),
189 )
190 .map_err(FenceReconciliationRequestError::from)
191 }
192}
193
194#[derive(Clone, Debug)]
196pub enum FenceReconciliationEvidence {
197 AcquiredCapture {
199 fence: Box<ApplicationFenceEvidence>,
201 attribution: ArtifactChecksumRecord,
203 },
204 AcquiredRestore {
206 fence: Box<RestoreFenceEvidence>,
208 attribution: ArtifactChecksumRecord,
210 },
211 NotAcquired {
213 exclusion: ArtifactChecksumRecord,
215 },
216 Unresolved {
218 uncertainty: ArtifactChecksumRecord,
220 },
221}
222#[derive(Clone, Debug)]
224pub struct FenceReconciliationObservationInput {
225 pub request: ArtifactChecksumRecord,
227 pub mutation_attempt: u32,
229 pub observation_attempt: u32,
231 pub context: PlanContextRecord,
233 pub inventory: InventoryRecord,
235 pub selected_targets: Vec<String>,
237 pub settlement: FenceReconciliationEvidence,
239 pub evidence: ArtifactChecksumRecord,
241 pub remote_observations: u32,
243}
244#[derive(Clone, Debug)]
246pub struct FenceReconciliationObservation {
247 input: FenceReconciliationObservationInput,
248}
249impl FenceReconciliationObservation {
250 pub fn new(
254 mut input: FenceReconciliationObservationInput,
255 ) -> Result<Self, FenceReconciliationObservationError> {
256 if input.mutation_attempt == 0
257 || input.observation_attempt <= input.mutation_attempt
258 || input.observation_attempt > MAX_OPERATION_ATTEMPTS
259 {
260 return Err(FenceReconciliationObservationError::InvalidAttempts);
261 }
262 if input.selected_targets.is_empty() || input.selected_targets.len() > MAX_INVENTORY_TARGETS
263 {
264 return Err(FenceReconciliationObservationError::InvalidTargetCount);
265 }
266 for target in &mut input.selected_targets {
267 *target = input.inventory.target(target)?.canister_id().into();
268 }
269 input.selected_targets.sort();
270 if input
271 .selected_targets
272 .windows(2)
273 .any(|pair| pair[0] == pair[1])
274 {
275 return Err(FenceReconciliationObservationError::DuplicateTarget);
276 }
277 Ok(Self { input })
278 }
279 #[must_use]
281 pub const fn input(&self) -> &FenceReconciliationObservationInput {
282 &self.input
283 }
284}
285
286#[derive(Debug, Error)]
288pub enum FenceReconciliationRequestError {
289 #[error("fence reconciliation original journal authority mismatch")]
291 AuthorityMismatch,
292 #[error("fence reconciliation requires a pending acquisition mutation")]
294 NoPendingMutation,
295 #[error("fence reconciliation pending acquisition mismatch")]
297 MutationMismatch,
298 #[error("fence reconciliation requires a pending observation reservation")]
300 NoPendingObservation,
301 #[error("fence reconciliation observation attempt mismatch")]
303 ObservationMismatch,
304 #[error("fence reconciliation reserved request mismatch")]
306 RequestMismatch,
307 #[error(transparent)]
309 Obligation(#[from] FenceObligationError),
310 #[error(transparent)]
312 Plan(#[from] OperationPlanError),
313}
314#[derive(Debug, Error)]
316pub enum FenceReconciliationObservationError {
317 #[error("invalid fence reconciliation attempt identities")]
319 InvalidAttempts,
320 #[error("invalid fence reconciliation target count")]
322 InvalidTargetCount,
323 #[error("duplicate fence reconciliation target")]
325 DuplicateTarget,
326 #[error(transparent)]
328 Inventory(#[from] InventoryRecordError),
329}
330
331#[cfg(test)]
332mod tests;
333
334impl From<crate::model::attempt_journal::ObservationReservationError>
335 for FenceReconciliationRequestError
336{
337 fn from(error: crate::model::attempt_journal::ObservationReservationError) -> Self {
338 use crate::model::attempt_journal::ObservationReservationError as Reservation;
339 match error {
340 Reservation::AuthorityMismatch => Self::AuthorityMismatch,
341 Reservation::NoPendingMutation => Self::NoPendingMutation,
342 Reservation::NoPendingObservation => Self::NoPendingObservation,
343 Reservation::MutationMismatch => Self::MutationMismatch,
344 Reservation::ObservationMismatch => Self::ObservationMismatch,
345 Reservation::RequestMismatch => Self::RequestMismatch,
346 }
347 }
348}