tqt: channel_details: add more info: sent/received, channel id, funding tx, short channel id, node id - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit 864d9108883afa111ddaec5c3e3d4f4ba8382db3 DIR parent 762d8be84f6a254e359f37bcb48fa37c8b837ad0 HTML Author: Janus <ysangkok@gmail.com> Date: Tue, 27 Nov 2018 21:43:28 +0100 qt: channel_details: add more info: sent/received, channel id, funding tx, short channel id, node id Diffstat: M electrum/gui/qt/channel_details.py | 97 +++++++++++++++++++++++++------ M electrum/gui/qt/channels_list.py | 5 ++++- M electrum/lnutil.py | 6 ++++++ M electrum/lnworker.py | 4 ++-- 4 files changed, 91 insertions(+), 21 deletions(-) --- DIR diff --git a/electrum/gui/qt/channel_details.py b/electrum/gui/qt/channel_details.py t@@ -5,10 +5,12 @@ import PyQt5.QtWidgets as QtWidgets import PyQt5.QtCore as QtCore from electrum.i18n import _ -from electrum.lnchan import UpdateAddHtlc +from electrum.lnchan import UpdateAddHtlc, HTLCOwner from electrum.util import bh2u, format_time -from electrum.lnchan import HTLCOwner +from electrum.lnutil import format_short_channel_id, SENT, RECEIVED from electrum.lnaddr import LnAddr, lndecode +from electrum.bitcoin import COIN + if TYPE_CHECKING: from .main_window import ElectrumWindow t@@ -17,22 +19,38 @@ class HTLCItem(QtGui.QStandardItem): super().__init__(*args, **kwargs) self.setEditable(False) -class ChannelDetailsDialog(QtWidgets.QDialog): +class SelectableLabel(QtWidgets.QLabel): + def __init__(self, text=''): + super().__init__(text) + self.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) + +class LinkedLabel(QtWidgets.QLabel): + def __init__(self, text, on_clicked): + super().__init__(text) + self.linkActivated.connect(on_clicked) - def make_inflight(self, lnaddr, i: UpdateAddHtlc): - it = HTLCItem(_('HTLC with ID ') + str(i.htlc_id)) +class ChannelDetailsDialog(QtWidgets.QDialog): + def make_htlc_item(self, i: UpdateAddHtlc, direction: HTLCOwner) -> HTLCItem: + it = HTLCItem(_('Sent HTLC with ID {}' if SENT == direction else 'Received HTLC with ID {}').format(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))]) + return it + + def append_lnaddr(self, it: HTLCItem, lnaddr: LnAddr): 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(_('Amount in sat')), HTLCItem(str(lnaddr.amount * COIN))]) # might have a comma because mSAT! invoice.appendRow([HTLCItem(_('Description')), HTLCItem(dict(lnaddr.tags).get('d', _('N/A')))]) invoice.appendRow([HTLCItem(_('Date')), HTLCItem(format_time(lnaddr.date))]) it.appendRow([invoice]) + + def make_inflight(self, lnaddr, i: UpdateAddHtlc, direction: HTLCOwner) -> HTLCItem: + it = self.make_htlc_item(i, direction) + self.append_lnaddr(it, lnaddr) return it - def make_model(self, htlcs): + def make_model(self, htlcs) -> QtGui.QStandardItemModel: model = QtGui.QStandardItemModel(0, 2) model.setHorizontalHeaderLabels(['HTLC', 'Property value']) parentItem = model.invisibleRootItem() t@@ -41,6 +59,8 @@ class ChannelDetailsDialog(QtWidgets.QDialog): self.keyname_rows = {} + invoices = dict(self.window.wallet.lnworker.invoices) + for keyname, i in folder_types.items(): myFont=QtGui.QFont() myFont.setBold(True) t@@ -51,18 +71,19 @@ class ChannelDetailsDialog(QtWidgets.QDialog): mapping = {} num = 0 if keyname == 'inflight': - for lnaddr, i in htlcs[keyname]: - it = self.make_inflight(lnaddr, i) + for lnaddr, i, direction in htlcs[keyname]: + it = self.make_inflight(lnaddr, i, direction) 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 + it = self.make_htlc_item(i, direction) + hex_pay_hash = bh2u(i.payment_hash) + if hex_pay_hash in invoices: + # if we made the invoice and still have it, we can show more info + invoice = invoices[hex_pay_hash][1] + self.append_lnaddr(it, lndecode(invoice)) self.folders[keyname].appendRow(it) mapping[i.payment_hash] = num num += 1 t@@ -85,25 +106,65 @@ class ChannelDetailsDialog(QtWidgets.QDialog): 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)) + self.folders['inflight'].appendRow(self.make_inflight(lnaddr, htlc, direction)) @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) + self.update_sent_received() + + def update_sent_received(self): + self.sent_label.setText(str(sum(self.chan.settled[SENT]))) + self.received_label.setText(str(sum(self.chan.settled[RECEIVED]))) + + @QtCore.pyqtSlot(str) + def show_tx(self, link_text: str): + funding_tx = self.window.wallet.transactions[self.chan.funding_outpoint.txid] + self.window.show_transaction(funding_tx, tx_desc=_('Funding Transaction')) def __init__(self, window: 'ElectrumWindow', chan_id: bytes): super().__init__(window) + + # initialize instance fields + self.window = window + chan = self.chan = window.wallet.lnworker.channels[chan_id] + self.format = lambda msat: window.format_amount_and_units(msat / 1000) + + # connect signals with slots self.ln_payment_completed.connect(self.do_ln_payment_completed) self.htlc_added.connect(self.do_htlc_added) - htlcs = window.wallet.lnworker._list_invoices(chan_id) - self.format = lambda msat: window.format_amount_and_units(msat / 1000) + + # register callbacks for updating window.network.register_callback(self.ln_payment_completed.emit, ['ln_payment_completed']) window.network.register_callback(self.htlc_added.emit, ['htlc_added']) + + # set attributes of QDialog self.setWindowTitle(_('Channel Details')) self.setMinimumSize(800, 400) + + # add layouts vbox = QtWidgets.QVBoxLayout(self) + form_layout = QtWidgets.QFormLayout(None) + vbox.addLayout(form_layout) + + # add form content + form_layout.addRow(_('Node ID:'), SelectableLabel(bh2u(chan.node_id))) + form_layout.addRow(_('Channel ID:'), SelectableLabel(bh2u(chan.channel_id))) + funding_label_text = f'<a href=click_destination>{chan.funding_outpoint.txid}</a>:{chan.funding_outpoint.output_index}' + form_layout.addRow(_('Funding Outpoint:'), LinkedLabel(funding_label_text, self.show_tx)) + form_layout.addRow(_('Short Channel ID:'), SelectableLabel(format_short_channel_id(chan.short_channel_id))) + self.received_label = SelectableLabel() + form_layout.addRow(_('Received (mSAT):'), self.received_label) + self.sent_label = SelectableLabel() + form_layout.addRow(_('Sent (mSAT):'), self.sent_label) + + # add htlc tree view to vbox (wouldn't scale correctly in QFormLayout) + form_layout.addRow(_('Payments (HTLCs):'), None) w = QtWidgets.QTreeView(self) + htlcs = window.wallet.lnworker._list_invoices(chan_id) w.setModel(self.make_model(htlcs)) - #w.header().setStretchLastSection(False) w.header().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) vbox.addWidget(w) + + # initialize sent/received fields + self.update_sent_received() DIR diff --git a/electrum/gui/qt/channels_list.py b/electrum/gui/qt/channels_list.py t@@ -46,7 +46,10 @@ class ChannelsList(MyTreeWidget): network = self.parent.network lnworker = self.parent.wallet.lnworker menu = QMenu() - channel_id = self.currentItem().data(0, QtCore.Qt.UserRole) + item = self.currentItem() + if not item: + return + channel_id = item.data(0, QtCore.Qt.UserRole) def on_success(txid): self.main_window.show_error('Channel closed' + '\n' + txid) def on_failure(exc_info): DIR diff --git a/electrum/lnutil.py b/electrum/lnutil.py t@@ -664,3 +664,9 @@ class EncumberedTransaction(NamedTuple("EncumberedTransaction", [('name', str), NUM_MAX_HOPS_IN_PAYMENT_PATH = 20 NUM_MAX_EDGES_IN_PAYMENT_PATH = NUM_MAX_HOPS_IN_PAYMENT_PATH + 1 +def format_short_channel_id(short_channel_id: Optional[bytes]): + if not short_channel_id: + return _('Not yet available') + return str(int.from_bytes(short_channel_id[:3], 'big')) \ + + 'x' + str(int.from_bytes(short_channel_id[3:6], 'big')) \ + + 'x' + str(int.from_bytes(short_channel_id[6:], 'big')) DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py t@@ -128,7 +128,7 @@ class LNWorker(PrintError): if report['inflight']: yield 'Outgoing payments in progress:' yield '------------------------------' - for addr, htlc in report['inflight']: + for addr, htlc, direction in report['inflight']: yield str(addr) yield str(htlc) yield '' t@@ -162,7 +162,7 @@ class LNWorker(PrintError): htlc = self.find_htlc_for_addr(addr, None if chan_id is None else [chan_id]) if not htlc: self.print_error('Warning, in flight HTLC not found in any channel') - inflight.append((addr, htlc)) + inflight.append((addr, htlc, direction)) return {'settled': settled, 'unsettled': unsettled, 'inflight': inflight} def find_htlc_for_addr(self, addr, whitelist=None):