Browse Source

lnchannel: implement "freezing" channels (for sending)

and expose it in Qt GUI
hard-fail-on-bad-server-string
SomberNight 5 years ago
parent
commit
deb50e7ec3
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 40
      electrum/gui/qt/channels_list.py
  2. 31
      electrum/lnchannel.py

40
electrum/gui/qt/channels_list.py

@ -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() + '; '\

31
electrum/lnchannel.py

@ -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…
Cancel
Save