Skip to content

Conversation

tankyleo
Copy link
Contributor

Otherwise, we could potentially hit the max 10_000vB size limit on V3 transactions (BIP 431 rule 4) if we aggregate enough HTLC-claims together.

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Sep 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.

@TheBlueMatt TheBlueMatt added this to the 0.2 milestone Sep 25, 2025
Copy link

codecov bot commented Sep 25, 2025

Codecov Report

❌ Patch coverage is 97.24771% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.76%. Comparing base (19a9dbd) to head (c222c41).
⚠️ Report is 32 commits behind head on main.

Files with missing lines Patch % Lines
lightning/src/events/bump_transaction/mod.rs 96.63% 0 Missing and 4 partials ⚠️
lightning/src/ln/zero_fee_commitment_tests.rs 97.97% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4129      +/-   ##
==========================================
+ Coverage   88.63%   88.76%   +0.13%     
==========================================
  Files         180      180              
  Lines      134878   135660     +782     
  Branches   134878   135660     +782     
==========================================
+ Hits       119543   120425     +882     
+ Misses      12567    12467     -100     
  Partials     2768     2768              
Flag Coverage Δ
fuzzing 21.71% <0.00%> (-0.05%) ⬇️
tests 88.61% <97.24%> (+0.14%) ⬆️

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.

// See rust-bitcoin to_vbytes_ceil
let self_vbytes = (self.weight() + 3) / 4; // This is the weight of the witnesses alone, we need to add more here
let other_vbytes = (other.weight() + 3) / 4;
// What is a good offset to use here to leave room for the user-provided input-output pair?
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmmmmmmmmm, I wonder if we shouldn't just not merge and then do the work in the events::bump_transaction module and add like a needs_v3_transaction: Option<()> in the event? That way its at least pretty explicit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You mean aggregate in bump_transaction ? How about restrict ourselves to aggregate max 25 HTLCs ? This should be good for any conceivable user-provided input-output pair.

Copy link
Collaborator

Choose a reason for hiding this comment

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

No as in break an aggregation in bump_transaction

@tankyleo tankyleo force-pushed the 2025-09-cap-0fc-holder-htlc-txs branch 2 times, most recently from 63e8f1c to db273bb Compare September 26, 2025 11:54
@tankyleo tankyleo requested review from TheBlueMatt and removed request for TheBlueMatt and carlaKC September 26, 2025 13:04
@tankyleo tankyleo force-pushed the 2025-09-cap-0fc-holder-htlc-txs branch from db273bb to 107eb25 Compare September 28, 2025 14:24
@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.

@tankyleo tankyleo removed the request for review from TheBlueMatt October 1, 2025 07:29
// Cap the size of transactions claiming `HolderHTLCOutput` in 0FC channels.
// Otherwise, we could hit the max 10_000vB size limit on V3 transactions
// (BIP 431 rule 4).
25
Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess this is okay, but I thought the point of pulling this out into the bump_transaction logic is so that we could do the split after we see the inputs the user wants to contribute so that we can get close to 10k without exceeding it, rather than picking some random "max overhead" constant.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks yea let me know the commit below sounds

Otherwise, we could hit the max 10_000vB size limit on V3 transactions
(BIP 431 rule 4).
@tankyleo tankyleo force-pushed the 2025-09-cap-0fc-holder-htlc-txs branch from 107eb25 to c222c41 Compare October 9, 2025 14:15
@tankyleo tankyleo marked this pull request as ready for review October 9, 2025 14:17
@ldk-reviews-bot ldk-reviews-bot requested a review from jkczyz October 9, 2025 14:17
@tankyleo tankyleo requested review from TheBlueMatt and removed request for jkczyz October 9, 2025 14:17
@carlaKC carlaKC self-requested a review October 9, 2025 14:41
Copy link
Contributor

@carlaKC carlaKC left a comment

Choose a reason for hiding this comment

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

approach lgtm

Comment on lines +890 to +896
// This matches the scheme used in `onchaintx.rs`, so for non-0fc-channels,
// this should match the `ClaimId` of the claim generated in `onchaintx.rs`.
let mut engine = Sha256::engine();
for htlc in htlcs {
engine.input(&htlc.commitment_txid.to_byte_array());
engine.input(&htlc.htlc.transaction_output_index.unwrap().to_be_bytes());
}
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: pull out into ClaimID::from_prev_outs to de-duplicate?

let unsigned_tx_weight =
htlc_tx.weight().to_wu() - (htlc_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT);
let expected_signed_tx_weight = unsigned_tx_weight + total_satisfaction_weight;
if expected_signed_tx_weight >= max_weight {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this just be >? We're allowed to be on exactly 10,000 vbytes, just no larger.

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 considered this, but went for the more conservative approach here.

let expected_signed_tx_weight = unsigned_tx_weight + total_satisfaction_weight;
if expected_signed_tx_weight >= max_weight {
let extra_weight = expected_signed_tx_weight - max_weight;
let htlcs_to_remove = (extra_weight / chan_utils::htlc_timeout_tx_weight(channel_type)
Copy link
Contributor

Choose a reason for hiding this comment

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

Not critical, but I think these weights include transaction overhead (version etc) so we'll overcount by 40 wu per htlc?

Copy link
Contributor

Choose a reason for hiding this comment

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

Good catch, we should just use the weight of the HTLC input-output pair

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks ! yes this likely resolves the weird + 2 I've got below

Comment on lines +114 to +115
let _node_a_id = nodes[0].node.get_our_node_id();
let _node_b_id = nodes[1].node.get_our_node_id();
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: unused vars unnecessary?

if expected_signed_tx_weight >= max_weight {
let extra_weight = expected_signed_tx_weight - max_weight;
let htlcs_to_remove = (extra_weight / chan_utils::htlc_timeout_tx_weight(channel_type)
// If we remove extra_weight / timeout_weight + 1 we sometimes still land above max_weight
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume this wouldn't be an issue if we just round up the integer division?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

likely it's because htlc_timeout_tx_weight is the weight of the full-transaction, but we want to only count the bytes from the single input-output pair

// (BIP 431 rule 4).
chan_utils::TRUC_MAX_WEIGHT
} else {
u64::MAX
Copy link
Contributor

Choose a reason for hiding this comment

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

Might as well cap this to the max weight we can relay?

engine.input(&htlc.commitment_txid.to_byte_array());
engine.input(&htlc.htlc.transaction_output_index.unwrap().to_be_bytes());
}
let utxo_id = ClaimId(Sha256::from_engine(engine).to_byte_array());
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't we want to reuse the same ID until we move on to the next batch?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yea, was just wondering this - should we use the one from the event at least until we split the batch, which would work in the common case. Might want to sort the outputs so that we use them in a deterministic order (I imagine they are in the event, but...)

});
htlc_tx.input.push(htlc_input);
let htlc_output = htlc_descriptor.tx_output(&self.secp);
htlc_tx.output.push(htlc_output);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we want to break out of this loop early if we get close to the max_weight? Would mean generating the claim ID during this loop rather than separately but that's trivial. Would avoid an extra coin-selection pass in a few cases, but its not critical.

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

Successfully merging this pull request may close these issues.

5 participants