Avoid reusing just-failed channels in the router, making the impossibility penalty configurable - #1600

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-07-explicit-avoid-retries
Jul 15, 2022
Merged

Avoid reusing just-failed channels in the router, making the impossibility penalty configurable#1600
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-07-explicit-avoid-retries

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Jul 6, 2022

Copy link
Copy Markdown
Collaborator

Some users want to keep a very long scorer data half-life to maintain knowledge for a longer period of time. Its somewhat unclear if that's optimal, but it is clear that it can cause the scorer to refuse to build a route when the only available channel failed most recently within the halflife. This is rather unexpected behavior, and the fact that the scorer must behave this way to avoid #1241 is very annoying in that it prevents fixing this.

Here we move the avoidance of just-failed channels into the router itself, allowing us to make the impossibility penalty configurable, which we do as well.

Closes#1241, superseding #1252.

@codecov-commenter

codecov-commenter commented Jul 6, 2022

Copy link
Copy Markdown

Codecov Report

Merging #1600 (a863778) into main (4e5f74a) will increase coverage by 0.14%.
The diff coverage is 87.50%.

❗ Current head a863778 differs from pull request most recent head 5bff5f9. Consider uploading reports for the commit 5bff5f9 to get more accurate results

@@ Coverage Diff @@## main #1600 +/- ##
==========================================
+ Coverage 90.86% 91.00% +0.14% 
==========================================
Files 80 80 Lines 44437 45502 +1065 Branches 44437 45502 +1065 ==========================================
+ Hits 40377 41409 +1032 - Misses 4060 4093 +33 
Impacted FilesCoverage Δ
lightning/src/routing/router.rs92.45% <77.77%> (+0.06%)⬆️
lightning/src/routing/scoring.rs97.57% <95.00%> (+1.50%)⬆️
lightning/src/ln/channelmanager.rs84.90% <100.00%> (+0.02%)⬆️
lightning/src/ln/functional_test_utils.rs95.24% <100.00%> (+<0.01%)⬆️
lightning/src/chain/onchaintx.rs93.98% <0.00%> (-0.93%)⬇️
lightning/src/util/events.rs39.25% <0.00%> (-0.29%)⬇️
lightning/src/ln/functional_tests.rs96.95% <0.00%> (-0.17%)⬇️
lightning/src/ln/channel.rs88.77% <0.00%> (+0.02%)⬆️
... and 2 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4e5f74a...5bff5f9. Read the comment docs.

