Skip to content

Commit

Permalink
add all member functions to Program, matching chia.types.blockchain_f…
Browse files Browse the repository at this point in the history
…ormat.SerializedProgram
  • Loading branch information
arvidn committed Nov 4, 2023
1 parent 1517f0f commit 3f4bf12
Show file tree
Hide file tree
Showing 12 changed files with 385 additions and 16 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
python-version: ['3.8', '3.9', '3.10', '3.11']

steps:
- uses: actions/checkout@v3
Expand Down Expand Up @@ -55,7 +55,7 @@ jobs:
- name: Build
run: |
python -m pip install clvm_tools colorama blspy
python -m pip install clvm_tools colorama blspy chia-blockchain
maturin develop --release -m wheel/Cargo.toml
- name: python mypy
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ jobs:
source venv/bin/activate
git clone https://github.com/Chia-Network/clvm_tools.git --branch=main --single-branch
pip install ./clvm_tools
pip install colorama maturin pytest
pip install colorama maturin pytest chia-blockchain
maturin develop --release -m wheel/Cargo.toml
pytest tests
grcov . --binary-path target -s . --branch --ignore-not-existing --ignore='*/.cargo/*' --ignore='tests/*' --ignore='venv/*' -o rust_cov.info
Expand Down
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions chia-protocol/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@ repository = "https://github.com/Chia-Network/chia_rs/chia-protocol/"
py-bindings = ["dep:pyo3", "dep:chia_py_streamable_macro", "chia-traits/py-bindings"]

[dependencies]
pyo3 = { version = "0.19.0", features = ["multiple-pymethods"], optional = true }
pyo3 = { version = "0.19.0", features = ["multiple-pymethods", "num-bigint"], optional = true }
sha2 = "0.10.8"
hex = "0.4.3"
chia_streamable_macro = { version = "=0.2.12", path = "../chia_streamable_macro" }
chia_py_streamable_macro = { version = "=0.2.13", path = "../chia_py_streamable_macro", optional = true }
clvmr = "=0.3.0"
chia-traits = { version = "=0.2.13", path = "../chia-traits" }
clvm-traits = { version = "=0.2.12", path = "../clvm-traits", features = ["derive"] }
chia-bls = { version = "0.2.13", path = "../chia-bls" }
clvm-utils = { version = "=0.2.12", path = "../clvm-utils" }
chia-bls = { version = "=0.2.13", path = "../chia-bls" }
arbitrary = { version = "1.3.0", features = ["derive"] }

[dev-dependencies]
Expand Down
260 changes: 251 additions & 9 deletions chia-protocol/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,6 @@ use clvmr::Allocator;
use sha2::{Digest, Sha256};
use std::io::Cursor;

#[cfg(feature = "py-bindings")]
use chia_traits::{FromJsonDict, ToJsonDict};

#[cfg(feature = "py-bindings")]
use chia_py_streamable_macro::PyStreamable;

#[cfg(feature = "py-bindings")]
use pyo3::prelude::*;

#[cfg_attr(feature = "py-bindings", pyclass, derive(PyStreamable))]
#[derive(Hash, Debug, Clone, Eq, PartialEq)]
pub struct Program(Bytes);
Expand Down Expand Up @@ -59,6 +50,257 @@ impl Program {
}
}

impl Default for Program {
fn default() -> Self {
Self(vec![0x80_u8].into())
}
}

#[cfg(feature = "py-bindings")]
use crate::lazy_node::LazyNode;

#[cfg(feature = "py-bindings")]
use chia_traits::{FromJsonDict, ToJsonDict};

#[cfg(feature = "py-bindings")]
use chia_py_streamable_macro::PyStreamable;

#[cfg(feature = "py-bindings")]
use pyo3::prelude::*;

#[cfg(feature = "py-bindings")]
use pyo3::types::{PyList, PyTuple};

#[cfg(feature = "py-bindings")]
use clvmr::serde::node_from_bytes_backrefs;

#[cfg(feature = "py-bindings")]
use clvmr::allocator::SExp;

#[cfg(feature = "py-bindings")]
use pyo3::exceptions::*;

