mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-09-03 12:30:07 +00:00
lnchannel: implement "freezing" channels (for sending)
and expose it in Qt GUI
This commit is contained in:
parent
9c8d2be638
commit
deb50e7ec3
2 changed files with 61 additions and 10 deletions
|
@ -1,12 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import traceback
|
||||
from enum import IntEnum
|
||||
from typing import Sequence, Optional
|
||||
|
||||
from PyQt5 import QtCore, QtGui
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import (QMenu, QHBoxLayout, QLabel, QVBoxLayout, QGridLayout, QLineEdit,
|
||||
QPushButton, QAbstractItemView)
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtGui import QFont, QStandardItem, QBrush
|
||||
|
||||
from electrum.util import bh2u, NotEnoughFunds, NoDynamicFeeEstimates
|
||||
from electrum.i18n import _
|
||||
|
@ -15,7 +16,7 @@ from electrum.wallet import Abstract_Wallet
|
|||
from electrum.lnutil import LOCAL, REMOTE, format_short_channel_id, LN_MAX_FUNDING_SAT
|
||||
|
||||
from .util import (MyTreeView, WindowModalDialog, Buttons, OkButton, CancelButton,
|
||||
EnterButton, WaitingDialog, MONOSPACE_FONT)
|
||||
EnterButton, WaitingDialog, MONOSPACE_FONT, ColorScheme)
|
||||
from .amountedit import BTCAmountEdit, FreezableLineEdit
|
||||
|
||||
|
||||
|
@ -43,6 +44,8 @@ class ChannelsList(MyTreeView):
|
|||
Columns.CHANNEL_STATUS: _('Status'),
|
||||
}
|
||||
|
||||
_default_item_bg_brush = None # type: Optional[QBrush]
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent, self.create_menu, stretch_column=self.Columns.NODE_ID,
|
||||
editable_columns=[])
|
||||
|
@ -141,6 +144,12 @@ class ChannelsList(MyTreeView):
|
|||
cc = self.add_copy_menu(menu, idx)
|
||||
cc.addAction(_("Long Channel ID"), lambda: self.place_text_on_clipboard(channel_id.hex(),
|
||||
title=_("Long Channel ID")))
|
||||
|
||||
if not chan.is_frozen():
|
||||
menu.addAction(_("Freeze"), lambda: chan.set_frozen(True))
|
||||
else:
|
||||
menu.addAction(_("Unfreeze"), lambda: chan.set_frozen(False))
|
||||
|
||||
funding_tx = self.parent.wallet.db.get_transaction(chan.funding_outpoint.txid)
|
||||
if funding_tx:
|
||||
menu.addAction(_("View funding transaction"), lambda: self.parent.show_transaction(funding_tx))
|
||||
|
@ -169,9 +178,12 @@ class ChannelsList(MyTreeView):
|
|||
return
|
||||
for row in range(self.model().rowCount()):
|
||||
item = self.model().item(row, self.Columns.NODE_ID)
|
||||
if item.data(ROLE_CHANNEL_ID) == chan.channel_id:
|
||||
for column, v in enumerate(self.format_fields(chan)):
|
||||
self.model().item(row, column).setData(v, QtCore.Qt.DisplayRole)
|
||||
if item.data(ROLE_CHANNEL_ID) != chan.channel_id:
|
||||
continue
|
||||
for column, v in enumerate(self.format_fields(chan)):
|
||||
self.model().item(row, column).setData(v, QtCore.Qt.DisplayRole)
|
||||
items = [self.model().item(row, column) for column in self.Columns]
|
||||
self._update_chan_frozen_bg(chan=chan, items=items)
|
||||
self.update_can_send(lnworker)
|
||||
|
||||
@QtCore.pyqtSlot(Abstract_Wallet)
|
||||
|
@ -187,13 +199,31 @@ class ChannelsList(MyTreeView):
|
|||
for chan in lnworker.channels.values():
|
||||
items = [QtGui.QStandardItem(x) for x in self.format_fields(chan)]
|
||||
self.set_editability(items)
|
||||
if self._default_item_bg_brush is None:
|
||||
self._default_item_bg_brush = items[self.Columns.NODE_ID].background()
|
||||
items[self.Columns.NODE_ID].setData(chan.channel_id, ROLE_CHANNEL_ID)
|
||||
items[self.Columns.NODE_ID].setFont(QFont(MONOSPACE_FONT))
|
||||
items[self.Columns.LOCAL_BALANCE].setFont(QFont(MONOSPACE_FONT))
|
||||
items[self.Columns.REMOTE_BALANCE].setFont(QFont(MONOSPACE_FONT))
|
||||
self._update_chan_frozen_bg(chan=chan, items=items)
|
||||
self.model().insertRow(0, items)
|
||||
self.sortByColumn(self.Columns.SHORT_CHANID, Qt.DescendingOrder)
|
||||
|
||||
def _update_chan_frozen_bg(self, *, chan: Channel, items: Sequence[QStandardItem]):
|
||||
assert self._default_item_bg_brush is not None
|
||||
for col in [
|
||||
self.Columns.LOCAL_BALANCE,
|
||||
self.Columns.REMOTE_BALANCE,
|
||||
self.Columns.CHANNEL_STATUS,
|
||||
]:
|
||||
item = items[col]
|
||||
if chan.is_frozen():
|
||||
item.setBackground(ColorScheme.BLUE.as_color(True))
|
||||
item.setToolTip(_("This channel is frozen. Frozen channels will not be used for outgoing payments."))
|
||||
else:
|
||||
item.setBackground(self._default_item_bg_brush)
|
||||
item.setToolTip("")
|
||||
|
||||
def update_can_send(self, lnworker):
|
||||
msg = _('Can send') + ' ' + self.parent.format_amount(lnworker.can_send())\
|
||||
+ ' ' + self.parent.base_unit() + '; '\
|
||||
|
|
|
@ -390,7 +390,22 @@ class Channel(Logger):
|
|||
def is_redeemed(self):
|
||||
return self.get_state() == channel_states.REDEEMED
|
||||
|
||||
def _check_can_pay(self, amount_msat: int) -> None:
|
||||
def is_frozen(self) -> bool:
|
||||
"""Whether the user has marked this channel as frozen.
|
||||
Frozen channels are not supposed to be used for new outgoing payments.
|
||||
(note that payment-forwarding ignores this option)
|
||||
"""
|
||||
return self.storage.get('frozen_for_sending', False)
|
||||
|
||||
def set_frozen(self, b: bool) -> None:
|
||||
self.storage['frozen_for_sending'] = bool(b)
|
||||
if self.lnworker:
|
||||
self.lnworker.network.trigger_callback('channel', self)
|
||||
|
||||
def _assert_we_can_add_htlc(self, amount_msat: int) -> None:
|
||||
"""Raises PaymentFailure if the local party cannot add this new HTLC.
|
||||
(this is relevant both for payments initiated by us and when forwarding)
|
||||
"""
|
||||
# TODO check if this method uses correct ctns (should use "latest" + 1)
|
||||
if self.is_closed():
|
||||
raise PaymentFailure('Channel closed')
|
||||
|
@ -398,6 +413,8 @@ class Channel(Logger):
|
|||
raise PaymentFailure('Channel not open', self.get_state())
|
||||
if not self.can_send_ctx_updates():
|
||||
raise PaymentFailure('Channel cannot send ctx updates')
|
||||
if not self.can_send_update_add_htlc():
|
||||
raise PaymentFailure('Channel cannot add htlc')
|
||||
if self.available_to_spend(LOCAL) < amount_msat:
|
||||
raise PaymentFailure(f'Not enough local balance. Have: {self.available_to_spend(LOCAL)}, Need: {amount_msat}')
|
||||
if len(self.hm.htlcs(LOCAL)) + 1 > self.config[REMOTE].max_accepted_htlcs:
|
||||
|
@ -409,9 +426,14 @@ class Channel(Logger):
|
|||
if amount_msat < self.config[REMOTE].htlc_minimum_msat:
|
||||
raise PaymentFailure(f'HTLC value too small: {amount_msat} msat')
|
||||
|
||||
def can_pay(self, amount_msat):
|
||||
def can_pay(self, amount_msat: int) -> bool:
|
||||
"""Returns whether we can initiate a new payment of given value.
|
||||
(we are the payer, not just a forwarding node)
|
||||
"""
|
||||
if self.is_frozen():
|
||||
return False
|
||||
try:
|
||||
self._check_can_pay(amount_msat)
|
||||
self._assert_we_can_add_htlc(amount_msat)
|
||||
except PaymentFailure:
|
||||
return False
|
||||
return True
|
||||
|
@ -430,11 +452,10 @@ class Channel(Logger):
|
|||
|
||||
This docstring is from LND.
|
||||
"""
|
||||
assert self.can_send_ctx_updates(), f"cannot update channel. {self.get_state()!r} {self.peer_state!r}"
|
||||
if isinstance(htlc, dict): # legacy conversion # FIXME remove
|
||||
htlc = UpdateAddHtlc(**htlc)
|
||||
assert isinstance(htlc, UpdateAddHtlc)
|
||||
self._check_can_pay(htlc.amount_msat)
|
||||
self._assert_we_can_add_htlc(htlc.amount_msat)
|
||||
if htlc.htlc_id is None:
|
||||
htlc = attr.evolve(htlc, htlc_id=self.hm.get_next_htlc_id(LOCAL))
|
||||
with self.db_lock:
|
||||
|
|
Loading…
Add table
Reference in a new issue