chia-blockchain/chia/full_node/mempool_check_conditions.py
Arvid Norberg 1b6201852a
bump chia_rs to 0.2.2 (#14533)
* bump chia_rs to latest version

* add new error codes for ASSERT_BEFORE_* condition failures, ASSERT_CONCURRENT_SPEND failing, ASSERT_CONCURRENT_PUZZLE_FAILED and for impossible constraints

* add new condition opcodes for ASSERT_BEFORE_*, ASSERT_CONCURRENT_SPEND and ASSERT_CONCURRENT_PUZZLE

* fixup Spend and SpendBundleCondition types

* fixup tests that otherwise fail with MINTING_COIN

* fix test that otherwise would fail with RESERVE_FEE_CONDITION_FAILED

* remove use of NO_NEG_DIV flag. It's now implied

* remove duplicate test parameters
2023-02-10 15:14:56 -06:00

141 lines
5.2 KiB
Python

from __future__ import annotations
import logging
from typing import Dict, List, Optional, Tuple
from chia_rs import LIMIT_STACK, MEMPOOL_MODE
from chia_rs import get_puzzle_and_solution_for_coin as get_puzzle_and_solution_for_coin_rust
from chia_rs import run_chia_program
from clvm.casts import int_from_bytes
from chia.consensus.cost_calculator import NPCResult
from chia.consensus.default_constants import DEFAULT_CONSTANTS
from chia.full_node.generator import setup_generator_args
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.serialized_program import SerializedProgram
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.coin_record import CoinRecord
from chia.types.coin_spend import CoinSpend
from chia.types.generator_types import BlockGenerator
from chia.types.spend_bundle_conditions import SpendBundleConditions
from chia.util.errors import Err
from chia.util.ints import uint16, uint32, uint64
from chia.wallet.puzzles.load_clvm import load_serialized_clvm_maybe_recompile
from chia.wallet.puzzles.rom_bootstrap_generator import get_generator
GENERATOR_MOD = get_generator()
DESERIALIZE_MOD = load_serialized_clvm_maybe_recompile(
"chialisp_deserialisation.clvm", package_or_requirement="chia.wallet.puzzles"
)
log = logging.getLogger(__name__)
def get_name_puzzle_conditions(
generator: BlockGenerator, max_cost: int, *, cost_per_byte: int, mempool_mode: bool, height: Optional[uint32] = None
) -> NPCResult:
block_program, block_program_args = setup_generator_args(generator)
size_cost = len(bytes(generator.program)) * cost_per_byte
max_cost -= size_cost
if max_cost < 0:
return NPCResult(uint16(Err.INVALID_BLOCK_COST.value), None, uint64(0))
# in mempool mode, the height doesn't matter, because it's always strict.
# But otherwise, height must be specified to know which rules to apply
assert mempool_mode or height is not None
if mempool_mode:
flags = MEMPOOL_MODE
elif height is not None and height >= DEFAULT_CONSTANTS.SOFT_FORK_HEIGHT:
flags = LIMIT_STACK
else:
flags = 0
try:
err, result = GENERATOR_MOD.run_as_generator(max_cost, flags, block_program, block_program_args)
assert (err is None) != (result is None)
if err is not None:
return NPCResult(uint16(err), None, uint64(0))
else:
assert result is not None
return NPCResult(None, result, uint64(result.cost + size_cost))
except BaseException:
log.exception("get_name_puzzle_condition failed")
return NPCResult(uint16(Err.GENERATOR_RUNTIME_ERROR.value), None, uint64(0))
def get_puzzle_and_solution_for_coin(
generator: BlockGenerator, coin: Coin
) -> Tuple[Optional[Exception], Optional[SerializedProgram], Optional[SerializedProgram]]:
try:
args = bytearray(b"\xff")
args += bytes(DESERIALIZE_MOD)
args += b"\xff"
args += bytes(Program.to([bytes(a) for a in generator.generator_refs]))
args += b"\x80\x80"
puzzle, solution = get_puzzle_and_solution_for_coin_rust(
bytes(generator.program),
bytes(args),
DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM,
coin.parent_coin_info,
coin.amount,
coin.puzzle_hash,
)
return None, SerializedProgram.from_bytes(puzzle), SerializedProgram.from_bytes(solution)
except Exception as e:
return e, None, None
def get_spends_for_block(generator: BlockGenerator) -> List[CoinSpend]:
args = bytearray(b"\xff")
args += bytes(DESERIALIZE_MOD)
args += b"\xff"
args += bytes(Program.to([bytes(a) for a in generator.generator_refs]))
args += b"\x80\x80"
_, ret = run_chia_program(
bytes(generator.program),
bytes(args),
DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM,
0,
)
spends: List[CoinSpend] = []
for spend in Program.to(ret).first().as_iter():
parent, puzzle, amount, solution = spend.as_iter()
puzzle_hash = puzzle.get_tree_hash()
coin = Coin(parent.atom, puzzle_hash, int_from_bytes(amount.atom))
spends.append(CoinSpend(coin, puzzle, solution))
return spends
def mempool_check_time_locks(
removal_coin_records: Dict[bytes32, CoinRecord],
bundle_conds: SpendBundleConditions,
prev_transaction_block_height: uint32,
timestamp: uint64,
) -> Optional[Err]:
"""
Check all time and height conditions against current state.
"""
if prev_transaction_block_height < bundle_conds.height_absolute:
return Err.ASSERT_HEIGHT_ABSOLUTE_FAILED
if timestamp < bundle_conds.seconds_absolute:
return Err.ASSERT_SECONDS_ABSOLUTE_FAILED
for spend in bundle_conds.spends:
unspent = removal_coin_records[bytes32(spend.coin_id)]
if spend.height_relative is not None:
if prev_transaction_block_height < unspent.confirmed_block_index + spend.height_relative:
return Err.ASSERT_HEIGHT_RELATIVE_FAILED
if timestamp < unspent.timestamp + spend.seconds_relative:
return Err.ASSERT_SECONDS_RELATIVE_FAILED
return None