Skip to main content

coven_protocol/store_commit/device_state/
retained.rs

1use super::*;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub struct VerifiedStoreDeviceOperations {
5    proposals: Vec<(
6        RetainedStoreDeviceExclusionProposal,
7        StoreDeviceExclusionProposal,
8    )>,
9    outcomes: Vec<VerifiedStoreDeviceExclusionOutcome>,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13enum VerifiedStoreDeviceExclusionOutcome {
14    Excluded(RetainedStoreDeviceExclusionOutcome),
15    Cancelled(RetainedStoreDeviceExclusionOutcome),
16}
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(deny_unknown_fields)]
20pub struct RetainedStoreDeviceRegistrationActivations {
21    registrations: Vec<RetainedStoreDeviceRegistrationActivation>,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(deny_unknown_fields)]
26struct RetainedStoreDeviceRegistrationActivation {
27    canonical_registration: Vec<u8>,
28    authority: StoreDeviceRegistrationActivation,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(deny_unknown_fields)]
33pub struct RetainedStoreDeviceOperations {
34    proposals: Vec<RetainedStoreDeviceExclusionProposal>,
35    outcomes: Vec<RetainedStoreDeviceExclusionOutcome>,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39#[serde(deny_unknown_fields)]
40pub struct RetainedStoreDeviceExclusionProposal {
41    reference: StoreDeviceExclusionProposalRef,
42    canonical_proposal: Vec<u8>,
43    canonical_target_registration: Vec<u8>,
44    canonical_owner_registration: Vec<u8>,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "snake_case", deny_unknown_fields)]
49pub enum RetainedStoreDeviceExclusionOutcome {
50    Excluded {
51        reference: StoreDeviceExclusionRef,
52        canonical_outcome: Vec<u8>,
53        proposal: RetainedStoreDeviceExclusionProposal,
54        canonical_owner_registration: Vec<u8>,
55    },
56    Cancelled {
57        reference: StoreDeviceExclusionCancellationRef,
58        canonical_outcome: Vec<u8>,
59        proposal: RetainedStoreDeviceExclusionProposal,
60        canonical_owner_registration: Vec<u8>,
61    },
62}
63
64impl VerifiedStoreDeviceOperations {
65    pub fn proposals(
66        &self,
67    ) -> impl ExactSizeIterator<
68        Item = (
69            &StoreDeviceExclusionProposalRef,
70            &StoreDeviceExclusionProposal,
71        ),
72    > {
73        self.proposals
74            .iter()
75            .map(|(source, proposal)| (&source.reference, proposal))
76    }
77
78    pub fn exclusions(&self) -> impl Iterator<Item = &StoreDeviceExclusionRef> {
79        self.outcomes.iter().filter_map(|outcome| match outcome {
80            VerifiedStoreDeviceExclusionOutcome::Excluded(source) => {
81                Some(source.exclusion_reference())
82            }
83            VerifiedStoreDeviceExclusionOutcome::Cancelled(_) => None,
84        })
85    }
86
87    pub(crate) fn from_retained_sources(
88        root: &StoreRootRef,
89        commit: &StoreBatchCommit,
90        proposals: Vec<RetainedStoreDeviceExclusionProposal>,
91        outcomes: Vec<RetainedStoreDeviceExclusionOutcome>,
92    ) -> Result<Self, StoreProtocolError> {
93        let proposal_refs = proposals
94            .iter()
95            .map(|source| source.reference.clone())
96            .collect::<Vec<_>>();
97        let outcome_refs = outcomes
98            .iter()
99            .map(RetainedStoreDeviceExclusionOutcome::wire_reference)
100            .collect::<Vec<_>>();
101        if proposal_refs.as_slice() != commit.device_exclusion_proposals()
102            || outcome_refs.as_slice() != commit.device_exclusion_outcomes()
103        {
104            return Err(StoreProtocolError::DeviceStateMismatch);
105        }
106        let retained = RetainedStoreDeviceOperations {
107            proposals: proposals.clone(),
108            outcomes: outcomes.clone(),
109        };
110        let proposals = proposals
111            .into_iter()
112            .map(|source| {
113                let proposal = source.verify(root)?;
114                Ok((source, proposal))
115            })
116            .collect::<Result<Vec<_>, StoreProtocolError>>()?;
117        let outcomes = outcomes
118            .into_iter()
119            .map(|source| source.verify(root))
120            .collect::<Result<Vec<_>, StoreProtocolError>>()?;
121        let verified = Self {
122            proposals,
123            outcomes,
124        };
125        if verified.to_retained() != retained {
126            return Err(StoreProtocolError::DeviceStateMismatch);
127        }
128        Ok(verified)
129    }
130
131    pub fn without_exclusions(commit: &StoreBatchCommit) -> Result<Self, StoreProtocolError> {
132        if !commit.device_exclusion_proposals().is_empty()
133            || !commit.device_exclusion_outcomes().is_empty()
134        {
135            return Err(StoreProtocolError::DeviceStateMismatch);
136        }
137        Ok(Self {
138            proposals: Vec::new(),
139            outcomes: Vec::new(),
140        })
141    }
142
143    pub fn to_retained(&self) -> RetainedStoreDeviceOperations {
144        RetainedStoreDeviceOperations {
145            proposals: self
146                .proposals
147                .iter()
148                .map(|(source, _)| source.clone())
149                .collect(),
150            outcomes: self
151                .outcomes
152                .iter()
153                .map(VerifiedStoreDeviceExclusionOutcome::source)
154                .cloned()
155                .collect(),
156        }
157    }
158
159    pub fn apply_to(
160        &self,
161        predecessor: ResolvedStoreDeviceState,
162    ) -> Result<ResolvedStoreDeviceState, StoreProtocolError> {
163        let mut state = predecessor;
164        for (source, proposal) in &self.proposals {
165            state = state.propose_exclusion(source.reference.clone(), proposal)?;
166        }
167        for outcome in &self.outcomes {
168            state = match outcome {
169                VerifiedStoreDeviceExclusionOutcome::Excluded(source) => {
170                    state.exclude(source.exclusion_reference().clone())?
171                }
172                VerifiedStoreDeviceExclusionOutcome::Cancelled(source) => {
173                    state.cancel_exclusion(source.cancellation_reference().clone())?
174                }
175            };
176        }
177        Ok(state)
178    }
179
180    /// Return the state these operations continue to contribute after their
181    /// publication is accepted. This does not establish their issuer authority
182    /// or recheck transition preconditions at a later snapshot boundary.
183    pub fn accepted_effect(&self) -> Result<ResolvedStoreDeviceState, StoreProtocolError> {
184        let proposals =
185            self.proposals
186                .iter()
187                .map(|(source, _)| StoreDeviceProposalState::Pending {
188                    proposal: source.reference.clone(),
189                });
190        let outcomes = self.outcomes.iter().map(|outcome| match outcome {
191            VerifiedStoreDeviceExclusionOutcome::Excluded(source) => {
192                let exclusion = source.exclusion_reference();
193                StoreDeviceProposalState::Superseded {
194                    proposal: exclusion.proposal.clone(),
195                    terminals: vec![exclusion.clone()],
196                }
197            }
198            VerifiedStoreDeviceExclusionOutcome::Cancelled(source) => {
199                StoreDeviceProposalState::Cancelled {
200                    outcome: source.cancellation_reference().clone(),
201                }
202            }
203        });
204        ResolvedStoreDeviceState::merge(
205            proposals
206                .chain(outcomes)
207                .map(ResolvedStoreDeviceState::exclusion_effect)
208                .collect::<Result<Vec<_>, _>>()?,
209        )
210    }
211}
212
213impl RetainedStoreDeviceRegistrationActivations {
214    pub fn from_verified(
215        root: &StoreRootRef,
216        commit: &StoreBatchCommit,
217        registrations: &[ActivatedStoreDeviceRegistration],
218    ) -> Result<Self, StoreProtocolError> {
219        if registrations.len() != commit.device_registrations().len() {
220            return Err(StoreProtocolError::DeviceStateMismatch);
221        }
222        let retained = Self {
223            registrations: registrations
224                .iter()
225                .map(|registration| RetainedStoreDeviceRegistrationActivation {
226                    canonical_registration: registration.value().to_bytes(),
227                    authority: registration.activation().clone(),
228                })
229                .collect(),
230        };
231        retained.verify_for(root, commit)?;
232        Ok(retained)
233    }
234
235    pub fn verify_for(
236        &self,
237        root: &StoreRootRef,
238        commit: &StoreBatchCommit,
239    ) -> Result<Vec<ActivatedStoreDeviceRegistration>, StoreProtocolError> {
240        if self.registrations.len() != commit.device_registrations().len() {
241            return Err(StoreProtocolError::DeviceStateMismatch);
242        }
243        commit
244            .device_registrations()
245            .iter()
246            .zip(&self.registrations)
247            .map(|(activated, retained)| retained.verify(root, activated))
248            .collect()
249    }
250}
251
252impl RetainedStoreDeviceRegistrationActivation {
253    fn verify(
254        &self,
255        root: &StoreRootRef,
256        activated: &ActivatedStoreDeviceRegistrationRef,
257    ) -> Result<ActivatedStoreDeviceRegistration, StoreProtocolError> {
258        let registration = verify_retained_registration(
259            root,
260            &activated.registration,
261            &self.canonical_registration,
262        )?;
263        let registration = ReferencedStoreDeviceRegistration::verified(
264            activated.registration.clone(),
265            registration,
266        )?;
267        let registration =
268            ActivatedStoreDeviceRegistration::verified(registration, self.authority.clone())?;
269        registration.verify_reference(activated)?;
270        Ok(registration)
271    }
272}
273
274impl RetainedStoreDeviceOperations {
275    pub fn from_sources(
276        proposals: Vec<RetainedStoreDeviceExclusionProposal>,
277        outcomes: Vec<RetainedStoreDeviceExclusionOutcome>,
278    ) -> Self {
279        Self {
280            proposals,
281            outcomes,
282        }
283    }
284
285    pub fn verify_for(
286        &self,
287        root: &StoreRootRef,
288        commit: &StoreBatchCommit,
289    ) -> Result<VerifiedStoreDeviceOperations, StoreProtocolError> {
290        VerifiedStoreDeviceOperations::from_retained_sources(
291            root,
292            commit,
293            self.proposals.clone(),
294            self.outcomes.clone(),
295        )
296    }
297}
298
299impl RetainedStoreDeviceExclusionProposal {
300    pub fn from_exact(
301        reference: StoreDeviceExclusionProposalRef,
302        proposal: &StoreDeviceExclusionProposal,
303        target: &StoreDeviceRegistration,
304        owner: &StoreDeviceRegistration,
305    ) -> Result<Self, StoreProtocolError> {
306        let retained = Self {
307            reference,
308            canonical_proposal: proposal.to_bytes(),
309            canonical_target_registration: target.to_bytes(),
310            canonical_owner_registration: owner.to_bytes(),
311        };
312        let opened = retained.verify_with_registrations(&target.store_root)?;
313        if opened.object.value != *proposal || opened.target != *target || opened.owner != *owner {
314            return Err(StoreProtocolError::DeviceStateMismatch);
315        }
316        Ok(retained)
317    }
318
319    pub fn from_verified(proposal: &VerifiedDeviceExclusionProposal) -> Self {
320        Self {
321            reference: proposal.reference.clone(),
322            canonical_proposal: proposal.object.bytes.clone(),
323            canonical_target_registration: proposal.target.to_bytes(),
324            canonical_owner_registration: proposal.owner.to_bytes(),
325        }
326    }
327
328    pub fn reference(&self) -> &StoreDeviceExclusionProposalRef {
329        &self.reference
330    }
331
332    fn verify(
333        &self,
334        root: &StoreRootRef,
335    ) -> Result<StoreDeviceExclusionProposal, StoreProtocolError> {
336        self.verify_with_registrations(root)
337            .map(|proposal| proposal.object.value)
338    }
339
340    fn verify_with_registrations(
341        &self,
342        root: &StoreRootRef,
343    ) -> Result<VerifiedDeviceExclusionProposal, StoreProtocolError> {
344        self.reference.object.verify(&self.canonical_proposal)?;
345        let unverified: StoreDeviceExclusionProposal =
346            serde_json::from_slice(&self.canonical_proposal)?;
347        if unverified.to_bytes() != self.canonical_proposal {
348            return Err(StoreProtocolError::Malformed(
349                "retained Store device exclusion proposal is not canonically encoded".to_string(),
350            ));
351        }
352        let target = verify_retained_registration(
353            root,
354            &unverified.target,
355            &self.canonical_target_registration,
356        )?;
357        let owner = verify_retained_registration(
358            root,
359            &unverified.owner_registration,
360            &self.canonical_owner_registration,
361        )?;
362        let proposal = StoreDeviceExclusionProposal::parse_at(
363            &self.canonical_proposal,
364            &self.reference,
365            &target,
366            &owner,
367        )?;
368        Ok(VerifiedDeviceExclusionProposal {
369            reference: self.reference.clone(),
370            object: crate::objects::VerifiedObject {
371                value: proposal,
372                bytes: self.canonical_proposal.clone(),
373                semantic_hash: self.reference.proposal_hash,
374                object: self.reference.object.clone(),
375            },
376            target,
377            owner,
378        })
379    }
380}
381
382impl RetainedStoreDeviceExclusionOutcome {
383    pub fn from_exact(
384        reference: &StoreDeviceExclusionOutcomeRef,
385        proposal: RetainedStoreDeviceExclusionProposal,
386        outcome: &StoreDeviceExclusionOutcome,
387        owner: &StoreDeviceRegistration,
388    ) -> Result<Self, StoreProtocolError> {
389        if reference.proposal() != outcome.proposal()
390            || reference.outcome_hash() != outcome.outcome_hash()
391        {
392            return Err(StoreProtocolError::DeviceStateMismatch);
393        }
394        let canonical_outcome = outcome.to_bytes();
395        reference.object().verify(&canonical_outcome)?;
396        Ok(match (reference, outcome) {
397            (
398                StoreDeviceExclusionOutcomeRef::Excluded(reference),
399                StoreDeviceExclusionOutcome::Excluded(_),
400            ) => Self::Excluded {
401                reference: reference.clone(),
402                canonical_outcome,
403                proposal,
404                canonical_owner_registration: owner.to_bytes(),
405            },
406            (
407                StoreDeviceExclusionOutcomeRef::Cancelled(reference),
408                StoreDeviceExclusionOutcome::Cancelled(_),
409            ) => Self::Cancelled {
410                reference: reference.clone(),
411                canonical_outcome,
412                proposal,
413                canonical_owner_registration: owner.to_bytes(),
414            },
415            _ => return Err(StoreProtocolError::DeviceStateMismatch),
416        })
417    }
418
419    pub fn from_verified(
420        reference: &StoreDeviceExclusionOutcomeRef,
421        proposal: RetainedStoreDeviceExclusionProposal,
422        outcome: &VerifiedDeviceExclusionOutcome,
423    ) -> Result<Self, StoreProtocolError> {
424        match (reference, &outcome.object.value) {
425            (
426                StoreDeviceExclusionOutcomeRef::Excluded(reference),
427                StoreDeviceExclusionOutcome::Excluded(_),
428            ) => Ok(Self::Excluded {
429                reference: reference.clone(),
430                canonical_outcome: outcome.object.bytes.clone(),
431                proposal,
432                canonical_owner_registration: outcome.owner.to_bytes(),
433            }),
434            (
435                StoreDeviceExclusionOutcomeRef::Cancelled(reference),
436                StoreDeviceExclusionOutcome::Cancelled(_),
437            ) => Ok(Self::Cancelled {
438                reference: reference.clone(),
439                canonical_outcome: outcome.object.bytes.clone(),
440                proposal,
441                canonical_owner_registration: outcome.owner.to_bytes(),
442            }),
443            _ => Err(StoreProtocolError::DeviceStateMismatch),
444        }
445    }
446
447    pub fn wire_reference(&self) -> StoreDeviceExclusionOutcomeRef {
448        match self {
449            Self::Excluded { reference, .. } => {
450                StoreDeviceExclusionOutcomeRef::Excluded(reference.clone())
451            }
452            Self::Cancelled { reference, .. } => {
453                StoreDeviceExclusionOutcomeRef::Cancelled(reference.clone())
454            }
455        }
456    }
457
458    fn exclusion_reference(&self) -> &StoreDeviceExclusionRef {
459        match self {
460            Self::Excluded { reference, .. } => reference,
461            Self::Cancelled { .. } => unreachable!("verified exclusion changed variant"),
462        }
463    }
464
465    fn cancellation_reference(&self) -> &StoreDeviceExclusionCancellationRef {
466        match self {
467            Self::Cancelled { reference, .. } => reference,
468            Self::Excluded { .. } => unreachable!("verified cancellation changed variant"),
469        }
470    }
471
472    fn verify(
473        self,
474        root: &StoreRootRef,
475    ) -> Result<VerifiedStoreDeviceExclusionOutcome, StoreProtocolError> {
476        let (reference, canonical_outcome, proposal_source, canonical_owner_registration) =
477            match &self {
478                Self::Excluded {
479                    reference,
480                    canonical_outcome,
481                    proposal,
482                    canonical_owner_registration,
483                } => (
484                    StoreDeviceExclusionOutcomeRef::Excluded(reference.clone()),
485                    canonical_outcome,
486                    proposal,
487                    canonical_owner_registration,
488                ),
489                Self::Cancelled {
490                    reference,
491                    canonical_outcome,
492                    proposal,
493                    canonical_owner_registration,
494                } => (
495                    StoreDeviceExclusionOutcomeRef::Cancelled(reference.clone()),
496                    canonical_outcome,
497                    proposal,
498                    canonical_owner_registration,
499                ),
500            };
501        reference.object().verify(canonical_outcome)?;
502        let proposal = proposal_source.verify_with_registrations(root)?;
503        let unverified: StoreDeviceExclusionOutcome = serde_json::from_slice(canonical_outcome)?;
504        if unverified.to_bytes() != *canonical_outcome {
505            return Err(StoreProtocolError::Malformed(
506                "retained Store device exclusion outcome is not canonically encoded".to_string(),
507            ));
508        }
509        let owner_reference = match &unverified {
510            StoreDeviceExclusionOutcome::Excluded(exclusion) => &exclusion.owner_registration,
511            StoreDeviceExclusionOutcome::Cancelled(cancellation) => {
512                &cancellation.owner_registration
513            }
514        };
515        let owner =
516            verify_retained_registration(root, owner_reference, canonical_owner_registration)?;
517        let outcome = StoreDeviceExclusionOutcome::parse_at(
518            canonical_outcome,
519            &reference,
520            &proposal.object.value,
521            &proposal.target,
522            &owner,
523        )?;
524        match (&self, outcome) {
525            (Self::Excluded { .. }, StoreDeviceExclusionOutcome::Excluded(_)) => {
526                Ok(VerifiedStoreDeviceExclusionOutcome::Excluded(self))
527            }
528            (Self::Cancelled { .. }, StoreDeviceExclusionOutcome::Cancelled(_)) => {
529                Ok(VerifiedStoreDeviceExclusionOutcome::Cancelled(self))
530            }
531            _ => Err(StoreProtocolError::DeviceStateMismatch),
532        }
533    }
534}
535
536fn verify_retained_registration(
537    root: &StoreRootRef,
538    reference: &StoreDeviceRegistrationRef,
539    canonical_registration: &[u8],
540) -> Result<StoreDeviceRegistration, StoreProtocolError> {
541    reference.object.verify(canonical_registration)?;
542    let registration =
543        StoreDeviceRegistration::parse_at(canonical_registration, root, reference.device_id)?;
544    if registration.to_bytes() != canonical_registration {
545        return Err(StoreProtocolError::Malformed(
546            "retained Store device registration is not canonically encoded".to_string(),
547        ));
548    }
549    reference.verify_registration(&registration)?;
550    Ok(registration)
551}
552
553impl VerifiedStoreDeviceExclusionOutcome {
554    fn source(&self) -> &RetainedStoreDeviceExclusionOutcome {
555        match self {
556            Self::Excluded(source) | Self::Cancelled(source) => source,
557        }
558    }
559}