@@ -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 (`-log10(0.1) == 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: 256 msat
576+ pub amount_penalty_multiplier_msat : u64 ,
558577}
559578
560579/// Accounting for channel liquidity balance uncertainty.
@@ -602,12 +621,25 @@ impl<G: Deref<Target = NetworkGraph>, T: Time> ProbabilisticScorerUsingTime<G, T
602621 }
603622}
604623
624+ impl ProbabilisticScoringParameters {
625+ #[ cfg( test) ]
626+ fn zero_penalty ( ) -> Self {
627+ Self {
628+ base_penalty_msat : 0 ,
629+ liquidity_penalty_multiplier_msat : 0 ,
630+ liquidity_offset_half_life : Duration :: from_secs ( 3600 ) ,
631+ amount_penalty_multiplier_msat : 0 ,
632+ }
633+ }
634+ }
635+
605636impl Default for ProbabilisticScoringParameters {
606637 fn default ( ) -> Self {
607638 Self {
608639 base_penalty_msat : 500 ,
609640 liquidity_penalty_multiplier_msat : 40_000 ,
610641 liquidity_offset_half_life : Duration :: from_secs ( 3600 ) ,
642+ amount_penalty_multiplier_msat : 256 ,
611643 }
612644 }
613645}
@@ -665,11 +697,17 @@ impl<T: Time> ChannelLiquidity<T> {
665697 }
666698}
667699
700+ /// Bounds `-log10` to avoid excessive liquidity penalties for payments with low success
701+ /// probabilities.
702+ const NEGATIVE_LOG10_UPPER_BOUND : u64 = 2 ;
703+
704+ /// The divisor used when computing the amount penalty.
705+ const AMOUNT_PENALTY_DIVISOR : u64 = 1 << 20 ;
706+
668707impl < L : Deref < Target = u64 > , T : Time , U : Deref < Target = T > > DirectedChannelLiquidity < L , T , U > {
669708 /// Returns a penalty for routing the given HTLC `amount_msat` through the channel in this
670709 /// 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 ) ;
710+ fn penalty_msat ( & self , amount_msat : u64 , params : ProbabilisticScoringParameters ) -> u64 {
673711 let max_liquidity_msat = self . max_liquidity_msat ( ) ;
674712 let min_liquidity_msat = core:: cmp:: min ( self . min_liquidity_msat ( ) , max_liquidity_msat) ;
675713 if amount_msat <= min_liquidity_msat {
@@ -681,18 +719,40 @@ impl<L: Deref<Target = u64>, T: Time, U: Deref<Target = T>> DirectedChannelLiqui
681719 // Avoid using the failed channel on retry.
682720 u64:: max_value ( )
683721 } else {
684- max_penalty_msat
722+ // Equivalent to hitting the else clause below with the amount equal to the
723+ // effective capacity and without any certainty on the liquidity upper bound.
724+ let negative_log10_times_1024 = NEGATIVE_LOG10_UPPER_BOUND * 1024 ;
725+ self . combined_penalty_msat ( amount_msat, negative_log10_times_1024, params)
685726 }
686727 } else {
687728 let numerator = ( max_liquidity_msat - amount_msat) . saturating_add ( 1 ) ;
688729 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)
730+ let negative_log10_times_1024 =
731+ approx:: negative_log10_times_1024 ( numerator, denominator) ;
732+ self . combined_penalty_msat ( amount_msat, negative_log10_times_1024, params)
693733 }
694734 }
695735
736+ #[ inline( always) ]
737+ fn combined_penalty_msat (
738+ & self , amount_msat : u64 , negative_log10_times_1024 : u64 ,
739+ params : ProbabilisticScoringParameters
740+ ) -> u64 {
741+ let liquidity_penalty_msat = {
742+ // Upper bound the liquidity penalty to ensure some channel is selected.
743+ let multiplier_msat = params. liquidity_penalty_multiplier_msat ;
744+ let max_penalty_msat = multiplier_msat. saturating_mul ( NEGATIVE_LOG10_UPPER_BOUND ) ;
745+ ( negative_log10_times_1024. saturating_mul ( multiplier_msat) / 1024 ) . min ( max_penalty_msat)
746+ } ;
747+ let amount_penalty_msat = negative_log10_times_1024
748+ . saturating_mul ( params. amount_penalty_multiplier_msat )
749+ . saturating_mul ( amount_msat) / 1024 / AMOUNT_PENALTY_DIVISOR ;
750+
751+ params. base_penalty_msat
752+ . saturating_add ( liquidity_penalty_msat)
753+ . saturating_add ( amount_penalty_msat)
754+ }
755+
696756 /// Returns the lower bound of the channel liquidity balance in this direction.
697757 fn min_liquidity_msat ( & self ) -> u64 {
698758 self . decayed_offset_msat ( * self . min_liquidity_offset_msat )
@@ -762,14 +822,12 @@ impl<G: Deref<Target = NetworkGraph>, T: Time> Score for ProbabilisticScorerUsin
762822 & self , short_channel_id : u64 , amount_msat : u64 , capacity_msat : u64 , source : & NodeId ,
763823 target : & NodeId
764824 ) -> u64 {
765- let liquidity_penalty_multiplier_msat = self . params . liquidity_penalty_multiplier_msat ;
766825 let liquidity_offset_half_life = self . params . liquidity_offset_half_life ;
767826 self . channel_liquidities
768827 . get ( & short_channel_id)
769828 . unwrap_or ( & ChannelLiquidity :: new ( ) )
770829 . 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 )
830+ . penalty_msat ( amount_msat, self . params )
773831 }
774832
775833 fn payment_path_failed ( & mut self , path : & [ & RouteHop ] , short_channel_id : u64 ) {
@@ -1747,7 +1805,8 @@ mod tests {
17471805 fn increased_penalty_nearing_liquidity_upper_bound ( ) {
17481806 let network_graph = network_graph ( ) ;
17491807 let params = ProbabilisticScoringParameters {
1750- base_penalty_msat : 0 , liquidity_penalty_multiplier_msat : 1_000 , ..Default :: default ( )
1808+ liquidity_penalty_multiplier_msat : 1_000 ,
1809+ ..ProbabilisticScoringParameters :: zero_penalty ( )
17511810 } ;
17521811 let scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
17531812 let source = source_node_id ( ) ;
@@ -1772,7 +1831,8 @@ mod tests {
17721831 let last_updated = SinceEpoch :: now ( ) ;
17731832 let network_graph = network_graph ( ) ;
17741833 let params = ProbabilisticScoringParameters {
1775- base_penalty_msat : 0 , liquidity_penalty_multiplier_msat : 1_000 , ..Default :: default ( )
1834+ liquidity_penalty_multiplier_msat : 1_000 ,
1835+ ..ProbabilisticScoringParameters :: zero_penalty ( )
17761836 } ;
17771837 let scorer = ProbabilisticScorer :: new ( params, & network_graph)
17781838 . with_channel ( 42 ,
@@ -1792,7 +1852,8 @@ mod tests {
17921852 fn does_not_further_penalize_own_channel ( ) {
17931853 let network_graph = network_graph ( ) ;
17941854 let params = ProbabilisticScoringParameters {
1795- base_penalty_msat : 0 , liquidity_penalty_multiplier_msat : 1_000 , ..Default :: default ( )
1855+ liquidity_penalty_multiplier_msat : 1_000 ,
1856+ ..ProbabilisticScoringParameters :: zero_penalty ( )
17961857 } ;
17971858 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
17981859 let sender = sender_node_id ( ) ;
@@ -1813,7 +1874,8 @@ mod tests {
18131874 fn sets_liquidity_lower_bound_on_downstream_failure ( ) {
18141875 let network_graph = network_graph ( ) ;
18151876 let params = ProbabilisticScoringParameters {
1816- base_penalty_msat : 0 , liquidity_penalty_multiplier_msat : 1_000 , ..Default :: default ( )
1877+ liquidity_penalty_multiplier_msat : 1_000 ,
1878+ ..ProbabilisticScoringParameters :: zero_penalty ( )
18171879 } ;
18181880 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
18191881 let source = source_node_id ( ) ;
@@ -1835,7 +1897,8 @@ mod tests {
18351897 fn sets_liquidity_upper_bound_on_failure ( ) {
18361898 let network_graph = network_graph ( ) ;
18371899 let params = ProbabilisticScoringParameters {
1838- base_penalty_msat : 0 , liquidity_penalty_multiplier_msat : 1_000 , ..Default :: default ( )
1900+ liquidity_penalty_multiplier_msat : 1_000 ,
1901+ ..ProbabilisticScoringParameters :: zero_penalty ( )
18391902 } ;
18401903 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
18411904 let source = source_node_id ( ) ;
@@ -1857,7 +1920,8 @@ mod tests {
18571920 fn reduces_liquidity_upper_bound_along_path_on_success ( ) {
18581921 let network_graph = network_graph ( ) ;
18591922 let params = ProbabilisticScoringParameters {
1860- base_penalty_msat : 0 , liquidity_penalty_multiplier_msat : 1_000 , ..Default :: default ( )
1923+ liquidity_penalty_multiplier_msat : 1_000 ,
1924+ ..ProbabilisticScoringParameters :: zero_penalty ( )
18611925 } ;
18621926 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
18631927 let sender = sender_node_id ( ) ;
@@ -1881,9 +1945,9 @@ mod tests {
18811945 fn decays_liquidity_bounds_over_time ( ) {
18821946 let network_graph = network_graph ( ) ;
18831947 let params = ProbabilisticScoringParameters {
1884- base_penalty_msat : 0 ,
18851948 liquidity_penalty_multiplier_msat : 1_000 ,
18861949 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
1950+ ..ProbabilisticScoringParameters :: zero_penalty ( )
18871951 } ;
18881952 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
18891953 let source = source_node_id ( ) ;
@@ -1933,9 +1997,9 @@ mod tests {
19331997 fn decays_liquidity_bounds_without_shift_overflow ( ) {
19341998 let network_graph = network_graph ( ) ;
19351999 let params = ProbabilisticScoringParameters {
1936- base_penalty_msat : 0 ,
19372000 liquidity_penalty_multiplier_msat : 1_000 ,
19382001 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
2002+ ..ProbabilisticScoringParameters :: zero_penalty ( )
19392003 } ;
19402004 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
19412005 let source = source_node_id ( ) ;
@@ -1958,9 +2022,9 @@ mod tests {
19582022 fn restricts_liquidity_bounds_after_decay ( ) {
19592023 let network_graph = network_graph ( ) ;
19602024 let params = ProbabilisticScoringParameters {
1961- base_penalty_msat : 0 ,
19622025 liquidity_penalty_multiplier_msat : 1_000 ,
19632026 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
2027+ ..ProbabilisticScoringParameters :: zero_penalty ( )
19642028 } ;
19652029 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
19662030 let source = source_node_id ( ) ;
@@ -1996,9 +2060,9 @@ mod tests {
19962060 fn restores_persisted_liquidity_bounds ( ) {
19972061 let network_graph = network_graph ( ) ;
19982062 let params = ProbabilisticScoringParameters {
1999- base_penalty_msat : 0 ,
20002063 liquidity_penalty_multiplier_msat : 1_000 ,
20012064 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
2065+ ..ProbabilisticScoringParameters :: zero_penalty ( )
20022066 } ;
20032067 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
20042068 let source = source_node_id ( ) ;
@@ -2026,9 +2090,9 @@ mod tests {
20262090 fn decays_persisted_liquidity_bounds ( ) {
20272091 let network_graph = network_graph ( ) ;
20282092 let params = ProbabilisticScoringParameters {
2029- base_penalty_msat : 0 ,
20302093 liquidity_penalty_multiplier_msat : 1_000 ,
20312094 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
2095+ ..ProbabilisticScoringParameters :: zero_penalty ( )
20322096 } ;
20332097 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
20342098 let source = source_node_id ( ) ;
@@ -2061,7 +2125,8 @@ mod tests {
20612125 let target = target_node_id ( ) ;
20622126
20632127 let params = ProbabilisticScoringParameters {
2064- base_penalty_msat : 0 , liquidity_penalty_multiplier_msat : 1_000 , ..Default :: default ( )
2128+ liquidity_penalty_multiplier_msat : 1_000 ,
2129+ ..ProbabilisticScoringParameters :: zero_penalty ( )
20652130 } ;
20662131 let scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
20672132 assert_eq ! ( scorer. channel_penalty_msat( 42 , 128 , 1_024 , & source, & target) , 58 ) ;
@@ -2073,14 +2138,38 @@ mod tests {
20732138 assert_eq ! ( scorer. channel_penalty_msat( 42 , 128 , 1_024 , & source, & target) , 558 ) ;
20742139 }
20752140
2141+ #[ test]
2142+ fn adds_amount_penalty_to_liquidity_penalty ( ) {
2143+ let network_graph = network_graph ( ) ;
2144+ let source = source_node_id ( ) ;
2145+ let target = target_node_id ( ) ;
2146+
2147+ let params = ProbabilisticScoringParameters {
2148+ liquidity_penalty_multiplier_msat : 1_000 ,
2149+ amount_penalty_multiplier_msat : 0 ,
2150+ ..ProbabilisticScoringParameters :: zero_penalty ( )
2151+ } ;
2152+ let scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
2153+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 512_000 , 1_024_000 , & source, & target) , 300 ) ;
2154+
2155+ let params = ProbabilisticScoringParameters {
2156+ liquidity_penalty_multiplier_msat : 1_000 ,
2157+ amount_penalty_multiplier_msat : 256 ,
2158+ ..ProbabilisticScoringParameters :: zero_penalty ( )
2159+ } ;
2160+ let scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
2161+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 512_000 , 1_024_000 , & source, & target) , 337 ) ;
2162+ }
2163+
20762164 #[ test]
20772165 fn calculates_log10_without_overflowing_u64_max_value ( ) {
20782166 let network_graph = network_graph ( ) ;
20792167 let source = source_node_id ( ) ;
20802168 let target = target_node_id ( ) ;
20812169
20822170 let params = ProbabilisticScoringParameters {
2083- base_penalty_msat : 0 , ..Default :: default ( )
2171+ liquidity_penalty_multiplier_msat : 40_000 ,
2172+ ..ProbabilisticScoringParameters :: zero_penalty ( )
20842173 } ;
20852174 let scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
20862175 assert_eq ! (
0 commit comments