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

[3/4] Route Blinding: send MPP over multiple blinded paths #8764

Merged
merged 9 commits into from
Aug 1, 2024

Conversation

ellemouton
Copy link
Collaborator

@ellemouton ellemouton commented May 16, 2024

In this PR, we remove the restriction of only being able to send to a single blinded payment path.
Nodes will now be able to take advantage of the multiple blinded paths provided for a given
payment to perform MPP if needed.

Depends on: #8735
Tracking Issue: #6690


This change is Reviewable

Copy link
Contributor

coderabbitai bot commented May 16, 2024

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.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

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>.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @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.

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 as 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 resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

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.

@ellemouton ellemouton changed the title [3/3] Route Blinding: send MPP over multiple blinded paths [3/4] Route Blinding: send MPP over multiple blinded paths May 16, 2024
@ellemouton ellemouton self-assigned this May 16, 2024
@saubyk saubyk added this to the v0.18.1 milestone May 16, 2024
@ziggie1984 ziggie1984 self-requested a review June 21, 2024 16:25
Copy link
Collaborator

@bitromortac bitromortac left a comment

Choose a reason for hiding this comment

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

Made initial contact with this PR, cool work ⚡! Will re-review the whole series.

itest/lnd_route_blinding_test.go Outdated Show resolved Hide resolved
@saubyk saubyk added the P2 should be fixed if one has time label Jun 25, 2024
@ellemouton ellemouton force-pushed the rb-send-via-multi-path branch 2 times, most recently from 0c3de82 to 50b4295 Compare July 10, 2024 10:23
@saubyk saubyk mentioned this pull request Jul 14, 2024
Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

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

Completed an initial pass, need to fill the following gaps in my mental model:

  • What makes this case different than the single path MPP case?
  • Why do the pubkey need to be swapped out?
  • What are the new semantics and constraints re the final CLTV as relates to the path?

routing/blinding.go Show resolved Hide resolved
routing/blinding.go Show resolved Hide resolved
Copy link
Collaborator Author

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

Thanks for taking a look @Roasbeef 🦖

Responding to your Qs:

What makes this case different than the single path MPP case?

In the single MPP case, a single blinded path can be used multiple times in the construction of the various paths used for the parts. So importantly: the end of the path will always be the same (same blinded path). Meaning that if something fails within that part of the route, nothing can be done by the sender & retries are not helpful.

With this PR, the sender finally can take advantage of all the different blinded paths provided by the recipient in the invoice. So now, the MPP parts can have different endings. If an error occurs in one blinded path, the sender can try another. Or 2 MPP parts can be sent along two different blinded paths.

Why do the pubkeys need to be swapped out?

In our pathfinding logic today, things work cause we have a single target pubkey. Even if we only have hop hints to rely on, all paths lead to Rome (Rome being this single pub key). With blinded paths, the last hop of each hop will have a different pub key. In order to take advantage of the existing path finding logic as it stands today, we swap out these various different destination pub keys for a single one so that the blinded path edges act no differently to hop hints & once again we can rely on all paths leading to Rome.

It's only at onion construction time (once a path has been chosen) where we actually care about the true value of the destination pub key.

What are the new semantics and constraints re the final CLTV as relates to the path?

The important thing to know with route blinding is that the min_final_cltv_expiry_delta is no longer communicated explicitly to the sender via the invoice. Instead, it is now included in the over-all CLTV delta of the path. What this then looks like in the code is that for the additional edges we overlay onto the graph, we make is such that the CLTV delta for the intro node is this accumulated CLTV delta and then each hop after that has a CLTV delta of 0. So we just say that the final CLTV delta is also 0.

However there is one edge case here: it is for the case where the path has only an introduction node (and no hops). In this case: the intro node is also the intro node and ie, the CLTV for the path is the same as the final CLTV. BUT we cannot rely on this being correctly overlayed via additional edges here cause there are no additional edges if the intro node is the only node. So we have to make sure that the finalCLTV in this unique case is set to the path's CLTV and not left as zero.

Finally, there is one more assumption we make here: if a recipient provides us with a set of paths of various lengths but one of those paths is an intro-node-only path, then we just go ahead and discard the rest of the paths since it doesnt make sense to try those since we know that the intro node is the dest node.

routing/blinding.go Show resolved Hide resolved
routing/blinding.go Show resolved Hide resolved
routing/blinding.go Show resolved Hide resolved
@ziggie1984
Copy link
Collaborator

Finally, there is one more assumption we make here: if a recipient provides us with a set of paths of various lengths but one of those paths is an intro-node-only path, then we just go ahead and discard the rest of the paths since it doesnt make sense to try those since we know that the intro node is the dest node.

