Skip to content

Commit

Permalink
Try to send the largest amount from a single channel
Browse files Browse the repository at this point in the history
  • Loading branch information
yaslama committed Sep 23, 2020
1 parent 50976e5 commit f64f98b
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 20 deletions.
23 changes: 19 additions & 4 deletions routing/pathfind.go
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,8 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
// balance available.
self := g.graph.sourceNode()

var splitNeeded bool

if source == self {
max, total, err := getOutgoingBalance(
self, outgoingChanMap, g.bandwidthHints, g.graph,
Expand All @@ -497,7 +499,8 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
// If there is only not enough capacity on a single route, it
// may still be possible to complete the payment by splitting.
if max < amt {
return nil, errNoPathFound
splitNeeded = true
amt = max
}
}

Expand Down Expand Up @@ -832,8 +835,12 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
continue
}

var amtForPolicy lnwire.MilliSatoshi
if !splitNeeded || fromNode != source {
amtForPolicy = amtToSend
}
policy := unifiedPolicy.getPolicy(
amtToSend, g.bandwidthHints,
amtForPolicy, g.bandwidthHints,
)

if policy == nil {
Expand Down Expand Up @@ -876,6 +883,9 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
// target.
var pathEdges []*channeldb.ChannelEdgePolicy
currentNode := source
if n, ok := distance[source]; ok && splitNeeded {
n.amountToReceive = amt
}
for {
// Determine the next hop forward using the next map.
currentNodeWithDist, ok := distance[currentNode]
Expand All @@ -895,6 +905,8 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
// to source.
if currentNode == target {
break
} else if splitNeeded {
amt -= (currentNodeWithDist.nextHop).ComputeFeeFromIncoming(amt)
}
}

Expand All @@ -914,8 +926,11 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
log.Debugf("Found route: probability=%v, hops=%v, fee=%v",
distance[source].probability, len(pathEdges),
distance[source].amountToReceive-amt)

return pathEdges, nil
var e error
if splitNeeded {
e = extendedNoRouteError{err: errNoPathFound, amt: amt}
}
return pathEdges, e
}

// getProbabilityBasedDist converts a weight into a distance that takes into
Expand Down
59 changes: 44 additions & 15 deletions routing/payment_session.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package routing

import (
"errors"
"fmt"

"github.com/btcsuite/btclog"
Expand Down Expand Up @@ -47,6 +48,11 @@ const (
errMissingDependentFeature
)

type extendedNoRouteError struct {
err noRouteError
amt lnwire.MilliSatoshi
}

var (
// DefaultShardMinAmt is the default amount beyond which we won't try to
// further split the payment if no route is found. It is the minimum
Expand Down Expand Up @@ -83,6 +89,20 @@ func (e noRouteError) Error() string {
}
}

// Error returns the string representation of the extendedNoRouteError
func (e extendedNoRouteError) Error() string {
switch e.err {
case errNoPathFound:
return fmt.Sprintf("unable to find a path to destination. Try to lower the amount to: %v", e.amt)
default:
return e.err.Error()
}
}

func (e extendedNoRouteError) Unwrap() error {
return e.err
}

// FailureReason converts a path finding error into a payment-level failure.
func (e noRouteError) FailureReason() channeldb.FailureReason {
switch e {
Expand All @@ -104,6 +124,11 @@ func (e noRouteError) FailureReason() channeldb.FailureReason {
}
}

// FailureReason converts a path finding error into a payment-level failure.
func (e extendedNoRouteError) FailureReason() channeldb.FailureReason {
return e.err.FailureReason()
}

// PaymentSession is used during SendPayment attempts to provide routes to
// attempt. It also defines methods to give the PaymentSession additional
// information learned during the previous attempts.
Expand Down Expand Up @@ -268,7 +293,7 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
cleanup()

switch {
case err == errNoPathFound:
case errors.Is(err, errNoPathFound):
// Don't split if this is a legacy payment without mpp
// record.
if p.payment.PaymentAddr == nil {
Expand All @@ -288,22 +313,26 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
return nil, errNoPathFound
}

// This is where the magic happens. If we can't find a
// route, try it for half the amount.
maxAmt /= 2

// Put a lower bound on the minimum shard size.
if maxAmt < p.minShardAmt {
p.log.Debugf("not splitting because minimum "+
"shard amount %v has been reached",
p.minShardAmt)

return nil, errNoPathFound
var e extendedNoRouteError
if errors.As(err, &e) {
maxAmt = e.amt
} else {
// This is where the magic happens. If we can't find a
// route, try it for half the amount.
maxAmt /= 2

// Put a lower bound on the minimum shard size.
if maxAmt < p.minShardAmt {
p.log.Debugf("not splitting because minimum "+
"shard amount %v has been reached",
p.minShardAmt)

return nil, errNoPathFound
}
// Go pathfinding.
continue
}

// Go pathfinding.
continue

// If there isn't enough local bandwidth, there is no point in
// splitting. It won't be possible to create a complete set in
// any case, but the sent out partial payments would be held by
Expand Down
2 changes: 1 addition & 1 deletion routing/unified_policies.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ func (u *unifiedPolicy) getPolicyLocal(amt lnwire.MilliSatoshi,

for _, edge := range u.edges {
// Check valid amount range for the channel.
if !edge.amtInRange(amt) {
if amt > 0 && !edge.amtInRange(amt) {
continue
}

Expand Down

0 comments on commit f64f98b

Please sign in to comment.