Skip to main content

coven_protocol/remote_object/
domains.rs

1use super::nonactivation::*;
2use super::*;
3
4#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
5#[serde(deny_unknown_fields)]
6pub struct CandidateExclusiveTarget {
7    pub family: CandidateFamilyId,
8    pub domain: CandidateExclusiveObjectDomain,
9    pub semantic_hash: ObjectHash,
10    pub object: ExactObjectRef,
11}
12
13impl CandidateExclusiveTarget {
14    pub(super) fn validate_semantic(&self, bytes: &[u8]) -> Result<(), RemoteObjectRecordError> {
15        match &self.domain {
16            CandidateExclusiveObjectDomain::CircleBootstrapImage { reference, .. }
17                if bytes.is_empty() && self.semantic_hash == reference.image_hash =>
18            {
19                Ok(())
20            }
21            CandidateExclusiveObjectDomain::CircleBootstrapImage { .. } => {
22                Err(RemoteObjectRecordError::StoredReferenceMismatch)
23            }
24            _ => validate_semantic_hash(self.semantic_hash, bytes),
25        }
26    }
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30#[serde(rename_all = "snake_case", deny_unknown_fields)]
31pub enum CandidateExclusiveObjectDomain {
32    MergeMembershipEntry {
33        family: CandidateFamilyId,
34        reference: crate::membership::MembershipEntryRef,
35    },
36    MergeMembershipHead {
37        family: CandidateFamilyId,
38        reference: crate::membership::MembershipHeadRef,
39    },
40    MergeMembershipWrappedStoreKey {
41        family: CandidateFamilyId,
42        reference: crate::wrapped_store_key::WrappedStoreKeyRef,
43    },
44    StorePackage {
45        reference: crate::store_commit::StorePackageRef,
46    },
47    CirclePackage {
48        reference: crate::store_commit::CirclePackageRef,
49    },
50    CircleAccessLeaf {
51        family: CandidateFamilyId,
52        circle_id: CircleId,
53        reference: crate::store_commit::CircleAccessLeafObjectRef,
54    },
55    CircleAccessEnvelope {
56        family: CandidateFamilyId,
57        circle_id: CircleId,
58        reference: crate::store_commit::CircleAccessEnvelopeObjectRef,
59    },
60    CircleEpochCloseIntent {
61        family: CandidateFamilyId,
62        circle_id: CircleId,
63        reference: crate::circle_control::CircleEpochCloseIntentRef,
64    },
65    CircleEpochCloseOutcome {
66        family: CandidateFamilyId,
67        circle_id: CircleId,
68        reference: crate::circle_control::CircleEpochCloseOutcomeRef,
69    },
70    CircleEpochCloseCancellation {
71        family: CandidateFamilyId,
72        circle_id: CircleId,
73        reference: crate::circle_control::CircleEpochCloseCancellationRef,
74    },
75    CircleBootstrapImage {
76        family: CandidateFamilyId,
77        circle_id: CircleId,
78        owner_pubkey: String,
79        epoch_id: crate::circle::CircleEpochId,
80        recipient_slot: String,
81        reference: crate::store_commit::SnapshotImageRef,
82    },
83}
84
85impl CandidateExclusiveObjectDomain {
86    pub(super) fn family(&self) -> CandidateFamilyId {
87        match self {
88            Self::MergeMembershipEntry { family, .. }
89            | Self::MergeMembershipHead { family, .. }
90            | Self::MergeMembershipWrappedStoreKey { family, .. } => *family,
91            Self::StorePackage { reference } => reference.candidate_family,
92            Self::CirclePackage { reference } => reference.package.candidate_family,
93            Self::CircleAccessLeaf { family, .. }
94            | Self::CircleAccessEnvelope { family, .. }
95            | Self::CircleEpochCloseIntent { family, .. }
96            | Self::CircleEpochCloseOutcome { family, .. }
97            | Self::CircleEpochCloseCancellation { family, .. }
98            | Self::CircleBootstrapImage { family, .. } => *family,
99        }
100    }
101
102    pub(super) fn object(&self) -> &ExactObjectRef {
103        match self {
104            Self::MergeMembershipEntry { reference, .. } => &reference.object,
105            Self::MergeMembershipHead { reference, .. } => &reference.object,
106            Self::MergeMembershipWrappedStoreKey { reference, .. } => &reference.object,
107            Self::StorePackage { reference } => &reference.object,
108            Self::CirclePackage { reference } => &reference.package.object,
109            Self::CircleAccessLeaf { reference, .. } => &reference.object,
110            Self::CircleAccessEnvelope { reference, .. } => &reference.object,
111            Self::CircleEpochCloseIntent { reference, .. } => &reference.object,
112            Self::CircleEpochCloseOutcome { reference, .. } => &reference.object,
113            Self::CircleEpochCloseCancellation { reference, .. } => &reference.object,
114            Self::CircleBootstrapImage { reference, .. } => &reference.object,
115        }
116    }
117
118    pub fn shared_destination(&self) -> Option<SharedLiveSetObjectDomain> {
119        match self {
120            Self::StorePackage { reference } => Some(SharedLiveSetObjectDomain::StorePackage {
121                reference: reference.clone(),
122            }),
123            Self::CirclePackage { reference } => Some(SharedLiveSetObjectDomain::CirclePackage {
124                reference: reference.clone(),
125            }),
126            Self::CircleBootstrapImage { reference, .. } => {
127                Some(SharedLiveSetObjectDomain::CircleBootstrapImage {
128                    reference: reference.clone(),
129                })
130            }
131            Self::MergeMembershipEntry { .. }
132            | Self::MergeMembershipHead { .. }
133            | Self::MergeMembershipWrappedStoreKey { .. }
134            | Self::CircleAccessLeaf { .. }
135            | Self::CircleAccessEnvelope { .. }
136            | Self::CircleEpochCloseIntent { .. }
137            | Self::CircleEpochCloseOutcome { .. }
138            | Self::CircleEpochCloseCancellation { .. } => None,
139        }
140    }
141
142    pub fn retained_destination(&self) -> Option<RetainedAuthorityObjectDomain> {
143        match self {
144            Self::MergeMembershipEntry { reference, .. } => {
145                Some(RetainedAuthorityObjectDomain::MergeMembershipEntry {
146                    reference: reference.clone(),
147                })
148            }
149            Self::MergeMembershipHead { reference, .. } => {
150                Some(RetainedAuthorityObjectDomain::MergeMembershipHead {
151                    reference: reference.clone(),
152                })
153            }
154            Self::MergeMembershipWrappedStoreKey { reference, .. } => Some(
155                RetainedAuthorityObjectDomain::MergeMembershipWrappedStoreKey {
156                    reference: reference.clone(),
157                },
158            ),
159            Self::CircleAccessLeaf {
160                family,
161                circle_id,
162                reference,
163            } => Some(RetainedAuthorityObjectDomain::CircleAccessLeaf {
164                family: *family,
165                circle_id: *circle_id,
166                reference: reference.clone(),
167            }),
168            Self::CircleAccessEnvelope {
169                family,
170                circle_id,
171                reference,
172            } => Some(RetainedAuthorityObjectDomain::CircleAccessEnvelope {
173                family: *family,
174                circle_id: *circle_id,
175                reference: reference.clone(),
176            }),
177            Self::CircleEpochCloseIntent {
178                family,
179                circle_id,
180                reference,
181            } => Some(RetainedAuthorityObjectDomain::CircleEpochCloseIntent {
182                family: *family,
183                circle_id: *circle_id,
184                reference: reference.clone(),
185            }),
186            Self::CircleEpochCloseOutcome {
187                family,
188                circle_id,
189                reference,
190            } => Some(RetainedAuthorityObjectDomain::CircleEpochCloseOutcome {
191                family: *family,
192                circle_id: *circle_id,
193                reference: reference.clone(),
194            }),
195            Self::CircleEpochCloseCancellation {
196                family,
197                circle_id,
198                reference,
199            } => Some(
200                RetainedAuthorityObjectDomain::CircleEpochCloseCancellation {
201                    family: *family,
202                    circle_id: *circle_id,
203                    reference: reference.clone(),
204                },
205            ),
206            Self::StorePackage { .. }
207            | Self::CirclePackage { .. }
208            | Self::CircleBootstrapImage { .. } => None,
209        }
210    }
211}
212
213impl SharedLiveSetObjectDomain {
214    pub(super) fn package_object(&self) -> Result<&ExactObjectRef, RemoteObjectRecordError> {
215        match self {
216            Self::StoredBlob
217            | Self::StoreSnapshotImage { .. }
218            | Self::StoreMembershipRollup { .. }
219            | Self::CircleBootstrapImage { .. } => Err(RemoteObjectRecordError::DomainMismatch),
220            Self::StorePackage { reference } => Ok(&reference.object),
221            Self::CirclePackage { reference } => Ok(&reference.package.object),
222        }
223    }
224}
225
226#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
227#[serde(deny_unknown_fields)]
228pub struct SharedLiveSetObjectRef {
229    pub domain: SharedLiveSetObjectDomain,
230    pub semantic_hash: ObjectHash,
231    pub object: ExactObjectRef,
232}
233
234impl SharedLiveSetObjectRef {
235    pub(super) fn validate_semantic(&self, bytes: &[u8]) -> Result<(), RemoteObjectRecordError> {
236        match &self.domain {
237            SharedLiveSetObjectDomain::StoreSnapshotImage { reference }
238                if bytes.is_empty()
239                    && self.semantic_hash == reference.image_hash
240                    && self.object == reference.object =>
241            {
242                Ok(())
243            }
244            SharedLiveSetObjectDomain::StoreSnapshotImage { .. } => {
245                Err(RemoteObjectRecordError::StoredReferenceMismatch)
246            }
247            SharedLiveSetObjectDomain::CircleBootstrapImage { reference }
248                if bytes.is_empty()
249                    && self.semantic_hash == reference.image_hash
250                    && self.object == reference.object =>
251            {
252                Ok(())
253            }
254            SharedLiveSetObjectDomain::CircleBootstrapImage { .. } => {
255                Err(RemoteObjectRecordError::StoredReferenceMismatch)
256            }
257            // Like the images: the bytes went to the provider and were never
258            // kept here, so the record names its reference and nothing else.
259            SharedLiveSetObjectDomain::StoreMembershipRollup { reference }
260                if bytes.is_empty()
261                    && self.semantic_hash == reference.rollup_hash
262                    && self.object == reference.object =>
263            {
264                Ok(())
265            }
266            SharedLiveSetObjectDomain::StoreMembershipRollup { .. } => {
267                Err(RemoteObjectRecordError::StoredReferenceMismatch)
268            }
269            _ => validate_semantic_hash(self.semantic_hash, bytes),
270        }
271    }
272}
273
274#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
275#[serde(rename_all = "snake_case", deny_unknown_fields)]
276pub enum SharedLiveSetObjectDomain {
277    StoredBlob,
278    StoreSnapshotImage {
279        reference: crate::store_commit::SnapshotImageRef,
280    },
281    /// The membership rollup one snapshot generation published beside its
282    /// image. Content-addressed, so two generations over the same membership
283    /// frontier name the same object — which is why it is owned rather than
284    /// simply deleted with the generation that named it last.
285    StoreMembershipRollup {
286        reference: crate::store_commit::MembershipRollupRef,
287    },
288    CircleBootstrapImage {
289        reference: crate::store_commit::SnapshotImageRef,
290    },
291    StorePackage {
292        reference: crate::store_commit::StorePackageRef,
293    },
294    CirclePackage {
295        reference: crate::store_commit::CirclePackageRef,
296    },
297}
298
299#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
300#[serde(deny_unknown_fields)]
301pub struct RetainedAuthorityObjectRef {
302    pub domain: RetainedAuthorityObjectDomain,
303    pub semantic_hash: ObjectHash,
304    pub object: ExactObjectRef,
305}
306
307#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
308#[serde(deny_unknown_fields)]
309pub struct ProtocolInertObject {
310    pub identity: RetainedAuthorityObjectRef,
311    pub(crate) former_candidates: Vec<CandidateNonactivation>,
312}
313
314impl ProtocolInertObject {
315    pub(super) fn new(
316        identity: RetainedAuthorityObjectRef,
317        former_candidates: Vec<CandidateNonactivation>,
318    ) -> Result<Self, RemoteObjectRecordError> {
319        let value = Self {
320            identity,
321            former_candidates,
322        };
323        value.validate()?;
324        Ok(value)
325    }
326
327    pub fn object_id(&self) -> ObjectHash {
328        remote_object_id(&self.identity.object)
329    }
330
331    pub fn validate(&self) -> Result<(), RemoteObjectRecordError> {
332        validate_nonactivations(&self.former_candidates)
333    }
334
335    pub fn candidate_nonactivation_proof(
336        &self,
337        candidate: &StoreBatchCommitRef,
338    ) -> Result<Option<&CandidateNonactivationProof>, RemoteObjectRecordError> {
339        self.validate()?;
340        find_nonactivation_proof(&self.former_candidates, candidate)
341    }
342
343    pub fn is_terminal_head_for(
344        &self,
345        candidate: &StoreBatchCommitRef,
346        object: &ExactObjectRef,
347    ) -> Result<bool, RemoteObjectRecordError> {
348        self.validate()?;
349        Ok(self.identity.object == *object
350            && matches!(
351                &self.identity.domain,
352                RetainedAuthorityObjectDomain::DeviceHead { reference, head_commit }
353                    if reference.object == *object && head_commit == candidate
354            )
355            && matches!(
356                self.candidate_nonactivation_proof(candidate)?,
357                Some(
358                    CandidateNonactivationProof::AuthorExclusion { .. }
359                        | CandidateNonactivationProof::MergeMembershipGrantRevocation { .. }
360                )
361            ))
362    }
363}
364
365impl RetainedAuthorityObjectRef {
366    pub(super) fn validate_semantic(&self, bytes: &[u8]) -> Result<(), RemoteObjectRecordError> {
367        validate_semantic_hash(self.semantic_hash, bytes)
368    }
369}
370
371#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
372#[serde(rename_all = "snake_case", deny_unknown_fields)]
373pub enum RetainedAuthorityObjectDomain {
374    Commit {
375        reference: StoreBatchCommitRef,
376    },
377    DeviceHead {
378        reference: crate::store_commit::StoreDeviceHeadRef,
379        /// The commit this head publishes, read out of the head's bytes when
380        /// the record was built. A head must belong to a candidate that owns
381        /// it, and carrying the commit is what lets a load check that without
382        /// re-parsing megabytes of signed head.
383        head_commit: StoreBatchCommitRef,
384    },
385    Acknowledgement {
386        reference: crate::store_commit::StoreAckRef,
387    },
388    CircleAcknowledgement {
389        reference: CircleAckRef,
390    },
391    MergeMembershipWrappedStoreKey {
392        reference: crate::wrapped_store_key::WrappedStoreKeyRef,
393    },
394    StoreMembershipResolution {
395        reference: crate::membership::StoreMembershipConflictResolutionRef,
396    },
397    MergeMembershipEntry {
398        reference: crate::membership::MembershipEntryRef,
399    },
400    MergeMembershipHead {
401        reference: crate::membership::MembershipHeadRef,
402    },
403    DeviceExclusionProposal {
404        reference: crate::store_commit::StoreDeviceExclusionProposalRef,
405    },
406    DeviceExclusionOutcome {
407        reference: crate::store_commit::StoreDeviceExclusionOutcomeRef,
408    },
409    ReclaimEvidence {
410        reference: crate::reclaim::ReclaimEvidenceRef,
411    },
412    ReclaimAuthorization {
413        reference: crate::reclaim::ReclaimAuthorizationRef,
414    },
415    ReclaimReceipt {
416        reference: crate::reclaim::ReclaimReceiptRef,
417    },
418    CircleAccessLeaf {
419        family: CandidateFamilyId,
420        circle_id: CircleId,
421        reference: crate::store_commit::CircleAccessLeafObjectRef,
422    },
423    CircleAccessEnvelope {
424        family: CandidateFamilyId,
425        circle_id: CircleId,
426        reference: crate::store_commit::CircleAccessEnvelopeObjectRef,
427    },
428    CircleEpochCloseIntent {
429        family: CandidateFamilyId,
430        circle_id: CircleId,
431        reference: crate::circle_control::CircleEpochCloseIntentRef,
432    },
433    CircleEpochCloseOutcome {
434        family: CandidateFamilyId,
435        circle_id: CircleId,
436        reference: crate::circle_control::CircleEpochCloseOutcomeRef,
437    },
438    CircleEpochCloseCancellation {
439        family: CandidateFamilyId,
440        circle_id: CircleId,
441        reference: crate::circle_control::CircleEpochCloseCancellationRef,
442    },
443}