lnchannel: implement freezing channels (for receiving)
A bit weird, I know... :) It allows for rebalancing our own channels! :P
This commit is contained in:
@@ -146,10 +146,15 @@ class ChannelsList(MyTreeView):
|
|||||||
cc.addAction(_("Long Channel ID"), lambda: self.place_text_on_clipboard(channel_id.hex(),
|
cc.addAction(_("Long Channel ID"), lambda: self.place_text_on_clipboard(channel_id.hex(),
|
||||||
title=_("Long Channel ID")))
|
title=_("Long Channel ID")))
|
||||||
|
|
||||||
if not chan.is_frozen():
|
if not chan.is_frozen_for_sending():
|
||||||
menu.addAction(_("Freeze"), lambda: chan.set_frozen(True))
|
menu.addAction(_("Freeze (for sending)"), lambda: chan.set_frozen_for_sending(True))
|
||||||
else:
|
else:
|
||||||
menu.addAction(_("Unfreeze"), lambda: chan.set_frozen(False))
|
menu.addAction(_("Unfreeze (for sending)"), lambda: chan.set_frozen_for_sending(False))
|
||||||
|
if not chan.is_frozen_for_receiving():
|
||||||
|
menu.addAction(_("Freeze (for receiving)"), lambda: chan.set_frozen_for_receiving(True))
|
||||||
|
else:
|
||||||
|
menu.addAction(_("Unfreeze (for receiving)"), lambda: chan.set_frozen_for_receiving(False))
|
||||||
|
|
||||||
|
|
||||||
funding_tx = self.parent.wallet.db.get_transaction(chan.funding_outpoint.txid)
|
funding_tx = self.parent.wallet.db.get_transaction(chan.funding_outpoint.txid)
|
||||||
if funding_tx:
|
if funding_tx:
|
||||||
@@ -212,18 +217,22 @@ class ChannelsList(MyTreeView):
|
|||||||
|
|
||||||
def _update_chan_frozen_bg(self, *, chan: Channel, items: Sequence[QStandardItem]):
|
def _update_chan_frozen_bg(self, *, chan: Channel, items: Sequence[QStandardItem]):
|
||||||
assert self._default_item_bg_brush is not None
|
assert self._default_item_bg_brush is not None
|
||||||
for col in [
|
# frozen for sending
|
||||||
self.Columns.LOCAL_BALANCE,
|
item = items[self.Columns.LOCAL_BALANCE]
|
||||||
self.Columns.REMOTE_BALANCE,
|
if chan.is_frozen_for_sending():
|
||||||
self.Columns.CHANNEL_STATUS,
|
item.setBackground(ColorScheme.BLUE.as_color(True))
|
||||||
]:
|
item.setToolTip(_("This channel is frozen for sending. It will not be used for outgoing payments."))
|
||||||
item = items[col]
|
else:
|
||||||
if chan.is_frozen():
|
item.setBackground(self._default_item_bg_brush)
|
||||||
item.setBackground(ColorScheme.BLUE.as_color(True))
|
item.setToolTip("")
|
||||||
item.setToolTip(_("This channel is frozen. Frozen channels will not be used for outgoing payments."))
|
# frozen for receiving
|
||||||
else:
|
item = items[self.Columns.REMOTE_BALANCE]
|
||||||
item.setBackground(self._default_item_bg_brush)
|
if chan.is_frozen_for_receiving():
|
||||||
item.setToolTip("")
|
item.setBackground(ColorScheme.BLUE.as_color(True))
|
||||||
|
item.setToolTip(_("This channel is frozen for receiving. It will not be included in invoices."))
|
||||||
|
else:
|
||||||
|
item.setBackground(self._default_item_bg_brush)
|
||||||
|
item.setToolTip("")
|
||||||
|
|
||||||
def update_can_send(self, lnworker: LNWallet):
|
def update_can_send(self, lnworker: LNWallet):
|
||||||
msg = _('Can send') + ' ' + self.parent.format_amount(lnworker.num_sats_can_send())\
|
msg = _('Can send') + ' ' + self.parent.format_amount(lnworker.num_sats_can_send())\
|
||||||
|
|||||||
@@ -392,18 +392,30 @@ class Channel(Logger):
|
|||||||
def is_redeemed(self):
|
def is_redeemed(self):
|
||||||
return self.get_state() == channel_states.REDEEMED
|
return self.get_state() == channel_states.REDEEMED
|
||||||
|
|
||||||
def is_frozen(self) -> bool:
|
def is_frozen_for_sending(self) -> bool:
|
||||||
"""Whether the user has marked this channel as frozen.
|
"""Whether the user has marked this channel as frozen for sending.
|
||||||
Frozen channels are not supposed to be used for new outgoing payments.
|
Frozen channels are not supposed to be used for new outgoing payments.
|
||||||
(note that payment-forwarding ignores this option)
|
(note that payment-forwarding ignores this option)
|
||||||
"""
|
"""
|
||||||
return self.storage.get('frozen_for_sending', False)
|
return self.storage.get('frozen_for_sending', False)
|
||||||
|
|
||||||
def set_frozen(self, b: bool) -> None:
|
def set_frozen_for_sending(self, b: bool) -> None:
|
||||||
self.storage['frozen_for_sending'] = bool(b)
|
self.storage['frozen_for_sending'] = bool(b)
|
||||||
if self.lnworker:
|
if self.lnworker:
|
||||||
self.lnworker.network.trigger_callback('channel', self)
|
self.lnworker.network.trigger_callback('channel', self)
|
||||||
|
|
||||||
|
def is_frozen_for_receiving(self) -> bool:
|
||||||
|
"""Whether the user has marked this channel as frozen for receiving.
|
||||||
|
Frozen channels are not supposed to be used for new incoming payments.
|
||||||
|
(note that payment-forwarding ignores this option)
|
||||||
|
"""
|
||||||
|
return self.storage.get('frozen_for_receiving', False)
|
||||||
|
|
||||||
|
def set_frozen_for_receiving(self, b: bool) -> None:
|
||||||
|
self.storage['frozen_for_receiving'] = bool(b)
|
||||||
|
if self.lnworker:
|
||||||
|
self.lnworker.network.trigger_callback('channel', self)
|
||||||
|
|
||||||
def _assert_can_add_htlc(self, *, htlc_proposer: HTLCOwner, amount_msat: int) -> None:
|
def _assert_can_add_htlc(self, *, htlc_proposer: HTLCOwner, amount_msat: int) -> None:
|
||||||
"""Raises PaymentFailure if the htlc_proposer cannot add this new HTLC.
|
"""Raises PaymentFailure if the htlc_proposer cannot add this new HTLC.
|
||||||
(this is relevant both for forwarding and endpoint)
|
(this is relevant both for forwarding and endpoint)
|
||||||
@@ -437,11 +449,9 @@ class Channel(Logger):
|
|||||||
if amount_msat > LN_MAX_HTLC_VALUE_MSAT and not self._ignore_max_htlc_value:
|
if amount_msat > LN_MAX_HTLC_VALUE_MSAT and not self._ignore_max_htlc_value:
|
||||||
raise PaymentFailure(f"HTLC value over protocol maximum: {amount_msat} > {LN_MAX_HTLC_VALUE_MSAT} msat")
|
raise PaymentFailure(f"HTLC value over protocol maximum: {amount_msat} > {LN_MAX_HTLC_VALUE_MSAT} msat")
|
||||||
|
|
||||||
def can_pay(self, amount_msat: int) -> bool:
|
def can_pay(self, amount_msat: int, *, check_frozen=False) -> bool:
|
||||||
"""Returns whether we can initiate a new payment of given value.
|
"""Returns whether we can add an HTLC of given value."""
|
||||||
(we are the payer, not just a forwarding node)
|
if check_frozen and self.is_frozen_for_sending():
|
||||||
"""
|
|
||||||
if self.is_frozen():
|
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
self._assert_can_add_htlc(htlc_proposer=LOCAL, amount_msat=amount_msat)
|
self._assert_can_add_htlc(htlc_proposer=LOCAL, amount_msat=amount_msat)
|
||||||
@@ -449,6 +459,16 @@ class Channel(Logger):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def can_receive(self, amount_msat: int, *, check_frozen=False) -> bool:
|
||||||
|
"""Returns whether the remote can add an HTLC of given value."""
|
||||||
|
if check_frozen and self.is_frozen_for_receiving():
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
self._assert_can_add_htlc(htlc_proposer=REMOTE, amount_msat=amount_msat)
|
||||||
|
except PaymentFailure:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def should_try_to_reestablish_peer(self) -> bool:
|
def should_try_to_reestablish_peer(self) -> bool:
|
||||||
return channel_states.PREOPENING < self._state < channel_states.FORCE_CLOSING and self.peer_state == peer_states.DISCONNECTED
|
return channel_states.PREOPENING < self._state < channel_states.FORCE_CLOSING and self.peer_state == peer_states.DISCONNECTED
|
||||||
|
|
||||||
|
|||||||
@@ -205,11 +205,12 @@ class LNPathFinder(Logger):
|
|||||||
is_mine = edge_channel_id in my_channels
|
is_mine = edge_channel_id in my_channels
|
||||||
if is_mine:
|
if is_mine:
|
||||||
if edge_startnode == nodeA: # payment outgoing, on our channel
|
if edge_startnode == nodeA: # payment outgoing, on our channel
|
||||||
if not my_channels[edge_channel_id].can_pay(amount_msat):
|
if not my_channels[edge_channel_id].can_pay(amount_msat, check_frozen=True):
|
||||||
return
|
return
|
||||||
else: # payment incoming, on our channel. (funny business, cycle weirdness)
|
else: # payment incoming, on our channel. (funny business, cycle weirdness)
|
||||||
assert edge_endnode == nodeA, (bh2u(edge_startnode), bh2u(edge_endnode))
|
assert edge_endnode == nodeA, (bh2u(edge_startnode), bh2u(edge_endnode))
|
||||||
pass # TODO?
|
if not my_channels[edge_channel_id].can_receive(amount_msat, check_frozen=True):
|
||||||
|
return
|
||||||
edge_cost, fee_for_edge_msat = self._edge_cost(
|
edge_cost, fee_for_edge_msat = self._edge_cost(
|
||||||
edge_channel_id,
|
edge_channel_id,
|
||||||
start_node=edge_startnode,
|
start_node=edge_startnode,
|
||||||
|
|||||||
@@ -1266,12 +1266,7 @@ class LNWallet(LNWorker):
|
|||||||
if chan.short_channel_id is not None}
|
if chan.short_channel_id is not None}
|
||||||
# note: currently we add *all* our channels; but this might be a privacy leak?
|
# note: currently we add *all* our channels; but this might be a privacy leak?
|
||||||
for chan in channels:
|
for chan in channels:
|
||||||
# check channel is open
|
if not chan.can_receive(amount_sat, check_frozen=True):
|
||||||
if chan.get_state() != channel_states.OPEN:
|
|
||||||
continue
|
|
||||||
# check channel has sufficient balance
|
|
||||||
# FIXME because of on-chain fees of ctx, this check is insufficient
|
|
||||||
if amount_sat and chan.balance(REMOTE) // 1000 < amount_sat:
|
|
||||||
continue
|
continue
|
||||||
chan_id = chan.short_channel_id
|
chan_id = chan.short_channel_id
|
||||||
assert isinstance(chan_id, bytes), chan_id
|
assert isinstance(chan_id, bytes), chan_id
|
||||||
|
|||||||
Reference in New Issue
Block a user