lnhtlc: fee update upgrade and passes ReciverCommits and SenderCommits tests, fix NameErrors in lnbase
This commit is contained in:
parent
0679278eb2
commit
16e541f642
@ -596,7 +596,8 @@ class Peer(PrintError):
|
|||||||
current_per_commitment_point=None,
|
current_per_commitment_point=None,
|
||||||
amount_msat=remote_amount,
|
amount_msat=remote_amount,
|
||||||
revocation_store=their_revocation_store,
|
revocation_store=their_revocation_store,
|
||||||
next_htlc_id = 0
|
next_htlc_id = 0,
|
||||||
|
feerate=local_feerate
|
||||||
),
|
),
|
||||||
"local_state": LocalState(
|
"local_state": LocalState(
|
||||||
ctn = -1,
|
ctn = -1,
|
||||||
@ -605,9 +606,10 @@ class Peer(PrintError):
|
|||||||
next_htlc_id = 0,
|
next_htlc_id = 0,
|
||||||
funding_locked_received = False,
|
funding_locked_received = False,
|
||||||
was_announced = False,
|
was_announced = False,
|
||||||
current_commitment_signature = None
|
current_commitment_signature = None,
|
||||||
|
feerate=local_feerate
|
||||||
),
|
),
|
||||||
"constraints": ChannelConstraints(capacity=funding_sat, feerate=local_feerate, 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)
|
||||||
}
|
}
|
||||||
m = HTLCStateMachine(chan)
|
m = HTLCStateMachine(chan)
|
||||||
sig_64, _ = m.sign_next_commitment()
|
sig_64, _ = m.sign_next_commitment()
|
||||||
@ -942,13 +944,13 @@ class Peer(PrintError):
|
|||||||
|
|
||||||
await self.receive_revoke(chan)
|
await self.receive_revoke(chan)
|
||||||
|
|
||||||
m.settle_htlc(payment_preimage, htlc_id)
|
chan.settle_htlc(payment_preimage, htlc_id)
|
||||||
self.send_message(gen_msg("update_fulfill_htlc", channel_id=channel_id, id=htlc_id, payment_preimage=payment_preimage))
|
self.send_message(gen_msg("update_fulfill_htlc", channel_id=channel_id, id=htlc_id, payment_preimage=payment_preimage))
|
||||||
|
|
||||||
# remote commitment transaction without htlcs
|
# remote commitment transaction without htlcs
|
||||||
bare_ctx = chan.make_commitment(m.remote_state.ctn + 1, False, m.remote_state.next_per_commitment_point,
|
bare_ctx = chan.make_commitment(chan.remote_state.ctn + 1, False, chan.remote_state.next_per_commitment_point,
|
||||||
m.remote_state.amount_msat - expected_received_msat, m.local_state.amount_msat + expected_received_msat)
|
chan.remote_state.amount_msat - expected_received_msat, chan.local_state.amount_msat + expected_received_msat)
|
||||||
sig_64 = sign_and_get_sig_string(bare_ctx, m.local_config, m.remote_config)
|
sig_64 = sign_and_get_sig_string(bare_ctx, chan.local_config, chan.remote_config)
|
||||||
self.send_message(gen_msg("commitment_signed", channel_id=channel_id, signature=sig_64, num_htlcs=0))
|
self.send_message(gen_msg("commitment_signed", channel_id=channel_id, signature=sig_64, num_htlcs=0))
|
||||||
|
|
||||||
await self.receive_revoke(chan)
|
await self.receive_revoke(chan)
|
||||||
|
|||||||
235
lib/lnhtlc.py
235
lib/lnhtlc.py
@ -12,10 +12,36 @@ from .lnutil import secret_to_pubkey, derive_privkey, derive_pubkey, derive_blin
|
|||||||
from .lnutil import sign_and_get_sig_string
|
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 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 HTLC_TIMEOUT_WEIGHT, HTLC_SUCCESS_WEIGHT
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
SettleHtlc = namedtuple("SettleHtlc", ["htlc_id"])
|
SettleHtlc = namedtuple("SettleHtlc", ["htlc_id"])
|
||||||
RevokeAndAck = namedtuple("RevokeAndAck", ["per_commitment_secret", "next_per_commitment_point"])
|
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)
|
||||||
|
|
||||||
class UpdateAddHtlc:
|
class UpdateAddHtlc:
|
||||||
def __init__(self, amount_msat, payment_hash, cltv_expiry, total_fee):
|
def __init__(self, amount_msat, payment_hash, cltv_expiry, total_fee):
|
||||||
self.amount_msat = amount_msat
|
self.amount_msat = amount_msat
|
||||||
@ -107,7 +133,11 @@ class HTLCStateMachine(PrintError):
|
|||||||
|
|
||||||
self.total_msat_sent = 0
|
self.total_msat_sent = 0
|
||||||
self.total_msat_received = 0
|
self.total_msat_received = 0
|
||||||
self.pending_feerate = None
|
self.pending_fee_update = None
|
||||||
|
self.pending_ack_fee_update = None
|
||||||
|
|
||||||
|
self.local_commitment = self.pending_local_commitment
|
||||||
|
self.remote_commitment = self.pending_remote_commitment
|
||||||
|
|
||||||
def add_htlc(self, htlc):
|
def add_htlc(self, htlc):
|
||||||
"""
|
"""
|
||||||
@ -154,30 +184,35 @@ class HTLCStateMachine(PrintError):
|
|||||||
if htlc.l_locked_in is None: htlc.l_locked_in = self.local_state.ctn
|
if htlc.l_locked_in is None: htlc.l_locked_in = self.local_state.ctn
|
||||||
self.print_error("sign_next_commitment")
|
self.print_error("sign_next_commitment")
|
||||||
|
|
||||||
sig_64 = sign_and_get_sig_string(self.remote_commitment, self.local_config, self.remote_config)
|
if self.constraints.is_initiator and self.pending_fee_update:
|
||||||
|
self.pending_ack_fee_update = self.pending_fee_update
|
||||||
|
self.pending_fee_update = None
|
||||||
|
|
||||||
their_remote_htlc_privkey_number = derive_privkey(
|
with PendingFeerateApplied(self):
|
||||||
int.from_bytes(self.local_config.htlc_basepoint.privkey, 'big'),
|
sig_64 = sign_and_get_sig_string(self.pending_remote_commitment, self.local_config, self.remote_config)
|
||||||
self.remote_state.next_per_commitment_point)
|
|
||||||
their_remote_htlc_privkey = their_remote_htlc_privkey_number.to_bytes(32, 'big')
|
|
||||||
|
|
||||||
for_us = False
|
their_remote_htlc_privkey_number = derive_privkey(
|
||||||
|
int.from_bytes(self.local_config.htlc_basepoint.privkey, 'big'),
|
||||||
|
self.remote_state.next_per_commitment_point)
|
||||||
|
their_remote_htlc_privkey = their_remote_htlc_privkey_number.to_bytes(32, 'big')
|
||||||
|
|
||||||
htlcsigs = []
|
for_us = False
|
||||||
for we_receive, htlcs in zip([True, False], [self.htlcs_in_remote, self.htlcs_in_local]):
|
|
||||||
assert len(htlcs) <= 1
|
|
||||||
for htlc in htlcs:
|
|
||||||
weight = HTLC_SUCCESS_WEIGHT if we_receive else HTLC_TIMEOUT_WEIGHT
|
|
||||||
if htlc.amount_msat // 1000 - weight * (self.constraints.feerate // 1000) < self.remote_config.dust_limit_sat:
|
|
||||||
continue
|
|
||||||
original_htlc_output_index = 0
|
|
||||||
args = [self.remote_state.next_per_commitment_point, for_us, we_receive, htlc.amount_msat + htlc.total_fee, htlc.cltv_expiry, htlc.payment_hash, self.remote_commitment, original_htlc_output_index]
|
|
||||||
htlc_tx = make_htlc_tx_with_open_channel(self, *args)
|
|
||||||
sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey))
|
|
||||||
htlc_sig = ecc.sig_string_from_der_sig(sig[:-1])
|
|
||||||
htlcsigs.append(htlc_sig)
|
|
||||||
|
|
||||||
return sig_64, htlcsigs
|
htlcsigs = []
|
||||||
|
for we_receive, htlcs in zip([True, False], [self.htlcs_in_remote, self.htlcs_in_local]):
|
||||||
|
assert len(htlcs) <= 1
|
||||||
|
for htlc in htlcs:
|
||||||
|
weight = HTLC_SUCCESS_WEIGHT if we_receive else HTLC_TIMEOUT_WEIGHT
|
||||||
|
if htlc.amount_msat // 1000 - weight * (self.remote_state.feerate // 1000) < self.remote_config.dust_limit_sat:
|
||||||
|
continue
|
||||||
|
original_htlc_output_index = 0
|
||||||
|
args = [self.remote_state.next_per_commitment_point, for_us, we_receive, htlc.amount_msat + htlc.total_fee, htlc.cltv_expiry, htlc.payment_hash, self.pending_remote_commitment, original_htlc_output_index]
|
||||||
|
htlc_tx = make_htlc_tx_with_open_channel(self, *args)
|
||||||
|
sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey))
|
||||||
|
htlc_sig = ecc.sig_string_from_der_sig(sig[:-1])
|
||||||
|
htlcsigs.append(htlc_sig)
|
||||||
|
|
||||||
|
return sig_64, htlcsigs
|
||||||
|
|
||||||
def receive_new_commitment(self, sig, htlc_sigs):
|
def receive_new_commitment(self, sig, htlc_sigs):
|
||||||
"""
|
"""
|
||||||
@ -197,26 +232,31 @@ class HTLCStateMachine(PrintError):
|
|||||||
if htlc.r_locked_in is None: htlc.r_locked_in = self.remote_state.ctn
|
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
|
assert len(htlc_sigs) == 0 or type(htlc_sigs[0]) is bytes
|
||||||
|
|
||||||
preimage_hex = self.local_commitment.serialize_preimage(0)
|
if not self.constraints.is_initiator:
|
||||||
pre_hash = Hash(bfh(preimage_hex))
|
self.pending_ack_fee_update = self.pending_fee_update
|
||||||
if not ecc.verify_signature(self.remote_config.multisig_key.pubkey, sig, pre_hash):
|
self.pending_fee_update = None
|
||||||
raise Exception('failed verifying signature of our updated commitment transaction: ' + str(sig))
|
|
||||||
|
|
||||||
_, this_point, _ = self.points
|
with PendingFeerateApplied(self):
|
||||||
|
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):
|
||||||
|
raise Exception('failed verifying signature of our updated commitment transaction: ' + str(sig))
|
||||||
|
|
||||||
if len(self.htlcs_in_remote) > 0 and len(self.local_commitment.outputs()) == 3:
|
_, this_point, _ = self.points
|
||||||
print("CHECKING HTLC SIGS")
|
|
||||||
we_receive = True
|
|
||||||
payment_hash = self.htlcs_in_remote[0].payment_hash
|
|
||||||
amount_msat = self.htlcs_in_remote[0].amount_msat
|
|
||||||
cltv_expiry = self.htlcs_in_remote[0].cltv_expiry
|
|
||||||
htlc_tx = make_htlc_tx_with_open_channel(self, this_point, True, we_receive, amount_msat, cltv_expiry, payment_hash, self.local_commitment, 0)
|
|
||||||
pre_hash = Hash(bfh(htlc_tx.serialize_preimage(0)))
|
|
||||||
remote_htlc_pubkey = derive_pubkey(self.remote_config.htlc_basepoint.pubkey, this_point)
|
|
||||||
if not ecc.verify_signature(remote_htlc_pubkey, htlc_sigs[0], pre_hash):
|
|
||||||
raise Exception("failed verifying signature an HTLC tx spending from one of our commit tx'es HTLC outputs")
|
|
||||||
|
|
||||||
# TODO check htlc in htlcs_in_local
|
if len(self.htlcs_in_remote) > 0 and len(self.pending_local_commitment.outputs()) == 3:
|
||||||
|
print("CHECKING HTLC SIGS")
|
||||||
|
we_receive = True
|
||||||
|
payment_hash = self.htlcs_in_remote[0].payment_hash
|
||||||
|
amount_msat = self.htlcs_in_remote[0].amount_msat
|
||||||
|
cltv_expiry = self.htlcs_in_remote[0].cltv_expiry
|
||||||
|
htlc_tx = make_htlc_tx_with_open_channel(self, this_point, True, we_receive, amount_msat, cltv_expiry, payment_hash, self.pending_local_commitment, 0)
|
||||||
|
pre_hash = Hash(bfh(htlc_tx.serialize_preimage(0)))
|
||||||
|
remote_htlc_pubkey = derive_pubkey(self.remote_config.htlc_basepoint.pubkey, this_point)
|
||||||
|
if not ecc.verify_signature(remote_htlc_pubkey, htlc_sigs[0], pre_hash):
|
||||||
|
raise Exception("failed verifying signature an HTLC tx spending from one of our commit tx'es HTLC outputs")
|
||||||
|
|
||||||
|
# TODO check htlc in htlcs_in_local
|
||||||
|
|
||||||
def revoke_current_commitment(self):
|
def revoke_current_commitment(self):
|
||||||
"""
|
"""
|
||||||
@ -233,18 +273,28 @@ class HTLCStateMachine(PrintError):
|
|||||||
|
|
||||||
last_secret, this_point, next_point = self.points
|
last_secret, this_point, next_point = self.points
|
||||||
|
|
||||||
if self.pending_feerate is not None:
|
new_feerate = self.local_state.feerate
|
||||||
new_feerate = self.pending_feerate
|
|
||||||
else:
|
|
||||||
new_feerate = self.constraints.feerate
|
|
||||||
|
|
||||||
self.local_state=self.local_state._replace(
|
if not self.constraints.is_initiator and self.pending_fee_update is not None:
|
||||||
ctn=self.local_state.ctn + 1
|
new_feerate = self.pending_fee_update
|
||||||
)
|
self.pending_fee_update = None
|
||||||
self.constraints=self.constraints._replace(
|
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
|
feerate=new_feerate
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.local_state=self.local_state._replace(
|
||||||
|
ctn=self.local_state.ctn + 1,
|
||||||
|
feerate=new_feerate
|
||||||
|
)
|
||||||
|
|
||||||
|
self.local_commitment = self.pending_local_commitment
|
||||||
|
|
||||||
return RevokeAndAck(last_secret, next_point), "current htlcs"
|
return RevokeAndAck(last_secret, next_point), "current htlcs"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -322,11 +372,14 @@ class HTLCStateMachine(PrintError):
|
|||||||
ctn=self.remote_state.ctn + 1,
|
ctn=self.remote_state.ctn + 1,
|
||||||
current_per_commitment_point=next_point,
|
current_per_commitment_point=next_point,
|
||||||
next_per_commitment_point=revocation.next_per_commitment_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
|
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
|
||||||
)
|
)
|
||||||
self.local_state=self.local_state._replace(
|
self.local_state=self.local_state._replace(
|
||||||
amount_msat = self.local_state.amount_msat + (received_this_batch - sent_this_batch) - sent_fees + received_fees
|
amount_msat = self.local_state.amount_msat + (received_this_batch - sent_this_batch) - sent_fees + received_fees
|
||||||
)
|
)
|
||||||
|
self.local_commitment = self.pending_local_commitment
|
||||||
|
self.remote_commitment = self.pending_remote_commitment
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def htlcsum(htlcs):
|
def htlcsum(htlcs):
|
||||||
@ -351,7 +404,7 @@ class HTLCStateMachine(PrintError):
|
|||||||
return remote_msat, total_fee_remote, local_msat, total_fee_local
|
return remote_msat, total_fee_remote, local_msat, total_fee_local
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def remote_commitment(self):
|
def pending_remote_commitment(self):
|
||||||
remote_msat, total_fee_remote, local_msat, total_fee_local = self.amounts()
|
remote_msat, total_fee_remote, local_msat, total_fee_local = self.amounts()
|
||||||
assert local_msat >= 0
|
assert local_msat >= 0
|
||||||
assert remote_msat >= 0
|
assert remote_msat >= 0
|
||||||
@ -364,29 +417,30 @@ class HTLCStateMachine(PrintError):
|
|||||||
|
|
||||||
trimmed = 0
|
trimmed = 0
|
||||||
|
|
||||||
htlcs_in_local = []
|
with PendingFeerateApplied(self):
|
||||||
for htlc in self.htlcs_in_local:
|
htlcs_in_local = []
|
||||||
if htlc.amount_msat // 1000 - HTLC_SUCCESS_WEIGHT * (self.constraints.feerate // 1000) < self.remote_config.dust_limit_sat:
|
for htlc in self.htlcs_in_local:
|
||||||
trimmed += htlc.amount_msat // 1000
|
if htlc.amount_msat // 1000 - HTLC_SUCCESS_WEIGHT * (self.remote_state.feerate // 1000) < self.remote_config.dust_limit_sat:
|
||||||
continue
|
trimmed += htlc.amount_msat // 1000
|
||||||
htlcs_in_local.append(
|
continue
|
||||||
( make_received_htlc(local_revocation_pubkey, local_htlc_pubkey, remote_htlc_pubkey, htlc.payment_hash, htlc.cltv_expiry), htlc.amount_msat + htlc.total_fee))
|
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))
|
||||||
|
|
||||||
htlcs_in_remote = []
|
htlcs_in_remote = []
|
||||||
for htlc in self.htlcs_in_remote:
|
for htlc in self.htlcs_in_remote:
|
||||||
if htlc.amount_msat // 1000 - HTLC_TIMEOUT_WEIGHT * (self.constraints.feerate // 1000) < self.remote_config.dust_limit_sat:
|
if htlc.amount_msat // 1000 - HTLC_TIMEOUT_WEIGHT * (self.remote_state.feerate // 1000) < self.remote_config.dust_limit_sat:
|
||||||
trimmed += htlc.amount_msat // 1000
|
trimmed += htlc.amount_msat // 1000
|
||||||
continue
|
continue
|
||||||
htlcs_in_remote.append(
|
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))
|
( 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,
|
commit = self.make_commitment(self.remote_state.ctn + 1,
|
||||||
False, this_point,
|
False, this_point,
|
||||||
remote_msat - total_fee_remote, local_msat - total_fee_local, htlcs_in_local + htlcs_in_remote, trimmed)
|
remote_msat - total_fee_remote, local_msat - total_fee_local, htlcs_in_local + htlcs_in_remote, trimmed)
|
||||||
return commit
|
return commit
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def local_commitment(self):
|
def pending_local_commitment(self):
|
||||||
remote_msat, total_fee_remote, local_msat, total_fee_local = self.amounts()
|
remote_msat, total_fee_remote, local_msat, total_fee_local = self.amounts()
|
||||||
assert local_msat >= 0
|
assert local_msat >= 0
|
||||||
assert remote_msat >= 0
|
assert remote_msat >= 0
|
||||||
@ -399,26 +453,27 @@ class HTLCStateMachine(PrintError):
|
|||||||
|
|
||||||
trimmed = 0
|
trimmed = 0
|
||||||
|
|
||||||
htlcs_in_local = []
|
with PendingFeerateApplied(self):
|
||||||
for htlc in self.htlcs_in_local:
|
htlcs_in_local = []
|
||||||
if htlc.amount_msat // 1000 - HTLC_TIMEOUT_WEIGHT * (self.constraints.feerate // 1000) < self.local_config.dust_limit_sat:
|
for htlc in self.htlcs_in_local:
|
||||||
trimmed += htlc.amount_msat // 1000
|
if htlc.amount_msat // 1000 - HTLC_TIMEOUT_WEIGHT * (self.local_state.feerate // 1000) < self.local_config.dust_limit_sat:
|
||||||
continue
|
trimmed += htlc.amount_msat // 1000
|
||||||
htlcs_in_local.append(
|
continue
|
||||||
( make_offered_htlc(remote_revocation_pubkey, remote_htlc_pubkey, local_htlc_pubkey, htlc.payment_hash), htlc.amount_msat + htlc.total_fee))
|
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 = []
|
htlcs_in_remote = []
|
||||||
for htlc in self.htlcs_in_remote:
|
for htlc in self.htlcs_in_remote:
|
||||||
if htlc.amount_msat // 1000 - HTLC_SUCCESS_WEIGHT * (self.constraints.feerate // 1000) < self.local_config.dust_limit_sat:
|
if htlc.amount_msat // 1000 - HTLC_SUCCESS_WEIGHT * (self.local_state.feerate // 1000) < self.local_config.dust_limit_sat:
|
||||||
trimmed += htlc.amount_msat // 1000
|
trimmed += htlc.amount_msat // 1000
|
||||||
continue
|
continue
|
||||||
htlcs_in_remote.append(
|
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))
|
( 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,
|
commit = self.make_commitment(self.local_state.ctn + 1,
|
||||||
True, this_point,
|
True, this_point,
|
||||||
local_msat - total_fee_local, remote_msat - total_fee_remote, htlcs_in_local + htlcs_in_remote, trimmed)
|
local_msat - total_fee_local, remote_msat - total_fee_remote, htlcs_in_local + htlcs_in_remote, trimmed)
|
||||||
return commit
|
return commit
|
||||||
|
|
||||||
def gen_htlc_indices(self, subject, just_unsettled=True):
|
def gen_htlc_indices(self, subject, just_unsettled=True):
|
||||||
assert subject in ["local", "remote"]
|
assert subject in ["local", "remote"]
|
||||||
@ -479,10 +534,14 @@ class HTLCStateMachine(PrintError):
|
|||||||
return self.constraints.capacity - sum(x[2] for x in self.local_commitment.outputs())
|
return self.constraints.capacity - sum(x[2] for x in self.local_commitment.outputs())
|
||||||
|
|
||||||
def update_fee(self, fee):
|
def update_fee(self, fee):
|
||||||
self.pending_feerate = fee
|
if not self.constraints.is_initiator:
|
||||||
|
raise Exception("only initiator can update_fee, this counterparty is not initiator")
|
||||||
|
self.pending_fee_update = fee
|
||||||
|
|
||||||
def receive_update_fee(self, fee):
|
def receive_update_fee(self, fee):
|
||||||
self.pending_feerate = 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
|
||||||
|
|
||||||
def to_save(self):
|
def to_save(self):
|
||||||
return {
|
return {
|
||||||
@ -538,7 +597,7 @@ class HTLCStateMachine(PrintError):
|
|||||||
local_msat,
|
local_msat,
|
||||||
remote_msat,
|
remote_msat,
|
||||||
chan.local_config.dust_limit_sat,
|
chan.local_config.dust_limit_sat,
|
||||||
chan.constraints.feerate,
|
chan.local_state.feerate if for_us else chan.remote_state.feerate,
|
||||||
for_us,
|
for_us,
|
||||||
chan.constraints.is_initiator,
|
chan.constraints.is_initiator,
|
||||||
htlcs=htlcs,
|
htlcs=htlcs,
|
||||||
|
|||||||
@ -17,9 +17,9 @@ ChannelConfig = namedtuple("ChannelConfig", [
|
|||||||
"payment_basepoint", "multisig_key", "htlc_basepoint", "delayed_basepoint", "revocation_basepoint",
|
"payment_basepoint", "multisig_key", "htlc_basepoint", "delayed_basepoint", "revocation_basepoint",
|
||||||
"to_self_delay", "dust_limit_sat", "max_htlc_value_in_flight_msat", "max_accepted_htlcs"])
|
"to_self_delay", "dust_limit_sat", "max_htlc_value_in_flight_msat", "max_accepted_htlcs"])
|
||||||
OnlyPubkeyKeypair = namedtuple("OnlyPubkeyKeypair", ["pubkey"])
|
OnlyPubkeyKeypair = namedtuple("OnlyPubkeyKeypair", ["pubkey"])
|
||||||
RemoteState = namedtuple("RemoteState", ["ctn", "next_per_commitment_point", "amount_msat", "revocation_store", "current_per_commitment_point", "next_htlc_id"])
|
RemoteState = namedtuple("RemoteState", ["ctn", "next_per_commitment_point", "amount_msat", "revocation_store", "current_per_commitment_point", "next_htlc_id", "feerate"])
|
||||||
LocalState = namedtuple("LocalState", ["ctn", "per_commitment_secret_seed", "amount_msat", "next_htlc_id", "funding_locked_received", "was_announced", "current_commitment_signature"])
|
LocalState = namedtuple("LocalState", ["ctn", "per_commitment_secret_seed", "amount_msat", "next_htlc_id", "funding_locked_received", "was_announced", "current_commitment_signature", "feerate"])
|
||||||
ChannelConstraints = namedtuple("ChannelConstraints", ["feerate", "capacity", "is_initiator", "funding_txn_minimum_depth"])
|
ChannelConstraints = namedtuple("ChannelConstraints", ["capacity", "is_initiator", "funding_txn_minimum_depth"])
|
||||||
#OpenChannel = namedtuple("OpenChannel", ["channel_id", "short_channel_id", "funding_outpoint", "local_config", "remote_config", "remote_state", "local_state", "constraints", "node_id"])
|
#OpenChannel = namedtuple("OpenChannel", ["channel_id", "short_channel_id", "funding_outpoint", "local_config", "remote_config", "remote_state", "local_state", "constraints", "node_id"])
|
||||||
|
|
||||||
class RevocationStore:
|
class RevocationStore:
|
||||||
@ -201,7 +201,7 @@ def make_htlc_tx_with_open_channel(chan, pcp, for_us, we_receive, amount_msat, c
|
|||||||
is_htlc_success = for_us == we_receive
|
is_htlc_success = for_us == we_receive
|
||||||
htlc_tx_output = make_htlc_tx_output(
|
htlc_tx_output = make_htlc_tx_output(
|
||||||
amount_msat = amount_msat,
|
amount_msat = amount_msat,
|
||||||
local_feerate = chan.constraints.feerate,
|
local_feerate = chan.local_state.feerate if for_us else chan.remote_state.feerate,
|
||||||
revocationpubkey=revocation_pubkey,
|
revocationpubkey=revocation_pubkey,
|
||||||
local_delayedpubkey=delayedpubkey,
|
local_delayedpubkey=delayedpubkey,
|
||||||
success = is_htlc_success,
|
success = is_htlc_success,
|
||||||
|
|||||||
@ -49,7 +49,8 @@ def create_channel_state(funding_txid, funding_index, funding_sat, local_feerate
|
|||||||
current_per_commitment_point=cur,
|
current_per_commitment_point=cur,
|
||||||
amount_msat=remote_amount,
|
amount_msat=remote_amount,
|
||||||
revocation_store=their_revocation_store,
|
revocation_store=their_revocation_store,
|
||||||
next_htlc_id = 0
|
next_htlc_id = 0,
|
||||||
|
feerate=local_feerate
|
||||||
),
|
),
|
||||||
"local_state":lnbase.LocalState(
|
"local_state":lnbase.LocalState(
|
||||||
ctn = 0,
|
ctn = 0,
|
||||||
@ -58,9 +59,10 @@ def create_channel_state(funding_txid, funding_index, funding_sat, local_feerate
|
|||||||
next_htlc_id = 0,
|
next_htlc_id = 0,
|
||||||
funding_locked_received=True,
|
funding_locked_received=True,
|
||||||
was_announced=False,
|
was_announced=False,
|
||||||
current_commitment_signature=None
|
current_commitment_signature=None,
|
||||||
|
feerate=local_feerate
|
||||||
),
|
),
|
||||||
"constraints":lnbase.ChannelConstraints(capacity=funding_sat, feerate=local_feerate, is_initiator=is_initiator, funding_txn_minimum_depth=3),
|
"constraints":lnbase.ChannelConstraints(capacity=funding_sat, is_initiator=is_initiator, funding_txn_minimum_depth=3),
|
||||||
"node_id":other_node_id
|
"node_id":other_node_id
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,19 +111,17 @@ class TestLNBaseHTLCStateMachine(unittest.TestCase):
|
|||||||
else:
|
else:
|
||||||
self.assertFalse()
|
self.assertFalse()
|
||||||
|
|
||||||
def test_SimpleAddSettleWorkflow(self):
|
def setUp(self):
|
||||||
|
|
||||||
# Create a test channel which will be used for the duration of this
|
# Create a test channel which will be used for the duration of this
|
||||||
# unittest. The channel will be funded evenly with Alice having 5 BTC,
|
# unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
# and Bob having 5 BTC.
|
# and Bob having 5 BTC.
|
||||||
alice_channel, bob_channel = create_test_channels()
|
self.alice_channel, self.bob_channel = create_test_channels()
|
||||||
|
|
||||||
paymentPreimage = b"\x01" * 32
|
self.paymentPreimage = b"\x01" * 32
|
||||||
paymentHash = bitcoin.sha256(paymentPreimage)
|
paymentHash = bitcoin.sha256(self.paymentPreimage)
|
||||||
htlcAmt = one_bitcoin_in_msat
|
self.htlc = lnhtlc.UpdateAddHtlc(
|
||||||
htlc = lnhtlc.UpdateAddHtlc(
|
|
||||||
payment_hash = paymentHash,
|
payment_hash = paymentHash,
|
||||||
amount_msat = htlcAmt,
|
amount_msat = one_bitcoin_in_msat,
|
||||||
cltv_expiry = 5,
|
cltv_expiry = 5,
|
||||||
total_fee = 0
|
total_fee = 0
|
||||||
)
|
)
|
||||||
@ -129,9 +129,13 @@ class TestLNBaseHTLCStateMachine(unittest.TestCase):
|
|||||||
# First Alice adds the outgoing HTLC to her local channel's state
|
# First Alice adds the outgoing HTLC to her local channel's state
|
||||||
# update log. Then Alice sends this wire message over to Bob who adds
|
# update log. Then Alice sends this wire message over to Bob who adds
|
||||||
# this htlc to his remote state update log.
|
# this htlc to his remote state update log.
|
||||||
aliceHtlcIndex = alice_channel.add_htlc(htlc)
|
self.aliceHtlcIndex = self.alice_channel.add_htlc(self.htlc)
|
||||||
|
|
||||||
bobHtlcIndex = bob_channel.receive_htlc(htlc)
|
self.bobHtlcIndex = self.bob_channel.receive_htlc(self.htlc)
|
||||||
|
|
||||||
|
def test_SimpleAddSettleWorkflow(self):
|
||||||
|
alice_channel, bob_channel = self.alice_channel, self.bob_channel
|
||||||
|
htlc = self.htlc
|
||||||
|
|
||||||
# Next alice commits this change by sending a signature message. Since
|
# Next alice commits this change by sending a signature message. Since
|
||||||
# we expect the messages to be ordered, Bob will receive the HTLC we
|
# we expect the messages to be ordered, Bob will receive the HTLC we
|
||||||
@ -192,15 +196,15 @@ class TestLNBaseHTLCStateMachine(unittest.TestCase):
|
|||||||
# them should be exactly the amount of the HTLC.
|
# them should be exactly the amount of the HTLC.
|
||||||
self.assertEqual(len(alice_channel.local_commitment.outputs()), 3, "alice should have three commitment outputs, instead have %s"% len(alice_channel.local_commitment.outputs()))
|
self.assertEqual(len(alice_channel.local_commitment.outputs()), 3, "alice should have three commitment outputs, instead have %s"% len(alice_channel.local_commitment.outputs()))
|
||||||
self.assertEqual(len(bob_channel.local_commitment.outputs()), 3, "bob should have three commitment outputs, instead have %s"% len(bob_channel.local_commitment.outputs()))
|
self.assertEqual(len(bob_channel.local_commitment.outputs()), 3, "bob should have three commitment outputs, instead have %s"% len(bob_channel.local_commitment.outputs()))
|
||||||
self.assertOutputExistsByValue(alice_channel.local_commitment, htlcAmt // 1000)
|
self.assertOutputExistsByValue(alice_channel.local_commitment, htlc.amount_msat // 1000)
|
||||||
self.assertOutputExistsByValue(bob_channel.local_commitment, htlcAmt // 1000)
|
self.assertOutputExistsByValue(bob_channel.local_commitment, htlc.amount_msat // 1000)
|
||||||
|
|
||||||
# Now we'll repeat a similar exchange, this time with Bob settling the
|
# Now we'll repeat a similar exchange, this time with Bob settling the
|
||||||
# HTLC once he learns of the preimage.
|
# HTLC once he learns of the preimage.
|
||||||
preimage = paymentPreimage
|
preimage = self.paymentPreimage
|
||||||
bob_channel.settle_htlc(preimage, bobHtlcIndex)
|
bob_channel.settle_htlc(preimage, self.bobHtlcIndex)
|
||||||
|
|
||||||
alice_channel.receive_htlc_settle(preimage, aliceHtlcIndex)
|
alice_channel.receive_htlc_settle(preimage, self.aliceHtlcIndex)
|
||||||
|
|
||||||
bobSig2, bobHtlcSigs2 = bob_channel.sign_next_commitment()
|
bobSig2, bobHtlcSigs2 = bob_channel.sign_next_commitment()
|
||||||
alice_channel.receive_new_commitment(bobSig2, bobHtlcSigs2)
|
alice_channel.receive_new_commitment(bobSig2, bobHtlcSigs2)
|
||||||
@ -234,12 +238,71 @@ class TestLNBaseHTLCStateMachine(unittest.TestCase):
|
|||||||
self.assertEqual(alice_channel.local_update_log, [], "alice's local not updated, should be empty, has %s entries instead"% len(alice_channel.local_update_log))
|
self.assertEqual(alice_channel.local_update_log, [], "alice's local not updated, should be empty, has %s entries instead"% len(alice_channel.local_update_log))
|
||||||
self.assertEqual(alice_channel.remote_update_log, [], "alice's remote not updated, should be empty, has %s entries instead"% len(alice_channel.remote_update_log))
|
self.assertEqual(alice_channel.remote_update_log, [], "alice's remote not updated, should be empty, has %s entries instead"% len(alice_channel.remote_update_log))
|
||||||
|
|
||||||
|
def alice_to_bob_fee_update(self):
|
||||||
|
fee = 111
|
||||||
|
self.alice_channel.update_fee(fee)
|
||||||
|
self.bob_channel.receive_update_fee(fee)
|
||||||
|
return fee
|
||||||
|
|
||||||
|
def test_UpdateFeeSenderCommits(self):
|
||||||
|
fee = self.alice_to_bob_fee_update()
|
||||||
|
|
||||||
|
alice_channel, bob_channel = self.alice_channel, self.bob_channel
|
||||||
|
|
||||||
|
alice_sig, alice_htlc_sigs = alice_channel.sign_next_commitment()
|
||||||
|
bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs)
|
||||||
|
|
||||||
|
self.assertNotEqual(fee, bob_channel.local_state.feerate)
|
||||||
|
rev, _ = bob_channel.revoke_current_commitment()
|
||||||
|
self.assertEqual(fee, bob_channel.local_state.feerate)
|
||||||
|
|
||||||
|
bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment()
|
||||||
|
alice_channel.receive_revocation(rev)
|
||||||
|
alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs)
|
||||||
|
|
||||||
|
self.assertNotEqual(fee, alice_channel.local_state.feerate)
|
||||||
|
rev, _ = alice_channel.revoke_current_commitment()
|
||||||
|
self.assertEqual(fee, alice_channel.local_state.feerate)
|
||||||
|
|
||||||
|
bob_channel.receive_revocation(rev)
|
||||||
|
|
||||||
|
|
||||||
|
def test_UpdateFeeReceiverCommits(self):
|
||||||
|
fee = self.alice_to_bob_fee_update()
|
||||||
|
|
||||||
|
alice_channel, bob_channel = self.alice_channel, self.bob_channel
|
||||||
|
|
||||||
|
bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment()
|
||||||
|
alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs)
|
||||||
|
|
||||||
|
alice_revocation, _ = alice_channel.revoke_current_commitment()
|
||||||
|
bob_channel.receive_revocation(alice_revocation)
|
||||||
|
alice_sig, alice_htlc_sigs = alice_channel.sign_next_commitment()
|
||||||
|
bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs)
|
||||||
|
|
||||||
|
self.assertNotEqual(fee, bob_channel.local_state.feerate)
|
||||||
|
bob_revocation, _ = bob_channel.revoke_current_commitment()
|
||||||
|
self.assertEqual(fee, bob_channel.local_state.feerate)
|
||||||
|
|
||||||
|
bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment()
|
||||||
|
alice_channel.receive_revocation(bob_revocation)
|
||||||
|
alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs)
|
||||||
|
|
||||||
|
self.assertNotEqual(fee, alice_channel.local_state.feerate)
|
||||||
|
alice_revocation, _ = alice_channel.revoke_current_commitment()
|
||||||
|
self.assertEqual(fee, alice_channel.local_state.feerate)
|
||||||
|
|
||||||
|
bob_channel.receive_revocation(alice_revocation)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TestLNHTLCDust(unittest.TestCase):
|
||||||
def test_HTLCDustLimit(self):
|
def test_HTLCDustLimit(self):
|
||||||
alice_channel, bob_channel = create_test_channels()
|
alice_channel, bob_channel = create_test_channels()
|
||||||
|
|
||||||
paymentPreimage = b"\x01" * 32
|
paymentPreimage = b"\x01" * 32
|
||||||
paymentHash = bitcoin.sha256(paymentPreimage)
|
paymentHash = bitcoin.sha256(paymentPreimage)
|
||||||
fee_per_kw = alice_channel.constraints.feerate
|
fee_per_kw = alice_channel.local_state.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)
|
||||||
@ -263,32 +326,6 @@ class TestLNBaseHTLCStateMachine(unittest.TestCase):
|
|||||||
self.assertEqual(len(alice_channel.local_commitment.outputs()), 2)
|
self.assertEqual(len(alice_channel.local_commitment.outputs()), 2)
|
||||||
self.assertEqual(alice_channel.total_msat_sent // 1000, htlcAmt)
|
self.assertEqual(alice_channel.total_msat_sent // 1000, htlcAmt)
|
||||||
|
|
||||||
def test_UpdateFeeSenderCommits(self):
|
|
||||||
alice_channel, bob_channel = create_test_channels()
|
|
||||||
|
|
||||||
paymentPreimage = b"\x01" * 32
|
|
||||||
paymentHash = bitcoin.sha256(paymentPreimage)
|
|
||||||
htlc = lnhtlc.UpdateAddHtlc(
|
|
||||||
payment_hash = paymentHash,
|
|
||||||
amount_msat = one_bitcoin_in_msat,
|
|
||||||
cltv_expiry = 5, # also in create_test_channels
|
|
||||||
total_fee = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
aliceHtlcIndex = alice_channel.add_htlc(htlc)
|
|
||||||
bobHtlcIndex = bob_channel.receive_htlc(htlc)
|
|
||||||
|
|
||||||
fee = 111
|
|
||||||
alice_channel.update_fee(fee)
|
|
||||||
bob_channel.receive_update_fee(fee)
|
|
||||||
|
|
||||||
alice_sig, alice_htlc_sigs = alice_channel.sign_next_commitment()
|
|
||||||
bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs)
|
|
||||||
self.assertNotEqual(fee, alice_channel.constraints.feerate)
|
|
||||||
rev, _ = alice_channel.revoke_current_commitment()
|
|
||||||
self.assertEqual(fee, alice_channel.constraints.feerate)
|
|
||||||
bob_channel.receive_revocation(rev)
|
|
||||||
|
|
||||||
def force_state_transition(chanA, chanB):
|
def force_state_transition(chanA, chanB):
|
||||||
chanB.receive_new_commitment(*chanA.sign_next_commitment())
|
chanB.receive_new_commitment(*chanA.sign_next_commitment())
|
||||||
rev, _ = chanB.revoke_current_commitment()
|
rev, _ = chanB.revoke_current_commitment()
|
||||||
@ -301,7 +338,7 @@ def force_state_transition(chanA, chanB):
|
|||||||
# function provides a simple way to allow test balance assertions to take fee
|
# function provides a simple way to allow test balance assertions to take fee
|
||||||
# calculations into account.
|
# calculations into account.
|
||||||
def calc_static_fee(numHTLCs):
|
def calc_static_fee(numHTLCs):
|
||||||
commitWeight = 724
|
commitWeight = 724
|
||||||
htlcWeight = 172
|
htlcWeight = 172
|
||||||
feePerKw = 24//4 * 1000
|
feePerKw = 24//4 * 1000
|
||||||
return feePerKw * (commitWeight + htlcWeight*numHTLCs) // 1000
|
return feePerKw * (commitWeight + htlcWeight*numHTLCs) // 1000
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user