mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-27 07:23:25 +00:00
avoid leaving FORCE_CLOSING state, rebroadcast closing tx if reorged out
This commit is contained in:
parent
266d9462f3
commit
a774f3339e
4 changed files with 44 additions and 26 deletions
|
@ -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():
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Add table
Reference in a new issue