diff --git a/chia/_tests/core/full_node/test_generator_tools.py b/chia/_tests/core/full_node/test_generator_tools.py index 4cb5e5beee54..00ed05d9cfcd 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,14 @@ 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) - assert conditions == [] - assert "get_spends_for_block() encountered a puzzle we couldn't serialize: " in caplog.text + conditions = get_spends_for_trusted_block( + test_constants, TEST_GENERATOR.program, TEST_GENERATOR.generator_refs, 100 + ) + assert conditions[0]["block_spends"] == [] 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..c770a8140d51 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 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,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(BlockGenerator(SerializedProgram.to(None), []), TEST_COIN, 0, test_constants) + try: + 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]) diff --git a/chia/_tests/core/test_cost_calculation.py b/chia/_tests/core/test_cost_calculation.py index 7011199d434d..70956112c1e0 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 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( + 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( + 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( + 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/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/_tests/util/spend_sim.py b/chia/_tests/util/spend_sim.py index e3d9a873d79f..f746d4eedd42 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 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( + 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..aa892e9cb910 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 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, + 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 edc9426ae8aa..c6bf67f94c02 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 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 @@ -482,14 +481,21 @@ 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) + 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 {"block_spends": spends} + # 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: if "header_hash" not in request: @@ -503,22 +509,13 @@ 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": [ - { - "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: @@ -787,10 +784,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( + 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/full_node_rpc_client.py b/chia/full_node/full_node_rpc_client.py index ee0c87d1402f..6bb6026e89b4 100644 --- a/chia/full_node/full_node_rpc_client.py +++ b/chia/full_node/full_node_rpc_client.py @@ -10,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]: @@ -202,10 +205,10 @@ 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 + output.append(CoinSpend.from_json_dict(block_spend)) + return output except Exception: return None @@ -214,7 +217,15 @@ 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: diff --git a/chia/full_node/mempool_check_conditions.py b/chia/full_node/mempool_check_conditions.py deleted file mode 100644 index e0b546026767..000000000000 --- a/chia/full_node/mempool_check_conditions.py +++ /dev/null @@ -1,102 +0,0 @@ -from __future__ import annotations - -import logging - -from chia_puzzles_py.programs import CHIALISP_DESERIALISATION -from chia_rs import ( - CoinSpend, - ConsensusConstants, - get_flags_for_height_and_constants, - run_chia_program, -) -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.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[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( - bytes(generator.program), - bytes(args), - constants.MAX_BLOCK_COST_CLVM, - 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( - bytes(generator.program), - bytes(args), - constants.MAX_BLOCK_COST_CLVM, - flags, - ) - - 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 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