Add enable_htlc_hold cfg flag + fail hold htlcs
What changed, and why it matters
This commit adds an experimental, test-only configuration flag for a new Lightning protocol feature called 'HTLC hold.' When the flag is disabled (the default), the node rejects incoming HTLCs that ask it to hold payments. This is a defensive correctness change to make sure a node does not accidentally promise a service it has not advertised, rather than a fix for an active security bug.
No urgent action. Treat as normal feature work. If deploying HTLC hold in production later, ensure the feature is no longer test-only and that the backwards-compatibility note in the config documentation is observed.
Security signals we found
New feature flag gated by #[cfg(test)] indicates experimental/BOLTS-draft functionality
Defensive fail-back prevents a node from being asked to hold HTLCs it did not advertise
Commit message references BOLTS PR 989 (draft spec change)
No memory-safety issues, no panic/unwrap changes, no cryptographic changes
Evidence from the diff
The patch introduces UserConfig::enable_htlc_hold (gated by #[cfg(test)]) and a corresponding provided_init_features bit. In ChannelManager’s HTLC-forwarding path, if an incoming update_add_htlc carries hold_htlc but the local node has not advertised htlc_hold support, the HTLC is failed back with TemporaryNodeFailure. The commit comment explicitly notes that disconnecting the peer would be unsafe because toggling the config off after a peer already enqueued updates would cause repeated disconnects; failing back is the chosen safe behavior.
Changed components
lightning/src/ln/channelmanager.rslightning/src/util/config.rsInspect captured patch +47 / −0
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index dfc10e8..a4d00cc 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -6420,6 +6420,32 @@ where
});
let shared_secret = next_hop.shared_secret().secret_bytes();
+ // Nodes shouldn't expect us to hold HTLCs for them if we don't advertise htlc_hold feature
+ // support.
+ //
+ // If we wanted to pretend to be a node that didn't understand the feature at all here, the
+ // correct behavior would've been to disconnect the sender when we first received the
+ // update_add message. However, this would make the `UserConfig::enable_htlc_hold` option
+ // unsafe -- if our node switched the config option from on to off just after the sender
+ // enqueued their update_add + CS, the sender would continue retransmitting those messages
+ // and we would keep disconnecting them until the HTLC timed out.
+ if update_add_htlc.hold_htlc.is_some()
+ && !BaseMessageHandler::provided_node_features(self).supports_htlc_hold()
+ {
+ let reason = LocalHTLCFailureReason::TemporaryNodeFailure;
+ let htlc_fail = self.htlc_failure_from_update_add_err(
+ &update_add_htlc,
+ &incoming_counterparty_node_id,
+ reason,
+ is_intro_node_blinded_forward,
+ &shared_secret,
+ );
+ let failure_type =
+ get_htlc_failure_type(outgoing_scid_opt, update_add_htlc.payment_hash);
+ htlc_fails.push((htlc_fail, failure_type, reason.into()));
+ continue;
+ }
+
// Process the HTLC on the incoming channel.
match self.do_funded_channel_callback(
incoming_scid,
@@ -14847,6 +14873,13 @@ pub fn provided_init_features(config: &UserConfig) -> InitFeatures {
features.set_anchor_zero_fee_commitments_optional();
}
+ // If we are configured to be an announced node, we are expected to be always-online and can
+ // advertise the htlc_hold feature.
+ #[cfg(test)]
+ if config.enable_htlc_hold {
+ features.set_htlc_hold_optional();
+ }
+
features
}
diff --git a/lightning/src/util/config.rs b/lightning/src/util/config.rs
index a0c7467..f0c0330 100644
--- a/lightning/src/util/config.rs
+++ b/lightning/src/util/config.rs
@@ -935,6 +935,18 @@ pub struct UserConfig {
///
/// Default value: `false`
pub enable_dual_funded_channels: bool,
+ /// LDK supports a feature for always-online nodes such that these nodes can hold onto an HTLC
+ /// from an often-offline channel peer until the often-offline payment recipient sends an onion
+ /// message telling the always-online node to release the HTLC. If this is set to `true`, our node
+ /// will carry out this feature for channel peers that request it.
+ ///
+ /// This should only be set to `true` for nodes which expect to be online reliably.
+ ///
+ /// Setting this to `true` may break backwards compatibility with LDK versions < 0.2.
+ ///
+ /// Default value: `false`
+ #[cfg(test)]
+ pub enable_htlc_hold: bool,
}
impl Default for UserConfig {
@@ -949,6 +961,8 @@ impl Default for UserConfig {
accept_intercept_htlcs: false,
manually_handle_bolt12_invoices: false,
enable_dual_funded_channels: false,
+ #[cfg(test)]
+ enable_htlc_hold: false,
}
}
}
Why this scored 32/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.