ic_backup/model/restore_safety/
mod.rs1mod requirement;
4pub use requirement::{
5 MAX_RESTORE_SAFETY_REQUIREMENT_BYTES, RestoreFenceBindingRecord, RestoreSafetyLaneRecord,
6 RestoreSafetyRequirementError, RestoreSafetyRequirementRecord, RestoreSafetyRequirementRequest,
7};
8
9use crate::model::{
10 artifacts::ArtifactChecksumRecord,
11 attempt_journal::OperationBindingRecord,
12 consistency::ApplicationFenceState,
13 evidence_digest::EvidenceDigest,
14 ic_request::{IcManagementMethodRecord, IcManagementRequestRecord, IcRequestError},
15 inventory::{InventoryRecord, MAX_INVENTORY_TARGETS},
16 operation_plan::{OperationPlanError, OperationPlanRecord, PlanContextRecord},
17};
18use ic_management_canister_types::CanisterStatusType;
19use thiserror::Error;
20
21pub const MAX_RESTORE_SAFETY_REMOTE_OBSERVATIONS: u32 = 1024;
23#[derive(Clone, Debug)]
25pub struct RestoreSafetyRequestInput {
26 pub operation_sequence: u64,
28 pub challenge: ArtifactChecksumRecord,
30 pub max_remote_observations: u32,
32}
33#[derive(Clone, Debug)]
35pub struct RestoreSafetyRequest<'a> {
36 plan: &'a OperationPlanRecord,
37 requirement: &'a RestoreSafetyRequirementRecord,
38 binding: OperationBindingRecord,
39 wire: &'a IcManagementRequestRecord,
40 input: RestoreSafetyRequestInput,
41}
42impl<'a> RestoreSafetyRequest<'a> {
43 pub fn new(
51 plan: &'a OperationPlanRecord,
52 source: &OperationPlanRecord,
53 requirement: &'a RestoreSafetyRequirementRecord,
54 wire: &'a IcManagementRequestRecord,
55 input: RestoreSafetyRequestInput,
56 ) -> Result<Self, RestoreSafetyRequestError> {
57 requirement.validate_plans(plan, source)?;
58 if input.max_remote_observations > MAX_RESTORE_SAFETY_REMOTE_OBSERVATIONS {
59 return Err(RestoreSafetyRequestError::ObservationLimitTooLarge);
60 }
61 if !matches!(
62 wire.method(),
63 IcManagementMethodRecord::LoadCanisterSnapshot
64 | IcManagementMethodRecord::StartCanister
65 ) {
66 return Err(RestoreSafetyRequestError::UnsupportedMethod);
67 }
68 let binding = plan
69 .attempt_authority(input.operation_sequence)?
70 .binding()
71 .clone();
72 wire.validate_mutation_binding(&binding)?;
73 Ok(Self {
74 plan,
75 requirement,
76 binding,
77 wire,
78 input,
79 })
80 }
81 #[must_use]
83 pub const fn binding(&self) -> &OperationBindingRecord {
84 &self.binding
85 }
86 #[must_use]
88 pub const fn inventory(&self) -> &InventoryRecord {
89 self.plan.inventory()
90 }
91 #[must_use]
93 pub fn selected_targets(&self) -> &[String] {
94 self.plan.selected_targets()
95 }
96 #[must_use]
98 pub const fn requirement(&self) -> &RestoreSafetyRequirementRecord {
99 self.requirement
100 }
101 #[must_use]
103 pub const fn wire(&self) -> &IcManagementRequestRecord {
104 self.wire
105 }
106 #[must_use]
108 pub const fn challenge(&self) -> &ArtifactChecksumRecord {
109 &self.input.challenge
110 }
111 #[must_use]
113 pub const fn max_remote_observations(&self) -> u32 {
114 self.input.max_remote_observations
115 }
116 #[must_use]
120 pub fn digest(&self) -> ArtifactChecksumRecord {
121 let mut framing = EvidenceDigest::new(b"ic-backup/restore-safety-request/v1\0");
122 framing.checksum(&self.requirement.digest());
123 framing.u64_be(self.binding.operation_sequence());
124 framing.checksum(&self.wire.digest());
125 framing.checksum(self.challenge());
126 framing.u32_be(self.max_remote_observations());
127 framing.finish()
128 }
129}
130#[derive(Clone, Debug)]
132pub struct TargetRestoreEvidence {
133 pub target: String,
135 pub state: CanisterStatusType,
137 pub lifecycle_evidence: ArtifactChecksumRecord,
139 pub restored_acceptance: Option<ArtifactChecksumRecord>,
142}
143impl super::observed_targets::ObservedTargetRow for TargetRestoreEvidence {
144 fn target(&self) -> &str {
145 &self.target
146 }
147
148 fn target_mut(&mut self) -> &mut String {
149 &mut self.target
150 }
151}
152#[derive(Clone, Debug)]
154pub struct RestoreFenceEvidence {
155 pub state: ApplicationFenceState,
157 pub binding: RestoreFenceBindingRecord,
159 pub whole_selection: ArtifactChecksumRecord,
161 pub rewind_independent_custody: ArtifactChecksumRecord,
163 pub external_obligations_and_replay: ArtifactChecksumRecord,
166 pub controlled_execution: Option<ArtifactChecksumRecord>,
169}
170#[derive(Clone, Debug)]
172pub enum RestoreSafetyEvidence {
173 NoIrreversibleEffects(ArtifactChecksumRecord),
175 ApplicationFenced(Box<RestoreFenceEvidence>),
177 Unresolved(ArtifactChecksumRecord),
179}
180#[derive(Clone, Debug)]
182pub struct RestoreSafetyObservationInput {
183 pub request: ArtifactChecksumRecord,
185 pub context: PlanContextRecord,
187 pub inventory: InventoryRecord,
189 pub source_plan_intent: ArtifactChecksumRecord,
191 pub source_artifacts: ArtifactChecksumRecord,
194 pub targets: Vec<TargetRestoreEvidence>,
196 pub safety: RestoreSafetyEvidence,
198 pub evidence: ArtifactChecksumRecord,
200 pub remote_observations: u32,
202}
203#[derive(Clone, Debug)]
205pub struct RestoreSafetyObservation {
206 input: RestoreSafetyObservationInput,
207}
208impl RestoreSafetyObservation {
209 pub fn new(
213 mut input: RestoreSafetyObservationInput,
214 ) -> Result<Self, RestoreSafetyObservationError> {
215 input.targets = super::observed_targets::canonicalize(
216 input.targets,
217 &input.inventory,
218 || RestoreSafetyObservationError::InvalidTargetCount,
219 || RestoreSafetyObservationError::InvalidPrincipal,
220 || RestoreSafetyObservationError::TargetAbsentFromInventory,
221 || RestoreSafetyObservationError::DuplicateTarget,
222 )?;
223 Ok(Self { input })
224 }
225 #[must_use]
227 pub const fn request(&self) -> &ArtifactChecksumRecord {
228 &self.input.request
229 }
230 #[must_use]
232 pub const fn context(&self) -> &PlanContextRecord {
233 &self.input.context
234 }
235 #[must_use]
237 pub const fn inventory(&self) -> &InventoryRecord {
238 &self.input.inventory
239 }
240 #[must_use]
242 pub const fn source_plan_intent(&self) -> &ArtifactChecksumRecord {
243 &self.input.source_plan_intent
244 }
245 #[must_use]
247 pub const fn source_artifacts(&self) -> &ArtifactChecksumRecord {
248 &self.input.source_artifacts
249 }
250 #[must_use]
252 pub fn targets(&self) -> &[TargetRestoreEvidence] {
253 &self.input.targets
254 }
255 #[must_use]
257 pub const fn safety(&self) -> &RestoreSafetyEvidence {
258 &self.input.safety
259 }
260 #[must_use]
262 pub const fn evidence(&self) -> &ArtifactChecksumRecord {
263 &self.input.evidence
264 }
265 #[must_use]
267 pub const fn remote_observations(&self) -> u32 {
268 self.input.remote_observations
269 }
270}
271#[derive(Debug, Eq, Error, PartialEq)]
273pub enum RestoreSafetyObservationError {
274 #[error("restore safety targets must contain 1..={MAX_INVENTORY_TARGETS} entries")]
276 InvalidTargetCount,
277 #[error("invalid restore safety target principal")]
279 InvalidPrincipal,
280 #[error("duplicate restore safety target")]
282 DuplicateTarget,
283 #[error("restore safety target absent from inventory")]
285 TargetAbsentFromInventory,
286}
287#[derive(Debug, Error)]
289pub enum RestoreSafetyRequestError {
290 #[error(transparent)]
292 Requirement(#[from] RestoreSafetyRequirementError),
293 #[error(transparent)]
295 Plan(#[from] OperationPlanError),
296 #[error(transparent)]
298 Payload(#[from] IcRequestError),
299 #[error("restore safety requires exact load or start request")]
301 UnsupportedMethod,
302 #[error("restore safety observation ceiling exceeds {MAX_RESTORE_SAFETY_REMOTE_OBSERVATIONS}")]
304 ObservationLimitTooLarge,
305}
306
307#[cfg(test)]
308mod tests;