Remove redundant is_initiator from FundingTemplate/FundingContribution
What changed, and why it matters
This commit removes an unused flag called is_initiator from internal funding/splicing data structures. The code now assumes the creator of a FundingContribution is always the splice initiator when estimating transaction fees, which the commit message says is intentionally conservative (it may overestimate fees rather than underestimate them). There is no direct evidence this fixes an exploitable vulnerability; it appears to be a code-cleanup/refactoring change with a minor behavioral shift in fee estimation.
Treat as a low-risk refactoring commit. Reviewers should confirm that FundingTemplate/FundingContribution can never be created by the splice acceptor and that the serialization change is backward-compatible for the intended deployment context. No urgent security action is indicated by the diff alone.
Security signals we found
Hard-codes initiator assumption in fee estimation
Changes TLV serialization layout for FundingContribution
Removes redundant state that could theoretically have become inconsistent
Comment explicitly notes conservative overestimation behavior
Evidence from the diff
The patch deletes the is_initiator field from FundingTemplate and FundingContribution and from the build_funding_contribution! macro. In the one place the field was consumed—estimate_transaction_fee—it is now hard-coded to true. The commit message explains this is safe because FundingTemplate is only created by the splice initiator, and the channel already tracks splice initiation via is_holder_quiescence_initiator. A side effect is that non-initiator fee estimation now uses initiator weight, which the comment explicitly calls conservative. Serialization of FundingContribution changes: TLV field 13 is reused for is_splice. Tests are updated to remove the field and one expected fee value is adjusted because the test case now uses is_splice=false (not initiator=false) to get the lower weight path.
Changed components
lightning/src/ln/funding.rslightning/src/ln/channel.rsFundingTemplateFundingContributionbuild_funding_contribution! macrosplice fee estimationInspect captured patch +24 / −66
diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index c71ee7a..85a23ca 100644
--- a/lightning/src/ln/channel.rs
+++ b/lightning/src/ln/channel.rs
@@ -12187,7 +12187,7 @@ where
satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT + FUNDING_TRANSACTION_WITNESS_WEIGHT,
};
- Ok(FundingTemplate::new(Some(shared_input), feerate, true))
+ Ok(FundingTemplate::new(Some(shared_input), feerate))
}
pub fn funding_contributed<L: Logger>(
@@ -13540,7 +13540,6 @@ where
}
let prev_funding_input = self.funding.to_splice_funding_input();
- let is_initiator = contribution.is_initiator();
let our_funding_contribution = match contribution.net_value() {
Ok(net_value) => net_value,
Err(e) => {
@@ -13557,7 +13556,7 @@ where
let (our_funding_inputs, our_funding_outputs) = contribution.into_tx_parts();
let context = FundingNegotiationContext {
- is_initiator,
+ is_initiator: true,
our_funding_contribution,
funding_tx_locktime: locktime,
funding_feerate_sat_per_1000_weight: funding_feerate_per_kw,
diff --git a/lightning/src/ln/funding.rs b/lightning/src/ln/funding.rs
index d18aca9..20319fa 100644
--- a/lightning/src/ln/funding.rs
+++ b/lightning/src/ln/funding.rs
@@ -44,26 +44,21 @@ pub struct FundingTemplate {
/// The fee rate to use for coin selection.
feerate: FeeRate,
-
- /// Whether the contributor initiated the funding, and thus is responsible for fees incurred for
- /// common fields and shared inputs and outputs.
- is_initiator: bool,
}
impl FundingTemplate {
/// Constructs a [`FundingTemplate`] for a splice using the provided shared input.
- pub(super) fn new(shared_input: Option<Input>, feerate: FeeRate, is_initiator: bool) -> Self {
- Self { shared_input, feerate, is_initiator }
+ pub(super) fn new(shared_input: Option<Input>, feerate: FeeRate) -> Self {
+ Self { shared_input, feerate }
}
}
macro_rules! build_funding_contribution {
- ($value_added:expr, $outputs:expr, $shared_input:expr, $feerate:expr, $is_initiator:expr, $wallet:ident, $($await:tt)*) => {{
+ ($value_added:expr, $outputs:expr, $shared_input:expr, $feerate:expr, $wallet:ident, $($await:tt)*) => {{
let value_added: Amount = $value_added;
let outputs: Vec<TxOut> = $outputs;
let shared_input: Option<Input> = $shared_input;
let feerate: FeeRate = $feerate;
- let is_initiator: bool = $is_initiator;
let value_removed = outputs.iter().map(|txout| txout.value).sum();
let is_splice = shared_input.is_some();
@@ -103,7 +98,10 @@ macro_rules! build_funding_contribution {
let CoinSelection { confirmed_utxos: inputs, change_output } = coin_selection;
- let estimated_fee = estimate_transaction_fee(&inputs, &outputs, is_initiator, is_splice, feerate);
+ // The caller creating a FundingContribution is always the initiator for fee estimation
+ // purposes — this is conservative, overestimating rather than underestimating fees if
+ // the node ends up as the acceptor.
+ let estimated_fee = estimate_transaction_fee(&inputs, &outputs, true, is_splice, feerate);
let contribution = FundingContribution {
value_added,
@@ -112,7 +110,6 @@ macro_rules! build_funding_contribution {
outputs,
change_output,
feerate,
- is_initiator,
is_splice,
};
@@ -129,8 +126,8 @@ impl FundingTemplate {
if value_added == Amount::ZERO {
return Err(());
}
- let FundingTemplate { shared_input, feerate, is_initiator } = self;
- build_funding_contribution!(value_added, vec![], shared_input, feerate, is_initiator, wallet, await)
+ let FundingTemplate { shared_input, feerate } = self;
+ build_funding_contribution!(value_added, vec![], shared_input, feerate, wallet, await)
}
/// Creates a [`FundingContribution`] for adding funds to a channel using `wallet` to perform
@@ -141,15 +138,8 @@ impl FundingTemplate {
if value_added == Amount::ZERO {
return Err(());
}
- let FundingTemplate { shared_input, feerate, is_initiator } = self;
- build_funding_contribution!(
- value_added,
- vec![],
- shared_input,
- feerate,
- is_initiator,
- wallet,
- )
+ let FundingTemplate { shared_input, feerate } = self;
+ build_funding_contribution!(value_added, vec![], shared_input, feerate, wallet,)
}
/// Creates a [`FundingContribution`] for removing funds from a channel using `wallet` to
@@ -160,8 +150,8 @@ impl FundingTemplate {
if outputs.is_empty() {
return Err(());
}
- let FundingTemplate { shared_input, feerate, is_initiator } = self;
- build_funding_contribution!(Amount::ZERO, outputs, shared_input, feerate, is_initiator, wallet, await)
+ let FundingTemplate { shared_input, feerate } = self;
+ build_funding_contribution!(Amount::ZERO, outputs, shared_input, feerate, wallet, await)
}
/// Creates a [`FundingContribution`] for removing funds from a channel using `wallet` to
@@ -172,15 +162,8 @@ impl FundingTemplate {
if outputs.is_empty() {
return Err(());
}
- let FundingTemplate { shared_input, feerate, is_initiator } = self;
- build_funding_contribution!(
- Amount::ZERO,
- outputs,
- shared_input,
- feerate,
- is_initiator,
- wallet,
- )
+ let FundingTemplate { shared_input, feerate } = self;
+ build_funding_contribution!(Amount::ZERO, outputs, shared_input, feerate, wallet,)
}
/// Creates a [`FundingContribution`] for both adding and removing funds from a channel using
@@ -191,8 +174,8 @@ impl FundingTemplate {
if value_added == Amount::ZERO && outputs.is_empty() {
return Err(());
}
- let FundingTemplate { shared_input, feerate, is_initiator } = self;
- build_funding_contribution!(value_added, outputs, shared_input, feerate, is_initiator, wallet, await)
+ let FundingTemplate { shared_input, feerate } = self;
+ build_funding_contribution!(value_added, outputs, shared_input, feerate, wallet, await)
}
/// Creates a [`FundingContribution`] for both adding and removing funds from a channel using
@@ -203,15 +186,8 @@ impl FundingTemplate {
if value_added == Amount::ZERO && outputs.is_empty() {
return Err(());
}
- let FundingTemplate { shared_input, feerate, is_initiator } = self;
- build_funding_contribution!(
- value_added,
- outputs,
- shared_input,
- feerate,
- is_initiator,
- wallet,
- )
+ let FundingTemplate { shared_input, feerate } = self;
+ build_funding_contribution!(value_added, outputs, shared_input, feerate, wallet,)
}
}
@@ -290,10 +266,6 @@ pub struct FundingContribution {
/// The fee rate used to select `inputs`.
feerate: FeeRate,
- /// Whether the contributor initiated the funding, and thus is responsible for fees incurred for
- /// common fields and shared inputs and outputs.
- is_initiator: bool,
-
/// Whether the contribution is for funding a splice.
is_splice: bool,
}
@@ -305,8 +277,7 @@ impl_writeable_tlv_based!(FundingContribution, {
(7, outputs, optional_vec),
(9, change_output, option),
(11, feerate, required),
- (13, is_initiator, required),
- (15, is_splice, required),
+ (13, is_splice, required),
});
impl FundingContribution {
@@ -314,10 +285,6 @@ impl FundingContribution {
self.feerate
}
- pub(super) fn is_initiator(&self) -> bool {
- self.is_initiator
- }
-
pub(super) fn is_splice(&self) -> bool {
self.is_splice
}
@@ -513,7 +480,6 @@ mod tests {
],
outputs: vec![],
change_output: None,
- is_initiator: true,
is_splice: true,
feerate: FeeRate::from_sat_per_kwu(2000),
};
@@ -534,7 +500,6 @@ mod tests {
funding_output_sats(200_000),
],
change_output: None,
- is_initiator: true,
is_splice: true,
feerate: FeeRate::from_sat_per_kwu(2000),
};
@@ -555,7 +520,6 @@ mod tests {
funding_output_sats(400_000),
],
change_output: None,
- is_initiator: true,
is_splice: true,
feerate: FeeRate::from_sat_per_kwu(2000),
};
@@ -576,7 +540,6 @@ mod tests {
funding_output_sats(400_000),
],
change_output: None,
- is_initiator: true,
is_splice: true,
feerate: FeeRate::from_sat_per_kwu(90000),
};
@@ -600,7 +563,6 @@ mod tests {
],
outputs: vec![],
change_output: None,
- is_initiator: true,
is_splice: true,
feerate: FeeRate::from_sat_per_kwu(2000),
};
@@ -625,7 +587,6 @@ mod tests {
],
outputs: vec![],
change_output: None,
- is_initiator: true,
is_splice: true,
feerate: FeeRate::from_sat_per_kwu(2000),
};
@@ -644,7 +605,6 @@ mod tests {
],
outputs: vec![],
change_output: None,
- is_initiator: true,
is_splice: true,
feerate: FeeRate::from_sat_per_kwu(2200),
};
@@ -657,9 +617,9 @@ mod tests {
);
}
- // barely covers, less fees (no extra weight, not initiator)
+ // barely covers, less fees (not a splice)
{
- let expected_fee = if cfg!(feature = "grind_signatures") { 1084 } else { 1088 };
+ let expected_fee = if cfg!(feature = "grind_signatures") { 1512 } else { 1516 };
let contribution = FundingContribution {
value_added: Amount::from_sat(300_000 - expected_fee - 20),
estimated_fee: Amount::from_sat(expected_fee),
@@ -669,7 +629,6 @@ mod tests {
],
outputs: vec![],
change_output: None,
- is_initiator: false,
is_splice: false,
feerate: FeeRate::from_sat_per_kwu(2000),
};
Why this scored 18/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.