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