From d381499dc86622b0025765a1c159940c09b3c914 Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Mon, 3 Aug 2026 16:31:47 -0300 Subject: [PATCH 01/27] Bump ethrex to 4f658c2b, regenerate fixtures --- Makefile | 2 +- crypto/ethrex-crypto/Cargo.lock | 68 +------------- crypto/ethrex-crypto/Cargo.toml | 2 +- executor/programs/rust/ethrex/Cargo.lock | 46 ++++++---- executor/programs/rust/ethrex/Cargo.toml | 11 ++- executor/tests/README.md | 10 +-- executor/tests/ethrex_10_transfers.bin | Bin 14671 -> 13474 bytes executor/tests/ethrex_bench_4.bin | Bin 17371 -> 16174 bytes executor/tests/ethrex_empty_block.bin | Bin 9723 -> 8526 bytes executor/tests/ethrex_simple_tx.bin | Bin 12745 -> 11548 bytes tooling/ethrex-block-converter/Cargo.lock | 66 ++++++++------ tooling/ethrex-block-converter/Cargo.toml | 16 ++-- tooling/ethrex-block-converter/README.md | 41 +++++++-- tooling/ethrex-block-converter/src/main.rs | 32 +++++-- tooling/ethrex-fixtures/Cargo.lock | 100 +++++++++------------ tooling/ethrex-fixtures/Cargo.toml | 10 +-- tooling/ethrex-tests/Cargo.lock | 66 +++++--------- tooling/ethrex-tests/Cargo.toml | 2 +- tooling/ethrex-tests/tests/ethrex.rs | 10 ++- 19 files changed, 225 insertions(+), 257 deletions(-) diff --git a/Makefile b/Makefile index 25dce43de..098a04296 100644 --- a/Makefile +++ b/Makefile @@ -308,7 +308,7 @@ test-rust: compile-programs-rust ETHREX_REAL_BLOCK_NETWORK := mainnet ETHREX_REAL_BLOCK := 25368371 ETHREX_REAL_BLOCK_FIXTURE_URL := https://github.com/yetanotherco/lambda_vm/releases/download/bench-fixtures-v1/ethrex_mainnet_25368371.bin -ETHREX_REAL_BLOCK_FIXTURE_SHA256 := 61eba49b6b254f4a05def5a47b08a21ae3eee56f0d37bcd7b3a24b0cc1e4a300 +ETHREX_REAL_BLOCK_FIXTURE_SHA256 := 0a301731b84515260c2ad7779fef3e4ef8b2424fbcc055ee2f5a224bc88120ab # The block's source cache, hosted in the same release. Only `regen-real-block-fixture` # reads it — the converter's TESTS use a different, upstream-pinned cache (below). ETHREX_REAL_BLOCK_CACHE_URL := https://github.com/yetanotherco/lambda_vm/releases/download/bench-fixtures-v1/cache_mainnet_25368371.json diff --git a/crypto/ethrex-crypto/Cargo.lock b/crypto/ethrex-crypto/Cargo.lock index ec809fff9..8747b05a0 100644 --- a/crypto/ethrex-crypto/Cargo.lock +++ b/crypto/ethrex-crypto/Cargo.lock @@ -168,18 +168,6 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" -[[package]] -name = "bitvec" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - [[package]] name = "block-buffer" version = "0.10.4" @@ -189,19 +177,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bls12_381" -version = "0.8.0" -source = "git+https://github.com/lambdaclass/bls12_381?branch=expose-affine-constructors#78cad0378b17fc3157b83f514be192bf46edf9a1" -dependencies = [ - "digest", - "ff", - "group", - "pairing", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "byteorder" version = "1.5.0" @@ -391,15 +366,13 @@ dependencies = [ [[package]] name = "ethrex-crypto" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "ark-bn254", "ark-ec", "ark-ff", - "bls12_381", "ethereum-types", - "ff", "hex-literal", "k256", "num-bigint", @@ -416,7 +389,6 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "bitvec", "rand_core 0.6.4", "subtle", ] @@ -438,12 +410,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - [[package]] name = "generic-array" version = "0.14.9" @@ -636,15 +602,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "pairing" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" -dependencies = [ - "group", -] - [[package]] name = "paste" version = "1.0.15" @@ -703,12 +660,6 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - [[package]] name = "rand" version = "0.8.6" @@ -919,12 +870,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "thiserror" version = "1.0.69" @@ -1031,15 +976,6 @@ version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - [[package]] name = "zerocopy" version = "0.8.52" diff --git a/crypto/ethrex-crypto/Cargo.toml b/crypto/ethrex-crypto/Cargo.toml index ea6c91074..5310c00b0 100644 --- a/crypto/ethrex-crypto/Cargo.toml +++ b/crypto/ethrex-crypto/Cargo.toml @@ -18,7 +18,7 @@ license = "MIT OR Apache-2.0" # Defines the `Crypto` trait, `CryptoError`, and `keccak::keccak_hash`. Same rev # + `default-features = false` as the guest's ethrex-crypto, so feature # unification adds nothing to the guest build (no C secp256k1 / malachite / kzg). -ethrex-crypto = { git = "https://github.com/lambdaclass/ethrex.git", rev = "156cb8d6a3974f411d71622eecd1b249ee37ff1c", package = "ethrex-crypto", default-features = false } +ethrex-crypto = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-crypto", default-features = false } # Pinned to the exact 0.13.4 ethrex uses so the guest resolves a single k256 # (a version split would make `FieldElement`/`Scalar` incompatible types). # `expose-field` is required by the x-only reconstruction. diff --git a/executor/programs/rust/ethrex/Cargo.lock b/executor/programs/rust/ethrex/Cargo.lock index e1674f74f..df0a1bb6f 100644 --- a/executor/programs/rust/ethrex/Cargo.lock +++ b/executor/programs/rust/ethrex/Cargo.lock @@ -721,8 +721,8 @@ dependencies = [ [[package]] name = "ethrex-common" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "crc32fast", @@ -749,17 +749,16 @@ dependencies = [ [[package]] name = "ethrex-crypto" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "ark-bn254", "ark-ec", "ark-ff", - "bls12_381", "ethereum-types", - "ff", "hex-literal", "k256", + "malachite", "num-bigint", "p256", "ripemd", @@ -770,9 +769,10 @@ dependencies = [ [[package]] name = "ethrex-guest-program" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ + "bls12_381", "bytes", "ethereum-types", "ethrex-common", @@ -780,6 +780,7 @@ dependencies = [ "ethrex-l2-common", "ethrex-rlp", "ethrex-vm", + "ff", "hex", "k256", "lambda-vm-syscalls", @@ -791,8 +792,8 @@ dependencies = [ [[package]] name = "ethrex-l2-common" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "ethereum-types", @@ -810,8 +811,8 @@ dependencies = [ [[package]] name = "ethrex-levm" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "derive_more", @@ -827,8 +828,8 @@ dependencies = [ [[package]] name = "ethrex-rlp" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "ethereum-types", @@ -837,8 +838,8 @@ dependencies = [ [[package]] name = "ethrex-trie" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "anyhow", "bytes", @@ -846,18 +847,19 @@ dependencies = [ "ethereum-types", "ethrex-crypto", "ethrex-rlp", - "lazy_static", + "hashbrown 0.15.5", "rayon", "rkyv", "rustc-hash", "serde", + "spin", "thiserror 2.0.18", ] [[package]] name = "ethrex-vm" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "derive_more", @@ -2047,6 +2049,12 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" + [[package]] name = "spki" version = "0.7.3" diff --git a/executor/programs/rust/ethrex/Cargo.toml b/executor/programs/rust/ethrex/Cargo.toml index 4922712dd..58d63cd5f 100644 --- a/executor/programs/rust/ethrex/Cargo.toml +++ b/executor/programs/rust/ethrex/Cargo.toml @@ -25,13 +25,16 @@ lambda-vm-ethrex-crypto = { path = "../../../../crypto/ethrex-crypto" } # crypto defaults); ethrex's own `LambdaVmCrypto` compiles unused — we inject our # own. KZG is NOT linked under this feature, so the point-evaluation precompile # (0x0a) is unsupported — see src/main.rs. -ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "156cb8d6a3974f411d71622eecd1b249ee37ff1c", package = "ethrex-guest-program", default-features = false, features = ["lambdavm"] } +ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-guest-program", default-features = false, features = ["lambdavm"] } # Exact pin: must match the fixture writer (tooling/ethrex-fixtures) and the # executor test reader so the rkyv ProgramInput layout stays consistent. rkyv = { version = "=0.8.16", features = ["std", "unaligned"] } -# `ethrex-guest-program`'s `lambdavm` feature pins `lambda-vm-syscalls` to an -# older commit. Override it with our working-tree copy so the guest links our -# current syscalls (keccak_permute + the Print-ecall no-op fix). +# `ethrex-guest-program`'s `lambdavm` feature git-deps `lambda-vm-syscalls` at a +# pinned commit of THIS repo, which upstream bumps on its own schedule. Override it +# with the working-tree copy so the guest always links the same syscalls as the rest +# of the repo — including uncommitted local edits — rather than whatever commit +# ethrex last pinned. (At the current ethrex rev the two happen to coincide; this +# override is what keeps that coincidence from being load-bearing.) [patch."https://github.com/yetanotherco/lambda_vm.git"] lambda-vm-syscalls = { path = "../../../../syscalls" } diff --git a/executor/tests/README.md b/executor/tests/README.md index fdbf47bcb..ceee3c58d 100644 --- a/executor/tests/README.md +++ b/executor/tests/README.md @@ -15,7 +15,7 @@ merges to `main`): ```text https://github.com/lambdaclass/ethrex.git -156cb8d6a3974f411d71622eecd1b249ee37ff1c +4f658c2b3d10e3f21d35ce546870f55ca3f940fc ``` ### Generation @@ -41,15 +41,15 @@ Known fixtures: ```text ethrex_empty_block.bin - sha256: d3e594f07cc74e4ddc9db9e9db220a65a2d2e578b619fc3ce06e346007b3ca43 + sha256: ca7454142f13db5d04356366e3917d264cbea709ddd1b5e6526720dbaa12064d contents: stateless ethrex empty block ProgramInput (0 transactions) ethrex_simple_tx.bin - sha256: 15e3b3efa434186682537755d828ac8bbdde4be3fc7cbe34f26687b618a6c6ab + sha256: 29c1607297d21d88accb33767c8e03eb6536d746e6b2399930afdc9d67f1fe3f contents: stateless ethrex block with one plain ETH transfer transaction ethrex_10_transfers.bin - sha256: 38901ee4d40b99cf0aa7f642a92f0fc8db76d974bf43033a1673839020c3c28e + sha256: d04cfed35bd16c8248ab8ebf2f3ca2ff01c08269271b17e5d5db3b1f22ea03ad contents: stateless ethrex block with ten plain ETH transfer transactions ``` @@ -58,7 +58,7 @@ ethrex_10_transfers.bin The blocks above are synthetic (N plain ETH transfers over a small genesis). For a representative workload — real contract execution, real trie depth, real bytecode — `make ethrex-real-block-fixture` downloads -`ethrex_mainnet_25368371.bin` (1,110,156 B) from the `bench-fixtures-v1` release +`ethrex_mainnet_25368371.bin` (1,110,165 B) from the `bench-fixtures-v1` release and verifies it against `ETHREX_REAL_BLOCK_FIXTURE_SHA256` in the Makefile before moving it into place. It is gitignored rather than committed, so the checksum lives next to the URL in the Makefile rather than in the table above (the checksum diff --git a/executor/tests/ethrex_10_transfers.bin b/executor/tests/ethrex_10_transfers.bin index 8b6c89182dff52ad084c2a5fd0af30b9357ffd07..dd9303cfe4167942b6d264c42058a2ddc6c3c544 100644 GIT binary patch delta 160 zcmX?Kv?y~!x&bpo?~}^f4TGjKMRn~yT3WyoRM*|gC^JHx8@v^1uQrwXIiXb`u`hfiNRz8E)D=H C)J6vY delta 440 zcmZ3KdA?{vy1`@x4Hbn+->MnE9y~6Yy!V+#@?D{`TaBLCaGY9Jm$)>1x~SSzrp<=3 z0*sRb#5)xkp4|QUt)Q@CPvH@VEH1u$)#dhv(;4z7Eacd~BsWFl?f=Pw!b+3hidefa zGO&Bie0k1Zp4nD)XLm!F$Q7^Wfx*n&%WW|EXMGU9!!gcUxv;tU0fT!PR4T=U1|)Z#{ol zgs0H^)?`Hy6_-*rv*W^@vWxvqCaH3VEttRAea=Jn(z$wXTP=3Xzm)ozk>UEJdvCOp zl|-UW=SJn<{Nn%rj0_A6b~pb2X93bXZf#y@ c#K<_=fQw`DZZ6KrdyLmGar^=5Gyn+!0D^0|W&i*H diff --git a/executor/tests/ethrex_bench_4.bin b/executor/tests/ethrex_bench_4.bin index 45fe930380e9f02aedaa1aeb681926704d058346..6b716bfc9002715b7c010a4d17072122742f5b75 100644 GIT binary patch delta 176 zcmccJ&bY2_!)GgIhQC)PKh%()eA?cX>GUly)6hYgDfSVNslc@2Iap-6gDcaHH(-$+ z@4-~@XRw5$qcYQ+A0QFN50gI{DcCSFOf|p7{hP%+Y_iLMh%%ZEZf`i9A%DU`j{Qq=Q#9WGpX?~2G}%_t z#+iZLYv#*y_VUcOsyn+Ix=eP~?Gov4cZ_@}cO^AW^ps5^BZH$@&+O72Q~yRuKYt`8 zs(x40Iim5hNOs`f`0Rz!oty_j7Ar9_X#P{VzPeVrFGMN0E{7^$4XrbKg{U5eIuAFgR-t39P zf#7Q%_7cBVtn2<lvuq+cp>tsVErO9sB2+F2@VWn4>6(*Ge zld&1hlkgJFApqpv`Sx&ec)W4MKxzsOZzZQcF_!>ukf8{_q-|s#83~@`^ld%;f zM0Ex>#}kPiqfa!FB@0%dpR-4t;Rkh{F71UlxS!Hx;{gEIlHTkqV^1HsnCSQ`Sg782YHLi}QRTW?N!0 zna7iH3Ph6-6*~g}&+3zK86lH59YX@v?~`yDA(NdQLjv9OlW-X!lLH>h0bG;e9U_zQ z9v}k%&i#`>4nSt6{Rm_#Fj;&Di?r%>GQIo(X6oxJ zPiEp(o?NQ0V8h5T)%+IsZx-{g$u7?xR%=M+FSp^pa#j1uuGzcaZM^m8_?8EM|NmzI rg0olu|7Qf!U+(<>&jO_L?r*-R&&W7AftzdcWrICT|9?-OXlMZdNMt_< delta 437 zcmbOebuxLwLygG_{K|?9lfG3mem!_xGI{SajpVyRXSW(Xv*9?ktS)hB_;gXVsZ5gv z#a01TzE@;;a`)%Ag2IYDg-0B+xcKr_m)jdoXULzhkYoRn+!T$s|0gHP#XGTk&3t*z zUY^-jb!T@&m&wk$T_XMMj*$=LuB66^p0Y_~WN;MgnO(YL>fb2o=Z~aB)$fWrM>Jj* z$qw8bpS@7Jlk*_dLZH($|EXMGU9!!gcUxv;tU0fT!PR4T=U1|)Z#{olgs0H^*5r@e zDz2q$X2*p)Wf%LKOj6|zTQGmK`<#dDrE~S(wp#3%e<}4bBg6Gc_ugnHD~Uv%&Y8NK zvt2+YrbW)=v4pGHo`CwP(_(xXO#V$)l$2LuWRSbP|HIbDl{3!En>}$j5PZ$UUgFn^ zb={w96w{)U3}+sn%qZ$I*+I~T>H2+;j>*iLu1rgxf|;3`%1q{OflLLarcYpz^O~JZ z#$Uk9N-brkn4e(gUM(dihI5k(c@=;LaR`ZD{QsYkfq}v9#{d5;Kzhfm&C=S8jFUHL Ub57RN*}}x}2dK+va-TLE0MX2`#sB~S diff --git a/tooling/ethrex-block-converter/Cargo.lock b/tooling/ethrex-block-converter/Cargo.lock index a8268a857..ca8f6b2cc 100644 --- a/tooling/ethrex-block-converter/Cargo.lock +++ b/tooling/ethrex-block-converter/Cargo.lock @@ -883,6 +883,7 @@ version = "0.1.0" dependencies = [ "ethrex-common", "ethrex-config", + "ethrex-crypto", "ethrex-guest-program", "k256", "lambda-vm-ethrex-crypto", @@ -894,8 +895,8 @@ dependencies = [ [[package]] name = "ethrex-blockchain" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "crossbeam", @@ -916,8 +917,8 @@ dependencies = [ [[package]] name = "ethrex-common" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "crc32fast", @@ -944,8 +945,8 @@ dependencies = [ [[package]] name = "ethrex-config" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "ethrex-common", "ethrex-p2p", @@ -956,16 +957,14 @@ dependencies = [ [[package]] name = "ethrex-crypto" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "ark-bn254", "ark-ec", "ark-ff", - "bls12_381", "c-kzg", "ethereum-types", - "ff", "hex-literal", "k256", "malachite", @@ -979,9 +978,10 @@ dependencies = [ [[package]] name = "ethrex-guest-program" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ + "bls12_381", "bytes", "ethereum-types", "ethrex-common", @@ -989,6 +989,7 @@ dependencies = [ "ethrex-l2-common", "ethrex-rlp", "ethrex-vm", + "ff", "hex", "k256", "lambda-vm-syscalls", @@ -1000,8 +1001,8 @@ dependencies = [ [[package]] name = "ethrex-l2-common" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "ethereum-types", @@ -1019,8 +1020,8 @@ dependencies = [ [[package]] name = "ethrex-levm" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "derive_more", @@ -1036,8 +1037,8 @@ dependencies = [ [[package]] name = "ethrex-metrics" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "ethrex-common", "serde", @@ -1048,8 +1049,8 @@ dependencies = [ [[package]] name = "ethrex-p2p" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "aes", "aes-gcm", @@ -1090,8 +1091,8 @@ dependencies = [ [[package]] name = "ethrex-rlp" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "ethereum-types", @@ -1100,8 +1101,8 @@ dependencies = [ [[package]] name = "ethrex-storage" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "anyhow", "bytes", @@ -1122,8 +1123,8 @@ dependencies = [ [[package]] name = "ethrex-trie" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "anyhow", "bytes", @@ -1131,18 +1132,19 @@ dependencies = [ "ethereum-types", "ethrex-crypto", "ethrex-rlp", - "lazy_static", + "hashbrown 0.15.5", "rayon", "rkyv", "rustc-hash", "serde", + "spin", "thiserror 2.0.19", ] [[package]] name = "ethrex-vm" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "derive_more", @@ -2725,6 +2727,12 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" + [[package]] name = "spki" version = "0.7.3" diff --git a/tooling/ethrex-block-converter/Cargo.toml b/tooling/ethrex-block-converter/Cargo.toml index f61e68a68..87550cd8a 100644 --- a/tooling/ethrex-block-converter/Cargo.toml +++ b/tooling/ethrex-block-converter/Cargo.toml @@ -9,8 +9,13 @@ edition = "2024" [dependencies] # Pinned to the SAME ethrex rev as the guest (open LambdaVM-backend PR branch) # so the generated ProgramInput rkyv layout matches what the guest deserializes. -ethrex-common = { git = "https://github.com/lambdaclass/ethrex.git", rev = "156cb8d6a3974f411d71622eecd1b249ee37ff1c", package = "ethrex-common", default-features = false } -ethrex-config = { git = "https://github.com/lambdaclass/ethrex.git", rev = "156cb8d6a3974f411d71622eecd1b249ee37ff1c", package = "ethrex-config", default-features = false } +ethrex-common = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-common", default-features = false } +ethrex-config = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-config", default-features = false } +# Only for `NativeCrypto`, the host `Crypto` impl that `into_execution_witness` +# now takes. Nothing re-exports it, and this crate's own guest-crypto impl is a +# dev-dependency, so the bin build needs its own line. Already in the graph via +# `ethrex-config` → `ethrex-p2p`, so this adds no compilation. +ethrex-crypto = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-crypto", default-features = false } # Matches the guest's own declaration (executor/programs/rust/ethrex/Cargo.toml). # # CAVEAT: this line alone does NOT reproduce the guest's precompile surface. The @@ -21,7 +26,7 @@ ethrex-config = { git = "https://github.com/lambdaclass/ethrex.git", rev = "156c # `ark-ff/asm` BN254) that the guest does not have; verify with # `cargo tree -e features -i ethrex-crypto`. Closing that gap means dropping # `ethrex-config` and sourcing `ChainConfig` another way — see the README. -ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "156cb8d6a3974f411d71622eecd1b249ee37ff1c", package = "ethrex-guest-program", default-features = false, features = ["lambdavm"] } +ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-guest-program", default-features = false, features = ["lambdavm"] } # Exact pin: the fixture writer and the guest/executor readers must agree on the # rkyv layout. Keep this in sync with the guest @@ -55,7 +60,8 @@ lambda-vm-ethrex-crypto = { path = "../../crypto/ethrex-crypto" } # one can stay a dev-dependency without forcing the rebuild described above. sha2 = "0.10" -# `ethrex-guest-program`'s `lambdavm` feature pins `lambda-vm-syscalls` to an -# older commit; override it with our working-tree copy, as the guest does. +# `ethrex-guest-program`'s `lambdavm` feature git-deps `lambda-vm-syscalls` at a +# pinned commit of this repo; override it with the working-tree copy, as the guest +# does, so both resolve the same syscalls. [patch."https://github.com/yetanotherco/lambda_vm.git"] lambda-vm-syscalls = { path = "../../syscalls" } diff --git a/tooling/ethrex-block-converter/README.md b/tooling/ethrex-block-converter/README.md index 17cf8a2f8..268d6a0fb 100644 --- a/tooling/ethrex-block-converter/README.md +++ b/tooling/ethrex-block-converter/README.md @@ -88,11 +88,18 @@ Artifacts live in the **[`bench-fixtures-v1`][release]** release on | asset | sha256 | read by | |---|---|---| -| `ethrex_mainnet_25368371.bin` | `61eba49b…` | every benchmark (**current default**) | +| `ethrex_mainnet_25368371.bin` | `0a301731…` | every benchmark (**current default**) | | `cache_mainnet_25368371.json` | `7aa88a5f…` | `regen-real-block-fixture` | | `ethrex_mainnet_25453112.bin` | `0298663d…` | alternate candidate | | `cache_mainnet_25453112.json` | `20ffbbc1…` | alternate candidate | +> **Re-upload pending.** The `0a301731…` above is what the Makefile verifies against +> after the ethrex `4f658c2b` bump, which moved the rkyv layout and so changed the +> fixture bytes. The hosted asset still carries the pre-bump `61eba49b…` until it is +> replaced in the release, so `make ethrex-real-block-fixture` fails its checksum on a +> clean checkout in the meantime — loudly, which is the designed failure mode. Only the +> `.bin` is stale; the caches are inputs and are unaffected. + Each block has two assets: the fixture and the **cache** it was converted from (`make ethrex-real-block-cache`, ~2 MB, same verify-then-move contract). Only `regen-real-block-fixture` reads the cache. Note it is *not* the cache the @@ -130,12 +137,15 @@ cargo run --release -- Output is deterministic for a given cache file: ```text -wrote ../../executor/tests/ethrex_mainnet_25368371.bin (1110156 bytes): 1 block(s) \ +wrote ../../executor/tests/ethrex_mainnet_25368371.bin (1110165 bytes): 1 block(s) \ from mainnet starting at #25368371, 29 transaction(s), 2428684 gas ``` -Verified: regenerating from the hosted cache reproduces `61eba49b…` byte for byte, -which is what proves the hosted `.bin` and the hosted cache describe the same block. +Verified at the current ethrex rev: regenerating from the hosted cache reproduces +`0a301731…` byte for byte, and the result passes `test_ethrex_real_block_native` — +which is what proves the hosted cache and the fixture the Makefile expects describe +the same block. (Byte count and digest are both rev-dependent: the `4f658c2b` bump +moved them from 1,110,156 B / `61eba49b…`, the bytes still hosted in the release.) The converter's `conversion_is_reproducible` test enforces the same property, but against its own pinned block rather than this one — see [Validation](#validation). @@ -350,6 +360,15 @@ current default), so sizing a candidate from its gas mispredicts cost. **Current default — main-vintage (merge `fdb92f67`, main @ `9ccdaf2`):** +These figures were measured on the pre-bump fixture (`61eba49b…`, 1,110,156 B) and are +left as measured rather than restamped. The ethrex `4f658c2b` bump changed the fixture +bytes, so they are a baseline for a workload that no longer exists byte-for-byte. + +Post-bump CPU counterparts, measured ABBA on `vm-benchmarks-1` at the same epoch 2^22 +(see `ETHREX_BUMP_CYCLE_RESULTS.md`): **45,074,552 cycles** (−11.24%), **142.37 s** CPU +prove (−10.87%), **936.7 MB** proof (−12.22%), peak RSS flat at ~48 GB. The GPU column +has no post-bump counterpart yet. + | block | gas | cycles | GPU prove (RTX 5090) | CPU prove | proof | fixture | |---|---|---|---|---|---|---| | **mainnet 25368371** | 2.43M | **50,781,557** (clang 21)
50,713,534 (clang 18) | **59.87 s** @ epoch 2^22 | **158.8 s** @ epoch 2^22 (2.65x the GPU wall) | 1.15 GB CPU / 1.12 GB GPU | 1,110,156 B, `61eba49b…` | @@ -452,10 +471,16 @@ first run after a repoint reports one-sided numbers until main republishes. ## Why the JSON and not ethrex-replay's own `.bin` `ethrex-replay` can already emit a rkyv `ProgramInput`, but it tracks ethrex -`main`, where the type has diverged from the rev our guest pins -(`156cb8d6…`): `main` has an extra `fee_configs` field, moved the type from -`l1::` to `input::`, and uses rkyv 0.8.10 against our exact `=0.8.16`. Its -binary would not deserialize in our guest. +`main` while we pin a branch off it, and the type has diverged between the two +before: against the previously pinned rev (`156cb8d6…`) `main` carried an extra +`fee_configs` field and had moved the type from `l1::` to `input::`, so replay's +binary would not deserialize in our guest at all. + +At the currently pinned rev (`4f658c2b…`, rebased onto recent `main`) the +`ProgramInput` definition matches `main`'s again, and that gap has closed for +now. What has not closed: replay resolves rkyv itself from ethrex's `^0.8.10` +rather than our exact `=0.8.16`, `main` has no `lambdavm` feature to build the +guest side against, and the next bump can reopen the type gap without warning. The cache JSON carries only `blocks` + `witness` + `network` as plain serde, so it survives that drift. This tool re-reads it with **our** pinned ethrex types diff --git a/tooling/ethrex-block-converter/src/main.rs b/tooling/ethrex-block-converter/src/main.rs index f96b03f24..545c7cc6e 100644 --- a/tooling/ethrex-block-converter/src/main.rs +++ b/tooling/ethrex-block-converter/src/main.rs @@ -5,18 +5,23 @@ //! cargo run --release -- use ethrex_common::types::Block; -use ethrex_common::types::block_execution_witness::RpcExecutionWitness; +use ethrex_common::types::block_execution_witness::{RpcExecutionWitness, decode_witness_headers}; use ethrex_config::networks::Network; +use ethrex_crypto::NativeCrypto; use ethrex_guest_program::l1::ProgramInput; use serde::Deserialize; /// The subset of `ethrex-replay`'s on-disk cache that a `ProgramInput` needs. /// /// Deliberately deserialized with *our* pinned ethrex types rather than by -/// depending on `ethrex-replay`: it tracks ethrex `main`, where `ProgramInput` -/// has diverged (extra `fee_configs` field, different module path, rkyv 0.8.10 -/// vs our `=0.8.16`), so its own `.bin` output would not deserialize in our -/// guest. This JSON is the version-tolerant interface between the two. +/// depending on `ethrex-replay`: it tracks ethrex `main`, we pin a branch off it, +/// and `ProgramInput` has diverged between the two before (it once carried an +/// extra `fee_configs` field and lived at a different module path). At the +/// currently pinned rev the type happens to match `main`'s again, but replay +/// still resolves rkyv itself from ethrex's `^0.8.10` against our exact `=0.8.16`, +/// and nothing stops the type drifting apart on the next bump. This JSON carries +/// only `blocks` + `witness` + `network` as plain serde, so it is the +/// version-tolerant interface between the two regardless. /// /// Extra fields in the file (L2 blob data, custom `chain_config`) are ignored. #[derive(Deserialize)] @@ -70,10 +75,19 @@ fn program_input_from_cache( // `into_execution_witness` rebuilds the trie structures from the flat node // list and needs the parent header, which the cache carries inside `witness`. + // Those headers are now decoded by the caller rather than inside the call, and + // the `Crypto` argument is what recomputes the block hashes the parent lookup + // matches on. `NativeCrypto` is keccak-only here, so it produces the same bytes + // as the `LambdaVmEcsmCrypto` the guest injects (whose host path is also + // software keccak) — the fixture does not depend on which one converts it. let chain_config = cache.network.get_genesis()?.config; - let witness = cache - .witness - .into_execution_witness(chain_config, summary.first_block_number)?; + let decoded_headers = decode_witness_headers(&cache.witness.headers)?; + let witness = cache.witness.into_execution_witness( + chain_config, + summary.first_block_number, + &decoded_headers, + &NativeCrypto, + )?; Ok((ProgramInput::new(cache.blocks, witness), summary)) } @@ -216,7 +230,7 @@ mod tests { .map(|b| format!("{b:02x}")) .collect(); assert_eq!( - digest, "1f7d4c4cdf9bd52472d9ebafdb4038f57a88c3c92d65c96fd86d7e323db87142", + digest, "6ffc80b25ec9ed3c203c35bdb8a4a05aff08d98b1a9c71648447bc199a552dc9", "fixture bytes changed — regenerate it and update the README checksum", ); } diff --git a/tooling/ethrex-fixtures/Cargo.lock b/tooling/ethrex-fixtures/Cargo.lock index 8d671d94c..1702880a9 100644 --- a/tooling/ethrex-fixtures/Cargo.lock +++ b/tooling/ethrex-fixtures/Cargo.lock @@ -389,19 +389,6 @@ dependencies = [ "objc2", ] -[[package]] -name = "bls12_381" -version = "0.8.0" -source = "git+https://github.com/lambdaclass/bls12_381?branch=expose-affine-constructors#78cad0378b17fc3157b83f514be192bf46edf9a1" -dependencies = [ - "digest", - "ff", - "group", - "pairing", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "blst" version = "0.3.16" @@ -481,9 +468,9 @@ dependencies = [ [[package]] name = "c-kzg" -version = "2.1.7" +version = "2.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6648ed1e4ea8e8a1a4a2c78e1cda29a3fd500bc622899c340d8525ea9a76b24a" +checksum = "38d04308254695569fdb9bfe3bacc1c91837a670d0806605eb82d63748fbd3a6" dependencies = [ "blst", "cc", @@ -988,8 +975,8 @@ dependencies = [ [[package]] name = "ethrex-blockchain" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "crossbeam", @@ -1010,8 +997,8 @@ dependencies = [ [[package]] name = "ethrex-common" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "crc32fast", @@ -1040,16 +1027,14 @@ dependencies = [ [[package]] name = "ethrex-crypto" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "ark-bn254", "ark-ec", "ark-ff", - "bls12_381", "c-kzg", "ethereum-types", - "ff", "hex-literal", "k256", "malachite", @@ -1081,8 +1066,8 @@ dependencies = [ [[package]] name = "ethrex-guest-program" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "ethereum-types", @@ -1100,8 +1085,8 @@ dependencies = [ [[package]] name = "ethrex-l2-common" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "ethereum-types", @@ -1119,8 +1104,8 @@ dependencies = [ [[package]] name = "ethrex-l2-rpc" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "axum", "bytes", @@ -1142,6 +1127,7 @@ dependencies = [ "serde_json", "thiserror 2.0.18", "tokio", + "tokio-util", "tower-http", "tracing", "tracing-subscriber", @@ -1150,8 +1136,8 @@ dependencies = [ [[package]] name = "ethrex-levm" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "derive_more", @@ -1168,8 +1154,8 @@ dependencies = [ [[package]] name = "ethrex-metrics" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "axum", "ethrex-common", @@ -1184,8 +1170,8 @@ dependencies = [ [[package]] name = "ethrex-p2p" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "aes", "aes-gcm", @@ -1228,8 +1214,8 @@ dependencies = [ [[package]] name = "ethrex-rlp" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "ethereum-types", @@ -1238,8 +1224,8 @@ dependencies = [ [[package]] name = "ethrex-rpc" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "axum", "axum-extra", @@ -1259,6 +1245,7 @@ dependencies = [ "hex-literal", "jsonwebtoken", "rand 0.8.6", + "rayon", "reqwest", "secp256k1", "serde", @@ -1277,8 +1264,8 @@ dependencies = [ [[package]] name = "ethrex-storage" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "anyhow", "bytes", @@ -1299,8 +1286,8 @@ dependencies = [ [[package]] name = "ethrex-storage-rollup" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "async-trait", "bincode", @@ -1315,8 +1302,8 @@ dependencies = [ [[package]] name = "ethrex-trie" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "anyhow", "bytes", @@ -1324,18 +1311,19 @@ dependencies = [ "ethereum-types", "ethrex-crypto", "ethrex-rlp", - "lazy_static", + "hashbrown 0.15.5", "rayon", "rkyv", "rustc-hash", "serde", + "spin", "thiserror 2.0.18", ] [[package]] name = "ethrex-vm" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "derive_more", @@ -1375,7 +1363,6 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "bitvec", "rand_core 0.6.4", "subtle", ] @@ -2541,15 +2528,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "pairing" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" -dependencies = [ - "group", -] - [[package]] name = "parity-scale-codec" version = "3.7.5" @@ -3543,6 +3521,12 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" + [[package]] name = "spki" version = "0.7.3" diff --git a/tooling/ethrex-fixtures/Cargo.toml b/tooling/ethrex-fixtures/Cargo.toml index 5e99f37d3..3320ba25b 100644 --- a/tooling/ethrex-fixtures/Cargo.toml +++ b/tooling/ethrex-fixtures/Cargo.toml @@ -9,11 +9,11 @@ edition = "2024" [dependencies] # Pinned to the SAME ethrex rev as the guest (open LambdaVM-backend PR branch) # so the generated ProgramInput rkyv layout matches what the guest deserializes. -ethrex-blockchain = { git = "https://github.com/lambdaclass/ethrex.git", rev = "156cb8d6a3974f411d71622eecd1b249ee37ff1c", package = "ethrex-blockchain" } -ethrex-storage = { git = "https://github.com/lambdaclass/ethrex.git", rev = "156cb8d6a3974f411d71622eecd1b249ee37ff1c", package = "ethrex-storage" } -ethrex-common = { git = "https://github.com/lambdaclass/ethrex.git", rev = "156cb8d6a3974f411d71622eecd1b249ee37ff1c", package = "ethrex-common" } -ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "156cb8d6a3974f411d71622eecd1b249ee37ff1c", package = "ethrex-guest-program" } -ethrex-l2-rpc = { git = "https://github.com/lambdaclass/ethrex.git", rev = "156cb8d6a3974f411d71622eecd1b249ee37ff1c", package = "ethrex-l2-rpc" } +ethrex-blockchain = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-blockchain" } +ethrex-storage = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-storage" } +ethrex-common = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-common" } +ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-guest-program" } +ethrex-l2-rpc = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-l2-rpc" } tokio = { version = "1", features = ["rt-multi-thread", "macros"] } # Exact pin: the fixture writer and the guest/executor readers must agree on the diff --git a/tooling/ethrex-tests/Cargo.lock b/tooling/ethrex-tests/Cargo.lock index 250e2411f..eafb4bdff 100644 --- a/tooling/ethrex-tests/Cargo.lock +++ b/tooling/ethrex-tests/Cargo.lock @@ -247,19 +247,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bls12_381" -version = "0.8.0" -source = "git+https://github.com/lambdaclass/bls12_381?branch=expose-affine-constructors#78cad0378b17fc3157b83f514be192bf46edf9a1" -dependencies = [ - "digest", - "ff", - "group", - "pairing", - "rand_core", - "subtle", -] - [[package]] name = "bs58" version = "0.5.1" @@ -706,8 +693,8 @@ dependencies = [ [[package]] name = "ethrex-common" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "crc32fast", @@ -736,15 +723,13 @@ dependencies = [ [[package]] name = "ethrex-crypto" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "ark-bn254", "ark-ec", "ark-ff", - "bls12_381", "ethereum-types", - "ff", "hex-literal", "k256", "malachite", @@ -759,8 +744,8 @@ dependencies = [ [[package]] name = "ethrex-guest-program" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "ethereum-types", @@ -778,8 +763,8 @@ dependencies = [ [[package]] name = "ethrex-l2-common" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "ethereum-types", @@ -797,8 +782,8 @@ dependencies = [ [[package]] name = "ethrex-levm" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "derive_more", @@ -815,8 +800,8 @@ dependencies = [ [[package]] name = "ethrex-rlp" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "ethereum-types", @@ -834,8 +819,8 @@ dependencies = [ [[package]] name = "ethrex-trie" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "anyhow", "bytes", @@ -843,18 +828,19 @@ dependencies = [ "ethereum-types", "ethrex-crypto", "ethrex-rlp", - "lazy_static", + "hashbrown 0.15.5", "rayon", "rkyv", "rustc-hash", "serde", + "spin", "thiserror 2.0.18", ] [[package]] name = "ethrex-vm" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +version = "22.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" dependencies = [ "bytes", "derive_more", @@ -885,7 +871,6 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "bitvec", "rand_core", "subtle", ] @@ -1434,15 +1419,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "pairing" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" -dependencies = [ - "group", -] - [[package]] name = "parity-scale-codec" version = "3.7.5" @@ -1951,6 +1927,12 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" + [[package]] name = "spki" version = "0.7.3" diff --git a/tooling/ethrex-tests/Cargo.toml b/tooling/ethrex-tests/Cargo.toml index f4774278d..5ff8ef39f 100644 --- a/tooling/ethrex-tests/Cargo.toml +++ b/tooling/ethrex-tests/Cargo.toml @@ -12,7 +12,7 @@ edition = "2024" executor = { path = "../../executor" } # Pinned to the SAME ethrex rev as the guest (open LambdaVM-backend PR branch) # so the native reference reads the same ProgramInput rkyv layout. -ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "156cb8d6a3974f411d71622eecd1b249ee37ff1c", package = "ethrex-guest-program" } +ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-guest-program" } # Exact pin: the fixture writer and the guest/executor readers must agree on the # rkyv layout. Keep this in sync with tooling/ethrex-fixtures and # executor/programs/rust/ethrex/Cargo.toml. diff --git a/tooling/ethrex-tests/tests/ethrex.rs b/tooling/ethrex-tests/tests/ethrex.rs index 6374f2f43..9ad1ee786 100644 --- a/tooling/ethrex-tests/tests/ethrex.rs +++ b/tooling/ethrex-tests/tests/ethrex.rs @@ -127,11 +127,13 @@ fn test_ethrex_real_block_native() { /// backend too (invalid G1 encoding), and both paths surface as /// `CryptoError::Other`, so the variant can't tell them apart. The string comes /// from `ethrex-crypto`'s `KzgError::Unimplemented`; if upstream rewords it this -/// test goes red, which is the safe direction. +/// test goes red, which is the safe direction — and it has: the 4f658c2b rev bump +/// dropped `openvm-kzg` from the sentence, so the expected text moved with it. /// /// Worth knowing why this can regress: `ethrex-crypto`'s own default feature set -/// is `["std", "kzg-rs", "secp256k1"]`, so any future dependency pulling it in -/// with defaults on restores a backend and silently removes the screen. +/// is `["std", "kzg-rs", "secp256k1", "aws-lc-rs", "blst"]`, so any future +/// dependency pulling it in with defaults on restores a backend and silently +/// removes the screen. #[test] fn no_kzg_backend_linked() { use ethrex_guest_program::crypto::{Crypto, NativeCrypto}; @@ -141,7 +143,7 @@ fn no_kzg_backend_linked() { Err(err) => format!("{err:?}"), }; assert!( - message.contains("One of features c-kzg, openvm-kzg or kzg-rs should be active"), + message.contains("One of features c-kzg or kzg-rs should be active"), "a KZG backend is linked into ethrex-tests, so test_ethrex_real_block_native no \ longer screens precompile 0x0a: {message}" ); From 4657bbbce5a2515882b9dbe82df88d88cc89facf Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Mon, 3 Aug 2026 16:33:18 -0300 Subject: [PATCH 02/27] Cover ethrex_bench_4.bin in the checksum gate --- Makefile | 3 ++- executor/tests/README.md | 12 ++++++++++++ tooling/ethrex-fixtures/update_readme_checksums.py | 4 ++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 098a04296..9fd34b10f 100644 --- a/Makefile +++ b/Makefile @@ -499,7 +499,8 @@ regen-ethrex-fixtures: cd tooling/ethrex-fixtures && \ cargo run --release -- 0 ../../executor/tests/ethrex_empty_block.bin && \ cargo run --release -- 1 ../../executor/tests/ethrex_simple_tx.bin && \ - cargo run --release -- 10 ../../executor/tests/ethrex_10_transfers.bin + cargo run --release -- 10 ../../executor/tests/ethrex_10_transfers.bin && \ + cargo run --release -- 4 ../../executor/tests/ethrex_bench_4.bin distinct $(MAKE) update-ethrex-fixture-checksums update-ethrex-fixture-checksums: diff --git a/executor/tests/README.md b/executor/tests/README.md index ceee3c58d..ae35e6135 100644 --- a/executor/tests/README.md +++ b/executor/tests/README.md @@ -30,8 +30,15 @@ cd tooling/ethrex-fixtures cargo run --release -- 0 ../../executor/tests/ethrex_empty_block.bin # empty block cargo run --release -- 1 ../../executor/tests/ethrex_simple_tx.bin # 1 transfer cargo run --release -- 10 ../../executor/tests/ethrex_10_transfers.bin # 10 transfers +cargo run --release -- 4 ../../executor/tests/ethrex_bench_4.bin distinct # recursion profile ``` +`ethrex_bench_4.bin` is the odd one out: `distinct` mode, and it is read by the +recursion profile target rather than the executor tests (see the Makefile's +`recursion-profile-block-input`). It is committed like the rest, so it is +regenerated and checksummed with them — a rev bump makes every one of these +undecodable, not just the three the executor reads. + To regenerate after an ethrex rev bump, update the `rev` in `tooling/ethrex-fixtures/Cargo.toml` (and the guest's), then run `make regen-ethrex-fixtures` from the repo root. The target rebuilds the @@ -51,6 +58,11 @@ ethrex_simple_tx.bin ethrex_10_transfers.bin sha256: d04cfed35bd16c8248ab8ebf2f3ca2ff01c08269271b17e5d5db3b1f22ea03ad contents: stateless ethrex block with ten plain ETH transfer transactions + +ethrex_bench_4.bin + sha256: bb26e6d6595f537c2fd57bfd59da6abe82241bd92690a21c2225da94d5cf507a + contents: stateless ethrex block with four plain ETH transfers, `distinct` mode + (N senders -> N recipients); read by the recursion profile target ``` ## Real-block fixtures diff --git a/tooling/ethrex-fixtures/update_readme_checksums.py b/tooling/ethrex-fixtures/update_readme_checksums.py index 76997dbe1..f9fbee7fb 100644 --- a/tooling/ethrex-fixtures/update_readme_checksums.py +++ b/tooling/ethrex-fixtures/update_readme_checksums.py @@ -12,6 +12,10 @@ "ethrex_empty_block.bin", "ethrex_simple_tx.bin", "ethrex_10_transfers.bin", + # Committed like the three above, but generated in `distinct` mode and read by + # the recursion profile target rather than the executor tests. Omitting it left + # a committed fixture that a rev bump silently staled with nothing to catch it. + "ethrex_bench_4.bin", ) From 03d3ffed5be8ecb6b1390914b343f1500cf83f8a Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Mon, 3 Aug 2026 17:58:44 -0300 Subject: [PATCH 03/27] Point the real-block fixture at the post-bump asset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rkyv layout moved with the ethrex rev, so the block's fixture did too, and the release now carries both: the pre-bump bytes under the original name and the post-bump ones under a name carrying the rev. Uploading under a new name rather than replacing the asset keeps `main` working while this branch is open — its Makefile still pins the pre-bump sha256, and a replaced asset would fail that checksum on every push, taking the published benchmark baseline with it. --- Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9fd34b10f..5adc8a44f 100644 --- a/Makefile +++ b/Makefile @@ -307,7 +307,11 @@ test-rust: compile-programs-rust # measured cost. ETHREX_REAL_BLOCK_NETWORK := mainnet ETHREX_REAL_BLOCK := 25368371 -ETHREX_REAL_BLOCK_FIXTURE_URL := https://github.com/yetanotherco/lambda_vm/releases/download/bench-fixtures-v1/ethrex_mainnet_25368371.bin +# The asset name carries the ethrex rev because the bytes are a function of it: the +# archived ProgramInput layout moves with the pin, so one block has one fixture per rev. +# Uploading under a new name rather than replacing the old one keeps `main` — which still +# expects the pre-bump sha256 — fetching its own artifact while this branch is open. +ETHREX_REAL_BLOCK_FIXTURE_URL := https://github.com/yetanotherco/lambda_vm/releases/download/bench-fixtures-v1/ethrex_mainnet_25368371_4f658c2b.bin ETHREX_REAL_BLOCK_FIXTURE_SHA256 := 0a301731b84515260c2ad7779fef3e4ef8b2424fbcc055ee2f5a224bc88120ab # The block's source cache, hosted in the same release. Only `regen-real-block-fixture` # reads it — the converter's TESTS use a different, upstream-pinned cache (below). From 55b400cf1bbd0c57b029d6081449d524da6f90d6 Mon Sep 17 00:00:00 2001 From: diegokingston Date: Mon, 10 Aug 2026 16:20:14 -0300 Subject: [PATCH 04/27] docs(ethrex): point the fixture docs at the rev-suffixed asset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Makefile fetches ethrex_mainnet_25368371_4f658c2b.bin (the suffixed upload keeps main's pre-bump checksum working while the branch is open), but the converter README's asset table still listed the un-suffixed name with the post-bump checksum next to a 'Re-upload pending' note that the suffixed upload made moot, and executor/tests/README.md named the un-suffixed asset with the post-bump byte count. Also drop the dangling ETHREX_BUMP_CYCLE_RESULTS.md reference — the numbers are quoted inline. --- executor/tests/README.md | 2 +- tooling/ethrex-block-converter/README.md | 20 +++++++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/executor/tests/README.md b/executor/tests/README.md index ae35e6135..fc6b3f8a8 100644 --- a/executor/tests/README.md +++ b/executor/tests/README.md @@ -70,7 +70,7 @@ ethrex_bench_4.bin The blocks above are synthetic (N plain ETH transfers over a small genesis). For a representative workload — real contract execution, real trie depth, real bytecode — `make ethrex-real-block-fixture` downloads -`ethrex_mainnet_25368371.bin` (1,110,165 B) from the `bench-fixtures-v1` release +`ethrex_mainnet_25368371_4f658c2b.bin` (1,110,165 B) from the `bench-fixtures-v1` release and verifies it against `ETHREX_REAL_BLOCK_FIXTURE_SHA256` in the Makefile before moving it into place. It is gitignored rather than committed, so the checksum lives next to the URL in the Makefile rather than in the table above (the checksum diff --git a/tooling/ethrex-block-converter/README.md b/tooling/ethrex-block-converter/README.md index 268d6a0fb..48c58e27b 100644 --- a/tooling/ethrex-block-converter/README.md +++ b/tooling/ethrex-block-converter/README.md @@ -88,17 +88,16 @@ Artifacts live in the **[`bench-fixtures-v1`][release]** release on | asset | sha256 | read by | |---|---|---| -| `ethrex_mainnet_25368371.bin` | `0a301731…` | every benchmark (**current default**) | +| `ethrex_mainnet_25368371_4f658c2b.bin` | `0a301731…` | every benchmark (**current default**) | | `cache_mainnet_25368371.json` | `7aa88a5f…` | `regen-real-block-fixture` | | `ethrex_mainnet_25453112.bin` | `0298663d…` | alternate candidate | | `cache_mainnet_25453112.json` | `20ffbbc1…` | alternate candidate | -> **Re-upload pending.** The `0a301731…` above is what the Makefile verifies against -> after the ethrex `4f658c2b` bump, which moved the rkyv layout and so changed the -> fixture bytes. The hosted asset still carries the pre-bump `61eba49b…` until it is -> replaced in the release, so `make ethrex-real-block-fixture` fails its checksum on a -> clean checkout in the meantime — loudly, which is the designed failure mode. Only the -> `.bin` is stale; the caches are inputs and are unaffected. +> The asset name carries the ethrex rev because the bytes are a function of it: the +> archived `ProgramInput` rkyv layout moves with the pin, so one block has one +> fixture per rev. The pre-bump bytes stay hosted under the original name +> (`ethrex_mainnet_25368371.bin`, `61eba49b…`) so older `main`s — whose Makefile +> pins that sha256 — keep fetching their own artifact. Each block has two assets: the fixture and the **cache** it was converted from (`make ethrex-real-block-cache`, ~2 MB, same verify-then-move contract). Only @@ -364,10 +363,9 @@ These figures were measured on the pre-bump fixture (`61eba49b…`, 1,110,156 B) left as measured rather than restamped. The ethrex `4f658c2b` bump changed the fixture bytes, so they are a baseline for a workload that no longer exists byte-for-byte. -Post-bump CPU counterparts, measured ABBA on `vm-benchmarks-1` at the same epoch 2^22 -(see `ETHREX_BUMP_CYCLE_RESULTS.md`): **45,074,552 cycles** (−11.24%), **142.37 s** CPU -prove (−10.87%), **936.7 MB** proof (−12.22%), peak RSS flat at ~48 GB. The GPU column -has no post-bump counterpart yet. +Post-bump CPU counterparts, measured ABBA on `vm-benchmarks-1` at the same epoch 2^22: +**45,074,552 cycles** (−11.24%), **142.37 s** CPU prove (−10.87%), **936.7 MB** proof +(−12.22%), peak RSS flat at ~48 GB. The GPU column has no post-bump counterpart yet. | block | gas | cycles | GPU prove (RTX 5090) | CPU prove | proof | fixture | |---|---|---|---|---|---|---| From d829a2cbb844f09c9d22ca6bdbaf8857c6e8859c Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Wed, 26 Aug 2026 17:10:30 -0300 Subject: [PATCH 05/27] Move the ethrex pin from 4f658c2b to 797df554, off the unmerged feat/lambdavm-prover-backend branch and onto ethrex main, and stop declaring the lambdavm feature. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Requiring that feature is what tied the guest to the backend branch, since it exists nowhere else — and that branch is 69 commits behind main and has not moved since 2026-08-04, so waiting for it to merge was costing real performance. On the real benchmark block the guest goes from 34,241,608 to 30,498,818 cycles and from 120.715 s to 110.259 s of proving time (median of 3, spread under 0.8%), dropping from 9 epochs to 8; against main's pin the cycle count falls 22.9%. No feature replaces it. ethrex's per-zkVM features are not backend selectors — each is only a list of optional crypto dependencies, and none gates any method of the Crypto trait. Measured against the sibling feature activating the widest dependency set, ethrex's precompile stress fixtures come out identical to the cycle (stress_modexp_150M 6,373,285,966 and stress_alt_bn128_150M 22,986,061,145 both ways) with a smaller ELF. What makes this guest LambdaVM never travelled through a feature: lambda-vm-syscalls, lambda-vm-ethrex-crypto and the riscv64im-lambda-vm-elf target are direct, and every run still reports its 116 ECSM and 10,659 keccak precompile calls. 797df554 is the last commit where this is only a rev change. b5271885 rewrites the guest entry point to run_stateless_guest over SSZ input, which needs a new shim and a new fixture format; and between 797df554 and that boundary there is no perf(levm) work, while the two commits that matter (#7105, #7104) are already in. Release tags do not help — v24.0.0 predates both and is diverged from main, because ethrex cuts releases from a side branch. The rkyv ProgramInput layout moved with the rev, so every committed fixture is regenerated and the converter's reproducibility digest updated, as that test asks for on a legitimate bump. scripts/set_ethrex_rev.sh moves all 11 pins together: a guest and tooling on different revs do not fail to build, they produce a fixture the guest silently misreads. The real-block fixture asset for the new rev still has to be uploaded to the bench-fixtures-v1 release before CI can fetch it. --- BUMP_797df554_RESULTS.md | 130 +++++++++++++++++ Makefile | 4 +- crypto/ethrex-crypto/Cargo.toml | 2 +- executor/programs/rust/ethrex/Cargo.lock | 157 +++++++++------------ executor/programs/rust/ethrex/Cargo.toml | 49 +++++-- executor/tests/README.md | 8 +- executor/tests/ethrex_10_transfers.bin | Bin 13474 -> 13492 bytes executor/tests/ethrex_bench_4.bin | Bin 16174 -> 16192 bytes executor/tests/ethrex_empty_block.bin | Bin 8526 -> 8544 bytes executor/tests/ethrex_simple_tx.bin | Bin 11548 -> 11566 bytes scripts/set_ethrex_rev.sh | 87 ++++++++++++ tooling/ethrex-block-converter/Cargo.lock | 129 ++++++++++------- tooling/ethrex-block-converter/Cargo.toml | 8 +- tooling/ethrex-block-converter/src/main.rs | 2 +- tooling/ethrex-fixtures/Cargo.lock | 109 ++++++++++---- tooling/ethrex-fixtures/Cargo.toml | 10 +- tooling/ethrex-tests/Cargo.lock | 84 ++++++++--- tooling/ethrex-tests/Cargo.toml | 2 +- 18 files changed, 564 insertions(+), 217 deletions(-) create mode 100644 BUMP_797df554_RESULTS.md create mode 100755 scripts/set_ethrex_rev.sh diff --git a/BUMP_797df554_RESULTS.md b/BUMP_797df554_RESULTS.md new file mode 100644 index 000000000..21a6b1525 --- /dev/null +++ b/BUMP_797df554_RESULTS.md @@ -0,0 +1,130 @@ +# Bumping the ethrex pin to `797df554` + +Moves the guest from `4f658c2b` (2026-07-31, on the unmerged +`feat/lambdavm-prover-backend` branch) to `797df554` (2026-08-07, on ethrex +`main`). + +## Results + +Real mainnet block 25368371 — the fixture `bench_abba.sh`, `bench_verify.sh`, +`perf_diff.sh` and `benchmark-pr.yml` all resolve through +`make print-real-block-fixture`. + +| | before (`4f658c2b`) | after (`797df554`) | delta | +|---|---|---|---| +| **Proving time** (median of 3) | **120.715 s** | **110.259 s** | **−8.66%** | +| run spread | 0.681 s (0.56%) | 0.821 s (0.74%) | | +| Executed cycles | 34,241,608 | 30,498,818 | −10.93% | +| **Epochs** (2^22) | **9** | **8** | −1 | +| Peak heap | 47.3 GB | 46.2 GB | −2.4% | + +10.46 s off every proof, against a run spread below 0.8% on both sides — the +delta is roughly twelve times the noise. + +Against `main`'s pin (`156cb8d6`, 2026-05-22) the guest goes from **39,563,400** +to **30,498,818** cycles, **−22.9%**. + +### Time does not track cycles + +−10.93% of cycles buys −8.66% of proving time, and that gap is structural rather +than measurement error: proving cost also carries per-epoch fixed work and table +rows that do not shrink with the instruction count. Quoting a cycle delta as if it +were a time delta will overpromise. + +The epoch count is the clearest instance. At 2^22 cycles per epoch, 34.2M needed 9 +and 30.5M fits in 8, and that single step accounts for much of the 10 s. It also +means the next increment of cycle savings returns less until it drops the ninth +epoch — the benefit arrives in steps, not smoothly. + +## What changed + +**The pin**, in all 11 places (`scripts/set_ethrex_rev.sh` moves them together, so +the guest and the fixture tooling cannot drift apart — a mismatch there does not +fail to build, it produces a fixture the guest silently misreads). + +**The `lambdavm` feature is gone**, and not replaced. `ethrex-guest-program` +carries one feature per zkVM and none of them selects a backend: each is only a +list of optional crypto dependencies, and none gates any method of the `Crypto` +trait (its gates are `secp256k1`, `c-kzg`, `blst`, `std`). Measured against the +sibling feature that activates the widest dependency set, ethrex's own precompile +stress fixtures come out identical to the cycle — `stress_modexp_150M` +6,373,285,966 and `stress_alt_bn128_150M` 22,986,061,145 both ways — with the real +block at 30,498,818 vs 30,501,620 and a 2,928-byte smaller ELF. + +Requiring `lambdavm` is what tied the guest to the backend branch, since that is +the only place it exists. What makes this guest LambdaVM never travelled through +it: `lambda-vm-syscalls`, `lambda-vm-ethrex-crypto` and the +`riscv64im-lambda-vm-elf` target are direct, and every run above reports 116 ECSM +and 10,659 keccak precompile calls — the injected crypto is live on both sides. + +**Fixtures regenerated.** The rkyv `ProgramInput` layout moved with the rev, so +every committed `.bin` from the old pin fails to deserialize. All four synthetic +fixtures plus the real block were rebuilt; the converter's reproducibility digest +was updated, which is what that test asks for on a legitimate rev bump. + +| fixture | cycles | ECSM | +|---|---|---| +| `ethrex_empty_block` | 431,162 | 0 | +| `ethrex_simple_tx` | 653,263 | 4 | +| `ethrex_bench_4` | 1,047,001 | 16 | +| `ethrex_10_transfers` | 1,286,761 | 40 | +| real block 25368371 | 30,498,818 | 116 | + +## Why `797df554` and not something newer + +`b5271885` (2026-08-14) rewrites the guest entry point from `ProgramInput` / +`execution_program` to `run_stateless_guest` taking schema-prefixed SSZ +`statelessInputBytes`. Past that commit this is no longer a pin bump — it needs a +new guest shim and a new fixture format. `797df554` is the last point where the +bump is only the rev. + +Within that window it is also the right end of it: between 2026-08-07 and the +boundary there are 14 commits and **no `perf(levm)`**. The two that matter — +`perf(levm): monomorphize the dispatch loop on the validation observer` (#7105) +and `perf(l1): give branch-node hashing a monomorphic RLP encoder` (#7104) — are +already in. + +Release tags do not help here. `v24.0.0`'s commit predates both, and it is +*diverged* from `main` (4 commits of its own, 66 behind) because ethrex cuts +releases from a side branch rather than from the tip — so a tag can lack work that +is already merged. `v25.0.0` is past the API boundary. + +## Method + +`vm-benchmarks-1`, 96 cores / 125 GB, rustc 1.94.0. The real-block step of +`.github/workflows/benchmark-pr.yml` verbatim: + +``` +cli prove $ELF --private-input $REAL_INPUT \ + --continuations --epoch-size-log2 22 -o /tmp/real_proof.bin --time +``` + +3 runs per side, reported as median plus spread. + +Both sides started on an idle box (load 0.61 and 1.99). The baseline was held back +until load fell below 3 after the first side finished — starting it hot would have +inflated the baseline and flattered the bump. + +Each side used **its own canonical fixture**: the baseline fetched the published +`_4f658c2b` release asset, the bumped side used the newly generated `_797df554` +one. They cannot share a file — the rkyv layout differs — and each side's own +asset is what CI would fetch, which is the honest comparison rather than two +ad-hoc conversions. + +Cycle counts are deterministic: the bumped side reported 30,498,818 identically on +a laptop and on the bench box. + +Raw logs in `bench_ethrex_bump_20260826/`. + +## Before merging + +`ethrex_mainnet_25368371_797df554.bin` (in the repo root) must be uploaded to the +`bench-fixtures-v1` release of **yetanotherco/lambda_vm**. The Makefile already +points at it with its sha256; until the asset exists, `make +ethrex-real-block-fixture` works locally (the file is in place and verifies) but +CI cannot fetch it. + +This also stops being a routine bump: it leaves `feat/lambdavm-prover-backend` and +consumes ethrex `main` as a library. That branch is 69 commits behind `main` and +has not moved since 2026-08-04, so waiting for it to merge was itself costing the +−22.9% above. The PR's existing approval predates this change. diff --git a/Makefile b/Makefile index b1f13c9e6..9c4c096a1 100644 --- a/Makefile +++ b/Makefile @@ -311,8 +311,8 @@ ETHREX_REAL_BLOCK := 25368371 # archived ProgramInput layout moves with the pin, so one block has one fixture per rev. # Uploading under a new name rather than replacing the old one keeps `main` — which still # expects the pre-bump sha256 — fetching its own artifact while this branch is open. -ETHREX_REAL_BLOCK_FIXTURE_URL := https://github.com/yetanotherco/lambda_vm/releases/download/bench-fixtures-v1/ethrex_mainnet_25368371_4f658c2b.bin -ETHREX_REAL_BLOCK_FIXTURE_SHA256 := 0a301731b84515260c2ad7779fef3e4ef8b2424fbcc055ee2f5a224bc88120ab +ETHREX_REAL_BLOCK_FIXTURE_URL := https://github.com/yetanotherco/lambda_vm/releases/download/bench-fixtures-v1/ethrex_mainnet_25368371_797df554.bin +ETHREX_REAL_BLOCK_FIXTURE_SHA256 := 573004e62e3680a00d3cdbae19dc4897e2ec60d6ec0c1d05d9ef118cb8aef17f # The block's source cache, hosted in the same release. Only `regen-real-block-fixture` # reads it — the converter's TESTS use a different, upstream-pinned cache (below). ETHREX_REAL_BLOCK_CACHE_URL := https://github.com/yetanotherco/lambda_vm/releases/download/bench-fixtures-v1/cache_mainnet_25368371.json diff --git a/crypto/ethrex-crypto/Cargo.toml b/crypto/ethrex-crypto/Cargo.toml index 5310c00b0..09d53ac3b 100644 --- a/crypto/ethrex-crypto/Cargo.toml +++ b/crypto/ethrex-crypto/Cargo.toml @@ -18,7 +18,7 @@ license = "MIT OR Apache-2.0" # Defines the `Crypto` trait, `CryptoError`, and `keccak::keccak_hash`. Same rev # + `default-features = false` as the guest's ethrex-crypto, so feature # unification adds nothing to the guest build (no C secp256k1 / malachite / kzg). -ethrex-crypto = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-crypto", default-features = false } +ethrex-crypto = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-crypto", default-features = false } # Pinned to the exact 0.13.4 ethrex uses so the guest resolves a single k256 # (a version split would make `FieldElement`/`Scalar` incompatible types). # `expose-field` is required by the x-only reconstruction. diff --git a/executor/programs/rust/ethrex/Cargo.lock b/executor/programs/rust/ethrex/Cargo.lock index 93a5d800d..63705e473 100644 --- a/executor/programs/rust/ethrex/Cargo.lock +++ b/executor/programs/rust/ethrex/Cargo.lock @@ -189,22 +189,6 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" -[[package]] -name = "bitcoin-io" -version = "0.1.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11301df0b06f22dea7bb1916403fdd88a371031e495c49b8f96931b28189e175" - -[[package]] -name = "bitcoin_hashes" -version = "0.14.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9901a56e133a1fc86eeb1113e2591f45f4682451ca893bff494d2f88918e3f" -dependencies = [ - "bitcoin-io", - "hex-conservative", -] - [[package]] name = "bitvec" version = "1.0.1" @@ -226,19 +210,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bls12_381" -version = "0.8.0" -source = "git+https://github.com/lambdaclass/bls12_381?branch=expose-affine-constructors#78cad0378b17fc3157b83f514be192bf46edf9a1" -dependencies = [ - "digest", - "ff", - "group", - "pairing", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "bs58" version = "0.5.1" @@ -697,8 +668,8 @@ dependencies = [ [[package]] name = "ethrex-common" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "crc32fast", @@ -712,6 +683,10 @@ dependencies = [ "indexmap 2.14.0", "lazy_static", "libc", + "libssz", + "libssz-derive", + "libssz-merkle", + "libssz-types", "lru", "once_cell", "rkyv", @@ -725,8 +700,8 @@ dependencies = [ [[package]] name = "ethrex-crypto" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "ark-bn254", "ark-ec", @@ -745,10 +720,9 @@ dependencies = [ [[package]] name = "ethrex-guest-program" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ - "bls12_381", "bytes", "ethereum-types", "ethrex-common", @@ -756,10 +730,7 @@ dependencies = [ "ethrex-l2-common", "ethrex-rlp", "ethrex-vm", - "ff", "hex", - "k256", - "lambda-vm-syscalls", "rkyv", "serde", "serde_with", @@ -768,8 +739,8 @@ dependencies = [ [[package]] name = "ethrex-l2-common" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "ethereum-types", @@ -778,7 +749,6 @@ dependencies = [ "k256", "lambdaworks-crypto", "rkyv", - "secp256k1", "serde", "serde_with", "thiserror 2.0.18", @@ -787,14 +757,15 @@ dependencies = [ [[package]] name = "ethrex-levm" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "derive_more", "ethrex-common", "ethrex-crypto", "ethrex-rlp", + "libssz", "malachite", "rustc-hash", "serde", @@ -804,8 +775,8 @@ dependencies = [ [[package]] name = "ethrex-rlp" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "ethereum-types", @@ -814,8 +785,8 @@ dependencies = [ [[package]] name = "ethrex-trie" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "anyhow", "bytes", @@ -834,8 +805,8 @@ dependencies = [ [[package]] name = "ethrex-vm" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "derive_more", @@ -856,7 +827,6 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "bitvec", "rand_core 0.6.4", "subtle", ] @@ -1019,15 +989,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hex-conservative" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" -dependencies = [ - "arrayvec", -] - [[package]] name = "hex-literal" version = "0.4.1" @@ -1284,6 +1245,47 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +[[package]] +name = "libssz" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d498c0482bba87d2647ea4601ea76cf2b498065e3958798a88f49274f3ced5e9" +dependencies = [ + "smallvec", +] + +[[package]] +name = "libssz-derive" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ddfb5c969c28a4a54043e630f80c723352637bd1020f256ee3ac7a8814922b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "libssz-merkle" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63c6d6d5ce5d79bba66bc98c99869eedffedf7f14f0aa0915f1a62802650bdf6" +dependencies = [ + "libssz", + "sha2", +] + +[[package]] +name = "libssz-types" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747273ab2d923e82ed147091fe0fb3e602dd2012c872cdad5efe69e27c3b4099" +dependencies = [ + "libssz", + "libssz-merkle", + "smallvec", +] + [[package]] name = "log" version = "0.4.32" @@ -1429,15 +1431,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "pairing" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" -dependencies = [ - "group", -] - [[package]] name = "parity-scale-codec" version = "3.7.5" @@ -1861,26 +1854,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "secp256k1" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" -dependencies = [ - "bitcoin_hashes", - "rand 0.8.6", - "secp256k1-sys", -] - -[[package]] -name = "secp256k1-sys" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" -dependencies = [ - "cc", -] - [[package]] name = "serde" version = "1.0.228" @@ -2005,6 +1978,12 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + [[package]] name = "spin" version = "0.9.9" diff --git a/executor/programs/rust/ethrex/Cargo.toml b/executor/programs/rust/ethrex/Cargo.toml index 58d63cd5f..1d2ef0f4a 100644 --- a/executor/programs/rust/ethrex/Cargo.toml +++ b/executor/programs/rust/ethrex/Cargo.toml @@ -18,20 +18,49 @@ lambda-vm-syscalls = { path = "../../../../syscalls" } # lambda_vm repo and injected in src/main.rs — so crypto changes stay in our repo # and don't require an ethrex PR. lambda-vm-ethrex-crypto = { path = "../../../../crypto/ethrex-crypto" } -# Pinned by immutable `rev` to a commit on the open LambdaVM-backend PR branch -# (feat/lambdavm-prover-backend) of ethrex; re-pin to the merge commit once it -# lands on ethrex `main`. The `lambdavm` feature is kept only for its dependency -# wiring (`ProgramInput`/`execution_program`/`ProgramOutput::encode` + pure-Rust -# crypto defaults); ethrex's own `LambdaVmCrypto` compiles unused — we inject our -# own. KZG is NOT linked under this feature, so the point-evaluation precompile -# (0x0a) is unsupported — see src/main.rs. -ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-guest-program", default-features = false, features = ["lambdavm"] } +# Pinned by immutable `rev` to a commit on ethrex `main`. It used to point at the +# open LambdaVM-backend PR branch (`feat/lambdavm-prover-backend`) and wait for +# that to merge; it no longer does, because the branch stopped moving 69 commits +# behind `main` while guest performance work kept landing there. Measured on the +# real benchmark block, that wait had grown to -22.9%. +# +# No `features = [...]`, deliberately — not even `lambdavm`. +# +# `ethrex-guest-program` carries one feature per zkVM and, despite the names, none +# of them selects a backend: each is only a list of optional crypto dependencies +# to activate, and none gates any method of the `Crypto` trait (its gates are +# `secp256k1`, `c-kzg`, `blst`, `std`). `lambdavm` was +# `["dep:k256", "dep:lambda-vm-syscalls", "ethrex-l2-common/secp256k1"]`, and +# since #702 moved the `Crypto` impl into this repo the syscalls it pulled are for +# ethrex's own provider, which we no longer use — while this guest already depends +# on `lambda-vm-syscalls` by path, three lines above. +# +# What makes this guest LambdaVM is those path deps plus the +# `riscv64im-lambda-vm-elf` target. None of it travels through a feature: every +# run reports its ECSM and keccak precompile calls, which is what actually proves +# the injected crypto is live. +# +# Verified rather than assumed. Against the sibling feature activating the widest +# dependency set, ethrex's own precompile stress fixtures come out identical to +# the cycle — `stress_modexp_150M` 6,373,285,966 and `stress_alt_bn128_150M` +# 22,986,061,145 both ways — and the real benchmark block is 30,498,818 here vs +# 30,501,620 there, with a 2,928-byte smaller ELF. The difference is dead +# `substrate-bn`/`bls12_381` that the arkworks-based trait defaults never call. +# +# Declaring nothing is also what frees the pin: `lambdavm` exists ONLY on that +# unmerged branch, so requiring it is precisely what tied this guest to it. See +# scripts/bench_ethrex_pr.sh, which re-pins across arbitrary revs to measure +# upstream guest PRs. +# +# KZG is not linked, so the point-evaluation precompile (0x0a) is unsupported — +# see src/main.rs. +ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-guest-program", default-features = false } # Exact pin: must match the fixture writer (tooling/ethrex-fixtures) and the # executor test reader so the rkyv ProgramInput layout stays consistent. rkyv = { version = "=0.8.16", features = ["std", "unaligned"] } -# `ethrex-guest-program`'s `lambdavm` feature git-deps `lambda-vm-syscalls` at a -# pinned commit of THIS repo, which upstream bumps on its own schedule. Override it +# `lambda-vm-syscalls` can still reach the graph as a git dep pinned to a commit +# of THIS repo, which upstream bumps on its own schedule. Override it # with the working-tree copy so the guest always links the same syscalls as the rest # of the repo — including uncommitted local edits — rather than whatever commit # ethrex last pinned. (At the current ethrex rev the two happen to coincide; this diff --git a/executor/tests/README.md b/executor/tests/README.md index fc6b3f8a8..6ecbc7212 100644 --- a/executor/tests/README.md +++ b/executor/tests/README.md @@ -48,19 +48,19 @@ Known fixtures: ```text ethrex_empty_block.bin - sha256: ca7454142f13db5d04356366e3917d264cbea709ddd1b5e6526720dbaa12064d + sha256: 8d6f6061c71c23fad1d5dee26242d631efe0bff8d7f49422c2ba4cde9d4be919 contents: stateless ethrex empty block ProgramInput (0 transactions) ethrex_simple_tx.bin - sha256: 29c1607297d21d88accb33767c8e03eb6536d746e6b2399930afdc9d67f1fe3f + sha256: c40bce364f22758ab7fa6fe8b45ce4c305dee5add4536ef6dca0e74e410e2729 contents: stateless ethrex block with one plain ETH transfer transaction ethrex_10_transfers.bin - sha256: d04cfed35bd16c8248ab8ebf2f3ca2ff01c08269271b17e5d5db3b1f22ea03ad + sha256: 4d862e8537284729ff11c7bcf91c971e562dd6bbce2a1e181ba5bf48cb6b65cf contents: stateless ethrex block with ten plain ETH transfer transactions ethrex_bench_4.bin - sha256: bb26e6d6595f537c2fd57bfd59da6abe82241bd92690a21c2225da94d5cf507a + sha256: 03ed0d175622af6ef9a981d7652ba7c86630b9473f49cae17edf649724b704e1 contents: stateless ethrex block with four plain ETH transfers, `distinct` mode (N senders -> N recipients); read by the recursion profile target ``` diff --git a/executor/tests/ethrex_10_transfers.bin b/executor/tests/ethrex_10_transfers.bin index dd9303cfe4167942b6d264c42058a2ddc6c3c544..201351189e5462b12704ee2e5084ad44ab349682 100644 GIT binary patch delta 70 zcmZ3Kxg~Rh2KU4OPKonh{{QD-G`~N?XVqlblFeWeLmfRf4ZKlq6Wb*_gX~xM5 WM7SrfHQvqi?f2x15(bkExHtggr5hsv delta 74 zcmdmzxhQjk2DkK$FaQ5@F)%Rb{QdtQNHH)fLm9@!lNEV{H^*~ZsxzM5Jkv;;ak2py X$7CI2&dKMD*D(G6J(*F|03-weDz6$@ diff --git a/executor/tests/ethrex_bench_4.bin b/executor/tests/ethrex_bench_4.bin index 6b716bfc9002715b7c010a4d17072122742f5b75..23c2453ee946e46c1b8ab3d8b9fca79d03b11d11 100644 GIT binary patch delta 74 zcmZ2icc5;A3fsg0PKnDu|Nm!UU|>-G`~N?XVqlblFeZOsliVE2cFc^?WV4K|G~?tC ahTN0&SlK4q+ihj~_IvU}4TH%#tQ-Jb7aT1B delta 61 zcmX?5x2|r33Y*ZKpa1`}FfcIa{QdtQNHH)fZ?khSCNE@`+|0&oC&PGjv#Y!`-G`~N?XVqlblFec}*NN&E&Y^Kh5WHXnxG~?t4 ZQrwgKwAm-?>1<*8_It9Tq!EzI1^_i%8+QNz delta 61 zcmZ1%H79DrB4(kuKmY$TGB7ac{QdtQNHH)fZ{Euss?K diff --git a/scripts/set_ethrex_rev.sh b/scripts/set_ethrex_rev.sh new file mode 100755 index 000000000..0ae03673d --- /dev/null +++ b/scripts/set_ethrex_rev.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# Re-pin every ethrex git dependency in this repo to one rev, atomically. +# +# The guest and the host-side tooling exchange ethrex types (the SSZ stateless +# input the converter writes and the guest decodes), so a rev that differs +# between them is not a version skew that fails to build -- it is a fixture the +# guest silently decodes as the all-zero default. Keeping every pin on one rev +# is what makes that impossible, which is why this rewrites all of them together +# rather than leaving them to be bumped by hand. +# +# Usage: +# scripts/set_ethrex_rev.sh <40-char-sha> re-pin everything +# scripts/set_ethrex_rev.sh --guest-only <40-char-sha> re-pin only the guest graph +# scripts/set_ethrex_rev.sh --show print the current pin(s) +# +# `--guest-only` moves the crates the guest ELF is built from and leaves the +# host-side fixture tooling where it is. That split is what lets a benchmark vary +# the guest across two revs while holding the fixture constant: the fixture is +# just bytes, its wire format does not change within a PR, and regenerating it per +# rev would instead drag the whole ethrex HOST api (which does drift) into the +# measurement. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +# Every manifest carrying an ethrex git dep. Kept explicit rather than globbed so +# a new one has to be added deliberately -- a manifest silently left behind is +# exactly the skew described above. +# The guest ELF's own graph. `crypto/ethrex-crypto` is a path dep of BOTH the +# guest and the converter, so moving it moves what the converter links too -- +# which is why a `--guest-only` run must come after the fixture already exists. +GUEST_MANIFESTS=( + executor/programs/rust/ethrex/Cargo.toml + crypto/ethrex-crypto/Cargo.toml +) + +# Host-side tooling: builds fixtures, never lands in the ELF. +TOOLING_MANIFESTS=( + tooling/ethrex-tests/Cargo.toml + tooling/ethrex-block-converter/Cargo.toml + tooling/ethrex-fixtures/Cargo.toml +) + +MANIFESTS=("${GUEST_MANIFESTS[@]}" "${TOOLING_MANIFESTS[@]}") + +current_revs() { + grep -ho 'rev = "[0-9a-f]\{40\}"' "${MANIFESTS[@]}" | sort -u | sed 's/rev = //;s/"//g' +} + +if [[ "${1:-}" == "--show" || $# -eq 0 ]]; then + # No `mapfile`: macOS ships bash 3.2, where it does not exist. + revs="$(current_revs)" + if [[ "$(printf '%s\n' "$revs" | wc -l | tr -d ' ')" == "1" ]]; then + echo "$revs" + else + # Expected mid-benchmark (a `--guest-only` re-pin is exactly this state), so + # report it rather than failing. + echo "MIXED:" >&2 + printf ' %s\n' $revs >&2 + exit 1 + fi + exit 0 +fi + +TARGETS=("${MANIFESTS[@]}") +SCOPE="all manifests" +if [[ "${1:-}" == "--guest-only" ]]; then + shift + TARGETS=("${GUEST_MANIFESTS[@]}") + SCOPE="the guest graph only" +fi + +NEW_REV="${1:-}" +if [[ ! "$NEW_REV" =~ ^[0-9a-f]{40}$ ]]; then + echo "error: expected a 40-character commit sha, got '$NEW_REV'" >&2 + exit 2 +fi + +for manifest in "${TARGETS[@]}"; do + [[ -f "$manifest" ]] || { echo "error: missing $manifest" >&2; exit 1; } + # Only rewrite revs on ethrex deps: the same file may pin other git deps. + perl -pi -e 's{(github\.com/lambdaclass/ethrex\.git", rev = ")[0-9a-f]{40}(")}{${1}'"$NEW_REV"'${2}}g' "$manifest" +done + +echo "re-pinned $SCOPE to $NEW_REV:" +printf ' %s\n' "${TARGETS[@]}" diff --git a/tooling/ethrex-block-converter/Cargo.lock b/tooling/ethrex-block-converter/Cargo.lock index e68dfc38c..5c216acbb 100644 --- a/tooling/ethrex-block-converter/Cargo.lock +++ b/tooling/ethrex-block-converter/Cargo.lock @@ -305,19 +305,6 @@ dependencies = [ "objc2", ] -[[package]] -name = "bls12_381" -version = "0.8.0" -source = "git+https://github.com/lambdaclass/bls12_381?branch=expose-affine-constructors#78cad0378b17fc3157b83f514be192bf46edf9a1" -dependencies = [ - "digest", - "ff", - "group", - "pairing", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "blst" version = "0.3.17" @@ -877,18 +864,21 @@ dependencies = [ [[package]] name = "ethrex-blockchain" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "crossbeam", "ethrex-common", "ethrex-crypto", + "ethrex-guest-program", "ethrex-metrics", "ethrex-rlp", "ethrex-storage", "ethrex-trie", "ethrex-vm", + "libssz", + "libssz-merkle", "rayon", "rustc-hash", "thiserror 2.0.19", @@ -899,8 +889,8 @@ dependencies = [ [[package]] name = "ethrex-common" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "crc32fast", @@ -914,6 +904,10 @@ dependencies = [ "indexmap 2.14.0", "lazy_static", "libc", + "libssz", + "libssz-derive", + "libssz-merkle", + "libssz-types", "lru", "once_cell", "rkyv", @@ -927,8 +921,8 @@ dependencies = [ [[package]] name = "ethrex-config" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "ethrex-common", "ethrex-p2p", @@ -939,8 +933,8 @@ dependencies = [ [[package]] name = "ethrex-crypto" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "ark-bn254", "ark-ec", @@ -960,10 +954,9 @@ dependencies = [ [[package]] name = "ethrex-guest-program" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ - "bls12_381", "bytes", "ethereum-types", "ethrex-common", @@ -971,10 +964,7 @@ dependencies = [ "ethrex-l2-common", "ethrex-rlp", "ethrex-vm", - "ff", "hex", - "k256", - "lambda-vm-syscalls", "rkyv", "serde", "serde_with", @@ -983,8 +973,8 @@ dependencies = [ [[package]] name = "ethrex-l2-common" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "ethereum-types", @@ -993,7 +983,6 @@ dependencies = [ "k256", "lambdaworks-crypto", "rkyv", - "secp256k1", "serde", "serde_with", "thiserror 2.0.19", @@ -1002,14 +991,15 @@ dependencies = [ [[package]] name = "ethrex-levm" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "derive_more", "ethrex-common", "ethrex-crypto", "ethrex-rlp", + "libssz", "malachite", "rustc-hash", "serde", @@ -1019,8 +1009,8 @@ dependencies = [ [[package]] name = "ethrex-metrics" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "ethrex-common", "serde", @@ -1031,8 +1021,8 @@ dependencies = [ [[package]] name = "ethrex-p2p" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "aes", "aes-gcm", @@ -1073,8 +1063,8 @@ dependencies = [ [[package]] name = "ethrex-rlp" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "ethereum-types", @@ -1083,8 +1073,8 @@ dependencies = [ [[package]] name = "ethrex-storage" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "anyhow", "bytes", @@ -1105,8 +1095,8 @@ dependencies = [ [[package]] name = "ethrex-trie" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "anyhow", "bytes", @@ -1125,8 +1115,8 @@ dependencies = [ [[package]] name = "ethrex-vm" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "derive_more", @@ -1159,7 +1149,6 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "bitvec", "rand_core 0.6.4", "subtle", ] @@ -1700,6 +1689,47 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +[[package]] +name = "libssz" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d498c0482bba87d2647ea4601ea76cf2b498065e3958798a88f49274f3ced5e9" +dependencies = [ + "smallvec", +] + +[[package]] +name = "libssz-derive" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ddfb5c969c28a4a54043e630f80c723352637bd1020f256ee3ac7a8814922b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "libssz-merkle" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63c6d6d5ce5d79bba66bc98c99869eedffedf7f14f0aa0915f1a62802650bdf6" +dependencies = [ + "libssz", + "sha2", +] + +[[package]] +name = "libssz-types" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747273ab2d923e82ed147091fe0fb3e602dd2012c872cdad5efe69e27c3b4099" +dependencies = [ + "libssz", + "libssz-merkle", + "smallvec", +] + [[package]] name = "lock_api" version = "0.4.14" @@ -1926,15 +1956,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "pairing" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" -dependencies = [ - "group", -] - [[package]] name = "parity-scale-codec" version = "3.7.5" diff --git a/tooling/ethrex-block-converter/Cargo.toml b/tooling/ethrex-block-converter/Cargo.toml index 87550cd8a..28f8d7e1d 100644 --- a/tooling/ethrex-block-converter/Cargo.toml +++ b/tooling/ethrex-block-converter/Cargo.toml @@ -9,13 +9,13 @@ edition = "2024" [dependencies] # Pinned to the SAME ethrex rev as the guest (open LambdaVM-backend PR branch) # so the generated ProgramInput rkyv layout matches what the guest deserializes. -ethrex-common = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-common", default-features = false } -ethrex-config = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-config", default-features = false } +ethrex-common = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-common", default-features = false } +ethrex-config = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-config", default-features = false } # Only for `NativeCrypto`, the host `Crypto` impl that `into_execution_witness` # now takes. Nothing re-exports it, and this crate's own guest-crypto impl is a # dev-dependency, so the bin build needs its own line. Already in the graph via # `ethrex-config` → `ethrex-p2p`, so this adds no compilation. -ethrex-crypto = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-crypto", default-features = false } +ethrex-crypto = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-crypto", default-features = false } # Matches the guest's own declaration (executor/programs/rust/ethrex/Cargo.toml). # # CAVEAT: this line alone does NOT reproduce the guest's precompile surface. The @@ -26,7 +26,7 @@ ethrex-crypto = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f65 # `ark-ff/asm` BN254) that the guest does not have; verify with # `cargo tree -e features -i ethrex-crypto`. Closing that gap means dropping # `ethrex-config` and sourcing `ChainConfig` another way — see the README. -ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-guest-program", default-features = false, features = ["lambdavm"] } +ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-guest-program", default-features = false } # Exact pin: the fixture writer and the guest/executor readers must agree on the # rkyv layout. Keep this in sync with the guest diff --git a/tooling/ethrex-block-converter/src/main.rs b/tooling/ethrex-block-converter/src/main.rs index 545c7cc6e..87bdfe51d 100644 --- a/tooling/ethrex-block-converter/src/main.rs +++ b/tooling/ethrex-block-converter/src/main.rs @@ -230,7 +230,7 @@ mod tests { .map(|b| format!("{b:02x}")) .collect(); assert_eq!( - digest, "6ffc80b25ec9ed3c203c35bdb8a4a05aff08d98b1a9c71648447bc199a552dc9", + digest, "8b3fb1294a5cd7296452d4a0422b0623c2d243e84bc8a51d3f15bc3899354553", "fixture bytes changed — regenerate it and update the README checksum", ); } diff --git a/tooling/ethrex-fixtures/Cargo.lock b/tooling/ethrex-fixtures/Cargo.lock index 1702880a9..03e77ca4a 100644 --- a/tooling/ethrex-fixtures/Cargo.lock +++ b/tooling/ethrex-fixtures/Cargo.lock @@ -975,18 +975,21 @@ dependencies = [ [[package]] name = "ethrex-blockchain" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "crossbeam", "ethrex-common", "ethrex-crypto", + "ethrex-guest-program", "ethrex-metrics", "ethrex-rlp", "ethrex-storage", "ethrex-trie", "ethrex-vm", + "libssz", + "libssz-merkle", "rayon", "rustc-hash", "thiserror 2.0.18", @@ -997,8 +1000,8 @@ dependencies = [ [[package]] name = "ethrex-common" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "crc32fast", @@ -1012,6 +1015,10 @@ dependencies = [ "indexmap 2.14.0", "lazy_static", "libc", + "libssz", + "libssz-derive", + "libssz-merkle", + "libssz-types", "lru", "once_cell", "rayon", @@ -1027,8 +1034,8 @@ dependencies = [ [[package]] name = "ethrex-crypto" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "ark-bn254", "ark-ec", @@ -1066,8 +1073,8 @@ dependencies = [ [[package]] name = "ethrex-guest-program" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "ethereum-types", @@ -1085,8 +1092,8 @@ dependencies = [ [[package]] name = "ethrex-l2-common" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "ethereum-types", @@ -1104,8 +1111,8 @@ dependencies = [ [[package]] name = "ethrex-l2-rpc" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "axum", "bytes", @@ -1136,14 +1143,15 @@ dependencies = [ [[package]] name = "ethrex-levm" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "derive_more", "ethrex-common", "ethrex-crypto", "ethrex-rlp", + "libssz", "malachite", "rayon", "rustc-hash", @@ -1154,8 +1162,8 @@ dependencies = [ [[package]] name = "ethrex-metrics" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "axum", "ethrex-common", @@ -1170,8 +1178,8 @@ dependencies = [ [[package]] name = "ethrex-p2p" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "aes", "aes-gcm", @@ -1214,8 +1222,8 @@ dependencies = [ [[package]] name = "ethrex-rlp" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "ethereum-types", @@ -1224,8 +1232,8 @@ dependencies = [ [[package]] name = "ethrex-rpc" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "axum", "axum-extra", @@ -1264,8 +1272,8 @@ dependencies = [ [[package]] name = "ethrex-storage" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "anyhow", "bytes", @@ -1286,8 +1294,8 @@ dependencies = [ [[package]] name = "ethrex-storage-rollup" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "async-trait", "bincode", @@ -1302,8 +1310,8 @@ dependencies = [ [[package]] name = "ethrex-trie" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "anyhow", "bytes", @@ -1322,8 +1330,8 @@ dependencies = [ [[package]] name = "ethrex-vm" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "derive_more", @@ -2212,6 +2220,47 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +[[package]] +name = "libssz" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d498c0482bba87d2647ea4601ea76cf2b498065e3958798a88f49274f3ced5e9" +dependencies = [ + "smallvec", +] + +[[package]] +name = "libssz-derive" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ddfb5c969c28a4a54043e630f80c723352637bd1020f256ee3ac7a8814922b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "libssz-merkle" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63c6d6d5ce5d79bba66bc98c99869eedffedf7f14f0aa0915f1a62802650bdf6" +dependencies = [ + "libssz", + "sha2", +] + +[[package]] +name = "libssz-types" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747273ab2d923e82ed147091fe0fb3e602dd2012c872cdad5efe69e27c3b4099" +dependencies = [ + "libssz", + "libssz-merkle", + "smallvec", +] + [[package]] name = "linux-raw-sys" version = "0.4.15" diff --git a/tooling/ethrex-fixtures/Cargo.toml b/tooling/ethrex-fixtures/Cargo.toml index 3320ba25b..daeda8b15 100644 --- a/tooling/ethrex-fixtures/Cargo.toml +++ b/tooling/ethrex-fixtures/Cargo.toml @@ -9,11 +9,11 @@ edition = "2024" [dependencies] # Pinned to the SAME ethrex rev as the guest (open LambdaVM-backend PR branch) # so the generated ProgramInput rkyv layout matches what the guest deserializes. -ethrex-blockchain = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-blockchain" } -ethrex-storage = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-storage" } -ethrex-common = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-common" } -ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-guest-program" } -ethrex-l2-rpc = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-l2-rpc" } +ethrex-blockchain = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-blockchain" } +ethrex-storage = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-storage" } +ethrex-common = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-common" } +ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-guest-program" } +ethrex-l2-rpc = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-l2-rpc" } tokio = { version = "1", features = ["rt-multi-thread", "macros"] } # Exact pin: the fixture writer and the guest/executor readers must agree on the diff --git a/tooling/ethrex-tests/Cargo.lock b/tooling/ethrex-tests/Cargo.lock index 84712135c..0a86228f1 100644 --- a/tooling/ethrex-tests/Cargo.lock +++ b/tooling/ethrex-tests/Cargo.lock @@ -693,8 +693,8 @@ dependencies = [ [[package]] name = "ethrex-common" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "crc32fast", @@ -708,6 +708,10 @@ dependencies = [ "indexmap 2.14.0", "lazy_static", "libc", + "libssz", + "libssz-derive", + "libssz-merkle", + "libssz-types", "lru", "once_cell", "rayon", @@ -723,8 +727,8 @@ dependencies = [ [[package]] name = "ethrex-crypto" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "ark-bn254", "ark-ec", @@ -744,8 +748,8 @@ dependencies = [ [[package]] name = "ethrex-guest-program" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "ethereum-types", @@ -763,8 +767,8 @@ dependencies = [ [[package]] name = "ethrex-l2-common" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "ethereum-types", @@ -782,14 +786,15 @@ dependencies = [ [[package]] name = "ethrex-levm" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "derive_more", "ethrex-common", "ethrex-crypto", "ethrex-rlp", + "libssz", "malachite", "rayon", "rustc-hash", @@ -800,8 +805,8 @@ dependencies = [ [[package]] name = "ethrex-rlp" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "ethereum-types", @@ -819,8 +824,8 @@ dependencies = [ [[package]] name = "ethrex-trie" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "anyhow", "bytes", @@ -839,8 +844,8 @@ dependencies = [ [[package]] name = "ethrex-vm" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "23.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" dependencies = [ "bytes", "derive_more", @@ -1275,6 +1280,47 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +[[package]] +name = "libssz" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d498c0482bba87d2647ea4601ea76cf2b498065e3958798a88f49274f3ced5e9" +dependencies = [ + "smallvec", +] + +[[package]] +name = "libssz-derive" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ddfb5c969c28a4a54043e630f80c723352637bd1020f256ee3ac7a8814922b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "libssz-merkle" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63c6d6d5ce5d79bba66bc98c99869eedffedf7f14f0aa0915f1a62802650bdf6" +dependencies = [ + "libssz", + "sha2", +] + +[[package]] +name = "libssz-types" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747273ab2d923e82ed147091fe0fb3e602dd2012c872cdad5efe69e27c3b4099" +dependencies = [ + "libssz", + "libssz-merkle", + "smallvec", +] + [[package]] name = "log" version = "0.4.33" @@ -1928,6 +1974,12 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + [[package]] name = "spin" version = "0.9.9" diff --git a/tooling/ethrex-tests/Cargo.toml b/tooling/ethrex-tests/Cargo.toml index 5ff8ef39f..3c89ea8f7 100644 --- a/tooling/ethrex-tests/Cargo.toml +++ b/tooling/ethrex-tests/Cargo.toml @@ -12,7 +12,7 @@ edition = "2024" executor = { path = "../../executor" } # Pinned to the SAME ethrex rev as the guest (open LambdaVM-backend PR branch) # so the native reference reads the same ProgramInput rkyv layout. -ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "4f658c2b3d10e3f21d35ce546870f55ca3f940fc", package = "ethrex-guest-program" } +ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-guest-program" } # Exact pin: the fixture writer and the guest/executor readers must agree on the # rkyv layout. Keep this in sync with tooling/ethrex-fixtures and # executor/programs/rust/ethrex/Cargo.toml. From fdbd91c61ed159092d5c09c1ece9ecad2f3989b6 Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Wed, 26 Aug 2026 17:25:49 -0300 Subject: [PATCH 06/27] rm file --- BUMP_797df554_RESULTS.md | 130 --------------------------------------- 1 file changed, 130 deletions(-) delete mode 100644 BUMP_797df554_RESULTS.md diff --git a/BUMP_797df554_RESULTS.md b/BUMP_797df554_RESULTS.md deleted file mode 100644 index 21a6b1525..000000000 --- a/BUMP_797df554_RESULTS.md +++ /dev/null @@ -1,130 +0,0 @@ -# Bumping the ethrex pin to `797df554` - -Moves the guest from `4f658c2b` (2026-07-31, on the unmerged -`feat/lambdavm-prover-backend` branch) to `797df554` (2026-08-07, on ethrex -`main`). - -## Results - -Real mainnet block 25368371 — the fixture `bench_abba.sh`, `bench_verify.sh`, -`perf_diff.sh` and `benchmark-pr.yml` all resolve through -`make print-real-block-fixture`. - -| | before (`4f658c2b`) | after (`797df554`) | delta | -|---|---|---|---| -| **Proving time** (median of 3) | **120.715 s** | **110.259 s** | **−8.66%** | -| run spread | 0.681 s (0.56%) | 0.821 s (0.74%) | | -| Executed cycles | 34,241,608 | 30,498,818 | −10.93% | -| **Epochs** (2^22) | **9** | **8** | −1 | -| Peak heap | 47.3 GB | 46.2 GB | −2.4% | - -10.46 s off every proof, against a run spread below 0.8% on both sides — the -delta is roughly twelve times the noise. - -Against `main`'s pin (`156cb8d6`, 2026-05-22) the guest goes from **39,563,400** -to **30,498,818** cycles, **−22.9%**. - -### Time does not track cycles - -−10.93% of cycles buys −8.66% of proving time, and that gap is structural rather -than measurement error: proving cost also carries per-epoch fixed work and table -rows that do not shrink with the instruction count. Quoting a cycle delta as if it -were a time delta will overpromise. - -The epoch count is the clearest instance. At 2^22 cycles per epoch, 34.2M needed 9 -and 30.5M fits in 8, and that single step accounts for much of the 10 s. It also -means the next increment of cycle savings returns less until it drops the ninth -epoch — the benefit arrives in steps, not smoothly. - -## What changed - -**The pin**, in all 11 places (`scripts/set_ethrex_rev.sh` moves them together, so -the guest and the fixture tooling cannot drift apart — a mismatch there does not -fail to build, it produces a fixture the guest silently misreads). - -**The `lambdavm` feature is gone**, and not replaced. `ethrex-guest-program` -carries one feature per zkVM and none of them selects a backend: each is only a -list of optional crypto dependencies, and none gates any method of the `Crypto` -trait (its gates are `secp256k1`, `c-kzg`, `blst`, `std`). Measured against the -sibling feature that activates the widest dependency set, ethrex's own precompile -stress fixtures come out identical to the cycle — `stress_modexp_150M` -6,373,285,966 and `stress_alt_bn128_150M` 22,986,061,145 both ways — with the real -block at 30,498,818 vs 30,501,620 and a 2,928-byte smaller ELF. - -Requiring `lambdavm` is what tied the guest to the backend branch, since that is -the only place it exists. What makes this guest LambdaVM never travelled through -it: `lambda-vm-syscalls`, `lambda-vm-ethrex-crypto` and the -`riscv64im-lambda-vm-elf` target are direct, and every run above reports 116 ECSM -and 10,659 keccak precompile calls — the injected crypto is live on both sides. - -**Fixtures regenerated.** The rkyv `ProgramInput` layout moved with the rev, so -every committed `.bin` from the old pin fails to deserialize. All four synthetic -fixtures plus the real block were rebuilt; the converter's reproducibility digest -was updated, which is what that test asks for on a legitimate rev bump. - -| fixture | cycles | ECSM | -|---|---|---| -| `ethrex_empty_block` | 431,162 | 0 | -| `ethrex_simple_tx` | 653,263 | 4 | -| `ethrex_bench_4` | 1,047,001 | 16 | -| `ethrex_10_transfers` | 1,286,761 | 40 | -| real block 25368371 | 30,498,818 | 116 | - -## Why `797df554` and not something newer - -`b5271885` (2026-08-14) rewrites the guest entry point from `ProgramInput` / -`execution_program` to `run_stateless_guest` taking schema-prefixed SSZ -`statelessInputBytes`. Past that commit this is no longer a pin bump — it needs a -new guest shim and a new fixture format. `797df554` is the last point where the -bump is only the rev. - -Within that window it is also the right end of it: between 2026-08-07 and the -boundary there are 14 commits and **no `perf(levm)`**. The two that matter — -`perf(levm): monomorphize the dispatch loop on the validation observer` (#7105) -and `perf(l1): give branch-node hashing a monomorphic RLP encoder` (#7104) — are -already in. - -Release tags do not help here. `v24.0.0`'s commit predates both, and it is -*diverged* from `main` (4 commits of its own, 66 behind) because ethrex cuts -releases from a side branch rather than from the tip — so a tag can lack work that -is already merged. `v25.0.0` is past the API boundary. - -## Method - -`vm-benchmarks-1`, 96 cores / 125 GB, rustc 1.94.0. The real-block step of -`.github/workflows/benchmark-pr.yml` verbatim: - -``` -cli prove $ELF --private-input $REAL_INPUT \ - --continuations --epoch-size-log2 22 -o /tmp/real_proof.bin --time -``` - -3 runs per side, reported as median plus spread. - -Both sides started on an idle box (load 0.61 and 1.99). The baseline was held back -until load fell below 3 after the first side finished — starting it hot would have -inflated the baseline and flattered the bump. - -Each side used **its own canonical fixture**: the baseline fetched the published -`_4f658c2b` release asset, the bumped side used the newly generated `_797df554` -one. They cannot share a file — the rkyv layout differs — and each side's own -asset is what CI would fetch, which is the honest comparison rather than two -ad-hoc conversions. - -Cycle counts are deterministic: the bumped side reported 30,498,818 identically on -a laptop and on the bench box. - -Raw logs in `bench_ethrex_bump_20260826/`. - -## Before merging - -`ethrex_mainnet_25368371_797df554.bin` (in the repo root) must be uploaded to the -`bench-fixtures-v1` release of **yetanotherco/lambda_vm**. The Makefile already -points at it with its sha256; until the asset exists, `make -ethrex-real-block-fixture` works locally (the file is in place and verifies) but -CI cannot fetch it. - -This also stops being a routine bump: it leaves `feat/lambdavm-prover-backend` and -consumes ethrex `main` as a library. That branch is 69 commits behind `main` and -has not moved since 2026-08-04, so waiting for it to merge was itself costing the -−22.9% above. The PR's existing approval predates this change. From fecc3f3dedfcd3cfa0b4a0d51d57240d49771ada Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Tue, 8 Sep 2026 17:30:11 -0300 Subject: [PATCH 07/27] Move the ethrex pin from 797df554 (23.0.0) to 2cb18b0b (25.0.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since execution-specs #3278 the guest takes the spec's schema-prefixed SSZ statelessInputBytes instead of an rkyv ProgramInput, with the fork coming from the schema id — ethrex 25 implements one, 0x1501 (Amsterdam) — so the guest, the converter, the fixture generator and the host-reference tests move to that format and the four synthetic fixtures are regenerated. The benchmark fixture becomes generated rather than fetched: mainnet has no Amsterdam fork, so no hosted artifact for that block can be valid, and the old one now runs 496 cycles with successful_validation = 0 instead of failing. real_block rebuilds mainnet 25368371 as an Amsterdam block from its replay cache, and bench_abba.sh refuses to measure a workload the guest rejected. --- .github/workflows/benchmark-pr.yml | 24 +- .github/workflows/ethrex-block-converter.yml | 16 +- Makefile | 65 +- crypto/ethrex-crypto/Cargo.lock | 4 +- crypto/ethrex-crypto/Cargo.toml | 2 +- crypto/ethrex-crypto/src/lib.rs | 3 +- executor/programs/rust/ethrex/Cargo.lock | 53 +- executor/programs/rust/ethrex/Cargo.toml | 6 +- executor/programs/rust/ethrex/src/main.rs | 21 +- executor/tests/README.md | 76 +-- executor/tests/ethrex_10_transfers.bin | Bin 13492 -> 9443 bytes executor/tests/ethrex_bench_4.bin | Bin 16192 -> 10056 bytes executor/tests/ethrex_empty_block.bin | Bin 8544 -> 6281 bytes executor/tests/ethrex_simple_tx.bin | Bin 11566 -> 7441 bytes scripts/bench_abba.sh | 35 +- tooling/ethrex-block-converter/Cargo.lock | 78 +-- tooling/ethrex-block-converter/Cargo.toml | 34 +- tooling/ethrex-block-converter/README.md | 577 ++---------------- tooling/ethrex-block-converter/src/main.rs | 383 +++++++----- tooling/ethrex-fixtures/Cargo.lock | 88 +-- tooling/ethrex-fixtures/Cargo.toml | 24 +- tooling/ethrex-fixtures/README.md | 123 ++-- tooling/ethrex-fixtures/genesis.json | 12 + tooling/ethrex-fixtures/src/bin/real_block.rs | 512 ++++++++++++++++ tooling/ethrex-fixtures/src/main.rs | 177 +++++- tooling/ethrex-tests/Cargo.lock | 58 +- tooling/ethrex-tests/Cargo.toml | 16 +- .../tests/eest_amsterdam_probe.rs | 81 +++ tooling/ethrex-tests/tests/ethrex.rs | 148 +---- 29 files changed, 1446 insertions(+), 1170 deletions(-) create mode 100644 tooling/ethrex-fixtures/src/bin/real_block.rs create mode 100644 tooling/ethrex-tests/tests/eest_amsterdam_probe.rs diff --git a/.github/workflows/benchmark-pr.yml b/.github/workflows/benchmark-pr.yml index b9da23925..934dd1996 100644 --- a/.github/workflows/benchmark-pr.yml +++ b/.github/workflows/benchmark-pr.yml @@ -76,9 +76,9 @@ env: # can move the synthetic number and the real one in opposite directions. # # The path is resolved from the Makefile (`make -s print-real-block-fixture`) into - # REAL_INPUT at run time. The ~1 MB .bin is gitignored and FETCHED by URL + sha256 - # (see "Fetch ethrex real-block fixture"); while that URL is unset the whole - # section degrades to a warning rather than failing the job. + # REAL_INPUT at run time. The .bin is gitignored and BUILT from the block's replay + # cache (see "Build ethrex real-block fixture"); while that cache's URL is unset the + # whole section degrades to a warning rather than failing the job. # # Continuations are mandatory here, not a preference: a monolithic prove costs # ~4.9 GB of peak heap per million cycles on this workload family (from the @@ -240,8 +240,8 @@ jobs: # the growth sweep still runs and feeds the baseline artifact (no comment is # posted on those events — the Comment step is gated to comment triggers); # on a plain /bench nothing is left to measure, and the footer says so. - if [ "$RUN_REAL" = "true" ] && [ -z "$(make -s print-real-block-fixture-url)" ]; then - echo "::warning::Real-block benchmark skipped: ETHREX_REAL_BLOCK_FIXTURE_URL is unset in the Makefile." + if [ "$RUN_REAL" = "true" ] && [ -z "$(make -s print-real-block-cache-url)" ]; then + echo "::warning::Real-block benchmark skipped: ETHREX_REAL_BLOCK_CACHE_URL is unset in the Makefile." RUN_REAL=false fi echo "run_real=$RUN_REAL" >> "$GITHUB_OUTPUT" @@ -296,14 +296,16 @@ jobs: echo "Using $RUNS iterations, TABLE_PARALLELISM=default" fi - - name: Fetch ethrex real-block fixture + - name: Build ethrex real-block fixture if: steps.config.outputs.run_real == 'true' run: | - # ~1 MB, gitignored, and never in the checkout: fetch it rather than failing - # on a missing file, the same way the synthetic fixtures are generated above. - # This is a URL + sha256 download, not a build — no converter, no ethrex host - # dependency tree, no ethrex-replay cache. The step is already gated on the - # URL being set (see "Determine run count"). + # Gitignored and never in the checkout, like the synthetic fixtures above. + # Built, not downloaded: ethrex 25's guest decodes only the Amsterdam schema, + # so a hosted artifact for this pre-Amsterdam block cannot be valid — it would + # run ~500 cycles and commit `successful_validation = 0` without failing. The + # generator rebuilds the block's real transactions on its real pre-state and + # validates the result through the guest before writing. The step is gated on + # the cache URL being set (see "Determine run count"). # # Untracked, so like the ELF it survives `git checkout origin/main` and both # sides prove the identical block. diff --git a/.github/workflows/ethrex-block-converter.yml b/.github/workflows/ethrex-block-converter.yml index 53c640e9b..e8260e129 100644 --- a/.github/workflows/ethrex-block-converter.yml +++ b/.github/workflows/ethrex-block-converter.yml @@ -83,15 +83,17 @@ jobs: workspaces: | tooling/ethrex-tests -> target - # Fetch-and-verify, not build: no converter, no ethrex-replay cache, no rev pin. - # The guard covers the window after a repoint but before the new artifact is - # uploaded: the screen below is its only consumer, and failing the job on an - # unset URL would block PRs on an upload nobody in the PR can perform. - - name: Fetch real-block fixture + # Built from the block's replay cache, not downloaded: ethrex 25's guest decodes + # only the Amsterdam schema, so no hosted artifact for a pre-Amsterdam block can + # be valid. The generator validates the block through the guest before writing, + # which is what the screen below then re-checks against the ELF. The guard covers + # an unset cache URL, where failing the job would block PRs on an upload nobody + # in the PR can perform. + - name: Build real-block fixture id: fixture run: | - if [ -z "$(make -s print-real-block-fixture-url)" ]; then - echo "::warning::ETHREX_REAL_BLOCK_FIXTURE_URL is unset — skipping the real-block usability screen. Set it in the Makefile once the .bin is hosted." + if [ -z "$(make -s print-real-block-cache-url)" ]; then + echo "::warning::ETHREX_REAL_BLOCK_CACHE_URL is unset — skipping the real-block usability screen. Set it in the Makefile once the cache is hosted." echo "present=false" >> "$GITHUB_OUTPUT" exit 0 fi diff --git a/Makefile b/Makefile index 1f178b3c1..ced7743f2 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ test-prover-cuda test-prover-comprehensive-cuda \ bench-math-cuda bench-prover bench-prover-cuda build check clippy fmt lint regen-ethrex-fixtures \ update-ethrex-fixture-checksums check-ethrex-fixture-checksums ethrex-real-block-fixture \ ethrex-real-block-cache ethrex-real-block-converter-cache print-real-block-fixture \ -print-real-block-fixture-url \ +print-real-block-cache-url \ test-ethrex-real-block-converter regen-real-block-fixture UNAME := $(shell uname) @@ -280,7 +280,7 @@ test-rust: compile-programs-rust # from tooling/ethrex-fixtures. Two artifacts, both gitignored and both FETCHED # rather than built: # -# the fixture the rkyv ProgramInput the benchmarks prove (~1 MB) +# the fixture the schema-prefixed SSZ input the benchmarks prove # the cache the ethrex-replay JSON it was converted from (~2 MB), read only # by `regen-real-block-fixture`. The converter's TESTS read a # different, upstream-pinned cache — see below. @@ -307,14 +307,22 @@ test-rust: compile-programs-rust # measured cost. ETHREX_REAL_BLOCK_NETWORK := mainnet ETHREX_REAL_BLOCK := 25368371 -# The asset name carries the ethrex rev because the bytes are a function of it: the -# archived ProgramInput layout moves with the pin, so one block has one fixture per rev. -# Uploading under a new name rather than replacing the old one keeps `main` — which still -# expects the pre-bump sha256 — fetching its own artifact while this branch is open. -ETHREX_REAL_BLOCK_FIXTURE_URL := https://github.com/yetanotherco/lambda_vm/releases/download/bench-fixtures-v1/ethrex_mainnet_25368371_797df554.bin -ETHREX_REAL_BLOCK_FIXTURE_SHA256 := 573004e62e3680a00d3cdbae19dc4897e2ec60d6ec0c1d05d9ef118cb8aef17f -# The block's source cache, hosted in the same release. Only `regen-real-block-fixture` -# reads it — the converter's TESTS use a different, upstream-pinned cache (below). +# The fixture is GENERATED from the cache below, not fetched. ethrex 25's guest +# decodes only the Amsterdam schema (`0x1501`) and mainnet has no Amsterdam fork, +# so no hosted artifact for this block can be valid: the release's rkyv one now +# runs 496 cycles and commits `successful_validation = 0` instead of failing. +# `tooling/ethrex-fixtures --bin real_block` rebuilds the block's real +# transactions and real pre-state as an Amsterdam block and validates the result +# through the guest before writing it, so a stale or wrong-fork fixture cannot +# reach a benchmark. Nothing to publish, and it works offline once the cache is +# there. Read the caveat in tooling/ethrex-fixtures/README.md before quoting +# numbers: Amsterdam's gas model (EIP-8037 state gas, cold access 2600 -> 3000) +# makes 10 of this block's Osaka-era transactions run out of gas. +ETHREX_REAL_BLOCK_FIXTURE_URL := +ETHREX_REAL_BLOCK_FIXTURE_SHA256 := +# The block's source cache: an ethrex-replay dump, fork-independent, still the one +# hosted in bench-fixtures-v1. Only the fixture rebuild reads it; converter TESTS +# use a different, upstream-pinned cache (below). ETHREX_REAL_BLOCK_CACHE_URL := https://github.com/yetanotherco/lambda_vm/releases/download/bench-fixtures-v1/cache_mainnet_25368371.json ETHREX_REAL_BLOCK_CACHE_SHA256 := 7aa88a5f7c5755b7575870f95e6c5c26186947f5e9e0d52199148c74e2a2736b @@ -375,8 +383,16 @@ define ensure_verified trap - EXIT endef -ethrex-real-block-fixture: - $(call ensure_verified,$(ETHREX_REAL_BLOCK_FIXTURE_URL),$(ETHREX_REAL_BLOCK_FIXTURE_SHA256),$(ETHREX_REAL_BLOCK_FIXTURE),fixture,ETHREX_REAL_BLOCK_FIXTURE_URL) +# A file rule, unlike the fetched artifacts above: those are phony so their digest +# is re-checked on every invocation, which is how a stale download is caught. This +# one is BUILT from the cache (whose digest IS re-checked) by a deterministic +# generator that validates the block through the guest before writing, so the file +# existing is proof enough and rebuilding it on every benchmark would just add a +# cargo build to the critical path. +ethrex-real-block-fixture: $(ETHREX_REAL_BLOCK_FIXTURE) + +$(ETHREX_REAL_BLOCK_FIXTURE): ethrex-real-block-cache + $(MAKE) regen-real-block-fixture ethrex-real-block-cache: $(call ensure_verified,$(ETHREX_REAL_BLOCK_CACHE_URL),$(ETHREX_REAL_BLOCK_CACHE_SHA256),$(ETHREX_REAL_BLOCK_CACHE),cache,ETHREX_REAL_BLOCK_CACHE_URL) @@ -389,10 +405,11 @@ ethrex-real-block-cache: print-real-block-fixture: @echo $(ETHREX_REAL_BLOCK_FIXTURE) -# Lets CI ask "is the fixture hosted yet?" without parsing the Makefile. Prints -# nothing while the URL is unset, which is the condition callers branch on. -print-real-block-fixture-url: - @echo $(ETHREX_REAL_BLOCK_FIXTURE_URL) +# Lets CI ask "can the fixture be produced?" without parsing the Makefile. The +# fixture itself is generated, so what has to be hosted is its source cache; +# prints nothing while that URL is unset, which is the condition callers branch on. +print-real-block-cache-url: + @echo $(ETHREX_REAL_BLOCK_CACHE_URL) # ===== Real-block converter (regeneration tool, off the build path) ===== # @@ -402,7 +419,7 @@ print-real-block-fixture-url: # # Its TEST input is pinned to Hoodi 1265656, independently of whichever block the # benchmarks currently prove, and stays there across a repoint. What these tests -# exercise is the CONVERSION — cache JSON in, correctly-laid-out rkyv out — which +# exercise is the CONVERSION — cache JSON in, correctly serialized SSZ out — which # any real block demonstrates equally well. Hoodi's is the one cache ethrex-replay # publishes, so pinning there costs us no hosting, cannot drift, and leaves the # benchmark block free to change without touching this crate. @@ -439,16 +456,16 @@ ethrex-real-block-converter-cache: $(ETHREX_CONVERTER_CACHE) test-ethrex-real-block-converter: $(ETHREX_CONVERTER_CACHE) cd tooling/ethrex-block-converter && cargo test --release -# Manual regeneration of the BENCHMARK fixture (not the converter's test block): -# fetches that block's own cache and re-converts it, overwriting the fixture in -# place so you can hash the result and upload it. That upload, plus SHA256/URL at -# the top, is how the fixture is actually replaced. +# The BENCHMARK fixture (not the converter's test block): rebuilt from that block's +# own replay cache. Deterministic, so re-running it is how the fixture is replaced — +# there is no artifact to upload. regen-real-block-fixture: ethrex-real-block-cache - cd tooling/ethrex-block-converter && \ - cargo run --release -- ../../$(ETHREX_REAL_BLOCK_CACHE) ../../$(ETHREX_REAL_BLOCK_FIXTURE) + cd tooling/ethrex-fixtures && \ + cargo run --release --bin real_block -- \ + ../../$(ETHREX_REAL_BLOCK_CACHE) ../../$(ETHREX_REAL_BLOCK_FIXTURE) # ethrex host-reference tests live in the detached `tooling/ethrex-tests` -# workspace (ethrex pins rkyv's `unaligned` feature; isolated Cargo.lock). +# workspace (the ethrex host dependency tree is isolated in its own Cargo.lock). # Needs the real-block fixture, so it needs the fixture URL to be set. This is a # local convenience target: no workflow invokes it. The PR gate spells out the # `-offline` variant below inline (pr_main.yaml), and ethrex-block-converter.yml's diff --git a/crypto/ethrex-crypto/Cargo.lock b/crypto/ethrex-crypto/Cargo.lock index 0cdd80692..67a36c419 100644 --- a/crypto/ethrex-crypto/Cargo.lock +++ b/crypto/ethrex-crypto/Cargo.lock @@ -342,8 +342,8 @@ dependencies = [ [[package]] name = "ethrex-crypto" -version = "22.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=4f658c2b3d10e3f21d35ce546870f55ca3f940fc#4f658c2b3d10e3f21d35ce546870f55ca3f940fc" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "ark-bn254", "ark-ec", diff --git a/crypto/ethrex-crypto/Cargo.toml b/crypto/ethrex-crypto/Cargo.toml index 09d53ac3b..f5315df65 100644 --- a/crypto/ethrex-crypto/Cargo.toml +++ b/crypto/ethrex-crypto/Cargo.toml @@ -18,7 +18,7 @@ license = "MIT OR Apache-2.0" # Defines the `Crypto` trait, `CryptoError`, and `keccak::keccak_hash`. Same rev # + `default-features = false` as the guest's ethrex-crypto, so feature # unification adds nothing to the guest build (no C secp256k1 / malachite / kzg). -ethrex-crypto = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-crypto", default-features = false } +ethrex-crypto = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-crypto", default-features = false } # Pinned to the exact 0.13.4 ethrex uses so the guest resolves a single k256 # (a version split would make `FieldElement`/`Scalar` incompatible types). # `expose-field` is required by the x-only reconstruction. diff --git a/crypto/ethrex-crypto/src/lib.rs b/crypto/ethrex-crypto/src/lib.rs index ec36b0831..6625a99ee 100644 --- a/crypto/ethrex-crypto/src/lib.rs +++ b/crypto/ethrex-crypto/src/lib.rs @@ -2,7 +2,7 @@ //! //! Implements ethrex's `Crypto` trait with LambdaVM precompile acceleration and //! is injected into the ethrex guest (`Arc::new(LambdaVmEcsmCrypto)` → -//! `execution_program`). Living in the lambda_vm repo (not in ethrex) means +//! `run_stateless_guest`). Living in the lambda_vm repo (not in ethrex) means //! accelerator changes don't require an ethrex PR. //! //! Accelerated today: @@ -16,6 +16,7 @@ //! Every other `Crypto` method inherits the trait default (vetted pure-Rust //! crates: `ark-bn254`, `bls12_381`, `p256`, `sha2`, `ripemd`, …). +#[cfg(not(target_arch = "riscv64"))] use ethrex_crypto::keccak::keccak_hash; use ethrex_crypto::{Crypto, CryptoError}; use k256::elliptic_curve::group::prime::PrimeCurveAffine; diff --git a/executor/programs/rust/ethrex/Cargo.lock b/executor/programs/rust/ethrex/Cargo.lock index 63705e473..14f929569 100644 --- a/executor/programs/rust/ethrex/Cargo.lock +++ b/executor/programs/rust/ethrex/Cargo.lock @@ -663,13 +663,12 @@ dependencies = [ "ethrex-guest-program", "lambda-vm-ethrex-crypto", "lambda-vm-syscalls", - "rkyv", ] [[package]] name = "ethrex-common" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "crc32fast", @@ -700,8 +699,8 @@ dependencies = [ [[package]] name = "ethrex-crypto" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "ark-bn254", "ark-ec", @@ -720,8 +719,8 @@ dependencies = [ [[package]] name = "ethrex-guest-program" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "ethereum-types", @@ -731,6 +730,10 @@ dependencies = [ "ethrex-rlp", "ethrex-vm", "hex", + "libssz", + "libssz-derive", + "libssz-merkle", + "libssz-types", "rkyv", "serde", "serde_with", @@ -739,8 +742,8 @@ dependencies = [ [[package]] name = "ethrex-l2-common" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "ethereum-types", @@ -757,8 +760,8 @@ dependencies = [ [[package]] name = "ethrex-levm" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "derive_more", @@ -775,8 +778,8 @@ dependencies = [ [[package]] name = "ethrex-rlp" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "ethereum-types", @@ -785,8 +788,8 @@ dependencies = [ [[package]] name = "ethrex-trie" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "anyhow", "bytes", @@ -805,8 +808,8 @@ dependencies = [ [[package]] name = "ethrex-vm" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "derive_more", @@ -1247,18 +1250,18 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libssz" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d498c0482bba87d2647ea4601ea76cf2b498065e3958798a88f49274f3ced5e9" +checksum = "b4bdd6d63ed811ae164966de20810be780e07de784a4834ccfe6be90480c369e" dependencies = [ "smallvec", ] [[package]] name = "libssz-derive" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08ddfb5c969c28a4a54043e630f80c723352637bd1020f256ee3ac7a8814922b" +checksum = "aeee1b9ac9200429f7e9830492765445989ea61c3fb9028ad5a96e1dd5f5e913" dependencies = [ "proc-macro2", "quote", @@ -1267,9 +1270,9 @@ dependencies = [ [[package]] name = "libssz-merkle" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63c6d6d5ce5d79bba66bc98c99869eedffedf7f14f0aa0915f1a62802650bdf6" +checksum = "863eca32d1a43e5ec41106a515552efa8307768d37c68d26b7f21ff13cfee1a7" dependencies = [ "libssz", "sha2", @@ -1277,9 +1280,9 @@ dependencies = [ [[package]] name = "libssz-types" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747273ab2d923e82ed147091fe0fb3e602dd2012c872cdad5efe69e27c3b4099" +checksum = "d4231ac301726840a3fe111f11bd4619d3c97ed155cb94c88dbf92b70e04e017" dependencies = [ "libssz", "libssz-merkle", diff --git a/executor/programs/rust/ethrex/Cargo.toml b/executor/programs/rust/ethrex/Cargo.toml index 1d2ef0f4a..ebef0bffe 100644 --- a/executor/programs/rust/ethrex/Cargo.toml +++ b/executor/programs/rust/ethrex/Cargo.toml @@ -54,11 +54,7 @@ lambda-vm-ethrex-crypto = { path = "../../../../crypto/ethrex-crypto" } # # KZG is not linked, so the point-evaluation precompile (0x0a) is unsupported — # see src/main.rs. -ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-guest-program", default-features = false } -# Exact pin: must match the fixture writer (tooling/ethrex-fixtures) and the -# executor test reader so the rkyv ProgramInput layout stays consistent. -rkyv = { version = "=0.8.16", features = ["std", "unaligned"] } - +ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-guest-program", default-features = false } # `lambda-vm-syscalls` can still reach the graph as a git dep pinned to a commit # of THIS repo, which upstream bumps on its own schedule. Override it # with the working-tree copy so the guest always links the same syscalls as the rest diff --git a/executor/programs/rust/ethrex/src/main.rs b/executor/programs/rust/ethrex/src/main.rs index 8154978cf..8ea1c5093 100644 --- a/executor/programs/rust/ethrex/src/main.rs +++ b/executor/programs/rust/ethrex/src/main.rs @@ -1,26 +1,11 @@ use std::sync::Arc; -use ethrex_guest_program::l1::{ProgramInput, execution_program}; +use ethrex_guest_program::l1::run_stateless_guest; use lambda_vm_ethrex_crypto::LambdaVmEcsmCrypto; -use rkyv::rancor::Error; pub fn main() { - // Zero-copy private input: borrow the memory-mapped input region in place - // (the host pre-loads it before execution) so rkyv deserializes straight - // out of it. `get_private_input()` is this same slice plus a `to_vec()` — - // a full extra copy and one large allocation (~50k cycles on a 20-tx - // block). let input = lambda_vm_syscalls::syscalls::get_private_input_slice(); - let input = rkyv::from_bytes::(input).unwrap(); - // LambdaVM crypto provider, defined in the lambda_vm repo and injected here - // (so crypto changes don't require an ethrex PR — see `crypto/ethrex-crypto`). - // It accelerates trait-routed `keccak256` (via the keccak_permute precompile) - // and `secp256k1_ecrecover` (via the ECSM precompile); everything else uses - // ethrex's pure-Rust trait defaults. ethrex's trie/RLP keccak that goes - // through the free `keccak_hash` fn is still software, and KZG (0x0a) is - // unsupported under the `lambdavm` feature (blob txs execute; a point-eval - // precompile call reverts). let crypto = Arc::new(LambdaVmEcsmCrypto); - let output = execution_program(input, crypto).unwrap(); - lambda_vm_syscalls::syscalls::commit(&output.encode()); + let output = run_stateless_guest(input, crypto); + lambda_vm_syscalls::syscalls::commit(&output); } diff --git a/executor/tests/README.md b/executor/tests/README.md index 6ecbc7212..36b8fb70a 100644 --- a/executor/tests/README.md +++ b/executor/tests/README.md @@ -1,79 +1,47 @@ # Executor Test Fixtures -## Ethrex private inputs +The `ethrex_*.bin` files are schema-prefixed SSZ stateless inputs consumed by +`ethrex_guest_program::l1::run_stateless_guest`. The first two bytes are the +Amsterdam schema ID (`0x1501`); the body contains the payload, public keys, and +execution witness. -The `ethrex_*.bin` files are rkyv-serialized `ethrex_guest_program::l1::ProgramInput` -values consumed by the ethrex guest (`executor/programs/rust/ethrex`). - -The native-reference tests live in `tooling/ethrex-tests` (a detached -workspace: ethrex pins rkyv `unaligned`, which must not feature-unify with the -main workspace's aligned proof format). - -The ethrex guest, the native test reference, and the fixture generator are all -pinned to the same ethrex revision (the open LambdaVM-backend PR branch, until it -merges to `main`): +The guest, native reference tests, and fixture generator all use this ethrex +commit: ```text https://github.com/lambdaclass/ethrex.git -4f658c2b3d10e3f21d35ce546870f55ca3f940fc +2cb18b0b95b27a2555d3debffdebc43c9685d6e3 ``` -### Generation - -These blobs are generated reproducibly by the in-repo tool `tooling/ethrex-fixtures` -(in-memory, offline — no RPC). It builds a synthetic block with N signed ETH -transfers from a funded genesis account and serializes the resulting -`ProgramInput`: +Generate the committed fixtures with: ```bash -cd tooling/ethrex-fixtures -cargo run --release -- 0 ../../executor/tests/ethrex_empty_block.bin # empty block -cargo run --release -- 1 ../../executor/tests/ethrex_simple_tx.bin # 1 transfer -cargo run --release -- 10 ../../executor/tests/ethrex_10_transfers.bin # 10 transfers -cargo run --release -- 4 ../../executor/tests/ethrex_bench_4.bin distinct # recursion profile +make regen-ethrex-fixtures ``` -`ethrex_bench_4.bin` is the odd one out: `distinct` mode, and it is read by the -recursion profile target rather than the executor tests (see the Makefile's -`recursion-profile-block-input`). It is committed like the rest, so it is -regenerated and checksummed with them — a rev bump makes every one of these -undecodable, not just the three the executor reads. - -To regenerate after an ethrex rev bump, update the `rev` in -`tooling/ethrex-fixtures/Cargo.toml` (and the guest's), then run -`make regen-ethrex-fixtures` from the repo root. The target rebuilds the -committed fixtures and refreshes the checksums below. +The generator enables Amsterdam in its synthetic genesis and includes the two +EIP-8282 request predeploys required by ethrex 25. Known fixtures: ```text ethrex_empty_block.bin - sha256: 8d6f6061c71c23fad1d5dee26242d631efe0bff8d7f49422c2ba4cde9d4be919 - contents: stateless ethrex empty block ProgramInput (0 transactions) + sha256: d914d36e673dc0e24bc4e105f3037e78305e63f6121e1937058dcc704fabbb8e + contents: stateless ethrex empty block (0 transactions) ethrex_simple_tx.bin - sha256: c40bce364f22758ab7fa6fe8b45ce4c305dee5add4536ef6dca0e74e410e2729 - contents: stateless ethrex block with one plain ETH transfer transaction + sha256: 4dd4ab89d904981844f28b093fde0ed18ffa4d61273482eb8592d41db6a38e7d + contents: stateless ethrex block with one plain ETH transfer ethrex_10_transfers.bin - sha256: 4d862e8537284729ff11c7bcf91c971e562dd6bbce2a1e181ba5bf48cb6b65cf - contents: stateless ethrex block with ten plain ETH transfer transactions + sha256: e86c5fc80b8b603c4a58fd6ab6ce5bbb40d378c67c8b65f4d89a15b01f69fc6f + contents: stateless ethrex block with ten plain ETH transfers ethrex_bench_4.bin - sha256: 03ed0d175622af6ef9a981d7652ba7c86630b9473f49cae17edf649724b704e1 - contents: stateless ethrex block with four plain ETH transfers, `distinct` mode - (N senders -> N recipients); read by the recursion profile target + sha256: dbfe0d808ff9476ef70bfd4459b82330a2dc038bdfdf2808447ed04012556386 + contents: stateless ethrex block with four distinct plain ETH transfers ``` -## Real-block fixtures - -The blocks above are synthetic (N plain ETH transfers over a small genesis). -For a representative workload — real contract execution, real trie depth, real -bytecode — `make ethrex-real-block-fixture` downloads -`ethrex_mainnet_25368371_4f658c2b.bin` (1,110,165 B) from the `bench-fixtures-v1` release -and verifies it against `ETHREX_REAL_BLOCK_FIXTURE_SHA256` in the Makefile before -moving it into place. It is gitignored rather than committed, so the checksum -lives next to the URL in the Makefile rather than in the table above (the checksum -script only covers committed fixtures). See -`tooling/ethrex-block-converter/README.md` for how the fixture is produced and -repointed. +The real-block fixture and its source cache are separate, fetched artifacts. +Both must be regenerated and published for the new ethrex revision before the +real-block benchmark and acceptance test can run. diff --git a/executor/tests/ethrex_10_transfers.bin b/executor/tests/ethrex_10_transfers.bin index 201351189e5462b12704ee2e5084ad44ab349682..90eb03c15247a4d44d39ff9722aea9bdb4cdd429 100644 GIT binary patch delta 4997 zcmb_g2{=^i8$V};u{8D(%5p};$i7~c5G_bqMx&HDLu7s{6n9`9IJ9-t#=~`ObO2_kDlw`~JR}_nA2#{K839 z5vD;9)FJ^vo#ZYuc!XG*5Tp;*LJ6EBtEp88wgOJn&91J=AIgqJ_smKZF(+|6+yx{1 z#Y1Vaj-*-3Hl*9r>{P3cO{1!yQqnJV(C~aU732HTIa=q{WE5`yg^_2XPw1Kd~=5mzNa5HDQEJl$S3&T1B) z_8P5JaQY=1w6^$-v$Q0xHG z3&bDz-3783R9y~4PXvNUg0Mu;hE6PT-k}@NPNk($U7}FDhj?ZNthg5AVFV}SWd4su zcegv*4`^R{-4RQvafnUUA1$=$+NIb+di0Kn8L@KRv_4=!Z!dgVNygLDb(^kR3YO*3`TzJEwjxIJFVEFmm6J?98ahnCQPKt zO5(15@{AiP3w@kGYMUNeW7{&nV${gAxOlxK_tGQ)>2CnM?%j)8HC+X*UtwsQpnS76 zx)Ng8oj$#2W4;K*!);iJaFJ2h@G1W495dL;x)sLU{5^O9#|Jg6KS%k*&$H(*%mTr(QtCOv* zyW{Marsk*S<0W}k@5U&jF|KhEK;&-#dQ-1m zt|x8tJ7Ug9H43Brf<;IlY8zOtWxDX}>eB%S>L_m>J6_*+;zH#lfaqTWz{EiqE6qwg zyK1S=f{@=Zhpa5}@2o;?CgWJc*IC=)lLZ8m=XH7DX7ypl{&RZj>3aqx_8X*#K7GGv zALp7`vcoyvBmm`a0JiN4QaPnoT&14p%s7$Qy<6ed=mM?H*Bx7;)(36R)&7lOl$E7# z2>C6OXW&rMmUHx+4xPTx^jg-gUG~eG&rWMim;?~}O8|^`(5`5=u{JrU?Nq$Tx~+*D zoor{^=a9SE{wcc^$;GE15fQn~ejm#n71ze~a&>2VzoP||Y`rhGS7v>xvY5l2w5f&_ z4NEn_N!)qnrvr`XSFNvVrc44z{5OCL#)FCQB>^QN1#jl7QtvETBSD&-a#(35Y1Cw1 zW3R7un175fVW@m_`Y#_<%HGm{53M`saG`{yox1(yObu5B!^O|mFL*OVsukSM1m8?? zJf7e@A5P;PxD|U{OtIAES4PBMH;7M~($p<{K_ehOc}nwO4fAL(#21;;yxCIEu!H!b zQ<~8IUt2|*HmJxir8_+tD+4=lD8EQ7*HylWUtB2R1w+>dcveK#$+ zNn&38N(D?hKK{Gy%WVy}mhM{kg_W}p>d)swk?!AZUl|Bad=a@a{WDWCdCMMSsXx4h z4YUL3L*hDpb#rB#ekPguD0w0f!=mFA_To84{IBsKOusu5lCc?VHN|%HndDq7zr0hk zxw@EivngI8yrCXT3=FbVtBq%b0ZYNV%smcF_h{@Ver(RH-s9?VuQF3_oQV1}jj?tv zs)gUt$O_N0W`0Mb8S%_(^g9}5`1+A2zopUnEgYTZ@hy$cjV3ik!&5ZIDV93t`Aanx zd`qJ}pTDKi`G*fZ>UmIm4Ghnz~dV~B8@(vw=mo=?_K2&9)SjG%a;x?(*^qIvfMyq$FHKa z0SqJXWNm~h1e)+8a1#y06KweRpes!>Pq8q&bgEWT-tURg=1XM(3u8cb_Jc z6SxziRq_gb4j#Lro9{;E_N=1R3i35$!adr~%zt|Ivfz@o(3HUMt0G2|PGA+R&$09^ z%&4zirjY-3kw+X=`Sl)C?+SR)fpa`Xn1w#VFmi*WQdH!ND5-wX{na|gks6t_M%S(*w308uS>x8) zDtbln1!vXLs;)*zR?EMY_gmscw~JGi2#&VeFIQ`OT@V2}o1u^Bj5U^U_#~}f@VmOr zlSoacg=B+A8;7kwT!@<6x{TV@eb7RXPvGzMkvS722%>Y4hKpx7`Kc8t*)i?T8m%My zEr$0mXsJdG3iFhLO!UgV@Nh*hZ9xoyWVAm|aIrG{`ufM#2Iq?YVzfs`Ju!%0n6xT) zaqb1?xFuPTPT+;Q2w6zCECUf0A`$o{1|l|&N#G~-5D`qkC-4=s5iN|^&qY2xzV~j= z&-SHO?R!IwIMbPw`A)^_qo4ToIGNI40Poh>Q{Htth!#;W|AY#AHF>l#hEMgM&#s0N zVog5roZDJYC_fJ3R>%npj;s$u4D*G6ef{lh&W=z`Ox78BL;Htd`|oUt;o4Wk(V|l zX1U6XatHmHbjKzR!E8$B=~p~xFAa=DkG(!5;JnYS{@_B1xohLYoc6+GFZk>mjX{JO z3Gq^l5CdU&y8+oPrL9M!ul832xySNvKS+QM%9wp&^WK^%9}9B70Fp5gCQ9I_!kByB z_|l~yj3P+`MPHJN+F+3J(f=4H;>3@#Id`%w%ARK=6$J(@5H%!NvY~j&J1|r zfzeUVK`)P_4QYxC&dDGBkVxRFGlVV+W{d~On=w{g*O*drCNdivBFPcV+QN0}NA?a# zV6h!cX2Ta*zfP6_J&^p$&Zy&rwf_eK&omwbHU^Mp4;vmQuC))RdP=H(+WK3jg7WE( z`iDD19vyr$vXsefCh*V3hy&3#XK0SqOMG`Op7)m0J+hvq@*toDB9I*Ijyd$Wl>&z$dO&P%M}_*)80sPS^*O#0InNr>T_X^&QS2Y?e&2c zv0PihUF7!c$oOmBr5>sa^S%;zo{2EGdZBUKfXKE>Sg5hS?eN^)5l1i8mVaj4-Z(tX z|E@x!q>Zu)VA2$r$r#^By{MUiU5yJBnKD5XU9gDJR-G$W2=&O+gM5(;zKKJ0io zMKqYjfw>8!!~_yoBpu+UbJ681eYiMRB*V?k%@QrAp?g4qz|UNGnsVL50y=<8T3r<%$UHWiEaKQ6-K%>x+tN3$>eZFzfEjM5O#4WqIPs#$X zGr|QmPmkF)emQi}js25%?~Pi5`1EmEf4tmj-MFdc*^=ihkw0NQe>b#bZsfE%BL}^o zCs)`RARy|v@bxJOWz8B0#afAMw%)!p({gfLoSKh*QSNbOwnZhHToGOPJ}L0EBI#fu+ph%#v^@}--fJNIu9b-7uYZma zV!LM!%gyz)2PYD5mvu_^a2$O;th2?qgqt^m`)mm*UQtkU^4gQc$I~}Yc^DrQb7k!9 zrg?tKobfhY#r`cI=(F)D7NpwHowO39pWXD}kJB07&NZ2{cwxk#!a&c};((E|2LW5= zK1?|i9afT5V&8npA&&|kZyj#ECfx7(xw9tc3Omm5O?KE(G0LU|1gt$Q`2D3}VTe|O zy7)CO>f-L6``R)6!W}S77V>ZjlR&*#c3^M+ni@q~{fUAfR$m-i@l1GbLu779-Vm1_ zN2Yj$HAD?8IASlfe$~4L1iU>E;@@c?%+N|?&t7Cvcjbj0_CdDUW2*(}>0XO3N4byp z9hbK({m^1$V^O|WO8?>|tB3u5A5ZQ3J=wHU-qj)hd;86&${LRc7X`I|AZ!nW%(of{ z=~{_gw#`~Jby`CC(2Mas+?%hMepkEf2YbI=T~Fp%@9R8u`NHJebl$ZQUoKysUhFbN zCbP@W+*Wzet8dZz%TY7rg@yJlAQ0_=Q1V6tp-3yi-P|03`FE)ip|ye~w=6w|&s;Wt z61H?-<+hZwGd<0#e(B})+ngBLhT#tJ#I5-UBhBi*?$T>*ebT?ohaPzqel07m1q4xh zAiVrj1L3Jwg8n;`>Es>>;R!)-0?c>t6{n6VsEhM#@;xmQJX-e*Z z!E09_rR-n(W2`DiH`SPOS8m$*&v$GAfou;1D{W{qtwbiyi-`GbVtvWre$8kX$>!9a zC^^CBI%D<4vprDNXXdBA?MPxIN0C%q=*4H!^Wm|jpE%)hmf%4KF@cT(>w9O^5h~3HG z1^W*9*+0sn3a&d<6xlm$$gyfbQ$(SGsn7e$?^w;G@rZndIU;wa`CEt-%X_6%$`ySm zq$t*IfSMUTKq{s5#S@V$9C%4LDFPcVlBuQyyy1^?Xmb_!9aK1tk$gk3whdy6>xJ&u&j5B*Hw*35ZWtuY|CzTzFlb%O>GGW9BSXhM=N8uubMp1whB!$u> zDMCn;Bw1E~G6aK)C=9wGB95T}&2prG5(7X`tcauphzY-)?#aipQVaAOc=~uic=_5> zXdjf`a+x)%nzFR5H{f4))OP^Bv#F6XSfv1dg0U@AV>@js#X(0ndG+9cN6fU}vG;X# z_cmPLcobp0^vlMkU$ZN5)n9@6(*zv zG>&5?JR=UaK%=!2Gd^a6VQhCH&QTPJGD4cc7(_tf9E0IFN{9gi>7qoW0L4Wj0u>QL zl4A&pW5k@0g@I8HLsiUqTc8Wz;O_qwYae5?wH@&8l!h7^rsvDx_90zP*fF@Ww50m# zNYy0{@X;z0;8Qdr*9>N%%8-Q@8ncPHHW`}ldLSUG*TApR)>2_X9b$U-Br7%C8oP@JZP z2+DB?tU5S{3kjU&aBY>zU|^N`zwQ?qmeHD3xMYtFzv_G7t7>kICzW`QO-YaN1Npx? zOucvkwdsG`lfK-lqP5BOYq1fVosEefY*0Q%t;%kP09W}mu6lCg<~b>0{--T|aXUI> z`otPZ>V;jdCxYy+XUzfwT2DZgkUb`?bD*+E8Zc=h1AfJaCSyo|&-qlh!PN-?9v7e4 z?seIk9UJu1^XL5J^fA!ga)^Zzo?BM=jJ>LR;fw zajLPhQl$Vs+hEg2kYQI;r=HCELrhLs>LYz!KcOkImFBQ|kAJE++gL!ot^w+buGl|b ztL@$haI3E97<*tByP(J}ekqq3^Gz9ec(yUVzc*dgZLREKGy-ZDmp1qZHCvhnUJZU} z7B|Y)GyuEWbN15W+CyWkhU`*8u*DW?%)hBZ)WUn9_JpyMKwW<<0e-qf?Y2rHP13ED z;+sl10052mluFg#&#Y>VuCl2f-Cwd!*I3nWz3G2wSQw214ByLTmR1H$=VP$Q6%nC& zP2XKt@1kz2{##T(k&)9!sCCu#S!MfB4+u$8_rpsTP+bu{{C`yI=J3tQ75)-gMuF$R a8OM{ioWh;m67jbXVH3O|!t0Jm7yUmySq(h^ diff --git a/executor/tests/ethrex_bench_4.bin b/executor/tests/ethrex_bench_4.bin index 23c2453ee946e46c1b8ab3d8b9fca79d03b11d11..3a0cba1097ce1f6383a186e7fcf4361804f14322 100644 GIT binary patch delta 5056 zcmai22{=^k+dpT6_rSt#nz$>XOh=?3hAXyQj}#- zRO)?+QYjS@QoWW}Bg*%jp|9TW`v2eWT-SY`XYS{B-@ki#&T~E2@Z#>RS674;APBlB z0zvPI14J-|R_H>I5f~FhP&15Ig1CH|*TdjroxwIi>syzMj#lB3(lsv>_Qc7OLNT<<1aHQk!#vpYvR zfu?!_Q+Rp)YfVytPN~-z(`;JY1BFG5(0L*n|0;9^Y1CMFp_n=Wf_UM2X%j;#DudZi{!Gtls5Q)% zf3yA-F+sX~d(uQvDoJkY5+)fVy4d7M+4+p%Zdi<@;(9tR|62z~&VwpCz}@e7A@d)U zgny+Z2pFr}pA_v;;WJz^S03GXjU0Vqxsr}HYcNVXuT;&sf+rr0k>5$>)%2b2+0ocA z8g4%3X=(dp(_@X8wXTN?%7!BRWTNEOi4i z@u1IZWhw%hZCTGOW6rxn;RN0tCQQHq_;T>mXwQuusVObu%bNV=GiBK9e=OgA#c2Lx zZPJP|Bl()!^Hl-!vT+fkPFG$GbTFCnQ}9)3Ew3Un3kz_<B+LKoS_vPh&{qIUW4XVLLZQDOcb~p&w35pvDFvn5O_f{{JgdKsNxCG8zyR z4dyLCG{Jci0ptz?yp+LV!~*gGh%Y#Rct8w=ASeTfxG)4A2J#umR&eTe0`U}upa(!! zib2p9AP>YLC|3f4IFb-l2-qs6A?TqDU;!r>MGk`Q$wLs|i}~}x7PQBynGZC{L&l8w z!~ws$gxj|jJSuAEuc$NHxz~MnG>wVrTuyoZKI*Mka9qcE!?$w!5wE6RB7J+U(x>-d z+|%7+4!r&16HC<^H&_vFQuwiYsdtF6#`?A6lr+8(-Mt_LiMuAOFwSIRw_N^Qd)e=` zOsbQ_!I#o08CzVTvR*V7k(9zCn{$St#%hQuKyUd>_cP=EPGvf!Ni9x}Obo;9eZAyP zX-geZd%rK@f5S3*9~=KV^IzMXOg6Uurdum4%_ps0!z}Hs@>-o4X^TMNU01RGxtv> zMddYL{B)ico(4l8pW=^}JO(}dT%t@^nQvZ?ph4dg0ZnF`DJX`!QPP|7}kRU@^gK0=~a zepo(ejjxsyLtQ-Vp!I3FM#ua72)x-!W{94k?3nWx8O`&rHSJ$UYP+l;8o$^*Z8KUC zW!vd4H87ZI$uGiCA#=oVu0{NaeCr$K{y7dZLSK2=P2_e0m4DyE(gmI%Nng>kmS|P1 z^g!*5Jr)C?jh0dVl!AS$H@C)Y21=g1a{0WQdE7gFt7pTk^JN;dKplQ0C`T8oT&|~Y zb(iefPY?4RQrcv@%CZUA#A~|JzK>5te?dOF(Y*BJ#x3onW%iGE?a^jd`%R~)`x7Uz zJqI;=8sHz~2lJ8Uo0nNdr>aioM0dL`?EH4xa{9Ex<663LNuhG6nL(v5ny2Ke!;i+0 z^zNQ`H*1rbp>3Vbu2o|f>7Vs96GF90k~b8rE~udVf^4{?B_EsrN-cW-it$de6lA6} zt>WDFjdNDnEw9VEVHPRGF9YR0y7)(VEl-TvW?kGWA86E?`rWs7XP0N-8E>IYd)CI$ zTMr)Up<-y14gzCnrVavjKWNQY=hUq5Nt5n-OB0gvp5$?dD;#FhXyWs7j$hAZ=pK0s zK9M#Ib<;u!fG1T8(Pv^MgpaKfPfH#M&p2lnQ*73K&E0g=H~i+oc!k=*Gk+0+W}Anh z_1ef{?7UsEDs3b2`@QHksV#Q2S|~o&>0uetM9{9oDK_`2^jcxV06WjDWcI*2OtqHclP+b#5SP(thYJ%uto_gvTp+hZG zM0@=~v);5Ir9}l#uT_q!JYS=$MJh{Z6a<8!wM!5YCVvEHe7_Vgg18O-HWa+q*&wc9 zrL0Zq>*MTts?lAC6lZ^~-|DUJQzw@6p3Rv_rW~kQUWJJ!7k`p6I(bDiuficq%i$c` zPI`_1mcFU0QHRLq++=2x$I!F3Ai_yl4nw;aAS;>J-VQbM)UE+n-KWMo9|S&*Iooah zM__SRef&UJ;P+FPw`18 zJL)D9-C-X=p-U6Xo# z=)l3%xO=DZc?Zfn^A6UJRfT%Rw*C1m9?F!pn4-aOLvd3$qDP|cT!$7=9YJGMjpRZs_?^?ANQ72)oip{G6^sq?6+ffQ(ro&f?5 zbWCD+^TbrlBc7k_<=sapvQ0PRGKD(qzPda%FV6JXRR0G`>S&2F0e3WbdfN5q6=Lc4VJ>1j#GR0sbnS2F9ugc;LnuNW; z7oZC)3|u^TMBli}_k!PdHCgtrO#fo77GKO%{>@^CKYyn$op#dzLvu;EbsY)O1>6xB zSuw(qST4&;r?hLJ^i_+}_eO1wwJELQY;T|U^zP?u3^h=gCE_T6AT6SQIJ`mKq;bx$3~FDD2*jsHFJ-Ti!)>UDU{}^c|q@~eEOM` zcTP+Ci*2@yVkoiwm$Jbv(354mU8Xd!zs76C7Hp`D$f;6V zx1#-m=f#%@-PB?qswFxf ztrqb^14V*Rc~L|7DC!`(WPzyy{V3Hc2tGmj18BZ9I_wKveGpq%E-xBKL`TJxi9xUkDk-kd zAdsnam`$L=99jU4EvHBiqq1O5JRu>Tz!6FVylEWzdcOb;$`MNU^z^i%S1Qntfn0zc z8Gp^?!UW0Su9jMc&dfbZ%&p)*0!R+m3uXiknK+9A!GeT9P(%iWNf*I$rCG9kaV0W0 zI?avrxsf?HVwN0VdL@l!4PF>-K=g$I=tPh`g~=u|gJ3`iYfwGGB!CvcVu5ls3MwjK zVbLoIED8(M9~2jcDJ+fIDFFu7-gGDRpV71E^I+6kY`G;4B?9OyQfO|*p$o$lL1Tj8 zh5y@`Cv%;713E)qN|6518br_pdAv{ zNO|Wz5wQ)SE28==~q_uYK!^H6fclqI!oO&gzm>J?HOlVW3D-pDIn)s>~Z?S(F6K z^jAgr>D<%5qt#7!)E?& z_O)Hz+xGS?^hx=C&a9!U%F+2=UlC)l_>PHCU)Oa@>4c%$`loSD1)0UiPwkG#O-ge{ zr!Oo!+BK+mrBb$k=fsA@%wt6>x7cwvOFolMqy^`tsK)k|EdNgA@P$%HzsK_RPw*bw zr|jo?7np>(ZR?fbMi!iQEth>~?q1?lAhfl>)C;vv{W!MpFz>^m_X&TkQm1r3)01vvx+#wLIv z1a&zgAZJ*~a%_U2h%BcOMJ~CQV^P4EaLvA+o|#NCL2V}i$F?JM1z3_?C=`^Ra-4uOxFR=Ffl_fKV zZ=vrpr@EdET3UD4?wk(}6;Dmywsj~Iy(BOBjfGc)Ua5f#zJ0KKa#p9X+9E!kzG&5^ z%m%T-1jSJl?{5Togb^GeE-MZ^w5-_Urf9rWxV3e}+T7@jf4>u5E2Pz&@rj%J&T6xy zV`^CD|stg%FBD5 zTfH`I_lf9H7tqX7zHVs!+B@IxJY`swR-i{(fm~SF+}gw36emme;x!*V^(%jA&egJ+ zv-4x7Cnrt~JGGga-Ta${$IDhG;O{RTdt>;(VT)px7qw{h@J#26XKfD}q{SVhjmI>4 zW(rtg8zSxKGwqeA;e7{RjyTt)Vb8zuZ}#s}|7i30-btsDf_F?YF2>j#_TYipKlWUd z8m;~uJkW9E*TA-GN~;y{&2EfOKXd5r_LXm~n|UX8Paq8qsm88KiCvJ7GY=eY(yh*` z8z#k^E9|v8qX{#yQU7mfo z*IRF7WM$!^D?>Jyjk&-XsWf`ai?Mrg`&>}@GC_!!HJKB!LI~mnNhXRUiC7|%L@7g5 zK_#Nh!ZIvss)7`%NsO!`B*;{hWCjNq%jI?ot2*gZRfB*LCiq4RrN&1 zyz1TwWNh>>?JycXqhHoX1LhVEi2R~u{5t8~x3;bPw3|?xoB43uPhI+Mxs6enKDeut z(RA2LX>_&YXhM4XU&bEFi>wn@kI%WbtnE`{-CB{+Gz4@YBQ3Q zQw3R5b&aQ>m}o2sFa}6*Mh(dpLMDNNq(6oCqx|46N5->)1&^KeVphJaGGqSG6seFJF}%KD1f1eA;2|75~-vLBGp(K>r~LG#4((% z!c(I}(Rf6tEXynwc!9x0cDr&UAl(v>uIS35n(P6X_=5;vC3@}jJ;39RDYA`bqq zLB?6P=PbQ;c83Q}|HV~Hs?dpyu0h^&AY*h8xM`?$cyM{2(XlDn!B6(vbo_MLYZE_L zC%0E;&1p49y4t#M&b*NseY~moZTU;8R&9FmuDJ*qHLCe2Y+23Uvq_W_6b*5d@)9Fp zm8FzuqJR}u<26ZVF<}YAi-c1Y9urN&P{cTm;{{I9I4D|XBHjGICz74bt_p8tI~#2k0*?$C4=OBhZMAvscJTCumjlD!**$&gvdLTKba!5Cn-X-bMeR!mf4Fa} ze{y@B55M=8i*(N>KOZHlRN+;D5k<<$Jb^AnWE2Jp0I%{Ife=(w4vs9USmi{-=#q?h zO@J#~BPv0V{H~b%D;REeER|z2nU|>o?GWIm!KKUcR6)87n^hHVWR+tnMmkj#EGdY{ zNav}}BMeuDMk$AcKX|(d=*$2MLOc7FCqeWO*5mbEG}MO_^fUg=pQIa zJfMM9feH%C!f6ymA`lN6C-Q;>wNb^gN>v2CprXhMl?kR;pp@!}X9dQrn9$}uUfY3; zo{HBRfBQhE-mU+Z*S7v*1Z>|wHthSeo4dgALlq{_IPt= ztAfXmb-(io4 zEayCH$|a7Xmj`$`@AQqI8@t9lJk1|y(qHN9;w5;Rvda7meW$qQA<~fy|H&zvU3_(T lYL23mP!qi=~Q0VP+EU@LRLq`;(nG( zO84|b?AOnH3*Kx$@o7(+7l`<;I4Xbc#G;^W?q&b3T^f<-W*-(jA<|5`;-NrnSn(~KWh;O?@ocq4Z4QOf0p{j9xt$OsSUL% zA6*?GXL6q8dc~-m(++D--5l&1cMqywL&C-jNPkmoK8on($SD((HSNHkI$f!iug!QBy}W>jsgCP{P`CSA z-}sN6OIy|%${Zd!=rdVPkwvzcu?`C)Xs5_%YYYJlMKeo}HD)k#NRij)VDqVp502SN zH=uu9%V=lDeITue_GEg#Zv3db^BCtrlDdXPXBXLEF2oefqSE3a|C?XD8}PEpaCr4? z*SeaMF}KIMU)C%zsU!0r1Qr8kiVU&SCCs74^U=N}cQDif1d=<`ttYlQmb&DPZtw;NX(##Qbe zpWnaU=UN=q>$Ioo*2YJj{xNZzDU!{^9H}wu!P?^Q|B+fdKc_IDmGE2!m&Tq;zW(XJ z!@NbWRQ%Hv8NW{3aF{O4pWR0ph*W&K~D6<1SAn%TM@f!PK5JAkvBVR>DsJ7%=uQ+3Qcr2SDrz~i0 zJJt-hf~C86GmCZ(v9JZi6vCoJSB}T?O1_*hA~FX>BJH$=Y)Cwbf1CGMuw?LF z3hY5|zV8T6YaRVT72CLE^r-15s=m9WY^_lZd~IDZ`VwEek7E&T$_&>2Ty7 zMHcG7=V`FH$DOY)I2I61nNx-rQeHcSMpi zw*U{Mhn|~(hZJY~;j735J*Pk$u~Gr!D1z(33Vao!5aGC;A_8ZFCm0ri9R{m$1Qw|y zz%$+-Ez!V40b2&Uh=Dsw2J=Z@eV-*Vu}~_(VG)ifl`u;Nr-_9^sSMs7ErD@FjY_0Z zQXGx~{Gbl0^ey=@%Bi}DW}#qX!;rxYG0r0U8BTH;!WZMP62W1$R3cTftnp;A0#@%q zviBfrJq;*9qsGIcC2CTwS0EG$eer5Go(t{@;`sD5dshNnmZn zaCH%(LWZ$%G%x6CHR!Rft?wJGpc_mmBX9xteYpx0w}8oB zy+9ufM4_`WVdQQDKdep=61TiT&gVt`%1Y#?$NxbJpO2wGJkDiSRA`*jr^pvJj#V!W MnqipIU>HpN2d)q3Pyhe` delta 2078 zcmb_deN0wW7(eIS?~ik?BEB}#yf+ODT%rPPI)Pb1iq!~^#VmrEEpbWCY10-;;}07n zUeAU}#-fbbDkgz6iHJ8{u1qP{=9Y@g{vaLAkCBBGw{zb6zW03-+N^cAd!CPT?(;mq z-+9itdlnC^wD+E>Pu#L>_sur5^x-pwA8HFHj=lN$L+cJ_WDIsU9XVb?Qv-I;Nwklq z1`j36UB$n)TeZytd;5krboMN2c;vBkaAMxNdtevc9kBV^(VYuLbHa6lp@ll^uTIGT z{NYnN46AH_!B8sAzsrV9Su!i_PuJx$^CA)^g%x@oC(&m}1vN+!{eq-*Zktvl%yaRMC_*PV%amZ~foxeKx~?HpHcZik zf-VtLlq95Bz+`}QRTGe;stQsSStF*R6VoCx)+_-L5qa1eMTTuB!;<~4u>BL}$$miF z1rLBD`lw>dCV}FROF3jfUydS=vJ2>2-b}A2F6fM^!5CF@jqrE5T*Ok$hhXD^%(V@T z`m@E;H>V{l%|CqSrd7PYylYdHy|1s(-F7mv8qhi~LYrYvDl8jIia~&cv8)LSG)0UN zQ59&IL?)UgLL?$VR*_^FGDL(x)>#sfWJNNF6zQb((*Mo2BQ{6z{|#}^s)0w_*Ip!V zj+|~V2mvw zi;Cd2^{qQ^Ye2t|eUwvTam3ze^?**lGvHIwX{l2rV(pokkb2?Mth({SoZkutgH6R{ zAN4ONJKUSKX8)_t4Okmr@!PW`768zF@y1bZ0QzIh=RBC+nOf6a_| zosUhnS@om7`w24~x0<`@SlFEh!HSFL5~|w|;+_2|TWd+n<{jUHOUFjHF3mFr?`?E0 zRY7oh*U9Sz#S=+0iUvwe=kCFFP4xQf9dg;{0E`T~uE^wuE1u1L{23O07PySsT#r1y>pv)gXb>)A-v^zf z*)c1+Cgur-D$9M#pZ?|AlCUnbkr%uEg*9PYVLT5C{ka`{}wrDijSM7nHfL#dpRLMbzIrnX1q z*j!plZMKcVP6?I1LT$>%rXoqn<*Tt$`<|h1cYD6)`QGRGzwbNm`}_ZY|J!-qsa`F1 zXX(Rw5Cr+sA?O)xkOsEUEfxgXfz^~wbRcFHrr;{JqIrJfm9ojgbS&3Ai_SrWA;I$L z!zvr~(w8FU{+q;Jdfep?B6qwpX6yK>#W;KO8jl$Tu7MlHDx0m}viHPhf z4kNVL>)3l~L96fP=e4OWX$jZGH8L~*aeb+$_121g-cu392Qr}XG7*#<{Ez!zCK9rq zB!`Rt;LXX~mb*y(Z|ZU`V4qa68f|vhYTJQ0@eM*z$i{h~AAj-~C;wlEQ0_mMLYf|L zx)$TRM;(hVWYk{TYu4U)3c1;qshZf@f@j6WyO=a(+H3*P5oxXuFE|zdrX#)B{Pp+j ze(9kb6)B%&ES>Aes)U-Zz_3gY6zo%Bg0n7l2%6T1pdhe60ptx3U(f)BFysd@Kr0Z~?hAR!Pv2KX?5OesTZ_Y7it3#{U{5QKE(NaMH3m z@4>T_zTnv1cgq*_Y1<_AOg%)OZgG*k`}Wi~y={wu#^3bFeA5YA@5F_rFY7-KiL*Be zTrt5D%k41#6LIM7b4q#>I8G7_Hp--kX7xy-$^Z=o7cnDPxTExk2%IZsT4X2hO&~qO zg0&BsF?Y>W`P4&=vP)InrX9O_UdL`#0Ou2Z<|qaBh-g&mNpNKabPguf z4?LgH@pj4N|E%-Murw-sTVwDn*ocK%$`eQwt`jp|NBydnO1TpS$qjDi5v_iYTCzP@ z`YG0HuD+N-FMH&6E1;vHwkbXKSftZ#ufQjC_?PiV^_&Bdp(?q9Oasc&hM`DfGo|g{ z*Bv#oDY|2~)4CLe54EZF5@(yasI$C`B-UG^3udbzPc24UFH{T5 z4%HnjOzT}auWS0S^Si@eb~a-6m8Aypj<&U71WPZ>Ql3VllHSLA{9G4)7+TlWy0UKk z6gFbT&Wbm!%wBb3`HAzq86-_ECJ75ulty{A-xO7%m`H+S_K}nHy#B)S^rMBatr zVaJIr>rNLXb|@JX^qIG}tv|3oAD4HK#C0}`05$b_iY0g+syyQ{NqC~D1zG+@g^^%2 z?ftd14ra_3f<|ahy5n2f%AT$Y!{6dXb=nkVD+%r%#J#pL*KI0j5zZGRv z*&4N{Y;Y&Ew$*eUPrW=|7ayG7ar^!r=%}XClt4D@WLOd(d>#biNO?mN-D(OlHWM|+ z$@TmAhe<)6Ou%aLusWrhwX?SDhY)4?@m=pz`vO+ijFUtb6F7BIJ!O-<7YI&6dR=x#mu-`Jw4KXE*P^dRI^%xybNetx209<^nAWCX_Ce6Gqpnx zHjL*W-PW4?7aGDYP+A4)JH^blr5xbe0tb2~=sPds8yDGmzH`q>I)1UK_79ue zo8PI$+||vR<6&q70;6D>i|!n{U?#yYr%$R}NP24-9FDMV1em>b!?HDdeWy%7krg(F zY*8cJ5WGFxD5& z7sI$jFB-Oq5MfAIG|VBG3}@@;h#&zUgLxQ|nF(u0!zmF#L4s&_XE-0m5D|?p2oqp% z7%-K`FR|!mwI_z!gj+r9caV_!tdD&%-lmcr*+M zVUvg;u;B~%LLo?3lOm%E79m!P2zf$~e~?@l<_S$^wg?QgLohDuFYkqz7EGL1@^Yr2 zGyt7J(r0%fObO;G3KI>_`)_4_ovq9xP#KOZqW-ZosG>B8nW8j^HB}n)uhP^apc@2& zY3kqZBqAgnRU**_u3yZOUotrC(5KpQoYb}NiJ;eGv>*C&ndRkT>(oiYLC>tFO{E!z zN%0%h8>j#A^w#swxc$F+>=b{y>a|t#q{Xk%?@qNhd@n0(YkVE$aInz9*Wamn>&X7d a)a%R3d^JCaLJb;!{9wdh8ye=~67nZY$P@ek delta 3010 zcmb_e3s6->7~b9U*mL%rvj>fb66U?8G9eRa_(%rGG&M~V6-;u(S3Y_$Acc-*NH&&} zmX}pCQZo}&iY6o9ie9zRp*IRn>6lHWU{aW7K8o9(`-Y2YO?T$*zyH7gp8wzP+ueU1 z{XfXM8=VvC1rYqJE=hK)^#wztct;uSut!O!E2@5l4rk}|4KA0<8ZPll(D*H?iCA2p zQr;ZDbpF7D^8B;scXzuyV9$kv`N<+UB)->*Jv9l%<7d>AM_n-R)(I=vx7kOZ zn>I6lMBl&WI-Ie#;?I|c&+cDzrA!|;s3!eAHNN@K>U{$ze3+DUdi$zvhcj4Sw9R1; zpM>|HoW0>#ZO-Nabv-K7Pk;F4>h_-L9gZY*8G8I+@qS#_&6%*PTLZdtsxWa=eEp(P zP9Qv7jTc-Z&ewn3@b=-ce=NK4Y}&r!itiuDi+OeL*d7xT3(1bvBVD~uq_2AWrx&ho zbw)r(qQ>f=P3-fR%jIy!6L=suHpvYL>#5iXba#2f(THQO#n>S4^y--Lr^{sx-OXoN z9x&S5VPPGj*v9r#*y#3Sn0!Z%%6;Kugs$6fxLlLDo5`n*;WCTMt9<^jW|wOnm)B7C zdWX}-a-;j&He7p$y}NKo+KrsG<4;vIWnWnS@trlLB?mX3T=VsBOI}N4iAtBshUib= zkaaxf6a;`wD3KJXAp|6>5~QiB0#&4{h9Mvw>qsFYcY_MmHJ}+(6^ID{CWfLC0fsRr z9vq40ae~u$YFoWTZ{c#V!`=y53%0e@0+wU=2LM~{QwBpYbMfOV*Jm$1kR5Xc7JKW;}%2 z0ox{e;IWH-%mQw$AxjW<^Zn=rcE1l;54@brM-$eU9qk;JURphJi&(c}l>NlnrbidH zdqy>CD|hGqz7T@@3O*{oOP_M^+N{F&`y43@?>@Zx;^O42M><{}KC7VU#a*1gt|sU2 zbb!Cg! zL7b*>2B-UQ0jx~(uOG0Z0j2HG7PG%tPJo$u(8Qj4D6^@Cgf$8z!;n=0LtQisL=_A* zoyt@-MTkTs$O@7)O@@e4$nQ6aN-~x-D)IZwI%Dnd|9!^nVo6(g19ndfTD)ruf!bPm z4hdTMa-YIUC`XN7k-G9~$u;n;O=IsLaeeKCS2A*U%vls()$m3Wn%v@u+#KL<2&X8i z?JPTKp+H+XomBlfc|HtSb8Ax|BF{B>W!Q)TnVC=IpFE_gwdJQ$zV25s>CJh`v1L0y z+&i%IgSVOMFcO+is;u)mregzZqDXnQ(p1DxGL;k=tDIsT65Y@NuXdV7G=m^$Kx`O> z31krq7V*50-QL9t*c_$R!UL;xAH|^a1z6QBod1dS%)ovR4guJ_zyM%tf|b)jO0iW} z0kiwcYtTJm7r(G-p1o7+-IgFO`_VnX`8pO8l~=j~i@nEA*A>!?m8*)8$hxm&mW*obNok^`f$VzBj5*_gLMn-}E0kkwM=9f%y)5 zbWA|=`2;Ku=ag)}<{xfs>gDdsytN+G(`G&5)>YnGw$^%jR-t>Gzq|n9|Fq#Bx%CL% lQipSRl)Yrz1HGT#vuI6~l$bI{y3RAa!k>-&MWgtl{|y;RV_*OP diff --git a/scripts/bench_abba.sh b/scripts/bench_abba.sh index 0baa21db7..8c1fe7d2d 100755 --- a/scripts/bench_abba.sh +++ b/scripts/bench_abba.sh @@ -148,12 +148,11 @@ if [ ! -f "$ELF_REL" ]; then make "$ELF_REL" fi if [ "$WORKLOAD" = "real" ]; then - # ~1 MB, gitignored, never in a fresh checkout — and a rented GPU box is always a - # fresh checkout. Fetched by URL + sha256, not built: no converter, no ethrex host - # dependency tree, so this costs seconds on the box. Unconditional on purpose: the - # target hashes whatever is on disk on every invocation, which is what catches a - # copy left behind by an earlier run in the same rental. A match costs ~35 ms. - echo "==> Verifying ethrex real-block fixture (fetches on a digest miss)" + # Gitignored, never in a fresh checkout — and a rented GPU box is always a fresh + # checkout. Built from the block's replay cache (fetched by URL + sha256): ethrex 25's + # guest decodes only the Amsterdam schema, so no hosted artifact for this block can be + # valid. The generator validates the block through the guest before writing. + echo "==> Building ethrex real-block fixture (from its replay cache)" make ethrex-real-block-fixture elif [ ! -f "$INPUT_REL" ]; then echo "==> Generating ethrex ${TX_COUNT}-transfer fixture (missing)" @@ -163,6 +162,30 @@ fi ELF="$(cd "$(dirname "$ELF_REL")" && pwd)/$(basename "$ELF_REL")" INPUT="$(cd "$(dirname "$INPUT_REL")" && pwd)/$(basename "$INPUT_REL")" +# A workload the guest REJECTS still produces a proof -- of a program that decoded two +# bytes and gave up. `run_stateless_guest` cannot fail: on a schema it does not +# recognise it commits `successful_validation = 0` and exits cleanly, which is what the +# pre-Amsterdam rkyv fixture now does in 496 cycles. Proving that reads as a ~99% +# improvement, in green, on both sides of the A/B. One cheap execution up front turns +# that class of mistake -- stale fixture, wrong fork, fixture built against another +# ethrex rev -- into a hard stop. The floor is far below any real block (the current +# one is ~20M cycles) and far above a rejected one. +MIN_PLAUSIBLE_CYCLES=1000000 +if [ ! -x ./target/release/cli ]; then + cargo build --release -p cli >/dev/null +fi +workload_cycles="$(./target/release/cli execute "$ELF" --private-input "$INPUT" --cycles \ + | awk '/^Cycles:/ {print $2}')" +if [ "${workload_cycles:-0}" -lt "$MIN_PLAUSIBLE_CYCLES" ]; then + echo "ERROR: the workload executed only ${workload_cycles:-0} cycles, below the" >&2 + echo " ${MIN_PLAUSIBLE_CYCLES} floor. The guest almost certainly rejected the input:" >&2 + echo " ELF $ELF" >&2 + echo " input $INPUT" >&2 + echo " Rebuild the fixture at this ethrex rev (make regen-real-block-fixture)." >&2 + exit 1 +fi +echo "==> Workload executes: $workload_cycles cycles" + # --- 2. Build (or reuse) both prover binaries --- need_build=0 if [ "${REBUILD:-0}" = "1" ] || [ ! -x "$WORK/cli_A" ] || [ ! -x "$WORK/cli_B" ]; then diff --git a/tooling/ethrex-block-converter/Cargo.lock b/tooling/ethrex-block-converter/Cargo.lock index 5c216acbb..ffa6e7224 100644 --- a/tooling/ethrex-block-converter/Cargo.lock +++ b/tooling/ethrex-block-converter/Cargo.lock @@ -854,9 +854,11 @@ dependencies = [ "ethrex-config", "ethrex-crypto", "ethrex-guest-program", + "ethrex-rlp", "k256", "lambda-vm-ethrex-crypto", - "rkyv", + "libssz", + "libssz-types", "serde", "serde_json", "sha2", @@ -864,8 +866,8 @@ dependencies = [ [[package]] name = "ethrex-blockchain" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "crossbeam", @@ -889,8 +891,8 @@ dependencies = [ [[package]] name = "ethrex-common" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "crc32fast", @@ -912,6 +914,7 @@ dependencies = [ "once_cell", "rkyv", "rustc-hash", + "secp256k1", "serde", "serde_json", "sha2", @@ -921,8 +924,8 @@ dependencies = [ [[package]] name = "ethrex-config" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "ethrex-common", "ethrex-p2p", @@ -933,8 +936,8 @@ dependencies = [ [[package]] name = "ethrex-crypto" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "ark-bn254", "ark-ec", @@ -947,6 +950,7 @@ dependencies = [ "num-bigint", "p256", "ripemd", + "secp256k1", "sha2", "thiserror 2.0.19", "tiny-keccak", @@ -954,8 +958,8 @@ dependencies = [ [[package]] name = "ethrex-guest-program" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "ethereum-types", @@ -965,6 +969,10 @@ dependencies = [ "ethrex-rlp", "ethrex-vm", "hex", + "libssz", + "libssz-derive", + "libssz-merkle", + "libssz-types", "rkyv", "serde", "serde_with", @@ -973,8 +981,8 @@ dependencies = [ [[package]] name = "ethrex-l2-common" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "ethereum-types", @@ -991,8 +999,8 @@ dependencies = [ [[package]] name = "ethrex-levm" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "derive_more", @@ -1009,8 +1017,8 @@ dependencies = [ [[package]] name = "ethrex-metrics" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "ethrex-common", "serde", @@ -1021,8 +1029,8 @@ dependencies = [ [[package]] name = "ethrex-p2p" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "aes", "aes-gcm", @@ -1063,8 +1071,8 @@ dependencies = [ [[package]] name = "ethrex-rlp" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "ethereum-types", @@ -1073,8 +1081,8 @@ dependencies = [ [[package]] name = "ethrex-storage" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "anyhow", "bytes", @@ -1095,8 +1103,8 @@ dependencies = [ [[package]] name = "ethrex-trie" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "anyhow", "bytes", @@ -1115,8 +1123,8 @@ dependencies = [ [[package]] name = "ethrex-vm" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "derive_more", @@ -1691,18 +1699,18 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libssz" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d498c0482bba87d2647ea4601ea76cf2b498065e3958798a88f49274f3ced5e9" +checksum = "b4bdd6d63ed811ae164966de20810be780e07de784a4834ccfe6be90480c369e" dependencies = [ "smallvec", ] [[package]] name = "libssz-derive" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08ddfb5c969c28a4a54043e630f80c723352637bd1020f256ee3ac7a8814922b" +checksum = "aeee1b9ac9200429f7e9830492765445989ea61c3fb9028ad5a96e1dd5f5e913" dependencies = [ "proc-macro2", "quote", @@ -1711,9 +1719,9 @@ dependencies = [ [[package]] name = "libssz-merkle" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63c6d6d5ce5d79bba66bc98c99869eedffedf7f14f0aa0915f1a62802650bdf6" +checksum = "863eca32d1a43e5ec41106a515552efa8307768d37c68d26b7f21ff13cfee1a7" dependencies = [ "libssz", "sha2", @@ -1721,9 +1729,9 @@ dependencies = [ [[package]] name = "libssz-types" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747273ab2d923e82ed147091fe0fb3e602dd2012c872cdad5efe69e27c3b4099" +checksum = "d4231ac301726840a3fe111f11bd4619d3c97ed155cb94c88dbf92b70e04e017" dependencies = [ "libssz", "libssz-merkle", diff --git a/tooling/ethrex-block-converter/Cargo.toml b/tooling/ethrex-block-converter/Cargo.toml index 28f8d7e1d..0dafd4ec4 100644 --- a/tooling/ethrex-block-converter/Cargo.toml +++ b/tooling/ethrex-block-converter/Cargo.toml @@ -7,15 +7,14 @@ edition = "2024" [workspace] [dependencies] -# Pinned to the SAME ethrex rev as the guest (open LambdaVM-backend PR branch) -# so the generated ProgramInput rkyv layout matches what the guest deserializes. -ethrex-common = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-common", default-features = false } -ethrex-config = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-config", default-features = false } -# Only for `NativeCrypto`, the host `Crypto` impl that `into_execution_witness` -# now takes. Nothing re-exports it, and this crate's own guest-crypto impl is a -# dev-dependency, so the bin build needs its own line. Already in the graph via -# `ethrex-config` → `ethrex-p2p`, so this adds no compilation. -ethrex-crypto = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-crypto", default-features = false } +# Pinned to the SAME ethrex rev as the guest so the generated SSZ input matches +# what the stateless guest deserializes. +ethrex-common = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-common", default-features = false, features = ["secp256k1"] } +ethrex-config = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-config", default-features = false } +# Provides `NativeCrypto`, the host `Crypto` implementation used to recover +# transaction keys, hash the BAL, and validate the generated stateless input. +ethrex-crypto = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-crypto", default-features = false, features = ["secp256k1"] } +ethrex-rlp = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-rlp" } # Matches the guest's own declaration (executor/programs/rust/ethrex/Cargo.toml). # # CAVEAT: this line alone does NOT reproduce the guest's precompile surface. The @@ -26,14 +25,11 @@ ethrex-crypto = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797d # `ark-ff/asm` BN254) that the guest does not have; verify with # `cargo tree -e features -i ethrex-crypto`. Closing that gap means dropping # `ethrex-config` and sourcing `ChainConfig` another way — see the README. -ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-guest-program", default-features = false } +ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-guest-program", default-features = false } -# Exact pin: the fixture writer and the guest/executor readers must agree on the -# rkyv layout. Keep this in sync with the guest -# (executor/programs/rust/ethrex/Cargo.toml) and the other two detached ethrex -# tool workspaces (tooling/ethrex-fixtures, tooling/ethrex-tests). The `executor` -# crate itself declares no rkyv — the guest is what deserializes ProgramInput. -rkyv = { version = "=0.8.16", features = ["std", "unaligned"] } +# Exact pin: keep the SSZ implementation in sync with the guest's ethrex rev. +libssz = "=0.3.0" +libssz-types = "=0.3.0" serde = { version = "1", features = ["derive"] } serde_json = "1" @@ -51,10 +47,8 @@ serde_json = "1" k256 = { version = "=0.13.4", default-features = false, features = ["arithmetic", "expose-field"] } [dev-dependencies] -# The `Crypto` impl the guest injects. Off riscv64 its keccak and ECSM paths both -# fall back to software, so this exercises the guest's *fallback* crypto semantics -# — not the accelerators themselves. (Its k256 feature needs are mirrored in -# [dependencies] above — see the comment there.) +# The LambdaVM `Crypto` implementation used by host-side parity tests. Off +# riscv64 its keccak and ECSM paths both fall back to software. lambda-vm-ethrex-crypto = { path = "../../crypto/ethrex-crypto" } # No feature delta against the rest of the graph (sha2/std is already on), so this # one can stay a dev-dependency without forcing the rebuild described above. diff --git a/tooling/ethrex-block-converter/README.md b/tooling/ethrex-block-converter/README.md index 48c58e27b..2b29b837f 100644 --- a/tooling/ethrex-block-converter/README.md +++ b/tooling/ethrex-block-converter/README.md @@ -1,565 +1,62 @@ # ethrex-block-converter -Converts a **real Ethereum block** into a serialized `ProgramInput` `.bin` for -the lambda-vm ethrex guest, reading an [`ethrex-replay`][replay] cache JSON. +Converts an `ethrex-replay` cache into the schema-prefixed SSZ stateless input +consumed by `ethrex_guest_program::l1::run_stateless_guest`. -This complements `tooling/ethrex-fixtures`, which builds *synthetic* blocks of N -plain ETH transfers. Those are cheap and deterministic but not representative: -they execute no contract code, touch a genesis state trie only a couple of -levels deep, and carry no bytecode in the witness. This tool produces the -opposite — a block that actually looks like Ethereum. - -Figures below are for the **current default** real block, mainnet 25368371; a -repoint replaces them (see [Adopting a different block](#adopting-a-different-block)). -All are measured. - -| | `ethrex_bench_20.bin` (synthetic) | `ethrex_mainnet_25368371.bin` (real, default) | -|---|---|---| -| gas used | 420,000 | **2,428,684** | -| transactions | 20 (all plain transfers) | 29 (real mix) | -| serialized size | 32,766 B | 1,110,156 B | -| cycles | 8,734,622 | **50,781,557** | -| keccak / ecsm calls | 411 / 80 | **10,478** / 116 | -| keccaks per ECSM call | 5.1 | **90** | - -The last row counts keccaks per **ECSM call**, which is the ratio of the two rows -above it. One ecrecover issues four ECSM ecalls — `lincomb2_with_oracle` in -`crypto/ethrex-crypto/src/lib.rs` makes four oracle queries, each an `ecsm_mul` — so -per *ecrecover* the figures are 20.6 and 361, and the 80 and 116 above are 4x the 20 -synthetic and 29 real signature checks. - -Note the real block uses ~5.8x the gas and costs ~5.8x the cycles here, and that it -inverts the crypto mix: the synthetic block is ecrecover-bound, the real one keccak- -and trie-bound. That inversion is the point — a prover change can move the two -numbers in opposite directions. (The gas and cycle ratios agreeing is a coincidence -of this pair, not a rule: cycles/gas is 20.9 for this block on this ELF, and spans -29.7–38.2 across the three candidate blocks when all three are measured on one -common pre-LTO ELF — a ~29% spread.) - -**Pin the ELF whenever you quote a cycle count.** Counts above were measured on this -branch at merge `fdb92f67` (main @ `9ccdaf2`), guest built with **clang 21.1.8**. -Two things move them: -- **Guest optimisation.** #861 gave the guest thin LTO; this block read 74,819,518 - on a mid-July pre-LTO ELF, so anything quoting ~74.8M or ~65.6M is **superseded**. -- **clang major version**, by ~2%. The guest embeds C (secp256k1-sys) and the - Makefile pins target flags but not the compiler, so `cc` picks up whatever `clang` - is on PATH. The RTX 5090 box (clang-18) measured **50,713,534** for this block on - main @ `9ccdaf2` — 0.13% below the number above, same commit, different compiler. - -Why this block specifically: it was the **only** block in a 90-day Dune sweep that -matched the shape constraints in the 1.6–2.6M gas band — exactly 2 heavy -transactions, no single whale transaction dominating, and a sane plain-transfer -share. Its composition is 11.94 tx/Mgas, 44.3% of gas in heavy transactions, 22.5% -in the top transaction, p50 transaction gas 41,297. A block that is merely *large* -is easy to find; one that is structurally typical is not. - -The synthetic column is `ethrex_bench_20.bin` as the benchmark scripts actually -generate it — `ethrex-fixtures 20 … distinct`, i.e. 20 distinct genesis-funded -senders to 20 distinct recipients (`scripts/bench_verify.sh`, -`scripts/bench_recursion_scaling.sh`). The same block in `same` mode (one sender, -one recipient) serializes to 16,811 B. - -Block 25368371 is **verified to run on the guest's precompile surface** (see -[Validation](#validation)); it needs no accelerator we don't have. Any -replacement block must clear the same check — that is what makes it usable, not -just realistic. - -## Getting the fixture - -The fixture is **fetched, not built**: - -```bash -make ethrex-real-block-fixture -``` - -That downloads the finished `.bin` from `ETHREX_REAL_BLOCK_FIXTURE_URL` and -verifies `ETHREX_REAL_BLOCK_FIXTURE_SHA256` before moving it into place — -the same contract as `prepare-sysroot`. The file is gitignored (~1 MB; see -`executor/.gitignore`), and a corrupt or interrupted download is discarded rather -than left looking valid. - -The digest of whatever is already on disk is re-checked on every invocation, not -only when the file is missing — so a stale copy left over from a re-upload under the -same block number, a corrupted file, or a hand-placed one is all caught and -re-fetched. That check is the reason these are phony targets rather than file rules. - -Artifacts live in the **[`bench-fixtures-v1`][release]** release on -`yetanotherco/lambda_vm`, fetched unauthenticated: - -| asset | sha256 | read by | -|---|---|---| -| `ethrex_mainnet_25368371_4f658c2b.bin` | `0a301731…` | every benchmark (**current default**) | -| `cache_mainnet_25368371.json` | `7aa88a5f…` | `regen-real-block-fixture` | -| `ethrex_mainnet_25453112.bin` | `0298663d…` | alternate candidate | -| `cache_mainnet_25453112.json` | `20ffbbc1…` | alternate candidate | - -> The asset name carries the ethrex rev because the bytes are a function of it: the -> archived `ProgramInput` rkyv layout moves with the pin, so one block has one -> fixture per rev. The pre-bump bytes stay hosted under the original name -> (`ethrex_mainnet_25368371.bin`, `61eba49b…`) so older `main`s — whose Makefile -> pins that sha256 — keep fetching their own artifact. - -Each block has two assets: the fixture and the **cache** it was converted from -(`make ethrex-real-block-cache`, ~2 MB, same verify-then-move contract). Only -`regen-real-block-fixture` reads the cache. Note it is *not* the cache the -converter's own tests use — see [Validation](#validation). - -This crate is not on the fixture's path at all. Fetching a verified binary takes the -converter, the ~335-package ethrex host dependency tree and an ethrex-replay `rev` -pin off the critical path of everyone who just wants to run a benchmark. It also -decouples the benchmark block from what upstream hosts: ethrex-replay publishes a -cache for Hoodi and nothing else, so any mainnet block is unreachable by the -convert-locally route — producing its cache takes ~4 minutes and ~700 calls against -an archive RPC — and trivial by this one. - -[release]: https://github.com/yetanotherco/lambda_vm/releases/tag/bench-fixtures-v1 - -## Regenerating the fixture (ethrex rev bumps) - -Needed roughly twice a year, when the guest's ethrex `rev` moves and the rkyv -layout changes with it: - -```bash -make regen-real-block-fixture # fetches that block's cache, rebuilds the .bin -sha256sum "$(make -s print-real-block-fixture)" -``` - -Then upload the result and update `ETHREX_REAL_BLOCK_FIXTURE_SHA256` and its URL. - -Directly, against any cache file: +The converter reads the block, raw witness preimages, and public network from +the cache. It builds the Amsterdam `NewPayloadRequest`, recovers one public key +per transaction, carries the raw state/code/header witness, and validates the +serialized result with ethrex's native guest before writing it. ```bash cd tooling/ethrex-block-converter cargo run --release -- ``` -Output is deterministic for a given cache file: - -```text -wrote ../../executor/tests/ethrex_mainnet_25368371.bin (1110165 bytes): 1 block(s) \ - from mainnet starting at #25368371, 29 transaction(s), 2428684 gas -``` - -Verified at the current ethrex rev: regenerating from the hosted cache reproduces -`0a301731…` byte for byte, and the result passes `test_ethrex_real_block_native` — -which is what proves the hosted cache and the fixture the Makefile expects describe -the same block. (Byte count and digest are both rev-dependent: the `4f658c2b` bump -moved them from 1,110,156 B / `61eba49b…`, the bytes still hosted in the release.) - -The converter's `conversion_is_reproducible` test enforces the same property, but -against its own pinned block rather than this one — see [Validation](#validation). - -## What benchmarks with it - -Costs below are for the **current default block** (mainnet 25368371) and move with -it — see [Measured cost of candidate blocks](#measured-cost-of-candidate-blocks). - -| Where | How to run it | Cost (current default) | -|---|---|---| -| `benchmark-pr.yml` | **`/bench`** on a PR — also push to main and `workflow_dispatch` | 3 runs, 158.8 s each (~8 min of proving) | -| `bench-abba.yml` | **`/bench-abba [N]`** on a PR | ~72 min at the default 12 pairs (see below) | -| `benchmark-gpu.yml` | **`/bench-gpu [N]`** on a PR | 59.87 s/prove on an RTX 5090 (see below) | -| `scripts/bench_verify.sh` | `scripts/bench_verify.sh ` | ~2.6 min per side, then cached | -| `scripts/perf_diff.sh` | `scripts/perf_diff.sh ` | 5 recordings, so ~13 min of proving | -| `scripts/bench_abba.sh` | `scripts/bench_abba.sh [base] [pairs]` | 2 x 158.8 s per pair | - -This block is what all three scripts prove by default (`WORKLOAD=real`); pass -`WORKLOAD=synthetic` for the N-plain-transfer fixture instead. `/bench-verify` is the -one flow that pins `synthetic`, because it reports a monolithic arm as well as a -continuation one and a real block does not fit monolithically. - -None of them hardcode the fixture path or a block number — they read the path from -`make -s print-real-block-fixture` and run `make ethrex-real-block-fixture` on every -invocation, so the digest is re-checked rather than trusted. - -**Every bench flow proves this block.** `/bench` runs it sampled on the shared -runner, against the cached baseline main publishes; `/bench-abba [N]` runs it as -N A/B/B/A pairs on that same runner; `/bench-gpu [N]` runs the same pairs on a -rented box, comparing PR vs main on the same machine — absolute GPU times are -host-CPU-dependent, so only same-box deltas are meaningful. - -**Escalating from `/bench` to `/bench-abba`.** `/bench` resolves about 3%: three -runs of a 158.8 s prove, so it reports 3–10% as unresolved rather than as a -verdict. The paired test resolves a 95% delta of `t* × sd / sqrt(N)`, where `sd` -is the pair-delta standard deviation on the runner. **That sd is not measured -yet.** The columns below bracket it between 1.0% — the GPU box's measured 0.64% -pair sd plus margin — and 2.0%, which is `sqrt(2) ×` this runner's measured 1.43% -single-run CV: - -| pairs | wall | resolves (sd 2.0%) | resolves (sd 1.0%) | -|---|---|---|---| -| 8 | ~50 min | 1.7% | 0.8% | -| **12** | **~72 min** | **1.3%** | **0.6%** | -| 20 | ~1h55m | 0.9% | 0.5% | -| 32 | ~3h | 0.7% | 0.4% | - -12 pairs is the default. Wall is two 158.8 s proves per pair plus ~8 min of -setup. The first real `/bench-abba` run **measures** the sd — it is the `sd` -field of the paired-t line in the result comment — and this table should be -re-pinned to that value once it exists. - -**GPU baseline (measured), and why the GPU epoch is 2^22.** On an RTX 5090 (32,607 MiB) -against main @ `9ccdaf2`, same fixture and CLI, one prove per setting: - -| epoch | wall | VRAM | epochs | proof | -|---|---|---|---|---| -| 2^21 | 70.52 s | 19,193 MiB (58.9%) | 25 | 1.65 GB | -| **2^22** | **59.87 s** | 23,193 MiB (71.1%) | 13 | 1.12 GB | -| 2^23 | OOM after 9.7 s | 32,079 MiB (98.4%) — needs ~44 GiB | — | — | - -**VRAM is the binding constraint**, so 2^22 is simply the largest setting that fits a -32 GiB card — and it is ~15% faster than 2^21 (equivalently, 2^21 is ~18% slower) with -28.9% headroom to spare. 2^23 is out of reach for every card below 48 GiB, not just -this one. `benchmark-gpu.yml` defaults the real-block path to 2^22 for this reason; -raw traces are in `~/workspace/lambda_vm_bench_cache/gpu_epoch_calib_2026-07-31/` -(`PROVENANCE.txt`). - -2^22 is also what the CPU runner uses, but the two arrive there for different reasons -and must not be derived from each other: VRAM binds on the GPU path and host RAM on the -CPU one. The workflows pin it on both sides (`GPU_REAL_EPOCH_LOG2` here, -`REAL_BLOCK_EPOCH_LOG2` in `benchmark-pr.yml`, `ABBA_REAL_EPOCH_LOG2` in -`bench-abba.yml`); `bench_abba.sh` and `bench_verify.sh` still *default* to 2^20, as -does the CLI's `DEFAULT_CONTINUATION_EPOCH_SIZE_LOG2`, because 2^22 needs ~32 GiB of -host memory on a CPU build (peak RSS on the calibration box) and would break laptops. -See [Choosing the epoch size](#choosing-the-epoch-size) for the CPU tiers. - -The CPU bench runner is roughly **2.65x** the GPU wall time for the same block: 158.8 s -median against the calibration RTX 5090's 59.87 s, both at epoch 2^22. - -Do not derive one from the other in general: the CPU rate (3.13 s/Mcycle on this -block) does not transfer to the GPU, and the RTX 5090 sweep found the prover -CPU-bound at the serial producer above epoch 2^21, so GPU time lands closer to CPU -time than a naive device-throughput estimate suggests. - -**Continuations are mandatory, not a tuning choice.** Peak heap on a monolithic -prove grows ~4.9 GB per million cycles on this workload family (measured on the -bench server: `10,728 MB + 2,007 MB/transfer`, R² = 0.998 across 4→20 transfers), -so this block would need **~240 GB** monolithically — and a heavier candidate far -more. `--continuations` makes peak heap a function of the epoch size instead of the -trace length, so the same block fits in **~32 GiB** at epoch 2^22 on the calibration -box — the setting both the CPU bench runner and the GPU path now use, picked by host -RAM on one and by VRAM on the other. Host peaks are machine-specific: the bench runner -itself measured **~52 GB** of peak heap for the same block and epoch. See -[Choosing the epoch size](#choosing-the-epoch-size) for the full curve and the other -tiers. The bundle on disk is ~1.15 GB (1.12 GB on the GPU path); a block would have to -be ~1.9x heavier to push it past the 2 GiB (2.147 GB) rkyv offset limit, which needs -`pointer_width_64` to serialize. - -**`/bench` proves this block and nothing else** — 3 sampled runs against the cached -3-run baseline main publishes on every push. `/bench N` changes the sample count -(clamped to 5). - -The synthetic N-transfer screen that used to run alongside it was **removed**. Two -reasons, recorded here because "add a cheap screen back" is an easy suggestion to -make twice: - -1. Its only unique coverage was the **monolithic** prove path, which is vestigial — - reportedly slower than a single-epoch continuation. Spending runner time to cover - a path we intend to delete is not a trade worth making. -2. Its crypto mix is one **no real block has**: 9.16 ECSM per Mcycle, against a real - block's 2.28. Screening against it tunes the prover for a worst case that - cannot occur. - -The synthetic fixtures themselves are not gone — `/bench-growth` still sweeps them for -a heap-vs-block-size slope, which needs a family of blocks and so cannot come from one -real one, and `/bench-verify` still proves the 20-transfer block so it can report a -monolithic arm as well as a continuation one. - -**The cost is a shared resource.** One runner carries every `/bench`, `/bench-abba` -and `/bench-verify` in the repo, and a `/bench` now occupies it for **~15 min**, on -every comment and every push to main — ~8 min of that is the three proves, the rest is -checkout, a two-sided build, the fixture fetch and the guest ELF. `BENCH_RUNS_REAL` in -`benchmark-pr.yml` is the dial. A run measured **158.8 s** on that runner (median of 3, -2.8% spread, 13 epochs, epoch 2^22), so 3 runs is the right count; the dial is there -for a future block or prover change that takes a run past ~6 min. - -Cycle counts here (8.73M synthetic, 50.78M real) are from merge `fdb92f67` (main @ -`9ccdaf2`, clang 21). They move with guest optimisation and ~2% with the clang major, -so pin the ELF whenever you quote one, or it will look like a regression the next time -someone measures. +The output starts with the two-byte big-endian schema ID `0x1501`. The current +ethrex 25 stateless schema validates one Amsterdam block at a time. A replay +cache must therefore contain one block, its `slot_number`, its +`block_access_list_hash`, and the raw BAL when the BAL is non-empty. Caches +created before Amsterdam are rejected rather than silently rewriting their +block hash or chain rules. -## Where validation runs +The converter supports empty execution requests, which is the format currently +written by `ethrex-replay` for the public L1 cache. Caches with non-empty +requests are rejected because replay does not persist those request bodies. -The checks themselves are described under [Validation](#validation) below; this is -where each one executes. +## Revision pin -`.github/workflows/ethrex-block-converter.yml` runs them on changes to this crate, -`tooling/ethrex-tests`, or the `Makefile` — **not** on every PR. The fixture is a -benchmark input, read by no product code; it has to be right when it changes, not on -every commit, and running it per-PR put a network fetch and a cold build of ~335 -packages in the required gate. +The converter, fixture generator, guest, and host tests all pin: -`no_kzg_backend_linked` is the exception and stays in the required gate -(`pr_main.yaml`): it is a pure unit test costing microseconds, and it is the property -the usability screen depends on, so it should fail on the PR that breaks it rather -than on some later, unrelated one. - -## Prerequisites (for regeneration only) -Rust (stable) and network access on first run (cargo fetches the pinned ethrex -crates; `make` downloads the cache). **No RV64 target or sysroot needed** — this -is a host tool. - -## Getting a cache for a different block (regeneration only) - -The cache format is `ethrex-replay`'s, so use that tool to produce one — it -handles the RPC fetching, multiple client backends, and the `eth_getProof` -fallback, none of which is worth reimplementing here: - -```bash -# In a checkout of https://github.com/lambdaclass/ethrex-replay -ethrex-replay cache --rpc-url -``` - -`debug_executionWitness` requires a **reth or ethrex** node; public providers -(Alchemy, Infura) do not serve it. `ethrex-replay` also supports `eth_getProof` -for geth/nethermind. - -Only **mainnet, Hoodi and Sepolia** caches are accepted. ethrex-replay writes -`network: "LocalDevnet"` for any other chain, and that resolves to a test chain -(chain_id 9, every fork active from timestamp 0) — so converting it would replay -the block under invented rules while still passing every check here, since the -witness is only ever validated against whichever config we chose. The converter -refuses instead; `unmappable_network_is_rejected` pins that. - -## Adopting a different block - -The benchmark block and this crate's test block are **independent** — the fetch is -what decouples them — so a repoint touches two files and neither is this crate's -source. - -**1. The Makefile — the only place a block number appears.** Five lines: - -```make -ETHREX_REAL_BLOCK_NETWORK := -ETHREX_REAL_BLOCK := -ETHREX_REAL_BLOCK_FIXTURE_URL := -ETHREX_REAL_BLOCK_FIXTURE_SHA256 := -ETHREX_REAL_BLOCK_CACHE_URL / _SHA256 := +```text +https://github.com/lambdaclass/ethrex.git +2cb18b0b95b27a2555d3debffdebc43c9685d6e3 ``` -**2. `REAL_BLOCK_FIXTURE` in `tooling/ethrex-tests`**, which points the usability -screen at the block actually being benchmarked. That is the whole of it. - -**Nothing in this crate moves.** Its test constants stay pinned to Hoodi 1265656 -across every repoint — what they exercise is the conversion, not the workload, and -Hoodi's is the one cache ethrex-replay publishes, so pinning there costs no hosting -and cannot drift. - -Everything else derives from the Makefile — the fixture name, and through -`make -s print-real-block-fixture` the benchmark scripts and `benchmark-pr.yml`. No -workflow, script or env var names a block. Nothing in `executor/.gitignore` needs -touching either: it already ignores every accepted network's fixture name, so a -repointed ~1 MB fixture cannot become committable by accident. - -### Measured cost of candidate blocks - -Cost is a property of the block, so it changes with the repoint. All figures are -measured, never derived from gas — **cycles per gas is not constant** (20.9 for the -current default), so sizing a candidate from its gas mispredicts cost. - -**Current default — main-vintage (merge `fdb92f67`, main @ `9ccdaf2`):** - -These figures were measured on the pre-bump fixture (`61eba49b…`, 1,110,156 B) and are -left as measured rather than restamped. The ethrex `4f658c2b` bump changed the fixture -bytes, so they are a baseline for a workload that no longer exists byte-for-byte. - -Post-bump CPU counterparts, measured ABBA on `vm-benchmarks-1` at the same epoch 2^22: -**45,074,552 cycles** (−11.24%), **142.37 s** CPU prove (−10.87%), **936.7 MB** proof -(−12.22%), peak RSS flat at ~48 GB. The GPU column has no post-bump counterpart yet. - -| block | gas | cycles | GPU prove (RTX 5090) | CPU prove | proof | fixture | -|---|---|---|---|---|---|---| -| **mainnet 25368371** | 2.43M | **50,781,557** (clang 21)
50,713,534 (clang 18) | **59.87 s** @ epoch 2^22 | **158.8 s** @ epoch 2^22 (2.65x the GPU wall) | 1.15 GB CPU / 1.12 GB GPU | 1,110,156 B, `61eba49b…` | - -Epoch 2^22 is the GPU recommendation: VRAM binds, 2^22 leaves 28.9% headroom on a -32 GiB card and 2^23 does not fit one at all. See -[Choosing the epoch size](#choosing-the-epoch-size) for the CPU tiers. - -**Alternates — PRE-LTO vintage, superseded, re-measure before quoting.** These were -taken on a mid-July guest ELF, before #861 gave the guest thin LTO; the same build -change took the current default from 74,819,518 to ~50.7M, so expect these to fall by -a comparable factor. Kept because they are the selection evidence, not because the -numbers are current: - -| block | gas | cycles (pre-LTO) | fixture | -|---|---|---|---| -| mainnet 25453112 | 4.24M | 125,932,956 | 2,019,747 B, `0298663d…` | -| hoodi 1265656 | 4.40M | 168,319,360 | 1,021,207 B, `1f7d4c4c…` | - -All three clear the usability screen. Add a row rather than editing the wiring, and -say which ELF a number came from. - -Two things these numbers show that a gas-based estimate would have got wrong, and both -survive the vintage change because they are same-ELF comparisons. **Gas does not size -cost:** on one common pre-LTO ELF the three blocks run at 30.8, 29.7 -and 38.2 cycles per gas, so budgeting a candidate from gas alone is off by up to ~29% -— 25453112 and hoodi 1265656 sit within 4% of each other on gas (4.24M vs 4.40M) yet -25453112 costs ~25% fewer cycles (125.9M vs 168.3M). Gas happens to *order* these -three correctly; it does not size them. And **fixture size does not track cost** -either: the current default is the cheapest block and the middle-sized fixture. - -The default is the cheapest of the three, which matters because the CPU workload sits -on a single shared bench runner. It was also the only block in a 90-day Dune sweep -matching the shape constraints (2 heavy transactions, no whale, sane transfer share) -in the 1.6–2.6M gas band — so it is cheap *and* structurally typical, not cheap -because it is degenerate. - -### Choosing the epoch size +Keep these pins together. The SSZ wire format and the guest implementation are +coupled to the ethrex revision. -`--epoch-size-log2` trades memory for speed, and **the right value is a property of the -machine, not of the block**. Three tiers, all measured: +## Real-block fixture -| where | epoch | why | -|---|---|---| -| GPU, 32 GiB card | **2^22** | VRAM-bound — 2^23 does not fit | -| CPU bench runner (≥64 GiB) | **2^22** | host-RAM-bound — it already peaks at ~52 GB here, and 2^23 measured 60 GiB on a roomier box | -| CPU server, 128 GiB class | **2^23** | the knee; 2^24 fits but is not worth it | -| laptops (CLI default) | **2^20** | unchanged, so a plain `cli prove` still works | - -CPU sweep, 2026-07-31, on a 124 GiB / 32-core box, real block, **branch vintage** -(53,757,588 cycles on that box's clang-21 pre-LTO ELF): - -| epoch | epochs | wall | peak RSS | proof | -|---|---|---|---|---| -| 2^20 | 52 | 616.90 s | 14.56 GiB | 2.83 GB | -| 2^21 | 26 | 464.26 s | 18.43 GiB | 1.72 GB | -| 2^22 | 13 | 397.88 s | 32.21 GiB | 1.15 GB | -| **2^23** | 7 | **356.47 s** | **60.01 GiB** | 0.90 GB | -| 2^24 | 4 | ~334 s | ~97–105 GiB *(provisional)* | — | - -**There is a real knee.** Speed gained per doubling shrinks — 24.7%, 14.3%, 10.4%, -~6% — while memory roughly doubles at each step near the top. 2^23 uses 48% of a -124 GiB box (~52% headroom); 2^24 buys only ~6% more speed for ~15% headroom, so it -**fits but is not recommended on a shared box**, where one co-tenant turns a tight fit -into an OOM. The 2^24 RSS figure is provisional pending the calibration agent's formal -report. - -A main-vintage anchor also ran on the same box: 2^23 = 344.12 s / 58.87 GiB, i.e. ~3% -faster than branch vintage at the same memory — as expected, since #861 cut cycles and -peak RSS is set by the epoch size rather than the trace length. - -**Do not read absolute seconds or peak memory off this table for another machine.** This -box took 397.88 s at 2^22 on its branch-vintage ELF; the bench runner takes 158.8 s at -the same epoch on the main-vintage one — a 2.5x gap, against the ~6% the two cycle -counts differ by. The *ratios* between epochs transfer; the wall times do not. Memory -transfers no better: the bench runner peaks at ~52 GB of heap at 2^22 where this box -measured 32.21 GiB RSS. - -### Verifying a repoint - -Run **both**, in this order: +The benchmark fixture is a fetched release artifact, not a build dependency: ```bash -make ethrex-real-block-fixture # fetch + verify the new .bin -make test-ethrex # the block is USABLE on the guest +make ethrex-real-block-fixture ``` -`make test-ethrex` is the one that matters here, and the converter's tests cannot -replace it — they run against a different block. A new block is only -usable if it needs no accelerator the guest lacks, and this crate cannot tell you -that — its graph links a working c-kzg, so a block calling point evaluation (0x0a) -passes here and fails in the guest. `test_ethrex_real_block_native` in -`tooling/ethrex-tests` is the screen. See [Validation](#validation). - -Benchmark comparability does not survive the swap, and that is intentional rather -than a wrinkle to work around: `benchmark-pr.yml` records which block it measured -and refuses to diff a PR against a baseline that measured a different one, so the -first run after a repoint reports one-sided numbers until main republishes. - -## Why the JSON and not ethrex-replay's own `.bin` - -`ethrex-replay` can already emit a rkyv `ProgramInput`, but it tracks ethrex -`main` while we pin a branch off it, and the type has diverged between the two -before: against the previously pinned rev (`156cb8d6…`) `main` carried an extra -`fee_configs` field and had moved the type from `l1::` to `input::`, so replay's -binary would not deserialize in our guest at all. - -At the currently pinned rev (`4f658c2b…`, rebased onto recent `main`) the -`ProgramInput` definition matches `main`'s again, and that gap has closed for -now. What has not closed: replay resolves rkyv itself from ethrex's `^0.8.10` -rather than our exact `=0.8.16`, `main` has no `lambdavm` feature to build the -guest side against, and the next bump can reopen the type gap without warning. - -The cache JSON carries only `blocks` + `witness` + `network` as plain serde, so -it survives that drift. This tool re-reads it with **our** pinned ethrex types -and re-serializes with **our** rkyv, which is what keeps the output layout -correct by construction. When the guest's ethrex `rev` is bumped, bump it here -too (and in `tooling/ethrex-fixtures` and the guest) and regenerate. - -A previous real-block fixture (`ethrex_hoodi.bin`) was lost exactly to this kind -of drift — it predated the `Crypto` trait and stopped deserializing. Reading the -version-tolerant JSON instead of a pinned binary is the mitigation. - -## Validation - -Six checks, ordered so the argument builds: the host-side parity test first, then -what it does *not* cover, then the checks in `tooling/ethrex-tests` that close the -gap. Five run on the host and need no RV64 toolchain, and each executes in -milliseconds. - -What costs time is a cold build of the ethrex host dependency tree — ~335 -packages, including the `blst`, `c-kzg` and `secp256k1-sys` C builds, `malachite` -and `ark-ff/asm` — not the tests. That build is why these checks live in their own -path-filtered workflow rather than the PR gate (see [Where validation -runs](#where-validation-runs)); `ethrex-block-converter.yml` caches this workspace's -`target/` under its own key, so only cold runs pay it. - -These run against this crate's own pinned block (Hoodi 1265656), **not** the -benchmark block — they test the conversion, which any real block exercises equally, -and Hoodi's is the one cache ethrex-replay publishes. Only -`test_ethrex_real_block_native` follows the benchmark block. - -**`cargo test` here — `real_block_executes_under_guest_crypto`.** Executes the -block through `LambdaVmEcsmCrypto`, the `Crypto` impl the guest injects, so it is -exercised via the guest's own trait dispatch. Stateless re-execution ends in a -post-state-root check, so any divergence from consensus fails here. - -**It does not screen KZG.** Declaring `ethrex-guest-program` with -`default-features = false, features = ["lambdavm"]` is necessary but not -sufficient: the `ethrex-config` dependency (used only for -`Network::get_genesis()`) pulls `ethrex-p2p`, whose `default = ["c-kzg"]` -propagates down to `ethrex-crypto/c-kzg` — and `default-features = false` cannot -switch it off, because ethrex's own workspace declares `ethrex-p2p` with defaults -on. Verify with `cargo tree -e features -i ethrex-crypto`. So point evaluation -(0x0a) resolves to a working c-kzg here and to nothing in the guest. - -Scope of the gap: in `ethrex-crypto`, KZG is the **only** precompile whose -*availability* is feature-gated. The other two gates swap between working -implementations — `secp256k1` picks libsecp256k1 over k256, `std` picks malachite -over num-bigint for modexp — so they change which code runs, not whether a block -can execute. Dropping `ethrex-config` would therefore be a CI-time improvement -(it also sheds `ethrex-p2p`, `ethrex-blockchain`, `ethrex-storage` and the c-kzg -and secp256k1 C builds), not a correctness fix. - -**`cargo test` here — `unmappable_network_is_rejected`.** Refuses a cache whose -`network` cannot be mapped to real chain rules rather than converting it under -substituted ones — see [above](#getting-a-cache-for-a-different-block) for why -that matters. - -**`cargo test` here — `conversion_is_reproducible`.** Pins the block's stats and -the fixture's **sha256**. A length assert would not do: `ChainConfig` is -fixed-size, so a substituted chain config yields a byte-length-identical fixture, -and rkyv's `big_endian` feature would byte-swap in place — neither changes the -byte count. This is what catches an ethrex rev bump that moves the rkyv layout -instead of silently producing a fixture the guest can't read. - -**`tooling/ethrex-tests` — `no_kzg_backend_linked`.** Asserts that crate links no -KZG backend. That was incidental to its dependency graph, and it is the property -the next check relies on, so it is pinned here rather than assumed. - -**`tooling/ethrex-tests` — `test_ethrex_real_block_native`.** The one check that -follows the BENCHMARK block. Checks the serialized `.bin` itself deserializes and -executes. Since that crate links no KZG -backend, this is also **what screens point evaluation (0x0a)**: a block reaching -it diverges from consensus and fails here. - -**`tooling/ethrex-tests` — `test_ethrex_real_block_vm`** (`#[ignore]`, excluded -from PR CI). The block through the guest ELF, comparing the VM's committed -output against the native reference. Needs the RV64 toolchain and its runtime is -unmeasured; run it on a build server, not a laptop: +After an ethrex revision bump, regenerate it from the matching cache: ```bash -cd tooling/ethrex-tests && cargo test --release test_ethrex_real_block_vm -- --ignored +make regen-real-block-fixture +sha256sum "$(make -s print-real-block-fixture)" ``` -[replay]: https://github.com/lambdaclass/ethrex-replay +Publish the resulting SSZ artifact and matching Amsterdam cache, then update +their URLs and checksums in the Makefile before enabling the real-block +benchmark. The old release assets are rkyv `ProgramInput` artifacts and are +incompatible with ethrex 25. + +The converter's tests use the Hoodi cache under `caches/` and verify that an +unmapped network is rejected. The checked release cache predates Amsterdam, so +it is intentionally rejected until a cache containing the new payload fields +is published. diff --git a/tooling/ethrex-block-converter/src/main.rs b/tooling/ethrex-block-converter/src/main.rs index 87bdfe51d..d7b1c3d1b 100644 --- a/tooling/ethrex-block-converter/src/main.rs +++ b/tooling/ethrex-block-converter/src/main.rs @@ -1,37 +1,38 @@ -//! Convert a real Ethereum block into the rkyv-serialized `ProgramInput` the -//! lambda-vm ethrex guest consumes, from an `ethrex-replay` cache JSON. +//! Convert an `ethrex-replay` cache into the SSZ stateless input consumed by +//! the lambda-vm ethrex guest. //! //! Usage: //! cargo run --release -- +use ethrex_common::constants::{DEFAULT_REQUESTS_HASH, EMPTY_BLOCK_ACCESS_LIST_HASH}; use ethrex_common::types::Block; -use ethrex_common::types::block_execution_witness::{RpcExecutionWitness, decode_witness_headers}; +use ethrex_common::types::block_access_list::BlockAccessList; +use ethrex_common::types::block_execution_witness::RpcExecutionWitness; +use ethrex_common::types::stateless_ssz::{ + Bytes20, ExecutionPayload, ExecutionRequests, LogsBloom, NewPayloadRequest, + STATELESS_INPUT_SCHEMA_ID, SszExecutionWitness, SszPublicKeys, SszStatelessInput, +}; use ethrex_config::networks::Network; use ethrex_crypto::NativeCrypto; -use ethrex_guest_program::l1::ProgramInput; +use ethrex_guest_program::l1::run_stateless_guest; +use libssz::SszEncode; +use libssz_types::{ProgressiveList, SszList, SszVector}; use serde::Deserialize; -/// The subset of `ethrex-replay`'s on-disk cache that a `ProgramInput` needs. +/// The subset of an `ethrex-replay` cache needed by the stateless SSZ input. /// -/// Deliberately deserialized with *our* pinned ethrex types rather than by -/// depending on `ethrex-replay`: it tracks ethrex `main`, we pin a branch off it, -/// and `ProgramInput` has diverged between the two before (it once carried an -/// extra `fee_configs` field and lived at a different module path). At the -/// currently pinned rev the type happens to match `main`'s again, but replay -/// still resolves rkyv itself from ethrex's `^0.8.10` against our exact `=0.8.16`, -/// and nothing stops the type drifting apart on the next bump. This JSON carries -/// only `blocks` + `witness` + `network` as plain serde, so it is the -/// version-tolerant interface between the two regardless. -/// -/// Extra fields in the file (L2 blob data, custom `chain_config`) are ignored. +/// Current replay caches carry the witness as raw RLP preimages. A future cache +/// may carry the Amsterdam block access list beside the block; accepting it here +/// keeps the converter independent of replay's Rust type layout. #[derive(Deserialize)] struct Cache { blocks: Vec, witness: RpcExecutionWitness, network: Network, + #[serde(default, alias = "blockAccessList")] + block_access_list: Option, } -/// Summary of the converted block, for the CLI's one-line report. struct BlockSummary { network: String, first_block_number: u64, @@ -40,56 +41,208 @@ struct BlockSummary { gas_used: u64, } -fn program_input_from_cache( +fn empty_execution_requests() -> ExecutionRequests { + ExecutionRequests { + deposits: ProgressiveList::new(), + withdrawals: ProgressiveList::new(), + consolidations: ProgressiveList::new(), + builder_deposits: ProgressiveList::new(), + builder_exits: ProgressiveList::new(), + } +} + +fn ssz_witness( + witness: &RpcExecutionWitness, +) -> Result> { + let state = witness + .state + .iter() + .enumerate() + .map(|(i, bytes)| { + SszList::try_from(bytes.to_vec()) + .map_err(|e| format!("witness state[{i}] is too large: {e:?}")) + }) + .collect::, _>>()?; + let codes = witness + .codes + .iter() + .enumerate() + .map(|(i, bytes)| { + SszList::try_from(bytes.to_vec()) + .map_err(|e| format!("witness codes[{i}] is too large: {e:?}")) + }) + .collect::, _>>()?; + let headers = witness + .headers + .iter() + .enumerate() + .map(|(i, bytes)| { + SszList::try_from(bytes.to_vec()) + .map_err(|e| format!("witness headers[{i}] is too large: {e:?}")) + }) + .collect::, _>>()?; + + Ok(SszExecutionWitness { + state: ProgressiveList::from(state), + codes: ProgressiveList::from(codes), + headers: SszList::try_from(headers) + .map_err(|e| format!("witness has too many headers: {e:?}"))?, + }) +} + +fn stateless_input_from_cache( cache_path: &str, -) -> Result<(ProgramInput, BlockSummary), Box> { +) -> Result<(Vec, BlockSummary), Box> { let cache: Cache = serde_json::from_reader(std::io::BufReader::new(std::fs::File::open(cache_path)?))?; - let Some(first_block) = cache.blocks.first() else { + let Some(block) = cache.blocks.first() else { return Err("cache contains no blocks".into()); }; - let summary = BlockSummary { - network: cache.network.to_string(), - first_block_number: first_block.header.number, - blocks: cache.blocks.len(), - transactions: cache.blocks.iter().map(|b| b.body.transactions.len()).sum(), - gas_used: cache.blocks.iter().map(|b| b.header.gas_used).sum(), - }; - - // The chain rules come from `network`, and ethrex-replay maps every chain it - // doesn't recognise (anything but mainnet / Hoodi / Sepolia) onto - // `LocalDevnet` — which resolves to a test chain: chain_id 9, every fork - // active from timestamp 0. Converting under that would execute the block - // against invented rules while still satisfying every check downstream (the - // witness is replayed against whatever config we picked, and host and guest - // read the same one), so refuse rather than guess. + if cache.blocks.len() != 1 { + return Err(format!( + "cache contains {} blocks; the ethrex 25 stateless guest accepts one block", + cache.blocks.len() + ) + .into()); + } if !matches!(cache.network, Network::PublicNetwork(_)) { return Err(format!( - "unsupported network `{}`: its chain rules would be guessed, not read \ - (ethrex-replay writes LocalDevnet for any chain it does not recognise)", + "unsupported network `{}`: its chain rules would be guessed, not read", cache.network ) .into()); } - // `into_execution_witness` rebuilds the trie structures from the flat node - // list and needs the parent header, which the cache carries inside `witness`. - // Those headers are now decoded by the caller rather than inside the call, and - // the `Crypto` argument is what recomputes the block hashes the parent lookup - // matches on. `NativeCrypto` is keccak-only here, so it produces the same bytes - // as the `LambdaVmEcsmCrypto` the guest injects (whose host path is also - // software keccak) — the fixture does not depend on which one converts it. - let chain_config = cache.network.get_genesis()?.config; - let decoded_headers = decode_witness_headers(&cache.witness.headers)?; - let witness = cache.witness.into_execution_witness( - chain_config, - summary.first_block_number, - &decoded_headers, - &NativeCrypto, - )?; + let summary = BlockSummary { + network: cache.network.to_string(), + first_block_number: block.header.number, + blocks: 1, + transactions: block.body.transactions.len(), + gas_used: block.header.gas_used, + }; + let header = &block.header; - Ok((ProgramInput::new(cache.blocks, witness), summary)) + // The new wire format is the Amsterdam payload. A pre-Amsterdam replay + // cache has no BAL or slot and cannot be upgraded without replaying the + // block, so reject it instead of changing its block hash or chain rules. + let block_access_list_hash = header + .block_access_list_hash + .ok_or("cache block has no Amsterdam block_access_list_hash")?; + let slot_number = header + .slot_number + .ok_or("cache block has no Amsterdam slot_number")?; + let block_access_list = match cache.block_access_list.as_ref() { + Some(bal) => bal.clone(), + None if block_access_list_hash == *EMPTY_BLOCK_ACCESS_LIST_HASH => BlockAccessList::new(), + None => { + return Err("cache block has a non-empty BAL hash but no raw block access list".into()); + } + }; + if block_access_list.compute_hash(&NativeCrypto) != block_access_list_hash { + return Err("cache block access list does not match block_access_list_hash".into()); + } + + let requests_hash = header + .requests_hash + .ok_or("cache block has no requests_hash")?; + if requests_hash != *DEFAULT_REQUESTS_HASH { + return Err( + "cache contains execution requests, but the replay cache has no request data".into(), + ); + } + let base_fee = header + .base_fee_per_gas + .ok_or("cache block has no base_fee_per_gas")?; + let transactions = block + .body + .transactions + .iter() + .map(|tx| tx.encode_canonical_to_vec().into()) + .collect::>>() + .into(); + let public_keys = block + .body + .transactions + .iter() + .enumerate() + .map(|(i, tx)| { + let key = tx + .public_key(&NativeCrypto) + .map_err(|e| format!("failed to recover public key for transaction {i}: {e}"))? + .ok_or_else(|| format!("transaction {i} has no recoverable signature"))?; + SszVector::try_from(key.to_vec()) + .map_err(|e| format!("public key for transaction {i} is invalid: {e:?}")) + }) + .collect::, String>>()?; + + let withdrawals = block + .body + .withdrawals + .clone() + .unwrap_or_default() + .into_iter() + .map( + |withdrawal| ethrex_common::types::stateless_ssz::Withdrawal { + index: withdrawal.index, + validator_index: withdrawal.validator_index, + address: Bytes20(withdrawal.address.0), + amount: withdrawal.amount, + }, + ) + .collect::>() + .into(); + let logs_bloom: LogsBloom = SszVector::try_from(header.logs_bloom.0.to_vec()) + .map_err(|e| format!("logs bloom is not 256 bytes: {e:?}"))?; + let extra_data = SszList::try_from(header.extra_data.to_vec()) + .map_err(|e| format!("extra data exceeds 32 bytes: {e:?}"))?; + let mut base_fee_bytes = [0u8; 32]; + base_fee_bytes[..8].copy_from_slice(&base_fee.to_le_bytes()); + + let execution_payload = ExecutionPayload { + parent_hash: header.parent_hash.0, + fee_recipient: Bytes20(header.coinbase.0), + state_root: header.state_root.0, + receipts_root: header.receipts_root.0, + logs_bloom, + prev_randao: header.prev_randao.0, + block_number: header.number, + gas_limit: header.gas_limit, + gas_used: header.gas_used, + timestamp: header.timestamp, + extra_data, + base_fee_per_gas: base_fee_bytes, + block_hash: header.compute_block_hash(&NativeCrypto).0, + transactions, + withdrawals, + blob_gas_used: header.blob_gas_used.unwrap_or_default(), + excess_blob_gas: header.excess_blob_gas.unwrap_or_default(), + block_access_list: ethrex_rlp::encode::RLPEncode::encode_to_vec(&block_access_list).into(), + slot_number, + }; + let new_payload_request = NewPayloadRequest { + execution_payload, + versioned_hashes: block + .body + .transactions + .iter() + .flat_map(|tx| tx.blob_versioned_hashes()) + .map(|hash| hash.0) + .collect::>() + .into(), + parent_beacon_block_root: header.parent_beacon_block_root.unwrap_or_default().0, + execution_requests: empty_execution_requests(), + }; + let input = SszStatelessInput { + new_payload_request, + witness: ssz_witness(&cache.witness)?, + chain_id: cache.network.get_genesis()?.config.chain_id, + public_keys: SszPublicKeys::from(public_keys), + }; + + let mut bytes = STATELESS_INPUT_SCHEMA_ID.to_be_bytes().to_vec(); + input.ssz_append(&mut bytes); + Ok((bytes, summary)) } fn usage_and_exit(program: &str) -> ! { @@ -109,13 +262,15 @@ fn main() -> Result<(), Box> { usage_and_exit(&program); } - let (program_input, summary) = program_input_from_cache(&cache_path)?; - let bytes = rkyv::to_bytes::(&program_input)?; + let (bytes, summary) = stateless_input_from_cache(&cache_path)?; + let output = run_stateless_guest(&bytes, std::sync::Arc::new(NativeCrypto)); + if output.len() != 43 || output[32] == 0 { + return Err("converted stateless input failed native validation".into()); + } std::fs::write(&out_path, &bytes)?; println!( - "wrote {out_path} ({} bytes): {} block(s) from {} starting at #{}, \ - {} transaction(s), {} gas", + "wrote {out_path} ({} bytes): {} block(s) from {} starting at #{}, {} transaction(s), {} gas", bytes.len(), summary.blocks, summary.network, @@ -129,109 +284,43 @@ fn main() -> Result<(), Box> { #[cfg(test)] mod tests { use super::*; - use ethrex_config::networks::HOODI_CHAIN_ID; const CACHE: &str = "caches/cache_hoodi_1265656.json"; + const CACHE_MISSING: &str = "caches/cache_hoodi_1265656.json is missing — run `make ethrex-real-block-converter-cache` first"; - /// This crate is pinned to Hoodi 1265656 on purpose, independently of whichever - /// block the benchmarks currently prove: it is the one cache ethrex-replay hosts - /// upstream, so keeping the converter's test input there costs us no hosting and - /// cannot drift. What is under test here is the CONVERSION, not the benchmark - /// workload — see tooling/ethrex-block-converter/README.md. - /// - /// `caches/` is gitignored and fetched on demand, so every test here fails on - /// a clean checkout until the cache is downloaded. Say so instead of surfacing - /// a bare `No such file or directory` from `unwrap()`. - const CACHE_MISSING: &str = "caches/cache_hoodi_1265656.json is missing — run \ - `make ethrex-real-block-converter-cache` from the repo root first"; - - /// Executes the converted block with `LambdaVmEcsmCrypto`, the `Crypto` impl - /// the guest injects, so the block is exercised through the same trait - /// dispatch the guest uses. Stateless re-execution ends in a post-state-root - /// check, so any divergence from consensus fails here — on the host, with no - /// RV64 toolchain and no proving run. - /// - /// It does NOT screen KZG: this crate's graph links `c-kzg` (via - /// `ethrex-config` → `ethrex-p2p`, see Cargo.toml), so point evaluation - /// (0x0a) resolves to a working implementation here while the guest has none. - /// A block calling 0x0a would pass this test. - /// - /// `test_ethrex_real_block_native` in `tooling/ethrex-tests` is what covers - /// 0x0a, and `no_kzg_backend_linked` there keeps it covered. That split is - /// sufficient rather than a workaround: KZG is the only precompile in - /// `ethrex-crypto` whose *availability* is feature-gated — the other gates - /// swap between two working implementations. #[test] - fn real_block_executes_under_guest_crypto() { - use ethrex_guest_program::l1::execution_program; - use lambda_vm_ethrex_crypto::LambdaVmEcsmCrypto; - use std::sync::Arc; - - let (program_input, _) = program_input_from_cache(CACHE).expect(CACHE_MISSING); - execution_program(program_input, Arc::new(LambdaVmEcsmCrypto)).unwrap(); + fn legacy_cache_is_rejected_without_amsterdam_fields() { + let result = stateless_input_from_cache(CACHE); + let Err(error) = result else { + panic!("the checked-in replay cache unexpectedly has Amsterdam fields"); + }; + if !std::path::Path::new(CACHE).exists() { + assert!( + error.to_string().contains("No such file") + || error.to_string().contains("os error"), + "{CACHE_MISSING}: {error}" + ); + } else { + assert!( + error.to_string().contains("Amsterdam"), + "unexpected error: {error}" + ); + } } - /// A cache whose `network` we can't map to real chain rules must be refused, - /// not converted under substituted ones. Only the `network` field is changed - /// here, and the result is byte-length-identical to the real fixture — which - /// is exactly why the other tests cannot catch this on their own. #[test] fn unmappable_network_is_rejected() { let mut cache: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(CACHE).expect(CACHE_MISSING)).unwrap(); cache["network"] = serde_json::json!("LocalDevnet"); - - let path = std::env::temp_dir().join(format!( - "ethrex_real_block_localdevnet_{}.json", - std::process::id() - )); + let path = + std::env::temp_dir().join(format!("ethrex_localdevnet_{}.json", std::process::id())); std::fs::write(&path, serde_json::to_vec(&cache).unwrap()).unwrap(); - let result = program_input_from_cache(path.to_str().unwrap()); + let result = stateless_input_from_cache(path.to_str().unwrap()); std::fs::remove_file(&path).ok(); - let Err(err) = result else { - panic!("LocalDevnet resolves to chain_id 9 with all forks at 0; must not convert"); + let Err(error) = result else { + panic!("LocalDevnet must be rejected"); }; - - assert!( - err.to_string().contains("unsupported network"), - "wrong rejection reason: {err}" - ); - } - - /// The serialized form is what the guest actually reads, so pin it: a - /// change here means the rkyv layout moved and every consumer of the - /// fixture (and its README checksum) needs regenerating. - #[test] - fn conversion_is_reproducible() { - use sha2::Digest; - - let (program_input, summary) = program_input_from_cache(CACHE).expect(CACHE_MISSING); - let bytes = rkyv::to_bytes::(&program_input).unwrap(); - - assert_eq!(summary.first_block_number, 1_265_656); - assert_eq!(summary.transactions, 11); - assert_eq!(summary.gas_used, 4_402_947); - - // Asserted separately from the digest below, not covered by it. The digest - // is exactly what a legitimate ethrex rev bump forces someone to rewrite - // (the layout moves, this goes red, a fresh digest gets pasted in) — and at - // that moment it stops covering the substituted-chain-config case it was - // chosen for. This assert survives that churn. - assert_eq!( - program_input.execution_witness.chain_config.chain_id, HOODI_CHAIN_ID, - "chain config is not Hoodi's — the block would replay under other rules", - ); - - // Digest, not length: a layout change can preserve the byte count exactly - // (`ChainConfig` is fixed-size, and rkyv's `big_endian` feature would only - // byte-swap in place), so `len()` cannot pin the archived form. - let digest: String = sha2::Sha256::digest(&bytes) - .iter() - .map(|b| format!("{b:02x}")) - .collect(); - assert_eq!( - digest, "8b3fb1294a5cd7296452d4a0422b0623c2d243e84bc8a51d3f15bc3899354553", - "fixture bytes changed — regenerate it and update the README checksum", - ); + assert!(error.to_string().contains("unsupported network"), "{error}"); } } diff --git a/tooling/ethrex-fixtures/Cargo.lock b/tooling/ethrex-fixtures/Cargo.lock index 03e77ca4a..bdd48518e 100644 --- a/tooling/ethrex-fixtures/Cargo.lock +++ b/tooling/ethrex-fixtures/Cargo.lock @@ -975,8 +975,8 @@ dependencies = [ [[package]] name = "ethrex-blockchain" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "crossbeam", @@ -1000,8 +1000,8 @@ dependencies = [ [[package]] name = "ethrex-common" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "crc32fast", @@ -1034,8 +1034,8 @@ dependencies = [ [[package]] name = "ethrex-crypto" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "ark-bn254", "ark-ec", @@ -1063,9 +1063,12 @@ dependencies = [ "ethrex-common", "ethrex-guest-program", "ethrex-l2-rpc", + "ethrex-rlp", "ethrex-storage", + "ethrex-trie", "hex", - "rkyv", + "libssz", + "libssz-types", "secp256k1", "serde_json", "tokio", @@ -1073,8 +1076,8 @@ dependencies = [ [[package]] name = "ethrex-guest-program" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "ethereum-types", @@ -1084,6 +1087,10 @@ dependencies = [ "ethrex-rlp", "ethrex-vm", "hex", + "libssz", + "libssz-derive", + "libssz-merkle", + "libssz-types", "rkyv", "serde", "serde_with", @@ -1092,8 +1099,8 @@ dependencies = [ [[package]] name = "ethrex-l2-common" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "ethereum-types", @@ -1102,7 +1109,6 @@ dependencies = [ "k256", "lambdaworks-crypto", "rkyv", - "secp256k1", "serde", "serde_with", "thiserror 2.0.18", @@ -1111,8 +1117,8 @@ dependencies = [ [[package]] name = "ethrex-l2-rpc" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "axum", "bytes", @@ -1143,8 +1149,8 @@ dependencies = [ [[package]] name = "ethrex-levm" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "derive_more", @@ -1162,8 +1168,8 @@ dependencies = [ [[package]] name = "ethrex-metrics" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "axum", "ethrex-common", @@ -1178,8 +1184,8 @@ dependencies = [ [[package]] name = "ethrex-p2p" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "aes", "aes-gcm", @@ -1222,8 +1228,8 @@ dependencies = [ [[package]] name = "ethrex-rlp" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "ethereum-types", @@ -1232,8 +1238,8 @@ dependencies = [ [[package]] name = "ethrex-rpc" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "axum", "axum-extra", @@ -1272,8 +1278,8 @@ dependencies = [ [[package]] name = "ethrex-storage" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "anyhow", "bytes", @@ -1294,8 +1300,8 @@ dependencies = [ [[package]] name = "ethrex-storage-rollup" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "async-trait", "bincode", @@ -1310,8 +1316,8 @@ dependencies = [ [[package]] name = "ethrex-trie" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "anyhow", "bytes", @@ -1330,8 +1336,8 @@ dependencies = [ [[package]] name = "ethrex-vm" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "derive_more", @@ -2222,18 +2228,18 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libssz" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d498c0482bba87d2647ea4601ea76cf2b498065e3958798a88f49274f3ced5e9" +checksum = "b4bdd6d63ed811ae164966de20810be780e07de784a4834ccfe6be90480c369e" dependencies = [ "smallvec", ] [[package]] name = "libssz-derive" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08ddfb5c969c28a4a54043e630f80c723352637bd1020f256ee3ac7a8814922b" +checksum = "aeee1b9ac9200429f7e9830492765445989ea61c3fb9028ad5a96e1dd5f5e913" dependencies = [ "proc-macro2", "quote", @@ -2242,9 +2248,9 @@ dependencies = [ [[package]] name = "libssz-merkle" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63c6d6d5ce5d79bba66bc98c99869eedffedf7f14f0aa0915f1a62802650bdf6" +checksum = "863eca32d1a43e5ec41106a515552efa8307768d37c68d26b7f21ff13cfee1a7" dependencies = [ "libssz", "sha2", @@ -2252,9 +2258,9 @@ dependencies = [ [[package]] name = "libssz-types" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747273ab2d923e82ed147091fe0fb3e602dd2012c872cdad5efe69e27c3b4099" +checksum = "d4231ac301726840a3fe111f11bd4619d3c97ed155cb94c88dbf92b70e04e017" dependencies = [ "libssz", "libssz-merkle", @@ -3696,7 +3702,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.2", "once_cell", "rustix 1.1.4", "windows-sys 0.61.2", diff --git a/tooling/ethrex-fixtures/Cargo.toml b/tooling/ethrex-fixtures/Cargo.toml index daeda8b15..a7335eb15 100644 --- a/tooling/ethrex-fixtures/Cargo.toml +++ b/tooling/ethrex-fixtures/Cargo.toml @@ -7,18 +7,22 @@ edition = "2024" [workspace] [dependencies] -# Pinned to the SAME ethrex rev as the guest (open LambdaVM-backend PR branch) -# so the generated ProgramInput rkyv layout matches what the guest deserializes. -ethrex-blockchain = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-blockchain" } -ethrex-storage = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-storage" } -ethrex-common = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-common" } -ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-guest-program" } -ethrex-l2-rpc = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-l2-rpc" } +# Pinned to the SAME ethrex rev as the guest so generated SSZ inputs match what +# the stateless guest deserializes. +ethrex-blockchain = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-blockchain" } +ethrex-storage = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-storage" } +ethrex-common = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-common" } +ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-guest-program" } +ethrex-l2-rpc = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-l2-rpc" } +ethrex-rlp = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-rlp" } +# Walking the witness tries: the storage keys are `keccak(slot)`, so recovering a +# genesis-shaped alloc from a witness needs the node types, not just the accessors. +ethrex-trie = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-trie" } tokio = { version = "1", features = ["rt-multi-thread", "macros"] } -# Exact pin: the fixture writer and the guest/executor readers must agree on the -# rkyv layout. Keep this in sync with executor/{Cargo.toml,programs/rust/ethrex/Cargo.toml}. -rkyv = { version = "=0.8.16", features = ["std", "unaligned"] } +# Exact pin: keep the SSZ implementation in sync with the guest's ethrex rev. +libssz = "=0.3.0" +libssz-types = "=0.3.0" serde_json = "1" bytes = "1" secp256k1 = "0.30" diff --git a/tooling/ethrex-fixtures/README.md b/tooling/ethrex-fixtures/README.md index 7c9b00e0f..04b734860 100644 --- a/tooling/ethrex-fixtures/README.md +++ b/tooling/ethrex-fixtures/README.md @@ -1,79 +1,84 @@ # ethrex-fixtures -Generates synthetic **ethrex block fixtures** — serialized `ProgramInput` `.bin` -files — for the lambda-vm prover tests and benchmarks. Fully in-memory and -offline (no RPC, no node): it builds a genesis chain, creates a block with a -chosen number of signed ETH-transfer transactions, runs ethrex's stateless -witness generation, and writes the rkyv-encoded `ProgramInput`. +Generates deterministic synthetic ethrex blocks and writes the +schema-prefixed SSZ stateless input consumed by the LambdaVM guest. The tool +builds an in-memory chain, creates signed ETH transfers, generates the raw +execution witness, and validates the resulting input with ethrex's native +stateless guest before writing it. -The ethrex dependency is pinned to the **same revision as the guest** -(`executor/programs/rust/ethrex`), so the produced fixtures deserialize and -execute in the guest. When you bump the guest's ethrex rev, bump the `rev` in -this crate's `Cargo.toml` too and regenerate. - -## Prerequisites -- Rust (stable) and network access (the first build fetches the pinned ethrex - crates). **No RV64 target or sysroot needed** — this is a host tool. - -## How to run +The ethrex revision is pinned to the same commit as the guest. After changing +that pin, regenerate the committed fixtures with `make regen-ethrex-fixtures`. ```bash cd tooling/ethrex-fixtures -cargo run --release -- [mode] +cargo run --release -- [same|recipients|distinct] ``` -- `` — how many ETH transfers to include in the block (`0` = empty - block). -- `` — where to write the `.bin` (relative to this directory). -- `[mode]` — account diversity (optional, default `same`): - - `same` — one funded sender (`RICH_PK`) → one fixed recipient (`0xdeadbeef`). - - `recipients` — one funded sender → N distinct recipients (1 → N fan-out). - - `distinct` — N distinct, genesis-funded senders → N distinct recipients - (N independent 1-1 pairs; senders are deterministic synthetic keys injected - into the genesis allocation). This is what the CI benchmark uses, since the - state-trie witness for many distinct accounts is closer to a real block. - -It prints the output size, the number of transactions, and the mode, e.g.: +`same` uses one funded sender, `recipients` sends to distinct recipients, and +`distinct` uses deterministic funded senders. The standard fixtures are: -``` -wrote ../../executor/tests/ethrex_simple_tx.bin (12745 bytes): block #1 with 1/1 transfer(s) [1 sender -> 1 recipient] +```bash +cargo run --release -- 0 ../../executor/tests/ethrex_empty_block.bin +cargo run --release -- 1 ../../executor/tests/ethrex_simple_tx.bin +cargo run --release -- 10 ../../executor/tests/ethrex_10_transfers.bin +cargo run --release -- 4 ../../executor/tests/ethrex_bench_4.bin distinct ``` -Output is deterministic for a given `(n_transfers, mode)`. +The generator is host-only; it needs no RV64 target or sysroot. Output is +deterministic for a given transfer count and mode. -## Creating blocks with different numbers of transactions +## The benchmark block: `--bin real_block` -Just change the first argument: +The synthetic blocks above are transfers, which is not what a real block costs: +a mainnet block is keccak- and trie-bound, and a prover change can move the two +numbers in opposite directions. `real_block` produces the benchmark workload +from a real block instead — its own transactions, its own accounts, its own +contract code — rebuilt as an Amsterdam block the guest accepts: ```bash -# empty block (0 transactions) -cargo run --release -- 0 ../../executor/tests/ethrex_empty_block.bin +cargo run --release --bin real_block -- +# or, with the cache fetched for you: +make regen-real-block-fixture +``` -# 1 transfer -cargo run --release -- 1 ../../executor/tests/ethrex_simple_tx.bin +It reads an ethrex-replay cache, installs the block's pre-state into an +in-memory store **keyed the way the tries are keyed** — `keccak(address)` and +`keccak(slot)`, so no key preimages are needed and every account and slot the +block touches arrives intact — registers a parent header at the block's real +height, replays the transactions in the block's own order through the t8n +entry point, and validates the result through the native guest before writing. -# 10 transfers -cargo run --release -- 10 ../../executor/tests/ethrex_10_transfers.bin +Output for mainnet 25368371 (29 transactions, 2,428,684 gas): -# 50 transfers (custom) -cargo run --release -- 50 /tmp/ethrex_50_transfers.bin +``` +installed 91 accounts / 117 storage slots / 91 codes +rebuilt #25368371 (29/29 txs, 1803938 gas, 12 reverted) ``` -For committed fixtures, prefer `make regen-ethrex-fixtures` from the repo root; -it regenerates the standard fixtures and refreshes -`executor/tests/README.md` checksums. - -> Note: bigger blocks cost ~4M cycles per transfer (software ecrecover -> dominates), so they execute fine but may be too heavy to *prove* on a typical -> machine — e.g. 10 transfers ≈ 42M cycles. - -## Details -- Transactions are plain ETH transfers. In `same`/`recipients` mode they are - signed by a funded dev account from `genesis.json` (well-known load-test key — - not a secret); in `distinct` mode each is signed by its own synthetic key, - funded by injecting an entry into the genesis allocation. Output is - deterministic in all modes. -- Currently only ETH transfers are supported. (ERC20 / contract calls would be - a future extension.) -- Once the upstream LambdaVM-backend ethrex PR merges, this tool can be replaced - by `ethrex-replay custom block` on ethrex `main`. +### Why 12 transactions revert, and why that is not a defect here + +Those transactions were signed with gas limits computed under Osaka. Amsterdam +changes the gas model: cold account access goes from 2600 to 3000, and EIP-8037 +carves a state-gas reservoir out of the transaction's excess gas limit. A +transfer sent with a limit of exactly 21,000 has no excess to carve, so it runs +out of gas. The same rebuild under Osaka rules — `REAL_BLOCK_FORK=osaka`, which +prints the totals and exits without writing, since the guest only decodes the +Amsterdam schema — reverts **2** transactions instead of 12: + +| rules | txs | gas | reverted | +| --- | --- | ---: | ---: | +| Osaka (what the block was built for) | 29/29 | 1,473,522 | 2 | +| Amsterdam | 29/29 | 1,803,938 | 12 | +| mainnet, as mined | 29 | 2,428,684 | — | + +So the gap is the fork, not the reconstruction: a pre-Amsterdam block cannot be +a faithful Amsterdam workload at any level of tooling effort. What this fixture +is, therefore, is a *real-mix* Amsterdam block — real contract code, real +calldata, real signatures (the accelerator counters agree exactly with the +retired fixture at 116 ECSM calls), real trie depth — and not a replay of +mainnet economics. For workloads whose gas limits were computed for Amsterdam, +use the EEST benchmark fixtures; `ETHREX_BENCH_WORKLOAD_AFTER_BUMP.md` in the +repository root has both sets measured side by side. + +Measured on the guest ELF at ethrex `2cb18b0b`: 20,360,647 cycles, 2,701 keccak +calls, 116 ECSM calls. diff --git a/tooling/ethrex-fixtures/genesis.json b/tooling/ethrex-fixtures/genesis.json index af3626151..6208bf936 100644 --- a/tooling/ethrex-fixtures/genesis.json +++ b/tooling/ethrex-fixtures/genesis.json @@ -1126,6 +1126,18 @@ "balance": "0xc097ce7bc90715b34b9f1000000000", "nonce": "0x0" }, + "0x0000bff46984e3725691fa540a8c7589300d8282": { + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe146101065760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461023457600182026001905f5b5f82111560695781019083028483029004916001019190604e565b90939004925050503660b814608957366102345734610234575f5260205ff35b8034106102345760383567ffffffffffffffff1680633b9aca001161023457633b9aca00029034031061023457600154600101600155600354806006026004015f358155600101602035815560010160403581556001016060358155600101608035815560010160a035905560b85f5f3760b85fa0600101600355005b600354600254808203806101001161011d57506101005b5f5b8181146101c3578281016006026004018160b8028154815260200181600101548152602001816002015480825260401c67ffffffffffffffff16816010018160381c81600701538160301c81600601538160281c81600501538160201c81600401538160181c81600301538160101c81600201538160081c81600101535360200181600301548152602001816004015481526020019060050154905260010161011f565b91018092146101d557906002556101e0565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561020d57505f5b6001546020828201116102225750505f610228565b01602090035b5f555f60015560b8025ff35b5f5ffd", + "storage": {}, + "balance": "0x0", + "nonce": "0x1" + }, + "0x000064d678505ad48f8ccb093bc65613800e8282": { + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460cb5760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461018857600182026001905f5b5f82111560685781019083028483029004916001019190604d565b909390049250505036603014608857366101885734610188575f5260205ff35b341061018857600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260305f60143760445fa0600101600355005b6003546002548082038060101160df575060105b5f5b8181146101175782810160030260040181604402815460601b8152601401816001015481526020019060020154905260010160e1565b91018092146101295790600255610134565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561016157505f5b6001546002828201116101765750505f61017c565b01600290035b5f555f6001556044025ff35b5f5ffd", + "storage": {}, + "balance": "0x0", + "nonce": "0x1" + }, "0x4e59b44847b379578588920ca78fbf26c0b4956c": { "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", "storage": {}, diff --git a/tooling/ethrex-fixtures/src/bin/real_block.rs b/tooling/ethrex-fixtures/src/bin/real_block.rs new file mode 100644 index 000000000..b77d96d8a --- /dev/null +++ b/tooling/ethrex-fixtures/src/bin/real_block.rs @@ -0,0 +1,512 @@ +//! Rebuild a real mainnet block as an Amsterdam block the stateless guest accepts. +//! +//! The retired benchmark fixture (`ethrex_mainnet_25368371.bin`) is an rkyv +//! `ProgramInput` from before execution-specs #3278. ethrex 25's guest only +//! decodes schema `0x1501` (Amsterdam) and mainnet has no Amsterdam fork yet, so +//! that block cannot be re-serialized: it has no block access list hash and no +//! slot number, and its state carries none of the EIP-8282 predeploys Amsterdam +//! requires. What CAN be reused is its workload — the real transactions and the +//! real accounts they touch. +//! +//! This takes an ethrex-replay cache, seeds a local Amsterdam genesis with the +//! accounts from the block's own execution witness, replays the block's +//! transactions on top, and emits the SSZ stateless input for the resulting +//! block. The output is NOT the mainnet block: the state root, block hash and +//! gas schedule are this chain's. It is the same transaction mix, which is the +//! part no synthetic block reproduces. +//! +//! Usage: +//! cargo run --release --bin real_block -- +//! +//! NOTE: `build_stateless_input` and its helpers are copied from `main.rs` +//! rather than shared. A real PR should lift them into a module; keeping this +//! binary self-contained leaves the committed generator untouched. + +use bytes::Bytes; +use ethrex_blockchain::payload::{BuildPayloadArgs, create_payload}; +use ethrex_blockchain::{Blockchain, BlockchainOptions}; +use ethrex_common::types::block_access_list::BlockAccessList; +use ethrex_common::types::block_execution_witness::{ + ExecutionWitness, RpcExecutionWitness, amsterdam_chain_config, decode_witness_headers, +}; +use ethrex_common::types::stateless_ssz::{ + Bytes20, ExecutionPayload, ExecutionRequests, LogsBloom, NewPayloadRequest, + STATELESS_INPUT_SCHEMA_ID, SszExecutionWitness, SszPublicKeys, SszStatelessInput, +}; +use ethrex_common::types::{AccountState, Block, ELASTICITY_MULTIPLIER, Genesis}; +use ethrex_common::{Address, H256, U256}; +use ethrex_guest_program::crypto::{Crypto, NativeCrypto}; +use ethrex_guest_program::l1::run_stateless_guest; +use ethrex_rlp::decode::RLPDecode; +use ethrex_rlp::encode::RLPEncode; +use ethrex_storage::{EngineType, Store}; +use ethrex_trie::{Nibbles, Node, NodeRef}; +use libssz::SszEncode; +use libssz_types::{ProgressiveList, SszList, SszVector}; +use std::collections::BTreeMap; + +const GENESIS_JSON: &str = include_str!("../../genesis.json"); +/// Mainnet: the cached transactions are signed for it, so the local chain has to +/// claim the same id or every signature fails. +const CHAIN_ID: u64 = 1; +/// Low enough that every cached transaction clears the block's base fee. The +/// real block's was 0.119 gwei; a transaction rejected for fee reasons would +/// silently shrink the workload. +const GENESIS_BASE_FEE: u64 = 1_000; +const GAS_CEIL: u64 = 60_000_000; + +fn empty_execution_requests() -> ExecutionRequests { + ExecutionRequests { + deposits: ProgressiveList::new(), + withdrawals: ProgressiveList::new(), + consolidations: ProgressiveList::new(), + builder_deposits: ProgressiveList::new(), + builder_exits: ProgressiveList::new(), + } +} + +fn build_stateless_input( + block: &Block, + witness: &RpcExecutionWitness, + block_access_list: Option<&BlockAccessList>, + chain_id: u64, +) -> Result, Box> { + let header = &block.header; + let bal = block_access_list.ok_or("Amsterdam fixture has no block access list")?; + let block_access_list_hash = header + .block_access_list_hash + .ok_or("Amsterdam fixture header has no block_access_list_hash")?; + if bal.compute_hash(&NativeCrypto) != block_access_list_hash { + return Err("fixture BAL does not match block_access_list_hash".into()); + } + let slot_number = header + .slot_number + .ok_or("Amsterdam fixture header has no slot_number")?; + + let transactions = block + .body + .transactions + .iter() + .map(|tx| tx.encode_canonical_to_vec().into()) + .collect::>>() + .into(); + let public_keys = block + .body + .transactions + .iter() + .enumerate() + .map(|(i, tx)| { + let key = tx + .public_key(&NativeCrypto) + .map_err(|e| format!("failed to recover public key for transaction {i}: {e}"))? + .ok_or_else(|| format!("transaction {i} has no recoverable signature"))?; + SszVector::try_from(key.to_vec()) + .map_err(|e| format!("public key for transaction {i} is invalid: {e:?}")) + }) + .collect::, String>>()?; + let withdrawals = block + .body + .withdrawals + .clone() + .unwrap_or_default() + .into_iter() + .map( + |withdrawal| ethrex_common::types::stateless_ssz::Withdrawal { + index: withdrawal.index, + validator_index: withdrawal.validator_index, + address: Bytes20(withdrawal.address.0), + amount: withdrawal.amount, + }, + ) + .collect::>() + .into(); + let logs_bloom: LogsBloom = SszVector::try_from(header.logs_bloom.0.to_vec())?; + let extra_data = SszList::try_from(header.extra_data.to_vec())?; + let base_fee = header.base_fee_per_gas.ok_or("fixture has no base fee")?; + let mut base_fee_bytes = [0u8; 32]; + base_fee_bytes[..8].copy_from_slice(&base_fee.to_le_bytes()); + + let execution_payload = ExecutionPayload { + parent_hash: header.parent_hash.0, + fee_recipient: Bytes20(header.coinbase.0), + state_root: header.state_root.0, + receipts_root: header.receipts_root.0, + logs_bloom, + prev_randao: header.prev_randao.0, + block_number: header.number, + gas_limit: header.gas_limit, + gas_used: header.gas_used, + timestamp: header.timestamp, + extra_data, + base_fee_per_gas: base_fee_bytes, + block_hash: header.compute_block_hash(&NativeCrypto).0, + transactions, + withdrawals, + blob_gas_used: header.blob_gas_used.unwrap_or_default(), + excess_blob_gas: header.excess_blob_gas.unwrap_or_default(), + block_access_list: ethrex_rlp::encode::RLPEncode::encode_to_vec(bal).into(), + slot_number, + }; + let ssz_witness = SszExecutionWitness { + state: ProgressiveList::from( + witness + .state + .iter() + .map(|bytes| SszList::try_from(bytes.to_vec())) + .collect::, _>>()?, + ), + codes: ProgressiveList::from( + witness + .codes + .iter() + .map(|bytes| SszList::try_from(bytes.to_vec())) + .collect::, _>>()?, + ), + headers: SszList::try_from( + witness + .headers + .iter() + .map(|bytes| SszList::try_from(bytes.to_vec())) + .collect::, _>>()?, + )?, + }; + let input = SszStatelessInput { + new_payload_request: NewPayloadRequest { + execution_payload, + versioned_hashes: block + .body + .transactions + .iter() + .flat_map(|tx| tx.blob_versioned_hashes()) + .map(|hash| hash.0) + .collect::>() + .into(), + parent_beacon_block_root: header.parent_beacon_block_root.unwrap_or_default().0, + execution_requests: empty_execution_requests(), + }, + witness: ssz_witness, + chain_id, + public_keys: SszPublicKeys::from(public_keys), + }; + let mut bytes = STATELESS_INPUT_SCHEMA_ID.to_be_bytes().to_vec(); + input.ssz_append(&mut bytes); + Ok(bytes) +} + +/// Walk an embedded witness trie and return every `(32-byte key, value)` leaf it +/// still holds. Pruned subtrees appear as `NodeRef::Hash` and are skipped: they +/// are the proof siblings, which no transaction in the block reads. +fn collect_leaves(node: &Node, path: Nibbles, out: &mut Vec<(H256, Vec)>) { + let descend = |child: &NodeRef, path: Nibbles, out: &mut Vec<(H256, Vec)>| { + if let NodeRef::Node(child, _) = child { + collect_leaves(child, path, out); + } + }; + match node { + Node::Branch(branch) => { + for (i, child) in branch.choices.iter().enumerate() { + descend(child, path.append_new(i as u8), out); + } + } + Node::Extension(ext) => descend(&ext.child, path.concat(&ext.prefix), out), + Node::Leaf(leaf) => { + let bytes = path.concat(&leaf.partial).to_bytes(); + if bytes.len() == 32 { + out.push((H256::from_slice(&bytes), leaf.value.clone())); + } + } + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let mut args = std::env::args(); + let program = args.next().unwrap_or_else(|| "real_block".into()); + let (Some(cache_path), Some(out_path)) = (args.next(), args.next()) else { + eprintln!("usage: {program} "); + std::process::exit(2); + }; + + // --- 1. cache -> real block + its execution witness -------------------- + let cache: serde_json::Value = serde_json::from_slice(&std::fs::read(&cache_path)?)?; + let blocks = cache + .get("blocks") + .and_then(|b| b.as_array()) + .ok_or("cache has no `blocks` array")?; + if blocks.len() != 1 { + return Err(format!("cache holds {} blocks; expected exactly 1", blocks.len()).into()); + } + let real_block: Block = serde_json::from_value(blocks[0].clone())?; + let rpc_witness: RpcExecutionWitness = serde_json::from_value( + cache + .get("witness") + .ok_or("cache has no `witness`")? + .clone(), + )?; + + let decoded_headers = decode_witness_headers(&rpc_witness.headers) + .map_err(|e| format!("decode witness headers: {e:?}"))?; + let witness: ExecutionWitness = rpc_witness + .clone() + .into_execution_witness( + amsterdam_chain_config(CHAIN_ID), + real_block.header.number, + &decoded_headers, + &NativeCrypto, + ) + .map_err(|e| format!("into_execution_witness: {e:?}"))?; + + let codes_by_hash: std::collections::HashMap> = witness + .codes + .iter() + .map(|code| (H256(NativeCrypto.keccak256(code)), code.clone())) + .collect(); + // --- 2. a local Amsterdam chain to host the block ---------------------- + // The committed genesis carries the system contracts and the two EIP-8282 + // predeploys Amsterdam needs; only the chain id and fee floor change. The + // block's own accounts do NOT go in the alloc — they are installed into the + // tries below, which needs no key preimages. + let mut genesis: Genesis = serde_json::from_str(GENESIS_JSON)?; + let blob_schedule = genesis.config.blob_schedule; + genesis.config = amsterdam_chain_config(CHAIN_ID); + genesis.config.blob_schedule = blob_schedule; + // `REAL_BLOCK_FORK=osaka` executes the same block under the rules it was + // built for. The guest cannot consume the result (it only decodes the + // Amsterdam schema), so this exists purely to attribute a gas difference to + // the fork rather than to this tool's reconstruction. + let osaka_probe = std::env::var("REAL_BLOCK_FORK").as_deref() == Ok("osaka"); + if osaka_probe { + genesis.config.amsterdam_time = None; + } + genesis.gas_limit = GAS_CEIL; + genesis.base_fee_per_gas = Some(GENESIS_BASE_FEE); + genesis.timestamp = real_block.header.timestamp.saturating_sub(12); + + let mut accounts = Vec::new(); + if let Some(root) = &witness.state_trie_root { + collect_leaves(root, Nibbles::default(), &mut accounts); + } + + let mut store = Store::new(".ethrex-real-block-tmp", EngineType::InMemory)?; + store.add_initial_state(genesis).await?; + let genesis_header = store + .get_block_header(store.get_latest_block_number()?)? + .ok_or("missing genesis header")?; + + // Install the block's own pre-state into the store's tries, keyed the way the + // tries themselves are keyed: `keccak(address)` and `keccak(slot)`. That is + // what makes this faithful — a `Genesis.alloc` is keyed by the preimages, and + // the witness cannot give those back (they are hashes; the replay cache's + // `keys` field carries only the few the RPC returned, and upstream is removing + // it). Writing the hashed keys directly needs no preimages at all, so every + // account and every storage slot the block touches arrives intact. + let genesis_root = genesis_header.state_root; + // The layered opener's `put_batch` is `unimplemented!()` — the writable path is + // the direct one, which talks to the storage backend without the reorg overlay. + let mut state_trie = store.open_direct_state_trie(genesis_root)?; + let mut installed_accounts = 0usize; + let mut installed_slots = 0usize; + let mut installed_codes = 0usize; + for (hashed_address, encoded) in &accounts { + let mut account = AccountState::decode(encoded) + .map_err(|e| format!("decode account {hashed_address:#x}: {e:?}"))?; + if let Some(storage_root) = witness.storage_trie_roots.get(hashed_address) { + let mut leaves = Vec::new(); + collect_leaves(storage_root, Nibbles::default(), &mut leaves); + let mut storage_trie = store.open_direct_storage_trie( + *hashed_address, + *ethrex_common::constants::EMPTY_TRIE_HASH, + )?; + for (hashed_slot, value) in &leaves { + storage_trie.insert(hashed_slot.0.to_vec(), value.clone())?; + } + installed_slots += leaves.len(); + account.storage_root = storage_trie.hash(&NativeCrypto)?; + } + if let Some(code) = codes_by_hash.get(&account.code_hash) { + store + .add_account_code(ethrex_common::types::Code::from_bytecode( + Bytes::from(code.clone()), + &NativeCrypto, + )) + .await?; + installed_codes += 1; + } + state_trie.insert(hashed_address.0.to_vec(), account.encode_to_vec())?; + installed_accounts += 1; + } + let installed_root = state_trie.hash(&NativeCrypto)?; + + // Build at the real height, not at #1. The seeded accounts hold the block's + // own pre-state, but a contract that stores a block number and compares it + // against `block.number` reads nonsense on a chain that just started — and + // several of the block's transactions do exactly that. So register a parent + // header at the real parent's height carrying the genesis state root (which + // is the seeded state), and build on top of it. The guest only ever sees + // this parent and the block itself, and those two are contiguous. + let mut head = genesis_header.clone(); + head.hash = Default::default(); + head.number = real_block.header.number - 1; + head.parent_hash = genesis_header.hash(); + head.timestamp = real_block.header.timestamp.saturating_sub(12); + head.gas_limit = GAS_CEIL; + head.base_fee_per_gas = Some(GENESIS_BASE_FEE); + head.state_root = installed_root; + let head_hash = head.hash(); + let head_number = head.number; + store.add_block_header(head_hash, head.clone()).await?; + store + .add_block_body(head_hash, ethrex_common::types::BlockBody::empty()) + .await?; + store.add_block_number(head_hash, head_number).await?; + store + .forkchoice_update( + vec![(head_number, head_hash)], + head_number, + head_hash, + None, + None, + ) + .await?; + // Real blocks carry builder/MEV transactions with a zero tip cap, which the + // default mempool floor rejects. Dropping them would silently shrink the + // workload this fixture exists to reproduce. + let blockchain = Blockchain::new( + store.clone(), + BlockchainOptions { + min_tip_wei: 0, + ..BlockchainOptions::default() + }, + ); + + // --- 3. replay the real transactions, in the block's own order ---------- + // Not through the mempool: `build_payload` would re-sort by effective tip and + // an arbitrage transaction that ran second on mainnet reverts when it runs + // first. The t8n entry point takes an explicit, ordered list, reports what it + // could not apply instead of aborting, and seeds BLOCKHASH with the ancestor + // hashes the witness carries. + let total_txs = real_block.body.transactions.len(); + let payload_args = BuildPayloadArgs { + parent: head_hash, + timestamp: head.timestamp + 12, + fee_recipient: real_block.header.coinbase, + random: real_block.header.prev_randao, + withdrawals: Some(vec![]), + beacon_root: Some(H256::zero()), + slot_number: Some(1), + version: 4, + elasticity_multiplier: ELASTICITY_MULTIPLIER, + gas_ceil: GAS_CEIL, + }; + let skeleton = create_payload(&payload_args, &store, Bytes::new())?; + let mut block_hash_cache = BTreeMap::new(); + block_hash_cache.insert(head_number, head_hash); + let (result, rejected, t8n_error) = blockchain.build_payload_t8n( + skeleton, + real_block.body.transactions.clone(), + block_hash_cache, + false, + )?; + if let Some(error) = t8n_error { + return Err(format!("payload build reported: {error}").into()); + } + + // Amsterdam always emits one `EncodedRequests` per request type (a lone type + // byte when the list is empty), so the count is not the signal — a non-empty + // payload is. The SSZ input below declares no requests, which only matches + // the header's `requests_hash` while every list is in fact empty. + let non_empty_requests = result + .requests + .iter() + .filter(|encoded| !encoded.is_empty()) + .count(); + if non_empty_requests > 0 { + return Err(format!( + "the rebuilt block produced {non_empty_requests} non-empty EIP-7685 request \ + list(s); the SSZ input this tool writes declares none" + ) + .into()); + } + if osaka_probe { + let reverted = result.receipts.iter().filter(|r| !r.succeeded).count(); + println!( + "osaka probe #{} ({}/{} txs, {} gas, {reverted} reverted) vs real {} gas", + result.payload.header.number, + result.payload.body.transactions.len(), + total_txs, + result.payload.header.gas_used, + real_block.header.gas_used, + ); + return Ok(()); + } + + let block = result.payload; + let included = block.body.transactions.len(); + + // --- 4. witness -> SSZ -> native validation ---------------------------- + let witness = blockchain + .generate_witness_for_blocks(std::slice::from_ref(&block)) + .await?; + let witness: RpcExecutionWitness = witness.try_into()?; + let bytes = build_stateless_input( + &block, + &witness, + result.block_access_list.as_ref(), + CHAIN_ID, + )?; + let output = run_stateless_guest(&bytes, std::sync::Arc::new(NativeCrypto)); + if output.len() != 43 || output[32] == 0 { + return Err("rebuilt block failed native stateless validation".into()); + } + std::fs::write(&out_path, &bytes)?; + + // --- 5. report --------------------------------------------------------- + let reverted = result.receipts.iter().filter(|r| !r.succeeded).count(); + let mut per_tx = String::new(); + let mut prev_cumulative = 0u64; + for (i, receipt) in result.receipts.iter().enumerate() { + let gas = receipt.cumulative_gas_used.saturating_sub(prev_cumulative); + prev_cumulative = receipt.cumulative_gas_used; + let to = match block.body.transactions.get(i).map(|tx| tx.to()) { + Some(ethrex_common::types::TxKind::Call(address)) => format!("{address:#x}"), + Some(ethrex_common::types::TxKind::Create) => "create".to_string(), + None => "?".to_string(), + }; + per_tx.push_str(&format!( + " tx {i:>2}: {:>9} gas {:<8} -> {to}\n", + gas, + if receipt.succeeded { "ok" } else { "REVERTED" } + )); + } + println!( + "real block #{} ({} txs, {} gas)", + real_block.header.number, total_txs, real_block.header.gas_used + ); + println!( + "witness {} account leaves / {} codes / {} trie nodes", + accounts.len(), + rpc_witness.codes.len(), + rpc_witness.state.len() + ); + println!( + "installed {installed_accounts} accounts / {installed_slots} storage slots / \ + {installed_codes} codes, state root {installed_root:#x}" + ); + println!( + "rebuilt #{} ({included}/{total_txs} txs, {} gas, {reverted} reverted)", + block.header.number, block.header.gas_used + ); + print!("{per_tx}"); + if !rejected.is_empty() { + println!("rejected ({} of {total_txs}):", rejected.len()); + for entry in &rejected { + println!(" {entry:?}"); + } + } + println!( + "gas vs real {:+.2}%", + (block.header.gas_used as f64 / real_block.header.gas_used as f64 - 1.0) * 100.0 + ); + println!("wrote {out_path} ({} bytes)", bytes.len()); + Ok(()) +} diff --git a/tooling/ethrex-fixtures/src/main.rs b/tooling/ethrex-fixtures/src/main.rs index 5cb60acae..4875fde77 100644 --- a/tooling/ethrex-fixtures/src/main.rs +++ b/tooling/ethrex-fixtures/src/main.rs @@ -1,4 +1,4 @@ -//! Generate synthetic ethrex block fixtures (serialized `ProgramInput`) for the +//! Generate synthetic ethrex block fixtures (SSZ stateless inputs) for the //! lambda-vm prover/benchmarks — in-memory, offline, deterministic. //! //! Usage: @@ -18,19 +18,28 @@ //! the LambdaVM-backend ethrex PR lands on ethrex `main` and fixtures are //! generated via `ethrex-replay custom block` instead. //! -//! Pinned to the same ethrex rev as the guest, so the rkyv `ProgramInput` -//! layout matches what the guest deserializes. +//! Pinned to the same ethrex rev as the guest, so the SSZ layout matches what +//! the guest deserializes. use bytes::Bytes; use ethrex_blockchain::payload::{BuildPayloadArgs, create_payload}; use ethrex_blockchain::{Blockchain, BlockchainOptions}; +use ethrex_common::types::block_access_list::BlockAccessList; +use ethrex_common::types::block_execution_witness::RpcExecutionWitness; +use ethrex_common::types::stateless_ssz::{ + Bytes20, ExecutionPayload, ExecutionRequests, LogsBloom, NewPayloadRequest, + STATELESS_INPUT_SCHEMA_ID, SszExecutionWitness, SszPublicKeys, SszStatelessInput, +}; use ethrex_common::types::{ EIP1559Transaction, ELASTICITY_MULTIPLIER, Genesis, GenesisAccount, Transaction, TxKind, }; use ethrex_common::{Address, H256, U256}; -use ethrex_guest_program::l1::ProgramInput; +use ethrex_guest_program::crypto::NativeCrypto; +use ethrex_guest_program::l1::run_stateless_guest; use ethrex_l2_rpc::signer::{LocalSigner, Signable, Signer}; use ethrex_storage::{EngineType, Store}; +use libssz::SszEncode; +use libssz_types::{ProgressiveList, SszList, SszVector}; use secp256k1::SecretKey; /// Well-known load-test rich account (funded in genesis.json). Key is public @@ -76,6 +85,144 @@ fn rich_signer() -> Result> { Ok(LocalSigner::new(SecretKey::from_slice(&hex::decode(RICH_PK)?)?).into()) } +fn empty_execution_requests() -> ExecutionRequests { + ExecutionRequests { + deposits: ProgressiveList::new(), + withdrawals: ProgressiveList::new(), + consolidations: ProgressiveList::new(), + builder_deposits: ProgressiveList::new(), + builder_exits: ProgressiveList::new(), + } +} + +fn build_stateless_input( + block: ðrex_common::types::Block, + witness: &RpcExecutionWitness, + block_access_list: Option<&BlockAccessList>, + chain_id: u64, +) -> Result, Box> { + let header = &block.header; + let bal = block_access_list.ok_or("Amsterdam fixture has no block access list")?; + let block_access_list_hash = header + .block_access_list_hash + .ok_or("Amsterdam fixture header has no block_access_list_hash")?; + if bal.compute_hash(&NativeCrypto) != block_access_list_hash { + return Err("fixture BAL does not match block_access_list_hash".into()); + } + let slot_number = header + .slot_number + .ok_or("Amsterdam fixture header has no slot_number")?; + + let transactions = block + .body + .transactions + .iter() + .map(|tx| tx.encode_canonical_to_vec().into()) + .collect::>>() + .into(); + let public_keys = block + .body + .transactions + .iter() + .enumerate() + .map(|(i, tx)| { + let key = tx + .public_key(&NativeCrypto) + .map_err(|e| format!("failed to recover public key for transaction {i}: {e}"))? + .ok_or_else(|| format!("transaction {i} has no recoverable signature"))?; + SszVector::try_from(key.to_vec()) + .map_err(|e| format!("public key for transaction {i} is invalid: {e:?}")) + }) + .collect::, String>>()?; + let withdrawals = block + .body + .withdrawals + .clone() + .unwrap_or_default() + .into_iter() + .map( + |withdrawal| ethrex_common::types::stateless_ssz::Withdrawal { + index: withdrawal.index, + validator_index: withdrawal.validator_index, + address: Bytes20(withdrawal.address.0), + amount: withdrawal.amount, + }, + ) + .collect::>() + .into(); + let logs_bloom: LogsBloom = SszVector::try_from(header.logs_bloom.0.to_vec())?; + let extra_data = SszList::try_from(header.extra_data.to_vec())?; + let base_fee = header.base_fee_per_gas.ok_or("fixture has no base fee")?; + let mut base_fee_bytes = [0u8; 32]; + base_fee_bytes[..8].copy_from_slice(&base_fee.to_le_bytes()); + + let execution_payload = ExecutionPayload { + parent_hash: header.parent_hash.0, + fee_recipient: Bytes20(header.coinbase.0), + state_root: header.state_root.0, + receipts_root: header.receipts_root.0, + logs_bloom, + prev_randao: header.prev_randao.0, + block_number: header.number, + gas_limit: header.gas_limit, + gas_used: header.gas_used, + timestamp: header.timestamp, + extra_data, + base_fee_per_gas: base_fee_bytes, + block_hash: header.compute_block_hash(&NativeCrypto).0, + transactions, + withdrawals, + blob_gas_used: header.blob_gas_used.unwrap_or_default(), + excess_blob_gas: header.excess_blob_gas.unwrap_or_default(), + block_access_list: ethrex_rlp::encode::RLPEncode::encode_to_vec(bal).into(), + slot_number, + }; + let ssz_witness = SszExecutionWitness { + state: ProgressiveList::from( + witness + .state + .iter() + .map(|bytes| SszList::try_from(bytes.to_vec())) + .collect::, _>>()?, + ), + codes: ProgressiveList::from( + witness + .codes + .iter() + .map(|bytes| SszList::try_from(bytes.to_vec())) + .collect::, _>>()?, + ), + headers: SszList::try_from( + witness + .headers + .iter() + .map(|bytes| SszList::try_from(bytes.to_vec())) + .collect::, _>>()?, + )?, + }; + let input = SszStatelessInput { + new_payload_request: NewPayloadRequest { + execution_payload, + versioned_hashes: block + .body + .transactions + .iter() + .flat_map(|tx| tx.blob_versioned_hashes()) + .map(|hash| hash.0) + .collect::>() + .into(), + parent_beacon_block_root: header.parent_beacon_block_root.unwrap_or_default().0, + execution_requests: empty_execution_requests(), + }, + witness: ssz_witness, + chain_id, + public_keys: SszPublicKeys::from(public_keys), + }; + let mut bytes = STATELESS_INPUT_SCHEMA_ID.to_be_bytes().to_vec(); + input.ssz_append(&mut bytes); + Ok(bytes) +} + /// Distinct recipient address for tx index `i` (fresh account, no funding needed). fn recipient_for(i: u64) -> Address { Address::from_low_u64_be(0xdead_0000_0000u64 + i) @@ -107,6 +254,11 @@ async fn main() -> Result<(), Box> { // --- 1. genesis -> in-memory store ------------------------------------- let mut genesis: Genesis = serde_json::from_str(GENESIS_JSON)?; + // The stateless guest consumes the Amsterdam SSZ schema. Keep the local + // chain's fork schedule aligned with that schema while preserving the + // fixture's deterministic execution rules. + genesis.config.amsterdam_time = Some(0); + genesis.config.blob_schedule.amsterdam = Some(genesis.config.blob_schedule.bpo2); // For `distinct`, fund each synthetic sender in genesis so its tx is valid. if mode == Mode::Distinct { @@ -127,7 +279,7 @@ async fn main() -> Result<(), Box> { let mut store = Store::new(".ethrex-fixtures-tmp", EngineType::InMemory)?; store.add_initial_state(genesis).await?; - let head_number = store.get_latest_block_number().await?; + let head_number = store.get_latest_block_number()?; let head = store .get_block_header(head_number)? .ok_or("missing genesis header")?; @@ -187,14 +339,15 @@ async fn main() -> Result<(), Box> { random: H256::zero(), withdrawals: Some(vec![]), beacon_root: Some(H256::zero()), - slot_number: None, - version: 3, + slot_number: Some(1), + version: 4, elasticity_multiplier: ELASTICITY_MULTIPLIER, gas_ceil: 60_000_000, }; let skeleton = create_payload(&payload_args, &store, Bytes::new())?; let result = blockchain.build_payload(skeleton)?; let block = result.payload; + let block_access_list = result.block_access_list; let included = block.body.transactions.len(); assert_eq!( included as u64, n_transfers, @@ -202,12 +355,16 @@ async fn main() -> Result<(), Box> { (check gas limit / account balance / nonces)" ); - // --- 4. stateless witness -> ProgramInput -> rkyv ---------------------- + // --- 4. stateless witness -> SSZ --------------------------------------- let witness = blockchain .generate_witness_for_blocks(std::slice::from_ref(&block)) .await?; - let program_input = ProgramInput::new(vec![block], witness); - let bytes = rkyv::to_bytes::(&program_input)?; + let witness: RpcExecutionWitness = witness.try_into()?; + let bytes = build_stateless_input(&block, &witness, block_access_list.as_ref(), chain_id)?; + let output = run_stateless_guest(&bytes, std::sync::Arc::new(NativeCrypto)); + if output.len() != 43 || output[32] == 0 { + return Err("generated stateless fixture failed native validation".into()); + } std::fs::write(&out_path, &bytes)?; let mode_label = match mode { diff --git a/tooling/ethrex-tests/Cargo.lock b/tooling/ethrex-tests/Cargo.lock index 0a86228f1..62e8cd38c 100644 --- a/tooling/ethrex-tests/Cargo.lock +++ b/tooling/ethrex-tests/Cargo.lock @@ -693,8 +693,8 @@ dependencies = [ [[package]] name = "ethrex-common" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "crc32fast", @@ -714,7 +714,6 @@ dependencies = [ "libssz-types", "lru", "once_cell", - "rayon", "rkyv", "rustc-hash", "secp256k1", @@ -727,8 +726,8 @@ dependencies = [ [[package]] name = "ethrex-crypto" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "ark-bn254", "ark-ec", @@ -748,8 +747,8 @@ dependencies = [ [[package]] name = "ethrex-guest-program" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "ethereum-types", @@ -759,6 +758,10 @@ dependencies = [ "ethrex-rlp", "ethrex-vm", "hex", + "libssz", + "libssz-derive", + "libssz-merkle", + "libssz-types", "rkyv", "serde", "serde_with", @@ -767,8 +770,8 @@ dependencies = [ [[package]] name = "ethrex-l2-common" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "ethereum-types", @@ -777,7 +780,6 @@ dependencies = [ "k256", "lambdaworks-crypto", "rkyv", - "secp256k1", "serde", "serde_with", "thiserror 2.0.18", @@ -786,8 +788,8 @@ dependencies = [ [[package]] name = "ethrex-levm" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "derive_more", @@ -796,7 +798,6 @@ dependencies = [ "ethrex-rlp", "libssz", "malachite", - "rayon", "rustc-hash", "serde", "strum", @@ -805,8 +806,8 @@ dependencies = [ [[package]] name = "ethrex-rlp" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "ethereum-types", @@ -819,13 +820,13 @@ version = "0.1.0" dependencies = [ "ethrex-guest-program", "executor", - "rkyv", + "libssz", ] [[package]] name = "ethrex-trie" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "anyhow", "bytes", @@ -844,8 +845,8 @@ dependencies = [ [[package]] name = "ethrex-vm" -version = "23.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=797df5540c7d35cafd69b6971a74b2a49c67d1dd#797df5540c7d35cafd69b6971a74b2a49c67d1dd" +version = "25.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" dependencies = [ "bytes", "derive_more", @@ -854,7 +855,6 @@ dependencies = [ "ethrex-crypto", "ethrex-levm", "ethrex-rlp", - "rayon", "rustc-hash", "serde", "thiserror 2.0.18", @@ -1282,18 +1282,18 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libssz" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d498c0482bba87d2647ea4601ea76cf2b498065e3958798a88f49274f3ced5e9" +checksum = "b4bdd6d63ed811ae164966de20810be780e07de784a4834ccfe6be90480c369e" dependencies = [ "smallvec", ] [[package]] name = "libssz-derive" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08ddfb5c969c28a4a54043e630f80c723352637bd1020f256ee3ac7a8814922b" +checksum = "aeee1b9ac9200429f7e9830492765445989ea61c3fb9028ad5a96e1dd5f5e913" dependencies = [ "proc-macro2", "quote", @@ -1302,9 +1302,9 @@ dependencies = [ [[package]] name = "libssz-merkle" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63c6d6d5ce5d79bba66bc98c99869eedffedf7f14f0aa0915f1a62802650bdf6" +checksum = "863eca32d1a43e5ec41106a515552efa8307768d37c68d26b7f21ff13cfee1a7" dependencies = [ "libssz", "sha2", @@ -1312,9 +1312,9 @@ dependencies = [ [[package]] name = "libssz-types" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747273ab2d923e82ed147091fe0fb3e602dd2012c872cdad5efe69e27c3b4099" +checksum = "d4231ac301726840a3fe111f11bd4619d3c97ed155cb94c88dbf92b70e04e017" dependencies = [ "libssz", "libssz-merkle", diff --git a/tooling/ethrex-tests/Cargo.toml b/tooling/ethrex-tests/Cargo.toml index 3c89ea8f7..484f80b76 100644 --- a/tooling/ethrex-tests/Cargo.toml +++ b/tooling/ethrex-tests/Cargo.toml @@ -3,20 +3,16 @@ name = "ethrex-tests" version = "0.1.0" edition = "2024" -# Detached workspace: ethrex pins rkyv with the `unaligned` feature (a global -# archived-layout switch), which must never feature-unify with the main -# workspace's aligned rkyv proof format. See tests/ethrex.rs. +# Detached workspace: the ethrex host reference has its own dependency graph, +# separate from the main workspace. See tests/ethrex.rs. [workspace] [dev-dependencies] executor = { path = "../../executor" } -# Pinned to the SAME ethrex rev as the guest (open LambdaVM-backend PR branch) -# so the native reference reads the same ProgramInput rkyv layout. -ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "797df5540c7d35cafd69b6971a74b2a49c67d1dd", package = "ethrex-guest-program" } -# Exact pin: the fixture writer and the guest/executor readers must agree on the -# rkyv layout. Keep this in sync with tooling/ethrex-fixtures and -# executor/programs/rust/ethrex/Cargo.toml. -rkyv = { version = "=0.8.16", features = ["std", "unaligned"] } +# Pinned to the SAME ethrex rev as the guest so the native reference reads the +# same SSZ stateless input and output. +ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-guest-program" } +libssz = "=0.3.0" # Match the root workspace's optimized dev profile: these tests execute the VM. [profile.dev] diff --git a/tooling/ethrex-tests/tests/eest_amsterdam_probe.rs b/tooling/ethrex-tests/tests/eest_amsterdam_probe.rs new file mode 100644 index 000000000..9bf6743f0 --- /dev/null +++ b/tooling/ethrex-tests/tests/eest_amsterdam_probe.rs @@ -0,0 +1,81 @@ +//! Throwaway probe (untracked): do the upstream EEST Amsterdam benchmark +//! fixtures execute on the guest this branch pins? +//! +//! Inputs are `statelessInputBytes` lifted verbatim out of +//! `tests-zkevm-benchmark@v0.8.2` blockchain tests, so nothing is rebuilt host +//! side. Run with: +//! cargo test --manifest-path tooling/ethrex-tests/Cargo.toml --release \ +//! --test eest_amsterdam_probe -- --nocapture + +use ethrex_guest_program::crypto::NativeCrypto; +use ethrex_guest_program::l1::run_stateless_guest; +use std::sync::Arc; + +const BIN_DIR: &str = "../../eest_amsterdam_bins"; + +#[test] +fn eest_amsterdam_fixtures_validate_natively() { + // The .bin files are downloaded/generated artifacts, not committed: a checkout + // without them skips rather than fails, so this probe can live in the tree. + if !std::path::Path::new(BIN_DIR).is_dir() { + println!("{BIN_DIR} absent — skipping"); + return; + } + let mut entries: Vec<_> = std::fs::read_dir(BIN_DIR) + .expect("bin dir") + .filter_map(|e| e.ok().map(|e| e.path())) + .filter(|p| p.extension().is_some_and(|x| x == "bin")) + .collect(); + entries.sort(); + assert!(!entries.is_empty(), "no .bin found in {BIN_DIR}"); + + for path in entries { + let inputs = std::fs::read(&path).expect("read fixture"); + let schema_id = u16::from_be_bytes([inputs[0], inputs[1]]); + let started = std::time::Instant::now(); + let output = run_stateless_guest(&inputs, Arc::new(NativeCrypto)); + let elapsed = started.elapsed(); + let name = path.file_name().unwrap().to_string_lossy(); + println!( + "{:<60} in={:>9} B schema={:#06x} out={} B ok={} chain_id={} native={:?}", + &name[..name.len().min(60)], + inputs.len(), + schema_id, + output.len(), + output.get(32).copied().unwrap_or(255), + u64::from_le_bytes(output[33..41].try_into().unwrap()), + elapsed, + ); + assert_eq!(output.len(), 43, "{name}: unexpected output length"); + assert_eq!(output[32], 1, "{name}: native stateless validation failed"); + } +} + +/// What the retired real-block fixture does on this branch's guest: the rkyv +/// archive's first two bytes are not the Amsterdam schema id, so the input is +/// rejected and the guest commits an all-zero result. It does NOT error out — +/// which is exactly why a stale fixture would silently benchmark nothing. +/// +/// Reads a copy kept outside `executor/tests`, since that directory now holds the +/// regenerated (valid) fixture for the same block. Skips when the copy is absent. +#[test] +fn legacy_rkyv_real_block_is_rejected_silently() { + let path = format!("{BIN_DIR}/legacy_rkyv_real_block.bin"); + if !std::path::Path::new(&path).is_file() { + println!("{path} absent — skipping"); + return; + } + let inputs = std::fs::read(&path).expect("legacy fixture"); + let output = run_stateless_guest(&inputs, Arc::new(NativeCrypto)); + println!( + "legacy fixture: {} B, first two bytes {:#04x}{:02x}, output {} B, ok={}, all_zero={}", + inputs.len(), + inputs[0], + inputs[1], + output.len(), + output[32], + output.iter().all(|b| *b == 0), + ); + assert_eq!(output.len(), 43); + assert_eq!(output[32], 0, "legacy input must not validate"); +} diff --git a/tooling/ethrex-tests/tests/ethrex.rs b/tooling/ethrex-tests/tests/ethrex.rs index 9ad1ee786..95943ad99 100644 --- a/tooling/ethrex-tests/tests/ethrex.rs +++ b/tooling/ethrex-tests/tests/ethrex.rs @@ -1,22 +1,19 @@ -//! Host-reference execution tests for the ethrex guest, relocated out of the -//! `executor` test suite: `ethrex-guest-program` pins rkyv with the -//! `unaligned` feature (a global archived-layout switch), which would -//! feature-unify with the main workspace's aligned rkyv and silently change -//! the proof wire format. This crate is a detached workspace so the two rkyv -//! configurations never meet. +//! Host-reference and VM execution tests for the ethrex stateless guest. //! //! Fixtures are generated by `tooling/ethrex-fixtures`; the guest ELF comes //! from `make compile-programs`. See `executor/tests/README.md`. +use ethrex_guest_program::crypto::NativeCrypto; +use ethrex_guest_program::l1::run_stateless_guest; use executor::elf::Elf; -use executor::vm::execution::Executor; -use executor::vm::execution::ReturnValues; +use executor::vm::execution::{Executor, ExecutorError, ReturnValues}; +use std::sync::Arc; fn run_program_without_expect( elf_path: &str, private_inputs: Vec, -) -> Result { - println!("Testing {}", elf_path); +) -> Result { + println!("Testing {elf_path}"); let elf_data = std::fs::read(elf_path).unwrap(); let program = Elf::load(&elf_data).unwrap(); println!("Program entry: 0x{:016x}", program.entry_point); @@ -25,118 +22,57 @@ fn run_program_without_expect( executor.finish() } -fn run_program_and_check_public_output( - elf_path: &str, - expected_output: Vec, - private_inputs: Vec, -) { - let result = - run_program_without_expect(elf_path, private_inputs).expect("Failed to run program"); +fn native_output(inputs: &[u8]) -> Vec { + let output = run_stateless_guest(inputs, Arc::new(NativeCrypto)); + assert_eq!(output.len(), 43, "unexpected stateless output length"); + assert_eq!(output[32], 1, "native stateless validation failed"); + output +} - assert_eq!(result.memory_values, expected_output); +fn run_fixture(name: &str) { + let inputs = std::fs::read(format!("{FIXTURES_DIR}/{name}")).unwrap(); + let output = native_output(&inputs); + let result = run_program_without_expect(ELF_PATH, inputs).expect("failed to run guest ELF"); + assert_eq!(result.memory_values, output); } const ELF_PATH: &str = "../../executor/program_artifacts/rust/ethrex.elf"; const FIXTURES_DIR: &str = "../../executor/tests"; /// Larger-block smoke test: a synthetic ethrex block with 10 ETH transfers. -/// (The old `ethrex_hoodi.bin` real-block fixture predated the `Crypto` trait -/// and no longer deserializes; the current real-block fixture is separate.) -#[ignore = "heavier synthetic block (10 txs); run in the dedicated --ignored CI step"] +#[ignore = "heavier synthetic block; run in the dedicated ignored CI step"] #[test] fn test_ethrex() { - use ethrex_guest_program::crypto::NativeCrypto; - use ethrex_guest_program::l1::{ProgramInput, execution_program}; - use rkyv::rancor::Error; - use std::fs; - use std::sync::Arc; - let inputs = fs::read(format!("{FIXTURES_DIR}/ethrex_10_transfers.bin")).unwrap(); - let input = rkyv::from_bytes::(&inputs).unwrap(); - let output = execution_program(input, Arc::new(NativeCrypto)).unwrap(); - run_program_and_check_public_output(ELF_PATH, output.encode(), inputs); + run_fixture("ethrex_10_transfers.bin"); } -/// Executes a stateless ethrex block containing a single (plain ETH transfer) -/// transaction. Execution only — no proving — against the ethrex guest ELF -/// built from the same pinned ethrex revision as the native reference. #[test] fn test_ethrex_simple_tx() { - use ethrex_guest_program::crypto::NativeCrypto; - use ethrex_guest_program::l1::{ProgramInput, execution_program}; - use rkyv::rancor::Error; - use std::sync::Arc; - let inputs = std::fs::read(format!("{FIXTURES_DIR}/ethrex_simple_tx.bin")).unwrap(); - let input = rkyv::from_bytes::(&inputs).unwrap(); - let output = execution_program(input, Arc::new(NativeCrypto)).unwrap(); - run_program_and_check_public_output(ELF_PATH, output.encode(), inputs); + run_fixture("ethrex_simple_tx.bin"); } -/// Executes a stateless ethrex block with NO transactions (empty block). -/// Execution only — no proving. Pins the committed `ethrex_empty_block.bin` -/// fixture so its rkyv `ProgramInput` layout (the 0-tx edge case) is -/// exercised and stays consistent with the guest across ethrex rev bumps. #[test] fn test_ethrex_empty_block() { - use ethrex_guest_program::crypto::NativeCrypto; - use ethrex_guest_program::l1::{ProgramInput, execution_program}; - use rkyv::rancor::Error; - use std::sync::Arc; - let inputs = std::fs::read(format!("{FIXTURES_DIR}/ethrex_empty_block.bin")).unwrap(); - let input = rkyv::from_bytes::(&inputs).unwrap(); - let output = execution_program(input, Arc::new(NativeCrypto)).unwrap(); - run_program_and_check_public_output(ELF_PATH, output.encode(), inputs); + run_fixture("ethrex_empty_block.bin"); } const REAL_BLOCK_FIXTURE: &str = "ethrex_mainnet_25368371.bin"; -/// Host-only acceptance gate for the real-block fixture that -/// `make ethrex-real-block-fixture` fetches and checksums (produced offline by -/// `tooling/ethrex-block-converter`): the block is -/// re-executed statelessly against its own witness, so a successful run means -/// the recovered tries, codes and headers reproduce the header's post-state -/// root. Needs no guest ELF, which is what keeps it runnable where the RV64 -/// toolchain isn't available. -/// -/// Checks the *serialized artifact* specifically — that the published rkyv -/// bytes deserialize and execute — which is why it reads the `.bin` rather -/// than converting the cache itself. -/// -/// It is also, in practice, the check that the block needs no KZG: this crate's -/// dependency graph links no KZG backend, so a block calling point evaluation -/// (0x0a) diverges from consensus here and fails. That property is incidental to -/// the dep graph rather than declared, so `no_kzg_backend_linked` below pins it. -/// (`tooling/ethrex-block-converter`'s parity test does NOT cover 0x0a — it links -/// `c-kzg` transitively via `ethrex-config`.) +/// Host-only acceptance gate for the real SSZ stateless fixture produced by +/// `tooling/ethrex-block-converter`. #[test] fn test_ethrex_real_block_native() { - use ethrex_guest_program::crypto::NativeCrypto; - use ethrex_guest_program::l1::{ProgramInput, execution_program}; - use rkyv::rancor::Error; - use std::sync::Arc; let inputs = std::fs::read(format!("{FIXTURES_DIR}/{REAL_BLOCK_FIXTURE}")).unwrap(); - let input = rkyv::from_bytes::(&inputs).unwrap(); - execution_program(input, Arc::new(NativeCrypto)).unwrap(); + native_output(&inputs); } -/// Pins the property the test above leans on: this crate's dependency graph must -/// link no KZG backend, so a block calling point evaluation (0x0a) diverges from -/// consensus and fails rather than passing on a surface the guest doesn't have. -/// If a future dependency pulls `c-kzg` or `kzg-rs` in, this goes red instead of -/// the screen silently disappearing. -/// Detection is by error *text*, deliberately: zero input fails under a linked -/// backend too (invalid G1 encoding), and both paths surface as -/// `CryptoError::Other`, so the variant can't tell them apart. The string comes -/// from `ethrex-crypto`'s `KzgError::Unimplemented`; if upstream rewords it this -/// test goes red, which is the safe direction — and it has: the 4f658c2b rev bump -/// dropped `openvm-kzg` from the sentence, so the expected text moved with it. -/// -/// Worth knowing why this can regress: `ethrex-crypto`'s own default feature set -/// is `["std", "kzg-rs", "secp256k1", "aws-lc-rs", "blst"]`, so any future -/// dependency pulling it in with defaults on restores a backend and silently -/// removes the screen. +/// The dependency graph intentionally has no KZG backend. Keep this check so +/// a future feature change cannot silently make the guest and host precompile +/// surfaces diverge. #[test] fn no_kzg_backend_linked() { - use ethrex_guest_program::crypto::{Crypto, NativeCrypto}; + use ethrex_guest_program::crypto::Crypto; + let result = NativeCrypto.verify_kzg_proof(&[0u8; 32], &[0u8; 32], &[0u8; 48], &[0u8; 48]); let message = match result { Ok(()) => "verify_kzg_proof accepted zero input".to_string(), @@ -144,30 +80,12 @@ fn no_kzg_backend_linked() { }; assert!( message.contains("One of features c-kzg or kzg-rs should be active"), - "a KZG backend is linked into ethrex-tests, so test_ethrex_real_block_native no \ - longer screens precompile 0x0a: {message}" + "a KZG backend is linked into ethrex-tests: {message}" ); } -/// The same real block through the guest ELF, checking the VM's committed -/// output matches the native reference. Split from the native gate above -/// because this one needs the ethrex ELF and is far heavier than the synthetic -/// fixtures (a ~1 MB witness, real contract execution). -/// -/// Deliberately excluded from the PR CI step, which otherwise runs everything -/// via `--include-ignored`: the cycle cost of a real block in the VM has not -/// been measured yet, so it is opt-in until we know what it does to job time. -/// Run it explicitly with: -/// cd tooling/ethrex-tests && cargo test --release test_ethrex_real_block_vm -- --ignored -#[ignore = "real block through the VM; unmeasured runtime, run explicitly on a build server"] +#[ignore = "real block through the VM; unmeasured runtime"] #[test] fn test_ethrex_real_block_vm() { - use ethrex_guest_program::crypto::NativeCrypto; - use ethrex_guest_program::l1::{ProgramInput, execution_program}; - use rkyv::rancor::Error; - use std::sync::Arc; - let inputs = std::fs::read(format!("{FIXTURES_DIR}/{REAL_BLOCK_FIXTURE}")).unwrap(); - let input = rkyv::from_bytes::(&inputs).unwrap(); - let output = execution_program(input, Arc::new(NativeCrypto)).unwrap(); - run_program_and_check_public_output(ELF_PATH, output.encode(), inputs); + run_fixture(REAL_BLOCK_FIXTURE); } From cab67d33092f0a2acacb9a216a583dd495ea270c Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Tue, 8 Sep 2026 18:13:19 -0300 Subject: [PATCH 08/27] Re-pin the benchmark documentation and thresholds to the rebuilt workload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every figure carrying seconds or bytes in the bench workflows described the retired pre-Amsterdam block: 158.8 s per prove, 50.78M cycles, ~52 GB of peak heap, ~5.3 min per ABBA pair. The workload is now 20,360,647 cycles and 69.10 s at epoch 2^22 — mean of 10 proves on the bench runner, sd 0.41 s, CV 0.59%, 35.75 GiB peak, 466 MB bundle — so the durations the bot quotes, the ABBA pair table, the epoch trade-off and the run-count dial are re-measured rather than inherited, and the pre-bump figures are labelled as having measured a different fixture. The three deterministic counters are stated as such, since they are what tells a reader the workload is the one the job thinks it is. Two defects the re-read surfaced. The fixture's file rule depended on a phony target, so it regenerated on every invocation instead of only when missing. And the epoch and cost tables the converter's README used to carry went away when it was rewritten for SSZ, leaving seven references pointing at nothing; they now live in tooling/ethrex-fixtures/README.md, next to the generator that produces the workload. pr_main's justification for the detached ethrex-tests workspace also still cited rkyv's `unaligned` feature, which the bump removed. --- .github/workflows/bench-abba.yml | 34 +++++++------ .github/workflows/benchmark-gpu.yml | 9 ++-- .github/workflows/benchmark-pr.yml | 75 +++++++++++++++++------------ .github/workflows/pr_main.yaml | 8 +-- Makefile | 16 ++++-- scripts/bench_verify.sh | 2 +- scripts/perf_diff.sh | 2 +- tooling/ethrex-fixtures/README.md | 22 +++++++++ 8 files changed, 108 insertions(+), 60 deletions(-) diff --git a/.github/workflows/bench-abba.yml b/.github/workflows/bench-abba.yml index 23e323d57..afb8da0bc 100644 --- a/.github/workflows/bench-abba.yml +++ b/.github/workflows/bench-abba.yml @@ -10,18 +10,23 @@ name: Bench ABBA tiebreaker # # Resolution, from the paired t-test (resolvable 95% delta = t* x sd / sqrt(N)). The # pair-delta sd on this runner is NOT yet measured; the two columns bracket it between -# 1.0% (the GPU box's measured 0.64% plus margin) and 2.0% (sqrt(2) x this runner's -# measured 1.43% single-run CV): +# 0.6% and 0.85% (sqrt(2) x this runner's measured 0.59% single-run CV, from the +# 10-prove baseline in tooling/ethrex-fixtures/README.md): # -# pairs wall resolves (sd 2.0% / sd 1.0%) -# 8 ~50 min 1.7% / 0.8% -# 12 ~72 min 1.3% / 0.6% <- default -# 20 ~1h55m 0.9% / 0.5% -# 32 ~3h 0.7% / 0.4% +# pairs wall resolves (sd 0.85% / sd 0.6%) +# 8 ~26 min 0.71% / 0.50% +# 12 ~36 min 0.54% / 0.38% <- default +# 20 ~54 min 0.40% / 0.28% +# 32 ~1h22m 0.31% / 0.22% # -# Wall is 2 x 158.8 s per pair (the runner's measured prove at epoch 2^22) plus ~8 min +# Wall is 2 x 69.10 s per pair (the runner's measured prove at epoch 2^22) plus ~8 min # of setup. The FIRST run measures the sd — it is the `sd` field of the paired-t line # in the result comment — and this table should be re-pinned to it then. +# +# Both columns tightened when the workload was rebuilt for ethrex 25: the block is +# 20.36M cycles instead of 50.78M and the single-run CV fell from 1.43% to 0.59%, so a +# pair now costs 2.3 min instead of 5.3. Numbers from before that bump measured a +# different fixture and are not comparable. on: issue_comment: types: [created] @@ -29,7 +34,8 @@ on: env: # The epoch /bench proves this block at, so the tiebreaker resolves the same # configuration and not just the same block. Memory picks it: this runner peaks at - # ~52 GB here against its >=64 GiB floor. See benchmark-pr.yml's REAL_BLOCK_EPOCH_LOG2. + # 35.75 GiB here against its >=64 GiB floor, and 2^23 would take that to 51.46 GiB + # for 7.5% of wall. See benchmark-pr.yml's REAL_BLOCK_EPOCH_LOG2. ABBA_REAL_EPOCH_LOG2: "22" concurrency: @@ -50,8 +56,8 @@ jobs: startsWith(github.event.comment.body, '/bench-abba') && contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association) runs-on: [self-hosted, bench] - # Hang guardrail, not expected duration: a real-block pair is 2 x 158.8 s = ~5.3 min, - # so the default 12 pairs runs ~72 min and the 40-pair clamp ~3.7 hr, plus up to + # Hang guardrail, not expected duration: a real-block pair is 2 x 69.10 s = ~2.3 min, + # so the default 12 pairs runs ~36 min and the 40-pair clamp ~1.6 hr, plus up to # ~30 min of two-sided build on a cold cache. timeout-minutes: 360 steps: @@ -117,9 +123,9 @@ jobs: await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, - // A pair is TWO proves at the runner's measured 158.8 s, so ~5.3 min/pair, - // plus ~8 min of checkout, two-sided build and fixture fetch. - body: `⏳ **ABBA tiebreaker started** on the bench server: ${process.env.PAIRS} pairs of ${process.env.WORKLOAD} — a pair is 2 proves at ~158.8 s, so roughly ${Math.round(8 + Number(process.env.PAIRS) * 5.3)} min. Pass a smaller pair count for a quicker, coarser run. The bench server is occupied until it finishes.` + // A pair is TWO proves at the runner's measured 69.10 s, so ~2.3 min/pair, + // plus ~8 min of checkout, two-sided build and fixture build. + body: `⏳ **ABBA tiebreaker started** on the bench server: ${process.env.PAIRS} pairs of ${process.env.WORKLOAD} — a pair is 2 proves at ~69 s, so roughly ${Math.round(8 + Number(process.env.PAIRS) * 2.3)} min. Pass a smaller pair count for a quicker, coarser run. The bench server is occupied until it finishes.` }); - name: Checkout (full history for ref resolution) diff --git a/.github/workflows/benchmark-gpu.yml b/.github/workflows/benchmark-gpu.yml index 4a9c33398..ac4d09310 100644 --- a/.github/workflows/benchmark-gpu.yml +++ b/.github/workflows/benchmark-gpu.yml @@ -9,7 +9,7 @@ name: Benchmark GPU (PR) # Triggered by a "/bench-gpu [N]" comment on a PR (N = pair count, default 14) or via # workflow_dispatch. # -# Workload: the real block (see tooling/ethrex-block-converter/README.md), proven with +# Workload: the real block (see tooling/ethrex-fixtures/README.md), proven with # --continuations at the calibrated epoch size below. # Orchestration runs on a GitHub-hosted runner; all GPU work happens on the rented # Vast box (provisioned by the template onstart). @@ -231,9 +231,12 @@ jobs: run: | # cpu_ram filter is in GB. Floor 48 GB: the real block at epoch 2^22 peaks at # ~36 GB host RSS on the CUDA path (measured, main vintage) — ~25% headroom. + # The workload rebuilt for ethrex 25 peaks at 35.75 GiB on the CPU path + # (10-prove baseline of 2026-09-08), so the floor keeps its headroom; it is + # the epoch, not the block, that would move it. # Continuation peak is set by the epoch size, not the block, so bigger blocks - # don't move it; raising the epoch would (see the calibration tables in - # tooling/ethrex-block-converter/README.md). + # don't move it; raising the epoch would (see the epoch table in + # tooling/ethrex-fixtures/README.md). # gpu_frac=1 requires a WHOLE-MACHINE offer (you rent every GPU on the host), so # Vast places no other tenant on the box: CPU cores, RAM/memory bandwidth, PCIe, # and NVMe are fully dedicated. Without it the "most expensive" sort below lands on diff --git a/.github/workflows/benchmark-pr.yml b/.github/workflows/benchmark-pr.yml index 934dd1996..4a1b01410 100644 --- a/.github/workflows/benchmark-pr.yml +++ b/.github/workflows/benchmark-pr.yml @@ -70,10 +70,15 @@ env: ELF: executor/program_artifacts/rust/ethrex.elf # The workload: a real Ethereum block. WHICH block lives in # the Makefile and nowhere else — nothing in this file names one, so a repoint - # moves this job without editing it. At the current default that is 50.78M cycles, - # 10,478 keccak calls and 116 ecsm calls: ~5.8x the work of the synthetic block with a - # ~18x different keccak:ecrecover mix. That is the whole point — a prover change - # can move the synthetic number and the real one in opposite directions. + # moves this job without editing it. At the current default that is 20,360,647 + # cycles, 2,701 keccak calls and 116 ecsm calls: 7.5x the work of the synthetic + # block (ethrex_bench_4, 2,727,681 cycles) with a keccak:ecrecover mix of 23:1 + # against its 13:1. That is the whole point — a prover change can move the + # synthetic number and the real one in opposite directions. + # + # Those three counts are deterministic for a given ELF and input, so they are the + # check that the workload is what this job thinks it is; every figure below that + # carries seconds or bytes is machine-dependent and was measured on THIS runner. # # The path is resolved from the Makefile (`make -s print-real-block-fixture`) into # REAL_INPUT at run time. The .bin is gitignored and BUILT from the block's replay @@ -83,30 +88,32 @@ env: # Continuations are mandatory here, not a preference: a monolithic prove costs # ~4.9 GB of peak heap per million cycles on this workload family (from the # measured growth fit, 10,728 MB + 2,007 MB/transfer at R^2 = 0.998), so the - # current default would need ~240 GB and a heavier candidate far more. + # current default would need ~100 GB and a heavier candidate far more. # `--continuations` makes peak heap a function of the epoch size instead of the - # trace length. Costs move with the block; the per-candidate table is in - # tooling/ethrex-block-converter/README.md. + # trace length. Costs move with the block; what this one costs is in + # tooling/ethrex-fixtures/README.md. + # + # Budget ~470 MB of disk for the bundle each run (measured: 466,015,152 bytes) — + # hence the `rm -f` after every prove. A heavier block pushes it past 2 GiB, which + # needs rkyv `pointer_width_64` in the PROVER's bundle serialisation; a PR branch + # predating that fix fails at write time rather than mismeasuring. # - # Budget ~1.2 GB of disk for the bundle each run — hence the `rm -f` after every - # prove. A heavier block pushes it past 2 GiB, which needs rkyv `pointer_width_64`; - # a PR branch predating that fix fails at write time rather than mismeasuring. + # Epoch 2^22, re-measured on THIS runner on 2026-09-08 for the rebuilt Amsterdam + # workload (10 proves at 2^22, one each at 2^21 and 2^23; box idle): # - # Epoch 2^22, from the CPU sweep on 2026-07-31 (124 GiB / 32-core box, real block, - # branch vintage; full table in tooling/ethrex-block-converter/README.md): + # 2^21 78.54 s 23.93 GiB RSS 683 MB proof + # 2^22 69.10 s 35.75 GiB RSS 466 MB proof <- default + # 2^23 63.93 s 51.46 GiB RSS 378 MB proof # - # 2^21 464.26 s 18.43 GiB RSS 26 epochs 1.72 GB proof - # 2^22 397.88 s 32.21 GiB RSS 13 epochs 1.15 GB proof - # 2^23 356.47 s 60.01 GiB RSS 7 epochs 0.90 GB proof + # Memory, not speed, is what picks 2^22: 2^23 buys 7.5% of wall but leaves little + # margin against this runner's >=64 GiB floor, and 2^21 pays 13.7% of wall to save + # 12 GiB nobody needs. The 2^22 line is the baseline the A/B leans on: mean 69.10 s + # over 10 runs, sd 0.41 s, CV 0.59%, whole range 1.23 s. Verification of one bundle + # is 6.86 s. Same table, with the epoch trade-off, in tooling/ethrex-fixtures/README.md. # - # Those RSS figures are the CALIBRATION BOX's, and memory does not transfer between - # machines any better than seconds do: this runner measured ~52 GB of peak heap for - # the same block at 2^22 — over 1.5x the calibration box's 32.21 GiB, and close - # enough to its >=64 GiB floor that 2^23 (60 GiB on the roomier box, so more here) - # is out of the question. Memory, not speed, is what picks 2^22. Moving off 2^21 is - # worth ~14% wall on the calibration box; treat that ratio as transferable and - # neither the absolute seconds nor the RSS column as such. This runner's own - # measured time at 2^22 is 158.8 s (median of 3, 2.8% spread, 13 epochs). + # The pre-bump numbers this block replaces (50.78M cycles, 158.8 s at 2^22 on this + # runner, ~52 GB) measured a DIFFERENT workload: that fixture was an rkyv + # ProgramInput, which ethrex 25's guest rejects. Do not compare across the bump. # # Deliberately NOT the CLI's DEFAULT_CONTINUATION_EPOCH_SIZE_LOG2, which stays 20 so # a laptop can still prove; and not the GPU path's 2^22, which happens to coincide @@ -116,11 +123,15 @@ env: # each run is minutes rather than seconds; enough that one slow run shows up as # spread instead of moving the median. # - # THIS IS THE DIAL. A run measures 158.8 s on this runner (median of 3, 2.8% spread, - # at the epoch above), so 3 runs is ~8 min of proving and the right count. Should a + # THIS IS THE DIAL. A run measures 69.10 s on this runner (mean of 10, CV 0.59%, at + # the epoch above), so 3 runs is ~3.5 min of proving and the right count. Should a # future block or prover change take a run past ~6 min, /bench becomes a ~25 min # occupancy of a runner every other bench queues behind, and this count is what to # turn down (2, or 1) before reaching for anything else. + # + # What 3 runs can actually resolve, from that sd: a two-sided 95% delta of ~2.08%. + # Five per side gets to ~1.04% and ten to ~0.60%, so a sub-2% claim needs `/bench N` + # (clamped to 5) or the ABBA tiebreaker, not a re-read of a 3-run table. # `/bench N` overrides this, clamped to [1,5]. Past 5 the cached comparison can't # beat the ~1% session-drift wall anyway — that is what /bench-abba is for, and it # proves this same block at this same epoch, so escalating keeps the question fixed. @@ -225,10 +236,10 @@ jobs: # not need a second command to ask for, and it is the only workload /bench # proves. # - # The cost is real and lands on a shared runner: 158.8 s per run x - # BENCH_RUNS_REAL is ~8 min of proving, so every /bench and every push to main - # occupies the bench server for roughly 15 min once checkout, the two-sided - # build, the fixture fetch and the guest ELF are counted, and /bench-abba and + # The cost is real and lands on a shared runner: 69.10 s per run x + # BENCH_RUNS_REAL is ~3.5 min of proving, so every /bench and every push to main + # occupies the bench server for roughly 10 min once checkout, the two-sided + # build, the fixture build and the guest ELF are counted, and /bench-abba and # /bench-verify queue behind it. That trade was made deliberately — see # BENCH_RUNS_REAL above for the dial if it proves too expensive. RUN_REAL=true @@ -269,9 +280,9 @@ jobs: # Clamp to 1-5. Beyond 5 the single-session cached comparison barely improves # (it can't beat the ~1% drift wall); use /bench-abba for finer deltas — it - # resolves ~1% over the same block in ~72 min at its default 12 pairs. - # At 158.8 s a run this is also the difference between a ~10 min and a ~20 min - # occupancy of the one bench runner. + # resolves ~0.5% over the same block in ~36 min at its default 12 pairs. + # At 69.10 s a run this is also the difference between a ~7 min and a ~12 min + # occupancy of the one bench runner. Three runs resolve ~2.08%, five ~1.04%. if [ "$RUNS" -lt 1 ] 2>/dev/null || [ "$RUNS" -gt 5 ] 2>/dev/null; then echo "::warning::Run count $RUNS out of range [1,5], defaulting to $BENCH_RUNS_REAL" RUNS=$BENCH_RUNS_REAL diff --git a/.github/workflows/pr_main.yaml b/.github/workflows/pr_main.yaml index 767e166de..08e64c086 100644 --- a/.github/workflows/pr_main.yaml +++ b/.github/workflows/pr_main.yaml @@ -131,10 +131,10 @@ jobs: cargo test --release -p executor test_ckzg -- --ignored # ethrex host-reference tests live in the detached `tooling/ethrex-tests` - # workspace (ethrex pins rkyv's `unaligned` feature, which must not - # feature-unify with the main workspace's aligned proof format), so run - # them from that directory to use its isolated Cargo.lock. The guest ELF - # and committed fixtures are already present from the steps above. + # workspace (the ethrex host dependency tree is heavy and pinned by rev in its + # own Cargo.lock, kept out of the main build), so run them from that directory + # to use that lockfile. The guest ELF and committed fixtures are already + # present from the steps above. # --include-ignored also runs the heavier synthetic-block test. # # `--skip test_ethrex_real_block` is a substring match, so it drops BOTH diff --git a/Makefile b/Makefile index ced7743f2..2c1400322 100644 --- a/Makefile +++ b/Makefile @@ -303,8 +303,8 @@ test-rust: compile-programs-rust # names a block. Outside this file the repoint touches only REAL_BLOCK_FIXTURE in # tooling/ethrex-tests, which points the usability screen at the block actually # being proven. The converter's own pins do NOT move — see below. -# tooling/ethrex-block-converter/README.md carries the procedure and each candidate's -# measured cost. +# tooling/ethrex-fixtures/README.md carries what the workload costs and how to pick +# the epoch size; the converter's README covers converting a cache by hand. ETHREX_REAL_BLOCK_NETWORK := mainnet ETHREX_REAL_BLOCK := 25368371 # The fixture is GENERATED from the cache below, not fetched. ethrex 25's guest @@ -362,8 +362,9 @@ define ensure_verified if [ -z "$(1)" ]; then \ echo "$(4): $(5) is unset." >&2; \ echo " The $(ETHREX_REAL_BLOCK_ID) $(4) is fetched, not built. Set $(5) in the" >&2; \ - echo " Makefile to wherever the artifact is hosted; see" >&2; \ - echo " tooling/ethrex-block-converter/README.md for how to produce and host one." >&2; \ + echo " Makefile to wherever it is hosted. The FIXTURE is not fetched at all -" >&2; \ + echo " it is built by 'make regen-real-block-fixture'; see" >&2; \ + echo " tooling/ethrex-fixtures/README.md." >&2; \ exit 1; \ fi; \ mkdir -p $(dir $(3)); \ @@ -391,7 +392,12 @@ endef # cargo build to the critical path. ethrex-real-block-fixture: $(ETHREX_REAL_BLOCK_FIXTURE) -$(ETHREX_REAL_BLOCK_FIXTURE): ethrex-real-block-cache +# No prerequisites on purpose. `ethrex-real-block-cache` is phony (so its digest is +# re-checked on every call), and a phony prerequisite always reads as newer than its +# target, which would rebuild the fixture on every single benchmark invocation. The +# cache is fetched from inside `regen-real-block-fixture` instead, so this recipe runs +# only when the file is genuinely missing. +$(ETHREX_REAL_BLOCK_FIXTURE): $(MAKE) regen-real-block-fixture ethrex-real-block-cache: diff --git a/scripts/bench_verify.sh b/scripts/bench_verify.sh index 369041c48..9deaea0df 100755 --- a/scripts/bench_verify.sh +++ b/scripts/bench_verify.sh @@ -37,7 +37,7 @@ # CONT_EPOCH_LOG2= continuation epoch size (default 20, min 18). 20 is the # laptop-safe setting, not the fast one: prefer the calibrated tier for the box # you are on — 2^22 on the bench runner or a 64 GiB machine, 2^23 on a 128 GiB -# one (see tooling/ethrex-block-converter/README.md, "Choosing the epoch size"), +# one (see tooling/ethrex-fixtures/README.md, "Choosing the epoch size"), # which is what /bench and /bench-abba pin. (`cli prove --epoch-size-log2 --help` # measured ethrex 10tx at ~9.5 GB for 2^20 vs ~15.8 GB for 2^21.) Note this does # NOT match bench_recursion_cycles.sh's BLOCK_EPOCH_LOG2=21: that arm needs FEW diff --git a/scripts/perf_diff.sh b/scripts/perf_diff.sh index 2c359a548..e6e949fb7 100755 --- a/scripts/perf_diff.sh +++ b/scripts/perf_diff.sh @@ -16,7 +16,7 @@ # Env: WORKLOAD=real|synthetic (default real) picks the block to profile; # EPOCH_SIZE_LOG2= (default 22) sizes the epoch, WORKLOAD=real only. # 22 is the calibrated bench-runner tier, matching /bench; use 23 on a -# 128 GiB box (tooling/ethrex-block-converter/README.md, "Choosing the epoch size"). +# 128 GiB box (tooling/ethrex-fixtures/README.md, "Choosing the epoch size"). # # Pick the workload that matches the run you are localizing, because the symbol # mix follows the block: the real default is 50.78M cycles, 10,478 keccak calls and diff --git a/tooling/ethrex-fixtures/README.md b/tooling/ethrex-fixtures/README.md index 04b734860..f7a5f062c 100644 --- a/tooling/ethrex-fixtures/README.md +++ b/tooling/ethrex-fixtures/README.md @@ -82,3 +82,25 @@ repository root has both sets measured side by side. Measured on the guest ELF at ethrex `2cb18b0b`: 20,360,647 cycles, 2,701 keccak calls, 116 ECSM calls. + +### Choosing the epoch size, and what the workload costs + +Measured on the bench runner (`vm-benchmarks-1`, 96 cores / 125 GB, idle) on +2026-09-08, with the guest ELF at ethrex `2cb18b0b`: + +| epoch | wall | host RSS | proof | +| ---: | ---: | ---: | ---: | +| 2^21 | 78.54 s | 23.93 GiB | 683 MB | +| **2^22** | **69.10 s** (mean of 10, sd 0.41 s, CV 0.59 %) | **35.75 GiB** | **466 MB** | +| 2^23 | 63.93 s | 51.46 GiB | 378 MB | + +Verifying one 2^22 bundle takes 6.86 s. 2^22 is what `/bench`, `/bench-abba` and +the GPU bench pin: 2^23 buys 7.5 % of wall but leaves little margin against the +runner's 64 GiB floor, and 2^21 pays 13.7 % to save memory nobody needs. + +With that sd, a two-sided 95 % comparison resolves ~2.08 % at three runs per +side, ~1.04 % at five and ~0.60 % at ten — which is why a sub-2 % claim needs +`/bench N` or the ABBA tiebreaker rather than a re-read of a three-run table. + +Continuations are not optional here: monolithic proving costs ~4.9 GB of peak +heap per million cycles on this family, so 20.36M cycles would need ~100 GB. From 349d2033b296cbf7e22ab8153782af7aedeca7d8 Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Tue, 8 Sep 2026 19:28:03 -0300 Subject: [PATCH 09/27] Repoint the benchmark to mainnet 25453112, the closest Amsterdam-rebuilt equivalent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Under Amsterdam, 25368371 loses so much of its gas to the new state-gas model that the workload it rebuilds into is 20.36M cycles against the 30.50M the retired fixture costs on today's guest — a third lighter than what /bench used to prove. Screening twelve real mainnet blocks (both release caches plus ethrex's curated zkevm_bench corpus) through the generator put 25453112 closest at 37.14M cycles, +22% against that target, where the next candidate up lands at +197%. Its cache is already hosted in bench-fixtures-v1 with a published digest, so the repoint publishes nothing: block number, cache URL and sha256 in the Makefile, and the fixture name the usability screen reads. Reverts are not a selection criterion. Every pre-Amsterdam block loses 26-50% of its transactions to the fork, which is a property of the fork rather than of any block, and several of the twelve end up consuming more gas than they did on mainnet because Amsterdam makes the surviving transactions dearer. Measured on the bench runner at epoch 2^22, five proves, box idle: 125.17 s mean, sd 0.71 s, CV 0.57%, 43.00 GiB peak, 790 MB bundle, 12.0 s to verify. Every seconds-or-bytes figure in the bench workflows moves with it, as does what three runs can resolve (~2.00%). One behavioural change comes with it: the GPU arm's cpu_ram floor goes from 48 GB to 64. At 43.00 GiB the peak is 96% of a 48 GB box, which is an OOM waiting for a rental with a hungrier allocator; 64 GB restores the headroom the old floor had and matches the CPU runner's. It costs offer availability. --- .github/workflows/bench-abba.yml | 36 ++++++------- .github/workflows/benchmark-gpu.yml | 19 ++++--- .github/workflows/benchmark-pr.yml | 58 ++++++++++---------- Makefile | 15 ++++-- tooling/ethrex-fixtures/README.md | 80 +++++++++++++++------------- tooling/ethrex-tests/tests/ethrex.rs | 2 +- 6 files changed, 114 insertions(+), 96 deletions(-) diff --git a/.github/workflows/bench-abba.yml b/.github/workflows/bench-abba.yml index afb8da0bc..c691fd5b2 100644 --- a/.github/workflows/bench-abba.yml +++ b/.github/workflows/bench-abba.yml @@ -10,23 +10,23 @@ name: Bench ABBA tiebreaker # # Resolution, from the paired t-test (resolvable 95% delta = t* x sd / sqrt(N)). The # pair-delta sd on this runner is NOT yet measured; the two columns bracket it between -# 0.6% and 0.85% (sqrt(2) x this runner's measured 0.59% single-run CV, from the -# 10-prove baseline in tooling/ethrex-fixtures/README.md): +# 0.6% and 0.81% (sqrt(2) x this runner's measured 0.57% single-run CV, from the +# 5-prove baseline in tooling/ethrex-fixtures/README.md): # -# pairs wall resolves (sd 0.85% / sd 0.6%) -# 8 ~26 min 0.71% / 0.50% -# 12 ~36 min 0.54% / 0.38% <- default -# 20 ~54 min 0.40% / 0.28% -# 32 ~1h22m 0.31% / 0.22% +# pairs wall resolves (sd 0.81% / sd 0.6%) +# 8 ~41 min 0.67% / 0.50% +# 12 ~58 min 0.51% / 0.38% <- default +# 20 ~1h31m 0.38% / 0.28% +# 32 ~2h22m 0.29% / 0.22% # -# Wall is 2 x 69.10 s per pair (the runner's measured prove at epoch 2^22) plus ~8 min +# Wall is 2 x 125.17 s per pair (the runner's measured prove at epoch 2^22) plus ~8 min # of setup. The FIRST run measures the sd — it is the `sd` field of the paired-t line # in the result comment — and this table should be re-pinned to it then. # -# Both columns tightened when the workload was rebuilt for ethrex 25: the block is -# 20.36M cycles instead of 50.78M and the single-run CV fell from 1.43% to 0.59%, so a -# pair now costs 2.3 min instead of 5.3. Numbers from before that bump measured a -# different fixture and are not comparable. +# Both columns tightened when the workload was rebuilt for ethrex 25: the single-run +# CV fell from 1.43% to 0.57%, so 12 pairs now resolve ~0.5% where they used to resolve +# ~1.3%. Numbers from before that bump measured a different fixture (block 25368371 as +# an rkyv ProgramInput) and are not comparable. on: issue_comment: types: [created] @@ -34,8 +34,8 @@ on: env: # The epoch /bench proves this block at, so the tiebreaker resolves the same # configuration and not just the same block. Memory picks it: this runner peaks at - # 35.75 GiB here against its >=64 GiB floor, and 2^23 would take that to 51.46 GiB - # for 7.5% of wall. See benchmark-pr.yml's REAL_BLOCK_EPOCH_LOG2. + # 43.00 GiB here against its >=64 GiB floor, and 2^23 would take that past 50 GiB + # for ~7% of wall. See benchmark-pr.yml's REAL_BLOCK_EPOCH_LOG2. ABBA_REAL_EPOCH_LOG2: "22" concurrency: @@ -56,8 +56,8 @@ jobs: startsWith(github.event.comment.body, '/bench-abba') && contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association) runs-on: [self-hosted, bench] - # Hang guardrail, not expected duration: a real-block pair is 2 x 69.10 s = ~2.3 min, - # so the default 12 pairs runs ~36 min and the 40-pair clamp ~1.6 hr, plus up to + # Hang guardrail, not expected duration: a real-block pair is 2 x 125.17 s = ~4.2 min, + # so the default 12 pairs runs ~58 min and the 40-pair clamp ~2.9 hr, plus up to # ~30 min of two-sided build on a cold cache. timeout-minutes: 360 steps: @@ -123,9 +123,9 @@ jobs: await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, - // A pair is TWO proves at the runner's measured 69.10 s, so ~2.3 min/pair, + // A pair is TWO proves at the runner's measured 125.17 s, so ~4.2 min/pair, // plus ~8 min of checkout, two-sided build and fixture build. - body: `⏳ **ABBA tiebreaker started** on the bench server: ${process.env.PAIRS} pairs of ${process.env.WORKLOAD} — a pair is 2 proves at ~69 s, so roughly ${Math.round(8 + Number(process.env.PAIRS) * 2.3)} min. Pass a smaller pair count for a quicker, coarser run. The bench server is occupied until it finishes.` + body: `⏳ **ABBA tiebreaker started** on the bench server: ${process.env.PAIRS} pairs of ${process.env.WORKLOAD} — a pair is 2 proves at ~125 s, so roughly ${Math.round(8 + Number(process.env.PAIRS) * 4.2)} min. Pass a smaller pair count for a quicker, coarser run. The bench server is occupied until it finishes.` }); - name: Checkout (full history for ref resolution) diff --git a/.github/workflows/benchmark-gpu.yml b/.github/workflows/benchmark-gpu.yml index ac4d09310..2fec522bb 100644 --- a/.github/workflows/benchmark-gpu.yml +++ b/.github/workflows/benchmark-gpu.yml @@ -229,11 +229,16 @@ jobs: # because vast can't numerically compare the driver_version string server-side. MIN_DRIVER: "580" run: | - # cpu_ram filter is in GB. Floor 48 GB: the real block at epoch 2^22 peaks at - # ~36 GB host RSS on the CUDA path (measured, main vintage) — ~25% headroom. - # The workload rebuilt for ethrex 25 peaks at 35.75 GiB on the CPU path - # (10-prove baseline of 2026-09-08), so the floor keeps its headroom; it is - # the epoch, not the block, that would move it. + # cpu_ram filter is in GB. Floor 64 GB, raised from 48 with the ethrex 25 + # workload: that block peaks at 43.00 GiB (46.2 GB) at epoch 2^22 on the CPU + # path, which on a 48 GB box is 96% of RAM and an OOM waiting for a rental + # with a slightly hungrier allocator. 64 GB restores the ~28% headroom the + # 48 GB floor had for the old block (~36 GB host RSS on the CUDA path, + # measured at main vintage) and matches the CPU runner's own floor. It costs + # offer availability: fewer boxes qualify, so PRICE_CAP may need a look if + # the query starts coming back empty. Peak is set by the epoch size, not the + # block, so dropping GPU_REAL_EPOCH_LOG2 to 21 is the other lever (~24 GiB, + # +14% wall). # Continuation peak is set by the epoch size, not the block, so bigger blocks # don't move it; raising the epoch would (see the epoch table in # tooling/ethrex-fixtures/README.md). @@ -249,7 +254,7 @@ jobs: # at the toolchain sanity gate. `reliability` is the queryable field (the # `reliability2` in the response schema is display-only, not filterable). # Over-strict just yields no offers, surfaced by the retry loop's "No offer". - QUERY="gpu_name=${GPU_NAME} num_gpus=1 gpu_frac=1 cpu_cores_effective>=16 cpu_cores_effective<=32 cpu_ram>=48 disk_space>=64 verified=true rentable=true reliability>=0.95 cuda_max_good>=12.8 dph_total<=${PRICE_CAP}" + QUERY="gpu_name=${GPU_NAME} num_gpus=1 gpu_frac=1 cpu_cores_effective>=16 cpu_cores_effective<=32 cpu_ram>=64 disk_space>=64 verified=true rentable=true reliability>=0.95 cuda_max_good>=12.8 dph_total<=${PRICE_CAP}" echo "Query: $QUERY (+ client-side driver_version major >= $MIN_DRIVER)" # Keep only offers whose driver major >= MIN_DRIVER, then most expensive first # (within the price cap). Within the now whole-machine pool, price just tracks @@ -498,7 +503,7 @@ jobs: # That surfaces as the OOM killer reaping a rustc ("signal: 9") or as an # allocation failure mid-compile. (Distinct from the toolchain gate's concern # above, which is a host that is broken before any load is applied.) - # 8 leaves ~6 GB/job on the >=48 GB floor; the build is a one-time per-bench + # 8 leaves ~8 GB/job on the >=64 GB floor; the build is a one-time per-bench # cost, and the job timeout above has ample room for it. REMOTE="set -e; cd /workspace/lambda_vm; \ command -v python3 >/dev/null || { apt-get update -qq && apt-get install -y -qq python3; }; \ diff --git a/.github/workflows/benchmark-pr.yml b/.github/workflows/benchmark-pr.yml index 4a1b01410..8eb2d3f9c 100644 --- a/.github/workflows/benchmark-pr.yml +++ b/.github/workflows/benchmark-pr.yml @@ -70,9 +70,9 @@ env: ELF: executor/program_artifacts/rust/ethrex.elf # The workload: a real Ethereum block. WHICH block lives in # the Makefile and nowhere else — nothing in this file names one, so a repoint - # moves this job without editing it. At the current default that is 20,360,647 - # cycles, 2,701 keccak calls and 116 ecsm calls: 7.5x the work of the synthetic - # block (ethrex_bench_4, 2,727,681 cycles) with a keccak:ecrecover mix of 23:1 + # moves this job without editing it. At the current default that is 37,137,386 + # cycles, 6,003 keccak calls and 164 ecsm calls: 13.6x the work of the synthetic + # block (ethrex_bench_4, 2,727,681 cycles) with a keccak:ecrecover mix of 37:1 # against its 13:1. That is the whole point — a prover change can move the # synthetic number and the real one in opposite directions. # @@ -88,32 +88,32 @@ env: # Continuations are mandatory here, not a preference: a monolithic prove costs # ~4.9 GB of peak heap per million cycles on this workload family (from the # measured growth fit, 10,728 MB + 2,007 MB/transfer at R^2 = 0.998), so the - # current default would need ~100 GB and a heavier candidate far more. + # current default would need ~182 GB and a heavier candidate far more. # `--continuations` makes peak heap a function of the epoch size instead of the # trace length. Costs move with the block; what this one costs is in # tooling/ethrex-fixtures/README.md. # - # Budget ~470 MB of disk for the bundle each run (measured: 466,015,152 bytes) — + # Budget ~790 MB of disk for the bundle each run (measured: 828,430,552 bytes) — # hence the `rm -f` after every prove. A heavier block pushes it past 2 GiB, which # needs rkyv `pointer_width_64` in the PROVER's bundle serialisation; a PR branch # predating that fix fails at write time rather than mismeasuring. # - # Epoch 2^22, re-measured on THIS runner on 2026-09-08 for the rebuilt Amsterdam - # workload (10 proves at 2^22, one each at 2^21 and 2^23; box idle): + # Epoch 2^22, measured on THIS runner on 2026-09-08 (box idle): mean 125.17 s over + # 5 proves, sd 0.71 s, CV 0.57%, 43.00 GiB peak RSS, 790 MB bundle; verifying one + # bundle is 12.0 s. # - # 2^21 78.54 s 23.93 GiB RSS 683 MB proof - # 2^22 69.10 s 35.75 GiB RSS 466 MB proof <- default - # 2^23 63.93 s 51.46 GiB RSS 378 MB proof - # - # Memory, not speed, is what picks 2^22: 2^23 buys 7.5% of wall but leaves little - # margin against this runner's >=64 GiB floor, and 2^21 pays 13.7% of wall to save - # 12 GiB nobody needs. The 2^22 line is the baseline the A/B leans on: mean 69.10 s - # over 10 runs, sd 0.41 s, CV 0.59%, whole range 1.23 s. Verification of one bundle - # is 6.86 s. Same table, with the epoch trade-off, in tooling/ethrex-fixtures/README.md. + # Memory, not speed, is what picks 2^22. The epoch trade-off was swept on the + # previous block (2^21 +13.7% wall / -12 GiB, 2^23 -7.5% wall / +16 GiB): peak RSS + # is set by the epoch size rather than the block, so the shape carries over even + # though the absolute seconds do not. 2^23 would take this workload past 50 GiB + # against the runner's >=64 GiB floor. Both tables in + # tooling/ethrex-fixtures/README.md. # - # The pre-bump numbers this block replaces (50.78M cycles, 158.8 s at 2^22 on this - # runner, ~52 GB) measured a DIFFERENT workload: that fixture was an rkyv - # ProgramInput, which ethrex 25's guest rejects. Do not compare across the bump. + # The pre-bump numbers this replaces (block 25368371, 50.78M cycles, 158.8 s at + # 2^22 on this runner, ~52 GB) measured a DIFFERENT workload: that fixture was an + # rkyv ProgramInput, which ethrex 25's guest rejects. Do not compare across the + # bump. This block was picked to land as close to it as an Amsterdam rebuild can — + # see the screen in the Makefile's ETHREX_REAL_BLOCK comment. # # Deliberately NOT the CLI's DEFAULT_CONTINUATION_EPOCH_SIZE_LOG2, which stays 20 so # a laptop can still prove; and not the GPU path's 2^22, which happens to coincide @@ -123,14 +123,14 @@ env: # each run is minutes rather than seconds; enough that one slow run shows up as # spread instead of moving the median. # - # THIS IS THE DIAL. A run measures 69.10 s on this runner (mean of 10, CV 0.59%, at - # the epoch above), so 3 runs is ~3.5 min of proving and the right count. Should a + # THIS IS THE DIAL. A run measures 125.17 s on this runner (mean of 5, CV 0.57%, at + # the epoch above), so 3 runs is ~6.3 min of proving and the right count. Should a # future block or prover change take a run past ~6 min, /bench becomes a ~25 min # occupancy of a runner every other bench queues behind, and this count is what to # turn down (2, or 1) before reaching for anything else. # - # What 3 runs can actually resolve, from that sd: a two-sided 95% delta of ~2.08%. - # Five per side gets to ~1.04% and ten to ~0.60%, so a sub-2% claim needs `/bench N` + # What 3 runs can actually resolve, from that sd: a two-sided 95% delta of ~2.00%. + # Five per side gets to ~1.00% and ten to ~0.58%, so a sub-2% claim needs `/bench N` # (clamped to 5) or the ABBA tiebreaker, not a re-read of a 3-run table. # `/bench N` overrides this, clamped to [1,5]. Past 5 the cached comparison can't # beat the ~1% session-drift wall anyway — that is what /bench-abba is for, and it @@ -236,9 +236,9 @@ jobs: # not need a second command to ask for, and it is the only workload /bench # proves. # - # The cost is real and lands on a shared runner: 69.10 s per run x - # BENCH_RUNS_REAL is ~3.5 min of proving, so every /bench and every push to main - # occupies the bench server for roughly 10 min once checkout, the two-sided + # The cost is real and lands on a shared runner: 125.17 s per run x + # BENCH_RUNS_REAL is ~6.3 min of proving, so every /bench and every push to main + # occupies the bench server for roughly 13 min once checkout, the two-sided # build, the fixture build and the guest ELF are counted, and /bench-abba and # /bench-verify queue behind it. That trade was made deliberately — see # BENCH_RUNS_REAL above for the dial if it proves too expensive. @@ -280,9 +280,9 @@ jobs: # Clamp to 1-5. Beyond 5 the single-session cached comparison barely improves # (it can't beat the ~1% drift wall); use /bench-abba for finer deltas — it - # resolves ~0.5% over the same block in ~36 min at its default 12 pairs. - # At 69.10 s a run this is also the difference between a ~7 min and a ~12 min - # occupancy of the one bench runner. Three runs resolve ~2.08%, five ~1.04%. + # resolves ~0.5% over the same block in ~58 min at its default 12 pairs. + # At 125.17 s a run this is also the difference between a ~10 min and a ~20 min + # occupancy of the one bench runner. Three runs resolve ~2.00%, five ~1.00%. if [ "$RUNS" -lt 1 ] 2>/dev/null || [ "$RUNS" -gt 5 ] 2>/dev/null; then echo "::warning::Run count $RUNS out of range [1,5], defaulting to $BENCH_RUNS_REAL" RUNS=$BENCH_RUNS_REAL diff --git a/Makefile b/Makefile index 2c1400322..4c43f0ec7 100644 --- a/Makefile +++ b/Makefile @@ -306,7 +306,16 @@ test-rust: compile-programs-rust # tooling/ethrex-fixtures/README.md carries what the workload costs and how to pick # the epoch size; the converter's README covers converting a cache by hand. ETHREX_REAL_BLOCK_NETWORK := mainnet -ETHREX_REAL_BLOCK := 25368371 +ETHREX_REAL_BLOCK := 25453112 +# WHY THIS BLOCK: 25368371 was the pre-bump default, and under Amsterdam its +# transactions lose so much gas to the new state-gas model that the workload drops +# to 20.36M cycles against the 30.50M the retired fixture cost on today's guest. A +# screen of twelve real mainnet blocks (our two release caches plus ethrex's curated +# zkevm_bench corpus) put this one closest: 37.14M cycles, +22% against that target, +# where 25368371 is -33% and the next candidate up (25087308) is +197%. Reverts are +# not a selection criterion — every pre-Amsterdam block loses 26-50% of its +# transactions to the fork, which is a property of the fork and not of the block. +# # The fixture is GENERATED from the cache below, not fetched. ethrex 25's guest # decodes only the Amsterdam schema (`0x1501`) and mainnet has no Amsterdam fork, # so no hosted artifact for this block can be valid: the release's rkyv one now @@ -323,8 +332,8 @@ ETHREX_REAL_BLOCK_FIXTURE_SHA256 := # The block's source cache: an ethrex-replay dump, fork-independent, still the one # hosted in bench-fixtures-v1. Only the fixture rebuild reads it; converter TESTS # use a different, upstream-pinned cache (below). -ETHREX_REAL_BLOCK_CACHE_URL := https://github.com/yetanotherco/lambda_vm/releases/download/bench-fixtures-v1/cache_mainnet_25368371.json -ETHREX_REAL_BLOCK_CACHE_SHA256 := 7aa88a5f7c5755b7575870f95e6c5c26186947f5e9e0d52199148c74e2a2736b +ETHREX_REAL_BLOCK_CACHE_URL := https://github.com/yetanotherco/lambda_vm/releases/download/bench-fixtures-v1/cache_mainnet_25453112.json +ETHREX_REAL_BLOCK_CACHE_SHA256 := 20ffbbc1b051df9dfa6285f0ea7bd3f6d054db883aba92230ea20350f1b1d4ad ETHREX_REAL_BLOCK_ID := $(ETHREX_REAL_BLOCK_NETWORK)_$(ETHREX_REAL_BLOCK) ETHREX_REAL_BLOCK_FIXTURE := executor/tests/ethrex_$(ETHREX_REAL_BLOCK_ID).bin diff --git a/tooling/ethrex-fixtures/README.md b/tooling/ethrex-fixtures/README.md index f7a5f062c..5ce78b66a 100644 --- a/tooling/ethrex-fixtures/README.md +++ b/tooling/ethrex-fixtures/README.md @@ -48,59 +48,63 @@ block touches arrives intact — registers a parent header at the block's real height, replays the transactions in the block's own order through the t8n entry point, and validates the result through the native guest before writing. -Output for mainnet 25368371 (29 transactions, 2,428,684 gas): +Output for mainnet 25453112 (38 transactions, 4,238,394 gas): ``` -installed 91 accounts / 117 storage slots / 91 codes -rebuilt #25368371 (29/29 txs, 1803938 gas, 12 reverted) +installed 132 accounts / 261 storage slots / 132 codes +rebuilt #25453112 (38/38 txs, 3761976 gas, 10 reverted) ``` -### Why 12 transactions revert, and why that is not a defect here +### Why 10 transactions revert, and why that is not a defect here Those transactions were signed with gas limits computed under Osaka. Amsterdam changes the gas model: cold account access goes from 2600 to 3000, and EIP-8037 carves a state-gas reservoir out of the transaction's excess gas limit. A transfer sent with a limit of exactly 21,000 has no excess to carve, so it runs -out of gas. The same rebuild under Osaka rules — `REAL_BLOCK_FORK=osaka`, which -prints the totals and exits without writing, since the guest only decodes the -Amsterdam schema — reverts **2** transactions instead of 12: - -| rules | txs | gas | reverted | -| --- | --- | ---: | ---: | -| Osaka (what the block was built for) | 29/29 | 1,473,522 | 2 | -| Amsterdam | 29/29 | 1,803,938 | 12 | -| mainnet, as mined | 29 | 2,428,684 | — | - -So the gap is the fork, not the reconstruction: a pre-Amsterdam block cannot be -a faithful Amsterdam workload at any level of tooling effort. What this fixture -is, therefore, is a *real-mix* Amsterdam block — real contract code, real -calldata, real signatures (the accelerator counters agree exactly with the -retired fixture at 116 ECSM calls), real trie depth — and not a replay of -mainnet economics. For workloads whose gas limits were computed for Amsterdam, -use the EEST benchmark fixtures; `ETHREX_BENCH_WORKLOAD_AFTER_BUMP.md` in the -repository root has both sets measured side by side. - -Measured on the guest ELF at ethrex `2cb18b0b`: 20,360,647 cycles, 2,701 keccak -calls, 116 ECSM calls. +out of gas. Running the same rebuild under Osaka rules — `REAL_BLOCK_FORK=osaka`, +which prints the totals and exits without writing, since the guest only decodes +the Amsterdam schema — is what separates the fork from the reconstruction: on +block 25368371 it reverts 2 transactions where Amsterdam reverts 12. + +Every pre-Amsterdam block loses transactions this way. A screen of twelve real +mainnet blocks (both of our release caches plus ethrex's curated zkevm_bench +corpus) put the revert share between 26% and 50% with no exceptions, so it is not +a criterion for picking one. What the screen was for is weight: the retired +fixture cost 30.50M cycles on today's guest, and this block rebuilds to 37.14M +(+22%), the closest of the twelve — 25368371 comes in at -33% and the next +candidate up, 25087308, at +197%. Several blocks end up consuming *more* gas than +they did on mainnet (+7% to +22%), because Amsterdam makes the surviving +transactions dearer. + +So what this fixture is, is a *real-mix* Amsterdam block — real contract code, +real calldata, real signatures, real trie depth — and not a replay of mainnet +economics. For workloads whose gas limits were computed for Amsterdam, use the +EEST benchmark fixtures; `ETHREX_BENCH_WORKLOAD_AFTER_BUMP.md` in the repository +root has both sets measured side by side. + +Measured on the guest ELF at ethrex `2cb18b0b`: 37,137,386 cycles, 6,003 keccak +calls, 164 ECSM calls. Fixture: 549,144 bytes. ### Choosing the epoch size, and what the workload costs -Measured on the bench runner (`vm-benchmarks-1`, 96 cores / 125 GB, idle) on -2026-09-08, with the guest ELF at ethrex `2cb18b0b`: +Measured on the bench runner (`vm-benchmarks-1`, which is also the CI +self-hosted `bench` runner: 96 cores / 125 GB, idle) on 2026-09-08, with the +guest ELF at ethrex `2cb18b0b`: -| epoch | wall | host RSS | proof | -| ---: | ---: | ---: | ---: | -| 2^21 | 78.54 s | 23.93 GiB | 683 MB | -| **2^22** | **69.10 s** (mean of 10, sd 0.41 s, CV 0.59 %) | **35.75 GiB** | **466 MB** | -| 2^23 | 63.93 s | 51.46 GiB | 378 MB | +| | mean | sd | CV | peak RSS | proof | verify | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| epoch 2^22, 5 proves | **125.17 s** | 0.71 s | 0.57 % | 43.00 GiB | 790 MB | 12.0 s | -Verifying one 2^22 bundle takes 6.86 s. 2^22 is what `/bench`, `/bench-abba` and -the GPU bench pin: 2^23 buys 7.5 % of wall but leaves little margin against the -runner's 64 GiB floor, and 2^21 pays 13.7 % to save memory nobody needs. +2^22 is what `/bench`, `/bench-abba` and the GPU bench pin. The epoch trade-off +itself was swept on the previous block (2^21 costs +13.7 % of wall to save +12 GiB; 2^23 buys −7.5 % for +16 GiB): peak RSS is set by the epoch size rather +than by the block, so that shape carries over even though the seconds do not. +2^23 would take this workload past 50 GiB against the runner's 64 GiB floor, +which is why memory and not speed picks the default. -With that sd, a two-sided 95 % comparison resolves ~2.08 % at three runs per -side, ~1.04 % at five and ~0.60 % at ten — which is why a sub-2 % claim needs +With that sd, a two-sided 95 % comparison resolves ~2.00 % at three runs per +side, ~1.00 % at five and ~0.58 % at ten — which is why a sub-2 % claim needs `/bench N` or the ABBA tiebreaker rather than a re-read of a three-run table. Continuations are not optional here: monolithic proving costs ~4.9 GB of peak -heap per million cycles on this family, so 20.36M cycles would need ~100 GB. +heap per million cycles on this family, so 37.14M cycles would need ~182 GB. diff --git a/tooling/ethrex-tests/tests/ethrex.rs b/tooling/ethrex-tests/tests/ethrex.rs index 95943ad99..a28e9be77 100644 --- a/tooling/ethrex-tests/tests/ethrex.rs +++ b/tooling/ethrex-tests/tests/ethrex.rs @@ -56,7 +56,7 @@ fn test_ethrex_empty_block() { run_fixture("ethrex_empty_block.bin"); } -const REAL_BLOCK_FIXTURE: &str = "ethrex_mainnet_25368371.bin"; +const REAL_BLOCK_FIXTURE: &str = "ethrex_mainnet_25453112.bin"; /// Host-only acceptance gate for the real SSZ stateless fixture produced by /// `tooling/ethrex-block-converter`. From f195b1362b00875d7089aff162c8113fbbb28c3e Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Wed, 9 Sep 2026 12:37:35 -0300 Subject: [PATCH 10/27] Fire the real-block validation workflow on the fixture generator and cache its build. The usability screen now builds the fixture with tooling/ethrex-fixtures instead of downloading it, so a change to real_block.rs or genesis.json could land without the job that validates it ever running, and the ethrex host tree it compiles was outside the rust-cache workspaces list. --- .github/workflows/ethrex-block-converter.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ethrex-block-converter.yml b/.github/workflows/ethrex-block-converter.yml index e8260e129..f8ca2a2ff 100644 --- a/.github/workflows/ethrex-block-converter.yml +++ b/.github/workflows/ethrex-block-converter.yml @@ -7,14 +7,15 @@ name: ethrex block-converter tests # network download plus a cold build of ~335 packages (blst, c-kzg and secp256k1-sys # C builds, malachite, ark-ff/asm). # -# It fires on the things that can actually invalidate it: the converter, the ethrex -# host-reference tests, and the Makefile (which holds the block pin, the fixture URL -# and its sha256). +# It fires on the things that can actually invalidate it: the fixture generator, the +# converter, the ethrex host-reference tests, and the Makefile (which holds the block +# pin, the cache URL and its sha256). on: workflow_dispatch: pull_request: branches: ["**"] paths: + - 'tooling/ethrex-fixtures/**' - 'tooling/ethrex-block-converter/**' - 'tooling/ethrex-tests/**' - 'Makefile' @@ -22,6 +23,7 @@ on: push: branches: ["main"] paths: + - 'tooling/ethrex-fixtures/**' - 'tooling/ethrex-block-converter/**' - 'tooling/ethrex-tests/**' - 'Makefile' @@ -81,6 +83,7 @@ jobs: shared-key: "lambda-vm-real-block-usable" cache-all-crates: "true" workspaces: | + tooling/ethrex-fixtures -> target tooling/ethrex-tests -> target # Built from the block's replay cache, not downloaded: ethrex 25's guest decodes From 7e562f6e48396e3d43f257ff649b7d8f39a65260 Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Wed, 9 Sep 2026 15:02:13 -0300 Subject: [PATCH 11/27] Drop the dead ETHREX_REAL_BLOCK_FIXTURE_URL and point the benchmark comment's diagnostic at ETHREX_REAL_BLOCK_CACHE_URL, which is the variable that actually gates the real-block step now that the fixture is generated instead of fetched: anyone following the old message would have gone looking at a variable that is permanently empty and read by nothing. Limit the minimum-cycle floor in bench_abba.sh to WORKLOAD=real. The synthetic fixtures are already gated twice, by their committed checksums and by the successful_validation assert in the host tests, while a small TX_COUNT lands near the floor legitimately -- a one-transfer block ran 1.80M cycles before the bump and an empty one 0.99M -- so applying it there would reject valid runs. Correct three comments the rebuild left stale: the EEST probe is committed rather than untracked, the real-block fixture is produced by tooling/ethrex-fixtures rather than by the converter, and the real block costs 37M cycles rather than 20M. Record in the Makefile that the fixture name is pinned in the host tests as well, so repointing the block needs that edit too; a glob there would silently pick up a stale fixture from an earlier block and pass. --- .github/workflows/benchmark-pr.yml | 2 +- Makefile | 5 ++++- scripts/bench_abba.sh | 10 ++++++++-- tooling/ethrex-tests/tests/eest_amsterdam_probe.rs | 6 ++++-- tooling/ethrex-tests/tests/ethrex.rs | 4 +++- 5 files changed, 20 insertions(+), 7 deletions(-) diff --git a/.github/workflows/benchmark-pr.yml b/.github/workflows/benchmark-pr.yml index 8eb2d3f9c..78e359c91 100644 --- a/.github/workflows/benchmark-pr.yml +++ b/.github/workflows/benchmark-pr.yml @@ -978,7 +978,7 @@ jobs: if (!realTime) { body += `\n> 🧱 **No prover measurement — the real-block fixture was not available.** `; body += `\`/bench\` proves only the real block, so nothing was measured this run. `; - body += `Check that \`ETHREX_REAL_BLOCK_FIXTURE_URL\` is set in the Makefile and that the artifact is reachable; the run log carries the warning.\n`; + body += `Check that \`ETHREX_REAL_BLOCK_CACHE_URL\` is set in the Makefile and that the replay cache is reachable; the fixture is generated from it, and the run log carries the warning.\n`; } const sha = process.env.COMMIT_SHA.substring(0, 8); body += `\nCommit: ${sha} · Baseline: ${baseSrc} · Runner: self-hosted bench\n`; diff --git a/Makefile b/Makefile index 4c43f0ec7..5068065ad 100644 --- a/Makefile +++ b/Makefile @@ -327,7 +327,6 @@ ETHREX_REAL_BLOCK := 25453112 # there. Read the caveat in tooling/ethrex-fixtures/README.md before quoting # numbers: Amsterdam's gas model (EIP-8037 state gas, cold access 2600 -> 3000) # makes 10 of this block's Osaka-era transactions run out of gas. -ETHREX_REAL_BLOCK_FIXTURE_URL := ETHREX_REAL_BLOCK_FIXTURE_SHA256 := # The block's source cache: an ethrex-replay dump, fork-independent, still the one # hosted in bench-fixtures-v1. Only the fixture rebuild reads it; converter TESTS @@ -416,6 +415,10 @@ ethrex-real-block-cache: # scripts/bench_abba.sh, scripts/perf_diff.sh and # .github/workflows/benchmark-pr.yml read the fixture path from here instead of # hardcoding it, so repointing the block above moves every benchmark at once. +# One consumer is NOT wired through here: `REAL_BLOCK_FIXTURE` in +# tooling/ethrex-tests/tests/ethrex.rs is a const, because a glob would happily +# pick up a stale fixture from an earlier block and pass. Repointing the block +# means editing it too; it fails on the missing file, loudly. # `-s` on the caller's side keeps the output clean. print-real-block-fixture: @echo $(ETHREX_REAL_BLOCK_FIXTURE) diff --git a/scripts/bench_abba.sh b/scripts/bench_abba.sh index 8c1fe7d2d..c8fd107a8 100755 --- a/scripts/bench_abba.sh +++ b/scripts/bench_abba.sh @@ -169,14 +169,20 @@ INPUT="$(cd "$(dirname "$INPUT_REL")" && pwd)/$(basename "$INPUT_REL")" # improvement, in green, on both sides of the A/B. One cheap execution up front turns # that class of mistake -- stale fixture, wrong fork, fixture built against another # ethrex rev -- into a hard stop. The floor is far below any real block (the current -# one is ~20M cycles) and far above a rejected one. +# one is ~37M cycles) and far above a rejected one. +# +# Real workload only. The synthetic fixtures are regenerated by the same tool at the +# same rev and are gated twice already (`make check-ethrex-fixture-checksums` plus the +# `successful_validation == 1` assert in tooling/ethrex-tests), while a small TX_COUNT +# legitimately lands near this floor -- a 1-transfer block ran 1.80M cycles before the +# bump, an empty one 0.99M. Applying the floor there would reject valid runs. MIN_PLAUSIBLE_CYCLES=1000000 if [ ! -x ./target/release/cli ]; then cargo build --release -p cli >/dev/null fi workload_cycles="$(./target/release/cli execute "$ELF" --private-input "$INPUT" --cycles \ | awk '/^Cycles:/ {print $2}')" -if [ "${workload_cycles:-0}" -lt "$MIN_PLAUSIBLE_CYCLES" ]; then +if [ "$WORKLOAD" = "real" ] && [ "${workload_cycles:-0}" -lt "$MIN_PLAUSIBLE_CYCLES" ]; then echo "ERROR: the workload executed only ${workload_cycles:-0} cycles, below the" >&2 echo " ${MIN_PLAUSIBLE_CYCLES} floor. The guest almost certainly rejected the input:" >&2 echo " ELF $ELF" >&2 diff --git a/tooling/ethrex-tests/tests/eest_amsterdam_probe.rs b/tooling/ethrex-tests/tests/eest_amsterdam_probe.rs index 9bf6743f0..3bd4f593f 100644 --- a/tooling/ethrex-tests/tests/eest_amsterdam_probe.rs +++ b/tooling/ethrex-tests/tests/eest_amsterdam_probe.rs @@ -1,5 +1,7 @@ -//! Throwaway probe (untracked): do the upstream EEST Amsterdam benchmark -//! fixtures execute on the guest this branch pins? +//! Probe: do the upstream EEST Amsterdam benchmark fixtures execute on the +//! guest this rev pins? Committed as the measuring tool for adding an EEST +//! workload alongside the real-mix one; its inputs are not committed, so on a +//! plain checkout it skips (see below) and costs CI nothing. //! //! Inputs are `statelessInputBytes` lifted verbatim out of //! `tests-zkevm-benchmark@v0.8.2` blockchain tests, so nothing is rebuilt host diff --git a/tooling/ethrex-tests/tests/ethrex.rs b/tooling/ethrex-tests/tests/ethrex.rs index a28e9be77..97963b560 100644 --- a/tooling/ethrex-tests/tests/ethrex.rs +++ b/tooling/ethrex-tests/tests/ethrex.rs @@ -59,7 +59,9 @@ fn test_ethrex_empty_block() { const REAL_BLOCK_FIXTURE: &str = "ethrex_mainnet_25453112.bin"; /// Host-only acceptance gate for the real SSZ stateless fixture produced by -/// `tooling/ethrex-block-converter`. +/// `tooling/ethrex-fixtures --bin real_block`. The fixture name is pinned here +/// as well as in the Makefile, so repointing the block needs both edits; a +/// missed one fails loudly on the missing file rather than proving less. #[test] fn test_ethrex_real_block_native() { let inputs = std::fs::read(format!("{FIXTURES_DIR}/{REAL_BLOCK_FIXTURE}")).unwrap(); From 9ba052c6c03806edb1efa04ef5a5a9a103815e48 Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Wed, 9 Sep 2026 15:04:16 -0300 Subject: [PATCH 12/27] Make the real-block generator refuse to write a fixture that carries less work than the block it claims to reproduce. Two paths could shrink it silently, and neither was caught by any existing guard, because both leave a block that is perfectly valid: the guest still commits successful_validation = 1, so the native-validation check passes and the benchmark reports the missing work as an improvement. The first is a dropped transaction. build_payload_t8n returns the ones it could not apply and the generator only printed them; the transaction mix is the entire reason this fixture exists over a synthetic block, so applying fewer than all of them is now an error. Screening candidate blocks does need the partial ones reported rather than refused -- the twelve-block screen saw shares from 2/3 to 557/676 -- so REAL_BLOCK_ALLOW_DROPS=1 keeps that usable. The second is a witness leaf that does not yield a 32-byte trie key. collect_leaves skipped those without a counter, which loses an account or a storage slot; the transaction reading it then sees zero and reverts, and the only symptom is a revert count that is already non-zero and expected. It now returns how many it could not use and the caller aborts. The printed installed-versus-collected counts could never have caught this: both are derived from collect_leaves' output, so they agree by construction. Also record in the README that reverting and being dropped are different things, since the revert discussion there reads as if a lost transaction were normal. --- tooling/ethrex-fixtures/README.md | 12 ++++- tooling/ethrex-fixtures/src/bin/real_block.rs | 50 ++++++++++++++++--- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/tooling/ethrex-fixtures/README.md b/tooling/ethrex-fixtures/README.md index 5ce78b66a..64ac08064 100644 --- a/tooling/ethrex-fixtures/README.md +++ b/tooling/ethrex-fixtures/README.md @@ -69,7 +69,17 @@ block 25368371 it reverts 2 transactions where Amsterdam reverts 12. Every pre-Amsterdam block loses transactions this way. A screen of twelve real mainnet blocks (both of our release caches plus ethrex's curated zkevm_bench corpus) put the revert share between 26% and 50% with no exceptions, so it is not -a criterion for picking one. What the screen was for is weight: the retired +a criterion for picking one. + +Reverting is separate from being *dropped*: a reverted transaction was applied and +paid for its gas, while a dropped one never entered the block. This block applies +all 38 of its transactions, and the generator refuses to write a fixture that +applies fewer, because every other guard would still pass — a block with fewer +transactions is a valid block, so the loss would show up only as a smaller +benchmark. Screening candidate blocks does need the partial ones, so pass +`REAL_BLOCK_ALLOW_DROPS=1` for that; note also that the generator is not universal +(block 25087563 fails with `StateRootMismatch`), which is why screening is a +required step before pinning a different block. What the screen was for is weight: the retired fixture cost 30.50M cycles on today's guest, and this block rebuilds to 37.14M (+22%), the closest of the twelve — 25368371 comes in at -33% and the next candidate up, 25087308, at +197%. Several blocks end up consuming *more* gas than diff --git a/tooling/ethrex-fixtures/src/bin/real_block.rs b/tooling/ethrex-fixtures/src/bin/real_block.rs index b77d96d8a..175b260b1 100644 --- a/tooling/ethrex-fixtures/src/bin/real_block.rs +++ b/tooling/ethrex-fixtures/src/bin/real_block.rs @@ -196,23 +196,39 @@ fn build_stateless_input( /// Walk an embedded witness trie and return every `(32-byte key, value)` leaf it /// still holds. Pruned subtrees appear as `NodeRef::Hash` and are skipped: they /// are the proof siblings, which no transaction in the block reads. -fn collect_leaves(node: &Node, path: Nibbles, out: &mut Vec<(H256, Vec)>) { - let descend = |child: &NodeRef, path: Nibbles, out: &mut Vec<(H256, Vec)>| { +/// +/// Returns the number of leaves that could NOT be turned into a 32-byte key. In +/// a hashed trie there is no such thing, so a non-zero count means a malformed +/// witness node — and the caller must refuse to build on it. Dropping one +/// silently loses an account or a storage slot, the transaction that reads it +/// then sees zero and reverts, and the fixture ends up carrying less work than +/// the block it claims to reproduce with the revert count as its only symptom. +/// The installed-vs-collected counts cannot catch it: both are derived from this +/// function's output, so they agree by construction. +fn collect_leaves(node: &Node, path: Nibbles, out: &mut Vec<(H256, Vec)>) -> usize { + let descend = |child: &NodeRef, path: Nibbles, out: &mut Vec<(H256, Vec)>| -> usize { if let NodeRef::Node(child, _) = child { - collect_leaves(child, path, out); + collect_leaves(child, path, out) + } else { + 0 } }; match node { Node::Branch(branch) => { + let mut unusable = 0; for (i, child) in branch.choices.iter().enumerate() { - descend(child, path.append_new(i as u8), out); + unusable += descend(child, path.append_new(i as u8), out); } + unusable } Node::Extension(ext) => descend(&ext.child, path.concat(&ext.prefix), out), Node::Leaf(leaf) => { let bytes = path.concat(&leaf.partial).to_bytes(); if bytes.len() == 32 { out.push((H256::from_slice(&bytes), leaf.value.clone())); + 0 + } else { + 1 } } } @@ -283,8 +299,9 @@ async fn main() -> Result<(), Box> { genesis.timestamp = real_block.header.timestamp.saturating_sub(12); let mut accounts = Vec::new(); + let mut unusable_leaves = 0usize; if let Some(root) = &witness.state_trie_root { - collect_leaves(root, Nibbles::default(), &mut accounts); + unusable_leaves += collect_leaves(root, Nibbles::default(), &mut accounts); } let mut store = Store::new(".ethrex-real-block-tmp", EngineType::InMemory)?; @@ -312,7 +329,7 @@ async fn main() -> Result<(), Box> { .map_err(|e| format!("decode account {hashed_address:#x}: {e:?}"))?; if let Some(storage_root) = witness.storage_trie_roots.get(hashed_address) { let mut leaves = Vec::new(); - collect_leaves(storage_root, Nibbles::default(), &mut leaves); + unusable_leaves += collect_leaves(storage_root, Nibbles::default(), &mut leaves); let mut storage_trie = store.open_direct_storage_trie( *hashed_address, *ethrex_common::constants::EMPTY_TRIE_HASH, @@ -336,6 +353,14 @@ async fn main() -> Result<(), Box> { installed_accounts += 1; } let installed_root = state_trie.hash(&NativeCrypto)?; + if unusable_leaves > 0 { + return Err(format!( + "{unusable_leaves} witness leaf/leaves did not yield a 32-byte trie key, so \ + that much of the block's pre-state was never installed. The transactions \ + reading it would see zero and revert, shrinking the workload silently." + ) + .into()); + } // Build at the real height, not at #1. The seeded accounts hold the block's // own pre-state, but a contract that stores a block number and compares it @@ -442,6 +467,19 @@ async fn main() -> Result<(), Box> { let block = result.payload; let included = block.body.transactions.len(); + // The tx mix is the whole reason this fixture exists, so a builder that applied + // fewer than all of them has produced a smaller workload than the block it claims + // to reproduce -- and every guard below still passes, because a block with fewer + // transactions is a perfectly valid block. The screen sets the escape: surveying + // candidate blocks needs the partial ones reported, not refused. + if included != total_txs && std::env::var_os("REAL_BLOCK_ALLOW_DROPS").is_none() { + return Err(format!( + "the payload builder applied only {included} of {total_txs} transactions; \ + rejected: {rejected:?}. Set REAL_BLOCK_ALLOW_DROPS=1 to write the fixture \ + anyway." + ) + .into()); + } // --- 4. witness -> SSZ -> native validation ---------------------------- let witness = blockchain From f16e360e2b8d5946c1b32b1dfdfcc00f34925100 Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Wed, 9 Sep 2026 15:24:36 -0300 Subject: [PATCH 13/27] Drop the unused Address and U256 imports from the real-block generator. They arrived with the file and no CI job would have reported them: `make lint` only covers the root workspace, and tooling/ethrex-fixtures is a detached one, so the ethrex host tree it pulls stays out of the main build. Checked with cargo check --all-targets in the three detached crates the branch touches; this was the only warning in any of them. --- tooling/ethrex-fixtures/src/bin/real_block.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/ethrex-fixtures/src/bin/real_block.rs b/tooling/ethrex-fixtures/src/bin/real_block.rs index 175b260b1..ab6e637f8 100644 --- a/tooling/ethrex-fixtures/src/bin/real_block.rs +++ b/tooling/ethrex-fixtures/src/bin/real_block.rs @@ -34,7 +34,7 @@ use ethrex_common::types::stateless_ssz::{ STATELESS_INPUT_SCHEMA_ID, SszExecutionWitness, SszPublicKeys, SszStatelessInput, }; use ethrex_common::types::{AccountState, Block, ELASTICITY_MULTIPLIER, Genesis}; -use ethrex_common::{Address, H256, U256}; +use ethrex_common::H256; use ethrex_guest_program::crypto::{Crypto, NativeCrypto}; use ethrex_guest_program::l1::run_stateless_guest; use ethrex_rlp::decode::RLPDecode; From b0641ac83b88b52dd7eb6a83f6831e652d362d5a Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Wed, 9 Sep 2026 15:29:23 -0300 Subject: [PATCH 14/27] Pin the generated real-block fixture by sha256 and verify it where the fixture is used, not only where it is produced. The variable was already there and empty; filling it closes the one path by which a benchmark could measure the wrong workload without anything going red. Generating the fixture validates the block through the guest, but generation only runs when the file is MISSING -- deliberately, so that a benchmark does not pay for a cargo build -- and the bench runner is persistent. A fixture built before a rev bump therefore survives it, and every benchmark keeps proving the old workload. The minimum-cycle floor in bench_abba.sh cannot see it either: a stale fixture still runs tens of millions of cycles, and both sides of an A/B read the same file, so the delta looks healthy while the absolute numbers belong to a different block. The Makefile comment claimed a stale fixture could not reach a benchmark; that claim is now true rather than aspirational. Hashing 549 KB is free and only a mismatch pays for a rebuild, so the original reason for not re-checking on every invocation is preserved. On mismatch the fixture is regenerated and re-checked, which self-heals the stale case; if the fresh output still differs, the build stops and says that the inputs or the output format moved and the recorded numbers no longer describe the workload. An empty digest disables the check and announces it, which is the state right after repointing the block and before the new digest is known. Safe to pin because the generator is deterministic across machines, not just across runs: the same 549,144 bytes and the same 08a52e10 digest come out on macOS arm64 and on the Linux x86-64 bench runner, from the same installed state root 0xce4c19cd. --- Makefile | 53 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 5068065ad..52205dd40 100644 --- a/Makefile +++ b/Makefile @@ -322,12 +322,24 @@ ETHREX_REAL_BLOCK := 25453112 # runs 496 cycles and commits `successful_validation = 0` instead of failing. # `tooling/ethrex-fixtures --bin real_block` rebuilds the block's real # transactions and real pre-state as an Amsterdam block and validates the result -# through the guest before writing it, so a stale or wrong-fork fixture cannot -# reach a benchmark. Nothing to publish, and it works offline once the cache is -# there. Read the caveat in tooling/ethrex-fixtures/README.md before quoting -# numbers: Amsterdam's gas model (EIP-8037 state gas, cold access 2600 -> 3000) -# makes 10 of this block's Osaka-era transactions run out of gas. -ETHREX_REAL_BLOCK_FIXTURE_SHA256 := +# through the guest before writing it. Nothing to publish, and it works offline +# once the cache is there. Read the caveat in tooling/ethrex-fixtures/README.md +# before quoting numbers: Amsterdam's gas model (EIP-8037 state gas, cold access +# 2600 -> 3000) makes 10 of this block's Osaka-era transactions run out of gas. +# +# The digest below is what keeps a stale fixture out of a benchmark. Generating +# validates the block, but generation only happens when the file is MISSING (see +# the rule), and the bench runner is persistent -- so without this, a fixture +# built before a rev bump survives it and every benchmark keeps proving the old +# workload. The minimum-cycle floor in bench_abba.sh cannot see that either: a +# stale fixture still runs tens of millions of cycles, and both sides of an A/B +# read the same file, so the delta looks healthy while the absolute numbers +# belong to the wrong block. Verified byte-identical on macOS arm64 and on the +# Linux x86-64 runner, which is what makes pinning it safe. Repointing the block +# or moving the ethrex rev changes it: run `make regen-real-block-fixture`, take +# the new digest, and paste it here. Leaving it empty disables the check and says +# so out loud. +ETHREX_REAL_BLOCK_FIXTURE_SHA256 := 08a52e10c2f89870dade14f7b0dfe01625e57416afbff664ad31fcd067233eea # The block's source cache: an ethrex-replay dump, fork-independent, still the one # hosted in bench-fixtures-v1. Only the fixture rebuild reads it; converter TESTS # use a different, upstream-pinned cache (below). @@ -395,10 +407,33 @@ endef # A file rule, unlike the fetched artifacts above: those are phony so their digest # is re-checked on every invocation, which is how a stale download is caught. This # one is BUILT from the cache (whose digest IS re-checked) by a deterministic -# generator that validates the block through the guest before writing, so the file -# existing is proof enough and rebuilding it on every benchmark would just add a -# cargo build to the critical path. +# generator, so its digest is re-checked here the same way -- hashing 549 KB costs +# nothing, and only a MISMATCH pays for a rebuild, which is what keeps a cargo +# build off every benchmark's critical path. ethrex-real-block-fixture: $(ETHREX_REAL_BLOCK_FIXTURE) + @set -e; \ + want="$(ETHREX_REAL_BLOCK_FIXTURE_SHA256)"; \ + if [ -z "$$want" ]; then \ + echo "::warning::ETHREX_REAL_BLOCK_FIXTURE_SHA256 is unset - the fixture is not being verified."; \ + exit 0; \ + fi; \ + if command -v sha256sum >/dev/null 2>&1; then shacmd="sha256sum"; \ + elif command -v shasum >/dev/null 2>&1; then shacmd="shasum -a 256"; \ + else echo "fixture: missing sha256sum or shasum for checksum verification" >&2; exit 1; fi; \ + sha_of() { $$shacmd "$$1" | awk '{print $$1}'; }; \ + if [ "$$(sha_of "$(ETHREX_REAL_BLOCK_FIXTURE)")" = "$$want" ]; then exit 0; fi; \ + echo "fixture $(ETHREX_REAL_BLOCK_FIXTURE) does not match $$want - regenerating."; \ + $(MAKE) regen-real-block-fixture; \ + got="$$(sha_of "$(ETHREX_REAL_BLOCK_FIXTURE)")"; \ + if [ "$$got" != "$$want" ]; then \ + echo "ERROR: the freshly generated fixture is $$got, not the pinned $$want." >&2; \ + echo " The generator is deterministic, so this means its inputs or its output" >&2; \ + echo " format moved: the ethrex rev, the cache, or the generator itself. If that" >&2; \ + echo " was intended, re-baseline ETHREX_REAL_BLOCK_FIXTURE_SHA256 in the Makefile" >&2; \ + echo " and re-measure -- the workload is no longer the one every recorded number" >&2; \ + echo " was taken against." >&2; \ + exit 1; \ + fi # No prerequisites on purpose. `ethrex-real-block-cache` is phony (so its digest is # re-checked on every call), and a phony prerequisite always reads as newer than its From d1bf454405bc79d223cde433f3223e49754a5c94 Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Wed, 9 Sep 2026 16:11:44 -0300 Subject: [PATCH 15/27] Re-baseline the rebuilt workload over 14 proves and correct what the benchmark harness publishes about its own resolving power. The five-prove figure it was pinned to, CV 0.57%, came with a 95% interval of [0.34%, 1.64%]; the fuller sample lands at 1.26%, near the top of that interval, so the pair table was promising roughly twice the resolution it can deliver -- 12 pairs resolve ~1.0%, not ~0.51%. The reason is not prover jitter. On this runner wall time and the share of CPU a prove actually gets correlate at -0.98 across a ten-prove sweep, perfectly monotonic: 7583% of a CPU takes 123.98 s and 7318% takes 129.61 s. Two proves that overlapped another job on the box got 4769% and 4687% and took 215.58 s and 220.03 s, +73%, while producing byte-identical proof bundles -- nothing but the clock moved. Gated to the proves that got the most CPU the same sweep has a CV of 0.34%, which is better than what was published. So the number was never wrong so much as conditional on something nobody recorded. bench_abba.sh now records the CPU share of every prove and prints an exclusivity line that flags any prove under 90% of the batch's best. The threshold is relative because the script also runs on rented 16-32 core GPU hosts where an absolute percentage means nothing, and it flags rather than discards because the ABBA pairing already absorbs most of the effect -- what a flag means is that the batch's spread must not be read as prover noise. The box is shared and everyone logs in as the same account, so contention cannot be attributed after the fact; it has to be recorded while measuring. The pair table now carries both conditions instead of bracketing an unknown, and the peak RSS figure moves from 43.00 GiB to the measured maximum of 44.81 GiB. That last one tightens an argument rather than weakening it: 47.0 GB is 98% of a 48 GB box, so the GPU arm's floor was already past the point of no headroom, not near it. --- .github/workflows/bench-abba.yml | 37 ++++++++++++---------- .github/workflows/benchmark-gpu.yml | 6 ++-- .github/workflows/benchmark-pr.yml | 2 +- scripts/bench_abba.sh | 49 +++++++++++++++++++++++++++-- tooling/ethrex-fixtures/README.md | 18 +++++++---- 5 files changed, 83 insertions(+), 29 deletions(-) diff --git a/.github/workflows/bench-abba.yml b/.github/workflows/bench-abba.yml index c691fd5b2..13db6f37c 100644 --- a/.github/workflows/bench-abba.yml +++ b/.github/workflows/bench-abba.yml @@ -9,24 +9,29 @@ name: Bench ABBA tiebreaker # Syntax: "/bench-abba [N]", where N is the pair count (default 12). # # Resolution, from the paired t-test (resolvable 95% delta = t* x sd / sqrt(N)). The -# pair-delta sd on this runner is NOT yet measured; the two columns bracket it between -# 0.6% and 0.81% (sqrt(2) x this runner's measured 0.57% single-run CV, from the -# 5-prove baseline in tooling/ethrex-fixtures/README.md): +# two columns are not a guess bracketing an unknown: they are the same runner under +# two conditions, because its variance is contention and not prover jitter. Over a +# 14-prove baseline (tooling/ethrex-fixtures/README.md) wall time and the share of CPU +# a prove actually got correlate at -0.98, perfectly monotonic, so the single-run CV is +# 0.34% across the proves that got the most CPU and 1.26% across all of them. sd of a +# pair delta is sqrt(2) x that: # -# pairs wall resolves (sd 0.81% / sd 0.6%) -# 8 ~41 min 0.67% / 0.50% -# 12 ~58 min 0.51% / 0.38% <- default -# 20 ~1h31m 0.38% / 0.28% -# 32 ~2h22m 0.29% / 0.22% +# pairs wall resolves (quiet box, sd 0.49% / shared, sd 1.78%) +# 8 ~41 min 0.34% / 1.24% +# 12 ~58 min 0.28% / 1.01% <- default +# 20 ~1h31m 0.21% / 0.78% +# 32 ~2h22m 0.16% / 0.62% # -# Wall is 2 x 125.17 s per pair (the runner's measured prove at epoch 2^22) plus ~8 min -# of setup. The FIRST run measures the sd — it is the `sd` field of the paired-t line -# in the result comment — and this table should be re-pinned to it then. +# Wall is 2 x ~125 s per pair (the runner's measured prove at epoch 2^22) plus ~8 min of +# setup. Read the column the run earned, not the one you want: the script prints an +# exclusivity line reporting the CPU share of every prove and flagging any that came in +# under 90% of the batch's best. A flagged batch belongs in the right-hand column. # -# Both columns tightened when the workload was rebuilt for ethrex 25: the single-run -# CV fell from 1.43% to 0.57%, so 12 pairs now resolve ~0.5% where they used to resolve -# ~1.3%. Numbers from before that bump measured a different fixture (block 25368371 as -# an rkyv ProgramInput) and are not comparable. +# The rebuild for ethrex 25 did tighten this, but not as far as first recorded: a +# 5-prove baseline put the CV at 0.57%, and its 95% interval was [0.34%, 1.64%] — wide +# enough that the point estimate was not worth pinning a table to, which is what the +# 14-prove follow-up then showed. Numbers from before the bump measured a different +# fixture (block 25368371 as an rkyv ProgramInput) and are not comparable. on: issue_comment: types: [created] @@ -34,7 +39,7 @@ on: env: # The epoch /bench proves this block at, so the tiebreaker resolves the same # configuration and not just the same block. Memory picks it: this runner peaks at - # 43.00 GiB here against its >=64 GiB floor, and 2^23 would take that past 50 GiB + # 44.81 GiB here against its >=64 GiB floor, and 2^23 would take that past 50 GiB # for ~7% of wall. See benchmark-pr.yml's REAL_BLOCK_EPOCH_LOG2. ABBA_REAL_EPOCH_LOG2: "22" diff --git a/.github/workflows/benchmark-gpu.yml b/.github/workflows/benchmark-gpu.yml index 2fec522bb..9961d0411 100644 --- a/.github/workflows/benchmark-gpu.yml +++ b/.github/workflows/benchmark-gpu.yml @@ -230,9 +230,9 @@ jobs: MIN_DRIVER: "580" run: | # cpu_ram filter is in GB. Floor 64 GB, raised from 48 with the ethrex 25 - # workload: that block peaks at 43.00 GiB (46.2 GB) at epoch 2^22 on the CPU - # path, which on a 48 GB box is 96% of RAM and an OOM waiting for a rental - # with a slightly hungrier allocator. 64 GB restores the ~28% headroom the + # workload: that block peaks at 44.81 GiB (47.0 GB) at epoch 2^22 on the CPU + # path, which on a 48 GB box is 98% of RAM -- not headroom to lose to a + # rental with a slightly hungrier allocator, it is already over. 64 GB restores the ~28% headroom the # 48 GB floor had for the old block (~36 GB host RSS on the CUDA path, # measured at main vintage) and matches the CPU runner's own floor. It costs # offer availability: fewer boxes qualify, so PRICE_CAP may need a look if diff --git a/.github/workflows/benchmark-pr.yml b/.github/workflows/benchmark-pr.yml index 78e359c91..462bdaf6a 100644 --- a/.github/workflows/benchmark-pr.yml +++ b/.github/workflows/benchmark-pr.yml @@ -99,7 +99,7 @@ env: # predating that fix fails at write time rather than mismeasuring. # # Epoch 2^22, measured on THIS runner on 2026-09-08 (box idle): mean 125.17 s over - # 5 proves, sd 0.71 s, CV 0.57%, 43.00 GiB peak RSS, 790 MB bundle; verifying one + # 14 proves, sd 1.58 s, CV 1.26%, 44.81 GiB peak RSS, 790 MB bundle; verifying one # bundle is 12.0 s. # # Memory, not speed, is what picks 2^22. The epoch trade-off was swept on the diff --git a/scripts/bench_abba.sh b/scripts/bench_abba.sh index c8fd107a8..2966c1c79 100755 --- a/scripts/bench_abba.sh +++ b/scripts/bench_abba.sh @@ -242,11 +242,31 @@ else fi # --- 3. Interleaved A/B/B/A measurement (fresh CSV -- pre-committed batch) --- +# Every prove also records the share of CPU it actually got. On a shared box that +# is the difference between a number and a coincidence: measured on the bench +# runner, wall time and CPU share correlate at -0.98 across a ten-prove sweep, +# perfectly monotonic, and a colleague's job landing mid-sweep cost 47 of 75 cores +# and +73% of wall. So the spread this script reports is a property of how +# exclusive the box was, not of the prover -- gated to the runs that got the most +# CPU, the same sweep's CV falls from 1.54% to 0.34%. The ABBA pairing cancels most +# of it (both sides meet the same neighbours), which is why this flags rather than +# discards; a flagged batch is one whose spread should not be read as prover noise. +CPU_SHARES="$WORK/cpu_shares.txt"; : > "$CPU_SHARES" +TIME_BIN="" +if /usr/bin/time -f %P true >/dev/null 2>&1; then TIME_BIN=/usr/bin/time; fi + run_prove() { # $1=binary -> echoes proving time (s) - local out t + local out t share tf + tf="$(mktemp)" # shellcheck disable=SC2086 # CONT_ARGS is intentionally word-split (0 or 2 args) - out="$("$1" prove "$ELF" --private-input "$INPUT" -o "$PROOF" --time $CONT_ARGS 2>&1)" - rm -f "$PROOF" + if [ -n "$TIME_BIN" ]; then + out="$($TIME_BIN -f '%P' -o "$tf" "$1" prove "$ELF" --private-input "$INPUT" -o "$PROOF" --time $CONT_ARGS 2>&1)" + else + out="$("$1" prove "$ELF" --private-input "$INPUT" -o "$PROOF" --time $CONT_ARGS 2>&1)" + fi + share="$(tr -d '%' < "$tf" | tr -d '[:space:]')" + rm -f "$tf" "$PROOF" + case "$share" in ''|*[!0-9]*) : ;; *) echo "$share" >> "$CPU_SHARES" ;; esac t="$(printf '%s\n' "$out" | grep -o 'Proving time: [0-9.]*' | awk '{print $3}')" if [ -z "$t" ]; then echo "ERROR: could not parse 'Proving time' from cli output:" >&2 @@ -269,6 +289,29 @@ for i in $(seq 1 "$N_PAIRS"); do "$i" "$N_PAIRS" "$a" "$b" "$(awk "BEGIN{print ($a-$b)/$b*100}")" done +# Exclusivity report. Self-calibrating: the best prove of this batch defines what +# the box can give, so a run well under it met a neighbour. No box-specific +# constant, which matters because this script also runs on rented 16-32 core GPU +# hosts where an absolute percentage means nothing. +if [ -s "$CPU_SHARES" ]; then + awk ' + { n++; s[n]=$1; if ($1>mx) mx=$1; if (mn==0 || $1 Exclusivity: CPU share %d%%-%d%% of %d proves", mn, mx, n + if (bad) { + printf ", %d below 90%% of the best\n", bad + printf " Something else was on the box. The pairing absorbs most of it, but do\n" + printf " not read this batch spread as prover noise, and re-run on a quiet box\n" + printf " before quoting a resolvable delta.\n" + } else { + printf ", all within 10%% of the best\n" + } + }' "$CPU_SHARES" +fi + # --- 4. Paired t-test + robust median/Wilcoxon --- python3 - "$WORK/pairs.csv" <<'PY' import sys, csv, math diff --git a/tooling/ethrex-fixtures/README.md b/tooling/ethrex-fixtures/README.md index 64ac08064..036ff1961 100644 --- a/tooling/ethrex-fixtures/README.md +++ b/tooling/ethrex-fixtures/README.md @@ -98,12 +98,13 @@ calls, 164 ECSM calls. Fixture: 549,144 bytes. ### Choosing the epoch size, and what the workload costs Measured on the bench runner (`vm-benchmarks-1`, which is also the CI -self-hosted `bench` runner: 96 cores / 125 GB, idle) on 2026-09-08, with the -guest ELF at ethrex `2cb18b0b`: +self-hosted `bench` runner: 96 cores / 125 GB) with the guest ELF at ethrex +`2cb18b0b`, over 14 proves across two sittings: | | mean | sd | CV | peak RSS | proof | verify | | --- | ---: | ---: | ---: | ---: | ---: | ---: | -| epoch 2^22, 5 proves | **125.17 s** | 0.71 s | 0.57 % | 43.00 GiB | 790 MB | 12.0 s | +| epoch 2^22, 14 proves | **125.33 s** | 1.58 s | 1.26 % | 44.8 GiB | 790 MB | 12.0 s | +| of those, the 5 that got the most CPU | 124.47 s | 0.43 s | 0.34 % | | | | 2^22 is what `/bench`, `/bench-abba` and the GPU bench pin. The epoch trade-off itself was swept on the previous block (2^21 costs +13.7 % of wall to save @@ -112,9 +113,14 @@ than by the block, so that shape carries over even though the seconds do not. 2^23 would take this workload past 50 GiB against the runner's 64 GiB floor, which is why memory and not speed picks the default. -With that sd, a two-sided 95 % comparison resolves ~2.00 % at three runs per -side, ~1.00 % at five and ~0.58 % at ten — which is why a sub-2 % claim needs -`/bench N` or the ABBA tiebreaker rather than a re-read of a three-run table. +The two rows are the same binary on the same block; what separates them is how +much of the shared box each prove got. Wall time here is a function of CPU share, +not of the prover, so quote a spread only together with the condition it was +measured under — `scripts/bench_abba.sh` records the CPU share of every prove and +flags a contended batch, and its comments carry the measurement. A two-sided 95 % +comparison resolves ~0.6 % at three runs per side on a quiet box and ~2.0 % on a +busy one, which is why a sub-2 % claim needs `/bench N` or the ABBA tiebreaker +rather than a re-read of a three-run table. Continuations are not optional here: monolithic proving costs ~4.9 GB of peak heap per million cycles on this family, so 37.14M cycles would need ~182 GB. From e78c0032d3cd4db446249fdd20dabe9ccbeff35d Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Thu, 10 Sep 2026 12:23:02 -0300 Subject: [PATCH 16/27] Pin ethrex 26.0.0 and fix the fixture regen --- .github/workflows/bench-abba.yml | 2 +- .github/workflows/benchmark-gpu.yml | 2 +- .github/workflows/benchmark-pr.yml | 4 +- .github/workflows/ethrex-block-converter.yml | 2 +- Makefile | 2 +- crypto/ethrex-crypto/Cargo.lock | 4 +- crypto/ethrex-crypto/Cargo.toml | 2 +- executor/programs/rust/ethrex/Cargo.lock | 32 +++++----- executor/programs/rust/ethrex/Cargo.toml | 2 +- executor/tests/README.md | 6 +- tooling/ethrex-block-converter/Cargo.lock | 52 ++++++++-------- tooling/ethrex-block-converter/Cargo.toml | 10 ++-- tooling/ethrex-block-converter/README.md | 6 +- tooling/ethrex-block-converter/src/main.rs | 2 +- tooling/ethrex-fixtures/Cargo.lock | 60 +++++++++---------- tooling/ethrex-fixtures/Cargo.toml | 20 ++++--- tooling/ethrex-fixtures/README.md | 5 +- tooling/ethrex-fixtures/src/bin/real_block.rs | 2 +- tooling/ethrex-tests/Cargo.lock | 32 +++++----- tooling/ethrex-tests/Cargo.toml | 2 +- 20 files changed, 129 insertions(+), 120 deletions(-) diff --git a/.github/workflows/bench-abba.yml b/.github/workflows/bench-abba.yml index 13db6f37c..ede04cadc 100644 --- a/.github/workflows/bench-abba.yml +++ b/.github/workflows/bench-abba.yml @@ -27,7 +27,7 @@ name: Bench ABBA tiebreaker # exclusivity line reporting the CPU share of every prove and flagging any that came in # under 90% of the batch's best. A flagged batch belongs in the right-hand column. # -# The rebuild for ethrex 25 did tighten this, but not as far as first recorded: a +# The workload rebuild did tighten this, but not as far as first recorded: a # 5-prove baseline put the CV at 0.57%, and its 95% interval was [0.34%, 1.64%] — wide # enough that the point estimate was not worth pinning a table to, which is what the # 14-prove follow-up then showed. Numbers from before the bump measured a different diff --git a/.github/workflows/benchmark-gpu.yml b/.github/workflows/benchmark-gpu.yml index 9961d0411..1f715d45a 100644 --- a/.github/workflows/benchmark-gpu.yml +++ b/.github/workflows/benchmark-gpu.yml @@ -229,7 +229,7 @@ jobs: # because vast can't numerically compare the driver_version string server-side. MIN_DRIVER: "580" run: | - # cpu_ram filter is in GB. Floor 64 GB, raised from 48 with the ethrex 25 + # cpu_ram filter is in GB. Floor 64 GB, raised from 48 with the rebuilt # workload: that block peaks at 44.81 GiB (47.0 GB) at epoch 2^22 on the CPU # path, which on a 48 GB box is 98% of RAM -- not headroom to lose to a # rental with a slightly hungrier allocator, it is already over. 64 GB restores the ~28% headroom the diff --git a/.github/workflows/benchmark-pr.yml b/.github/workflows/benchmark-pr.yml index 58634404d..4d8152dd6 100644 --- a/.github/workflows/benchmark-pr.yml +++ b/.github/workflows/benchmark-pr.yml @@ -113,7 +113,7 @@ env: # # The pre-bump numbers this replaces (block 25368371, 50.78M cycles, 158.8 s at # 2^22 on this runner, ~52 GB) measured a DIFFERENT workload: that fixture was an - # rkyv ProgramInput, which ethrex 25's guest rejects. Do not compare across the + # rkyv ProgramInput, which the pinned guest rejects. Do not compare across the # bump. This block was picked to land as close to it as an Amsterdam rebuild can — # see the screen in the Makefile's ETHREX_REAL_BLOCK comment. # @@ -315,7 +315,7 @@ jobs: if: steps.config.outputs.run_real == 'true' run: | # Gitignored and never in the checkout, like the synthetic fixtures above. - # Built, not downloaded: ethrex 25's guest decodes only the Amsterdam schema, + # Built, not downloaded: the pinned guest decodes only the Amsterdam schema, # so a hosted artifact for this pre-Amsterdam block cannot be valid — it would # run ~500 cycles and commit `successful_validation = 0` without failing. The # generator rebuilds the block's real transactions on its real pre-state and diff --git a/.github/workflows/ethrex-block-converter.yml b/.github/workflows/ethrex-block-converter.yml index f8ca2a2ff..8a97a335d 100644 --- a/.github/workflows/ethrex-block-converter.yml +++ b/.github/workflows/ethrex-block-converter.yml @@ -86,7 +86,7 @@ jobs: tooling/ethrex-fixtures -> target tooling/ethrex-tests -> target - # Built from the block's replay cache, not downloaded: ethrex 25's guest decodes + # Built from the block's replay cache, not downloaded: the pinned guest decodes # only the Amsterdam schema, so no hosted artifact for a pre-Amsterdam block can # be valid. The generator validates the block through the guest before writing, # which is what the screen below then re-checks against the ELF. The guard covers diff --git a/Makefile b/Makefile index 48509816f..2dfc12277 100644 --- a/Makefile +++ b/Makefile @@ -316,7 +316,7 @@ ETHREX_REAL_BLOCK := 25453112 # not a selection criterion — every pre-Amsterdam block loses 26-50% of its # transactions to the fork, which is a property of the fork and not of the block. # -# The fixture is GENERATED from the cache below, not fetched. ethrex 25's guest +# The fixture is GENERATED from the cache below, not fetched. The pinned guest # decodes only the Amsterdam schema (`0x1501`) and mainnet has no Amsterdam fork, # so no hosted artifact for this block can be valid: the release's rkyv one now # runs 496 cycles and commits `successful_validation = 0` instead of failing. diff --git a/crypto/ethrex-crypto/Cargo.lock b/crypto/ethrex-crypto/Cargo.lock index 67a36c419..44c941387 100644 --- a/crypto/ethrex-crypto/Cargo.lock +++ b/crypto/ethrex-crypto/Cargo.lock @@ -342,8 +342,8 @@ dependencies = [ [[package]] name = "ethrex-crypto" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "ark-bn254", "ark-ec", diff --git a/crypto/ethrex-crypto/Cargo.toml b/crypto/ethrex-crypto/Cargo.toml index 774dd2cf4..a3377a32e 100644 --- a/crypto/ethrex-crypto/Cargo.toml +++ b/crypto/ethrex-crypto/Cargo.toml @@ -19,7 +19,7 @@ license = "MIT OR Apache-2.0" # + `default-features = false` as the guest's ethrex-crypto, so feature # unification adds nothing to the guest build (no C secp256k1 / malachite / kzg). # `std` is on anyway, via ethrex-trie's default. -ethrex-crypto = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-crypto", default-features = false } +ethrex-crypto = { git = "https://github.com/lambdaclass/ethrex.git", rev = "8effcb0671c5d0b12fe0161ea37c174ec4466b6a", package = "ethrex-crypto", default-features = false } # Pinned to the exact 0.13.4 ethrex uses so the guest resolves a single k256 # (a version split would make `FieldElement`/`Scalar` incompatible types). # `expose-field` is required by the x-only reconstruction. diff --git a/executor/programs/rust/ethrex/Cargo.lock b/executor/programs/rust/ethrex/Cargo.lock index 14f929569..441cd6fde 100644 --- a/executor/programs/rust/ethrex/Cargo.lock +++ b/executor/programs/rust/ethrex/Cargo.lock @@ -667,8 +667,8 @@ dependencies = [ [[package]] name = "ethrex-common" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "crc32fast", @@ -699,8 +699,8 @@ dependencies = [ [[package]] name = "ethrex-crypto" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "ark-bn254", "ark-ec", @@ -719,8 +719,8 @@ dependencies = [ [[package]] name = "ethrex-guest-program" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "ethereum-types", @@ -742,8 +742,8 @@ dependencies = [ [[package]] name = "ethrex-l2-common" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "ethereum-types", @@ -760,8 +760,8 @@ dependencies = [ [[package]] name = "ethrex-levm" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "derive_more", @@ -778,8 +778,8 @@ dependencies = [ [[package]] name = "ethrex-rlp" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "ethereum-types", @@ -788,8 +788,8 @@ dependencies = [ [[package]] name = "ethrex-trie" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "anyhow", "bytes", @@ -808,8 +808,8 @@ dependencies = [ [[package]] name = "ethrex-vm" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "derive_more", diff --git a/executor/programs/rust/ethrex/Cargo.toml b/executor/programs/rust/ethrex/Cargo.toml index 65930a94f..06cae1ae2 100644 --- a/executor/programs/rust/ethrex/Cargo.toml +++ b/executor/programs/rust/ethrex/Cargo.toml @@ -55,7 +55,7 @@ lambda-vm-ethrex-crypto = { path = "../../../../crypto/ethrex-crypto" } # # KZG is not linked, so the point-evaluation precompile (0x0a) is unsupported — # see src/main.rs. -ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-guest-program", default-features = false } +ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "8effcb0671c5d0b12fe0161ea37c174ec4466b6a", package = "ethrex-guest-program", default-features = false } # `lambda-vm-syscalls` can still reach the graph as a git dep pinned to a commit # of THIS repo, which upstream bumps on its own schedule. Override it # with the working-tree copy so the guest always links the same syscalls as the rest diff --git a/executor/tests/README.md b/executor/tests/README.md index ed5e0148c..12309f39a 100644 --- a/executor/tests/README.md +++ b/executor/tests/README.md @@ -10,14 +10,14 @@ commit: ```text https://github.com/lambdaclass/ethrex.git -2cb18b0b95b27a2555d3debffdebc43c9685d6e3 +8effcb0671c5d0b12fe0161ea37c174ec4466b6a ``` Five manifests carry that pin, not one. `scripts/set_ethrex_rev.sh --show` prints it and fails if they ever disagree. The generator enables Amsterdam in its synthetic genesis and includes the two -EIP-8282 request predeploys required by ethrex 25. +EIP-8282 request predeploys the pinned ethrex requires. ### Generation @@ -75,7 +75,7 @@ ethrex_bench_4.bin The blocks above are synthetic. For a representative workload — real contract execution, real trie depth, real bytecode — `make ethrex-real-block-fixture` BUILDS `ethrex_mainnet_25453112.bin` from the block's replay cache, which is the -only fetched artifact; nothing about the fixture is published, because ethrex 25's +only fetched artifact; nothing about the fixture is published, because the pinned guest decodes only the Amsterdam schema and no hosted artifact for a pre-Amsterdam block can be valid. It is gitignored rather than committed, so its digest lives next to the block pin in the Makefile rather than in the table above, and it is diff --git a/tooling/ethrex-block-converter/Cargo.lock b/tooling/ethrex-block-converter/Cargo.lock index ffa6e7224..057713c5c 100644 --- a/tooling/ethrex-block-converter/Cargo.lock +++ b/tooling/ethrex-block-converter/Cargo.lock @@ -866,8 +866,8 @@ dependencies = [ [[package]] name = "ethrex-blockchain" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "crossbeam", @@ -891,8 +891,8 @@ dependencies = [ [[package]] name = "ethrex-common" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "crc32fast", @@ -924,8 +924,8 @@ dependencies = [ [[package]] name = "ethrex-config" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "ethrex-common", "ethrex-p2p", @@ -936,8 +936,8 @@ dependencies = [ [[package]] name = "ethrex-crypto" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "ark-bn254", "ark-ec", @@ -958,8 +958,8 @@ dependencies = [ [[package]] name = "ethrex-guest-program" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "ethereum-types", @@ -981,8 +981,8 @@ dependencies = [ [[package]] name = "ethrex-l2-common" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "ethereum-types", @@ -999,8 +999,8 @@ dependencies = [ [[package]] name = "ethrex-levm" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "derive_more", @@ -1017,8 +1017,8 @@ dependencies = [ [[package]] name = "ethrex-metrics" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "ethrex-common", "serde", @@ -1029,8 +1029,8 @@ dependencies = [ [[package]] name = "ethrex-p2p" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "aes", "aes-gcm", @@ -1071,8 +1071,8 @@ dependencies = [ [[package]] name = "ethrex-rlp" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "ethereum-types", @@ -1081,8 +1081,8 @@ dependencies = [ [[package]] name = "ethrex-storage" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "anyhow", "bytes", @@ -1103,8 +1103,8 @@ dependencies = [ [[package]] name = "ethrex-trie" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "anyhow", "bytes", @@ -1123,8 +1123,8 @@ dependencies = [ [[package]] name = "ethrex-vm" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "derive_more", diff --git a/tooling/ethrex-block-converter/Cargo.toml b/tooling/ethrex-block-converter/Cargo.toml index 4c546d28a..04304554e 100644 --- a/tooling/ethrex-block-converter/Cargo.toml +++ b/tooling/ethrex-block-converter/Cargo.toml @@ -9,14 +9,14 @@ edition = "2024" [dependencies] # Pinned to the SAME ethrex rev as the guest so the generated SSZ input matches # what the stateless guest deserializes. -ethrex-common = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-common", default-features = false, features = ["secp256k1"] } -ethrex-config = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-config", default-features = false } +ethrex-common = { git = "https://github.com/lambdaclass/ethrex.git", rev = "8effcb0671c5d0b12fe0161ea37c174ec4466b6a", package = "ethrex-common", default-features = false, features = ["secp256k1"] } +ethrex-config = { git = "https://github.com/lambdaclass/ethrex.git", rev = "8effcb0671c5d0b12fe0161ea37c174ec4466b6a", package = "ethrex-config", default-features = false } # Provides `NativeCrypto`, the host `Crypto` implementation used to recover # transaction keys, hash the BAL, and validate the generated stateless input. # Already in the graph via `ethrex-config` -> `ethrex-p2p`, so this adds no # compilation; the line exists because the bin build needs its own. -ethrex-crypto = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-crypto", default-features = false, features = ["secp256k1"] } -ethrex-rlp = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-rlp" } +ethrex-crypto = { git = "https://github.com/lambdaclass/ethrex.git", rev = "8effcb0671c5d0b12fe0161ea37c174ec4466b6a", package = "ethrex-crypto", default-features = false, features = ["secp256k1"] } +ethrex-rlp = { git = "https://github.com/lambdaclass/ethrex.git", rev = "8effcb0671c5d0b12fe0161ea37c174ec4466b6a", package = "ethrex-rlp" } # Matches the guest's own declaration (executor/programs/rust/ethrex/Cargo.toml). # # CAVEAT: this line alone does NOT reproduce the guest's precompile surface. The @@ -27,7 +27,7 @@ ethrex-rlp = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0 # verify with `cargo tree -e features -i ethrex-crypto`. c-kzg is the whole gap now — # malachite modexp and `ark-ff/asm` are on both sides since ethrex-trie's `std` default. # Closing it means dropping `ethrex-config` and sourcing `ChainConfig` another way. -ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-guest-program", default-features = false } +ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "8effcb0671c5d0b12fe0161ea37c174ec4466b6a", package = "ethrex-guest-program", default-features = false } # Exact pin: keep the SSZ implementation in sync with the guest's ethrex rev. libssz = "=0.3.0" diff --git a/tooling/ethrex-block-converter/README.md b/tooling/ethrex-block-converter/README.md index 2b29b837f..2f6d766a2 100644 --- a/tooling/ethrex-block-converter/README.md +++ b/tooling/ethrex-block-converter/README.md @@ -14,7 +14,7 @@ cargo run --release -- ``` The output starts with the two-byte big-endian schema ID `0x1501`. The current -ethrex 25 stateless schema validates one Amsterdam block at a time. A replay +pinned stateless schema validates one Amsterdam block at a time. A replay cache must therefore contain one block, its `slot_number`, its `block_access_list_hash`, and the raw BAL when the BAL is non-empty. Caches created before Amsterdam are rejected rather than silently rewriting their @@ -30,7 +30,7 @@ The converter, fixture generator, guest, and host tests all pin: ```text https://github.com/lambdaclass/ethrex.git -2cb18b0b95b27a2555d3debffdebc43c9685d6e3 +8effcb0671c5d0b12fe0161ea37c174ec4466b6a ``` Keep these pins together. The SSZ wire format and the guest implementation are @@ -54,7 +54,7 @@ sha256sum "$(make -s print-real-block-fixture)" Publish the resulting SSZ artifact and matching Amsterdam cache, then update their URLs and checksums in the Makefile before enabling the real-block benchmark. The old release assets are rkyv `ProgramInput` artifacts and are -incompatible with ethrex 25. +incompatible with the pinned ethrex. The converter's tests use the Hoodi cache under `caches/` and verify that an unmapped network is rejected. The checked release cache predates Amsterdam, so diff --git a/tooling/ethrex-block-converter/src/main.rs b/tooling/ethrex-block-converter/src/main.rs index a3809c806..7f5bdfd0f 100644 --- a/tooling/ethrex-block-converter/src/main.rs +++ b/tooling/ethrex-block-converter/src/main.rs @@ -108,7 +108,7 @@ fn stateless_input_from_cache( }; if cache.blocks.len() != 1 { return Err(format!( - "cache contains {} blocks; the ethrex 25 stateless guest accepts one block", + "cache contains {} blocks; the pinned stateless guest accepts one block", cache.blocks.len() ) .into()); diff --git a/tooling/ethrex-fixtures/Cargo.lock b/tooling/ethrex-fixtures/Cargo.lock index bdd48518e..d755840ac 100644 --- a/tooling/ethrex-fixtures/Cargo.lock +++ b/tooling/ethrex-fixtures/Cargo.lock @@ -975,8 +975,8 @@ dependencies = [ [[package]] name = "ethrex-blockchain" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "crossbeam", @@ -1000,8 +1000,8 @@ dependencies = [ [[package]] name = "ethrex-common" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "crc32fast", @@ -1034,8 +1034,8 @@ dependencies = [ [[package]] name = "ethrex-crypto" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "ark-bn254", "ark-ec", @@ -1076,8 +1076,8 @@ dependencies = [ [[package]] name = "ethrex-guest-program" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "ethereum-types", @@ -1099,8 +1099,8 @@ dependencies = [ [[package]] name = "ethrex-l2-common" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "ethereum-types", @@ -1117,8 +1117,8 @@ dependencies = [ [[package]] name = "ethrex-l2-rpc" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "axum", "bytes", @@ -1149,8 +1149,8 @@ dependencies = [ [[package]] name = "ethrex-levm" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "derive_more", @@ -1168,8 +1168,8 @@ dependencies = [ [[package]] name = "ethrex-metrics" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "axum", "ethrex-common", @@ -1184,8 +1184,8 @@ dependencies = [ [[package]] name = "ethrex-p2p" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "aes", "aes-gcm", @@ -1228,8 +1228,8 @@ dependencies = [ [[package]] name = "ethrex-rlp" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "ethereum-types", @@ -1238,8 +1238,8 @@ dependencies = [ [[package]] name = "ethrex-rpc" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "axum", "axum-extra", @@ -1278,8 +1278,8 @@ dependencies = [ [[package]] name = "ethrex-storage" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "anyhow", "bytes", @@ -1300,8 +1300,8 @@ dependencies = [ [[package]] name = "ethrex-storage-rollup" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "async-trait", "bincode", @@ -1316,8 +1316,8 @@ dependencies = [ [[package]] name = "ethrex-trie" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "anyhow", "bytes", @@ -1336,8 +1336,8 @@ dependencies = [ [[package]] name = "ethrex-vm" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "derive_more", diff --git a/tooling/ethrex-fixtures/Cargo.toml b/tooling/ethrex-fixtures/Cargo.toml index 1b5bd3249..ce79d4c0d 100644 --- a/tooling/ethrex-fixtures/Cargo.toml +++ b/tooling/ethrex-fixtures/Cargo.toml @@ -2,6 +2,12 @@ name = "ethrex-fixtures" version = "0.1.0" edition = "2024" +# This crate grew a second binary (`real_block`), which made a bare `cargo run` +# ambiguous and broke `make regen-ethrex-fixtures` plus the commands the fixture +# READMEs tell you to run on a rev bump. The synthetic generator is the one that +# should answer to a bare `cargo run`; the real-block rebuild is always explicit +# (`--bin real_block`, as the Makefile invokes it). +default-run = "ethrex-fixtures" # Detached workspace: keeps the heavy ethrex host deps out of the main build. [workspace] @@ -10,15 +16,15 @@ edition = "2024" # Pinned to the SAME ethrex rev as the guest so generated SSZ inputs match what # the stateless guest deserializes. Re-pin every manifest together with # scripts/set_ethrex_rev.sh. -ethrex-blockchain = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-blockchain" } -ethrex-storage = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-storage" } -ethrex-common = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-common" } -ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-guest-program" } -ethrex-l2-rpc = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-l2-rpc" } -ethrex-rlp = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-rlp" } +ethrex-blockchain = { git = "https://github.com/lambdaclass/ethrex.git", rev = "8effcb0671c5d0b12fe0161ea37c174ec4466b6a", package = "ethrex-blockchain" } +ethrex-storage = { git = "https://github.com/lambdaclass/ethrex.git", rev = "8effcb0671c5d0b12fe0161ea37c174ec4466b6a", package = "ethrex-storage" } +ethrex-common = { git = "https://github.com/lambdaclass/ethrex.git", rev = "8effcb0671c5d0b12fe0161ea37c174ec4466b6a", package = "ethrex-common" } +ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "8effcb0671c5d0b12fe0161ea37c174ec4466b6a", package = "ethrex-guest-program" } +ethrex-l2-rpc = { git = "https://github.com/lambdaclass/ethrex.git", rev = "8effcb0671c5d0b12fe0161ea37c174ec4466b6a", package = "ethrex-l2-rpc" } +ethrex-rlp = { git = "https://github.com/lambdaclass/ethrex.git", rev = "8effcb0671c5d0b12fe0161ea37c174ec4466b6a", package = "ethrex-rlp" } # Walking the witness tries: the storage keys are `keccak(slot)`, so recovering a # genesis-shaped alloc from a witness needs the node types, not just the accessors. -ethrex-trie = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-trie" } +ethrex-trie = { git = "https://github.com/lambdaclass/ethrex.git", rev = "8effcb0671c5d0b12fe0161ea37c174ec4466b6a", package = "ethrex-trie" } tokio = { version = "1", features = ["rt-multi-thread", "macros"] } # Exact pin: keep the SSZ implementation in sync with the guest's ethrex rev. diff --git a/tooling/ethrex-fixtures/README.md b/tooling/ethrex-fixtures/README.md index 1cd68fecd..b0a7c1e09 100644 --- a/tooling/ethrex-fixtures/README.md +++ b/tooling/ethrex-fixtures/README.md @@ -93,7 +93,10 @@ EEST benchmark fixtures; `ETHREX_BENCH_WORKLOAD_AFTER_BUMP.md` in the repository root has both sets measured side by side. Measured on the guest ELF at ethrex `2cb18b0b`: 37,137,386 cycles, 6,003 keccak -calls, 164 ECSM calls. Fixture: 549,144 bytes. +calls, 164 ECSM calls. Fixture: 549,144 bytes. The pin has since moved to +`8effcb06`, which declares 26.0.0 and whose tree is byte-identical in every crate +this repo compiles, so the counts carry — and the fixture regenerates to the same +digest at both. **Pin the ELF whenever you quote a cycle count.** Those three counts are deterministic for a given ELF and input, and they move with anything that changes diff --git a/tooling/ethrex-fixtures/src/bin/real_block.rs b/tooling/ethrex-fixtures/src/bin/real_block.rs index f9328f9b0..a2e19cddf 100644 --- a/tooling/ethrex-fixtures/src/bin/real_block.rs +++ b/tooling/ethrex-fixtures/src/bin/real_block.rs @@ -1,7 +1,7 @@ //! Rebuild a real mainnet block as an Amsterdam block the stateless guest accepts. //! //! The retired benchmark fixture (`ethrex_mainnet_25368371.bin`) is an rkyv -//! `ProgramInput` from before execution-specs #3278. ethrex 25's guest only +//! `ProgramInput` from before execution-specs #3278. The pinned guest only //! decodes schema `0x1501` (Amsterdam) and mainnet has no Amsterdam fork yet, so //! that block cannot be re-serialized: it has no block access list hash and no //! slot number, and its state carries none of the EIP-8282 predeploys Amsterdam diff --git a/tooling/ethrex-tests/Cargo.lock b/tooling/ethrex-tests/Cargo.lock index 62e8cd38c..4a27fae68 100644 --- a/tooling/ethrex-tests/Cargo.lock +++ b/tooling/ethrex-tests/Cargo.lock @@ -693,8 +693,8 @@ dependencies = [ [[package]] name = "ethrex-common" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "crc32fast", @@ -726,8 +726,8 @@ dependencies = [ [[package]] name = "ethrex-crypto" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "ark-bn254", "ark-ec", @@ -747,8 +747,8 @@ dependencies = [ [[package]] name = "ethrex-guest-program" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "ethereum-types", @@ -770,8 +770,8 @@ dependencies = [ [[package]] name = "ethrex-l2-common" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "ethereum-types", @@ -788,8 +788,8 @@ dependencies = [ [[package]] name = "ethrex-levm" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "derive_more", @@ -806,8 +806,8 @@ dependencies = [ [[package]] name = "ethrex-rlp" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "ethereum-types", @@ -825,8 +825,8 @@ dependencies = [ [[package]] name = "ethrex-trie" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "anyhow", "bytes", @@ -845,8 +845,8 @@ dependencies = [ [[package]] name = "ethrex-vm" -version = "25.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=2cb18b0b95b27a2555d3debffdebc43c9685d6e3#2cb18b0b95b27a2555d3debffdebc43c9685d6e3" +version = "26.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=8effcb0671c5d0b12fe0161ea37c174ec4466b6a#8effcb0671c5d0b12fe0161ea37c174ec4466b6a" dependencies = [ "bytes", "derive_more", diff --git a/tooling/ethrex-tests/Cargo.toml b/tooling/ethrex-tests/Cargo.toml index a6e085f06..9912b7dd7 100644 --- a/tooling/ethrex-tests/Cargo.toml +++ b/tooling/ethrex-tests/Cargo.toml @@ -12,7 +12,7 @@ executor = { path = "../../executor" } # Pinned to the SAME ethrex rev as the guest so the native reference reads the # same SSZ stateless input and output. Re-pin every manifest together with # scripts/set_ethrex_rev.sh. -ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "2cb18b0b95b27a2555d3debffdebc43c9685d6e3", package = "ethrex-guest-program" } +ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "8effcb0671c5d0b12fe0161ea37c174ec4466b6a", package = "ethrex-guest-program" } libssz = "=0.3.0" # Match the root workspace's optimized dev profile: these tests execute the VM. From f0a4121e195290624fc6a7f2616df84456c34b8d Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Thu, 10 Sep 2026 15:22:44 -0300 Subject: [PATCH 17/27] Re-measure the workload at the new pin --- .github/workflows/benchmark-pr.yml | 2 +- tooling/ethrex-fixtures/README.md | 30 +++++++++++++++++++----------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.github/workflows/benchmark-pr.yml b/.github/workflows/benchmark-pr.yml index 4d8152dd6..b96e938f1 100644 --- a/.github/workflows/benchmark-pr.yml +++ b/.github/workflows/benchmark-pr.yml @@ -70,7 +70,7 @@ env: ELF: executor/program_artifacts/rust/ethrex.elf # The workload: a real Ethereum block. WHICH block lives in # the Makefile and nowhere else — nothing in this file names one, so a repoint - # moves this job without editing it. At the current default that is 37,137,386 + # moves this job without editing it. At the current default that is 37,137,748 # cycles, 6,003 keccak calls and 164 ecsm calls: 13.6x the work of the synthetic # block (ethrex_bench_4, 2,727,681 cycles) with a keccak:ecrecover mix of 37:1 # against its 13:1. That is the whole point — a prover change can move the diff --git a/tooling/ethrex-fixtures/README.md b/tooling/ethrex-fixtures/README.md index b0a7c1e09..d05e18dca 100644 --- a/tooling/ethrex-fixtures/README.md +++ b/tooling/ethrex-fixtures/README.md @@ -92,19 +92,27 @@ economics. For workloads whose gas limits were computed for Amsterdam, use the EEST benchmark fixtures; `ETHREX_BENCH_WORKLOAD_AFTER_BUMP.md` in the repository root has both sets measured side by side. -Measured on the guest ELF at ethrex `2cb18b0b`: 37,137,386 cycles, 6,003 keccak -calls, 164 ECSM calls. Fixture: 549,144 bytes. The pin has since moved to -`8effcb06`, which declares 26.0.0 and whose tree is byte-identical in every crate -this repo compiles, so the counts carry — and the fixture regenerates to the same -digest at both. +Measured on the guest ELF at ethrex `8effcb06`: **37,137,748 cycles**, 6,003 +keccak calls, 164 ECSM calls. Fixture: 549,144 bytes. -**Pin the ELF whenever you quote a cycle count.** Those three counts are +**Pin the ELF whenever you quote a cycle count.** The three counts are deterministic for a given ELF and input, and they move with anything that changes -the guest: the ethrex rev, guest optimisation flags, and the **clang major -version** by around 2 %. The guest embeds C (`secp256k1-sys`) and the Makefile -pins target flags but not the compiler, so `cc` picks up whatever `clang` is on -PATH — two boxes with different clang majors report different counts for the same -commit. A count without its ELF is not comparable to anything. +the guest — including changes that touch none of the source it compiles. Moving +the pin from `2cb18b0b` to `8effcb06` cost **+362 cycles, 0.001 %**, with keccak +and ECSM identical, and that was the whole difference: no `.rs` file in the +guest's dependency graph differs between the two revs and no crates.io dependency +moved, so what shifted is the version metadata the ethrex crates carry. Immaterial +next to the ~1 % this workload can resolve, but it does mean an exact count +belongs to an exact rev. + +The compiler is not pinned either — the guest embeds C (`secp256k1-sys`) and the +Makefile pins target flags but not `cc` — so in principle two boxes with different +clang majors can disagree. In practice the effect is small and not well +characterised: the one figure recorded in this repo is 0.13 % on a different +block, while this block came out at exactly 37,137,386 on both macOS/arm64 and the +Linux x86-64 runner at the previous pin, from ELF binaries that were themselves +different. Quote the rev; do not assume the machine matters, and do not assume it +does not. ### Choosing the epoch size, and what the workload costs From c46a1c0780e75794fdcab94ca016cbf980dd9062 Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Thu, 10 Sep 2026 18:31:57 -0300 Subject: [PATCH 18/27] Share the SSZ input builder between both generators `build_stateless_input` and `empty_execution_requests` existed as two near-identical copies, one per binary in this crate, differing only in how the block type was spelled. The file that carried the second copy said a real PR should lift them into a module, and this is that PR. They now live in src/lib.rs and both binaries import them, so the next ethrex rev bump moves one encoder instead of two that nothing forced to stay in step. Behaviour-preserving, and checked that way rather than assumed: after the move all five generated fixtures still come out at the digests they already had, the real-block one byte-for-byte at 08a52e10 with the same installed state root. --- tooling/ethrex-fixtures/src/bin/real_block.rs | 146 +---------------- tooling/ethrex-fixtures/src/lib.rs | 155 ++++++++++++++++++ tooling/ethrex-fixtures/src/main.rs | 146 +---------------- 3 files changed, 157 insertions(+), 290 deletions(-) create mode 100644 tooling/ethrex-fixtures/src/lib.rs diff --git a/tooling/ethrex-fixtures/src/bin/real_block.rs b/tooling/ethrex-fixtures/src/bin/real_block.rs index a2e19cddf..5f382cdb8 100644 --- a/tooling/ethrex-fixtures/src/bin/real_block.rs +++ b/tooling/ethrex-fixtures/src/bin/real_block.rs @@ -26,23 +26,17 @@ use bytes::Bytes; use ethrex_blockchain::payload::{BuildPayloadArgs, create_payload}; use ethrex_blockchain::{Blockchain, BlockchainOptions}; use ethrex_common::H256; -use ethrex_common::types::block_access_list::BlockAccessList; use ethrex_common::types::block_execution_witness::{ ExecutionWitness, RpcExecutionWitness, amsterdam_chain_config, decode_witness_headers, }; -use ethrex_common::types::stateless_ssz::{ - Bytes20, ExecutionPayload, ExecutionRequests, LogsBloom, NewPayloadRequest, - STATELESS_INPUT_SCHEMA_ID, SszExecutionWitness, SszPublicKeys, SszStatelessInput, -}; use ethrex_common::types::{AccountState, Block, ELASTICITY_MULTIPLIER, Genesis}; +use ethrex_fixtures::build_stateless_input; use ethrex_guest_program::crypto::{Crypto, NativeCrypto}; use ethrex_guest_program::l1::run_stateless_guest; use ethrex_rlp::decode::RLPDecode; use ethrex_rlp::encode::RLPEncode; use ethrex_storage::{EngineType, Store}; use ethrex_trie::{Nibbles, Node, NodeRef}; -use libssz::SszEncode; -use libssz_types::{ProgressiveList, SszList, SszVector}; use std::collections::BTreeMap; const GENESIS_JSON: &str = include_str!("../../genesis.json"); @@ -55,144 +49,6 @@ const CHAIN_ID: u64 = 1; const GENESIS_BASE_FEE: u64 = 1_000; const GAS_CEIL: u64 = 60_000_000; -fn empty_execution_requests() -> ExecutionRequests { - ExecutionRequests { - deposits: ProgressiveList::new(), - withdrawals: ProgressiveList::new(), - consolidations: ProgressiveList::new(), - builder_deposits: ProgressiveList::new(), - builder_exits: ProgressiveList::new(), - } -} - -fn build_stateless_input( - block: &Block, - witness: &RpcExecutionWitness, - block_access_list: Option<&BlockAccessList>, - chain_id: u64, -) -> Result, Box> { - let header = &block.header; - let bal = block_access_list.ok_or("Amsterdam fixture has no block access list")?; - let block_access_list_hash = header - .block_access_list_hash - .ok_or("Amsterdam fixture header has no block_access_list_hash")?; - if bal.compute_hash(&NativeCrypto) != block_access_list_hash { - return Err("fixture BAL does not match block_access_list_hash".into()); - } - let slot_number = header - .slot_number - .ok_or("Amsterdam fixture header has no slot_number")?; - - let transactions = block - .body - .transactions - .iter() - .map(|tx| tx.encode_canonical_to_vec().into()) - .collect::>>() - .into(); - let public_keys = block - .body - .transactions - .iter() - .enumerate() - .map(|(i, tx)| { - let key = tx - .public_key(&NativeCrypto) - .map_err(|e| format!("failed to recover public key for transaction {i}: {e}"))? - .ok_or_else(|| format!("transaction {i} has no recoverable signature"))?; - SszVector::try_from(key.to_vec()) - .map_err(|e| format!("public key for transaction {i} is invalid: {e:?}")) - }) - .collect::, String>>()?; - let withdrawals = block - .body - .withdrawals - .clone() - .unwrap_or_default() - .into_iter() - .map( - |withdrawal| ethrex_common::types::stateless_ssz::Withdrawal { - index: withdrawal.index, - validator_index: withdrawal.validator_index, - address: Bytes20(withdrawal.address.0), - amount: withdrawal.amount, - }, - ) - .collect::>() - .into(); - let logs_bloom: LogsBloom = SszVector::try_from(header.logs_bloom.0.to_vec())?; - let extra_data = SszList::try_from(header.extra_data.to_vec())?; - let base_fee = header.base_fee_per_gas.ok_or("fixture has no base fee")?; - let mut base_fee_bytes = [0u8; 32]; - base_fee_bytes[..8].copy_from_slice(&base_fee.to_le_bytes()); - - let execution_payload = ExecutionPayload { - parent_hash: header.parent_hash.0, - fee_recipient: Bytes20(header.coinbase.0), - state_root: header.state_root.0, - receipts_root: header.receipts_root.0, - logs_bloom, - prev_randao: header.prev_randao.0, - block_number: header.number, - gas_limit: header.gas_limit, - gas_used: header.gas_used, - timestamp: header.timestamp, - extra_data, - base_fee_per_gas: base_fee_bytes, - block_hash: header.compute_block_hash(&NativeCrypto).0, - transactions, - withdrawals, - blob_gas_used: header.blob_gas_used.unwrap_or_default(), - excess_blob_gas: header.excess_blob_gas.unwrap_or_default(), - block_access_list: ethrex_rlp::encode::RLPEncode::encode_to_vec(bal).into(), - slot_number, - }; - let ssz_witness = SszExecutionWitness { - state: ProgressiveList::from( - witness - .state - .iter() - .map(|bytes| SszList::try_from(bytes.to_vec())) - .collect::, _>>()?, - ), - codes: ProgressiveList::from( - witness - .codes - .iter() - .map(|bytes| SszList::try_from(bytes.to_vec())) - .collect::, _>>()?, - ), - headers: SszList::try_from( - witness - .headers - .iter() - .map(|bytes| SszList::try_from(bytes.to_vec())) - .collect::, _>>()?, - )?, - }; - let input = SszStatelessInput { - new_payload_request: NewPayloadRequest { - execution_payload, - versioned_hashes: block - .body - .transactions - .iter() - .flat_map(|tx| tx.blob_versioned_hashes()) - .map(|hash| hash.0) - .collect::>() - .into(), - parent_beacon_block_root: header.parent_beacon_block_root.unwrap_or_default().0, - execution_requests: empty_execution_requests(), - }, - witness: ssz_witness, - chain_id, - public_keys: SszPublicKeys::from(public_keys), - }; - let mut bytes = STATELESS_INPUT_SCHEMA_ID.to_be_bytes().to_vec(); - input.ssz_append(&mut bytes); - Ok(bytes) -} - /// Walk an embedded witness trie and return every `(32-byte key, value)` leaf it /// still holds. Pruned subtrees appear as `NodeRef::Hash` and are skipped: they /// are the proof siblings, which no transaction in the block reads. diff --git a/tooling/ethrex-fixtures/src/lib.rs b/tooling/ethrex-fixtures/src/lib.rs new file mode 100644 index 000000000..00849e20f --- /dev/null +++ b/tooling/ethrex-fixtures/src/lib.rs @@ -0,0 +1,155 @@ +//! Shared SSZ input construction for this crate's two generators. +//! +//! `build_stateless_input` is the encoder both binaries need — `ethrex-fixtures` for the +//! synthetic blocks it builds from a funded genesis, and `real_block` for a real mainnet +//! block rebuilt as an Amsterdam one. It used to be copied into each, which meant the +//! copies had to be moved together on every ethrex rev bump with nothing enforcing it. + +use ethrex_common::types::Block; +use ethrex_common::types::block_access_list::BlockAccessList; +use ethrex_common::types::block_execution_witness::RpcExecutionWitness; +use ethrex_common::types::stateless_ssz::{ + Bytes20, ExecutionPayload, ExecutionRequests, LogsBloom, NewPayloadRequest, + STATELESS_INPUT_SCHEMA_ID, SszExecutionWitness, SszPublicKeys, SszStatelessInput, +}; +use ethrex_guest_program::crypto::NativeCrypto; +use libssz::SszEncode; +use libssz_types::{ProgressiveList, SszList, SszVector}; + +pub fn empty_execution_requests() -> ExecutionRequests { + ExecutionRequests { + deposits: ProgressiveList::new(), + withdrawals: ProgressiveList::new(), + consolidations: ProgressiveList::new(), + builder_deposits: ProgressiveList::new(), + builder_exits: ProgressiveList::new(), + } +} + +pub fn build_stateless_input( + block: &Block, + witness: &RpcExecutionWitness, + block_access_list: Option<&BlockAccessList>, + chain_id: u64, +) -> Result, Box> { + let header = &block.header; + let bal = block_access_list.ok_or("Amsterdam fixture has no block access list")?; + let block_access_list_hash = header + .block_access_list_hash + .ok_or("Amsterdam fixture header has no block_access_list_hash")?; + if bal.compute_hash(&NativeCrypto) != block_access_list_hash { + return Err("fixture BAL does not match block_access_list_hash".into()); + } + let slot_number = header + .slot_number + .ok_or("Amsterdam fixture header has no slot_number")?; + + let transactions = block + .body + .transactions + .iter() + .map(|tx| tx.encode_canonical_to_vec().into()) + .collect::>>() + .into(); + let public_keys = block + .body + .transactions + .iter() + .enumerate() + .map(|(i, tx)| { + let key = tx + .public_key(&NativeCrypto) + .map_err(|e| format!("failed to recover public key for transaction {i}: {e}"))? + .ok_or_else(|| format!("transaction {i} has no recoverable signature"))?; + SszVector::try_from(key.to_vec()) + .map_err(|e| format!("public key for transaction {i} is invalid: {e:?}")) + }) + .collect::, String>>()?; + let withdrawals = block + .body + .withdrawals + .clone() + .unwrap_or_default() + .into_iter() + .map( + |withdrawal| ethrex_common::types::stateless_ssz::Withdrawal { + index: withdrawal.index, + validator_index: withdrawal.validator_index, + address: Bytes20(withdrawal.address.0), + amount: withdrawal.amount, + }, + ) + .collect::>() + .into(); + let logs_bloom: LogsBloom = SszVector::try_from(header.logs_bloom.0.to_vec())?; + let extra_data = SszList::try_from(header.extra_data.to_vec())?; + let base_fee = header.base_fee_per_gas.ok_or("fixture has no base fee")?; + let mut base_fee_bytes = [0u8; 32]; + base_fee_bytes[..8].copy_from_slice(&base_fee.to_le_bytes()); + + let execution_payload = ExecutionPayload { + parent_hash: header.parent_hash.0, + fee_recipient: Bytes20(header.coinbase.0), + state_root: header.state_root.0, + receipts_root: header.receipts_root.0, + logs_bloom, + prev_randao: header.prev_randao.0, + block_number: header.number, + gas_limit: header.gas_limit, + gas_used: header.gas_used, + timestamp: header.timestamp, + extra_data, + base_fee_per_gas: base_fee_bytes, + block_hash: header.compute_block_hash(&NativeCrypto).0, + transactions, + withdrawals, + blob_gas_used: header.blob_gas_used.unwrap_or_default(), + excess_blob_gas: header.excess_blob_gas.unwrap_or_default(), + block_access_list: ethrex_rlp::encode::RLPEncode::encode_to_vec(bal).into(), + slot_number, + }; + let ssz_witness = SszExecutionWitness { + state: ProgressiveList::from( + witness + .state + .iter() + .map(|bytes| SszList::try_from(bytes.to_vec())) + .collect::, _>>()?, + ), + codes: ProgressiveList::from( + witness + .codes + .iter() + .map(|bytes| SszList::try_from(bytes.to_vec())) + .collect::, _>>()?, + ), + headers: SszList::try_from( + witness + .headers + .iter() + .map(|bytes| SszList::try_from(bytes.to_vec())) + .collect::, _>>()?, + )?, + }; + let input = SszStatelessInput { + new_payload_request: NewPayloadRequest { + execution_payload, + versioned_hashes: block + .body + .transactions + .iter() + .flat_map(|tx| tx.blob_versioned_hashes()) + .map(|hash| hash.0) + .collect::>() + .into(), + parent_beacon_block_root: header.parent_beacon_block_root.unwrap_or_default().0, + execution_requests: empty_execution_requests(), + }, + witness: ssz_witness, + chain_id, + public_keys: SszPublicKeys::from(public_keys), + }; + let mut bytes = STATELESS_INPUT_SCHEMA_ID.to_be_bytes().to_vec(); + input.ssz_append(&mut bytes); + Ok(bytes) +} diff --git a/tooling/ethrex-fixtures/src/main.rs b/tooling/ethrex-fixtures/src/main.rs index 4875fde77..e91ac0e6a 100644 --- a/tooling/ethrex-fixtures/src/main.rs +++ b/tooling/ethrex-fixtures/src/main.rs @@ -24,22 +24,16 @@ use bytes::Bytes; use ethrex_blockchain::payload::{BuildPayloadArgs, create_payload}; use ethrex_blockchain::{Blockchain, BlockchainOptions}; -use ethrex_common::types::block_access_list::BlockAccessList; use ethrex_common::types::block_execution_witness::RpcExecutionWitness; -use ethrex_common::types::stateless_ssz::{ - Bytes20, ExecutionPayload, ExecutionRequests, LogsBloom, NewPayloadRequest, - STATELESS_INPUT_SCHEMA_ID, SszExecutionWitness, SszPublicKeys, SszStatelessInput, -}; use ethrex_common::types::{ EIP1559Transaction, ELASTICITY_MULTIPLIER, Genesis, GenesisAccount, Transaction, TxKind, }; use ethrex_common::{Address, H256, U256}; +use ethrex_fixtures::build_stateless_input; use ethrex_guest_program::crypto::NativeCrypto; use ethrex_guest_program::l1::run_stateless_guest; use ethrex_l2_rpc::signer::{LocalSigner, Signable, Signer}; use ethrex_storage::{EngineType, Store}; -use libssz::SszEncode; -use libssz_types::{ProgressiveList, SszList, SszVector}; use secp256k1::SecretKey; /// Well-known load-test rich account (funded in genesis.json). Key is public @@ -85,144 +79,6 @@ fn rich_signer() -> Result> { Ok(LocalSigner::new(SecretKey::from_slice(&hex::decode(RICH_PK)?)?).into()) } -fn empty_execution_requests() -> ExecutionRequests { - ExecutionRequests { - deposits: ProgressiveList::new(), - withdrawals: ProgressiveList::new(), - consolidations: ProgressiveList::new(), - builder_deposits: ProgressiveList::new(), - builder_exits: ProgressiveList::new(), - } -} - -fn build_stateless_input( - block: ðrex_common::types::Block, - witness: &RpcExecutionWitness, - block_access_list: Option<&BlockAccessList>, - chain_id: u64, -) -> Result, Box> { - let header = &block.header; - let bal = block_access_list.ok_or("Amsterdam fixture has no block access list")?; - let block_access_list_hash = header - .block_access_list_hash - .ok_or("Amsterdam fixture header has no block_access_list_hash")?; - if bal.compute_hash(&NativeCrypto) != block_access_list_hash { - return Err("fixture BAL does not match block_access_list_hash".into()); - } - let slot_number = header - .slot_number - .ok_or("Amsterdam fixture header has no slot_number")?; - - let transactions = block - .body - .transactions - .iter() - .map(|tx| tx.encode_canonical_to_vec().into()) - .collect::>>() - .into(); - let public_keys = block - .body - .transactions - .iter() - .enumerate() - .map(|(i, tx)| { - let key = tx - .public_key(&NativeCrypto) - .map_err(|e| format!("failed to recover public key for transaction {i}: {e}"))? - .ok_or_else(|| format!("transaction {i} has no recoverable signature"))?; - SszVector::try_from(key.to_vec()) - .map_err(|e| format!("public key for transaction {i} is invalid: {e:?}")) - }) - .collect::, String>>()?; - let withdrawals = block - .body - .withdrawals - .clone() - .unwrap_or_default() - .into_iter() - .map( - |withdrawal| ethrex_common::types::stateless_ssz::Withdrawal { - index: withdrawal.index, - validator_index: withdrawal.validator_index, - address: Bytes20(withdrawal.address.0), - amount: withdrawal.amount, - }, - ) - .collect::>() - .into(); - let logs_bloom: LogsBloom = SszVector::try_from(header.logs_bloom.0.to_vec())?; - let extra_data = SszList::try_from(header.extra_data.to_vec())?; - let base_fee = header.base_fee_per_gas.ok_or("fixture has no base fee")?; - let mut base_fee_bytes = [0u8; 32]; - base_fee_bytes[..8].copy_from_slice(&base_fee.to_le_bytes()); - - let execution_payload = ExecutionPayload { - parent_hash: header.parent_hash.0, - fee_recipient: Bytes20(header.coinbase.0), - state_root: header.state_root.0, - receipts_root: header.receipts_root.0, - logs_bloom, - prev_randao: header.prev_randao.0, - block_number: header.number, - gas_limit: header.gas_limit, - gas_used: header.gas_used, - timestamp: header.timestamp, - extra_data, - base_fee_per_gas: base_fee_bytes, - block_hash: header.compute_block_hash(&NativeCrypto).0, - transactions, - withdrawals, - blob_gas_used: header.blob_gas_used.unwrap_or_default(), - excess_blob_gas: header.excess_blob_gas.unwrap_or_default(), - block_access_list: ethrex_rlp::encode::RLPEncode::encode_to_vec(bal).into(), - slot_number, - }; - let ssz_witness = SszExecutionWitness { - state: ProgressiveList::from( - witness - .state - .iter() - .map(|bytes| SszList::try_from(bytes.to_vec())) - .collect::, _>>()?, - ), - codes: ProgressiveList::from( - witness - .codes - .iter() - .map(|bytes| SszList::try_from(bytes.to_vec())) - .collect::, _>>()?, - ), - headers: SszList::try_from( - witness - .headers - .iter() - .map(|bytes| SszList::try_from(bytes.to_vec())) - .collect::, _>>()?, - )?, - }; - let input = SszStatelessInput { - new_payload_request: NewPayloadRequest { - execution_payload, - versioned_hashes: block - .body - .transactions - .iter() - .flat_map(|tx| tx.blob_versioned_hashes()) - .map(|hash| hash.0) - .collect::>() - .into(), - parent_beacon_block_root: header.parent_beacon_block_root.unwrap_or_default().0, - execution_requests: empty_execution_requests(), - }, - witness: ssz_witness, - chain_id, - public_keys: SszPublicKeys::from(public_keys), - }; - let mut bytes = STATELESS_INPUT_SCHEMA_ID.to_be_bytes().to_vec(); - input.ssz_append(&mut bytes); - Ok(bytes) -} - /// Distinct recipient address for tx index `i` (fresh account, no funding needed). fn recipient_for(i: u64) -> Address { Address::from_low_u64_be(0xdead_0000_0000u64 + i) From 2609d8c74fa5384479df61a234f7ee8bbb873a76 Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Thu, 10 Sep 2026 18:32:16 -0300 Subject: [PATCH 19/27] Re-pin the profiling and sizing comments to the rebuilt workload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both were left describing the retired block. perf_diff.sh advertised 50.78M cycles, 10,478 keccak and 116 ecsm as the real default, plus ~240 GB monolithic, 158.8 s per recording, ~1.2 GB per bundle and ~52 GB of RAM; the real numbers are 37,137,748 / 6,003 / 164, ~182 GB, ~125 s, ~790 MB and ~45 GiB. Its synthetic figures were stale too, so they were re-measured on the current guest rather than dropped: 7,029,393 cycles, 486 keccak, 80 ecsm for the 20-transfer block, which keeps the point the comment is making — the keccak:ecsm ratio is 37:1 against 6:1, so a hot symbol in one workload need not be hot in the other. bench_abba.sh's sizing table still carried "the pair-delta sd is NOT yet measured" with a 1.0%/2.0% bracket, which the workflow that invokes it no longer says: it now quotes the two measured conditions. A script disagreeing with its own workflow is worse than either being out of date, so the table is the same one, with a note to keep them in step. Also drops perf_diff.sh's "~2% with the clang major" figure. It came from a headline whose own evidence put the effect at 0.13%, and the guest reports the same count here on two toolchains, so the file now points at the README's caveat instead of quoting a number nothing measured. --- scripts/bench_abba.sh | 22 +++++++++++++--------- scripts/perf_diff.sh | 23 +++++++++++++---------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/scripts/bench_abba.sh b/scripts/bench_abba.sh index 2966c1c79..7a9f39abf 100755 --- a/scripts/bench_abba.sh +++ b/scripts/bench_abba.sh @@ -48,17 +48,21 @@ # WORKLOAD=synthetic to reproduce a number recorded against that fixture. # # Sizing at WORKLOAD=real, from the paired t-test (resolvable 95% delta = -# t* x sd / sqrt(N)). The pair-delta sd on the bench runner is NOT yet measured; the -# two columns bracket it between 1.0% (the GPU box's measured 0.64% plus margin) and -# 2.0% (sqrt(2) x the runner's measured 1.43% single-run CV): +# t* x sd / sqrt(N)). The two columns are the same runner under two conditions, not a +# guess bracketing an unknown: its variance is contention, so the single-run CV is +# 0.34% across the proves that got the most CPU and 1.26% across all of a 14-prove +# baseline. sd of a pair delta is sqrt(2) x that. Keep this table in sync with the one +# in .github/workflows/bench-abba.yml: # -# pairs wall resolves (sd 2.0% / sd 1.0%) -# 8 ~50 min 1.7% / 0.8% -# 12 ~72 min 1.3% / 0.6% <- workflow default -# 20 ~1h55m 0.9% / 0.5% -# 32 ~3h 0.7% / 0.4% +# pairs wall resolves (quiet box, sd 0.49% / shared, sd 1.78%) +# 8 ~41 min 0.34% / 1.24% +# 12 ~58 min 0.28% / 1.01% <- workflow default +# 20 ~1h31m 0.21% / 0.78% +# 32 ~2h22m 0.16% / 0.62% # -# Wall assumes epoch 2^22 (158.8 s per prove, two per pair) plus ~8 min of setup. +# Wall assumes epoch 2^22 (~125 s per prove, two per pair) plus ~8 min of setup. Read +# the column the run earned: the exclusivity line printed after the pairs reports the +# CPU share of every prove and flags any under 90% of the batch's best. # The first real ABBA run MEASURES that sd — read it off the `sd` field of the # paired-t line printed below — and this table should be re-pinned to it. # diff --git a/scripts/perf_diff.sh b/scripts/perf_diff.sh index e6e949fb7..ee4a01c37 100755 --- a/scripts/perf_diff.sh +++ b/scripts/perf_diff.sh @@ -18,17 +18,20 @@ # 22 is the calibrated bench-runner tier, matching /bench; use 23 on a # 128 GiB box (tooling/ethrex-fixtures/README.md, "Choosing the epoch size"). # -# Pick the workload that matches the run you are localizing, because the symbol -# mix follows the block: the real default is 50.78M cycles, 10,478 keccak calls and -# 116 ecsm calls, and the synthetic option (20 plain transfers) inverts that at -# 8.73M cycles, 411 keccak, 80 ecsm — so a hot symbol in one need not be hot in the other. -# Both counts are from the same guest ELF (merge fdb92f67, main @ 9ccdaf2, clang 21); -# they move with guest optimisation (#861's thin LTO) and ~2% with the clang major, so -# pin the ELF when quoting one. +# Pick the workload that matches the run you are localizing, because the symbol mix +# follows the block: the real default is 37,137,748 cycles, 6,003 keccak calls and 164 +# ecsm calls, and the synthetic option (20 plain transfers) shifts that to 7,029,393 +# cycles, 486 keccak, 80 ecsm — a keccak:ecsm ratio of 37:1 against 6:1, so a hot symbol +# in one need not be hot in the other. # -# WORKLOAD=real also switches to a continuation prove (monolithic would need ~240 GB -# at that trace length), which is 158.8 s per recording on the bench runner — five -# recordings, so budget ~13 min of proving, plus ~1.2 GB of disk per bundle and ~52 GB +# Both counts are deterministic for a given ELF and input, and belong to the ethrex rev +# in the manifests (`scripts/set_ethrex_rev.sh --show`) — they move with the rev, with +# guest optimisation, and possibly with the compiler, which the Makefile does not pin. +# See "Pin the ELF whenever you quote a cycle count" in tooling/ethrex-fixtures/README.md. +# +# WORKLOAD=real also switches to a continuation prove (monolithic would need ~182 GB at +# that trace length), which is ~125 s per recording on the bench runner — five +# recordings, so budget ~11 min of proving, plus ~790 MB of disk per bundle and ~45 GiB # of RAM at the default epoch. # # Produces: From dbce739a1ba32a696e66e14de69caba1a16d6fc1 Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Thu, 10 Sep 2026 18:32:26 -0300 Subject: [PATCH 20/27] Reuse the baseline prover for the cycle floor instead of building a third one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The floor check ran before the A/B binaries were built, so it built its own `cli` — at the repo root with default features, sharing neither the worktree's target directory nor its feature set. That is a full extra release build of the prover stack: free on a warm bench runner, a cold build on paid time on the rented GPU box that also runs this script. It now runs after step 2 and executes with `cli_B`, the baseline prover, which measures the workload just as well. Two smaller things in the same block. The execute is now gated on WORKLOAD=real, matching the comparison it feeds — a synthetic run was paying for a measurement nobody read. And the failure path keeps its diagnostic: a bare assignment from a failing command substitution aborts under `set -e` before the message can print, so the operator got a silent death instead of the line telling them to regenerate the fixture. Wrapping it makes the guardrail fail closed *and* say why. --- scripts/bench_abba.sh | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/scripts/bench_abba.sh b/scripts/bench_abba.sh index 7a9f39abf..f997c1b8b 100755 --- a/scripts/bench_abba.sh +++ b/scripts/bench_abba.sh @@ -180,21 +180,13 @@ INPUT="$(cd "$(dirname "$INPUT_REL")" && pwd)/$(basename "$INPUT_REL")" # `successful_validation == 1` assert in tooling/ethrex-tests), while a small TX_COUNT # legitimately lands near this floor -- a 1-transfer block ran 1.80M cycles before the # bump, an empty one 0.99M. Applying the floor there would reject valid runs. +# +# Runs after step 2 and reuses `cli_B`: the baseline prover executes the workload just as +# well as a freshly built one, and building a third binary here would be a full extra +# release build -- at the repo root with default features, so sharing neither the +# worktree's target dir nor its feature set. Free on a warm runner, a cold build on paid +# time on a rented GPU box. MIN_PLAUSIBLE_CYCLES=1000000 -if [ ! -x ./target/release/cli ]; then - cargo build --release -p cli >/dev/null -fi -workload_cycles="$(./target/release/cli execute "$ELF" --private-input "$INPUT" --cycles \ - | awk '/^Cycles:/ {print $2}')" -if [ "$WORKLOAD" = "real" ] && [ "${workload_cycles:-0}" -lt "$MIN_PLAUSIBLE_CYCLES" ]; then - echo "ERROR: the workload executed only ${workload_cycles:-0} cycles, below the" >&2 - echo " ${MIN_PLAUSIBLE_CYCLES} floor. The guest almost certainly rejected the input:" >&2 - echo " ELF $ELF" >&2 - echo " input $INPUT" >&2 - echo " Rebuild the fixture at this ethrex rev (make regen-real-block-fixture)." >&2 - exit 1 -fi -echo "==> Workload executes: $workload_cycles cycles" # --- 2. Build (or reuse) both prover binaries --- need_build=0 @@ -245,6 +237,23 @@ else echo " cli_A=${SHA_A:0:10} cli_B=${SHA_B:0:10} features=$BENCH_FEATURES" fi +if [ "$WORKLOAD" = "real" ]; then + # `|| workload_cycles=""` keeps the failure inside a compound: a bare assignment from a + # failing command substitution aborts under `set -e` before the diagnostic below can + # print, which is fail-closed but silent. + workload_cycles="$("$WORK/cli_B" execute "$ELF" --private-input "$INPUT" --cycles 2>&1 \ + | awk '/^Cycles:/ {print $2}')" || workload_cycles="" + if [ "${workload_cycles:-0}" -lt "$MIN_PLAUSIBLE_CYCLES" ]; then + echo "ERROR: the workload executed only ${workload_cycles:-0} cycles, below the" >&2 + echo " ${MIN_PLAUSIBLE_CYCLES} floor. The guest almost certainly rejected the input:" >&2 + echo " ELF $ELF" >&2 + echo " input $INPUT" >&2 + echo " Rebuild the fixture at this ethrex rev (make regen-real-block-fixture)." >&2 + exit 1 + fi + echo "==> Workload executes: $workload_cycles cycles" +fi + # --- 3. Interleaved A/B/B/A measurement (fresh CSV -- pre-committed batch) --- # Every prove also records the share of CPU it actually got. On a shared box that # is the difference between a number and a coincidence: measured on the bench From 150453bedbf0c1a194b084bbdb2792fbe5aad7d1 Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Thu, 10 Sep 2026 18:32:38 -0300 Subject: [PATCH 21/27] Check the shapes before indexing them in the EEST probe The probe read `inputs[0..2]` and `output[33..41]` inside the print, above the asserts that establish those lengths, so a short file or a short output panicked on a slice index instead of failing with the message that names the fixture. The same print had a defensive `output.get(32).unwrap_or(255)` next to a raw `output[33..41]`, which is the inconsistency that hid it. Asserts first, then the print reads plainly. The filename was also byte-sliced to 60 characters, which panics on a multi-byte boundary; it takes 60 chars now. --- .../ethrex-tests/tests/eest_amsterdam_probe.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tooling/ethrex-tests/tests/eest_amsterdam_probe.rs b/tooling/ethrex-tests/tests/eest_amsterdam_probe.rs index 3bd4f593f..7a9c1c150 100644 --- a/tooling/ethrex-tests/tests/eest_amsterdam_probe.rs +++ b/tooling/ethrex-tests/tests/eest_amsterdam_probe.rs @@ -33,22 +33,32 @@ fn eest_amsterdam_fixtures_validate_natively() { for path in entries { let inputs = std::fs::read(&path).expect("read fixture"); + let name = path.file_name().unwrap().to_string_lossy(); + // Every field below is read by index, so the shapes are checked first: a short + // file or a short output should fail with the message that says which, not with + // a slice-index panic. `chars()` because a byte slice of a `str` would panic on + // a multi-byte boundary in a non-ASCII filename. + // 2 = the big-endian schema-id prefix. Spelled out rather than imported: the + // constant lives in `ethrex-common`, which this crate does not depend on. + assert!( + inputs.len() >= 2, + "{name}: file is shorter than the schema-id prefix" + ); let schema_id = u16::from_be_bytes([inputs[0], inputs[1]]); let started = std::time::Instant::now(); let output = run_stateless_guest(&inputs, Arc::new(NativeCrypto)); let elapsed = started.elapsed(); - let name = path.file_name().unwrap().to_string_lossy(); + assert_eq!(output.len(), 43, "{name}: unexpected output length"); println!( "{:<60} in={:>9} B schema={:#06x} out={} B ok={} chain_id={} native={:?}", - &name[..name.len().min(60)], + name.chars().take(60).collect::(), inputs.len(), schema_id, output.len(), - output.get(32).copied().unwrap_or(255), + output[32], u64::from_le_bytes(output[33..41].try_into().unwrap()), elapsed, ); - assert_eq!(output.len(), 43, "{name}: unexpected output length"); assert_eq!(output[32], 1, "{name}: native stateless validation failed"); } } From 1cf1b40f05ffd91f0bff0493130f07a8d5fe686a Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Thu, 10 Sep 2026 18:32:38 -0300 Subject: [PATCH 22/27] Drop the converter's unused dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `lambda-vm-ethrex-crypto`, `sha2` and `k256` have no remaining users in the crate: the first two were needed by tests that the SSZ rewrite removed, and the `k256` entry existed only to align features with one of them. They are not free — this crate's CI job pays a cold build of the ethrex host tree, C libraries included, to run its two tests. --- tooling/ethrex-block-converter/Cargo.lock | 66 ----------------------- tooling/ethrex-block-converter/Cargo.toml | 3 -- 2 files changed, 69 deletions(-) diff --git a/tooling/ethrex-block-converter/Cargo.lock b/tooling/ethrex-block-converter/Cargo.lock index 057713c5c..e741597cc 100644 --- a/tooling/ethrex-block-converter/Cargo.lock +++ b/tooling/ethrex-block-converter/Cargo.lock @@ -510,12 +510,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - [[package]] name = "crossbeam" version = "0.8.4" @@ -777,12 +771,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "embedded-hal" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" - [[package]] name = "enum-ordinalize" version = "4.4.1" @@ -855,13 +843,10 @@ dependencies = [ "ethrex-crypto", "ethrex-guest-program", "ethrex-rlp", - "k256", - "lambda-vm-ethrex-crypto", "libssz", "libssz-types", "serde", "serde_json", - "sha2", ] [[package]] @@ -1630,27 +1615,6 @@ version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" -[[package]] -name = "lambda-vm-ethrex-crypto" -version = "0.1.0" -dependencies = [ - "ethrex-crypto", - "k256", - "lambda-vm-syscalls", -] - -[[package]] -name = "lambda-vm-syscalls" -version = "0.1.0" -dependencies = [ - "getrandom 0.2.17", - "getrandom 0.3.4", - "lazy_static", - "rand 0.9.5", - "riscv", - "thiserror 1.0.69", -] - [[package]] name = "lambdaworks-crypto" version = "0.13.0" @@ -2342,36 +2306,6 @@ dependencies = [ "digest", ] -[[package]] -name = "riscv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05cfa3f7b30c84536a9025150d44d26b8e1cc20ddf436448d74cd9591eefb25" -dependencies = [ - "critical-section", - "embedded-hal", - "paste", - "riscv-macros", - "riscv-pac", -] - -[[package]] -name = "riscv-macros" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d323d13972c1b104aa036bc692cd08b822c8bbf23d79a27c526095856499799" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "riscv-pac" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436" - [[package]] name = "rkyv" version = "0.8.16" diff --git a/tooling/ethrex-block-converter/Cargo.toml b/tooling/ethrex-block-converter/Cargo.toml index 04304554e..7baf86e09 100644 --- a/tooling/ethrex-block-converter/Cargo.toml +++ b/tooling/ethrex-block-converter/Cargo.toml @@ -46,12 +46,9 @@ serde_json = "1" # Keep in sync with crypto/ethrex-crypto's k256 line; verify: # cargo tree -e features,no-dev -i k256 # must list expose-field # cargo tree -e features -i k256 # must match the above -k256 = { version = "=0.13.4", default-features = false, features = ["arithmetic", "expose-field"] } [dev-dependencies] # The LambdaVM `Crypto` implementation used by host-side parity tests. Off # riscv64 its keccak and ECSM paths both fall back to software. -lambda-vm-ethrex-crypto = { path = "../../crypto/ethrex-crypto" } # No feature delta against the rest of the graph (sha2/std is already on), so this # one can stay a dev-dependency without forcing the rebuild described above. -sha2 = "0.10" From ff9d4ab7994c9e03578716aa727a9dbc0ed95864 Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Thu, 10 Sep 2026 18:32:49 -0300 Subject: [PATCH 23/27] Say plainly that the guest's syscalls patch matches nothing today MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The comment claimed the patched and the pinned copies "happen to coincide" at the current rev, which reads as though the git dependency were in the graph and the override were holding it in place. It is not there at all: the lockfile carries no git+https://github.com/yetanotherco/lambda_vm.git source, because the only thing that pulls those syscalls is `ethrex-guest-program`'s `lambdavm` feature and this manifest does not enable it. Cargo does not warn about the unmatched entry either, which was worth checking before writing it down. The patch stays. The hazard it covers is real and silent — a future ethrex rev that pulls the syscalls from git would have the guest link a different copy than the rest of the repo — and the guest is the one binary where that divergence would matter. --- executor/programs/rust/ethrex/Cargo.toml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/executor/programs/rust/ethrex/Cargo.toml b/executor/programs/rust/ethrex/Cargo.toml index 06cae1ae2..8036ae315 100644 --- a/executor/programs/rust/ethrex/Cargo.toml +++ b/executor/programs/rust/ethrex/Cargo.toml @@ -56,11 +56,12 @@ lambda-vm-ethrex-crypto = { path = "../../../../crypto/ethrex-crypto" } # KZG is not linked, so the point-evaluation precompile (0x0a) is unsupported — # see src/main.rs. ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "8effcb0671c5d0b12fe0161ea37c174ec4466b6a", package = "ethrex-guest-program", default-features = false } -# `lambda-vm-syscalls` can still reach the graph as a git dep pinned to a commit -# of THIS repo, which upstream bumps on its own schedule. Override it -# with the working-tree copy so the guest always links the same syscalls as the rest -# of the repo — including uncommitted local edits — rather than whatever commit -# ethrex last pinned. (At the current ethrex rev the two happen to coincide; this -# override is what keeps that coincidence from being load-bearing.) +# Defence in depth, inert today. `lambda-vm-syscalls` can reach this graph only as a git +# dep of `ethrex-guest-program`'s `lambdavm` feature, which this manifest does not enable +# — the lockfile carries no `git+https://github.com/yetanotherco/lambda_vm.git` source at +# all, so this patch currently matches nothing and cargo does not warn about it. It stays +# because the hazard it covers is real and silent: a future ethrex rev that pulls those +# syscalls from git would link a different copy than the rest of the repo, and the guest +# is the one binary where that divergence matters. [patch."https://github.com/yetanotherco/lambda_vm.git"] lambda-vm-syscalls = { path = "../../../../syscalls" } From 32024ae74dd1bf94c81dc38eabdfcfb12c733283 Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Fri, 11 Sep 2026 11:12:02 -0300 Subject: [PATCH 24/27] Keep the EEST probe out of the tree until its workload lands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nothing in the repository produces `eest_amsterdam_bins` — no make target, no workflow, no ignore entry — so both tests took their early return and passed on every checkout: dead test code by construction. The inputs are lifted by hand out of an execution-specs release tarball with a script that is not in the repository either. It comes back together with the EEST workload, and with the step that produces its inputs, so that the tests it adds actually run. Its two tests need splitting first: the positive scan took every `.bin` in the directory and required each to validate, including the legacy rkyv fixture that the second test requires to be REJECTED, so supplying that fixture would have made the first test fail. --- .../tests/eest_amsterdam_probe.rs | 93 ------------------- 1 file changed, 93 deletions(-) delete mode 100644 tooling/ethrex-tests/tests/eest_amsterdam_probe.rs diff --git a/tooling/ethrex-tests/tests/eest_amsterdam_probe.rs b/tooling/ethrex-tests/tests/eest_amsterdam_probe.rs deleted file mode 100644 index 7a9c1c150..000000000 --- a/tooling/ethrex-tests/tests/eest_amsterdam_probe.rs +++ /dev/null @@ -1,93 +0,0 @@ -//! Probe: do the upstream EEST Amsterdam benchmark fixtures execute on the -//! guest this rev pins? Committed as the measuring tool for adding an EEST -//! workload alongside the real-mix one; its inputs are not committed, so on a -//! plain checkout it skips (see below) and costs CI nothing. -//! -//! Inputs are `statelessInputBytes` lifted verbatim out of -//! `tests-zkevm-benchmark@v0.8.2` blockchain tests, so nothing is rebuilt host -//! side. Run with: -//! cargo test --manifest-path tooling/ethrex-tests/Cargo.toml --release \ -//! --test eest_amsterdam_probe -- --nocapture - -use ethrex_guest_program::crypto::NativeCrypto; -use ethrex_guest_program::l1::run_stateless_guest; -use std::sync::Arc; - -const BIN_DIR: &str = "../../eest_amsterdam_bins"; - -#[test] -fn eest_amsterdam_fixtures_validate_natively() { - // The .bin files are downloaded/generated artifacts, not committed: a checkout - // without them skips rather than fails, so this probe can live in the tree. - if !std::path::Path::new(BIN_DIR).is_dir() { - println!("{BIN_DIR} absent — skipping"); - return; - } - let mut entries: Vec<_> = std::fs::read_dir(BIN_DIR) - .expect("bin dir") - .filter_map(|e| e.ok().map(|e| e.path())) - .filter(|p| p.extension().is_some_and(|x| x == "bin")) - .collect(); - entries.sort(); - assert!(!entries.is_empty(), "no .bin found in {BIN_DIR}"); - - for path in entries { - let inputs = std::fs::read(&path).expect("read fixture"); - let name = path.file_name().unwrap().to_string_lossy(); - // Every field below is read by index, so the shapes are checked first: a short - // file or a short output should fail with the message that says which, not with - // a slice-index panic. `chars()` because a byte slice of a `str` would panic on - // a multi-byte boundary in a non-ASCII filename. - // 2 = the big-endian schema-id prefix. Spelled out rather than imported: the - // constant lives in `ethrex-common`, which this crate does not depend on. - assert!( - inputs.len() >= 2, - "{name}: file is shorter than the schema-id prefix" - ); - let schema_id = u16::from_be_bytes([inputs[0], inputs[1]]); - let started = std::time::Instant::now(); - let output = run_stateless_guest(&inputs, Arc::new(NativeCrypto)); - let elapsed = started.elapsed(); - assert_eq!(output.len(), 43, "{name}: unexpected output length"); - println!( - "{:<60} in={:>9} B schema={:#06x} out={} B ok={} chain_id={} native={:?}", - name.chars().take(60).collect::(), - inputs.len(), - schema_id, - output.len(), - output[32], - u64::from_le_bytes(output[33..41].try_into().unwrap()), - elapsed, - ); - assert_eq!(output[32], 1, "{name}: native stateless validation failed"); - } -} - -/// What the retired real-block fixture does on this branch's guest: the rkyv -/// archive's first two bytes are not the Amsterdam schema id, so the input is -/// rejected and the guest commits an all-zero result. It does NOT error out — -/// which is exactly why a stale fixture would silently benchmark nothing. -/// -/// Reads a copy kept outside `executor/tests`, since that directory now holds the -/// regenerated (valid) fixture for the same block. Skips when the copy is absent. -#[test] -fn legacy_rkyv_real_block_is_rejected_silently() { - let path = format!("{BIN_DIR}/legacy_rkyv_real_block.bin"); - if !std::path::Path::new(&path).is_file() { - println!("{path} absent — skipping"); - return; - } - let inputs = std::fs::read(&path).expect("legacy fixture"); - let output = run_stateless_guest(&inputs, Arc::new(NativeCrypto)); - println!( - "legacy fixture: {} B, first two bytes {:#04x}{:02x}, output {} B, ok={}, all_zero={}", - inputs.len(), - inputs[0], - inputs[1], - output.len(), - output[32], - output.iter().all(|b| *b == 0), - ); - assert_eq!(output.len(), 43); - assert_eq!(output[32], 0, "legacy input must not validate"); -} From a5d4c740e59e054b1b52ee24923a6a886bba6dad Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Fri, 11 Sep 2026 11:12:10 -0300 Subject: [PATCH 25/27] Say what the converter's tests actually cover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SSZ rewrite deleted the two tests that exercised conversion and left two that only assert a rejection, but three comments still described the old ones: the Makefile target promised "host-side parity through the guest's own Crypto impl ... and the reproducibility digest", the rev-stamp comment warned about a `conversion_is_reproducible` failure that can no longer happen, and the crate's own comment claimed the tests exercise the conversion itself. What can be reached today is the two rejection paths; the success path needs a cache from a network running Amsterdam, carrying `slot_number` and `block_access_list_hash`, and none is published. The comments say that now, including that the encoder is exercised by hand and by nothing automatic. Two leftovers in the same crate: the README still described the benchmark fixture as a fetched release artifact with an SSZ artifact to publish, which this branch replaced with a generator, and dropping the unused dependencies left their justifications behind — eleven lines explaining a `k256` entry that is gone, and an empty `[dev-dependencies]` holding only comments about two deleted ones. --- Makefile | 29 +++++++++++--------- tooling/ethrex-block-converter/Cargo.toml | 18 ------------- tooling/ethrex-block-converter/README.md | 32 +++++++++++------------ 3 files changed, 33 insertions(+), 46 deletions(-) diff --git a/Makefile b/Makefile index 2dfc12277..5dc403512 100644 --- a/Makefile +++ b/Makefile @@ -471,23 +471,27 @@ print-real-block-cache-url: # path builds this crate. # # Its TEST input is pinned to Hoodi 1265656, independently of whichever block the -# benchmarks currently prove, and stays there across a repoint. What these tests -# exercise is the CONVERSION — cache JSON in, correctly serialized SSZ out — which -# any real block demonstrates equally well. Hoodi's is the one cache ethrex-replay -# publishes, so pinning there costs us no hosting, cannot drift, and leaves the -# benchmark block free to change without touching this crate. +# benchmarks currently prove, and stays there across a repoint. Hoodi's is the one +# cache ethrex-replay publishes, so pinning there costs us no hosting, cannot drift, +# and leaves the benchmark block free to change without touching this crate. +# +# What those tests reach is only the REJECTION paths: a pre-Amsterdam cache, and an +# unmappable network. The conversion's success path cannot be covered yet — it needs a +# cache carrying `slot_number` and `block_access_list_hash`, and no published cache has +# them until a network runs Amsterdam. Until one does, this crate's SSZ encoder is +# exercised by hand (`cargo run`) and by nothing automatic. # # Pinned by immutable `rev`, as the guest pins ethrex itself: a branch ref would let -# the converter's reproducibility digest drift under a fixed input. +# the test input move under a fixed set of assertions. ETHREX_REPLAY_REV := 2693e0182a8734117151d8ea2891eda5afc60383 ETHREX_CONVERTER_TEST_BLOCK := hoodi_1265656 ETHREX_CONVERTER_CACHE := tooling/ethrex-block-converter/caches/cache_$(ETHREX_CONVERTER_TEST_BLOCK).json # The cache filename is keyed on the block only, and its download rule has no other # prerequisite, so make would treat an already-present cache as up to date across an # `ETHREX_REPLAY_REV` bump and silently keep reading the old input. Depending on a -# rev-stamped marker makes a re-pin discard the stale cache; without it the mismatch -# only surfaces downstream as a `conversion_is_reproducible` digest failure, which -# reads as "regenerate the fixture" and points at the wrong thing. +# rev-stamped marker makes a re-pin discard the stale cache. Nothing downstream would +# catch it otherwise: the two tests that remain assert that a cache is REJECTED, and a +# stale cache satisfies that just as well as the right one. ETHREX_REPLAY_REV_STAMP := tooling/ethrex-block-converter/caches/.replay-rev-$(ETHREX_REPLAY_REV) $(ETHREX_REPLAY_REV_STAMP): @@ -503,9 +507,10 @@ $(ETHREX_CONVERTER_CACHE): $(ETHREX_REPLAY_REV_STAMP) ethrex-real-block-converter-cache: $(ETHREX_CONVERTER_CACHE) -# Converter correctness: host-side parity through the guest's own Crypto impl, the -# network-rejection guard, and the reproducibility digest. Runs on changes to the -# converter (see .github/workflows/ethrex-block-converter.yml), not on every PR. +# Converter correctness, as far as it is testable today: a pre-Amsterdam cache and an +# unmappable network are both rejected. The success path needs an Amsterdam cache — +# see the comment above. Runs on changes to the converter +# (see .github/workflows/ethrex-block-converter.yml), not on every PR. test-ethrex-real-block-converter: $(ETHREX_CONVERTER_CACHE) cd tooling/ethrex-block-converter && cargo test --locked --release diff --git a/tooling/ethrex-block-converter/Cargo.toml b/tooling/ethrex-block-converter/Cargo.toml index 7baf86e09..877a3087f 100644 --- a/tooling/ethrex-block-converter/Cargo.toml +++ b/tooling/ethrex-block-converter/Cargo.toml @@ -34,21 +34,3 @@ libssz = "=0.3.0" libssz-types = "=0.3.0" serde = { version = "1", features = ["derive"] } serde_json = "1" - -# Not used by this crate's code directly — declared to pin k256's feature set. -# `lambda-vm-ethrex-crypto` (a dev-dependency) is otherwise the only thing asking -# for `expose-field`, and under resolver 3 dev-dependency features are not unified -# into builds that exclude dev-dependencies. This crate is built both ways against -# one target dir: `cargo run` for `make regen-real-block-fixture`, `cargo test` for -# `make test-ethrex-real-block-converter`. Without this line the two resolve -# different k256 feature sets, so alternating between them recompiles k256 and every -# ethrex crate above it. Declaring the features here puts them in both resolutions. -# Keep in sync with crypto/ethrex-crypto's k256 line; verify: -# cargo tree -e features,no-dev -i k256 # must list expose-field -# cargo tree -e features -i k256 # must match the above - -[dev-dependencies] -# The LambdaVM `Crypto` implementation used by host-side parity tests. Off -# riscv64 its keccak and ECSM paths both fall back to software. -# No feature delta against the rest of the graph (sha2/std is already on), so this -# one can stay a dev-dependency without forcing the rebuild described above. diff --git a/tooling/ethrex-block-converter/README.md b/tooling/ethrex-block-converter/README.md index 2f6d766a2..5f3197ea7 100644 --- a/tooling/ethrex-block-converter/README.md +++ b/tooling/ethrex-block-converter/README.md @@ -38,25 +38,25 @@ coupled to the ethrex revision. ## Real-block fixture -The benchmark fixture is a fetched release artifact, not a build dependency: - -```bash -make ethrex-real-block-fixture -``` - -After an ethrex revision bump, regenerate it from the matching cache: +The benchmark fixture is generated rather than fetched, and not by this crate: +`tooling/ethrex-fixtures --bin real_block` rebuilds a real mainnet block as an +Amsterdam block. What is hosted is that block's replay cache, which is +fork-independent. `make ethrex-real-block-fixture` produces the fixture when it is +missing; after an ethrex revision bump, rebuild it and re-baseline its digest: ```bash make regen-real-block-fixture -sha256sum "$(make -s print-real-block-fixture)" +sha256sum "$(make -s print-real-block-fixture)" # -> ETHREX_REAL_BLOCK_FIXTURE_SHA256 ``` -Publish the resulting SSZ artifact and matching Amsterdam cache, then update -their URLs and checksums in the Makefile before enabling the real-block -benchmark. The old release assets are rkyv `ProgramInput` artifacts and are -incompatible with the pinned ethrex. +There is no artifact left to publish: the generator is deterministic, so re-running +it is how the fixture is replaced. The old release assets are rkyv `ProgramInput` +artifacts and are incompatible with the pinned ethrex. + +## What the tests cover -The converter's tests use the Hoodi cache under `caches/` and verify that an -unmapped network is rejected. The checked release cache predates Amsterdam, so -it is intentionally rejected until a cache containing the new payload fields -is published. +Both tests assert a rejection. The pinned Hoodi cache predates Amsterdam and is +refused rather than rewritten, and an unmappable network is refused too. The +success path — cache in, valid SSZ out — has no automated coverage: it needs a +cache from a network that runs Amsterdam, and none is published yet. Until one is, +the encoder is exercised by hand through the `cargo run` above. From acacd85bf73bb71dc5b56d0c65c6a49c65c3c5bd Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Fri, 11 Sep 2026 11:12:10 -0300 Subject: [PATCH 26/27] Re-pin the last epoch-sizing number in perf_diff.sh The comment on `EPOCH_SIZE_LOG2` still sized the epoch against the retired workload's ~52 GB peak, while the header of the same file already carried the rebuilt one. It is 44.81 GiB at 2^22 on that runner and 2^23 crosses 50 GiB, which is what benchmark-pr.yml and bench-abba.yml already say. --- scripts/perf_diff.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/perf_diff.sh b/scripts/perf_diff.sh index ee4a01c37..8e3150761 100755 --- a/scripts/perf_diff.sh +++ b/scripts/perf_diff.sh @@ -52,8 +52,9 @@ REF_A="$1" REF_B="${2:-origin/main}" WORKLOAD="${WORKLOAD:-real}" # 2^22: the calibrated tier for the bench server this script targets, same as -# /bench's real-block arm. Memory picks it, not speed — that server peaks at ~52 GB on -# a >=64 GiB floor, and 2^23 measured 60 GiB on a roomier box, so it would not fit here. +# /bench's real-block arm. Memory picks it, not speed — this workload peaks at 44.81 GiB +# there against a >=64 GiB floor, and 2^23 would take it past 50 GiB for ~7% of wall +# (the epoch sweep is in tooling/ethrex-fixtures/README.md). EPOCH_SIZE_LOG2="${EPOCH_SIZE_LOG2:-22}" case "$WORKLOAD" in synthetic|real) ;; From 1ca1030bc9420c6aeb55c390f8000d45dd1c3bf1 Mon Sep 17 00:00:00 2001 From: jotabulacios Date: Fri, 11 Sep 2026 11:12:10 -0300 Subject: [PATCH 27/27] Drop the copy note that the shared module made false MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `build_stateless_input` and its helpers now live in `src/lib.rs` and this binary imports them, so the note saying they are copied from `main.rs` — and that lifting them into a module was something "a real PR" should do — describes the tree before that change. --- tooling/ethrex-fixtures/src/bin/real_block.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tooling/ethrex-fixtures/src/bin/real_block.rs b/tooling/ethrex-fixtures/src/bin/real_block.rs index 5f382cdb8..081a70000 100644 --- a/tooling/ethrex-fixtures/src/bin/real_block.rs +++ b/tooling/ethrex-fixtures/src/bin/real_block.rs @@ -17,10 +17,6 @@ //! //! Usage: //! cargo run --release --bin real_block -- -//! -//! NOTE: `build_stateless_input` and its helpers are copied from `main.rs` -//! rather than shared. A real PR should lift them into a module; keeping this -//! binary self-contained leaves the committed generator untouched. use bytes::Bytes; use ethrex_blockchain::payload::{BuildPayloadArgs, create_payload};