Skip to content

Commit

Permalink
Merge f5cb8d4 into fe0a7b6
Browse files Browse the repository at this point in the history
  • Loading branch information
djseeds committed Sep 8, 2017
2 parents fe0a7b6 + f5cb8d4 commit 7cd77e6
Show file tree
Hide file tree
Showing 9 changed files with 577 additions and 304 deletions.
707 changes: 423 additions & 284 deletions lnrpc/rpc.pb.go

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions lnrpc/rpc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,16 @@ message SendRequest {
payment to the recipient.
*/
string payment_request = 6;

// Cut off specifying a fee value above which path finding will be short circuited
oneof fee_cut_off {
// Abandon the route if fees greater than amt
bool absolute_cut_off = 7 [json_name = "absolute_cut_off"];
// Abandon the route if fees greater than limit_satoshis
uint64 limit_satoshis = 8 [json_name = "limit_satoshis"];
//Abandon the route if fees greater than limit_ratio * amt
double limit_ratio = 9 [json_name = "limit_ratio"];
}
}
message SendResponse {
string payment_error = 1 [json_name = "payment_error"];
Expand Down
15 changes: 15 additions & 0 deletions lnrpc/rpc.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -1775,6 +1775,21 @@
"payment_request": {
"type": "string",
"description": "*\nA bare-bones invoice for a payment within the Lightning Network. With the\ndetails of the invoice, the sender has all the data necessary to send a\npayment to the recipient."
},
"absolute_cut_off": {
"type": "boolean",
"format": "boolean",
"title": "Abandon the route if fees greater than amt"
},
"limit_satoshis": {
"type": "string",
"format": "uint64",
"title": "Abandon the route if fees greater than limit_satoshis"
},
"limit_ratio": {
"type": "number",
"format": "double",
"title": "Abandon the route if fees greater than limit_ratio * amt"
}
}
},
Expand Down
4 changes: 4 additions & 0 deletions routing/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ const (
// this update can't bring us something new, or because a node
// announcement was given for node not found in any channel.
ErrIgnored

// ErrFeeCutoffExceeded is returned when the fees within a route
// exceed a user-specified fee cutoff.
ErrFeeCutoffExceeded
)

// routerError is a structure that represent the error inside the routing package,
Expand Down
16 changes: 13 additions & 3 deletions routing/pathfind.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,14 @@ func (s sortableRoutes) Swap(i, j int) {

// newRoute returns a fully valid route between the source and target that's
// capable of supporting a payment of `amtToSend` after fees are fully
// computed. If the route is too long, or the selected path cannot support the
// fully payment including fees, then a non-nil error is returned.
// computed. If the route is too long, the selected path cannot support the
// fully payment including fees, or the path requires more than *maxFee fees,
// then a non-nil error is returned.
//
// NOTE: The passed slice of ChannelHops MUST be sorted in forward order: from
// the source to the target node of the path finding attempt.
func newRoute(amtToSend lnwire.MilliSatoshi, pathEdges []*ChannelHop,
currentHeight uint32) (*Route, error) {
currentHeight uint32, maxFee *lnwire.MilliSatoshi) (*Route, error) {

// First, we'll create a new empty route with enough hops to match the
// amount of path edges. We set the TotalTimeLock to the current block
Expand Down Expand Up @@ -268,6 +269,15 @@ func newRoute(amtToSend lnwire.MilliSatoshi, pathEdges []*ChannelHop,
nextHop.Fee = 0
}

// After we calculate the total fees in the route, we must check
// if we have exceeded the user-specified fee threshold.
// If we have, we should abandon this route and return an error.
if maxFee != nil && route.TotalFees > *maxFee {
err := fmt.Sprintf("route fee of %v exceeds threshold of %v",
route.TotalFees, *maxFee)
return nil, newErrf(ErrFeeCutoffExceeded, err)
}

// Next, increment the total timelock of the entire route such
// that each hops time lock increases as we walk backwards in
// the route, using the delta of the previous hop.
Expand Down
4 changes: 2 additions & 2 deletions routing/pathfind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ func TestBasicGraphPathFinding(t *testing.T) {
if err != nil {
t.Fatalf("unable to find path: %v", err)
}
route, err := newRoute(paymentAmt, path, startingHeight)
route, err := newRoute(paymentAmt, path, startingHeight, nil)
if err != nil {
t.Fatalf("unable to create path: %v", err)
}
Expand Down Expand Up @@ -416,7 +416,7 @@ func TestBasicGraphPathFinding(t *testing.T) {
if err != nil {
t.Fatalf("unable to find route: %v", err)
}
route, err = newRoute(paymentAmt, path, startingHeight)
route, err = newRoute(paymentAmt, path, startingHeight, nil)
if err != nil {
t.Fatalf("unable to create path: %v", err)
}
Expand Down
15 changes: 9 additions & 6 deletions routing/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -813,9 +813,10 @@ type routingMsg struct {
// inner loop. Once we have a set of candidate routes, we calculate the
// required fee and time lock values running backwards along the route. The
// route that will be ranked the highest is the one with the lowest cumulative
// fee along the route.
// fee along the route. If maxFee is not nil, routes with fees greater than
// *maxFee will be abandoned.
func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
amt lnwire.MilliSatoshi) ([]*Route, error) {
amt lnwire.MilliSatoshi, maxFee *lnwire.MilliSatoshi) ([]*Route, error) {

dest := target.SerializeCompressed()
log.Debugf("Searching for path to %x, sending %v", dest, amt)
Expand Down Expand Up @@ -854,7 +855,7 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
// Attempt to make the path into a route. We snip off the first
// hop in the path as it contains a "self-hop" that is inserted
// by our KSP algorithm.
route, err := newRoute(amt, path[1:], uint32(currentHeight))
route, err := newRoute(amt, path[1:], uint32(currentHeight), maxFee)
if err != nil {
continue
}
Expand Down Expand Up @@ -967,13 +968,15 @@ type LightningPayment struct {
}

// SendPayment attempts to send a payment as described within the passed
// LightningPayment. This function is blocking and will return either: when the
// LightningPayment. If maxFee is not nil, SendPayment will attempt to
// find a route that requires a fee of less than *maxFee satoshis.
// This function is blocking and will return either: when the
// payment is successful, or all candidates routes have been attempted and
// resulted in a failed payment. If the payment succeeds, then a non-nil Route
// will be returned which describes the path the successful payment traversed
// within the network to reach the destination. Additionally, the payment
// preimage will also be returned.
func (r *ChannelRouter) SendPayment(payment *LightningPayment) ([32]byte, *Route, error) {
func (r *ChannelRouter) SendPayment(payment *LightningPayment, maxFee *lnwire.MilliSatoshi) ([32]byte, *Route, error) {
log.Tracef("Dispatching route for lightning payment: %v",
newLogClosure(func() string {
payment.Target.Curve = nil
Expand Down Expand Up @@ -1002,7 +1005,7 @@ func (r *ChannelRouter) SendPayment(payment *LightningPayment) ([32]byte, *Route
// payment amount. If no such routes can be found then an error will be
// returned.
if !ok {
freshRoutes, err := r.FindRoutes(payment.Target, payment.Amount)
freshRoutes, err := r.FindRoutes(payment.Target, payment.Amount, maxFee)
if err != nil {
return preImage, nil, err
}
Expand Down
88 changes: 82 additions & 6 deletions routing/router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,14 @@ func TestFindRoutesFeeSorting(t *testing.T) {
// Execute a query for all possible routes between roasbeef and luo ji.
paymentAmt := lnwire.NewMSatFromSatoshis(100)
target := ctx.aliases["luoji"]
routes, err := ctx.router.FindRoutes(target, paymentAmt)
routes, err := ctx.router.FindRoutes(target, paymentAmt, nil)
if err != nil {
t.Fatalf("unable to find any routes: %v", err)
}

// Exactly, two such paths should be found.
// Exactly two such paths should be found.
if len(routes) != 2 {
t.Fatalf("2 routes shouldn't been selected, instead %v were: %v",
t.Fatalf("2 routes should be selected, instead %v were: %v",
len(routes), spew.Sdump(routes))
}

Expand All @@ -146,6 +146,82 @@ func TestFindRoutesFeeSorting(t *testing.T) {
}
}

// TestFindRoutesFeeCutoff asserts that the FindRoutes method returns no
// routes with fees greater than the maxFee cutoff.
func TestFindRoutesFeeCutoff(t *testing.T) {
t.Parallel()

const startingBlockHeight = 101
var maxFee lnwire.MilliSatoshi
ctx, cleanUp, err := createTestCtx(startingBlockHeight, basicGraphFilePath)
defer cleanUp()
if err != nil {
t.Fatalf("unable to create router: %v", err)
}

paymentAmt := lnwire.NewMSatFromSatoshis(100)
target := ctx.aliases["luoji"]

// There are two possible paths from roasbeef to luo ji, with fees of
// 0 and 10 satoshis, respectively

// First, we check that both paths to luo ji are returned
// when maxFee is greater than required by each path
maxFee = 15
routes, err := ctx.router.FindRoutes(target, paymentAmt, &maxFee)

if err != nil {
t.Fatalf("unable to find any routes: %v", err)
}

// Exactly two paths should be found.
if len(routes) != 2 {
t.Fatalf("2 routes should be selected, instead %v were: %v",
len(routes), spew.Sdump(routes))
}

// Next, we check that only one path to luo ji is returned
// when maxFee is between the fees for each path
maxFee = 5
routes, err = ctx.router.FindRoutes(target, paymentAmt, &maxFee)

if err != nil {
t.Fatalf("unable to find any routes: %v", err)
}

// Only one path should be found.
if len(routes) != 1 {
t.Fatalf("1 routes should be selected, instead %v were: %v",
len(routes), spew.Sdump(routes))
}

// To test the case when the route fee equals maxFee,
// we check that one path is returned with a maxFee of 0
maxFee = 0
routes, err = ctx.router.FindRoutes(target, paymentAmt, &maxFee)

if err != nil {
t.Fatalf("unable to find any routes: %v", err)
}

// Only one path should be found.
if len(routes) != 1 {
t.Fatalf("1 route should be selected, instead %v were: %v",
len(routes), spew.Sdump(routes))
}

// Finally, we test the case of a negative maxFee, in which case
// we should get an error that no valid routes were found
maxFee = -1
routes, err = ctx.router.FindRoutes(target, paymentAmt, &maxFee)

// This time there is something wrong if we do find a route
if err == nil {
t.Fatalf("0 routes should be selected, instead %v were: %v",
len(routes), spew.Sdump(routes))
}
}

// TestSendPaymentRouteFailureFallback tests that when sending a payment, if
// one of the target routes is seen as unavailable, then the next route in the
// queue is used instead. This process should continue until either a payment
Expand Down Expand Up @@ -188,7 +264,7 @@ func TestSendPaymentRouteFailureFallback(t *testing.T) {

// Send off the payment request to the router, route through satoshi
// should've been selected as a fall back and succeeded correctly.
paymentPreImage, route, err := ctx.router.SendPayment(&payment)
paymentPreImage, route, err := ctx.router.SendPayment(&payment, nil)
if err != nil {
t.Fatalf("unable to send payment: %v", err)
}
Expand Down Expand Up @@ -494,7 +570,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
// We should now be able to find one route to node 2.
paymentAmt := lnwire.NewMSatFromSatoshis(100)
targetNode := priv2.PubKey()
routes, err := ctx.router.FindRoutes(targetNode, paymentAmt)
routes, err := ctx.router.FindRoutes(targetNode, paymentAmt, nil)
if err != nil {
t.Fatalf("unable to find any routes: %v", err)
}
Expand Down Expand Up @@ -536,7 +612,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {

// Should still be able to find the route, and the info should be
// updated.
routes, err = ctx.router.FindRoutes(targetNode, paymentAmt)
routes, err = ctx.router.FindRoutes(targetNode, paymentAmt, nil)
if err != nil {
t.Fatalf("unable to find any routes: %v", err)
}
Expand Down
22 changes: 19 additions & 3 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -1530,6 +1530,22 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
htlcSema <- struct{}{}
}()

// Calculate the user-specified fee cut-off
// using the FeeCutOff oneof field.
var maxFee *lnwire.MilliSatoshi
switch nextPayment.GetFeeCutOff().(type) {
case *lnrpc.SendRequest_LimitSatoshis:
maxFeeSatoshi := btcutil.Amount(nextPayment.GetLimitSatoshis())
*maxFee = lnwire.NewMSatFromSatoshis(maxFeeSatoshi)
case *lnrpc.SendRequest_LimitRatio:
maxFeeSatoshi := btcutil.Amount(nextPayment.GetLimitRatio() * float64(amt))
*maxFee = lnwire.NewMSatFromSatoshis(maxFeeSatoshi)
case *lnrpc.SendRequest_AbsoluteCutOff:
if nextPayment.GetAbsoluteCutOff() {
*maxFee = lnwire.NewMSatFromSatoshis(amt)
}
}

// Construct a payment request to send to the
// channel router. If the payment is
// successful, the route chosen will be
Expand All @@ -1540,7 +1556,7 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
Amount: amtMSat,
PaymentHash: rHash,
}
preImage, route, err := r.server.chanRouter.SendPayment(payment)
preImage, route, err := r.server.chanRouter.SendPayment(payment, maxFee)
if err != nil {
// If we receive payment error than,
// instead of terminating the stream,
Expand Down Expand Up @@ -1663,7 +1679,7 @@ func (r *rpcServer) SendPaymentSync(ctx context.Context,
Target: destPub,
Amount: amtMSat,
PaymentHash: rHash,
})
}, nil)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -2276,7 +2292,7 @@ func (r *rpcServer) QueryRoutes(ctx context.Context,
// Query the channel router for a possible path to the destination that
// can carry `in.Amt` satoshis _including_ the total fee required on
// the route.
routes, err := r.server.chanRouter.FindRoutes(pubKey, amtMSat)
routes, err := r.server.chanRouter.FindRoutes(pubKey, amtMSat, nil)
if err != nil {
return nil, err
}
Expand Down

0 comments on commit 7cd77e6

Please sign in to comment.