Support held_htlc_available counterparty reply path
What changed, and why it matters
This commit is a routine code refactor in the Lightning Dev Kit library. It restructures how reply paths are specified for a new type of onion message (held_htlc_available) used in an experimental async-payments feature. The change adds a new enum so that future code can choose whether the reply path should end at the sender's own node or at an always-online channel counterparty. There is no security fix, vulnerability, or exploit here.
No security action required. Review as normal feature/refactor code.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit refactors the enqueue_held_htlc_available API in lightning/src/offers/flow.rs. Previously it accepted a payment_id and a Vec
Changed components
lightning/src/ln/channelmanager.rslightning/src/offers/flow.rsInspect captured patch +48 / −17
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index ff92cfc..523de99 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -91,7 +91,7 @@ use crate::ln::outbound_payment::{
};
use crate::ln::types::ChannelId;
use crate::offers::async_receive_offer_cache::AsyncReceiveOfferCache;
-use crate::offers::flow::{InvreqResponseInstructions, OffersMessageFlow};
+use crate::offers::flow::{HeldHtlcReplyPath, InvreqResponseInstructions, OffersMessageFlow};
use crate::offers::invoice::{
Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder, DEFAULT_RELATIVE_EXPIRY,
};
@@ -5500,11 +5500,12 @@ where
);
}
} else {
- let enqueue_held_htlc_available_res = self.flow.enqueue_held_htlc_available(
- invoice,
+ let reply_path = HeldHtlcReplyPath::ToUs {
payment_id,
- self.get_peers_for_blinded_path(),
- );
+ peers: self.get_peers_for_blinded_path(),
+ };
+ let enqueue_held_htlc_available_res =
+ self.flow.enqueue_held_htlc_available(invoice, reply_path);
if enqueue_held_htlc_available_res.is_err() {
self.abandon_payment_with_reason(
payment_id,
diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs
index 010722f..a6484f0 100644
--- a/lightning/src/offers/flow.rs
+++ b/lightning/src/offers/flow.rs
@@ -423,6 +423,26 @@ pub enum InvreqResponseInstructions {
},
}
+/// Parameters for the reply path to a [`HeldHtlcAvailable`] onion message.
+pub enum HeldHtlcReplyPath {
+ /// The reply path to the [`HeldHtlcAvailable`] message should terminate at our node.
+ ToUs {
+ /// The id of the payment.
+ payment_id: PaymentId,
+ /// The peers to use when creating this reply path.
+ peers: Vec<MessageForwardNode>,
+ },
+ /// The reply path to the [`HeldHtlcAvailable`] message should terminate at our next-hop channel
+ /// counterparty, as they are holding our HTLC until they receive the corresponding
+ /// [`ReleaseHeldHtlc`] message.
+ ///
+ /// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc
+ ToCounterparty {
+ /// The blinded path provided to us by our counterparty.
+ path: BlindedMessagePath,
+ },
+}
+
impl<MR: Deref, L: Deref> OffersMessageFlow<MR, L>
where
MR::Target: MessageRouter,
@@ -1159,26 +1179,36 @@ where
/// Enqueues `held_htlc_available` onion messages to be sent to the payee via the reply paths
/// contained within the provided [`StaticInvoice`].
///
- /// # Peers
- ///
- /// The user must provide a list of [`MessageForwardNode`] that will be used to generate valid
- /// reply paths for the recipient to send back the corresponding [`ReleaseHeldHtlc`] onion message.
- ///
/// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc
/// [`supports_onion_messages`]: crate::types::features::Features::supports_onion_messages
pub fn enqueue_held_htlc_available(
- &self, invoice: &StaticInvoice, payment_id: PaymentId, peers: Vec<MessageForwardNode>,
+ &self, invoice: &StaticInvoice, reply_path_params: HeldHtlcReplyPath,
) -> Result<(), Bolt12SemanticError> {
- let context =
- MessageContext::AsyncPayments(AsyncPaymentsContext::OutboundPayment { payment_id });
+ let reply_path_terminates_at_us =
+ matches!(reply_path_params, HeldHtlcReplyPath::ToUs { .. });
- let reply_paths = self
- .create_blinded_paths(peers, context)
- .map_err(|_| Bolt12SemanticError::MissingPaths)?;
+ let reply_paths = match reply_path_params {
+ HeldHtlcReplyPath::ToUs { payment_id, peers } => {
+ let context =
+ MessageContext::AsyncPayments(AsyncPaymentsContext::OutboundPayment {
+ payment_id,
+ });
+ self.create_blinded_paths(peers, context)
+ .map_err(|_| {
+ log_trace!(self.logger, "Failed to create blinded paths when enqueueing held_htlc_available message");
+ Bolt12SemanticError::MissingPaths
+ })?
+ },
+ HeldHtlcReplyPath::ToCounterparty { path } => vec![path],
+ };
+ log_trace!(
+ self.logger,
+ "Sending held_htlc_available message for async HTLC, with reply_path terminating at {}",
+ if reply_path_terminates_at_us { "our node" } else { "our always-online counterparty" }
+ );
let mut pending_async_payments_messages =
self.pending_async_payments_messages.lock().unwrap();
-
let message = AsyncPaymentsMessage::HeldHtlcAvailable(HeldHtlcAvailable {});
enqueue_onion_message_with_reply_paths(
message,
Why this scored 15/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.