canic_backup/plan/preflight/
authority.rs1use crate::plan::{
8 BackupPlan, BackupPlanError, BackupTarget, ControlAuthorityReceipt,
9 SnapshotReadAuthorityReceipt,
10 validation::{
11 validate_control_authority, validate_nonempty, validate_optional_nonempty,
12 validate_preflight_id, validate_preflight_window, validate_principal,
13 },
14};
15
16use std::collections::{BTreeMap, BTreeSet};
17
18impl BackupPlan {
19 pub fn apply_authority_preflight_receipts(
21 &mut self,
22 preflight_id: &str,
23 control_receipts: &[ControlAuthorityReceipt],
24 snapshot_read_receipts: &[SnapshotReadAuthorityReceipt],
25 as_of: &str,
26 ) -> Result<(), BackupPlanError> {
27 self.apply_control_authority_receipts(preflight_id, control_receipts, as_of)?;
28 self.apply_snapshot_read_authority_receipts(preflight_id, snapshot_read_receipts, as_of)
29 }
30
31 pub fn apply_control_authority_receipts(
33 &mut self,
34 preflight_id: &str,
35 receipts: &[ControlAuthorityReceipt],
36 as_of: &str,
37 ) -> Result<(), BackupPlanError> {
38 let mut receipts =
39 control_receipt_map(&self.plan_id, preflight_id, as_of, &self.targets, receipts)?;
40 let mut updates = Vec::with_capacity(self.targets.len());
41 for (index, target) in self.targets.iter().enumerate() {
42 let receipt = receipts.remove(&target.canister_id).ok_or_else(|| {
43 BackupPlanError::MissingControlAuthorityReceipt(target.canister_id.clone())
44 })?;
45 if !receipt.authority.is_proven() {
46 return Err(BackupPlanError::UnprovenControlAuthority(
47 target.canister_id.clone(),
48 ));
49 }
50 if self.requires_root_controller
51 && target.canister_id != self.root_canister_id
52 && !receipt.authority.is_proven_root_controller()
53 {
54 return Err(BackupPlanError::MissingRootController(
55 target.canister_id.clone(),
56 ));
57 }
58 updates.push((index, receipt.authority));
59 }
60
61 for (index, authority) in updates {
62 self.targets[index].control_authority = authority;
63 }
64 Ok(())
65 }
66
67 pub fn apply_snapshot_read_authority_receipts(
69 &mut self,
70 preflight_id: &str,
71 receipts: &[SnapshotReadAuthorityReceipt],
72 as_of: &str,
73 ) -> Result<(), BackupPlanError> {
74 let mut receipts =
75 snapshot_read_receipt_map(&self.plan_id, preflight_id, as_of, &self.targets, receipts)?;
76 let mut updates = Vec::with_capacity(self.targets.len());
77 for (index, target) in self.targets.iter().enumerate() {
78 let receipt = receipts.remove(&target.canister_id).ok_or_else(|| {
79 BackupPlanError::MissingSnapshotReadAuthorityReceipt(target.canister_id.clone())
80 })?;
81 if !receipt.authority.is_proven() {
82 return Err(BackupPlanError::UnprovenTargetSnapshotReadAuthority(
83 target.canister_id.clone(),
84 ));
85 }
86 updates.push((index, receipt.authority));
87 }
88
89 for (index, authority) in updates {
90 self.targets[index].snapshot_read_authority = authority;
91 }
92 Ok(())
93 }
94}
95
96fn control_receipt_map(
97 plan_id: &str,
98 preflight_id: &str,
99 as_of: &str,
100 targets: &[BackupTarget],
101 receipts: &[ControlAuthorityReceipt],
102) -> Result<BTreeMap<String, ControlAuthorityReceipt>, BackupPlanError> {
103 let target_ids = targets
104 .iter()
105 .map(|target| target.canister_id.as_str())
106 .collect::<BTreeSet<_>>();
107 let mut receipt_map = BTreeMap::new();
108
109 for receipt in receipts {
110 validate_authority_receipt_header(AuthorityReceiptHeaderInput {
111 expected_plan_id: plan_id,
112 expected_preflight_id: preflight_id,
113 as_of,
114 target_ids: &target_ids,
115 actual_plan_id: &receipt.plan_id,
116 actual_preflight_id: &receipt.preflight_id,
117 target_canister_id: &receipt.target_canister_id,
118 validated_at: &receipt.validated_at,
119 expires_at: &receipt.expires_at,
120 message: receipt.message.as_deref(),
121 })?;
122 validate_control_authority(&receipt.authority)?;
123 if receipt_map
124 .insert(receipt.target_canister_id.clone(), receipt.clone())
125 .is_some()
126 {
127 return Err(BackupPlanError::DuplicateAuthorityReceipt(
128 receipt.target_canister_id.clone(),
129 ));
130 }
131 }
132
133 Ok(receipt_map)
134}
135
136fn snapshot_read_receipt_map(
137 plan_id: &str,
138 preflight_id: &str,
139 as_of: &str,
140 targets: &[BackupTarget],
141 receipts: &[SnapshotReadAuthorityReceipt],
142) -> Result<BTreeMap<String, SnapshotReadAuthorityReceipt>, BackupPlanError> {
143 let target_ids = targets
144 .iter()
145 .map(|target| target.canister_id.as_str())
146 .collect::<BTreeSet<_>>();
147 let mut receipt_map = BTreeMap::new();
148
149 for receipt in receipts {
150 validate_authority_receipt_header(AuthorityReceiptHeaderInput {
151 expected_plan_id: plan_id,
152 expected_preflight_id: preflight_id,
153 as_of,
154 target_ids: &target_ids,
155 actual_plan_id: &receipt.plan_id,
156 actual_preflight_id: &receipt.preflight_id,
157 target_canister_id: &receipt.target_canister_id,
158 validated_at: &receipt.validated_at,
159 expires_at: &receipt.expires_at,
160 message: receipt.message.as_deref(),
161 })?;
162 if receipt_map
163 .insert(receipt.target_canister_id.clone(), receipt.clone())
164 .is_some()
165 {
166 return Err(BackupPlanError::DuplicateAuthorityReceipt(
167 receipt.target_canister_id.clone(),
168 ));
169 }
170 }
171
172 Ok(receipt_map)
173}
174
175struct AuthorityReceiptHeaderInput<'a> {
176 expected_plan_id: &'a str,
177 expected_preflight_id: &'a str,
178 as_of: &'a str,
179 target_ids: &'a BTreeSet<&'a str>,
180 actual_plan_id: &'a str,
181 actual_preflight_id: &'a str,
182 target_canister_id: &'a str,
183 validated_at: &'a str,
184 expires_at: &'a str,
185 message: Option<&'a str>,
186}
187
188fn validate_authority_receipt_header(
189 input: AuthorityReceiptHeaderInput<'_>,
190) -> Result<(), BackupPlanError> {
191 validate_nonempty("authority_receipts[].plan_id", input.actual_plan_id)?;
192 validate_preflight_id(input.actual_preflight_id)?;
193 validate_principal(
194 "authority_receipts[].target_canister_id",
195 input.target_canister_id,
196 )?;
197 validate_optional_nonempty("authority_receipts[].message", input.message)?;
198 validate_preflight_window(
199 input.actual_preflight_id,
200 input.validated_at,
201 input.expires_at,
202 input.as_of,
203 )?;
204
205 if input.actual_plan_id != input.expected_plan_id {
206 return Err(BackupPlanError::AuthorityReceiptPlanMismatch {
207 expected: input.expected_plan_id.to_string(),
208 actual: input.actual_plan_id.to_string(),
209 });
210 }
211 if input.actual_preflight_id != input.expected_preflight_id {
212 return Err(BackupPlanError::AuthorityReceiptPreflightMismatch {
213 expected: input.expected_preflight_id.to_string(),
214 actual: input.actual_preflight_id.to_string(),
215 });
216 }
217 if !input.target_ids.contains(input.target_canister_id) {
218 return Err(BackupPlanError::UnknownAuthorityReceiptTarget(
219 input.target_canister_id.to_string(),
220 ));
221 }
222
223 Ok(())
224}