use crate::imp::core::datasection::DIGS_DATA_OFFSET;
use crate::imp::core::merkle::MerkleTree;
use crate::imp::core::serving::concat_output;
use crate::imp::core::{
AuthenticationInfo, Bytes32, Bytes48, CompilationResult, CompilationStats, MetadataManifest,
TrustedHostKey,
};
use sha2::{Digest, Sha256};
use crate::imp::compiler::atomic_write::atomic_write_module;
use crate::imp::compiler::config::{CompilerConfig, CompilerStats, COMPILER_VERSION};
use crate::imp::compiler::data_section::{encode_data_section, DataSectionInputs};
use crate::imp::compiler::error::{CompilerError, Result};
use crate::imp::compiler::filler::deterministic_filler;
use crate::imp::compiler::inject::inject_data_section;
use crate::imp::compiler::key_table::{build_chunk_index_and_key_table, GenerationView};
use crate::imp::compiler::obfuscate::obfuscate;
use crate::imp::compiler::template::{
assert_host_imports, assert_memory_ceiling, baked_template_bytes, load_template,
};
pub const DATA_SECTION_MEM_OFFSET: u32 = DIGS_DATA_OFFSET;
#[derive(Debug, Clone)]
pub struct CompileOutcome {
pub result: CompilationResult,
pub detail: CompilerStats,
}
pub struct Compiler;
impl Compiler {
#[allow(clippy::too_many_arguments)]
pub fn compile<G: GenerationView>(
config: &CompilerConfig,
store_id: Bytes32,
store_pubkey: Bytes48,
generations: &[G],
manifest: MetadataManifest,
auth_info: AuthenticationInfo,
trusted_keys: &[TrustedHostKey],
chain_state: Option<crate::imp::core::datasection::ChainState>,
public_manifest: Option<crate::imp::core::PublicManifest>,
) -> Result<CompileOutcome> {
if trusted_keys.is_empty() {
return Err(CompilerError::NoTrustedKeys);
}
if generations.is_empty() {
return Err(CompilerError::GenerationLoad("no generations".into()));
}
let (chunk_index, key_table) = build_chunk_index_and_key_table(generations);
key_table.verify_against(chunk_index.len() as u32)?;
let store_roothash = generations.last().unwrap().root();
let root_history: Vec<Bytes32> = generations.iter().map(|g| g.root()).collect();
let merkle_leaves = current_generation_leaves(generations.last().unwrap());
let current_root = MerkleTree::from_leaves(merkle_leaves.clone()).root();
let chunk_pool_bodies: Vec<Vec<u8>> =
chunk_index.bodies_in_order().map(|b| b.to_vec()).collect();
let total_content_bytes: u64 = chunk_pool_bodies.iter().map(|b| b.len() as u64).sum();
let mut inputs = DataSectionInputs {
store_id,
current_root,
root_history,
store_pubkey,
trusted_keys: trusted_keys.to_vec(),
manifest,
auth_info,
key_table: key_table.entries().to_vec(),
chunk_pool_bodies,
merkle_leaves,
filler: Vec::new(),
chain_state,
public_manifest,
};
let blob_len_without_filler = encode_data_section(&inputs).len();
if blob_len_without_filler > config.uniform_blob_len {
return Err(CompilerError::Validation(
"content exceeds the uniform-size budget".into(),
));
}
let filler_len = config
.uniform_blob_len
.saturating_sub(blob_len_without_filler);
inputs.filler = deterministic_filler(&store_id, &store_roothash, filler_len);
let data_blob = encode_data_section(&inputs);
let template_bytes = match &config.template_override {
Some(b) => b.clone(),
None => baked_template_bytes().to_vec(),
};
load_template(&template_bytes)?;
let mut module = inject_data_section(&template_bytes, &data_blob, DATA_SECTION_MEM_OFFSET)?;
let obfuscation_applied = config.obfuscate;
if obfuscation_applied {
module = obfuscate(&module)?;
}
load_template(&module)?;
assert_memory_ceiling(&module)?;
assert_host_imports(&module)?;
let output_path =
atomic_write_module(&config.output_dir, &store_id, &store_roothash, &module)?;
let output_size = std::fs::metadata(&output_path)?.len();
let core_stats = CompilationStats {
chunk_count: chunk_index.len() as u64,
total_bytes: total_content_bytes,
generation_count: generations.len() as u64,
};
let detail = CompilerStats {
generation_count: generations.len() as u32,
unique_chunk_count: chunk_index.len() as u32,
resource_count: key_table.entries().len() as u32,
pool_byte_len: total_content_bytes,
data_section_byte_len: data_blob.len() as u64,
obfuscation_applied,
compiler_version: COMPILER_VERSION.to_string(),
};
let result = CompilationResult {
store_id,
roothash: store_roothash,
output_path,
output_size,
stats: core_stats,
};
Ok(CompileOutcome { result, detail })
}
}
fn current_generation_leaves<G: GenerationView>(current: &G) -> Vec<Bytes32> {
let mut keyed: Vec<([u8; 32], Bytes32)> = current
.resources()
.iter()
.map(|r| {
let bodies: Vec<Vec<u8>> = r.chunks().into_iter().map(|(_, body)| body).collect();
let slices: Vec<&[u8]> = bodies.iter().map(|b| b.as_slice()).collect();
let blob = concat_output(&slices);
let mut h = Sha256::new();
h.update(&blob);
let mut leaf = [0u8; 32];
leaf.copy_from_slice(&h.finalize());
(r.resource_key().0, Bytes32(leaf))
})
.collect();
keyed.sort_by(|a, b| a.0.cmp(&b.0));
keyed.into_iter().map(|(_, leaf)| leaf).collect()
}