@@ -506,19 +506,29 @@ pub struct ProbabilisticScorerUsingTime<G: Deref<Target = NetworkGraph>, T: Time
506
506
/// Parameters for configuring [`ProbabilisticScorer`].
507
507
#[ derive( Clone , Copy ) ]
508
508
pub struct ProbabilisticScoringParameters {
509
- /// A multiplier used to determine the amount in msats willing to be paid to avoid routing
510
- /// through a channel, as per multiplying by the negative `log10` of the channel's success
511
- /// probability for a payment.
509
+ /// The function calculating the cost of routing an amount through a channel.
512
510
///
513
- /// The success probability is determined by the effective channel capacity, the payment amount,
514
- /// and knowledge learned from prior successful and unsuccessful payments. The lower bound of
515
- /// the success probability is 0.01, effectively limiting the penalty to the range
516
- /// `0..=2*liquidity_penalty_multiplier_msat`. The knowledge learned is decayed over time based
517
- /// on [`liquidity_offset_half_life`].
511
+ /// The cost is multiplied by [`liquidity_penalty_multiplier_msat`] to determine the channel
512
+ /// penalty (i.e., the amount msats willing to be paid to avoid routing through the channel).
513
+ /// Penalties are limited to `2 * liquidity_penalty_multiplier_msat`.
518
514
///
519
- /// Default value: 10,000 msat
515
+ /// The cost is based in part by the knowledge learned from prior successful and unsuccessful
516
+ /// payments. This knowledge is decayed over time based on [`liquidity_offset_half_life`].
517
+ ///
518
+ /// Default value: [`ProbabilisticScoringCostFunction::NegativeLogSuccessProbability`]
520
519
///
520
+ /// [`liquidity_penalty_multiplier_msat`]: Self::liquidity_penalty_multiplier_msat
521
521
/// [`liquidity_offset_half_life`]: Self::liquidity_offset_half_life
522
+ pub cost_function : ProbabilisticScoringCostFunction ,
523
+
524
+ /// A multiplier used in conjunction with [`cost_function`] to determine the channel penalty.
525
+ ///
526
+ /// The channel penalty is the amount in msats willing to be paid to avoid routing through a
527
+ /// channel. It is effectively limited to `2 * liquidity_penalty_multiplier_msat`.
528
+ ///
529
+ /// Default value: 10,000 msat
530
+ ///
531
+ /// [`cost_function`]: Self::cost_function
522
532
pub liquidity_penalty_multiplier_msat : u64 ,
523
533
524
534
/// The time required to elapse before any knowledge learned about channel liquidity balances is
@@ -537,10 +547,22 @@ pub struct ProbabilisticScoringParameters {
537
547
pub liquidity_offset_half_life : Duration ,
538
548
}
539
549
540
- impl_writeable_tlv_based ! ( ProbabilisticScoringParameters , {
541
- ( 0 , liquidity_penalty_multiplier_msat, required) ,
542
- ( 2 , liquidity_offset_half_life, required) ,
543
- } ) ;
550
+ /// A function calculating the cost of routing an amount through a channel.
551
+ ///
552
+ /// Costs are calculated in terms of a payment `success_probability`, which is determined by the
553
+ /// effective channel capacity, the payment amount, and knowledge learned from prior successful and
554
+ /// unsuccessful payments. Some cost functions may instead use the `failure_probability`, which is
555
+ /// simply `1.0 - success_probability`. The `success_probability` has a lower bound of `0.01`.
556
+ #[ derive( Clone , Copy ) ]
557
+ pub enum ProbabilisticScoringCostFunction {
558
+ /// Uses `-log10(success_probability)`, which slowly increases the cost as `success_probability`
559
+ /// decreases before rapidly increasing.
560
+ NegativeLogSuccessProbability ,
561
+
562
+ /// Uses `2 * failure_probability`, which increases the cost linearly as `success_probability`
563
+ /// decreases.
564
+ TwiceFailureProbability ,
565
+ }
544
566
545
567
/// Accounting for channel liquidity balance uncertainty.
546
568
///
@@ -590,6 +612,7 @@ impl<G: Deref<Target = NetworkGraph>, T: Time> ProbabilisticScorerUsingTime<G, T
590
612
impl Default for ProbabilisticScoringParameters {
591
613
fn default ( ) -> Self {
592
614
Self {
615
+ cost_function : ProbabilisticScoringCostFunction :: NegativeLogSuccessProbability ,
593
616
liquidity_penalty_multiplier_msat : 10_000 ,
594
617
liquidity_offset_half_life : Duration :: from_secs ( 3600 ) ,
595
618
}
@@ -652,7 +675,8 @@ impl<T: Time> ChannelLiquidity<T> {
652
675
impl < L : Deref < Target = u64 > , T : Time , U : Deref < Target = T > > DirectedChannelLiquidity < L , T , U > {
653
676
/// Returns a penalty for routing the given HTLC `amount_msat` through the channel in this
654
677
/// direction.
655
- fn penalty_msat ( & self , amount_msat : u64 , liquidity_penalty_multiplier_msat : u64 ) -> u64 {
678
+ fn penalty_msat ( & self , amount_msat : u64 , params : & ProbabilisticScoringParameters ) -> u64 {
679
+ let max_penalty_msat = params. liquidity_penalty_multiplier_msat . saturating_mul ( 2 ) ;
656
680
let max_liquidity_msat = self . max_liquidity_msat ( ) ;
657
681
let min_liquidity_msat = core:: cmp:: min ( self . min_liquidity_msat ( ) , max_liquidity_msat) ;
658
682
if amount_msat > max_liquidity_msat {
@@ -662,11 +686,21 @@ impl<L: Deref<Target = u64>, T: Time, U: Deref<Target = T>> DirectedChannelLiqui
662
686
} else {
663
687
let numerator = max_liquidity_msat + 1 - amount_msat;
664
688
let denominator = max_liquidity_msat + 1 - min_liquidity_msat;
665
- approx:: negative_log10_times_1024 ( numerator, denominator)
666
- . saturating_mul ( liquidity_penalty_multiplier_msat) / 1024
689
+ match params. cost_function {
690
+ ProbabilisticScoringCostFunction :: NegativeLogSuccessProbability => {
691
+ approx:: negative_log10_times_1024 ( numerator, denominator)
692
+ . saturating_mul ( params. liquidity_penalty_multiplier_msat ) / 1024
693
+ } ,
694
+ ProbabilisticScoringCostFunction :: TwiceFailureProbability => {
695
+ // Avoid floating-point operations by multiplying the coefficient through:
696
+ // 2 * liquidity_penalty_multiplier_msat * (1 - success_probability)
697
+ let coefficient = max_penalty_msat;
698
+ coefficient. saturating_sub ( coefficient. saturating_mul ( numerator) / denominator)
699
+ } ,
700
+ }
667
701
}
668
702
// Upper bound the penalty to ensure some channel is selected.
669
- . min ( 2 * liquidity_penalty_multiplier_msat )
703
+ . min ( max_penalty_msat )
670
704
}
671
705
672
706
/// Returns the lower bound of the channel liquidity balance in this direction.
@@ -738,13 +772,11 @@ impl<G: Deref<Target = NetworkGraph>, T: Time> Score for ProbabilisticScorerUsin
738
772
& self , short_channel_id : u64 , amount_msat : u64 , capacity_msat : u64 , source : & NodeId ,
739
773
target : & NodeId
740
774
) -> u64 {
741
- let liquidity_penalty_multiplier_msat = self . params . liquidity_penalty_multiplier_msat ;
742
- let liquidity_offset_half_life = self . params . liquidity_offset_half_life ;
743
775
self . channel_liquidities
744
776
. get ( & short_channel_id)
745
777
. unwrap_or ( & ChannelLiquidity :: new ( ) )
746
- . as_directed ( source, target, capacity_msat, liquidity_offset_half_life)
747
- . penalty_msat ( amount_msat, liquidity_penalty_multiplier_msat )
778
+ . as_directed ( source, target, capacity_msat, self . params . liquidity_offset_half_life )
779
+ . penalty_msat ( amount_msat, & self . params )
748
780
}
749
781
750
782
fn payment_path_failed ( & mut self , path : & [ & RouteHop ] , short_channel_id : u64 ) {
@@ -1056,7 +1088,7 @@ pub(crate) use self::time::Time;
1056
1088
1057
1089
#[ cfg( test) ]
1058
1090
mod tests {
1059
- use super :: { ChannelLiquidity , ProbabilisticScoringParameters , ProbabilisticScorerUsingTime , ScoringParameters , ScorerUsingTime , Time } ;
1091
+ use super :: { ChannelLiquidity , ProbabilisticScoringCostFunction , ProbabilisticScoringParameters , ProbabilisticScorerUsingTime , ScoringParameters , ScorerUsingTime , Time } ;
1060
1092
use super :: time:: Eternity ;
1061
1093
1062
1094
use ln:: features:: { ChannelFeatures , NodeFeatures } ;
@@ -1745,6 +1777,32 @@ mod tests {
1745
1777
assert_eq ! ( scorer. channel_penalty_msat( 42 , 896 , 1_024 , & source, & target) , 903 ) ;
1746
1778
}
1747
1779
1780
+ #[ test]
1781
+ fn increased_penalty_linearly_nearing_liquidity_upper_bound ( ) {
1782
+ let network_graph = network_graph ( ) ;
1783
+ let params = ProbabilisticScoringParameters {
1784
+ cost_function : ProbabilisticScoringCostFunction :: TwiceFailureProbability ,
1785
+ liquidity_penalty_multiplier_msat : 1_000 ,
1786
+ ..Default :: default ( )
1787
+ } ;
1788
+ let scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
1789
+ let source = source_node_id ( ) ;
1790
+ let target = target_node_id ( ) ;
1791
+
1792
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 1_024 , 1_024_000 , & source, & target) , 2 ) ;
1793
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 10_240 , 1_024_000 , & source, & target) , 20 ) ;
1794
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 102_400 , 1_024_000 , & source, & target) , 200 ) ;
1795
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 1_024_000 , 1_024_000 , & source, & target) , 2_000 ) ;
1796
+
1797
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 125 , 1_000 , & source, & target) , 250 ) ;
1798
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 250 , 1_000 , & source, & target) , 500 ) ;
1799
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 375 , 1_000 , & source, & target) , 750 ) ;
1800
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 500 , 1_000 , & source, & target) , 1_000 ) ;
1801
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 625 , 1_000 , & source, & target) , 1_249 ) ;
1802
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 750 , 1_000 , & source, & target) , 1_499 ) ;
1803
+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 875 , 1_000 , & source, & target) , 1_749 ) ;
1804
+ }
1805
+
1748
1806
#[ test]
1749
1807
fn constant_penalty_outside_liquidity_bounds ( ) {
1750
1808
let last_updated = SinceEpoch :: now ( ) ;
@@ -1861,6 +1919,7 @@ mod tests {
1861
1919
let params = ProbabilisticScoringParameters {
1862
1920
liquidity_penalty_multiplier_msat : 1_000 ,
1863
1921
liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
1922
+ ..Default :: default ( )
1864
1923
} ;
1865
1924
let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
1866
1925
let source = source_node_id ( ) ;
@@ -1912,6 +1971,7 @@ mod tests {
1912
1971
let params = ProbabilisticScoringParameters {
1913
1972
liquidity_penalty_multiplier_msat : 1_000 ,
1914
1973
liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
1974
+ ..Default :: default ( )
1915
1975
} ;
1916
1976
let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
1917
1977
let source = source_node_id ( ) ;
@@ -1936,6 +1996,7 @@ mod tests {
1936
1996
let params = ProbabilisticScoringParameters {
1937
1997
liquidity_penalty_multiplier_msat : 1_000 ,
1938
1998
liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
1999
+ ..Default :: default ( )
1939
2000
} ;
1940
2001
let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
1941
2002
let source = source_node_id ( ) ;
@@ -1973,6 +2034,7 @@ mod tests {
1973
2034
let params = ProbabilisticScoringParameters {
1974
2035
liquidity_penalty_multiplier_msat : 1_000 ,
1975
2036
liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
2037
+ ..Default :: default ( )
1976
2038
} ;
1977
2039
let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
1978
2040
let source = source_node_id ( ) ;
@@ -2002,6 +2064,7 @@ mod tests {
2002
2064
let params = ProbabilisticScoringParameters {
2003
2065
liquidity_penalty_multiplier_msat : 1_000 ,
2004
2066
liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
2067
+ ..Default :: default ( )
2005
2068
} ;
2006
2069
let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
2007
2070
let source = source_node_id ( ) ;
0 commit comments