diff --git a/electrum/gui/qt/network_dialog.py b/electrum/gui/qt/network_dialog.py index bbf2df91f..e8cf3d6e3 100644 --- a/electrum/gui/qt/network_dialog.py +++ b/electrum/gui/qt/network_dialog.py @@ -37,7 +37,7 @@ from PyQt5.QtGui import QFontMetrics from electrum.i18n import _ from electrum import constants, blockchain, util from electrum.interface import ServerAddr -from electrum.network import Network +from electrum.network import Network, PREFERRED_NETWORK_PROTOCOL from electrum.logging import get_logger from .util import (Buttons, CloseButton, HelpButton, read_QIcon, char_width_in_lineedit, @@ -72,6 +72,8 @@ class NetworkDialog(QDialog): class NodesListWidget(QTreeWidget): + """List of connected servers.""" + SERVER_ADDR_ROLE = Qt.UserRole + 100 CHAIN_ID_ROLE = Qt.UserRole + 101 IS_SERVER_ROLE = Qt.UserRole + 102 @@ -129,6 +131,7 @@ class NodesListWidget(QTreeWidget): item = QTreeWidgetItem([i.host + star, '%d'%i.tip]) item.setData(0, self.IS_SERVER_ROLE, 1) item.setData(0, self.SERVER_ADDR_ROLE, i.server) + item.setToolTip(0, str(i.server)) x.addChild(item) if n_chains > 1: self.addTopLevelItem(x) @@ -143,6 +146,8 @@ class NodesListWidget(QTreeWidget): class ServerListWidget(QTreeWidget): + """List of all known servers.""" + class Columns(IntEnum): HOST = 0 PORT = 1 @@ -182,8 +187,9 @@ class ServerListWidget(QTreeWidget): pt.setX(50) self.customContextMenuRequested.emit(pt) - def update(self, servers, protocol, use_tor): + def update(self, servers, use_tor): self.clear() + protocol = PREFERRED_NETWORK_PROTOCOL for _host, d in sorted(servers.items()): if _host.endswith('.onion') and not use_tor: continue @@ -207,7 +213,6 @@ class NetworkChoiceLayout(object): def __init__(self, network: Network, config, wizard=False): self.network = network self.config = config - self.protocol = None self.tor_proxy = None self.tabs = tabs = QTabWidget() @@ -370,9 +375,8 @@ class NetworkChoiceLayout(object): host = interface.host if interface else _('None') self.server_label.setText(host) - self.set_protocol(protocol) self.servers = self.network.get_servers() - self.servers_list.update(self.servers, self.protocol, self.tor_cb.isChecked()) + self.servers_list.update(self.servers, self.tor_cb.isChecked()) self.enable_set_server() height_str = "%d "%(self.network.get_local_height()) + _('blocks') @@ -413,22 +417,6 @@ class NetworkChoiceLayout(object): def layout(self): return self.layout_ - def set_protocol(self, protocol): - if protocol != self.protocol: - self.protocol = protocol - - def change_protocol(self, use_ssl): - p = 's' if use_ssl else 't' - host = self.server_host.text() - pp = self.servers.get(host, constants.net.DEFAULT_PORTS) - if p not in pp.keys(): - p = list(pp.keys())[0] - port = pp[p] - self.server_host.setText(host) - self.server_port.setText(port) - self.set_protocol(p) - self.set_server() - def follow_branch(self, chain_id): self.network.run_from_another_thread(self.network.follow_chain_given_id(chain_id)) self.update() @@ -437,10 +425,6 @@ class NetworkChoiceLayout(object): self.network.run_from_another_thread(self.network.follow_chain_given_server(server)) self.update() - def server_changed(self, x): - if x: - self.change_server(str(x.text(0)), self.protocol) - def change_server(self, host, protocol): pp = self.servers.get(host, constants.net.DEFAULT_PORTS) if protocol and protocol not in protocol_letters: diff --git a/electrum/network.py b/electrum/network.py index 09c2c6ad3..e7cdb6850 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -75,6 +75,10 @@ NUM_TARGET_CONNECTED_SERVERS = 10 NUM_STICKY_SERVERS = 4 NUM_RECENT_SERVERS = 20 +_KNOWN_NETWORK_PROTOCOLS = {'t', 's'} +PREFERRED_NETWORK_PROTOCOL = 's' +assert PREFERRED_NETWORK_PROTOCOL in _KNOWN_NETWORK_PROTOCOLS + def parse_servers(result: Sequence[Tuple[str, str, List[str]]]) -> Dict[str, dict]: """ parse servers list into dict format""" @@ -115,23 +119,27 @@ def filter_noonion(servers): return {k: v for k, v in servers.items() if not k.endswith('.onion')} -def filter_protocol(hostmap, protocol='s') -> Sequence[ServerAddr]: +def filter_protocol(hostmap, *, allowed_protocols: Iterable[str] = None) -> Sequence[ServerAddr]: """Filters the hostmap for those implementing protocol.""" + if allowed_protocols is None: + allowed_protocols = {PREFERRED_NETWORK_PROTOCOL} eligible = [] for host, portmap in hostmap.items(): - port = portmap.get(protocol) - if port: - eligible.append(ServerAddr(host, port, protocol=protocol)) + for protocol in allowed_protocols: + port = portmap.get(protocol) + if port: + eligible.append(ServerAddr(host, port, protocol=protocol)) return eligible -def pick_random_server(hostmap=None, *, protocol='s', +def pick_random_server(hostmap=None, *, allowed_protocols: Iterable[str], exclude_set: Set[ServerAddr] = None) -> Optional[ServerAddr]: if hostmap is None: hostmap = constants.net.DEFAULT_SERVERS if exclude_set is None: exclude_set = set() - eligible = list(set(filter_protocol(hostmap, protocol)) - exclude_set) + servers = set(filter_protocol(hostmap, allowed_protocols=allowed_protocols)) + eligible = list(servers - exclude_set) return random.choice(eligible) if eligible else None @@ -273,6 +281,9 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): self.logger.info(f"blockchains {list(map(lambda b: b.forkpoint, blockchain.blockchains.values()))}") self._blockchain_preferred_block = self.config.get('blockchain_preferred_block', None) # type: Optional[Dict] self._blockchain = blockchain.get_best_chain() + + self._allowed_protocols = {PREFERRED_NETWORK_PROTOCOL} + # Server for addresses and transactions self.default_server = self.config.get('server', None) # Sanitize default server @@ -283,7 +294,7 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): self.logger.warning('failed to parse server-string; falling back to localhost.') self.default_server = ServerAddr.from_str("localhost:50002:s") else: - self.default_server = pick_random_server() + self.default_server = pick_random_server(allowed_protocols=self._allowed_protocols) assert isinstance(self.default_server, ServerAddr), f"invalid type for default_server: {self.default_server!r}" self.taskgroup = None @@ -549,7 +560,7 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): # we only give priority to recent_servers up to NUM_STICKY_SERVERS. with self.recent_servers_lock: recent_servers = list(self._recent_servers) - recent_servers = [s for s in recent_servers if s.protocol == self.protocol] + recent_servers = [s for s in recent_servers if s.protocol in self._allowed_protocols] if len(connected_servers & set(recent_servers)) < NUM_STICKY_SERVERS: for server in recent_servers: if server in connected_servers: @@ -559,7 +570,7 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): return server # try all servers we know about, pick one at random hostmap = self.get_servers() - servers = list(set(filter_protocol(hostmap, self.protocol)) - connected_servers) + servers = list(set(filter_protocol(hostmap, allowed_protocols=self._allowed_protocols)) - connected_servers) random.shuffle(servers) for server in servers: if not self._can_retry_addr(server, now=now): @@ -574,7 +585,7 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): util.trigger_callback('proxy_set', self.proxy) @log_exceptions - async def set_parameters(self, net_params: NetworkParameters): + async def set_parameters(self, net_params: NetworkParameters): # TODO proxy = net_params.proxy proxy_str = serialize_proxy(proxy) host, port, protocol = net_params.host, net_params.port, net_params.protocol @@ -598,7 +609,7 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): async with self.restart_lock: self.auto_connect = net_params.auto_connect - if self.proxy != proxy or self.protocol != protocol or self.oneserver != net_params.oneserver: + if self.proxy != proxy or self.oneserver != net_params.oneserver: # Restart the network defaulting to the given server await self._stop() self.default_server = server @@ -1138,7 +1149,6 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): assert not self._connecting self.logger.info('starting network') self._clear_addr_retry_times() - self.protocol = self.default_server.protocol self._set_proxy(deserialize_proxy(self.config.get('proxy'))) self._set_oneserver(self.config.get('oneserver', False)) await self.taskgroup.spawn(self._run_new_interface(self.default_server)) @@ -1282,7 +1292,7 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): session = self.interface.session return parse_servers(await session.send_request('server.peers.subscribe')) - async def send_multiple_requests(self, servers: List[str], method: str, params: Sequence): + async def send_multiple_requests(self, servers: Sequence[ServerAddr], method: str, params: Sequence): responses = dict() async def get_response(server: ServerAddr): interface = Interface(network=self, server=server, proxy=self.proxy) @@ -1299,6 +1309,5 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): responses[interface.server] = res async with TaskGroup() as group: for server in servers: - server = ServerAddr.from_str(server) await group.spawn(get_response(server)) return responses diff --git a/electrum/scripts/peers.py b/electrum/scripts/peers.py index a26572b30..64c25e0cd 100755 --- a/electrum/scripts/peers.py +++ b/electrum/scripts/peers.py @@ -17,7 +17,7 @@ network.start() async def f(): try: peers = await network.get_peers() - peers = filter_protocol(peers, 's') + peers = filter_protocol(peers) results = await network.send_multiple_requests(peers, 'blockchain.headers.subscribe', []) for server, header in sorted(results.items(), key=lambda x: x[1].get('height')): height = header.get('height') diff --git a/electrum/scripts/txradar.py b/electrum/scripts/txradar.py index 2166a87ba..8e301fd5d 100755 --- a/electrum/scripts/txradar.py +++ b/electrum/scripts/txradar.py @@ -23,7 +23,7 @@ network.start() async def f(): try: peers = await network.get_peers() - peers = filter_protocol(peers, 's') + peers = filter_protocol(peers) results = await network.send_multiple_requests(peers, 'blockchain.transaction.get', [txid]) r1, r2 = [], [] for k, v in results.items():