-
Notifications
You must be signed in to change notification settings - Fork 411
Don't over-penalize channels with inflight HTLCs #3356
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
866588c
7fa6770
572fab7
0305000
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -491,13 +491,12 @@ pub struct ProbabilisticScoringFeeParameters { | |
/// Default value: 500 msat | ||
pub base_penalty_msat: u64, | ||
|
||
/// A multiplier used with the total amount flowing over a channel to calculate a fixed penalty | ||
/// applied to each channel, in excess of the [`base_penalty_msat`]. | ||
/// A multiplier used with the payment amount to calculate a fixed penalty applied to each | ||
/// channel, in excess of the [`base_penalty_msat`]. | ||
/// | ||
/// The purpose of the amount penalty is to avoid having fees dominate the channel cost (i.e., | ||
/// fees plus penalty) for large payments. The penalty is computed as the product of this | ||
/// multiplier and `2^30`ths of the total amount flowing over a channel (i.e. the payment | ||
/// amount plus the amount of any other HTLCs flowing we sent over the same channel). | ||
/// multiplier and `2^30`ths of the payment amount. | ||
/// | ||
/// ie `base_penalty_amount_multiplier_msat * amount_msat / 2^30` | ||
/// | ||
|
@@ -524,14 +523,14 @@ pub struct ProbabilisticScoringFeeParameters { | |
/// [`liquidity_offset_half_life`]: ProbabilisticScoringDecayParameters::liquidity_offset_half_life | ||
pub liquidity_penalty_multiplier_msat: u64, | ||
|
||
/// A multiplier used in conjunction with the total amount flowing over a channel and the | ||
/// negative `log10` of the channel's success probability for the payment, as determined by our | ||
/// latest estimates of the channel's liquidity, to determine the amount penalty. | ||
/// A multiplier used in conjunction with the payment amount and the negative `log10` of the | ||
/// channel's success probability for the total amount flowing over a channel, as determined by | ||
/// our latest estimates of the channel's liquidity, to determine the amount penalty. | ||
/// | ||
/// The purpose of the amount penalty is to avoid having fees dominate the channel cost (i.e., | ||
/// fees plus penalty) for large payments. The penalty is computed as the product of this | ||
/// multiplier and `2^20`ths of the amount flowing over this channel, weighted by the negative | ||
/// `log10` of the success probability. | ||
/// multiplier and `2^20`ths of the payment amount, weighted by the negative `log10` of the | ||
/// success probability. | ||
/// | ||
/// `-log10(success_probability) * liquidity_penalty_amount_multiplier_msat * amount_msat / 2^20` | ||
/// | ||
|
@@ -560,15 +559,14 @@ pub struct ProbabilisticScoringFeeParameters { | |
/// [`liquidity_penalty_multiplier_msat`]: Self::liquidity_penalty_multiplier_msat | ||
pub historical_liquidity_penalty_multiplier_msat: u64, | ||
|
||
/// A multiplier used in conjunction with the total amount flowing over a channel and the | ||
/// negative `log10` of the channel's success probability for the payment, as determined based | ||
/// on the history of our estimates of the channel's available liquidity, to determine a | ||
/// A multiplier used in conjunction with the payment amount and the negative `log10` of the | ||
/// channel's success probability for the total amount flowing over a channel, as determined | ||
/// based on the history of our estimates of the channel's available liquidity, to determine a | ||
/// penalty. | ||
/// | ||
/// The purpose of the amount penalty is to avoid having fees dominate the channel cost for | ||
/// large payments. The penalty is computed as the product of this multiplier and `2^20`ths | ||
/// of the amount flowing over this channel, weighted by the negative `log10` of the success | ||
/// probability. | ||
/// of the payment amount, weighted by the negative `log10` of the success probability. | ||
/// | ||
/// This penalty is similar to [`liquidity_penalty_amount_multiplier_msat`], however, instead | ||
/// of using only our latest estimate for the current liquidity available in the channel, it | ||
|
@@ -1076,26 +1074,30 @@ fn three_f64_pow_3(a: f64, b: f64, c: f64) -> (f64, f64, f64) { | |
/// | ||
/// Must not return a numerator or denominator greater than 2^31 for arguments less than 2^31. | ||
/// | ||
/// `total_inflight_amount_msat` includes the amount of the HTLC and any HTLCs in flight over the | ||
/// channel. | ||
/// | ||
/// min_zero_implies_no_successes signals that a `min_liquidity_msat` of 0 means we've not | ||
/// (recently) seen an HTLC successfully complete over this channel. | ||
#[inline(always)] | ||
fn success_probability( | ||
amount_msat: u64, min_liquidity_msat: u64, max_liquidity_msat: u64, capacity_msat: u64, | ||
params: &ProbabilisticScoringFeeParameters, min_zero_implies_no_successes: bool, | ||
total_inflight_amount_msat: u64, min_liquidity_msat: u64, max_liquidity_msat: u64, | ||
capacity_msat: u64, params: &ProbabilisticScoringFeeParameters, | ||
min_zero_implies_no_successes: bool, | ||
) -> (u64, u64) { | ||
debug_assert!(min_liquidity_msat <= amount_msat); | ||
debug_assert!(amount_msat < max_liquidity_msat); | ||
debug_assert!(min_liquidity_msat <= total_inflight_amount_msat); | ||
debug_assert!(total_inflight_amount_msat < max_liquidity_msat); | ||
debug_assert!(max_liquidity_msat <= capacity_msat); | ||
|
||
let (numerator, mut denominator) = | ||
if params.linear_success_probability { | ||
(max_liquidity_msat - amount_msat, | ||
(max_liquidity_msat - total_inflight_amount_msat, | ||
(max_liquidity_msat - min_liquidity_msat).saturating_add(1)) | ||
} else { | ||
let capacity = capacity_msat as f64; | ||
let min = (min_liquidity_msat as f64) / capacity; | ||
let max = (max_liquidity_msat as f64) / capacity; | ||
let amount = (amount_msat as f64) / capacity; | ||
let amount = (total_inflight_amount_msat as f64) / capacity; | ||
|
||
// Assume the channel has a probability density function of (x - 0.5)^2 for values from | ||
// 0 to 1 (where 1 is the channel's full capacity). The success probability given some | ||
|
@@ -1138,14 +1140,18 @@ impl<L: Deref<Target = u64>, HT: Deref<Target = HistoricalLiquidityTracker>, T: | |
DirectedChannelLiquidity< L, HT, T> { | ||
/// Returns a liquidity penalty for routing the given HTLC `amount_msat` through the channel in | ||
/// this direction. | ||
fn penalty_msat(&self, amount_msat: u64, score_params: &ProbabilisticScoringFeeParameters) -> u64 { | ||
fn penalty_msat( | ||
&self, amount_msat: u64, inflight_htlc_msat: u64, | ||
score_params: &ProbabilisticScoringFeeParameters, | ||
) -> u64 { | ||
let total_inflight_amount_msat = amount_msat.saturating_add(inflight_htlc_msat); | ||
let available_capacity = self.capacity_msat; | ||
let max_liquidity_msat = self.max_liquidity_msat(); | ||
let min_liquidity_msat = core::cmp::min(self.min_liquidity_msat(), max_liquidity_msat); | ||
|
||
let mut res = if amount_msat <= min_liquidity_msat { | ||
let mut res = if total_inflight_amount_msat <= min_liquidity_msat { | ||
0 | ||
} else if amount_msat >= max_liquidity_msat { | ||
} else if total_inflight_amount_msat >= max_liquidity_msat { | ||
// Equivalent to hitting the else clause below with the amount equal to the effective | ||
// capacity and without any certainty on the liquidity upper bound, plus the | ||
// impossibility penalty. | ||
|
@@ -1155,8 +1161,10 @@ DirectedChannelLiquidity< L, HT, T> { | |
score_params.liquidity_penalty_amount_multiplier_msat) | ||
.saturating_add(score_params.considered_impossible_penalty_msat) | ||
} else { | ||
let (numerator, denominator) = success_probability(amount_msat, | ||
min_liquidity_msat, max_liquidity_msat, available_capacity, score_params, false); | ||
let (numerator, denominator) = success_probability( | ||
jkczyz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
total_inflight_amount_msat, min_liquidity_msat, max_liquidity_msat, | ||
available_capacity, score_params, false, | ||
); | ||
if denominator - numerator < denominator / PRECISION_LOWER_BOUND_DENOMINATOR { | ||
// If the failure probability is < 1.5625% (as 1 - numerator/denominator < 1/64), | ||
// don't bother trying to use the log approximation as it gets too noisy to be | ||
|
@@ -1171,7 +1179,7 @@ DirectedChannelLiquidity< L, HT, T> { | |
} | ||
}; | ||
|
||
if amount_msat >= available_capacity { | ||
if total_inflight_amount_msat >= available_capacity { | ||
// We're trying to send more than the capacity, use a max penalty. | ||
res = res.saturating_add(Self::combined_penalty_msat(amount_msat, | ||
NEGATIVE_LOG10_UPPER_BOUND * 2048, | ||
jkczyz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
@@ -1184,7 +1192,8 @@ DirectedChannelLiquidity< L, HT, T> { | |
score_params.historical_liquidity_penalty_amount_multiplier_msat != 0 { | ||
if let Some(cumulative_success_prob_times_billion) = self.liquidity_history | ||
.calculate_success_probability_times_billion( | ||
score_params, amount_msat, self.capacity_msat) | ||
score_params, total_inflight_amount_msat, self.capacity_msat | ||
) | ||
{ | ||
let historical_negative_log10_times_2048 = | ||
log_approx::negative_log10_times_2048(cumulative_success_prob_times_billion + 1, 1024 * 1024 * 1024); | ||
|
@@ -1195,8 +1204,10 @@ DirectedChannelLiquidity< L, HT, T> { | |
// If we don't have any valid points (or, once decayed, we have less than a full | ||
// point), redo the non-historical calculation with no liquidity bounds tracked and | ||
// the historical penalty multipliers. | ||
let (numerator, denominator) = success_probability(amount_msat, 0, | ||
available_capacity, available_capacity, score_params, true); | ||
let (numerator, denominator) = success_probability( | ||
total_inflight_amount_msat, 0, available_capacity, available_capacity, | ||
score_params, true, | ||
); | ||
let negative_log10_times_2048 = | ||
log_approx::negative_log10_times_2048(numerator, denominator); | ||
res = res.saturating_add(Self::combined_penalty_msat(amount_msat, negative_log10_times_2048, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doubt: Can you explain why I understand it might not be working as designed/intended at first but how was it wrong? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIUC, we want to use the total amount when computing the success probability since in-flight HTLCs will affect this. However, when computing the actual penalties we use the payment amount (i.e., That said, as written, our docs may need to be clearer about what amount is used to compute the success probability (total amount) and what amount is used for the penalty (payment amount w/ success probability). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Updated the docs in the latest push, BTW. |
||
|
@@ -1353,13 +1364,12 @@ impl<G: Deref<Target = NetworkGraph<L>>, L: Deref> ScoreLookUp for Probabilistic | |
_ => {}, | ||
} | ||
|
||
let amount_msat = usage.amount_msat.saturating_add(usage.inflight_htlc_msat); | ||
let capacity_msat = usage.effective_capacity.as_msat(); | ||
self.channel_liquidities | ||
.get(scid) | ||
.unwrap_or(&ChannelLiquidity::new(Duration::ZERO)) | ||
.as_directed(&source, &target, capacity_msat) | ||
.penalty_msat(amount_msat, score_params) | ||
.penalty_msat(usage.amount_msat, usage.inflight_htlc_msat, score_params) | ||
.saturating_add(anti_probing_penalty_msat) | ||
.saturating_add(base_penalty_msat) | ||
} | ||
|
@@ -1773,7 +1783,7 @@ mod bucketed_history { | |
|
||
#[inline] | ||
pub(super) fn calculate_success_probability_times_billion( | ||
&self, params: &ProbabilisticScoringFeeParameters, amount_msat: u64, | ||
&self, params: &ProbabilisticScoringFeeParameters, total_inflight_amount_msat: u64, | ||
capacity_msat: u64 | ||
) -> Option<u64> { | ||
// If historical penalties are enabled, we try to calculate a probability of success | ||
|
@@ -1783,7 +1793,7 @@ mod bucketed_history { | |
// state). For each pair, we calculate the probability as if the bucket's corresponding | ||
// min- and max- liquidity bounds were our current liquidity bounds and then multiply | ||
// that probability by the weight of the selected buckets. | ||
let payment_pos = amount_to_pos(amount_msat, capacity_msat); | ||
let payment_pos = amount_to_pos(total_inflight_amount_msat, capacity_msat); | ||
if payment_pos >= POSITION_TICKS { return None; } | ||
|
||
let min_liquidity_offset_history_buckets = | ||
|
@@ -3269,7 +3279,7 @@ mod tests { | |
short_channel_id: 42, | ||
}); | ||
|
||
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 2050); | ||
assert_eq!(scorer.channel_penalty_msat(&candidate, usage, ¶ms), 2048); | ||
|
||
let usage = ChannelUsage { | ||
amount_msat: 1, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: there is a typo in last commit's description,
mount -> amount.