I wonder if this would be considered a wrong behaviour of the person providing us the invoice, so maybe we should consider this as an invalid invoice to spot wrong implementations ?

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.

Splendid PR 👌

No blocking comment from my side, ship it 📦

routing/blinding.go Show resolved Hide resolved
routing/blinding.go Outdated Show resolved Hide resolved
routing/blinding.go Outdated Show resolved Hide resolved
rpcserver.go Outdated Show resolved Hide resolved
routing/blinding.go Show resolved Hide resolved
routing/blinding.go Outdated Show resolved Hide resolved
routing/pathfind.go Show resolved Hide resolved
routing/blinding.go Outdated Show resolved Hide resolved
routing/blinding.go Show resolved Hide resolved
routing/blinding.go Show resolved Hide resolved
@lightninglabs-deploy
Copy link

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

@Roasbeef
Copy link
Member

Roasbeef commented Jul 30, 2024

I went to create an invoice with a blinded path on testnet, and ran into this panic:

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x143a82b]

goroutine 4084 [running]:
github.com/lightningnetwork/lnd/routing.(*ChannelRouter).FindBlindedPaths(0xc0022b6f00, {0x2, 0x70, 0x68, 0x5c, 0xa8, 0x1a, 0x8e, 0x4d, 0x4d, ...}, ...)
	github.com/lightningnetwork/lnd@v0.18.2-beta/routing/router.go:727 +0x64b
github.com/lightningnetwork/lnd.(*rpcServer).AddInvoice.func3(0x20?)
	github.com/lightningnetwork/lnd@v0.18.2-beta/rpcserver.go:5816 +0x97
github.com/lightningnetwork/lnd/lnrpc/invoicesrpc.buildBlindedPaymentPaths(0xc0070713e0)
	github.com/lightningnetwork/lnd@v0.18.2-beta/lnrpc/invoicesrpc/addinvoice.go:1054 +0x42
github.com/lightningnetwork/lnd/lnrpc/invoicesrpc.AddInvoice({0x36f9090, 0xc003fc4450}, 0xc004d95080, 0xc0070715c8)
	github.com/lightningnetwork/lnd@v0.18.2-beta/lnrpc/invoicesrpc/addinvoice.go:517 +0x108e
github.com/lightningnetwork/lnd.(*rpcServer).AddInvoice(0xc00061a600, {0x36f9090, 0xc003fc4450}, 0xc001487980)
	github.com/lightningnetwork/lnd@v0.18.2-beta/rpcserver.go:5856 +0x6a5
github.com/lightningnetwork/lnd/lnrpc._Lightning_AddInvoice_Handler.func1({0x36f9090?, 0xc003fc4450?}, {0x22965c0?, 0xc001487980?})
	github.com/lightningnetwork/lnd@v0.18.2-beta/lnrpc/lightning_grpc.pb.go:2610 +0xcb
github.com/lightningnetwork/lnd/rpcperms.(*InterceptorChain).CreateServerOpts.(*InterceptorChain).middlewareUnaryServerInterceptor.func7({0x36f9090, 0xc003fc4450}, {0x22965c0, 0xc001487980}, 0xc00391a660, 0xc002205ec0)
	github.com/lightningnetwork/lnd@v0.18.2-beta/rpcperms/interceptor.go:832 +0x111
google.golang.org/grpc.getChainUnaryHandler.func1({0x36f9090, 0xc003fc4450}, {0x22965c0, 0xc001487980})
	google.golang.org/grpc@v1.59.0/server.go:1163 +0xb2
github.com/lightningnetwork/lnd/rpcperms.(*InterceptorChain).CreateServerOpts.(*InterceptorChain).MacaroonUnaryServerInterceptor.func5({0x36f9090, 0xc003fc4450}, {0x22965c0, 0xc001487980}, 0xc00391a660?, 0xc003f8fec0)
	github.com/lightningnetwork/lnd@v0.18.2-beta/rpcperms/interceptor.go:689 +0x7d
google.golang.org/grpc.getChainUnaryHandler.func1({0x36f9090, 0xc003fc4450}, {0x22965c0, 0xc001487980})
	google.golang.org/grpc@v1.59.0/server.go:1163 +0xb2
github.com/lightningnetwork/lnd/rpcperms.(*InterceptorChain).CreateServerOpts.(*InterceptorChain).rpcStateUnaryServerInterceptor.func3({0x36f9090, 0xc003fc4450}, {0x22965c0, 0xc001487980}, 0xc00391a660, 0xc002b80900)
	github.com/lightningnetwork/lnd@v0.18.2-beta/rpcperms/interceptor.go:781 +0xfc
google.golang.org/grpc.getChainUnaryHandler.func1({0x36f9090, 0xc003fc4450}, {0x22965c0, 0xc001487980})
	google.golang.org/grpc@v1.59.0/server.go:1163 +0xb2
