coven_database/store/store_session/circle_controls/
discard.rs1use super::*;
2use crate::store::store_session::{active_store_publication, candidate_records, StoreTransaction};
3use coven_protocol::membership::MembershipChain;
4use coven_protocol::store_commit::StorePublicationRef;
5
6impl StoreSession<'_> {
7 fn begin_circle_operation_discard(
8 &mut self,
9 expected: CircleOperationJournal,
10 membership: MembershipChain,
11 publication: StorePublicationRef,
12 ) -> Result<(), DbError> {
13 let tx = self.conn.unchecked_transaction()?;
14 let current = load_circle_operation_on(&tx, expected.operation_id.as_str())?
15 .ok_or_else(|| DbError::Message("discarded Circle operation is absent".into()))?;
16 if current != expected {
17 return Err(DbError::Message(
18 "Circle discard differs from its exact candidate".into(),
19 ));
20 }
21 let nonactivation = StoreTransaction::new(&tx, self.store_dir)
22 .candidate_grant_nonactivation(
23 self.verified_store_authority,
24 &membership,
25 expected.operation().commit_ref(),
26 expected.operation().commit(),
27 &publication,
28 )?;
29 circle_discard_reservation_on(&tx, &expected)?;
30 circle_bootstrap_blob_releases_on(&tx, &expected)?;
31 let mut discarding = expected;
32 discarding.begin_discard()?;
33 candidate_records::begin_candidate_nonactivation_targets_on(
34 &tx,
35 discarding.operation().commit_ref(),
36 &discarding
37 .candidate_owned_objects()?
38 .into_iter()
39 .collect::<Vec<_>>(),
40 &nonactivation,
41 )?;
42 update_circle_operation_phase_on(&tx, &discarding)?;
43 tx.commit()?;
44 Ok(())
45 }
46
47 fn circle_operation_discard_targets(
48 &self,
49 expected: &CircleOperationJournal,
50 ) -> Result<Vec<crate::CandidateCleanupObject>, DbError> {
51 if !expected.is_discarding()
52 || load_circle_operation_on(self.conn, expected.operation_id.as_str())?.as_ref()
53 != Some(expected)
54 {
55 return Err(DbError::Message(
56 "Circle discard journal changed before cleanup".into(),
57 ));
58 }
59 let active = circle_discard_reservation_on(self.conn, expected)?;
60 let mut targets = candidate_records::candidate_cleanup_targets_on(
61 self.conn,
62 expected.operation().commit_ref(),
63 &expected
64 .candidate_owned_objects()?
65 .into_iter()
66 .collect::<Vec<_>>(),
67 )?;
68 targets.push(crate::CandidateCleanupObject {
69 object: active.attempt()?.reference()?.object,
70 });
71 if let Some(previous) = active.superseded_entry() {
72 targets.push(crate::CandidateCleanupObject {
73 object: previous.object.clone(),
74 });
75 }
76 targets.sort_by(|a, b| a.object.cmp(&b.object));
77 targets.dedup_by(|a, b| a.object == b.object);
78 Ok(targets)
79 }
80
81 fn complete_circle_operation_discard(
82 &self,
83 expected: CircleOperationJournal,
84 ) -> Result<(), DbError> {
85 let tx = self.conn.unchecked_transaction()?;
86 self.circle_operation_discard_targets(&expected)?;
87 let objects = expected
88 .candidate_owned_objects()?
89 .into_iter()
90 .collect::<Vec<_>>();
91 let targets = candidate_records::candidate_cleanup_targets_on(
92 &tx,
93 expected.operation().commit_ref(),
94 &objects,
95 )?;
96 let mut removed = Vec::new();
97 for target in targets {
98 let id = remote_object_id(&target.object);
99 let mut remote = load_remote_object_on(&tx, id)?;
100 remote.mark_absent_verified()?;
101 update_remote_object_on(&tx, id, &remote)?;
102 removed.push(id);
103 }
104 candidate_records::require_candidate_cleanup_complete_on(
105 &tx,
106 expected.operation().commit_ref(),
107 &objects,
108 "Circle candidate cleanup is incomplete",
109 )?;
110 candidate_records::delete_remote_objects_on(&tx, removed, "discarded Circle candidate")?;
111 for (id, record) in circle_bootstrap_blob_releases_on(&tx, &expected)? {
112 update_remote_object_on(&tx, id, &record)?;
113 }
114 release_operation_payloads_on(&tx, &expected.operation_id)?;
115 if tx.execute(
116 "DELETE FROM circle_operations WHERE operation_id = ?1",
117 [expected.operation_id.as_str()],
118 )? != 1
119 {
120 return Err(DbError::Message(
121 "Circle discard journal disappeared".into(),
122 ));
123 }
124 let active = circle_discard_reservation_on(&tx, &expected)?;
125 let mut completed = active.clone();
126 if completed.superseded_entry().is_some() {
127 completed.complete_superseded_entry_cleanup()?;
128 active_store_publication::update_active_store_publication_on(&tx, &active, &completed)?;
129 }
130 active_store_publication::clear_active_store_publication_on(&tx, &completed)?;
131 tx.commit()?;
132 Ok(())
133 }
134}
135
136impl StoreDatabase {
137 pub async fn begin_circle_operation_discard(
138 &self,
139 expected: CircleOperationJournal,
140 membership: MembershipChain,
141 publication: StorePublicationRef,
142 ) -> Result<(), DbError> {
143 self.call_store(move |session| {
144 session.begin_circle_operation_discard(expected, membership, publication)
145 })
146 .await
147 }
148
149 pub async fn circle_operation_discard_targets(
150 &self,
151 expected: CircleOperationJournal,
152 ) -> Result<Vec<crate::CandidateCleanupObject>, DbError> {
153 self.call_store(move |session| session.circle_operation_discard_targets(&expected))
154 .await
155 }
156
157 pub async fn complete_circle_operation_discard(
158 &self,
159 expected: CircleOperationJournal,
160 ) -> Result<(), DbError> {
161 self.call_store(move |session| session.complete_circle_operation_discard(expected))
162 .await
163 }
164}
165
166fn circle_discard_reservation_on(
167 conn: &Connection,
168 journal: &CircleOperationJournal,
169) -> Result<ActiveStorePublication, DbError> {
170 let active =
171 active_store_publication::load_active_store_publication_on(conn)?.ok_or_else(|| {
172 DbError::Message("Circle discard lost its publication reservation".into())
173 })?;
174 let commit = journal.operation().commit();
175 if active.owner() != &ActiveStorePublicationOwner::CircleOperation(journal.operation_id.clone())
176 || active.commit_reservation()
177 != Some((
178 &commit.write_id,
179 &commit.author_registration,
180 &journal.operation().commit_ref().coord,
181 ))
182 || active.attempt()?.entry.payload
183 != coven_protocol::store_commit::StorePublicationPayload::Commit(
184 journal.operation().commit_ref().clone(),
185 )
186 || !active.retired_candidates().is_empty()
187 {
188 return Err(DbError::Message(
189 "Circle discard differs from its reserved candidate".into(),
190 ));
191 }
192 Ok(active)
193}
194
195fn circle_bootstrap_blob_releases_on(
196 conn: &Connection,
197 journal: &CircleOperationJournal,
198) -> Result<
199 Vec<(
200 coven_protocol::store_commit::ObjectHash,
201 coven_protocol::remote_object::RemoteObjectRecord,
202 )>,
203 DbError,
204> {
205 use coven_protocol::remote_object::{
206 OwnedObjectState, PendingCandidateRelease, RemoteObjectRecord,
207 };
208
209 journal.operation().bootstrap_blobs()?.into_iter().map(|(id, blob)| {
210 let record = load_remote_object_on(conn, id)?;
211 if record.object() != blob.object() {
212 return Err(DbError::Message("Circle bootstrap blob differs from its retained exact object".into()));
213 }
214 let PendingCandidateRelease::Retained(record) = record.release_pending_candidate(journal.operation().commit_ref())? else {
215 return Err(DbError::Message("Circle bootstrap blob lost its accepted ownership before discard".into()));
216 };
217 if !matches!(&record, RemoteObjectRecord::SharedLiveSet(shared)
218 if matches!(&shared.state, OwnedObjectState::UploadedVerified { ownership } if !ownership.activated.is_empty())) {
219 return Err(DbError::Message("Circle bootstrap blob has no accepted owner after discard".into()));
220 }
221 Ok((id, record))
222 }).collect()
223}