Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions .github/workflows/bench-verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,12 @@ jobs:
scripts/bench_verify.sh "$HEAD_SHA" origin/main "$PAIRS" 2>&1 | tee /tmp/verify_out.txt
sed -n '/=== Verify ABBA result/,$p' /tmp/verify_out.txt > /tmp/verify_result.txt

# Additive: deterministic recursion-guest cycle+accelerator diff (PR vs main).
# The measurement is one exact `execute --cycles` reading per ref (~1 min total, no
# ABBA). Cold wall time is dominated by BUILDS: MEASURE_CLI (release cli) once, plus
# Additive: deterministic recursion-guest cycle+accelerator diff (PR vs main),
# in three regimes: `min` (blowup=2, 1 query — cheap diagnostic floor) plus the
# realistic full-query base-layer points `blowup2` (219 queries) and `blowup4`
# (110 queries). Each measurement is one exact `execute --cycles` reading per ref
# (no ABBA; the full-query executes are ~1-2 min each).
# Cold wall time is dominated by BUILDS: MEASURE_CLI (release cli) once, plus
# per ref a guest build (~10-20 min) and a prover-test build for the blob dump; the
# GUEST_TARGET_DIR / HOST_TARGET_DIR below share build-std and the host cargo target
# across the two ref worktrees so the second ref is much cheaper (and per-worktree
Expand Down Expand Up @@ -122,6 +125,19 @@ jobs:
set -o pipefail
scripts/bench_recursion_cycles.sh "$HEAD_SHA" origin/main min 2>&1 | tee /tmp/recursion_out.txt
sed -n '/=== Recursion-guest cycle/,$p' /tmp/recursion_out.txt > /tmp/recursion_result.txt
# Full-query regimes: the realistic base-layer proofs (the single-query `min`
# regime above is a diagnostic floor, not the real verifier cost). Guest builds
# and worktrees are already cached from the min run, so each adds only a blob
# dump (a full-query prove of the empty inner program, seconds) and one
# ~1-2 min execute per ref. Tolerated failure (else-note): refs predating the
# preset-aware dump test can only measure `min`; the min table above still posts.
for preset in blowup2 blowup4; do
if scripts/bench_recursion_cycles.sh "$HEAD_SHA" origin/main "$preset" 2>&1 | tee "/tmp/recursion_out_${preset}.txt"; then
{ echo; sed -n '/=== Recursion-guest cycle/,$p' "/tmp/recursion_out_${preset}.txt"; } >> /tmp/recursion_result.txt
else
{ echo; echo "_(${preset} full-query regime unavailable for these refs — see the workflow log.)_"; } >> /tmp/recursion_result.txt
fi
done

- name: Post result
if: always()
Expand Down
27 changes: 20 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,18 @@ RECURSION_GUESTS := empty fibonacci
RECURSION_ARTIFACTS := $(addprefix $(RECURSION_ARTIFACTS_DIR)/, $(addsuffix .elf, $(RECURSION_GUESTS)))

# The recursion verifier itself (bench_vs/lambda/recursion) requires picking
# exactly one of its `min`/`blowup8` Cargo features at build time (fixes the
# inner ProofOptions — see main.rs). Each preset builds its own distinctly
# named [[bin]] (recursion-<preset>-bench) to its own artifact, via the
# exactly one of its preset Cargo features at build time (fixes the inner
# ProofOptions — see main.rs). Each preset builds its own distinctly named
# [[bin]] (recursion-<preset>-bench) to its own artifact, via the
# define/foreach/eval below rather than the generic %.elf pattern rule. The
# distinct bin names also make the two `cp`s race-free under `make -j`.
RECURSION_VERIFIER_PRESETS := min blowup8
RECURSION_VERIFIER_ARTIFACTS := $(addprefix $(RECURSION_ARTIFACTS_DIR)/recursion-, $(addsuffix .elf, $(RECURSION_VERIFIER_PRESETS)))
# distinct bin names also make the per-preset `cp`s race-free under `make -j`.
RECURSION_VERIFIER_PRESETS := min blowup2 blowup4 blowup8
# Continuation-verifying variants (the guest's `continuation` feature): verify a
# multi-epoch ContinuationProof bundle instead of a monolithic VmProof. Kept to
# the presets the recursion benchmarks actually measure.
RECURSION_CONT_PRESETS := min blowup2 blowup4
RECURSION_VERIFIER_ARTIFACTS := $(addprefix $(RECURSION_ARTIFACTS_DIR)/recursion-, $(addsuffix .elf, $(RECURSION_VERIFIER_PRESETS))) \
$(addprefix $(RECURSION_ARTIFACTS_DIR)/recursion-cont-, $(addsuffix .elf, $(RECURSION_CONT_PRESETS)))

