diff --git a/electrum/lntransport.py b/electrum/lntransport.py index 716f35e65..8f242eaa0 100644 --- a/electrum/lntransport.py +++ b/electrum/lntransport.py @@ -186,6 +186,9 @@ class LNPeerAddr: def net_addr_str(self) -> str: return str(self._net_addr) + def is_onion(self) -> bool: + return self.host.endswith('.onion') + def __eq__(self, other): if not isinstance(other, LNPeerAddr): return False diff --git a/electrum/lnworker.py b/electrum/lnworker.py index f6b503538..c8846595d 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -483,6 +483,8 @@ class LNPeerManager(Logger, EventListener, NetworkRetryManager[LNPeerAddr]): continue if not self.is_good_peer(peer): continue + if peer.is_onion() and not self.network.proxy or not self.network.proxy.enabled: + continue return [peer] # try random peer from graph unconnected_nodes = self.channel_db.get_200_randomly_sorted_nodes_not_in(self.peers.keys()) @@ -491,7 +493,10 @@ class LNPeerManager(Logger, EventListener, NetworkRetryManager[LNPeerAddr]): addrs = self.channel_db.get_node_addresses(node_id) if not addrs: continue - host, port, timestamp = self.choose_preferred_address(list(addrs)) + address = self.choose_preferred_address(list(addrs)) + if not address: + continue + host, port, timestamp = address try: peer = LNPeerAddr(host, port, node_id) except ValueError: @@ -550,15 +555,17 @@ class LNPeerManager(Logger, EventListener, NetworkRetryManager[LNPeerAddr]): self.logger.info(f'got {len(peers)} ln peers from dns seed') return peers - @staticmethod - def choose_preferred_address(addr_list: Sequence[Tuple[str, int, int]]) -> Tuple[str, int, int]: + def choose_preferred_address(self, addr_list: Sequence[Tuple[str, int, int]]) -> Optional[Tuple[str, int, int]]: assert len(addr_list) >= 1 # choose the most recent one that is an IP for host, port, timestamp in sorted(addr_list, key=lambda a: -a[2]): if is_ip_address(host): return host, port, timestamp + if not self.network.proxy or not self.network.proxy.enabled: + addr_list = [(h, p, ts) for h, p, ts in addr_list if not h.endswith('.onion')] + if not addr_list: + return None # otherwise choose one at random - # TODO maybe filter out onion if not on tor? choice = random.choice(addr_list) return choice @@ -583,9 +590,9 @@ class LNPeerManager(Logger, EventListener, NetworkRetryManager[LNPeerAddr]): host, port = addr.host, addr.port else: addrs = self.channel_db.get_node_addresses(node_id) - if not addrs: + if not addrs or not (address := self.choose_preferred_address(list(addrs))): raise ConnStringFormatError(_('Don\'t know any addresses for node:') + ' ' + node_id.hex()) - host, port, timestamp = self.choose_preferred_address(list(addrs)) + host, port, timestamp = address port = int(port) if not self.network.proxy or not self.network.proxy.enabled: diff --git a/tests/test_lnpeermgr.py b/tests/test_lnpeermgr.py index 169f7ddc1..01f9444f0 100644 --- a/tests/test_lnpeermgr.py +++ b/tests/test_lnpeermgr.py @@ -58,3 +58,33 @@ class TestLNPeerManager(ElectrumTestCase): with self.assertRaises(ConnStringFormatError) as cm: await peermgr.add_peer(bad_host_conn_str) self.assertIn("Hostname does not resolve", str(cm.exception)) + + def test_choose_preferred_address(self): + peermgr = self.lnpeermgr + + # prefer most recent IP address + addr_list = [ + ("192.168.1.1", 9735, 100), + ("host.onion", 9735, 200), + ("10.0.0.1", 9735, 150), + ("host.com", 9735, 250) + ] + result = peermgr.choose_preferred_address(addr_list) + self.assertEqual(result, ("10.0.0.1", 9735, 150)) # Most recent IP + + # no IP, proxy disabled, filter .onion and choose random + self.assertFalse(peermgr.network.proxy.enabled) + addr_list = [("host.com", 9735, 100), ("host.onion", 9735, 200)] + result = peermgr.choose_preferred_address(addr_list) + self.assertEqual(result, ("host.com", 9735, 100)) + + # empty list after filtering + addr_list = [("host.onion", 9735, 100)] + result = peermgr.choose_preferred_address(addr_list) + self.assertIsNone(result) + + # return onion if proxy enabled + peermgr.network.proxy.enabled = True + addr_list = [("host.onion", 9735, 100)] + result = peermgr.choose_preferred_address(addr_list) + self.assertEqual(result, ("host.onion", 9735, 100)) diff --git a/tests/test_lntransport.py b/tests/test_lntransport.py index bcad665db..360b35b81 100644 --- a/tests/test_lntransport.py +++ b/tests/test_lntransport.py @@ -141,3 +141,35 @@ class TestLNTransport(ElectrumTestCase): self.assertEqual(extract_nodeid(f"{pubkey1.hex()}@[2001:41d0:e:734::1]:8888"), (pubkey1, "[2001:41d0:e:734::1]:8888")) # just pubkey self.assertEqual(extract_nodeid(f"{pubkey1.hex()}"), (pubkey1, None)) + + +class TestLNPeerAddr(ElectrumTestCase): + + def test_validate_net_address(self): + # Test invalid host + with self.assertRaises(ValueError): + LNPeerAddr("", 9735, b'\x00'*33) + with self.assertRaises(ValueError): + LNPeerAddr("999.999.999.999", 9735, b'\x00'*33) + # Test invalid port + with self.assertRaises(ValueError): + LNPeerAddr("127.0.0.1", -1, b'\x00'*33) + with self.assertRaises(ValueError): + LNPeerAddr("127.0.0.1", 70000, b'\x00'*33) + + def test_is_onion(self): + # Test onion addresses + addr1 = LNPeerAddr("example.onion", 9735, b'\x00'*33) + self.assertTrue(addr1.is_onion()) + addr2 = LNPeerAddr("subdomain.example.onion", 9735, b'\x00'*33) + self.assertTrue(addr2.is_onion()) + + # Test non-onion + addr3 = LNPeerAddr("example.com", 9735, b'\x00'*33) + self.assertFalse(addr3.is_onion()) + addr4 = LNPeerAddr("127.0.0.1", 9735, b'\x00'*33) + self.assertFalse(addr4.is_onion()) + addr5 = LNPeerAddr("::1", 9735, b'\x00'*33) + self.assertFalse(addr5.is_onion()) + addr6 = LNPeerAddr("onion", 9735, b'\x00'*33) + self.assertFalse(addr6.is_onion())