lnworker: prevent creation of PaymentInfo with 0 exp
What changed, and why it matters
This commit fixes a bug in Electrum's Lightning invoice handling where an invoice with no expiry date could be stored with an expiry of 0 seconds. In Electrum, 0 seconds means 'no expiry' in some places, but the Lightning code expects a special 'never expire' value (100 years) instead. The bug caused an internal assertion to fail, which could crash the wallet or make incoming Lightning payments fail unexpectedly. The fix replaces 0 with the proper 'never expire' value when creating payment information.
Review whether other code paths that construct PaymentInfo or handle invoice expiry also normalize 0 to LN_EXPIRY_NEVER. Consider adding a regression test for no-expiry Lightning invoices. Users should update to a version containing this fix if they use Lightning and encounter crashes or failed incoming payments with no-expiry invoices.
Security signals we found
Assertion failure in payment info creation
Lightning invoice expiry handling inconsistency
Potential denial-of-service via malformed/crafted no-expiry invoice
Fixes user-reported crash issue #10350
Evidence from the diff
In electrum/lnworker.py, PaymentInfo asserts that expiry_delay > 0. However, LNWallet.create_payment_info() passed exp_delay directly, which could be 0 for invoices without an expiry. The patch changes expiry_delay=exp_delay or LN_EXPIRY_NEVER so that 0 is coalesced to LN_EXPIRY_NEVER (100 years). The assertion message was also enhanced to aid debugging. This prevents HTLCs for no-expiry invoices from being failed incorrectly and resolves the assertion error reported in issue #10350.
Changed components
electrum/lnworker.pyLNWallet.create_payment_info()PaymentInfo dataclass/assertionInspect captured patch +2 / −2
diff --git a/electrum/lnworker.py b/electrum/lnworker.py
index ee7caab..9fefeb1 100644
--- a/electrum/lnworker.py
+++ b/electrum/lnworker.py
@@ -146,7 +146,7 @@ class PaymentInfo:
assert isinstance(self.direction, int)
assert isinstance(self.status, int)
assert isinstance(self.min_final_cltv_delta, int)
- assert isinstance(self.expiry_delay, int) and self.expiry_delay > 0
+ assert isinstance(self.expiry_delay, int) and self.expiry_delay > 0, repr(self.expiry_delay)
assert isinstance(self.creation_ts, int)
assert isinstance(self.invoice_features, LnFeatures)
@@ -2414,7 +2414,7 @@ class LNWallet(LNWorker):
direction=RECEIVED,
status=PR_UNPAID,
min_final_cltv_delta=min_final_cltv_delta,
- expiry_delay=exp_delay,
+ expiry_delay=exp_delay or LN_EXPIRY_NEVER,
invoice_features=invoice_features,
)
self.save_preimage(payment_hash, payment_preimage, write_to_disk=False)
Why this scored 32/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.