diff --git a/electrum/gui/qt/network_dialog.py b/electrum/gui/qt/network_dialog.py index cb2d4297c..1d523f0ca 100644 --- a/electrum/gui/qt/network_dialog.py +++ b/electrum/gui/qt/network_dialog.py @@ -40,6 +40,7 @@ from electrum import constants, blockchain, util from electrum.interface import ServerAddr, PREFERRED_NETWORK_PROTOCOL from electrum.network import Network from electrum.logging import get_logger +from electrum.util import detect_tor_socks_proxy from .util import (Buttons, CloseButton, HelpButton, read_QIcon, char_width_in_lineedit, PasswordLineEdit) @@ -66,6 +67,11 @@ class NetworkDialog(QDialog, QtEventListener): self.register_callbacks() self._cleaned_up = False + def show(self): + super().show() + if td := self.nlayout.td: + td.trigger_rescan() + @qt_event_listener def on_event_network_updated(self): self.nlayout.update() @@ -203,10 +209,11 @@ class NetworkChoiceLayout(object): self.tor_proxy = None self.tabs = tabs = QTabWidget() - proxy_tab = QWidget() + self._proxy_tab = proxy_tab = QWidget() blockchain_tab = QWidget() tabs.addTab(blockchain_tab, _('Overview')) tabs.addTab(proxy_tab, _('Proxy')) + tabs.currentChanged.connect(self._on_tab_changed) fixed_width_hostname = 24 * char_width_in_lineedit() fixed_width_port = 6 * char_width_in_lineedit() @@ -417,6 +424,10 @@ class NetworkChoiceLayout(object): net_params = net_params._replace(proxy=proxy) self.network.run_from_another_thread(self.network.set_parameters(net_params)) + def _on_tab_changed(self): + if self.tabs.currentWidget() is self._proxy_tab: + self.td.trigger_rescan() + def suggest_proxy(self, found_proxy): if found_proxy is None: self.tor_cb.hide() @@ -457,38 +468,25 @@ class TorDetector(QThread): def __init__(self): QThread.__init__(self) - self._stop_event = threading.Event() + self._work_to_do_evt = threading.Event() + self._stopping = False def run(self): - # Probable ports for Tor to listen at - ports = [9050, 9150] while True: - for p in ports: - net_addr = ("127.0.0.1", p) - if TorDetector.is_tor_port(net_addr): - self.found_proxy.emit(net_addr) - break - else: - self.found_proxy.emit(None) - stopping = self._stop_event.wait(10) - if stopping: + # do rescan + net_addr = detect_tor_socks_proxy() + self.found_proxy.emit(net_addr) + # wait until triggered + self._work_to_do_evt.wait() + self._work_to_do_evt.clear() + if self._stopping: return + def trigger_rescan(self) -> None: + self._work_to_do_evt.set() + def stop(self): - self._stop_event.set() + self._stopping = True + self._work_to_do_evt.set() self.exit() self.wait() - - @staticmethod - def is_tor_port(net_addr: Tuple[str, int]) -> bool: - try: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.settimeout(0.1) - s.connect(net_addr) - # Tor responds uniquely to HTTP-like requests - s.send(b"GET\n") - if b"Tor is not an HTTP Proxy" in s.recv(1024): - return True - except socket.error: - pass - return False diff --git a/electrum/util.py b/electrum/util.py index 55ffb0b58..a4d05d8d9 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -47,6 +47,7 @@ import random import secrets import functools from abc import abstractmethod, ABC +import socket import attr import aiohttp @@ -1472,6 +1473,32 @@ class NetworkJobOnDefaultServer(Logger, ABC): return s +def detect_tor_socks_proxy() -> Optional[Tuple[str, int]]: + # Probable ports for Tor to listen at + candidates = [ + ("127.0.0.1", 9050), + ("127.0.0.1", 9150), + ] + for net_addr in candidates: + if is_tor_socks_port(*net_addr): + return net_addr + return None + + +def is_tor_socks_port(host: str, port: int) -> bool: + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.settimeout(0.1) + s.connect((host, port)) + # Tor responds uniquely to HTTP-like requests + s.send(b"GET\n") + if b"Tor is not an HTTP Proxy" in s.recv(1024): + return True + except socket.error: + pass + return False + + _asyncio_event_loop = None # type: Optional[asyncio.AbstractEventLoop] def get_asyncio_loop() -> asyncio.AbstractEventLoop: """Returns the global asyncio event loop we use."""