def main(): parser = argparse.ArgumentParser(description='Interact with an electrum server') parser.add_argument('method', default=None, help='"blockchain.numblocks.subscribe" or similar') parser.add_argument('args', nargs="*", default=[], help='Arguments for method') parser.add_argument('--server', default='cluelessperson.com', help='Hostname of Electrum server to use') parser.add_argument('--protocol', default='s', help='Protocol code: t=TCP Cleartext, s=SSL, etc') parser.add_argument('--port', default=None, help='Port number to override default for protocol') parser.add_argument('--tor', default=False, action="store_true", help='Use local Tor proxy to connect') args = parser.parse_args() import logging logging.getLogger('connectrum').setLevel(logging.DEBUG) # convert to our datastruct about servers. svr = ServerInfo(args.server, args.server, ports=((args.protocol+str(args.port)) if args.port else args.protocol)) loop = asyncio.get_event_loop() conn = StratumClient() connector = conn.connect(svr, args.protocol, use_tor=svr.is_onion, disable_cert_verify=True) loop.run_until_complete(interact(conn, svr, connector, args.method, args.args)) loop.close()
async def setup_client() -> StratumClient: try: return CLIENT except NameError: pass server = ServerInfo({ "nickname": None, "hostname": "fortress.qtornado.com", "ip_addr": None, "ports": ["s50002", "t50001"], "version": "1.4", "pruning_limit": 0, "seen_at": 1533670768.8676858 }) client = StratumClient() await asyncio.wait_for(client.connect(server_info=server, proto_code='s', use_tor=False, disable_cert_verify=True), timeout=5) # # await asyncio.wait_for( # client.RPC( # 'server.version', # 'bitcoin-spv-merkle', # '1.2'), # timeout=5) return client
async def probe(svr, proto_code, use_tor): conn = StratumClient() try: await conn.connect(svr, proto_code, use_tor=(svr.is_onion or use_tor), short_term=True) except: failed.add(str(svr)) return None peers, _ = conn.subscribe('server.peers.subscribe') peers = await peers print("%s gave %d peers" % (svr, len(peers))) connected.add(str(svr)) # track them all. more = ks.add_peer_response(peers) if more: print("found %d more servers from %s: %s" % (len(more), svr, ', '.join(more))) conn.close() return str(svr)
async def setup_client(): if CLIENT is not None: return CLIENT server = ServerInfo({ "nickname": None, "hostname": "bitcoin.cluelessperson.com", "ip_addr": "172.92.140.254", "ports": ["s50002", "t50001"], "version": "1.2", "pruning_limit": 0, "seen_at": 1533670768.588772 }) client = StratumClient() await asyncio.wait_for(client.connect(server_info=server, proto_code='s', use_tor=False, disable_cert_verify=True), timeout=5) await asyncio.wait_for(client.RPC('server.version', 'bitcoin-spv-merkle', '1.2'), timeout=5) return client
def __init__(self, loop: asyncio.AbstractEventLoop, server: str, port: int, proto: str) -> None: """ Connection object constructor. :param loop: an asyncio event loop :param server: a string containing a hostname :param port: port number that the server listens on :returns: A new Connection object """ logging.info("Connecting...") self.server_info = ServerInfo( server, hostname=server, ports=port) # type: ServerInfo logging.info(str(self.server_info.get_port(proto))) self.client = StratumClient(loop) # type: StratumClient self.connection = self.client.connect( self.server_info, proto_code=proto, use_tor=True, disable_cert_verify=(proto != "s")) # type: asyncio.Future loop.run_until_complete(self._do_connect()) self.queue = None # type: asyncio.Queue
async def new_client(self, network: str) -> StratumClient: while True: server = self._get_server_info(network) try: client = StratumClient() await asyncio.wait_for( client.connect( server_info=server, proto_code='s', use_tor=False, disable_cert_verify=True), timeout=self._timeout_seconds) await asyncio.wait_for( client.RPC( 'server.version', self.user_agent, self.protocol_version), timeout=self._timeout_seconds) asyncio.ensure_future(self._keepalive(client, network)) self._servers.append(str(server)) return client except Exception as e: print('failed:', server) print(e, str(e)) # fall back to top of loop and try a new server pass
def main(): svr = ServerInfo("test-net", "testnet.hsmiths.com", ports="s50002") loop = asyncio.get_event_loop() conn = StratumClient() connector = conn.connect(svr, disable_cert_verify=True) loop.run_until_complete( interact(conn, svr, connector, "blockchain.address.get_balance", ["3KF9nXowQ4asSGxRRzeiTpDjMuwM2nypAN"])) loop.close()
async def connect(self): server_info = ServerInfo("", hostname="mdw.ddns.net", ports=50001) self.client = StratumClient(self.app.loop) self.connection = self.client.connect( server_info, proto_code="t") # type: asyncio.Future try: await self.connection self.connected = True except Exception: print("Unable to connect to server:", server_info)
def main(): parser = argparse.ArgumentParser(description='Subscribe to BTC events') parser.add_argument('method', help='"blockchain.numblocks.subscribe" or similar') parser.add_argument('args', nargs="*", default=[], help='Arguments for method') parser.add_argument('--server', default='cluelessperson.com', help='Hostname of Electrum server to use') parser.add_argument('--protocol', default='s', help='Protocol code: t=TCP Cleartext, s=SSL, etc') parser.add_argument('--port', default=None, help='Port number to override default for protocol') parser.add_argument('--tor', default=False, action="store_true", help='Use local Tor proxy to connect') parser.add_argument('--debug', default=False, action="store_true", help='Enable debug output from connectrum library') args = parser.parse_args() if args.debug: import logging logging.getLogger('connectrum').setLevel(logging.DEBUG) # convert to our datastruct about servers. svr = ServerInfo(args.server, args.server, ports=((args.protocol + str(args.port)) if args.port else args.protocol)) loop = asyncio.get_event_loop() conn = StratumClient() connector = conn.connect(svr, args.protocol, use_tor=svr.is_onion, disable_cert_verify=True) loop.run_until_complete( listen(conn, svr, connector, args.method, args.args)) loop.close()
async def startup_code(app): # pick a random server app['conn'] = conn = StratumClient() try: await conn.connect(el_server, disable_cert_verify=True, use_tor=('localhost', 9150) if el_server.is_onion else False) except Exception as exc: print("unable to connect: %r" % exc) sys.exit() print( "Connected to electrum server: {hostname}:{port} ssl={ssl} tor={tor} ip_addr={ip_addr}" .format(**conn.actual_connection)) # track top block async def track_top_block(): global top_blk fut, Q = conn.subscribe('blockchain.headers.subscribe') top_blk = await fut while 1: top_blk = max(await Q.get()) print("new top-block: %r" % (top_blk, )) app.loop.create_task(track_top_block())
def call_electrum(conn, method, *args): # call a method and format up the response nicely print("args") print(args) svr = ServerInfo("electrumx.tamami-foundation.org", "electrumx.tamami-foundation.org", ports=(("tcp" + str("50001")) if "50001" else "tcp")) conn = StratumClient() time.sleep(10) t = '' try: resp = conn.RPC(method, *args) time.sleep(10) except ElectrumErrorResponse as e: response, req = e.args t += "2-1" return t print("CALL_ELECTRUM REPONSE") print(resp) return resp
async def startup_code(app): # pick a random server app['conn'] = conn = StratumClient() await conn.connect(el_server, disable_cert_verify=True) # track top block async def track_top_block(): global top_blk fut, Q = conn.subscribe('blockchain.numblocks.subscribe') top_blk = await fut while 1: top_blk = max(await Q.get()) print("new top-block: %r" % (top_blk, )) app.loop.create_task(track_top_block())
async def find_utxos(server: ServerInfo, master_key: BIP32, max_gap: int, max_account: int, address: Optional[str], fee_rate: Optional[int], should_broadcast: bool): """ Connect to an electrum server and find all the UTXOs spendable by a master key. """ print('⏳ Connecting to electrum server, this might take a while') client = StratumClient() await client.connect(server, disable_cert_verify=True) print('🌍 Connected to electrum server successfully') utxos = await scanner.scan_master_key(client, master_key, max_gap, max_account) if len(utxos) == 0: print('😔 Didn\'t find any unspent outputs') client.close() return balance = sum([utxo.amount_in_sat for utxo in utxos]) print(f'💸 Total spendable balance found: {balance} sats') if master_key.master_privkey is None: print('✍️ Re-run with a private key to create a sweep transaction') client.close() return if address is None: print('ℹ️ Re-run with `--address` to create a sweep transaction') client.close() return if fee_rate is None: fee_rate_in_btc_per_kb = await client.RPC('blockchain.estimatefee', 1) if fee_rate_in_btc_per_kb == -1: print( '🔁 Couldn\'t fetch fee rates, try again with manual fee rates using `--fee-rate`' ) client.close() return fee_rate = int(fee_rate_in_btc_per_kb * 10**8 / 1024) print(f'🚌 Fetched next-block fee rate of {fee_rate} sat/vbyte') tx_without_fee = transactions.Transaction(master_key, utxos, address, balance) fee = tx_without_fee.virtual_size() * fee_rate tx = transactions.Transaction(master_key, utxos, address, balance - fee) bin_tx = tx.to_bytes() print('👇 This transaction sweeps all funds to the address provided') print() print(bin_tx.hex()) print() if not should_broadcast: print( '📋 Copy this transaction and broadcast it manually to the network, or re-run with `--broadcast`' ) client.close() return try: print('📣 Broadcasting transaction to the network') txid = await client.RPC('blockchain.transaction.broadcast', bin_tx.hex()) print(f'✅ Transaction {txid} successfully broadcasted') except connectrum.exc.ElectrumErrorResponse as err: print(f'⛔️ Transaction broadcasting failed: {err}') client.close()
class Connection: """ Connection object. Connects to an Electrum server, and handles all Stratum protocol messages. """ # pylint: disable=E1111 def __init__(self, loop: asyncio.AbstractEventLoop, server: str, port: int, proto: str) -> None: """ Connection object constructor. :param loop: an asyncio event loop :param server: a string containing a hostname :param port: port number that the server listens on :returns: A new Connection object """ logging.info("Connecting...") self.server_info = ServerInfo(server, hostname=server, ports=port) # type: ServerInfo logging.info(str(self.server_info.get_port(proto))) self.client = StratumClient(loop) # type: StratumClient self.connection = self.client.connect( self.server_info, proto_code=proto, use_tor=True, disable_cert_verify=(proto != "s")) # type: asyncio.Future self.queue = None # type: asyncio.Queue async def do_connect(self) -> None: """ Coroutine. Establishes a persistent connection to an Electrum server. Awaits the connection because AFAIK an init method can't be async. """ await self.connection logging.info("Connected to server") async def listen_rpc(self, method: str, args: List) -> Any: """ Coroutine. Sends a normal RPC message to the server and awaits response. :param method: The Electrum API method to use :param args: Params associated with current method :returns: Future. Response from server for this method(args) """ return await self.client.RPC(method, *args) def listen_subscribe(self, method: str, args: List) -> None: """ Sends a "subscribe" message to the server and adds to the queue. Throws away the immediate future containing the "history" hash. :param method: The Electrum API method to use :param args: Params associated with current method """ t = self.client.subscribe( method, *args) # type: Tuple[asyncio.Future, asyncio.Queue] future, queue = t self.queue = queue return future async def consume_queue( self, queue_func: Callable[[List[str]], Awaitable[None]]) -> None: """ Coroutine. Infinite loop that consumes the current subscription queue. :param queue_func: A function to call when new responses arrive """ while True: result = await self.queue.get() # type: List[str] await queue_func(result)
class Server: def __init__(self, _chain): self.chain = _chain self.client = None self.connection = None self.server_list = None self.app = web.Application() self.app.router.add_get('/servers', self.handle) self.app.on_startup.append(self.start_background_tasks) self.app.on_cleanup.append(self.cleanup_background_tasks) self.load_server_list() self.connected = False def load_server_list(self): try: with open("servers.json", "r") as infile: self.server_list = json.load(infile) except Exception: self.server_list = list() async def connect(self): server_info = ServerInfo("", hostname="mdw.ddns.net", ports=50001) self.client = StratumClient(self.app.loop) self.connection = self.client.connect( server_info, proto_code="t") # type: asyncio.Future try: await self.connection self.connected = True except Exception: print("Unable to connect to server:", server_info) async def get_peers(self): server_list = list() peers = await self.client.RPC("server.peers.subscribe") for peer in peers: host, info = peer[1:] if info[0] not in ("v1.1", "v1.2"): continue proto_port = info[1] proto, port = proto_port[0], int(proto_port[1:]) server = [host, port, proto] server_list.append(server) return server_list async def update_server_list(self): if self.connected: self.server_list = await self.get_peers() else: self.server_list = await scrape_electrum_servers( self.chain, loop=self.app.loop) with open("servers.json", "w") as outfile: json.dump(self.server_list, outfile) async def update_loop(self): while True: if self.connected and not self.client.protocol: self.connected = False if not self.connected: pass # await self.connect() await self.update_server_list() await asyncio.sleep(600) async def start_background_tasks(self, app): app['dispatch'] = asyncio.ensure_future(self.update_loop()) async def cleanup_background_tasks(self, app): app['dispatch'].cancel() await app['dispatch'] async def handle(self, request): response_obj = {'servers': self.server_list} return web.json_response(response_obj)