Skip to content

Conversation

@benthecarman
Copy link
Contributor

@benthecarman benthecarman commented Dec 3, 2025

When doing a splice, to properly calculate fees we need the channel's funding utxo in our wallet, otherwise our wallet won't know the channel's original size. This adds the channel funding txo to the wallet before a splice as we only really need this information for splices and not for all channels.

@benthecarman benthecarman requested a review from tnull December 3, 2025 19:53
@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Dec 3, 2025

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

@benthecarman benthecarman force-pushed the add-chan-to-wallet branch 2 times, most recently from 091e391 to d7f8bc9 Compare December 3, 2025 20:14
Copy link
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

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

Makes sense to me. Do we think this is worth a backport for 0.7 or are we fine waiting for 0.8 with shipping this?

The different cases in the event handler could use a comment or two to explain when we'd land on which side of the if/else clauses there. Also, we really need to be careful with just replaying events in cases where we don't expect immediate resolution of the error on retry, as we'd otherwise just spin and block any other event processing. This could also use better test coverage to show why we need this change in the first place.

Besides that, also pinging @jkczyz as a second reviewer on this.

src/event.rs Outdated
self.logger,
"Failed to find channel info for pending channel {channel_id} with counterparty {counterparty_node_id}"
);
return Err(ReplayEvent());
Copy link
Collaborator

Choose a reason for hiding this comment

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

This would induce an infinite loop in event handling, no? Rather than doing this, we probably need to just log the error for now, and abort the flow when we can with 0.3? Also, would it make sense to add a debug_assert here, given that we expect this always to be avaialble? (same below)

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 error below can only happen on a persistence failure, elsewhere in the event handling we replay on those errors so I think it makes sense to keep as is.

For this one, it should always be available so I will just change to a debug_assert

},
}
} else {
log_info!(
Copy link
Collaborator

Choose a reason for hiding this comment

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

In what case would we end up in this else branch, and would we need to do something related to the redeemscript, too?

Copy link
Contributor Author

@benthecarman benthecarman Dec 4, 2025

Choose a reason for hiding this comment

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

According to the docs, the funding_txo "Will be None if the channel's funding transaction reached an acceptable depth prior to version 0.2". Since this was also added in 0.2, the redeem script would also be None so there shouldn't be anything to add. Either way, we need the funding txo to be able to insert so we can't add it.

node_b.splice_in(&user_channel_id_b, node_a.node_id(), 4_000_000).unwrap();

expect_splice_pending_event!(node_a, node_b.node_id());
let txo = expect_splice_pending_event!(node_a, node_b.node_id());
Copy link
Collaborator

Choose a reason for hiding this comment

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

These test changes pass on main. Can you add coverage for the cases where the previous approach would lead to inaccurate fee estimations?

Copy link
Contributor Author

@benthecarman benthecarman Dec 4, 2025

Choose a reason for hiding this comment

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

The bug/issue only exists with bitcoind syncing so to do so, we'd have to change the test to sync with bitcoind rpc. Is that wanted?

Copy link
Collaborator

Choose a reason for hiding this comment

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

The bug/issue only exists with bitcoind syncing so to do so, we'd have to change the test to sync with bitcoind rpc. Is that wanted?

Sure, feel free to do so. Alternatively, you could also add a _bitcoind variant test case (ofc reusing the logic) so we have two test cases for bitcoind and esplora - that's if we think there would be more chain source specific edge cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added

}
}

impl UniffiCustomTypeConverter for ScriptBuf {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: While this should be fine for now, we generally try to get away from the UniffiCustomTypeConverter approach, and instead convert more and more objects to proper interfaces.

@tnull tnull requested a review from jkczyz December 4, 2025 08:11
@ldk-reviews-bot
Copy link

🔔 1st Reminder

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

src/event.rs Outdated
);
},
Some(output) => {
if let Err(e) = self.wallet.insert_txo(funding_txo, output) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmmm... I guess we didn't really need the redeem script in ChannelPending and SplicePending since we only need the previous funding output in the wallet for the next splice. Might have been better in ChannelReady. We'd be able to avoid the look-up, although it wouldn't remove the Option check.

Copy link
Contributor Author

@benthecarman benthecarman Dec 9, 2025

Choose a reason for hiding this comment

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

Yeah sadly I messed up here, i think we needed to add the redeem script along with the new channel size so we had the full utxo information

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, though if we insert the previous funding txo when initiating the splice as mentioned in the other comment, then I think we'll have all the necessary information in ChannelDetails? Essentially, we'd be lazily adding the previous funding output to the wallet when initiating the splice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Made so we add it before a splice-in. I still think this one is good to keep so if you're doing any other wallet related action, it will have a more complete set of information and you don't need to initiate a splice for it to have the channel information

Copy link
Collaborator

Choose a reason for hiding this comment

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

I still think this one is good to keep so if you're doing any other wallet related action, it will have a more complete set of information and you don't need to initiate a splice for it to have the channel information

Could you provide an example for what else we would use this information? If we don't need it I'd prefer to drop this change to declutter the logic here and avoid the extra persistence roundtrip.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay yeah on further reflection, no real use case, especially with the api that ldk-node exposes to the wallet.

src/event.rs Outdated
Comment on lines 1462 to 1467
let chans =
self.channel_manager.list_channels_with_counterparty(&counterparty_node_id);
let chan_output = chans
.iter()
.find(|c| c.user_channel_id == user_channel_id)
.and_then(|c| c.get_funding_output());
Copy link
Contributor

Choose a reason for hiding this comment

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

Doing this in ChannelReady wouldn't work for splicing preexisting channels. Wonder if we should instead do this when initiating a splice? There we already look up the channel. It would also let us use the real shared_input when selecting UTXOs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Doing this in ChannelReady wouldn't work for splicing preexisting channels.

I dont think this is right, in the docs it says we get a ChannelReady for splices, also my test seems to confirm

Copy link
Contributor

Choose a reason for hiding this comment

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

Correct, we do get it for spliced channels, but we need the initial funding output because that is the one being spent. And for channels existing before this change, we would have already processed the ChannelReady event for the initial funding.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah i see for backward compat sake, added!

src/lib.rs Outdated
let shared_output = bitcoin::TxOut {
value: shared_input.previous_utxo.value + Amount::from_sat(splice_amount_sats),
script_pubkey: make_funding_redeemscript(&dummy_pubkey, &dummy_pubkey).to_p2wsh(),
script_pubkey: funding_output.script_pubkey,
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 still be the dummy one or note that it is not really the shared output's script_pubkey. I guess the amount is already fake since we don't know what the counterparty will contribute.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added a comment

…lices

LDK gives us the actual funding output so we no longer need to create a dummy one with fake pubkeys
Copy link
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

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

Excuse the delay here

src/event.rs Outdated
);
},
Some(output) => {
if let Err(e) = self.wallet.insert_txo(funding_txo, output) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I still think this one is good to keep so if you're doing any other wallet related action, it will have a more complete set of information and you don't need to initiate a splice for it to have the channel information

Could you provide an example for what else we would use this information? If we don't need it I'd prefer to drop this change to declutter the logic here and avoid the extra persistence roundtrip.

node_b.splice_in(&user_channel_id_b, node_a.node_id(), 4_000_000).unwrap();

expect_splice_pending_event!(node_a, node_b.node_id());
let txo = expect_splice_pending_event!(node_a, node_b.node_id());
Copy link
Collaborator

Choose a reason for hiding this comment

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

The bug/issue only exists with bitcoind syncing so to do so, we'd have to change the test to sync with bitcoind rpc. Is that wanted?

Sure, feel free to do so. Alternatively, you could also add a _bitcoind variant test case (ofc reusing the logic) so we have two test cases for bitcoind and esplora - that's if we think there would be more chain source specific edge cases.

@benthecarman
Copy link
Contributor Author

Addressed review, ended up removing it on events and just doing it before a splice. Made sure test coverage hits on both backends and for splice in and out

We insert a channel's funding utxo into our wallet so we can later
calculate the fees for the transaction, otherwise our wallet would have
incomplete information. We do it before the splice as we only really
need this information for splices and not for all channels.
Exposes the funding_redeem_script that LDK already exposes
Copy link
Collaborator

@tnull tnull left a comment

Choose a reason for hiding this comment

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

LGTM, one nit. Otherwise should be ready pending @jkczyz's review.


#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn splice_channel() {
run_splice_channel_test(false).await;
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: Not that it matters much, but elsewhere in LDK Node / LDK tests the usual pattern would be to name the 'inner' test methods do_splice_channel. Feel free to make that rename if you touch the code again.

@tnull tnull merged commit bbefa73 into lightningdevkit:main Dec 16, 2025
18 checks passed
@benthecarman benthecarman deleted the add-chan-to-wallet branch December 16, 2025 09:02
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