Delete DID wallet after transfer (#13389)

* Delete DID wallet after transfer

* Adjust tests

* Fix merge

* Remove rl_wallet

* Fix tests

* Fix DID bugs

* Add tests

* Add additional spend for DID update

* Fix unit test

* Handle bugged DID

* Cover empty hint case

* Fix unnecessary DID deletion
This commit is contained in:
Kronus91 2022-11-14 09:12:28 -08:00 committed by GitHub
parent 7e6a906056
commit 3879cea12e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 107 additions and 14 deletions

View File

@ -18,6 +18,7 @@ from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.coin_spend import CoinSpend
from chia.types.spend_bundle import SpendBundle
from chia.util.ints import uint64, uint32, uint8, uint128
from chia.wallet.did_wallet.did_wallet_puzzles import create_fullpuz
from chia.wallet.util.transaction_type import TransactionType
from chia.util.condition_tools import conditions_dict_for_solution, pkm_pairs_for_conditions_dict
from chia.wallet.did_wallet.did_info import DIDInfo
@ -374,6 +375,10 @@ class DIDWallet:
return
self.log.info(f"DID wallet has been notified that coin was added: {coin.name()}:{coin}")
inner_puzzle = await self.inner_puzzle_for_did_puzzle(coin.puzzle_hash)
# Check inner puzzle consistency
assert self.did_info.origin_coin is not None
full_puzzle = create_fullpuz(inner_puzzle, self.did_info.origin_coin.name())
assert full_puzzle.get_tree_hash() == coin.puzzle_hash
if self.did_info.temp_coin is not None:
self.wallet_state_manager.state_changed("did_coin_added", self.wallet_info.id)
new_info = DIDInfo(
@ -466,7 +471,7 @@ class DIDWallet:
child_coin,
new_did_inner_puzhash,
bytes(new_pubkey),
False,
did_info.sent_recovery_transaction,
did_info.metadata,
)
@ -556,10 +561,19 @@ class DIDWallet:
coins = await self.select_coins(uint64(1))
assert coins is not None
coin = coins.pop()
new_puzhash = await self.get_new_did_inner_hash()
new_inner_puzzle = await self.get_new_did_innerpuz()
uncurried = did_wallet_puzzles.uncurry_innerpuz(new_inner_puzzle)
assert uncurried is not None
p2_puzzle = uncurried[0]
# innerpuz solution is (mode, p2_solution)
p2_solution = self.standard_wallet.make_solution(
primaries=[{"puzzlehash": new_puzhash, "amount": uint64(coin.amount), "memos": [new_puzhash]}],
primaries=[
{
"puzzlehash": new_inner_puzzle.get_tree_hash(),
"amount": uint64(coin.amount),
"memos": [p2_puzzle.get_tree_hash()],
}
],
coin_announcements={coin.name()},
)
innersol: Program = Program.to([1, p2_solution])
@ -583,7 +597,24 @@ class DIDWallet:
innersol,
]
)
list_of_coinspends = [CoinSpend(coin, full_puzzle, fullsol)]
# Create an additional spend to confirm the change on-chain
new_full_puzzle: Program = did_wallet_puzzles.create_fullpuz(
new_inner_puzzle,
self.did_info.origin_coin.name(),
)
new_full_sol = Program.to(
[
[
coin.parent_coin_info,
innerpuz.get_tree_hash(),
coin.amount,
],
coin.amount,
innersol,
]
)
new_coin = Coin(coin.name(), new_full_puzzle.get_tree_hash(), coin.amount)
list_of_coinspends = [CoinSpend(coin, full_puzzle, fullsol), CoinSpend(new_coin, new_full_puzzle, new_full_sol)]
unsigned_spend_bundle = SpendBundle(list_of_coinspends, G2Element())
spend_bundle = await self.sign(unsigned_spend_bundle)
if fee > 0:
@ -716,7 +747,7 @@ class DIDWallet:
self,
coin_announcements: Optional[Set[bytes]] = None,
puzzle_announcements: Optional[Set[bytes]] = None,
new_innerpuzhash: Optional[bytes32] = None,
new_innerpuzzle: Optional[Program] = None,
):
assert self.did_info.current_inner is not None
assert self.did_info.origin_coin is not None
@ -725,11 +756,19 @@ class DIDWallet:
coin = coins.pop()
innerpuz: Program = self.did_info.current_inner
# Quote message puzzle & solution
if new_innerpuzhash is None:
new_innerpuzhash = innerpuz.get_tree_hash()
if new_innerpuzzle is None:
new_innerpuzzle = innerpuz
uncurried = did_wallet_puzzles.uncurry_innerpuz(new_innerpuzzle)
assert uncurried is not None
p2_puzzle = uncurried[0]
p2_solution = self.standard_wallet.make_solution(
primaries=[{"puzzlehash": new_innerpuzhash, "amount": uint64(coin.amount), "memos": [new_innerpuzhash]}],
primaries=[
{
"puzzlehash": new_innerpuzzle.get_tree_hash(),
"amount": uint64(coin.amount),
"memos": [p2_puzzle.get_tree_hash()],
}
],
puzzle_announcements=puzzle_announcements,
coin_announcements=coin_announcements,
)
@ -834,10 +873,17 @@ class DIDWallet:
message = did_wallet_puzzles.create_recovery_message_puzzle(recovering_coin_name, newpuz, pubkey)
innermessage = message.get_tree_hash()
innerpuz: Program = self.did_info.current_inner
uncurried = did_wallet_puzzles.uncurry_innerpuz(innerpuz)
assert uncurried is not None
p2_puzzle = uncurried[0]
# innerpuz solution is (mode, p2_solution)
p2_solution = self.standard_wallet.make_solution(
primaries=[
{"puzzlehash": innerpuz.get_tree_hash(), "amount": uint64(coin.amount), "memos": []},
{
"puzzlehash": innerpuz.get_tree_hash(),
"amount": uint64(coin.amount),
"memos": [p2_puzzle.get_tree_hash()],
},
{"puzzlehash": innermessage, "amount": uint64(0), "memos": []},
],
)
@ -1393,7 +1439,7 @@ class DIDWallet:
None,
None,
None,
False,
True,
metadata,
)
return did_info