@tnull
tnull self-requested a review July 7, 2022 07:07

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, generally looks good, just some comments.

Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from 848afc3 to e6a114cCompareJuly 7, 2022 16:06
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and addressed feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from e6a114c to 846291fCompareJuly 7, 2022 17:07
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from e3396ef to cc1a045CompareJuly 8, 2022 14:25
tnull
tnull previously approved these changes Jul 11, 2022

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment threadlightning/src/routing/scoring.rs
Comment threadlightning/src/routing/scoring.rs
Comment threadlightning/src/routing/router.rs Outdated
Comment on lines 1004 to 1056
if contributes_sufficient_value && doesnt_exceed_max_path_length &&
doesnt_exceed_cltv_delta_limit && may_overpay_to_meet_path_minimum_msat {
doesnt_exceed_cltv_delta_limit && !payment_failed_on_this_channel &&
may_overpay_to_meet_path_minimum_msat
{
hit_minimum_limit = true;
} else if contributes_sufficient_value && doesnt_exceed_max_path_length &&
doesnt_exceed_cltv_delta_limit && over_path_minimum_msat {
doesnt_exceed_cltv_delta_limit && over_path_minimum_msat &&
!payment_failed_on_this_channel
{
// Note that low contribution here (limited by available_liquidity_msat)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of checking payment_failed_on_this_channel twice, consider having a leading if expression.

if payment_failed_on_this_channel {}elseif/* ... */{

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, good idea, I moved all the existing if conditions to that!

Comment threadlightning/src/routing/router.rs
Comment threadlightning/src/routing/scoring.rs Outdated
Comment on lines +629 to +646
// 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment needs to be updated now since it's not only when equal to the effective capacity. Seems like the earlier change that added this now removed logic is what caused calculates_log10_without_overflowing_u64_max_value to not exercise the correct code path as mentioned earlier on that test (i.e., it doesn't hit the negative_log10_times_2048 case below.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read the comment differently - I read the comment to say "the calculation we're doing here is equivalent to..." which is still true, no?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, you're right!

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Largely looks good but one comment.

Comment on lines +629 to +646
// 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, you're right!

Comment on lines +632 to +649
let negative_log10_times_2048 = NEGATIVE_LOG10_UPPER_BOUND * 2048;
self.combined_penalty_msat(amount_msat, negative_log10_times_2048, params)
.saturating_add(params.considered_impossible_penalty_msat)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to do the max of these two rather than adding? Is the idea that we want this to be >= anything given in the else clause?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, that's the idea. I suppose we could do a max. Originally I had it not adding the combined_penalty call at all but that seemed to brittle to deal with so switched to adding. I don't honestly have a strong opinion between adding and max, either way the docs can tell users what's going on, but for max i kinda worry users will acidentally set it too low and get no penalty here, which seems strange?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They would also need to set the other params lower, though, since max would select the combined penalty over the considered_impossible_penalty_msat if the latter were too low. So it's all kinda relative. Don't feel too strongly either though note that max may make debugging a little easier as otherwise you may need to mentally subtract some combined penalty if it is not obvious.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, sure, I just meant it'd be easy for a user to look at the field and think "okay, let me pick something a bit higher than the liquidity offset, forget to multiply by 2 or whatever, and end up with a penalty equal to the liquidity penalty, which seems wrong? I dunno, I'm happy to mentally convert when we're debugging. Unless you feel strongly I'd suggest we leave it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, sure, I just meant it'd be easy for a user to look at the field and think "okay, let me pick something a bit higher than the liquidity offset, forget to multiply by 2 or whatever, and end up with a penalty equal to the liquidity penalty, which seems wrong?

Plus base and amount penalty, FWIW.

I dunno, I'm happy to mentally convert when we're debugging. Unless you feel strongly I'd suggest we leave it.

Sure we can leave it. Though one thing I just realized is that either way now the penalty will be variable across channels depending on the amount. Maybe less so when using max but maybe that's an argument in favor of adding. That way if left to choose only channels exceeding the maximum liquidity, we'd prefer ones that would otherwise be penalized less.

jkczyz
jkczyz previously approved these changes Jul 13, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without further changes.

jkczyz
jkczyz previously approved these changes Jul 13, 2022
Comment threadlightning/src/routing/scoring.rs Outdated
jkczyz
jkczyz previously approved these changes Jul 13, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed yet again. Change since yesterday was:

diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs
index 6aa19abaa..9fa62d83f 100644
--- a/lightning/src/routing/scoring.rs
+++ b/lightning/src/routing/scoring.rs
@@ -397,7 +397,8 @@ pub struct ProbabilisticScoringParameters {
/// current estimate of the channel's available liquidity.
///
-	/// Note that in this case the [`liquidity_penalty_multiplier_msat`] and
-	/// [`amount_penalty_multiplier_msat`]-based penalties are still included in the overall
-	/// penalty.
+	/// Note that in this case all other penalties, including the
+	/// [`liquidity_penalty_multiplier_msat`] and [`amount_penalty_multiplier_msat`]-based
+	/// penalties, as well as the [`base_penalty_msat`] and the [`anti_probing_penalty_msat`], if
+	/// applicable, are still included in the overall penalty.
///
/// If you wish to avoid creating paths with such channels entirely, setting this to a value of
@@ -408,4 +409,6 @@ pub struct ProbabilisticScoringParameters {
/// [`liquidity_penalty_multiplier_msat`]: Self::liquidity_penalty_multiplier_msat
/// [`amount_penalty_multiplier_msat`]: Self::amount_penalty_multiplier_msat
+	/// [`base_penalty_msat`]: Self::base_penalty_msat
+	/// [`anti_probing_penalty_msat`]: Self::anti_probing_penalty_msat
pub considered_impossible_penalty_msat: u64,
}

jkczyz
jkczyz previously approved these changes Jul 13, 2022
tnull
tnull previously approved these changes Jul 14, 2022
@jkczyz

Copy link
Copy Markdown
Contributor

66ca68a mentions failures on a payment-level, but isn't it really on a path-level? i.e., if two parts of an MPP fail on different channels, retrying one part could retry over the channel failed on the other part?

@TheBlueMatt
TheBlueMatt dismissed stale reviews from tnull and jkczyz via 5bff5f9July 14, 2022 15:59
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from 8a1b418 to 5bff5f9CompareJuly 14, 2022 15:59
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops, right, sorry, rewrote that commit message without changes to the diff, added a note that it this does have the drawback of potentially retrying different parts along the same path, but hopefully the scorer doesn't let that happen unless the payment is gonna fail anyway.

@jkczyz

Copy link
Copy Markdown
Contributor

Oops, right, sorry, rewrote that commit message without changes to the diff, added a note that it this does have the drawback of potentially retrying different parts along the same path, but hopefully the scorer doesn't let that happen unless the payment is gonna fail anyway.

Yeah, same across payments, but as you said hopefully the scorer will learn quickly enough.

jkczyz
jkczyz previously approved these changes Jul 14, 2022
When an HTLC fails, we currently rely on the scorer learning the
failed channel and assigning an infinite (`u64::max_value()`)
penalty to the channel so as to avoid retrying over the exact same
path (if there's only one available path). This is common when
trying to pay a mobile client behind an LSP if the mobile client is
currently offline.
This leads to the scorer being overly conservative in some cases -
returning `u64::max_value()` when a given path hasn't been tried
for a given payment may not be the best decision, even if that
channel failed 50 minutes ago.
By tracking channels which failed on a payment part level and
explicitly refusing to route over them we can relax the
requirements on the scorer, allowing it to make different decisions
on how to treat channels that failed relatively recently without
causing payments to retry the same path forever.
This does have the drawback that it could allow two separate part
of a payment to traverse the same path even though that path just
failed, however this should only occur if the payment is going to
fail anyway, at least as long as the scorer is properly learning.
Closeslightningdevkit#1241, superseding lightningdevkit#1252.
When we consider sending an HTLC over a given channel impossible
due to our current knowledge of the channel's liquidity, we
currently always assign a penalty of `u64::max_value()`. However,
because we now refuse to retry a payment along the same path in
the router itself, we can now make this value configurable. This
allows users to have a relatively high knowledge decay interval
without the side-effect of refusing to try the only available path
in cases where a channel is intermittently available.
In general we should avoid taking paths that we are confident will
not work as much possible, but we should be willing to try each
payment at least once, even if its over a channel that failed
recently. A full Bitcoin penalty for such a channel seems
reasonable - lightning fees are unlikely to ever reach that point
so such channels will be scored much worse than any other potential
path, while still being below `u64::max_value()`.
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased to address merge conflict.

@TheBlueMatt
TheBlueMatt merged commit f75b6cb into lightningdevkit:mainJul 15, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Handle only-last-hop temp failure better

4 participants

@TheBlueMatt@codecov-commenter@jkczyz@tnull
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Avoid reusing just-failed channels in the router, making the impossibility penalty configurable - #1600

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-07-explicit-avoid-retries
Jul 15, 2022
Merged

Avoid reusing just-failed channels in the router, making the impossibility penalty configurable#1600
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-07-explicit-avoid-retries

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Jul 6, 2022

Copy link
Copy Markdown
Collaborator

Some users want to keep a very long scorer data half-life to maintain knowledge for a longer period of time. Its somewhat unclear if that's optimal, but it is clear that it can cause the scorer to refuse to build a route when the only available channel failed most recently within the halflife. This is rather unexpected behavior, and the fact that the scorer must behave this way to avoid #1241 is very annoying in that it prevents fixing this.

Here we move the avoidance of just-failed channels into the router itself, allowing us to make the impossibility penalty configurable, which we do as well.

Closes#1241, superseding #1252.

@codecov-commenter

codecov-commenter commented Jul 6, 2022

Copy link
Copy Markdown

Codecov Report

Merging #1600 (a863778) into main (4e5f74a) will increase coverage by 0.14%.
The diff coverage is 87.50%.

❗ Current head a863778 differs from pull request most recent head 5bff5f9. Consider uploading reports for the commit 5bff5f9 to get more accurate results

@@ Coverage Diff @@## main #1600 +/- ##
==========================================
+ Coverage 90.86% 91.00% +0.14% 
==========================================
Files 80 80 Lines 44437 45502 +1065 Branches 44437 45502 +1065 ==========================================
+ Hits 40377 41409 +1032 - Misses 4060 4093 +33 
Impacted FilesCoverage Δ
lightning/src/routing/router.rs92.45% <77.77%> (+0.06%)⬆️
lightning/src/routing/scoring.rs97.57% <95.00%> (+1.50%)⬆️
lightning/src/ln/channelmanager.rs84.90% <100.00%> (+0.02%)⬆️
lightning/src/ln/functional_test_utils.rs95.24% <100.00%> (+<0.01%)⬆️
lightning/src/chain/onchaintx.rs93.98% <0.00%> (-0.93%)⬇️
lightning/src/util/events.rs39.25% <0.00%> (-0.29%)⬇️
lightning/src/ln/functional_tests.rs96.95% <0.00%> (-0.17%)⬇️
lightning/src/ln/channel.rs88.77% <0.00%> (+0.02%)⬆️
... and 2 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4e5f74a...5bff5f9. Read the comment docs.

@tnull
tnull self-requested a review July 7, 2022 07:07

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, generally looks good, just some comments.

Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from 848afc3 to e6a114cCompareJuly 7, 2022 16:06
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and addressed feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from e6a114c to 846291fCompareJuly 7, 2022 17:07
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from e3396ef to cc1a045CompareJuly 8, 2022 14:25
tnull
tnull previously approved these changes Jul 11, 2022

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment threadlightning/src/routing/scoring.rs
Comment threadlightning/src/routing/scoring.rs
Comment threadlightning/src/routing/router.rs Outdated
Comment on lines 1004 to 1056
if contributes_sufficient_value && doesnt_exceed_max_path_length &&
doesnt_exceed_cltv_delta_limit && may_overpay_to_meet_path_minimum_msat {
doesnt_exceed_cltv_delta_limit && !payment_failed_on_this_channel &&
may_overpay_to_meet_path_minimum_msat
{
hit_minimum_limit = true;
} else if contributes_sufficient_value && doesnt_exceed_max_path_length &&
doesnt_exceed_cltv_delta_limit && over_path_minimum_msat {
doesnt_exceed_cltv_delta_limit && over_path_minimum_msat &&
!payment_failed_on_this_channel
{
// Note that low contribution here (limited by available_liquidity_msat)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of checking payment_failed_on_this_channel twice, consider having a leading if expression.

if payment_failed_on_this_channel {}elseif/* ... */{

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, good idea, I moved all the existing if conditions to that!

Comment threadlightning/src/routing/router.rs
Comment threadlightning/src/routing/scoring.rs Outdated
Comment on lines +629 to +646
// 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment needs to be updated now since it's not only when equal to the effective capacity. Seems like the earlier change that added this now removed logic is what caused calculates_log10_without_overflowing_u64_max_value to not exercise the correct code path as mentioned earlier on that test (i.e., it doesn't hit the negative_log10_times_2048 case below.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read the comment differently - I read the comment to say "the calculation we're doing here is equivalent to..." which is still true, no?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, you're right!

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Largely looks good but one comment.

Comment on lines +629 to +646
// 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, you're right!

Comment on lines +632 to +649
let negative_log10_times_2048 = NEGATIVE_LOG10_UPPER_BOUND * 2048;
self.combined_penalty_msat(amount_msat, negative_log10_times_2048, params)
.saturating_add(params.considered_impossible_penalty_msat)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to do the max of these two rather than adding? Is the idea that we want this to be >= anything given in the else clause?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, that's the idea. I suppose we could do a max. Originally I had it not adding the combined_penalty call at all but that seemed to brittle to deal with so switched to adding. I don't honestly have a strong opinion between adding and max, either way the docs can tell users what's going on, but for max i kinda worry users will acidentally set it too low and get no penalty here, which seems strange?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They would also need to set the other params lower, though, since max would select the combined penalty over the considered_impossible_penalty_msat if the latter were too low. So it's all kinda relative. Don't feel too strongly either though note that max may make debugging a little easier as otherwise you may need to mentally subtract some combined penalty if it is not obvious.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, sure, I just meant it'd be easy for a user to look at the field and think "okay, let me pick something a bit higher than the liquidity offset, forget to multiply by 2 or whatever, and end up with a penalty equal to the liquidity penalty, which seems wrong? I dunno, I'm happy to mentally convert when we're debugging. Unless you feel strongly I'd suggest we leave it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, sure, I just meant it'd be easy for a user to look at the field and think "okay, let me pick something a bit higher than the liquidity offset, forget to multiply by 2 or whatever, and end up with a penalty equal to the liquidity penalty, which seems wrong?

Plus base and amount penalty, FWIW.

I dunno, I'm happy to mentally convert when we're debugging. Unless you feel strongly I'd suggest we leave it.

Sure we can leave it. Though one thing I just realized is that either way now the penalty will be variable across channels depending on the amount. Maybe less so when using max but maybe that's an argument in favor of adding. That way if left to choose only channels exceeding the maximum liquidity, we'd prefer ones that would otherwise be penalized less.

jkczyz
jkczyz previously approved these changes Jul 13, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without further changes.

jkczyz
jkczyz previously approved these changes Jul 13, 2022
Comment threadlightning/src/routing/scoring.rs Outdated
jkczyz
jkczyz previously approved these changes Jul 13, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed yet again. Change since yesterday was:

diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs
index 6aa19abaa..9fa62d83f 100644
--- a/lightning/src/routing/scoring.rs
+++ b/lightning/src/routing/scoring.rs
@@ -397,7 +397,8 @@ pub struct ProbabilisticScoringParameters {
/// current estimate of the channel's available liquidity.
///
-	/// Note that in this case the [`liquidity_penalty_multiplier_msat`] and
-	/// [`amount_penalty_multiplier_msat`]-based penalties are still included in the overall
-	/// penalty.
+	/// Note that in this case all other penalties, including the
+	/// [`liquidity_penalty_multiplier_msat`] and [`amount_penalty_multiplier_msat`]-based
+	/// penalties, as well as the [`base_penalty_msat`] and the [`anti_probing_penalty_msat`], if
+	/// applicable, are still included in the overall penalty.
///
/// If you wish to avoid creating paths with such channels entirely, setting this to a value of
@@ -408,4 +409,6 @@ pub struct ProbabilisticScoringParameters {
/// [`liquidity_penalty_multiplier_msat`]: Self::liquidity_penalty_multiplier_msat
/// [`amount_penalty_multiplier_msat`]: Self::amount_penalty_multiplier_msat
+	/// [`base_penalty_msat`]: Self::base_penalty_msat
+	/// [`anti_probing_penalty_msat`]: Self::anti_probing_penalty_msat
pub considered_impossible_penalty_msat: u64,
}

jkczyz
jkczyz previously approved these changes Jul 13, 2022
tnull
tnull previously approved these changes Jul 14, 2022
@jkczyz

Copy link
Copy Markdown
Contributor

66ca68a mentions failures on a payment-level, but isn't it really on a path-level? i.e., if two parts of an MPP fail on different channels, retrying one part could retry over the channel failed on the other part?

@TheBlueMatt
TheBlueMatt dismissed stale reviews from tnull and jkczyz via 5bff5f9July 14, 2022 15:59
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from 8a1b418 to 5bff5f9CompareJuly 14, 2022 15:59
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops, right, sorry, rewrote that commit message without changes to the diff, added a note that it this does have the drawback of potentially retrying different parts along the same path, but hopefully the scorer doesn't let that happen unless the payment is gonna fail anyway.

@jkczyz

Copy link
Copy Markdown
Contributor

Oops, right, sorry, rewrote that commit message without changes to the diff, added a note that it this does have the drawback of potentially retrying different parts along the same path, but hopefully the scorer doesn't let that happen unless the payment is gonna fail anyway.

Yeah, same across payments, but as you said hopefully the scorer will learn quickly enough.

jkczyz
jkczyz previously approved these changes Jul 14, 2022
When an HTLC fails, we currently rely on the scorer learning the
failed channel and assigning an infinite (`u64::max_value()`)
penalty to the channel so as to avoid retrying over the exact same
path (if there's only one available path). This is common when
trying to pay a mobile client behind an LSP if the mobile client is
currently offline.
This leads to the scorer being overly conservative in some cases -
returning `u64::max_value()` when a given path hasn't been tried
for a given payment may not be the best decision, even if that
channel failed 50 minutes ago.
By tracking channels which failed on a payment part level and
explicitly refusing to route over them we can relax the
requirements on the scorer, allowing it to make different decisions
on how to treat channels that failed relatively recently without
causing payments to retry the same path forever.
This does have the drawback that it could allow two separate part
of a payment to traverse the same path even though that path just
failed, however this should only occur if the payment is going to
fail anyway, at least as long as the scorer is properly learning.
Closeslightningdevkit#1241, superseding lightningdevkit#1252.
When we consider sending an HTLC over a given channel impossible
due to our current knowledge of the channel's liquidity, we
currently always assign a penalty of `u64::max_value()`. However,
because we now refuse to retry a payment along the same path in
the router itself, we can now make this value configurable. This
allows users to have a relatively high knowledge decay interval
without the side-effect of refusing to try the only available path
in cases where a channel is intermittently available.
In general we should avoid taking paths that we are confident will
not work as much possible, but we should be willing to try each
payment at least once, even if its over a channel that failed
recently. A full Bitcoin penalty for such a channel seems
reasonable - lightning fees are unlikely to ever reach that point
so such channels will be scored much worse than any other potential
path, while still being below `u64::max_value()`.
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased to address merge conflict.

@TheBlueMatt
TheBlueMatt merged commit f75b6cb into lightningdevkit:mainJul 15, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Handle only-last-hop temp failure better

4 participants

@TheBlueMatt@codecov-commenter@jkczyz@tnull
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Avoid reusing just-failed channels in the router, making the impossibility penalty configurable - #1600

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-07-explicit-avoid-retries
Jul 15, 2022
Merged

Avoid reusing just-failed channels in the router, making the impossibility penalty configurable#1600
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-07-explicit-avoid-retries

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Jul 6, 2022

Copy link
Copy Markdown
Collaborator

Some users want to keep a very long scorer data half-life to maintain knowledge for a longer period of time. Its somewhat unclear if that's optimal, but it is clear that it can cause the scorer to refuse to build a route when the only available channel failed most recently within the halflife. This is rather unexpected behavior, and the fact that the scorer must behave this way to avoid #1241 is very annoying in that it prevents fixing this.

Here we move the avoidance of just-failed channels into the router itself, allowing us to make the impossibility penalty configurable, which we do as well.

Closes#1241, superseding #1252.

@codecov-commenter

codecov-commenter commented Jul 6, 2022

Copy link
Copy Markdown

Codecov Report

Merging #1600 (a863778) into main (4e5f74a) will increase coverage by 0.14%.
The diff coverage is 87.50%.

❗ Current head a863778 differs from pull request most recent head 5bff5f9. Consider uploading reports for the commit 5bff5f9 to get more accurate results

@@ Coverage Diff @@## main #1600 +/- ##
==========================================
+ Coverage 90.86% 91.00% +0.14% 
==========================================
Files 80 80 Lines 44437 45502 +1065 Branches 44437 45502 +1065 ==========================================
+ Hits 40377 41409 +1032 - Misses 4060 4093 +33 
Impacted FilesCoverage Δ
lightning/src/routing/router.rs92.45% <77.77%> (+0.06%)⬆️
lightning/src/routing/scoring.rs97.57% <95.00%> (+1.50%)⬆️
lightning/src/ln/channelmanager.rs84.90% <100.00%> (+0.02%)⬆️
lightning/src/ln/functional_test_utils.rs95.24% <100.00%> (+<0.01%)⬆️
lightning/src/chain/onchaintx.rs93.98% <0.00%> (-0.93%)⬇️
lightning/src/util/events.rs39.25% <0.00%> (-0.29%)⬇️
lightning/src/ln/functional_tests.rs96.95% <0.00%> (-0.17%)⬇️
lightning/src/ln/channel.rs88.77% <0.00%> (+0.02%)⬆️
... and 2 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4e5f74a...5bff5f9. Read the comment docs.

@tnull
tnull self-requested a review July 7, 2022 07:07

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, generally looks good, just some comments.

Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from 848afc3 to e6a114cCompareJuly 7, 2022 16:06
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and addressed feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from e6a114c to 846291fCompareJuly 7, 2022 17:07
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from e3396ef to cc1a045CompareJuly 8, 2022 14:25
tnull
tnull previously approved these changes Jul 11, 2022

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment threadlightning/src/routing/scoring.rs
Comment threadlightning/src/routing/scoring.rs
Comment threadlightning/src/routing/router.rs Outdated
Comment on lines 1004 to 1056
if contributes_sufficient_value && doesnt_exceed_max_path_length &&
doesnt_exceed_cltv_delta_limit && may_overpay_to_meet_path_minimum_msat {
doesnt_exceed_cltv_delta_limit && !payment_failed_on_this_channel &&
may_overpay_to_meet_path_minimum_msat
{
hit_minimum_limit = true;
} else if contributes_sufficient_value && doesnt_exceed_max_path_length &&
doesnt_exceed_cltv_delta_limit && over_path_minimum_msat {
doesnt_exceed_cltv_delta_limit && over_path_minimum_msat &&
!payment_failed_on_this_channel
{
// Note that low contribution here (limited by available_liquidity_msat)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of checking payment_failed_on_this_channel twice, consider having a leading if expression.

if payment_failed_on_this_channel {}elseif/* ... */{

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, good idea, I moved all the existing if conditions to that!

Comment threadlightning/src/routing/router.rs
Comment threadlightning/src/routing/scoring.rs Outdated
Comment on lines +629 to +646
// 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment needs to be updated now since it's not only when equal to the effective capacity. Seems like the earlier change that added this now removed logic is what caused calculates_log10_without_overflowing_u64_max_value to not exercise the correct code path as mentioned earlier on that test (i.e., it doesn't hit the negative_log10_times_2048 case below.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read the comment differently - I read the comment to say "the calculation we're doing here is equivalent to..." which is still true, no?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, you're right!

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Largely looks good but one comment.

Comment on lines +629 to +646
// 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, you're right!

Comment on lines +632 to +649
let negative_log10_times_2048 = NEGATIVE_LOG10_UPPER_BOUND * 2048;
self.combined_penalty_msat(amount_msat, negative_log10_times_2048, params)
.saturating_add(params.considered_impossible_penalty_msat)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to do the max of these two rather than adding? Is the idea that we want this to be >= anything given in the else clause?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, that's the idea. I suppose we could do a max. Originally I had it not adding the combined_penalty call at all but that seemed to brittle to deal with so switched to adding. I don't honestly have a strong opinion between adding and max, either way the docs can tell users what's going on, but for max i kinda worry users will acidentally set it too low and get no penalty here, which seems strange?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They would also need to set the other params lower, though, since max would select the combined penalty over the considered_impossible_penalty_msat if the latter were too low. So it's all kinda relative. Don't feel too strongly either though note that max may make debugging a little easier as otherwise you may need to mentally subtract some combined penalty if it is not obvious.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, sure, I just meant it'd be easy for a user to look at the field and think "okay, let me pick something a bit higher than the liquidity offset, forget to multiply by 2 or whatever, and end up with a penalty equal to the liquidity penalty, which seems wrong? I dunno, I'm happy to mentally convert when we're debugging. Unless you feel strongly I'd suggest we leave it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, sure, I just meant it'd be easy for a user to look at the field and think "okay, let me pick something a bit higher than the liquidity offset, forget to multiply by 2 or whatever, and end up with a penalty equal to the liquidity penalty, which seems wrong?

Plus base and amount penalty, FWIW.

I dunno, I'm happy to mentally convert when we're debugging. Unless you feel strongly I'd suggest we leave it.

Sure we can leave it. Though one thing I just realized is that either way now the penalty will be variable across channels depending on the amount. Maybe less so when using max but maybe that's an argument in favor of adding. That way if left to choose only channels exceeding the maximum liquidity, we'd prefer ones that would otherwise be penalized less.

jkczyz
jkczyz previously approved these changes Jul 13, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without further changes.

jkczyz
jkczyz previously approved these changes Jul 13, 2022
Comment threadlightning/src/routing/scoring.rs Outdated
jkczyz
jkczyz previously approved these changes Jul 13, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed yet again. Change since yesterday was:

diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs
index 6aa19abaa..9fa62d83f 100644
--- a/lightning/src/routing/scoring.rs
+++ b/lightning/src/routing/scoring.rs
@@ -397,7 +397,8 @@ pub struct ProbabilisticScoringParameters {
/// current estimate of the channel's available liquidity.
///
-	/// Note that in this case the [`liquidity_penalty_multiplier_msat`] and
-	/// [`amount_penalty_multiplier_msat`]-based penalties are still included in the overall
-	/// penalty.
+	/// Note that in this case all other penalties, including the
+	/// [`liquidity_penalty_multiplier_msat`] and [`amount_penalty_multiplier_msat`]-based
+	/// penalties, as well as the [`base_penalty_msat`] and the [`anti_probing_penalty_msat`], if
+	/// applicable, are still included in the overall penalty.
///
/// If you wish to avoid creating paths with such channels entirely, setting this to a value of
@@ -408,4 +409,6 @@ pub struct ProbabilisticScoringParameters {
/// [`liquidity_penalty_multiplier_msat`]: Self::liquidity_penalty_multiplier_msat
/// [`amount_penalty_multiplier_msat`]: Self::amount_penalty_multiplier_msat
+	/// [`base_penalty_msat`]: Self::base_penalty_msat
+	/// [`anti_probing_penalty_msat`]: Self::anti_probing_penalty_msat
pub considered_impossible_penalty_msat: u64,
}

jkczyz
jkczyz previously approved these changes Jul 13, 2022
tnull
tnull previously approved these changes Jul 14, 2022
@jkczyz

Copy link
Copy Markdown
Contributor

66ca68a mentions failures on a payment-level, but isn't it really on a path-level? i.e., if two parts of an MPP fail on different channels, retrying one part could retry over the channel failed on the other part?

@TheBlueMatt
TheBlueMatt dismissed stale reviews from tnull and jkczyz via 5bff5f9July 14, 2022 15:59
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from 8a1b418 to 5bff5f9CompareJuly 14, 2022 15:59
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops, right, sorry, rewrote that commit message without changes to the diff, added a note that it this does have the drawback of potentially retrying different parts along the same path, but hopefully the scorer doesn't let that happen unless the payment is gonna fail anyway.

@jkczyz

Copy link
Copy Markdown
Contributor

Oops, right, sorry, rewrote that commit message without changes to the diff, added a note that it this does have the drawback of potentially retrying different parts along the same path, but hopefully the scorer doesn't let that happen unless the payment is gonna fail anyway.

Yeah, same across payments, but as you said hopefully the scorer will learn quickly enough.

jkczyz
jkczyz previously approved these changes Jul 14, 2022
When an HTLC fails, we currently rely on the scorer learning the
failed channel and assigning an infinite (`u64::max_value()`)
penalty to the channel so as to avoid retrying over the exact same
path (if there's only one available path). This is common when
trying to pay a mobile client behind an LSP if the mobile client is
currently offline.
This leads to the scorer being overly conservative in some cases -
returning `u64::max_value()` when a given path hasn't been tried
for a given payment may not be the best decision, even if that
channel failed 50 minutes ago.
By tracking channels which failed on a payment part level and
explicitly refusing to route over them we can relax the
requirements on the scorer, allowing it to make different decisions
on how to treat channels that failed relatively recently without
causing payments to retry the same path forever.
This does have the drawback that it could allow two separate part
of a payment to traverse the same path even though that path just
failed, however this should only occur if the payment is going to
fail anyway, at least as long as the scorer is properly learning.
Closeslightningdevkit#1241, superseding lightningdevkit#1252.
When we consider sending an HTLC over a given channel impossible
due to our current knowledge of the channel's liquidity, we
currently always assign a penalty of `u64::max_value()`. However,
because we now refuse to retry a payment along the same path in
the router itself, we can now make this value configurable. This
allows users to have a relatively high knowledge decay interval
without the side-effect of refusing to try the only available path
in cases where a channel is intermittently available.
In general we should avoid taking paths that we are confident will
not work as much possible, but we should be willing to try each
payment at least once, even if its over a channel that failed
recently. A full Bitcoin penalty for such a channel seems
reasonable - lightning fees are unlikely to ever reach that point
so such channels will be scored much worse than any other potential
path, while still being below `u64::max_value()`.
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased to address merge conflict.

@TheBlueMatt
TheBlueMatt merged commit f75b6cb into lightningdevkit:mainJul 15, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Handle only-last-hop temp failure better

4 participants

@TheBlueMatt@codecov-commenter@jkczyz@tnull
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Avoid reusing just-failed channels in the router, making the impossibility penalty configurable - #1600

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-07-explicit-avoid-retries
Jul 15, 2022
Merged

Avoid reusing just-failed channels in the router, making the impossibility penalty configurable#1600
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-07-explicit-avoid-retries

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Jul 6, 2022

Copy link
Copy Markdown
Collaborator

Some users want to keep a very long scorer data half-life to maintain knowledge for a longer period of time. Its somewhat unclear if that's optimal, but it is clear that it can cause the scorer to refuse to build a route when the only available channel failed most recently within the halflife. This is rather unexpected behavior, and the fact that the scorer must behave this way to avoid #1241 is very annoying in that it prevents fixing this.

Here we move the avoidance of just-failed channels into the router itself, allowing us to make the impossibility penalty configurable, which we do as well.

Closes#1241, superseding #1252.

@codecov-commenter

codecov-commenter commented Jul 6, 2022

Copy link
Copy Markdown

Codecov Report

Merging #1600 (a863778) into main (4e5f74a) will increase coverage by 0.14%.
The diff coverage is 87.50%.

❗ Current head a863778 differs from pull request most recent head 5bff5f9. Consider uploading reports for the commit 5bff5f9 to get more accurate results

@@ Coverage Diff @@## main #1600 +/- ##
==========================================
+ Coverage 90.86% 91.00% +0.14% 
==========================================
Files 80 80 Lines 44437 45502 +1065 Branches 44437 45502 +1065 ==========================================
+ Hits 40377 41409 +1032 - Misses 4060 4093 +33 
Impacted FilesCoverage Δ
lightning/src/routing/router.rs92.45% <77.77%> (+0.06%)⬆️
lightning/src/routing/scoring.rs97.57% <95.00%> (+1.50%)⬆️
lightning/src/ln/channelmanager.rs84.90% <100.00%> (+0.02%)⬆️
lightning/src/ln/functional_test_utils.rs95.24% <100.00%> (+<0.01%)⬆️
lightning/src/chain/onchaintx.rs93.98% <0.00%> (-0.93%)⬇️
lightning/src/util/events.rs39.25% <0.00%> (-0.29%)⬇️
lightning/src/ln/functional_tests.rs96.95% <0.00%> (-0.17%)⬇️
lightning/src/ln/channel.rs88.77% <0.00%> (+0.02%)⬆️
... and 2 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4e5f74a...5bff5f9. Read the comment docs.

@tnull
tnull self-requested a review July 7, 2022 07:07

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, generally looks good, just some comments.

Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from 848afc3 to e6a114cCompareJuly 7, 2022 16:06
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and addressed feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from e6a114c to 846291fCompareJuly 7, 2022 17:07
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from e3396ef to cc1a045CompareJuly 8, 2022 14:25
tnull
tnull previously approved these changes Jul 11, 2022

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment threadlightning/src/routing/scoring.rs
Comment threadlightning/src/routing/scoring.rs
Comment threadlightning/src/routing/router.rs Outdated
Comment on lines 1004 to 1056
if contributes_sufficient_value && doesnt_exceed_max_path_length &&
doesnt_exceed_cltv_delta_limit && may_overpay_to_meet_path_minimum_msat {
doesnt_exceed_cltv_delta_limit && !payment_failed_on_this_channel &&
may_overpay_to_meet_path_minimum_msat
{
hit_minimum_limit = true;
} else if contributes_sufficient_value && doesnt_exceed_max_path_length &&
doesnt_exceed_cltv_delta_limit && over_path_minimum_msat {
doesnt_exceed_cltv_delta_limit && over_path_minimum_msat &&
!payment_failed_on_this_channel
{
// Note that low contribution here (limited by available_liquidity_msat)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of checking payment_failed_on_this_channel twice, consider having a leading if expression.

if payment_failed_on_this_channel {}elseif/* ... */{

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, good idea, I moved all the existing if conditions to that!

Comment threadlightning/src/routing/router.rs
Comment threadlightning/src/routing/scoring.rs Outdated
Comment on lines +629 to +646
// 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment needs to be updated now since it's not only when equal to the effective capacity. Seems like the earlier change that added this now removed logic is what caused calculates_log10_without_overflowing_u64_max_value to not exercise the correct code path as mentioned earlier on that test (i.e., it doesn't hit the negative_log10_times_2048 case below.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read the comment differently - I read the comment to say "the calculation we're doing here is equivalent to..." which is still true, no?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, you're right!

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Largely looks good but one comment.

Comment on lines +629 to +646
// 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, you're right!

Comment on lines +632 to +649
let negative_log10_times_2048 = NEGATIVE_LOG10_UPPER_BOUND * 2048;
self.combined_penalty_msat(amount_msat, negative_log10_times_2048, params)
.saturating_add(params.considered_impossible_penalty_msat)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to do the max of these two rather than adding? Is the idea that we want this to be >= anything given in the else clause?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, that's the idea. I suppose we could do a max. Originally I had it not adding the combined_penalty call at all but that seemed to brittle to deal with so switched to adding. I don't honestly have a strong opinion between adding and max, either way the docs can tell users what's going on, but for max i kinda worry users will acidentally set it too low and get no penalty here, which seems strange?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They would also need to set the other params lower, though, since max would select the combined penalty over the considered_impossible_penalty_msat if the latter were too low. So it's all kinda relative. Don't feel too strongly either though note that max may make debugging a little easier as otherwise you may need to mentally subtract some combined penalty if it is not obvious.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, sure, I just meant it'd be easy for a user to look at the field and think "okay, let me pick something a bit higher than the liquidity offset, forget to multiply by 2 or whatever, and end up with a penalty equal to the liquidity penalty, which seems wrong? I dunno, I'm happy to mentally convert when we're debugging. Unless you feel strongly I'd suggest we leave it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, sure, I just meant it'd be easy for a user to look at the field and think "okay, let me pick something a bit higher than the liquidity offset, forget to multiply by 2 or whatever, and end up with a penalty equal to the liquidity penalty, which seems wrong?

Plus base and amount penalty, FWIW.

I dunno, I'm happy to mentally convert when we're debugging. Unless you feel strongly I'd suggest we leave it.

Sure we can leave it. Though one thing I just realized is that either way now the penalty will be variable across channels depending on the amount. Maybe less so when using max but maybe that's an argument in favor of adding. That way if left to choose only channels exceeding the maximum liquidity, we'd prefer ones that would otherwise be penalized less.

jkczyz
jkczyz previously approved these changes Jul 13, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without further changes.

jkczyz
jkczyz previously approved these changes Jul 13, 2022
Comment threadlightning/src/routing/scoring.rs Outdated
jkczyz
jkczyz previously approved these changes Jul 13, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed yet again. Change since yesterday was:

diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs
index 6aa19abaa..9fa62d83f 100644
--- a/lightning/src/routing/scoring.rs
+++ b/lightning/src/routing/scoring.rs
@@ -397,7 +397,8 @@ pub struct ProbabilisticScoringParameters {
/// current estimate of the channel's available liquidity.
///
-	/// Note that in this case the [`liquidity_penalty_multiplier_msat`] and
-	/// [`amount_penalty_multiplier_msat`]-based penalties are still included in the overall
-	/// penalty.
+	/// Note that in this case all other penalties, including the
+	/// [`liquidity_penalty_multiplier_msat`] and [`amount_penalty_multiplier_msat`]-based
+	/// penalties, as well as the [`base_penalty_msat`] and the [`anti_probing_penalty_msat`], if
+	/// applicable, are still included in the overall penalty.
///
/// If you wish to avoid creating paths with such channels entirely, setting this to a value of
@@ -408,4 +409,6 @@ pub struct ProbabilisticScoringParameters {
/// [`liquidity_penalty_multiplier_msat`]: Self::liquidity_penalty_multiplier_msat
/// [`amount_penalty_multiplier_msat`]: Self::amount_penalty_multiplier_msat
+	/// [`base_penalty_msat`]: Self::base_penalty_msat
+	/// [`anti_probing_penalty_msat`]: Self::anti_probing_penalty_msat
pub considered_impossible_penalty_msat: u64,
}

jkczyz
jkczyz previously approved these changes Jul 13, 2022
tnull
tnull previously approved these changes Jul 14, 2022
@jkczyz

Copy link
Copy Markdown
Contributor

66ca68a mentions failures on a payment-level, but isn't it really on a path-level? i.e., if two parts of an MPP fail on different channels, retrying one part could retry over the channel failed on the other part?

@TheBlueMatt
TheBlueMatt dismissed stale reviews from tnull and jkczyz via 5bff5f9July 14, 2022 15:59
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from 8a1b418 to 5bff5f9CompareJuly 14, 2022 15:59
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops, right, sorry, rewrote that commit message without changes to the diff, added a note that it this does have the drawback of potentially retrying different parts along the same path, but hopefully the scorer doesn't let that happen unless the payment is gonna fail anyway.

@jkczyz

Copy link
Copy Markdown
Contributor

Oops, right, sorry, rewrote that commit message without changes to the diff, added a note that it this does have the drawback of potentially retrying different parts along the same path, but hopefully the scorer doesn't let that happen unless the payment is gonna fail anyway.

Yeah, same across payments, but as you said hopefully the scorer will learn quickly enough.

jkczyz
jkczyz previously approved these changes Jul 14, 2022
When an HTLC fails, we currently rely on the scorer learning the
failed channel and assigning an infinite (`u64::max_value()`)
penalty to the channel so as to avoid retrying over the exact same
path (if there's only one available path). This is common when
trying to pay a mobile client behind an LSP if the mobile client is
currently offline.
This leads to the scorer being overly conservative in some cases -
returning `u64::max_value()` when a given path hasn't been tried
for a given payment may not be the best decision, even if that
channel failed 50 minutes ago.
By tracking channels which failed on a payment part level and
explicitly refusing to route over them we can relax the
requirements on the scorer, allowing it to make different decisions
on how to treat channels that failed relatively recently without
causing payments to retry the same path forever.
This does have the drawback that it could allow two separate part
of a payment to traverse the same path even though that path just
failed, however this should only occur if the payment is going to
fail anyway, at least as long as the scorer is properly learning.
Closeslightningdevkit#1241, superseding lightningdevkit#1252.
When we consider sending an HTLC over a given channel impossible
due to our current knowledge of the channel's liquidity, we
currently always assign a penalty of `u64::max_value()`. However,
because we now refuse to retry a payment along the same path in
the router itself, we can now make this value configurable. This
allows users to have a relatively high knowledge decay interval
without the side-effect of refusing to try the only available path
in cases where a channel is intermittently available.
In general we should avoid taking paths that we are confident will
not work as much possible, but we should be willing to try each
payment at least once, even if its over a channel that failed
recently. A full Bitcoin penalty for such a channel seems
reasonable - lightning fees are unlikely to ever reach that point
so such channels will be scored much worse than any other potential
path, while still being below `u64::max_value()`.
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased to address merge conflict.

@TheBlueMatt
TheBlueMatt merged commit f75b6cb into lightningdevkit:mainJul 15, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Handle only-last-hop temp failure better

4 participants

@TheBlueMatt@codecov-commenter@jkczyz@tnull
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Avoid reusing just-failed channels in the router, making the impossibility penalty configurable - #1600

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-07-explicit-avoid-retries
Jul 15, 2022
Merged

Avoid reusing just-failed channels in the router, making the impossibility penalty configurable#1600
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-07-explicit-avoid-retries

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Jul 6, 2022

Copy link
Copy Markdown
Collaborator

Some users want to keep a very long scorer data half-life to maintain knowledge for a longer period of time. Its somewhat unclear if that's optimal, but it is clear that it can cause the scorer to refuse to build a route when the only available channel failed most recently within the halflife. This is rather unexpected behavior, and the fact that the scorer must behave this way to avoid #1241 is very annoying in that it prevents fixing this.

Here we move the avoidance of just-failed channels into the router itself, allowing us to make the impossibility penalty configurable, which we do as well.

Closes#1241, superseding #1252.

@codecov-commenter

codecov-commenter commented Jul 6, 2022

Copy link
Copy Markdown

Codecov Report

Merging #1600 (a863778) into main (4e5f74a) will increase coverage by 0.14%.
The diff coverage is 87.50%.

❗ Current head a863778 differs from pull request most recent head 5bff5f9. Consider uploading reports for the commit 5bff5f9 to get more accurate results

@@ Coverage Diff @@## main #1600 +/- ##
==========================================
+ Coverage 90.86% 91.00% +0.14% 
==========================================
Files 80 80 Lines 44437 45502 +1065 Branches 44437 45502 +1065 ==========================================
+ Hits 40377 41409 +1032 - Misses 4060 4093 +33 
Impacted FilesCoverage Δ
lightning/src/routing/router.rs92.45% <77.77%> (+0.06%)⬆️
lightning/src/routing/scoring.rs97.57% <95.00%> (+1.50%)⬆️
lightning/src/ln/channelmanager.rs84.90% <100.00%> (+0.02%)⬆️
lightning/src/ln/functional_test_utils.rs95.24% <100.00%> (+<0.01%)⬆️
lightning/src/chain/onchaintx.rs93.98% <0.00%> (-0.93%)⬇️
lightning/src/util/events.rs39.25% <0.00%> (-0.29%)⬇️
lightning/src/ln/functional_tests.rs96.95% <0.00%> (-0.17%)⬇️
lightning/src/ln/channel.rs88.77% <0.00%> (+0.02%)⬆️
... and 2 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4e5f74a...5bff5f9. Read the comment docs.

@tnull
tnull self-requested a review July 7, 2022 07:07

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, generally looks good, just some comments.

Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from 848afc3 to e6a114cCompareJuly 7, 2022 16:06
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and addressed feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from e6a114c to 846291fCompareJuly 7, 2022 17:07
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from e3396ef to cc1a045CompareJuly 8, 2022 14:25
tnull
tnull previously approved these changes Jul 11, 2022

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment threadlightning/src/routing/scoring.rs
Comment threadlightning/src/routing/scoring.rs
Comment threadlightning/src/routing/router.rs Outdated
Comment on lines 1004 to 1056
if contributes_sufficient_value && doesnt_exceed_max_path_length &&
doesnt_exceed_cltv_delta_limit && may_overpay_to_meet_path_minimum_msat {
doesnt_exceed_cltv_delta_limit && !payment_failed_on_this_channel &&
may_overpay_to_meet_path_minimum_msat
{
hit_minimum_limit = true;
} else if contributes_sufficient_value && doesnt_exceed_max_path_length &&
doesnt_exceed_cltv_delta_limit && over_path_minimum_msat {
doesnt_exceed_cltv_delta_limit && over_path_minimum_msat &&
!payment_failed_on_this_channel
{
// Note that low contribution here (limited by available_liquidity_msat)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of checking payment_failed_on_this_channel twice, consider having a leading if expression.

if payment_failed_on_this_channel {}elseif/* ... */{

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, good idea, I moved all the existing if conditions to that!

Comment threadlightning/src/routing/router.rs
Comment threadlightning/src/routing/scoring.rs Outdated
Comment on lines +629 to +646
// 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment needs to be updated now since it's not only when equal to the effective capacity. Seems like the earlier change that added this now removed logic is what caused calculates_log10_without_overflowing_u64_max_value to not exercise the correct code path as mentioned earlier on that test (i.e., it doesn't hit the negative_log10_times_2048 case below.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read the comment differently - I read the comment to say "the calculation we're doing here is equivalent to..." which is still true, no?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, you're right!

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Largely looks good but one comment.

Comment on lines +629 to +646
// 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, you're right!

Comment on lines +632 to +649
let negative_log10_times_2048 = NEGATIVE_LOG10_UPPER_BOUND * 2048;
self.combined_penalty_msat(amount_msat, negative_log10_times_2048, params)
.saturating_add(params.considered_impossible_penalty_msat)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to do the max of these two rather than adding? Is the idea that we want this to be >= anything given in the else clause?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, that's the idea. I suppose we could do a max. Originally I had it not adding the combined_penalty call at all but that seemed to brittle to deal with so switched to adding. I don't honestly have a strong opinion between adding and max, either way the docs can tell users what's going on, but for max i kinda worry users will acidentally set it too low and get no penalty here, which seems strange?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They would also need to set the other params lower, though, since max would select the combined penalty over the considered_impossible_penalty_msat if the latter were too low. So it's all kinda relative. Don't feel too strongly either though note that max may make debugging a little easier as otherwise you may need to mentally subtract some combined penalty if it is not obvious.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, sure, I just meant it'd be easy for a user to look at the field and think "okay, let me pick something a bit higher than the liquidity offset, forget to multiply by 2 or whatever, and end up with a penalty equal to the liquidity penalty, which seems wrong? I dunno, I'm happy to mentally convert when we're debugging. Unless you feel strongly I'd suggest we leave it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, sure, I just meant it'd be easy for a user to look at the field and think "okay, let me pick something a bit higher than the liquidity offset, forget to multiply by 2 or whatever, and end up with a penalty equal to the liquidity penalty, which seems wrong?

Plus base and amount penalty, FWIW.

I dunno, I'm happy to mentally convert when we're debugging. Unless you feel strongly I'd suggest we leave it.

Sure we can leave it. Though one thing I just realized is that either way now the penalty will be variable across channels depending on the amount. Maybe less so when using max but maybe that's an argument in favor of adding. That way if left to choose only channels exceeding the maximum liquidity, we'd prefer ones that would otherwise be penalized less.

jkczyz
jkczyz previously approved these changes Jul 13, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without further changes.

jkczyz
jkczyz previously approved these changes Jul 13, 2022
Comment threadlightning/src/routing/scoring.rs Outdated
jkczyz
jkczyz previously approved these changes Jul 13, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed yet again. Change since yesterday was:

diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs
index 6aa19abaa..9fa62d83f 100644
--- a/lightning/src/routing/scoring.rs
+++ b/lightning/src/routing/scoring.rs
@@ -397,7 +397,8 @@ pub struct ProbabilisticScoringParameters {
/// current estimate of the channel's available liquidity.
///
-	/// Note that in this case the [`liquidity_penalty_multiplier_msat`] and
-	/// [`amount_penalty_multiplier_msat`]-based penalties are still included in the overall
-	/// penalty.
+	/// Note that in this case all other penalties, including the
+	/// [`liquidity_penalty_multiplier_msat`] and [`amount_penalty_multiplier_msat`]-based
+	/// penalties, as well as the [`base_penalty_msat`] and the [`anti_probing_penalty_msat`], if
+	/// applicable, are still included in the overall penalty.
///
/// If you wish to avoid creating paths with such channels entirely, setting this to a value of
@@ -408,4 +409,6 @@ pub struct ProbabilisticScoringParameters {
/// [`liquidity_penalty_multiplier_msat`]: Self::liquidity_penalty_multiplier_msat
/// [`amount_penalty_multiplier_msat`]: Self::amount_penalty_multiplier_msat
+	/// [`base_penalty_msat`]: Self::base_penalty_msat
+	/// [`anti_probing_penalty_msat`]: Self::anti_probing_penalty_msat
pub considered_impossible_penalty_msat: u64,
}

jkczyz
jkczyz previously approved these changes Jul 13, 2022
tnull
tnull previously approved these changes Jul 14, 2022
@jkczyz

Copy link
Copy Markdown
Contributor

66ca68a mentions failures on a payment-level, but isn't it really on a path-level? i.e., if two parts of an MPP fail on different channels, retrying one part could retry over the channel failed on the other part?

@TheBlueMatt
TheBlueMatt dismissed stale reviews from tnull and jkczyz via 5bff5f9July 14, 2022 15:59
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from 8a1b418 to 5bff5f9CompareJuly 14, 2022 15:59
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops, right, sorry, rewrote that commit message without changes to the diff, added a note that it this does have the drawback of potentially retrying different parts along the same path, but hopefully the scorer doesn't let that happen unless the payment is gonna fail anyway.

@jkczyz

Copy link
Copy Markdown
Contributor

Oops, right, sorry, rewrote that commit message without changes to the diff, added a note that it this does have the drawback of potentially retrying different parts along the same path, but hopefully the scorer doesn't let that happen unless the payment is gonna fail anyway.

Yeah, same across payments, but as you said hopefully the scorer will learn quickly enough.

jkczyz
jkczyz previously approved these changes Jul 14, 2022
When an HTLC fails, we currently rely on the scorer learning the
failed channel and assigning an infinite (`u64::max_value()`)
penalty to the channel so as to avoid retrying over the exact same
path (if there's only one available path). This is common when
trying to pay a mobile client behind an LSP if the mobile client is
currently offline.
This leads to the scorer being overly conservative in some cases -
returning `u64::max_value()` when a given path hasn't been tried
for a given payment may not be the best decision, even if that
channel failed 50 minutes ago.
By tracking channels which failed on a payment part level and
explicitly refusing to route over them we can relax the
requirements on the scorer, allowing it to make different decisions
on how to treat channels that failed relatively recently without
causing payments to retry the same path forever.
This does have the drawback that it could allow two separate part
of a payment to traverse the same path even though that path just
failed, however this should only occur if the payment is going to
fail anyway, at least as long as the scorer is properly learning.
Closeslightningdevkit#1241, superseding lightningdevkit#1252.
When we consider sending an HTLC over a given channel impossible
due to our current knowledge of the channel's liquidity, we
currently always assign a penalty of `u64::max_value()`. However,
because we now refuse to retry a payment along the same path in
the router itself, we can now make this value configurable. This
allows users to have a relatively high knowledge decay interval
without the side-effect of refusing to try the only available path
in cases where a channel is intermittently available.
In general we should avoid taking paths that we are confident will
not work as much possible, but we should be willing to try each
payment at least once, even if its over a channel that failed
recently. A full Bitcoin penalty for such a channel seems
reasonable - lightning fees are unlikely to ever reach that point
so such channels will be scored much worse than any other potential
path, while still being below `u64::max_value()`.
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased to address merge conflict.

@TheBlueMatt
TheBlueMatt merged commit f75b6cb into lightningdevkit:mainJul 15, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Handle only-last-hop temp failure better

4 participants

@TheBlueMatt@codecov-commenter@jkczyz@tnull
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Avoid reusing just-failed channels in the router, making the impossibility penalty configurable - #1600

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-07-explicit-avoid-retries
Jul 15, 2022
Merged

Avoid reusing just-failed channels in the router, making the impossibility penalty configurable#1600
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-07-explicit-avoid-retries

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Jul 6, 2022

Copy link
Copy Markdown
Collaborator

Some users want to keep a very long scorer data half-life to maintain knowledge for a longer period of time. Its somewhat unclear if that's optimal, but it is clear that it can cause the scorer to refuse to build a route when the only available channel failed most recently within the halflife. This is rather unexpected behavior, and the fact that the scorer must behave this way to avoid #1241 is very annoying in that it prevents fixing this.

Here we move the avoidance of just-failed channels into the router itself, allowing us to make the impossibility penalty configurable, which we do as well.

Closes#1241, superseding #1252.

@codecov-commenter

codecov-commenter commented Jul 6, 2022

Copy link
Copy Markdown

Codecov Report

Merging #1600 (a863778) into main (4e5f74a) will increase coverage by 0.14%.
The diff coverage is 87.50%.

❗ Current head a863778 differs from pull request most recent head 5bff5f9. Consider uploading reports for the commit 5bff5f9 to get more accurate results

@@ Coverage Diff @@## main #1600 +/- ##
==========================================
+ Coverage 90.86% 91.00% +0.14% 
==========================================
Files 80 80 Lines 44437 45502 +1065 Branches 44437 45502 +1065 ==========================================
+ Hits 40377 41409 +1032 - Misses 4060 4093 +33 
Impacted FilesCoverage Δ
lightning/src/routing/router.rs92.45% <77.77%> (+0.06%)⬆️
lightning/src/routing/scoring.rs97.57% <95.00%> (+1.50%)⬆️
lightning/src/ln/channelmanager.rs84.90% <100.00%> (+0.02%)⬆️
lightning/src/ln/functional_test_utils.rs95.24% <100.00%> (+<0.01%)⬆️
lightning/src/chain/onchaintx.rs93.98% <0.00%> (-0.93%)⬇️
lightning/src/util/events.rs39.25% <0.00%> (-0.29%)⬇️
lightning/src/ln/functional_tests.rs96.95% <0.00%> (-0.17%)⬇️
lightning/src/ln/channel.rs88.77% <0.00%> (+0.02%)⬆️
... and 2 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4e5f74a...5bff5f9. Read the comment docs.

@tnull
tnull self-requested a review July 7, 2022 07:07

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, generally looks good, just some comments.

Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from 848afc3 to e6a114cCompareJuly 7, 2022 16:06
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and addressed feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from e6a114c to 846291fCompareJuly 7, 2022 17:07
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from e3396ef to cc1a045CompareJuly 8, 2022 14:25
tnull
tnull previously approved these changes Jul 11, 2022

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment threadlightning/src/routing/scoring.rs
Comment threadlightning/src/routing/scoring.rs
Comment threadlightning/src/routing/router.rs Outdated
Comment on lines 1004 to 1056
if contributes_sufficient_value && doesnt_exceed_max_path_length &&
doesnt_exceed_cltv_delta_limit && may_overpay_to_meet_path_minimum_msat {
doesnt_exceed_cltv_delta_limit && !payment_failed_on_this_channel &&
may_overpay_to_meet_path_minimum_msat
{
hit_minimum_limit = true;
} else if contributes_sufficient_value && doesnt_exceed_max_path_length &&
doesnt_exceed_cltv_delta_limit && over_path_minimum_msat {
doesnt_exceed_cltv_delta_limit && over_path_minimum_msat &&
!payment_failed_on_this_channel
{
// Note that low contribution here (limited by available_liquidity_msat)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of checking payment_failed_on_this_channel twice, consider having a leading if expression.

if payment_failed_on_this_channel {}elseif/* ... */{

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, good idea, I moved all the existing if conditions to that!

Comment threadlightning/src/routing/router.rs
Comment threadlightning/src/routing/scoring.rs Outdated
Comment on lines +629 to +646
// 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment needs to be updated now since it's not only when equal to the effective capacity. Seems like the earlier change that added this now removed logic is what caused calculates_log10_without_overflowing_u64_max_value to not exercise the correct code path as mentioned earlier on that test (i.e., it doesn't hit the negative_log10_times_2048 case below.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read the comment differently - I read the comment to say "the calculation we're doing here is equivalent to..." which is still true, no?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, you're right!

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Largely looks good but one comment.

Comment on lines +629 to +646
// 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, you're right!

Comment on lines +632 to +649
let negative_log10_times_2048 = NEGATIVE_LOG10_UPPER_BOUND * 2048;
self.combined_penalty_msat(amount_msat, negative_log10_times_2048, params)
.saturating_add(params.considered_impossible_penalty_msat)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to do the max of these two rather than adding? Is the idea that we want this to be >= anything given in the else clause?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, that's the idea. I suppose we could do a max. Originally I had it not adding the combined_penalty call at all but that seemed to brittle to deal with so switched to adding. I don't honestly have a strong opinion between adding and max, either way the docs can tell users what's going on, but for max i kinda worry users will acidentally set it too low and get no penalty here, which seems strange?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They would also need to set the other params lower, though, since max would select the combined penalty over the considered_impossible_penalty_msat if the latter were too low. So it's all kinda relative. Don't feel too strongly either though note that max may make debugging a little easier as otherwise you may need to mentally subtract some combined penalty if it is not obvious.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, sure, I just meant it'd be easy for a user to look at the field and think "okay, let me pick something a bit higher than the liquidity offset, forget to multiply by 2 or whatever, and end up with a penalty equal to the liquidity penalty, which seems wrong? I dunno, I'm happy to mentally convert when we're debugging. Unless you feel strongly I'd suggest we leave it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, sure, I just meant it'd be easy for a user to look at the field and think "okay, let me pick something a bit higher than the liquidity offset, forget to multiply by 2 or whatever, and end up with a penalty equal to the liquidity penalty, which seems wrong?

Plus base and amount penalty, FWIW.

I dunno, I'm happy to mentally convert when we're debugging. Unless you feel strongly I'd suggest we leave it.

Sure we can leave it. Though one thing I just realized is that either way now the penalty will be variable across channels depending on the amount. Maybe less so when using max but maybe that's an argument in favor of adding. That way if left to choose only channels exceeding the maximum liquidity, we'd prefer ones that would otherwise be penalized less.

jkczyz
jkczyz previously approved these changes Jul 13, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without further changes.

jkczyz
jkczyz previously approved these changes Jul 13, 2022
Comment threadlightning/src/routing/scoring.rs Outdated
jkczyz
jkczyz previously approved these changes Jul 13, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed yet again. Change since yesterday was:

diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs
index 6aa19abaa..9fa62d83f 100644
--- a/lightning/src/routing/scoring.rs
+++ b/lightning/src/routing/scoring.rs
@@ -397,7 +397,8 @@ pub struct ProbabilisticScoringParameters {
/// current estimate of the channel's available liquidity.
///
-	/// Note that in this case the [`liquidity_penalty_multiplier_msat`] and
-	/// [`amount_penalty_multiplier_msat`]-based penalties are still included in the overall
-	/// penalty.
+	/// Note that in this case all other penalties, including the
+	/// [`liquidity_penalty_multiplier_msat`] and [`amount_penalty_multiplier_msat`]-based
+	/// penalties, as well as the [`base_penalty_msat`] and the [`anti_probing_penalty_msat`], if
+	/// applicable, are still included in the overall penalty.
///
/// If you wish to avoid creating paths with such channels entirely, setting this to a value of
@@ -408,4 +409,6 @@ pub struct ProbabilisticScoringParameters {
/// [`liquidity_penalty_multiplier_msat`]: Self::liquidity_penalty_multiplier_msat
/// [`amount_penalty_multiplier_msat`]: Self::amount_penalty_multiplier_msat
+	/// [`base_penalty_msat`]: Self::base_penalty_msat
+	/// [`anti_probing_penalty_msat`]: Self::anti_probing_penalty_msat
pub considered_impossible_penalty_msat: u64,
}

jkczyz
jkczyz previously approved these changes Jul 13, 2022
tnull
tnull previously approved these changes Jul 14, 2022
@jkczyz

Copy link
Copy Markdown
Contributor

66ca68a mentions failures on a payment-level, but isn't it really on a path-level? i.e., if two parts of an MPP fail on different channels, retrying one part could retry over the channel failed on the other part?

@TheBlueMatt
TheBlueMatt dismissed stale reviews from tnull and jkczyz via 5bff5f9July 14, 2022 15:59
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from 8a1b418 to 5bff5f9CompareJuly 14, 2022 15:59
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops, right, sorry, rewrote that commit message without changes to the diff, added a note that it this does have the drawback of potentially retrying different parts along the same path, but hopefully the scorer doesn't let that happen unless the payment is gonna fail anyway.

@jkczyz

Copy link
Copy Markdown
Contributor

Oops, right, sorry, rewrote that commit message without changes to the diff, added a note that it this does have the drawback of potentially retrying different parts along the same path, but hopefully the scorer doesn't let that happen unless the payment is gonna fail anyway.

Yeah, same across payments, but as you said hopefully the scorer will learn quickly enough.

jkczyz
jkczyz previously approved these changes Jul 14, 2022
When an HTLC fails, we currently rely on the scorer learning the
failed channel and assigning an infinite (`u64::max_value()`)
penalty to the channel so as to avoid retrying over the exact same
path (if there's only one available path). This is common when
trying to pay a mobile client behind an LSP if the mobile client is
currently offline.
This leads to the scorer being overly conservative in some cases -
returning `u64::max_value()` when a given path hasn't been tried
for a given payment may not be the best decision, even if that
channel failed 50 minutes ago.
By tracking channels which failed on a payment part level and
explicitly refusing to route over them we can relax the
requirements on the scorer, allowing it to make different decisions
on how to treat channels that failed relatively recently without
causing payments to retry the same path forever.
This does have the drawback that it could allow two separate part
of a payment to traverse the same path even though that path just
failed, however this should only occur if the payment is going to
fail anyway, at least as long as the scorer is properly learning.
Closeslightningdevkit#1241, superseding lightningdevkit#1252.
When we consider sending an HTLC over a given channel impossible
due to our current knowledge of the channel's liquidity, we
currently always assign a penalty of `u64::max_value()`. However,
because we now refuse to retry a payment along the same path in
the router itself, we can now make this value configurable. This
allows users to have a relatively high knowledge decay interval
without the side-effect of refusing to try the only available path
in cases where a channel is intermittently available.
In general we should avoid taking paths that we are confident will
not work as much possible, but we should be willing to try each
payment at least once, even if its over a channel that failed
recently. A full Bitcoin penalty for such a channel seems
reasonable - lightning fees are unlikely to ever reach that point
so such channels will be scored much worse than any other potential
path, while still being below `u64::max_value()`.
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased to address merge conflict.

@TheBlueMatt
TheBlueMatt merged commit f75b6cb into lightningdevkit:mainJul 15, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Handle only-last-hop temp failure better

4 participants

@TheBlueMatt@codecov-commenter@jkczyz@tnull
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Avoid reusing just-failed channels in the router, making the impossibility penalty configurable - #1600

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-07-explicit-avoid-retries
Jul 15, 2022
Merged

Avoid reusing just-failed channels in the router, making the impossibility penalty configurable#1600
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-07-explicit-avoid-retries

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Jul 6, 2022

Copy link
Copy Markdown
Collaborator

Some users want to keep a very long scorer data half-life to maintain knowledge for a longer period of time. Its somewhat unclear if that's optimal, but it is clear that it can cause the scorer to refuse to build a route when the only available channel failed most recently within the halflife. This is rather unexpected behavior, and the fact that the scorer must behave this way to avoid #1241 is very annoying in that it prevents fixing this.

Here we move the avoidance of just-failed channels into the router itself, allowing us to make the impossibility penalty configurable, which we do as well.

Closes#1241, superseding #1252.

@codecov-commenter

codecov-commenter commented Jul 6, 2022

Copy link
Copy Markdown

Codecov Report

Merging #1600 (a863778) into main (4e5f74a) will increase coverage by 0.14%.
The diff coverage is 87.50%.

❗ Current head a863778 differs from pull request most recent head 5bff5f9. Consider uploading reports for the commit 5bff5f9 to get more accurate results

@@ Coverage Diff @@## main #1600 +/- ##
==========================================
+ Coverage 90.86% 91.00% +0.14% 
==========================================
Files 80 80 Lines 44437 45502 +1065 Branches 44437 45502 +1065 ==========================================
+ Hits 40377 41409 +1032 - Misses 4060 4093 +33 
Impacted FilesCoverage Δ
lightning/src/routing/router.rs92.45% <77.77%> (+0.06%)⬆️
lightning/src/routing/scoring.rs97.57% <95.00%> (+1.50%)⬆️
lightning/src/ln/channelmanager.rs84.90% <100.00%> (+0.02%)⬆️
lightning/src/ln/functional_test_utils.rs95.24% <100.00%> (+<0.01%)⬆️
lightning/src/chain/onchaintx.rs93.98% <0.00%> (-0.93%)⬇️
lightning/src/util/events.rs39.25% <0.00%> (-0.29%)⬇️
lightning/src/ln/functional_tests.rs96.95% <0.00%> (-0.17%)⬇️
lightning/src/ln/channel.rs88.77% <0.00%> (+0.02%)⬆️
... and 2 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4e5f74a...5bff5f9. Read the comment docs.

@tnull
tnull self-requested a review July 7, 2022 07:07

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, generally looks good, just some comments.

Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from 848afc3 to e6a114cCompareJuly 7, 2022 16:06
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and addressed feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from e6a114c to 846291fCompareJuly 7, 2022 17:07
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from e3396ef to cc1a045CompareJuly 8, 2022 14:25
tnull
tnull previously approved these changes Jul 11, 2022

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment threadlightning/src/routing/scoring.rs
Comment threadlightning/src/routing/scoring.rs
Comment threadlightning/src/routing/router.rs Outdated
Comment on lines 1004 to 1056
if contributes_sufficient_value && doesnt_exceed_max_path_length &&
doesnt_exceed_cltv_delta_limit && may_overpay_to_meet_path_minimum_msat {
doesnt_exceed_cltv_delta_limit && !payment_failed_on_this_channel &&
may_overpay_to_meet_path_minimum_msat
{
hit_minimum_limit = true;
} else if contributes_sufficient_value && doesnt_exceed_max_path_length &&
doesnt_exceed_cltv_delta_limit && over_path_minimum_msat {
doesnt_exceed_cltv_delta_limit && over_path_minimum_msat &&
!payment_failed_on_this_channel
{
// Note that low contribution here (limited by available_liquidity_msat)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of checking payment_failed_on_this_channel twice, consider having a leading if expression.

if payment_failed_on_this_channel {}elseif/* ... */{

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, good idea, I moved all the existing if conditions to that!

Comment threadlightning/src/routing/router.rs
Comment threadlightning/src/routing/scoring.rs Outdated
Comment on lines +629 to +646
// 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment needs to be updated now since it's not only when equal to the effective capacity. Seems like the earlier change that added this now removed logic is what caused calculates_log10_without_overflowing_u64_max_value to not exercise the correct code path as mentioned earlier on that test (i.e., it doesn't hit the negative_log10_times_2048 case below.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read the comment differently - I read the comment to say "the calculation we're doing here is equivalent to..." which is still true, no?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, you're right!

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Largely looks good but one comment.

Comment on lines +629 to +646
// 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, you're right!

Comment on lines +632 to +649
let negative_log10_times_2048 = NEGATIVE_LOG10_UPPER_BOUND * 2048;
self.combined_penalty_msat(amount_msat, negative_log10_times_2048, params)
.saturating_add(params.considered_impossible_penalty_msat)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to do the max of these two rather than adding? Is the idea that we want this to be >= anything given in the else clause?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, that's the idea. I suppose we could do a max. Originally I had it not adding the combined_penalty call at all but that seemed to brittle to deal with so switched to adding. I don't honestly have a strong opinion between adding and max, either way the docs can tell users what's going on, but for max i kinda worry users will acidentally set it too low and get no penalty here, which seems strange?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They would also need to set the other params lower, though, since max would select the combined penalty over the considered_impossible_penalty_msat if the latter were too low. So it's all kinda relative. Don't feel too strongly either though note that max may make debugging a little easier as otherwise you may need to mentally subtract some combined penalty if it is not obvious.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, sure, I just meant it'd be easy for a user to look at the field and think "okay, let me pick something a bit higher than the liquidity offset, forget to multiply by 2 or whatever, and end up with a penalty equal to the liquidity penalty, which seems wrong? I dunno, I'm happy to mentally convert when we're debugging. Unless you feel strongly I'd suggest we leave it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, sure, I just meant it'd be easy for a user to look at the field and think "okay, let me pick something a bit higher than the liquidity offset, forget to multiply by 2 or whatever, and end up with a penalty equal to the liquidity penalty, which seems wrong?

Plus base and amount penalty, FWIW.

I dunno, I'm happy to mentally convert when we're debugging. Unless you feel strongly I'd suggest we leave it.

Sure we can leave it. Though one thing I just realized is that either way now the penalty will be variable across channels depending on the amount. Maybe less so when using max but maybe that's an argument in favor of adding. That way if left to choose only channels exceeding the maximum liquidity, we'd prefer ones that would otherwise be penalized less.

jkczyz
jkczyz previously approved these changes Jul 13, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without further changes.

jkczyz
jkczyz previously approved these changes Jul 13, 2022
Comment threadlightning/src/routing/scoring.rs Outdated
jkczyz
jkczyz previously approved these changes Jul 13, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed yet again. Change since yesterday was:

diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs
index 6aa19abaa..9fa62d83f 100644
--- a/lightning/src/routing/scoring.rs
+++ b/lightning/src/routing/scoring.rs
@@ -397,7 +397,8 @@ pub struct ProbabilisticScoringParameters {
/// current estimate of the channel's available liquidity.
///
-	/// Note that in this case the [`liquidity_penalty_multiplier_msat`] and
-	/// [`amount_penalty_multiplier_msat`]-based penalties are still included in the overall
-	/// penalty.
+	/// Note that in this case all other penalties, including the
+	/// [`liquidity_penalty_multiplier_msat`] and [`amount_penalty_multiplier_msat`]-based
+	/// penalties, as well as the [`base_penalty_msat`] and the [`anti_probing_penalty_msat`], if
+	/// applicable, are still included in the overall penalty.
///
/// If you wish to avoid creating paths with such channels entirely, setting this to a value of
@@ -408,4 +409,6 @@ pub struct ProbabilisticScoringParameters {
/// [`liquidity_penalty_multiplier_msat`]: Self::liquidity_penalty_multiplier_msat
/// [`amount_penalty_multiplier_msat`]: Self::amount_penalty_multiplier_msat
+	/// [`base_penalty_msat`]: Self::base_penalty_msat
+	/// [`anti_probing_penalty_msat`]: Self::anti_probing_penalty_msat
pub considered_impossible_penalty_msat: u64,
}

jkczyz
jkczyz previously approved these changes Jul 13, 2022
tnull
tnull previously approved these changes Jul 14, 2022
@jkczyz

Copy link
Copy Markdown
Contributor

66ca68a mentions failures on a payment-level, but isn't it really on a path-level? i.e., if two parts of an MPP fail on different channels, retrying one part could retry over the channel failed on the other part?

@TheBlueMatt
TheBlueMatt dismissed stale reviews from tnull and jkczyz via 5bff5f9July 14, 2022 15:59
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from 8a1b418 to 5bff5f9CompareJuly 14, 2022 15:59
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops, right, sorry, rewrote that commit message without changes to the diff, added a note that it this does have the drawback of potentially retrying different parts along the same path, but hopefully the scorer doesn't let that happen unless the payment is gonna fail anyway.

@jkczyz

Copy link
Copy Markdown
Contributor

Oops, right, sorry, rewrote that commit message without changes to the diff, added a note that it this does have the drawback of potentially retrying different parts along the same path, but hopefully the scorer doesn't let that happen unless the payment is gonna fail anyway.

Yeah, same across payments, but as you said hopefully the scorer will learn quickly enough.

jkczyz
jkczyz previously approved these changes Jul 14, 2022
When an HTLC fails, we currently rely on the scorer learning the
failed channel and assigning an infinite (`u64::max_value()`)
penalty to the channel so as to avoid retrying over the exact same
path (if there's only one available path). This is common when
trying to pay a mobile client behind an LSP if the mobile client is
currently offline.
This leads to the scorer being overly conservative in some cases -
returning `u64::max_value()` when a given path hasn't been tried
for a given payment may not be the best decision, even if that
channel failed 50 minutes ago.
By tracking channels which failed on a payment part level and
explicitly refusing to route over them we can relax the
requirements on the scorer, allowing it to make different decisions
on how to treat channels that failed relatively recently without
causing payments to retry the same path forever.
This does have the drawback that it could allow two separate part
of a payment to traverse the same path even though that path just
failed, however this should only occur if the payment is going to
fail anyway, at least as long as the scorer is properly learning.
Closeslightningdevkit#1241, superseding lightningdevkit#1252.
When we consider sending an HTLC over a given channel impossible
due to our current knowledge of the channel's liquidity, we
currently always assign a penalty of `u64::max_value()`. However,
because we now refuse to retry a payment along the same path in
the router itself, we can now make this value configurable. This
allows users to have a relatively high knowledge decay interval
without the side-effect of refusing to try the only available path
in cases where a channel is intermittently available.
In general we should avoid taking paths that we are confident will
not work as much possible, but we should be willing to try each
payment at least once, even if its over a channel that failed
recently. A full Bitcoin penalty for such a channel seems
reasonable - lightning fees are unlikely to ever reach that point
so such channels will be scored much worse than any other potential
path, while still being below `u64::max_value()`.
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased to address merge conflict.

@TheBlueMatt
TheBlueMatt merged commit f75b6cb into lightningdevkit:mainJul 15, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Handle only-last-hop temp failure better

4 participants

@TheBlueMatt@codecov-commenter@jkczyz@tnull
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Avoid reusing just-failed channels in the router, making the impossibility penalty configurable - #1600

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-07-explicit-avoid-retries
Jul 15, 2022
Merged

Avoid reusing just-failed channels in the router, making the impossibility penalty configurable#1600
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2022-07-explicit-avoid-retries

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Jul 6, 2022

Copy link
Copy Markdown
Collaborator

Some users want to keep a very long scorer data half-life to maintain knowledge for a longer period of time. Its somewhat unclear if that's optimal, but it is clear that it can cause the scorer to refuse to build a route when the only available channel failed most recently within the halflife. This is rather unexpected behavior, and the fact that the scorer must behave this way to avoid #1241 is very annoying in that it prevents fixing this.

Here we move the avoidance of just-failed channels into the router itself, allowing us to make the impossibility penalty configurable, which we do as well.

Closes#1241, superseding #1252.

@codecov-commenter

codecov-commenter commented Jul 6, 2022

Copy link
Copy Markdown

Codecov Report

Merging #1600 (a863778) into main (4e5f74a) will increase coverage by 0.14%.
The diff coverage is 87.50%.

❗ Current head a863778 differs from pull request most recent head 5bff5f9. Consider uploading reports for the commit 5bff5f9 to get more accurate results

@@ Coverage Diff @@## main #1600 +/- ##
==========================================
+ Coverage 90.86% 91.00% +0.14% 
==========================================
Files 80 80 Lines 44437 45502 +1065 Branches 44437 45502 +1065 ==========================================
+ Hits 40377 41409 +1032 - Misses 4060 4093 +33 
Impacted FilesCoverage Δ
lightning/src/routing/router.rs92.45% <77.77%> (+0.06%)⬆️
lightning/src/routing/scoring.rs97.57% <95.00%> (+1.50%)⬆️
lightning/src/ln/channelmanager.rs84.90% <100.00%> (+0.02%)⬆️
lightning/src/ln/functional_test_utils.rs95.24% <100.00%> (+<0.01%)⬆️
lightning/src/chain/onchaintx.rs93.98% <0.00%> (-0.93%)⬇️
lightning/src/util/events.rs39.25% <0.00%> (-0.29%)⬇️
lightning/src/ln/functional_tests.rs96.95% <0.00%> (-0.17%)⬇️
lightning/src/ln/channel.rs88.77% <0.00%> (+0.02%)⬆️
... and 2 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4e5f74a...5bff5f9. Read the comment docs.

@tnull
tnull self-requested a review July 7, 2022 07:07

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, generally looks good, just some comments.

Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs Outdated
Comment threadlightning/src/routing/router.rs
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
Comment threadlightning/src/routing/scoring.rs Outdated
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from 848afc3 to e6a114cCompareJuly 7, 2022 16:06
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and addressed feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from e6a114c to 846291fCompareJuly 7, 2022 17:07
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from e3396ef to cc1a045CompareJuly 8, 2022 14:25
tnull
tnull previously approved these changes Jul 11, 2022

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment threadlightning/src/routing/scoring.rs
Comment threadlightning/src/routing/scoring.rs
Comment threadlightning/src/routing/router.rs Outdated
Comment on lines 1004 to 1056
if contributes_sufficient_value && doesnt_exceed_max_path_length &&
doesnt_exceed_cltv_delta_limit && may_overpay_to_meet_path_minimum_msat {
doesnt_exceed_cltv_delta_limit && !payment_failed_on_this_channel &&
may_overpay_to_meet_path_minimum_msat
{
hit_minimum_limit = true;
} else if contributes_sufficient_value && doesnt_exceed_max_path_length &&
doesnt_exceed_cltv_delta_limit && over_path_minimum_msat {
doesnt_exceed_cltv_delta_limit && over_path_minimum_msat &&
!payment_failed_on_this_channel
{
// Note that low contribution here (limited by available_liquidity_msat)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of checking payment_failed_on_this_channel twice, consider having a leading if expression.

if payment_failed_on_this_channel {}elseif/* ... */{

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, good idea, I moved all the existing if conditions to that!

Comment threadlightning/src/routing/router.rs
Comment threadlightning/src/routing/scoring.rs Outdated
Comment on lines +629 to +646
// 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment needs to be updated now since it's not only when equal to the effective capacity. Seems like the earlier change that added this now removed logic is what caused calculates_log10_without_overflowing_u64_max_value to not exercise the correct code path as mentioned earlier on that test (i.e., it doesn't hit the negative_log10_times_2048 case below.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read the comment differently - I read the comment to say "the calculation we're doing here is equivalent to..." which is still true, no?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, you're right!

@jkczyzjkczyz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Largely looks good but one comment.

Comment on lines +629 to +646
// 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, you're right!

Comment on lines +632 to +649
let negative_log10_times_2048 = NEGATIVE_LOG10_UPPER_BOUND * 2048;
self.combined_penalty_msat(amount_msat, negative_log10_times_2048, params)
.saturating_add(params.considered_impossible_penalty_msat)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to do the max of these two rather than adding? Is the idea that we want this to be >= anything given in the else clause?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, that's the idea. I suppose we could do a max. Originally I had it not adding the combined_penalty call at all but that seemed to brittle to deal with so switched to adding. I don't honestly have a strong opinion between adding and max, either way the docs can tell users what's going on, but for max i kinda worry users will acidentally set it too low and get no penalty here, which seems strange?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They would also need to set the other params lower, though, since max would select the combined penalty over the considered_impossible_penalty_msat if the latter were too low. So it's all kinda relative. Don't feel too strongly either though note that max may make debugging a little easier as otherwise you may need to mentally subtract some combined penalty if it is not obvious.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, sure, I just meant it'd be easy for a user to look at the field and think "okay, let me pick something a bit higher than the liquidity offset, forget to multiply by 2 or whatever, and end up with a penalty equal to the liquidity penalty, which seems wrong? I dunno, I'm happy to mentally convert when we're debugging. Unless you feel strongly I'd suggest we leave it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, sure, I just meant it'd be easy for a user to look at the field and think "okay, let me pick something a bit higher than the liquidity offset, forget to multiply by 2 or whatever, and end up with a penalty equal to the liquidity penalty, which seems wrong?

Plus base and amount penalty, FWIW.

I dunno, I'm happy to mentally convert when we're debugging. Unless you feel strongly I'd suggest we leave it.

Sure we can leave it. Though one thing I just realized is that either way now the penalty will be variable across channels depending on the amount. Maybe less so when using max but maybe that's an argument in favor of adding. That way if left to choose only channels exceeding the maximum liquidity, we'd prefer ones that would otherwise be penalized less.

jkczyz
jkczyz previously approved these changes Jul 13, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed without further changes.

jkczyz
jkczyz previously approved these changes Jul 13, 2022
Comment threadlightning/src/routing/scoring.rs Outdated
jkczyz
jkczyz previously approved these changes Jul 13, 2022
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Squashed yet again. Change since yesterday was:

diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs
index 6aa19abaa..9fa62d83f 100644
--- a/lightning/src/routing/scoring.rs
+++ b/lightning/src/routing/scoring.rs
@@ -397,7 +397,8 @@ pub struct ProbabilisticScoringParameters {
/// current estimate of the channel's available liquidity.
///
-	/// Note that in this case the [`liquidity_penalty_multiplier_msat`] and
-	/// [`amount_penalty_multiplier_msat`]-based penalties are still included in the overall
-	/// penalty.
+	/// Note that in this case all other penalties, including the
+	/// [`liquidity_penalty_multiplier_msat`] and [`amount_penalty_multiplier_msat`]-based
+	/// penalties, as well as the [`base_penalty_msat`] and the [`anti_probing_penalty_msat`], if
+	/// applicable, are still included in the overall penalty.
///
/// If you wish to avoid creating paths with such channels entirely, setting this to a value of
@@ -408,4 +409,6 @@ pub struct ProbabilisticScoringParameters {
/// [`liquidity_penalty_multiplier_msat`]: Self::liquidity_penalty_multiplier_msat
/// [`amount_penalty_multiplier_msat`]: Self::amount_penalty_multiplier_msat
+	/// [`base_penalty_msat`]: Self::base_penalty_msat
+	/// [`anti_probing_penalty_msat`]: Self::anti_probing_penalty_msat
pub considered_impossible_penalty_msat: u64,
}

jkczyz
jkczyz previously approved these changes Jul 13, 2022
tnull
tnull previously approved these changes Jul 14, 2022
@jkczyz

Copy link
Copy Markdown
Contributor

66ca68a mentions failures on a payment-level, but isn't it really on a path-level? i.e., if two parts of an MPP fail on different channels, retrying one part could retry over the channel failed on the other part?

@TheBlueMatt
TheBlueMatt dismissed stale reviews from tnull and jkczyz via 5bff5f9July 14, 2022 15:59
@TheBlueMatt
TheBlueMattforce-pushed the 2022-07-explicit-avoid-retries branch from 8a1b418 to 5bff5f9CompareJuly 14, 2022 15:59
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Oops, right, sorry, rewrote that commit message without changes to the diff, added a note that it this does have the drawback of potentially retrying different parts along the same path, but hopefully the scorer doesn't let that happen unless the payment is gonna fail anyway.

@jkczyz

Copy link
Copy Markdown
Contributor

Oops, right, sorry, rewrote that commit message without changes to the diff, added a note that it this does have the drawback of potentially retrying different parts along the same path, but hopefully the scorer doesn't let that happen unless the payment is gonna fail anyway.

Yeah, same across payments, but as you said hopefully the scorer will learn quickly enough.

jkczyz
jkczyz previously approved these changes Jul 14, 2022
When an HTLC fails, we currently rely on the scorer learning the
failed channel and assigning an infinite (`u64::max_value()`)
penalty to the channel so as to avoid retrying over the exact same
path (if there's only one available path). This is common when
trying to pay a mobile client behind an LSP if the mobile client is
currently offline.
This leads to the scorer being overly conservative in some cases -
returning `u64::max_value()` when a given path hasn't been tried
for a given payment may not be the best decision, even if that
channel failed 50 minutes ago.
By tracking channels which failed on a payment part level and
explicitly refusing to route over them we can relax the
requirements on the scorer, allowing it to make different decisions
on how to treat channels that failed relatively recently without
causing payments to retry the same path forever.
This does have the drawback that it could allow two separate part
of a payment to traverse the same path even though that path just
failed, however this should only occur if the payment is going to
fail anyway, at least as long as the scorer is properly learning.
Closeslightningdevkit#1241, superseding lightningdevkit#1252.
When we consider sending an HTLC over a given channel impossible
due to our current knowledge of the channel's liquidity, we
currently always assign a penalty of `u64::max_value()`. However,
because we now refuse to retry a payment along the same path in
the router itself, we can now make this value configurable. This
allows users to have a relatively high knowledge decay interval
without the side-effect of refusing to try the only available path
in cases where a channel is intermittently available.
In general we should avoid taking paths that we are confident will
not work as much possible, but we should be willing to try each
payment at least once, even if its over a channel that failed
recently. A full Bitcoin penalty for such a channel seems
reasonable - lightning fees are unlikely to ever reach that point
so such channels will be scored much worse than any other potential
path, while still being below `u64::max_value()`.
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased to address merge conflict.

@TheBlueMatt
TheBlueMatt merged commit f75b6cb into lightningdevkit:mainJul 15, 2022
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Handle only-last-hop temp failure better

4 participants

@TheBlueMatt@codecov-commenter@jkczyz@tnull