chia_protocol/
program.rs

1use crate::bytes::Bytes;
2use chia_sha2::Sha256;
3use chia_traits::chia_error::{Error, Result};
4use chia_traits::Streamable;
5use clvm_traits::{FromClvm, FromClvmError, ToClvm, ToClvmError};
6use clvmr::allocator::NodePtr;
7use clvmr::cost::Cost;
8use clvmr::reduction::EvalErr;
9use clvmr::run_program;
10use clvmr::serde::{
11    node_from_bytes, node_from_bytes_backrefs, node_to_bytes, serialized_length_from_bytes,
12    serialized_length_from_bytes_trusted,
13};
14use clvmr::{Allocator, ChiaDialect};
15#[cfg(feature = "py-bindings")]
16use pyo3::prelude::*;
17#[cfg(feature = "py-bindings")]
18use pyo3::types::PyType;
19use std::io::Cursor;
20use std::ops::Deref;
21
22#[cfg_attr(feature = "py-bindings", pyclass, derive(PyStreamable))]
23#[derive(Debug, Clone, PartialEq, Eq, Hash)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub struct Program(Bytes);
26
27impl Default for Program {
28    fn default() -> Self {
29        Self(vec![0x80].into())
30    }
31}
32
33impl Program {
34    pub fn new(bytes: Bytes) -> Self {
35        Self(bytes)
36    }
37
38    pub fn len(&self) -> usize {
39        self.0.len()
40    }
41
42    pub fn is_empty(&self) -> bool {
43        self.0.is_empty()
44    }
45
46    pub fn as_slice(&self) -> &[u8] {
47        self.0.as_slice()
48    }
49
50    pub fn to_vec(&self) -> Vec<u8> {
51        self.0.to_vec()
52    }
53
54    pub fn into_inner(self) -> Bytes {
55        self.0
56    }
57
58    pub fn into_bytes(self) -> Vec<u8> {
59        self.0.into_inner()
60    }
61
62    pub fn run<A: ToClvm<Allocator>>(
63        &self,
64        a: &mut Allocator,
65        flags: u32,
66        max_cost: Cost,
67        arg: &A,
68    ) -> std::result::Result<(Cost, NodePtr), EvalErr> {
69        let arg = arg.to_clvm(a).map_err(|_| {
70            EvalErr(
71                a.nil(),
72                "failed to convert argument to CLVM objects".to_string(),
73            )
74        })?;
75        let program =
76            node_from_bytes_backrefs(a, self.0.as_ref()).expect("invalid SerializedProgram");
77        let dialect = ChiaDialect::new(flags);
78        let reduction = run_program(a, &dialect, program, arg, max_cost)?;
79        Ok((reduction.0, reduction.1))
80    }
81}
82
83impl From<Bytes> for Program {
84    fn from(value: Bytes) -> Self {
85        Self(value)
86    }
87}
88
89impl From<Program> for Bytes {
90    fn from(value: Program) -> Self {
91        value.0
92    }
93}
94
95impl From<Vec<u8>> for Program {
96    fn from(value: Vec<u8>) -> Self {
97        Self(Bytes::new(value))
98    }
99}
100
101impl From<&[u8]> for Program {
102    fn from(value: &[u8]) -> Self {
103        Self(value.into())
104    }
105}
106
107impl From<Program> for Vec<u8> {
108    fn from(value: Program) -> Self {
109        value.0.into()
110    }
111}
112
113impl AsRef<[u8]> for Program {
114    fn as_ref(&self) -> &[u8] {
115        self.0.as_ref()
116    }
117}
118
119impl Deref for Program {
120    type Target = [u8];
121
122    fn deref(&self) -> &[u8] {
123        &self.0
124    }
125}
126
127#[cfg(feature = "arbitrary")]
128impl<'a> arbitrary::Arbitrary<'a> for Program {
129    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
130        // generate an arbitrary CLVM structure. Not likely a valid program.
131        let mut items_left = 1;
132        let mut total_items = 0;
133        let mut buf = Vec::<u8>::with_capacity(200);
134
135        while items_left > 0 {
136            if total_items < 100 && u.ratio(1, 4).unwrap() {
137                // make a pair
138                buf.push(0xff);
139                items_left += 2;
140            } else {
141                // make an atom. just single bytes for now
142                buf.push(u.int_in_range(0..=0x80).unwrap());
143            }
144            total_items += 1;
145            items_left -= 1;
146        }
147        Ok(Self(buf.into()))
148    }
149}
150
151#[cfg(feature = "py-bindings")]
152use crate::lazy_node::LazyNode;
153
154#[cfg(feature = "py-bindings")]
155use chia_traits::{FromJsonDict, ToJsonDict};
156
157#[cfg(feature = "py-bindings")]
158use chia_py_streamable_macro::PyStreamable;
159
160#[cfg(feature = "py-bindings")]
161use pyo3::types::{PyList, PyTuple};
162
163#[cfg(feature = "py-bindings")]
164use clvmr::allocator::SExp;
165
166#[cfg(feature = "py-bindings")]
167use pyo3::exceptions::*;
168
169// TODO: this conversion function should probably be converted to a type holding
170// the PyAny object implementing the ToClvm trait. That way, the Program::to()
171// function could turn a python structure directly into bytes, without taking
172// the detour via Allocator. propagating python errors through ToClvmError is a
173// bit tricky though
174#[cfg(feature = "py-bindings")]
175fn clvm_convert(a: &mut Allocator, o: &Bound<'_, PyAny>) -> PyResult<NodePtr> {
176    // None
177    if o.is_none() {
178        Ok(a.nil())
179    // bytes
180    } else if let Ok(buffer) = o.extract::<&[u8]>() {
181        a.new_atom(buffer)
182            .map_err(|e| PyMemoryError::new_err(e.to_string()))
183    // str
184    } else if let Ok(text) = o.extract::<String>() {
185        a.new_atom(text.as_bytes())
186            .map_err(|e| PyMemoryError::new_err(e.to_string()))
187    // int
188    } else if let Ok(val) = o.extract::<clvmr::number::Number>() {
189        a.new_number(val)
190            .map_err(|e| PyMemoryError::new_err(e.to_string()))
191    // Tuple (SExp-like)
192    } else if let Ok(pair) = o.downcast::<PyTuple>() {
193        if pair.len() == 2 {
194            let left = clvm_convert(a, &pair.get_item(0)?)?;
195            let right = clvm_convert(a, &pair.get_item(1)?)?;
196            a.new_pair(left, right)
197                .map_err(|e| PyMemoryError::new_err(e.to_string()))
198        } else {
199            Err(PyValueError::new_err(format!(
200                "can't cast tuple of size {}",
201                pair.len()
202            )))
203        }
204    // List
205    } else if let Ok(list) = o.downcast::<PyList>() {
206        let mut rev = Vec::new();
207        for py_item in list.iter() {
208            rev.push(py_item);
209        }
210        let mut ret = a.nil();
211        for py_item in rev.into_iter().rev() {
212            let item = clvm_convert(a, &py_item)?;
213            ret = a
214                .new_pair(item, ret)
215                .map_err(|e| PyMemoryError::new_err(e.to_string()))?;
216        }
217        Ok(ret)
218    // SExp (such as clvm.SExp)
219    } else if let (Ok(atom), Ok(pair)) = (o.getattr("atom"), o.getattr("pair")) {
220        if atom.is_none() {
221            if pair.is_none() {
222                Err(PyTypeError::new_err(format!("invalid SExp item {o}")))
223            } else {
224                let pair = pair.downcast::<PyTuple>()?;
225                let left = clvm_convert(a, &pair.get_item(0)?)?;
226                let right = clvm_convert(a, &pair.get_item(1)?)?;
227                a.new_pair(left, right)
228                    .map_err(|e| PyMemoryError::new_err(e.to_string()))
229            }
230        } else {
231            a.new_atom(atom.extract::<&[u8]>()?)
232                .map_err(|e| PyMemoryError::new_err(e.to_string()))
233        }
234    // Program itself. This is interpreted as a program in serialized form, and
235    // just a buffer of that serialization. This is an optimization to finding
236    // __bytes__() and calling it
237    } else if let Ok(prg) = o.extract::<Program>() {
238        a.new_atom(prg.0.as_slice())
239            .map_err(|e| PyMemoryError::new_err(e.to_string()))
240    // anything convertible to bytes
241    } else if let Ok(fun) = o.getattr("__bytes__") {
242        let bytes = fun.call0()?;
243        let buffer = bytes.extract::<&[u8]>()?;
244        a.new_atom(buffer)
245            .map_err(|e| PyMemoryError::new_err(e.to_string()))
246    } else {
247        Err(PyTypeError::new_err(format!(
248            "unknown parameter to run_with_cost() {o}"
249        )))
250    }
251}
252
253#[cfg(feature = "py-bindings")]
254fn clvm_serialize(a: &mut Allocator, o: &Bound<'_, PyAny>) -> PyResult<NodePtr> {
255    /*
256    When passing arguments to run(), there's some special treatment, before falling
257    back on the regular python -> CLVM conversion (implemented by clvm_convert
258    above). This function mimics the _serialize() function in python:
259
260       def _serialize(node: object) -> bytes:
261           if isinstance(node, list):
262               serialized_list = bytearray()
263               for a in node:
264                   serialized_list += b"\xff"
265                   serialized_list += _serialize(a)
266               serialized_list += b"\x80"
267               return bytes(serialized_list)
268           if type(node) is SerializedProgram:
269               return bytes(node)
270           if type(node) is Program:
271               return bytes(node)
272           else:
273               ret: bytes = SExp.to(node).as_bin()
274               return ret
275    */
276
277    // List
278    if let Ok(list) = o.downcast::<PyList>() {
279        let mut rev = Vec::new();
280        for py_item in list.iter() {
281            rev.push(py_item);
282        }
283        let mut ret = a.nil();
284        for py_item in rev.into_iter().rev() {
285            let item = clvm_serialize(a, &py_item)?;
286            ret = a
287                .new_pair(item, ret)
288                .map_err(|e| PyMemoryError::new_err(e.to_string()))?;
289        }
290        Ok(ret)
291    // Program itself
292    } else if let Ok(prg) = o.extract::<Program>() {
293        Ok(node_from_bytes_backrefs(a, prg.0.as_slice())?)
294    } else {
295        clvm_convert(a, o)
296    }
297}
298
299#[cfg(feature = "py-bindings")]
300fn to_program(py: Python<'_>, node: LazyNode) -> PyResult<Bound<'_, PyAny>> {
301    let int_module = PyModule::import(py, "chia.types.blockchain_format.program")?;
302    let ty = int_module.getattr("Program")?;
303    ty.call1((node.into_pyobject(py)?,))
304}
305
306#[cfg(feature = "py-bindings")]
307#[allow(clippy::needless_pass_by_value)]
308#[pymethods]
309impl Program {
310    #[pyo3(name = "default")]
311    #[staticmethod]
312    fn py_default() -> Self {
313        Self::default()
314    }
315
316    #[staticmethod]
317    #[pyo3(name = "to")]
318    fn py_to(args: &Bound<'_, PyAny>) -> PyResult<Program> {
319        let mut a = Allocator::new_limited(500_000_000);
320        let clvm = clvm_convert(&mut a, args)?;
321        Program::from_clvm(&a, clvm)
322            .map_err(|error| PyErr::new::<PyTypeError, _>(error.to_string()))
323    }
324
325    fn get_tree_hash(&self) -> crate::Bytes32 {
326        clvm_utils::tree_hash_from_bytes(self.0.as_ref())
327            .unwrap()
328            .into()
329    }
330
331    #[staticmethod]
332    fn from_program(py: Python<'_>, p: PyObject) -> PyResult<Self> {
333        let buf = p.getattr(py, "__bytes__")?.call0(py)?;
334        let buf = buf.extract::<&[u8]>(py)?;
335        Ok(Self(buf.into()))
336    }
337
338    #[staticmethod]
339    fn fromhex(h: String) -> Result<Self> {
340        let s = if let Some(st) = h.strip_prefix("0x") {
341            st
342        } else {
343            &h[..]
344        };
345        Self::from_bytes(hex::decode(s).map_err(|_| Error::InvalidString)?.as_slice())
346    }
347
348    fn run_mempool_with_cost<'a>(
349        &self,
350        py: Python<'a>,
351        max_cost: u64,
352        args: &Bound<'_, PyAny>,
353    ) -> PyResult<(u64, Bound<'a, PyAny>)> {
354        use clvmr::MEMPOOL_MODE;
355        #[allow(clippy::used_underscore_items)]
356        self._run(py, max_cost, MEMPOOL_MODE, args)
357    }
358
359    fn run_with_cost<'a>(
360        &self,
361        py: Python<'a>,
362        max_cost: u64,
363        args: &Bound<'_, PyAny>,
364    ) -> PyResult<(u64, Bound<'a, PyAny>)> {
365        #[allow(clippy::used_underscore_items)]
366        self._run(py, max_cost, 0, args)
367    }
368
369    // exposed to python so allowing use of the python private indicator leading underscore
370    fn _run<'a>(
371        &self,
372        py: Python<'a>,
373        max_cost: u64,
374        flags: u32,
375        args: &Bound<'_, PyAny>,
376    ) -> PyResult<(u64, Bound<'a, PyAny>)> {
377        use clvmr::reduction::Response;
378        use std::rc::Rc;
379
380        let mut a = Allocator::new_limited(500_000_000);
381        // The python behavior here is a bit messy, and is best not emulated
382        // on the rust side. We must be able to pass a Program as an argument,
383        // and it being treated as the CLVM structure it represents. In python's
384        // SerializedProgram, we have a hack where we interpret the first
385        // "layer" of SerializedProgram, or lists of SerializedProgram this way.
386        // But if we encounter an Optional or tuple, we defer to the clvm
387        // wheel's conversion function to SExp. This level does not have any
388        // special treatment for SerializedProgram (as that would cause a
389        // circular dependency).
390        let clvm_args = clvm_serialize(&mut a, args)?;
391
392        let r: Response = (|| -> PyResult<Response> {
393            let program = node_from_bytes_backrefs(&mut a, self.0.as_ref())?;
394            let dialect = ChiaDialect::new(flags);
395
396            Ok(py.allow_threads(|| run_program(&mut a, &dialect, program, clvm_args, max_cost)))
397        })()?;
398        match r {
399            Ok(reduction) => {
400                let val = LazyNode::new(Rc::new(a), reduction.1);
401                Ok((reduction.0, to_program(py, val)?))
402            }
403            Err(eval_err) => {
404                let blob = node_to_bytes(&a, eval_err.0).ok().map(hex::encode);
405                Err(PyValueError::new_err((eval_err.1, blob)))
406            }
407        }
408    }
409
410    fn to_program<'a>(&self, py: Python<'a>) -> PyResult<Bound<'a, PyAny>> {
411        use std::rc::Rc;
412        let mut a = Allocator::new_limited(500_000_000);
413        let prg = node_from_bytes_backrefs(&mut a, self.0.as_ref())?;
414        let prg = LazyNode::new(Rc::new(a), prg);
415        to_program(py, prg)
416    }
417
418    fn uncurry<'a>(&self, py: Python<'a>) -> PyResult<(Bound<'a, PyAny>, Bound<'a, PyAny>)> {
419        use clvm_utils::CurriedProgram;
420        use std::rc::Rc;
421
422        let mut a = Allocator::new_limited(500_000_000);
423        let prg = node_from_bytes_backrefs(&mut a, self.0.as_ref())?;
424        let Ok(uncurried) = CurriedProgram::<NodePtr, NodePtr>::from_clvm(&a, prg) else {
425            let a = Rc::new(a);
426            let prg = LazyNode::new(a.clone(), prg);
427            let ret = a.nil();
428            let ret = LazyNode::new(a, ret);
429            return Ok((to_program(py, prg)?, to_program(py, ret)?));
430        };
431
432        let mut curried_args = Vec::<NodePtr>::new();
433        let mut args = uncurried.args;
434        loop {
435            if let SExp::Atom = a.sexp(args) {
436                break;
437            }
438            // the args of curried puzzles are in the form of:
439            // (c . ((q . <arg1>) . (<rest> . ())))
440            let (_, ((_, arg), (rest, ()))) =
441                <(
442                    clvm_traits::MatchByte<4>,
443                    (clvm_traits::match_quote!(NodePtr), (NodePtr, ())),
444                ) as FromClvm<Allocator>>::from_clvm(&a, args)
445                .map_err(|error| PyErr::new::<PyTypeError, _>(error.to_string()))?;
446            curried_args.push(arg);
447            args = rest;
448        }
449        let mut ret = a.nil();
450        for item in curried_args.into_iter().rev() {
451            ret = a.new_pair(item, ret).map_err(|_e| Error::EndOfBuffer)?;
452        }
453        let a = Rc::new(a);
454        let prg = LazyNode::new(a.clone(), uncurried.program);
455        let ret = LazyNode::new(a, ret);
456        Ok((to_program(py, prg)?, to_program(py, ret)?))
457    }
458}
459
460impl Streamable for Program {
461    fn update_digest(&self, digest: &mut Sha256) {
462        digest.update(&self.0);
463    }
464
465    fn stream(&self, out: &mut Vec<u8>) -> Result<()> {
466        out.extend_from_slice(self.0.as_ref());
467        Ok(())
468    }
469
470    fn parse<const TRUSTED: bool>(input: &mut Cursor<&[u8]>) -> Result<Self> {
471        let pos = input.position();
472        let buf: &[u8] = &input.get_ref()[pos as usize..];
473        let len = if TRUSTED {
474            serialized_length_from_bytes_trusted(buf).map_err(|_e| Error::EndOfBuffer)?
475        } else {
476            serialized_length_from_bytes(buf).map_err(|_e| Error::EndOfBuffer)?
477        };
478        if buf.len() < len as usize {
479            return Err(Error::EndOfBuffer);
480        }
481        let program = buf[..len as usize].to_vec();
482        input.set_position(pos + len);
483        Ok(Program(program.into()))
484    }
485}
486
487#[cfg(feature = "py-bindings")]
488impl ToJsonDict for Program {
489    fn to_json_dict(&self, py: Python<'_>) -> PyResult<PyObject> {
490        self.0.to_json_dict(py)
491    }
492}
493
494#[cfg(feature = "py-bindings")]
495#[pymethods]
496impl Program {
497    #[classmethod]
498    #[pyo3(name = "from_parent")]
499    pub fn from_parent(_cls: &Bound<'_, PyType>, _instance: &Self) -> PyResult<PyObject> {
500        Err(PyNotImplementedError::new_err(
501            "This class does not support from_parent().",
502        ))
503    }
504}
505
506#[cfg(feature = "py-bindings")]
507impl FromJsonDict for Program {
508    fn from_json_dict(o: &Bound<'_, PyAny>) -> PyResult<Self> {
509        let bytes = Bytes::from_json_dict(o)?;
510        let len =
511            serialized_length_from_bytes(bytes.as_slice()).map_err(|_e| Error::EndOfBuffer)?;
512        if len as usize != bytes.len() {
513            // If the bytes in the JSON string is not a valid CLVM
514            // serialization, or if it has garbage at the end of the string,
515            // reject it
516            return Err(Error::InvalidClvm)?;
517        }
518        Ok(Self(bytes))
519    }
520}
521
522impl FromClvm<Allocator> for Program {
523    fn from_clvm(a: &Allocator, node: NodePtr) -> std::result::Result<Self, FromClvmError> {
524        Ok(Self(
525            node_to_bytes(a, node)
526                .map_err(|error| FromClvmError::Custom(error.to_string()))?
527                .into(),
528        ))
529    }
530}
531
532impl ToClvm<Allocator> for Program {
533    fn to_clvm(&self, a: &mut Allocator) -> std::result::Result<NodePtr, ToClvmError> {
534        node_from_bytes(a, self.0.as_ref()).map_err(|error| ToClvmError::Custom(error.to_string()))
535    }
536}
537
538#[cfg(test)]
539mod tests {
540    use super::*;
541
542    #[test]
543    fn program_roundtrip() {
544        let a = &mut Allocator::new();
545        let expected = "ff01ff02ff62ff0480";
546        let expected_bytes = hex::decode(expected).unwrap();
547
548        let ptr = node_from_bytes(a, &expected_bytes).unwrap();
549        let program = Program::from_clvm(a, ptr).unwrap();
550
551        let round_trip = program.to_clvm(a).unwrap();
552        assert_eq!(expected, hex::encode(node_to_bytes(a, round_trip).unwrap()));
553    }
554
555    #[test]
556    fn program_run() {
557        let a = &mut Allocator::new();
558
559        // (+ 2 5)
560        let prg = Program::from_bytes(&hex::decode("ff10ff02ff0580").expect("hex::decode"))
561            .expect("from_bytes");
562        let (cost, result) = prg.run(a, 0, 1000, &[1300, 37]).expect("run");
563        assert_eq!(cost, 869);
564        assert_eq!(a.number(result), 1337.into());
565    }
566}
567
568#[cfg(all(test, feature = "serde"))]
569mod serde_tests {
570    use super::*;
571
572    #[test]
573    fn test_program_is_bytes() -> anyhow::Result<()> {
574        let bytes = Bytes::new(vec![1, 2, 3]);
575        let program = Program::new(bytes.clone());
576
577        let bytes_json = serde_json::to_string(&bytes)?;
578        let program_json = serde_json::to_string(&program)?;
579
580        assert_eq!(program_json, bytes_json);
581
582        Ok(())
583    }
584}