diff --git a/chia/cmds/dao.py b/chia/cmds/dao.py index b54d488be2d46..9fe4cb1074ab8 100644 --- a/chia/cmds/dao.py +++ b/chia/cmds/dao.py @@ -178,7 +178,7 @@ def dao_create_cmd( ) -> None: from .dao_funcs import create_dao_wallet - if self_destruct == proposal_timelock: + if self_destruct == proposal_timelock: # pragma: no cover raise ValueError("Self Destruct and Proposal Timelock cannot be the same value") print("Creating new DAO") diff --git a/chia/wallet/wallet_state_manager.py b/chia/wallet/wallet_state_manager.py index b51085c50291f..6029b36b6feb8 100644 --- a/chia/wallet/wallet_state_manager.py +++ b/chia/wallet/wallet_state_manager.py @@ -789,7 +789,7 @@ class WalletStateManager: # Check if the coin is a DAO CAT dao_cat_args = match_dao_cat_puzzle(uncurried) if dao_cat_args: - return await self.handle_dao_cat(dao_cat_args, parent_coin_state, coin_state, coin_spend), None + return await self.handle_dao_cat(dao_cat_args, parent_coin_state, coin_state, coin_spend, fork_height), None # Check if the coin is a CAT cat_curried_args = match_cat_puzzle(uncurried) @@ -1021,6 +1021,7 @@ class WalletStateManager: parent_coin_state: CoinState, coin_state: CoinState, coin_spend: CoinSpend, + fork_height: Optional[uint32], ) -> Optional[WalletIdentifier]: """ Handle the new coin when it is a DAO CAT @@ -1032,6 +1033,19 @@ class WalletStateManager: assert isinstance(wallet, DAOCATWallet) if wallet.dao_cat_info.limitations_program_hash == asset_id: return WalletIdentifier.create(wallet) + # Found a DAO_CAT, but we don't have a wallet for it. Add to unacknowledged + await self.interested_store.add_unacknowledged_token( + asset_id, + CATWallet.default_wallet_name_for_unknown_cat(asset_id.hex()), + None if parent_coin_state.spent_height is None else uint32(parent_coin_state.spent_height), + parent_coin_state.coin.puzzle_hash, + ) + await self.interested_store.add_unacknowledged_coin_state( + asset_id, + coin_state, + fork_height, + ) + self.state_changed("added_stray_cat") return None # pragma: no cover async def handle_cat( diff --git a/tests/wallet/dao_wallet/test_dao_wallets.py b/tests/wallet/dao_wallet/test_dao_wallets.py index 86c025ee1b773..fde45bc2f4284 100644 --- a/tests/wallet/dao_wallet/test_dao_wallets.py +++ b/tests/wallet/dao_wallet/test_dao_wallets.py @@ -3348,3 +3348,190 @@ async def test_dao_votes( await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) assert dao_wallet_0.dao_info.proposals_list[3].amount_voted == 210000 + + +@pytest.mark.limit_consensus_modes(reason="does not depend on consensus rules") +@pytest.mark.parametrize( + "trusted", + [True, False], +) +@pytest.mark.anyio +async def test_dao_resync( + self_hostname: str, two_wallet_nodes: SimulatorsAndWallets, trusted: bool, consensus_mode: ConsensusMode +) -> None: + full_nodes, wallets, _ = two_wallet_nodes + full_node_api = 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_api_0 = WalletRpcApi(wallet_node_0) + wallet_api_1 = WalletRpcApi(wallet_node_1) + wallet_0 = wallet_node_0.wallet_state_manager.main_wallet + wallet_1 = wallet_node_1.wallet_state_manager.main_wallet + ph = await wallet_0.get_new_puzzlehash() + ph_1 = await wallet_1.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(self_hostname, full_node_server.get_port()), None) + await server_1.start_client(PeerInfo(self_hostname, full_node_server.get_port()), None) + + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph_1)) + + funds = calculate_pool_reward(uint32(1)) + calculate_base_farmer_reward(uint32(1)) + + await time_out_assert(20, wallet_0.get_confirmed_balance, funds) + await time_out_assert(20, full_node_api.wallet_is_synced, True, wallet_node_0) + + cat_amt = 2000 + dao_rules = DAORules( + proposal_timelock=uint64(10), + soft_close_length=uint64(5), + attendance_required=uint64(1000), # 10% + pass_percentage=uint64(5100), # 51% + self_destruct_length=uint64(20), + oracle_spend_delay=uint64(10), + proposal_minimum_amount=uint64(1), + ) + + fee = uint64(10) + fee_for_cat = uint64(20) + dao_wallet_0 = await DAOWallet.create_new_dao_and_wallet( + wallet_node_0.wallet_state_manager, + wallet_0, + uint64(cat_amt * 2), + dao_rules, + DEFAULT_TX_CONFIG, + fee=fee, + fee_for_cat=fee_for_cat, + ) + assert dao_wallet_0 is not None + + txs = await wallet_0.wallet_state_manager.tx_store.get_all_unconfirmed() + await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) + + treasury_id = dao_wallet_0.dao_info.treasury_id + + # get the cat wallets + cat_wallet_0 = dao_wallet_0.wallet_state_manager.wallets[dao_wallet_0.dao_info.cat_wallet_id] + # dao_cat_wallet_0 = dao_wallet_0.wallet_state_manager.wallets[dao_wallet_0.dao_info.dao_cat_wallet_id] + + # Create the other user's wallet from the treasury id + dao_wallet_1 = await DAOWallet.create_new_dao_wallet_for_existing_dao( + wallet_node_1.wallet_state_manager, + wallet_1, + treasury_id, + ) + assert dao_wallet_1 is not None + assert dao_wallet_0.dao_info.treasury_id == dao_wallet_1.dao_info.treasury_id + + # Get the cat wallets for wallet_1 + cat_wallet_1 = dao_wallet_1.wallet_state_manager.wallets[dao_wallet_1.dao_info.cat_wallet_id] + + # Send some cats to the dao_cat lockup + dao_cat_amt = uint64(100) + txs = await dao_wallet_0.enter_dao_cat_voting_mode(dao_cat_amt, DEFAULT_TX_CONFIG) + for tx in txs: + await wallet_0.wallet_state_manager.add_pending_transaction(tx) + + await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) + await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) + await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) + + # send some cats from wallet_0 to wallet_1 so we can test voting + cat_txs = await cat_wallet_0.generate_signed_transaction([cat_amt], [ph_1], DEFAULT_TX_CONFIG) + await wallet_0.wallet_state_manager.add_pending_transaction(cat_txs[0]) + + await full_node_api.wait_transaction_records_entered_mempool(records=cat_txs, timeout=60) + await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) + await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=30) + + await time_out_assert(10, cat_wallet_1.get_confirmed_balance, cat_amt) + + recipient_puzzle_hash = await wallet_1.get_new_puzzlehash() + proposal_amount_1 = uint64(9998) + xch_proposal_inner = generate_simple_proposal_innerpuz( + treasury_id, + [recipient_puzzle_hash], + [proposal_amount_1], + [None], + ) + proposal_tx = await dao_wallet_0.generate_new_proposal(xch_proposal_inner, DEFAULT_TX_CONFIG, uint64(10)) + await wallet_0.wallet_state_manager.add_pending_transaction(proposal_tx) + await full_node_api.wait_transaction_records_entered_mempool(records=[proposal_tx], timeout=60) + await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) + + # make another proposal spending all the dao_cats + xch_proposal_inner = generate_simple_proposal_innerpuz( + treasury_id, + [recipient_puzzle_hash], + [proposal_amount_1], + [None], + ) + proposal_tx = await dao_wallet_0.generate_new_proposal(xch_proposal_inner, DEFAULT_TX_CONFIG) + await wallet_0.wallet_state_manager.add_pending_transaction(proposal_tx) + await full_node_api.wait_transaction_records_entered_mempool(records=[proposal_tx], timeout=60) + await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) + await full_node_api.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=30) + + # set flag to reset wallet sync data on start + await wallet_api_0.set_wallet_resync_on_startup({"enable": True}) + fingerprint_0 = wallet_node_0.logged_in_fingerprint + await wallet_api_1.set_wallet_resync_on_startup({"enable": True}) + fingerprint_1 = wallet_node_1.logged_in_fingerprint + # Delete tx records + await wallet_node_0.wallet_state_manager.tx_store.rollback_to_block(0) + wallet_node_0._close() + await wallet_node_0._await_closed() + wallet_node_1._close() + await wallet_node_1._await_closed() + wallet_node_0.config["database_path"] = "wallet/db/blockchain_wallet_v2_test_1_CHALLENGE_KEY.sqlite" + wallet_node_1.config["database_path"] = "wallet/db/blockchain_wallet_v2_test_2_CHALLENGE_KEY.sqlite" + # Start resync + await wallet_node_0._start_with_fingerprint(fingerprint_0) + await wallet_node_1._start_with_fingerprint(fingerprint_1) + await server_0.start_client(PeerInfo(self_hostname, full_node_server.get_port()), None) + await server_1.start_client(PeerInfo(self_hostname, full_node_server.get_port()), None) + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash_0)) + await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=20) + await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_1, timeout=20) + wallet_0 = wallet_node_0.wallet_state_manager.main_wallet + + assert len(await wallet_node_0.wallet_state_manager.get_all_wallet_info_entries()) == 1 + + new_dao_wallet = await DAOWallet.create_new_dao_wallet_for_existing_dao( + wallet_node_0.wallet_state_manager, + wallet_0, + treasury_id, + ) + + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash_0)) + await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=20) + assert len(await wallet_node_0.wallet_state_manager.get_all_wallet_info_entries()) == 4 + new_cat_wallet = new_dao_wallet.wallet_state_manager.wallets[new_dao_wallet.dao_info.cat_wallet_id] + new_dao_cat_wallet = new_dao_wallet.wallet_state_manager.wallets[new_dao_wallet.dao_info.dao_cat_wallet_id] + + await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash_0)) + await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_0, timeout=20) + + # Check the new wallets have the right balances + await time_out_assert(20, new_cat_wallet.get_confirmed_balance, cat_amt - dao_cat_amt) + await time_out_assert(20, new_dao_cat_wallet.get_confirmed_balance, dao_cat_amt) + + # Check the proposals are found and accurate + assert len(new_dao_wallet.dao_info.proposals_list) == 2 + assert new_dao_wallet.dao_info.proposals_list[0].yes_votes == 10 + assert new_dao_wallet.dao_info.proposals_list[1].yes_votes == 100