@@ -2715,6 +2715,7 @@ impl_writeable_tlv_based!(PendingFunding, {
27152715enum FundingNegotiation {
27162716 AwaitingAck {
27172717 context: FundingNegotiationContext,
2718+ change_strategy: ChangeStrategy,
27182719 new_holder_funding_key: PublicKey,
27192720 },
27202721 ConstructingTransaction {
@@ -6547,18 +6548,25 @@ pub(super) struct FundingNegotiationContext {
65476548 /// The funding outputs we will be contributing to the channel.
65486549 #[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
65496550 pub our_funding_outputs: Vec<TxOut>,
6551+ }
6552+
6553+ /// How the funding transaction's change is determined.
6554+ #[derive(Debug)]
6555+ pub(super) enum ChangeStrategy {
6556+ /// The change output, if any, is included in the FundingContribution's outputs.
6557+ FromCoinSelection,
6558+
65506559 /// The change output script. This will be used if needed or -- if not set -- generated using
65516560 /// `SignerProvider::get_destination_script`.
6552- #[allow(dead_code)] // TODO(splicing): Remove once splicing is enabled.
6553- pub change_script: Option<ScriptBuf>,
6561+ LegacyUserProvided(Option<ScriptBuf>),
65546562}
65556563
65566564impl FundingNegotiationContext {
65576565 /// Prepare and start interactive transaction negotiation.
65586566 /// If error occurs, it is caused by our side, not the counterparty.
65596567 fn into_interactive_tx_constructor<SP: Deref, ES: Deref>(
65606568 mut self, context: &ChannelContext<SP>, funding: &FundingScope, signer_provider: &SP,
6561- entropy_source: &ES, holder_node_id: PublicKey,
6569+ entropy_source: &ES, holder_node_id: PublicKey, change_strategy: ChangeStrategy,
65626570 ) -> Result<InteractiveTxConstructor, NegotiationError>
65636571 where
65646572 SP::Target: SignerProvider,
@@ -6583,46 +6591,15 @@ impl FundingNegotiationContext {
65836591 script_pubkey: funding.get_funding_redeemscript().to_p2wsh(),
65846592 };
65856593
6586- // Optionally add change output
6587- let change_value_opt = if !self.our_funding_inputs.is_empty() {
6588- match calculate_change_output_value(
6589- &self,
6590- self.shared_funding_input.is_some(),
6591- &shared_funding_output.script_pubkey,
6592- context.holder_dust_limit_satoshis,
6593- ) {
6594- Ok(change_value_opt) => change_value_opt,
6595- Err(reason) => {
6596- return Err(self.into_negotiation_error(reason));
6597- },
6598- }
6599- } else {
6600- None
6601- };
6602-
6603- if let Some(change_value) = change_value_opt {
6604- let change_script = if let Some(script) = self.change_script {
6605- script
6606- } else {
6607- match signer_provider.get_destination_script(context.channel_keys_id) {
6608- Ok(script) => script,
6609- Err(_) => {
6610- let reason = AbortReason::InternalError("Error getting change script");
6611- return Err(self.into_negotiation_error(reason));
6612- },
6613- }
6614- };
6615- let mut change_output = TxOut { value: change_value, script_pubkey: change_script };
6616- let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
6617- let change_output_fee =
6618- fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
6619- let change_value_decreased_with_fee =
6620- change_value.to_sat().saturating_sub(change_output_fee);
6621- // Check dust limit again
6622- if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
6623- change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6624- self.our_funding_outputs.push(change_output);
6625- }
6594+ match self.calculate_change_output(
6595+ context,
6596+ signer_provider,
6597+ &shared_funding_output,
6598+ change_strategy,
6599+ ) {
6600+ Ok(Some(change_output)) => self.our_funding_outputs.push(change_output),
6601+ Ok(None) => {},
6602+ Err(reason) => return Err(self.into_negotiation_error(reason)),
66266603 }
66276604
66286605 let constructor_args = InteractiveTxConstructorArgs {
@@ -6644,6 +6621,55 @@ impl FundingNegotiationContext {
66446621 InteractiveTxConstructor::new(constructor_args)
66456622 }
66466623
6624+ fn calculate_change_output<SP: Deref>(
6625+ &self, context: &ChannelContext<SP>, signer_provider: &SP, shared_funding_output: &TxOut,
6626+ change_strategy: ChangeStrategy,
6627+ ) -> Result<Option<TxOut>, AbortReason>
6628+ where
6629+ SP::Target: SignerProvider,
6630+ {
6631+ if self.our_funding_inputs.is_empty() {
6632+ return Ok(None);
6633+ }
6634+
6635+ let change_script = match change_strategy {
6636+ ChangeStrategy::FromCoinSelection => return Ok(None),
6637+ ChangeStrategy::LegacyUserProvided(change_script) => change_script,
6638+ };
6639+
6640+ let change_value = calculate_change_output_value(
6641+ &self,
6642+ self.shared_funding_input.is_some(),
6643+ &shared_funding_output.script_pubkey,
6644+ context.holder_dust_limit_satoshis,
6645+ )?;
6646+
6647+ if let Some(change_value) = change_value {
6648+ let change_script = match change_script {
6649+ Some(script) => script,
6650+ None => match signer_provider.get_destination_script(context.channel_keys_id) {
6651+ Ok(script) => script,
6652+ Err(_) => {
6653+ return Err(AbortReason::InternalError("Error getting change script"))
6654+ },
6655+ },
6656+ };
6657+ let mut change_output = TxOut { value: change_value, script_pubkey: change_script };
6658+ let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
6659+ let change_output_fee =
6660+ fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
6661+ let change_value_decreased_with_fee =
6662+ change_value.to_sat().saturating_sub(change_output_fee);
6663+ // Check dust limit again
6664+ if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
6665+ change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6666+ return Ok(Some(change_output));
6667+ }
6668+ }
6669+
6670+ Ok(None)
6671+ }
6672+
66476673 fn into_negotiation_error(self, reason: AbortReason) -> NegotiationError {
66486674 let (contributed_inputs, contributed_outputs) = self.into_contributed_inputs_and_outputs();
66496675 NegotiationError { reason, contributed_inputs, contributed_outputs }
@@ -12128,14 +12154,13 @@ where
1212812154 shared_funding_input: Some(prev_funding_input),
1212912155 our_funding_inputs,
1213012156 our_funding_outputs,
12131- change_script,
1213212157 };
1213312158
12134- self.send_splice_init_internal(context)
12159+ self.send_splice_init_internal(context, ChangeStrategy::LegacyUserProvided(change_script) )
1213512160 }
1213612161
1213712162 fn send_splice_init_internal(
12138- &mut self, context: FundingNegotiationContext,
12163+ &mut self, context: FundingNegotiationContext, change_strategy: ChangeStrategy,
1213912164 ) -> msgs::SpliceInit {
1214012165 debug_assert!(self.pending_splice.is_none());
1214112166 // Rotate the funding pubkey using the prev_funding_txid as a tweak
@@ -12156,8 +12181,11 @@ where
1215612181 let funding_contribution_satoshis = context.our_funding_contribution.to_sat();
1215712182 let locktime = context.funding_tx_locktime.to_consensus_u32();
1215812183
12159- let funding_negotiation =
12160- FundingNegotiation::AwaitingAck { context, new_holder_funding_key: funding_pubkey };
12184+ let funding_negotiation = FundingNegotiation::AwaitingAck {
12185+ context,
12186+ change_strategy,
12187+ new_holder_funding_key: funding_pubkey,
12188+ };
1216112189 self.pending_splice = Some(PendingFunding {
1216212190 funding_negotiation: Some(funding_negotiation),
1216312191 negotiated_candidates: vec![],
@@ -12387,7 +12415,6 @@ where
1238712415 shared_funding_input: Some(prev_funding_input),
1238812416 our_funding_inputs: Vec::new(),
1238912417 our_funding_outputs: Vec::new(),
12390- change_script: None,
1239112418 };
1239212419
1239312420 let mut interactive_tx_constructor = funding_negotiation_context
@@ -12397,6 +12424,8 @@ where
1239712424 signer_provider,
1239812425 entropy_source,
1239912426 holder_node_id.clone(),
12427+ // ChangeStrategy doesn't matter when no inputs are contributed
12428+ ChangeStrategy::FromCoinSelection,
1240012429 )
1240112430 .map_err(|err| {
1240212431 ChannelError::WarnAndDisconnect(format!(
@@ -12451,11 +12480,11 @@ where
1245112480 let pending_splice =
1245212481 self.pending_splice.as_mut().expect("We should have returned an error earlier!");
1245312482 // TODO: Good candidate for a let else statement once MSRV >= 1.65
12454- let funding_negotiation_context =
12455- if let Some(FundingNegotiation::AwaitingAck { context, .. }) =
12483+ let ( funding_negotiation_context, change_strategy) =
12484+ if let Some(FundingNegotiation::AwaitingAck { context, change_strategy, .. }) =
1245612485 pending_splice.funding_negotiation.take()
1245712486 {
12458- context
12487+ ( context, change_strategy)
1245912488 } else {
1246012489 panic!("We should have returned an error earlier!");
1246112490 };
@@ -12467,6 +12496,7 @@ where
1246712496 signer_provider,
1246812497 entropy_source,
1246912498 holder_node_id.clone(),
12499+ change_strategy,
1247012500 )
1247112501 .map_err(|err| {
1247212502 ChannelError::WarnAndDisconnect(format!(
@@ -12497,7 +12527,7 @@ where
1249712527 let (funding_negotiation_context, new_holder_funding_key) = match &pending_splice
1249812528 .funding_negotiation
1249912529 {
12500- Some(FundingNegotiation::AwaitingAck { context, new_holder_funding_key }) => {
12530+ Some(FundingNegotiation::AwaitingAck { context, new_holder_funding_key, .. }) => {
1250112531 (context, new_holder_funding_key)
1250212532 },
1250312533 Some(FundingNegotiation::ConstructingTransaction { .. })
@@ -13460,7 +13490,7 @@ where
1346013490 },
1346113491 };
1346213492 let funding_feerate_per_kw = contribution.feerate().to_sat_per_kwu() as u32;
13463- let (our_funding_inputs, our_funding_outputs, change_script ) = contribution.into_tx_parts();
13493+ let (our_funding_inputs, our_funding_outputs) = contribution.into_tx_parts();
1346413494
1346513495 let context = FundingNegotiationContext {
1346613496 is_initiator,
@@ -13470,10 +13500,9 @@ where
1347013500 shared_funding_input: Some(prev_funding_input),
1347113501 our_funding_inputs,
1347213502 our_funding_outputs,
13473- change_script,
1347413503 };
1347513504
13476- let splice_init = self.send_splice_init_internal(context);
13505+ let splice_init = self.send_splice_init_internal(context, ChangeStrategy::FromCoinSelection );
1347713506 return Ok(Some(StfuResponse::SpliceInit(splice_init)));
1347813507 },
1347913508 #[cfg(any(test, fuzzing))]
@@ -14310,7 +14339,6 @@ where
1431014339 shared_funding_input: None,
1431114340 our_funding_inputs: funding_inputs,
1431214341 our_funding_outputs: Vec::new(),
14313- change_script: None,
1431414342 };
1431514343 let chan = Self {
1431614344 funding,
@@ -14464,7 +14492,6 @@ where
1446414492 shared_funding_input: None,
1446514493 our_funding_inputs: our_funding_inputs.clone(),
1446614494 our_funding_outputs: Vec::new(),
14467- change_script: None,
1446814495 };
1446914496 let shared_funding_output = TxOut {
1447014497 value: Amount::from_sat(funding.get_value_satoshis()),
0 commit comments