coven_protocol/store_commit/
pending_device_join.rs1use super::*;
2
3impl StoreBatchCommit {
4 pub fn has_pending_device_join_bootstrap<'a>(
8 &self,
9 state: &ResolvedStoreDeviceState,
10 frontier: &CommitFrontier,
11 accepted: impl IntoIterator<Item = &'a StoreBatchCommit>,
12 ) -> Result<bool, StoreProtocolError> {
13 let accepted = accepted.into_iter().collect::<Vec<_>>();
14 for decision in self.device_join_attempt_decisions() {
15 let DeviceJoinAttemptDecisionRef::Attempt(attempt_id) = decision else {
16 continue;
17 };
18 if let Some(activation) = self.device_registrations().iter().find(|activation| {
19 matches!(&activation.authority, StoreDeviceRegistrationActivationRef::Join { attempt_id: registered } if registered == attempt_id)
20 }) {
21 if self.registration_has_unconsumed_bootstrap(&activation.registration, state, frontier)? {
22 return Ok(true);
23 }
24 continue;
25 }
26 let terminated = accepted.iter().any(|commit| {
27 commit.device_registrations().iter().any(|activation| {
28 matches!(&activation.authority, StoreDeviceRegistrationActivationRef::Join { attempt_id: registered } if registered == attempt_id)
29 }) || commit.device_join_attempt_decisions().iter().any(|decision| {
30 matches!(decision, DeviceJoinAttemptDecisionRef::Abandoned(abandonment) if &abandonment.attempt_id == attempt_id)
31 })
32 });
33 if !terminated {
34 return Ok(true);
35 }
36 }
37 Ok(false)
38 }
39
40 pub fn pending_bootstrap_registration(
44 &self,
45 state: &ResolvedStoreDeviceState,
46 frontier: &CommitFrontier,
47 ) -> Result<Option<&StoreDeviceRegistrationRef>, StoreProtocolError> {
48 let [registration] = self.device_registrations() else {
49 return Ok(None);
50 };
51 let StoreDeviceRegistrationActivationRef::Join { attempt_id } = ®istration.authority
52 else {
53 return Ok(None);
54 };
55 if !self.device_join_attempt_decisions().iter().any(|decision| {
56 matches!(decision, DeviceJoinAttemptDecisionRef::Attempt(opened) if opened == attempt_id)
57 }) {
58 return Ok(None);
59 }
60 let target = ®istration.registration;
61 Ok(self
62 .registration_has_unconsumed_bootstrap(target, state, frontier)?
63 .then_some(target))
64 }
65
66 fn registration_has_unconsumed_bootstrap(
67 &self,
68 target: &StoreDeviceRegistrationRef,
69 state: &ResolvedStoreDeviceState,
70 frontier: &CommitFrontier,
71 ) -> Result<bool, StoreProtocolError> {
72 let device = state
73 .devices
74 .get(&target.device_id)
75 .filter(|device| &device.registration == target)
76 .ok_or(StoreProtocolError::DeviceStateMismatch)?;
77 let stream = StreamActivation::device_authorized_stream_id(
78 self.store_root_hash,
79 target,
80 StreamAnchorDomain::StoreAnnouncements,
81 );
82 Ok(matches!(device.status, StoreDeviceStatus::Active)
83 && !frontier.commits().contains_key(&stream))
84 }
85}
86
87impl RetainedVerifiedMergeHistorySummary {
88 pub fn pending_device_join_accepted_commit(
89 &self,
90 reference: &StoreBatchCommitRef,
91 ) -> Result<Option<AcceptedStoreCommitPublication>, StoreProtocolError> {
92 let mut accepted = None;
93 for closure in self.pending_device_joins.values() {
94 if !closure
95 .commits
96 .iter()
97 .any(|commit| &commit.reference == reference)
98 {
99 continue;
100 }
101 let exact = closure.accepted_commit(reference)?;
102 if accepted.as_ref().is_some_and(|previous| previous != &exact) {
103 return Err(StoreProtocolError::Malformed(
104 "pending Join closures disagree on an accepted commit".into(),
105 ));
106 }
107 accepted = Some(exact);
108 }
109 Ok(accepted)
110 }
111
112 pub fn pending_device_join_artifacts(
115 &self,
116 ) -> Result<BTreeSet<crate::objects::ExactObjectRef>, StoreProtocolError> {
117 let mut objects = BTreeSet::new();
118 for closure in self.pending_device_joins.values() {
119 let snapshot = closure
120 .publication
121 .current
122 .latest_snapshot()
123 .ok_or_else(|| {
124 StoreProtocolError::Malformed("pending Join has no snapshot boundary".into())
125 })?;
126 let retained = self
127 .reclaim
128 .snapshots
129 .get(&snapshot.snapshot.snapshot_hash)
130 .filter(|retained| &retained.accepted == snapshot)
131 .ok_or_else(|| {
132 StoreProtocolError::Malformed(
133 "pending Join snapshot has no exact retained artifact ownership".into(),
134 )
135 })?;
136 objects.extend(retained.objects().into_iter().cloned());
137 objects.extend(
138 closure
139 .publication
140 .verify()?
141 .entries()
142 .iter()
143 .map(|entry| entry.reference().object.clone()),
144 );
145 }
146 Ok(objects)
147 }
148
149 pub fn pending_device_join_snapshot_slots(&self) -> BTreeSet<crate::objects::ObjectSlot> {
150 self.pending_device_joins
151 .values()
152 .filter_map(|closure| {
153 closure
154 .publication
155 .current
156 .latest_snapshot()
157 .map(|snapshot| snapshot.snapshot.object.slot().clone())
158 })
159 .collect()
160 }
161}