From ec37426df0ce507962d5589d5d893c59f43a7296 Mon Sep 17 00:00:00 2001 From: ytx1991 Date: Wed, 15 Jun 2022 09:02:55 -0700 Subject: [PATCH 1/3] Add set nft DID api --- chia/rpc/wallet_rpc_api.py | 15 +++ chia/wallet/nft_wallet/nft_wallet.py | 43 ++++++- chia/wallet/wallet_state_manager.py | 2 + tests/wallet/nft_wallet/test_nft_wallet.py | 130 ++++++++++++++++++++- 4 files changed, 184 insertions(+), 6 deletions(-) diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py index abb648ad4a..c103cae48c 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -137,6 +137,7 @@ class WalletRpcApi: "/nft_mint_nft": self.nft_mint_nft, "/nft_get_nfts": self.nft_get_nfts, "/nft_get_by_did": self.nft_get_by_did, + "/nft_set_nft_did": self.nft_set_nft_did, "/nft_get_wallet_did": self.nft_get_wallet_did, "/nft_get_wallets_with_dids": self.nft_get_wallets_with_dids, "/nft_get_info": self.nft_get_info, @@ -1395,6 +1396,20 @@ class WalletRpcApi: nft_info_list.append(nft_puzzles.get_nft_info_from_puzzle(nft)) return {"wallet_id": wallet_id, "success": True, "nft_list": nft_info_list} + async def nft_set_nft_did(self, request): + try: + assert self.service.wallet_state_manager is not None + wallet_id = uint32(request["wallet_id"]) + nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id] + did_id = decode_puzzle_hash(request["did_id"]) + nft_coin_info = nft_wallet.get_nft_coin_by_id(bytes32.from_hexstr(request["nft_coin_id"])) + fee = uint64(request.get("fee", 0)) + spend_bundle = await nft_wallet.set_nft_did(nft_coin_info, did_id, fee=fee) + return {"wallet_id": wallet_id, "success": True, "spend_bundle": spend_bundle} + except Exception as e: + log.exception(f"Failed to set DID on NFT: {e}") + return {"success": False, "error": str(e)} + async def nft_get_by_did(self, request) -> Dict: did_id: Optional[bytes32] = None if "did_id" in request: diff --git a/chia/wallet/nft_wallet/nft_wallet.py b/chia/wallet/nft_wallet/nft_wallet.py index 7aa3403652..5c99ac1d20 100644 --- a/chia/wallet/nft_wallet/nft_wallet.py +++ b/chia/wallet/nft_wallet/nft_wallet.py @@ -316,24 +316,27 @@ class NFTWallet: async def get_did_approval_info( self, nft_id: bytes32, + did_id: bytes32 = None, ) -> Tuple[bytes32, SpendBundle]: """Get DID spend with announcement created we need to transfer NFT with did with current inner hash of DID We also store `did_id` and then iterate to find the did wallet as we'd otherwise have to subscribe to any changes to DID wallet and storing wallet_id is not guaranteed to be consistent on wallet crash/reset. """ + if did_id is None: + did_id = self.did_id for _, wallet in self.wallet_state_manager.wallets.items(): self.log.debug("Checking wallet type %s", wallet.type()) if wallet.type() == WalletType.DISTRIBUTED_ID: - self.log.debug("Found a DID wallet, checking did: %r == %r", wallet.get_my_DID(), self.did_id) - if bytes32.fromhex(wallet.get_my_DID()) == self.did_id: + self.log.debug("Found a DID wallet, checking did: %r == %r", wallet.get_my_DID(), did_id) + if bytes32.fromhex(wallet.get_my_DID()) == did_id: self.log.debug("Creating announcement from DID for nft_id: %s", nft_id) did_bundle = await wallet.create_message_spend(puzzle_announcements=[nft_id]) self.log.debug("Sending DID announcement from puzzle: %s", did_bundle.removals()) did_inner_hash = wallet.did_info.current_inner.get_tree_hash() break else: - raise ValueError(f"Missing DID Wallet for did_id: {self.did_id}") + raise ValueError(f"Missing DID Wallet for did_id: {did_id}") return did_inner_hash, did_bundle async def generate_new_nft( @@ -715,6 +718,7 @@ class NFTWallet: new_owner: Optional[bytes32] = None, new_did_inner_hash: Optional[bytes32] = None, trade_prices_list: Optional[Program] = None, + additional_bundles: List[SpendBundle] = [], ) -> List[TransactionRecord]: if memos is None: memos = [[] for _ in range(len(puzzle_hashes))] @@ -736,9 +740,12 @@ class NFTWallet: coins=coins, coin_announcements_to_consume=coin_announcements_to_consume, puzzle_announcements_to_consume=puzzle_announcements_to_consume, + new_owner=new_owner, + new_did_inner_hash=new_did_inner_hash, + trade_prices_list=trade_prices_list, ) spend_bundle = await self.sign(unsigned_spend_bundle) - + spend_bundle.aggregate(additional_bundles) tx_list = [ TransactionRecord( confirmed_at_height=uint32(0), @@ -850,3 +857,31 @@ class NFTWallet: unsigned_spend_bundle = SpendBundle.aggregate([nft_spend_bundle, chia_spend_bundle]) return (unsigned_spend_bundle, chia_tx) + + async def set_nft_did(self, nft_coin_info: NFTCoinInfo, did_id: bytes32, fee: uint64 = uint64(0)) -> SpendBundle: + self.log.info("Setting NFT DID with parameters: nft=%s did=%s", nft_coin_info, did_id) + unft = UncurriedNFT.uncurry(nft_coin_info.full_puzzle) + nft_id = unft.singleton_launcher_id + puzzle_hashes_to_sign = [unft.p2_puzzle.get_tree_hash()] + if did_id: + did_inner_hash, did_bundle = await self.get_did_approval_info(nft_id, did_id) + additional_bundles = [did_bundle] + else: + did_inner_hash = None + additional_bundles = [] + nft_tx_record = await self.generate_signed_transaction( + [nft_coin_info.coin.amount], puzzle_hashes_to_sign, fee, {nft_coin_info.coin}, new_owner=did_id, new_did_inner_hash=did_inner_hash, additional_bundles=additional_bundles + ) + spend_bundle: Optional[SpendBundle] = None + for tx in nft_tx_record: + if spend_bundle is None: + spend_bundle = tx.spend_bundle + else: + spend_bundle.aggregate([tx.spend_bundle]) + await self.standard_wallet.push_transaction(tx) + self.wallet_state_manager.state_changed("nft_coin_did_set", self.wallet_info.id) + if spend_bundle: + return spend_bundle + else: + raise ValueError("Couldn't set DID on given NFT") + diff --git a/chia/wallet/wallet_state_manager.py b/chia/wallet/wallet_state_manager.py index 7e7068f006..1deca06a2a 100644 --- a/chia/wallet/wallet_state_manager.py +++ b/chia/wallet/wallet_state_manager.py @@ -26,6 +26,7 @@ from chia.types.blockchain_format.sized_bytes import bytes32 from chia.types.coin_spend import CoinSpend from chia.types.full_block import FullBlock from chia.types.mempool_inclusion_status import MempoolInclusionStatus +from chia.util.bech32m import encode_puzzle_hash from chia.util.byte_types import hexstr_to_bytes from chia.util.config import process_config_start_method from chia.util.db_synchronous import db_synchronous_on @@ -37,6 +38,7 @@ from chia.wallet.cat_wallet.cat_utils import construct_cat_puzzle, match_cat_puz from chia.wallet.cat_wallet.cat_wallet import CATWallet from chia.wallet.derivation_record import DerivationRecord from chia.wallet.derive_keys import master_sk_to_wallet_sk, master_sk_to_wallet_sk_unhardened +from chia.wallet.did_wallet.did_info import DID_HRP from chia.wallet.did_wallet.did_wallet import DIDWallet from chia.wallet.did_wallet.did_wallet_puzzles import DID_INNERPUZ_MOD, create_fullpuz, match_did_puzzle from chia.wallet.key_val_store import KeyValStore diff --git a/tests/wallet/nft_wallet/test_nft_wallet.py b/tests/wallet/nft_wallet/test_nft_wallet.py index ca2099eb09..40d316daab 100644 --- a/tests/wallet/nft_wallet/test_nft_wallet.py +++ b/tests/wallet/nft_wallet/test_nft_wallet.py @@ -851,6 +851,7 @@ async def test_nft_transfer_nft_with_did(two_wallet_nodes: Any, trusted: Any) -> wallet_0 = wallet_node_0.wallet_state_manager.main_wallet wallet_1 = wallet_node_1.wallet_state_manager.main_wallet api_0 = WalletRpcApi(wallet_node_0) + api_1 = WalletRpcApi(wallet_node_1) ph = await wallet_0.get_new_puzzlehash() ph1 = await wallet_1.get_new_puzzlehash() @@ -960,8 +961,15 @@ async def test_nft_transfer_nft_with_did(two_wallet_nodes: Any, trusted: Any) -> else: raise AssertionError("NFT not transferred") - nft_wallet_1 = wallet_1.wallet_state_manager.wallets[2] - await time_out_assert(15, len, 1, nft_wallet_1.nft_wallet_info.my_nft_coins) + resp = await api_1.nft_get_by_did(dict(did_id=hmr_did_id)) + assert resp.get("success") + nft_wallet_id_1 = resp.get("wallet_id") + coins_response = await api_1.nft_get_nfts(dict(wallet_id=nft_wallet_id_1)) + assert coins_response.get("success") + assert len(coins_response.get("nft_list")) == 1 + assert coins_response.get("nft_list")[0].owner_did is None + + @pytest.mark.parametrize( @@ -1093,3 +1101,121 @@ async def test_update_metadata_for_nft_did(two_wallet_nodes: Any, trusted: Any) raise await asyncio.sleep(0.5) time_left -= 0.5 + +@pytest.mark.parametrize( + "trusted", + [True, False], +) +@pytest.mark.asyncio +async def test_nft_set_did(two_wallet_nodes: Any, trusted: Any) -> None: + num_blocks = 5 + full_nodes, wallets = two_wallet_nodes + full_node_api: FullNodeSimulator = full_nodes[0] + full_node_server = full_node_api.server + wallet_node_0, server_0 = wallets[0] + wallet_node_1, server_1 = wallets[1] + wallet_0 = wallet_node_0.wallet_state_manager.main_wallet + api_0 = WalletRpcApi(wallet_node_0) + ph = await wallet_0.get_new_puzzlehash() + + if trusted: + wallet_node_0.config["trusted_peers"] = { + full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex() + } + wallet_node_1.config["trusted_peers"] = { + full_node_api.full_node.server.node_id.hex(): full_node_api.full_node.server.node_id.hex() + } + else: + wallet_node_0.config["trusted_peers"] = {} + wallet_node_1.config["trusted_peers"] = {} + + await server_0.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None) + await server_1.start_client(PeerInfo("localhost", uint16(full_node_server._port)), None) + + for _ in range(1, num_blocks): + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + + funds = sum( + [calculate_pool_reward(uint32(i)) + calculate_base_farmer_reward(uint32(i)) for i in range(1, num_blocks - 1)] + ) + + await time_out_assert(10, wallet_0.get_unconfirmed_balance, funds) + await time_out_assert(10, wallet_0.get_confirmed_balance, funds) + did_wallet: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node_0.wallet_state_manager, wallet_0, uint64(1) + ) + spend_bundle_list = await wallet_node_0.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(wallet_0.id()) + + spend_bundle = spend_bundle_list[0].spend_bundle + await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name()) + + for _ in range(1, num_blocks): + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + await time_out_assert(15, wallet_0.get_pending_change_balance, 0) + hex_did_id = did_wallet.get_my_DID() + hmr_did_id = encode_puzzle_hash(bytes32.from_hexstr(hex_did_id), DID_HRP) + + res = await api_0.create_new_wallet(dict(wallet_type="nft_wallet", name="NFT WALLET 1")) + assert isinstance(res, dict) + assert res.get("success") + nft_wallet_0_id = res["wallet_id"] + + await time_out_assert(5, did_wallet.get_confirmed_balance, 1) + + # Create a NFT with DID + resp = await api_0.nft_mint_nft( + { + "wallet_id": nft_wallet_0_id, + "hash": "0xD4584AD463139FA8C0D9F68F4B59F185", + "uris": ["https://www.chia.net/img/branding/chia-logo.svg"], + "mu": ["https://www.chia.net/img/branding/chia-logo.svg"], + } + ) + assert resp.get("success") + sb = resp["spend_bundle"] + + # ensure hints are generated + assert compute_memos(sb) + await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, sb.name()) + + for i in range(1, num_blocks): + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + + # Check DID NFT + time_left = 5.0 + coins_response = {} + while time_left > 0: + coins_response = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_0_id)) + if coins_response.get("nft_list"): + break + await asyncio.sleep(0.5) + time_left -= 0.5 + assert coins_response["nft_list"], isinstance(coins_response, dict) + assert coins_response.get("success") + coins = coins_response["nft_list"] + assert len(coins) == 1 + assert coins[0].owner_did is None + nft_coin_id = coins[0].nft_coin_id + # Set DID + resp = await api_0.nft_set_nft_did(dict(wallet_id=nft_wallet_0_id, did_id=hmr_did_id, nft_coin_id=nft_coin_id.hex())) + assert resp.get("success") + + for i in range(1, num_blocks): + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + + time_left = 5.0 + coins_response = {} + while time_left > 0: + coins_response = await api_0.nft_get_by_did(dict(did_id=hmr_did_id)) + if coins_response.get("wallet_id"): + break + await asyncio.sleep(0.5) + time_left -= 0.5 + nft_wallet_1_id = coins_response.get("wallet_id") + print(coins_response) + # Check NFT DID + resp = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_1_id)) + assert resp.get("success") + coins = coins_response["nft_list"] + assert len(coins) == 1 + assert coins[0].owner_did == hmr_did_id From 0d14bfbe3a7ed975a245b38fbb381e53ac6f76af Mon Sep 17 00:00:00 2001 From: ytx1991 Date: Wed, 15 Jun 2022 18:54:49 -0700 Subject: [PATCH 2/3] Fix bugs & Add tests --- chia/rpc/wallet_rpc_api.py | 6 +- chia/wallet/nft_wallet/nft_puzzles.py | 14 ++- chia/wallet/nft_wallet/nft_wallet.py | 40 ++++--- chia/wallet/wallet_state_manager.py | 15 ++- tests/wallet/nft_wallet/test_nft_wallet.py | 124 +++++++++++++++++++-- 5 files changed, 163 insertions(+), 36 deletions(-) diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py index 28f8b22eed..c535aacd2a 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -1405,7 +1405,9 @@ class WalletRpcApi: assert self.service.wallet_state_manager is not None wallet_id = uint32(request["wallet_id"]) nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id] - did_id = decode_puzzle_hash(request["did_id"]) + did_id: Optional[bytes32] = None + if "did_id" in request: + did_id = decode_puzzle_hash(request["did_id"]) nft_coin_info = nft_wallet.get_nft_coin_by_id(bytes32.from_hexstr(request["nft_coin_id"])) fee = uint64(request.get("fee", 0)) spend_bundle = await nft_wallet.set_nft_did(nft_coin_info, did_id, fee=fee) @@ -1484,7 +1486,7 @@ class WalletRpcApi: txs = await nft_wallet.generate_signed_transaction( [nft_coin_info.coin.amount], [puzzle_hash], - coins=[nft_coin_info.coin], + coins={nft_coin_info.coin}, fee=fee, ) spend_bundle: Optional[SpendBundle] = None diff --git a/chia/wallet/nft_wallet/nft_puzzles.py b/chia/wallet/nft_wallet/nft_puzzles.py index 3119072832..d47f8f19d0 100644 --- a/chia/wallet/nft_wallet/nft_puzzles.py +++ b/chia/wallet/nft_wallet/nft_puzzles.py @@ -226,7 +226,7 @@ def create_ownership_layer_puzzle( def create_ownership_layer_transfer_solution( new_did: bytes, - new_did_inner_hash: bytes32, + new_did_inner_hash: bytes, trade_prices_list: List[List[int]], new_puzhash: bytes32, ) -> Program: @@ -256,7 +256,7 @@ def create_ownership_layer_transfer_solution( return solution -def get_metadata_and_phs(unft: UncurriedNFT, puzzle: Program, solution: SerializedProgram) -> Tuple[Program, bytes32]: +def get_metadata_and_phs(unft: UncurriedNFT, solution: SerializedProgram) -> Tuple[Program, bytes32]: conditions = unft.p2_puzzle.run(unft.get_innermost_solution(solution.to_program())) metadata = unft.metadata puzhash_for_derivation: Optional[bytes32] = None @@ -298,3 +298,13 @@ def recurry_nft_puzzle(unft: UncurriedNFT, solution: Program, sp2_puzzle: Progra assert unft.transfer_program inner_puzzle = construct_ownership_layer(new_did_id, unft.transfer_program, sp2_puzzle) return inner_puzzle + + +def get_new_owner_did(solution: Program) -> Optional[bytes32]: + conditions = solution.at("rrfffrfr").as_iter() + new_did_id = None + for condition in conditions: + if condition.first().as_int() == -10: + # this is the change owner magic condition + new_did_id = condition.at("rf").atom + return new_did_id diff --git a/chia/wallet/nft_wallet/nft_wallet.py b/chia/wallet/nft_wallet/nft_wallet.py index 9a48d3edbd..b26a7de5a3 100644 --- a/chia/wallet/nft_wallet/nft_wallet.py +++ b/chia/wallet/nft_wallet/nft_wallet.py @@ -192,7 +192,7 @@ class NFTWallet: self.log.debug("Puzzle solution received to wallet: %s", self.wallet_info) coin_name = coin_spend.coin.name() puzzle: Program = Program.from_bytes(bytes(coin_spend.puzzle_reveal)) - delegated_puz_solution: Program = Program.from_bytes(bytes(coin_spend.solution)).rest().rest().first().first() + delegated_puz_solution: Program = coin_spend.solution.to_program().rest().rest().first().first() # At this point, the puzzle must be a NFT puzzle. # This method will be called only when the wallet state manager uncurried this coin as a NFT puzzle. @@ -202,7 +202,7 @@ class NFTWallet: ) singleton_id = uncurried_nft.singleton_launcher_id parent_inner_puzhash = uncurried_nft.nft_state_layer.get_tree_hash() - metadata, p2_puzzle_hash = get_metadata_and_phs(uncurried_nft, puzzle, coin_spend.solution) + metadata, p2_puzzle_hash = get_metadata_and_phs(uncurried_nft, coin_spend.solution) self.log.debug("Got back puzhash from solution: %s", p2_puzzle_hash) self.log.debug("Got back updated metadata: %s", metadata) derivation_record: Optional[ @@ -415,10 +415,11 @@ class NFTWallet: record: Optional[DerivationRecord] = None # Create inner solution for eve spend if did_id is not None: - did_inner_hash, did_bundle = await self.get_did_approval_info(launcher_coin.name()) + did_inner_hash = b"" + if did_id != b"": + did_inner_hash, did_bundle = await self.get_did_approval_info(launcher_coin.name()) + bundles_to_agg.append(did_bundle) innersol = create_ownership_layer_transfer_solution(did_id, did_inner_hash, [], target_puzzle_hash) - bundles_to_agg.append(did_bundle) - self.log.debug("Created an inner DID NFT solution: %s", disassemble(innersol)) else: condition_list = [make_create_coin_condition(target_puzzle_hash, amount, [target_puzzle_hash])] @@ -705,9 +706,8 @@ class NFTWallet: memos: Optional[List[List[bytes]]] = None, coin_announcements_to_consume: Optional[Set[Announcement]] = None, puzzle_announcements_to_consume: Optional[Set[Announcement]] = None, - ignore_max_send_amount: bool = False, - new_owner: Optional[bytes32] = None, - new_did_inner_hash: Optional[bytes32] = None, + new_owner: Optional[bytes] = None, + new_did_inner_hash: Optional[bytes] = None, trade_prices_list: Optional[Program] = None, additional_bundles: List[SpendBundle] = [], ) -> List[TransactionRecord]: @@ -735,8 +735,9 @@ class NFTWallet: new_did_inner_hash=new_did_inner_hash, trade_prices_list=trade_prices_list, ) + spend_bundle = await self.sign(unsigned_spend_bundle) - spend_bundle.aggregate(additional_bundles) + spend_bundle = SpendBundle.aggregate([spend_bundle] + additional_bundles) tx_list = [ TransactionRecord( confirmed_at_height=uint32(0), @@ -789,8 +790,8 @@ class NFTWallet: coins: Set[Coin] = None, coin_announcements_to_consume: Optional[Set[Announcement]] = None, puzzle_announcements_to_consume: Optional[Set[Announcement]] = None, - new_owner: Optional[bytes32] = None, - new_did_inner_hash: Optional[bytes32] = None, + new_owner: Optional[bytes] = None, + new_did_inner_hash: Optional[bytes] = None, trade_prices_list: Optional[Program] = None, ) -> Tuple[SpendBundle, Optional[TransactionRecord]]: if coins is None or len(coins) > 1: @@ -847,10 +848,12 @@ class NFTWallet: unsigned_spend_bundle = SpendBundle.aggregate([nft_spend_bundle, chia_spend_bundle]) - return (unsigned_spend_bundle, chia_tx) + return unsigned_spend_bundle, chia_tx - async def set_nft_did(self, nft_coin_info: NFTCoinInfo, did_id: bytes32, fee: uint64 = uint64(0)) -> SpendBundle: - self.log.info("Setting NFT DID with parameters: nft=%s did=%s", nft_coin_info, did_id) + async def set_nft_did( + self, nft_coin_info: NFTCoinInfo, did_id: Optional[bytes32], fee: uint64 = uint64(0) + ) -> SpendBundle: + self.log.debug("Setting NFT DID with parameters: nft=%s did=%s", nft_coin_info, did_id) unft = UncurriedNFT.uncurry(nft_coin_info.full_puzzle) nft_id = unft.singleton_launcher_id puzzle_hashes_to_sign = [unft.p2_puzzle.get_tree_hash()] @@ -861,7 +864,13 @@ class NFTWallet: did_inner_hash = None additional_bundles = [] nft_tx_record = await self.generate_signed_transaction( - [nft_coin_info.coin.amount], puzzle_hashes_to_sign, fee, {nft_coin_info.coin}, new_owner=did_id, new_did_inner_hash=did_inner_hash, additional_bundles=additional_bundles + [nft_coin_info.coin.amount], + puzzle_hashes_to_sign, + fee, + {nft_coin_info.coin}, + new_owner=did_id if did_id else b"", + new_did_inner_hash=did_inner_hash if did_inner_hash else b"", + additional_bundles=additional_bundles, ) spend_bundle: Optional[SpendBundle] = None for tx in nft_tx_record: @@ -875,4 +884,3 @@ class NFTWallet: return spend_bundle else: raise ValueError("Couldn't set DID on given NFT") - diff --git a/chia/wallet/wallet_state_manager.py b/chia/wallet/wallet_state_manager.py index ebec9184ac..2b418c9315 100644 --- a/chia/wallet/wallet_state_manager.py +++ b/chia/wallet/wallet_state_manager.py @@ -26,7 +26,6 @@ from chia.types.blockchain_format.sized_bytes import bytes32 from chia.types.coin_spend import CoinSpend from chia.types.full_block import FullBlock from chia.types.mempool_inclusion_status import MempoolInclusionStatus -from chia.util.bech32m import encode_puzzle_hash from chia.util.byte_types import hexstr_to_bytes from chia.util.config import process_config_start_method from chia.util.db_synchronous import db_synchronous_on @@ -43,7 +42,7 @@ from chia.wallet.did_wallet.did_wallet import DIDWallet from chia.wallet.did_wallet.did_wallet_puzzles import DID_INNERPUZ_MOD, create_fullpuz, match_did_puzzle from chia.wallet.key_val_store import KeyValStore from chia.wallet.nft_wallet.nft_info import NFTWalletInfo -from chia.wallet.nft_wallet.nft_puzzles import get_metadata_and_phs +from chia.wallet.nft_wallet.nft_puzzles import get_metadata_and_phs, get_new_owner_did from chia.wallet.nft_wallet.nft_wallet import NFTWallet from chia.wallet.nft_wallet.uncurry_nft import UncurriedNFT from chia.wallet.outer_puzzles import AssetType @@ -722,7 +721,16 @@ class WalletStateManager: """ wallet_id = None wallet_type = None - did_id = uncurried_nft.owner_did + did_id = None + if uncurried_nft.supports_did: + # Try to get the latest owner DID + did_id = get_new_owner_did(coin_spend.solution.to_program()) + if did_id is None: + # No DID owner update, use the original DID + did_id = uncurried_nft.owner_did + if did_id == b"": + # Owner DID is updated to None + did_id = None self.log.debug("Handling NFT: %s, DID: %s", coin_spend, did_id) for wallet_info in await self.get_all_wallet_info_entries(wallet_type=WalletType.NFT): nft_wallet_info: NFTWalletInfo = NFTWalletInfo.from_json_dict(json.loads(wallet_info.data)) @@ -751,7 +759,6 @@ class WalletStateManager: ) metadata, p2_puzzle_hash = get_metadata_and_phs( uncurried_nft, - Program.from_bytes(bytes(coin_spend.puzzle_reveal)), coin_spend.solution, ) derivation_record: Optional[ diff --git a/tests/wallet/nft_wallet/test_nft_wallet.py b/tests/wallet/nft_wallet/test_nft_wallet.py index b20fdc30d8..e8f2ab2b96 100644 --- a/tests/wallet/nft_wallet/test_nft_wallet.py +++ b/tests/wallet/nft_wallet/test_nft_wallet.py @@ -932,6 +932,7 @@ async def test_nft_transfer_nft_with_did(two_wallet_nodes: Any, trusted: Any) -> assert coins_response.get("success") coins = coins_response["nft_list"] assert len(coins) == 1 + assert coins[0].owner_did.hex() == hex_did_id try: wallet_1.wallet_state_manager.wallets[2] @@ -946,6 +947,8 @@ async def test_nft_transfer_nft_with_did(two_wallet_nodes: Any, trusted: Any) -> nft_coin_id=coins[0].nft_coin_id.hex(), ) ) + tx = await did_wallet.transfer_did(ph1, uint64(0), True) + assert tx assert resp.get("success") sb = resp["spend_bundle"] assert compute_memos(sb) @@ -962,14 +965,47 @@ async def test_nft_transfer_nft_with_did(two_wallet_nodes: Any, trusted: Any) -> time_left -= 0.5 else: raise AssertionError("NFT not transferred") - - resp = await api_1.nft_get_by_did(dict(did_id=hmr_did_id)) + # Check if the NFT owner DID is reset + resp = await api_1.nft_get_by_did(dict()) assert resp.get("success") nft_wallet_id_1 = resp.get("wallet_id") - coins_response = await api_1.nft_get_nfts(dict(wallet_id=nft_wallet_id_1)) + time_left = 10.0 + while time_left > 0: + coins_response = await api_1.nft_get_nfts(dict(wallet_id=nft_wallet_id_1)) + if len(coins_response["nft_list"]) == 0: + break + await asyncio.sleep(0.5) + time_left -= 0.5 assert coins_response.get("success") - assert len(coins_response.get("nft_list")) == 1 - assert coins_response.get("nft_list")[0].owner_did is None + assert len(coins_response["nft_list"]) == 1 + assert coins_response["nft_list"][0].owner_did is None + nft_coin_id = coins_response["nft_list"][0].nft_coin_id + # Set DID + + resp = await api_1.nft_set_nft_did( + dict(wallet_id=nft_wallet_id_1, did_id=hmr_did_id, nft_coin_id=nft_coin_id.hex()) + ) + assert resp.get("success") + + for i in range(1, num_blocks): + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1)) + + time_left = 5.0 + coins_response = {} + while time_left > 0: + coins_response = await api_1.nft_get_by_did(dict(did_id=hmr_did_id)) + if coins_response.get("wallet_id"): + break + await asyncio.sleep(0.5) + time_left -= 0.5 + nft_wallet_1_id = coins_response.get("wallet_id") + assert nft_wallet_1_id + # Check NFT DID + resp = await api_1.nft_get_nfts(dict(wallet_id=nft_wallet_1_id)) + assert resp.get("success") + coins = resp["nft_list"] + assert len(coins) == 1 + assert coins[0].owner_did.hex() == hex_did_id @pytest.mark.parametrize( @@ -1102,6 +1138,7 @@ async def test_update_metadata_for_nft_did(two_wallet_nodes: Any, trusted: Any) await asyncio.sleep(0.5) time_left -= 0.5 + @pytest.mark.parametrize( "trusted", [True, False], @@ -1169,6 +1206,7 @@ async def test_nft_set_did(two_wallet_nodes: Any, trusted: Any) -> None: "hash": "0xD4584AD463139FA8C0D9F68F4B59F185", "uris": ["https://www.chia.net/img/branding/chia-logo.svg"], "mu": ["https://www.chia.net/img/branding/chia-logo.svg"], + "did_id": "", } ) assert resp.get("success") @@ -1196,10 +1234,14 @@ async def test_nft_set_did(two_wallet_nodes: Any, trusted: Any) -> None: assert len(coins) == 1 assert coins[0].owner_did is None nft_coin_id = coins[0].nft_coin_id - # Set DID - resp = await api_0.nft_set_nft_did(dict(wallet_id=nft_wallet_0_id, did_id=hmr_did_id, nft_coin_id=nft_coin_id.hex())) + # Test set None -> DID1 + did_wallet1: DIDWallet = await DIDWallet.create_new_did_wallet( + wallet_node_0.wallet_state_manager, wallet_0, uint64(1) + ) + resp = await api_0.nft_set_nft_did( + dict(wallet_id=nft_wallet_0_id, did_id=hmr_did_id, nft_coin_id=nft_coin_id.hex()) + ) assert resp.get("success") - for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) @@ -1212,10 +1254,68 @@ async def test_nft_set_did(two_wallet_nodes: Any, trusted: Any) -> None: await asyncio.sleep(0.5) time_left -= 0.5 nft_wallet_1_id = coins_response.get("wallet_id") - print(coins_response) + assert nft_wallet_1_id # Check NFT DID - resp = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_1_id)) + time_left = 10.0 + while time_left > 0: + resp = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_1_id)) + if resp.get("nft_list"): + break + await asyncio.sleep(0.5) + time_left -= 0.5 assert resp.get("success") - coins = coins_response["nft_list"] + coins = resp["nft_list"] assert len(coins) == 1 - assert coins[0].owner_did == hmr_did_id + assert coins[0].owner_did.hex() == hex_did_id + nft_coin_id = coins[0].nft_coin_id + # Test set DID1 -> DID2 + hex_did_id = did_wallet1.get_my_DID() + hmr_did_id = encode_puzzle_hash(bytes32.from_hexstr(hex_did_id), DID_HRP) + resp = await api_0.nft_set_nft_did( + dict(wallet_id=nft_wallet_1_id, did_id=hmr_did_id, nft_coin_id=nft_coin_id.hex()) + ) + assert resp.get("success") + for i in range(1, num_blocks): + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + + time_left = 5.0 + coins_response = {} + while time_left > 0: + coins_response = await api_0.nft_get_by_did(dict(did_id=hmr_did_id)) + if coins_response.get("wallet_id"): + break + await asyncio.sleep(0.5) + time_left -= 0.5 + nft_wallet_2_id = coins_response.get("wallet_id") + assert nft_wallet_2_id + # Check NFT DID + time_left = 10.0 + while time_left > 0: + resp = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_2_id)) + if resp.get("nft_list"): + break + await asyncio.sleep(0.5) + time_left -= 0.5 + assert resp.get("success") + coins = resp["nft_list"] + assert len(coins) == 1 + assert coins[0].owner_did.hex() == hex_did_id + nft_coin_id = coins[0].nft_coin_id + # Test set DID2 -> None + resp = await api_0.nft_set_nft_did(dict(wallet_id=nft_wallet_2_id, nft_coin_id=nft_coin_id.hex())) + assert resp.get("success") + for i in range(1, num_blocks): + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + + # Check NFT DID + time_left = 10.0 + while time_left > 0: + resp = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_0_id)) + if resp.get("nft_list"): + break + await asyncio.sleep(0.5) + time_left -= 0.5 + assert resp.get("success") + coins = resp["nft_list"] + assert len(coins) == 1 + assert coins[0].owner_did is None From 7af5e629006621c6067048943e167e9ed2123359 Mon Sep 17 00:00:00 2001 From: ytx1991 Date: Wed, 15 Jun 2022 20:12:10 -0700 Subject: [PATCH 3/3] Fix tests --- chia/rpc/wallet_rpc_api.py | 2 +- chia/wallet/nft_wallet/nft_puzzles.py | 2 +- chia/wallet/nft_wallet/nft_wallet.py | 2 +- tests/wallet/nft_wallet/test_nft_wallet.py | 10 ++++++++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py index c535aacd2a..0bdd027f56 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -1414,7 +1414,7 @@ class WalletRpcApi: return {"wallet_id": wallet_id, "success": True, "spend_bundle": spend_bundle} except Exception as e: log.exception(f"Failed to set DID on NFT: {e}") - return {"success": False, "error": str(e)} + return {"success": False, "error": f"Failed to set DID on NFT: {e}"} async def nft_get_by_did(self, request) -> Dict: did_id: Optional[bytes32] = None diff --git a/chia/wallet/nft_wallet/nft_puzzles.py b/chia/wallet/nft_wallet/nft_puzzles.py index d47f8f19d0..c61891b621 100644 --- a/chia/wallet/nft_wallet/nft_puzzles.py +++ b/chia/wallet/nft_wallet/nft_puzzles.py @@ -285,7 +285,7 @@ def get_metadata_and_phs(unft: UncurriedNFT, solution: SerializedProgram) -> Tup def recurry_nft_puzzle(unft: UncurriedNFT, solution: Program, sp2_puzzle: Program) -> Program: log.debug("Generating NFT puzzle with ownership support: %s", disassemble(solution)) conditions = solution.at("frfr").as_iter() - new_did_id = None + new_did_id = unft.owner_did new_puzhash = None for condition in conditions: if condition.first().as_int() == -10: diff --git a/chia/wallet/nft_wallet/nft_wallet.py b/chia/wallet/nft_wallet/nft_wallet.py index b26a7de5a3..94a30a7309 100644 --- a/chia/wallet/nft_wallet/nft_wallet.py +++ b/chia/wallet/nft_wallet/nft_wallet.py @@ -571,7 +571,6 @@ class NFTWallet: condition_list = [ [51, puzzle_hash, coin.amount, [puzzle_hash]], [-24, NFT_METADATA_UPDATER, (key, uri)], - [-10, [], [], []], ] inner_solution = Program.to([[solution_for_conditions(condition_list)]]) else: @@ -706,6 +705,7 @@ class NFTWallet: memos: Optional[List[List[bytes]]] = None, coin_announcements_to_consume: Optional[Set[Announcement]] = None, puzzle_announcements_to_consume: Optional[Set[Announcement]] = None, + ignore_max_send_amount: bool = False, new_owner: Optional[bytes] = None, new_did_inner_hash: Optional[bytes] = None, trade_prices_list: Optional[Program] = None, diff --git a/tests/wallet/nft_wallet/test_nft_wallet.py b/tests/wallet/nft_wallet/test_nft_wallet.py index e8f2ab2b96..62ffac5b31 100644 --- a/tests/wallet/nft_wallet/test_nft_wallet.py +++ b/tests/wallet/nft_wallet/test_nft_wallet.py @@ -1001,7 +1001,13 @@ async def test_nft_transfer_nft_with_did(two_wallet_nodes: Any, trusted: Any) -> nft_wallet_1_id = coins_response.get("wallet_id") assert nft_wallet_1_id # Check NFT DID - resp = await api_1.nft_get_nfts(dict(wallet_id=nft_wallet_1_id)) + time_left = 15.0 + while time_left > 0: + resp = await api_1.nft_get_nfts(dict(wallet_id=nft_wallet_1_id)) + if coins_response.get("nft_list"): + break + await asyncio.sleep(0.5) + time_left -= 0.5 assert resp.get("success") coins = resp["nft_list"] assert len(coins) == 1 @@ -1116,7 +1122,7 @@ async def test_update_metadata_for_nft_did(two_wallet_nodes: Any, trusted: Any) for i in range(1, num_blocks): await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) # check that new URI was added - time_left = 5.0 + time_left = 10.0 while time_left > 0: coins_response = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_0_id)) try: