Skip to content

Commit b5b9ba8

Browse files
authored
feat: cycle limit (#1027)
2 parents ef0fc3b + b60254f commit b5b9ba8

File tree

5 files changed

+62
-2
lines changed

5 files changed

+62
-2
lines changed

core/src/runtime/context.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,23 @@ use super::{hookify, BoxedHook, HookEnv, HookRegistry, SubproofVerifier};
77
#[derive(Clone, Default)]
88
pub struct SP1Context<'a> {
99
/// The registry of hooks invokable from inside SP1.
10-
/// `None` denotes the default list of hooks.
10+
///
11+
/// Note: `None` denotes the default list of hooks.
1112
pub hook_registry: Option<HookRegistry<'a>>,
13+
14+
/// The verifier for verifying subproofs.
1215
pub subproof_verifier: Option<Arc<dyn SubproofVerifier + 'a>>,
16+
17+
/// The maximum number of cpu cycles to use for execution.
18+
pub max_cycles: Option<u64>,
1319
}
1420

1521
#[derive(Clone, Default)]
1622
pub struct SP1ContextBuilder<'a> {
1723
no_default_hooks: bool,
1824
hook_registry_entries: Vec<(u32, BoxedHook<'a>)>,
1925
subproof_verifier: Option<Arc<dyn SubproofVerifier + 'a>>,
26+
max_cycles: Option<u64>,
2027
}
2128

2229
impl<'a> SP1Context<'a> {
@@ -52,9 +59,11 @@ impl<'a> SP1ContextBuilder<'a> {
5259
HookRegistry { table }
5360
});
5461
let subproof_verifier = take(&mut self.subproof_verifier);
62+
let cycle_limit = take(&mut self.max_cycles);
5563
SP1Context {
5664
hook_registry,
5765
subproof_verifier,
66+
max_cycles: cycle_limit,
5867
}
5968
}
6069

@@ -91,6 +100,12 @@ impl<'a> SP1ContextBuilder<'a> {
91100
self.subproof_verifier = Some(subproof_verifier);
92101
self
93102
}
103+
104+
/// Set the maximum number of cpu cycles to use for execution.
105+
pub fn max_cycles(&mut self, max_cycles: u64) -> &mut Self {
106+
self.max_cycles = Some(max_cycles);
107+
self
108+
}
94109
}
95110

96111
#[cfg(test)]
@@ -104,9 +119,11 @@ mod tests {
104119
let SP1Context {
105120
hook_registry,
106121
subproof_verifier,
122+
max_cycles: cycle_limit,
107123
} = SP1Context::builder().build();
108124
assert!(hook_registry.is_none());
109125
assert!(subproof_verifier.is_none());
126+
assert!(cycle_limit.is_none());
110127
}
111128

112129
#[test]

core/src/runtime/mod.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ pub struct Runtime<'a> {
103103

104104
/// Registry of hooks, to be invoked by writing to certain file descriptors.
105105
pub hook_registry: HookRegistry<'a>,
106+
107+
/// The maximum number of cpu cycles to use for execution.
108+
pub max_cycles: Option<u64>,
106109
}
107110

108111
#[derive(Error, Debug)]
@@ -115,6 +118,8 @@ pub enum ExecutionError {
115118
UnsupportedSyscall(u32),
116119
#[error("breakpoint encountered")]
117120
Breakpoint(),
121+
#[error("exceeded cycle limit of {0}")]
122+
ExceededCycleLimit(u64),
118123
#[error("got unimplemented as opcode")]
119124
Unimplemented(),
120125
}
@@ -176,6 +181,7 @@ impl<'a> Runtime<'a> {
176181
print_report: false,
177182
subproof_verifier,
178183
hook_registry,
184+
max_cycles: context.max_cycles,
179185
}
180186
}
181187

@@ -992,6 +998,13 @@ impl<'a> Runtime<'a> {
992998
self.state.channel = 0;
993999
}
9941000

1001+
// If the cycle limit is exceeded, return an error.
1002+
if let Some(max_cycles) = self.max_cycles {
1003+
if self.state.global_clk >= max_cycles {
1004+
return Err(ExecutionError::ExceededCycleLimit(max_cycles));
1005+
}
1006+
}
1007+
9951008
Ok(self.state.pc.wrapping_sub(self.program.pc_base)
9961009
>= (self.program.instructions.len() * 4) as u32)
9971010
}
@@ -1105,7 +1118,9 @@ impl<'a> Runtime<'a> {
11051118

11061119
// Ensure that all proofs and input bytes were read, otherwise warn the user.
11071120
if self.state.proof_stream_ptr != self.state.proof_stream.len() {
1108-
panic!("Not all proofs were read. Proving will fail during recursion. Did you pass too many proofs in or forget to call verify_sp1_proof?");
1121+
panic!(
1122+
"Not all proofs were read. Proving will fail during recursion. Did you pass too many proofs in or forget to call verify_sp1_proof?"
1123+
);
11091124
}
11101125
if self.state.input_stream_ptr != self.state.input_stream.len() {
11111126
log::warn!("Not all input bytes were read.");

sdk/src/action.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ impl<'a> Execute<'a> {
6363
self.context_builder.without_default_hooks();
6464
self
6565
}
66+
67+
/// Set the maximum number of cpu cycles to use for execution.
68+
///
69+
/// If the cycle limit is exceeded, execution will return [sp1_core::runtime::ExecutionError::ExceededCycleLimit].
70+
pub fn max_cycles(mut self, max_cycles: u64) -> Self {
71+
self.context_builder.max_cycles(max_cycles);
72+
self
73+
}
6674
}
6775

6876
/// Builder to prepare and configure proving execution of a program on an input.
@@ -175,4 +183,12 @@ impl<'a> Prove<'a> {
175183
self.opts.reconstruct_commitments = value;
176184
self
177185
}
186+
187+
/// Set the maximum number of cpu cycles to use for execution.
188+
///
189+
/// If the cycle limit is exceeded, execution will return [sp1_core::runtime::ExecutionError::ExceededCycleLimit].
190+
pub fn cycle_limit(mut self, cycle_limit: u64) -> Self {
191+
self.context_builder.max_cycles(cycle_limit);
192+
self
193+
}
178194
}

sdk/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,17 @@ mod tests {
335335
client.execute(elf, stdin).run().unwrap();
336336
}
337337

338+
#[should_panic]
339+
#[test]
340+
fn test_cycle_limit_fail() {
341+
utils::setup_logger();
342+
let client = ProverClient::local();
343+
let elf = include_bytes!("../../tests/panic/elf/riscv32im-succinct-zkvm-elf");
344+
let mut stdin = SP1Stdin::new();
345+
stdin.write(&10usize);
346+
client.execute(elf, stdin).max_cycles(1).run().unwrap();
347+
}
348+
338349
#[test]
339350
fn test_e2e_prove_plonk() {
340351
utils::setup_logger();

sdk/src/network/prover.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ fn warn_if_not_default(opts: &SP1ProverOpts, context: &SP1Context) {
162162
let SP1Context {
163163
hook_registry,
164164
subproof_verifier,
165+
..
165166
} = context;
166167
if hook_registry.is_some() {
167168
tracing::warn!(

0 commit comments

Comments
 (0)