Browse Source

LNWorker: give up payment after timeout, not number of attempts.

Limiting attempts may interrupt a MPP before we receive a MPP_timeout
The attempts parameter is still used in unit tests.
patch-4
ThomasV 3 years ago
parent
commit
f4e902e907
  1. 6
      electrum/commands.py
  2. 2
      electrum/gui/kivy/uix/screens.py
  3. 2
      electrum/gui/qt/main_window.py
  4. 11
      electrum/lnworker.py
  5. 1
      electrum/tests/test_lnpeer.py

6
electrum/commands.py

@ -1103,12 +1103,12 @@ class Commands:
return invoice.to_debug_json()
@command('wnl')
async def lnpay(self, invoice, attempts=1, timeout=30, wallet: Abstract_Wallet = None):
async def lnpay(self, invoice, timeout=120, wallet: Abstract_Wallet = None):
lnworker = wallet.lnworker
lnaddr = lnworker._check_invoice(invoice)
payment_hash = lnaddr.paymenthash
wallet.save_invoice(Invoice.from_bech32(invoice))
success, log = await lnworker.pay_invoice(invoice, attempts=attempts)
success, log = await lnworker.pay_invoice(invoice)
return {
'payment_hash': payment_hash.hex(),
'success': success,
@ -1347,7 +1347,6 @@ command_options = {
'domain': ("-D", "List of addresses"),
'memo': ("-m", "Description of the request"),
'expiration': (None, "Time in seconds"),
'attempts': (None, "Number of payment attempts"),
'timeout': (None, "Timeout in seconds"),
'force': (None, "Create new address beyond gap limit, if no more addresses are available."),
'pending': (None, "Show only pending requests."),
@ -1394,7 +1393,6 @@ arg_types = {
'encrypt_file': eval_bool,
'rbf': eval_bool,
'timeout': float,
'attempts': int,
}
config_variables = {

2
electrum/gui/kivy/uix/screens.py

@ -361,7 +361,7 @@ class SendScreen(CScreen, Logger):
amount_msat = invoice.get_amount_msat()
def pay_thread():
try:
coro = self.app.wallet.lnworker.pay_invoice(invoice.lightning_invoice, amount_msat=amount_msat, attempts=10)
coro = self.app.wallet.lnworker.pay_invoice(invoice.lightning_invoice, amount_msat=amount_msat)
fut = asyncio.run_coroutine_threadsafe(coro, self.app.network.asyncio_loop)
fut.result()
except Exception as e:

2
electrum/gui/qt/main_window.py

@ -1724,7 +1724,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
if not self.question(msg):
return
self.save_pending_invoice()
coro = self.wallet.lnworker.pay_invoice(invoice.lightning_invoice, amount_msat=amount_msat, attempts=LN_NUM_PAYMENT_ATTEMPTS)
coro = self.wallet.lnworker.pay_invoice(invoice.lightning_invoice, amount_msat=amount_msat)
self.run_coroutine_from_thread(coro)
def on_request_status(self, wallet, key, status):

11
electrum/lnworker.py

@ -607,6 +607,7 @@ class LNWallet(LNWorker):
lnwatcher: Optional['LNWalletWatcher']
MPP_EXPIRY = 120
TIMEOUT_SHUTDOWN_FAIL_PENDING_HTLCS = 3 # seconds
PAYMENT_TIMEOUT = 120
def __init__(self, wallet: 'Abstract_Wallet', xprv):
self.wallet = wallet
@ -1091,7 +1092,7 @@ class LNWallet(LNWorker):
async def _pay_scheduled_invoices(self):
for invoice in self.wallet.get_scheduled_invoices():
if invoice.is_lightning() and self.can_pay_invoice(invoice):
await self.pay_invoice(invoice.lightning_invoice, attempts=10)
await self.pay_invoice(invoice.lightning_invoice)
def can_pay_invoice(self, invoice: Invoice) -> bool:
assert invoice.is_lightning()
@ -1131,7 +1132,7 @@ class LNWallet(LNWorker):
async def pay_invoice(
self, invoice: str, *,
amount_msat: int = None,
attempts: int = 1,
attempts: int = None, # used only in unit tests
full_path: LNPaymentPath = None) -> Tuple[bool, List[HtlcLog]]:
lnaddr = self._check_invoice(invoice, amount_msat=amount_msat)
@ -1192,7 +1193,7 @@ class LNWallet(LNWorker):
min_cltv_expiry: int,
r_tags,
invoice_features: int,
attempts: int = 1,
attempts: int = None,
full_path: LNPaymentPath = None,
fwd_trampoline_onion=None,
fwd_trampoline_fee=None,
@ -1213,7 +1214,7 @@ class LNWallet(LNWorker):
use_two_trampolines = True
trampoline_fee_level = self.INITIAL_TRAMPOLINE_FEE_LEVEL
start_time = time.time()
amount_inflight = 0 # what we sent in htlcs (that receiver gets, without fees)
while True:
amount_to_send = amount_to_pay - amount_inflight
@ -1269,7 +1270,7 @@ class LNWallet(LNWorker):
self.network.path_finder.update_inflight_htlcs(htlc_log.route, add_htlcs=False)
return
# htlc failed
if len(log) >= attempts:
if (attempts is not None and len(log) >= attempts) or (attempts is None and time.time() - start_time > self.PAYMENT_TIMEOUT):
raise PaymentFailure('Giving up after %d attempts'%len(log))
# if we get a tmp channel failure, it might work to split the amount and try more routes
# if we get a channel update, we might retry the same route and amount

1
electrum/tests/test_lnpeer.py

@ -117,6 +117,7 @@ class MockWallet:
class MockLNWallet(Logger, NetworkRetryManager[LNPeerAddr]):
MPP_EXPIRY = 2 # HTLC timestamps are cast to int, so this cannot be 1
PAYMENT_TIMEOUT = 120
TIMEOUT_SHUTDOWN_FAIL_PENDING_HTLCS = 0
INITIAL_TRAMPOLINE_FEE_LEVEL = 0

Loading…
Cancel
Save