Skip to content

Implement non-strict forwarding - #3127

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
wvanlint:non_strict_forwarding
Jun 20, 2024
Merged

Implement non-strict forwarding#3127
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
wvanlint:non_strict_forwarding

Conversation

@wvanlint

@wvanlintwvanlint commented Jun 14, 2024

Copy link
Copy Markdown
Contributor

This change implements non-strict forwarding, allowing the node to forward an HTLC along a channel other than the one specified by short_channel_id in the onion message, so long as the receiver has the same node public key intended by short_channel_id (BOLT). This can improve payment reliability when there are multiple channels with the same peer e.g. when outbound liquidity is replenished by opening a new channel.

The implemented forwarding strategy now chooses the channel with the least amount of outbound liquidity that can forward an HTLC to maximize the probability of being able to successfully forward a subsequent HTLC.

Fixes#1278.

I also tested a refactoring to reduce duplication by processing all HTLCForwardInfo::AddHTLCs separately first. However, the current approach is a more minimal and clearer change and we can assume that the order of magnitude of pending forwards will be small per channel.

@wvanlint
wvanlintforce-pushed the non_strict_forwarding branch from bbd46e5 to 694ed23CompareJune 14, 2024 23:32
@codecov-commenter

codecov-commenter commented Jun 14, 2024

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 85.89744% with 22 lines in your changes missing coverage. Please review.

Project coverage is 89.90%. Comparing base (07f3380) to head (8d915a8).

FilesPatch %Lines
lightning/src/ln/channelmanager.rs70.76%14 Missing and 5 partials ⚠️
lightning/src/ln/payment_tests.rs96.70%1 Missing and 2 partials ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@ Coverage Diff @@## main #3127 +/- ##
==========================================
- Coverage 89.92% 89.90% -0.03% 
==========================================
Files 121 121 Lines 99172 99290 +118 Branches 99172 99290 +118 ==========================================
+ Hits 89180 89263 +83 - Misses 7391 7415 +24 - Partials 2601 2612 +11 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@wvanlint
wvanlintforce-pushed the non_strict_forwarding branch 5 times, most recently from fa5ec86 to 8d915a8CompareJune 17, 2024 02:02
@wvanlint
wvanlint marked this pull request as ready for review June 17, 2024 02:04

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks!

Comment threadlightning/src/ln/channelmanager.rs Outdated
let mut channels_with_peer = peer_state.channel_by_id.values_mut().filter_map(|phase| match phase {
ChannelPhase::Funded(chan) => Some(chan),
_ => None,
}).collect::<Vec<&mut Channel<_>>>();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Rather than iterate + collect, we should be able to just pick a singular channel we want to use by iterating, filter_map'ing, and min_by'ing using Channel::get_available_balances and selecting the channel that has the lowest next_outbound_htlc_limit_msat above the amount we're trying to send. That would also simplify the patch as we don't have to iterate channels and try to add anymore.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done. This was mainly done to avoid duplication with the validation in send_htlc. Do we also want to verify the establishment/shutdown state?

if !matches!(self.context.channel_state,ChannelState::ChannelReady(_)) ||
self.context.channel_state.is_local_shutdown_sent() ||
self.context.channel_state.is_remote_shutdown_sent()
{
returnErr(ChannelError::Ignore("Cannot send HTLC until channel is fully established and we haven't started shutting down".to_owned()));
}
let channel_total_msat = self.context.channel_value_satoshis*1000;
if amount_msat > channel_total_msat {
returnErr(ChannelError::Ignore(format!("Cannot send amount {}, because it is more than the total value of the channel {}", amount_msat, channel_total_msat)));
}
if amount_msat == 0{
returnErr(ChannelError::Ignore("Cannot send 0-msat HTLC".to_owned()));
}
let available_balances = self.context.get_available_balances(fee_estimator);
if amount_msat < available_balances.next_outbound_htlc_minimum_msat{
returnErr(ChannelError::Ignore(format!("Cannot send less than our next-HTLC minimum - {} msat",
available_balances.next_outbound_htlc_minimum_msat)));
}
if amount_msat > available_balances.next_outbound_htlc_limit_msat{
returnErr(ChannelError::Ignore(format!("Cannot send more than our next-HTLC maximum - {} msat",
available_balances.next_outbound_htlc_limit_msat)));
}
ifself.context.channel_state.is_peer_disconnected(){
// Note that this should never really happen, if we're !is_live() on receipt of an
// incoming HTLC for relay will result in us rejecting the HTLC and we won't allow
// the user to send directly into a !is_live() channel. However, if we
// disconnected during the time the previous hop was doing the commitment dance we may
// end up getting here after the forwarding delay. In any case, returning an
// IgnoreError will get ChannelManager to do the right thing and fail backwards now.
returnErr(ChannelError::Ignore("Cannot send an HTLC while disconnected from channel counterparty".to_owned()));
}

@wvanlintwvanlintJun 18, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Had to adjust full_stack::tests::test_no_existing_test_breakage to account for the additional fee estimation call in get_available_balances.

@wvanlintwvanlintJun 20, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Do we also want to verify the establishment/shutdown state?

To check back in here, I did add an is_usable() check which I believe is necessary, but let me know if that's not the case.

@tnull
tnull requested review from tnullJune 17, 2024 20:42
@wvanlint
wvanlintforce-pushed the non_strict_forwarding branch 2 times, most recently from 6c7a6b2 to ca19602CompareJune 18, 2024 01:35
TheBlueMatt
TheBlueMatt previously approved these changes Jun 18, 2024

@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.

Mostly looks good to me, just some questions.

Feel free to squash the fixup!

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
This change implements non-strict forwarding, allowing the node to
forward an HTLC along a channel other than the one specified
by short_channel_id in the onion message, so long as the receiver has
the same node public key intended by short_channel_id
([BOLT](https://github.com/lightning/bolts/blob/57ce4b1e05c996fa649f00dc13521f6d496a288f/04-onion-routing.md#non-strict-forwarding)).
This can improve payment reliability when there are multiple channels
with the same peer e.g. when outbound liquidity is replenished by
opening a new channel.
The implemented forwarding strategy now chooses the channel with the
lowest outbound liquidity that can forward an HTLC to maximize the
probability of being able to successfully forward a subsequent HTLC.
Fixeslightningdevkit#1278.
@wvanlint

Copy link
Copy Markdown
ContributorAuthor

Thanks! Squashed the fixups.

@tnull

tnull commented Jun 20, 2024

Copy link
Copy Markdown
Contributor

Thanks! Squashed the fixups.

Thanks, just a quick note: next time it would help reviewing if you left them up for ~another round of review (i.e., ask reviewers before squashing them). Given that the whole block was re-indented, its now non-trivial to see what really changed with the last force-push.

@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

I'll leave the last ACK to Matt as he requested some of the changes added since he ACKed last.

@TheBlueMatt
TheBlueMatt merged commit 3828516 into lightningdevkit:mainJun 20, 2024
@wvanlint
wvanlint deleted the non_strict_forwarding branch October 17, 2024 22:08
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.

Use substitude channel for forwarding if we have another with the same peer

4 participants

@wvanlint@codecov-commenter@tnull@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
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;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Implement non-strict forwarding by wvanlint · Pull Request #3127 · lightningdevkit/rust-lightning · GitHub
Skip to content

Implement non-strict forwarding - #3127

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
wvanlint:non_strict_forwarding
Jun 20, 2024
Merged

Implement non-strict forwarding#3127
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
wvanlint:non_strict_forwarding

Conversation

@wvanlint

@wvanlintwvanlint commented Jun 14, 2024

Copy link
Copy Markdown
Contributor

This change implements non-strict forwarding, allowing the node to forward an HTLC along a channel other than the one specified by short_channel_id in the onion message, so long as the receiver has the same node public key intended by short_channel_id (BOLT). This can improve payment reliability when there are multiple channels with the same peer e.g. when outbound liquidity is replenished by opening a new channel.

The implemented forwarding strategy now chooses the channel with the least amount of outbound liquidity that can forward an HTLC to maximize the probability of being able to successfully forward a subsequent HTLC.

Fixes#1278.

I also tested a refactoring to reduce duplication by processing all HTLCForwardInfo::AddHTLCs separately first. However, the current approach is a more minimal and clearer change and we can assume that the order of magnitude of pending forwards will be small per channel.

@wvanlint
wvanlintforce-pushed the non_strict_forwarding branch from bbd46e5 to 694ed23CompareJune 14, 2024 23:32
@codecov-commenter

codecov-commenter commented Jun 14, 2024

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 85.89744% with 22 lines in your changes missing coverage. Please review.

Project coverage is 89.90%. Comparing base (07f3380) to head (8d915a8).

FilesPatch %Lines
lightning/src/ln/channelmanager.rs70.76%14 Missing and 5 partials ⚠️
lightning/src/ln/payment_tests.rs96.70%1 Missing and 2 partials ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@ Coverage Diff @@## main #3127 +/- ##
==========================================
- Coverage 89.92% 89.90% -0.03% 
==========================================
Files 121 121 Lines 99172 99290 +118 Branches 99172 99290 +118 ==========================================
+ Hits 89180 89263 +83 - Misses 7391 7415 +24 - Partials 2601 2612 +11 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@wvanlint
wvanlintforce-pushed the non_strict_forwarding branch 5 times, most recently from fa5ec86 to 8d915a8CompareJune 17, 2024 02:02
@wvanlint
wvanlint marked this pull request as ready for review June 17, 2024 02:04

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks!

Comment threadlightning/src/ln/channelmanager.rs Outdated
let mut channels_with_peer = peer_state.channel_by_id.values_mut().filter_map(|phase| match phase {
ChannelPhase::Funded(chan) => Some(chan),
_ => None,
}).collect::<Vec<&mut Channel<_>>>();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Rather than iterate + collect, we should be able to just pick a singular channel we want to use by iterating, filter_map'ing, and min_by'ing using Channel::get_available_balances and selecting the channel that has the lowest next_outbound_htlc_limit_msat above the amount we're trying to send. That would also simplify the patch as we don't have to iterate channels and try to add anymore.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done. This was mainly done to avoid duplication with the validation in send_htlc. Do we also want to verify the establishment/shutdown state?

if !matches!(self.context.channel_state,ChannelState::ChannelReady(_)) ||
self.context.channel_state.is_local_shutdown_sent() ||
self.context.channel_state.is_remote_shutdown_sent()
{
returnErr(ChannelError::Ignore("Cannot send HTLC until channel is fully established and we haven't started shutting down".to_owned()));
}
let channel_total_msat = self.context.channel_value_satoshis*1000;
if amount_msat > channel_total_msat {
returnErr(ChannelError::Ignore(format!("Cannot send amount {}, because it is more than the total value of the channel {}", amount_msat, channel_total_msat)));
}
if amount_msat == 0{
returnErr(ChannelError::Ignore("Cannot send 0-msat HTLC".to_owned()));
}
let available_balances = self.context.get_available_balances(fee_estimator);
if amount_msat < available_balances.next_outbound_htlc_minimum_msat{
returnErr(ChannelError::Ignore(format!("Cannot send less than our next-HTLC minimum - {} msat",
available_balances.next_outbound_htlc_minimum_msat)));
}
if amount_msat > available_balances.next_outbound_htlc_limit_msat{
returnErr(ChannelError::Ignore(format!("Cannot send more than our next-HTLC maximum - {} msat",
available_balances.next_outbound_htlc_limit_msat)));
}
ifself.context.channel_state.is_peer_disconnected(){
// Note that this should never really happen, if we're !is_live() on receipt of an
// incoming HTLC for relay will result in us rejecting the HTLC and we won't allow
// the user to send directly into a !is_live() channel. However, if we
// disconnected during the time the previous hop was doing the commitment dance we may
// end up getting here after the forwarding delay. In any case, returning an
// IgnoreError will get ChannelManager to do the right thing and fail backwards now.
returnErr(ChannelError::Ignore("Cannot send an HTLC while disconnected from channel counterparty".to_owned()));
}

@wvanlintwvanlintJun 18, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Had to adjust full_stack::tests::test_no_existing_test_breakage to account for the additional fee estimation call in get_available_balances.

@wvanlintwvanlintJun 20, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Do we also want to verify the establishment/shutdown state?

To check back in here, I did add an is_usable() check which I believe is necessary, but let me know if that's not the case.

@tnull
tnull requested review from tnullJune 17, 2024 20:42
@wvanlint
wvanlintforce-pushed the non_strict_forwarding branch 2 times, most recently from 6c7a6b2 to ca19602CompareJune 18, 2024 01:35
TheBlueMatt
TheBlueMatt previously approved these changes Jun 18, 2024

@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.

Mostly looks good to me, just some questions.

Feel free to squash the fixup!

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
This change implements non-strict forwarding, allowing the node to
forward an HTLC along a channel other than the one specified
by short_channel_id in the onion message, so long as the receiver has
the same node public key intended by short_channel_id
([BOLT](https://github.com/lightning/bolts/blob/57ce4b1e05c996fa649f00dc13521f6d496a288f/04-onion-routing.md#non-strict-forwarding)).
This can improve payment reliability when there are multiple channels
with the same peer e.g. when outbound liquidity is replenished by
opening a new channel.
The implemented forwarding strategy now chooses the channel with the
lowest outbound liquidity that can forward an HTLC to maximize the
probability of being able to successfully forward a subsequent HTLC.
Fixeslightningdevkit#1278.
@wvanlint

Copy link
Copy Markdown
ContributorAuthor

Thanks! Squashed the fixups.

@tnull

tnull commented Jun 20, 2024

Copy link
Copy Markdown
Contributor

Thanks! Squashed the fixups.

Thanks, just a quick note: next time it would help reviewing if you left them up for ~another round of review (i.e., ask reviewers before squashing them). Given that the whole block was re-indented, its now non-trivial to see what really changed with the last force-push.

@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

I'll leave the last ACK to Matt as he requested some of the changes added since he ACKed last.

@TheBlueMatt
TheBlueMatt merged commit 3828516 into lightningdevkit:mainJun 20, 2024
@wvanlint
wvanlint deleted the non_strict_forwarding branch October 17, 2024 22:08
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.

Use substitude channel for forwarding if we have another with the same peer

4 participants

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

Implement non-strict forwarding - #3127

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
wvanlint:non_strict_forwarding
Jun 20, 2024
Merged

Implement non-strict forwarding#3127
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
wvanlint:non_strict_forwarding

Conversation

@wvanlint

@wvanlintwvanlint commented Jun 14, 2024

Copy link
Copy Markdown
Contributor

This change implements non-strict forwarding, allowing the node to forward an HTLC along a channel other than the one specified by short_channel_id in the onion message, so long as the receiver has the same node public key intended by short_channel_id (BOLT). This can improve payment reliability when there are multiple channels with the same peer e.g. when outbound liquidity is replenished by opening a new channel.

The implemented forwarding strategy now chooses the channel with the least amount of outbound liquidity that can forward an HTLC to maximize the probability of being able to successfully forward a subsequent HTLC.

Fixes#1278.

I also tested a refactoring to reduce duplication by processing all HTLCForwardInfo::AddHTLCs separately first. However, the current approach is a more minimal and clearer change and we can assume that the order of magnitude of pending forwards will be small per channel.

@wvanlint
wvanlintforce-pushed the non_strict_forwarding branch from bbd46e5 to 694ed23CompareJune 14, 2024 23:32
@codecov-commenter

codecov-commenter commented Jun 14, 2024

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 85.89744% with 22 lines in your changes missing coverage. Please review.

Project coverage is 89.90%. Comparing base (07f3380) to head (8d915a8).

FilesPatch %Lines
lightning/src/ln/channelmanager.rs70.76%14 Missing and 5 partials ⚠️
lightning/src/ln/payment_tests.rs96.70%1 Missing and 2 partials ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@ Coverage Diff @@## main #3127 +/- ##
==========================================
- Coverage 89.92% 89.90% -0.03% 
==========================================
Files 121 121 Lines 99172 99290 +118 Branches 99172 99290 +118 ==========================================
+ Hits 89180 89263 +83 - Misses 7391 7415 +24 - Partials 2601 2612 +11 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@wvanlint
wvanlintforce-pushed the non_strict_forwarding branch 5 times, most recently from fa5ec86 to 8d915a8CompareJune 17, 2024 02:02
@wvanlint
wvanlint marked this pull request as ready for review June 17, 2024 02:04

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks!

Comment threadlightning/src/ln/channelmanager.rs Outdated
let mut channels_with_peer = peer_state.channel_by_id.values_mut().filter_map(|phase| match phase {
ChannelPhase::Funded(chan) => Some(chan),
_ => None,
}).collect::<Vec<&mut Channel<_>>>();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Rather than iterate + collect, we should be able to just pick a singular channel we want to use by iterating, filter_map'ing, and min_by'ing using Channel::get_available_balances and selecting the channel that has the lowest next_outbound_htlc_limit_msat above the amount we're trying to send. That would also simplify the patch as we don't have to iterate channels and try to add anymore.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done. This was mainly done to avoid duplication with the validation in send_htlc. Do we also want to verify the establishment/shutdown state?

if !matches!(self.context.channel_state,ChannelState::ChannelReady(_)) ||
self.context.channel_state.is_local_shutdown_sent() ||
self.context.channel_state.is_remote_shutdown_sent()
{
returnErr(ChannelError::Ignore("Cannot send HTLC until channel is fully established and we haven't started shutting down".to_owned()));
}
let channel_total_msat = self.context.channel_value_satoshis*1000;
if amount_msat > channel_total_msat {
returnErr(ChannelError::Ignore(format!("Cannot send amount {}, because it is more than the total value of the channel {}", amount_msat, channel_total_msat)));
}
if amount_msat == 0{
returnErr(ChannelError::Ignore("Cannot send 0-msat HTLC".to_owned()));
}
let available_balances = self.context.get_available_balances(fee_estimator);
if amount_msat < available_balances.next_outbound_htlc_minimum_msat{
returnErr(ChannelError::Ignore(format!("Cannot send less than our next-HTLC minimum - {} msat",
available_balances.next_outbound_htlc_minimum_msat)));
}
if amount_msat > available_balances.next_outbound_htlc_limit_msat{
returnErr(ChannelError::Ignore(format!("Cannot send more than our next-HTLC maximum - {} msat",
available_balances.next_outbound_htlc_limit_msat)));
}
ifself.context.channel_state.is_peer_disconnected(){
// Note that this should never really happen, if we're !is_live() on receipt of an
// incoming HTLC for relay will result in us rejecting the HTLC and we won't allow
// the user to send directly into a !is_live() channel. However, if we
// disconnected during the time the previous hop was doing the commitment dance we may
// end up getting here after the forwarding delay. In any case, returning an
// IgnoreError will get ChannelManager to do the right thing and fail backwards now.
returnErr(ChannelError::Ignore("Cannot send an HTLC while disconnected from channel counterparty".to_owned()));
}

@wvanlintwvanlintJun 18, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Had to adjust full_stack::tests::test_no_existing_test_breakage to account for the additional fee estimation call in get_available_balances.

@wvanlintwvanlintJun 20, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Do we also want to verify the establishment/shutdown state?

To check back in here, I did add an is_usable() check which I believe is necessary, but let me know if that's not the case.

@tnull
tnull requested review from tnullJune 17, 2024 20:42
@wvanlint
wvanlintforce-pushed the non_strict_forwarding branch 2 times, most recently from 6c7a6b2 to ca19602CompareJune 18, 2024 01:35
TheBlueMatt
TheBlueMatt previously approved these changes Jun 18, 2024

@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.

Mostly looks good to me, just some questions.

Feel free to squash the fixup!

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
This change implements non-strict forwarding, allowing the node to
forward an HTLC along a channel other than the one specified
by short_channel_id in the onion message, so long as the receiver has
the same node public key intended by short_channel_id
([BOLT](https://github.com/lightning/bolts/blob/57ce4b1e05c996fa649f00dc13521f6d496a288f/04-onion-routing.md#non-strict-forwarding)).
This can improve payment reliability when there are multiple channels
with the same peer e.g. when outbound liquidity is replenished by
opening a new channel.
The implemented forwarding strategy now chooses the channel with the
lowest outbound liquidity that can forward an HTLC to maximize the
probability of being able to successfully forward a subsequent HTLC.
Fixeslightningdevkit#1278.
@wvanlint

Copy link
Copy Markdown
ContributorAuthor

Thanks! Squashed the fixups.

@tnull

tnull commented Jun 20, 2024

Copy link
Copy Markdown
Contributor

Thanks! Squashed the fixups.

Thanks, just a quick note: next time it would help reviewing if you left them up for ~another round of review (i.e., ask reviewers before squashing them). Given that the whole block was re-indented, its now non-trivial to see what really changed with the last force-push.

@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

I'll leave the last ACK to Matt as he requested some of the changes added since he ACKed last.

@TheBlueMatt
TheBlueMatt merged commit 3828516 into lightningdevkit:mainJun 20, 2024
@wvanlint
wvanlint deleted the non_strict_forwarding branch October 17, 2024 22:08
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.

Use substitude channel for forwarding if we have another with the same peer

4 participants

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

Implement non-strict forwarding - #3127

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
wvanlint:non_strict_forwarding
Jun 20, 2024
Merged

Implement non-strict forwarding#3127
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
wvanlint:non_strict_forwarding

Conversation

@wvanlint

@wvanlintwvanlint commented Jun 14, 2024

Copy link
Copy Markdown
Contributor

This change implements non-strict forwarding, allowing the node to forward an HTLC along a channel other than the one specified by short_channel_id in the onion message, so long as the receiver has the same node public key intended by short_channel_id (BOLT). This can improve payment reliability when there are multiple channels with the same peer e.g. when outbound liquidity is replenished by opening a new channel.

The implemented forwarding strategy now chooses the channel with the least amount of outbound liquidity that can forward an HTLC to maximize the probability of being able to successfully forward a subsequent HTLC.

Fixes#1278.

I also tested a refactoring to reduce duplication by processing all HTLCForwardInfo::AddHTLCs separately first. However, the current approach is a more minimal and clearer change and we can assume that the order of magnitude of pending forwards will be small per channel.

@wvanlint
wvanlintforce-pushed the non_strict_forwarding branch from bbd46e5 to 694ed23CompareJune 14, 2024 23:32
@codecov-commenter

codecov-commenter commented Jun 14, 2024

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 85.89744% with 22 lines in your changes missing coverage. Please review.

Project coverage is 89.90%. Comparing base (07f3380) to head (8d915a8).

FilesPatch %Lines
lightning/src/ln/channelmanager.rs70.76%14 Missing and 5 partials ⚠️
lightning/src/ln/payment_tests.rs96.70%1 Missing and 2 partials ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@ Coverage Diff @@## main #3127 +/- ##
==========================================
- Coverage 89.92% 89.90% -0.03% 
==========================================
Files 121 121 Lines 99172 99290 +118 Branches 99172 99290 +118 ==========================================
+ Hits 89180 89263 +83 - Misses 7391 7415 +24 - Partials 2601 2612 +11 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@wvanlint
wvanlintforce-pushed the non_strict_forwarding branch 5 times, most recently from fa5ec86 to 8d915a8CompareJune 17, 2024 02:02
@wvanlint
wvanlint marked this pull request as ready for review June 17, 2024 02:04

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks!

Comment threadlightning/src/ln/channelmanager.rs Outdated
let mut channels_with_peer = peer_state.channel_by_id.values_mut().filter_map(|phase| match phase {
ChannelPhase::Funded(chan) => Some(chan),
_ => None,
}).collect::<Vec<&mut Channel<_>>>();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Rather than iterate + collect, we should be able to just pick a singular channel we want to use by iterating, filter_map'ing, and min_by'ing using Channel::get_available_balances and selecting the channel that has the lowest next_outbound_htlc_limit_msat above the amount we're trying to send. That would also simplify the patch as we don't have to iterate channels and try to add anymore.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done. This was mainly done to avoid duplication with the validation in send_htlc. Do we also want to verify the establishment/shutdown state?

if !matches!(self.context.channel_state,ChannelState::ChannelReady(_)) ||
self.context.channel_state.is_local_shutdown_sent() ||
self.context.channel_state.is_remote_shutdown_sent()
{
returnErr(ChannelError::Ignore("Cannot send HTLC until channel is fully established and we haven't started shutting down".to_owned()));
}
let channel_total_msat = self.context.channel_value_satoshis*1000;
if amount_msat > channel_total_msat {
returnErr(ChannelError::Ignore(format!("Cannot send amount {}, because it is more than the total value of the channel {}", amount_msat, channel_total_msat)));
}
if amount_msat == 0{
returnErr(ChannelError::Ignore("Cannot send 0-msat HTLC".to_owned()));
}
let available_balances = self.context.get_available_balances(fee_estimator);
if amount_msat < available_balances.next_outbound_htlc_minimum_msat{
returnErr(ChannelError::Ignore(format!("Cannot send less than our next-HTLC minimum - {} msat",
available_balances.next_outbound_htlc_minimum_msat)));
}
if amount_msat > available_balances.next_outbound_htlc_limit_msat{
returnErr(ChannelError::Ignore(format!("Cannot send more than our next-HTLC maximum - {} msat",
available_balances.next_outbound_htlc_limit_msat)));
}
ifself.context.channel_state.is_peer_disconnected(){
// Note that this should never really happen, if we're !is_live() on receipt of an
// incoming HTLC for relay will result in us rejecting the HTLC and we won't allow
// the user to send directly into a !is_live() channel. However, if we
// disconnected during the time the previous hop was doing the commitment dance we may
// end up getting here after the forwarding delay. In any case, returning an
// IgnoreError will get ChannelManager to do the right thing and fail backwards now.
returnErr(ChannelError::Ignore("Cannot send an HTLC while disconnected from channel counterparty".to_owned()));
}

@wvanlintwvanlintJun 18, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Had to adjust full_stack::tests::test_no_existing_test_breakage to account for the additional fee estimation call in get_available_balances.

@wvanlintwvanlintJun 20, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Do we also want to verify the establishment/shutdown state?

To check back in here, I did add an is_usable() check which I believe is necessary, but let me know if that's not the case.

@tnull
tnull requested review from tnullJune 17, 2024 20:42
@wvanlint
wvanlintforce-pushed the non_strict_forwarding branch 2 times, most recently from 6c7a6b2 to ca19602CompareJune 18, 2024 01:35
TheBlueMatt
TheBlueMatt previously approved these changes Jun 18, 2024

@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.

Mostly looks good to me, just some questions.

Feel free to squash the fixup!

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
This change implements non-strict forwarding, allowing the node to
forward an HTLC along a channel other than the one specified
by short_channel_id in the onion message, so long as the receiver has
the same node public key intended by short_channel_id
([BOLT](https://github.com/lightning/bolts/blob/57ce4b1e05c996fa649f00dc13521f6d496a288f/04-onion-routing.md#non-strict-forwarding)).
This can improve payment reliability when there are multiple channels
with the same peer e.g. when outbound liquidity is replenished by
opening a new channel.
The implemented forwarding strategy now chooses the channel with the
lowest outbound liquidity that can forward an HTLC to maximize the
probability of being able to successfully forward a subsequent HTLC.
Fixeslightningdevkit#1278.
@wvanlint

Copy link
Copy Markdown
ContributorAuthor

Thanks! Squashed the fixups.

@tnull

tnull commented Jun 20, 2024

Copy link
Copy Markdown
Contributor

Thanks! Squashed the fixups.

Thanks, just a quick note: next time it would help reviewing if you left them up for ~another round of review (i.e., ask reviewers before squashing them). Given that the whole block was re-indented, its now non-trivial to see what really changed with the last force-push.

@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

I'll leave the last ACK to Matt as he requested some of the changes added since he ACKed last.

@TheBlueMatt
TheBlueMatt merged commit 3828516 into lightningdevkit:mainJun 20, 2024
@wvanlint
wvanlint deleted the non_strict_forwarding branch October 17, 2024 22:08
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.

Use substitude channel for forwarding if we have another with the same peer

4 participants

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

Implement non-strict forwarding - #3127

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
wvanlint:non_strict_forwarding
Jun 20, 2024
Merged

Implement non-strict forwarding#3127
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
wvanlint:non_strict_forwarding

Conversation

@wvanlint

@wvanlintwvanlint commented Jun 14, 2024

Copy link
Copy Markdown
Contributor

This change implements non-strict forwarding, allowing the node to forward an HTLC along a channel other than the one specified by short_channel_id in the onion message, so long as the receiver has the same node public key intended by short_channel_id (BOLT). This can improve payment reliability when there are multiple channels with the same peer e.g. when outbound liquidity is replenished by opening a new channel.

The implemented forwarding strategy now chooses the channel with the least amount of outbound liquidity that can forward an HTLC to maximize the probability of being able to successfully forward a subsequent HTLC.

Fixes#1278.

I also tested a refactoring to reduce duplication by processing all HTLCForwardInfo::AddHTLCs separately first. However, the current approach is a more minimal and clearer change and we can assume that the order of magnitude of pending forwards will be small per channel.

@wvanlint
wvanlintforce-pushed the non_strict_forwarding branch from bbd46e5 to 694ed23CompareJune 14, 2024 23:32
@codecov-commenter

codecov-commenter commented Jun 14, 2024

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 85.89744% with 22 lines in your changes missing coverage. Please review.

Project coverage is 89.90%. Comparing base (07f3380) to head (8d915a8).

FilesPatch %Lines
lightning/src/ln/channelmanager.rs70.76%14 Missing and 5 partials ⚠️
lightning/src/ln/payment_tests.rs96.70%1 Missing and 2 partials ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@ Coverage Diff @@## main #3127 +/- ##
==========================================
- Coverage 89.92% 89.90% -0.03% 
==========================================
Files 121 121 Lines 99172 99290 +118 Branches 99172 99290 +118 ==========================================
+ Hits 89180 89263 +83 - Misses 7391 7415 +24 - Partials 2601 2612 +11 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@wvanlint
wvanlintforce-pushed the non_strict_forwarding branch 5 times, most recently from fa5ec86 to 8d915a8CompareJune 17, 2024 02:02
@wvanlint
wvanlint marked this pull request as ready for review June 17, 2024 02:04

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks!

Comment threadlightning/src/ln/channelmanager.rs Outdated
let mut channels_with_peer = peer_state.channel_by_id.values_mut().filter_map(|phase| match phase {
ChannelPhase::Funded(chan) => Some(chan),
_ => None,
}).collect::<Vec<&mut Channel<_>>>();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Rather than iterate + collect, we should be able to just pick a singular channel we want to use by iterating, filter_map'ing, and min_by'ing using Channel::get_available_balances and selecting the channel that has the lowest next_outbound_htlc_limit_msat above the amount we're trying to send. That would also simplify the patch as we don't have to iterate channels and try to add anymore.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done. This was mainly done to avoid duplication with the validation in send_htlc. Do we also want to verify the establishment/shutdown state?

if !matches!(self.context.channel_state,ChannelState::ChannelReady(_)) ||
self.context.channel_state.is_local_shutdown_sent() ||
self.context.channel_state.is_remote_shutdown_sent()
{
returnErr(ChannelError::Ignore("Cannot send HTLC until channel is fully established and we haven't started shutting down".to_owned()));
}
let channel_total_msat = self.context.channel_value_satoshis*1000;
if amount_msat > channel_total_msat {
returnErr(ChannelError::Ignore(format!("Cannot send amount {}, because it is more than the total value of the channel {}", amount_msat, channel_total_msat)));
}
if amount_msat == 0{
returnErr(ChannelError::Ignore("Cannot send 0-msat HTLC".to_owned()));
}
let available_balances = self.context.get_available_balances(fee_estimator);
if amount_msat < available_balances.next_outbound_htlc_minimum_msat{
returnErr(ChannelError::Ignore(format!("Cannot send less than our next-HTLC minimum - {} msat",
available_balances.next_outbound_htlc_minimum_msat)));
}
if amount_msat > available_balances.next_outbound_htlc_limit_msat{
returnErr(ChannelError::Ignore(format!("Cannot send more than our next-HTLC maximum - {} msat",
available_balances.next_outbound_htlc_limit_msat)));
}
ifself.context.channel_state.is_peer_disconnected(){
// Note that this should never really happen, if we're !is_live() on receipt of an
// incoming HTLC for relay will result in us rejecting the HTLC and we won't allow
// the user to send directly into a !is_live() channel. However, if we
// disconnected during the time the previous hop was doing the commitment dance we may
// end up getting here after the forwarding delay. In any case, returning an
// IgnoreError will get ChannelManager to do the right thing and fail backwards now.
returnErr(ChannelError::Ignore("Cannot send an HTLC while disconnected from channel counterparty".to_owned()));
}

@wvanlintwvanlintJun 18, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Had to adjust full_stack::tests::test_no_existing_test_breakage to account for the additional fee estimation call in get_available_balances.

@wvanlintwvanlintJun 20, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Do we also want to verify the establishment/shutdown state?

To check back in here, I did add an is_usable() check which I believe is necessary, but let me know if that's not the case.

@tnull
tnull requested review from tnullJune 17, 2024 20:42
@wvanlint
wvanlintforce-pushed the non_strict_forwarding branch 2 times, most recently from 6c7a6b2 to ca19602CompareJune 18, 2024 01:35
TheBlueMatt
TheBlueMatt previously approved these changes Jun 18, 2024

@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.

Mostly looks good to me, just some questions.

Feel free to squash the fixup!

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
This change implements non-strict forwarding, allowing the node to
forward an HTLC along a channel other than the one specified
by short_channel_id in the onion message, so long as the receiver has
the same node public key intended by short_channel_id
([BOLT](https://github.com/lightning/bolts/blob/57ce4b1e05c996fa649f00dc13521f6d496a288f/04-onion-routing.md#non-strict-forwarding)).
This can improve payment reliability when there are multiple channels
with the same peer e.g. when outbound liquidity is replenished by
opening a new channel.
The implemented forwarding strategy now chooses the channel with the
lowest outbound liquidity that can forward an HTLC to maximize the
probability of being able to successfully forward a subsequent HTLC.
Fixeslightningdevkit#1278.
@wvanlint

Copy link
Copy Markdown
ContributorAuthor

Thanks! Squashed the fixups.

@tnull

tnull commented Jun 20, 2024

Copy link
Copy Markdown
Contributor

Thanks! Squashed the fixups.

Thanks, just a quick note: next time it would help reviewing if you left them up for ~another round of review (i.e., ask reviewers before squashing them). Given that the whole block was re-indented, its now non-trivial to see what really changed with the last force-push.

@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

I'll leave the last ACK to Matt as he requested some of the changes added since he ACKed last.

@TheBlueMatt
TheBlueMatt merged commit 3828516 into lightningdevkit:mainJun 20, 2024
@wvanlint
wvanlint deleted the non_strict_forwarding branch October 17, 2024 22:08
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.

Use substitude channel for forwarding if we have another with the same peer

4 participants

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

Implement non-strict forwarding - #3127

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
wvanlint:non_strict_forwarding
Jun 20, 2024
Merged

Implement non-strict forwarding#3127
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
wvanlint:non_strict_forwarding

Conversation

@wvanlint

@wvanlintwvanlint commented Jun 14, 2024

Copy link
Copy Markdown
Contributor

This change implements non-strict forwarding, allowing the node to forward an HTLC along a channel other than the one specified by short_channel_id in the onion message, so long as the receiver has the same node public key intended by short_channel_id (BOLT). This can improve payment reliability when there are multiple channels with the same peer e.g. when outbound liquidity is replenished by opening a new channel.

The implemented forwarding strategy now chooses the channel with the least amount of outbound liquidity that can forward an HTLC to maximize the probability of being able to successfully forward a subsequent HTLC.

Fixes#1278.

I also tested a refactoring to reduce duplication by processing all HTLCForwardInfo::AddHTLCs separately first. However, the current approach is a more minimal and clearer change and we can assume that the order of magnitude of pending forwards will be small per channel.

@wvanlint
wvanlintforce-pushed the non_strict_forwarding branch from bbd46e5 to 694ed23CompareJune 14, 2024 23:32
@codecov-commenter

codecov-commenter commented Jun 14, 2024

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 85.89744% with 22 lines in your changes missing coverage. Please review.

Project coverage is 89.90%. Comparing base (07f3380) to head (8d915a8).

FilesPatch %Lines
lightning/src/ln/channelmanager.rs70.76%14 Missing and 5 partials ⚠️
lightning/src/ln/payment_tests.rs96.70%1 Missing and 2 partials ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@ Coverage Diff @@## main #3127 +/- ##
==========================================
- Coverage 89.92% 89.90% -0.03% 
==========================================
Files 121 121 Lines 99172 99290 +118 Branches 99172 99290 +118 ==========================================
+ Hits 89180 89263 +83 - Misses 7391 7415 +24 - Partials 2601 2612 +11 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@wvanlint
wvanlintforce-pushed the non_strict_forwarding branch 5 times, most recently from fa5ec86 to 8d915a8CompareJune 17, 2024 02:02
@wvanlint
wvanlint marked this pull request as ready for review June 17, 2024 02:04

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks!

Comment threadlightning/src/ln/channelmanager.rs Outdated
let mut channels_with_peer = peer_state.channel_by_id.values_mut().filter_map(|phase| match phase {
ChannelPhase::Funded(chan) => Some(chan),
_ => None,
}).collect::<Vec<&mut Channel<_>>>();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Rather than iterate + collect, we should be able to just pick a singular channel we want to use by iterating, filter_map'ing, and min_by'ing using Channel::get_available_balances and selecting the channel that has the lowest next_outbound_htlc_limit_msat above the amount we're trying to send. That would also simplify the patch as we don't have to iterate channels and try to add anymore.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done. This was mainly done to avoid duplication with the validation in send_htlc. Do we also want to verify the establishment/shutdown state?

if !matches!(self.context.channel_state,ChannelState::ChannelReady(_)) ||
self.context.channel_state.is_local_shutdown_sent() ||
self.context.channel_state.is_remote_shutdown_sent()
{
returnErr(ChannelError::Ignore("Cannot send HTLC until channel is fully established and we haven't started shutting down".to_owned()));
}
let channel_total_msat = self.context.channel_value_satoshis*1000;
if amount_msat > channel_total_msat {
returnErr(ChannelError::Ignore(format!("Cannot send amount {}, because it is more than the total value of the channel {}", amount_msat, channel_total_msat)));
}
if amount_msat == 0{
returnErr(ChannelError::Ignore("Cannot send 0-msat HTLC".to_owned()));
}
let available_balances = self.context.get_available_balances(fee_estimator);
if amount_msat < available_balances.next_outbound_htlc_minimum_msat{
returnErr(ChannelError::Ignore(format!("Cannot send less than our next-HTLC minimum - {} msat",
available_balances.next_outbound_htlc_minimum_msat)));
}
if amount_msat > available_balances.next_outbound_htlc_limit_msat{
returnErr(ChannelError::Ignore(format!("Cannot send more than our next-HTLC maximum - {} msat",
available_balances.next_outbound_htlc_limit_msat)));
}
ifself.context.channel_state.is_peer_disconnected(){
// Note that this should never really happen, if we're !is_live() on receipt of an
// incoming HTLC for relay will result in us rejecting the HTLC and we won't allow
// the user to send directly into a !is_live() channel. However, if we
// disconnected during the time the previous hop was doing the commitment dance we may
// end up getting here after the forwarding delay. In any case, returning an
// IgnoreError will get ChannelManager to do the right thing and fail backwards now.
returnErr(ChannelError::Ignore("Cannot send an HTLC while disconnected from channel counterparty".to_owned()));
}

@wvanlintwvanlintJun 18, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Had to adjust full_stack::tests::test_no_existing_test_breakage to account for the additional fee estimation call in get_available_balances.

@wvanlintwvanlintJun 20, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Do we also want to verify the establishment/shutdown state?

To check back in here, I did add an is_usable() check which I believe is necessary, but let me know if that's not the case.

@tnull
tnull requested review from tnullJune 17, 2024 20:42
@wvanlint
wvanlintforce-pushed the non_strict_forwarding branch 2 times, most recently from 6c7a6b2 to ca19602CompareJune 18, 2024 01:35
TheBlueMatt
TheBlueMatt previously approved these changes Jun 18, 2024

@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.

Mostly looks good to me, just some questions.

Feel free to squash the fixup!

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
This change implements non-strict forwarding, allowing the node to
forward an HTLC along a channel other than the one specified
by short_channel_id in the onion message, so long as the receiver has
the same node public key intended by short_channel_id
([BOLT](https://github.com/lightning/bolts/blob/57ce4b1e05c996fa649f00dc13521f6d496a288f/04-onion-routing.md#non-strict-forwarding)).
This can improve payment reliability when there are multiple channels
with the same peer e.g. when outbound liquidity is replenished by
opening a new channel.
The implemented forwarding strategy now chooses the channel with the
lowest outbound liquidity that can forward an HTLC to maximize the
probability of being able to successfully forward a subsequent HTLC.
Fixeslightningdevkit#1278.
@wvanlint

Copy link
Copy Markdown
ContributorAuthor

Thanks! Squashed the fixups.

@tnull

tnull commented Jun 20, 2024

Copy link
Copy Markdown
Contributor

Thanks! Squashed the fixups.

Thanks, just a quick note: next time it would help reviewing if you left them up for ~another round of review (i.e., ask reviewers before squashing them). Given that the whole block was re-indented, its now non-trivial to see what really changed with the last force-push.

@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

I'll leave the last ACK to Matt as he requested some of the changes added since he ACKed last.

@TheBlueMatt
TheBlueMatt merged commit 3828516 into lightningdevkit:mainJun 20, 2024
@wvanlint
wvanlint deleted the non_strict_forwarding branch October 17, 2024 22:08
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.

Use substitude channel for forwarding if we have another with the same peer

4 participants

@wvanlint@codecov-commenter@tnull@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Implement non-strict forwarding by wvanlint · Pull Request #3127 · lightningdevkit/rust-lightning · GitHub
Skip to content

Implement non-strict forwarding - #3127

Merged
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
wvanlint:non_strict_forwarding
Jun 20, 2024
Merged

Implement non-strict forwarding#3127
TheBlueMatt merged 1 commit into
lightningdevkit:mainfrom
wvanlint:non_strict_forwarding

Conversation

@wvanlint

@wvanlintwvanlint commented Jun 14, 2024

Copy link
Copy Markdown
Contributor

This change implements non-strict forwarding, allowing the node to forward an HTLC along a channel other than the one specified by short_channel_id in the onion message, so long as the receiver has the same node public key intended by short_channel_id (BOLT). This can improve payment reliability when there are multiple channels with the same peer e.g. when outbound liquidity is replenished by opening a new channel.

The implemented forwarding strategy now chooses the channel with the least amount of outbound liquidity that can forward an HTLC to maximize the probability of being able to successfully forward a subsequent HTLC.

Fixes#1278.

I also tested a refactoring to reduce duplication by processing all HTLCForwardInfo::AddHTLCs separately first. However, the current approach is a more minimal and clearer change and we can assume that the order of magnitude of pending forwards will be small per channel.

@wvanlint
wvanlintforce-pushed the non_strict_forwarding branch from bbd46e5 to 694ed23CompareJune 14, 2024 23:32
@codecov-commenter

codecov-commenter commented Jun 14, 2024

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 85.89744% with 22 lines in your changes missing coverage. Please review.

Project coverage is 89.90%. Comparing base (07f3380) to head (8d915a8).

FilesPatch %Lines
lightning/src/ln/channelmanager.rs70.76%14 Missing and 5 partials ⚠️
lightning/src/ln/payment_tests.rs96.70%1 Missing and 2 partials ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@ Coverage Diff @@## main #3127 +/- ##
==========================================
- Coverage 89.92% 89.90% -0.03% 
==========================================
Files 121 121 Lines 99172 99290 +118 Branches 99172 99290 +118 ==========================================
+ Hits 89180 89263 +83 - Misses 7391 7415 +24 - Partials 2601 2612 +11 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@wvanlint
wvanlintforce-pushed the non_strict_forwarding branch 5 times, most recently from fa5ec86 to 8d915a8CompareJune 17, 2024 02:02
@wvanlint
wvanlint marked this pull request as ready for review June 17, 2024 02:04

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks!

Comment threadlightning/src/ln/channelmanager.rs Outdated
let mut channels_with_peer = peer_state.channel_by_id.values_mut().filter_map(|phase| match phase {
ChannelPhase::Funded(chan) => Some(chan),
_ => None,
}).collect::<Vec<&mut Channel<_>>>();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Rather than iterate + collect, we should be able to just pick a singular channel we want to use by iterating, filter_map'ing, and min_by'ing using Channel::get_available_balances and selecting the channel that has the lowest next_outbound_htlc_limit_msat above the amount we're trying to send. That would also simplify the patch as we don't have to iterate channels and try to add anymore.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done. This was mainly done to avoid duplication with the validation in send_htlc. Do we also want to verify the establishment/shutdown state?

if !matches!(self.context.channel_state,ChannelState::ChannelReady(_)) ||
self.context.channel_state.is_local_shutdown_sent() ||
self.context.channel_state.is_remote_shutdown_sent()
{
returnErr(ChannelError::Ignore("Cannot send HTLC until channel is fully established and we haven't started shutting down".to_owned()));
}
let channel_total_msat = self.context.channel_value_satoshis*1000;
if amount_msat > channel_total_msat {
returnErr(ChannelError::Ignore(format!("Cannot send amount {}, because it is more than the total value of the channel {}", amount_msat, channel_total_msat)));
}
if amount_msat == 0{
returnErr(ChannelError::Ignore("Cannot send 0-msat HTLC".to_owned()));
}
let available_balances = self.context.get_available_balances(fee_estimator);
if amount_msat < available_balances.next_outbound_htlc_minimum_msat{
returnErr(ChannelError::Ignore(format!("Cannot send less than our next-HTLC minimum - {} msat",
available_balances.next_outbound_htlc_minimum_msat)));
}
if amount_msat > available_balances.next_outbound_htlc_limit_msat{
returnErr(ChannelError::Ignore(format!("Cannot send more than our next-HTLC maximum - {} msat",
available_balances.next_outbound_htlc_limit_msat)));
}
ifself.context.channel_state.is_peer_disconnected(){
// Note that this should never really happen, if we're !is_live() on receipt of an
// incoming HTLC for relay will result in us rejecting the HTLC and we won't allow
// the user to send directly into a !is_live() channel. However, if we
// disconnected during the time the previous hop was doing the commitment dance we may
// end up getting here after the forwarding delay. In any case, returning an
// IgnoreError will get ChannelManager to do the right thing and fail backwards now.
returnErr(ChannelError::Ignore("Cannot send an HTLC while disconnected from channel counterparty".to_owned()));
}

@wvanlintwvanlintJun 18, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Had to adjust full_stack::tests::test_no_existing_test_breakage to account for the additional fee estimation call in get_available_balances.

@wvanlintwvanlintJun 20, 2024

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Do we also want to verify the establishment/shutdown state?

To check back in here, I did add an is_usable() check which I believe is necessary, but let me know if that's not the case.

@tnull
tnull requested review from tnullJune 17, 2024 20:42
@wvanlint
wvanlintforce-pushed the non_strict_forwarding branch 2 times, most recently from 6c7a6b2 to ca19602CompareJune 18, 2024 01:35
TheBlueMatt
TheBlueMatt previously approved these changes Jun 18, 2024

@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.

Mostly looks good to me, just some questions.

Feel free to squash the fixup!

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channelmanager.rs Outdated
This change implements non-strict forwarding, allowing the node to
forward an HTLC along a channel other than the one specified
by short_channel_id in the onion message, so long as the receiver has
the same node public key intended by short_channel_id
([BOLT](https://github.com/lightning/bolts/blob/57ce4b1e05c996fa649f00dc13521f6d496a288f/04-onion-routing.md#non-strict-forwarding)).
This can improve payment reliability when there are multiple channels
with the same peer e.g. when outbound liquidity is replenished by
opening a new channel.
The implemented forwarding strategy now chooses the channel with the
lowest outbound liquidity that can forward an HTLC to maximize the
probability of being able to successfully forward a subsequent HTLC.
Fixeslightningdevkit#1278.
@wvanlint

Copy link
Copy Markdown
ContributorAuthor

Thanks! Squashed the fixups.

@tnull

tnull commented Jun 20, 2024

Copy link
Copy Markdown
Contributor

Thanks! Squashed the fixups.

Thanks, just a quick note: next time it would help reviewing if you left them up for ~another round of review (i.e., ask reviewers before squashing them). Given that the whole block was re-indented, its now non-trivial to see what really changed with the last force-push.

@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

I'll leave the last ACK to Matt as he requested some of the changes added since he ACKed last.

@TheBlueMatt
TheBlueMatt merged commit 3828516 into lightningdevkit:mainJun 20, 2024
@wvanlint
wvanlint deleted the non_strict_forwarding branch October 17, 2024 22:08
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.

Use substitude channel for forwarding if we have another with the same peer

4 participants

@wvanlint@codecov-commenter@tnull@TheBlueMatt