1use super::validation::require_version;
2use super::*;
3
4#[path = "pending_device_join.rs"]
5mod pending_device_join;
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(deny_unknown_fields)]
9pub struct MembershipCausalFloor {
10 pub effective_coordinates: Vec<MembershipCoord>,
11}
12
13impl MembershipCausalFloor {
14 pub fn from_membership(membership: &crate::membership::MembershipChain) -> Self {
15 Self {
16 effective_coordinates: membership.effective_frontier(),
17 }
18 }
19
20 pub fn advance(
21 &mut self,
22 coordinate: crate::membership::MembershipCoord,
23 ) -> Result<(), StoreProtocolError> {
24 let stream = coordinate.stream_key();
25 self.effective_coordinates
26 .retain(|current| current.stream_key() != stream);
27 self.effective_coordinates.push(coordinate);
28 self.effective_coordinates.sort();
29 self.validate()
30 }
31
32 pub fn is_included_in(&self, membership: &crate::membership::MembershipChain) -> bool {
33 self.effective_coordinates
34 .iter()
35 .all(|coordinate| membership.effectively_contains_coord(coordinate))
36 }
37
38 fn validate(&self) -> Result<(), StoreProtocolError> {
39 if self
40 .effective_coordinates
41 .windows(2)
42 .any(|pair| pair[0] >= pair[1])
43 {
44 return Err(StoreProtocolError::Malformed(
45 "Merge history membership floor is not canonical".to_string(),
46 ));
47 }
48 Ok(())
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56#[serde(deny_unknown_fields)]
57pub struct RetainedVerifiedActivatedAck {
58 pub acknowledgement: (StoreAckRef, StoreAck),
59 pub activating_commit: StoreBatchCommitRef,
60 pub predecessors: Vec<(StoreAckRef, StoreAck)>,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
71#[serde(deny_unknown_fields)]
72pub struct RetainedAcknowledgementChain {
73 #[serde(with = "ordered_map_entries")]
74 pub chain: BTreeMap<u64, (StoreAckRef, StoreAck)>,
75 pub activating_commit: StoreBatchCommitRef,
76 pub activating_commit_value: StoreBatchCommit,
77}
78
79#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
93#[serde(deny_unknown_fields)]
94pub struct RetainedReplaySnapshotAuthority {
95 pub store_root: StoreRootRef,
96 pub founder_registration: StoreDeviceRegistrationRef,
97 pub snapshot: StoreSnapshotRef,
98 pub metadata: SnapshotMeta,
99 #[serde(with = "ordered_map_entries")]
100 pub active_registrations: BTreeMap<StoreDeviceId, ReferencedStoreDeviceRegistration>,
101}
102
103impl RetainedReplaySnapshotAuthority {
104 pub fn validate(&self) -> Result<(), StoreProtocolError> {
105 self.metadata.state.devices.validate_canonical()?;
106 let author = self
107 .active_registrations
108 .get(&self.metadata.author_registration.device_id)
109 .filter(|registration| registration.reference() == &self.metadata.author_registration)
110 .ok_or_else(|| {
111 StoreProtocolError::Malformed(
112 "retained snapshot author is absent from its active registrations".to_string(),
113 )
114 })?;
115 self.metadata.verify_at(
116 self.store_root.store_root_hash,
117 &self.snapshot,
118 author.value(),
119 )?;
120 let expected_active = self
121 .metadata
122 .state
123 .devices
124 .devices
125 .iter()
126 .filter_map(|(device_id, record)| {
127 matches!(record.status, StoreDeviceStatus::Active)
128 .then_some((*device_id, &record.registration))
129 })
130 .collect::<BTreeMap<_, _>>();
131 if expected_active.len() != self.active_registrations.len()
132 || expected_active.iter().any(|(device_id, reference)| {
133 self.active_registrations
134 .get(device_id)
135 .is_none_or(|registration| registration.reference() != *reference)
136 })
137 {
138 return Err(StoreProtocolError::Malformed(
139 "retained snapshot replay authority does not exactly cover active devices"
140 .to_string(),
141 ));
142 }
143 for (device_id, registration) in &self.active_registrations {
144 let bytes = registration.value().to_bytes();
145 registration.reference().object.verify(&bytes)?;
146 StoreDeviceRegistration::parse_at(&bytes, &self.store_root, *device_id)?;
147 registration
148 .reference()
149 .verify_registration(registration.value())?;
150 }
151 Ok(())
152 }
153}
154
155impl RetainedVerifiedActivatedAck {
156 pub fn acknowledgement(&self) -> &(StoreAckRef, StoreAck) {
157 &self.acknowledgement
158 }
159
160 pub fn proof_objects(&self) -> impl Iterator<Item = &(StoreAckRef, StoreAck)> {
161 self.predecessors
162 .iter()
163 .chain(std::iter::once(&self.acknowledgement))
164 }
165
166 pub fn validate_predecessors(&self) -> Result<(), StoreProtocolError> {
167 let mut successor = &self.acknowledgement;
168 for predecessor in self.predecessors.iter().rev() {
169 let (reference, value) = predecessor;
170 reference.object.verify(&value.to_bytes())?;
171 if reference.registration != self.acknowledgement.0.registration
172 || reference.registration != value.registration
173 || reference.sequence != value.sequence
174 || reference.ack_hash != value.ack_hash()
175 || reference.sequence.checked_add(1) != Some(successor.0.sequence)
176 || successor.1.successor.predecessor.as_ref() != Some(&reference.object)
177 || successor.0.object.slot() != &value.successor.next_slot
178 || value.successor.activation != successor.1.successor.activation
179 || value.store_root_hash != successor.1.store_root_hash
180 {
181 return Err(StoreProtocolError::DeviceStateMismatch);
182 }
183 successor = predecessor;
184 }
185 Ok(())
186 }
187}
188
189impl RetainedAcknowledgementChain {
190 pub fn activated(
195 activated: &RetainedVerifiedActivatedAck,
196 activating_commit_value: &StoreBatchCommit,
197 ) -> Self {
198 Self {
199 chain: activated
200 .proof_objects()
201 .map(|proof| (proof.0.sequence, proof.clone()))
202 .collect(),
203 activating_commit: activated.activating_commit.clone(),
204 activating_commit_value: activating_commit_value.clone(),
205 }
206 }
207
208 pub fn extend(
213 &mut self,
214 activated: &RetainedVerifiedActivatedAck,
215 activating_commit_value: &StoreBatchCommit,
216 ) -> bool {
217 let (reference, _) = &activated.acknowledgement;
218 for proof in activated.proof_objects() {
219 match self.chain.get(&proof.0.sequence) {
220 Some(existing) if existing == proof => {}
221 Some(_) => return false,
222 None => {
223 self.chain.insert(proof.0.sequence, proof.clone());
224 }
225 }
226 }
227 if self
228 .latest()
229 .is_some_and(|(latest, _)| latest.sequence == reference.sequence)
230 {
231 self.activating_commit = activated.activating_commit.clone();
232 self.activating_commit_value = activating_commit_value.clone();
233 }
234 true
235 }
236
237 pub fn latest(&self) -> Option<&(StoreAckRef, StoreAck)> {
238 self.chain
239 .last_key_value()
240 .map(|(_, acknowledgement)| acknowledgement)
241 }
242
243 pub fn exactly_extends(&self, predecessor: &Self) -> bool {
244 self.chain.len() > predecessor.chain.len()
245 && predecessor.chain.iter().all(|(sequence, acknowledgement)| {
246 self.chain.get(sequence) == Some(acknowledgement)
247 })
248 }
249
250 pub fn validate_chain(
251 &self,
252 root: &StoreRootRef,
253 registration: &ReferencedStoreDeviceRegistration,
254 ) -> Result<(), StoreProtocolError> {
255 if self.chain.is_empty() {
256 return Err(StoreProtocolError::DeviceStateMismatch);
257 }
258 let mut predecessor: Option<&StoreAckRef> = None;
259 for (expected_sequence, (sequence, (reference, value))) in (1_u64..).zip(self.chain.iter())
260 {
261 if *sequence != expected_sequence
262 || reference.sequence != expected_sequence
263 || value.sequence != expected_sequence
264 || reference.registration != *registration.reference()
265 || value.registration != *registration.reference()
266 || value.successor.predecessor.as_ref()
267 != predecessor.map(|reference| &reference.object)
268 {
269 return Err(StoreProtocolError::DeviceStateMismatch);
270 }
271 reference.object.verify(&value.to_bytes())?;
272 StoreAck::parse_at(&value.to_bytes(), root, reference, registration.value())?;
273 predecessor = Some(reference);
274 }
275 Ok(())
276 }
277}
278
279#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
280#[serde(deny_unknown_fields)]
281pub struct RetainedMergeMembershipProof {
282 pub commit: StoreBatchCommitRef,
283 pub commit_value: StoreBatchCommit,
284 pub entry: MembershipEntryRef,
285 pub entry_value: MembershipEntry,
286 pub head: MembershipHeadRef,
287 pub head_value: AuthorHead,
288}
289
290#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
293#[serde(deny_unknown_fields)]
294pub struct RetainedMergeCommitEvidence {
295 pub acknowledgement: Option<Box<RetainedVerifiedActivatedAck>>,
296 pub membership_proof: Option<Box<RetainedMergeMembershipProof>>,
297}
298
299impl RetainedMergeCommitEvidence {
300 pub fn none() -> Self {
301 Self {
302 acknowledgement: None,
303 membership_proof: None,
304 }
305 }
306
307 pub fn validate_for(
308 &self,
309 commit_ref: &StoreBatchCommitRef,
310 commit: &StoreBatchCommit,
311 ) -> Result<(), StoreProtocolError> {
312 commit_ref.verify_commit(commit)?;
313 if commit.acknowledgement().is_some() != self.acknowledgement.is_some()
314 || commit.control().is_some() != self.membership_proof.is_some()
315 {
316 return Err(StoreProtocolError::DeviceStateMismatch);
317 }
318 if let Some(acknowledgement) = &self.acknowledgement {
319 acknowledgement.validate_predecessors()?;
320 let (reference, _) = acknowledgement.acknowledgement();
321 if acknowledgement.activating_commit != *commit_ref
322 || commit.acknowledgement() != Some(reference)
323 {
324 return Err(StoreProtocolError::DeviceStateMismatch);
325 }
326 }
327 if let Some(proof) = &self.membership_proof {
328 if proof.commit != *commit_ref || proof.commit_value != *commit {
329 return Err(StoreProtocolError::DeviceStateMismatch);
330 }
331 let control = commit
332 .control()
333 .ok_or(StoreProtocolError::DeviceStateMismatch)?;
334 if control.transition.body.entry != proof.entry
335 || proof.entry.coord != proof.entry_value.coord()
336 || !crate::membership::verify_membership_entry(&proof.entry_value)
337 || !control
338 .transition
339 .matches_head(&proof.head_value, &proof.head)
340 || !matches!(
341 &proof.head_value.activation,
342 crate::membership::MembershipHeadActivation::StoreCommit { commit, .. }
343 if commit == commit_ref
344 )
345 {
346 return Err(StoreProtocolError::DeviceStateMismatch);
347 }
348 proof
349 .entry
350 .object
351 .verify(&serde_json::to_vec(&proof.entry_value)?)?;
352 proof
353 .head
354 .object
355 .verify(&serde_json::to_vec(&proof.head_value)?)?;
356 }
357 Ok(())
358 }
359}
360
361#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
362#[serde(deny_unknown_fields)]
363pub struct RetainedVerifiedMergeHistorySummary {
364 pub reclaim: RetainedReclaimState,
365 pub version: u32,
366 pub store_root_hash: ObjectHash,
367 #[serde(with = "ordered_map_entries")]
368 pub causal_cut: BTreeMap<StoreCommitCoord, StoreBatchCommitRef>,
369 #[serde(with = "ordered_map_entries")]
374 pub last_non_acknowledgement_commits: BTreeMap<AuthorStreamId, StoreBatchCommitRef>,
375 pub post_state: StoreDeviceStateRef,
376 pub membership_floor: MembershipCausalFloor,
377 #[serde(with = "ordered_map_entries")]
378 pub registrations: BTreeMap<StoreDeviceId, ReferencedStoreDeviceRegistration>,
379 #[serde(with = "ordered_map_entries")]
380 pub acknowledgements: BTreeMap<StoreDeviceId, RetainedAcknowledgementChain>,
381 #[serde(with = "ordered_map_entries")]
382 pub membership_proofs: BTreeMap<StoreBatchCommitRef, RetainedMergeMembershipProof>,
383 #[serde(with = "ordered_map_entries")]
384 pub pending_owner_promotions: BTreeMap<OwnerPromotionId, RetainedOwnerPromotionRequest>,
385 #[serde(with = "ordered_map_entries")]
386 pub pending_device_joins:
387 BTreeMap<StoreBatchCommitRef, device_join_exchange::DeviceJoinBootstrapClosure>,
388}
389
390#[derive(Debug, Clone)]
391pub struct OpenedRetainedMergeHistorySummary {
392 pub summary: RetainedVerifiedMergeHistorySummary,
393 pub post_state: ResolvedStoreDeviceState,
394}
395
396impl RetainedVerifiedMergeHistorySummary {
397 pub fn validate_shape(&self) -> Result<(), StoreProtocolError> {
398 require_version(self.version)?;
399 self.reclaim.validate()?;
400 self.membership_floor.validate()?;
401 let mut frontier = BTreeMap::new();
402 for (coord, reference) in &self.causal_cut {
403 if coord != &reference.coord {
404 return Err(StoreProtocolError::Malformed(
405 "Merge history causal cut contains a mismatched coordinate".to_string(),
406 ));
407 }
408 let stream_id = reference.coord.stream_id;
409 let sequence = reference.coord.sequence;
410 match frontier.entry(stream_id) {
411 std::collections::btree_map::Entry::Vacant(entry) => {
412 entry.insert(reference.clone());
413 }
414 std::collections::btree_map::Entry::Occupied(mut entry) => {
415 if sequence > entry.get().coord.sequence() {
416 entry.insert(reference.clone());
417 }
418 }
419 }
420 }
421 if self.post_state.frontier() != &CommitFrontier(frontier) {
422 return Err(StoreProtocolError::DeviceStateMismatch);
423 }
424 for (stream, reference) in &self.last_non_acknowledgement_commits {
425 reference.coord.validate()?;
426 if stream != &reference.coord.stream_id
427 || !self.post_state.frontier().covers_commit(reference)
428 || self
429 .causal_cut
430 .get(&reference.coord)
431 .is_some_and(|covered| covered != reference)
432 {
433 return Err(StoreProtocolError::Malformed(
434 "Merge acknowledgement summary differs from its exact author frontier".into(),
435 ));
436 }
437 }
438 for (device_id, registration) in &self.registrations {
439 if device_id != ®istration.reference().device_id
440 || registration.value().store_root.store_root_hash != self.store_root_hash
441 {
442 return Err(StoreProtocolError::DeviceStateMismatch);
443 }
444 registration
445 .reference()
446 .verify_registration(registration.value())?;
447 registration
448 .reference()
449 .object
450 .verify(®istration.value().to_bytes())?;
451 StoreDeviceRegistration::parse_at(
452 ®istration.value().to_bytes(),
453 ®istration.value().store_root,
454 *device_id,
455 )?;
456 }
457 for (device_id, acknowledgement) in &self.acknowledgements {
458 let registration = self
459 .registrations
460 .get(device_id)
461 .ok_or(StoreProtocolError::DeviceStateMismatch)?;
462 acknowledgement.validate_chain(®istration.value().store_root, registration)?;
463 let (acknowledgement_ref, acknowledgement_value) = acknowledgement
464 .latest()
465 .ok_or(StoreProtocolError::DeviceStateMismatch)?;
466 acknowledgement
467 .activating_commit
468 .verify_commit(&acknowledgement.activating_commit_value)?;
469 if device_id != &acknowledgement_ref.registration.device_id
470 || acknowledgement.activating_commit_value.acknowledgement()
471 != Some(acknowledgement_ref)
472 || acknowledgement.activating_commit_value.author_registration
473 != *registration.reference()
474 || self
475 .causal_cut
476 .get(&acknowledgement.activating_commit.coord)
477 != Some(&acknowledgement.activating_commit)
478 {
479 return Err(StoreProtocolError::DeviceStateMismatch);
480 }
481 let predecessor_cut = acknowledgement
482 .activating_commit_value
483 .order
484 .predecessor_cut()?;
485 if acknowledgement_value.store_cut != predecessor_cut
486 || acknowledgement_value.device_state
487 != acknowledgement.activating_commit_value.device_state
488 {
489 return Err(StoreProtocolError::DeviceStateMismatch);
490 }
491 }
492 for (id, proof) in &self.pending_owner_promotions {
493 proof.validate_shape()?;
494 let request = proof.request()?;
495 let author = self
496 .registrations
497 .get(&request.promoter_registration.device_id)
498 .filter(|author| author.reference() == &request.promoter_registration)
499 .ok_or(StoreProtocolError::OwnerPromotionMismatch)?;
500 if id != &request.promotion_id
501 || request.store_root_hash != self.store_root_hash
502 || !self
503 .post_state
504 .frontier()
505 .covers_commit(&proof.publication.value.commit)
506 {
507 return Err(StoreProtocolError::OwnerPromotionMismatch);
508 }
509 proof
510 .publication
511 .value
512 .verify_for(&proof.commit, author.value())?;
513 }
514 for (activation, closure) in &self.pending_device_joins {
515 closure.accepted_commit(activation)?;
516 let opening = closure.verified_commit(activation)?;
517 if closure.publication.current.store_root_hash != self.store_root_hash
518 || !self.post_state.frontier().covers_commit(activation)
519 || !opening
520 .device_join_attempt_decisions()
521 .iter()
522 .any(|decision| matches!(decision, DeviceJoinAttemptDecisionRef::Attempt(_)))
523 || closure.publication.current.latest_snapshot().is_none()
524 {
525 return Err(StoreProtocolError::DeviceStateMismatch);
526 }
527 }
528 for (reference, proof) in &self.membership_proofs {
529 if reference != &proof.commit
530 || self.causal_cut.get(&proof.commit.coord) != Some(&proof.commit)
531 {
532 return Err(StoreProtocolError::DeviceStateMismatch);
533 }
534 proof.commit.verify_commit(&proof.commit_value)?;
535 let Some(control) = proof.commit_value.control() else {
536 return Err(StoreProtocolError::DeviceStateMismatch);
537 };
538 let transition = &control.transition;
539 if transition.body.entry != proof.entry
540 || proof.entry.coord != proof.entry_value.coord()
541 || !crate::membership::verify_membership_entry(&proof.entry_value)
542 {
543 return Err(StoreProtocolError::DeviceStateMismatch);
544 }
545 proof
546 .entry
547 .object
548 .verify(&serde_json::to_vec(&proof.entry_value)?)?;
549 let head_author = self
550 .registrations
551 .get(&proof.head_value.body.author_registration.device_id)
552 .ok_or(StoreProtocolError::DeviceStateMismatch)?;
553 if !transition.matches_head(&proof.head_value, &proof.head)
554 || !proof.head_value.verify(head_author.value())
555 || !matches!(
556 &proof.head_value.activation,
557 crate::membership::MembershipHeadActivation::StoreCommit { commit, .. }
558 if commit == &proof.commit
559 )
560 {
561 return Err(StoreProtocolError::DeviceStateMismatch);
562 }
563 proof
564 .head
565 .object
566 .verify(&serde_json::to_vec(&proof.head_value)?)?;
567 }
568 Ok(())
569 }
570
571 pub fn validate_snapshot_baseline(&self) -> Result<(), StoreProtocolError> {
572 self.validate_shape()
573 }
574}