Skip to content

Commit 11f95e0

Browse files
committed
Add method check_v2_funding_inputs_sufficient()
1 parent e1d22fa commit 11f95e0

File tree

2 files changed

+170
-35
lines changed

2 files changed

+170
-35
lines changed

lightning/src/ln/channel.rs

Lines changed: 169 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4737,6 +4737,54 @@ pub(super) fn calculate_our_funding_satoshis(
47374737
}
47384738
}
47394739

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+
47404788
/// Context for dual-funded channels.
47414789
pub(super) struct DualFundingChannelContext {
47424790
/// The amount in satoshis we will be contributing to the channel.
@@ -8434,28 +8482,10 @@ impl<SP: Deref> FundedChannel<SP> where
84348482
// Note: post-splice channel value is not yet known at this point, counterparty contribution is not known
84358483
// (Cannot test for miminum required post-splice channel value)
84368484

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)?;
84598489

84608490
self.pending_splice_pre = Some(PendingSplice {
84618491
our_funding_contribution: our_funding_contribution_satoshis,
@@ -11134,8 +11164,12 @@ mod tests {
1113411164
use bitcoin::constants::ChainHash;
1113511165
use bitcoin::script::{ScriptBuf, Builder};
1113611166
use bitcoin::transaction::{Transaction, TxOut, Version};
11167+
#[cfg(splicing)]
11168+
use bitcoin::transaction::TxIn;
1113711169
use bitcoin::opcodes;
1113811170
use bitcoin::network::Network;
11171+
#[cfg(splicing)]
11172+
use bitcoin::Weight;
1113911173
use crate::ln::onion_utils::INVALID_ONION_BLINDING;
1114011174
use crate::types::payment::{PaymentHash, PaymentPreimage};
1114111175
use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint};
@@ -12942,15 +12976,126 @@ mod tests {
1294212976
);
1294312977
}
1294412978

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)]
1294613091
fn get_pre_and_post(pre_channel_value: u64, our_funding_contribution: i64, their_funding_contribution: i64) -> (u64, u64) {
1294713092
use crate::ln::channel::PendingSplice;
1294813093

1294913094
let post_channel_value = PendingSplice::compute_post_value(pre_channel_value, our_funding_contribution, their_funding_contribution);
1295013095
(pre_channel_value, post_channel_value)
1295113096
}
1295213097

12953-
#[cfg(all(test, splicing))]
13098+
#[cfg(splicing)]
1295413099
#[test]
1295513100
fn test_splice_compute_post_value() {
1295613101
{

lightning/src/ln/dual_funding_tests.rs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,7 @@ fn do_test_v2_channel_establishment(
6161
.collect();
6262

6363
// Alice creates a dual-funded channel as initiator.
64-
let funding_feerate = node_cfgs[0]
65-
.fee_estimator
66-
.get_est_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee);
67-
let funding_satoshis = calculate_our_funding_satoshis(
68-
true,
69-
&initiator_funding_inputs[..],
70-
total_weight,
71-
funding_feerate,
72-
MIN_CHAN_DUST_LIMIT_SATOSHIS,
73-
)
74-
.unwrap();
64+
let funding_satoshis = session.funding_input_sats;
7565
let mut channel = PendingV2Channel::new_outbound(
7666
&LowerBoundedFeeEstimator(node_cfgs[0].fee_estimator),
7767
&nodes[0].node.entropy_source,

0 commit comments

Comments
 (0)