Browse Source

channel details with list of htlcs

dependabot/pip/contrib/deterministic-build/ecdsa-0.13.3
Janus 7 years ago
committed by ThomasV
parent
commit
e3409d32ef
  1. 2
      electrum/gui/kivy/uix/screens.py
  2. 141
      electrum/gui/qt/channel_details.py
  3. 6
      electrum/gui/qt/channels_list.py
  4. 2
      electrum/gui/qt/main_window.py
  5. 2
      electrum/lnaddr.py
  6. 14
      electrum/lnbase.py
  7. 4
      electrum/lnchan.py
  8. 2
      electrum/lnutil.py
  9. 48
      electrum/lnworker.py
  10. 2
      electrum/tests/test_lnbase.py

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

@ -294,7 +294,7 @@ class SendScreen(CScreen):
fut = asyncio.run_coroutine_threadsafe(coro, self.app.network.asyncio_loop) fut = asyncio.run_coroutine_threadsafe(coro, self.app.network.asyncio_loop)
fut.add_done_callback(self.ln_payment_result) fut.add_done_callback(self.ln_payment_result)
def payment_completed_async_thread(self, event, direction, htlc, preimage): def payment_completed_async_thread(self, event, date, direction, htlc, preimage, chan_id):
Clock.schedule_once(lambda dt: self.payment_completed(direction, htlc, preimage)) Clock.schedule_once(lambda dt: self.payment_completed(direction, htlc, preimage))
def payment_completed(self, direction, htlc, preimage): def payment_completed(self, direction, htlc, preimage):

141
electrum/gui/qt/channel_details.py

