Date: 2026-08-31
Block: 115260 (glamsterdam-devnet-7, chainId 7082904758)
Guest: stateless_guest (EvmAsm, Linux93 halt)
Emulator: ZisK ziskemu v1.2.0-alpha (fbbc69b, 2026-08-26)
| Field | Value |
|---|---|
| Network | glamsterdam-devnet-7 |
| Chain ID | 7082904758 |
| Block number | 115260 |
| Gas used | 65,318,413 |
statelessInputBytes size |
568,669 bytes |
statelessOutputBytes size |
69 bytes |
| Fixture archive | 115260-115269.tar.zst |
Expected output (statelessOutputBytes, hex):
7734570c97a937506b9b771b328a2e5bdb8b74af65c54c747603e4b3d1e8d7ce
0125000000b68c2ca6010000000c0000000400000008000000080000000000000000000000
(69 bytes: 32-byte post-state root || 37-byte StatelessValidationResult)
- Lean 4 + Lake (for building the guest ELF)
riscv64-elf-ld(RISC-V cross-linker)ziskemuv1.2.0+ at~/.zisk/bin/ziskemu- Python 3 (for fixture parsing)
zstd(for decompressing the archive)
# Inside the evm-asm repo
lake exe codegen --program stateless_guest --halt linux93 -o gen-out/stateless_guestThis produces gen-out/stateless_guest.o.
ZisK 1.2.0 rejects any ELF whose PT_LOAD segments fall below 0x80000000 (ROM
start). GNU ld places ELF program headers at 0x7ffff000 when text is at
0x80000000, which triggers the rejection. Fix with an explicit PHDRS block:
# /tmp/zisk_clean.ld
PHDRS {
text PT_LOAD FLAGS(5);
data PT_LOAD FLAGS(6);
bss PT_LOAD FLAGS(6);
rlp PT_LOAD FLAGS(6);
ssz PT_LOAD FLAGS(6);
}
SECTIONS {
. = 0x80000000;
.text : { *(.text .text.*) *(.rodata .rodata.*) } :text
. = 0xa0b00000;
.data : { *(.data .data.*) *(.sdata .sdata.*) } :data
. = 0xa0b70000;
.bss (NOLOAD) : { *(.bss .bss.*) *(COMMON) } :bss
. = 0xbf5e2000;
.rlp_recursive_frame (NOLOAD) : { *(.rlp_recursive_frame) } :rlp
. = 0xbf980000;
.sszscratch (NOLOAD) : { *(.sszscratch) } :ssz
/DISCARD/ : { *(.riscv.attributes) *(.note.*) *(.comment) }
}
riscv64-elf-ld -T /tmp/zisk_clean.ld -nostdlib --no-relax \
-o gen-out/stateless_guest_clean.elf gen-out/stateless_guest.oThe resulting ELF starts at 0x80000000 and is accepted by ZisK 1.2.0.
Note: The first PT_LOAD segment is still
PF_X | PF_R; ZisK warns thatPF_X(execute-and-not-read) would give maximum performance. This affects proving performance but not emulation correctness.
The fixture's statelessInputBytes field is a schema-prefixed SSZ blob.
ZisK's input convention requires the blob to be prefixed with an 8-byte
little-endian length word and zero-padded to 8-byte alignment.
#!/usr/bin/env python3
import json, struct, pathlib, tarfile, io
# Extract the fixture
archive = pathlib.Path("115260-115269.tar.zst")
# (requires zstd support; use: tar -I zstd -xf 115260-115269.tar.zst)
with tarfile.open(archive, "r:zst") as tf:
# Find the block 115260 fixture
names = [n for n in tf.getnames() if "115260" in n and n.endswith(".json")]
fixture_name = names[0]
data = json.loads(tf.extractfile(fixture_name).read())
# Navigate to statelessInputBytes (schema: {network: {block: {_info, statelessInputBytes, ...}}})
network = next(iter(data.values()))
block = next(iter(network.values()))
hex_input = block["statelessInputBytes"]
blob = bytes.fromhex(hex_input[2:] if hex_input.startswith("0x") else hex_input)
# Pack: 8-byte LE length prefix + blob + padding
length_prefix = struct.pack("<Q", len(blob))
payload = length_prefix + blob
padded = payload + b"\x00" * ((8 - len(payload) % 8) % 8)
out = pathlib.Path("block115260.input")
out.write_bytes(padded)
print(f"blob={len(blob)} bytes, packed={len(padded)} bytes -> {out}")Expected output: blob=568669 bytes, packed=568680 bytes -> block115260.input
~/.zisk/bin/ziskemu \
-e gen-out/stateless_guest_clean.elf \
-i block115260.input \
-o block115260.output \
-m -X --sdk -SFlag meanings:
-eELF binary (the guest)-iinput file (packed blob)-ooutput file (where the guest writes its result)-mlog metrics (steps, cost)-Xextended stats (cost distribution by category)--sdkSDK-style cost report-Sload ELF symbols for symbol-annotated traces
| Metric | Value |
|---|---|
| Steps | 4,345,958,375 |
| ZisK cost | 676,878,588,802 |
| Wall-clock (emulation) | ~142.5 s |
| Throughput | ~30.5 Msteps/s |
| Category | Cost | Share |
|---|---|---|
| Main (EVM interpreter loop) | 295,525,169,500 | 43.7% |
| Precompiles (keccak256, etc.) | 276,199,796,808 | 40.8% |
| Memory | 67,278,850,968 | 9.9% |
| Opcodes | 37,587,461,702 | 5.6% |
| Base | 287,309,824 | 0.0% |
| Total | 676,878,588,802 | 100% |
The dominant cost is EVM execution + keccak precompile (together >84%), which is consistent with the block's 65 Mgas workload and keccak-heavy witness hashing.
╔══════════════════════════════════════════════════════════════╗
║ ◆ REPORT SUMMARY ║
╠══════════════════════════════════════════════════════════════╣
║ STEPS 4,345,958,375 ║
║ COST 676,878,588,802 ║
║ RAM 0.00 MB / 0.00 MB ║
╚══════════════════════════════════════════════════════════════╝
╔══════════════════════════════════════════════════════════════╗
║ ◆ COST DISTRIBUTION SUMMARY ║
╠══════════════════════════════════════════════════════════════╣
║ Base ░░░░░░░░░░░░░ 287,309,824 0.0% ║
║ Main ██████████████ 295,525,169,500 43.7% ║
║ Opcodes ████░░░░░░░░░░ 37,587,461,702 5.6% ║
║ Precompiles █████████████░ 276,199,796,808 40.8% ║
║ Memory ████░░░░░░░░░░ 67,278,850,968 9.9% ║
╚══════════════════════════════════════════════════════════════╝
process_rom() steps=4345958375 duration=142.5259 tp=30.4924 Msteps/s
The guest writes its 69-byte StatelessValidationResult to OUTPUT_ADDR = 0xa0010000. In ZisK v1.2.0-alpha, the emulator's -o file is populated with
256 bytes of zeros regardless of what the guest writes to RAM. This appears to
be a change in the output-file convention between ZisK versions (the project
targets ZisK 1.2.0, which may expect a different output-flush mechanism).
The execution still runs to completion (all 4.35B steps), so the performance
metrics above are valid.
Ress is reth's stateless execution mode. According to Ethereum Foundation zkEVM benchmarking work (Benchmarking zkVMs for Ethereum), Ress achieves P99 < 1 s block validation latency on Holesky — running natively on the host CPU with state access replaced by witness data.
Ethrex participates in
benchmarkoor and has been validated
on all glamsterdam devnets. Stateless execution times are measured through the
zkevm-benchmark-workload
harness. Typical native (non-zkVM) stateless validation is in the
tens-of-milliseconds range for similar gas loads.
| Layer | Value |
|---|---|
| Emulation (this run) | ~142 s |
| Estimated proving overhead | 1.3–1.5× vs execution-only (from zkEVM benchmark data) |
| Estimated total proving time | ~200–250 s (single machine, no parallelism) |
The ZisK cost figure (676 B) is the metric the ZisK prover uses to schedule parallel proving segments. Proving time scales roughly linearly with cost and depends heavily on hardware. With ZisK's segmented parallel prover and sufficient GPUs, proving latency is expected to drop well below wall-clock emulation time.
| Client | Mode | Latency |
|---|---|---|
| Reth (Ress) | Native stateless | P99 < 1 s |
| Ethrex | Native stateless | O(10 ms) typical |
| EvmAsm / ZisK | zkVM emulation | ~142 s (emulation), ~200–250 s (estimated proof) |
The zkVM path is 2–3 orders of magnitude slower than native stateless execution, but produces a succinct cryptographic proof of correct block execution — the tradeoff is proof overhead for verifiability.
| File | Description |
|---|---|
115260-115269.tar.zst |
Source fixture archive |
gen-out/stateless_guest.o |
Codegen output (RISC-V object) |
gen-out/stateless_guest_clean.elf |
Re-linked ELF (ZisK 1.2.0 compatible) |
/tmp/zisk_clean.ld |
Custom linker script (explicit PHDRS) |
block115260.input |
Packed ziskemu input (568,680 bytes) |
block115260.output |
ziskemu output (all zeros — see known issue) |