github.com/lightningnetwork/lnd/rpcperms.(*InterceptorChain).CreateServerOpts.errorLogUnaryServerInterceptor.func1({0x36f9090?, 0xc003fc4450?}, {0x22965c0?, 0xc001487980?}, 0xc00391a660, 0xc002205ec0?)
	github.com/lightningnetwork/lnd@v0.18.2-beta/rpcperms/interceptor.go:605 +0x52
google.golang.org/grpc.NewServer.chainUnaryServerInterceptors.chainUnaryInterceptors.func1({0x36f9090, 0xc003fc4450}, {0x22965c0, 0xc001487980}, 0xc00391a660, 0x78?)
	google.golang.org/grpc@v1.59.0/server.go:1154 +0x85
github.com/lightningnetwork/lnd/lnrpc._Lightning_AddInvoice_Handler({0x22d29c0, 0xc00061a600}, {0x36f9090, 0xc003fc4450}, 0xc004b3b280, 0xc0007747a0)
	github.com/lightningnetwork/lnd@v0.18.2-beta/lnrpc/lightning_grpc.pb.go:2612 +0x143
google.golang.org/grpc.(*Server).processUnaryRPC(0xc0002661e0, {0x36f9090, 0xc003ed0f30}, {0x3708e60, 0xc004871a00}, 0xc002440240, 0xc000769770, 0x4a551d8, 0x0)
	google.golang.org/grpc@v1.59.0/server.go:1343 +0xdd1
google.golang.org/grpc.(*Server).handleStream(0xc0002661e0, {0x3708e60, 0xc004871a00}, 0xc002440240)
	google.golang.org/grpc@v1.59.0/server.go:1737 +0xc47
google.golang.org/grpc.(*Server).serveStreams.func1.1()
	google.golang.org/grpc@v1.59.0/server.go:986 +0x86
created by google.golang.org/grpc.(*Server).serveStreams.func1 in goroutine 4083
	google.golang.org/grpc@v1.59.0/server.go:997 +0x136

Perhaps it's the case of a missing edge policy: https://github.com/ellemouton/lnd/blob/53dab50d393f3acf80d846e5bdcbdad8ee4b6cb5/routing/router.go#L725-L728

@Roasbeef
Copy link
Member

Can be rebased to point to master now!

@ziggie1984
Copy link
Collaborator

Hmm seems like we need to check that the inpolicy is not nil as we already do in other parts in the routing codebase ?

// If there is no edge policy for this candidate node, skip.
		// Note that we are searching backwards so this node would have
		// come prior to the pivot node in the route.
		if channel.InPolicy == nil {
			return nil
		}

@Roasbeef
Copy link
Member

@ziggie1984 yep was thinking the same thing, I'll add that then try again. FWIW, this is a very very very old node, which makes it great for testing!

This commit introduces a new type, `BlindedPaymentPathSet`. For now, it
holds only a single `BlindedPayment` but eventually it will hold and
manage a set of blinded payments provided for a specific payment. To
make the PR easier to follow though, we start off just letting it hold a
single one and do some basic replacements.
Building on from the previous commit, here we pass the PathSet around
everywhere where we previously passed around the single BlindedPayment.
If multiple blinded paths are provided, they will each have a different
pub key for the destination node. This makes using our existing
pathfinding logic tricky since it depends on having a single destination
node (characterised by a single pub key). We want to re-use this logic.
So what we do is swap out the pub keys of the destinaion hop with a
pseudo target pub key. This will then be used during pathfinding. Later
on once a path is found, we will swap the real destination keys back in
so that onion creation can be done.
Instead of needing to remember how to handle the FinalCLTV value of a
blinded payment path at various points in the code base, we hide the
logic behind a unified FinalCLTVDelta method on the blinded path.
Continue adding some complexity behind the BlindedPaymentPathSet. What
we do here is add a new IntroNodeOnlyPath method. The assumption we
make here is: If multiple blinded paths are provided to us in an invoice
but one of those paths only includes an intro node, then there is no
point in looking at any other path since we know that the intro node is
the destination node. So in such a case, we would have discarded any
other path in the `NewBlindedPaymentPathSet` constructor. So then we
would only have a single blinded path made up of an introduction node
only. In this specific case, in the `newRoute` function, no edge passed
to the function would have a blindedPayment associated with it (since
there are no blinded hops in this case). So we will have a case where
`blindedPathSet` passed to `newRoute` is not nil but `blindedPayment` is
nil since nonce was extacted from any edge. If this happens then we can
assume that this is the Intro-Node-Only situation described above. And
so we grabe the associated payment from the path set.
@ellemouton ellemouton changed the base branch from elle-rb-receives-2 to master July 31, 2024 07:33
Copy link
Collaborator Author

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

