-
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
rpc+routing: add support for fee limits when finding routes for payments #1113
Conversation
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.
Nice job! This feature is a much needed one, as at the moment you might end up paying excessive amounts of fees with no way of controlling it! 🚔
A few comments, main one that we should add test for the new RPC.
Thanks to @djseeds and @sebastiandelgado for getting this started, and @danrobinson for initial review! 👏
rpcserver.go
Outdated
btcutil.Amount(feeLimit.GetFixed()), | ||
) | ||
case *lnrpc.FeeLimit_Percent: | ||
return amount * lnwire.MilliSatoshi(feeLimit.GetPercent()/100) |
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.
generally best to move the division last to avoid loss of precision, as I think this will become 0
by integer division.
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.
rpcserver.go
Outdated
// payment amount as an upper bound since there might be some | ||
// routes where you pay more in fees than the amount of the | ||
// payment. | ||
return maxPaymentMSat |
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.
Maybe we should restrict this to 2x payment amount or something?
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.
Yeah I was thinking this too. Not sure if we want to enforce this without adding a no_fee_limit
flag though.
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.
If the caller really wants no fee limit, she can set the fee limit to a ridiculously high number.
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.
Still think we need to have a sane default value 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.
Forgot to address this - should be good now.
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 gave this a spin on testnet and seems o be working as advertised! Only thing I realized is that very small payments might be impossible to get through with this default value, since fees might always be higher. This might be okay though, as you can always increase your fee limit.
Also, for large payments, you might end up paying 2x the amount if the only route is a high fee route (value + fee
when fee=value
). This might lead to some unpleasant surprises. Should we set a default, hard coded max value? We could even consider doing this at the lncli
level. Atm the default value appears to be 0
if you look at lncli payinvoice -h
. We could change this to a reasonable value?
err error | ||
amount int64 | ||
) | ||
return sendPaymentRequest(ctx, req) |
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.
Nice refactor! Can you add a short comment why we short circuit in case pay_req
is set?
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.
@@ -992,8 +1004,10 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { | |||
|
|||
// Should still be able to find the routes, and the info should be | |||
// updated. | |||
routes, err = ctx.router.FindRoutes(targetNode, paymentAmt, | |||
defaultNumRoutes, DefaultFinalCLTVDelta) | |||
routes, err = ctx.router.FindRoutes( |
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.
Could add a short test here for the fee limit exceeded case?
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.
rpcserver.go
Outdated
@@ -1832,6 +1852,10 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer) | |||
p.cltvDelta = uint16(nextPayment.FinalCltvDelta) | |||
} | |||
|
|||
p.feeLimit = calculateFeeLimit( |
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.
Make an addition to the integration tests to check that percentage and absolute fee limits are working correctly?
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.
lnd_test.go
Outdated
} | ||
|
||
// Wait for Alice to receive the channel update. | ||
time.Sleep(5 * time.Second) |
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.
we should try to eliminate sleeps during integration tests, as they are becoming quite time consuming. Is there any way you could subscribe to graph updates to determine when Alice has seen the policy update? (maybe the policy update test does this).
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.
for _, channel := range listResp.Channels { | ||
switch channel.RemotePubkey { | ||
case net.Alice.PubKeyStr: | ||
aliceBobChanID = channel.ChanId |
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 could get these from openChannelAndAssert
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.
EDIT: Since these are short channel IDs I realized you probably can't that easily, so this approach is fine :)
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.
Starting to look very good to me! One final comment about shortening the test, then I will do a final review after squash an rebase 👍
lnd_test.go
Outdated
|
||
// waitForChanUpdate is a helper closure that will wait for an update | ||
// for a specific channel and assert its routing policy. | ||
waitForChanUpdate := func(graphUpdates chan *lnrpc.GraphTopologyUpdate, |
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's a waitForChannelUpdate
elsewhere in this file. Is it possible to unify them, or are they checking different criteria?
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.
lnd_test.go
Outdated
} | ||
|
||
// assertChannelPolicy asserts that the passed node's known channel | ||
// policy for the passed chanPoint is consistent with Bob's current |
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.
Bob
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.
|
||
// waitForChannelUpdate waits for a node to receive updates from the advertising | ||
// node for the specified channels. | ||
func waitForChannelUpdate(t *harnessTest, graphUpdates chan *lnrpc.GraphTopologyUpdate, |
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.
👍
for _, channel := range listResp.Channels { | ||
switch channel.RemotePubkey { | ||
case net.Alice.PubKeyStr: | ||
aliceBobChanID = channel.ChanId |
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.
EDIT: Since these are short channel IDs I realized you probably can't that easily, so this approach is fine :)
Still think we need a default fee cutoff set. Otherwise this LGTM 👍 |
7b9faf0
to
0388447
Compare
68182b0
to
980ba6b
Compare
Needs a rebase to address proto conflicts. |
Rebased. |
Needs rebase and probably some tweaking again after |
Rebased. |
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.
LGTM 👾
Can you try to rebase s.t, the binaries are never even added in the first place?
Also, FWIW the current impl is a bit off, as it'll just error out, rather then seeking the set of paths that respect the feel limit. However, with #1321, we'll have the opportunity to implement a proper greedy solution.
cmd/lncli/commands.go
Outdated
@@ -2547,6 +2614,8 @@ func queryRoutes(ctx *cli.Context) error { | |||
return fmt.Errorf("amt argument missing") | |||
} | |||
|
|||
feeLimit = calculateFeeLimit(ctx, amt) | |||
|
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 debug binaries have been committed.
- Extend SendRequest and QueryRoutesRequest protos - newRoute function takes fee limit and cuts off routes that exceed it - queryRoutes, payInvoice and sendPayment commands take the feeLimit inputs and pass them down to newRoute - When no feeLimit is included, don't enforce any feeLimits at all (by setting feeLimit to maxValue)
This PR adds support for fee limits when finding possible routes for payments.
The fee limit can be specified in two ways: either as a fixed amount of satoshis or as a percentage of the payment's amount.
Replaces #714 and fixes #278.