Merge branch 'main' into pools.testnet9

This commit is contained in:
wjblanke 2021-06-24 11:17:55 -07:00 committed by GitHub
commit b7bf08fd77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1498 additions and 117 deletions

View File

@ -97,9 +97,14 @@ jobs:
- name: Install developer requirements
run: |
. ./activate
venv/bin/python -m pip install pytest pytest-asyncio pytest-xdist
venv/bin/python -m pip install pytest pytest-asyncio pytest-xdist pytest-monitor
- name: Test core-full_node code with pytest
run: |
. ./activate
./venv/bin/py.test tests/core/full_node/test_address_manager.py tests/core/full_node/test_block_store.py tests/core/full_node/test_coin_store.py tests/core/full_node/test_conditions.py tests/core/full_node/test_full_node.py tests/core/full_node/test_full_node_store.py tests/core/full_node/test_initial_freeze.py tests/core/full_node/test_mempool.py tests/core/full_node/test_mempool_performance.py tests/core/full_node/test_node_load.py tests/core/full_node/test_performance.py tests/core/full_node/test_sync_store.py tests/core/full_node/test_transactions.py -s -v --durations 0
- name: Check resource usage
run: |
sqlite3 -readonly -separator " " .pymon "select item,cpu_usage,total_time,mem_usage from TEST_METRICS order by mem_usage desc;" >metrics.out
cat metrics.out
./tests/check_pytest_monitor_output.py <metrics.out

View File

@ -330,7 +330,10 @@ async def validate_block_body(
curr_block_generator: Optional[BlockGenerator] = await get_block_generator(curr)
assert curr_block_generator is not None and curr.transactions_info is not None
curr_npc_result = get_name_puzzle_conditions(
curr_block_generator, min(constants.MAX_BLOCK_COST_CLVM, curr.transactions_info.cost), False
curr_block_generator,
min(constants.MAX_BLOCK_COST_CLVM, curr.transactions_info.cost),
cost_per_byte=constants.COST_PER_BYTE,
safe_mode=False,
)
removals_in_curr, additions_in_curr = tx_removals_and_additions(curr_npc_result.npc_list)
else:

View File

@ -126,7 +126,9 @@ def create_foliage(
# Calculate the cost of transactions
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, True)
result: NPCResult = get_name_puzzle_conditions(
block_generator, constants.MAX_BLOCK_COST_CLVM, cost_per_byte=constants.COST_PER_BYTE, safe_mode=True
)
cost = calculate_cost_of_program(block_generator.program, result, constants.COST_PER_BYTE)
removal_amount = 0

View File

@ -204,7 +204,10 @@ class Blockchain(BlockchainInterface):
return ReceiveBlockResult.INVALID_BLOCK, Err.GENERATOR_REF_HAS_NO_GENERATOR, None
assert block_generator is not None and block.transactions_info is not None
npc_result = get_name_puzzle_conditions(
block_generator, min(self.constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), False
block_generator,
min(self.constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost),
cost_per_byte=self.constants.COST_PER_BYTE,
safe_mode=False,
)
removals, tx_additions = tx_removals_and_additions(npc_result.npc_list)
else:
@ -375,7 +378,12 @@ class Blockchain(BlockchainInterface):
if npc_result is None:
block_generator: Optional[BlockGenerator] = await self.get_block_generator(block)
assert block_generator is not None
npc_result = get_name_puzzle_conditions(block_generator, self.constants.MAX_BLOCK_COST_CLVM, False)
npc_result = get_name_puzzle_conditions(
block_generator,
self.constants.MAX_BLOCK_COST_CLVM,
cost_per_byte=self.constants.COST_PER_BYTE,
safe_mode=False,
)
tx_removals, tx_additions = tx_removals_and_additions(npc_result.npc_list)
return tx_removals, tx_additions
else:
@ -523,7 +531,10 @@ class Blockchain(BlockchainInterface):
if block_generator is None:
return PreValidationResult(uint16(Err.GENERATOR_REF_HAS_NO_GENERATOR.value), None, None)
npc_result = get_name_puzzle_conditions(
block_generator, min(self.constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), False
block_generator,
min(self.constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost),
cost_per_byte=self.constants.COST_PER_BYTE,
safe_mode=False,
)
error_code, cost_result = await validate_block_body(
self.constants,

View File

@ -78,7 +78,10 @@ def batch_pre_validate_blocks(
block_generator: BlockGenerator = BlockGenerator.from_bytes(prev_generator_bytes)
assert block_generator.program == block.transactions_generator
npc_result = get_name_puzzle_conditions(
block_generator, min(constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost), True
block_generator,
min(constants.MAX_BLOCK_COST_CLVM, block.transactions_info.cost),
cost_per_byte=constants.COST_PER_BYTE,
safe_mode=True,
)
removals, tx_additions = tx_removals_and_additions(npc_result.npc_list)

View File

@ -808,11 +808,6 @@ def launch_service(root_path: Path, service_command) -> Tuple[subprocess.Popen,
log.debug(f"Launching service with CHIA_ROOT: {os.environ['CHIA_ROOT']}")
lockfile = singleton(service_launch_lock_path(root_path, service_command))
if lockfile is None:
logging.error(f"{service_command}: already running")
raise subprocess.SubprocessError
# Insert proper e
service_array = service_command.split()
service_executable = executable_for_service(service_array[0])

View File

@ -1,8 +1,10 @@
import logging
import time
from typing import Dict, List, Optional, Set
from typing import Tuple, Dict, List, Optional, Set
from clvm import SExp
from chia.consensus.cost_calculator import NPCResult
from chia.consensus.condition_costs import ConditionCost
from chia.full_node.generator import create_generator_args, setup_generator_args
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import NIL
@ -11,9 +13,9 @@ from chia.types.coin_record import CoinRecord
from chia.types.condition_with_args import ConditionWithArgs
from chia.types.generator_types import BlockGenerator
from chia.types.name_puzzle_condition import NPC
from chia.util.clvm import int_from_bytes
from chia.util.clvm import int_from_bytes, int_to_bytes
from chia.util.condition_tools import ConditionOpcode, conditions_by_opcode
from chia.util.errors import Err
from chia.util.errors import Err, ValidationError
from chia.util.ints import uint32, uint64, uint16
from chia.wallet.puzzles.generator_loader import GENERATOR_FOR_SINGLE_COIN_MOD
from chia.wallet.puzzles.rom_bootstrap_generator import get_generator
@ -138,40 +140,212 @@ def mempool_assert_my_amount(condition: ConditionWithArgs, unspent: CoinRecord)
return None
def get_name_puzzle_conditions(generator: BlockGenerator, max_cost: int, safe_mode: bool) -> NPCResult:
def parse_aggsig(args: SExp) -> List[bytes]:
pubkey = args.first().atom
args = args.rest()
message = args.first().atom
if len(pubkey) != 48:
raise ValidationError(Err.INVALID_CONDITION)
if len(message) > 1024:
raise ValidationError(Err.INVALID_CONDITION)
return [pubkey, message]
def parse_create_coin(args: SExp) -> List[bytes]:
puzzle_hash = args.first().atom
args = args.rest()
if len(puzzle_hash) != 32:
raise ValidationError(Err.INVALID_CONDITION)
amount_int = args.first().as_int()
if amount_int >= 2 ** 64:
raise ValidationError(Err.COIN_AMOUNT_EXCEEDS_MAXIMUM)
if amount_int < 0:
raise ValidationError(Err.COIN_AMOUNT_NEGATIVE)
# note that this may change the representation of amount. If the original
# buffer had redundant leading zeroes, they will be stripped
return [puzzle_hash, int_to_bytes(amount_int)]
def parse_seconds(args: SExp, error_code: Err) -> Optional[List[bytes]]:
seconds_int = args.first().as_int()
# this condition is inherently satisified, there is no need to keep it
if seconds_int <= 0:
return None
if seconds_int >= 2 ** 64:
raise ValidationError(error_code)
# note that this may change the representation of seconds. If the original
# buffer had redundant leading zeroes, they will be stripped
return [int_to_bytes(seconds_int)]
def parse_height(args: SExp, error_code: Err) -> Optional[List[bytes]]:
height_int = args.first().as_int()
# this condition is inherently satisified, there is no need to keep it
if height_int <= 0:
return None
if height_int >= 2 ** 32:
raise ValidationError(error_code)
# note that this may change the representation of the height. If the original
# buffer had redundant leading zeroes, they will be stripped
return [int_to_bytes(height_int)]
def parse_fee(args: SExp) -> List[bytes]:
fee_int = args.first().as_int()
if fee_int >= 2 ** 64 or fee_int < 0:
raise ValidationError(Err.RESERVE_FEE_CONDITION_FAILED)
# note that this may change the representation of the fee. If the original
# buffer had redundant leading zeroes, they will be stripped
return [int_to_bytes(fee_int)]
def parse_hash(args: SExp, error_code: Err) -> List[bytes]:
h = args.first().atom
if len(h) != 32:
raise ValidationError(error_code)
return [h]
def parse_amount(args: SExp) -> List[bytes]:
amount_int = args.first().as_int()
if amount_int < 0:
raise ValidationError(Err.ASSERT_MY_AMOUNT_FAILED)
if amount_int >= 2 ** 64:
raise ValidationError(Err.ASSERT_MY_AMOUNT_FAILED)
# note that this may change the representation of amount. If the original
# buffer had redundant leading zeroes, they will be stripped
return [int_to_bytes(amount_int)]
def parse_announcement(args: SExp) -> List[bytes]:
msg = args.first().atom
if len(msg) > 1024:
raise ValidationError(Err.INVALID_CONDITION)
return [msg]
def parse_condition_args(args: SExp, condition: ConditionOpcode) -> Tuple[int, Optional[List[bytes]]]:
"""
Parse a list with exactly the expected args, given opcode,
from an SExp into a list of bytes. If there are fewer or more elements in
the list, raise a RuntimeError. If the condition is inherently true (such as
a time- or height lock with a negative time or height, the returned list is None
"""
op = ConditionOpcode
cc = ConditionCost
if condition is op.AGG_SIG_UNSAFE or condition is op.AGG_SIG_ME:
return cc.AGG_SIG.value, parse_aggsig(args)
elif condition is op.CREATE_COIN:
return cc.CREATE_COIN.value, parse_create_coin(args)
elif condition is op.ASSERT_SECONDS_ABSOLUTE:
return cc.ASSERT_SECONDS_ABSOLUTE.value, parse_seconds(args, Err.ASSERT_SECONDS_ABSOLUTE_FAILED)
elif condition is op.ASSERT_SECONDS_RELATIVE:
return cc.ASSERT_SECONDS_RELATIVE.value, parse_seconds(args, Err.ASSERT_SECONDS_RELATIVE_FAILED)
elif condition is op.ASSERT_HEIGHT_ABSOLUTE:
return cc.ASSERT_HEIGHT_ABSOLUTE.value, parse_height(args, Err.ASSERT_HEIGHT_ABSOLUTE_FAILED)
elif condition is op.ASSERT_HEIGHT_RELATIVE:
return cc.ASSERT_HEIGHT_RELATIVE.value, parse_height(args, Err.ASSERT_HEIGHT_RELATIVE_FAILED)
elif condition is op.ASSERT_MY_COIN_ID:
return cc.ASSERT_MY_COIN_ID.value, parse_hash(args, Err.ASSERT_MY_COIN_ID_FAILED)
elif condition is op.RESERVE_FEE:
return cc.RESERVE_FEE.value, parse_fee(args)
elif condition is op.CREATE_COIN_ANNOUNCEMENT:
return cc.CREATE_COIN_ANNOUNCEMENT.value, parse_announcement(args)
elif condition is op.ASSERT_COIN_ANNOUNCEMENT:
return cc.ASSERT_COIN_ANNOUNCEMENT.value, parse_hash(args, Err.ASSERT_ANNOUNCE_CONSUMED_FAILED)
elif condition is op.CREATE_PUZZLE_ANNOUNCEMENT:
return cc.CREATE_PUZZLE_ANNOUNCEMENT.value, parse_announcement(args)
elif condition is op.ASSERT_PUZZLE_ANNOUNCEMENT:
return cc.ASSERT_PUZZLE_ANNOUNCEMENT.value, parse_hash(args, Err.ASSERT_ANNOUNCE_CONSUMED_FAILED)
elif condition is op.ASSERT_MY_PARENT_ID:
return cc.ASSERT_MY_PARENT_ID.value, parse_hash(args, Err.ASSERT_MY_PARENT_ID_FAILED)
elif condition is op.ASSERT_MY_PUZZLEHASH:
return cc.ASSERT_MY_PUZZLEHASH.value, parse_hash(args, Err.ASSERT_MY_PUZZLEHASH_FAILED)
elif condition is op.ASSERT_MY_AMOUNT:
return cc.ASSERT_MY_AMOUNT.value, parse_amount(args)
else:
raise ValidationError(Err.INVALID_CONDITION)
CONDITION_OPCODES: Set[bytes] = set(item.value for item in ConditionOpcode)
def parse_condition(cond: SExp, safe_mode: bool) -> Tuple[int, Optional[ConditionWithArgs]]:
condition = cond.first().as_atom()
if condition in CONDITION_OPCODES:
opcode: ConditionOpcode = ConditionOpcode(condition)
cost, args = parse_condition_args(cond.rest(), opcode)
cvl = ConditionWithArgs(opcode, args) if args is not None else None
elif not safe_mode:
opcode = ConditionOpcode.UNKNOWN
cvl = ConditionWithArgs(opcode, cond.rest().as_atom_list())
cost = 0
else:
raise ValidationError(Err.INVALID_CONDITION)
return cost, cvl
def get_name_puzzle_conditions(
generator: BlockGenerator, max_cost: int, *, cost_per_byte: int, safe_mode: 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:
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:
cost, result = GENERATOR_MOD.run_safe_with_cost(max_cost, block_program, block_program_args)
clvm_cost, result = GENERATOR_MOD.run_safe_with_cost(max_cost, block_program, block_program_args)
else:
cost, result = GENERATOR_MOD.run_with_cost(max_cost, block_program, block_program_args)
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] = []
opcodes: Set[bytes] = set(item.value for item in ConditionOpcode)
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()
spent_coin_puzzle_hash: bytes32 = res.rest().first().as_atom()
spent_coin_amount: uint64 = uint64(res.rest().rest().first().as_int())
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(res.first().as_int())
res = res.rest()
spent_coin: Coin = Coin(spent_coin_parent_id, spent_coin_puzzle_hash, spent_coin_amount)
for cond in res.rest().rest().rest().first().as_iter():
if cond.first().as_atom() in opcodes:
opcode: ConditionOpcode = ConditionOpcode(cond.first().as_atom())
elif not safe_mode:
opcode = ConditionOpcode.UNKNOWN
else:
return NPCResult(uint16(Err.GENERATOR_RUNTIME_ERROR.value), [], uint64(0))
cvl = ConditionWithArgs(opcode, cond.rest().as_atom_list())
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()])
)
return NPCResult(None, npc_list, uint64(cost))
return NPCResult(None, npc_list, uint64(clvm_cost))
except ValidationError as e:
return NPCResult(uint16(e.code.value), [], uint64(0))
except Exception:
return NPCResult(uint16(Err.GENERATOR_RUNTIME_ERROR.value), [], uint64(0))

View File

@ -38,10 +38,10 @@ from chia.util.streamable import recurse_jsonify
log = logging.getLogger(__name__)
def get_npc_multiprocess(spend_bundle_bytes: bytes, max_cost: int) -> bytes:
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, True))
return bytes(get_name_puzzle_conditions(program, max_cost, cost_per_byte=cost_per_byte, safe_mode=True))
class MempoolManager:
@ -219,6 +219,7 @@ class MempoolManager:
get_npc_multiprocess,
bytes(new_spend),
int(self.limit_factor * self.constants.MAX_BLOCK_COST_CLVM),
self.constants.COST_PER_BYTE,
)
end_time = time.time()
log.info(f"It took {end_time - start_time} to pre validate transaction")
@ -248,6 +249,8 @@ class MempoolManager:
log.debug(f"Cost: {cost}")
if cost > int(self.limit_factor * self.constants.MAX_BLOCK_COST_CLVM):
# we shouldn't ever end up here, since the cost is limited when we
# execute the CLVM program.
return None, MempoolInclusionStatus.FAILED, Err.BLOCK_COST_EXCEEDS_MAX
if npc_result.error is not None:

