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 ic_request::{IcManagementMethodRecord, IcManagementRequestRecord, IcRequestError},
14 inventory::{InventoryRecord, MAX_INVENTORY_TARGETS},
15 operation_plan::{OperationPlanError, OperationPlanRecord, PlanContextRecord},
16};
17use ic_management_canister_types::CanisterStatusType;
18use thiserror::Error;
19
20pub const MAX_RESTORE_SAFETY_REMOTE_OBSERVATIONS: u32 = 1024;
22#[derive(Clone, Debug)]
24pub struct RestoreSafetyRequestInput {
25 pub operation_sequence: u64,
27 pub challenge: ArtifactChecksumRecord,
29 pub max_remote_observations: u32,
31}
32#[derive(Clone, Debug)]
34pub struct RestoreSafetyRequest<'a> {
35 plan: &'a OperationPlanRecord,
36 requirement: &'a RestoreSafetyRequirementRecord,
37 binding: OperationBindingRecord,
38 wire: &'a IcManagementRequestRecord,
39 input: RestoreSafetyRequestInput,
40}
41impl<'a> RestoreSafetyRequest<'a> {
42 pub fn new(
50 plan: &'a OperationPlanRecord,
51 source: &OperationPlanRecord,
52 requirement: &'a RestoreSafetyRequirementRecord,
53 wire: &'a IcManagementRequestRecord,
54 input: RestoreSafetyRequestInput,
55 ) -> Result<Self, RestoreSafetyRequestError> {
56 requirement.validate_plans(plan, source)?;
57 if input.max_remote_observations > MAX_RESTORE_SAFETY_REMOTE_OBSERVATIONS {
58 return Err(RestoreSafetyRequestError::ObservationLimitTooLarge);
59 }
60 if !matches!(
61 wire.method(),
62 IcManagementMethodRecord::LoadCanisterSnapshot
63 | IcManagementMethodRecord::StartCanister
64 ) {
65 return Err(RestoreSafetyRequestError::UnsupportedMethod);
66 }
67 let binding = plan
68 .attempt_authority(input.operation_sequence)?
69 .binding()
70 .clone();
71 wire.validate_mutation_binding(&binding)?;
72 Ok(Self {
73 plan,
74 requirement,
75 binding,
76 wire,
77 input,
78 })
79 }
80 #[must_use]
82 pub const fn binding(&self) -> &OperationBindingRecord {
83 &self.binding
84 }
85 #[must_use]
87 pub const fn inventory(&self) -> &InventoryRecord {
88 self.plan.inventory()
89 }
90 #[must_use]
92 pub fn selected_targets(&self) -> &[String] {
93 self.plan.selected_targets()
94 }
95 #[must_use]
97 pub const fn requirement(&self) -> &RestoreSafetyRequirementRecord {
98 self.requirement
99 }
100 #[must_use]
102 pub const fn wire(&self) -> &IcManagementRequestRecord {
103 self.wire
104 }
105 #[must_use]
107 pub const fn challenge(&self) -> &ArtifactChecksumRecord {
108 &self.input.challenge
109 }
110 #[must_use]
112 pub const fn max_remote_observations(&self) -> u32 {
113 self.input.max_remote_observations
114 }
115 #[must_use]
119 pub fn digest(&self) -> ArtifactChecksumRecord {
120 let mut bytes = b"ic-backup/restore-safety-request/v1\0".to_vec();
121 bytes.extend_from_slice(self.requirement.digest().hash().as_bytes());
122 bytes.extend_from_slice(&self.binding.operation_sequence().to_be_bytes());
123 bytes.extend_from_slice(self.wire.digest().hash().as_bytes());
124 bytes.extend_from_slice(self.challenge().hash().as_bytes());
125 bytes.extend_from_slice(&self.max_remote_observations().to_be_bytes());
126 ArtifactChecksumRecord::from_bytes(&bytes)
127 }
128}
129#[derive(Clone, Debug)]
131pub struct TargetRestoreEvidence {
132 pub target: String,
134 pub state: CanisterStatusType,
136 pub lifecycle_evidence: ArtifactChecksumRecord,
138 pub restored_acceptance: Option<ArtifactChecksumRecord>,
141}
142impl super::observed_targets::ObservedTargetRow for TargetRestoreEvidence {
143 fn target(&self) -> &str {
144 &self.target
145 }
146
147 fn target_mut(&mut self) -> &mut String {
148 &mut self.target
149 }
150}
151#[derive(Clone, Debug)]
153pub struct RestoreFenceEvidence {
154 pub state: ApplicationFenceState,
156 pub binding: RestoreFenceBindingRecord,
158 pub whole_selection: ArtifactChecksumRecord,
160 pub rewind_independent_custody: ArtifactChecksumRecord,
162 pub external_obligations_and_replay: ArtifactChecksumRecord,
165 pub controlled_execution: Option<ArtifactChecksumRecord>,
168}
169#[derive(Clone, Debug)]
171pub enum RestoreSafetyEvidence {
172 NoIrreversibleEffects(ArtifactChecksumRecord),
174 ApplicationFenced(Box<RestoreFenceEvidence>),
176 Unresolved(ArtifactChecksumRecord),
178}
179#[derive(Clone, Debug)]
181pub struct RestoreSafetyObservationInput {
182 pub request: ArtifactChecksumRecord,
184 pub context: PlanContextRecord,
186 pub inventory: InventoryRecord,
188 pub source_plan_intent: ArtifactChecksumRecord,
190 pub source_artifacts: ArtifactChecksumRecord,
193 pub targets: Vec<TargetRestoreEvidence>,
195 pub safety: RestoreSafetyEvidence,
197 pub evidence: ArtifactChecksumRecord,
199 pub remote_observations: u32,
201}
202#[derive(Clone, Debug)]
204pub struct RestoreSafetyObservation {
205 input: RestoreSafetyObservationInput,
206}
207impl RestoreSafetyObservation {
208 pub fn new(
212 mut input: RestoreSafetyObservationInput,
213 ) -> Result<Self, RestoreSafetyObservationError> {
214 input.targets = super::observed_targets::canonicalize(
215 input.targets,
216 &input.inventory,
217 || RestoreSafetyObservationError::InvalidTargetCount,
218 || RestoreSafetyObservationError::InvalidPrincipal,
219 || RestoreSafetyObservationError::TargetAbsentFromInventory,
220 || RestoreSafetyObservationError::DuplicateTarget,
221 )?;
222 Ok(Self { input })
223 }
224 #[must_use]
226 pub const fn request(&self) -> &ArtifactChecksumRecord {
227 &self.input.request
228 }
229 #[must_use]
231 pub const fn context(&self) -> &PlanContextRecord {
232 &self.input.context
233 }
234 #[must_use]
236 pub const fn inventory(&self) -> &InventoryRecord {
237 &self.input.inventory
238 }
239 #[must_use]
241 pub const fn source_plan_intent(&self) -> &ArtifactChecksumRecord {
242 &self.input.source_plan_intent
243 }
244 #[must_use]
246 pub const fn source_artifacts(&self) -> &ArtifactChecksumRecord {
247 &self.input.source_artifacts
248 }
249 #[must_use]
251 pub fn targets(&self) -> &[TargetRestoreEvidence] {
252 &self.input.targets
253 }
254 #[must_use]
256 pub const fn safety(&self) -> &RestoreSafetyEvidence {
257 &self.input.safety
258 }
259 #[must_use]
261 pub const fn evidence(&self) -> &ArtifactChecksumRecord {
262 &self.input.evidence
263 }
264 #[must_use]
266 pub const fn remote_observations(&self) -> u32 {
267 self.input.remote_observations
268 }
269}
270#[derive(Debug, Eq, Error, PartialEq)]
272pub enum RestoreSafetyObservationError {
273 #[error("restore safety targets must contain 1..={MAX_INVENTORY_TARGETS} entries")]
275 InvalidTargetCount,
276 #[error("invalid restore safety target principal")]
278 InvalidPrincipal,
279 #[error("duplicate restore safety target")]
281 DuplicateTarget,
282 #[error("restore safety target absent from inventory")]
284 TargetAbsentFromInventory,
285}
286#[derive(Debug, Error)]
288pub enum RestoreSafetyRequestError {
289 #[error(transparent)]
291 Requirement(#[from] RestoreSafetyRequirementError),
292 #[error(transparent)]
294 Plan(#[from] OperationPlanError),
295 #[error(transparent)]
297 Payload(#[from] IcRequestError),
298 #[error("restore safety requires exact load or start request")]
300 UnsupportedMethod,
301 #[error("restore safety observation ceiling exceeds {MAX_RESTORE_SAFETY_REMOTE_OBSERVATIONS}")]
303 ObservationLimitTooLarge,
304}
305
306#[cfg(test)]
307mod tests;