From 84e31e00db5dc77cf88b33d7847f9462b42f1c85 Mon Sep 17 00:00:00 2001 From: Mario Rugiero Date: Thu, 16 Jul 2026 18:36:23 -0300 Subject: [PATCH 1/5] bench(recursion): measure the verifier at real query counts, over real blocks The recursion cycle benchmark only measured MIN_PROOF_OPTIONS (blowup=2, 1 FRI query) verifying a proof of the empty program - a diagnostic floor, not the production verifier cost (blowup=2 at 128-bit is 219 queries, and real inner proofs are multi-epoch continuation bundles of real blocks). Presets and guests: - Preset::Blowup2 (219 q) / Blowup4 (110 q) - the realistic base-layer regimes - alongside the existing Min/Blowup8; one guest ELF per preset. - bench_vs/lambda/recursion's `continuation` guest feature: verify a whole ContinuationProof bundle in-VM (recursion-cont-.elf), built on the continuation supplied-roots verify path and the zero-copy continuation guest API from the prior two PRs. Benchmark plumbing: - test_dump_recursion_input gains env knobs: RECURSION_DUMP_PRESET, RECURSION_DUMP_INNER_ELF/INPUT (any inner program, e.g. ethrex blocks), RECURSION_DUMP_EPOCH_LOG2 (continuation mode). - scripts/bench_recursion_cycles.sh: preset-aware dump, --release test invocations, per-preset regime labels; /bench-verify now posts min + blowup2 + blowup4 tables. - scripts/bench_recursion_scaling.sh: block-size x preset ladder over the committed ethrex fixtures (1/4/8/16 transfers). - A real-block detailed profile test (blowup=4, 4 transfers) plus a `make test-profile-recursion-block` target, since cycle counts across every preset/block size are already covered by the scripts above. --- .github/workflows/bench-verify.yml | 22 +- Makefile | 32 ++- bench_vs/lambda/recursion/Cargo.toml | 37 +++- bench_vs/lambda/recursion/src/main.rs | 57 +++-- executor/.gitignore | 6 + executor/tests/ethrex_bench_1.bin | Bin 0 -> 13341 bytes executor/tests/ethrex_bench_16.bin | Bin 0 -> 29082 bytes executor/tests/ethrex_bench_4.bin | Bin 0 -> 17371 bytes executor/tests/ethrex_bench_8.bin | Bin 0 -> 21410 bytes prover/src/recursion.rs | 120 ++++++----- prover/src/tests/recursion_smoke_test.rs | 252 ++++++++++++++++++++++- scripts/bench_recursion_cycles.sh | 60 ++++-- scripts/bench_recursion_scaling.sh | 112 ++++++++++ 13 files changed, 594 insertions(+), 104 deletions(-) create mode 100644 executor/tests/ethrex_bench_1.bin create mode 100644 executor/tests/ethrex_bench_16.bin create mode 100644 executor/tests/ethrex_bench_4.bin create mode 100644 executor/tests/ethrex_bench_8.bin create mode 100755 scripts/bench_recursion_scaling.sh diff --git a/.github/workflows/bench-verify.yml b/.github/workflows/bench-verify.yml index baf550bac..e79022082 100644 --- a/.github/workflows/bench-verify.yml +++ b/.github/workflows/bench-verify.yml @@ -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 @@ -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() diff --git a/Makefile b/Makefile index 12ac291f5..911edeb17 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ compile-programs compile-recursion-elfs clean-asm clean-rust clean-bench clean-shared \ clean-recursion-elfs clean test test-asm \ test-rust test-ethrex test-executor test-syscalls test-flamegraph flamegraph-prover test-profile-recursion test-profile-recursion-single test-profile-recursion-multi \ +test-profile-recursion-block \ test-fast test-prover test-prover-all test-prover-debug test-disk-spill test-math-cuda test-cuda-integration test-cuda-fallback \ test-prover-cuda test-prover-comprehensive-cuda \ bench-math-cuda bench-prover bench-prover-cuda build check clippy fmt lint regen-ethrex-fixtures \ @@ -56,13 +57,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--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--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. @@ -215,7 +221,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--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 @@ -236,6 +242,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--bench -> recursion-cont-.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) @@ -278,6 +292,10 @@ test-profile-recursion-single: compile-recursion-elfs test-profile-recursion-multi: compile-recursion-elfs cargo test --package lambda-vm-prover --lib test_recursion_profile_multiquery -- --ignored --nocapture +# Real-block profile (ethrex, blowup=4/4 transfers), via the `continuation` guest. +test-profile-recursion-block: compile-recursion-elfs $(RUST_ARTIFACTS_DIR)/ethrex.elf + cargo test --package lambda-vm-prover --lib --release test_recursion_profile_blowup4_block -- --ignored --nocapture + # Regenerate the committed ethrex block fixtures (see tooling/ethrex-fixtures). # Run after bumping the ethrex rev; README checksums are refreshed automatically. regen-ethrex-fixtures: diff --git a/bench_vs/lambda/recursion/Cargo.toml b/bench_vs/lambda/recursion/Cargo.toml index 473e3107c..f612949a8 100644 --- a/bench_vs/lambda/recursion/Cargo.toml +++ b/bench_vs/lambda/recursion/Cargo.toml @@ -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--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", diff --git a/bench_vs/lambda/recursion/src/main.rs b/bench_vs/lambda/recursion/src/main.rs index 33a061364..9e190ad14 100644 --- a/bench_vs/lambda/recursion/src/main.rs +++ b/bench_vs/lambda/recursion/src/main.rs @@ -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. //! @@ -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; @@ -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(); } diff --git a/executor/.gitignore b/executor/.gitignore index fa48867ab..3ed575591 100644 --- a/executor/.gitignore +++ b/executor/.gitignore @@ -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 diff --git a/executor/tests/ethrex_bench_1.bin b/executor/tests/ethrex_bench_1.bin new file mode 100644 index 0000000000000000000000000000000000000000..6a9c94380a13bb2d863e56b2e1cb3e75af75cb01 GIT binary patch literal 13341 zcmeHO3tUWF+h1$X{aUG%O65>VD7w0xN|aQ(m@bT(-A$1s5<+IW=zlziT_Lx~e0wCv=kU(FZ~c7E?|b*}H+w&8?Pvd=XRZIUp1ta9WOqE#q z&7+RqS6+CW?P_kV;HQ)OzQev~I}P_<{#osWC7coSW*=VoX4Cs1203F?+@x#gs({N}z{!L7Qj(us+Ko!~H9>ZHG#yZu_7l zlls2vLP|j42#s9RyIyID)>%m@iYpzCJAT3Z>YDG1ZXW~!q4wy{?SsQV3%bj&QT6Ti zQ3kbfMpX@D&=JlYP7j--&y__jojrhxfJfx>STgA3PxZnI$l^Gls+^Z>p`x&2Dsd{$ zk)zN5bkY(0$bh-f01ln%%+W`o8IC7~iS8J_8Sb;hCw`60(Z@~-6^TEFtaL`DI4thE zZ=3UNS;if9IioNPK_Q>R=7dSfqQ1^N$PY&%!Vx}xGYa8mK5FB{<|p#;33hgN2B?t? z8trT#yiQNJ``lzb$zCKDb_EI@Q5=hl;t&)Mb$O0h6-WQm0aexKJL;mSB6gJ#2bD2I ztvQ*(D{zj0ja`ExEN&Q%i%LqN&CYhHB%8zL@h~65ox^yTk`RxFS|dCb2ElVcWv~*2 zEKE{`d=!D-Y4Eo(FgVPCk4j?NCQ5uBYNW(NNpLm~)xiX!P#B2`1figc33#C(j|n)T zAc+ZxP#|LhgtKQ09dB`BZ-(3i%*P~W%mqhxn2B|k9S0LRSgY7#ks#|1FFV_D*eJw$ zAS?_@xx-dxJ3Ed$eBi`}D8k3FIT#$)t=33yc>O4pYQ^V0_21+j{mgr+koVNDc^4vm z%{%^ky!Y~N@)!T;C;o%J%ReH8-pBYy`uNxU#Q!H;9vbvr{)OE1MFI}8_zpsL9)4kG z(r0$e|08ym^|1qc*@^CDCr-!?{EMBS&+Ht1FA%t6ktczrn%UTKE_Qs2<(!!x1%hS}3 zk1|CiXORkp!sPk?dxP8va_3bpH)6hzffP4WRA)r@Dh$$#n3P%pM0PM&G-9Tx+hIC( zPxC+kAX1rB0vXZ}1Q19RDng@B$dH0iC=3P;p_AwcnTp5MAel+00UCoz!BHguAW<1) z3Ka(>-%f*Ek^W|`JYVU6_&SU%s(hcKr8G-wQD|9*R4Y3!A#l+Lk4=pz5`x`!jImBQ zj2(r2@h5gP@9mTy?4igY`$-z$ygUBsV9w)Cr}yzbLmqb8Rs}h&C`Xd(oSR4%B_3Wff&c+gs(zz)b z`zaA?j_>}{f%Ggg{=xa~367-^a~~k-fP_$IIgRvZ}pGqtr(ew?E!7 z-Ef&WR=hv>PiK$0TuP)K8GCKntxI=CZ+C1~3qMos+FC!s#lsH*RNwt>yPBbFx!HbP zcF4&#zs{F+JFcuIm3CdG86GT~4+ddL?gy;)Rzr}Idd32^KagKZS&U3~+A%q9Oq8{j z88_p_95vFTN<*e-Pw(_iPV4)Ge`)gTlSBfON~IulB8`rxLmZXBq~i$$gd_pz*f5}y zDL8~cCX)~{iAZ76NmM3Xf=OghByb25kBB)UUWHsXQQNi@^$#8r+UfAJ)-n24^XBxH z&bG=lkJ_$7qmk9v2>2cI>K`~FewC5=bNm2`wV?{*ADkZq#)L;ca5^1PR3u|ta7udW z>Ct`x*8VFwqGMHz5itO5>BoqGKud#V^^@jW59zX6 z5X=h`PENesm~QlSrMW#F)p_>lsO>ZjSLCTprFTHN+_F7? zw`v|VrKkC2&zoDxEp1`9fGm$P*%cM{+-5;w8`GBa{Gc3TPtcGT61P6eYCf^Zxw*%- zi1+$bOZKTv()|c6I*Ep5P%^|OAxs*D%%IRoWCjyYg%S*s1cO4r5hUo?p2!S zR4Nq@NkkG3B2>w@v$^pl$yZiiB*`AJ%8(Q$anX~JvKn)^{>|{o388EAZkOXq?t!DT zE5bA;t?s^oca6+ig7{;pcM1me^;(*v5c|j5rO)%0A8LrfgJ`w~LIaqu$}_JTwq~U9 zK7}2cj*YSY&ekMMxir){4gHPiO(-`=c~ZRWVg7Peb$H zGb^fJMaj>WX1@lGbMlff|2&+^XP4?mXrgw`%j?qIc=+PTYH&7m#m28Of51u2AABkhNBKIfH zF}$q#9Cvl$Si&4o7LhXiuxX1KrJ+s0RTBf_R|y;-R~mk<#<0WTd8{HQ%By4bsu{UX&CGZc>WbTpjaxlt zg2Qd2d@hzFD!TP1w95n07SR~}7!eR0FW$2%Hdl(wDMRt&n6)-#!Q%w>AG}Jwf2{Aol*c9uh%^S=jPPW|dyW+NchvoQTZQ@|;L!MSsukWiM z2+Y#5Srcr!cg%}+TykthxXh`C$(@M17#P1wq7>~1Fxc$NZ1(O-TecCkIz9Ntl)|YC z+}hYiw&lijuUHP2|7zAQrSu_W_$#TFI2JZ`ceH_ogd zp+%w*X*dFzLdP)y6W}GNbU>lei8wq12M_{Df=s92C2#~h1waWRLIw<~1VX}*0F?nC zLGrB)H-_L>?l#5~dwv>#r>CW#7q0exzU^mXN85jyiNpvva3iMrbxxj3ZP@GQMur8` z{G|)k1}R^v_Tl%u){Tz4I9m)1(ZTXlG{jk5Ft~X4B{@0oS?Yq6b=~BwlHGacn!65O zxMF+6lQpfVlK_F&0a|$M@R_sM=?ZhPu`^^c6rQvwv9x@TMM)TQnmDj~WF5Ev-v6x$a0g~zTiKAdc$ ze|y@&g|lQ9u1Y^x6ds*4M8h9~cNTl`i7z!SRCd&NB~4@;@-#`-pQ^sE{n4hn-!AG* zs}aMB=l}*ryl&aJXRpf5S~b-D%{A#WhS)>}8)=^S0Ky-}-cD5?7%N_O7g}#MPH)qN@}}mHCuDUKZJ=5GTfig&1q1ps?ekMhUvP+jPv>y}!qlJfT@+gSyzZ zxii~>ZIq;a-$0Ny|G~3As=J~FTV>m1t$lm!(b*W|nOY9dqeIWyugu(`+Kj~hT@hYtmK4lgQlx1T;!=f#d7XUIvhaXQ&5OoYh3d4E ziSZ4l+XuoyF))Dspizy-3?kp#8^5t?+PsxDcYn#qxrd4ktW!e0t0We#5eFmR??bt^ zrs}r*jIiVpODEU}U}FL_}QQ2`0Z4Tp_~!z;6F$MXnq_~pA&S{ zWYf_Fb6gD>Gj!_CH)gCN`4m*0lc5J%-4dfMU^&sCTsmgxE?d0>HJVdq#u1C{Eu`f; z#y*WSI-XJE-3CE@Lemp1&);O`RxU~^qC`n5&5KahYm=XDP;9p@Y3n>2F&@l;a>MD) zg0@xLs#FZ_nv~4GKEqhPdvETBmurU2XM2oGDHp?2k%^@rp#{PlMSkg0t0Wh=M^8IO zgSzidR+3gJZxnIxbI*E;#wO=F+2Hsi5bV8XA6)U|&nUMN?I*h!PjLLYMOhY*tCw_<%%ke*%T_(CRd zmYTFZzDj**Sn0zf4oXwA1Ykv%NGl{pXz}KeIfpjtFrvzjLjjo(6_hn`Rz}$1v26!b zc4D7yH+JFIoZ$NDj5hu-F*-2)#JS_$npOTEXdc(3BdAm4hd?m$m{sCq@2zdp>&N9k zFLNrBna<5GIu(1hWT!4ZS*6Md%zG!&kMb9fG_PKNJkO?Pnp1%8PCb2BBh#KLw8lJS zD>-HUh|%KT5YJud{owpjoj`!c(x=yr-f~}T_Wfg|ul!Yp{ZjRx315)@e)*SEUxdHw z{JrLjazBMH$bC5QY3KM)G7;Mg|3Blk!1_~}g6)NM-^}=+>}%eC54^9r{{t;ddaeKf literal 0 HcmV?d00001 diff --git a/executor/tests/ethrex_bench_16.bin b/executor/tests/ethrex_bench_16.bin new file mode 100644 index 0000000000000000000000000000000000000000..d466f9aba9ffa1bda3210b8fc262e639d9b5e51f GIT binary patch literal 29082 zcmeHQ3tUXw_uuEt%$b=p=gg#sP{=Diq@yPPmNKTeJd=f=r3J4`7;rQ7Uc`phhf^Pr0~aXGTQ>hlV&ZreE%A9K z-)*IHA^I&U?oXiNQlTpT(2gph$n6$j$uGi^K;*&n~K z@x#Q+NQv7)m5?@v*n0f|1joqVjwb8Qd zYBD<{%yp#ug#D9y_xvE==h)D`6TXX~b73b`#803is8Gptpy~AqRc6r z_vX}k-&52!K;)AedRJZNJ|6ZyresFVD8yQ;rD$oZ@q2g2$rgdGvR3ET1=$=>KPIXh zK3KOifbW)cLdE$BR2(W)GvBrIYDz~6Hmz8SZk~RifUyLwp@io@$iNyYBI>2_td zyd*y(U17W5wWwFLBiDPo%y-*ftfv;~n6WLQDSb)I+w~iiN5-^k{C9zTRoDp?{u8Kh zDpV_4+EFd(NRg}eVrB$8YcL|3N9id4ZfMw0*VwcNStL%DJRa6pYX6ZTyGsL3yStB0 zH<;&YgV_%c;l*v4oi_AEA-+fi-lND=@3t9%&& zVvh9MLz%bsNa?uusk=r_sLh}BhqXm*hSoIk)gP*wK~e*L_K$UGysO*y$qb8;<6(C9 z{mN6{@95d<$3VWi(+L&pCs47dP@QXTN0r-=B5HaAH1C_r-JP%|-aqty&)Q1|m-UX+ zZCR%%)2eeS{8-hsjfr7W$L+}er@c~JtdeT>#g-nrLASgqa<<>YU~^TGuloMui%zJR zKY@x#h3dtdc2rL~QiOY}VS#lKZh;;>z0+NGjV7ibNUO0kjMaUy*vdii)`OZX-7yz6 z3m+szrzB|k*KYA$Ho9rV$)rP;FY1%ljM}LDV<2DM>4XaV2~-#rD(Mb1;vFeCx67;N z16xsApd*K$ztmSvAssy$q7yUupGH>o*pw!sdyJhW$tLuQ{8C0w8VV<2DM>4b{@ z6R7A^sFpUhqq6Ntkxh^GEfMov=C*jh#|Sz0h0V2(c`KLVN>+np%&+HdKO$bB?XJVw zbS;LDWv&C9l`RH6%{UCbh0@OU%S7WZR;>A|??1ligo^ePsAyEEVqdkR+SHMP)RrD{ zp-D~twueYx+U;5QMB@}sWuOk0ySbGlpK(s||MR1F$OFMW}B+O8_{e$F=Hfy!5X|M5j9 zRLD=DLa0#HH@2gC*pY&#vVW8}%?rjzqC9eFPMx-tnumvE#4W#synW-x9GLlJ2x9zb z_s)rUf@xK9ib>v$fll|q`Zt)Oq>^0DM%{HbKL+yEoldC0PoM%+sCspv5$Q+~4fcTY zy@~3tu!liUn%4&&9^|K?;@#_ZIxTQ)QN*?8q>;$=hh-~l1vXm-C7c>J`NLh^2lr&& z4>yr5BS$YEHGbV!egE+VGidBlo^F183lm?EIc&*{Yh@ZCk!h{jyQeTu?F)Ikpl`9> zqdNujSEu-8*c>Uo(x*&xs#HYa3bAL~r@WtZW%IP})hr#+_r)Pi!!v~wQg*kF?`wUp zP;Ps?c&?w{_QuyORcj6`(_egXGckraEzUgjRj{b@wfsd|<)^YkLt4F`th{ro-sI(- zy2L^J45PV=SbJu;9d@3?S~$AUzOVJpSE))vbI9+t`M$eUnH1C8dhz>}vIy`fyY38%Oi$_oQN$q~;Jamglz>=uqj@1_^M$aZ_WWfYCyZK0h(ZnDPL89XwtlTz^nzV zUz6663&XywnXd|Owh>}KuCmBf(1UO6x$avy-(HJbATLb?h&L)g!uaNV?;a8YTT?G2 z%m?-LLBr_b0t5*U6X=?xq30y?7XH3t^)RX-*I8P%{k$^wBD{MH!0KwMp*%TH|k25k;7hu;yP#-83ZPk~4e z!Cg}WfrvTZ+{+90(Z|%=3lg=}=OxfZy^LWMUWNj3c!eOwFeihA2~gxafWMW2p5BIG z0ue|(Njl6+Fh<%-z(kC^1S4SvYs<*N45KY012gotOfQ(BwPi$L25rl*U@7G8c*o5*Ek>Qv<%a0D&Xm?Tz%XK&ng)4EPqv zBNKB(fQHe``LH@Tth#8sc>PF}%7o{8>`(Gt(82fEHonJx$@jLJzT`Xodwg%_|D-?p zFZjs+72oAQ+CxwS`Hz0$zx+r3f2`L_u;RP?Z{y7;NuVLdVTNtodHk6><2$&c{g=4o z{E0hAJ9h%wxf9aH9pq2$tmxp*5xDiYfRe|9h7BW_n!)r9d_v8HrnYS`ZASTt-wNip z*NIefYQ(qSXuD2yt{6~VVf?gSuh8{&%KWU!;fx20Ipa0a(FU;z(T42PiBu0~N`frm zAGP1>;eVkF@$_XqyX@)dYi*;}!!wZzQb0R_sEY6S*g`-#z@F4KCQ?m@+u)qG5Cwq5 zaSS6_fX-%;TrP)>@&JQi(HVe_pZ-op`2H8}WZVXu_to9_hCVXt@u-5u-xyq2t%Qy-uI+w{t1F98N#K z!NJ%K)3z~5gsFb};QZi|a-9!^pa6M+q1k;85Wq#P@Z*O2Y7iX{#4vGQ>lZ~SsHk*a8Q8pWD1xhdpl!eow1|cji z!2ukoCp4S~023!!Y@CLOywO5Dsa3-DY>^>qYbcjp)Doj8J*&r(wPzZ7RGEitTDIif z!i~2D=xV3}8n9~zJ5*v1Otru5>Q~p@F>KvZ2RLg%f7+8@`^=;z)Ta02XZnT4ATKDI zBrfxcx7bF_JVD(GnBEl266EZoxbfo39=guX&r4?j{VOwkCr>Z+s_Y?tE?;NI@D_zLP@I%9|INvC~ zH*D*iyF+|VU7BBYZS35I_6QKDTHJp)L|vjla~~~vbxyVYv&QoHi))zqFE4P^_n)x_ zqLgSsOzf#?g4wBui`b6nRFn7#Axi6Y6*WB*Ui?0U`J_mlOWo71s06_S2!~DZ*fb^$ z@K`j2!@y8BhlU^s#>N2~BLJ6!bD^eSY%T|3GkGi&5Cj(oJUZI#)5Wq5-RRco0wkIb z-=ytDFGeJLdaUAQKIj$d~lnsNz>z_vHlw2K_P8{LS@#^_O`j z^^ZduMyy=DVk8(}oMovNZKe=&CjbF-#j(Y72McHZl74G_^bewnA>(ocZYu|?yclV9 z(T+$)z$?B2_ra8#DF?4ecu#nGHFvS+#>OG59CAMhGy?2IP@x{N7cM!nX=cbP?!EbL zBQFdcpJVS~RFWLGLoNA;Tj8*f%hb~H%TR!G566^=@1D2?Z<$*_ON)NJ>RPO?MDI%( zyLJvYf4*~SZg=#w3%WtWQ3Bzz5iX7pET{(zlFniQ46dqh9KjHH14BtR6F~?%fzf#^ z9uuPj4$7l(5gg7I|BaQ@e`pm1j#EC2{~5HPbXcZ(%2@qe>m*CVT`5;0S8TUh-0S2h z-T?C&hmxSnBfCTJsq5&1Y9Qcv%{*U4HH%91@KABD=Tftricf7l9Pj;Vb#}C}v1)x9 zb@K?2J}YfeDd1ls9U+25~{QDuZ)(qSq)Yw|8_xv(p zr93QUeZ+DExXc}{HOTCGp^b8`Hg0`$kJgT@tt%v4=2bNtq7ep%B)X@kDI14x-rv7! zmFj!mJkPancB=G_D|nXc_#)Rq?6mIOeQ+DTL_vGjguyMZPCijTxGhOJMfy;~!vRnG zjgnm@y^p)+!Z-wsUIUC2&SsRH-iF90Y_+q!)c{Cd->b@v?B{uICWDOfQwX1?=FZI8-)*8ltrU+XiT_a<#BP0 zK{5#@9p|ulG@QTymxnSDE{l#aQ3S_XOa@$?(=nXJVKC?-Z;U)cbE7VCd^m2iNOYgPM3>(Yl1l+hAzOC|Eby3{H zvX=*jp=)5b+jigo!mS3iV*i&YfK=JqKBFI<6$MH@eveE}`etW~>t~!0n{slPy_2qk zE1z0zh1hCz!-#c3vB9xVL0_GerK11=eTuW*G-Vhk>zs~lV5&$4_jHP(rOsj(FYh*ksO19g0%Bc$+WUq}>9Yk2TRB7LqfhmU ztegr&Qf1Ahz#YoP2B;BBGmQy#y8S$ux#EJ#fd`w$2Dvm5=V#PeX~vXD4qt?Txmgc3 z?$R1x+_zpya;Nq*)uRRk7jIx8X2QZpUc$*G!zgy90?9!`p~%-AcJ zS`{q<#~)r|FB5fQcB@N~5Uqe0SrJGZL!`A1s)K z0R0WsDbM#ylDk&)e!;u(K|;Q6T1v$({cNwM6P3v)Hi~sYv{-Bs&K4Y$APAC%VFcXI za#4&;hoLbXfYacXmB(W-NR+|hut;bS#aJ{R!sW2J0M0G{Td9B6M$K9-#JY_mczyGSf-+yL)?W<37ZO8$oQ$Qth-6n!Cn@C|pBjng>W<7LB^w8v(@wHzX?!%e%N==557b5uJIp#uMu= z45~AF=Av1>MOr=#G*PW3KqS>31pyI^GXL50b}P%q)(%rWy{So0uEIK!by#Fhgw(;t zRf6*fV6smyx||Tww9aPLhk%n}zcU?Gx4sXtG?bC67OZ3+g?q=fVEdcey)ozAMCL~q zK2ACnwO|jpcbq@|L!(i3-{Kp-f2(p6Ob%3O76QE*a5iI+Y=X%mxpb)8B$G$788ilu zfS+6v#s7U0ByH)?UDiRavHU} zOXUyR_8Tc}m1Us!cJb>Y8=9pKif^*qiHY4ZN&GSv&|gIOx*%GBPT(XBIsxF0gNAY0IE%rda~aT0 z08oJA9E`=G1BAdZxbVX0a4!N#0y>%*&=Q0(x_w#o{}pDjhObfeDr=9)@eIelhU|un zv9HnKS`KkmZI)-_8+6$*Y6S#Z|C*=&FCw(mV~PO&Q?DTl7c5S--Vl^LjJ6aq$hM>%c9Gzp5%G|g4lne6$D7;`(BQy)LKPN@lR>Qu^A=P(@gGiLnq;@Zjr%T7S03*s}*d{7oh!> zuTA1ypzdjRP;@~fpq-OPz-Uz_j3{D~IDyijrH6o?ah$~>a2RXEW3t&?7^laiLrW-& z2MgF71~ibu;8p~>!n-*F>95Qdna6jzt-jo&Y-08HE2~8B#l4@vIXZXh@r!|j?kZkF zW9JJo!XPqC=Jn0;ryEvJKBRsp(0+thPh)JtjD5DNZdo+U7(Jj`m^${NPcPwD_ZBi* zHRFCsg_;wK(QJwq*osG}R^f`6zLpcZb+y=(O{&N*11bF%04iCI$? zFL)@qv0$g#abx+i9ge`6$~f9B-ei!UQeLOHjb)*@_wMw{sm{KVwIbYGg%SM?(mkVE z(?T@6pd0_xP{v6fo;%{P)~$I>bQ{a9$y=(Al(AR)>WnFmxgc`!`pro&CWm$2B75os z=>==Df@VoYCfxQ~?9epOt@omNLrliE3@RcdB z8?jvS;&AWy%x=K~;@8^gt0}nXty=fw<*vncvXQF|J^7sBdIRTr^l=&UWS$VM0GGgi zV?2~}vku;Hjb##ZC0C1@J@oo_#{r4wz7gqkh zHgAUJ)-1i8>A!`>y{~_^(|JbO}8*tv`bMi5`l}Q<-q?%F*c2 zBUJTmd-+p0-z^lN-%jbX2?72OOq%SBdQ2|ZG}HBb%cNshC)!)5pNgxPxKfB#fYc@K z{VPj7jP@NJ^mP2{tliut*1DYBH&x}*PkwuSIcKymb%1~k6TJdkdWfNn(AdZU!@>

a8|vtSXrVkD`ZC!#k3}*8fZW1P|vhc!0)% z@nLj$9ED9{zrc*nH0f9|6Gp+&}8+0WqXY%EhbK7oT~8o8@(mnzd3 z>{K*r5|DV3wl^iSzG|+!#?xPBJiuGc(AcHT!|$9M~Ueq`|l1R!Y*))C;j2)fSHb3d;k5(0m@vq{s54*?5cW2Qqx3lhjj- zWS#BG`1Za$Y6u8W>ofAWezv_&;3|!SQF^MkwD#N061Q{Pwm;h^AhP#R2Lv=v+fCox zIP_dm!?l-@~>pm-XbtGYf^UMB9&hOoHuYei>`14{ZRoUzHTb@r?7 z6C#8`N{GcS$ZGg7G*=M*cCym&-M?+ft>a8f2IYa(W;3f@^rXdXUn3yS`ceHKmtL;# zsgta?)8oz2CwUt*W-1y!4_KQw+chy>rVFA)pg4&l2+D*Z&?JfAOcs+45BAbYCXEiD z#|fU+!gw&Qm`)?1>z@s91Yy!)cw1Xk908+=f03AI1SAz~y4KHWhlcUls?AFNvCHmV zkTt42>S&SkdRb{7q@@f2cG9;kuDtPT5L3uP&)j=*)hLYE1qM22HqXo+qT#Rz3TF7+ zldcE7ej7I~lK<4ZMBV?7Q6YWTi)WYwuAhH6`l>O+{(aNMpQY3kU|@F`_$70&Ml-t# zr|hw`X!hvPMzMwJ){m9mZt>&HP*h7i@q)S^LQnwh{gkEm11qWq zs*i3KOS$ztOzxuQ9AWC%zpzJduRK5R__c?Ej9MQ|)e)^7C-?AT#XA|LTiFUR*M+HL zbB(>V=xuR>134}zw|OgGPZKir6Sv)z|bxzH5O0!$wC7{Is-Iupiq!)E|AfYL}vK%mfZ_6tpa zgZrB7H5(ea#9;M0{ee4dRo;;z_mfn%&dvjtvy~fk9t$(hhv|pnkR$6vl{V&D&d$1U z=jg%6xo%YwS}Cp~^1%&{)eta@KhY>LsnmPgZ)TN-hl=B?a&s#N+_Uv`N6x;|S^l6- zm{$57TZ#0Kn*VfA^J`wv%F@y@bb!`<9o4=5uiSI0OcdbJgs6(rDA5^p)fbxsj2y{J zkLGA@(O-0C=Mm>VJ~mTS9hVEy3Z-NUp&`I$?V{W;gE9b4Vgc#(SDhAIRw_9hEP6F% zL=Iw(XPKvh)X_E9yzmf0Gwb?$_3F*e(mD`$4#xSDRvTB{PColp_;hwa}L zjF6X!KAhkkHF){Bl;y{qeYfa|GQye`8V}Ga@)D*WVAZr!y_YJ{m{=WBj|)1c(zZU=AISGKNHUK1g%Yojv_JM&=uATfbRIRgIh zNy#{tz>~Ip;J~hac=PS_-jy+9lGSIi(us=ID_3a?(MmT6-`^T_&qfiHVDn(e4%}~( zfPu2<92g49gYODrG2zyN2VXOU!Q;y)lgouSY4B}aTmlB^V;lte&zv@cs|?zu3e^AZ z)Q45x1;_2VZhCvoQZ9)4T|XmszAz*#@}FV!zo(UMdBavWs0Q_jb>*qs9UTOUni4M$ zUJS*=#{2Cb97OEjKhRDfabnqWkJr)Rezv3x0vhYLzjjN|oOrQzpB`>D#ZUdXn7#JY z?5aVMK~g;>he4w?IV<(5yY5P}GySc$d+AS_{Gl1U?iR#t*0M|8=eNXF838vmSkJ8+ zlq8?C%EyJeznuTR&PzMB$$7m`QCeoqBxzx69@J>T!OK10md2ej9d_ooV9mJ^CfbW? zLYqcZZ{OOw-gsrst5XHN7v(&ZTXb9WLdNKa6Ig}2)>Y0Z2Sc#_hV$cp#rDRW z8T0s}Mt|DGAz4A5fht*RkBNt_pZt4ezbjS?Bnl^3AYgLO;JB5Uk?+l;oD);x1-B*Zip-+Ue7jb|G5shvjjN&*U(9ap8!a}f9}Uz_k0Y9&6uRRB->}}3C75Ys~B(L Q&orX(^e4Fg2ZhG>g#Z8m literal 0 HcmV?d00001 diff --git a/executor/tests/ethrex_bench_4.bin b/executor/tests/ethrex_bench_4.bin new file mode 100644 index 0000000000000000000000000000000000000000..45fe930380e9f02aedaa1aeb681926704d058346 GIT binary patch literal 17371 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 IlE0YyFQDcqaR2}S literal 0 HcmV?d00001 diff --git a/executor/tests/ethrex_bench_8.bin b/executor/tests/ethrex_bench_8.bin new file mode 100644 index 0000000000000000000000000000000000000000..b5790e9492ee904614ed07b23ff6ca560846206d GIT binary patch literal 21410 zcmeHP2|QI>+h1$%bM_hcImRfiG$1sZ8(c|IhDfHU&QX*|R2r0Ij3`tj6dI%(sg#f* zDJc{wO-i9?(nOl`Ev0*VQ{Op#+`jw1zt`_)uV=6Q>}Re2dY)&kXAS#c&G>yBeDy~w ziy!;x_Wtt8Qiz~ie)aG#vhDz)Y4ER%?^3%uIyyQZ#CN5TbMxu!dF4z;Ec?_RAH~%M zgbF{S+0`=M^u+>ach#HsnsZHcE*O;EOWKx}G;!UdtwD>`+DDv7IpXxan==go7Nq`cXPU-qW%ObsvuPDA3mdc z(v^akhGLZ^M_kq}J3UZ#1T|sdE9F?;s)}*z#_+C-iFv;|7rI1}cX>!aJL9p-k^s_R z%ruMLTE$qOO81<8X~UT2jPGLTp4kl*41fi1eLL=Rk!p-;77mE8W_Fiys&4?k4 z^IIN0;4brI4s#wP?{KYn=P`*=Lmv~?=Bsf$B4;gNEqiCt$DTzsns*eILHMnycf=A@3yZgJuB-`g%E_9C z58Sp-&nx;q&DLBr>hgyq-xy=*A#_;hv~};{HNywfyjkDH&^@yoD(ojvVS-S3z59%6 zVOI)PGK#Vc`lTffT>ij+ph6%0@Rb^w_MsEEO?fT7D5b_Ux8$*riP7o|p4jDgGfL8bS?a^f>**2u6*%aRx=y4Z>ye3qoh7Bp$Xu5X( zcQJI&?1l>b1S%j1)!8?nQ5AKiP>(U7W`DBwOQI?K;hT`Kql1F=Gyc*W~@$8Qy8_v=b$tx5~dnt!a~a=%MrlVqa9yu_dFobX}% zkKDmZ z8Txmf61JG`iHVb0X_;gFr_8V3`92xc-qFzsc<7h0X;2rTz7)kv-~N4+;G5a<<+UXE zgKc@d4-Py{KS}-++c}_rhd&YJZzaJW_bIyd{Lg2-`a2Ce0wk9 z2-|G=FaYDjD4qi^Kva_NV(Sm10&>MVc&Bv|$_&CO=Ky`m>l*QBhlqpa2Wd>QnX4v#OP%{S#G zcfNsJ0|)dDAG7ic!2S3lqWs&o7JLy0o`b(Xs$-ySfInjDl;_Vk#r&<12!Bhy1iFLJ z3Q_VfijTqX0RGkn`UF@;@kJ2#c-bg_zK*Ovp9ZJ<^G70r=p=?9g4#*QBZAUNNFxI8 zBt#H_brN($KsbGRXT|Gn95z9}&8Utswx|l$jxZTDmIV)yJcPNv;N2brGNe>{d;{^|ClJh8R;MUqJQO2`u`-?pYQcu{del-izVRDD$24` zI}iTU&e$&P82+EMv-FF0;AicGe%4Mzr*`09+VSer&M_29IU>vBqCjyvO6^hl8ud`d zXzG%P(l;0{`CG&M_C8tAm>T`<8=d#bf&Mpv&5XKQ`Q)5K>^+OF&05{kk||Xo-saSTcfeq{@ zSYooE;V==6X>-tcPB1x4Djl*g3{YtdCdOhg=#YUi7$k{fY#JM*GbxA-(m8AvV38aK z&g7yM1|~^oFmWjIdLr}_Sl{H8XUZ)x7t0}eer}W>Tm{DW;1_X#Gmey&KD9+GM?Sm!rfY_JynIEVYMSXGcCyn^i4je6KEW#$) z>=+j@EW}jRz%S{=39QURs!h{KUL~$B6atFs=n9Iv<5J#2Ip09@!$lBdGCH=#D|@MXz>aV=Mh7Wi#!RaoI(^pUR@>MDW>+2 zUTJKB+nVW>sR_HaQjd9;4UM=cM0*GvvVM^aaJEV3lEgmUt;~0JrFh1+S1Yc@2TAs= z%G$eUxWn^3lZtwy9zF04oQbg^hXFZE$fhGdppq0i9S|gk!(>7Nq6Y*02d)7@e)G&ARV~MQ?M;yq{?BEmdQ_a; zeso8`%T;;X)U7n1WC$)E0@*XNHpjy%E&?y8BgG?r!Mn%y%g<#?=DwQJgdb(d^ zQS61)=V+2gOp;Jk(kpR^8|r*JWA?8%6RkUC|L~8XqEUi@u?ZF$9U#u6a@Z6q6{B$h z8_h46bOw%5>2w-Kr=hrtO=EJ{Tn<4ZgczqYt`cgh@p9Gq@e13P@ZFd9^?hd9ddWKU z#_dgs_0Jm16XslMJ2Vtq-8E+X8%{L@JNADP2avhs+fVKO88I*{F!;XBiJ-hZ3G=K% z@d+n}x_OwoujC1KTOp>5^XWyYSS}wKT&rES##vL=Sx2b z)R$-Omt)To602}P5#=j+DWoAzxpS+LwcpmWv7X*Opzd+q6r4^OKyr}0&` zvy^6GEoSA;9;G4&6dhzhoxorNG>d20#zlJEdcJ|?bzbApz0IS;m$$ReO?m8W5LYQR zd@clbx%W2iojA7QwY=Jgi@#jeq3Rem_UJ-4`Pjl~9m~swCXWEzhovwGN2Ycp} zNUkWmH+w1s=Ia~No*$GV_j>hx!M*WOQu(n_+V#EWdH(H%^{Is$#d}~{Y#Iyopmc~< zcQ`Bton)|Sbdp11LM}<;k_;+N<+9P0Lt-or0WgxuWKtlFpy3c>{!Jr>JL-CMk)aDe zVt@+t%8y**Q@#*-5UlOq-~fi+nTC}Ee1_;f+E=Z7*hOxntaGk~*~7~T$;*rFV%L%X zn40zm+JA_Q9{9(+9=a)&;StQMm)&uD`{_W2Wub0yZ(#Yy z%mn`fObh5Wt0)&%a^%ZTK*QgY%c@?6NYA@T#q?oxO2J|~aaSP}20SkJQ;V#)G_ zm|NNGYVVzO+81W0)}9Q!zhL&qhzu7nPhj5&W_S)ZiI-4zIDED4&B9gLY+oaGXt;hj zkSySjU9dpLdf`ss1yRJ z(}Aa|wVzr(50l}AI91L3t>{i+y2FVQ`OCRmM%iSWLx9JPxw5!SxjIN|gzB=aKr^G^ zOEJl`4Q+Tt@W8y7I3a>g2#yVjTjg}n&caIEFVAU&fqPk)?E`~H#}W^Yd#RBq@nK01 zdM%PlV{y=`8%dzWX|%{jk{E|hgDf_O;4ruph*2<{pkq`Pi+~u11JUjcl|v~77XDNXYOa38Am`jwsyNh$X%Z?r9ru@vve%y0I-@t8U5zf-9HH6lTqWxZhUAYNds z+zX{ZaC}PTxLTHhrhqNSfD113=q0r*FWr?s5nNAjE@;GbdyD8 zjf?JL<5t(}Vh^*K?}U+Yt@Zf3YT0LRPQH6~?tE}#c2kY4?{lxS+9cKfQMnQ|7fXks1EGo&S5EzHa<}z7SF2Gr6 zQIvwtU@%Cc*EiCBevg^PB3L+;&S2vlzyTC4lMNUwHi1(}9AH$mBhF?}xHy%<0FX;y zbU-rE3N=mxOcFq9uh;)?*$4#J)>pNsW!hZ|Xn(Gwoi)*2JXb+XuA*vw)Q5KU(1>%> z|9vb~n3XrJb9YC`{3CN=11mQP0V|SK2&i!zzPf-WIWwR zbEnKDy}=&kqs9ZthZ+0Ra-KBU`RKL$W7-3}HHeJ&d^5c6tWMCn#G1tD$63F3Y~nFQ zwuc?vu~uL(SaV)*ggm>1_zL=st9pEvcK8@Xml$~5LX#gF7B zT@RTS$5fM@>dc+LaRozwK&#)#eDgfFz_1m1hhxn&Z%#buI#t5eJMmy%U}#KVC3gtk z8118MX;nH~{`6{F%oy^}-0`uR6BPG1J=|C`@Z89W)k2gau$zQH$N#Nl#FLk$1`SGz z?uN?j$$ia2mg^`Ve-8p5gx!o+1Z%&1TJodFT8Mf8CyTQU*OZjy6YADSCDdFkKVubI z*-xcp{^>_cgB$xr2r=mvV!r_7KK-av#NYH*f7tMSzppQP%rZ&^HDQhR#*NF(WW`-x zL69)-{*zr*Z6SS3QqA_Pd7b^Rc)gyns^#-g-{NU2lXuAXz_i#Hlf)pzXlUP$Bq5VV zr%@;vmqOBT3Sa|(jwlgaw3$Z1Nwm((089vJ6c&R+qK!i~+Cuw>Yy?7(Qo8x7oX2iG zt1}H-hOLWVeCNF4^!n^Yjs>q4SNDVOED!Ew6orhPZN3r*UflG7V z80RVIxz9xgvB~bl%ESJ@j~NrgYYC{-Ubkyh#BU)ISvFxIvyN`NVui5($eO<73+4a= z1un7(E(J&x5UuvE*FEw^ys7^Ys|9<(pV z*7?fs)*%x-?}lA(7^toGMm+82^Qa*g46KEbad3`kU;pCZfE~U^!l!rKn`9#PXv~nN z3)kPv54)MC5_e4)8C#m&=84^suvntKFmz#?)0zsqJ(P6V* z4@`bOup*cw3!TSBp3Fhp-V}ml0WK5OkIhDV;%q8~gn&!3(5XWfz|et1g5(kaqHP-v zNm2pr9{>P?=k@iOQwt3yZkjr^d5c2fxiSsDRShe*qpy#wlVwhHrCsfJ7qIk3tM|Yo&@sHZV07i0jz%@9mHpdeuTRN6 zU|0R(*2`Q)R>79YTK(<4Q9&VO08KHKTiIH{@22U!GO63N)yh7-Xo&rxd@a*BU*}4$ ztB)`;j=H@s9ic40?PyX!?BFG1(v}=w8no3+j2hKG$7;aDa(`iDfE7k3rB|L-lFu(* zIk&%iXzBLgVF?9qqUCOD=(!o{Gz%kR&nsT2$Dq`F@58sA)ORdXUmY!BI^V>ID7hCh zNSrTH1HrDqw5;PvTv?ZU?u^E!+P9PY*2n3jYEPwSvahdNw!%<|QhLGo{^mo65MwjA zToy#L2@+5-28Bi9kc&S5q0>;{#6@2Y5$HrBM&oeML!2PdH$v#V3BiJp?DaV0Z&~3H ztO78UhgV(4*4>`e)DTdb@5b>q+i#zCUM%=piTJs)h^Uxfqv(I1Qh<%X$W3so{}PU# z^t!!KL7-|N*>?CsBq2UF_~77h_Q8V#UHOuQipl>yw?Yyps%cFFZ;& zW7V%WF zLrZ?&U|<(*V>q`tvVBD3&g~r`R?7pBorbh@2(CvDOpD7w zs~iM6_lc2Q4wp*B0hIs*6|I1vT^n?+gh|q<6gv8uEy!fi=^Qk(!f+-F$GPYWE^6}8r8 z`bjF?k~FaMza-%{t$^pYQ)WHmq~I3*H{t-w9|vDe^Ex(u z2TItNFO11!gowz_43g7ml%AxOZ?QIJ>r6ASRM59C5XjY@c$U@Zy{)pp*4^<%)2~d{ zlYYA|BfQmTz&wXJqvA@zGl4P$_G&%w4RGz0fP==M`ivkBMRZ#EU;CAB?{umKaCFY+ z@=o?QaOzm>cQAQXy_w+_xofl1uDTrjAX{}x|4>IGD7!8|29UfhfCB>E+qGhz8GFdp zMM_=)oJ5aR6`=Kn5r0bR{`#Mf{v^8dbkF&dxrfA`lE27-Ezi_G(FD47{)na1 z0@F|Toj*O;nfLAeH2bxmo}-y1V4{CLlgv-|lIY6$MD@;PKz!DC&4qb^+Y6~9byrZ| Rq8}Qd3;O=1TghMG{txSn0v`YX literal 0 HcmV?d00001 diff --git a/prover/src/recursion.rs b/prover/src/recursion.rs index 654b58fa4..2ac9fcfc0 100644 --- a/prover/src/recursion.rs +++ b/prover/src/recursion.rs @@ -20,9 +20,9 @@ //! //! [`program_id`] deliberately does not fold the `ProofOptions`: the security //! level is pinned by which verifier guest the outer proof is checked against -//! (`recursion-min.elf` vs `recursion-blowup8.elf`, fixed at build time — see -//! [`Preset`]). A consumer must pin that outer ELF too, or a 1-query `min` -//! attestation is indistinguishable from a 128-bit `blowup8` one. +//! (`recursion-min.elf` vs `recursion-blowup2.elf`/`recursion-blowup8.elf`, +//! fixed at build time — see [`Preset`]). A consumer must pin that outer ELF +//! too, or a 1-query `min` attestation is indistinguishable from a 128-bit one. use crypto::hash::platform_keccak::PlatformKeccak256 as Keccak256; use digest::Digest; @@ -52,15 +52,37 @@ pub const MIN_PROOF_OPTIONS: ProofOptions = ProofOptions { pub enum Preset { /// Blowup=2, 1 query ([`MIN_PROOF_OPTIONS`]) — insecure, diagnostics only. Min, - /// Blowup=8, multi-query — 128-bit security. + /// Blowup=2, full 128-bit query count (219 queries at 20 grinding bits) — + /// the realistic base-layer shape: production pipelines prove the base + /// proof at low blowup (2/4) and reserve high blowup for the final wrap. + Blowup2, + /// Blowup=4, 110 queries — the other realistic base-layer point (e.g. + /// Zisk's compressor layer): 2× the prover LDE of blowup=2 for half the + /// queries to verify. + Blowup4, + /// Blowup=8, multi-query (73 queries) — 128-bit security at final-wrap-style + /// parameters: more prover work per row, far fewer queries to verify. Blowup8, } impl Preset { + /// Every preset, for name→preset lookups (e.g. the blob-dump test's + /// `RECURSION_DUMP_PRESET`). Keep in sync with the enum. + pub const ALL: [Preset; 4] = [ + Preset::Min, + Preset::Blowup2, + Preset::Blowup4, + Preset::Blowup8, + ]; + /// The fixed `ProofOptions` this preset's guest verifies with. pub fn options(&self) -> ProofOptions { match self { Preset::Min => MIN_PROOF_OPTIONS, + Preset::Blowup2 => crate::GoldilocksCubicProofOptions::with_blowup(2) + .expect("blowup=2 is always valid"), + Preset::Blowup4 => crate::GoldilocksCubicProofOptions::with_blowup(4) + .expect("blowup=4 is always valid"), Preset::Blowup8 => crate::GoldilocksCubicProofOptions::with_blowup(8) .expect("blowup=8 is always valid"), } @@ -71,6 +93,8 @@ impl Preset { pub fn artifact_stem(&self) -> &'static str { match self { Preset::Min => "recursion-min", + Preset::Blowup2 => "recursion-blowup2", + Preset::Blowup4 => "recursion-blowup4", Preset::Blowup8 => "recursion-blowup8", } } @@ -79,6 +103,8 @@ impl Preset { pub fn name(&self) -> &'static str { match self { Preset::Min => "min", + Preset::Blowup2 => "blowup2", + Preset::Blowup4 => "blowup4", Preset::Blowup8 => "blowup8", } } @@ -127,6 +153,49 @@ pub fn encode_guest_input( }) } +/// The continuation guest's private-input layout (the `continuation` guest +/// feature). Mirrors [`crate::GuestInput`] with the monolithic proof replaced +/// by the bundle and the PAGE roots replaced by the global-memory genesis +/// roots (see [`crate::continuation::continuation_precomputed_commitments`]). +/// Rkyv-archived on the same magic-prefixed wire format as the monolithic +/// blob ([`crate::encode_recursion_input`]); the guest is feature-pinned to +/// one layout, and a blob of the other kind fails the bytecheck validation. +#[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +pub struct ContinuationGuestInput { + pub bundle: crate::continuation::ContinuationProof, + pub inner_elf: Vec, + pub decode_commitment: Commitment, + pub page_commitments: Vec<(u64, Commitment)>, +} + +/// Build the continuation guest's private-input blob for `bundle` of +/// `inner_elf`: precomputes the roots and rkyv-encodes a +/// [`ContinuationGuestInput`] behind the standard aligning prefix. Takes the +/// bundle by value (it is large; the encoder is its last consumer). +pub fn encode_continuation_guest_input( + bundle: crate::continuation::ContinuationProof, + inner_elf: &[u8], + opts: &ProofOptions, +) -> Result, Error> { + let (decode_commitment, page_commitments) = + crate::continuation::continuation_precomputed_commitments(inner_elf, &bundle, opts)?; + let input = ContinuationGuestInput { + bundle, + inner_elf: inner_elf.to_vec(), + decode_commitment, + page_commitments, + }; + let archive = rkyv::to_bytes::(&input) + .map_err(|e| Error::Execution(format!("rkyv encode failed: {e}")))?; + let mut blob = Vec::with_capacity(crate::RECURSION_INPUT_PREFIX_LEN + archive.len()); + blob.extend_from_slice(&crate::RECURSION_INPUT_MAGIC); + blob.extend_from_slice(&crate::RECURSION_INPUT_VERSION.to_le_bytes()); + blob.extend_from_slice(&[0u8; 4]); // reserved + debug_assert_eq!(blob.len(), crate::RECURSION_INPUT_PREFIX_LEN); + blob.extend_from_slice(&archive); + Ok(blob) +} + /// Domain tag for [`program_id`]. const PROGRAM_ID_TAG: &[u8] = b"LAMBDAVM_PROGRAM_ID_V1"; @@ -223,49 +292,6 @@ pub fn verify_and_attest_blob( Ok(Some(attestation)) } -/// The continuation guest's private-input layout (the `continuation` guest -/// feature). Mirrors [`crate::GuestInput`] with the monolithic proof replaced -/// by the bundle and the PAGE roots replaced by the global-memory genesis -/// roots (see [`crate::continuation::continuation_precomputed_commitments`]). -/// Rkyv-archived on the same magic-prefixed wire format as the monolithic -/// blob ([`crate::encode_recursion_input`]); the guest is feature-pinned to -/// one layout, and a blob of the other kind fails the bytecheck validation. -#[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] -pub struct ContinuationGuestInput { - pub bundle: crate::continuation::ContinuationProof, - pub inner_elf: Vec, - pub decode_commitment: Commitment, - pub page_commitments: Vec<(u64, Commitment)>, -} - -/// Build the continuation guest's private-input blob for `bundle` of -/// `inner_elf`: precomputes the roots and rkyv-encodes a -/// [`ContinuationGuestInput`] behind the standard aligning prefix. Takes the -/// bundle by value (it is large; the encoder is its last consumer). -pub fn encode_continuation_guest_input( - bundle: crate::continuation::ContinuationProof, - inner_elf: &[u8], - opts: &ProofOptions, -) -> Result, Error> { - let (decode_commitment, page_commitments) = - crate::continuation::continuation_precomputed_commitments(inner_elf, &bundle, opts)?; - let input = ContinuationGuestInput { - bundle, - inner_elf: inner_elf.to_vec(), - decode_commitment, - page_commitments, - }; - let archive = rkyv::to_bytes::(&input) - .map_err(|e| Error::Execution(format!("rkyv encode failed: {e}")))?; - let mut blob = Vec::with_capacity(crate::RECURSION_INPUT_PREFIX_LEN + archive.len()); - blob.extend_from_slice(&crate::RECURSION_INPUT_MAGIC); - blob.extend_from_slice(&crate::RECURSION_INPUT_VERSION.to_le_bytes()); - blob.extend_from_slice(&[0u8; 4]); // reserved - debug_assert_eq!(blob.len(), crate::RECURSION_INPUT_PREFIX_LEN); - blob.extend_from_slice(&archive); - Ok(blob) -} - /// [`verify_and_attest_blob`]'s logic for a continuation bundle: takes the /// wire-format blob ([`encode_continuation_guest_input`]) and does the /// intended `continuation` guest's whole job in one call — verify every diff --git a/prover/src/tests/recursion_smoke_test.rs b/prover/src/tests/recursion_smoke_test.rs index bd1244c30..e95b1052a 100644 --- a/prover/src/tests/recursion_smoke_test.rs +++ b/prover/src/tests/recursion_smoke_test.rs @@ -185,6 +185,67 @@ fn setup_guest_run( (guest_elf_bytes, program, executor) } +/// Read the ethrex guest ELF (built by `make executor/program_artifacts/rust/ethrex.elf`). +fn read_ethrex_elf(root: &std::path::Path) -> Vec { + let path = root.join("executor/program_artifacts/rust/ethrex.elf"); + std::fs::read(&path).unwrap_or_else(|e| { + panic!( + "failed to read {} — run `make executor/program_artifacts/rust/ethrex.elf`: {e}", + path.display() + ) + }) +} + +/// Read a committed ethrex block fixture (`executor/tests/ethrex_bench_.bin`). +fn read_ethrex_fixture(root: &std::path::Path, txs: u32) -> Vec { + let path = root.join(format!("executor/tests/ethrex_bench_{txs}.bin")); + std::fs::read(&path).unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display())) +} + +/// [`setup_guest_run`] for the `continuation` guest feature: proves `inner_elf` +/// on `inner_input` via continuations (`epoch_log2`-cycle epochs) and loads +/// `recursion-cont-.elf` instead of the monolithic `recursion-.elf` +/// — the realistic in-VM cost of verifying a real (multi-epoch) inner proof +/// instead of the `empty`-program diagnostic floor. +fn setup_continuation_guest_run( + label: &str, + preset: Preset, + inner_elf_bytes: &[u8], + inner_input: &[u8], + epoch_log2: u32, +) -> ( + Vec, + executor::elf::Elf, + executor::vm::execution::Executor, +) { + let root = workspace_root(); + let guest_elf_bytes = read_guest_elf(&root, &format!("recursion-cont-{}", preset.name())); + + let opts = preset.options(); + eprintln!( + "[{label}] proving inner continuation (blowup={}, fri_queries={}, epoch=2^{epoch_log2}) ...", + opts.blowup_factor, opts.fri_number_of_queries + ); + let bundle = + crate::continuation::prove_continuation(inner_elf_bytes, inner_input, epoch_log2, &opts) + .expect("inner continuation prove should succeed"); + eprintln!("[{label}] continuation epochs: {}", bundle.num_epochs()); + let blob = recursion::encode_continuation_guest_input(bundle, inner_elf_bytes, &opts) + .expect("recursion::encode_continuation_guest_input failed"); + eprintln!("[{label}] rkyv blob: {} bytes", blob.len()); + + let program = executor::elf::Elf::load(&guest_elf_bytes).expect("ELF load failed"); + assert_ne!( + program.entry_point, + 0, + "recursion-cont-{} ELF has entry_point=0 — build artifact is malformed", + preset.name() + ); + let executor = + executor::vm::execution::Executor::new(&program, blob).expect("Executor::new failed"); + (guest_elf_bytes, program, executor) +} + /// Demangled enclosing-function name for a PC via the ELF symbol table; /// `` if none covers it. No file:line (symtab has no DWARF). fn resolve_pc(symbols: &executor::elf::SymbolTable, pc: u64) -> String { @@ -324,16 +385,68 @@ fn print_step_breakdown(buckets: &[u64; 7], total_cycles: u64) { } } -/// Single-pass execute-only profiler. Always prints total cycles, the -/// per-step cycle breakdown (marker decode is cheap — one `InstructionCache` -/// lookup per cycle), and a rough trace/LDE estimate; with `detailed`, also -/// the top-25 functions table (needs a `pc_hist` HashMap, so gated). +/// Single-pass execute-only profiler over the `empty` inner program (the +/// verifier's intrinsic recursion overhead, not a real workload). Always +/// prints total cycles, the per-step cycle breakdown (marker decode is cheap — +/// one `InstructionCache` lookup per cycle), and a rough trace/LDE estimate; +/// with `detailed`, also the top-25 functions table (needs a `pc_hist` +/// HashMap, so gated). fn run_profile(preset: Preset, progress_stride: usize, detailed: bool) { + let (guest_elf_bytes, program, executor) = setup_guest_run("profile", preset); + run_profile_from( + preset, + &guest_elf_bytes, + &program, + executor, + progress_stride, + detailed, + ); +} + +/// [`run_profile`] over a REAL inner program instead of `empty`: verifies +/// `inner_elf_bytes`/`inner_input` via the `continuation` guest +/// (`recursion-cont-.elf`) — the realistic in-VM verifier cost, not +/// the intrinsic-overhead floor. +fn run_profile_continuation( + preset: Preset, + inner_elf_bytes: &[u8], + inner_input: &[u8], + epoch_log2: u32, + progress_stride: usize, + detailed: bool, +) { + let (guest_elf_bytes, program, executor) = setup_continuation_guest_run( + "profile-block", + preset, + inner_elf_bytes, + inner_input, + epoch_log2, + ); + run_profile_from( + preset, + &guest_elf_bytes, + &program, + executor, + progress_stride, + detailed, + ); +} + +/// Shared profiling loop for [`run_profile`]/[`run_profile_continuation`]: runs +/// an already-set-up guest executor and prints the same cycle/step/function +/// breakdown regardless of which inner program it verified. +fn run_profile_from( + preset: Preset, + guest_elf_bytes: &[u8], + program: &executor::elf::Elf, + mut executor: executor::vm::execution::Executor, + progress_stride: usize, + detailed: bool, +) { use std::collections::HashMap; let opts = preset.options(); - let (guest_elf_bytes, program, mut executor) = setup_guest_run("profile", preset); - let symbols = executor::elf::SymbolTable::parse(&guest_elf_bytes); + let symbols = executor::elf::SymbolTable::parse(guest_elf_bytes); let instructions = executor::vm::execution::InstructionCache::new(&program.data) .expect("instruction cache build failed"); @@ -771,19 +884,93 @@ fn test_recursion_prove_1query() { } /// Dump the guest's private-input blob to `/tmp/recursion_input.bin` for the -/// CLI's `execute --flamegraph`. +/// CLI's `execute --flamegraph` and `scripts/bench_recursion_cycles.sh`. +/// +/// Env knobs: +/// * `RECURSION_DUMP_PRESET` (`min`|`blowup2`|`blowup4`|`blowup8`, default +/// `min`) — the [`Preset`] whose options the inner proof is generated under; +/// must match the `recursion-.elf` the blob will be fed to, or the +/// guest rejects the proof. +/// * `RECURSION_DUMP_INNER_ELF` (path, default the `empty` guest) — the inner +/// program to prove, for realistic trace heights (e.g. the ethrex guest). +/// * `RECURSION_DUMP_INNER_INPUT` (path, default none) — the inner program's +/// private input (e.g. an ethrex-fixtures block). +/// * `RECURSION_DUMP_EPOCH_LOG2` (int, default unset = monolithic) — prove the +/// inner via continuations with `2^n`-cycle epochs (memory-bounded prover) +/// and encode a [`recursion::ContinuationGuestInput`] blob instead; feed it +/// to `recursion-cont-.elf`, not the monolithic guest. #[test] #[ignore = "diagnostic: writes recursion private input to /tmp/recursion_input.bin"] fn test_dump_recursion_input() { let root = workspace_root(); - let empty_elf_bytes = read_guest_elf(&root, "empty"); - let (_inner_proof, blob) = - prove_inner_and_encode_blob("dump-input", &empty_elf_bytes, &[], &MIN_PROOF_OPTIONS); + let preset_name = std::env::var("RECURSION_DUMP_PRESET").unwrap_or_else(|_| "min".to_string()); + let preset = Preset::ALL + .into_iter() + .find(|p| p.name() == preset_name) + .unwrap_or_else(|| { + panic!( + "unknown RECURSION_DUMP_PRESET '{preset_name}' (expected min|blowup2|blowup4|blowup8)" + ) + }); + + let (inner_elf_bytes, inner_label) = match std::env::var("RECURSION_DUMP_INNER_ELF") { + Ok(p) => ( + std::fs::read(&p).unwrap_or_else(|e| panic!("read RECURSION_DUMP_INNER_ELF {p}: {e}")), + p, + ), + Err(_) => (read_guest_elf(&root, "empty"), "empty".to_string()), + }; + let inner_input = match std::env::var("RECURSION_DUMP_INNER_INPUT") { + Ok(p) => { + std::fs::read(&p).unwrap_or_else(|e| panic!("read RECURSION_DUMP_INNER_INPUT {p}: {e}")) + } + Err(_) => Vec::new(), + }; + + let blob = match std::env::var("RECURSION_DUMP_EPOCH_LOG2") { + Ok(s) => { + let epoch_log2: u32 = s + .parse() + .unwrap_or_else(|e| panic!("bad RECURSION_DUMP_EPOCH_LOG2 '{s}': {e}")); + let opts = preset.options(); + eprintln!( + "[dump-input] proving inner continuation (blowup={}, fri_queries={}, epoch=2^{epoch_log2}) ...", + opts.blowup_factor, opts.fri_number_of_queries + ); + let bundle = crate::continuation::prove_continuation( + &inner_elf_bytes, + &inner_input, + epoch_log2, + &opts, + ) + .expect("inner continuation prove should succeed"); + eprintln!("[dump-input] continuation epochs: {}", bundle.num_epochs()); + recursion::encode_continuation_guest_input(bundle, &inner_elf_bytes, &opts) + .expect("recursion::encode_continuation_guest_input failed") + } + Err(_) => { + let (_inner_proof, blob) = prove_inner_and_encode_blob( + "dump-input", + &inner_elf_bytes, + &inner_input, + &preset.options(), + ); + blob + } + }; + assert!( + blob.len() <= executor::vm::memory::MAX_PRIVATE_INPUT_SIZE as usize, + "recursion input exceeds MAX_PRIVATE_INPUT_SIZE" + ); let path = "/tmp/recursion_input.bin"; std::fs::write(path, &blob).expect("write blob"); - eprintln!("[dump-input] wrote {} bytes to {path}", blob.len()); + eprintln!( + "[dump-input] preset={} inner={inner_label} wrote {} bytes to {path}", + preset.name(), + blob.len() + ); } /// Cycle count only of the recursion guest verifying a 1-query inner proof. @@ -800,6 +987,49 @@ fn test_recursion_cycles_multiquery() { run_profile(Preset::Blowup8, 500, false); } +/// Cycle count only at 128-bit security with the realistic base-layer shape: +/// blowup=2 yields ~0.49 bits/query, so the full 219-query FRI dominates. +#[test] +#[ignore = "diagnostic: recursion guest cycle count (blowup=2, 219 queries)"] +fn test_recursion_cycles_blowup2() { + run_profile(Preset::Blowup2, 500, false); +} + +/// Cycle count only at 128-bit security, blowup=4 (110 queries) — the other +/// realistic base-layer point. +#[test] +#[ignore = "diagnostic: recursion guest cycle count (blowup=4, 110 queries)"] +fn test_recursion_cycles_blowup4() { + run_profile(Preset::Blowup4, 500, false); +} + +/// Continuation epoch size for the real-block profile below — matches +/// `scripts/bench_recursion_scaling.sh`'s default. +const BLOCK_EPOCH_LOG2: u32 = 21; + +/// Full profile (top-25 + per-step) of the recursion `continuation` guest +/// verifying a REAL ethrex block (4 transfers) — not the `empty`-program +/// diagnostic floor `test_recursion_profile_1query`/`_multiquery` measure. +/// blowup=4 (110 queries): the cheaper of the two realistic base-layer +/// presets, so the full histogram stays tractable; `bench_recursion_cycles.sh`/ +/// `bench_recursion_scaling.sh` already cover cycle counts across every preset +/// and block size, so this only needs to exist for the detailed breakdown. +#[test] +#[ignore = "diagnostic: heavy; recursion guest histogram + steps over a real ethrex block (blowup=4)"] +fn test_recursion_profile_blowup4_block() { + let root = workspace_root(); + let ethrex_elf_bytes = read_ethrex_elf(&root); + let fixture = read_ethrex_fixture(&root, 4); + run_profile_continuation( + Preset::Blowup4, + ðrex_elf_bytes, + &fixture, + BLOCK_EPOCH_LOG2, + 500, + true, + ); +} + /// Full profile (top-25 + per-step) of the 1-query run. #[test] #[ignore = "diagnostic: ~8 min; recursion guest histogram + steps (1 query)"] diff --git a/scripts/bench_recursion_cycles.sh b/scripts/bench_recursion_cycles.sh index 5db264a45..c6ae76058 100755 --- a/scripts/bench_recursion_cycles.sh +++ b/scripts/bench_recursion_cycles.sh @@ -18,8 +18,8 @@ # single measuring CLI (MEASURE_CLI) built once from the checkout this script runs in: # * Guest cycles — retired instructions. # * Keccak calls — keccak-permutation accelerator ecalls (one cycle each, but each -# runs a whole permutation invisibly, so it's the companion signal; -# currently 0 until the verifier is wired to the keccak syscall). +# runs a whole permutation invisibly, so it's the companion signal: +# the verifier's Merkle/transcript hashing rides on this syscall). # The CLI also prints an Ecsm (EC scalar-mul) count, but the STARK verifier does no # scalar-mul, so it is structurally 0 for a recursion proof — dropped as noise, not read. # MEASURE_CLI's executor counts ANY ref's guest ELF correctly (it just feeds the blob @@ -35,14 +35,22 @@ # Usage: scripts/bench_recursion_cycles.sh REF_A [REF_B=origin/main] [PRESET=min] # REF_A ref/SHA to evaluate (the PR side). # REF_B baseline ref/SHA (default origin/main). -# PRESET recursion-verifier preset (default min). Per ref the tool prefers -# recursion-.elf and falls back to recursion.elf (older refs / main -# build a single unnamed recursion guest). It is EXPECTED and correct for the -# two sides to use DIFFERENT artifacts — e.g. main→recursion.elf while a -# preset PR→recursion-min.elf — because both are verified under the SAME min -# proof options (the dump test pins MIN_PROOF_OPTIONS). The printed per-ref -# `guest=` labels show which each side used; a differing name is the -# expected comparison, not a mismatch. +# PRESET recursion-verifier preset (default min): min = blowup=2, 1 query +# (cheap diagnostic regime); blowup2 = blowup=2, 219 queries (the +# realistic base-layer proof shape at 128-bit — production pipelines +# prove the base at low blowup and wrap at high blowup); blowup4 = +# blowup=4, 110 queries (the other realistic base-layer point); +# blowup8 = blowup=8, 73 queries. The preset picks BOTH the guest ELF +# (recursion-.elf, falling back to recursion.elf on older refs +# that build a single unnamed min guest) AND the inner-proof options of +# the dumped blob (exported as RECURSION_DUMP_PRESET to the dump test). +# Refs predating the preset-aware dump test always dump min options, so +# only PRESET=min is measurable for them — the script fails loudly +# up front rather than let the guest reject the blob in-VM. It is +# EXPECTED and correct for the two sides to use DIFFERENT artifact +# names (e.g. main→recursion.elf vs PR→recursion-min.elf) — both are +# verified under the SAME preset options. The printed per-ref +# `guest=` labels show which each side used. # Env: # REBUILD=1 force rebuild of MEASURE_CLI and re-run of every ref # (guest build + blob dump + measurement); ignore caches. @@ -104,8 +112,8 @@ prune_worktree_cache() { echo "==> Pruning old ref worktree $wt (keeping newest $PRUNE_KEEP)" >&2 git worktree remove --force "$wt" >/dev/null 2>&1 || rm -rf "$wt" rm -f "$WORK"/result_"${s8}"_*.txt "$WORK"/blob_"${s8}"_*.bin \ - "$WORK"/build_guest_"${s8}".log "$WORK"/dump_"${s8}".log \ - "$WORK"/measure_"${s8}".err + "$WORK"/build_guest_"${s8}".log "$WORK"/dump_"${s8}"*.log \ + "$WORK"/measure_"${s8}"*.err done <<< "$stale" git worktree prune >/dev/null 2>&1 || true } @@ -229,22 +237,29 @@ measure_ref() { fi echo "==> [$role] guest ELF: $(basename "$guest_elf")" >&2 - # 2c. Generate this ref's own input blob via its ignored dump test. + # 2c. Generate this ref's own input blob via its ignored dump test, under this + # preset's options (RECURSION_DUMP_PRESET). Refs predating the preset-aware dump + # test always prove the min options; feeding that blob to a non-min guest would + # only fail in-VM verification much later, so refuse loudly up front instead. if ! grep -rq "fn test_dump_recursion_input" "$wt/prover/src/tests/" 2>/dev/null; then echo "ERROR: [$role] ref $ref ($sha8) has no 'test_dump_recursion_input' — cannot generate its input blob." >&2 exit 1 fi - echo "==> [$role] dumping recursion input blob (cargo test test_dump_recursion_input) ..." >&2 + if [ "$PRESET" != "min" ] && ! grep -rq "RECURSION_DUMP_PRESET" "$wt/prover/src/tests/" 2>/dev/null; then + echo "ERROR: [$role] ref $ref ($sha8) predates the preset-aware dump test (no RECURSION_DUMP_PRESET) — only PRESET=min is measurable for it." >&2 + exit 1 + fi + echo "==> [$role] dumping recursion input blob (cargo test test_dump_recursion_input, preset=$PRESET) ..." >&2 rm -f /tmp/recursion_input.bin - local dlog="$WORK/dump_${sha8}.log" + local dlog="$WORK/dump_${sha8}_${PRESET}.log" if [ -n "${HOST_TARGET_DIR:-}" ]; then - if ! ( cd "$wt" && CARGO_TARGET_DIR="$HOST_TARGET_DIR" cargo test -p lambda-vm-prover --lib test_dump_recursion_input -- --ignored --nocapture ) >"$dlog" 2>&1; then + if ! ( cd "$wt" && RECURSION_DUMP_PRESET="$PRESET" CARGO_TARGET_DIR="$HOST_TARGET_DIR" cargo test --release -p lambda-vm-prover --lib test_dump_recursion_input -- --ignored --nocapture ) >"$dlog" 2>&1; then echo "ERROR: [$role] blob-dump test failed for $ref ($sha8). Tail of $dlog:" >&2 tail -40 "$dlog" >&2 exit 1 fi else - if ! ( cd "$wt" && cargo test -p lambda-vm-prover --lib test_dump_recursion_input -- --ignored --nocapture ) >"$dlog" 2>&1; then + if ! ( cd "$wt" && RECURSION_DUMP_PRESET="$PRESET" cargo test --release -p lambda-vm-prover --lib test_dump_recursion_input -- --ignored --nocapture ) >"$dlog" 2>&1; then echo "ERROR: [$role] blob-dump test failed for $ref ($sha8). Tail of $dlog:" >&2 tail -40 "$dlog" >&2 exit 1 @@ -262,9 +277,9 @@ measure_ref() { echo "==> [$role] measuring: $MEASURE_CLI execute $(basename "$guest_elf") --private-input --cycles" >&2 local t0 t1 dt out t0=$(date +%s) - if ! out="$("$MEASURE_CLI" execute "$guest_elf" --private-input "$blob" --cycles 2>"$WORK/measure_${sha8}.err")"; then + if ! out="$("$MEASURE_CLI" execute "$guest_elf" --private-input "$blob" --cycles 2>"$WORK/measure_${sha8}_${PRESET}.err")"; then echo "ERROR: [$role] MEASURE_CLI execute failed for $ref ($sha8). Tail of stderr:" >&2 - tail -20 "$WORK/measure_${sha8}.err" >&2 + tail -20 "$WORK/measure_${sha8}_${PRESET}.err" >&2 exit 1 fi t1=$(date +%s); dt=$((t1 - t0)) @@ -334,10 +349,13 @@ mcycd() { } # Human label for the proof regime this preset measures, so a reader can't mistake the -# single-query `min` number for the full 128-bit verifier cost. CI always passes `min`. +# single-query `min` number for the full 128-bit verifier cost. CI passes `min` plus +# the full-query regimes `blowup2`/`blowup4` (see .github/workflows/bench-verify.yml). case "$PRESET" in min) REGIME="single query (blowup=2, 1 query)" ;; - blowup8) REGIME="128-bit (blowup=8, multi-query)" ;; + blowup2) REGIME="128-bit (blowup=2, 219 queries — realistic base-layer)" ;; + blowup4) REGIME="128-bit (blowup=4, 110 queries — realistic base-layer)" ;; + blowup8) REGIME="128-bit (blowup=8, 73 queries)" ;; *) REGIME="$PRESET" ;; esac diff --git a/scripts/bench_recursion_scaling.sh b/scripts/bench_recursion_scaling.sh new file mode 100755 index 000000000..3242c0b06 --- /dev/null +++ b/scripts/bench_recursion_scaling.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +# +# bench_recursion_scaling.sh — in-VM recursion-verifier scaling ladder. +# +# Sweeps ethrex block sizes × verifier presets: proves each block's inner +# execution via CONTINUATIONS (memory-bounded 2^EPOCH_LOG2-cycle epochs, so any +# block size proves on a bounded-RAM box), then executes the continuation +# recursion guest (recursion-cont-.elf) on the bundle and records the +# EXACT deterministic guest cycle count — the in-VM cost of verifying that +# block's proof. +# +# Sweep order is PRESET-MAJOR on purpose: the full block-size curve for the +# first preset completes before the next preset starts, so the headline regime +# (blowup2 = the realistic base-layer options, 219 FRI queries) yields a usable +# scaling curve as early as possible instead of only when everything ends. +# +# Usage: scripts/bench_recursion_scaling.sh [RESULTS_FILE=/tmp/recursion_scaling.txt] +# Env: +# TXS="1 4 8 16" block sizes (transfers); fixtures are read from +# executor/tests/ethrex_bench_.bin (committed for +# 1/4/8/16) and generated via tooling/ethrex-fixtures +# when missing. +# PRESETS="blowup2 blowup4 min" verifier presets, most important first. +# EPOCH_LOG2=21 inner continuation epoch size (log2 cycles). +# DUMP_FEATURES="" extra cargo features for the proving dump, +# e.g. DUMP_FEATURES=cuda on a GPU box. +# +# Prereqs (the script fails fast on each): +# cargo build --release -p cli +# make compile-recursion-elfs (recursion-cont-*.elf) +# make executor/program_artifacts/rust/ethrex.elf (the inner guest) +# +# Output: one key=value line per cell in RESULTS_FILE, e.g. +# txs=4 preset=blowup2 epochs=2 blob=145080513 cycles=18984803380 keccak=3152604 exec_wall_s=164 +# Cycle counts are deterministic (machine-independent); wall times are not. +set -euo pipefail + +ROOT="$(git rev-parse --show-toplevel)" +cd "$ROOT" + +TXS="${TXS:-1 4 8 16}" +PRESETS="${PRESETS:-blowup2 blowup4 min}" +EPOCH_LOG2="${EPOCH_LOG2:-21}" +RESULTS="${1:-/tmp/recursion_scaling.txt}" +WORK="$(mktemp -d /tmp/recursion_scaling.XXXXXX)" + +CLI=target/release/cli +ART=executor/program_artifacts/recursion +ETHREX=executor/program_artifacts/rust/ethrex.elf + +[ -x "$CLI" ] || { echo "ERROR: $CLI missing — run: cargo build --release -p cli" >&2; exit 1; } +[ -f "$ETHREX" ] || { echo "ERROR: $ETHREX missing — run: make $ETHREX" >&2; exit 1; } +for P in $PRESETS; do + [ -f "$ART/recursion-cont-${P}.elf" ] || { + echo "ERROR: $ART/recursion-cont-${P}.elf missing — run: make compile-recursion-elfs" >&2 + exit 1 + } +done + +echo "==> results -> $RESULTS (work dir: $WORK)" +: > "$RESULTS" + +for P in $PRESETS; do + for N in $TXS; do + FIX=executor/tests/ethrex_bench_${N}.bin + if [ ! -f "$FIX" ]; then + echo "==> [${P}/${N}tx] generating missing fixture $FIX" >&2 + ( cd tooling/ethrex-fixtures && cargo build --release ) >"$WORK/fixtures_build.log" 2>&1 + tooling/ethrex-fixtures/target/release/ethrex-fixtures "$N" "$FIX" distinct >&2 + fi + + # Inner block cost, once per block size (cheap; repeated per preset is fine — + # the count is deterministic and the run takes well under a second). + ic="$("$CLI" execute "$ETHREX" --private-input "$FIX" --cycles | awk -F': ' '/^Cycles:/{print $2; exit}')" + + echo "==> [${P}/${N}tx] proving inner continuation (epoch=2^${EPOCH_LOG2}) ..." >&2 + rm -f /tmp/recursion_input.bin + DLOG="$WORK/dump_${N}tx_${P}.log" + if ! ( RECURSION_DUMP_PRESET="$P" RECURSION_DUMP_EPOCH_LOG2="$EPOCH_LOG2" \ + RECURSION_DUMP_INNER_ELF="$PWD/$ETHREX" RECURSION_DUMP_INNER_INPUT="$PWD/$FIX" \ + cargo test --release -p lambda-vm-prover ${DUMP_FEATURES:+--features "$DUMP_FEATURES"} --lib test_dump_recursion_input -- --ignored --nocapture ) \ + >"$DLOG" 2>&1 || [ ! -f /tmp/recursion_input.bin ]; then + echo "txs=$N preset=$P inner_cycles=$ic DUMP_FAILED" >> "$RESULTS" + echo "ERROR: [${P}/${N}tx] dump failed; tail of $DLOG:" >&2 + tail -20 "$DLOG" >&2 + continue + fi + epochs="$(grep -o 'continuation epochs: [0-9]*' "$DLOG" | awk '{print $3}')" + BLOB="$WORK/blob_${N}tx_${P}.bin" + mv /tmp/recursion_input.bin "$BLOB" + sz="$(wc -c < "$BLOB" | tr -d ' ')" + + echo "==> [${P}/${N}tx] executing recursion-cont-${P}.elf (${epochs} epochs, ${sz} bytes) ..." >&2 + t0=$(date +%s) + if out="$("$CLI" execute "$ART/recursion-cont-${P}.elf" --private-input "$BLOB" --cycles 2>"$WORK/exec_${N}tx_${P}.err")"; then + t1=$(date +%s) + cyc="$(printf '%s\n' "$out" | awk -F': ' '/^Cycles:/{print $2; exit}')" + kec="$(printf '%s\n' "$out" | awk -F': ' '/^Keccak calls:/{print $2; exit}')" + line="txs=$N preset=$P inner_cycles=$ic epochs=$epochs blob=$sz cycles=$cyc keccak=$kec exec_wall_s=$((t1 - t0))" + echo "$line" >> "$RESULTS" + echo " $line" >&2 + else + echo "txs=$N preset=$P inner_cycles=$ic epochs=$epochs blob=$sz EXEC_FAILED" >> "$RESULTS" + echo "ERROR: [${P}/${N}tx] guest execute failed; tail of stderr:" >&2 + tail -10 "$WORK/exec_${N}tx_${P}.err" >&2 + fi + rm -f "$BLOB" + done +done + +echo "==> done. Results:" +cat "$RESULTS" From 9c87b8c6448a4224ad77d5d35a1cbbaa8471290a Mon Sep 17 00:00:00 2001 From: Mario Rugiero Date: Fri, 17 Jul 2026 16:05:13 -0300 Subject: [PATCH 2/5] fix(recursion-bench): pin guest --bin, prune fixtures, harden scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address PR review feedback: - build_guest_elf: pass --bin so a continuation build's superset features (`continuation min`) don't also rebuild the plain preset's bin into the same shared target-dir path a concurrent `make -j` job is writing to. - drop redundant ethrex_bench_{1,8,16}.bin fixtures (only _4 is read by tests); bench_recursion_scaling.sh regenerates them on demand. - bench_recursion_scaling.sh: don't let one failed cycle-count/grep abort the whole sweep under `set -e`; clean up its temp dir. - recursion.rs docs: list blowup4 among the fixed-preset ELFs. - recursion profile tests: check the guest's actual committed attestation against a trusted host recompute, not just that it ran without crashing — covers both the monolithic and continuation (real ethrex block) profile paths. --- Makefile | 10 ++- executor/.gitignore | 10 +-- executor/tests/ethrex_bench_1.bin | Bin 13341 -> 0 bytes executor/tests/ethrex_bench_16.bin | Bin 29082 -> 0 bytes executor/tests/ethrex_bench_8.bin | Bin 21410 -> 0 bytes prover/src/recursion.rs | 7 +- prover/src/tests/recursion_smoke_test.rs | 84 ++++++++++++++++++++--- scripts/bench_recursion_scaling.sh | 5 +- 8 files changed, 96 insertions(+), 20 deletions(-) delete mode 100644 executor/tests/ethrex_bench_1.bin delete mode 100644 executor/tests/ethrex_bench_16.bin delete mode 100644 executor/tests/ethrex_bench_8.bin diff --git a/Makefile b/Makefile index 911edeb17..783817b2b 100644 --- a/Makefile +++ b/Makefile @@ -60,8 +60,13 @@ RECURSION_ARTIFACTS := $(addprefix $(RECURSION_ARTIFACTS_DIR)/, $(addsuffix .elf # 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--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 per-preset `cp`s race-free under `make -j`. +# define/foreach/eval below rather than the generic %.elf pattern rule. +# `required-features` on each [[bin]] is a subset match, not an exact-set +# match, so e.g. `--features "continuation min"` (the cont-min build) also +# satisfies plain `recursion-min-bench`'s `required-features = ["min"]` and +# cargo builds it too — racing with a concurrent `make -j` job building +# `recursion-min.elf` (`--features min`) to that same shared-target-dir path. +# `--bin $(2)` in build_guest_elf pins each invocation to its one target bin. 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 @@ -197,6 +202,7 @@ cd $(1) && \ -Z build-std=core,alloc,std,compiler_builtins,panic_abort \ -Z build-std-features=compiler-builtins-mem \ -Z json-target-spec \ + --bin $(2) \ $(3) cp $(SHARED_TARGET_DIR)/riscv64im-lambda-vm-elf/release/$(2) $@ endef diff --git a/executor/.gitignore b/executor/.gitignore index 3ed575591..ee277c65b 100644 --- a/executor/.gitignore +++ b/executor/.gitignore @@ -2,9 +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 +# _4 is committed: prover/src/tests/recursion_smoke_test.rs reads it directly +# (~17 KB), and scripts/bench_recursion_scaling.sh also reads it as part of its +# scaling ladder. The other ladder sizes (_1/_8/_16) and bigger ones (e.g. _20) +# stay ignored — bench_recursion_scaling.sh generates any missing fixture on +# demand via tooling/ethrex-fixtures. !/tests/ethrex_bench_4.bin -!/tests/ethrex_bench_8.bin -!/tests/ethrex_bench_16.bin diff --git a/executor/tests/ethrex_bench_1.bin b/executor/tests/ethrex_bench_1.bin deleted file mode 100644 index 6a9c94380a13bb2d863e56b2e1cb3e75af75cb01..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13341 zcmeHO3tUWF+h1$X{aUG%O65>VD7w0xN|aQ(m@bT(-A$1s5<+IW=zlziT_Lx~e0wCv=kU(FZ~c7E?|b*}H+w&8?Pvd=XRZIUp1ta9WOqE#q z&7+RqS6+CW?P_kV;HQ)OzQev~I}P_<{#osWC7coSW*=VoX4Cs1203F?+@x#gs({N}z{!L7Qj(us+Ko!~H9>ZHG#yZu_7l zlls2vLP|j42#s9RyIyID)>%m@iYpzCJAT3Z>YDG1ZXW~!q4wy{?SsQV3%bj&QT6Ti zQ3kbfMpX@D&=JlYP7j--&y__jojrhxfJfx>STgA3PxZnI$l^Gls+^Z>p`x&2Dsd{$ zk)zN5bkY(0$bh-f01ln%%+W`o8IC7~iS8J_8Sb;hCw`60(Z@~-6^TEFtaL`DI4thE zZ=3UNS;if9IioNPK_Q>R=7dSfqQ1^N$PY&%!Vx}xGYa8mK5FB{<|p#;33hgN2B?t? z8trT#yiQNJ``lzb$zCKDb_EI@Q5=hl;t&)Mb$O0h6-WQm0aexKJL;mSB6gJ#2bD2I ztvQ*(D{zj0ja`ExEN&Q%i%LqN&CYhHB%8zL@h~65ox^yTk`RxFS|dCb2ElVcWv~*2 zEKE{`d=!D-Y4Eo(FgVPCk4j?NCQ5uBYNW(NNpLm~)xiX!P#B2`1figc33#C(j|n)T zAc+ZxP#|LhgtKQ09dB`BZ-(3i%*P~W%mqhxn2B|k9S0LRSgY7#ks#|1FFV_D*eJw$ zAS?_@xx-dxJ3Ed$eBi`}D8k3FIT#$)t=33yc>O4pYQ^V0_21+j{mgr+koVNDc^4vm z%{%^ky!Y~N@)!T;C;o%J%ReH8-pBYy`uNxU#Q!H;9vbvr{)OE1MFI}8_zpsL9)4kG z(r0$e|08ym^|1qc*@^CDCr-!?{EMBS&+Ht1FA%t6ktczrn%UTKE_Qs2<(!!x1%hS}3 zk1|CiXORkp!sPk?dxP8va_3bpH)6hzffP4WRA)r@Dh$$#n3P%pM0PM&G-9Tx+hIC( zPxC+kAX1rB0vXZ}1Q19RDng@B$dH0iC=3P;p_AwcnTp5MAel+00UCoz!BHguAW<1) z3Ka(>-%f*Ek^W|`JYVU6_&SU%s(hcKr8G-wQD|9*R4Y3!A#l+Lk4=pz5`x`!jImBQ zj2(r2@h5gP@9mTy?4igY`$-z$ygUBsV9w)Cr}yzbLmqb8Rs}h&C`Xd(oSR4%B_3Wff&c+gs(zz)b z`zaA?j_>}{f%Ggg{=xa~367-^a~~k-fP_$IIgRvZ}pGqtr(ew?E!7 z-Ef&WR=hv>PiK$0TuP)K8GCKntxI=CZ+C1~3qMos+FC!s#lsH*RNwt>yPBbFx!HbP zcF4&#zs{F+JFcuIm3CdG86GT~4+ddL?gy;)Rzr}Idd32^KagKZS&U3~+A%q9Oq8{j z88_p_95vFTN<*e-Pw(_iPV4)Ge`)gTlSBfON~IulB8`rxLmZXBq~i$$gd_pz*f5}y zDL8~cCX)~{iAZ76NmM3Xf=OghByb25kBB)UUWHsXQQNi@^$#8r+UfAJ)-n24^XBxH z&bG=lkJ_$7qmk9v2>2cI>K`~FewC5=bNm2`wV?{*ADkZq#)L;ca5^1PR3u|ta7udW z>Ct`x*8VFwqGMHz5itO5>BoqGKud#V^^@jW59zX6 z5X=h`PENesm~QlSrMW#F)p_>lsO>ZjSLCTprFTHN+_F7? zw`v|VrKkC2&zoDxEp1`9fGm$P*%cM{+-5;w8`GBa{Gc3TPtcGT61P6eYCf^Zxw*%- zi1+$bOZKTv()|c6I*Ep5P%^|OAxs*D%%IRoWCjyYg%S*s1cO4r5hUo?p2!S zR4Nq@NkkG3B2>w@v$^pl$yZiiB*`AJ%8(Q$anX~JvKn)^{>|{o388EAZkOXq?t!DT zE5bA;t?s^oca6+ig7{;pcM1me^;(*v5c|j5rO)%0A8LrfgJ`w~LIaqu$}_JTwq~U9 zK7}2cj*YSY&ekMMxir){4gHPiO(-`=c~ZRWVg7Peb$H zGb^fJMaj>WX1@lGbMlff|2&+^XP4?mXrgw`%j?qIc=+PTYH&7m#m28Of51u2AABkhNBKIfH zF}$q#9Cvl$Si&4o7LhXiuxX1KrJ+s0RTBf_R|y;-R~mk<#<0WTd8{HQ%By4bsu{UX&CGZc>WbTpjaxlt zg2Qd2d@hzFD!TP1w95n07SR~}7!eR0FW$2%Hdl(wDMRt&n6)-#!Q%w>AG}Jwf2{Aol*c9uh%^S=jPPW|dyW+NchvoQTZQ@|;L!MSsukWiM z2+Y#5Srcr!cg%}+TykthxXh`C$(@M17#P1wq7>~1Fxc$NZ1(O-TecCkIz9Ntl)|YC z+}hYiw&lijuUHP2|7zAQrSu_W_$#TFI2JZ`ceH_ogd zp+%w*X*dFzLdP)y6W}GNbU>lei8wq12M_{Df=s92C2#~h1waWRLIw<~1VX}*0F?nC zLGrB)H-_L>?l#5~dwv>#r>CW#7q0exzU^mXN85jyiNpvva3iMrbxxj3ZP@GQMur8` z{G|)k1}R^v_Tl%u){Tz4I9m)1(ZTXlG{jk5Ft~X4B{@0oS?Yq6b=~BwlHGacn!65O zxMF+6lQpfVlK_F&0a|$M@R_sM=?ZhPu`^^c6rQvwv9x@TMM)TQnmDj~WF5Ev-v6x$a0g~zTiKAdc$ ze|y@&g|lQ9u1Y^x6ds*4M8h9~cNTl`i7z!SRCd&NB~4@;@-#`-pQ^sE{n4hn-!AG* zs}aMB=l}*ryl&aJXRpf5S~b-D%{A#WhS)>}8)=^S0Ky-}-cD5?7%N_O7g}#MPH)qN@}}mHCuDUKZJ=5GTfig&1q1ps?ekMhUvP+jPv>y}!qlJfT@+gSyzZ zxii~>ZIq;a-$0Ny|G~3As=J~FTV>m1t$lm!(b*W|nOY9dqeIWyugu(`+Kj~hT@hYtmK4lgQlx1T;!=f#d7XUIvhaXQ&5OoYh3d4E ziSZ4l+XuoyF))Dspizy-3?kp#8^5t?+PsxDcYn#qxrd4ktW!e0t0We#5eFmR??bt^ zrs}r*jIiVpODEU}U}FL_}QQ2`0Z4Tp_~!z;6F$MXnq_~pA&S{ zWYf_Fb6gD>Gj!_CH)gCN`4m*0lc5J%-4dfMU^&sCTsmgxE?d0>HJVdq#u1C{Eu`f; z#y*WSI-XJE-3CE@Lemp1&);O`RxU~^qC`n5&5KahYm=XDP;9p@Y3n>2F&@l;a>MD) zg0@xLs#FZ_nv~4GKEqhPdvETBmurU2XM2oGDHp?2k%^@rp#{PlMSkg0t0Wh=M^8IO zgSzidR+3gJZxnIxbI*E;#wO=F+2Hsi5bV8XA6)U|&nUMN?I*h!PjLLYMOhY*tCw_<%%ke*%T_(CRd zmYTFZzDj**Sn0zf4oXwA1Ykv%NGl{pXz}KeIfpjtFrvzjLjjo(6_hn`Rz}$1v26!b zc4D7yH+JFIoZ$NDj5hu-F*-2)#JS_$npOTEXdc(3BdAm4hd?m$m{sCq@2zdp>&N9k zFLNrBna<5GIu(1hWT!4ZS*6Md%zG!&kMb9fG_PKNJkO?Pnp1%8PCb2BBh#KLw8lJS zD>-HUh|%KT5YJud{owpjoj`!c(x=yr-f~}T_Wfg|ul!Yp{ZjRx315)@e)*SEUxdHw z{JrLjazBMH$bC5QY3KM)G7;Mg|3Blk!1_~}g6)NM-^}=+>}%eC54^9r{{t;ddaeKf diff --git a/executor/tests/ethrex_bench_16.bin b/executor/tests/ethrex_bench_16.bin deleted file mode 100644 index d466f9aba9ffa1bda3210b8fc262e639d9b5e51f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29082 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

a8|vtSXrVkD`ZC!#k3}*8fZW1P|vhc!0)% z@nLj$9ED9{zrc*nH0f9|6Gp+&}8+0WqXY%EhbK7oT~8o8@(mnzd3 z>{K*r5|DV3wl^iSzG|+!#?xPBJiuGc(AcHT!|$9M~Ueq`|l1R!Y*))C;j2)fSHb3d;k5(0m@vq{s54*?5cW2Qqx3lhjj- zWS#BG`1Za$Y6u8W>ofAWezv_&;3|!SQF^MkwD#N061Q{Pwm;h^AhP#R2Lv=v+fCox zIP_dm!?l-@~>pm-XbtGYf^UMB9&hOoHuYei>`14{ZRoUzHTb@r?7 z6C#8`N{GcS$ZGg7G*=M*cCym&-M?+ft>a8f2IYa(W;3f@^rXdXUn3yS`ceHKmtL;# zsgta?)8oz2CwUt*W-1y!4_KQw+chy>rVFA)pg4&l2+D*Z&?JfAOcs+45BAbYCXEiD z#|fU+!gw&Qm`)?1>z@s91Yy!)cw1Xk908+=f03AI1SAz~y4KHWhlcUls?AFNvCHmV zkTt42>S&SkdRb{7q@@f2cG9;kuDtPT5L3uP&)j=*)hLYE1qM22HqXo+qT#Rz3TF7+ zldcE7ej7I~lK<4ZMBV?7Q6YWTi)WYwuAhH6`l>O+{(aNMpQY3kU|@F`_$70&Ml-t# zr|hw`X!hvPMzMwJ){m9mZt>&HP*h7i@q)S^LQnwh{gkEm11qWq zs*i3KOS$ztOzxuQ9AWC%zpzJduRK5R__c?Ej9MQ|)e)^7C-?AT#XA|LTiFUR*M+HL zbB(>V=xuR>134}zw|OgGPZKir6Sv)z|bxzH5O0!$wC7{Is-Iupiq!)E|AfYL}vK%mfZ_6tpa zgZrB7H5(ea#9;M0{ee4dRo;;z_mfn%&dvjtvy~fk9t$(hhv|pnkR$6vl{V&D&d$1U z=jg%6xo%YwS}Cp~^1%&{)eta@KhY>LsnmPgZ)TN-hl=B?a&s#N+_Uv`N6x;|S^l6- zm{$57TZ#0Kn*VfA^J`wv%F@y@bb!`<9o4=5uiSI0OcdbJgs6(rDA5^p)fbxsj2y{J zkLGA@(O-0C=Mm>VJ~mTS9hVEy3Z-NUp&`I$?V{W;gE9b4Vgc#(SDhAIRw_9hEP6F% zL=Iw(XPKvh)X_E9yzmf0Gwb?$_3F*e(mD`$4#xSDRvTB{PColp_;hwa}L zjF6X!KAhkkHF){Bl;y{qeYfa|GQye`8V}Ga@)D*WVAZr!y_YJ{m{=WBj|)1c(zZU=AISGKNHUK1g%Yojv_JM&=uATfbRIRgIh zNy#{tz>~Ip;J~hac=PS_-jy+9lGSIi(us=ID_3a?(MmT6-`^T_&qfiHVDn(e4%}~( zfPu2<92g49gYODrG2zyN2VXOU!Q;y)lgouSY4B}aTmlB^V;lte&zv@cs|?zu3e^AZ z)Q45x1;_2VZhCvoQZ9)4T|XmszAz*#@}FV!zo(UMdBavWs0Q_jb>*qs9UTOUni4M$ zUJS*=#{2Cb97OEjKhRDfabnqWkJr)Rezv3x0vhYLzjjN|oOrQzpB`>D#ZUdXn7#JY z?5aVMK~g;>he4w?IV<(5yY5P}GySc$d+AS_{Gl1U?iR#t*0M|8=eNXF838vmSkJ8+ zlq8?C%EyJeznuTR&PzMB$$7m`QCeoqBxzx69@J>T!OK10md2ej9d_ooV9mJ^CfbW? zLYqcZZ{OOw-gsrst5XHN7v(&ZTXb9WLdNKa6Ig}2)>Y0Z2Sc#_hV$cp#rDRW z8T0s}Mt|DGAz4A5fht*RkBNt_pZt4ezbjS?Bnl^3AYgLO;JB5Uk?+l;oD);x1-B*Zip-+Ue7jb|G5shvjjN&*U(9ap8!a}f9}Uz_k0Y9&6uRRB->}}3C75Ys~B(L Q&orX(^e4Fg2ZhG>g#Z8m diff --git a/executor/tests/ethrex_bench_8.bin b/executor/tests/ethrex_bench_8.bin deleted file mode 100644 index b5790e9492ee904614ed07b23ff6ca560846206d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21410 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 Rq8}Qd3;O=1TghMG{txSn0v`YX diff --git a/prover/src/recursion.rs b/prover/src/recursion.rs index 2ac9fcfc0..4bd390655 100644 --- a/prover/src/recursion.rs +++ b/prover/src/recursion.rs @@ -20,9 +20,10 @@ //! //! [`program_id`] deliberately does not fold the `ProofOptions`: the security //! level is pinned by which verifier guest the outer proof is checked against -//! (`recursion-min.elf` vs `recursion-blowup2.elf`/`recursion-blowup8.elf`, -//! fixed at build time — see [`Preset`]). A consumer must pin that outer ELF -//! too, or a 1-query `min` attestation is indistinguishable from a 128-bit one. +//! (`recursion-min.elf` vs `recursion-blowup2.elf`/`recursion-blowup4.elf`/ +//! `recursion-blowup8.elf`, fixed at build time — see [`Preset`]). A consumer +//! must pin that outer ELF too, or a 1-query `min` attestation is +//! indistinguishable from a 128-bit one. use crypto::hash::platform_keccak::PlatformKeccak256 as Keccak256; use digest::Digest; diff --git a/prover/src/tests/recursion_smoke_test.rs b/prover/src/tests/recursion_smoke_test.rs index e95b1052a..d8c294d4d 100644 --- a/prover/src/tests/recursion_smoke_test.rs +++ b/prover/src/tests/recursion_smoke_test.rs @@ -155,9 +155,22 @@ fn drive_executor( (total_cycles, start.elapsed()) } +/// The identity + output a correct in-VM run must commit — the profile +/// tests' correctness oracle. Computed host-side (trustless recompute) before +/// the guest runs, then checked against the guest's actual committed +/// attestation once profiling finishes ([`run_profile_from`]). Mirrors the +/// production consumer check ([`recursion::check_attestation`]) for the +/// monolithic path, and its continuation analog +/// ([`crate::continuation::continuation_precomputed_commitments`] + +/// [`recursion::program_id_from_elf`]) for the continuation path. +struct ExpectedAttestation { + id: [u8; 32], + output: Vec, +} + /// Shared preamble: build the blob (an `empty` inner proof under the preset's /// options), load the `recursion-.elf` verifier, and stand up an -/// executor. Returns `(elf_bytes, program, executor)`. +/// executor. Returns `(elf_bytes, program, executor, expected_attestation)`. fn setup_guest_run( label: &str, preset: Preset, @@ -165,14 +178,21 @@ fn setup_guest_run( Vec, executor::elf::Elf, executor::vm::execution::Executor, + ExpectedAttestation, ) { let root = workspace_root(); let empty_elf_bytes = read_guest_elf(&root, "empty"); let guest_elf_bytes = read_guest_elf(&root, preset.artifact_stem()); - let (_inner_proof, blob) = + let (inner_proof, blob) = prove_inner_and_encode_blob(label, &empty_elf_bytes, &[], &preset.options()); + let expected = ExpectedAttestation { + id: recursion::expected_program_id(&empty_elf_bytes, &preset.options()) + .expect("expected_program_id errored"), + output: inner_proof.public_output, + }; + let program = executor::elf::Elf::load(&guest_elf_bytes).expect("ELF load failed"); assert_ne!( program.entry_point, @@ -182,7 +202,7 @@ fn setup_guest_run( ); let executor = executor::vm::execution::Executor::new(&program, blob).expect("Executor::new failed"); - (guest_elf_bytes, program, executor) + (guest_elf_bytes, program, executor, expected) } /// Read the ethrex guest ELF (built by `make executor/program_artifacts/rust/ethrex.elf`). @@ -206,7 +226,8 @@ fn read_ethrex_fixture(root: &std::path::Path, txs: u32) -> Vec { /// on `inner_input` via continuations (`epoch_log2`-cycle epochs) and loads /// `recursion-cont-.elf` instead of the monolithic `recursion-.elf` /// — the realistic in-VM cost of verifying a real (multi-epoch) inner proof -/// instead of the `empty`-program diagnostic floor. +/// instead of the `empty`-program diagnostic floor. Returns +/// `(elf_bytes, program, executor, expected_attestation)`. fn setup_continuation_guest_run( label: &str, preset: Preset, @@ -217,6 +238,7 @@ fn setup_continuation_guest_run( Vec, executor::elf::Elf, executor::vm::execution::Executor, + ExpectedAttestation, ) { let root = workspace_root(); let guest_elf_bytes = read_guest_elf(&root, &format!("recursion-cont-{}", preset.name())); @@ -230,6 +252,26 @@ fn setup_continuation_guest_run( crate::continuation::prove_continuation(inner_elf_bytes, inner_input, epoch_log2, &opts) .expect("inner continuation prove should succeed"); eprintln!("[{label}] continuation epochs: {}", bundle.num_epochs()); + + // Ground truth, computed on the bundle before `encode_continuation_guest_input` + // consumes it: a full trustless host verify (mirrors the monolithic pipeline's + // host `verify_with_options` check) for the expected output, plus the + // continuation analog of `check_attestation`'s recompute for the expected id. + let expected_output = + crate::continuation::verify_continuation(inner_elf_bytes, &bundle, &opts) + .expect("verify_continuation errored") + .expect("continuation bundle must verify on host before profiling the guest"); + let (expected_decode, expected_pages) = + crate::continuation::continuation_precomputed_commitments(inner_elf_bytes, &bundle, &opts) + .expect("continuation_precomputed_commitments errored"); + let expected_id = + recursion::program_id_from_elf(inner_elf_bytes, &expected_decode, &expected_pages) + .expect("program_id_from_elf errored"); + let expected = ExpectedAttestation { + id: expected_id, + output: expected_output, + }; + let blob = recursion::encode_continuation_guest_input(bundle, inner_elf_bytes, &opts) .expect("recursion::encode_continuation_guest_input failed"); eprintln!("[{label}] rkyv blob: {} bytes", blob.len()); @@ -243,7 +285,7 @@ fn setup_continuation_guest_run( ); let executor = executor::vm::execution::Executor::new(&program, blob).expect("Executor::new failed"); - (guest_elf_bytes, program, executor) + (guest_elf_bytes, program, executor, expected) } /// Demangled enclosing-function name for a PC via the ELF symbol table; @@ -392,7 +434,7 @@ fn print_step_breakdown(buckets: &[u64; 7], total_cycles: u64) { /// with `detailed`, also the top-25 functions table (needs a `pc_hist` /// HashMap, so gated). fn run_profile(preset: Preset, progress_stride: usize, detailed: bool) { - let (guest_elf_bytes, program, executor) = setup_guest_run("profile", preset); + let (guest_elf_bytes, program, executor, expected) = setup_guest_run("profile", preset); run_profile_from( preset, &guest_elf_bytes, @@ -400,6 +442,7 @@ fn run_profile(preset: Preset, progress_stride: usize, detailed: bool) { executor, progress_stride, detailed, + &expected, ); } @@ -415,7 +458,7 @@ fn run_profile_continuation( progress_stride: usize, detailed: bool, ) { - let (guest_elf_bytes, program, executor) = setup_continuation_guest_run( + let (guest_elf_bytes, program, executor, expected) = setup_continuation_guest_run( "profile-block", preset, inner_elf_bytes, @@ -429,6 +472,7 @@ fn run_profile_continuation( executor, progress_stride, detailed, + &expected, ); } @@ -442,6 +486,7 @@ fn run_profile_from( mut executor: executor::vm::execution::Executor, progress_stride: usize, detailed: bool, + expected: &ExpectedAttestation, ) { use std::collections::HashMap; @@ -499,6 +544,29 @@ fn run_profile_from( }, ); + // Correctness, not just crash-freedom: read the guest's actual committed + // attestation and check it against the trusted host recompute + // (`expected`, built by `setup_guest_run`/`setup_continuation_guest_run` + // before the guest ran) — the same identity+output binding a production + // consumer checks via `check_attestation`/its continuation analog. + let committed = executor + .finish() + .expect("read committed output after execution") + .memory_values; + let (id, output) = recursion::split_attestation(&committed) + .expect("attestation too short (guest committed fewer than 32 bytes)"); + assert_eq!( + id, expected.id, + "guest attestation program_id mismatch — in-VM verify accepted a different \ + (ELF, roots) identity than the trusted host recompute" + ); + assert_eq!( + output, expected.output.as_slice(), + "attested inner public output mismatch — the in-VM verify's committed output \ + diverges from the trusted host recompute" + ); + eprintln!("[profile] guest attestation matched the trusted host recompute (program_id + inner public output) ✓"); + eprintln!(); eprintln!("============================================================"); eprintln!( @@ -787,7 +855,7 @@ fn test_recursion_execute_1query() { #[test] #[ignore = "slow: runs the in-VM STARK verifier (minutes on CI)"] fn test_recursion_step_markers_observed_in_order() { - let (_bytes, program, mut executor) = setup_guest_run("step-markers", Preset::Min); + let (_bytes, program, mut executor, _expected) = setup_guest_run("step-markers", Preset::Min); let instructions = executor::vm::execution::InstructionCache::new(&program.data) .expect("instruction cache build failed"); diff --git a/scripts/bench_recursion_scaling.sh b/scripts/bench_recursion_scaling.sh index 3242c0b06..1472efa7d 100755 --- a/scripts/bench_recursion_scaling.sh +++ b/scripts/bench_recursion_scaling.sh @@ -43,6 +43,7 @@ PRESETS="${PRESETS:-blowup2 blowup4 min}" EPOCH_LOG2="${EPOCH_LOG2:-21}" RESULTS="${1:-/tmp/recursion_scaling.txt}" WORK="$(mktemp -d /tmp/recursion_scaling.XXXXXX)" +trap 'rm -rf "$WORK"' EXIT CLI=target/release/cli ART=executor/program_artifacts/recursion @@ -71,7 +72,7 @@ for P in $PRESETS; do # Inner block cost, once per block size (cheap; repeated per preset is fine — # the count is deterministic and the run takes well under a second). - ic="$("$CLI" execute "$ETHREX" --private-input "$FIX" --cycles | awk -F': ' '/^Cycles:/{print $2; exit}')" + ic="$("$CLI" execute "$ETHREX" --private-input "$FIX" --cycles | awk -F': ' '/^Cycles:/{print $2; exit}')" || true echo "==> [${P}/${N}tx] proving inner continuation (epoch=2^${EPOCH_LOG2}) ..." >&2 rm -f /tmp/recursion_input.bin @@ -85,7 +86,7 @@ for P in $PRESETS; do tail -20 "$DLOG" >&2 continue fi - epochs="$(grep -o 'continuation epochs: [0-9]*' "$DLOG" | awk '{print $3}')" + epochs="$(grep -o 'continuation epochs: [0-9]*' "$DLOG" | awk '{print $3}')" || true BLOB="$WORK/blob_${N}tx_${P}.bin" mv /tmp/recursion_input.bin "$BLOB" sz="$(wc -c < "$BLOB" | tr -d ' ')" From 3131de9c4ed5541237e3c0b84e25304cac1263a8 Mon Sep 17 00:00:00 2001 From: Mario Rugiero Date: Fri, 17 Jul 2026 19:38:45 -0300 Subject: [PATCH 3/5] fmt --- prover/src/tests/recursion_smoke_test.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/prover/src/tests/recursion_smoke_test.rs b/prover/src/tests/recursion_smoke_test.rs index d8c294d4d..9593e316a 100644 --- a/prover/src/tests/recursion_smoke_test.rs +++ b/prover/src/tests/recursion_smoke_test.rs @@ -257,10 +257,9 @@ fn setup_continuation_guest_run( // consumes it: a full trustless host verify (mirrors the monolithic pipeline's // host `verify_with_options` check) for the expected output, plus the // continuation analog of `check_attestation`'s recompute for the expected id. - let expected_output = - crate::continuation::verify_continuation(inner_elf_bytes, &bundle, &opts) - .expect("verify_continuation errored") - .expect("continuation bundle must verify on host before profiling the guest"); + let expected_output = crate::continuation::verify_continuation(inner_elf_bytes, &bundle, &opts) + .expect("verify_continuation errored") + .expect("continuation bundle must verify on host before profiling the guest"); let (expected_decode, expected_pages) = crate::continuation::continuation_precomputed_commitments(inner_elf_bytes, &bundle, &opts) .expect("continuation_precomputed_commitments errored"); @@ -561,11 +560,14 @@ fn run_profile_from( (ELF, roots) identity than the trusted host recompute" ); assert_eq!( - output, expected.output.as_slice(), + output, + expected.output.as_slice(), "attested inner public output mismatch — the in-VM verify's committed output \ diverges from the trusted host recompute" ); - eprintln!("[profile] guest attestation matched the trusted host recompute (program_id + inner public output) ✓"); + eprintln!( + "[profile] guest attestation matched the trusted host recompute (program_id + inner public output) ✓" + ); eprintln!(); eprintln!("============================================================"); From 9ce7c7fd40b55335e9f2bd923954309c7e17456d Mon Sep 17 00:00:00 2001 From: Mario Rugiero Date: Fri, 17 Jul 2026 19:49:53 -0300 Subject: [PATCH 4/5] address feedback + unreliable time in comments --- .github/workflows/bench-verify.yml | 47 ++++++++++++++++-------------- scripts/bench_recursion_cycles.sh | 2 +- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/.github/workflows/bench-verify.yml b/.github/workflows/bench-verify.yml index e79022082..c4ca8fa15 100644 --- a/.github/workflows/bench-verify.yml +++ b/.github/workflows/bench-verify.yml @@ -26,10 +26,9 @@ jobs: startsWith(github.event.comment.body, '/bench-verify') && contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association) runs-on: [self-hosted, bench] - # Job cap. The verifier bench is ~5-6 min and the recursion measurement itself ~1 min, - # but on a cold runner the recursion BUILDS dominate: MEASURE_CLI (release cli) once, - # plus PER REF a guest build (~10-20 min) and a prover-test build for the blob dump. - # All cached in /tmp for later runs, and build-std / the host cargo target are shared + # Job cap. On a cold runner the recursion BUILDS dominate: MEASURE_CLI (release cli) + # once, plus PER REF a guest build and a prover-test build for the blob dump. All + # cached in /tmp for later runs, and build-std / the host cargo target are shared # across ref worktrees (see the recursion step's env) to keep the cold run under this # cap. The recursion step also has its own tighter timeout so a runaway build there # can't burn the whole job and lose the already-captured verifier result. @@ -46,7 +45,7 @@ jobs: await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, - body: '⏳ **Benchmark started** on the bench server. The verifier bench takes ~5 min; the recursion-guest cycle comparison then adds guest builds — a few minutes when cached, up to ~1h on a cold run. The bench server is occupied until it finishes.' + body: '⏳ **Benchmark started** on the bench server. The recursion-guest cycle comparison adds guest builds on top of the verifier bench, longer on a cold runner. The bench server is occupied until it finishes.' }); - name: Resolve PR head + pair count @@ -96,9 +95,8 @@ jobs: # 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 + # (no ABBA). Cold wall time is dominated by BUILDS: MEASURE_CLI (release cli) + # once, plus a guest build and a prover-test build for the blob dump per ref; 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 # disk shrinks). continue-on-error + `!cancelled()` keep this fully isolated from the @@ -108,11 +106,9 @@ jobs: id: recursion if: ${{ !cancelled() }} continue-on-error: true - # Fail-fast well under the 90-min job cap so a runaway recursion build can't burn + # Fail-fast well under the job cap so a runaway recursion build can't burn # the whole job and lose the already-captured verifier result (continue-on-error - # absorbs the timeout; the job still posts the verifier verdict). Sized with - # headroom over a cold shared-build run (MEASURE_CLI + 2 guest builds + 2 blob-dump - # builds). + # absorbs the timeout; the job still posts the verifier verdict). timeout-minutes: 70 env: HEAD_SHA: ${{ steps.cfg.outputs.head_sha }} @@ -128,16 +124,23 @@ jobs: # 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 + # dump and one execute per ref. Both refs' dump tests must understand + # RECURSION_DUMP_PRESET to produce a comparable blob for a given preset — until + # `origin/main` merges the preset-aware dump test, it can only ever dump `min` + # options, so every blowup2/blowup4 attempt against it would fail the same way. + # Check that support once, up front, instead of attempting (and failing) each + # preset in turn. + if git grep -q RECURSION_DUMP_PRESET origin/main -- prover/src/tests/ 2>/dev/null; then + 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 + else + { echo; echo "_(blowup2/blowup4 full-query regimes need \`origin/main\` to have the preset-aware dump test (RECURSION_DUMP_PRESET) — not merged yet, so only \`min\` is compared for this PR.)_"; } >> /tmp/recursion_result.txt + fi - name: Post result if: always() diff --git a/scripts/bench_recursion_cycles.sh b/scripts/bench_recursion_cycles.sh index c6ae76058..2235c9158 100755 --- a/scripts/bench_recursion_cycles.sh +++ b/scripts/bench_recursion_cycles.sh @@ -206,7 +206,7 @@ measure_ref() { # 2a. Build the recursion guest ELF(s) (+ empty.elf inner program). GUEST_TARGET_DIR, # when set, shares the RV64 build dir across ref worktrees (reuses build-std). - echo "==> [$role] make compile-recursion-elfs @ $sha8 (this can take 10-20 min the first time) ..." >&2 + echo "==> [$role] make compile-recursion-elfs @ $sha8 (slow the first time) ..." >&2 local glog="$WORK/build_guest_${sha8}.log" local -a make_args=(compile-recursion-elfs) if [ -n "${GUEST_TARGET_DIR:-}" ]; then From ed9d34f1f05b822a27164b7dde7fbebe3356fb51 Mon Sep 17 00:00:00 2001 From: Mario Rugiero Date: Fri, 17 Jul 2026 19:52:47 -0300 Subject: [PATCH 5/5] feedback --- bench_vs/lambda/recursion/src/main.rs | 9 ++++----- prover/src/tests/recursion_smoke_test.rs | 8 ++++++++ scripts/bench_recursion_scaling.sh | 20 ++++++++++++++------ 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/bench_vs/lambda/recursion/src/main.rs b/bench_vs/lambda/recursion/src/main.rs index 9e190ad14..116ba54fd 100644 --- a/bench_vs/lambda/recursion/src/main.rs +++ b/bench_vs/lambda/recursion/src/main.rs @@ -16,7 +16,7 @@ //! `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 +//! `recursion::verify_continuation_and_attest` — same trust model; the //! bundle is materialized with one rkyv deserialize pass (zero-copy epoch //! verify is follow-up work). //! @@ -96,10 +96,9 @@ pub fn main() -> ! { .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"); + let attestation = lambda_vm_prover::recursion::verify_continuation_and_attest(blob, &options) + .expect("verify errored") + .expect("inner continuation proof failed verification"); lambda_vm_syscalls::syscalls::commit(&attestation); lambda_vm_syscalls::syscalls::sys_halt(); diff --git a/prover/src/tests/recursion_smoke_test.rs b/prover/src/tests/recursion_smoke_test.rs index 9593e316a..1fe7b9c91 100644 --- a/prover/src/tests/recursion_smoke_test.rs +++ b/prover/src/tests/recursion_smoke_test.rs @@ -1000,6 +1000,14 @@ fn test_dump_recursion_input() { let blob = match std::env::var("RECURSION_DUMP_EPOCH_LOG2") { Ok(s) => { + // No recursion-cont-blowup8.elf is built (RECURSION_CONT_PRESETS stops + // at blowup4), so this blob would have no guest to verify it. + assert_ne!( + preset, + Preset::Blowup8, + "RECURSION_DUMP_PRESET=blowup8 has no recursion-cont-blowup8.elf guest; \ + continuation mode only supports min|blowup2|blowup4" + ); let epoch_log2: u32 = s .parse() .unwrap_or_else(|e| panic!("bad RECURSION_DUMP_EPOCH_LOG2 '{s}': {e}")); diff --git a/scripts/bench_recursion_scaling.sh b/scripts/bench_recursion_scaling.sh index 1472efa7d..11a3d67f5 100755 --- a/scripts/bench_recursion_scaling.sh +++ b/scripts/bench_recursion_scaling.sh @@ -17,8 +17,8 @@ # Usage: scripts/bench_recursion_scaling.sh [RESULTS_FILE=/tmp/recursion_scaling.txt] # Env: # TXS="1 4 8 16" block sizes (transfers); fixtures are read from -# executor/tests/ethrex_bench_.bin (committed for -# 1/4/8/16) and generated via tooling/ethrex-fixtures +# executor/tests/ethrex_bench_.bin (only _4 is +# committed) and generated via tooling/ethrex-fixtures # when missing. # PRESETS="blowup2 blowup4 min" verifier presets, most important first. # EPOCH_LOG2=21 inner continuation epoch size (log2 cycles). @@ -70,9 +70,12 @@ for P in $PRESETS; do tooling/ethrex-fixtures/target/release/ethrex-fixtures "$N" "$FIX" distinct >&2 fi - # Inner block cost, once per block size (cheap; repeated per preset is fine — - # the count is deterministic and the run takes well under a second). - ic="$("$CLI" execute "$ETHREX" --private-input "$FIX" --cycles | awk -F': ' '/^Cycles:/{print $2; exit}')" || true + # Inner block cost, once per block size (cheap; deterministic). + if ! ic="$("$CLI" execute "$ETHREX" --private-input "$FIX" --cycles | awk -F': ' '/^Cycles:/{print $2; exit}')" || [ -z "$ic" ]; then + echo "txs=$N preset=$P inner_cycles=FAILED" >> "$RESULTS" + echo "ERROR: [${P}/${N}tx] inner cycle measurement failed for $FIX" >&2 + continue + fi echo "==> [${P}/${N}tx] proving inner continuation (epoch=2^${EPOCH_LOG2}) ..." >&2 rm -f /tmp/recursion_input.bin @@ -86,7 +89,12 @@ for P in $PRESETS; do tail -20 "$DLOG" >&2 continue fi - epochs="$(grep -o 'continuation epochs: [0-9]*' "$DLOG" | awk '{print $3}')" || true + epochs="$(grep -o 'continuation epochs: [0-9]*' "$DLOG" | awk '{print $3}')" + if [ -z "$epochs" ]; then + echo "txs=$N preset=$P inner_cycles=$ic EPOCHS_PARSE_FAILED" >> "$RESULTS" + echo "ERROR: [${P}/${N}tx] could not parse epoch count from $DLOG" >&2 + continue + fi BLOB="$WORK/blob_${N}tx_${P}.bin" mv /tmp/recursion_input.bin "$BLOB" sz="$(wc -c < "$BLOB" | tr -d ' ')"