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

routing: update route hints when seeing a ChannelUpdate msg #5218

Closed

Conversation

yyforyongyu
Copy link
Collaborator

@yyforyongyu yyforyongyu commented Apr 16, 2021

The issue is defined in #2540.

In short, because we treat the RouteHints as ephemeral info which doesn't belong to db, when we first heard them from the invoice and convert them into ChannelEdgePolicy, we didn't update them after the edge updates them. This PR takes the ChannelUpdate message wrapped in the error message found in sendPayment, and applies the update if it was meant for the ChannelEdgePolicy of the private channel.

The Change

In order to be able to update private edge policies, some refactor has been done, as in,

  • shardHandler has to be aware of PaymentSession to access and modify the additionalEdges.
  • we now handle send error messages in shardHandler, decide whether the update is for private or public channels. PaymentSession will handle it first if the update is for the private channel, using the new method UpdateAdditionalEdge. Otherwise, we pass it to ChannelRouter to handle it.

The Question

Although we treat the RouteHints as ephemeral info, it actually isn't. The route hints are stored in payReq during payment initialization, which means, users will see inconsistent records once the private ChannelEdgePolicy is updated. The question is, should we go further to change the route hints stored in the payReq?

The Alternative

Another thought is to treat the ChannelEdgePolicy created from RouteHints as normal policies, which means we can save them to db and process them using the logic path created for public channel. The downside is a) we might see the db grows too large (maybe prune after each payment?) and b) users could slowly build up the graph using the route hints thus breach privacy.

This PR took the tests from #2151, rebased, refactored, and included in this PR. There is still this excellent test TestSendPaymentPrivateEdgeDirection, which I will include in another PR to keep the current one compact and focused.

Fixes #2540.
Fixes #4807.

Copy link
Contributor

@halseth halseth left a comment

Choose a reason for hiding this comment

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

should we go further to change the route hints stored in the payReq

I don't think we should, since it will make the invoice invalid. For record keeping it feels like e rather should store it unchanged, the information we learn during path finding won't be used again later anyway.

func (p *shardHandler) handleSendError(attempt *channeldb.HTLCAttemptInfo,
sendErr error) error {

reason := p.router.processSendError(
attempt.AttemptID, &attempt.Route, sendErr,
Copy link
Contributor

Choose a reason for hiding this comment

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

is an alternative to pass the payment session to this method?

Copy link
Member

Choose a reason for hiding this comment

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

To processSendError itself? IMO this is a better change as it moves the error handling closer to the source in a sense as the shard handler is what's responsible for retrying, tracking the set of in-flight shards, reporting the failure to mission control, etc.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

is an alternative to pass the payment session to this method?

Yeah that's indeed an alternative, and probably a change easier to make. However, after going through the current design, I think our ChannelRouter is made intentionally to not care about in-memory states such as PaymentSession. If we want to go the alternative way, ChannelRouter would need to store PaymentSession in its struct to be able to use it inside the method processSendError. Plus what @Roasbeef said above.

@Roasbeef Roasbeef added this to the 0.13.0 milestone Apr 20, 2021
@Roasbeef Roasbeef added this to In progress in v0.13.0-beta via automation Apr 20, 2021
@Roasbeef Roasbeef added P2 should be fixed if one has time bug fix routing nodes labels Apr 20, 2021
@Roasbeef
Copy link
Member

The question is, should we go further to change the route hints stored in the payReq?

As mentioned above, if we do this, then the sig on the invoice will no longer validate as it's now essentially invalid. It's also the case that w/e we update it to can also be invalidated when the private channel updates their policies once again. Early in the network private channels didn't update policies much as there was no clear use, but lately many wallets implement "on the fly" channel creation and use a change in the "fake" private channel chan update to have the sender retry once the channels parmaters have been finalized.

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.

Nice job resurrecting such an old PR! This'll really help out many of the popular LN wallets that utilize private channel updates to improve UX by opening channels on the fly.

@@ -118,11 +118,14 @@ type testGraph struct {

// testNode represents a node within the test graph above. We skip certain
// information such as the node's IP address as that information isn't needed
// for our tests.
// for our tests. Private keys are optional. If set, they should be consistent
Copy link
Member

Choose a reason for hiding this comment

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

FWIW new tests don't really use the old static graph file and instead opt tin create a graph on the fly using some of the newly added test helpers. However I understand this was a revived PR and at the time of writing of this original commit, this was the only/preferred way to do things.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Got it. So are you suggesting we switch these tests to make the graph created on the fly in this PR?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

And unrelated to this PR, we probably want to refactor old tests to get rid of the static graph file someday?

Copy link
Member

Choose a reason for hiding this comment

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

Got it. So are you suggesting we switch these tests to make the graph created on the fly in this PR?

Don't think it's a blocker, as this commit only exists as we salvaged the OG commit from the contributor.

routing/router_test.go Outdated Show resolved Hide resolved
routing/router.go Show resolved Hide resolved
func (p *shardHandler) handleSendError(attempt *channeldb.HTLCAttemptInfo,
sendErr error) error {

reason := p.router.processSendError(
attempt.AttemptID, &attempt.Route, sendErr,
Copy link
Member

Choose a reason for hiding this comment

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

To processSendError itself? IMO this is a better change as it moves the error handling closer to the source in a sense as the shard handler is what's responsible for retrying, tracking the set of in-flight shards, reporting the failure to mission control, etc.

routing/payment_lifecycle_test.go Outdated Show resolved Hide resolved
routing/payment_session.go Show resolved Hide resolved
routing/payment_session.go Outdated Show resolved Hide resolved
// Apply channel update to additional edges first. If no update is
// performed, we will continue to update the channel edge policy in our
// db.
if p.paySession != nil && p.paySession.UpdateAdditionalEdge(
Copy link
Member

Choose a reason for hiding this comment

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

Alternatively, couldn't we check that the chan ID being updated is amongst the set of ephemeral edges?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is changed, now we first lookup the edge policy inside additionalEdges, then decide which one to update.

target := route.NewVertex(pubKey)

for v, edges := range p.additionalEdges {
if v != target {
Copy link
Member

Choose a reason for hiding this comment

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

We can do a map look up here (of the outer map) to avoid the outer loop iteration.

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 my bad, Changed.

lntest/itest/lnd_routing_test.go Show resolved Hide resolved
v0.13.0-beta automation moved this from In progress to Review in progress Apr 20, 2021
@yyforyongyu yyforyongyu force-pushed the 2540-fix-retry-err branch 5 times, most recently from 3be6464 to 78ed2df Compare April 29, 2021 03:14
@yyforyongyu yyforyongyu force-pushed the 2540-fix-retry-err branch 3 times, most recently from 7c7da01 to f0285b9 Compare April 30, 2021 08:49
Copy link
Contributor

@halseth halseth left a comment

Choose a reason for hiding this comment

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

Great work on this PR and added tests! 👍 Nothing major from me, looks pretty good.

routing/payment_lifecycle_test.go Show resolved Hide resolved
routing/payment_lifecycle_test.go Outdated Show resolved Hide resolved
routing/router_test.go Show resolved Hide resolved
routing/router.go Outdated Show resolved Hide resolved
// If we received an unknown failure message from a node along the
// route, the failure message will be nil.
failureMessage := rtErr.WireMessage()
if err := p.handleFailureMessage(
Copy link
Contributor

Choose a reason for hiding this comment

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

style nit on this and more:

err := p.handleFailureMessage(
    &attempt.Route, failureSourceIdx, failureMessage,
)
if err != nil {

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Wait I thought the one-liner was the preferable style...?

Copy link
Contributor

Choose a reason for hiding this comment

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

usually only when it actually fits on a single line :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ha got it. Guess I went too far on the one-line style.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Changed it back. Like it when we can decrease the total number of lines.

@@ -138,6 +140,7 @@ func RouteHintsToEdges(routeHints [][]zpay32.HopHint, target route.Vertex) (
hopHint.FeeProportionalMillionths,
),
TimeLockDelta: hopHint.CLTVExpiryDelta,
LastUpdate: time.Now(),
Copy link
Contributor

Choose a reason for hiding this comment

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

hm this wasn't set at all before? What consequences did that have?

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'd say nothing since previously we didn't update the channel edge policy created from route hints.

routing/payment_session.go Outdated Show resolved Hide resolved
routing/payment_lifecycle.go Show resolved Hide resolved
lntest/itest/lnd_routing_test.go Show resolved Hide resolved
@yyforyongyu yyforyongyu force-pushed the 2540-fix-retry-err branch 2 times, most recently from b007d7f to 981b406 Compare May 1, 2021 08:27
@yyforyongyu
Copy link
Collaborator Author

Tests still fail, related to #5259

@yyforyongyu yyforyongyu requested a review from halseth May 1, 2021 08:30
Copy link
Contributor

@halseth halseth left a comment

Choose a reason for hiding this comment

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

Only nits from me at this point. LGTM 🚀

// UpdateAdditionalEdge updates the channel edge policy for a private edge. It
// validates the message signature and checks it's up to date, then applies the
// updates to the supplied policy. It returns a boolean to indicate whether
// there's an error when applying the updates.
Copy link
Contributor

Choose a reason for hiding this comment

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

why not just return the error?

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 I thought about that too. Then I thought it might be better to have the same structure as the method p.router.applyChannelUpdate. Plus the error found in this one won't be handled except for logging.

@yyforyongyu yyforyongyu force-pushed the 2540-fix-retry-err branch 2 times, most recently from aa1fac2 to 875a70d Compare May 5, 2021 10:16
@yyforyongyu yyforyongyu force-pushed the 2540-fix-retry-err branch 2 times, most recently from cd829e6 to 203fb86 Compare May 20, 2021 00:30
@Roasbeef Roasbeef requested a review from cfromknecht May 20, 2021 00:34
yyforyongyu and others added 17 commits May 26, 2021 20:32
The simulated error returned was rejected due to signature failure,
and didn't simulate correctly the insufficient fees error as
intended. Fix error by including correct signature.
This commit refactors some of the tests in router_test.go to use the
require package.
This commit moves the handleSendError method from ChannelRouter to
shardHandler. In doing so, shardHandler can now apply updates to the
in-memory paymentSession if they are found in the error message.
This commit adds the method UpdateAdditionalEdge in PaymentSession,
which allows the addtional channel edge policy to be updated from a
ChannelUpdate message. Another method, GetAdditionalEdgePolicy is added
to allow querying additional edge policies.
This commit adds payment session to shardHandler to enable private edge
policies being updated in shardHandler. The relevant interface and mock
are updated. From now on, upon seeing a ChannelUpdate message,
shardHandler will first try to find the target policy in additionalEdges
and update it. If nothing found, it will then check the database for
edge policy to update.
@Roasbeef
Copy link
Member

Merged as part of #5332.

@Roasbeef Roasbeef closed this Jun 25, 2021
v0.13.0-beta automation moved this from Blocked to Done Jun 25, 2021
@yyforyongyu yyforyongyu deleted the 2540-fix-retry-err branch June 25, 2021 07:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug fix P2 should be fixed if one has time routing nodes
Projects
No open projects
5 participants