routing: payment splitting pre-check

This commit reverts cb4cd49dc8 to bring
back the insufficient local balance failure.

Distinguishing betweeen this failure and a regular "no route" failure
prevents meaningless htlcs from being sent out.
This commit is contained in:
Joost Jager 2020-04-16 10:45:00 +02:00
parent f44a9e1d4c
commit 7fc1938f10
No known key found for this signature in database
GPG Key ID: A61B9D4C393C59C7
5 changed files with 63 additions and 18 deletions

View File

@ -116,9 +116,6 @@ const (
// FailureReasonInsufficientBalance indicates that we didn't have enough
// balance to complete the payment.
//
// This reason isn't assigned anymore, but may still exist for older
// payments.
FailureReasonInsufficientBalance FailureReason = 4
// TODO(halseth): cancel state.

View File

@ -192,6 +192,19 @@ var mppTestCases = []mppSendTestCase{
expectedFailure: true,
maxShards: 1000,
},
// Test that no attempts are made if the total local balance is
// insufficient.
{
name: "insufficient total balance",
graph: func(g *mockGraph) {
twoPathGraph(g, 100000, 500000)
},
amt: 300000,
expectedAttempts: 0,
expectedFailure: true,
maxShards: 10,
},
}
// TestMppSend tests that a payment can be completed using multiple shards.

View File

@ -326,13 +326,14 @@ type PathFindingConfig struct {
MinProbability float64
}
// getMaxOutgoingAmt returns the maximum available balance in any of the
// channels of the given node.
func getMaxOutgoingAmt(node route.Vertex, outgoingChan *uint64,
// getOutgoingBalance returns the maximum available balance in any of the
// channels of the given node. The second return parameters is the total
// available balance.
func getOutgoingBalance(node route.Vertex, outgoingChan *uint64,
bandwidthHints map[uint64]lnwire.MilliSatoshi,
g routingGraph) (lnwire.MilliSatoshi, error) {
g routingGraph) (lnwire.MilliSatoshi, lnwire.MilliSatoshi, error) {
var max lnwire.MilliSatoshi
var max, total lnwire.MilliSatoshi
cb := func(edgeInfo *channeldb.ChannelEdgeInfo, outEdge,
_ *channeldb.ChannelEdgePolicy) error {
@ -349,26 +350,30 @@ func getMaxOutgoingAmt(node route.Vertex, outgoingChan *uint64,
bandwidth, ok := bandwidthHints[chanID]
// If the bandwidth is not available for whatever reason, don't
// fail the pathfinding early.
// If the bandwidth is not available, use the channel capacity.
// This can happen when a channel is added to the graph after
// we've already queried the bandwidth hints.
if !ok {
max = lnwire.MaxMilliSatoshi
return nil
bandwidth = lnwire.NewMSatFromSatoshis(
edgeInfo.Capacity,
)
}
if bandwidth > max {
max = bandwidth
}
total += bandwidth
return nil
}
// Iterate over all channels of the to node.
err := g.forEachNodeChannel(node, cb)
if err != nil {
return 0, err
return 0, 0, err
}
return max, err
return max, total, err
}
// findPath attempts to find a path from the source node within the ChannelGraph
@ -447,12 +452,21 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
self := g.graph.sourceNode()
if source == self {
max, err := getMaxOutgoingAmt(
max, total, err := getOutgoingBalance(
self, r.OutgoingChannelID, g.bandwidthHints, g.graph,
)
if err != nil {
return nil, err
}
// If the total outgoing balance isn't sufficient, it will be
// impossible to complete the payment.
if total < amt {
return nil, errInsufficientBalance
}
// 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
}

View File

@ -1768,7 +1768,7 @@ func TestPathInsufficientCapacity(t *testing.T) {
noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, payAmt, 0,
)
if err != errNoPathFound {
if err != errInsufficientBalance {
t.Fatalf("graph shouldn't be able to support payment: %v", err)
}
}
@ -2790,6 +2790,7 @@ type pathFindingTestContext struct {
t *testing.T
graph *channeldb.ChannelGraph
restrictParams RestrictParams
bandwidthHints map[uint64]lnwire.MilliSatoshi
pathFindingConfig PathFindingConfig
testGraphInstance *testGraphInstance
source route.Vertex
@ -2844,8 +2845,8 @@ func (c *pathFindingTestContext) findPath(target route.Vertex,
error) {
return dbFindPath(
c.graph, nil, nil, &c.restrictParams, &c.pathFindingConfig,
c.source, target, amt, 0,
c.graph, nil, c.bandwidthHints, &c.restrictParams,
&c.pathFindingConfig, c.source, target, amt, 0,
)
}

View File

@ -30,6 +30,10 @@ const (
// not exist in the graph.
errNoPathFound
// errInsufficientLocalBalance is returned when none of the local
// channels have enough balance for the payment.
errInsufficientBalance
// errEmptyPaySession is returned when the empty payment session is
// queried for a route.
errEmptyPaySession
@ -57,6 +61,9 @@ func (e noRouteError) Error() string {
case errEmptyPaySession:
return "empty payment session"
case errInsufficientBalance:
return "insufficient local balance"
default:
return "unknown no-route error"
}
@ -73,6 +80,9 @@ func (e noRouteError) FailureReason() channeldb.FailureReason {
return channeldb.FailureReasonNoRoute
case errInsufficientBalance:
return channeldb.FailureReasonInsufficientBalance
default:
return channeldb.FailureReasonError
}
@ -278,6 +288,16 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
// 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
// the receiver until the mpp timeout.
case err == errInsufficientBalance:
p.log.Debug("not splitting because local balance " +
"is insufficient")
return nil, err
case err != nil:
return nil, err
}