From c191c3c38ccb42908f83fb8ce0932780341dba2c Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 10 Jul 2025 14:58:50 +0100 Subject: [PATCH 01/10] initial commit --- chia/full_node/full_node_rpc_api.py | 15 +---- chia/full_node/mempool_check_conditions.py | 64 +++++----------------- 2 files changed, 15 insertions(+), 64 deletions(-) diff --git a/chia/full_node/full_node_rpc_api.py b/chia/full_node/full_node_rpc_api.py index edc9426ae8aa..686e8154020b 100644 --- a/chia/full_node/full_node_rpc_api.py +++ b/chia/full_node/full_node_rpc_api.py @@ -489,7 +489,7 @@ async def get_block_spends(self, request: dict[str, Any]) -> EndpointResult: spends = get_spends_for_block(block_generator, full_block.height, self.service.constants) - return {"block_spends": spends} + return spends async def get_block_spends_with_conditions(self, request: dict[str, Any]) -> EndpointResult: if "header_hash" not in request: @@ -507,18 +507,7 @@ async def get_block_spends_with_conditions(self, request: dict[str, Any]) -> End block_generator, full_block.height, self.service.constants ) - return { - "block_spends_with_conditions": [ - { - "coin_spend": spend_with_conditions.coin_spend, - "conditions": [ - {"opcode": condition.opcode, "vars": [var.hex() for var in condition.vars]} - for condition in spend_with_conditions.conditions - ], - } - for spend_with_conditions in spends_with_conditions - ] - } + return {"block_spends_with_conditions": [spends_with_conditions]} async def get_block_record_by_height(self, request: dict[str, Any]) -> EndpointResult: if "height" not in request: diff --git a/chia/full_node/mempool_check_conditions.py b/chia/full_node/mempool_check_conditions.py index e0b546026767..52f08b412b4b 100644 --- a/chia/full_node/mempool_check_conditions.py +++ b/chia/full_node/mempool_check_conditions.py @@ -7,15 +7,13 @@ CoinSpend, ConsensusConstants, get_flags_for_height_and_constants, - run_chia_program, + get_spends_for_trusted_block, ) from chia_rs import get_puzzle_and_solution_for_coin2 as get_puzzle_and_solution_for_coin_rust -from chia_rs.sized_ints import uint64 -from chia.consensus.condition_tools import conditions_for_solution from chia.types.blockchain_format.coin import Coin from chia.types.blockchain_format.program import Program -from chia.types.coin_spend import CoinSpendWithConditions, SpendInfo, make_spend +from chia.types.coin_spend import SpendInfo from chia.types.generator_types import BlockGenerator DESERIALIZE_MOD = Program.from_bytes(CHIALISP_DESERIALISATION) @@ -40,63 +38,27 @@ def get_puzzle_and_solution_for_coin( raise ValueError(f"Failed to get puzzle and solution for coin {coin}, error: {e}") from e -def get_spends_for_block(generator: BlockGenerator, height: int, constants: ConsensusConstants) -> list[CoinSpend]: - args = bytearray(b"\xff") - args += bytes(DESERIALIZE_MOD) - args += b"\xff" - args += bytes(Program.to(generator.generator_refs)) - args += b"\x80\x80" - - _, ret = run_chia_program( +def get_spends_for_block( + generator: BlockGenerator, height: int, constants: ConsensusConstants +) -> dict[str, list[CoinSpend]]: + spends = get_spends_for_trusted_block( + constants, bytes(generator.program), - bytes(args), - constants.MAX_BLOCK_COST_CLVM, + generator.generator_refs, get_flags_for_height_and_constants(height, constants), ) - spends: list[CoinSpend] = [] - - for spend in Program.to(ret).first().as_iter(): - try: - parent, puzzle, amount, solution = spend.as_iter() - puzzle_hash = puzzle.get_tree_hash() - coin = Coin(parent.as_atom(), puzzle_hash, uint64(amount.as_int())) - spends.append(make_spend(coin, puzzle, solution)) - except ValueError: - log.warning("get_spends_for_block() encountered a puzzle we couldn't serialize: {e}") - return spends def get_spends_for_block_with_conditions( generator: BlockGenerator, height: int, constants: ConsensusConstants -) -> list[CoinSpendWithConditions]: - args = bytearray(b"\xff") - args += bytes(DESERIALIZE_MOD) - args += b"\xff" - args += bytes(Program.to(generator.generator_refs)) - args += b"\x80\x80" - - flags = get_flags_for_height_and_constants(height, constants) - - _, ret = run_chia_program( +) -> dict[any]: + spends = get_spends_for_trusted_block( + constants, bytes(generator.program), - bytes(args), - constants.MAX_BLOCK_COST_CLVM, - flags, + generator.generator_refs, + get_flags_for_height_and_constants(height, constants), ) - spends: list[CoinSpendWithConditions] = [] - - for spend in Program.to(ret).first().as_iter(): - try: - parent, puzzle, amount, solution = spend.as_iter() - puzzle_hash = puzzle.get_tree_hash() - coin = Coin(parent.as_atom(), puzzle_hash, uint64(amount.as_int())) - coin_spend = make_spend(coin, puzzle, solution) - conditions = conditions_for_solution(puzzle, solution, constants.MAX_BLOCK_COST_CLVM) - spends.append(CoinSpendWithConditions(coin_spend, conditions)) - except ValueError: - log.warning("get_spends_for_block_with_conditions() encountered a puzzle we couldn't serialize: {e}") - return spends From b99424cf4d4c9f99cc27bb5e682950f03a803353 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 11 Jul 2025 10:54:42 +0100 Subject: [PATCH 02/10] fix precommit errors --- chia/full_node/full_node_rpc_api.py | 4 ++-- chia/full_node/mempool_check_conditions.py | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/chia/full_node/full_node_rpc_api.py b/chia/full_node/full_node_rpc_api.py index 686e8154020b..ebb912430a93 100644 --- a/chia/full_node/full_node_rpc_api.py +++ b/chia/full_node/full_node_rpc_api.py @@ -482,14 +482,14 @@ async def get_block_spends(self, request: dict[str, Any]) -> EndpointResult: if full_block is None: raise ValueError(f"Block {header_hash.hex()} not found") - spends: list[CoinSpend] = [] + spends: list[dict[str, list[CoinSpend]]] = [] block_generator = await get_block_generator(self.service.blockchain.lookup_block_generators, full_block) if block_generator is None: # if block is not a transaction block. return {"block_spends": spends} spends = get_spends_for_block(block_generator, full_block.height, self.service.constants) - return spends + return {"block_spends": spends[0]} async def get_block_spends_with_conditions(self, request: dict[str, Any]) -> EndpointResult: if "header_hash" not in request: diff --git a/chia/full_node/mempool_check_conditions.py b/chia/full_node/mempool_check_conditions.py index 52f08b412b4b..92ce86ee3d41 100644 --- a/chia/full_node/mempool_check_conditions.py +++ b/chia/full_node/mempool_check_conditions.py @@ -1,6 +1,7 @@ from __future__ import annotations import logging +import typing from chia_puzzles_py.programs import CHIALISP_DESERIALISATION from chia_rs import ( @@ -40,10 +41,10 @@ def get_puzzle_and_solution_for_coin( def get_spends_for_block( generator: BlockGenerator, height: int, constants: ConsensusConstants -) -> dict[str, list[CoinSpend]]: +) -> list[dict[str, list[CoinSpend]]]: spends = get_spends_for_trusted_block( constants, - bytes(generator.program), + generator.program, generator.generator_refs, get_flags_for_height_and_constants(height, constants), ) @@ -53,10 +54,10 @@ def get_spends_for_block( def get_spends_for_block_with_conditions( generator: BlockGenerator, height: int, constants: ConsensusConstants -) -> dict[any]: +) -> list[dict[str, typing.Any]]: spends = get_spends_for_trusted_block( constants, - bytes(generator.program), + generator.program, generator.generator_refs, get_flags_for_height_and_constants(height, constants), ) From c73b02a1aef67b8ccfb3f55dbd89fba81474dafc Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 11 Jul 2025 12:35:34 +0100 Subject: [PATCH 03/10] test passing --- chia/_tests/core/test_full_node_rpc.py | 5 ++++- chia/full_node/full_node_rpc_api.py | 5 ++--- chia/full_node/full_node_rpc_client.py | 21 +++++++++++++++------ chia/full_node/mempool_check_conditions.py | 3 ++- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/chia/_tests/core/test_full_node_rpc.py b/chia/_tests/core/test_full_node_rpc.py index 0a24054854df..38e7badc2703 100644 --- a/chia/_tests/core/test_full_node_rpc.py +++ b/chia/_tests/core/test_full_node_rpc.py @@ -254,7 +254,8 @@ async def test1( assert coin_spend_with_conditions.coin_spend.solution == SerializedProgram.fromhex( "ff80ffff01ffff33ffa063c767818f8b7cc8f3760ce34a09b7f34cd9ddf09d345c679b6897e7620c575cff8601977420dc0080ffff3cffa0a2366d6d8e1ce7496175528f5618a13da8401b02f2bac1eaae8f28aea9ee54798080ff8080" ) - assert coin_spend_with_conditions.conditions == [ + + expected = [ ConditionWithArgs( ConditionOpcode(b"2"), [ @@ -279,6 +280,8 @@ async def test1( ), ] + assert coin_spend_with_conditions.conditions == expected + coin_spend_with_conditions = block_spends_with_conditions[2] assert coin_spend_with_conditions.coin_spend.coin == Coin( diff --git a/chia/full_node/full_node_rpc_api.py b/chia/full_node/full_node_rpc_api.py index ebb912430a93..9a086b80dfcc 100644 --- a/chia/full_node/full_node_rpc_api.py +++ b/chia/full_node/full_node_rpc_api.py @@ -489,7 +489,7 @@ async def get_block_spends(self, request: dict[str, Any]) -> EndpointResult: spends = get_spends_for_block(block_generator, full_block.height, self.service.constants) - return {"block_spends": spends[0]} + return spends[0] async def get_block_spends_with_conditions(self, request: dict[str, Any]) -> EndpointResult: if "header_hash" not in request: @@ -506,8 +506,7 @@ async def get_block_spends_with_conditions(self, request: dict[str, Any]) -> End spends_with_conditions = get_spends_for_block_with_conditions( block_generator, full_block.height, self.service.constants ) - - return {"block_spends_with_conditions": [spends_with_conditions]} + return {"block_spends_with_conditions": spends_with_conditions} async def get_block_record_by_height(self, request: dict[str, Any]) -> EndpointResult: if "height" not in request: diff --git a/chia/full_node/full_node_rpc_client.py b/chia/full_node/full_node_rpc_client.py index ee0c87d1402f..51542c4b939e 100644 --- a/chia/full_node/full_node_rpc_client.py +++ b/chia/full_node/full_node_rpc_client.py @@ -2,6 +2,9 @@ from typing import Any, Optional, cast +from chia.types.condition_opcodes import ConditionOpcode +from chia.types.condition_with_args import ConditionWithArgs +from chia.util.byte_types import hexstr_to_bytes from chia_rs import BlockRecord, CoinSpend, EndOfSubSlotBundle, FullBlock, SpendBundle from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint32 @@ -202,11 +205,11 @@ async def get_block_records(self, start: int, end: int) -> list[dict[str, Any]]: async def get_block_spends(self, header_hash: bytes32) -> Optional[list[CoinSpend]]: try: response = await self.fetch("get_block_spends", {"header_hash": header_hash.hex()}) - block_spends = [] + output = [] for block_spend in response["block_spends"]: - block_spends.append(CoinSpend.from_json_dict(block_spend)) - return block_spends - except Exception: + output.append(CoinSpend.from_json_dict(block_spend)) + return output + except Exception as e: return None async def get_block_spends_with_conditions(self, header_hash: bytes32) -> Optional[list[CoinSpendWithConditions]]: @@ -214,10 +217,16 @@ async def get_block_spends_with_conditions(self, header_hash: bytes32) -> Option response = await self.fetch("get_block_spends_with_conditions", {"header_hash": header_hash.hex()}) block_spends: list[CoinSpendWithConditions] = [] for block_spend in response["block_spends_with_conditions"]: - block_spends.append(CoinSpendWithConditions.from_json_dict(block_spend)) + coin_spend = CoinSpend.from_json_dict(block_spend["coin_spend"]) + cond_tuples = block_spend["conditions"] + conditions = [] + for condition in cond_tuples: + cwa = ConditionWithArgs(opcode=ConditionOpcode(bytes([condition[0]])), vars=[hexstr_to_bytes(b) for b in condition[1]]) + conditions.append(cwa) + block_spends.append(CoinSpendWithConditions(coin_spend=coin_spend, conditions=conditions)) return block_spends - except Exception: + except Exception as e: return None async def push_tx(self, spend_bundle: SpendBundle) -> dict[str, Any]: diff --git a/chia/full_node/mempool_check_conditions.py b/chia/full_node/mempool_check_conditions.py index 92ce86ee3d41..386817c9bb97 100644 --- a/chia/full_node/mempool_check_conditions.py +++ b/chia/full_node/mempool_check_conditions.py @@ -9,6 +9,7 @@ ConsensusConstants, get_flags_for_height_and_constants, get_spends_for_trusted_block, + get_spends_for_trusted_block_with_conditions, ) from chia_rs import get_puzzle_and_solution_for_coin2 as get_puzzle_and_solution_for_coin_rust @@ -55,7 +56,7 @@ def get_spends_for_block( def get_spends_for_block_with_conditions( generator: BlockGenerator, height: int, constants: ConsensusConstants ) -> list[dict[str, typing.Any]]: - spends = get_spends_for_trusted_block( + spends = get_spends_for_trusted_block_with_conditions( constants, generator.program, generator.generator_refs, From 421bcc2016a87390dd0bb5fbb213225269878dea Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 11 Jul 2025 12:36:53 +0100 Subject: [PATCH 04/10] ruff --- chia/full_node/full_node_rpc_client.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/chia/full_node/full_node_rpc_client.py b/chia/full_node/full_node_rpc_client.py index 51542c4b939e..6bb6026e89b4 100644 --- a/chia/full_node/full_node_rpc_client.py +++ b/chia/full_node/full_node_rpc_client.py @@ -2,9 +2,6 @@ from typing import Any, Optional, cast -from chia.types.condition_opcodes import ConditionOpcode -from chia.types.condition_with_args import ConditionWithArgs -from chia.util.byte_types import hexstr_to_bytes from chia_rs import BlockRecord, CoinSpend, EndOfSubSlotBundle, FullBlock, SpendBundle from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint32 @@ -13,7 +10,10 @@ from chia.rpc.rpc_client import RpcClient from chia.types.coin_record import CoinRecord from chia.types.coin_spend import CoinSpendWithConditions +from chia.types.condition_opcodes import ConditionOpcode +from chia.types.condition_with_args import ConditionWithArgs from chia.types.unfinished_header_block import UnfinishedHeaderBlock +from chia.util.byte_types import hexstr_to_bytes def coin_record_dict_backwards_compat(coin_record: dict[str, Any]) -> dict[str, Any]: @@ -209,7 +209,7 @@ async def get_block_spends(self, header_hash: bytes32) -> Optional[list[CoinSpen for block_spend in response["block_spends"]: output.append(CoinSpend.from_json_dict(block_spend)) return output - except Exception as e: + except Exception: return None async def get_block_spends_with_conditions(self, header_hash: bytes32) -> Optional[list[CoinSpendWithConditions]]: @@ -221,12 +221,14 @@ async def get_block_spends_with_conditions(self, header_hash: bytes32) -> Option cond_tuples = block_spend["conditions"] conditions = [] for condition in cond_tuples: - cwa = ConditionWithArgs(opcode=ConditionOpcode(bytes([condition[0]])), vars=[hexstr_to_bytes(b) for b in condition[1]]) + cwa = ConditionWithArgs( + opcode=ConditionOpcode(bytes([condition[0]])), vars=[hexstr_to_bytes(b) for b in condition[1]] + ) conditions.append(cwa) block_spends.append(CoinSpendWithConditions(coin_spend=coin_spend, conditions=conditions)) return block_spends - except Exception as e: + except Exception: return None async def push_tx(self, spend_bundle: SpendBundle) -> dict[str, Any]: From 2305918365719b41738f8a25d4a45cdc34e90241 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 11 Jul 2025 14:02:32 +0100 Subject: [PATCH 05/10] remove SpendInfo and mempool_check_conditions --- .../core/full_node/test_generator_tools.py | 16 +++-- chia/_tests/core/mempool/test_mempool.py | 11 +++- chia/_tests/core/test_cost_calculation.py | 41 +++++++++--- chia/_tests/util/spend_sim.py | 12 +++- chia/full_node/full_node_api.py | 15 +++-- chia/full_node/full_node_rpc_api.py | 38 +++++++---- chia/full_node/mempool_check_conditions.py | 66 ------------------- chia/types/coin_spend.py | 8 --- 8 files changed, 95 insertions(+), 112 deletions(-) delete mode 100644 chia/full_node/mempool_check_conditions.py diff --git a/chia/_tests/core/full_node/test_generator_tools.py b/chia/_tests/core/full_node/test_generator_tools.py index 4cb5e5beee54..4239eddc3407 100644 --- a/chia/_tests/core/full_node/test_generator_tools.py +++ b/chia/_tests/core/full_node/test_generator_tools.py @@ -2,12 +2,16 @@ import pytest from chia_rs import Program as SerializedProgram -from chia_rs import SpendBundleConditions, SpendConditions +from chia_rs import ( + SpendBundleConditions, + SpendConditions, + get_spends_for_trusted_block, + get_spends_for_trusted_block_with_conditions, +) from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint32, uint64 from chia.consensus.generator_tools import tx_removals_and_additions -from chia.full_node.mempool_check_conditions import get_spends_for_block, get_spends_for_block_with_conditions from chia.simulator.block_tools import test_constants from chia.types.blockchain_format.coin import Coin from chia.types.generator_types import BlockGenerator @@ -102,12 +106,16 @@ def test_empty_conditions() -> None: def test_get_spends_for_block(caplog: pytest.LogCaptureFixture) -> None: - conditions = get_spends_for_block(TEST_GENERATOR, 100, test_constants) + conditions = get_spends_for_trusted_block( + test_constants, TEST_GENERATOR.program, TEST_GENERATOR.generator_refs, 100 + ) assert conditions == [] assert "get_spends_for_block() encountered a puzzle we couldn't serialize: " in caplog.text def test_get_spends_for_block_with_conditions(caplog: pytest.LogCaptureFixture) -> None: - conditions = get_spends_for_block_with_conditions(TEST_GENERATOR, 100, test_constants) + conditions = get_spends_for_trusted_block_with_conditions( + test_constants, TEST_GENERATOR.program, TEST_GENERATOR.generator_refs, 100 + ) assert conditions == [] assert "get_spends_for_block_with_conditions() encountered a puzzle we couldn't serialize: " in caplog.text diff --git a/chia/_tests/core/mempool/test_mempool.py b/chia/_tests/core/mempool/test_mempool.py index 2895ee00efd7..72ab9824b37a 100644 --- a/chia/_tests/core/mempool/test_mempool.py +++ b/chia/_tests/core/mempool/test_mempool.py @@ -15,8 +15,10 @@ G2Element, SpendBundle, SpendBundleConditions, + get_flags_for_height_and_constants, run_block_generator2, ) +from chia_rs import get_puzzle_and_solution_for_coin2 as get_puzzle_and_solution_for_coin_rust from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint32, uint64 from clvm_tools import binutils @@ -46,7 +48,6 @@ from chia.full_node.fee_estimation import EmptyMempoolInfo, MempoolInfo from chia.full_node.full_node_api import FullNodeAPI from chia.full_node.mempool import Mempool -from chia.full_node.mempool_check_conditions import get_puzzle_and_solution_for_coin from chia.full_node.mempool_manager import MEMPOOL_MIN_FEE_INCREASE, LineageInfoCache from chia.full_node.pending_tx_cache import ConflictTxCache, PendingTxCache from chia.protocols import full_node_protocol, wallet_protocol @@ -3198,7 +3199,13 @@ def test_get_puzzle_and_solution_for_coin_failure() -> None: with pytest.raises( ValueError, match=f"Failed to get puzzle and solution for coin {TEST_COIN}, error: \\('coin not found', '80'\\)" ): - get_puzzle_and_solution_for_coin(BlockGenerator(SerializedProgram.to(None), []), TEST_COIN, 0, test_constants) + get_puzzle_and_solution_for_coin_rust( + SerializedProgram.to(None), + [], + test_constants.MAX_BLOCK_COST_CLVM, + TEST_COIN, + get_flags_for_height_and_constants(0, test_constants), + ) @pytest.mark.parametrize("old", [True, False]) diff --git a/chia/_tests/core/test_cost_calculation.py b/chia/_tests/core/test_cost_calculation.py index 7011199d434d..eed2740026a1 100644 --- a/chia/_tests/core/test_cost_calculation.py +++ b/chia/_tests/core/test_cost_calculation.py @@ -4,7 +4,8 @@ import pathlib import pytest -from chia_rs import G1Element +from chia_rs import G1Element, get_flags_for_height_and_constants +from chia_rs import get_puzzle_and_solution_for_coin2 as get_puzzle_and_solution_for_coin_rust from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint32, uint64 from clvm_tools import binutils @@ -16,7 +17,6 @@ from chia.consensus.cost_calculator import NPCResult from chia.consensus.default_constants import DEFAULT_CONSTANTS from chia.full_node.bundle_tools import simple_solution_generator -from chia.full_node.mempool_check_conditions import get_puzzle_and_solution_for_coin from chia.simulator.block_tools import BlockTools, test_constants from chia.types.blockchain_format.coin import Coin from chia.types.blockchain_format.program import Program, run_with_cost @@ -89,9 +89,15 @@ async def test_basics(softfork_height: int, bt: BlockTools) -> None: coin_spend = spend_bundle.coin_spends[0] assert npc_result.conds is not None assert coin_spend.coin.name() == npc_result.conds.spends[0].coin_id - spend_info = get_puzzle_and_solution_for_coin(program, coin_spend.coin, softfork_height, bt.constants) - assert spend_info.puzzle == coin_spend.puzzle_reveal - assert spend_info.solution == coin_spend.solution + puzzle, solution = get_puzzle_and_solution_for_coin_rust( + program.program, + program.generator_refs, + bt.constants.MAX_BLOCK_COST_CLVM, + coin_spend.coin, + get_flags_for_height_and_constants(softfork_height, bt.constants), + ) + assert puzzle == coin_spend.puzzle_reveal + assert solution == coin_spend.solution if softfork_height >= bt.constants.HARD_FORK_HEIGHT: clvm_cost = 27360 @@ -170,8 +176,14 @@ async def test_mempool_mode(softfork_height: int, bt: BlockTools) -> None: bytes32.fromhex("14947eb0e69ee8fc8279190fc2d38cb4bbb61ba28f1a270cfd643a0e8d759576"), uint64(300), ) - spend_info = get_puzzle_and_solution_for_coin(generator, coin, softfork_height, bt.constants) - assert spend_info.puzzle == puzzle.to_serialized() + puz, _solution = get_puzzle_and_solution_for_coin_rust( + generator.program, + generator.generator_refs, + bt.constants.MAX_BLOCK_COST_CLVM, + coin, + get_flags_for_height_and_constants(0, bt.constants), + ) + assert puz == puzzle.to_serialized() @pytest.mark.anyio @@ -282,8 +294,11 @@ async def test_standard_tx(benchmark_runner: BenchmarkRunner) -> None: @pytest.mark.anyio async def test_get_puzzle_and_solution_for_coin_performance(benchmark_runner: BenchmarkRunner) -> None: + from chia_puzzles_py.programs import CHIALISP_DESERIALISATION + from chia._tests.core.large_block import LARGE_BLOCK - from chia.full_node.mempool_check_conditions import DESERIALIZE_MOD + + DESERIALIZE_MOD = Program.from_bytes(CHIALISP_DESERIALISATION) assert LARGE_BLOCK.transactions_generator is not None # first, list all spent coins in the block @@ -309,5 +324,11 @@ async def test_get_puzzle_and_solution_for_coin_performance(benchmark_runner: Be with benchmark_runner.assert_runtime(seconds=8.5): for _ in range(3): for c in spent_coins: - spend_info = get_puzzle_and_solution_for_coin(generator, c, 0, test_constants) - assert spend_info.puzzle.get_tree_hash() == c.puzzle_hash + puz, _solution = get_puzzle_and_solution_for_coin_rust( + generator.program, + generator.generator_refs, + test_constants.MAX_BLOCK_COST_CLVM, + c, + get_flags_for_height_and_constants(0, test_constants), + ) + assert puz.get_tree_hash() == c.puzzle_hash diff --git a/chia/_tests/util/spend_sim.py b/chia/_tests/util/spend_sim.py index e3d9a873d79f..d7432647d98c 100644 --- a/chia/_tests/util/spend_sim.py +++ b/chia/_tests/util/spend_sim.py @@ -19,6 +19,7 @@ get_flags_for_height_and_constants, run_block_generator2, ) +from chia_rs import get_puzzle_and_solution_for_coin2 as get_puzzle_and_solution_for_coin_rust from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint32, uint64 from typing_extensions import Self @@ -30,7 +31,6 @@ from chia.full_node.coin_store import CoinStore from chia.full_node.hint_store import HintStore from chia.full_node.mempool import Mempool -from chia.full_node.mempool_check_conditions import get_puzzle_and_solution_for_coin from chia.full_node.mempool_manager import MempoolManager from chia.types.blockchain_format.coin import Coin from chia.types.blockchain_format.program import INFINITE_COST @@ -442,8 +442,14 @@ async def get_puzzle_and_solution(self, coin_id: bytes32, height: uint32) -> Coi generator: BlockGenerator = filtered_generators[0].transactions_generator # type: ignore[assignment] coin_record = await self.service.coin_store.get_coin_record(coin_id) assert coin_record is not None - spend_info = get_puzzle_and_solution_for_coin(generator, coin_record.coin, height, self.service.defaults) - return CoinSpend(coin_record.coin, spend_info.puzzle, spend_info.solution) + puzzle, solution = get_puzzle_and_solution_for_coin_rust( + generator.program, + generator.generator_refs, + self.service.defaults.MAX_BLOCK_COST_CLVM, + coin_record.coin, + get_flags_for_height_and_constants(height, self.service.defaults), + ) + return CoinSpend(coin_record.coin, puzzle, solution) async def get_all_mempool_tx_ids(self) -> list[bytes32]: return self.service.mempool_manager.mempool.all_item_ids() diff --git a/chia/full_node/full_node_api.py b/chia/full_node/full_node_api.py index 9dce12721227..43ec35bf3608 100644 --- a/chia/full_node/full_node_api.py +++ b/chia/full_node/full_node_api.py @@ -30,6 +30,7 @@ additions_and_removals, get_flags_for_height_and_constants, ) +from chia_rs import get_puzzle_and_solution_for_coin2 as get_puzzle_and_solution_for_coin_rust from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint8, uint32, uint64, uint128 from chiabip158 import PyBIP158 @@ -43,7 +44,6 @@ from chia.full_node.coin_store import CoinStore from chia.full_node.fee_estimator_interface import FeeEstimatorInterface from chia.full_node.full_block_utils import get_height_and_tx_status_from_block, header_block_from_block -from chia.full_node.mempool_check_conditions import get_puzzle_and_solution_for_coin from chia.full_node.tx_processing_queue import TransactionQueueEntry, TransactionQueueFull from chia.protocols import farmer_protocol, full_node_protocol, introducer_protocol, timelord_protocol, wallet_protocol from chia.protocols.fee_estimate import FeeEstimate, FeeEstimateGroup, fee_rate_v2_to_v1 @@ -1427,17 +1427,18 @@ async def request_puzzle_solution(self, request: wallet_protocol.RequestPuzzleSo ) assert block_generator is not None try: - spend_info = await asyncio.get_running_loop().run_in_executor( + puzzle, solution = await asyncio.get_running_loop().run_in_executor( self.executor, - get_puzzle_and_solution_for_coin, - block_generator, + get_puzzle_and_solution_for_coin_rust, + block_generator.program, + block_generator.generator_refs, + self.full_node.constants.MAX_BLOCK_COST_CLVM, coin_record.coin, - height, - self.full_node.constants, + get_flags_for_height_and_constants(height, self.full_node.constants), ) except ValueError: return reject_msg - wrapper = PuzzleSolutionResponse(coin_name, height, spend_info.puzzle, spend_info.solution) + wrapper = PuzzleSolutionResponse(coin_name, height, puzzle, solution) response = wallet_protocol.RespondPuzzleSolution(wrapper) response_msg = make_msg(ProtocolMessageTypes.respond_puzzle_solution, response) return response_msg diff --git a/chia/full_node/full_node_rpc_api.py b/chia/full_node/full_node_rpc_api.py index 9a086b80dfcc..31c1c268cbec 100644 --- a/chia/full_node/full_node_rpc_api.py +++ b/chia/full_node/full_node_rpc_api.py @@ -13,8 +13,12 @@ PlotSize, SpendBundle, SpendBundleConditions, + get_flags_for_height_and_constants, + get_spends_for_trusted_block, + get_spends_for_trusted_block_with_conditions, run_block_generator2, ) +from chia_rs import get_puzzle_and_solution_for_coin2 as get_puzzle_and_solution_for_coin_rust from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint32, uint64, uint128 @@ -23,11 +27,6 @@ from chia.consensus.pos_quality import UI_ACTUAL_SPACE_CONSTANT_FACTOR from chia.full_node.fee_estimator_interface import FeeEstimatorInterface from chia.full_node.full_node import FullNode -from chia.full_node.mempool_check_conditions import ( - get_puzzle_and_solution_for_coin, - get_spends_for_block, - get_spends_for_block_with_conditions, -) from chia.protocols.outbound_message import NodeType from chia.rpc.rpc_server import Endpoint, EndpointResult from chia.types.blockchain_format.proof_of_space import calculate_prefix_bits @@ -487,7 +486,12 @@ async def get_block_spends(self, request: dict[str, Any]) -> EndpointResult: if block_generator is None: # if block is not a transaction block. return {"block_spends": spends} - spends = get_spends_for_block(block_generator, full_block.height, self.service.constants) + spends = get_spends_for_trusted_block( + self.service.constants, + block_generator.program, + block_generator.generator_refs, + get_flags_for_height_and_constants(full_block.height, self.service.constants), + ) return spends[0] @@ -503,8 +507,11 @@ async def get_block_spends_with_conditions(self, request: dict[str, Any]) -> End if block_generator is None: # if block is not a transaction block. return {"block_spends_with_conditions": []} - spends_with_conditions = get_spends_for_block_with_conditions( - block_generator, full_block.height, self.service.constants + spends_with_conditions = get_spends_for_trusted_block_with_conditions( + self.service.constants, + block_generator.program, + block_generator.generator_refs, + get_flags_for_height_and_constants(full_block.height, self.service.constants), ) return {"block_spends_with_conditions": spends_with_conditions} @@ -775,10 +782,17 @@ async def get_puzzle_and_solution(self, request: dict[str, Any]) -> EndpointResu ) assert block_generator is not None - spend_info = get_puzzle_and_solution_for_coin( - block_generator, coin_record.coin, block.height, self.service.constants - ) - return {"coin_solution": CoinSpend(coin_record.coin, spend_info.puzzle, spend_info.solution)} + try: + puzzle, solution = get_puzzle_and_solution_for_coin_rust( + block_generator.program, + block_generator.generator_refs, + self.service.constants.MAX_BLOCK_COST_CLVM, + coin_record.coin, + get_flags_for_height_and_constants(block.height, self.service.constants), + ) + return {"coin_solution": CoinSpend(coin_record.coin, puzzle, solution)} + except Exception as e: + raise ValueError(f"Failed to get puzzle and solution for coin {coin_record.coin}, error: {e}") from e async def get_additions_and_removals(self, request: dict[str, Any]) -> EndpointResult: if "header_hash" not in request: diff --git a/chia/full_node/mempool_check_conditions.py b/chia/full_node/mempool_check_conditions.py deleted file mode 100644 index 386817c9bb97..000000000000 --- a/chia/full_node/mempool_check_conditions.py +++ /dev/null @@ -1,66 +0,0 @@ -from __future__ import annotations - -import logging -import typing - -from chia_puzzles_py.programs import CHIALISP_DESERIALISATION -from chia_rs import ( - CoinSpend, - ConsensusConstants, - get_flags_for_height_and_constants, - get_spends_for_trusted_block, - get_spends_for_trusted_block_with_conditions, -) -from chia_rs import get_puzzle_and_solution_for_coin2 as get_puzzle_and_solution_for_coin_rust - -from chia.types.blockchain_format.coin import Coin -from chia.types.blockchain_format.program import Program -from chia.types.coin_spend import SpendInfo -from chia.types.generator_types import BlockGenerator - -DESERIALIZE_MOD = Program.from_bytes(CHIALISP_DESERIALISATION) - - -log = logging.getLogger(__name__) - - -def get_puzzle_and_solution_for_coin( - generator: BlockGenerator, coin: Coin, height: int, constants: ConsensusConstants -) -> SpendInfo: - try: - puzzle, solution = get_puzzle_and_solution_for_coin_rust( - generator.program, - generator.generator_refs, - constants.MAX_BLOCK_COST_CLVM, - coin, - get_flags_for_height_and_constants(height, constants), - ) - return SpendInfo(puzzle, solution) - except Exception as e: - raise ValueError(f"Failed to get puzzle and solution for coin {coin}, error: {e}") from e - - -def get_spends_for_block( - generator: BlockGenerator, height: int, constants: ConsensusConstants -) -> list[dict[str, list[CoinSpend]]]: - spends = get_spends_for_trusted_block( - constants, - generator.program, - generator.generator_refs, - get_flags_for_height_and_constants(height, constants), - ) - - return spends - - -def get_spends_for_block_with_conditions( - generator: BlockGenerator, height: int, constants: ConsensusConstants -) -> list[dict[str, typing.Any]]: - spends = get_spends_for_trusted_block_with_conditions( - constants, - generator.program, - generator.generator_refs, - get_flags_for_height_and_constants(height, constants), - ) - - return spends diff --git a/chia/types/coin_spend.py b/chia/types/coin_spend.py index ed403577a169..235228285ec9 100644 --- a/chia/types/coin_spend.py +++ b/chia/types/coin_spend.py @@ -10,7 +10,6 @@ from chia.types.blockchain_format.serialized_program import SerializedProgram from chia.types.condition_opcodes import ConditionOpcode from chia.types.condition_with_args import ConditionWithArgs -from chia.util.streamable import Streamable, streamable def make_spend( @@ -36,13 +35,6 @@ def make_spend( return CoinSpend(coin, pr, sol) -@streamable -@dataclass(frozen=True) -class SpendInfo(Streamable): - puzzle: SerializedProgram - solution: SerializedProgram - - @dataclass(frozen=True) class CoinSpendWithConditions: coin_spend: CoinSpend From 6916e6a7700bca6f2e38c40c150fb2ab82ffe5f9 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 11 Jul 2025 14:07:38 +0100 Subject: [PATCH 06/10] no need to call it _rust now --- chia/_tests/core/mempool/test_mempool.py | 4 ++-- chia/_tests/core/test_cost_calculation.py | 8 ++++---- chia/_tests/util/spend_sim.py | 4 ++-- chia/full_node/full_node_api.py | 4 ++-- chia/full_node/full_node_rpc_api.py | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/chia/_tests/core/mempool/test_mempool.py b/chia/_tests/core/mempool/test_mempool.py index 72ab9824b37a..6cfc974ae89e 100644 --- a/chia/_tests/core/mempool/test_mempool.py +++ b/chia/_tests/core/mempool/test_mempool.py @@ -18,7 +18,7 @@ get_flags_for_height_and_constants, run_block_generator2, ) -from chia_rs import get_puzzle_and_solution_for_coin2 as get_puzzle_and_solution_for_coin_rust +from chia_rs import get_puzzle_and_solution_for_coin2 as get_puzzle_and_solution_for_coin from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint32, uint64 from clvm_tools import binutils @@ -3199,7 +3199,7 @@ def test_get_puzzle_and_solution_for_coin_failure() -> None: with pytest.raises( ValueError, match=f"Failed to get puzzle and solution for coin {TEST_COIN}, error: \\('coin not found', '80'\\)" ): - get_puzzle_and_solution_for_coin_rust( + get_puzzle_and_solution_for_coin( SerializedProgram.to(None), [], test_constants.MAX_BLOCK_COST_CLVM, diff --git a/chia/_tests/core/test_cost_calculation.py b/chia/_tests/core/test_cost_calculation.py index eed2740026a1..70956112c1e0 100644 --- a/chia/_tests/core/test_cost_calculation.py +++ b/chia/_tests/core/test_cost_calculation.py @@ -5,7 +5,7 @@ import pytest from chia_rs import G1Element, get_flags_for_height_and_constants -from chia_rs import get_puzzle_and_solution_for_coin2 as get_puzzle_and_solution_for_coin_rust +from chia_rs import get_puzzle_and_solution_for_coin2 as get_puzzle_and_solution_for_coin from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint32, uint64 from clvm_tools import binutils @@ -89,7 +89,7 @@ async def test_basics(softfork_height: int, bt: BlockTools) -> None: coin_spend = spend_bundle.coin_spends[0] assert npc_result.conds is not None assert coin_spend.coin.name() == npc_result.conds.spends[0].coin_id - puzzle, solution = get_puzzle_and_solution_for_coin_rust( + puzzle, solution = get_puzzle_and_solution_for_coin( program.program, program.generator_refs, bt.constants.MAX_BLOCK_COST_CLVM, @@ -176,7 +176,7 @@ async def test_mempool_mode(softfork_height: int, bt: BlockTools) -> None: bytes32.fromhex("14947eb0e69ee8fc8279190fc2d38cb4bbb61ba28f1a270cfd643a0e8d759576"), uint64(300), ) - puz, _solution = get_puzzle_and_solution_for_coin_rust( + puz, _solution = get_puzzle_and_solution_for_coin( generator.program, generator.generator_refs, bt.constants.MAX_BLOCK_COST_CLVM, @@ -324,7 +324,7 @@ async def test_get_puzzle_and_solution_for_coin_performance(benchmark_runner: Be with benchmark_runner.assert_runtime(seconds=8.5): for _ in range(3): for c in spent_coins: - puz, _solution = get_puzzle_and_solution_for_coin_rust( + puz, _solution = get_puzzle_and_solution_for_coin( generator.program, generator.generator_refs, test_constants.MAX_BLOCK_COST_CLVM, diff --git a/chia/_tests/util/spend_sim.py b/chia/_tests/util/spend_sim.py index d7432647d98c..f746d4eedd42 100644 --- a/chia/_tests/util/spend_sim.py +++ b/chia/_tests/util/spend_sim.py @@ -19,7 +19,7 @@ get_flags_for_height_and_constants, run_block_generator2, ) -from chia_rs import get_puzzle_and_solution_for_coin2 as get_puzzle_and_solution_for_coin_rust +from chia_rs import get_puzzle_and_solution_for_coin2 as get_puzzle_and_solution_for_coin from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint32, uint64 from typing_extensions import Self @@ -442,7 +442,7 @@ async def get_puzzle_and_solution(self, coin_id: bytes32, height: uint32) -> Coi generator: BlockGenerator = filtered_generators[0].transactions_generator # type: ignore[assignment] coin_record = await self.service.coin_store.get_coin_record(coin_id) assert coin_record is not None - puzzle, solution = get_puzzle_and_solution_for_coin_rust( + puzzle, solution = get_puzzle_and_solution_for_coin( generator.program, generator.generator_refs, self.service.defaults.MAX_BLOCK_COST_CLVM, diff --git a/chia/full_node/full_node_api.py b/chia/full_node/full_node_api.py index 43ec35bf3608..aa892e9cb910 100644 --- a/chia/full_node/full_node_api.py +++ b/chia/full_node/full_node_api.py @@ -30,7 +30,7 @@ additions_and_removals, get_flags_for_height_and_constants, ) -from chia_rs import get_puzzle_and_solution_for_coin2 as get_puzzle_and_solution_for_coin_rust +from chia_rs import get_puzzle_and_solution_for_coin2 as get_puzzle_and_solution_for_coin from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint8, uint32, uint64, uint128 from chiabip158 import PyBIP158 @@ -1429,7 +1429,7 @@ async def request_puzzle_solution(self, request: wallet_protocol.RequestPuzzleSo try: puzzle, solution = await asyncio.get_running_loop().run_in_executor( self.executor, - get_puzzle_and_solution_for_coin_rust, + get_puzzle_and_solution_for_coin, block_generator.program, block_generator.generator_refs, self.full_node.constants.MAX_BLOCK_COST_CLVM, diff --git a/chia/full_node/full_node_rpc_api.py b/chia/full_node/full_node_rpc_api.py index 31c1c268cbec..ad957cd1c647 100644 --- a/chia/full_node/full_node_rpc_api.py +++ b/chia/full_node/full_node_rpc_api.py @@ -18,7 +18,7 @@ get_spends_for_trusted_block_with_conditions, run_block_generator2, ) -from chia_rs import get_puzzle_and_solution_for_coin2 as get_puzzle_and_solution_for_coin_rust +from chia_rs import get_puzzle_and_solution_for_coin2 as get_puzzle_and_solution_for_coin from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint32, uint64, uint128 @@ -783,7 +783,7 @@ async def get_puzzle_and_solution(self, request: dict[str, Any]) -> EndpointResu assert block_generator is not None try: - puzzle, solution = get_puzzle_and_solution_for_coin_rust( + puzzle, solution = get_puzzle_and_solution_for_coin( block_generator.program, block_generator.generator_refs, self.service.constants.MAX_BLOCK_COST_CLVM, From 39237037398c6e2b55d948ab6a5f13ffd5ccc492 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 11 Jul 2025 15:11:02 +0100 Subject: [PATCH 07/10] fix mempool test --- chia/_tests/core/mempool/test_mempool.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/chia/_tests/core/mempool/test_mempool.py b/chia/_tests/core/mempool/test_mempool.py index 6cfc974ae89e..7760a26244f1 100644 --- a/chia/_tests/core/mempool/test_mempool.py +++ b/chia/_tests/core/mempool/test_mempool.py @@ -3199,13 +3199,16 @@ def test_get_puzzle_and_solution_for_coin_failure() -> None: with pytest.raises( ValueError, match=f"Failed to get puzzle and solution for coin {TEST_COIN}, error: \\('coin not found', '80'\\)" ): - get_puzzle_and_solution_for_coin( - SerializedProgram.to(None), - [], - test_constants.MAX_BLOCK_COST_CLVM, - TEST_COIN, - get_flags_for_height_and_constants(0, test_constants), - ) + try: + test = get_puzzle_and_solution_for_coin( + SerializedProgram.to(None), + [], + test_constants.MAX_BLOCK_COST_CLVM, + TEST_COIN, + get_flags_for_height_and_constants(0, test_constants), + ) + except Exception as e: + raise ValueError(f"Failed to get puzzle and solution for coin {TEST_COIN}, error: {e}") from e @pytest.mark.parametrize("old", [True, False]) From 6e988b72011928f6ee12ddebce5328d9769f37d1 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 11 Jul 2025 15:25:12 +0100 Subject: [PATCH 08/10] fix precommit errors --- chia/_tests/core/mempool/test_mempool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chia/_tests/core/mempool/test_mempool.py b/chia/_tests/core/mempool/test_mempool.py index 7760a26244f1..c770a8140d51 100644 --- a/chia/_tests/core/mempool/test_mempool.py +++ b/chia/_tests/core/mempool/test_mempool.py @@ -3200,7 +3200,7 @@ def test_get_puzzle_and_solution_for_coin_failure() -> None: ValueError, match=f"Failed to get puzzle and solution for coin {TEST_COIN}, error: \\('coin not found', '80'\\)" ): try: - test = get_puzzle_and_solution_for_coin( + get_puzzle_and_solution_for_coin( SerializedProgram.to(None), [], test_constants.MAX_BLOCK_COST_CLVM, From fcf1960ddabeb84a82c55c3cdee5a6dab9c5e49a Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 11 Jul 2025 16:21:34 +0100 Subject: [PATCH 09/10] fix test generator tools --- chia/_tests/core/full_node/test_generator_tools.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/chia/_tests/core/full_node/test_generator_tools.py b/chia/_tests/core/full_node/test_generator_tools.py index 4239eddc3407..00ed05d9cfcd 100644 --- a/chia/_tests/core/full_node/test_generator_tools.py +++ b/chia/_tests/core/full_node/test_generator_tools.py @@ -109,8 +109,7 @@ def test_get_spends_for_block(caplog: pytest.LogCaptureFixture) -> None: conditions = get_spends_for_trusted_block( test_constants, TEST_GENERATOR.program, TEST_GENERATOR.generator_refs, 100 ) - assert conditions == [] - assert "get_spends_for_block() encountered a puzzle we couldn't serialize: " in caplog.text + assert conditions[0]["block_spends"] == [] def test_get_spends_for_block_with_conditions(caplog: pytest.LogCaptureFixture) -> None: @@ -118,4 +117,3 @@ def test_get_spends_for_block_with_conditions(caplog: pytest.LogCaptureFixture) test_constants, TEST_GENERATOR.program, TEST_GENERATOR.generator_refs, 100 ) assert conditions == [] - assert "get_spends_for_block_with_conditions() encountered a puzzle we couldn't serialize: " in caplog.text From 7f31b90c6ce4ec33a5caac45468ff1cf243dc0d1 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Mon, 14 Jul 2025 16:28:51 +0100 Subject: [PATCH 10/10] add comment about impending chia_rs fix --- chia/full_node/full_node_rpc_api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chia/full_node/full_node_rpc_api.py b/chia/full_node/full_node_rpc_api.py index ad957cd1c647..c6bf67f94c02 100644 --- a/chia/full_node/full_node_rpc_api.py +++ b/chia/full_node/full_node_rpc_api.py @@ -493,6 +493,8 @@ async def get_block_spends(self, request: dict[str, Any]) -> EndpointResult: get_flags_for_height_and_constants(full_block.height, self.service.constants), ) + # chia_rs returning a list is a mistake that will be fixed in the next release + # it ought to be returning a dict of {"block_spends": [spends]} return spends[0] async def get_block_spends_with_conditions(self, request: dict[str, Any]) -> EndpointResult: