Skip to content

Conversation

valentinewallace
Copy link
Contributor

@valentinewallace valentinewallace commented Sep 23, 2025

Previously, when a static invoice server forwarded an invoice request to an
often-offline recipient, they would treat the outbound message like any other
outbound onion message initiated by their own node. That means they would
buffer the onion message internally in the onion messenger and generate a
ConnectionNeeded event if the next-hop node was offline.

Buffering the onion message in this case poses a DoS risk for the invoice
server node, since they do not control the quantity of invoice requests they
receive on behalf of often-offline recipients. Instead, we should treat these
forwarded invoice requests like any other onion message that needs to be
forwarded -- if the next-hop node is offline, either drop the message or
generate an OnionMessageIntercepted event for it (pushing the DoS management
onto the handler of the interception event).

Closes #4110

Also closes #4112:

Imagine an LSP with a mobile client. The LSP wants to pay an offer that client
issued. It generates an onion messages invreq and sends it. The OnionMessenger
notices it's not connected and searches the network graph. It doesn't find the
mobile client so doesn't create a direct connect event. The message gets
dropped and the payment fails.

Here we start generating the DC event irrespective of the network graph so the
LSP can use LSPS5 to wake the client.

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Sep 23, 2025

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@valentinewallace valentinewallace added this to the 0.2 milestone Sep 23, 2025
NextMessageHop::ShortChannelId(scid) => match self.node_id_lookup.next_node_id(scid) {
Some(pubkey) => pubkey,
None => {
log_trace!(self.logger, "Dropping forwarded onion messager: unable to resolve next hop using SCID {} {}", scid, log_suffix);
Copy link
Contributor

Choose a reason for hiding this comment

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

There's a typo in the log message: messager should be message to maintain consistency with other error messages in this file.

Suggested change
log_trace!(self.logger, "Dropping forwarded onion messager: unable to resolve next hop using SCID {} {}", scid, log_suffix);
log_trace!(self.logger, "Dropping forwarded onion message: unable to resolve next hop using SCID {} {}", scid, log_suffix);

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Copy link

codecov bot commented Sep 23, 2025

Codecov Report

❌ Patch coverage is 86.76471% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.53%. Comparing base (f707245) to head (1bd3f36).
⚠️ Report is 47 commits behind head on main.

Files with missing lines Patch % Lines
lightning/src/onion_message/messenger.rs 85.04% 14 Missing and 2 partials ⚠️
lightning/src/ln/async_payments_tests.rs 85.71% 0 Missing and 1 partial ⚠️
lightning/src/onion_message/functional_tests.rs 94.73% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4114      +/-   ##
==========================================
- Coverage   88.72%   88.53%   -0.19%     
==========================================
  Files         177      179       +2     
  Lines      133286   134333    +1047     
  Branches   133286   134333    +1047     
==========================================
+ Hits       118253   118938     +685     
- Misses      12326    12638     +312     
- Partials     2707     2757      +50     
Flag Coverage Δ
fuzzing 21.79% <38.53%> (+0.05%) ⬆️
tests 88.37% <86.46%> (-0.19%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ 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
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.

A few small comments, but mostly looks good.

/// recipient is online to provide a new invoice. This path should be persisted and
/// later provided to [`ChannelManager::respond_to_static_invoice_request`].
///
/// This path's [`BlindedMessagePath::introduction_node`] MUST be set to our node or one of our
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't this be documented on the client end of the protocol not the server end?

Copy link
Contributor Author

@valentinewallace valentinewallace Sep 24, 2025

Choose a reason for hiding this comment

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

I generally agree, though the only other possible place I see is on the ServeStaticInvoice onion message field where no one will see it. Maybe that's fine? There's nothing clients can do in practice to satisfy this requirement right now anyway since LDK generates the path without their intervention, so I guess I'll move it.

Edit: basically duplicated the docs in both places so they are more likely to be surfaced

@ldk-reviews-bot
Copy link

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

In an upcoming commit, we will need to repurpose this logic when, as a static
invoice server node, we update our support for forwarding invoice requests from
payers to often-offline recipients. We currently treat these forwarded invreqs
as outbound onion messages that are initiated by our node, when they should
really be treated as forwarded onion messages.
Makes the next commit cleaner when we add support for forwarding onion messages
within send_onion_message_internal. Also rename enqueue_onion_message to
specify that it is used for outbound onion messages.
Previously, when a static invoice server forwarded an invoice request to an
often-offline recipient, they would treat the outbound message like any other
outbound onion message initiated by their own node. That means they would
buffer the onion message internally in the onion messenger and generate a
ConnectionNeeded event if the next-hop node was offline.

Buffering the onion message in this case poses a DoS risk for the invoice
server node, since they do not control the quantity of invoice requests they
receive on behalf of often-offline recipients. Instead, we should treat these
forwarded invoice requests like any other onion message that needs to be
forwarded -- if the next-hop node is offline, either drop the message or
generate an OnionMessageIntercepted event for it (pushing the DoS management
onto the handler of the interception event).
@valentinewallace valentinewallace force-pushed the 2025-09-fwd-invreq-in-mailbox branch from 9d3a2bc to 2460c46 Compare September 24, 2025 16:44
@valentinewallace valentinewallace force-pushed the 2025-09-fwd-invreq-in-mailbox branch from 8c9ceef to e0a805f Compare September 24, 2025 17:49
Comment on lines 692 to 698
Some((features, addresses)) if features.supports_onion_messages() => {
Ok(OnionMessagePath {
intermediate_nodes: vec![],
destination,
first_node_addresses,
first_node_addresses: addresses.to_vec(),
})
},
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe we should not do this new always-generate-DC-event behavior if the node is announced, actually?

Copy link
Collaborator

Choose a reason for hiding this comment

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

You mean announced && !supports_onion_messages? That seems reasonable. Otherwise we could even drop the supports_onion_messages check now, seems weird to generate the DC event for a peer that doesn't support OMs and just drop the connection addresses lol.

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 didn't follow that -- how can we drop the OM feature check but also avoid generating DC events for non-OM-supporting peers? I pushed what I think is correct.

@valentinewallace valentinewallace force-pushed the 2025-09-fwd-invreq-in-mailbox branch from e0a805f to 9e2b626 Compare September 24, 2025 18:47
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.

Two comments, otherwise LGTM.

@valentinewallace valentinewallace force-pushed the 2025-09-fwd-invreq-in-mailbox branch from 9e2b626 to b03f377 Compare September 24, 2025 20:47
Imagine an LSP with a mobile client. The LSP wants to pay an offer that client
issued. It generates an onion messages invreq and sends it. The OnionMessenger
notices it's not connected and searches the network graph. It doesn't find the
mobile client so doesn't create a direct connect event. The message gets
dropped and the payment fails.

Here we start generating the DC event irrespective of the network graph so the
LSP can use LSPS5 to wake the client.
@valentinewallace valentinewallace force-pushed the 2025-09-fwd-invreq-in-mailbox branch from b03f377 to 1bd3f36 Compare September 24, 2025 20:48
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.

Just gonna land this. Its quite straightforward.

@TheBlueMatt TheBlueMatt merged commit 7926fb9 into lightningdevkit:main Sep 24, 2025
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants