commit 996799d79ed737dcf8048edea452bcd608b3ada1
parent e59eb147c04572a313853e19b8c0e58416eb452f
Author: SomberNight <somber.night@protonmail.com>
Date: Mon, 15 Jun 2020 15:42:56 +0200
lnchannel: update_fee: improve "can afford" check
Diffstat:
2 files changed, 32 insertions(+), 15 deletions(-)
diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py
@@ -1108,7 +1108,8 @@ class Channel(AbstractChannel):
return max_send_msat
- def included_htlcs(self, subject: HTLCOwner, direction: Direction, ctn: int = None) -> Sequence[UpdateAddHtlc]:
+ def included_htlcs(self, subject: HTLCOwner, direction: Direction, ctn: int = None, *,
+ feerate: int = None) -> Sequence[UpdateAddHtlc]:
"""Returns list of non-dust HTLCs for subject's commitment tx at ctn,
filtered by direction (of HTLCs).
"""
@@ -1116,7 +1117,8 @@ class Channel(AbstractChannel):
assert type(direction) is Direction
if ctn is None:
ctn = self.get_oldest_unrevoked_ctn(subject)
- feerate = self.get_feerate(subject, ctn=ctn)
+ if feerate is None:
+ feerate = self.get_feerate(subject, ctn=ctn)
conf = self.config[subject]
if direction == RECEIVED:
threshold_sat = received_htlc_trim_threshold_sat(dust_limit_sat=conf.dust_limit_sat, feerate=feerate)
@@ -1254,8 +1256,22 @@ class Channel(AbstractChannel):
# feerate uses sat/kw
if self.constraints.is_initiator != from_us:
raise Exception(f"Cannot update_fee: wrong initiator. us: {from_us}")
- # TODO check that funder can afford the new on-chain fees (+ channel reserve)
- # (maybe check both ctxs, at least if from_us is True??)
+ sender = LOCAL if from_us else REMOTE
+ ctx_owner = -sender
+ ctn = self.get_next_ctn(ctx_owner)
+ sender_balance_msat = self.balance_minus_outgoing_htlcs(whose=sender, ctx_owner=ctx_owner, ctn=ctn)
+ sender_reserve_msat = self.config[-sender].reserve_sat * 1000
+ num_htlcs_in_ctx = len(self.included_htlcs(ctx_owner, SENT, ctn=ctn, feerate=feerate) +
+ self.included_htlcs(ctx_owner, RECEIVED, ctn=ctn, feerate=feerate))
+ ctx_fees_msat = calc_fees_for_commitment_tx(
+ num_htlcs=num_htlcs_in_ctx,
+ feerate=feerate,
+ is_local_initiator=self.constraints.is_initiator,
+ )
+ remainder = sender_balance_msat - sender_reserve_msat - ctx_fees_msat[sender]
+ if remainder < 0:
+ raise Exception(f"Cannot update_fee. {sender} tried to update fee but they cannot afford it. "
+ f"Their balance would go below reserve: {remainder} msat missing.")
with self.db_lock:
if from_us:
assert self.can_send_ctx_updates(), f"cannot update channel. {self.get_state()!r} {self.peer_state!r}"
@@ -1302,13 +1318,6 @@ class Channel(AbstractChannel):
is_local_initiator=self.constraints.is_initiator == (subject == LOCAL),
)
- # TODO: we need to also include the respective channel reserves here, but not at the
- # beginning of the channel lifecycle when the reserve might not be met yet
- if remote_msat - onchain_fees[REMOTE] < 0:
- raise Exception(f"negative remote_msat in make_commitment: {remote_msat}")
- if local_msat - onchain_fees[LOCAL] < 0:
- raise Exception(f"negative local_msat in make_commitment: {local_msat}")
-
if self.is_static_remotekey_enabled():
payment_pubkey = other_config.payment_basepoint.pubkey
else:
diff --git a/electrum/lnutil.py b/electrum/lnutil.py
@@ -639,8 +639,12 @@ class HTLCOwner(IntFlag):
LOCAL = 1
REMOTE = -LOCAL
- def inverted(self):
- return HTLCOwner(-self)
+ def inverted(self) -> 'HTLCOwner':
+ return -self
+
+ def __neg__(self) -> 'HTLCOwner':
+ return HTLCOwner(super().__neg__())
+
class Direction(IntFlag):
SENT = -1 # in the context of HTLCs: "offered" HTLCs
@@ -654,10 +658,14 @@ REMOTE = HTLCOwner.REMOTE
def make_commitment_outputs(*, fees_per_participant: Mapping[HTLCOwner, int], local_amount_msat: int, remote_amount_msat: int,
local_script: str, remote_script: str, htlcs: List[ScriptHtlc], dust_limit_sat: int) -> Tuple[List[PartialTxOutput], List[PartialTxOutput]]:
- to_local_amt = local_amount_msat - fees_per_participant[LOCAL]
+ # BOLT-03: "Base commitment transaction fees are extracted from the funder's amount;
+ # if that amount is insufficient, the entire amount of the funder's output is used."
+ # -> if funder cannot afford feerate, their output might go negative, so take max(0, x) here:
+ to_local_amt = max(0, local_amount_msat - fees_per_participant[LOCAL])
to_local = PartialTxOutput(scriptpubkey=bfh(local_script), value=to_local_amt // 1000)
- to_remote_amt = remote_amount_msat - fees_per_participant[REMOTE]
+ to_remote_amt = max(0, remote_amount_msat - fees_per_participant[REMOTE])
to_remote = PartialTxOutput(scriptpubkey=bfh(remote_script), value=to_remote_amt // 1000)
+
non_htlc_outputs = [to_local, to_remote]
htlc_outputs = []
for script, htlc in htlcs: