@@ -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