URI: 
       tCatch exceptions raised in LNWorker._pay_to_route Reset payment status if an exception is caught. Also, do not pass status to the 'invoice_status' network callback. This fixes #5869, #5870, #5964. - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 4dc74870e1facb410c7b46a49a2f14712c4e58ba
   DIR parent 472c0defee233e36c37305a931b0760922e59ed9
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Mon, 17 Feb 2020 12:11:33 +0100
       
       Catch exceptions raised in LNWorker._pay_to_route
       Reset payment status if an exception is caught.
       Also, do not pass status to the 'invoice_status' network callback.
       This fixes #5869, #5870, #5964.
       
       Diffstat:
         M electrum/gui/kivy/main_window.py    |       6 +++++-
         M electrum/gui/kivy/uix/dialogs/invo… |       4 ++--
         M electrum/gui/qt/invoice_list.py     |       9 ++++-----
         M electrum/gui/qt/main_window.py      |       8 +++++---
         M electrum/lnworker.py                |      23 +++++++++++++++++------
         M electrum/wallet.py                  |       2 +-
       
       6 files changed, 34 insertions(+), 18 deletions(-)
       ---
   DIR diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py
       t@@ -226,7 +226,11 @@ class ElectrumWindow(App):
                    self.show_info(_('Payment Received') + '\n' + key)
                    self._trigger_update_history()
        
       -    def on_invoice_status(self, event, key, status):
       +    def on_invoice_status(self, event, key):
       +        req = self.wallet.get_invoice(key)
       +        if req is None:
       +            return
       +        status = req['status']
                # todo: update single item
                self.update_tab('send')
                if self.invoice_popup and self.invoice_popup.key == key:
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/invoice_dialog.py b/electrum/gui/kivy/uix/dialogs/invoice_dialog.py
       t@@ -8,7 +8,7 @@ from kivy.clock import Clock
        
        from electrum.gui.kivy.i18n import _
        from electrum.util import pr_tooltips, pr_color
       -from electrum.util import PR_UNKNOWN, PR_UNPAID
       +from electrum.util import PR_UNKNOWN, PR_UNPAID, PR_FAILED
        
        if TYPE_CHECKING:
            from electrum.gui.kivy.main_window import ElectrumWindow
       t@@ -78,7 +78,7 @@ class InvoiceDialog(Factory.Popup):
                self.status = status
                self.status_str = pr_tooltips[status]
                self.status_color = pr_color[status]
       -        self.can_pay = self.status == PR_UNPAID
       +        self.can_pay = self.status in[PR_UNPAID, PR_FAILED]
        
            def on_dismiss(self):
                self.app.request_popup = None
   DIR diff --git a/electrum/gui/qt/invoice_list.py b/electrum/gui/qt/invoice_list.py
       t@@ -32,7 +32,7 @@ from PyQt5.QtWidgets import QAbstractItemView
        from PyQt5.QtWidgets import QMenu, QVBoxLayout, QTreeWidget, QTreeWidgetItem
        
        from electrum.i18n import _
       -from electrum.util import format_time, PR_UNPAID, PR_PAID, PR_INFLIGHT
       +from electrum.util import format_time, PR_UNPAID, PR_PAID, PR_INFLIGHT, PR_FAILED
        from electrum.util import get_request_status
        from electrum.util import PR_TYPE_ONCHAIN, PR_TYPE_LN
        from electrum.lnutil import PaymentAttemptLog
       t@@ -73,10 +73,7 @@ class InvoiceList(MyTreeView):
                self.setSelectionMode(QAbstractItemView.ExtendedSelection)
                self.update()
        
       -    def update_item(self, key, status):
       -        req = self.parent.wallet.get_invoice(key)
       -        if req is None:
       -            return
       +    def update_item(self, key, req):
                model = self.model()
                for row in range(0, model.rowCount()):
                    item = model.item(row, 0)
       t@@ -169,6 +166,8 @@ class InvoiceList(MyTreeView):
                menu.addAction(_("Details"), lambda: self.parent.show_invoice(key))
                if invoice['status'] == PR_UNPAID:
                    menu.addAction(_("Pay"), lambda: self.parent.do_pay_invoice(invoice))
       +        if invoice['status'] == PR_FAILED:
       +            menu.addAction(_("Retry"), lambda: self.parent.do_pay_invoice(invoice))
                if self.parent.wallet.lnworker:
                    log = self.parent.wallet.lnworker.logs.get(key)
                    if log:
   DIR diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
       t@@ -1439,10 +1439,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                    self.notify(_('Payment received') + '\n' + key)
                    self.need_update.set()
        
       -    def on_invoice_status(self, key, status):
       -        if key not in self.wallet.invoices:
       +    def on_invoice_status(self, key):
       +        req = self.wallet.get_invoice(key)
       +        if req is None:
                    return
       -        self.invoice_list.update_item(key, status)
       +        status = req['status']
       +        self.invoice_list.update_item(key, req)
                if status == PR_PAID:
                    self.show_message(_('Payment succeeded'))
                    self.need_update.set()
   DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py
       t@@ -818,16 +818,18 @@ class LNWallet(LNWorker):
                for i in range(attempts):
                    try:
                        route = await self._create_route_from_invoice(decoded_invoice=lnaddr)
       -            except NoPathFound as e:
       +                self.set_payment_status(payment_hash, PR_INFLIGHT)
       +                self.network.trigger_callback('invoice_status', key)
       +                payment_attempt_log = await self._pay_to_route(route, lnaddr)
       +            except Exception as e:
                        log.append(PaymentAttemptLog(success=False, exception=e))
       +                self.set_payment_status(payment_hash, PR_UNPAID)
                        break
       -            self.network.trigger_callback('invoice_status', key, PR_INFLIGHT)
       -            payment_attempt_log = await self._pay_to_route(route, lnaddr)
                    log.append(payment_attempt_log)
                    success = payment_attempt_log.success
                    if success:
                        break
       -        self.network.trigger_callback('invoice_status', key, PR_PAID if success else PR_FAILED)
       +        self.network.trigger_callback('invoice_status', key)
                return success
        
            async def _pay_to_route(self, route: LNPaymentRoute, lnaddr: LnAddr) -> PaymentAttemptLog:
       t@@ -837,8 +839,9 @@ class LNWallet(LNWorker):
                    self.channel_db.remove_channel(short_channel_id)
                    raise Exception(f"PathFinder returned path with short_channel_id "
                                    f"{short_channel_id} that is not in channel list")
       -        self.set_payment_status(lnaddr.paymenthash, PR_INFLIGHT)
       -        peer = self.peers[route[0].node_id]
       +        peer = self.peers.get(route[0].node_id)
       +        if not peer:
       +            raise Exception('Dropped peer')
                htlc = await peer.pay(route, chan, int(lnaddr.amount * COIN * 1000), lnaddr.paymenthash, lnaddr.get_min_final_cltv_expiry())
                self.network.trigger_callback('htlc_added', htlc, lnaddr, SENT)
                success, preimage, reason = await self.await_payment(lnaddr.paymenthash)
       t@@ -1056,6 +1059,14 @@ class LNWallet(LNWorker):
                    status = PR_UNPAID
                return status
        
       +    def get_invoice_status(self, key):
       +        # status may be PR_FAILED
       +        status = self.get_payment_status(bfh(key))
       +        log = self.logs[key]
       +        if status == PR_UNPAID and log:
       +            status = PR_FAILED
       +        return status
       +
            async def await_payment(self, payment_hash):
                success, preimage, reason = await self.pending_payments[payment_hash]
                self.pending_payments.pop(payment_hash)
   DIR diff --git a/electrum/wallet.py b/electrum/wallet.py
       t@@ -656,7 +656,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
                if request_type == PR_TYPE_ONCHAIN:
                    item['status'] = PR_PAID if self.is_onchain_invoice_paid(item) else PR_UNPAID
                elif self.lnworker and request_type == PR_TYPE_LN:
       -            item['status'] = self.lnworker.get_payment_status(bfh(item['rhash']))
       +            item['status'] = self.lnworker.get_invoice_status(key)
                else:
                    return
                return item