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