From a26debd5922c724923ca6e84ca4b61368ee27dc0 Mon Sep 17 00:00:00 2001 From: Ricardo Guilherme Schmidt <3esmit@gmail.com> Date: Thu, 2 Jul 2026 02:08:27 -0300 Subject: [PATCH] build: add shared guest program build --- .dockerignore | 4 +++ .github/workflows/ci.yml | 18 +++++++++++ .gitignore | 2 -- CLAUDE.md | 27 ++++++++++++++--- Cargo.lock | 9 ++++++ Cargo.toml | 1 + Makefile | 5 ++- scripts/build-guests.Dockerfile | 44 +++++++++++++++++++++++++++ scripts/build-guests.sh | 34 +++++++++++++++++++++ tools/risc0-packager/Cargo.toml | 12 ++++++++ tools/risc0-packager/src/main.rs | 52 ++++++++++++++++++++++++++++++++ 11 files changed, 201 insertions(+), 7 deletions(-) create mode 100644 .dockerignore create mode 100644 scripts/build-guests.Dockerfile create mode 100755 scripts/build-guests.sh create mode 100644 tools/risc0-packager/Cargo.toml create mode 100644 tools/risc0-packager/src/main.rs diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..76640e2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.git +target +programs/*/methods/guest/target +node_modules diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8473744..935ead6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -125,6 +125,24 @@ jobs: env: RISC0_DEV_MODE: 1 + build-programs: + name: Build Guest Programs + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v5 + + - name: Build guest program binaries + run: make build-programs + + - name: Check guest program binaries + run: | + set -eu + for manifest in programs/*/methods/guest/Cargo.toml; do + program="$(basename "$(dirname "$(dirname "$(dirname "${manifest}")")")")" + test -s "target/guest/${program}.bin" + done + check-idl: name: Check IDL runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index b7e3e68..08c08e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ target/ -token/methods/guest/target/ -amm/methods/guest/target/ *.bin diff --git a/CLAUDE.md b/CLAUDE.md index 56cdc48..c3ea208 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -32,11 +32,27 @@ RISC0_DEV_MODE=1 cargo test -p twap_oracle_program # Format make fmt -# Build the guest ZK binary (requires risc0 toolchain) +# Build all guest ZK binaries +make build-programs + +# Build one guest directly only when debugging a single guest build cargo risczero build --manifest-path programs//methods/guest/Cargo.toml ``` -Built binaries output to: `/methods/guest/target/riscv32im-risc0-zkvm-elf/docker/.bin` +`make build-programs` uses `scripts/build-guests.sh` and +`scripts/build-guests.Dockerfile` to compile every guest crate in one Docker +BuildKit build. Cargo git, registry, and target artifacts are shared through +BuildKit cache mounts. Each raw guest ELF is packaged into the deployable RISC +Zero `.bin` format, and only those final program binaries are exported. + +Built binaries output to: +`target/guest/.bin` + +Use `RISC0_DOCKER_CONTAINER_TAG` to override the guest builder image tag. Use +`RISC0_BUILD_CACHE_ID` to isolate BuildKit caches for clean benchmarks or +parallel experiments. Use `RISC0_DOCKER_BUILD_NETWORK` to opt in to a custom +Docker build network mode, such as `host`, when the default Docker build +network is not sufficient. ## IDL Generation @@ -77,11 +93,14 @@ strip = "symbols" That profile is part of program identity. After every release build, inspect the binary and update every value that depends on the ImageID before submitting transactions: deployed program IDs, client/config files, PDA-derived account addresses, AMM `token_program_id` and `twap_oracle_program_id`, and ATA `token_program_id` inputs. Do not mix raw or old ImageIDs with release-profile binaries. ```bash +# Build all guest binaries with the shared BuildKit cache +make build-programs + # Deploy a program binary to the sequencer -wallet deploy-program +wallet deploy-program target/guest/.bin # Inspect the ProgramId of a built binary -spel inspect +spel inspect target/guest/.bin ``` ## Workspace Structure diff --git a/Cargo.lock b/Cargo.lock index 161af19..b7888ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3133,6 +3133,15 @@ dependencies = [ "serde", ] +[[package]] +name = "risc0-packager" +version = "0.1.0" +dependencies = [ + "anyhow", + "risc0-binfmt", + "risc0-build", +] + [[package]] name = "risc0-zkos-v1compat" version = "2.2.2" diff --git a/Cargo.toml b/Cargo.toml index c48d05a..43959d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ members = [ "programs/stablecoin/methods", "programs/integration_tests", "tools/idl-gen", + "tools/risc0-packager", ] exclude = [ "programs/token/methods/guest", diff --git a/Makefile b/Makefile index 4a691f2..e5e664e 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,7 @@ -.PHONY: clippy clippy-guest clippy-all test integration-test fmt idl +.PHONY: build-programs clippy clippy-guest clippy-all test integration-test fmt idl + +build-programs: + ./scripts/build-guests.sh clippy: RISC0_SKIP_BUILD=1 cargo clippy --workspace --all-targets -- -D warnings diff --git a/scripts/build-guests.Dockerfile b/scripts/build-guests.Dockerfile new file mode 100644 index 0000000..b11641e --- /dev/null +++ b/scripts/build-guests.Dockerfile @@ -0,0 +1,44 @@ +# syntax=docker/dockerfile:1.7 + +ARG RISC0_DOCKER_CONTAINER_TAG=r0.1.88.0 +FROM risczero/risc0-guest-builder:${RISC0_DOCKER_CONTAINER_TAG} AS build +ARG RISC0_BUILD_CACHE_ID=lez-programs-risc0-guests + +WORKDIR /src +COPY . . + +ENV CARGO_TARGET_DIR=/src/target/risc0-guests +ENV RISC0_FEATURE_bigint2="" +ENV CC_riscv32im_risc0_zkvm_elf=/root/.risc0/cpp/bin/riscv32-unknown-elf-gcc +ENV CFLAGS_riscv32im_risc0_zkvm_elf="-march=rv32im -nostdlib" + +RUN --mount=type=cache,id=${RISC0_BUILD_CACHE_ID}-cargo-git,sharing=locked,target=/root/.cargo/git \ + --mount=type=cache,id=${RISC0_BUILD_CACHE_ID}-cargo-registry,sharing=locked,target=/root/.cargo/registry \ + --mount=type=cache,id=${RISC0_BUILD_CACHE_ID}-target,sharing=locked,target=/src/target/risc0-guests <<'EOF' +set -eu + +target_triple="riscv32im-risc0-zkvm-elf" +programs="amm ata stablecoin token twap_oracle" +unit_separator="$(printf '\037')" +guest_rustflags="-C${unit_separator}passes=lower-atomic${unit_separator}-C${unit_separator}link-arg=-Ttext=0x00200800${unit_separator}-C${unit_separator}link-arg=--fatal-warnings${unit_separator}-C${unit_separator}panic=abort${unit_separator}--cfg${unit_separator}getrandom_backend=\"custom\"" +export CARGO_ENCODED_RUSTFLAGS="${guest_rustflags}" + +for program in ${programs}; do + manifest="programs/${program}/methods/guest/Cargo.toml" + echo "==> Building ${program}" + cargo +risc0 build --release --locked --target "${target_triple}" --manifest-path "${manifest}" +done + +mkdir -p /guest-output +unset CARGO_ENCODED_RUSTFLAGS +cargo +risc0 build --locked -p risc0-packager +packager="${CARGO_TARGET_DIR}/debug/risc0-packager" + +for program in ${programs}; do + elf="${CARGO_TARGET_DIR}/${target_triple}/release/${program}" + "${packager}" "${elf}" "/guest-output/${program}.bin" +done +EOF + +FROM scratch AS export +COPY --from=build /guest-output / diff --git a/scripts/build-guests.sh b/scripts/build-guests.sh new file mode 100755 index 0000000..70f6bb6 --- /dev/null +++ b/scripts/build-guests.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +risc0_tag="${RISC0_DOCKER_CONTAINER_TAG:-r0.1.88.0}" +cache_id="${RISC0_BUILD_CACHE_ID:-lez-programs-risc0-guests}" +network_mode="${RISC0_DOCKER_BUILD_NETWORK:-}" +staging_dir="$(mktemp -d)" + +cleanup() { + rm -rf "${staging_dir}" +} +trap cleanup EXIT + +network_args=() +if [[ -n "${network_mode}" ]]; then + network_args=(--network "${network_mode}") +fi + +DOCKER_BUILDKIT=1 docker build ${network_args[@]+"${network_args[@]}"} \ + --build-arg "RISC0_DOCKER_CONTAINER_TAG=${risc0_tag}" \ + --build-arg "RISC0_BUILD_CACHE_ID=${cache_id}" \ + --output="${staging_dir}" \ + -f "${repo_root}/scripts/build-guests.Dockerfile" \ + "${repo_root}" + +out_dir="${repo_root}/target/guest" +rm -rf "${out_dir}" +mkdir -p "${out_dir}" + +for program in amm ata stablecoin token twap_oracle; do + rm -rf "${repo_root}/programs/${program}/methods/guest/target" + install -m 0644 "${staging_dir}/${program}.bin" "${out_dir}/${program}.bin" +done diff --git a/tools/risc0-packager/Cargo.toml b/tools/risc0-packager/Cargo.toml new file mode 100644 index 0000000..e33a362 --- /dev/null +++ b/tools/risc0-packager/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "risc0-packager" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1" +risc0-binfmt = "=3.0.4" +risc0-build = "=3.0.5" + +[lints] +workspace = true diff --git a/tools/risc0-packager/src/main.rs b/tools/risc0-packager/src/main.rs new file mode 100644 index 0000000..0881a0d --- /dev/null +++ b/tools/risc0-packager/src/main.rs @@ -0,0 +1,52 @@ +use std::{env, ffi::OsString, fs, path::PathBuf}; + +use anyhow::{bail, Context, Result}; +use risc0_binfmt::ProgramBinary; + +fn main() -> Result<()> { + let mut args = env::args_os(); + let command = args + .next() + .unwrap_or_else(|| OsString::from("risc0-packager")); + let input = required_arg(&mut args, &command, "guest ELF")?; + let output = required_arg(&mut args, &command, "output .bin")?; + + if args.next().is_some() { + bail!( + "usage: {} ", + command.to_string_lossy() + ); + } + + if let Some(parent) = output + .parent() + .filter(|parent| !parent.as_os_str().is_empty()) + { + fs::create_dir_all(parent) + .with_context(|| format!("failed to create {}", parent.display()))?; + } + + let user_elf = + fs::read(&input).with_context(|| format!("failed to read {}", input.display()))?; + let kernel_elf = risc0_build::GuestOptions::default().kernel(); + let binary = ProgramBinary::new(&user_elf, &kernel_elf); + + fs::write(&output, binary.encode()) + .with_context(|| format!("failed to write {}", output.display()))?; + + Ok(()) +} + +fn required_arg( + args: &mut impl Iterator, + command: &OsString, + name: &str, +) -> Result { + match args.next() { + Some(value) => Ok(PathBuf::from(value)), + None => bail!( + "missing {name}; usage: {} ", + command.to_string_lossy() + ), + } +}