#[cfg(feature = "py-bindings")]
fn clvm_convert(a: &mut Allocator, o: &PyAny) -> PyResult<NodePtr> {
// None
if o.is_none() {
Ok(a.null())
// Program itself
} else if let Ok(prg) = o.extract::<Program>() {
Ok(node_from_bytes_backrefs(a, prg.0.as_slice())?)
// bytes
} else if let Ok(buffer) = o.extract::<&[u8]>() {
a.new_atom(buffer)
.map_err(|e| PyMemoryError::new_err(e.to_string()))
// str
} else if let Ok(text) = o.extract::<String>() {
a.new_atom(text.as_bytes())
.map_err(|e| PyMemoryError::new_err(e.to_string()))
// int
} else if let Ok(val) = o.extract::<clvmr::number::Number>() {
a.new_number(val)
.map_err(|e| PyMemoryError::new_err(e.to_string()))
// Tuple (SExp-like)
} else if let Ok(pair) = o.downcast::<PyTuple>() {
if pair.len() != 2 {
Err(PyValueError::new_err(format!(
"can't cast tuple of size {}",
pair.len()
)))
} else {
let left = clvm_convert(a, pair.get_item(0)?)?;
let right = clvm_convert(a, pair.get_item(1)?)?;
a.new_pair(left, right)
.map_err(|e| PyMemoryError::new_err(e.to_string()))
}
// List
} else if let Ok(list) = o.downcast::<PyList>() {
let mut rev = Vec::<&PyAny>::new();
for py_item in list.iter() {
rev.push(py_item);
}
let mut ret = a.null();
for py_item in rev.into_iter().rev() {
let item = clvm_convert(a, py_item)?;
ret = a
.new_pair(item, ret)
.map_err(|e| PyMemoryError::new_err(e.to_string()))?;
}
Ok(ret)
// SExp (such as clvm.SExp)
} else if let (Ok(atom), Ok(pair)) = (o.getattr("atom"), o.getattr("pair")) {
if atom.is_none() {
if pair.is_none() {
Err(PyTypeError::new_err(format!("invalid SExp item {o}")))
} else {
let pair = pair.downcast::<PyTuple>()?;
let left = clvm_convert(a, &pair[0])?;
let right = clvm_convert(a, &pair[1])?;
a.new_pair(left, right)
.map_err(|e| PyMemoryError::new_err(e.to_string()))
}
} else {
a.new_atom(atom.extract::<&[u8]>()?)
.map_err(|e| PyMemoryError::new_err(e.to_string()))
}
// anything convertible to bytes
} else if let Ok(fun) = o.getattr("__bytes__") {
let bytes = fun.call0()?;
let buffer = bytes.extract::<&[u8]>()?;
a.new_atom(buffer)
.map_err(|e| PyMemoryError::new_err(e.to_string()))
} else {
Err(PyTypeError::new_err(format!(
"unknown parameter to run_with_cost() {o}"
)))
}
}

#[cfg(feature = "py-bindings")]
fn to_program(py: Python<'_>, node: LazyNode) -> PyResult<&PyAny> {
use pyo3::types::PyDict;
let ctx: &PyDict = PyDict::new(py);
ctx.set_item("node", node)?;
py.run(
"from chia.types.blockchain_format.program import Program\n\
ret = Program(node)\n",
None,
Some(ctx),
)?;
Ok(ctx.get_item("ret").unwrap())
}

#[cfg(feature = "py-bindings")]
#[pymethods]
impl Program {
#[pyo3(name = "default")]
#[staticmethod]
fn py_default() -> Self {
Self::default()
}

fn get_tree_hash(&self) -> crate::Bytes32 {
let mut cursor = Cursor::new(self.0.as_ref());
clvmr::serde::tree_hash_from_stream(&mut cursor)
.unwrap()
.into()
}

#[staticmethod]
fn from_program(py: Python<'_>, p: PyObject) -> PyResult<Self> {
let buf = p.getattr(py, "__bytes__")?.call0(py)?;
let buf = buf.extract::<&[u8]>(py)?;
Ok(Self(buf.into()))
}

#[staticmethod]
fn fromhex(h: String) -> Result<Self> {
let s = if let Some(st) = h.strip_prefix("0x") {
st
} else {
&h[..]
};
Self::from_bytes(hex::decode(s).map_err(|_| Error::InvalidString)?.as_slice())
}

fn run_mempool_with_cost<'a>(
&self,
py: Python<'a>,
max_cost: u64,
args: &PyAny,
) -> PyResult<(u64, &'a PyAny)> {
use clvmr::MEMPOOL_MODE;
self._run(py, max_cost, MEMPOOL_MODE, args)
}

fn run_with_cost<'a>(
&self,
py: Python<'a>,
max_cost: u64,
args: &PyAny,
) -> PyResult<(u64, &'a PyAny)> {
self._run(py, max_cost, 0, args)
}

fn _run<'a>(
&self,
py: Python<'a>,
max_cost: u64,
flags: u32,
args: &PyAny,
) -> PyResult<(u64, &'a PyAny)> {
use clvmr::reduction::Response;
use clvmr::run_program;
use clvmr::ChiaDialect;
use std::rc::Rc;

let mut a = Allocator::new_limited(500000000, 62500000, 62500000);
let clvm_args = clvm_convert(&mut a, args)?;

let r: Response = (|| -> PyResult<Response> {
let program = node_from_bytes_backrefs(&mut a, self.0.as_ref())?;
let dialect = ChiaDialect::new(flags);

Ok(py.allow_threads(|| run_program(&mut a, &dialect, program, clvm_args, max_cost)))
})()?;
match r {
Ok(reduction) => {
let val = LazyNode::new(Rc::new(a), reduction.1);
Ok((reduction.0, to_program(py, val)?))
}
Err(eval_err) => Err(PyValueError::new_err(eval_err.to_string())),
}
}

fn to_program<'a>(&self, py: Python<'a>) -> PyResult<&'a PyAny> {
use std::rc::Rc;
let mut a = Allocator::new_limited(500000000, 62500000, 62500000);
let prg = node_from_bytes_backrefs(&mut a, self.0.as_ref())?;
let prg = LazyNode::new(Rc::new(a), prg);
to_program(py, prg)
}

fn uncurry<'a>(&self, py: Python<'a>) -> PyResult<(&'a PyAny, &'a PyAny)> {
use clvm_utils::CurriedProgram;
use std::rc::Rc;

let mut a = Allocator::new_limited(500000000, 62500000, 62500000);
let prg = node_from_bytes_backrefs(&mut a, self.0.as_ref())?;
let Ok(uncurried) = CurriedProgram::<NodePtr>::from_clvm(&a, prg) else {
let a = Rc::new(a);
let prg = LazyNode::new(a.clone(), prg);
let ret = a.null();
let ret = LazyNode::new(a, ret);
return Ok((to_program(py, prg)?, to_program(py, ret)?));
};

let mut curried_args = Vec::<NodePtr>::new();
let mut args = uncurried.args;
loop {
if let SExp::Atom = a.sexp(args) {
break;
}
// the args of curried puzzles are in the form of:
// (c . ((q . <arg1>) . (<rest> . ())))
let (_, ((_, arg), (rest, _))) =
<(
clvm_traits::MatchByte<4>,
(clvm_traits::match_quote!(NodePtr), (NodePtr, ())),
) as clvm_traits::FromClvm>::from_clvm(&a, args)?;
curried_args.push(arg);
args = rest;
}
let mut ret = a.null();
for item in curried_args.into_iter().rev() {
ret = a.new_pair(item, ret).map_err(|_e| Error::EndOfBuffer)?;
}
let a = Rc::new(a);
let prg = LazyNode::new(a.clone(), uncurried.program);
let ret = LazyNode::new(a, ret);
Ok((to_program(py, prg)?, to_program(py, ret)?))
}
}

impl Streamable for Program {
fn update_digest(&self, digest: &mut Sha256) {
digest.update(&self.0);
Expand Down
2 changes: 2 additions & 0 deletions clvm-traits/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ features = ["derive"]

[features]
derive = ["dep:clvm-derive"]
py-bindings = ["dep:pyo3"]

[dependencies]
pyo3 = { version = ">=0.19.0", optional = true }
clvm-derive = { version = "0.2.12", path = "../clvm-derive", optional = true }
clvmr = "0.3.0"
num-bigint = "0.4.3"
Expand Down
10 changes: 10 additions & 0 deletions clvm-traits/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,13 @@ impl From<EvalErr> for Error {
Self::Allocator(value)
}
}

#[cfg(feature = "py-bindings")]
use pyo3::PyErr;

#[cfg(feature = "py-bindings")]
impl std::convert::From<Error> for PyErr {
fn from(err: Error) -> PyErr {
pyo3::exceptions::PyValueError::new_err(err.to_string())
}
}
Loading

0 comments on commit 3f4bf12

Please sign in to comment.