URI: 
       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()