diff --git a/.github/workflows/zilkworm-benchmark.yml b/.github/workflows/zilkworm-benchmark.yml index b07113f97..9229efb36 100644 --- a/.github/workflows/zilkworm-benchmark.yml +++ b/.github/workflows/zilkworm-benchmark.yml @@ -21,7 +21,7 @@ on: type: string required: false description: Git ref of axiom-crypto/zilkworm-stateless to build the guest from - default: feat/openvm-rv64 + default: feat/openvm-pairing input_source: type: string required: false @@ -104,7 +104,7 @@ on: type: string required: false description: Git ref of axiom-crypto/zilkworm-stateless to build the guest from - default: feat/openvm-rv64 + default: feat/openvm-pairing input_source: type: string required: false diff --git a/bin/zilkworm-benchmark/README.md b/bin/zilkworm-benchmark/README.md index 87dee0b94..628b34387 100644 --- a/bin/zilkworm-benchmark/README.md +++ b/bin/zilkworm-benchmark/README.md @@ -3,19 +3,20 @@ Benchmark host for [Zilkworm](https://github.com/eth-act/zilkworm-stateless) (mirrored at [axiom-crypto/zilkworm-stateless](https://github.com/axiom-crypto/zilkworm-stateless)), a bare-metal C++ stateless Ethereum block validator compiled to an RV64IM ELF. The guest runs -on OpenVM's RV64 target (`rv64i + rv64m + io + keccak + sha2` extensions) and -goes through the same execute / prove pipeline as the Reth benchmark. +on OpenVM's RV64 target (`rv64i + rv64m + io + keccak + sha2` plus the +modular / Fp2 / short-Weierstrass / pairing extensions) and goes through the +same execute / prove pipeline as the Reth benchmark. ## Guest -Build the guest ELF from the `feat/openvm-rv64` branch of +Build the guest ELF from the `feat/openvm-pairing` branch of `axiom-crypto/zilkworm-stateless` (requires cmake >= 3.28 and the xPack `riscv-none-elf-gcc` toolchain, see that repo's README): ```bash git clone https://github.com/axiom-crypto/zilkworm-stateless.git cd zilkworm-stateless -git checkout feat/openvm-rv64 +git checkout feat/openvm-pairing make guest_openvm # produces build/openvm/z6m_guest.elf ``` diff --git a/bin/zilkworm-benchmark/openvm.toml b/bin/zilkworm-benchmark/openvm.toml deleted file mode 100644 index 8d8e6d7ce..000000000 --- a/bin/zilkworm-benchmark/openvm.toml +++ /dev/null @@ -1,50 +0,0 @@ -# VM extension config for the Zilkworm RV64IM guest. -# -# The order of supported_moduli / fp2 moduli / ecc curves is ABI: it defines -# the funct7 indices of the modular / Fp2 / short-Weierstrass custom -# instructions the guest emits. Keep in sync with zilkworm-stateless's -# zkvm/openvm/src/include/openvm_ecc.hpp (MOD_*/FP2_*/CURVE_* constants). - -[app_vm_config.rv64i] -[app_vm_config.rv64m] -[app_vm_config.io] - -[app_vm_config.keccak] -[app_vm_config.sha2] - -[app_vm_config.modular] -supported_moduli = [ - # 0: secp256k1 coordinate field - "115792089237316195423570985008687907853269984665640564039457584007908834671663", - # 1: secp256k1 scalar field - "115792089237316195423570985008687907852837564279074904382605163141518161494337", - # 2: bn254 (alt bn128) coordinate field - "21888242871839275222246405745257275088696311157297823662689037894645226208583", - # 3: bn254 scalar field - "21888242871839275222246405745257275088548364400416034343698204186575808495617", -] - -[app_vm_config.fp2] -supported_moduli = [ - # 0: bn254 Fp2 - [ - "Bn254Fp2", - "21888242871839275222246405745257275088696311157297823662689037894645226208583", - ], -] - -# 0: secp256k1 -[[app_vm_config.ecc.supported_curves]] -struct_name = "Secp256k1Point" -modulus = "115792089237316195423570985008687907853269984665640564039457584007908834671663" -scalar = "115792089237316195423570985008687907852837564279074904382605163141518161494337" -a = "0" -b = "7" - -# 1: bn254 G1 -[[app_vm_config.ecc.supported_curves]] -struct_name = "Bn254G1Affine" -modulus = "21888242871839275222246405745257275088696311157297823662689037894645226208583" -scalar = "21888242871839275222246405745257275088548364400416034343698204186575808495617" -a = "0" -b = "3" diff --git a/bin/zilkworm-benchmark/src/lib.rs b/bin/zilkworm-benchmark/src/lib.rs index c3602e75f..7d2eddd02 100644 --- a/bin/zilkworm-benchmark/src/lib.rs +++ b/bin/zilkworm-benchmark/src/lib.rs @@ -48,10 +48,12 @@ use tracing::{info, info_span}; const VM_MAX_CONSTRAINT_DEGREE: usize = 4; -/// Capacity of the user public-values address space in bytes. Must be at -/// least `z6m::MAX_PUBLIC_VALUES_SIZE` (256) so the guest's largest SSZ -/// `StatelessValidationResult` fits. -const PUBLIC_VALUES_BYTES: usize = 256; +/// Capacity of the user public-values address space in bytes. The guest's SSZ +/// `StatelessValidationResult` is 105 bytes for mainnet blocks (root[32] || +/// success[1] || offset[4] || chain_config), so 128 is the smallest power of +/// two that fits it. (z6m's MAX_PUBLIC_VALUES_SIZE buffer is 256, but only the +/// encoded prefix is revealed.) +const PUBLIC_VALUES_BYTES: usize = 128; /// Enum representing the execution mode of the host executable. #[derive(Debug, Clone, clap::ValueEnum)] @@ -108,6 +110,23 @@ pub struct BenchmarkCli { /// Estimated proving-memory cap per VM segment, in bytes #[arg(long, default_value_t = DEFAULT_MAX_MEMORY)] pub segment_max_memory: usize, + + /// Whether the proving-memory model assumes the Reed-Solomon code matrix + /// stays resident after `stacked_commit`. + /// + /// This is a segmentation *setting*, not a backend detail, and the two + /// engines disagree on it: the CPU engine passes `true` + /// (stark-backend `Engine::proving_memory_config`) while the CUDA engine + /// uses `GpuProverConfig::default()`, which is `false`. Caching leaves + /// less of `--segment-max-memory` for trace, so the same guest segments + /// differently: block 24001988 gives 119 segments under the CPU model and + /// 79 under the GPU model. + /// + /// Defaults to the GPU model so local `execute-metered` estimates match + /// the GPU benchmark runs; pass `--cache-rs-code-matrix` to model a CPU + /// prover instead. + #[arg(long, default_value_t = false)] + pub cache_rs_code_matrix: bool, } /// The arguments for the host executable. @@ -158,16 +177,21 @@ pub struct HostArgs { pub agg_pk_path: Option, } -/// VM extension config matching the instructions the Zilkworm guest issues: -/// RV64IM + hint/reveal IO, the keccak-f[1600] and SHA-256 accelerators, and -/// the modular / Fp2 / short-Weierstrass extensions used by the guest's ECC -/// precompile hooks (secp256k1 ecrecover, bn254 field + curve ops). The -/// moduli/curve order in openvm.toml is ABI — it must match the funct7 -/// indices in zilkworm's zkvm/openvm/src/include/openvm_ecc.hpp. +/// VM extension config for the Zilkworm guest. +/// +/// Uses `SdkVmConfig::standard()` — byte-for-byte the same extension set the +/// Reth benchmark host uses (`reth_vm_config`) — so the two guests are proven +/// on an identical circuit and their segment counts are directly comparable. +/// `standard()` also fixes the modulus / Fp2 / curve ordering that the guest's +/// funct7 indices encode (see zilkworm's zkvm/openvm/src/include/openvm_ecc.hpp), +/// and it enables the Int256 (`bigint`) extension. +/// +/// The one deliberate difference from Reth: public-value capacity. Reth reveals +/// a 32-byte block hash and configures 32 cells (64 bytes); the Zilkworm guest +/// reveals the SSZ `StatelessValidationResult`, which is 105 bytes for mainnet +/// blocks, so it needs 64 cells (128 bytes). pub fn zilkworm_vm_config() -> SdkVmConfig { - let mut config = SdkVmConfig::from_toml(include_str!("../openvm.toml")) - .expect("invalid embedded openvm.toml") - .optimize(); + let mut config = SdkVmConfig::standard(); config.system.config = config .system .config @@ -374,6 +398,7 @@ pub fn run_zilkworm_benchmark(args: HostArgs) -> Result<()> { }); let program_name = format!("zilkworm.{}.{}", args.mode, label); + let cache_rs_code_matrix = args.benchmark.cache_rs_code_matrix; let stdin: StdIn = vec![input_bytes].into(); run_with_metric_collection("OUTPUT_PATH", move || { @@ -389,7 +414,8 @@ pub fn run_zilkworm_benchmark(args: HostArgs) -> Result<()> { } } BenchMode::ExecuteMetered => { - let compiled = sdk.compile_metered(exe)?; + let mut compiled = sdk.compile_metered(exe)?; + compiled.ctx.set_cache_rs_code_matrix(cache_rs_code_matrix); let (public_values, segments) = sdk.execute_metered(&compiled, stdin)?; info!("Execute metered completed, {} segments", segments.len()); report_public_values(&public_values);