canic_backup/plan/preflight/
authority.rs1use crate::plan::{
8 BackupPlan, BackupPlanError, BackupTarget, ControlAuthorityReceipt,
9 SnapshotReadAuthorityReceipt,
10 validation::{
11 validate_nonempty, validate_optional_nonempty, validate_preflight_id,
12 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 if receipt_map
123 .insert(receipt.target_canister_id.clone(), receipt.clone())
124 .is_some()
125 {
126 return Err(BackupPlanError::DuplicateAuthorityReceipt(
127 receipt.target_canister_id.clone(),
128 ));
129 }
130 }
131
132 Ok(receipt_map)
133}
134
135fn snapshot_read_receipt_map(
136 plan_id: &str,
137 preflight_id: &str,
138 as_of: &str,
139 targets: &[BackupTarget],
140 receipts: &[SnapshotReadAuthorityReceipt],
141) -> Result<BTreeMap<String, SnapshotReadAuthorityReceipt>, BackupPlanError> {
142 let target_ids = targets
143 .iter()
144 .map(|target| target.canister_id.as_str())
145 .collect::<BTreeSet<_>>();
146 let mut receipt_map = BTreeMap::new();
147
148 for receipt in receipts {
149 validate_authority_receipt_header(AuthorityReceiptHeaderInput {
150 expected_plan_id: plan_id,
151 expected_preflight_id: preflight_id,
152 as_of,
153 target_ids: &target_ids,
154 actual_plan_id: &receipt.plan_id,
155 actual_preflight_id: &receipt.preflight_id,
156 target_canister_id: &receipt.target_canister_id,
157 validated_at: &receipt.validated_at,
158 expires_at: &receipt.expires_at,
159 message: receipt.message.as_deref(),
160 })?;
161 if receipt_map
162 .insert(receipt.target_canister_id.clone(), receipt.clone())
163 .is_some()
164 {
165 return Err(BackupPlanError::DuplicateAuthorityReceipt(
166 receipt.target_canister_id.clone(),
167 ));
168 }
169 }
170
171 Ok(receipt_map)
172}
173
174struct AuthorityReceiptHeaderInput<'a> {
175 expected_plan_id: &'a str,
176 expected_preflight_id: &'a str,
177 as_of: &'a str,
178 target_ids: &'a BTreeSet<&'a str>,
179 actual_plan_id: &'a str,
180 actual_preflight_id: &'a str,
181 target_canister_id: &'a str,
182 validated_at: &'a str,
183 expires_at: &'a str,
184 message: Option<&'a str>,
185}
186
187fn validate_authority_receipt_header(
188 input: AuthorityReceiptHeaderInput<'_>,
189) -> Result<(), BackupPlanError> {
190 validate_nonempty("authority_receipts[].plan_id", input.actual_plan_id)?;
191 validate_preflight_id(input.actual_preflight_id)?;
192 validate_principal(
193 "authority_receipts[].target_canister_id",
194 input.target_canister_id,
195 )?;
196 validate_optional_nonempty("authority_receipts[].message", input.message)?;
197 validate_preflight_window(
198 input.actual_preflight_id,
199 input.validated_at,
200 input.expires_at,
201 input.as_of,
202 )?;
203
204 if input.actual_plan_id != input.expected_plan_id {
205 return Err(BackupPlanError::AuthorityReceiptPlanMismatch {
206 expected: input.expected_plan_id.to_string(),
207 actual: input.actual_plan_id.to_string(),
208 });
209 }
210 if input.actual_preflight_id != input.expected_preflight_id {
211 return Err(BackupPlanError::AuthorityReceiptPreflightMismatch {
212 expected: input.expected_preflight_id.to_string(),
213 actual: input.actual_preflight_id.to_string(),
214 });
215 }
216 if !input.target_ids.contains(input.target_canister_id) {
217 return Err(BackupPlanError::UnknownAuthorityReceiptTarget(
218 input.target_canister_id.to_string(),
219 ));
220 }
221
222 Ok(())
223}