lnchannel: give offered htlcs some time to fail on restart
What changed, and why it matters
This commit changes Electrum's Lightning code so that, after the wallet restarts, it waits 30 seconds before force-closing a payment channel just because an outgoing timed payment (HTLC) looks expired. The goal is to avoid unnecessary on-chain channel closures when the peer would have cancelled the payment off-chain once the connection came back. It is a robustness/usability fix, not a clear security patch, and it does not claim to fix a known vulnerability.
Treat as a normal code-quality/robustness improvement. No urgent security action is indicated by the commit itself. Reviewers may want to confirm the 30-second window does not materially delay legitimate force-closes in adversarial scenarios, but the commit does not present evidence of such a flaw.
Security signals we found
Behavioral change in channel force-close timing
Adds startup grace period for expiring offered HTLCs
No cryptographic or validation changes
No mention of vulnerability, CVE, or security bug in commit message
Evidence from the diff
In lnchannel.py, should_be_closed_due_to_expiring_htlcs now skips offered HTLCs that look expired if the LNWallet was instantiated less than TIME_FOR_OFFERED_HTLCS_TO_GET_FAILED_OFFCHAIN_ON_RESTART (30) seconds ago. A new constant is added in lnutil.py, and lnworker.py records an instantiation_timestamp when LNWallet is created. The change only affects the force-close decision path for offered HTLCs after restart; it does not alter cryptographic validation, revocation, or HTLC timeout enforcement itself.
Changed components
electrum/lnchannel.pyelectrum/lnutil.pyelectrum/lnworker.pyInspect captured patch +6 / −0
diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py
index 49878de..ac6c441 100644
--- a/electrum/lnchannel.py
+++ b/electrum/lnchannel.py
@@ -1972,12 +1972,15 @@ class Channel(AbstractChannel):
# If there is an offered HTLC which has already expired (+ some grace period after), we
# will unilaterally close the channel and time out the HTLC
offered_htlc_deadline_delta = lnutil.NBLOCK_DEADLINE_DELTA_AFTER_EXPIRY_FOR_OFFERED_HTLCS
+ time_since_startup = now() - self.lnworker.instantiation_timestamp
for sub, dir, ctn in ((LOCAL, SENT, self.get_latest_ctn(LOCAL)),
(REMOTE, RECEIVED, self.get_oldest_unrevoked_ctn(REMOTE)),
(REMOTE, RECEIVED, self.get_latest_ctn(REMOTE)),):
for htlc_id, htlc in self.hm.htlcs_by_direction(subject=sub, direction=dir, ctn=ctn).items():
if htlc.cltv_abs + offered_htlc_deadline_delta > local_height:
continue
+ if time_since_startup < lnutil.TIME_FOR_OFFERED_HTLCS_TO_GET_FAILED_OFFCHAIN_ON_RESTART:
+ continue # give the peer some time to fail the htlc offchain
htlcs_we_could_reclaim[(SENT, htlc_id)] = htlc
# Note: previously we used a threshold concept, "min_value_worth_closing_channel_over_sat", and
# only force-closed the channel if the total value of these expiring htlcs was large enough.
diff --git a/electrum/lnutil.py b/electrum/lnutil.py
index 56b620a..24a9bac 100644
--- a/electrum/lnutil.py
+++ b/electrum/lnutil.py
@@ -529,6 +529,8 @@ MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED = 2016
# timeout after which we consider a zeroconf channel without funding tx to be failed
ZEROCONF_TIMEOUT = 60 * 10
+TIME_FOR_OFFERED_HTLCS_TO_GET_FAILED_OFFCHAIN_ON_RESTART = 30
+
class RevocationStore:
# closely based on code in lightningnetwork/lnd
diff --git a/electrum/lnworker.py b/electrum/lnworker.py
index 92ae704..82c1d03 100644
--- a/electrum/lnworker.py
+++ b/electrum/lnworker.py
@@ -1008,6 +1008,7 @@ class LNWallet(Logger):
self.wallet = wallet
self.config = wallet.config
self.db = wallet.db
+ self.instantiation_timestamp = int(time.time())
self.node_keypair = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.NODE_KEY)
self.backup_key = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.BACKUP_CIPHER).privkey
self.static_payment_key = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.PAYMENT_BASE)
Why this scored 30/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.