Skip to main content

coven_replication/sync/store/merge_conflict/
abandonment.rs

1use crate::sync::store::StoreError;
2use coven_protocol::objects::PreparedExactObject;
3use coven_protocol::store_commit::{
4    ObjectHash, StoreBatchCommitDeletionTarget, StoreDeviceHead, StoreDeviceRegistration,
5    VerifiedStoreBatchCommit,
6};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum MergeCandidateAbandonment {
10    NotRequired,
11    Abandoned,
12    CandidateActivated,
13}
14
15/// The nonactivation proof for discarding a candidate whose slot is already
16/// resolved. Unlike Merge abandonment, discard never publishes an abandonment
17/// commit to race for the slot — it is invoked after the slot is lost, so it
18/// observes the outcome directly. A different verified winner occupying the
19/// successor slot is a standalone proof (the candidate is bound to that
20/// create-once slot and can never take it), independent of the author's status.
21/// Author exclusion covers a slot the author was excluded from before anyone
22/// claimed it. An accepted Store commit whose membership state tombstones the
23/// candidate's exact grant and whose predecessor cut excludes the candidate is
24/// the membership-revocation proof.
25/// Publish the exact prepared object graph in sequence order. Every remote object
26/// is verified at its reserved slot before the exact head activates the commit.
27#[derive(Debug, Clone)]
28pub struct VerifiedMergeWinner {
29    store_root_hash: ObjectHash,
30    expected_slot: coven_protocol::objects::ObjectSlot,
31    expected: StoreDeviceHead,
32    expected_commit: Box<VerifiedStoreBatchCommit>,
33    winner: StoreDeviceHead,
34    winner_prepared: PreparedExactObject,
35    winner_commit: Box<VerifiedStoreBatchCommit>,
36}
37
38impl VerifiedMergeWinner {
39    pub(crate) fn from_verified_parts(
40        store_root_hash: ObjectHash,
41        expected_slot: coven_protocol::objects::ObjectSlot,
42        expected: StoreDeviceHead,
43        expected_commit: VerifiedStoreBatchCommit,
44        winner: StoreDeviceHead,
45        winner_prepared: PreparedExactObject,
46        winner_commit: VerifiedStoreBatchCommit,
47    ) -> Self {
48        Self {
49            store_root_hash,
50            expected_slot,
51            expected,
52            expected_commit: Box::new(expected_commit),
53            winner,
54            winner_prepared,
55            winner_commit: Box::new(winner_commit),
56        }
57    }
58
59    pub(crate) fn verified_nonactivation(
60        &self,
61        candidate: StoreBatchCommitDeletionTarget,
62        author: &StoreDeviceRegistration,
63    ) -> Result<
64        coven_protocol::remote_object::VerifiedCandidateNonactivation,
65        coven_protocol::remote_object::RemoteObjectRecordError,
66    > {
67        let commit = candidate
68            .verify_nonactivation_candidate(self.store_root_hash, author)
69            .map_err(
70                coven_protocol::remote_object::RemoteObjectRecordError::InvalidProofProtocol,
71            )?;
72        let reference = commit.reference().clone();
73        if self.expected.store_root_hash != self.store_root_hash
74            || commit.store_root_hash != self.store_root_hash
75            || self.expected.author_registration != commit.author_registration
76            || self.expected.commit.coord != reference.coord
77            || self.expected_commit.author_registration != commit.author_registration
78            || self.expected_commit.order.predecessor() != commit.order.predecessor()
79            || self.winner.store_root_hash != self.store_root_hash
80            || self.winner.author_registration != self.expected.author_registration
81            || self.winner.commit.coord != self.expected.commit.coord
82            || self.winner.successor.activation != self.expected.successor.activation
83            || self.winner.successor.predecessor != self.expected.successor.predecessor
84            || self.winner_prepared.reference().slot() != &self.expected_slot
85            || self.winner.commit == reference
86            || self.winner.commit != *self.winner_commit.reference()
87        {
88            return Err(
89                coven_protocol::remote_object::RemoteObjectRecordError::InvalidProof(
90                    "Merge winner observation is not bound to the losing candidate's exact activation point"
91                        .to_string(),
92                ),
93            );
94        }
95        coven_protocol::remote_object::VerifiedCandidateNonactivation::from_verified_merge_winner(
96            candidate,
97            coven_protocol::store_commit::StoreDeviceHeadRef {
98                head_hash: self.winner.head_hash(),
99                object: self.winner_prepared.reference().clone(),
100            },
101            self.winner.commit.clone(),
102        )
103    }
104
105    pub(crate) fn verified_nonactivations(
106        &self,
107        targets: impl IntoIterator<Item = StoreBatchCommitDeletionTarget>,
108        author: &StoreDeviceRegistration,
109    ) -> Result<Vec<coven_protocol::remote_object::VerifiedCandidateNonactivation>, StoreError>
110    {
111        let mut nonactivations = Vec::new();
112        for target in targets {
113            if target.coord == self.winner.commit.coord
114                && target.object == self.winner.commit.object
115                && target.canonical_signed_bytes == self.winner_commit.to_bytes()
116            {
117                continue;
118            }
119            nonactivations.push(
120                self.verified_nonactivation(target, author)
121                    .map_err(StoreError::from)?,
122            );
123        }
124        Ok(nonactivations)
125    }
126
127    pub(crate) fn winner(&self) -> &StoreDeviceHead {
128        &self.winner
129    }
130
131    pub(crate) fn winner_prepared(&self) -> &PreparedExactObject {
132        &self.winner_prepared
133    }
134
135    pub(crate) fn into_head(self) -> (StoreDeviceHead, PreparedExactObject) {
136        (self.winner, self.winner_prepared)
137    }
138
139    #[cfg(test)]
140    pub(crate) fn winner_commit(&self) -> &VerifiedStoreBatchCommit {
141        &self.winner_commit
142    }
143
144    #[cfg(test)]
145    pub(crate) fn winner_mut_for_test(&mut self) -> &mut StoreDeviceHead {
146        &mut self.winner
147    }
148
149    #[cfg(test)]
150    pub(crate) fn set_expected_slot_for_test(
151        &mut self,
152        expected_slot: coven_protocol::objects::ObjectSlot,
153    ) {
154        self.expected_slot = expected_slot;
155    }
156}
157
158pub enum ExcludedCandidateHeadObservation {
159    AuthorExclusion,
160    MergeWinner(VerifiedMergeWinner),
161}