Skip to content
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

Parse inbound Trampoline onions #3620

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

arik-so
Copy link
Contributor

@arik-so arik-so commented Feb 25, 2025

Initially seeking concept ACK due to the refactor of passing Hop instead of InboundOnionPayload to create_receive_ and create_fwd_pending_htlc_info, which we need in order to have access to both the outer and inner onions for Trampoline handling.

@TheBlueMatt TheBlueMatt marked this pull request as draft February 25, 2025 18:37
@valentinewallace
Copy link
Contributor

Don't see any issues right now with that. I'm wondering where the pathfinding to the next trampoline hop part is going to go?

@arik-so arik-so force-pushed the arik/trampoline/inbound-decoding branch 3 times, most recently from 4d145a3 to d22e0fe Compare February 27, 2025 16:59
@arik-so
Copy link
Contributor Author

arik-so commented Feb 27, 2025

Rebased this onto #3626 so CI should no longer be failing.

@arik-so arik-so added the weekly goal Someone wants to land this this week label Feb 27, 2025
@arik-so arik-so force-pushed the arik/trampoline/inbound-decoding branch 5 times, most recently from f64afa4 to 3013c22 Compare February 28, 2025 17:56
@arik-so arik-so marked this pull request as ready for review February 28, 2025 17:58
@arik-so arik-so force-pushed the arik/trampoline/inbound-decoding branch 3 times, most recently from 392bc6d to 913970f Compare February 28, 2025 19:35
Copy link

codecov bot commented Feb 28, 2025

Codecov Report

Attention: Patch coverage is 76.55502% with 49 lines in your changes missing coverage. Please review.

Project coverage is 90.13%. Comparing base (d136406) to head (913970f).
Report is 31 commits behind head on main.

Files with missing lines Patch % Lines
lightning/src/ln/msgs.rs 10.63% 40 Missing and 2 partials ⚠️
lightning/src/ln/onion_payment.rs 96.34% 1 Missing and 2 partials ⚠️
lightning/src/ln/channelmanager.rs 97.26% 2 Missing ⚠️
lightning/src/ln/onion_utils.rs 71.42% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3620      +/-   ##
==========================================
+ Coverage   88.59%   90.13%   +1.54%     
==========================================
  Files         151      152       +1     
  Lines      118454   126851    +8397     
  Branches   118454   126851    +8397     
==========================================
+ Hits       104941   114335    +9394     
+ Misses      10994    10060     -934     
+ Partials     2519     2456      -63     

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

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.

Mostly looks good, a few nits and one or two real comments.

(16, payment_metadata, option),
(18, total_msat, (option, encoding: (u64, HighZeroBytesDroppedBigSize))),
(20, trampoline_onion_packet, option),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Here, and the 14 above, should we use placeholder values (ie high numbers) for now until the BOLT PR gets merged?

Copy link
Contributor Author

@arik-so arik-so Feb 28, 2025

Choose a reason for hiding this comment

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

we can, and I think we briefly used to in 3386, though Eclair's already been using these type IDs.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, okay, probably fine, then?

