Account for shared input EMPTY_SCRIPT_SIG_WEIGHT
What changed, and why it matters
This commit fixes a fee-calculation bug in an experimental Lightning channel-splicing feature. When two parties splice a channel, the initiator pays the on-chain Bitcoin transaction fee. The code previously forgot to count a small but mandatory 8-weight-unit 'empty script signature' cost for the old funding input it re-spends. That made the initiator's fee estimate slightly too low. The fix adds that missing weight, so the initiator contributes enough fee and the splice transaction is more likely to confirm at the intended fee rate. It is a correctness/economic bug, not a direct theft-of-funds vulnerability, but a low fee could cause a transaction to stall or be dropped from mempools.
Review whether any deployed splicing code path could have produced under-fee splice transactions and, if so, monitor for stuck transactions or consider bumping. Otherwise, treat as a normal correctness fix and ensure splicing tests cover fee edge cases.
Security signals we found
Fee underestimation in transaction building
Splicing feature gated by #[cfg(splicing)] experimental flag
Initiator-pays fee model creates economic imbalance if estimate is wrong
No input validation bypass or memory-safety issue present
Constants-only change with test expectation updates
Evidence from the diff
In rust-lightning’s splicing code, estimate_v2_funding_transaction_fee/check_v2_funding_inputs_sufficient computes the initiator’s share of the new funding transaction fee. For a splice, the old funding output is an additional shared input whose cost the initiator must cover. The existing code added FUNDING_TRANSACTION_WITNESS_WEIGHT for the old input’s witness but omitted EMPTY_SCRIPT_SIG_WEIGHT (the non-witness bytes of an input: outpoint, sequence, scriptSig length). The patch imports EMPTY_SCRIPT_SIG_WEIGHT under #[cfg(splicing)] and adds it to total_input_satisfaction_weight when is_initiator && is_splice. It also renames witness_weight to input_satisfaction_weight for clarity. Test expectations are updated by +8 weight units, matching the added constant.
Changed components
lightning/src/ln/channel.rsestimate_v2_funding_transaction_feecheck_v2_funding_inputs_sufficientSplicing funding transaction fee estimationInspect captured patch +13 / −10
diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index 2edeaa4..c5c0f39 100644
--- a/lightning/src/ln/channel.rs
+++ b/lightning/src/ln/channel.rs
@@ -38,6 +38,8 @@ use crate::chain::channelmonitor::{
use crate::chain::transaction::{OutPoint, TransactionData};
use crate::chain::BestBlock;
use crate::events::bump_transaction::BASE_INPUT_WEIGHT;
+#[cfg(splicing)]
+use crate::events::bump_transaction::EMPTY_SCRIPT_SIG_WEIGHT;
use crate::events::ClosureReason;
use crate::ln::chan_utils;
#[cfg(splicing)]
@@ -5879,18 +5881,18 @@ fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satos
/// Estimate our part of the fee of the new funding transaction.
/// input_count: Number of contributed inputs.
-/// witness_weight: The witness weight for contributed inputs.
+/// input_satisfaction_weight: The satisfaction weight for contributed inputs.
#[allow(dead_code)] // TODO(dual_funding): TODO(splicing): Remove allow once used.
#[rustfmt::skip]
fn estimate_v2_funding_transaction_fee(
- is_initiator: bool, input_count: usize, witness_weight: Weight,
+ is_initiator: bool, input_count: usize, input_satisfaction_weight: Weight,
funding_feerate_sat_per_1000_weight: u32,
) -> u64 {
// Inputs
let mut weight = (input_count as u64) * BASE_INPUT_WEIGHT;
// Witnesses
- weight = weight.saturating_add(witness_weight.to_wu());
+ weight = weight.saturating_add(input_satisfaction_weight.to_wu());
// If we are the initiator, we must pay for weight of all common fields in the funding transaction.
if is_initiator {
@@ -5919,14 +5921,15 @@ fn check_v2_funding_inputs_sufficient(
contribution_amount: i64, funding_inputs: &[(TxIn, Transaction, Weight)], is_initiator: bool,
is_splice: bool, funding_feerate_sat_per_1000_weight: u32,
) -> Result<u64, ChannelError> {
- let mut total_input_witness_weight = Weight::from_wu(funding_inputs.iter().map(|(_, _, w)| w.to_wu()).sum());
+ let mut total_input_satisfaction_weight = Weight::from_wu(funding_inputs.iter().map(|(_, _, w)| w.to_wu()).sum());
let mut funding_inputs_len = funding_inputs.len();
if is_initiator && is_splice {
// consider the weight of the input and witness needed for spending the old funding transaction
funding_inputs_len += 1;
- total_input_witness_weight += Weight::from_wu(FUNDING_TRANSACTION_WITNESS_WEIGHT);
+ total_input_satisfaction_weight +=
+ Weight::from_wu(EMPTY_SCRIPT_SIG_WEIGHT + FUNDING_TRANSACTION_WITNESS_WEIGHT);
}
- let estimated_fee = estimate_v2_funding_transaction_fee(is_initiator, funding_inputs_len, total_input_witness_weight, funding_feerate_sat_per_1000_weight);
+ let estimated_fee = estimate_v2_funding_transaction_fee(is_initiator, funding_inputs_len, total_input_satisfaction_weight, funding_feerate_sat_per_1000_weight);
let mut total_input_sats = 0u64;
for (idx, input) in funding_inputs.iter().enumerate() {
@@ -15931,7 +15934,7 @@ mod tests {
true,
2000,
).unwrap(),
- 2268,
+ 2276,
);
// negative case, inputs clearly insufficient
@@ -15947,13 +15950,13 @@ mod tests {
);
assert_eq!(
format!("{:?}", res.err().unwrap()),
- "Warn: Total input amount 100000 is lower than needed for contribution 220000, considering fees of 1730. Need more inputs.",
+ "Warn: Total input amount 100000 is lower than needed for contribution 220000, considering fees of 1738. Need more inputs.",
);
}
// barely covers
{
- let expected_fee: u64 = 2268;
+ let expected_fee: u64 = 2276;
assert_eq!(
check_v2_funding_inputs_sufficient(
(300_000 - expected_fee - 20) as i64,
@@ -15983,7 +15986,7 @@ mod tests {
);
assert_eq!(
format!("{:?}", res.err().unwrap()),
- "Warn: Total input amount 300000 is lower than needed for contribution 298032, considering fees of 2495. Need more inputs.",
+ "Warn: Total input amount 300000 is lower than needed for contribution 298032, considering fees of 2504. Need more inputs.",
);
}
Why this scored 44/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.