1use super::commit::{
2 StoreCommitVerifier, StoreMembershipObjectVerifier, VerifiedMergeMembershipClosure,
3};
4use crate::sync::store::pull;
5use crate::sync::store::pull::*;
6use crate::sync::store::StoreError;
7use coven_database::{
8 DeviceJoinBootstrapActivation, DeviceJoinBootstrapCommit, DeviceJoinBootstrapPlan,
9};
10use coven_database::{VerifiedAcknowledgedStoreSnapshot, VerifiedStoreSnapshotAuthority};
11use coven_protocol::circle_activation::VerifiedCircleActivations;
12use coven_protocol::circle_control::StoreMembershipStateRef;
13use coven_protocol::membership::{MembershipChain, MembershipStatus};
14use coven_protocol::objects::{
15 ExactObjectRef, ProtocolObjectContext, ProtocolObjectDomain, StorageError,
16};
17use coven_protocol::objects::{StoreObjectError, VerifiedObject};
18use coven_protocol::store_commit::{
19 ActivatedStoreDeviceRegistration, ActivatedStoreDeviceRegistrationRef, CommitFrontier,
20 DeviceJoinAttemptDecisionRef, DeviceStreamAnchor, ObjectHash,
21 OpenedRetainedMergeHistorySummary, OwnerRecoveryNode, OwnerRecoveryNodeRef,
22 ReferencedStoreDeviceRegistration, ResolvedStoreDeviceState,
23 RetainedVerifiedMergeHistorySummary, StoreBatchCommit, StoreBatchCommitRef, StoreCommitCoord,
24 StoreDeviceHead, StoreDeviceId, StoreDeviceProposalState, StoreDeviceRegistration,
25 StoreDeviceRegistrationActivation, StoreDeviceRegistrationActivationRef,
26 StoreDeviceRegistrationOrigin, StoreDeviceRegistrationRef, StoreDeviceStateRef,
27 StoreDeviceStatus, StoreHistoryCut, StoreProtocolError, StoreRootRef, VerifiedStoreBatchCommit,
28 VerifiedStoreDeviceOperations,
29};
30use coven_protocol::store_commit::{
31 SnapshotMeta, StoreAck, StoreAckRef, StoreDeviceExclusionOutcomeRef,
32 StoreDeviceExclusionProposalRef, StoreDeviceHeadRef, StoreSnapshotRef,
33 VerifiedDeviceExclusionOutcome, VerifiedDeviceExclusionProposal,
34};
35use coven_protocol::{
36 causal_grants, membership as protocol_membership, provider, remote_object, store_commit,
37};
38use std::collections::{BTreeMap, BTreeSet};
39
40use super::commit::DeviceStateResolver;
41use crate::sync::store::device_join;
42
43mod device_join_verification;
44mod loaders;
45mod membership_control;
46mod predecessor;
47use predecessor::{
48 predecessor_verifies_provider_administrator, predecessor_verifies_provider_administrator_grant,
49};
50mod promotion;
51mod rollup;
52mod snapshots;
53mod stream;
54mod successor;
55pub use membership_control::VerifiedMergeMembershipPrefix;
56pub(crate) use membership_control::{
57 merge_membership_state_ref, verified_merge_membership_prefix,
58 verify_merge_membership_state_ref, VerifiedMergeMembershipControl,
59 VerifiedMergeMembershipHeadActivation, VerifiedMergePrefixHeadStatus,
60};
61pub(crate) use predecessor::{
62 predecessor_verifies_owner, PredecessorSearch, VerifiedMergePredecessorHistory,
63};
64pub(crate) use promotion::{
65 VerifiedMergeConflictResolutionActivation, VerifiedOwnerPromotionRequestActivation,
66};
67pub(crate) use snapshots::{
68 weigh_every_snapshot, SelectedAcknowledgedStoreSnapshot, SelectedInstallableStoreSnapshot,
69 SelectedReplayBaselineRetirement, SelectedStoreSnapshot, StoreSnapshotDescentStep,
70};
71pub use successor::MergeHistorySuccessorEvidence;
72pub use successor::PreparedMergeHistorySuccessor;
73pub(crate) use successor::{
74 compose_merge_snapshot_history_summary, compose_verified_merge_snapshot_history_summary,
75 validate_composed_snapshot_history_summary,
76};
77#[cfg(test)]
78pub(crate) use successor::{insert_latest_acknowledgement, merge_retained_merge_history};
79pub(super) mod join_validation;
80mod membership;
81use membership::VerifiedPrefixMembershipActivation;
82pub(crate) mod registration;
83use join_validation::*;
84pub(crate) use registration::RegistrationLoadError;
85use registration::*;
86
87pub(crate) struct VerifiedMergeHistoryCommit {
88 pub(crate) verified: VerifiedStoreBatchCommit,
89 pub(crate) predecessor_membership: MembershipChain,
90 pub(crate) predecessor_state: ResolvedStoreDeviceState,
91 pub(crate) state_after: ResolvedStoreDeviceState,
92 pub(crate) registrations: Vec<ActivatedStoreDeviceRegistration>,
93 pub(crate) operations: VerifiedStoreDeviceOperations,
94 pub(crate) acknowledgement: Option<(store_commit::StoreAckRef, store_commit::StoreAck)>,
95 pub(crate) membership_control: Option<VerifiedMergeMembershipControl>,
96 pub(crate) activation_head: StoreDeviceHead,
97 pub(crate) activation_head_object: ExactObjectRef,
98 pub(crate) history_evidence: store_commit::RetainedMergeCommitEvidence,
99}
100
101pub(crate) struct VerifiedMergeHistoryAuthority {
102 pub(crate) device_state: ResolvedStoreDeviceState,
103 pub(crate) membership: MembershipChain,
104}
105
106impl<'a> MergeHistoryVerifier<'a> {
107 fn cached_verified_membership(
108 &self,
109 state: &StoreMembershipStateRef,
110 authority: &VerifiedMergeMembershipPrefix,
111 ) -> Option<MembershipChain> {
112 self.verified_memberships
113 .iter()
114 .rev()
115 .find(|verified| {
116 verified.membership.head_refs() == state.heads
117 && verified.membership.resolution_refs() == state.resolutions
118 && authority.extends(&verified.authority)
119 })
120 .map(|verified| verified.membership.clone())
121 }
122
123 fn remember_verified_membership(
124 &mut self,
125 authority: VerifiedMergeMembershipPrefix,
126 membership: MembershipChain,
127 ) {
128 if self.verified_memberships.iter().any(|verified| {
129 verified.membership.head_refs() == membership.head_refs()
130 && verified.membership.resolution_refs() == membership.resolution_refs()
131 && authority.extends(&verified.authority)
132 }) {
133 return;
134 }
135 self.verified_memberships.push(VerifiedMembershipChain {
136 authority,
137 membership,
138 });
139 }
140
141 pub(crate) fn verified_root(&self) -> &crate::sync::store::protocol_root::VerifiedStoreRoot {
142 &self.root
143 }
144
145 pub(crate) fn membership_objects(&self) -> StoreMembershipObjectVerifier<'_, 'a> {
146 self.commit_verifier.membership_objects()
147 }
148
149 pub(crate) async fn retain_acknowledgement(
150 &self,
151 activating_commit: &StoreBatchCommitRef,
152 activating_commit_value: &StoreBatchCommit,
153 registration: &StoreDeviceRegistration,
154 reference: StoreAckRef,
155 value: StoreAck,
156 ) -> Result<store_commit::RetainedVerifiedActivatedAck, StorePullError> {
157 if activating_commit_value.acknowledgement() != Some(&reference)
158 || activating_commit_value.author_registration != reference.registration
159 || value.registration != reference.registration
160 {
161 return Err(StorePullError::InvalidState(
162 "Store acknowledgement differs from its activating commit".to_string(),
163 ));
164 }
165 activating_commit
166 .verify_commit(activating_commit_value)
167 .map_err(StorePullError::Protocol)?;
168 let object = value.to_bytes();
175 reference
176 .object
177 .verify(&object)
178 .map_err(|error| StorePullError::context("retained acknowledgement object", error))?;
179 StoreAck::parse_at(&object, self.root.reference(), &reference, registration)
180 .map_err(StorePullError::Protocol)?;
181 Ok(store_commit::RetainedVerifiedActivatedAck {
182 acknowledgement: (reference, value),
183 activating_commit: activating_commit.clone(),
184 })
185 }
186
187 pub(crate) async fn load_local_device_operations_with_resolver(
188 &mut self,
189 resolver: &DeviceStateResolver<'_>,
190 verified_commit: &VerifiedStoreBatchCommit,
191 membership: &MembershipChain,
192 state_ref: &StoreDeviceStateRef,
193 state: ResolvedStoreDeviceState,
194 ) -> Result<VerifiedStoreDeviceOperations, StorePullError> {
195 if verified_commit.store_root_hash() != self.root.reference().store_root_hash {
196 return Err(StorePullError::InvalidState(
197 "local device-operation commit belongs to another Store root".to_string(),
198 ));
199 }
200 let commit = verified_commit.value();
201 if commit.device_exclusion_proposals().is_empty()
202 && commit.device_exclusion_outcomes().is_empty()
203 {
204 return VerifiedStoreDeviceOperations::without_exclusions(commit)
205 .map_err(StorePullError::Protocol);
206 }
207 if state_ref != &commit.device_state {
208 return Err(StorePullError::InvalidState(
209 "local exclusion commit differs from its materialized predecessor device state"
210 .to_string(),
211 ));
212 }
213 verify_merge_membership_state_ref(&commit.membership_state, membership, &state)?;
214 Box::pin(self.commit_verifier.load_commit_device_operations(
215 Some(resolver),
216 commit,
217 &state,
218 Some(membership),
219 ))
220 .await
221 .map_err(StorePullError::from)
222 }
223
224 pub(crate) async fn derive_local_post_device_state(
225 &self,
226 commit: &StoreBatchCommit,
227 predecessor_state: ResolvedStoreDeviceState,
228 registrations: &[ActivatedStoreDeviceRegistration],
229 device_operations: VerifiedStoreDeviceOperations,
230 ) -> Result<ResolvedStoreDeviceState, StorePullError> {
231 let (authorized_predecessor, recovery_author) = predecessor_state
232 .preactivate_recovery_author(commit, registrations)
233 .map_err(StorePullError::Protocol)?;
234 let owner_recovery = self
235 .commit_verifier
236 .verify_owner_recovery_activation(commit)
237 .await?;
238 device_operations
239 .apply_to(authorized_predecessor, &commit.device_state)
240 .and_then(|state| {
241 state.apply_verified_lifecycle(
242 commit,
243 registrations,
244 recovery_author.as_ref(),
245 owner_recovery,
246 )
247 })
248 .map_err(StorePullError::Protocol)
249 }
250
251 pub(crate) async fn from_commit_verifier(
259 _authority: crate::sync::store::authorization::HistoryConstructionAuthority,
260 root: crate::sync::store::protocol_root::VerifiedStoreRoot,
261 commit_verifier: StoreCommitVerifier<'a>,
262 ) -> Result<Self, StorePullError> {
263 let founder = commit_verifier.load_founder_registration().await?;
264 let founder = &founder;
265 let verified_root = root.protocol();
266 let founder_ref =
267 StoreDeviceRegistrationRef::from_registration(&founder.value, founder.object.clone());
268 let founder_origin_matches = matches!(
269 founder.value.origin,
270 store_commit::StoreDeviceRegistrationOrigin::Founder { creation_id }
271 if creation_id == verified_root.descriptor.creation_id
272 );
273 if founder.value.store_root != *root.reference()
274 || founder.value.author_pubkey != verified_root.descriptor.founder_pubkey
275 || founder.value.provider != verified_root.descriptor.founder_provider_admin.provider
276 || founder.object.slot() != &verified_root.descriptor.founder_registration
277 || founder.semantic_hash != founder_ref.registration_hash
278 || !founder_origin_matches
279 {
280 return Err(StorePullError::InvalidState(
281 "verified founder registration belongs to another Store root".to_string(),
282 ));
283 }
284 let genesis = ResolvedStoreDeviceState::founder(
285 root.reference(),
286 founder_ref.clone(),
287 &verified_root.descriptor.founder_pubkey,
288 verified_root.descriptor.founder_grant.clone(),
289 &verified_root.descriptor.founder_recovery,
290 )
291 .map_err(StorePullError::Protocol)?;
292 Ok(Self {
293 root,
294 commit_verifier,
295 founder: founder_ref,
296 history: VerifiedMergeHistory {
297 genesis,
298 baseline: coven_database::InstalledReplayBaseline::default(),
299 retained: BTreeMap::new(),
300 commits: BTreeMap::new(),
301 },
302 verified_memberships: Vec::new(),
303 })
304 }
305
306 pub(crate) async fn covered_reference_status(
307 &mut self,
308 coverage: &CommitFrontier,
309 stream_id: &str,
310 reference: &StoreBatchCommitRef,
311 ) -> MaterializedCheck {
312 if commit_stream_id(&reference.coord) != stream_id {
313 return MaterializedCheck::Held(HeldStorePositionReason::WrongSlot(format!(
314 "commit reference stream {} differs from dependency stream {stream_id}",
315 commit_stream_id(&reference.coord)
316 )));
317 }
318 let coverage = coverage.clone().into_refs();
319 let Some(covered) = coverage.get(stream_id) else {
320 return MaterializedCheck::Missing;
321 };
322 if reference.coord.sequence() > covered.coord.sequence() {
323 return MaterializedCheck::Missing;
324 }
325 let mut cursor = covered.clone();
326 loop {
327 if cursor == *reference {
328 return MaterializedCheck::Yes;
329 }
330 if cursor.coord.sequence() <= reference.coord.sequence() {
331 return MaterializedCheck::Held(HeldStorePositionReason::HashMismatch {
332 referenced_device_id: stream_id.to_string(),
333 referenced_commit: reference.clone(),
334 materialized_hash: cursor.commit_hash,
335 });
336 }
337 let verified_commit = match self.load_ref(&cursor).await {
338 Ok(commit) => commit,
339 Err(error) => {
340 return MaterializedCheck::Held(
341 HeldStorePositionReason::ObjectUnreadablePull {
342 key: "exact Store commit".to_string(),
343 source: error.into(),
344 },
345 );
346 }
347 };
348 let Some(predecessor) = verified_commit.value().order.predecessor() else {
349 return MaterializedCheck::Missing;
350 };
351 cursor = predecessor.clone();
352 }
353 }
354
355 pub(crate) async fn validate_commit_acknowledgement(
356 &self,
357 commit: &StoreBatchCommit,
358 activating_author: &StoreDeviceRegistration,
359 ) -> Result<Option<(StoreAckRef, StoreAck)>, RegistrationLoadError> {
360 let Some(reference) = commit.acknowledgement() else {
361 return Ok(None);
362 };
363 let ack = self
364 .load_store_ack(reference, activating_author)
365 .await
366 .map_err(RegistrationLoadError::Object)?;
367 let predecessor_cut = commit
368 .order
369 .predecessor_cut()
370 .map_err(RegistrationLoadError::from)?;
371 if ack.registration != commit.author_registration
372 || ack.store_cut != predecessor_cut
373 || ack.device_state != commit.device_state
374 {
375 return Err(RegistrationLoadError::Invalid(
376 "Store acknowledgement differs from its activating commit predecessor".to_string(),
377 ));
378 }
379 if let Some(snapshot) = &ack.snapshot {
380 let snapshot_author = self
381 .load_registration(&snapshot.author_registration)
382 .await
383 .map_err(RegistrationLoadError::Object)?;
384 let (_, metadata) = self
385 .load_store_snapshot(
386 &snapshot.author_registration,
387 &snapshot_author.value,
388 &snapshot.snapshot,
389 )
390 .await
391 .map_err(RegistrationLoadError::from)?;
392 if !ack.store_cut.frontier().covers(&metadata.coverage) {
393 return Err(RegistrationLoadError::Invalid(
394 "Store acknowledgement does not cover its exact snapshot".to_string(),
395 ));
396 }
397 }
398 Ok(Some((reference.clone(), ack)))
399 }
400
401 pub(crate) fn remember(
402 &mut self,
403 commit: VerifiedStoreBatchCommit,
404 ) -> Result<(), StoreProtocolError> {
405 self.commit_verifier.remember(commit)
406 }
407
408 pub(crate) fn admit_snapshot_announcements(
447 &mut self,
448 frontier: &BTreeMap<
449 coven_protocol::causal_grants::AuthorStreamId,
450 store_commit::RetainedAcceptedStoreAnnouncement,
451 >,
452 ) -> Result<(), StorePullError> {
453 for announcement in frontier.values() {
454 let head = &announcement.value;
455 let head_ref = StoreDeviceHeadRef {
456 head_hash: head.head_hash(),
457 object: announcement.reference.object.clone(),
458 };
459 if announcement.reference != head_ref {
460 return Err(StorePullError::InvalidState(
461 "snapshot announcement differs from its own head reference".to_string(),
462 ));
463 }
464 self.commit_verifier
465 .remember_verified_head(
466 &head_ref,
467 VerifiedObject {
468 value: head.clone(),
469 bytes: head.to_bytes(),
470 semantic_hash: head_ref.head_hash,
471 object: head_ref.object.clone(),
472 },
473 )
474 .map_err(StorePullError::Protocol)?;
475 self.commit_verifier
476 .remember_covered_announcement(
477 &head.author_registration,
478 crate::sync::store::commit_verification::commit::CoveredStoreAnnouncement {
479 sequence: head.commit.coord.sequence(),
480 commit: head.commit.clone(),
481 head: head_ref,
482 next_slot: head.successor.next_slot.clone(),
483 },
484 )
485 .map_err(StorePullError::Protocol)?;
486 }
487 Ok(())
488 }
489
490 pub(crate) fn admit_installed_baseline(
502 &mut self,
503 baseline: coven_database::InstalledReplayBaseline,
504 ) -> Result<(), StorePullError> {
505 let installed = self.history.baseline.coverage();
506 if !installed.commits().is_empty() && installed != baseline.coverage() {
507 return Err(StorePullError::InvalidState(
508 "installed replay baseline coverage moved under its history verifier".to_string(),
509 ));
510 }
511 if let Some(summary) = baseline.history_summary() {
520 for chain in summary.summary.acknowledgements.values() {
521 for (reference, value) in chain.chain.values() {
522 self.commit_verifier
523 .remember_acknowledgement(reference, value)
524 .map_err(StorePullError::Protocol)?;
525 }
526 }
527 }
528 self.history.baseline = baseline;
529 Ok(())
530 }
531
532 pub(crate) fn replay_baseline_coverage(&self) -> &CommitFrontier {
538 self.history.baseline.coverage()
539 }
540
541 pub(crate) fn replay_baseline_stands_on(
546 &self,
547 snapshot: &store_commit::StoreSnapshotRef,
548 ) -> bool {
549 self.history.baseline.stands_on(snapshot)
550 }
551
552 pub(crate) fn newest_acknowledged_snapshot(
554 &self,
555 registration: &StoreDeviceRegistrationRef,
556 ) -> Option<store_commit::StoreSnapshotLocator> {
557 self.commit_verifier
558 .newest_acknowledged_snapshot(registration)
559 }
560
561 pub(crate) async fn load_acknowledged_snapshot(
575 &mut self,
576 locator: &store_commit::StoreSnapshotLocator,
577 author: &StoreDeviceRegistration,
578 ) -> Result<
579 Option<coven_database::PublishedStoreSnapshot>,
580 crate::sync::store::snapshots::SnapshotError,
581 > {
582 let (reference, meta) = match self
583 .commit_verifier
584 .load_store_snapshot(&locator.author_registration, author, &locator.snapshot)
585 .await
586 {
587 Ok(loaded) => loaded,
588 Err(StoreObjectError::Storage(StorageError::NotFound(_))) => return Ok(None),
589 Err(error) => return Err(crate::sync::store::snapshots::SnapshotError::from(error)),
590 };
591 Ok(Some(coven_database::PublishedStoreSnapshot {
592 successor_slot: meta.successor.next_slot.clone(),
593 reference,
594 meta,
595 }))
596 }
597
598 pub(crate) fn admit_published_snapshots(
602 &mut self,
603 snapshots: Vec<coven_database::PublishedStoreSnapshot>,
604 ) -> Result<(), StorePullError> {
605 let Some(author) = snapshots
606 .first()
607 .map(|snapshot| snapshot.meta.author_registration.clone())
608 else {
609 return Ok(());
610 };
611 if snapshots
612 .iter()
613 .any(|snapshot| snapshot.meta.author_registration != author)
614 {
615 return Err(StorePullError::InvalidState(
616 "one Store snapshot stream carries two authors".to_string(),
617 ));
618 }
619 self.commit_verifier
620 .remember_published_snapshot_stream(&author, snapshots)
621 .map_err(StorePullError::Protocol)
622 }
623
624 pub(crate) fn admit_retained_history(
625 &mut self,
626 retained: &[coven_database::OwnedVerifiedMergeMaterialization],
627 ) -> Result<(), StorePullError> {
628 let mut announced = BTreeMap::<StoreDeviceRegistrationRef, u64>::new();
629 for materialization in retained {
630 let commit = materialization.commit();
631 let commit_ref = materialization.commit_ref();
632 self.history
633 .retained
634 .insert(commit_ref.clone(), materialization.registrations().to_vec());
635 self.commit_verifier
636 .remember(materialization.verified_commit().clone())
637 .map_err(StorePullError::Protocol)?;
638 if let Some(activated) = &materialization.history_evidence().acknowledgement {
643 let (reference, value) = activated.acknowledgement();
644 self.commit_verifier
645 .remember_acknowledgement(reference, value)
646 .map_err(StorePullError::Protocol)?;
647 }
648 let head = materialization.activation_head();
649 let head_ref = StoreDeviceHeadRef {
650 head_hash: head.head_hash(),
651 object: materialization.activation_head_object().clone(),
652 };
653 self.commit_verifier
654 .remember_verified_head(
655 &head_ref,
656 VerifiedObject {
657 value: head.clone(),
658 bytes: head.to_bytes(),
659 semantic_hash: head_ref.head_hash,
660 object: head_ref.object.clone(),
661 },
662 )
663 .map_err(StorePullError::Protocol)?;
664 let sequence = commit_ref.coord.sequence();
670 let expected = match announced.get(&commit.author_registration) {
676 Some(previous) => previous.saturating_add(1),
677 None => self
678 .commit_verifier
679 .covered_announcement_floor(&commit.author_registration)
680 .saturating_add(1),
681 };
682 if sequence != expected {
683 continue;
684 }
685 self.commit_verifier
686 .remember_accepted_announcement(
687 &commit.author_registration,
688 sequence,
689 commit_ref.clone(),
690 head_ref,
691 head.successor.next_slot.clone(),
692 )
693 .map_err(StorePullError::Protocol)?;
694 announced.insert(commit.author_registration.clone(), sequence);
695 }
696 Ok(())
697 }
698
699 pub(crate) async fn retain_local_same_principal_join_activation(
700 &mut self,
701 materialization: coven_database::OwnedVerifiedMergeMaterialization,
702 ) -> Result<(), StorePullError> {
703 let reference = materialization.commit_ref().clone();
704 self.verify_refs(commit_predecessor_references(materialization.commit()))
705 .await?;
706 if let Some(existing) = self.history.commits.get(&reference) {
707 if existing.verified.value() == materialization.commit()
708 && existing.verified.author() == materialization.verified_commit().author()
709 {
710 return Ok(());
711 }
712 return Err(StorePullError::InvalidState(
713 "local join activation conflicts with its already-verified Store commit"
714 .to_string(),
715 ));
716 }
717 let commit = materialization.commit();
718 if commit.control().is_some()
719 || commit.acknowledgement().is_some()
720 || commit.device_join_attempt_decisions().len() != 1
721 || commit.device_registrations().len() != 1
722 || materialization.registrations().len() != 1
723 {
724 return Err(StorePullError::InvalidState(
725 "local same-principal activation is not one exact join operation".to_string(),
726 ));
727 }
728 let predecessor_cut = commit
729 .order
730 .predecessor_cut()
731 .map_err(StorePullError::Protocol)?;
732 let authority = self.verify_merge_history_authority_from_verified_history(
733 &predecessor_cut.0,
734 &commit.membership_state,
735 )?;
736 let predecessor_state = authority.device_state;
737 let registrations = materialization.registrations().to_vec();
738 let operations = materialization.device_operations().clone();
739 let state_after = self
740 .derive_local_post_device_state(
741 commit,
742 predecessor_state.clone(),
743 ®istrations,
744 operations.clone(),
745 )
746 .await?;
747 let verified = materialization.verified_commit().clone();
748 let activation_head = materialization.activation_head().clone();
749 let activation_head_object = materialization.activation_head_object().clone();
750 let history_evidence = materialization.history_evidence().clone();
751 self.history.commits.insert(
752 reference,
753 VerifiedMergeHistoryCommit {
754 verified,
755 predecessor_membership: authority.membership,
756 predecessor_state,
757 state_after,
758 registrations,
759 operations,
760 acknowledgement: None,
761 membership_control: None,
762 activation_head,
763 activation_head_object,
764 history_evidence,
765 },
766 );
767 Ok(())
768 }
769
770 pub(crate) fn verified_predecessor_state(
771 &self,
772 commit: &StoreBatchCommit,
773 ) -> Result<ResolvedStoreDeviceState, StorePullError> {
774 let states = self.history.resolved_states();
775 verified_merge_predecessor_state(&self.history.genesis, &states, commit)
776 }
777
778 pub(crate) fn verified_membership_prefix(
779 &self,
780 predecessors: impl IntoIterator<Item = StoreBatchCommitRef>,
781 ) -> Result<VerifiedMergeMembershipPrefix, StorePullError> {
782 verified_merge_membership_prefix(&self.history, predecessors)
783 }
784
785 pub(crate) fn verified_pull_candidate(
786 &self,
787 reference: &StoreBatchCommitRef,
788 ) -> Option<pull::VerifiedPullCandidate> {
789 self.history
790 .commits
791 .get(reference)
792 .map(|commit| pull::VerifiedPullCandidate {
793 verified: commit.verified.clone(),
794 predecessor_membership: commit.predecessor_membership.clone(),
795 registrations: commit.registrations.clone(),
796 operations: commit.operations.clone(),
797 membership_control: commit
798 .membership_control
799 .as_ref()
800 .map(|control| control.activations.clone()),
801 })
802 }
803
804 pub(crate) fn accepted_commit_membership_state(
805 &self,
806 reference: &StoreBatchCommitRef,
807 ) -> Option<&StoreMembershipStateRef> {
808 self.history
809 .commits
810 .get(reference)
811 .map(|commit| &commit.verified.value().membership_state)
812 }
813
814 pub(crate) fn verified_predecessor_membership(
815 &self,
816 reference: &StoreBatchCommitRef,
817 ) -> Option<&MembershipChain> {
818 self.history
819 .commits
820 .get(reference)
821 .map(|commit| &commit.predecessor_membership)
822 }
823
824 pub(super) fn verifies_membership_head_activation(
825 &self,
826 reference: &protocol_membership::MembershipHeadRef,
827 head: &protocol_membership::AuthorHead,
828 activation: &StoreBatchCommitRef,
829 ) -> bool {
830 self.history
831 .commits
832 .get(activation)
833 .and_then(|commit| commit.membership_control.as_ref())
834 .is_some_and(|control| control.verifies_head_activation(reference, head, activation))
835 }
836
837 pub(super) async fn verify_membership_head_activation(
838 &mut self,
839 reference: &protocol_membership::MembershipHeadRef,
840 head: &protocol_membership::AuthorHead,
841 activation: &StoreBatchCommitRef,
842 ) -> Result<bool, StorePullError> {
843 let verified = self.load_ref(activation).await?;
844 let commit = verified.value();
845 let author = verified.author();
846 let transition = commit
847 .control()
848 .map(|control| &control.transition)
849 .ok_or_else(|| {
850 StorePullError::InvalidState(
851 "membership head activation commit has no Merge membership transition"
852 .to_string(),
853 )
854 })?;
855 if !transition.matches_head(head, reference)
856 || transition.body.author_registration != commit.author_registration
857 {
858 return Err(StorePullError::InvalidState(
859 "membership head differs from its exact activating Store transition".to_string(),
860 ));
861 }
862 let activation_observation = self
863 .exact_next_announcement_slot(&commit.author_registration, author, Some(&verified))
864 .await;
865 match activation_observation {
866 Ok((_, Some(_))) => {}
867 Ok((_, None)) => return Ok(false),
868 Err(StoreError::MergeAnnouncementOccupied { .. })
869 | Err(StoreError::Object(coven_protocol::objects::StoreObjectError::Storage(
870 StorageError::NotFound(_),
871 ))) => return Ok(false),
872 Err(error) => return Err(StorePullError::Store(Box::new(error))),
873 }
874 self.verify_refs([activation.clone()]).await?;
875 if !self.verifies_membership_head_activation(reference, head, activation) {
876 return Err(StorePullError::InvalidState(
877 "membership head activation differs from its verified Merge membership control"
878 .to_string(),
879 ));
880 }
881 Ok(true)
882 }
883
884 pub(crate) async fn verify_merge_history_authority(
885 &mut self,
886 frontier: &BTreeMap<protocol_membership::AuthorStreamId, StoreBatchCommitRef>,
887 membership_state: &StoreMembershipStateRef,
888 ) -> Result<VerifiedMergeHistoryAuthority, StorePullError> {
889 self.verify_refs(frontier.values().cloned()).await?;
890 let (device_state, verified_membership_activations) =
891 self.verified_merge_history_authority_parts(frontier)?;
892 let membership = match self
893 .cached_verified_membership(membership_state, &verified_membership_activations)
894 {
895 Some(membership) => membership,
896 None => self
897 .load_membership_at_verified_prefix(
898 &membership_state.heads,
899 &membership_state.resolutions,
900 &verified_membership_activations,
901 None,
902 )
903 .await
904 .map_err(StorePullError::MembershipChain)?,
905 };
906 verified_membership_activations.validate_complete_membership(&membership)?;
907 verify_merge_membership_state_ref(membership_state, &membership, &device_state)?;
908 self.remember_verified_membership(verified_membership_activations, membership.clone());
909 Ok(VerifiedMergeHistoryAuthority {
910 device_state,
911 membership,
912 })
913 }
914
915 pub(crate) fn verify_merge_history_authority_from_verified_history(
916 &self,
917 frontier: &BTreeMap<protocol_membership::AuthorStreamId, StoreBatchCommitRef>,
918 membership_state: &StoreMembershipStateRef,
919 ) -> Result<VerifiedMergeHistoryAuthority, StorePullError> {
920 let (device_state, verified_membership_activations) =
921 self.verified_merge_history_authority_parts(frontier)?;
922 let membership = self
923 .cached_verified_membership(membership_state, &verified_membership_activations)
924 .ok_or_else(|| {
925 StorePullError::InvalidState(
926 "Merge membership authority is absent from the already-verified history"
927 .to_string(),
928 )
929 })?;
930 verified_membership_activations.validate_complete_membership(&membership)?;
931 verify_merge_membership_state_ref(membership_state, &membership, &device_state)?;
932 Ok(VerifiedMergeHistoryAuthority {
933 device_state,
934 membership,
935 })
936 }
937
938 fn verified_merge_history_authority_parts(
939 &self,
940 frontier: &BTreeMap<protocol_membership::AuthorStreamId, StoreBatchCommitRef>,
941 ) -> Result<(ResolvedStoreDeviceState, VerifiedMergeMembershipPrefix), StorePullError> {
942 let device_state = if frontier.is_empty() {
943 self.history.genesis.clone()
944 } else {
945 ResolvedStoreDeviceState::merge(
946 frontier
947 .values()
948 .map(|reference| {
949 self.history.state_after(reference).cloned().ok_or_else(|| {
950 StorePullError::InvalidState(
951 "Merge history frontier is absent from its verified graph"
952 .to_string(),
953 )
954 })
955 })
956 .collect::<Result<Vec<_>, _>>()?,
957 )
958 .map_err(StorePullError::Protocol)?
959 };
960 let membership =
961 verified_merge_membership_prefix(&self.history, frontier.values().cloned())?;
962 Ok((device_state, membership))
963 }
964}
965
966fn verified_merge_commit_closure(
973 history: &VerifiedMergeHistory,
974 tips: impl IntoIterator<Item = StoreBatchCommitRef>,
975) -> Result<BTreeSet<StoreBatchCommitRef>, StorePullError> {
976 let mut pending = tips.into_iter().collect::<Vec<_>>();
977 let mut closure = BTreeSet::new();
978 while let Some(reference) = pending.pop() {
979 if !closure.insert(reference.clone()) {
980 continue;
981 }
982 if history.superseded(&reference) {
983 continue;
984 }
985 let verified = history.commits.get(&reference).ok_or_else(|| {
986 StorePullError::InvalidState(
987 "verified Merge predecessor closure is absent from its history".to_string(),
988 )
989 })?;
990 pending.extend(commit_predecessor_references(verified.verified.value()));
991 }
992 Ok(closure)
993}
994
995fn merge_device_state_from_verified_history(
996 reference: &StoreDeviceStateRef,
997 history: &VerifiedMergeHistory,
998 allowed_tips: impl IntoIterator<Item = StoreBatchCommitRef>,
999) -> Result<ResolvedStoreDeviceState, StorePullError> {
1000 let genesis = &history.genesis;
1001 let frontier = reference.frontier();
1002 let allowed = verified_merge_commit_closure(history, allowed_tips)?;
1003 if frontier
1004 .commits()
1005 .values()
1006 .any(|reference| !allowed.contains(reference))
1007 {
1008 return Err(StorePullError::InvalidState(
1009 "Merge device state names a commit outside its causal predecessor history".to_string(),
1010 ));
1011 }
1012 let state = if frontier.commits().is_empty() {
1013 genesis.clone()
1014 } else {
1015 ResolvedStoreDeviceState::merge(
1016 frontier
1017 .commits()
1018 .values()
1019 .map(|reference| {
1020 history.state_after(reference).cloned().ok_or_else(|| {
1021 StorePullError::InvalidState(
1022 "Merge device-state frontier is absent from its verified history"
1023 .to_string(),
1024 )
1025 })
1026 })
1027 .collect::<Result<Vec<_>, _>>()?,
1028 )
1029 .map_err(StorePullError::Protocol)?
1030 };
1031 let expected = StoreDeviceStateRef::from_resolved(frontier.clone(), &state)
1032 .map_err(StorePullError::Protocol)?;
1033 if &expected != reference {
1034 return Err(StorePullError::InvalidState(
1035 "Merge device-state reference differs from its verified history".to_string(),
1036 ));
1037 }
1038 Ok(state)
1039}
1040
1041pub(crate) struct VerifiedMergeHistory {
1042 pub(crate) genesis: ResolvedStoreDeviceState,
1043 pub(crate) baseline: coven_database::InstalledReplayBaseline,
1050 pub(crate) retained: BTreeMap<StoreBatchCommitRef, Vec<ActivatedStoreDeviceRegistration>>,
1058 pub(crate) commits: BTreeMap<StoreBatchCommitRef, VerifiedMergeHistoryCommit>,
1059}
1060
1061impl VerifiedMergeHistory {
1062 pub(crate) fn superseded(&self, reference: &StoreBatchCommitRef) -> bool {
1066 self.baseline.covers(reference) && !self.retained.contains_key(reference)
1067 }
1068
1069 pub(crate) fn retained_registrations(
1078 &self,
1079 reference: &StoreBatchCommitRef,
1080 ) -> Option<&[ActivatedStoreDeviceRegistration]> {
1081 self.retained
1082 .get(reference)
1083 .map(|registrations| registrations.as_slice())
1084 }
1085
1086 pub(crate) fn state_after(
1090 &self,
1091 reference: &StoreBatchCommitRef,
1092 ) -> Option<&ResolvedStoreDeviceState> {
1093 self.commits
1094 .get(reference)
1095 .map(|commit| &commit.state_after)
1096 .or_else(|| self.baseline.covered_state(reference))
1097 }
1098
1099 pub(crate) fn resolved_states(
1102 &self,
1103 ) -> BTreeMap<StoreBatchCommitRef, ResolvedStoreDeviceState> {
1104 self.baseline
1105 .covered_states()
1106 .map(|(reference, state)| (reference.clone(), state.clone()))
1107 .chain(
1108 self.commits
1109 .iter()
1110 .map(|(reference, verified)| (reference.clone(), verified.state_after.clone())),
1111 )
1112 .collect()
1113 }
1114}
1115
1116struct VerifiedMembershipChain {
1117 authority: VerifiedMergeMembershipPrefix,
1118 membership: MembershipChain,
1119}
1120
1121pub struct MergeHistoryVerifier<'a> {
1122 root: crate::sync::store::protocol_root::VerifiedStoreRoot,
1123 commit_verifier: StoreCommitVerifier<'a>,
1124 founder: StoreDeviceRegistrationRef,
1128 history: VerifiedMergeHistory,
1129 verified_memberships: Vec<VerifiedMembershipChain>,
1130}
1131
1132type PredecessorCommitPredicate<'a> = Box<dyn FnMut(&VerifiedStoreBatchCommit) -> bool + Send + 'a>;
1133
1134pub struct MergeOutboundAuthorization {
1135 pub(crate) membership: MembershipChain,
1136 pub(crate) membership_state: StoreMembershipStateRef,
1137 pub(crate) device_state_ref: StoreDeviceStateRef,
1138 pub(crate) device_state: ResolvedStoreDeviceState,
1139}