Skip to content

Commit 84f659e

Browse files
committed
Add method check_v2_funding_inputs_sufficient()
1 parent 49da875 commit 84f659e

File tree

2 files changed

+181
-42
lines changed

2 files changed

+181
-42
lines changed

lightning/src/ln/channel.rs

Lines changed: 169 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4611,6 +4611,54 @@ pub(super) fn calculate_our_funding_satoshis(
46114611
}
46124612
}
46134613

4614+
/// Verify that the provided inputs by a counterparty to the funding transaction are enough
4615+
/// to cover the intended contribution amount *plus* the proportional fees of the counterparty.
4616+
/// Fees are computed using `estimate_v2_funding_transaction_fee`, and contain
4617+
/// the fees of the inputs, fees of the inputs weight, and for the initiator,
4618+
/// the fees of the common fields as well as the output and extra input weights.
4619+
/// Returns estimated (partial) fees as additional information
4620+
pub(super) fn check_v2_funding_inputs_sufficient(
4621+
contribution_amount: i64, funding_inputs: &[(TxIn, Transaction, Weight)], is_initiator: bool,
4622+
extra_common_input_weight: Option<Weight>, funding_feerate_sat_per_1000_weight: u32,
4623+
) -> Result<u64, ChannelError> {
4624+
let mut total_input_witness_weight = Weight::from_wu(funding_inputs.iter().map(|(_, _, w)| w.to_wu()).sum());
4625+
if is_initiator {
4626+
if let Some(extra) = extra_common_input_weight {
4627+
total_input_witness_weight += extra;
4628+
}
4629+
}
4630+
let estimated_fee = estimate_v2_funding_transaction_fee(is_initiator, funding_inputs.len(), total_input_witness_weight, funding_feerate_sat_per_1000_weight);
4631+
4632+
let mut total_input_sats = 0u64;
4633+
for (idx, input) in funding_inputs.iter().enumerate() {
4634+
if let Some(output) = input.1.output.get(input.0.previous_output.vout as usize) {
4635+
total_input_sats = total_input_sats.saturating_add(output.value.to_sat());
4636+
} else {
4637+
return Err(ChannelError::Warn(format!(
4638+
"Transaction with txid {} does not have an output with vout of {} corresponding to TxIn at funding_inputs[{}]",
4639+
input.1.compute_txid(), input.0.previous_output.vout, idx
4640+
)));
4641+
}
4642+
}
4643+
4644+
// If the inputs are enough to cover intended contribution amount, with fees even when
4645+
// there is a change output, we are fine.
4646+
// If the inputs are less, but enough to cover intended contribution amount, with
4647+
// (lower) fees with no change, we are also fine (change will not be generated).
4648+
// So it's enough to check considering the lower, no-change fees.
4649+
// Note: dust limit is not relevant in this check.
4650+
4651+
let minimal_input_amount_needed = contribution_amount.saturating_add(estimated_fee as i64);
4652+
if (total_input_sats as i64) < minimal_input_amount_needed {
4653+
Err(ChannelError::Warn(format!(
4654+
"Total input amount {} is lower than needed for contribution {}, considering fees of {}. Need more inputs.",
4655+
total_input_sats, contribution_amount, estimated_fee,
4656+
)))
4657+
} else {
4658+
Ok(estimated_fee)
4659+
}
4660+
}
4661+
46144662
/// Context for dual-funded channels.
46154663
pub(super) struct DualFundingChannelContext {
46164664
/// The amount in satoshis we will be contributing to the channel.
@@ -8259,28 +8307,10 @@ impl<SP: Deref> FundedChannel<SP> where
82598307
// Note: post-splice channel value is not yet known at this point, counterparty contribution is not known
82608308
// (Cannot test for miminum required post-splice channel value)
82618309

8262-
// Pre-check that inputs are sufficient to cover our contribution.
8263-
// Note: fees are not taken into account here.
8264-
let sum_input: u64 = our_funding_inputs.iter().map(
8265-
|(txin, tx, _)| tx.output.get(txin.previous_output.vout as usize).map(|tx| tx.value.to_sat()).unwrap_or(0)
8266-
).sum();
8267-
8268-
// The +1 is to include the input of the old funding
8269-
let funding_input_count = our_funding_inputs.len() + 1;
8270-
// Input witness weight, extended with weight for spending old funding
8271-
let total_witness_weight = Weight::from_wu(
8272-
our_funding_inputs.iter().map(|(_, _, w)| w.to_wu()).sum::<u64>()
8273-
.saturating_add(FUNDING_TRANSACTION_WITNESS_WEIGHT)
8274-
);
8275-
let estimated_fee = estimate_v2_funding_transaction_fee(true, funding_input_count, total_witness_weight, funding_feerate_per_kw);
8276-
let available_input = sum_input.saturating_sub(estimated_fee);
8277-
8278-
if (available_input as i64) < our_funding_contribution_satoshis {
8279-
return Err(ChannelError::Warn(format!(
8280-
"Provided inputs are insufficient for our contribution, {} {}",
8281-
sum_input, our_funding_contribution_satoshis,
8282-
)));
8283-
}
8310+
// Check that inputs are sufficient to cover our contribution.
8311+
// Extra common weight is the weight for spending the old funding
8312+
let extra_input_weight = Weight::from_wu(FUNDING_TRANSACTION_WITNESS_WEIGHT);
8313+
let _fee = check_v2_funding_inputs_sufficient(our_funding_contribution_satoshis, &our_funding_inputs, true, Some(extra_input_weight), funding_feerate_per_kw)?;
82848314

82858315
self.pending_splice_pre = Some(PendingSplice {
82868316
our_funding_contribution: our_funding_contribution_satoshis,
@@ -10727,8 +10757,12 @@ mod tests {
1072710757
use bitcoin::constants::ChainHash;
1072810758
use bitcoin::script::{ScriptBuf, Builder};
1072910759
use bitcoin::transaction::{Transaction, TxOut, Version};
10760+
#[cfg(splicing)]
10761+
use bitcoin::transaction::TxIn;
1073010762
use bitcoin::opcodes;
1073110763
use bitcoin::network::Network;
10764+
#[cfg(splicing)]
10765+
use bitcoin::Weight;
1073210766
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
1073310767
use crate::types::payment::{PaymentHash, PaymentPreimage};
1073410768
use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
@@ -12536,15 +12570,126 @@ mod tests {
1253612570
);
1253712571
}
1253812572

12539-
#[cfg(all(test, splicing))]
12573+
#[cfg(splicing)]
12574+
fn funding_input_sats(input_value_sats: u64) -> (TxIn, Transaction, Weight) {
12575+
use crate::sign::P2WPKH_WITNESS_WEIGHT;
12576+
12577+
let input_1_prev_out = TxOut { value: Amount::from_sat(input_value_sats), script_pubkey: ScriptBuf::default() };
12578+
let input_1_prev_tx = Transaction {
12579+
input: vec![], output: vec![input_1_prev_out],
12580+
version: Version::TWO, lock_time: bitcoin::absolute::LockTime::ZERO,
12581+
};
12582+
let input_1_txin = TxIn {
12583+
previous_output: bitcoin::OutPoint { txid: input_1_prev_tx.compute_txid(), vout: 0 },
12584+
..Default::default()
12585+
};
12586+
(input_1_txin, input_1_prev_tx, Weight::from_wu(P2WPKH_WITNESS_WEIGHT))
12587+
}
12588+
12589+
#[cfg(splicing)]
12590+
#[test]
12591+
fn test_check_v2_funding_inputs_sufficient() {
12592+
use crate::ln::chan_utils::FUNDING_TRANSACTION_WITNESS_WEIGHT;
12593+
use crate::ln::channel::check_v2_funding_inputs_sufficient;
12594+
use bitcoin::Weight;
12595+
12596+
// positive case, inputs well over intended contribution
12597+
assert_eq!(
12598+
check_v2_funding_inputs_sufficient(
12599+
220_000,
12600+
&[
12601+
funding_input_sats(200_000),
12602+
funding_input_sats(100_000),
12603+
],
12604+
true,
12605+
Some(Weight::from_wu(FUNDING_TRANSACTION_WITNESS_WEIGHT)),
12606+
2000,
12607+
).unwrap(),
12608+
1948,
12609+
);
12610+
12611+
// negative case, inputs clearly insufficient
12612+
{
12613+
let res = check_v2_funding_inputs_sufficient(
12614+
220_000,
12615+
&[
12616+
funding_input_sats(100_000),
12617+
],
12618+
true,
12619+
Some(Weight::from_wu(FUNDING_TRANSACTION_WITNESS_WEIGHT)),
12620+
2000,
12621+
);
12622+
assert_eq!(
12623+
format!("{:?}", res.err().unwrap()),
12624+
"Warn : Total input amount 100000 is lower than needed for contribution 220000, considering fees of 1410. Need more inputs.",
12625+
);
12626+
}
12627+
12628+
// barely covers
12629+
{
12630+
let expected_fee: u64 = 1948;
12631+
assert_eq!(
12632+
check_v2_funding_inputs_sufficient(
12633+
(300_000 - expected_fee - 20) as i64,
12634+
&[
12635+
funding_input_sats(200_000),
12636+
funding_input_sats(100_000),
12637+
],
12638+
true,
12639+
Some(Weight::from_wu(FUNDING_TRANSACTION_WITNESS_WEIGHT)),
12640+
2000,
12641+
).unwrap(),
12642+
expected_fee,
12643+
);
12644+
}
12645+
12646+
// higher fee rate, does not cover
12647+
{
12648+
let expected_fee: u64 = 1948;
12649+
let res = check_v2_funding_inputs_sufficient(
12650+
298032,
12651+
&[
12652+
funding_input_sats(200_000),
12653+
funding_input_sats(100_000),
12654+
],
12655+
true,
12656+
Some(Weight::from_wu(FUNDING_TRANSACTION_WITNESS_WEIGHT)),
12657+
2200,
12658+
);
12659+
assert_eq!(
12660+
format!("{:?}", res.err().unwrap()),
12661+
"Warn : Total input amount 300000 is lower than needed for contribution 298032, considering fees of 2143. Need more inputs.",
12662+
);
12663+
}
12664+
12665+
// barely covers, less fees (no extra weight, no init)
12666+
{
12667+
let expected_fee: u64 = 1076;
12668+
assert_eq!(
12669+
check_v2_funding_inputs_sufficient(
12670+
(300_000 - expected_fee - 20) as i64,
12671+
&[
12672+
funding_input_sats(200_000),
12673+
funding_input_sats(100_000),
12674+
],
12675+
false,
12676+
None,
12677+
2000,
12678+
).unwrap(),
12679+
expected_fee,
12680+
);
12681+
}
12682+
}
12683+
12684+
#[cfg(splicing)]
1254012685
fn get_pre_and_post(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64) -> (u64, u64) {
1254112686
use crate::ln::channel::PendingSplice;
1254212687

1254312688
let post_channel_value = PendingSplice::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution);
1254412689
(pre_channel_value, post_channel_value)
1254512690
}
1254612691

12547-
#[cfg(all(test, splicing))]
12692+
#[cfg(splicing)]
1254812693
#[test]
1254912694
fn test_splice_compute_post_value() {
1255012695
{

lightning/src/ln/dual_funding_tests.rs

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,13 @@
1111
1212
#[cfg(dual_funding)]
1313
use {
14-
crate::chain::chaininterface::{ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator},
14+
crate::chain::chaininterface::{ConfirmationTarget, LowerBoundedFeeEstimator},
1515
crate::events::{Event, MessageSendEvent, MessageSendEventsProvider},
1616
crate::ln::chan_utils::{
1717
make_funding_redeemscript, ChannelPublicKeys, ChannelTransactionParameters,
1818
CounterpartyChannelTransactionParameters,
1919
},
20-
crate::ln::channel::{
21-
calculate_our_funding_satoshis, PendingV2Channel, MIN_CHAN_DUST_LIMIT_SATOSHIS,
22-
},
20+
crate::ln::channel::PendingV2Channel,
2321
crate::ln::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint, RevocationBasepoint},
2422
crate::ln::functional_test_utils::*,
2523
crate::ln::msgs::ChannelMessageHandler,
@@ -29,12 +27,12 @@ use {
2927
crate::sign::ChannelSigner as _,
3028
crate::util::ser::TransactionU16LenLimited,
3129
crate::util::test_utils,
32-
bitcoin::Weight,
3330
};
3431

3532
#[cfg(dual_funding)]
3633
// Dual-funding: V2 Channel Establishment Tests
3734
struct V2ChannelEstablishmentTestSession {
35+
funding_input_sats: u64,
3836
initiator_input_value_satoshis: u64,
3937
}
4038

@@ -63,17 +61,7 @@ fn do_test_v2_channel_establishment(
6361
.collect();
6462

6563
// Alice creates a dual-funded channel as initiator.
66-
let funding_feerate = node_cfgs[0]
67-
.fee_estimator
68-
.get_est_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
69-
let funding_satoshis = calculate_our_funding_satoshis(
70-
true,
71-
&initiator_funding_inputs[..],
72-
total_weight,
73-
funding_feerate,
74-
MIN_CHAN_DUST_LIMIT_SATOSHIS,
75-
)
76-
.unwrap();
64+
let funding_satoshis = session.funding_input_sats;
7765
let mut channel = PendingV2Channel::new_outbound(
7866
&LowerBoundedFeeEstimator(node_cfgs[0].fee_estimator),
7967
&nodes[0].node.entropy_source,
@@ -263,12 +251,18 @@ fn do_test_v2_channel_establishment(
263251
fn test_v2_channel_establishment() {
264252
// Only initiator contributes, no persist pending
265253
do_test_v2_channel_establishment(
266-
V2ChannelEstablishmentTestSession { initiator_input_value_satoshis: 100_000 },
254+
V2ChannelEstablishmentTestSession {
255+
funding_input_sats: 100_000,
256+
initiator_input_value_satoshis: 150_000,
257+
},
267258
false,
268259
);
269260
// Only initiator contributes, persist pending
270261
do_test_v2_channel_establishment(
271-
V2ChannelEstablishmentTestSession { initiator_input_value_satoshis: 100_000 },
262+
V2ChannelEstablishmentTestSession {
263+
funding_input_sats: 100_000,
264+
initiator_input_value_satoshis: 150_000,
265+
},
272266
true,
273267
);
274268
}

0 commit comments

Comments
 (0)