Browse Source

avoid leaving FORCE_CLOSING state, rebroadcast closing tx if reorged out

dependabot/pip/contrib/deterministic-build/ecdsa-0.13.3
Janus 6 years ago
committed by ThomasV
parent
commit
c570bc5fb1
  1. 4
      electrum/gui/qt/channels_list.py
  2. 22
      electrum/lnbase.py
  3. 23
      electrum/lnchan.py
  4. 15
      electrum/lnworker.py

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

22
electrum/lnbase.py

@ -377,6 +377,7 @@ class Peer(PrintError):
except:
pass
for chan in self.channels.values():
if chan.get_state() != 'FORCE_CLOSING':
chan.set_state('DISCONNECTED')
self.network.trigger_callback('channel', chan)
@ -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

23
electrum/lnchan.py

@ -187,6 +187,10 @@ class Channel(PrintError):
self.remote_commitment = self.pending_remote_commitment
self._is_funding_txo_spent = None # "don't know"
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)

15
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,6 +174,7 @@ class LNWorker(PrintError):
return
chan.set_funding_txo_spentness(is_spent)
if is_spent:
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()

Loading…
Cancel
Save