@@ -1748,7 +1748,7 @@ pub(super) trait InteractivelyFunded<SP: Deref> where SP::Target: SignerProvider
1748
1748
1749
1749
maybe_add_funding_change_output(signer_provider, self.is_initiator(), self.dual_funding_context().our_funding_satoshis,
1750
1750
&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(
1752
1752
|_| APIError::APIMisuseError { err: "Could not create change output".to_string() })?;
1753
1753
1754
1754
let constructor_args = InteractiveTxConstructorArgs {
@@ -4253,13 +4253,11 @@ fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satos
4253
4253
}
4254
4254
4255
4255
#[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,
4257
4257
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> {
4263
4261
let our_funding_inputs_weight = funding_inputs_prev_outputs.iter().fold(0u64, |weight, prev_output| {
4264
4262
weight.saturating_add(estimate_input_weight(prev_output).to_wu())
4265
4263
});
@@ -4275,32 +4273,54 @@ pub(super) fn maybe_add_funding_change_output<SP: Deref>(signer_provider: &SP, i
4275
4273
fees_sats = fees_sats.saturating_add(common_fees);
4276
4274
}
4277
4275
4276
+ let total_input_satoshis: u64 = funding_inputs_prev_outputs.iter().map(|out| out.value.to_sat()).sum();
4277
+
4278
4278
let remaining_value = total_input_satoshis
4279
4279
.saturating_sub(our_funding_satoshis)
4280
4280
.saturating_sub(fees_sats);
4281
4281
4282
4282
if remaining_value < holder_dust_limit_satoshis {
4283
4283
Ok(None)
4284
4284
} 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))
4301
4286
}
4302
4287
}
4303
4288
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
+
4304
4324
pub(super) fn calculate_our_funding_satoshis(
4305
4325
is_initiator: bool, funding_inputs: &[(TxIn, TransactionU16LenLimited)],
4306
4326
total_witness_weight: Weight, funding_feerate_sat_per_1000_weight: u32,
@@ -10307,8 +10327,9 @@ mod tests {
10307
10327
use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
10308
10328
use crate::ln::channelmanager::{self, HTLCSource, PaymentId};
10309
10329
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 };
10311
10331
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};
10312
10333
use crate::types::features::{ChannelFeatures, ChannelTypeFeatures, NodeFeatures};
10313
10334
use crate::ln::msgs;
10314
10335
use crate::ln::msgs::{ChannelUpdate, DecodeError, UnsignedChannelUpdate, MAX_VALUE_MSAT};
@@ -12071,4 +12092,46 @@ mod tests {
12071
12092
assert_eq!(node_a_chan.context.channel_state, ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::THEIR_CHANNEL_READY));
12072
12093
assert!(node_a_chan.check_get_channel_ready(0, &&logger).is_some());
12073
12094
}
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
+ }
12074
12137
}
0 commit comments