Skip to main content

coven_protocol/store_commit/
batch_commit.rs

1use super::identifiers::commit_stream_id;
2use super::operation_refs::{
3    validate_commit_acknowledgement, validate_commit_circle_acknowledgements,
4    validate_device_exclusion_refs, validate_device_join_attempt_decision_refs,
5    validate_device_registration_refs, validate_provider_access_refs,
6};
7use super::validation::{
8    validate_commit_order, validate_commit_predecessor_states, validate_membership_coord,
9};
10use super::*;
11
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(deny_unknown_fields)]
14pub struct StoreBatchCommitBody {
15    pub store_root_hash: ObjectHash,
16    pub write_id: WriteId,
17    pub author_registration: StoreDeviceRegistrationRef,
18    pub order: StoreCommitOrder,
19    pub publication_base: StorePublicationBase,
20    pub membership_state: StoreMembershipStateRef,
21    pub device_state: StoreDeviceStateRef,
22    pub membership_authority: Option<MembershipCoord>,
23    pub candidate_objects: CandidateObjectManifest,
24    pub body: StoreCommitBody,
25}
26
27impl SignedBody for StoreBatchCommitBody {
28    const DOMAIN: &'static [u8] = COMMIT_DOMAIN;
29}
30
31pub type StoreBatchCommit = Signed<StoreBatchCommitBody>;
32
33mod authoring;
34mod validation;
35pub(super) use validation::candidate_manifest;
36#[cfg(test)]
37pub(super) use validation::validate_stream_activations;
38
39impl StoreBatchCommit {
40    pub(crate) fn verified_candidate_objects(
41        &self,
42    ) -> Result<&CandidateObjectManifest, StoreProtocolError> {
43        let expected = candidate_manifest(self.candidate_family(), &self.body)?;
44        if self.candidate_objects != expected {
45            return Err(StoreProtocolError::Malformed(
46                "candidate object manifest differs from exact commit body graph".to_string(),
47            ));
48        }
49        Ok(&self.candidate_objects)
50    }
51
52    pub fn seq(&self) -> u64 {
53        self.order.seq()
54    }
55
56    pub fn publication_base(&self) -> &StorePublicationBase {
57        &self.publication_base
58    }
59
60    pub fn candidate_family(&self) -> CandidateFamilyId {
61        CandidateFamilyId::derive(
62            self.store_root_hash,
63            &self.author_registration,
64            &self.write_id,
65            &self.order,
66        )
67    }
68
69    pub fn operations(&self) -> Option<&StoreCommitOperations> {
70        match &self.body {
71            StoreCommitBody::Operations(operations) => Some(operations),
72            StoreCommitBody::ReclaimAuthorization { .. }
73            | StoreCommitBody::ReclaimReceipt { .. }
74            | StoreCommitBody::OwnerPromotionRequest { .. }
75            | StoreCommitBody::AbandonCandidates { .. } => None,
76        }
77    }
78
79    pub fn control(&self) -> Option<&StoreControl> {
80        self.operations()
81            .and_then(|operations| operations.control.as_ref())
82    }
83
84    pub fn acknowledgement(&self) -> Option<&StoreAckRef> {
85        self.operations()
86            .and_then(|operations| operations.acknowledgement.as_ref())
87    }
88
89    pub fn circle_acknowledgements(&self) -> &[CircleAckRef] {
90        self.operations().map_or(&[], |operations| {
91            operations.circle_acknowledgements.as_slice()
92        })
93    }
94
95    pub fn retained_operation_objects(&self) -> Result<Vec<ExactObjectRef>, StoreProtocolError> {
96        let objects = self
97            .acknowledgement()
98            .map(|reference| reference.object.clone())
99            .into_iter()
100            .chain(
101                self.circle_acknowledgements()
102                    .iter()
103                    .map(|reference| reference.object.clone()),
104            )
105            .chain(
106                self.provider_access_grants()
107                    .iter()
108                    .map(|reference| reference.object.clone()),
109            )
110            .chain(self.device_join_attempt_decisions().iter().filter_map(
111                |decision| match decision {
112                    DeviceJoinAttemptDecisionRef::Attempt(_) => None,
113                    DeviceJoinAttemptDecisionRef::Abandoned(reference) => {
114                        Some(reference.object.clone())
115                    }
116                },
117            ))
118            .chain(
119                self.device_registrations()
120                    .iter()
121                    .map(|activation| activation.registration.object.clone()),
122            )
123            .chain(
124                self.device_exclusion_proposals()
125                    .iter()
126                    .map(|reference| reference.object.clone()),
127            )
128            .chain(
129                self.device_exclusion_outcomes()
130                    .iter()
131                    .map(|reference| reference.object().clone()),
132            )
133            .chain(
134                self.reclaim_authorization()
135                    .into_iter()
136                    .flat_map(|reference| {
137                        [reference.evidence.object.clone(), reference.object.clone()]
138                    }),
139            )
140            .chain(
141                self.reclaim_receipt()
142                    .map(|reference| reference.object.clone()),
143            )
144            .collect::<Vec<_>>();
145        if objects
146            .iter()
147            .collect::<std::collections::BTreeSet<_>>()
148            .len()
149            != objects.len()
150        {
151            return Err(StoreProtocolError::Malformed(
152                "Store operation publication repeats a retained authority object".to_string(),
153            ));
154        }
155        Ok(objects)
156    }
157
158    pub fn abandoned_candidates(&self) -> &[CandidateCleanupManifest] {
159        match &self.body {
160            StoreCommitBody::AbandonCandidates { manifests } => manifests,
161            StoreCommitBody::Operations(_)
162            | StoreCommitBody::ReclaimAuthorization { .. }
163            | StoreCommitBody::ReclaimReceipt { .. }
164            | StoreCommitBody::OwnerPromotionRequest { .. } => &[],
165        }
166    }
167
168    pub fn reclaim_authorization(&self) -> Option<&crate::reclaim::ReclaimAuthorizationRef> {
169        match &self.body {
170            StoreCommitBody::ReclaimAuthorization { authorization } => Some(authorization.as_ref()),
171            StoreCommitBody::Operations(_)
172            | StoreCommitBody::ReclaimReceipt { .. }
173            | StoreCommitBody::OwnerPromotionRequest { .. }
174            | StoreCommitBody::AbandonCandidates { .. } => None,
175        }
176    }
177
178    pub fn reclaim_receipt(&self) -> Option<&crate::reclaim::ReclaimReceiptRef> {
179        match &self.body {
180            StoreCommitBody::ReclaimReceipt { receipt } => Some(receipt.as_ref()),
181            StoreCommitBody::Operations(_)
182            | StoreCommitBody::ReclaimAuthorization { .. }
183            | StoreCommitBody::OwnerPromotionRequest { .. }
184            | StoreCommitBody::AbandonCandidates { .. } => None,
185        }
186    }
187
188    pub fn device_join_attempt_decisions(&self) -> &[DeviceJoinAttemptDecisionRef] {
189        self.operations().map_or(&[], |operations| {
190            operations.device_join_attempt_decisions.as_slice()
191        })
192    }
193
194    pub fn provider_access_grants(&self) -> &[crate::provider::StoreMemberProviderAccessGrantRef] {
195        self.operations().map_or(&[], |operations| {
196            operations.provider_access_grants.as_slice()
197        })
198    }
199
200    pub fn device_registrations(&self) -> &[ActivatedStoreDeviceRegistrationRef] {
201        match &self.body {
202            StoreCommitBody::Operations(operations) => operations.device_registrations.as_slice(),
203            StoreCommitBody::ReclaimAuthorization { .. }
204            | StoreCommitBody::ReclaimReceipt { .. }
205            | StoreCommitBody::OwnerPromotionRequest { .. } => &[],
206            StoreCommitBody::AbandonCandidates { .. } => &[],
207        }
208    }
209
210    pub fn device_exclusion_proposals(&self) -> &[StoreDeviceExclusionProposalRef] {
211        self.operations().map_or(&[], |operations| {
212            operations.device_exclusion_proposals.as_slice()
213        })
214    }
215
216    pub fn device_exclusion_outcomes(&self) -> &[StoreDeviceExclusionOutcomeRef] {
217        self.operations().map_or(&[], |operations| {
218            operations.device_exclusion_outcomes.as_slice()
219        })
220    }
221
222    pub fn stream_activations(&self) -> &[StreamActivation] {
223        self.operations()
224            .map_or(&[], |operations| operations.stream_activations.as_slice())
225    }
226
227    pub fn owner_promotion_request(&self) -> Option<&OwnerPromotionRequest> {
228        match &self.body {
229            StoreCommitBody::OwnerPromotionRequest { request } => Some(request),
230            StoreCommitBody::Operations(_)
231            | StoreCommitBody::ReclaimAuthorization { .. }
232            | StoreCommitBody::ReclaimReceipt { .. }
233            | StoreCommitBody::AbandonCandidates { .. } => None,
234        }
235    }
236
237    pub fn circle_controls(&self) -> &[CircleControlRef] {
238        self.operations()
239            .map_or(&[], |operations| operations.circle_controls.as_slice())
240    }
241
242    pub fn store_package(&self) -> Option<&StorePackageRef> {
243        self.operations()
244            .and_then(|operations| operations.store_package.as_ref())
245    }
246
247    pub fn circle_packages(&self) -> &[CirclePackageRef] {
248        self.operations()
249            .map_or(&[], |operations| operations.circle_packages.as_slice())
250    }
251}