Skip to main content

coven_database/store/store_session/circle_controls/
discard.rs

1use 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}