From 44f1b29852e6fdf503f49a439d06d4c6b781ef36 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 1 Aug 2018 18:32:16 +0200 Subject: [PATCH] qt: pay_lightning_invoice - attempt paying multiple times in case of failure --- electrum/gui/qt/main_window.py | 36 ++++++++++++++++++++++++++++------ electrum/lnaddr.py | 4 +++- electrum/lnhtlc.py | 2 ++ electrum/lnworker.py | 8 ++++---- 4 files changed, 39 insertions(+), 11 deletions(-) diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index 6fa94b7bd..cd100db29 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -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: diff --git a/electrum/lnaddr.py b/electrum/lnaddr.py index ff48823ac..99b842112 100755 --- a/electrum/lnaddr.py +++ b/electrum/lnaddr.py @@ -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") diff --git a/electrum/lnhtlc.py b/electrum/lnhtlc.py index 936296826..ed9c1a856 100644 --- a/electrum/lnhtlc.py +++ b/electrum/lnhtlc.py @@ -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): diff --git a/electrum/lnworker.py b/electrum/lnworker.py index b3d400332..0e6b6adac 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -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 _ @@ -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() @@ -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: @@ -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)