Update fee and dust handling for zero fee channels - #3884

Merged
TheBlueMatt merged 10 commits into
lightningdevkit:mainfrom
carlaKC:3789-fees-and-dust
Jul 17, 2025
Merged

Update fee and dust handling for zero fee channels#3884
TheBlueMatt merged 10 commits into
lightningdevkit:mainfrom
carlaKC:3789-fees-and-dust

Conversation

@carlaKC

Copy link
Copy Markdown
Contributor

This PR completes the off-chain handling of V3 channels, as described in #3789.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 23, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@carlaKC

Copy link
Copy Markdown
ContributorAuthor

This still needs a few tests, opening up early for conceptual review.

) -> u64 {
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {
debug_assert_eq!(self.feerate_per_kw, 0);
debug_assert!(fee_spike_buffer_htlc.is_none());

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.

Wondering whether it's worth having this assertion (which makes it necessary to check channel type before calling fee functions). The alternative would be to just ignore the parameters completely for zero fee channels (even though Somefee_spike_buffer_htlc doesn't make sense for the type).

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.

Can't say I have a strong preference. The assertion seems fine to me.

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

All LGTM, didn't carefully check every hunk, though.

Comment threadlightning/src/ln/channel.rs Outdated
Outbound,
}

/// Returns the fees for success and timeout second stage HTLC transactions.

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.

nit: Probably belongs in chan_utils.rs given it kinda mirrors commit_and_htlc_tx_fees_sat and is also called from there?

) -> u64 {
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {
debug_assert_eq!(self.feerate_per_kw, 0);
debug_assert!(fee_spike_buffer_htlc.is_none());

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.

Can't say I have a strong preference. The assertion seems fine to me.

@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 001c8e9 to 4157d24CompareJuly 10, 2025 13:16
@carlaKC
carlaKC marked this pull request as ready for review July 10, 2025 13:32
@carlaKC
carlaKC removed the request for review from joostjagerJuly 10, 2025 13:32
Comment threadlightning/src/ln/channel.rs Outdated
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 4157d24 to 5fe6fa7CompareJuly 10, 2025 13:40
Comment threadlightning/src/ln/update_fee_tests.rs Outdated
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @tankyleo! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/chan_utils.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/update_fee_tests.rs
Comment threadlightning/src/ln/channel.rs Outdated

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

Still grokking some of the changes here and the zero-fee commitment proposal but from my kind of limited context it LGTM (:

Comment threadlightning/src/ln/chan_utils.rs Outdated
Comment threadlightning/src/ln/chan_utils.rs Outdated
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs Outdated
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 5fe6fa7 to f76a270CompareJuly 15, 2025 19:33
@carlaKC

Copy link
Copy Markdown
ContributorAuthor

Addressed review: only major change is moving responsibility for not triggering update_fee on zero fee channels to channelmanger (as there's been no change between new/old fee) and changing channel to just assert that zero fee channels don't hit update code.

@tankyleotankyleo 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 mod two nits, feel free to squash / ping second reviewer :)

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
carlaKCand others added 7 commits July 16, 2025 14:05
This fee rate is currently used in two scenarios:
- To count any fees above what we consider to be a sane estimate towards
our dust exposure.
- To get a maximum dust exposure (when using
MaxDustHTLCExposure::FeeEstimator strategy).
When we have zero fee commitments:
- Commitments are zero fee, so we don't need to count fees towards dust
exposure.
- The amount of dust we have is not dependent on fees, as everything is
zero fee.
- We still want to limit our total dust exposure.
This commit updates get_dust_exposure_limiting_feerate to allow a None
value to prepare for support for zero fee commitments. This clearly
allows us to indicate when we don't care about fee rates for dust
considerations.
In get_max_dust_htlc_exposure_msat, we simply hardcode a value of
1 sat/vbyte if a feerate dependent strategy is being used.
Co-authored-by: Matt Corallo <git@bluematt.me>
LDK does not support a channel type that supports both zero fee and
nonzero fee anchors (as this is impossible), so we can drop the
unnecessary check in build_htlc_output.
This commit pulls calculation of second stage fees into a helper
function. A side effect of this refactor is that it fixes a rounding
issue in commit_and_htlc_tx_fees_sat. Previously, rounding down of
fees would happen after multiplying by the number of HTLCs. Now the
fees will be rounded down before multiplying by the number of HTLCs.
This wasn't a serious issue - it would just cause us very slightly
over estimate our dust exposure at certain fee amounts that needed
rounding. A hard-coded value in test_nondust_htlc_excess_fees_are_dust
is updated to account for this rounding change.
Update second_stage_tx_fees_sat to return zero for zero fee commitments.
As is the case for anchors_zero_fee_commitments, this changes ensures
that we won't trim the second stage transaction fee off of the HTLC
amount.
When we have zero fee commitments, we don't need to calculate our fee
rate or check that it isn't stale because it is always zero.
Co-authored-by: Matt Corallo <git@bluematt.me>
Co-authored-by: Matt Corallo <git@bluematt.me>
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch 2 times, most recently from b421d59 to 60064d1CompareJuly 16, 2025 18:13
@carlaKC

carlaKC commented Jul 16, 2025

Copy link
Copy Markdown
ContributorAuthor

Squashed + addressed last 2x comments + rebased.

Edit: plus small silent rebase failure, new test on master needed an import this branch deleted.

Update test helper in preparation to test zero fee commitments, where
the local balance will differ from the previously hardcoded value.
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 60064d1 to 54559b3CompareJuly 16, 2025 18:35
};
let real_dust_limit_success_sat = htlc_success_dust_limit + context.holder_dust_limit_satoshis;
let real_dust_limit_timeout_sat = htlc_timeout_dust_limit + context.holder_dust_limit_satoshis;
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {

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.

non-blocking - should the comment on this method be updated? the htlc is not an Option

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

One nit and one thing maybe worth following up on, but not worth holding up on.

}

/// Returns a fee estimate for the commitment transaction depending on channel type.
pub(super) fn commitment_sat_per_1000_weight_for_type<F: Deref>(

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.

Not a huge fan of the name because it hides which fee types we're using - we're using the "feerates that we'd want to assign to a channel" types, which are different from the "minimum/maximum we'd let our peer assign to a channel", but the name doesn't capture that.

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.

Can you think of anything less atrociously long than holder_preferred_commitment_sat_per_1000_weight_for_type?

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.

selected_commitment_sat_per_1000_weight?

} else {
non_anchor_feerate
};
let new_feerate = commitment_sat_per_1000_weight_for_type(&self.fee_estimator, funded_chan.funding.get_channel_type());

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.

Hmm, would be nice to keep the old behavior where we fetch the feerates outside the loop. In general implementations of the FeeEstimator trait should be pretty effecient, but if we can reduce the number of calls we probably should. Same above in maybe_update_chan_fees.

@TheBlueMatt
TheBlueMatt merged commit eae2bb1 into lightningdevkit:mainJul 17, 2025
@github-project-automationgithub-project-automationBot moved this from Goal: Open to Done in Weekly GoalsJul 17, 2025
@carlaKCcarlaKC mentioned this pull request Jul 17, 2025
TheBlueMatt added a commit that referenced this pull request Jul 18, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

5 participants

@carlaKC@ldk-reviews-bot@TheBlueMatt@elnosh@tankyleo
, '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

Update fee and dust handling for zero fee channels - #3884

Merged
TheBlueMatt merged 10 commits into
lightningdevkit:mainfrom
carlaKC:3789-fees-and-dust
Jul 17, 2025
Merged

Update fee and dust handling for zero fee channels#3884
TheBlueMatt merged 10 commits into
lightningdevkit:mainfrom
carlaKC:3789-fees-and-dust

Conversation

@carlaKC

Copy link
Copy Markdown
Contributor

This PR completes the off-chain handling of V3 channels, as described in #3789.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 23, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@carlaKC

Copy link
Copy Markdown
ContributorAuthor

This still needs a few tests, opening up early for conceptual review.

) -> u64 {
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {
debug_assert_eq!(self.feerate_per_kw, 0);
debug_assert!(fee_spike_buffer_htlc.is_none());

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.

Wondering whether it's worth having this assertion (which makes it necessary to check channel type before calling fee functions). The alternative would be to just ignore the parameters completely for zero fee channels (even though Somefee_spike_buffer_htlc doesn't make sense for the type).

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.

Can't say I have a strong preference. The assertion seems fine to me.

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

All LGTM, didn't carefully check every hunk, though.

Comment threadlightning/src/ln/channel.rs Outdated
Outbound,
}

/// Returns the fees for success and timeout second stage HTLC transactions.

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.

nit: Probably belongs in chan_utils.rs given it kinda mirrors commit_and_htlc_tx_fees_sat and is also called from there?

) -> u64 {
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {
debug_assert_eq!(self.feerate_per_kw, 0);
debug_assert!(fee_spike_buffer_htlc.is_none());

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.

Can't say I have a strong preference. The assertion seems fine to me.

@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 001c8e9 to 4157d24CompareJuly 10, 2025 13:16
@carlaKC
carlaKC marked this pull request as ready for review July 10, 2025 13:32
@carlaKC
carlaKC removed the request for review from joostjagerJuly 10, 2025 13:32
Comment threadlightning/src/ln/channel.rs Outdated
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 4157d24 to 5fe6fa7CompareJuly 10, 2025 13:40
Comment threadlightning/src/ln/update_fee_tests.rs Outdated
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @tankyleo! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/chan_utils.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/update_fee_tests.rs
Comment threadlightning/src/ln/channel.rs Outdated

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

Still grokking some of the changes here and the zero-fee commitment proposal but from my kind of limited context it LGTM (:

Comment threadlightning/src/ln/chan_utils.rs Outdated
Comment threadlightning/src/ln/chan_utils.rs Outdated
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs Outdated
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 5fe6fa7 to f76a270CompareJuly 15, 2025 19:33
@carlaKC

Copy link
Copy Markdown
ContributorAuthor

Addressed review: only major change is moving responsibility for not triggering update_fee on zero fee channels to channelmanger (as there's been no change between new/old fee) and changing channel to just assert that zero fee channels don't hit update code.

@tankyleotankyleo 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 mod two nits, feel free to squash / ping second reviewer :)

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
carlaKCand others added 7 commits July 16, 2025 14:05
This fee rate is currently used in two scenarios:
- To count any fees above what we consider to be a sane estimate towards
our dust exposure.
- To get a maximum dust exposure (when using
MaxDustHTLCExposure::FeeEstimator strategy).
When we have zero fee commitments:
- Commitments are zero fee, so we don't need to count fees towards dust
exposure.
- The amount of dust we have is not dependent on fees, as everything is
zero fee.
- We still want to limit our total dust exposure.
This commit updates get_dust_exposure_limiting_feerate to allow a None
value to prepare for support for zero fee commitments. This clearly
allows us to indicate when we don't care about fee rates for dust
considerations.
In get_max_dust_htlc_exposure_msat, we simply hardcode a value of
1 sat/vbyte if a feerate dependent strategy is being used.
Co-authored-by: Matt Corallo <git@bluematt.me>
LDK does not support a channel type that supports both zero fee and
nonzero fee anchors (as this is impossible), so we can drop the
unnecessary check in build_htlc_output.
This commit pulls calculation of second stage fees into a helper
function. A side effect of this refactor is that it fixes a rounding
issue in commit_and_htlc_tx_fees_sat. Previously, rounding down of
fees would happen after multiplying by the number of HTLCs. Now the
fees will be rounded down before multiplying by the number of HTLCs.
This wasn't a serious issue - it would just cause us very slightly
over estimate our dust exposure at certain fee amounts that needed
rounding. A hard-coded value in test_nondust_htlc_excess_fees_are_dust
is updated to account for this rounding change.
Update second_stage_tx_fees_sat to return zero for zero fee commitments.
As is the case for anchors_zero_fee_commitments, this changes ensures
that we won't trim the second stage transaction fee off of the HTLC
amount.
When we have zero fee commitments, we don't need to calculate our fee
rate or check that it isn't stale because it is always zero.
Co-authored-by: Matt Corallo <git@bluematt.me>
Co-authored-by: Matt Corallo <git@bluematt.me>
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch 2 times, most recently from b421d59 to 60064d1CompareJuly 16, 2025 18:13
@carlaKC

carlaKC commented Jul 16, 2025

Copy link
Copy Markdown
ContributorAuthor

Squashed + addressed last 2x comments + rebased.

Edit: plus small silent rebase failure, new test on master needed an import this branch deleted.

Update test helper in preparation to test zero fee commitments, where
the local balance will differ from the previously hardcoded value.
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 60064d1 to 54559b3CompareJuly 16, 2025 18:35
};
let real_dust_limit_success_sat = htlc_success_dust_limit + context.holder_dust_limit_satoshis;
let real_dust_limit_timeout_sat = htlc_timeout_dust_limit + context.holder_dust_limit_satoshis;
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {

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.

non-blocking - should the comment on this method be updated? the htlc is not an Option

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

One nit and one thing maybe worth following up on, but not worth holding up on.

}

/// Returns a fee estimate for the commitment transaction depending on channel type.
pub(super) fn commitment_sat_per_1000_weight_for_type<F: Deref>(

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.

Not a huge fan of the name because it hides which fee types we're using - we're using the "feerates that we'd want to assign to a channel" types, which are different from the "minimum/maximum we'd let our peer assign to a channel", but the name doesn't capture that.

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.

Can you think of anything less atrociously long than holder_preferred_commitment_sat_per_1000_weight_for_type?

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.

selected_commitment_sat_per_1000_weight?

} else {
non_anchor_feerate
};
let new_feerate = commitment_sat_per_1000_weight_for_type(&self.fee_estimator, funded_chan.funding.get_channel_type());

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.

Hmm, would be nice to keep the old behavior where we fetch the feerates outside the loop. In general implementations of the FeeEstimator trait should be pretty effecient, but if we can reduce the number of calls we probably should. Same above in maybe_update_chan_fees.

@TheBlueMatt
TheBlueMatt merged commit eae2bb1 into lightningdevkit:mainJul 17, 2025
@github-project-automationgithub-project-automationBot moved this from Goal: Open to Done in Weekly GoalsJul 17, 2025
@carlaKCcarlaKC mentioned this pull request Jul 17, 2025
TheBlueMatt added a commit that referenced this pull request Jul 18, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

5 participants

@carlaKC@ldk-reviews-bot@TheBlueMatt@elnosh@tankyleo
, '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

Update fee and dust handling for zero fee channels - #3884

Merged
TheBlueMatt merged 10 commits into
lightningdevkit:mainfrom
carlaKC:3789-fees-and-dust
Jul 17, 2025
Merged

Update fee and dust handling for zero fee channels#3884
TheBlueMatt merged 10 commits into
lightningdevkit:mainfrom
carlaKC:3789-fees-and-dust

Conversation

@carlaKC

Copy link
Copy Markdown
Contributor

This PR completes the off-chain handling of V3 channels, as described in #3789.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 23, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@carlaKC

Copy link
Copy Markdown
ContributorAuthor

This still needs a few tests, opening up early for conceptual review.

) -> u64 {
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {
debug_assert_eq!(self.feerate_per_kw, 0);
debug_assert!(fee_spike_buffer_htlc.is_none());

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.

Wondering whether it's worth having this assertion (which makes it necessary to check channel type before calling fee functions). The alternative would be to just ignore the parameters completely for zero fee channels (even though Somefee_spike_buffer_htlc doesn't make sense for the type).

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.

Can't say I have a strong preference. The assertion seems fine to me.

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

All LGTM, didn't carefully check every hunk, though.

Comment threadlightning/src/ln/channel.rs Outdated
Outbound,
}

/// Returns the fees for success and timeout second stage HTLC transactions.

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.

nit: Probably belongs in chan_utils.rs given it kinda mirrors commit_and_htlc_tx_fees_sat and is also called from there?

) -> u64 {
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {
debug_assert_eq!(self.feerate_per_kw, 0);
debug_assert!(fee_spike_buffer_htlc.is_none());

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.

Can't say I have a strong preference. The assertion seems fine to me.

@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 001c8e9 to 4157d24CompareJuly 10, 2025 13:16
@carlaKC
carlaKC marked this pull request as ready for review July 10, 2025 13:32
@carlaKC
carlaKC removed the request for review from joostjagerJuly 10, 2025 13:32
Comment threadlightning/src/ln/channel.rs Outdated
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 4157d24 to 5fe6fa7CompareJuly 10, 2025 13:40
Comment threadlightning/src/ln/update_fee_tests.rs Outdated
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @tankyleo! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/chan_utils.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/update_fee_tests.rs
Comment threadlightning/src/ln/channel.rs Outdated

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

Still grokking some of the changes here and the zero-fee commitment proposal but from my kind of limited context it LGTM (:

Comment threadlightning/src/ln/chan_utils.rs Outdated
Comment threadlightning/src/ln/chan_utils.rs Outdated
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs Outdated
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 5fe6fa7 to f76a270CompareJuly 15, 2025 19:33
@carlaKC

Copy link
Copy Markdown
ContributorAuthor

Addressed review: only major change is moving responsibility for not triggering update_fee on zero fee channels to channelmanger (as there's been no change between new/old fee) and changing channel to just assert that zero fee channels don't hit update code.

@tankyleotankyleo 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 mod two nits, feel free to squash / ping second reviewer :)

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
carlaKCand others added 7 commits July 16, 2025 14:05
This fee rate is currently used in two scenarios:
- To count any fees above what we consider to be a sane estimate towards
our dust exposure.
- To get a maximum dust exposure (when using
MaxDustHTLCExposure::FeeEstimator strategy).
When we have zero fee commitments:
- Commitments are zero fee, so we don't need to count fees towards dust
exposure.
- The amount of dust we have is not dependent on fees, as everything is
zero fee.
- We still want to limit our total dust exposure.
This commit updates get_dust_exposure_limiting_feerate to allow a None
value to prepare for support for zero fee commitments. This clearly
allows us to indicate when we don't care about fee rates for dust
considerations.
In get_max_dust_htlc_exposure_msat, we simply hardcode a value of
1 sat/vbyte if a feerate dependent strategy is being used.
Co-authored-by: Matt Corallo <git@bluematt.me>
LDK does not support a channel type that supports both zero fee and
nonzero fee anchors (as this is impossible), so we can drop the
unnecessary check in build_htlc_output.
This commit pulls calculation of second stage fees into a helper
function. A side effect of this refactor is that it fixes a rounding
issue in commit_and_htlc_tx_fees_sat. Previously, rounding down of
fees would happen after multiplying by the number of HTLCs. Now the
fees will be rounded down before multiplying by the number of HTLCs.
This wasn't a serious issue - it would just cause us very slightly
over estimate our dust exposure at certain fee amounts that needed
rounding. A hard-coded value in test_nondust_htlc_excess_fees_are_dust
is updated to account for this rounding change.
Update second_stage_tx_fees_sat to return zero for zero fee commitments.
As is the case for anchors_zero_fee_commitments, this changes ensures
that we won't trim the second stage transaction fee off of the HTLC
amount.
When we have zero fee commitments, we don't need to calculate our fee
rate or check that it isn't stale because it is always zero.
Co-authored-by: Matt Corallo <git@bluematt.me>
Co-authored-by: Matt Corallo <git@bluematt.me>
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch 2 times, most recently from b421d59 to 60064d1CompareJuly 16, 2025 18:13
@carlaKC

carlaKC commented Jul 16, 2025

Copy link
Copy Markdown
ContributorAuthor

Squashed + addressed last 2x comments + rebased.

Edit: plus small silent rebase failure, new test on master needed an import this branch deleted.

Update test helper in preparation to test zero fee commitments, where
the local balance will differ from the previously hardcoded value.
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 60064d1 to 54559b3CompareJuly 16, 2025 18:35
};
let real_dust_limit_success_sat = htlc_success_dust_limit + context.holder_dust_limit_satoshis;
let real_dust_limit_timeout_sat = htlc_timeout_dust_limit + context.holder_dust_limit_satoshis;
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {

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.

non-blocking - should the comment on this method be updated? the htlc is not an Option

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

One nit and one thing maybe worth following up on, but not worth holding up on.

}

/// Returns a fee estimate for the commitment transaction depending on channel type.
pub(super) fn commitment_sat_per_1000_weight_for_type<F: Deref>(

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.

Not a huge fan of the name because it hides which fee types we're using - we're using the "feerates that we'd want to assign to a channel" types, which are different from the "minimum/maximum we'd let our peer assign to a channel", but the name doesn't capture that.

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.

Can you think of anything less atrociously long than holder_preferred_commitment_sat_per_1000_weight_for_type?

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.

selected_commitment_sat_per_1000_weight?

} else {
non_anchor_feerate
};
let new_feerate = commitment_sat_per_1000_weight_for_type(&self.fee_estimator, funded_chan.funding.get_channel_type());

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.

Hmm, would be nice to keep the old behavior where we fetch the feerates outside the loop. In general implementations of the FeeEstimator trait should be pretty effecient, but if we can reduce the number of calls we probably should. Same above in maybe_update_chan_fees.

@TheBlueMatt
TheBlueMatt merged commit eae2bb1 into lightningdevkit:mainJul 17, 2025
@github-project-automationgithub-project-automationBot moved this from Goal: Open to Done in Weekly GoalsJul 17, 2025
@carlaKCcarlaKC mentioned this pull request Jul 17, 2025
TheBlueMatt added a commit that referenced this pull request Jul 18, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

5 participants

@carlaKC@ldk-reviews-bot@TheBlueMatt@elnosh@tankyleo
, '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

Update fee and dust handling for zero fee channels - #3884

Merged
TheBlueMatt merged 10 commits into
lightningdevkit:mainfrom
carlaKC:3789-fees-and-dust
Jul 17, 2025
Merged

Update fee and dust handling for zero fee channels#3884
TheBlueMatt merged 10 commits into
lightningdevkit:mainfrom
carlaKC:3789-fees-and-dust

Conversation

@carlaKC

Copy link
Copy Markdown
Contributor

This PR completes the off-chain handling of V3 channels, as described in #3789.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 23, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@carlaKC

Copy link
Copy Markdown
ContributorAuthor

This still needs a few tests, opening up early for conceptual review.

) -> u64 {
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {
debug_assert_eq!(self.feerate_per_kw, 0);
debug_assert!(fee_spike_buffer_htlc.is_none());

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.

Wondering whether it's worth having this assertion (which makes it necessary to check channel type before calling fee functions). The alternative would be to just ignore the parameters completely for zero fee channels (even though Somefee_spike_buffer_htlc doesn't make sense for the type).

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.

Can't say I have a strong preference. The assertion seems fine to me.

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

All LGTM, didn't carefully check every hunk, though.

Comment threadlightning/src/ln/channel.rs Outdated
Outbound,
}

/// Returns the fees for success and timeout second stage HTLC transactions.

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.

nit: Probably belongs in chan_utils.rs given it kinda mirrors commit_and_htlc_tx_fees_sat and is also called from there?

) -> u64 {
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {
debug_assert_eq!(self.feerate_per_kw, 0);
debug_assert!(fee_spike_buffer_htlc.is_none());

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.

Can't say I have a strong preference. The assertion seems fine to me.

@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 001c8e9 to 4157d24CompareJuly 10, 2025 13:16
@carlaKC
carlaKC marked this pull request as ready for review July 10, 2025 13:32
@carlaKC
carlaKC removed the request for review from joostjagerJuly 10, 2025 13:32
Comment threadlightning/src/ln/channel.rs Outdated
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 4157d24 to 5fe6fa7CompareJuly 10, 2025 13:40
Comment threadlightning/src/ln/update_fee_tests.rs Outdated
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @tankyleo! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/chan_utils.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/update_fee_tests.rs
Comment threadlightning/src/ln/channel.rs Outdated

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

Still grokking some of the changes here and the zero-fee commitment proposal but from my kind of limited context it LGTM (:

Comment threadlightning/src/ln/chan_utils.rs Outdated
Comment threadlightning/src/ln/chan_utils.rs Outdated
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs Outdated
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 5fe6fa7 to f76a270CompareJuly 15, 2025 19:33
@carlaKC

Copy link
Copy Markdown
ContributorAuthor

Addressed review: only major change is moving responsibility for not triggering update_fee on zero fee channels to channelmanger (as there's been no change between new/old fee) and changing channel to just assert that zero fee channels don't hit update code.

@tankyleotankyleo 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 mod two nits, feel free to squash / ping second reviewer :)

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
carlaKCand others added 7 commits July 16, 2025 14:05
This fee rate is currently used in two scenarios:
- To count any fees above what we consider to be a sane estimate towards
our dust exposure.
- To get a maximum dust exposure (when using
MaxDustHTLCExposure::FeeEstimator strategy).
When we have zero fee commitments:
- Commitments are zero fee, so we don't need to count fees towards dust
exposure.
- The amount of dust we have is not dependent on fees, as everything is
zero fee.
- We still want to limit our total dust exposure.
This commit updates get_dust_exposure_limiting_feerate to allow a None
value to prepare for support for zero fee commitments. This clearly
allows us to indicate when we don't care about fee rates for dust
considerations.
In get_max_dust_htlc_exposure_msat, we simply hardcode a value of
1 sat/vbyte if a feerate dependent strategy is being used.
Co-authored-by: Matt Corallo <git@bluematt.me>
LDK does not support a channel type that supports both zero fee and
nonzero fee anchors (as this is impossible), so we can drop the
unnecessary check in build_htlc_output.
This commit pulls calculation of second stage fees into a helper
function. A side effect of this refactor is that it fixes a rounding
issue in commit_and_htlc_tx_fees_sat. Previously, rounding down of
fees would happen after multiplying by the number of HTLCs. Now the
fees will be rounded down before multiplying by the number of HTLCs.
This wasn't a serious issue - it would just cause us very slightly
over estimate our dust exposure at certain fee amounts that needed
rounding. A hard-coded value in test_nondust_htlc_excess_fees_are_dust
is updated to account for this rounding change.
Update second_stage_tx_fees_sat to return zero for zero fee commitments.
As is the case for anchors_zero_fee_commitments, this changes ensures
that we won't trim the second stage transaction fee off of the HTLC
amount.
When we have zero fee commitments, we don't need to calculate our fee
rate or check that it isn't stale because it is always zero.
Co-authored-by: Matt Corallo <git@bluematt.me>
Co-authored-by: Matt Corallo <git@bluematt.me>
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch 2 times, most recently from b421d59 to 60064d1CompareJuly 16, 2025 18:13
@carlaKC

carlaKC commented Jul 16, 2025

Copy link
Copy Markdown
ContributorAuthor

Squashed + addressed last 2x comments + rebased.

Edit: plus small silent rebase failure, new test on master needed an import this branch deleted.

Update test helper in preparation to test zero fee commitments, where
the local balance will differ from the previously hardcoded value.
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 60064d1 to 54559b3CompareJuly 16, 2025 18:35
};
let real_dust_limit_success_sat = htlc_success_dust_limit + context.holder_dust_limit_satoshis;
let real_dust_limit_timeout_sat = htlc_timeout_dust_limit + context.holder_dust_limit_satoshis;
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {

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.

non-blocking - should the comment on this method be updated? the htlc is not an Option

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

One nit and one thing maybe worth following up on, but not worth holding up on.

}

/// Returns a fee estimate for the commitment transaction depending on channel type.
pub(super) fn commitment_sat_per_1000_weight_for_type<F: Deref>(

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.

Not a huge fan of the name because it hides which fee types we're using - we're using the "feerates that we'd want to assign to a channel" types, which are different from the "minimum/maximum we'd let our peer assign to a channel", but the name doesn't capture that.

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.

Can you think of anything less atrociously long than holder_preferred_commitment_sat_per_1000_weight_for_type?

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.

selected_commitment_sat_per_1000_weight?

} else {
non_anchor_feerate
};
let new_feerate = commitment_sat_per_1000_weight_for_type(&self.fee_estimator, funded_chan.funding.get_channel_type());

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.

Hmm, would be nice to keep the old behavior where we fetch the feerates outside the loop. In general implementations of the FeeEstimator trait should be pretty effecient, but if we can reduce the number of calls we probably should. Same above in maybe_update_chan_fees.

@TheBlueMatt
TheBlueMatt merged commit eae2bb1 into lightningdevkit:mainJul 17, 2025
@github-project-automationgithub-project-automationBot moved this from Goal: Open to Done in Weekly GoalsJul 17, 2025
@carlaKCcarlaKC mentioned this pull request Jul 17, 2025
TheBlueMatt added a commit that referenced this pull request Jul 18, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

5 participants

@carlaKC@ldk-reviews-bot@TheBlueMatt@elnosh@tankyleo
, '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

Update fee and dust handling for zero fee channels - #3884

Merged
TheBlueMatt merged 10 commits into
lightningdevkit:mainfrom
carlaKC:3789-fees-and-dust
Jul 17, 2025
Merged

Update fee and dust handling for zero fee channels#3884
TheBlueMatt merged 10 commits into
lightningdevkit:mainfrom
carlaKC:3789-fees-and-dust

Conversation

@carlaKC

Copy link
Copy Markdown
Contributor

This PR completes the off-chain handling of V3 channels, as described in #3789.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 23, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@carlaKC

Copy link
Copy Markdown
ContributorAuthor

This still needs a few tests, opening up early for conceptual review.

) -> u64 {
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {
debug_assert_eq!(self.feerate_per_kw, 0);
debug_assert!(fee_spike_buffer_htlc.is_none());

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.

Wondering whether it's worth having this assertion (which makes it necessary to check channel type before calling fee functions). The alternative would be to just ignore the parameters completely for zero fee channels (even though Somefee_spike_buffer_htlc doesn't make sense for the type).

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.

Can't say I have a strong preference. The assertion seems fine to me.

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

All LGTM, didn't carefully check every hunk, though.

Comment threadlightning/src/ln/channel.rs Outdated
Outbound,
}

/// Returns the fees for success and timeout second stage HTLC transactions.

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.

nit: Probably belongs in chan_utils.rs given it kinda mirrors commit_and_htlc_tx_fees_sat and is also called from there?

) -> u64 {
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {
debug_assert_eq!(self.feerate_per_kw, 0);
debug_assert!(fee_spike_buffer_htlc.is_none());

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.

Can't say I have a strong preference. The assertion seems fine to me.

@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 001c8e9 to 4157d24CompareJuly 10, 2025 13:16
@carlaKC
carlaKC marked this pull request as ready for review July 10, 2025 13:32
@carlaKC
carlaKC removed the request for review from joostjagerJuly 10, 2025 13:32
Comment threadlightning/src/ln/channel.rs Outdated
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 4157d24 to 5fe6fa7CompareJuly 10, 2025 13:40
Comment threadlightning/src/ln/update_fee_tests.rs Outdated
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @tankyleo! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/chan_utils.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/update_fee_tests.rs
Comment threadlightning/src/ln/channel.rs Outdated

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

Still grokking some of the changes here and the zero-fee commitment proposal but from my kind of limited context it LGTM (:

Comment threadlightning/src/ln/chan_utils.rs Outdated
Comment threadlightning/src/ln/chan_utils.rs Outdated
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs Outdated
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 5fe6fa7 to f76a270CompareJuly 15, 2025 19:33
@carlaKC

Copy link
Copy Markdown
ContributorAuthor

Addressed review: only major change is moving responsibility for not triggering update_fee on zero fee channels to channelmanger (as there's been no change between new/old fee) and changing channel to just assert that zero fee channels don't hit update code.

@tankyleotankyleo 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 mod two nits, feel free to squash / ping second reviewer :)

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
carlaKCand others added 7 commits July 16, 2025 14:05
This fee rate is currently used in two scenarios:
- To count any fees above what we consider to be a sane estimate towards
our dust exposure.
- To get a maximum dust exposure (when using
MaxDustHTLCExposure::FeeEstimator strategy).
When we have zero fee commitments:
- Commitments are zero fee, so we don't need to count fees towards dust
exposure.
- The amount of dust we have is not dependent on fees, as everything is
zero fee.
- We still want to limit our total dust exposure.
This commit updates get_dust_exposure_limiting_feerate to allow a None
value to prepare for support for zero fee commitments. This clearly
allows us to indicate when we don't care about fee rates for dust
considerations.
In get_max_dust_htlc_exposure_msat, we simply hardcode a value of
1 sat/vbyte if a feerate dependent strategy is being used.
Co-authored-by: Matt Corallo <git@bluematt.me>
LDK does not support a channel type that supports both zero fee and
nonzero fee anchors (as this is impossible), so we can drop the
unnecessary check in build_htlc_output.
This commit pulls calculation of second stage fees into a helper
function. A side effect of this refactor is that it fixes a rounding
issue in commit_and_htlc_tx_fees_sat. Previously, rounding down of
fees would happen after multiplying by the number of HTLCs. Now the
fees will be rounded down before multiplying by the number of HTLCs.
This wasn't a serious issue - it would just cause us very slightly
over estimate our dust exposure at certain fee amounts that needed
rounding. A hard-coded value in test_nondust_htlc_excess_fees_are_dust
is updated to account for this rounding change.
Update second_stage_tx_fees_sat to return zero for zero fee commitments.
As is the case for anchors_zero_fee_commitments, this changes ensures
that we won't trim the second stage transaction fee off of the HTLC
amount.
When we have zero fee commitments, we don't need to calculate our fee
rate or check that it isn't stale because it is always zero.
Co-authored-by: Matt Corallo <git@bluematt.me>
Co-authored-by: Matt Corallo <git@bluematt.me>
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch 2 times, most recently from b421d59 to 60064d1CompareJuly 16, 2025 18:13
@carlaKC

carlaKC commented Jul 16, 2025

Copy link
Copy Markdown
ContributorAuthor

Squashed + addressed last 2x comments + rebased.

Edit: plus small silent rebase failure, new test on master needed an import this branch deleted.

Update test helper in preparation to test zero fee commitments, where
the local balance will differ from the previously hardcoded value.
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 60064d1 to 54559b3CompareJuly 16, 2025 18:35
};
let real_dust_limit_success_sat = htlc_success_dust_limit + context.holder_dust_limit_satoshis;
let real_dust_limit_timeout_sat = htlc_timeout_dust_limit + context.holder_dust_limit_satoshis;
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {

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.

non-blocking - should the comment on this method be updated? the htlc is not an Option

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

One nit and one thing maybe worth following up on, but not worth holding up on.

}

/// Returns a fee estimate for the commitment transaction depending on channel type.
pub(super) fn commitment_sat_per_1000_weight_for_type<F: Deref>(

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.

Not a huge fan of the name because it hides which fee types we're using - we're using the "feerates that we'd want to assign to a channel" types, which are different from the "minimum/maximum we'd let our peer assign to a channel", but the name doesn't capture that.

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.

Can you think of anything less atrociously long than holder_preferred_commitment_sat_per_1000_weight_for_type?

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.

selected_commitment_sat_per_1000_weight?

} else {
non_anchor_feerate
};
let new_feerate = commitment_sat_per_1000_weight_for_type(&self.fee_estimator, funded_chan.funding.get_channel_type());

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.

Hmm, would be nice to keep the old behavior where we fetch the feerates outside the loop. In general implementations of the FeeEstimator trait should be pretty effecient, but if we can reduce the number of calls we probably should. Same above in maybe_update_chan_fees.

@TheBlueMatt
TheBlueMatt merged commit eae2bb1 into lightningdevkit:mainJul 17, 2025
@github-project-automationgithub-project-automationBot moved this from Goal: Open to Done in Weekly GoalsJul 17, 2025
@carlaKCcarlaKC mentioned this pull request Jul 17, 2025
TheBlueMatt added a commit that referenced this pull request Jul 18, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

5 participants

@carlaKC@ldk-reviews-bot@TheBlueMatt@elnosh@tankyleo
, '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

Update fee and dust handling for zero fee channels - #3884

Merged
TheBlueMatt merged 10 commits into
lightningdevkit:mainfrom
carlaKC:3789-fees-and-dust
Jul 17, 2025
Merged

Update fee and dust handling for zero fee channels#3884
TheBlueMatt merged 10 commits into
lightningdevkit:mainfrom
carlaKC:3789-fees-and-dust

Conversation

@carlaKC

Copy link
Copy Markdown
Contributor

This PR completes the off-chain handling of V3 channels, as described in #3789.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 23, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@carlaKC

Copy link
Copy Markdown
ContributorAuthor

This still needs a few tests, opening up early for conceptual review.

) -> u64 {
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {
debug_assert_eq!(self.feerate_per_kw, 0);
debug_assert!(fee_spike_buffer_htlc.is_none());

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.

Wondering whether it's worth having this assertion (which makes it necessary to check channel type before calling fee functions). The alternative would be to just ignore the parameters completely for zero fee channels (even though Somefee_spike_buffer_htlc doesn't make sense for the type).

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.

Can't say I have a strong preference. The assertion seems fine to me.

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

All LGTM, didn't carefully check every hunk, though.

Comment threadlightning/src/ln/channel.rs Outdated
Outbound,
}

/// Returns the fees for success and timeout second stage HTLC transactions.

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.

nit: Probably belongs in chan_utils.rs given it kinda mirrors commit_and_htlc_tx_fees_sat and is also called from there?

) -> u64 {
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {
debug_assert_eq!(self.feerate_per_kw, 0);
debug_assert!(fee_spike_buffer_htlc.is_none());

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.

Can't say I have a strong preference. The assertion seems fine to me.

@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 001c8e9 to 4157d24CompareJuly 10, 2025 13:16
@carlaKC
carlaKC marked this pull request as ready for review July 10, 2025 13:32
@carlaKC
carlaKC removed the request for review from joostjagerJuly 10, 2025 13:32
Comment threadlightning/src/ln/channel.rs Outdated
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 4157d24 to 5fe6fa7CompareJuly 10, 2025 13:40
Comment threadlightning/src/ln/update_fee_tests.rs Outdated
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @tankyleo! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/chan_utils.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/update_fee_tests.rs
Comment threadlightning/src/ln/channel.rs Outdated

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

Still grokking some of the changes here and the zero-fee commitment proposal but from my kind of limited context it LGTM (:

Comment threadlightning/src/ln/chan_utils.rs Outdated
Comment threadlightning/src/ln/chan_utils.rs Outdated
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs Outdated
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 5fe6fa7 to f76a270CompareJuly 15, 2025 19:33
@carlaKC

Copy link
Copy Markdown
ContributorAuthor

Addressed review: only major change is moving responsibility for not triggering update_fee on zero fee channels to channelmanger (as there's been no change between new/old fee) and changing channel to just assert that zero fee channels don't hit update code.

@tankyleotankyleo 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 mod two nits, feel free to squash / ping second reviewer :)

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
carlaKCand others added 7 commits July 16, 2025 14:05
This fee rate is currently used in two scenarios:
- To count any fees above what we consider to be a sane estimate towards
our dust exposure.
- To get a maximum dust exposure (when using
MaxDustHTLCExposure::FeeEstimator strategy).
When we have zero fee commitments:
- Commitments are zero fee, so we don't need to count fees towards dust
exposure.
- The amount of dust we have is not dependent on fees, as everything is
zero fee.
- We still want to limit our total dust exposure.
This commit updates get_dust_exposure_limiting_feerate to allow a None
value to prepare for support for zero fee commitments. This clearly
allows us to indicate when we don't care about fee rates for dust
considerations.
In get_max_dust_htlc_exposure_msat, we simply hardcode a value of
1 sat/vbyte if a feerate dependent strategy is being used.
Co-authored-by: Matt Corallo <git@bluematt.me>
LDK does not support a channel type that supports both zero fee and
nonzero fee anchors (as this is impossible), so we can drop the
unnecessary check in build_htlc_output.
This commit pulls calculation of second stage fees into a helper
function. A side effect of this refactor is that it fixes a rounding
issue in commit_and_htlc_tx_fees_sat. Previously, rounding down of
fees would happen after multiplying by the number of HTLCs. Now the
fees will be rounded down before multiplying by the number of HTLCs.
This wasn't a serious issue - it would just cause us very slightly
over estimate our dust exposure at certain fee amounts that needed
rounding. A hard-coded value in test_nondust_htlc_excess_fees_are_dust
is updated to account for this rounding change.
Update second_stage_tx_fees_sat to return zero for zero fee commitments.
As is the case for anchors_zero_fee_commitments, this changes ensures
that we won't trim the second stage transaction fee off of the HTLC
amount.
When we have zero fee commitments, we don't need to calculate our fee
rate or check that it isn't stale because it is always zero.
Co-authored-by: Matt Corallo <git@bluematt.me>
Co-authored-by: Matt Corallo <git@bluematt.me>
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch 2 times, most recently from b421d59 to 60064d1CompareJuly 16, 2025 18:13
@carlaKC

carlaKC commented Jul 16, 2025

Copy link
Copy Markdown
ContributorAuthor

Squashed + addressed last 2x comments + rebased.

Edit: plus small silent rebase failure, new test on master needed an import this branch deleted.

Update test helper in preparation to test zero fee commitments, where
the local balance will differ from the previously hardcoded value.
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 60064d1 to 54559b3CompareJuly 16, 2025 18:35
};
let real_dust_limit_success_sat = htlc_success_dust_limit + context.holder_dust_limit_satoshis;
let real_dust_limit_timeout_sat = htlc_timeout_dust_limit + context.holder_dust_limit_satoshis;
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {

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.

non-blocking - should the comment on this method be updated? the htlc is not an Option

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

One nit and one thing maybe worth following up on, but not worth holding up on.

}

/// Returns a fee estimate for the commitment transaction depending on channel type.
pub(super) fn commitment_sat_per_1000_weight_for_type<F: Deref>(

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.

Not a huge fan of the name because it hides which fee types we're using - we're using the "feerates that we'd want to assign to a channel" types, which are different from the "minimum/maximum we'd let our peer assign to a channel", but the name doesn't capture that.

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.

Can you think of anything less atrociously long than holder_preferred_commitment_sat_per_1000_weight_for_type?

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.

selected_commitment_sat_per_1000_weight?

} else {
non_anchor_feerate
};
let new_feerate = commitment_sat_per_1000_weight_for_type(&self.fee_estimator, funded_chan.funding.get_channel_type());

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.

Hmm, would be nice to keep the old behavior where we fetch the feerates outside the loop. In general implementations of the FeeEstimator trait should be pretty effecient, but if we can reduce the number of calls we probably should. Same above in maybe_update_chan_fees.

@TheBlueMatt
TheBlueMatt merged commit eae2bb1 into lightningdevkit:mainJul 17, 2025
@github-project-automationgithub-project-automationBot moved this from Goal: Open to Done in Weekly GoalsJul 17, 2025
@carlaKCcarlaKC mentioned this pull request Jul 17, 2025
TheBlueMatt added a commit that referenced this pull request Jul 18, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

5 participants

@carlaKC@ldk-reviews-bot@TheBlueMatt@elnosh@tankyleo
, '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

Update fee and dust handling for zero fee channels - #3884

Merged
TheBlueMatt merged 10 commits into
lightningdevkit:mainfrom
carlaKC:3789-fees-and-dust
Jul 17, 2025
Merged

Update fee and dust handling for zero fee channels#3884
TheBlueMatt merged 10 commits into
lightningdevkit:mainfrom
carlaKC:3789-fees-and-dust

Conversation

@carlaKC

Copy link
Copy Markdown
Contributor

This PR completes the off-chain handling of V3 channels, as described in #3789.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 23, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@carlaKC

Copy link
Copy Markdown
ContributorAuthor

This still needs a few tests, opening up early for conceptual review.

) -> u64 {
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {
debug_assert_eq!(self.feerate_per_kw, 0);
debug_assert!(fee_spike_buffer_htlc.is_none());

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.

Wondering whether it's worth having this assertion (which makes it necessary to check channel type before calling fee functions). The alternative would be to just ignore the parameters completely for zero fee channels (even though Somefee_spike_buffer_htlc doesn't make sense for the type).

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.

Can't say I have a strong preference. The assertion seems fine to me.

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

All LGTM, didn't carefully check every hunk, though.

Comment threadlightning/src/ln/channel.rs Outdated
Outbound,
}

/// Returns the fees for success and timeout second stage HTLC transactions.

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.

nit: Probably belongs in chan_utils.rs given it kinda mirrors commit_and_htlc_tx_fees_sat and is also called from there?

) -> u64 {
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {
debug_assert_eq!(self.feerate_per_kw, 0);
debug_assert!(fee_spike_buffer_htlc.is_none());

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.

Can't say I have a strong preference. The assertion seems fine to me.

@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 001c8e9 to 4157d24CompareJuly 10, 2025 13:16
@carlaKC
carlaKC marked this pull request as ready for review July 10, 2025 13:32
@carlaKC
carlaKC removed the request for review from joostjagerJuly 10, 2025 13:32
Comment threadlightning/src/ln/channel.rs Outdated
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 4157d24 to 5fe6fa7CompareJuly 10, 2025 13:40
Comment threadlightning/src/ln/update_fee_tests.rs Outdated
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @tankyleo! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/chan_utils.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/update_fee_tests.rs
Comment threadlightning/src/ln/channel.rs Outdated

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

Still grokking some of the changes here and the zero-fee commitment proposal but from my kind of limited context it LGTM (:

Comment threadlightning/src/ln/chan_utils.rs Outdated
Comment threadlightning/src/ln/chan_utils.rs Outdated
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs Outdated
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 5fe6fa7 to f76a270CompareJuly 15, 2025 19:33
@carlaKC

Copy link
Copy Markdown
ContributorAuthor

Addressed review: only major change is moving responsibility for not triggering update_fee on zero fee channels to channelmanger (as there's been no change between new/old fee) and changing channel to just assert that zero fee channels don't hit update code.

@tankyleotankyleo 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 mod two nits, feel free to squash / ping second reviewer :)

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
carlaKCand others added 7 commits July 16, 2025 14:05
This fee rate is currently used in two scenarios:
- To count any fees above what we consider to be a sane estimate towards
our dust exposure.
- To get a maximum dust exposure (when using
MaxDustHTLCExposure::FeeEstimator strategy).
When we have zero fee commitments:
- Commitments are zero fee, so we don't need to count fees towards dust
exposure.
- The amount of dust we have is not dependent on fees, as everything is
zero fee.
- We still want to limit our total dust exposure.
This commit updates get_dust_exposure_limiting_feerate to allow a None
value to prepare for support for zero fee commitments. This clearly
allows us to indicate when we don't care about fee rates for dust
considerations.
In get_max_dust_htlc_exposure_msat, we simply hardcode a value of
1 sat/vbyte if a feerate dependent strategy is being used.
Co-authored-by: Matt Corallo <git@bluematt.me>
LDK does not support a channel type that supports both zero fee and
nonzero fee anchors (as this is impossible), so we can drop the
unnecessary check in build_htlc_output.
This commit pulls calculation of second stage fees into a helper
function. A side effect of this refactor is that it fixes a rounding
issue in commit_and_htlc_tx_fees_sat. Previously, rounding down of
fees would happen after multiplying by the number of HTLCs. Now the
fees will be rounded down before multiplying by the number of HTLCs.
This wasn't a serious issue - it would just cause us very slightly
over estimate our dust exposure at certain fee amounts that needed
rounding. A hard-coded value in test_nondust_htlc_excess_fees_are_dust
is updated to account for this rounding change.
Update second_stage_tx_fees_sat to return zero for zero fee commitments.
As is the case for anchors_zero_fee_commitments, this changes ensures
that we won't trim the second stage transaction fee off of the HTLC
amount.
When we have zero fee commitments, we don't need to calculate our fee
rate or check that it isn't stale because it is always zero.
Co-authored-by: Matt Corallo <git@bluematt.me>
Co-authored-by: Matt Corallo <git@bluematt.me>
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch 2 times, most recently from b421d59 to 60064d1CompareJuly 16, 2025 18:13
@carlaKC

carlaKC commented Jul 16, 2025

Copy link
Copy Markdown
ContributorAuthor

Squashed + addressed last 2x comments + rebased.

Edit: plus small silent rebase failure, new test on master needed an import this branch deleted.

Update test helper in preparation to test zero fee commitments, where
the local balance will differ from the previously hardcoded value.
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 60064d1 to 54559b3CompareJuly 16, 2025 18:35
};
let real_dust_limit_success_sat = htlc_success_dust_limit + context.holder_dust_limit_satoshis;
let real_dust_limit_timeout_sat = htlc_timeout_dust_limit + context.holder_dust_limit_satoshis;
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {

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.

non-blocking - should the comment on this method be updated? the htlc is not an Option

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

One nit and one thing maybe worth following up on, but not worth holding up on.

}

/// Returns a fee estimate for the commitment transaction depending on channel type.
pub(super) fn commitment_sat_per_1000_weight_for_type<F: Deref>(

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.

Not a huge fan of the name because it hides which fee types we're using - we're using the "feerates that we'd want to assign to a channel" types, which are different from the "minimum/maximum we'd let our peer assign to a channel", but the name doesn't capture that.

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.

Can you think of anything less atrociously long than holder_preferred_commitment_sat_per_1000_weight_for_type?

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.

selected_commitment_sat_per_1000_weight?

} else {
non_anchor_feerate
};
let new_feerate = commitment_sat_per_1000_weight_for_type(&self.fee_estimator, funded_chan.funding.get_channel_type());

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.

Hmm, would be nice to keep the old behavior where we fetch the feerates outside the loop. In general implementations of the FeeEstimator trait should be pretty effecient, but if we can reduce the number of calls we probably should. Same above in maybe_update_chan_fees.

@TheBlueMatt
TheBlueMatt merged commit eae2bb1 into lightningdevkit:mainJul 17, 2025
@github-project-automationgithub-project-automationBot moved this from Goal: Open to Done in Weekly GoalsJul 17, 2025
@carlaKCcarlaKC mentioned this pull request Jul 17, 2025
TheBlueMatt added a commit that referenced this pull request Jul 18, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

5 participants

@carlaKC@ldk-reviews-bot@TheBlueMatt@elnosh@tankyleo
, '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

Update fee and dust handling for zero fee channels - #3884

Merged
TheBlueMatt merged 10 commits into
lightningdevkit:mainfrom
carlaKC:3789-fees-and-dust
Jul 17, 2025
Merged

Update fee and dust handling for zero fee channels#3884
TheBlueMatt merged 10 commits into
lightningdevkit:mainfrom
carlaKC:3789-fees-and-dust

Conversation

@carlaKC

Copy link
Copy Markdown
Contributor

This PR completes the off-chain handling of V3 channels, as described in #3789.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 23, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@carlaKC

Copy link
Copy Markdown
ContributorAuthor

This still needs a few tests, opening up early for conceptual review.

) -> u64 {
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {
debug_assert_eq!(self.feerate_per_kw, 0);
debug_assert!(fee_spike_buffer_htlc.is_none());

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.

Wondering whether it's worth having this assertion (which makes it necessary to check channel type before calling fee functions). The alternative would be to just ignore the parameters completely for zero fee channels (even though Somefee_spike_buffer_htlc doesn't make sense for the type).

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.

Can't say I have a strong preference. The assertion seems fine to me.

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

All LGTM, didn't carefully check every hunk, though.

Comment threadlightning/src/ln/channel.rs Outdated
Outbound,
}

/// Returns the fees for success and timeout second stage HTLC transactions.

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.

nit: Probably belongs in chan_utils.rs given it kinda mirrors commit_and_htlc_tx_fees_sat and is also called from there?

) -> u64 {
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {
debug_assert_eq!(self.feerate_per_kw, 0);
debug_assert!(fee_spike_buffer_htlc.is_none());

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.

Can't say I have a strong preference. The assertion seems fine to me.

@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 001c8e9 to 4157d24CompareJuly 10, 2025 13:16
@carlaKC
carlaKC marked this pull request as ready for review July 10, 2025 13:32
@carlaKC
carlaKC removed the request for review from joostjagerJuly 10, 2025 13:32
Comment threadlightning/src/ln/channel.rs Outdated
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 4157d24 to 5fe6fa7CompareJuly 10, 2025 13:40
Comment threadlightning/src/ln/update_fee_tests.rs Outdated
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @tankyleo! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/chan_utils.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/update_fee_tests.rs
Comment threadlightning/src/ln/channel.rs Outdated

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

Still grokking some of the changes here and the zero-fee commitment proposal but from my kind of limited context it LGTM (:

Comment threadlightning/src/ln/chan_utils.rs Outdated
Comment threadlightning/src/ln/chan_utils.rs Outdated
Comment threadlightning/src/ln/channel.rs
Comment threadlightning/src/ln/channel.rs Outdated
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 5fe6fa7 to f76a270CompareJuly 15, 2025 19:33
@carlaKC

Copy link
Copy Markdown
ContributorAuthor

Addressed review: only major change is moving responsibility for not triggering update_fee on zero fee channels to channelmanger (as there's been no change between new/old fee) and changing channel to just assert that zero fee channels don't hit update code.

@tankyleotankyleo 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 mod two nits, feel free to squash / ping second reviewer :)

Comment threadlightning/src/ln/functional_tests.rs Outdated
Comment threadlightning/src/ln/functional_tests.rs Outdated
carlaKCand others added 7 commits July 16, 2025 14:05
This fee rate is currently used in two scenarios:
- To count any fees above what we consider to be a sane estimate towards
our dust exposure.
- To get a maximum dust exposure (when using
MaxDustHTLCExposure::FeeEstimator strategy).
When we have zero fee commitments:
- Commitments are zero fee, so we don't need to count fees towards dust
exposure.
- The amount of dust we have is not dependent on fees, as everything is
zero fee.
- We still want to limit our total dust exposure.
This commit updates get_dust_exposure_limiting_feerate to allow a None
value to prepare for support for zero fee commitments. This clearly
allows us to indicate when we don't care about fee rates for dust
considerations.
In get_max_dust_htlc_exposure_msat, we simply hardcode a value of
1 sat/vbyte if a feerate dependent strategy is being used.
Co-authored-by: Matt Corallo <git@bluematt.me>
LDK does not support a channel type that supports both zero fee and
nonzero fee anchors (as this is impossible), so we can drop the
unnecessary check in build_htlc_output.
This commit pulls calculation of second stage fees into a helper
function. A side effect of this refactor is that it fixes a rounding
issue in commit_and_htlc_tx_fees_sat. Previously, rounding down of
fees would happen after multiplying by the number of HTLCs. Now the
fees will be rounded down before multiplying by the number of HTLCs.
This wasn't a serious issue - it would just cause us very slightly
over estimate our dust exposure at certain fee amounts that needed
rounding. A hard-coded value in test_nondust_htlc_excess_fees_are_dust
is updated to account for this rounding change.
Update second_stage_tx_fees_sat to return zero for zero fee commitments.
As is the case for anchors_zero_fee_commitments, this changes ensures
that we won't trim the second stage transaction fee off of the HTLC
amount.
When we have zero fee commitments, we don't need to calculate our fee
rate or check that it isn't stale because it is always zero.
Co-authored-by: Matt Corallo <git@bluematt.me>
Co-authored-by: Matt Corallo <git@bluematt.me>
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch 2 times, most recently from b421d59 to 60064d1CompareJuly 16, 2025 18:13
@carlaKC

carlaKC commented Jul 16, 2025

Copy link
Copy Markdown
ContributorAuthor

Squashed + addressed last 2x comments + rebased.

Edit: plus small silent rebase failure, new test on master needed an import this branch deleted.

Update test helper in preparation to test zero fee commitments, where
the local balance will differ from the previously hardcoded value.
@carlaKC
carlaKCforce-pushed the 3789-fees-and-dust branch from 60064d1 to 54559b3CompareJuly 16, 2025 18:35
};
let real_dust_limit_success_sat = htlc_success_dust_limit + context.holder_dust_limit_satoshis;
let real_dust_limit_timeout_sat = htlc_timeout_dust_limit + context.holder_dust_limit_satoshis;
if funding.get_channel_type().supports_anchor_zero_fee_commitments() {

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.

non-blocking - should the comment on this method be updated? the htlc is not an Option

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

One nit and one thing maybe worth following up on, but not worth holding up on.

}

/// Returns a fee estimate for the commitment transaction depending on channel type.
pub(super) fn commitment_sat_per_1000_weight_for_type<F: Deref>(

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.

Not a huge fan of the name because it hides which fee types we're using - we're using the "feerates that we'd want to assign to a channel" types, which are different from the "minimum/maximum we'd let our peer assign to a channel", but the name doesn't capture that.

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.

Can you think of anything less atrociously long than holder_preferred_commitment_sat_per_1000_weight_for_type?

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.

selected_commitment_sat_per_1000_weight?

} else {
non_anchor_feerate
};
let new_feerate = commitment_sat_per_1000_weight_for_type(&self.fee_estimator, funded_chan.funding.get_channel_type());

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.

Hmm, would be nice to keep the old behavior where we fetch the feerates outside the loop. In general implementations of the FeeEstimator trait should be pretty effecient, but if we can reduce the number of calls we probably should. Same above in maybe_update_chan_fees.

@TheBlueMatt
TheBlueMatt merged commit eae2bb1 into lightningdevkit:mainJul 17, 2025
@github-project-automationgithub-project-automationBot moved this from Goal: Open to Done in Weekly GoalsJul 17, 2025
@carlaKCcarlaKC mentioned this pull request Jul 17, 2025
TheBlueMatt added a commit that referenced this pull request Jul 18, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

5 participants

@carlaKC@ldk-reviews-bot@TheBlueMatt@elnosh@tankyleo