electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 2927478192e40fd6966af17654471ebfb9051bba
parent e7ab9e4054d37c95cd57ad051d6d8249cc7ddccf
Author: ThomasV <thomasv@electrum.org>
Date:   Tue, 25 Feb 2020 12:35:07 +0100

lnpeer: closing fee negociation:
 - use fee_rate from config
 - set upper bound on fee
 - add test_close to test_lnpeer

Diffstat:
Melectrum/lnchannel.py | 3+++
Melectrum/lnpeer.py | 27++++++++++++++++++++-------
Melectrum/tests/test_lnchannel.py | 3+++
Melectrum/tests/test_lnpeer.py | 18++++++++++++++++++
4 files changed, 44 insertions(+), 7 deletions(-)

diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py @@ -743,6 +743,9 @@ class Channel(Logger): def pending_local_fee(self): return self.constraints.capacity - sum(x.value for x in self.get_next_commitment(LOCAL).outputs()) + def get_latest_fee(self, subject): + return self.constraints.capacity - sum(x.value for x in self.get_latest_commitment(subject).outputs()) + def update_fee(self, feerate: int, from_us: bool): # feerate uses sat/kw if self.constraints.is_initiator != from_us: diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py @@ -1381,20 +1381,33 @@ class Peer(Logger): while len(chan.hm.htlcs(LOCAL)) + len(chan.hm.htlcs(REMOTE)) > 0: self.logger.info(f'(chan: {chan.short_channel_id}) waiting for htlcs to settle...') await asyncio.sleep(1) - our_fee = chan.pending_local_fee() - scriptpubkey = bfh(bitcoin.address_to_script(chan.sweep_address)) + their_scriptpubkey = payload['scriptpubkey'] + our_scriptpubkey = bfh(bitcoin.address_to_script(chan.sweep_address)) + # estimate fee of closing tx + our_sig, closing_tx = chan.make_closing_tx(our_scriptpubkey, their_scriptpubkey, fee_sat=0) + fee_rate = self.network.config.fee_per_kb() + our_fee = fee_rate * closing_tx.estimated_size() // 1000 + # BOLT2: The sending node MUST set fee less than or equal to the base fee of the final ctx + max_fee = chan.get_latest_fee(LOCAL if is_local else REMOTE) + our_fee = min(our_fee, max_fee) # negotiate fee while True: - our_sig, closing_tx = chan.make_closing_tx(scriptpubkey, payload['scriptpubkey'], fee_sat=our_fee) + self.logger.info(f'sending closing_signed {our_fee}') + our_sig, closing_tx = chan.make_closing_tx(our_scriptpubkey, their_scriptpubkey, fee_sat=our_fee) self.send_message('closing_signed', channel_id=chan.channel_id, fee_satoshis=our_fee, signature=our_sig) # FIXME: the remote SHOULD send closing_signed, but some don't. cs_payload = await self.wait_for_message('closing_signed', chan.channel_id) their_fee = int.from_bytes(cs_payload['fee_satoshis'], 'big') their_sig = cs_payload['signature'] - if our_fee == their_fee: + # TODO: verify their sig + if their_fee > max_fee: + raise Exception(f'the proposed fee exceeds the base fee of the latest commitment transaction {is_local, their_fee, max_fee}') + # Agree if difference is lower or equal to one (see below) + if abs(our_fee - their_fee) < 2: break - # TODO: negotiate better - our_fee = their_fee + # BOLT2: receiver MUST propose a value "strictly between" the received fee_satoshis and its previously-sent fee_satoshis. + our_fee = (our_fee + (1 if our_fee < their_fee else -1) + their_fee) // 2 + # add signatures closing_tx.add_signature_to_txin(txin_idx=0, signing_pubkey=chan.config[LOCAL].multisig_key.pubkey.hex(), @@ -1403,5 +1416,5 @@ class Peer(Logger): signing_pubkey=chan.config[REMOTE].multisig_key.pubkey.hex(), sig=bh2u(der_sig_from_sig_string(their_sig) + b'\x01')) # broadcast - await self.network.broadcast_transaction(closing_tx) + await self.network.try_broadcasting(closing_tx, 'closing') return closing_tx.txid() diff --git a/electrum/tests/test_lnchannel.py b/electrum/tests/test_lnchannel.py @@ -165,6 +165,9 @@ def create_test_channels(feerate=6000, local=None, remote=None): alice.hm.channel_open_finished() bob.hm.channel_open_finished() + # TODO: sweep_address in lnchannel.py should use static_remotekey + alice.sweep_address = bitcoin.pubkey_to_address('p2wpkh', alice.config[LOCAL].payment_basepoint.pubkey.hex()) + bob.sweep_address = bitcoin.pubkey_to_address('p2wpkh', bob.config[LOCAL].payment_basepoint.pubkey.hex()) return alice, bob class TestFee(ElectrumTestCase): diff --git a/electrum/tests/test_lnpeer.py b/electrum/tests/test_lnpeer.py @@ -283,6 +283,24 @@ class TestPeer(ElectrumTestCase): with self.assertRaises(concurrent.futures.CancelledError): run(f()) + def test_close(self): + alice_channel, bob_channel = create_test_channels() + p1, p2, w1, w2, _q1, _q2 = self.prepare_peers(alice_channel, bob_channel) + w1.network.config.set_key('dynamic_fees', False) + w2.network.config.set_key('dynamic_fees', False) + w1.network.config.set_key('fee_per_kb', 5000) + w2.network.config.set_key('fee_per_kb', 1000) + async def pay(): + await asyncio.wait_for(p1.initialized, 1) + await asyncio.wait_for(p2.initialized, 1) + await p1.close_channel(alice_channel.channel_id) + gath.cancel() + gath = asyncio.gather(pay(), p1._message_loop(), p2._message_loop()) + async def f(): + await gath + with self.assertRaises(concurrent.futures.CancelledError): + run(f()) + def test_channel_usage_after_closing(self): alice_channel, bob_channel = create_test_channels() p1, p2, w1, w2, q1, q2 = self.prepare_peers(alice_channel, bob_channel)