From c085ccd9f3430c85c8aa4a737cc692eedf91779d Mon Sep 17 00:00:00 2001 From: Han Date: Sat, 7 Feb 2026 11:05:23 +0900 Subject: [PATCH] Add `ProverResouce::Cluster` and add cluster support for ZisK (#284) --- .github/scripts/build-image.sh | 25 +- Cargo.lock | 597 +++++++++++++++++- Cargo.toml | 10 +- README.md | 10 +- crates/dockerized/server/src/main.rs | 6 +- crates/dockerized/src/lib.rs | 6 +- crates/dockerized/src/zkvm.rs | 63 +- crates/zkvm-interface/Cargo.toml | 1 - crates/zkvm-interface/src/zkvm.rs | 2 +- crates/zkvm-interface/src/zkvm/error.rs | 18 +- crates/zkvm-interface/src/zkvm/resource.rs | 119 ++-- crates/zkvm/airbender/src/lib.rs | 3 +- crates/zkvm/airbender/src/zkvm.rs | 46 +- .../zkvm/jolt/src/compiler/rust_rv64imac.rs | 4 +- crates/zkvm/jolt/src/lib.rs | 3 +- crates/zkvm/jolt/src/zkvm.rs | 37 +- crates/zkvm/miden/src/lib.rs | 3 +- crates/zkvm/miden/src/zkvm.rs | 35 +- crates/zkvm/nexus/src/lib.rs | 3 +- crates/zkvm/nexus/src/zkvm.rs | 39 +- .../zkvm/openvm/src/compiler/rust_rv32ima.rs | 4 +- crates/zkvm/openvm/src/lib.rs | 3 +- crates/zkvm/openvm/src/zkvm.rs | 70 +- crates/zkvm/openvm/src/zkvm/error.rs | 3 + crates/zkvm/pico/src/compiler/rust_rv32ima.rs | 4 +- crates/zkvm/pico/src/lib.rs | 3 +- crates/zkvm/pico/src/zkvm.rs | 37 +- .../zkvm/risc0/src/compiler/rust_rv32ima.rs | 4 +- crates/zkvm/risc0/src/lib.rs | 5 +- crates/zkvm/risc0/src/zkvm.rs | 73 ++- crates/zkvm/risc0/src/zkvm/error.rs | 13 +- crates/zkvm/sp1/src/compiler/rust_rv32ima.rs | 4 +- crates/zkvm/sp1/src/lib.rs | 5 +- crates/zkvm/sp1/src/zkvm.rs | 26 +- crates/zkvm/sp1/src/zkvm/error.rs | 3 + crates/zkvm/sp1/src/zkvm/sdk.rs | 30 +- crates/zkvm/ziren/src/lib.rs | 3 +- crates/zkvm/ziren/src/zkvm.rs | 24 +- crates/zkvm/ziren/src/zkvm/error.rs | 5 +- crates/zkvm/zisk/Cargo.toml | 12 +- .../zkvm/zisk/src/compiler/go_customized.rs | 4 +- crates/zkvm/zisk/src/lib.rs | 22 +- crates/zkvm/zisk/src/zkvm.rs | 109 ++-- crates/zkvm/zisk/src/zkvm/cluster_client.rs | 183 ++++++ crates/zkvm/zisk/src/zkvm/error.rs | 29 +- crates/zkvm/zisk/src/zkvm/sdk.rs | 553 ++++------------ crates/zkvm/zisk/src/zkvm/server.rs | 461 ++++++++++++++ docker/zisk/Dockerfile.cluster | 114 ++++ examples/zisk/docker-compose.cluster.yml | 131 ++++ 49 files changed, 2126 insertions(+), 841 deletions(-) create mode 100644 crates/zkvm/zisk/src/zkvm/cluster_client.rs create mode 100644 crates/zkvm/zisk/src/zkvm/server.rs create mode 100644 docker/zisk/Dockerfile.cluster create mode 100644 examples/zisk/docker-compose.cluster.yml diff --git a/.github/scripts/build-image.sh b/.github/scripts/build-image.sh index 1f9fcd4..ca6a069 100755 --- a/.github/scripts/build-image.sh +++ b/.github/scripts/build-image.sh @@ -9,12 +9,13 @@ IMAGE_REGISTRY="" BUILD_BASE=false BUILD_COMPILER=false BUILD_SERVER=false +BUILD_CLUSTER=false CUDA=false CUDA_ARCH="" RUSTFLAGS="" usage() { - echo "Usage: $0 --zkvm --tag [--base] [--compiler] [--server] [--registry ] [--cuda] [--cuda-arch ] [--rustflags ]" + echo "Usage: $0 --zkvm --tag [--base] [--compiler] [--server] [--cluster] [--registry ] [--cuda] [--cuda-arch ] [--rustflags ]" echo "" echo "Required:" echo " --zkvm zkVM to build for (e.g., zisk, sp1, risc0)" @@ -24,6 +25,7 @@ usage() { echo " --base Build the base images" echo " --compiler Build the compiler image" echo " --server Build the server image" + echo " --cluster Build the cluster image" echo "" echo "Optional:" echo " --registry Registry prefix (e.g., ghcr.io/eth-act/ere)" @@ -60,6 +62,10 @@ while [[ $# -gt 0 ]]; do BUILD_SERVER=true shift ;; + --cluster) + BUILD_CLUSTER=true + shift + ;; --cuda) CUDA=true shift @@ -93,8 +99,8 @@ if [ -z "$IMAGE_TAG" ]; then usage fi -if [ "$BUILD_BASE" = false ] && [ "$BUILD_COMPILER" = false ] && [ "$BUILD_SERVER" = false ]; then - echo "Error: At least one of --base, --compiler, --server is required" +if [ "$BUILD_BASE" = false ] && [ "$BUILD_COMPILER" = false ] && [ "$BUILD_SERVER" = false ] && [ "$BUILD_CLUSTER" = false ]; then + echo "Error: At least one of --base, --compiler, --server, --cluster is required" usage fi @@ -118,6 +124,7 @@ BASE_IMAGE="${IMAGE_PREFIX}ere-base:${IMAGE_TAG}" BASE_ZKVM_IMAGE="${IMAGE_PREFIX}ere-base-${ZKVM}:${IMAGE_TAG}" COMPILER_ZKVM_IMAGE="${IMAGE_PREFIX}ere-compiler-${ZKVM}:${IMAGE_TAG}" SERVER_ZKVM_IMAGE="${IMAGE_PREFIX}ere-server-${ZKVM}:${IMAGE_TAG}" +CLUSTER_ZKVM_IMAGE="${IMAGE_PREFIX}ere-cluster-${ZKVM}:${IMAGE_TAG}" # Prepare build arguments @@ -125,16 +132,19 @@ BASE_BUILD_ARGS=() BASE_ZKVM_BUILD_ARGS=(--build-arg "BASE_IMAGE=$BASE_IMAGE") COMPILER_ZKVM_BUILD_ARGS=(--build-arg "BASE_ZKVM_IMAGE=$BASE_ZKVM_IMAGE") SERVER_ZKVM_BUILD_ARGS=(--build-arg "BASE_ZKVM_IMAGE=$BASE_ZKVM_IMAGE") +CLUSTER_ZKVM_BUILD_ARGS=() if [ "$CUDA" = true ]; then BASE_BUILD_ARGS+=(--build-arg "CUDA=1") BASE_ZKVM_BUILD_ARGS+=(--build-arg "CUDA=1") SERVER_ZKVM_BUILD_ARGS+=(--build-arg "CUDA=1") + CLUSTER_ZKVM_BUILD_ARGS+=(--build-arg "CUDA=1") fi if [ -n "$CUDA_ARCH" ]; then BASE_ZKVM_BUILD_ARGS+=(--build-arg "CUDA_ARCH=$CUDA_ARCH") SERVER_ZKVM_BUILD_ARGS+=(--build-arg "CUDA_ARCH=$CUDA_ARCH") + CLUSTER_ZKVM_BUILD_ARGS+=(--build-arg "CUDA_ARCH=$CUDA_ARCH") fi if [ -n "$RUSTFLAGS" ]; then @@ -178,4 +188,13 @@ if [ "$BUILD_SERVER" = true ]; then . fi +if [ "$BUILD_CLUSTER" = true ]; then + echo "Building zkvm cluster image: $CLUSTER_ZKVM_IMAGE" + docker build \ + --file "docker/${ZKVM}/Dockerfile.cluster" \ + --tag "$CLUSTER_ZKVM_IMAGE" \ + "${CLUSTER_ZKVM_BUILD_ARGS[@]}" \ + . +fi + echo "Build complete!" diff --git a/Cargo.lock b/Cargo.lock index 014947f..d2f0bbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2145,6 +2145,16 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "build-probe-mpi" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78ace2bb02fc18ad937f1599a853fcf3da2327bc1eb3c8e62b1f2fe4573bfd6" +dependencies = [ + "pkg-config", + "shell-words", +] + [[package]] name = "bumpalo" version = "3.17.0" @@ -2333,10 +2343,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.22" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -2573,6 +2584,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "common" version = "0.2.0" @@ -2688,6 +2708,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +[[package]] +name = "conv" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" +dependencies = [ + "custom_derive", +] + [[package]] name = "convert_case" version = "0.7.1" @@ -3059,6 +3088,23 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "curves" +version = "0.15.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman?tag=v0.15.0#78497c5a05ea316df2188f98c1df66bffb80192f" +dependencies = [ + "fields", + "num-bigint 0.4.6", + "num-traits", + "rand 0.9.2", +] + +[[package]] +name = "custom_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" + [[package]] name = "darling" version = "0.20.11" @@ -3784,6 +3830,15 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "env" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc95de49ad098572c02d3fbf368c9a020bfff5ae78483685b77f51d8a7e9486d" +dependencies = [ + "num_threads", +] + [[package]] name = "env_filter" version = "0.1.3" @@ -4206,12 +4261,20 @@ dependencies = [ "ere-compile-utils", "ere-test-utils", "ere-zkvm-interface", + "futures-util", + "http 1.3.1", + "parking_lot", + "proofman-verifier", "serde", "strum 0.27.2", "tempfile", "thiserror 2.0.12", + "tokio", + "tonic 0.14.3", "tracing", + "uuid 1.17.0", "wait-timeout", + "zisk-distributed-grpc-api", ] [[package]] @@ -4224,7 +4287,6 @@ dependencies = [ "clap", "indexmap 2.10.0", "serde", - "serde-untagged", "serde_json", "serde_yaml", "strum 0.27.2", @@ -4557,7 +4619,7 @@ dependencies = [ "tokio", "tracing", "walkdir", - "yansi", + "yansi 0.5.1", ] [[package]] @@ -4697,6 +4759,16 @@ dependencies = [ "serde", ] +[[package]] +name = "fields" +version = "0.15.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman?tag=v0.15.0#78497c5a05ea316df2188f98c1df66bffb80192f" +dependencies = [ + "num-bigint 0.4.6", + "paste", + "serde", +] + [[package]] name = "final_reduced_risc_v_machine" version = "0.1.0" @@ -4709,6 +4781,12 @@ dependencies = [ "verifier_generator", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "findshlibs" version = "0.10.2" @@ -6522,6 +6600,25 @@ version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +[[package]] +name = "libffi" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce826c243048e3d5cec441799724de52e2d42f820468431fc3fceee2341871e2" +dependencies = [ + "libc", + "libffi-sys", +] + +[[package]] +name = "libffi-sys" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36115160c57e8529781b4183c2bb51fdc1f6d6d1ed345591d84be7703befb3c" +dependencies = [ + "cc", +] + [[package]] name = "libgit2-sys" version = "0.17.0+1.8.1" @@ -7260,6 +7357,32 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "mpi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677762a4bde2c81158fc566a69b97d11b0c3358694e64f4f922ac5189be311cc" +dependencies = [ + "build-probe-mpi", + "conv", + "libffi", + "mpi-sys", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "mpi-sys" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f655543f54b263cbc3d2456bf714bd807d66a33eff8f70136687f0776d34f76" +dependencies = [ + "bindgen 0.69.5", + "build-probe-mpi", + "cc", +] + [[package]] name = "multimap" version = "0.8.3" @@ -7857,6 +7980,25 @@ dependencies = [ "malloc_buf", ] +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + [[package]] name = "object" version = "0.35.0" @@ -10244,6 +10386,15 @@ dependencies = [ "group 0.13.0", ] +[[package]] +name = "papergrid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608b6444acf7f5ea39e8bd06dd6037e34a4b5ddfb29ae840edad49ea798e9e79" +dependencies = [ + "unicode-width 0.1.14", +] + [[package]] name = "parity-scale-codec" version = "3.7.4" @@ -10433,6 +10584,17 @@ dependencies = [ "indexmap 2.10.0", ] +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset 0.5.7", + "hashbrown 0.15.3", + "indexmap 2.10.0", +] + [[package]] name = "pharos" version = "0.5.3" @@ -10605,7 +10767,7 @@ dependencies = [ "static_assertions", "strum 0.26.3", "strum_macros 0.26.4", - "sysinfo", + "sysinfo 0.30.13", "thiserror 1.0.69", "tiny-keccak", "tracing", @@ -10616,6 +10778,23 @@ dependencies = [ "zkhash 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "pil-std-lib" +version = "0.15.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman?tag=v0.15.0#78497c5a05ea316df2188f98c1df66bffb80192f" +dependencies = [ + "colored", + "fields", + "num-bigint 0.4.6", + "num-traits", + "proofman-common", + "proofman-hints", + "proofman-util", + "rayon", + "tracing", + "witness", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -10874,6 +11053,126 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proofman" +version = "0.15.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman?tag=v0.15.0#78497c5a05ea316df2188f98c1df66bffb80192f" +dependencies = [ + "blake3", + "borsh", + "bytemuck", + "chrono", + "colored", + "crossbeam-channel", + "csv", + "curves", + "fields", + "libloading 0.8.7", + "mpi", + "num-bigint 0.4.6", + "num-traits", + "pil-std-lib", + "proofman-common", + "proofman-hints", + "proofman-macros", + "proofman-starks-lib-c", + "proofman-util", + "proofman-verifier", + "rand 0.9.2", + "rayon", + "serde", + "serde_json", + "tokio", + "tokio-util", + "tracing", + "witness", +] + +[[package]] +name = "proofman-common" +version = "0.15.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman?tag=v0.15.0#78497c5a05ea316df2188f98c1df66bffb80192f" +dependencies = [ + "borsh", + "colored", + "crossbeam-channel", + "crossbeam-queue", + "csv", + "env", + "fields", + "indexmap 2.10.0", + "lazy_static", + "libloading 0.8.7", + "mpi", + "num_cpus", + "proofman-macros", + "proofman-starks-lib-c", + "proofman-util", + "rayon", + "serde", + "serde_json", + "sysinfo 0.35.2", + "tabled", + "thiserror 2.0.12", + "tracing", + "tracing-subscriber 0.3.20", + "yansi 1.0.1", +] + +[[package]] +name = "proofman-hints" +version = "0.15.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman?tag=v0.15.0#78497c5a05ea316df2188f98c1df66bffb80192f" +dependencies = [ + "fields", + "proofman-common", + "proofman-starks-lib-c", + "proofman-util", + "tracing", +] + +[[package]] +name = "proofman-macros" +version = "0.15.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman?tag=v0.15.0#78497c5a05ea316df2188f98c1df66bffb80192f" +dependencies = [ + "proc-macro2", + "quote", + "rayon", + "syn 2.0.101", +] + +[[package]] +name = "proofman-starks-lib-c" +version = "0.15.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman?tag=v0.15.0#78497c5a05ea316df2188f98c1df66bffb80192f" +dependencies = [ + "crossbeam-channel", + "tracing", +] + +[[package]] +name = "proofman-util" +version = "0.15.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman?tag=v0.15.0#78497c5a05ea316df2188f98c1df66bffb80192f" +dependencies = [ + "colored", + "fields", + "sysinfo 0.35.2", + "tracing", +] + +[[package]] +name = "proofman-verifier" +version = "0.15.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman?tag=v0.15.0#78497c5a05ea316df2188f98c1df66bffb80192f" +dependencies = [ + "bytemuck", + "fields", + "rayon", + "tracing", +] + [[package]] name = "proptest" version = "1.9.0" @@ -10923,6 +11222,16 @@ dependencies = [ "prost-derive 0.13.5", ] +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive 0.14.3", +] + [[package]] name = "prost-build" version = "0.11.9" @@ -10986,6 +11295,27 @@ dependencies = [ "tempfile", ] +[[package]] +name = "prost-build" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" +dependencies = [ + "heck 0.5.0", + "itertools 0.14.0", + "log", + "multimap 0.10.1", + "petgraph 0.8.3", + "prettyplease 0.2.32", + "prost 0.14.3", + "prost-types 0.14.3", + "pulldown-cmark", + "pulldown-cmark-to-cmark", + "regex", + "syn 2.0.101", + "tempfile", +] + [[package]] name = "prost-derive" version = "0.11.9" @@ -11025,6 +11355,19 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "prost-types" version = "0.11.9" @@ -11052,6 +11395,15 @@ dependencies = [ "prost 0.13.5", ] +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost 0.14.3", +] + [[package]] name = "prover" version = "0.1.0" @@ -11114,6 +11466,26 @@ dependencies = [ "parking_lot", ] +[[package]] +name = "pulldown-cmark" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" +dependencies = [ + "bitflags 2.9.0", + "memchr", + "unicase", +] + +[[package]] +name = "pulldown-cmark-to-cmark" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50793def1b900256624a709439404384204a5dc3a6ec580281bfaac35e882e90" +dependencies = [ + "pulldown-cmark", +] + [[package]] name = "quanta" version = "0.12.5" @@ -12772,18 +13144,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde-untagged" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" -dependencies = [ - "erased-serde", - "serde", - "serde_core", - "typeid", -] - [[package]] name = "serde_arrays" version = "0.1.0" @@ -12838,6 +13198,7 @@ version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" dependencies = [ + "indexmap 2.10.0", "itoa", "memchr", "ryu", @@ -13034,6 +13395,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shell-words" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" + [[package]] name = "shlex" version = "1.3.0" @@ -13596,7 +13963,7 @@ dependencies = [ "sp1-stark", "strum 0.26.3", "strum_macros 0.26.4", - "sysinfo", + "sysinfo 0.30.13", "tempfile", "thiserror 1.0.69", "tokio", @@ -13635,7 +14002,7 @@ dependencies = [ "sp1-derive", "sp1-primitives", "strum 0.26.3", - "sysinfo", + "sysinfo 0.30.13", "tracing", ] @@ -14104,6 +14471,20 @@ dependencies = [ "windows 0.52.0", ] +[[package]] +name = "sysinfo" +version = "0.35.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3ffa3e4ff2b324a57f7aeb3c349656c7b127c3c189520251a648102a92496e" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows 0.61.1", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -14146,6 +14527,27 @@ dependencies = [ "libc", ] +[[package]] +name = "tabled" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2407502760ccfd538f2fb1f843dd87b6daf1a17848d57bc5a25617e408ef4c7a" +dependencies = [ + "papergrid", + "tabled_derive", +] + +[[package]] +name = "tabled_derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "278ea3921cee8c5a69e0542998a089f7a14fa43c9c4e4f9951295da89bd0c943" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "tap" version = "1.0.1" @@ -14486,9 +14888,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -14656,6 +15058,35 @@ dependencies = [ "tracing", ] +[[package]] +name = "tonic" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a286e33f82f8a1ee2df63f4fa35c0becf4a85a0cb03091a15fd7bf0b402dc94a" +dependencies = [ + "async-trait", + "axum 0.8.5", + "base64 0.22.1", + "bytes", + "h2 0.4.10", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-timeout 0.5.2", + "hyper-util", + "percent-encoding", + "pin-project", + "socket2 0.6.0", + "sync_wrapper 1.0.2", + "tokio", + "tokio-stream", + "tower 0.5.2", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tonic-build" version = "0.8.4" @@ -14669,6 +15100,45 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "tonic-build" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27aac809edf60b741e2d7db6367214d078856b8a5bff0087e94ff330fb97b6fc" +dependencies = [ + "prettyplease 0.2.32", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "tonic-prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6c55a2d6a14174563de34409c9f92ff981d006f56da9c6ecd40d9d4a31500b0" +dependencies = [ + "bytes", + "prost 0.14.3", + "tonic 0.14.3", +] + +[[package]] +name = "tonic-prost-build" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4556786613791cfef4ed134aa670b61a85cfcacf71543ef33e8d801abae988f" +dependencies = [ + "prettyplease 0.2.32", + "proc-macro2", + "prost-build 0.14.3", + "prost-types 0.14.3", + "quote", + "syn 2.0.101", + "tempfile", + "tonic-build 0.14.3", +] + [[package]] name = "tower" version = "0.4.13" @@ -14697,9 +15167,12 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", + "indexmap 2.10.0", "pin-project-lite", + "slab", "sync_wrapper 1.0.2", "tokio", + "tokio-util", "tower-layer", "tower-service", "tracing", @@ -14870,6 +15343,16 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.2.25" @@ -14889,12 +15372,15 @@ dependencies = [ "nu-ansi-term", "once_cell", "regex-automata", + "serde", + "serde_json", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", + "tracing-serde", ] [[package]] @@ -15159,6 +15645,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -15293,6 +15785,7 @@ dependencies = [ "getrandom 0.3.3", "js-sys", "rand 0.9.2", + "serde", "uuid-macro-internal", "wasm-bindgen", ] @@ -16144,6 +16637,20 @@ dependencies = [ "bitflags 2.9.0", ] +[[package]] +name = "witness" +version = "0.15.0" +source = "git+https://github.com/0xPolygonHermez/pil2-proofman?tag=v0.15.0#78497c5a05ea316df2188f98c1df66bffb80192f" +dependencies = [ + "colored", + "fields", + "libloading 0.8.7", + "proofman-common", + "proofman-util", + "serde_json", + "tracing", +] + [[package]] name = "worker" version = "0.1.0" @@ -16220,6 +16727,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yoke" version = "0.8.0" @@ -16375,6 +16888,44 @@ dependencies = [ "zopfli", ] +[[package]] +name = "zisk-distributed-common" +version = "0.1.0" +source = "git+https://github.com/han0110/zisk?branch=patch%2Fv0.15.0-cluster#6ffe5f4123a258765cf814c5ff3d3bb8988fb964" +dependencies = [ + "anyhow", + "borsh", + "chrono", + "proofman", + "proofman-common", + "serde", + "serde_json", + "thiserror 2.0.12", + "tracing", + "tracing-appender", + "tracing-subscriber 0.3.20", + "uuid 1.17.0", +] + +[[package]] +name = "zisk-distributed-grpc-api" +version = "0.15.0" +source = "git+https://github.com/han0110/zisk?branch=patch%2Fv0.15.0-cluster#6ffe5f4123a258765cf814c5ff3d3bb8988fb964" +dependencies = [ + "anyhow", + "chrono", + "prost 0.14.3", + "prost-types 0.14.3", + "serde", + "serde_json", + "tonic 0.14.3", + "tonic-prost", + "tonic-prost-build", + "tracing", + "uuid 1.17.0", + "zisk-distributed-common", +] + [[package]] name = "ziskos" version = "0.15.0" @@ -16838,7 +17389,7 @@ dependencies = [ "thiserror 1.0.69", "tokio", "tonic 0.8.3", - "tonic-build", + "tonic-build 0.8.4", "tracing", "twirp-rs", "uuid 1.17.0", @@ -16885,7 +17436,7 @@ dependencies = [ "serde", "strum 0.26.3", "strum_macros 0.26.4", - "sysinfo", + "sysinfo 0.30.13", "tracing", "tracing-forest", "tracing-subscriber 0.3.20", diff --git a/Cargo.toml b/Cargo.toml index b87f977..a8d2147 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,7 @@ anyhow = "1.0.98" auto_impl = "1.3.0" bincode = { version = "2.0.1", default-features = false } blake3 = "1.8.2" -bytemuck = "1.24.0" +bytemuck = "1.25.0" cargo_metadata = "0.19.0" ciborium = { version = "0.2.2", default-features = false } ciborium-io = { version = "0.2.2", default-features = false } @@ -58,7 +58,10 @@ dashmap = "6.1.0" digest = { version = "0.10.7", default-features = false } eyre = "0.6.12" fnv = { version = "1.0.7", default-features = false } +futures-util = "0.3" +http = "1" indexmap = "2.10.0" +parking_lot = "0.12.5" paste = "1.0.15" postcard = { version = "1.0.8", default-features = false } prost = "0.13" @@ -68,7 +71,6 @@ rkyv = { version = "0.8.12", default-features = false } serde = { version = "1.0.219", default-features = false } serde_bytes = { version = "0.11.19", default-features = false } serde_json = "1.0.142" -serde-untagged = "0.1" serde_yaml = "0.9.34" sha2 = { version = "0.10.9", default-features = false } strum = "0.27.2" @@ -76,11 +78,13 @@ tempfile = "3.20.0" thiserror = "2.0.12" tokio = "1.0" toml = "0.8.23" +tonic = "0.14" tower-http = "0.6.6" tracing = "0.1.41" tracing-subscriber = "0.3.19" twirp = "0.9.1" twirp-build = "0.9.0" +uuid = "1" wait-timeout = "0.2.1" # Airbender dependencies @@ -140,6 +144,8 @@ zkm-zkvm = { git = "https://github.com/ProjectZKM/Ziren.git", tag = "v1.2.3", de # ZisK dependencies ziskos = { git = "https://github.com/0xPolygonHermez/zisk.git", tag = "v0.15.0" } +zisk-distributed-grpc-api = { git = "https://github.com/han0110/zisk", branch = "patch/v0.15.0-cluster" } +zisk-proofman-verifier = { git = "https://github.com/0xPolygonHermez/pil2-proofman", package = "proofman-verifier", tag = "v0.15.0" } # Local dependencies ere-zkvm-interface = { path = "crates/zkvm-interface" } diff --git a/README.md b/README.md index 4dc1a50..406b47b 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,7 @@ OutputHashedPlatform::::write_whole_output(&large_output | Risc0 | [`3.0.4`](https://github.com/risc0/risc0/tree/v3.0.4) | Yes | | SP1 | [`5.2.4`](https://github.com/succinctlabs/sp1/tree/v5.2.4) | Yes | | Ziren | [`1.2.3`](https://github.com/ProjectZKM/Ziren/tree/v1.2.3) | No | -| Zisk | [`0.13.0`](https://github.com/0xPolygonHermez/zisk/tree/v0.13.0) | Yes | +| Zisk | [`0.15.0`](https://github.com/0xPolygonHermez/zisk/tree/v0.15.0) | Yes | ## Examples @@ -273,7 +273,7 @@ ere-sp1 = { git = "https://github.com/eth-act/ere.git" } use ere_sp1::{compiler::RustRv32imaCustomized, zkvm::EreSP1}; use ere_zkvm_interface::{ compiler::Compiler, - zkvm::{Input, ProofKind, ProverResourceType, zkVM}, + zkvm::{Input, ProofKind, ProverResource, zkVM}, }; use std::path::Path; @@ -285,7 +285,7 @@ fn main() -> Result<(), Box> { let program = compiler.compile(guest_directory)?; // Create zkVM instance (setup/preprocessing happens here) - let zkvm = EreSP1::new(program, ProverResourceType::Cpu)?; + let zkvm = EreSP1::new(program, ProverResource::Cpu)?; // Prepare input // Use `with_prefixed_stdin` when guest uses `Platform::read_whole_input()` @@ -341,7 +341,7 @@ ere-dockerized = { git = "https://github.com/eth-act/ere.git" } use ere_dockerized::{CompilerKind, DockerizedCompiler, DockerizedzkVM, zkVMKind}; use ere_zkvm_interface::{ compiler::Compiler, - zkvm::{Input, ProofKind, ProverResourceType, zkVM}, + zkvm::{Input, ProofKind, ProverResource, zkVM}, }; use std::path::Path; @@ -355,7 +355,7 @@ fn main() -> Result<(), Box> { // Create zkVM instance (builds Docker images if needed) // It spawns a container that runs a gRPC server handling zkVM operations - let zkvm = DockerizedzkVM::new(zkVMKind::SP1, program, ProverResourceType::Cpu)?; + let zkvm = DockerizedzkVM::new(zkVMKind::SP1, program, ProverResource::Cpu)?; // Prepare input // Use `with_prefixed_stdin` when guest uses `Platform::read_whole_input()` diff --git a/crates/dockerized/server/src/main.rs b/crates/dockerized/server/src/main.rs index 96a9899..a639993 100644 --- a/crates/dockerized/server/src/main.rs +++ b/crates/dockerized/server/src/main.rs @@ -1,7 +1,7 @@ use anyhow::{Context, Error}; use clap::Parser; use ere_server::server::{router, zkVMServer}; -use ere_zkvm_interface::zkvm::{ProverResourceType, zkVM}; +use ere_zkvm_interface::zkvm::{ProverResource, zkVM}; use std::{ io::{self, Read}, net::{Ipv4Addr, SocketAddr}, @@ -48,7 +48,7 @@ struct Args { program_path: Option, /// Prover resource type. #[command(subcommand)] - resource: ProverResourceType, + resource: ProverResource, } #[tokio::main] @@ -120,7 +120,7 @@ async fn shutdown_signal() { } } -fn construct_zkvm(program: Vec, resource: ProverResourceType) -> Result { +fn construct_zkvm(program: Vec, resource: ProverResource) -> Result { let (program, _) = bincode::serde::decode_from_slice(&program, bincode::config::legacy()) .with_context(|| "Failed to deserialize program")?; diff --git a/crates/dockerized/src/lib.rs b/crates/dockerized/src/lib.rs index 75a828e..aced963 100644 --- a/crates/dockerized/src/lib.rs +++ b/crates/dockerized/src/lib.rs @@ -16,7 +16,7 @@ //! 4. `ere-server-{zkvm}:{version}` - Server image with the `ere-server` binary //! built with the selected zkVM feature //! -//! When [`ProverResourceType::Gpu`] is selected, the image with GPU support +//! When [`ProverResource::Gpu`] is selected, the image with GPU support //! will be built and tagged with specific suffix. //! //! To force rebuild all images, set the environment variable @@ -29,7 +29,7 @@ //! use ere_dockerized::{CompilerKind, DockerizedCompiler, DockerizedzkVM, zkVMKind}; //! use ere_zkvm_interface::{ //! compiler::Compiler, -//! zkvm::{Input, ProofKind, ProverResourceType, zkVM}, +//! zkvm::{Input, ProofKind, ProverResource, zkVM}, //! }; //! use std::path::Path; //! @@ -45,7 +45,7 @@ //! let program = compiler.compile(&guest_path)?; //! //! // Create zkVM instance -//! let resource = ProverResourceType::Cpu; +//! let resource = ProverResource::Cpu; //! let zkvm = DockerizedzkVM::new(zkvm_kind, program, resource)?; //! //! // Serialize input diff --git a/crates/dockerized/src/zkvm.rs b/crates/dockerized/src/zkvm.rs index a27b25b..f3e9780 100644 --- a/crates/dockerized/src/zkvm.rs +++ b/crates/dockerized/src/zkvm.rs @@ -22,7 +22,7 @@ use ere_server::{ use ere_zkvm_interface::{ CommonError, zkvm::{ - Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResourceType, + Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, ProverResource, PublicValues, zkVM, }, }; @@ -47,7 +47,7 @@ pub use error::Error; /// 3. `ere-server-{zkvm}:{version}` - Server image with the `ere-server` binary /// built with the selected zkVM feature /// -/// When [`ProverResourceType::Gpu`] is selected, the image with GPU support +/// When [`ProverResource::Gpu`] is selected, the image with GPU support /// will be built and tagged with specific suffix. /// /// Images are cached and only rebuilt if they don't exist or if the @@ -158,12 +158,12 @@ impl ServerContainer { fn new( zkvm_kind: zkVMKind, program: &SerializedProgram, - resource: &ProverResourceType, + resource: &ProverResource, ) -> Result { let port = Self::PORT_OFFSET + zkvm_kind as u16; let name = format!("ere-server-{zkvm_kind}"); - let gpu = matches!(resource, ProverResourceType::Gpu); + let gpu = resource.is_gpu(); let mut cmd = DockerRunCmd::new(server_zkvm_image(zkvm_kind, gpu)) .rm() .inherit_env("RUST_LOG") @@ -181,23 +181,25 @@ impl ServerContainer { // zkVM specific options cmd = match zkvm_kind { zkVMKind::Risc0 => cmd - .inherit_env("RISC0_SEGMENT_PO2") - .inherit_env("RISC0_KECCAK_PO2"), + .inherit_env("ERE_RISC0_SEGMENT_PO2") + .inherit_env("ERE_RISC0_KECCAK_PO2"), // ZisK uses shared memory to exchange data between processes, it // requires at least 16G shared memory, here we set 32G for safety. zkVMKind::Zisk => cmd .option("shm-size", "32G") .option("ulimit", "memlock=-1:-1") - .inherit_env("ZISK_PORT") - .inherit_env("ZISK_CHUNK_SIZE_BITS") - .inherit_env("ZISK_UNLOCK_MAPPED_MEMORY") - .inherit_env("ZISK_MINIMAL_MEMORY") - .inherit_env("ZISK_PREALLOCATE") - .inherit_env("ZISK_SHARED_TABLES") - .inherit_env("ZISK_MAX_STREAMS") - .inherit_env("ZISK_NUMBER_THREADS_WITNESS") - .inherit_env("ZISK_MAX_WITNESS_STORED") - .inherit_env("ZISK_PROVE_TIMEOUT_SEC"), + .inherit_env("ERE_ZISK_SETUP_ON_INIT") + .inherit_env("ERE_ZISK_PORT") + .inherit_env("ERE_ZISK_UNLOCK_MAPPED_MEMORY") + .inherit_env("ERE_ZISK_MINIMAL_MEMORY") + .inherit_env("ERE_ZISK_PREALLOCATE") + .inherit_env("ERE_ZISK_SHARED_TABLES") + .inherit_env("ERE_ZISK_MAX_STREAMS") + .inherit_env("ERE_ZISK_NUMBER_THREADS_WITNESS") + .inherit_env("ERE_ZISK_MAX_WITNESS_STORED") + .inherit_env("ERE_ZISK_START_SERVER_TIMEOUT_SEC") + .inherit_env("ERE_ZISK_SHUTDOWN_SERVER_TIMEOUT_SEC") + .inherit_env("ERE_ZISK_PROVE_TIMEOUT_SEC"), _ => cmd, }; @@ -272,7 +274,7 @@ impl ServerContainer { pub struct DockerizedzkVM { zkvm_kind: zkVMKind, program: SerializedProgram, - resource: ProverResourceType, + resource: ProverResource, container: RwLock>, } @@ -280,9 +282,9 @@ impl DockerizedzkVM { pub fn new( zkvm_kind: zkVMKind, program: SerializedProgram, - resource: ProverResourceType, + resource: ProverResource, ) -> Result { - build_server_image(zkvm_kind, matches!(resource, ProverResourceType::Gpu))?; + build_server_image(zkvm_kind, resource.is_gpu())?; let container = ServerContainer::new(zkvm_kind, &program, &resource)?; @@ -302,7 +304,7 @@ impl DockerizedzkVM { &self.program } - pub fn resource(&self) -> &ProverResourceType { + pub fn resource(&self) -> &ProverResource { &self.resource } @@ -424,14 +426,15 @@ async fn wait_until_healthy(endpoint: &Url, http_client: Client) -> Result<(), E } fn block_on(future: impl Future) -> T { - static FALLBACK_RT: OnceLock = OnceLock::new(); - let handle = tokio::runtime::Handle::try_current().unwrap_or_else(|_| { - FALLBACK_RT - .get_or_init(|| tokio::runtime::Runtime::new().expect("Failed to create runtime")) - .handle() - .clone() - }); - tokio::task::block_in_place(|| handle.block_on(future)) + match tokio::runtime::Handle::try_current() { + Ok(handle) => tokio::task::block_in_place(|| handle.block_on(future)), + Err(_) => { + static FALLBACK_RT: OnceLock = OnceLock::new(); + FALLBACK_RT + .get_or_init(|| tokio::runtime::Runtime::new().expect("Failed to create runtime")) + .block_on(future) + } + } } #[cfg(test)] @@ -445,7 +448,7 @@ mod test { use ere_test_utils::{ host::*, io::serde::bincode::BincodeLegacy, program::basic::BasicProgram, }; - use ere_zkvm_interface::zkvm::{Input, ProofKind, ProverResourceType, zkVM}; + use ere_zkvm_interface::zkvm::{Input, ProofKind, ProverResource, zkVM}; fn zkvm( zkvm_kind: zkVMKind, @@ -453,7 +456,7 @@ mod test { program: &'static str, ) -> DockerizedzkVM { let program = compile(zkvm_kind, compiler_kind, program).clone(); - DockerizedzkVM::new(zkvm_kind, program, ProverResourceType::Cpu).unwrap() + DockerizedzkVM::new(zkvm_kind, program, ProverResource::Cpu).unwrap() } macro_rules! test { diff --git a/crates/zkvm-interface/Cargo.toml b/crates/zkvm-interface/Cargo.toml index 8b3e024..30fa2af 100644 --- a/crates/zkvm-interface/Cargo.toml +++ b/crates/zkvm-interface/Cargo.toml @@ -11,7 +11,6 @@ auto_impl.workspace = true bincode = { workspace = true, features = ["alloc", "serde"] } indexmap = { workspace = true, features = ["serde"] } serde = { workspace = true, features = ["derive"] } -serde-untagged.workspace = true strum = { workspace = true, features = ["derive"] } thiserror.workspace = true diff --git a/crates/zkvm-interface/src/zkvm.rs b/crates/zkvm-interface/src/zkvm.rs index 0b48e33..8f51cd3 100644 --- a/crates/zkvm-interface/src/zkvm.rs +++ b/crates/zkvm-interface/src/zkvm.rs @@ -11,7 +11,7 @@ mod resource; pub use error::CommonError; pub use proof::{Proof, ProofKind}; pub use report::{ProgramExecutionReport, ProgramProvingReport}; -pub use resource::{NetworkProverConfig, ProverResourceType}; +pub use resource::{ProverResource, ProverResourceKind, RemoteProverConfig}; /// Input for the prover to execute/prove a guest program. #[derive(Clone, Debug, Default)] diff --git a/crates/zkvm-interface/src/zkvm/error.rs b/crates/zkvm-interface/src/zkvm/error.rs index f53d0e4..d47e5f6 100644 --- a/crates/zkvm-interface/src/zkvm/error.rs +++ b/crates/zkvm-interface/src/zkvm/error.rs @@ -1,4 +1,4 @@ -use crate::zkvm::ProofKind; +use crate::zkvm::{ProofKind, resource::ProverResourceKind}; use std::{ io, path::Path, @@ -51,6 +51,12 @@ pub enum CommonError { #[error("Unsupported input: {0}")] UnsupportedInput(String), + #[error("Unsupported prover resource kind {unsupported:?}, expect one of {supported:?}")] + UnsupportedProverResourceKind { + unsupported: ProverResourceKind, + supported: Vec, + }, + #[error("Unsupported proof kind {unsupported:?}, expect one of {supported:?}")] UnsupportedProofKind { unsupported: ProofKind, @@ -141,6 +147,16 @@ impl CommonError { Self::UnsupportedInput(reason.as_ref().to_string()) } + pub fn unsupported_prover_resource_kind( + unsupported: ProverResourceKind, + supported: impl IntoIterator, + ) -> Self { + Self::UnsupportedProverResourceKind { + unsupported, + supported: supported.into_iter().collect(), + } + } + pub fn unsupported_proof_kind( unsupported: ProofKind, supported: impl IntoIterator, diff --git a/crates/zkvm-interface/src/zkvm/resource.rs b/crates/zkvm-interface/src/zkvm/resource.rs index e804bc8..d27adab 100644 --- a/crates/zkvm-interface/src/zkvm/resource.rs +++ b/crates/zkvm-interface/src/zkvm/resource.rs @@ -1,11 +1,11 @@ -use serde::{Deserialize, Deserializer, Serialize, de::Unexpected}; -use serde_untagged::UntaggedEnumVisitor; +use serde::{Deserialize, Serialize}; +use strum::{Display, EnumDiscriminants, EnumIs, EnumIter, EnumString}; -/// Configuration for network-based proving +/// Configuration for remote proving #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(feature = "clap", derive(clap::Args))] -pub struct NetworkProverConfig { - /// The endpoint URL of the prover network service +pub struct RemoteProverConfig { + /// The endpoint URL of the remote prover #[cfg_attr(feature = "clap", arg(long))] pub endpoint: String, /// Optional API key for authentication @@ -14,7 +14,7 @@ pub struct NetworkProverConfig { } #[cfg(feature = "clap")] -impl NetworkProverConfig { +impl RemoteProverConfig { 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])) @@ -24,18 +24,35 @@ impl NetworkProverConfig { } /// ResourceType specifies what resource will be used to create the proofs. -#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[derive( + Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, EnumDiscriminants, EnumIs, +)] +#[strum_discriminants( + name(ProverResourceKind), + derive(Display, EnumString, EnumIter, Hash), + strum(serialize_all = "lowercase") +)] #[cfg_attr(feature = "clap", derive(clap::Subcommand))] -pub enum ProverResourceType { +#[serde(tag = "kind", rename_all = "lowercase")] +pub enum ProverResource { #[default] Cpu, Gpu, - /// Use a remote prover network - Network(NetworkProverConfig), + /// Official proving network + Network(RemoteProverConfig), + /// Self-hosted proving cluster + Cluster(RemoteProverConfig), +} + +impl ProverResource { + /// Returns [`ProverResourceKind`]. + pub fn kind(&self) -> ProverResourceKind { + self.into() + } } #[cfg(feature = "clap")] -impl ProverResourceType { +impl ProverResource { pub fn to_args(&self) -> Vec<&str> { match self { Self::Cpu => vec!["cpu"], @@ -43,51 +60,22 @@ impl ProverResourceType { Self::Network(config) => core::iter::once("network") .chain(config.to_args()) .collect(), + Self::Cluster(config) => core::iter::once("cluster") + .chain(config.to_args()) + .collect(), } } } -impl Serialize for ProverResourceType { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - match self { - Self::Cpu => "cpu".serialize(serializer), - Self::Gpu => "gpu".serialize(serializer), - Self::Network(config) => config.serialize(serializer), - } - } -} - -impl<'de> Deserialize<'de> for ProverResourceType { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - UntaggedEnumVisitor::new() - .string(|resource| match resource { - "cpu" => Ok(Self::Cpu), - "gpu" => Ok(Self::Gpu), - _ => Err(serde::de::Error::invalid_value( - Unexpected::Str(resource), - &r#""cpu" or "gpu""#, - )), - }) - .map(|map| map.deserialize().map(Self::Network)) - .deserialize(deserializer) - } -} - #[cfg(test)] mod test { - use crate::zkvm::resource::ProverResourceType; + use crate::zkvm::resource::ProverResource; use core::fmt::Debug; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] struct Config { - resources: Vec, + resources: Vec, } fn test_round_trip<'de, SE: Debug, DE: Debug>( @@ -101,7 +89,20 @@ mod test { #[test] fn test_round_trip_toml() { const TOML: &str = r#" -resources = ["cpu", "gpu", { endpoint = "http://localhost:3000" }] +[[resources]] +kind = "cpu" + +[[resources]] +kind = "gpu" + +[[resources]] +kind = "network" +endpoint = "http://localhost:3000" +api_key = "my_api_key" + +[[resources]] +kind = "cluster" +endpoint = "http://localhost:3000" "#; test_round_trip(TOML, toml::to_string, toml::from_str); } @@ -110,9 +111,13 @@ resources = ["cpu", "gpu", { endpoint = "http://localhost:3000" }] fn test_round_trip_yaml() { const YAML: &str = r#" resources: -- cpu -- gpu -- endpoint: http://localhost:3000 +- kind: cpu +- kind: gpu +- kind: network + endpoint: http://localhost:3000 + api_key: my_api_key +- kind: cluster + endpoint: http://localhost:3000 api_key: null "#; test_round_trip(YAML, serde_yaml::to_string, serde_yaml::from_str); @@ -123,10 +128,20 @@ resources: const JSON: &str = r#" { "resources": [ - "cpu", - "gpu", { - "endpoint": "", + "kind": "cpu" + }, + { + "kind": "gpu" + }, + { + "kind": "network", + "endpoint": "http://localhost:3000", + "api_key": "my_api_key" + }, + { + "kind": "cluster", + "endpoint": "http://localhost:3000", "api_key": null } ] diff --git a/crates/zkvm/airbender/src/lib.rs b/crates/zkvm/airbender/src/lib.rs index 30576af..c7e3bba 100644 --- a/crates/zkvm/airbender/src/lib.rs +++ b/crates/zkvm/airbender/src/lib.rs @@ -33,13 +33,14 @@ //! //! # `zkVM` implementation //! -//! ## Supported `ProverResourceType` +//! ## Supported `ProverResource` //! //! | Resource | Supported | //! | --------- | :-------: | //! | `Cpu` | Yes | //! | `Gpu` | Yes | //! | `Network` | No | +//! | `Cluster` | No | //! //! [`install_airbender_sdk.sh`]: https://github.com/eth-act/ere/blob/master/scripts/sdk_installers/install_airbender_sdk.sh diff --git a/crates/zkvm/airbender/src/zkvm.rs b/crates/zkvm/airbender/src/zkvm.rs index 501b513..34c7ada 100644 --- a/crates/zkvm/airbender/src/zkvm.rs +++ b/crates/zkvm/airbender/src/zkvm.rs @@ -1,9 +1,12 @@ use crate::{program::AirbenderProgram, zkvm::sdk::AirbenderSdk}; use airbender_execution_utils::ProgramProof; use anyhow::bail; -use ere_zkvm_interface::zkvm::{ - CommonError, Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, - ProverResourceType, PublicValues, zkVM, zkVMProgramDigest, +use ere_zkvm_interface::{ + ProverResourceKind, + zkvm::{ + CommonError, Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, + ProverResource, PublicValues, zkVM, zkVMProgramDigest, + }, }; use std::time::Instant; @@ -20,9 +23,14 @@ pub struct EreAirbender { } impl EreAirbender { - pub fn new(program: AirbenderProgram, resource: ProverResourceType) -> Result { - let gpu = matches!(resource, ProverResourceType::Gpu); - let sdk = AirbenderSdk::new(program.bin(), gpu); + pub fn new(program: AirbenderProgram, resource: ProverResource) -> Result { + if !matches!(resource, ProverResource::Cpu | ProverResource::Gpu) { + Err(CommonError::unsupported_prover_resource_kind( + resource.kind(), + [ProverResourceKind::Cpu, ProverResourceKind::Gpu], + ))?; + } + let sdk = AirbenderSdk::new(program.bin(), resource.is_gpu()); Ok(Self { sdk }) } } @@ -30,7 +38,9 @@ impl EreAirbender { impl zkVM for EreAirbender { fn execute(&self, input: &Input) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> { if input.proofs.is_some() { - bail!(CommonError::unsupported_input("no dedicated proofs stream")) + bail!(Error::from(CommonError::unsupported_input( + "no dedicated proofs stream" + ))) } let start = Instant::now(); @@ -53,13 +63,15 @@ impl zkVM for EreAirbender { proof_kind: ProofKind, ) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> { if input.proofs.is_some() { - bail!(CommonError::unsupported_input("no dedicated proofs stream")) + bail!(Error::from(CommonError::unsupported_input( + "no dedicated proofs stream" + ))) } if proof_kind != ProofKind::Compressed { - bail!(CommonError::unsupported_proof_kind( + bail!(Error::from(CommonError::unsupported_proof_kind( proof_kind, [ProofKind::Compressed] - )) + ))) } let start = Instant::now(); let (public_values, proof) = self.sdk.prove(input.stdin())?; @@ -77,10 +89,10 @@ impl zkVM for EreAirbender { fn verify(&self, proof: &Proof) -> anyhow::Result { let Proof::Compressed(proof) = proof else { - bail!(CommonError::unsupported_proof_kind( + bail!(Error::from(CommonError::unsupported_proof_kind( proof.kind(), [ProofKind::Compressed] - )) + ))) }; let (proof, _): (ProgramProof, _) = @@ -119,7 +131,7 @@ mod tests { }; use ere_zkvm_interface::{ compiler::Compiler, - zkvm::{Input, ProofKind, ProverResourceType, zkVM}, + zkvm::{Input, ProofKind, ProverResource, zkVM}, }; use std::sync::OnceLock; @@ -137,7 +149,7 @@ mod tests { #[test] fn test_execute() { let program = basic_program(); - let zkvm = EreAirbender::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreAirbender::new(program, ProverResource::Cpu).unwrap(); let test_case = BasicProgram::::valid_test_case().into_output_sha256(); run_zkvm_execute(&zkvm, &test_case); @@ -146,7 +158,7 @@ mod tests { #[test] fn test_execute_invalid_test_case() { let program = basic_program(); - let zkvm = EreAirbender::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreAirbender::new(program, ProverResource::Cpu).unwrap(); for input in [ Input::new(), @@ -159,7 +171,7 @@ mod tests { #[test] fn test_prove() { let program = basic_program(); - let zkvm = EreAirbender::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreAirbender::new(program, ProverResource::Cpu).unwrap(); let test_case = BasicProgram::::valid_test_case().into_output_sha256(); run_zkvm_prove(&zkvm, &test_case); @@ -168,7 +180,7 @@ mod tests { #[test] fn test_prove_invalid_test_case() { let program = basic_program(); - let zkvm = EreAirbender::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreAirbender::new(program, ProverResource::Cpu).unwrap(); for input in [ Input::new(), diff --git a/crates/zkvm/jolt/src/compiler/rust_rv64imac.rs b/crates/zkvm/jolt/src/compiler/rust_rv64imac.rs index 0ed5b7b..3ae6388 100644 --- a/crates/zkvm/jolt/src/compiler/rust_rv64imac.rs +++ b/crates/zkvm/jolt/src/compiler/rust_rv64imac.rs @@ -62,7 +62,7 @@ mod tests { use ere_zkvm_interface::{ Input, compiler::Compiler, - zkvm::{ProverResourceType, zkVM}, + zkvm::{ProverResource, zkVM}, }; #[test] @@ -76,7 +76,7 @@ mod tests { fn test_execute() { let guest_directory = testing_guest_directory("jolt", "stock_nightly_no_std"); let program = RustRv64imac.compile(&guest_directory).unwrap(); - let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreJolt::new(program, ProverResource::Cpu).unwrap(); zkvm.execute(&Input::new()).unwrap(); } diff --git a/crates/zkvm/jolt/src/lib.rs b/crates/zkvm/jolt/src/lib.rs index e69868e..57d17b5 100644 --- a/crates/zkvm/jolt/src/lib.rs +++ b/crates/zkvm/jolt/src/lib.rs @@ -21,13 +21,14 @@ //! //! # `zkVM` implementation //! -//! ## Supported `ProverResourceType` +//! ## Supported `ProverResource` //! //! | Resource | Supported | //! | --------- | :-------: | //! | `Cpu` | Yes | //! | `Gpu` | No | //! | `Network` | No | +//! | `Cluster` | No | //! //! [`install_jolt_sdk.sh`]: https://github.com/eth-act/ere/blob/master/scripts/sdk_installers/install_jolt_sdk.sh diff --git a/crates/zkvm/jolt/src/zkvm.rs b/crates/zkvm/jolt/src/zkvm.rs index 2a15681..97744bc 100644 --- a/crates/zkvm/jolt/src/zkvm.rs +++ b/crates/zkvm/jolt/src/zkvm.rs @@ -5,7 +5,7 @@ use crate::{ use anyhow::bail; use ere_zkvm_interface::zkvm::{ CommonError, Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, - ProverResourceType, PublicValues, zkVM, + ProverResource, ProverResourceKind, PublicValues, zkVM, }; use jolt_ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use std::{env, io::Cursor, time::Instant}; @@ -22,9 +22,12 @@ pub struct EreJolt { } impl EreJolt { - pub fn new(program: JoltProgram, resource: ProverResourceType) -> Result { - if !matches!(resource, ProverResourceType::Cpu) { - panic!("Network or GPU proving not yet implemented for Miden. Use CPU resource type."); + pub fn new(program: JoltProgram, resource: ProverResource) -> Result { + if !matches!(resource, ProverResource::Cpu) { + Err(CommonError::unsupported_prover_resource_kind( + resource.kind(), + [ProverResourceKind::Cpu], + ))?; } let sdk = JoltSdk::new(program.elf(), JoltConfig::from_env()); Ok(EreJolt { sdk }) @@ -34,7 +37,9 @@ impl EreJolt { impl zkVM for EreJolt { fn execute(&self, input: &Input) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> { if input.proofs.is_some() { - bail!(CommonError::unsupported_input("no dedicated proofs stream")) + bail!(Error::from(CommonError::unsupported_input( + "no dedicated proofs stream" + ))) } let start = Instant::now(); @@ -57,13 +62,15 @@ impl zkVM for EreJolt { proof_kind: ProofKind, ) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> { if input.proofs.is_some() { - bail!(CommonError::unsupported_input("no dedicated proofs stream")) + bail!(Error::from(CommonError::unsupported_input( + "no dedicated proofs stream" + ))) } if proof_kind != ProofKind::Compressed { - bail!(CommonError::unsupported_proof_kind( + bail!(Error::from(CommonError::unsupported_proof_kind( proof_kind, [ProofKind::Compressed] - )) + ))) } let start = Instant::now(); @@ -84,10 +91,10 @@ impl zkVM for EreJolt { fn verify(&self, proof: &Proof) -> anyhow::Result { let Proof::Compressed(proof) = proof else { - bail!(CommonError::unsupported_proof_kind( + bail!(Error::from(CommonError::unsupported_proof_kind( proof.kind(), [ProofKind::Compressed] - )) + ))) }; let proof = JoltProof::deserialize_compressed(&mut Cursor::new(proof)) @@ -118,7 +125,7 @@ mod tests { use ere_zkvm_interface::{ Input, compiler::Compiler, - zkvm::{ProofKind, ProverResourceType, zkVM}, + zkvm::{ProofKind, ProverResource, zkVM}, }; use std::sync::{Mutex, OnceLock}; @@ -141,7 +148,7 @@ mod tests { #[test] fn test_execute() { let program = basic_program(); - let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreJolt::new(program, ProverResource::Cpu).unwrap(); let test_case = BasicProgram::::valid_test_case(); run_zkvm_execute(&zkvm, &test_case); @@ -150,7 +157,7 @@ mod tests { #[test] fn test_execute_invalid_test_case() { let program = basic_program(); - let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreJolt::new(program, ProverResource::Cpu).unwrap(); for input in [ Input::new(), @@ -165,7 +172,7 @@ mod tests { let _guard = PROVE_LOCK.lock().unwrap(); let program = basic_program(); - let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreJolt::new(program, ProverResource::Cpu).unwrap(); let test_case = BasicProgram::::valid_test_case(); run_zkvm_prove(&zkvm, &test_case); @@ -176,7 +183,7 @@ mod tests { let _guard = PROVE_LOCK.lock().unwrap(); let program = basic_program(); - let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreJolt::new(program, ProverResource::Cpu).unwrap(); for input in [ Input::new(), diff --git a/crates/zkvm/miden/src/lib.rs b/crates/zkvm/miden/src/lib.rs index 8f342df..707fade 100644 --- a/crates/zkvm/miden/src/lib.rs +++ b/crates/zkvm/miden/src/lib.rs @@ -10,13 +10,14 @@ //! //! # `zkVM` implementation //! -//! ## Supported `ProverResourceType` +//! ## Supported `ProverResource` //! //! | Resource | Supported | //! | --------- | :-------: | //! | `Cpu` | Yes | //! | `Gpu` | No | //! | `Network` | No | +//! | `Cluster` | No | #![cfg_attr( all(not(test), feature = "compiler", feature = "zkvm"), diff --git a/crates/zkvm/miden/src/zkvm.rs b/crates/zkvm/miden/src/zkvm.rs index ea65254..7363a9b 100644 --- a/crates/zkvm/miden/src/zkvm.rs +++ b/crates/zkvm/miden/src/zkvm.rs @@ -2,7 +2,7 @@ use crate::program::{MidenProgram, MidenProgramInfo, MidenSerdeWrapper}; use anyhow::bail; use ere_zkvm_interface::zkvm::{ CommonError, Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, - ProverResourceType, PublicValues, zkVM, zkVMProgramDigest, + ProverResource, ProverResourceKind, PublicValues, zkVM, zkVMProgramDigest, }; use miden_core::{ Program, @@ -38,9 +38,12 @@ pub struct EreMiden { } impl EreMiden { - pub fn new(program: MidenProgram, resource: ProverResourceType) -> Result { - if !matches!(resource, ProverResourceType::Cpu) { - panic!("Network or GPU proving not yet implemented for Miden. Use CPU resource type."); + pub fn new(program: MidenProgram, resource: ProverResource) -> Result { + if !matches!(resource, ProverResource::Cpu) { + Err(CommonError::unsupported_prover_resource_kind( + resource.kind(), + [ProverResourceKind::Cpu], + ))?; } Ok(Self { program: program.0 }) } @@ -58,7 +61,9 @@ impl EreMiden { impl zkVM for EreMiden { fn execute(&self, input: &Input) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> { if input.proofs.is_some() { - bail!(CommonError::unsupported_input("no dedicated proofs stream")) + bail!(Error::from(CommonError::unsupported_input( + "no dedicated proofs stream" + ))) } let stack_inputs = StackInputs::default(); @@ -92,13 +97,15 @@ impl zkVM for EreMiden { proof_kind: ProofKind, ) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> { if input.proofs.is_some() { - bail!(CommonError::unsupported_input("no dedicated proofs stream")) + bail!(Error::from(CommonError::unsupported_input( + "no dedicated proofs stream" + ))) } if proof_kind != ProofKind::Compressed { - bail!(CommonError::unsupported_proof_kind( + bail!(Error::from(CommonError::unsupported_proof_kind( proof_kind, [ProofKind::Compressed] - )) + ))) } let stack_inputs = StackInputs::default(); @@ -129,10 +136,10 @@ impl zkVM for EreMiden { fn verify(&self, proof: &Proof) -> anyhow::Result { let Proof::Compressed(proof) = proof else { - bail!(CommonError::unsupported_proof_kind( + bail!(Error::from(CommonError::unsupported_proof_kind( proof.kind(), [ProofKind::Compressed] - )) + ))) }; let program_info: ProgramInfo = self.program.clone().into(); @@ -200,7 +207,7 @@ mod tests { use ere_zkvm_interface::{ Input, compiler::Compiler, - zkvm::{ProofKind, ProverResourceType, zkVM}, + zkvm::{ProofKind, ProverResource, zkVM}, }; fn load_miden_program(guest_name: &str) -> MidenProgram { @@ -212,7 +219,7 @@ mod tests { #[test] fn test_prove_and_verify_add() { let program = load_miden_program("add"); - let zkvm = EreMiden::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreMiden::new(program, ProverResource::Cpu).unwrap(); let const_a = -Felt::ONE; let const_b = Felt::ONE / Felt::ONE.double(); @@ -237,7 +244,7 @@ mod tests { #[test] fn test_prove_and_verify_fib() { let program = load_miden_program("fib"); - let zkvm = EreMiden::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreMiden::new(program, ProverResource::Cpu).unwrap(); let n_iterations = 50u32; let expected_fib = Felt::try_from(12_586_269_025u64).unwrap(); @@ -261,7 +268,7 @@ mod tests { #[test] fn test_invalid_test_case() { let program = load_miden_program("add"); - let zkvm = EreMiden::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreMiden::new(program, ProverResource::Cpu).unwrap(); let empty_inputs = Input::new(); assert!(zkvm.execute(&empty_inputs).is_err()); diff --git a/crates/zkvm/nexus/src/lib.rs b/crates/zkvm/nexus/src/lib.rs index 9883687..974b482 100644 --- a/crates/zkvm/nexus/src/lib.rs +++ b/crates/zkvm/nexus/src/lib.rs @@ -10,13 +10,14 @@ //! //! # `zkVM` implementation //! -//! ## Supported `ProverResourceType` +//! ## Supported `ProverResource` //! //! | Resource | Supported | //! | --------- | :-------: | //! | `Cpu` | Yes | //! | `Gpu` | No | //! | `Network` | No | +//! | `Cluster` | No | #![cfg_attr( all(not(test), feature = "compiler", feature = "zkvm"), diff --git a/crates/zkvm/nexus/src/zkvm.rs b/crates/zkvm/nexus/src/zkvm.rs index aa57fac..06d57b3 100644 --- a/crates/zkvm/nexus/src/zkvm.rs +++ b/crates/zkvm/nexus/src/zkvm.rs @@ -2,7 +2,7 @@ use crate::program::NexusProgram; use anyhow::bail; use ere_zkvm_interface::zkvm::{ CommonError, Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, - ProverResourceType, PublicValues, zkVM, + ProverResource, ProverResourceKind, PublicValues, zkVM, }; use nexus_core::nvm::{self, ElfFile, internals::LinearMemoryLayout}; use nexus_sdk::{CheckedView, KnownExitCodes, Viewable}; @@ -42,17 +42,20 @@ pub struct EreNexus { } impl EreNexus { - pub fn new(program: NexusProgram, resource: ProverResourceType) -> Result { + pub fn new(program: NexusProgram, resource: ProverResource) -> Result { Self::with_extensions(program, resource, vec![]) } pub fn with_extensions( program: NexusProgram, - resource: ProverResourceType, + resource: ProverResource, extensions: Vec, ) -> Result { - if !matches!(resource, ProverResourceType::Cpu) { - panic!("Network or GPU proving not yet implemented for Nexus. Use CPU resource type."); + if !matches!(resource, ProverResource::Cpu) { + Err(CommonError::unsupported_prover_resource_kind( + resource.kind(), + [ProverResourceKind::Cpu], + ))?; } Ok(Self { @@ -65,7 +68,9 @@ impl EreNexus { impl zkVM for EreNexus { fn execute(&self, input: &Input) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> { if input.proofs.is_some() { - bail!(CommonError::unsupported_input("no dedicated proofs stream")) + bail!(Error::from(CommonError::unsupported_input( + "no dedicated proofs stream" + ))) } let elf = ElfFile::from_bytes(self.program.elf()).map_err(Error::ParseElf)?; @@ -102,13 +107,15 @@ impl zkVM for EreNexus { proof_kind: ProofKind, ) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> { if input.proofs.is_some() { - bail!(CommonError::unsupported_input("no dedicated proofs stream")) + bail!(Error::from(CommonError::unsupported_input( + "no dedicated proofs stream" + ))) } if proof_kind != ProofKind::Compressed { - bail!(CommonError::unsupported_proof_kind( + bail!(Error::from(CommonError::unsupported_proof_kind( proof_kind, [ProofKind::Compressed] - )) + ))) } let elf = ElfFile::from_bytes(self.program.elf()).map_err(Error::ParseElf)?; @@ -155,10 +162,10 @@ impl zkVM for EreNexus { fn verify(&self, proof: &Proof) -> anyhow::Result { let Proof::Compressed(proof) = proof else { - bail!(CommonError::unsupported_proof_kind( + bail!(Error::from(CommonError::unsupported_proof_kind( proof.kind(), [ProofKind::Compressed] - )) + ))) }; info!("Verifying proof..."); @@ -244,7 +251,7 @@ mod tests { use ere_zkvm_interface::{ Input, compiler::Compiler, - zkvm::{ProofKind, ProverResourceType, zkVM}, + zkvm::{ProofKind, ProverResource, zkVM}, }; use std::sync::OnceLock; @@ -262,7 +269,7 @@ mod tests { #[test] fn test_execute() { let program = basic_program(); - let zkvm = EreNexus::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreNexus::new(program, ProverResource::Cpu).unwrap(); let test_case = BasicProgram::::valid_test_case(); run_zkvm_execute(&zkvm, &test_case); @@ -271,7 +278,7 @@ mod tests { #[test] fn test_execute_invalid_test_case() { let program = basic_program(); - let zkvm = EreNexus::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreNexus::new(program, ProverResource::Cpu).unwrap(); for input in [ Input::new(), @@ -284,7 +291,7 @@ mod tests { #[test] fn test_prove() { let program = basic_program(); - let zkvm = EreNexus::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreNexus::new(program, ProverResource::Cpu).unwrap(); let test_case = BasicProgram::::valid_test_case(); run_zkvm_prove(&zkvm, &test_case); @@ -293,7 +300,7 @@ mod tests { #[test] fn test_prove_invalid_test_case() { let program = basic_program(); - let zkvm = EreNexus::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreNexus::new(program, ProverResource::Cpu).unwrap(); for input in [ Input::new(), diff --git a/crates/zkvm/openvm/src/compiler/rust_rv32ima.rs b/crates/zkvm/openvm/src/compiler/rust_rv32ima.rs index 38dd77e..615e1a8 100644 --- a/crates/zkvm/openvm/src/compiler/rust_rv32ima.rs +++ b/crates/zkvm/openvm/src/compiler/rust_rv32ima.rs @@ -63,7 +63,7 @@ mod tests { use ere_zkvm_interface::{ Input, compiler::Compiler, - zkvm::{ProverResourceType, zkVM}, + zkvm::{ProverResource, zkVM}, }; #[test] @@ -77,7 +77,7 @@ mod tests { fn test_execute() { let guest_directory = testing_guest_directory("openvm", "stock_nightly_no_std"); let program = RustRv32ima.compile(&guest_directory).unwrap(); - let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreOpenVM::new(program, ProverResource::Cpu).unwrap(); zkvm.execute(&Input::new()).unwrap(); } diff --git a/crates/zkvm/openvm/src/lib.rs b/crates/zkvm/openvm/src/lib.rs index 1db3bb0..438215d 100644 --- a/crates/zkvm/openvm/src/lib.rs +++ b/crates/zkvm/openvm/src/lib.rs @@ -25,13 +25,14 @@ //! //! # `zkVM` implementation //! -//! ## Supported `ProverResourceType` +//! ## Supported `ProverResource` //! //! | Resource | Supported | //! | --------- | :-------: | //! | `Cpu` | Yes | //! | `Gpu` | Yes | //! | `Network` | No | +//! | `Cluster` | No | //! //! [`install_openvm_sdk.sh`]: https://github.com/eth-act/ere/blob/master/scripts/sdk_installers/install_openvm_sdk.sh diff --git a/crates/zkvm/openvm/src/zkvm.rs b/crates/zkvm/openvm/src/zkvm.rs index 4773876..5977252 100644 --- a/crates/zkvm/openvm/src/zkvm.rs +++ b/crates/zkvm/openvm/src/zkvm.rs @@ -2,7 +2,7 @@ use crate::program::OpenVMProgram; use anyhow::bail; use ere_zkvm_interface::zkvm::{ CommonError, Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, - ProverResourceType, PublicValues, zkVM, zkVMProgramDigest, + ProverResource, ProverResourceKind, PublicValues, zkVM, zkVMProgramDigest, }; use openvm_circuit::arch::instructions::exe::VmExe; use openvm_continuations::verifier::internal::types::VmStarkProof; @@ -31,22 +31,21 @@ pub struct EreOpenVM { agg_pk: AggProvingKey, agg_vk: AggVerifyingKey, app_commit: AppExecutionCommit, - resource: ProverResourceType, + resource: ProverResource, } impl EreOpenVM { - pub fn new(program: OpenVMProgram, resource: ProverResourceType) -> Result { - match resource { - #[cfg(not(feature = "cuda"))] - ProverResourceType::Gpu => { - panic!("Feature `cuda` is disabled. Enable `cuda` to use GPU resource type") - } - ProverResourceType::Network(_) => { - panic!( - "Network proving not yet implemented for OpenVM. Use CPU or GPU resource type." - ); - } - _ => {} + pub fn new(program: OpenVMProgram, resource: ProverResource) -> Result { + #[cfg(not(feature = "cuda"))] + if resource.is_gpu() { + return Err(Error::CudaFeatureDisabled); + } + + if !matches!(resource, ProverResource::Cpu | ProverResource::Gpu) { + Err(CommonError::unsupported_prover_resource_kind( + resource.kind(), + [ProverResourceKind::Cpu, ProverResourceKind::Gpu], + ))?; } let app_config = if let Some(value) = program.app_config() { @@ -124,7 +123,9 @@ impl EreOpenVM { impl zkVM for EreOpenVM { fn execute(&self, input: &Input) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> { if input.proofs.is_some() { - bail!(CommonError::unsupported_input("no dedicated proofs stream")) + bail!(Error::from(CommonError::unsupported_input( + "no dedicated proofs stream" + ))) } let mut stdin = StdIn::default(); @@ -151,13 +152,15 @@ impl zkVM for EreOpenVM { proof_kind: ProofKind, ) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> { if input.proofs.is_some() { - bail!(CommonError::unsupported_input("no dedicated proofs stream")) + bail!(Error::from(CommonError::unsupported_input( + "no dedicated proofs stream" + ))) } if proof_kind != ProofKind::Compressed { - bail!(CommonError::unsupported_proof_kind( + bail!(Error::from(CommonError::unsupported_proof_kind( proof_kind, [ProofKind::Compressed] - )) + ))) } let mut stdin = StdIn::default(); @@ -165,18 +168,15 @@ impl zkVM for EreOpenVM { let now = std::time::Instant::now(); let (proof, app_commit) = match self.resource { - ProverResourceType::Cpu => self.cpu_sdk()?.prove(self.app_exe.clone(), stdin), + ProverResource::Cpu => self.cpu_sdk()?.prove(self.app_exe.clone(), stdin), #[cfg(feature = "cuda")] - ProverResourceType::Gpu => self.gpu_sdk()?.prove(self.app_exe.clone(), stdin), + ProverResource::Gpu => self.gpu_sdk()?.prove(self.app_exe.clone(), stdin), #[cfg(not(feature = "cuda"))] - ProverResourceType::Gpu => { - panic!("Feature `cuda` is disabled. Enable `cuda` to use GPU resource type") - } - ProverResourceType::Network(_) => { - panic!( - "Network proving not yet implemented for OpenVM. Use CPU or GPU resource type." - ); - } + ProverResource::Gpu => bail!(Error::CudaFeatureDisabled), + _ => bail!(Error::from(CommonError::unsupported_prover_resource_kind( + self.resource.kind(), + [ProverResourceKind::Cpu, ProverResourceKind::Gpu], + ))), } .map_err(Error::Prove)?; let elapsed = now.elapsed(); @@ -205,10 +205,10 @@ impl zkVM for EreOpenVM { fn verify(&self, proof: &Proof) -> anyhow::Result { let Proof::Compressed(proof) = proof else { - bail!(CommonError::unsupported_proof_kind( + bail!(Error::from(CommonError::unsupported_proof_kind( proof.kind(), [ProofKind::Compressed] - )) + ))) }; let proof = VmStarkProof::::decode(&mut proof.as_slice()) @@ -265,7 +265,7 @@ mod tests { }; use ere_zkvm_interface::{ compiler::Compiler, - zkvm::{Input, ProofKind, ProverResourceType, zkVM}, + zkvm::{Input, ProofKind, ProverResource, zkVM}, }; use std::sync::OnceLock; @@ -283,7 +283,7 @@ mod tests { #[test] fn test_execute() { let program = basic_program(); - let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreOpenVM::new(program, ProverResource::Cpu).unwrap(); let test_case = BasicProgram::::valid_test_case().into_output_sha256(); run_zkvm_execute(&zkvm, &test_case); @@ -292,7 +292,7 @@ mod tests { #[test] fn test_execute_invalid_test_case() { let program = basic_program(); - let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreOpenVM::new(program, ProverResource::Cpu).unwrap(); for input in [ Input::new(), @@ -305,7 +305,7 @@ mod tests { #[test] fn test_prove() { let program = basic_program(); - let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreOpenVM::new(program, ProverResource::Cpu).unwrap(); let test_case = BasicProgram::::valid_test_case().into_output_sha256(); run_zkvm_prove(&zkvm, &test_case); @@ -314,7 +314,7 @@ mod tests { #[test] fn test_prove_invalid_test_case() { let program = basic_program(); - let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreOpenVM::new(program, ProverResource::Cpu).unwrap(); for input in [ Input::new(), diff --git a/crates/zkvm/openvm/src/zkvm/error.rs b/crates/zkvm/openvm/src/zkvm/error.rs index 3f758e0..3314389 100644 --- a/crates/zkvm/openvm/src/zkvm/error.rs +++ b/crates/zkvm/openvm/src/zkvm/error.rs @@ -8,6 +8,9 @@ pub enum Error { CommonError(#[from] CommonError), // Common + #[error("Enable `cuda` feature to enable `ProverResource::Gpu`")] + CudaFeatureDisabled, + #[error("Invalid AppConfig: {0}")] InvalidAppConfig(toml::de::Error), diff --git a/crates/zkvm/pico/src/compiler/rust_rv32ima.rs b/crates/zkvm/pico/src/compiler/rust_rv32ima.rs index bc8ff6b..d005e9e 100644 --- a/crates/zkvm/pico/src/compiler/rust_rv32ima.rs +++ b/crates/zkvm/pico/src/compiler/rust_rv32ima.rs @@ -54,7 +54,7 @@ mod tests { use ere_zkvm_interface::{ Input, compiler::Compiler, - zkvm::{ProverResourceType, zkVM}, + zkvm::{ProverResource, zkVM}, }; #[test] @@ -68,7 +68,7 @@ mod tests { fn test_execute() { let guest_directory = testing_guest_directory("pico", "stock_nightly_no_std"); let program = RustRv32ima.compile(&guest_directory).unwrap(); - let zkvm = ErePico::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = ErePico::new(program, ProverResource::Cpu).unwrap(); zkvm.execute(&Input::new()).unwrap(); } diff --git a/crates/zkvm/pico/src/lib.rs b/crates/zkvm/pico/src/lib.rs index 45b7415..74f810f 100644 --- a/crates/zkvm/pico/src/lib.rs +++ b/crates/zkvm/pico/src/lib.rs @@ -20,13 +20,14 @@ //! //! # `zkVM` implementation //! -//! ## Supported `ProverResourceType` +//! ## Supported `ProverResource` //! //! | Resource | Supported | //! | --------- | :-------: | //! | `Cpu` | Yes | //! | `Gpu` | No | //! | `Network` | No | +//! | `Cluster` | No | //! //! [`install_pico_sdk.sh`]: https://github.com/eth-act/ere/blob/master/scripts/sdk_installers/install_pico_sdk.sh diff --git a/crates/zkvm/pico/src/zkvm.rs b/crates/zkvm/pico/src/zkvm.rs index 98eab7c..d3b68b9 100644 --- a/crates/zkvm/pico/src/zkvm.rs +++ b/crates/zkvm/pico/src/zkvm.rs @@ -5,7 +5,7 @@ use crate::{ use anyhow::bail; use ere_zkvm_interface::zkvm::{ CommonError, Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, - ProverResourceType, PublicValues, zkVM, zkVMProgramDigest, + ProverResource, ProverResourceKind, PublicValues, zkVM, zkVMProgramDigest, }; use pico_p3_field::PrimeField32; use pico_vm::emulator::stdin::EmulatorStdinBuilder; @@ -31,9 +31,12 @@ pub struct ErePico { } impl ErePico { - pub fn new(program: PicoProgram, resource: ProverResourceType) -> Result { - if !matches!(resource, ProverResourceType::Cpu) { - panic!("Network or GPU proving not yet implemented for Pico. Use CPU resource type."); + pub fn new(program: PicoProgram, resource: ProverResource) -> Result { + if !matches!(resource, ProverResource::Cpu) { + Err(CommonError::unsupported_prover_resource_kind( + resource.kind(), + [ProverResourceKind::Cpu], + ))?; } Ok(ErePico { program }) } @@ -46,7 +49,9 @@ impl ErePico { impl zkVM for ErePico { fn execute(&self, input: &Input) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> { if input.proofs.is_some() { - bail!(CommonError::unsupported_input("no dedicated proofs stream")) + bail!(Error::from(CommonError::unsupported_input( + "no dedicated proofs stream" + ))) } let mut stdin = EmulatorStdinBuilder::default(); @@ -77,13 +82,15 @@ impl zkVM for ErePico { proof_kind: ProofKind, ) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> { if input.proofs.is_some() { - bail!(CommonError::unsupported_input("no dedicated proofs stream")) + bail!(Error::from(CommonError::unsupported_input( + "no dedicated proofs stream" + ))) } if proof_kind != ProofKind::Compressed { - bail!(CommonError::unsupported_proof_kind( + bail!(Error::from(CommonError::unsupported_proof_kind( proof_kind, [ProofKind::Compressed] - )) + ))) } let mut stdin = EmulatorStdinBuilder::default(); @@ -116,10 +123,10 @@ impl zkVM for ErePico { fn verify(&self, proof: &Proof) -> anyhow::Result { let Proof::Compressed(proof) = proof else { - bail!(CommonError::unsupported_proof_kind( + bail!(Error::from(CommonError::unsupported_proof_kind( proof.kind(), [ProofKind::Compressed] - )) + ))) }; let client = self.client(); @@ -195,7 +202,7 @@ mod tests { }; use ere_zkvm_interface::{ compiler::Compiler, - zkvm::{Input, ProofKind, ProverResourceType, zkVM}, + zkvm::{Input, ProofKind, ProverResource, zkVM}, }; use std::sync::OnceLock; @@ -213,7 +220,7 @@ mod tests { #[test] fn test_execute() { let program = basic_program(); - let zkvm = ErePico::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = ErePico::new(program, ProverResource::Cpu).unwrap(); let test_case = BasicProgram::::valid_test_case(); run_zkvm_execute(&zkvm, &test_case); @@ -222,7 +229,7 @@ mod tests { #[test] fn test_execute_invalid_test_case() { let program = basic_program(); - let zkvm = ErePico::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = ErePico::new(program, ProverResource::Cpu).unwrap(); for input in [ Input::new(), @@ -235,7 +242,7 @@ mod tests { #[test] fn test_prove() { let program = basic_program(); - let zkvm = ErePico::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = ErePico::new(program, ProverResource::Cpu).unwrap(); let test_case = BasicProgram::::valid_test_case(); run_zkvm_prove(&zkvm, &test_case); @@ -244,7 +251,7 @@ mod tests { #[test] fn test_prove_invalid_test_case() { let program = basic_program(); - let zkvm = ErePico::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = ErePico::new(program, ProverResource::Cpu).unwrap(); for input in [ Input::new(), diff --git a/crates/zkvm/risc0/src/compiler/rust_rv32ima.rs b/crates/zkvm/risc0/src/compiler/rust_rv32ima.rs index e02dbf3..d3aee4c 100644 --- a/crates/zkvm/risc0/src/compiler/rust_rv32ima.rs +++ b/crates/zkvm/risc0/src/compiler/rust_rv32ima.rs @@ -66,7 +66,7 @@ mod tests { use ere_zkvm_interface::{ Input, compiler::Compiler, - zkvm::{ProverResourceType, zkVM}, + zkvm::{ProverResource, zkVM}, }; #[test] @@ -80,7 +80,7 @@ mod tests { fn test_execute() { let guest_directory = testing_guest_directory("risc0", "stock_nightly_no_std"); let program = RustRv32ima.compile(&guest_directory).unwrap(); - let zkvm = EreRisc0::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreRisc0::new(program, ProverResource::Cpu).unwrap(); zkvm.execute(&Input::new()).unwrap(); } diff --git a/crates/zkvm/risc0/src/lib.rs b/crates/zkvm/risc0/src/lib.rs index c349271..94b3d32 100644 --- a/crates/zkvm/risc0/src/lib.rs +++ b/crates/zkvm/risc0/src/lib.rs @@ -17,7 +17,7 @@ //! //! - [`rzup`] //! - Installation via `rzup install` -//! - `r0vm-cuda` - Used by `zkVM::prove` if `ProverResourceType::Gpu` is +//! - `r0vm-cuda` - Used by `zkVM::prove` if `ProverResource::Gpu` is //! selected //! - `docker` - Used by `zkVM::prove` if `ProofKind::Groth16` is selected //! @@ -32,13 +32,14 @@ //! //! # `zkVM` implementation //! -//! ## Supported `ProverResourceType` +//! ## Supported `ProverResource` //! //! | Resource | Supported | //! | --------- | :-------: | //! | `Cpu` | Yes | //! | `Gpu` | Yes | //! | `Network` | No | +//! | `Cluster` | No | //! //! [`install_risc0_sdk.sh`]: https://github.com/eth-act/ere/blob/master/scripts/sdk_installers/install_risc0_sdk.sh //! [`rzup`]: https://risczero.com/install diff --git a/crates/zkvm/risc0/src/zkvm.rs b/crates/zkvm/risc0/src/zkvm.rs index 35f2a2a..bd4a5f4 100644 --- a/crates/zkvm/risc0/src/zkvm.rs +++ b/crates/zkvm/risc0/src/zkvm.rs @@ -2,7 +2,7 @@ use crate::program::Risc0Program; use anyhow::bail; use ere_zkvm_interface::zkvm::{ CommonError, Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, - ProverResourceType, PublicValues, zkVM, zkVMProgramDigest, + ProverResource, ProverResourceKind, PublicValues, zkVM, zkVMProgramDigest, }; use risc0_zkvm::{ AssumptionReceipt, DEFAULT_MAX_PO2, DefaultProver, Digest, ExecutorEnv, ExternalProver, @@ -44,33 +44,41 @@ const KECCAK_PO2_RANGE: RangeInclusive = 14..=18; pub struct EreRisc0 { program: Risc0Program, - resource: ProverResourceType, + resource: ProverResource, segment_po2: usize, keccak_po2: usize, } impl EreRisc0 { - pub fn new(program: Risc0Program, resource: ProverResourceType) -> Result { - if matches!(resource, ProverResourceType::Network(_)) { - panic!( - "Network proving not yet implemented for RISC Zero. Use CPU or GPU resource type." - ); + pub fn new(program: Risc0Program, resource: ProverResource) -> Result { + if !matches!(resource, ProverResource::Cpu | ProverResource::Gpu) { + Err(CommonError::unsupported_prover_resource_kind( + resource.kind(), + [ProverResourceKind::Cpu, ProverResourceKind::Gpu], + ))?; } - let [segment_po2, keccak_po2] = [ - ("RISC0_SEGMENT_PO2", DEFAULT_SEGMENT_PO2, SEGMENT_PO2_RANGE), - ("RISC0_KECCAK_PO2", DEFAULT_KECCAK_PO2, KECCAK_PO2_RANGE), - ] - .map(|(key, default, range)| { - let val = env::var(key) - .ok() - .and_then(|po2| po2.parse::().ok()) - .unwrap_or(default); - if !range.contains(&val) { - panic!("Unsupported po2 value {val} of {key}, expected in range {range:?}") + let parse_env = |key: &str, default: usize, range: RangeInclusive| { + let Ok(val) = env::var(key) else { + return Ok(default); + }; + + match val.parse() { + Ok(val) if range.contains(&val) => Ok(val), + _ => Err(Error::UnsupportedPo2Value { + key: key.to_string(), + val, + range, + }), } - val - }); + }; + + let segment_po2 = parse_env( + "ERE_RISC0_SEGMENT_PO2", + DEFAULT_SEGMENT_PO2, + SEGMENT_PO2_RANGE, + )?; + let keccak_po2 = parse_env("ERE_RISC0_KECCAK_PO2", DEFAULT_KECCAK_PO2, KECCAK_PO2_RANGE)?; Ok(Self { program, @@ -112,8 +120,8 @@ impl zkVM for EreRisc0 { let env = self.input_to_env(input)?; let prover = match self.resource { - ProverResourceType::Cpu => Rc::new(ExternalProver::new("ipc", "r0vm")), - ProverResourceType::Gpu => { + ProverResource::Cpu => Rc::new(ExternalProver::new("ipc", "r0vm")), + ProverResource::Gpu => { if cfg!(feature = "metal") { // When `metal` is enabled, we use the `LocalProver` to do // proving. but it's not public so we use `default_prover` @@ -127,11 +135,10 @@ impl zkVM for EreRisc0 { Rc::new(DefaultProver::new("r0vm-cuda").map_err(Error::InitializeCudaProver)?) } } - ProverResourceType::Network(_) => { - panic!( - "Network proving not yet implemented for RISC Zero. Use CPU or GPU resource type." - ); - } + _ => bail!(Error::from(CommonError::unsupported_prover_resource_kind( + self.resource.kind(), + [ProverResourceKind::Cpu, ProverResourceKind::Gpu], + ))), }; let opts = match proof_kind { @@ -234,7 +241,7 @@ mod tests { use ere_zkvm_interface::{ Input, compiler::Compiler, - zkvm::{ProofKind, ProverResourceType, zkVM}, + zkvm::{ProofKind, ProverResource, zkVM}, }; use std::sync::OnceLock; @@ -252,7 +259,7 @@ mod tests { #[test] fn test_execute() { let program = basic_program(); - let zkvm = EreRisc0::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreRisc0::new(program, ProverResource::Cpu).unwrap(); let test_case = BasicProgram::::valid_test_case(); run_zkvm_execute(&zkvm, &test_case); @@ -261,7 +268,7 @@ mod tests { #[test] fn test_execute_invalid_test_case() { let program = basic_program(); - let zkvm = EreRisc0::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreRisc0::new(program, ProverResource::Cpu).unwrap(); for input in [ Input::new(), @@ -274,7 +281,7 @@ mod tests { #[test] fn test_prove() { let program = basic_program(); - let zkvm = EreRisc0::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreRisc0::new(program, ProverResource::Cpu).unwrap(); let test_case = BasicProgram::::valid_test_case(); run_zkvm_prove(&zkvm, &test_case); @@ -283,7 +290,7 @@ mod tests { #[test] fn test_prove_invalid_test_case() { let program = basic_program(); - let zkvm = EreRisc0::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreRisc0::new(program, ProverResource::Cpu).unwrap(); for input in [ Input::new(), @@ -300,7 +307,7 @@ mod tests { .unwrap(); for i in 1..=16_u32 { - let zkvm = EreRisc0::new(program.clone(), ProverResourceType::Cpu).unwrap(); + let zkvm = EreRisc0::new(program.clone(), ProverResource::Cpu).unwrap(); let input = Input::new().with_stdin(i.to_le_bytes().to_vec()); diff --git a/crates/zkvm/risc0/src/zkvm/error.rs b/crates/zkvm/risc0/src/zkvm/error.rs index 8119b79..135c2ff 100644 --- a/crates/zkvm/risc0/src/zkvm/error.rs +++ b/crates/zkvm/risc0/src/zkvm/error.rs @@ -1,12 +1,23 @@ -use ere_zkvm_interface::zkvm::ProofKind; +use core::ops::RangeInclusive; +use ere_zkvm_interface::zkvm::{CommonError, ProofKind}; use risc0_zkp::verify::VerificationError; use thiserror::Error; #[derive(Debug, Error)] pub enum Error { + #[error(transparent)] + CommonError(#[from] CommonError), + #[error("Deserialize proofs in Input failed: {0:?}")] DeserializeInputProofs(bincode::error::DecodeError), + #[error("Unsupported power of 2 value {val} of {key}, expected in range {range:?}")] + UnsupportedPo2Value { + key: String, + val: String, + range: RangeInclusive, + }, + // Execute #[error("Failed to build `ExecutorEnv`: {0}")] BuildExecutorEnv(anyhow::Error), diff --git a/crates/zkvm/sp1/src/compiler/rust_rv32ima.rs b/crates/zkvm/sp1/src/compiler/rust_rv32ima.rs index 8350c6d..509c328 100644 --- a/crates/zkvm/sp1/src/compiler/rust_rv32ima.rs +++ b/crates/zkvm/sp1/src/compiler/rust_rv32ima.rs @@ -54,7 +54,7 @@ mod tests { use ere_zkvm_interface::{ Input, compiler::Compiler, - zkvm::{ProverResourceType, zkVM}, + zkvm::{ProverResource, zkVM}, }; #[test] @@ -68,7 +68,7 @@ mod tests { fn test_execute() { let guest_directory = testing_guest_directory("sp1", "stock_nightly_no_std"); let program = RustRv32ima.compile(&guest_directory).unwrap(); - let zkvm = EreSP1::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreSP1::new(program, ProverResource::Cpu).unwrap(); zkvm.execute(&Input::new()).unwrap(); } diff --git a/crates/zkvm/sp1/src/lib.rs b/crates/zkvm/sp1/src/lib.rs index ebabc57..12f45ce 100644 --- a/crates/zkvm/sp1/src/lib.rs +++ b/crates/zkvm/sp1/src/lib.rs @@ -12,7 +12,7 @@ //! //! ## `zkVM` requirements //! -//! - `docker` - Used by `zkVM::prove` if `ProverResourceType::Gpu` is selected +//! - `docker` - Used by `zkVM::prove` if `ProverResource::Gpu` is selected //! //! # `Compiler` implementation //! @@ -25,13 +25,14 @@ //! //! # `zkVM` implementation //! -//! ## Supported `ProverResourceType` +//! ## Supported `ProverResource` //! //! | Resource | Supported | //! | --------- | :-------: | //! | `Cpu` | Yes | //! | `Gpu` | Yes | //! | `Network` | Yes | +//! | `Cluster` | No | //! //! [`install_sp1_sdk.sh`]: https://github.com/eth-act/ere/blob/master/scripts/sdk_installers/install_sp1_sdk.sh //! [`sp1up`]: https://sp1up.succinct.xyz diff --git a/crates/zkvm/sp1/src/zkvm.rs b/crates/zkvm/sp1/src/zkvm.rs index 672fb43..83f7208 100644 --- a/crates/zkvm/sp1/src/zkvm.rs +++ b/crates/zkvm/sp1/src/zkvm.rs @@ -2,7 +2,7 @@ use crate::{program::SP1Program, zkvm::sdk::Prover}; use anyhow::bail; use ere_zkvm_interface::zkvm::{ CommonError, Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, - ProverResourceType, PublicValues, zkVM, zkVMProgramDigest, + ProverResource, PublicValues, zkVM, zkVMProgramDigest, }; use sp1_sdk::{SP1ProofMode, SP1ProofWithPublicValues, SP1ProvingKey, SP1Stdin, SP1VerifyingKey}; use std::{ @@ -23,7 +23,7 @@ include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs")); pub struct EreSP1 { program: SP1Program, /// Prover resource configuration for creating clients - resource: ProverResourceType, + resource: ProverResource, /// Proving key pk: SP1ProvingKey, /// Verification key @@ -39,7 +39,7 @@ pub struct EreSP1 { } impl EreSP1 { - pub fn new(program: SP1Program, resource: ProverResourceType) -> Result { + pub fn new(program: SP1Program, resource: ProverResource) -> Result { let prover = Prover::new(&resource)?; let (pk, vk) = prover.setup(&program.elf)?; Ok(Self { @@ -97,7 +97,7 @@ impl zkVM for EreSP1 { let mut prover = self.prover_mut()?; // Restart GPU prover if the prover is dropped before. - if matches!(self.resource, ProverResourceType::Gpu) && matches!(&*prover, Prover::Cpu(_)) { + if matches!(self.resource, ProverResource::Gpu) && matches!(&*prover, Prover::Cpu(_)) { *prover = Prover::new(&self.resource).and_then(|prover| { prover.setup(&self.program.elf)?; Ok(prover) @@ -107,7 +107,7 @@ impl zkVM for EreSP1 { let start = Instant::now(); let proof = panic::catch_unwind(|| prover.prove(&self.pk, &stdin, mode)).map_err(|err| { - if matches!(self.resource, ProverResourceType::Gpu) { + if matches!(self.resource, ProverResource::Gpu) { // Drop the panicked GPU prover and replace it with CPU one, // next prove call will try to restart it. take(&mut *prover); @@ -201,7 +201,7 @@ mod tests { use ere_zkvm_interface::{ Input, compiler::Compiler, - zkvm::{NetworkProverConfig, ProofKind, ProverResourceType, zkVM}, + zkvm::{ProofKind, ProverResource, RemoteProverConfig, zkVM}, }; use std::sync::OnceLock; @@ -219,7 +219,7 @@ mod tests { #[test] fn test_execute() { let program = basic_program(); - let zkvm = EreSP1::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreSP1::new(program, ProverResource::Cpu).unwrap(); let test_case = BasicProgram::::valid_test_case(); run_zkvm_execute(&zkvm, &test_case); @@ -228,7 +228,7 @@ mod tests { #[test] fn test_execute_invalid_test_case() { let program = basic_program(); - let zkvm = EreSP1::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreSP1::new(program, ProverResource::Cpu).unwrap(); for input in [ Input::new(), @@ -241,7 +241,7 @@ mod tests { #[test] fn test_prove() { let program = basic_program(); - let zkvm = EreSP1::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreSP1::new(program, ProverResource::Cpu).unwrap(); let test_case = BasicProgram::::valid_test_case(); run_zkvm_prove(&zkvm, &test_case); @@ -250,7 +250,7 @@ mod tests { #[test] fn test_prove_invalid_test_case() { let program = basic_program(); - let zkvm = EreSP1::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreSP1::new(program, ProverResource::Cpu).unwrap(); for input in [ Input::new(), @@ -269,13 +269,13 @@ mod tests { return; } - // Create a network prover configuration - let network_config = NetworkProverConfig { + // Create a remote prover configuration + let config = RemoteProverConfig { endpoint: std::env::var("NETWORK_RPC_URL").unwrap_or_default(), api_key: std::env::var("NETWORK_PRIVATE_KEY").ok(), }; let program = basic_program(); - let zkvm = EreSP1::new(program, ProverResourceType::Network(network_config)).unwrap(); + let zkvm = EreSP1::new(program, ProverResource::Network(config)).unwrap(); let test_case = BasicProgram::::valid_test_case(); run_zkvm_prove(&zkvm, &test_case); diff --git a/crates/zkvm/sp1/src/zkvm/error.rs b/crates/zkvm/sp1/src/zkvm/error.rs index 582df30..a326e16 100644 --- a/crates/zkvm/sp1/src/zkvm/error.rs +++ b/crates/zkvm/sp1/src/zkvm/error.rs @@ -19,6 +19,9 @@ pub enum Error { #[error("Deserialize proofs in Input failed: {0:?}")] DeserializeInputProofs(bincode::error::DecodeError), + #[error("Missing `api_key` in `RemoteProverConfig`")] + MissingApiKey, + // Execute #[error("SP1 execution failed: {0}")] Execute(#[source] anyhow::Error), diff --git a/crates/zkvm/sp1/src/zkvm/sdk.rs b/crates/zkvm/sp1/src/zkvm/sdk.rs index 0725c03..2974d38 100644 --- a/crates/zkvm/sp1/src/zkvm/sdk.rs +++ b/crates/zkvm/sp1/src/zkvm/sdk.rs @@ -1,7 +1,7 @@ use crate::zkvm::{Error, panic_msg}; use ere_zkvm_interface::{ - CommonError, - zkvm::{NetworkProverConfig, ProverResourceType}, + CommonError, RemoteProverConfig, + zkvm::{ProverResource, ProverResourceKind}, }; use sp1_sdk::{ CpuProver, NetworkProver, Prover as _, ProverClient, SP1ProofMode, SP1ProofWithPublicValues, @@ -27,16 +27,24 @@ pub enum Prover { impl Default for Prover { fn default() -> Self { - Self::new(&ProverResourceType::Cpu).unwrap() + Self::new(&ProverResource::Cpu).unwrap() } } impl Prover { - pub fn new(resource: &ProverResourceType) -> Result { + pub fn new(resource: &ProverResource) -> Result { Ok(match resource { - ProverResourceType::Cpu => Self::Cpu(ProverClient::builder().cpu().build()), - ProverResourceType::Gpu => Self::Gpu(CudaProver::new()?), - ProverResourceType::Network(config) => Self::Network(build_network_prover(config)), + ProverResource::Cpu => Self::Cpu(ProverClient::builder().cpu().build()), + ProverResource::Gpu => Self::Gpu(CudaProver::new()?), + ProverResource::Network(config) => Self::Network(build_network_prover(config)?), + _ => Err(CommonError::unsupported_prover_resource_kind( + resource.kind(), + [ + ProverResourceKind::Cpu, + ProverResourceKind::Gpu, + ProverResourceKind::Network, + ], + ))?, }) } @@ -168,7 +176,7 @@ impl CudaProver { } } -fn build_network_prover(config: &NetworkProverConfig) -> NetworkProver { +fn build_network_prover(config: &RemoteProverConfig) -> Result { let mut builder = ProverClient::builder().network(); // Check if we have a private key in the config or environment if let Some(api_key) = &config.api_key { @@ -176,9 +184,7 @@ fn build_network_prover(config: &NetworkProverConfig) -> NetworkProver { } else if let Ok(private_key) = env::var("NETWORK_PRIVATE_KEY") { builder = builder.private_key(&private_key); } else { - panic!( - "Network proving requires a private key. Set NETWORK_PRIVATE_KEY environment variable or provide api_key in NetworkProverConfig" - ); + return Err(Error::MissingApiKey); } // Set the RPC URL if provided if !config.endpoint.is_empty() { @@ -187,5 +193,5 @@ fn build_network_prover(config: &NetworkProverConfig) -> NetworkProver { builder = builder.rpc_url(&rpc_url); } // Otherwise SP1 SDK will use its default RPC URL - builder.build() + Ok(builder.build()) } diff --git a/crates/zkvm/ziren/src/lib.rs b/crates/zkvm/ziren/src/lib.rs index 922540f..cbf089d 100644 --- a/crates/zkvm/ziren/src/lib.rs +++ b/crates/zkvm/ziren/src/lib.rs @@ -19,13 +19,14 @@ //! //! # `zkVM` implementation //! -//! ## Supported `ProverResourceType` +//! ## Supported `ProverResource` //! //! | Resource | Supported | //! | --------- | :-------: | //! | `Cpu` | Yes | //! | `Gpu` | No | //! | `Network` | No | +//! | `Cluster` | No | //! //! [`install_ziren_sdk.sh`]: https://github.com/eth-act/ere/blob/master/scripts/sdk_installers/install_ziren_sdk.sh diff --git a/crates/zkvm/ziren/src/zkvm.rs b/crates/zkvm/ziren/src/zkvm.rs index dd4ea3a..9ec63b4 100644 --- a/crates/zkvm/ziren/src/zkvm.rs +++ b/crates/zkvm/ziren/src/zkvm.rs @@ -2,7 +2,7 @@ use crate::program::ZirenProgram; use anyhow::bail; use ere_zkvm_interface::zkvm::{ CommonError, Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, - ProverResourceType, PublicValues, zkVM, zkVMProgramDigest, + ProverResource, ProverResourceKind, PublicValues, zkVM, zkVMProgramDigest, }; use std::{panic, time::Instant}; use tracing::info; @@ -24,12 +24,12 @@ pub struct EreZiren { } impl EreZiren { - pub fn new(program: ZirenProgram, resource: ProverResourceType) -> Result { - if matches!( - resource, - ProverResourceType::Gpu | ProverResourceType::Network(_) - ) { - panic!("Network or Gpu proving not yet implemented for ZKM. Use CPU resource type."); + pub fn new(program: ZirenProgram, resource: ProverResource) -> Result { + if !matches!(resource, ProverResource::Cpu) { + Err(CommonError::unsupported_prover_resource_kind( + resource.kind(), + [ProverResourceKind::Cpu], + ))?; } let (pk, vk) = CpuProver::new().setup(program.elf()); Ok(Self { program, pk, vk }) @@ -160,7 +160,7 @@ mod tests { }; use ere_zkvm_interface::{ compiler::Compiler, - zkvm::{Input, ProofKind, ProverResourceType, zkVM}, + zkvm::{Input, ProofKind, ProverResource, zkVM}, }; use std::sync::OnceLock; @@ -178,7 +178,7 @@ mod tests { #[test] fn test_execute() { let program = basic_program(); - let zkvm = EreZiren::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreZiren::new(program, ProverResource::Cpu).unwrap(); let test_case = BasicProgram::::valid_test_case(); run_zkvm_execute(&zkvm, &test_case); @@ -187,7 +187,7 @@ mod tests { #[test] fn test_execute_invalid_test_case() { let program = basic_program(); - let zkvm = EreZiren::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreZiren::new(program, ProverResource::Cpu).unwrap(); for input in [ Input::new(), @@ -200,7 +200,7 @@ mod tests { #[test] fn test_prove() { let program = basic_program(); - let zkvm = EreZiren::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreZiren::new(program, ProverResource::Cpu).unwrap(); let test_case = BasicProgram::::valid_test_case(); run_zkvm_prove(&zkvm, &test_case); @@ -209,7 +209,7 @@ mod tests { #[test] fn test_prove_invalid_test_case() { let program = basic_program(); - let zkvm = EreZiren::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreZiren::new(program, ProverResource::Cpu).unwrap(); for input in [ Input::new(), diff --git a/crates/zkvm/ziren/src/zkvm/error.rs b/crates/zkvm/ziren/src/zkvm/error.rs index fe2f5bf..bc70e82 100644 --- a/crates/zkvm/ziren/src/zkvm/error.rs +++ b/crates/zkvm/ziren/src/zkvm/error.rs @@ -1,9 +1,12 @@ -use ere_zkvm_interface::zkvm::ProofKind; +use ere_zkvm_interface::zkvm::{CommonError, ProofKind}; use thiserror::Error; use zkm_sdk::{ZKMProofKind, ZKMVerificationError}; #[derive(Debug, Error)] pub enum Error { + #[error(transparent)] + CommonError(#[from] CommonError), + // Execute #[error("Ziren execution failed: {0}")] Execute(#[source] anyhow::Error), diff --git a/crates/zkvm/zisk/Cargo.toml b/crates/zkvm/zisk/Cargo.toml index 581a6ff..7dba35b 100644 --- a/crates/zkvm/zisk/Cargo.toml +++ b/crates/zkvm/zisk/Cargo.toml @@ -8,18 +8,28 @@ license.workspace = true [dependencies] anyhow.workspace = true blake3.workspace = true -bytemuck.workspace = true +bytemuck = { workspace = true, features = ["extern_crate_alloc"] } +futures-util.workspace = true +http.workspace = true +parking_lot.workspace = true serde.workspace = true strum = { workspace = true, features = ["derive"] } tempfile.workspace = true thiserror.workspace = true +tokio = { workspace = true, features = ["rt-multi-thread"] } +tonic.workspace = true tracing.workspace = true +uuid = { workspace = true, features = ["v4"] } wait-timeout.workspace = true # Local dependencies ere-compile-utils = { workspace = true, optional = true } ere-zkvm-interface.workspace = true +# Zisk dependencies +zisk-distributed-grpc-api.workspace = true +zisk-proofman-verifier.workspace = true + [dev-dependencies] ere-test-utils = { workspace = true, features = ["host"] } diff --git a/crates/zkvm/zisk/src/compiler/go_customized.rs b/crates/zkvm/zisk/src/compiler/go_customized.rs index 825d6ea..3e47817 100644 --- a/crates/zkvm/zisk/src/compiler/go_customized.rs +++ b/crates/zkvm/zisk/src/compiler/go_customized.rs @@ -68,7 +68,7 @@ mod tests { io::serde::cbor::Cbor, program::basic::BasicProgram, }; - use ere_zkvm_interface::{ProverResourceType, compiler::Compiler}; + use ere_zkvm_interface::{ProverResource, compiler::Compiler}; #[test] fn test_compile() { @@ -81,7 +81,7 @@ mod tests { fn test_execute() { let guest_directory = testing_guest_directory("zisk", "basic_go"); let program = GoCustomized.compile(&guest_directory).unwrap(); - let zkvm = EreZisk::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreZisk::new(program, ProverResource::Cpu).unwrap(); let test_case = BasicProgram::::valid_test_case(); run_zkvm_execute(&zkvm, &test_case); diff --git a/crates/zkvm/zisk/src/lib.rs b/crates/zkvm/zisk/src/lib.rs index 87288ed..2eebb95 100644 --- a/crates/zkvm/zisk/src/lib.rs +++ b/crates/zkvm/zisk/src/lib.rs @@ -16,7 +16,7 @@ //! ## `zkVM` requirements //! //! - Installation via [`ziskup`] -//! - `cargo-zisk-cuda` - Used by `zkVM::prove` if `ProverResourceType::Gpu` is +//! - `cargo-zisk-cuda` - Used by `zkVM::prove` if `ProverResource::Gpu` is //! selected //! //! # `Compiler` implementation @@ -30,13 +30,31 @@ //! //! # `zkVM` implementation //! -//! ## Supported `ProverResourceType` +//! ## Supported `ProverResource` //! //! | Resource | Supported | //! | --------- | :-------: | //! | `Cpu` | Yes | //! | `Gpu` | Yes | //! | `Network` | No | +//! | `Cluster` | Yes | +//! +//! ## Environment variables +//! +//! | Variable | Type | Default | Description | +//! | -------------------------------------- | ----- | ------- | ---------------------------------------------------------------------- | +//! | `ERE_ZISK_SETUP_ON_INIT` | Flag | | Trigger ROM setup at initialization instead of lazily | +//! | `ERE_ZISK_PORT` | Value | | Pass `--port {port}` to the server | +//! | `ERE_ZISK_UNLOCK_MAPPED_MEMORY` | Flag | | Pass `--unlock-mapped-memory` to the server | +//! | `ERE_ZISK_MINIMAL_MEMORY` | Flag | | Pass `--minimal_memory` to the server | +//! | `ERE_ZISK_PREALLOCATE` | Flag | | Pass `--preallocate` to the server | +//! | `ERE_ZISK_SHARED_TABLES` | Flag | | Pass `--shared-tables` to the server | +//! | `ERE_ZISK_MAX_STREAMS` | Value | | Pass `--max-streams {max_streams}` to the server | +//! | `ERE_ZISK_NUMBER_THREADS_WITNESS` | Value | | Pass `--number-threads-witness {number_threads_witness}` to the server | +//! | `ERE_ZISK_MAX_WITNESS_STORED` | Value | | Pass `--max-witness-stored {max_witness_stored}` to the server | +//! | `ERE_ZISK_START_SERVER_TIMEOUT_SEC` | Value | `120` | Timeout waiting for server to start | +//! | `ERE_ZISK_SHUTDOWN_SERVER_TIMEOUT_SEC` | Value | `30` | Timeout for server shutdown | +//! | `ERE_ZISK_PROVE_TIMEOUT_SEC` | Value | `3600` | Timeout for proof generation | //! //! [`install_zisk_sdk.sh`]: https://github.com/eth-act/ere/blob/master/scripts/sdk_installers/install_zisk_sdk.sh //! [`install_tamago.sh`]: https://github.com/eth-act/ere/blob/master/scripts/install_tamago.sh diff --git a/crates/zkvm/zisk/src/zkvm.rs b/crates/zkvm/zisk/src/zkvm.rs index d7029c2..8f97754 100644 --- a/crates/zkvm/zisk/src/zkvm.rs +++ b/crates/zkvm/zisk/src/zkvm.rs @@ -1,20 +1,18 @@ use crate::{ program::ZiskProgram, - zkvm::sdk::{RomDigest, START_SERVER_TIMEOUT, ZiskOptions, ZiskSdk, ZiskServer}, + zkvm::sdk::{RomDigest, ZiskSdk}, }; use anyhow::bail; use ere_zkvm_interface::zkvm::{ CommonError, Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProofKind, - ProverResourceType, PublicValues, zkVM, zkVMProgramDigest, + ProverResource, PublicValues, zkVM, zkVMProgramDigest, }; -use std::{ - sync::{Mutex, MutexGuard}, - time::Instant, -}; -use tracing::error; +use std::time::Instant; +mod cluster_client; mod error; mod sdk; +mod server; pub use error::Error; @@ -22,57 +20,21 @@ include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs")); pub struct EreZisk { sdk: ZiskSdk, - /// Use `Mutex` because the server can only handle signle proving task at a - /// time. - /// - /// Use `Option` inside to lazily initialize only when `prove` is called. - server: Mutex>, } impl EreZisk { - pub fn new(program: ZiskProgram, resource: ProverResourceType) -> Result { - if matches!(resource, ProverResourceType::Network(_)) { - panic!("Network proving not yet implemented for ZisK. Use CPU or GPU resource type."); - } - let sdk = ZiskSdk::new(program.elf, resource, ZiskOptions::from_env())?; - Ok(Self { - sdk, - server: Mutex::new(None), - }) - } - - fn server(&'_ self) -> Result>, Error> { - let mut server = self.server.lock().map_err(|_| Error::MutexPoisoned)?; - - if server - .as_ref() - .is_none_or(|server| server.status(START_SERVER_TIMEOUT).is_err()) - { - const MAX_RETRY: usize = 3; - let mut retry = 0; - *server = loop { - drop(server.take()); - match self.sdk.server() { - Ok(server) => break Some(server), - Err(Error::TimeoutWaitingServerReady) if retry < MAX_RETRY => { - error!("Timeout waiting server ready, restarting..."); - retry += 1; - continue; - } - Err(err) => return Err(err), - } - } - } - - // FIXME: Use `MutexGuard::map` to unwrap the inner `Option` when it's stabilized. - Ok(server) + pub fn new(program: ZiskProgram, resource: ProverResource) -> Result { + let sdk = ZiskSdk::new(program.elf, resource)?; + Ok(Self { sdk }) } } impl zkVM for EreZisk { fn execute(&self, input: &Input) -> anyhow::Result<(PublicValues, ProgramExecutionReport)> { if input.proofs.is_some() { - bail!(CommonError::unsupported_input("no dedicated proofs stream")) + bail!(Error::from(CommonError::unsupported_input( + "no dedicated proofs stream" + ))) } let start = Instant::now(); @@ -95,21 +57,18 @@ impl zkVM for EreZisk { proof_kind: ProofKind, ) -> anyhow::Result<(PublicValues, Proof, ProgramProvingReport)> { if input.proofs.is_some() { - bail!(CommonError::unsupported_input("no dedicated proofs stream")) + bail!(Error::from(CommonError::unsupported_input( + "no dedicated proofs stream" + ))) } if proof_kind != ProofKind::Compressed { - bail!(CommonError::unsupported_proof_kind( + bail!(Error::from(CommonError::unsupported_proof_kind( proof_kind, [ProofKind::Compressed] - )) + ))) } - let mut server = self.server()?; - let server = server.as_mut().expect("server initialized"); - - let start = Instant::now(); - let (public_values, proof) = server.prove(input.stdin())?; - let proving_time = start.elapsed(); + let (public_values, proof, proving_time) = self.sdk.prove(input.stdin())?; Ok(( public_values, @@ -120,10 +79,10 @@ impl zkVM for EreZisk { fn verify(&self, proof: &Proof) -> anyhow::Result { let Proof::Compressed(proof) = proof else { - bail!(CommonError::unsupported_proof_kind( + bail!(Error::from(CommonError::unsupported_proof_kind( proof.kind(), [ProofKind::Compressed] - )) + ))) }; Ok(self.sdk.verify(proof)?) @@ -155,8 +114,9 @@ mod tests { program::basic::BasicProgram, }; use ere_zkvm_interface::{ + RemoteProverConfig, compiler::Compiler, - zkvm::{Input, ProofKind, ProverResourceType, zkVM}, + zkvm::{Input, ProofKind, ProverResource, zkVM}, }; use std::sync::{Mutex, OnceLock}; @@ -178,7 +138,7 @@ mod tests { #[test] fn test_execute() { let program = basic_program(); - let zkvm = EreZisk::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreZisk::new(program, ProverResource::Cpu).unwrap(); let test_case = BasicProgram::::valid_test_case(); run_zkvm_execute(&zkvm, &test_case); @@ -187,7 +147,7 @@ mod tests { #[test] fn test_execute_invalid_test_case() { let program = basic_program(); - let zkvm = EreZisk::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreZisk::new(program, ProverResource::Cpu).unwrap(); for input in [ Input::new(), @@ -200,7 +160,7 @@ mod tests { #[test] fn test_prove() { let program = basic_program(); - let zkvm = EreZisk::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreZisk::new(program, ProverResource::Cpu).unwrap(); let _guard = PROVE_LOCK.lock().unwrap(); @@ -211,7 +171,7 @@ mod tests { #[test] fn test_prove_invalid_test_case() { let program = basic_program(); - let zkvm = EreZisk::new(program, ProverResourceType::Cpu).unwrap(); + let zkvm = EreZisk::new(program, ProverResource::Cpu).unwrap(); let _guard = PROVE_LOCK.lock().unwrap(); @@ -222,4 +182,23 @@ mod tests { zkvm.prove(&input, ProofKind::default()).unwrap_err(); } } + + #[test] + #[ignore = "Requires ZisK cluster running"] + fn test_cluster_prove() { + let program = basic_program(); + let zkvm = EreZisk::new( + program, + ProverResource::Cluster(RemoteProverConfig { + endpoint: "http://127.0.0.1:50051".to_string(), + ..Default::default() + }), + ) + .unwrap(); + + let _guard = PROVE_LOCK.lock().unwrap(); + + let test_case = BasicProgram::::valid_test_case(); + run_zkvm_prove(&zkvm, &test_case); + } } diff --git a/crates/zkvm/zisk/src/zkvm/cluster_client.rs b/crates/zkvm/zisk/src/zkvm/cluster_client.rs new file mode 100644 index 0000000..ba27897 --- /dev/null +++ b/crates/zkvm/zisk/src/zkvm/cluster_client.rs @@ -0,0 +1,183 @@ +//! Remote ZisK cluster proving. + +use crate::zkvm::Error; +use ere_zkvm_interface::zkvm::RemoteProverConfig; +use futures_util::StreamExt; +use std::sync::OnceLock; +use std::time::Duration; +use tonic::transport::Channel; +use tracing::debug; +use zisk_distributed_grpc_api::{ + ErrorResponse, InputMode, LaunchProofRequest, ProofStatusType, SubscribeToProofRequest, + SystemStatusRequest, launch_proof_response, system_status_response, + zisk_distributed_api_client::ZiskDistributedApiClient, +}; + +/// Wrapper for the ZisK cluster client. +/// +/// Connects to the ZisK cluster via gRPC and submits proof jobs. +pub struct ClusterClient { + client: ZiskDistributedApiClient, +} + +impl ClusterClient { + /// Create a new ClusterClient that connects to the cluster. + pub fn new(config: &RemoteProverConfig) -> Result { + let client = block_on(connect(&config.endpoint))?; + Ok(Self { client }) + } + + /// Sync wrapper for [`Self::prove_async`]. + pub fn prove(&self, input: &[u8]) -> Result<(Vec, Duration), Error> { + block_on(self.prove_async(input)) + } + + /// Send proof request to cluster and wait for completion. + /// + /// Returns the proof and proving time reported by the cluster. + async fn prove_async(&self, input: &[u8]) -> Result<(Vec, Duration), Error> { + let mut client = self.client.clone(); + + // Check system status to get available compute capacity + + debug!("Checking system status..."); + + let status_response = client.system_status(SystemStatusRequest {}).await?; + + let compute_capacity = match status_response.into_inner().result { + Some(system_status_response::Result::Status(status)) => { + debug!( + total_workers = status.total_workers, + compute_capacity = status.compute_capacity, + idle_workers = status.idle_workers, + busy_workers = status.busy_workers, + active_jobs = status.active_jobs, + "System status", + ); + + if status.total_workers == 0 || status.compute_capacity == 0 { + return Err(cluster_error("No worker available in the cluster")); + } + if status.active_jobs != 0 { + return Err(cluster_error("Cluster is busy with another proof job")); + } + + status.compute_capacity + } + Some(system_status_response::Result::Error(res)) => { + return Err(cluster_error_from_response("System status error", res)); + } + None => { + return Err(cluster_error("Received empty system status response")); + } + }; + + // Launch proof + + let data_id = uuid::Uuid::new_v4().to_string(); + + debug!(data_id = data_id, "Launching proof..."); + + let launch_request = LaunchProofRequest { + data_id, + compute_capacity, + input_mode: InputMode::Data.into(), + input_path: None, + input_data: Some(input.to_vec()), + simulated_node: None, + }; + + let launch_response = client.launch_proof(launch_request).await?; + + let job_id = match launch_response.into_inner().result { + Some(launch_proof_response::Result::JobId(job_id)) => { + debug!(job_id = job_id, "Proof launched successfully"); + + job_id + } + Some(launch_proof_response::Result::Error(res)) => { + return Err(cluster_error_from_response("Launch proof error", res)); + } + None => { + return Err(cluster_error("Received empty launch proof response")); + } + }; + + // Subscribe to proof status updates + + debug!(job_id = job_id, "Subscribing to proof status updates..."); + + let stream = client + .subscribe_to_proof(SubscribeToProofRequest { job_id }) + .await?; + + // Wait for proof status update (completion or failure) + + debug!("Waiting for proof status update (completion or failure)..."); + + if let Some(update) = stream.into_inner().next().await { + let update = update.map_err(cluster_error)?; + + match ProofStatusType::try_from(update.status) { + Ok(ProofStatusType::ProofStatusCompleted) => match update.final_proof { + Some(final_proof) => { + let proof = bytemuck::cast_slice(&final_proof.values).to_vec(); + let proving_time = Duration::from_millis(update.duration_ms); + + debug!( + proof_size = proof.len(), + proving_time = ?proving_time, + "Proof generated successfully" + ); + + Ok((proof, proving_time)) + } + None => Err(cluster_error("Missing final proof")), + }, + Ok(ProofStatusType::ProofStatusFailed) => Err(update + .error + .map(|res| cluster_error_from_response("Proof generation error", res)) + .unwrap_or_else(|| cluster_error("Unknown error"))), + Err(err) => Err(cluster_error(err)), + } + } else { + Err(cluster_error("Stream ended without completion status")) + } + } +} + +/// Connect to the ZisK cluster at the given gRPC endpoint. +async fn connect(endpoint: &str) -> Result, Error> { + let channel = Channel::from_shared(endpoint.to_string())? + .connect() + .await?; + Ok(ZiskDistributedApiClient::new(channel)) +} + +/// Run a future to completion, reusing the current tokio runtime or creating one. +fn block_on(future: impl Future) -> T { + match tokio::runtime::Handle::try_current() { + Ok(handle) => tokio::task::block_in_place(|| handle.block_on(future)), + Err(_) => { + static FALLBACK_RT: OnceLock = OnceLock::new(); + FALLBACK_RT + .get_or_init(|| tokio::runtime::Runtime::new().expect("Failed to create runtime")) + .block_on(future) + } + } +} + +/// Returns `Error::ClusterError`. +fn cluster_error(s: impl ToString) -> Error { + Error::ClusterError(s.to_string()) +} + +/// Returns `Error::ClusterError` formatted with error code and message. +fn cluster_error_from_response(s: impl ToString, res: ErrorResponse) -> Error { + Error::ClusterError(format!( + "{}, code: {}, message: {}", + s.to_string(), + res.code, + res.message + )) +} diff --git a/crates/zkvm/zisk/src/zkvm/error.rs b/crates/zkvm/zisk/src/zkvm/error.rs index 394fc2b..995f891 100644 --- a/crates/zkvm/zisk/src/zkvm/error.rs +++ b/crates/zkvm/zisk/src/zkvm/error.rs @@ -1,5 +1,4 @@ use crate::zkvm::sdk::RomDigest; -use bytemuck::PodCastError; use ere_zkvm_interface::zkvm::CommonError; use thiserror::Error; @@ -20,9 +19,6 @@ pub enum Error { RomSetupFailedBefore, // Prove - #[error("Mutex of ZiskServer is poisoned")] - MutexPoisoned, - #[error("Server crashed")] ServerCrashed, @@ -32,15 +28,28 @@ pub enum Error { #[error("Timeout waiting for server ready")] TimeoutWaitingServerReady, - #[error("Uknown server status, stdout: {stdout}")] + #[error("Unknown server status, stdout: {stdout}")] UnknownServerStatus { stdout: String }, - // Verify - #[error("Invalid proof: {0}")] - InvalidProof(String), + // Cluster + #[error("Invalid cluster endpoint: {0}")] + InvalidClusterEndpoint(#[from] http::uri::InvalidUri), - #[error("Cast proof to `u64` slice failed: {0}")] - CastProofBytesToU64s(PodCastError), + #[error("Cluster gRPC error: {0}")] + ClusterGrpcError(#[from] tonic::Status), + + #[error("Failed to connect to cluster: {0}")] + ClusterConnectionFailed(#[from] tonic::transport::Error), + + #[error("Cluster error: {0}")] + ClusterError(String), + + // Verify + #[error("Invalid proof")] + InvalidProof, + + #[error("Invalid proof size {0}, expected a multiple of 8")] + InvalidProofSize(usize), #[error("Invalid public value format")] InvalidPublicValue, diff --git a/crates/zkvm/zisk/src/zkvm/sdk.rs b/crates/zkvm/zisk/src/zkvm/sdk.rs index f6f0cd5..9d697a4 100644 --- a/crates/zkvm/zisk/src/zkvm/sdk.rs +++ b/crates/zkvm/zisk/src/zkvm/sdk.rs @@ -1,162 +1,59 @@ use crate::zkvm::Error; -use ere_zkvm_interface::zkvm::{CommonError, ProverResourceType, PublicValues}; +use crate::zkvm::cluster_client::ClusterClient; +use crate::zkvm::server::{ZiskServer, ZiskServerOptions}; +use ere_zkvm_interface::zkvm::{CommonError, ProverResource, ProverResourceKind, PublicValues}; +use std::borrow::Cow; +use std::path::Path; +use std::process::Command; use std::{ - collections::BTreeMap, env, fs, - io::{BufRead, Write}, - iter, - net::{Ipv4Addr, TcpStream}, - path::{Path, PathBuf}, - process::{Child, Command, Stdio}, + io::BufRead, + mem::transmute, + path::PathBuf, sync::OnceLock, - thread, time::{Duration, Instant}, }; -use strum::{EnumIter, IntoEnumIterator}; use tempfile::tempdir; -use tracing::{error, info}; -use wait_timeout::ChildExt; +use tracing::info; -pub const START_SERVER_TIMEOUT: Duration = Duration::from_secs(120); // 2 mins -pub const SHUTDOWN_SERVER_TIMEOUT: Duration = Duration::from_secs(30); // 30 secs -pub const DEFAULT_PROVE_TIMEOUT: Duration = Duration::from_secs(3600); // 1 hour +/// Verifying key of the aggregation proof. +/// +/// Extracted from `$HOME/.zisk/provingKey/zisk/vadcop_final/vadcop_final.verkey.json`. +pub const VADCOP_FINAL_VK: [u8; 32] = unsafe { + // Use `transmute` to keep the endianness because `zisk_proofman_verifier::verify` will + // use `bytemuck` to cast it to `[u64; 4]`. + transmute([ + 723851053263266420u64, + 2272245643171245153u64, + 9868173762158752255u64, + 6004219199197288727u64, + ]) +}; /// Merkle root of ROM trace generated by `cargo-zisk rom-setup`. pub type RomDigest = [u64; 4]; -/// Options of `cargo-zisk` commands. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, EnumIter)] -pub enum ZiskOption { - Port, - UnlockMappedMemory, // Should be set if locked memory is not enough - MinimalMemory, - // GPU options - Preallocate, // Should be set only if GPU memory is enough - SharedTables, - MaxStreams, - NumberThreadsWitness, - MaxWitnessStored, -} - -impl ZiskOption { - /// The key of the env variable to read from. - fn env_var_key(&self) -> &'static str { - match self { - Self::Port => "ZISK_PORT", - Self::UnlockMappedMemory => "ZISK_UNLOCK_MAPPED_MEMORY", - Self::MinimalMemory => "ZISK_MINIMAL_MEMORY", - Self::Preallocate => "ZISK_PREALLOCATE", - Self::SharedTables => "ZISK_SHARED_TABLES", - Self::MaxStreams => "ZISK_MAX_STREAMS", - Self::NumberThreadsWitness => "ZISK_NUMBER_THREADS_WITNESS", - Self::MaxWitnessStored => "ZISK_MAX_WITNESS_STORED", - } - } - - /// Whether the option is a flag (false-by-default boolean option) or not. - /// - /// When we read the option from env variable, if the option is a flag, - /// we only check if the env variable is set or not. - fn is_flag(&self) -> bool { - match self { - Self::UnlockMappedMemory - | Self::MinimalMemory - | Self::Preallocate - | Self::SharedTables => true, - Self::Port | Self::MaxStreams | Self::NumberThreadsWitness | Self::MaxWitnessStored => { - false - } - } - } - - /// The option key to be appended to `cargo-zisk` command arguments. - fn key(&self) -> &'static str { - match self { - Self::Port => "--port", - Self::UnlockMappedMemory => "--unlock-mapped-memory", - // NOTE: Use snake case for `prove-client` command - // Issue for tracking: https://github.com/eth-act/ere/issues/151. - Self::MinimalMemory => "--minimal_memory", - Self::Preallocate => "--preallocate", - Self::SharedTables => "--shared-tables", - Self::MaxStreams => "--max-streams", - Self::NumberThreadsWitness => "--number-threads-witness", - Self::MaxWitnessStored => "--max-witness-stored", - } - } -} - -#[derive(Clone)] -pub struct ZiskOptions(BTreeMap); - -impl ZiskOptions { - /// Read options from env variables. - pub fn from_env() -> Self { - Self( - ZiskOption::iter() - .flat_map(|option| env::var(option.env_var_key()).ok().map(|val| (option, val))) - .collect(), - ) - } - - /// Returns `cargo-zisk` command arguments by given options that have been - /// set. - fn args(&self, options: impl IntoIterator) -> impl Iterator { - options - .into_iter() - .filter(|option| self.0.contains_key(option)) - .flat_map(|option| { - iter::once(option.key()) - .chain((!option.is_flag()).then(|| self.0[&option].as_str())) - }) - } - - /// Returns `cargo-zisk server` command arguments. - fn server_args(&self) -> impl Iterator { - self.args([ - ZiskOption::Port, - ZiskOption::UnlockMappedMemory, - ZiskOption::Preallocate, - ZiskOption::SharedTables, - ZiskOption::MaxStreams, - ZiskOption::NumberThreadsWitness, - ZiskOption::MaxWitnessStored, - ]) - } - - /// Returns `cargo-zisk prove-client` command arguments. - fn prove_client_args(&self) -> impl Iterator { - self.args([ZiskOption::Port]) - } - - /// Returns `cargo-zisk prove-client prove` command arguments. - fn prove_args(&self) -> impl Iterator { - self.prove_client_args() - .chain(self.args([ZiskOption::MinimalMemory])) - } +pub enum ZiskProver { + Server(ZiskServer), + Cluster(ClusterClient), } pub struct ZiskSdk { elf_path: PathBuf, - cuda: bool, - options: ZiskOptions, - /// ROM digest will be setup only when `ZiskSdk::server` is called. + resource: ProverResource, + /// ROM digest will be setup when `ZiskSdk::prove` or `ZiskSdk::verify` + /// is called, or if env variable `ERE_ZISK_SETUP_ON_INIT` is set. /// /// Use `Option` inside because ROM setup might fail, we can get rid of /// it if `OnceLock::get_or_try_init` is stabilized. rom_digest: OnceLock>, + /// Prover instance, either a local server or a cluster client. + prover: ZiskProver, } impl ZiskSdk { /// Returns SDK for the ELF. - pub fn new( - elf: Vec, - resource: ProverResourceType, - options: ZiskOptions, - ) -> Result { - if matches!(resource, ProverResourceType::Network(_)) { - panic!("Network proving not yet implemented for ZisK. Use CPU or GPU resource type."); - } + pub fn new(elf: Vec, resource: ProverResource) -> Result { // Save ELF to `~/.zisk/cache` along with the ROM binaries, to avoid it // been cleaned up during a long run process. let cache_dir_path = dot_zisk_dir_path().join("cache"); @@ -170,12 +67,39 @@ impl ZiskSdk { fs::write(&elf_path, elf).map_err(|err| CommonError::write_file("elf", &elf_path, err))?; - Ok(Self { + let prover = match &resource { + ProverResource::Cpu | ProverResource::Gpu => ZiskProver::Server(ZiskServer::new( + &elf_path, + resource.is_gpu(), + ZiskServerOptions::from_env(), + )), + ProverResource::Cluster(config) => ZiskProver::Cluster(ClusterClient::new(config)?), + _ => Err(CommonError::unsupported_prover_resource_kind( + resource.kind(), + [ + ProverResourceKind::Cpu, + ProverResourceKind::Gpu, + ProverResourceKind::Cluster, + ], + ))?, + }; + + let sdk = Self { elf_path, - cuda: matches!(resource, ProverResourceType::Gpu), - options, + resource, rom_digest: OnceLock::new(), - }) + prover, + }; + + if env::var_os("ERE_ZISK_SETUP_ON_INIT").is_some() { + sdk.rom_digest()?; + + if let ZiskProver::Server(server) = &sdk.prover { + server.ensure_ready()?; + } + } + + Ok(sdk) } /// Execute the ELF with the given `input`. @@ -233,7 +157,7 @@ impl ZiskSdk { // FIXME: Use `get_or_try_init` when it is stabilized let mut result = Ok(()); let rom_digest = *self.rom_digest.get_or_init(|| { - check_setup(self.cuda) + check_setup(self.resource.is_gpu()) .and_then(|_| rom_setup(&self.elf_path)) .map_err(|err| result = Err(err)) .ok() @@ -242,75 +166,51 @@ impl ZiskSdk { rom_digest.ok_or(Error::RomSetupFailedBefore) } - /// Start a server of the ELF. - pub fn server(&self) -> Result { - // Setup ROM and get ROM digest if it's not done yet. - let rom_digest = self.rom_digest()?; + /// Prove the ELF with the given input. + /// + /// Returns the public values, proof, and proving time. + pub fn prove(&self, input: &[u8]) -> Result<(PublicValues, Vec, Duration), Error> { + let (proof, proving_time) = match &self.prover { + ZiskProver::Cluster(client) => client.prove(input)?, + ZiskProver::Server(server) => { + self.rom_digest()?; - let (cargo_zisk, witness_lib_path) = if self.cuda { - let witness_lib_path = dot_zisk_dir_path() - .join("bin") - .join("libzisk_witness_cuda.so"); - ("cargo-zisk-cuda", Some(witness_lib_path)) - } else { - ("cargo-zisk", None) + let start = Instant::now(); + let proof = server.prove(input)?; + let proving_time = start.elapsed(); + + (proof, proving_time) + } }; - info!("Starting ZisK server..."); - - let mut cmd = Command::new(cargo_zisk); - cmd.arg("server") - .args(self.options.server_args()) - .arg("--elf") - .arg(&self.elf_path) - .arg("--aggregation"); - if let Some(witness_lib_path) = witness_lib_path { - cmd.arg("--witness-lib").arg(witness_lib_path); - } - - let child = cmd.spawn().map_err(|err| CommonError::command(&cmd, err))?; - let server = ZiskServer { - options: self.options.clone(), - rom_digest, - child, - }; - - server.wait_until_ready()?; - - Ok(server) - } - - /// Verify the proof of the ELF, and returns public values. - pub fn verify(&self, proof: &[u8]) -> Result { - let rom_digest = self.rom_digest()?; - - let tempdir = tempdir().map_err(CommonError::tempdir)?; - let proof_path = tempdir.path().join("proof"); - - fs::write(&proof_path, proof) - .map_err(|err| CommonError::write_file("proof", &proof_path, err))?; - - let mut cmd = Command::new("cargo-zisk"); - let output = cmd - .arg("verify") - .arg("--proof") - .arg(&proof_path) - .output() - .map_err(|err| CommonError::command(&cmd, err))?; - - if !output.status.success() { - Err(Error::InvalidProof( - String::from_utf8_lossy(&output.stderr).to_string(), - ))? - } - - let proof = fs::read(&proof_path) - .map_err(|err| CommonError::read_file("proof", &proof_path, err))?; - // Deserialize public values. let (proved_rom_digest, public_values) = deserialize_public_values(&proof)?; // The proved ROM digest should be equal to preprocessed one. + let rom_digest = self.rom_digest()?; + if proved_rom_digest != rom_digest { + return Err(Error::UnexpectedRomDigest { + preprocessed: rom_digest, + proved: proved_rom_digest, + }); + } + + Ok((public_values, proof, proving_time)) + } + + /// Verify the proof of the ELF, and returns public values. + pub fn verify(&self, proof: &[u8]) -> Result { + let proof = align_to_u64(proof)?; + + if !zisk_proofman_verifier::verify(&proof, &VADCOP_FINAL_VK) { + Err(Error::InvalidProof)? + } + + // Deserialize public values. + let (proved_rom_digest, public_values) = deserialize_public_values(&proof)?; + + // The proved ROM digest should be equal to preprocessed one. + let rom_digest = self.rom_digest()?; if proved_rom_digest != rom_digest { return Err(Error::UnexpectedRomDigest { preprocessed: rom_digest, @@ -322,202 +222,6 @@ impl ZiskSdk { } } -/// ZisK server status returned from `cargo-zisk prove-client status`. -#[derive(Debug)] -pub enum ZiskServerStatus { - Idle, - Working, -} - -/// Wrapper for ZisK server child process. -pub struct ZiskServer { - options: ZiskOptions, - rom_digest: RomDigest, - child: Child, -} - -impl Drop for ZiskServer { - fn drop(&mut self) { - info!("Shutting down ZisK server"); - - let mut cmd = Command::new("cargo-zisk"); - let result = cmd - .args(["prove-client", "shutdown"]) - .args(self.options.prove_client_args()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .and_then( - |mut child| match child.wait_timeout(SHUTDOWN_SERVER_TIMEOUT)? { - Some(_) => child.wait_with_output(), - None => { - child.kill().ok(); - Err(std::io::Error::other("shutdown command timed out")) - } - }, - ); - - if result.as_ref().is_ok_and(|output| output.status.success()) { - info!("Shutdown ZisK server"); - } else { - error!( - "Failed to shutdown ZisK server: {}", - result - .map(|output| String::from_utf8_lossy(&output.stderr).to_string()) - .unwrap_or_else(|err| err.to_string()) - ); - error!("Shutdown server child process and asm services manually..."); - let _ = self.child.kill(); - shutdown_asm_service(23115); - shutdown_asm_service(23116); - shutdown_asm_service(23117); - remove_shm_files(); - } - } -} - -impl ZiskServer { - /// Get status of server. - pub fn status(&self, timeout: Duration) -> Result { - let mut cmd = Command::new("cargo-zisk"); - let mut child = cmd - .args(["prove-client", "status"]) - .args(self.options.prove_client_args()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .map_err(|err| CommonError::command(&cmd, err))?; - - if child - .wait_timeout(timeout) - .map_err(|err| CommonError::command(&cmd, err))? - .is_none() - { - // Timeout reached, kill the process - child.kill().ok(); - return Err(Error::TimeoutWaitingServerReady); - } - - let output = child - .wait_with_output() - .map_err(|err| CommonError::command(&cmd, err))?; - - if !output.status.success() { - return Err(CommonError::command_exit_non_zero( - &cmd, - output.status, - Some(&output), - ))?; - } - - let stdout = String::from_utf8_lossy(&output.stdout); - if stdout.contains("idle") { - Ok(ZiskServerStatus::Idle) - } else if stdout.contains("working") { - Ok(ZiskServerStatus::Working) - } else { - Err(Error::UnknownServerStatus { - stdout: stdout.to_string(), - }) - } - } - - /// Send prove request to server and wait for proof to be created. - pub fn prove(&mut self, input: &[u8]) -> Result<(PublicValues, Vec), Error> { - // Prefix that ZisK server will add to the file name of the proof. - // We use constant because the file will be save to a temporary dir, - // so there will be no conflict. - const PREFIX: &str = "ere"; - - let tempdir = tempdir().map_err(CommonError::tempdir)?; - let input_path = tempdir.path().join("input"); - let output_path = tempdir.path().join("output"); - let proof_path = output_path.join(format!("{PREFIX}-vadcop_final_proof.bin")); - - fs::write(&input_path, input) - .map_err(|err| CommonError::write_file("input", &input_path, err))?; - - // NOTE: Use snake case for `prove-client` command - // Issue for tracking: https://github.com/eth-act/ere/issues/151. - let mut cmd = Command::new("cargo-zisk"); - let output = cmd - .args(["prove-client", "prove"]) - .arg("--input") - .arg(input_path) - .arg("--output_dir") - .arg(&output_path) - .args(["-p", PREFIX]) - .args(["--aggregation", "--verify_proofs"]) - .args(self.options.prove_args()) - .output() - .map_err(|err| CommonError::command(&cmd, err))?; - - if !output.status.success() { - return Err(CommonError::command_exit_non_zero( - &cmd, - output.status, - Some(&output), - ))?; - } - - // By default set 1 hour timeout for prove. - let prove_timeout = env::var("ZISK_PROVE_TIMEOUT_SEC") - .ok() - .and_then(|timeout| timeout.parse::().ok()) - .map(Duration::from_secs) - .unwrap_or(DEFAULT_PROVE_TIMEOUT); - - // ZisK server will finish the `prove` requested above then respond the - // following `status`. So if the following `status` succeeds, the proof - // should also be ready. - self.status(prove_timeout).map_err(|err| { - if matches!(err, Error::TimeoutWaitingServerReady) { - Error::TimeoutWaitingServerProving - } else if err.to_string().contains("EOF") { - Error::ServerCrashed - } else { - err - } - })?; - - let proof = fs::read(&proof_path) - .map_err(|err| CommonError::read_file("proof", &proof_path, err))?; - - // Deserialize public values. - let (proved_rom_digest, public_values) = deserialize_public_values(&proof)?; - - // The proved ROM digest should be equal to preprocessed one. - if proved_rom_digest != self.rom_digest { - return Err(Error::UnexpectedRomDigest { - preprocessed: self.rom_digest, - proved: proved_rom_digest, - }); - } - - Ok((public_values, proof)) - } - - /// Wait until the server status to be idle. - fn wait_until_ready(&self) -> Result<(), Error> { - const INTERVAL: Duration = Duration::from_secs(1); - - info!("Waiting until server is ready..."); - - let start = Instant::now(); - while !matches!( - self.status(START_SERVER_TIMEOUT), - Ok(ZiskServerStatus::Idle) - ) { - if start.elapsed() > START_SERVER_TIMEOUT { - return Err(Error::TimeoutWaitingServerReady); - } - thread::sleep(INTERVAL); - } - - Ok(()) - } -} - /// Does global setup if it is not done yet. fn check_setup(cuda: bool) -> Result<(), Error> { info!("Running command `cargo-zisk check-setup --aggregation`..."); @@ -588,42 +292,11 @@ fn rom_setup(elf_path: &Path) -> Result { Ok(rom_digest) } -/// Send shutdown request to ZisK asm services. -fn shutdown_asm_service(port: u16) { - // According to https://github.com/0xPolygonHermez/zisk/blob/v0.15.0/emulator-asm/asm-runner/src/asm_services/mod.rs#L34. - const CMD_SHUTDOWN_REQUEST_ID: u64 = 1000000; - if let Ok(mut stream) = TcpStream::connect((Ipv4Addr::LOCALHOST, port)) { - let _ = stream.write_all( - &[CMD_SHUTDOWN_REQUEST_ID, 0, 0, 0, 0] - .into_iter() - .flat_map(|word| word.to_le_bytes()) - .collect::>(), - ); - } -} - -/// Remove shared memory created by ZisK. -fn remove_shm_files() { - let Ok(shm_dir) = fs::read_dir(Path::new("/dev/shm")) else { - return; - }; - - for entry in shm_dir.flatten() { - let path = entry.path(); - if path - .file_name() - .and_then(|n| n.to_str()) - .is_some_and(|name| name.starts_with("ZISK") || name.starts_with("sem")) - { - let _ = fs::remove_file(&path); - } - } -} - /// Deserialize public values as json string sequence, and parse the `RomDigest` /// and user set public values as `Vec`. fn deserialize_public_values(proof: &[u8]) -> Result<(RomDigest, Vec), Error> { - let proof = bytemuck::try_cast_slice::<_, u64>(proof).map_err(Error::CastProofBytesToU64s)?; + let proof = align_to_u64(proof)?; + let proof = bytemuck::cast_slice::(&proof); // The public values contain at least the the total number of public values, // `RomDigest`, and the number of user set public values. @@ -653,7 +326,23 @@ fn deserialize_public_values(proof: &[u8]) -> Result<(RomDigest, Vec), Error Ok((rom_digest, public_values)) } +/// Returns u64-aligned bytes. +/// +/// Returns an error if `data.len()` is not a multiple of 8. +fn align_to_u64(data: &[u8]) -> Result, Error> { + if !data.len().is_multiple_of(8) { + return Err(Error::InvalidProofSize(data.len())); + } + Ok(if data.as_ptr().cast::().is_aligned() { + Cow::Borrowed(data) + } else { + let mut aligned: Vec = vec![0; data.len() / 8]; + bytemuck::cast_slice_mut(&mut aligned).copy_from_slice(data); + Cow::Owned(bytemuck::cast_slice(&aligned).to_vec()) + }) +} + /// Returns path to `~/.zisk` directory. -fn dot_zisk_dir_path() -> PathBuf { +pub(crate) fn dot_zisk_dir_path() -> PathBuf { PathBuf::from(env::var("HOME").expect("env `$HOME` should be set")).join(".zisk") } diff --git a/crates/zkvm/zisk/src/zkvm/server.rs b/crates/zkvm/zisk/src/zkvm/server.rs new file mode 100644 index 0000000..a212ba7 --- /dev/null +++ b/crates/zkvm/zisk/src/zkvm/server.rs @@ -0,0 +1,461 @@ +//! Local ZisK server management via `cargo-zisk` commands. + +use crate::zkvm::{Error, sdk::dot_zisk_dir_path}; +use ere_zkvm_interface::zkvm::CommonError; +use parking_lot::Mutex; +use std::{ + collections::BTreeMap, + env, fs, + io::Write, + iter, + net::{Ipv4Addr, TcpStream}, + path::{Path, PathBuf}, + process::{Child, Command, Stdio}, + thread, + time::{Duration, Instant}, +}; +use strum::{EnumIter, IntoEnumIterator}; +use tempfile::tempdir; +use tracing::{error, info}; +use wait_timeout::ChildExt; + +pub const DEFAULT_START_SERVER_TIMEOUT_SEC: u64 = 120; // 2 mins +pub const DEFAULT_SHUTDOWN_SERVER_TIMEOUT_SEC: u64 = 30; // 30 secs +pub const DEFAULT_PROVE_TIMEOUT_SEC: u64 = 3600; // 1 hour + +/// ZisK server status returned from `cargo-zisk prove-client status`. +#[derive(Debug)] +pub enum ZiskServerStatus { + Idle, + Working, +} + +/// Options of `cargo-zisk` commands. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, EnumIter)] +pub enum ZiskServerOption { + Port, + UnlockMappedMemory, // Should be set if locked memory is not enough + MinimalMemory, + // GPU options + Preallocate, // Should be set only if GPU memory is enough + SharedTables, + MaxStreams, + NumberThreadsWitness, + MaxWitnessStored, +} + +impl ZiskServerOption { + /// The key of the env variable to read from. + fn env_var_key(&self) -> &'static str { + match self { + Self::Port => "ERE_ZISK_PORT", + Self::UnlockMappedMemory => "ERE_ZISK_UNLOCK_MAPPED_MEMORY", + Self::MinimalMemory => "ERE_ZISK_MINIMAL_MEMORY", + Self::Preallocate => "ERE_ZISK_PREALLOCATE", + Self::SharedTables => "ERE_ZISK_SHARED_TABLES", + Self::MaxStreams => "ERE_ZISK_MAX_STREAMS", + Self::NumberThreadsWitness => "ERE_ZISK_NUMBER_THREADS_WITNESS", + Self::MaxWitnessStored => "ERE_ZISK_MAX_WITNESS_STORED", + } + } + + /// Whether the option is a flag (false-by-default boolean option) or not. + /// + /// When we read the option from env variable, if the option is a flag, + /// we only check if the env variable is set or not. + fn is_flag(&self) -> bool { + match self { + Self::UnlockMappedMemory + | Self::MinimalMemory + | Self::Preallocate + | Self::SharedTables => true, + Self::Port | Self::MaxStreams | Self::NumberThreadsWitness | Self::MaxWitnessStored => { + false + } + } + } + + /// The option key to be appended to `cargo-zisk` command arguments. + fn key(&self) -> &'static str { + match self { + Self::Port => "--port", + Self::UnlockMappedMemory => "--unlock-mapped-memory", + // NOTE: Use snake case for `prove-client` command + // Issue for tracking: https://github.com/eth-act/ere/issues/151. + Self::MinimalMemory => "--minimal_memory", + Self::Preallocate => "--preallocate", + Self::SharedTables => "--shared-tables", + Self::MaxStreams => "--max-streams", + Self::NumberThreadsWitness => "--number-threads-witness", + Self::MaxWitnessStored => "--max-witness-stored", + } + } +} + +/// Configurable options for `cargo-zisk server` and `cargo-zisk prove-client` commands. +#[derive(Clone)] +pub struct ZiskServerOptions(BTreeMap); + +impl ZiskServerOptions { + /// Read options from env variables. + pub fn from_env() -> Self { + Self( + ZiskServerOption::iter() + .flat_map(|option| env::var(option.env_var_key()).ok().map(|val| (option, val))) + .collect(), + ) + } + + /// Returns `cargo-zisk` command arguments by given options that have been + /// set. + fn args( + &self, + options: impl IntoIterator, + ) -> impl Iterator { + options + .into_iter() + .filter(|option| self.0.contains_key(option)) + .flat_map(|option| { + iter::once(option.key()) + .chain((!option.is_flag()).then(|| self.0[&option].as_str())) + }) + } + + /// Returns `cargo-zisk server` command arguments. + pub(crate) fn server_args(&self) -> impl Iterator { + self.args([ + ZiskServerOption::Port, + ZiskServerOption::UnlockMappedMemory, + ZiskServerOption::Preallocate, + ZiskServerOption::SharedTables, + ZiskServerOption::MaxStreams, + ZiskServerOption::NumberThreadsWitness, + ZiskServerOption::MaxWitnessStored, + ]) + } + + /// Returns `cargo-zisk prove-client` command arguments. + pub(crate) fn prove_client_args(&self) -> impl Iterator { + self.args([ZiskServerOption::Port]) + } + + /// Returns `cargo-zisk prove-client prove` command arguments. + pub(crate) fn prove_args(&self) -> impl Iterator { + self.prove_client_args() + .chain(self.args([ZiskServerOption::MinimalMemory])) + } +} + +/// Wrapper for ZisK server child process. +pub struct ZiskServer { + options: ZiskServerOptions, + elf_path: PathBuf, + cuda: bool, + child: Mutex>, +} + +impl Drop for ZiskServer { + fn drop(&mut self) { + self.shutdown(); + } +} + +impl ZiskServer { + /// Create a new ZisK server for the given ELF. + /// + /// The server process is lazily started on the first call to [`prove`](ZiskServer::prove). + pub fn new(elf_path: &Path, cuda: bool, options: ZiskServerOptions) -> Self { + Self { + elf_path: elf_path.to_path_buf(), + cuda, + options, + child: Mutex::new(None), + } + } + + /// Send prove request to server and wait for proof to be created. + /// + /// Returns the proof. + pub fn prove(&self, input: &[u8]) -> Result, Error> { + self.ensure_ready()?; + + // Prefix that ZisK server will add to the file name of the proof. + // We use constant because the file will be save to a temporary dir, + // so there will be no conflict. + const PREFIX: &str = "ere"; + + let tempdir = tempdir().map_err(CommonError::tempdir)?; + let input_path = tempdir.path().join("input"); + let output_path = tempdir.path().join("output"); + let proof_path = output_path.join(format!("{PREFIX}-vadcop_final_proof.bin")); + + fs::write(&input_path, input) + .map_err(|err| CommonError::write_file("input", &input_path, err))?; + + // NOTE: Use snake case for `prove-client` command + // Issue for tracking: https://github.com/eth-act/ere/issues/151. + let mut cmd = Command::new("cargo-zisk"); + let output = cmd + .args(["prove-client", "prove"]) + .arg("--input") + .arg(input_path) + .arg("--output_dir") + .arg(&output_path) + .args(["-p", PREFIX]) + .args(["--aggregation", "--verify_proofs"]) + .args(self.options.prove_args()) + .output() + .map_err(|err| CommonError::command(&cmd, err))?; + + if !output.status.success() { + return Err(CommonError::command_exit_non_zero( + &cmd, + output.status, + Some(&output), + ))?; + } + + // ZisK server will finish the `prove` requested above then respond the + // following `status`. So if the following `status` succeeds, the proof + // should also be ready. + self.status(prove_timeout()).map_err(|err| { + if matches!(err, Error::TimeoutWaitingServerReady) { + Error::TimeoutWaitingServerProving + } else if err.to_string().contains("EOF") { + Error::ServerCrashed + } else { + err + } + })?; + + let proof = fs::read(&proof_path) + .map_err(|err| CommonError::read_file("proof", &proof_path, err))?; + + Ok(proof) + } + + /// Ensure the server is running and responsive, restarting it if needed. + pub fn ensure_ready(&self) -> Result<(), Error> { + if self.child.lock().is_some() && self.status(start_server_timeout()).is_ok() { + return Ok(()); + } + + const MAX_RETRY: usize = 3; + let mut attempt = 0; + + loop { + self.shutdown(); + match self.start() { + Ok(()) => return Ok(()), + Err(Error::TimeoutWaitingServerReady) if attempt < MAX_RETRY => { + error!("Timeout waiting server ready, restarting..."); + attempt += 1; + continue; + } + Err(err) => return Err(err), + } + } + } + + /// Spawn the server process and wait until it's ready. + fn start(&self) -> Result<(), Error> { + info!("Starting ZisK server..."); + + let (cargo_zisk, witness_lib_name) = if self.cuda { + ("cargo-zisk-cuda", "libzisk_witness_cuda.so") + } else { + ("cargo-zisk", "libzisk_witness.so") + }; + let witness_lib_path = dot_zisk_dir_path().join("bin").join(witness_lib_name); + + let mut cmd = Command::new(cargo_zisk); + cmd.arg("server") + .args(self.options.server_args()) + .arg("--elf") + .arg(&self.elf_path) + .arg("--witness-lib") + .arg(witness_lib_path) + .arg("--aggregation"); + + let child = cmd.spawn().map_err(|err| CommonError::command(&cmd, err))?; + + { + let mut guard = self.child.lock(); + *guard = Some(child); + } + + self.wait_until_ready()?; + + Ok(()) + } + + /// Wait until the server status to be idle. + pub fn wait_until_ready(&self) -> Result<(), Error> { + const INTERVAL: Duration = Duration::from_secs(1); + + let timeout = start_server_timeout(); + + info!("Waiting until server is ready..."); + + let start = Instant::now(); + while !matches!(self.status(timeout), Ok(ZiskServerStatus::Idle)) { + if start.elapsed() > timeout { + return Err(Error::TimeoutWaitingServerReady); + } + thread::sleep(INTERVAL); + } + + Ok(()) + } + + /// Gracefully shut down the server, falling back to force-kill on failure. + fn shutdown(&self) { + let mut guard = self.child.lock(); + let Some(mut child) = guard.take() else { + return; + }; + + info!("Shutting down ZisK server"); + + let mut cmd = Command::new("cargo-zisk"); + let result = cmd + .args(["prove-client", "shutdown"]) + .args(self.options.prove_client_args()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .and_then( + |mut child| match child.wait_timeout(shutdown_server_timeout())? { + Some(_) => child.wait_with_output(), + None => { + child.kill().ok(); + Err(std::io::Error::other("shutdown command timed out")) + } + }, + ); + + if result.as_ref().is_ok_and(|output| output.status.success()) { + info!("Shutdown ZisK server"); + } else { + error!( + "Failed to shutdown ZisK server: {}", + result + .map(|output| String::from_utf8_lossy(&output.stderr).to_string()) + .unwrap_or_else(|err| err.to_string()) + ); + error!("Shutdown server child process and asm services manually..."); + let _ = child.kill(); + shutdown_asm_service(23115); + shutdown_asm_service(23116); + shutdown_asm_service(23117); + remove_shm_files(); + } + } + + /// Get status of server. + fn status(&self, timeout: Duration) -> Result { + let mut cmd = Command::new("cargo-zisk"); + let mut child = cmd + .args(["prove-client", "status"]) + .args(self.options.prove_client_args()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(|err| CommonError::command(&cmd, err))?; + + if child + .wait_timeout(timeout) + .map_err(|err| CommonError::command(&cmd, err))? + .is_none() + { + // Timeout reached, kill the process + child.kill().ok(); + return Err(Error::TimeoutWaitingServerReady); + } + + let output = child + .wait_with_output() + .map_err(|err| CommonError::command(&cmd, err))?; + + if !output.status.success() { + return Err(CommonError::command_exit_non_zero( + &cmd, + output.status, + Some(&output), + ))?; + } + + let stdout = String::from_utf8_lossy(&output.stdout); + if stdout.contains("idle") { + Ok(ZiskServerStatus::Idle) + } else if stdout.contains("working") { + Ok(ZiskServerStatus::Working) + } else { + Err(Error::UnknownServerStatus { + stdout: stdout.to_string(), + }) + } + } +} + +/// Send shutdown request to ZisK asm services. +fn shutdown_asm_service(port: u16) { + // According to https://github.com/0xPolygonHermez/zisk/blob/v0.15.0/emulator-asm/asm-runner/src/asm_services/mod.rs#L34. + const CMD_SHUTDOWN_REQUEST_ID: u64 = 1000000; + if let Ok(mut stream) = TcpStream::connect((Ipv4Addr::LOCALHOST, port)) { + let _ = stream.write_all( + &[CMD_SHUTDOWN_REQUEST_ID, 0, 0, 0, 0] + .into_iter() + .flat_map(|word| word.to_le_bytes()) + .collect::>(), + ); + } +} + +/// Remove shared memory created by ZisK. +fn remove_shm_files() { + let Ok(shm_dir) = fs::read_dir(Path::new("/dev/shm")) else { + return; + }; + + for entry in shm_dir.flatten() { + let path = entry.path(); + if path + .file_name() + .and_then(|n| n.to_str()) + .is_some_and(|name| name.starts_with("ZISK") || name.starts_with("sem")) + { + let _ = fs::remove_file(&path); + } + } +} + +/// Returns the server start timeout, configurable via `ERE_ZISK_START_SERVER_TIMEOUT_SEC`. +fn start_server_timeout() -> Duration { + timeout( + "ERE_ZISK_START_SERVER_TIMEOUT_SEC", + DEFAULT_START_SERVER_TIMEOUT_SEC, + ) +} + +/// Returns the server shutdown timeout, configurable via `ERE_ZISK_SHUTDOWN_SERVER_TIMEOUT_SEC`. +fn shutdown_server_timeout() -> Duration { + timeout( + "ERE_ZISK_SHUTDOWN_SERVER_TIMEOUT_SEC", + DEFAULT_SHUTDOWN_SERVER_TIMEOUT_SEC, + ) +} + +/// Returns the prove timeout, configurable via `ERE_ZISK_PROVE_TIMEOUT_SEC`. +fn prove_timeout() -> Duration { + timeout("ERE_ZISK_PROVE_TIMEOUT_SEC", DEFAULT_PROVE_TIMEOUT_SEC) +} + +/// Read a timeout from the given env variable key, falling back to `default`. +fn timeout(key: &str, default: u64) -> Duration { + let sec = env::var(key) + .ok() + .and_then(|timeout| timeout.parse::().ok()) + .unwrap_or(default); + Duration::from_secs(sec) +} diff --git a/docker/zisk/Dockerfile.cluster b/docker/zisk/Dockerfile.cluster new file mode 100644 index 0000000..e550808 --- /dev/null +++ b/docker/zisk/Dockerfile.cluster @@ -0,0 +1,114 @@ +ARG BASE_IMAGE=ubuntu:24.04 +ARG BASE_CUDA_IMAGE=nvidia/cuda:12.9.1-devel-ubuntu24.04 +ARG RUNTIME_IMAGE=ubuntu:24.04 +ARG RUNTIME_CUDA_IMAGE=nvidia/cuda:12.9.1-runtime-ubuntu24.04 + +# Whether to enable CUDA feature or not. +ARG CUDA + +FROM $BASE_IMAGE AS base +FROM $BASE_CUDA_IMAGE AS base_cuda +FROM base${CUDA:+_cuda} AS build_stage + +# Taken from https://0xpolygonhermez.github.io/zisk/getting_started/installation.html +RUN apt-get update && apt-get install -y --no-install-recommends \ + git \ + xz-utils \ + jq \ + curl \ + build-essential \ + qemu-system \ + libomp-dev \ + libgmp-dev \ + nlohmann-json3-dev \ + protobuf-compiler \ + uuid-dev \ + libgrpc++-dev \ + libsecp256k1-dev \ + libsodium-dev \ + libpqxx-dev \ + nasm \ + libopenmpi-dev \ + openmpi-bin \ + openmpi-common \ + libclang-dev \ + clang \ + gcc-riscv64-unknown-elf \ + libprotobuf-dev \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install rustup +ARG RUST_VERSION=1.88.0 + +ENV RUSTUP_HOME=/usr/local/rustup \ + CARGO_HOME=/usr/local/cargo \ + PATH=/usr/local/cargo/bin:$PATH + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain ${RUST_VERSION} --no-modify-path + +# Clone repo +WORKDIR /app + +RUN git clone https://github.com/han0110/zisk.git --depth 1 --branch patch/v0.15.0-cluster /app + +# Whether to enable CUDA feature or not. +ARG CUDA + +# Default to build for RTX 50 series +ARG CUDA_ARCH=sm_120 + +# Build binaries +RUN cargo build --release ${CUDA:+--features gpu} + +FROM $RUNTIME_IMAGE AS runtime +FROM $RUNTIME_CUDA_IMAGE AS runtime_cuda +FROM runtime${CUDA:+_cuda} AS runtime_stage + +# 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 \ + curl \ + build-essential \ + qemu-system \ + libomp-dev \ + libgmp-dev \ + nlohmann-json3-dev \ + protobuf-compiler \ + uuid-dev \ + libgrpc++-dev \ + libsecp256k1-dev \ + libsodium-dev \ + libpqxx-dev \ + nasm \ + libopenmpi-dev \ + openmpi-bin \ + openmpi-common \ + libclang-dev \ + clang \ + gcc-riscv64-unknown-elf \ + libprotobuf-dev \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN curl -sSL https://github.com/fullstorydev/grpcurl/releases/download/v1.9.3/grpcurl_1.9.3_linux_x86_64.tar.gz \ + | tar -xz -C /usr/local/bin grpcurl + +WORKDIR /app + +# Copy script +COPY --from=build_stage /app/ziskup/ziskup /app/ziskup/ziskup + +# Copy binaries (to /usr/local/bin instead of ~/.zisk/bin because we want to mount ~/.zisk later) +COPY --from=build_stage /app/target/release/cargo-zisk /usr/local/bin/cargo-zisk +COPY --from=build_stage /app/target/release/libzisk_witness.so /usr/local/bin/libzisk_witness.so +COPY --from=build_stage /app/target/release/zisk-coordinator /usr/local/bin/zisk-coordinator +COPY --from=build_stage /app/target/release/zisk-worker /usr/local/bin/zisk-worker + +# Copy proto for health check +COPY --from=build_stage /app/distributed/crates/grpc-api/proto/zisk_distributed_api.proto /app/proto/zisk_distributed_api.proto + +# Copy configs +COPY --from=build_stage /app/distributed/crates/coordinator/config/ /app/config/coordinator/ +COPY --from=build_stage /app/distributed/crates/worker/config/ /app/config/worker/ + +CMD ["/bin/bash"] diff --git a/examples/zisk/docker-compose.cluster.yml b/examples/zisk/docker-compose.cluster.yml new file mode 100644 index 0000000..49e0b11 --- /dev/null +++ b/examples/zisk/docker-compose.cluster.yml @@ -0,0 +1,131 @@ +services: + zisk-setup: + image: ere-cluster-zisk:local-cuda + entrypoint: ["/bin/bash", "-c"] + command: + - | + set -e + + if [ ! -d "/root/.zisk/provingKey" ]; then + echo "Install rustup temporarily for ziskup..." + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain none + source "$$HOME/.cargo/env" + + echo "Download proving key..." + SETUP_KEY=proving-no-consttree /app/ziskup/ziskup + + echo "Cleaning up rustup, cargo, toolchains and binaries..." + rustup self uninstall -y + rm -rf /root/.zisk/toolchains + rm /root/.zisk/bin/cargo-zisk + rm /root/.zisk/bin/libzisk_witness.so + rm /root/.zisk/bin/riscv2zisk + rm /root/.zisk/bin/zisk-coordinator + rm /root/.zisk/bin/ziskemu + rm /root/.zisk/bin/ziskup + rm /root/.zisk/bin/zisk-worker + + echo "Generating constant tree files (GPU). This may take a while..." + cargo-zisk check-setup -a + else + echo "Proving key already exists, skipping" + fi + + echo "Running rom-setup..." + cargo-zisk rom-setup -e /app/elf + + echo "Setup complete" + volumes: + - zisk_setup:/root/.zisk + - ${ELF_PATH}:/app/elf:ro + deploy: + resources: + reservations: + devices: + - driver: nvidia + device_ids: ['0'] + capabilities: [gpu] + + zisk-coordinator: + image: ere-cluster-zisk:local-cuda + command: + - "zisk-coordinator" + - "--config" + - "/app/config/coordinator/prod.toml" + ports: + - "50051:50051" + # volumes: + # Uncomment to override config + # - ./coordinator-config:/app/config/coordinator/prod.toml:ro + environment: + - RUST_LOG=info + restart: unless-stopped + healthcheck: + test: ["CMD", "grpcurl", "-plaintext", "-import-path", "/app/proto", "-proto", "zisk_distributed_api.proto", "localhost:50051", "zisk.distributed.api.v1.ZiskDistributedApi/HealthCheck"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + + zisk-worker-0: + image: ere-cluster-zisk:local-cuda + command: + - "zisk-worker" + - "--config" + - "/app/config/worker/prod.toml" + - "--coordinator-url" + - "http://zisk-coordinator:50051" + - "--elf" + - "/app/elf" + - "--witness-lib" + - "/usr/local/bin/libzisk_witness.so" + volumes: + # Mount proving key + - zisk_setup:/root/.zisk:ro + # Mount ELF + - ${ELF_PATH}:/app/elf:ro + # Uncomment to override config + # - ./worker-config:/app/config/worker/prod.toml:ro + environment: + - RUST_LOG=info + restart: unless-stopped + depends_on: + zisk-setup: + condition: service_completed_successfully + zisk-coordinator: + condition: service_healthy + shm_size: 32G + ulimits: + memlock: + soft: -1 + hard: -1 + healthcheck: + test: ["CMD", "pgrep", "-f", "zisk-worker"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + deploy: + resources: + reservations: + devices: + - driver: nvidia + device_ids: ['0'] + capabilities: [gpu] + + # Uncomment to add more workers if more GPUs are available. + # zisk-worker-x: + # extends: zisk-worker-0 + # deploy: + # resources: + # reservations: + # devices: + # - driver: nvidia + # device_ids: ['x'] + # capabilities: [gpu] + +volumes: + zisk_setup: + +networks: + default: