use crate::manifest::DensorManifest;
use crate::seal::{to_hex, CanonicalHasher};
use crate::stage::StageReceiptSummary;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeReceiptV1 {
pub pipeline_id: String,
pub stages: Vec<StageReceiptSummary>,
pub non_claim: String,
pub receipt_hash: String,
}
impl RuntimeReceiptV1 {
pub const NON_CLAIM: &'static str = "A deterministic record of which stages ran over which densors against \
which frozen authorities — NOT a claim that the result is correct, optimal, or meaningful in any domain; \
the runtime is a mechanism, the meaning lives in the authorities it cites.";
fn seal(
pipeline_id: &str,
manifest: &DensorManifest,
stages: &[StageReceiptSummary],
) -> String {
let mut h = CanonicalHasher::new();
h.field("schema", b"dsfb_densor_runtime_receipt_v1");
h.field("pipeline_id", pipeline_id.as_bytes());
for a in &manifest.authorities {
h.field("authority_name", a.name.as_bytes());
h.hash32("authority_hash", &a.hash);
}
for s in stages {
h.field("stage_id", s.stage_id.as_bytes());
h.hash32("input_hash", &s.input_hash);
h.hash32("output_hash", &s.output_hash);
for a in &s.authority_hashes {
h.field("stage_authority", a.name.as_bytes());
h.hash32("stage_authority_hash", &a.hash);
}
}
h.field("non_claim", Self::NON_CLAIM.as_bytes());
h.finalize_hex()
}
pub fn build(manifest: &DensorManifest, stages: Vec<StageReceiptSummary>) -> Self {
let receipt_hash = Self::seal(&manifest.pipeline_id, manifest, &stages);
RuntimeReceiptV1 {
pipeline_id: manifest.pipeline_id.clone(),
stages,
non_claim: Self::NON_CLAIM.to_string(),
receipt_hash,
}
}
pub fn verify(&self, manifest: &DensorManifest) -> bool {
self.non_claim == Self::NON_CLAIM
&& self.receipt_hash == Self::seal(&self.pipeline_id, manifest, &self.stages)
}
pub fn short(&self) -> &str {
if self.receipt_hash.len() >= 12 {
&self.receipt_hash[..12]
} else {
&self.receipt_hash
}
}
}
pub fn hex(h: &[u8; 32]) -> String {
to_hex(h)
}