@@ -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 comprises 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,41 @@ 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+ /// Computes the liquidity and amount penalties and adds them to the base penalty.
737+ #[ inline( always) ]
738+ fn combined_penalty_msat (
739+ & self , amount_msat : u64 , negative_log10_times_1024 : u64 ,
740+ params : ProbabilisticScoringParameters
741+ ) -> u64 {
742+ let liquidity_penalty_msat = {
743+ // Upper bound the liquidity penalty to ensure some channel is selected.
744+ let multiplier_msat = params. liquidity_penalty_multiplier_msat ;
745+ let max_penalty_msat = multiplier_msat. saturating_mul ( NEGATIVE_LOG10_UPPER_BOUND ) ;
746+ ( negative_log10_times_1024. saturating_mul ( multiplier_msat) / 1024 ) . min ( max_penalty_msat)
747+ } ;
748+ let amount_penalty_msat = negative_log10_times_1024
749+ . saturating_mul ( params. amount_penalty_multiplier_msat )
750+ . saturating_mul ( amount_msat) / 1024 / AMOUNT_PENALTY_DIVISOR ;
751+
752+ params. base_penalty_msat
753+ . saturating_add ( liquidity_penalty_msat)
754+ . saturating_add ( amount_penalty_msat)
755+ }
756+
696757 /// Returns the lower bound of the channel liquidity balance in this direction.
697758 fn min_liquidity_msat ( & self ) -> u64 {
698759 self . decayed_offset_msat ( * self . min_liquidity_offset_msat )
@@ -762,14 +823,12 @@ impl<G: Deref<Target = NetworkGraph>, T: Time> Score for ProbabilisticScorerUsin
762823 & self , short_channel_id : u64 , amount_msat : u64 , capacity_msat : u64 , source : & NodeId ,
763824 target : & NodeId
764825 ) -> u64 {
765- let liquidity_penalty_multiplier_msat = self . params . liquidity_penalty_multiplier_msat ;
766826 let liquidity_offset_half_life = self . params . liquidity_offset_half_life ;
767827 self . channel_liquidities
768828 . get ( & short_channel_id)
769829 . unwrap_or ( & ChannelLiquidity :: new ( ) )
770830 . 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 )
831+ . penalty_msat ( amount_msat, self . params )
773832 }
774833
775834 fn payment_path_failed ( & mut self , path : & [ & RouteHop ] , short_channel_id : u64 ) {
@@ -1747,7 +1806,8 @@ mod tests {
17471806 fn increased_penalty_nearing_liquidity_upper_bound ( ) {
17481807 let network_graph = network_graph ( ) ;
17491808 let params = ProbabilisticScoringParameters {
1750- base_penalty_msat : 0 , liquidity_penalty_multiplier_msat : 1_000 , ..Default :: default ( )
1809+ liquidity_penalty_multiplier_msat : 1_000 ,
1810+ ..ProbabilisticScoringParameters :: zero_penalty ( )
17511811 } ;
17521812 let scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
17531813 let source = source_node_id ( ) ;
@@ -1772,7 +1832,8 @@ mod tests {
17721832 let last_updated = SinceEpoch :: now ( ) ;
17731833 let network_graph = network_graph ( ) ;
17741834 let params = ProbabilisticScoringParameters {
1775- base_penalty_msat : 0 , liquidity_penalty_multiplier_msat : 1_000 , ..Default :: default ( )
1835+ liquidity_penalty_multiplier_msat : 1_000 ,
1836+ ..ProbabilisticScoringParameters :: zero_penalty ( )
17761837 } ;
17771838 let scorer = ProbabilisticScorer :: new ( params, & network_graph)
17781839 . with_channel ( 42 ,
@@ -1792,7 +1853,8 @@ mod tests {
17921853 fn does_not_further_penalize_own_channel ( ) {
17931854 let network_graph = network_graph ( ) ;
17941855 let params = ProbabilisticScoringParameters {
1795- base_penalty_msat : 0 , liquidity_penalty_multiplier_msat : 1_000 , ..Default :: default ( )
1856+ liquidity_penalty_multiplier_msat : 1_000 ,
1857+ ..ProbabilisticScoringParameters :: zero_penalty ( )
17961858 } ;
17971859 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
17981860 let sender = sender_node_id ( ) ;
@@ -1813,7 +1875,8 @@ mod tests {
18131875 fn sets_liquidity_lower_bound_on_downstream_failure ( ) {
18141876 let network_graph = network_graph ( ) ;
18151877 let params = ProbabilisticScoringParameters {
1816- base_penalty_msat : 0 , liquidity_penalty_multiplier_msat : 1_000 , ..Default :: default ( )
1878+ liquidity_penalty_multiplier_msat : 1_000 ,
1879+ ..ProbabilisticScoringParameters :: zero_penalty ( )
18171880 } ;
18181881 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
18191882 let source = source_node_id ( ) ;
@@ -1835,7 +1898,8 @@ mod tests {
18351898 fn sets_liquidity_upper_bound_on_failure ( ) {
18361899 let network_graph = network_graph ( ) ;
18371900 let params = ProbabilisticScoringParameters {
1838- base_penalty_msat : 0 , liquidity_penalty_multiplier_msat : 1_000 , ..Default :: default ( )
1901+ liquidity_penalty_multiplier_msat : 1_000 ,
1902+ ..ProbabilisticScoringParameters :: zero_penalty ( )
18391903 } ;
18401904 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
18411905 let source = source_node_id ( ) ;
@@ -1857,7 +1921,8 @@ mod tests {
18571921 fn reduces_liquidity_upper_bound_along_path_on_success ( ) {
18581922 let network_graph = network_graph ( ) ;
18591923 let params = ProbabilisticScoringParameters {
1860- base_penalty_msat : 0 , liquidity_penalty_multiplier_msat : 1_000 , ..Default :: default ( )
1924+ liquidity_penalty_multiplier_msat : 1_000 ,
1925+ ..ProbabilisticScoringParameters :: zero_penalty ( )
18611926 } ;
18621927 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
18631928 let sender = sender_node_id ( ) ;
@@ -1881,9 +1946,9 @@ mod tests {
18811946 fn decays_liquidity_bounds_over_time ( ) {
18821947 let network_graph = network_graph ( ) ;
18831948 let params = ProbabilisticScoringParameters {
1884- base_penalty_msat : 0 ,
18851949 liquidity_penalty_multiplier_msat : 1_000 ,
18861950 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
1951+ ..ProbabilisticScoringParameters :: zero_penalty ( )
18871952 } ;
18881953 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
18891954 let source = source_node_id ( ) ;
@@ -1933,9 +1998,9 @@ mod tests {
19331998 fn decays_liquidity_bounds_without_shift_overflow ( ) {
19341999 let network_graph = network_graph ( ) ;
19352000 let params = ProbabilisticScoringParameters {
1936- base_penalty_msat : 0 ,
19372001 liquidity_penalty_multiplier_msat : 1_000 ,
19382002 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
2003+ ..ProbabilisticScoringParameters :: zero_penalty ( )
19392004 } ;
19402005 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
19412006 let source = source_node_id ( ) ;
@@ -1958,9 +2023,9 @@ mod tests {
19582023 fn restricts_liquidity_bounds_after_decay ( ) {
19592024 let network_graph = network_graph ( ) ;
19602025 let params = ProbabilisticScoringParameters {
1961- base_penalty_msat : 0 ,
19622026 liquidity_penalty_multiplier_msat : 1_000 ,
19632027 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
2028+ ..ProbabilisticScoringParameters :: zero_penalty ( )
19642029 } ;
19652030 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
19662031 let source = source_node_id ( ) ;
@@ -1996,9 +2061,9 @@ mod tests {
19962061 fn restores_persisted_liquidity_bounds ( ) {
19972062 let network_graph = network_graph ( ) ;
19982063 let params = ProbabilisticScoringParameters {
1999- base_penalty_msat : 0 ,
20002064 liquidity_penalty_multiplier_msat : 1_000 ,
20012065 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
2066+ ..ProbabilisticScoringParameters :: zero_penalty ( )
20022067 } ;
20032068 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
20042069 let source = source_node_id ( ) ;
@@ -2026,9 +2091,9 @@ mod tests {
20262091 fn decays_persisted_liquidity_bounds ( ) {
20272092 let network_graph = network_graph ( ) ;
20282093 let params = ProbabilisticScoringParameters {
2029- base_penalty_msat : 0 ,
20302094 liquidity_penalty_multiplier_msat : 1_000 ,
20312095 liquidity_offset_half_life : Duration :: from_secs ( 10 ) ,
2096+ ..ProbabilisticScoringParameters :: zero_penalty ( )
20322097 } ;
20332098 let mut scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
20342099 let source = source_node_id ( ) ;
@@ -2061,7 +2126,8 @@ mod tests {
20612126 let target = target_node_id ( ) ;
20622127
20632128 let params = ProbabilisticScoringParameters {
2064- base_penalty_msat : 0 , liquidity_penalty_multiplier_msat : 1_000 , ..Default :: default ( )
2129+ liquidity_penalty_multiplier_msat : 1_000 ,
2130+ ..ProbabilisticScoringParameters :: zero_penalty ( )
20652131 } ;
20662132 let scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
20672133 assert_eq ! ( scorer. channel_penalty_msat( 42 , 128 , 1_024 , & source, & target) , 58 ) ;
@@ -2073,14 +2139,38 @@ mod tests {
20732139 assert_eq ! ( scorer. channel_penalty_msat( 42 , 128 , 1_024 , & source, & target) , 558 ) ;
20742140 }
20752141
2142+ #[ test]
2143+ fn adds_amount_penalty_to_liquidity_penalty ( ) {
2144+ let network_graph = network_graph ( ) ;
2145+ let source = source_node_id ( ) ;
2146+ let target = target_node_id ( ) ;
2147+
2148+ let params = ProbabilisticScoringParameters {
2149+ liquidity_penalty_multiplier_msat : 1_000 ,
2150+ amount_penalty_multiplier_msat : 0 ,
2151+ ..ProbabilisticScoringParameters :: zero_penalty ( )
2152+ } ;
2153+ let scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
2154+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 512_000 , 1_024_000 , & source, & target) , 300 ) ;
2155+
2156+ let params = ProbabilisticScoringParameters {
2157+ liquidity_penalty_multiplier_msat : 1_000 ,
2158+ amount_penalty_multiplier_msat : 256 ,
2159+ ..ProbabilisticScoringParameters :: zero_penalty ( )
2160+ } ;
2161+ let scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
2162+ assert_eq ! ( scorer. channel_penalty_msat( 42 , 512_000 , 1_024_000 , & source, & target) , 337 ) ;
2163+ }
2164+
20762165 #[ test]
20772166 fn calculates_log10_without_overflowing_u64_max_value ( ) {
20782167 let network_graph = network_graph ( ) ;
20792168 let source = source_node_id ( ) ;
20802169 let target = target_node_id ( ) ;
20812170
20822171 let params = ProbabilisticScoringParameters {
2083- base_penalty_msat : 0 , ..Default :: default ( )
2172+ liquidity_penalty_multiplier_msat : 40_000 ,
2173+ ..ProbabilisticScoringParameters :: zero_penalty ( )
20842174 } ;
20852175 let scorer = ProbabilisticScorer :: new ( params, & network_graph) ;
20862176 assert_eq ! (
0 commit comments