1
0

lnworker: use channel_id instead of scid in ReceivedMPPHtlc

Store the channel id instead of the scid in ReceivedMPPHtlc.
The scid can be None, in theory even for multiple channels at the same
time. Using the channel_id which is always available and unique seems
less error prone at the cost of temporarily higher storage requirements
in the db for the duration of the pending htlcs.

Alternatively we could use the local scid alias however using the
channel_id seems less complex and leaves less room for ambiguity.
This commit is contained in:
f321x
2025-12-08 12:00:52 +01:00
parent 8a88ebe6bc
commit 5be598b808
4 changed files with 46 additions and 18 deletions

View File

@@ -2150,7 +2150,7 @@ class Peer(Logger, EventListener):
Does additional checks on the incoming htlc and return the payment key if the tests pass,
otherwise raises OnionRoutingError which will get the htlc failed.
"""
_log_fail_reason = self._log_htlc_fail_reason_cb(chan.short_channel_id, htlc, processed_onion.hop_data.payload)
_log_fail_reason = self._log_htlc_fail_reason_cb(chan.channel_id, htlc, processed_onion.hop_data.payload)
# Check that our blockchain tip is sufficiently recent so that we have an approx idea of the height.
# We should not release the preimage for an HTLC that its sender could already time out as
@@ -2269,7 +2269,7 @@ class Peer(Logger, EventListener):
self.lnworker.dont_expire_htlcs.pop(payment_hash.hex(), None) # htlcs wont get expired anymore
for mpp_htlc in list(htlc_set.htlcs):
htlc_id = mpp_htlc.htlc.htlc_id
chan = self.lnworker.get_channel_by_short_id(mpp_htlc.scid)
chan = self.lnworker.channels[mpp_htlc.channel_id]
if chan.channel_id not in self.channels:
# this htlc belongs to another peer and has to be settled in their htlc_switch
continue
@@ -2311,7 +2311,7 @@ class Peer(Logger, EventListener):
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.lnworker.get_channel_by_short_id(mpp_htlc.scid)
chan = self.lnworker.channels[mpp_htlc.channel_id]
htlc_id = mpp_htlc.htlc.htlc_id
if chan.channel_id not in self.channels:
# this htlc belongs to another peer and has to be settled in their htlc_switch
@@ -2854,7 +2854,7 @@ class Peer(Logger, EventListener):
)
self.lnworker.update_or_create_mpp_with_received_htlc(
payment_key=payment_key,
scid=chan.short_channel_id,
channel_id=chan.channel_id,
htlc=htlc,
unprocessed_onion_packet=onion_packet_hex, # outer onion if trampoline
)
@@ -2938,11 +2938,12 @@ class Peer(Logger, EventListener):
def _log_htlc_fail_reason_cb(
self,
scid: ShortChannelID,
channel_id: bytes,
htlc: UpdateAddHtlc,
onion_payload: dict
) -> Callable[[str], None]:
def _log_fail_reason(reason: str) -> None:
scid = self.lnworker.channels[channel_id].short_channel_id
self.logger.info(f"will FAIL HTLC: {str(scid)=}. {reason=}. {str(htlc)=}. {onion_payload=}")
return _log_fail_reason
@@ -2960,7 +2961,7 @@ class Peer(Logger, EventListener):
onion_payload = {}
self._log_htlc_fail_reason_cb(
mpp_htlc.scid,
mpp_htlc.channel_id,
mpp_htlc.htlc,
onion_payload,
)(f"mpp set {id(mpp_set)} failed: {reason}")
@@ -3074,7 +3075,7 @@ class Peer(Logger, EventListener):
if mpp_set.resolution == RecvMPPResolution.WAITING:
# calculate the sum of just in time channel opening fees
htlc_channels = [self.lnworker.get_channel_by_short_id(scid) for scid in set(h.scid for h in mpp_set.htlcs)]
htlc_channels = [self.lnworker.channels[channel_id] for channel_id in set(h.channel_id for h in mpp_set.htlcs)]
jit_opening_fees_msat = sum((c.jit_opening_fee or 0) for c in htlc_channels)
# check if set is first stage multi-trampoline payment to us

View File

@@ -1965,18 +1965,18 @@ del r
class ReceivedMPPHtlc(NamedTuple):
scid: ShortChannelID
channel_id: bytes
htlc: UpdateAddHtlc
unprocessed_onion: str
def __repr__(self):
return f"{self.scid}, {self.htlc=}, {self.unprocessed_onion[:15]=}..."
return f"chan_id={self.channel_id.hex()}, {self.htlc=}, {self.unprocessed_onion[:15]=}..."
@staticmethod
def from_tuple(scid, htlc, unprocessed_onion) -> 'ReceivedMPPHtlc':
assert is_hex_str(unprocessed_onion) and is_hex_str(scid)
def from_tuple(channel_id, htlc, unprocessed_onion) -> 'ReceivedMPPHtlc':
assert is_hex_str(unprocessed_onion) and is_hex_str(channel_id)
return ReceivedMPPHtlc(
scid=ShortChannelID(bytes.fromhex(scid)),
channel_id=bytes.fromhex(channel_id),
htlc=UpdateAddHtlc.from_tuple(*htlc),
unprocessed_onion=unprocessed_onion,
)

View File

@@ -2583,7 +2583,7 @@ class LNWallet(LNWorker):
self,
*,
payment_key: str,
scid: ShortChannelID,
channel_id: bytes,
htlc: UpdateAddHtlc,
unprocessed_onion_packet: str,
):
@@ -2610,7 +2610,7 @@ class LNWallet(LNWorker):
if mpp_status.resolution > RecvMPPResolution.WAITING:
# we are getting a htlc for a set that is not in WAITING state, it cannot be safely added
self.logger.info(f"htlc set cannot accept htlc, failing htlc: {scid=} {htlc.htlc_id=}")
self.logger.info(f"htlc set cannot accept htlc, failing htlc: {channel_id=} {htlc.htlc_id=}")
if mpp_status == RecvMPPResolution.EXPIRED:
raise OnionRoutingFailure(code=OnionFailureCode.MPP_TIMEOUT, data=b'')
raise OnionRoutingFailure(
@@ -2619,7 +2619,7 @@ class LNWallet(LNWorker):
)
new_htlc = ReceivedMPPHtlc(
scid=scid,
channel_id=channel_id,
htlc=htlc,
unprocessed_onion=unprocessed_onion_packet,
)
@@ -2707,7 +2707,7 @@ class LNWallet(LNWorker):
# only cleanup when channel is REDEEMED as mpp set is still required for lnsweep
assert chan._state == ChannelState.REDEEMED
for payment_key_hex, mpp_status in list(self.received_mpp_htlcs.items()):
htlcs_to_remove = [htlc for htlc in mpp_status.htlcs if htlc.scid == chan.short_channel_id]
htlcs_to_remove = [htlc for htlc in mpp_status.htlcs if htlc.channel_id == chan.channel_id]
for stale_mpp_htlc in htlcs_to_remove:
assert mpp_status.resolution != RecvMPPResolution.WAITING
self.logger.info(f'maybe_cleanup_mpp: removing htlc of MPP {payment_key_hex}')
@@ -3547,7 +3547,7 @@ class LNWallet(LNWorker):
assert not any_outer_onion.are_we_final
assert len(processed_htlc_set) == 1, processed_htlc_set
forward_htlc = any_mpp_htlc.htlc
incoming_chan = self.get_channel_by_short_id(any_mpp_htlc.scid)
incoming_chan = self.channels[any_mpp_htlc.channel_id]
next_htlc = await self._maybe_forward_htlc(
incoming_chan=incoming_chan,
htlc=forward_htlc,

View File

@@ -69,7 +69,7 @@ class WalletUnfinished(WalletFileException):
# seed_version is now used for the version of the wallet file
OLD_SEED_VERSION = 4 # electrum versions < 2.0
NEW_SEED_VERSION = 11 # electrum versions >= 2.0
FINAL_SEED_VERSION = 64 # electrum >= 2.7 will set this to prevent
FINAL_SEED_VERSION = 65 # electrum >= 2.7 will set this to prevent
# old versions from overwriting new format
@@ -236,6 +236,7 @@ class WalletDBUpgrader(Logger):
self._convert_version_62()
self._convert_version_63()
self._convert_version_64()
self._convert_version_65()
self.put('seed_version', FINAL_SEED_VERSION) # just to be sure
def _convert_wallet_type(self):
@@ -1288,6 +1289,32 @@ class WalletDBUpgrader(Logger):
self.data['lightning_payments'] = new_payment_infos
self.data['seed_version'] = 64
def _convert_version_65(self):
"""Store channel_id instead of short_channel_id in ReceivedMPPHtlc"""
if not self._is_upgrade_method_needed(64, 64):
return
channels = self.data.get('channels', {})
def scid_to_channel_id(scid):
for channel_id, channel_data in channels.items():
if scid == channel_data.get('short_channel_id'):
return channel_id
raise KeyError(f"missing {scid=} in channels")
mpp_sets = self.data.get('received_mpp_htlcs', {})
new_mpp_sets = {}
for payment_key, mpp_set in mpp_sets.items():
resolution, htlc_list, parent_set_key = mpp_set
new_htlc_list = []
for htlc_data_tuple in htlc_list:
scid, update_add_htlc, onion = htlc_data_tuple
channel_id = scid_to_channel_id(scid)
new_htlc_list.append((channel_id, update_add_htlc, onion))
new_mpp_sets[payment_key] = (resolution, new_htlc_list, parent_set_key)
self.data['received_mpp_htlcs'] = new_mpp_sets
self.data['seed_version'] = 65
def _convert_imported(self):
if not self._is_upgrade_method_needed(0, 13):
return