sp1_core_executor/
context.rs1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct StatusCode(u32);
19
20impl StatusCode {
21 pub const SUCCESS: Self = Self(0);
23 pub const PANIC: Self = Self(1);
25 pub const ANY: Self = Self(u32::MAX);
27
28 #[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 #[must_use]
47 pub const fn as_u32(&self) -> u32 {
48 self.0
49 }
50
51 #[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#[derive(Clone)]
60pub struct SP1Context<'a> {
61 pub hook_registry: Option<HookRegistry<'a>>,
65
66 pub subproof_verifier: Option<Arc<dyn SubproofVerifier>>,
68
69 pub max_cycles: Option<u64>,
71
72 pub deferred_proof_verification: bool,
74
75 pub expected_exit_code: StatusCode,
77
78 pub calculate_gas: bool,
83
84 pub proof_nonce: [u32; PROOF_NONCE_NUM_WORDS],
87
88 pub io_options: IoOptions<'a>,
90}
91
92impl Default for SP1Context<'_> {
93 fn default() -> Self {
94 Self::builder().build()
95 }
96}
97
98pub 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 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 #[must_use]
121 pub fn builder() -> SP1ContextBuilder<'a> {
122 SP1ContextBuilder::new()
123 }
124}
125
126impl<'a> SP1ContextBuilder<'a> {
127 #[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 deferred_proof_verification: true,
139 calculate_gas: true,
140 expected_exit_code: None,
141 proof_nonce: [0, 0, 0, 0], io_options: IoOptions::new(),
143 }
144 }
145
146 pub fn build(&mut self) -> SP1Context<'a> {
150 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 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 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 pub fn without_default_hooks(&mut self) -> &mut Self {
217 self.no_default_hooks = true;
218 self
219 }
220
221 pub fn calculate_gas(&mut self, value: bool) -> &mut Self {
228 self.calculate_gas = value;
229 self
230 }
231
232 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 pub fn max_cycles(&mut self, max_cycles: u64) -> &mut Self {
243 self.max_cycles = Some(max_cycles);
244 self
245 }
246
247 pub fn set_deferred_proof_verification(&mut self, value: bool) -> &mut Self {
249 self.deferred_proof_verification = value;
250 self
251 }
252
253 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 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 pub fn expected_exit_code(&mut self, code: StatusCode) -> &mut Self {
267 self.expected_exit_code = Some(code);
268 self
269 }
270
271 pub fn proof_nonce(&mut self, nonce: [u32; 4]) -> &mut Self {
274 self.proof_nonce = nonce;
275 self
276 }
277}
278
279#[derive(Default)]
285pub struct IoOptions<'a> {
286 pub stdout: Option<&'a mut dyn IoWriter>,
288 pub stderr: Option<&'a mut dyn IoWriter>,
290}
291
292impl IoOptions<'_> {
293 #[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
305pub 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}