Add 'channels' parameter to create invoice and pay.
Add rebalance dialog to GUI
This commit is contained in:
@@ -202,8 +202,18 @@ class ChannelsList(MyTreeView):
|
|||||||
menu.addAction(_("Import channel backup"), lambda: self.parent.do_process_from_text_channel_backup())
|
menu.addAction(_("Import channel backup"), lambda: self.parent.do_process_from_text_channel_backup())
|
||||||
menu.exec_(self.viewport().mapToGlobal(position))
|
menu.exec_(self.viewport().mapToGlobal(position))
|
||||||
return
|
return
|
||||||
multi_select = len(selected) > 1
|
if len(selected) == 2:
|
||||||
if multi_select:
|
idx1 = selected[0]
|
||||||
|
idx2 = selected[1]
|
||||||
|
channel_id1 = idx1.sibling(idx1.row(), self.Columns.NODE_ALIAS).data(ROLE_CHANNEL_ID)
|
||||||
|
channel_id2 = idx2.sibling(idx2.row(), self.Columns.NODE_ALIAS).data(ROLE_CHANNEL_ID)
|
||||||
|
chan1 = self.lnworker.channels.get(channel_id1)
|
||||||
|
chan2 = self.lnworker.channels.get(channel_id2)
|
||||||
|
if chan1 and chan2:
|
||||||
|
menu.addAction(_("Rebalance"), lambda: self.parent.rebalance_dialog(chan1, chan2))
|
||||||
|
menu.exec_(self.viewport().mapToGlobal(position))
|
||||||
|
return
|
||||||
|
elif len(selected) > 2:
|
||||||
return
|
return
|
||||||
idx = self.indexAt(position)
|
idx = self.indexAt(position)
|
||||||
if not idx.isValid():
|
if not idx.isValid():
|
||||||
|
|||||||
@@ -3652,3 +3652,43 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
|||||||
_("Electrum will now exit."))
|
_("Electrum will now exit."))
|
||||||
self.showing_cert_mismatch_error = False
|
self.showing_cert_mismatch_error = False
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
|
def rebalance_dialog(self, chan1, chan2, amount_sat=None):
|
||||||
|
d = WindowModalDialog(self, _("Rebalance channels"))
|
||||||
|
d.reverse = False
|
||||||
|
vbox = QVBoxLayout(d)
|
||||||
|
vbox.addWidget(WWLabel(_('Rebalance your channels in order to increase your sending or receiving capacity') + ':'))
|
||||||
|
grid = QGridLayout()
|
||||||
|
amount_e = BTCAmountEdit(self.get_decimal_point)
|
||||||
|
amount_e.setAmount(amount_sat)
|
||||||
|
rev_button = QPushButton(u'\U000021c4')
|
||||||
|
label1 = QLabel('')
|
||||||
|
label2 = QLabel('')
|
||||||
|
def update():
|
||||||
|
if d.reverse:
|
||||||
|
d.chan_from = chan2
|
||||||
|
d.chan_to = chan1
|
||||||
|
else:
|
||||||
|
d.chan_from = chan1
|
||||||
|
d.chan_to = chan2
|
||||||
|
label1.setText(d.chan_from.short_id_for_GUI())
|
||||||
|
label2.setText(d.chan_to.short_id_for_GUI())
|
||||||
|
update()
|
||||||
|
def on_reverse():
|
||||||
|
d.reverse = not d.reverse
|
||||||
|
update()
|
||||||
|
rev_button.clicked.connect(on_reverse)
|
||||||
|
grid.addWidget(QLabel(_("From")), 0, 0)
|
||||||
|
grid.addWidget(label1, 0, 1)
|
||||||
|
grid.addWidget(QLabel(_("To")), 1, 0)
|
||||||
|
grid.addWidget(label2, 1, 1)
|
||||||
|
grid.addWidget(QLabel(_("Amount")), 2, 0)
|
||||||
|
grid.addWidget(amount_e, 2, 1)
|
||||||
|
grid.addWidget(rev_button, 0, 2)
|
||||||
|
vbox.addLayout(grid)
|
||||||
|
vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
|
||||||
|
if not d.exec_():
|
||||||
|
return
|
||||||
|
amount_msat = amount_e.get_amount() * 1000
|
||||||
|
coro = self.wallet.lnworker.rebalance_channels(d.chan_from, d.chan_to, amount_msat=amount_msat)
|
||||||
|
self.run_coroutine_from_thread(coro)
|
||||||
|
|||||||
@@ -1103,7 +1103,9 @@ class LNWallet(LNWorker):
|
|||||||
self, invoice: str, *,
|
self, invoice: str, *,
|
||||||
amount_msat: int = None,
|
amount_msat: int = None,
|
||||||
attempts: int = None, # used only in unit tests
|
attempts: int = None, # used only in unit tests
|
||||||
full_path: LNPaymentPath = None) -> Tuple[bool, List[HtlcLog]]:
|
full_path: LNPaymentPath = None,
|
||||||
|
channels: Optional[Sequence[Channel]] = None,
|
||||||
|
) -> Tuple[bool, List[HtlcLog]]:
|
||||||
|
|
||||||
lnaddr = self._check_invoice(invoice, amount_msat=amount_msat)
|
lnaddr = self._check_invoice(invoice, amount_msat=amount_msat)
|
||||||
min_cltv_expiry = lnaddr.get_min_final_cltv_expiry()
|
min_cltv_expiry = lnaddr.get_min_final_cltv_expiry()
|
||||||
@@ -1138,7 +1140,8 @@ class LNWallet(LNWorker):
|
|||||||
r_tags=r_tags,
|
r_tags=r_tags,
|
||||||
invoice_features=invoice_features,
|
invoice_features=invoice_features,
|
||||||
attempts=attempts,
|
attempts=attempts,
|
||||||
full_path=full_path)
|
full_path=full_path,
|
||||||
|
channels=channels)
|
||||||
success = True
|
success = True
|
||||||
except PaymentFailure as e:
|
except PaymentFailure as e:
|
||||||
self.logger.info(f'payment failure: {e!r}')
|
self.logger.info(f'payment failure: {e!r}')
|
||||||
@@ -1167,7 +1170,9 @@ class LNWallet(LNWorker):
|
|||||||
full_path: LNPaymentPath = None,
|
full_path: LNPaymentPath = None,
|
||||||
fwd_trampoline_onion=None,
|
fwd_trampoline_onion=None,
|
||||||
fwd_trampoline_fee=None,
|
fwd_trampoline_fee=None,
|
||||||
fwd_trampoline_cltv_delta=None) -> None:
|
fwd_trampoline_cltv_delta=None,
|
||||||
|
channels: Optional[Sequence[Channel]] = None,
|
||||||
|
) -> None:
|
||||||
|
|
||||||
if fwd_trampoline_onion:
|
if fwd_trampoline_onion:
|
||||||
# todo: compare to the fee of the actual route we found
|
# todo: compare to the fee of the actual route we found
|
||||||
@@ -1204,7 +1209,8 @@ class LNWallet(LNWorker):
|
|||||||
payment_secret=payment_secret,
|
payment_secret=payment_secret,
|
||||||
trampoline_fee_level=trampoline_fee_level,
|
trampoline_fee_level=trampoline_fee_level,
|
||||||
use_two_trampolines=use_two_trampolines,
|
use_two_trampolines=use_two_trampolines,
|
||||||
fwd_trampoline_onion=fwd_trampoline_onion
|
fwd_trampoline_onion=fwd_trampoline_onion,
|
||||||
|
channels=channels,
|
||||||
)
|
)
|
||||||
# 2. send htlcs
|
# 2. send htlcs
|
||||||
async for route, amount_msat, total_msat, amount_receiver_msat, cltv_delta, bucket_payment_secret, trampoline_onion in routes:
|
async for route, amount_msat, total_msat, amount_receiver_msat, cltv_delta, bucket_payment_secret, trampoline_onion in routes:
|
||||||
@@ -1493,7 +1499,9 @@ class LNWallet(LNWorker):
|
|||||||
trampoline_fee_level: int,
|
trampoline_fee_level: int,
|
||||||
use_two_trampolines: bool,
|
use_two_trampolines: bool,
|
||||||
fwd_trampoline_onion=None,
|
fwd_trampoline_onion=None,
|
||||||
full_path: LNPaymentPath = None) -> AsyncGenerator[Tuple[LNPaymentRoute, int], None]:
|
full_path: LNPaymentPath = None,
|
||||||
|
channels: Optional[Sequence[Channel]] = None,
|
||||||
|
) -> AsyncGenerator[Tuple[LNPaymentRoute, int], None]:
|
||||||
|
|
||||||
"""Creates multiple routes for splitting a payment over the available
|
"""Creates multiple routes for splitting a payment over the available
|
||||||
private channels.
|
private channels.
|
||||||
@@ -1503,8 +1511,12 @@ class LNWallet(LNWorker):
|
|||||||
invoice_features = LnFeatures(invoice_features)
|
invoice_features = LnFeatures(invoice_features)
|
||||||
trampoline_features = LnFeatures.VAR_ONION_OPT
|
trampoline_features = LnFeatures.VAR_ONION_OPT
|
||||||
local_height = self.network.get_local_height()
|
local_height = self.network.get_local_height()
|
||||||
my_active_channels = [chan for chan in self.channels.values() if
|
if channels:
|
||||||
chan.is_active() and not chan.is_frozen_for_sending()]
|
my_active_channels = channels
|
||||||
|
else:
|
||||||
|
my_active_channels = [
|
||||||
|
chan for chan in self.channels.values() if
|
||||||
|
chan.is_active() and not chan.is_frozen_for_sending()]
|
||||||
try:
|
try:
|
||||||
self.logger.info("trying single-part payment")
|
self.logger.info("trying single-part payment")
|
||||||
# try to send over a single channel
|
# try to send over a single channel
|
||||||
@@ -1760,11 +1772,12 @@ class LNWallet(LNWorker):
|
|||||||
expiry: int,
|
expiry: int,
|
||||||
fallback_address: str,
|
fallback_address: str,
|
||||||
write_to_disk: bool = True,
|
write_to_disk: bool = True,
|
||||||
|
channels: Optional[Sequence[Channel]] = None,
|
||||||
) -> Tuple[LnAddr, str]:
|
) -> Tuple[LnAddr, str]:
|
||||||
|
|
||||||
assert amount_msat is None or amount_msat > 0
|
assert amount_msat is None or amount_msat > 0
|
||||||
timestamp = int(time.time())
|
timestamp = int(time.time())
|
||||||
routing_hints, trampoline_hints = self.calc_routing_hints_for_invoice(amount_msat)
|
routing_hints, trampoline_hints = self.calc_routing_hints_for_invoice(amount_msat, channels=channels)
|
||||||
if not routing_hints:
|
if not routing_hints:
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
"Warning. No routing hints added to invoice. "
|
"Warning. No routing hints added to invoice. "
|
||||||
@@ -1993,11 +2006,12 @@ class LNWallet(LNWorker):
|
|||||||
self.set_invoice_status(key, PR_UNPAID)
|
self.set_invoice_status(key, PR_UNPAID)
|
||||||
util.trigger_callback('payment_failed', self.wallet, key, '')
|
util.trigger_callback('payment_failed', self.wallet, key, '')
|
||||||
|
|
||||||
def calc_routing_hints_for_invoice(self, amount_msat: Optional[int]):
|
def calc_routing_hints_for_invoice(self, amount_msat: Optional[int], channels=None):
|
||||||
"""calculate routing hints (BOLT-11 'r' field)"""
|
"""calculate routing hints (BOLT-11 'r' field)"""
|
||||||
routing_hints = []
|
routing_hints = []
|
||||||
channels = list(self.get_channels_to_include_in_invoice(amount_msat))
|
if channels is None:
|
||||||
random.shuffle(channels) # let's not leak channel order
|
channels = list(self.get_channels_to_include_in_invoice(amount_msat))
|
||||||
|
random.shuffle(channels) # let's not leak channel order
|
||||||
scid_to_my_channels = {chan.short_channel_id: chan for chan in channels
|
scid_to_my_channels = {chan.short_channel_id: chan for chan in channels
|
||||||
if chan.short_channel_id is not None}
|
if chan.short_channel_id is not None}
|
||||||
for chan in channels:
|
for chan in channels:
|
||||||
@@ -2119,6 +2133,17 @@ class LNWallet(LNWorker):
|
|||||||
)
|
)
|
||||||
return Decimal(can_receive_msat) / 1000
|
return Decimal(can_receive_msat) / 1000
|
||||||
|
|
||||||
|
async def rebalance_channels(self, chan1, chan2, amount_msat):
|
||||||
|
lnaddr, invoice = self.create_invoice(
|
||||||
|
amount_msat=amount_msat,
|
||||||
|
message='rebalance',
|
||||||
|
expiry=3600,
|
||||||
|
fallback_address=None,
|
||||||
|
channels = [chan2]
|
||||||
|
)
|
||||||
|
await self.pay_invoice(
|
||||||
|
invoice, channels=[chan1])
|
||||||
|
|
||||||
def num_sats_can_receive_no_mpp(self) -> Decimal:
|
def num_sats_can_receive_no_mpp(self) -> Decimal:
|
||||||
with self.lock:
|
with self.lock:
|
||||||
channels = [
|
channels = [
|
||||||
|
|||||||
Reference in New Issue
Block a user