diff --git a/chia/consensus/block_body_validation.py b/chia/consensus/block_body_validation.py index 9bf0b1e639d7..5e2f45e422d0 100644 --- a/chia/consensus/block_body_validation.py +++ b/chia/consensus/block_body_validation.py @@ -318,6 +318,7 @@ async def validate_block_body( min(constants.MAX_BLOCK_COST_CLVM, curr.transactions_info.cost), cost_per_byte=constants.COST_PER_BYTE, mempool_mode=False, + height=curr.height, ) removals_in_curr, additions_in_curr = tx_removals_and_additions(curr_npc_result.conds) else: diff --git a/chia/consensus/blockchain.py b/chia/consensus/blockchain.py index 51d44a154cf2..a319d21fa29a 100644 --- a/chia/consensus/blockchain.py +++ b/chia/consensus/blockchain.py @@ -443,6 +443,7 @@ class Blockchain(BlockchainInterface): self.constants.MAX_BLOCK_COST_CLVM, cost_per_byte=self.constants.COST_PER_BYTE, mempool_mode=False, + height=block.height, ) tx_removals, tx_additions = tx_removals_and_additions(npc_result.conds) return tx_removals, tx_additions, npc_result @@ -646,13 +647,14 @@ class Blockchain(BlockchainInterface): validate_signatures=validate_signatures, ) - async def run_generator(self, unfinished_block: bytes, generator: BlockGenerator) -> NPCResult: + async def run_generator(self, unfinished_block: bytes, generator: BlockGenerator, height: uint32) -> NPCResult: task = asyncio.get_running_loop().run_in_executor( self.pool, _run_generator, self.constants, unfinished_block, bytes(generator), + height, ) npc_result_bytes = await task if npc_result_bytes is None: diff --git a/chia/consensus/constants.py b/chia/consensus/constants.py index 14715687ab44..60bc0da1d833 100644 --- a/chia/consensus/constants.py +++ b/chia/consensus/constants.py @@ -61,6 +61,7 @@ class ConsensusConstants: MAX_GENERATOR_SIZE: uint32 MAX_GENERATOR_REF_LIST_SIZE: uint32 POOL_SUB_SLOT_ITERS: uint64 + SOFT_FORK_HEIGHT: uint32 def replace(self, **changes: object) -> "ConsensusConstants": return dataclasses.replace(self, **changes) diff --git a/chia/consensus/default_constants.py b/chia/consensus/default_constants.py index 57b5adb591d2..8235d0002dfd 100644 --- a/chia/consensus/default_constants.py +++ b/chia/consensus/default_constants.py @@ -55,6 +55,7 @@ default_kwargs = { "MAX_GENERATOR_SIZE": 1000000, "MAX_GENERATOR_REF_LIST_SIZE": 512, # Number of references allowed in the block generator ref list "POOL_SUB_SLOT_ITERS": 37600000000, # iters limit * NUM_SPS + "SOFT_FORK_HEIGHT": 3630000, } diff --git a/chia/consensus/multiprocess_validation.py b/chia/consensus/multiprocess_validation.py index 834a739e1f7c..1763d99d955a 100644 --- a/chia/consensus/multiprocess_validation.py +++ b/chia/consensus/multiprocess_validation.py @@ -93,6 +93,7 @@ def batch_pre_validate_blocks( min(constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), cost_per_byte=constants.COST_PER_BYTE, mempool_mode=False, + height=block.height, ) removals, tx_additions = tx_removals_and_additions(npc_result.conds) if npc_result is not None and npc_result.error is not None: @@ -368,6 +369,7 @@ def _run_generator( constants: ConsensusConstants, unfinished_block_bytes: bytes, block_generator_bytes: bytes, + height: uint32, ) -> Optional[bytes]: """ Runs the CLVM generator from bytes inputs. This is meant to be called under a ProcessPoolExecutor, in order to @@ -383,6 +385,7 @@ def _run_generator( min(constants.MAX_BLOCK_COST_CLVM, unfinished_block.transactions_info.cost), cost_per_byte=constants.COST_PER_BYTE, mempool_mode=False, + height=height, ) return bytes(npc_result) except ValidationError as e: diff --git a/chia/full_node/full_node.py b/chia/full_node/full_node.py index 8f31169524ad..b6f9c90900c4 100644 --- a/chia/full_node/full_node.py +++ b/chia/full_node/full_node.py @@ -1883,7 +1883,8 @@ class FullNode: if block_bytes is None: block_bytes = bytes(block) - npc_result = await self.blockchain.run_generator(block_bytes, block_generator) + height = uint32(0) if prev_b is None else uint32(prev_b.height + 1) + npc_result = await self.blockchain.run_generator(block_bytes, block_generator, height) pre_validation_time = time.time() - pre_validation_start # blockchain.run_generator throws on errors, so npc_result is diff --git a/chia/full_node/full_node_api.py b/chia/full_node/full_node_api.py index e55176819ea8..96fba0c20d23 100644 --- a/chia/full_node/full_node_api.py +++ b/chia/full_node/full_node_api.py @@ -1114,6 +1114,7 @@ class FullNodeAPI: self.full_node.constants.MAX_BLOCK_COST_CLVM, cost_per_byte=self.full_node.constants.COST_PER_BYTE, mempool_mode=False, + height=request.height, ), ) diff --git a/chia/full_node/mempool_check_conditions.py b/chia/full_node/mempool_check_conditions.py index d6c6c098206f..8156cc1abbf9 100644 --- a/chia/full_node/mempool_check_conditions.py +++ b/chia/full_node/mempool_check_conditions.py @@ -3,7 +3,7 @@ from __future__ import annotations import logging from typing import Dict, Optional, Tuple -from chia_rs import MEMPOOL_MODE, NO_NEG_DIV +from chia_rs import LIMIT_STACK, MEMPOOL_MODE, NO_NEG_DIV from chia_rs import get_puzzle_and_solution_for_coin as get_puzzle_and_solution_for_coin_rust from chia.consensus.cost_calculator import NPCResult @@ -30,7 +30,7 @@ log = logging.getLogger(__name__) def get_name_puzzle_conditions( - generator: BlockGenerator, max_cost: int, *, cost_per_byte: int, mempool_mode: bool + generator: BlockGenerator, max_cost: int, *, cost_per_byte: int, mempool_mode: bool, height: Optional[uint32] = None ) -> NPCResult: block_program, block_program_args = setup_generator_args(generator) size_cost = len(bytes(generator.program)) * cost_per_byte @@ -38,11 +38,14 @@ def get_name_puzzle_conditions( if max_cost < 0: return NPCResult(uint16(Err.INVALID_BLOCK_COST.value), None, uint64(0)) - # mempool mode also has these rules apply - assert (MEMPOOL_MODE & NO_NEG_DIV) != 0 + # in mempool mode, the height doesn't matter, because it's always strict. + # But otherwise, height must be specified to know which rules to apply + assert mempool_mode or height is not None if mempool_mode: flags = MEMPOOL_MODE + elif height is not None and height >= DEFAULT_CONSTANTS.SOFT_FORK_HEIGHT: + flags = NO_NEG_DIV | LIMIT_STACK else: # conditions must use integers in canonical encoding (i.e. no redundant # leading zeros) diff --git a/tests/blockchain/test_blockchain.py b/tests/blockchain/test_blockchain.py index fe12cb420149..23071c200307 100644 --- a/tests/blockchain/test_blockchain.py +++ b/tests/blockchain/test_blockchain.py @@ -243,7 +243,7 @@ class TestBlockHeaderValidation: assert empty_blockchain.get_peak().height == len(blocks) - 1 @pytest.mark.asyncio - async def test_unfinished_blocks(self, empty_blockchain, bt): + async def test_unfinished_blocks(self, empty_blockchain, softfork_height, bt): blockchain = empty_blockchain blocks = bt.get_consecutive_blocks(3) for block in blocks[:-1]: @@ -264,7 +264,7 @@ class TestBlockHeaderValidation: if unf.transactions_generator is not None: block_generator: BlockGenerator = await blockchain.get_block_generator(unf) block_bytes = bytes(unf) - npc_result = await blockchain.run_generator(block_bytes, block_generator) + npc_result = await blockchain.run_generator(block_bytes, block_generator, height=softfork_height) validate_res = await blockchain.validate_unfinished_block(unf, npc_result, False) err = validate_res.error @@ -288,7 +288,7 @@ class TestBlockHeaderValidation: if unf.transactions_generator is not None: block_generator: BlockGenerator = await blockchain.get_block_generator(unf) block_bytes = bytes(unf) - npc_result = await blockchain.run_generator(block_bytes, block_generator) + npc_result = await blockchain.run_generator(block_bytes, block_generator, height=softfork_height) validate_res = await blockchain.validate_unfinished_block(unf, npc_result, False) assert validate_res.error is None @@ -333,7 +333,7 @@ class TestBlockHeaderValidation: assert blockchain.get_peak().height == num_blocks - 1 @pytest.mark.asyncio - async def test_unf_block_overflow(self, empty_blockchain, bt): + async def test_unf_block_overflow(self, empty_blockchain, softfork_height, bt): blockchain = empty_blockchain blocks = [] @@ -367,7 +367,7 @@ class TestBlockHeaderValidation: if block.transactions_generator is not None: block_generator: BlockGenerator = await blockchain.get_block_generator(unf) block_bytes = bytes(unf) - npc_result = await blockchain.run_generator(block_bytes, block_generator) + npc_result = await blockchain.run_generator(block_bytes, block_generator, height=softfork_height) validate_res = await blockchain.validate_unfinished_block( unf, npc_result, skip_overflow_ss_validation=True ) @@ -2262,7 +2262,7 @@ class TestBodyValidation: ) @pytest.mark.asyncio - async def test_cost_exceeds_max(self, empty_blockchain, bt): + async def test_cost_exceeds_max(self, empty_blockchain, softfork_height, bt): # 7 b = empty_blockchain blocks = bt.get_consecutive_blocks( @@ -2296,6 +2296,7 @@ class TestBodyValidation: b.constants.MAX_BLOCK_COST_CLVM * 1000, cost_per_byte=b.constants.COST_PER_BYTE, mempool_mode=False, + height=softfork_height, ) err = (await b.receive_block(blocks[-1], PreValidationResult(None, uint64(1), npc_result, True)))[1] assert err in [Err.BLOCK_COST_EXCEEDS_MAX] @@ -2312,7 +2313,7 @@ class TestBodyValidation: pass @pytest.mark.asyncio - async def test_invalid_cost_in_block(self, empty_blockchain, bt): + async def test_invalid_cost_in_block(self, empty_blockchain, softfork_height, bt): # 9 b = empty_blockchain blocks = bt.get_consecutive_blocks( @@ -2356,6 +2357,7 @@ class TestBodyValidation: min(b.constants.MAX_BLOCK_COST_CLVM * 1000, block.transactions_info.cost), cost_per_byte=b.constants.COST_PER_BYTE, mempool_mode=False, + height=softfork_height, ) result, err, _ = await b.receive_block(block_2, PreValidationResult(None, uint64(1), npc_result, False)) assert err == Err.INVALID_BLOCK_COST @@ -2380,6 +2382,7 @@ class TestBodyValidation: min(b.constants.MAX_BLOCK_COST_CLVM * 1000, block.transactions_info.cost), cost_per_byte=b.constants.COST_PER_BYTE, mempool_mode=False, + height=softfork_height, ) result, err, _ = await b.receive_block(block_2, PreValidationResult(None, uint64(1), npc_result, False)) assert err == Err.INVALID_BLOCK_COST @@ -2404,6 +2407,7 @@ class TestBodyValidation: min(b.constants.MAX_BLOCK_COST_CLVM * 1000, block.transactions_info.cost), cost_per_byte=b.constants.COST_PER_BYTE, mempool_mode=False, + height=softfork_height, ) result, err, _ = await b.receive_block(block_2, PreValidationResult(None, uint64(1), npc_result, False)) diff --git a/tests/blockchain/test_blockchain_transactions.py b/tests/blockchain/test_blockchain_transactions.py index e7ae61843803..743989e18e6e 100644 --- a/tests/blockchain/test_blockchain_transactions.py +++ b/tests/blockchain/test_blockchain_transactions.py @@ -267,7 +267,7 @@ 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): + async def test_validate_blockchain_spend_reorg_coin(self, two_nodes, softfork_height): num_blocks = 10 wallet_a = WALLET_A coinbase_puzzlehash = WALLET_A_PUZZLE_HASHES[0] @@ -308,6 +308,7 @@ class TestBlockchainTransactions: new_blocks[-1], test_constants.MAX_BLOCK_COST_CLVM, cost_per_byte=test_constants.COST_PER_BYTE, + height=softfork_height, )[1]: if coin.puzzle_hash == receiver_1_puzzlehash: coin_2 = coin @@ -331,6 +332,7 @@ class TestBlockchainTransactions: new_blocks[-1], test_constants.MAX_BLOCK_COST_CLVM, cost_per_byte=test_constants.COST_PER_BYTE, + height=softfork_height, )[1]: if coin.puzzle_hash == receiver_2_puzzlehash: coin_3 = coin diff --git a/tests/conftest.py b/tests/conftest.py index c6f606848f85..353b086eee7c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -132,6 +132,11 @@ def db_version(request): return request.param +@pytest.fixture(scope="function", params=[1000000, 3630000]) +def softfork_height(request): + return request.param + + saved_blocks_version = "rc5" diff --git a/tests/core/full_node/stores/test_coin_store.py b/tests/core/full_node/stores/test_coin_store.py index 0c3672a03994..ab2135c4a1d0 100644 --- a/tests/core/full_node/stores/test_coin_store.py +++ b/tests/core/full_node/stores/test_coin_store.py @@ -51,7 +51,7 @@ def get_future_reward_coins(block: FullBlock) -> Tuple[Coin, Coin]: class TestCoinStoreWithBlocks: @pytest.mark.asyncio - async def test_basic_coin_store(self, db_version, bt): + async def test_basic_coin_store(self, db_version, softfork_height, bt): wallet_a = WALLET_A reward_ph = wallet_a.get_new_puzzlehash() @@ -100,6 +100,7 @@ class TestCoinStoreWithBlocks: bt.constants.MAX_BLOCK_COST_CLVM, cost_per_byte=bt.constants.COST_PER_BYTE, mempool_mode=False, + height=softfork_height, ) tx_removals, tx_additions = tx_removals_and_additions(npc_result.conds) else: diff --git a/tests/core/mempool/test_mempool.py b/tests/core/mempool/test_mempool.py index 9ead1cc40cc8..03836ed324b2 100644 --- a/tests/core/mempool/test_mempool.py +++ b/tests/core/mempool/test_mempool.py @@ -1981,6 +1981,7 @@ def generator_condition_tester( mempool_mode: bool = False, quote: bool = True, max_cost: int = MAX_BLOCK_COST_CLVM, + height: uint32, ) -> NPCResult: prg = f"(q ((0x0101010101010101010101010101010101010101010101010101010101010101 {'(q ' if quote else ''} {conditions} {')' if quote else ''} 123 (() (q . ())))))" # noqa print(f"program: {prg}") @@ -1988,18 +1989,18 @@ 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, mempool_mode=mempool_mode + generator, max_cost, cost_per_byte=COST_PER_BYTE, mempool_mode=mempool_mode, height=height ) return npc_result class TestGeneratorConditions: - def test_invalid_condition_args_terminator(self): + def test_invalid_condition_args_terminator(self, softfork_height): # note how the condition argument list isn't correctly terminated with a # NIL atom. This is allowed, and all arguments beyond the ones we look # at are ignored, including the termination of the list - npc_result = generator_condition_tester("(80 50 . 1)") + npc_result = generator_condition_tester("(80 50 . 1)", height=softfork_height) assert npc_result.error is None assert len(npc_result.conds.spends) == 1 assert npc_result.conds.spends[0].seconds_relative == 50 @@ -2009,24 +2010,29 @@ class TestGeneratorConditions: [ (True, -1, Err.GENERATOR_RUNTIME_ERROR.value), (False, -1, Err.GENERATOR_RUNTIME_ERROR.value), + (False, -1, Err.GENERATOR_RUNTIME_ERROR.value), (True, 1, None), (False, 1, None), + (False, 1, None), ], ) - def test_div(self, mempool, operand, expected): + def test_div(self, mempool, operand, expected, softfork_height): # op_div is disallowed on negative numbers in the mempool, and after the # softfork npc_result = generator_condition_tester( - f"(c (c (q . 80) (c (/ (q . 50) (q . {operand})) ())) ())", quote=False, mempool_mode=mempool + f"(c (c (q . 80) (c (/ (q . 50) (q . {operand})) ())) ())", + quote=False, + mempool_mode=mempool, + height=softfork_height, ) assert npc_result.error == expected - def test_invalid_condition_list_terminator(self): + def test_invalid_condition_list_terminator(self, softfork_height): # note how the list of conditions isn't correctly terminated with a # NIL atom. This is a failure - npc_result = generator_condition_tester("(80 50) . 3") + npc_result = generator_condition_tester("(80 50) . 3", height=softfork_height) assert npc_result.error in [Err.INVALID_CONDITION.value, Err.GENERATOR_RUNTIME_ERROR.value] @pytest.mark.parametrize( @@ -2038,10 +2044,12 @@ class TestGeneratorConditions: ConditionOpcode.ASSERT_SECONDS_RELATIVE, ], ) - def test_duplicate_height_time_conditions(self, opcode): + def test_duplicate_height_time_conditions(self, opcode, softfork_height): # 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"({opcode.value[0]} {i})" for i in range(50, 101)])) + npc_result = generator_condition_tester( + " ".join([f"({opcode.value[0]} {i})" for i in range(50, 101)]), height=softfork_height + ) print(npc_result) assert npc_result.error is None assert len(npc_result.conds.spends) == 1 @@ -2063,11 +2071,11 @@ class TestGeneratorConditions: ConditionOpcode.CREATE_PUZZLE_ANNOUNCEMENT, ], ) - def test_just_announcement(self, opcode): + def test_just_announcement(self, opcode, softfork_height): 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'({opcode.value[0]} "{message}") ' * 50) + npc_result = generator_condition_tester(f'({opcode.value[0]} "{message}") ' * 50, height=softfork_height) assert npc_result.error is None assert len(npc_result.conds.spends) == 1 # create-announcements and assert-announcements are dropped once @@ -2080,36 +2088,36 @@ class TestGeneratorConditions: ConditionOpcode.ASSERT_PUZZLE_ANNOUNCEMENT, ], ) - def test_assert_announcement_fail(self, opcode): + def test_assert_announcement_fail(self, opcode, softfork_height): 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'({opcode.value[0]} "{message}") ') + npc_result = generator_condition_tester(f'({opcode.value[0]} "{message}") ', height=softfork_height) print(npc_result) assert npc_result.error == Err.ASSERT_ANNOUNCE_CONSUMED_FAILED.value - def test_multiple_reserve_fee(self): + def test_multiple_reserve_fee(self, softfork_height): # 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, height=softfork_height) assert npc_result.error is None assert npc_result.conds.reserve_fee == 300 assert len(npc_result.conds.spends) == 1 - def test_duplicate_outputs(self): + def test_duplicate_outputs(self, softfork_height): # 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) + npc_result = generator_condition_tester(f'(51 "{puzzle_hash}" 10) ' * 2, height=softfork_height) assert npc_result.error == Err.DUPLICATE_OUTPUT.value - def test_create_coin_cost(self): + def test_create_coin_cost(self, softfork_height): # CREATE_COIN puzzle_hash = "abababababababababababababababab" @@ -2117,6 +2125,7 @@ class TestGeneratorConditions: npc_result = generator_condition_tester( f'(51 "{puzzle_hash}" 10) ', max_cost=20470 + 95 * COST_PER_BYTE + ConditionCost.CREATE_COIN.value, + height=softfork_height, ) assert npc_result.error is None assert npc_result.cost == 20470 + 95 * COST_PER_BYTE + ConditionCost.CREATE_COIN.value @@ -2127,10 +2136,11 @@ class TestGeneratorConditions: npc_result = generator_condition_tester( f'(51 "{puzzle_hash}" 10) ', max_cost=20470 + 95 * COST_PER_BYTE + ConditionCost.CREATE_COIN.value - 1, + height=softfork_height, ) assert npc_result.error in [Err.BLOCK_COST_EXCEEDS_MAX.value, Err.INVALID_BLOCK_COST.value] - def test_agg_sig_cost(self): + def test_agg_sig_cost(self, softfork_height): # AGG_SIG_ME pubkey = "abababababababababababababababababababababababab" @@ -2138,6 +2148,7 @@ class TestGeneratorConditions: npc_result = generator_condition_tester( f'(49 "{pubkey}" "foobar") ', max_cost=20512 + 117 * COST_PER_BYTE + ConditionCost.AGG_SIG.value, + height=softfork_height, ) assert npc_result.error is None assert npc_result.cost == 20512 + 117 * COST_PER_BYTE + ConditionCost.AGG_SIG.value @@ -2147,10 +2158,11 @@ class TestGeneratorConditions: npc_result = generator_condition_tester( f'(49 "{pubkey}" "foobar") ', max_cost=20512 + 117 * COST_PER_BYTE + ConditionCost.AGG_SIG.value - 1, + height=softfork_height, ) assert npc_result.error in [Err.BLOCK_COST_EXCEEDS_MAX.value, Err.INVALID_BLOCK_COST.value] - def test_create_coin_different_parent(self): + def test_create_coin_different_parent(self, softfork_height): # if the coins we create have different parents, they are never # considered duplicate, even when they have the same puzzle hash and @@ -2163,49 +2175,60 @@ class TestGeneratorConditions: ) generator = BlockGenerator(program, [], []) npc_result: NPCResult = get_name_puzzle_conditions( - generator, MAX_BLOCK_COST_CLVM, cost_per_byte=COST_PER_BYTE, mempool_mode=False + generator, MAX_BLOCK_COST_CLVM, cost_per_byte=COST_PER_BYTE, mempool_mode=False, height=softfork_height ) assert npc_result.error is None assert len(npc_result.conds.spends) == 2 for s in npc_result.conds.spends: assert s.create_coin == [(puzzle_hash.encode("ascii"), 10, None)] - def test_create_coin_different_puzzhash(self): + def test_create_coin_different_puzzhash(self, softfork_height): # 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)', height=softfork_height + ) assert npc_result.error is None assert len(npc_result.conds.spends) == 1 assert (puzzle_hash_1.encode("ascii"), 5, None) in npc_result.conds.spends[0].create_coin assert (puzzle_hash_2.encode("ascii"), 5, None) in npc_result.conds.spends[0].create_coin - def test_create_coin_different_amounts(self): + def test_create_coin_different_amounts(self, softfork_height): # 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)', height=softfork_height + ) assert npc_result.error is None assert len(npc_result.conds.spends) == 1 coins = npc_result.conds.spends[0].create_coin assert (puzzle_hash.encode("ascii"), 5, None) in coins assert (puzzle_hash.encode("ascii"), 4, None) in coins - def test_create_coin_with_hint(self): + def test_create_coin_with_hint(self, softfork_height): # CREATE_COIN puzzle_hash_1 = "abababababababababababababababab" hint = "12341234123412341234213421341234" - npc_result = generator_condition_tester(f'(51 "{puzzle_hash_1}" 5 ("{hint}"))') + npc_result = generator_condition_tester(f'(51 "{puzzle_hash_1}" 5 ("{hint}"))', height=softfork_height) assert npc_result.error is None assert len(npc_result.conds.spends) == 1 coins = npc_result.conds.spends[0].create_coin assert coins == [(puzzle_hash_1.encode("ascii"), 5, hint.encode("ascii"))] - @pytest.mark.parametrize("mempool", [True, False, False]) - def test_unknown_condition(self, mempool: bool): + @pytest.mark.parametrize( + "mempool,height", + [ + (True, None), + (False, 2300000), + (False, 3630000), + ], + ) + def test_unknown_condition(self, mempool: bool, height: uint32): for c in ['(2 100 "foo" "bar")', "(100)", "(4 1) (2 2) (3 3)", '("foobar")']: - npc_result = generator_condition_tester(c, mempool_mode=mempool) + npc_result = generator_condition_tester(c, mempool_mode=mempool, height=height) print(npc_result) if mempool: assert npc_result.error == Err.INVALID_CONDITION.value @@ -2340,11 +2363,11 @@ class TestMaliciousGenerators: ], ) @pytest.mark.benchmark - def test_duplicate_large_integer_ladder(self, request, opcode): + def test_duplicate_large_integer_ladder(self, request, opcode, softfork_height): condition = SINGLE_ARG_INT_LADDER_COND.format(opcode=opcode.value[0], num=28, filler="0x00") with assert_runtime(seconds=0.7, label=request.node.name): - npc_result = generator_condition_tester(condition, quote=False) + npc_result = generator_condition_tester(condition, quote=False, height=softfork_height) assert npc_result.error == error_for_condition(opcode) @@ -2358,11 +2381,11 @@ class TestMaliciousGenerators: ], ) @pytest.mark.benchmark - def test_duplicate_large_integer(self, request, opcode): + def test_duplicate_large_integer(self, request, opcode, softfork_height): condition = SINGLE_ARG_INT_COND.format(opcode=opcode.value[0], num=280000, val=100, filler="0x00") with assert_runtime(seconds=1.1, label=request.node.name): - npc_result = generator_condition_tester(condition, quote=False) + npc_result = generator_condition_tester(condition, quote=False, height=softfork_height) assert npc_result.error == error_for_condition(opcode) @@ -2376,11 +2399,11 @@ class TestMaliciousGenerators: ], ) @pytest.mark.benchmark - def test_duplicate_large_integer_substr(self, request, opcode): + def test_duplicate_large_integer_substr(self, request, opcode, softfork_height): condition = SINGLE_ARG_INT_SUBSTR_COND.format(opcode=opcode.value[0], num=280000, val=100, filler="0x00") with assert_runtime(seconds=1.1, label=request.node.name): - npc_result = generator_condition_tester(condition, quote=False) + npc_result = generator_condition_tester(condition, quote=False, height=softfork_height) assert npc_result.error == error_for_condition(opcode) @@ -2394,13 +2417,13 @@ class TestMaliciousGenerators: ], ) @pytest.mark.benchmark - def test_duplicate_large_integer_substr_tail(self, request, opcode): + def test_duplicate_large_integer_substr_tail(self, request, opcode, softfork_height): condition = SINGLE_ARG_INT_SUBSTR_TAIL_COND.format( opcode=opcode.value[0], num=280, val="0xffffffff", filler="0x00" ) with assert_runtime(seconds=0.3, label=request.node.name): - npc_result = generator_condition_tester(condition, quote=False) + npc_result = generator_condition_tester(condition, quote=False, height=softfork_height) assert npc_result.error == error_for_condition(opcode) @@ -2414,32 +2437,32 @@ class TestMaliciousGenerators: ], ) @pytest.mark.benchmark - def test_duplicate_large_integer_negative(self, request, opcode): + def test_duplicate_large_integer_negative(self, request, opcode, softfork_height): condition = SINGLE_ARG_INT_COND.format(opcode=opcode.value[0], num=280000, val=100, filler="0xff") with assert_runtime(seconds=1, label=request.node.name): - npc_result = generator_condition_tester(condition, quote=False) + npc_result = generator_condition_tester(condition, quote=False, height=softfork_height) assert npc_result.error is None assert len(npc_result.conds.spends) == 1 @pytest.mark.benchmark - def test_duplicate_reserve_fee(self, request): + def test_duplicate_reserve_fee(self, request, softfork_height): opcode = ConditionOpcode.RESERVE_FEE condition = SINGLE_ARG_INT_COND.format(opcode=opcode.value[0], num=280000, val=100, filler="0x00") with assert_runtime(seconds=1, label=request.node.name): - npc_result = generator_condition_tester(condition, quote=False) + npc_result = generator_condition_tester(condition, quote=False, height=softfork_height) assert npc_result.error == error_for_condition(opcode) @pytest.mark.benchmark - def test_duplicate_reserve_fee_negative(self, request: pytest.FixtureRequest): + def test_duplicate_reserve_fee_negative(self, request: pytest.FixtureRequest, softfork_height): opcode = ConditionOpcode.RESERVE_FEE condition = SINGLE_ARG_INT_COND.format(opcode=opcode.value[0], num=200000, val=100, filler="0xff") with assert_runtime(seconds=0.8, label=request.node.name): - npc_result = generator_condition_tester(condition, quote=False) + npc_result = generator_condition_tester(condition, quote=False, height=softfork_height) # RESERVE_FEE conditions fail unconditionally if they have a negative # amount @@ -2450,11 +2473,11 @@ class TestMaliciousGenerators: "opcode", [ConditionOpcode.CREATE_COIN_ANNOUNCEMENT, ConditionOpcode.CREATE_PUZZLE_ANNOUNCEMENT] ) @pytest.mark.benchmark - def test_duplicate_coin_announces(self, request, opcode): + def test_duplicate_coin_announces(self, request, opcode, softfork_height): condition = CREATE_ANNOUNCE_COND.format(opcode=opcode.value[0], num=5950000) with assert_runtime(seconds=9, label=request.node.name): - npc_result = generator_condition_tester(condition, quote=False) + npc_result = generator_condition_tester(condition, quote=False, height=softfork_height) assert npc_result.error is None assert len(npc_result.conds.spends) == 1 @@ -2462,7 +2485,7 @@ class TestMaliciousGenerators: # TODO: optimize clvm to make this run in < 1 second @pytest.mark.benchmark - def test_create_coin_duplicates(self, request: pytest.FixtureRequest): + def test_create_coin_duplicates(self, request: pytest.FixtureRequest, softfork_height): # CREATE_COIN # this program will emit 6000 identical CREATE_COIN conditions. However, # we'll just end up looking at two of them, and fail at the first @@ -2470,13 +2493,13 @@ class TestMaliciousGenerators: condition = CREATE_COIN.format(num=600000) with assert_runtime(seconds=0.8, label=request.node.name): - npc_result = generator_condition_tester(condition, quote=False) + npc_result = generator_condition_tester(condition, quote=False, height=softfork_height) assert npc_result.error == Err.DUPLICATE_OUTPUT.value assert npc_result.conds is None @pytest.mark.benchmark - def test_many_create_coin(self, request): + def test_many_create_coin(self, request, softfork_height): # CREATE_COIN # this program will emit many CREATE_COIN conditions, all with different # amounts. @@ -2484,7 +2507,7 @@ class TestMaliciousGenerators: condition = CREATE_UNIQUE_COINS.format(num=6094) with assert_runtime(seconds=0.3, label=request.node.name): - npc_result = generator_condition_tester(condition, quote=False) + npc_result = generator_condition_tester(condition, quote=False, height=softfork_height) assert npc_result.error is None assert len(npc_result.conds.spends) == 1 diff --git a/tests/core/test_cost_calculation.py b/tests/core/test_cost_calculation.py index 647700c89503..ab74ac8ecbf3 100644 --- a/tests/core/test_cost_calculation.py +++ b/tests/core/test_cost_calculation.py @@ -54,7 +54,7 @@ def large_block_generator(size): class TestCostCalculation: @pytest.mark.asyncio - async def test_basics(self, bt): + async def test_basics(self, softfork_height, bt): wallet_tool = bt.get_pool_wallet_tool() ph = wallet_tool.get_new_puzzlehash() num_blocks = 3 @@ -80,6 +80,7 @@ class TestCostCalculation: test_constants.MAX_BLOCK_COST_CLVM, cost_per_byte=test_constants.COST_PER_BYTE, mempool_mode=False, + height=softfork_height, ) assert npc_result.error is None @@ -113,7 +114,7 @@ class TestCostCalculation: ) @pytest.mark.asyncio - async def test_mempool_mode(self, bt): + async def test_mempool_mode(self, softfork_height, bt): wallet_tool = bt.get_pool_wallet_tool() ph = wallet_tool.get_new_puzzlehash() @@ -152,6 +153,7 @@ class TestCostCalculation: test_constants.MAX_BLOCK_COST_CLVM, cost_per_byte=test_constants.COST_PER_BYTE, mempool_mode=True, + height=softfork_height, ) assert npc_result.error is not None npc_result = get_name_puzzle_conditions( @@ -159,6 +161,7 @@ class TestCostCalculation: test_constants.MAX_BLOCK_COST_CLVM, cost_per_byte=test_constants.COST_PER_BYTE, mempool_mode=False, + height=softfork_height, ) assert npc_result.error is None @@ -171,7 +174,7 @@ class TestCostCalculation: assert error is None @pytest.mark.asyncio - async def test_clvm_mempool_mode(self): + async def test_clvm_mempool_mode(self, softfork_height): block = Program.from_bytes(bytes(SMALL_BLOCK_GENERATOR.program)) disassembly = binutils.disassemble(block) # this is a valid generator program except the first clvm @@ -192,12 +195,13 @@ class TestCostCalculation: test_constants.MAX_BLOCK_COST_CLVM, cost_per_byte=test_constants.COST_PER_BYTE, mempool_mode=False, + height=softfork_height, ) assert npc_result.error is None @pytest.mark.asyncio @pytest.mark.benchmark - async def test_tx_generator_speed(self, request): + async def test_tx_generator_speed(self, request, softfork_height): LARGE_BLOCK_COIN_CONSUMED_COUNT = 687 generator_bytes = large_block_generator(LARGE_BLOCK_COIN_CONSUMED_COUNT) program = SerializedProgram.from_bytes(generator_bytes) @@ -209,13 +213,14 @@ class TestCostCalculation: test_constants.MAX_BLOCK_COST_CLVM, cost_per_byte=test_constants.COST_PER_BYTE, mempool_mode=False, + height=softfork_height, ) assert npc_result.error is None assert len(npc_result.conds.spends) == LARGE_BLOCK_COIN_CONSUMED_COUNT @pytest.mark.asyncio - async def test_clvm_max_cost(self): + async def test_clvm_max_cost(self, softfork_height): block = Program.from_bytes(bytes(SMALL_BLOCK_GENERATOR.program)) disassembly = binutils.disassemble(block) @@ -235,6 +240,7 @@ class TestCostCalculation: 10000000, cost_per_byte=0, mempool_mode=False, + height=softfork_height, ) assert npc_result.error is not None @@ -242,7 +248,9 @@ class TestCostCalculation: # raise the max cost to make sure this passes # ensure we pass if the program does not exceeds the cost - npc_result = get_name_puzzle_conditions(generator, 23000000, cost_per_byte=0, mempool_mode=False) + npc_result = get_name_puzzle_conditions( + generator, 23000000, cost_per_byte=0, mempool_mode=False, height=softfork_height + ) assert npc_result.error is None assert npc_result.cost > 10000000 diff --git a/tests/generator/test_rom.py b/tests/generator/test_rom.py index 0e7259106854..f388dc3ed79e 100644 --- a/tests/generator/test_rom.py +++ b/tests/generator/test_rom.py @@ -94,14 +94,16 @@ class TestROM: assert cost == EXPECTED_ABBREVIATED_COST assert r.as_bin().hex() == EXPECTED_OUTPUT - def test_get_name_puzzle_conditions(self): + def test_get_name_puzzle_conditions(self, softfork_height): # this tests that extra block or coin data doesn't confuse `get_name_puzzle_conditions` gen = block_generator() cost, r = run_generator_unsafe(gen, max_cost=MAX_COST) print(r) - npc_result = get_name_puzzle_conditions(gen, max_cost=MAX_COST, cost_per_byte=COST_PER_BYTE, mempool_mode=False) + npc_result = get_name_puzzle_conditions( + gen, max_cost=MAX_COST, cost_per_byte=COST_PER_BYTE, mempool_mode=False, height=softfork_height + ) assert npc_result.error is None assert npc_result.cost == EXPECTED_COST + ConditionCost.CREATE_COIN.value + ( len(bytes(gen.program)) * COST_PER_BYTE diff --git a/tests/util/generator_tools_testing.py b/tests/util/generator_tools_testing.py index 9fecf6969a8c..18ee01538794 100644 --- a/tests/util/generator_tools_testing.py +++ b/tests/util/generator_tools_testing.py @@ -8,10 +8,11 @@ from chia.types.blockchain_format.sized_bytes import bytes32 from chia.types.full_block import FullBlock from chia.types.generator_types import BlockGenerator from chia.util.generator_tools import tx_removals_and_additions +from chia.util.ints import uint32 def run_and_get_removals_and_additions( - block: FullBlock, max_cost: int, *, cost_per_byte: int, mempool_mode=False + block: FullBlock, max_cost: int, *, cost_per_byte: int, height: uint32, mempool_mode=False ) -> Tuple[List[bytes32], List[Coin]]: removals: List[bytes32] = [] additions: List[Coin] = [] @@ -26,6 +27,7 @@ def run_and_get_removals_and_additions( max_cost, cost_per_byte=cost_per_byte, mempool_mode=mempool_mode, + height=height, ) assert npc_result.error is None rem, add = tx_removals_and_additions(npc_result.conds)