docker: separate workspace and guest program directories (#57)

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
Co-authored-by: kevaundray <kevtheappdev@gmail.com>
This commit is contained in:
Ignacio Hagopian
2025-07-20 23:58:02 +02:00
committed by GitHub
parent f01a6c16db
commit f05aa50032
14 changed files with 194 additions and 81 deletions

View File

@@ -1,9 +1,9 @@
name: Rust Checks
on:
push:
branches: [ main, master ]
branches: [ master ]
pull_request:
branches: [ main, master ]
branches: [ master ]
env:
CARGO_TERM_COLOR: always

42
Cargo.lock generated
View File

@@ -1055,6 +1055,8 @@ name = "build-utils"
version = "0.1.0"
dependencies = [
"cargo_metadata 0.20.0",
"thiserror 2.0.12",
"tracing",
]
[[package]]
@@ -1267,9 +1269,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.38"
version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000"
checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9"
dependencies = [
"clap_builder",
"clap_derive",
@@ -1277,9 +1279,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.38"
version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120"
checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d"
dependencies = [
"anstream",
"anstyle",
@@ -1289,9 +1291,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.32"
version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -2293,7 +2295,6 @@ dependencies = [
"sp1-sdk",
"tempfile",
"thiserror 2.0.12",
"toml",
"tracing",
"zkvm-interface",
]
@@ -7312,9 +7313,9 @@ dependencies = [
[[package]]
name = "risc0-build"
version = "2.2.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "714776c8ccf3e206ecf499dab6561259beef6e7a82dfb49ccf5c911c7350dd5e"
checksum = "62ffc0f135e6c1e9851e7e19438d03ff41a9d49199ee4f6c17b8bb30b4f83910"
dependencies = [
"anyhow",
"cargo_metadata 0.19.2",
@@ -7568,9 +7569,9 @@ dependencies = [
[[package]]
name = "risc0-zkvm"
version = "2.2.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59aaf1898f2f5d526a79d53dbe6288aeb1ce52a17184b85af84d06dedb1a367"
checksum = "9684b333c1c5d83f29ce2a92314ccfafd9d8cdfa6c4e19c07b97015d2f1eb9d0"
dependencies = [
"addr2line 0.22.0",
"anyhow",
@@ -8515,6 +8516,17 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "sp1-guest-compiler"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"tempfile",
"toml",
"tracing",
]
[[package]]
name = "sp1-primitives"
version = "5.0.5"
@@ -10313,18 +10325,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.25"
version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.25"
version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -77,8 +77,9 @@ ere-sp1 = { path = "crates/ere-sp1" }
use zkvm_interface::{Compiler, zkVM, Input};
use ere_sp1::{EreSP1, RV32_IM_SUCCINCT_ZKVM_ELF};
let guest = std::path::Path::new("guest/hello");
let elf = RV32_IM_SUCCINCT_ZKVM_ELF::compile(guest)?; // compile
let mount_directory = std::path::Path::new(".");
let guest_relative = std::path::Path::new("guest/hello");
let elf = RV32_IM_SUCCINCT_ZKVM_ELF::compile(mount_directory, guest_relative)?; // compile
let mut io = Input::new();
io.write(&42u32)?;
let zkvm = EreSP1::new(elf);

View File

@@ -1,3 +1,5 @@
use std::path::Path;
use error::JoltError;
use jolt_core::host::Program;
use jolt_methods::{preprocess_prover, preprocess_verifier, prove_generic, verify_generic};
@@ -24,8 +26,14 @@ impl Compiler for JOLT_TARGET {
type Program = Program;
fn compile(path_to_program: &std::path::Path) -> Result<Self::Program, Self::Error> {
let manifest_path = path_to_program.to_path_buf().join("Cargo.toml");
fn compile(
workspace_directory: &Path,
guest_relative: &Path,
) -> Result<Self::Program, Self::Error> {
let manifest_path = workspace_directory
.join(guest_relative)
.to_path_buf()
.join("Cargo.toml");
let package_name = package_name_from_manifest(&manifest_path).unwrap();
let mut program = Program::new(&package_name);
program.set_std(true);
@@ -116,7 +124,7 @@ impl zkVM for EreJolt {
#[cfg(test)]
mod tests {
use crate::{EreJolt, JOLT_TARGET};
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use zkvm_interface::{Compiler, Input, ProverResourceType, zkVM};
// TODO: for now, we just get one test file
@@ -136,14 +144,14 @@ mod tests {
#[test]
fn test_compile_trait() {
let test_guest_path = get_compile_test_guest_program_path();
let program = JOLT_TARGET::compile(&test_guest_path).unwrap();
let program = JOLT_TARGET::compile(&test_guest_path, Path::new("")).unwrap();
assert!(program.elf.is_some(), "elf has not been compiled");
}
#[test]
fn test_execute() {
let test_guest_path = get_compile_test_guest_program_path();
let program = JOLT_TARGET::compile(&test_guest_path).unwrap();
let program = JOLT_TARGET::compile(&test_guest_path, Path::new("")).unwrap();
let mut inputs = Input::new();
inputs.write(1 as u32);

View File

@@ -1,4 +1,4 @@
use std::time::Instant;
use std::{path::Path, time::Instant};
use openvm_build::GuestOptions;
use openvm_circuit::arch::ContinuationVmProof;
@@ -30,14 +30,14 @@ impl Compiler for OPENVM_TARGET {
type Program = Elf;
fn compile(path_to_program: &std::path::Path) -> Result<Self::Program, Self::Error> {
fn compile(workspace_path: &Path, guest_relative: &Path) -> Result<Self::Program, Self::Error> {
let sdk = Sdk::new();
// Build the guest crate
let elf: Elf = sdk
.build(
GuestOptions::default(),
path_to_program,
workspace_path.join(guest_relative),
&Default::default(),
)
.map_err(|e| CompileError::Client(e.into()))?;
@@ -199,7 +199,8 @@ mod tests {
#[test]
fn test_compile() {
let test_guest_path = get_compile_test_guest_program_path();
let elf = OPENVM_TARGET::compile(&test_guest_path).expect("compilation failed");
let elf =
OPENVM_TARGET::compile(&test_guest_path, Path::new("")).expect("compilation failed");
assert!(
!elf.instructions.is_empty(),
"ELF bytes should not be empty."
@@ -211,7 +212,8 @@ mod tests {
fn test_execute_empty_input_panic() {
// Panics because the program expects input arguments, but we supply none
let test_guest_path = get_compile_test_guest_program_path();
let elf = OPENVM_TARGET::compile(&test_guest_path).expect("compilation failed");
let elf =
OPENVM_TARGET::compile(&test_guest_path, Path::new("")).expect("compilation failed");
let empty_input = Input::new();
let zkvm = EreOpenVM::new(elf, ProverResourceType::Cpu);
@@ -221,7 +223,8 @@ mod tests {
#[test]
fn test_execute() {
let test_guest_path = get_compile_test_guest_program_path();
let elf = OPENVM_TARGET::compile(&test_guest_path).expect("compilation failed");
let elf =
OPENVM_TARGET::compile(&test_guest_path, Path::new("")).expect("compilation failed");
let mut input = Input::new();
input.write(10u64);
@@ -232,7 +235,8 @@ mod tests {
#[test]
fn test_prove_verify() {
let test_guest_path = get_compile_test_guest_program_path();
let elf = OPENVM_TARGET::compile(&test_guest_path).expect("compilation failed");
let elf =
OPENVM_TARGET::compile(&test_guest_path, Path::new("")).expect("compilation failed");
let mut input = Input::new();
input.write(10u64);

View File

@@ -1,5 +1,5 @@
use pico_sdk::client::DefaultProverClient;
use std::{process::Command, time::Instant};
use std::{path::Path, process::Command, time::Instant};
use zkvm_interface::{
Compiler, Input, InputItem, ProgramExecutionReport, ProgramProvingReport, ProverResourceType,
zkVM, zkVMError,
@@ -17,15 +17,20 @@ impl Compiler for PICO_TARGET {
type Program = Vec<u8>;
fn compile(path: &std::path::Path) -> Result<Self::Program, Self::Error> {
fn compile(
workspace_directory: &Path,
guest_relative: &Path,
) -> Result<Self::Program, Self::Error> {
let guest_path = workspace_directory.join(guest_relative);
// 1. Check guest path
if !path.exists() {
return Err(PicoError::PathNotFound(path.to_path_buf()));
if !guest_path.exists() {
return Err(PicoError::PathNotFound(guest_path.to_path_buf()));
}
// 2. Run `cargo pico build`
let status = Command::new("cargo")
.current_dir(path)
.current_dir(&guest_path)
.env("RUST_LOG", "info")
.args(["pico", "build"])
.status()?; // From<io::Error> → Spawn
@@ -35,7 +40,7 @@ impl Compiler for PICO_TARGET {
}
// 3. Locate the ELF file
let elf_path = path.join("elf/riscv32im-pico-zkvm-elf");
let elf_path = guest_path.join("elf/riscv32im-pico-zkvm-elf");
if !elf_path.exists() {
return Err(PicoError::ElfNotFound(elf_path));
@@ -140,7 +145,7 @@ impl zkVM for ErePico {
#[cfg(test)]
mod tests {
use crate::PICO_TARGET;
use std::path::PathBuf;
use std::{path::Path, path::PathBuf};
use zkvm_interface::Compiler;
fn get_compile_test_guest_program_path() -> PathBuf {
@@ -167,7 +172,7 @@ mod tests {
let test_guest_path = get_compile_test_guest_program_path();
println!("Using test guest path: {}", test_guest_path.display());
match PICO_TARGET::compile(&test_guest_path) {
match PICO_TARGET::compile(&test_guest_path, Path::new("")) {
Ok(elf_bytes) => {
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
}

View File

@@ -1,4 +1,4 @@
use std::time::Instant;
use std::{path::Path, time::Instant};
use compile::compile_risczero_program;
use risc0_zkvm::{ExecutorEnv, ProverOpts, Receipt, default_executor, default_prover};
@@ -23,8 +23,12 @@ impl Compiler for RV32_IM_RISCZERO_ZKVM_ELF {
type Program = Risc0Program;
fn compile(path_to_program: &std::path::Path) -> Result<Self::Program, Self::Error> {
compile_risczero_program(path_to_program).map_err(RiscZeroError::from)
fn compile(
workspace_directory: &Path,
guest_relative: &Path,
) -> Result<Self::Program, Self::Error> {
compile_risczero_program(&workspace_directory.join(guest_relative))
.map_err(RiscZeroError::from)
}
}
@@ -153,7 +157,7 @@ mod prove_tests {
fn get_compiled_test_r0_elf_for_prove() -> Result<Risc0Program, RiscZeroError> {
let test_guest_path = get_prove_test_guest_program_path();
RV32_IM_RISCZERO_ZKVM_ELF::compile(&test_guest_path)
RV32_IM_RISCZERO_ZKVM_ELF::compile(&test_guest_path, Path::new(""))
}
#[test]
@@ -206,7 +210,7 @@ mod execute_tests {
fn get_compiled_test_r0_elf() -> Result<Risc0Program, RiscZeroError> {
let test_guest_path = get_execute_test_guest_program_path();
RV32_IM_RISCZERO_ZKVM_ELF::compile(&test_guest_path)
RV32_IM_RISCZERO_ZKVM_ELF::compile(&test_guest_path, Path::new(""))
}
fn get_execute_test_guest_program_path() -> PathBuf {

View File

@@ -1,6 +1,7 @@
use std::{
path::{Path, PathBuf},
process::Command,
str::FromStr,
};
use build_utils::docker;
@@ -9,40 +10,46 @@ use tracing::info;
use crate::error::CompileError;
pub fn compile(guest_program_full_path: &Path) -> Result<Vec<u8>, CompileError> {
pub fn compile(
workspace_directory: &Path,
guest_program_relative: &Path,
) -> Result<Vec<u8>, CompileError> {
// Build the SP1 docker image
let tag = "ere-build-sp1:latest";
docker::build_image(&PathBuf::from("docker/sp1/Dockerfile"), tag)
.map_err(|e| CompileError::DockerImageBuildFailed(Box::new(e)))?;
// Compile the guest program using the SP1 docker image
let guest_program_path_str = guest_program_full_path
// Prepare paths for compilation
let mount_directory_str = workspace_directory
.to_str()
.ok_or_else(|| CompileError::InvalidGuestPath(guest_program_full_path.to_path_buf()))?;
.ok_or_else(|| CompileError::InvalidMountPath(workspace_directory.to_path_buf()))?;
let elf_output_dir = TempDir::new().map_err(CompileError::CreatingTempOutputDirectoryFailed)?;
let elf_output_dir_str = elf_output_dir
.path()
.to_str()
.ok_or_else(|| CompileError::InvalidTempOutputPath(elf_output_dir.path().to_path_buf()))?;
info!("Compiling program: {}", guest_program_path_str);
let container_mount_directory = PathBuf::from_str("/guest-workspace").unwrap();
let container_guest_program_path = container_mount_directory.join(guest_program_relative);
let container_guest_program_str = container_guest_program_path
.to_str()
.ok_or_else(|| CompileError::InvalidGuestPath(guest_program_relative.to_path_buf()))?;
let status = Command::new("docker")
.args([
"run",
"--rm",
// Mount volumes
"-v",
&format!("{guest_program_path_str}:/guest-program"),
"-v",
&format!("{elf_output_dir_str}:/output"),
tag,
// Guest compiler execution
"./guest-compiler",
"/guest-program",
"/output",
])
.status()
info!(
"Compiling program: mount_directory={} guest_program={}",
mount_directory_str, container_guest_program_str
);
// Build and run Docker command
let docker_cmd = DockerRunCommand::new(tag)
.remove_after_run()
.with_volume(mount_directory_str, "/guest-workspace")
.with_volume(elf_output_dir_str, "/output")
.with_command(["./guest-compiler", container_guest_program_str, "/output"]);
let status = docker_cmd
.run()
.map_err(CompileError::DockerCommandFailed)?;
if !status.success() {
@@ -82,7 +89,7 @@ mod tests {
fn test_compile_sp1_program() {
let test_guest_path = get_compile_test_guest_program_path();
match compile(&test_guest_path) {
match compile(&test_guest_path, Path::new("")) {
Ok(elf_bytes) => {
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
}
@@ -95,7 +102,7 @@ mod tests {
#[test]
fn test_compile_trait() {
let test_guest_path = get_compile_test_guest_program_path();
match RV32_IM_SUCCINCT_ZKVM_ELF::compile(&test_guest_path) {
match RV32_IM_SUCCINCT_ZKVM_ELF::compile(&test_guest_path, Path::new("")) {
Ok(elf_bytes) => {
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
}
@@ -108,3 +115,63 @@ mod tests {
}
}
}
#[derive(Debug)]
struct DockerRunCommand {
image: String,
volumes: Vec<(String, String)>, // (host_path, container_path)
command: Vec<String>,
// remove image after running
remove_after: bool,
}
impl DockerRunCommand {
fn new(image: impl Into<String>) -> Self {
Self {
image: image.into(),
volumes: Vec::new(),
command: Vec::new(),
remove_after: false,
}
}
fn with_volume(
mut self,
host_path: impl Into<String>,
container_path: impl Into<String>,
) -> Self {
self.volumes.push((host_path.into(), container_path.into()));
self
}
fn with_command(mut self, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.command.extend(args.into_iter().map(|s| s.into()));
self
}
fn remove_after_run(mut self) -> Self {
self.remove_after = true;
self
}
fn to_args(&self) -> Vec<String> {
let mut args = vec!["run".to_string()];
if self.remove_after {
args.push("--rm".to_string());
}
for (host_path, container_path) in &self.volumes {
args.extend(["-v".to_string(), format!("{host_path}:{container_path}")]);
}
args.push(self.image.clone());
args.extend(self.command.iter().cloned());
args
}
fn run(&self) -> Result<std::process::ExitStatus, std::io::Error> {
Command::new("docker").args(self.to_args()).status()
}
}

View File

@@ -33,6 +33,8 @@ pub enum CompileError {
DockerCommandFailed(#[source] std::io::Error),
#[error("Docker container run failed with status: {0}")]
DockerContainerRunFailed(std::process::ExitStatus),
#[error("Invalid mount path: {0}")]
InvalidMountPath(PathBuf),
#[error("Invalid guest program path: {0}")]
InvalidGuestPath(PathBuf),
#[error("Failed to create temporary directory: {0}")]

View File

@@ -1,6 +1,6 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
use std::time::Instant;
use std::{path::Path, time::Instant};
use sp1_sdk::{
CpuProver, CudaProver, NetworkProver, Prover, ProverClient, SP1ProofWithPublicValues,
@@ -106,8 +106,11 @@ impl Compiler for RV32_IM_SUCCINCT_ZKVM_ELF {
type Program = Vec<u8>;
fn compile(path_to_program: &std::path::Path) -> Result<Self::Program, Self::Error> {
compile::compile(path_to_program).map_err(SP1Error::from)
fn compile(
workspace_directory: &Path,
guest_relative: &Path,
) -> Result<Self::Program, Self::Error> {
compile::compile(workspace_directory, guest_relative).map_err(SP1Error::from)
}
}
@@ -232,7 +235,7 @@ mod execute_tests {
fn get_compiled_test_sp1_elf() -> Result<Vec<u8>, SP1Error> {
let test_guest_path = get_execute_test_guest_program_path();
RV32_IM_SUCCINCT_ZKVM_ELF::compile(&test_guest_path)
RV32_IM_SUCCINCT_ZKVM_ELF::compile(&test_guest_path, Path::new(""))
}
fn get_execute_test_guest_program_path() -> PathBuf {
@@ -303,7 +306,7 @@ mod prove_tests {
fn get_compiled_test_sp1_elf_for_prove() -> Result<Vec<u8>, SP1Error> {
let test_guest_path = get_prove_test_guest_program_path();
RV32_IM_SUCCINCT_ZKVM_ELF::compile(&test_guest_path)
RV32_IM_SUCCINCT_ZKVM_ELF::compile(&test_guest_path, Path::new(""))
}
#[test]

View File

@@ -154,7 +154,7 @@ mod tests {
#[test]
fn test_compile_trait() {
let test_guest_path = get_compile_test_guest_program_path();
match RV64_IMA_ZISK_ZKVM_ELF::compile(&test_guest_path) {
match RV64_IMA_ZISK_ZKVM_ELF::compile(&test_guest_path, Path::new("")) {
Ok(elf_bytes) => {
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
}

View File

@@ -30,8 +30,11 @@ impl Compiler for RV64_IMA_ZISK_ZKVM_ELF {
type Program = Vec<u8>;
fn compile(path_to_program: &Path) -> Result<Self::Program, Self::Error> {
compile_zisk_program(path_to_program).map_err(ZiskError::Compile)
fn compile(
workspace_directory: &Path,
guest_relative: &Path,
) -> Result<Self::Program, Self::Error> {
compile_zisk_program(&workspace_directory.join(guest_relative)).map_err(ZiskError::Compile)
}
}
@@ -391,7 +394,7 @@ mod execute_tests {
fn get_compiled_test_zisk_elf() -> Result<Vec<u8>, ZiskError> {
let test_guest_path = get_execute_test_guest_program_path();
RV64_IMA_ZISK_ZKVM_ELF::compile(&test_guest_path)
RV64_IMA_ZISK_ZKVM_ELF::compile(&test_guest_path, Path::new(""))
}
fn get_execute_test_guest_program_path() -> PathBuf {
@@ -457,7 +460,7 @@ mod prove_tests {
fn get_compiled_test_zisk_elf_for_prove() -> Result<Vec<u8>, ZiskError> {
let test_guest_path = get_prove_test_guest_program_path();
RV64_IMA_ZISK_ZKVM_ELF::compile(&test_guest_path)
RV64_IMA_ZISK_ZKVM_ELF::compile(&test_guest_path, Path::new(""))
}
#[test]

View File

@@ -7,7 +7,7 @@ use serde::Serialize;
#[derive(Clone)]
pub enum InputItem {
/// A serializable object stored as a trait object
Object(Arc<Box<dyn ErasedSerialize + Send + Sync>>),
Object(Arc<dyn ErasedSerialize + Send + Sync>),
/// Pre-serialized bytes (e.g., from bincode)
Bytes(Vec<u8>),
}
@@ -42,8 +42,7 @@ impl Input {
/// Write a serializable value as a trait object
pub fn write<T: Serialize + Send + Sync + 'static>(&mut self, value: T) {
self.items
.push(InputItem::Object(Arc::new(Box::new(value))));
self.items.push(InputItem::Object(Arc::new(value)));
}
/// Write pre-serialized bytes directly

View File

@@ -17,7 +17,12 @@ pub trait Compiler {
type Program: Clone + Send + Sync;
/// Compiles the program and returns the program
fn compile(path_to_program: &Path) -> Result<Self::Program, Self::Error>;
///
/// # Arguments
/// * `mount_directory` - The base directory (workspace root)
/// * `guest_relative` - The relative path from mount_directory to the guest program
fn compile(mount_directory: &Path, guest_relative: &Path)
-> Result<Self::Program, Self::Error>;
}
/// ResourceType specifies what resource will be used to create the proofs.