Test/ef io edge cases#589
Conversation
…angle from host stubs
…int/Panic/Commit ECALLs
for len in load_bytes instead of an implicit cast.
…returning VmProof
Guest programs: - ef_io_no_input: read_input with no private input (buf_size==0), then write_output with hardcoded "ok" - ef_io_zero_write: write_output(ptr, 0) followed by write_output(ptr, 5) - ef_io_multi_write: 10 single-byte write_output calls concatenating to "abcdefghij" Unit tests (memory.rs): - Zero-length commit after data is a no-op - Zero-length commit at exact 1 MB cap boundary succeeds - Failed commit (cap exceeded) does not modify the output buffer Integration tests (rust.rs): - test_ef_io_no_input - test_ef_io_zero_write - test_ef_io_multi_write Prover E2E test: - test_prove_ef_io_no_input: full prove+verify with no private input
Codex Code ReviewNo issues found in the PR diff. I attempted a lightweight |
PR Review: EF IO Edge CasesHigh
Medium
Low
|
| @@ -117,7 +129,11 @@ impl Memory { | |||
| ); | |||
| } | |||
There was a problem hiding this comment.
[High] load_half panics on unaligned access while all other memory functions in this PR return Err. A malformed guest can abort the host process.
| } | |
| return Err(MemoryError::UnalignedAccess); |
(Also add UnalignedAccess to the MemoryError enum if not already present.)
| /// Total cap on public output bytes across all `commit_public_output` calls. | ||
| /// The COMMIT AIR concatenates calls via the running `x254` index, so this | ||
| /// is enforced as a running-total budget rather than a per-call limit. | ||
| pub const MAX_PUBLIC_OUTPUT_TOTAL_SIZE: u64 = 1024 * 1024; |
There was a problem hiding this comment.
[Medium] This is a 1024x increase from the old 1 KB per-call limit and a semantic shift to a cumulative limit. Public output size directly affects proof cost. What is this value based on — the EF zkVM IO spec, COMMIT AIR constraints, or something else? Needs a comment or PR description justification.
| unsafe { | ||
| let len_ptr = PRIVATE_INPUT_START as *const u32; | ||
| let len = core::ptr::read_volatile(len_ptr) as usize; | ||
| *buf_ptr = (PRIVATE_INPUT_START + 4) as *const u8; |
There was a problem hiding this comment.
[Medium] The EF IO spec says buf_ptr is unspecified when buf_size == 0. This always writes a non-null pointer, which misleads callers that test buf_ptr == null to detect "no input" — a common pattern in portable zkVM code.
| *buf_ptr = (PRIVATE_INPUT_START + 4) as *const u8; | |
| *buf_ptr = if len == 0 { | |
| core::ptr::null() | |
| } else { | |
| (PRIVATE_INPUT_START + 4) as *const u8 | |
| }; |
| .filter(|c| c.is_private_input) | ||
| .count(); | ||
|
|
||
| debug_assert_eq!( |
There was a problem hiding this comment.
[Low] debug_assert_eq! is compiled out in release builds. If executor view and trace-reconstructed output silently diverge in production, incorrect proofs are generated with no runtime signal. Use assert_eq! here — or document why eliding this check in release is safe.
|
|
||
| #[cfg(target_arch = "riscv64")] | ||
| enum SyscallNumbers { | ||
| pub enum SyscallNumbers { |
There was a problem hiding this comment.
[Low] The enum is cast as usize in inline asm. Without #[repr(usize)] the discriminant representation is implementation-defined. Add the repr to make the ABI contract explicit.
| pub enum SyscallNumbers { | |
| #[repr(usize)] | |
| pub enum SyscallNumbers { |
| in("a2") size, | ||
| in("a7") SyscallNumbers::Commit as usize, | ||
| ); | ||
| } |
There was a problem hiding this comment.
[Low] ecall writes its return value into a0. Without a clobber declaration, the compiler may keep a live value in a0 across this asm block and get it silently overwritten. Add lateout("a0") _ to the constraints. (The same issue exists in the ecall blocks in syscalls.rs.)
|
Benchmark Results for modified programs 🚀
|
No description provided.