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):