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_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}