@@ -6002,10 +6002,8 @@ impl FundingNegotiationContext {
60026002 debug_assert!(matches!(context.channel_state, ChannelState::NegotiatingFunding(_)));
60036003 }
60046004
6005- // Add output for funding tx
60066005 // Note: For the error case when the inputs are insufficient, it will be handled after
60076006 // the `calculate_change_output_value` call below
6008- let mut funding_outputs = Vec::new();
60096007
60106008 let shared_funding_output = TxOut {
60116009 value: Amount::from_sat(funding.get_value_satoshis()),
@@ -6017,18 +6015,27 @@ impl FundingNegotiationContext {
60176015 &self,
60186016 self.shared_funding_input.is_some(),
60196017 &shared_funding_output.script_pubkey,
6020- &funding_outputs,
60216018 context.holder_dust_limit_satoshis,
60226019 )?
60236020 } else {
60246021 None
60256022 };
60266023
6027- let (inputs_to_contribute, change_script) = match self.funding_tx_contributions {
6028- FundingTxContributions::InputsOnly { inputs, change_script } => {
6029- (inputs.into_iter().map(|(txin, tx, _)| (txin, tx)).collect(), change_script)
6030- },
6031- };
6024+ let (funding_inputs, mut funding_outputs, change_script) =
6025+ match self.funding_tx_contributions {
6026+ FundingTxContributions::InputsOnly { inputs, change_script } => (
6027+ inputs.into_iter().map(|(txin, tx, _)| (txin, tx)).collect(),
6028+ vec![],
6029+ change_script,
6030+ ),
6031+ FundingTxContributions::OutputsOnly { outputs } => (vec![], outputs, None),
6032+ #[cfg(test)]
6033+ FundingTxContributions::InputsAndOutputs { inputs, outputs, change_script } => (
6034+ inputs.into_iter().map(|(txin, tx, _)| (txin, tx)).collect(),
6035+ outputs,
6036+ change_script,
6037+ ),
6038+ };
60326039
60336040 // Add change output if necessary
60346041 if let Some(change_value) = change_value_opt {
@@ -6062,7 +6069,7 @@ impl FundingNegotiationContext {
60626069 feerate_sat_per_kw: self.funding_feerate_sat_per_1000_weight,
60636070 is_initiator: self.is_initiator,
60646071 funding_tx_locktime: self.funding_tx_locktime,
6065- inputs_to_contribute,
6072+ inputs_to_contribute: funding_inputs ,
60666073 shared_funding_input: self.shared_funding_input,
60676074 shared_funding_output: SharedOwnedOutput::new(
60686075 shared_funding_output,
@@ -6083,6 +6090,26 @@ pub enum FundingTxContributions {
60836090 /// change output.
60846091 inputs: Vec<(TxIn, Transaction, Weight)>,
60856092
6093+ /// An optional change output script. This will be used if needed or, if not set, generated
6094+ /// using `SignerProvider::get_destination_script`.
6095+ change_script: Option<ScriptBuf>,
6096+ },
6097+ /// When only outputs are contributed to then funding transaction. This must correspond to a
6098+ /// negative contribution amount.
6099+ OutputsOnly {
6100+ /// The outputs used for removing an amount.
6101+ outputs: Vec<TxOut>,
6102+ },
6103+ /// When both inputs and outputs are contributed to the funding transaction.
6104+ #[cfg(test)]
6105+ InputsAndOutputs {
6106+ /// The inputs used to meet the contributed amount. Any excess amount will be sent to a
6107+ /// change output.
6108+ inputs: Vec<(TxIn, Transaction, Weight)>,
6109+
6110+ /// The outputs used for removing an amount.
6111+ outputs: Vec<TxOut>,
6112+
60866113 /// An optional change output script. This will be used if needed or, if not set, generated
60876114 /// using `SignerProvider::get_destination_script`.
60886115 change_script: Option<ScriptBuf>,
@@ -6094,8 +6121,26 @@ impl FundingTxContributions {
60946121 pub fn inputs(&self) -> &[(TxIn, Transaction, Weight)] {
60956122 match self {
60966123 FundingTxContributions::InputsOnly { inputs, .. } => &inputs[..],
6124+ FundingTxContributions::OutputsOnly { .. } => &[],
6125+ #[cfg(test)]
6126+ FundingTxContributions::InputsAndOutputs { inputs, .. } => &inputs[..],
60976127 }
60986128 }
6129+
6130+ /// Returns an inputs to be contributed to the funding transaction.
6131+ pub fn outputs(&self) -> &[TxOut] {
6132+ match self {
6133+ FundingTxContributions::InputsOnly { .. } => &[],
6134+ FundingTxContributions::OutputsOnly { outputs } => &outputs[..],
6135+ #[cfg(test)]
6136+ FundingTxContributions::InputsAndOutputs { outputs, .. } => &outputs[..],
6137+ }
6138+ }
6139+
6140+ /// Returns the sum of the output amounts.
6141+ pub fn amount_removed(&self) -> Amount {
6142+ self.outputs().iter().map(|txout| txout.value).sum()
6143+ }
60996144}
61006145
61016146// Holder designates channel data owned for the benefit of the user client.
@@ -10666,43 +10711,90 @@ where
1066610711 if our_funding_contribution > SignedAmount::MAX_MONEY {
1066710712 return Err(APIError::APIMisuseError {
1066810713 err: format!(
10669- "Channel {} cannot be spliced; contribution exceeds total bitcoin supply: {}",
10714+ "Channel {} cannot be spliced in ; contribution exceeds total bitcoin supply: {}",
1067010715 self.context.channel_id(),
1067110716 our_funding_contribution,
1067210717 ),
1067310718 });
1067410719 }
1067510720
10676- if our_funding_contribution < SignedAmount::ZERO {
10721+ if our_funding_contribution < - SignedAmount::MAX_MONEY {
1067710722 return Err(APIError::APIMisuseError {
1067810723 err: format!(
10679- "TODO(splicing): Splice-out not supported, only splice in; channel ID {}, contribution {}",
10680- self.context.channel_id(), our_funding_contribution,
10681- ),
10724+ "Channel {} cannot be spliced out; contribution exceeds total bitcoin supply: {}",
10725+ self.context.channel_id(),
10726+ our_funding_contribution,
10727+ ),
10728+ });
10729+ }
10730+
10731+ let funding_inputs = funding_tx_contributions.inputs();
10732+ let funding_outputs = funding_tx_contributions.outputs();
10733+ if !funding_inputs.is_empty() && !funding_outputs.is_empty() {
10734+ return Err(APIError::APIMisuseError {
10735+ err: format!(
10736+ "Channel {} cannot be both spliced in and out; operation not supported",
10737+ self.context.channel_id(),
10738+ ),
1068210739 });
1068310740 }
1068410741
10685- // TODO(splicing): Once splice-out is supported, check that channel balance does not go below 0
10686- // (or below channel reserve)
10742+ if our_funding_contribution < SignedAmount::ZERO {
10743+ // TODO(splicing): Check that channel balance does not go below the channel reserve
10744+ let post_channel_value = AddSigned::checked_add_signed(
10745+ self.funding.get_value_satoshis(),
10746+ our_funding_contribution_satoshis,
10747+ );
10748+ // FIXME: Should we check value_to_self instead? Do HTLCs need to be accounted for?
10749+ // FIXME: Check that we can pay for the outputs from the channel value?
10750+ if post_channel_value.is_none() {
10751+ return Err(APIError::APIMisuseError {
10752+ err: format!(
10753+ "Channel {} cannot be spliced out; contribution exceeds the channel value: {}",
10754+ self.context.channel_id(),
10755+ our_funding_contribution,
10756+ ),
10757+ });
10758+ }
1068710759
10688- // Note: post-splice channel value is not yet known at this point, counterparty contribution is not known
10689- // (Cannot test for miminum required post-splice channel value)
10760+ let amount_removed =
10761+ funding_tx_contributions.amount_removed().to_signed().map_err(|_| {
10762+ APIError::APIMisuseError {
10763+ err: format!(
10764+ "Channel {} cannot be spliced out; txout amounts invalid",
10765+ self.context.channel_id(),
10766+ ),
10767+ }
10768+ })?;
10769+ if -amount_removed != our_funding_contribution {
10770+ return Err(APIError::APIMisuseError {
10771+ err: format!(
10772+ "Channel {} cannot be spliced out; unexpected txout amounts: {}",
10773+ self.context.channel_id(),
10774+ amount_removed,
10775+ ),
10776+ });
10777+ }
10778+ } else {
10779+ // Note: post-splice channel value is not yet known at this point, counterparty contribution is not known
10780+ // (Cannot test for miminum required post-splice channel value)
1069010781
10691- // Check that inputs are sufficient to cover our contribution.
10692- let _fee = check_v2_funding_inputs_sufficient(
10693- our_funding_contribution.to_sat(),
10694- funding_tx_contributions.inputs(),
10695- true,
10696- true,
10697- funding_feerate_per_kw,
10698- )
10699- .map_err(|err| APIError::APIMisuseError {
10700- err: format!(
10701- "Insufficient inputs for splicing; channel ID {}, err {}",
10702- self.context.channel_id(),
10703- err,
10704- ),
10705- })?;
10782+ // Check that inputs are sufficient to cover our contribution.
10783+ let _fee = check_v2_funding_inputs_sufficient(
10784+ our_funding_contribution.to_sat(),
10785+ funding_tx_contributions.inputs(),
10786+ true,
10787+ true,
10788+ funding_feerate_per_kw,
10789+ )
10790+ .map_err(|err| APIError::APIMisuseError {
10791+ err: format!(
10792+ "Insufficient inputs for splicing; channel ID {}, err {}",
10793+ self.context.channel_id(),
10794+ err,
10795+ ),
10796+ })?;
10797+ }
1070610798
1070710799 for (_, tx, _) in funding_tx_contributions.inputs().iter() {
1070810800 const MESSAGE_TEMPLATE: msgs::TxAddInput = msgs::TxAddInput {
0 commit comments