mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2024-09-19 23:21:46 +03:00
Fix hint parsing for CATs and DIDs (#15259)
* Fix hint parsing for CATs and DIDs * Handle not hinted coins * Fix compute_coin_hints condition checking * don't try to sync non-singleton children of singleton * coverage ignores * rename function * Make function do what it says * wallet: Some suggestions from #15274 for #15259 (#15547) * wallet: Some suggestions from #15274 for #15259 * Return a dict with the coin id as key * Drop `compute_hint_for_coin`, Test `compute_spend_hints_and_additions` --------- Co-authored-by: dustinface <35775977+xdustinface@users.noreply.github.com>
This commit is contained in:
parent
a449a2feae
commit
424c51072d
@ -72,7 +72,7 @@ from chia.wallet.trading.offer import Offer
|
||||
from chia.wallet.transaction_record import TransactionRecord
|
||||
from chia.wallet.uncurried_puzzle import uncurry_puzzle
|
||||
from chia.wallet.util.address_type import AddressType, is_valid_address
|
||||
from chia.wallet.util.compute_hints import compute_coin_hints
|
||||
from chia.wallet.util.compute_hints import compute_spend_hints_and_additions
|
||||
from chia.wallet.util.compute_memos import compute_memos
|
||||
from chia.wallet.util.query_filter import HashFilter, TransactionTypeFilter
|
||||
from chia.wallet.util.transaction_type import CLAWBACK_TRANSACTION_TYPES, TransactionType
|
||||
@ -1981,27 +1981,25 @@ class WalletRpcApi:
|
||||
return {"success": False, "error": "The coin is not a DID."}
|
||||
p2_puzzle, recovery_list_hash, num_verification, singleton_struct, metadata = curried_args
|
||||
|
||||
hint_list = compute_coin_hints(coin_spend)
|
||||
derivation_record = None
|
||||
hinted_coins = compute_spend_hints_and_additions(coin_spend)
|
||||
# Hint is required, if it doesn't have any hint then it should be invalid
|
||||
is_invalid = len(hint_list) == 0
|
||||
for hint in hint_list:
|
||||
derivation_record = (
|
||||
await self.service.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(
|
||||
bytes32(hint)
|
||||
)
|
||||
)
|
||||
if derivation_record is not None:
|
||||
is_invalid = False
|
||||
hint: Optional[bytes32] = None
|
||||
for hinted_coin in hinted_coins.values():
|
||||
if hinted_coin.coin.amount % 2 == 1 and hinted_coin.hint is not None:
|
||||
hint = hinted_coin.hint
|
||||
break
|
||||
is_invalid = True
|
||||
if is_invalid:
|
||||
if hint is None:
|
||||
# This is an invalid DID, check if we are owner
|
||||
derivation_record = (
|
||||
await self.service.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(
|
||||
p2_puzzle.get_tree_hash()
|
||||
)
|
||||
)
|
||||
else:
|
||||
derivation_record = (
|
||||
await self.service.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(hint)
|
||||
)
|
||||
|
||||
launcher_id = singleton_struct.rest().first().as_python()
|
||||
if derivation_record is None:
|
||||
return {"success": False, "error": f"This DID {launcher_id.hex()} is not belong to the connected wallet"}
|
||||
|
@ -1,22 +1,37 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Optional
|
||||
|
||||
from chia.types.blockchain_format.program import INFINITE_COST
|
||||
from chia.types.blockchain_format.coin import Coin
|
||||
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
|
||||
from chia.types.condition_opcodes import ConditionOpcode
|
||||
from chia.util.ints import uint64
|
||||
|
||||
|
||||
def compute_coin_hints(cs: CoinSpend) -> List[bytes32]:
|
||||
@dataclass(frozen=True)
|
||||
class HintedCoin:
|
||||
coin: Coin
|
||||
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)
|
||||
|
||||
h_list: List[bytes32] = []
|
||||
for condition_data in result_program.as_python():
|
||||
condition = condition_data[0]
|
||||
args = condition_data[1:]
|
||||
if condition == ConditionOpcode.CREATE_COIN and len(args) > 2:
|
||||
if isinstance(args[2], list):
|
||||
if isinstance(args[2][0], bytes):
|
||||
h_list.append(bytes32(args[2][0]))
|
||||
return h_list
|
||||
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
|
||||
|
@ -77,7 +77,7 @@ from chia.wallet.trading.trade_status import TradeStatus
|
||||
from chia.wallet.transaction_record import TransactionRecord
|
||||
from chia.wallet.uncurried_puzzle import uncurry_puzzle
|
||||
from chia.wallet.util.address_type import AddressType
|
||||
from chia.wallet.util.compute_hints import compute_coin_hints
|
||||
from chia.wallet.util.compute_hints import compute_spend_hints_and_additions
|
||||
from chia.wallet.util.compute_memos import compute_memos
|
||||
from chia.wallet.util.puzzle_decorator import PuzzleDecoratorManager
|
||||
from chia.wallet.util.query_filter import HashFilter
|
||||
@ -681,12 +681,12 @@ class WalletStateManager:
|
||||
# hint
|
||||
# First spend where 1 mojo coin -> Singleton launcher -> NFT -> NFT
|
||||
uncurried_nft = UncurriedNFT.uncurry(uncurried.mod, uncurried.args)
|
||||
if uncurried_nft is not None:
|
||||
if uncurried_nft is not None and coin_state.coin.amount % 2 == 1:
|
||||
return await self.handle_nft(coin_spend, uncurried_nft, parent_coin_state, coin_state)
|
||||
|
||||
# Check if the coin is a DID
|
||||
did_curried_args = match_did_puzzle(uncurried.mod, uncurried.args)
|
||||
if did_curried_args is not None:
|
||||
if did_curried_args is not None and coin_state.coin.amount % 2 == 1:
|
||||
return await self.handle_did(did_curried_args, parent_coin_state, coin_state, coin_spend, peer)
|
||||
|
||||
# Check if the coin is clawback
|
||||
@ -865,12 +865,9 @@ class WalletStateManager:
|
||||
"""
|
||||
mod_hash, tail_hash, inner_puzzle = curried_args
|
||||
|
||||
hint_list = compute_coin_hints(coin_spend)
|
||||
derivation_record = None
|
||||
for hint in hint_list:
|
||||
derivation_record = await self.puzzle_store.get_derivation_record_for_puzzle_hash(bytes32(hint))
|
||||
if derivation_record is not None:
|
||||
break
|
||||
hinted_coin = compute_spend_hints_and_additions(coin_spend)[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)
|
||||
|
||||
if derivation_record is None:
|
||||
self.log.info(f"Received state for the coin that doesn't belong to us {coin_state}")
|
||||
@ -924,13 +921,9 @@ class WalletStateManager:
|
||||
inner_puzzle_hash = p2_puzzle.get_tree_hash()
|
||||
self.log.info(f"parent: {parent_coin_state.coin.name()} inner_puzzle_hash for parent is {inner_puzzle_hash}")
|
||||
|
||||
hint_list = compute_coin_hints(coin_spend)
|
||||
|
||||
derivation_record = None
|
||||
for hint in hint_list:
|
||||
derivation_record = await self.puzzle_store.get_derivation_record_for_puzzle_hash(bytes32(hint))
|
||||
if derivation_record is not None:
|
||||
break
|
||||
hinted_coin = compute_spend_hints_and_additions(coin_spend)[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)
|
||||
|
||||
launch_id: bytes32 = bytes32(bytes(singleton_struct.rest().first())[1:])
|
||||
if derivation_record is None:
|
||||
|
@ -17,8 +17,14 @@ from types import TracebackType
|
||||
from typing import Any, Callable, Collection, Iterator, List, Optional, Type, Union
|
||||
|
||||
import pytest
|
||||
from chia_rs import Coin
|
||||
from typing_extensions import Protocol, final
|
||||
|
||||
from chia.types.blockchain_format.sized_bytes import bytes32
|
||||
from chia.types.condition_opcodes import ConditionOpcode
|
||||
from chia.util.hash import std_hash
|
||||
from chia.util.ints import uint64
|
||||
from chia.wallet.util.compute_hints import HintedCoin
|
||||
from tests.core.data_layer.util import ChiaRoot
|
||||
|
||||
|
||||
@ -332,3 +338,32 @@ class DataCasesDecorator(Protocol):
|
||||
|
||||
def named_datacases(name: str) -> DataCasesDecorator:
|
||||
return functools.partial(datacases, _name=name)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class CoinGenerator:
|
||||
_seed: int = -1
|
||||
|
||||
def _get_hash(self) -> bytes32:
|
||||
self._seed += 1
|
||||
return std_hash(self._seed)
|
||||
|
||||
def _get_amount(self) -> uint64:
|
||||
self._seed += 1
|
||||
return uint64(self._seed)
|
||||
|
||||
def get(self, parent_coin_id: Optional[bytes32] = None, include_hint: bool = True) -> HintedCoin:
|
||||
if parent_coin_id is None:
|
||||
parent_coin_id = self._get_hash()
|
||||
hint = None
|
||||
if include_hint:
|
||||
hint = self._get_hash()
|
||||
return HintedCoin(Coin(parent_coin_id, self._get_hash(), self._get_amount()), hint)
|
||||
|
||||
|
||||
def coin_creation_args(hinted_coin: HintedCoin) -> List[Any]:
|
||||
if hinted_coin.hint is not None:
|
||||
memos = [hinted_coin.hint]
|
||||
else:
|
||||
memos = []
|
||||
return [ConditionOpcode.CREATE_COIN, hinted_coin.coin.puzzle_hash, hinted_coin.coin.amount, memos]
|
||||
|
20
tests/wallet/test_util.py
Normal file
20
tests/wallet/test_util.py
Normal file
@ -0,0 +1,20 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.types.coin_spend import CoinSpend
|
||||
from chia.wallet.util.compute_hints import compute_spend_hints_and_additions
|
||||
from tests.util.misc import CoinGenerator, coin_creation_args
|
||||
|
||||
|
||||
def test_compute_spend_hints_and_additions() -> None:
|
||||
coin_generator = CoinGenerator()
|
||||
parent_coin = coin_generator.get()
|
||||
hinted_coins = [coin_generator.get(parent_coin.coin.name(), include_hint=i % 2 == 0) for i in range(10)]
|
||||
create_coin_args = [coin_creation_args(create_coin) for create_coin in hinted_coins]
|
||||
coin_spend = CoinSpend(
|
||||
parent_coin.coin,
|
||||
Program.to(1),
|
||||
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
|
Loading…
Reference in New Issue
Block a user