@ -0,0 +1,141 @@
from typing import Optional, TYPE_CHECKING
import PyQt5.QtGui as QtGui
import PyQt5.QtWidgets as QtWidgets
import PyQt5.QtCore as QtCore
from electrum.i18n import _
from electrum.lnchan import UpdateAddHtlc
from electrum.util import bh2u, format_time
from electrum.lnchan import HTLCOwner
from electrum.lnaddr import LnAddr, lndecode
if TYPE_CHECKING:
from .main_window import ElectrumWindow
class HTLCItem(QtGui.QStandardItem):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setEditable(False)
class ChannelDetailsDialog(QtWidgets.QDialog):
def make_inflight(self, lnaddr, i: UpdateAddHtlc):
it = HTLCItem(_('HTLC with ID ') + str(i.htlc_id))
it.appendRow([HTLCItem(_('Amount')),HTLCItem(self.format(i.amount_msat))])
it.appendRow([HTLCItem(_('CLTV expiry')),HTLCItem(str(i.cltv_expiry))])
it.appendRow([HTLCItem(_('Payment hash')),HTLCItem(bh2u(i.payment_hash))])
invoice = HTLCItem(_('Invoice'))
invoice.appendRow([HTLCItem(_('Remote node public key')), HTLCItem(bh2u(lnaddr.pubkey.serialize()))])
invoice.appendRow([HTLCItem(_('Amount in BTC')), HTLCItem(str(lnaddr.amount))])
invoice.appendRow([HTLCItem(_('Description')), HTLCItem(dict(lnaddr.tags).get('d', _('N/A')))])
invoice.appendRow([HTLCItem(_('Date')), HTLCItem(format_time(lnaddr.date))])
it.appendRow([invoice])
return it
def make_model(self, htlcs):
model = QtGui.QStandardItemModel(0, 2)
model.setHorizontalHeaderLabels(['HTLC', 'Property value'])
parentItem = model.invisibleRootItem()
folder_types = {'settled': _('Fulfilled HTLCs'), 'inflight': _('HTLCs in current commitment transaction')}
self.folders = {}
self.keyname_rows = {}
for keyname, i in folder_types.items():
myFont=QtGui.QFont()
myFont.setBold(True)
folder = HTLCItem(i)
folder.setFont(myFont)
parentItem.appendRow(folder)
self.folders[keyname] = folder
mapping = {}
num = 0
if keyname == 'inflight':
for lnaddr, i in htlcs[keyname]:
it = self.make_inflight(lnaddr, i)
self.folders[keyname].appendRow(it)
mapping[i.payment_hash] = num
num += 1
elif keyname == 'settled':
for date, direction, i, preimage in htlcs[keyname]:
it = HTLCItem(_('HTLC with ID ') + str(i.htlc_id))
it.appendRow([HTLCItem(_('Amount')),HTLCItem(self.format(i.amount_msat))])
it.appendRow([HTLCItem(_('CLTV expiry')),HTLCItem(str(i.cltv_expiry))])
it.appendRow([HTLCItem(_('Payment hash')),HTLCItem(bh2u(i.payment_hash))])
# NOTE no invoices because user can delete invoices after settlement
self.folders[keyname].appendRow(it)
mapping[i.payment_hash] = num
num += 1
self.keyname_rows[keyname] = mapping
return model
def move(self, fro: str, to: str, payment_hash: bytes):
assert fro != to
row_idx = self.keyname_rows[fro].pop(payment_hash)
row = self.folders[fro].takeRow(row_idx)
self.folders[to].appendRow(row)
dest_mapping = self.keyname_rows[to]
dest_mapping[payment_hash] = len(dest_mapping)
ln_payment_completed = QtCore.pyqtSignal(str, float, HTLCOwner, UpdateAddHtlc, bytes, bytes)
htlc_added = QtCore.pyqtSignal(str, UpdateAddHtlc, LnAddr, HTLCOwner)
@QtCore.pyqtSlot(str, UpdateAddHtlc, LnAddr, HTLCOwner)
def do_htlc_added(self, evtname, htlc, lnaddr, direction):
mapping = self.keyname_rows['inflight']
mapping[htlc.payment_hash] = len(mapping)
self.folders['inflight'].appendRow(self.make_inflight(lnaddr, htlc))
@QtCore.pyqtSlot(str, float, HTLCOwner, UpdateAddHtlc, bytes, bytes)
def do_ln_payment_completed(self, evtname, date, direction, htlc, preimage, chan_id):
self.move('inflight', 'settled', htlc.payment_hash)
def __init__(self, window: Optional['ElectrumWindow'], chan_id: bytes):
super().__init__(window)
self.window = window
assert type(window).__name__ in ['NoneType', 'ElectrumWindow']
self.ln_payment_completed.connect(self.do_ln_payment_completed)
self.htlc_added.connect(self.do_htlc_added)
if not window:
self.format = str
htlcs = {
'settled':
[
],
'inflight':
[
(lndecode("lnbcrt100n1pdl9c2vpp5z6ztyjy8an80te3u6l0fxuhjzt9pfa27a27uqap3xt8nv6dq47esdqgw3jhxapncqzy3rzjq2j0zgr9slpsefhaem0rq9w3kgjx6mjfd9tp7pe8yw23jqydcdtrsqqrc5qqqqgqqqqqqqlgqqqqqqgqjq5v97p0f0ftkwzmpxhjj6magd5ars465krljcp5z28j3nxl8d0kqjkzf6acjerxdu3yvtus75kakx3yvyus6c68hdwm2hpunusr47w3gpee4hgp"), UpdateAddHtlc(amount_msat=10001, payment_hash=b"\x01"*32, cltv_expiry=500, htlc_id=1)),
(lndecode('lnbcrt22m1pdl9kc7pp5qw903tar0e3ar4mu4h8m3zratj0sddqhfftpsjgcx0jsekzk43dsdqqcqzy3a6ev4vh6lt62xrzlq5l23g59pv0g3tur6drnduhczqg8smqlm75nklwx8r0mm535e4x8uq6tzqw7j7tvy70qaapfnt3e9n6rltvcs7cppzmqys'), UpdateAddHtlc(amount_msat=10002, payment_hash=b"\x02"*32, cltv_expiry=501, htlc_id=2)),
(lndecode('lnbcrt1u1pdl9k6tpp58la47qfxz6mvtgjmnmkl8xe8vcrkhluxrldlhv3dgdlla6tr3mvqdqgw3jhxapncqzy3rzjq2j0zgr9slpsefhaem0rq9w3kgjx6mjfd9tp7pe8yw23jqydcdtrsqqrc5qqqqgqqqqqqqlgqqqqqqgqjqavsdk9qdjwgfdywhlqtuzn5atkhzt9sgjz6tfll67wc34rh80mqzjme3meqyutrj0p7tvxczeuag956h6fv0356ezstgpfgqy47d7vsq7vhx6l'), UpdateAddHtlc(amount_msat=10003, payment_hash=b"\x03"*32, cltv_expiry=502, htlc_id=3)),
],
}
else:
htlcs = self.window.wallet.lnworker._list_invoices(chan_id)
self.format = lambda msat: self.window.format_amount_and_units(msat / 1000)
self.window.network.register_callback(self.ln_payment_completed.emit, ['ln_payment_completed'])
self.window.network.register_callback(self.htlc_added.emit, ['htlc_added'])
self.setWindowTitle(_('Channel Details'))
self.setMinimumSize(800, 400)
vbox = QtWidgets.QVBoxLayout(self)
w = QtWidgets.QTreeView(self)
w.setModel(self.make_model(htlcs))
#w.header().setStretchLastSection(False)
w.header().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents)
vbox.addWidget(w)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
d = ChannelDetailsDialog(None, b"\x01"*32)
d.show()
timer = QtCore.QTimer()
timer.setSingleShot(True)
def tick():
d.move('inflight', 'settled', b'\x02' * 32)
timer.timeout.connect(tick)
timer.start(3000)
sys.exit(app.exec_())

