diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py index 51eadd207e91..f9d3225ae7c1 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -2227,7 +2227,7 @@ async def did_find_lost_did(self, request: Dict[str, Any]) -> EndpointResult: get_inner_puzzle_from_singleton(coin_spend.puzzle_reveal.to_program()), coin_state, ) - hinted_coins = compute_spend_hints_and_additions(coin_spend) + hinted_coins, _ = compute_spend_hints_and_additions(coin_spend) # Hint is required, if it doesn't have any hint then it should be invalid hint: Optional[bytes32] = None for hinted_coin in hinted_coins.values(): diff --git a/chia/wallet/cat_wallet/cat_utils.py b/chia/wallet/cat_wallet/cat_utils.py index eb92dfca2286..75b53109955e 100644 --- a/chia/wallet/cat_wallet/cat_utils.py +++ b/chia/wallet/cat_wallet/cat_utils.py @@ -21,6 +21,7 @@ ANYONE_CAN_SPEND_PUZZLE = Program.to(1) # simply return the conditions CAT_MOD = load_clvm_maybe_recompile("cat_v2.clsp", package_or_requirement="chia.wallet.cat_wallet.puzzles") CAT_MOD_HASH = CAT_MOD.get_tree_hash() +CAT_MOD_HASH_HASH: bytes32 = Program.to(CAT_MOD_HASH).get_tree_hash() def empty_program() -> Program: diff --git a/chia/wallet/trade_manager.py b/chia/wallet/trade_manager.py index 5af9a4b3c820..c0eb2845cda6 100644 --- a/chia/wallet/trade_manager.py +++ b/chia/wallet/trade_manager.py @@ -83,6 +83,7 @@ class TradeManager: wallet_state_manager: Any log: logging.Logger trade_store: TradeStore + most_recently_deserialized_trade: Optional[Tuple[bytes32, Offer]] @staticmethod async def create( @@ -98,6 +99,7 @@ async def create( self.wallet_state_manager = wallet_state_manager self.trade_store = await TradeStore.create(db_wrapper) + self.most_recently_deserialized_trade = None return self async def get_offers_with_status(self, status: TradeStatus) -> List[TradeRecord]: @@ -142,7 +144,14 @@ async def coins_of_interest_farmed( if coin_state.spent_height is None: self.log.error(f"Coin: {coin_state.coin}, has not been spent so trade can remain valid") # Then let's filter the offer into coins that WE offered - offer = Offer.from_bytes(trade.offer) + if ( + self.most_recently_deserialized_trade is not None + and trade.trade_id == self.most_recently_deserialized_trade[0] + ): + offer = self.most_recently_deserialized_trade[1] + else: + offer = Offer.from_bytes(trade.offer) + self.most_recently_deserialized_trade = (trade.trade_id, offer) primary_coin_ids = [c.name() for c in offer.removals()] # TODO: Add `WalletCoinStore.get_coins`. result = await self.wallet_state_manager.coin_store.get_coin_records( @@ -613,18 +622,24 @@ async def check_offer_validity(self, offer: Offer, peer: WSChiaConnection) -> bo async def calculate_tx_records_for_offer(self, offer: Offer, validate: bool) -> List[TransactionRecord]: if validate: final_spend_bundle: SpendBundle = offer.to_valid_spend() + hint_dict: Dict[bytes32, bytes32] = {} + additions_dict: Dict[bytes32, Coin] = {} + for hinted_coins, _ in ( + compute_spend_hints_and_additions(spend) for spend in final_spend_bundle.coin_spends + ): + hint_dict.update({id: hc.hint for id, hc in hinted_coins.items() if hc.hint is not None}) + additions_dict.update({id: hc.coin for id, hc in hinted_coins.items()}) + all_additions: List[Coin] = list(a for a in additions_dict.values()) else: final_spend_bundle = offer._bundle + hint_dict = offer.hints() + all_additions = offer.additions() settlement_coins: List[Coin] = [c for coins in offer.get_offered_coins().values() for c in coins] settlement_coin_ids: List[bytes32] = [c.name() for c in settlement_coins] - hint_dict: Dict[bytes32, bytes32] = {} - additions_dict: Dict[bytes32, Coin] = {} - for hinted_coins in (compute_spend_hints_and_additions(spend) for spend in final_spend_bundle.coin_spends): - hint_dict.update({id: hc.hint for id, hc in hinted_coins.items() if hc.hint is not None}) - additions_dict.update({id: hc.coin for id, hc in hinted_coins.items()}) + removals: List[Coin] = final_spend_bundle.removals() - additions: List[Coin] = list(a for a in additions_dict.values() if a not in removals) + additions: List[Coin] = list(a for a in all_additions if a not in removals) valid_times: ConditionValidTimes = parse_timelock_info( parse_conditions_non_consensus( condition diff --git a/chia/wallet/trading/offer.py b/chia/wallet/trading/offer.py index f3329dfd1f28..08802dd3d5ab 100644 --- a/chia/wallet/trading/offer.py +++ b/chia/wallet/trading/offer.py @@ -11,7 +11,7 @@ from chia.types.blockchain_format.coin import Coin, coin_as_list from chia.types.blockchain_format.program import INFINITE_COST, Program from chia.types.blockchain_format.sized_bytes import bytes32 -from chia.types.coin_spend import CoinSpend, compute_additions_with_cost +from chia.types.coin_spend import CoinSpend from chia.types.spend_bundle import SpendBundle from chia.util.bech32m import bech32_decode, bech32_encode, convertbits from chia.util.errors import Err, ValidationError @@ -29,6 +29,7 @@ from chia.wallet.puzzle_drivers import PuzzleInfo, Solver from chia.wallet.puzzles.load_clvm import load_clvm_maybe_recompile from chia.wallet.uncurried_puzzle import UncurriedPuzzle, uncurry_puzzle +from chia.wallet.util.compute_hints import compute_spend_hints_and_additions from chia.wallet.util.puzzle_compression import ( compress_object_with_puzzles, decompress_object_with_puzzles, @@ -76,6 +77,7 @@ class Offer: # this is a cache of the coin additions made by the SpendBundle (_bundle) # ordered by the coin being spent _additions: Dict[Coin, List[Coin]] = field(init=False) + _hints: Dict[bytes32, bytes32] = field(init=False) _offered_coins: Dict[Optional[bytes32], List[Coin]] = field(init=False) _final_spend_bundle: Optional[SpendBundle] = field(init=False) _conditions: Optional[Dict[Coin, List[Condition]]] = field(init=False) @@ -137,19 +139,22 @@ def __post_init__(self) -> None: # populate the _additions cache adds: Dict[Coin, List[Coin]] = {} + hints: Dict[bytes32, bytes32] = {} max_cost = DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM for cs in self._bundle.coin_spends: # you can't spend the same coin twice in the same SpendBundle assert cs.coin not in adds try: - coins, cost = compute_additions_with_cost(cs) + hinted_coins, cost = compute_spend_hints_and_additions(cs) max_cost -= cost - adds[cs.coin] = coins + adds[cs.coin] = [hc.coin for hc in hinted_coins.values()] + hints = {**hints, **{id: hc.hint for id, hc in hinted_coins.items() if hc.hint is not None}} except Exception: continue if max_cost < 0: raise ValidationError(Err.BLOCK_COST_EXCEEDS_MAX, "compute_additions for CoinSpend") object.__setattr__(self, "_additions", adds) + object.__setattr__(self, "_hints", hints) object.__setattr__(self, "_conditions", None) def conditions(self) -> Dict[Coin, List[Condition]]: @@ -185,6 +190,9 @@ def absolute_valid_times_ban_relatives(self) -> ConditionValidTimes: raise ValueError("Offers with relative timelocks are not currently supported") return valid_times + def hints(self) -> Dict[bytes32, bytes32]: + return self._hints + def additions(self) -> List[Coin]: return [c for additions in self._additions.values() for c in additions] diff --git a/chia/wallet/util/compute_hints.py b/chia/wallet/util/compute_hints.py index cab07ec143db..6962c60589b7 100644 --- a/chia/wallet/util/compute_hints.py +++ b/chia/wallet/util/compute_hints.py @@ -1,13 +1,16 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Dict, Optional +from typing import Dict, Optional, Tuple +from chia.consensus.condition_costs import ConditionCost +from chia.consensus.default_constants import DEFAULT_CONSTANTS from chia.types.blockchain_format.coin import Coin -from chia.types.blockchain_format.program import INFINITE_COST, Program +from chia.types.blockchain_format.program import Program from chia.types.blockchain_format.sized_bytes import bytes32 from chia.types.coin_spend import CoinSpend from chia.types.condition_opcodes import ConditionOpcode +from chia.util.errors import Err, ValidationError from chia.util.ints import uint64 @@ -17,21 +20,44 @@ class HintedCoin: hint: Optional[bytes32] -def compute_spend_hints_and_additions(cs: CoinSpend) -> Dict[bytes32, HintedCoin]: - _, result_program = cs.puzzle_reveal.run_with_cost(INFINITE_COST, cs.solution) +def compute_spend_hints_and_additions( + cs: CoinSpend, + *, + max_cost: int = DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, +) -> Tuple[Dict[bytes32, HintedCoin], int]: + cost, result_program = cs.puzzle_reveal.run_with_cost(max_cost, cs.solution) hinted_coins: Dict[bytes32, HintedCoin] = {} for condition in result_program.as_iter(): - if condition.at("f").atom == ConditionOpcode.CREATE_COIN: # It's a create coin: - coin: Coin = Coin(cs.coin.name(), bytes32(condition.at("rf").atom), uint64(condition.at("rrf").as_int())) - hint: Optional[bytes32] = None - if ( - condition.at("rrr") != Program.to(None) # There's more than two arguments - and condition.at("rrrf").atom is None # The 3rd argument is a cons - ): - potential_hint: bytes = condition.at("rrrff").atom - if len(potential_hint) == 32: - hint = bytes32(potential_hint) - hinted_coins[bytes32(coin.name())] = HintedCoin(coin, hint) - - return hinted_coins + if cost > max_cost: + raise ValidationError(Err.BLOCK_COST_EXCEEDS_MAX, "compute_spend_hints_and_additions() for CoinSpend") + atoms = condition.as_iter() + op = next(atoms).atom + if op in [ + ConditionOpcode.AGG_SIG_PARENT, + ConditionOpcode.AGG_SIG_PUZZLE, + ConditionOpcode.AGG_SIG_AMOUNT, + ConditionOpcode.AGG_SIG_PUZZLE_AMOUNT, + ConditionOpcode.AGG_SIG_PARENT_AMOUNT, + ConditionOpcode.AGG_SIG_PARENT_PUZZLE, + ConditionOpcode.AGG_SIG_UNSAFE, + ConditionOpcode.AGG_SIG_ME, + ]: + cost += ConditionCost.AGG_SIG.value + continue + if op != ConditionOpcode.CREATE_COIN.value: + continue + cost += ConditionCost.CREATE_COIN.value + + coin: Coin = Coin(cs.coin.name(), bytes32(condition.at("rf").atom), uint64(condition.at("rrf").as_int())) + hint: Optional[bytes32] = None + if ( + condition.at("rrr") != Program.to(None) # There's more than two arguments + and condition.at("rrrf").atom is None # The 3rd argument is a cons + ): + potential_hint: Optional[bytes] = condition.at("rrrff").atom + if potential_hint is not None and len(potential_hint) == 32: + hint = bytes32(potential_hint) + hinted_coins[bytes32(coin.name())] = HintedCoin(coin, hint) + + return hinted_coins, cost diff --git a/chia/wallet/vc_wallet/cr_cat_drivers.py b/chia/wallet/vc_wallet/cr_cat_drivers.py index 1d527ace3679..496b05b2fe56 100644 --- a/chia/wallet/vc_wallet/cr_cat_drivers.py +++ b/chia/wallet/vc_wallet/cr_cat_drivers.py @@ -21,12 +21,13 @@ from chia.wallet.puzzles.load_clvm import load_clvm_maybe_recompile from chia.wallet.puzzles.singleton_top_layer_v1_1 import SINGLETON_LAUNCHER_HASH, SINGLETON_MOD_HASH from chia.wallet.uncurried_puzzle import UncurriedPuzzle, uncurry_puzzle +from chia.wallet.util.curry_and_treehash import curry_and_treehash from chia.wallet.vc_wallet.vc_drivers import ( COVENANT_LAYER_HASH, EML_TP_COVENANT_ADAPTER_HASH, EXTIGENT_METADATA_LAYER_HASH, - GUARANTEED_NIL_TP, - P2_ANNOUNCED_DELEGATED_PUZZLE, + GUARANTEED_NIL_TP_HASH, + P2_ANNOUNCED_DELEGATED_PUZZLE_HASH, create_did_tp, create_eml_covenant_morpher, ) @@ -49,6 +50,46 @@ package_or_requirement="chia.wallet.vc_wallet.cr_puzzles", include_standard_libraries=True, ) +CREDENTIAL_STRUCT: Program = Program.to( + ( + ( + ( + SINGLETON_MOD_HASH, + SINGLETON_LAUNCHER_HASH, + ), + ( + EXTIGENT_METADATA_LAYER_HASH, + EML_TP_COVENANT_ADAPTER_HASH, + ), + ), + ( + curry_and_treehash( + Program.to((1, EXTIGENT_METADATA_LAYER_HASH)).get_tree_hash_precalc(EXTIGENT_METADATA_LAYER_HASH), + Program.to(EXTIGENT_METADATA_LAYER_HASH).get_tree_hash(), + Program.to(None).get_tree_hash(), + GUARANTEED_NIL_TP_HASH, + Program.to(GUARANTEED_NIL_TP_HASH).get_tree_hash(), + P2_ANNOUNCED_DELEGATED_PUZZLE_HASH, + ), + ( + Program.to( + int_to_bytes(2) + Program.to((1, COVENANT_LAYER_HASH)).get_tree_hash_precalc(COVENANT_LAYER_HASH) + ), + Program.to( + ( + [ + 4, + (1, create_eml_covenant_morpher(create_did_tp().get_tree_hash())), + [4, (1, create_did_tp()), 1], + ], + None, + ) + ).get_tree_hash(), + ), + ), + ), +) +CREDENTIAL_STRUCT_HASH: bytes32 = CREDENTIAL_STRUCT.get_tree_hash() # Basic drivers @@ -58,55 +99,33 @@ def construct_cr_layer( inner_puzzle: Program, ) -> Program: first_curry: Program = CREDENTIAL_RESTRICTION.curry( - Program.to( - ( - ( - ( - SINGLETON_MOD_HASH, - SINGLETON_LAUNCHER_HASH, - ), - ( - EXTIGENT_METADATA_LAYER_HASH, - EML_TP_COVENANT_ADAPTER_HASH, - ), - ), - ( - Program.to(EXTIGENT_METADATA_LAYER_HASH) - .curry( - Program.to(EXTIGENT_METADATA_LAYER_HASH).get_tree_hash(), - Program.to(None), - GUARANTEED_NIL_TP, - GUARANTEED_NIL_TP.get_tree_hash(), - P2_ANNOUNCED_DELEGATED_PUZZLE, - ) - .get_tree_hash_precalc( - EXTIGENT_METADATA_LAYER_HASH, Program.to(EXTIGENT_METADATA_LAYER_HASH).get_tree_hash() - ), - ( - Program.to( - int_to_bytes(2) - + Program.to((1, COVENANT_LAYER_HASH)).get_tree_hash_precalc(COVENANT_LAYER_HASH) - ), - Program.to( - ( - [ - 4, - (1, create_eml_covenant_morpher(create_did_tp().get_tree_hash())), - [4, (1, create_did_tp()), 1], - ], - None, - ) - ).get_tree_hash(), - ), - ), - ), - ), + CREDENTIAL_STRUCT, authorized_providers, proofs_checker, ) return first_curry.curry(first_curry.get_tree_hash(), inner_puzzle) +def construct_cr_layer_hash( + authorized_providers_hash: bytes32, + proofs_checker_hash: bytes32, + inner_puzzle_hash: bytes32, +) -> bytes32: + first_curry_hash: bytes32 = curry_and_treehash( + Program.to((1, CREDENTIAL_RESTRICTION_HASH)).get_tree_hash_precalc(CREDENTIAL_RESTRICTION_HASH), + CREDENTIAL_STRUCT_HASH, + authorized_providers_hash, + proofs_checker_hash, + ) + first_curry_hash_hash: bytes32 = Program.to(first_curry_hash).get_tree_hash() + final_hash: bytes32 = curry_and_treehash( + Program.to((1, first_curry_hash)).get_tree_hash_precalc(first_curry_hash), + first_curry_hash_hash, + inner_puzzle_hash, + ) + return final_hash + + def match_cr_layer( uncurried_puzzle: UncurriedPuzzle, ) -> Optional[Tuple[List[bytes32], Program, Program]]: diff --git a/chia/wallet/vc_wallet/cr_cat_wallet.py b/chia/wallet/vc_wallet/cr_cat_wallet.py index aba001173984..f1ac72949b9b 100644 --- a/chia/wallet/vc_wallet/cr_cat_wallet.py +++ b/chia/wallet/vc_wallet/cr_cat_wallet.py @@ -21,7 +21,7 @@ from chia.util.ints import uint8, uint32, uint64, uint128 from chia.util.misc import VersionedBlob from chia.wallet.cat_wallet.cat_info import CATCoinData, CRCATInfo -from chia.wallet.cat_wallet.cat_utils import CAT_MOD, construct_cat_puzzle +from chia.wallet.cat_wallet.cat_utils import CAT_MOD_HASH, CAT_MOD_HASH_HASH, construct_cat_puzzle from chia.wallet.cat_wallet.cat_wallet import CATWallet from chia.wallet.coin_selection import select_coins from chia.wallet.conditions import Condition, ConditionValidTimes, UnknownCondition, parse_timelock_info @@ -44,7 +44,7 @@ CRCATMetadata, CRCATVersion, ProofsChecker, - construct_cr_layer, + construct_cr_layer_hash, construct_pending_approval_state, ) from chia.wallet.vc_wallet.vc_drivers import VerifiedCredential @@ -212,7 +212,9 @@ async def add_crcat_coin(self, coin_spend: CoinSpend, coin: Coin, height: uint32 try: new_cr_cats: List[CRCAT] = CRCAT.get_next_from_coin_spend(coin_spend) hint_dict = { - id: hc.hint for id, hc in compute_spend_hints_and_additions(coin_spend).items() if hc.hint is not None + id: hc.hint + for id, hc in compute_spend_hints_and_additions(coin_spend)[0].items() + if hc.hint is not None } cr_cat: CRCAT = list(filter(lambda c: c.coin.name() == coin.name(), new_cr_cats))[0] if ( @@ -871,28 +873,41 @@ async def match_hinted_coin(self, coin: Coin, hint: bytes32) -> bool: This matches coins that are either CRCATs with the hint as the inner puzzle, or CRCATs in the pending approval state that will come to us once claimed. """ - return ( + authorized_providers_hash: bytes32 = Program.to(self.info.authorized_providers).get_tree_hash() + proofs_checker_hash: bytes32 = self.info.proofs_checker.as_program().get_tree_hash() + hint_inner_hash: bytes32 = construct_cr_layer_hash( + authorized_providers_hash, + proofs_checker_hash, + hint, + ) + if ( construct_cat_puzzle( - CAT_MOD, + Program.to(CAT_MOD_HASH), self.info.limitations_program_hash, - construct_cr_layer( - self.info.authorized_providers, - self.info.proofs_checker.as_program(), - hint, # type: ignore - ), - ).get_tree_hash_precalc(hint) + hint_inner_hash, # type: ignore + mod_code_hash=CAT_MOD_HASH_HASH, + ).get_tree_hash_precalc(hint, CAT_MOD_HASH, CAT_MOD_HASH_HASH, hint_inner_hash) == coin.puzzle_hash - or construct_cat_puzzle( - CAT_MOD, + ): + return True + + pending_approval_inner_hash: bytes32 = construct_cr_layer_hash( + authorized_providers_hash, + proofs_checker_hash, + construct_pending_approval_state(hint, uint64(coin.amount)).get_tree_hash(), + ) + if ( + construct_cat_puzzle( + Program.to(CAT_MOD_HASH), self.info.limitations_program_hash, - construct_cr_layer( - self.info.authorized_providers, - self.info.proofs_checker.as_program(), - construct_pending_approval_state(hint, uint64(coin.amount)), - ), - ).get_tree_hash() + pending_approval_inner_hash, # type: ignore + mod_code_hash=CAT_MOD_HASH_HASH, + ).get_tree_hash_precalc(CAT_MOD_HASH, CAT_MOD_HASH_HASH, pending_approval_inner_hash) == coin.puzzle_hash - ) + ): + return True + else: + return False if TYPE_CHECKING: diff --git a/chia/wallet/vc_wallet/vc_drivers.py b/chia/wallet/vc_wallet/vc_drivers.py index b499536a65db..9943d3f20dab 100644 --- a/chia/wallet/vc_wallet/vc_drivers.py +++ b/chia/wallet/vc_wallet/vc_drivers.py @@ -305,6 +305,7 @@ def solve_std_vc_backdoor( GUARANTEED_NIL_TP, P2_ANNOUNCED_DELEGATED_PUZZLE, ) +GUARANTEED_NIL_TP_HASH: bytes32 = GUARANTEED_NIL_TP.get_tree_hash() OWNERSHIP_LAYER_LAUNCHER_HASH = OWNERSHIP_LAYER_LAUNCHER.get_tree_hash() diff --git a/chia/wallet/wallet_state_manager.py b/chia/wallet/wallet_state_manager.py index 4c5665289a6e..db9b6aabbf10 100644 --- a/chia/wallet/wallet_state_manager.py +++ b/chia/wallet/wallet_state_manager.py @@ -1054,7 +1054,7 @@ async def handle_cat( :param fork_height: Current block height :return: Wallet ID & Wallet Type """ - hinted_coin = compute_spend_hints_and_additions(coin_spend)[coin_state.coin.name()] + hinted_coin = compute_spend_hints_and_additions(coin_spend)[0][coin_state.coin.name()] assert hinted_coin.hint is not None, f"hint missing for coin {hinted_coin.coin}" derivation_record = await self.puzzle_store.get_derivation_record_for_puzzle_hash(hinted_coin.hint) @@ -1161,7 +1161,7 @@ async def handle_did( inner_puzzle_hash = parent_data.p2_puzzle.get_tree_hash() self.log.info(f"parent: {parent_coin_state.coin.name()} inner_puzzle_hash for parent is {inner_puzzle_hash}") - hinted_coin = compute_spend_hints_and_additions(coin_spend)[coin_state.coin.name()] + hinted_coin = compute_spend_hints_and_additions(coin_spend)[0][coin_state.coin.name()] assert hinted_coin.hint is not None, f"hint missing for coin {hinted_coin.coin}" derivation_record = await self.puzzle_store.get_derivation_record_for_puzzle_hash(hinted_coin.hint) @@ -1358,7 +1358,7 @@ async def handle_dao_finished_proposals( async def get_dao_wallet_from_coinspend_hint( self, coin_spend: CoinSpend, coin_state: CoinState ) -> Optional[WalletIdentifier]: - hinted_coin = compute_spend_hints_and_additions(coin_spend)[coin_state.coin.name()] + hinted_coin = compute_spend_hints_and_additions(coin_spend)[0][coin_state.coin.name()] if hinted_coin: for wallet in self.wallets.values(): if wallet.type() == WalletType.DAO.value: diff --git a/tests/wallet/test_util.py b/tests/wallet/test_util.py index 70da8163623f..033c43f1bf1f 100644 --- a/tests/wallet/test_util.py +++ b/tests/wallet/test_util.py @@ -1,10 +1,15 @@ from __future__ import annotations +import pytest + from chia.consensus.default_constants import DEFAULT_CONSTANTS +from chia.types.blockchain_format.coin import Coin from chia.types.blockchain_format.program import Program -from chia.types.blockchain_format.sized_bytes import bytes32 +from chia.types.blockchain_format.sized_bytes import bytes32, bytes48 from chia.types.coin_spend import CoinSpend -from chia.wallet.util.compute_hints import compute_spend_hints_and_additions +from chia.util.errors import ValidationError +from chia.util.ints import uint64 +from chia.wallet.util.compute_hints import HintedCoin, compute_spend_hints_and_additions from chia.wallet.util.tx_config import ( DEFAULT_COIN_SELECTION_CONFIG, DEFAULT_TX_CONFIG, @@ -25,7 +30,25 @@ def test_compute_spend_hints_and_additions() -> None: Program.to(create_coin_args), ) expected_dict = {hinted_coin.coin.name(): hinted_coin for hinted_coin in hinted_coins} - assert compute_spend_hints_and_additions(coin_spend) == expected_dict + assert compute_spend_hints_and_additions(coin_spend)[0] == expected_dict + + not_hinted_coin = HintedCoin(Coin(parent_coin.coin.name(), bytes32([0] * 32), uint64(0)), None) + assert compute_spend_hints_and_additions( + CoinSpend(parent_coin.coin, Program.to(1), Program.to([[51, bytes32([0] * 32), 0, [["not", "a"], "hint"]]])) + )[0] == {not_hinted_coin.coin.name(): not_hinted_coin} + + with pytest.raises(ValidationError): + compute_spend_hints_and_additions( + CoinSpend( + parent_coin.coin, Program.to(1), Program.to([[51, bytes32([0] * 32), 0] for _ in range(0, 10000)]) + ) + ) + with pytest.raises(ValidationError): + compute_spend_hints_and_additions( + CoinSpend( + parent_coin.coin, Program.to(1), Program.to([[50, bytes48([0] * 48), b""] for _ in range(0, 10000)]) + ) + ) def test_cs_config() -> None: diff --git a/tests/wallet/vc_wallet/test_vc_wallet.py b/tests/wallet/vc_wallet/test_vc_wallet.py index 860058236446..9e6f84042e2b 100644 --- a/tests/wallet/vc_wallet/test_vc_wallet.py +++ b/tests/wallet/vc_wallet/test_vc_wallet.py @@ -243,7 +243,8 @@ async def check_length(length: int, func: Callable[..., Awaitable[Any]], *args: "flags_needed": cr_cat_wallet_0.info.proofs_checker.flags, } == (await client_0.get_wallets(wallet_type=cr_cat_wallet_0.type()))[0] assert await wallet_node_0.wallet_state_manager.get_wallet_for_asset_id(cr_cat_wallet_0.get_asset_id()) is not None - wallet_1_addr = encode_puzzle_hash(await wallet_1.get_new_puzzlehash(), "txch") + wallet_1_ph = await wallet_1.get_new_puzzlehash() + wallet_1_addr = encode_puzzle_hash(wallet_1_ph, "txch") tx = await client_0.cat_spend( cr_cat_wallet_0.id(), DEFAULT_TX_CONFIG, @@ -295,6 +296,7 @@ async def check_length(length: int, func: Callable[..., Awaitable[Any]], *args: "asset_id": cr_cat_wallet_1.get_asset_id(), "fingerprint": wallet_node_1.logged_in_fingerprint, } + assert await cr_cat_wallet_1.match_hinted_coin(next(c for c in tx.additions if c.amount == 90), wallet_1_ph) pending_tx = await client_1.get_transactions( cr_cat_wallet_1.id(), 0,