Browse Source

lnhtlc: only store feerate once, don't store heights since we do not roll back

dependabot/pip/contrib/deterministic-build/ecdsa-0.13.3
Janus 6 years ago
committed by ThomasV
parent
commit
b26dc66567
  1. 13
      electrum/lnbase.py
  2. 59
      electrum/lnhtlc.py
  3. 3
      electrum/lnutil.py
  4. 31
      electrum/tests/test_lnhtlc.py

13
electrum/lnbase.py

@ -530,7 +530,7 @@ class Peer(PrintError):
chan.set_state('DISCONNECTED') chan.set_state('DISCONNECTED')
self.network.trigger_callback('channel', chan) self.network.trigger_callback('channel', chan)
def make_local_config(self, funding_sat, push_msat, initiator: HTLCOwner, feerate): def make_local_config(self, funding_sat, push_msat, initiator: HTLCOwner):
# key derivation # key derivation
channel_counter = self.lnworker.get_and_inc_counter_for_channel_keys() channel_counter = self.lnworker.get_and_inc_counter_for_channel_keys()
keypair_generator = lambda family: generate_keypair(self.lnworker.ln_keystore, family, channel_counter) keypair_generator = lambda family: generate_keypair(self.lnworker.ln_keystore, family, channel_counter)
@ -552,7 +552,6 @@ class Peer(PrintError):
ctn=-1, ctn=-1,
next_htlc_id=0, next_htlc_id=0,
amount_msat=initial_msat, amount_msat=initial_msat,
feerate=feerate,
) )
per_commitment_secret_seed = keypair_generator(LnKeyFamily.REVOCATION_ROOT).privkey per_commitment_secret_seed = keypair_generator(LnKeyFamily.REVOCATION_ROOT).privkey
return local_config, per_commitment_secret_seed return local_config, per_commitment_secret_seed
@ -561,7 +560,7 @@ class Peer(PrintError):
async def channel_establishment_flow(self, password, funding_sat, push_msat, temp_channel_id): async def channel_establishment_flow(self, password, funding_sat, push_msat, temp_channel_id):
await self.initialized await self.initialized
feerate = self.current_feerate_per_kw() feerate = self.current_feerate_per_kw()
local_config, per_commitment_secret_seed = self.make_local_config(funding_sat, push_msat, LOCAL, feerate) local_config, per_commitment_secret_seed = self.make_local_config(funding_sat, push_msat, LOCAL)
# for the first commitment transaction # for the first commitment transaction
per_commitment_secret_first = get_per_commitment_secret_from_seed(per_commitment_secret_seed, RevocationStore.START_INDEX) per_commitment_secret_first = get_per_commitment_secret_from_seed(per_commitment_secret_seed, RevocationStore.START_INDEX)
per_commitment_point_first = secret_to_pubkey(int.from_bytes(per_commitment_secret_first, 'big')) per_commitment_point_first = secret_to_pubkey(int.from_bytes(per_commitment_secret_first, 'big'))
@ -611,7 +610,6 @@ class Peer(PrintError):
ctn = -1, ctn = -1,
amount_msat=push_msat, amount_msat=push_msat,
next_htlc_id = 0, next_htlc_id = 0,
feerate=feerate,
next_per_commitment_point=remote_per_commitment_point, next_per_commitment_point=remote_per_commitment_point,
current_per_commitment_point=None, current_per_commitment_point=None,
@ -640,7 +638,7 @@ class Peer(PrintError):
current_commitment_signature = None, current_commitment_signature = None,
current_htlc_signatures = None, current_htlc_signatures = None,
), ),
"constraints": ChannelConstraints(capacity=funding_sat, is_initiator=True, funding_txn_minimum_depth=funding_txn_minimum_depth), "constraints": ChannelConstraints(capacity=funding_sat, is_initiator=True, funding_txn_minimum_depth=funding_txn_minimum_depth, feerate=feerate),
"remote_commitment_to_be_revoked": None, "remote_commitment_to_be_revoked": None,
} }
m = HTLCStateMachine(chan) m = HTLCStateMachine(chan)
@ -675,7 +673,7 @@ class Peer(PrintError):
feerate = int.from_bytes(payload['feerate_per_kw'], 'big') feerate = int.from_bytes(payload['feerate_per_kw'], 'big')
temp_chan_id = payload['temporary_channel_id'] temp_chan_id = payload['temporary_channel_id']
local_config, per_commitment_secret_seed = self.make_local_config(funding_sat * 1000, push_msat, REMOTE, feerate) local_config, per_commitment_secret_seed = self.make_local_config(funding_sat * 1000, push_msat, REMOTE)
# for the first commitment transaction # for the first commitment transaction
per_commitment_secret_first = get_per_commitment_secret_from_seed(per_commitment_secret_seed, RevocationStore.START_INDEX) per_commitment_secret_first = get_per_commitment_secret_from_seed(per_commitment_secret_seed, RevocationStore.START_INDEX)
@ -723,7 +721,6 @@ class Peer(PrintError):
ctn = -1, ctn = -1,
amount_msat=remote_balance_sat, amount_msat=remote_balance_sat,
next_htlc_id = 0, next_htlc_id = 0,
feerate=feerate,
next_per_commitment_point=payload['first_per_commitment_point'], next_per_commitment_point=payload['first_per_commitment_point'],
current_per_commitment_point=None, current_per_commitment_point=None,
@ -737,7 +734,7 @@ class Peer(PrintError):
current_commitment_signature = None, current_commitment_signature = None,
current_htlc_signatures = None, current_htlc_signatures = None,
), ),
"constraints": ChannelConstraints(capacity=funding_sat, is_initiator=False, funding_txn_minimum_depth=min_depth), "constraints": ChannelConstraints(capacity=funding_sat, is_initiator=False, funding_txn_minimum_depth=min_depth, feerate=feerate),
"remote_commitment_to_be_revoked": None, "remote_commitment_to_be_revoked": None,
} }
m = HTLCStateMachine(chan) m = HTLCStateMachine(chan)