6
electrum/gui/qt/channels_list.py

@ -11,6 +11,7 @@ from electrum.lnutil import LOCAL, REMOTE, ConnStringFormatError
from .util import MyTreeWidget, SortableTreeWidgetItem, WindowModalDialog, Buttons, OkButton, CancelButton from .util import MyTreeWidget, SortableTreeWidgetItem, WindowModalDialog, Buttons, OkButton, CancelButton
from .amountedit import BTCAmountEdit from .amountedit import BTCAmountEdit
from .channel_details import ChannelDetailsDialog
class ChannelsList(MyTreeWidget): class ChannelsList(MyTreeWidget):
update_rows = QtCore.pyqtSignal() update_rows = QtCore.pyqtSignal()
@ -62,10 +63,15 @@ class ChannelsList(MyTreeWidget):
coro = lnworker.force_close_channel(channel_id) coro = lnworker.force_close_channel(channel_id)
return network.run_from_another_thread(coro) return network.run_from_another_thread(coro)
WaitingDialog(self, 'please wait..', task, on_success, on_failure) WaitingDialog(self, 'please wait..', task, on_success, on_failure)
menu.addAction(_("Details..."), lambda: self.details(channel_id))
menu.addAction(_("Close channel"), close) menu.addAction(_("Close channel"), close)
menu.addAction(_("Force-close channel"), force_close) menu.addAction(_("Force-close channel"), force_close)
menu.exec_(self.viewport().mapToGlobal(position)) menu.exec_(self.viewport().mapToGlobal(position))
def details(self, channel_id):
assert self.parent.wallet
ChannelDetailsDialog(self.parent, channel_id).show()
@QtCore.pyqtSlot(Channel) @QtCore.pyqtSlot(Channel)
def do_update_single_row(self, chan): def do_update_single_row(self, chan):
for i in range(self.topLevelItemCount()): for i in range(self.topLevelItemCount()):

2
electrum/gui/qt/main_window.py

@ -396,7 +396,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
self.require_fee_update = True self.require_fee_update = True
self.history_model.on_fee_histogram() self.history_model.on_fee_histogram()
elif event == 'ln_message': elif event == 'ln_message':
lnworker, message = args lnworker, message, htlc_id = args
if lnworker == self.wallet.lnworker: if lnworker == self.wallet.lnworker:
self.show_message(message) self.show_message(message)
else: else:

2
electrum/lnaddr.py

@ -250,7 +250,7 @@ class LnAddr(object):
def __str__(self): def __str__(self):
return "LnAddr[{}, amount={}{} tags=[{}]]".format( return "LnAddr[{}, amount={}{} tags=[{}]]".format(
hexlify(self.pubkey.serialize()).decode('utf-8'), hexlify(self.pubkey.serialize()).decode('utf-8') if self.pubkey else None,
self.amount, self.currency, self.amount, self.currency,
", ".join([k + '=' + str(v) for k, v in self.tags]) ", ".join([k + '=' + str(v) for k, v in self.tags])
) )

14
electrum/lnbase.py