# Override with: make ... SYSROOT_DIR=$HOME/.lambda-vm-sysroot
# to install the sysroot in a user-writable location and avoid sudo.
Expand Down Expand Up @@ -215,7 +220,7 @@ $(BENCH_ARTIFACTS_DIR)/%.elf: FORCE | prepare-sysroot $(BENCH_ARTIFACTS_DIR)
$(RECURSION_ARTIFACTS_DIR)/%.elf: FORCE | prepare-sysroot $(RECURSION_ARTIFACTS_DIR)
$(call build_guest_elf,$(RECURSION_GUESTS_DIR)/$*,$*-bench)

# The recursion verifier's `min`/`blowup8` presets: same crate dir, one
# The recursion verifier's presets (RECURSION_VERIFIER_PRESETS): same crate dir, one
# differently named [[bin]] per preset (recursion-<preset>-bench, gated on that
# preset's Cargo feature) -> a differently named artifact. Generated per preset
# from RECURSION_VERIFIER_PRESETS via define/foreach/eval rather than a pattern
Expand All @@ -236,6 +241,14 @@ $(RECURSION_ARTIFACTS_DIR)/recursion-$(1).elf: FORCE | prepare-sysroot $(RECURSI
endef
$(foreach preset,$(RECURSION_VERIFIER_PRESETS),$(eval $(call recursion_verifier_rule,$(preset))))

# Continuation variants: same crate, `continuation` feature on top of the preset
# feature -> recursion-cont-<preset>-bench -> recursion-cont-<preset>.elf.
define recursion_cont_verifier_rule
$(RECURSION_ARTIFACTS_DIR)/recursion-cont-$(1).elf: FORCE | prepare-sysroot $(RECURSION_ARTIFACTS_DIR)
$$(call build_guest_elf,$$(RECURSION_GUESTS_DIR)/recursion,recursion-cont-$(1)-bench,--features "continuation $(1)")
endef
$(foreach preset,$(RECURSION_CONT_PRESETS),$(eval $(call recursion_cont_verifier_rule,$(preset))))

clean-asm:
-rm -rf $(ASM_ARTIFACTS_DIR)

Expand Down
37 changes: 34 additions & 3 deletions bench_vs/lambda/recursion/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,57 @@ edition = "2024"
# Exactly one selects the fixed ProofOptions (see main.rs) — hardcoded, not
# private input, so a malicious input can't downgrade the security level.
# Cargo features are additive by design, so the compile_error! mutual-exclusion
# guard in main.rs is the loud failure that stops a mislabeled artifact if both
# guard in main.rs is the loud failure that stops a mislabeled artifact if two
# ever get enabled at once (e.g. under `--all-features`). The crate must stay a
# standalone `[workspace]` (not a root-workspace member) so root-level feature
# unification can never turn both on.
# unification can never turn two on.
min = []
blowup2 = []
blowup4 = []
blowup8 = []
# Orthogonal to the presets: verify a ContinuationProof bundle (multi-epoch,
# memory-bounded inner prove) instead of a monolithic VmProof. Selects the
# `recursion-cont-<preset>-bench` bins below.
continuation = []

# One distinctly named binary per preset (selected by its feature) so a parallel
# `make -j` builds them to different filenames — structurally race-free, no cp
# clobbering. Both use src/main.rs; required-features gates each to its preset.
# clobbering. All use src/main.rs; required-features gates each to its preset.
[[bin]]
name = "recursion-min-bench"
path = "src/main.rs"
required-features = ["min"]

[[bin]]
name = "recursion-blowup2-bench"
path = "src/main.rs"
required-features = ["blowup2"]

[[bin]]
name = "recursion-blowup4-bench"
path = "src/main.rs"
required-features = ["blowup4"]

[[bin]]
name = "recursion-blowup8-bench"
path = "src/main.rs"
required-features = ["blowup8"]

[[bin]]
name = "recursion-cont-min-bench"
path = "src/main.rs"
required-features = ["continuation", "min"]

[[bin]]
name = "recursion-cont-blowup2-bench"
path = "src/main.rs"
required-features = ["continuation", "blowup2"]

[[bin]]
name = "recursion-cont-blowup4-bench"
path = "src/main.rs"
required-features = ["continuation", "blowup4"]

[dependencies]
lambda-vm-prover = { path = "../../../prover", default-features = false, features = [
"profile-markers",
Expand Down
57 changes: 45 additions & 12 deletions bench_vs/lambda/recursion/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,23 @@
//! `recursion::verify_and_attest_blob` — no deserialization pass, no owned
//! `VmProof`.
//!
//! `ProofOptions` is fixed by the `min`/`blowup8` Cargo feature (a `Preset`),
//! not private input — an attacker could otherwise pick trivially weak options
//! and have the guest accept as if a real proof had been checked.
//! The `continuation` feature swaps the monolithic proof for a multi-epoch
//! `ContinuationProof` bundle on the same wire format
//! (`recursion::ContinuationGuestInput`, built by
//! `recursion::encode_continuation_guest_input`), verified via
//! `recursion::verify_continuation_and_attest_blob` — same trust model; the
//! bundle is materialized with one rkyv deserialize pass (zero-copy epoch
//! verify is follow-up work).
//!
//! On success commits `program_id || inner_public_output` via
//! `recursion::verify_and_attest_blob` (a single ELF parse and a single
//! full-ELF Keccak, shared between the statement absorb and the `program_id`
//! fold). The id fold is what the consumer rebinds to a trusted ELF
//! (`check_attestation`); it is not self-enforcing here — the binding is
//! `ProofOptions` is fixed by exactly one preset Cargo feature
//! (`min`/`blowup2`/`blowup4`/`blowup8` — a `Preset`), not private input — an
//! attacker could otherwise pick trivially weak options and have the guest
//! accept as if a real proof had been checked.
//!
//! On success commits `program_id || inner_public_output` (a single ELF parse
//! and a single full-ELF Keccak, shared between the statement absorb and the
//! `program_id` fold). The id fold is what the consumer rebinds to a trusted
//! ELF (`check_attestation`); it is not self-enforcing here — the binding is
//! established by the consumer via `recursion::check_attestation` (a
//! host-side recompute+compare), never in-guest.
//!
Expand All @@ -31,14 +39,30 @@

use lambda_vm_prover::recursion::Preset;

#[cfg(not(any(feature = "min", feature = "blowup8")))]
compile_error!("select exactly one of the `min`/`blowup8` features");
#[cfg(all(feature = "min", feature = "blowup8"))]
compile_error!("select exactly one of the `min`/`blowup8` features");
#[cfg(not(any(
feature = "min",
feature = "blowup2",
feature = "blowup4",
feature = "blowup8"
)))]
compile_error!("select exactly one of the `min`/`blowup2`/`blowup4`/`blowup8` features");
#[cfg(any(
all(feature = "min", feature = "blowup2"),
all(feature = "min", feature = "blowup4"),
all(feature = "min", feature = "blowup8"),
all(feature = "blowup2", feature = "blowup4"),
all(feature = "blowup2", feature = "blowup8"),
all(feature = "blowup4", feature = "blowup8"),
))]
compile_error!("select exactly one of the `min`/`blowup2`/`blowup4`/`blowup8` features");

/// The build preset fixing the inner `ProofOptions` (see the module docs).
#[cfg(feature = "min")]
const PRESET: Preset = Preset::Min;
#[cfg(feature = "blowup2")]
const PRESET: Preset = Preset::Blowup2;
#[cfg(feature = "blowup4")]
const PRESET: Preset = Preset::Blowup4;
#[cfg(feature = "blowup8")]
const PRESET: Preset = Preset::Blowup8;

Expand All @@ -65,9 +89,18 @@ pub fn main() -> ! {
// is what the consumer rebinds to a trusted ELF (`check_attestation`); it is
// not self-enforcing here.
let options = PRESET.options();

#[cfg(not(feature = "continuation"))]
let attestation = lambda_vm_prover::recursion::verify_and_attest_blob(blob, &options)
.expect("verify errored")
.expect("inner proof failed verification");

#[cfg(feature = "continuation")]
let attestation =
lambda_vm_prover::recursion::verify_continuation_and_attest_blob(blob, &options)
.expect("verify errored")
.expect("inner continuation proof failed verification");

lambda_vm_syscalls::syscalls::commit(&attestation);
lambda_vm_syscalls::syscalls::sys_halt();
}
6 changes: 6 additions & 0 deletions executor/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,9 @@
/program_artifacts/rust
/tests/ethrex_hoodi.bin
/tests/ethrex_bench_*.bin
# The small scaling-ladder fixtures ARE committed (scripts/bench_recursion_scaling.sh
# reads them; ~13-30 KB each). Bigger generated fixtures (e.g. _20) stay ignored.
!/tests/ethrex_bench_1.bin
!/tests/ethrex_bench_4.bin
!/tests/ethrex_bench_8.bin
!/tests/ethrex_bench_16.bin
14 changes: 10 additions & 4 deletions executor/src/vm/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,16 @@ pub type U64HashMap<V> = HashMap<u64, V, U64BuildHasher>;
/// The COMMIT AIR concatenates calls via the running `x254` index, so this
/// is enforced as a running-total budget rather than a per-call limit.
pub const MAX_PUBLIC_OUTPUT_TOTAL_SIZE: u64 = 1024 * 1024;
/// Maximum size of the private input memory region (in bytes). 64 MiB so that a
/// whole `VmProof` can be passed as private input to a verifier guest (naive
/// recursion).
pub const MAX_PRIVATE_INPUT_SIZE: u64 = 64 * 1024 * 1024;
/// Maximum size of the private input memory region (in bytes). Sized so that a
/// whole `VmProof` — or a multi-epoch `ContinuationProof` bundle — can be
/// passed as private input to a verifier guest (naive recursion): at
/// production options (blowup=2, 219 FRI queries) real proofs are large —
/// ethrex with 20 transfers proves to ~231 MiB monolithic, and a continuation
/// bundle carries one such proof per epoch — so 512 MiB with the guest ELF and
/// encoding overhead riding along. Nothing else is mapped above
/// `PRIVATE_INPUT_START_INDEX` until the stack at the top of the address
/// space, so growing this region is layout-safe.
pub const MAX_PRIVATE_INPUT_SIZE: u64 = 512 * 1024 * 1024;
/// Fixed high address where private input is mapped. Guest programs can read
/// directly from this address (ZisK-style memory-mapped input).
/// Layout: 4-byte LE length prefix at `PRIVATE_INPUT_START_INDEX`, then data at +4.
Expand Down
Binary file added executor/tests/ethrex_bench_1.bin
Binary file not shown.
Binary file added executor/tests/ethrex_bench_16.bin
Binary file not shown.
Binary file added executor/tests/ethrex_bench_4.bin
Binary file not shown.
Binary file added executor/tests/ethrex_bench_8.bin
Binary file not shown.
2 changes: 1 addition & 1 deletion executor/tests/flamegraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,7 @@ fn test_run_with_flamegraph_returns_generator_on_executor_new_failure() {
// even on failure.
let elf_bytes = std::fs::read("./program_artifacts/rust/add.elf").unwrap();
let program = executor::elf::Elf::load(&elf_bytes).unwrap();
let oversized_input = vec![0u8; 64 * 1024 * 1024 + 1];
let oversized_input = vec![0u8; executor::vm::memory::MAX_PRIVATE_INPUT_SIZE as usize + 1];

let (generator, result) = executor::flamegraph::run_with_flamegraph(
&elf_bytes,
Expand Down
Loading
Loading