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