Merge pull request #4602 from joostjager/proportional-attempt-cost
routing: proportional attempt cost
This commit is contained in:
8 files changed
+99
-37
No files matched your search
@@ -46,9 +46,9 @@ func DefaultConfig() *Config {
|
|||||||
AprioriWeight: routing.DefaultAprioriWeight,
|
AprioriWeight: routing.DefaultAprioriWeight,
|
||||||
MinRouteProbability: routing.DefaultMinRouteProbability,
|
MinRouteProbability: routing.DefaultMinRouteProbability,
|
||||||
PenaltyHalfLife: routing.DefaultPenaltyHalfLife,
|
PenaltyHalfLife: routing.DefaultPenaltyHalfLife,
|
||||||
AttemptCost: routing.DefaultPaymentAttemptPenalty.
|
AttemptCost: routing.DefaultAttemptCost.ToSatoshis(),
|
||||||
ToSatoshis(),
|
AttemptCostPPM: routing.DefaultAttemptCostPPM,
|
||||||
MaxMcHistory: routing.DefaultMaxMcHistory,
|
MaxMcHistory: routing.DefaultMaxMcHistory,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Config{
|
return &Config{
|
||||||
@@ -63,6 +63,7 @@ func GetRoutingConfig(cfg *Config) *RoutingConfig {
|
|||||||
AprioriWeight: cfg.AprioriWeight,
|
AprioriWeight: cfg.AprioriWeight,
|
||||||
MinRouteProbability: cfg.MinRouteProbability,
|
MinRouteProbability: cfg.MinRouteProbability,
|
||||||
AttemptCost: cfg.AttemptCost,
|
AttemptCost: cfg.AttemptCost,
|
||||||
|
AttemptCostPPM: cfg.AttemptCostPPM,
|
||||||
PenaltyHalfLife: cfg.PenaltyHalfLife,
|
PenaltyHalfLife: cfg.PenaltyHalfLife,
|
||||||
MaxMcHistory: cfg.MaxMcHistory,
|
MaxMcHistory: cfg.MaxMcHistory,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,10 +29,16 @@ type RoutingConfig struct {
|
|||||||
// channel is back at 50% probability.
|
// channel is back at 50% probability.
|
||||||
PenaltyHalfLife time.Duration `long:"penaltyhalflife" description:"Defines the duration after which a penalized node or channel is back at 50% probability"`
|
PenaltyHalfLife time.Duration `long:"penaltyhalflife" description:"Defines the duration after which a penalized node or channel is back at 50% probability"`
|
||||||
|
|
||||||
// AttemptCost is the virtual cost in path finding weight units of
|
// AttemptCost is the fixed virtual cost in path finding of a failed
|
||||||
// executing a payment attempt that fails. It is used to trade off
|
// payment attempt. It is used to trade off potentially better routes
|
||||||
// potentially better routes against their probability of succeeding.
|
// 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
|
// MaxMcHistory defines the maximum number of payment results that
|
||||||
// are held on disk by mission control.
|
// are held on disk by mission control.
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ func newIntegratedRoutingContext(t *testing.T) *integratedRoutingContext {
|
|||||||
},
|
},
|
||||||
|
|
||||||
pathFindingCfg: PathFindingConfig{
|
pathFindingCfg: PathFindingConfig{
|
||||||
PaymentAttemptPenalty: 1000,
|
AttemptCost: 1000,
|
||||||
MinProbability: 0.01,
|
MinProbability: 0.01,
|
||||||
},
|
},
|
||||||
|
|
||||||
source: source,
|
source: source,
|
||||||
|
|||||||
+35
-11
@@ -45,11 +45,22 @@ type pathFinder = func(g *graphParams, r *RestrictParams,
|
|||||||
[]*channeldb.ChannelEdgePolicy, error)
|
[]*channeldb.ChannelEdgePolicy, error)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// DefaultPaymentAttemptPenalty is the virtual cost in path finding weight
|
// DefaultAttemptCost is the default fixed virtual cost in path finding
|
||||||
// units of executing a payment attempt that fails. It is used to trade
|
// of a failed payment attempt. It is used to trade off potentially
|
||||||
// off potentially better routes against their probability of
|
// better routes against their probability of succeeding.
|
||||||
// succeeding.
|
DefaultAttemptCost = lnwire.NewMSatFromSatoshis(100)
|
||||||
DefaultPaymentAttemptPenalty = 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
|
// DefaultMinRouteProbability is the default minimum probability for routes
|
||||||
// returned from findPath.
|
// returned from findPath.
|
||||||
@@ -315,11 +326,16 @@ type RestrictParams struct {
|
|||||||
// PathFindingConfig defines global parameters that control the trade-off in
|
// PathFindingConfig defines global parameters that control the trade-off in
|
||||||
// path finding between fees and probabiity.
|
// path finding between fees and probabiity.
|
||||||
type PathFindingConfig struct {
|
type PathFindingConfig struct {
|
||||||
// PaymentAttemptPenalty is the virtual cost in path finding weight
|
// AttemptCost is the fixed virtual cost in path finding of a failed
|
||||||
// units of executing a payment attempt that fails. It is used to trade
|
// payment attempt. It is used to trade off potentially better routes
|
||||||
// off potentially better routes against their probability of
|
// against their probability of succeeding.
|
||||||
// succeeding.
|
AttemptCost lnwire.MilliSatoshi
|
||||||
PaymentAttemptPenalty 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
|
// MinProbability defines the minimum success probability of the
|
||||||
// returned route.
|
// returned route.
|
||||||
@@ -550,6 +566,14 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
|
|||||||
// if the cltv limit is MaxUint32.
|
// if the cltv limit is MaxUint32.
|
||||||
absoluteCltvLimit := uint64(r.CltvLimit) + uint64(finalHtlcExpiry)
|
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
|
// processEdge is a helper closure that will be used to make sure edges
|
||||||
// satisfy our specific requirements.
|
// satisfy our specific requirements.
|
||||||
processEdge := func(fromVertex route.Vertex,
|
processEdge := func(fromVertex route.Vertex,
|
||||||
@@ -644,7 +668,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
|
|||||||
// probability.
|
// probability.
|
||||||
tempDist := getProbabilityBasedDist(
|
tempDist := getProbabilityBasedDist(
|
||||||
tempWeight, probability,
|
tempWeight, probability,
|
||||||
int64(cfg.PaymentAttemptPenalty),
|
absoluteAttemptCost,
|
||||||
)
|
)
|
||||||
|
|
||||||
// If there is already a best route stored, compare this
|
// If there is already a best route stored, compare this
|
||||||
|
|||||||
+36
-11
@@ -2461,25 +2461,43 @@ func TestProbabilityRouting(t *testing.T) {
|
|||||||
p10, p11, p20 float64
|
p10, p11, p20 float64
|
||||||
minProbability float64
|
minProbability float64
|
||||||
expectedChan uint64
|
expectedChan uint64
|
||||||
|
amount btcutil.Amount
|
||||||
}{
|
}{
|
||||||
// Test two variations with probabilities that should multiply
|
// Test two variations with probabilities that should multiply
|
||||||
// to the same total route probability. In both cases the three
|
// to the same total route probability. In both cases the three
|
||||||
// hop route should be the best route. The three hop route has a
|
// 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
|
// 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
|
// (chan 11) = 13. The attempt cost is 9 + 1% * 100 = 10. Path
|
||||||
// + 10 (attempt penalty) / 0.4 = 38. The two hop route is 25 +
|
// finding distance should work out to: 13 + 10 (attempt
|
||||||
// 10 / 0.7 = 39.
|
// penalty) / 0.4 = 38. The two hop route is 25 + 10 / 0.7 = 39.
|
||||||
{
|
{
|
||||||
name: "three hop 1",
|
name: "three hop 1",
|
||||||
p10: 0.8, p11: 0.5, p20: 0.7,
|
p10: 0.8, p11: 0.5, p20: 0.7,
|
||||||
minProbability: 0.1,
|
minProbability: 0.1,
|
||||||
expectedChan: 10,
|
expectedChan: 10,
|
||||||
|
amount: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "three hop 2",
|
name: "three hop 2",
|
||||||
p10: 0.5, p11: 0.8, p20: 0.7,
|
p10: 0.5, p11: 0.8, p20: 0.7,
|
||||||
minProbability: 0.1,
|
minProbability: 0.1,
|
||||||
expectedChan: 10,
|
expectedChan: 10,
|
||||||
|
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
|
// If the probability of the two hop route is increased, its
|
||||||
@@ -2492,6 +2510,7 @@ func TestProbabilityRouting(t *testing.T) {
|
|||||||
p10: 0.5, p11: 0.8, p20: 0.85,
|
p10: 0.5, p11: 0.8, p20: 0.85,
|
||||||
minProbability: 0.1,
|
minProbability: 0.1,
|
||||||
expectedChan: 20,
|
expectedChan: 20,
|
||||||
|
amount: 100,
|
||||||
},
|
},
|
||||||
|
|
||||||
// If the same probabilities are used with a probability lower bound of
|
// If the same probabilities are used with a probability lower bound of
|
||||||
@@ -2502,6 +2521,7 @@ func TestProbabilityRouting(t *testing.T) {
|
|||||||
p10: 0.8, p11: 0.5, p20: 0.7,
|
p10: 0.8, p11: 0.5, p20: 0.7,
|
||||||
minProbability: 0.5,
|
minProbability: 0.5,
|
||||||
expectedChan: 20,
|
expectedChan: 20,
|
||||||
|
amount: 100,
|
||||||
},
|
},
|
||||||
|
|
||||||
// With a probability limit above the probability of both routes, we
|
// With a probability limit above the probability of both routes, we
|
||||||
@@ -2512,21 +2532,24 @@ func TestProbabilityRouting(t *testing.T) {
|
|||||||
p10: 0.8, p11: 0.5, p20: 0.7,
|
p10: 0.8, p11: 0.5, p20: 0.7,
|
||||||
minProbability: 0.8,
|
minProbability: 0.8,
|
||||||
expectedChan: 0,
|
expectedChan: 0,
|
||||||
|
amount: 100,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
testProbabilityRouting(
|
testProbabilityRouting(
|
||||||
t, tc.p10, tc.p11, tc.p20,
|
t, tc.amount, tc.p10, tc.p11, tc.p20,
|
||||||
tc.minProbability, tc.expectedChan,
|
tc.minProbability, tc.expectedChan,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testProbabilityRouting(t *testing.T, p10, p11, p20, minProbability float64,
|
func testProbabilityRouting(t *testing.T, paymentAmt btcutil.Amount,
|
||||||
expectedChan uint64) {
|
p10, p11, p20, minProbability float64, expectedChan uint64) {
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -2558,7 +2581,6 @@ func testProbabilityRouting(t *testing.T, p10, p11, p20, minProbability float64,
|
|||||||
|
|
||||||
alias := ctx.testGraphInstance.aliasMap
|
alias := ctx.testGraphInstance.aliasMap
|
||||||
|
|
||||||
paymentAmt := lnwire.NewMSatFromSatoshis(100)
|
|
||||||
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.
|
||||||
@@ -2582,11 +2604,14 @@ func testProbabilityRouting(t *testing.T, p10, p11, p20, minProbability float64,
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx.pathFindingConfig = PathFindingConfig{
|
ctx.pathFindingConfig = PathFindingConfig{
|
||||||
PaymentAttemptPenalty: lnwire.NewMSatFromSatoshis(10),
|
AttemptCost: lnwire.NewMSatFromSatoshis(9),
|
||||||
MinProbability: minProbability,
|
AttemptCostPPM: 10000,
|
||||||
|
MinProbability: minProbability,
|
||||||
}
|
}
|
||||||
|
|
||||||
path, err := ctx.findPath(target, paymentAmt)
|
path, err := ctx.findPath(
|
||||||
|
target, lnwire.NewMSatFromSatoshis(paymentAmt),
|
||||||
|
)
|
||||||
if expectedChan == 0 {
|
if expectedChan == 0 {
|
||||||
if err != errNoPathFound {
|
if err != errNoPathFound {
|
||||||
t.Fatalf("expected no path found, but got %v", err)
|
t.Fatalf("expected no path found, but got %v", err)
|
||||||
@@ -2656,7 +2681,7 @@ func TestEqualCostRouteSelection(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx.pathFindingConfig = PathFindingConfig{
|
ctx.pathFindingConfig = PathFindingConfig{
|
||||||
PaymentAttemptPenalty: lnwire.NewMSatFromSatoshis(1),
|
AttemptCost: lnwire.NewMSatFromSatoshis(1),
|
||||||
}
|
}
|
||||||
|
|
||||||
path, err := ctx.findPath(target, paymentAmt)
|
path, err := ctx.findPath(target, paymentAmt)
|
||||||
|
|||||||
@@ -79,8 +79,8 @@ func createTestCtxFromGraphInstance(startingHeight uint32, graphInstance *testGr
|
|||||||
chainView := newMockChainView(chain)
|
chainView := newMockChainView(chain)
|
||||||
|
|
||||||
pathFindingConfig := PathFindingConfig{
|
pathFindingConfig := PathFindingConfig{
|
||||||
MinProbability: 0.01,
|
MinProbability: 0.01,
|
||||||
PaymentAttemptPenalty: 100,
|
AttemptCost: 100,
|
||||||
}
|
}
|
||||||
|
|
||||||
mcConfig := &MissionControlConfig{
|
mcConfig := &MissionControlConfig{
|
||||||
|
|||||||
+6
-2
@@ -641,8 +641,12 @@ litecoin.node=ltcd
|
|||||||
; probability (default: 1h0m0s)
|
; probability (default: 1h0m0s)
|
||||||
; routerrpc.penaltyhalflife=2h
|
; routerrpc.penaltyhalflife=2h
|
||||||
|
|
||||||
; The (virtual) cost in sats of a failed payment attempt (default: 100)
|
; The (virtual) fixed cost in sats of a failed payment attempt (default: 100)
|
||||||
; routerrpc.attemptcost=90
|
; 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
|
; The maximum number of payment results that are held on disk by mission control
|
||||||
; (default: 1000)
|
; (default: 1000)
|
||||||
|
|||||||
@@ -725,14 +725,16 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||||||
}
|
}
|
||||||
|
|
||||||
srvrLog.Debugf("Instantiating payment session source with config: "+
|
srvrLog.Debugf("Instantiating payment session source with config: "+
|
||||||
"PaymentAttemptPenalty=%v, MinRouteProbability=%v",
|
"AttemptCost=%v + %v%%, MinRouteProbability=%v",
|
||||||
int64(routingConfig.AttemptCost),
|
int64(routingConfig.AttemptCost),
|
||||||
|
float64(routingConfig.AttemptCostPPM)/10000,
|
||||||
routingConfig.MinRouteProbability)
|
routingConfig.MinRouteProbability)
|
||||||
|
|
||||||
pathFindingConfig := routing.PathFindingConfig{
|
pathFindingConfig := routing.PathFindingConfig{
|
||||||
PaymentAttemptPenalty: lnwire.NewMSatFromSatoshis(
|
AttemptCost: lnwire.NewMSatFromSatoshis(
|
||||||
routingConfig.AttemptCost,
|
routingConfig.AttemptCost,
|
||||||
),
|
),
|
||||||
|
AttemptCostPPM: routingConfig.AttemptCostPPM,
|
||||||
MinProbability: routingConfig.MinRouteProbability,
|
MinProbability: routingConfig.MinRouteProbability,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user