diff --git a/lnrpc/routerrpc/config.go b/lnrpc/routerrpc/config.go index 0782c685..1d1a6f8d 100644 --- a/lnrpc/routerrpc/config.go +++ b/lnrpc/routerrpc/config.go @@ -47,6 +47,7 @@ func DefaultConfig() *Config { MinRouteProbability: routing.DefaultMinRouteProbability, PenaltyHalfLife: routing.DefaultPenaltyHalfLife, AttemptCost: routing.DefaultAttemptCost.ToSatoshis(), + AttemptCostPPM: routing.DefaultAttemptCostPPM, MaxMcHistory: routing.DefaultMaxMcHistory, } @@ -62,6 +63,7 @@ func GetRoutingConfig(cfg *Config) *RoutingConfig { AprioriWeight: cfg.AprioriWeight, MinRouteProbability: cfg.MinRouteProbability, AttemptCost: cfg.AttemptCost, + AttemptCostPPM: cfg.AttemptCostPPM, PenaltyHalfLife: cfg.PenaltyHalfLife, MaxMcHistory: cfg.MaxMcHistory, } diff --git a/lnrpc/routerrpc/routing_config.go b/lnrpc/routerrpc/routing_config.go index a6fb2801..dd0fe93d 100644 --- a/lnrpc/routerrpc/routing_config.go +++ b/lnrpc/routerrpc/routing_config.go @@ -32,7 +32,13 @@ type RoutingConfig struct { // AttemptCost is the fixed virtual cost in path finding of a failed // payment attempt. It is used to trade off potentially better routes // against their probability of succeeding. - AttemptCost btcutil.Amount `long:"attemptcost" description:"The (virtual) cost in sats of a failed payment attempt"` + AttemptCost btcutil.Amount `long:"attemptcost" description:"The fixed (virtual) cost in sats of a failed payment attempt"` + + // AttemptCostPPM is the proportional virtual cost in path finding of a + // failed payment attempt. It is used to trade off potentially better + // routes against their probability of succeeding. This parameter is + // expressed in parts per million of the total payment amount. + AttemptCostPPM int64 `long:"attemptcostppm" description:"The proportional (virtual) cost in sats of a failed payment attempt expressed in parts per million of the total payment amount"` // MaxMcHistory defines the maximum number of payment results that // are held on disk by mission control. diff --git a/routing/pathfind.go b/routing/pathfind.go index 13c40072..cfc7afae 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -45,11 +45,23 @@ type pathFinder = func(g *graphParams, r *RestrictParams, []*channeldb.ChannelEdgePolicy, error) var ( - // DefaultAttemptCost is the default virtual cost in path finding of a - // failed payment attempt. It is used to trade off potentially better - // routes against their probability of succeeding. + // DefaultAttemptCost is the default fixed virtual cost in path finding + // of a failed payment attempt. It is used to trade off potentially + // better routes against their probability of succeeding. DefaultAttemptCost = lnwire.NewMSatFromSatoshis(100) + // DefaultAttemptCostPPM is the default proportional virtual cost in + // path finding weight units of executing a payment attempt that fails. + // It is used to trade off potentially better routes against their + // probability of succeeding. This parameter is expressed in parts per + // million of the payment amount. + // + // It is impossible to pick a perfect default value. The current value + // of 0.1% is based on the idea that a transaction fee of 1% is within + // reasonable territory and that a payment shouldn't need more than 10 + // attempts. + DefaultAttemptCostPPM = int64(1000) + // DefaultMinRouteProbability is the default minimum probability for routes // returned from findPath. DefaultMinRouteProbability = float64(0.01) @@ -314,11 +326,17 @@ type RestrictParams struct { // PathFindingConfig defines global parameters that control the trade-off in // path finding between fees and probabiity. type PathFindingConfig struct { - // AttemptCost is the virtual cost in path finding of a failed + // AttemptCost is the fixed virtual cost in path finding of a failed // payment attempt. It is used to trade off potentially better routes // against their probability of succeeding. AttemptCost lnwire.MilliSatoshi + // AttemptCostPPM is the proportional virtual cost in path finding of a + // failed payment attempt. It is used to trade off potentially better + // routes against their probability of succeeding. This parameter is + // expressed in parts per million of the total payment amount. + AttemptCostPPM int64 + // MinProbability defines the minimum success probability of the // returned route. MinProbability float64 @@ -548,6 +566,14 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, // if the cltv limit is MaxUint32. absoluteCltvLimit := uint64(r.CltvLimit) + uint64(finalHtlcExpiry) + // Calculate the absolute attempt cost that is used for probability + // estimation. + absoluteAttemptCost := int64(cfg.AttemptCost) + + int64(amt)*cfg.AttemptCostPPM/1000000 + + log.Debugf("Pathfinding absolute attempt cost: %v sats", + float64(absoluteAttemptCost)/1000) + // processEdge is a helper closure that will be used to make sure edges // satisfy our specific requirements. processEdge := func(fromVertex route.Vertex, @@ -642,7 +668,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, // probability. tempDist := getProbabilityBasedDist( tempWeight, probability, - int64(cfg.AttemptCost), + absoluteAttemptCost, ) // If there is already a best route stored, compare this diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 76c0e68e..9c419403 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -2467,9 +2467,9 @@ func TestProbabilityRouting(t *testing.T) { // to the same total route probability. In both cases the three // hop route should be the best route. The three hop route has a // probability of 0.5 * 0.8 = 0.4. The fee is 5 (chan 10) + 8 - // (chan 11) = 13. Path finding distance should work out to: 13 - // + 10 (attempt penalty) / 0.4 = 38. The two hop route is 25 + - // 10 / 0.7 = 39. + // (chan 11) = 13. The attempt cost is 9 + 1% * 100 = 10. Path + // finding distance should work out to: 13 + 10 (attempt + // penalty) / 0.4 = 38. The two hop route is 25 + 10 / 0.7 = 39. { name: "three hop 1", p10: 0.8, p11: 0.5, p20: 0.7, @@ -2485,6 +2485,21 @@ func TestProbabilityRouting(t *testing.T) { amount: 100, }, + // If a larger amount is sent, the effect of the proportional + // attempt cost becomes more noticeable. This amount in this + // test brings the attempt cost to 9 + 1% * 300 = 12 sat. The + // three hop path finding distance should work out to: 13 + 12 + // (attempt penalty) / 0.4 = 43. The two hop route is 25 + 12 / + // 0.7 = 42. For this higher amount, the two hop route is + // expected to be selected. + { + name: "two hop high amount", + p10: 0.8, p11: 0.5, p20: 0.7, + minProbability: 0.1, + expectedChan: 20, + amount: 300, + }, + // If the probability of the two hop route is increased, its // distance becomes 25 + 10 / 0.85 = 37. This is less than the // three hop route with its distance 38. So with an attempt @@ -2589,7 +2604,8 @@ func testProbabilityRouting(t *testing.T, paymentAmt btcutil.Amount, } ctx.pathFindingConfig = PathFindingConfig{ - AttemptCost: lnwire.NewMSatFromSatoshis(10), + AttemptCost: lnwire.NewMSatFromSatoshis(9), + AttemptCostPPM: 10000, MinProbability: minProbability, } diff --git a/sample-lnd.conf b/sample-lnd.conf index b92f8b76..873a6d6d 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -619,8 +619,12 @@ litecoin.node=ltcd ; probability (default: 1h0m0s) ; routerrpc.penaltyhalflife=2h -; The (virtual) cost in sats of a failed payment attempt (default: 100) -; routerrpc.attemptcost=90 +; The (virtual) fixed cost in sats of a failed payment attempt (default: 100) +; routerrpc.attemptcost=90 + +; The (virtual) proportional cost in ppm of the total amount of a failed payment +; attempt (default: 1000) +; routerrpc.attemptcostppm=900 ; The maximum number of payment results that are held on disk by mission control ; (default: 1000) diff --git a/server.go b/server.go index f2308db8..9fb89aef 100644 --- a/server.go +++ b/server.go @@ -724,14 +724,16 @@ func newServer(cfg *Config, listenAddrs []net.Addr, } srvrLog.Debugf("Instantiating payment session source with config: "+ - "AttemptCost=%v, MinRouteProbability=%v", + "AttemptCost=%v + %v%%, MinRouteProbability=%v", int64(routingConfig.AttemptCost), + float64(routingConfig.AttemptCostPPM)/10000, routingConfig.MinRouteProbability) pathFindingConfig := routing.PathFindingConfig{ AttemptCost: lnwire.NewMSatFromSatoshis( routingConfig.AttemptCost, ), + AttemptCostPPM: routingConfig.AttemptCostPPM, MinProbability: routingConfig.MinRouteProbability, }