1use serde::{Deserialize, Serialize};
4
5use coven_protocol::objects::PreparedExactObject;
6use coven_protocol::prepared_commit::PreparedStoreOperationCommit;
7use coven_protocol::reclaim::{
8 ReclaimAuthorization, ReclaimAuthorizationRef, ReclaimEvidence, ReclaimEvidenceRef,
9 ReclaimReceipt, ReclaimReceiptRef, ReclaimTarget,
10};
11use coven_protocol::remote_object::{RemoteObjectRecord, RemoteObjectRecordError};
12use coven_protocol::store_commit::{ObjectHash, StoreBatchCommitRef};
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "snake_case", deny_unknown_fields)]
16pub enum DurableStoreReclaimObject {
17 Authorization {
18 evidence_ref: ReclaimEvidenceRef,
19 evidence: ReclaimEvidence,
20 evidence_prepared: PreparedExactObject,
21 authorization_ref: ReclaimAuthorizationRef,
22 authorization: ReclaimAuthorization,
23 authorization_prepared: PreparedExactObject,
24 },
25 Receipt {
26 receipt_ref: ReclaimReceiptRef,
27 receipt: ReclaimReceipt,
28 receipt_prepared: PreparedExactObject,
29 },
30}
31
32impl DurableStoreReclaimObject {
33 pub fn authorization_ref(&self) -> &ReclaimAuthorizationRef {
34 match self {
35 Self::Authorization {
36 authorization_ref, ..
37 } => authorization_ref,
38 Self::Receipt { receipt_ref, .. } => &receipt_ref.authorization,
39 }
40 }
41
42 pub fn validate(&self) -> Result<(), StoreReclaimJournalError> {
43 match self {
44 Self::Authorization {
45 evidence_ref,
46 evidence,
47 evidence_prepared,
48 authorization_ref,
49 authorization,
50 authorization_prepared,
51 } => {
52 evidence_ref
53 .verify(evidence)
54 .map_err(StoreReclaimJournalError::from)?;
55 authorization_ref
56 .verify_identity(authorization)
57 .map_err(StoreReclaimJournalError::from)?;
58 if evidence_prepared.reference() != &evidence_ref.object
59 || authorization_prepared.reference() != &authorization_ref.object
60 || authorization_ref.evidence != *evidence_ref
61 || authorization.evidence != *evidence_ref
62 || authorization.target != evidence.claim.target()
63 || authorization.store_root_hash != evidence.store_root_hash
64 {
65 return Err(StoreReclaimJournalError::Invalid(
66 "reclaim authorization graph has inconsistent exact identities".to_string(),
67 ));
68 }
69 }
70 Self::Receipt {
71 receipt_ref,
72 receipt,
73 receipt_prepared,
74 } => {
75 receipt_ref
76 .verify_identity(receipt)
77 .map_err(StoreReclaimJournalError::from)?;
78 if receipt_prepared.reference() != &receipt_ref.object {
79 return Err(StoreReclaimJournalError::Invalid(
80 "reclaim receipt differs from its prepared exact object".to_string(),
81 ));
82 }
83 }
84 }
85 Ok(())
86 }
87
88 pub fn commit_names_object(&self, candidate: &PreparedStoreOperationCommit) -> bool {
89 match self {
90 Self::Authorization {
91 authorization_ref, ..
92 } => candidate.commit.reclaim_authorization() == Some(authorization_ref),
93 Self::Receipt { receipt_ref, .. } => {
94 candidate.commit.reclaim_receipt() == Some(receipt_ref)
95 }
96 }
97 }
98
99 pub fn remote_objects(
100 &self,
101 candidate: &PreparedStoreOperationCommit,
102 ) -> Result<Vec<coven_protocol::remote_object::ClosedRemoteObject>, StoreReclaimJournalError>
103 {
104 self.validate()?;
105 if !self.commit_names_object(candidate) {
106 return Err(StoreReclaimJournalError::Invalid(
107 "reclaim candidate does not activate its exact durable object".to_string(),
108 ));
109 }
110 let owner = candidate.reference.clone();
111 let authorities = match self {
112 Self::Authorization {
113 evidence_ref,
114 evidence,
115 evidence_prepared,
116 authorization_ref,
117 authorization,
118 authorization_prepared,
119 } => vec![
120 RemoteObjectRecord::candidate_activated_reclaim_evidence(
121 evidence_ref.clone(),
122 &evidence.to_bytes(),
123 evidence_prepared.stored_bytes(),
124 owner.clone(),
125 )?,
126 RemoteObjectRecord::candidate_activated_reclaim_authorization(
127 authorization_ref.clone(),
128 &authorization.to_bytes(),
129 authorization_prepared.stored_bytes(),
130 owner,
131 )?,
132 ],
133 Self::Receipt {
134 receipt_ref,
135 receipt,
136 receipt_prepared,
137 } => vec![RemoteObjectRecord::candidate_activated_reclaim_receipt(
138 receipt_ref.clone(),
139 &receipt.to_bytes(),
140 receipt_prepared.stored_bytes(),
141 owner,
142 )?],
143 };
144 candidate
145 .retained_authority_remote_objects(authorities)
146 .map_err(StoreReclaimJournalError::Outbound)
147 }
148}
149
150#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
151#[serde(rename_all = "snake_case", deny_unknown_fields)]
152pub enum ReclaimedStorePackage {
153 AbsentVerified {
154 authorization: ReclaimAuthorizationRef,
155 authorization_activation: StoreBatchCommitRef,
156 },
157 Receipted {
158 authorization: ReclaimAuthorizationRef,
159 authorization_activation: StoreBatchCommitRef,
160 receipt: ReclaimReceiptRef,
161 receipt_activation: StoreBatchCommitRef,
162 },
163}
164
165impl ReclaimedStorePackage {
166 pub fn absent_verified(
167 authorization: ReclaimAuthorizationRef,
168 authorization_activation: StoreBatchCommitRef,
169 ) -> Result<Self, StoreReclaimJournalError> {
170 let value = Self::AbsentVerified {
171 authorization,
172 authorization_activation,
173 };
174 value.validate()?;
175 Ok(value)
176 }
177
178 pub fn receipted(
179 authorization: ReclaimAuthorizationRef,
180 authorization_activation: StoreBatchCommitRef,
181 receipt: ReclaimReceiptRef,
182 receipt_activation: StoreBatchCommitRef,
183 ) -> Result<Self, StoreReclaimJournalError> {
184 let value = Self::Receipted {
185 authorization,
186 authorization_activation,
187 receipt,
188 receipt_activation,
189 };
190 value.validate()?;
191 Ok(value)
192 }
193
194 pub fn authorization(&self) -> &ReclaimAuthorizationRef {
195 match self {
196 Self::AbsentVerified { authorization, .. } | Self::Receipted { authorization, .. } => {
197 authorization
198 }
199 }
200 }
201
202 pub fn authorization_activation(&self) -> &StoreBatchCommitRef {
203 match self {
204 Self::AbsentVerified {
205 authorization_activation,
206 ..
207 }
208 | Self::Receipted {
209 authorization_activation,
210 ..
211 } => authorization_activation,
212 }
213 }
214
215 pub fn object_id(&self) -> ObjectHash {
216 coven_protocol::remote_object::remote_object_id(self.authorization().target().object())
217 }
218
219 pub fn validate(&self) -> Result<(), StoreReclaimJournalError> {
220 let authorization = self.authorization();
221 let authorization_activation = self.authorization_activation();
222 validate_reclaim_identity(authorization, authorization_activation)?;
223 let target = authorization.target();
224 let target_activation = authorization.target_activation();
225 if *target.object() == authorization.object
226 || *target.object() == authorization.evidence.object
227 || target_activation.names_authority_object(target.object())
228 {
229 return Err(StoreReclaimJournalError::Invalid(
230 "reclaimed package aliases authority or crosses Store histories".to_string(),
231 ));
232 }
233 if let Self::Receipted {
234 receipt,
235 receipt_activation,
236 ..
237 } = self
238 {
239 if &receipt.authorization != authorization
240 || receipt.object == *authorization.target().object()
241 || receipt_activation == authorization_activation
242 {
243 return Err(StoreReclaimJournalError::Invalid(
244 "reclaim receipt does not close its exact authorization history".to_string(),
245 ));
246 }
247 }
248 Ok(())
249 }
250}
251
252fn validate_reclaim_identity(
253 authorization: &ReclaimAuthorizationRef,
254 authorization_activation: &StoreBatchCommitRef,
255) -> Result<(), StoreReclaimJournalError> {
256 if authorization
260 .target_activation()
261 .names_authority_object(&authorization_activation.object)
262 {
263 return Err(StoreReclaimJournalError::Invalid(
264 "reclaim authorization does not follow its target in one Store history".to_string(),
265 ));
266 }
267 Ok(())
268}
269
270#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
271#[serde(rename_all = "snake_case", deny_unknown_fields)]
272pub enum DurableStoreReclaimOperation {
273 AuthorizationCandidate {
274 object: Box<DurableStoreReclaimObject>,
275 candidate: Box<PreparedStoreOperationCommit>,
276 },
277 Authorized {
278 authorization: ReclaimAuthorizationRef,
279 activation: StoreBatchCommitRef,
280 },
281 AbsentVerified {
282 authorization: ReclaimAuthorizationRef,
283 authorization_activation: StoreBatchCommitRef,
284 target: ReclaimTarget,
285 },
286 ReceiptCandidate {
287 authorization: ReclaimAuthorizationRef,
288 authorization_activation: StoreBatchCommitRef,
289 object: Box<DurableStoreReclaimObject>,
290 candidate: Box<PreparedStoreOperationCommit>,
291 },
292 Completed {
293 authorization: ReclaimAuthorizationRef,
294 authorization_activation: StoreBatchCommitRef,
295 receipt: ReclaimReceiptRef,
296 receipt_activation: StoreBatchCommitRef,
297 },
298}
299
300#[derive(Debug, Clone, PartialEq, Eq)]
307pub struct StuckReclaimOperation {
308 pub operation_id: ObjectHash,
309 pub target: ReclaimTarget,
310 pub error: String,
311}
312
313impl DurableStoreReclaimOperation {
314 pub fn operation_id(&self) -> ObjectHash {
315 self.authorization().authorization_hash
316 }
317
318 pub fn authorization(&self) -> &ReclaimAuthorizationRef {
319 match self {
320 Self::AuthorizationCandidate { object, .. } => object.authorization_ref(),
321 Self::Authorized { authorization, .. }
322 | Self::AbsentVerified { authorization, .. }
323 | Self::ReceiptCandidate { authorization, .. }
324 | Self::Completed { authorization, .. } => authorization,
325 }
326 }
327
328 pub fn candidate(&self) -> Option<&PreparedStoreOperationCommit> {
329 match self {
330 Self::AuthorizationCandidate { candidate, .. }
331 | Self::ReceiptCandidate { candidate, .. } => Some(candidate),
332 Self::Authorized { .. } | Self::AbsentVerified { .. } | Self::Completed { .. } => None,
333 }
334 }
335
336 pub fn object(&self) -> Option<&DurableStoreReclaimObject> {
337 match self {
338 Self::AuthorizationCandidate { object, .. } | Self::ReceiptCandidate { object, .. } => {
339 Some(object)
340 }
341 Self::Authorized { .. } | Self::AbsentVerified { .. } | Self::Completed { .. } => None,
342 }
343 }
344
345 pub fn validate(&self) -> Result<(), StoreReclaimJournalError> {
346 match self {
347 Self::AuthorizationCandidate { object, candidate } => {
348 object.validate()?;
349 candidate
350 .reference
351 .verify_commit(&candidate.commit)
352 .map_err(StoreReclaimJournalError::from)?;
353 if !object.commit_names_object(candidate) {
354 return Err(StoreReclaimJournalError::Invalid(
355 "reclaim journal candidate names another operation".to_string(),
356 ));
357 }
358 }
359 Self::Authorized {
360 authorization,
361 activation,
362 } => validate_reclaim_identity(authorization, activation)?,
363 Self::AbsentVerified {
364 authorization,
365 authorization_activation,
366 target,
367 } => {
368 if target != authorization.target() {
369 return Err(StoreReclaimJournalError::Invalid(
370 "reclaim target differs from its exact authorization".to_string(),
371 ));
372 }
373 ReclaimedStorePackage::absent_verified(
374 authorization.clone(),
375 authorization_activation.clone(),
376 )?;
377 }
378 Self::ReceiptCandidate {
379 authorization,
380 authorization_activation,
381 object,
382 candidate,
383 ..
384 } => {
385 validate_reclaim_identity(authorization, authorization_activation)?;
386 object.validate()?;
387 candidate
388 .reference
389 .verify_commit(&candidate.commit)
390 .map_err(StoreReclaimJournalError::from)?;
391 if object.authorization_ref() != authorization
392 || !matches!(&**object, DurableStoreReclaimObject::Receipt { .. })
393 || !object.commit_names_object(candidate)
394 {
395 return Err(StoreReclaimJournalError::Invalid(
396 "reclaim receipt candidate changes its authorization".to_string(),
397 ));
398 }
399 }
400 Self::Completed {
401 authorization,
402 authorization_activation,
403 receipt,
404 receipt_activation,
405 } => {
406 ReclaimedStorePackage::receipted(
407 authorization.clone(),
408 authorization_activation.clone(),
409 receipt.clone(),
410 receipt_activation.clone(),
411 )?;
412 }
413 }
414 Ok(())
415 }
416}
417
418#[derive(Debug, thiserror::Error)]
419pub enum StoreReclaimJournalError {
420 #[error("invalid durable Store reclaim state: {0}")]
421 Invalid(String),
422 #[error(transparent)]
423 RemoteObject(#[from] RemoteObjectRecordError),
424 #[error(transparent)]
425 Outbound(#[from] coven_protocol::prepared_commit::PreparedCommitError),
426 #[error(transparent)]
427 Storage(#[from] coven_protocol::objects::StorageError),
428 #[error(transparent)]
429 Protocol(#[from] coven_protocol::store_commit::StoreProtocolError),
430}