@@ -6001,10 +6001,8 @@ impl FundingNegotiationContext {
6001
6001
debug_assert!(matches!(context.channel_state, ChannelState::NegotiatingFunding(_)));
6002
6002
}
6003
6003
6004
- // Add output for funding tx
6005
6004
// Note: For the error case when the inputs are insufficient, it will be handled after
6006
6005
// the `calculate_change_output_value` call below
6007
- let mut funding_outputs = Vec::new();
6008
6006
6009
6007
let shared_funding_output = TxOut {
6010
6008
value: Amount::from_sat(funding.get_value_satoshis()),
@@ -6016,18 +6014,27 @@ impl FundingNegotiationContext {
6016
6014
&self,
6017
6015
self.shared_funding_input.is_some(),
6018
6016
&shared_funding_output.script_pubkey,
6019
- &funding_outputs,
6020
6017
context.holder_dust_limit_satoshis,
6021
6018
)?
6022
6019
} else {
6023
6020
None
6024
6021
};
6025
6022
6026
- let (inputs_to_contribute, change_script) = match self.funding_tx_contributions {
6027
- FundingTxContributions::InputsOnly { inputs, change_script } => {
6028
- (inputs.into_iter().map(|(txin, tx, _)| (txin, tx)).collect(), change_script)
6029
- },
6030
- };
6023
+ let (funding_inputs, mut funding_outputs, change_script) =
6024
+ match self.funding_tx_contributions {
6025
+ FundingTxContributions::InputsOnly { inputs, change_script } => (
6026
+ inputs.into_iter().map(|(txin, tx, _)| (txin, tx)).collect(),
6027
+ vec![],
6028
+ change_script,
6029
+ ),
6030
+ FundingTxContributions::OutputsOnly { outputs } => (vec![], outputs, None),
6031
+ #[cfg(test)]
6032
+ FundingTxContributions::InputsAndOutputs { inputs, outputs, change_script } => (
6033
+ inputs.into_iter().map(|(txin, tx, _)| (txin, tx)).collect(),
6034
+ outputs,
6035
+ change_script,
6036
+ ),
6037
+ };
6031
6038
6032
6039
// Add change output if necessary
6033
6040
if let Some(change_value) = change_value_opt {
@@ -6061,7 +6068,7 @@ impl FundingNegotiationContext {
6061
6068
feerate_sat_per_kw: self.funding_feerate_sat_per_1000_weight,
6062
6069
is_initiator: self.is_initiator,
6063
6070
funding_tx_locktime: self.funding_tx_locktime,
6064
- inputs_to_contribute,
6071
+ inputs_to_contribute: funding_inputs ,
6065
6072
shared_funding_input: self.shared_funding_input,
6066
6073
shared_funding_output: SharedOwnedOutput::new(
6067
6074
shared_funding_output,
@@ -6082,6 +6089,26 @@ pub enum FundingTxContributions {
6082
6089
/// change output.
6083
6090
inputs: Vec<(TxIn, Transaction, Weight)>,
6084
6091
6092
+ /// An optional change output script. This will be used if needed or, if not set, generated
6093
+ /// using `SignerProvider::get_destination_script`.
6094
+ change_script: Option<ScriptBuf>,
6095
+ },
6096
+ /// When only outputs are contributed to then funding transaction. This must correspond to a
6097
+ /// negative contribution amount.
6098
+ OutputsOnly {
6099
+ /// The outputs used for removing an amount.
6100
+ outputs: Vec<TxOut>,
6101
+ },
6102
+ /// When both inputs and outputs are contributed to the funding transaction.
6103
+ #[cfg(test)]
6104
+ InputsAndOutputs {
6105
+ /// The inputs used to meet the contributed amount. Any excess amount will be sent to a
6106
+ /// change output.
6107
+ inputs: Vec<(TxIn, Transaction, Weight)>,
6108
+
6109
+ /// The outputs used for removing an amount.
6110
+ outputs: Vec<TxOut>,
6111
+
6085
6112
/// An optional change output script. This will be used if needed or, if not set, generated
6086
6113
/// using `SignerProvider::get_destination_script`.
6087
6114
change_script: Option<ScriptBuf>,
@@ -6093,8 +6120,26 @@ impl FundingTxContributions {
6093
6120
pub fn inputs(&self) -> &[(TxIn, Transaction, Weight)] {
6094
6121
match self {
6095
6122
FundingTxContributions::InputsOnly { inputs, .. } => &inputs[..],
6123
+ FundingTxContributions::OutputsOnly { .. } => &[],
6124
+ #[cfg(test)]
6125
+ FundingTxContributions::InputsAndOutputs { inputs, .. } => &inputs[..],
6096
6126
}
6097
6127
}
6128
+
6129
+ /// Returns an inputs to be contributed to the funding transaction.
6130
+ pub fn outputs(&self) -> &[TxOut] {
6131
+ match self {
6132
+ FundingTxContributions::InputsOnly { .. } => &[],
6133
+ FundingTxContributions::OutputsOnly { outputs } => &outputs[..],
6134
+ #[cfg(test)]
6135
+ FundingTxContributions::InputsAndOutputs { outputs, .. } => &outputs[..],
6136
+ }
6137
+ }
6138
+
6139
+ /// Returns the sum of the output amounts.
6140
+ pub fn amount_removed(&self) -> Amount {
6141
+ self.outputs().iter().map(|txout| txout.value).sum()
6142
+ }
6098
6143
}
6099
6144
6100
6145
// Holder designates channel data owned for the benefit of the user client.
@@ -10665,43 +10710,90 @@ where
10665
10710
if our_funding_contribution > SignedAmount::MAX_MONEY {
10666
10711
return Err(APIError::APIMisuseError {
10667
10712
err: format!(
10668
- "Channel {} cannot be spliced; contribution exceeds total bitcoin supply: {}",
10713
+ "Channel {} cannot be spliced in ; contribution exceeds total bitcoin supply: {}",
10669
10714
self.context.channel_id(),
10670
10715
our_funding_contribution,
10671
10716
),
10672
10717
});
10673
10718
}
10674
10719
10675
- if our_funding_contribution < SignedAmount::ZERO {
10720
+ if our_funding_contribution < - SignedAmount::MAX_MONEY {
10676
10721
return Err(APIError::APIMisuseError {
10677
10722
err: format!(
10678
- "TODO(splicing): Splice-out not supported, only splice in; channel ID {}, contribution {}",
10679
- self.context.channel_id(), our_funding_contribution,
10680
- ),
10723
+ "Channel {} cannot be spliced out; contribution exceeds total bitcoin supply: {}",
10724
+ self.context.channel_id(),
10725
+ our_funding_contribution,
10726
+ ),
10727
+ });
10728
+ }
10729
+
10730
+ let funding_inputs = funding_tx_contributions.inputs();
10731
+ let funding_outputs = funding_tx_contributions.outputs();
10732
+ if !funding_inputs.is_empty() && !funding_outputs.is_empty() {
10733
+ return Err(APIError::APIMisuseError {
10734
+ err: format!(
10735
+ "Channel {} cannot be both spliced in and out; operation not supported",
10736
+ self.context.channel_id(),
10737
+ ),
10681
10738
});
10682
10739
}
10683
10740
10684
- // TODO(splicing): Once splice-out is supported, check that channel balance does not go below 0
10685
- // (or below channel reserve)
10741
+ if our_funding_contribution < SignedAmount::ZERO {
10742
+ // TODO(splicing): Check that channel balance does not go below the channel reserve
10743
+ let post_channel_value = AddSigned::checked_add_signed(
10744
+ self.funding.get_value_satoshis(),
10745
+ our_funding_contribution_satoshis,
10746
+ );
10747
+ // FIXME: Should we check value_to_self instead? Do HTLCs need to be accounted for?
10748
+ // FIXME: Check that we can pay for the outputs from the channel value?
10749
+ if post_channel_value.is_none() {
10750
+ return Err(APIError::APIMisuseError {
10751
+ err: format!(
10752
+ "Channel {} cannot be spliced out; contribution exceeds the channel value: {}",
10753
+ self.context.channel_id(),
10754
+ our_funding_contribution,
10755
+ ),
10756
+ });
10757
+ }
10686
10758
10687
- // Note: post-splice channel value is not yet known at this point, counterparty contribution is not known
10688
- // (Cannot test for miminum required post-splice channel value)
10759
+ let amount_removed =
10760
+ funding_tx_contributions.amount_removed().to_signed().map_err(|_| {
10761
+ APIError::APIMisuseError {
10762
+ err: format!(
10763
+ "Channel {} cannot be spliced out; txout amounts invalid",
10764
+ self.context.channel_id(),
10765
+ ),
10766
+ }
10767
+ })?;
10768
+ if -amount_removed != our_funding_contribution {
10769
+ return Err(APIError::APIMisuseError {
10770
+ err: format!(
10771
+ "Channel {} cannot be spliced out; unexpected txout amounts: {}",
10772
+ self.context.channel_id(),
10773
+ amount_removed,
10774
+ ),
10775
+ });
10776
+ }
10777
+ } else {
10778
+ // Note: post-splice channel value is not yet known at this point, counterparty contribution is not known
10779
+ // (Cannot test for miminum required post-splice channel value)
10689
10780
10690
- // Check that inputs are sufficient to cover our contribution.
10691
- let _fee = check_v2_funding_inputs_sufficient(
10692
- our_funding_contribution.to_sat(),
10693
- funding_tx_contributions.inputs(),
10694
- true,
10695
- true,
10696
- funding_feerate_per_kw,
10697
- )
10698
- .map_err(|err| APIError::APIMisuseError {
10699
- err: format!(
10700
- "Insufficient inputs for splicing; channel ID {}, err {}",
10701
- self.context.channel_id(),
10702
- err,
10703
- ),
10704
- })?;
10781
+ // Check that inputs are sufficient to cover our contribution.
10782
+ let _fee = check_v2_funding_inputs_sufficient(
10783
+ our_funding_contribution.to_sat(),
10784
+ funding_tx_contributions.inputs(),
10785
+ true,
10786
+ true,
10787
+ funding_feerate_per_kw,
10788
+ )
10789
+ .map_err(|err| APIError::APIMisuseError {
10790
+ err: format!(
10791
+ "Insufficient inputs for splicing; channel ID {}, err {}",
10792
+ self.context.channel_id(),
10793
+ err,
10794
+ ),
10795
+ })?;
10796
+ }
10705
10797
10706
10798
for (_, tx, _) in funding_tx_contributions.inputs().iter() {
10707
10799
const MESSAGE_TEMPLATE: msgs::TxAddInput = msgs::TxAddInput {
0 commit comments