Repository: chia-network/chia-blockchain
Issue: [Bug] IPv6 RPC connections broken

### What happened?

https://github.com/Chia-Network/chia-blockchain/pull/11578 broke IPv6 RPC connections by hard coding `0.0.0.0` for the host at https://github.com/Chia-Network/chia-blockchain/pull/11578/files#diff-926449f427065cf100c0af5785ac61529c768ab4cccbef25ccb86e82074b753fR270.  Initial exploration started in https://github.com/Chia-Network/chia-blockchain/pull/11623.  Actual fix being worked in https://github.com/Chia-Network/chia-blockchain/pull/11764.

### Version

main: https://github.com/Chia-Network/chia-blockchain/commit/817baa3096ee7cb4f820bf04599a2d231faeff94

### What platform are you using?

Linux

### What ui mode are you using?

CLI

### Relevant log output

```shell
2022-05-24T14:53:31.336 full_node chia.rpc.util           : WARNING  Error while handling message: Traceback (most recent call last):
  File "chia\\rpc\\util.py", line 16, in inner
  File "chia\\rpc\\rpc_server.py", line 172, in open_connection
ValueError: Start client failed, or server is not set
```
```


Changed files:
chia/rpc/rpc_server.py
chia/server/server.py
chia/util/network.py

diff --git a/chia/rpc/rpc_server.py b/chia/rpc/rpc_server.py
index 9a7ff003b..fbd5e5072 100644
--- a/chia/rpc/rpc_server.py
+++ b/chia/rpc/rpc_server.py
@@ -14,6 +14,7 @@ from chia.types.peer_info import PeerInfo
 from chia.util.byte_types import hexstr_to_bytes
 from chia.util.ints import uint16
 from chia.util.json_util import dict_to_json_str
+from chia.util.network import select_port
 from chia.util.ws_message import create_payload, create_payload_dict, format_response, pong
 
 log = logging.getLogger(__name__)
@@ -328,7 +329,13 @@ async def start_rpc_server(
 
     site = web.TCPSite(runner, self_hostname, int(rpc_port), ssl_context=rpc_server.ssl_context)
     await site.start()
-    rpc_port = runner.addresses[0][1]
+
+    #
+    # On a dual-stack system, we want to get the (first) IPv4 port unless
+    # prefer_ipv6 is set in which case we use the IPv6 port
+    #
+    if rpc_port == 0:
+        rpc_port = select_port(root_path, runner.addresses)
 
     async def cleanup():
         await rpc_server.stop()
diff --git a/chia/server/server.py b/chia/server/server.py
index 326085fc9..f8c88976a 100644
--- a/chia/server/server.py
+++ b/chia/server/server.py
@@ -30,7 +30,7 @@ from chia.types.blockchain_format.sized_bytes import bytes32
 from chia.types.peer_info import PeerInfo
 from chia.util.errors import Err, ProtocolError
 from chia.util.ints import uint16
-from chia.util.network import is_in_network, is_localhost
+from chia.util.network import is_in_network, is_localhost, select_port
 from chia.util.ssl_check import verify_ssl_certs_and_keys
 
 max_message_size = 50 * 1024 * 1024  # 50MB
@@ -267,13 +267,19 @@ class ChiaServer:
         # this port from the socket itself and update self._port.
         self.site = web.TCPSite(
             self.runner,
-            host="0.0.0.0",
+            host="",  # should listen to both IPv4 and IPv6 on a dual-stack system
             port=int(self._port),
             shutdown_timeout=3,
             ssl_context=ssl_context,
         )
         await self.site.start()
-        self._port = self.runner.addresses[0][1]
+        #
+        # On a dual-stack system, we want to get the (first) IPv4 port unless
+        # prefer_ipv6 is set in which case we use the IPv6 port
+        #
+        if self._port == 0:
+            self._port = select_port(self.root_path, self.runner.addresses)
+
         self.log.info(f"Started listening on port: {self._port}")
 
     async def incoming_connection(self, request):
diff --git a/chia/util/network.py b/chia/util/network.py
index 3bce97ac5..bbf48a442 100644
--- a/chia/util/network.py
+++ b/chia/util/network.py
@@ -1,9 +1,11 @@
 import socket
+from pathlib import Path
 from ipaddress import ip_address, IPv4Network, IPv6Network
 from typing import Iterable, List, Tuple, Union, Any, Optional, Dict
 from chia.server.outbound_message import NodeType
 from chia.types.blockchain_format.sized_bytes import bytes32
 from chia.types.peer_info import PeerInfo
+from chia.util.config import load_config
 from chia.util.ints import uint16
 
 
@@ -84,3 +86,21 @@ def is_trusted_inner(peer_host: str, peer_node_id: bytes32, trusted_peers: Dict,
         return False
 
     return True
+
+
+def select_port(root_path: Path, addresses: List[Any]) -> uint16:
+    global_config = load_config(root_path, "config.yaml")
+    prefer_ipv6 = global_config.get("prefer_ipv6", False)
+    selected_port: uint16
+    for address_string, port, *_ in addresses:
+        address = ip_address(address_string)
+        if address.version == 6 and prefer_ipv6:
+            selected_port = port
+            break
+        elif address.version == 4 and not prefer_ipv6:
+            selected_port = port
+            break
+    else:
+        selected_port = addresses[0][1]  # no matches, just use the first one in the list
+
+    return selected_port