Merge pull request #4198 from joostjager/mpp-precheck
routing: payment splitting pre-check
This commit is contained in:
commit
3b8ddece41
@ -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.
|
||||
|
@ -107,7 +107,7 @@ func onePathGraph(g *mockGraph) {
|
||||
g.addChannel(chanIm1Target, targetNodeID, im1NodeID, 100000)
|
||||
}
|
||||
|
||||
func twoPathGraph(g *mockGraph) {
|
||||
func twoPathGraph(g *mockGraph, capacityOut, capacityIn btcutil.Amount) {
|
||||
// Create the following network of nodes:
|
||||
// source -> intermediate1 -> target
|
||||
// source -> intermediate2 -> target
|
||||
@ -120,10 +120,10 @@ func twoPathGraph(g *mockGraph) {
|
||||
intermediate2 := newMockNode(im2NodeID)
|
||||
g.addNode(intermediate2)
|
||||
|
||||
g.addChannel(chanSourceIm1, sourceNodeID, im1NodeID, 200000)
|
||||
g.addChannel(chanSourceIm2, sourceNodeID, im2NodeID, 200000)
|
||||
g.addChannel(chanIm1Target, targetNodeID, im1NodeID, 100000)
|
||||
g.addChannel(chanIm2Target, targetNodeID, im2NodeID, 100000)
|
||||
g.addChannel(chanSourceIm1, sourceNodeID, im1NodeID, capacityOut)
|
||||
g.addChannel(chanSourceIm2, sourceNodeID, im2NodeID, capacityOut)
|
||||
g.addChannel(chanIm1Target, targetNodeID, im1NodeID, capacityIn)
|
||||
g.addChannel(chanIm2Target, targetNodeID, im2NodeID, capacityIn)
|
||||
}
|
||||
|
||||
var mppTestCases = []mppSendTestCase{
|
||||
@ -136,8 +136,10 @@ var mppTestCases = []mppSendTestCase{
|
||||
// too. Mpp payment complete.
|
||||
{
|
||||
|
||||
name: "sufficient inbound",
|
||||
graph: twoPathGraph,
|
||||
name: "sufficient inbound",
|
||||
graph: func(g *mockGraph) {
|
||||
twoPathGraph(g, 200000, 100000)
|
||||
},
|
||||
amt: 70000,
|
||||
expectedAttempts: 5,
|
||||
expectedSuccesses: []expectedHtlcSuccess{
|
||||
@ -155,8 +157,10 @@ var mppTestCases = []mppSendTestCase{
|
||||
|
||||
// Test that a cap on the max htlcs makes it impossible to pay.
|
||||
{
|
||||
name: "no splitting",
|
||||
graph: twoPathGraph,
|
||||
name: "no splitting",
|
||||
graph: func(g *mockGraph) {
|
||||
twoPathGraph(g, 200000, 100000)
|
||||
},
|
||||
amt: 70000,
|
||||
expectedAttempts: 2,
|
||||
expectedSuccesses: []expectedHtlcSuccess{},
|
||||
@ -188,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.
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user