59
electrum/lnhtlc.py

@ -1,5 +1,5 @@
# ported from lnd 42de4400bff5105352d0552155f73589166d162b # ported from lnd 42de4400bff5105352d0552155f73589166d162b
from collections import namedtuple from collections import namedtuple, defaultdict
import binascii import binascii
import json import json
from enum import Enum, auto from enum import Enum, auto
@ -34,26 +34,14 @@ FUNDEE_SIGNED = FeeUpdateProgress.FUNDEE_SIGNED
FUNDEE_ACKED = FeeUpdateProgress.FUNDEE_ACKED FUNDEE_ACKED = FeeUpdateProgress.FUNDEE_ACKED
FUNDER_SIGNED = FeeUpdateProgress.FUNDER_SIGNED FUNDER_SIGNED = FeeUpdateProgress.FUNDER_SIGNED
from collections import namedtuple class FeeUpdate(defaultdict):
class FeeUpdate:
def __init__(self, chan, rate): def __init__(self, chan, rate):
super().__init__(lambda: False)
self.rate = rate self.rate = rate
self.progress = {FUNDEE_SIGNED: None, FUNDEE_ACKED: None, FUNDER_SIGNED: None}
self.chan = chan self.chan = chan
def set(self, field):
self.progress[field] = self.chan.current_height[LOCAL if self.chan.constraints.is_initiator else REMOTE]
def had(self, field):
"""
returns true when the progress field given has been
set at the current commitment number of the funder
"""
return self.progress[field] is not None
def pending_feerate(self, subject): def pending_feerate(self, subject):
if self.had(FUNDEE_ACKED): if self[FUNDEE_ACKED]:
return self.rate return self.rate
if subject == REMOTE and self.chan.constraints.is_initiator: if subject == REMOTE and self.chan.constraints.is_initiator:
return self.rate return self.rate
@ -230,9 +218,9 @@ class HTLCStateMachine(PrintError):
for pending_fee in self.fee_mgr: for pending_fee in self.fee_mgr:
if not self.constraints.is_initiator: if not self.constraints.is_initiator:
pending_fee.set(FUNDEE_SIGNED) pending_fee[FUNDEE_SIGNED] = True
if self.constraints.is_initiator and pending_fee.had(FUNDEE_ACKED): if self.constraints.is_initiator and pending_fee[FUNDEE_ACKED]:
pending_fee.set(FUNDER_SIGNED) pending_fee[FUNDER_SIGNED] = True
self.process_new_offchain_ctx(pending_remote_commitment, ours=False) self.process_new_offchain_ctx(pending_remote_commitment, ours=False)
@ -283,9 +271,9 @@ class HTLCStateMachine(PrintError):
for pending_fee in self.fee_mgr: for pending_fee in self.fee_mgr:
if not self.constraints.is_initiator: if not self.constraints.is_initiator:
pending_fee.set(FUNDEE_SIGNED) pending_fee[FUNDEE_SIGNED] = True
if self.constraints.is_initiator and pending_fee.had(FUNDEE_ACKED): if self.constraints.is_initiator and pending_fee[FUNDEE_ACKED]:
pending_fee.set(FUNDER_SIGNED) pending_fee[FUNDER_SIGNED] = True
self.process_new_offchain_ctx(pending_local_commitment, ours=True) self.process_new_offchain_ctx(pending_local_commitment, ours=True)
@ -305,25 +293,23 @@ class HTLCStateMachine(PrintError):
last_secret, this_point, next_point = self.points last_secret, this_point, next_point = self.points
new_local_feerate = self.config[LOCAL].feerate new_feerate = self.constraints.feerate
new_remote_feerate = self.config[REMOTE].feerate
for pending_fee in self.fee_mgr[:]: for pending_fee in self.fee_mgr[:]:
if not self.constraints.is_initiator and pending_fee.had(FUNDEE_SIGNED): if not self.constraints.is_initiator and pending_fee[FUNDEE_SIGNED]:
new_local_feerate = new_remote_feerate = pending_fee.rate new_feerate = pending_fee.rate
self.fee_mgr.remove(pending_fee) self.fee_mgr.remove(pending_fee)
print("FEERATE CHANGE COMPLETE (non-initiator)") print("FEERATE CHANGE COMPLETE (non-initiator)")
if self.constraints.is_initiator and pending_fee.had(FUNDER_SIGNED): if self.constraints.is_initiator and pending_fee[FUNDER_SIGNED]:
new_local_feerate = new_remote_feerate = pending_fee.rate new_feerate = pending_fee.rate
self.fee_mgr.remove(pending_fee) self.fee_mgr.remove(pending_fee)
print("FEERATE CHANGE COMPLETE (initiator)") print("FEERATE CHANGE COMPLETE (initiator)")
self.config[LOCAL]=self.config[LOCAL]._replace( self.config[LOCAL]=self.config[LOCAL]._replace(
ctn=self.config[LOCAL].ctn + 1, ctn=self.config[LOCAL].ctn + 1,
feerate=new_local_feerate
) )
self.config[REMOTE]=self.config[REMOTE]._replace( self.constraints=self.constraints._replace(
feerate=new_remote_feerate feerate=new_feerate
) )
self.local_commitment = self.pending_local_commitment self.local_commitment = self.pending_local_commitment
@ -427,7 +413,7 @@ class HTLCStateMachine(PrintError):
for pending_fee in self.fee_mgr: for pending_fee in self.fee_mgr:
if self.constraints.is_initiator: if self.constraints.is_initiator:
pending_fee.set(FUNDEE_ACKED) pending_fee[FUNDEE_ACKED] = True
self.local_commitment = self.pending_local_commitment self.local_commitment = self.pending_local_commitment
self.remote_commitment = self.pending_remote_commitment self.remote_commitment = self.pending_remote_commitment
@ -471,18 +457,13 @@ class HTLCStateMachine(PrintError):
return self.make_commitment(REMOTE, this_point) return self.make_commitment(REMOTE, this_point)
def pending_feerate(self, subject): def pending_feerate(self, subject):
candidate = None candidate = self.constraints.feerate
for pending_fee in self.fee_mgr: for pending_fee in self.fee_mgr:
x = pending_fee.pending_feerate(subject) x = pending_fee.pending_feerate(subject)
if x is not None: if x is not None:
candidate = x candidate = x
feerate = candidate if candidate is not None else self._committed_feerate[subject] return candidate
return feerate
@property
def _committed_feerate(self):
return {LOCAL: self.config[LOCAL].feerate, REMOTE: self.config[REMOTE].feerate}
@property @property
def pending_local_commitment(self): def pending_local_commitment(self):

