@@ -506,19 +506,29 @@ pub struct ProbabilisticScorerUsingTime<G: Deref<Target = NetworkGraph>, T: Time
506506/// Parameters for configuring [`ProbabilisticScorer`].
507507#[ derive( Clone , Copy ) ]
508508pub 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.
512510 ///
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`.
518514 ///
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`]
520519 ///
520+ /// [`liquidity_penalty_multiplier_msat`]: Self::liquidity_penalty_multiplier_msat
521521 /// [`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
522532 pub liquidity_penalty_multiplier_msat : u64 ,
523533
524534 /// The time required to elapse before any knowledge learned about channel liquidity balances is
@@ -537,10 +547,28 @@ pub struct ProbabilisticScoringParameters {
537547 pub liquidity_offset_half_life : Duration ,
538548}
539549
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+ ///
561+ /// This effectively linearizes penalties across multiple hops as a result of the log product
562+ /// property (i.e., a penalty can be reasoned about in terms of the success probability of an
563+ /// entire path). However, this may result in low penalties when the channel liquidity balance
564+ /// uncertainty is high as payments are less likely to be close to the bounds (i.e., success
565+ /// probability is unlikely to be low).
566+ NegativeLogSuccessProbability ,
567+
568+ /// Uses `2 * failure_probability`, which increases the cost linearly as `success_probability`
569+ /// decreases.
570+ TwiceFailureProbability ,
571+ }
544572
545573/// Accounting for channel liquidity balance uncertainty.
546574///
@@ -590,6 +618,7 @@ impl<G: Deref<Target = NetworkGraph>, T: Time> ProbabilisticScorerUsingTime<G, T
590618impl Default for ProbabilisticScoringParameters {
591619 fn default ( ) -> Self {
592620 Self {
621+ cost_function : ProbabilisticScoringCostFunction :: NegativeLogSuccessProbability ,
593622 liquidity_penalty_multiplier_msat : 10_000 ,
594623 liquidity_offset_half_life : Duration :: from_secs ( 3600 ) ,
595624 }
@@ -652,21 +681,32 @@ impl<T: Time> ChannelLiquidity<T> {
652681impl < L : Deref < Target = u64 > , T : Time , U : Deref < Target = T > > DirectedChannelLiquidity < L , T , U > {
653682 /// Returns a penalty for routing the given HTLC `amount_msat` through the channel in this
654683 /// direction.
655- fn penalty_msat ( & self , amount_msat : u64 , liquidity_penalty_multiplier_msat : u64 ) -> u64 {
684+ fn penalty_msat ( & self , amount_msat : u64 , params : & ProbabilisticScoringParameters ) -> u64 {
685+ let max_penalty_msat = params. liquidity_penalty_multiplier_msat . saturating_mul ( 2 ) ;
656686 let max_liquidity_msat = self . max_liquidity_msat ( ) ;
657687 let min_liquidity_msat = core:: cmp:: min ( self . min_liquidity_msat ( ) , max_liquidity_msat) ;
658688 if amount_msat > max_liquidity_msat {
659- u64 :: max_value ( )
689+ max_penalty_msat
660690 } else if amount_msat <= min_liquidity_msat {
661691 0
662692 } else {
663693 let numerator = max_liquidity_msat + 1 - amount_msat;
664694 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
695+ match params. cost_function {
696+ ProbabilisticScoringCostFunction :: NegativeLogSuccessProbability => {
697+ let penalty_msat = approx:: negative_log10_times_1024 ( numerator, denominator)
698+ . saturating_mul ( params. liquidity_penalty_multiplier_msat ) / 1024 ;
699+ // Upper bound the penalty to ensure some channel is selected.
700+ penalty_msat. min ( max_penalty_msat)
701+ } ,
702+ ProbabilisticScoringCostFunction :: TwiceFailureProbability => {
703+ // Avoid floating-point operations by multiplying the coefficient through:
704+ // 2 * liquidity_penalty_multiplier_msat * (1 - success_probability)
705+ let coefficient = max_penalty_msat;
706+ coefficient. saturating_sub ( coefficient. saturating_mul ( numerator) / denominator)
707+ } ,
708+ }
667709 }
668- // Upper bound the penalty to ensure some channel is selected.
669- . min ( 2 * liquidity_penalty_multiplier_msat)
670710 }
671711
672712 /// Returns the lower bound of the channel liquidity balance in this direction.
@@ -738,13 +778,11 @@ impl<G: Deref<Target = NetworkGraph>, T: Time> Score for ProbabilisticScorerUsin
738778 & self , short_channel_id : u64 , amount_msat : u64 , capacity_msat : u64 , source : & NodeId ,
739779 target : & NodeId
740780 ) -> 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 ;
743781 self . channel_liquidities
744782 . get ( & short_channel_id)
745783 . unwrap_or ( & ChannelLiquidity :: new ( ) )
746- . as_directed ( source, target, capacity_msat, liquidity_offset_half_life)
747- . penalty_msat ( amount_msat, liquidity_penalty_multiplier_msat )
784+ . as_directed ( source, target, capacity_msat, self . params . liquidity_offset_half_life )
785+ . penalty_msat ( amount_msat, & self . params )
748786 }
749787
750788 fn payment_path_failed ( & mut self , path : & [ & RouteHop ] , short_channel_id : u64 ) {
@@ -1053,7 +1091,7 @@ pub(crate) use self::time::Time;
10531091
10541092#[ cfg( test) ]
10551093mod tests {
1056- use super :: { ChannelLiquidity , ProbabilisticScoringParameters , ProbabilisticScorerUsingTime , ScoringParameters , ScorerUsingTime , Time } ;
1094+ use super :: { ChannelLiquidity , ProbabilisticScoringCostFunction , ProbabilisticScoringParameters , ProbabilisticScorerUsingTime , ScoringParameters , ScorerUsingTime , Time } ;
10571095 use super :: time:: Eternity ;
10581096
10591097 use ln:: features:: { ChannelFeatures , NodeFeatures } ;
@@ -1742,6 +1780,32 @@ mod tests {
17421780 assert_eq ! ( scorer. channel_penalty_msat( 42 , 896 , 1_024 , & source, & target) , 903 ) ;
17431781 }
17441782
1783+ #[ test]
1784+ fn increased_penalty_linearly_nearing_liquidity_upper_bound ( ) {
1785+ let network_graph = network_graph ( ) ;
1786+ let params = ProbabilisticScoringParameters {
1787+ cost_function : ProbabilisticScoringCostFunction :: TwiceFailureProbability ,
1788+ liquidity_penalty_multiplier_msat : 1_000 ,
1789+ ..Default :: default ( )
1790+ } ;
1791+ let scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
1792+ let source = source_node_id ( ) ;
1793+ let target = target_node_id ( ) ;
1794+
1795+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 1_024 , 1_024_000 , & source, & target) , 2 ) ;
1796+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 10_240 , 1_024_000 , & source, & target) , 20 ) ;
1797+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 102_400 , 1_024_000 , & source, & target) , 200 ) ;
1798+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 1_024_000 , 1_024_000 , & source, & target) , 2_000 ) ;
1799+
1800+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 125 , 1_000 , & source, & target) , 250 ) ;
1801+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 250 , 1_000 , & source, & target) , 500 ) ;
1802+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 375 , 1_000 , & source, & target) , 750 ) ;
1803+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 500 , 1_000 , & source, & target) , 1_000 ) ;
1804+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 625 , 1_000 , & source, & target) , 1_249 ) ;
1805+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 750 , 1_000 , & source, & target) , 1_499 ) ;
1806+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 875 , 1_000 , & source, & target) , 1_749 ) ;
1807+ }
1808+
17451809 #[ test]
17461810 fn constant_penalty_outside_liquidity_bounds ( ) {
17471811 let last_updated = SinceEpoch :: now ( ) ;
@@ -1858,6 +1922,7 @@ mod tests {
18581922 let params = ProbabilisticScoringParameters {
18591923 liquidity_penalty_multiplier_msat : 1_000 ,
18601924 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
1925+ ..Default :: default ( )
18611926 } ;
18621927 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
18631928 let source = source_node_id ( ) ;
@@ -1909,6 +1974,7 @@ mod tests {
19091974 let params = ProbabilisticScoringParameters {
19101975 liquidity_penalty_multiplier_msat : 1_000 ,
19111976 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
1977+ ..Default :: default ( )
19121978 } ;
19131979 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
19141980 let source = source_node_id ( ) ;
@@ -1933,6 +1999,7 @@ mod tests {
19331999 let params = ProbabilisticScoringParameters {
19342000 liquidity_penalty_multiplier_msat : 1_000 ,
19352001 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
2002+ ..Default :: default ( )
19362003 } ;
19372004 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
19382005 let source = source_node_id ( ) ;
@@ -1970,6 +2037,7 @@ mod tests {
19702037 let params = ProbabilisticScoringParameters {
19712038 liquidity_penalty_multiplier_msat : 1_000 ,
19722039 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
2040+ ..Default :: default ( )
19732041 } ;
19742042 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
19752043 let source = source_node_id ( ) ;
@@ -1999,6 +2067,7 @@ mod tests {
19992067 let params = ProbabilisticScoringParameters {
20002068 liquidity_penalty_multiplier_msat : 1_000 ,
20012069 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
2070+ ..Default :: default ( )
20022071 } ;
20032072 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
20042073 let source = source_node_id ( ) ;
0 commit comments