sp1_core_executor/
context.rs

1use core::mem::take;
2
3use crate::{
4    hook::{hookify, BoxedHook, HookEnv, HookRegistry},
5    subproof::SubproofVerifier,
6};
7use hashbrown::HashMap;
8use std::io::Write;
9
10use sp1_primitives::consts::fd::LOWEST_ALLOWED_FD;
11
12/// Context to run a program inside SP1.
13#[derive(Clone)]
14pub struct SP1Context<'a> {
15    /// The registry of hooks invocable from inside SP1.
16    ///
17    /// Note: `None` denotes the default list of hooks.
18    pub hook_registry: Option<HookRegistry<'a>>,
19
20    /// The verifier for verifying subproofs.
21    pub subproof_verifier: Option<&'a dyn SubproofVerifier>,
22
23    /// The maximum number of cpu cycles to use for execution.
24    pub max_cycles: Option<u64>,
25
26    /// Deferred proof verification.
27    pub deferred_proof_verification: bool,
28
29    /// Whether gas (available in the `ExecutionReport`) should be calculated during execution.
30    /// Does nothing while proving.
31    ///
32    /// This option will noticeably slow down execution, so it should be disabled in most cases.
33    pub calculate_gas: bool,
34
35    /// The IO options for the [`SP1Executor`].
36    pub io_options: IoOptions<'a>,
37}
38
39impl Default for SP1Context<'_> {
40    fn default() -> Self {
41        Self::builder().build()
42    }
43}
44
45/// A builder for [`SP1Context`].
46pub struct SP1ContextBuilder<'a> {
47    no_default_hooks: bool,
48    hook_registry_entries: Vec<(u32, BoxedHook<'a>)>,
49    subproof_verifier: Option<&'a dyn SubproofVerifier>,
50    max_cycles: Option<u64>,
51    deferred_proof_verification: bool,
52    calculate_gas: bool,
53    io_options: IoOptions<'a>,
54}
55
56impl Default for SP1ContextBuilder<'_> {
57    fn default() -> Self {
58        Self {
59            no_default_hooks: false,
60            hook_registry_entries: Vec::new(),
61            subproof_verifier: None,
62            max_cycles: None,
63            // Always verify deferred proofs by default.
64            deferred_proof_verification: true,
65            calculate_gas: true,
66            io_options: IoOptions::default(),
67        }
68    }
69}
70
71impl<'a> SP1Context<'a> {
72    /// Create a new context builder. See [`SP1ContextBuilder`] for more details.
73    #[must_use]
74    pub fn builder() -> SP1ContextBuilder<'a> {
75        SP1ContextBuilder::new()
76    }
77}
78
79impl<'a> SP1ContextBuilder<'a> {
80    /// Create a new [`SP1ContextBuilder`].
81    ///
82    /// Prefer using [`SP1Context::builder`].
83    #[must_use]
84    pub fn new() -> Self {
85        SP1ContextBuilder::default()
86    }
87
88    /// Build and return the [`SP1Context`].
89    ///
90    /// Clears and resets the builder, allowing it to be reused.
91    pub fn build(&mut self) -> SP1Context<'a> {
92        // If hook_registry_entries is nonempty or no_default_hooks true,
93        // indicating a non-default value of hook_registry.
94        //
95        // Panics:
96        // - If any hook file descriptor is less than [`LOWEST_ALLOWED_FD`].
97        let hook_registry =
98            (!self.hook_registry_entries.is_empty() || self.no_default_hooks).then(|| {
99                let mut table = if take(&mut self.no_default_hooks) {
100                    HashMap::default()
101                } else {
102                    HookRegistry::default().table
103                };
104
105                self.hook_registry_entries
106                    .iter()
107                    .map(|(fd, _)| fd)
108                    .filter(|fd| table.contains_key(*fd))
109                    .for_each(|fd| {
110                        tracing::warn!("Overriding default hook with file descriptor {}", fd);
111                    });
112
113                // Allows overwriting default hooks.
114                table.extend(take(&mut self.hook_registry_entries));
115                HookRegistry { table }
116            });
117
118        let subproof_verifier = take(&mut self.subproof_verifier);
119        let cycle_limit = take(&mut self.max_cycles);
120        let deferred_proof_verification = take(&mut self.deferred_proof_verification);
121        let calculate_gas = take(&mut self.calculate_gas);
122        SP1Context {
123            hook_registry,
124            subproof_verifier,
125            max_cycles: cycle_limit,
126            deferred_proof_verification,
127            calculate_gas,
128            io_options: take(&mut self.io_options),
129        }
130    }
131
132    /// Add a runtime [Hook](super::Hook) into the context.
133    ///
134    /// Hooks may be invoked from within SP1 by writing to the specified file descriptor `fd`
135    /// with [`sp1_zkvm::io::write`], returning a list of arbitrary data that may be read
136    /// with successive calls to [`sp1_zkvm::io::read`].
137    ///
138    /// # Panics
139    /// Panics if `fd` <= [`LOWEST_ALLOWED_FD`].
140    pub fn hook(
141        &mut self,
142        fd: u32,
143        f: impl FnMut(HookEnv, &[u8]) -> Vec<Vec<u8>> + Send + Sync + 'a,
144    ) -> &mut Self {
145        assert!(fd > LOWEST_ALLOWED_FD, "Hook file descriptors must be greater than 10.");
146
147        self.hook_registry_entries.push((fd, hookify(f)));
148        self
149    }
150
151    /// Avoid registering the default hooks in the runtime.
152    ///
153    /// It is not necessary to call this to override hooks --- instead, simply
154    /// register a hook with the same value of `fd` by calling [`Self::hook`].
155    pub fn without_default_hooks(&mut self) -> &mut Self {
156        self.no_default_hooks = true;
157        self
158    }
159
160    /// Whether gas should be calculated while executing. Defaults to `true`.
161    /// Determines whether the gas field in the `ExecutionReport` is `None` or `Some`.
162    ///
163    /// During proving, gas is not calculated, so this option has no effect.
164    ///
165    /// Disabling gas calculation will likely speed up execution.
166    pub fn calculate_gas(&mut self, value: bool) -> &mut Self {
167        self.calculate_gas = value;
168        self
169    }
170
171    /// Add a subproof verifier.
172    ///
173    /// The verifier is used to sanity check `verify_sp1_proof` during runtime.
174    pub fn subproof_verifier(&mut self, subproof_verifier: &'a dyn SubproofVerifier) -> &mut Self {
175        self.subproof_verifier = Some(subproof_verifier);
176        self
177    }
178
179    /// Set the maximum number of cpu cycles to use for execution.
180    /// `report.total_instruction_count()` will be less than or equal to `max_cycles`.
181    pub fn max_cycles(&mut self, max_cycles: u64) -> &mut Self {
182        self.max_cycles = Some(max_cycles);
183        self
184    }
185
186    /// Set the deferred proof verification flag.
187    pub fn set_deferred_proof_verification(&mut self, value: bool) -> &mut Self {
188        self.deferred_proof_verification = value;
189        self
190    }
191
192    /// Set the `stdout` writer.
193    pub fn stdout<W: IoWriter>(&mut self, writer: &'a mut W) -> &mut Self {
194        self.io_options.stdout = Some(writer);
195        self
196    }
197
198    /// Set the `stderr` writer.
199    pub fn stderr<W: IoWriter>(&mut self, writer: &'a mut W) -> &mut Self {
200        self.io_options.stderr = Some(writer);
201        self
202    }
203}
204
205/// The IO options for the [`SP1Executor`].
206///
207/// This struct is used to redirect the `stdout` and `stderr` of the [`SP1Executor`].
208#[derive(Default)]
209pub struct IoOptions<'a> {
210    /// A writer to redirect `stdout` to.
211    pub stdout: Option<&'a mut dyn IoWriter>,
212    /// A writer to redirect `stderr` to.
213    pub stderr: Option<&'a mut dyn IoWriter>,
214}
215
216impl Clone for IoOptions<'_> {
217    fn clone(&self) -> Self {
218        IoOptions { stdout: None, stderr: None }
219    }
220}
221
222/// A trait for [`Write`] types to be used in the executor.
223///
224/// This trait is generically implemented for any [`Write`] + [`Send`] type.
225pub trait IoWriter: Write + Send {}
226
227impl<W: Write + Send> IoWriter for W {}
228
229#[cfg(test)]
230mod tests {
231    use crate::{subproof::NoOpSubproofVerifier, SP1Context};
232
233    #[test]
234    fn defaults() {
235        let SP1Context { hook_registry, subproof_verifier, max_cycles: cycle_limit, .. } =
236            SP1Context::builder().build();
237        assert!(hook_registry.is_none());
238        assert!(subproof_verifier.is_none());
239        assert!(cycle_limit.is_none());
240    }
241
242    #[test]
243    fn without_default_hooks() {
244        let SP1Context { hook_registry, .. } =
245            SP1Context::builder().without_default_hooks().build();
246        assert!(hook_registry.unwrap().table.is_empty());
247    }
248
249    #[test]
250    fn with_custom_hook() {
251        let SP1Context { hook_registry, .. } =
252            SP1Context::builder().hook(30, |_, _| vec![]).build();
253        assert!(hook_registry.unwrap().table.contains_key(&30));
254    }
255
256    #[test]
257    fn without_default_hooks_with_custom_hook() {
258        let SP1Context { hook_registry, .. } =
259            SP1Context::builder().without_default_hooks().hook(30, |_, _| vec![]).build();
260        assert_eq!(&hook_registry.unwrap().table.into_keys().collect::<Vec<_>>(), &[30]);
261    }
262
263    #[test]
264    fn subproof_verifier() {
265        let verifier = NoOpSubproofVerifier;
266
267        let SP1Context { subproof_verifier, .. } =
268            SP1Context::builder().subproof_verifier(&verifier).build();
269        assert!(subproof_verifier.is_some());
270    }
271}