View File

@ -6,6 +6,7 @@ import signal
from sys import platform
from typing import Any, Callable, List, Optional, Tuple
from chia.daemon.server import singleton, service_launch_lock_path
from chia.server.ssl_context import chia_ssl_ca_paths, private_ssl_ca_paths
try:
@ -163,6 +164,10 @@ class Service:
)
async def run(self) -> None:
lockfile = singleton(service_launch_lock_path(self.root_path, self._service_name))
if lockfile is None:
self._log.error(f"{self._service_name}: already running")
raise ValueError(f"{self._service_name}: already running")
await self.start()
await self.wait_closed()

View File

@ -18,7 +18,7 @@ class TransactionsInfo(Streamable):
generator_refs_root: bytes32 # sha256 of the concatenation of the generator ref list entries
aggregated_signature: G2Element
fees: uint64 # This only includes user fees, not block rewards
cost: uint64 # This is the total cost of running this block in the CLVM
cost: uint64 # This is the total cost of this block, including CLVM cost, cost of program size and conditions
reward_claims_incorporated: List[Coin] # These can be in any order

View File

@ -96,10 +96,6 @@ def created_outputs_for_conditions_dict(
) -> List[Coin]:
output_coins = []
for cvp in conditions_dict.get(ConditionOpcode.CREATE_COIN, []):
# TODO: check condition very carefully
# (ensure there are the correct number and type of parameters)
# maybe write a type-checking framework for conditions
# and don't just fail with asserts
puzzle_hash, amount_bin = cvp.vars[0], cvp.vars[1]
amount = int_from_bytes(amount_bin)
coin = Coin(input_coin_name, puzzle_hash, uint64(amount))

View File

