Skip to main content

coven_protocol/store_commit/
owner_promotion_publication.rs

1use super::*;
2
3impl SignedBody for OwnerPromotionRequestActivation {
4    const DOMAIN: &'static [u8] = b"coven.owner-promotion-request-publication.v1\0";
5}
6
7/// The original promoter's statement of the request's winning publication.
8/// Its publisher signs this after observing the exact accepted provider position.
9pub type OwnerPromotionRequestPublication = Signed<OwnerPromotionRequestActivation>;
10
11impl OwnerPromotionRequestActivation {
12    pub fn validate_for_request_commit(
13        &self,
14        commit: &StoreBatchCommit,
15    ) -> Result<(), StoreProtocolError> {
16        let request = commit
17            .owner_promotion_request()
18            .ok_or(StoreProtocolError::OwnerPromotionMismatch)?;
19        self.commit.verify_commit(commit)?;
20        self.commit.object.verify(&commit.to_bytes())?;
21        self.publication.validate_slot()?;
22        StorePublicationPosition::new(self.publication.position.get())?;
23        if self.publication.store_root_hash != request.store_root_hash
24            || commit.store_root_hash != request.store_root_hash
25            || commit.author_registration != request.promoter_registration
26            || commit.membership_state != request.predecessor_membership
27            || commit.device_state != request.predecessor_devices
28        {
29            return Err(StoreProtocolError::OwnerPromotionMismatch);
30        }
31        Ok(())
32    }
33}
34
35impl OwnerPromotionRequestPublication {
36    pub fn signed(
37        commit: &StoreBatchCommit,
38        accepted_entry: &StorePublicationEntry,
39        accepted_reference: &StorePublicationRef,
40        author: &StoreDeviceRegistration,
41        device_signer: &UserKeypair,
42    ) -> Result<Self, StoreProtocolError> {
43        let StorePublicationPayload::Commit(reference) = &accepted_entry.payload else {
44            return Err(StoreProtocolError::OwnerPromotionMismatch);
45        };
46        StorePublicationEntry::parse_at(
47            &accepted_entry.to_bytes(),
48            commit.store_root_hash,
49            accepted_reference,
50            &author.device_signing_pubkey,
51        )?;
52        if accepted_entry.author_registration != commit.author_registration {
53            return Err(StoreProtocolError::OwnerPromotionMismatch);
54        }
55        let value = Signed::sign(
56            OwnerPromotionRequestActivation {
57                commit: reference.clone(),
58                publication: accepted_reference.clone(),
59            },
60            device_signer,
61        );
62        value.verify_for(commit, author)?;
63        Ok(value)
64    }
65
66    pub fn verify_for(
67        &self,
68        commit: &StoreBatchCommit,
69        author: &StoreDeviceRegistration,
70    ) -> Result<(), StoreProtocolError> {
71        self.body().validate_for_request_commit(commit)?;
72        let request = commit
73            .owner_promotion_request()
74            .ok_or(StoreProtocolError::OwnerPromotionMismatch)?;
75        request.verify(&author.store_root, author)?;
76        commit.verify_by(&author.device_signing_pubkey)?;
77        self.verify_by(&author.device_signing_pubkey)
78    }
79}
80
81/// Exact publication result retained by a request's continuing operation.
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
83#[serde(deny_unknown_fields)]
84pub struct RetainedOwnerPromotionRequestPublication {
85    pub value: OwnerPromotionRequestPublication,
86    pub object: ExactObjectRef,
87}
88
89impl RetainedOwnerPromotionRequestPublication {
90    pub fn validate_for(&self, commit: &StoreBatchCommit) -> Result<(), StoreProtocolError> {
91        self.value.require_version()?;
92        self.value.body().validate_for_request_commit(commit)?;
93        self.object.verify(&self.value.to_bytes())?;
94        let request = commit
95            .owner_promotion_request()
96            .ok_or(StoreProtocolError::OwnerPromotionMismatch)?;
97        let expected = format!(
98            "{}.json",
99            owner_promotion_request_publication_semantic_prefix(request.promotion_id),
100        );
101        if self.object.slot() != &request.publication_slot
102            || self.object.slot().logical_key() != expected
103        {
104            return Err(StoreProtocolError::OwnerPromotionMismatch);
105        }
106        Ok(())
107    }
108}
109
110/// Continuing authority for one request whose target may still accept it.
111/// The request's result supplies exact acceptance; its original predecessor
112/// state remains owned only while that request can consume it.
113#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
114#[serde(deny_unknown_fields)]
115pub struct RetainedOwnerPromotionRequest {
116    pub commit: StoreBatchCommit,
117    pub publication: RetainedOwnerPromotionRequestPublication,
118    pub predecessor_state: ResolvedStoreDeviceState,
119}
120
121impl RetainedOwnerPromotionRequest {
122    pub fn request(&self) -> Result<&OwnerPromotionRequest, StoreProtocolError> {
123        self.commit
124            .owner_promotion_request()
125            .ok_or(StoreProtocolError::OwnerPromotionMismatch)
126    }
127
128    pub fn validate_shape(&self) -> Result<(), StoreProtocolError> {
129        self.publication.validate_for(&self.commit)?;
130        self.predecessor_state.validate_canonical()?;
131        let predecessor = StoreDeviceStateRef::from_resolved(
132            self.commit.order.predecessor_cut()?.frontier(),
133            &self.predecessor_state,
134        )?;
135        if predecessor != self.request()?.predecessor_devices {
136            return Err(StoreProtocolError::OwnerPromotionMismatch);
137        }
138        Ok(())
139    }
140}
141
142pub fn owner_promotion_request_publication_semantic_prefix(id: OwnerPromotionId) -> String {
143    format!("store-v1/owner-promotion-publications/{id}")
144}