@@ -6002,10 +6002,8 @@ impl FundingNegotiationContext {
6002
6002
debug_assert!(matches!(context.channel_state, ChannelState::NegotiatingFunding(_)));
6003
6003
}
6004
6004
6005
- // Add output for funding tx
6006
6005
// Note: For the error case when the inputs are insufficient, it will be handled after
6007
6006
// the `calculate_change_output_value` call below
6008
- let mut funding_outputs = Vec::new();
6009
6007
6010
6008
let shared_funding_output = TxOut {
6011
6009
value: Amount::from_sat(funding.get_value_satoshis()),
@@ -6017,18 +6015,27 @@ impl FundingNegotiationContext {
6017
6015
&self,
6018
6016
self.shared_funding_input.is_some(),
6019
6017
&shared_funding_output.script_pubkey,
6020
- &funding_outputs,
6021
6018
context.holder_dust_limit_satoshis,
6022
6019
)?
6023
6020
} else {
6024
6021
None
6025
6022
};
6026
6023
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
+ };
6032
6039
6033
6040
// Add change output if necessary
6034
6041
if let Some(change_value) = change_value_opt {
@@ -6062,7 +6069,7 @@ impl FundingNegotiationContext {
6062
6069
feerate_sat_per_kw: self.funding_feerate_sat_per_1000_weight,
6063
6070
is_initiator: self.is_initiator,
6064
6071
funding_tx_locktime: self.funding_tx_locktime,
6065
- inputs_to_contribute,
6072
+ inputs_to_contribute: funding_inputs ,
6066
6073
shared_funding_input: self.shared_funding_input,
6067
6074
shared_funding_output: SharedOwnedOutput::new(
6068
6075
shared_funding_output,
@@ -6083,6 +6090,26 @@ pub enum FundingTxContributions {
6083
6090
/// change output.
6084
6091
inputs: Vec<(TxIn, Transaction, Weight)>,
6085
6092
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
+
6086
6113
/// An optional change output script. This will be used if needed or, if not set, generated
6087
6114
/// using `SignerProvider::get_destination_script`.
6088
6115
change_script: Option<ScriptBuf>,
@@ -6094,8 +6121,26 @@ impl FundingTxContributions {
6094
6121
pub fn inputs(&self) -> &[(TxIn, Transaction, Weight)] {
6095
6122
match self {
6096
6123
FundingTxContributions::InputsOnly { inputs, .. } => &inputs[..],
6124
+ FundingTxContributions::OutputsOnly { .. } => &[],
6125
+ #[cfg(test)]
6126
+ FundingTxContributions::InputsAndOutputs { inputs, .. } => &inputs[..],
6097
6127
}
6098
6128
}
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
+ }
6099
6144
}
6100
6145
6101
6146
// Holder designates channel data owned for the benefit of the user client.
@@ -10666,43 +10711,90 @@ where
10666
10711
if our_funding_contribution > SignedAmount::MAX_MONEY {
10667
10712
return Err(APIError::APIMisuseError {
10668
10713
err: format!(
10669
- "Channel {} cannot be spliced; contribution exceeds total bitcoin supply: {}",
10714
+ "Channel {} cannot be spliced in ; contribution exceeds total bitcoin supply: {}",
10670
10715
self.context.channel_id(),
10671
10716
our_funding_contribution,
10672
10717
),
10673
10718
});
10674
10719
}
10675
10720
10676
- if our_funding_contribution < SignedAmount::ZERO {
10721
+ if our_funding_contribution < - SignedAmount::MAX_MONEY {
10677
10722
return Err(APIError::APIMisuseError {
10678
10723
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
+ ),
10682
10739
});
10683
10740
}
10684
10741
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
+ }
10687
10759
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)
10690
10781
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
+ }
10706
10798
10707
10799
for (_, tx, _) in funding_tx_contributions.inputs().iter() {
10708
10800
const MESSAGE_TEMPLATE: msgs::TxAddInput = msgs::TxAddInput {
0 commit comments