ic_build/artifacts/evidence/
mod.rs1use super::{
3 CapturedArtifact, ResolvedCargoBuildInputs, WasmBuildOutcome, WasmBuildSpec,
4 build_wasm_canisters_cached, resolve_cargo_build_inputs,
5};
6use ic_host_artifacts::artifact::hash_reader;
7use ic_host_process::child::OwnedChild;
8use serde::Serialize;
9use std::{fs, io, path::Path, process::Command};
10
11#[derive(Debug)]
18pub struct WasmBuildEvidence {
19 spec: WasmBuildSpec,
20 inputs: ResolvedCargoBuildInputs,
21 build: WasmBuildOutcome,
22}
23
24#[derive(Debug, Serialize)]
26pub struct WasmArtifactReceipt {
27 pub wasm_sha256: String,
29 pub build_fingerprint: String,
31 pub input_digest: String,
33 pub validation_digest: String,
35}
36
37impl WasmBuildEvidence {
38 pub fn acquire(
46 spec: WasmBuildSpec,
47 spawn: impl FnMut(&mut Command) -> io::Result<OwnedChild>,
48 ) -> io::Result<Self> {
49 let inputs = resolve_cargo_build_inputs(&spec).map_err(io::Error::other)?;
50 let build = build_wasm_canisters_cached(&spec, spawn).map_err(io::Error::other)?;
51 let evidence = Self {
52 spec,
53 inputs,
54 build,
55 };
56 evidence.verify_current()?;
57 Ok(evidence)
58 }
59
60 pub fn verify_current(&self) -> io::Result<()> {
65 let current = resolve_cargo_build_inputs(&self.spec).map_err(io::Error::other)?;
66 if self.build.record().fingerprint() != self.inputs.fingerprint()
67 || current.fingerprint() != self.inputs.fingerprint()
68 || current.validation_digest() != self.inputs.validation_digest()
69 {
70 return Err(io::Error::new(
71 io::ErrorKind::InvalidData,
72 "build inputs changed",
73 ));
74 }
75 Ok(())
76 }
77
78 pub fn export_artifact(
91 &self,
92 index: usize,
93 destination: &Path,
94 maximum_bytes: usize,
95 ) -> io::Result<WasmArtifactReceipt> {
96 self.verify_current()?;
97 let record = self.build.record();
98 let source = record.artifacts().get(index).ok_or_else(|| {
99 io::Error::new(io::ErrorKind::InvalidInput, "artifact index out of range")
100 })?;
101 let parent = destination.parent().ok_or_else(|| {
102 io::Error::new(io::ErrorKind::InvalidInput, "missing destination parent")
103 })?;
104 if parent
105 .canonicalize()?
106 .starts_with(record.exact_cache_path().canonicalize()?)
107 {
108 return Err(io::Error::new(
109 io::ErrorKind::InvalidInput,
110 "destination is inside retained cache",
111 ));
112 }
113 let captured = CapturedArtifact::capture(source, parent, maximum_bytes)?;
114 let identity = hash_reader(fs::File::open(captured.path())?, maximum_bytes as u64)
115 .map_err(io::Error::from)?;
116 self.verify_current()?;
117 captured.retain_to(destination).map_err(io::Error::other)?;
118 Ok(WasmArtifactReceipt {
119 wasm_sha256: identity.sha256.to_string(),
120 build_fingerprint: record.fingerprint().to_string(),
121 input_digest: self.inputs.input_digest().to_string(),
122 validation_digest: self.inputs.validation_digest().to_string(),
123 })
124 }
125}
126
127#[cfg(all(test, unix))]
128mod tests;