tlnchannel.available_to_spend: consider both receiver's and sender's ctx - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit 7fccd4fc5e0044d35b4cf717b1fc0137b6321b0e DIR parent 817411b889fa57ea8205ed4abea1cc08aa2d666a HTML Author: SomberNight <somber.night@protonmail.com> Date: Wed, 10 Jun 2020 20:23:12 +0200 lnchannel.available_to_spend: consider both receiver's and sender's ctx Diffstat: M electrum/lnchannel.py | 85 ++++++++++++++----------------- 1 file changed, 38 insertions(+), 47 deletions(-) --- DIR diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py t@@ -1054,53 +1054,44 @@ class Channel(AbstractChannel): assert type(subject) is HTLCOwner sender = subject receiver = subject.inverted() - ctx_owner = receiver - # TODO but what about the other ctx? BOLT-02 only talks about checking the receiver's ctx, - # however the channel reserve is only meaningful if we also check the sender's ctx! - # in particular, note that dust limits can be different between the parties! - # but due to the racy nature of this, we cannot be sure exactly what the sender's - # next ctx will look like (e.g. what feerate it will use). hmmm :/ - ctn = self.get_next_ctn(ctx_owner) - sender_balance_msat = self.balance_minus_outgoing_htlcs(whose=sender, ctx_owner=ctx_owner, ctn=ctn) - receiver_balance_msat = self.balance_minus_outgoing_htlcs(whose=receiver, ctx_owner=ctx_owner, ctn=ctn) - sender_reserve_msat = self.config[receiver].reserve_sat * 1000 - receiver_reserve_msat = self.config[sender].reserve_sat * 1000 - initiator = LOCAL if self.constraints.is_initiator else REMOTE - # the initiator/funder pays on-chain fees - num_htlcs_in_ctx = len(self.included_htlcs(ctx_owner, SENT, ctn=ctn) + self.included_htlcs(ctx_owner, RECEIVED, ctn=ctn)) - feerate = self.get_feerate(ctx_owner, ctn=ctn) - ctx_fees_msat = calc_fees_for_commitment_tx( - num_htlcs=num_htlcs_in_ctx, - feerate=feerate, - is_local_initiator=self.constraints.is_initiator, - round_to_sat=False, - ) - # note: if this supposed new HTLC is large enough to create an output, the initiator needs to pay for that too - # note: if sender != initiator, both the sender and the receiver need to "afford" the payment - htlc_fee_msat = fee_for_htlc_output(feerate=feerate) - # TODO stuck channels. extra funder reserve? "fee spike buffer" (maybe only if "strict") - # see https://github.com/lightningnetwork/lightning-rfc/issues/728 - # note: in terms of on-chain outputs, as we are considering the htlc_receiver's ctx, this is a "received" HTLC - htlc_trim_threshold_msat = received_htlc_trim_threshold_sat(dust_limit_sat=self.config[receiver].dust_limit_sat, feerate=feerate) * 1000 - if strict: - # also consider the other ctx, where the trim threshold is different - # note: the 'feerate' we use is not technically correct but we have no way - # of knowing the actual future feerate ahead of time (this is a protocol bug) - htlc_trim_threshold_msat = min(htlc_trim_threshold_msat, - offered_htlc_trim_threshold_sat(dust_limit_sat=self.config[sender].dust_limit_sat, feerate=feerate) * 1000) - max_send_msat = sender_balance_msat - sender_reserve_msat - ctx_fees_msat[sender] - if max_send_msat < htlc_trim_threshold_msat: - # there will be no corresponding HTLC output - return max_send_msat - if sender == initiator: - max_send_after_htlc_fee_msat = max_send_msat - htlc_fee_msat - max_send_msat = max(htlc_trim_threshold_msat - 1, max_send_after_htlc_fee_msat) - return max_send_msat - else: - # the receiver is the initiator, so they need to be able to pay tx fees - if receiver_balance_msat - receiver_reserve_msat - ctx_fees_msat[receiver] - htlc_fee_msat < 0: - max_send_msat = htlc_trim_threshold_msat - 1 - return max_send_msat + initiator = LOCAL if self.constraints.is_initiator else REMOTE # the initiator/funder pays on-chain fees + + def consider_ctx(*, ctx_owner: HTLCOwner) -> int: + ctn = self.get_next_ctn(ctx_owner) + sender_balance_msat = self.balance_minus_outgoing_htlcs(whose=sender, ctx_owner=ctx_owner, ctn=ctn) + receiver_balance_msat = self.balance_minus_outgoing_htlcs(whose=receiver, ctx_owner=ctx_owner, ctn=ctn) + sender_reserve_msat = self.config[receiver].reserve_sat * 1000 + receiver_reserve_msat = self.config[sender].reserve_sat * 1000 + num_htlcs_in_ctx = len(self.included_htlcs(ctx_owner, SENT, ctn=ctn) + self.included_htlcs(ctx_owner, RECEIVED, ctn=ctn)) + feerate = self.get_feerate(ctx_owner, ctn=ctn) + ctx_fees_msat = calc_fees_for_commitment_tx( + num_htlcs=num_htlcs_in_ctx, + feerate=feerate, + is_local_initiator=self.constraints.is_initiator, + round_to_sat=False, + ) + htlc_fee_msat = fee_for_htlc_output(feerate=feerate) + htlc_trim_func = received_htlc_trim_threshold_sat if ctx_owner == receiver else offered_htlc_trim_threshold_sat + htlc_trim_threshold_msat = htlc_trim_func(dust_limit_sat=self.config[ctx_owner].dust_limit_sat, feerate=feerate) * 1000 + max_send_msat = sender_balance_msat - sender_reserve_msat - ctx_fees_msat[sender] + if max_send_msat < htlc_trim_threshold_msat: + # there will be no corresponding HTLC output + return max_send_msat + if sender == initiator: + max_send_after_htlc_fee_msat = max_send_msat - htlc_fee_msat + max_send_msat = max(htlc_trim_threshold_msat - 1, max_send_after_htlc_fee_msat) + return max_send_msat + else: + # the receiver is the initiator, so they need to be able to pay tx fees + if receiver_balance_msat - receiver_reserve_msat - ctx_fees_msat[receiver] - htlc_fee_msat < 0: + max_send_msat = htlc_trim_threshold_msat - 1 + return max_send_msat + + max_send_msat = min(consider_ctx(ctx_owner=receiver), + consider_ctx(ctx_owner=sender)) + max_send_msat = max(max_send_msat, 0) + return max_send_msat + def included_htlcs(self, subject: HTLCOwner, direction: Direction, ctn: int = None) -> Sequence[UpdateAddHtlc]: """Returns list of non-dust HTLCs for subject's commitment tx at ctn,