From aa4366081a5e4ef6a6ee18d0ee0204e657fd65e4 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 20 Aug 2025 10:28:48 +0200 Subject: [PATCH 1/5] Fix `test_overshoot_final_cltv` Previously, the `pending_forwards` in this test would simply be empty, having the test not run any of the subsequent logic. Here we add a necessary `process_pending_update_add_htlcs()` step, and will move to a more strict test logic in the following commits. --- lightning/src/ln/onion_route_tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index 315943e8bae..af3ba7e03d9 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -1553,6 +1553,7 @@ fn test_overshoot_final_cltv() { commitment_signed_dance!(nodes[1], nodes[0], &update_0.commitment_signed, false, true); assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty()); + nodes[1].node.process_pending_update_add_htlcs(); for (_, pending_forwards) in nodes[1].node.forward_htlcs.lock().unwrap().iter_mut() { for f in pending_forwards.iter_mut() { match f { From 4c35c4a3cc6f77da9ddd0c718a06fd75dbca4dee Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 30 Jul 2025 12:19:00 +0200 Subject: [PATCH 2/5] Make tests touching `forward_htlcs` more strict Previously, some of the tests manipulating `forward_htlcs` were just iterating, but not actually checking whether the expected entries were present and they were actually changed. Here we make these test cases more strict. --- lightning/src/ln/onion_route_tests.rs | 98 ++++++++++++++++----------- lightning/src/ln/payment_tests.rs | 68 +++++++++++-------- 2 files changed, 96 insertions(+), 70 deletions(-) diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index af3ba7e03d9..dd0e11ea116 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -1173,17 +1173,21 @@ fn test_onion_failure() { |_| {}, || { nodes[1].node.process_pending_update_add_htlcs(); - for (_, pending_forwards) in nodes[1].node.forward_htlcs.lock().unwrap().iter_mut() { - for f in pending_forwards.iter_mut() { - match f { - &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { - ref mut forward_info, - .. - }) => forward_info.outgoing_cltv_value -= 1, - _ => {}, - } + assert_eq!(nodes[1].node.forward_htlcs.lock().unwrap().len(), 1); + if let Some((_, pending_forwards)) = + nodes[1].node.forward_htlcs.lock().unwrap().iter_mut().next() + { + assert_eq!(pending_forwards.len(), 1); + match pending_forwards.get_mut(0).unwrap() { + &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { + ref mut forward_info, + .. + }) => forward_info.outgoing_cltv_value -= 1, + _ => panic!("Unexpected HTLCForwardInfo"), } - } + } else { + panic!("Expected pending forwards!"); + }; }, true, Some(LocalHTLCFailureReason::FinalIncorrectCLTVExpiry), @@ -1203,17 +1207,21 @@ fn test_onion_failure() { || { nodes[1].node.process_pending_update_add_htlcs(); // violate amt_to_forward > msg.amount_msat - for (_, pending_forwards) in nodes[1].node.forward_htlcs.lock().unwrap().iter_mut() { - for f in pending_forwards.iter_mut() { - match f { - &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { - ref mut forward_info, - .. - }) => forward_info.outgoing_amt_msat -= 1, - _ => {}, - } + assert_eq!(nodes[1].node.forward_htlcs.lock().unwrap().len(), 1); + if let Some((_, pending_forwards)) = + nodes[1].node.forward_htlcs.lock().unwrap().iter_mut().next() + { + assert_eq!(pending_forwards.len(), 1); + match pending_forwards.get_mut(0).unwrap() { + &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { + ref mut forward_info, + .. + }) => forward_info.outgoing_amt_msat -= 1, + _ => panic!("Unexpected HTLCForwardInfo"), } - } + } else { + panic!("Expected pending forwards!"); + }; }, true, Some(LocalHTLCFailureReason::FinalIncorrectHTLCAmount), @@ -1554,16 +1562,20 @@ fn test_overshoot_final_cltv() { assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty()); nodes[1].node.process_pending_update_add_htlcs(); - for (_, pending_forwards) in nodes[1].node.forward_htlcs.lock().unwrap().iter_mut() { - for f in pending_forwards.iter_mut() { - match f { - &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { - ref mut forward_info, .. - }) => forward_info.outgoing_cltv_value += 1, - _ => {}, - } + assert_eq!(nodes[1].node.forward_htlcs.lock().unwrap().len(), 1); + if let Some((_, pending_forwards)) = + nodes[1].node.forward_htlcs.lock().unwrap().iter_mut().next() + { + assert_eq!(pending_forwards.len(), 1); + match pending_forwards.get_mut(0).unwrap() { + &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { ref mut forward_info, .. }) => { + forward_info.outgoing_cltv_value += 1 + }, + _ => panic!("Unexpected HTLCForwardInfo"), } - } + } else { + panic!("Expected pending forwards!"); + }; expect_and_process_pending_htlcs(&nodes[1], false); check_added_monitors!(&nodes[1], 1); @@ -2615,19 +2627,23 @@ fn test_phantom_final_incorrect_cltv_expiry() { nodes[1].node.process_pending_update_add_htlcs(); // Modify the payload so the phantom hop's HMAC is bogus. - for (_, pending_forwards) in nodes[1].node.forward_htlcs.lock().unwrap().iter_mut() { - for f in pending_forwards.iter_mut() { - match f { - &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { - forward_info: PendingHTLCInfo { ref mut outgoing_cltv_value, .. }, - .. - }) => { - *outgoing_cltv_value -= 1; - }, - _ => panic!("Unexpected forward"), - } + assert_eq!(nodes[1].node.forward_htlcs.lock().unwrap().len(), 1); + if let Some((_, pending_forwards)) = + nodes[1].node.forward_htlcs.lock().unwrap().iter_mut().next() + { + assert_eq!(pending_forwards.len(), 1); + match pending_forwards.get_mut(0).unwrap() { + &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { + forward_info: PendingHTLCInfo { ref mut outgoing_cltv_value, .. }, + .. + }) => { + *outgoing_cltv_value -= 1; + }, + _ => panic!("Unexpected HTLCForwardInfo"), } - } + } else { + panic!("Expected pending forwards!"); + }; nodes[1].node.process_pending_htlc_forwards(); expect_htlc_failure_conditions( nodes[1].node.get_and_clear_pending_events(), diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index 67c7599a6c0..e04cf88d215 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -636,12 +636,14 @@ fn test_reject_mpp_keysend_htlc_mismatching_secret() { nodes[3].node.process_pending_update_add_htlcs(); assert!(nodes[3].node.get_and_clear_pending_msg_events().is_empty()); - for (_, pending_forwards) in nodes[3].node.forward_htlcs.lock().unwrap().iter_mut() { - for f in pending_forwards.iter_mut() { - match f { - &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { - ref mut forward_info, .. - }) => match forward_info.routing { + assert_eq!(nodes[3].node.forward_htlcs.lock().unwrap().len(), 1); + if let Some((_, pending_forwards)) = + nodes[3].node.forward_htlcs.lock().unwrap().iter_mut().next() + { + assert_eq!(pending_forwards.len(), 1); + match pending_forwards.get_mut(0).unwrap() { + &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { ref mut forward_info, .. }) => { + match forward_info.routing { PendingHTLCRouting::ReceiveKeysend { ref mut payment_data, .. } => { *payment_data = Some(msgs::FinalOnionHopData { payment_secret: PaymentSecret([42; 32]), @@ -649,11 +651,15 @@ fn test_reject_mpp_keysend_htlc_mismatching_secret() { }); }, _ => panic!("Expected PendingHTLCRouting::ReceiveKeysend"), - }, - _ => {}, - } + } + }, + _ => { + panic!("Unexpected HTLCForwardInfo"); + }, } - } + } else { + panic!("Expected pending receive"); + }; nodes[3].node.process_pending_htlc_forwards(); // Pay along nodes[2] @@ -685,26 +691,30 @@ fn test_reject_mpp_keysend_htlc_mismatching_secret() { nodes[3].node.process_pending_update_add_htlcs(); assert!(nodes[3].node.get_and_clear_pending_msg_events().is_empty()); - for (_, pending_forwards) in nodes[3].node.forward_htlcs.lock().unwrap().iter_mut() { - for f in pending_forwards.iter_mut() { - match f { - &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { - ref mut forward_info, .. - }) => { - match forward_info.routing { - PendingHTLCRouting::ReceiveKeysend { ref mut payment_data, .. } => { - *payment_data = Some(msgs::FinalOnionHopData { - payment_secret: PaymentSecret([43; 32]), // Doesn't match the secret used above - total_msat: amount * 2, - }); - }, - _ => panic!("Expected PendingHTLCRouting::ReceiveKeysend"), - } - }, - _ => {}, - } + assert_eq!(nodes[3].node.forward_htlcs.lock().unwrap().len(), 1); + if let Some((_, pending_forwards)) = + nodes[3].node.forward_htlcs.lock().unwrap().iter_mut().next() + { + assert_eq!(pending_forwards.len(), 1); + match pending_forwards.get_mut(0).unwrap() { + &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { ref mut forward_info, .. }) => { + match forward_info.routing { + PendingHTLCRouting::ReceiveKeysend { ref mut payment_data, .. } => { + *payment_data = Some(msgs::FinalOnionHopData { + payment_secret: PaymentSecret([43; 32]), // Doesn't match the secret used above + total_msat: amount * 2, + }); + }, + _ => panic!("Expected PendingHTLCRouting::ReceiveKeysend"), + } + }, + _ => { + panic!("Unexpected HTLCForwardInfo"); + }, } - } + } else { + panic!("Expected pending receive"); + }; nodes[3].node.process_pending_htlc_forwards(); let fail_type = HTLCHandlingFailureType::Receive { payment_hash }; expect_and_process_pending_htlcs_and_htlc_handling_failed(&nodes[3], &[fail_type]); From 0d1705d5a66cfad5e9843854914890b8776432a6 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 20 Aug 2025 10:51:56 +0200 Subject: [PATCH 3/5] f Use other match pattern here already .. to keep changes in the following commits minimal. --- lightning/src/ln/payment_tests.rs | 83 +++++++++++++------------------ 1 file changed, 34 insertions(+), 49 deletions(-) diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index e04cf88d215..92db400337a 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -637,29 +637,22 @@ fn test_reject_mpp_keysend_htlc_mismatching_secret() { assert!(nodes[3].node.get_and_clear_pending_msg_events().is_empty()); assert_eq!(nodes[3].node.forward_htlcs.lock().unwrap().len(), 1); - if let Some((_, pending_forwards)) = - nodes[3].node.forward_htlcs.lock().unwrap().iter_mut().next() - { - assert_eq!(pending_forwards.len(), 1); - match pending_forwards.get_mut(0).unwrap() { - &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { ref mut forward_info, .. }) => { - match forward_info.routing { - PendingHTLCRouting::ReceiveKeysend { ref mut payment_data, .. } => { - *payment_data = Some(msgs::FinalOnionHopData { - payment_secret: PaymentSecret([42; 32]), - total_msat: amount * 2, - }); - }, - _ => panic!("Expected PendingHTLCRouting::ReceiveKeysend"), - } - }, - _ => { - panic!("Unexpected HTLCForwardInfo"); - }, - } - } else { - panic!("Expected pending receive"); - }; + match nodes[3].node.forward_htlcs.lock().unwrap().get_mut(&0).unwrap().get_mut(0).unwrap() { + &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { ref mut forward_info, .. }) => { + match forward_info.routing { + PendingHTLCRouting::ReceiveKeysend { ref mut payment_data, .. } => { + *payment_data = Some(msgs::FinalOnionHopData { + payment_secret: PaymentSecret([42; 32]), + total_msat: amount * 2, + }); + }, + _ => panic!("Expected PendingHTLCRouting::ReceiveKeysend"), + } + }, + _ => { + panic!("Unexpected HTLCForwardInfo"); + }, + } nodes[3].node.process_pending_htlc_forwards(); // Pay along nodes[2] @@ -687,34 +680,26 @@ fn test_reject_mpp_keysend_htlc_mismatching_secret() { let update_add_3 = update_3.update_add_htlcs[0].clone(); nodes[3].node.handle_update_add_htlc(node_c_id, &update_add_3); commitment_signed_dance!(nodes[3], nodes[2], update_3.commitment_signed, false, true); - expect_htlc_failure_conditions(nodes[3].node.get_and_clear_pending_events(), &[]); - nodes[3].node.process_pending_update_add_htlcs(); - + assert!(nodes[3].node.get_and_clear_pending_events().is_empty()); assert!(nodes[3].node.get_and_clear_pending_msg_events().is_empty()); + nodes[3].node.process_pending_update_add_htlcs(); assert_eq!(nodes[3].node.forward_htlcs.lock().unwrap().len(), 1); - if let Some((_, pending_forwards)) = - nodes[3].node.forward_htlcs.lock().unwrap().iter_mut().next() - { - assert_eq!(pending_forwards.len(), 1); - match pending_forwards.get_mut(0).unwrap() { - &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { ref mut forward_info, .. }) => { - match forward_info.routing { - PendingHTLCRouting::ReceiveKeysend { ref mut payment_data, .. } => { - *payment_data = Some(msgs::FinalOnionHopData { - payment_secret: PaymentSecret([43; 32]), // Doesn't match the secret used above - total_msat: amount * 2, - }); - }, - _ => panic!("Expected PendingHTLCRouting::ReceiveKeysend"), - } - }, - _ => { - panic!("Unexpected HTLCForwardInfo"); - }, - } - } else { - panic!("Expected pending receive"); - }; + match nodes[3].node.forward_htlcs.lock().unwrap().get_mut(&0).unwrap().get_mut(0).unwrap() { + &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { ref mut forward_info, .. }) => { + match forward_info.routing { + PendingHTLCRouting::ReceiveKeysend { ref mut payment_data, .. } => { + *payment_data = Some(msgs::FinalOnionHopData { + payment_secret: PaymentSecret([43; 32]), // Doesn't match the secret used above + total_msat: amount * 2, + }); + }, + _ => panic!("Expected PendingHTLCRouting::ReceiveKeysend"), + } + }, + _ => { + panic!("Unexpected HTLCForwardInfo"); + }, + } nodes[3].node.process_pending_htlc_forwards(); let fail_type = HTLCHandlingFailureType::Receive { payment_hash }; expect_and_process_pending_htlcs_and_htlc_handling_failed(&nodes[3], &[fail_type]); From 2c85a0d71c4f7ecd7f5af3e4d5a037cb640305ed Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 30 Jul 2025 09:07:32 +0200 Subject: [PATCH 4/5] Move receiving HTLCs to a `receive_htlcs` field Previously, we'd store receiving HTLCs side-by-side with HTLCs forwards in the `forwards_htlcs` field under SCID 0. Here, we opt to split out a separate `receive_htlcs` field, also omitting the 0 magic value. --- lightning/src/ln/channelmanager.rs | 106 +++++++++++++++++++++++------ lightning/src/ln/payment_tests.rs | 8 +-- 2 files changed, 89 insertions(+), 25 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index af82f862878..245c26ff285 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -2478,6 +2478,8 @@ where // | | // | |__`pending_intercepted_htlcs` // | +// |__`receive_htlcs` +// | // |__`decode_update_add_htlcs` // | // |__`per_peer_state` @@ -2553,7 +2555,7 @@ pub struct ChannelManager< /// See `ChannelManager` struct-level documentation for lock order requirements. pending_outbound_payments: OutboundPayments, - /// SCID/SCID Alias -> forward infos. Key of 0 means payments received. + /// SCID/SCID Alias -> forward infos. /// /// Note that because we may have an SCID Alias as the key we can have two entries per channel, /// though in practice we probably won't be receiving HTLCs for a channel both via the alias @@ -2562,6 +2564,9 @@ pub struct ChannelManager< /// Note that no consistency guarantees are made about the existence of a channel with the /// `short_channel_id` here, nor the `short_channel_id` in the `PendingHTLCInfo`! /// + /// This will also hold any [`FailHTLC`]s arising from handling [`Self::pending_intercepted_htlcs`] or + /// [`Self::receive_htlcs`]. + /// /// See `ChannelManager` struct-level documentation for lock order requirements. #[cfg(test)] pub(super) forward_htlcs: Mutex>>, @@ -2570,9 +2575,21 @@ pub struct ChannelManager< /// Storage for HTLCs that have been intercepted and bubbled up to the user. We hold them here /// until the user tells us what we should do with them. /// + /// Note that any failures that may arise from handling these will be pushed to + /// [`Self::forward_htlcs`] with the previous hop's SCID. + /// /// See `ChannelManager` struct-level documentation for lock order requirements. pending_intercepted_htlcs: Mutex>, - + /// Storage for HTLCs that are meant for us. + /// + /// Note that any failures that may arise from handling these will be pushed to + /// [`Self::forward_htlcs`] with the previous hop's SCID. + /// + /// See `ChannelManager` struct-level documentation for lock order requirements. + #[cfg(test)] + pub(super) receive_htlcs: Mutex>, + #[cfg(not(test))] + receive_htlcs: Mutex>, /// SCID/SCID Alias -> pending `update_add_htlc`s to decode. /// /// Note that because we may have an SCID Alias as the key we can have two entries per channel, @@ -3755,6 +3772,7 @@ where outbound_scid_aliases: Mutex::new(new_hash_set()), pending_outbound_payments: OutboundPayments::new(new_hash_map()), forward_htlcs: Mutex::new(new_hash_map()), + receive_htlcs: Mutex::new(Vec::new()), decode_update_add_htlcs: Mutex::new(new_hash_map()), claimable_payments: Mutex::new(ClaimablePayments { claimable_payments: new_hash_map(), pending_claiming_payments: new_hash_map() }), pending_intercepted_htlcs: Mutex::new(new_hash_map()), @@ -6494,6 +6512,9 @@ where if !self.forward_htlcs.lock().unwrap().is_empty() { return true; } + if !self.receive_htlcs.lock().unwrap().is_empty() { + return true; + } if !self.decode_update_add_htlcs.lock().unwrap().is_empty() { return true; } @@ -6541,20 +6562,19 @@ where for (short_chan_id, mut pending_forwards) in forward_htlcs { should_persist = NotifyOption::DoPersist; - if short_chan_id != 0 { - self.process_forward_htlcs( - short_chan_id, - &mut pending_forwards, - &mut failed_forwards, - &mut phantom_receives, - ); - } else { - self.process_receive_htlcs( - &mut pending_forwards, - &mut new_events, - &mut failed_forwards, - ); - } + self.process_forward_htlcs( + short_chan_id, + &mut pending_forwards, + &mut failed_forwards, + &mut phantom_receives, + ); + } + + let mut receive_htlcs = Vec::new(); + mem::swap(&mut receive_htlcs, &mut self.receive_htlcs.lock().unwrap()); + if !receive_htlcs.is_empty() { + self.process_receive_htlcs(receive_htlcs, &mut new_events, &mut failed_forwards); + should_persist = NotifyOption::DoPersist; } let best_block_height = self.best_block.read().unwrap().height; @@ -7068,11 +7088,11 @@ where } fn process_receive_htlcs( - &self, pending_forwards: &mut Vec, + &self, receive_htlcs: Vec, new_events: &mut VecDeque<(Event, Option)>, failed_forwards: &mut Vec, ) { - 'next_forwardable_htlc: for forward_info in pending_forwards.drain(..) { + 'next_forwardable_htlc: for forward_info in receive_htlcs.into_iter() { match forward_info { HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { prev_short_channel_id, @@ -10613,8 +10633,21 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ let scid = match forward_info.routing { PendingHTLCRouting::Forward { short_channel_id, .. } => short_channel_id, PendingHTLCRouting::TrampolineForward { .. } => 0, - PendingHTLCRouting::Receive { .. } => 0, - PendingHTLCRouting::ReceiveKeysend { .. } => 0, + PendingHTLCRouting::Receive { .. } + | PendingHTLCRouting::ReceiveKeysend { .. } => { + self.receive_htlcs.lock().unwrap().push(HTLCForwardInfo::AddHTLC( + PendingAddHTLCInfo { + prev_short_channel_id, + prev_counterparty_node_id, + prev_funding_outpoint, + prev_channel_id, + prev_htlc_id, + prev_user_channel_id, + forward_info, + }, + )); + continue; + }, }; // Pull this now to avoid introducing a lock order with `forward_htlcs`. let is_our_scid = self.short_to_chan_info.read().unwrap().contains_key(&scid); @@ -15279,6 +15312,8 @@ where } } + let receive_htlcs = self.receive_htlcs.lock().unwrap(); + let mut decode_update_add_htlcs_opt = None; let decode_update_add_htlcs = self.decode_update_add_htlcs.lock().unwrap(); if !decode_update_add_htlcs.is_empty() { @@ -15446,6 +15481,7 @@ where (17, in_flight_monitor_updates, option), (19, peer_storage_dir, optional_vec), (21, WithoutLength(&self.flow.writeable_async_receive_offer_cache()), required), + (23, *receive_htlcs, required_vec), }); Ok(()) @@ -16006,6 +16042,7 @@ where const MAX_ALLOC_SIZE: usize = 1024 * 64; let forward_htlcs_count: u64 = Readable::read(reader)?; let mut forward_htlcs = hash_map_with_capacity(cmp::min(forward_htlcs_count as usize, 128)); + let mut legacy_receive_htlcs: Vec = Vec::new(); for _ in 0..forward_htlcs_count { let short_channel_id = Readable::read(reader)?; let pending_forwards_count: u64 = Readable::read(reader)?; @@ -16014,7 +16051,26 @@ where MAX_ALLOC_SIZE / mem::size_of::(), )); for _ in 0..pending_forwards_count { - pending_forwards.push(Readable::read(reader)?); + let pending_htlc = Readable::read(reader)?; + // Prior to LDK 0.2, Receive HTLCs used to be stored in `forward_htlcs` under SCID == 0. Here we migrate + // the old data if necessary. + if short_channel_id == 0 { + match pending_htlc { + HTLCForwardInfo::AddHTLC(ref htlc_info) => { + if matches!( + htlc_info.forward_info.routing, + PendingHTLCRouting::Receive { .. } + | PendingHTLCRouting::ReceiveKeysend { .. } + ) { + legacy_receive_htlcs.push(pending_htlc); + continue; + } + }, + _ => {}, + } + } + + pending_forwards.push(pending_htlc); } forward_htlcs.insert(short_channel_id, pending_forwards); } @@ -16131,6 +16187,7 @@ where let mut inbound_payment_id_secret = None; let mut peer_storage_dir: Option)>> = None; let mut async_receive_offer_cache: AsyncReceiveOfferCache = AsyncReceiveOfferCache::new(); + let mut receive_htlcs = None; read_tlv_fields!(reader, { (1, pending_outbound_payments_no_retry, option), (2, pending_intercepted_htlcs, option), @@ -16149,8 +16206,14 @@ where (17, in_flight_monitor_updates, option), (19, peer_storage_dir, optional_vec), (21, async_receive_offer_cache, (default_value, async_receive_offer_cache)), + (23, receive_htlcs, optional_vec), }); let mut decode_update_add_htlcs = decode_update_add_htlcs.unwrap_or_else(|| new_hash_map()); + debug_assert!( + receive_htlcs.as_ref().map_or(true, |r| r.is_empty()) + || legacy_receive_htlcs.is_empty() + ); + let receive_htlcs = receive_htlcs.unwrap_or_else(|| legacy_receive_htlcs); let peer_storage_dir: Vec<(PublicKey, Vec)> = peer_storage_dir.unwrap_or_else(Vec::new); if fake_scid_rand_bytes.is_none() { fake_scid_rand_bytes = Some(args.entropy_source.get_secure_random_bytes()); @@ -16981,6 +17044,7 @@ where pending_intercepted_htlcs: Mutex::new(pending_intercepted_htlcs.unwrap()), forward_htlcs: Mutex::new(forward_htlcs), + receive_htlcs: Mutex::new(receive_htlcs), decode_update_add_htlcs: Mutex::new(decode_update_add_htlcs), claimable_payments: Mutex::new(ClaimablePayments { claimable_payments, diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index 92db400337a..f85dd0a239a 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -636,8 +636,8 @@ fn test_reject_mpp_keysend_htlc_mismatching_secret() { nodes[3].node.process_pending_update_add_htlcs(); assert!(nodes[3].node.get_and_clear_pending_msg_events().is_empty()); - assert_eq!(nodes[3].node.forward_htlcs.lock().unwrap().len(), 1); - match nodes[3].node.forward_htlcs.lock().unwrap().get_mut(&0).unwrap().get_mut(0).unwrap() { + assert_eq!(nodes[3].node.receive_htlcs.lock().unwrap().len(), 1); + match nodes[3].node.receive_htlcs.lock().unwrap().get_mut(0).unwrap() { &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { ref mut forward_info, .. }) => { match forward_info.routing { PendingHTLCRouting::ReceiveKeysend { ref mut payment_data, .. } => { @@ -683,8 +683,8 @@ fn test_reject_mpp_keysend_htlc_mismatching_secret() { assert!(nodes[3].node.get_and_clear_pending_events().is_empty()); assert!(nodes[3].node.get_and_clear_pending_msg_events().is_empty()); nodes[3].node.process_pending_update_add_htlcs(); - assert_eq!(nodes[3].node.forward_htlcs.lock().unwrap().len(), 1); - match nodes[3].node.forward_htlcs.lock().unwrap().get_mut(&0).unwrap().get_mut(0).unwrap() { + assert_eq!(nodes[3].node.receive_htlcs.lock().unwrap().len(), 1); + match nodes[3].node.receive_htlcs.lock().unwrap().get_mut(0).unwrap() { &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { ref mut forward_info, .. }) => { match forward_info.routing { PendingHTLCRouting::ReceiveKeysend { ref mut payment_data, .. } => { From e08d3dcdfdb5eb943e6d18051b377693736509dd Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 20 Aug 2025 10:56:52 +0200 Subject: [PATCH 5/5] f Add comment that scid == 0 means trampoline now --- lightning/src/ln/channelmanager.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 245c26ff285..90444c3676f 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -2557,6 +2557,8 @@ pub struct ChannelManager< /// SCID/SCID Alias -> forward infos. /// + /// Note that key of 0 means it's a trampoline payment. + /// /// Note that because we may have an SCID Alias as the key we can have two entries per channel, /// though in practice we probably won't be receiving HTLCs for a channel both via the alias /// and via the classic SCID.