@@ -24,9 +24,9 @@ use bitcoin::hashes::Hash;
24
24
use bitcoin::secp256k1::constants::PUBLIC_KEY_SIZE;
25
25
use bitcoin::secp256k1::{ecdsa::Signature, Secp256k1};
26
26
use bitcoin::secp256k1::{PublicKey, SecretKey};
27
- #[cfg(splicing)]
28
- use bitcoin::Sequence;
29
27
use bitcoin::{secp256k1, sighash, TxIn};
28
+ #[cfg(splicing)]
29
+ use bitcoin::{FeeRate, Sequence};
30
30
31
31
use crate::chain::chaininterface::{
32
32
fee_for_weight, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator,
@@ -5879,18 +5879,60 @@ fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satos
5879
5879
cmp::min(channel_value_satoshis, cmp::max(q, dust_limit_satoshis))
5880
5880
}
5881
5881
5882
+ #[cfg(splicing)]
5883
+ fn check_splice_contribution_sufficient(
5884
+ channel_balance: Amount, contribution: &SpliceContribution, is_initiator: bool,
5885
+ funding_feerate: FeeRate,
5886
+ ) -> Result<Amount, ChannelError> {
5887
+ let contribution_amount = contribution.value();
5888
+ if contribution_amount < SignedAmount::ZERO {
5889
+ let estimated_fee = Amount::from_sat(estimate_v2_funding_transaction_fee(
5890
+ contribution.inputs(),
5891
+ contribution.outputs(),
5892
+ is_initiator,
5893
+ true, // is_splice
5894
+ funding_feerate.to_sat_per_kwu() as u32,
5895
+ ));
5896
+
5897
+ if channel_balance >= contribution_amount.unsigned_abs() + estimated_fee {
5898
+ Ok(estimated_fee)
5899
+ } else {
5900
+ Err(ChannelError::Warn(format!(
5901
+ "Available channel balance {} is lower than needed for splicing out {}, considering fees of {}",
5902
+ channel_balance, contribution_amount.unsigned_abs(), estimated_fee,
5903
+ )))
5904
+ }
5905
+ } else {
5906
+ check_v2_funding_inputs_sufficient(
5907
+ contribution_amount.to_sat(),
5908
+ contribution.inputs(),
5909
+ is_initiator,
5910
+ true,
5911
+ funding_feerate.to_sat_per_kwu() as u32,
5912
+ )
5913
+ .map(Amount::from_sat)
5914
+ }
5915
+ }
5916
+
5882
5917
/// Estimate our part of the fee of the new funding transaction.
5883
5918
#[allow(dead_code)] // TODO(dual_funding): TODO(splicing): Remove allow once used.
5884
5919
#[rustfmt::skip]
5885
5920
fn estimate_v2_funding_transaction_fee(
5886
- funding_inputs: &[FundingTxInput], is_initiator: bool, is_splice: bool,
5921
+ funding_inputs: &[FundingTxInput], outputs: &[TxOut], is_initiator: bool, is_splice: bool,
5887
5922
funding_feerate_sat_per_1000_weight: u32,
5888
5923
) -> u64 {
5889
- let mut weight : u64 = funding_inputs
5924
+ let input_weight : u64 = funding_inputs
5890
5925
.iter()
5891
5926
.map(|input| BASE_INPUT_WEIGHT.saturating_add(input.utxo.satisfaction_weight))
5892
5927
.fold(0, |total_weight, input_weight| total_weight.saturating_add(input_weight));
5893
5928
5929
+ let output_weight: u64 = outputs
5930
+ .iter()
5931
+ .map(|txout| txout.weight().to_wu())
5932
+ .fold(0, |total_weight, output_weight| total_weight.saturating_add(output_weight));
5933
+
5934
+ let mut weight = input_weight.saturating_add(output_weight);
5935
+
5894
5936
// The initiator pays for all common fields and the shared output in the funding transaction.
5895
5937
if is_initiator {
5896
5938
weight = weight
@@ -5927,7 +5969,7 @@ fn check_v2_funding_inputs_sufficient(
5927
5969
is_splice: bool, funding_feerate_sat_per_1000_weight: u32,
5928
5970
) -> Result<u64, ChannelError> {
5929
5971
let estimated_fee = estimate_v2_funding_transaction_fee(
5930
- funding_inputs, is_initiator, is_splice, funding_feerate_sat_per_1000_weight,
5972
+ funding_inputs, &[], is_initiator, is_splice, funding_feerate_sat_per_1000_weight,
5931
5973
);
5932
5974
5933
5975
let mut total_input_sats = 0u64;
@@ -5975,6 +6017,9 @@ pub(super) struct FundingNegotiationContext {
5975
6017
/// The funding inputs we will be contributing to the channel.
5976
6018
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
5977
6019
pub our_funding_inputs: Vec<FundingTxInput>,
6020
+ /// The funding outputs we will be contributing to the channel.
6021
+ #[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
6022
+ pub our_funding_outputs: Vec<TxOut>,
5978
6023
/// The change output script. This will be used if needed or -- if not set -- generated using
5979
6024
/// `SignerProvider::get_destination_script`.
5980
6025
#[allow(dead_code)] // TODO(splicing): Remove once splicing is enabled.
@@ -6004,45 +6049,46 @@ impl FundingNegotiationContext {
6004
6049
debug_assert!(matches!(context.channel_state, ChannelState::NegotiatingFunding(_)));
6005
6050
}
6006
6051
6007
- // Add output for funding tx
6008
6052
// Note: For the error case when the inputs are insufficient, it will be handled after
6009
6053
// the `calculate_change_output_value` call below
6010
- let mut funding_outputs = Vec::new();
6011
6054
6012
6055
let shared_funding_output = TxOut {
6013
6056
value: Amount::from_sat(funding.get_value_satoshis()),
6014
6057
script_pubkey: funding.get_funding_redeemscript().to_p2wsh(),
6015
6058
};
6016
6059
6017
6060
// Optionally add change output
6018
- if self.our_funding_contribution > SignedAmount::ZERO {
6019
- let change_value_opt = calculate_change_output_value(
6061
+ let change_value_opt = if self.our_funding_contribution > SignedAmount::ZERO {
6062
+ calculate_change_output_value(
6020
6063
&self,
6021
6064
self.shared_funding_input.is_some(),
6022
6065
&shared_funding_output.script_pubkey,
6023
- &funding_outputs,
6024
6066
context.holder_dust_limit_satoshis,
6025
- )?;
6026
- if let Some(change_value) = change_value_opt {
6027
- let change_script = if let Some(script) = self.change_script {
6028
- script
6029
- } else {
6030
- signer_provider
6031
- .get_destination_script(context.channel_keys_id)
6032
- .map_err(|_err| AbortReason::InternalError("Error getting change script"))?
6033
- };
6034
- let mut change_output =
6035
- TxOut { value: Amount::from_sat(change_value), script_pubkey: change_script };
6036
- let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
6037
- let change_output_fee =
6038
- fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
6039
- let change_value_decreased_with_fee =
6040
- change_value.saturating_sub(change_output_fee);
6041
- // Check dust limit again
6042
- if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
6043
- change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6044
- funding_outputs.push(change_output);
6045
- }
6067
+ )?
6068
+ } else {
6069
+ None
6070
+ };
6071
+
6072
+ let mut funding_outputs = self.our_funding_outputs;
6073
+
6074
+ if let Some(change_value) = change_value_opt {
6075
+ let change_script = if let Some(script) = self.change_script {
6076
+ script
6077
+ } else {
6078
+ signer_provider
6079
+ .get_destination_script(context.channel_keys_id)
6080
+ .map_err(|_err| AbortReason::InternalError("Error getting change script"))?
6081
+ };
6082
+ let mut change_output =
6083
+ TxOut { value: Amount::from_sat(change_value), script_pubkey: change_script };
6084
+ let change_output_weight = get_output_weight(&change_output.script_pubkey).to_wu();
6085
+ let change_output_fee =
6086
+ fee_for_weight(self.funding_feerate_sat_per_1000_weight, change_output_weight);
6087
+ let change_value_decreased_with_fee = change_value.saturating_sub(change_output_fee);
6088
+ // Check dust limit again
6089
+ if change_value_decreased_with_fee > context.holder_dust_limit_satoshis {
6090
+ change_output.value = Amount::from_sat(change_value_decreased_with_fee);
6091
+ funding_outputs.push(change_output);
6046
6092
}
6047
6093
}
6048
6094
@@ -10630,47 +10676,78 @@ where
10630
10676
// TODO(splicing): check for quiescence
10631
10677
10632
10678
let our_funding_contribution = contribution.value();
10679
+ if our_funding_contribution == SignedAmount::ZERO {
10680
+ return Err(APIError::APIMisuseError {
10681
+ err: format!(
10682
+ "Channel {} cannot be spliced; contribution cannot be zero",
10683
+ self.context.channel_id(),
10684
+ ),
10685
+ });
10686
+ }
10687
+
10633
10688
if our_funding_contribution > SignedAmount::MAX_MONEY {
10634
10689
return Err(APIError::APIMisuseError {
10635
10690
err: format!(
10636
- "Channel {} cannot be spliced; contribution exceeds total bitcoin supply: {}",
10691
+ "Channel {} cannot be spliced in ; contribution exceeds total bitcoin supply: {}",
10637
10692
self.context.channel_id(),
10638
10693
our_funding_contribution,
10639
10694
),
10640
10695
});
10641
10696
}
10642
10697
10643
- if our_funding_contribution < SignedAmount::ZERO {
10698
+ if our_funding_contribution < - SignedAmount::MAX_MONEY {
10644
10699
return Err(APIError::APIMisuseError {
10645
10700
err: format!(
10646
- "TODO(splicing): Splice-out not supported, only splice in; channel ID {}, contribution {}",
10647
- self.context.channel_id(), our_funding_contribution,
10648
- ),
10701
+ "Channel {} cannot be spliced out; contribution exhausts total bitcoin supply: {}",
10702
+ self.context.channel_id(),
10703
+ our_funding_contribution,
10704
+ ),
10649
10705
});
10650
10706
}
10651
10707
10652
- // TODO(splicing): Once splice-out is supported, check that channel balance does not go below 0
10653
- // (or below channel reserve)
10654
-
10655
10708
// Note: post-splice channel value is not yet known at this point, counterparty contribution is not known
10656
10709
// (Cannot test for miminum required post-splice channel value)
10657
10710
10658
- // Check that inputs are sufficient to cover our contribution.
10659
- let _fee = check_v2_funding_inputs_sufficient(
10660
- our_funding_contribution.to_sat(),
10661
- contribution.inputs(),
10662
- true,
10663
- true,
10664
- funding_feerate_per_kw,
10711
+ let channel_balance = Amount::from_sat(self.funding.get_value_to_self_msat() / 1000);
10712
+ let fees = check_splice_contribution_sufficient(
10713
+ channel_balance,
10714
+ &contribution,
10715
+ true, // is_initiator
10716
+ FeeRate::from_sat_per_kwu(funding_feerate_per_kw as u64),
10665
10717
)
10666
- .map_err(|err| APIError::APIMisuseError {
10667
- err: format!(
10668
- "Insufficient inputs for splicing; channel ID {}, err {}",
10669
- self.context.channel_id(),
10670
- err,
10671
- ),
10718
+ .map_err(|e| {
10719
+ let splice_type = if our_funding_contribution < SignedAmount::ZERO {
10720
+ "spliced out"
10721
+ } else {
10722
+ "spliced in"
10723
+ };
10724
+ APIError::APIMisuseError {
10725
+ err: format!(
10726
+ "Channel {} cannot be {}; {}",
10727
+ self.context.channel_id(),
10728
+ splice_type,
10729
+ e,
10730
+ ),
10731
+ }
10672
10732
})?;
10673
10733
10734
+ // Fees for splice-out are paid from the channel balance whereas fees for splice-in are paid
10735
+ // by the funding inputs.
10736
+ let adjusted_funding_contribution = if our_funding_contribution < SignedAmount::ZERO {
10737
+ let adjusted_funding_contribution = our_funding_contribution
10738
+ - fees.to_signed().expect("fees should never exceed Amount::MAX_MONEY");
10739
+
10740
+ // TODO(splicing): Check that channel balance does not go below the channel reserve
10741
+ let _post_channel_balance = AddSigned::checked_add_signed(
10742
+ channel_balance.to_sat(),
10743
+ adjusted_funding_contribution.to_sat(),
10744
+ );
10745
+
10746
+ adjusted_funding_contribution
10747
+ } else {
10748
+ our_funding_contribution
10749
+ };
10750
+
10674
10751
for FundingTxInput { utxo, prevtx, .. } in contribution.inputs().iter() {
10675
10752
const MESSAGE_TEMPLATE: msgs::TxAddInput = msgs::TxAddInput {
10676
10753
channel_id: ChannelId([0; 32]),
@@ -10693,14 +10770,15 @@ where
10693
10770
}
10694
10771
10695
10772
let prev_funding_input = self.funding.to_splice_funding_input();
10696
- let (our_funding_inputs, change_script) = contribution.into_tx_parts();
10773
+ let (our_funding_inputs, our_funding_outputs, change_script) = contribution.into_tx_parts();
10697
10774
let funding_negotiation_context = FundingNegotiationContext {
10698
10775
is_initiator: true,
10699
- our_funding_contribution,
10776
+ our_funding_contribution: adjusted_funding_contribution ,
10700
10777
funding_tx_locktime: LockTime::from_consensus(locktime),
10701
10778
funding_feerate_sat_per_1000_weight: funding_feerate_per_kw,
10702
10779
shared_funding_input: Some(prev_funding_input),
10703
10780
our_funding_inputs,
10781
+ our_funding_outputs,
10704
10782
change_script,
10705
10783
};
10706
10784
@@ -10716,7 +10794,7 @@ where
10716
10794
10717
10795
Ok(msgs::SpliceInit {
10718
10796
channel_id: self.context.channel_id,
10719
- funding_contribution_satoshis: our_funding_contribution .to_sat(),
10797
+ funding_contribution_satoshis: adjusted_funding_contribution .to_sat(),
10720
10798
funding_feerate_per_kw,
10721
10799
locktime,
10722
10800
funding_pubkey,
@@ -10825,6 +10903,7 @@ where
10825
10903
funding_feerate_sat_per_1000_weight: msg.funding_feerate_per_kw,
10826
10904
shared_funding_input: Some(prev_funding_input),
10827
10905
our_funding_inputs: Vec::new(),
10906
+ our_funding_outputs: Vec::new(),
10828
10907
change_script: None,
10829
10908
};
10830
10909
@@ -12523,6 +12602,7 @@ where
12523
12602
funding_feerate_sat_per_1000_weight,
12524
12603
shared_funding_input: None,
12525
12604
our_funding_inputs: funding_inputs,
12605
+ our_funding_outputs: Vec::new(),
12526
12606
change_script: None,
12527
12607
};
12528
12608
let chan = Self {
@@ -12677,6 +12757,7 @@ where
12677
12757
funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight,
12678
12758
shared_funding_input: None,
12679
12759
our_funding_inputs: our_funding_inputs.clone(),
12760
+ our_funding_outputs: Vec::new(),
12680
12761
change_script: None,
12681
12762
};
12682
12763
let shared_funding_output = TxOut {
@@ -12702,7 +12783,7 @@ where
12702
12783
inputs_to_contribute,
12703
12784
shared_funding_input: None,
12704
12785
shared_funding_output: SharedOwnedOutput::new(shared_funding_output, our_funding_contribution_sats),
12705
- outputs_to_contribute: Vec::new (),
12786
+ outputs_to_contribute: funding_negotiation_context.our_funding_outputs.clone (),
12706
12787
}
12707
12788
).map_err(|err| {
12708
12789
let reason = ClosureReason::ProcessingError { err: err.to_string() };
@@ -15870,43 +15951,43 @@ mod tests {
15870
15951
15871
15952
// 2 inputs, initiator, 2000 sat/kw feerate
15872
15953
assert_eq!(
15873
- estimate_v2_funding_transaction_fee(&two_inputs, true, false, 2000),
15954
+ estimate_v2_funding_transaction_fee(&two_inputs, &[], true, false, 2000),
15874
15955
1520,
15875
15956
);
15876
15957
15877
15958
// higher feerate
15878
15959
assert_eq!(
15879
- estimate_v2_funding_transaction_fee(&two_inputs, true, false, 3000),
15960
+ estimate_v2_funding_transaction_fee(&two_inputs, &[], true, false, 3000),
15880
15961
2280,
15881
15962
);
15882
15963
15883
15964
// only 1 input
15884
15965
assert_eq!(
15885
- estimate_v2_funding_transaction_fee(&one_input, true, false, 2000),
15966
+ estimate_v2_funding_transaction_fee(&one_input, &[], true, false, 2000),
15886
15967
974,
15887
15968
);
15888
15969
15889
15970
// 0 inputs
15890
15971
assert_eq!(
15891
- estimate_v2_funding_transaction_fee(&[], true, false, 2000),
15972
+ estimate_v2_funding_transaction_fee(&[], &[], true, false, 2000),
15892
15973
428,
15893
15974
);
15894
15975
15895
15976
// not initiator
15896
15977
assert_eq!(
15897
- estimate_v2_funding_transaction_fee(&[], false, false, 2000),
15978
+ estimate_v2_funding_transaction_fee(&[], &[], false, false, 2000),
15898
15979
0,
15899
15980
);
15900
15981
15901
15982
// splice initiator
15902
15983
assert_eq!(
15903
- estimate_v2_funding_transaction_fee(&one_input, true, true, 2000),
15984
+ estimate_v2_funding_transaction_fee(&one_input, &[], true, true, 2000),
15904
15985
1746,
15905
15986
);
15906
15987
15907
15988
// splice acceptor
15908
15989
assert_eq!(
15909
- estimate_v2_funding_transaction_fee(&one_input, false, true, 2000),
15990
+ estimate_v2_funding_transaction_fee(&one_input, &[], false, true, 2000),
15910
15991
546,
15911
15992
);
15912
15993
}
0 commit comments