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