routing: local balance check

This commit is contained in:
Joost Jager committed 2019-12-04 09:45:07 +01:00
1 parent c15b0316a2
commit 97344af8f3
7 files changed
+250 -131

No files matched your search

+67 -2
View File
@@ -76,6 +76,10 @@ var (
// errMaxHopsExceeded is returned when a candidate path is found, but
// the length of that path exceeds HopLimit.
errMaxHopsExceeded = errors.New("potential path has too many hops")
// errInsufficientLocalBalance is returned when none of the local
// channels have enough balance for the payment.
errInsufficientBalance = errors.New("insufficient local balance")
)
// edgePolicyWithSource is a helper struct to keep track of the source node
@@ -305,6 +309,50 @@ 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,
g *graphParams, tx *bbolt.Tx) (lnwire.MilliSatoshi, error) {
var max lnwire.MilliSatoshi
cb := func(_ *bbolt.Tx, edgeInfo *channeldb.ChannelEdgeInfo, outEdge,
_ *channeldb.ChannelEdgePolicy) error {
if outEdge == nil {
return nil
}
chanID := outEdge.ChannelID
// Enforce outgoing channel restriction.
if outgoingChan != nil && chanID != *outgoingChan {
return nil
}
bandwidth, ok := g.bandwidthHints[chanID]
// If the bandwidth is not available for whatever reason, don't
// fail the pathfinding early.
if !ok {
max = lnwire.MaxMilliSatoshi
return nil
}
if bandwidth > max {
max = bandwidth
}
return nil
}
// Iterate over all channels of the to node.
err := g.graph.ForEachNodeChannel(tx, node[:], cb)
if err != nil {
return 0, err
}
return max, err
}
// findPath attempts to find a path from the source node within the
// ChannelGraph to the target node that's capable of supporting a payment of
// `amt` value. The current approach implemented is modified version of
@@ -332,7 +380,13 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
"time=%v", nodesVisited, edgesExpanded, timeElapsed)
}()
var err error
// Get source node outside of the pathfinding tx, to prevent a deadlock.
selfNode, err := g.graph.SourceNode()
if err != nil {
return nil, err
}
self := selfNode.PubKeyBytes
tx := g.tx
if tx == nil {
tx, err = g.graph.Database().Begin(false)
@@ -365,6 +419,18 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
}
}
// If we are routing from ourselves, check that we have enough local
// balance available.
if source == self {
max, err := getMaxOutgoingAmt(self, r.OutgoingChannelID, g, tx)
if err != nil {
return nil, err
}
if max < amt {
return nil, errInsufficientBalance
}
}
// First we'll initialize an empty heap which'll help us to quickly
// locate the next edge we should visit next during our graph
// traversal.
@@ -375,7 +441,6 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
additionalEdgesWithSrc := make(map[route.Vertex][]*edgePolicyWithSource)
for vertex, outgoingEdgePolicies := range g.additionalEdges {
// Build reverse lookup to find incoming edges. Needed because
// search is taken place from target to source.
for _, outgoingEdgePolicy := range outgoingEdgePolicies {
+31
View File
@@ -2271,6 +2271,37 @@ func TestRouteToSelf(t *testing.T) {
ctx.assertPath(path, []uint64{1, 3, 2})
}
// TestInsufficientBalance tests that a dedicated error is returned for
// insufficient local balance.
func TestInsufficientBalance(t *testing.T) {
t.Parallel()
testChannels := []*testChannel{
symmetricTestChannel("source", "target", 100000, &testChannelPolicy{
Expiry: 144,
FeeBaseMsat: 500,
}, 1),
}
ctx := newPathFindingTestContext(t, testChannels, "source")
defer ctx.cleanup()
paymentAmt := lnwire.NewMSatFromSatoshis(100)
target := ctx.keyFromAlias("target")
ctx.graphParams.bandwidthHints = map[uint64]lnwire.MilliSatoshi{
1: lnwire.NewMSatFromSatoshis(50),
}
// Find the best path to self. We expect this to be source->a->source,
// because a charges the lowest forwarding fee.
_, err := ctx.findPath(target, paymentAmt)
if err != errInsufficientBalance {
t.Fatalf("expected insufficient balance error, but got: %v",
err)
}
}
type pathFindingTestContext struct {
t *testing.T
graphParams graphParams
+3
View File
@@ -194,6 +194,9 @@ func errorToPaymentFailure(err error) channeldb.FailureReason {
errPrebuiltRouteTried:
return channeldb.FailureReasonNoRoute
case errInsufficientBalance:
return channeldb.FailureReasonInsufficientBalance
}
return channeldb.FailureReasonError