@ -246,7 +246,10 @@ class CCWallet:
program: BlockGenerator = simple_solution_generator(tx.spend_bundle)
# npc contains names of the coins removed, puzzle_hashes and their spend conditions
result: NPCResult = get_name_puzzle_conditions(
program, self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM, True
program,
self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM,
cost_per_byte=self.wallet_state_manager.constants.COST_PER_BYTE,
safe_mode=True,
)
cost_result: uint64 = calculate_cost_of_program(
program.program, result, self.wallet_state_manager.constants.COST_PER_BYTE

View File

@ -78,7 +78,10 @@ class Wallet:
program: BlockGenerator = simple_solution_generator(tx.spend_bundle)
# npc contains names of the coins removed, puzzle_hashes and their spend conditions
result: NPCResult = get_name_puzzle_conditions(
program, self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM, True
program,
self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM,
cost_per_byte=self.wallet_state_manager.constants.COST_PER_BYTE,
safe_mode=True,
)
cost_result: uint64 = calculate_cost_of_program(
program.program, result, self.wallet_state_manager.constants.COST_PER_BYTE

View File

@ -12,6 +12,7 @@ from pathlib import Path
from typing import Callable, Dict, List, Optional, Tuple, Any
from blspy import AugSchemeMPL, G1Element, G2Element, PrivateKey
from chiabip158 import PyBIP158
from chia.cmds.init_funcs import create_all_ssl, create_default_chia_config
from chia.full_node.bundle_tools import (
@ -19,16 +20,21 @@ from chia.full_node.bundle_tools import (
detect_potential_template_generator,
simple_solution_generator,
)
from chia.util.errors import Err
from chia.full_node.generator import setup_generator_args
from chia.full_node.mempool_check_conditions import GENERATOR_MOD
from chia.plotting.create_plots import create_plots
from chia.consensus.block_creation import create_unfinished_block, unfinished_block_to_full_block
from chia.consensus.block_creation import unfinished_block_to_full_block
from chia.consensus.block_record import BlockRecord
from chia.consensus.block_rewards import calculate_base_farmer_reward, calculate_pool_reward
from chia.consensus.blockchain_interface import BlockchainInterface
from chia.consensus.coinbase import create_puzzlehash_for_pk
from chia.consensus.coinbase import create_puzzlehash_for_pk, create_farmer_coin, create_pool_coin
from chia.consensus.constants import ConsensusConstants
from chia.consensus.default_constants import DEFAULT_CONSTANTS
from chia.consensus.deficit import calculate_deficit
from chia.consensus.full_block_to_block_record import block_to_block_record
from chia.consensus.make_sub_epoch_summary import next_sub_epoch_summary
from chia.consensus.cost_calculator import NPCResult, calculate_cost_of_program
from chia.consensus.pot_iterations import (
calculate_ip_iters,
calculate_iterations_quality,
@ -40,9 +46,11 @@ from chia.consensus.vdf_info_computation import get_signage_point_vdf_info
from chia.full_node.signage_point import SignagePoint
from chia.plotting.plot_tools import PlotInfo, load_plots, parse_plot_info
from chia.types.blockchain_format.classgroup import ClassgroupElement
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.coin import Coin, hash_coin_list
from chia.types.blockchain_format.foliage import Foliage, FoliageBlockData, FoliageTransactionBlock, TransactionsInfo
from chia.types.blockchain_format.pool_target import PoolTarget
from chia.types.blockchain_format.proof_of_space import ProofOfSpace
from chia.types.blockchain_format.reward_chain_block import RewardChainBlockUnfinished
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.blockchain_format.slots import (
ChallengeChainSubSlot,
@ -52,17 +60,22 @@ from chia.types.blockchain_format.slots import (
)
from chia.types.blockchain_format.sub_epoch_summary import SubEpochSummary
from chia.types.blockchain_format.vdf import VDFInfo, VDFProof
from chia.types.condition_with_args import ConditionWithArgs
from chia.types.end_of_slot_bundle import EndOfSubSlotBundle
from chia.types.full_block import FullBlock
from chia.types.generator_types import BlockGenerator, CompressorArg
from chia.types.spend_bundle import SpendBundle
from chia.types.unfinished_block import UnfinishedBlock
from chia.types.name_puzzle_condition import NPC
from chia.util.bech32m import encode_puzzle_hash
from chia.util.block_cache import BlockCache
from chia.util.condition_tools import ConditionOpcode, conditions_by_opcode
from chia.util.config import load_config, save_config
from chia.util.hash import std_hash
from chia.util.ints import uint8, uint32, uint64, uint128
from chia.util.ints import uint8, uint16, uint32, uint64, uint128
from chia.util.keychain import Keychain, bytes_to_mnemonic
from chia.util.merkle_set import MerkleSet
from chia.util.prev_transaction_block import get_prev_transaction_block
from chia.util.path import mkdir
from chia.util.vdf_prover import get_vdf_info_and_proof
from tests.wallet_tools import WalletTool
@ -850,7 +863,7 @@ class BlockTools:
if len(finished_sub_slots) < skip_slots:
continue
unfinished_block = create_unfinished_block(
unfinished_block = create_test_unfinished_block(
constants,
sub_slot_total_iters,
constants.SUB_SLOT_ITERS_STARTING,
@ -1354,7 +1367,7 @@ def get_full_block_and_block_record(
sp_iters = calculate_sp_iters(constants, sub_slot_iters, signage_point_index)
ip_iters = calculate_ip_iters(constants, sub_slot_iters, signage_point_index, required_iters)
unfinished_block = create_unfinished_block(
unfinished_block = create_test_unfinished_block(
constants,
sub_slot_start_total_iters,
sub_slot_iters,
@ -1402,3 +1415,420 @@ def get_full_block_and_block_record(
)
return full_block, block_record
def get_name_puzzle_conditions_test(generator: BlockGenerator, max_cost: int) -> NPCResult:
"""
This is similar to get_name_puzzle_conditions(), but it doesn't validate
the conditions. We rely on this in tests to create invalid blocks.
safe_mode is implicitly True in this call
"""
try:
block_program, block_program_args = setup_generator_args(generator)
clvm_cost, result = GENERATOR_MOD.run_safe_with_cost(max_cost, block_program, block_program_args)
npc_list: List[NPC] = []
for res in result.first().as_iter():
conditions_list: List[ConditionWithArgs] = []
spent_coin_parent_id: bytes32 = res.first().as_atom()
res = res.rest()
spent_coin_puzzle_hash: bytes32 = res.first().as_atom()
res = res.rest()
spent_coin_amount: uint64 = uint64(res.first().as_int())
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():
condition = cond.first().as_atom()
cvl = ConditionWithArgs(ConditionOpcode(condition), cond.rest().as_atom_list())
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()])
)
return NPCResult(None, npc_list, uint64(clvm_cost))
except Exception:
return NPCResult(uint16(Err.GENERATOR_RUNTIME_ERROR.value), [], uint64(0))
def create_test_foliage(
constants: ConsensusConstants,
reward_block_unfinished: RewardChainBlockUnfinished,
block_generator: Optional[BlockGenerator],
aggregate_sig: G2Element,
additions: List[Coin],
removals: List[Coin],
prev_block: Optional[BlockRecord],
blocks: BlockchainInterface,
total_iters_sp: uint128,
timestamp: uint64,
farmer_reward_puzzlehash: bytes32,
pool_target: PoolTarget,
get_plot_signature: Callable[[bytes32, G1Element], G2Element],
get_pool_signature: Callable[[PoolTarget, Optional[G1Element]], Optional[G2Element]],
seed: bytes32 = b"",
) -> Tuple[Foliage, Optional[FoliageTransactionBlock], Optional[TransactionsInfo]]:
"""
Creates a foliage for a given reward chain block. This may or may not be a tx block. In the case of a tx block,
the return values are not None. This is called at the signage point, so some of this information may be
tweaked at the infusion point.
Args:
constants: consensus constants being used for this chain
reward_block_unfinished: the reward block to look at, potentially at the signage point
block_generator: transactions to add to the foliage block, if created
aggregate_sig: aggregate of all transctions (or infinity element)
prev_block: the previous block at the signage point
blocks: dict from header hash to blocks, of all ancestor blocks
total_iters_sp: total iters at the signage point
timestamp: timestamp to put into the foliage block
farmer_reward_puzzlehash: where to pay out farming reward
pool_target: where to pay out pool reward
get_plot_signature: retrieve the signature corresponding to the plot public key
get_pool_signature: retrieve the signature corresponding to the pool public key
seed: seed to randomize block
"""
if prev_block is not None:
res = get_prev_transaction_block(prev_block, blocks, total_iters_sp)
is_transaction_block: bool = res[0]
prev_transaction_block: Optional[BlockRecord] = res[1]
else:
# Genesis is a transaction block
prev_transaction_block = None
is_transaction_block = True
random.seed(seed)
# Use the extension data to create different blocks based on header hash
extension_data: bytes32 = random.randint(0, 100000000).to_bytes(32, "big")
if prev_block is None:
height: uint32 = uint32(0)
else:
height = uint32(prev_block.height + 1)
# Create filter
byte_array_tx: List[bytes32] = []
tx_additions: List[Coin] = []
tx_removals: List[bytes32] = []
pool_target_signature: Optional[G2Element] = get_pool_signature(
pool_target, reward_block_unfinished.proof_of_space.pool_public_key
)
foliage_data = FoliageBlockData(
reward_block_unfinished.get_hash(),
pool_target,
pool_target_signature,
farmer_reward_puzzlehash,
extension_data,
)
foliage_block_data_signature: G2Element = get_plot_signature(
foliage_data.get_hash(),
reward_block_unfinished.proof_of_space.plot_public_key,
)
prev_block_hash: bytes32 = constants.GENESIS_CHALLENGE
if height != 0:
assert prev_block is not None
prev_block_hash = prev_block.header_hash
generator_block_heights_list: List[uint32] = []
if is_transaction_block:
cost = uint64(0)
# Calculate the cost of transactions
if block_generator is not None:
generator_block_heights_list = block_generator.block_height_list()
result: NPCResult = get_name_puzzle_conditions_test(block_generator, constants.MAX_BLOCK_COST_CLVM)
cost = calculate_cost_of_program(block_generator.program, result, constants.COST_PER_BYTE)
removal_amount = 0
addition_amount = 0
for coin in removals:
removal_amount += coin.amount
for coin in additions:
addition_amount += coin.amount
spend_bundle_fees = removal_amount - addition_amount
else:
spend_bundle_fees = 0
reward_claims_incorporated = []
if height > 0:
assert prev_transaction_block is not None
assert prev_block is not None
curr: BlockRecord = prev_block
while not curr.is_transaction_block:
curr = blocks.block_record(curr.prev_hash)
assert curr.fees is not None
pool_coin = create_pool_coin(
curr.height, curr.pool_puzzle_hash, calculate_pool_reward(curr.height), constants.GENESIS_CHALLENGE
)
farmer_coin = create_farmer_coin(
curr.height,
curr.farmer_puzzle_hash,
uint64(calculate_base_farmer_reward(curr.height) + curr.fees),
constants.GENESIS_CHALLENGE,
)
assert curr.header_hash == prev_transaction_block.header_hash
reward_claims_incorporated += [pool_coin, farmer_coin]
if curr.height > 0:
curr = blocks.block_record(curr.prev_hash)
# Prev block is not genesis
while not curr.is_transaction_block:
pool_coin = create_pool_coin(
curr.height,
curr.pool_puzzle_hash,
calculate_pool_reward(curr.height),
constants.GENESIS_CHALLENGE,
)
farmer_coin = create_farmer_coin(
curr.height,
curr.farmer_puzzle_hash,
calculate_base_farmer_reward(curr.height),
constants.GENESIS_CHALLENGE,
)
reward_claims_incorporated += [pool_coin, farmer_coin]
curr = blocks.block_record(curr.prev_hash)
additions.extend(reward_claims_incorporated.copy())
for coin in additions:
tx_additions.append(coin)
byte_array_tx.append(bytearray(coin.puzzle_hash))
for coin in removals:
tx_removals.append(coin.name())
byte_array_tx.append(bytearray(coin.name()))
bip158: PyBIP158 = PyBIP158(byte_array_tx)
encoded = bytes(bip158.GetEncoded())
removal_merkle_set = MerkleSet()
addition_merkle_set = MerkleSet()
# Create removal Merkle set
for coin_name in tx_removals:
removal_merkle_set.add_already_hashed(coin_name)
# Create addition Merkle set
puzzlehash_coin_map: Dict[bytes32, List[Coin]] = {}
for coin in tx_additions:
if coin.puzzle_hash in puzzlehash_coin_map:
puzzlehash_coin_map[coin.puzzle_hash].append(coin)
else:
puzzlehash_coin_map[coin.puzzle_hash] = [coin]
# Addition Merkle set contains puzzlehash and hash of all coins with that puzzlehash
for puzzle, coins in puzzlehash_coin_map.items():
addition_merkle_set.add_already_hashed(puzzle)
addition_merkle_set.add_already_hashed(hash_coin_list(coins))
additions_root = addition_merkle_set.get_root()
removals_root = removal_merkle_set.get_root()
generator_hash = bytes32([0] * 32)
if block_generator is not None:
generator_hash = std_hash(block_generator.program)
generator_refs_hash = bytes32([1] * 32)
if generator_block_heights_list not in (None, []):
generator_ref_list_bytes = b"".join([bytes(i) for i in generator_block_heights_list])
generator_refs_hash = std_hash(generator_ref_list_bytes)
filter_hash: bytes32 = std_hash(encoded)
transactions_info: Optional[TransactionsInfo] = TransactionsInfo(
generator_hash,
generator_refs_hash,
aggregate_sig,
uint64(spend_bundle_fees),
cost,
reward_claims_incorporated,
)
if prev_transaction_block is None:
prev_transaction_block_hash: bytes32 = constants.GENESIS_CHALLENGE
else:
prev_transaction_block_hash = prev_transaction_block.header_hash
assert transactions_info is not None
foliage_transaction_block: Optional[FoliageTransactionBlock] = FoliageTransactionBlock(
prev_transaction_block_hash,
timestamp,
filter_hash,
additions_root,
removals_root,
transactions_info.get_hash(),
)
assert foliage_transaction_block is not None
foliage_transaction_block_hash: Optional[bytes32] = foliage_transaction_block.get_hash()
foliage_transaction_block_signature: Optional[G2Element] = get_plot_signature(
foliage_transaction_block_hash, reward_block_unfinished.proof_of_space.plot_public_key
)
assert foliage_transaction_block_signature is not None
else:
foliage_transaction_block_hash = None
foliage_transaction_block_signature = None
foliage_transaction_block = None
transactions_info = None
assert (foliage_transaction_block_hash is None) == (foliage_transaction_block_signature is None)
foliage = Foliage(
prev_block_hash,
reward_block_unfinished.get_hash(),
foliage_data,
foliage_block_data_signature,
foliage_transaction_block_hash,
foliage_transaction_block_signature,
)
return foliage, foliage_transaction_block, transactions_info
def create_test_unfinished_block(
constants: ConsensusConstants,
sub_slot_start_total_iters: uint128,
sub_slot_iters: uint64,
signage_point_index: uint8,
sp_iters: uint64,
ip_iters: uint64,
proof_of_space: ProofOfSpace,
slot_cc_challenge: bytes32,
farmer_reward_puzzle_hash: bytes32,
pool_target: PoolTarget,
get_plot_signature: Callable[[bytes32, G1Element], G2Element],
get_pool_signature: Callable[[PoolTarget, Optional[G1Element]], Optional[G2Element]],
signage_point: SignagePoint,
timestamp: uint64,
blocks: BlockchainInterface,
seed: bytes32 = b"",
block_generator: Optional[BlockGenerator] = None,
aggregate_sig: G2Element = G2Element(),
additions: Optional[List[Coin]] = None,
removals: Optional[List[Coin]] = None,
prev_block: Optional[BlockRecord] = None,
finished_sub_slots_input: List[EndOfSubSlotBundle] = None,
) -> UnfinishedBlock:
"""
Creates a new unfinished block using all the information available at the signage point. This will have to be
modified using information from the infusion point.
Args:
constants: consensus constants being used for this chain
sub_slot_start_total_iters: the starting sub-slot iters at the signage point sub-slot
sub_slot_iters: sub-slot-iters at the infusion point epoch
signage_point_index: signage point index of the block to create
sp_iters: sp_iters of the block to create
ip_iters: ip_iters of the block to create
proof_of_space: proof of space of the block to create
slot_cc_challenge: challenge hash at the sp sub-slot
farmer_reward_puzzle_hash: where to pay out farmer rewards
pool_target: where to pay out pool rewards
get_plot_signature: function that returns signature corresponding to plot public key
get_pool_signature: function that returns signature corresponding to pool public key
signage_point: signage point information (VDFs)
timestamp: timestamp to add to the foliage block, if created
seed: seed to randomize chain
block_generator: transactions to add to the foliage block, if created
aggregate_sig: aggregate of all transctions (or infinity element)
additions: Coins added in spend_bundle
removals: Coins removed in spend_bundle
prev_block: previous block (already in chain) from the signage point
blocks: dictionary from header hash to SBR of all included SBR
finished_sub_slots_input: finished_sub_slots at the signage point
Returns:
"""
if finished_sub_slots_input is None:
finished_sub_slots: List[EndOfSubSlotBundle] = []
else:
finished_sub_slots = finished_sub_slots_input.copy()
overflow: bool = sp_iters > ip_iters
total_iters_sp: uint128 = uint128(sub_slot_start_total_iters + sp_iters)
is_genesis: bool = prev_block is None
new_sub_slot: bool = len(finished_sub_slots) > 0
cc_sp_hash: Optional[bytes32] = slot_cc_challenge
# Only enters this if statement if we are in testing mode (making VDF proofs here)
if signage_point.cc_vdf is not None:
assert signage_point.rc_vdf is not None
cc_sp_hash = signage_point.cc_vdf.output.get_hash()
rc_sp_hash = signage_point.rc_vdf.output.get_hash()
else:
if new_sub_slot:
rc_sp_hash = finished_sub_slots[-1].reward_chain.get_hash()
else:
if is_genesis:
rc_sp_hash = constants.GENESIS_CHALLENGE
else:
assert prev_block is not None
assert blocks is not None
curr = prev_block
while not curr.first_in_sub_slot:
curr = blocks.block_record(curr.prev_hash)
assert curr.finished_reward_slot_hashes is not None
rc_sp_hash = curr.finished_reward_slot_hashes[-1]
signage_point = SignagePoint(None, None, None, None)
cc_sp_signature: Optional[G2Element] = get_plot_signature(cc_sp_hash, proof_of_space.plot_public_key)
rc_sp_signature: Optional[G2Element] = get_plot_signature(rc_sp_hash, proof_of_space.plot_public_key)
assert cc_sp_signature is not None
assert rc_sp_signature is not None
assert AugSchemeMPL.verify(proof_of_space.plot_public_key, cc_sp_hash, cc_sp_signature)
total_iters = uint128(sub_slot_start_total_iters + ip_iters + (sub_slot_iters if overflow else 0))
rc_block = RewardChainBlockUnfinished(
total_iters,
signage_point_index,
slot_cc_challenge,
proof_of_space,
signage_point.cc_vdf,
cc_sp_signature,
signage_point.rc_vdf,
rc_sp_signature,
)
if additions is None:
additions = []
if removals is None:
removals = []
(foliage, foliage_transaction_block, transactions_info,) = create_test_foliage(
constants,
rc_block,
block_generator,
aggregate_sig,
additions,
removals,
prev_block,
blocks,
total_iters_sp,
timestamp,
farmer_reward_puzzle_hash,
pool_target,
get_plot_signature,
get_pool_signature,
seed,
)
return UnfinishedBlock(
finished_sub_slots,
rc_block,
signage_point.cc_proof,
signage_point.rc_proof,
foliage,
foliage_transaction_block,
transactions_info,
block_generator.program if block_generator else None,
block_generator.block_height_list() if block_generator else [],
)

View File

@ -1947,7 +1947,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.BLOCK_COST_EXCEEDS_MAX
assert (await b.receive_block(blocks[-1]))[1] == Err.INVALID_BLOCK_COST
@pytest.mark.asyncio
async def test_clvm_must_not_fail(self, empty_blockchain):
@ -2006,7 +2006,7 @@ class TestBodyValidation:
new_fsb_sig = bt.get_plot_signature(new_m, block.reward_chain_block.proof_of_space.plot_public_key)
block_2 = recursive_replace(block_2, "foliage.foliage_transaction_block_signature", new_fsb_sig)
err = (await b.receive_block(block_2))[1]
assert err == Err.GENERATOR_RUNTIME_ERROR
assert err == Err.INVALID_BLOCK_COST
# too high
block_2: FullBlock = recursive_replace(block, "transactions_info.cost", uint64(1000000))
@ -2021,7 +2021,9 @@ class TestBodyValidation:
block_2 = recursive_replace(block_2, "foliage.foliage_transaction_block_signature", new_fsb_sig)
err = (await b.receive_block(block_2))[1]
assert err == Err.INVALID_BLOCK_COST
# when the CLVM program exceeds cost during execution, it will fail with
# a general runtime error
assert err == Err.GENERATOR_RUNTIME_ERROR
err = (await b.receive_block(block))[1]
assert err is None

View File

@ -326,7 +326,9 @@ class TestBlockchainTransactions:
await full_node_api_1.full_node.respond_block(full_node_protocol.RespondBlock(new_blocks[-1]))
coin_2 = None
for coin in run_and_get_removals_and_additions(new_blocks[-1], test_constants.MAX_BLOCK_COST_CLVM)[1]:
for coin in run_and_get_removals_and_additions(
new_blocks[-1], test_constants.MAX_BLOCK_COST_CLVM, test_constants.COST_PER_BYTE
)[1]:
if coin.puzzle_hash == receiver_1_puzzlehash:
coin_2 = coin
break
@ -345,7 +347,9 @@ class TestBlockchainTransactions:
await full_node_api_1.full_node.respond_block(full_node_protocol.RespondBlock(new_blocks[-1]))
coin_3 = None
for coin in run_and_get_removals_and_additions(new_blocks[-1], test_constants.MAX_BLOCK_COST_CLVM)[1]:
for coin in run_and_get_removals_and_additions(
new_blocks[-1], test_constants.MAX_BLOCK_COST_CLVM, test_constants.COST_PER_BYTE
)[1]:
if coin.puzzle_hash == receiver_2_puzzlehash:
coin_3 = coin
break

View File

@ -0,0 +1,19 @@
#!/bin/env python3
import sys
ret = 0
# example input line
# test_non_tx_aggregate_limits 0.997759588095738 1.45325589179993 554.45703125
for ln in sys.stdin:
line = ln.strip().split()
print(f"{float(line[1]) * 100.0: 7.2f}% CPU {float(line[2]):6.2f}s {line[3]:6.5} MB RAM {line[0]}")
if float(line[3]) > 1500:
print(" ERROR: ^^ exceeded RAM limit ^^ \n")
ret += 1
if ret > 0:
print("some tests used too much RAM")
sys.exit(ret)

View File

@ -103,7 +103,12 @@ class TestCoinStore:
if block.is_transaction_block():
if block.transactions_generator is not None:
block_gen: BlockGenerator = BlockGenerator(block.transactions_generator, [])
npc_result = get_name_puzzle_conditions(block_gen, bt.constants.MAX_BLOCK_COST_CLVM, False)
npc_result = get_name_puzzle_conditions(
block_gen,
bt.constants.MAX_BLOCK_COST_CLVM,
cost_per_byte=bt.constants.COST_PER_BYTE,
safe_mode=False,
)
tx_removals, tx_additions = tx_removals_and_additions(npc_result.npc_list)
else:
tx_removals, tx_additions = [], []

View File

@ -4,6 +4,8 @@ import logging
from typing import Dict, List, Optional, Tuple, Callable
import pytest
from clvm import SExp
from clvm.EvalError import EvalError
import chia.server.ws_connection as ws
@ -19,17 +21,19 @@ from chia.types.condition_with_args import ConditionWithArgs
from chia.types.spend_bundle import SpendBundle
from chia.util.clvm import int_to_bytes
from chia.util.condition_tools import conditions_for_solution
from chia.util.errors import Err
from chia.util.errors import Err, ValidationError
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
from tests.connection_utils import connect_and_get_peer
from tests.core.node_height import node_height_at_least
from tests.setup_nodes import bt, setup_simulators_and_wallets
from tests.time_out_assert import time_out_assert
from chia.types.blockchain_format.program import Program, INFINITE_COST
from chia.consensus.condition_costs import ConditionCost
BURN_PUZZLE_HASH = b"0" * 32
BURN_PUZZLE_HASH_2 = b"1" * 32
@ -377,6 +381,20 @@ class TestMempoolManager:
assert status == MempoolInclusionStatus.PENDING
assert err == Err.ASSERT_HEIGHT_ABSOLUTE_FAILED
@pytest.mark.asyncio
async def test_block_index_missing_arg(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
blocks = await full_node_1.get_all_full_blocks()
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_HEIGHT_ABSOLUTE, [])
dic = {ConditionOpcode.ASSERT_HEIGHT_ABSOLUTE: [cvp]}
blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic)
sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name())
assert sb1 is None
# the transaction may become valid later
assert status == MempoolInclusionStatus.FAILED
assert err == Err.GENERATOR_RUNTIME_ERROR
@pytest.mark.asyncio
async def test_correct_block_index(self, two_nodes):
@ -389,6 +407,19 @@ class TestMempoolManager:
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_block_index_garbage(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
# garbage at the end of the argument list is ignored
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_HEIGHT_ABSOLUTE, [int_to_bytes(1), b"garbage"])
dic = {ConditionOpcode.ASSERT_HEIGHT_ABSOLUTE: [cvp]}
blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic)
sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name())
assert sb1 is spend_bundle1
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_negative_block_index(self, two_nodes):
@ -414,6 +445,19 @@ class TestMempoolManager:
assert status == MempoolInclusionStatus.PENDING
assert err == Err.ASSERT_HEIGHT_RELATIVE_FAILED
@pytest.mark.asyncio
async def test_block_age_missing_arg(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_HEIGHT_RELATIVE, [])
dic = {cvp.opcode: [cvp]}
blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic)
sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name())
assert sb1 is None
# the transaction may become valid later
assert status == MempoolInclusionStatus.FAILED
assert err == Err.GENERATOR_RUNTIME_ERROR
@pytest.mark.asyncio
async def test_correct_block_age(self, two_nodes):
@ -427,6 +471,20 @@ class TestMempoolManager:
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_block_age_garbage(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
# garbage at the end of the argument list is ignored
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_HEIGHT_RELATIVE, [int_to_bytes(1), b"garbage"])
dic = {cvp.opcode: [cvp]}
blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic, num_blocks=4)
sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name())
assert sb1 is spend_bundle1
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_negative_block_age(self, two_nodes):
@ -455,6 +513,22 @@ class TestMempoolManager:
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_my_id_garbage(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
blocks = await full_node_1.get_all_full_blocks()
coin = list(blocks[-1].get_included_reward_coins())[0]
# garbage at the end of the argument list is ignored
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_MY_COIN_ID, [coin.name(), b"garbage"])
dic = {cvp.opcode: [cvp]}
blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic, coin=coin)
sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name())
assert sb1 is spend_bundle1
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_invalid_my_id(self, two_nodes):
@ -471,6 +545,20 @@ class TestMempoolManager:
assert status == MempoolInclusionStatus.FAILED
assert err == Err.ASSERT_MY_COIN_ID_FAILED
@pytest.mark.asyncio
async def test_my_id_missing_arg(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
blocks = await full_node_1.get_all_full_blocks()
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_MY_COIN_ID, [])
dic = {cvp.opcode: [cvp]}
blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic)
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
@pytest.mark.asyncio
async def test_assert_time_exceeds(self, two_nodes):
@ -486,6 +574,20 @@ class TestMempoolManager:
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_assert_time_fail(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
time_now = full_node_1.full_node.blockchain.get_peak().timestamp + 1000
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_SECONDS_ABSOLUTE, [int_to_bytes(time_now)])
dic = {cvp.opcode: [cvp]}
blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic)
sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name())
assert sb1 is None
assert status == MempoolInclusionStatus.FAILED
assert err == Err.ASSERT_SECONDS_ABSOLUTE_FAILED
@pytest.mark.asyncio
async def test_assert_time_negative(self, two_nodes):
@ -500,6 +602,34 @@ class TestMempoolManager:
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_assert_time_missing_arg(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_SECONDS_ABSOLUTE, [])
dic = {cvp.opcode: [cvp]}
blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic)
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
@pytest.mark.asyncio
async def test_assert_time_garbage(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
time_now = full_node_1.full_node.blockchain.get_peak().timestamp + 5
# garbage at the end of the argument list is ignored
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_SECONDS_ABSOLUTE, [int_to_bytes(time_now), b"garbage"])
dic = {cvp.opcode: [cvp]}
blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic)
sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name())
assert sb1 is spend_bundle1
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_assert_time_relative_exceeds(self, two_nodes):
@ -527,6 +657,36 @@ class TestMempoolManager:
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_assert_time_relative_garbage(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
time_relative = 0
# garbage at the end of the arguments is ignored
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_SECONDS_RELATIVE, [int_to_bytes(time_relative), b"garbage"])
dic = {cvp.opcode: [cvp]}
blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic)
sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name())
assert sb1 is spend_bundle1
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_assert_time_relative_missing_arg(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_SECONDS_RELATIVE, [])
dic = {cvp.opcode: [cvp]}
blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic)
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
@pytest.mark.asyncio
async def test_assert_time_relative_negative(self, two_nodes):
@ -564,6 +724,73 @@ class TestMempoolManager:
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_coin_announcement_garbage(self, two_nodes):
def test_fun(coin_1: Coin, coin_2: Coin) -> SpendBundle:
announce = Announcement(coin_2.name(), b"test")
# garbage at the end is ignored
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT, [announce.name(), b"garbage"])
dic = {cvp.opcode: [cvp]}
# garbage at the end is ignored
cvp2 = ConditionWithArgs(ConditionOpcode.CREATE_COIN_ANNOUNCEMENT, [b"test", b"garbage"])
dic2 = {cvp.opcode: [cvp2]}
spend_bundle1 = generate_test_spend_bundle(coin_1, dic)
spend_bundle2 = generate_test_spend_bundle(coin_2, dic2)
bundle = SpendBundle.aggregate([spend_bundle1, spend_bundle2])
return bundle
full_node_1, full_node_2, server_1, server_2 = two_nodes
blocks, bundle, status, err = await self.condition_tester2(two_nodes, test_fun)
mempool_bundle = full_node_1.full_node.mempool_manager.get_spendbundle(bundle.name())
assert mempool_bundle is bundle
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_coin_announcement_missing_arg(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
def test_fun(coin_1: Coin, coin_2: Coin):
# missing arg here
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT, [])
dic = {cvp.opcode: [cvp]}
cvp2 = ConditionWithArgs(ConditionOpcode.CREATE_COIN_ANNOUNCEMENT, [b"test"])
dic2 = {cvp.opcode: [cvp2]}
spend_bundle1 = generate_test_spend_bundle(coin_1, dic)
spend_bundle2 = generate_test_spend_bundle(coin_2, dic2)
return SpendBundle.aggregate([spend_bundle1, spend_bundle2])
blocks, bundle, status, err = await self.condition_tester2(two_nodes, test_fun)
assert full_node_1.full_node.mempool_manager.get_spendbundle(bundle.name()) is None
assert status == MempoolInclusionStatus.FAILED
assert err == Err.GENERATOR_RUNTIME_ERROR
@pytest.mark.asyncio
async def test_coin_announcement_missing_arg2(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
def test_fun(coin_1: Coin, coin_2: Coin):
announce = Announcement(coin_2.name(), b"test")
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT, [announce.name()])
dic = {cvp.opcode: [cvp]}
# missing arg here
cvp2 = ConditionWithArgs(ConditionOpcode.CREATE_COIN_ANNOUNCEMENT, [])
dic2 = {cvp.opcode: [cvp2]}
spend_bundle1 = generate_test_spend_bundle(coin_1, dic)
spend_bundle2 = generate_test_spend_bundle(coin_2, dic2)
return SpendBundle.aggregate([spend_bundle1, spend_bundle2])
blocks, bundle, status, err = await self.condition_tester2(two_nodes, test_fun)
assert full_node_1.full_node.mempool_manager.get_spendbundle(bundle.name()) is None
assert status == MempoolInclusionStatus.FAILED
assert err == Err.GENERATOR_RUNTIME_ERROR
@pytest.mark.asyncio
async def test_coin_announcement_too_big(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
@ -578,7 +805,6 @@ class TestMempoolManager:
cvp2 = ConditionWithArgs(ConditionOpcode.CREATE_COIN_ANNOUNCEMENT, [b"test"])
dic2 = {cvp.opcode: [cvp2]}
spend_bundle1 = generate_test_spend_bundle(coin_1, dic)
spend_bundle2 = generate_test_spend_bundle(coin_2, dic2)
return SpendBundle.aggregate([spend_bundle1, spend_bundle2])
@ -615,7 +841,6 @@ class TestMempoolManager:
)
dic2 = {cvp.opcode: [cvp2]}
spend_bundle1 = generate_test_spend_bundle(coin_1, dic)
spend_bundle2 = generate_test_spend_bundle(coin_2, dic2)
return SpendBundle.aggregate([spend_bundle1, spend_bundle2])
@ -645,7 +870,6 @@ class TestMempoolManager:
)
dic2 = {cvp.opcode: [cvp2]}
spend_bundle1 = generate_test_spend_bundle(coin_1, dic)
# coin 2 is making the announcement, right message wrong coin
spend_bundle2 = generate_test_spend_bundle(coin_2, dic2)
@ -672,7 +896,6 @@ class TestMempoolManager:
cvp2 = ConditionWithArgs(ConditionOpcode.CREATE_PUZZLE_ANNOUNCEMENT, [bytes(0x80)])
dic2 = {cvp.opcode: [cvp2]}
spend_bundle1 = generate_test_spend_bundle(coin_1, dic)
spend_bundle2 = generate_test_spend_bundle(coin_2, dic2)
return SpendBundle.aggregate([spend_bundle1, spend_bundle2])
@ -685,6 +908,85 @@ class TestMempoolManager:
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_puzzle_announcement_garbage(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
def test_fun(coin_1: Coin, coin_2: Coin):
announce = Announcement(coin_2.puzzle_hash, bytes(0x80))
# garbage at the end is ignored
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_PUZZLE_ANNOUNCEMENT, [announce.name(), b"garbage"])
dic = {cvp.opcode: [cvp]}
# garbage at the end is ignored
cvp2 = ConditionWithArgs(ConditionOpcode.CREATE_PUZZLE_ANNOUNCEMENT, [bytes(0x80), b"garbage"])
dic2 = {cvp.opcode: [cvp2]}
spend_bundle1 = generate_test_spend_bundle(coin_1, dic)
spend_bundle2 = generate_test_spend_bundle(coin_2, dic2)
return SpendBundle.aggregate([spend_bundle1, spend_bundle2])
blocks, bundle, status, err = await self.condition_tester2(two_nodes, test_fun)
mempool_bundle = full_node_1.full_node.mempool_manager.get_spendbundle(bundle.name())
assert mempool_bundle is bundle
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_puzzle_announcement_missing_arg(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
def test_fun(coin_1: Coin, coin_2: Coin):
# missing arg here
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_PUZZLE_ANNOUNCEMENT, [])
dic = {cvp.opcode: [cvp]}
cvp2 = ConditionWithArgs(
ConditionOpcode.CREATE_PUZZLE_ANNOUNCEMENT,
[b"test"],
)
dic2 = {cvp.opcode: [cvp2]}
spend_bundle1 = generate_test_spend_bundle(coin_1, dic)
spend_bundle2 = generate_test_spend_bundle(coin_2, dic2)
return SpendBundle.aggregate([spend_bundle1, spend_bundle2])
blocks, bundle, status, err = await self.condition_tester2(two_nodes, test_fun)
mempool_bundle = full_node_1.full_node.mempool_manager.get_spendbundle(bundle.name())
assert mempool_bundle is None
assert status == MempoolInclusionStatus.FAILED
assert err == Err.GENERATOR_RUNTIME_ERROR
@pytest.mark.asyncio
async def test_puzzle_announcement_missing_arg2(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
def test_fun(coin_1: Coin, coin_2: Coin):
announce = Announcement(coin_2.puzzle_hash, b"test")
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_PUZZLE_ANNOUNCEMENT, [announce.name()])
dic = {cvp.opcode: [cvp]}
# missing arg here
cvp2 = ConditionWithArgs(
ConditionOpcode.CREATE_PUZZLE_ANNOUNCEMENT,
[],
)
dic2 = {cvp.opcode: [cvp2]}
spend_bundle1 = generate_test_spend_bundle(coin_1, dic)
spend_bundle2 = generate_test_spend_bundle(coin_2, dic2)
return SpendBundle.aggregate([spend_bundle1, spend_bundle2])
blocks, bundle, status, err = await self.condition_tester2(two_nodes, test_fun)
mempool_bundle = full_node_1.full_node.mempool_manager.get_spendbundle(bundle.name())
assert mempool_bundle is None
assert status == MempoolInclusionStatus.FAILED
assert err == Err.GENERATOR_RUNTIME_ERROR
@pytest.mark.asyncio
async def test_invalid_puzzle_announcement_rejected(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
@ -702,7 +1004,6 @@ class TestMempoolManager:
)
dic2 = {cvp.opcode: [cvp2]}
spend_bundle1 = generate_test_spend_bundle(coin_1, dic)
spend_bundle2 = generate_test_spend_bundle(coin_2, dic2)
return SpendBundle.aggregate([spend_bundle1, spend_bundle2])
@ -732,7 +1033,6 @@ class TestMempoolManager:
)
dic2 = {cvp.opcode: [cvp2]}
spend_bundle1 = generate_test_spend_bundle(coin_1, dic)
spend_bundle2 = generate_test_spend_bundle(coin_2, dic2)
return SpendBundle.aggregate([spend_bundle1, spend_bundle2])
@ -758,6 +1058,29 @@ class TestMempoolManager:
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_assert_fee_condition_garbage(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
# garbage at the end of the arguments is ignored
cvp = ConditionWithArgs(ConditionOpcode.RESERVE_FEE, [int_to_bytes(10), b"garbage"])
dic = {cvp.opcode: [cvp]}
blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic, fee=10)
mempool_bundle = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name())
assert mempool_bundle is not None
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_assert_fee_condition_missing_arg(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
cvp = ConditionWithArgs(ConditionOpcode.RESERVE_FEE, [])
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
@pytest.mark.asyncio
async def test_assert_fee_condition_negative_fee(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
@ -965,6 +1288,38 @@ class TestMempoolManager:
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_my_parent_garbage(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
blocks = await full_node_1.get_all_full_blocks()
coin = list(blocks[-1].get_included_reward_coins())[0]
# garbage at the end of the arguments list is allowed but stripped
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_MY_PARENT_ID, [coin.parent_coin_info, b"garbage"])
dic = {cvp.opcode: [cvp]}
blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic, coin=coin)
sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name())
assert sb1 is spend_bundle1
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_my_parent_missing_arg(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
blocks = await full_node_1.get_all_full_blocks()
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_MY_PARENT_ID, [])
dic = {cvp.opcode: [cvp]}
blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic)
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
@pytest.mark.asyncio
async def test_invalid_my_parent(self, two_nodes):
@ -998,6 +1353,38 @@ class TestMempoolManager:
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_my_puzhash_garbage(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
blocks = await full_node_1.get_all_full_blocks()
coin = list(blocks[-1].get_included_reward_coins())[0]
# garbage at the end of the arguments list is allowed but stripped
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_MY_PUZZLEHASH, [coin.puzzle_hash, b"garbage"])
dic = {cvp.opcode: [cvp]}
blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic, coin=coin)
sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name())
assert sb1 is spend_bundle1
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_my_puzhash_missing_arg(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
blocks = await full_node_1.get_all_full_blocks()
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_MY_PUZZLEHASH, [])
dic = {cvp.opcode: [cvp]}
blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic)
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
@pytest.mark.asyncio
async def test_invalid_my_puzhash(self, two_nodes):
@ -1030,6 +1417,38 @@ class TestMempoolManager:
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_my_amount_garbage(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
blocks = await full_node_1.get_all_full_blocks()
coin = list(blocks[-1].get_included_reward_coins())[0]
# garbage at the end of the arguments list is allowed but stripped
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_MY_AMOUNT, [int_to_bytes(coin.amount), b"garbage"])
dic = {cvp.opcode: [cvp]}
blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic, coin=coin)
sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name())
assert sb1 is spend_bundle1
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
@pytest.mark.asyncio
async def test_my_amount_missing_arg(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
blocks = await full_node_1.get_all_full_blocks()
cvp = ConditionWithArgs(ConditionOpcode.ASSERT_MY_AMOUNT, [])
dic = {cvp.opcode: [cvp]}
blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic)
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
@pytest.mark.asyncio
async def test_invalid_my_amount(self, two_nodes):
@ -1074,3 +1493,333 @@ class TestMempoolManager:
assert sb1 is None
assert status == MempoolInclusionStatus.FAILED
assert err == Err.ASSERT_MY_AMOUNT_FAILED
@pytest.mark.asyncio
async def test_unknown_condition(self, two_nodes):
full_node_1, full_node_2, server_1, server_2 = two_nodes
cvp = ConditionWithArgs(ConditionOpcode.UNKNOWN, [])
dic = {cvp.opcode: [cvp]}
blocks, spend_bundle1, peer, status, err = await self.condition_tester(two_nodes, dic)
sb1 = full_node_1.full_node.mempool_manager.get_spendbundle(spend_bundle1.name())
assert sb1 is None
assert status == MempoolInclusionStatus.FAILED
assert err == Err.INVALID_CONDITION
class TestConditionParser:
def test_parse_condition_agg_sig(self):
valid_pubkey = b"b" * 48
short_pubkey = b"b" * 47
long_pubkey = b"b" * 49
valid_message = b"a" * 1024
long_message = b"a" * 1025
empty_message = b""
for condition_code in [ConditionOpcode.AGG_SIG_UNSAFE, ConditionOpcode.AGG_SIG_ME]:
cost, args = parse_condition_args(SExp.to([valid_pubkey, valid_message]), condition_code)
assert cost == ConditionCost.AGG_SIG.value
assert args == [valid_pubkey, valid_message]
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([valid_pubkey, long_message]), condition_code)
# empty messages are allowed
cost, args = parse_condition_args(SExp.to([valid_pubkey, empty_message]), condition_code)
assert cost == ConditionCost.AGG_SIG.value
assert args == [valid_pubkey, empty_message]
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([short_pubkey, valid_message]), condition_code)
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([long_pubkey, valid_message]), condition_code)
# missing message argument
with pytest.raises(EvalError):
cost, args = parse_condition_args(SExp.to([valid_pubkey]), condition_code)
# missing all arguments
with pytest.raises(EvalError):
cost, args = parse_condition_args(SExp.to([]), condition_code)
# garbage at the end of the arguments list is allowed but stripped
cost, args = parse_condition_args(SExp.to([valid_pubkey, valid_message, b"garbage"]), condition_code)
assert cost == ConditionCost.AGG_SIG.value
assert args == [valid_pubkey, valid_message]
def test_parse_condition_create_coin(self):
valid_hash = b"b" * 32
short_hash = b"b" * 31
long_hash = b"b" * 33
valid_amount = int_to_bytes(1000000000)
# this is greater than max coin amount
large_amount = int_to_bytes(2 ** 64)
leading_zeros_amount = bytes([0] * 100) + int_to_bytes(1000000000)
negative_amount = int_to_bytes(-1000)
# this ist still -1, but just with a lot of (redundant) 0xff bytes
# prepended
large_negative_amount = bytes([0xFF] * 100) + int_to_bytes(-1)
cost, args = parse_condition_args(SExp.to([valid_hash, valid_amount]), ConditionOpcode.CREATE_COIN)
assert cost == ConditionCost.CREATE_COIN.value
assert args == [valid_hash, valid_amount]
cost, args = parse_condition_args(SExp.to([valid_hash, leading_zeros_amount]), ConditionOpcode.CREATE_COIN)
assert cost == ConditionCost.CREATE_COIN.value
# the amount will have its leading zeros stripped
assert args == [valid_hash, valid_amount]
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([valid_hash, large_amount]), ConditionOpcode.CREATE_COIN)
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([short_hash, valid_amount]), ConditionOpcode.CREATE_COIN)
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([long_hash, valid_amount]), ConditionOpcode.CREATE_COIN)
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([valid_hash, negative_amount]), ConditionOpcode.CREATE_COIN)
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([valid_hash, large_negative_amount]), ConditionOpcode.CREATE_COIN)
# missing amount
with pytest.raises(EvalError):
cost, args = parse_condition_args(SExp.to([valid_hash]), ConditionOpcode.CREATE_COIN)
# missing everything
with pytest.raises(EvalError):
cost, args = parse_condition_args(SExp.to([]), ConditionOpcode.CREATE_COIN)
# garbage at the end of the arguments list is allowed but stripped
cost, args = parse_condition_args(SExp.to([valid_hash, valid_amount, b"garbage"]), ConditionOpcode.CREATE_COIN)
assert cost == ConditionCost.CREATE_COIN.value
assert args == [valid_hash, valid_amount]
def test_parse_condition_seconds(self):
valid_timestamp = int_to_bytes(100)
leading_zeros_timestamp = bytes([0] * 100) + int_to_bytes(100)
negative_timestamp = int_to_bytes(-100)
large_negative_timestamp = bytes([0xFF] * 100) + int_to_bytes(-1)
for condition_code in [ConditionOpcode.ASSERT_SECONDS_ABSOLUTE, ConditionOpcode.ASSERT_SECONDS_RELATIVE]:
cost, args = parse_condition_args(SExp.to([valid_timestamp]), condition_code)
assert cost == 0
assert args == [valid_timestamp]
cost, args = parse_condition_args(SExp.to([leading_zeros_timestamp]), condition_code)
assert cost == 0
assert args == [valid_timestamp]
# a condition with a negative timestamp is always true
cost, args = parse_condition_args(SExp.to([negative_timestamp]), condition_code)
assert cost == 0
assert args is None
cost, args = parse_condition_args(SExp.to([large_negative_timestamp]), condition_code)
assert cost == 0
assert args is None
# garbage at the end of the arguments list is allowed but stripped
cost, args = parse_condition_args(SExp.to([valid_timestamp, b"garbage"]), condition_code)
assert cost == 0
assert args == [valid_timestamp]
# missing argument
with pytest.raises(EvalError):
cost, args = parse_condition_args(SExp.to([]), condition_code)
def test_parse_condition_height(self):
valid_height = int_to_bytes(100)
leading_zeros_height = bytes([0] * 100) + int_to_bytes(100)
negative_height = int_to_bytes(-100)
large_negative_height = bytes([0xFF] * 100) + int_to_bytes(-1)
for condition_code in [ConditionOpcode.ASSERT_HEIGHT_ABSOLUTE, ConditionOpcode.ASSERT_HEIGHT_RELATIVE]:
cost, args = parse_condition_args(SExp.to([valid_height]), condition_code)
assert cost == 0
assert args == [valid_height]
cost, args = parse_condition_args(SExp.to([leading_zeros_height]), condition_code)
assert cost == 0
assert args == [valid_height]
# a condition with a negative height is always true
cost, args = parse_condition_args(SExp.to([negative_height]), condition_code)
assert cost == 0
assert args is None
cost, args = parse_condition_args(SExp.to([large_negative_height]), condition_code)
assert cost == 0
assert args is None
# garbage at the end of the arguments list is allowed but stripped
cost, args = parse_condition_args(SExp.to([valid_height, b"garbage"]), condition_code)
assert cost == 0
assert args == [valid_height]
# missing argument
with pytest.raises(EvalError):
cost, args = parse_condition_args(SExp.to([]), condition_code)
def test_parse_condition_coin_id(self):
valid_coin_id = b"a" * 32
short_coin_id = b"a" * 31
long_coin_id = b"a" * 33
for condition_code in [ConditionOpcode.ASSERT_MY_COIN_ID, ConditionOpcode.ASSERT_MY_PARENT_ID]:
cost, args = parse_condition_args(SExp.to([valid_coin_id]), condition_code)
assert cost == 0
assert args == [valid_coin_id]
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([short_coin_id]), condition_code)
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([long_coin_id]), condition_code)
# garbage at the end of the arguments list is allowed but stripped
cost, args = parse_condition_args(SExp.to([valid_coin_id, b"garbage"]), condition_code)
assert cost == 0
assert args == [valid_coin_id]
with pytest.raises(EvalError):
cost, args = parse_condition_args(SExp.to([]), condition_code)
def test_parse_condition_fee(self):
valid_fee = int_to_bytes(100)
leading_zeros_fee = bytes([0] * 100) + int_to_bytes(100)
negative_fee = int_to_bytes(-100)
large_negative_fee = bytes([0xFF] * 100) + int_to_bytes(-1)
large_fee = int_to_bytes(2 ** 64)
cost, args = parse_condition_args(SExp.to([valid_fee]), ConditionOpcode.RESERVE_FEE)
assert cost == 0
assert args == [valid_fee]
cost, args = parse_condition_args(SExp.to([leading_zeros_fee]), ConditionOpcode.RESERVE_FEE)
assert cost == 0
assert args == [valid_fee]
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([negative_fee]), ConditionOpcode.RESERVE_FEE)
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([large_fee]), ConditionOpcode.RESERVE_FEE)
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([large_negative_fee]), ConditionOpcode.RESERVE_FEE)
# garbage at the end of the arguments list is allowed but stripped
cost, args = parse_condition_args(SExp.to([valid_fee, b"garbage"]), ConditionOpcode.RESERVE_FEE)
assert cost == 0
assert args == [valid_fee]
# missing argument
with pytest.raises(EvalError):
cost, args = parse_condition_args(SExp.to([]), ConditionOpcode.RESERVE_FEE)
def test_parse_condition_create_announcement(self):
valid_msg = b"a" * 1024
long_msg = b"a" * 1025
empty_msg = b""
for condition_code in [ConditionOpcode.CREATE_COIN_ANNOUNCEMENT, ConditionOpcode.CREATE_PUZZLE_ANNOUNCEMENT]:
cost, args = parse_condition_args(SExp.to([valid_msg]), condition_code)
assert cost == 0
assert args == [valid_msg]
cost, args = parse_condition_args(SExp.to([empty_msg]), condition_code)
assert cost == 0
assert args == [empty_msg]
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([long_msg]), condition_code)
# missing argument
with pytest.raises(EvalError):
cost, args = parse_condition_args(SExp.to([]), condition_code)
def test_parse_condition_assert_announcement(self):
valid_hash = b"b" * 32
short_hash = b"b" * 31
long_hash = b"b" * 33
for condition_code in [
ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT,
ConditionOpcode.ASSERT_PUZZLE_ANNOUNCEMENT,
ConditionOpcode.ASSERT_MY_PUZZLEHASH,
]:
cost, args = parse_condition_args(SExp.to([valid_hash]), condition_code)
assert cost == 0
assert args == [valid_hash]
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([short_hash]), condition_code)
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([long_hash]), condition_code)
# missing argument
with pytest.raises(EvalError):
cost, args = parse_condition_args(SExp.to([]), condition_code)
def test_parse_condition_my_amount(self):
valid_amount = int_to_bytes(100)
leading_zeros_amount = bytes([0] * 100) + int_to_bytes(100)
negative_amount = int_to_bytes(-100)
large_negative_amount = bytes([0xFF] * 100) + int_to_bytes(-1)
large_amount = int_to_bytes(2 ** 64)
cost, args = parse_condition_args(SExp.to([valid_amount]), ConditionOpcode.ASSERT_MY_AMOUNT)
assert cost == 0
assert args == [valid_amount]
cost, args = parse_condition_args(SExp.to([leading_zeros_amount]), ConditionOpcode.ASSERT_MY_AMOUNT)
assert cost == 0
assert args == [valid_amount]
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([negative_amount]), ConditionOpcode.ASSERT_MY_AMOUNT)
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([large_amount]), ConditionOpcode.ASSERT_MY_AMOUNT)
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([large_negative_amount]), ConditionOpcode.ASSERT_MY_AMOUNT)
# garbage at the end of the arguments list is allowed but stripped
cost, args = parse_condition_args(SExp.to([valid_amount, b"garbage"]), ConditionOpcode.ASSERT_MY_AMOUNT)
assert cost == 0
assert args == [valid_amount]
# missing argument
with pytest.raises(EvalError):
cost, args = parse_condition_args(SExp.to([]), ConditionOpcode.ASSERT_MY_AMOUNT)
def test_parse_unknown_condition(self):
for opcode in [129, 0, 1, 1000, 74]:
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([b"test"]), opcode)
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([b"foo", b"bar"]), opcode)
with pytest.raises(ValidationError):
cost, args = parse_condition_args(SExp.to([]), opcode)

View File

@ -75,7 +75,9 @@ class TestCostCalculation:
assert spend_bundle is not None
program: BlockGenerator = simple_solution_generator(spend_bundle)
npc_result: NPCResult = get_name_puzzle_conditions(program, test_constants.MAX_BLOCK_COST_CLVM, False)
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
)
cost = calculate_cost_of_program(program.program, npc_result, test_constants.COST_PER_BYTE)
@ -130,9 +132,13 @@ class TestCostCalculation:
).as_bin()
)
generator = BlockGenerator(program, [])
npc_result: NPCResult = get_name_puzzle_conditions(generator, test_constants.MAX_BLOCK_COST_CLVM, True)
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
)
assert npc_result.error is not None
npc_result: NPCResult = get_name_puzzle_conditions(generator, test_constants.MAX_BLOCK_COST_CLVM, False)
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
)
assert npc_result.error is None
coin_name = npc_result.npc_list[0].coin_name
@ -151,9 +157,13 @@ class TestCostCalculation:
# mode, the unknown operator should be treated as if it returns ().
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, True)
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
)
assert npc_result.error is not None
npc_result: NPCResult = get_name_puzzle_conditions(generator, test_constants.MAX_BLOCK_COST_CLVM, False)
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
)
assert npc_result.error is None
@pytest.mark.asyncio
@ -164,7 +174,9 @@ class TestCostCalculation:
start_time = time.time()
generator = BlockGenerator(program, [])
npc_result = get_name_puzzle_conditions(generator, test_constants.MAX_BLOCK_COST_CLVM, 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
)
end_time = time.time()
duration = end_time - start_time
assert npc_result.error is None
@ -189,14 +201,14 @@ class TestCostCalculation:
# ensure we fail if the program exceeds the cost
generator = BlockGenerator(program, [])
npc_result: NPCResult = get_name_puzzle_conditions(generator, 10000000, False)
npc_result: NPCResult = get_name_puzzle_conditions(generator, 10000000, cost_per_byte=0, safe_mode=False)
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, False)
npc_result: NPCResult = get_name_puzzle_conditions(generator, 20000000, cost_per_byte=0, safe_mode=False)
assert npc_result.error is None
assert npc_result.clvm_cost > 10000000

