tnetwork: introduce NUM_STICKY_SERVERS - electrum - Electrum Bitcoin wallet
 (HTM) git clone https://git.parazyd.org/electrum
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
       ---
 (DIR) commit ac749f3a19ec10ee6f826c88d670e6cebab9aa17
 (DIR) parent 34e3e48ba5942614a1d1c44f57f7e9a4da3d701f
 (HTM) Author: SomberNight <somber.night@protonmail.com>
       Date:   Tue, 14 Apr 2020 19:58:22 +0200
       
       network: introduce NUM_STICKY_SERVERS
       
       Diffstat:
         M electrum/network.py                 |      28 ++++++++++++++++++----------
       
       1 file changed, 18 insertions(+), 10 deletions(-)
       ---
 (DIR) diff --git a/electrum/network.py b/electrum/network.py
       t@@ -72,6 +72,7 @@ _logger = get_logger(__name__)
        
        
        NUM_TARGET_CONNECTED_SERVERS = 10
       +NUM_STICKY_SERVERS = 4
        NUM_RECENT_SERVERS = 20
        MAX_RETRY_DELAY_FOR_SERVERS = 600  # sec
        INIT_RETRY_DELAY_FOR_SERVERS = 15  # sec
       t@@ -551,20 +552,27 @@ class Network(Logger):
            def _get_next_server_to_try(self) -> Optional[ServerAddr]:
                now = time.time()
                with self.interfaces_lock:
       -            exclude_set = set(self.interfaces) | self.connecting
       -        # first try from recent servers
       +            connected_servers = set(self.interfaces) | self.connecting
       +        # First try from recent servers. (which are persisted)
       +        # As these are servers we successfully connected to recently, they are
       +        # most likely to work. This also makes servers "sticky".
       +        # Note: with sticky servers, it is more difficult for an attacker to eclipse the client,
       +        #       however if they succeed, the eclipsing would persist. To try to balance this,
       +        #       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]
       -        for server in recent_servers:
       -            if server in exclude_set:
       -                continue
       -            if not self._can_retry_server(server, now=now):
       -                continue
       -            return server
       -        # try all servers we know about
       +        if len(connected_servers & set(recent_servers)) < NUM_STICKY_SERVERS:
       +            for server in recent_servers:
       +                if server in connected_servers:
       +                    continue
       +                if not self._can_retry_server(server, now=now):
       +                    continue
       +                return server
       +        # try all servers we know about, pick one at random
                hostmap = self.get_servers()
       -        servers = set(filter_protocol(hostmap, self.protocol)) - exclude_set
       +        servers = list(set(filter_protocol(hostmap, self.protocol)) - connected_servers)
       +        random.shuffle(servers)
                for server in servers:
                    if not self._can_retry_server(server, now=now):
                        continue