Skip to main content

chia_protocol/
program.rs

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