Add util to gather channels that support htlc_hold
What changed, and why it matters
This commit adds a small internal helper in a Lightning payment library. It collects payment channels whose peer supports a new 'hold HTLC' feature, used for a specific type of invoice (StaticInvoice/async payments). The change also tweaks an existing internal filter helper to accept mutable closures. There is no obvious security bug in the diff itself; it is a feature addition.
No immediate action required. Review as part of normal code review for the async-payments/StaticInvoice feature. Ensure callers handle the returned error and that the feature-bit gating cannot be bypassed by a malicious peer.
Security signals we found
New helper selects channels based on a feature bit and a local config flag
Trait bound relaxed from Fn+Copy to FnMut in an internal utility
No input validation, serialization, or cryptographic changes visible
No explicit security claim or fix language in commit message
Evidence from the diff
The patch introduces ChannelManager::hold_htlc_channels(), which returns funded, live channels whose counterparty advertises InitFeatures::supports_htlc_hold, provided the local config flag hold_outbound_htlcs_at_next_hop is enabled. It reuses list_funded_channels_with_filter, changing that helper’s trait bound from Fn + Copy to FnMut and passing mut f so closures can capture mutable state. The new method is intended to support sending async payments to StaticInvoices. No unsafe code, cryptographic operations, or network parsing changes are present.
Changed components
lightning/src/ln/channelmanager.rsChannelManager::list_funded_channels_with_filternew ChannelManager::hold_htlc_channelsInspect captured patch +25 / −3
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 69907d8..bbe36b6 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -4087,9 +4087,9 @@ where
}
fn list_funded_channels_with_filter<
- Fn: FnMut(&(&InitFeatures, &ChannelId, &Channel<SP>)) -> bool + Copy,
+ Fn: FnMut(&(&InitFeatures, &ChannelId, &Channel<SP>)) -> bool,
>(
- &self, f: Fn,
+ &self, mut f: Fn,
) -> Vec<ChannelDetails> {
// Allocate our best estimate of the number of channels we have in the `res`
// Vec. Sadly the `short_to_chan_info` map doesn't cover channels without
@@ -4109,7 +4109,7 @@ where
.iter()
.map(|(cid, c)| (&peer_state.latest_features, cid, c))
.filter(|(_, _, chan)| chan.is_funded())
- .filter(f);
+ .filter(|v| f(v));
res.extend(filtered_chan_by_id.map(|(_, _channel_id, channel)| {
ChannelDetails::from_channel(
channel,
@@ -5503,6 +5503,28 @@ where
res
}
+ /// Returns a list of channels where our counterparty supports
+ /// [`InitFeatures::supports_htlc_hold`], or an error if there are none or we are configured not
+ /// to hold HTLCs at our next-hop channel counterparty. Useful for sending async payments to
+ /// [`StaticInvoice`]s.
+ fn hold_htlc_channels(&self) -> Result<Vec<ChannelDetails>, ()> {
+ let should_send_async = self.config.read().unwrap().hold_outbound_htlcs_at_next_hop;
+ if !should_send_async {
+ return Err(());
+ }
+
+ let hold_htlc_channels =
+ self.list_funded_channels_with_filter(|&(init_features, _, ref channel)| {
+ init_features.supports_htlc_hold() && channel.context().is_live()
+ });
+
+ if hold_htlc_channels.is_empty() {
+ Err(())
+ } else {
+ Ok(hold_htlc_channels)
+ }
+ }
+
fn send_payment_for_static_invoice(
&self, payment_id: PaymentId,
) -> Result<(), Bolt12PaymentError> {
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.