tfix 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 - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit c0a1af2032117ba81e1eac4c1d67e51900b2f767 DIR parent 5bc74772a24a2f911276f4a19fc0749241d4bff1 HTML Author: ThomasV <thomasv@electrum.org> Date: Thu, 22 Nov 2018 16:18:28 +0100 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 Diffstat: M electrum/lnbase.py | 37 +++++++++++++++++++------------ M electrum/lnchan.py | 10 +++++----- 2 files changed, 28 insertions(+), 19 deletions(-) --- DIR diff --git a/electrum/lnbase.py b/electrum/lnbase.py t@@ -1147,8 +1147,6 @@ class Peer(PrintError): @log_exceptions async def close_channel(self, chan_id: bytes): 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.send_shutdown(chan) payload = await self.shutdown_received[chan_id] t@@ -1176,16 +1174,27 @@ class Peer(PrintError): @log_exceptions 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)) - signature, fee, txid = chan.make_closing_tx(scriptpubkey, payload['scriptpubkey']) - self.send_message('closing_signed', channel_id=chan.channel_id, fee_satoshis=fee, signature=signature) - while chan.get_state() != 'CLOSED': - try: - closing_signed = await asyncio.wait_for(self.closing_signed[chan.channel_id].get(), 1) - except asyncio.TimeoutError: - pass - else: - fee = int.from_bytes(closing_signed['fee_satoshis'], 'big') - signature, _, txid = chan.make_closing_tx(scriptpubkey, payload['scriptpubkey'], fee_sat=fee) - self.send_message('closing_signed', channel_id=chan.channel_id, fee_satoshis=fee, signature=signature) - return txid + # negociate fee + while True: + our_sig, closing_tx = chan.make_closing_tx(scriptpubkey, payload['scriptpubkey'], fee_sat=our_fee) + self.send_message('closing_signed', channel_id=chan.channel_id, fee_satoshis=our_fee, signature=our_sig) + cs_payload = await asyncio.wait_for(self.closing_signed[chan.channel_id].get(), 1) + their_fee = int.from_bytes(cs_payload['fee_satoshis'], 'big') + their_sig = cs_payload['signature'] + if our_fee == their_fee: + break + # TODO: negociate better + our_fee = their_fee + # 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() DIR diff --git a/electrum/lnchan.py b/electrum/lnchan.py t@@ -824,12 +824,12 @@ class Channel(PrintError): ), htlcs=htlcs) + def get_local_index(self): + return int(self.config[LOCAL].multisig_key.pubkey < self.config[REMOTE].multisig_key.pubkey) + def make_closing_tx(self, local_script: bytes, remote_script: bytes, - fee_sat: Optional[int]=None) -> Tuple[bytes, int, str]: + fee_sat: int) -> Tuple[bytes, int, str]: """ cooperative close """ - if fee_sat is None: - fee_sat = self.pending_local_fee() - _, outputs = make_commitment_outputs({ LOCAL: fee_sat * 1000 if self.constraints.is_initiator else 0, REMOTE: fee_sat * 1000 if not self.constraints.is_initiator else 0, t@@ -849,7 +849,7 @@ class Channel(PrintError): der_sig = bfh(closing_tx.sign_txin(0, self.config[LOCAL].multisig_key.privkey)) 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): # local_commitment always gives back the next expected local_commitment,