From 3851ce5dd1a26550f546aafc4438d30623d54a33 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 18 Jan 2022 19:50:46 +0100 Subject: [PATCH] scripts: add "update_default_servers.py" --- electrum/network.py | 21 +++++++- electrum/scripts/update_default_servers.py | 63 ++++++++++++++++++++++ 2 files changed, 82 insertions(+), 2 deletions(-) create mode 100755 electrum/scripts/update_default_servers.py diff --git a/electrum/network.py b/electrum/network.py index ce222ea1e..904edb01d 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -1331,11 +1331,19 @@ 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: Sequence[ServerAddr], method: str, params: Sequence): + async def send_multiple_requests( + self, + servers: Sequence[ServerAddr], + method: str, + params: Sequence, + *, + timeout: int = None, + ): + if timeout is None: + timeout = self.get_network_timeout_seconds(NetworkTimeout.Urgent) responses = dict() async def get_response(server: ServerAddr): interface = Interface(network=self, server=server, proxy=self.proxy) - timeout = self.get_network_timeout_seconds(NetworkTimeout.Urgent) try: await asyncio.wait_for(interface.ready, timeout) except BaseException as e: @@ -1350,3 +1358,12 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): for server in servers: await group.spawn(get_response(server)) return responses + + async def prune_offline_servers(self, hostmap): + peers = filter_protocol(hostmap, allowed_protocols=("t", "s",)) + timeout = self.get_network_timeout_seconds(NetworkTimeout.Generic) + replies = await self.send_multiple_requests(peers, 'blockchain.headers.subscribe', [], timeout=timeout) + servers_replied = {serveraddr.host for serveraddr in replies.keys()} + servers_dict = {k: v for k, v in hostmap.items() + if k in servers_replied} + return servers_dict diff --git a/electrum/scripts/update_default_servers.py b/electrum/scripts/update_default_servers.py new file mode 100755 index 000000000..04a6f970f --- /dev/null +++ b/electrum/scripts/update_default_servers.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# This script prints a new "servers.json" to stdout. +# It prunes the offline servers from the existing list (note: run with Tor proxy to keep .onions), +# and adds new servers from provided file(s) of candidate servers. +# A file of new candidate servers can be created via e.g.: +# $ ./electrum/scripts/servers.py > reply.txt + +import asyncio +import sys +import json + +from electrum.network import Network +from electrum.util import create_and_start_event_loop, log_exceptions +from electrum.simple_config import SimpleConfig +from electrum import constants + +try: + fname1 = sys.argv[1] + fname2 = sys.argv[2] if len(sys.argv) > 2 else None +except Exception: + print("usage: update_default_servers.py []") + print(" - the file(s) should contain json hostmaps for new servers to be added") + print(" - if two files are provided, their intersection is used (peers found in both).\n" + " file1 should have the newer data.") + sys.exit(1) + + +def get_newly_added_servers(fname1, fname2=None): + with open(fname1) as f: + res_hostmap = json.loads(f.read()) + if fname2 is not None: + with open(fname2) as f: + dict2 = json.loads(f.read()) + common_set = set.intersection(set(res_hostmap), set(dict2)) + res_hostmap = {k: v for k, v in res_hostmap.items() if k in common_set} + return res_hostmap + + +# testnet? +#constants.set_testnet() +config = SimpleConfig({'testnet': False}) + +loop, stopping_fut, loop_thread = create_and_start_event_loop() +network = Network(config) +network.start() + +@log_exceptions +async def f(): + try: + # prune existing servers + old_servers_all = constants.net.DEFAULT_SERVERS + old_servers_online = await network.prune_offline_servers(constants.net.DEFAULT_SERVERS) + # add new servers + newly_added_servers = get_newly_added_servers(fname1, fname2) + res_servers = {**old_servers_online, **newly_added_servers} + + print(json.dumps(res_servers, indent=4, sort_keys=True)) + print(f"got reply from {len(old_servers_online)}/{len(old_servers_all)} old servers", file=sys.stderr) + print(f"{len(newly_added_servers)=}. total: {len(res_servers)=}", file=sys.stderr) + finally: + stopping_fut.set_result(1) + +asyncio.run_coroutine_threadsafe(f(), loop)