@ -25,8 +25,8 @@ from .util import PrintError, bh2u, print_error, bfh, log_exceptions, list_enabl
from .transaction import Transaction, TxOutput from .transaction import Transaction, TxOutput
from .lnonion import (new_onion_packet, decode_onion_error, OnionFailureCode, calc_hops_data_for_payment, from .lnonion import (new_onion_packet, decode_onion_error, OnionFailureCode, calc_hops_data_for_payment,
process_onion_packet, OnionPacket, construct_onion_error, OnionRoutingFailureMessage) process_onion_packet, OnionPacket, construct_onion_error, OnionRoutingFailureMessage)
from .lnchan import Channel, RevokeAndAck, htlcsum from .lnchan import Channel, RevokeAndAck, htlcsum, UpdateAddHtlc
from .lnutil import (Outpoint, LocalConfig, from .lnutil import (Outpoint, LocalConfig, RECEIVED,
RemoteConfig, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore, RemoteConfig, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore,
funding_output_script, get_per_commitment_secret_from_seed, funding_output_script, get_per_commitment_secret_from_seed,
secret_to_pubkey, LNPeerAddr, PaymentFailure, LnLocalFeatures, secret_to_pubkey, LNPeerAddr, PaymentFailure, LnLocalFeatures,
@ -896,7 +896,7 @@ class Peer(PrintError):
self.revoke(chan) self.revoke(chan)
self.send_commitment(chan) # htlc will be removed self.send_commitment(chan) # htlc will be removed
await self.receive_revoke(chan) await self.receive_revoke(chan)
self.network.trigger_callback('ln_message', self.lnworker, 'Payment failed') self.network.trigger_callback('ln_message', self.lnworker, 'Payment failed', htlc_id)
async def _handle_error_code_from_failed_htlc(self, error_reason, route: List['RouteEdge'], channel_id, htlc_id): async def _handle_error_code_from_failed_htlc(self, error_reason, route: List['RouteEdge'], channel_id, htlc_id):
chan = self.channels[channel_id] chan = self.channels[channel_id]
@ -969,6 +969,7 @@ class Peer(PrintError):
self.attempted_route[(chan.channel_id, htlc_id)] = route self.attempted_route[(chan.channel_id, htlc_id)] = route
self.print_error(f"starting payment. route: {route}") self.print_error(f"starting payment. route: {route}")
await self.update_channel(chan, "update_add_htlc", channel_id=chan.channel_id, id=htlc_id, cltv_expiry=cltv, amount_msat=amount_msat, payment_hash=payment_hash, onion_routing_packet=onion.to_bytes()) await self.update_channel(chan, "update_add_htlc", channel_id=chan.channel_id, id=htlc_id, cltv_expiry=cltv, amount_msat=amount_msat, payment_hash=payment_hash, onion_routing_packet=onion.to_bytes())
return UpdateAddHtlc(**htlc, htlc_id=htlc_id)
async def receive_revoke(self, chan: Channel): async def receive_revoke(self, chan: Channel):
revoke_and_ack_msg = await self.revoke_and_ack[chan.channel_id].get() revoke_and_ack_msg = await self.revoke_and_ack[chan.channel_id].get()
@ -1007,7 +1008,7 @@ class Peer(PrintError):
self.revoke(chan) self.revoke(chan)
self.send_commitment(chan) # htlc will be removed self.send_commitment(chan) # htlc will be removed
await self.receive_revoke(chan) await self.receive_revoke(chan)
self.network.trigger_callback('ln_message', self.lnworker, 'Payment sent') self.network.trigger_callback('ln_message', self.lnworker, 'Payment sent', htlc_id)
# used in lightning-integration # used in lightning-integration
self.payment_preimages[sha256(preimage)].put_nowait(preimage) self.payment_preimages[sha256(preimage)].put_nowait(preimage)
@ -1034,7 +1035,7 @@ class Peer(PrintError):
pass # TODO fail the channel pass # TODO fail the channel
# add htlc # add htlc
htlc = {'amount_msat': amount_msat_htlc, 'payment_hash':payment_hash, 'cltv_expiry':cltv_expiry} htlc = {'amount_msat': amount_msat_htlc, 'payment_hash':payment_hash, 'cltv_expiry':cltv_expiry}
chan.receive_htlc(htlc) htlc_id = chan.receive_htlc(htlc)
await self.receive_commitment(chan) await self.receive_commitment(chan)
self.revoke(chan) self.revoke(chan)
self.send_commitment(chan) self.send_commitment(chan)
@ -1074,6 +1075,7 @@ class Peer(PrintError):
data=amount_msat_htlc.to_bytes(8, byteorder="big")) data=amount_msat_htlc.to_bytes(8, byteorder="big"))
await self.fail_htlc(chan, htlc_id, onion_packet, reason) await self.fail_htlc(chan, htlc_id, onion_packet, reason)
return return
self.network.trigger_callback('htlc_added', UpdateAddHtlc(**htlc, htlc_id=htlc_id), invoice, RECEIVED)
# settle htlc # settle htlc
await self.settle_htlc(chan, htlc_id, preimage) await self.settle_htlc(chan, htlc_id, preimage)
@ -1083,7 +1085,7 @@ class Peer(PrintError):
channel_id=chan.channel_id, channel_id=chan.channel_id,
id=htlc_id, id=htlc_id,
payment_preimage=preimage) payment_preimage=preimage)
self.network.trigger_callback('ln_message', self.lnworker, 'Payment received') self.network.trigger_callback('ln_message', self.lnworker, 'Payment received', htlc_id)
async def fail_htlc(self, chan: Channel, htlc_id: int, onion_packet: OnionPacket, async def fail_htlc(self, chan: Channel, htlc_id: int, onion_packet: OnionPacket,
reason: OnionRoutingFailureMessage): reason: OnionRoutingFailureMessage):

