Browse Source

redo LNWorker pay:

- wait until htlc has been fulfilled
 - raise if htlc is not fulfilled
 - return boolean success
 - try multiple paths in GUI
regtest_lnd
ThomasV 6 years ago
committed by SomberNight
parent
commit
71d4f63921
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 3
      electrum/commands.py
  2. 18
      electrum/gui/qt/main_window.py
  3. 4
      electrum/lnpeer.py
  4. 21
      electrum/lnworker.py
  5. 2
      electrum/tests/regtest/regtest.sh
  6. 18
      electrum/tests/test_lnpeer.py

3
electrum/commands.py

@ -785,8 +785,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):

18
electrum/gui/qt/main_window.py

@ -1674,33 +1674,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)

4
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):

21
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):

2
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

18
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():

Loading…
Cancel
Save