routing: use capacity in pathfinding

Extends the pathfinder with a capacity argument for later usage.

In tests, the inserted testCapacity has no effect, but will be used
later to estimate reduced probabilities from it.
This commit is contained in:
bitromortac
2022-05-26 10:18:12 +02:00
parent 2b6308a61f
commit 516e3a8cca
11 changed files with 70 additions and 37 deletions

View File

@ -103,7 +103,7 @@ type MissionControl interface {
// GetProbability is expected to return the success probability of a // GetProbability is expected to return the success probability of a
// payment from fromNode to toNode. // payment from fromNode to toNode.
GetProbability(fromNode, toNode route.Vertex, GetProbability(fromNode, toNode route.Vertex,
amt lnwire.MilliSatoshi) float64 amt lnwire.MilliSatoshi, capacity btcutil.Amount) float64
// ResetHistory resets the history of MissionControl returning it to a // ResetHistory resets the history of MissionControl returning it to a
// state as if no payment attempts have been made. // state as if no payment attempts have been made.
@ -258,7 +258,8 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context,
restrictions := &routing.RestrictParams{ restrictions := &routing.RestrictParams{
FeeLimit: feeLimit, FeeLimit: feeLimit,
ProbabilitySource: func(fromNode, toNode route.Vertex, ProbabilitySource: func(fromNode, toNode route.Vertex,
amt lnwire.MilliSatoshi) float64 { amt lnwire.MilliSatoshi,
capacity btcutil.Amount) float64 {
if _, ok := ignoredNodes[fromNode]; ok { if _, ok := ignoredNodes[fromNode]; ok {
return 0 return 0
@ -277,7 +278,7 @@ func (r *RouterBackend) QueryRoutes(ctx context.Context,
} }
return r.MissionControl.GetProbability( return r.MissionControl.GetProbability(
fromNode, toNode, amt, fromNode, toNode, amt, capacity,
) )
}, },
DestCustomRecords: record.CustomSet(in.DestCustomRecords), DestCustomRecords: record.CustomSet(in.DestCustomRecords),
@ -362,7 +363,7 @@ func (r *RouterBackend) getSuccessProbability(rt *route.Route) float64 {
toNode := hop.PubKeyBytes toNode := hop.PubKeyBytes
probability := r.MissionControl.GetProbability( probability := r.MissionControl.GetProbability(
fromNode, toNode, amtToFwd, fromNode, toNode, amtToFwd, 0,
) )
successProb *= probability successProb *= probability

View File

@ -145,18 +145,18 @@ func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool,
} }
if restrictions.ProbabilitySource(route.Vertex{2}, if restrictions.ProbabilitySource(route.Vertex{2},
route.Vertex{1}, 0, route.Vertex{1}, 0, 0,
) != 0 { ) != 0 {
t.Fatal("expecting 0% probability for ignored edge") t.Fatal("expecting 0% probability for ignored edge")
} }
if restrictions.ProbabilitySource(ignoreNodeVertex, if restrictions.ProbabilitySource(ignoreNodeVertex,
route.Vertex{6}, 0, route.Vertex{6}, 0, 0,
) != 0 { ) != 0 {
t.Fatal("expecting 0% probability for ignored node") t.Fatal("expecting 0% probability for ignored node")
} }
if restrictions.ProbabilitySource(node1, node2, 0) != 0 { if restrictions.ProbabilitySource(node1, node2, 0, 0) != 0 {
t.Fatal("expecting 0% probability for ignored pair") t.Fatal("expecting 0% probability for ignored pair")
} }
@ -181,7 +181,7 @@ func testQueryRoutes(t *testing.T, useMissionControl bool, useMsat bool,
expectedProb = testMissionControlProb expectedProb = testMissionControlProb
} }
if restrictions.ProbabilitySource(route.Vertex{4}, if restrictions.ProbabilitySource(route.Vertex{4},
route.Vertex{5}, 0, route.Vertex{5}, 0, 0,
) != expectedProb { ) != expectedProb {
t.Fatal("expecting 100% probability") t.Fatal("expecting 100% probability")
} }
@ -239,7 +239,7 @@ type mockMissionControl struct {
} }
func (m *mockMissionControl) GetProbability(fromNode, toNode route.Vertex, func (m *mockMissionControl) GetProbability(fromNode, toNode route.Vertex,
amt lnwire.MilliSatoshi) float64 { amt lnwire.MilliSatoshi, capacity btcutil.Amount) float64 {
return testMissionControlProb return testMissionControlProb
} }

View File

@ -710,7 +710,7 @@ func (s *Server) QueryProbability(ctx context.Context,
amt := lnwire.MilliSatoshi(req.AmtMsat) amt := lnwire.MilliSatoshi(req.AmtMsat)
mc := s.cfg.RouterBackend.MissionControl mc := s.cfg.RouterBackend.MissionControl
prob := mc.GetProbability(fromNode, toNode, amt) prob := mc.GetProbability(fromNode, toNode, amt, 0)
history := mc.GetPairHistorySnapshot(fromNode, toNode) history := mc.GetPairHistorySnapshot(fromNode, toNode)
return &QueryProbabilityResponse{ return &QueryProbabilityResponse{

View File

@ -6,6 +6,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
@ -333,7 +334,7 @@ func (m *MissionControl) ResetHistory() error {
// GetProbability is expected to return the success probability of a payment // GetProbability is expected to return the success probability of a payment
// from fromNode along edge. // from fromNode along edge.
func (m *MissionControl) GetProbability(fromNode, toNode route.Vertex, func (m *MissionControl) GetProbability(fromNode, toNode route.Vertex,
amt lnwire.MilliSatoshi) float64 { amt lnwire.MilliSatoshi, capacity btcutil.Amount) float64 {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
@ -346,7 +347,9 @@ func (m *MissionControl) GetProbability(fromNode, toNode route.Vertex,
return m.estimator.getLocalPairProbability(now, results, toNode) return m.estimator.getLocalPairProbability(now, results, toNode)
} }
return m.estimator.getPairProbability(now, results, toNode, amt) return m.estimator.getPairProbability(
now, results, toNode, amt, capacity,
)
} }
// GetHistorySnapshot takes a snapshot from the current mission control state // GetHistorySnapshot takes a snapshot from the current mission control state

View File

@ -112,7 +112,7 @@ func (ctx *mcTestContext) restartMc() {
func (ctx *mcTestContext) expectP(amt lnwire.MilliSatoshi, expected float64) { func (ctx *mcTestContext) expectP(amt lnwire.MilliSatoshi, expected float64) {
ctx.t.Helper() ctx.t.Helper()
p := ctx.mc.GetProbability(mcTestNode1, mcTestNode2, amt) p := ctx.mc.GetProbability(mcTestNode1, mcTestNode2, amt, testCapacity)
if p != expected { if p != expected {
ctx.t.Fatalf("expected probability %v but got %v", expected, p) ctx.t.Fatalf("expected probability %v but got %v", expected, p)
} }
@ -148,9 +148,11 @@ func TestMissionControl(t *testing.T) {
testTime := time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC) testTime := time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC)
// For local channels, we expect a higher probability than our a prior // For local channels, we expect a higher probability than our apriori
// test probability. // test probability.
selfP := ctx.mc.GetProbability(mcTestSelf, mcTestNode1, 100) selfP := ctx.mc.GetProbability(
mcTestSelf, mcTestNode1, 100, testCapacity,
)
if selfP != prevSuccessProbability { if selfP != prevSuccessProbability {
t.Fatalf("expected prev success prob for untried local chans") t.Fatalf("expected prev success prob for untried local chans")
} }

View File

@ -5,6 +5,7 @@ import (
"sync" "sync"
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/htlcswitch"
@ -139,7 +140,7 @@ func (m *mockMissionControlOld) ReportPaymentSuccess(paymentID uint64,
} }
func (m *mockMissionControlOld) GetProbability(fromNode, toNode route.Vertex, func (m *mockMissionControlOld) GetProbability(fromNode, toNode route.Vertex,
amt lnwire.MilliSatoshi) float64 { amt lnwire.MilliSatoshi, capacity btcutil.Amount) float64 {
return 0 return 0
} }
@ -650,9 +651,9 @@ func (m *mockMissionControl) ReportPaymentSuccess(paymentID uint64,
} }
func (m *mockMissionControl) GetProbability(fromNode, toNode route.Vertex, func (m *mockMissionControl) GetProbability(fromNode, toNode route.Vertex,
amt lnwire.MilliSatoshi) float64 { amt lnwire.MilliSatoshi, capacity btcutil.Amount) float64 {
args := m.Called(fromNode, toNode, amt) args := m.Called(fromNode, toNode, amt, capacity)
return args.Get(0).(float64) return args.Get(0).(float64)
} }

View File

@ -7,6 +7,7 @@ import (
"math" "math"
"time" "time"
"github.com/btcsuite/btcd/btcutil"
sphinx "github.com/lightningnetwork/lightning-onion" sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/feature" "github.com/lightningnetwork/lnd/feature"
@ -306,7 +307,7 @@ type RestrictParams struct {
// ProbabilitySource is a callback that is expected to return the // ProbabilitySource is a callback that is expected to return the
// success probability of traversing the channel from the node. // success probability of traversing the channel from the node.
ProbabilitySource func(route.Vertex, route.Vertex, ProbabilitySource func(route.Vertex, route.Vertex,
lnwire.MilliSatoshi) float64 lnwire.MilliSatoshi, btcutil.Amount) float64
// FeeLimit is a maximum fee amount allowed to be used on the path from // FeeLimit is a maximum fee amount allowed to be used on the path from
// the source to the target. // the source to the target.
@ -639,6 +640,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
// Request the success probability for this edge. // Request the success probability for this edge.
edgeProbability := r.ProbabilitySource( edgeProbability := r.ProbabilitySource(
fromVertex, toNodeDist.node, amountToSend, fromVertex, toNodeDist.node, amountToSend,
edge.capacity,
) )
log.Trace(newLogClosure(func() string { log.Trace(newLogClosure(func() string {

View File

@ -105,7 +105,9 @@ var (
// noProbabilitySource is used in testing to return the same probability 1 for // noProbabilitySource is used in testing to return the same probability 1 for
// all edges. // all edges.
func noProbabilitySource(route.Vertex, route.Vertex, lnwire.MilliSatoshi) float64 { func noProbabilitySource(route.Vertex, route.Vertex, lnwire.MilliSatoshi,
btcutil.Amount) float64 {
return 1 return 1
} }
@ -2796,8 +2798,9 @@ func testProbabilityRouting(t *testing.T, useCache bool,
target := ctx.testGraphInstance.aliasMap["target"] target := ctx.testGraphInstance.aliasMap["target"]
// Configure a probability source with the test parameters. // Configure a probability source with the test parameters.
ctx.restrictParams.ProbabilitySource = func(fromNode, toNode route.Vertex, ctx.restrictParams.ProbabilitySource = func(fromNode,
amt lnwire.MilliSatoshi) float64 { toNode route.Vertex, amt lnwire.MilliSatoshi,
capacity btcutil.Amount) float64 {
if amt == 0 { if amt == 0 {
t.Fatal("expected non-zero amount") t.Fatal("expected non-zero amount")
@ -2878,8 +2881,9 @@ func runEqualCostRouteSelection(t *testing.T, useCache bool) {
paymentAmt := lnwire.NewMSatFromSatoshis(100) paymentAmt := lnwire.NewMSatFromSatoshis(100)
target := ctx.testGraphInstance.aliasMap["target"] target := ctx.testGraphInstance.aliasMap["target"]
ctx.restrictParams.ProbabilitySource = func(fromNode, toNode route.Vertex, ctx.restrictParams.ProbabilitySource = func(fromNode,
amt lnwire.MilliSatoshi) float64 { toNode route.Vertex, amt lnwire.MilliSatoshi,
capacity btcutil.Amount) float64 {
switch { switch {
case fromNode == alias["source"] && toNode == alias["a"]: case fromNode == alias["source"] && toNode == alias["a"]:

View File

@ -5,6 +5,7 @@ import (
"math" "math"
"time" "time"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/routing/route"
) )
@ -150,8 +151,8 @@ func (p *probabilityEstimator) getWeight(age time.Duration) float64 {
// toNode based on historical payment outcomes for the from node. Those outcomes // toNode based on historical payment outcomes for the from node. Those outcomes
// are passed in via the results parameter. // are passed in via the results parameter.
func (p *probabilityEstimator) getPairProbability( func (p *probabilityEstimator) getPairProbability(
now time.Time, results NodeResults, now time.Time, results NodeResults, toNode route.Vertex,
toNode route.Vertex, amt lnwire.MilliSatoshi) float64 { amt lnwire.MilliSatoshi, capacity btcutil.Amount) float64 {
nodeProbability := p.getNodeProbability(now, results, amt) nodeProbability := p.getNodeProbability(now, results, amt)

View File

@ -4,6 +4,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/routing/route"
) )
@ -23,6 +24,9 @@ const (
aprioriHopProb = 0.6 aprioriHopProb = 0.6
aprioriWeight = 0.75 aprioriWeight = 0.75
aprioriPrevSucProb = 0.95 aprioriPrevSucProb = 0.95
// testCapacity is used to define a capacity for some channels.
testCapacity = btcutil.Amount(100_000)
) )
type estimatorTestContext struct { type estimatorTestContext struct {
@ -53,7 +57,8 @@ func newEstimatorTestContext(t *testing.T) *estimatorTestContext {
// assertPairProbability asserts that the calculated success probability is // assertPairProbability asserts that the calculated success probability is
// correct. // correct.
func (c *estimatorTestContext) assertPairProbability(now time.Time, func (c *estimatorTestContext) assertPairProbability(now time.Time,
toNode byte, amt lnwire.MilliSatoshi, expectedProb float64) { toNode byte, amt lnwire.MilliSatoshi, capacity btcutil.Amount,
expectedProb float64) {
c.t.Helper() c.t.Helper()
@ -64,7 +69,9 @@ func (c *estimatorTestContext) assertPairProbability(now time.Time,
const tolerance = 0.01 const tolerance = 0.01
p := c.estimator.getPairProbability(now, results, route.Vertex{toNode}, amt) p := c.estimator.getPairProbability(
now, results, route.Vertex{toNode}, amt, capacity,
)
diff := p - expectedProb diff := p - expectedProb
if diff > tolerance || diff < -tolerance { if diff > tolerance || diff < -tolerance {
c.t.Fatalf("expected probability %v for node %v, but got %v", c.t.Fatalf("expected probability %v for node %v, but got %v",
@ -77,7 +84,7 @@ func (c *estimatorTestContext) assertPairProbability(now time.Time,
func TestProbabilityEstimatorNoResults(t *testing.T) { func TestProbabilityEstimatorNoResults(t *testing.T) {
ctx := newEstimatorTestContext(t) ctx := newEstimatorTestContext(t)
ctx.assertPairProbability(testTime, 0, 0, aprioriHopProb) ctx.assertPairProbability(testTime, 0, 0, testCapacity, aprioriHopProb)
} }
// TestProbabilityEstimatorOneSuccess tests the probability estimation for nodes // TestProbabilityEstimatorOneSuccess tests the probability estimation for nodes
@ -94,13 +101,15 @@ func TestProbabilityEstimatorOneSuccess(t *testing.T) {
// Because of the previous success, this channel keep reporting a high // Because of the previous success, this channel keep reporting a high
// probability. // probability.
ctx.assertPairProbability( ctx.assertPairProbability(
testTime, node1, 100, aprioriPrevSucProb, testTime, node1, 100, testCapacity, aprioriPrevSucProb,
) )
// Untried channels are also influenced by the success. With a // Untried channels are also influenced by the success. With a
// aprioriWeight of 0.75, the a priori probability is assigned weight 3. // aprioriWeight of 0.75, the a priori probability is assigned weight 3.
expectedP := (3*aprioriHopProb + 1*aprioriPrevSucProb) / 4 expectedP := (3*aprioriHopProb + 1*aprioriPrevSucProb) / 4
ctx.assertPairProbability(testTime, untriedNode, 100, expectedP) ctx.assertPairProbability(
testTime, untriedNode, 100, testCapacity, expectedP,
)
} }
// TestProbabilityEstimatorOneFailure tests the probability estimation for nodes // TestProbabilityEstimatorOneFailure tests the probability estimation for nodes
@ -119,11 +128,15 @@ func TestProbabilityEstimatorOneFailure(t *testing.T) {
// the failure after one hour is 0.5. This makes the node probability // the failure after one hour is 0.5. This makes the node probability
// 0.51: // 0.51:
expectedNodeProb := (3*aprioriHopProb + 0.5*0) / 3.5 expectedNodeProb := (3*aprioriHopProb + 0.5*0) / 3.5
ctx.assertPairProbability(testTime, untriedNode, 100, expectedNodeProb) ctx.assertPairProbability(
testTime, untriedNode, 100, testCapacity, expectedNodeProb,
)
// The pair probability decays back to the node probability. With the // The pair probability decays back to the node probability. With the
// weight at 0.5, we expected a pair probability of 0.5 * 0.51 = 0.25. // weight at 0.5, we expected a pair probability of 0.5 * 0.51 = 0.25.
ctx.assertPairProbability(testTime, node1, 100, expectedNodeProb/2) ctx.assertPairProbability(
testTime, node1, 100, testCapacity, expectedNodeProb/2,
)
} }
// TestProbabilityEstimatorMix tests the probability estimation for nodes for // TestProbabilityEstimatorMix tests the probability estimation for nodes for
@ -147,7 +160,9 @@ func TestProbabilityEstimatorMix(t *testing.T) {
// We expect the probability for a previously successful channel to // We expect the probability for a previously successful channel to
// remain high. // remain high.
ctx.assertPairProbability(testTime, node1, 100, prevSuccessProbability) ctx.assertPairProbability(
testTime, node1, 100, testCapacity, prevSuccessProbability,
)
// For an untried node, we expected the node probability to be returned. // For an untried node, we expected the node probability to be returned.
// This is a weighted average of the results above and the a priori // This is a weighted average of the results above and the a priori
@ -155,9 +170,13 @@ func TestProbabilityEstimatorMix(t *testing.T) {
expectedNodeProb := (3*aprioriHopProb + 1*prevSuccessProbability) / expectedNodeProb := (3*aprioriHopProb + 1*prevSuccessProbability) /
(3 + 1 + 0.25 + 0.125) (3 + 1 + 0.25 + 0.125)
ctx.assertPairProbability(testTime, untriedNode, 100, expectedNodeProb) ctx.assertPairProbability(
testTime, untriedNode, 100, testCapacity, expectedNodeProb,
)
// For the previously failed connection with node 1, we expect 0.75 * // For the previously failed connection with node 1, we expect 0.75 *
// the node probability = 0.47. // the node probability = 0.47.
ctx.assertPairProbability(testTime, node2, 100, expectedNodeProb*0.75) ctx.assertPairProbability(
testTime, node2, 100, testCapacity, expectedNodeProb*0.75,
)
} }

View File

@ -227,7 +227,7 @@ type MissionController interface {
// GetProbability is expected to return the success probability of a // GetProbability is expected to return the success probability of a
// payment from fromNode along edge. // payment from fromNode along edge.
GetProbability(fromNode, toNode route.Vertex, GetProbability(fromNode, toNode route.Vertex,
amt lnwire.MilliSatoshi) float64 amt lnwire.MilliSatoshi, capacity btcutil.Amount) float64
} }
// FeeSchema is the set fee configuration for a Lightning Node on the network. // FeeSchema is the set fee configuration for a Lightning Node on the network.