diff --git a/crates/chia-consensus/fuzz/Cargo.toml b/crates/chia-consensus/fuzz/Cargo.toml index 126f1f604..2bc426fcd 100644 --- a/crates/chia-consensus/fuzz/Cargo.toml +++ b/crates/chia-consensus/fuzz/Cargo.toml @@ -78,13 +78,6 @@ test = false doc = false bench = false -[[bin]] -name = "run-puzzle" -path = "fuzz_targets/run-puzzle.rs" -test = false -doc = false -bench = false - [[bin]] name = "fast-forward" path = "fuzz_targets/fast-forward.rs" diff --git a/crates/chia-consensus/fuzz/fuzz_targets/fast-forward.rs b/crates/chia-consensus/fuzz/fuzz_targets/fast-forward.rs index d23ddae24..02b1e11fc 100644 --- a/crates/chia-consensus/fuzz/fuzz_targets/fast-forward.rs +++ b/crates/chia-consensus/fuzz/fuzz_targets/fast-forward.rs @@ -1,8 +1,11 @@ #![no_main] use chia_consensus::consensus_constants::TEST_CONSTANTS; use chia_consensus::fast_forward::fast_forward_singleton; -use chia_consensus::gen::conditions::{MempoolVisitor, ELIGIBLE_FOR_FF}; -use chia_consensus::gen::run_puzzle::run_puzzle; +use chia_consensus::gen::conditions::{ + parse_conditions, MempoolVisitor, ParseState, SpendBundleConditions, SpendConditions, + ELIGIBLE_FOR_FF, +}; +use chia_consensus::gen::spend_visitor::SpendVisitor; use chia_consensus::gen::validation_error::{ErrorCode, ValidationErr}; use chia_protocol::Bytes32; use chia_protocol::Coin; @@ -10,12 +13,17 @@ use chia_protocol::CoinSpend; use chia_traits::streamable::Streamable; use clvm_traits::ToClvm; use clvm_utils::tree_hash; -use clvmr::serde::node_to_bytes; +use clvmr::serde::{node_from_bytes, node_to_bytes}; use clvmr::{Allocator, NodePtr}; use hex_literal::hex; use libfuzzer_sys::fuzz_target; use std::io::Cursor; +use clvmr::chia_dialect::ChiaDialect; +use clvmr::reduction::Reduction; +use clvmr::run_program::run_program; +use std::sync::Arc; + fuzz_target!(|data: &[u8]| { let Ok(spend) = CoinSpend::parse::(&mut Cursor::new(data)) else { return; @@ -66,6 +74,60 @@ fuzz_target!(|data: &[u8]| { } }); +fn run_puzzle( + a: &mut Allocator, + puzzle: &[u8], + solution: &[u8], + parent_id: &[u8], + amount: u64, +) -> core::result::Result { + let puzzle = node_from_bytes(a, puzzle)?; + let solution = node_from_bytes(a, solution)?; + + let dialect = ChiaDialect::new(0); + let max_cost = 11_000_000_000; + let Reduction(clvm_cost, conditions) = run_program(a, &dialect, puzzle, solution, max_cost)?; + + let mut ret = SpendBundleConditions { + removal_amount: amount as u128, + ..Default::default() + }; + let mut state = ParseState::default(); + + let puzzle_hash = tree_hash(a, puzzle); + let coin_id = Arc::::new( + Coin { + parent_coin_info: parent_id.try_into().unwrap(), + puzzle_hash: puzzle_hash.into(), + amount, + } + .coin_id(), + ); + + let mut spend = SpendConditions::new( + a.new_atom(parent_id)?, + amount, + a.new_atom(&puzzle_hash)?, + coin_id, + ); + + let mut visitor = MempoolVisitor::new_spend(&mut spend); + + let mut cost_left = max_cost - clvm_cost; + parse_conditions( + a, + &mut ret, + &mut state, + spend, + conditions, + 0, + &mut cost_left, + &TEST_CONSTANTS, + &mut visitor, + )?; + ret.cost = max_cost - cost_left; + Ok(ret) +} fn test_ff( a: &mut Allocator, spend: &CoinSpend, @@ -83,27 +145,21 @@ fn test_ff( let new_solution = node_to_bytes(a, new_solution).expect("serialize new solution"); // run original spend - let conditions1 = run_puzzle::( + let conditions1 = run_puzzle( a, spend.puzzle_reveal.as_slice(), spend.solution.as_slice(), &spend.coin.parent_coin_info, spend.coin.amount, - 11_000_000_000, - 0, - &TEST_CONSTANTS, ); // run new spend - let conditions2 = run_puzzle::( + let conditions2 = run_puzzle( a, spend.puzzle_reveal.as_slice(), new_solution.as_slice(), &new_coin.parent_coin_info, new_coin.amount, - 11_000_000_000, - 0, - &TEST_CONSTANTS, ); // These are the kinds of failures that can happen because of the diff --git a/crates/chia-consensus/fuzz/fuzz_targets/run-puzzle.rs b/crates/chia-consensus/fuzz/fuzz_targets/run-puzzle.rs deleted file mode 100644 index dcd7c7571..000000000 --- a/crates/chia-consensus/fuzz/fuzz_targets/run-puzzle.rs +++ /dev/null @@ -1,28 +0,0 @@ -#![no_main] -use chia_consensus::consensus_constants::TEST_CONSTANTS; -use chia_consensus::gen::conditions::MempoolVisitor; -use chia_consensus::gen::flags::ALLOW_BACKREFS; -use chia_consensus::gen::run_puzzle::run_puzzle; -use chia_protocol::CoinSpend; -use chia_traits::streamable::Streamable; -use clvmr::Allocator; -use libfuzzer_sys::fuzz_target; -use std::io::Cursor; - -fuzz_target!(|data: &[u8]| { - let mut a = Allocator::new(); - - let Ok(spend) = CoinSpend::parse::(&mut Cursor::new(data)) else { - return; - }; - let _ = run_puzzle::( - &mut a, - spend.puzzle_reveal.as_slice(), - spend.solution.as_slice(), - (&spend.coin.parent_coin_info).into(), - spend.coin.amount, - 11_000_000_000, - ALLOW_BACKREFS, - &TEST_CONSTANTS, - ); -}); diff --git a/crates/chia-consensus/src/fast_forward.rs b/crates/chia-consensus/src/fast_forward.rs index 1c0bdb17c..a803a5a21 100644 --- a/crates/chia-consensus/src/fast_forward.rs +++ b/crates/chia-consensus/src/fast_forward.rs @@ -155,16 +155,85 @@ pub fn fast_forward_singleton( #[cfg(test)] mod tests { use super::*; + use crate::consensus_constants::ConsensusConstants; use crate::consensus_constants::TEST_CONSTANTS; use crate::gen::conditions::MempoolVisitor; - use crate::gen::run_puzzle::run_puzzle; + use crate::gen::conditions::{ + parse_conditions, ParseState, SpendBundleConditions, SpendConditions, + }; + use crate::gen::spend_visitor::SpendVisitor; + use crate::gen::validation_error::ValidationErr; + use chia_protocol::Bytes32; + use chia_protocol::Coin; use chia_protocol::CoinSpend; use chia_traits::streamable::Streamable; use clvm_traits::ToClvm; - use clvmr::serde::{node_from_bytes, node_to_bytes}; + use clvm_utils::tree_hash; + use clvmr::allocator::Allocator; + use clvmr::chia_dialect::ChiaDialect; + use clvmr::reduction::Reduction; + use clvmr::run_program::run_program; + use clvmr::serde::{node_from_bytes, node_from_bytes_backrefs, node_to_bytes}; use hex_literal::hex; use rstest::rstest; use std::fs; + use std::sync::Arc; + + pub fn run_puzzle( + a: &mut Allocator, + puzzle: &[u8], + solution: &[u8], + parent_id: &[u8], + amount: u64, + ) -> core::result::Result { + let puzzle = node_from_bytes(a, puzzle)?; + let solution = node_from_bytes(a, solution)?; + + let dialect = ChiaDialect::new(0); + let max_cost = 11_000_000_000; + let Reduction(clvm_cost, conditions) = + run_program(a, &dialect, puzzle, solution, max_cost)?; + + let mut ret = SpendBundleConditions { + removal_amount: amount as u128, + ..Default::default() + }; + let mut state = ParseState::default(); + + let puzzle_hash = tree_hash(a, puzzle); + let coin_id = Arc::::new( + Coin { + parent_coin_info: parent_id.try_into().unwrap(), + puzzle_hash: puzzle_hash.into(), + amount, + } + .coin_id(), + ); + + let mut spend = SpendConditions::new( + a.new_atom(parent_id)?, + amount, + a.new_atom(&puzzle_hash)?, + coin_id, + ); + + let mut visitor = MempoolVisitor::new_spend(&mut spend); + + let mut cost_left = max_cost - clvm_cost; + parse_conditions( + a, + &mut ret, + &mut state, + spend, + conditions, + 0, + &mut cost_left, + &TEST_CONSTANTS, + &mut visitor, + )?; + ret.cost = max_cost - cost_left; + Ok(ret) + } // this test loads CoinSpends from file (Coin, puzzle, solution)-triples // and "fast-forwards" the spend onto a few different parent-parent coins @@ -226,28 +295,22 @@ mod tests { let new_solution = node_to_bytes(&a, new_solution).expect("serialize new solution"); // run original spend - let conditions1 = run_puzzle::( + let conditions1 = run_puzzle( &mut a, spend.puzzle_reveal.as_slice(), spend.solution.as_slice(), &spend.coin.parent_coin_info, spend.coin.amount, - 11_000_000_000, - 0, - &TEST_CONSTANTS, ) .expect("run_puzzle"); // run new spend - let conditions2 = run_puzzle::( + let conditions2 = run_puzzle( &mut a, spend.puzzle_reveal.as_slice(), new_solution.as_slice(), &new_coin.parent_coin_info, new_coin.amount, - 11_000_000_000, - 0, - &TEST_CONSTANTS, ) .expect("run_puzzle"); diff --git a/crates/chia-consensus/src/gen/mod.rs b/crates/chia-consensus/src/gen/mod.rs index 2da5c7d1d..01d5a66db 100644 --- a/crates/chia-consensus/src/gen/mod.rs +++ b/crates/chia-consensus/src/gen/mod.rs @@ -8,7 +8,6 @@ pub mod messages; pub mod opcodes; pub mod owned_conditions; pub mod run_block_generator; -pub mod run_puzzle; pub mod sanitize_int; pub mod solution_generator; pub mod spend_visitor; diff --git a/crates/chia-consensus/src/gen/run_puzzle.rs b/crates/chia-consensus/src/gen/run_puzzle.rs deleted file mode 100644 index 36ceb3196..000000000 --- a/crates/chia-consensus/src/gen/run_puzzle.rs +++ /dev/null @@ -1,80 +0,0 @@ -use crate::consensus_constants::ConsensusConstants; -use crate::gen::conditions::{ - parse_conditions, ParseState, SpendBundleConditions, SpendConditions, -}; -use crate::gen::flags::ALLOW_BACKREFS; -use crate::gen::spend_visitor::SpendVisitor; -use crate::gen::validation_error::ValidationErr; -use chia_protocol::Bytes32; -use chia_protocol::Coin; -use clvm_utils::tree_hash; -use clvmr::allocator::Allocator; -use clvmr::chia_dialect::ChiaDialect; -use clvmr::reduction::Reduction; -use clvmr::run_program::run_program; -use clvmr::serde::{node_from_bytes, node_from_bytes_backrefs}; -use std::sync::Arc; - -#[allow(clippy::too_many_arguments)] -pub fn run_puzzle( - a: &mut Allocator, - puzzle: &[u8], - solution: &[u8], - parent_id: &[u8], - amount: u64, - max_cost: u64, - flags: u32, - constants: &ConsensusConstants, -) -> Result { - let deserialize = if (flags & ALLOW_BACKREFS) != 0 { - node_from_bytes_backrefs - } else { - node_from_bytes - }; - let puzzle = deserialize(a, puzzle)?; - let solution = deserialize(a, solution)?; - - let dialect = ChiaDialect::new(flags); - let Reduction(clvm_cost, conditions) = run_program(a, &dialect, puzzle, solution, max_cost)?; - - let mut ret = SpendBundleConditions { - removal_amount: amount as u128, - ..Default::default() - }; - let mut state = ParseState::default(); - - let puzzle_hash = tree_hash(a, puzzle); - let coin_id = Arc::::new( - Coin { - parent_coin_info: parent_id.try_into().unwrap(), - puzzle_hash: puzzle_hash.into(), - amount, - } - .coin_id(), - ); - - let mut spend = SpendConditions::new( - a.new_atom(parent_id)?, - amount, - a.new_atom(&puzzle_hash)?, - coin_id, - ); - - let mut visitor = V::new_spend(&mut spend); - - let mut cost_left = max_cost - clvm_cost; - - parse_conditions( - a, - &mut ret, - &mut state, - spend, - conditions, - flags, - &mut cost_left, - constants, - &mut visitor, - )?; - ret.cost = max_cost - cost_left; - Ok(ret) -} diff --git a/tests/test_run_puzzle.py b/tests/test_run_puzzle.py deleted file mode 100644 index bcb3b14bc..000000000 --- a/tests/test_run_puzzle.py +++ /dev/null @@ -1,143 +0,0 @@ -from chia_rs import run_puzzle, run_chia_program, ALLOW_BACKREFS -from chia_rs.sized_bytes import bytes32 -import pytest -from run_gen import print_spend_bundle_conditions, DEFAULT_CONSTANTS -from clvm.SExp import SExp -from clvm.casts import int_from_bytes -from clvm_tools import binutils - - -@pytest.mark.parametrize("flags", [0, ALLOW_BACKREFS]) -@pytest.mark.parametrize( - "input_file", - ["generator-tests/block-834752.txt", "generator-tests/block-834752-compressed.txt"], -) -def test_block_834752(flags: int, input_file: str) -> None: - block = bytes.fromhex(open(input_file, "r").read().split("\n")[0]) - - if (flags & ALLOW_BACKREFS) == 0 and "compressed" in input_file: - with pytest.raises(OSError, match="bad encoding"): - cost, ret = run_chia_program(block, b"\xff\x80\x80", 11000000000, flags) - return - else: - cost, ret = run_chia_program(block, b"\xff\x80\x80", 11000000000, flags) - - assert ret.pair is not None - ret = ret.pair[0] - puzzles = [] - - while ret.pair is not None: - spend = ret.pair[0] - assert spend.pair is not None - - parent = spend.pair[0].atom - assert parent is not None - spend = spend.pair[1] - assert spend.pair is not None - - puzzle = SExp.to(spend.pair[0]) - spend = spend.pair[1] - assert spend.pair is not None - - amount = int_from_bytes(spend.pair[0].atom) - spend = spend.pair[1] - assert spend.pair is not None - - solution = SExp.to(spend.pair[0]) - spend = spend.pair[1] - - puzzles.append((bytes32(parent), amount, puzzle.as_bin(), solution.as_bin())) - ret = ret.pair[1] - - output = "" - for parent, amount, puzzle, solution in puzzles: - conds = run_puzzle( - puzzle, solution, parent, amount, 11000000000, flags, DEFAULT_CONSTANTS - ) - output += print_spend_bundle_conditions(conds) - - assert ( - output - == """\ -SPENDS: -- coin id: afd297097757a8f5a3f3266933a6c29a7674c71028825562e7e4cac02b9228f6 ph: b78c1c1c0fe082b9c7f18d7e7c716b1607fd62dbdf3eef18f79e2717789ac55f - CREATE_COIN: ph: dca1429bcffab70d2218df91683ebe292305925337c0fffa91d5244838ffbd80 amount: 1 - AGG_SIG_ME pk: 8116639d853ecd6109277a9d83d3acc7e53a18d3524262ec9b99df923d22a390cbf0f632bced556dd9886bbf53f444b6 msg: d497b66589f5d8bdc0631678cc488e25360d114eee51b84a3c1685771a7daa2d -cost: 3729870 -removal_amount: 1 -addition_amount: 1 -SPENDS: -- coin id: 8522228562997c720038e6dca3721c36b054d766e0de80087ae9d7b4b229df29 ph: 1dc30429a17e6ee7d5b18a795da6bf838b7c87d0cf65cfb000fca5bab1ccbe19 - CREATE_COIN: ph: c30e2a348a6a4f56fc8d4ce44cebda06f72420d68e454dd765ed40b782fca307 amount: 1 - AGG_SIG_ME pk: 8a505367d099210c24f1be8945a83de7db6d9396a9742c8f609aebf7ba56bbaacb038819b122741211bbc9227c903573 msg: 6cbb9e5def3ed896de9651b54145afc27bcbba9c61ca853b1e3bf0e6b8a78e9a -cost: 3729870 -removal_amount: 1 -addition_amount: 1 -SPENDS: -- coin id: b4fa0835dd9d595cf3d3c5e574ce081c7cca37452c4c336caaf8fbf644c5b268 ph: 0ac6539fede8c4cd50610af58f82b8cc74c774577f0a098db8ab2f42e5e90a9f - CREATE_COIN: ph: 948a5a1b3367d80aebc23a7ae772b140b8626f908d5b921f9980a0f01280e3c8 amount: 1 - AGG_SIG_ME pk: ab6824901d856c5a8c1664c990d1ef94a19ed7b4ab28a6b8e064f1a11e07f5e75bdb6ff8242f517534df36ae03c81da0 msg: 38442a894ff28abb1225d3698c2e09aaea12827c9141f9e4c85267a38cec3e6f -cost: 3729868 -removal_amount: 1 -addition_amount: 1 -SPENDS: -- coin id: 3c3412900b156403b13bb4191f1d6818619f73c97337829a4f821012b24d88eb ph: bc0e759db02410acb193d0c1c0a6841a2a821c9322570e2f23dfe220d9e6ae8f - ASSERT_HEIGHT_RELATIVE 32 - CREATE_COIN: ph: 013beb3fa36f6d3f221253f7ef380e4d1197246b57b453ce32d339e9be4b2eec amount: 1 - AGG_SIG_ME pk: afdbc8d2811665196a20931b06ffe981a2ec64aebd2d917478bf8441d77cb2b62f96194277d91983c5ca9edf0a17fdcc msg: f19e77711df9e26a0e1fda2279bd73d6dcb9afe91dafadb4c3c20b1fe441a1b3 -cost: 3792779 -removal_amount: 1 -addition_amount: 1 -SPENDS: -- coin id: a66d7b064c9fdfbcffe0755766c1a5d66899fab9f9a6cb4f93d614d676bc8292 ph: ba5089cb215c3f37dbc5718820d16d454694869e756653a516d2abb3faacd843 - ASSERT_HEIGHT_RELATIVE 32 - CREATE_COIN: ph: 87c64d9ef085869b1bf272816a752d093e272fa69d6840181cd48fa8eb86dcc3 amount: 1 - AGG_SIG_ME pk: 8e2ab4bd0f4b65f0e6e1cc1f54fd3a953a36afc98ec25a741958bf1f19d0a416f2b39b89d4bd9870f11d6bf09030780e msg: 01f207c53eb38d9a0ca38981ddedf93c9d62ac0073f5706c1d513cc109206a00 -cost: 3792843 -removal_amount: 1 -addition_amount: 1 -SPENDS: -- coin id: ed418e8b3f49d86a0ab42343e1dc864f796026b8646c953a3bd54329a3843c1f ph: 87864b58be87e5f0fe566955088597ea0f5aafaad1ce0ed8dd02f5c84d257aa6 - ASSERT_HEIGHT_RELATIVE 32 - CREATE_COIN: ph: 70aea9db5a7c74a2dfa632ddac0e7cd3283c6439c1feae0417645b6392104ded amount: 1 - AGG_SIG_ME pk: a5383a3b9a6c1a94a85e4f982e1fa3af2c99087e5f6df8b887d30c109f71043671683a1ae985d7d874fbe07dfa6d88b7 msg: 5d53d6baf98f40ce52d588135fa97fc7c6756c6fb6315298902d631548704d8c -cost: 3792835 -removal_amount: 1 -addition_amount: 1 -""" - ) - - -@pytest.mark.parametrize("flags", [0, ALLOW_BACKREFS]) -def test_failure(flags: int) -> None: - - output = "" - parent = bytes32(b"1" * 32) - amount = 1337 - # (mod (solution) (if (= solution (q . 1)) () (x solution))) - puzzle = binutils.assemble("(a (i (= 2 (q . 1)) () (q 8 2)) 1)").as_bin() - solution1 = binutils.assemble("(1)").as_bin() - solution2 = binutils.assemble("(2)").as_bin() - - # the puzzle expects (1) - conds = run_puzzle( - puzzle, solution1, parent, amount, 11000000000, flags, DEFAULT_CONSTANTS - ) - output += print_spend_bundle_conditions(conds) - print(output) - assert ( - output - == """\ -SPENDS: -- coin id: 7767e945d8b73704d3ed84277b3df4572cec7d418629dc2f0325385e708c7724 ph: 51f3dbcff4ada9fe7030d6c017f243b903f9baf503e2d0590b3b78b5c2589674 -cost: 465 -removal_amount: 1337 -addition_amount: 0 -""" - ) - - with pytest.raises(ValueError, match="ValidationError"): - # the puzzle does not expect (2) - run_puzzle( - puzzle, solution2, parent, amount, 11000000000, flags, DEFAULT_CONSTANTS - ) diff --git a/wheel/generate_type_stubs.py b/wheel/generate_type_stubs.py index 1db73d5a0..a0b81eab0 100644 --- a/wheel/generate_type_stubs.py +++ b/wheel/generate_type_stubs.py @@ -288,10 +288,6 @@ def run_block_generator2( program: ReadableBuffer, block_refs: List[ReadableBuffer], max_cost: int, flags: int, constants: ConsensusConstants ) -> Tuple[Optional[int], Optional[SpendBundleConditions]]: ... -def run_puzzle( - puzzle: bytes, solution: bytes, parent_id: bytes32, amount: int, max_cost: int, flags: int, constants: ConsensusConstants -) -> SpendBundleConditions: ... - def confirm_included_already_hashed( root: bytes32, item: bytes32, diff --git a/wheel/python/chia_rs/chia_rs.pyi b/wheel/python/chia_rs/chia_rs.pyi index d11412604..05147d0de 100644 --- a/wheel/python/chia_rs/chia_rs.pyi +++ b/wheel/python/chia_rs/chia_rs.pyi @@ -30,10 +30,6 @@ def run_block_generator2( program: ReadableBuffer, block_refs: List[ReadableBuffer], max_cost: int, flags: int, constants: ConsensusConstants ) -> Tuple[Optional[int], Optional[SpendBundleConditions]]: ... -def run_puzzle( - puzzle: bytes, solution: bytes, parent_id: bytes32, amount: int, max_cost: int, flags: int, constants: ConsensusConstants -) -> SpendBundleConditions: ... - def confirm_included_already_hashed( root: bytes32, item: bytes32, diff --git a/wheel/src/api.rs b/wheel/src/api.rs index 9a4a79579..51f661978 100644 --- a/wheel/src/api.rs +++ b/wheel/src/api.rs @@ -1,13 +1,11 @@ use crate::run_generator::{py_to_slice, run_block_generator, run_block_generator2}; use chia_consensus::allocator::make_allocator; use chia_consensus::consensus_constants::ConsensusConstants; -use chia_consensus::gen::conditions::MempoolVisitor; use chia_consensus::gen::flags::{ ALLOW_BACKREFS, MEMPOOL_MODE, NO_UNKNOWN_CONDS, STRICT_ARGS_COUNT, }; use chia_consensus::gen::owned_conditions::{OwnedSpendBundleConditions, OwnedSpendConditions}; use chia_consensus::gen::run_block_generator::setup_generator_args; -use chia_consensus::gen::run_puzzle::run_puzzle as native_run_puzzle; use chia_consensus::gen::solution_generator::solution_generator as native_solution_generator; use chia_consensus::gen::solution_generator::solution_generator_backrefs as native_solution_generator_backrefs; use chia_consensus::merkle_set::compute_merkle_set_root as compute_merkle_root_impl; @@ -228,23 +226,6 @@ pub fn get_puzzle_and_solution_for_coin2<'a>( )) } -#[pyfunction] -fn run_puzzle( - puzzle: &[u8], - solution: &[u8], - parent_id: &[u8], - amount: u64, - max_cost: Cost, - flags: u32, - constants: &ConsensusConstants, -) -> PyResult { - let mut a = make_allocator(LIMIT_HEAP); - let conds = native_run_puzzle::( - &mut a, puzzle, solution, parent_id, amount, max_cost, flags, constants, - )?; - Ok(OwnedSpendBundleConditions::from(&a, conds)) -} - // this is like a CoinSpend but with references to the puzzle and solution, // rather than owning them type CoinSpendRef = (Coin, PyBackedBytes, PyBackedBytes); @@ -469,7 +450,6 @@ pub fn chia_rs(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { // generator functions m.add_function(wrap_pyfunction!(run_block_generator, m)?)?; m.add_function(wrap_pyfunction!(run_block_generator2, m)?)?; - 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)?)?;