@@ -47,6 +47,8 @@ use crate::ln::chan_utils::{
47
47
get_commitment_transaction_number_obscure_factor,
48
48
ClosingTransaction, commit_tx_fee_sat,
49
49
};
50
+ #[cfg(splicing)]
51
+ use crate::ln::chan_utils::FUNDING_TRANSACTION_WITNESS_WEIGHT;
50
52
use crate::ln::chan_utils;
51
53
use crate::ln::onion_utils::HTLCFailReason;
52
54
use crate::chain::BestBlock;
@@ -4129,28 +4131,63 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
4129
4131
}
4130
4132
}
4131
4133
4132
- /// Check that a balance value meets the channel reserve requirements or violates them (below reserve).
4133
- /// The channel value is an input as opposed to using from self, so that this can be used in case of splicing
4134
- /// to checks with new channel value (before being comitted to it).
4134
+ /// Check a balance against a channel reserver requirement
4135
4135
#[cfg(splicing)]
4136
- pub fn check_balance_meets_reserve_requirements(&self, balance: u64, channel_value: u64) -> Result<(), ChannelError> {
4136
+ pub fn check_balance_meets_reserve_requirement(balance: u64, channel_value: u64, dust_limit: u64) -> (bool, u64) {
4137
+ let channel_reserve = get_v2_channel_reserve_satoshis(channel_value, dust_limit);
4137
4138
if balance == 0 {
4138
- return Ok(());
4139
+ // 0 balance is fine
4140
+ (true, channel_reserve)
4141
+ } else {
4142
+ ((balance >= channel_reserve), channel_reserve)
4139
4143
}
4140
- let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
4141
- channel_value, self.holder_dust_limit_satoshis);
4142
- if balance < holder_selected_channel_reserve_satoshis {
4144
+ }
4145
+
4146
+ /// Check that post-splicing balance meets reserver requirements, but only if it met it pre-splice as well
4147
+ #[cfg(splicing)]
4148
+ pub fn check_splice_balance_meets_v2_reserve_requirement_noerr(pre_balance: u64, post_balance: u64, pre_channel_value: u64, post_channel_value: u64, dust_limit: u64) -> (bool, u64) {
4149
+ match Self::check_balance_meets_reserve_requirement(
4150
+ post_balance, post_channel_value, dust_limit
4151
+ ) {
4152
+ (true, channel_reserve) => (true, channel_reserve),
4153
+ (false, channel_reserve) =>
4154
+ // post is not OK, check pre
4155
+ match Self::check_balance_meets_reserve_requirement(
4156
+ pre_balance, pre_channel_value, dust_limit
4157
+ ) {
4158
+ (true, _) =>
4159
+ // pre OK, post not -> not
4160
+ (false, channel_reserve),
4161
+ (false, _) =>
4162
+ // post not OK, but so was pre -> OK
4163
+ (true, channel_reserve),
4164
+ }
4165
+ }
4166
+ }
4167
+
4168
+ /// Check that balances meet the channel reserve requirements or violates them (below reserve).
4169
+ /// The channel value is an input as opposed to using from self, so that this can be used in case of splicing
4170
+ /// to check with new channel value (before being comitted to it).
4171
+ #[cfg(splicing)]
4172
+ pub fn check_splice_balances_meet_v2_reserve_requirements(&self, self_balance_pre: u64, self_balance_post: u64, counterparty_balance_pre: u64, counterparty_balance_post: u64, channel_value_pre: u64, channel_value_post: u64) -> Result<(), ChannelError> {
4173
+ let (is_ok, channel_reserve_self) = Self::check_splice_balance_meets_v2_reserve_requirement_noerr(
4174
+ self_balance_pre, self_balance_post, channel_value_pre, channel_value_post,
4175
+ self.holder_dust_limit_satoshis
4176
+ );
4177
+ if !is_ok {
4143
4178
return Err(ChannelError::Warn(format!(
4144
- "Balance below reserve mandated by holder, {} vs {}",
4145
- balance, holder_selected_channel_reserve_satoshis ,
4179
+ "Balance below reserve, mandated by holder, {} vs {}",
4180
+ self_balance_post, channel_reserve_self ,
4146
4181
)));
4147
4182
}
4148
- let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
4149
- channel_value, self.counterparty_dust_limit_satoshis);
4150
- if balance < counterparty_selected_channel_reserve_satoshis {
4183
+ let (is_ok, channel_reserve_cp) = Self::check_splice_balance_meets_v2_reserve_requirement_noerr(
4184
+ counterparty_balance_pre, counterparty_balance_post, channel_value_pre, channel_value_post,
4185
+ self.counterparty_dust_limit_satoshis
4186
+ );
4187
+ if !is_ok {
4151
4188
return Err(ChannelError::Warn(format!(
4152
4189
"Balance below reserve mandated by counterparty, {} vs {}",
4153
- balance, counterparty_selected_channel_reserve_satoshis ,
4190
+ counterparty_balance_post, channel_reserve_cp ,
4154
4191
)));
4155
4192
}
4156
4193
Ok(())
@@ -4718,6 +4755,58 @@ fn estimate_v2_funding_transaction_fee(
4718
4755
fee_for_weight(funding_feerate_sat_per_1000_weight, weight)
4719
4756
}
4720
4757
4758
+ /// Verify that the provided inputs by a counterparty to the funding transaction are enough
4759
+ /// to cover the intended contribution amount *plus* the proportional fees of the counterparty.
4760
+ /// Fees are computed using `estimate_v2_funding_transaction_fee`, and contain
4761
+ /// the fees of the inputs, fees of the inputs weight, and for the initiator,
4762
+ /// the fees of the common fields as well as the output and extra input weights.
4763
+ /// Returns estimated (partial) fees as additional information
4764
+ #[cfg(splicing)]
4765
+ pub(super) fn check_v2_funding_inputs_sufficient(
4766
+ contribution_amount: i64, funding_inputs: &[(TxIn, Transaction, Weight)], is_initiator: bool,
4767
+ is_splice: bool, funding_feerate_sat_per_1000_weight: u32,
4768
+ ) -> Result<u64, ChannelError> {
4769
+ let mut total_input_witness_weight = Weight::from_wu(funding_inputs.iter().map(|(_, _, w)| w.to_wu()).sum());
4770
+ if is_initiator && is_splice {
4771
+ // consider the weight of the witness needed for spending the old funding transaction
4772
+ total_input_witness_weight += Weight::from_wu(FUNDING_TRANSACTION_WITNESS_WEIGHT);
4773
+ }
4774
+ let estimated_fee = estimate_v2_funding_transaction_fee(is_initiator, funding_inputs.len(), total_input_witness_weight, funding_feerate_sat_per_1000_weight);
4775
+
4776
+ let mut total_input_sats = 0u64;
4777
+ for (idx, input) in funding_inputs.iter().enumerate() {
4778
+ if let Some(output) = input.1.output.get(input.0.previous_output.vout as usize) {
4779
+ total_input_sats = total_input_sats.saturating_add(output.value.to_sat());
4780
+ } else {
4781
+ return Err(ChannelError::Warn(format!(
4782
+ "Transaction with txid {} does not have an output with vout of {} corresponding to TxIn at funding_inputs[{}]",
4783
+ input.1.compute_txid(), input.0.previous_output.vout, idx
4784
+ )));
4785
+ }
4786
+ }
4787
+
4788
+ // If the inputs are enough to cover intended contribution amount, with fees even when
4789
+ // there is a change output, we are fine.
4790
+ // If the inputs are less, but enough to cover intended contribution amount, with
4791
+ // (lower) fees with no change, we are also fine (change will not be generated).
4792
+ // So it's enough to check considering the lower, no-change fees.
4793
+ //
4794
+ // Note: dust limit is not relevant in this check.
4795
+ //
4796
+ // TODO(splicing): refine check including the fact wether a change will be added or not.
4797
+ // Can be done once dual funding preparation is included.
4798
+
4799
+ let minimal_input_amount_needed = contribution_amount.saturating_add(estimated_fee as i64);
4800
+ if (total_input_sats as i64) < minimal_input_amount_needed {
4801
+ Err(ChannelError::Warn(format!(
4802
+ "Total input amount {} is lower than needed for contribution {}, considering fees of {}. Need more inputs.",
4803
+ total_input_sats, contribution_amount, estimated_fee,
4804
+ )))
4805
+ } else {
4806
+ Ok(estimated_fee)
4807
+ }
4808
+ }
4809
+
4721
4810
/// Context for dual-funded channels.
4722
4811
pub(super) struct DualFundingChannelContext {
4723
4812
/// The amount in satoshis we will be contributing to the channel.
@@ -8382,7 +8471,7 @@ impl<SP: Deref> FundedChannel<SP> where
8382
8471
/// Includes the witness weight for this input (e.g. P2WPKH_WITNESS_WEIGHT=109 for typical P2WPKH inputs).
8383
8472
#[cfg(splicing)]
8384
8473
pub fn splice_channel(&mut self, our_funding_contribution_satoshis: i64,
8385
- _our_funding_inputs : &Vec<(TxIn, Transaction, Weight)>,
8474
+ our_funding_inputs : &Vec<(TxIn, Transaction, Weight)>,
8386
8475
funding_feerate_per_kw: u32, locktime: u32,
8387
8476
) -> Result<msgs::SpliceInit, APIError> {
8388
8477
// Check if a splice has been initiated already.
@@ -8416,6 +8505,13 @@ impl<SP: Deref> FundedChannel<SP> where
8416
8505
// Note: post-splice channel value is not yet known at this point, counterparty contribution is not known
8417
8506
// (Cannot test for miminum required post-splice channel value)
8418
8507
8508
+ // Check that inputs are sufficient to cover our contribution.
8509
+ // Extra common weight is the weight for spending the old funding
8510
+ let _fee = check_v2_funding_inputs_sufficient(our_funding_contribution_satoshis, &our_funding_inputs, true, true, funding_feerate_per_kw)
8511
+ .map_err(|err| APIError::APIMisuseError { err: format!(
8512
+ "Insufficient inputs for splicing; channel ID {}, err {}",
8513
+ self.context.channel_id(), err,
8514
+ )})?;
8419
8515
8420
8516
self.pending_splice_pre = Some(PendingSplice {
8421
8517
our_funding_contribution: our_funding_contribution_satoshis,
@@ -8517,10 +8613,13 @@ impl<SP: Deref> FundedChannel<SP> where
8517
8613
8518
8614
let pre_channel_value = self.funding.get_value_satoshis();
8519
8615
let post_channel_value = PendingSplice::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution_satoshis);
8520
- let post_balance = PendingSplice::add_checked(self.funding.value_to_self_msat, our_funding_contribution);
8521
- // Early check for reserve requirement, assuming maximum balance of full channel value
8616
+ let pre_balance_self = self.funding.value_to_self_msat;
8617
+ let post_balance_self = PendingSplice::add_checked(pre_balance_self, our_funding_contribution);
8618
+ let pre_balance_counterparty = pre_channel_value.saturating_sub(pre_balance_self);
8619
+ let post_balance_counterparty = post_channel_value.saturating_sub(post_balance_self);
8620
+ // Pre-check for reserve requirement
8522
8621
// This will also be checked later at tx_complete
8523
- let _res = self.context.check_balance_meets_reserve_requirements(post_balance , post_channel_value)?;
8622
+ let _res = self.context.check_splice_balances_meet_v2_reserve_requirements(pre_balance_self, post_balance_self, pre_balance_counterparty, post_balance_counterparty, pre_channel_value , post_channel_value)?;
8524
8623
Ok(())
8525
8624
}
8526
8625
@@ -11087,8 +11186,12 @@ mod tests {
11087
11186
use bitcoin::constants::ChainHash;
11088
11187
use bitcoin::script::{ScriptBuf, Builder};
11089
11188
use bitcoin::transaction::{Transaction, TxOut, Version};
11189
+ #[cfg(splicing)]
11190
+ use bitcoin::transaction::TxIn;
11090
11191
use bitcoin::opcodes;
11091
11192
use bitcoin::network::Network;
11193
+ #[cfg(splicing)]
11194
+ use bitcoin::Weight;
11092
11195
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
11093
11196
use crate::types::payment::{PaymentHash, PaymentPreimage};
11094
11197
use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
@@ -12891,6 +12994,114 @@ mod tests {
12891
12994
);
12892
12995
}
12893
12996
12997
+ #[cfg(splicing)]
12998
+ fn funding_input_sats(input_value_sats: u64) -> (TxIn, Transaction, Weight) {
12999
+ use crate::sign::P2WPKH_WITNESS_WEIGHT;
13000
+
13001
+ let input_1_prev_out = TxOut { value: Amount::from_sat(input_value_sats), script_pubkey: ScriptBuf::default() };
13002
+ let input_1_prev_tx = Transaction {
13003
+ input: vec![], output: vec![input_1_prev_out],
13004
+ version: Version::TWO, lock_time: bitcoin::absolute::LockTime::ZERO,
13005
+ };
13006
+ let input_1_txin = TxIn {
13007
+ previous_output: bitcoin::OutPoint { txid: input_1_prev_tx.compute_txid(), vout: 0 },
13008
+ ..Default::default()
13009
+ };
13010
+ (input_1_txin, input_1_prev_tx, Weight::from_wu(P2WPKH_WITNESS_WEIGHT))
13011
+ }
13012
+
13013
+ #[cfg(splicing)]
13014
+ #[test]
13015
+ fn test_check_v2_funding_inputs_sufficient() {
13016
+ use crate::ln::channel::check_v2_funding_inputs_sufficient;
13017
+
13018
+ // positive case, inputs well over intended contribution
13019
+ assert_eq!(
13020
+ check_v2_funding_inputs_sufficient(
13021
+ 220_000,
13022
+ &[
13023
+ funding_input_sats(200_000),
13024
+ funding_input_sats(100_000),
13025
+ ],
13026
+ true,
13027
+ true,
13028
+ 2000,
13029
+ ).unwrap(),
13030
+ 1948,
13031
+ );
13032
+
13033
+ // negative case, inputs clearly insufficient
13034
+ {
13035
+ let res = check_v2_funding_inputs_sufficient(
13036
+ 220_000,
13037
+ &[
13038
+ funding_input_sats(100_000),
13039
+ ],
13040
+ true,
13041
+ true,
13042
+ 2000,
13043
+ );
13044
+ assert_eq!(
13045
+ format!("{:?}", res.err().unwrap()),
13046
+ "Warn: Total input amount 100000 is lower than needed for contribution 220000, considering fees of 1410. Need more inputs.",
13047
+ );
13048
+ }
13049
+
13050
+ // barely covers
13051
+ {
13052
+ let expected_fee: u64 = 1948;
13053
+ assert_eq!(
13054
+ check_v2_funding_inputs_sufficient(
13055
+ (300_000 - expected_fee - 20) as i64,
13056
+ &[
13057
+ funding_input_sats(200_000),
13058
+ funding_input_sats(100_000),
13059
+ ],
13060
+ true,
13061
+ true,
13062
+ 2000,
13063
+ ).unwrap(),
13064
+ expected_fee,
13065
+ );
13066
+ }
13067
+
13068
+ // higher fee rate, does not cover
13069
+ {
13070
+ let res = check_v2_funding_inputs_sufficient(
13071
+ 298032,
13072
+ &[
13073
+ funding_input_sats(200_000),
13074
+ funding_input_sats(100_000),
13075
+ ],
13076
+ true,
13077
+ true,
13078
+ 2200,
13079
+ );
13080
+ assert_eq!(
13081
+ format!("{:?}", res.err().unwrap()),
13082
+ "Warn: Total input amount 300000 is lower than needed for contribution 298032, considering fees of 2143. Need more inputs.",
13083
+ );
13084
+ }
13085
+
13086
+ // barely covers, less fees (no extra weight, no init)
13087
+ {
13088
+ let expected_fee: u64 = 1076;
13089
+ assert_eq!(
13090
+ check_v2_funding_inputs_sufficient(
13091
+ (300_000 - expected_fee - 20) as i64,
13092
+ &[
13093
+ funding_input_sats(200_000),
13094
+ funding_input_sats(100_000),
13095
+ ],
13096
+ false,
13097
+ false,
13098
+ 2000,
13099
+ ).unwrap(),
13100
+ expected_fee,
13101
+ );
13102
+ }
13103
+ }
13104
+
12894
13105
#[cfg(splicing)]
12895
13106
fn get_pre_and_post(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64) -> (u64, u64) {
12896
13107
use crate::ln::channel::PendingSplice;
0 commit comments