commit 6d9ef2969030d9dac8111f36e1044621bcb15840
parent 669b84fbd68a8ea56d91053dd7a39143e4b1e310
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:
6 files changed, 24 insertions(+), 42 deletions(-)
diff --git a/electrum/commands.py b/electrum/commands.py
@@ -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):
diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
@@ -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)
diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py
@@ -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]
@@ -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):
diff --git a/electrum/lnworker.py b/electrum/lnworker.py
@@ -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
@@ -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:
@@ -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
@@ -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):
diff --git a/electrum/tests/regtest/regtest.sh b/electrum/tests/regtest/regtest.sh
@@ -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
diff --git a/electrum/tests/test_lnpeer.py b/electrum/tests/test_lnpeer.py
@@ -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):
@@ -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():