Skip to main content

coven_replication/sync/store/commit_publication/operation/
mod.rs

1use super::*;
2use crate::sync::store::authorization::load_wrapped_store_key;
3use crate::sync::store::commit_verification::commit::StoreMembershipObjectVerifier;
4use crate::sync::store::membership::MembershipMutationError;
5use coven_database::VerifiedMergeMembershipObjects;
6use coven_protocol::membership::{
7    self, MembershipChain, MembershipChange, MembershipEntry, MembershipError, MembershipHeadRef,
8};
9use coven_protocol::membership_mutation::{
10    PreparedMembershipPublication, PreparedMembershipTransition,
11};
12use coven_protocol::objects::{
13    ProtocolObjectContext, ProtocolObjectDomain, StorageError, StoreObjectError,
14};
15use coven_protocol::store_commit::{
16    self, commit_semantic_prefix, head_slot_prefix, membership_head_slot_prefix,
17    StoreBatchCommitDeletionTarget, StoreDeviceHeadRef,
18};
19use coven_protocol::wrapped_store_key::{PreparedWrappedStoreKey, WrappedStoreKeyRef};
20use coven_storage as store_objects;
21use std::sync::Arc;
22
23mod abandonment;
24mod blob_lifecycle;
25pub(crate) use blob_lifecycle::TombstoneGcError;
26mod blob_preparation;
27mod blob_upload;
28pub(crate) mod commit_plan;
29pub(super) mod membership_mutation;
30pub(super) mod membership_mutation_journal;
31mod preparation;
32
33mod commit_publication;
34mod facades;
35mod membership_commands;
36mod membership_publication;
37mod operation_test_support;
38mod signing;
39mod store_writes;
40
41pub(super) use blob_preparation::close_prepared_packages;
42pub(crate) use blob_preparation::prepare_partition_blob_locator;
43
44use membership_mutation_journal::{
45    decode_membership_mutation, exact_owned_remote, AdmissionMutationPlan, MembershipMutationPlan,
46    MembershipMutationProgress, MutationPersistence, ReplacementWrappedKey, ResolveMutationPlan,
47    RevokeMembershipPublication, RevokeMutationPlan,
48};
49
50pub(crate) struct MergeConflictResolutionCommitPlan {
51    authorship: coven_database::store::OwnStreamAuthorship,
52    writer: Arc<LocalStoreWriter>,
53    root: coven_protocol::store_commit::StoreRootRef,
54    coord: coven_protocol::store_commit::StoreCommitCoord,
55    order: coven_protocol::store_commit::StoreCommitOrder,
56    membership: coven_protocol::membership::MembershipChain,
57    device_state: coven_protocol::store_commit::StoreDeviceStateRef,
58    device_state_value: coven_protocol::store_commit::ResolvedStoreDeviceState,
59}
60
61impl MergeConflictResolutionCommitPlan {
62    #[allow(clippy::too_many_arguments)]
63    fn new(
64        authorship: coven_database::store::OwnStreamAuthorship,
65        writer: Arc<LocalStoreWriter>,
66        root: coven_protocol::store_commit::StoreRootRef,
67        coord: coven_protocol::store_commit::StoreCommitCoord,
68        order: coven_protocol::store_commit::StoreCommitOrder,
69        authorization: crate::sync::store::merge_conflict::MergeConflictResolutionAuthorization,
70    ) -> Self {
71        Self {
72            authorship,
73            writer,
74            root,
75            coord,
76            order,
77            membership: authorization.membership,
78            device_state: authorization.device_state_ref,
79            device_state_value: authorization.device_state,
80        }
81    }
82
83    pub(super) fn root(&self) -> &coven_protocol::store_commit::StoreRootRef {
84        &self.root
85    }
86
87    pub(super) fn membership(&self) -> &coven_protocol::membership::MembershipChain {
88        &self.membership
89    }
90
91    pub(super) fn grant_authorized_stream_id(
92        &self,
93        grant: &coven_protocol::membership::MembershipGrantId,
94        domain: coven_protocol::store_commit::StreamAnchorDomain,
95    ) -> coven_protocol::membership::AuthorStreamId {
96        self.writer
97            .grant_authorized_stream_id(self.root.store_root_hash, grant, domain)
98    }
99
100    pub(super) fn sign_conflict_resolution(
101        &self,
102        chain: &coven_protocol::membership::MembershipChain,
103        selection: coven_protocol::membership::MembershipConflictSelection,
104        replacement_grant: coven_protocol::membership::MembershipGrantId,
105        membership: coven_protocol::store_commit::GrantStreamAnchor,
106        recovery: coven_protocol::store_commit::GrantStreamAnchor,
107    ) -> Result<
108        coven_protocol::membership::StoreMembershipConflictResolution,
109        MembershipMutationError,
110    > {
111        self.writer.sign_conflict_resolution(
112            chain,
113            self.root.store_root_hash,
114            selection,
115            replacement_grant,
116            membership,
117            recovery,
118            self.device_state.clone(),
119        )
120    }
121
122    pub(super) fn sign_conflict_resolution_activation(
123        &self,
124        chain: &coven_protocol::membership::MembershipChain,
125        stream_id: coven_protocol::membership::AuthorStreamId,
126        reference: coven_protocol::membership::StoreMembershipConflictResolutionRef,
127        resolution: &coven_protocol::membership::StoreMembershipConflictResolution,
128        created_at: String,
129    ) -> Result<
130        coven_protocol::membership::MembershipEntry,
131        coven_protocol::membership::MembershipError,
132    > {
133        self.writer.sign_conflict_resolution_activation(
134            chain,
135            self.root.store_root_hash,
136            stream_id,
137            reference,
138            resolution,
139            created_at,
140        )
141    }
142
143    pub(super) fn finish(
144        self,
145        membership: &coven_protocol::membership::MembershipChain,
146        resolution: &coven_protocol::membership::StoreMembershipConflictResolutionRef,
147    ) -> Result<commit_plan::StoreOperationCommitPlan, StoreError> {
148        let coven_protocol::membership::MembershipStatus::Resolved(resolved) = membership.status()
149        else {
150            return Err(StoreError::InvalidOutbound(
151                "conflict-resolution candidate membership remains conflicted".to_string(),
152            ));
153        };
154        if membership
155            .resolution_refs()
156            .binary_search(resolution)
157            .is_err()
158        {
159            return Err(StoreError::InvalidOutbound(
160                "conflict-resolution candidate membership omits its exact resolution".to_string(),
161            ));
162        }
163        let replacement_grant = coven_protocol::membership::derive_store_resolution_grant(
164            &resolution.conflict_hash,
165            &resolution.resolver_pubkey,
166        );
167        let authority =
168            coven_protocol::membership::MembershipGrantCreationAuthority::ConflictResolution(
169                resolution.clone(),
170            );
171        if membership
172            .active_grant(&replacement_grant)
173            .is_none_or(|record| {
174                record.member_pubkey != self.writer.author_pubkey()
175                    || record.creation_authority != authority
176            })
177        {
178            return Err(StoreError::InvalidOutbound(
179                "conflict-resolution candidate is not authorized by its replacement Owner grant"
180                    .to_string(),
181            ));
182        }
183        let membership_state = coven_protocol::circle_control::StoreMembershipStateRef::from_parts(
184            membership.head_refs().to_vec(),
185            membership.resolution_refs().to_vec(),
186            self.device_state.recovery().to_vec(),
187            resolved.state_hash,
188        )
189        .map_err(StoreError::from)?;
190        let common = commit_plan::StoreOperationPlanCommon::new(
191            self.authorship,
192            self.writer,
193            self.root,
194            self.coord,
195            self.order,
196            membership_state,
197            self.device_state,
198            coven_protocol::store_commit::StoreOperationMembershipAuthority {
199                predecessor: authority,
200            },
201            Some(replacement_grant),
202        );
203        Ok(commit_plan::StoreOperationCommitPlan::new(
204            common,
205            membership.clone(),
206            self.device_state_value,
207        ))
208    }
209}
210
211#[derive(Debug, thiserror::Error)]
212pub enum StoreWriterAuthorizationError {
213    #[error("Store authority: {0}")]
214    StoreAuthority(SyncCycleFailure),
215    #[error("Store writer registration: {0}")]
216    Registration(StoreRegistrationError),
217}
218
219#[derive(Debug, thiserror::Error)]
220pub(crate) enum AuthorizationRefreshError {
221    #[error("select this device's wrapped-key authority: {0}")]
222    Membership(#[source] coven_protocol::membership::MembershipError),
223    #[error("read this device's wrapped key: {0}")]
224    WrappedKey(#[source] crate::sync::store::membership::MembershipMutationError),
225    #[error("rotation gate database state: {0}")]
226    Database(#[source] coven_database::DbError),
227    #[error("merge this device's live and selected keyrings: {0}")]
228    InvalidKeyring(#[source] coven_keys::encryption::EncryptionError),
229    #[error("adopt committed store-key rotation: {0}")]
230    KeyAdoption(#[source] coven_keys::keys::KeyError),
231}