@@ -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;
@@ -4691,6 +4693,58 @@ fn estimate_v2_funding_transaction_fee(
46914693 fee_for_weight(funding_feerate_sat_per_1000_weight, weight)
46924694}
46934695
4696+ /// Verify that the provided inputs by a counterparty to the funding transaction are enough
4697+ /// to cover the intended contribution amount *plus* the proportional fees of the counterparty.
4698+ /// Fees are computed using `estimate_v2_funding_transaction_fee`, and contain
4699+ /// the fees of the inputs, fees of the inputs weight, and for the initiator,
4700+ /// the fees of the common fields as well as the output and extra input weights.
4701+ /// Returns estimated (partial) fees as additional information
4702+ #[cfg(splicing)]
4703+ pub(super) fn check_v2_funding_inputs_sufficient(
4704+ contribution_amount: i64, funding_inputs: &[(TxIn, Transaction, Weight)], is_initiator: bool,
4705+ is_splice: bool, funding_feerate_sat_per_1000_weight: u32,
4706+ ) -> Result<u64, ChannelError> {
4707+ let mut total_input_witness_weight = Weight::from_wu(funding_inputs.iter().map(|(_, _, w)| w.to_wu()).sum());
4708+ if is_initiator && is_splice {
4709+ // consider the weight of the witness needed for spending the old funding transaction
4710+ total_input_witness_weight += Weight::from_wu(FUNDING_TRANSACTION_WITNESS_WEIGHT);
4711+ }
4712+ let estimated_fee = estimate_v2_funding_transaction_fee(is_initiator, funding_inputs.len(), total_input_witness_weight, funding_feerate_sat_per_1000_weight);
4713+
4714+ let mut total_input_sats = 0u64;
4715+ for (idx, input) in funding_inputs.iter().enumerate() {
4716+ if let Some(output) = input.1.output.get(input.0.previous_output.vout as usize) {
4717+ total_input_sats = total_input_sats.saturating_add(output.value.to_sat());
4718+ } else {
4719+ return Err(ChannelError::Warn(format!(
4720+ "Transaction with txid {} does not have an output with vout of {} corresponding to TxIn at funding_inputs[{}]",
4721+ input.1.compute_txid(), input.0.previous_output.vout, idx
4722+ )));
4723+ }
4724+ }
4725+
4726+ // If the inputs are enough to cover intended contribution amount, with fees even when
4727+ // there is a change output, we are fine.
4728+ // If the inputs are less, but enough to cover intended contribution amount, with
4729+ // (lower) fees with no change, we are also fine (change will not be generated).
4730+ // So it's enough to check considering the lower, no-change fees.
4731+ //
4732+ // Note: dust limit is not relevant in this check.
4733+ //
4734+ // TODO(splicing): refine check including the fact wether a change will be added or not.
4735+ // Can be done once dual funding preparation is included.
4736+
4737+ let minimal_input_amount_needed = contribution_amount.saturating_add(estimated_fee as i64);
4738+ if (total_input_sats as i64) < minimal_input_amount_needed {
4739+ Err(ChannelError::Warn(format!(
4740+ "Total input amount {} is lower than needed for contribution {}, considering fees of {}. Need more inputs.",
4741+ total_input_sats, contribution_amount, estimated_fee,
4742+ )))
4743+ } else {
4744+ Ok(estimated_fee)
4745+ }
4746+ }
4747+
46944748/// Context for dual-funded channels.
46954749pub(super) struct DualFundingChannelContext {
46964750 /// The amount in satoshis we will be contributing to the channel.
@@ -8355,7 +8409,7 @@ impl<SP: Deref> FundedChannel<SP> where
83558409 /// Includes the witness weight for this input (e.g. P2WPKH_WITNESS_WEIGHT=109 for typical P2WPKH inputs).
83568410 #[cfg(splicing)]
83578411 pub fn splice_channel(&mut self, our_funding_contribution_satoshis: i64,
8358- _our_funding_inputs : &Vec<(TxIn, Transaction, Weight)>,
8412+ our_funding_inputs : &Vec<(TxIn, Transaction, Weight)>,
83598413 funding_feerate_per_kw: u32, locktime: u32,
83608414 ) -> Result<msgs::SpliceInit, APIError> {
83618415 // Check if a splice has been initiated already.
@@ -8389,6 +8443,13 @@ impl<SP: Deref> FundedChannel<SP> where
83898443 // Note: post-splice channel value is not yet known at this point, counterparty contribution is not known
83908444 // (Cannot test for miminum required post-splice channel value)
83918445
8446+ // Check that inputs are sufficient to cover our contribution.
8447+ // Extra common weight is the weight for spending the old funding
8448+ let _fee = check_v2_funding_inputs_sufficient(our_funding_contribution_satoshis, &our_funding_inputs, true, true, funding_feerate_per_kw)
8449+ .map_err(|err| APIError::APIMisuseError { err: format!(
8450+ "Insufficient inputs for splicing; channel ID {}, err {}",
8451+ self.context.channel_id(), err,
8452+ )})?;
83928453
83938454 self.pending_splice_pre = Some(PendingSplice {
83948455 our_funding_contribution: our_funding_contribution_satoshis,
@@ -8440,7 +8501,8 @@ impl<SP: Deref> FundedChannel<SP> where
84408501
84418502 if their_funding_contribution_satoshis.saturating_add(our_funding_contribution_satoshis) < 0 {
84428503 return Err(ChannelError::Warn(format!(
8443- "Splice-out not supported, only splice in, relative {} + {}",
8504+ "Splice-out not supported, only splice in, contribution is {} ({} + {})",
8505+ their_funding_contribution_satoshis + our_funding_contribution_satoshis,
84448506 their_funding_contribution_satoshis, our_funding_contribution_satoshis,
84458507 )));
84468508 }
@@ -11050,8 +11112,12 @@ mod tests {
1105011112 use bitcoin::constants::ChainHash;
1105111113 use bitcoin::script::{ScriptBuf, Builder};
1105211114 use bitcoin::transaction::{Transaction, TxOut, Version};
11115+ #[cfg(splicing)]
11116+ use bitcoin::transaction::TxIn;
1105311117 use bitcoin::opcodes;
1105411118 use bitcoin::network::Network;
11119+ #[cfg(splicing)]
11120+ use bitcoin::Weight;
1105511121 use crate::ln::onion_utils::INVALID_ONION_BLINDING;
1105611122 use crate::types::payment::{PaymentHash, PaymentPreimage};
1105711123 use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
@@ -12854,6 +12920,114 @@ mod tests {
1285412920 );
1285512921 }
1285612922
12923+ #[cfg(splicing)]
12924+ fn funding_input_sats(input_value_sats: u64) -> (TxIn, Transaction, Weight) {
12925+ use crate::sign::P2WPKH_WITNESS_WEIGHT;
12926+
12927+ let input_1_prev_out = TxOut { value: Amount::from_sat(input_value_sats), script_pubkey: ScriptBuf::default() };
12928+ let input_1_prev_tx = Transaction {
12929+ input: vec![], output: vec![input_1_prev_out],
12930+ version: Version::TWO, lock_time: bitcoin::absolute::LockTime::ZERO,
12931+ };
12932+ let input_1_txin = TxIn {
12933+ previous_output: bitcoin::OutPoint { txid: input_1_prev_tx.compute_txid(), vout: 0 },
12934+ ..Default::default()
12935+ };
12936+ (input_1_txin, input_1_prev_tx, Weight::from_wu(P2WPKH_WITNESS_WEIGHT))
12937+ }
12938+
12939+ #[cfg(splicing)]
12940+ #[test]
12941+ fn test_check_v2_funding_inputs_sufficient() {
12942+ use crate::ln::channel::check_v2_funding_inputs_sufficient;
12943+
12944+ // positive case, inputs well over intended contribution
12945+ assert_eq!(
12946+ check_v2_funding_inputs_sufficient(
12947+ 220_000,
12948+ &[
12949+ funding_input_sats(200_000),
12950+ funding_input_sats(100_000),
12951+ ],
12952+ true,
12953+ true,
12954+ 2000,
12955+ ).unwrap(),
12956+ 1948,
12957+ );
12958+
12959+ // negative case, inputs clearly insufficient
12960+ {
12961+ let res = check_v2_funding_inputs_sufficient(
12962+ 220_000,
12963+ &[
12964+ funding_input_sats(100_000),
12965+ ],
12966+ true,
12967+ true,
12968+ 2000,
12969+ );
12970+ assert_eq!(
12971+ format!("{:?}", res.err().unwrap()),
12972+ "Warn: Total input amount 100000 is lower than needed for contribution 220000, considering fees of 1410. Need more inputs.",
12973+ );
12974+ }
12975+
12976+ // barely covers
12977+ {
12978+ let expected_fee: u64 = 1948;
12979+ assert_eq!(
12980+ check_v2_funding_inputs_sufficient(
12981+ (300_000 - expected_fee - 20) as i64,
12982+ &[
12983+ funding_input_sats(200_000),
12984+ funding_input_sats(100_000),
12985+ ],
12986+ true,
12987+ true,
12988+ 2000,
12989+ ).unwrap(),
12990+ expected_fee,
12991+ );
12992+ }
12993+
12994+ // higher fee rate, does not cover
12995+ {
12996+ let res = check_v2_funding_inputs_sufficient(
12997+ 298032,
12998+ &[
12999+ funding_input_sats(200_000),
13000+ funding_input_sats(100_000),
13001+ ],
13002+ true,
13003+ true,
13004+ 2200,
13005+ );
13006+ assert_eq!(
13007+ format!("{:?}", res.err().unwrap()),
13008+ "Warn: Total input amount 300000 is lower than needed for contribution 298032, considering fees of 2143. Need more inputs.",
13009+ );
13010+ }
13011+
13012+ // barely covers, less fees (no extra weight, no init)
13013+ {
13014+ let expected_fee: u64 = 1076;
13015+ assert_eq!(
13016+ check_v2_funding_inputs_sufficient(
13017+ (300_000 - expected_fee - 20) as i64,
13018+ &[
13019+ funding_input_sats(200_000),
13020+ funding_input_sats(100_000),
13021+ ],
13022+ false,
13023+ false,
13024+ 2000,
13025+ ).unwrap(),
13026+ expected_fee,
13027+ );
13028+ }
13029+ }
13030+
1285713031 #[cfg(splicing)]
1285813032 fn get_pre_and_post(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64) -> (u64, u64) {
1285913033 use crate::ln::channel::PendingSplice;
0 commit comments