View File

@ -50,6 +50,7 @@ from chia.wallet.derive_keys import (
master_sk_to_wallet_sk_unhardened_intermediate,
_derive_path_unhardened,
)
from chia.wallet.did_wallet.did_info import DIDInfo
from chia.wallet.wallet_protocol import WalletProtocol
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
@ -763,18 +764,51 @@ class WalletStateManager:
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)
old_inner_puzhash = DID_INNERPUZ_MOD.curry(
p2_puzzle, recovery_list_hash, num_verification, singleton_struct, metadata
).get_tree_hash()
derivation_record = None
# Hint is required, if it doesn't have any hint then it should a bugged DID
is_bugged = len(hint_list) == 0
new_p2_puzhash: Optional[bytes32] = 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:
new_p2_puzhash = hint
break
# Check if the mismatch is because of the memo bug
if hint == old_inner_puzhash:
new_p2_puzhash = hint
is_bugged = True
break
if is_bugged:
# This is a bugged DID, check if we are owner
derivation_record = await self.puzzle_store.get_derivation_record_for_puzzle_hash(bytes32(p2_puzzle))
launch_id: bytes32 = bytes32(bytes(singleton_struct.rest().first())[1:])
if derivation_record is None:
if new_p2_puzhash == old_inner_puzhash or p2_puzzle == new_p2_puzhash:
# We only delete DID when the p2 puzzle doesn't change
self.log.info(f"Not sure if the DID belong to us, {coin_state}. Waiting for next spend ...")
return wallet_id, wallet_type
self.log.info(f"Received state for the coin that doesn't belong to us {coin_state}")
# Check if it was owned by us
removed_wallet_ids = []
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 launch_id == did_info.origin_coin.name()
and not did_info.sent_recovery_transaction
):
await self.user_store.delete_wallet(wallet_info.id)
removed_wallet_ids.append(wallet_info.id)
for remove_id in removed_wallet_ids:
self.wallets.pop(remove_id)
self.log.info(f"Removed DID wallet {remove_id}, Launch_ID: {launch_id.hex()}")
else:
our_inner_puzzle: Program = self.main_wallet.puzzle_for_pk(derivation_record.pubkey)
launch_id: bytes32 = bytes32(bytes(singleton_struct.rest().first())[1:])
self.log.info(f"Found DID, launch_id {launch_id}.")
did_puzzle = DID_INNERPUZ_MOD.curry(
our_inner_puzzle, recovery_list_hash, num_verification, singleton_struct, metadata

View File

@ -28,6 +28,10 @@ async def get_wallet_num(wallet_manager):
return len(await wallet_manager.get_all_wallet_info_entries())
def get_parent_num(did_wallet: DIDWallet):
return len(did_wallet.did_info.parent_info)
class TestDIDWallet:
@pytest.mark.parametrize(
"trusted",
@ -566,7 +570,6 @@ class TestDIDWallet:
)
coins = await did_wallet.select_coins(1)
coin = coins.pop()
new_ph = await did_wallet_4.get_new_did_inner_hash()
pubkey = (
await did_wallet_4.wallet_state_manager.get_unused_derivation_record(did_wallet_4.wallet_info.id)
@ -580,6 +583,7 @@ class TestDIDWallet:
await time_out_assert_not_none(5, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph2))
await time_out_assert(15, wallet.get_pending_change_balance, 0)
(
test_info_list,
test_message_spend_bundle,
@ -591,7 +595,7 @@ class TestDIDWallet:
)
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())
await time_out_assert_not_none(15, full_node_api.full_node.mempool_manager.get_spendbundle, spend_bundle.name())
for i in range(1, num_blocks):
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
@ -697,6 +701,7 @@ class TestDIDWallet:
)
)
did_wallet_2: Optional[DIDWallet] = wallet_node_2.wallet_state_manager.wallets[did_wallets[0].id]
assert len(wallet_node.wallet_state_manager.wallets) == 1
assert did_wallet_1.did_info.origin_coin == did_wallet_2.did_info.origin_coin
if with_recovery:
assert did_wallet_1.did_info.backup_ids[0] == did_wallet_2.did_info.backup_ids[0]
@ -964,8 +969,12 @@ class TestDIDWallet:
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
await time_out_assert(15, did_wallet_1.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 101)
await time_out_assert(15, wallet.get_confirmed_balance, 3999999998899)
await time_out_assert(15, wallet.get_unconfirmed_balance, 3999999998899)
puzhash = did_wallet_1.did_info.current_inner.get_tree_hash()
parent_num = get_parent_num(did_wallet_1)
metadata = {}
metadata["Twitter"] = "http://www.twitter.com"
await did_wallet_1.update_metadata(metadata)
@ -975,8 +984,12 @@ class TestDIDWallet:
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph1))
await time_out_assert(15, did_wallet_1.get_confirmed_balance, 101)
await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 101)
await time_out_assert(15, get_parent_num, parent_num + 2, did_wallet_1)
assert puzhash != did_wallet_1.did_info.current_inner.get_tree_hash()
await time_out_assert(15, wallet.get_confirmed_balance, 3999999997899)
await time_out_assert(15, wallet.get_unconfirmed_balance, 3999999997899)
assert did_wallet_1.did_info.metadata.find("Twitter") > 0
@pytest.mark.parametrize(