Skip to content

Add Shared Input support in interactive TX construction #3842

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 3 commits into
base: main
Choose a base branch
from

Conversation

optout21
Copy link
Contributor

@optout21 optout21 commented Jun 10, 2025

In interactive TX construction, add support for shared input:

  • if we expect a shared input, make sure that the remote node provides it
  • if we provide a shared input, make sure that it's accounted appropriately

Additionally, the prevtx field of the TxAddInput message is changed to Optional, as it should not be set for the shared input (it cannot, as the full funding transaction is not stored on the acceptor side) (spec discussion: lightning/bolts#1160 (comment))

To be used by splicing, see #3736 .

Note: this PR does not include splicing negotiation.

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Jun 10, 2025

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

@optout21 optout21 marked this pull request as ready for review June 11, 2025 19:03
@optout21 optout21 requested review from jkczyz, wpaulino and dunxen June 11, 2025 19:03
@ldk-reviews-bot
Copy link

🔔 1st Reminder

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

1 similar comment
@ldk-reviews-bot
Copy link

🔔 1st Reminder

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

@ldk-reviews-bot
Copy link

🔔 2nd Reminder

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

1 similar comment
@ldk-reviews-bot
Copy link

🔔 2nd Reminder

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

@optout21
Copy link
Contributor Author

Great, can you push those changes here then and rework the shared funding input work to follow a similar approach?

I managed to do it: first I refactored the shared output support as discussed, then added shared input support in a similar style. Although this PR does not include spicing, I also checked the changes with splicing (shared input is used there).

@optout21 optout21 force-pushed the shared-input branch 5 times, most recently from 166e364 to b2e0da8 Compare June 16, 2025 13:47
// If there is a shared input, account for it,
// and for the initiator also consider the fee
if let Some(shared_input) = shared_input {
total_input_satoshis = total_input_satoshis.saturating_add(shared_input);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this supposed to be the full channel value of the shared input, or just the locally owned portion of 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.

The locally owned portion, as this calculation is for ensuring that the contribution is enough to cover the (locally owned) outputs and fees.

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 have some doubts about the possible rounding errors due to the msat->sat conversion.

Copy link
Contributor

Choose a reason for hiding this comment

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

The msat->sat conversion shouldn't be an issue, we always round down to the nearest satoshi. We should document the shared_input argument in the method docs though.

@optout21 optout21 requested a review from wpaulino June 17, 2025 03:10
@optout21
Copy link
Contributor Author

@wpaulino thanks for the comments. I will process them (I've re-requested review accidentally, please ignore that).

@ldk-reviews-bot
Copy link

🔔 3rd Reminder

Hey @dunxen @jkczyz @wpaulino! 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.

1 similar comment
@ldk-reviews-bot
Copy link

🔔 3rd Reminder

Hey @dunxen @jkczyz @wpaulino! 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.

@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @dunxen @jkczyz @wpaulino! 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.

Copy link

codecov bot commented Jun 19, 2025

Codecov Report

Attention: Patch coverage is 90.89506% with 59 lines in your changes missing coverage. Please review.

Project coverage is 89.68%. Comparing base (bed20cf) to head (f41de9a).

Files with missing lines Patch % Lines
lightning/src/ln/interactivetxs.rs 91.39% 43 Missing and 9 partials ⚠️
lightning/src/ln/channel.rs 54.54% 5 Missing ⚠️
lightning/src/util/ser.rs 77.77% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3842      +/-   ##
==========================================
- Coverage   89.70%   89.68%   -0.03%     
==========================================
  Files         164      164              
  Lines      133359   133563     +204     
  Branches   133359   133563     +204     
==========================================
+ Hits       119632   119787     +155     
- Misses      11052    11086      +34     
- Partials     2675     2690      +15     

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

@optout21 optout21 force-pushed the shared-input branch 3 times, most recently from 21e5e36 to bb322a8 Compare June 19, 2025 12:24
@optout21
Copy link
Contributor Author

Review comments addressed. Keeping the fixup commits separately for now, but in the end I will just squash into a single commit.

@ldk-reviews-bot
Copy link

🔔 4th Reminder

Hey @dunxen @jkczyz @wpaulino! 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.

1 similar comment
@ldk-reviews-bot
Copy link

🔔 4th Reminder

Hey @dunxen @jkczyz @wpaulino! 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.

@ldk-reviews-bot
Copy link

🔔 2nd Reminder

Hey @dunxen @jkczyz @wpaulino! 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.

optout21 added 2 commits June 21, 2025 14:43
This simplifies tracking separately the expected and actual shared output.
In the initiator case, we can just provide the shared output separately,
instead of including it within other outputs, and marking which one is the output.
We can use the same field for the intended shared output in the initiator
case, and the expected one in the acceptor case.
In interactivetxs, add support for shared inputs, similar to shared outputs.
A shared input is optional, and is used in case of splicing to add
the current funding as an input.
@optout21
Copy link
Contributor Author

optout21 commented Jun 21, 2025

Squashed, rebased, new comment addressed with a fix. Ready for review.

@ldk-reviews-bot
Copy link

🔔 5th Reminder

Hey @dunxen @jkczyz @wpaulino! 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.

1 similar comment
@ldk-reviews-bot
Copy link

🔔 5th Reminder

Hey @dunxen @jkczyz @wpaulino! 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.

@ldk-reviews-bot
Copy link

🔔 3rd Reminder

Hey @dunxen @jkczyz @wpaulino! 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.

// - the peer's paid feerate does not meet or exceed the agreed feerate (based on the minimum fee).
self.check_counterparty_fees(remote_inputs_value.saturating_sub(remote_outputs_value))?;

let shared_funding_output = self.shared_funding_output.clone();
let constructed_tx = ConstructedTransaction::new(self);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we have new return an error and check the conditions below as part of it?

// - the `prevtx` and `prevtx_vout` are identical to a previously added
// (and not removed) input's
return Err(AbortReason::PrevTxOutInvalid);
if let Some(shared_funding_input) = &self.shared_funding_input {
Copy link
Contributor

Choose a reason for hiding this comment

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

We need to abort if prevtx is also set here, and below in the else case if shared_funding_input is set

Comment on lines 649 to +650
} else {
// The receiving node:
// - MUST fail the negotiation if:
// - `prevtx_vout` is greater or equal to the number of outputs on `prevtx`
return Err(AbortReason::PrevTxOutInvalid);
}
if let Some(prevtx) = &msg.prevtx {
Copy link
Contributor

Choose a reason for hiding this comment

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

We can save one indent by collapsing these into a single line

added_by: AddingRole::Remote,
input,
});
if !self.prevtx_outpoints.insert(prev_outpoint) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't think it matters here, but best to only insert the entry above after we know it's not a duplicate

Comment on lines +836 to +837
} else {
if let Some(prevtx) = &msg.prevtx {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, can save one indent

input: TxIn,
prev_tx: Option<TransactionU16LenLimited>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this optional? This is always required for a non-shared input.

pub(crate) struct InteractiveTxInput {
serial_id: SerialId,
added_by: AddingRole,
input: InputOwned,
Copy link
Contributor

Choose a reason for hiding this comment

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

We really need to use a different type here so we're not tracking prevtx for Single inputs

Comment on lines 1926 to 1928
if is_initiator {
our_funding_inputs_weight =
our_funding_inputs_weight.saturating_add(estimate_input_weight(output).to_wu());
} else {
return Err(AbortReason::PrevTxOutInvalid);
our_funding_inputs_weight.saturating_add(P2WSH_INPUT_WEIGHT_LOWER_BOUND);
Copy link
Contributor

Choose a reason for hiding this comment

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

This should probably go in the is_initiator check below instead

let txid = prev_funding_tx.compute_txid();
let value = prev_funding_tx.output.get(vout as usize).unwrap().value.to_sat();
if local_owned > value {
println!("Warning: local owned > value for shared input, {} {}", local_owned, value);
Copy link
Contributor

Choose a reason for hiding this comment

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

We shouldn't print in tests, just make this an assertion instead?

// If there is a shared input, account for it,
// and for the initiator also consider the fee
if let Some(shared_input) = shared_input {
total_input_satoshis = total_input_satoshis.saturating_add(shared_input);
Copy link
Contributor

Choose a reason for hiding this comment

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

The msat->sat conversion shouldn't be an issue, we always round down to the nearest satoshi. We should document the shared_input argument in the method docs though.

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.

5 participants