Skip to content

Commit ee19328

Browse files
authored
Return continuation invariant errors instead of panicking (#731)
1 parent e252322 commit ee19328

2 files changed

Lines changed: 25 additions & 14 deletions

File tree

prover/src/continuation.rs

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -352,12 +352,13 @@ fn prove_epoch(
352352
);
353353

354354
// Continuation epochs use the L2G bookend, so PAGE is skipped: page_configs is
355-
// empty. The verifier hard-codes this (passes `&[]`); assert the prover agrees so
355+
// empty. The verifier hard-codes this (passes `&[]`); check the prover agrees so
356356
// the two sides build identical AIRs.
357-
debug_assert!(
358-
traces.page_configs.is_empty(),
359-
"continuation epoch must have no PAGE configs (L2G bookend replaces PAGE)"
360-
);
357+
if !traces.page_configs.is_empty() {
358+
return Err(Error::ContinuationInvariant(
359+
"continuation epoch must have no PAGE configs (L2G bookend replaces PAGE)".to_string(),
360+
));
361+
}
361362

362363
// R_{i+1}, read from the committed REGISTER trace (FINI, bound to the last write).
363364
let reg_fini = register::fini_from_trace(&traces.register);
@@ -413,7 +414,9 @@ fn prove_epoch(
413414
let l2g_root = proof
414415
.proofs
415416
.last()
416-
.expect("epoch proof has at least the L2G sub-table")
417+
.ok_or_else(|| {
418+
Error::ContinuationInvariant("epoch proof is missing the L2G sub-table".to_string())
419+
})?
417420
.lde_trace_main_merkle_root;
418421

419422
Ok(EpochProof {
@@ -676,9 +679,11 @@ pub fn prove_continuation(
676679
} else {
677680
// Epoch i+1's init is epoch i's bound fini, reused directly (same
678681
// `register_word_address_list` order) — the cross-epoch register binding.
679-
prev_fini
680-
.clone()
681-
.expect("prev_fini is set after the first epoch")
682+
prev_fini.clone().ok_or_else(|| {
683+
Error::ContinuationInvariant(
684+
"previous epoch final registers are missing after the first epoch".to_string(),
685+
)
686+
})?
682687
};
683688

684689
// Run one epoch; `logs` is this epoch's chunk only (the executor clears it).
@@ -693,11 +698,12 @@ pub fn prove_continuation(
693698

694699
// Invariant: a non-final epoch ran the full `epoch_size` (a power of two),
695700
// so its CPU table has no padding rows.
696-
debug_assert!(
697-
is_final || logs.len().is_power_of_two(),
698-
"intermediate epoch must run a power-of-two number of cycles (got {})",
699-
logs.len()
700-
);
701+
if !is_final && logs.len() != epoch_size {
702+
return Err(Error::ContinuationInvariant(format!(
703+
"intermediate epoch ran {} cycles, expected {epoch_size}",
704+
logs.len()
705+
)));
706+
}
701707

702708
let label = local_to_global::epoch_label(index);
703709
let traces = Traces::from_image_and_logs(

prover/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ pub enum Error {
188188
InvalidTableCounts(String),
189189
/// Continuation epoch size exponent is invalid.
190190
InvalidContinuationEpochSize(String),
191+
/// Continuation proof construction hit an internal invariant failure.
192+
ContinuationInvariant(String),
191193
/// A non-final continuation epoch contains the program-terminating
192194
/// instruction. The terminating instruction must be in the final epoch.
193195
HaltInNonFinalEpoch,
@@ -207,6 +209,9 @@ impl fmt::Display for Error {
207209
Error::InvalidContinuationEpochSize(msg) => {
208210
write!(f, "invalid continuation epoch size: {msg}")
209211
}
212+
Error::ContinuationInvariant(msg) => {
213+
write!(f, "continuation invariant failed: {msg}")
214+
}
210215
Error::HaltInNonFinalEpoch => {
211216
write!(
212217
f,

0 commit comments

Comments
 (0)