View File

@ -16,6 +16,7 @@ from chia.util.ints import uint32
from chia.wallet.puzzles.load_clvm import load_clvm
MAX_COST = int(1e15)
COST_PER_BYTE = int(12000)
DESERIALIZE_MOD = load_clvm("chialisp_deserialisation.clvm", package_or_requirement="chia.wallet.puzzles")
@ -101,7 +102,7 @@ class TestROM(TestCase):
cost, r = run_generator(gen, max_cost=MAX_COST)
print(r)
npc_result = get_name_puzzle_conditions(gen, max_cost=MAX_COST, safe_mode=False)
npc_result = get_name_puzzle_conditions(gen, max_cost=MAX_COST, cost_per_byte=COST_PER_BYTE, safe_mode=False)
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, safe_mode=False
block: FullBlock, max_cost: int, cost_per_byte: int, safe_mode=False
) -> Tuple[List[bytes32], List[Coin]]:
removals: List[bytes32] = []
additions: List[Coin] = []
@ -19,7 +19,9 @@ def run_and_get_removals_and_additions(
return [], []
if block.transactions_generator is not None:
npc_result = get_name_puzzle_conditions(BlockGenerator(block.transactions_generator, []), max_cost, safe_mode)
npc_result = get_name_puzzle_conditions(
BlockGenerator(block.transactions_generator, []), max_cost, cost_per_byte=cost_per_byte, safe_mode=safe_mode
)
# build removals list
for npc in npc_result.npc_list:
removals.append(npc.coin_name)

View File

@ -22,23 +22,6 @@ from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import (
puzzle_for_pk,
solution_for_conditions,
)
from chia.wallet.puzzles.puzzle_utils import (
make_assert_aggsig_condition,
make_assert_coin_announcement,
make_assert_puzzle_announcement,
make_assert_relative_height_exceeds_condition,
make_assert_absolute_height_exceeds_condition,
make_assert_my_coin_id_condition,
make_assert_absolute_seconds_exceeds_condition,
make_assert_relative_seconds_exceeds_condition,
make_create_coin_announcement,
make_create_puzzle_announcement,
make_create_coin_condition,
make_reserve_fee_condition,
make_assert_my_parent_id,
make_assert_my_puzzlehash,
make_assert_my_amount,
)
DEFAULT_SEED = b"seed" * 8
assert len(DEFAULT_SEED) == 32
@ -103,36 +86,7 @@ class WalletTool:
for con_list in condition_dic.values():
for cvp in con_list:
if cvp.opcode == ConditionOpcode.CREATE_COIN:
ret.append(make_create_coin_condition(cvp.vars[0], cvp.vars[1]))
if cvp.opcode == ConditionOpcode.CREATE_COIN_ANNOUNCEMENT:
ret.append(make_create_coin_announcement(cvp.vars[0]))
if cvp.opcode == ConditionOpcode.CREATE_PUZZLE_ANNOUNCEMENT:
ret.append(make_create_puzzle_announcement(cvp.vars[0]))
if cvp.opcode == ConditionOpcode.AGG_SIG_UNSAFE:
ret.append(make_assert_aggsig_condition(cvp.vars[0]))
if cvp.opcode == ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT:
ret.append(make_assert_coin_announcement(cvp.vars[0]))
if cvp.opcode == ConditionOpcode.ASSERT_PUZZLE_ANNOUNCEMENT:
ret.append(make_assert_puzzle_announcement(cvp.vars[0]))
if cvp.opcode == ConditionOpcode.ASSERT_SECONDS_ABSOLUTE:
ret.append(make_assert_absolute_seconds_exceeds_condition(cvp.vars[0]))
if cvp.opcode == ConditionOpcode.ASSERT_SECONDS_RELATIVE:
ret.append(make_assert_relative_seconds_exceeds_condition(cvp.vars[0]))
if cvp.opcode == ConditionOpcode.ASSERT_MY_COIN_ID:
ret.append(make_assert_my_coin_id_condition(cvp.vars[0]))
if cvp.opcode == ConditionOpcode.ASSERT_HEIGHT_ABSOLUTE:
ret.append(make_assert_absolute_height_exceeds_condition(cvp.vars[0]))
if cvp.opcode == ConditionOpcode.ASSERT_HEIGHT_RELATIVE:
ret.append(make_assert_relative_height_exceeds_condition(cvp.vars[0]))
if cvp.opcode == ConditionOpcode.RESERVE_FEE:
ret.append(make_reserve_fee_condition(cvp.vars[0]))
if cvp.opcode == ConditionOpcode.ASSERT_MY_PARENT_ID:
ret.append(make_assert_my_parent_id(cvp.vars[0]))
if cvp.opcode == ConditionOpcode.ASSERT_MY_PUZZLEHASH:
ret.append(make_assert_my_puzzlehash(cvp.vars[0]))
if cvp.opcode == ConditionOpcode.ASSERT_MY_AMOUNT:
ret.append(make_assert_my_amount(cvp.vars[0]))
ret.append([cvp.opcode.value] + cvp.vars)
return solution_for_conditions(Program.to(ret))
def generate_unsigned_transaction(