tfix some CLI/RPC commands - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit 6b8ad2d1263124fcc157536bd41c219736d552ae DIR parent 3b9a55fab4165f935b60525e45b5f4138930b78f HTML Author: SomberNight <somber.night@protonmail.com> Date: Thu, 27 Sep 2018 18:01:25 +0200 fix some CLI/RPC commands Diffstat: M electrum/commands.py | 11 ++++++----- M electrum/interface.py | 4 +++- M electrum/network.py | 46 ++++++++++++++++++++++++++++--- M electrum/synchronizer.py | 5 +++-- 4 files changed, 54 insertions(+), 12 deletions(-) --- DIR diff --git a/electrum/commands.py b/electrum/commands.py t@@ -181,7 +181,7 @@ class Commands: walletless server query, results are not checked by SPV. """ sh = bitcoin.address_to_scripthash(address) - return self.network.get_history_for_scripthash(sh) + return self.network.run_from_another_thread(self.network.get_history_for_scripthash(sh)) @command('w') def listunspent(self): t@@ -199,7 +199,7 @@ class Commands: is a walletless server query, results are not checked by SPV. """ sh = bitcoin.address_to_scripthash(address) - return self.network.listunspent_for_scripthash(sh) + return self.network.run_from_another_thread(self.network.listunspent_for_scripthash(sh)) @command('') def serialize(self, jsontx): t@@ -322,7 +322,7 @@ class Commands: server query, results are not checked by SPV. """ sh = bitcoin.address_to_scripthash(address) - out = self.network.get_balance_for_scripthash(sh) + out = self.network.run_from_another_thread(self.network.get_balance_for_scripthash(sh)) out["confirmed"] = str(Decimal(out["confirmed"])/COIN) out["unconfirmed"] = str(Decimal(out["unconfirmed"])/COIN) return out t@@ -331,7 +331,7 @@ class Commands: def getmerkle(self, txid, height): """Get Merkle branch of a transaction included in a block. Electrum uses this to verify transactions (Simple Payment Verification).""" - return self.network.get_merkle_for_transaction(txid, int(height)) + return self.network.run_from_another_thread(self.network.get_merkle_for_transaction(txid, int(height))) @command('n') def getservers(self): t@@ -517,7 +517,7 @@ class Commands: if self.wallet and txid in self.wallet.transactions: tx = self.wallet.transactions[txid] else: - raw = self.network.get_transaction(txid) + raw = self.network.run_from_another_thread(self.network.get_transaction(txid)) if raw: tx = Transaction(raw) else: t@@ -637,6 +637,7 @@ class Commands: @command('n') def notify(self, address, URL): """Watch an address. Every time the address changes, a http POST is sent to the URL.""" + raise NotImplementedError() # TODO this method is currently broken def callback(x): import urllib.request headers = {'content-type':'application/json'} DIR diff --git a/electrum/interface.py b/electrum/interface.py t@@ -76,7 +76,7 @@ class NotificationSession(ClientSession): super().send_request(*args, **kwargs), timeout) except asyncio.TimeoutError as e: - raise GracefulDisconnect('request timed out: {}'.format(args)) from e + raise RequestTimedOut('request timed out: {}'.format(args)) from e async def subscribe(self, method, params, queue): # note: until the cache is written for the first time, t@@ -105,6 +105,7 @@ class NotificationSession(ClientSession): class GracefulDisconnect(Exception): pass +class RequestTimedOut(GracefulDisconnect): pass class ErrorParsingSSLCert(Exception): pass class ErrorGettingSSLCertFromServer(Exception): pass t@@ -140,6 +141,7 @@ class Interface(PrintError): self._requested_chunks = set() self.network = network self._set_proxy(proxy) + self.session = None self.tip_header = None self.tip = 0 DIR diff --git a/electrum/network.py b/electrum/network.py t@@ -45,7 +45,7 @@ from .bitcoin import COIN from . import constants from . import blockchain from .blockchain import Blockchain, HEADER_SIZE -from .interface import Interface, serialize_server, deserialize_server +from .interface import Interface, serialize_server, deserialize_server, RequestTimedOut from .version import PROTOCOL_VERSION from .simple_config import SimpleConfig t@@ -638,13 +638,34 @@ class Network(PrintError): with b.lock: b.update_size() - async def get_merkle_for_transaction(self, tx_hash, tx_height): + def best_effort_reliable(func): + async def make_reliable_wrapper(self, *args, **kwargs): + for i in range(10): + iface = self.interface + session = iface.session if iface else None + if not session: + # no main interface; try again + await asyncio.sleep(0.1) + continue + try: + return await func(self, *args, **kwargs) + except RequestTimedOut: + if self.interface != iface: + # main interface changed; try again + continue + raise + raise Exception('no interface to do request on... gave up.') + return make_reliable_wrapper + + @best_effort_reliable + async def get_merkle_for_transaction(self, tx_hash: str, tx_height: int) -> dict: return await self.interface.session.send_request('blockchain.transaction.get_merkle', [tx_hash, tx_height]) + @best_effort_reliable async def broadcast_transaction(self, tx, timeout=10): try: out = await self.interface.session.send_request('blockchain.transaction.broadcast', [str(tx)], timeout=timeout) - except asyncio.TimeoutError as e: + except RequestTimedOut as e: return False, "error: operation timed out" except Exception as e: return False, "error: " + str(e) t@@ -653,10 +674,27 @@ class Network(PrintError): return False, "error: " + out return True, out + @best_effort_reliable async def request_chunk(self, height, tip=None, *, can_return_early=False): return await self.interface.request_chunk(height, tip=tip, can_return_early=can_return_early) - def blockchain(self): + @best_effort_reliable + async def get_transaction(self, tx_hash: str) -> str: + return await self.interface.session.send_request('blockchain.transaction.get', [tx_hash]) + + @best_effort_reliable + async def get_history_for_scripthash(self, sh: str) -> List[dict]: + return await self.interface.session.send_request('blockchain.scripthash.get_history', [sh]) + + @best_effort_reliable + async def listunspent_for_scripthash(self, sh: str) -> List[dict]: + return await self.interface.session.send_request('blockchain.scripthash.listunspent', [sh]) + + @best_effort_reliable + async def get_balance_for_scripthash(self, sh: str) -> dict: + return await self.interface.session.send_request('blockchain.scripthash.get_balance', [sh]) + + def blockchain(self) -> Blockchain: interface = self.interface if interface and interface.blockchain is not None: self.blockchain_index = interface.blockchain.forkpoint DIR diff --git a/electrum/synchronizer.py b/electrum/synchronizer.py t@@ -51,6 +51,7 @@ class Synchronizer(PrintError): ''' def __init__(self, wallet): self.wallet = wallet + self.network = wallet.network self.asyncio_loop = wallet.network.asyncio_loop self.requested_tx = {} self.requested_histories = {} t@@ -86,7 +87,7 @@ class Synchronizer(PrintError): # request address history self.requested_histories[addr] = status h = address_to_scripthash(addr) - result = await self.session.send_request("blockchain.scripthash.get_history", [h]) + result = await self.network.get_history_for_scripthash(h) self.print_error("receiving history", addr, len(result)) hashes = set(map(lambda item: item['tx_hash'], result)) hist = list(map(lambda item: (item['tx_hash'], item['height']), result)) t@@ -125,7 +126,7 @@ class Synchronizer(PrintError): await group.spawn(self._get_transaction, tx_hash) async def _get_transaction(self, tx_hash): - result = await self.session.send_request('blockchain.transaction.get', [tx_hash]) + result = await self.network.get_transaction(tx_hash) tx = Transaction(result) try: tx.deserialize()