From b8ada1ceb760eb83acddc964dfea69469d0feffc Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Fri, 28 Jan 2022 21:29:11 +0100 Subject: [PATCH] introduce restrictions on generators at a specific height (#9957) * introduce restrictions on generators at a specific height. disallow division on negative numbers and disallow redundant leading zeros on integer condition arguments (produced by a generator) * use SOFT_FORK_HEIGHT constant * there is no need to specify height when validating block in block_creation * Update tests/core/full_node/test_mempool.py Co-authored-by: dustinface <35775977+xdustinface@users.noreply.github.com> Co-authored-by: dustinface <35775977+xdustinface@users.noreply.github.com> --- chia/consensus/block_body_validation.py | 1 + chia/consensus/blockchain.py | 4 +- chia/consensus/constants.py | 1 + chia/consensus/default_constants.py | 1 + chia/consensus/multiprocess_validation.py | 3 + chia/full_node/full_node.py | 3 +- chia/full_node/mempool_check_conditions.py | 29 +- chia/types/blockchain_format/program.py | 2 +- chia/wallet/util/compute_hints.py | 6 +- setup.py | 2 +- tests/blockchain/test_blockchain.py | 18 +- .../test_blockchain_transactions.py | 12 +- tests/conftest.py | 5 + tests/core/full_node/test_coin_store.py | 3 +- tests/core/full_node/test_mempool.py | 252 +++++++++++------- tests/core/test_cost_calculation.py | 27 +- tests/generator/test_rom.py | 6 +- tests/util/generator_tools_testing.py | 4 +- tools/run_block.py | 30 ++- 19 files changed, 283 insertions(+), 126 deletions(-) diff --git a/chia/consensus/block_body_validation.py b/chia/consensus/block_body_validation.py index cb42728006..4c725fbbd1 100644 --- a/chia/consensus/block_body_validation.py +++ b/chia/consensus/block_body_validation.py @@ -320,6 +320,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.npc_list) else: diff --git a/chia/consensus/blockchain.py b/chia/consensus/blockchain.py index b8fd126925..9b49142979 100644 --- a/chia/consensus/blockchain.py +++ b/chia/consensus/blockchain.py @@ -455,6 +455,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.npc_list) return tx_removals, tx_additions, npc_result @@ -634,13 +635,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_json, 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 74c3a2f5d4..e13f4e4910 100644 --- a/chia/consensus/constants.py +++ b/chia/consensus/constants.py @@ -59,6 +59,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) -> "ConsensusConstants": return dataclasses.replace(self, **changes) diff --git a/chia/consensus/default_constants.py b/chia/consensus/default_constants.py index 6f84c8a8ac..be9029ba1f 100644 --- a/chia/consensus/default_constants.py +++ b/chia/consensus/default_constants.py @@ -54,6 +54,7 @@ testnet_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": 2000000, } diff --git a/chia/consensus/multiprocess_validation.py b/chia/consensus/multiprocess_validation.py index 5e95306e9c..ba19fd1e4c 100644 --- a/chia/consensus/multiprocess_validation.py +++ b/chia/consensus/multiprocess_validation.py @@ -90,6 +90,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.npc_list) if npc_result is not None and npc_result.error is not None: @@ -370,6 +371,7 @@ def _run_generator( constants_dict: bytes, 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 @@ -386,6 +388,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 08694c3055..56e8678457 100644 --- a/chia/full_node/full_node.py +++ b/chia/full_node/full_node.py @@ -1675,7 +1675,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 pairs_pks, pairs_msgs = pkm_pairs(npc_result.npc_list, self.constants.AGG_SIG_ME_ADDITIONAL_DATA) diff --git a/chia/full_node/mempool_check_conditions.py b/chia/full_node/mempool_check_conditions.py index 787a59b31b..a5403f9120 100644 --- a/chia/full_node/mempool_check_conditions.py +++ b/chia/full_node/mempool_check_conditions.py @@ -1,10 +1,11 @@ import logging import time from typing import Dict, List, Optional -from clvm_rs import STRICT_MODE as MEMPOOL_MODE +from clvm_rs import MEMPOOL_MODE, COND_CANON_INTS, NO_NEG_DIV from clvm.casts import int_from_bytes, int_to_bytes from chia.consensus.cost_calculator import NPCResult +from chia.consensus.default_constants import DEFAULT_CONSTANTS from chia.full_node.generator import create_generator_args, setup_generator_args from chia.types.blockchain_format.program import NIL from chia.types.coin_record import CoinRecord @@ -107,8 +108,13 @@ def add_cond( conds[op].append(ConditionWithArgs(op, args)) +def unwrap(x: Optional[uint32]) -> uint32: + assert x is not None + return x + + 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 @@ -116,7 +122,24 @@ def get_name_puzzle_conditions( if max_cost < 0: return NPCResult(uint16(Err.INVALID_BLOCK_COST.value), [], uint64(0)) - flags = MEMPOOL_MODE if mempool_mode else 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 + + # mempool mode also has these rules apply + assert (MEMPOOL_MODE & COND_CANON_INTS) != 0 + assert (MEMPOOL_MODE & NO_NEG_DIV) != 0 + + if mempool_mode: + flags = MEMPOOL_MODE + elif unwrap(height) >= DEFAULT_CONSTANTS.SOFT_FORK_HEIGHT: + # conditions must use integers in canonical encoding (i.e. no redundant + # leading zeros) + # the division operator may not be used with negative operands + flags = COND_CANON_INTS | NO_NEG_DIV + else: + flags = 0 + try: err, result = GENERATOR_MOD.run_as_generator(max_cost, flags, block_program, block_program_args) diff --git a/chia/types/blockchain_format/program.py b/chia/types/blockchain_format/program.py index 1a1c0d5b41..52c06ef0e1 100644 --- a/chia/types/blockchain_format/program.py +++ b/chia/types/blockchain_format/program.py @@ -7,7 +7,7 @@ from clvm.casts import int_from_bytes from clvm.EvalError import EvalError from clvm.operators import OPERATOR_LOOKUP from clvm.serialize import sexp_from_stream, sexp_to_stream -from clvm_rs import STRICT_MODE as MEMPOOL_MODE, run_chia_program, serialized_length, run_generator2 +from clvm_rs import MEMPOOL_MODE, run_chia_program, serialized_length, run_generator2 from clvm_tools.curry import curry, uncurry from chia.types.blockchain_format.sized_bytes import bytes32 diff --git a/chia/wallet/util/compute_hints.py b/chia/wallet/util/compute_hints.py index 678e28faaf..b6d924bbdc 100644 --- a/chia/wallet/util/compute_hints.py +++ b/chia/wallet/util/compute_hints.py @@ -17,7 +17,11 @@ def compute_coin_hints(cs: CoinSpend) -> List[bytes]: generator = simple_solution_generator(bundle) npc_result = get_name_puzzle_conditions( - generator, INFINITE_COST, cost_per_byte=DEFAULT_CONSTANTS.COST_PER_BYTE, mempool_mode=False + generator, + INFINITE_COST, + cost_per_byte=DEFAULT_CONSTANTS.COST_PER_BYTE, + mempool_mode=False, + height=DEFAULT_CONSTANTS.SOFT_FORK_HEIGHT, ) h_list = [] for npc in npc_result.npc_list: diff --git a/setup.py b/setup.py index 17c1e86248..35ff8467e5 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ dependencies = [ "chiabip158==1.1", # bip158-style wallet filters "chiapos==1.0.8", # proof of space "clvm==0.9.7", - "clvm_rs==0.1.17", + "clvm_rs==0.1.19", "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/blockchain/test_blockchain.py b/tests/blockchain/test_blockchain.py index 8cfa3c21c4..aeaf9ec993 100644 --- a/tests/blockchain/test_blockchain.py +++ b/tests/blockchain/test_blockchain.py @@ -251,7 +251,7 @@ class TestBlockHeaderValidation: assert empty_blockchain.get_peak().height == len(blocks) - 1 @pytest.mark.asyncio - async def test_unfinished_blocks(self, empty_blockchain): + async def test_unfinished_blocks(self, empty_blockchain, softfork_height): blockchain = empty_blockchain blocks = bt.get_consecutive_blocks(3) for block in blocks[:-1]: @@ -272,7 +272,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 @@ -296,7 +296,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 @@ -342,7 +342,7 @@ class TestBlockHeaderValidation: assert blockchain.get_peak().height == num_blocks - 1 @pytest.mark.asyncio - async def test_unf_block_overflow(self, empty_blockchain): + async def test_unf_block_overflow(self, empty_blockchain, softfork_height): blockchain = empty_blockchain blocks = [] @@ -376,7 +376,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 ) @@ -2165,7 +2165,7 @@ class TestBodyValidation: ) @pytest.mark.asyncio - async def test_cost_exceeds_max(self, empty_blockchain): + async def test_cost_exceeds_max(self, empty_blockchain, softfork_height): # 7 b = empty_blockchain blocks = bt.get_consecutive_blocks( @@ -2199,6 +2199,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] @@ -2215,7 +2216,7 @@ class TestBodyValidation: pass @pytest.mark.asyncio - async def test_invalid_cost_in_block(self, empty_blockchain): + async def test_invalid_cost_in_block(self, empty_blockchain, softfork_height): # 9 b = empty_blockchain blocks = bt.get_consecutive_blocks( @@ -2259,6 +2260,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 @@ -2283,6 +2285,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 @@ -2307,6 +2310,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 4c81337567..b8c26ed4f7 100644 --- a/tests/blockchain/test_blockchain_transactions.py +++ b/tests/blockchain/test_blockchain_transactions.py @@ -281,7 +281,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] @@ -320,7 +320,10 @@ 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, + cost_per_byte=test_constants.COST_PER_BYTE, + height=softfork_height, )[1]: if coin.puzzle_hash == receiver_1_puzzlehash: coin_2 = coin @@ -341,7 +344,10 @@ 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, + 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 057cfd89be..620d35aa41 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -35,6 +35,11 @@ def db_version(request): return request.param +@pytest.fixture(scope="function", params=[1000000, 2000000]) +def softfork_height(request): + return request.param + + block_format_version = "rc4" diff --git a/tests/core/full_node/test_coin_store.py b/tests/core/full_node/test_coin_store.py index 77649cb83b..2f48cf7acb 100644 --- a/tests/core/full_node/test_coin_store.py +++ b/tests/core/full_node/test_coin_store.py @@ -58,7 +58,7 @@ def get_future_reward_coins(block: FullBlock) -> Tuple[Coin, Coin]: class TestCoinStoreWithBlocks: @pytest.mark.asyncio @pytest.mark.parametrize("cache_size", [0]) - async def test_basic_coin_store(self, cache_size: uint32, db_version): + async def test_basic_coin_store(self, cache_size: uint32, db_version, softfork_height): wallet_a = WALLET_A reward_ph = wallet_a.get_new_puzzlehash() @@ -107,6 +107,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.npc_list) else: diff --git a/tests/core/full_node/test_mempool.py b/tests/core/full_node/test_mempool.py index ae93e4f008..706b7accf5 100644 --- a/tests/core/full_node/test_mempool.py +++ b/tests/core/full_node/test_mempool.py @@ -26,7 +26,7 @@ from chia.types.mempool_item import MempoolItem from chia.util.clvm import int_to_bytes from chia.util.condition_tools import conditions_for_solution, pkm_pairs from chia.util.errors import Err -from chia.util.ints import uint64 +from chia.util.ints import uint64, uint32 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 @@ -1762,6 +1762,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}") @@ -1769,18 +1770,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.npc_list) == 1 opcode = ConditionOpcode(bytes([80])) @@ -1790,11 +1791,31 @@ class TestGeneratorConditions: c = npc_result.npc_list[0].conditions[0][1][0] assert c == ConditionWithArgs(opcode=ConditionOpcode.ASSERT_SECONDS_RELATIVE, vars=[bytes([50])]) - def test_invalid_condition_list_terminator(self): + @pytest.mark.parametrize( + "mempool,height,operand,expected", + [ + (True, 0, -1, Err.GENERATOR_RUNTIME_ERROR.value), + (False, 1000000, -1, None), + (False, 2000000, -1, Err.GENERATOR_RUNTIME_ERROR.value), + (True, 0, 1, None), + (False, 1000000, 1, None), + (False, 2000000, 1, None), + ], + ) + def test_div(self, mempool, height, operand, expected): + + # 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, height=height + ) + assert npc_result.error == expected + + 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( @@ -1806,10 +1827,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.npc_list) == 1 @@ -1827,11 +1850,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.npc_list) == 1 # create-announcements and assert-announcements are dropped once @@ -1845,23 +1868,23 @@ 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 assert npc_result.npc_list == [] - 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 len(npc_result.npc_list) == 1 opcode = ConditionOpcode(bytes([cond])) @@ -1875,23 +1898,25 @@ class TestGeneratorConditions: assert reserve_fee == 300 assert len(npc_result.npc_list[0].conditions[0][1]) == 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 assert npc_result.npc_list == [] - def test_create_coin_cost(self): + def test_create_coin_cost(self, softfork_height): # 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 + ConditionCost.CREATE_COIN.value + 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 @@ -1899,17 +1924,21 @@ 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 + ConditionCost.CREATE_COIN.value - 1 + 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" # 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 + ConditionCost.AGG_SIG.value + 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 @@ -1917,11 +1946,13 @@ 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 + ConditionCost.AGG_SIG.value - 1 + 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 @@ -1934,7 +1965,7 @@ 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.npc_list) == 2 @@ -1947,12 +1978,14 @@ class TestGeneratorConditions: ) ] - 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.npc_list) == 1 opcode = ConditionOpcode.CREATE_COIN @@ -1965,11 +1998,13 @@ class TestGeneratorConditions: in npc_result.npc_list[0].conditions[0][1] ) - 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.npc_list) == 1 opcode = ConditionOpcode.CREATE_COIN @@ -1982,11 +2017,11 @@ class TestGeneratorConditions: in npc_result.npc_list[0].conditions[0][1] ) - 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.npc_list) == 1 opcode = ConditionOpcode.CREATE_COIN @@ -1995,14 +2030,18 @@ class TestGeneratorConditions: ) @pytest.mark.parametrize( - "mempool_mode", - [True, False], + "mempool,height", + [ + (True, None), + (False, 1000000), + (False, 2000000), + ], ) - def test_unknown_condition(self, mempool_mode): + def test_unknown_condition(self, mempool, height): for c in ['(1 100 "foo" "bar")', "(100)", "(1 1) (2 2) (3 3)", '("foobar")']: - npc_result = generator_condition_tester(c, mempool_mode=mempool_mode) + npc_result = generator_condition_tester(c, mempool_mode=mempool, height=height) print(npc_result) - if mempool_mode: + if mempool: assert npc_result.error == Err.INVALID_CONDITION.value assert npc_result.npc_list == [] else: @@ -2104,6 +2143,22 @@ CREATE_COIN = '(a (q 2 2 (c 2 (c (q 51 "abababababababababababababababab" 1) (c CREATE_UNIQUE_COINS = '(a (q 2 6 (c 2 (c (q 51 "abababababababababababababababab") (c 5 ())))) (c (q (a (i 5 (q 4 9 (a 4 (c 2 (c 13 (c 11 ()))))) (q 4 11 ())) 1) 2 (i 11 (q 4 (a 4 (c 2 (c 5 (c 11 ())))) (a 6 (c 2 (c 5 (c (- 11 (q . 1)) ()))))) ()) 1) (q {num})))' # noqa +# some of the malicious tests will fail post soft-fork, this function helps test +# the specific error to expect +def error_for_condition(cond: ConditionOpcode) -> int: + if cond == ConditionOpcode.ASSERT_HEIGHT_ABSOLUTE: + return Err.ASSERT_HEIGHT_ABSOLUTE_FAILED.value + if cond == ConditionOpcode.ASSERT_HEIGHT_RELATIVE: + return Err.ASSERT_HEIGHT_RELATIVE_FAILED.value + if cond == ConditionOpcode.ASSERT_SECONDS_ABSOLUTE: + return Err.ASSERT_SECONDS_ABSOLUTE_FAILED.value + if cond == ConditionOpcode.ASSERT_SECONDS_RELATIVE: + return Err.ASSERT_SECONDS_RELATIVE_FAILED.value + if cond == ConditionOpcode.RESERVE_FEE: + return Err.RESERVE_FEE_CONDITION_FAILED.value + assert False + + class TestMaliciousGenerators: # TODO: create a lot of announcements. The messages can be made different by @@ -2120,19 +2175,22 @@ class TestMaliciousGenerators: ConditionOpcode.ASSERT_SECONDS_RELATIVE, ], ) - def test_duplicate_large_integer_ladder(self, opcode): + def test_duplicate_large_integer_ladder(self, opcode, softfork_height): condition = SINGLE_ARG_INT_LADDER_COND.format(opcode=opcode.value[0], num=28, filler="0x00") start_time = time() - npc_result = generator_condition_tester(condition, quote=False) + npc_result = generator_condition_tester(condition, quote=False, height=softfork_height) run_time = time() - start_time - assert npc_result.error is None - assert len(npc_result.npc_list) == 1 - assert npc_result.npc_list[0].conditions == [ - ( - opcode, - [ConditionWithArgs(opcode, [int_to_bytes(28)])], - ) - ] + if softfork_height >= 2000000: + assert npc_result.error == error_for_condition(opcode) + else: + assert npc_result.error is None + assert len(npc_result.npc_list) == 1 + assert npc_result.npc_list[0].conditions == [ + ( + opcode, + [ConditionWithArgs(opcode, [int_to_bytes(28)])], + ) + ] assert run_time < 1.5 print(f"run time:{run_time}") @@ -2145,19 +2203,22 @@ class TestMaliciousGenerators: ConditionOpcode.ASSERT_SECONDS_RELATIVE, ], ) - def test_duplicate_large_integer(self, opcode): + def test_duplicate_large_integer(self, opcode, softfork_height): condition = SINGLE_ARG_INT_COND.format(opcode=opcode.value[0], num=280000, val=100, filler="0x00") start_time = time() - npc_result = generator_condition_tester(condition, quote=False) + npc_result = generator_condition_tester(condition, quote=False, height=softfork_height) run_time = time() - start_time - assert npc_result.error is None - assert len(npc_result.npc_list) == 1 - assert npc_result.npc_list[0].conditions == [ - ( - opcode, - [ConditionWithArgs(opcode, [bytes([100])])], - ) - ] + if softfork_height >= 2000000: + assert npc_result.error == error_for_condition(opcode) + else: + assert npc_result.error is None + assert len(npc_result.npc_list) == 1 + assert npc_result.npc_list[0].conditions == [ + ( + opcode, + [ConditionWithArgs(opcode, [bytes([100])])], + ) + ] assert run_time < 2.5 print(f"run time:{run_time}") @@ -2170,19 +2231,22 @@ class TestMaliciousGenerators: ConditionOpcode.ASSERT_SECONDS_RELATIVE, ], ) - def test_duplicate_large_integer_substr(self, opcode): + def test_duplicate_large_integer_substr(self, opcode, softfork_height): condition = SINGLE_ARG_INT_SUBSTR_COND.format(opcode=opcode.value[0], num=280000, val=100, filler="0x00") start_time = time() - npc_result = generator_condition_tester(condition, quote=False) + npc_result = generator_condition_tester(condition, quote=False, height=softfork_height) run_time = time() - start_time - assert npc_result.error is None - assert len(npc_result.npc_list) == 1 - assert npc_result.npc_list[0].conditions == [ - ( - opcode, - [ConditionWithArgs(opcode, [bytes([100])])], - ) - ] + if softfork_height >= 2000000: + assert npc_result.error == error_for_condition(opcode) + else: + assert npc_result.error is None + assert len(npc_result.npc_list) == 1 + assert npc_result.npc_list[0].conditions == [ + ( + opcode, + [ConditionWithArgs(opcode, [bytes([100])])], + ) + ] assert run_time < 3 print(f"run time:{run_time}") @@ -2195,18 +2259,21 @@ class TestMaliciousGenerators: ConditionOpcode.ASSERT_SECONDS_RELATIVE, ], ) - def test_duplicate_large_integer_substr_tail(self, opcode): + def test_duplicate_large_integer_substr_tail(self, opcode, softfork_height): condition = SINGLE_ARG_INT_SUBSTR_TAIL_COND.format( opcode=opcode.value[0], num=280, val="0xffffffff", filler="0x00" ) start_time = time() - npc_result = generator_condition_tester(condition, quote=False) + npc_result = generator_condition_tester(condition, quote=False, height=softfork_height) run_time = time() - start_time - assert npc_result.error is None - assert len(npc_result.npc_list) == 1 + if softfork_height >= 2000000: + assert npc_result.error == error_for_condition(opcode) + else: + assert npc_result.error is None + assert len(npc_result.npc_list) == 1 - print(npc_result.npc_list[0].conditions[0][1]) - assert ConditionWithArgs(opcode, [int_to_bytes(0xFFFFFFFF)]) in npc_result.npc_list[0].conditions[0][1] + print(npc_result.npc_list[0].conditions[0][1]) + assert ConditionWithArgs(opcode, [int_to_bytes(0xFFFFFFFF)]) in npc_result.npc_list[0].conditions[0][1] assert run_time < 1 print(f"run time:{run_time}") @@ -2219,10 +2286,10 @@ class TestMaliciousGenerators: ConditionOpcode.ASSERT_SECONDS_RELATIVE, ], ) - def test_duplicate_large_integer_negative(self, opcode): + def test_duplicate_large_integer_negative(self, opcode, softfork_height): condition = SINGLE_ARG_INT_COND.format(opcode=opcode.value[0], num=280000, val=100, filler="0xff") start_time = time() - npc_result = generator_condition_tester(condition, quote=False) + npc_result = generator_condition_tester(condition, quote=False, height=softfork_height) run_time = time() - start_time assert npc_result.error is None assert len(npc_result.npc_list) == 1 @@ -2230,28 +2297,31 @@ class TestMaliciousGenerators: assert run_time < 2 print(f"run time:{run_time}") - def test_duplicate_reserve_fee(self): + def test_duplicate_reserve_fee(self, softfork_height): opcode = ConditionOpcode.RESERVE_FEE condition = SINGLE_ARG_INT_COND.format(opcode=opcode.value[0], num=280000, val=100, filler="0x00") start_time = time() - npc_result = generator_condition_tester(condition, quote=False) + npc_result = generator_condition_tester(condition, quote=False, height=softfork_height) run_time = time() - start_time - assert npc_result.error is None - assert len(npc_result.npc_list) == 1 - assert npc_result.npc_list[0].conditions == [ - ( - opcode.value, - [ConditionWithArgs(opcode, [int_to_bytes(100 * 280000)])], - ) - ] + if softfork_height >= 2000000: + assert npc_result.error == error_for_condition(opcode) + else: + assert npc_result.error is None + assert len(npc_result.npc_list) == 1 + assert npc_result.npc_list[0].conditions == [ + ( + opcode.value, + [ConditionWithArgs(opcode, [int_to_bytes(100 * 280000)])], + ) + ] assert run_time < 2 print(f"run time:{run_time}") - def test_duplicate_reserve_fee_negative(self): + def test_duplicate_reserve_fee_negative(self, softfork_height): opcode = ConditionOpcode.RESERVE_FEE condition = SINGLE_ARG_INT_COND.format(opcode=opcode.value[0], num=200000, val=100, filler="0xff") start_time = time() - npc_result = generator_condition_tester(condition, quote=False) + npc_result = generator_condition_tester(condition, quote=False, height=softfork_height) run_time = time() - start_time # RESERVE_FEE conditions fail unconditionally if they have a negative # amount @@ -2263,10 +2333,10 @@ class TestMaliciousGenerators: @pytest.mark.parametrize( "opcode", [ConditionOpcode.CREATE_COIN_ANNOUNCEMENT, ConditionOpcode.CREATE_PUZZLE_ANNOUNCEMENT] ) - def test_duplicate_coin_announces(self, opcode): + def test_duplicate_coin_announces(self, opcode, softfork_height): condition = CREATE_ANNOUNCE_COND.format(opcode=opcode.value[0], num=5950000) start_time = time() - npc_result = generator_condition_tester(condition, quote=False) + npc_result = generator_condition_tester(condition, quote=False, height=softfork_height) run_time = time() - start_time assert npc_result.error is None assert len(npc_result.npc_list) == 1 @@ -2276,28 +2346,28 @@ class TestMaliciousGenerators: assert run_time < 21 print(f"run time:{run_time}") - def test_create_coin_duplicates(self): + def test_create_coin_duplicates(self, 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 # duplicate condition = CREATE_COIN.format(num=600000) start_time = time() - npc_result = generator_condition_tester(condition, quote=False) + npc_result = generator_condition_tester(condition, quote=False, height=softfork_height) run_time = time() - start_time assert npc_result.error == Err.DUPLICATE_OUTPUT.value assert len(npc_result.npc_list) == 0 assert run_time < 2 print(f"run time:{run_time}") - def test_many_create_coin(self): + def test_many_create_coin(self, softfork_height): # CREATE_COIN # this program will emit many CREATE_COIN conditions, all with different # amounts. # the number 6095 was chosen carefully to not exceed the maximum cost condition = CREATE_UNIQUE_COINS.format(num=6094) start_time = time() - npc_result = generator_condition_tester(condition, quote=False) + npc_result = generator_condition_tester(condition, quote=False, height=softfork_height) run_time = time() - start_time assert npc_result.error is None assert len(npc_result.npc_list) == 1 diff --git a/tests/core/test_cost_calculation.py b/tests/core/test_cost_calculation.py index 7200b8ba05..0a4e970186 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): + async def test_basics(self, softfork_height): 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, ) cost = calculate_cost_of_program(program.program, npc_result, test_constants.COST_PER_BYTE) @@ -111,7 +112,7 @@ class TestCostCalculation: ) @pytest.mark.asyncio - async def test_mempool_mode(self): + async def test_mempool_mode(self, softfork_height): wallet_tool = bt.get_pool_wallet_tool() ph = wallet_tool.get_new_puzzlehash() @@ -150,6 +151,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( @@ -157,6 +159,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 @@ -167,7 +170,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 @@ -188,11 +191,12 @@ 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 - async def test_tx_generator_speed(self): + async def test_tx_generator_speed(self, softfork_height): LARGE_BLOCK_COIN_CONSUMED_COUNT = 687 generator_bytes = large_block_generator(LARGE_BLOCK_COIN_CONSUMED_COUNT) program = SerializedProgram.from_bytes(generator_bytes) @@ -204,6 +208,7 @@ class TestCostCalculation: test_constants.MAX_BLOCK_COST_CLVM, cost_per_byte=test_constants.COST_PER_BYTE, mempool_mode=False, + height=softfork_height, ) end_time = time.time() duration = end_time - start_time @@ -214,7 +219,7 @@ class TestCostCalculation: assert duration < 1 @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) @@ -229,14 +234,22 @@ 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, mempool_mode=False) + npc_result: NPCResult = get_name_puzzle_conditions( + generator, + 10000000, + cost_per_byte=0, + mempool_mode=False, + height=softfork_height, + ) assert npc_result.error is not None assert npc_result.cost == 0 # 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, 20000000, cost_per_byte=0, mempool_mode=False) + npc_result = get_name_puzzle_conditions( + generator, 20000000, 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 2eb0aac0d3..ec9cf42b18 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 a5d089995c..41eec8c722 100644 --- a/tests/util/generator_tools_testing.py +++ b/tests/util/generator_tools_testing.py @@ -6,10 +6,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 additions_for_npc +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] = [] @@ -24,6 +25,7 @@ def run_and_get_removals_and_additions( max_cost, cost_per_byte=cost_per_byte, mempool_mode=mempool_mode, + height=height, ) # build removals list for npc in npc_result.npc_list: diff --git a/tools/run_block.py b/tools/run_block.py index 13c75cacaa..669c26fb92 100644 --- a/tools/run_block.py +++ b/tools/run_block.py @@ -41,6 +41,8 @@ from typing import List, TextIO, Tuple, Dict import click +from clvm_rs import COND_CANON_INTS, NO_NEG_DIV + from chia.consensus.constants import ConsensusConstants from chia.consensus.default_constants import DEFAULT_CONSTANTS from chia.types.blockchain_format.program import SerializedProgram @@ -87,9 +89,18 @@ def npc_to_dict(npc: NPC): } -def run_generator(block_generator: BlockGenerator, constants: ConsensusConstants, max_cost: int) -> List[CAT]: +def run_generator( + block_generator: BlockGenerator, constants: ConsensusConstants, max_cost: int, height: uint32 +) -> List[CAT]: + + if height >= DEFAULT_CONSTANTS.SOFT_FORK_HEIGHT: + # conditions must use integers in canonical encoding (i.e. no redundant + # leading zeros) + # the division operator may not be used with negative operands + flags = COND_CANON_INTS | NO_NEG_DIV + else: + flags = 0 - flags = 0 _, result = block_generator.program.run_with_cost(max_cost, flags, block_generator.generator_refs()) coin_spends = result.first() @@ -171,17 +182,23 @@ def run_full_block(block: FullBlock, constants: ConsensusConstants) -> List[CAT] if block.transactions_generator is None or block.transactions_info is None: raise RuntimeError("transactions_generator of FullBlock is null") block_generator = BlockGenerator(block.transactions_generator, generator_args) - return run_generator(block_generator, constants, min(constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost)) + return run_generator( + block_generator, constants, min(constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), block.height + ) def run_generator_with_args( - generator_program_hex: str, generator_args: List[GeneratorArg], constants: ConsensusConstants, cost: uint64 + generator_program_hex: str, + generator_args: List[GeneratorArg], + constants: ConsensusConstants, + cost: uint64, + height: uint32, ) -> List[CAT]: if not generator_program_hex: return [] generator_program = SerializedProgram.fromhex(generator_program_hex) block_generator = BlockGenerator(generator_program, generator_args) - return run_generator(block_generator, constants, min(constants.MAX_BLOCK_COST_CLVM, cost)) + return run_generator(block_generator, constants, min(constants.MAX_BLOCK_COST_CLVM, cost), height) @click.command() @@ -195,11 +212,12 @@ def run_json_block(full_block, constants: ConsensusConstants) -> List[CAT]: ref_list = full_block["block"]["transactions_generator_ref_list"] tx_info: dict = full_block["block"]["transactions_info"] generator_program_hex: str = full_block["block"]["transactions_generator"] + height = full_block["block"]["reward_chain_block"]["height"] cat_list: List[CAT] = [] if tx_info and generator_program_hex: cost = tx_info["cost"] args = ref_list_to_args(ref_list) - cat_list = run_generator_with_args(generator_program_hex, args, constants, cost) + cat_list = run_generator_with_args(generator_program_hex, args, constants, cost, height) return cat_list