@@ -517,7 +517,7 @@ pub struct ProbabilisticScorerUsingTime<G: Deref<Target = NetworkGraph>, T: Time
517517
518518/// Parameters for configuring [`ProbabilisticScorer`].
519519///
520- /// Used to configure a base penalty and a liquidity penalty , the sum of which is the channel
520+ /// Used to configure base, liquidity, and amount penalties , the sum of which composes the channel
521521/// penalty (i.e., the amount in msats willing to be paid to avoid routing through the channel).
522522#[ derive( Clone , Copy ) ]
523523pub struct ProbabilisticScoringParameters {
@@ -555,6 +555,25 @@ pub struct ProbabilisticScoringParameters {
555555 /// When built with the `no-std` feature, time will never elapse. Therefore, the channel
556556 /// liquidity knowledge will never decay except when the bounds cross.
557557 pub liquidity_offset_half_life : Duration ,
558+
559+ /// A multiplier used in conjunction with a payment amount and the negative `log10` of the
560+ /// channel's success probability for the payment to determine the amount penalty.
561+ ///
562+ /// The purpose of the amount penalty is to avoid having fees dominate the channel cost (i.e.,
563+ /// fees plus penalty) for large payments. The penalty is computed as the product of this
564+ /// multiplier and `2^20`ths of the payment amount, weighted by the negative `log10` of the
565+ /// success probability.
566+ ///
567+ /// `-log10(success_probability) * amount_penalty_multiplier_msat * amount_msat / 2^20`
568+ ///
569+ /// In practice, this means for 0.1 success probability (i.e., `-log10 == 1`) each `2^20`th of
570+ /// the amount will result in a penalty of the multiplier. And, as the success probability
571+ /// decreases, the negative `log10` weighting will increase dramatically. For higher success
572+ /// probabilities, the multiplier will have a decreasing effect as the negative `log10` will
573+ /// fall below `1`.
574+ ///
575+ /// Default value: 0
576+ pub amount_penalty_multiplier_msat : u64 ,
558577}
559578
560579/// Accounting for channel liquidity balance uncertainty.
@@ -608,6 +627,7 @@ impl Default for ProbabilisticScoringParameters {
608627 base_penalty_msat : 500 ,
609628 liquidity_penalty_multiplier_msat : 40_000 ,
610629 liquidity_offset_half_life : Duration :: from_secs ( 3600 ) ,
630+ amount_penalty_multiplier_msat : 0 ,
611631 }
612632 }
613633}
@@ -665,11 +685,17 @@ impl<T: Time> ChannelLiquidity<T> {
665685 }
666686}
667687
688+ /// Bounds `-log10` to avoid excessive liquidity penalties for payments with low success
689+ /// probabilities.
690+ const NEGATIVE_LOG10_UPPER_BOUND : u64 = 2 ;
691+
692+ /// The divisor used when computing the amount penalty.
693+ const AMOUNT_PENALTY_DIVISOR : u64 = 1 << 20 ;
694+
668695impl < L : Deref < Target = u64 > , T : Time , U : Deref < Target = T > > DirectedChannelLiquidity < L , T , U > {
669696 /// Returns a penalty for routing the given HTLC `amount_msat` through the channel in this
670697 /// direction.
671- fn penalty_msat ( & self , amount_msat : u64 , liquidity_penalty_multiplier_msat : u64 ) -> u64 {
672- let max_penalty_msat = liquidity_penalty_multiplier_msat. saturating_mul ( 2 ) ;
698+ fn penalty_msat ( & self , amount_msat : u64 , params : ProbabilisticScoringParameters ) -> u64 {
673699 let max_liquidity_msat = self . max_liquidity_msat ( ) ;
674700 let min_liquidity_msat = core:: cmp:: min ( self . min_liquidity_msat ( ) , max_liquidity_msat) ;
675701 if amount_msat <= min_liquidity_msat {
@@ -681,18 +707,40 @@ impl<L: Deref<Target = u64>, T: Time, U: Deref<Target = T>> DirectedChannelLiqui
681707 // Avoid using the failed channel on retry.
682708 u64:: max_value ( )
683709 } else {
684- max_penalty_msat
710+ // Equivalent to hitting the else clause below with the amount equal to the
711+ // effective capacity and without any certainty on the liquidity upper bound.
712+ let negative_log10_times_1024 = NEGATIVE_LOG10_UPPER_BOUND * 1024 ;
713+ self . combined_penalty_msat ( amount_msat, negative_log10_times_1024, params)
685714 }
686715 } else {
687716 let numerator = ( max_liquidity_msat - amount_msat) . saturating_add ( 1 ) ;
688717 let denominator = ( max_liquidity_msat - min_liquidity_msat) . saturating_add ( 1 ) ;
689- let penalty_msat = approx:: negative_log10_times_1024 ( numerator, denominator)
690- . saturating_mul ( liquidity_penalty_multiplier_msat) / 1024 ;
691- // Upper bound the penalty to ensure some channel is selected.
692- penalty_msat. min ( max_penalty_msat)
718+ let negative_log10_times_1024 =
719+ approx:: negative_log10_times_1024 ( numerator, denominator) ;
720+ self . combined_penalty_msat ( amount_msat, negative_log10_times_1024, params)
693721 }
694722 }
695723
724+ #[ inline( always) ]
725+ fn combined_penalty_msat (
726+ & self , amount_msat : u64 , negative_log10_times_1024 : u64 ,
727+ params : ProbabilisticScoringParameters
728+ ) -> u64 {
729+ let liquidity_penalty_msat = {
730+ // Upper bound the liquidity penalty to ensure some channel is selected.
731+ let multiplier_msat = params. liquidity_penalty_multiplier_msat ;
732+ let max_penalty_msat = multiplier_msat. saturating_mul ( NEGATIVE_LOG10_UPPER_BOUND ) ;
733+ ( negative_log10_times_1024. saturating_mul ( multiplier_msat) / 1024 ) . min ( max_penalty_msat)
734+ } ;
735+ let amount_penalty_msat = negative_log10_times_1024
736+ . saturating_mul ( params. amount_penalty_multiplier_msat )
737+ . saturating_mul ( amount_msat) / 1024 / AMOUNT_PENALTY_DIVISOR ;
738+
739+ params. base_penalty_msat
740+ . saturating_add ( liquidity_penalty_msat)
741+ . saturating_add ( amount_penalty_msat)
742+ }
743+
696744 /// Returns the lower bound of the channel liquidity balance in this direction.
697745 fn min_liquidity_msat ( & self ) -> u64 {
698746 self . decayed_offset_msat ( * self . min_liquidity_offset_msat )
@@ -762,14 +810,12 @@ impl<G: Deref<Target = NetworkGraph>, T: Time> Score for ProbabilisticScorerUsin
762810 & self , short_channel_id : u64 , amount_msat : u64 , capacity_msat : u64 , source : & NodeId ,
763811 target : & NodeId
764812 ) -> u64 {
765- let liquidity_penalty_multiplier_msat = self . params . liquidity_penalty_multiplier_msat ;
766813 let liquidity_offset_half_life = self . params . liquidity_offset_half_life ;
767814 self . channel_liquidities
768815 . get ( & short_channel_id)
769816 . unwrap_or ( & ChannelLiquidity :: new ( ) )
770817 . as_directed ( source, target, capacity_msat, liquidity_offset_half_life)
771- . penalty_msat ( amount_msat, liquidity_penalty_multiplier_msat)
772- . saturating_add ( self . params . base_penalty_msat )
818+ . penalty_msat ( amount_msat, self . params )
773819 }
774820
775821 fn payment_path_failed ( & mut self , path : & [ & RouteHop ] , short_channel_id : u64 ) {
@@ -1884,6 +1930,7 @@ mod tests {
18841930 base_penalty_msat : 0 ,
18851931 liquidity_penalty_multiplier_msat : 1_000 ,
18861932 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
1933+ ..Default :: default ( )
18871934 } ;
18881935 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
18891936 let source = source_node_id ( ) ;
@@ -1936,6 +1983,7 @@ mod tests {
19361983 base_penalty_msat : 0 ,
19371984 liquidity_penalty_multiplier_msat : 1_000 ,
19381985 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
1986+ ..Default :: default ( )
19391987 } ;
19401988 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
19411989 let source = source_node_id ( ) ;
@@ -1961,6 +2009,7 @@ mod tests {
19612009 base_penalty_msat : 0 ,
19622010 liquidity_penalty_multiplier_msat : 1_000 ,
19632011 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
2012+ ..Default :: default ( )
19642013 } ;
19652014 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
19662015 let source = source_node_id ( ) ;
@@ -1999,6 +2048,7 @@ mod tests {
19992048 base_penalty_msat : 0 ,
20002049 liquidity_penalty_multiplier_msat : 1_000 ,
20012050 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
2051+ ..Default :: default ( )
20022052 } ;
20032053 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
20042054 let source = source_node_id ( ) ;
@@ -2029,6 +2079,7 @@ mod tests {
20292079 base_penalty_msat : 0 ,
20302080 liquidity_penalty_multiplier_msat : 1_000 ,
20312081 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
2082+ ..Default :: default ( )
20322083 } ;
20332084 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
20342085 let source = source_node_id ( ) ;
@@ -2073,6 +2124,29 @@ mod tests {
20732124 assert_eq ! ( scorer. channel_penalty_msat( 42 , 128 , 1_024 , & source, & target) , 558 ) ;
20742125 }
20752126
2127+ #[ test]
2128+ fn adds_amount_penalty_to_liquidity_penalty ( ) {
2129+ let network_graph = network_graph ( ) ;
2130+ let source = source_node_id ( ) ;
2131+ let target = target_node_id ( ) ;
2132+
2133+ let params = ProbabilisticScoringParameters {
2134+ liquidity_penalty_multiplier_msat : 1_000 ,
2135+ amount_penalty_multiplier_msat : 0 ,
2136+ ..Default :: default ( )
2137+ } ;
2138+ let scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
2139+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 512_000 , 1_024_000 , & source, & target) , 800 ) ;
2140+
2141+ let params = ProbabilisticScoringParameters {
2142+ liquidity_penalty_multiplier_msat : 1_000 ,
2143+ amount_penalty_multiplier_msat : 256 ,
2144+ ..Default :: default ( )
2145+ } ;
2146+ let scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
2147+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 512_000 , 1_024_000 , & source, & target) , 837 ) ;
2148+ }
2149+
20762150 #[ test]
20772151 fn calculates_log10_without_overflowing_u64_max_value ( ) {
20782152 let network_graph = network_graph ( ) ;
0 commit comments