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