Repository: Chia-Network/chia-blockchain

Issue:
[Bug] Datalayer subscribing to new store sometimes fails with Launcher ID is not a valid coin
### What happened?

Occasional failures when subscribing to a datalayer singleton with `Launcher ID <id> is not a valid coin` - for example:
```
2023-05-30T15:25:35.014 wallet chia.rpc.util              : WARNING  Error while handling message: Traceback (most recent call last):
  File "chia\rpc\util.py", line 18, in inner
  File "chia\rpc\wallet_rpc_api.py", line 3094, in dl_track_new
  File "chia\data_layer\data_layer_wallet.py", line 220, in track_new_launcher_id
  File "chia\data_layer\data_layer_wallet.py", line 196, in get_launcher_coin_state
ValueError: Launcher ID fa47700cb693529602c3eab47a5d681ffe0145dabeee6c69cabdd7869537b917 is not a valid coin
```

The code in `dl_track_new` only asks for the coin from a single node. If this node for whatever reason doesn't have this coin, this error is returned. The wallet will likely continue to ask the same peer again on a retry.

It might make sense to use (certainly when in light wallet mode) the full list of peers and ask all the peers about this coin, as one node might have it correctly. This would hopefully reduce errors where the one selected node happens for whatever reason to not have this coin in its DB

### Version

all

### What platform are you using?

Windows

### What ui mode are you using?

GUI

### Relevant log output

_No response_


Discussion:

Resolving patch:
diff --git a/chia/data_layer/data_layer_errors.py b/chia/data_layer/data_layer_errors.py
--- a/chia/data_layer/data_layer_errors.py
+++ b/chia/data_layer/data_layer_errors.py
@@ -40,3 +40,7 @@ def __init__(self, key: bytes) -> None:
 
 class OfferIntegrityError(Exception):
     pass
+
+
+class LauncherCoinNotFoundError(Exception):
+    pass
diff --git a/chia/data_layer/data_layer_wallet.py b/chia/data_layer/data_layer_wallet.py
--- a/chia/data_layer/data_layer_wallet.py
+++ b/chia/data_layer/data_layer_wallet.py
@@ -11,7 +11,7 @@
 from typing_extensions import final
 
 from chia.consensus.block_record import BlockRecord
-from chia.data_layer.data_layer_errors import OfferIntegrityError
+from chia.data_layer.data_layer_errors import LauncherCoinNotFoundError, OfferIntegrityError
 from chia.data_layer.data_layer_util import OfferStore, ProofOfInclusion, ProofOfInclusionLayer, StoreProofs, leaf_hash
 from chia.protocols.wallet_protocol import CoinState
 from chia.server.ws_connection import WSChiaConnection
@@ -199,7 +199,7 @@ async def get_launcher_coin_state(self, launcher_id: bytes32, peer: WSChiaConnec
         )
 
         if len(coin_states) == 0:
-            raise ValueError(f"Launcher ID {launcher_id} is not a valid coin")
+            raise LauncherCoinNotFoundError(f"Launcher ID {launcher_id} is not a valid coin")
         if coin_states[0].coin.puzzle_hash != SINGLETON_LAUNCHER.get_tree_hash():
             raise ValueError(f"Coin with ID {launcher_id} is not a singleton launcher")
         if coin_states[0].created_height is None:
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
@@ -9,6 +9,7 @@
 from blspy import AugSchemeMPL, G1Element, G2Element, PrivateKey
 
 from chia.consensus.block_rewards import calculate_base_farmer_reward
+from chia.data_layer.data_layer_errors import LauncherCoinNotFoundError
 from chia.data_layer.data_layer_wallet import DataLayerWallet
 from chia.pools.pool_wallet import PoolWallet
 from chia.pools.pool_wallet_info import FARMING_TO_POOL, PoolState, PoolWalletInfo, create_pool_state
@@ -3185,10 +3186,18 @@ async def dl_track_new(self, request) -> Dict:
                 dl_wallet = await DataLayerWallet.create_new_dl_wallet(
                     self.service.wallet_state_manager,
                 )
-        await dl_wallet.track_new_launcher_id(
-            bytes32.from_hexstr(request["launcher_id"]),
-            self.service.get_full_node_peer(),
-        )
+        peer_list = self.service.get_full_node_peers_in_order()
+        peer_length = len(peer_list)
+        for i, peer in enumerate(peer_list):
+            try:
+                await dl_wallet.track_new_launcher_id(
+                    bytes32.from_hexstr(request["launcher_id"]),
+                    peer,
+                )
+            except LauncherCoinNotFoundError as e:
+                if i == peer_length - 1:
+                    raise e  # raise the error if we've tried all peers
+                continue  # try some other peers, maybe someone has it
         return {}
 
     async def dl_stop_tracking(self, request) -> Dict:


Test patch:
diff --git a/tests/wallet/db_wallet/test_dl_wallet.py b/tests/wallet/db_wallet/test_dl_wallet.py
--- a/tests/wallet/db_wallet/test_dl_wallet.py
+++ b/tests/wallet/db_wallet/test_dl_wallet.py
@@ -6,6 +6,7 @@
 
 import pytest
 
+from chia.data_layer.data_layer_errors import LauncherCoinNotFoundError
 from chia.data_layer.data_layer_wallet import DataLayerWallet, Mirror
 from chia.simulator.setup_nodes import SimulatorsAndWallets
 from chia.simulator.simulator_protocol import FarmNewBlockProtocol
@@ -172,6 +173,12 @@ async def test_tracking_non_owned(
         async with wallet_node_1.wallet_state_manager.lock:
             dl_wallet_1 = await DataLayerWallet.create_new_dl_wallet(wallet_node_1.wallet_state_manager)
 
+        peer = wallet_node_1.get_full_node_peer()
+
+        # Test tracking a launcher id that does not exist
+        with pytest.raises(LauncherCoinNotFoundError):
+            await dl_wallet_0.track_new_launcher_id(bytes32([1] * 32), peer)
+
         nodes = [Program.to("thing").get_tree_hash(), Program.to([8]).get_tree_hash()]
         current_tree = MerkleTree(nodes)
         current_root = current_tree.calculate_root()
@@ -187,7 +194,6 @@ async def test_tracking_non_owned(
         await time_out_assert(15, is_singleton_confirmed, True, dl_wallet_0, launcher_id)
         await asyncio.sleep(0.5)
 
-        peer = wallet_node_1.get_full_node_peer()
         await dl_wallet_1.track_new_launcher_id(launcher_id, peer)
         await time_out_assert(15, is_singleton_confirmed, True, dl_wallet_1, launcher_id)
         await asyncio.sleep(0.5)
diff --git a/tests/wallet/rpc/test_dl_wallet_rpc.py b/tests/wallet/rpc/test_dl_wallet_rpc.py
--- a/tests/wallet/rpc/test_dl_wallet_rpc.py
+++ b/tests/wallet/rpc/test_dl_wallet_rpc.py
@@ -111,6 +111,10 @@ async def is_singleton_confirmed(rpc_client: WalletRpcClient, lid: bytes32) -> b
 
             assert await client.dl_history(launcher_id) == [new_singleton_record, singleton_record]
 
+            # Test tracking a launcher id that does not exist
+            with pytest.raises(ValueError):
+                await client_2.dl_track_new(bytes32([1] * 32))
+
             await client_2.dl_track_new(launcher_id)
 
             async def is_singleton_generation(rpc_client: WalletRpcClient, lid: bytes32, generation: int) -> bool: