URI: 
       twallet: when sweeping, do network reqs in parallel, and don't block GUI - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 7bcb59ffb5a30d3a116b086c9ae291bf4bb788f3
   DIR parent 40a51cc09019b46d7ca9f2a457e95e6986e1db8a
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Fri,  5 Jun 2020 20:30:25 +0200
       
       wallet: when sweeping, do network reqs in parallel, and don't block GUI
       
       Diffstat:
         M electrum/gui/qt/main_window.py      |      22 +++++++++++++---------
         M electrum/tests/test_wallet_vertica… |       5 +++++
         M electrum/wallet.py                  |      83 +++++++++++++++++++------------
       
       3 files changed, 68 insertions(+), 42 deletions(-)
       ---
   DIR diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
       t@@ -2841,15 +2841,19 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                except InternalAddressCorruption as e:
                    self.show_error(str(e))
                    raise
       -        try:
       -            coins, keypairs = sweep_preparations(get_pk(), self.network)
       -        except Exception as e:  # FIXME too broad...
       -            self.show_message(repr(e))
       -            return
       -        scriptpubkey = bfh(bitcoin.address_to_script(addr))
       -        outputs = [PartialTxOutput(scriptpubkey=scriptpubkey, value='!')]
       -        self.warn_if_watching_only()
       -        self.pay_onchain_dialog(coins, outputs, external_keypairs=keypairs)
       +        privkeys = get_pk()
       +
       +        def on_success(result):
       +            coins, keypairs = result
       +            outputs = [PartialTxOutput.from_address_and_value(addr, value='!')]
       +            self.warn_if_watching_only()
       +            self.pay_onchain_dialog(coins, outputs, external_keypairs=keypairs)
       +        def on_failure(exc_info):
       +            self.on_error(exc_info)
       +        msg = _('Preparing sweep transaction...')
       +        task = lambda: self.network.run_from_another_thread(
       +            sweep_preparations(privkeys, self.network))
       +        WaitingDialog(self, msg, task, on_success, on_failure)
        
            def _do_import(self, title, header_layout, func):
                text = text_dialog(self, title, header_layout, _('Import'), allow_multi=True)
   DIR diff --git a/electrum/tests/test_wallet_vertical.py b/electrum/tests/test_wallet_vertical.py
       t@@ -1404,6 +1404,11 @@ class TestWalletSending(TestCaseForTestnet):
                            return [{'tx_hash': 'ac24de8b58e826f60bd7b9ba31670bdfc3e8aedb2f28d0e91599d741569e3429', 'tx_pos': 1, 'height': 1325785, 'value': 1000000}]
                        else:
                            return []
       +            async def get_transaction(self, txid):
       +                if txid == "ac24de8b58e826f60bd7b9ba31670bdfc3e8aedb2f28d0e91599d741569e3429":
       +                    return "010000000001021b41471d6af3aa80ebe536dbf4f505a6d46af456131a8e12e1950171959b690e0f00000000fdffffff2ef29833a69863b31e884fc5e6f7b99a23b5601e14f0eb65905faa42fec0776d0000000000fdffffff02f96a070000000000160014e61b989a740056254b5f8061281ac96ca15d35e140420f00000000004341049afa8fb50f52104b381a673c6e4fb7fb54987271d0e948dd9a568bb2af6f9310a7a809ce06e09d1510e5836f20414596232e2c0be63715459fa3cf8e7092af05ac0247304402201fe20012c1c732a6a8f942c4e0feed5ed0bddfb94db736ec3d0c0d38f0f7f46a022021d690e6d2688b90b76002f4c3134981502d666211e85e8a6ca91e78405dfa3801210346fb31136ab48e6c648865264d32004b43643d01f0ba485cffac4bb0b3f739470247304402204a2473ab4b3bfc8e6b1a6b8675dc2c3d115d8c04f5df37f29779dca6d300d9db02205e72ebbccd018c67b86ae4da6b0e6222902a8de85915ed6115330b9328764b370121027a93ffc9444a12d99307318e2e538949072cb35b2aca344b8163795a022414c7d73a1400"
       +                else:
       +                    raise Exception("unexpected txid")
        
                privkeys = ['93NQ7CFbwTPyKDJLXe97jczw33fiLijam2SCZL3Uinz1NSbHrTu', ]
                network = NetworkMock()
   DIR diff --git a/electrum/wallet.py b/electrum/wallet.py
       t@@ -43,6 +43,8 @@ from typing import TYPE_CHECKING, List, Optional, Tuple, Union, NamedTuple, Sequ
        from abc import ABC, abstractmethod
        import itertools
        
       +from aiorpcx import TaskGroup
       +
        from .i18n import _
        from .bip32 import BIP32Node, convert_bip32_intpath_to_strpath, convert_bip32_path_to_list_of_uint32
        from .crypto import sha256
       t@@ -92,64 +94,79 @@ TX_STATUS = [
        ]
        
        
       -def _append_utxos_to_inputs(inputs: List[PartialTxInput], network: 'Network', pubkey, txin_type, imax):
       +async def _append_utxos_to_inputs(*, inputs: List[PartialTxInput], network: 'Network',
       +                                  pubkey: str, txin_type: str, imax: int) -> None:
            if txin_type in ('p2pkh', 'p2wpkh', 'p2wpkh-p2sh'):
                address = bitcoin.pubkey_to_address(txin_type, pubkey)
                scripthash = bitcoin.address_to_scripthash(address)
            elif txin_type == 'p2pk':
                script = bitcoin.public_key_to_p2pk_script(pubkey)
                scripthash = bitcoin.script_to_scripthash(script)
       -        address = None
            else:
                raise Exception(f'unexpected txin_type to sweep: {txin_type}')
        
       -    u = network.run_from_another_thread(network.listunspent_for_scripthash(scripthash))
       -    for item in u:
       -        if len(inputs) >= imax:
       -            break
       +    async def append_single_utxo(item):
       +        prev_tx_raw = await network.get_transaction(item['tx_hash'])
       +        prev_tx = Transaction(prev_tx_raw)
       +        prev_txout = prev_tx.outputs()[item['tx_pos']]
       +        if scripthash != bitcoin.script_to_scripthash(prev_txout.scriptpubkey.hex()):
       +            raise Exception('scripthash mismatch when sweeping')
                prevout_str = item['tx_hash'] + ':%d' % item['tx_pos']
                prevout = TxOutpoint.from_str(prevout_str)
       -        utxo = PartialTxInput(prevout=prevout)
       -        utxo._trusted_value_sats = int(item['value'])
       -        utxo._trusted_address = address
       -        utxo.block_height = int(item['height'])
       -        utxo.script_type = txin_type
       -        utxo.pubkeys = [bfh(pubkey)]
       -        utxo.num_sig = 1
       +        txin = PartialTxInput(prevout=prevout)
       +        txin.utxo = prev_tx
       +        txin.block_height = int(item['height'])
       +        txin.script_type = txin_type
       +        txin.pubkeys = [bfh(pubkey)]
       +        txin.num_sig = 1
                if txin_type == 'p2wpkh-p2sh':
       -            utxo.redeem_script = bfh(bitcoin.p2wpkh_nested_script(pubkey))
       -        inputs.append(utxo)
       +            txin.redeem_script = bfh(bitcoin.p2wpkh_nested_script(pubkey))
       +        inputs.append(txin)
       +
       +    u = await network.listunspent_for_scripthash(scripthash)
       +    async with TaskGroup() as group:
       +        for item in u:
       +            if len(inputs) >= imax:
       +                break
       +            await group.spawn(append_single_utxo(item))
        
       -def sweep_preparations(privkeys, network: 'Network', imax=100):
        
       -    def find_utxos_for_privkey(txin_type, privkey, compressed):
       +async def sweep_preparations(privkeys, network: 'Network', imax=100):
       +
       +    async def find_utxos_for_privkey(txin_type, privkey, compressed):
                pubkey = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
       -        _append_utxos_to_inputs(inputs, network, pubkey, txin_type, imax)
       +        await _append_utxos_to_inputs(
       +            inputs=inputs,
       +            network=network,
       +            pubkey=pubkey,
       +            txin_type=txin_type,
       +            imax=imax)
                keypairs[pubkey] = privkey, compressed
       +
            inputs = []  # type: List[PartialTxInput]
            keypairs = {}
       -    for sec in privkeys:
       -        txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec)
       -        find_utxos_for_privkey(txin_type, privkey, compressed)
       -        # do other lookups to increase support coverage
       -        if is_minikey(sec):
       -            # minikeys don't have a compressed byte
       -            # we lookup both compressed and uncompressed pubkeys
       -            find_utxos_for_privkey(txin_type, privkey, not compressed)
       -        elif txin_type == 'p2pkh':
       -            # WIF serialization does not distinguish p2pkh and p2pk
       -            # we also search for pay-to-pubkey outputs
       -            find_utxos_for_privkey('p2pk', privkey, compressed)
       +    async with TaskGroup() as group:
       +        for sec in privkeys:
       +            txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec)
       +            await group.spawn(find_utxos_for_privkey(txin_type, privkey, compressed))
       +            # do other lookups to increase support coverage
       +            if is_minikey(sec):
       +                # minikeys don't have a compressed byte
       +                # we lookup both compressed and uncompressed pubkeys
       +                await group.spawn(find_utxos_for_privkey(txin_type, privkey, not compressed))
       +            elif txin_type == 'p2pkh':
       +                # WIF serialization does not distinguish p2pkh and p2pk
       +                # we also search for pay-to-pubkey outputs
       +                await group.spawn(find_utxos_for_privkey('p2pk', privkey, compressed))
            if not inputs:
       -        raise Exception(_('No inputs found. (Note that inputs need to be confirmed)'))
       -        # FIXME actually inputs need not be confirmed now, see https://github.com/kyuupichan/electrumx/issues/365
       +        raise Exception(_('No inputs found.'))
            return inputs, keypairs
        
        
        def sweep(privkeys, *, network: 'Network', config: 'SimpleConfig',
                  to_address: str, fee: int = None, imax=100,
                  locktime=None, tx_version=None) -> PartialTransaction:
       -    inputs, keypairs = sweep_preparations(privkeys, network, imax)
       +    inputs, keypairs = network.run_from_another_thread(sweep_preparations(privkeys, network, imax))
            total = sum(txin.value_sats() for txin in inputs)
            if fee is None:
                outputs = [PartialTxOutput(scriptpubkey=bfh(bitcoin.address_to_script(to_address)),