coven_database/store/store_session/
candidate_records.rs1use crate::*;
2use coven_protocol::objects::ExactObjectRef;
3use coven_protocol::remote_object::remote_object_id;
4use coven_protocol::store_commit::{ObjectHash, StoreBatchCommitRef};
5use rusqlite::Connection;
6use std::collections::BTreeSet;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct CandidateCleanupObject {
10 pub object: ExactObjectRef,
11}
12
13pub(crate) fn begin_candidate_nonactivation_targets_on(
21 tx: &rusqlite::Transaction<'_>,
22 candidate: &StoreBatchCommitRef,
23 objects: &[ExactObjectRef],
24 nonactivation: &coven_protocol::remote_object::CandidateNonactivation,
25) -> Result<Vec<CandidateCleanupObject>, DbError> {
26 let mut unique = BTreeSet::new();
27 let mut cleanup = Vec::new();
28 for object in objects {
29 let object_id = remote_object_id(object);
30 if !unique.insert(object_id) {
31 return Err(DbError::Message(
32 "losing candidate repeats an exact owned object".to_string(),
33 ));
34 }
35 if let Some(target) =
36 begin_remote_candidate_nonactivation_on(tx, object_id, nonactivation.clone())?
37 {
38 cleanup.push(CandidateCleanupObject { object: target });
39 }
40 }
41 let commit_complete = load_remote_object_on(tx, remote_object_id(&candidate.object))?
42 .candidate_cleanup_complete(candidate)?;
43 if !objects.contains(&candidate.object)
44 || !(commit_complete
45 || cleanup
46 .iter()
47 .any(|target| target.object == candidate.object))
48 {
49 return Err(DbError::Message(
50 "losing candidate has no exact commit cleanup target".to_string(),
51 ));
52 }
53 cleanup.sort_by(|left, right| left.object.cmp(&right.object));
54 Ok(cleanup)
55}
56
57pub(crate) fn candidate_cleanup_targets_on(
62 conn: &Connection,
63 candidate: &StoreBatchCommitRef,
64 objects: &[ExactObjectRef],
65) -> Result<Vec<CandidateCleanupObject>, DbError> {
66 let mut unique = BTreeSet::new();
67 let mut cleanup = Vec::new();
68 for object in objects {
69 let object_id = remote_object_id(object);
70 if !unique.insert(object_id) {
71 return Err(DbError::Message(
72 "candidate cleanup repeats an exact object".to_string(),
73 ));
74 }
75 let remote = load_remote_object_on(conn, object_id)?;
76 if let Some(target) = remote.cleanup_target() {
77 cleanup.push(CandidateCleanupObject {
78 object: target.clone(),
79 });
80 } else if !remote
81 .candidate_cleanup_complete(candidate)
82 .map_err(DbError::from)?
83 {
84 return Err(DbError::Message(format!(
85 "candidate object {object_id} has no cleanup decision"
86 )));
87 }
88 }
89 cleanup.sort_by(|left, right| left.object.cmp(&right.object));
90 Ok(cleanup)
91}
92
93pub(crate) fn require_candidate_cleanup_complete_on(
94 conn: &Connection,
95 candidate: &StoreBatchCommitRef,
96 objects: &[ExactObjectRef],
97 context: &str,
98) -> Result<(), DbError> {
99 if candidate_cleanup_targets_on(conn, candidate, objects)?.is_empty() {
100 Ok(())
101 } else {
102 Err(DbError::Message(context.to_string()))
103 }
104}
105
106pub(crate) fn delete_remote_objects_on(
107 tx: &rusqlite::Transaction<'_>,
108 object_ids: impl IntoIterator<Item = ObjectHash>,
109 context: &str,
110) -> Result<(), DbError> {
111 let mut unique = BTreeSet::new();
112 for object_id in object_ids {
113 if !unique.insert(object_id) {
114 return Err(DbError::Message(format!(
115 "{context} repeats remote object {object_id}"
116 )));
117 }
118 if !crate::remote_object_records::delete_remote_object_on(tx, object_id)? {
119 return Err(DbError::Message(format!(
120 "{context} object {object_id} disappeared during cleanup"
121 )));
122 }
123 }
124 Ok(())
125}
126
127pub(super) fn load_device_exclusion_activation_on(
128 records: crate::store::store_session::StoreRecords<'_>,
129 retained: &mut dyn super::verified_store_authority::VerifiedStoreLookup,
130 root: &coven_protocol::store_commit::StoreRootRef,
131 exclusion: &coven_protocol::store_commit::StoreDeviceExclusionRef,
132) -> Result<StoreBatchCommitRef, DbError> {
133 let exclusion_json = serde_json::to_string(exclusion)
134 .map_err(|error| DbError::context("serialize device exclusion reference", error))?;
135 let activation_commit = records
136 .author_exclusion_activation_row(&exclusion_json)?
137 .ok_or_else(|| {
138 DbError::Message("applied device exclusion has no exact activation".into())
139 })?;
140 let activation_commit: StoreBatchCommitRef = serde_json::from_str(&activation_commit)
141 .map_err(|error| DbError::context("parse device exclusion activation commit", error))?;
142 let materialization =
143 retained.retained_materialization_by_ref_on(records, &activation_commit)?;
144 if materialization.root() != root
145 || !materialization
146 .device_operations()
147 .exclusions()
148 .any(|candidate| candidate == exclusion)
149 {
150 return Err(DbError::Message(
151 "device exclusion differs from its exact retained activation".into(),
152 ));
153 }
154 Ok(activation_commit)
155}
156
157impl super::StoreTransaction<'_, '_> {
158 pub(super) fn candidate_grant_nonactivation(
159 self,
160 authority: &mut super::verified_store_authority::VerifiedStoreAuthority,
161 membership: &coven_protocol::membership::MembershipChain,
162 candidate: &StoreBatchCommitRef,
163 commit: &coven_protocol::store_commit::StoreBatchCommit,
164 publication: &coven_protocol::store_commit::StorePublicationRef,
165 ) -> Result<coven_protocol::remote_object::CandidateNonactivation, DbError> {
166 let coverage = self.require_accepted_membership(authority, membership, publication)?;
167 let records = super::StoreRecords::new(self.transaction, self.store_dir);
168 let root = authority.required_root_authority_on(records)?;
169 let registration =
170 authority.activated_registration_on(records, &root, &commit.author_registration)?;
171 coven_protocol::store_commit::VerifiedStoreBatchCommit::parse(
172 &commit.to_bytes(),
173 root.store_root_hash,
174 candidate,
175 ®istration,
176 )?;
177 let creation = commit
178 .membership_authority
179 .as_ref()
180 .ok_or_else(|| DbError::Message("candidate has no membership authority".into()))?;
181 let retirement = membership
182 .write_authority_retirement(creation, ®istration.author_pubkey)
183 .ok_or_else(|| DbError::Message("candidate has no accepted grant retirement".into()))?;
184 coven_protocol::remote_object::CandidateNonactivation::from_durable_parts(
185 candidate,
186 commit,
187 coven_protocol::remote_object::CandidateNonactivationProof::AuthorityRetirement {
188 publication: publication.clone(),
189 coverage,
190 creation: creation.clone(),
191 retirement: retirement.clone(),
192 },
193 )
194 .map_err(DbError::from)
195 }
196}