Skip to content

Feat/hint arena - #942

Open
diegokingston wants to merge 17 commits into
chore/bump-ethrex-revfrom
feat/hint-arena
Open

Feat/hint arena#942
diegokingston wants to merge 17 commits into
chore/bump-ethrex-revfrom
feat/hint-arena

Conversation

@diegokingston

@diegokingston diegokingston commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Hints move from an ecall served by a dedicated AIR chip to plain bytes in the private-input region, and the executor answers them during the run instead of in a separate pass.

The private-input region is already data the prover chooses and the verifier never recomputes, so hints can live there and the guest reads them with ordinary loads — there is no host write appearing mid-trace, and nothing to justify. The catch is that the initial image is fixed before cycle 0, which would mean executing the guest once to learn which hints it asks for and again to prove. Instead, the guest publishes each request through ordinary stores and bumping the count word is the executor's cue: it computes the answer and seeds the slot as an initial value, for a cell nothing has touched, so it lands in the image as if it had been there since cycle 0 — SP1's mw_hint. Seeding a written cell or an out-of-order request is an error, since the memory argument requires a read after a write to return the write. resolve_hints and the second execution are gone.

The reason to prefer this over the ecall is not speed. An ecall adds a mechanism: a write that has to be explained, so a table has to exist, and be argued about in every review and every audit from here on. The arena adds nothing — it reuses a degree of freedom the system already grants, since private-input bytes are already chosen by the prover and already never recomputed by the verifier. Hints stop being a special kind of data and become the kind the system already handles. And a table is not paid for once by whoever proves: it is checked by everyone who verifies that proof afterwards, including the verifier we run inside the VM.

What goes away is 41 columns and 373 lines of AIR, and with them two obligations that had to be held every time the chip was reviewed: it had to bind the destination address, or the witness chooses where the 32 bytes land, and mu had to be a bit, which the Ecall bus alone does not establish since it only fixes the sum over rows sharing a tuple. The arena adds no AIR obligation in exchange — the bytes are private input, which the system already treats as prover-chosen — and HNT-A1 is unchanged either way.

Prove time is a tie over 12 interleaved pairs on a real block. The in-VM recursion verifier, three runs per side on a real 20-tx block at blowup 2 / 219 queries: 2,216,446,035 -> 2,188,339,913 cycles, -28,106,122 (-1.268%).

The non-constraining hint ecall (and its prover HINT table) is replaced
by untrusted 32-byte hint slots appended to the private-input region:

  [u32 len][data][pad8][u32 hint_count][u32 pad][32-byte slots]

Guests consume slots positionally (syscalls: hint_count/hint_slot/
next_hint/request_hint) and must still verify each hint in-circuit,
falling back to software on failure or arena exhaustion. A lying host
can only force fallbacks, never change the result. When hints are not
known beforehand, request_hint appends (hint_id, input) to a request
log above the private-input window; the host reads it back
(Memory::hint_requests / ExecutionResult::hint_requests), answers with
compute_hint, and re-runs with a complete arena
(executor::collect_hints — the two-pass flow).

- executor: encode_private_input_region (single source of truth for
  the wire format), hints threaded through Executor::new, ecall
  dispatch and HINT_* error variants removed
- prover: HINT table and its trace-builder/CPU plumbing removed;
  hints threaded through prove_*/count_*/Traces::from_*
- ethrex-crypto: field_inv/scalar_inv/decompress_r read from the
  arena (verify + software fallback kept)
- cli: --hints on execute/prove/count-elements, execute
  --record-hints for the recording pass
- guests: hint_arena (slot API) and ecrecover_hints (N recoveries);
  two-pass measurement drivers on the executor and prover
  (continuation) sides

Measured on 30 ecrecovers: 6.41M guest cycles with software fallback
vs 866k with arena hints (7.40x); continuation proof of the hinted
run verifies end-to-end (14 epochs @ 2^16).
@diegokingston

Copy link
Copy Markdown
Collaborator Author

/bench

@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown

Benchmark — real block (ethrex_mainnet_25368371.bin) (median of 3)

continuations · epoch 2^22 · 8 epochs

