-
Notifications
You must be signed in to change notification settings - Fork 377
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
Intercept HTLC forwards for JIT channels #1835
Intercept HTLC forwards for JIT channels #1835
Conversation
beae304
to
90c4d75
Compare
8e3a664
to
b166af0
Compare
Codecov ReportBase: 90.70% // Head: 90.67% // Decreases project coverage by
Additional details and impacted files@@ Coverage Diff @@
## main #1835 +/- ##
==========================================
- Coverage 90.70% 90.67% -0.03%
==========================================
Files 91 91
Lines 48090 48303 +213
Branches 48090 48303 +213
==========================================
+ Hits 43618 43800 +182
- Misses 4472 4503 +31
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report at Codecov. |
5e8146c
to
638a5d1
Compare
2b60d57
to
d763bb5
Compare
Are there plans for generic HTLC interception at all? Not dependent on the routing hints of someone else's invoice? There can still be logic into determining the type based on SCID but in general all HTLC's could still be intercepted or at least subscribed to based on type. There are many use cases for generic HTLC interception that shouldn't depend on LDK development, here are a few:
You could still have SCID logic but I would love to see generic solved for first and then application specific tbh. I think that's worked really well in the LND/CLN ecosystems. |
Yes, the plan is to swiftly follow up to this PR with adding general forward interception :) (1), (4), and (5) are already supported via our |
d763bb5
to
099533d
Compare
Had to rebase to get #1844. |
Awesome! Concept ACK from me then. I browsed through the code and the API usage sounds good to me but probably can't comment on the rest 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First look-through generally looks good :).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need a fallback failure on a timer. If the user screws up and loses the intercept event entirely we shouldn't just sit on the HTLC forever and get the channel force-closed.
Great feedback once again @ViktorTigerstrom! Working on addressing it |
0759af8
to
ee70d50
Compare
72087f8
to
225a22b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me. Only real feedback I have left is that it would be valuable to include test coverage to ensure that we don't generate the PaymentIntercepted
event + populate pending_intercepted_htlcs
when accept_intercept_htlcs
is set to false.
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]); | ||
commitment_signed_dance!(nodes[1], nodes[0], &payment_event.commitment_msg, false, true); | ||
|
||
// Check that we generate the PaymentIntercepted event when an intercept forward is detected. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be valuable to also have test coverage ensuring that the PaymentIntercepted
event isn't generated + pending_intercepted_htlcs
isn't populated when accept_intercept_htlcs
is false.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gonna save that for follow-up for the sake of the size of this PR, but you can manually check that the test fails when the config change lines are commented out. FWIW, that would set a new standard of testing rigor for us since i.e. the zero-conf PR didn't/doesn't test its config.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok fair enough :)
lightning/src/ln/channelmanager.rs
Outdated
_ => unreachable!(), | ||
}; | ||
timed_out_htlcs.push((prev_hop_data, htlc.forward_info.payment_hash, | ||
HTLCFailReason::Reason { failure_code: 0x1000 | 14, data: Vec::new() }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
14 is definitely right here, but its an UPDATE error, so we need to include a channel_update for the outbound channel, which is obviously....unclear? I guess we could deliberately include the wrong channel's update here and use the previous-hop channel, but that's pretty nonsensical. Maybe we just temporary_node_failure
?
225a22b
to
a5c5d53
Compare
LGTM, I think. |
Looks good to me as well! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, just a tiny nit and question :)
payment_hash: PaymentHash, | ||
/// How many msats were received on the inbound edge of this HTLC. | ||
inbound_amount_msat: u64, | ||
/// How many msats the payer intended to route to the next node. Depending on the reason you are |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new rust style team pls save us
}); | ||
|
||
failed_intercept_forwards.push((htlc_source, forward_info.payment_hash, | ||
HTLCFailReason::Reason { failure_code: 0x4000 | 10, data: Vec::new() }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not for this PR, but I've wondered whether we should collect failure code consts somewhere so it's easy to see what it is. I can create an issue if we'd want that, unless I missed some prior reasoning not to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think that would be nice to have!
/// [`HTLCIntercepted`]: events::Event::HTLCIntercepted | ||
// TODO: when we move to deciding the best outbound channel at forward time, only take | ||
// `next_node_id` and not `next_hop_channel_id` | ||
pub fn forward_intercepted_htlc(&self, intercept_id: InterceptId, next_hop_channel_id: &[u8; 32], _next_node_id: PublicKey, amt_to_forward_msat: u64) -> Result<(), APIError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just a question to check my understanding:
Is this basically the procedure for an LDK user?
- Calls
ChannelManager::get_intercept_scid
- Generates invoice with intercept scid in route hints for end-user
- Receives an
HTLCIntercepted
event when the HTLC is intercepted - At this point the LDK user can create a channel
- Then call this method to forward over that JIT channel
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that's my understanding, where the LDK user is the LSP in your example (nodes[1]
in the test), and the end-user is nodes[2]
in the test. I guess step (2) in your example doesn't necessarily need to be on the LDK user's side, as long as the Intercept scid is passed to the end-user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I should have read the test.
Yeah I think I phrased that weirdly for (2). End user generates invoice but just shoves the scid they got from the LSP in the route hints.
No htlcs are intercepted yet, that will be added in upcoming commit(s) Co-authored-by: John Cantrell <[email protected]> Co-authored-by: Valentine Wallace <[email protected]>
This is useful for LSPs who wish to create a just-in-time channel for end users receiving a lightning payment. These fake scids will be encoded into route hints in end user invoices, and signal to LDK to create an event triggering the JIT channel, after which the payment will be received. Co-authored-by: John Cantrell <[email protected]> Co-authored-by: Valentine Wallace <[email protected]>
Used in upcoming commit(s) so users can intercept forwarded HTLCs Co-authored-by: John Cantrell <[email protected]> Co-authored-by: Valentine Wallace <[email protected]>
And store the pending intercepted HTLC in pending_intercepted_htlcs Co-authored-by: John Cantrell <[email protected]> Co-authored-by: Valentine Wallace <[email protected]>
See ChannelManager::forward_intercepted_htlc and ChannelManager::get_intercept_scid for details Co-authored-by: John Cantrell <[email protected]> Co-authored-by: Valentine Wallace <[email protected]>
Co-authored-by: John Cantrell <[email protected]> Co-authored-by: Valentine Wallace <[email protected]>
a5c5d53
to
acff8f6
Compare
Rebased due to conflicts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK acff8f6
Reviewed code changes since last diff, and also checked test coverage. Remaining comments are minor.
let next_hop_scid = match self.channel_state.lock().unwrap().by_id.get(next_hop_channel_id) { | ||
Some(chan) => { | ||
if !chan.is_usable() { | ||
return Err(APIError::APIMisuseError { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have APIError::ChannelUnavailable
in fact, could be used.
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier); | ||
|
||
let payment = self.pending_intercepted_htlcs.lock().unwrap().remove(&intercept_id) | ||
.ok_or_else(|| APIError::APIMisuseError { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mutating this line does not break intercepted_payment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, gonna land to get the conflicts out of the way but please address the two issues @ariard noted and the below one in a followup.
}; | ||
connect_block(&nodes[0], &block); | ||
connect_block(&nodes[1], &block); | ||
let block_count = 183; // find_route adds a random CLTV offset, so hardcode rather than summing consts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It adds a random value in a range, though, so we should still be able to sum consts. Please change it to summing consts cause otherwise our entire test suite fails any time we change any constant :(
Note that if you use the get_route
method it won't add the random value, as well.
For context, LSPs need to be able to open 0-conf JIT channels to users upon the first time the user is receiving a payment. To do this, they will put fake route hints in end user invoices that signal to LDK that this is an intercept forward, similar to phantom payments. LDK will then generate an event, giving the LSP the opportunity to open the JIT channel. The LSP can then forward the payment over the newly opened channel, or fail it.
Based on #1840Supercedes #1601