ic_backup/model/fence_obligation/
mod.rs1use crate::model::evidence_digest::EvidenceDigest;
4use crate::model::{
5 artifacts::ArtifactChecksumRecord,
6 consistency::{
7 ApplicationFenceBinding, ConsistencyGuaranteeRecord, ConsistencyRequirementError,
8 ConsistencyRequirementRecord,
9 },
10 operation_plan::{OperationPlanError, OperationPlanRecord},
11 restore_safety::{
12 RestoreFenceBindingRecord, RestoreSafetyLaneRecord, RestoreSafetyRequirementError,
13 RestoreSafetyRequirementRecord,
14 },
15};
16use serde::{Deserialize, Serialize};
17use thiserror::Error;
18
19pub const MAX_FENCE_OBLIGATION_BYTES: u64 = 1024;
21
22#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
24#[serde(tag = "purpose", rename_all = "snake_case", deny_unknown_fields)]
25pub enum FenceObligationScopeRecord {
26 Capture {
28 requirement: ArtifactChecksumRecord,
30 identity: ArtifactChecksumRecord,
32 membership_revision: ArtifactChecksumRecord,
34 },
35 Restore {
37 requirement: ArtifactChecksumRecord,
39 fence: RestoreFenceBindingRecord,
41 },
42}
43
44#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
52#[serde(try_from = "ObligationFields")]
53pub struct FenceObligationRecord {
54 version: u16,
55 plan_intent: ArtifactChecksumRecord,
56 acquisition_operation: u64,
57 scope: FenceObligationScopeRecord,
58}
59#[derive(Deserialize)]
60#[serde(deny_unknown_fields)]
61struct ObligationFields {
62 version: u16,
63 plan_intent: ArtifactChecksumRecord,
64 acquisition_operation: u64,
65 scope: FenceObligationScopeRecord,
66}
67impl TryFrom<ObligationFields> for FenceObligationRecord {
68 type Error = FenceObligationError;
69 fn try_from(fields: ObligationFields) -> Result<Self, Self::Error> {
70 if fields.version != 1 {
71 return Err(FenceObligationError::UnsupportedVersion(fields.version));
72 }
73 Ok(Self {
74 version: 1,
75 plan_intent: fields.plan_intent,
76 acquisition_operation: fields.acquisition_operation,
77 scope: fields.scope,
78 })
79 }
80}
81impl FenceObligationRecord {
82 pub fn for_capture(
86 plan: &OperationPlanRecord,
87 requirement: &ConsistencyRequirementRecord,
88 acquisition_operation: u64,
89 fence: &ApplicationFenceBinding,
90 ) -> Result<Self, FenceObligationError> {
91 requirement.validate_plan(plan)?;
92 if requirement.guarantee() != ConsistencyGuaranteeRecord::ApplicationCoordinated {
93 return Err(FenceObligationError::FenceNotRequired);
94 }
95 Self::new(
96 plan,
97 acquisition_operation,
98 FenceObligationScopeRecord::Capture {
99 requirement: requirement.digest(),
100 identity: fence.identity.clone(),
101 membership_revision: fence.membership_revision.clone(),
102 },
103 )
104 }
105 pub fn for_restore(
109 plan: &OperationPlanRecord,
110 source: &OperationPlanRecord,
111 requirement: &RestoreSafetyRequirementRecord,
112 acquisition_operation: u64,
113 ) -> Result<Self, FenceObligationError> {
114 requirement.validate_plans(plan, source)?;
115 if requirement.safety() != RestoreSafetyLaneRecord::ApplicationFenced {
116 return Err(FenceObligationError::FenceNotRequired);
117 }
118 let fence = requirement
119 .expected_fence()
120 .ok_or(FenceObligationError::FenceNotRequired)?;
121 Self::new(
122 plan,
123 acquisition_operation,
124 FenceObligationScopeRecord::Restore {
125 requirement: requirement.digest(),
126 fence: fence.clone(),
127 },
128 )
129 }
130 fn new(
131 plan: &OperationPlanRecord,
132 acquisition_operation: u64,
133 scope: FenceObligationScopeRecord,
134 ) -> Result<Self, FenceObligationError> {
135 let record = Self {
136 version: 1,
137 plan_intent: plan.digest(),
138 acquisition_operation,
139 scope,
140 };
141 record.validate_plan(plan)?;
142 Ok(record)
143 }
144 #[must_use]
146 pub const fn plan_intent(&self) -> &ArtifactChecksumRecord {
147 &self.plan_intent
148 }
149 #[must_use]
151 pub const fn acquisition_operation(&self) -> u64 {
152 self.acquisition_operation
153 }
154 #[must_use]
156 pub const fn scope(&self) -> &FenceObligationScopeRecord {
157 &self.scope
158 }
159 pub fn validate_plan(&self, plan: &OperationPlanRecord) -> Result<(), FenceObligationError> {
163 if self.plan_intent != plan.digest() {
164 return Err(FenceObligationError::PlanMismatch);
165 }
166 if plan
167 .attempt_authority(self.acquisition_operation)?
168 .budget()
169 .mutations()
170 == 0
171 {
172 return Err(FenceObligationError::NoAcquisitionAllowance);
173 }
174 Ok(())
175 }
176 pub fn validate_capture(
180 &self,
181 plan: &OperationPlanRecord,
182 requirement: &ConsistencyRequirementRecord,
183 fence: &ApplicationFenceBinding,
184 ) -> Result<(), FenceObligationError> {
185 let original = Self::for_capture(plan, requirement, self.acquisition_operation, fence)?;
186 self.validate_original(&original)
187 }
188 pub fn validate_restore(
192 &self,
193 plan: &OperationPlanRecord,
194 source: &OperationPlanRecord,
195 requirement: &RestoreSafetyRequirementRecord,
196 ) -> Result<(), FenceObligationError> {
197 let original = Self::for_restore(plan, source, requirement, self.acquisition_operation)?;
198 self.validate_original(&original)
199 }
200 fn validate_original(&self, original: &Self) -> Result<(), FenceObligationError> {
201 if self != original {
202 return Err(FenceObligationError::BindingMismatch);
203 }
204 Ok(())
205 }
206 #[must_use]
212 pub fn digest(&self) -> ArtifactChecksumRecord {
213 let mut bytes = EvidenceDigest::new(b"ic-backup/fence-obligation/v1\0");
214 bytes.append(self.plan_intent.hash().as_bytes());
215 bytes.append(&self.acquisition_operation.to_be_bytes());
216 let (tag, hashes) = match &self.scope {
217 FenceObligationScopeRecord::Capture {
218 requirement,
219 identity,
220 membership_revision,
221 } => (0, vec![requirement, identity, membership_revision]),
222 FenceObligationScopeRecord::Restore { requirement, fence } => (
223 1,
224 vec![
225 requirement,
226 &fence.identity,
227 &fence.membership_revision,
228 &fence.external_obligations_revision,
229 ],
230 ),
231 };
232 bytes.byte(tag);
233 for hash in hashes {
234 bytes.append(hash.hash().as_bytes());
235 }
236 bytes.finish()
237 }
238}
239#[derive(Debug, Error)]
241pub enum FenceObligationError {
242 #[error("unsupported fence obligation version {0}")]
244 UnsupportedVersion(u16),
245 #[error("fence obligation original plan mismatch")]
247 PlanMismatch,
248 #[error("fence obligation original binding mismatch")]
250 BindingMismatch,
251 #[error("original requirement does not require an application fence")]
253 FenceNotRequired,
254 #[error("original fence acquisition operation has no mutation allowance")]
256 NoAcquisitionAllowance,
257 #[error(transparent)]
259 Plan(#[from] OperationPlanError),
260 #[error(transparent)]
262 Consistency(#[from] ConsistencyRequirementError),
263 #[error(transparent)]
265 Restore(#[from] RestoreSafetyRequirementError),
266}
267
268#[cfg(test)]
269mod tests;