Metric main PR Δ
Peak heap 47352 MB 47332 MB -20 MB (+-0.0%) ⚪
Prove time 137.700s 110.240s -27.460s (-19.9%) 🟢

🎉 Improvement on the real block — prove time down 19.9%.

Prove-time spread 1.2% (110.240s / 109.242s / 110.537s)

Commit: 1a61b19 · Baseline: cached · Runner: self-hosted bench

@diegokingston
diegokingston changed the base branch from main to chore/bump-ethrex-rev August 19, 2026 20:29
@diegokingston

Copy link
Copy Markdown
Collaborator Author

/bench

@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown

Benchmark Results for modified programs 🚀

Command Mean [ms] Min [ms] Max [ms] Relative
head ecsm 2.6 ± 0.0 2.5 2.6 1.00
Command Mean [ms] Min [ms] Max [ms] Relative
head hashmap 115.6 ± 2.3 112.0 119.7 1.00
Command Mean [ms] Min [ms] Max [ms] Relative
head keccak 127.6 ± 2.2 124.2 130.8 1.00
Command Mean [ms] Min [ms] Max [ms] Relative
head syscall_commit 84.1 ± 0.5 83.5 85.2 1.00

The restack brought in the ethrex bump, which moved lambda-vm-ethrex-crypto
to ethrex 4f658c2b while the guest kept its direct dep at 156cb8d6 — two
distinct Crypto traits, so the guest no longer compiled. Unify on the bumped
rev (lockfile collapses to a single ethrex-crypto entry).
@github-actions

Copy link
Copy Markdown

Benchmark Results for unmodified programs 🚀

Command Mean [ms] Min [ms] Max [ms] Relative
base binary_search 59.5 ± 1.0 58.2 60.9 1.01 ± 0.02
head binary_search 59.0 ± 0.6 57.9 59.9 1.00
Command Mean [ms] Min [ms] Max [ms] Relative
base bitwise_ops 59.4 ± 1.5 58.1 61.3 1.00
head bitwise_ops 59.6 ± 1.6 57.9 61.2 1.00 ± 0.04
Command Mean [ms] Min [ms] Max [ms] Relative
base fibonacci_26 63.5 ± 0.8 62.7 64.9 1.00 ± 0.02
head fibonacci_26 63.3 ± 0.7 62.7 64.6 1.00
Command Mean [ms] Min [ms] Max [ms] Relative
base matrix_multiply 65.1 ± 0.7 64.1 66.3 1.00
head matrix_multiply 105.6 ± 108.6 64.9 413.8 1.62 ± 1.67
Command Mean [ms] Min [ms] Max [ms] Relative
base modular_exp 58.9 ± 0.6 58.2 60.0 1.00 ± 0.02
head modular_exp 58.8 ± 0.9 57.8 61.0 1.00
Command Mean [ms] Min [ms] Max [ms] Relative
base quicksort 63.4 ± 0.6 62.5 64.4 1.00 ± 0.01
head quicksort 63.1 ± 0.7 62.1 64.4 1.00
Command Mean [ms] Min [ms] Max [ms] Relative
base sieve 64.9 ± 0.3 64.5 65.5 1.00
head sieve 66.0 ± 2.5 64.6 72.9 1.02 ± 0.04
Command Mean [ms] Min [ms] Max [ms] Relative
base sum_array 77.1 ± 4.0 74.0 85.7 1.04 ± 0.05
head sum_array 74.5 ± 0.7 73.7 75.6 1.00

The removed hint ecall accelerated hint-consuming guests on EVERY prove,
no caller opt-in; the arena made acceleration opt-in, so hint-less flows
(plain cli prove, bench_abba.sh) silently proved the software-fallback
trace — measured on ethrex_10_transfers: 3.35M cycles without hints vs
1.31M hinted (the ecall baseline was 1.70M).

resolve_hints restores the always-on ergonomics: when the caller passes
an empty arena, prove_with_options_and_inputs / prove_continuation /
count_elements first run the recording pass (collect_hints) and prove
the hinted trace. Both runs commit the same output — the arena only
changes cost — so the statement being proved is unchanged. Guests that
request nothing get an empty arena and an identical trace; the only
cost is one extra execution, negligible against proving.

Regression coverage: test_prove_ecrecover_hints_auto_records_arena
asserts hint-less and explicit-arena calls produce identical element
counts and equal verified public outputs.
@diegokingston

Copy link
Copy Markdown
Collaborator Author

/bench

- cargo fmt over the hint-arena diff
- group private_input/hints paths into ProveInputPaths so cmd_prove and
  cmd_prove_continuation stay under clippy's too_many_arguments (8/7),
  matching the FlamegraphCliOptions precedent

Verified: cargo fmt --check --all plus all four CI clippy passes
(default, debug-checks, disk-spill, cuda) green; the ethrex fixture
checksum gate passes.
@jotabulacios

Copy link
Copy Markdown
Collaborator

/bench

…pass.

request_hint now publishes its (hint_id, input) entry to the request log before
reading the arena slot, and the store that bumps the log's count word is the
executor's cue to answer it: it computes the hint and seeds the slot as an
INITIAL value, on a cell nobody has touched. That needs no memory write in the
trace - a write would have to be justified by a chip, an initial value does not -
and it is the same statement as shipping the slot in the private input up front,
because the region's bytes always stay exactly
encode_private_input_region(input, arena). A test pins that invariant.

The bytes stay unconstrained and untrusted: the guest verifies them and falls
back to software on failure, exactly as before. A slot an explicit arena already
covers is left alone, so passing hints up front keeps working unchanged, and
Memory::silence_hints answers nothing so the software-fallback path stays
measurable and testable.

ExecutionResult now reports the arena the run actually used, and the monolithic
prove path takes it from the single execution it already performs instead of
calling resolve_hints, which is left as the continuation prover's pre-pass since
that path freezes its initial image and provenance before streaming epochs. That
pre-pass now drains its logs rather than collecting them: reading back an arena
does not need tens of millions of Log entries at 40 bytes per cycle on a path
whose whole purpose is bounding memory.

Measured on 30 ecrecovers: the proved trace goes from 866,413 to 868,423 cycles
(+0.23%, the request-log stores) while total executed cycles drop from 7,280,839
across two runs to 868,423 in one. An arena decided mid-run produces a trace
identical cycle for cycle to one supplied up front, and both proofs verify with
the same public output.

Also reserves fallibly in Memory::hint_requests, whose capacity comes from a
guest-written count word, and derives HINT_LOG_START from PRIVATE_INPUT_START and
MAX_PRIVATE_INPUT_SIZE instead of restating 512 MiB.
…ex blocks.

Each fixture runs three ways - every hint recomputed in software, answered on
demand during the run, and supplied up front as an arena - printing cycles and
wall clock for each, and asserting the last two produce the same trace.

These are the numbers the one-pass change is argued from, and they are not
visible in /bench: on the mainnet block it proves, the recording pass a hint-less
prove used to run was 40.08M cycles (1.31s) against a proved execution of 34.19M
(1.18s). That is about 1% of a 123s prove, under the 2% spread three runs
resolve, so it has to be isolated rather than read off a two-branch comparison.
The continuation prover froze three things before streaming epochs - the carried
memory image, the genesis provenance, and the PAGE init data the global proof
commits - so it needed the arena up front and kept a recording pass after the
monolithic path had already dropped one. None of the three has to be frozen:
they only ever grow, and the executor discovers the new facts as the guest runs.

`GenesisFacts` holds the page data and the arena's slot count behind a mutex. The
producer folds in whatever the executor seeded right after each epoch executes
and before `collect_epoch` replays that epoch's memory - which is where it has to
happen, since that replay reads the image and `epoch_boundary` takes each touched
cell's init from the provenance, and both would otherwise read zero for a slot
the guest just read. The global prove reads the finished picture after the
boundary channel closes, i.e. after the producer is done, so the lock is never
contended and the ordering is the channel's rather than the lock's.

Three things made this contained, all of them properties of the existing code:
the per-epoch trace build never looks at the arena (inside `build_traces` the
hints only reach PAGE generation, which continuation epochs skip via the L2G
bookend); `init_page_data` and the page count are consumed only by the global
prove, after it has drained every boundary; and an epoch executes before its own
op collection, which is the gap the fold-in needs.

`private_input_page_count` now takes the arena's slot count rather than the
slots: the span only ever depended on how many there are, and a continuation
knows the count before it has the bytes.

Seeding claims a cell as genesis, which is only true of a cell with no history,
so a cell an earlier epoch already bound is refused with a ContinuationInvariant
instead of producing a bundle that fails to verify with nothing pointing at the
cause.

`resolve_hints` is gone with its last caller: no prove path executes the guest
more than once now. Regression coverage proves and verifies the ecrecover guest
four ways - arena answered on demand and supplied up front, monolithic and
continuation - and requires the same committed output from all four.
…nputs.

The two-pass flow is gone: an empty arena is now the normal case, answered by
the executor during the single execution the call already performs. The stale
paragraph also left a dangling intra-doc link to a function that no longer
exists.
…mory.

`Memory::store_word` compared every stored address against the request log's
count word. The check belongs on the store INSTRUCTION instead: it means "the
guest completed a log entry", not "someone wrote a word into a Memory", and this
crate is linked into the in-VM STARK verifier (the recursion guest links
`lambda-vm-prover`, which depends on it), which writes words through `Memory`
without ever executing a guest store.

This does NOT recover the cycles I first attributed to it. Measured on
`recursion-min` against feat/hint-arena (328,052,567 cycles): the hint work costs
328,979,652, and moving the hook leaves it at exactly 328,979,652. Deleting the
call entirely drops to 328,045,817, so the cost tracks the code being reachable
rather than where it is called from, and the verifier never executes it. The
+927k is isolated to `executor/src/vm/memory.rs` and remains unexplained; it is
not codegen drift (two null perturbations each moved .text while leaving the
cycle count exactly at the baseline, and two builds of the same source with
different .text give identical cycles).

Keeping the move on its own merits, with the measurement recorded here so the
next person does not re-derive it.
@jotabulacios

Copy link
Copy Markdown
Collaborator

/bench

@jotabulacios

Copy link
Copy Markdown
Collaborator

/ai-review

@github-actions

Copy link
Copy Markdown

Codex Code Review

  • Mediumexecutor/src/flamegraph.rs:336: Passing &[] does not make execution hint-free; the executor automatically seeds requested hints on demand. Consequently, flamegraphs use accelerated paths despite the CLI promising conservative software-fallback profiles. Call executor.silence_hints() after construction.

@jotabulacios

Copy link
Copy Markdown
Collaborator

/bench-verify

@github-actions

Copy link
Copy Markdown

Benchmark started on the bench server. Two verifier arms (monolithic + continuations over an ethrex 20-tx block), then the recursion-guest cycle comparison, which adds guest builds on top — longer on a cold runner. The bench server is occupied until it finishes.

@github-actions

Copy link
Copy Markdown

AI Review

PR #942 · 55 changed files

Warning: the diff was truncated before review.

Findings

Status Sev Location Finding Found by
confirmed medium prover/src/continuation.rs:1262 Continuation prover trace builder uses stale hint count if PAGE generation is re-enabled for epochs nemotron
openrouter/nvidia/nemotron-3-ultra-550b-a55b
confirmed low bin/cli/src/main.rs:344 CLI read_hints lacks early size validation against MAX_PRIVATE_INPUT_SIZE nemotron
openrouter/nvidia/nemotron-3-ultra-550b-a55b
confirmed low executor/programs/rust/ecrecover_hints/src/main.rs:11 ecrecover_hints guest doc lists hint request order incorrectly (sqrt, field_inv, scalar_inv vs actual sqrt, scalar_inv, field_inv) glm
openrouter/z-ai/glm-5.2
confirmed low executor/src/vm/instruction/execution.rs:278 Hint-answer hook fires on ANY guest SW to HINT_LOG_START_INDEX, can crash non-hint guests glm
openrouter/z-ai/glm-5.2
confirmed low syscalls/src/syscalls.rs:183 Sharp edge: mixing request_hint with hint_count/next_hint causes silent desync nemotron
openrouter/nvidia/nemotron-3-ultra-550b-a55b

Status column reflects the verdict from the verifier: deepseek-verifier (openrouter/deepseek/deepseek-v4-pro).

AI-001: Continuation prover trace builder uses stale hint count if PAGE generation is re-enabled for epochs
  • Status: confirmed
  • Severity: medium
  • Location: prover/src/continuation.rs:1262
  • Found by: nemotron:openrouter/nvidia/nemotron-3-ultra-550b-a55b
  • Verified by: deepseek-verifier:openrouter/deepseek/deepseek-v4-pro
  • Rejected by: -

Claim

The build_worker passes the caller's original hints parameter to Traces::build_from_collected, but the effective arena may have grown on-demand during execution. The comment acknowledges this is harmless only because PAGE generation is currently skipped for epochs; if re-enabled, it would size the private-input region from a stale slot count.

Evidence

Lines 1262-1269: 'Reached only by PAGE generation, which the line above skips — so hints here is the caller's arena, NOT the effective one the run may have grown on demand, and that is harmless only because nothing reads it. Anything that re-enables PAGE for epochs has to take the effective arena instead (the count is in GenesisFacts), or it will size the private-input region from a stale slot count.'

Suggested fix

When PAGE generation is re-enabled for epochs, pass the effective hint count from GenesisFacts instead of the caller's original hints. Consider adding a test that enables PAGE for epochs to catch this.

AI-002: CLI read_hints lacks early size validation against MAX_PRIVATE_INPUT_SIZE
  • Status: confirmed
  • Severity: low
  • Location: bin/cli/src/main.rs:344
  • Found by: nemotron:openrouter/nvidia/nemotron-3-ultra-550b-a55b
  • Verified by: deepseek-verifier:openrouter/deepseek/deepseek-v4-pro
  • Rejected by: -

Claim

read_hints reads the entire hint file into memory before checking if it exceeds the 512 MiB private-input window. A maliciously large file could cause excessive memory allocation in the CLI before the executor rejects it during encoding.

Evidence

read_hints only validates bytes.len() % 32 == 0. The executor's encode_private_input_region enforces the cap, but only after the CLI has already read the whole file.

Suggested fix

Add a check in read_hints: if bytes.len() > 32 * (MAX_PRIVATE_INPUT_SIZE / 32) { return Err(...) } using the constant from executor::vm::memory, or stream the file in chunks.

AI-004: ecrecover_hints guest doc lists hint request order incorrectly (sqrt, field_inv, scalar_inv vs actual sqrt, scalar_inv, field_inv)
  • Status: confirmed
  • Severity: low
  • Location: executor/programs/rust/ecrecover_hints/src/main.rs:11
  • Found by: glm:openrouter/z-ai/glm-5.2
  • Verified by: deepseek-verifier:openrouter/deepseek/deepseek-v4-pro
  • Rejected by: -

Claim

The module doc states the per-recovery hint request order is 'sqrt (decompress), the batched field inverse (lincomb), and the scalar inverse' and that 'the host's arena must follow the same order'. The actual call order in ecsm_ecrecover is decompress_r (HINT_FIELD_SQRT) → scalar_inv (HINT_SCALAR_INV) → ecsm_lincomb2field_inv (HINT_FIELD_INV), i.e. sqrt, scalar_inv, field_inv — the 2nd and 3rd slots are swapped relative to the doc.

Evidence

crypto/ethrex-crypto/src/lib.rs:254 calls decompress_r(r_bytes, y_is_odd) first, then :261 scalar_inv(&r), then :275 ecsm_lincomb2(...) whose lincomb2_with_oracle calls field_inv (line 440). The guest doc (ecrecover_hints/src/main.rs:8-12) claims 'sqrt ... the batched field inverse (lincomb), and the scalar inverse, in that order'. Consumption is positional, so a hand-built arena following the doc would mismatch slots and force software fallbacks. --record-hints records the real order so recorded arenas are unaffected.

Suggested fix

Correct the doc to 'sqrt (decompress), the scalar inverse, and the batched field inverse (lincomb), in that order' to match the actual ecsm_ecrecover call sequence.

AI-005: Hint-answer hook fires on ANY guest SW to HINT_LOG_START_INDEX, can crash non-hint guests
  • Status: confirmed
  • Severity: low
  • Location: executor/src/vm/instruction/execution.rs:278
  • Found by: glm:openrouter/z-ai/glm-5.2
  • Verified by: deepseek-verifier:openrouter/deepseek/deepseek-v4-pro
  • Rejected by: -

Claim

The Store::Word arm unconditionally calls memory.answer_hint_request(read_value as u32) whenever a store targets HINT_LOG_START_INDEX, with no gate indicating the guest actually uses the hint-arena mechanism. A store of any value > 1 to that exact address by a non-hint guest makes answer_hint_request return HintRequestOutOfOrder (since index != hint_arena.len() with an empty arena), which propagates as ExecutionError::MemoryError and aborts execution.

Evidence

execution.rs:273-278 runs memory.store_word(addr, ...) then if addr == crate::vm::memory::HINT_LOG_START_INDEX { memory.answer_hint_request(read_value as u32)?; }. In memory.rs answer_hint_request, for a non-zero count where index != self.hint_arena.len() it returns Err(MemoryError::HintRequestOutOfOrder). The address 0x11F000008 lies in a high reserved region that normal guests do not write to, so practical risk is low, but the hook is unguarded and address-magic-based.

Suggested fix

Gate the hook on whether the guest is actually using hints — e.g. only answer when !self.hint_arena.is_empty() || <a mode flag>, or treat HintRequestOutOfOrder/HintSlotAlreadyWritten as a no-op (early return Ok) when the arena has never been seeded, rather than a hard execution error.

AI-007: Sharp edge: mixing request_hint with hint_count/next_hint causes silent desync
  • Status: confirmed
  • Severity: low
  • Location: syscalls/src/syscalls.rs:183
  • Found by: nemotron:openrouter/nvidia/nemotron-3-ultra-550b-a55b
  • Verified by: deepseek-verifier:openrouter/deepseek/deepseek-v4-pro
  • Rejected by: -

Claim

The code explicitly documents that request_hint and hint_count/next_hint must not be mixed in the same guest, but provides no runtime guard. A guest that accidentally mixes them will consume slots incorrectly (request_hint uses the count word, next_hint uses a separate static cursor).

Evidence

Lines 183-188 in syscalls.rs and lines 386-391 in memory.rs both document this as a known sharp edge with no enforcement. The comment suggests a mode flag in the header's spare pad word would close it properly.

Suggested fix

Add a mode flag in the hint arena header's spare pad word (the 4-byte zero pad after the count) to distinguish 'pre-supplied arena' vs 'on-demand' mode, and reject mixed usage at runtime.

Reviewer Lanes

Lane Model Prompt Status Findings
glm openrouter/z-ai/glm-5.2 general success 2
kimi openrouter/moonshotai/kimi-k2.7-code general success 0
minimax minimax/MiniMax-M3 general error: opencode failed (provider/auth/runtime error) and no findings were submitted 0
moonmath zro/minimax-m3 general error: opencode failed (provider/auth/runtime error) and no findings were submitted 0
nemotron openrouter/nvidia/nemotron-3-ultra-550b-a55b general success 5

Verification Lanes

Lane Model Status Confirmed Rejected Uncertain
deepseek-verifier openrouter/deepseek/deepseek-v4-pro success 5 2 0

Native Codex and Claude reviews run separately and post their own comments. They are not included in this structured provenance report.

Discarded candidates (2) — rejected by the verifier
  • Redundant expect in read_hints (bin/cli/src/main.rs:358, found by nemotron:openrouter/nvidia/nemotron-3-ultra-550b-a55b) — The expect("32-byte chunk") on line 358 is technically redundant since chunks_exact(32) guarantees 32-byte slices and try_into::<[u8;32]> cannot fail from a 32-byte slice. However, this is a coding-style observation, not an actionable issue. The expect serves as an explicit invariant assertion (it would never fire in practice) and removing it would not change behavior. This is not a bug or a meaningful sharp edge.
  • Outdated documentation for PRIVATE_INPUT_START layout in syscalls.rs (syscalls/src/syscalls.rs:5, found by nemotron:openrouter/nvidia/nemotron-3-ultra-550b-a55b) — The syscalls.rs:5-6 comment on PRIVATE_INPUT_START describes the guest's view: '4-byte LE length prefix at this address, data at +4.' This is still correct for what get_private_input() returns — it reads the length prefix at the base address and the main data at +4. The hint arena header follows the main data (after alignment padding) and is documented separately in the hint_count/hint_arena_header_addr/read_slot functions within the same file, plus fully specified in memory.rs:50-51. The comment isn't outdated; it's scoped to what the guest sees when reading the main private input section.

