Skip to content

[Custom Transactions] Add TxBuilder::get_available_balances#4026

Open
tankyleo wants to merge 12 commits intolightningdevkit:mainfrom
tankyleo:balances
Open

[Custom Transactions] Add TxBuilder::get_available_balances#4026
tankyleo wants to merge 12 commits intolightningdevkit:mainfrom
tankyleo:balances

Conversation

@tankyleo
Copy link
Contributor

Depends on #3921

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Aug 25, 2025

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

@tankyleo tankyleo requested a review from TheBlueMatt August 25, 2025 05:35
@tankyleo
Copy link
Contributor Author

@TheBlueMatt would appreciate a high-level design pass on this one thank you. I largely just move get_available_balances over to TxBuilder and then cleanup a bunch of things we no longer need in channel.

Comment on lines 128 to 141
fn get_available_balances(
&self, is_outbound_from_holder: bool, channel_value_satoshis: u64,
value_to_holder_msat: u64, pending_htlcs: &[HTLCAmountDirection], feerate_per_kw: u32,
dust_exposure_limiting_feerate: Option<u32>, max_dust_htlc_exposure_msat: u64,
holder_channel_constraints: ChannelConstraints,
counterparty_channel_constraints: ChannelConstraints, channel_type: &ChannelTypeFeatures,
) -> crate::ln::channel::AvailableBalances;
fn get_next_commitment_stats(
&self, local: bool, is_outbound_from_holder: bool, channel_value_satoshis: u64,
value_to_holder_msat: u64, next_commitment_htlcs: &[HTLCAmountDirection],
addl_nondust_htlc_count: usize, feerate_per_kw: u32,
dust_exposure_limiting_feerate: Option<u32>, broadcaster_dust_limit_satoshis: u64,
channel_type: &ChannelTypeFeatures,
) -> NextCommitmentStats;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm thinking we'll want to merge these two into one :)

Copy link
Collaborator

Choose a reason for hiding this comment

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

I feel like we could, yea?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Current blocker for me is that get_available_balances is a function of both local and remote commitments, but will dig further thank you :)

@codecov
Copy link

codecov bot commented Aug 25, 2025

Codecov Report

❌ Patch coverage is 96.30314% with 20 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.02%. Comparing base (7fcfb39) to head (26ea1e0).

Files with missing lines Patch % Lines
lightning/src/ln/channel.rs 93.11% 11 Missing and 4 partials ⚠️
lightning/src/sign/tx_builder.rs 98.45% 2 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4026      +/-   ##
==========================================
+ Coverage   86.01%   86.02%   +0.01%     
==========================================
  Files         156      156              
  Lines      102857   102891      +34     
  Branches   102857   102891      +34     
==========================================
+ Hits        88471    88513      +42     
  Misses      11877    11877              
+ Partials     2509     2501       -8     
Flag Coverage Δ
tests 86.02% <96.30%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

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

I'm confused, I was under the impression we were going to pause the custom tx builder work for a month or two after landing #3921?

@ldk-reviews-bot
Copy link

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

@tankyleo
Copy link
Contributor Author

I'm confused, I was under the impression we were going to pause the custom tx builder work for a month or two after landing #3921?

Agreed yes this doesn't have to land anytime soon - I still wanted to get this PR out of my head and written before I stop TxBuilder for a while.

Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

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

API looks good to me, but, indeed, next_commitment_stats and available_balances feel like two things that are just close enough that they might as well be merged, no?

holder_channel_constraints.dust_limit_satoshis,
channel_type,
)
// TODO: should `get_available_balances` be fallible ?
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmmm, could definitely go either way. I do like the idea of merging get_next_commitment_stats and get_available_balances, at which point we'd want the joint thing to be fallible, though.

This commit moves the previous `TxBuilder::get_next_commitment_stats`
method to a private function, and then calls this function in
`TxBuilder::get_onchain_stats`.

Similar to the previous `TxBuilder::get_next_commitment_stats` method,
`TxBuilder::get_onchain_stats` fails if the party cannot afford the
HTLCs outbound from said party, and the anchors if they are the funder.

Aside from the API changes on `TxBuilder`, there should be no functional
changes in this commit.
Move calls to `TxBuilder::commit_tx_fee_sat` in
`new_for_inbound_channel` and `new_for_outbound_channel` to
`TxBuilder::get_onchain_stats`, and set the parameters such that the
exact same behavior is maintained.

We also replace calls to `TxBuilder::commit_tx_fee_sat` in
`get_pending_htlc_stats`, `next_local_commit_tx_fee_msat`, and
`next_remote_commit_tx_fee_msat` with `chan_utils::commit_tx_fee_sat`.
All three functions get deleted in an upcoming commit, so we accept
this temporary use of the `chan_utils::commit_tx_fee_sat` function.
We make temporary use of the raw
`tx_builder::saturating_sub_anchor_outputs` function in
`get_available_balances_for_scope`. This ok because we move most of
the `get_available_balances_for_scope` function to the
`TxBuilder::get_onchain_stats` call in an upcoming commit.

Again, no functional change is introduced in this commit.
In an upcoming commit, we move `get_available_balances_for_scope` behind
`TxBuilder::get_onchain_stats`, and pass channel parameters relevant to
balance calculations in `TxBuilder::get_onchain_stats` via
`ChannelConstraints`.

There are no functional changes in this commit.
This snippet is currently used in
`tx_builder::get_next_commitent_stats`, and will be used in an upcoming
commit in `get_available_balances_for_scope`.

There are no functional changes in this commit, as the
`extra_accepted_htlc_dust_exposure` member of `NextCommitmentStats` was
not used.
We no longer make use of `get_pending_htlc_stats`,
`get_dust_buffer_feerate`, `next_local_commit_tx_fee_msat`, and
`next_remote_commit_tx_fee_msat` in the `channel` module, and instead
make use of tooling from the `tx_builder` module.

`HTLCStats::pending_outbound_htlcs` is now
`pending_htlcs.iter().filter(|htlc| htlc.outbound).count()`, and
remains the same value.

`HTLCStats::pending_inbound_htlcs_value_msat` is now calculated in
`get_available_balances_for_scope`, and remains the same value.

`HTLCStats::pending_outbound_htlcs_value_msat` is now calculated in
`get_available_balances_for_scope`, and remains the same value.

To determine whether a HTLC is dust for the purpose of calculating total
dust exposure, we now refer only to `ChannelContext::feerate_per_kw`,
and ignore any upcoming fee updates stored in `pending_update_fee`.

The same applies for dust exposure due to excess fees; we ignore any fee
updates in `ChannelContext::pending_update_fee`, and only refer to
`ChannelContext::feerate_per_kw`.

For outbound feerate updates, this is ok because all such updates first
get placed in the holding cell. We validate dust exposure again upon
freeing the feerate update from the holding cell, and immediately
generate the corresponding commitment.

For inbound feerate updates, it is possible that the peer sends us a
feerate update that is in excess of our dust exposure limiting feerate,
at the same time that we send non-dust HTLCs that exhaust the max dust
exposure at the new feerate. This leads to a channel force-close when
the peer sends us their commitment signed including the HTLCs and the
new feerate.

The same set of HTLCs is accounted for when calculating dust exposure.

`max_reserved_commit_tx_fee_msat` and `min_reserved_commit_tx_fee_msat`
both account for *all* pending HTLCs, including
`AwaitingRemoteRevokeToRemove` and `AwaitingRemovedRemoteRevoke` HTLCs;
these values previously only accounted for `LocalAnnounced`,
`Committed`, and `RemoteRemoved` HTLCs.

In the case where `is_outbound_from_holder` is false,
`max_reserved_commit_tx_fee_msat` now also accounts for HTLCs in the
holding cell, whereas it previously would not.

These values are also the result of the feerate getting multiplied by
the fee spike buffer increase multiple, instead of the final commitment
transaction fee getting multiplied by that multiple. This results in
higher values, as we multiply before the rounding down to the nearest
satoshi.

Finally, these values also account for any non-dust HTLCs that
transition to dust at the higher feerate, resulting in lower values.
This is a direct code move to `tx_builder::get_available_balances`.
We choose to multiply `FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE` by the
feerate when checking the fee spike buffer in
`can_accept_incoming_htlc` instead of multiplying the multiple by the
commitment transaction fee. This allows us to delete
`NextCommitmentStats::commit_tx_fee_sat`, and return balances including
the commitment transaction fee in `TxBuilder::get_onchain_stats`. This
unblocks a good amount of cleanup.

Note that this means LDK now rejects HTLCs that previous versions of LDK
would have accepted. We made the mirroring change in
`get_available_balances_for_scope` a few commits earlier.

We also now account for non-dust HTLCs turning to dust at the multiplied
feerate, decreasing the overall weight of the transaction.

We also remove other fields in `NextCommitmentStats` which can be easily
calculated in `channel` only.

`TxBuilder::get_onchain_stats` could also check the reserve
requirements, given that it gets the reserves in `ChannelConstraints`.
I leave this to follow-up work.
Note that `AvailableBalances` will always refer to the holder's
balances, even when `local` is set to `false`, when calling
`TxBuilder::get_onchain_stats`.
self.get_holder_channel_constraints(funding),
self.get_counterparty_channel_constraints(funding),
funding.get_channel_type(),
).unwrap().available_balances
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If this direction is good I'll bubble any errors here upstream

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Goal: Merge

Development

Successfully merging this pull request may close these issues.

3 participants