@@ -4737,6 +4737,54 @@ pub(super) fn calculate_our_funding_satoshis(
4737
4737
}
4738
4738
}
4739
4739
4740
+ /// Verify that the provided inputs by a counterparty to the funding transaction are enough
4741
+ /// to cover the intended contribution amount *plus* the proportional fees of the counterparty.
4742
+ /// Fees are computed using `estimate_v2_funding_transaction_fee`, and contain
4743
+ /// the fees of the inputs, fees of the inputs weight, and for the initiator,
4744
+ /// the fees of the common fields as well as the output and extra input weights.
4745
+ /// Returns estimated (partial) fees as additional information
4746
+ pub(super) fn check_v2_funding_inputs_sufficient(
4747
+ contribution_amount: i64, funding_inputs: &[(TxIn, Transaction, Weight)], is_initiator: bool,
4748
+ extra_common_input_weight: Option<Weight>, funding_feerate_sat_per_1000_weight: u32,
4749
+ ) -> Result<u64, ChannelError> {
4750
+ let mut total_input_witness_weight = Weight::from_wu(funding_inputs.iter().map(|(_, _, w)| w.to_wu()).sum());
4751
+ if is_initiator {
4752
+ if let Some(extra) = extra_common_input_weight {
4753
+ total_input_witness_weight += extra;
4754
+ }
4755
+ }
4756
+ let estimated_fee = estimate_v2_funding_transaction_fee(is_initiator, funding_inputs.len(), total_input_witness_weight, funding_feerate_sat_per_1000_weight);
4757
+
4758
+ let mut total_input_sats = 0u64;
4759
+ for (idx, input) in funding_inputs.iter().enumerate() {
4760
+ if let Some(output) = input.1.output.get(input.0.previous_output.vout as usize) {
4761
+ total_input_sats = total_input_sats.saturating_add(output.value.to_sat());
4762
+ } else {
4763
+ return Err(ChannelError::Warn(format!(
4764
+ "Transaction with txid {} does not have an output with vout of {} corresponding to TxIn at funding_inputs[{}]",
4765
+ input.1.compute_txid(), input.0.previous_output.vout, idx
4766
+ )));
4767
+ }
4768
+ }
4769
+
4770
+ // If the inputs are enough to cover intended contribution amount, with fees even when
4771
+ // there is a change output, we are fine.
4772
+ // If the inputs are less, but enough to cover intended contribution amount, with
4773
+ // (lower) fees with no change, we are also fine (change will not be generated).
4774
+ // So it's enough to check considering the lower, no-change fees.
4775
+ // Note: dust limit is not relevant in this check.
4776
+
4777
+ let minimal_input_amount_needed = contribution_amount.saturating_add(estimated_fee as i64);
4778
+ if (total_input_sats as i64) < minimal_input_amount_needed {
4779
+ Err(ChannelError::Warn(format!(
4780
+ "Total input amount {} is lower than needed for contribution {}, considering fees of {}. Need more inputs.",
4781
+ total_input_sats, contribution_amount, estimated_fee,
4782
+ )))
4783
+ } else {
4784
+ Ok(estimated_fee)
4785
+ }
4786
+ }
4787
+
4740
4788
/// Context for dual-funded channels.
4741
4789
pub(super) struct DualFundingChannelContext {
4742
4790
/// The amount in satoshis we will be contributing to the channel.
@@ -8434,28 +8482,10 @@ impl<SP: Deref> FundedChannel<SP> where
8434
8482
// Note: post-splice channel value is not yet known at this point, counterparty contribution is not known
8435
8483
// (Cannot test for miminum required post-splice channel value)
8436
8484
8437
- // Pre-check that inputs are sufficient to cover our contribution.
8438
- // Note: fees are not taken into account here.
8439
- let sum_input: u64 = our_funding_inputs.iter().map(
8440
- |(txin, tx, _)| tx.output.get(txin.previous_output.vout as usize).map(|tx| tx.value.to_sat()).unwrap_or(0)
8441
- ).sum();
8442
-
8443
- // The +1 is to include the input of the old funding
8444
- let funding_input_count = our_funding_inputs.len() + 1;
8445
- // Input witness weight, extended with weight for spending old funding
8446
- let total_witness_weight = Weight::from_wu(
8447
- our_funding_inputs.iter().map(|(_, _, w)| w.to_wu()).sum::<u64>()
8448
- .saturating_add(FUNDING_TRANSACTION_WITNESS_WEIGHT)
8449
- );
8450
- let estimated_fee = estimate_v2_funding_transaction_fee(true, funding_input_count, total_witness_weight, funding_feerate_per_kw);
8451
- let available_input = sum_input.saturating_sub(estimated_fee);
8452
-
8453
- if (available_input as i64) < our_funding_contribution_satoshis {
8454
- return Err(ChannelError::Warn(format!(
8455
- "Provided inputs are insufficient for our contribution, {} {}",
8456
- sum_input, our_funding_contribution_satoshis,
8457
- )));
8458
- }
8485
+ // Check that inputs are sufficient to cover our contribution.
8486
+ // Extra common weight is the weight for spending the old funding
8487
+ let extra_input_weight = Weight::from_wu(FUNDING_TRANSACTION_WITNESS_WEIGHT);
8488
+ let _fee = check_v2_funding_inputs_sufficient(our_funding_contribution_satoshis, &our_funding_inputs, true, Some(extra_input_weight), funding_feerate_per_kw)?;
8459
8489
8460
8490
self.pending_splice_pre = Some(PendingSplice {
8461
8491
our_funding_contribution: our_funding_contribution_satoshis,
@@ -11134,8 +11164,12 @@ mod tests {
11134
11164
use bitcoin::constants::ChainHash;
11135
11165
use bitcoin::script::{ScriptBuf, Builder};
11136
11166
use bitcoin::transaction::{Transaction, TxOut, Version};
11167
+ #[cfg(splicing)]
11168
+ use bitcoin::transaction::TxIn;
11137
11169
use bitcoin::opcodes;
11138
11170
use bitcoin::network::Network;
11171
+ #[cfg(splicing)]
11172
+ use bitcoin::Weight;
11139
11173
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
11140
11174
use crate::types::payment::{PaymentHash, PaymentPreimage};
11141
11175
use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
@@ -12942,15 +12976,126 @@ mod tests {
12942
12976
);
12943
12977
}
12944
12978
12945
- #[cfg(all(test, splicing))]
12979
+ #[cfg(splicing)]
12980
+ fn funding_input_sats(input_value_sats: u64) -> (TxIn, Transaction, Weight) {
12981
+ use crate::sign::P2WPKH_WITNESS_WEIGHT;
12982
+
12983
+ let input_1_prev_out = TxOut { value: Amount::from_sat(input_value_sats), script_pubkey: ScriptBuf::default() };
12984
+ let input_1_prev_tx = Transaction {
12985
+ input: vec![], output: vec![input_1_prev_out],
12986
+ version: Version::TWO, lock_time: bitcoin::absolute::LockTime::ZERO,
12987
+ };
12988
+ let input_1_txin = TxIn {
12989
+ previous_output: bitcoin::OutPoint { txid: input_1_prev_tx.compute_txid(), vout: 0 },
12990
+ ..Default::default()
12991
+ };
12992
+ (input_1_txin, input_1_prev_tx, Weight::from_wu(P2WPKH_WITNESS_WEIGHT))
12993
+ }
12994
+
12995
+ #[cfg(splicing)]
12996
+ #[test]
12997
+ fn test_check_v2_funding_inputs_sufficient() {
12998
+ use crate::ln::chan_utils::FUNDING_TRANSACTION_WITNESS_WEIGHT;
12999
+ use crate::ln::channel::check_v2_funding_inputs_sufficient;
13000
+ use bitcoin::Weight;
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
+ Some(Weight::from_wu(FUNDING_TRANSACTION_WITNESS_WEIGHT)),
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
+ Some(Weight::from_wu(FUNDING_TRANSACTION_WITNESS_WEIGHT)),
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
+ Some(Weight::from_wu(FUNDING_TRANSACTION_WITNESS_WEIGHT)),
13046
+ 2000,
13047
+ ).unwrap(),
13048
+ expected_fee,
13049
+ );
13050
+ }
13051
+
13052
+ // higher fee rate, does not cover
13053
+ {
13054
+ let expected_fee: u64 = 1948;
13055
+ let res = check_v2_funding_inputs_sufficient(
13056
+ 298032,
13057
+ &[
13058
+ funding_input_sats(200_000),
13059
+ funding_input_sats(100_000),
13060
+ ],
13061
+ true,
13062
+ Some(Weight::from_wu(FUNDING_TRANSACTION_WITNESS_WEIGHT)),
13063
+ 2200,
13064
+ );
13065
+ assert_eq!(
13066
+ format!("{:?}", res.err().unwrap()),
13067
+ "Warn : Total input amount 300000 is lower than needed for contribution 298032, considering fees of 2143. Need more inputs.",
13068
+ );
13069
+ }
13070
+
13071
+ // barely covers, less fees (no extra weight, no init)
13072
+ {
13073
+ let expected_fee: u64 = 1076;
13074
+ assert_eq!(
13075
+ check_v2_funding_inputs_sufficient(
13076
+ (300_000 - expected_fee - 20) as i64,
13077
+ &[
13078
+ funding_input_sats(200_000),
13079
+ funding_input_sats(100_000),
13080
+ ],
13081
+ false,
13082
+ None,
13083
+ 2000,
13084
+ ).unwrap(),
13085
+ expected_fee,
13086
+ );
13087
+ }
13088
+ }
13089
+
13090
+ #[cfg(splicing)]
12946
13091
fn get_pre_and_post(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64) -> (u64, u64) {
12947
13092
use crate::ln::channel::PendingSplice;
12948
13093
12949
13094
let post_channel_value = PendingSplice::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution);
12950
13095
(pre_channel_value, post_channel_value)
12951
13096
}
12952
13097
12953
- #[cfg(all(test, splicing) )]
13098
+ #[cfg(splicing)]
12954
13099
#[test]
12955
13100
fn test_splice_compute_post_value() {
12956
13101
{
0 commit comments