Raw lane outputs, candidates, final issues, and model metrics are uploaded as workflow artifacts.

@github-actions

Copy link
Copy Markdown

Verifier benchmark — 1a61b199ac vs main (20 pairs, monolithic + continuations)

❌ Run failed. Last log lines:

   Compiling ark-bn254 v0.5.0
   Compiling ethrex-crypto v13.0.0 (https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6)
   Compiling ethrex-trie v13.0.0 (https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6)
   Compiling ethrex-common v13.0.0 (https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6)
   Compiling axum-extra v0.10.3
   Compiling ethrex-levm v13.0.0 (https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6)
   Compiling ethrex-l2-common v13.0.0 (https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6)
   Compiling ethrex-storage v13.0.0 (https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6)
   Compiling ethrex-metrics v13.0.0 (https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6)
   Compiling ethrex-storage-rollup v13.0.0 (https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6)
   Compiling ethrex-vm v13.0.0 (https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6)
   Compiling ethrex-blockchain v13.0.0 (https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6)
   Compiling ethrex-p2p v13.0.0 (https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6)
   Compiling ethrex-rpc v13.0.0 (https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6)
   Compiling ethrex-l2-rpc v13.0.0 (https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6)
   Compiling ethrex-fixtures v0.1.0 (/home/app/actions-runner/_work/lambda_vm/lambda_vm/tooling/ethrex-fixtures)
    Finished `release` profile [optimized] target(s) in 34.64s
wrote executor/tests/ethrex_bench_20.bin (32766 bytes): block #1 with 20/20 transfer(s) [N senders -> N recipients]
==> Cached binaries are for different refs/features; rebuilding.
==> Building both cli binaries in isolated worktree /tmp/verify_wt
Preparing worktree (detached HEAD 884eb457)
==> Building cli @ 884eb45780 -> cli_B  (features: jemalloc-stats)
==> Building cli @ 1a61b199ac -> cli_A  (features: jemalloc-stats)
==> Proving with cli_B (884eb45780) 
==> Proving with cli_A (1a61b199ac) 
ERROR: prove failed for /tmp/verify_run/cli_A. Tail of log:
Reading ELF file...
Reading private input file...
Generating proof (blowup=2, queries=219)...
Proof generation failed: execution error: Failed to execute instruction: Unknown syscall number: 18446744073709551585

Recursion guest cycles — verifier running INSIDE the VM (main vs PR)

empty program · monolithic · blowup=2, 1 query (diagnostic — NOT a real verifier cost)

Single exact reading per ref — no ABBA: guest cycles are deterministic for a fixed
(guest ELF, input blob), so there is no machine drift to cancel.

Metric main PR Δ
Guest cycles 331.6M 329.0M -2.6M (-0.80%)
Keccak calls 3029 2994 -35
  baseline  origin/main  884eb45780  guest=recursion-min.elf
  PR        1a61b199ac20ce05282dd5c6199071690b6689b3  1a61b199ac  guest=recursion-min.elf
  note: cycles reproduce to ~±100k (build codegen + proof nondeterminism);
        treat sub-100k deltas as noise, not signal.
raw (exact integer counts)
ref_b_sha=884eb45780016f33b729051a1a70c8abe9a511a6 ref_b_elf=recursion-min.elf ref_b_cycles=331649890 ref_b_keccak=3029 ref_b_execute_wall_s=9
ref_a_sha=1a61b199ac20ce05282dd5c6199071690b6689b3 ref_a_elf=recursion-min.elf ref_a_cycles=329007684 ref_a_keccak=2994 ref_a_execute_wall_s=10
delta_cycles=-2642206 delta_keccak=-35

(blowup2-block regime FAILED — see the workflow log.)

@jotabulacios
jotabulacios marked this pull request as ready for review August 27, 2026 20:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants