Skip to content

Conversation

gijswijs
Copy link
Collaborator

@gijswijs gijswijs commented May 26, 2025

This first pull request introduces the basic structures for onion messages into LND.

It adds a new message type, OnionMessage, to the lnwire package. This includes the message's definition, comprising a blinding point and an onion blob, along with the necessary serialization and deserialization logic for peer-to-peer communication.

Additionally, it exposes functionality for handling these messages via gRPC by adding two new endpoints:

SendOnionMessage: Allows a client to send an onion message to a specific peer.
SubscribeOnionMessages: Allows a client to subscribe to a stream of incoming onion messages. .

Scope of this PR:

  • Receive onion messages by registering a msgmux endpoint in brontide
  • 'Listen in' on onion messages by dispatching the message to subscribers
  • Sending onion messages to a specified peer (The peer is passed through the endpoint; not derived from the onion message itself)
  • itest testing the above behavior

Usage of msgmux

msgmux provides a generic message routing system to decouple message handling from the main peer.Brontide
read loop. It's not widely used in LND as of yet, so an explanation might be in place.

  1. A peer.Brontide instance is configured with a msgmux.Router.
  2. Sub-systems that need to handle specific wire messages, like the onion_message.OnionEndpoint, implement the msgmux.Endpoint interface and are registered with the router.
  3. In brontide's readHandler, when a new message is received from a peer, it is first passed to the msgRouter.RouteMsg() method.
  4. The router checks all its registered endpoints. If an endpoint reports that it CanHandle() the message, the router forwards the message to that endpoint for processing.
  5. If any endpoint successfully handles the message, RouteMsg returns nil. The brontide.readHandler then skips its large, legacy switch statement and proceeds to read the next message.
  6. If no endpoint can handle the message, RouteMsg returns ErrUnableToRouteMsg, and brontide falls back to its switch statement to process the message in the traditional way.

This allows new message handling logic to be added as self-contained endpoints without modifying the peer's core read loop.

Out-of-scope (for this PR)

Copy link
Contributor

coderabbitai bot commented May 26, 2025

Important

Review skipped

Auto reviews are limited to specific labels.

🏷️ Labels to auto review (1)
  • llm-review

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@gijswijs gijswijs requested review from ziggie1984 and ellemouton and removed request for ziggie1984 May 26, 2025 14:18
Copy link
Collaborator

@ziggie1984 ziggie1984 left a comment

Choose a reason for hiding this comment

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

Nice start of the onion message saga 🎉, I think the msgmux is the way to go here. I am not sure if we should use one server for all peers but if we use it we should buffer the update channel of the onionserver.

Let's now decrypt the onion message in the next PR ?

@gijswijs gijswijs force-pushed the onion-messaging branch 2 times, most recently from 5762808 to 9f23971 Compare June 10, 2025 15:49
@saubyk saubyk added this to the v0.20.0 milestone Jun 11, 2025
@saubyk saubyk added this to lnd v0.20 Jun 11, 2025
@saubyk saubyk moved this to In progress in lnd v0.20 Jun 11, 2025
Copy link
Collaborator

@ellemouton ellemouton left a comment

Choose a reason for hiding this comment

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

removing request for review in the mean time :) feel free to re-request when ready!

Copy link
Contributor

@mohamedawnallah mohamedawnallah left a comment

Choose a reason for hiding this comment

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

Thanks, @gijswijs, for putting this together! I've added some comments. Overall, I like the design and how it's seamlessly integrated with the LND system/subsystems. I can form a clear mental picture of the changes. Nice work!

@gijswijs gijswijs force-pushed the onion-messaging branch 2 times, most recently from 9549248 to d872936 Compare September 1, 2025 11:37
@gijswijs gijswijs changed the title PoC Onion messaging using msgmux Onion messaging using msgmux Sep 9, 2025
@gijswijs gijswijs marked this pull request as ready for review September 9, 2025 15:27
@gijswijs gijswijs changed the title Onion messaging using msgmux Basic structures for onion messages into LND Sep 15, 2025
@gijswijs gijswijs modified the milestones: v0.20.0, v0.21.0 Sep 17, 2025
Copy link
Collaborator

@ellemouton ellemouton left a comment

Choose a reason for hiding this comment

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

looking good! nice and clean. Couple of comments

type OnionMessage struct {
// BlindingPoint is the route blinding ephemeral pubkey to be used for
// the onion message.
BlindingPoint *btcec.PublicKey
Copy link
Collaborator

Choose a reason for hiding this comment

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

should we rename it to PathKey to be consistent with the latest version of the spec? It will also help clarity once the actual blob contains blinded path info

Copy link
Collaborator

Choose a reason for hiding this comment

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

perhaps we should also explain more re what it is used for. an intermediate node expects an encrypted_recipient_data which it can decrypt into an encrypted_data_tlv using the path_key which it is handed along with the onion message.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: +1 for renaming to path_key, maybe also do this for our whole codebase where we currently use blindingPoint quite often ?

server.go Outdated
// SendOnionMessage sends a custom message to the peer with the specified
// pubkey.
// TODO(gijs): change this message to include path finding.
func (s *server) SendOnionMessage(peerPub [33]byte,
Copy link
Collaborator

Choose a reason for hiding this comment

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

wonder if we should not have a dedicated OnionMessageServer? instead of adding this logic directly to the main server struct.

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 having a separate sub-rpc-server for onion-messages ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So I mimicked the implementation of the customMessageServer and that's also why this logic is in the main server struct.
In phase 2 when we also will implement path finding for onion messages it definitely makes sense to rethink this, but for now I would keep it like this.

@saubyk saubyk removed this from lnd v0.20 Sep 24, 2025
@saubyk saubyk added this to v0.21 Sep 24, 2025
@saubyk saubyk moved this to In review in v0.21 Sep 24, 2025
Copy link
Collaborator

@ziggie1984 ziggie1984 left a comment

Choose a reason for hiding this comment

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

I think we are close here, please also add release notes similar 🙏

type OnionMessage struct {
// BlindingPoint is the route blinding ephemeral pubkey to be used for
// the onion message.
BlindingPoint *btcec.PublicKey
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: +1 for renaming to path_key, maybe also do this for our whole codebase where we currently use blindingPoint quite often ?

)

// Subsystem defines the logging code for this subsystem.
const Subsystem = "OMSG"
Copy link
Collaborator

Choose a reason for hiding this comment

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

What does the G stand for here ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

onion -> o
message -> msg
onion + message -> omsg

require.NoError(ht.T, err)
randomPub := randomPriv.PubKey()
msgBlindingPoint := randomPub.SerializeCompressed()
msgOnion := []byte{1, 2, 3}
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 fail because of the following (I know it is not a spec. MUST but why don't we follow it?):

SHOULD set onion_message_packet len to 1366 or 32834.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, we will follow it.
There's an ongoing debate on how to handle onion_message_packet sizes in the lightning-onion package, but whatever solution we end up with, in one of the PRs in this saga the size will be clamped to those two values.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Micro-nit: add a comment that this is only for testing purposes and the onionMsg is supposed to be either 1300 or 32... big

server.go Outdated
// SendOnionMessage sends a custom message to the peer with the specified
// pubkey.
// TODO(gijs): change this message to include path finding.
func (s *server) SendOnionMessage(peerPub [33]byte,
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 having a separate sub-rpc-server for onion-messages ?

@lightninglabs-deploy
Copy link

@gijswijs, remember to re-request review from reviewers when ready

// manner as onions used to route HTLCs, with the exception that it uses
// blinded routes by default.
OnionBlob []byte
}
Copy link
Member

Choose a reason for hiding this comment

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

No need for an ExtraData field here?

Copy link
Collaborator

Choose a reason for hiding this comment

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

not sure if we need it on this level tho, why would I use a blinded route and add more information basically unblinded, I think the bulk of extra data will be in onionmsg_tlv where we defintily need the ExtraData, but I mean there is no cost of provisioning it here as well.

/* lncli: `subscribeonion`
SubscribeOnionMessages subscribes to a stream of incoming onion messages.
*/
rpc SubscribeOnionMessages (SubscribeOnionMessagesRequest)
Copy link
Member

Choose a reason for hiding this comment

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

Is this intended to be just for forwarded onion messages, or the ones received?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

For the ones received.


// OnionMessageServer is an instance of a message server that dispatches
// onion messages to subscribers.
OnionMessageServer *subscribe.Server
Copy link
Member

Choose a reason for hiding this comment

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

Just to verify my understanding: this is just hooked up into direct point-to-point messaging for now, to make a simple demo via the RPC server?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, and to be able to see what messages are received in itests.

return peer.SendMessageLazy(true, msg)
}

// SendOnionMessage sends a custom message to the peer with the specified
Copy link
Member

Choose a reason for hiding this comment

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

IIUC, you'll do this in a follow up PR, but I think the actor package can fit rather nicely here.

We have two actor types: the processor, and the endpoint.

The processor handles the crypto logic to figure out who to send the message to next.

A message endpoint becomes an actor, identifiable by the public key of the node.

When we read an onion message off the wire, we send it to the processor actor, who the sends it to the relevant processor actor.

I have a PR that abstracts out the mailbox for actors, so it can be anything that implements the queue abstraction.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I know that PR. And I thought of using it. But I also wanted to keep the number of moving parts as low as possible. This PR saga will pull in the (yet unmerged) BackPressureQueue in and I thought it was wise not to pull in another unmerged PR.

@gijswijs
Copy link
Collaborator Author

gijswijs commented Oct 7, 2025

Ok, this PR is ready for the next round of reviews.
This is what I did so far:

  1. Naming Improvements:
    • The package onion_message was renamed to onionmessage.
    • In the wire and RPC message definitions (lnwire and lnrpc), a key field was renamed from BlindingPoint to PathKey to align with spec. Comments and related documentation were also updated to reflect this change.
  2. Context Propagation:
    • The SendOnionMessage RPC handler and its underlying methods now accept a context.Context. This allows the operation to be cancelled, for instance if the request times out or the client disconnects. For good measure I made a similar change to the SendCustomMessage RPC handler.
  3. Logging:
    • Logging was improved to use a structured format (slog).

The following will be addressed in later PRs:

  1. Handling of ExtraData specifically for onion messages (see: Basic structures for onion messages into LND #9868 (comment))

Open questions:

  1. Should we implement the actor package? (see: Basic structures for onion messages into LND #9868 (comment))
  2. Should we rename BlindingPoint to PathKey in all other places as well? This is a substantial renaming and should be done in a separate PR.

@gijswijs gijswijs force-pushed the onion-messaging branch 2 times, most recently from fa0822b to 3a18daf Compare October 7, 2025 14:03
@ziggie1984 ziggie1984 changed the base branch from master to 0-21-0-staging October 7, 2025 19:59
@ziggie1984
Copy link
Collaborator

can you rebase on 21-staging

LogBytesPreview returns a slog attribute that shows a hex preview of the
given byte slice. The result is truncated for readability. The preview
length is fixed to avoid leaking full binary payloads in logs while
still allowing identification of the data.
This message type is a message that carries an onion-encrypted payload
used for BOLT12 messages.
This commit creates the necessary endpoints for onion messages.
Specifically, it adds the following:

- `SendOnionMessage` endpoint to send onion messages.
- `SubscribeOnionMessages` endpoint to subscribe to incoming onion
  messages.

It uses the `msgmux` package to handle the onion messages.
The only way to unblock SendCustomMessage is if the peer activates,
disconnects or the server shuts down. This means that if the context is
cancelled, we will still wait until one of those other events happen.

With this commit we thread the context through to SendCustomMessage, so
that if the context is cancelled, we can return early. This improves the
cancellation semantics.
@gijswijs
Copy link
Collaborator Author

gijswijs commented Oct 8, 2025

can you rebase on 21-staging

✔️

Copy link
Collaborator

@ziggie1984 ziggie1984 left a comment

Choose a reason for hiding this comment

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

LGTM

// manner as onions used to route HTLCs, with the exception that it uses
// blinded routes by default.
OnionBlob []byte
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

not sure if we need it on this level tho, why would I use a blinded route and add more information basically unblinded, I think the bulk of extra data will be in onionmsg_tlv where we defintily need the ExtraData, but I mean there is no cost of provisioning it here as well.

require.NoError(ht.T, err)
randomPub := randomPriv.PubKey()
msgBlindingPoint := randomPub.SerializeCompressed()
msgOnion := []byte{1, 2, 3}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Micro-nit: add a comment that this is only for testing purposes and the onionMsg is supposed to be either 1300 or 32... big

}

peer := msg.PeerPub.SerializeCompressed()
log.DebugS(ctx, "OnionEndpoint received OnionMessage",
Copy link
Collaborator

Choose a reason for hiding this comment

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

maybe it makes sense to add the peer to the ctx so that it is printed below as well?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: In review
Development

Successfully merging this pull request may close these issues.

7 participants