Thanks for the review @ziggie1984 & thanks for catching that panic @Roasbeef - turns out, we really dont need the InPolicy here. We just need the ChannelID, so i've removed the InPolicy dep (see the last commit)

routing/blinding.go Show resolved Hide resolved
}

// For now, we assert that all the paths have the same set of features.
features := paths[0].Features
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

invoice features & path features are different.

not sure im completely following the question?

routing/blinding.go Outdated Show resolved Hide resolved
routing/blinding.go Outdated Show resolved Hide resolved
routing/blinding.go Show resolved Hide resolved
lnrpc/routerrpc/router_backend.go Show resolved Hide resolved
routing/blinding.go Outdated Show resolved Hide resolved
routing/blinding.go Outdated Show resolved Hide resolved
routing/pathfind.go Show resolved Hide resolved
routing/blinding.go Show resolved Hide resolved
@ellemouton
Copy link
Collaborator Author

ellemouton commented Jul 31, 2024

oops - I accidentally clicked re-request @ziggie1984 🙈 sorry about that

To prevent an attacker from causing us to assign a huge in-memory
buffer, we place a cap on the maximum cipher text size of a blinded path
hop.
@Roasbeef
Copy link
Member

Roasbeef commented Aug 1, 2024

Tried out the latest branch, and I was able to make an initial blinded paths payment!

Ran into some other issues along the way:

  • When trying to pay with default settings, I got INVALID_ONION_BLINDING each time.
  • That was with a node that may have been added within the blinded path as I had a direct channel with it.
  • I then tried to reduce max-num-paths to 1, initially thinking it woudl give me only 1 hop routes, but I understand now that means only have a single path.
  • After that I got some errors where it was unable to make a path at all:
     tlncli addinvoice --amt=100 --blind
    [lncli] rpc error: code = Unknown desc = could not collect blinded path relay info: no channel updates found from node 
    022fb1230aefbd135141997f03343f5410e6f1ab11d337af6df92cb67d5a856748 for channel 2681963946846388225
     tlncli addinvoice --amt=100 --blind
    [lncli] rpc error: code = Unknown desc = could not build any blinded paths
    
    • Perhaps we should have an inner retry loop here. I did the command a few more times until one of them finally worked.
  • I tried again with only a single blinded path ( max-num-paths=1):
    • The first time around, it was trying to find very long routes to the dest, and failing: TEMPORARY_CHANNEL_FAILURE @ 2nd hop | 34.580 | 36.342 | 100 | 2.707 | 2871717 | 3157069518315978752 | 030f375d8aecdddc8523->24hr Drive Thru->POSTECHCCBR->TuDuD->lnd-test.24h.to->SOMBERMASTER->039c25->025eac
    • I tried again, with the path succeeding with a route of similar length:
    | SUCCEEDED                           |        1.229 |        6.271 | 100          | 3.106 |  2871679 | 3157069
    518315978752 | 030f375d8aecdddc8523->aranguren.org->OCEAN_TESTNET_WK1->03fef5->022060 |
    +-------------------------------------+--------------+--------------+--------------+-------+----------+------------ 
    ---------+------------------------------------------------------------------------+
    Amount + fee:   100 + 3.106 sat
    Payment hash:   ab44ac84be151fde822a7dda35a7acf908d431ad719850ddfddccdb05220d38e
    Payment status: SUCCEEDED, preimage: 
    b6243fdf1869bae1c962c5b528b8ba563845bad51320bebd921635cbb2044ed2
    

@Roasbeef
Copy link
Member

Roasbeef commented Aug 1, 2024

I think the instances of INVALID_ONION_BLINDING might be older nodes on testnet that aren't running the latest version of blinded paths.

Some other featrure requests after trying things out:

  • Command to unblind blinded paths as the creator. Can be useful for debugging.
  • All dynamically setting values like num paths and path length via lncli addinvoice. During testing I had to restart each time I wanted to mess with a different value.
  • Add ability to omit a node from the blinded path. Would've been useful to pinpoint if it's just certain nodes sending INVALID_ONION_BLINDING errors.

Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

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

Reviewed 2 of 9 files at r2, 5 of 10 files at r6, 5 of 5 files at r7, 1 of 1 files at r8, all commit messages.
Reviewable status: all files reviewed, 18 unresolved discussions (waiting on @bitromortac, @ellemouton, and @ziggie1984)

@Roasbeef Roasbeef merged commit 04dde98 into lightningnetwork:master Aug 1, 2024
32 of 34 checks passed
@ellemouton ellemouton deleted the rb-send-via-multi-path branch August 1, 2024 06:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
blinded paths P2 should be fixed if one has time
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants