OXYGENHUB

Coverage / crates/flow-core/src/receipt.rs 65.2%

45 of 69 lines run, 8 of 47 functions · run #185 on main · run missed

1
//! Execution receipts (spec §6.1), written by the host after a job ends,
2
//! never by the job itself.
3
4
use crate::canonical::{object_hash, Hash};
5
use oxy_core::cbor::{encode, Value};
6
use serde::{Deserialize, Serialize};
7
8
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
9
pub struct Receipt {
10
    pub job_name: String,
11
    #[serde(with = "hex32")]
12
    pub pipeline_hash: Hash,
13
    #[serde(with = "hex32")]
14
    pub scope_tree_hash: Hash,
15
    #[serde(with = "hex32s")]
16
    pub inputs: Vec<Hash>,
17
    #[serde(with = "hex32")]
18
    pub environment: Hash,
19
    #[serde(with = "hex32s")]
20
    pub outputs: Vec<Hash>,
21
    pub exit_code: u32,
22
    pub duration_ms: u64,
23
}
24
25
impl Receipt {
265×
    pub fn encode(&self) -> Vec<u8> {
2710×
        let refs = |v: &[Hash]| Value::Array(v.iter().map(|h| Value::Bytes(h.to_vec())).collect());
285×
        encode(&Value::map([
295×
            (0, Value::Uint(1)),
305×
            (1, Value::Text(self.job_name.clone())),
315×
            (2, Value::Bytes(self.pipeline_hash.to_vec())),
325×
            (3, Value::Bytes(self.scope_tree_hash.to_vec())),
335×
            (4, refs(&self.inputs)),
345×
            (5, Value::Bytes(self.environment.to_vec())),
355×
            (6, refs(&self.outputs)),
365×
            (7, Value::Uint(self.exit_code as u64)),
375×
            (8, Value::Uint(self.duration_ms)),
385×
        ]))
395×
    }
40
41
    /// `BLAKE3("oxy1 receipt " || len || 0x00 || body)`.
424×
    pub fn hash(&self) -> Hash {
434×
        object_hash("receipt", &self.encode())
444×
    }
45
46
    /// The same receipt as an Oxygen object (Oxygen spec §14.6), which a
47
    /// runner signs and a repository keeps.
482×
    pub fn to_oxy(&self) -> oxy_core::receipt::Receipt {
49
        use oxy_core::hash::ObjectRef;
50
        oxy_core::receipt::Receipt {
512×
            job_name: self.job_name.clone(),
522×
            pipeline_hash: ObjectRef(self.pipeline_hash),
532×
            scope_tree_hash: ObjectRef(self.scope_tree_hash),
542×
            inputs: self.inputs.iter().map(|h| ObjectRef(*h)).collect(),
552×
            environment: ObjectRef(self.environment),
562×
            outputs: self.outputs.iter().map(|h| ObjectRef(*h)).collect(),
572×
            exit_code: self.exit_code,
582×
            duration_ms: self.duration_ms,
59
        }
602×
    }
61
}
62
63
/// What a job ran in (the receipt's `environment`): the pinned pieces of
64
/// the isolation tier, the rootfs, argv and env.
65
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
66
pub struct Environment {
67
    pub runtime: String,
68
    /// Hashes of the tier's pinned parts (hypervisor, kernel, init), by name.
69
    pub parts: Vec<(String, String)>,
70
    pub rootfs: String,
71
    pub argv: Vec<String>,
72
    pub env: Vec<(String, String)>,
73
}
74
75
impl Environment {
760×
    pub fn hash(&self) -> Hash {
770×
        let t = |s: &str| Value::Text(s.to_string());
780×
        let pairs = |v: &[(String, String)]| Value::Array(v.iter().map(|(a, b)| Value::Array(vec![t(a), t(b)])).collect());
790×
        let body = encode(&Value::map([
800×
            (0, t(&self.runtime)),
810×
            (1, pairs(&self.parts)),
820×
            (2, t(&self.rootfs)),
830×
            (3, Value::Array(self.argv.iter().map(|a| t(a)).collect())),
840×
            (4, pairs(&self.env)),
85
        ]));
860×
        object_hash("environment", &body)
870×
    }
88
}
89
90
mod hex32 {
91
    use serde::{Deserialize, Deserializer, Serializer};
923×
    pub fn serialize<S: Serializer>(h: &[u8; 32], s: S) -> Result<S::Ok, S::Error> {
933×
        s.serialize_str(&hex::encode(h))
943×
    }
953×
    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<[u8; 32], D::Error> {
963×
        let s = String::deserialize(d)?;
973×
        let v = hex::decode(&s).map_err(serde::de::Error::custom)?;
983×
        v.try_into().map_err(|_| serde::de::Error::custom("expected 32 bytes"))
993×
    }
100
}
101
102
mod hex32s {
103
    use serde::{Deserialize, Deserializer, Serializer};
1042×
    pub fn serialize<S: Serializer>(v: &[[u8; 32]], s: S) -> Result<S::Ok, S::Error> {
1052×
        s.collect_seq(v.iter().map(hex::encode))
1062×
    }
1072×
    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<[u8; 32]>, D::Error> {
1082×
        let v = Vec::<String>::deserialize(d)?;
1092×
        v.into_iter()
1102×
            .map(|s| {
1110×
                let b = hex::decode(&s).map_err(serde::de::Error::custom)?;
1120×
                b.try_into().map_err(|_| serde::de::Error::custom("expected 32 bytes"))
1130×
            })
1142×
            .collect()
1152×
    }
116
}