URI: 
       tqt: pay_lightning_invoice - attempt paying multiple times in case of failure - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit b85aea1541098d0284de5b92c1ca997c12c68cdc
   DIR parent 4d1785799be2cfc2e34971a48f8c0c5deda21098
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Wed,  1 Aug 2018 18:32:16 +0200
       
       qt: pay_lightning_invoice - attempt paying multiple times in case of failure
       
       Diffstat:
         M electrum/gui/qt/main_window.py      |      36 ++++++++++++++++++++++++++------
         M electrum/lnaddr.py                  |       4 +++-
         M electrum/lnhtlc.py                  |       2 ++
         M electrum/lnworker.py                |       8 ++++----
       
       4 files changed, 39 insertions(+), 11 deletions(-)
       ---
   DIR diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
       t@@ -1664,13 +1664,37 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                self.do_send(preview = True)
        
            def pay_lightning_invoice(self, invoice):
       -        try:
       -            amount = self.amount_e.get_amount()
       -            f = self.wallet.lnworker.pay(invoice, amount_sat=amount)
       -        except InvoiceError as e:
       -            self.show_error(str(e))
       -        else:
       +        amount = self.amount_e.get_amount()
       +        LN_NUM_PAYMENT_ATTEMPTS = 1  # TODO increase
       +
       +        def on_success(result):
       +            self.print_error('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))
       +            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:
       +                    future = self.wallet.lnworker.pay(invoice, amount_sat=amount)
       +                    future.result()
       +                    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)
       +
       +        msg = _('Sending lightning payment...')
       +        WaitingDialog(self, msg, task, on_success, on_failure)
        
            def do_send(self, preview = False):
                if self.payto_e.is_lightning:
   DIR diff --git a/electrum/lnaddr.py b/electrum/lnaddr.py
       t@@ -248,7 +248,9 @@ class LnAddr(object):
                    ", ".join([k + '=' + str(v) for k, v in self.tags])
                )
        
       -def lndecode(a, verbose=False, expected_hrp=constants.net.SEGWIT_HRP):
       +def lndecode(a, verbose=False, expected_hrp=None):
       +    if expected_hrp is None:
       +        expected_hrp = constants.net.SEGWIT_HRP
            hrp, data = bech32_decode(a, ignore_long_length=True)
            if not hrp:
                raise ValueError("Bad bech32 checksum")
   DIR diff --git a/electrum/lnhtlc.py b/electrum/lnhtlc.py
       t@@ -511,10 +511,12 @@ class HTLCStateMachine(PrintError):
        
            @property
            def htlcs_in_local(self):
       +        """in the local log. 'offered by us'"""
                return self.gen_htlc_indices("local")
        
            @property
            def htlcs_in_remote(self):
       +        """in the remote log. 'offered by them'"""
                return self.gen_htlc_indices("remote")
        
            def settle_htlc(self, preimage, htlc_id):
   DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py
       t@@ -16,7 +16,8 @@ from .lnbase import Peer, privkey_to_pubkey, aiosafe
        from .lnaddr import lnencode, LnAddr, lndecode
        from .ecc import der_sig_from_sig_string
        from .lnhtlc import HTLCStateMachine
       -from .lnutil import Outpoint, calc_short_channel_id, LNPeerAddr, get_compressed_pubkey_from_bech32
       +from .lnutil import (Outpoint, calc_short_channel_id, LNPeerAddr, get_compressed_pubkey_from_bech32,
       +                     PaymentFailure)
        from .lnwatcher import LNChanCloseHandler
        from .i18n import _
        
       t@@ -175,7 +176,6 @@ class LNWorker(PrintError):
                return asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop)
        
            def pay(self, invoice, amount_sat=None):
       -        # TODO try some number of paths (e.g. 10) in case of failures
                addr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
                payment_hash = addr.paymenthash
                invoice_pubkey = addr.pubkey.serialize()
       t@@ -185,7 +185,7 @@ class LNWorker(PrintError):
                amount_msat = int(amount_sat * 1000)
                path = self.network.path_finder.find_path_for_payment(self.pubkey, invoice_pubkey, amount_msat)
                if path is None:
       -            raise Exception("No path found")
       +            raise PaymentFailure(_("No path found"))
                node_id, short_channel_id = path[0]
                peer = self.peers[node_id]
                with self.lock:
       t@@ -194,7 +194,7 @@ class LNWorker(PrintError):
                    if chan.short_channel_id == short_channel_id:
                        break
                else:
       -            raise Exception("ChannelDB returned path with short_channel_id that is not in channel list")
       +            raise Exception("ChannelDB returned path with short_channel_id {} that is not in channel list".format(bh2u(short_channel_id)))
                coro = peer.pay(path, chan, amount_msat, payment_hash, invoice_pubkey, addr.min_final_cltv_expiry)
                return asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop)