From 2f677992154d6d1d811d9a0df48f92bd46fabef4 Mon Sep 17 00:00:00 2001 From: arvidn Date: Sat, 23 Sep 2023 16:33:33 +0200 Subject: [PATCH] add python bindings --- src/error.rs | 10 +++++++ tests/test_fast_forward.py | 23 +++++++++++++++ wheel/chia_rs.pyi | 4 +++ wheel/generate_type_stubs.py | 4 +++ wheel/src/api.rs | 54 ++++++++++++++++++++++++++++++++++++ 5 files changed, 95 insertions(+) create mode 100644 tests/test_fast_forward.py diff --git a/src/error.rs b/src/error.rs index bf6609523..807d44d24 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,6 +2,9 @@ use crate::gen::validation_error::ValidationErr; use clvmr::reduction::EvalErr; use thiserror::Error; +#[cfg(feature = "py-bindings")] +use pyo3::PyErr; + #[derive(Debug, Clone, PartialEq, Eq, Error)] pub enum Error { #[error("CLVM {0}")] @@ -38,4 +41,11 @@ pub enum Error { Custom(String), } +#[cfg(feature = "py-bindings")] +impl std::convert::From for PyErr { + fn from(err: Error) -> PyErr { + pyo3::exceptions::PyValueError::new_err(err.to_string()) + } +} + pub type Result = std::result::Result; diff --git a/tests/test_fast_forward.py b/tests/test_fast_forward.py new file mode 100644 index 000000000..38ed79c23 --- /dev/null +++ b/tests/test_fast_forward.py @@ -0,0 +1,23 @@ +from chia_rs import Spend, CoinSpend, Coin, supports_fast_forward, fast_forward_singleton +import pytest + +@pytest.mark.parametrize("file", ["bb13", "e3c0"]) +def test_supports_fast_forward(file: str) -> None: + with open(f"ff-tests/{file}.spend", "rb") as f: + spend = CoinSpend.from_bytes(f.read()) + assert supports_fast_forward(spend) + +@pytest.mark.parametrize("file", ["bb13", "e3c0"]) +def test_fast_forward_singleton(file: str) -> None: + with open(f"ff-tests/{file}.spend", "rb") as f: + spend = CoinSpend.from_bytes(f.read()) + + parents_parent = bytes([0] * 32) + new_parent = Coin(parents_parent, spend.coin.puzzle_hash, spend.coin.amount) + new_coin = Coin(new_parent.name(), new_parent.puzzle_hash, new_parent.amount) + new_solution = fast_forward_singleton(spend, new_coin, new_parent) + + expected = bytearray(bytes(spend.solution)) + # this is where the parent's parent coin ID lives in the solution + expected[3:35] = parents_parent + assert expected == new_solution diff --git a/wheel/chia_rs.pyi b/wheel/chia_rs.pyi index 55f0b9450..2aa49d4ec 100644 --- a/wheel/chia_rs.pyi +++ b/wheel/chia_rs.pyi @@ -13,6 +13,9 @@ def solution_generator_backrefs(spends: Sequence[Tuple[Coin, bytes, bytes]]) -> def compute_merkle_set_root(items: Sequence[bytes]) -> bytes: ... +def supports_fast_forward(spend: CoinSpend) -> bool : ... +def fast_forward_singleton(spend: CoinSpend, new_coin: Coin, new_parent: Coin) -> bytes: ... + def run_block_generator( program: ReadableBuffer, args: List[ReadableBuffer], max_cost: int, flags: int ) -> Tuple[Optional[int], Optional[SpendBundleConditions]]: ... @@ -42,6 +45,7 @@ ENABLE_FIXED_DIV: int = ... ALLOW_BACKREFS: int = ... ELIGIBLE_FOR_DEDUP: int = ... +ELIGIBLE_FOR_FF: int = ... NO_UNKNOWN_OPS: int = ... diff --git a/wheel/generate_type_stubs.py b/wheel/generate_type_stubs.py index 0ec0bca1a..2047b852f 100644 --- a/wheel/generate_type_stubs.py +++ b/wheel/generate_type_stubs.py @@ -189,6 +189,9 @@ def solution_generator_backrefs(spends: Sequence[Tuple[Coin, bytes, bytes]]) -> def compute_merkle_set_root(items: Sequence[bytes]) -> bytes: ... +def supports_fast_forward(spend: CoinSpend) -> bool : ... +def fast_forward_singleton(spend: CoinSpend, new_coin: Coin, new_parent: Coin) -> bytes: ... + def run_block_generator( program: ReadableBuffer, args: List[ReadableBuffer], max_cost: int, flags: int ) -> Tuple[Optional[int], Optional[SpendBundleConditions]]: ... @@ -218,6 +221,7 @@ def run_puzzle( ALLOW_BACKREFS: int = ... ELIGIBLE_FOR_DEDUP: int = ... +ELIGIBLE_FOR_FF: int = ... NO_UNKNOWN_OPS: int = ... diff --git a/wheel/src/api.rs b/wheel/src/api.rs index 0d22352af..009fe3021 100644 --- a/wheel/src/api.rs +++ b/wheel/src/api.rs @@ -50,6 +50,7 @@ use std::iter::zip; use crate::run_program::{run_chia_program, serialized_length}; use crate::adapt_response::eval_err_to_pyresult; +use chia::fast_forward::fast_forward_singleton as native_ff; use chia::gen::get_puzzle_and_solution::get_puzzle_and_solution_for_coin as parse_puzzle_solution; use chia::gen::validation_error::ValidationErr; use clvmr::allocator::NodePtr; @@ -274,6 +275,57 @@ impl AugSchemeMPL { } } +#[pyfunction] +fn supports_fast_forward(spend: &CoinSpend) -> bool { + // the test function just attempts the rebase onto a dummy parent coin + let new_parent = Coin { + parent_coin_info: [0_u8; 32].into(), + puzzle_hash: spend.coin.puzzle_hash, + amount: spend.coin.amount, + }; + let new_coin = Coin { + parent_coin_info: new_parent.coin_id().into(), + puzzle_hash: spend.coin.puzzle_hash, + amount: spend.coin.amount, + }; + + let mut a = make_allocator(LIMIT_HEAP); + let Ok(puzzle) = node_from_bytes(&mut a, spend.puzzle_reveal.as_slice()) else { + return false; + }; + let Ok(solution) = node_from_bytes(&mut a, spend.solution.as_slice()) else { + return false; + }; + + native_ff( + &mut a, + puzzle, + solution, + &spend.coin, + &new_coin, + &new_parent, + ) + .is_ok() +} + +#[pyfunction] +fn fast_forward_singleton<'p>( + py: Python<'p>, + spend: &CoinSpend, + new_coin: &Coin, + new_parent: &Coin, +) -> PyResult<&'p PyBytes> { + let mut a = make_allocator(LIMIT_HEAP); + let puzzle = node_from_bytes(&mut a, spend.puzzle_reveal.as_slice())?; + let solution = node_from_bytes(&mut a, spend.solution.as_slice())?; + + let new_solution = native_ff(&mut a, puzzle, solution, &spend.coin, new_coin, new_parent)?; + Ok(PyBytes::new( + py, + node_to_bytes(&a, new_solution)?.as_slice(), + )) +} + #[pymodule] pub fn chia_rs(py: Python, m: &PyModule) -> PyResult<()> { // generator functions @@ -282,6 +334,8 @@ pub fn chia_rs(py: Python, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(run_puzzle, m)?)?; m.add_function(wrap_pyfunction!(solution_generator, m)?)?; m.add_function(wrap_pyfunction!(solution_generator_backrefs, m)?)?; + m.add_function(wrap_pyfunction!(supports_fast_forward, m)?)?; + m.add_function(wrap_pyfunction!(fast_forward_singleton, m)?)?; m.add_class::()?; m.add( "ELIGIBLE_FOR_DEDUP",