Repository: Chia-Network/chia-blockchain

Issue:
Delete key from GUI fails [Bug] 
### What happened?

Delete key from the GUI fails, but it succeeds from the CLI.

To recreate:
- create a new key with the GUI
- "logout" to show the `wallet keys` diaglog
- Click the three vertical dots on the new key, and click `Delete`

A new spinner appears, but the key is not deleted. A `WARNING` message appears in the log (see below).

I'm seeing the same behavior on Windows and Linux. The wallet is synced when I attempt to delete it. It doesn't seem to matter whether the node is synced.

The stack trace is from 2.0.1, though I'm also seeing it in 2.1.2-rc2.

### Version

2.0.1

### What platform are you using?

Windows

### What ui mode are you using?

GUI

### Relevant log output

```shell
2023-12-05T13:35:19.539 wallet chia.rpc.rpc_server        : WARNING  Error while handling message: Traceback (most recent call last):
  File "chia/rpc/rpc_server.py", line 340, in safe_handle
  File "chia/rpc/rpc_server.py", line 331, in ws_api
  File "chia/rpc/wallet_rpc_api.py", line 462, in check_delete_key
  File "chia/rpc/wallet_rpc_api.py", line 440, in _check_key_used_for_rewards
  File "chia/util/bech32m.py", line 120, in decode_puzzle_hash
ValueError: Invalid Address
```



Discussion:
@danieljperry while the spinner is visible, the GUI is asking the wallet to check if the wallet-to-be-deleted is used for farming or pool rewards. The code in question is:

```
        farmer_target = config["farmer"].get("xch_target_address")
        pool_target = config["pool"].get("xch_target_address")
        address_to_check: List[bytes32] = [decode_puzzle_hash(farmer_target), decode_puzzle_hash(pool_target)]
```

Can you check if your config has valid values for the farmer/pool `xch_target_address`?
Thanks, that was the issue. Both instances of the target address were blank:
`xch_target_address: ''`

When I added a real address, I could delete the key. However, the UX issue still remains. If I am just running a wallet and am not farming, I cannot delete a key from the GUI unless I add a dummy address to my config.yaml. Before the `address_to_check` line, would it be possible to check whether the `farmer_target` and `pool_target` are valid addresses?
Zlatko can u take a look at this if u have some time

Resolving patch:
diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py
--- a/chia/rpc/wallet_rpc_api.py
+++ b/chia/rpc/wallet_rpc_api.py
@@ -483,14 +483,31 @@ async def _check_key_used_for_rewards(
             return False, False
 
         config: Dict[str, Any] = load_config(new_root, "config.yaml")
-        farmer_target = config["farmer"].get("xch_target_address")
-        pool_target = config["pool"].get("xch_target_address")
-        address_to_check: List[bytes32] = [decode_puzzle_hash(farmer_target), decode_puzzle_hash(pool_target)]
+        farmer_target = config["farmer"].get("xch_target_address", "")
+        pool_target = config["pool"].get("xch_target_address", "")
+        address_to_check: List[bytes32] = []
+
+        try:
+            farmer_decoded = decode_puzzle_hash(farmer_target)
+            address_to_check.append(farmer_decoded)
+        except ValueError:
+            farmer_decoded = None
+
+        try:
+            pool_decoded = decode_puzzle_hash(pool_target)
+            address_to_check.append(pool_decoded)
+        except ValueError:
+            pool_decoded = None
 
         found_addresses: Set[bytes32] = match_address_to_sk(sk, address_to_check, max_ph_to_search)
+        found_farmer = False
+        found_pool = False
+
+        if farmer_decoded is not None:
+            found_farmer = farmer_decoded in found_addresses
 
-        found_farmer = address_to_check[0] in found_addresses
-        found_pool = address_to_check[1] in found_addresses
+        if pool_decoded is not None:
+            found_pool = pool_decoded in found_addresses
 
         return found_farmer, found_pool
 


Test patch:
diff --git a/tests/wallet/rpc/test_wallet_rpc.py b/tests/wallet/rpc/test_wallet_rpc.py
--- a/tests/wallet/rpc/test_wallet_rpc.py
+++ b/tests/wallet/rpc/test_wallet_rpc.py
@@ -1610,6 +1610,45 @@ async def have_nfts():
     }
 
 
+async def _check_delete_key(
+    client: WalletRpcClient, wallet_node: WalletNode, farmer_fp: int, pool_fp: int, observer: bool = False
+) -> None:
+    # Add in reward addresses into farmer and pool for testing delete key checks
+    # set farmer to first private key
+    create_sk = master_sk_to_wallet_sk_unhardened if observer else master_sk_to_wallet_sk
+
+    sk = await wallet_node.get_key_for_fingerprint(farmer_fp)
+    assert sk is not None
+    farmer_ph = create_puzzlehash_for_pk(create_sk(sk, uint32(0)).get_g1())
+
+    sk = await wallet_node.get_key_for_fingerprint(pool_fp)
+    assert sk is not None
+    pool_ph = create_puzzlehash_for_pk(create_sk(sk, uint32(0)).get_g1())
+
+    with lock_and_load_config(wallet_node.root_path, "config.yaml") as test_config:
+        test_config["farmer"]["xch_target_address"] = encode_puzzle_hash(farmer_ph, "txch")
+        test_config["pool"]["xch_target_address"] = encode_puzzle_hash(pool_ph, "txch")
+        save_config(wallet_node.root_path, "config.yaml", test_config)
+
+    # Check farmer_fp key
+    sk_dict = await client.check_delete_key(farmer_fp)
+    assert sk_dict["fingerprint"] == farmer_fp
+    assert sk_dict["used_for_farmer_rewards"] is True
+    assert sk_dict["used_for_pool_rewards"] is False
+
+    # Check pool_fp key
+    sk_dict = await client.check_delete_key(pool_fp)
+    assert sk_dict["fingerprint"] == pool_fp
+    assert sk_dict["used_for_farmer_rewards"] is False
+    assert sk_dict["used_for_pool_rewards"] is True
+
+    # Check unknown key
+    sk_dict = await client.check_delete_key(123456, 10)
+    assert sk_dict["fingerprint"] == 123456
+    assert sk_dict["used_for_farmer_rewards"] is False
+    assert sk_dict["used_for_pool_rewards"] is False
+
+
 @pytest.mark.limit_consensus_modes(allowed=[ConsensusMode.PLAIN, ConsensusMode.HARD_FORK_2_0], reason="save time")
 @pytest.mark.anyio
 async def test_key_and_address_endpoints(wallet_rpc_environment: WalletRpcTestEnvironment):
