URI: 
       tFix #5975: Forget or redeem channels that are never funded. - initiator: wait until double spent - non-initiator: wait until timeout - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 5785c2fa2f7df705eab653fe40c4aa9520ec0bff
   DIR parent ed29a45d50cbb234b384f4c37bb5751a54a10dcc
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Mon, 24 Feb 2020 12:01:54 +0100
       
       Fix #5975: Forget or redeem channels that are never funded.
        - initiator: wait until double spent
        - non-initiator: wait until timeout
       
       Diffstat:
         M electrum/gui/qt/channels_list.py    |      10 ++++++----
         M electrum/lnchannel.py               |       1 +
         M electrum/lnpeer.py                  |       2 ++
         M electrum/lnwatcher.py               |       5 ++++-
         M electrum/lnworker.py                |      23 +++++++++++++++++++++++
       
       5 files changed, 36 insertions(+), 5 deletions(-)
       ---
   DIR diff --git a/electrum/gui/qt/channels_list.py b/electrum/gui/qt/channels_list.py
       t@@ -120,10 +120,12 @@ class ChannelsList(MyTreeView):
                        menu.addAction(_("Close channel"), lambda: self.close_channel(channel_id))
                    menu.addAction(_("Force-close channel"), lambda: self.force_close(channel_id))
                else:
       -            txid, height, timestamp = chan.get_closing_height()
       -            closing_tx = self.lnworker.lnwatcher.db.get_transaction(txid)
       -            if closing_tx:
       -                menu.addAction(_("View closing transaction"), lambda: self.parent.show_transaction(closing_tx))
       +            item = chan.get_closing_height()
       +            if item:
       +                txid, height, timestamp = item
       +                closing_tx = self.lnworker.lnwatcher.db.get_transaction(txid)
       +                if closing_tx:
       +                    menu.addAction(_("View closing transaction"), lambda: self.parent.show_transaction(closing_tx))
                if chan.is_redeemed():
                    menu.addAction(_("Remove"), lambda: self.remove_channel(channel_id))
                menu.exec_(self.viewport().mapToGlobal(position))
   DIR diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py
       t@@ -97,6 +97,7 @@ state_transitions = [
            (cs.CLOSING, cs.CLOSED),
            (cs.FORCE_CLOSING, cs.CLOSED),
            (cs.CLOSED, cs.REDEEMED),
       +    (cs.PREOPENING, cs.REDEEMED), # channel never funded
        ]
        del cs  # delete as name is ambiguous without context
        
   DIR diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py
       t@@ -590,6 +590,7 @@ class Peer(Logger):
                               sweep_address=self.lnworker.sweep_address,
                               lnworker=self.lnworker,
                               initial_feerate=feerate)
       +        chan.storage['funding_inputs'] = [txin.prevout for txin in funding_tx.inputs()]
                sig_64, _ = chan.sign_next_commitment()
                self.temp_id_to_id[temp_channel_id] = channel_id
                self.send_message("funding_created",
       t@@ -684,6 +685,7 @@ class Peer(Logger):
                               sweep_address=self.lnworker.sweep_address,
                               lnworker=self.lnworker,
                               initial_feerate=feerate)
       +        chan.storage['init_timestamp'] = int(time.time())
                remote_sig = funding_created['signature']
                chan.receive_new_commitment(remote_sig, [])
                sig_64, _ = chan.sign_next_commitment()
   DIR diff --git a/electrum/lnwatcher.py b/electrum/lnwatcher.py
       t@@ -322,6 +322,9 @@ class WatchTower(LNWatcher):
                pass
        
        
       +
       +CHANNEL_OPENING_TIMEOUT = 24*60*60
       +
        class LNWalletWatcher(LNWatcher):
        
            def __init__(self, lnworker, network):
       t@@ -337,7 +340,7 @@ class LNWalletWatcher(LNWatcher):
                    return
                if funding_height.height == TX_HEIGHT_LOCAL:
                    chan.delete_funding_height()
       -            return
       +            await self.lnworker.update_unfunded_channel(chan, funding_txid)
                elif closing_height.height == TX_HEIGHT_LOCAL:
                    chan.save_funding_height(funding_txid, funding_height.height, funding_height.timestamp)
                    await self.lnworker.update_open_channel(chan, funding_txid, funding_height)
   DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py
       t@@ -654,6 +654,29 @@ class LNWallet(LNWorker):
                    if chan.funding_outpoint.to_str() == txo:
                        return chan
        
       +    async def update_unfunded_channel(self, chan, funding_txid):
       +        if chan.get_state() in [channel_states.PREOPENING, channel_states.OPENING]:
       +            if chan.constraints.is_initiator:
       +                inputs = chan.storage.get('funding_inputs', [])
       +                if not inputs:
       +                    self.logger.info(f'channel funding inputs are not provided')
       +                    chan.set_state(channel_states.REDEEMED)
       +                for i in inputs:
       +                    spender_txid = self.wallet.db.get_spent_outpoint(*i)
       +                    if spender_txid is None:
       +                        continue
       +                    if spender_txid != funding_txid:
       +                        tx_mined_height = self.wallet.get_tx_height(spender_txid)
       +                        if tx_mined_height.conf > 6:
       +                            self.logger.info(f'channel is double spent {inputs}, {ds}')
       +                            # set to REDEEMED so that it can be removed manually
       +                            chan.set_state(channel_states.REDEEMED)
       +                            break
       +            else:
       +                now = int(time.time())
       +                if now - chan.storage.get('init_timestamp', 0) > CHANNEL_INIT_TIMEOUT:
       +                    self.remove_channel(chan.channel_id)
       +
            async def update_open_channel(self, chan, funding_txid, funding_height):
        
                if chan.get_state() == channel_states.OPEN and self.should_channel_be_closed_due_to_expiring_htlcs(chan):