@@ -1748,7 +1748,7 @@ pub(super) trait InteractivelyFunded<SP: Deref> where SP::Target: SignerProvider
17481748
17491749 maybe_add_funding_change_output(signer_provider, self.is_initiator(), self.dual_funding_context().our_funding_satoshis,
17501750 &funding_inputs_prev_outputs, &mut funding_outputs, self.dual_funding_context().funding_feerate_sat_per_1000_weight,
1751- total_input_satoshis, self.context().holder_dust_limit_satoshis, self.context().channel_keys_id).map_err(
1751+ self.context().holder_dust_limit_satoshis, self.context().channel_keys_id).map_err(
17521752 |_| APIError::APIMisuseError { err: "Could not create change output".to_string() })?;
17531753
17541754 let constructor_args = InteractiveTxConstructorArgs {
@@ -4253,13 +4253,11 @@ fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satos
42534253}
42544254
42554255#[allow(dead_code)] // TODO(dual_funding): Remove once begin_interactive_funding_tx_construction() is used
4256- pub(super) fn maybe_add_funding_change_output<SP: Deref>(signer_provider: &SP, is_initiator: bool,
4256+ fn need_to_add_funding_change_output( is_initiator: bool,
42574257 our_funding_satoshis: u64, funding_inputs_prev_outputs: &Vec<TxOut>,
4258- funding_outputs: &mut Vec<OutputOwned>, funding_feerate_sat_per_1000_weight: u32,
4259- total_input_satoshis: u64, holder_dust_limit_satoshis: u64, channel_keys_id: [u8; 32],
4260- ) -> Result<Option<TxOut>, ChannelError> where
4261- SP::Target: SignerProvider,
4262- {
4258+ funding_outputs: &Vec<OutputOwned>, funding_feerate_sat_per_1000_weight: u32,
4259+ holder_dust_limit_satoshis: u64,
4260+ ) -> Result<Option<u64>, ChannelError> {
42634261 let our_funding_inputs_weight = funding_inputs_prev_outputs.iter().fold(0u64, |weight, prev_output| {
42644262 weight.saturating_add(estimate_input_weight(prev_output).to_wu())
42654263 });
@@ -4275,32 +4273,54 @@ pub(super) fn maybe_add_funding_change_output<SP: Deref>(signer_provider: &SP, i
42754273 fees_sats = fees_sats.saturating_add(common_fees);
42764274 }
42774275
4276+ let total_input_satoshis: u64 = funding_inputs_prev_outputs.iter().map(|out| out.value.to_sat()).sum();
4277+
42784278 let remaining_value = total_input_satoshis
42794279 .saturating_sub(our_funding_satoshis)
42804280 .saturating_sub(fees_sats);
42814281
42824282 if remaining_value < holder_dust_limit_satoshis {
42834283 Ok(None)
42844284 } else {
4285- let change_script = signer_provider.get_destination_script(channel_keys_id).map_err(
4286- |_| ChannelError::Close((
4287- "Failed to get change script as new destination script".to_owned(),
4288- ClosureReason::ProcessingError { err: "Failed to get change script as new destination script".to_owned() }
4289- ))
4290- )?;
4291- let mut change_output = TxOut {
4292- value: Amount::from_sat(remaining_value),
4293- script_pubkey: change_script,
4294- };
4295- let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
4296-
4297- let change_output_fee = fee_for_weight(funding_feerate_sat_per_1000_weight, change_output_weight);
4298- change_output.value = Amount::from_sat(remaining_value.saturating_sub(change_output_fee));
4299- funding_outputs.push(OutputOwned::Single(change_output.clone()));
4300- Ok(Some(change_output))
4285+ Ok(Some(remaining_value))
43014286 }
43024287}
43034288
4289+ #[allow(dead_code)] // TODO(dual_funding): Remove once begin_interactive_funding_tx_construction() is used
4290+ fn maybe_add_funding_change_output<SP: Deref>(signer_provider: &SP, is_initiator: bool,
4291+ our_funding_satoshis: u64, funding_inputs_prev_outputs: &Vec<TxOut>,
4292+ funding_outputs: &mut Vec<OutputOwned>, funding_feerate_sat_per_1000_weight: u32,
4293+ holder_dust_limit_satoshis: u64, channel_keys_id: [u8; 32],
4294+ ) -> Result<Option<TxOut>, ChannelError> where SP::Target: SignerProvider {
4295+ let remaining_value = match need_to_add_funding_change_output(
4296+ is_initiator, our_funding_satoshis, funding_inputs_prev_outputs,
4297+ funding_outputs, funding_feerate_sat_per_1000_weight, holder_dust_limit_satoshis
4298+ )? {
4299+ None => {
4300+ // No need to add
4301+ return Ok(None);
4302+ }
4303+ Some(remaining_value) => remaining_value,
4304+ };
4305+
4306+ let change_script = signer_provider.get_destination_script(channel_keys_id).map_err(
4307+ |_| ChannelError::Close((
4308+ "Failed to get change script as new destination script".to_owned(),
4309+ ClosureReason::ProcessingError { err: "Failed to get change script as new destination script".to_owned() }
4310+ ))
4311+ )?;
4312+ let mut change_output = TxOut {
4313+ value: Amount::from_sat(remaining_value),
4314+ script_pubkey: change_script,
4315+ };
4316+ let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
4317+
4318+ let change_output_fee = fee_for_weight(funding_feerate_sat_per_1000_weight, change_output_weight);
4319+ change_output.value = Amount::from_sat(remaining_value.saturating_sub(change_output_fee));
4320+ funding_outputs.push(OutputOwned::Single(change_output.clone()));
4321+ Ok(Some(change_output))
4322+ }
4323+
43044324pub(super) fn calculate_our_funding_satoshis(
43054325 is_initiator: bool, funding_inputs: &[(TxIn, TransactionU16LenLimited)],
43064326 total_witness_weight: Weight, funding_feerate_sat_per_1000_weight: u32,
@@ -10307,8 +10327,9 @@ mod tests {
1030710327 use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
1030810328 use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
1030910329 use crate::ln::channel::InitFeatures;
10310- use crate::ln::channel::{AwaitingChannelReadyFlags, Channel, ChannelState, InboundHTLCOutput, OutboundV1Channel, InboundV1Channel, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator, HTLCUpdateAwaitingACK, commit_tx_fee_sat};
10330+ use crate::ln::channel::{AwaitingChannelReadyFlags, Channel, ChannelState, InboundHTLCOutput, OutboundV1Channel, InboundV1Channel, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator, HTLCUpdateAwaitingACK, commit_tx_fee_sat, need_to_add_funding_change_output };
1031110331 use crate::ln::channel::{MAX_FUNDING_SATOSHIS_NO_WUMBO, TOTAL_BITCOIN_SUPPLY_SATOSHIS, MIN_THEIR_CHAN_RESERVE_SATOSHIS};
10332+ use crate::ln::interactivetxs::{OutputOwned, SharedOwnedOutput};
1031210333 use crate::types::features::{ChannelFeatures, ChannelTypeFeatures, NodeFeatures};
1031310334 use crate::ln::msgs;
1031410335 use crate::ln::msgs::{ChannelUpdate, DecodeError, UnsignedChannelUpdate, MAX_VALUE_MSAT};
@@ -12071,4 +12092,46 @@ mod tests {
1207112092 assert_eq!(node_a_chan.context.channel_state, ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::THEIR_CHANNEL_READY));
1207212093 assert!(node_a_chan.check_get_channel_ready(0, &&logger).is_some());
1207312094 }
12095+
12096+ #[test]
12097+ fn test_need_to_add_funding_change_output() {
12098+ let prevouts = vec![
12099+ TxOut { value: Amount::from_sat(70_000), script_pubkey: ScriptBuf::new()},
12100+ TxOut { value: Amount::from_sat(60_000), script_pubkey: ScriptBuf::new()},
12101+ ];
12102+ let txout = TxOut { value: Amount::from_sat(125_000), script_pubkey: ScriptBuf::new()};
12103+ let outputs = vec![OutputOwned::Shared(SharedOwnedOutput::new(txout, 105_000))];
12104+ let our_contributed = 110_000;
12105+ let funding_feerate_sat_per_1000_weight = 3000;
12106+
12107+ let total_inputs: u64 = prevouts.iter().map(|o| o.value.to_sat()).sum();
12108+ let gross_change = total_inputs - our_contributed;
12109+ let fees = 1746;
12110+ let common_fees = 126;
12111+ {
12112+ // There is leftover for change
12113+ let res = need_to_add_funding_change_output(true, our_contributed, &prevouts, &outputs, funding_feerate_sat_per_1000_weight, 300);
12114+ assert_eq!(res.unwrap().unwrap(), gross_change - fees - common_fees);
12115+ }
12116+ {
12117+ // There is leftover for change, without common fees
12118+ let res = need_to_add_funding_change_output(false, our_contributed, &prevouts, &outputs, funding_feerate_sat_per_1000_weight, 300);
12119+ assert_eq!(res.unwrap().unwrap(), gross_change - fees);
12120+ }
12121+ {
12122+ // Insufficient inputs, no leftover
12123+ let res = need_to_add_funding_change_output(false, 130_000, &prevouts, &outputs, funding_feerate_sat_per_1000_weight, 300);
12124+ assert!(res.unwrap().is_none());
12125+ }
12126+ {
12127+ // Very small leftover
12128+ let res = need_to_add_funding_change_output(false, 128_100, &prevouts, &outputs, funding_feerate_sat_per_1000_weight, 300);
12129+ assert!(res.unwrap().is_none());
12130+ }
12131+ {
12132+ // Small leftover, but not dust
12133+ let res = need_to_add_funding_change_output(false, 128_100, &prevouts, &outputs, funding_feerate_sat_per_1000_weight, 100);
12134+ assert_eq!(res.unwrap().unwrap(), 154);
12135+ }
12136+ }
1207412137}
0 commit comments