-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Update routing hints upon insufficient fees failure. #2151
Update routing hints upon insufficient fees failure. #2151
Conversation
routing/missioncontrol.go
Outdated
@@ -291,6 +291,27 @@ func generateBandwidthHints(sourceNode *channeldb.LightningNode, | |||
return bandwidthHints, nil | |||
} | |||
|
|||
// UpdateEdgePolicy updates the edge policy of the routing hints kept in the | |||
// payment session. The number of hints updated is returned. | |||
func (p *paymentSession) UpdateEdgePolicy(e *channeldb.ChannelEdgePolicy) uint32 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems the return value is used nowhere and can just as well be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
routing/router.go
Outdated
@@ -1838,6 +1840,8 @@ func (r *ChannelRouter) sendPayment(payment *LightningPayment, | |||
// newly updated fees. | |||
case *lnwire.FailFeeInsufficient: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not the only case where we can get back a channel update. For example, FailIncorrectCltvExpiry
. I think we should try to update additionalEdges
also in those cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can the update maybe be done inside applyChannelUpdate
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
t.Parallel() | ||
|
||
const startingBlockHeight = 101 | ||
ctx, cleanUp, err := createTestCtxFromFile(startingBlockHeight, basicGraphFilePath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are currently two ways of writing these kind of path finding tests. One is the way you did it, using the json file. The other option is used in for example TestChannelUpdateValidation
. It allows programmatically building the test graph and generates some values like the keys by itself (as the actual value isn't important for the tests, it just needs a value). You could consider using that instead. I think it is clearer than keep adding new channels to the json file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that you will still have the problem of needing a private key even if you use TestChannelUpdateValidation approach, as the validation of the error message is part of the tested code. Anyhow, I like the approach of using the basic graph in many tests all based on a non trivial topology. It requires the writer of the test to rethink whether the logic indeed covers all scenarios, beyond the simplest topology needed for testing the particular feature or fix she was aiming to do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's okay, just wanted to mention the option.
Needs to be rebased |
Also, have a look at the commit message format described in https://dev.lightning.community/contribute/. We add the subsystem prefix to the message. |
57fbf15
to
cc5444f
Compare
Thanks for the review! |
1ba5022
to
4b1f171
Compare
I rebased again, but for some reason the third CI test (coverage) fails in auto pilot and I don't think this has anything to do with the code modification in this PR. I forced push twice already and got the same error there. |
4b1f171
to
2ef99a0
Compare
routing/router.go
Outdated
@@ -1818,6 +1820,12 @@ func (r *ChannelRouter) sendPayment(payment *LightningPayment, | |||
update *lnwire.ChannelUpdate, | |||
pubKey *btcec.PublicKey) { | |||
|
|||
err := ValidateChannelUpdateAnn(pubKey, update) | |||
if err != nil { | |||
log.Errorf("Unable to validate channel update: %v", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line and others too exceed the max line width of 80 chars (project coding standard).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The log line is of 77 width. I believe I kept the <80 width everywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have it at 96 chars. Do you have tab size 8?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tab size 4. Using vscode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The standard is 80 chars max @ tab size 8.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry about that. Fixed.
routing/router.go
Outdated
@@ -1818,6 +1820,12 @@ func (r *ChannelRouter) sendPayment(payment *LightningPayment, | |||
update *lnwire.ChannelUpdate, | |||
pubKey *btcec.PublicKey) { | |||
|
|||
err := ValidateChannelUpdateAnn(pubKey, update) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this added here? Validation is also done inside r.applyChannelUpdate
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree this is ugly. My excuse it that I'm trying to minimize the changes I do in code to those mandatory for this PR. Therefore, if validation fails, processChannelUpdateAndRetry should immediately return, and should not update paySession at all (neither update hints or report edge policy failure), so the check must be there. However we can not rely on the error returned by applyChannelUpdate, as it may well error in other scenarios (i.e. not validation errors) which are perfectly ok, and do require retries. Initially I thought of doing the validation much earlier, i.e. before the switch on the error types, but this looks like a change that is too major for this PR; hence my feeling was that adding validation here is the right way to go, with only bad consequence being that its checked twice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is ok to not update the payment session if the graph update fails. ReportEdgeFailure
is important to call in case of a validation error. If we get an incorrect update we don't want to try that channel anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. I removed the extra validation and returned if applyChannelUpdate fails after the edge is pruned.
I'm uncomfortable with the handling of error messages that fail in signature validation, as they do prune edges. Doesn't that allow an attacker to send error messages and cause the routing to fail?
routing/router.go
Outdated
FeeBaseMSat: uBaseFee, | ||
FeeProportionalMillionths: uFeeRate, | ||
}) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You've added it in here, but for other cases where r.applyChannelUpdate
is called directly, the payment session isn't updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My idea is that the hints are updated only when a retry is aimed at. It now handles all those cases (not only insufficient fees). Otherwise the vertex or channel is removed completely, and there is no need for the update. I thought that is the whole purpose of defining processChannelUpdateAndRetry (which includes the paySession in its closure).
Said in another way, it doesn't seem right to me to plug it into applyChannelUpdate, exactly because this function is called for changing database edges/vertexes (and don't have paySession in closure).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think you're right. All other calls to applyChannelUpdate
prune immediately, so those edges will never show up in subsequent payment attempts anymore.
t.Parallel() | ||
|
||
const startingBlockHeight = 101 | ||
ctx, cleanUp, err := createTestCtxFromFile(startingBlockHeight, basicGraphFilePath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's okay, just wanted to mention the option.
routing/missioncontrol.go
Outdated
// run over hints and update policy if edge id matches | ||
for _, edges := range p.additionalEdges { | ||
for _, edge := range edges { | ||
if edge.ChannelID == e.ChannelID { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you updating the channel in both directions? I think you may also need to match the pubkey in additional edges to update only a single direction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The channel is updated only in one direction. That is, the node generates an error on the outgoing channel policy, and you update the hints accordingly. Am I missing something here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, suppose you have two additional edges relating to the same channel id but in opposite direction. They will both be updated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. I changed it such that it takes direction into account too, such that policy will be updated only if channel ID and direction both match.
I think this also fixes a general hidden bug in routing through hint hops, not related to this PR, as additionalEdges policies didn't take into account direction. That is, the Flags were not initialized in NewPaymentSession, and routing assumes the direction is initialized in all edge policies.
cf3f300
to
a0ccce4
Compare
Ready for inspection :-) |
routing/missioncontrol.go
Outdated
if edge.ChannelID == e.ChannelID && | ||
edge.Flags&lnwire.ChanUpdateDirection == | ||
e.Flags&lnwire.ChanUpdateDirection { | ||
edge.FeeBaseMSat = e.FeeBaseMSat |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New line after wrapped line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
routing/router.go
Outdated
var direction lnwire.ChanUpdateFlag | ||
if failedEdge.direction == 1 { | ||
direction = lnwire.ChanUpdateDirection | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
failedEdge.direction
can be used directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure. failedEdge direction is unit8 while Flags are unit16.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, you may need to do a type cast, but the if statement is not necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
routing/missioncontrol.go
Outdated
// UpdateEdgePolicy updates the edge policy of the routing hints kept in the | ||
// payment session. | ||
func (p *paymentSession) UpdateEdgePolicy(e *channeldb.ChannelEdgePolicy) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove newline
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
routing/missioncontrol.go
Outdated
// payment session. | ||
func (p *paymentSession) UpdateEdgePolicy(e *channeldb.ChannelEdgePolicy) { | ||
|
||
// run over hints and update policy if edge id and direction matches |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Capital at beginning of sentence. Period at end.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
log.Error(err) | ||
return err | ||
} | ||
if exists { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a fix in itself? Ideally this could be yet another separate commit, but if you find it too small for that, it's fine with me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to add it as otherwise you would get errors on applyChannelUpdate when trying to update private channels hops.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think maybe the proper fix here is something like this: ad92e5c (feel free to cherry-pick it into this PR if needed).
I believe then you wouldn't have to flip the AssumeChannelValid
in the config during tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created #2549
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be removed now that #2549 is merged.
routing/router.go
Outdated
uBaseFee := lnwire.MilliSatoshi(update.BaseFee) | ||
uFeeRate := lnwire.MilliSatoshi(update.FeeRate) | ||
uMinHTLC := lnwire.MilliSatoshi( | ||
update.HtlcMinimumMsat) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This vars are introduced because of line length I assume. But maybe that indicates that now is the time to extract this closure to a separate function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. But I don't get much if I move it to a separate function defined within the closure, as the names are so long here that it doesn't make it much better. I rather leave it as is. Defining a separate function altogether for an internal code that is called once is not something I personally prefer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are plenty of internal functions that are called only once, it does help to make the code more understandable. Inline functions can be useful especially if a lot of local vars are bound.
How about just creating a function taking failedEdge
and update
and returning the ChannelEdgePolicy
? Or better, updating the signature of UpdateEdgePolicy
to take failedEdge
and update
. Also consistent with other pay session funcs that take an edgeLocator
too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is the way to go. Thanks!
routing/router_test.go
Outdated
|
||
// The route should pass via the private channel | ||
if !route.containsChannel(privateChannelID) { | ||
t.Fatalf("route did not pass through private channel between pham nuwen " + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Line length.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
routing/missioncontrol.go
Outdated
@@ -219,9 +226,9 @@ func (m *missionControl) NewPaymentSession(routeHints [][]HopHint, | |||
hopHint.FeeProportionalMillionths, | |||
), | |||
TimeLockDelta: hopHint.CLTVExpiryDelta, | |||
Flags: direction, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was indeed not set, but I think it didn't have impact before, because that flag was never read right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it did have impact, i.e. it was an open bug, but the situations where it matters were rare enough such that it wasn't noticed. I haven't fully thought this out. What happened was that when I tried to fix UpdateEdgePolicy to consider direction as well, following your comment, the (new) test failed, and adding direction here fixed it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it was an omission that surfaced when you fixed UpdateEdgePolicy
. But I think that before that, the situations where it mattered where non-existent. Just interested to know whether that was true. In any case, it is fixed now.
a0ccce4
to
d469a0e
Compare
Changes made. |
This would be great if possible, as we at Fastbitcoins are encountering the issue this PR addresses. |
Ok, great. Let me know if anything is needed on my side. |
373efcd
to
5868c85
Compare
@halseth Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is close after a rebase! That should let you remove the AssumeChanValid
hack now that #2549 is merged.
Again, really good find for the direction pruning bug, and the added test case is 🔥
// before further processing an edge update. Toggel AssumeChannelValid | ||
// to avoid dropping the channel update, as the utxo set query is | ||
// not mocked in this test. | ||
ctx.router.cfg.AssumeChannelValid = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this still needed?
// r3: C->E is pruned | ||
// leaving r4 as viable 4th route | ||
// | ||
// This test was added to test direction initialization in NewPaymentSession; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent test description! 👍
routing/router_test.go
Outdated
ctx, cleanUp, err := createTestCtxFromGraphInstance(startingBlockHeight, | ||
testGraph) | ||
|
||
defer cleanUp() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move defer cleanUp()
after error check
// before further processing an edge update. Toggel AssumeChannelValid | ||
// to avoid dropping the channel update, as the utxo set query is | ||
// not mocked in this test. | ||
ctx.router.cfg.AssumeChannelValid = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not needed anymore I think
// not mocked in this test. | ||
ctx.router.cfg.AssumeChannelValid = true | ||
|
||
// Send payment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: missing period.
routing/payment_session.go
Outdated
@@ -35,6 +35,51 @@ type paymentSession struct { | |||
preBuiltRoutes []*Route | |||
} | |||
|
|||
// edgeLocatorOfEdgePolicy returns the edge locator corresponding to | |||
// the channel edge policy | |||
func edgeLocatorOfEdgePolicy(ep *channeldb.ChannelEdgePolicy) *edgeLocator { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just return non-pointer value.
routing/payment_session.go
Outdated
|
||
// UpdateEdgePolicy updates edge policy of the routing hints kept in the | ||
// payment session. | ||
func (p *paymentSession) UpdateEdgePolicy(el *edgeLocator, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also better to take non-pointer for the edgeLocator
here.
log.Error(err) | ||
return err | ||
} | ||
if exists { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be removed now that #2549 is merged.
routing/router_test.go
Outdated
|
||
const startingBlockHeight = 101 | ||
ctx, cleanUp, err := createTestCtxFromFile(startingBlockHeight, basicGraphFilePath) | ||
defer cleanUp() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comment wrt cleanup
// before further processing an edge update. Toggel AssumeChannelValid | ||
// to avoid dropping the channel update, as the utxo set query is | ||
// not mocked in this test. | ||
ctx.router.cfg.AssumeChannelValid = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can be removed
and its parser. Required to correctly sign and later verify error messages in some test scenarios.
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.
Hints are updated if the error message is either: - FailFeeInsufficient - FailAmountBelowMinimum - FailIncorrectCltvExpiry
5868c85
to
0f1d233
Compare
0f1d233
to
3d2cea0
Compare
I did the rebase, but I cannot figure out why the new direction unitest TestSendPaymentPrivateEdgeDirection fails. It didn't fail in the version before the rebase. |
@Bluetegu No worries! Thanks for your excellent contributions 👍 |
Somebody should pick this up and rebase the still relevant parts. |
Replaced by #5332 |
This pull request address issue #2066
Reconstruct the problem:
Setup composed of 3 nodes, A -> B -> C, where B -> C is private.
Create an invoice on C (C:addinvoice with private flag)
Increase fee on B->C (B:updatechanpolicy to a higher fee)
Attempt to pay invoice from A (A:sendpayment --pay_req=xxx)
Receive payment error ("unable to route payment to destination: FeeInsufficient ... ")
After the fix the payment goes through.
A unit test on the 'basic graph' was added. The test adds a private channel between son goku and elst, with lower fees compared with the alternative routes from roasbeef to elst. A fee error is returned on first attempt to route through the private channel, with a higher fee, but still better compared with the alternative routes. The test checks that the after the update the payment is still routed through the private channel but with the updated higher fees.
A bug in the existing test TestSendPaymentErrorRepeatedFeeInsufficient was fixed too. I did a minimal fix. I believe this test can be further improved. The major issue was that the simulated error returned was rejected due to signature failure, and hence didn't simulate correctly the insufficient fees error as intended. In order to correctly sign the errors, an optional private key attribute was added to the basic graph and its parser.