@@ -2717,6 +2717,7 @@ impl_writeable_tlv_based!(PendingFunding, {
27172717enum FundingNegotiation {
27182718 AwaitingAck {
27192719 context: FundingNegotiationContext,
2720+ change_strategy: ChangeStrategy,
27202721 new_holder_funding_key: PublicKey,
27212722 },
27222723 ConstructingTransaction {
@@ -6541,18 +6542,25 @@ pub(super) struct FundingNegotiationContext {
65416542 /// The funding outputs we will be contributing to the channel.
65426543 #[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
65436544 pub our_funding_outputs: Vec<TxOut>,
6545+ }
6546+
6547+ /// How the funding transaction's change is determined.
6548+ #[derive(Debug)]
6549+ pub(super) enum ChangeStrategy {
6550+ /// The change output, if any, is included in the FundingContribution's outputs.
6551+ FromCoinSelection,
6552+
65446553 /// The change output script. This will be used if needed or -- if not set -- generated using
65456554 /// `SignerProvider::get_destination_script`.
6546- #[allow(dead_code)] // TODO(splicing): Remove once splicing is enabled.
6547- pub change_script: Option<ScriptBuf>,
6555+ LegacyUserProvided(Option<ScriptBuf>),
65486556}
65496557
65506558impl FundingNegotiationContext {
65516559 /// Prepare and start interactive transaction negotiation.
65526560 /// If error occurs, it is caused by our side, not the counterparty.
65536561 fn into_interactive_tx_constructor<SP: Deref, ES: Deref>(
65546562 mut self, context: &ChannelContext<SP>, funding: &FundingScope, signer_provider: &SP,
6555- entropy_source: &ES, holder_node_id: PublicKey,
6563+ entropy_source: &ES, holder_node_id: PublicKey, change_strategy: ChangeStrategy,
65566564 ) -> Result<InteractiveTxConstructor, NegotiationError>
65576565 where
65586566 SP::Target: SignerProvider,
@@ -6577,46 +6585,15 @@ impl FundingNegotiationContext {
65776585 script_pubkey: funding.get_funding_redeemscript().to_p2wsh(),
65786586 };
65796587
6580- // Optionally add change output
6581- let change_value_opt = if !self.our_funding_inputs.is_empty() {
6582- match calculate_change_output_value(
6583- &self,
6584- self.shared_funding_input.is_some(),
6585- &shared_funding_output.script_pubkey,
6586- context.holder_dust_limit_satoshis,
6587- ) {
6588- Ok(change_value_opt) => change_value_opt,
6589- Err(reason) => {
6590- return Err(self.into_negotiation_error(reason));
6591- },
6592- }
6593- } else {
6594- None
6595- };
6596-
6597- if let Some(change_value) = change_value_opt {
6598- let change_script = if let Some(script) = self.change_script {
6599- script
6600- } else {
6601- match signer_provider.get_destination_script(context.channel_keys_id) {
6602- Ok(script) => script,
6603- Err(_) => {
6604- let reason = AbortReason::InternalError("Error getting change script");
6605- return Err(self.into_negotiation_error(reason));
6606- },
6607- }
6608- };
6609- let mut change_output = TxOut { value: change_value, script_pubkey: change_script };
6610- let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
6611- let change_output_fee =
6612- fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
6613- let change_value_decreased_with_fee =
6614- change_value.to_sat().saturating_sub(change_output_fee);
6615- // Check dust limit again
6616- if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
6617- change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6618- self.our_funding_outputs.push(change_output);
6619- }
6588+ match self.calculate_change_output(
6589+ context,
6590+ signer_provider,
6591+ &shared_funding_output,
6592+ change_strategy,
6593+ ) {
6594+ Ok(Some(change_output)) => self.our_funding_outputs.push(change_output),
6595+ Ok(None) => {},
6596+ Err(reason) => return Err(self.into_negotiation_error(reason)),
66206597 }
66216598
66226599 let constructor_args = InteractiveTxConstructorArgs {
@@ -6638,6 +6615,55 @@ impl FundingNegotiationContext {
66386615 InteractiveTxConstructor::new(constructor_args)
66396616 }
66406617
6618+ fn calculate_change_output<SP: Deref>(
6619+ &self, context: &ChannelContext<SP>, signer_provider: &SP, shared_funding_output: &TxOut,
6620+ change_strategy: ChangeStrategy,
6621+ ) -> Result<Option<TxOut>, AbortReason>
6622+ where
6623+ SP::Target: SignerProvider,
6624+ {
6625+ if self.our_funding_inputs.is_empty() {
6626+ return Ok(None);
6627+ }
6628+
6629+ let change_script = match change_strategy {
6630+ ChangeStrategy::FromCoinSelection => return Ok(None),
6631+ ChangeStrategy::LegacyUserProvided(change_script) => change_script,
6632+ };
6633+
6634+ let change_value = calculate_change_output_value(
6635+ &self,
6636+ self.shared_funding_input.is_some(),
6637+ &shared_funding_output.script_pubkey,
6638+ context.holder_dust_limit_satoshis,
6639+ )?;
6640+
6641+ if let Some(change_value) = change_value {
6642+ let change_script = match change_script {
6643+ Some(script) => script,
6644+ None => match signer_provider.get_destination_script(context.channel_keys_id) {
6645+ Ok(script) => script,
6646+ Err(_) => {
6647+ return Err(AbortReason::InternalError("Error getting change script"))
6648+ },
6649+ },
6650+ };
6651+ let mut change_output = TxOut { value: change_value, script_pubkey: change_script };
6652+ let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
6653+ let change_output_fee =
6654+ fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
6655+ let change_value_decreased_with_fee =
6656+ change_value.to_sat().saturating_sub(change_output_fee);
6657+ // Check dust limit again
6658+ if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
6659+ change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6660+ return Ok(Some(change_output));
6661+ }
6662+ }
6663+
6664+ Ok(None)
6665+ }
6666+
66416667 fn into_negotiation_error(self, reason: AbortReason) -> NegotiationError {
66426668 let (contributed_inputs, contributed_outputs) = self.into_contributed_inputs_and_outputs();
66436669 NegotiationError { reason, contributed_inputs, contributed_outputs }
@@ -12045,7 +12071,7 @@ where
1204512071 let prev_funding_input = self.funding.to_splice_funding_input();
1204612072 let is_initiator = contribution.is_initiator();
1204712073 let funding_feerate_per_kw = contribution.feerate().to_sat_per_kwu() as u32;
12048- let (our_funding_inputs, our_funding_outputs, change_script ) = contribution.into_tx_parts();
12074+ let (our_funding_inputs, our_funding_outputs) = contribution.into_tx_parts();
1204912075
1205012076 let context = FundingNegotiationContext {
1205112077 is_initiator,
@@ -12055,10 +12081,9 @@ where
1205512081 shared_funding_input: Some(prev_funding_input),
1205612082 our_funding_inputs,
1205712083 our_funding_outputs,
12058- change_script,
1205912084 };
1206012085
12061- Ok(self.send_splice_init_internal(context))
12086+ Ok(self.send_splice_init_internal(context, ChangeStrategy::FromCoinSelection ))
1206212087 }
1206312088
1206412089 fn send_splice_init(&mut self, instructions: SpliceInstructions) -> msgs::SpliceInit {
@@ -12080,14 +12105,13 @@ where
1208012105 shared_funding_input: Some(prev_funding_input),
1208112106 our_funding_inputs,
1208212107 our_funding_outputs,
12083- change_script,
1208412108 };
1208512109
12086- self.send_splice_init_internal(context)
12110+ self.send_splice_init_internal(context, ChangeStrategy::LegacyUserProvided(change_script) )
1208712111 }
1208812112
1208912113 fn send_splice_init_internal(
12090- &mut self, context: FundingNegotiationContext,
12114+ &mut self, context: FundingNegotiationContext, change_strategy: ChangeStrategy,
1209112115 ) -> msgs::SpliceInit {
1209212116 debug_assert!(self.pending_splice.is_none());
1209312117 // Rotate the funding pubkey using the prev_funding_txid as a tweak
@@ -12108,8 +12132,11 @@ where
1210812132 let funding_contribution_satoshis = context.our_funding_contribution.to_sat();
1210912133 let locktime = context.funding_tx_locktime.to_consensus_u32();
1211012134
12111- let funding_negotiation =
12112- FundingNegotiation::AwaitingAck { context, new_holder_funding_key: funding_pubkey };
12135+ let funding_negotiation = FundingNegotiation::AwaitingAck {
12136+ context,
12137+ change_strategy,
12138+ new_holder_funding_key: funding_pubkey,
12139+ };
1211312140 self.pending_splice = Some(PendingFunding {
1211412141 funding_negotiation: Some(funding_negotiation),
1211512142 negotiated_candidates: vec![],
@@ -12339,7 +12366,6 @@ where
1233912366 shared_funding_input: Some(prev_funding_input),
1234012367 our_funding_inputs: Vec::new(),
1234112368 our_funding_outputs: Vec::new(),
12342- change_script: None,
1234312369 };
1234412370
1234512371 let mut interactive_tx_constructor = funding_negotiation_context
@@ -12349,6 +12375,8 @@ where
1234912375 signer_provider,
1235012376 entropy_source,
1235112377 holder_node_id.clone(),
12378+ // ChangeStrategy doesn't matter when no inputs are contributed
12379+ ChangeStrategy::FromCoinSelection,
1235212380 )
1235312381 .map_err(|err| {
1235412382 ChannelError::WarnAndDisconnect(format!(
@@ -12403,11 +12431,11 @@ where
1240312431 let pending_splice =
1240412432 self.pending_splice.as_mut().expect("We should have returned an error earlier!");
1240512433 // TODO: Good candidate for a let else statement once MSRV >= 1.65
12406- let funding_negotiation_context =
12407- if let Some(FundingNegotiation::AwaitingAck { context, .. }) =
12434+ let ( funding_negotiation_context, change_strategy) =
12435+ if let Some(FundingNegotiation::AwaitingAck { context, change_strategy, .. }) =
1240812436 pending_splice.funding_negotiation.take()
1240912437 {
12410- context
12438+ ( context, change_strategy)
1241112439 } else {
1241212440 panic!("We should have returned an error earlier!");
1241312441 };
@@ -12419,6 +12447,7 @@ where
1241912447 signer_provider,
1242012448 entropy_source,
1242112449 holder_node_id.clone(),
12450+ change_strategy,
1242212451 )
1242312452 .map_err(|err| {
1242412453 ChannelError::WarnAndDisconnect(format!(
@@ -12449,7 +12478,7 @@ where
1244912478 let (funding_negotiation_context, new_holder_funding_key) = match &pending_splice
1245012479 .funding_negotiation
1245112480 {
12452- Some(FundingNegotiation::AwaitingAck { context, new_holder_funding_key }) => {
12481+ Some(FundingNegotiation::AwaitingAck { context, new_holder_funding_key, .. }) => {
1245312482 (context, new_holder_funding_key)
1245412483 },
1245512484 Some(FundingNegotiation::ConstructingTransaction { .. })
@@ -14241,7 +14270,6 @@ where
1424114270 shared_funding_input: None,
1424214271 our_funding_inputs: funding_inputs,
1424314272 our_funding_outputs: Vec::new(),
14244- change_script: None,
1424514273 };
1424614274 let chan = Self {
1424714275 funding,
@@ -14395,7 +14423,6 @@ where
1439514423 shared_funding_input: None,
1439614424 our_funding_inputs: our_funding_inputs.clone(),
1439714425 our_funding_outputs: Vec::new(),
14398- change_script: None,
1439914426 };
1440014427 let shared_funding_output = TxOut {
1440114428 value: Amount::from_sat(funding.get_value_satoshis()),
0 commit comments