4
electrum/lnchan.py

@ -149,7 +149,7 @@ class Channel(PrintError):
def __init__(self, state, name = None, payment_completed : Optional[Callable[[HTLCOwner, UpdateAddHtlc, bytes], None]] = None): def __init__(self, state, name = None, payment_completed : Optional[Callable[[HTLCOwner, UpdateAddHtlc, bytes], None]] = None):
self.preimages = {} self.preimages = {}
if not payment_completed: if not payment_completed:
payment_completed = lambda x, y, z: None payment_completed = lambda this, x, y, z: None
self.payment_completed = payment_completed self.payment_completed = payment_completed
assert 'local_state' not in state assert 'local_state' not in state
self.config = {} self.config = {}
@ -505,7 +505,7 @@ class Channel(PrintError):
preimage = self.preimages.pop(htlc_id) preimage = self.preimages.pop(htlc_id)
else: else:
preimage = None preimage = None
self.payment_completed(subject, htlc, preimage) self.payment_completed(self, subject, htlc, preimage)
self.log[subject].settles.clear() self.log[subject].settles.clear()
return old_amount - htlcsum(self.htlcs(subject, False)) return old_amount - htlcsum(self.htlcs(subject, False))

2
electrum/lnutil.py

@ -595,7 +595,7 @@ def extract_nodeid(connect_contents: str) -> Tuple[bytes, str]:
raise ConnStringFormatError(_('At least a hostname must be supplied after the at symbol.')) raise ConnStringFormatError(_('At least a hostname must be supplied after the at symbol.'))
try: try:
node_id = bfh(nodeid_hex) node_id = bfh(nodeid_hex)
assert len(node_id) == 33 assert len(node_id) == 33, len(node_id)
except: except:
raise ConnStringFormatError(_('Invalid node ID, must be 33 bytes and hexadecimal')) raise ConnStringFormatError(_('Invalid node ID, must be 33 bytes and hexadecimal'))
return node_id, rest return node_id, rest

48
electrum/lnworker.py

@ -12,6 +12,7 @@ import threading
import socket import socket
import json import json
from datetime import datetime, timezone from datetime import datetime, timezone
from functools import partial
import dns.resolver import dns.resolver
import dns.exception import dns.exception
@ -62,7 +63,7 @@ class LNWorker(PrintError):
def __init__(self, wallet: 'Abstract_Wallet', network: 'Network'): def __init__(self, wallet: 'Abstract_Wallet', network: 'Network'):
self.wallet = wallet self.wallet = wallet
# invoices we are currently trying to pay (might be pending HTLCs on a commitment transaction) # invoices we are currently trying to pay (might be pending HTLCs on a commitment transaction)
self.paying = self.wallet.storage.get('lightning_payments_inflight', {}) # type: Dict[bytes, Tuple[str, Optional[int]]] self.paying = self.wallet.storage.get('lightning_payments_inflight', {}) # type: Dict[bytes, Tuple[str, Optional[int], bytes]]
self.sweep_address = wallet.get_receiving_address() self.sweep_address = wallet.get_receiving_address()
self.network = network self.network = network
self.channel_db = self.network.channel_db self.channel_db = self.network.channel_db
@ -71,9 +72,11 @@ class LNWorker(PrintError):
self.node_keypair = generate_keypair(self.ln_keystore, LnKeyFamily.NODE_KEY, 0) self.node_keypair = generate_keypair(self.ln_keystore, LnKeyFamily.NODE_KEY, 0)
self.config = network.config self.config = network.config
self.peers = {} # type: Dict[bytes, Peer] # pubkey -> Peer self.peers = {} # type: Dict[bytes, Peer] # pubkey -> Peer
channels_map = map(lambda x: Channel(x, payment_completed=self.payment_completed), wallet.storage.get("channels", [])) self.channels = {} # type: Dict[bytes, Channel]
self.channels = {x.channel_id: x for x in channels_map} # type: Dict[bytes, Channel] for x in wallet.storage.get("channels", []):
for c in self.channels.values(): c = Channel(x, payment_completed=self.payment_completed)
self.channels[c.channel_id] = c
c.lnwatcher = network.lnwatcher c.lnwatcher = network.lnwatcher
c.sweep_address = self.sweep_address c.sweep_address = self.sweep_address
self.invoices = wallet.storage.get('lightning_invoices', {}) # type: Dict[str, Tuple[str,str]] # RHASH -> (preimage, invoice) self.invoices = wallet.storage.get('lightning_invoices', {}) # type: Dict[str, Tuple[str,str]] # RHASH -> (preimage, invoice)
@ -86,7 +89,8 @@ class LNWorker(PrintError):
self.network.register_callback(self.on_channel_txo, ['channel_txo']) self.network.register_callback(self.on_channel_txo, ['channel_txo'])
asyncio.run_coroutine_threadsafe(self.network.main_taskgroup.spawn(self.main_loop()), self.network.asyncio_loop) asyncio.run_coroutine_threadsafe(self.network.main_taskgroup.spawn(self.main_loop()), self.network.asyncio_loop)
def payment_completed(self, direction, htlc, preimage): def payment_completed(self, chan, direction, htlc, preimage):
chan_id = chan.channel_id
if direction == SENT: if direction == SENT:
assert htlc.payment_hash not in self.invoices assert htlc.payment_hash not in self.invoices
self.paying.pop(bh2u(htlc.payment_hash)) self.paying.pop(bh2u(htlc.payment_hash))
@ -94,10 +98,11 @@ class LNWorker(PrintError):
l = self.wallet.storage.get('lightning_payments_completed', []) l = self.wallet.storage.get('lightning_payments_completed', [])
if not preimage: if not preimage:
preimage, _addr = self.get_invoice(htlc.payment_hash) preimage, _addr = self.get_invoice(htlc.payment_hash)
l.append((time.time(), direction, json.loads(encoder.encode(htlc)), bh2u(preimage))) tupl = (time.time(), direction, json.loads(encoder.encode(htlc)), bh2u(preimage), bh2u(chan_id))
l.append(tupl)
self.wallet.storage.put('lightning_payments_completed', l) self.wallet.storage.put('lightning_payments_completed', l)
self.wallet.storage.write() self.wallet.storage.write()
self.network.trigger_callback('ln_payment_completed', direction, htlc, preimage) self.network.trigger_callback('ln_payment_completed', tupl[0], direction, htlc, preimage, chan_id)
def list_invoices(self): def list_invoices(self):
report = self._list_invoices() report = self._list_invoices()
@ -128,13 +133,16 @@ class LNWorker(PrintError):
yield str(htlc) yield str(htlc)
yield '' yield ''
def _list_invoices(self): def _list_invoices(self, chan_id=None):
invoices = dict(self.invoices) invoices = dict(self.invoices)
completed = self.wallet.storage.get('lightning_payments_completed', []) completed = self.wallet.storage.get('lightning_payments_completed', [])
settled = [] settled = []
unsettled = [] unsettled = []
inflight = [] inflight = []
for date, direction, htlc, hex_preimage in completed: for date, direction, htlc, hex_preimage, hex_chan_id in completed:
if chan_id is not None:
if bfh(hex_chan_id) != chan_id:
continue
htlcobj = UpdateAddHtlc(*htlc) htlcobj = UpdateAddHtlc(*htlc)
if direction == RECEIVED: if direction == RECEIVED:
preimage = bfh(invoices.pop(bh2u(htlcobj.payment_hash))[0]) preimage = bfh(invoices.pop(bh2u(htlcobj.payment_hash))[0])
@ -145,18 +153,21 @@ class LNWorker(PrintError):
for preimage, pay_req in invoices.values(): for preimage, pay_req in invoices.values():
addr = lndecode(pay_req, expected_hrp=constants.net.SEGWIT_HRP) addr = lndecode(pay_req, expected_hrp=constants.net.SEGWIT_HRP)
unsettled.append((addr, bfh(preimage), pay_req)) unsettled.append((addr, bfh(preimage), pay_req))
for pay_req, amount_sat in self.paying.values(): for pay_req, amount_sat, this_chan_id in self.paying.values():
if chan_id is not None and this_chan_id != chan_id:
continue
addr = lndecode(pay_req, expected_hrp=constants.net.SEGWIT_HRP) addr = lndecode(pay_req, expected_hrp=constants.net.SEGWIT_HRP)
if amount_sat is not None: if amount_sat is not None:
addr.amount = Decimal(amount_sat) / COIN addr.amount = Decimal(amount_sat) / COIN
htlc = self.find_htlc_for_addr(addr) htlc = self.find_htlc_for_addr(addr, None if chan_id is None else [chan_id])
if not htlc: if not htlc:
self.print_error('Warning, in flight HTLC not found in any channel') self.print_error('Warning, in flight HTLC not found in any channel')
inflight.append((addr, htlc)) inflight.append((addr, htlc))
return {'settled': settled, 'unsettled': unsettled, 'inflight': inflight} return {'settled': settled, 'unsettled': unsettled, 'inflight': inflight}
def find_htlc_for_addr(self, addr): def find_htlc_for_addr(self, addr, whitelist=None):
for chan in self.channels.values(): channels = [y for x,y in self.channels.items() if x in whitelist or whitelist is None]
for chan in channels:
for htlc in chan.log[LOCAL].adds.values(): for htlc in chan.log[LOCAL].adds.values():
if htlc.payment_hash == addr.paymenthash: if htlc.payment_hash == addr.paymenthash:
return htlc return htlc
@ -362,7 +373,13 @@ class LNWorker(PrintError):
addr = self._check_invoice(invoice, amount_sat) addr = self._check_invoice(invoice, amount_sat)
route = self._create_route_from_invoice(decoded_invoice=addr) route = self._create_route_from_invoice(decoded_invoice=addr)
peer = self.peers[route[0].node_id] peer = self.peers[route[0].node_id]
self.paying[bh2u(addr.paymenthash)] = (invoice, amount_sat) for chan in self.channels.values():
if chan.short_channel_id == route[0].short_channel_id:
chan_id = chan.channel_id
break
else:
assert False, 'Found route with short channel ID we don\'t have: ' + repr(route[0].short_channel_id)
self.paying[bh2u(addr.paymenthash)] = (invoice, amount_sat, chan_id)
self.wallet.storage.put('lightning_payments_inflight', self.paying) self.wallet.storage.put('lightning_payments_inflight', self.paying)
self.wallet.storage.write() self.wallet.storage.write()
return addr, peer, self._pay_to_route(route, addr) return addr, peer, self._pay_to_route(route, addr)
@ -377,7 +394,8 @@ class LNWorker(PrintError):
else: else:
raise Exception("PathFinder returned path with short_channel_id {} that is not in channel list".format(bh2u(short_channel_id))) raise Exception("PathFinder returned path with short_channel_id {} that is not in channel list".format(bh2u(short_channel_id)))
peer = self.peers[route[0].node_id] peer = self.peers[route[0].node_id]
return await peer.pay(route, chan, int(addr.amount * COIN * 1000), addr.paymenthash, addr.get_min_final_cltv_expiry()) 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)
@staticmethod @staticmethod
def _check_invoice(invoice, amount_sat=None): def _check_invoice(invoice, amount_sat=None):

2
electrum/tests/test_lnbase.py

@ -195,7 +195,7 @@ class TestPeer(unittest.TestCase):
def prepare_ln_message_future(w2 # receiver def prepare_ln_message_future(w2 # receiver
): ):
fut = asyncio.Future() fut = asyncio.Future()
def evt_set(event, _lnworker, msg): def evt_set(event, _lnworker, msg, _htlc_id):
fut.set_result(msg) fut.set_result(msg)
w2.network.register_callback(evt_set, ['ln_message']) w2.network.register_callback(evt_set, ['ln_message'])
return fut return fut

Loading…
Cancel
Save