1
0

lnchannel: allow deleting unfunded incoming channels

We tried to delete incoming channels that didn't get funded after
lnutil.CHANNEL_OPENING_TIMEOUT, however an assert prevented this:

```
  3.63 | E | lnwatcher.LNWatcher.[default_wallet-LNW] | Exception in check_onchain_situation: AssertionError()
Traceback (most recent call last):
  File "/home/user/code/electrum-fork/electrum/util.py", line 1233, in wrapper
    return await func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/user/code/electrum-fork/electrum/lnwatcher.py", line 117, in check_onchain_situation
    await self.update_channel_state(
    ...<5 lines>...
        keep_watching=keep_watching)
  File "/home/user/code/electrum-fork/electrum/lnwatcher.py", line 135, in update_channel_state
    chan.update_onchain_state(
    ~~~~~~~~~~~~~~~~~~~~~~~~~^
        funding_txid=funding_txid,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<2 lines>...
        closing_height=closing_height,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        keep_watching=keep_watching)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/user/code/electrum-fork/electrum/lnchannel.py", line 341, in update_onchain_state
    self.update_unfunded_state()
    ~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/home/user/code/electrum-fork/electrum/lnchannel.py", line 382, in update_unfunded_state
    self.lnworker.remove_channel(self.channel_id)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "/home/user/code/electrum-fork/electrum/lnworker.py", line 3244, in remove_channel
    assert chan.can_be_deleted()
           ~~~~~~~~~~~~~~~~~~~^^
AssertionError
```
This commit is contained in:
f321x
2025-12-05 14:42:08 +01:00
parent 6d1e8e8619
commit c34efce984
4 changed files with 80 additions and 9 deletions

View File

@@ -23,11 +23,13 @@
# (around commit 42de4400bff5105352d0552155f73589166d162b).
import unittest
from unittest import mock
import os
import binascii
from pprint import pformat
import logging
import dataclasses
import time
from electrum import bitcoin
from electrum import lnpeer
@@ -697,6 +699,46 @@ class TestChannel(ElectrumTestCase):
self.alice_channel.add_htlc(new)
self.assertIn('Not enough local balance', cm.exception.args[0])
def test_unfunded_channel_can_be_removed(self):
"""
Test that an incoming channel which stays unfunded longer than
lnutil.CHANNEL_OPENING_TIMEOUT_BLOCKS and lnutil.CHANNEL_OPENING_TIMEOUT_SEC
can be removed
"""
# set the init_height and init_timestamp
self.current_height = 800_000
self.bob_channel.storage['init_height'] = self.current_height
self.alice_channel.storage['init_height'] = self.current_height
self.bob_channel.storage['init_timestamp'] = int(time.time())
self.alice_channel.storage['init_timestamp'] = int(time.time())
mock_lnworker = mock.Mock()
mock_blockchain = mock.Mock()
mock_lnworker.wallet = mock.Mock()
mock_lnworker.wallet.is_up_to_date = lambda: True
mock_blockchain.is_tip_stale = lambda: False
mock_lnworker.network.blockchain = lambda: mock_blockchain
mock_lnworker.network.get_local_height = lambda: self.current_height
self.bob_channel.lnworker = mock_lnworker
self.alice_channel.lnworker = mock_lnworker
# test that the non-initiator can remove the channel after timeout
self.assertFalse(self.bob_channel.is_initiator())
self.bob_channel._state = ChannelState.OPENING
self.assertFalse(self.bob_channel.can_be_deleted())
self.current_height += lnutil.CHANNEL_OPENING_TIMEOUT_BLOCKS + 1
self.assertFalse(self.bob_channel.can_be_deleted()) # needs both block and time based timeout
self.bob_channel.storage['init_timestamp'] -= lnutil.CHANNEL_OPENING_TIMEOUT_SEC + 1
self.alice_channel.storage['init_timestamp'] -= lnutil.CHANNEL_OPENING_TIMEOUT_SEC + 1
self.assertTrue(self.bob_channel.can_be_deleted()) # now both timeouts are reached
self.current_height = 800_000 # reset to check if we can delete with just the time based timeout
self.assertFalse(self.bob_channel.can_be_deleted())
# test that the initiator can't remove the channel, even after timeout
self.current_height += lnutil.CHANNEL_OPENING_TIMEOUT_BLOCKS + 1
self.assertTrue(self.alice_channel.is_initiator())
self.alice_channel._state = ChannelState.OPENING
self.assertFalse(self.alice_channel.can_be_deleted())
class TestChannelAnchors(TestChannel):
TEST_ANCHOR_CHANNELS = True