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_platform::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.operation_binding(input.operation_sequence)?;
69 wire.validate_mutation_binding(&binding)?;
70 Ok(Self {
71 plan,
72 requirement,
73 binding,
74 wire,
75 input,
76 })
77 }
78 #[must_use]
80 pub const fn binding(&self) -> &OperationBindingRecord {
81 &self.binding
82 }
83 #[must_use]
85 pub const fn inventory(&self) -> &InventoryRecord {
86 self.plan.inventory()
87 }
88 #[must_use]
90 pub fn selected_targets(&self) -> &[String] {
91 self.plan.selected_targets()
92 }
93 #[must_use]
95 pub const fn requirement(&self) -> &RestoreSafetyRequirementRecord {
96 self.requirement
97 }
98 #[must_use]
100 pub const fn wire(&self) -> &IcManagementRequestRecord {
101 self.wire
102 }
103 #[must_use]
105 pub const fn challenge(&self) -> &ArtifactChecksumRecord {
106 &self.input.challenge
107 }
108 #[must_use]
110 pub const fn max_remote_observations(&self) -> u32 {
111 self.input.max_remote_observations
112 }
113 #[must_use]
117 pub fn digest(&self) -> ArtifactChecksumRecord {
118 let mut framing = EvidenceDigest::new(b"ic-backup/restore-safety-request/v1\0");
119 framing.checksum(&self.requirement.digest());
120 framing.u64_be(self.binding.operation_sequence());
121 framing.checksum(&self.wire.digest());
122 framing.checksum(self.challenge());
123 framing.u32_be(self.max_remote_observations());
124 framing.finish()
125 }
126}
127#[derive(Clone, Debug)]
129pub struct TargetRestoreEvidence {
130 pub target: String,
132 pub state: CanisterStatusType,
134 pub lifecycle_evidence: ArtifactChecksumRecord,
136 pub restored_acceptance: Option<ArtifactChecksumRecord>,
139}
140impl super::observed_targets::ObservedTargetRow for TargetRestoreEvidence {
141 fn target(&self) -> &str {
142 &self.target
143 }
144
145 fn target_mut(&mut self) -> &mut String {
146 &mut self.target
147 }
148}
149#[derive(Clone, Debug)]
151pub struct RestoreFenceEvidence {
152 pub state: ApplicationFenceState,
154 pub binding: RestoreFenceBindingRecord,
156 pub whole_selection: ArtifactChecksumRecord,
158 pub rewind_independent_custody: ArtifactChecksumRecord,
160 pub external_obligations_and_replay: ArtifactChecksumRecord,
163 pub controlled_execution: Option<ArtifactChecksumRecord>,
166}
167#[derive(Clone, Debug)]
169pub enum RestoreSafetyEvidence {
170 NoIrreversibleEffects(ArtifactChecksumRecord),
172 ApplicationFenced(Box<RestoreFenceEvidence>),
174 Unresolved(ArtifactChecksumRecord),
176}
177#[derive(Clone, Debug)]
179pub struct RestoreSafetyObservationInput {
180 pub request: ArtifactChecksumRecord,
182 pub context: PlanContextRecord,
184 pub inventory: InventoryRecord,
186 pub source_plan_intent: ArtifactChecksumRecord,
188 pub source_artifacts: ArtifactChecksumRecord,
191 pub targets: Vec<TargetRestoreEvidence>,
193 pub safety: RestoreSafetyEvidence,
195 pub evidence: ArtifactChecksumRecord,
197 pub remote_observations: u32,
199}
200#[derive(Clone, Debug)]
202pub struct RestoreSafetyObservation {
203 input: RestoreSafetyObservationInput,
204}
205impl RestoreSafetyObservation {
206 pub fn new(
210 mut input: RestoreSafetyObservationInput,
211 ) -> Result<Self, RestoreSafetyObservationError> {
212 input.targets = super::observed_targets::canonicalize(
213 input.targets,
214 &input.inventory,
215 || RestoreSafetyObservationError::InvalidTargetCount,
216 || RestoreSafetyObservationError::InvalidPrincipal,
217 || RestoreSafetyObservationError::TargetAbsentFromInventory,
218 || RestoreSafetyObservationError::DuplicateTarget,
219 )?;
220 Ok(Self { input })
221 }
222 #[must_use]
224 pub const fn request(&self) -> &ArtifactChecksumRecord {
225 &self.input.request
226 }
227 #[must_use]
229 pub const fn context(&self) -> &PlanContextRecord {
230 &self.input.context
231 }
232 #[must_use]
234 pub const fn inventory(&self) -> &InventoryRecord {
235 &self.input.inventory
236 }
237 #[must_use]
239 pub const fn source_plan_intent(&self) -> &ArtifactChecksumRecord {
240 &self.input.source_plan_intent
241 }
242 #[must_use]
244 pub const fn source_artifacts(&self) -> &ArtifactChecksumRecord {
245 &self.input.source_artifacts
246 }
247 #[must_use]
249 pub fn targets(&self) -> &[TargetRestoreEvidence] {
250 &self.input.targets
251 }
252 #[must_use]
254 pub const fn safety(&self) -> &RestoreSafetyEvidence {
255 &self.input.safety
256 }
257 #[must_use]
259 pub const fn evidence(&self) -> &ArtifactChecksumRecord {
260 &self.input.evidence
261 }
262 #[must_use]
264 pub const fn remote_observations(&self) -> u32 {
265 self.input.remote_observations
266 }
267}
268#[derive(Debug, Eq, Error, PartialEq)]
270pub enum RestoreSafetyObservationError {
271 #[error("restore safety targets must contain 1..={MAX_INVENTORY_TARGETS} entries")]
273 InvalidTargetCount,
274 #[error("invalid restore safety target principal")]
276 InvalidPrincipal,
277 #[error("duplicate restore safety target")]
279 DuplicateTarget,
280 #[error("restore safety target absent from inventory")]
282 TargetAbsentFromInventory,
283}
284#[derive(Debug, Error)]
286pub enum RestoreSafetyRequestError {
287 #[error(transparent)]
289 Requirement(#[from] RestoreSafetyRequirementError),
290 #[error(transparent)]
292 Plan(#[from] OperationPlanError),
293 #[error(transparent)]
295 Payload(#[from] IcRequestError),
296 #[error("restore safety requires exact load or start request")]
298 UnsupportedMethod,
299 #[error("restore safety observation ceiling exceeds {MAX_RESTORE_SAFETY_REMOTE_OBSERVATIONS}")]
301 ObservationLimitTooLarge,
302}
303
304#[cfg(test)]
305mod tests;