OXYGENHUB
◆ v0.4.4 oxygen / crates / oxy-core / src / repo / receipts.rs
6.4 KiB · Rust · last changed Approvals are kept in the repository: indexed by state, kept by gc 19 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
        self.store.put_receipt(&sr.receipt)?;
31
        let r = self.store.put_attest(&sr.attest)?;
32
        for sig in &sr.sigs {
33
            self.store.put_signature(&r, sig)?;
34
        }
35
        self.store.index_receipt(&sr.state(), &r)?;
36
        Ok(r)
37
    }
38
39
    /// The receipt named by attestation `r`, verified again from the store.
40
    pub fn receipt(&self, r: &ObjectRef) -> Result<ReceiptInfo> {
41
        let attest = self.store.get_attest(r)?;
42
        let AttestBody::Receipt { receipt, .. } = &attest.body else {
43
            return fail(format!("#{} is not a receipt", r.short(12)));
44
        };
45
        let receipt = self.store.get_receipt(receipt)?;
46
        let sigs = self.store.signatures(r)?;
47
        let Some(key) = sigs.first().map(|s| s.key) else {
48
            return fail(format!("receipt #{} has no signature", r.short(12)));
49
        };
50
        let runner = self.store.get_identity(&key)?;
51
        let signed = SignedReceipt {
52
            receipt,
53
            attest,
54
            sigs,
55
            runner,
56
        };
57
        let problem = signed.verify().err().map(|e| e.to_string());
58
        Ok(ReceiptInfo {
59
            attest: *r,
60
            revoked: self.is_revoked(&key)?,
61
            signed,
62
            problem,
63
        })
64
    }
65
66
    /// Every receipt kept for the state of `heads`, oldest first.
67
    pub fn receipts(&self, heads: &[ChangeHash]) -> Result<Vec<ReceiptInfo>> {
68
        let state = hash_object(
69
            ObjectType::State,
70
            &StateObject::new(heads.to_vec()).encode(),
71
        );
72
        let mut out = self
73
            .store
74
            .receipts_for(&state)?
75
            .iter()
76
            .map(|r| self.receipt(r))
77
            .collect::<Result<Vec<_>>>()?;
78
        out.sort_by_key(|i| i.signed.attest.hlc);
79
        Ok(out)
80
    }
81
82
    /// Receipts to keep through garbage collection: those whose state's
83
    /// heads `keep` holds, with their receipts, runners and outputs.
84
    /// Keep a signed approval (oxygenhub#235): verified, for this repository,
85
    /// with the signer's identity and indexed by the state it approves, so
86
    /// garbage collection keeps it while that state lives. Who may approve is
87
    /// the caller's rule.
88
    pub fn add_approval(&self, sa: &crate::approval::SignedApproval) -> Result<ObjectRef> {
89
        sa.verify()
90
            .map_err(|e| RepoError::Msg(format!("approval does not verify: {e}")))?;
91
        if sa.attest.repo != self.id()? {
92
            return fail("that approval is for another repository");
93
        }
94
        let state = sa
95
            .statement()
96
            .map_err(|e| RepoError::Msg(e.to_string()))?
97
            .state;
98
        self.store.put_identity(&sa.signer)?;
99
        let r = self.store.put_attest(&sa.attest)?;
100
        for sig in &sa.sigs {
101
            self.store.put_signature(&r, sig)?;
102
        }
103
        self.store.index_approval(&state, &r)?;
104
        Ok(r)
105
    }
106
107
    /// Approvals of states garbage collection keeps are kept, with their
108
    /// signatures and signers.
109
    pub(crate) fn reachable_approvals(&self, keep: &mut HashSet<ObjectRef>) -> Result<()> {
110
        for state in self.store.approval_states()? {
111
            if !keep.contains(&state) {
112
                let heads = match self.store.get_state(&state) {
113
                    Ok(s) => s.heads,
114
                    Err(StoreError::NotFound(_)) => vec![],
115
                    Err(e) => return Err(e.into()),
116
                };
117
                if !heads.iter().all(|h| keep.contains(h)) {
118
                    continue;
119
                }
120
                keep.insert(state);
121
            }
122
            for a in self.store.approvals_for(&state)? {
123
                keep.insert(a);
124
                for sig in self.store.signatures(&a)? {
125
                    keep.insert(sig.key);
126
                }
127
            }
128
        }
129
        Ok(())
130
    }
131
132
    pub(crate) fn reachable_receipts(&self, keep: &mut HashSet<ObjectRef>) -> Result<()> {
133
        for state in self.store.receipt_states()? {
134
            let heads = match self.store.get_state(&state) {
135
                Ok(s) => s.heads,
136
                // A receipt may arrive before its state object; its heads
137
                // are unknown, so keep it.
138
                Err(StoreError::NotFound(_)) => vec![],
139
                Err(e) => return Err(e.into()),
140
            };
141
            if !heads.iter().all(|h| keep.contains(h)) {
142
                continue;
143
            }
144
            keep.insert(state);
145
            for a in self.store.receipts_for(&state)? {
146
                keep.insert(a);
147
                for sig in self.store.signatures(&a)? {
148
                    keep.insert(sig.key);
149
                }
150
                let Ok(attest) = self.store.get_attest(&a) else {
151
                    continue;
152
                };
153
                let AttestBody::Receipt { receipt, .. } = attest.body else {
154
                    continue;
155
                };
156
                keep.insert(receipt);
157
                let Ok(r) = self.store.get_receipt(&receipt) else {
158
                    continue;
159
                };
160
                for b in r.outputs.iter().chain(&r.inputs) {
161
                    if keep.insert(*b) {
162
                        if let Ok(bytes) = self.store.get_typed(b, ObjectType::Blob) {
163
                            keep.extend(Blob::decode(&bytes)?.chunks);
164
                        }
165
                    }
166
                }
167
            }
168
        }
169
        Ok(())
170
    }
171
}