mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2024-09-11 18:58:59 +03:00
Merge branch 'main' into pools.testnet9
This commit is contained in:
commit
b7bf08fd77
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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])
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 [],
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
19
tests/check_pytest_monitor_output.py
Executable file
19
tests/check_pytest_monitor_output.py
Executable 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)
|
@ -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 = [], []
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)])
|
||||
|
@ -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)
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user