Merge pull request #11944 from Chia-Network/nft1_set_status

Pre-launch bug fix N in 1
This commit is contained in:
William Allen
2022-06-22 02:59:42 -05:00
committed by GitHub
5 changed files with 194 additions and 73 deletions
+23 -3
View File
@@ -138,6 +138,7 @@ class WalletRpcApi:
"/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_set_nft_status": self.nft_set_nft_status,
"/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,
@@ -1411,10 +1412,14 @@ 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: Optional[bytes32] = None
if "did_id" in request:
did_id = decode_puzzle_hash(request["did_id"])
did_id = request.get("did_id", "")
if did_id == "":
did_id = b""
else:
did_id = decode_puzzle_hash(did_id)
nft_coin_info = nft_wallet.get_nft_coin_by_id(bytes32.from_hexstr(request["nft_coin_id"]))
if not nft_puzzles.get_nft_info_from_puzzle(nft_coin_info).supports_did:
return {"success": False, "error": "The NFT doesn't support setting a DID."}
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}
@@ -1472,6 +1477,20 @@ class WalletRpcApi:
)
return {"success": True, "nft_wallets": did_nft_wallets}
async def nft_set_nft_status(self, request) -> Dict:
try:
wallet_id: uint32 = uint32(request["wallet_id"])
coin_id: bytes32 = bytes32.from_hexstr(request["coin_id"])
status: bool = request["in_transaction"]
assert self.service.wallet_state_manager is not None
nft_wallet: NFTWallet = self.service.wallet_state_manager.wallets[wallet_id]
if nft_wallet is not None:
await nft_wallet.update_coin_status(coin_id, status)
return {"success": True}
return {"success": False, "error": "NFT wallet doesn't exist."}
except Exception as e:
return {"success": False, "error": f"Cannot change the status of the NFT.{e}"}
async def nft_transfer_nft(self, request):
assert self.service.wallet_state_manager is not None
wallet_id = uint32(request["wallet_id"])
@@ -1500,6 +1519,7 @@ class WalletRpcApi:
if tx.spend_bundle is not None:
spend_bundle = tx.spend_bundle
await self.service.wallet_state_manager.add_pending_transaction(tx)
await nft_wallet.update_coin_status(nft_coin_info.coin.name(), True)
return {"wallet_id": wallet_id, "success": True, "spend_bundle": spend_bundle}
except Exception as e:
log.exception(f"Failed to transfer NFT: {e}")
+12 -16
View File
@@ -208,10 +208,7 @@ class NFTWallet:
] = await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(p2_puzzle_hash)
self.log.debug("Record for %s is: %s", p2_puzzle_hash, derivation_record)
if derivation_record is None:
self.log.info("Received a puzzle hash that is not ours, returning")
# we transferred it to another wallet, remove the coin from our wallet
await self.remove_coin(coin_spend.coin, in_transaction=in_transaction)
return
raise ValueError(f"Cannot find the DerivationRecord for {p2_puzzle_hash}")
p2_puzzle = puzzle_for_pk(derivation_record.pubkey)
if uncurried_nft.supports_did:
inner_puzzle = nft_puzzles.recurry_nft_puzzle(uncurried_nft, coin_spend.solution.to_program(), p2_puzzle)
@@ -1012,26 +1009,24 @@ class NFTWallet:
offer = Offer(notarized_payments, total_spend_bundle, driver_dict)
return offer
async def set_nft_did(
self, nft_coin_info: NFTCoinInfo, did_id: Optional[bytes32], fee: uint64 = uint64(0)
) -> SpendBundle:
async def set_nft_did(self, nft_coin_info: NFTCoinInfo, did_id: bytes, 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()]
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 = []
did_inner_hash = b""
additional_bundles = []
if did_id != b"":
did_inner_hash, did_bundle = await self.get_did_approval_info(nft_id, bytes32(did_id))
additional_bundles.append(did_bundle)
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 if did_id else b"",
new_did_inner_hash=did_inner_hash if did_inner_hash else b"",
new_owner=did_id,
new_did_inner_hash=did_inner_hash,
additional_bundles=additional_bundles,
)
spend_bundle: Optional[SpendBundle] = None
@@ -1039,8 +1034,9 @@ class NFTWallet:
if spend_bundle is None:
spend_bundle = tx.spend_bundle
else:
spend_bundle.aggregate([tx.spend_bundle])
spend_bundle = spend_bundle.aggregate([spend_bundle, tx.spend_bundle])
await self.standard_wallet.push_transaction(tx)
await self.update_coin_status(nft_coin_info.coin.name(), True)
self.wallet_state_manager.state_changed("nft_coin_did_set", self.wallet_info.id)
if spend_bundle:
return spend_bundle
+64 -51
View File
@@ -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,7 +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 DIDInfo
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
@@ -703,7 +704,12 @@ class WalletStateManager:
return None, None
launch_coin: CoinState = response[0]
did_wallet = await DIDWallet.create_new_did_wallet_from_coin_spend(
self, self.main_wallet, launch_coin.coin, did_puzzle, coin_spend, f"DID {launch_id.hex()}"
self,
self.main_wallet,
launch_coin.coin,
did_puzzle,
coin_spend,
f"DID {encode_puzzle_hash(launch_id, DID_HRP)}",
)
wallet_id = did_wallet.id()
wallet_type = WalletType(did_wallet.type())
@@ -721,67 +727,74 @@ class WalletStateManager:
"""
wallet_id = None
wallet_type = None
did_id = None
# DID ID determines which NFT wallet should process the NFT
new_did_id = None
old_did_id = None
# P2 puzzle hash determines if we should ignore the NFT
old_p2_puzhash = uncurried_nft.p2_puzzle.get_tree_hash()
metadata, new_p2_puzhash = get_metadata_and_phs(
uncurried_nft,
coin_spend.solution,
)
if uncurried_nft.supports_did:
# Try to get the latest owner DID
did_id = get_new_owner_did(uncurried_nft, 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)
new_did_id = get_new_owner_did(uncurried_nft, coin_spend.solution.to_program())
old_did_id = uncurried_nft.owner_did
if new_did_id is None:
new_did_id = old_did_id
if new_did_id == b"":
new_did_id = None
self.log.debug(
"Handling NFT: %s old DID:%s, new DID:%s, old P2:%s, new P2:%s",
coin_spend,
old_did_id,
new_did_id,
old_p2_puzhash,
new_p2_puzhash,
)
new_derivation_record: Optional[
DerivationRecord
] = await self.puzzle_store.get_derivation_record_for_puzzle_hash(new_p2_puzhash)
old_derivation_record: Optional[
DerivationRecord
] = await self.puzzle_store.get_derivation_record_for_puzzle_hash(old_p2_puzhash)
if new_derivation_record is None and old_derivation_record is None:
self.log.debug(
"Cannot find a P2 puzzle hash for NFT:%s, this NFT belongs to others.",
uncurried_nft.singleton_launcher_id.hex(),
)
return wallet_id, wallet_type
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))
if nft_wallet_info.did_id == did_id:
self.log.debug(
"Checking NFT wallet %r and inner puzzle %s",
wallet_info.name,
uncurried_nft.inner_puzzle.get_tree_hash(),
if nft_wallet_info.did_id == old_did_id:
self.log.info(
"Removing old NFT, NFT_ID:%s, DID_ID:%s",
uncurried_nft.singleton_launcher_id.hex(),
old_did_id,
)
nft_wallet: NFTWallet = self.wallets[wallet_info.id]
await nft_wallet.remove_coin(coin_spend.coin, in_transaction=True)
if nft_wallet_info.did_id == new_did_id:
self.log.info(
"Adding new NFT, NFT_ID:%s, DID_ID:%s",
uncurried_nft.singleton_launcher_id.hex(),
new_did_id,
)
wallet_id = wallet_info.id
wallet_type = WalletType.NFT
if wallet_id is None:
if did_id is not None:
found_did: bool = False
for wallet_info in await self.get_all_wallet_info_entries(wallet_type=WalletType.DECENTRALIZED_ID):
did_info: DIDInfo = DIDInfo.from_json_dict(json.loads(wallet_info.data))
if did_info.origin_coin is not None and did_info.origin_coin.name() == did_id:
found_did = True
break
if not found_did:
self.log.info(
"Cannot find a profile for DID:%s NFT:%s, checking the inner puzzle ...",
did_id.hex(),
uncurried_nft.singleton_launcher_id.hex(),
)
metadata, p2_puzzle_hash = get_metadata_and_phs(
uncurried_nft,
coin_spend.solution,
)
derivation_record: Optional[
DerivationRecord
] = await self.puzzle_store.get_derivation_record_for_puzzle_hash(p2_puzzle_hash)
if derivation_record is None:
self.log.info(
"Cannot find a P2 puzzle hash for DID:%s NFT:%s, this NFT belongs to others.",
did_id.hex(),
uncurried_nft.singleton_launcher_id.hex(),
)
return wallet_id, wallet_type
if wallet_id is None and new_derivation_record:
# Cannot find an existed NFT wallet for the new NFT
self.log.info(
"Cannot find a NFT wallet for NFT_ID: %s DID: %s, creating a new one.",
"Cannot find a NFT wallet for NFT_ID: %s DID_ID: %s, creating a new one.",
uncurried_nft.singleton_launcher_id,
did_id,
new_did_id,
)
nft_wallet: NFTWallet = await NFTWallet.create_new_nft_wallet(
self, self.main_wallet, did_id=did_id, name="NFT Wallet", in_transaction=True
new_nft_wallet: NFTWallet = await NFTWallet.create_new_nft_wallet(
self, self.main_wallet, did_id=new_did_id, name="NFT Wallet", in_transaction=True
)
wallet_id = uint32(nft_wallet.wallet_id)
wallet_id = uint32(new_nft_wallet.wallet_id)
wallet_type = WalletType.NFT
return wallet_id, wallet_type
async def new_coin_state(
+1 -2
View File
@@ -652,7 +652,6 @@ class TestDIDWallet:
# Transfer DID
new_puzhash = await wallet2.get_new_puzzlehash()
await did_wallet_1.transfer_did(new_puzhash, uint64(0), with_recovery)
print(f"Original launch_id {did_wallet_1.did_info.origin_coin.name()}")
spend_bundle_list = await wallet_node.wallet_state_manager.tx_store.get_unconfirmed_for_wallet(
did_wallet_1.id()
)
@@ -675,7 +674,7 @@ class TestDIDWallet:
if with_recovery:
assert did_wallet_1.did_info.backup_ids[0] == did_wallet_2.did_info.backup_ids[0]
assert did_wallet_1.did_info.num_of_backup_ids_needed == did_wallet_2.did_info.num_of_backup_ids_needed
metadata = json.loads(did_wallet_1.did_info.metadata)
metadata = json.loads(did_wallet_2.did_info.metadata)
assert metadata["Twitter"] == "Test"
assert metadata["GitHub"] == "测试"
+94 -1
View File
@@ -919,6 +919,7 @@ async def test_nft_transfer_nft_with_did(two_wallet_nodes: Any, trusted: Any) ->
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
await wait_rpc_state_condition(
5, api_0.nft_get_nfts, [dict(wallet_id=nft_wallet_0_id)], lambda x: not x["nft_list"]
)
@@ -926,6 +927,7 @@ async def test_nft_transfer_nft_with_did(two_wallet_nodes: Any, trusted: Any) ->
# wait for all wallets to be created
await time_out_assert(10, len, 3, wallet_1.wallet_state_manager.wallets)
did_wallet_1 = wallet_1.wallet_state_manager.wallets[3]
assert len(wallet_node_0.wallet_state_manager.wallets[nft_wallet_0_id].my_nft_coins) == 0
# Check if the NFT owner DID is reset
resp = await api_1.nft_get_by_did({})
assert resp.get("success")
@@ -933,7 +935,6 @@ async def test_nft_transfer_nft_with_did(two_wallet_nodes: Any, trusted: Any) ->
coins_response = await wait_rpc_state_condition(
10, api_1.nft_get_nfts, [dict(wallet_id=nft_wallet_id_1)], lambda x: x["nft_list"]
)
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
@@ -1047,6 +1048,7 @@ async def test_update_metadata_for_nft_did(two_wallet_nodes: Any, trusted: Any)
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
# Check DID NFT
coins_response = await wait_rpc_state_condition(
5, api_0.nft_get_nfts, [dict(wallet_id=nft_wallet_0_id)], lambda x: x["nft_list"]
)
@@ -1068,6 +1070,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
coins_response = await wait_rpc_state_condition(
5,
api_0.nft_get_nfts,
@@ -1196,6 +1199,7 @@ async def test_nft_set_did(two_wallet_nodes: Any, trusted: Any) -> None:
[dict(wallet_id=nft_wallet_1_id)],
lambda x: len(x["nft_list"]) > 0 and x["nft_list"][0].owner_did,
)
assert len(wallet_node_0.wallet_state_manager.wallets[nft_wallet_0_id].my_nft_coins) == 0
coins = resp["nft_list"]
assert len(coins) == 1
@@ -1208,12 +1212,16 @@ async def test_nft_set_did(two_wallet_nodes: Any, trusted: Any) -> None:
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())
)
await make_new_block_with(resp, full_node_api, ph)
coins_response = await wait_rpc_state_condition(
5, api_0.nft_get_by_did, [dict(did_id=hmr_did_id)], lambda x: x.get("wallet_id") is not None
)
nft_wallet_2_id = coins_response.get("wallet_id")
assert nft_wallet_2_id
assert len(wallet_node_0.wallet_state_manager.wallets[nft_wallet_1_id].my_nft_coins) == 0
# Check NFT DID
resp = await wait_rpc_state_condition(
10, api_0.nft_get_nfts, [dict(wallet_id=nft_wallet_2_id)], lambda x: len(x["nft_list"]) > 0
@@ -1234,3 +1242,88 @@ async def test_nft_set_did(two_wallet_nodes: Any, trusted: Any) -> None:
coins = resp["nft_list"]
assert len(coins) == 1
assert coins[0].owner_did is None
assert len(wallet_node_0.wallet_state_manager.wallets[nft_wallet_2_id].my_nft_coins) == 0
@pytest.mark.parametrize(
"trusted",
[True, False],
)
@pytest.mark.asyncio
async def test_set_nft_status(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)
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"]
# Create a NFT without 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())
await make_new_block_with(resp, full_node_api, ph)
# Check DID NFT
coins_response = await wait_rpc_state_condition(
10, api_0.nft_get_nfts, [dict(wallet_id=nft_wallet_0_id)], lambda x: len(x["nft_list"]) > 0
)
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
assert not coins[0].pending_transaction
nft_coin_id = coins[0].nft_coin_id
# Set status
resp = await api_0.nft_set_nft_status(
dict(wallet_id=nft_wallet_0_id, coin_id=nft_coin_id.hex(), in_transaction=True)
)
assert resp.get("success")
coins_response = await api_0.nft_get_nfts(dict(wallet_id=nft_wallet_0_id))
assert coins_response["nft_list"], isinstance(coins_response, dict)
assert coins_response.get("success")
coins = coins_response["nft_list"]
assert coins[0].pending_transaction