use super::{
CapturedArtifact, ResolvedCargoBuildInputs, WasmBuildOutcome, WasmBuildSpec,
build_wasm_canisters_cached, resolve_cargo_build_inputs,
};
use ic_host_artifacts::artifact::hash_reader;
use ic_host_process::child::OwnedChild;
use serde::Serialize;
use std::{fs, io, path::Path, process::Command};
#[derive(Debug)]
pub struct WasmBuildEvidence {
spec: WasmBuildSpec,
inputs: ResolvedCargoBuildInputs,
build: WasmBuildOutcome,
}
#[derive(Debug, Serialize)]
pub struct WasmArtifactReceipt {
pub wasm_sha256: String,
pub build_fingerprint: String,
pub input_digest: String,
pub validation_digest: String,
}
impl WasmBuildEvidence {
pub fn acquire(
spec: WasmBuildSpec,
spawn: impl FnMut(&mut Command) -> io::Result<OwnedChild>,
) -> io::Result<Self> {
let inputs = resolve_cargo_build_inputs(&spec).map_err(io::Error::other)?;
let build = build_wasm_canisters_cached(&spec, spawn).map_err(io::Error::other)?;
let evidence = Self {
spec,
inputs,
build,
};
evidence.verify_current()?;
Ok(evidence)
}
pub fn verify_current(&self) -> io::Result<()> {
let current = resolve_cargo_build_inputs(&self.spec).map_err(io::Error::other)?;
if self.build.record().fingerprint() != self.inputs.fingerprint()
|| current.fingerprint() != self.inputs.fingerprint()
|| current.validation_digest() != self.inputs.validation_digest()
{
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"build inputs changed",
));
}
Ok(())
}
pub fn export_artifact(
&self,
index: usize,
destination: &Path,
maximum_bytes: usize,
) -> io::Result<WasmArtifactReceipt> {
self.verify_current()?;
let record = self.build.record();
let source = record.artifacts().get(index).ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "artifact index out of range")
})?;
let parent = destination.parent().ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidInput, "missing destination parent")
})?;
if parent
.canonicalize()?
.starts_with(record.exact_cache_path().canonicalize()?)
{
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"destination is inside retained cache",
));
}
let captured = CapturedArtifact::capture(source, parent, maximum_bytes)?;
let identity = hash_reader(fs::File::open(captured.path())?, maximum_bytes as u64)
.map_err(io::Error::from)?;
self.verify_current()?;
captured.retain_to(destination).map_err(io::Error::other)?;
Ok(WasmArtifactReceipt {
wasm_sha256: identity.sha256.to_string(),
build_fingerprint: record.fingerprint().to_string(),
input_digest: self.inputs.input_digest().to_string(),
validation_digest: self.inputs.validation_digest().to_string(),
})
}
}
#[cfg(all(test, unix))]
mod tests;