1use crate::membership_mutation::PreparedMembershipPublication;
6use crate::objects::{ExactObjectRef, ExactObjectVersion, PreparedExactObject, StoreObjectError};
7use crate::store_commit::{
8 ActivatedStoreDeviceRegistration, SnapshotMeta, StoreBatchCommit, StoreBatchCommitRef,
9 StoreControl, StoreCurrentPublicationRecord, StorePublicationEntry, StorePublicationPayload,
10 StorePublicationRef, StoreSnapshotRef,
11};
12
13#[derive(Debug, thiserror::Error)]
16pub enum PreparedCommitError {
17 #[error("invalid prepared Store operation: {0}")]
18 Invariant(String),
19 #[error("prepared Store operation storage: {0}")]
20 Storage(#[from] crate::objects::StorageError),
21 #[error("prepared Store operation object: {0}")]
22 StoreObject(#[from] StoreObjectError),
23 #[error("prepared Store protocol: {0}")]
24 Protocol(#[from] crate::store_commit::StoreProtocolError),
25 #[error("prepared membership transition: {0}")]
26 Membership(#[from] crate::membership_mutation::MembershipPreparationError),
27 #[error("{operation}: {source}")]
28 Json {
29 operation: &'static str,
30 #[source]
31 source: serde_json::Error,
32 },
33 #[error("prepared Store remote object: {0}")]
34 RemoteObject(#[from] crate::remote_object::RemoteObjectRecordError),
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
44#[serde(deny_unknown_fields)]
45pub struct PreparedStoreOperationCommon {
46 pub commit: StoreBatchCommit,
47 pub reference: StoreBatchCommitRef,
48 pub registration_activation: Option<ActivatedStoreDeviceRegistration>,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
55#[serde(deny_unknown_fields)]
56pub struct PreparedStorePublication {
57 pub previous: StoreCurrentPublicationRecord,
58 pub previous_version: ExactObjectVersion,
59 pub entry: StorePublicationEntry,
60 pub entry_object: ExactObjectRef,
61 pub replacement: StoreCurrentPublicationRecord,
62}
63
64impl PreparedStorePublication {
65 pub fn reference(&self) -> Result<StorePublicationRef, PreparedCommitError> {
66 StorePublicationRef::from_entry(&self.entry, self.entry_object.clone())
67 .map_err(PreparedCommitError::from)
68 }
69
70 pub fn prepared_entry(&self) -> Result<PreparedExactObject, PreparedCommitError> {
71 PreparedExactObject::new(self.entry_object.clone(), self.entry.to_bytes())
72 .map_err(PreparedCommitError::from)
73 }
74
75 pub fn verify_commit(
76 &self,
77 commit: &crate::store_commit::VerifiedStoreBatchCommit,
78 ) -> Result<(), PreparedCommitError> {
79 let reference = self.reference()?;
80 let signing_pubkey = &commit.author().device_signing_pubkey;
81 StorePublicationEntry::parse_at(
82 &self.entry.to_bytes(),
83 commit.store_root_hash(),
84 &reference,
85 signing_pubkey,
86 )?;
87 self.replacement.verify_commit_transition(
88 &self.previous,
89 &self.entry,
90 &reference,
91 commit,
92 signing_pubkey,
93 )?;
94 Ok(())
95 }
96
97 pub fn validate_commit_shape(
98 &self,
99 commit: &StoreBatchCommit,
100 reference: &StoreBatchCommitRef,
101 ) -> Result<(), PreparedCommitError> {
102 let publication = self.reference()?;
103 if self.entry.predecessor.as_ref() != self.previous.accepted()
104 || self.entry.previous_state_hash != self.previous.state_hash()
105 || self.replacement.accepted() != Some(&publication)
106 || self.replacement.store_root_hash != self.previous.store_root_hash
107 || self.entry.store_root_hash != commit.store_root_hash
108 || self.entry.author_registration != commit.author_registration
109 || !matches!(&self.entry.payload, StorePublicationPayload::Commit(published) if published == reference)
110 {
111 return Err(PreparedCommitError::Invariant(
112 "prepared Store publication differs from its commit or predecessor".to_string(),
113 ));
114 }
115 Ok(())
116 }
117
118 pub fn validate_snapshot_shape(
119 &self,
120 snapshot: &SnapshotMeta,
121 reference: &StoreSnapshotRef,
122 ) -> Result<(), PreparedCommitError> {
123 let publication = self.reference()?;
124 if self.entry.predecessor.as_ref() != self.previous.accepted()
125 || self.entry.previous_state_hash != self.previous.state_hash()
126 || self.replacement.accepted() != Some(&publication)
127 || self.replacement.store_root_hash != self.previous.store_root_hash
128 || snapshot.publication_predecessor != self.previous
129 || self.entry.store_root_hash != snapshot.store_root_hash
130 || self.entry.author_registration != snapshot.author_registration
131 || !matches!(&self.entry.payload, StorePublicationPayload::Snapshot(published) if published == reference)
132 {
133 return Err(PreparedCommitError::Invariant(
134 "prepared Store publication differs from its snapshot or predecessor".to_string(),
135 ));
136 }
137 Ok(())
138 }
139}
140
141impl PreparedStoreOperationCommon {
142 pub fn prepared_commit(&self) -> Result<PreparedExactObject, PreparedCommitError> {
145 PreparedExactObject::new(self.reference.object.clone(), self.commit.to_bytes())
146 .map_err(PreparedCommitError::from)
147 }
148}
149
150#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
151#[serde(deny_unknown_fields)]
152pub struct PreparedStoreOperationCommit {
153 pub common: PreparedStoreOperationCommon,
154 pub publication: PreparedStorePublication,
155 pub history_evidence: super::store_commit::RetainedMergeCommitEvidence,
156}
157
158impl std::ops::Deref for PreparedStoreOperationCommit {
159 type Target = PreparedStoreOperationCommon;
160
161 fn deref(&self) -> &Self::Target {
162 &self.common
163 }
164}
165
166impl std::ops::DerefMut for PreparedStoreOperationCommit {
167 fn deref_mut(&mut self) -> &mut Self::Target {
168 &mut self.common
169 }
170}
171
172impl PreparedStoreOperationCommit {
173 pub(crate) fn candidate_remote_object(
174 &self,
175 ) -> Result<crate::remote_object::ClosedRemoteObject, PreparedCommitError> {
176 let commit_bytes = self.commit.to_bytes();
177 crate::remote_object::RemoteObjectRecord::candidate_commit(
180 self.reference.clone(),
181 &commit_bytes,
182 &commit_bytes,
183 )
184 .map_err(PreparedCommitError::from)
185 }
186
187 pub fn prepared_membership_publication(
188 &self,
189 ) -> Result<PreparedMembershipPublication, PreparedCommitError> {
190 let proof = self
191 .history_evidence
192 .membership_proof
193 .as_ref()
194 .ok_or_else(|| {
195 PreparedCommitError::Invariant(
196 "Store control lacks its prepared authority proof".into(),
197 )
198 })?;
199 let publication = PreparedMembershipPublication {
200 entry: proof.entry_value.clone(),
201 entry_ref: proof.entry.clone(),
202 head: proof.head_value.clone(),
203 head_ref: proof.head.clone(),
204 };
205 self.validate_closed_shape()?;
206 publication.validate()?;
207 Ok(publication)
208 }
209
210 pub(crate) fn retained_control_remote_objects(
211 &self,
212 authorities: Vec<crate::remote_object::ClosedRemoteObject>,
213 ) -> Result<Vec<crate::remote_object::ClosedRemoteObject>, PreparedCommitError> {
214 let publication = self.prepared_membership_publication()?;
215 self.close_merge_membership_remote_objects(&publication, &[], authorities)
216 }
217
218 pub fn merge_membership_activation_remote_objects(
219 &self,
220 wraps: &[super::wrapped_store_key::PreparedWrappedStoreKey],
221 ) -> Result<Vec<crate::remote_object::ClosedRemoteObject>, PreparedCommitError> {
222 let publication = self.prepared_membership_publication()?;
223 let expected_wraps: &[super::wrapped_store_key::WrappedStoreKeyRef] =
224 match &publication.entry.change {
225 super::membership::StoreAuthorityChange::RemoveMember { wrapped_keys, .. } => {
226 wrapped_keys
227 }
228 super::membership::StoreAuthorityChange::SetMember { wrapped_key, .. } => {
229 std::slice::from_ref(wrapped_key)
230 }
231 _ => {
232 return Err(PreparedCommitError::Invariant(
233 "Merge membership mutation graph contains another change".to_string(),
234 ));
235 }
236 };
237 if expected_wraps.len() != wraps.len()
238 || expected_wraps
239 .iter()
240 .zip(wraps)
241 .any(|(reference, prepared)| reference != &prepared.reference)
242 {
243 return Err(PreparedCommitError::Invariant(
244 "Merge membership mutation wraps differ from its exact entry".to_string(),
245 ));
246 }
247 self.close_merge_membership_remote_objects(&publication, wraps, Vec::new())
248 }
249
250 fn close_merge_membership_remote_objects(
251 &self,
252 publication: &PreparedMembershipPublication,
253 wraps: &[super::wrapped_store_key::PreparedWrappedStoreKey],
254 authorities: Vec<crate::remote_object::ClosedRemoteObject>,
255 ) -> Result<Vec<crate::remote_object::ClosedRemoteObject>, PreparedCommitError> {
256 let family = self.commit.candidate_family();
257 let mut objects = vec![self.candidate_remote_object()?];
258 objects.extend(publication.candidate_remote_objects(&self.commit, &self.reference)?);
259 for prepared in wraps {
260 let value = prepared.validate().map_err(PreparedCommitError::from)?;
261 let canonical =
262 serde_json::to_vec(&value).map_err(|source| PreparedCommitError::Json {
263 operation: "serialize Merge membership candidate wrap",
264 source,
265 })?;
266 objects.push(
267 crate::remote_object::RemoteObjectRecord::candidate_exclusive_merge_membership_wrapped_store_key(
268 family,
269 prepared.reference.clone(),
270 &canonical,
271 prepared.object.stored_bytes(),
272 self.reference.clone(),
273 )
274 .map_err(PreparedCommitError::from)?,
275 );
276 }
277 objects.extend(authorities);
278 let mut unique = std::collections::BTreeSet::new();
279 if objects
280 .iter()
281 .any(|object| !unique.insert(object.record().object_id()))
282 {
283 return Err(PreparedCommitError::Invariant(
284 "Merge membership authority graph repeats an exact object".to_string(),
285 ));
286 }
287 Ok(objects)
288 }
289
290 pub fn validate_closed_shape(&self) -> Result<(), PreparedCommitError> {
291 self.reference.verify_commit(&self.commit)?;
292 self.reference.object.verify(&self.commit.to_bytes())?;
293 self.publication
294 .validate_commit_shape(&self.commit, &self.reference)?;
295 self.history_evidence
296 .validate_for(&self.reference, &self.commit)?;
297 Ok(())
298 }
299
300 pub(crate) fn has_same_durable_activation_as(&self, other: &Self) -> bool {
301 self.reference == other.reference
302 && self.commit.to_bytes() == other.commit.to_bytes()
303 && self.registration_activation == other.registration_activation
304 && self.publication == other.publication
305 && self.history_evidence == other.history_evidence
306 }
307
308 pub fn acknowledgement_remote_objects(
309 &self,
310 acknowledgement: &crate::objects::ExactProtocolObject<super::store_commit::StoreAck>,
311 ) -> Result<Vec<crate::remote_object::ClosedRemoteObject>, PreparedCommitError> {
312 let reference = self.commit.acknowledgement().ok_or_else(|| {
313 PreparedCommitError::Invariant(
314 "prepared acknowledgement operation has no exact acknowledgement ref".to_string(),
315 )
316 })?;
317 if &reference.object != acknowledgement.prepared.reference()
318 || reference.ack_hash != acknowledgement.value.ack_hash()
319 || acknowledgement.value.to_bytes() != acknowledgement.bytes
320 {
321 return Err(PreparedCommitError::Invariant(
322 "prepared acknowledgement operation differs from its exact acknowledgement object"
323 .to_string(),
324 ));
325 }
326 let authority =
327 crate::remote_object::RemoteObjectRecord::candidate_activated_store_acknowledgement(
328 reference.clone(),
329 &acknowledgement.bytes,
330 acknowledgement.prepared.stored_bytes(),
331 self.reference.clone(),
332 )
333 .map_err(PreparedCommitError::from)?;
334 let mut authorities = vec![authority];
335 let retained = self
336 .history_evidence
337 .acknowledgement
338 .as_ref()
339 .ok_or_else(|| {
340 PreparedCommitError::Invariant(
341 "prepared acknowledgement omits its retained proof".into(),
342 )
343 })?;
344 retained.validate_predecessors()?;
345 for (reference, value) in &retained.predecessors {
346 let bytes = value.to_bytes();
347 authorities.push(
348 crate::remote_object::RemoteObjectRecord::candidate_activated_store_acknowledgement(
349 reference.clone(), &bytes, &bytes, self.reference.clone(),
350 )?,
351 );
352 }
353 self.retained_authority_remote_objects(authorities)
354 }
355
356 pub fn circle_acknowledgement_remote_objects(
357 &self,
358 acknowledgement: &crate::objects::ExactProtocolObject<super::store_commit::CircleAck>,
359 ) -> Result<Vec<crate::remote_object::ClosedRemoteObject>, PreparedCommitError> {
360 let reference = self
361 .commit
362 .circle_acknowledgements()
363 .iter()
364 .find(|reference| &reference.object == acknowledgement.prepared.reference())
365 .ok_or_else(|| {
366 PreparedCommitError::Invariant(
367 "prepared activation does not name its Circle acknowledgement object"
368 .to_string(),
369 )
370 })?;
371 if reference.circle_id != acknowledgement.value.circle_id
372 || reference.ack_hash != acknowledgement.value.ack_hash()
373 || acknowledgement.value.to_bytes() != acknowledgement.bytes
374 {
375 return Err(PreparedCommitError::Invariant(
376 "prepared Circle acknowledgement differs from its exact acknowledgement object"
377 .to_string(),
378 ));
379 }
380 let authority =
381 crate::remote_object::RemoteObjectRecord::candidate_activated_circle_acknowledgement(
382 reference.clone(),
383 &acknowledgement.bytes,
384 acknowledgement.prepared.stored_bytes(),
385 self.reference.clone(),
386 )
387 .map_err(PreparedCommitError::from)?;
388 self.retained_authority_remote_objects(vec![authority])
389 }
390
391 pub fn retained_authority_remote_objects(
392 &self,
393 authorities: Vec<crate::remote_object::ClosedRemoteObject>,
394 ) -> Result<Vec<crate::remote_object::ClosedRemoteObject>, PreparedCommitError> {
395 if authorities.is_empty() {
396 return Err(PreparedCommitError::Invariant(
397 "Store operation has no retained authority objects".to_string(),
398 ));
399 }
400 let mut authority_ids = std::collections::BTreeSet::new();
401 for authority in &authorities {
402 if !matches!(authority.record(), crate::remote_object::RemoteObjectRecord::RetainedAuthority(record)
403 if matches!(&record.state, crate::remote_object::RetainedAuthorityObjectState::Prepared { ownership }
404 if ownership.pending == std::collections::BTreeSet::from([self.reference.clone()])))
405 {
406 return Err(PreparedCommitError::Invariant(
407 "Store operation retained authority has different candidate ownership"
408 .to_string(),
409 ));
410 }
411 if !authority_ids.insert(authority.record().object_id()) {
412 return Err(PreparedCommitError::Invariant(
413 "Store operation repeats a retained authority object".to_string(),
414 ));
415 }
416 }
417 let mut objects = vec![self.candidate_remote_object()?];
418 objects.extend(authorities);
419 Ok(objects)
420 }
421
422 pub fn attach_merge_membership_proof(
423 &mut self,
424 publication: &PreparedMembershipPublication,
425 ) -> Result<(), PreparedCommitError> {
426 publication.validate().map_err(PreparedCommitError::from)?;
427 let reference = self.common.reference.clone();
428 let commit = self.common.commit.clone();
429 let Some(StoreControl { transition }) = commit.control() else {
430 return Err(PreparedCommitError::Invariant(
431 "Merge membership proof accompanies another Store control".to_string(),
432 ));
433 };
434 if !transition.matches_head(&publication.head, &publication.head_ref)
435 || publication.entry_ref != transition.body.entry
436 {
437 return Err(PreparedCommitError::Invariant(
438 "Merge membership proof differs from its signed Store transition".to_string(),
439 ));
440 }
441 self.history_evidence.membership_proof = Some(Box::new(
442 super::store_commit::RetainedMergeMembershipProof {
443 commit: reference,
444 commit_value: commit,
445 entry: publication.entry_ref.clone(),
446 entry_value: publication.entry.clone(),
447 head: publication.head_ref.clone(),
448 head_value: publication.head.clone(),
449 },
450 ));
451 self.validate_closed_shape()?;
452 Ok(())
453 }
454}
455
456#[derive(Debug, Clone)]
460pub struct CircleAckActivation {
461 pub reference: crate::store_commit::CircleAckRef,
462 pub ack: crate::objects::ExactProtocolObject<crate::store_commit::CircleAck>,
463}