OXYGENHUB
# e5f8eb2a2006 oxygen / crates / oxy-core / src / repo / receipts.rs
5.1 KiB · Rust · last changed The issuer countersigns when it accepted a certified update (keyless step 4c) 18 days ago source blame history raw
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.
23
    pub fn add_receipt(&self, sr: &SignedReceipt) -> Result<ObjectRef> {
24
        sr.verify()
25
            .map_err(|e| RepoError::Msg(format!("receipt does not verify: {e}")))?;
26
        if sr.attest.repo != self.id()? {
27
            return fail("that receipt is for another repository");
28
        }
29
        self.store.put_identity(&sr.runner)?;
30
        if let Some(c) = &sr.certificate {
31
            self.store.put_identity(&c.issuer)?;
32
            self.store.put_certificate(&c.cert)?;
33
        }
34
        self.store.put_receipt(&sr.receipt)?;
35
        let r = self.store.put_attest(&sr.attest)?;
36
        for sig in &sr.sigs {
37
            self.store.put_signature(&r, sig)?;
38
        }
39
        self.store.index_receipt(&sr.state(), &r)?;
40
        Ok(r)
41
    }
42
43
    /// The receipt named by attestation `r`, verified again from the store.
44
    pub fn receipt(&self, r: &ObjectRef) -> Result<ReceiptInfo> {
45
        let attest = self.store.get_attest(r)?;
46
        let AttestBody::Receipt { receipt, .. } = &attest.body else {
47
            return fail(format!("#{} is not a receipt", r.short(12)));
48
        };
49
        let receipt = self.store.get_receipt(receipt)?;
50
        let sigs = self.store.signatures(r)?;
51
        let Some(key) = sigs.first().map(|s| s.key) else {
52
            return fail(format!("receipt #{} has no signature", r.short(12)));
53
        };
54
        let runner = self.store.get_identity(&key)?;
55
        // Signed by a short-lived key: its certificate and issuer too.
56
        let certificate = match self.store.certificate_of(&key)? {
57
            Some(c) => {
58
                let cert = self.store.get_certificate(&c)?;
59
                let issuer = self.store.get_identity(&cert.cert.issuer)?;
60
                Some(crate::receipt::CertifiedBy { cert, issuer })
61
            }
62
            None => None,
63
        };
64
        let signed = SignedReceipt {
65
            receipt,
66
            attest,
67
            sigs,
68
            runner,
69
            certificate,
70
        };
71
        let problem = signed.verify().err().map(|e| e.to_string());
72
        Ok(ReceiptInfo {
73
            attest: *r,
74
            revoked: self.is_revoked(&key)?,
75
            signed,
76
            problem,
77
        })
78
    }
79
80
    /// Every receipt kept for the state of `heads`, oldest first.
81
    pub fn receipts(&self, heads: &[ChangeHash]) -> Result<Vec<ReceiptInfo>> {
82
        let state = hash_object(
83
            ObjectType::State,
84
            &StateObject::new(heads.to_vec()).encode(),
85
        );
86
        let mut out = self
87
            .store
88
            .receipts_for(&state)?
89
            .iter()
90
            .map(|r| self.receipt(r))
91
            .collect::<Result<Vec<_>>>()?;
92
        out.sort_by_key(|i| i.signed.attest.hlc);
93
        Ok(out)
94
    }
95
96
    /// Receipts to keep through garbage collection: those whose state's
97
    /// heads `keep` holds, with their receipts, runners and outputs.
98
    pub(crate) fn reachable_receipts(&self, keep: &mut HashSet<ObjectRef>) -> Result<()> {
99
        for state in self.store.receipt_states()? {
100
            let heads = match self.store.get_state(&state) {
101
                Ok(s) => s.heads,
102
                // A receipt may arrive before its state object; its heads
103
                // are unknown, so keep it.
104
                Err(StoreError::NotFound(_)) => vec![],
105
                Err(e) => return Err(e.into()),
106
            };
107
            if !heads.iter().all(|h| keep.contains(h)) {
108
                continue;
109
            }
110
            keep.insert(state);
111
            for a in self.store.receipts_for(&state)? {
112
                keep.insert(a);
113
                for sig in self.store.signatures(&a)? {
114
                    self.keep_signer(keep, sig.key)?;
115
                }
116
                let Ok(attest) = self.store.get_attest(&a) else {
117
                    continue;
118
                };
119
                let AttestBody::Receipt { receipt, .. } = attest.body else {
120
                    continue;
121
                };
122
                keep.insert(receipt);
123
                let Ok(r) = self.store.get_receipt(&receipt) else {
124
                    continue;
125
                };
126
                for b in r.outputs.iter().chain(&r.inputs) {
127
                    if keep.insert(*b) {
128
                        if let Ok(bytes) = self.store.get_typed(b, ObjectType::Blob) {
129
                            keep.extend(Blob::decode(&bytes)?.chunks);
130
                        }
131
                    }
132
                }
133
            }
134
        }
135
        Ok(())
136
    }
137
}