@@ -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;
@@ -4093,28 +4095,63 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
40934095 }
40944096 }
40954097
4096- /// Check that a balance value meets the channel reserve requirements or violates them (below reserve).
4097- /// The channel value is an input as opposed to using from self, so that this can be used in case of splicing
4098- /// to checks with new channel value (before being comitted to it).
4098+ /// Check a balance against a channel reserver requirement
40994099 #[cfg(splicing)]
4100- pub fn check_balance_meets_reserve_requirements(&self, balance: u64, channel_value: u64) -> Result<(), ChannelError> {
4100+ pub fn check_balance_meets_reserve_requirement(balance: u64, channel_value: u64, dust_limit: u64) -> (bool, u64) {
4101+ let channel_reserve = get_v2_channel_reserve_satoshis(channel_value, dust_limit);
41014102 if balance == 0 {
4102- return Ok(());
4103+ // 0 balance is fine
4104+ (true, channel_reserve)
4105+ } else {
4106+ ((balance >= channel_reserve), channel_reserve)
41034107 }
4104- let holder_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
4105- channel_value, self.holder_dust_limit_satoshis);
4106- if balance < holder_selected_channel_reserve_satoshis {
4108+ }
4109+
4110+ /// Check that post-splicing balance meets reserver requirements, but only if it met it pre-splice as well
4111+ #[cfg(splicing)]
4112+ 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) {
4113+ match Self::check_balance_meets_reserve_requirement(
4114+ post_balance, post_channel_value, dust_limit
4115+ ) {
4116+ (true, channel_reserve) => (true, channel_reserve),
4117+ (false, channel_reserve) =>
4118+ // post is not OK, check pre
4119+ match Self::check_balance_meets_reserve_requirement(
4120+ pre_balance, pre_channel_value, dust_limit
4121+ ) {
4122+ (true, _) =>
4123+ // pre OK, post not -> not
4124+ (false, channel_reserve),
4125+ (false, _) =>
4126+ // post not OK, but so was pre -> OK
4127+ (true, channel_reserve),
4128+ }
4129+ }
4130+ }
4131+
4132+ /// Check that balances meet 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 check with new channel value (before being comitted to it).
4135+ #[cfg(splicing)]
4136+ 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> {
4137+ let (is_ok, channel_reserve_self) = Self::check_splice_balance_meets_v2_reserve_requirement_noerr(
4138+ self_balance_pre, self_balance_post, channel_value_pre, channel_value_post,
4139+ self.holder_dust_limit_satoshis
4140+ );
4141+ if !is_ok {
41074142 return Err(ChannelError::Warn(format!(
4108- "Balance below reserve mandated by holder, {} vs {}",
4109- balance, holder_selected_channel_reserve_satoshis ,
4143+ "Balance below reserve, mandated by holder, {} vs {}",
4144+ self_balance_post, channel_reserve_self ,
41104145 )));
41114146 }
4112- let counterparty_selected_channel_reserve_satoshis = get_v2_channel_reserve_satoshis(
4113- channel_value, self.counterparty_dust_limit_satoshis);
4114- if balance < counterparty_selected_channel_reserve_satoshis {
4147+ let (is_ok, channel_reserve_cp) = Self::check_splice_balance_meets_v2_reserve_requirement_noerr(
4148+ counterparty_balance_pre, counterparty_balance_post, channel_value_pre, channel_value_post,
4149+ self.counterparty_dust_limit_satoshis
4150+ );
4151+ if !is_ok {
41154152 return Err(ChannelError::Warn(format!(
41164153 "Balance below reserve mandated by counterparty, {} vs {}",
4117- balance, counterparty_selected_channel_reserve_satoshis ,
4154+ counterparty_balance_post, channel_reserve_cp ,
41184155 )));
41194156 }
41204157 Ok(())
@@ -4676,6 +4713,58 @@ fn estimate_v2_funding_transaction_fee(
46764713 fee_for_weight(funding_feerate_sat_per_1000_weight, weight)
46774714}
46784715
4716+ /// Verify that the provided inputs by a counterparty to the funding transaction are enough
4717+ /// to cover the intended contribution amount *plus* the proportional fees of the counterparty.
4718+ /// Fees are computed using `estimate_v2_funding_transaction_fee`, and contain
4719+ /// the fees of the inputs, fees of the inputs weight, and for the initiator,
4720+ /// the fees of the common fields as well as the output and extra input weights.
4721+ /// Returns estimated (partial) fees as additional information
4722+ #[cfg(splicing)]
4723+ pub(super) fn check_v2_funding_inputs_sufficient(
4724+ contribution_amount: i64, funding_inputs: &[(TxIn, Transaction, Weight)], is_initiator: bool,
4725+ is_splice: bool, funding_feerate_sat_per_1000_weight: u32,
4726+ ) -> Result<u64, ChannelError> {
4727+ let mut total_input_witness_weight = Weight::from_wu(funding_inputs.iter().map(|(_, _, w)| w.to_wu()).sum());
4728+ if is_initiator && is_splice {
4729+ // consider the weight of the witness needed for spending the old funding transaction
4730+ total_input_witness_weight += Weight::from_wu(FUNDING_TRANSACTION_WITNESS_WEIGHT);
4731+ }
4732+ let estimated_fee = estimate_v2_funding_transaction_fee(is_initiator, funding_inputs.len(), total_input_witness_weight, funding_feerate_sat_per_1000_weight);
4733+
4734+ let mut total_input_sats = 0u64;
4735+ for (idx, input) in funding_inputs.iter().enumerate() {
4736+ if let Some(output) = input.1.output.get(input.0.previous_output.vout as usize) {
4737+ total_input_sats = total_input_sats.saturating_add(output.value.to_sat());
4738+ } else {
4739+ return Err(ChannelError::Warn(format!(
4740+ "Transaction with txid {} does not have an output with vout of {} corresponding to TxIn at funding_inputs[{}]",
4741+ input.1.compute_txid(), input.0.previous_output.vout, idx
4742+ )));
4743+ }
4744+ }
4745+
4746+ // If the inputs are enough to cover intended contribution amount, with fees even when
4747+ // there is a change output, we are fine.
4748+ // If the inputs are less, but enough to cover intended contribution amount, with
4749+ // (lower) fees with no change, we are also fine (change will not be generated).
4750+ // So it's enough to check considering the lower, no-change fees.
4751+ //
4752+ // Note: dust limit is not relevant in this check.
4753+ //
4754+ // TODO(splicing): refine check including the fact wether a change will be added or not.
4755+ // Can be done once dual funding preparation is included.
4756+
4757+ let minimal_input_amount_needed = contribution_amount.saturating_add(estimated_fee as i64);
4758+ if (total_input_sats as i64) < minimal_input_amount_needed {
4759+ Err(ChannelError::Warn(format!(
4760+ "Total input amount {} is lower than needed for contribution {}, considering fees of {}. Need more inputs.",
4761+ total_input_sats, contribution_amount, estimated_fee,
4762+ )))
4763+ } else {
4764+ Ok(estimated_fee)
4765+ }
4766+ }
4767+
46794768/// Context for dual-funded channels.
46804769pub(super) struct DualFundingChannelContext {
46814770 /// The amount in satoshis we will be contributing to the channel.
@@ -8337,7 +8426,7 @@ impl<SP: Deref> FundedChannel<SP> where
83378426 /// Includes the witness weight for this input (e.g. P2WPKH_WITNESS_WEIGHT=109 for typical P2WPKH inputs).
83388427 #[cfg(splicing)]
83398428 pub fn splice_channel(&mut self, our_funding_contribution_satoshis: i64,
8340- _our_funding_inputs : &Vec<(TxIn, Transaction, Weight)>,
8429+ our_funding_inputs : &Vec<(TxIn, Transaction, Weight)>,
83418430 funding_feerate_per_kw: u32, locktime: u32,
83428431 ) -> Result<msgs::SpliceInit, APIError> {
83438432 // Check if a splice has been initiated already.
@@ -8371,6 +8460,13 @@ impl<SP: Deref> FundedChannel<SP> where
83718460 // Note: post-splice channel value is not yet known at this point, counterparty contribution is not known
83728461 // (Cannot test for miminum required post-splice channel value)
83738462
8463+ // Check that inputs are sufficient to cover our contribution.
8464+ // Extra common weight is the weight for spending the old funding
8465+ let _fee = check_v2_funding_inputs_sufficient(our_funding_contribution_satoshis, &our_funding_inputs, true, true, funding_feerate_per_kw)
8466+ .map_err(|err| APIError::APIMisuseError { err: format!(
8467+ "Insufficient inputs for splicing; channel ID {}, err {}",
8468+ self.context.channel_id(), err,
8469+ )})?;
83748470
83758471 self.pending_splice_pre = Some(PendingSplice {
83768472 our_funding_contribution: our_funding_contribution_satoshis,
@@ -8472,10 +8568,13 @@ impl<SP: Deref> FundedChannel<SP> where
84728568
84738569 let pre_channel_value = self.funding.get_value_satoshis();
84748570 let post_channel_value = PendingSplice::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution_satoshis);
8475- let post_balance = PendingSplice::add_checked(self.funding.value_to_self_msat, our_funding_contribution);
8476- // Early check for reserve requirement, assuming maximum balance of full channel value
8571+ let pre_balance_self = self.funding.value_to_self_msat;
8572+ let post_balance_self = PendingSplice::add_checked(pre_balance_self, our_funding_contribution);
8573+ let pre_balance_counterparty = pre_channel_value.saturating_sub(pre_balance_self);
8574+ let post_balance_counterparty = post_channel_value.saturating_sub(post_balance_self);
8575+ // Pre-check for reserve requirement
84778576 // This will also be checked later at tx_complete
8478- let _res = self.context.check_balance_meets_reserve_requirements(post_balance , post_channel_value)?;
8577+ 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)?;
84798578 Ok(())
84808579 }
84818580
@@ -11067,8 +11166,12 @@ mod tests {
1106711166 use bitcoin::constants::ChainHash;
1106811167 use bitcoin::script::{ScriptBuf, Builder};
1106911168 use bitcoin::transaction::{Transaction, TxOut, Version};
11169+ #[cfg(splicing)]
11170+ use bitcoin::transaction::TxIn;
1107011171 use bitcoin::opcodes;
1107111172 use bitcoin::network::Network;
11173+ #[cfg(splicing)]
11174+ use bitcoin::Weight;
1107211175 use crate::ln::onion_utils::INVALID_ONION_BLINDING;
1107311176 use crate::types::payment::{PaymentHash, PaymentPreimage};
1107411177 use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
@@ -12875,6 +12978,114 @@ mod tests {
1287512978 );
1287612979 }
1287712980
12981+ #[cfg(splicing)]
12982+ fn funding_input_sats(input_value_sats: u64) -> (TxIn, Transaction, Weight) {
12983+ use crate::sign::P2WPKH_WITNESS_WEIGHT;
12984+
12985+ let input_1_prev_out = TxOut { value: Amount::from_sat(input_value_sats), script_pubkey: ScriptBuf::default() };
12986+ let input_1_prev_tx = Transaction {
12987+ input: vec![], output: vec![input_1_prev_out],
12988+ version: Version::TWO, lock_time: bitcoin::absolute::LockTime::ZERO,
12989+ };
12990+ let input_1_txin = TxIn {
12991+ previous_output: bitcoin::OutPoint { txid: input_1_prev_tx.compute_txid(), vout: 0 },
12992+ ..Default::default()
12993+ };
12994+ (input_1_txin, input_1_prev_tx, Weight::from_wu(P2WPKH_WITNESS_WEIGHT))
12995+ }
12996+
12997+ #[cfg(splicing)]
12998+ #[test]
12999+ fn test_check_v2_funding_inputs_sufficient() {
13000+ use crate::ln::channel::check_v2_funding_inputs_sufficient;
13001+
13002+ // positive case, inputs well over intended contribution
13003+ assert_eq!(
13004+ check_v2_funding_inputs_sufficient(
13005+ 220_000,
13006+ &[
13007+ funding_input_sats(200_000),
13008+ funding_input_sats(100_000),
13009+ ],
13010+ true,
13011+ true,
13012+ 2000,
13013+ ).unwrap(),
13014+ 1948,
13015+ );
13016+
13017+ // negative case, inputs clearly insufficient
13018+ {
13019+ let res = check_v2_funding_inputs_sufficient(
13020+ 220_000,
13021+ &[
13022+ funding_input_sats(100_000),
13023+ ],
13024+ true,
13025+ true,
13026+ 2000,
13027+ );
13028+ assert_eq!(
13029+ format!("{:?}", res.err().unwrap()),
13030+ "Warn: Total input amount 100000 is lower than needed for contribution 220000, considering fees of 1410. Need more inputs.",
13031+ );
13032+ }
13033+
13034+ // barely covers
13035+ {
13036+ let expected_fee: u64 = 1948;
13037+ assert_eq!(
13038+ check_v2_funding_inputs_sufficient(
13039+ (300_000 - expected_fee - 20) as i64,
13040+ &[
13041+ funding_input_sats(200_000),
13042+ funding_input_sats(100_000),
13043+ ],
13044+ true,
13045+ true,
13046+ 2000,
13047+ ).unwrap(),
13048+ expected_fee,
13049+ );
13050+ }
13051+
13052+ // higher fee rate, does not cover
13053+ {
13054+ let res = check_v2_funding_inputs_sufficient(
13055+ 298032,
13056+ &[
13057+ funding_input_sats(200_000),
13058+ funding_input_sats(100_000),
13059+ ],
13060+ true,
13061+ true,
13062+ 2200,
13063+ );
13064+ assert_eq!(
13065+ format!("{:?}", res.err().unwrap()),
13066+ "Warn: Total input amount 300000 is lower than needed for contribution 298032, considering fees of 2143. Need more inputs.",
13067+ );
13068+ }
13069+
13070+ // barely covers, less fees (no extra weight, no init)
13071+ {
13072+ let expected_fee: u64 = 1076;
13073+ assert_eq!(
13074+ check_v2_funding_inputs_sufficient(
13075+ (300_000 - expected_fee - 20) as i64,
13076+ &[
13077+ funding_input_sats(200_000),
13078+ funding_input_sats(100_000),
13079+ ],
13080+ false,
13081+ false,
13082+ 2000,
13083+ ).unwrap(),
13084+ expected_fee,
13085+ );
13086+ }
13087+ }
13088+
1287813089 #[cfg(splicing)]
1287913090 fn get_pre_and_post(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64) -> (u64, u64) {
1288013091 use crate::ln::channel::PendingSplice;
0 commit comments