What changed, and why it matters
This is a small performance cleanup in Electrum's Lightning code. It moves a save-to-database operation outside a loop so the wallet writes once instead of after every individual payment chunk. There is no direct security bug being fixed; the change reduces database writes and may make crashes during batch processing slightly cleaner, but it does not patch a vulnerability.
No security action required. Treat as routine code-quality/performance improvement. If auditing, verify that an exception inside the loop does not leave received_mpp_htlcs inconsistent with on-disk channel state, but the commit does not introduce that risk beyond the prior behavior.
Security signals we found
No memory-safety, cryptographic, or authorization change
No input validation change
Persistence timing change only; state semantics preserved
No CVE, advisory, or security disclosure referenced in commit
Evidence from the diff
The commit refactors Peer._fulfill_htlc_set and Peer._fail_htlc_set in electrum/lnpeer.py. Previously, self.lnworker.received_mpp_htlcs[payment_key] was assigned inside the loop after each HTLC was processed. Now the updated htlc_set is written once after the loop completes. The stated motivation is performance, because database writes can exceed 100 ms for large wallets. The functional end-state of received_mpp_htlcs is unchanged; only the persistence frequency changes.
Changed components
electrum/lnpeer.pyPeer._fulfill_htlc_setPeer._fail_htlc_setLightning MPP HTLC set persistenceInspect captured patch +4 / −4
diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py
index ae525ab..2809a79 100644
--- a/electrum/lnpeer.py
+++ b/electrum/lnpeer.py
@@ -2215,14 +2215,14 @@ class Peer(Logger, EventListener):
# this check is intended to gracefully handle stale htlcs in the set, e.g. after a crash
self.logger.debug(f"{mpp_htlc=} was already settled before, dropping it.")
htlc_set = htlc_set._replace(htlcs=htlc_set.htlcs - {mpp_htlc})
- self.lnworker.received_mpp_htlcs[payment_key] = htlc_set
continue
self._fulfill_htlc(chan, htlc_id, preimage)
htlc_set = htlc_set._replace(htlcs=htlc_set.htlcs - {mpp_htlc})
- self.lnworker.received_mpp_htlcs[payment_key] = htlc_set
# reset just-in-time opening fee of channel
chan.jit_opening_fee = None
+ self.lnworker.received_mpp_htlcs[payment_key] = htlc_set # save updated set
+
def _fulfill_htlc(self, chan: Channel, htlc_id: int, preimage: bytes):
assert chan.hm.is_htlc_irrevocably_added_yet(htlc_proposer=REMOTE, htlc_id=htlc_id)
self.received_htlcs_pending_removal.add((chan, htlc_id))
@@ -2258,7 +2258,6 @@ class Peer(Logger, EventListener):
# this check is intended to gracefully handle stale htlcs in the set, e.g. after a crash
self.logger.debug(f"{mpp_htlc=} was already failed before, dropping it.")
htlc_set = htlc_set._replace(htlcs=htlc_set.htlcs - {mpp_htlc})
- self.lnworker.received_mpp_htlcs[payment_key] = htlc_set
continue
onion_packet = self._parse_onion_packet(mpp_htlc.unprocessed_onion)
processed_onion_packet = self._process_incoming_onion_packet(
@@ -2290,7 +2289,8 @@ class Peer(Logger, EventListener):
error_bytes=error_bytes,
)
htlc_set = htlc_set._replace(htlcs=htlc_set.htlcs - {mpp_htlc})
- self.lnworker.received_mpp_htlcs[payment_key] = htlc_set
+
+ self.lnworker.received_mpp_htlcs[payment_key] = htlc_set # save updated set
def fail_htlc(self, *, chan: Channel, htlc_id: int, error_bytes: bytes):
self.logger.info(f"fail_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}.")
Why this scored 18/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.