Rust conditions (#7167)

* use rust implementation of condition checker

* enable rust condition checker at specific block height

* bump switch-over height
This commit is contained in:
Arvid Norberg 2021-08-17 23:57:41 +02:00 committed by GitHub
parent 2ea15bbda3
commit 5ee182932e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 336 additions and 135 deletions

View File

@ -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:

View File

@ -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)

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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))

View File

@ -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:

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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()
)

View File

@ -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,

View File

@ -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()

View File

@ -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:

View File

@ -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 == []

View File

@ -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

View File

@ -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)])

View File

@ -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: