From 3349d517aade07a621ad28d8a9de5a27917b5d50 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 13 May 2019 17:00:35 +0200 Subject: [PATCH] routing: add min probability to path finding This commit adds a new restriction to pathfinding that allows returning only routes with a minimum success probability. --- routing/pathfind.go | 15 ++++++++++++ routing/pathfind_test.go | 49 ++++++++++++++++++++++++++++++++-------- 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/routing/pathfind.go b/routing/pathfind.go index 4115bcd4..4feb7265 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -46,6 +46,10 @@ var ( // off potentially better routes against their probability of // succeeding. DefaultPaymentAttemptPenalty = lnwire.NewMSatFromSatoshis(100) + + // DefaultMinProbability is the default minimum probability for routes + // returned from findPath. + DefaultMinProbability = float64(0.01) ) // edgePolicyWithSource is a helper struct to keep track of the source node @@ -258,6 +262,10 @@ type RestrictParams struct { // off potentially better routes against their probability of // succeeding. PaymentAttemptPenalty lnwire.MilliSatoshi + + // MinProbability defines the minimum success probability of the + // returned route. + MinProbability float64 } // findPath attempts to find a path from the source node within the @@ -465,6 +473,13 @@ func findPath(g *graphParams, r *RestrictParams, source, target route.Vertex, // of the route must succeed. probability := toNodeDist.probability * edgeProbability + // If the probability is below the specified lower bound, we can + // abandon this direction. Adding further nodes can only lower + // the probability more. + if probability < r.MinProbability { + return + } + // By adding fromNode in the route, there will be an extra // weight composed of the fee that this node will charge and // the amount that will be locked for timeLockDelta blocks in diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 280dd64a..433265b8 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -2025,9 +2025,10 @@ func TestProbabilityRouting(t *testing.T) { t.Parallel() testCases := []struct { - name string - p10, p11, p20 float64 - expectedChan uint64 + name string + p10, p11, p20 float64 + minProbability float64 + expectedChan uint64 }{ // Test two variations with probabilities that should multiply // to the same total route probability. In both cases the three @@ -2039,12 +2040,14 @@ func TestProbabilityRouting(t *testing.T) { { name: "three hop 1", p10: 0.8, p11: 0.5, p20: 0.7, - expectedChan: 10, + minProbability: 0.1, + expectedChan: 10, }, { name: "three hop 2", p10: 0.5, p11: 0.8, p20: 0.7, - expectedChan: 10, + minProbability: 0.1, + expectedChan: 10, }, // If the probability of the two hop route is increased, its @@ -2055,20 +2058,42 @@ func TestProbabilityRouting(t *testing.T) { { name: "two hop higher cost", p10: 0.5, p11: 0.8, p20: 0.85, - expectedChan: 20, + minProbability: 0.1, + expectedChan: 20, + }, + + // If the same probabilities are used with a probability lower bound of + // 0.5, we expect the three hop route with probability 0.4 to be + // excluded and the two hop route to be picked. + { + name: "probability limit", + p10: 0.8, p11: 0.5, p20: 0.7, + minProbability: 0.5, + expectedChan: 20, + }, + + // With a probability limit above the probability of both routes, we + // expect no route to be returned. This expectation is signaled by using + // expected channel 0. + { + name: "probability limit no routes", + p10: 0.8, p11: 0.5, p20: 0.7, + minProbability: 0.8, + expectedChan: 0, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { testProbabilityRouting( - t, tc.p10, tc.p11, tc.p20, tc.expectedChan, + t, tc.p10, tc.p11, tc.p20, + tc.minProbability, tc.expectedChan, ) }) } } -func testProbabilityRouting(t *testing.T, p10, p11, p20 float64, +func testProbabilityRouting(t *testing.T, p10, p11, p20, minProbability float64, expectedChan uint64) { t.Parallel() @@ -2079,7 +2104,6 @@ func testProbabilityRouting(t *testing.T, p10, p11, p20 float64, testChannels := []*testChannel{ symmetricTestChannel("roasbeef", "a1", 100000, &testChannelPolicy{}), symmetricTestChannel("roasbeef", "b", 100000, &testChannelPolicy{}), - symmetricTestChannel("roasbeef", "c", 100000, &testChannelPolicy{}), symmetricTestChannel("a1", "a2", 100000, &testChannelPolicy{ Expiry: 144, FeeBaseMsat: lnwire.NewMSatFromSatoshis(5), @@ -2135,9 +2159,16 @@ func testProbabilityRouting(t *testing.T, p10, p11, p20 float64, FeeLimit: noFeeLimit, ProbabilitySource: probabilitySource, PaymentAttemptPenalty: lnwire.NewMSatFromSatoshis(10), + MinProbability: minProbability, }, sourceVertex, target, paymentAmt, ) + if expectedChan == 0 { + if err == nil || !IsError(err, ErrNoPathFound) { + t.Fatalf("expected no path found, but got %v", err) + } + return + } if err != nil { t.Fatal(err) }