Skip to content

[DRAFT] Generic HTLC Interception #3843

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

aylagreystone
Copy link

Adds a HTLCInterceptHandler trait with a default implementation that forwards the HTLC as expected. Opened as a draft to get some initial feedback on the overall direction because I'm not really sure if this is the right approach.

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Jun 10, 2025

👋 I see @wpaulino was un-assigned.
If you'd like another reviewer assignemnt, please click here.

Copy link

codecov bot commented Jun 10, 2025

Codecov Report

Attention: Patch coverage is 19.25134% with 151 lines in your changes missing coverage. Please review.

Project coverage is 89.76%. Comparing base (0848e7a) to head (331b37b).
Report is 28 commits behind head on main.

Files with missing lines Patch % Lines
lightning/src/ln/channelmanager.rs 12.71% 150 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3843      +/-   ##
==========================================
- Coverage   89.88%   89.76%   -0.12%     
==========================================
  Files         160      162       +2     
  Lines      129654   130361     +707     
  Branches   129654   130361     +707     
==========================================
+ Hits       116534   117014     +480     
- Misses      10425    10646     +221     
- Partials     2695     2701       +6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@tnull tnull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK, this def. goes in the direction I had in mind for #2855.

Might be good to get early feedback from others too though (cc @TheBlueMatt @wpaulino)

>,
Arc<test_utils::TestLogger>,
>;
type ChannelManager = channelmanager::ChannelManager<
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: It's tabs, not spaces across LDK-related projects. Please also just run cargo +1.63 fmt after each commit.

/// HTLCInterceptAction::Forward {
/// next_hop_channel_id: htlc_info.next_channel_id,
/// next_node_id: htlc_info.next_node_id,
/// amount_to_forward_msat: htlc_info.expected_outbound_amount_msat - fee,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this might work, I wonder if it would be better if we'd find a way to still restrict the possible changes that could happen as part of an HTLCInterceptionAction to safe values and/or values that are feasible to override. For example, we could define a struct ModifyForwardAction or similar that is held in HTLCInterceptAction::Forward and that would expose certain setters that still employ certain safety checks, etc.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Would the idea be that the handler is called with an instance of ModifyForwardAction initialized with the current next hop information and that represents a no-op essentially? Or were you thinking it would be something the end-user constructs themselves if they want to modify the forward?

@aylagreystone
Copy link
Author

One concern I had with this approach compared to the scid/event based approach that currently exists is around use-cases like LSPS5 where the end-user will want to hold the HTLC while they attempt to wake-up the recipient before attempting the forward.

This inline handler approach means this entire code-path would block in that scenario. Seems potentially dangerous to block in this hot-path but is something I would love some feedback on.

@wpaulino
Copy link
Contributor

One concern I had with this approach compared to the scid/event based approach that currently exists is around use-cases like LSPS5 where the end-user will want to hold the HTLC while they attempt to wake-up the recipient before attempting the forward.

This inline handler approach means this entire code-path would block in that scenario. Seems potentially dangerous to block in this hot-path but is something I would love some feedback on.

Yeah this was my immediate concern as well. The handler approach requires implementers to immediately act on the intercepted HTLC to not delay unrelated HTLCs in other channels, given this is interrupting the node's forwarding pipeline. If we really intend for generic interception then the flow needs to be async as we can't possible predict/enforce the type of code that would be run in the handler. The event approach would help solve this, and also allows for flexibility on the user's end to let them decide which specific channels they're interested in receiving intercept events for.

@wpaulino wpaulino removed their request for review June 12, 2025 19:45
@aylagreystone
Copy link
Author

Yeah this was my immediate concern as well. The handler approach requires implementers to immediately act on the intercepted HTLC to not delay unrelated HTLCs in other channels, given this is interrupting the node's forwarding pipeline. If we really intend for generic interception then the flow needs to be async as we can't possible predict/enforce the type of code that would be run in the handler. The event approach would help solve this, and also allows for flexibility on the user's end to let them decide which specific channels they're interested in receiving intercept events for.

Thanks for the feedback!

I had opened this PR previously (#3839) that extends the current scid + event based interception that exists today to allow users to opt-in specific channels to interception by creating the channel's scid alias using the Intercept namespace.

That approach works for my use cases so I'm happy to pick that back up if the team deems it a viable path forward.

@wpaulino
Copy link
Contributor

I had opened this PR previously (#3839) that extends the current scid + event based interception that exists today to allow users to opt-in specific channels to interception by creating the channel's scid alias using the Intercept namespace.

Let's wait for @TheBlueMatt to chime in, but that approach doesn't seem flexible enough (assuming that's the goal) because it'd require you to set interception during channel creation and it'd remain fixed throughout the lifetime of the channel. We'd probably want something more configurable that lets you specify the channels you're interested in at runtime.

@tnull
Copy link
Contributor

tnull commented Jun 13, 2025

If we really intend for generic interception then the flow needs to be async as we can't possible predict/enforce the type of code that would be run in the handler.

Yes, while I had hoped we can find an interface that is a bit sleeker I agree that we probably have to go the callback route, at least as one option. FWIW, we could have "I'll call back in" as one ForwardAction result returned from the handler, but I have to admit that going both ways is probably not necessary, even though I generally would prefer the inlined-handler style for the common case.

The event approach would help solve this, and also allows for flexibility on the user's end to let them decide which specific channels they're interested in receiving intercept events for.

I mentioned this on Discord also, but I don't expect many users to filter the channels before they intercept. I rather expect them to intercept all and then decide in their logic whether just to shortcut to forwarding, or take additional steps before continuing the flow. In particular, we also had user requests for a generalized interface that currently simply use HTLC interception (in LND) as some way to get additional data on all HTLCs forwarded, and to hook into more general 'HTLC firewalling' code.

All that is to say that IMO we'll need to find a solution for the interface that scales to the degree so a large forwarding node could in theory use it generally to hook all HTLCs they see. While I agree that we'll probably need a callback architecture to not block the flow, I don't think (ab)using the event queue for this is an option, as it would be really spammy, and would introduce at least two (?) additional unnecessary IOps.

TLDR: Building out a separate, callback & trait-based interface (i.e., essentially what's in this draft, but with callbacks rather than inline-handling) seems currently like the best option to me?

Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, it certainly seems like no matter what we do we need to support callbacks to decide what to do with an HTLC later. If we don't want to use the event pipeline for those (which I think I agree with, though we do already push an event per forwarded HTLC, so its not entirely out of the realm of possibility), then indeed having some trait decide which HTLCs to intercept makes sense to me.

Rather than having the trait return a "what to do with this HTLC", we could instead consider the trait returning "do we want to intercept this" and then either push it out via the normal intercept pipeline (without a notification event) or forward it normally. This would also make the patch pretty small, assuming this is the API we want. Of course it might be nice for some to have the "what to do with this HTLC" option in the API, but we could similarly implement that by just relying on the existing intercept logic, I assume.

/// Default value: `false`
///
/// [`HTLCInterceptHandler`]: crate::ln::channelmanager::HTLCInterceptHandler
pub accept_generic_htlc_intercept: bool,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dunno if we need to bother with a config flag if we're setting a trait for this. We can just use the Default trait impl and call it always, no?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants