URI: 
       tlnchannel: implement freezing channels (for receiving) - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 3ed6afce64b44431be87409e6ef64de8d29c979f
   DIR parent 79d202485e1747ad5d16cecb7c35db64ce502177
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Thu, 26 Mar 2020 07:09:38 +0100
       
       lnchannel: implement freezing channels (for receiving)
       
       A bit weird, I know... :)
       It allows for rebalancing our own channels! :P
       
       Diffstat:
         M electrum/gui/qt/channels_list.py    |      39 +++++++++++++++++++------------
         M electrum/lnchannel.py               |      36 ++++++++++++++++++++++++-------
         M electrum/lnrouter.py                |       5 +++--
         M electrum/lnworker.py                |       7 +------
       
       4 files changed, 56 insertions(+), 31 deletions(-)
       ---
   DIR diff --git a/electrum/gui/qt/channels_list.py b/electrum/gui/qt/channels_list.py
       t@@ -146,10 +146,15 @@ class ChannelsList(MyTreeView):
                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))
       +        if not chan.is_frozen_for_sending():
       +            menu.addAction(_("Freeze (for sending)"), lambda: chan.set_frozen_for_sending(True))
                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)
                if funding_tx:
       t@@ -212,18 +217,22 @@ class ChannelsList(MyTreeView):
        
            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("")
       +        # frozen for sending
       +        item = items[self.Columns.LOCAL_BALANCE]
       +        if chan.is_frozen_for_sending():
       +            item.setBackground(ColorScheme.BLUE.as_color(True))
       +            item.setToolTip(_("This channel is frozen for sending. It will not be used for outgoing payments."))
       +        else:
       +            item.setBackground(self._default_item_bg_brush)
       +            item.setToolTip("")
       +        # frozen for receiving
       +        item = items[self.Columns.REMOTE_BALANCE]
       +        if chan.is_frozen_for_receiving():
       +            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):
                msg = _('Can send') + ' ' + self.parent.format_amount(lnworker.num_sats_can_send())\
   DIR diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py
       t@@ -392,18 +392,30 @@ class Channel(Logger):
            def is_redeemed(self):
                return self.get_state() == channel_states.REDEEMED
        
       -    def is_frozen(self) -> bool:
       -        """Whether the user has marked this channel as frozen.
       +    def is_frozen_for_sending(self) -> bool:
       +        """Whether the user has marked this channel as frozen for sending.
                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:
       +    def set_frozen_for_sending(self, b: bool) -> None:
                self.storage['frozen_for_sending'] = bool(b)
                if self.lnworker:
                    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:
                """Raises PaymentFailure if the htlc_proposer cannot add this new HTLC.
                (this is relevant both for forwarding and endpoint)
       t@@ -437,11 +449,9 @@ class Channel(Logger):
                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")
        
       -    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():
       +    def can_pay(self, amount_msat: int, *, check_frozen=False) -> bool:
       +        """Returns whether we can add an HTLC of given value."""
       +        if check_frozen and self.is_frozen_for_sending():
                    return False
                try:
                    self._assert_can_add_htlc(htlc_proposer=LOCAL, amount_msat=amount_msat)
       t@@ -449,6 +459,16 @@ class Channel(Logger):
                    return False
                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:
                return channel_states.PREOPENING < self._state < channel_states.FORCE_CLOSING and self.peer_state == peer_states.DISCONNECTED
        
   DIR diff --git a/electrum/lnrouter.py b/electrum/lnrouter.py
       t@@ -205,11 +205,12 @@ class LNPathFinder(Logger):
                    is_mine = edge_channel_id in my_channels
                    if is_mine:
                        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
                        else:  # payment incoming, on our channel. (funny business, cycle weirdness)
                            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_channel_id,
                        start_node=edge_startnode,
   DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py
       t@@ -1266,12 +1266,7 @@ class LNWallet(LNWorker):
                                       if chan.short_channel_id is not None}
                # note: currently we add *all* our channels; but this might be a privacy leak?
                for chan in channels:
       -            # check channel is open
       -            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:
       +            if not chan.can_receive(amount_sat, check_frozen=True):
                        continue
                    chan_id = chan.short_channel_id
                    assert isinstance(chan_id, bytes), chan_id