the while loop in `suggest_node_channel_open()` of lnrater would not break if there are no "good" peers available available. As a result the gui blocks and electrum has to be killed. This can happen for example on signet. This removes the tested pk from the list of candidates so each candidate gets tested only once.
159 lines
6.7 KiB
Python
159 lines
6.7 KiB
Python
from typing import TYPE_CHECKING, Optional
|
|
from PyQt6.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton, QComboBox, QLineEdit, QHBoxLayout
|
|
|
|
import electrum_ecc as ecc
|
|
|
|
from electrum.i18n import _
|
|
from electrum.lnutil import MIN_FUNDING_SAT
|
|
from electrum.lnworker import hardcoded_trampoline_nodes
|
|
from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates
|
|
from electrum.fee_policy import FeePolicy
|
|
|
|
from .util import (WindowModalDialog, Buttons, OkButton, CancelButton,
|
|
EnterButton, WWLabel, char_width_in_lineedit)
|
|
from .amountedit import BTCAmountEdit
|
|
from .my_treeview import create_toolbar_with_menu
|
|
|
|
if TYPE_CHECKING:
|
|
from .main_window import ElectrumWindow
|
|
|
|
|
|
class NewChannelDialog(WindowModalDialog):
|
|
|
|
def __init__(self, window: 'ElectrumWindow', amount_sat: Optional[int] = None, min_amount_sat: Optional[int] = None):
|
|
WindowModalDialog.__init__(self, window, _('Open Channel'))
|
|
self.window = window
|
|
self.network = window.network
|
|
self.config = window.config
|
|
self.lnworker = self.window.wallet.lnworker
|
|
self.trampolines = hardcoded_trampoline_nodes()
|
|
self.trampoline_names = list(self.trampolines.keys())
|
|
self.min_amount_sat = min_amount_sat or MIN_FUNDING_SAT
|
|
vbox = QVBoxLayout(self)
|
|
toolbar, menu = create_toolbar_with_menu(self.config, '')
|
|
menu.addConfig(
|
|
self.config.cv.LIGHTNING_USE_RECOVERABLE_CHANNELS,
|
|
checked=self.lnworker.has_recoverable_channels(),
|
|
).setEnabled(self.lnworker.can_have_recoverable_channels())
|
|
vbox.addLayout(toolbar)
|
|
msg = _('Choose a remote node and an amount to fund the channel.')
|
|
msg += '\n' + _('Minimum required amount: {}').format(self.window.format_amount_and_units(self.min_amount_sat))
|
|
vbox.addWidget(WWLabel(msg))
|
|
if self.network.channel_db:
|
|
vbox.addWidget(QLabel(_('Enter Remote Node ID or connection string or invoice')))
|
|
self.remote_nodeid = QLineEdit()
|
|
self.remote_nodeid.setMinimumWidth(700)
|
|
self.suggest_button = QPushButton(self, text=_('Suggest Peer'))
|
|
self.suggest_button.clicked.connect(self.on_suggest)
|
|
else:
|
|
self.trampoline_combo = QComboBox()
|
|
self.trampoline_combo.addItems(self.trampoline_names)
|
|
self.trampoline_combo.setCurrentIndex(1)
|
|
self.amount_e = BTCAmountEdit(self.window.get_decimal_point)
|
|
self.amount_e.setAmount(amount_sat)
|
|
|
|
btn_width = 10 * char_width_in_lineedit()
|
|
self.min_button = EnterButton(_("Min"), self.spend_min)
|
|
self.min_button.setEnabled(bool(self.min_amount_sat))
|
|
self.min_button.setFixedWidth(btn_width)
|
|
self.max_button = EnterButton(_("Max"), self.spend_max)
|
|
self.max_button.setFixedWidth(btn_width)
|
|
self.max_button.setCheckable(True)
|
|
self.clear_button = QPushButton(self, text=_('Clear'))
|
|
self.clear_button.clicked.connect(self.on_clear)
|
|
self.clear_button.setFixedWidth(btn_width)
|
|
h = QGridLayout()
|
|
if self.network.channel_db:
|
|
h.addWidget(QLabel(_('Remote Node ID')), 0, 0)
|
|
h.addWidget(self.remote_nodeid, 0, 1, 1, 4)
|
|
h.addWidget(self.suggest_button, 0, 5)
|
|
else:
|
|
h.addWidget(QLabel(_('Remote Node')), 0, 0)
|
|
h.addWidget(self.trampoline_combo, 0, 1, 1, 4)
|
|
h.addWidget(QLabel('Amount'), 2, 0)
|
|
|
|
amt_hbox = QHBoxLayout()
|
|
amt_hbox.setContentsMargins(0, 0, 0, 0)
|
|
amt_hbox.addWidget(self.amount_e)
|
|
amt_hbox.addWidget(self.min_button)
|
|
amt_hbox.addWidget(self.max_button)
|
|
amt_hbox.addWidget(self.clear_button)
|
|
amt_hbox.addStretch()
|
|
h.addLayout(amt_hbox, 2, 1, 1, 4)
|
|
|
|
vbox.addLayout(h)
|
|
vbox.addStretch()
|
|
ok_button = OkButton(self)
|
|
ok_button.setDefault(True)
|
|
vbox.addLayout(Buttons(CancelButton(self), ok_button))
|
|
|
|
def on_suggest(self):
|
|
self.network.start_gossip()
|
|
nodeid = (self.lnworker.suggest_peer() or b"").hex()
|
|
if not nodeid:
|
|
self.remote_nodeid.setText("")
|
|
self.remote_nodeid.setPlaceholderText(
|
|
_("Couldn't find suitable peer yet, try again later.")
|
|
)
|
|
else:
|
|
self.remote_nodeid.setText(nodeid)
|
|
self.remote_nodeid.repaint() # macOS hack for #6269
|
|
|
|
def on_clear(self):
|
|
self.amount_e.setText('')
|
|
self.amount_e.setFrozen(False)
|
|
self.amount_e.repaint() # macOS hack for #6269
|
|
if self.network.channel_db:
|
|
self.remote_nodeid.setText('')
|
|
self.remote_nodeid.repaint() # macOS hack for #6269
|
|
self.max_button.setChecked(False)
|
|
self.max_button.repaint() # macOS hack for #6269
|
|
|
|
def spend_min(self):
|
|
self.max_button.setChecked(False)
|
|
self.amount_e.setFrozen(False)
|
|
self.amount_e.setAmount(self.min_amount_sat)
|
|
|
|
def spend_max(self):
|
|
self.amount_e.setFrozen(self.max_button.isChecked())
|
|
if not self.max_button.isChecked():
|
|
return
|
|
dummy_nodeid = ecc.GENERATOR.get_public_key_bytes(compressed=True)
|
|
make_tx = self.window.mktx_for_open_channel(funding_sat='!', node_id=dummy_nodeid)
|
|
try:
|
|
tx = make_tx(FeePolicy(self.config.FEE_POLICY))
|
|
except (NotEnoughFunds, NoDynamicFeeEstimates) as e:
|
|
self.max_button.setChecked(False)
|
|
self.amount_e.setFrozen(False)
|
|
self.window.show_error(str(e))
|
|
return
|
|
amount = tx.output_value()
|
|
amount = min(amount, self.config.LIGHTNING_MAX_FUNDING_SAT)
|
|
self.amount_e.setAmount(amount)
|
|
|
|
def run(self):
|
|
if not self.exec():
|
|
return
|
|
if self.max_button.isChecked() and self.amount_e.get_amount() < self.config.LIGHTNING_MAX_FUNDING_SAT:
|
|
# if 'max' enabled and amount is strictly less than max allowed,
|
|
# that means we have fewer coins than max allowed, and hence we can
|
|
# spend all coins
|
|
funding_sat = '!'
|
|
else:
|
|
funding_sat = self.amount_e.get_amount()
|
|
if not funding_sat:
|
|
return
|
|
if funding_sat != '!':
|
|
if self.min_amount_sat and funding_sat < self.min_amount_sat:
|
|
self.window.show_error(_('Amount too low'))
|
|
return
|
|
if self.network.channel_db:
|
|
connect_str = str(self.remote_nodeid.text()).strip()
|
|
else:
|
|
name = self.trampoline_names[self.trampoline_combo.currentIndex()]
|
|
connect_str = str(self.trampolines[name])
|
|
if not connect_str:
|
|
return
|
|
self.window.open_channel(connect_str, funding_sat, 0)
|
|
return True
|