URI: 
       tredo LNWorker pay: - wait until htlc has been fulfilled - raise if htlc is not fulfilled - return boolean success - try multiple paths in GUI - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 6d9ef2969030d9dac8111f36e1044621bcb15840
   DIR parent 669b84fbd68a8ea56d91053dd7a39143e4b1e310
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Thu, 23 May 2019 12:37:24 +0200
       
       redo LNWorker pay:
        - wait until htlc has been fulfilled
        - raise if htlc is not fulfilled
        - return boolean success
        - try multiple paths in GUI
       
       Diffstat:
         M electrum/commands.py                |       3 +--
         M electrum/gui/qt/main_window.py      |      18 +++++-------------
         M electrum/lnpeer.py                  |       4 ++--
         M electrum/lnworker.py                |      21 ++++++++++++---------
         M electrum/tests/regtest/regtest.sh   |       2 +-
         M electrum/tests/test_lnpeer.py       |      18 +++---------------
       
       6 files changed, 24 insertions(+), 42 deletions(-)
       ---
   DIR diff --git a/electrum/commands.py b/electrum/commands.py
       t@@ -781,8 +781,7 @@ class Commands:
        
            @command('wn')
            def lnpay(self, invoice):
       -        addr, peer, f = self.lnworker.pay(invoice)
       -        return f.result()
       +        return self.lnworker.pay(invoice, timeout=10)
        
            @command('wn')
            def addinvoice(self, requested_amount, message):
   DIR diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
       t@@ -1675,33 +1675,25 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
        
            def pay_lightning_invoice(self, invoice):
                amount = self.amount_e.get_amount()
       -        LN_NUM_PAYMENT_ATTEMPTS = 1  # TODO increase
       -
       +        LN_NUM_PAYMENT_ATTEMPTS = 3
                def on_success(result):
                    self.logger.info(f'ln payment success. {result}')
                    self.do_clear()
                def on_failure(exc_info):
                    type_, e, traceback = exc_info
                    if isinstance(e, PaymentFailure):
       -                self.show_error(_('Payment failed. Tried {} times:\n{}')
       -                                .format(LN_NUM_PAYMENT_ATTEMPTS, e))
       +                self.show_error(_('Payment failed. {}').format(e))
                    elif isinstance(e, InvoiceError):
                        self.show_error(_('InvoiceError: {}').format(e))
                    else:
                        raise e
                def task():
       -            failure_list = []
                    for i in range(LN_NUM_PAYMENT_ATTEMPTS):
       -                try:
       -                    addr, peer, future = self.wallet.lnworker.pay(invoice, amount_sat=amount)
       -                    future.result()
       +                success = self.wallet.lnworker.pay(invoice, amount_sat=amount, timeout=30)
       +                if success:
                            break
       -                except PaymentFailure as e:
       -                    failure_list.append(e)
       -                    # try again
                    else:
       -                msg = '\n'.join(str(e) for e in failure_list)
       -                raise PaymentFailure(msg)
       +                raise PaymentFailure('Failed after {i} attempts')
        
                msg = _('Sending lightning payment...')
                WaitingDialog(self, msg, task, on_success, on_failure)
   DIR diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py
       t@@ -973,7 +973,7 @@ class Peer(Logger):
            @log_exceptions
            async def _on_update_fail_htlc(self, chan, htlc_id, local_ctn):
                await self.await_local(chan, local_ctn)
       -        self.network.trigger_callback('ln_message', self.lnworker, 'Payment failed', htlc_id)
       +        self.lnworker.pending_payments[(chan.short_channel_id, htlc_id)].set_result(False)
        
            def _handle_error_code_from_failed_htlc(self, error_reason, route: List['RouteEdge'], channel_id, htlc_id):
                chan = self.channels[channel_id]
       t@@ -1117,7 +1117,7 @@ class Peer(Logger):
            @log_exceptions
            async def _on_update_fulfill_htlc(self, chan, htlc_id, preimage, local_ctn):
                await self.await_local(chan, local_ctn)
       -        self.network.trigger_callback('ln_message', self.lnworker, 'Payment sent', htlc_id)
       +        self.lnworker.pending_payments[(chan.short_channel_id, htlc_id)].set_result(True)
                self.payment_preimages[sha256(preimage)].put_nowait(preimage)
        
            def on_update_fail_malformed_htlc(self, payload):
   DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py
       t@@ -314,6 +314,7 @@ class LNWallet(LNWorker):
                    c.set_local_commitment(c.current_commitment(LOCAL))
                # timestamps of opening and closing transactions
                self.channel_timestamps = self.storage.get('lightning_channel_timestamps', {})
       +        self.pending_payments = defaultdict(asyncio.Future)
        
            def start_network(self, network: 'Network'):
                self.network = network
       t@@ -641,14 +642,15 @@ class LNWallet(LNWorker):
                chan = f.result(timeout)
                return chan.funding_outpoint.to_str()
        
       -    def pay(self, invoice, amount_sat=None):
       +    def pay(self, invoice, amount_sat=None, timeout=10):
                """
       -        This is not merged with _pay so that we can run the test with
       -        one thread only.
       +        Can be called from other threads
       +        Raises timeout exception if htlc is not fulfilled
                """
       -        addr, peer, coro = self.network.run_from_another_thread(self._pay(invoice, amount_sat))
       -        fut = asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop)
       -        return addr, peer, fut
       +        fut = asyncio.run_coroutine_threadsafe(
       +            self._pay(invoice, amount_sat),
       +            self.network.asyncio_loop)
       +        return fut.result(timeout=timeout)
        
            def get_channel_by_short_id(self, short_channel_id):
                with self.lock:
       t@@ -656,15 +658,14 @@ class LNWallet(LNWorker):
                        if chan.short_channel_id == short_channel_id:
                            return chan
        
       -    async def _pay(self, invoice, amount_sat=None, same_thread=False):
       +    async def _pay(self, invoice, amount_sat=None):
                addr = self._check_invoice(invoice, amount_sat)
                self.save_invoice(addr.paymenthash, invoice, SENT, is_paid=False)
                self.wallet.set_label(bh2u(addr.paymenthash), addr.get_description())
                route = await self._create_route_from_invoice(decoded_invoice=addr)
       -        peer = self.peers[route[0].node_id]
                if not self.get_channel_by_short_id(route[0].short_channel_id):
                    assert False, 'Found route with short channel ID we don\'t have: ' + repr(route[0].short_channel_id)
       -        return addr, peer, self._pay_to_route(route, addr, invoice)
       +        return await self._pay_to_route(route, addr, invoice)
        
            async def _pay_to_route(self, route, addr, pay_req):
                short_channel_id = route[0].short_channel_id
       t@@ -674,6 +675,8 @@ class LNWallet(LNWorker):
                peer = self.peers[route[0].node_id]
                htlc = await peer.pay(route, chan, int(addr.amount * COIN * 1000), addr.paymenthash, addr.get_min_final_cltv_expiry())
                self.network.trigger_callback('htlc_added', htlc, addr, SENT)
       +        success = await self.pending_payments[(short_channel_id, htlc.htlc_id)]
       +        return success
        
            @staticmethod
            def _check_invoice(invoice, amount_sat=None):
   DIR diff --git a/electrum/tests/regtest/regtest.sh b/electrum/tests/regtest/regtest.sh
       t@@ -101,7 +101,7 @@ if [[ $1 == "redeem_htlcs" ]]; then
            sleep 10
            # alice pays bob
            invoice=$($bob addinvoice 0.05 "test")
       -    $alice lnpay $invoice
       +    $alice lnpay $invoice || true
            sleep 1
            settled=$($alice list_channels | jq '.[] | .local_htlcs | .settles | length')
            if [[ "$settled" != "0" ]]; then
   DIR diff --git a/electrum/tests/test_lnpeer.py b/electrum/tests/test_lnpeer.py
       t@@ -90,6 +90,7 @@ class MockLNWallet:
                self.inflight = {}
                self.wallet = MockWallet()
                self.localfeatures = LnLocalFeatures(0)
       +        self.pending_payments = defaultdict(asyncio.Future)
        
            @property
            def lock(self):
       t@@ -216,25 +217,12 @@ class TestPeer(SequentialTestCase):
                w2.invoices[bh2u(RHASH)] = (pay_req, True, False)
                return pay_req
        
       -    @staticmethod
       -    def prepare_ln_message_future(w2 # receiver
       -            ):
       -        fut = asyncio.Future()
       -        def evt_set(event, _lnwallet, msg, _htlc_id):
       -            fut.set_result(msg)
       -        w2.network.register_callback(evt_set, ['ln_message'])
       -        return fut
       -
            def test_payment(self):
                p1, p2, w1, w2, _q1, _q2 = self.prepare_peers()
                pay_req = self.prepare_invoice(w2)
       -        fut = self.prepare_ln_message_future(w2)
       -
                async def pay():
       -            addr, peer, coro = await LNWallet._pay(w1, pay_req, same_thread=True)
       -            await coro
       -            print("HTLC ADDED")
       -            self.assertEqual(await fut, 'Payment received')
       +            result = await LNWallet._pay(w1, pay_req)
       +            self.assertEqual(result, True)
                    gath.cancel()
                gath = asyncio.gather(pay(), p1._message_loop(), p2._message_loop())
                async def f():