@@ -1658,67 +1697,32 @@ async def test_key_and_address_endpoints(wallet_rpc_environment: WalletRpcTestEn
     sk_dict = await client.get_private_key(pks[1])
     assert sk_dict["fingerprint"] == pks[1]
 
-    # Add in reward addresses into farmer and pool for testing delete key checks
-    # set farmer to first private key
-    sk = await wallet_node.get_key_for_fingerprint(pks[0])
-    assert sk is not None
-    test_ph = create_puzzlehash_for_pk(master_sk_to_wallet_sk(sk, uint32(0)).get_g1())
+    # test hardened keys
+    await _check_delete_key(client=client, wallet_node=wallet_node, farmer_fp=pks[0], pool_fp=pks[1], observer=False)
+
+    # test observer keys
+    await _check_delete_key(client=client, wallet_node=wallet_node, farmer_fp=pks[0], pool_fp=pks[1], observer=True)
+
+    # set farmer to empty string
     with lock_and_load_config(wallet_node.root_path, "config.yaml") as test_config:
-        test_config["farmer"]["xch_target_address"] = encode_puzzle_hash(test_ph, "txch")
-        # set pool to second private key
-        sk = await wallet_node.get_key_for_fingerprint(pks[1])
-        assert sk is not None
-        test_ph = create_puzzlehash_for_pk(master_sk_to_wallet_sk(sk, uint32(0)).get_g1())
-        test_config["pool"]["xch_target_address"] = encode_puzzle_hash(test_ph, "txch")
+        test_config["farmer"]["xch_target_address"] = ""
         save_config(wallet_node.root_path, "config.yaml", test_config)
 
-    # Check first key
-    sk_dict = await client.check_delete_key(pks[0])
-    assert sk_dict["fingerprint"] == pks[0]
-    assert sk_dict["used_for_farmer_rewards"] is True
-    assert sk_dict["used_for_pool_rewards"] is False
-
-    # Check second key
+    # Check key
     sk_dict = await client.check_delete_key(pks[1])
     assert sk_dict["fingerprint"] == pks[1]
     assert sk_dict["used_for_farmer_rewards"] is False
     assert sk_dict["used_for_pool_rewards"] is True
 
-    # Check unknown key
-    sk_dict = await client.check_delete_key(123456, 10)
-    assert sk_dict["fingerprint"] == 123456
-    assert sk_dict["used_for_farmer_rewards"] is False
-    assert sk_dict["used_for_pool_rewards"] is False
-
-    # Add in observer reward addresses into farmer and pool for testing delete key checks
-    # set farmer to first private key
-    sk = await wallet_node.get_key_for_fingerprint(pks[0])
-    assert sk is not None
-    test_ph = create_puzzlehash_for_pk(master_sk_to_wallet_sk_unhardened(sk, uint32(0)).get_g1())
+    # set farmer and pool to empty string
     with lock_and_load_config(wallet_node.root_path, "config.yaml") as test_config:
-        test_config["farmer"]["xch_target_address"] = encode_puzzle_hash(test_ph, "txch")
-        # set pool to second private key
-        sk = await wallet_node.get_key_for_fingerprint(pks[1])
-        assert sk is not None
-        test_ph = create_puzzlehash_for_pk(master_sk_to_wallet_sk_unhardened(sk, uint32(0)).get_g1())
-        test_config["pool"]["xch_target_address"] = encode_puzzle_hash(test_ph, "txch")
+        test_config["farmer"]["xch_target_address"] = ""
+        test_config["pool"]["xch_target_address"] = ""
         save_config(wallet_node.root_path, "config.yaml", test_config)
 
-    # Check first key
+    # Check key
     sk_dict = await client.check_delete_key(pks[0])
     assert sk_dict["fingerprint"] == pks[0]
-    assert sk_dict["used_for_farmer_rewards"] is True
-    assert sk_dict["used_for_pool_rewards"] is False
-
-    # Check second key
-    sk_dict = await client.check_delete_key(pks[1])
-    assert sk_dict["fingerprint"] == pks[1]
-    assert sk_dict["used_for_farmer_rewards"] is False
-    assert sk_dict["used_for_pool_rewards"] is True
-
-    # Check unknown key
-    sk_dict = await client.check_delete_key(123456, 10)
-    assert sk_dict["fingerprint"] == 123456
     assert sk_dict["used_for_farmer_rewards"] is False
     assert sk_dict["used_for_pool_rewards"] is False