use std::{
fs::File as SyncFile,
io,
path::{Path, PathBuf},
};
use ahash::HashMap;
use anyhow::{Context, bail};
use blake2b_simd::{Hash, State as Blake2b};
use cid::Cid;
use serde::{Deserialize, Serialize};
use tracing::{debug, warn};
const PROOF_DIGEST_LEN: usize = 16;
const FOREST_FORCE_TRUST_PARAMS_ENV: &str = "FOREST_FORCE_TRUST_PARAMS";
pub(super) const PROOFS_PARAMETER_CACHE_ENV: &str = "FIL_PROOFS_PARAMETER_CACHE";
const PARAM_DIR: &str = "filecoin-proof-parameters";
pub(super) const DEFAULT_PARAMETERS: &str = include_str!("./parameters.json");
pub(super) type ParameterMap = HashMap<String, ParameterData>;
#[derive(Debug, Deserialize, Serialize, Clone)]
pub(super) struct ParameterData {
#[serde(with = "crate::lotus_json::stringify")]
pub cid: Cid,
#[serde(with = "hex::serde")]
pub digest: [u8; PROOF_DIGEST_LEN],
pub sector_size: u64,
}
pub(super) async fn check_parameter_file(path: &Path, info: &ParameterData) -> anyhow::Result<()> {
crate::def_is_env_truthy!(force_trust_params, FOREST_FORCE_TRUST_PARAMS_ENV);
if force_trust_params() {
warn!("Assuming parameter files are okay. Do not use in production!");
return Ok(());
}
let hash = tokio::task::spawn_blocking({
let mut reader = std::io::BufReader::new(SyncFile::open(path)?);
let mut hasher = Blake2b::new();
move || -> io::Result<Hash> {
std::io::copy(&mut reader, &mut hasher)?;
Ok(hasher.finalize())
}
})
.await??;
let hash_chunk = hash
.as_bytes()
.get(..PROOF_DIGEST_LEN)
.context("invalid digest length")?;
if info.digest == hash_chunk {
debug!("Parameter file {:?} is ok", path);
Ok(())
} else {
bail!(
"Checksum mismatch in param file {:?}. ({:x?} != {:x?})",
path,
hash_chunk,
info.digest,
)
}
}
pub(super) fn param_dir(data_dir: &Path) -> PathBuf {
std::env::var(PROOFS_PARAMETER_CACHE_ENV)
.ok()
.and_then(|v| {
if v.is_empty() {
None
} else {
Some(PathBuf::from(v))
}
})
.unwrap_or_else(|| data_dir.join(PARAM_DIR))
}
fn set_proofs_parameter_cache_dir_env(data_dir: &Path) {
unsafe { std::env::set_var(PROOFS_PARAMETER_CACHE_ENV, param_dir(data_dir)) };
}
pub fn maybe_set_proofs_parameter_cache_dir_env(data_dir: &Path) {
if std::env::var(PROOFS_PARAMETER_CACHE_ENV).is_err() {
set_proofs_parameter_cache_dir_env(data_dir);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_proof_file_check() {
let tempfile = tempfile::Builder::new().tempfile().unwrap();
let path = tempfile.path();
let data = b"Cthulhu fhtagn!";
std::fs::write(path, data).unwrap();
let mut hasher = Blake2b::new();
hasher.update(data);
let digest = hasher
.finalize()
.as_bytes()
.get(..PROOF_DIGEST_LEN)
.unwrap()
.to_owned();
let param_data = ParameterData {
cid: Cid::default(),
digest: digest.try_into().unwrap(),
sector_size: 32,
};
check_parameter_file(path, ¶m_data).await.unwrap()
}
#[tokio::test]
async fn test_proof_file_check_no_file() {
let param_data = ParameterData {
cid: Cid::default(),
digest: [0; PROOF_DIGEST_LEN],
sector_size: 32,
};
let path = Path::new("cthulhuazathoh.dagon");
let ret = check_parameter_file(path, ¶m_data).await;
assert_eq!(
ret.unwrap_err().downcast_ref::<io::Error>().unwrap().kind(),
io::ErrorKind::NotFound
);
}
}