commit c570bc5fb1a9479264d8c36816349b0398d52991
parent 0ea87278fb18a535c5ba35eb542ea7dfd5672f18
Author: Janus <ysangkok@gmail.com>
Date: Mon, 5 Nov 2018 17:23:49 +0100
avoid leaving FORCE_CLOSING state, rebroadcast closing tx if reorged out
Diffstat:
4 files changed, 44 insertions(+), 26 deletions(-)
diff --git a/electrum/gui/qt/channels_list.py b/electrum/gui/qt/channels_list.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
+import traceback
import asyncio
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import *
@@ -48,7 +49,8 @@ class ChannelsList(MyTreeWidget):
def on_success(txid):
self.main_window.show_error('Channel closed' + '\n' + txid)
def on_failure(exc_info):
- type_, e, traceback = exc_info
+ type_, e, tb = exc_info
+ traceback.print_tb(tb)
self.main_window.show_error('Failed to close channel:\n{}'.format(repr(e)))
def close():
def task():
diff --git a/electrum/lnbase.py b/electrum/lnbase.py
@@ -377,7 +377,8 @@ class Peer(PrintError):
except:
pass
for chan in self.channels.values():
- chan.set_state('DISCONNECTED')
+ if chan.get_state() != 'FORCE_CLOSING':
+ chan.set_state('DISCONNECTED')
self.network.trigger_callback('channel', chan)
def make_local_config(self, funding_sat: int, push_msat: int, initiator: HTLCOwner) -> Tuple[ChannelConfig, bytes]:
@@ -442,7 +443,7 @@ class Peer(PrintError):
)
payload = await self.channel_accepted[temp_channel_id].get()
if payload.get('error'):
- raise Exception(payload.get('error'))
+ raise Exception('Remote Lightning peer reported error: ' + repr(payload.get('error')))
remote_per_commitment_point = payload['first_per_commitment_point']
funding_txn_minimum_depth = int.from_bytes(payload['minimum_depth'], 'big')
remote_dust_limit_sat = int.from_bytes(payload['dust_limit_satoshis'], byteorder='big')
@@ -1158,25 +1159,6 @@ class Peer(PrintError):
self.print_error('Channel closed', txid)
return txid
- async def force_close_channel(self, chan_id):
- chan = self.channels[chan_id]
- # local_commitment always gives back the next expected local_commitment,
- # but in this case, we want the current one. So substract one ctn number
- old_local_state = chan.config[LOCAL]
- chan.config[LOCAL]=chan.config[LOCAL]._replace(ctn=chan.config[LOCAL].ctn - 1)
- tx = chan.pending_local_commitment
- chan.config[LOCAL] = old_local_state
- tx.sign({bh2u(chan.config[LOCAL].multisig_key.pubkey): (chan.config[LOCAL].multisig_key.privkey, True)})
- remote_sig = chan.config[LOCAL].current_commitment_signature
- remote_sig = der_sig_from_sig_string(remote_sig) + b"\x01"
- none_idx = tx._inputs[0]["signatures"].index(None)
- tx.add_signature_to_txin(0, none_idx, bh2u(remote_sig))
- assert tx.is_complete()
- # TODO persist FORCE_CLOSING state to disk
- chan.set_state('FORCE_CLOSING')
- self.lnworker.save_channel(chan)
- return await self.network.broadcast_transaction(tx)
-
@log_exceptions
async def on_shutdown(self, payload):
# length of scripts allowed in BOLT-02
diff --git a/electrum/lnchan.py b/electrum/lnchan.py
@@ -187,7 +187,11 @@ class Channel(PrintError):
self.remote_commitment = self.pending_remote_commitment
self._is_funding_txo_spent = None # "don't know"
- self.set_state('DISCONNECTED')
+ self._state = None
+ if state.get('force_closed', False):
+ self.set_state('FORCE_CLOSING')
+ else:
+ self.set_state('DISCONNECTED')
self.lnwatcher = None
@@ -197,6 +201,8 @@ class Channel(PrintError):
self.log[sub].locked_in.update(self.log[sub].adds.keys())
def set_state(self, state: str):
+ if self._state == 'FORCE_CLOSING':
+ assert state == 'FORCE_CLOSING', 'new state was not FORCE_CLOSING: ' + state
self._state = state
def get_state(self):
@@ -713,6 +719,7 @@ class Channel(PrintError):
"onion_keys": str_bytes_dict_to_save(self.onion_keys),
"settled_local": self.settled[LOCAL],
"settled_remote": self.settled[REMOTE],
+ "force_closed": self.get_state() == 'FORCE_CLOSING',
}
# htlcs number must be monotonically increasing,
@@ -806,6 +813,7 @@ class Channel(PrintError):
def make_closing_tx(self, local_script: bytes, remote_script: bytes,
fee_sat: Optional[int]=None) -> Tuple[bytes, int, str]:
+ """ cooperative close """
if fee_sat is None:
fee_sat = self.pending_local_fee
@@ -830,6 +838,21 @@ class Channel(PrintError):
sig = ecc.sig_string_from_der_sig(der_sig[:-1])
return sig, fee_sat, closing_tx.txid()
+ def force_close_tx(self):
+ # local_commitment always gives back the next expected local_commitment,
+ # but in this case, we want the current one. So substract one ctn number
+ old_local_state = self.config[LOCAL]
+ self.config[LOCAL]=self.config[LOCAL]._replace(ctn=self.config[LOCAL].ctn - 1)
+ tx = self.pending_local_commitment
+ self.config[LOCAL] = old_local_state
+ tx.sign({bh2u(self.config[LOCAL].multisig_key.pubkey): (self.config[LOCAL].multisig_key.privkey, True)})
+ remote_sig = self.config[LOCAL].current_commitment_signature
+ remote_sig = ecc.der_sig_from_sig_string(remote_sig) + b"\x01"
+ none_idx = tx._inputs[0]["signatures"].index(None)
+ tx.add_signature_to_txin(0, none_idx, bh2u(remote_sig))
+ assert tx.is_complete()
+ return tx
+
def maybe_create_sweeptx_for_their_ctx_to_remote(chan, ctx, their_pcp: bytes,
sweep_address) -> Optional[EncumberedTransaction]:
assert isinstance(their_pcp, bytes)
diff --git a/electrum/lnworker.py b/electrum/lnworker.py
@@ -36,6 +36,7 @@ from .lnutil import (Outpoint, calc_short_channel_id, LNPeerAddr,
NUM_MAX_EDGES_IN_PAYMENT_PATH)
from .i18n import _
from .lnrouter import RouteEdge, is_route_sane_to_use
+from .address_synchronizer import TX_HEIGHT_LOCAL
if TYPE_CHECKING:
from .network import Network
@@ -173,7 +174,8 @@ class LNWorker(PrintError):
return
chan.set_funding_txo_spentness(is_spent)
if is_spent:
- chan.set_state("CLOSED")
+ if chan.get_state() != 'FORCE_CLOSING':
+ chan.set_state("CLOSED")
self.channel_db.remove_channel(chan.short_channel_id)
self.network.trigger_callback('channel', chan)
@@ -207,6 +209,13 @@ class LNWorker(PrintError):
await peer.bitcoin_fee_update(chan)
conf = addr_sync.get_tx_height(chan.funding_outpoint.txid).conf
peer.on_network_update(chan, conf)
+ elif chan.get_state() == 'FORCE_CLOSING':
+ txid = chan.force_close_tx().txid()
+ height = addr_sync.get_tx_height(txid).height
+ self.print_error("force closing tx", txid, "height", height)
+ if height == TX_HEIGHT_LOCAL:
+ self.print_error('REBROADCASTING CLOSING TX')
+ await self.force_close_channel(chan.channel_id)
async def _open_channel_coroutine(self, peer, local_amount_sat, push_sat, password):
# peer might just have been connected to
@@ -450,8 +459,10 @@ class LNWorker(PrintError):
async def force_close_channel(self, chan_id):
chan = self.channels[chan_id]
- peer = self.peers[chan.node_id]
- return await peer.force_close_channel(chan_id)
+ tx = chan.force_close_tx()
+ chan.set_state('FORCE_CLOSING')
+ self.save_channel(chan)
+ return await self.network.broadcast_transaction(tx)
def _get_next_peers_to_try(self) -> Sequence[LNPeerAddr]:
now = time.time()