lnpeer: dont remove from dont_settle_htlcs when failing
What changed, and why it matters
This change fixes a bug in Electrum's Lightning Network code. Previously, when the wallet decided to reject (fail) an incoming multi-part payment, it would remove the payment hash from two internal safety lists. Those lists are meant to prevent the wallet from prematurely settling or letting the payment expire while it is still waiting for all parts. Removing the hash during a failure could cause a later part of the same payment to be settled or expired incorrectly, potentially leading to loss of funds or routing problems. The patch simply stops removing the hash when failing.
Treat as a security-relevant bug fix and include in the next release. Users running Lightning nodes on affected versions should upgrade. Review related MPP failure paths for similar premature hash-removal patterns.
Security signals we found
Lightning Network HTLC state-management bug
Multi-part payment (MPP) race condition
Potential premature settlement/expiration of HTLCs
Funds-loss class bug in payment handling
Evidence from the diff
In electrum/lnpeer.py, the method that fails an HTLC set removed the payment_hash from LNWallet.dont_expire_htlcs and dont_settle_htlcs. These sets are used to suppress automatic settlement/expiration for HTLCs sharing a payment_hash while a multi-part payment is being assembled. By popping the hash during failure, a subsequent HTLC with the same payment_hash could be settled or expired before the failure path completes, creating a race or inconsistent channel state. The patch removes the two .pop() calls so the hash remains guarded.
Changed components
electrum/lnpeer.pyLNWallet.dont_settle_htlcsLNWallet.dont_expire_htlcsLightning Network peer/payment handlingInspect captured patch +0 / −2
diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py
index ecc1e98..f15dfc6 100644
--- a/electrum/lnpeer.py
+++ b/electrum/lnpeer.py
@@ -2314,8 +2314,6 @@ class Peer(Logger, EventListener):
local_height = self.network.blockchain().height()
payment_hash = htlc_set.get_payment_hash()
assert payment_hash is not None, "Empty htlc set?"
- self.lnworker.dont_expire_htlcs.pop(payment_hash.hex(), None)
- self.lnworker.dont_settle_htlcs.pop(payment_hash.hex(), None) # already failed
for mpp_htlc in list(htlc_set.htlcs):
chan = self.get_channel_by_id(mpp_htlc.channel_id)
htlc_id = mpp_htlc.htlc.htlc_id
Why this scored 57/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.