mirror of
https://github.com/eth-act/ere.git
synced 2026-02-19 11:54:42 -05:00
Add ere-dockerized (#75)
This commit is contained in:
6
.github/workflows/rust-checks.yml
vendored
6
.github/workflows/rust-checks.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
crate: [ere-sp1, ere-risc0]
|
||||
zkvm: [sp1, risc0]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Check clippy
|
||||
run: cargo clippy --bins --lib --examples --tests --benches -p ${{ matrix.crate }}
|
||||
run: cargo clippy --bins --lib --examples --tests --benches -p ere-${{ matrix.zkvm }}
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test --release -p ${{ matrix.crate }}
|
||||
run: cargo test --release -p ere-dockerized -- ${{ matrix.zkvm }}
|
||||
|
||||
3
.github/workflows/test-zisk-docker.yml
vendored
3
.github/workflows/test-zisk-docker.yml
vendored
@@ -30,4 +30,5 @@ jobs:
|
||||
run: |
|
||||
docker build \
|
||||
--tag ere-builder-zisk:latest \
|
||||
--file docker/zisk/Dockerfile .
|
||||
--file docker/zisk/Dockerfile \
|
||||
--build-arg CI=1 .
|
||||
|
||||
44
Cargo.lock
generated
44
Cargo.lock
generated
@@ -2359,6 +2359,20 @@ dependencies = [
|
||||
"zkvm-interface",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ere-dockerized"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"build-utils",
|
||||
"bytemuck",
|
||||
"risc0-zkvm",
|
||||
"serde",
|
||||
"tempfile",
|
||||
"thiserror 2.0.12",
|
||||
"zkvm-interface",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ere-jolt"
|
||||
version = "0.1.0"
|
||||
@@ -2425,7 +2439,9 @@ dependencies = [
|
||||
"borsh",
|
||||
"build-utils",
|
||||
"bytemuck",
|
||||
"cargo_metadata 0.19.2",
|
||||
"hex",
|
||||
"risc0-build",
|
||||
"risc0-zkvm",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2444,6 +2460,7 @@ dependencies = [
|
||||
"sp1-sdk",
|
||||
"tempfile",
|
||||
"thiserror 2.0.12",
|
||||
"toml 0.8.22",
|
||||
"tracing",
|
||||
"zkvm-interface",
|
||||
]
|
||||
@@ -7886,22 +7903,6 @@ dependencies = [
|
||||
"sppark",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "risc0-cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
"borsh",
|
||||
"clap",
|
||||
"hex",
|
||||
"risc0-zkvm",
|
||||
"tempfile",
|
||||
"toml 0.8.22",
|
||||
"tracing",
|
||||
"zkvm-interface",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "risc0-core"
|
||||
version = "2.0.0"
|
||||
@@ -8961,17 +8962,6 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sp1-guest-compiler"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"tempfile",
|
||||
"toml 0.8.22",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sp1-primitives"
|
||||
version = "5.1.0"
|
||||
|
||||
@@ -11,11 +11,9 @@ members = [
|
||||
"crates/ere-zisk",
|
||||
# zkVM interface
|
||||
"crates/zkvm-interface",
|
||||
# CLI
|
||||
# CLI and dockerized zkVM
|
||||
"crates/ere-cli",
|
||||
# Guest compilers
|
||||
"docker/sp1",
|
||||
"docker/risc0",
|
||||
"crates/ere-dockerized",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
@@ -39,6 +37,7 @@ hex = "0.4.3"
|
||||
zkvm-interface = { path = "crates/zkvm-interface" }
|
||||
build-utils = { path = "crates/build-utils" }
|
||||
ere-cli = { path = "crates/ere-cli" }
|
||||
ere-dockerized = { path = "crates/ere-dockerized" }
|
||||
ere-jolt = { path = "crates/ere-jolt" }
|
||||
ere-nexus = { path = "crates/ere-nexus" }
|
||||
ere-openvm = { path = "crates/ere-openvm" }
|
||||
|
||||
@@ -78,9 +78,8 @@ ere-sp1 = { path = "crates/ere-sp1" }
|
||||
use zkvm_interface::{Compiler, zkVM, Input, ProverResourceType};
|
||||
use ere_sp1::{EreSP1, RV32_IM_SUCCINCT_ZKVM_ELF};
|
||||
|
||||
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 guest_directory = std::path::Path::new("guest/hello");
|
||||
let elf = RV32_IM_SUCCINCT_ZKVM_ELF.compile(guest_directory)?; // compile
|
||||
let mut io = Input::new();
|
||||
io.write(&42u32)?;
|
||||
let zkvm = EreSP1::new(elf, ProverResourceType::Cpu); // create zkVM instance
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
|
||||
|
||||
use cargo_metadata::MetadataCommand;
|
||||
use std::{env, fs, path::Path};
|
||||
|
||||
pub mod docker;
|
||||
|
||||
// Detect and generate a Rust source file that contains the name and version of the SDK.
|
||||
pub fn detect_and_generate_name_and_sdk_version(name: &str, sdk_dep_name: &str) {
|
||||
let meta = cargo_metadata::MetadataCommand::new()
|
||||
gen_name_and_sdk_version(name, &detect_sdk_version(sdk_dep_name));
|
||||
}
|
||||
|
||||
// Detect version of the SDK.
|
||||
pub fn detect_sdk_version(sdk_dep_name: &str) -> String {
|
||||
let meta = MetadataCommand::new()
|
||||
.exec()
|
||||
.expect("Failed to get cargo metadata");
|
||||
|
||||
let version = meta
|
||||
.packages
|
||||
meta.packages
|
||||
.iter()
|
||||
.find(|pkg| pkg.name.eq_ignore_ascii_case(sdk_dep_name))
|
||||
.map(|pkg| pkg.version.to_string())
|
||||
.unwrap_or_else(|| {
|
||||
panic!("Dependency {sdk_dep_name} not found in Cargo.toml");
|
||||
});
|
||||
|
||||
gen_name_and_sdk_version(name, &version);
|
||||
})
|
||||
}
|
||||
|
||||
// Generate a Rust source file that contains the provided name and version of the SDK.
|
||||
@@ -31,3 +36,13 @@ pub fn gen_name_and_sdk_version(name: &str, version: &str) {
|
||||
.unwrap();
|
||||
println!("cargo:rerun-if-changed=Cargo.lock");
|
||||
}
|
||||
|
||||
/// Detects version of the crate of the `build.rs` that being ran.
|
||||
pub fn detect_self_crate_version() -> String {
|
||||
let meta = MetadataCommand::new()
|
||||
.exec()
|
||||
.expect("Failed to get cargo metadata");
|
||||
|
||||
// `root_package` returns the crate of the `build.rs` that being ran.
|
||||
meta.root_package().unwrap().version.to_string()
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
|
||||
|
||||
use anyhow::{Context, Error};
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::{fs, path::PathBuf};
|
||||
@@ -31,12 +33,9 @@ struct Cli {
|
||||
enum Commands {
|
||||
/// Compile a guest program
|
||||
Compile {
|
||||
/// Path to the base directory (workspace root)
|
||||
/// Path to the guest program
|
||||
#[arg(long)]
|
||||
mount_directory: PathBuf,
|
||||
/// Relative path from `mount_directory` to the guest program
|
||||
#[arg(long)]
|
||||
guest_relative: PathBuf,
|
||||
guest_path: PathBuf,
|
||||
/// Path where the compiled program will be written
|
||||
#[arg(long)]
|
||||
program_path: PathBuf,
|
||||
@@ -90,10 +89,9 @@ fn main() -> Result<(), Error> {
|
||||
|
||||
match args.command {
|
||||
Commands::Compile {
|
||||
mount_directory,
|
||||
guest_relative,
|
||||
guest_path,
|
||||
program_path,
|
||||
} => compile(mount_directory, guest_relative, program_path),
|
||||
} => compile(guest_path, program_path),
|
||||
Commands::Prove {
|
||||
program_path,
|
||||
input_path,
|
||||
@@ -114,31 +112,27 @@ fn main() -> Result<(), Error> {
|
||||
}
|
||||
}
|
||||
|
||||
fn compile(
|
||||
mount_directory: PathBuf,
|
||||
guest_relative: PathBuf,
|
||||
program_path: PathBuf,
|
||||
) -> Result<(), Error> {
|
||||
fn compile(guest_path: PathBuf, program_path: PathBuf) -> Result<(), Error> {
|
||||
#[cfg(feature = "jolt")]
|
||||
let program = ere_jolt::JOLT_TARGET::compile(&mount_directory, &guest_relative);
|
||||
let program = ere_jolt::JOLT_TARGET.compile(&guest_path);
|
||||
|
||||
#[cfg(feature = "nexus")]
|
||||
let program = ere_nexus::NEXUS_TARGET::compile(&mount_directory, &guest_relative);
|
||||
let program = ere_nexus::NEXUS_TARGET.compile(&guest_path);
|
||||
|
||||
#[cfg(feature = "openvm")]
|
||||
let program = ere_openvm::OPENVM_TARGET::compile(&mount_directory, &guest_relative);
|
||||
let program = ere_openvm::OPENVM_TARGET.compile(&guest_path);
|
||||
|
||||
#[cfg(feature = "pico")]
|
||||
let program = ere_pico::PICO_TARGET::compile(&mount_directory, &guest_relative);
|
||||
let program = ere_pico::PICO_TARGET.compile(&guest_path);
|
||||
|
||||
#[cfg(feature = "risc0")]
|
||||
let program = ere_risc0::RV32_IM_RISC0_ZKVM_ELF::compile(&mount_directory, &guest_relative);
|
||||
let program = ere_risc0::RV32_IM_RISC0_ZKVM_ELF.compile(&guest_path);
|
||||
|
||||
#[cfg(feature = "sp1")]
|
||||
let program = ere_sp1::RV32_IM_SUCCINCT_ZKVM_ELF::compile(&mount_directory, &guest_relative);
|
||||
let program = ere_sp1::RV32_IM_SUCCINCT_ZKVM_ELF.compile(&guest_path);
|
||||
|
||||
#[cfg(feature = "zisk")]
|
||||
let program = ere_zisk::RV64_IMA_ZISK_ZKVM_ELF::compile(&mount_directory, &guest_relative);
|
||||
let program = ere_zisk::RV64_IMA_ZISK_ZKVM_ELF.compile(&guest_path);
|
||||
|
||||
serde::write(
|
||||
&program_path,
|
||||
|
||||
26
crates/ere-dockerized/Cargo.toml
Normal file
26
crates/ere-dockerized/Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "ere-dockerized"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.3"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tempfile = "3.3"
|
||||
thiserror = "2"
|
||||
|
||||
zkvm-interface = { workspace = true, features = ["clap"] }
|
||||
|
||||
# Needed for Risc0 and OpenVM input serialization.
|
||||
bytemuck = "1.13"
|
||||
risc0-zkvm = { version = "^2.3.0", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
[build-dependencies]
|
||||
build-utils = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
65
crates/ere-dockerized/build.rs
Normal file
65
crates/ere-dockerized/build.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use build_utils::{detect_sdk_version, detect_self_crate_version};
|
||||
use std::{env, fs, path::Path};
|
||||
|
||||
fn main() {
|
||||
generate_crate_version();
|
||||
generate_zkvm_sdk_version_impl();
|
||||
println!("cargo:rerun-if-changed=Cargo.lock");
|
||||
}
|
||||
|
||||
fn generate_crate_version() {
|
||||
let crate_version = format!(
|
||||
"const CRATE_VERSION: &str = \"{}\";",
|
||||
detect_self_crate_version()
|
||||
);
|
||||
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let dst = Path::new(&out_dir).join("crate_version.rs");
|
||||
fs::write(dst, crate_version).unwrap();
|
||||
}
|
||||
|
||||
fn generate_zkvm_sdk_version_impl() {
|
||||
let [
|
||||
jolt_version,
|
||||
nexus_version,
|
||||
openvm_version,
|
||||
pico_version,
|
||||
risc0_version,
|
||||
sp1_version,
|
||||
] = [
|
||||
"jolt-sdk",
|
||||
"nexus-sdk",
|
||||
"openvm-sdk",
|
||||
"pico-sdk",
|
||||
"risc0-zkvm",
|
||||
"sp1-sdk",
|
||||
]
|
||||
.map(detect_sdk_version);
|
||||
|
||||
// FIXME: ZisK doens't depend on SDK yet, so we hardcode the version here,
|
||||
// same as the one in `scripts/sdk_installers/install_zisk_sdk.sh`.
|
||||
// Once ZisK's SDK is ready, we should update this to detect the SDK
|
||||
// version.
|
||||
// The issue for tracking https://github.com/eth-act/ere/issues/73.
|
||||
let zisk_version = "0.9.0";
|
||||
|
||||
let zkvm_sdk_version_impl = format!(
|
||||
r#"impl crate::ErezkVM {{
|
||||
pub fn sdk_version(&self) -> &'static str {{
|
||||
match self {{
|
||||
Self::Jolt => "{jolt_version}",
|
||||
Self::Nexus => "{nexus_version}",
|
||||
Self::OpenVM => "{openvm_version}",
|
||||
Self::Pico => "{pico_version}",
|
||||
Self::Risc0 => "{risc0_version}",
|
||||
Self::SP1 => "{sp1_version}",
|
||||
Self::Zisk => "{zisk_version}",
|
||||
}}
|
||||
}}
|
||||
}}"#,
|
||||
);
|
||||
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let dst = Path::new(&out_dir).join("zkvm_sdk_version_impl.rs");
|
||||
fs::write(dst, zkvm_sdk_version_impl).unwrap();
|
||||
}
|
||||
158
crates/ere-dockerized/src/docker.rs
Normal file
158
crates/ere-dockerized/src/docker.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
use crate::error::CommonError;
|
||||
use std::{
|
||||
fmt::{self, Display, Formatter},
|
||||
io,
|
||||
path::Path,
|
||||
process::Command,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CmdOption(String, Option<String>);
|
||||
|
||||
impl CmdOption {
|
||||
pub fn new(key: impl AsRef<str>, value: impl AsRef<str>) -> Self {
|
||||
Self(to_string(key), Some(to_string(value)))
|
||||
}
|
||||
|
||||
pub fn flag(key: impl AsRef<str>) -> Self {
|
||||
Self(to_string(key), None)
|
||||
}
|
||||
|
||||
pub fn to_args(&self) -> Vec<String> {
|
||||
let Self(key, value) = self;
|
||||
match value {
|
||||
Some(value) => vec![format!("--{key}"), format!("{value}")],
|
||||
None => vec![format!("--{key}")],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CmdOption {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let Self(key, value) = self;
|
||||
match value {
|
||||
Some(value) => write!(f, "--{key} {value}"),
|
||||
None => write!(f, "--{key}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DockerBuildCmd {
|
||||
options: Vec<CmdOption>,
|
||||
}
|
||||
|
||||
impl DockerBuildCmd {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn file(mut self, file: impl AsRef<Path>) -> Self {
|
||||
self.options
|
||||
.push(CmdOption::new("file", file.as_ref().to_string_lossy()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tag(mut self, tag: impl AsRef<str>) -> Self {
|
||||
self.options.push(CmdOption::new("tag", tag));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bulid_arg(mut self, key: impl AsRef<str>, value: impl AsRef<str>) -> Self {
|
||||
self.options.push(CmdOption::new(
|
||||
"build-arg",
|
||||
format!("{}={}", to_string(key), to_string(value)),
|
||||
));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn exec(self, context: impl AsRef<Path>) -> Result<(), io::Error> {
|
||||
let mut cmd = Command::new("docker");
|
||||
cmd.arg("build");
|
||||
for flag in self.options {
|
||||
cmd.args(flag.to_args());
|
||||
}
|
||||
cmd.arg(context.as_ref().to_string_lossy().to_string());
|
||||
|
||||
let status = cmd.status()?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(io::Error::other(format!(
|
||||
"Command {cmd:?} failed with status: {status}",
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DockerRunCmd {
|
||||
options: Vec<CmdOption>,
|
||||
image: String,
|
||||
}
|
||||
|
||||
impl DockerRunCmd {
|
||||
pub fn new(image: String) -> Self {
|
||||
Self {
|
||||
options: Vec::new(),
|
||||
image,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn volume(mut self, host: impl AsRef<Path>, container: impl AsRef<Path>) -> Self {
|
||||
self.options.push(CmdOption::new(
|
||||
"volume",
|
||||
format!(
|
||||
"{}:{}",
|
||||
host.as_ref().display(),
|
||||
container.as_ref().display(),
|
||||
),
|
||||
));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn gpus(mut self, devices: impl AsRef<str>) -> Self {
|
||||
self.options.push(CmdOption::new("gpus", devices));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn rm(mut self) -> Self {
|
||||
self.options.push(CmdOption::flag("rm"));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn exec(self, commands: impl IntoIterator<Item: AsRef<str>>) -> Result<(), io::Error> {
|
||||
let mut cmd = Command::new("docker");
|
||||
cmd.arg("run");
|
||||
for flag in self.options {
|
||||
cmd.args(flag.to_args());
|
||||
}
|
||||
cmd.arg(self.image);
|
||||
for command in commands {
|
||||
cmd.arg(command.as_ref());
|
||||
}
|
||||
|
||||
let status = cmd.status()?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(io::Error::other(format!(
|
||||
"Command {cmd:?} failed with status: {status}",
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn docker_image_exists(image: impl AsRef<str>) -> Result<bool, CommonError> {
|
||||
let output = Command::new("docker")
|
||||
.args(["images", "--quiet", image.as_ref()])
|
||||
.output()
|
||||
.map_err(CommonError::DockerImageCmd)?;
|
||||
// If image exists, image id will be printed hence stdout will be non-empty.
|
||||
Ok(!output.stdout.is_empty())
|
||||
}
|
||||
|
||||
fn to_string(s: impl AsRef<str>) -> String {
|
||||
s.as_ref().to_string()
|
||||
}
|
||||
102
crates/ere-dockerized/src/error.rs
Normal file
102
crates/ere-dockerized/src/error.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use std::{io, path::PathBuf};
|
||||
use thiserror::Error;
|
||||
use zkvm_interface::zkVMError;
|
||||
|
||||
impl From<DockerizedError> for zkVMError {
|
||||
fn from(value: DockerizedError) -> Self {
|
||||
zkVMError::Other(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CommonError> for zkVMError {
|
||||
fn from(value: CommonError) -> Self {
|
||||
zkVMError::Other(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DockerizedError {
|
||||
#[error(transparent)]
|
||||
Compile(#[from] CompileError),
|
||||
|
||||
#[error(transparent)]
|
||||
Execute(#[from] ExecuteError),
|
||||
|
||||
#[error(transparent)]
|
||||
Prove(#[from] ProveError),
|
||||
|
||||
#[error(transparent)]
|
||||
Verify(#[from] VerifyError),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CompileError {
|
||||
#[error(
|
||||
"Guest directory must be in mounting directory, mounting_directory: {mounting_directory}, guest_directory: {guest_directory}"
|
||||
)]
|
||||
GuestNotInMountingDirecty {
|
||||
mounting_directory: PathBuf,
|
||||
guest_directory: PathBuf,
|
||||
},
|
||||
#[error(transparent)]
|
||||
Common(#[from] CommonError),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ExecuteError {
|
||||
#[error(transparent)]
|
||||
Common(#[from] CommonError),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ProveError {
|
||||
#[error(transparent)]
|
||||
Common(#[from] CommonError),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum VerifyError {
|
||||
#[error(transparent)]
|
||||
Common(#[from] CommonError),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CommonError {
|
||||
#[error("{context}: {source}")]
|
||||
Io {
|
||||
#[source]
|
||||
source: io::Error,
|
||||
context: String,
|
||||
},
|
||||
#[error("Failed to execute `docker image`: {0}")]
|
||||
DockerImageCmd(io::Error),
|
||||
#[error("Failed to execute `docker build`: {0}")]
|
||||
DockerBuildCmd(io::Error),
|
||||
#[error("Failed to execute `docker run`: {0}")]
|
||||
DockerRunCmd(io::Error),
|
||||
#[error("{context}: {source}")]
|
||||
Serialization {
|
||||
#[source]
|
||||
source: Box<dyn std::error::Error + Send + Sync + 'static>,
|
||||
context: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl CommonError {
|
||||
pub fn io(source: io::Error, context: impl ToString) -> Self {
|
||||
Self::Io {
|
||||
source,
|
||||
context: context.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serilization(
|
||||
source: impl Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
|
||||
context: impl ToString,
|
||||
) -> Self {
|
||||
Self::Serialization {
|
||||
source: source.into(),
|
||||
context: context.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
64
crates/ere-dockerized/src/input.rs
Normal file
64
crates/ere-dockerized/src/input.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use crate::{ErezkVM, error::CommonError};
|
||||
use serde::Serialize;
|
||||
use zkvm_interface::{Input, InputItem};
|
||||
|
||||
impl ErezkVM {
|
||||
pub fn serialize_object(
|
||||
&self,
|
||||
obj: &(impl Serialize + ?Sized),
|
||||
) -> Result<Vec<u8>, CommonError> {
|
||||
match self {
|
||||
// Issue for tracking: https://github.com/eth-act/ere/issues/4.
|
||||
Self::Jolt => unimplemented!(),
|
||||
// Issue for tracking: https://github.com/eth-act/ere/issues/63.
|
||||
Self::Nexus => unimplemented!(),
|
||||
// FIXME: Instead of using `openvm::serde::to_vec`, we use Risc0's
|
||||
// serializer, because OpenVM uses the same one, to avoid the
|
||||
// duplicated extern symbol they export.
|
||||
// It'd be better to have each zkvm provides their
|
||||
// lightweight serde crate.
|
||||
Self::OpenVM => risc0_zkvm::serde::to_vec(obj)
|
||||
.map(|words| words.into_iter().flat_map(|w| w.to_le_bytes()).collect())
|
||||
.map_err(|err| {
|
||||
CommonError::serilization(
|
||||
err,
|
||||
"Failed to serialize object with `risc0_zkvm::serde::to_vec`",
|
||||
)
|
||||
}),
|
||||
Self::Pico => bincode::serialize(obj).map_err(|err| {
|
||||
CommonError::serilization(err, "Failed to serialize object with `bincode`")
|
||||
}),
|
||||
Self::Risc0 => risc0_zkvm::serde::to_vec(obj)
|
||||
.map(|vec| bytemuck::cast_slice(&vec).to_vec())
|
||||
.map_err(|err| {
|
||||
CommonError::serilization(
|
||||
err,
|
||||
"Failed to serialize object with `risc0_zkvm::serde::to_vec`",
|
||||
)
|
||||
}),
|
||||
Self::SP1 => bincode::serialize(obj).map_err(|err| {
|
||||
CommonError::serilization(err, "Failed to serialize object with `bincode`")
|
||||
}),
|
||||
Self::Zisk => bincode::serialize(obj).map_err(|err| {
|
||||
CommonError::serilization(err, "Failed to serialize object with `bincode`")
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize_inputs(&self, inputs: &Input) -> Result<Vec<u8>, CommonError> {
|
||||
bincode::serialize(
|
||||
&inputs
|
||||
.iter()
|
||||
.map(|input| {
|
||||
Ok(match input {
|
||||
InputItem::Object(obj) => self.serialize_object(&**obj)?,
|
||||
InputItem::Bytes(bytes) => bytes.clone(),
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<Vec<u8>>, CommonError>>()?,
|
||||
)
|
||||
.map_err(|err| {
|
||||
CommonError::serilization(err, "Failed to serialize sequence of bytes with `bincode`")
|
||||
})
|
||||
}
|
||||
}
|
||||
525
crates/ere-dockerized/src/lib.rs
Normal file
525
crates/ere-dockerized/src/lib.rs
Normal file
@@ -0,0 +1,525 @@
|
||||
//! # Ere Dockerized
|
||||
//!
|
||||
//! A Docker-based wrapper for other zkVM crates `ere-{zkvm}`.
|
||||
//!
|
||||
//! This crate provides a unified interface to dockerize the `Compiler` and
|
||||
//! `zkVM` implementation of other zkVM crates `ere-{zkvm}`, it requires only
|
||||
//! `docker` to be installed, but no zkVM specific SDK.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
//! use ere_dockerized::{EreDockerizedCompiler, EreDockerizedzkVM, ErezkVM};
|
||||
//! use zkvm_interface::{Compiler, Input, ProverResourceType, zkVM};
|
||||
//! use std::path::Path;
|
||||
//!
|
||||
//! // Compile a guest program
|
||||
//! let compiler = EreDockerizedCompiler::new(ErezkVM::SP1, "mounting/directory");
|
||||
//! let guest_path = Path::new("relative/path/to/guest/program");
|
||||
//! let program = compiler.compile(&guest_path)?;
|
||||
//!
|
||||
//! // Create zkVM instance
|
||||
//! let zkvm = EreDockerizedzkVM::new(
|
||||
//! ErezkVM::SP1,
|
||||
//! program,
|
||||
//! ProverResourceType::Cpu
|
||||
//! )?;
|
||||
//!
|
||||
//! // Prepare inputs
|
||||
//! let mut inputs = Input::new();
|
||||
//! inputs.write(42u32);
|
||||
//! inputs.write(100u16);
|
||||
//!
|
||||
//! // Execute program
|
||||
//! let execution_report = zkvm.execute(&inputs)?;
|
||||
//! println!("Execution cycles: {}", execution_report.total_num_cycles);
|
||||
//!
|
||||
//! // Generate proof
|
||||
//! let (proof, proving_report) = zkvm.prove(&inputs)?;
|
||||
//! println!("Proof generated in: {:?}", proving_report.proving_time);
|
||||
//!
|
||||
//! // Verify proof
|
||||
//! zkvm.verify(&proof)?;
|
||||
//! println!("Proof verified successfully!");
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
|
||||
|
||||
use crate::{
|
||||
docker::{DockerBuildCmd, DockerRunCmd, docker_image_exists},
|
||||
error::{CommonError, CompileError, DockerizedError, ExecuteError, ProveError, VerifyError},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
env,
|
||||
fmt::{self, Display, Formatter},
|
||||
fs, iter,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
use tempfile::TempDir;
|
||||
use zkvm_interface::{
|
||||
Compiler, Input, ProgramExecutionReport, ProgramProvingReport, ProverResourceType, zkVM,
|
||||
zkVMError,
|
||||
};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/crate_version.rs"));
|
||||
include!(concat!(env!("OUT_DIR"), "/zkvm_sdk_version_impl.rs"));
|
||||
|
||||
pub mod docker;
|
||||
pub mod error;
|
||||
pub mod input;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ErezkVM {
|
||||
Jolt,
|
||||
Nexus,
|
||||
OpenVM,
|
||||
Pico,
|
||||
Risc0,
|
||||
SP1,
|
||||
Zisk,
|
||||
}
|
||||
|
||||
impl ErezkVM {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Jolt => "jolt",
|
||||
Self::Nexus => "nexus",
|
||||
Self::OpenVM => "openvm",
|
||||
Self::Pico => "pico",
|
||||
Self::Risc0 => "risc0",
|
||||
Self::SP1 => "sp1",
|
||||
Self::Zisk => "zisk",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn base_tag(&self, version: &str) -> String {
|
||||
format!("ere-base:{version}")
|
||||
}
|
||||
|
||||
pub fn base_zkvm_tag(&self, version: &str) -> String {
|
||||
format!("ere-base-{self}:{version}")
|
||||
}
|
||||
|
||||
pub fn cli_zkvm_tag(&self, version: &str) -> String {
|
||||
format!("ere-cli-{self}:{version}")
|
||||
}
|
||||
|
||||
/// This method builds 3 Docker images in sequence:
|
||||
/// 1. `ere-base:latest`: Base image with common dependencies
|
||||
/// 2. `ere-base-{zkvm}:latest`: zkVM-specific base image with the zkVM SDK
|
||||
/// 3. `ere-cli-{zkvm}:latest`: CLI image with the `ere-cli` binary built with feature `{zkvm}`
|
||||
///
|
||||
/// Images are cached and only rebuilt if they don't exist or if the
|
||||
/// `ERE_FORCE_REBUILD_DOCKER_IMAGE=true` environment variable is set.
|
||||
pub fn build_docker_image(&self) -> Result<(), CommonError> {
|
||||
let workspace_dir = workspace_dir();
|
||||
|
||||
let force_rebuild = env::var("ERE_FORCE_REBUILD_DOCKER_IMAGE")
|
||||
.ok()
|
||||
.and_then(|s| s.parse::<bool>().ok())
|
||||
.unwrap_or_default();
|
||||
|
||||
if force_rebuild || !docker_image_exists(self.base_tag(CRATE_VERSION))? {
|
||||
DockerBuildCmd::new()
|
||||
.file(
|
||||
workspace_dir
|
||||
.join("docker")
|
||||
.join("base")
|
||||
.join("Dockerfile.base"),
|
||||
)
|
||||
.tag(self.base_tag(CRATE_VERSION))
|
||||
.tag(self.base_tag("latest"))
|
||||
.exec(&workspace_dir)
|
||||
.map_err(CommonError::DockerBuildCmd)?;
|
||||
}
|
||||
|
||||
if force_rebuild || !docker_image_exists(self.base_zkvm_tag(CRATE_VERSION))? {
|
||||
DockerBuildCmd::new()
|
||||
.file(
|
||||
workspace_dir
|
||||
.join("docker")
|
||||
.join(self.as_str())
|
||||
.join("Dockerfile"),
|
||||
)
|
||||
.tag(self.base_zkvm_tag(CRATE_VERSION))
|
||||
.tag(self.base_zkvm_tag("latest"))
|
||||
.bulid_arg("BASE_IMAGE_TAG", self.base_tag(CRATE_VERSION))
|
||||
.exec(&workspace_dir)
|
||||
.map_err(CommonError::DockerBuildCmd)?;
|
||||
}
|
||||
|
||||
if force_rebuild || !docker_image_exists(self.cli_zkvm_tag(CRATE_VERSION))? {
|
||||
DockerBuildCmd::new()
|
||||
.file(workspace_dir.join("docker").join("cli").join("Dockerfile"))
|
||||
.tag(self.cli_zkvm_tag(CRATE_VERSION))
|
||||
.tag(self.cli_zkvm_tag("latest"))
|
||||
.bulid_arg("BASE_ZKVM_IMAGE_TAG", self.base_zkvm_tag(CRATE_VERSION))
|
||||
.bulid_arg("ZKVM", self.as_str())
|
||||
.exec(&workspace_dir)
|
||||
.map_err(CommonError::DockerBuildCmd)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ErezkVM {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match s {
|
||||
"jolt" => Self::Jolt,
|
||||
"nexus" => Self::Nexus,
|
||||
"openvm" => Self::OpenVM,
|
||||
"pico" => Self::Pico,
|
||||
"risc0" => Self::Risc0,
|
||||
"sp1" => Self::SP1,
|
||||
"zisk" => Self::Zisk,
|
||||
_ => return Err(format!("Unsupported zkvm {s}")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ErezkVM {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EreDockerizedCompiler {
|
||||
zkvm: ErezkVM,
|
||||
mount_directory: PathBuf,
|
||||
}
|
||||
|
||||
impl EreDockerizedCompiler {
|
||||
pub fn new(zkvm: ErezkVM, mount_directory: impl AsRef<Path>) -> Self {
|
||||
Self {
|
||||
zkvm,
|
||||
mount_directory: mount_directory.as_ref().to_path_buf(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for serialized program.
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct SerializedProgram(Vec<u8>);
|
||||
|
||||
impl Compiler for EreDockerizedCompiler {
|
||||
type Error = CompileError;
|
||||
type Program = SerializedProgram;
|
||||
|
||||
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
|
||||
self.zkvm.build_docker_image()?;
|
||||
|
||||
let guest_relative_path = guest_directory
|
||||
.strip_prefix(&self.mount_directory)
|
||||
.map_err(|_| CompileError::GuestNotInMountingDirecty {
|
||||
mounting_directory: self.mount_directory.to_path_buf(),
|
||||
guest_directory: guest_directory.to_path_buf(),
|
||||
})?;
|
||||
let guest_path_in_docker = PathBuf::from("/guest").join(guest_relative_path);
|
||||
|
||||
let tempdir = TempDir::new()
|
||||
.map_err(|err| CommonError::io(err, "Failed to create temporary directory"))?;
|
||||
|
||||
DockerRunCmd::new(self.zkvm.cli_zkvm_tag(CRATE_VERSION))
|
||||
.rm()
|
||||
.volume(&self.mount_directory, "/guest")
|
||||
.volume(tempdir.path(), "/guest-output")
|
||||
.exec([
|
||||
"compile",
|
||||
"--guest-path",
|
||||
guest_path_in_docker.to_string_lossy().as_ref(),
|
||||
"--program-path",
|
||||
"/guest-output/program",
|
||||
])
|
||||
.map_err(CommonError::DockerRunCmd)?;
|
||||
|
||||
let program_path = tempdir.path().join("program");
|
||||
let program = fs::read(&program_path).map_err(|err| {
|
||||
CommonError::io(
|
||||
err,
|
||||
format!(
|
||||
"Failed to read compiled program at {}",
|
||||
program_path.display()
|
||||
),
|
||||
)
|
||||
})?;
|
||||
Ok(SerializedProgram(program))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EreDockerizedzkVM {
|
||||
zkvm: ErezkVM,
|
||||
program: SerializedProgram,
|
||||
resource: ProverResourceType,
|
||||
}
|
||||
|
||||
impl EreDockerizedzkVM {
|
||||
pub fn new(
|
||||
zkvm: ErezkVM,
|
||||
program: SerializedProgram,
|
||||
resource: ProverResourceType,
|
||||
) -> Result<Self, zkVMError> {
|
||||
zkvm.build_docker_image()?;
|
||||
Ok(Self {
|
||||
zkvm,
|
||||
program,
|
||||
resource,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl zkVM for EreDockerizedzkVM {
|
||||
fn execute(&self, inputs: &Input) -> Result<ProgramExecutionReport, zkVMError> {
|
||||
let tempdir = TempDir::new()
|
||||
.map_err(|err| CommonError::io(err, "Failed to create temporary directory"))
|
||||
.map_err(|err| DockerizedError::Execute(ExecuteError::Common(err)))?;
|
||||
|
||||
fs::write(tempdir.path().join("program"), &self.program.0)
|
||||
.map_err(|err| CommonError::io(err, "Failed to write program"))
|
||||
.map_err(|err| DockerizedError::Execute(ExecuteError::Common(err)))?;
|
||||
|
||||
fs::write(
|
||||
tempdir.path().join("input"),
|
||||
self.zkvm
|
||||
.serialize_inputs(inputs)
|
||||
.map_err(|err| DockerizedError::Execute(ExecuteError::Common(err)))?,
|
||||
)
|
||||
.map_err(|err| CommonError::io(err, "Failed to write input"))
|
||||
.map_err(|err| DockerizedError::Execute(ExecuteError::Common(err)))?;
|
||||
|
||||
let mut cmd = DockerRunCmd::new(self.zkvm.cli_zkvm_tag(CRATE_VERSION))
|
||||
.rm()
|
||||
.volume(tempdir.path(), "/workspace");
|
||||
|
||||
if matches!(self.resource, ProverResourceType::Gpu) {
|
||||
cmd = cmd.gpus("all")
|
||||
}
|
||||
|
||||
cmd.exec(
|
||||
iter::empty()
|
||||
.chain([
|
||||
"execute",
|
||||
"--program-path",
|
||||
"/workspace/program",
|
||||
"--input-path",
|
||||
"/workspace/input",
|
||||
"--report-path",
|
||||
"/workspace/report",
|
||||
])
|
||||
.chain(self.resource.to_args()),
|
||||
)
|
||||
.map_err(CommonError::DockerRunCmd)
|
||||
.map_err(|err| DockerizedError::Execute(ExecuteError::Common(err)))?;
|
||||
|
||||
let report_bytes = fs::read(tempdir.path().join("report"))
|
||||
.map_err(|err| CommonError::io(err, "Failed to read report"))
|
||||
.map_err(|err| DockerizedError::Execute(ExecuteError::Common(err)))?;
|
||||
|
||||
let report = bincode::deserialize(&report_bytes)
|
||||
.map_err(|err| CommonError::serilization(err, "Failed to deserialize report"))
|
||||
.map_err(|err| DockerizedError::Prove(ProveError::Common(err)))?;
|
||||
Ok(report)
|
||||
}
|
||||
|
||||
fn prove(&self, inputs: &Input) -> Result<(Vec<u8>, ProgramProvingReport), zkVMError> {
|
||||
let tempdir = TempDir::new()
|
||||
.map_err(|err| CommonError::io(err, "Failed to create temporary directory"))
|
||||
.map_err(|err| DockerizedError::Prove(ProveError::Common(err)))?;
|
||||
|
||||
fs::write(tempdir.path().join("program"), &self.program.0)
|
||||
.map_err(|err| CommonError::io(err, "Failed to write program"))
|
||||
.map_err(|err| DockerizedError::Prove(ProveError::Common(err)))?;
|
||||
fs::write(
|
||||
tempdir.path().join("input"),
|
||||
self.zkvm
|
||||
.serialize_inputs(inputs)
|
||||
.map_err(|err| DockerizedError::Prove(ProveError::Common(err)))?,
|
||||
)
|
||||
.map_err(|err| CommonError::io(err, "Failed to write input"))
|
||||
.map_err(|err| DockerizedError::Prove(ProveError::Common(err)))?;
|
||||
|
||||
let mut cmd = DockerRunCmd::new(self.zkvm.cli_zkvm_tag(CRATE_VERSION))
|
||||
.rm()
|
||||
.volume(tempdir.path(), "/workspace");
|
||||
|
||||
if matches!(self.resource, ProverResourceType::Gpu) {
|
||||
cmd = cmd.gpus("all")
|
||||
}
|
||||
|
||||
cmd.exec(
|
||||
iter::empty()
|
||||
.chain([
|
||||
"prove",
|
||||
"--program-path",
|
||||
"/workspace/program",
|
||||
"--input-path",
|
||||
"/workspace/input",
|
||||
"--proof-path",
|
||||
"/workspace/proof",
|
||||
"--report-path",
|
||||
"/workspace/report",
|
||||
])
|
||||
.chain(self.resource.to_args()),
|
||||
)
|
||||
.map_err(CommonError::DockerRunCmd)
|
||||
.map_err(|err| DockerizedError::Prove(ProveError::Common(err)))?;
|
||||
|
||||
let proof = fs::read(tempdir.path().join("proof"))
|
||||
.map_err(|err| CommonError::io(err, "Failed to read proof"))
|
||||
.map_err(|err| DockerizedError::Prove(ProveError::Common(err)))?;
|
||||
let report_bytes = fs::read(tempdir.path().join("report"))
|
||||
.map_err(|err| CommonError::io(err, "Failed to read report"))
|
||||
.map_err(|err| DockerizedError::Prove(ProveError::Common(err)))?;
|
||||
let report = bincode::deserialize(&report_bytes)
|
||||
.map_err(|err| CommonError::serilization(err, "Failed to deserialize report"))
|
||||
.map_err(|err| DockerizedError::Prove(ProveError::Common(err)))?;
|
||||
Ok((proof, report))
|
||||
}
|
||||
|
||||
fn verify(&self, proof: &[u8]) -> Result<(), zkVMError> {
|
||||
let tempdir = TempDir::new()
|
||||
.map_err(|err| CommonError::io(err, "Failed to create temporary directory"))
|
||||
.map_err(|err| DockerizedError::Verify(VerifyError::Common(err)))?;
|
||||
|
||||
fs::write(tempdir.path().join("program"), &self.program.0)
|
||||
.map_err(|err| CommonError::io(err, "Failed to write program"))
|
||||
.map_err(|err| DockerizedError::Verify(VerifyError::Common(err)))?;
|
||||
fs::write(tempdir.path().join("proof"), proof)
|
||||
.map_err(|err| CommonError::io(err, "Failed to write proof"))
|
||||
.map_err(|err| DockerizedError::Verify(VerifyError::Common(err)))?;
|
||||
|
||||
DockerRunCmd::new(self.zkvm.cli_zkvm_tag(CRATE_VERSION))
|
||||
.rm()
|
||||
.volume(tempdir.path(), "/workspace")
|
||||
.exec([
|
||||
"verify",
|
||||
"--program-path",
|
||||
"/workspace/program",
|
||||
"--proof-path",
|
||||
"/workspace/proof",
|
||||
])
|
||||
.map_err(CommonError::DockerRunCmd)
|
||||
.map_err(|err| DockerizedError::Verify(VerifyError::Common(err)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
self.zkvm.as_str()
|
||||
}
|
||||
|
||||
fn sdk_version(&self) -> &'static str {
|
||||
self.zkvm.sdk_version()
|
||||
}
|
||||
}
|
||||
|
||||
fn workspace_dir() -> PathBuf {
|
||||
let mut dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
dir.pop();
|
||||
dir.pop();
|
||||
dir.canonicalize().unwrap()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{EreDockerizedCompiler, EreDockerizedzkVM, ErezkVM, workspace_dir};
|
||||
use zkvm_interface::{Compiler, Input, ProverResourceType, zkVM};
|
||||
|
||||
// TODO: Test other ere-{zkvm} when they are end-to-end ready:
|
||||
// - ere-jolt
|
||||
// - ere-nexus
|
||||
// - ere-pico
|
||||
|
||||
#[test]
|
||||
fn dockerized_openvm() {
|
||||
let zkvm = ErezkVM::OpenVM;
|
||||
|
||||
let guest_directory = workspace_dir().join(format!("tests/{zkvm}/compile/basic"));
|
||||
let program = EreDockerizedCompiler::new(zkvm, workspace_dir())
|
||||
.compile(&guest_directory)
|
||||
.unwrap();
|
||||
|
||||
let zkvm = EreDockerizedzkVM::new(zkvm, program, ProverResourceType::Cpu).unwrap();
|
||||
|
||||
let mut inputs = Input::new();
|
||||
inputs.write(42u64);
|
||||
|
||||
let _report = zkvm.execute(&inputs).unwrap();
|
||||
|
||||
let (proof, _report) = zkvm.prove(&inputs).unwrap();
|
||||
|
||||
zkvm.verify(&proof).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dockerized_risc0() {
|
||||
let zkvm = ErezkVM::Risc0;
|
||||
|
||||
let guest_directory = workspace_dir().join(format!("tests/{zkvm}/compile/basic"));
|
||||
let program = EreDockerizedCompiler::new(zkvm, workspace_dir())
|
||||
.compile(&guest_directory)
|
||||
.unwrap();
|
||||
|
||||
let zkvm = EreDockerizedzkVM::new(zkvm, program, ProverResourceType::Cpu).unwrap();
|
||||
|
||||
let mut inputs = Input::new();
|
||||
inputs.write(42u32);
|
||||
|
||||
let _report = zkvm.execute(&inputs).unwrap();
|
||||
|
||||
let (proof, _report) = zkvm.prove(&inputs).unwrap();
|
||||
|
||||
zkvm.verify(&proof).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dockerized_sp1() {
|
||||
let zkvm = ErezkVM::SP1;
|
||||
|
||||
let guest_directory = workspace_dir().join(format!("tests/{zkvm}/prove/basic"));
|
||||
let program = EreDockerizedCompiler::new(zkvm, workspace_dir())
|
||||
.compile(&guest_directory)
|
||||
.unwrap();
|
||||
|
||||
let zkvm = EreDockerizedzkVM::new(zkvm, program, ProverResourceType::Cpu).unwrap();
|
||||
|
||||
let mut inputs = Input::new();
|
||||
inputs.write(42u32);
|
||||
inputs.write(42u16);
|
||||
|
||||
let _report = zkvm.execute(&inputs).unwrap();
|
||||
|
||||
let (proof, _report) = zkvm.prove(&inputs).unwrap();
|
||||
|
||||
zkvm.verify(&proof).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dockerized_zisk() {
|
||||
let zkvm = ErezkVM::Zisk;
|
||||
|
||||
let guest_directory = workspace_dir().join(format!("tests/{zkvm}/prove/basic"));
|
||||
let program = EreDockerizedCompiler::new(zkvm, workspace_dir())
|
||||
.compile(&guest_directory)
|
||||
.unwrap();
|
||||
|
||||
let zkvm = EreDockerizedzkVM::new(zkvm, program, ProverResourceType::Cpu).unwrap();
|
||||
|
||||
let mut inputs = Input::new();
|
||||
inputs.write(42u32);
|
||||
inputs.write(42u16);
|
||||
|
||||
let _report = zkvm.execute(&inputs).unwrap();
|
||||
|
||||
let (proof, _report) = zkvm.prove(&inputs).unwrap();
|
||||
|
||||
zkvm.verify(&proof).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -27,14 +27,9 @@ impl Compiler for JOLT_TARGET {
|
||||
|
||||
type Program = Vec<u8>;
|
||||
|
||||
fn compile(
|
||||
workspace_directory: &Path,
|
||||
guest_relative: &Path,
|
||||
) -> Result<Self::Program, Self::Error> {
|
||||
let guest_dir = workspace_directory.join(guest_relative);
|
||||
|
||||
fn compile(&self, guest_dir: &Path) -> Result<Self::Program, Self::Error> {
|
||||
// Change current directory for `Program::build` to build guest program.
|
||||
set_current_dir(&guest_dir).map_err(|source| CompileError::SetCurrentDirFailed {
|
||||
set_current_dir(guest_dir).map_err(|source| CompileError::SetCurrentDirFailed {
|
||||
source,
|
||||
path: guest_dir.to_path_buf(),
|
||||
})?;
|
||||
@@ -153,7 +148,7 @@ pub fn program(elf: &[u8]) -> Result<(TempDir, jolt::host::Program), zkVMError>
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{EreJolt, JOLT_TARGET};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::PathBuf;
|
||||
use zkvm_interface::{Compiler, Input, ProverResourceType, zkVM};
|
||||
|
||||
// TODO: for now, we just get one test file
|
||||
@@ -172,29 +167,29 @@ mod tests {
|
||||
#[test]
|
||||
fn test_compile_trait() {
|
||||
let test_guest_path = get_compile_test_guest_program_path();
|
||||
let elf = JOLT_TARGET::compile(&test_guest_path, Path::new("")).unwrap();
|
||||
let elf = JOLT_TARGET.compile(&test_guest_path).unwrap();
|
||||
assert!(!elf.is_empty(), "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, Path::new("")).unwrap();
|
||||
let elf = JOLT_TARGET.compile(&test_guest_path).unwrap();
|
||||
let mut inputs = Input::new();
|
||||
inputs.write(1_u32);
|
||||
|
||||
let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap();
|
||||
let zkvm = EreJolt::new(elf, ProverResourceType::Cpu).unwrap();
|
||||
zkvm.execute(&inputs).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prove_verify() {
|
||||
let test_guest_path = get_compile_test_guest_program_path();
|
||||
let program = JOLT_TARGET::compile(&test_guest_path, Path::new("")).unwrap();
|
||||
let elf = JOLT_TARGET.compile(&test_guest_path).unwrap();
|
||||
let mut inputs = Input::new();
|
||||
inputs.write(1_u32);
|
||||
|
||||
let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap();
|
||||
let zkvm = EreJolt::new(elf, ProverResourceType::Cpu).unwrap();
|
||||
let (proof, _) = zkvm.prove(&inputs).unwrap();
|
||||
zkvm.verify(&proof).unwrap();
|
||||
}
|
||||
|
||||
@@ -29,19 +29,14 @@ impl Compiler for NEXUS_TARGET {
|
||||
|
||||
type Program = PathBuf;
|
||||
|
||||
fn compile(
|
||||
workspace_directory: &Path,
|
||||
guest_relative: &Path,
|
||||
) -> Result<Self::Program, Self::Error> {
|
||||
let guest_path = workspace_directory.join(guest_relative);
|
||||
|
||||
fn compile(&self, guest_path: &Path) -> Result<Self::Program, Self::Error> {
|
||||
// 1. Check guest path
|
||||
if !guest_path.exists() {
|
||||
return Err(NexusError::PathNotFound(guest_path.to_path_buf()));
|
||||
}
|
||||
std::env::set_current_dir(&guest_path).map_err(|e| CompileError::Client(e.into()))?;
|
||||
std::env::set_current_dir(guest_path).map_err(|e| CompileError::Client(e.into()))?;
|
||||
|
||||
let package_name = get_cargo_package_name(&guest_path)
|
||||
let package_name = get_cargo_package_name(guest_path)
|
||||
.ok_or(CompileError::Client(Box::from(format!(
|
||||
"Failed to get guest package name, where guest path: {:?}",
|
||||
guest_path
|
||||
@@ -182,7 +177,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_compile() -> anyhow::Result<()> {
|
||||
let test_guest_path = get_test_guest_program_path();
|
||||
let elf_path = NEXUS_TARGET::compile(&test_guest_path, Path::new(""))?;
|
||||
let elf_path = NEXUS_TARGET.compile(&test_guest_path)?;
|
||||
let prover: Stwo<Local> = Stwo::new_from_file(&elf_path.to_string_lossy().to_string())?;
|
||||
let elf = prover.elf.clone();
|
||||
assert!(
|
||||
@@ -195,8 +190,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_execute() {
|
||||
let test_guest_path = get_test_guest_program_path();
|
||||
let elf =
|
||||
NEXUS_TARGET::compile(&test_guest_path, Path::new("")).expect("compilation failed");
|
||||
let elf = NEXUS_TARGET
|
||||
.compile(&test_guest_path)
|
||||
.expect("compilation failed");
|
||||
let mut input = Input::new();
|
||||
input.write(10u64);
|
||||
|
||||
@@ -207,7 +203,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_prove_verify() -> anyhow::Result<()> {
|
||||
let test_guest_path = get_test_guest_program_path();
|
||||
let elf = NEXUS_TARGET::compile(&test_guest_path, Path::new(""))?;
|
||||
let elf = NEXUS_TARGET.compile(&test_guest_path)?;
|
||||
let mut input = Input::new();
|
||||
input.write(10u64);
|
||||
|
||||
|
||||
@@ -37,9 +37,8 @@ impl Compiler for OPENVM_TARGET {
|
||||
type Program = OpenVMProgram;
|
||||
|
||||
// Inlining `openvm_sdk::Sdk::build` in order to get raw elf bytes.
|
||||
fn compile(workspace_path: &Path, guest_relative: &Path) -> Result<Self::Program, Self::Error> {
|
||||
let guest_directory = workspace_path.join(guest_relative);
|
||||
let pkg = openvm_build::get_package(&guest_directory);
|
||||
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
|
||||
let pkg = openvm_build::get_package(guest_directory);
|
||||
let guest_opts = GuestOptions::default().with_profile("release".to_string());
|
||||
let target_dir = match openvm_build::build_guest_package(&pkg, &guest_opts, None, &None) {
|
||||
Ok(target_dir) => target_dir,
|
||||
@@ -47,7 +46,7 @@ impl Compiler for OPENVM_TARGET {
|
||||
Err(None) => return Err(CompileError::BuildSkipped.into()),
|
||||
};
|
||||
|
||||
let elf_path = openvm_build::find_unique_executable(&guest_directory, target_dir, &None)
|
||||
let elf_path = openvm_build::find_unique_executable(guest_directory, target_dir, &None)
|
||||
.map_err(|e| CompileError::UniqueElfNotFound(e.into()))?;
|
||||
let elf = fs::read(&elf_path).map_err(|source| CompileError::ReadElfFailed {
|
||||
source,
|
||||
@@ -225,8 +224,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_compile() {
|
||||
let test_guest_path = get_compile_test_guest_program_path();
|
||||
let program =
|
||||
OPENVM_TARGET::compile(&test_guest_path, Path::new("")).expect("compilation failed");
|
||||
let program = OPENVM_TARGET.compile(&test_guest_path).unwrap();
|
||||
assert!(!program.elf.is_empty(), "ELF bytes should not be empty.");
|
||||
}
|
||||
|
||||
@@ -235,8 +233,7 @@ 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 program =
|
||||
OPENVM_TARGET::compile(&test_guest_path, Path::new("")).expect("compilation failed");
|
||||
let program = OPENVM_TARGET.compile(&test_guest_path).unwrap();
|
||||
let empty_input = Input::new();
|
||||
let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap();
|
||||
|
||||
@@ -246,8 +243,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_execute() {
|
||||
let test_guest_path = get_compile_test_guest_program_path();
|
||||
let program =
|
||||
OPENVM_TARGET::compile(&test_guest_path, Path::new("")).expect("compilation failed");
|
||||
let program = OPENVM_TARGET.compile(&test_guest_path).unwrap();
|
||||
let mut input = Input::new();
|
||||
input.write(10u64);
|
||||
|
||||
@@ -258,8 +254,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_prove_verify() {
|
||||
let test_guest_path = get_compile_test_guest_program_path();
|
||||
let program =
|
||||
OPENVM_TARGET::compile(&test_guest_path, Path::new("")).expect("compilation failed");
|
||||
let program = OPENVM_TARGET.compile(&test_guest_path).unwrap();
|
||||
let mut input = Input::new();
|
||||
input.write(10u64);
|
||||
|
||||
|
||||
@@ -17,12 +17,7 @@ impl Compiler for PICO_TARGET {
|
||||
|
||||
type Program = Vec<u8>;
|
||||
|
||||
fn compile(
|
||||
workspace_directory: &Path,
|
||||
guest_relative: &Path,
|
||||
) -> Result<Self::Program, Self::Error> {
|
||||
let guest_path = workspace_directory.join(guest_relative);
|
||||
|
||||
fn compile(&self, guest_path: &Path) -> Result<Self::Program, Self::Error> {
|
||||
// 1. Check guest path
|
||||
if !guest_path.exists() {
|
||||
return Err(PicoError::PathNotFound(guest_path.to_path_buf()));
|
||||
@@ -30,7 +25,7 @@ impl Compiler for PICO_TARGET {
|
||||
|
||||
// 2. Run `cargo pico build`
|
||||
let status = Command::new("cargo")
|
||||
.current_dir(&guest_path)
|
||||
.current_dir(guest_path)
|
||||
.env("RUST_LOG", "info")
|
||||
.args(["pico", "build"])
|
||||
.status()?; // From<io::Error> → Spawn
|
||||
@@ -145,7 +140,7 @@ impl zkVM for ErePico {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::PICO_TARGET;
|
||||
use std::{path::Path, path::PathBuf};
|
||||
use std::path::PathBuf;
|
||||
use zkvm_interface::Compiler;
|
||||
|
||||
fn get_compile_test_guest_program_path() -> PathBuf {
|
||||
@@ -172,7 +167,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, Path::new("")) {
|
||||
match PICO_TARGET.compile(&test_guest_path) {
|
||||
Ok(elf_bytes) => {
|
||||
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
|
||||
}
|
||||
|
||||
@@ -7,8 +7,9 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
zkvm-interface = { workspace = true }
|
||||
build-utils = { workspace = true }
|
||||
anyhow = "1.0"
|
||||
# Use `risc0-build` to build package instead of `cargo-risczero` for `unstable` features
|
||||
risc0-build = { version = "^2.3.0", features = ["unstable"] }
|
||||
risc0-zkvm = { version = "^2.3.0", features = ["unstable"] }
|
||||
borsh = "1.5.7"
|
||||
hex = "*"
|
||||
@@ -20,6 +21,7 @@ serde = { version = "1.0.219", features = ["derive", "rc"] }
|
||||
tracing = "0.1"
|
||||
bytemuck = "1.13"
|
||||
bincode = "1.3"
|
||||
cargo_metadata = "0.19"
|
||||
|
||||
[build-dependencies]
|
||||
build-utils = { workspace = true }
|
||||
|
||||
@@ -1,80 +1,42 @@
|
||||
use crate::error::CompileError;
|
||||
use build_utils::docker;
|
||||
use cargo_metadata::MetadataCommand;
|
||||
use risc0_build::GuestOptions;
|
||||
use risc0_zkvm::Digest;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
use tempfile::TempDir;
|
||||
use std::path::Path;
|
||||
use tracing::info;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Risc0Program {
|
||||
// TODO: Seems like the risc0 compilation is also compiling
|
||||
// TODO: the analogous prover and verifying key
|
||||
pub(crate) elf: Vec<u8>,
|
||||
pub(crate) image_id: Digest,
|
||||
}
|
||||
|
||||
pub fn compile_risc0_program(
|
||||
workspace_directory: &Path,
|
||||
guest_program_relative: &Path,
|
||||
) -> Result<Risc0Program, CompileError> {
|
||||
// Build the SP1 docker image
|
||||
let tag = "ere-risc0-cli:latest";
|
||||
docker::build_image(&PathBuf::from("docker/risc0/Dockerfile"), tag)
|
||||
.map_err(|e| CompileError::DockerImageBuildFailed(Box::new(e)))?;
|
||||
pub fn compile_risc0_program(guest_directory: &Path) -> Result<Risc0Program, CompileError> {
|
||||
info!("Compiling Risc0 program at {}", guest_directory.display());
|
||||
|
||||
// Prepare paths for compilation
|
||||
let mount_directory_str = workspace_directory
|
||||
.to_str()
|
||||
.ok_or_else(|| CompileError::InvalidMountPath(workspace_directory.to_path_buf()))?;
|
||||
let metadata = MetadataCommand::new().current_dir(guest_directory).exec()?;
|
||||
let package = metadata
|
||||
.root_package()
|
||||
.ok_or_else(|| CompileError::MissingPackageName {
|
||||
path: guest_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()))?;
|
||||
let guest =
|
||||
risc0_build::build_package(package, &metadata.target_directory, GuestOptions::default())
|
||||
.map_err(|source| CompileError::Risc0BuildFailure {
|
||||
source,
|
||||
crate_path: guest_directory.to_path_buf(),
|
||||
})?
|
||||
.into_iter()
|
||||
.next()
|
||||
.ok_or(CompileError::Risc0BuildMissingGuest)?;
|
||||
|
||||
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 elf = guest.elf.to_vec();
|
||||
let image_id = guest.image_id;
|
||||
|
||||
info!(
|
||||
"Compiling program: mount_directory={} guest_program={}",
|
||||
mount_directory_str, container_guest_program_str
|
||||
);
|
||||
|
||||
// Build and run Docker command
|
||||
let docker_cmd = docker::DockerRunCommand::new(tag)
|
||||
.remove_after_run()
|
||||
// Needed by `cargo risczero build` which uses docker in docker.
|
||||
.with_volume("/var/run/docker.sock", "/var/run/docker.sock")
|
||||
.with_volume(mount_directory_str, "/guest-workspace")
|
||||
.with_volume(elf_output_dir_str, "/output")
|
||||
.with_command(["compile", container_guest_program_str, "/output"]);
|
||||
|
||||
let status = docker_cmd
|
||||
.run()
|
||||
.map_err(CompileError::DockerCommandFailed)?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(CompileError::DockerContainerRunFailed(status));
|
||||
}
|
||||
|
||||
// Read the compiled ELF program from the output directory
|
||||
let elf = std::fs::read(elf_output_dir.path().join("guest.elf"))
|
||||
.map_err(CompileError::ReadCompiledELFProgram)?;
|
||||
let image_id = std::fs::read(elf_output_dir.path().join("image_id"))
|
||||
.and_then(|image_id| {
|
||||
Digest::try_from(image_id)
|
||||
.map_err(|image_id| format!("Invalid image id: {image_id:?}"))
|
||||
.map_err(std::io::Error::other)
|
||||
})
|
||||
.map_err(CompileError::ReadImageId)?;
|
||||
info!("Risc0 program compiled OK - {} bytes", elf.len());
|
||||
info!("Image ID - {image_id}");
|
||||
|
||||
Ok(Risc0Program { elf, image_id })
|
||||
}
|
||||
@@ -83,7 +45,7 @@ pub fn compile_risc0_program(
|
||||
mod tests {
|
||||
mod compile {
|
||||
use crate::compile::compile_risc0_program;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn get_test_risc0_methods_crate_path() -> PathBuf {
|
||||
let workspace_dir = env!("CARGO_WORKSPACE_DIR");
|
||||
@@ -100,8 +62,8 @@ mod tests {
|
||||
fn test_compile_risc0_method() {
|
||||
let test_methods_path = get_test_risc0_methods_crate_path();
|
||||
|
||||
let program = compile_risc0_program(&test_methods_path, Path::new(""))
|
||||
.expect("risc0 compilation failed");
|
||||
let program =
|
||||
compile_risc0_program(&test_methods_path).expect("risc0 compilation failed");
|
||||
assert!(
|
||||
!program.elf.is_empty(),
|
||||
"Risc0 ELF bytes should not be empty."
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::path::PathBuf;
|
||||
use std::{io, path::PathBuf};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -9,24 +9,28 @@ pub enum Risc0Error {
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CompileError {
|
||||
#[error("Failed to build Docker image: {0}")]
|
||||
DockerImageBuildFailed(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||
#[error("Docker command failed to execute: {0}")]
|
||||
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}")]
|
||||
CreatingTempOutputDirectoryFailed(#[source] std::io::Error),
|
||||
#[error("Failed to create temporary output path: {0}")]
|
||||
InvalidTempOutputPath(PathBuf),
|
||||
#[error("Failed to read compiled ELF program: {0}")]
|
||||
ReadCompiledELFProgram(#[source] std::io::Error),
|
||||
#[error("Failed to read image id: {0}")]
|
||||
ReadImageId(#[source] std::io::Error),
|
||||
#[error("Failed to compute image id: {0}")]
|
||||
ComputeImaegIdFailed(#[source] anyhow::Error),
|
||||
#[error("{context}: {source}")]
|
||||
Io {
|
||||
#[source]
|
||||
source: io::Error,
|
||||
context: &'static str,
|
||||
},
|
||||
#[error("`cargo metadata` failed: {0}")]
|
||||
MetadataCommand(#[from] cargo_metadata::Error),
|
||||
#[error("Could not find `[package].name` in guest Cargo.toml at {path}")]
|
||||
MissingPackageName { path: PathBuf },
|
||||
#[error("`risc0_build::build_package` for {crate_path} failed: {source}")]
|
||||
Risc0BuildFailure {
|
||||
#[source]
|
||||
source: anyhow::Error,
|
||||
crate_path: PathBuf,
|
||||
},
|
||||
#[error("`risc0_build::build_package` succeeded but failed to find guest")]
|
||||
Risc0BuildMissingGuest,
|
||||
}
|
||||
|
||||
impl CompileError {
|
||||
pub fn io(e: io::Error, context: &'static str) -> Self {
|
||||
Self::Io { source: e, context }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
use build_utils::docker;
|
||||
use crate::error::Risc0Error;
|
||||
use compile::compile_risc0_program;
|
||||
use risc0_zkvm::Receipt;
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use tempfile::TempDir;
|
||||
use risc0_zkvm::{ExecutorEnv, ProverOpts, Receipt, default_executor, default_prover};
|
||||
use std::{path::Path, time::Instant};
|
||||
use zkvm_interface::{
|
||||
Compiler, Input, InputItem, ProgramExecutionReport, ProgramProvingReport, ProverResourceType,
|
||||
zkVM, zkVMError,
|
||||
@@ -14,10 +10,9 @@ use zkvm_interface::{
|
||||
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
|
||||
|
||||
mod compile;
|
||||
pub use compile::Risc0Program;
|
||||
|
||||
mod error;
|
||||
use error::Risc0Error;
|
||||
|
||||
pub use compile::Risc0Program;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct RV32_IM_RISC0_ZKVM_ELF;
|
||||
@@ -27,11 +22,8 @@ impl Compiler for RV32_IM_RISC0_ZKVM_ELF {
|
||||
|
||||
type Program = Risc0Program;
|
||||
|
||||
fn compile(
|
||||
workspace_directory: &Path,
|
||||
guest_relative: &Path,
|
||||
) -> Result<Self::Program, Self::Error> {
|
||||
compile_risc0_program(workspace_directory, guest_relative).map_err(Risc0Error::from)
|
||||
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
|
||||
compile_risc0_program(guest_directory).map_err(Risc0Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,94 +63,55 @@ pub struct EreRisc0 {
|
||||
|
||||
impl zkVM for EreRisc0 {
|
||||
fn execute(&self, inputs: &Input) -> Result<ProgramExecutionReport, zkVMError> {
|
||||
// Build the Docker image
|
||||
let tag = "ere-risc0-cli:latest";
|
||||
docker::build_image(&PathBuf::from("docker/risc0/Dockerfile"), tag)
|
||||
.map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
|
||||
// Create temporary directory for file exchange
|
||||
let temp_dir = TempDir::new().map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
let elf_path = temp_dir.path().join("guest.elf");
|
||||
let input_path = temp_dir.path().join("input");
|
||||
let report_path = temp_dir.path().join("report");
|
||||
|
||||
// Write ELF file to temp directory
|
||||
fs::write(&elf_path, &self.program.elf).map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
// Write input bytes to temp directory
|
||||
fs::write(&input_path, &serialize_input(inputs)?)
|
||||
.map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
|
||||
// Run Docker command for execution
|
||||
let status = docker::DockerRunCommand::new(tag)
|
||||
.remove_after_run()
|
||||
.with_volume(temp_dir.path().to_string_lossy().to_string(), "/workspace")
|
||||
.with_command([
|
||||
"execute",
|
||||
"/workspace/guest.elf",
|
||||
"/workspace/input",
|
||||
"/workspace/report",
|
||||
])
|
||||
.run()
|
||||
.map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(zkVMError::Other("Docker execution command failed".into()));
|
||||
let executor = default_executor();
|
||||
let mut env = ExecutorEnv::builder();
|
||||
for input in inputs.iter() {
|
||||
match input {
|
||||
InputItem::Object(serialize) => {
|
||||
env.write(serialize).unwrap();
|
||||
}
|
||||
InputItem::Bytes(items) => {
|
||||
env.write_frame(items);
|
||||
}
|
||||
}
|
||||
}
|
||||
let env = env.build().map_err(|err| zkVMError::Other(err.into()))?;
|
||||
|
||||
// Read the execution report from the output file
|
||||
let report: ProgramExecutionReport = bincode::deserialize(
|
||||
&fs::read(report_path).map_err(|e| zkVMError::Other(Box::new(e)))?,
|
||||
)
|
||||
.map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
|
||||
Ok(report)
|
||||
let start = Instant::now();
|
||||
let session_info = executor
|
||||
.execute(env, &self.program.elf)
|
||||
.map_err(|err| zkVMError::Other(err.into()))?;
|
||||
Ok(ProgramExecutionReport {
|
||||
total_num_cycles: session_info.cycles() as u64,
|
||||
execution_duration: start.elapsed(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn prove(&self, inputs: &Input) -> Result<(Vec<u8>, ProgramProvingReport), zkVMError> {
|
||||
// Build the Docker image
|
||||
let tag = "ere-risc0-cli:latest";
|
||||
docker::build_image(&PathBuf::from("docker/risc0/Dockerfile"), tag)
|
||||
.map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
|
||||
// Create temporary directory for file exchange
|
||||
let temp_dir = TempDir::new().map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
let elf_path = temp_dir.path().join("guest.elf");
|
||||
let input_path = temp_dir.path().join("input");
|
||||
let proof_path = temp_dir.path().join("proof");
|
||||
let report_path = temp_dir.path().join("report");
|
||||
|
||||
// Write ELF file to temp directory
|
||||
fs::write(&elf_path, &self.program.elf).map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
// Write input bytes to temp directory
|
||||
fs::write(&input_path, &serialize_input(inputs)?)
|
||||
.map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
|
||||
// Run Docker command for proving
|
||||
let status = docker::DockerRunCommand::new(tag)
|
||||
.remove_after_run()
|
||||
.with_volume(temp_dir.path().to_string_lossy().to_string(), "/workspace")
|
||||
.with_command([
|
||||
"prove",
|
||||
"/workspace/guest.elf",
|
||||
"/workspace/input",
|
||||
"/workspace/proof",
|
||||
"/workspace/report",
|
||||
])
|
||||
.run()
|
||||
.map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(zkVMError::Other("Docker proving command failed".into()));
|
||||
let prover = default_prover();
|
||||
let mut env = ExecutorEnv::builder();
|
||||
for input in inputs.iter() {
|
||||
match input {
|
||||
InputItem::Object(serialize) => {
|
||||
env.write(serialize).unwrap();
|
||||
}
|
||||
InputItem::Bytes(items) => {
|
||||
env.write_frame(items);
|
||||
}
|
||||
}
|
||||
}
|
||||
let env = env.build().map_err(|err| zkVMError::Other(err.into()))?;
|
||||
|
||||
// Read the proof from the output file
|
||||
let proof = fs::read(proof_path).map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
let report = bincode::deserialize(
|
||||
&fs::read(report_path).map_err(|e| zkVMError::Other(Box::new(e)))?,
|
||||
)
|
||||
.map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
let now = std::time::Instant::now();
|
||||
let prove_info = prover
|
||||
.prove_with_opts(env, &self.program.elf, &ProverOpts::succinct())
|
||||
.map_err(|err| zkVMError::Other(err.into()))?;
|
||||
let proving_time = now.elapsed();
|
||||
|
||||
Ok((proof, report))
|
||||
let encoded =
|
||||
borsh::to_vec(&prove_info.receipt).map_err(|err| zkVMError::Other(Box::new(err)))?;
|
||||
Ok((encoded, ProgramProvingReport::new(proving_time)))
|
||||
}
|
||||
|
||||
fn verify(&self, proof: &[u8]) -> Result<(), zkVMError> {
|
||||
@@ -179,25 +132,6 @@ impl zkVM for EreRisc0 {
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize input bytes in the same way as the `ExecutorEnvBuilder`.
|
||||
fn serialize_input(inputs: &Input) -> Result<Vec<u8>, zkVMError> {
|
||||
let mut input_bytes = Vec::new();
|
||||
for input in inputs.iter() {
|
||||
match input {
|
||||
InputItem::Object(serialize) => {
|
||||
let vec = risc0_zkvm::serde::to_vec(serialize)
|
||||
.map_err(|e| zkVMError::Other(Box::new(e)))?;
|
||||
input_bytes.extend_from_slice(bytemuck::cast_slice(&vec));
|
||||
}
|
||||
InputItem::Bytes(items) => {
|
||||
input_bytes.extend_from_slice(&(items.len() as u32).to_le_bytes());
|
||||
input_bytes.extend_from_slice(items);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(input_bytes)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod prove_tests {
|
||||
use std::path::PathBuf;
|
||||
@@ -218,7 +152,7 @@ mod prove_tests {
|
||||
|
||||
fn get_compiled_test_r0_elf_for_prove() -> Result<Risc0Program, Risc0Error> {
|
||||
let test_guest_path = get_prove_test_guest_program_path();
|
||||
RV32_IM_RISC0_ZKVM_ELF::compile(&test_guest_path, Path::new(""))
|
||||
RV32_IM_RISC0_ZKVM_ELF.compile(&test_guest_path)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -266,7 +200,7 @@ mod execute_tests {
|
||||
|
||||
fn get_compiled_test_r0_elf() -> Result<Risc0Program, Risc0Error> {
|
||||
let test_guest_path = get_execute_test_guest_program_path();
|
||||
RV32_IM_RISC0_ZKVM_ELF::compile(&test_guest_path, Path::new(""))
|
||||
RV32_IM_RISC0_ZKVM_ELF.compile(&test_guest_path)
|
||||
}
|
||||
|
||||
fn get_execute_test_guest_program_path() -> PathBuf {
|
||||
|
||||
@@ -7,12 +7,12 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
zkvm-interface = { workspace = true }
|
||||
build-utils.workspace = true
|
||||
sp1-sdk = "5.1.0"
|
||||
tempfile = "3.3"
|
||||
bincode = "1.3"
|
||||
thiserror = "2"
|
||||
tracing = "0.1"
|
||||
toml.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
build-utils.workspace = true
|
||||
|
||||
@@ -1,65 +1,96 @@
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use build_utils::docker;
|
||||
use crate::error::CompileError;
|
||||
use std::{fs, path::Path, process::Command};
|
||||
use tempfile::TempDir;
|
||||
use tracing::info;
|
||||
|
||||
use crate::error::CompileError;
|
||||
pub fn compile(guest_directory: &Path) -> Result<Vec<u8>, CompileError> {
|
||||
info!("Compiling SP1 program at {}", guest_directory.display());
|
||||
|
||||
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)))?;
|
||||
|
||||
// Prepare paths for compilation
|
||||
let mount_directory_str = workspace_directory
|
||||
.to_str()
|
||||
.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()))?;
|
||||
|
||||
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()))?;
|
||||
|
||||
info!(
|
||||
"Compiling program: mount_directory={} guest_program={}",
|
||||
mount_directory_str, container_guest_program_str
|
||||
);
|
||||
|
||||
// Build and run Docker command
|
||||
let docker_cmd = docker::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() {
|
||||
return Err(CompileError::DockerContainerRunFailed(status));
|
||||
if !guest_directory.exists() || !guest_directory.is_dir() {
|
||||
return Err(CompileError::InvalidProgramPath(
|
||||
guest_directory.to_path_buf(),
|
||||
));
|
||||
}
|
||||
|
||||
// Read the compiled ELF program from the output directory
|
||||
let elf = std::fs::read(elf_output_dir.path().join("guest.elf"))
|
||||
.map_err(CompileError::ReadCompiledELFProgram)?;
|
||||
let guest_manifest_path = guest_directory.join("Cargo.toml");
|
||||
if !guest_manifest_path.exists() {
|
||||
return Err(CompileError::CargoTomlMissing {
|
||||
program_dir: guest_directory.to_path_buf(),
|
||||
manifest_path: guest_manifest_path.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(elf)
|
||||
// ── read + parse Cargo.toml ───────────────────────────────────────────
|
||||
let manifest_content =
|
||||
fs::read_to_string(&guest_manifest_path).map_err(|e| CompileError::ReadFile {
|
||||
path: guest_manifest_path.clone(),
|
||||
source: e,
|
||||
})?;
|
||||
|
||||
let manifest_toml: toml::Value =
|
||||
manifest_content
|
||||
.parse::<toml::Value>()
|
||||
.map_err(|e| CompileError::ParseCargoToml {
|
||||
path: guest_manifest_path.clone(),
|
||||
source: e,
|
||||
})?;
|
||||
|
||||
let program_name = manifest_toml
|
||||
.get("package")
|
||||
.and_then(|p| p.get("name"))
|
||||
.and_then(|n| n.as_str())
|
||||
.ok_or_else(|| CompileError::MissingPackageName {
|
||||
path: guest_manifest_path.clone(),
|
||||
})?;
|
||||
|
||||
info!("Parsed program name: {program_name}");
|
||||
|
||||
// ── build into a temp dir ─────────────────────────────────────────────
|
||||
let temp_output_dir = TempDir::new_in(guest_directory)?;
|
||||
let temp_output_dir_path = temp_output_dir.path();
|
||||
|
||||
info!(
|
||||
"Running `cargo prove build` → dir: {}",
|
||||
temp_output_dir_path.display(),
|
||||
);
|
||||
|
||||
let status = Command::new("cargo")
|
||||
.current_dir(guest_directory)
|
||||
.args([
|
||||
"prove",
|
||||
"build",
|
||||
"--output-directory",
|
||||
temp_output_dir_path.to_str().unwrap(),
|
||||
"--elf-name",
|
||||
"guest.elf",
|
||||
])
|
||||
.stdout(std::process::Stdio::inherit())
|
||||
.stderr(std::process::Stdio::inherit())
|
||||
.status()
|
||||
.map_err(|e| CompileError::CargoProveBuild {
|
||||
cwd: guest_directory.to_path_buf(),
|
||||
source: e,
|
||||
})?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(CompileError::CargoBuildFailed {
|
||||
status,
|
||||
path: guest_directory.to_path_buf(),
|
||||
});
|
||||
}
|
||||
|
||||
let elf_path = temp_output_dir_path.join("guest.elf");
|
||||
if !elf_path.exists() {
|
||||
return Err(CompileError::ElfNotFound(elf_path));
|
||||
}
|
||||
|
||||
let elf_bytes = fs::read(&elf_path).map_err(|e| CompileError::ReadFile {
|
||||
path: elf_path,
|
||||
source: e,
|
||||
})?;
|
||||
info!("SP1 program compiled OK - {} bytes", elf_bytes.len());
|
||||
|
||||
Ok(elf_bytes)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -88,7 +119,7 @@ mod tests {
|
||||
fn test_compile_sp1_program() {
|
||||
let test_guest_path = get_compile_test_guest_program_path();
|
||||
|
||||
match compile(&test_guest_path, Path::new("")) {
|
||||
match compile(&test_guest_path) {
|
||||
Ok(elf_bytes) => {
|
||||
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
|
||||
}
|
||||
@@ -101,7 +132,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, Path::new("")) {
|
||||
match RV32_IM_SUCCINCT_ZKVM_ELF.compile(&test_guest_path) {
|
||||
Ok(elf_bytes) => {
|
||||
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::path::PathBuf;
|
||||
use std::{path::PathBuf, process::ExitStatus};
|
||||
|
||||
use thiserror::Error;
|
||||
use zkvm_interface::zkVMError;
|
||||
@@ -27,22 +27,41 @@ pub enum SP1Error {
|
||||
/// Errors that can be encountered while compiling a SP1 program
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CompileError {
|
||||
#[error("Failed to build Docker image: {0}")]
|
||||
DockerImageBuildFailed(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||
#[error("Docker command failed to execute: {0}")]
|
||||
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}")]
|
||||
CreatingTempOutputDirectoryFailed(#[source] std::io::Error),
|
||||
#[error("Failed to create temporary output path: {0}")]
|
||||
InvalidTempOutputPath(PathBuf),
|
||||
#[error("Failed to read compiled ELF program: {0}")]
|
||||
ReadCompiledELFProgram(#[source] std::io::Error),
|
||||
#[error("Program path does not exist or is not a directory: {0}")]
|
||||
InvalidProgramPath(PathBuf),
|
||||
#[error(
|
||||
"Cargo.toml not found in program directory: {program_dir}. Expected at: {manifest_path}"
|
||||
)]
|
||||
CargoTomlMissing {
|
||||
program_dir: PathBuf,
|
||||
manifest_path: PathBuf,
|
||||
},
|
||||
#[error("Could not find `[package].name` in guest Cargo.toml at {path}")]
|
||||
MissingPackageName { path: PathBuf },
|
||||
#[error("Compiled ELF not found at expected path: {0}")]
|
||||
ElfNotFound(PathBuf),
|
||||
#[error("`cargo prove build` failed with status: {status} for program at {path}")]
|
||||
CargoBuildFailed { status: ExitStatus, path: PathBuf },
|
||||
#[error("Failed to read file at {path}: {source}")]
|
||||
ReadFile {
|
||||
path: PathBuf,
|
||||
#[source]
|
||||
source: std::io::Error,
|
||||
},
|
||||
#[error("Failed to parse guest Cargo.toml at {path}: {source}")]
|
||||
ParseCargoToml {
|
||||
path: PathBuf,
|
||||
#[source]
|
||||
source: toml::de::Error,
|
||||
},
|
||||
#[error("Failed to execute `cargo prove build` in {cwd}: {source}")]
|
||||
CargoProveBuild {
|
||||
cwd: PathBuf,
|
||||
#[source]
|
||||
source: std::io::Error,
|
||||
},
|
||||
#[error("Failed to create temporary output directory: {0}")]
|
||||
TempDir(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
||||
@@ -106,11 +106,8 @@ impl Compiler for RV32_IM_SUCCINCT_ZKVM_ELF {
|
||||
|
||||
type Program = Vec<u8>;
|
||||
|
||||
fn compile(
|
||||
workspace_directory: &Path,
|
||||
guest_relative: &Path,
|
||||
) -> Result<Self::Program, Self::Error> {
|
||||
compile::compile(workspace_directory, guest_relative).map_err(SP1Error::from)
|
||||
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
|
||||
compile::compile(guest_directory).map_err(SP1Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +232,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, Path::new(""))
|
||||
RV32_IM_SUCCINCT_ZKVM_ELF.compile(&test_guest_path)
|
||||
}
|
||||
|
||||
fn get_execute_test_guest_program_path() -> PathBuf {
|
||||
@@ -306,7 +303,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, Path::new(""))
|
||||
RV32_IM_SUCCINCT_ZKVM_ELF.compile(&test_guest_path)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -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, Path::new("")) {
|
||||
match RV64_IMA_ZISK_ZKVM_ELF.compile(&test_guest_path) {
|
||||
Ok(elf_bytes) => {
|
||||
assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty.");
|
||||
}
|
||||
|
||||
@@ -30,11 +30,8 @@ impl Compiler for RV64_IMA_ZISK_ZKVM_ELF {
|
||||
|
||||
type Program = Vec<u8>;
|
||||
|
||||
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)
|
||||
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
|
||||
compile_zisk_program(guest_directory).map_err(ZiskError::Compile)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,7 +445,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, Path::new(""))
|
||||
RV64_IMA_ZISK_ZKVM_ELF.compile(&test_guest_path)
|
||||
}
|
||||
|
||||
fn get_execute_test_guest_program_path() -> PathBuf {
|
||||
@@ -514,7 +511,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, Path::new(""))
|
||||
RV64_IMA_ZISK_ZKVM_ELF.compile(&test_guest_path)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -20,10 +20,8 @@ pub trait Compiler {
|
||||
/// Compiles the program and returns the program
|
||||
///
|
||||
/// # 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>;
|
||||
/// * `guest_directory` - The path to the guest program directory
|
||||
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error>;
|
||||
}
|
||||
|
||||
/// ResourceType specifies what resource will be used to create the proofs.
|
||||
@@ -37,6 +35,19 @@ pub enum ProverResourceType {
|
||||
Network(NetworkProverConfig),
|
||||
}
|
||||
|
||||
#[cfg(feature = "clap")]
|
||||
impl ProverResourceType {
|
||||
pub fn to_args(&self) -> Vec<&str> {
|
||||
match self {
|
||||
Self::Cpu => vec!["cpu"],
|
||||
Self::Gpu => vec!["gpu"],
|
||||
Self::Network(config) => core::iter::once("network")
|
||||
.chain(config.to_args())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An error that can occur during prove, execute or verification
|
||||
/// of a zkVM.
|
||||
///
|
||||
|
||||
@@ -12,3 +12,13 @@ pub struct NetworkProverConfig {
|
||||
/// Optional API key for authentication
|
||||
pub api_key: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "clap")]
|
||||
impl NetworkProverConfig {
|
||||
pub fn to_args(&self) -> Vec<&str> {
|
||||
core::iter::once(["--endpoint", self.endpoint.as_str()])
|
||||
.chain(self.api_key.as_deref().map(|val| ["--api-key", val]))
|
||||
.flatten()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
16
docker/cli/Dockerfile
Normal file
16
docker/cli/Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
||||
ARG BASE_ZKVM_IMAGE_TAG=ere-base-zkvm:latest
|
||||
|
||||
FROM ${BASE_ZKVM_IMAGE_TAG}
|
||||
|
||||
COPY . /ere
|
||||
|
||||
WORKDIR /ere
|
||||
|
||||
ARG ZKVM
|
||||
|
||||
RUN cargo build --release --package ere-cli --bin ere-cli --features ${ZKVM} && \
|
||||
cp /ere/target/release/ere-cli /ere/ere-cli && \
|
||||
cargo clean && \
|
||||
rm -rf $CARGO_HOME/registry/src $CARGO_HOME/registry/cache
|
||||
|
||||
ENTRYPOINT ["/ere/ere-cli"]
|
||||
@@ -1,5 +1,6 @@
|
||||
ARG BASE_IMAGE_TAG=latest
|
||||
FROM ere-base:${BASE_IMAGE_TAG}
|
||||
ARG BASE_IMAGE_TAG=ere-base:latest
|
||||
|
||||
FROM ${BASE_IMAGE_TAG}
|
||||
|
||||
# The ere-base image provides Rust, Cargo (with a default nightly), and common tools.
|
||||
# We operate as root for SDK installation.
|
||||
@@ -17,4 +18,4 @@ RUN /tmp/install_jolt_sdk.sh && rm /tmp/install_jolt_sdk.sh # Clean up the scrip
|
||||
# Verify jolt CLI is accessible.
|
||||
RUN jolt --version
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
CMD ["/bin/bash"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
ARG BASE_IMAGE_TAG=latest
|
||||
FROM ere-base:${BASE_IMAGE_TAG}
|
||||
ARG BASE_IMAGE_TAG=ere-base:latest
|
||||
|
||||
FROM ${BASE_IMAGE_TAG}
|
||||
|
||||
# The ere-base image provides Rust, Cargo, and common tools.
|
||||
# We operate as root for SDK installation.
|
||||
@@ -20,21 +21,10 @@ RUN /tmp/install_nexus_sdk.sh && rm /tmp/install_nexus_sdk.sh # Clean up the scr
|
||||
# Define the Nexus toolchain for convenience in subsequent commands if needed, though cargo-nexus should use it.
|
||||
ENV NEXUS_TOOLCHAIN_VERSION="nightly-2025-06-05"
|
||||
|
||||
# Set default toolchain
|
||||
RUN rustup default "$NEXUS_TOOLCHAIN_VERSION"
|
||||
|
||||
# Verify Nexus installation
|
||||
RUN echo "Verifying Nexus installation in Dockerfile (post-script)..." && cargo-nexus --version
|
||||
|
||||
# Copy the entire ere project context
|
||||
# The WORKDIR is /app from the base image
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
# Build
|
||||
RUN echo "Build tests for ere-nexus library..." && \
|
||||
cargo build --tests --release -p ere-nexus
|
||||
|
||||
# Run tests
|
||||
RUN echo "Running tests for ere-nexus library..." && \
|
||||
cargo test --release -p ere-nexus --lib -- --color always && \
|
||||
echo "Running Nexus tests Success..."
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
CMD ["/bin/bash"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
ARG BASE_IMAGE_TAG=latest
|
||||
FROM ere-base:${BASE_IMAGE_TAG}
|
||||
ARG BASE_IMAGE_TAG=ere-base:latest
|
||||
|
||||
FROM ${BASE_IMAGE_TAG}
|
||||
|
||||
# The ere-base image provides Rust, Cargo, and common tools.
|
||||
# We operate as root for SDK installation.
|
||||
@@ -20,13 +21,4 @@ ENV OPENVM_TOOLCHAIN_VERSION="nightly-2025-02-14"
|
||||
# Verify cargo-openvm is accessible with the correct toolchain
|
||||
RUN cargo "+${OPENVM_TOOLCHAIN_VERSION}" openvm --version
|
||||
|
||||
# Copy the entire ere project context
|
||||
# The WORKDIR is /app from the base image
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
# Run tests
|
||||
RUN echo "Running tests for ere-openvm library..." && \
|
||||
cargo test --release -p ere-openvm --lib -- --color always
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
CMD ["/bin/bash"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
ARG BASE_IMAGE_TAG=latest
|
||||
FROM ere-base:${BASE_IMAGE_TAG}
|
||||
ARG BASE_IMAGE_TAG=ere-base:latest
|
||||
|
||||
FROM ${BASE_IMAGE_TAG}
|
||||
|
||||
# The ere-base image provides Rust, Cargo, and common tools.
|
||||
# We operate as root for SDK installation.
|
||||
@@ -19,16 +20,10 @@ RUN /tmp/install_pico_sdk.sh && rm /tmp/install_pico_sdk.sh # Clean up the scrip
|
||||
# Define the Pico toolchain for convenience in subsequent commands if needed, though cargo pico should use it.
|
||||
ENV PICO_TOOLCHAIN_VERSION="nightly-2024-11-27"
|
||||
|
||||
# Set default toolchain
|
||||
RUN rustup default "$PICO_TOOLCHAIN_VERSION"
|
||||
|
||||
# Verify Pico installation
|
||||
RUN echo "Verifying Pico installation in Dockerfile (post-script)..." && cargo "+${PICO_TOOLCHAIN_VERSION}" pico --version
|
||||
RUN echo "Verifying Pico installation in Dockerfile (post-script)..." && cargo pico --version
|
||||
|
||||
# Copy the entire ere project context
|
||||
# The WORKDIR is /app from the base image
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
# Run tests
|
||||
RUN echo "Running tests for ere-pico library..." && \
|
||||
cargo "+${PICO_TOOLCHAIN_VERSION}" test --release -p ere-pico --lib -- --color always
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
CMD ["/bin/bash"]
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
[package]
|
||||
name = "risc0-cli"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
tempfile.workspace = true
|
||||
toml.workspace = true
|
||||
tracing.workspace = true
|
||||
clap.workspace = true
|
||||
anyhow.workspace = true
|
||||
hex.workspace = true
|
||||
zkvm-interface.workspace = true
|
||||
risc0-zkvm = { version = "^2.3.0", features = ["unstable"] }
|
||||
borsh = "1.5"
|
||||
bincode = "1.3"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,14 +1,6 @@
|
||||
ARG BASE_IMAGE_TAG=latest
|
||||
ARG BASE_IMAGE_TAG=ere-base:latest
|
||||
|
||||
FROM rust:1.85 AS builder
|
||||
|
||||
WORKDIR /risc0-cli
|
||||
|
||||
# Build `risc0-cli`
|
||||
COPY . .
|
||||
RUN cargo build --release -p risc0-cli
|
||||
|
||||
FROM ere-base:${BASE_IMAGE_TAG}
|
||||
FROM ${BASE_IMAGE_TAG}
|
||||
|
||||
# Copy and run the Risc0 SDK installer script
|
||||
COPY scripts/sdk_installers/install_risc0_sdk.sh /tmp/install_risc0_sdk.sh
|
||||
@@ -22,8 +14,4 @@ RUN echo "Verifying Risc0 installation in Dockerfile (post-script)..." && cargo
|
||||
# Get docker for `cargo risczero build`
|
||||
RUN curl -fsSL https://get.docker.com | sh
|
||||
|
||||
# Copy guest compiler binary
|
||||
COPY --from=builder /risc0-cli/target/release/risc0-cli /risc0-cli/risc0-cli
|
||||
|
||||
# Set entrypoint to `risc0-cli`
|
||||
ENTRYPOINT ["/risc0-cli/risc0-cli"]
|
||||
CMD ["/bin/bash"]
|
||||
|
||||
@@ -1,289 +0,0 @@
|
||||
use anyhow::Context;
|
||||
use clap::{Parser, Subcommand};
|
||||
use risc0_zkvm::{ExecutorEnv, ProverOpts, default_executor, default_prover};
|
||||
use std::{fs, path::PathBuf, process::Command};
|
||||
use toml::Value as TomlValue;
|
||||
use tracing::info;
|
||||
use zkvm_interface::{ProgramExecutionReport, ProgramProvingReport};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Compile a guest program
|
||||
Compile {
|
||||
/// Path to the guest program crate directory.
|
||||
guest_folder: PathBuf,
|
||||
/// Output folder where compiled `guest.elf` and `image_id` will be placed.
|
||||
output_folder: PathBuf,
|
||||
},
|
||||
/// Execute a compiled program
|
||||
Execute {
|
||||
/// Path to the compiled ELF file
|
||||
elf_path: PathBuf,
|
||||
/// Path to the serialized input bytes file
|
||||
input_path: PathBuf,
|
||||
/// Path where the execution report will be written
|
||||
report_path: PathBuf,
|
||||
},
|
||||
/// Prove execution of a compiled program
|
||||
Prove {
|
||||
/// Path to the compiled ELF file
|
||||
elf_path: PathBuf,
|
||||
/// Path to the serialized input bytes file
|
||||
input_path: PathBuf,
|
||||
/// Path where the proof will be written
|
||||
proof_path: PathBuf,
|
||||
/// Path where the report will be written
|
||||
report_path: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn main() -> anyhow::Result<()> {
|
||||
let args = Cli::parse();
|
||||
|
||||
match args.command {
|
||||
Commands::Compile {
|
||||
guest_folder,
|
||||
output_folder,
|
||||
} => compile(guest_folder, output_folder),
|
||||
Commands::Prove {
|
||||
elf_path,
|
||||
input_path,
|
||||
proof_path,
|
||||
report_path,
|
||||
} => prove(elf_path, input_path, proof_path, report_path),
|
||||
Commands::Execute {
|
||||
elf_path,
|
||||
input_path,
|
||||
report_path,
|
||||
} => execute(elf_path, input_path, report_path),
|
||||
}
|
||||
}
|
||||
|
||||
fn compile(guest_folder: PathBuf, output_folder: PathBuf) -> anyhow::Result<()> {
|
||||
let dir = guest_folder;
|
||||
|
||||
info!("Compiling Risc0 program at {}", dir.display());
|
||||
|
||||
if !dir.exists() || !dir.is_dir() {
|
||||
anyhow::bail!(
|
||||
"Program path does not exist or is not a directory: {}",
|
||||
dir.display()
|
||||
);
|
||||
}
|
||||
|
||||
let guest_manifest_path = dir.join("Cargo.toml");
|
||||
if !guest_manifest_path.exists() {
|
||||
anyhow::bail!(
|
||||
"Cargo.toml not found in program directory: {}. Expected at: {}",
|
||||
dir.display(),
|
||||
guest_manifest_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
// ── read + parse Cargo.toml ───────────────────────────────────────────
|
||||
let manifest_content = fs::read_to_string(&guest_manifest_path)
|
||||
.with_context(|| format!("Failed to read file at {}", guest_manifest_path.display()))?;
|
||||
|
||||
let manifest_toml: TomlValue = manifest_content.parse::<TomlValue>().with_context(|| {
|
||||
format!(
|
||||
"Failed to parse guest Cargo.toml at {}",
|
||||
guest_manifest_path.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
let program_name = manifest_toml
|
||||
.get("package")
|
||||
.and_then(|p| p.get("name"))
|
||||
.and_then(|n| n.as_str())
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Could not find `[package].name` in guest Cargo.toml at {}",
|
||||
guest_manifest_path.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
info!("Parsed program name: {program_name}");
|
||||
|
||||
// ── build into a temp dir ─────────────────────────────────────────────
|
||||
info!(
|
||||
"Running `cargo risczero build` → dir: {}",
|
||||
output_folder.display()
|
||||
);
|
||||
|
||||
let output = Command::new("cargo")
|
||||
.current_dir(&dir)
|
||||
.args(["risczero", "build"])
|
||||
.stderr(std::process::Stdio::inherit())
|
||||
.output()
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to execute `cargo risczer build` in {}",
|
||||
dir.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
if !output.status.success() {
|
||||
anyhow::bail!(
|
||||
"Failed to execute `cargo risczero build` in {}",
|
||||
dir.display()
|
||||
)
|
||||
}
|
||||
|
||||
let (image_id, elf_path) = {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let line = stdout
|
||||
.lines()
|
||||
.find(|line| line.starts_with("ImageID: "))
|
||||
.unwrap();
|
||||
let (image_id, elf_path) = line
|
||||
.trim_start_matches("ImageID: ")
|
||||
.split_once(" - ")
|
||||
.unwrap();
|
||||
(image_id.to_string(), PathBuf::from(elf_path))
|
||||
};
|
||||
|
||||
if !elf_path.exists() {
|
||||
anyhow::bail!(
|
||||
"Compiled ELF not found at expected path: {}",
|
||||
elf_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
let elf_bytes = fs::read(&elf_path)
|
||||
.with_context(|| format!("Failed to read file at {}", elf_path.display()))?;
|
||||
info!("Risc0 program compiled OK - {} bytes", elf_bytes.len());
|
||||
info!("Image ID - {image_id}");
|
||||
|
||||
fs::copy(&elf_path, output_folder.join("guest.elf")).with_context(|| {
|
||||
format!(
|
||||
"Failed to copy elf file from {} to {}",
|
||||
elf_path.display(),
|
||||
output_folder.join("guest.elf").display()
|
||||
)
|
||||
})?;
|
||||
fs::write(output_folder.join("image_id"), hex::decode(image_id)?).with_context(|| {
|
||||
format!(
|
||||
"Failed to write image id to {}",
|
||||
output_folder.join("image_id").display()
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute(elf_path: PathBuf, input_path: PathBuf, report_path: PathBuf) -> anyhow::Result<()> {
|
||||
info!("Starting execution for ELF at {}", elf_path.display());
|
||||
|
||||
// Read the ELF file
|
||||
let elf = fs::read(&elf_path)
|
||||
.with_context(|| format!("Failed to read ELF file at {}", elf_path.display()))?;
|
||||
|
||||
// Read the serialized input bytes
|
||||
let input_bytes = fs::read(&input_path)
|
||||
.with_context(|| format!("Failed to read input bytes at {}", input_path.display()))?;
|
||||
|
||||
info!("ELF size: {} bytes", elf.len());
|
||||
info!("Input size: {} bytes", input_bytes.len());
|
||||
|
||||
// Create executor environment using write_slice to write the serialized input bytes directly
|
||||
let executor = default_executor();
|
||||
let env = ExecutorEnv::builder()
|
||||
.write_slice(&input_bytes)
|
||||
.build()
|
||||
.context("Failed to build executor environment")?;
|
||||
|
||||
info!("Starting execution...");
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
// Execute the program
|
||||
let session_info = executor
|
||||
.execute(env, &elf)
|
||||
.context("Failed to execute program")?;
|
||||
|
||||
let execution_duration = start.elapsed();
|
||||
|
||||
info!("Execution completed in {:?}", execution_duration);
|
||||
info!("Total cycles: {}", session_info.cycles());
|
||||
|
||||
// Create execution report
|
||||
let report = ProgramExecutionReport {
|
||||
total_num_cycles: session_info.cycles() as u64,
|
||||
execution_duration,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Serialize and write the report
|
||||
let report_bytes =
|
||||
bincode::serialize(&report).context("Failed to serialize execution report")?;
|
||||
|
||||
fs::write(&report_path, report_bytes)
|
||||
.with_context(|| format!("Failed to write report to {}", report_path.display()))?;
|
||||
|
||||
info!("Execution report written to {}", report_path.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prove(
|
||||
elf_path: PathBuf,
|
||||
input_path: PathBuf,
|
||||
proof_path: PathBuf,
|
||||
report_path: PathBuf,
|
||||
) -> anyhow::Result<()> {
|
||||
info!(
|
||||
"Starting proof generation for ELF at {}",
|
||||
elf_path.display()
|
||||
);
|
||||
|
||||
// Read the ELF file
|
||||
let elf = fs::read(&elf_path)
|
||||
.with_context(|| format!("Failed to read ELF file at {}", elf_path.display()))?;
|
||||
|
||||
// Read the serialized input bytes
|
||||
let input_bytes = fs::read(&input_path)
|
||||
.with_context(|| format!("Failed to read input bytes at {}", input_path.display()))?;
|
||||
|
||||
info!("ELF size: {} bytes", elf.len());
|
||||
info!("Input size: {} bytes", input_bytes.len());
|
||||
|
||||
// Create prover environment using write_slice to write the serialized input bytes directly
|
||||
let prover = default_prover();
|
||||
let env = ExecutorEnv::builder()
|
||||
.write_slice(&input_bytes)
|
||||
.build()
|
||||
.context("Failed to build executor environment")?;
|
||||
|
||||
info!("Starting proof generation...");
|
||||
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
// Generate proof
|
||||
let prove_info = prover
|
||||
.prove_with_opts(env, &elf, &ProverOpts::succinct())
|
||||
.context("Failed to generate proof")?;
|
||||
|
||||
let proving_time = now.elapsed();
|
||||
|
||||
info!("Proof generation completed in {:?}", proving_time);
|
||||
|
||||
// Serialize and write the proof
|
||||
let proof_bytes = borsh::to_vec(&prove_info.receipt).context("Failed to serialize proof")?;
|
||||
fs::write(&proof_path, proof_bytes)
|
||||
.with_context(|| format!("Failed to write proof to {}", proof_path.display()))?;
|
||||
|
||||
let report_bytes = bincode::serialize(&ProgramProvingReport::new(proving_time))
|
||||
.context("Failed to serialize report")?;
|
||||
fs::write(&report_path, report_bytes)
|
||||
.with_context(|| format!("Failed to write report to {}", report_path.display()))?;
|
||||
|
||||
info!("Proof written to {}", proof_path.display());
|
||||
info!("Report written to {}", report_path.display());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
[package]
|
||||
name = "sp1-guest-compiler"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
tempfile.workspace = true
|
||||
toml.workspace = true
|
||||
tracing.workspace = true
|
||||
clap.workspace = true
|
||||
anyhow.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,23 +1,9 @@
|
||||
ARG BASE_IMAGE_TAG=latest
|
||||
ARG BASE_IMAGE_TAG=ere-base:latest
|
||||
|
||||
# Build guest-compiler binary
|
||||
FROM rust:1.85 AS builder
|
||||
RUN apt-get update && apt-get install -y build-essential libclang-dev
|
||||
WORKDIR /guest-compiler
|
||||
COPY . .
|
||||
RUN cargo build --release -p sp1-guest-compiler
|
||||
|
||||
# Build zkVM builder image
|
||||
FROM ere-base:${BASE_IMAGE_TAG}
|
||||
FROM ${BASE_IMAGE_TAG}
|
||||
|
||||
ARG USERNAME=ere_user
|
||||
|
||||
# Ensure Cargo/Rustup environment variables are set from the base image for SDK script
|
||||
# TODO: These should be inherited from ere-base.
|
||||
ENV RUSTUP_HOME=${RUSTUP_HOME:-/usr/local/rustup} \
|
||||
CARGO_HOME=${CARGO_HOME:-/usr/local/cargo} \
|
||||
PATH=${PATH:-/usr/local/cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin}
|
||||
|
||||
# Copy the SP1 SDK installer script
|
||||
COPY scripts/sdk_installers/install_sp1_sdk.sh /tmp/install_sp1_sdk.sh
|
||||
RUN chmod +x /tmp/install_sp1_sdk.sh
|
||||
@@ -41,11 +27,7 @@ ENV PATH="${SP1UP_HOME}/bin:${SP1_HOME}/bin:$PATH"
|
||||
# Verify SP1 installation (optional here, as script does it, but good for sanity)
|
||||
RUN cargo prove --version
|
||||
|
||||
# Copy guest compiler binary
|
||||
COPY --from=builder /guest-compiler/target/release/sp1-guest-compiler /guest-compiler/guest-compiler
|
||||
WORKDIR /guest-compiler
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
CMD ["/bin/bash"]
|
||||
|
||||
# TODO: Maybe we use root to install it in ere_user and then switch back to ere_user for security
|
||||
# USER ${USERNAME} # Switch to non-root user again
|
||||
# USER ${USERNAME} # Switch to non-root user again
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
use std::{fs, path::PathBuf, process::Command};
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::Parser;
|
||||
use toml::Value as TomlValue;
|
||||
use tracing::info;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version)]
|
||||
struct Cli {
|
||||
/// Path to the guest program crate directory.
|
||||
guest_folder: PathBuf,
|
||||
|
||||
/// Compiled ELF output folder where guest.elf will be placed.
|
||||
elf_output_folder: PathBuf,
|
||||
}
|
||||
|
||||
pub fn main() -> anyhow::Result<()> {
|
||||
let args = Cli::parse();
|
||||
|
||||
let dir = args.guest_folder;
|
||||
|
||||
info!("Compiling SP1 program at {}", dir.display());
|
||||
|
||||
if !dir.exists() || !dir.is_dir() {
|
||||
anyhow::bail!(
|
||||
"Program path does not exist or is not a directory: {}",
|
||||
dir.display()
|
||||
);
|
||||
}
|
||||
|
||||
let guest_manifest_path = dir.join("Cargo.toml");
|
||||
if !guest_manifest_path.exists() {
|
||||
anyhow::bail!(
|
||||
"Cargo.toml not found in program directory: {}. Expected at: {}",
|
||||
dir.display(),
|
||||
guest_manifest_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
// ── read + parse Cargo.toml ───────────────────────────────────────────
|
||||
let manifest_content = fs::read_to_string(&guest_manifest_path)
|
||||
.with_context(|| format!("Failed to read file at {}", guest_manifest_path.display()))?;
|
||||
|
||||
let manifest_toml: TomlValue = manifest_content.parse::<TomlValue>().with_context(|| {
|
||||
format!(
|
||||
"Failed to parse guest Cargo.toml at {}",
|
||||
guest_manifest_path.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
let program_name = manifest_toml
|
||||
.get("package")
|
||||
.and_then(|p| p.get("name"))
|
||||
.and_then(|n| n.as_str())
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Could not find `[package].name` in guest Cargo.toml at {}",
|
||||
guest_manifest_path.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
info!("Parsed program name: {program_name}");
|
||||
|
||||
// ── build into a temp dir ─────────────────────────────────────────────
|
||||
info!(
|
||||
"Running `cargo prove build` → dir: {}",
|
||||
args.elf_output_folder.display()
|
||||
);
|
||||
|
||||
let status = Command::new("cargo")
|
||||
.current_dir(&dir)
|
||||
.args([
|
||||
"prove",
|
||||
"build",
|
||||
"--output-directory",
|
||||
args.elf_output_folder.to_str().unwrap(),
|
||||
"--elf-name",
|
||||
"guest.elf",
|
||||
])
|
||||
.stdout(std::process::Stdio::inherit())
|
||||
.stderr(std::process::Stdio::inherit())
|
||||
.status()
|
||||
.with_context(|| format!("Failed to execute `cargo prove build` in {}", dir.display()))?;
|
||||
|
||||
if !status.success() {
|
||||
anyhow::bail!("Failed to execute `cargo prove build` in {}", dir.display())
|
||||
}
|
||||
|
||||
let elf_path = args.elf_output_folder.join("guest.elf");
|
||||
if !elf_path.exists() {
|
||||
anyhow::bail!(
|
||||
"Compiled ELF not found at expected path: {}",
|
||||
elf_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
let elf_bytes = fs::read(&elf_path)
|
||||
.with_context(|| format!("Failed to read file at {}", elf_path.display()))?;
|
||||
info!("SP1 program compiled OK - {} bytes", elf_bytes.len());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
ARG BASE_IMAGE_TAG=latest
|
||||
FROM ere-base:${BASE_IMAGE_TAG}
|
||||
ARG BASE_IMAGE_TAG=ere-base:latest
|
||||
|
||||
FROM ${BASE_IMAGE_TAG}
|
||||
|
||||
# The ere-base image provides Rust, Cargo, and common tools.
|
||||
# ZisK requires Ubuntu 22.04 or higher (ere-base uses 22.04 by default).
|
||||
# ZisK requires Ubuntu 22.04 or higher (ere-base uses 24.04 by default).
|
||||
# We operate as root for SDK and dependency installation.
|
||||
|
||||
# Install ZisK system dependencies (for Ubuntu)
|
||||
# Taken from https://0xpolygonhermez.github.io/zisk/getting_started/installation.html
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
xz-utils \
|
||||
jq \
|
||||
# build-essential is in ere-base
|
||||
# jq is in ere-base
|
||||
# curl is in ere-base
|
||||
# git is in ere-base
|
||||
# build-essential is in ere-base
|
||||
qemu-system \
|
||||
libomp-dev \
|
||||
libgmp-dev \
|
||||
@@ -28,7 +28,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
openmpi-bin \
|
||||
openmpi-common \
|
||||
libclang-dev \
|
||||
clang
|
||||
clang && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN wget https://developer.download.nvidia.com/compute/cuda/repos/$(. /etc/os-release && echo "${ID}${VERSION_ID}" | tr -d '.')/$(uname -i)/cuda-keyring_1.1-1_all.deb && \
|
||||
dpkg -i cuda-keyring_1.1-1_all.deb && \
|
||||
@@ -37,14 +38,21 @@ RUN wget https://developer.download.nvidia.com/compute/cuda/repos/$(. /etc/os-re
|
||||
apt install -y cuda-toolkit && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# If current environment is in CI or not.
|
||||
ARG CI
|
||||
|
||||
# Copy the ZisK SDK installer script from the workspace context
|
||||
COPY scripts/sdk_installers/install_zisk_sdk.sh /tmp/install_zisk_sdk.sh
|
||||
RUN chmod +x /tmp/install_zisk_sdk.sh
|
||||
|
||||
# Run the ZisK SDK installation script using ziskup.
|
||||
# This script installs the 'zisk' Rust toolchain and cargo-zisk.
|
||||
# TODO: Download the proving key if the CI runner has enough disk space.
|
||||
RUN SETUP_KEY=verify /tmp/install_zisk_sdk.sh && rm /tmp/install_zisk_sdk.sh # Clean up the script
|
||||
# This script installs the 'zisk' Rust toolchain and `cargo-zisk`
|
||||
#
|
||||
# If argument `CI` is set, we only install verifying key, this is used by github
|
||||
# CI runner which only has small disk space (proving key requires ~35 GB).
|
||||
RUN if [ -n "$CI" ]; then export SETUP_KEY=verify; fi && \
|
||||
/tmp/install_zisk_sdk.sh && \
|
||||
rm /tmp/install_zisk_sdk.sh # Clean up the script
|
||||
|
||||
# The 'zisk' Rust toolchain is now installed.
|
||||
# cargo-zisk is installed in /root/.zisk/bin.
|
||||
@@ -56,16 +64,4 @@ ENV PATH="${PATH}:${ZISK_BIN_DIR}"
|
||||
# Verify cargo-zisk is accessible
|
||||
RUN echo "Verifying Zisk installation in Dockerfile ..." && cargo-zisk --version
|
||||
|
||||
# Copy the entire ere project context
|
||||
# The WORKDIR is /app from the base image
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
# Run only compile and execution test, because proving requires ~31 GiB disk
|
||||
# space for the provingKey.
|
||||
# TODO: Run all tests if the CI runner has enough disk space to install the proving key.
|
||||
RUN echo "Running tests for ere-zisk library..." && \
|
||||
rm -rf ~/.zisk/provingKey && \
|
||||
cargo test --release -p ere-zisk --lib -- --color always compile::tests execute_tests
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
CMD ["/bin/bash"]
|
||||
|
||||
@@ -77,6 +77,13 @@ else
|
||||
fi
|
||||
|
||||
# Step 4: Make sure `lib-c`'s build script is ran.
|
||||
#
|
||||
# `ziskos` provides guest program runtime, and `lib-c` is a dependency of `ziskos`,
|
||||
# when we need to compile guest, the `build.rs` of `lib-c` will need to be ran once,
|
||||
# but if there are multiple `build.rs` running at the same time, it will panic.
|
||||
# So here we make sure it's already ran, and the built thing will be stored in
|
||||
# `$CARGO_HOME/git/checkouts/zisk-{hash}/{rev}/lib-c/c/build`, so could be
|
||||
# re-used as long as the `ziskos` has the same version.
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
cd "$TEMP_DIR"
|
||||
cargo init . --name build-lib-c
|
||||
|
||||
Reference in New Issue
Block a user