Skip to main content

sp1_core_executor/
context.rs

1use core::mem::take;
2use std::sync::Arc;
3
4use crate::{
5    hook::{hookify, BoxedHook, HookEnv, HookRegistry},
6    subproof::SubproofVerifier,
7};
8use hashbrown::HashMap;
9use sp1_hypercube::air::PROOF_NONCE_NUM_WORDS;
10use std::io::Write;
11
12use sp1_primitives::consts::fd::LOWEST_ALLOWED_FD;
13
14/// The status code of the execution.
15///
16/// Currently the only supported status codes are `0` for success and `1` for failure.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct StatusCode(u32);
19
20impl StatusCode {
21    /// The success status code.
22    pub const SUCCESS: Self = Self(0);
23    /// The panic status code.
24    pub const PANIC: Self = Self(1);
25    /// Accept either success or panic.
26    pub const ANY: Self = Self(u32::MAX);
27
28    /// Create a new status code from a u32.
29    ///
30    /// # Arguments
31    /// * `code` - The status code to create.
32    ///
33    /// # Returns
34    /// * `Some(StatusCode)` - The status code if it is valid: {0, 1}.
35    /// * `None` - The status code is not valid.
36    #[must_use]
37    pub const fn new(code: u32) -> Option<Self> {
38        match code {
39            0 => Some(Self::SUCCESS),
40            1 => Some(Self::PANIC),
41            _ => None,
42        }
43    }
44
45    /// Get the u32 value of the status code.
46    #[must_use]
47    pub const fn as_u32(&self) -> u32 {
48        self.0
49    }
50
51    /// Check if the status code is equal to the given value.
52    #[must_use]
53    pub const fn is_accepted_code(&self, code: u32) -> bool {
54        (code == 0 || code == 1) && (self.0 == Self::ANY.0 || self.0 == code)
55    }
56}
57
58/// Context to run a program inside SP1.
59#[derive(Clone)]
60pub struct SP1Context<'a> {
61    /// The registry of hooks invocable from inside SP1.
62    ///
63    /// Note: `None` denotes the default list of hooks.
64    pub hook_registry: Option<HookRegistry<'a>>,
65
66    /// The verifier for verifying subproofs.
67    pub subproof_verifier: Option<Arc<dyn SubproofVerifier>>,
68
69    /// The maximum number of cpu cycles to use for execution.
70    pub max_cycles: Option<u64>,
71
72    /// Deferred proof verification.
73    pub deferred_proof_verification: bool,
74
75    /// The expected exit code of the program.
76    pub expected_exit_code: StatusCode,
77
78    /// Whether gas (available in the `ExecutionReport`) should be calculated during execution.
79    /// Does nothing while proving.
80    ///
81    /// This option will noticeably slow down execution, so it should be disabled in most cases.
82    pub calculate_gas: bool,
83
84    /// The nonce used for this specific proof execution (4 x u32 = 128 bits of entropy).
85    /// This nonce ensures each proof is unique even for identical programs and inputs.
86    pub proof_nonce: [u32; PROOF_NONCE_NUM_WORDS],
87
88    /// The IO options for the [`SP1Executor`].
89    pub io_options: IoOptions<'a>,
90}
91
92impl Default for SP1Context<'_> {
93    fn default() -> Self {
94        Self::builder().build()
95    }
96}
97
98/// A builder for [`SP1Context`].
99pub struct SP1ContextBuilder<'a> {
100    no_default_hooks: bool,
101    hook_registry_entries: Vec<(u32, BoxedHook<'a>)>,
102    subproof_verifier: Option<Arc<dyn SubproofVerifier>>,
103    max_cycles: Option<u64>,
104    deferred_proof_verification: bool,
105    calculate_gas: bool,
106    expected_exit_code: Option<StatusCode>,
107    proof_nonce: [u32; 4],
108    // TODO remove the lifetime here, change stdout and stderr options to accept channels.
109    io_options: IoOptions<'a>,
110}
111
112impl Default for SP1ContextBuilder<'_> {
113    fn default() -> Self {
114        Self::new()
115    }
116}
117
118impl<'a> SP1Context<'a> {
119    /// Create a new context builder. See [`SP1ContextBuilder`] for more details.
120    #[must_use]
121    pub fn builder() -> SP1ContextBuilder<'a> {
122        SP1ContextBuilder::new()
123    }
124}
125
126impl<'a> SP1ContextBuilder<'a> {
127    /// Create a new [`SP1ContextBuilder`].
128    ///
129    /// Prefer using [`SP1Context::builder`].
130    #[must_use]
131    pub const fn new() -> Self {
132        Self {
133            no_default_hooks: false,
134            hook_registry_entries: Vec::new(),
135            subproof_verifier: None,
136            max_cycles: None,
137            // Always verify deferred proofs by default.
138            deferred_proof_verification: true,
139            calculate_gas: true,
140            expected_exit_code: None,
141            proof_nonce: [0, 0, 0, 0], // Default to zeros, will be set by SDK
142            io_options: IoOptions::new(),
143        }
144    }
145
146    /// Build and return the [`SP1Context`].
147    ///
148    /// Clears and resets the builder, allowing it to be reused.
149    pub fn build(&mut self) -> SP1Context<'a> {
150        // If hook_registry_entries is nonempty or no_default_hooks true,
151        // indicating a non-default value of hook_registry.
152        //
153        // Panics:
154        // - If any hook file descriptor is less than [`LOWEST_ALLOWED_FD`].
155        let hook_registry =
156            (!self.hook_registry_entries.is_empty() || self.no_default_hooks).then(|| {
157                let mut table = if take(&mut self.no_default_hooks) {
158                    HashMap::default()
159                } else {
160                    HookRegistry::default().table
161                };
162
163                self.hook_registry_entries
164                    .iter()
165                    .map(|(fd, _)| fd)
166                    .filter(|fd| table.contains_key(*fd))
167                    .for_each(|fd| {
168                        tracing::warn!("Overriding default hook with file descriptor {}", fd);
169                    });
170
171                // Allows overwriting default hooks.
172                table.extend(take(&mut self.hook_registry_entries));
173                HookRegistry { table }
174            });
175
176        let subproof_verifier = take(&mut self.subproof_verifier);
177        let cycle_limit = take(&mut self.max_cycles);
178        let deferred_proof_verification = take(&mut self.deferred_proof_verification);
179        let calculate_gas = take(&mut self.calculate_gas);
180        let proof_nonce = take(&mut self.proof_nonce);
181        SP1Context {
182            hook_registry,
183            subproof_verifier,
184            max_cycles: cycle_limit,
185            deferred_proof_verification,
186            calculate_gas,
187            proof_nonce,
188            io_options: take(&mut self.io_options),
189            expected_exit_code: self.expected_exit_code.unwrap_or(StatusCode::SUCCESS),
190        }
191    }
192
193    /// Add a runtime [Hook](super::Hook) into the context.
194    ///
195    /// Hooks may be invoked from within SP1 by writing to the specified file descriptor `fd`
196    /// with [`sp1_zkvm::io::write`], returning a list of arbitrary data that may be read
197    /// with successive calls to [`sp1_zkvm::io::read`].
198    ///
199    /// # Panics
200    /// Panics if `fd` <= [`LOWEST_ALLOWED_FD`].
201    pub fn hook(
202        &mut self,
203        fd: u32,
204        f: impl FnMut(HookEnv, &[u8]) -> Vec<Vec<u8>> + Send + Sync + 'a,
205    ) -> &mut Self {
206        assert!(fd > LOWEST_ALLOWED_FD, "Hook file descriptors must be greater than 10.");
207
208        self.hook_registry_entries.push((fd, hookify(f)));
209        self
210    }
211
212    /// Avoid registering the default hooks in the runtime.
213    ///
214    /// It is not necessary to call this to override hooks --- instead, simply
215    /// register a hook with the same value of `fd` by calling [`Self::hook`].
216    pub fn without_default_hooks(&mut self) -> &mut Self {
217        self.no_default_hooks = true;
218        self
219    }
220
221    /// Whether gas should be calculated while executing. Defaults to `true`.
222    /// Determines whether the gas field in the `ExecutionReport` is `None` or `Some`.
223    ///
224    /// During proving, gas is not calculated, so this option has no effect.
225    ///
226    /// Disabling gas calculation will likely speed up execution.
227    pub fn calculate_gas(&mut self, value: bool) -> &mut Self {
228        self.calculate_gas = value;
229        self
230    }
231
232    /// Add a subproof verifier.
233    ///
234    /// The verifier is used to sanity check `verify_sp1_proof` during runtime.
235    pub fn subproof_verifier(&mut self, subproof_verifier: Arc<dyn SubproofVerifier>) -> &mut Self {
236        self.subproof_verifier = Some(subproof_verifier);
237        self
238    }
239
240    /// Set the maximum number of cpu cycles to use for execution.
241    /// `report.total_instruction_count()` will be less than or equal to `max_cycles`.
242    pub fn max_cycles(&mut self, max_cycles: u64) -> &mut Self {
243        self.max_cycles = Some(max_cycles);
244        self
245    }
246
247    /// Set the deferred proof verification flag.
248    pub fn set_deferred_proof_verification(&mut self, value: bool) -> &mut Self {
249        self.deferred_proof_verification = value;
250        self
251    }
252
253    /// Set the `stdout` writer.
254    pub fn stdout<W: IoWriter>(&mut self, writer: &'a mut W) -> &mut Self {
255        self.io_options.stdout = Some(writer);
256        self
257    }
258
259    /// Set the `stderr` writer.
260    pub fn stderr<W: IoWriter>(&mut self, writer: &'a mut W) -> &mut Self {
261        self.io_options.stderr = Some(writer);
262        self
263    }
264
265    /// Set the expected exit code of the program.
266    pub fn expected_exit_code(&mut self, code: StatusCode) -> &mut Self {
267        self.expected_exit_code = Some(code);
268        self
269    }
270
271    /// Set the proof nonce for this execution.
272    /// This nonce ensures each proof is unique even for identical programs and inputs.
273    pub fn proof_nonce(&mut self, nonce: [u32; 4]) -> &mut Self {
274        self.proof_nonce = nonce;
275        self
276    }
277}
278
279/// The IO options for the [`SP1Executor`].
280///
281/// This struct is used to redirect the `stdout` and `stderr` of the [`SP1Executor`].
282///
283/// Note: Cloning this type will not clone the writers.
284#[derive(Default)]
285pub struct IoOptions<'a> {
286    /// A writer to redirect `stdout` to.
287    pub stdout: Option<&'a mut dyn IoWriter>,
288    /// A writer to redirect `stderr` to.
289    pub stderr: Option<&'a mut dyn IoWriter>,
290}
291
292impl IoOptions<'_> {
293    /// Create a new [`IoOptions`] with no writers.
294    #[must_use]
295    pub const fn new() -> Self {
296        Self { stdout: None, stderr: None }
297    }
298}
299impl Clone for IoOptions<'_> {
300    fn clone(&self) -> Self {
301        IoOptions { stdout: None, stderr: None }
302    }
303}
304
305/// A trait for [`Write`] types to be used in the executor.
306///
307/// This trait is generically implemented for any [`Write`] + [`Send`] type.
308pub trait IoWriter: Write + Send + Sync {}
309
310impl<W: Write + Send + Sync> IoWriter for W {}
311
312#[cfg(test)]
313mod tests {
314    use std::sync::Arc;
315
316    use crate::{subproof::NoOpSubproofVerifier, SP1Context};
317
318    #[test]
319    fn defaults() {
320        let SP1Context { hook_registry, subproof_verifier, max_cycles: cycle_limit, .. } =
321            SP1Context::builder().build();
322        assert!(hook_registry.is_none());
323        assert!(subproof_verifier.is_none());
324        assert!(cycle_limit.is_none());
325    }
326
327    #[test]
328    fn without_default_hooks() {
329        let SP1Context { hook_registry, .. } =
330            SP1Context::builder().without_default_hooks().build();
331        assert!(hook_registry.unwrap().table.is_empty());
332    }
333
334    #[test]
335    fn with_custom_hook() {
336        let SP1Context { hook_registry, .. } =
337            SP1Context::builder().hook(30, |_, _| vec![]).build();
338        assert!(hook_registry.unwrap().table.contains_key(&30));
339    }
340
341    #[test]
342    fn without_default_hooks_with_custom_hook() {
343        let SP1Context { hook_registry, .. } =
344            SP1Context::builder().without_default_hooks().hook(30, |_, _| vec![]).build();
345        assert_eq!(&hook_registry.unwrap().table.into_keys().collect::<Vec<_>>(), &[30]);
346    }
347
348    #[test]
349    fn subproof_verifier() {
350        let verifier = NoOpSubproofVerifier;
351
352        let SP1Context { subproof_verifier, .. } =
353            SP1Context::builder().subproof_verifier(Arc::new(verifier)).build();
354        assert!(subproof_verifier.is_some());
355    }
356}