mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2024-09-20 08:05:33 +03:00
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:
parent
2ea15bbda3
commit
5ee182932e
@ -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:
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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))
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
2
setup.py
2
setup.py
@ -6,7 +6,7 @@ dependencies = [
|
||||
"chiabip158==1.0", # bip158-style wallet filters
|
||||
"chiapos==1.0.4", # proof of space
|
||||
"clvm==0.9.7",
|
||||
"clvm_rs==0.1.8",
|
||||
"clvm_rs==0.1.10",
|
||||
"clvm_tools==0.4.3",
|
||||
"aiohttp==3.7.4", # HTTP server for full node rpc
|
||||
"aiosqlite==0.17.0", # asyncio wrapper for sqlite, to store blocks
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
)
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
|
@ -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:
|
||||
|
@ -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 == []
|
||||
|
@ -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
|
||||
|
@ -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)])
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user