async def get_n_fastest_spvs(self, timeout=3.0 ) -> Dict[Tuple[str, int], Optional[SPVPong]]: loop = asyncio.get_event_loop() pong_responses = asyncio.Queue() connection = SPVStatusClientProtocol(pong_responses) sent_ping_timestamps = {} _, ip_to_hostnames = await self.resolve_spv_dns() n = len(ip_to_hostnames) log.info("%i possible spv servers to try (%i urls in config)", n, len(self.config['explicit_servers'])) pongs = {} known_hubs = self.known_hubs try: await loop.create_datagram_endpoint(lambda: connection, ('0.0.0.0', 0)) # could raise OSError if it cant bind start = perf_counter() for server in ip_to_hostnames: connection.ping(server) sent_ping_timestamps[server] = perf_counter() while len(pongs) < n: (remote, ts), pong = await asyncio.wait_for( pong_responses.get(), timeout - (perf_counter() - start)) latency = ts - start log.info( "%s:%i has latency of %sms (available: %s, height: %i)", '/'.join(ip_to_hostnames[remote]), remote[1], round(latency * 1000, 2), pong.available, pong.height) known_hubs.hubs.setdefault( (ip_to_hostnames[remote][0], remote[1]), {}).update({"country": pong.country_name}) if pong.available: pongs[(ip_to_hostnames[remote][0], remote[1])] = pong return pongs except asyncio.TimeoutError: if pongs: log.info("%i/%i probed spv servers are accepting connections", len(pongs), len(ip_to_hostnames)) return pongs else: log.warning( "%i spv status probes failed, retrying later. servers tried: %s", len(sent_ping_timestamps), ', '.join('/'.join(hosts) + f' ({ip})' for ip, hosts in ip_to_hostnames.items())) random_server = random.choice(list(ip_to_hostnames.keys())) host, port = random_server log.warning("trying fallback to randomly selected spv: %s:%i", host, port) known_hubs.hubs.setdefault((host, port), {}) return {(host, port): None} finally: connection.close()
async def get_n_fastest_spvs(self, n=5, timeout=3.0 ) -> Dict[Tuple[str, int], SPVPong]: loop = asyncio.get_event_loop() pong_responses = asyncio.Queue() connection = SPVStatusClientProtocol(pong_responses) sent_ping_timestamps = {} _, ip_to_hostnames = await self.resolve_spv_dns() log.info("%i possible spv servers to try (%i urls in config)", len(ip_to_hostnames), len(self.config['default_servers'])) pongs = {} try: await loop.create_datagram_endpoint(lambda: connection, ('0.0.0.0', 0)) # could raise OSError if it cant bind start = perf_counter() for server in ip_to_hostnames: connection.ping(server) sent_ping_timestamps[server] = perf_counter() while len(pongs) < n: (remote, ts), pong = await asyncio.wait_for( pong_responses.get(), timeout - (perf_counter() - start)) latency = ts - start log.info( "%s:%i has latency of %sms (available: %s, height: %i)", '/'.join(ip_to_hostnames[remote]), remote[1], round(latency * 1000, 2), pong.available, pong.height) if pong.available: pongs[remote] = pong return pongs except asyncio.TimeoutError: if pongs: log.info("%i/%i probed spv servers are accepting connections", len(pongs), len(ip_to_hostnames)) else: log.warning( "%i spv status probes failed, retrying later. servers tried: %s", len(sent_ping_timestamps), ', '.join('/'.join(hosts) + f' ({ip})' for ip, hosts in ip_to_hostnames.items())) return pongs finally: connection.close()
async def _get_external_ip( default_servers ) -> typing.Tuple[typing.Optional[str], typing.Optional[str]]: # used if upnp is disabled or non-functioning from lbry.wallet.server.udp import SPVStatusClientProtocol # pylint: disable=C0415 hostname_to_ip = {} ip_to_hostnames = collections.defaultdict(list) async def resolve_spv(server, port): try: server_addr = await resolve_host(server, port, 'udp') hostname_to_ip[server] = (server_addr, port) ip_to_hostnames[(server_addr, port)].append(server) except Exception: log.exception("error looking up dns for spv servers") # accumulate the dns results await asyncio.gather(*(resolve_spv(server, port) for (server, port) in default_servers)) loop = asyncio.get_event_loop() pong_responses = asyncio.Queue() connection = SPVStatusClientProtocol(pong_responses) try: await loop.create_datagram_endpoint(lambda: connection, ('0.0.0.0', 0)) # could raise OSError if it cant bind randomized_servers = list(ip_to_hostnames.keys()) random.shuffle(randomized_servers) for server in randomized_servers: connection.ping(server) try: _, pong = await asyncio.wait_for(pong_responses.get(), 1) if is_valid_public_ipv4(pong.ip_address): return pong.ip_address, ip_to_hostnames[server][0] except asyncio.TimeoutError: pass return None, None finally: connection.close()