@@ -3000,6 +3092,15 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
amt_to_forward: amt.ok_or(DecodeError::InvalidValue)?,
outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?,
}))
} else if let Some(outgoing_node_id) = outgoing_node_id {
if payment_data.is_some() || payment_metadata.is_some() || encrypted_tlvs_opt.is_some() ||
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should update the other branches to check that outgoing_node_id is none (man there's gotta be an easier way to write this pattern....

Copy link
Contributor Author

Choose a reason for hiding this comment

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

great point!

@@ -3036,16 +3048,6 @@ impl<NS: Deref> ReadableArgs<(Option<PublicKey>, NS)> for InboundOnionPayload wh
amt_to_forward: amt.ok_or(DecodeError::InvalidValue)?,
outgoing_cltv_value: cltv_value.ok_or(DecodeError::InvalidValue)?,
}))
} else if let Some(trampoline_onion_packet) = trampoline_onion_packet {
Copy link
Collaborator

Choose a reason for hiding this comment

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

lets not move code we just introduced in the previous commit? Maybe just squash these two...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah I moved the second commit here for easy either squashing or dismissal

@@ -1811,6 +1811,23 @@ mod fuzzy_internal_msgs {
pub outgoing_cltv_value: u32,
}

#[allow(unused)]
pub struct InboundTrampolineForwardPayload {
pub outgoing_node_id: NodeId,
Copy link
Collaborator

Choose a reason for hiding this comment

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

"outgoing node id" implies to me that this is the next hop from us, rather than "next hop node id" which I think is what was meant here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

would next_node_id be better?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yea, or maybe next_trampoline?


#[allow(unused)]
pub struct InboundTrampolineBlindedForwardPayload {
pub outgoing_node_id: NodeId,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here.

@@ -1842,9 +1870,17 @@ mod fuzzy_internal_msgs {

pub enum InboundOnionPayload {
Forward(InboundOnionForwardPayload),
#[allow(unused)]
Copy link
Collaborator

Choose a reason for hiding this comment

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

for this kind of thing I'd much rather #[cfg(trampoline)] than #[allow(unused)]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that would imply cfg-based behavioral changes inside InboundOnionPayload's read implementation, but happy to change if that's ok

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think thats fine?

@@ -344,6 +363,9 @@ impl UnauthenticatedReceiveTlvs {
pub(crate) enum BlindedPaymentTlvs {
/// This blinded payment data is for a forwarding node.
Forward(ForwardTlvs),
/// This blinded payment data is for a forwarding Trampoline node.
#[allow(unused)]
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is used tho?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

used, but never constructed

Receive(InboundOnionReceivePayload),
BlindedForward(InboundOnionBlindedForwardPayload),
BlindedReceive(InboundOnionBlindedReceivePayload),

// These payloads should be seen inside an inner Trampoline onion
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should these go through a different enum/decode method since they shouldn't appear in the same context?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

certainly an option. I think we already discussed this, it would just be a lot of code duplication

Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh? There's not that much code for InboundOnionPayload to begin with? What would get duplicated aside from the one decode_tlv_stream call?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just pushed a version with the duplicated read implementation. The receive variants, being identical to the ones we'd expect in the outer onion, naturally also increase the duplication

@arik-so arik-so force-pushed the arik/trampoline/inbound-decoding branch 2 times, most recently from cd67a23 to 7631640 Compare March 2, 2025 18:28
@valentinewallace valentinewallace self-requested a review March 3, 2025 15:40
Copy link
Contributor

@valentinewallace valentinewallace left a comment

Choose a reason for hiding this comment

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

Generally looks good, just have some nits here!

pub struct InboundTrampolineEntrypointPayload {
pub amt_to_forward: u64,
pub outgoing_cltv_value: u32,
pub multipath_trampoline_data: Option<FinalOnionHopData>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you add a comment for why this is optional?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@@ -167,6 +273,8 @@ pub(super) fn create_recv_pending_htlc_info(
sender_intended_htlc_amt_msat, cltv_expiry_height, None, Some(payment_context),
intro_node_blinding_point.is_none(), true, invoice_request)
}
#[cfg(trampoline)]
onion_utils::Hop::TrampolineReceive { .. } | onion_utils::Hop::TrampolineBlindedReceive { .. } => todo!(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the todo left intentionally?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yup, will handle receiving and add error decoding tests in next PR

);
match decoded_trampoline_hop {
Ok((
next_trampoline_hop_data,
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: the indentation is a bit intense below, do you think we could avoid the nested matches?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm looking into this

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you match on

Ok((InboundTrampolinePayload::Forward, Some(..))) => { .. },
Ok((InboundTrampolinePayload::BlindedForward, Some(..))) => { .. },
Ok((InboundTrampolinePayload::Receive, None)) => { .. },
... etc

and just have a catch-all for forward-encoded-as-receive error and vice versa?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah, sure. I thought you wanted some more depth removed, but this works nicely, thanks

@arik-so arik-so force-pushed the arik/trampoline/inbound-decoding branch from 7631640 to 226a6e3 Compare March 4, 2025 02:01
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.

This LGTM, feel free to squash the fixup and @valentinewallace can let us know if we can land it.

@@ -1860,6 +1905,18 @@ mod fuzzy_internal_msgs {
multipath_trampoline_data: Option<FinalOnionHopData>,
trampoline_packet: TrampolineOnionPacket,
},
/// This is used for Trampoline hops that are not the blinded path intro hop.
/// We would only ever construct this variant when we are a Trampoline node forwarding a
Copy link
Collaborator

Choose a reason for hiding this comment

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

Wait, I'm confused here. So we construct this when we're forwarding a trampoline and the next trampoline hop we want to use is not the blinded path entry point?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

exactly right, we do this when we're already inside the blinded path, either when we're the intro node, or the intro node was already behind us. We do so because we cannot necessarily provide the blinding point in the update_add message given that the next Trampoline hop is not necessarily our next peer.

},
#[cfg(trampoline)]
Trampoline {
next_trampoline: NodeId,
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: might be nice to use a PublicKey here rather than a NodeId since we're happy to fail-back if the pk isn't valid.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

would you like the msgs.rs parsing to also read a PublicKey, but store it as a NodeId in the inbound payload structs, or shall I just change all of them to PublicKey instead?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I assume PublicKey? The only place we don't use a PublicKey is in update_add_htlc parsing cause then we'd disconnect our immediate peer just because the sender provided an invalid pk. Anywhere else should be a PublicKey cause we generally actually need to use it for crypto things, and we need a PublicKey for that anyway (except network graph, where full keys are too slow).

@arik-so arik-so force-pushed the arik/trampoline/inbound-decoding branch from 226a6e3 to 6852765 Compare March 5, 2025 02:55
@TheBlueMatt
Copy link
Collaborator

Oops needs rebase :/

@arik-so arik-so force-pushed the arik/trampoline/inbound-decoding branch 2 times, most recently from ba373d2 to 5850d83 Compare March 5, 2025 18:33
Copy link
Contributor

@valentinewallace valentinewallace left a comment

Choose a reason for hiding this comment

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

LGTM!

@arik-so arik-so force-pushed the arik/trampoline/inbound-decoding branch from 5850d83 to 05b142b Compare March 5, 2025 19:35
@TheBlueMatt
Copy link
Collaborator

Grr looks like this needs rebased again.

arik-so added 8 commits March 5, 2025 16:55
Given that blinded Trampoline hops cannot use blinded intermediate hops
between each other, the `current_path_key` used for decrypting the
blinded Trampoline onion cannot be included in the update_add_htlc
message due to the potential presence of intermediate hops.

That means that when we're not the payment initiator, but a Trampoline
hop forwarding a payment along a blinded path, we must include the
blinding point inside the outer onion, which this commit will allow us
to do.
For pending HTLC info processing, we will later need to access both the
outer and the Trampoline payloads. Thanks to the refactor eliminating
invalid Hop states, this is now possible by accessing the Hop struct,
which will carry both outer and Trampoline payload data when applicable.
Check inbound onion payloads for the presence of an inner onion, which
we will later also need to decrypt to handle Trampoline payments.
We will be using the same logic for decoding onion payloads for outer
and for Trampoline onions. To accommodate the Trampoline payloads, we
parse a next node ID rather than an SCID.
When encrypting errors, we currently have the ability to use two shared
secrets for Phantom Node payments.

Trampoline also requires the re-encryption of encrypted errors, first
using the Trampoline shared secret, and then using the outer one.

Given these two cryptographically equivalent use cases, we're renaming
the phantom_shared_secret parameter to secondary_shared_secret, and
explaining the now two contexts in which it will be applicable.
When parsing inbound HTLCs, we need to provide supplemental information
about potential outgoing hops, which hitherto always required the next
SCID. However, Trampoline uproots the approach because the sender did
not specify the SCID; rather, they rely on us to do the intermediate
routing. Thus we now need to distinguish between SCID-based and node-
ID-based outgoing routing.
In this commit, we start decrypting the inner onion we receive, and lay
some groundwork towards handling the subsequent routing or payment
receipt.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
weekly goal Someone wants to land this this week
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants