simplicity/bit_machine/
mod.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! Simplicity Execution
4//!
5//! Implementation of the Bit Machine, without TCO, as TCO precludes some
6//! frame management optimizations which can be used to great benefit.
7//!
8
9mod frame;
10mod limits;
11
12use std::collections::HashSet;
13use std::error;
14use std::fmt;
15use std::sync::Arc;
16
17use crate::jet::{Jet, JetFailed};
18use crate::node::{self, RedeemNode};
19use crate::types::Final;
20use crate::{analysis, Ihr};
21use crate::{Cmr, FailEntropy, Value};
22use frame::Frame;
23
24pub use self::limits::LimitError;
25
26/// An execution context for a Simplicity program
27pub struct BitMachine {
28    /// Space for bytes that read and write frames point to.
29    /// (De)allocation happens LIFO from left to right
30    data: Vec<u8>,
31    /// Top of data stack; index of first unused bit
32    next_frame_start: usize,
33    /// Read frame stack
34    read: Vec<Frame>,
35    /// Write frame stack
36    write: Vec<Frame>,
37    /// Acceptable source type
38    source_ty: Arc<Final>,
39}
40
41impl BitMachine {
42    /// Construct a Bit Machine with enough space to execute the given program.
43    pub fn for_program<J: Jet>(program: &RedeemNode<J>) -> Result<Self, LimitError> {
44        LimitError::check_program(program)?;
45        let io_width = program.arrow().source.bit_width() + program.arrow().target.bit_width();
46
47        Ok(Self {
48            data: vec![0; (io_width + program.bounds().extra_cells).div_ceil(8)],
49            next_frame_start: 0,
50            read: Vec::with_capacity(program.bounds().extra_frames + analysis::IO_EXTRA_FRAMES),
51            write: Vec::with_capacity(program.bounds().extra_frames + analysis::IO_EXTRA_FRAMES),
52            source_ty: program.arrow().source.clone(),
53        })
54    }
55
56    #[cfg(test)]
57    pub fn test_exec<J: Jet>(
58        program: Arc<crate::node::ConstructNode<J>>,
59        env: &J::Environment,
60    ) -> Result<Value, ExecutionError> {
61        use crate::node::SimpleFinalizer;
62
63        let prog = program
64            .finalize_types_non_program()
65            .expect("finalizing types")
66            .finalize(&mut SimpleFinalizer::new(None.into_iter()))
67            .expect("finalizing");
68        let mut mac = BitMachine::for_program(&prog).expect("program has reasonable bounds");
69        mac.exec(&prog, env)
70    }
71
72    /// Push a new frame of given size onto the write frame stack
73    fn new_frame(&mut self, len: usize) {
74        debug_assert!(
75            self.next_frame_start + len <= self.data.len() * 8,
76            "Data out of bounds: number of cells"
77        );
78        debug_assert!(
79            self.write.len() + self.read.len() < self.read.capacity(),
80            "Stacks out of bounds: number of frames"
81        );
82
83        self.write.push(Frame::new(self.next_frame_start, len));
84        self.next_frame_start += len;
85    }
86
87    /// Move the active write frame to the read frame stack
88    fn move_frame(&mut self) {
89        let mut _active_write_frame = self.write.pop().unwrap();
90        _active_write_frame.reset_cursor();
91        self.read.push(_active_write_frame);
92    }
93
94    /// Drop the active read frame
95    fn drop_frame(&mut self) {
96        let active_read_frame = self.read.pop().unwrap();
97        self.next_frame_start -= active_read_frame.bit_width();
98        assert_eq!(self.next_frame_start, active_read_frame.start());
99    }
100
101    /// Write a single bit to the active write frame
102    fn write_bit(&mut self, bit: bool) {
103        self.write
104            .last_mut()
105            .expect("Empty write frame stack")
106            .write_bit(bit, &mut self.data);
107    }
108
109    /// Move the cursor of the active write frame forward by
110    /// a specified number of bits
111    fn skip(&mut self, n: usize) {
112        // short circuit n = 0
113        if n == 0 {
114            return;
115        }
116        let idx = self.write.len() - 1;
117        self.write[idx].move_cursor_forward(n);
118    }
119
120    /// Copy the given number of bits from the active read frame
121    /// to the active write frame
122    fn copy(&mut self, n: usize) {
123        // short circuit n = 0
124        if n == 0 {
125            return;
126        }
127        let widx = self.write.len() - 1;
128        let ridx = self.read.len() - 1;
129        self.write[widx].copy_from(&self.read[ridx], n, &mut self.data);
130    }
131
132    /// Move the cursor of the active read frame forward
133    /// by the given number of bits
134    fn fwd(&mut self, n: usize) {
135        // short circuit n = 0
136        if n == 0 {
137            return;
138        }
139        let idx = self.read.len() - 1;
140        self.read[idx].move_cursor_forward(n);
141    }
142
143    /// Move the cursor of the active read frame back
144    /// by the given number of bits
145    fn back(&mut self, n: usize) {
146        // short circuit n = 0
147        if n == 0 {
148            return;
149        }
150        let idx = self.read.len() - 1;
151        self.read[idx].move_cursor_backward(n);
152    }
153
154    /// Write a big-endian u8 value to the active write frame
155    fn write_u8(&mut self, value: u8) {
156        self.write
157            .last_mut()
158            .expect("Empty write frame stack")
159            .write_u8(value, &mut self.data);
160    }
161
162    /// Read a bit from the active read frame
163    fn read_bit(&mut self) -> bool {
164        self.read
165            .last_mut()
166            .expect("Empty read frame stack")
167            .read_bit(&self.data)
168    }
169
170    /// Write a bit string to the active write frame
171    fn write_bytes(&mut self, bytes: &[u8]) {
172        for bit in bytes {
173            self.write_u8(*bit);
174        }
175    }
176
177    /// Write a value to the current write frame
178    fn write_value(&mut self, val: &Value) {
179        for bit in val.iter_padded() {
180            self.write_bit(bit);
181        }
182    }
183
184    /// Return the bit width of the active read frame.
185    fn active_read_bit_width(&self) -> usize {
186        self.read.last().map(|frame| frame.bit_width()).unwrap_or(0)
187    }
188
189    /// Return the bit width of the active write frame.
190    fn active_write_bit_width(&self) -> usize {
191        self.write
192            .last()
193            .map(|frame| frame.bit_width())
194            .unwrap_or(0)
195    }
196
197    /// Add a read frame with some given value in it, as input to the
198    /// program
199    pub fn input(&mut self, input: &Value) -> Result<(), ExecutionError> {
200        if !input.is_of_type(&self.source_ty) {
201            return Err(ExecutionError::InputWrongType(self.source_ty.clone()));
202        }
203        // Unit value doesn't need extra frame
204        if !input.is_empty() {
205            self.new_frame(input.padded_len());
206            self.write_value(input);
207            self.move_frame();
208        }
209        Ok(())
210    }
211
212    /// Execute the given `program` on the Bit Machine, using the given environment.
213    ///
214    ///  ## Precondition
215    ///
216    /// The Bit Machine is constructed via [`Self::for_program()`] to ensure enough space.
217    pub fn exec<J: Jet>(
218        &mut self,
219        program: &RedeemNode<J>,
220        env: &J::Environment,
221    ) -> Result<Value, ExecutionError> {
222        self.exec_with_tracker(program, env, &mut NoTracker)
223    }
224
225    /// Execute the given `program` on the Bit Machine and track executed case branches.
226    ///
227    /// If the program runs successfully, then two sets of IHRs are returned:
228    ///
229    /// 1) The IHRs of case nodes whose _left_ branch was executed.
230    /// 2) The IHRs of case nodes whose _right_ branch was executed.
231    ///
232    /// ## Precondition
233    ///
234    /// The Bit Machine is constructed via [`Self::for_program()`] to ensure enough space.
235    pub(crate) fn exec_prune<J: Jet>(
236        &mut self,
237        program: &RedeemNode<J>,
238        env: &J::Environment,
239    ) -> Result<SetTracker, ExecutionError> {
240        let mut tracker = SetTracker::default();
241        self.exec_with_tracker(program, env, &mut tracker)?;
242        Ok(tracker)
243    }
244
245    fn exec_with_tracker<J: Jet, T: CaseTracker>(
246        &mut self,
247        program: &RedeemNode<J>,
248        env: &J::Environment,
249        tracker: &mut T,
250    ) -> Result<Value, ExecutionError> {
251        enum CallStack<'a, J: Jet> {
252            Goto(&'a RedeemNode<J>),
253            MoveFrame,
254            DropFrame,
255            CopyFwd(usize),
256            Back(usize),
257        }
258
259        // Not used, but useful for debugging, so keep it around
260        impl<J: Jet> fmt::Debug for CallStack<'_, J> {
261            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
262                match self {
263                    CallStack::Goto(ins) => write!(f, "goto {}", ins.inner()),
264                    CallStack::MoveFrame => f.write_str("move frame"),
265                    CallStack::DropFrame => f.write_str("drop frame"),
266                    CallStack::CopyFwd(n) => write!(f, "copy/fwd {}", n),
267                    CallStack::Back(n) => write!(f, "back {}", n),
268                }
269            }
270        }
271
272        if self.read.is_empty() != self.source_ty.is_empty() {
273            return Err(ExecutionError::InputWrongType(self.source_ty.clone()));
274        }
275
276        let mut ip = program;
277        let mut call_stack = vec![];
278
279        let output_width = ip.arrow().target.bit_width();
280        if output_width > 0 {
281            self.new_frame(output_width);
282        }
283
284        'main_loop: loop {
285            match ip.inner() {
286                node::Inner::Unit => {}
287                node::Inner::Iden => {
288                    let size_a = ip.arrow().source.bit_width();
289                    self.copy(size_a);
290                }
291                node::Inner::InjL(left) => {
292                    let (b, c) = ip.arrow().target.as_sum().unwrap();
293                    self.write_bit(false);
294                    self.skip(b.pad_left(c));
295                    call_stack.push(CallStack::Goto(left));
296                }
297                node::Inner::InjR(left) => {
298                    let (b, c) = ip.arrow().target.as_sum().unwrap();
299                    self.write_bit(true);
300                    self.skip(b.pad_right(c));
301                    call_stack.push(CallStack::Goto(left));
302                }
303                node::Inner::Pair(left, right) => {
304                    call_stack.push(CallStack::Goto(right));
305                    call_stack.push(CallStack::Goto(left));
306                }
307                node::Inner::Comp(left, right) => {
308                    let size_b = left.arrow().target.bit_width();
309
310                    self.new_frame(size_b);
311                    call_stack.push(CallStack::DropFrame);
312                    call_stack.push(CallStack::Goto(right));
313                    call_stack.push(CallStack::MoveFrame);
314                    call_stack.push(CallStack::Goto(left));
315                }
316                node::Inner::Disconnect(left, right) => {
317                    let size_prod_256_a = left.arrow().source.bit_width();
318                    let size_a = size_prod_256_a - 256;
319                    let size_prod_b_c = left.arrow().target.bit_width();
320                    let size_b = size_prod_b_c - right.arrow().source.bit_width();
321
322                    self.new_frame(size_prod_256_a);
323                    self.write_bytes(right.cmr().as_ref());
324                    self.copy(size_a);
325                    self.move_frame();
326                    self.new_frame(size_prod_b_c);
327
328                    // Remember that call stack pushes are executed in reverse order
329                    call_stack.push(CallStack::DropFrame);
330                    call_stack.push(CallStack::DropFrame);
331                    call_stack.push(CallStack::Goto(right));
332                    call_stack.push(CallStack::CopyFwd(size_b));
333                    call_stack.push(CallStack::MoveFrame);
334                    call_stack.push(CallStack::Goto(left));
335                }
336                node::Inner::Take(left) => call_stack.push(CallStack::Goto(left)),
337                node::Inner::Drop(left) => {
338                    let size_a = ip.arrow().source.as_product().unwrap().0.bit_width();
339                    self.fwd(size_a);
340                    call_stack.push(CallStack::Back(size_a));
341                    call_stack.push(CallStack::Goto(left));
342                }
343                node::Inner::Case(..) | node::Inner::AssertL(..) | node::Inner::AssertR(..) => {
344                    let choice_bit = self.read[self.read.len() - 1].peek_bit(&self.data);
345
346                    let (sum_a_b, _c) = ip.arrow().source.as_product().unwrap();
347                    let (a, b) = sum_a_b.as_sum().unwrap();
348
349                    match (ip.inner(), choice_bit) {
350                        (node::Inner::Case(_, right), true)
351                        | (node::Inner::AssertR(_, right), true) => {
352                            self.fwd(1 + a.pad_right(b));
353                            call_stack.push(CallStack::Back(1 + a.pad_right(b)));
354                            call_stack.push(CallStack::Goto(right));
355                            tracker.track_right(ip.ihr());
356                        }
357                        (node::Inner::Case(left, _), false)
358                        | (node::Inner::AssertL(left, _), false) => {
359                            self.fwd(1 + a.pad_left(b));
360                            call_stack.push(CallStack::Back(1 + a.pad_left(b)));
361                            call_stack.push(CallStack::Goto(left));
362                            tracker.track_left(ip.ihr());
363                        }
364                        (node::Inner::AssertL(_, r_cmr), true) => {
365                            return Err(ExecutionError::ReachedPrunedBranch(*r_cmr))
366                        }
367                        (node::Inner::AssertR(l_cmr, _), false) => {
368                            return Err(ExecutionError::ReachedPrunedBranch(*l_cmr))
369                        }
370                        _ => unreachable!(),
371                    }
372                }
373                node::Inner::Witness(value) => self.write_value(value),
374                node::Inner::Jet(jet) => self.exec_jet(*jet, env)?,
375                node::Inner::Word(value) => self.write_value(value.as_value()),
376                node::Inner::Fail(entropy) => {
377                    return Err(ExecutionError::ReachedFailNode(*entropy))
378                }
379            }
380
381            ip = loop {
382                match call_stack.pop() {
383                    Some(CallStack::Goto(next)) => break next,
384                    Some(CallStack::MoveFrame) => self.move_frame(),
385                    Some(CallStack::DropFrame) => self.drop_frame(),
386                    Some(CallStack::CopyFwd(n)) => {
387                        self.copy(n);
388                        self.fwd(n);
389                    }
390                    Some(CallStack::Back(n)) => self.back(n),
391                    None => break 'main_loop,
392                };
393            };
394        }
395
396        if output_width > 0 {
397            let out_frame = self.write.last_mut().unwrap();
398            out_frame.reset_cursor();
399            let value = Value::from_padded_bits(
400                &mut out_frame.as_bit_iter(&self.data),
401                &program.arrow().target,
402            )
403            .expect("Decode value of output frame");
404
405            Ok(value)
406        } else {
407            Ok(Value::unit())
408        }
409    }
410
411    fn exec_jet<J: Jet>(&mut self, jet: J, env: &J::Environment) -> Result<(), JetFailed> {
412        use crate::ffi::c_jets::frame_ffi::{c_readBit, c_writeBit, CFrameItem};
413        use crate::ffi::c_jets::uword_width;
414        use crate::ffi::ffi::UWORD;
415
416        /// Create new C read frame that contains `bit_width` many bits from active read frame.
417        ///
418        /// Return C read frame together with underlying buffer.
419        ///
420        /// ## Safety
421        ///
422        /// The returned frame must outlive its buffer or there is a dangling pointer.
423        ///
424        /// ## Panics
425        ///
426        /// Active read frame has fewer bits than `bit_width`.
427        unsafe fn get_input_frame(
428            mac: &mut BitMachine,
429            bit_width: usize,
430        ) -> (CFrameItem, Vec<UWORD>) {
431            assert!(bit_width <= mac.active_read_bit_width());
432            let uword_width = uword_width(bit_width);
433            let mut buffer = vec![0; uword_width];
434
435            // Copy bits from active read frame into input frame
436            let buffer_end = buffer.as_mut_ptr().add(uword_width);
437            let mut write_frame = CFrameItem::new_write(bit_width, buffer_end);
438            for _ in 0..bit_width {
439                let bit = mac.read_bit();
440                c_writeBit(&mut write_frame, bit);
441            }
442            mac.back(bit_width);
443
444            // Convert input frame into read frame
445            let buffer_ptr = buffer.as_mut_ptr();
446            let read_frame = CFrameItem::new_read(bit_width, buffer_ptr);
447
448            (read_frame, buffer)
449        }
450
451        /// Create C write frame that is as wide as `bit_width`.
452        ///
453        /// Return C write frame together with underlying buffer.
454        ///
455        /// ## Safety
456        ///
457        /// The returned frame must outlive its buffer or there is a dangling pointer.
458        unsafe fn get_output_frame(bit_width: usize) -> (CFrameItem, Vec<UWORD>) {
459            let uword_width = uword_width(bit_width);
460            let mut buffer = vec![0; uword_width];
461
462            // Return output frame as write frame
463            let buffer_end = buffer.as_mut_ptr().add(uword_width);
464            let write_frame = CFrameItem::new_write(bit_width, buffer_end);
465
466            (write_frame, buffer)
467        }
468
469        /// Write `bit_width` many bits from `buffer` into active write frame.
470        ///
471        /// ## Panics
472        ///
473        /// Active write frame has fewer bits than `bit_width`.
474        ///
475        /// Buffer has fewer than bits than `bit_width` (converted to UWORDs).
476        fn update_active_write_frame(mac: &mut BitMachine, bit_width: usize, buffer: &[UWORD]) {
477            assert!(bit_width <= mac.active_write_bit_width());
478            assert!(uword_width(bit_width) <= buffer.len());
479            let buffer_ptr = buffer.as_ptr();
480            let mut read_frame = unsafe { CFrameItem::new_read(bit_width, buffer_ptr) };
481
482            for _ in 0..bit_width {
483                let bit = unsafe { c_readBit(&mut read_frame) };
484                mac.write_bit(bit);
485            }
486        }
487
488        // Sanity Check: This should never really fail, but still good to do
489        if !simplicity_sys::c_jets::sanity_checks() {
490            return Err(JetFailed);
491        }
492
493        let input_width = jet.source_ty().to_bit_width();
494        let output_width = jet.target_ty().to_bit_width();
495        // Input buffer is implicitly referenced by input read frame!
496        // Same goes for output buffer
497        let (input_read_frame, _input_buffer) = unsafe { get_input_frame(self, input_width) };
498        let (mut output_write_frame, output_buffer) = unsafe { get_output_frame(output_width) };
499
500        let jet_fn = jet.c_jet_ptr();
501        let c_env = J::c_jet_env(env);
502        let success = jet_fn(&mut output_write_frame, input_read_frame, c_env);
503
504        if !success {
505            Err(JetFailed)
506        } else {
507            update_active_write_frame(self, output_width, &output_buffer);
508            Ok(())
509        }
510    }
511}
512
513/// A type that keeps track of which case branches were executed
514/// during the execution of the Bit Machine.
515///
516/// The trait is implemented for [`SetTracker`], which does the actual tracking,
517/// and it is implemented for [`NoTracker`], which is a dummy tracker that is
518/// optimized out by the compiler.
519///
520/// The trait enables us to turn tracking on or off depending on a generic parameter.
521trait CaseTracker {
522    /// Track the execution of the left branch of the case node with the given `ihr`.
523    fn track_left(&mut self, ihr: Ihr);
524
525    /// Track the execution of the right branch of the case node with the given `ihr`.
526    fn track_right(&mut self, ihr: Ihr);
527}
528
529/// Tracker of executed left and right branches for each case node.
530#[derive(Clone, Debug, Default)]
531pub(crate) struct SetTracker {
532    left: HashSet<Ihr>,
533    right: HashSet<Ihr>,
534}
535
536impl SetTracker {
537    /// Access the set of IHRs of case nodes whose left branch was executed.
538    pub fn left(&self) -> &HashSet<Ihr> {
539        &self.left
540    }
541
542    /// Access the set of IHRs of case nodes whose right branch was executed.
543    pub fn right(&self) -> &HashSet<Ihr> {
544        &self.right
545    }
546}
547
548#[derive(Copy, Clone, Debug)]
549struct NoTracker;
550
551impl CaseTracker for SetTracker {
552    fn track_left(&mut self, ihr: Ihr) {
553        self.left.insert(ihr);
554    }
555
556    fn track_right(&mut self, ihr: Ihr) {
557        self.right.insert(ihr);
558    }
559}
560
561impl CaseTracker for NoTracker {
562    fn track_left(&mut self, _: Ihr) {}
563
564    fn track_right(&mut self, _: Ihr) {}
565}
566
567/// Errors related to simplicity Execution
568#[derive(Debug)]
569pub enum ExecutionError {
570    /// Provided input is of wrong type
571    InputWrongType(Arc<Final>),
572    /// Reached a fail node
573    ReachedFailNode(FailEntropy),
574    /// Reached a pruned branch
575    ReachedPrunedBranch(Cmr),
576    /// Exceeded some program limit
577    LimitExceeded(LimitError),
578    /// Jet failed during execution
579    JetFailed(JetFailed),
580}
581
582impl fmt::Display for ExecutionError {
583    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
584        match self {
585            ExecutionError::InputWrongType(expected_ty) => {
586                write!(f, "Expected input of type: {expected_ty}")
587            }
588            ExecutionError::ReachedFailNode(entropy) => {
589                write!(f, "Execution reached a fail node: {}", entropy)
590            }
591            ExecutionError::ReachedPrunedBranch(hash) => {
592                write!(f, "Execution reached a pruned branch: {}", hash)
593            }
594            ExecutionError::LimitExceeded(e) => e.fmt(f),
595            ExecutionError::JetFailed(jet_failed) => jet_failed.fmt(f),
596        }
597    }
598}
599
600impl error::Error for ExecutionError {
601    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
602        match self {
603            Self::InputWrongType(..)
604            | Self::ReachedFailNode(..)
605            | Self::ReachedPrunedBranch(..) => None,
606            Self::LimitExceeded(ref e) => Some(e),
607            Self::JetFailed(ref e) => Some(e),
608        }
609    }
610}
611
612impl From<LimitError> for ExecutionError {
613    fn from(e: LimitError) -> Self {
614        ExecutionError::LimitExceeded(e)
615    }
616}
617
618impl From<JetFailed> for ExecutionError {
619    fn from(jet_failed: JetFailed) -> Self {
620        ExecutionError::JetFailed(jet_failed)
621    }
622}
623
624#[cfg(test)]
625mod tests {
626    use super::*;
627
628    #[cfg(feature = "elements")]
629    use crate::jet::{elements::ElementsEnv, Elements};
630    #[cfg(feature = "elements")]
631    use crate::{node::RedeemNode, BitIter};
632    #[cfg(feature = "elements")]
633    use hex::DisplayHex;
634
635    #[cfg(feature = "elements")]
636    fn run_program_elements(
637        prog_bytes: &[u8],
638        witness_bytes: &[u8],
639        cmr_str: &str,
640        amr_str: &str,
641        ihr_str: &str,
642    ) -> Result<Value, ExecutionError> {
643        let prog_hex = prog_bytes.as_hex();
644
645        let prog = BitIter::from(prog_bytes);
646        let witness = BitIter::from(witness_bytes);
647        let prog = match RedeemNode::<Elements>::decode(prog, witness) {
648            Ok(prog) => prog,
649            Err(e) => panic!("program {} failed: {}", prog_hex, e),
650        };
651
652        // Check Merkle roots; check AMR last because the AMR is a more complicated
653        // calculation and historically has been much more likely to be wrong.
654        assert_eq!(
655            prog.cmr().to_string(),
656            cmr_str,
657            "CMR mismatch (got {} expected {}) for program {}",
658            prog.cmr(),
659            cmr_str,
660            prog_hex,
661        );
662        assert_eq!(
663            prog.amr().to_string(),
664            amr_str,
665            "AMR mismatch (got {} expected {}) for program {}",
666            prog.amr(),
667            amr_str,
668            prog_hex,
669        );
670        assert_eq!(
671            prog.ihr().to_string(),
672            ihr_str,
673            "IHR mismatch (got {} expected {}) for program {}",
674            prog.ihr(),
675            ihr_str,
676            prog_hex,
677        );
678
679        // Try to run it on the bit machine and return the result
680        let env = ElementsEnv::dummy();
681        BitMachine::for_program(&prog)
682            .expect("program has reasonable bounds")
683            .exec(&prog, &env)
684    }
685
686    #[test]
687    #[cfg(feature = "elements")]
688    fn crash_regression1() {
689        // cfe18fb44028870400
690        // This program caused an array OOB.
691        // # Witnesses
692        // wit1 = witness :: (((1 + (1 + 2^256)) * (1 + (1 + 2^256))) + 1) -> 1
693        //
694        // # Program code
695        // jt1 = jet_num_outputs    :: 1 -> 2^32                                          # cmr 447165a3...
696        // jt2 = jet_issuance_token :: 2^32 -> (1 + (1 + 2^256))                          # cmr 85e9591c...
697        // pr3 = pair jt2 jt2       :: 2^32 -> ((1 + (1 + 2^256)) * (1 + (1 + 2^256)))    # cmr 45d40848...
698        // cp4 = comp jt1 pr3       :: 1 -> ((1 + (1 + 2^256)) * (1 + (1 + 2^256)))       # cmr 7bb1824f...
699        // jl5 = injl cp4           :: 1 -> (((1 + (1 + 2^256)) * (1 + (1 + 2^256))) + 1) # cmr 277ee32c...
700        //
701        // main = comp jl5 wit1     :: 1 -> 1                                             # cmr 7050c4a6...
702
703        let res = run_program_elements(
704            &[0xcf, 0xe1, 0x8f, 0xb4, 0x40, 0x28, 0x87, 0x04, 0x00],
705            &[],
706            "0075f5368af7b453c2f7318493df12567bc6d733cf1ebb69a08ce93e1e529956",
707            "0ac9baaee94e416c6598a271dfc18d6014f751ac300cfe69eb758c20f26c624b",
708            "61f2cf59bfec55bb6e44cbae71c3ba225f1059f9a5679c0b42a7177daff52b5a",
709        );
710        assert_eq!(res.unwrap(), Value::unit());
711    }
712
713    #[test]
714    fn crash_regression2() {
715        use crate::node::{CoreConstructible as _, JetConstructible as _};
716
717        type Node = Arc<crate::ConstructNode<crate::jet::Core>>;
718
719        let mut bomb = Node::jet(
720            &crate::types::Context::new(),
721            crate::jet::Core::Ch8, // arbitrary jet with nonzero output size
722        );
723        for _ in 0..100 {
724            bomb = Node::pair(&bomb, &bomb).unwrap();
725        }
726        let _ = bomb.finalize_pruned(&());
727    }
728}