fix channel closure:
- add 'CLOSING' state - wait until channel has no inflight HTLC - end fee negocitation when both parties agree on the fee (previously code ended it only when the other party had broadcast) - broadcast the closing transaction
This commit is contained in:
@@ -1147,8 +1147,6 @@ class Peer(PrintError):
|
|||||||
@log_exceptions
|
@log_exceptions
|
||||||
async def close_channel(self, chan_id: bytes):
|
async def close_channel(self, chan_id: bytes):
|
||||||
chan = self.channels[chan_id]
|
chan = self.channels[chan_id]
|
||||||
if len(chan.htlcs(LOCAL, only_pending=True)) > 0:
|
|
||||||
raise Exception('Can\'t co-operatively close channel with payments ongoing (pending HTLCs). Please wait, or force-close the channel.')
|
|
||||||
self.shutdown_received[chan_id] = asyncio.Future()
|
self.shutdown_received[chan_id] = asyncio.Future()
|
||||||
self.send_shutdown(chan)
|
self.send_shutdown(chan)
|
||||||
payload = await self.shutdown_received[chan_id]
|
payload = await self.shutdown_received[chan_id]
|
||||||
@@ -1176,16 +1174,27 @@ class Peer(PrintError):
|
|||||||
|
|
||||||
@log_exceptions
|
@log_exceptions
|
||||||
async def _shutdown(self, chan: Channel, payload):
|
async def _shutdown(self, chan: Channel, payload):
|
||||||
|
# set state so that we stop accepting HTLCs
|
||||||
|
chan.set_state('CLOSING')
|
||||||
|
while len(chan.htlcs(LOCAL, only_pending=True)) > 0:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
our_fee = chan.pending_local_fee()
|
||||||
scriptpubkey = bfh(bitcoin.address_to_script(chan.sweep_address))
|
scriptpubkey = bfh(bitcoin.address_to_script(chan.sweep_address))
|
||||||
signature, fee, txid = chan.make_closing_tx(scriptpubkey, payload['scriptpubkey'])
|
# negociate fee
|
||||||
self.send_message('closing_signed', channel_id=chan.channel_id, fee_satoshis=fee, signature=signature)
|
while True:
|
||||||
while chan.get_state() != 'CLOSED':
|
our_sig, closing_tx = chan.make_closing_tx(scriptpubkey, payload['scriptpubkey'], fee_sat=our_fee)
|
||||||
try:
|
self.send_message('closing_signed', channel_id=chan.channel_id, fee_satoshis=our_fee, signature=our_sig)
|
||||||
closing_signed = await asyncio.wait_for(self.closing_signed[chan.channel_id].get(), 1)
|
cs_payload = await asyncio.wait_for(self.closing_signed[chan.channel_id].get(), 1)
|
||||||
except asyncio.TimeoutError:
|
their_fee = int.from_bytes(cs_payload['fee_satoshis'], 'big')
|
||||||
pass
|
their_sig = cs_payload['signature']
|
||||||
else:
|
if our_fee == their_fee:
|
||||||
fee = int.from_bytes(closing_signed['fee_satoshis'], 'big')
|
break
|
||||||
signature, _, txid = chan.make_closing_tx(scriptpubkey, payload['scriptpubkey'], fee_sat=fee)
|
# TODO: negociate better
|
||||||
self.send_message('closing_signed', channel_id=chan.channel_id, fee_satoshis=fee, signature=signature)
|
our_fee = their_fee
|
||||||
return txid
|
# add their signature
|
||||||
|
i = chan.get_local_index()
|
||||||
|
closing_tx.add_signature_to_txin(0, i, bh2u(our_sig))
|
||||||
|
closing_tx.add_signature_to_txin(0, 1-i, bh2u(their_sig))
|
||||||
|
# broadcast
|
||||||
|
await self.network.broadcast_transaction(closing_tx)
|
||||||
|
return closing_tx.txid()
|
||||||
|
|||||||
@@ -824,12 +824,12 @@ class Channel(PrintError):
|
|||||||
),
|
),
|
||||||
htlcs=htlcs)
|
htlcs=htlcs)
|
||||||
|
|
||||||
def make_closing_tx(self, local_script: bytes, remote_script: bytes,
|
def get_local_index(self):
|
||||||
fee_sat: Optional[int]=None) -> Tuple[bytes, int, str]:
|
return int(self.config[LOCAL].multisig_key.pubkey < self.config[REMOTE].multisig_key.pubkey)
|
||||||
""" cooperative close """
|
|
||||||
if fee_sat is None:
|
|
||||||
fee_sat = self.pending_local_fee()
|
|
||||||
|
|
||||||
|
def make_closing_tx(self, local_script: bytes, remote_script: bytes,
|
||||||
|
fee_sat: int) -> Tuple[bytes, int, str]:
|
||||||
|
""" cooperative close """
|
||||||
_, outputs = make_commitment_outputs({
|
_, outputs = make_commitment_outputs({
|
||||||
LOCAL: fee_sat * 1000 if self.constraints.is_initiator else 0,
|
LOCAL: fee_sat * 1000 if self.constraints.is_initiator else 0,
|
||||||
REMOTE: fee_sat * 1000 if not self.constraints.is_initiator else 0,
|
REMOTE: fee_sat * 1000 if not self.constraints.is_initiator else 0,
|
||||||
@@ -849,7 +849,7 @@ class Channel(PrintError):
|
|||||||
|
|
||||||
der_sig = bfh(closing_tx.sign_txin(0, self.config[LOCAL].multisig_key.privkey))
|
der_sig = bfh(closing_tx.sign_txin(0, self.config[LOCAL].multisig_key.privkey))
|
||||||
sig = ecc.sig_string_from_der_sig(der_sig[:-1])
|
sig = ecc.sig_string_from_der_sig(der_sig[:-1])
|
||||||
return sig, fee_sat, closing_tx.txid()
|
return sig, closing_tx
|
||||||
|
|
||||||
def force_close_tx(self):
|
def force_close_tx(self):
|
||||||
# local_commitment always gives back the next expected local_commitment,
|
# local_commitment always gives back the next expected local_commitment,
|
||||||
|
|||||||
Reference in New Issue
Block a user