Skip to content

Commit 38feb15

Browse files
Continuations (Approach 2): prove executions epoch-by-epoch (#685)
* split execution into epochs * Add an initial-memory image * build a single epoch's traces * Add an is_final flag for halt * Make the HALT table optional * Add a register_init parameter to VmAirs::new for the REGISTER preprocessed commitment * reject a non-final epoch that contains the program-terminating instruction * Add local-to-global boundary and process epochs to emit the boundary set * Add local-to-global air table * Add cross-epoch local-to-global memory linkage * Add memory_bus_interactions to emit epoch init/fini tokens * Wire the local-to-global table as the epoch-local Memory-bus bookend * Add prove_and_verify_continuation * stream epochs one at a time and drop traces after proving * add bench_continuation * Add multi-pass array asm program (1 MiB footprint, ~20M steps) as a worst-case local-to-global memory stress benchmark * Add a count mode to bench_continuation that reports a program's cycle count by running the executor only, as a no-proving proxy for monolithic proving memory * l2g val to a single byte column * Thread private inputs so we can bench ethrex program * Use the static preprocessed bitwise commitment * Avoid redundant per-epoch work (skip page and carry the memory) * add global_memory for init-elf binding * add clasification into stack vs data/heap in bench_continuation * store memory in dense per-page arrays instead of per-cell HashMaps * update doc * Range-check the local-to-global continuation table columns * slim range-check since memw already does it * make fini_epoch constant, add MU selector for padding rows, add epoch ordering constraint * Gate the local-to-global MU selector on the GlobalMemory bus only and constrain it boolean, leaving the epoch-local Memory bus and the range/ordering checks on unconditional multiplicity so the cross-epoch init_epoch < fini_epoch ordering check can never be skipped via MU; padding rows stay harmless because they self-cancel on the Memory bus and send only valid range/ordering lookups. Also add a design doc describing the continuation local-to-global memory protocol, both MU-wiring designs, and the soundness reasoning. * Revert the local-to-global MU wiring to Design X (MU gates every L2G interaction, including the epoch-local Memory-bus bookend), because the Design Y variant that gated only the GlobalMemory bus is unsound: with the Memory bookend on unconditional multiplicity, a prover can set MU=0 on a non-first-touch row to orphan a touched epoch from the cross-epoch chain while its epoch proof still passes, and point the prover-controlled finalization at the truncation, silently dropping a real memory write. Gating the Memory bookend with MU forces MU=1 on every touched cell, which forces every touching epoch into the telescoping chain, making the chain complete and the finalization trustworthy. Update the design doc to record both designs, the chain-truncation attack, and the anchoring reasoning that makes Design X sound. * Bind cross-epoch register * Bind continuation epoch and global proofs to their statement in Fiat-Shamir (ELF, epoch label, epoch, count) * update md * carry the x254 commit index across epochs * Force continuation epoch size to a power of two * Use a power-of-two epoch size in tests * Add a test with a non-power-of-two epoch size * Split the integrated continuation prove+verify * CLI continuation flag * Remove dead-code allow and update doc * Thread ProofOptions through prove_continuation/verify_continuation * Seed the per-epoch touched-cell prediction from the carried register file instead of a fresh one * Validate each epoch's reg_fini length * Assert test_commit_across_epochs_verifies actually produces more than one epoch * Continuations cleanup: docs, comments, and regression tests (#714) Keep the follow-up scoped to non-performance cleanup while preserving the soundness regression coverage. - Add L2G/global-memory regression tests for MU selector behavior, chain truncation, l2g-root binding, and private-input continuations. - Fix stale continuation/global-memory docs and comments. - Replace bare x254 byte address literals with register_base_address(254). - Remove the unused DEFAULT_EPOCH_SIZE constant and document run_epochs as a test/bench helper. * Use log2 epoch size for continuation CLI (#717) Replace the continuation CLI's raw --epoch-size / --num-epochs controls with --epoch-size-log2. The CLI now computes an exact power-of-two epoch size directly, defaults to 2^20, rejects tiny log2 values below 18, and no longer runs a cycle-count pre-pass to split into a target epoch count. Update the continuation design doc and help text with the ethrex 10-transfer memory sweep as guidance. * Delete init_ts column and drop ts from GlobalMemory bus * Replace the always-zero global_memory init_epoch column with a verifier-fixed GENESIS_EPOCH constant * Represent init state with dense representation instead of intermediate HashMaps * Avoid duplicate L2G trace work in continuations (#719) * Reuse genesis page data for continuation global proof (#720) * Polish continuation verification and CLI (#728) * Make continuation API take epoch size log2 (#730) * Return continuation invariant errors instead of panicking (#731) * Simplify continuation L2G trace construction (#732) * Clean up continuation AIR setup (#733) * Reject continuations exceeding the IsB20 cross-epoch ordering range (#734) The cross-epoch ordering check proves `init_epoch < fini_epoch` via an IsB20 (20-bit) lookup on `fini_epoch - 1 - init_epoch`, so a run can have at most 2^20 epochs. Beyond that the IsB20 bus cannot balance and no honest proof exists. Previously this was guarded only by a debug_assert in the prover's bitwise emission, so a release build would build an unprovable trace and fail cryptically — reachable via the library API with a small epoch size (the CLI's min epoch size keeps it out of reach there). Add a hard check in `prove_continuation`'s epoch loop returning `Error::InvalidContinuationEpochSize` with a clear message once the epoch count would exceed the range. This is a prover-side guard only: the verifier already rejects any such proof (the IsB20 table is preprocessed and the ordering sender is rebuilt verifier-side from a positional epoch label), so soundness is unchanged — it just turns a confusing failure into a clean error. Introduce `local_to_global::MAX_EPOCHS` as the single source of truth, used by both the new check and the existing debug_assert (replacing the `1 << 20` literal). * add doc and debug_assert --------- Co-authored-by: Mauro Toscano <12560266+MauroToscano@users.noreply.github.com>
1 parent ae858b8 commit 38feb15

31 files changed

Lines changed: 6560 additions & 227 deletions

.github/workflows/pr_main.yaml

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,28 +102,53 @@ jobs:
102102
cargo test --release -p executor test_ethrex -- --ignored
103103
cargo test --release -p executor test_ckzg -- --ignored
104104
105+
test-cli:
106+
name: CLI tests
107+
runs-on: ubuntu-latest
108+
if: github.event_name != 'push' || github.actor != 'github-merge-queue[bot]'
109+
steps:
110+
- name: Checkout sources
111+
uses: actions/checkout@v4
112+
113+
- name: Setup Rust Environment
114+
uses: ./.github/actions/setup-rust
115+
116+
- name: Cache cargo build artifacts
117+
uses: Swatinem/rust-cache@v2
118+
with:
119+
shared-key: "lambda-vm-cli-test"
120+
cache-all-crates: "true"
121+
122+
- name: Run CLI tests
123+
run: cargo test -p cli
124+
105125
# "Test" is a required check — keep this name to avoid branch protection changes.
106-
# This gate job passes only when executor tests AND all prover shards succeed.
126+
# This gate job passes only when CLI, executor, disk-spill, and prover tests succeed.
107127
test:
108128
name: Test
109129
if: always()
110-
needs: [test-executor, test-prover, test-disk-spill]
130+
needs: [test-executor, test-cli, test-prover, test-disk-spill]
111131
runs-on: ubuntu-latest
112132
steps:
113133
- name: Check results
114134
run: |
115135
executor="${{ needs.test-executor.result }}"
136+
cli="${{ needs.test-cli.result }}"
116137
prover="${{ needs.test-prover.result }}"
117138
disk_spill="${{ needs.test-disk-spill.result }}"
118139
119140
echo "test-executor: $executor"
141+
echo "test-cli: $cli"
120142
echo "test-prover: $prover"
121143
echo "test-disk-spill: $disk_spill"
122144
123145
# Allow "success" or "skipped" (skipped on merge queue pushes)
124146
if [[ "$executor" != "success" && "$executor" != "skipped" ]]; then
125147
exit 1
126148
fi
149+
if [[ "$cli" != "success" && "$cli" != "skipped" ]]; then
150+
exit 1
151+
fi
127152
if [[ "$prover" != "success" && "$prover" != "skipped" ]]; then
128153
exit 1
129154
fi

bin/cli/README.md

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,10 @@ cargo run -p cli --release -- prove <PROGRAM.elf> -o proof.bin [flags]
5757
| `--private-input <FILE>` | Pass private input bytes to the guest. |
5858
| `--blowup <N>` | FRI blowup factor (power of 2). Higher = fewer queries, smaller proof, slower proving. [default: 2] |
5959
| `--time` | Print total proving time. |
60-
| `--cycles` | Run one extra pre-pass outside the timer and print the dynamic instruction count. |
61-
| `--elements` | Build traces and print main-trace and aux-trace field element counts. |
60+
| `--cycles` | Run one extra execution outside the timer and print the dynamic instruction count. Also supported with `--continuations`. |
61+
| `--elements` | Build traces and print main-trace and aux-trace field element counts. Monolithic proving only; conflicts with `--continuations`. |
62+
| `--continuations` | Prove as a continuation bundle split into fixed-size epochs. |
63+
| `--epoch-size-log2 <N>` | Continuation epoch size as `2^N` cycles. Requires `--continuations`. Defaults to `20`; values below `18` are rejected. |
6264

6365
### Verify
6466

@@ -72,8 +74,10 @@ cargo run -p cli --release -- verify <PROOF> <PROGRAM.elf> [flags]
7274
|---|---|
7375
| `--blowup <N>` | FRI blowup factor used during proving. Must match. [default: 2] |
7476
| `--time` | Print verification time. |
77+
| `--continuations` | Verify a continuation proof bundle produced by `prove --continuations`. |
7578

76-
Returns exit code `0` on successful verification, `1` on failure.
79+
Returns exit code `0` on successful verification, `1` on failure. `--blowup` must
80+
match the value used during proving.
7781

7882
### Count Elements
7983

@@ -96,10 +100,29 @@ cargo run -p cli --release -- execute executor/program_artifacts/asm/add.elf
96100
cargo run -p cli --release -- prove executor/program_artifacts/asm/add.elf -o /tmp/proof.bin
97101
cargo run -p cli --release -- verify /tmp/proof.bin executor/program_artifacts/asm/add.elf
98102

103+
# Generate and verify a continuation proof
104+
cargo run -p cli --release -- prove program.elf -o /tmp/cont.bin --continuations --epoch-size-log2 20
105+
cargo run -p cli --release -- verify /tmp/cont.bin program.elf --continuations
106+
107+
# Generate a continuation proof and print total dynamic instruction count
108+
cargo run -p cli --release -- prove program.elf -o /tmp/cont.bin --continuations --cycles
109+
99110
# Prove with private input and print metrics
100111
cargo run -p cli --release -- prove program.elf -o /tmp/proof.bin --private-input input.bin --time --cycles
101112
```
102113

114+
For continuation proofs, `--epoch-size-log2` is the power in `2^N` cycles. Larger
115+
values reduce epoch count and fixed per-epoch overhead, but increase peak memory.
116+
As rough ethrex 10-transfer distinct-account reference points from a local sweep:
117+
`19` used about 6.9 GB peak heap, `20` about 9.5 GB, `21` about 15.8 GB, and `22`
118+
about 26.8 GB. For a new workload, use the highest value the machine can run
119+
without swapping.
120+
121+
Continuation proof bundles are self-contained for standalone verification. When
122+
`--private-input` is used, the serialized continuation proof includes the raw
123+
private input bytes so the verifier can rebuild the genesis memory commitment.
124+
Do not treat continuation proof files as confidential-input hiding artifacts.
125+
103126
## Guest Program Flamegraphs
104127

105128
Generate flamegraphs showing where the guest RISC-V program spends its execution time (by instruction count).

0 commit comments

Comments
 (0)