@@ -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,22 @@ 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+ NegativeLogSuccessProbability ,
561+
562+ /// Uses `2 * failure_probability`, which increases the cost linearly as `success_probability`
563+ /// decreases.
564+ TwiceFailureProbability ,
565+ }
544566
545567/// Accounting for channel liquidity balance uncertainty.
546568///
@@ -590,6 +612,7 @@ impl<G: Deref<Target = NetworkGraph>, T: Time> ProbabilisticScorerUsingTime<G, T
590612impl Default for ProbabilisticScoringParameters {
591613 fn default ( ) -> Self {
592614 Self {
615+ cost_function : ProbabilisticScoringCostFunction :: NegativeLogSuccessProbability ,
593616 liquidity_penalty_multiplier_msat : 10_000 ,
594617 liquidity_offset_half_life : Duration :: from_secs ( 3600 ) ,
595618 }
@@ -652,7 +675,8 @@ impl<T: Time> ChannelLiquidity<T> {
652675impl < L : Deref < Target = u64 > , T : Time , U : Deref < Target = T > > DirectedChannelLiquidity < L , T , U > {
653676 /// Returns a penalty for routing the given HTLC `amount_msat` through the channel in this
654677 /// 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 ) ;
656680 let max_liquidity_msat = self . max_liquidity_msat ( ) ;
657681 let min_liquidity_msat = core:: cmp:: min ( self . min_liquidity_msat ( ) , max_liquidity_msat) ;
658682 if amount_msat > max_liquidity_msat {
@@ -662,11 +686,21 @@ impl<L: Deref<Target = u64>, T: Time, U: Deref<Target = T>> DirectedChannelLiqui
662686 } else {
663687 let numerator = max_liquidity_msat + 1 - amount_msat;
664688 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+ }
667701 }
668702 // Upper bound the penalty to ensure some channel is selected.
669- . min ( 2 * liquidity_penalty_multiplier_msat )
703+ . min ( max_penalty_msat )
670704 }
671705
672706 /// 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
738772 & self , short_channel_id : u64 , amount_msat : u64 , capacity_msat : u64 , source : & NodeId ,
739773 target : & NodeId
740774 ) -> 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 ;
743775 self . channel_liquidities
744776 . get ( & short_channel_id)
745777 . 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 )
748780 }
749781
750782 fn payment_path_failed ( & mut self , path : & [ & RouteHop ] , short_channel_id : u64 ) {
@@ -1056,7 +1088,7 @@ pub(crate) use self::time::Time;
10561088
10571089#[ cfg( test) ]
10581090mod tests {
1059- use super :: { ChannelLiquidity , ProbabilisticScoringParameters , ProbabilisticScorerUsingTime , ScoringParameters , ScorerUsingTime , Time } ;
1091+ use super :: { ChannelLiquidity , ProbabilisticScoringCostFunction , ProbabilisticScoringParameters , ProbabilisticScorerUsingTime , ScoringParameters , ScorerUsingTime , Time } ;
10601092 use super :: time:: Eternity ;
10611093
10621094 use ln:: features:: { ChannelFeatures , NodeFeatures } ;
@@ -1745,6 +1777,32 @@ mod tests {
17451777 assert_eq ! ( scorer. channel_penalty_msat( 42 , 896 , 1_024 , & source, & target) , 903 ) ;
17461778 }
17471779
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+
17481806 #[ test]
17491807 fn constant_penalty_outside_liquidity_bounds ( ) {
17501808 let last_updated = SinceEpoch :: now ( ) ;
@@ -1861,6 +1919,7 @@ mod tests {
18611919 let params = ProbabilisticScoringParameters {
18621920 liquidity_penalty_multiplier_msat : 1_000 ,
18631921 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
1922+ ..Default :: default ( )
18641923 } ;
18651924 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
18661925 let source = source_node_id ( ) ;
@@ -1912,6 +1971,7 @@ mod tests {
19121971 let params = ProbabilisticScoringParameters {
19131972 liquidity_penalty_multiplier_msat : 1_000 ,
19141973 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
1974+ ..Default :: default ( )
19151975 } ;
19161976 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
19171977 let source = source_node_id ( ) ;
@@ -1936,6 +1996,7 @@ mod tests {
19361996 let params = ProbabilisticScoringParameters {
19371997 liquidity_penalty_multiplier_msat : 1_000 ,
19381998 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
1999+ ..Default :: default ( )
19392000 } ;
19402001 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
19412002 let source = source_node_id ( ) ;
@@ -1973,6 +2034,7 @@ mod tests {
19732034 let params = ProbabilisticScoringParameters {
19742035 liquidity_penalty_multiplier_msat : 1_000 ,
19752036 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
2037+ ..Default :: default ( )
19762038 } ;
19772039 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
19782040 let source = source_node_id ( ) ;
@@ -2002,6 +2064,7 @@ mod tests {
20022064 let params = ProbabilisticScoringParameters {
20032065 liquidity_penalty_multiplier_msat : 1_000 ,
20042066 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
2067+ ..Default :: default ( )
20052068 } ;
20062069 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
20072070 let source = source_node_id ( ) ;
0 commit comments