OXYGENHUB

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

71 of 82 lines run, 12 of 16 functions · run #257 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.
235×
    pub fn add_receipt(&self, sr: &SignedReceipt) -> Result<ObjectRef> {
245×
        sr.verify()
255×
            .map_err(|e| RepoError::Msg(format!("receipt does not verify: {e}")))?;
263×
        if sr.attest.repo != self.id()? {
271×
            return fail("that receipt is for another repository");
282×
        }
292×
        self.store.put_identity(&sr.runner)?;
302×
        self.store.put_receipt(&sr.receipt)?;
312×
        let r = self.store.put_attest(&sr.attest)?;
322×
        for sig in &sr.sigs {
332×
            self.store.put_signature(&r, sig)?;
34
        }
352×
        self.store.index_receipt(&sr.state(), &r)?;
362×
        Ok(r)
375×
    }
38
39
    /// The receipt named by attestation `r`, verified again from the store.
405×
    pub fn receipt(&self, r: &ObjectRef) -> Result<ReceiptInfo> {
415×
        let attest = self.store.get_attest(r)?;
425×
        let AttestBody::Receipt { receipt, .. } = &attest.body else {
430×
            return fail(format!("#{} is not a receipt", r.short(12)));
44
        };
455×
        let receipt = self.store.get_receipt(receipt)?;
465×
        let sigs = self.store.signatures(r)?;
475×
        let Some(key) = sigs.first().map(|s| s.key) else {
480×
            return fail(format!("receipt #{} has no signature", r.short(12)));
49
        };
505×
        let runner = self.store.get_identity(&key)?;
515×
        let signed = SignedReceipt {
525×
            receipt,
535×
            attest,
545×
            sigs,
555×
            runner,
565×
        };
575×
        let problem = signed.verify().err().map(|e| e.to_string());
58
        Ok(ReceiptInfo {
595×
            attest: *r,
605×
            revoked: self.is_revoked(&key)?,
615×
            signed,
625×
            problem,
63
        })
645×
    }
65
66
    /// Every receipt kept for the state of `heads`, oldest first.
677×
    pub fn receipts(&self, heads: &[ChangeHash]) -> Result<Vec<ReceiptInfo>> {
687×
        let state = hash_object(
697×
            ObjectType::State,
707×
            &StateObject::new(heads.to_vec()).encode(),
71
        );
727×
        let mut out = self
737×
            .store
747×
            .receipts_for(&state)?
757×
            .iter()
767×
            .map(|r| self.receipt(r))
777×
            .collect::<Result<Vec<_>>>()?;
787×
        out.sort_by_key(|i| i.signed.attest.hlc);
797×
        Ok(out)
807×
    }
81
82
    /// Receipts to keep through garbage collection: those whose state's
83
    /// heads `keep` holds, with their receipts, runners and outputs.
8412×
    pub(crate) fn reachable_receipts(&self, keep: &mut HashSet<ObjectRef>) -> Result<()> {
8512×
        for state in self.store.receipt_states()? {
861×
            let heads = match self.store.get_state(&state) {
871×
                Ok(s) => s.heads,
88
                // A receipt may arrive before its state object; its heads
89
                // are unknown, so keep it.
900×
                Err(StoreError::NotFound(_)) => vec![],
910×
                Err(e) => return Err(e.into()),
92
            };
931×
            if !heads.iter().all(|h| keep.contains(h)) {
940×
                continue;
951×
            }
961×
            keep.insert(state);
971×
            for a in self.store.receipts_for(&state)? {
981×
                keep.insert(a);
991×
                for sig in self.store.signatures(&a)? {
1001×
                    keep.insert(sig.key);
1011×
                }
1021×
                let Ok(attest) = self.store.get_attest(&a) else {
1030×
                    continue;
104
                };
1051×
                let AttestBody::Receipt { receipt, .. } = attest.body else {
1060×
                    continue;
107
                };
1081×
                keep.insert(receipt);
1091×
                let Ok(r) = self.store.get_receipt(&receipt) else {
1100×
                    continue;
111
                };
1121×
                for b in r.outputs.iter().chain(&r.inputs) {
1131×
                    if keep.insert(*b) {
1141×
                        if let Ok(bytes) = self.store.get_typed(b, ObjectType::Blob) {
1151×
                            keep.extend(Blob::decode(&bytes)?.chunks);
1160×
                        }
1170×
                    }
118
                }
119
            }
120
        }
12112×
        Ok(())
12212×
    }
123
}