From 3d424077fd310b0644e7a466a813f4f411bd66ac Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 10 Sep 2018 00:59:53 +0200 Subject: [PATCH] introduce NetworkParameters namedtuple --- electrum/gui/kivy/main_window.py | 15 +++---- electrum/gui/kivy/uix/dialogs/settings.py | 11 +++-- electrum/gui/kivy/uix/ui_screens/proxy.kv | 5 ++- electrum/gui/kivy/uix/ui_screens/server.kv | 8 ++-- electrum/gui/qt/main_window.py | 2 +- electrum/gui/qt/network_dialog.py | 26 +++++++----- electrum/gui/text.py | 14 ++++--- electrum/interface.py | 7 ++-- electrum/network.py | 48 +++++++++++++--------- 9 files changed, 80 insertions(+), 56 deletions(-) diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py index 0fd45be86..4ba41465d 100644 --- a/electrum/gui/kivy/main_window.py +++ b/electrum/gui/kivy/main_window.py @@ -94,8 +94,9 @@ class ElectrumWindow(App): auto_connect = BooleanProperty(False) def on_auto_connect(self, instance, x): - host, port, protocol, proxy, auto_connect = self.network.get_parameters() - self.network.set_parameters(host, port, protocol, proxy, self.auto_connect) + net_params = self.network.get_parameters() + net_params = net_params._replace(auto_connect=self.auto_connect) + self.network.set_parameters(net_params) def toggle_auto_connect(self, x): self.auto_connect = not self.auto_connect @@ -267,11 +268,11 @@ class ElectrumWindow(App): if self.network: self.num_blocks = self.network.get_local_height() self.num_nodes = len(self.network.get_interfaces()) - host, port, protocol, proxy_config, auto_connect = self.network.get_parameters() - self.server_host = host - self.server_port = port - self.auto_connect = auto_connect - self.proxy_config = proxy_config if proxy_config else {} + net_params = self.network.get_parameters() + self.server_host = net_params.host + self.server_port = net_params.port + self.auto_connect = net_params.auto_connect + self.proxy_config = net_params.proxy_config if net_params.proxy_config else {} self.plugins = kwargs.get('plugins', []) self.gui_object = kwargs.get('gui_object', None) diff --git a/electrum/gui/kivy/uix/dialogs/settings.py b/electrum/gui/kivy/uix/dialogs/settings.py index 4e63c4a50..5247c0ffb 100644 --- a/electrum/gui/kivy/uix/dialogs/settings.py +++ b/electrum/gui/kivy/uix/dialogs/settings.py @@ -8,6 +8,7 @@ from electrum.i18n import languages from electrum.gui.kivy.i18n import _ from electrum.plugin import run_hook from electrum import coinchooser +from electrum.network import NetworkProxy from .choice_dialog import ChoiceDialog @@ -154,13 +155,16 @@ class SettingsDialog(Factory.Popup): self._coinselect_dialog.open() def proxy_status(self): - server, port, protocol, proxy, auto_connect = self.app.network.get_parameters() + net_params = self.app.network.get_parameters() + proxy = net_params.proxy return proxy.get('host') +':' + proxy.get('port') if proxy else _('None') def proxy_dialog(self, item, dt): if self._proxy_dialog is None: - server, port, protocol, proxy, auto_connect = self.app.network.get_parameters() + net_params = self.app.network.get_parameters() + proxy = net_params.proxy def callback(popup): + nonlocal net_params if popup.ids.mode.text != 'None': proxy = { 'mode':popup.ids.mode.text, @@ -171,7 +175,8 @@ class SettingsDialog(Factory.Popup): } else: proxy = None - self.app.network.set_parameters(server, port, protocol, proxy, auto_connect) + net_params = net_params._replace(proxy=proxy) + self.app.network.set_parameters(net_params) item.status = self.proxy_status() popup = Builder.load_file('electrum/gui/kivy/uix/ui_screens/proxy.kv') popup.ids.mode.text = proxy.get('mode') if proxy else 'None' diff --git a/electrum/gui/kivy/uix/ui_screens/proxy.kv b/electrum/gui/kivy/uix/ui_screens/proxy.kv index b7231855e..087254add 100644 --- a/electrum/gui/kivy/uix/ui_screens/proxy.kv +++ b/electrum/gui/kivy/uix/ui_screens/proxy.kv @@ -63,7 +63,7 @@ Popup: height: '48dp' text: _('OK') on_release: - host, port, protocol, proxy, auto_connect = app.network.get_parameters() + net_params = app.network.get_parameters() proxy = {} proxy['mode']=str(root.ids.mode.text).lower() proxy['host']=str(root.ids.host.text) @@ -71,6 +71,7 @@ Popup: proxy['user']=str(root.ids.user.text) proxy['password']=str(root.ids.password.text) if proxy['mode']=='none': proxy = None - app.network.set_parameters(host, port, protocol, proxy, auto_connect) + net_params = net_params._replace(proxy=proxy) + app.network.set_parameters(net_params) app.proxy_config = proxy if proxy else {} nd.dismiss() diff --git a/electrum/gui/kivy/uix/ui_screens/server.kv b/electrum/gui/kivy/uix/ui_screens/server.kv index 293d46452..686ee3be4 100644 --- a/electrum/gui/kivy/uix/ui_screens/server.kv +++ b/electrum/gui/kivy/uix/ui_screens/server.kv @@ -56,8 +56,8 @@ Popup: height: '48dp' text: _('OK') on_release: - host, port, protocol, proxy, auto_connect = app.network.get_parameters() - host = str(root.ids.host.text) - port = str(root.ids.port.text) - app.network.set_parameters(host, port, protocol, proxy, auto_connect) + net_params = app.network.get_parameters() + net_params = net_params._replace(host=str(root.ids.host.text), + port=str(root.ids.port.text)) + app.network.set_parameters(net_params) nd.dismiss() diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index b62dd3e9b..539b72293 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -562,7 +562,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): def donate_to_server(self): d = self.network.get_donation_address() if d: - host = self.network.get_parameters()[0] + host = self.network.get_parameters().host self.pay_to_URI('bitcoin:%s?message=donation for %s'%(d, host)) else: self.show_error(_('No donation address for this server')) diff --git a/electrum/gui/qt/network_dialog.py b/electrum/gui/qt/network_dialog.py index 7949eab5a..b9b185e8f 100644 --- a/electrum/gui/qt/network_dialog.py +++ b/electrum/gui/qt/network_dialog.py @@ -335,7 +335,9 @@ class NetworkChoiceLayout(object): w.setEnabled(False) def update(self): - host, port, protocol, proxy_config, auto_connect = self.network.get_parameters() + net_params = self.network.get_parameters() + host, port, protocol = net_params.host, net_params.port, net_params.protocol + proxy_config, auto_connect = net_params.proxy, net_params.auto_connect self.server_host.setText(host) self.server_port.setText(str(port)) self.autoconnect_cb.setChecked(auto_connect) @@ -368,7 +370,7 @@ class NetworkChoiceLayout(object): self.nodes_list_widget.update(self.network) def fill_in_proxy_settings(self): - host, port, protocol, proxy_config, auto_connect = self.network.get_parameters() + proxy_config = self.network.get_parameters().proxy if not proxy_config: proxy_config = {"mode": "none", "host": "localhost", "port": "9050"} @@ -409,9 +411,10 @@ class NetworkChoiceLayout(object): def follow_server(self, server): self.network.switch_to_interface(server) - host, port, protocol, proxy, auto_connect = self.network.get_parameters() + net_params = self.network.get_parameters() host, port, protocol = deserialize_server(server) - self.network.set_parameters(host, port, protocol, proxy, auto_connect) + net_params = net_params._replace(host=host, port=port, protocol=protocol) + self.network.set_parameters(net_params) self.update() def server_changed(self, x): @@ -440,14 +443,14 @@ class NetworkChoiceLayout(object): pass def set_server(self): - host, port, protocol, proxy, auto_connect = self.network.get_parameters() - host = str(self.server_host.text()) - port = str(self.server_port.text()) - auto_connect = self.autoconnect_cb.isChecked() - self.network.set_parameters(host, port, protocol, proxy, auto_connect) + net_params = self.network.get_parameters() + net_params = net_params._replace(host=str(self.server_host.text()), + port=str(self.server_port.text()), + auto_connect=self.autoconnect_cb.isChecked()) + self.network.set_parameters(net_params) def set_proxy(self): - host, port, protocol, proxy, auto_connect = self.network.get_parameters() + net_params = self.network.get_parameters() if self.proxy_cb.isChecked(): proxy = { 'mode':str(self.proxy_mode.currentText()).lower(), 'host':str(self.proxy_host.text()), @@ -457,7 +460,8 @@ class NetworkChoiceLayout(object): else: proxy = None self.tor_cb.setChecked(False) - self.network.set_parameters(host, port, protocol, proxy, auto_connect) + net_params = net_params._replace(proxy=proxy) + self.network.set_parameters(net_params) def suggest_proxy(self, found_proxy): self.tor_proxy = found_proxy diff --git a/electrum/gui/text.py b/electrum/gui/text.py index 61e3ac1db..c27eb2df0 100644 --- a/electrum/gui/text.py +++ b/electrum/gui/text.py @@ -7,7 +7,10 @@ import electrum from electrum.util import format_satoshis, set_verbosity from electrum.bitcoin import is_address, COIN, TYPE_ADDRESS from electrum.transaction import TxOutput -from .. import Wallet, WalletStorage +from electrum.wallet import Wallet +from electrum.storage import WalletStorage +from electrum.network import NetworkParameters +from electrum.interface import deserialize_server _ = lambda x:x @@ -376,8 +379,9 @@ class ElectrumGui: def network_dialog(self): if not self.network: return - params = self.network.get_parameters() - host, port, protocol, proxy_config, auto_connect = params + net_params = self.network.get_parameters() + host, port, protocol = net_params.host, net_params.port, net_params.protocol + proxy_config, auto_connect = net_params.proxy, net_params.auto_connect srv = 'auto-connect' if auto_connect else self.network.default_server out = self.run_dialog('Network', [ {'label':'server', 'type':'str', 'value':srv}, @@ -389,13 +393,13 @@ class ElectrumGui: auto_connect = server == 'auto-connect' if not auto_connect: try: - host, port, protocol = server.split(':') + host, port, protocol = deserialize_server(server) except Exception: self.show_message("Error:" + server + "\nIn doubt, type \"auto-connect\"") return False if out.get('server') or out.get('proxy'): proxy = electrum.network.deserialize_proxy(out.get('proxy')) if out.get('proxy') else proxy_config - self.network.set_parameters(host, port, protocol, proxy, auto_connect) + self.network.set_parameters(NetworkParameters(host, port, protocol, proxy, auto_connect)) def settings_dialog(self): fee = str(Decimal(self.config.fee_per_kb()) / COIN) diff --git a/electrum/interface.py b/electrum/interface.py index 2b63c1978..718fe6a14 100644 --- a/electrum/interface.py +++ b/electrum/interface.py @@ -84,13 +84,13 @@ class CustomTaskGroup(TaskGroup): return super().spawn(*args, **kwargs) -def deserialize_server(server_str: str) -> Tuple[str, int, str]: +def deserialize_server(server_str: str) -> Tuple[str, str, str]: # host might be IPv6 address, hence do rsplit: host, port, protocol = str(server_str).rsplit(':', 2) if protocol not in ('s', 't'): raise ValueError('invalid network protocol: {}'.format(protocol)) - port = int(port) # Throw if cannot be converted to int - if not (0 < port < 2**16): + int(port) # Throw if cannot be converted to int + if not (0 < int(port) < 2**16): raise ValueError('port {} is out of valid range'.format(port)) return host, port, protocol @@ -106,6 +106,7 @@ class Interface(PrintError): self.ready = asyncio.Future() self.server = server self.host, self.port, self.protocol = deserialize_server(self.server) + self.port = int(self.port) self.config_path = config_path self.cert_path = os.path.join(self.config_path, 'certs', self.host) self.tip_header = None diff --git a/electrum/network.py b/electrum/network.py index ebe40f058..440d41cad 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -32,6 +32,7 @@ import json import sys import ipaddress import asyncio +from typing import NamedTuple, Optional import dns import dns.resolver @@ -44,6 +45,7 @@ from . import constants from . import blockchain from .interface import Interface, serialize_server, deserialize_server from .version import PROTOCOL_VERSION +from .simple_config import SimpleConfig NODES_RETRY_INTERVAL = 60 SERVER_RETRY_INTERVAL = 10 @@ -106,7 +108,12 @@ def pick_random_server(hostmap = None, protocol = 's', exclude_set = set()): return random.choice(eligible) if eligible else None -from .simple_config import SimpleConfig +NetworkParameters = NamedTuple("NetworkParameters", [("host", str), + ("port", str), + ("protocol", str), + ("proxy", Optional[dict]), + ("auto_connect", bool)]) + proxy_modes = ['socks4', 'socks5'] @@ -118,7 +125,7 @@ def serialize_proxy(p): p.get('user', ''), p.get('password', '')]) -def deserialize_proxy(s): +def deserialize_proxy(s: str) -> Optional[dict]: if not isinstance(s, str): return None if s.lower() == 'none': @@ -369,9 +376,9 @@ class Network(PrintError): else: self.trigger_callback(key, self.get_status_value(key)) - def get_parameters(self): + def get_parameters(self) -> NetworkParameters: host, port, protocol = deserialize_server(self.default_server) - return host, port, protocol, self.proxy, self.auto_connect + return NetworkParameters(host, port, protocol, self.proxy, self.auto_connect) def get_donation_address(self): if self.is_connected(): @@ -416,14 +423,13 @@ class Network(PrintError): self.start_interface(server) return server - def set_proxy(self, proxy): + def set_proxy(self, proxy: Optional[dict]): self.proxy = proxy # Store these somewhere so we can un-monkey-patch if not hasattr(socket, "_socketobject"): socket._getaddrinfo = socket.getaddrinfo if proxy: self.print_error('setting proxy', proxy) - proxy_mode = proxy_modes.index(proxy["mode"]) + 1 # prevent dns leaks, see http://stackoverflow.com/questions/13184205/dns-over-proxy socket.getaddrinfo = lambda *args: [(socket.AF_INET, socket.SOCK_STREAM, 6, '', (args[0], args[1]))] else: @@ -465,7 +471,7 @@ class Network(PrintError): return socket._getaddrinfo(addr, *args, **kwargs) @with_interface_lock - def start_network(self, protocol, proxy): + def start_network(self, protocol: str, proxy: Optional[dict]): assert not self.interface and not self.interfaces assert not self.connecting and self.socket_queue.empty() self.print_error('starting network') @@ -487,9 +493,11 @@ class Network(PrintError): # Get a new queue - no old pending connections thanks! self.socket_queue = queue.Queue() - def set_parameters(self, host, port, protocol, proxy, auto_connect): + def set_parameters(self, net_params: NetworkParameters): + proxy = net_params.proxy proxy_str = serialize_proxy(proxy) - server = serialize_server(host, port, protocol) + host, port, protocol = net_params.host, net_params.port, net_params.protocol + server_str = serialize_server(host, port, protocol) # sanitize parameters try: deserialize_server(serialize_server(host, port, protocol)) @@ -498,21 +506,21 @@ class Network(PrintError): int(proxy['port']) except: return - self.config.set_key('auto_connect', auto_connect, False) + self.config.set_key('auto_connect', net_params.auto_connect, False) self.config.set_key("proxy", proxy_str, False) - self.config.set_key("server", server, True) + self.config.set_key("server", server_str, True) # abort if changes were not allowed by config - if self.config.get('server') != server or self.config.get('proxy') != proxy_str: + if self.config.get('server') != server_str or self.config.get('proxy') != proxy_str: return - self.auto_connect = auto_connect + self.auto_connect = net_params.auto_connect if self.proxy != proxy or self.protocol != protocol: # Restart the network defaulting to the given server with self.interface_lock: self.stop_network() - self.default_server = server + self.default_server = server_str self.start_network(protocol, proxy) - elif self.default_server != server: - self.switch_to_interface(server) + elif self.default_server != server_str: + self.switch_to_interface(server_str) else: self.switch_lagging_interface() self.notify('updated') @@ -781,10 +789,10 @@ class Network(PrintError): with self.interface_lock: if self.interface: - server = self.interface.server - host, port, protocol, proxy, auto_connect = self.get_parameters() - host, port, protocol = deserialize_server(server) - self.set_parameters(host, port, protocol, proxy, auto_connect) + net_params = self.get_parameters() + host, port, protocol = deserialize_server(self.interface.server) + net_params = net_params._replace(host=host, port=port, protocol=protocol) + self.set_parameters(net_params) def get_local_height(self): return self.blockchain().height()