mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2024-09-11 18:58:59 +03:00
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:
parent
7e6a906056
commit
3879cea12e
@ -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
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user