URI: 
       tchannel_details.py - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
       tchannel_details.py (9518B)
       ---
            1 from typing import TYPE_CHECKING
            2 
            3 import PyQt5.QtGui as QtGui
            4 import PyQt5.QtWidgets as QtWidgets
            5 import PyQt5.QtCore as QtCore
            6 from PyQt5.QtWidgets import QLabel, QLineEdit
            7 
            8 from electrum import util
            9 from electrum.i18n import _
           10 from electrum.util import bh2u, format_time
           11 from electrum.lnutil import format_short_channel_id, LOCAL, REMOTE, UpdateAddHtlc, Direction
           12 from electrum.lnchannel import htlcsum, Channel, AbstractChannel
           13 from electrum.lnaddr import LnAddr, lndecode
           14 from electrum.bitcoin import COIN
           15 from electrum.wallet import Abstract_Wallet
           16 
           17 from .util import Buttons, CloseButton, ButtonsLineEdit
           18 
           19 if TYPE_CHECKING:
           20     from .main_window import ElectrumWindow
           21 
           22 class HTLCItem(QtGui.QStandardItem):
           23     def __init__(self, *args, **kwargs):
           24         super().__init__(*args, **kwargs)
           25         self.setEditable(False)
           26 
           27 class SelectableLabel(QtWidgets.QLabel):
           28     def __init__(self, text=''):
           29         super().__init__(text)
           30         self.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
           31 
           32 class LinkedLabel(QtWidgets.QLabel):
           33     def __init__(self, text, on_clicked):
           34         super().__init__(text)
           35         self.linkActivated.connect(on_clicked)
           36 
           37 class ChannelDetailsDialog(QtWidgets.QDialog):
           38     def make_htlc_item(self, i: UpdateAddHtlc, direction: Direction) -> HTLCItem:
           39         it = HTLCItem(_('Sent HTLC with ID {}' if Direction.SENT == direction else 'Received HTLC with ID {}').format(i.htlc_id))
           40         it.appendRow([HTLCItem(_('Amount')),HTLCItem(self.format_msat(i.amount_msat))])
           41         it.appendRow([HTLCItem(_('CLTV expiry')),HTLCItem(str(i.cltv_expiry))])
           42         it.appendRow([HTLCItem(_('Payment hash')),HTLCItem(bh2u(i.payment_hash))])
           43         return it
           44 
           45     def make_model(self, htlcs) -> QtGui.QStandardItemModel:
           46         model = QtGui.QStandardItemModel(0, 2)
           47         model.setHorizontalHeaderLabels(['HTLC', 'Property value'])
           48         parentItem = model.invisibleRootItem()
           49         folder_types = {
           50             'settled': _('Fulfilled HTLCs'),
           51             'inflight': _('HTLCs in current commitment transaction'),
           52             'failed': _('Failed HTLCs'),
           53         }
           54         self.folders = {}
           55         self.keyname_rows = {}
           56 
           57         for keyname, i in folder_types.items():
           58             myFont=QtGui.QFont()
           59             myFont.setBold(True)
           60             folder = HTLCItem(i)
           61             folder.setFont(myFont)
           62             parentItem.appendRow(folder)
           63             self.folders[keyname] = folder
           64             mapping = {}
           65             num = 0
           66             for item in htlcs:
           67                 pay_hash, chan_id, i, direction, status = item
           68                 if status != keyname:
           69                     continue
           70                 it = self.make_htlc_item(i, direction)
           71                 self.folders[keyname].appendRow(it)
           72                 mapping[i.payment_hash] = num
           73                 num += 1
           74             self.keyname_rows[keyname] = mapping
           75         return model
           76 
           77     def move(self, fro: str, to: str, payment_hash: bytes):
           78         assert fro != to
           79         row_idx = self.keyname_rows[fro].pop(payment_hash)
           80         row = self.folders[fro].takeRow(row_idx)
           81         self.folders[to].appendRow(row)
           82         dest_mapping = self.keyname_rows[to]
           83         dest_mapping[payment_hash] = len(dest_mapping)
           84 
           85     htlc_fulfilled = QtCore.pyqtSignal(str, bytes, bytes)
           86     htlc_failed = QtCore.pyqtSignal(str, bytes, bytes)
           87     htlc_added = QtCore.pyqtSignal(str, Channel, UpdateAddHtlc, Direction)
           88     state_changed = QtCore.pyqtSignal(str, Abstract_Wallet, AbstractChannel)
           89 
           90     @QtCore.pyqtSlot(str, Abstract_Wallet, AbstractChannel)
           91     def do_state_changed(self, wallet, chan):
           92         if wallet != self.wallet:
           93             return
           94         if chan == self.chan:
           95             self.update()
           96 
           97     @QtCore.pyqtSlot(str, Channel, UpdateAddHtlc, Direction)
           98     def on_htlc_added(self, evtname, chan, htlc, direction):
           99         if chan != self.chan:
          100             return
          101         mapping = self.keyname_rows['inflight']
          102         mapping[htlc.payment_hash] = len(mapping)
          103         self.folders['inflight'].appendRow(self.make_htlc_item(htlc, direction))
          104 
          105     @QtCore.pyqtSlot(str, bytes, bytes)
          106     def on_htlc_fulfilled(self, evtname, payment_hash, chan_id):
          107         if chan_id != self.chan.channel_id:
          108             return
          109         self.move('inflight', 'settled', payment_hash)
          110         self.update()
          111 
          112     @QtCore.pyqtSlot(str, bytes, bytes)
          113     def on_htlc_failed(self, evtname, payment_hash, chan_id):
          114         if chan_id != self.chan.channel_id:
          115             return
          116         self.move('inflight', 'failed', payment_hash)
          117         self.update()
          118 
          119     def update(self):
          120         self.can_send_label.setText(self.format_msat(self.chan.available_to_spend(LOCAL)))
          121         self.can_receive_label.setText(self.format_msat(self.chan.available_to_spend(REMOTE)))
          122         self.sent_label.setText(self.format_msat(self.chan.total_msat(Direction.SENT)))
          123         self.received_label.setText(self.format_msat(self.chan.total_msat(Direction.RECEIVED)))
          124 
          125     @QtCore.pyqtSlot(str)
          126     def show_tx(self, link_text: str):
          127         funding_tx = self.wallet.db.get_transaction(self.chan.funding_outpoint.txid)
          128         self.window.show_transaction(funding_tx, tx_desc=_('Funding Transaction'))
          129 
          130     def __init__(self, window: 'ElectrumWindow', chan_id: bytes):
          131         super().__init__(window)
          132 
          133         # initialize instance fields
          134         self.window = window
          135         self.wallet = window.wallet
          136         chan = self.chan = window.wallet.lnworker.channels[chan_id]
          137         self.format_msat = lambda msat: window.format_amount_and_units(msat / 1000)
          138 
          139         # connect signals with slots
          140         self.htlc_fulfilled.connect(self.on_htlc_fulfilled)
          141         self.htlc_failed.connect(self.on_htlc_failed)
          142         self.state_changed.connect(self.do_state_changed)
          143         self.htlc_added.connect(self.on_htlc_added)
          144 
          145         # register callbacks for updating
          146         util.register_callback(self.htlc_fulfilled.emit, ['htlc_fulfilled'])
          147         util.register_callback(self.htlc_failed.emit, ['htlc_failed'])
          148         util.register_callback(self.htlc_added.emit, ['htlc_added'])
          149         util.register_callback(self.state_changed.emit, ['channel'])
          150 
          151         # set attributes of QDialog
          152         self.setWindowTitle(_('Channel Details'))
          153         self.setMinimumSize(800, 400)
          154 
          155         # add layouts
          156         vbox = QtWidgets.QVBoxLayout(self)
          157         vbox.addWidget(QLabel(_('Remote Node ID:')))
          158         remote_id_e = ButtonsLineEdit(bh2u(chan.node_id))
          159         remote_id_e.addCopyButton(self.window.app)
          160         remote_id_e.setReadOnly(True)
          161         vbox.addWidget(remote_id_e)
          162         funding_label_text = f'<a href=click_destination>{chan.funding_outpoint.txid}</a>:{chan.funding_outpoint.output_index}'
          163         vbox.addWidget(QLabel(_('Funding Outpoint:')))
          164         vbox.addWidget(LinkedLabel(funding_label_text, self.show_tx))
          165 
          166         form_layout = QtWidgets.QFormLayout(None)
          167         # add form content
          168         form_layout.addRow(_('Channel ID:'), SelectableLabel(f"{chan.channel_id.hex()} (Short: {chan.short_channel_id})"))
          169         form_layout.addRow(_('State:'), SelectableLabel(chan.get_state_for_GUI()))
          170         self.initiator = 'Local' if chan.constraints.is_initiator else 'Remote'
          171         form_layout.addRow(_('Initiator:'), SelectableLabel(self.initiator))
          172         self.capacity = self.window.format_amount_and_units(chan.get_capacity())
          173         form_layout.addRow(_('Capacity:'), SelectableLabel(self.capacity))
          174         self.can_send_label = SelectableLabel()
          175         self.can_receive_label = SelectableLabel()
          176         form_layout.addRow(_('Can send:'), self.can_send_label)
          177         form_layout.addRow(_('Can receive:'), self.can_receive_label)
          178         self.received_label = SelectableLabel()
          179         form_layout.addRow(_('Received:'), self.received_label)
          180         self.sent_label = SelectableLabel()
          181         form_layout.addRow(_('Sent:'), self.sent_label)
          182         #self.htlc_minimum_msat = SelectableLabel(str(chan.config[REMOTE].htlc_minimum_msat))
          183         #form_layout.addRow(_('Minimum HTLC value accepted by peer (mSAT):'), self.htlc_minimum_msat)
          184         #self.max_htlcs = SelectableLabel(str(chan.config[REMOTE].max_accepted_htlcs))
          185         #form_layout.addRow(_('Maximum number of concurrent HTLCs accepted by peer:'), self.max_htlcs)
          186         #self.max_htlc_value = SelectableLabel(self.window.format_amount_and_units(chan.config[REMOTE].max_htlc_value_in_flight_msat / 1000))
          187         #form_layout.addRow(_('Maximum value of in-flight HTLCs accepted by peer:'), self.max_htlc_value)
          188         self.dust_limit = SelectableLabel(self.window.format_amount_and_units(chan.config[REMOTE].dust_limit_sat))
          189         form_layout.addRow(_('Remote dust limit:'), self.dust_limit)
          190         self.remote_reserve = self.window.format_amount_and_units(chan.config[REMOTE].reserve_sat)
          191         form_layout.addRow(_('Remote reserve:'), SelectableLabel(self.remote_reserve))
          192         vbox.addLayout(form_layout)
          193 
          194         # add htlc tree view to vbox (wouldn't scale correctly in QFormLayout)
          195         vbox.addWidget(QLabel(_('Payments (HTLCs):')))
          196         w = QtWidgets.QTreeView(self)
          197         htlc_dict = chan.get_payments()
          198         htlc_list = []
          199         for rhash, _list in htlc_dict.items():
          200             for _tuple in _list:
          201                 htlc_list.append((rhash.hex(),) + _tuple)
          202         w.setModel(self.make_model(htlc_list))
          203         w.header().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents)
          204         vbox.addWidget(w)
          205         vbox.addLayout(Buttons(CloseButton(self)))
          206         # initialize sent/received fields
          207         self.update()