3
electrum/lnutil.py

@ -26,7 +26,6 @@ common = [
('ctn' , int), ('ctn' , int),
('amount_msat' , int), ('amount_msat' , int),
('next_htlc_id' , int), ('next_htlc_id' , int),
('feerate' , int),
('payment_basepoint' , Keypair), ('payment_basepoint' , Keypair),
('multisig_key' , Keypair), ('multisig_key' , Keypair),
('htlc_basepoint' , Keypair), ('htlc_basepoint' , Keypair),
@ -49,7 +48,7 @@ LocalConfig = NamedTuple('LocalConfig', common + [
('current_htlc_signatures', List[bytes]), ('current_htlc_signatures', List[bytes]),
]) ])
ChannelConstraints = namedtuple("ChannelConstraints", ["capacity", "is_initiator", "funding_txn_minimum_depth"]) ChannelConstraints = namedtuple("ChannelConstraints", ["capacity", "is_initiator", "funding_txn_minimum_depth", "feerate"])
ScriptHtlc = namedtuple('ScriptHtlc', ['redeem_script', 'htlc']) ScriptHtlc = namedtuple('ScriptHtlc', ['redeem_script', 'htlc'])

31
electrum/tests/test_lnhtlc.py

@ -34,7 +34,6 @@ def create_channel_state(funding_txid, funding_index, funding_sat, local_feerate
initial_msat=remote_amount, initial_msat=remote_amount,
ctn = 0, ctn = 0,
next_htlc_id = 0, next_htlc_id = 0,
feerate=local_feerate,
amount_msat=remote_amount, amount_msat=remote_amount,
next_per_commitment_point=nex, next_per_commitment_point=nex,
@ -54,7 +53,6 @@ def create_channel_state(funding_txid, funding_index, funding_sat, local_feerate
initial_msat=local_amount, initial_msat=local_amount,
ctn = 0, ctn = 0,
next_htlc_id = 0, next_htlc_id = 0,
feerate=local_feerate,
amount_msat=local_amount, amount_msat=local_amount,
per_commitment_secret_seed=seed, per_commitment_secret_seed=seed,
@ -63,7 +61,12 @@ def create_channel_state(funding_txid, funding_index, funding_sat, local_feerate
current_commitment_signature=None, current_commitment_signature=None,
current_htlc_signatures=None, current_htlc_signatures=None,
), ),
"constraints":lnbase.ChannelConstraints(capacity=funding_sat, is_initiator=is_initiator, funding_txn_minimum_depth=3), "constraints":lnbase.ChannelConstraints(
capacity=funding_sat,
is_initiator=is_initiator,
funding_txn_minimum_depth=3,
feerate=local_feerate,
),
"node_id":other_node_id, "node_id":other_node_id,
"remote_commitment_to_be_revoked": None, "remote_commitment_to_be_revoked": None,
'onion_keys': {}, 'onion_keys': {},
@ -271,20 +274,20 @@ class TestLNBaseHTLCStateMachine(unittest.TestCase):
bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs) bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs)
self.assertNotEqual(fee, bob_channel.config[LOCAL].feerate) self.assertNotEqual(fee, bob_channel.constraints.feerate)
rev, _ = bob_channel.revoke_current_commitment() rev, _ = bob_channel.revoke_current_commitment()
self.assertEqual(fee, bob_channel.config[LOCAL].feerate) self.assertEqual(fee, bob_channel.constraints.feerate)
bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment() bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment()
alice_channel.receive_revocation(rev) alice_channel.receive_revocation(rev)
alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs) alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs)
self.assertNotEqual(fee, alice_channel.config[LOCAL].feerate) self.assertNotEqual(fee, alice_channel.constraints.feerate)
rev, _ = alice_channel.revoke_current_commitment() rev, _ = alice_channel.revoke_current_commitment()
self.assertEqual(fee, alice_channel.config[LOCAL].feerate) self.assertEqual(fee, alice_channel.constraints.feerate)
bob_channel.receive_revocation(rev) bob_channel.receive_revocation(rev)
self.assertEqual(fee, bob_channel.config[REMOTE].feerate) self.assertEqual(fee, bob_channel.constraints.feerate)
def test_UpdateFeeReceiverCommits(self): def test_UpdateFeeReceiverCommits(self):
@ -300,20 +303,20 @@ class TestLNBaseHTLCStateMachine(unittest.TestCase):
alice_sig, alice_htlc_sigs = alice_channel.sign_next_commitment() alice_sig, alice_htlc_sigs = alice_channel.sign_next_commitment()
bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs) bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs)
self.assertNotEqual(fee, bob_channel.config[LOCAL].feerate) self.assertNotEqual(fee, bob_channel.constraints.feerate)
bob_revocation, _ = bob_channel.revoke_current_commitment() bob_revocation, _ = bob_channel.revoke_current_commitment()
self.assertEqual(fee, bob_channel.config[LOCAL].feerate) self.assertEqual(fee, bob_channel.constraints.feerate)
bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment() bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment()
alice_channel.receive_revocation(bob_revocation) alice_channel.receive_revocation(bob_revocation)
alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs) alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs)
self.assertNotEqual(fee, alice_channel.config[LOCAL].feerate) self.assertNotEqual(fee, alice_channel.constraints.feerate)
alice_revocation, _ = alice_channel.revoke_current_commitment() alice_revocation, _ = alice_channel.revoke_current_commitment()
self.assertEqual(fee, alice_channel.config[LOCAL].feerate) self.assertEqual(fee, alice_channel.constraints.feerate)
bob_channel.receive_revocation(alice_revocation) bob_channel.receive_revocation(alice_revocation)
self.assertEqual(fee, bob_channel.config[REMOTE].feerate) self.assertEqual(fee, bob_channel.constraints.feerate)
@ -323,7 +326,7 @@ class TestLNHTLCDust(unittest.TestCase):
paymentPreimage = b"\x01" * 32 paymentPreimage = b"\x01" * 32
paymentHash = bitcoin.sha256(paymentPreimage) paymentHash = bitcoin.sha256(paymentPreimage)
fee_per_kw = alice_channel.config[LOCAL].feerate fee_per_kw = alice_channel.constraints.feerate
self.assertEqual(fee_per_kw, 6000) self.assertEqual(fee_per_kw, 6000)
htlcAmt = 500 + lnutil.HTLC_TIMEOUT_WEIGHT * (fee_per_kw // 1000) htlcAmt = 500 + lnutil.HTLC_TIMEOUT_WEIGHT * (fee_per_kw // 1000)
self.assertEqual(htlcAmt, 4478) self.assertEqual(htlcAmt, 4478)

Loading…
Cancel
Save