Merge branch 'main' into jn.sim-cli-flake-fix

This commit is contained in:
Jack Nelson 2023-06-30 16:41:13 -04:00
commit c0ce7b7b71
No known key found for this signature in database
GPG Key ID: 63C8D6E673ADF88B
9 changed files with 120 additions and 46 deletions

View File

@ -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"}

View File

@ -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

View File

@ -3,6 +3,7 @@ from __future__ import annotations
import dataclasses
import logging
import time
import traceback
from typing import TYPE_CHECKING, List, Optional, Set, Tuple, Type, TypeVar, Union
from blspy import G1Element, G2Element
@ -105,7 +106,13 @@ class VCWallet:
f"Cannot get verified credential coin: {coin.name().hex()} puzzle and solution"
) # pragma: no cover
return # pragma: no cover
vc = VerifiedCredential.get_next_from_coin_spend(cs)
try:
vc = VerifiedCredential.get_next_from_coin_spend(cs)
except Exception as e: # pragma: no cover
self.log.debug(
f"Syncing VC from coin spend failed (likely means it was revoked): {e}\n{traceback.format_exc()}"
)
return
vc_record: VCRecord = VCRecord(vc, height)
self.wallet_state_manager.state_changed(
"vc_coin_added", self.id(), dict(launcher_id=vc_record.vc.launcher_id.hex())

View File

@ -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:

View File

@ -7,8 +7,8 @@ from setuptools import find_packages, setup
dependencies = [
"aiofiles==23.1.0", # Async IO for files
"blspy==2.0.1", # Signature library
"anyio==3.7.0",
"blspy==2.0.2", # Signature library
"boto3==1.26.161", # AWS S3 for DL s3 plugin
"chiavdf==1.0.8", # timelord and vdf verification
"chiabip158==1.2", # bip158-style wallet filters

View File

@ -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]

View File

@ -0,0 +1,4 @@
# flake8: noqa: E501
from __future__ import annotations
checkout_blocks_and_plots = True

20
tests/wallet/test_util.py Normal file
View 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

View File

@ -13,6 +13,8 @@ import typing
import click
import typing_extensions
from chia.types.blockchain_format.sized_bytes import bytes32
here = pathlib.Path(__file__).parent.resolve()
root = here.parent
cache_path = root.joinpath(".chia_cache", "manage_clvm.json")
@ -99,7 +101,7 @@ def dump_cache(cache: Cache, file: typing.IO[str]) -> None:
json.dump(cache, file, indent=4)
def generate_hash_bytes(hex_bytes: bytes) -> bytes:
def generate_hash_bytes(hex_bytes: bytes) -> bytes32:
cleaned_blob = bytes.fromhex(hex_bytes.decode("utf-8"))
serialize_program = SerializedProgram.from_bytes(cleaned_blob)
return serialize_program.get_tree_hash()
@ -141,7 +143,7 @@ class ClvmBytes:
hex_bytes = paths.hex.read_bytes()
return cls(
hex=hex_bytes,
hash=bytes.fromhex(hash_dict[paths.hash])
hash=bytes32(bytes.fromhex(hash_dict[paths.hash]))
if paths.hash in hash_dict
else generate_hash_bytes(hex_bytes=hex_bytes),
)