tln: fundee must commit to fee first - electrum - Electrum Bitcoin wallet (HTM) git clone https://git.parazyd.org/electrum (DIR) Log (DIR) Files (DIR) Refs (DIR) Submodules --- (DIR) commit 66e7b4d25020ce1a01903116f63fd7f79b2a6d48 (DIR) parent fb00e29f1c5c1fffcc430c2084472eede6a2cdfa (HTM) Author: Janus <ysangkok@gmail.com> Date: Fri, 20 Jul 2018 16:44:03 +0200 ln: fundee must commit to fee first Diffstat: M electrum/lnhtlc.py | 191 ++++++++++++++++--------------- M electrum/tests/test_lnhtlc.py | 8 +++++++- 2 files changed, 107 insertions(+), 92 deletions(-) --- (DIR) diff --git a/electrum/lnhtlc.py b/electrum/lnhtlc.py t@@ -14,35 +14,18 @@ from .lnutil import sign_and_get_sig_string from .lnutil import make_htlc_tx_with_open_channel, make_commitment, make_received_htlc, make_offered_htlc from .lnutil import HTLC_TIMEOUT_WEIGHT, HTLC_SUCCESS_WEIGHT from .lnutil import funding_output_script -from contextlib import contextmanager SettleHtlc = namedtuple("SettleHtlc", ["htlc_id"]) RevokeAndAck = namedtuple("RevokeAndAck", ["per_commitment_secret", "next_per_commitment_point"]) -@contextmanager -def PendingFeerateApplied(machine): - old_local_state = machine.local_state - old_remote_state = machine.remote_state - - new_local_feerate = machine.local_state.feerate - new_remote_feerate = machine.remote_state.feerate - - if machine.constraints.is_initiator: - if machine.pending_fee_update is not None: - new_remote_feerate = machine.pending_fee_update - if machine.pending_ack_fee_update is not None: - new_local_feerate = machine.pending_ack_fee_update - else: - if machine.pending_fee_update is not None: - new_local_feerate = machine.pending_fee_update - if machine.pending_ack_fee_update is not None: - new_remote_feerate = machine.pending_ack_fee_update - - machine.local_state = machine.local_state._replace(feerate=new_local_feerate) - machine.remote_state = machine.remote_state._replace(feerate=new_remote_feerate) - yield - machine.local_state = old_local_state._replace(feerate=old_local_state.feerate) - machine.remote_state = old_remote_state._replace(feerate=old_remote_state.feerate) +FUNDEE_SIGNED = 1 +FUNDEE_ACKED = 2 +FUNDER_SIGNED = 4 + +class FeeUpdate: + def __init__(self, rate): + self.rate = rate + self.progress = 0 class UpdateAddHtlc: def __init__(self, amount_msat, payment_hash, cltv_expiry, total_fee): t@@ -89,6 +72,22 @@ def typeWrap(k, v, local): return v class HTLCStateMachine(PrintError): + @property + def pending_remote_feerate(self): + if self.pending_fee is not None: + if self.constraints.is_initiator or (self.pending_fee.progress & FUNDEE_ACKED): + return self.pending_fee.rate + return self.remote_state.feerate + + @property + def pending_local_feerate(self): + if self.pending_fee is not None: + if not self.constraints.is_initiator: + return self.pending_fee.rate + if self.constraints.is_initiator and (self.pending_fee.progress & FUNDEE_ACKED): + return self.pending_fee.rate + return self.local_state.feerate + def lookup_htlc(self, log, htlc_id): assert type(htlc_id) is int for htlc in log: t@@ -135,8 +134,7 @@ class HTLCStateMachine(PrintError): self.total_msat_sent = 0 self.total_msat_received = 0 - self.pending_fee_update = None - self.pending_ack_fee_update = None + self.pending_fee = None self.local_commitment = self.pending_local_commitment self.remote_commitment = self.pending_remote_commitment t@@ -192,10 +190,6 @@ class HTLCStateMachine(PrintError): if htlc.l_locked_in is None: htlc.l_locked_in = self.local_state.ctn self.print_error("sign_next_commitment") - if self.constraints.is_initiator and self.pending_fee_update: - self.pending_ack_fee_update = self.pending_fee_update - self.pending_fee_update = None - sig_64 = sign_and_get_sig_string(self.pending_remote_commitment, self.local_config, self.remote_config) their_remote_htlc_privkey_number = derive_privkey( t@@ -221,6 +215,12 @@ class HTLCStateMachine(PrintError): htlc_sig = ecc.sig_string_from_der_sig(sig[:-1]) htlcsigs.append(htlc_sig) + if self.pending_fee: + if not self.constraints.is_initiator: + self.pending_fee.progress |= FUNDEE_SIGNED + if self.constraints.is_initiator and (self.pending_fee.progress & FUNDEE_ACKED): + self.pending_fee.progress |= FUNDER_SIGNED + return sig_64, htlcsigs def receive_new_commitment(self, sig, htlc_sigs): t@@ -241,10 +241,6 @@ class HTLCStateMachine(PrintError): if htlc.r_locked_in is None: htlc.r_locked_in = self.remote_state.ctn assert len(htlc_sigs) == 0 or type(htlc_sigs[0]) is bytes - if not self.constraints.is_initiator: - self.pending_ack_fee_update = self.pending_fee_update - self.pending_fee_update = None - preimage_hex = self.pending_local_commitment.serialize_preimage(0) pre_hash = Hash(bfh(preimage_hex)) if not ecc.verify_signature(self.remote_config.multisig_key.pubkey, sig, pre_hash): t@@ -266,6 +262,13 @@ class HTLCStateMachine(PrintError): # TODO check htlc in htlcs_in_local + if self.pending_fee: + if not self.constraints.is_initiator: + self.pending_fee.progress |= FUNDEE_SIGNED + if self.constraints.is_initiator and (self.pending_fee.progress & FUNDEE_ACKED): + self.pending_fee.progress |= FUNDER_SIGNED + + def revoke_current_commitment(self): """ RevokeCurrentCommitment revokes the next lowest unrevoked commitment t@@ -281,24 +284,24 @@ class HTLCStateMachine(PrintError): last_secret, this_point, next_point = self.points - new_feerate = self.local_state.feerate - - if not self.constraints.is_initiator and self.pending_fee_update is not None: - new_feerate = self.pending_fee_update - self.pending_fee_update = None - self.pending_ack_fee_update = None - elif self.pending_ack_fee_update is not None: - new_feerate = self.pending_ack_fee_update - self.pending_fee_update = None - self.pending_ack_fee_update = None - - self.remote_state=self.remote_state._replace( - feerate=new_feerate - ) + new_local_feerate = self.local_state.feerate + + if self.pending_fee is not None: + if not self.constraints.is_initiator and (self.pending_fee.progress & FUNDEE_SIGNED): + new_local_feerate = self.pending_fee.rate + self.remote_state=self.remote_state._replace( + feerate=self.pending_fee.rate + ) + self.pending_fee = None + print("FEERATE CHANGE COMPLETE (non-initiator)") + if self.constraints.is_initiator and (self.pending_fee.progress & FUNDER_SIGNED): + new_local_feerate = self.pending_fee.rate + self.pending_fee = None + print("FEERATE CHANGE COMPLETE (initiator)") self.local_state=self.local_state._replace( ctn=self.local_state.ctn + 1, - feerate=new_feerate + feerate=new_local_feerate ) self.local_commitment = self.pending_local_commitment t@@ -380,12 +383,16 @@ class HTLCStateMachine(PrintError): ctn=self.remote_state.ctn + 1, current_per_commitment_point=next_point, next_per_commitment_point=revocation.next_per_commitment_point, - amount_msat=self.remote_state.amount_msat + (sent_this_batch - received_this_batch) + sent_fees - received_fees, - feerate=self.pending_fee_update if self.pending_fee_update is not None else self.remote_state.feerate + amount_msat=self.remote_state.amount_msat + (sent_this_batch - received_this_batch) + sent_fees - received_fees ) self.local_state=self.local_state._replace( amount_msat = self.local_state.amount_msat + (received_this_batch - sent_this_batch) - sent_fees + received_fees ) + + if self.pending_fee: + if self.constraints.is_initiator: + self.pending_fee.progress |= FUNDEE_ACKED + self.local_commitment = self.pending_local_commitment self.remote_commitment = self.pending_remote_commitment t@@ -423,25 +430,26 @@ class HTLCStateMachine(PrintError): local_htlc_pubkey = derive_pubkey(self.local_config.htlc_basepoint.pubkey, this_point) local_revocation_pubkey = derive_blinded_pubkey(self.local_config.revocation_basepoint.pubkey, this_point) - with PendingFeerateApplied(self): - htlcs_in_local = [] - for htlc in self.htlcs_in_local: - if htlc.amount_msat // 1000 - HTLC_SUCCESS_WEIGHT * (self.remote_state.feerate // 1000) < self.remote_config.dust_limit_sat: - continue - htlcs_in_local.append( - ( make_received_htlc(local_revocation_pubkey, local_htlc_pubkey, remote_htlc_pubkey, htlc.payment_hash, htlc.cltv_expiry), htlc.amount_msat + htlc.total_fee)) + feerate = self.pending_remote_feerate - htlcs_in_remote = [] - for htlc in self.htlcs_in_remote: - if htlc.amount_msat // 1000 - HTLC_TIMEOUT_WEIGHT * (self.remote_state.feerate // 1000) < self.remote_config.dust_limit_sat: - continue - htlcs_in_remote.append( - ( make_offered_htlc(local_revocation_pubkey, local_htlc_pubkey, remote_htlc_pubkey, htlc.payment_hash), htlc.amount_msat + htlc.total_fee)) + htlcs_in_local = [] + for htlc in self.htlcs_in_local: + if htlc.amount_msat // 1000 - HTLC_SUCCESS_WEIGHT * (feerate // 1000) < self.remote_config.dust_limit_sat: + continue + htlcs_in_local.append( + ( make_received_htlc(local_revocation_pubkey, local_htlc_pubkey, remote_htlc_pubkey, htlc.payment_hash, htlc.cltv_expiry), htlc.amount_msat + htlc.total_fee)) - commit = self.make_commitment(self.remote_state.ctn + 1, - False, this_point, - remote_msat - total_fee_remote, local_msat - total_fee_local, htlcs_in_local + htlcs_in_remote) - return commit + htlcs_in_remote = [] + for htlc in self.htlcs_in_remote: + if htlc.amount_msat // 1000 - HTLC_TIMEOUT_WEIGHT * (feerate // 1000) < self.remote_config.dust_limit_sat: + continue + htlcs_in_remote.append( + ( make_offered_htlc(local_revocation_pubkey, local_htlc_pubkey, remote_htlc_pubkey, htlc.payment_hash), htlc.amount_msat + htlc.total_fee)) + + commit = self.make_commitment(self.remote_state.ctn + 1, + False, this_point, + remote_msat - total_fee_remote, local_msat - total_fee_local, htlcs_in_local + htlcs_in_remote) + return commit @property def pending_local_commitment(self): t@@ -455,25 +463,26 @@ class HTLCStateMachine(PrintError): local_htlc_pubkey = derive_pubkey(self.local_config.htlc_basepoint.pubkey, this_point) remote_revocation_pubkey = derive_blinded_pubkey(self.remote_config.revocation_basepoint.pubkey, this_point) - with PendingFeerateApplied(self): - htlcs_in_local = [] - for htlc in self.htlcs_in_local: - if htlc.amount_msat // 1000 - HTLC_TIMEOUT_WEIGHT * (self.local_state.feerate // 1000) < self.local_config.dust_limit_sat: - continue - htlcs_in_local.append( - ( make_offered_htlc(remote_revocation_pubkey, remote_htlc_pubkey, local_htlc_pubkey, htlc.payment_hash), htlc.amount_msat + htlc.total_fee)) + feerate = self.pending_local_feerate - htlcs_in_remote = [] - for htlc in self.htlcs_in_remote: - if htlc.amount_msat // 1000 - HTLC_SUCCESS_WEIGHT * (self.local_state.feerate // 1000) < self.local_config.dust_limit_sat: - continue - htlcs_in_remote.append( - ( make_received_htlc(remote_revocation_pubkey, remote_htlc_pubkey, local_htlc_pubkey, htlc.payment_hash, htlc.cltv_expiry), htlc.amount_msat + htlc.total_fee)) + htlcs_in_local = [] + for htlc in self.htlcs_in_local: + if htlc.amount_msat // 1000 - HTLC_TIMEOUT_WEIGHT * (feerate // 1000) < self.local_config.dust_limit_sat: + continue + htlcs_in_local.append( + ( make_offered_htlc(remote_revocation_pubkey, remote_htlc_pubkey, local_htlc_pubkey, htlc.payment_hash), htlc.amount_msat + htlc.total_fee)) + + htlcs_in_remote = [] + for htlc in self.htlcs_in_remote: + if htlc.amount_msat // 1000 - HTLC_SUCCESS_WEIGHT * (feerate // 1000) < self.local_config.dust_limit_sat: + continue + htlcs_in_remote.append( + ( make_received_htlc(remote_revocation_pubkey, remote_htlc_pubkey, local_htlc_pubkey, htlc.payment_hash, htlc.cltv_expiry), htlc.amount_msat + htlc.total_fee)) - commit = self.make_commitment(self.local_state.ctn + 1, - True, this_point, - local_msat - total_fee_local, remote_msat - total_fee_remote, htlcs_in_local + htlcs_in_remote) - return commit + commit = self.make_commitment(self.local_state.ctn + 1, + True, this_point, + local_msat - total_fee_local, remote_msat - total_fee_remote, htlcs_in_local + htlcs_in_remote) + return commit def gen_htlc_indices(self, subject, just_unsettled=True): assert subject in ["local", "remote"] t@@ -530,18 +539,18 @@ class HTLCStateMachine(PrintError): return self.remote_state.ctn @property - def local_commit_fee(self): - return self.constraints.capacity - sum(x[2] for x in self.local_commitment.outputs()) + def pending_local_fee(self): + return self.constraints.capacity - sum(x[2] for x in self.pending_local_commitment.outputs()) def update_fee(self, fee): if not self.constraints.is_initiator: raise Exception("only initiator can update_fee, this counterparty is not initiator") - self.pending_fee_update = fee + self.pending_fee = FeeUpdate(rate=fee) def receive_update_fee(self, fee): if self.constraints.is_initiator: raise Exception("only the non-initiator can receive_update_fee, this counterparty is initiator") - self.pending_fee_update = fee + self.pending_fee = FeeUpdate(rate=fee) def to_save(self): return { t@@ -597,7 +606,7 @@ class HTLCStateMachine(PrintError): local_msat, remote_msat, conf.dust_limit_sat, - chan.local_state.feerate if for_us else chan.remote_state.feerate, + chan.pending_local_feerate if for_us else chan.pending_remote_feerate, for_us, chan.constraints.is_initiator, htlcs=htlcs) (DIR) diff --git a/electrum/tests/test_lnhtlc.py b/electrum/tests/test_lnhtlc.py t@@ -245,11 +245,15 @@ class TestLNBaseHTLCStateMachine(unittest.TestCase): return fee def test_UpdateFeeSenderCommits(self): + old_feerate = self.alice_channel.pending_local_feerate fee = self.alice_to_bob_fee_update() alice_channel, bob_channel = self.alice_channel, self.bob_channel + self.assertEqual(self.alice_channel.pending_local_feerate, old_feerate) alice_sig, alice_htlc_sigs = alice_channel.sign_next_commitment() + self.assertEqual(self.alice_channel.pending_local_feerate, old_feerate) + bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs) self.assertNotEqual(fee, bob_channel.local_state.feerate) t@@ -265,6 +269,7 @@ class TestLNBaseHTLCStateMachine(unittest.TestCase): self.assertEqual(fee, alice_channel.local_state.feerate) bob_channel.receive_revocation(rev) + self.assertEqual(fee, bob_channel.remote_state.feerate) def test_UpdateFeeReceiverCommits(self): t@@ -293,6 +298,7 @@ class TestLNBaseHTLCStateMachine(unittest.TestCase): self.assertEqual(fee, alice_channel.local_state.feerate) bob_channel.receive_revocation(alice_revocation) + self.assertEqual(fee, bob_channel.remote_state.feerate) t@@ -319,7 +325,7 @@ class TestLNHTLCDust(unittest.TestCase): self.assertEqual(len(alice_channel.local_commitment.outputs()), 3) self.assertEqual(len(bob_channel.local_commitment.outputs()), 2) default_fee = calc_static_fee(0) - self.assertEqual(bob_channel.local_commit_fee, default_fee + htlcAmt) + self.assertEqual(bob_channel.pending_local_fee, default_fee + htlcAmt) bob_channel.settle_htlc(paymentPreimage, htlc.htlc_id) alice_channel.receive_htlc_settle(paymentPreimage, aliceHtlcIndex) force_state_transition(bob_channel, alice_channel)