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