diff --git a/chia/consensus/block_body_validation.py b/chia/consensus/block_body_validation.py index 8e8685275eba..c147089ff717 100644 --- a/chia/consensus/block_body_validation.py +++ b/chia/consensus/block_body_validation.py @@ -325,6 +325,7 @@ async def validate_block_body( min(constants.MAX_BLOCK_COST_CLVM, curr.transactions_info.cost), cost_per_byte=constants.COST_PER_BYTE, safe_mode=False, + rust_checker=curr.height > constants.RUST_CONDITION_CHECKER, ) removals_in_curr, additions_in_curr = tx_removals_and_additions(curr_npc_result.npc_list) else: diff --git a/chia/consensus/block_creation.py b/chia/consensus/block_creation.py index 215ecd6dc748..001fb97a36e1 100644 --- a/chia/consensus/block_creation.py +++ b/chia/consensus/block_creation.py @@ -127,7 +127,11 @@ def create_foliage( if block_generator is not None: generator_block_heights_list = block_generator.block_height_list() result: NPCResult = get_name_puzzle_conditions( - block_generator, constants.MAX_BLOCK_COST_CLVM, cost_per_byte=constants.COST_PER_BYTE, safe_mode=True + block_generator, + constants.MAX_BLOCK_COST_CLVM, + cost_per_byte=constants.COST_PER_BYTE, + safe_mode=True, + rust_checker=height > constants.RUST_CONDITION_CHECKER, ) cost = calculate_cost_of_program(block_generator.program, result, constants.COST_PER_BYTE) diff --git a/chia/consensus/blockchain.py b/chia/consensus/blockchain.py index 38ddd4627dec..d868502b1c77 100644 --- a/chia/consensus/blockchain.py +++ b/chia/consensus/blockchain.py @@ -208,6 +208,7 @@ class Blockchain(BlockchainInterface): min(self.constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), cost_per_byte=self.constants.COST_PER_BYTE, safe_mode=False, + rust_checker=block.height > self.constants.RUST_CONDITION_CHECKER, ) removals, tx_additions = tx_removals_and_additions(npc_result.npc_list) else: @@ -383,6 +384,7 @@ class Blockchain(BlockchainInterface): self.constants.MAX_BLOCK_COST_CLVM, cost_per_byte=self.constants.COST_PER_BYTE, safe_mode=False, + rust_checker=block.height > self.constants.RUST_CONDITION_CHECKER, ) tx_removals, tx_additions = tx_removals_and_additions(npc_result.npc_list) return tx_removals, tx_additions @@ -535,6 +537,7 @@ class Blockchain(BlockchainInterface): min(self.constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), cost_per_byte=self.constants.COST_PER_BYTE, safe_mode=False, + rust_checker=uint32(prev_height + 1) > self.constants.RUST_CONDITION_CHECKER, ) error_code, cost_result = await validate_block_body( self.constants, diff --git a/chia/consensus/constants.py b/chia/consensus/constants.py index 7b5d6f50c824..f4a74f011634 100644 --- a/chia/consensus/constants.py +++ b/chia/consensus/constants.py @@ -51,6 +51,7 @@ class ConsensusConstants: WEIGHT_PROOF_THRESHOLD: uint8 WEIGHT_PROOF_RECENT_BLOCKS: uint32 MAX_BLOCK_COUNT_PER_REQUESTS: uint32 + RUST_CONDITION_CHECKER: uint64 BLOCKS_CACHE_SIZE: uint32 NETWORK_TYPE: int MAX_GENERATOR_SIZE: uint32 diff --git a/chia/consensus/default_constants.py b/chia/consensus/default_constants.py index a61529055851..fbee94b1cc43 100644 --- a/chia/consensus/default_constants.py +++ b/chia/consensus/default_constants.py @@ -50,6 +50,7 @@ testnet_kwargs = { "BLOCKS_CACHE_SIZE": 4608 + (128 * 4), "WEIGHT_PROOF_RECENT_BLOCKS": 1000, "MAX_BLOCK_COUNT_PER_REQUESTS": 32, # Allow up to 32 blocks per request + "RUST_CONDITION_CHECKER": 730000 + 138000, "NETWORK_TYPE": 0, "MAX_GENERATOR_SIZE": 1000000, "MAX_GENERATOR_REF_LIST_SIZE": 512, # Number of references allowed in the block generator ref list diff --git a/chia/consensus/multiprocess_validation.py b/chia/consensus/multiprocess_validation.py index 78d138d1861e..b7686603bb1c 100644 --- a/chia/consensus/multiprocess_validation.py +++ b/chia/consensus/multiprocess_validation.py @@ -82,6 +82,7 @@ def batch_pre_validate_blocks( min(constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), cost_per_byte=constants.COST_PER_BYTE, safe_mode=True, + rust_checker=block.height > constants.RUST_CONDITION_CHECKER, ) removals, tx_additions = tx_removals_and_additions(npc_result.npc_list) diff --git a/chia/full_node/mempool_check_conditions.py b/chia/full_node/mempool_check_conditions.py index 7ff7b0d007a9..bf857de480b1 100644 --- a/chia/full_node/mempool_check_conditions.py +++ b/chia/full_node/mempool_check_conditions.py @@ -2,6 +2,7 @@ import logging import time from typing import Tuple, Dict, List, Optional, Set from clvm import SExp +from clvm_rs import STRICT_MODE from chia.consensus.cost_calculator import NPCResult from chia.consensus.condition_costs import ConditionCost @@ -298,7 +299,7 @@ def parse_condition(cond: SExp, safe_mode: bool) -> Tuple[int, Optional[Conditio return cost, cvl -def get_name_puzzle_conditions( +def get_name_puzzle_conditions_python( generator: BlockGenerator, max_cost: int, *, cost_per_byte: int, safe_mode: bool ) -> NPCResult: """ @@ -312,54 +313,102 @@ def get_name_puzzle_conditions( are considered failures. This is the mode when accepting transactions into the mempool. """ - try: - block_program, block_program_args = setup_generator_args(generator) - max_cost -= len(bytes(generator.program)) * cost_per_byte - if max_cost < 0: - return NPCResult(uint16(Err.INVALID_BLOCK_COST.value), [], uint64(0)) - if safe_mode: - clvm_cost, result = GENERATOR_MOD.run_safe_with_cost(max_cost, block_program, block_program_args) - else: - clvm_cost, result = GENERATOR_MOD.run_with_cost(max_cost, block_program, block_program_args) + block_program, block_program_args = setup_generator_args(generator) + max_cost -= len(bytes(generator.program)) * cost_per_byte + if max_cost < 0: + return NPCResult(uint16(Err.INVALID_BLOCK_COST.value), [], uint64(0)) + if safe_mode: + clvm_cost, result = GENERATOR_MOD.run_safe_with_cost(max_cost, block_program, block_program_args) + else: + clvm_cost, result = GENERATOR_MOD.run_with_cost(max_cost, block_program, block_program_args) - max_cost -= clvm_cost - if max_cost < 0: - return NPCResult(uint16(Err.INVALID_BLOCK_COST.value), [], uint64(0)) - npc_list: List[NPC] = [] + max_cost -= clvm_cost + if max_cost < 0: + return NPCResult(uint16(Err.INVALID_BLOCK_COST.value), [], uint64(0)) + npc_list: List[NPC] = [] - for res in result.first().as_iter(): - conditions_list: List[ConditionWithArgs] = [] + for res in result.first().as_iter(): + conditions_list: List[ConditionWithArgs] = [] - if len(res.first().atom) != 32: - raise ValidationError(Err.INVALID_CONDITION) - spent_coin_parent_id: bytes32 = res.first().as_atom() - res = res.rest() - if len(res.first().atom) != 32: - raise ValidationError(Err.INVALID_CONDITION) - spent_coin_puzzle_hash: bytes32 = res.first().as_atom() - res = res.rest() - spent_coin_amount: uint64 = uint64(sanitize_int(res.first(), safe_mode)) - res = res.rest() - spent_coin: Coin = Coin(spent_coin_parent_id, spent_coin_puzzle_hash, spent_coin_amount) + if len(res.first().atom) != 32: + raise ValidationError(Err.INVALID_CONDITION) + spent_coin_parent_id: bytes32 = res.first().as_atom() + res = res.rest() + if len(res.first().atom) != 32: + raise ValidationError(Err.INVALID_CONDITION) + spent_coin_puzzle_hash: bytes32 = res.first().as_atom() + res = res.rest() + spent_coin_amount: uint64 = uint64(sanitize_int(res.first(), safe_mode)) + res = res.rest() + spent_coin: Coin = Coin(spent_coin_parent_id, spent_coin_puzzle_hash, spent_coin_amount) - for cond in res.first().as_iter(): - cost, cvl = parse_condition(cond, safe_mode) - max_cost -= cost - if max_cost < 0: - return NPCResult(uint16(Err.INVALID_BLOCK_COST.value), [], uint64(0)) - if cvl is not None: - conditions_list.append(cvl) + for cond in res.first().as_iter(): + cost, cvl = parse_condition(cond, safe_mode) + max_cost -= cost + if max_cost < 0: + return NPCResult(uint16(Err.INVALID_BLOCK_COST.value), [], uint64(0)) + if cvl is not None: + conditions_list.append(cvl) - conditions_dict = conditions_by_opcode(conditions_list) - if conditions_dict is None: - conditions_dict = {} - npc_list.append( - NPC(spent_coin.name(), spent_coin.puzzle_hash, [(a, b) for a, b in conditions_dict.items()]) - ) + conditions_dict = conditions_by_opcode(conditions_list) + if conditions_dict is None: + conditions_dict = {} + npc_list.append(NPC(spent_coin.name(), spent_coin.puzzle_hash, [(a, b) for a, b in conditions_dict.items()])) + return NPCResult(None, npc_list, uint64(clvm_cost)) + + +def get_name_puzzle_conditions_rust( + generator: BlockGenerator, max_cost: int, *, cost_per_byte: int, safe_mode: bool +) -> NPCResult: + block_program, block_program_args = setup_generator_args(generator) + max_cost -= len(bytes(generator.program)) * cost_per_byte + if max_cost < 0: + return NPCResult(uint16(Err.INVALID_BLOCK_COST.value), [], uint64(0)) + + flags = STRICT_MODE if safe_mode else 0 + err, result, clvm_cost = GENERATOR_MOD.run_as_generator(max_cost, flags, block_program, block_program_args) + if err is not None: + return NPCResult(uint16(err), [], uint64(0)) + else: + npc_list = [] + for r in result: + conditions = [] + for c in r.conditions: + cwa = [] + for cond_list in c[1]: + cwa.append(ConditionWithArgs(ConditionOpcode(bytes([cond_list.opcode])), cond_list.vars)) + conditions.append((ConditionOpcode(bytes([c[0]])), cwa)) + npc_list.append(NPC(r.coin_name, r.puzzle_hash, conditions)) return NPCResult(None, npc_list, uint64(clvm_cost)) + + +def get_name_puzzle_conditions( + generator: BlockGenerator, max_cost: int, *, cost_per_byte: int, safe_mode: bool, rust_checker: bool +) -> NPCResult: + """ + This executes the generator program and returns the coins and their + conditions. If the cost of the program (size, CLVM execution and conditions) + exceed max_cost, the function fails. In order to accurately take the size + of the program into account when calculating cost, cost_per_byte must be + specified. + safe_mode determines whether the clvm program and conditions are executed in + strict mode or not. When in safe/strict mode, unknow operations or conditions + are considered failures. This is the mode when accepting transactions into + the mempool. + """ + try: + if rust_checker: + return get_name_puzzle_conditions_rust( + generator, max_cost, cost_per_byte=cost_per_byte, safe_mode=safe_mode + ) + else: + return get_name_puzzle_conditions_python( + generator, max_cost, cost_per_byte=cost_per_byte, safe_mode=safe_mode + ) except ValidationError as e: return NPCResult(uint16(e.code.value), [], uint64(0)) - except Exception: + except Exception as e: + log.debug(f"get_name_puzzle_condition failed: {e}") return NPCResult(uint16(Err.GENERATOR_RUNTIME_ERROR.value), [], uint64(0)) diff --git a/chia/full_node/mempool_manager.py b/chia/full_node/mempool_manager.py index 989906c4fbb2..cfe2bddea660 100644 --- a/chia/full_node/mempool_manager.py +++ b/chia/full_node/mempool_manager.py @@ -42,7 +42,9 @@ log = logging.getLogger(__name__) def get_npc_multiprocess(spend_bundle_bytes: bytes, max_cost: int, cost_per_byte: int) -> bytes: program = simple_solution_generator(SpendBundle.from_bytes(spend_bundle_bytes)) # npc contains names of the coins removed, puzzle_hashes and their spend conditions - return bytes(get_name_puzzle_conditions(program, max_cost, cost_per_byte=cost_per_byte, safe_mode=True)) + return bytes( + get_name_puzzle_conditions(program, max_cost, cost_per_byte=cost_per_byte, safe_mode=True, rust_checker=True) + ) class MempoolManager: diff --git a/chia/types/blockchain_format/program.py b/chia/types/blockchain_format/program.py index c62187bd2111..41987b2b5646 100644 --- a/chia/types/blockchain_format/program.py +++ b/chia/types/blockchain_format/program.py @@ -1,5 +1,5 @@ import io -from typing import List, Set, Tuple +from typing import List, Set, Tuple, Optional, Any from clvm import KEYWORD_FROM_ATOM, KEYWORD_TO_ATOM, SExp from clvm import run_program as default_run_program @@ -7,7 +7,7 @@ from clvm.casts import int_from_bytes from clvm.EvalError import EvalError from clvm.operators import OP_REWRITE, OPERATOR_LOOKUP from clvm.serialize import sexp_from_stream, sexp_to_stream -from clvm_rs import STRICT_MODE, deserialize_and_run_program2, serialized_length +from clvm_rs import STRICT_MODE, deserialize_and_run_program2, serialized_length, run_generator from clvm_tools.curry import curry, uncurry from chia.types.blockchain_format.sized_bytes import bytes32 @@ -224,6 +224,31 @@ class SerializedProgram: def run_with_cost(self, max_cost: int, *args) -> Tuple[int, Program]: return self._run(max_cost, 0, *args) + def run_as_generator(self, max_cost: int, flags: int, *args) -> Tuple[Optional[int], List[Any], int]: + serialized_args = b"" + if len(args) > 1: + # when we have more than one argument, serialize them into a list + for a in args: + serialized_args += b"\xff" + serialized_args += _serialize(a) + serialized_args += b"\x80" + else: + serialized_args += _serialize(args[0]) + + native_opcode_names_by_opcode = dict( + ("op_%s" % OP_REWRITE.get(k, k), op) for op, k in KEYWORD_FROM_ATOM.items() if k not in "qa." + ) + err, npc_list, cost = run_generator( + self._buf, + serialized_args, + KEYWORD_TO_ATOM["q"][0], + KEYWORD_TO_ATOM["a"][0], + native_opcode_names_by_opcode, + max_cost, + flags, + ) + return None if err == 0 else err, npc_list, cost + def _run(self, max_cost: int, flags, *args) -> Tuple[int, Program]: # when multiple arguments are passed, concatenate them into a serialized # buffer. Some arguments may already be in serialized form (e.g. diff --git a/chia/wallet/cc_wallet/cc_wallet.py b/chia/wallet/cc_wallet/cc_wallet.py index d5c1af2e0437..a3e4729cf1e5 100644 --- a/chia/wallet/cc_wallet/cc_wallet.py +++ b/chia/wallet/cc_wallet/cc_wallet.py @@ -250,6 +250,7 @@ class CCWallet: self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM, cost_per_byte=self.wallet_state_manager.constants.COST_PER_BYTE, safe_mode=True, + rust_checker=True, ) cost_result: uint64 = calculate_cost_of_program( program.program, result, self.wallet_state_manager.constants.COST_PER_BYTE diff --git a/chia/wallet/wallet.py b/chia/wallet/wallet.py index b5e74f52ed88..fcd7617282e9 100644 --- a/chia/wallet/wallet.py +++ b/chia/wallet/wallet.py @@ -82,6 +82,7 @@ class Wallet: self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM, cost_per_byte=self.wallet_state_manager.constants.COST_PER_BYTE, safe_mode=True, + rust_checker=True, ) cost_result: uint64 = calculate_cost_of_program( program.program, result, self.wallet_state_manager.constants.COST_PER_BYTE diff --git a/setup.py b/setup.py index 4f11af01d043..e904583bebde 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ dependencies = [ "chiabip158==1.0", # bip158-style wallet filters "chiapos==1.0.4", # proof of space "clvm==0.9.7", - "clvm_rs==0.1.8", + "clvm_rs==0.1.10", "clvm_tools==0.4.3", "aiohttp==3.7.4", # HTTP server for full node rpc "aiosqlite==0.17.0", # asyncio wrapper for sqlite, to store blocks diff --git a/tests/block_tools.py b/tests/block_tools.py index 8c442cd79995..ee4828f6442e 100644 --- a/tests/block_tools.py +++ b/tests/block_tools.py @@ -91,6 +91,7 @@ from chia.wallet.derive_keys import ( test_constants = DEFAULT_CONSTANTS.replace( **{ + "RUST_CONDITION_CHECKER": 0, "MIN_PLOT_SIZE": 18, "MIN_BLOCKS_PER_CHALLENGE_BLOCK": 12, "DIFFICULTY_STARTING": 2 ** 12, diff --git a/tests/blockchain/test_blockchain.py b/tests/blockchain/test_blockchain.py index 5e2c94c3f825..0e52b6159c35 100644 --- a/tests/blockchain/test_blockchain.py +++ b/tests/blockchain/test_blockchain.py @@ -1821,13 +1821,16 @@ class TestBodyValidation: block: FullBlock = blocks[-1] # Too few + assert block.transactions_info too_few_reward_claims = block.transactions_info.reward_claims_incorporated[:-1] block_2: FullBlock = recursive_replace( block, "transactions_info.reward_claims_incorporated", too_few_reward_claims ) + assert block_2.transactions_info block_2 = recursive_replace( block_2, "foliage_transaction_block.transactions_info_hash", block_2.transactions_info.get_hash() ) + assert block_2.foliage_transaction_block block_2 = recursive_replace( block_2, "foliage.foliage_transaction_block_hash", block_2.foliage_transaction_block.get_hash() ) @@ -1844,9 +1847,11 @@ class TestBodyValidation: Coin(h, h, too_few_reward_claims[0].amount) ] block_2 = recursive_replace(block, "transactions_info.reward_claims_incorporated", too_many_reward_claims) + assert block_2.transactions_info block_2 = recursive_replace( block_2, "foliage_transaction_block.transactions_info_hash", block_2.transactions_info.get_hash() ) + assert block_2.foliage_transaction_block block_2 = recursive_replace( block_2, "foliage.foliage_transaction_block_hash", block_2.foliage_transaction_block.get_hash() ) @@ -1862,9 +1867,11 @@ class TestBodyValidation: block.transactions_info.reward_claims_incorporated[-1] ] block_2 = recursive_replace(block, "transactions_info.reward_claims_incorporated", duplicate_reward_claims) + assert block_2.transactions_info block_2 = recursive_replace( block_2, "foliage_transaction_block.transactions_info_hash", block_2.transactions_info.get_hash() ) + assert block_2.foliage_transaction_block block_2 = recursive_replace( block_2, "foliage.foliage_transaction_block_hash", block_2.foliage_transaction_block.get_hash() ) @@ -2049,7 +2056,7 @@ class TestBodyValidation: blocks = bt.get_consecutive_blocks( 1, block_list_input=blocks, guarantee_transaction_block=True, transaction_data=tx ) - assert (await b.receive_block(blocks[-1]))[1] == Err.INVALID_BLOCK_COST + assert (await b.receive_block(blocks[-1]))[1] in [Err.BLOCK_COST_EXCEEDS_MAX, Err.INVALID_BLOCK_COST] @pytest.mark.asyncio async def test_clvm_must_not_fail(self, empty_blockchain): @@ -2083,9 +2090,11 @@ class TestBodyValidation: # zero block_2: FullBlock = recursive_replace(block, "transactions_info.cost", uint64(0)) + assert block_2.transactions_info block_2 = recursive_replace( block_2, "foliage_transaction_block.transactions_info_hash", block_2.transactions_info.get_hash() ) + assert block_2.foliage_transaction_block block_2 = recursive_replace( block_2, "foliage.foliage_transaction_block_hash", block_2.foliage_transaction_block.get_hash() ) @@ -2098,9 +2107,11 @@ class TestBodyValidation: # too low block_2: FullBlock = recursive_replace(block, "transactions_info.cost", uint64(1)) + assert block_2.transactions_info block_2 = recursive_replace( block_2, "foliage_transaction_block.transactions_info_hash", block_2.transactions_info.get_hash() ) + assert block_2.foliage_transaction_block block_2 = recursive_replace( block_2, "foliage.foliage_transaction_block_hash", block_2.foliage_transaction_block.get_hash() ) @@ -2112,9 +2123,11 @@ class TestBodyValidation: # too high block_2: FullBlock = recursive_replace(block, "transactions_info.cost", uint64(1000000)) + assert block_2.transactions_info block_2 = recursive_replace( block_2, "foliage_transaction_block.transactions_info_hash", block_2.transactions_info.get_hash() ) + assert block_2.foliage_transaction_block block_2 = recursive_replace( block_2, "foliage.foliage_transaction_block_hash", block_2.foliage_transaction_block.get_hash() ) @@ -2515,9 +2528,11 @@ class TestBodyValidation: # wrong feees block_2: FullBlock = recursive_replace(block, "transactions_info.fees", uint64(1239)) + assert block_2.transactions_info block_2 = recursive_replace( block_2, "foliage_transaction_block.transactions_info_hash", block_2.transactions_info.get_hash() ) + assert block_2.foliage_transaction_block block_2 = recursive_replace( block_2, "foliage.foliage_transaction_block_hash", block_2.foliage_transaction_block.get_hash() ) diff --git a/tests/blockchain/test_blockchain_transactions.py b/tests/blockchain/test_blockchain_transactions.py index 951985339d87..5213a4e48eb1 100644 --- a/tests/blockchain/test_blockchain_transactions.py +++ b/tests/blockchain/test_blockchain_transactions.py @@ -288,7 +288,8 @@ class TestBlockchainTransactions: await full_node_api_1.full_node.respond_block(full_node_protocol.RespondBlock(block)) @pytest.mark.asyncio - async def test_validate_blockchain_spend_reorg_coin(self, two_nodes): + @pytest.mark.parametrize("rust_checker", [True, False]) + async def test_validate_blockchain_spend_reorg_coin(self, two_nodes, rust_checker: bool): num_blocks = 10 wallet_a = WALLET_A coinbase_puzzlehash = WALLET_A_PUZZLE_HASHES[0] @@ -311,7 +312,8 @@ class TestBlockchainTransactions: if coin.puzzle_hash == coinbase_puzzlehash: spend_coin = coin - spend_bundle = wallet_a.generate_signed_transaction(1000, receiver_1_puzzlehash, spend_coin) + assert spend_coin + spend_bundle = wallet_a.generate_signed_transaction(uint64(1000), receiver_1_puzzlehash, spend_coin) new_blocks = bt.get_consecutive_blocks( 1, @@ -326,14 +328,14 @@ class TestBlockchainTransactions: coin_2 = None for coin in run_and_get_removals_and_additions( - new_blocks[-1], test_constants.MAX_BLOCK_COST_CLVM, test_constants.COST_PER_BYTE + new_blocks[-1], test_constants.MAX_BLOCK_COST_CLVM, test_constants.COST_PER_BYTE, rust_checker=rust_checker )[1]: if coin.puzzle_hash == receiver_1_puzzlehash: coin_2 = coin break assert coin_2 is not None - spend_bundle = wallet_a.generate_signed_transaction(1000, receiver_2_puzzlehash, coin_2) + spend_bundle = wallet_a.generate_signed_transaction(uint64(1000), receiver_2_puzzlehash, coin_2) new_blocks = bt.get_consecutive_blocks( 1, @@ -347,14 +349,14 @@ class TestBlockchainTransactions: coin_3 = None for coin in run_and_get_removals_and_additions( - new_blocks[-1], test_constants.MAX_BLOCK_COST_CLVM, test_constants.COST_PER_BYTE + new_blocks[-1], test_constants.MAX_BLOCK_COST_CLVM, test_constants.COST_PER_BYTE, rust_checker=rust_checker )[1]: if coin.puzzle_hash == receiver_2_puzzlehash: coin_3 = coin break assert coin_3 is not None - spend_bundle = wallet_a.generate_signed_transaction(1000, receiver_3_puzzlehash, coin_3) + spend_bundle = wallet_a.generate_signed_transaction(uint64(1000), receiver_3_puzzlehash, coin_3) new_blocks = bt.get_consecutive_blocks( 1, diff --git a/tests/core/fixtures.py b/tests/core/fixtures.py index 42172573d82f..ff89e4e2daeb 100644 --- a/tests/core/fixtures.py +++ b/tests/core/fixtures.py @@ -16,10 +16,15 @@ from chia.util.path import mkdir from tests.setup_nodes import bt, test_constants +blockchain_db_counter: int = 0 + + async def create_blockchain(constants: ConsensusConstants): - db_path = Path("blockchain_test.db") + global blockchain_db_counter + db_path = Path(f"blockchain_test-{blockchain_db_counter}.db") if db_path.exists(): db_path.unlink() + blockchain_db_counter += 1 connection = await aiosqlite.connect(db_path) wrapper = DBWrapper(connection) coin_store = await CoinStore.create(wrapper) @@ -29,12 +34,12 @@ async def create_blockchain(constants: ConsensusConstants): return bc1, connection, db_path -@pytest.fixture(scope="function") -async def empty_blockchain(): +@pytest.fixture(scope="function", params=[0, 10000000]) +async def empty_blockchain(request): """ Provides a list of 10 valid blocks, as well as a blockchain with 9 blocks added to it. """ - bc1, connection, db_path = await create_blockchain(test_constants) + bc1, connection, db_path = await create_blockchain(test_constants.replace(RUST_CONDITION_CHECKER=request.param)) yield bc1 await connection.close() diff --git a/tests/core/full_node/test_coin_store.py b/tests/core/full_node/test_coin_store.py index 391b8c5aa36b..c0761b4f6955 100644 --- a/tests/core/full_node/test_coin_store.py +++ b/tests/core/full_node/test_coin_store.py @@ -56,7 +56,8 @@ def get_future_reward_coins(block: FullBlock) -> Tuple[Coin, Coin]: class TestCoinStore: @pytest.mark.asyncio - async def test_basic_coin_store(self): + @pytest.mark.parametrize("rust_checker", [True, False]) + async def test_basic_coin_store(self, rust_checker: bool): wallet_a = WALLET_A reward_ph = wallet_a.get_new_puzzlehash() @@ -76,7 +77,9 @@ class TestCoinStore: if coin.puzzle_hash == reward_ph: coins_to_spend.append(coin) - spend_bundle = wallet_a.generate_signed_transaction(1000, wallet_a.get_new_puzzlehash(), coins_to_spend[0]) + spend_bundle = wallet_a.generate_signed_transaction( + uint64(1000), wallet_a.get_new_puzzlehash(), coins_to_spend[0] + ) db_path = Path("fndb_test.db") if db_path.exists(): @@ -108,6 +111,7 @@ class TestCoinStore: bt.constants.MAX_BLOCK_COST_CLVM, cost_per_byte=bt.constants.COST_PER_BYTE, safe_mode=False, + rust_checker=rust_checker, ) tx_removals, tx_additions = tx_removals_and_additions(npc_result.npc_list) else: diff --git a/tests/core/full_node/test_mempool.py b/tests/core/full_node/test_mempool.py index 41834ed595e6..08edce41e412 100644 --- a/tests/core/full_node/test_mempool.py +++ b/tests/core/full_node/test_mempool.py @@ -26,7 +26,7 @@ from chia.util.ints import uint64 from chia.util.hash import std_hash from chia.types.mempool_inclusion_status import MempoolInclusionStatus from chia.util.api_decorators import api_request, peer_required, bytes_required -from chia.full_node.mempool_check_conditions import parse_condition_args, parse_condition +from chia.full_node.mempool_check_conditions import parse_condition_args, parse_condition, get_name_puzzle_conditions from tests.connection_utils import connect_and_get_peer from tests.core.node_height import node_height_at_least @@ -38,7 +38,6 @@ from chia.consensus.cost_calculator import NPCResult from chia.types.blockchain_format.program import SerializedProgram from clvm_tools import binutils from chia.types.generator_types import BlockGenerator -from chia.full_node.mempool_check_conditions import get_name_puzzle_conditions from clvm.casts import int_from_bytes BURN_PUZZLE_HASH = b"0" * 32 @@ -447,7 +446,7 @@ class TestMempoolManager: assert sb1 is None # the transaction may become valid later assert status == MempoolInclusionStatus.FAILED - assert err == Err.GENERATOR_RUNTIME_ERROR + assert err == Err.INVALID_CONDITION @pytest.mark.asyncio async def test_correct_block_index(self, two_nodes): @@ -510,7 +509,7 @@ class TestMempoolManager: assert sb1 is None # the transaction may become valid later assert status == MempoolInclusionStatus.FAILED - assert err == Err.GENERATOR_RUNTIME_ERROR + assert err == Err.INVALID_CONDITION @pytest.mark.asyncio async def test_correct_block_age(self, two_nodes): @@ -611,7 +610,7 @@ class TestMempoolManager: sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name()) assert sb1 is None assert status == MempoolInclusionStatus.FAILED - assert err == Err.GENERATOR_RUNTIME_ERROR + assert err == Err.INVALID_CONDITION @pytest.mark.asyncio async def test_assert_time_exceeds(self, two_nodes): @@ -667,7 +666,7 @@ class TestMempoolManager: sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name()) assert sb1 is None assert status == MempoolInclusionStatus.FAILED - assert err == Err.GENERATOR_RUNTIME_ERROR + assert err == Err.INVALID_CONDITION @pytest.mark.asyncio async def test_assert_time_garbage(self, two_nodes): @@ -739,7 +738,7 @@ class TestMempoolManager: sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name()) assert sb1 is None assert status == MempoolInclusionStatus.FAILED - assert err == Err.GENERATOR_RUNTIME_ERROR + assert err == Err.INVALID_CONDITION @pytest.mark.asyncio async def test_assert_time_relative_negative(self, two_nodes): @@ -756,6 +755,7 @@ class TestMempoolManager: assert status == MempoolInclusionStatus.SUCCESS assert err is None + # ensure one spend can assert a coin announcement from another spend @pytest.mark.asyncio async def test_correct_coin_announcement_consumed(self, two_nodes): def test_fun(coin_1: Coin, coin_2: Coin) -> SpendBundle: @@ -778,6 +778,8 @@ class TestMempoolManager: assert status == MempoolInclusionStatus.SUCCESS assert err is None + # ensure one spend can assert a coin announcement from another spend, even + # though the conditions have garbage (ignored) at the end @pytest.mark.asyncio async def test_coin_announcement_garbage(self, two_nodes): def test_fun(coin_1: Coin, coin_2: Coin) -> SpendBundle: @@ -821,7 +823,7 @@ class TestMempoolManager: assert full_node_1.full_node.mempool_manager.get_spendbundle(bundle.name()) is None assert status == MempoolInclusionStatus.FAILED - assert err == Err.GENERATOR_RUNTIME_ERROR + assert err == Err.INVALID_CONDITION @pytest.mark.asyncio async def test_coin_announcement_missing_arg2(self, two_nodes): @@ -843,7 +845,7 @@ class TestMempoolManager: assert full_node_1.full_node.mempool_manager.get_spendbundle(bundle.name()) is None assert status == MempoolInclusionStatus.FAILED - assert err == Err.GENERATOR_RUNTIME_ERROR + assert err == Err.INVALID_CONDITION @pytest.mark.asyncio async def test_coin_announcement_too_big(self, two_nodes): @@ -878,6 +880,8 @@ class TestMempoolManager: except AssertionError: pass + # ensure an assert coin announcement is rejected if it doesn't match the + # create announcement @pytest.mark.asyncio async def test_invalid_coin_announcement_rejected(self, two_nodes): full_node_1, full_node_2, server_1, server_2 = two_nodes @@ -888,7 +892,7 @@ class TestMempoolManager: cvp = ConditionWithArgs(ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT, [announce.name()]) dic = {cvp.opcode: [cvp]} - # Wrong message + # mismatching message cvp2 = ConditionWithArgs( ConditionOpcode.CREATE_COIN_ANNOUNCEMENT, [b"wrong test"], @@ -918,10 +922,7 @@ class TestMempoolManager: dic = {cvp.opcode: [cvp]} - cvp2 = ConditionWithArgs( - ConditionOpcode.CREATE_COIN_ANNOUNCEMENT, - [b"test"], - ) + cvp2 = ConditionWithArgs(ConditionOpcode.CREATE_COIN_ANNOUNCEMENT, [b"test"]) dic2 = {cvp.opcode: [cvp2]} spend_bundle1 = generate_test_spend_bundle(coin_1, dic) # coin 2 is making the announcement, right message wrong coin @@ -1011,7 +1012,7 @@ class TestMempoolManager: assert mempool_bundle is None assert status == MempoolInclusionStatus.FAILED - assert err == Err.GENERATOR_RUNTIME_ERROR + assert err == Err.INVALID_CONDITION @pytest.mark.asyncio async def test_puzzle_announcement_missing_arg2(self, two_nodes): @@ -1039,7 +1040,7 @@ class TestMempoolManager: assert mempool_bundle is None assert status == MempoolInclusionStatus.FAILED - assert err == Err.GENERATOR_RUNTIME_ERROR + assert err == Err.INVALID_CONDITION @pytest.mark.asyncio async def test_invalid_puzzle_announcement_rejected(self, two_nodes): @@ -1133,7 +1134,7 @@ class TestMempoolManager: dic = {cvp.opcode: [cvp]} blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic, fee=10) assert status == MempoolInclusionStatus.FAILED - assert err == Err.GENERATOR_RUNTIME_ERROR + assert err == Err.INVALID_CONDITION @pytest.mark.asyncio async def test_assert_fee_condition_negative_fee(self, two_nodes): @@ -1372,7 +1373,7 @@ class TestMempoolManager: assert sb1 is None assert status == MempoolInclusionStatus.FAILED - assert err == Err.GENERATOR_RUNTIME_ERROR + assert err == Err.INVALID_CONDITION @pytest.mark.asyncio async def test_invalid_my_parent(self, two_nodes): @@ -1437,7 +1438,7 @@ class TestMempoolManager: assert sb1 is None assert status == MempoolInclusionStatus.FAILED - assert err == Err.GENERATOR_RUNTIME_ERROR + assert err == Err.INVALID_CONDITION @pytest.mark.asyncio async def test_invalid_my_puzhash(self, two_nodes): @@ -1501,7 +1502,7 @@ class TestMempoolManager: assert sb1 is None assert status == MempoolInclusionStatus.FAILED - assert err == Err.GENERATOR_RUNTIME_ERROR + assert err == Err.INVALID_CONDITION @pytest.mark.asyncio async def test_invalid_my_amount(self, two_nodes): @@ -1950,7 +1951,12 @@ MAX_BLOCK_COST_CLVM = 11000000000 def generator_condition_tester( - conditions: str, safe_mode: bool = False, quote: bool = True, max_cost: int = MAX_BLOCK_COST_CLVM + conditions: str, + *, + rust_checker: bool, + safe_mode: bool = False, + quote: bool = True, + max_cost: int = MAX_BLOCK_COST_CLVM, ) -> NPCResult: prg = f"(q ((0x0101010101010101010101010101010101010101010101010101010101010101 {'(q ' if quote else ''} {conditions} {')' if quote else ''} 123 (() (q . ())))))" # noqa print(f"program: {prg}") @@ -1958,13 +1964,14 @@ def generator_condition_tester( generator = BlockGenerator(program, []) print(f"len: {len(bytes(program))}") npc_result: NPCResult = get_name_puzzle_conditions( - generator, max_cost, cost_per_byte=COST_PER_BYTE, safe_mode=safe_mode + generator, max_cost, cost_per_byte=COST_PER_BYTE, safe_mode=safe_mode, rust_checker=rust_checker ) return npc_result class TestGeneratorConditions: - def test_duplicate_height_time_conditions(self): + @pytest.mark.parametrize("rust_checker", [True, False]) + def test_duplicate_height_time_conditions(self, rust_checker: bool): # ASSERT_SECONDS_RELATIVE # ASSERT_SECONDS_ABSOLUTE # ASSERT_HEIGHT_RELATIVE @@ -1972,7 +1979,9 @@ class TestGeneratorConditions: for cond in [80, 81, 82, 83]: # even though the generator outputs multiple conditions, we only # need to return the highest one (i.e. most strict) - npc_result = generator_condition_tester(" ".join([f"({cond} {i})" for i in range(50, 101)])) + npc_result = generator_condition_tester( + " ".join([f"({cond} {i})" for i in range(50, 101)]), rust_checker=rust_checker + ) assert npc_result.error is None assert len(npc_result.npc_list) == 1 opcode = ConditionOpcode(bytes([cond])) @@ -1983,27 +1992,48 @@ class TestGeneratorConditions: max_arg = max(max_arg, int_from_bytes(c.vars[0])) assert max_arg == 100 - def test_just_announcement(self): + @pytest.mark.parametrize("rust_checker", [True, False]) + def test_just_announcement(self, rust_checker: bool): # CREATE_COIN_ANNOUNCEMENT # CREATE_PUZZLE_ANNOUNCEMENT for cond in [60, 62]: message = "a" * 1024 # announcements are validated on the Rust side and never returned # back. They are either satisified or cause an immediate failure - npc_result = generator_condition_tester(f'({cond} "{message}") ' * 50) + npc_result = generator_condition_tester(f'({cond} "{message}") ' * 50, rust_checker=rust_checker) assert npc_result.error is None assert len(npc_result.npc_list) == 1 - # create-announcements and assert-announcements are dropped once - # validated + if rust_checker: + # create-announcements and assert-announcements are dropped once + # validated + assert npc_result.npc_list[0].conditions == [] + else: + assert len(npc_result.npc_list[0].conditions) == 1 + print(npc_result.npc_list[0].conditions[0][0]) + assert npc_result.npc_list[0].conditions[0][0] == ConditionOpcode(bytes([cond])) + assert len(npc_result.npc_list[0].conditions[0][1]) == 50 - # assert npc_result.npc_list[0].conditions == [] + @pytest.mark.parametrize("rust_checker", [True, False]) + def test_assert_announcement_fail(self, rust_checker: bool): + # ASSERT_COIN_ANNOUNCEMENT + # ASSERT_PUZZLE_ANNOUNCEMENT + for cond in [61, 63]: + message = "a" * 1024 + # announcements are validated on the Rust side and never returned + # back. They ar either satisified or cause an immediate failure + # in this test we just assert announcements, we never make them, so + # these should fail + npc_result = generator_condition_tester(f'({cond} "{message}") ', rust_checker=rust_checker) + assert npc_result.error == Err.ASSERT_ANNOUNCE_CONSUMED_FAILED.value + assert npc_result.npc_list == [] - def test_multiple_reserve_fee(self): + @pytest.mark.parametrize("rust_checker", [True, False]) + def test_multiple_reserve_fee(self, rust_checker: bool): # RESERVE_FEE cond = 52 # even though the generator outputs 3 conditions, we only need to return one copy # with all the fees accumulated - npc_result = generator_condition_tester(f"({cond} 100) " * 3) + npc_result = generator_condition_tester(f"({cond} 100) " * 3, rust_checker=rust_checker) assert npc_result.error is None assert len(npc_result.npc_list) == 1 opcode = ConditionOpcode(bytes([cond])) @@ -2015,24 +2045,31 @@ class TestGeneratorConditions: reserve_fee += int_from_bytes(c.vars[0]) assert reserve_fee == 300 + if rust_checker: + assert len(npc_result.npc_list[0].conditions[0][1]) == 1 - # def test_duplicate_outputs(self): - # CREATE_COIN - # creating multiple coins with the same properties (same parent, same - # target puzzle hash and same amount) is not allowed. That's a consensus - # failure. - # puzzle_hash = "abababababababababababababababab" - # npc_result = generator_condition_tester(f'(51 "{puzzle_hash}" 10) ' * 2) - # assert npc_result.error == Err.DUPLICATE_OUTPUT.value - # assert len(npc_result.npc_list) == 0 + @pytest.mark.parametrize("rust_checker", [True, False]) + def test_duplicate_outputs(self, rust_checker: bool): + # CREATE_COIN + # creating multiple coins with the same properties (same parent, same + # target puzzle hash and same amount) is not allowed. That's a consensus + # failure. + puzzle_hash = "abababababababababababababababab" + npc_result = generator_condition_tester(f'(51 "{puzzle_hash}" 10) ' * 2, rust_checker=rust_checker) + if rust_checker: + assert npc_result.error == Err.DUPLICATE_OUTPUT.value + assert npc_result.npc_list == [] + else: + assert npc_result.error is None - def test_create_coin_cost(self): + @pytest.mark.parametrize("rust_checker", [True, False]) + def test_create_coin_cost(self, rust_checker: bool): # CREATE_COIN puzzle_hash = "abababababababababababababababab" # this max cost is exactly enough for the create coin condition npc_result = generator_condition_tester( - f'(51 "{puzzle_hash}" 10) ', max_cost=20470 + 95 * COST_PER_BYTE + 1800000 + f'(51 "{puzzle_hash}" 10) ', max_cost=20470 + 95 * COST_PER_BYTE + 1800000, rust_checker=rust_checker ) assert npc_result.error is None assert npc_result.clvm_cost == 20470 @@ -2040,17 +2077,18 @@ class TestGeneratorConditions: # if we subtract one from max cost, this should fail npc_result = generator_condition_tester( - f'(51 "{puzzle_hash}" 10) ', max_cost=20470 + 95 * COST_PER_BYTE + 1800000 - 1 + f'(51 "{puzzle_hash}" 10) ', max_cost=20470 + 95 * COST_PER_BYTE + 1800000 - 1, rust_checker=rust_checker ) assert npc_result.error in [Err.BLOCK_COST_EXCEEDS_MAX.value, Err.INVALID_BLOCK_COST.value] - def test_agg_sig_cost(self): + @pytest.mark.parametrize("rust_checker", [True, False]) + def test_agg_sig_cost(self, rust_checker: bool): # AGG_SIG_ME pubkey = "abababababababababababababababababababababababab" # this max cost is exactly enough for the AGG_SIG condition npc_result = generator_condition_tester( - f'(49 "{pubkey}" "foobar") ', max_cost=20512 + 117 * COST_PER_BYTE + 1200000 + f'(49 "{pubkey}" "foobar") ', max_cost=20512 + 117 * COST_PER_BYTE + 1200000, rust_checker=rust_checker ) assert npc_result.error is None assert npc_result.clvm_cost == 20512 @@ -2058,11 +2096,12 @@ class TestGeneratorConditions: # if we subtract one from max cost, this should fail npc_result = generator_condition_tester( - f'(49 "{pubkey}" "foobar") ', max_cost=20512 + 117 * COST_PER_BYTE + 1200000 - 1 + f'(49 "{pubkey}" "foobar") ', max_cost=20512 + 117 * COST_PER_BYTE + 1200000 - 1, rust_checker=rust_checker ) assert npc_result.error in [Err.BLOCK_COST_EXCEEDS_MAX.value, Err.INVALID_BLOCK_COST.value] - def test_create_coin_different_parent(self): + @pytest.mark.parametrize("rust_checker", [True, False]) + def test_create_coin_different_parent(self, rust_checker: bool): # if the coins we create have different parents, they are never # considered duplicate, even when they have the same puzzle hash and @@ -2075,7 +2114,7 @@ class TestGeneratorConditions: ) generator = BlockGenerator(program, []) npc_result: NPCResult = get_name_puzzle_conditions( - generator, MAX_BLOCK_COST_CLVM, cost_per_byte=COST_PER_BYTE, safe_mode=False + generator, MAX_BLOCK_COST_CLVM, cost_per_byte=COST_PER_BYTE, safe_mode=False, rust_checker=rust_checker ) assert npc_result.error is None assert len(npc_result.npc_list) == 2 @@ -2088,12 +2127,15 @@ class TestGeneratorConditions: ) ] - def test_create_coin_different_puzzhash(self): + @pytest.mark.parametrize("rust_checker", [True, False]) + def test_create_coin_different_puzzhash(self, rust_checker: bool): # CREATE_COIN # coins with different puzzle hashes are not considered duplicate puzzle_hash_1 = "abababababababababababababababab" puzzle_hash_2 = "cbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcb" - npc_result = generator_condition_tester(f'(51 "{puzzle_hash_1}" 5) (51 "{puzzle_hash_2}" 5)') + npc_result = generator_condition_tester( + f'(51 "{puzzle_hash_1}" 5) (51 "{puzzle_hash_2}" 5)', rust_checker=rust_checker + ) assert npc_result.error is None assert len(npc_result.npc_list) == 1 opcode = ConditionOpcode.CREATE_COIN @@ -2106,11 +2148,14 @@ class TestGeneratorConditions: in npc_result.npc_list[0].conditions[0][1] ) - def test_create_coin_different_amounts(self): + @pytest.mark.parametrize("rust_checker", [True, False]) + def test_create_coin_different_amounts(self, rust_checker: bool): # CREATE_COIN # coins with different amounts are not considered duplicate puzzle_hash = "abababababababababababababababab" - npc_result = generator_condition_tester(f'(51 "{puzzle_hash}" 5) (51 "{puzzle_hash}" 4)') + npc_result = generator_condition_tester( + f'(51 "{puzzle_hash}" 5) (51 "{puzzle_hash}" 4)', rust_checker=rust_checker + ) assert npc_result.error is None assert len(npc_result.npc_list) == 1 opcode = ConditionOpcode.CREATE_COIN @@ -2123,16 +2168,15 @@ class TestGeneratorConditions: in npc_result.npc_list[0].conditions[0][1] ) - def test_unknown_condition(self): + @pytest.mark.parametrize("rust_checker", [True, False]) + def test_unknown_condition(self, rust_checker: bool): for sm in [True, False]: for c in ['(1 100 "foo" "bar")', "(100)", "(1 1) (2 2) (3 3)", '("foobar")']: - npc_result = generator_condition_tester(c, sm) + npc_result = generator_condition_tester(c, safe_mode=sm, rust_checker=rust_checker) print(npc_result) if sm: assert npc_result.error == Err.INVALID_CONDITION.value assert npc_result.npc_list == [] else: assert npc_result.error is None - - -# assert npc_result.npc_list[0].conditions == [] + assert npc_result.npc_list[0].conditions == [] diff --git a/tests/core/test_cost_calculation.py b/tests/core/test_cost_calculation.py index 336dafb78139..7b8c5b4a09d1 100644 --- a/tests/core/test_cost_calculation.py +++ b/tests/core/test_cost_calculation.py @@ -54,7 +54,8 @@ def large_block_generator(size): class TestCostCalculation: @pytest.mark.asyncio - async def test_basics(self): + @pytest.mark.parametrize("rust_checker", [True, False]) + async def test_basics(self, rust_checker: bool): wallet_tool = bt.get_pool_wallet_tool() ph = wallet_tool.get_new_puzzlehash() num_blocks = 3 @@ -76,7 +77,11 @@ class TestCostCalculation: program: BlockGenerator = simple_solution_generator(spend_bundle) npc_result: NPCResult = get_name_puzzle_conditions( - program, test_constants.MAX_BLOCK_COST_CLVM, cost_per_byte=test_constants.COST_PER_BYTE, safe_mode=False + program, + test_constants.MAX_BLOCK_COST_CLVM, + cost_per_byte=test_constants.COST_PER_BYTE, + safe_mode=False, + rust_checker=rust_checker, ) cost = calculate_cost_of_program(program.program, npc_result, test_constants.COST_PER_BYTE) @@ -98,7 +103,8 @@ class TestCostCalculation: ) @pytest.mark.asyncio - async def test_strict_mode(self): + @pytest.mark.parametrize("rust_checker", [True, False]) + async def test_strict_mode(self, rust_checker: bool): wallet_tool = bt.get_pool_wallet_tool() ph = wallet_tool.get_new_puzzlehash() @@ -133,11 +139,19 @@ class TestCostCalculation: ) generator = BlockGenerator(program, []) npc_result: NPCResult = get_name_puzzle_conditions( - generator, test_constants.MAX_BLOCK_COST_CLVM, cost_per_byte=test_constants.COST_PER_BYTE, safe_mode=True + generator, + test_constants.MAX_BLOCK_COST_CLVM, + cost_per_byte=test_constants.COST_PER_BYTE, + safe_mode=True, + rust_checker=rust_checker, ) assert npc_result.error is not None - npc_result: NPCResult = get_name_puzzle_conditions( - generator, test_constants.MAX_BLOCK_COST_CLVM, cost_per_byte=test_constants.COST_PER_BYTE, safe_mode=False + npc_result = get_name_puzzle_conditions( + generator, + test_constants.MAX_BLOCK_COST_CLVM, + cost_per_byte=test_constants.COST_PER_BYTE, + safe_mode=False, + rust_checker=rust_checker, ) assert npc_result.error is None @@ -148,7 +162,8 @@ class TestCostCalculation: assert error is None @pytest.mark.asyncio - async def test_clvm_strict_mode(self): + @pytest.mark.parametrize("rust_checker", [True, False]) + async def test_clvm_strict_mode(self, rust_checker: bool): block = Program.from_bytes(bytes(SMALL_BLOCK_GENERATOR.program)) disassembly = binutils.disassemble(block) # this is a valid generator program except the first clvm @@ -158,16 +173,25 @@ class TestCostCalculation: program = SerializedProgram.from_bytes(binutils.assemble(f"(i (0xfe (q . 0)) (q . ()) {disassembly})").as_bin()) generator = BlockGenerator(program, []) npc_result: NPCResult = get_name_puzzle_conditions( - generator, test_constants.MAX_BLOCK_COST_CLVM, cost_per_byte=test_constants.COST_PER_BYTE, safe_mode=True + generator, + test_constants.MAX_BLOCK_COST_CLVM, + cost_per_byte=test_constants.COST_PER_BYTE, + safe_mode=True, + rust_checker=rust_checker, ) assert npc_result.error is not None - npc_result: NPCResult = get_name_puzzle_conditions( - generator, test_constants.MAX_BLOCK_COST_CLVM, cost_per_byte=test_constants.COST_PER_BYTE, safe_mode=False + npc_result = get_name_puzzle_conditions( + generator, + test_constants.MAX_BLOCK_COST_CLVM, + cost_per_byte=test_constants.COST_PER_BYTE, + safe_mode=False, + rust_checker=rust_checker, ) assert npc_result.error is None @pytest.mark.asyncio - async def test_tx_generator_speed(self): + @pytest.mark.parametrize("rust_checker", [True, False]) + async def test_tx_generator_speed(self, rust_checker: bool): LARGE_BLOCK_COIN_CONSUMED_COUNT = 687 generator_bytes = large_block_generator(LARGE_BLOCK_COIN_CONSUMED_COUNT) program = SerializedProgram.from_bytes(generator_bytes) @@ -175,7 +199,11 @@ class TestCostCalculation: start_time = time.time() generator = BlockGenerator(program, []) npc_result = get_name_puzzle_conditions( - generator, test_constants.MAX_BLOCK_COST_CLVM, cost_per_byte=test_constants.COST_PER_BYTE, safe_mode=False + generator, + test_constants.MAX_BLOCK_COST_CLVM, + cost_per_byte=test_constants.COST_PER_BYTE, + safe_mode=False, + rust_checker=rust_checker, ) end_time = time.time() duration = end_time - start_time @@ -186,7 +214,8 @@ class TestCostCalculation: assert duration < 1 @pytest.mark.asyncio - async def test_clvm_max_cost(self): + @pytest.mark.parametrize("rust_checker", [True, False]) + async def test_clvm_max_cost(self, rust_checker: bool): block = Program.from_bytes(bytes(SMALL_BLOCK_GENERATOR.program)) disassembly = binutils.disassemble(block) @@ -201,14 +230,18 @@ class TestCostCalculation: # ensure we fail if the program exceeds the cost generator = BlockGenerator(program, []) - npc_result: NPCResult = get_name_puzzle_conditions(generator, 10000000, cost_per_byte=0, safe_mode=False) + npc_result: NPCResult = get_name_puzzle_conditions( + generator, 10000000, cost_per_byte=0, safe_mode=False, rust_checker=rust_checker + ) assert npc_result.error is not None assert npc_result.clvm_cost == 0 # raise the max cost to make sure this passes # ensure we pass if the program does not exceeds the cost - npc_result: NPCResult = get_name_puzzle_conditions(generator, 20000000, cost_per_byte=0, safe_mode=False) + npc_result = get_name_puzzle_conditions( + generator, 20000000, cost_per_byte=0, safe_mode=False, rust_checker=rust_checker + ) assert npc_result.error is None assert npc_result.clvm_cost > 10000000 diff --git a/tests/generator/test_rom.py b/tests/generator/test_rom.py index 13059019e3a3..d9fb9e22ff86 100644 --- a/tests/generator/test_rom.py +++ b/tests/generator/test_rom.py @@ -1,3 +1,4 @@ +import pytest from clvm_tools import binutils from clvm_tools.clvmc import compile_clvm_text @@ -93,14 +94,17 @@ class TestROM: assert cost == EXPECTED_ABBREVIATED_COST assert r.as_bin().hex() == EXPECTED_OUTPUT - def test_get_name_puzzle_conditions(self): + @pytest.mark.parametrize("rust_checker", [True, False]) + def test_get_name_puzzle_conditions(self, rust_checker: bool): # this tests that extra block or coin data doesn't confuse `get_name_puzzle_conditions` gen = block_generator() cost, r = run_generator(gen, max_cost=MAX_COST) print(r) - npc_result = get_name_puzzle_conditions(gen, max_cost=MAX_COST, cost_per_byte=COST_PER_BYTE, safe_mode=False) + npc_result = get_name_puzzle_conditions( + gen, max_cost=MAX_COST, cost_per_byte=COST_PER_BYTE, safe_mode=False, rust_checker=rust_checker + ) assert npc_result.error is None assert npc_result.clvm_cost == EXPECTED_COST cond_1 = ConditionWithArgs(ConditionOpcode.CREATE_COIN, [bytes([0] * 31 + [1]), int_to_bytes(500)]) diff --git a/tests/util/generator_tools_testing.py b/tests/util/generator_tools_testing.py index a31ff707a78d..d0d048cba421 100644 --- a/tests/util/generator_tools_testing.py +++ b/tests/util/generator_tools_testing.py @@ -9,7 +9,7 @@ from chia.util.generator_tools import additions_for_npc def run_and_get_removals_and_additions( - block: FullBlock, max_cost: int, cost_per_byte: int, safe_mode=False + block: FullBlock, max_cost: int, cost_per_byte: int, rust_checker: bool, safe_mode=False ) -> Tuple[List[bytes32], List[Coin]]: removals: List[bytes32] = [] additions: List[Coin] = [] @@ -20,7 +20,11 @@ def run_and_get_removals_and_additions( if block.transactions_generator is not None: npc_result = get_name_puzzle_conditions( - BlockGenerator(block.transactions_generator, []), max_cost, cost_per_byte=cost_per_byte, safe_mode=safe_mode + BlockGenerator(block.transactions_generator, []), + max_cost, + cost_per_byte=cost_per_byte, + safe_mode=safe_mode, + rust_checker=rust_checker, ) # build removals list for npc in npc_result.npc_list: