Skip to content

Revert separate non-dust HTLC sources for holder commitments #3745

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

wpaulino
Copy link
Contributor

We previously provided non-dust HTLC sources to avoid storing duplicate non-dust HTLC data in the htlc_outputs Vec where all HTLCs would be tracked in a holder commitment update. With splicing, we'll unfortunately be forced to store redundant copies of non-dust HTLC data within the commitment transaction for each relevant FundingScope. As a result, providing non-dust HTLC sources separately no longer provides any benefits. In the future, we also plan to rework how the HTLC data for holder and counterparty commitments are tracked to avoid storing duplicate HTLCSources.

Along the way, this commit also omits setting the Option<Signature> for non-dust HTLCs, as they are already tracked within the HolderCommitmentTransaction.

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Apr 17, 2025

👋 Thanks for assigning @tankyleo 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.

Copy link

codecov bot commented Apr 17, 2025

Codecov Report

Attention: Patch coverage is 95.00000% with 2 lines in your changes missing coverage. Please review.

Project coverage is 89.11%. Comparing base (c6921fa) to head (4d0eca1).
Report is 29 commits behind head on main.

Files with missing lines Patch % Lines
lightning/src/ln/channel.rs 81.81% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3745      +/-   ##
==========================================
- Coverage   89.15%   89.11%   -0.04%     
==========================================
  Files         156      157       +1     
  Lines      123837   123923      +86     
  Branches   123837   123923      +86     
==========================================
+ Hits       110408   110440      +32     
- Misses      10754    10806      +52     
- Partials     2675     2677       +2     

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

@wpaulino wpaulino requested review from TheBlueMatt and removed request for joostjager April 18, 2025 02:18
@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @TheBlueMatt! 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.

@TheBlueMatt
Copy link
Collaborator

Sorry for the delay here. So given we're gonna do #3738, is the point of this change to have less diff across the two update types? In #3738 it was proposed that we finally split HTLCOutputInCommitment so that we don't deal with the spurious output index field anymore as well as avoids the redundant signature field. The PR details on this one explain that this is about splicing, but post-#3738 it wouldn't be used for splicing, and its not clear to me that this simplifies the code (eg back to the 0.1 state) much?

@wpaulino
Copy link
Contributor Author

So given we're gonna do #3738, is the point of this change to have less diff across the two update types?

Correct.

In #3738 it was proposed that we finally split HTLCOutputInCommitment so that we don't deal with the spurious output index field anymore as well as avoids the redundant signature field. The PR details on this one explain that this is about splicing, but post-#3738 it wouldn't be used for splicing, and its not clear to me that this simplifies the code (eg back to the 0.1 state) much?

This change isn't about splicing specifically, it just notes that the goal of having separate non-dust HTLC sources is unnecessary because splicing monitor updates will have duplicate HTLC data tracked in each CommitmentTransaction anyway.

So this change keeps both the legacy LatestHolderCommitmentTXInfo and the new LatestHolderCommitmentTX closer together, as we'll eventually replace the latter with the former.

@@ -3845,8 +3835,7 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {

Ok(LatestHolderCommitmentTXInfo {
commitment_tx: holder_commitment_tx,
htlc_outputs: dust_htlcs,
nondust_htlc_sources,
htlc_outputs,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does LDK 0.1 support handling the case with htlc_sources containing nondust HTLC sources with None for the signature? Or does it get confused cause it only expects either a separate nondust_htlc_sources or Some for some of the signatures?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops yeah, I had misread how this worked. We do still need to track the signatures redundantly for backwards compat.

/// `Option<Signature>` for backwards compatibility.
// Includes both dust and non-dust HTLCs. The `Option<Signature>` is always `None`, as they
// are already tracked within the `HolderCommitmentTransaction` above. We still have to
// track it for backwards compatibility though.
htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Signature>, Option<HTLCSource>)>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it worth just doing the sigs at write-time?

$ git diff
diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs
index a1acfd7df..687ebc61d 100644
--- a/lightning/src/chain/channelmonitor.rs
+++ b/lightning/src/chain/channelmonitor.rs
@@ -571,7 +571,7 @@ pub(crate) enum ChannelMonitorUpdateStep {
                // Includes both dust and non-dust HTLCs. The `Option<Signature>` is always `None`, as they
                // are already tracked within the `HolderCommitmentTransaction` above. We still have to
                // track it for backwards compatibility though.
-               htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Signature>, Option<HTLCSource>)>,
+               htlc_outputs: Vec<(HTLCOutputInCommitment, Option<HTLCSource>)>,
                claimed_htlcs: Vec<(SentHTLCId, PaymentPreimage)>,
        },
        LatestCounterpartyCommitmentTXInfo {
@@ -629,7 +629,12 @@ impl_writeable_tlv_based_enum_upgradable!(ChannelMonitorUpdateStep,
        (0, LatestHolderCommitmentTXInfo) => {
                (0, commitment_tx, required),
                (1, claimed_htlcs, optional_vec),
-               (2, htlc_outputs, required_vec),
+               (2, legacy_htlc_outputs, (legacy, crate::util::ser::WithoutLength<Vec<(HTLCOutputInCommitment, Option<Signature>, Option<HTLCSource>)>>, |us| {
+                       if let &Self::LatestHolderCommitmentTXInfo { htlc_outputs, .. } = us {
+                               Some(crate::util::ser::IterableOwned(htlc_outputs.iter().map(|(a, b)| (a, None::<Signature>, b))))
+                       } else { unreachable!() }
+               })),
+               (99999999, htlc_outputs, (static_value, legacy_htlc_outputs.ok_or(DecodeError::InvalidValue)?.0.into_iter().map(|(a, _, b)| (a, b)).collect()))
        },
        (1, LatestCounterpartyCommitmentTXInfo) => {
                (0, commitment_txid, required),
diff --git a/lightning/src/util/ser_macros.rs b/lightning/src/util/ser_macros.rs
index c3cf2044d..a2727e2ba 100644
--- a/lightning/src/util/ser_macros.rs
+++ b/lightning/src/util/ser_macros.rs
@@ -56,7 +56,7 @@ macro_rules! _encode_tlv {
                        if let Some(v) = &value {
                                let encoded_value = v.encode();
                                let mut read_slice = &encoded_value[..];
-                               let _: $fieldty = $crate::util::ser::Readable::read(&mut read_slice)
+                               let _: $fieldty = $crate::util::ser::LengthReadable::read_from_fixed_length_buffer(&mut read_slice)
                                        .expect("Failed to read written TLV, check types");
                                assert!(read_slice.is_empty(), "Reading written TLV was short, check types");
                        }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Don't think we can do this anymore without unrolling the macro, since we'd need to write it with the sigs, but do the legacy thing for reads.

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

Copy link
Contributor

@tankyleo tankyleo left a comment

Choose a reason for hiding this comment

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

I also have Matt's question about potential downgrades to 0.1 from here.

If none of the HTLC signatures are set, 0.1 assumes that the sources are provided separately by nondust_htlc_sources. But this is a wrong assumption to make after this commit.

@@ -638,7 +630,6 @@ impl_writeable_tlv_based_enum_upgradable!(ChannelMonitorUpdateStep,
(0, commitment_tx, required),
(1, claimed_htlcs, optional_vec),
(2, htlc_outputs, required_vec),
(4, nondust_htlc_sources, optional_vec),
Copy link
Contributor

Choose a reason for hiding this comment

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

Double checking: we can remove this even field because we have never written it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct since we never released a version with it written

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks feel free to resolve

We previously provided non-dust HTLC sources to avoid storing duplicate
non-dust HTLC data in the `htlc_outputs` `Vec` where all HTLCs would be
tracked in a holder commitment update. With splicing, we'll
unfortunately be forced to store redundant copies of non-dust HTLC data
within the commitment transaction for each relevant `FundingScope`. As a
result, providing non-dust HTLC sources separately no longer provides
any benefits. In the future, we also plan to rework how the HTLC data
for holder and counterparty commitments are tracked to avoid storing
duplicate `HTLCSource`s.
@wpaulino wpaulino force-pushed the revert-separate-nondusts-htlc-sources branch from 6dad376 to 4d0eca1 Compare April 29, 2025 18:41
Copy link
Contributor

@tankyleo tankyleo 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 nearly onboard, some more nits / questions

Comment on lines -3101 to -3108
debug_assert_eq!(htlc_outputs.iter().filter(|(_, s, _)| s.is_some()).count(), holder_commitment_tx.trust().nondust_htlcs().len());
for (a, b) in htlc_outputs.iter().filter(|(_, s, _)| s.is_some()).map(|(h, _, _)| h).zip(holder_commitment_tx.trust().nondust_htlcs().iter()) {
debug_assert_eq!(a, b);
}
debug_assert_eq!(htlc_outputs.iter().filter(|(_, s, _)| s.is_some()).count(), holder_commitment_tx.counterparty_htlc_sigs.len());
for (a, b) in htlc_outputs.iter().filter_map(|(_, s, _)| s.as_ref()).zip(holder_commitment_tx.counterparty_htlc_sigs.iter()) {
debug_assert_eq!(a, b);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I would have kept these debug statements around - would you prefer we remove them ?

if missing_nondust_source {
return Err(());
}

Ok(Self {
Copy link
Contributor

Choose a reason for hiding this comment

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

Want to add these debug checks above this line ?

Suggested change
Ok(Self {
debug_assert!(
holder_commitment_tx.nondust_htlcs().iter().zip(holder_signed_tx.htlc_outputs.iter().map(|(htlc, _, _)| htlc))
.all(|(htlc_a, htlc_b)| htlc_a == htlc_b)
);
debug_assert!(
holder_commitment_tx.counterparty_htlc_sigs.iter().zip(holder_signed_tx.htlc_outputs.iter().map(|(_, sig, _)| sig.as_ref().unwrap()))
.all(|(sig_a, sig_b)| sig_a == sig_b)
);
Ok(Self {

@@ -638,7 +630,6 @@ impl_writeable_tlv_based_enum_upgradable!(ChannelMonitorUpdateStep,
(0, commitment_tx, required),
(1, claimed_htlcs, optional_vec),
(2, htlc_outputs, required_vec),
(4, nondust_htlc_sources, optional_vec),
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks feel free to resolve

}
debug_assert!(source_opt.is_none(), "HTLCSource should have been put somewhere");
htlc_outputs.push((htlc, counterparty_htlc_sig, source_opt.cloned()));
Copy link
Contributor

Choose a reason for hiding this comment

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

A comment like this above this line:

                        // For backwards compatibility, set the signature of non-dust HTLCs here

// HTLCs in the `CommitmentTransaction`.
nondust_htlc_sources: Vec<HTLCSource>,
dust_htlcs: Vec<(HTLCOutputInCommitment, Option<HTLCSource>)>,
htlcs: Vec<(HTLCOutputInCommitment, Option<HTLCSource>)>,
Copy link
Contributor

Choose a reason for hiding this comment

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

// Non-dust `HTLCOutputInCommitment`'s are stored in both `tx`, and `htlcs`; we accept this tradeoff (since it allows us to keep the HTLC-source pairs intact / things will make more sense in splicing)

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.

Hmm, I guess I'm not really clear on if we need to do this now? Seems like it kinda sucks to go back to duplicating the signatures in the HTLC output list and and how much code does it actually clean up to map htlc_outputs from a 3-tuple to a pair vs joining the dust and non-dust outputs?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants