OXYGENHUB

Coverage / crates/oxy-core/src/repo/receipts.rs 84.6%

110 of 130 lines run, 17 of 26 functions · run #421 on main · run missed

1
//! Execution receipts in a repository (spec §14.6): kept beside the state
2
//! they were made on, with the runner's identity and signature.
3
4
use super::*;
5
use crate::receipt::SignedReceipt;
6
7
/// A stored receipt and what verifying it found.
8
#[derive(Clone, Debug)]
9
pub struct ReceiptInfo {
10
    /// The receipt attestation's ObjectRef, which names it.
11
    pub attest: ObjectRef,
12
    pub signed: SignedReceipt,
13
    /// Why it does not verify, if it does not.
14
    pub problem: Option<String>,
15
    /// The runner's key was revoked (spec §14.2).
16
    pub revoked: bool,
17
}
18
19
impl Repo {
20
    /// Verify `sr` and keep it: the receipt, the attestation with its
21
    /// signatures, and the runner's identity. Returns the attestation's
22
    /// ObjectRef. Receipts for another repository are refused.
236×
    pub fn add_receipt(&self, sr: &SignedReceipt) -> Result<ObjectRef> {
246×
        sr.verify()
256×
            .map_err(|e| RepoError::Msg(format!("receipt does not verify: {e}")))?;
264×
        if sr.attest.repo != self.id()? {
271×
            return fail("that receipt is for another repository");
283×
        }
293×
        self.store.put_identity(&sr.runner)?;
303×
        if let Some(c) = &sr.certificate {
311×
            self.store.put_identity(&c.issuer)?;
321×
            self.store.put_certificate(&c.cert)?;
332×
        }
343×
        self.store.put_receipt(&sr.receipt)?;
353×
        let r = self.store.put_attest(&sr.attest)?;
363×
        for sig in &sr.sigs {
373×
            self.store.put_signature(&r, sig)?;
38
        }
393×
        self.store.index_receipt(&sr.state(), &r)?;
403×
        Ok(r)
416×
    }
42
43
    /// The receipt named by attestation `r`, verified again from the store.
4410×
    pub fn receipt(&self, r: &ObjectRef) -> Result<ReceiptInfo> {
4510×
        let attest = self.store.get_attest(r)?;
4610×
        let AttestBody::Receipt { receipt, .. } = &attest.body else {
470×
            return fail(format!("#{} is not a receipt", r.short(12)));
48
        };
4910×
        let receipt = self.store.get_receipt(receipt)?;
5010×
        let sigs = self.store.signatures(r)?;
5110×
        let Some(key) = sigs.first().map(|s| s.key) else {
520×
            return fail(format!("receipt #{} has no signature", r.short(12)));
53
        };
5410×
        let runner = self.store.get_identity(&key)?;
55
        // Signed by a short-lived key: its certificate and issuer too.
5610×
        let certificate = match self.store.certificate_of(&key)? {
572×
            Some(c) => {
582×
                let cert = self.store.get_certificate(&c)?;
592×
                let issuer = self.store.get_identity(&cert.cert.issuer)?;
602×
                Some(crate::receipt::CertifiedBy { cert, issuer })
61
            }
628×
            None => None,
63
        };
6410×
        let signed = SignedReceipt {
6510×
            receipt,
6610×
            attest,
6710×
            sigs,
6810×
            runner,
6910×
            certificate,
7010×
        };
7110×
        let problem = signed.verify().err().map(|e| e.to_string());
72
        Ok(ReceiptInfo {
7310×
            attest: *r,
7410×
            revoked: self.is_revoked(&key)?,
7510×
            signed,
7610×
            problem,
77
        })
7810×
    }
79
80
    /// Every receipt kept for the state of `heads`, oldest first.
8110×
    pub fn receipts(&self, heads: &[ChangeHash]) -> Result<Vec<ReceiptInfo>> {
8210×
        let state = hash_object(
8310×
            ObjectType::State,
8410×
            &StateObject::new(heads.to_vec()).encode(),
85
        );
8610×
        let mut out = self
8710×
            .store
8810×
            .receipts_for(&state)?
8910×
            .iter()
9010×
            .map(|r| self.receipt(r))
9110×
            .collect::<Result<Vec<_>>>()?;
9210×
        out.sort_by_key(|i| i.signed.attest.hlc);
9310×
        Ok(out)
9410×
    }
95
96
    /// Receipts to keep through garbage collection: those whose state's
97
    /// heads `keep` holds, with their receipts, runners and outputs.
98
    /// Keep a signed approval (oxygenhub#235): verified, for this repository,
99
    /// with the signer's identity and indexed by the state it approves, so
100
    /// garbage collection keeps it while that state lives. Who may approve is
101
    /// the caller's rule.
1023×
    pub fn add_approval(&self, sa: &crate::approval::SignedApproval) -> Result<ObjectRef> {
1033×
        sa.verify()
1043×
            .map_err(|e| RepoError::Msg(format!("approval does not verify: {e}")))?;
1052×
        if sa.attest.repo != self.id()? {
1061×
            return fail("that approval is for another repository");
1071×
        }
1081×
        let state = sa
1091×
            .statement()
1101×
            .map_err(|e| RepoError::Msg(e.to_string()))?
111
            .state;
1121×
        self.store.put_identity(&sa.signer)?;
1131×
        let r = self.store.put_attest(&sa.attest)?;
1141×
        for sig in &sa.sigs {
1151×
            self.store.put_signature(&r, sig)?;
116
        }
1171×
        self.store.index_approval(&state, &r)?;
1181×
        Ok(r)
1193×
    }
120
121
    /// Approvals of states garbage collection keeps are kept, with their
122
    /// signatures and signers.
12326×
    pub(crate) fn reachable_approvals(&self, keep: &mut HashSet<ObjectRef>) -> Result<()> {
12426×
        for state in self.store.approval_states()? {
1251×
            if !keep.contains(&state) {
1260×
                let heads = match self.store.get_state(&state) {
1270×
                    Ok(s) => s.heads,
1280×
                    Err(StoreError::NotFound(_)) => vec![],
1290×
                    Err(e) => return Err(e.into()),
130
                };
1310×
                if !heads.iter().all(|h| keep.contains(h)) {
1320×
                    continue;
1330×
                }
1340×
                keep.insert(state);
1351×
            }
1361×
            for a in self.store.approvals_for(&state)? {
1371×
                keep.insert(a);
1381×
                for sig in self.store.signatures(&a)? {
1391×
                    keep.insert(sig.key);
1401×
                }
141
            }
142
        }
14326×
        Ok(())
14426×
    }
145
14626×
    pub(crate) fn reachable_receipts(&self, keep: &mut HashSet<ObjectRef>) -> Result<()> {
14726×
        for state in self.store.receipt_states()? {
1481×
            let heads = match self.store.get_state(&state) {
1491×
                Ok(s) => s.heads,
150
                // A receipt may arrive before its state object; its heads
151
                // are unknown, so keep it.
1520×
                Err(StoreError::NotFound(_)) => vec![],
1530×
                Err(e) => return Err(e.into()),
154
            };
1551×
            if !heads.iter().all(|h| keep.contains(h)) {
1560×
                continue;
1571×
            }
1581×
            keep.insert(state);
1592×
            for a in self.store.receipts_for(&state)? {
1602×
                keep.insert(a);
1612×
                for sig in self.store.signatures(&a)? {
1622×
                    self.keep_signer(keep, sig.key)?;
163
                }
1642×
                let Ok(attest) = self.store.get_attest(&a) else {
1650×
                    continue;
166
                };
1672×
                let AttestBody::Receipt { receipt, .. } = attest.body else {
1680×
                    continue;
169
                };
1702×
                keep.insert(receipt);
1712×
                let Ok(r) = self.store.get_receipt(&receipt) else {
1720×
                    continue;
173
                };
1742×
                for b in r.outputs.iter().chain(&r.inputs) {
1752×
                    if keep.insert(*b) {
1761×
                        if let Ok(bytes) = self.store.get_typed(b, ObjectType::Blob) {
1771×
                            keep.extend(Blob::decode(&bytes)?.chunks);
1780×
                        }
1791×
                    }
180
                }
181
            }
182
        }
18326×
        Ok(())
18426×
    }
185
}