@@ -2908,6 +2908,7 @@ impl_writeable_tlv_based!(PendingFunding, {
29082908enum FundingNegotiation {
29092909 AwaitingAck {
29102910 context: FundingNegotiationContext,
2911+ change_strategy: ChangeStrategy,
29112912 new_holder_funding_key: PublicKey,
29122913 },
29132914 ConstructingTransaction {
@@ -6680,18 +6681,25 @@ pub(super) struct FundingNegotiationContext {
66806681 /// The funding outputs we will be contributing to the channel.
66816682 #[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
66826683 pub our_funding_outputs: Vec<TxOut>,
6684+ }
6685+
6686+ /// How the funding transaction's change is determined.
6687+ #[derive(Debug)]
6688+ pub(super) enum ChangeStrategy {
6689+ /// The change output, if any, is included in the FundingContribution's outputs.
6690+ FromCoinSelection,
6691+
66836692 /// The change output script. This will be used if needed or -- if not set -- generated using
66846693 /// `SignerProvider::get_destination_script`.
6685- #[allow(dead_code)] // TODO(splicing): Remove once splicing is enabled.
6686- pub change_script: Option<ScriptBuf>,
6694+ LegacyUserProvided(Option<ScriptBuf>),
66876695}
66886696
66896697impl FundingNegotiationContext {
66906698 /// Prepare and start interactive transaction negotiation.
66916699 /// If error occurs, it is caused by our side, not the counterparty.
66926700 fn into_interactive_tx_constructor<SP: SignerProvider, ES: EntropySource>(
66936701 mut self, context: &ChannelContext<SP>, funding: &FundingScope, signer_provider: &SP,
6694- entropy_source: &ES, holder_node_id: PublicKey,
6702+ entropy_source: &ES, holder_node_id: PublicKey, change_strategy: ChangeStrategy,
66956703 ) -> Result<InteractiveTxConstructor, NegotiationError> {
66966704 debug_assert_eq!(
66976705 self.shared_funding_input.is_some(),
@@ -6712,46 +6720,15 @@ impl FundingNegotiationContext {
67126720 script_pubkey: funding.get_funding_redeemscript().to_p2wsh(),
67136721 };
67146722
6715- // Optionally add change output
6716- let change_value_opt = if !self.our_funding_inputs.is_empty() {
6717- match calculate_change_output_value(
6718- &self,
6719- self.shared_funding_input.is_some(),
6720- &shared_funding_output.script_pubkey,
6721- context.holder_dust_limit_satoshis,
6722- ) {
6723- Ok(change_value_opt) => change_value_opt,
6724- Err(reason) => {
6725- return Err(self.into_negotiation_error(reason));
6726- },
6727- }
6728- } else {
6729- None
6730- };
6731-
6732- if let Some(change_value) = change_value_opt {
6733- let change_script = if let Some(script) = self.change_script {
6734- script
6735- } else {
6736- match signer_provider.get_destination_script(context.channel_keys_id) {
6737- Ok(script) => script,
6738- Err(_) => {
6739- let reason = AbortReason::InternalError("Error getting change script");
6740- return Err(self.into_negotiation_error(reason));
6741- },
6742- }
6743- };
6744- let mut change_output = TxOut { value: change_value, script_pubkey: change_script };
6745- let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
6746- let change_output_fee =
6747- fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
6748- let change_value_decreased_with_fee =
6749- change_value.to_sat().saturating_sub(change_output_fee);
6750- // Check dust limit again
6751- if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
6752- change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6753- self.our_funding_outputs.push(change_output);
6754- }
6723+ match self.calculate_change_output(
6724+ context,
6725+ signer_provider,
6726+ &shared_funding_output,
6727+ change_strategy,
6728+ ) {
6729+ Ok(Some(change_output)) => self.our_funding_outputs.push(change_output),
6730+ Ok(None) => {},
6731+ Err(reason) => return Err(self.into_negotiation_error(reason)),
67556732 }
67566733
67576734 let constructor_args = InteractiveTxConstructorArgs {
@@ -6773,6 +6750,52 @@ impl FundingNegotiationContext {
67736750 InteractiveTxConstructor::new(constructor_args)
67746751 }
67756752
6753+ fn calculate_change_output<SP: SignerProvider>(
6754+ &self, context: &ChannelContext<SP>, signer_provider: &SP, shared_funding_output: &TxOut,
6755+ change_strategy: ChangeStrategy,
6756+ ) -> Result<Option<TxOut>, AbortReason> {
6757+ if self.our_funding_inputs.is_empty() {
6758+ return Ok(None);
6759+ }
6760+
6761+ let change_script = match change_strategy {
6762+ ChangeStrategy::FromCoinSelection => return Ok(None),
6763+ ChangeStrategy::LegacyUserProvided(change_script) => change_script,
6764+ };
6765+
6766+ let change_value = calculate_change_output_value(
6767+ &self,
6768+ self.shared_funding_input.is_some(),
6769+ &shared_funding_output.script_pubkey,
6770+ context.holder_dust_limit_satoshis,
6771+ )?;
6772+
6773+ if let Some(change_value) = change_value {
6774+ let change_script = match change_script {
6775+ Some(script) => script,
6776+ None => match signer_provider.get_destination_script(context.channel_keys_id) {
6777+ Ok(script) => script,
6778+ Err(_) => {
6779+ return Err(AbortReason::InternalError("Error getting change script"))
6780+ },
6781+ },
6782+ };
6783+ let mut change_output = TxOut { value: change_value, script_pubkey: change_script };
6784+ let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
6785+ let change_output_fee =
6786+ fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
6787+ let change_value_decreased_with_fee =
6788+ change_value.to_sat().saturating_sub(change_output_fee);
6789+ // Check dust limit again
6790+ if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
6791+ change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6792+ return Ok(Some(change_output));
6793+ }
6794+ }
6795+
6796+ Ok(None)
6797+ }
6798+
67766799 fn into_negotiation_error(self, reason: AbortReason) -> NegotiationError {
67776800 let (contributed_inputs, contributed_outputs) = self.into_contributed_inputs_and_outputs();
67786801 NegotiationError { reason, contributed_inputs, contributed_outputs }
@@ -12235,14 +12258,13 @@ where
1223512258 shared_funding_input: Some(prev_funding_input),
1223612259 our_funding_inputs,
1223712260 our_funding_outputs,
12238- change_script,
1223912261 };
1224012262
12241- self.send_splice_init_internal(context)
12263+ self.send_splice_init_internal(context, ChangeStrategy::LegacyUserProvided(change_script) )
1224212264 }
1224312265
1224412266 fn send_splice_init_internal(
12245- &mut self, context: FundingNegotiationContext,
12267+ &mut self, context: FundingNegotiationContext, change_strategy: ChangeStrategy,
1224612268 ) -> msgs::SpliceInit {
1224712269 debug_assert!(self.pending_splice.is_none());
1224812270 // Rotate the funding pubkey using the prev_funding_txid as a tweak
@@ -12263,8 +12285,11 @@ where
1226312285 let funding_contribution_satoshis = context.our_funding_contribution.to_sat();
1226412286 let locktime = context.funding_tx_locktime.to_consensus_u32();
1226512287
12266- let funding_negotiation =
12267- FundingNegotiation::AwaitingAck { context, new_holder_funding_key: funding_pubkey };
12288+ let funding_negotiation = FundingNegotiation::AwaitingAck {
12289+ context,
12290+ change_strategy,
12291+ new_holder_funding_key: funding_pubkey,
12292+ };
1226812293 self.pending_splice = Some(PendingFunding {
1226912294 funding_negotiation: Some(funding_negotiation),
1227012295 negotiated_candidates: vec![],
@@ -12490,7 +12515,6 @@ where
1249012515 shared_funding_input: Some(prev_funding_input),
1249112516 our_funding_inputs: Vec::new(),
1249212517 our_funding_outputs: Vec::new(),
12493- change_script: None,
1249412518 };
1249512519
1249612520 let mut interactive_tx_constructor = funding_negotiation_context
@@ -12500,6 +12524,8 @@ where
1250012524 signer_provider,
1250112525 entropy_source,
1250212526 holder_node_id.clone(),
12527+ // ChangeStrategy doesn't matter when no inputs are contributed
12528+ ChangeStrategy::FromCoinSelection,
1250312529 )
1250412530 .map_err(|err| {
1250512531 ChannelError::WarnAndDisconnect(format!(
@@ -12550,11 +12576,11 @@ where
1255012576 let pending_splice =
1255112577 self.pending_splice.as_mut().expect("We should have returned an error earlier!");
1255212578 // TODO: Good candidate for a let else statement once MSRV >= 1.65
12553- let funding_negotiation_context =
12554- if let Some(FundingNegotiation::AwaitingAck { context, .. }) =
12579+ let ( funding_negotiation_context, change_strategy) =
12580+ if let Some(FundingNegotiation::AwaitingAck { context, change_strategy, .. }) =
1255512581 pending_splice.funding_negotiation.take()
1255612582 {
12557- context
12583+ ( context, change_strategy)
1255812584 } else {
1255912585 panic!("We should have returned an error earlier!");
1256012586 };
@@ -12566,6 +12592,7 @@ where
1256612592 signer_provider,
1256712593 entropy_source,
1256812594 holder_node_id.clone(),
12595+ change_strategy,
1256912596 )
1257012597 .map_err(|err| {
1257112598 ChannelError::WarnAndDisconnect(format!(
@@ -12596,7 +12623,7 @@ where
1259612623 let (funding_negotiation_context, new_holder_funding_key) = match &pending_splice
1259712624 .funding_negotiation
1259812625 {
12599- Some(FundingNegotiation::AwaitingAck { context, new_holder_funding_key }) => {
12626+ Some(FundingNegotiation::AwaitingAck { context, new_holder_funding_key, .. }) => {
1260012627 (context, new_holder_funding_key)
1260112628 },
1260212629 Some(FundingNegotiation::ConstructingTransaction { .. })
@@ -13523,7 +13550,7 @@ where
1352313550 },
1352413551 };
1352513552 let funding_feerate_per_kw = contribution.feerate().to_sat_per_kwu() as u32;
13526- let (our_funding_inputs, our_funding_outputs, change_script ) = contribution.into_tx_parts();
13553+ let (our_funding_inputs, our_funding_outputs) = contribution.into_tx_parts();
1352713554
1352813555 let context = FundingNegotiationContext {
1352913556 is_initiator,
@@ -13533,10 +13560,9 @@ where
1353313560 shared_funding_input: Some(prev_funding_input),
1353413561 our_funding_inputs,
1353513562 our_funding_outputs,
13536- change_script,
1353713563 };
1353813564
13539- let splice_init = self.send_splice_init_internal(context);
13565+ let splice_init = self.send_splice_init_internal(context, ChangeStrategy::FromCoinSelection );
1354013566 return Ok(Some(StfuResponse::SpliceInit(splice_init)));
1354113567 },
1354213568 #[cfg(any(test, fuzzing))]
@@ -14320,7 +14346,6 @@ impl<SP: SignerProvider> PendingV2Channel<SP> {
1432014346 shared_funding_input: None,
1432114347 our_funding_inputs: funding_inputs,
1432214348 our_funding_outputs: Vec::new(),
14323- change_script: None,
1432414349 };
1432514350 let chan = Self {
1432614351 funding,
@@ -14467,7 +14492,6 @@ impl<SP: SignerProvider> PendingV2Channel<SP> {
1446714492 shared_funding_input: None,
1446814493 our_funding_inputs: our_funding_inputs.clone(),
1446914494 our_funding_outputs: Vec::new(),
14470- change_script: None,
1447114495 };
1447214496 let shared_funding_output = TxOut {
1447314497 value: Amount::from_sat(funding.get_value_satoshis()),
0 commit comments