tnetwork: allow mixed protocols among interfaces - electrum - Electrum Bitcoin wallet
 (HTM) git clone https://git.parazyd.org/electrum
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
       ---
 (DIR) commit adc3784bc24ab0e83e412846a4bfc776b33feef7
 (DIR) parent 872380a5259020b1140884b7214ec3d44f86222d
 (HTM) Author: SomberNight <somber.night@protonmail.com>
       Date:   Thu, 16 Apr 2020 19:56:30 +0200
       
       network: allow mixed protocols among interfaces
       
       Previously all the interfaces used either "t" or "s".
       Now the network only tries to use "s" for all interfaces, except for
       tthe main interface, which the user can manually specify to use "t".
       (so e.g. if you run with "--server localhost:50002:t", the main server will use "t",
       but all the rest will use "s")
       
       Diffstat:
         M electrum/gui/qt/network_dialog.py   |      34 ++++++++-----------------------
         M electrum/network.py                 |      37 +++++++++++++++++++------------
         M electrum/scripts/peers.py           |       2 +-
         M electrum/scripts/txradar.py         |       2 +-
       
       4 files changed, 34 insertions(+), 41 deletions(-)
       ---
 (DIR) diff --git a/electrum/gui/qt/network_dialog.py b/electrum/gui/qt/network_dialog.py
       t@@ -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,
       t@@ -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
       t@@ -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)
       t@@ -143,6 +146,8 @@ class NodesListWidget(QTreeWidget):
        
        
        class ServerListWidget(QTreeWidget):
       +    """List of all known servers."""
       +
            class Columns(IntEnum):
                HOST = 0
                PORT = 1
       t@@ -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
       t@@ -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()
       t@@ -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')
       t@@ -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()
       t@@ -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:
 (DIR) diff --git a/electrum/network.py b/electrum/network.py
       t@@ -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"""
       t@@ -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
        
        
       t@@ -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
       t@@ -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
       t@@ -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:
       t@@ -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):
       t@@ -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
       t@@ -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
       t@@ -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))
       t@@ -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)
       t@@ -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
 (DIR) diff --git a/electrum/scripts/peers.py b/electrum/scripts/peers.py
       t@@ -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')
 (DIR) diff --git a/electrum/scripts/txradar.py b/electrum/scripts/txradar.py
       t@@ -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():