What changed, and why it matters
This commit adds support for 'mixed mode splicing' in the Lightning Dev Kit, where a user can add funds to a channel and remove funds in the same transaction. The change fixes how fees are calculated so that the check uses the actual value of inputs being added, not just the net difference between added and removed funds. Previously, a net splice-out (more removed than added) could have caused the fee check to behave incorrectly because it used a negative net value. The commit also adds new tests for this behavior.
Review the updated fee and change calculations for arithmetic consistency and edge cases, particularly around Amount/SignedAmount conversions, dust limits, and the assert!(contributed_input_value > SignedAmount::ZERO). Ensure the new mixed-mode path is covered by fuzzing or additional adversarial tests.
Security signals we found
Fee sufficiency check previously used net signed contribution, which could be negative in splice-out cases
New mixed-mode splice path combines splice-in inputs and splice-out outputs in one transaction
Change calculation logic updated to derive contributed input value from funding contribution plus output value
Tests added covering net splice-in, net splice-out, and insufficient-input fee cases
Evidence from the diff
The patch introduces SpliceContribution::splice_in_and_out, allowing both splice-in inputs and splice-out outputs in one contribution. It changes check_splice_contribution_sufficient and check_v2_funding_inputs_sufficient to validate that contributed input value (value_added) plus estimated fees is covered by the provided inputs, rather than using the signed net contribution. This prevents the fee sufficiency check from being bypassed or miscalculated when outputs exceed inputs. calculate_change_output_value is also updated to compute contributed_input_value from our_funding_contribution + total_output_value, ensuring change is computed correctly for mixed-mode splices.
Changed components
lightning/src/ln/channel.rslightning/src/ln/funding.rslightning/src/ln/interactivetxs.rslightning/src/ln/splicing_tests.rsInspect captured patch +321 / −56
diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index a1c48b6..fd780da 100644
--- a/lightning/src/ln/channel.rs
+++ b/lightning/src/ln/channel.rs
@@ -6501,8 +6501,7 @@ fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satos
fn check_splice_contribution_sufficient(
contribution: &SpliceContribution, is_initiator: bool, funding_feerate: FeeRate,
) -> Result<SignedAmount, String> {
- let contribution_amount = contribution.value();
- if contribution_amount < SignedAmount::ZERO {
+ if contribution.inputs().is_empty() {
let estimated_fee = Amount::from_sat(estimate_v2_funding_transaction_fee(
contribution.inputs(),
contribution.outputs(),
@@ -6511,20 +6510,25 @@ fn check_splice_contribution_sufficient(
funding_feerate.to_sat_per_kwu() as u32,
));
+ let contribution_amount = contribution.net_value();
contribution_amount
.checked_sub(
estimated_fee.to_signed().expect("fees should never exceed Amount::MAX_MONEY"),
)
- .ok_or(format!("Our {contribution_amount} contribution plus the fee estimate exceeds the total bitcoin supply"))
+ .ok_or(format!(
+ "{estimated_fee} splice-out amount plus {} fee estimate exceeds the total bitcoin supply",
+ contribution_amount.unsigned_abs(),
+ ))
} else {
check_v2_funding_inputs_sufficient(
- contribution_amount.to_sat(),
+ contribution.value_added(),
contribution.inputs(),
+ contribution.outputs(),
is_initiator,
true,
funding_feerate.to_sat_per_kwu() as u32,
)
- .map(|_| contribution_amount)
+ .map(|_| contribution.net_value())
}
}
@@ -6583,16 +6587,16 @@ fn estimate_v2_funding_transaction_fee(
/// Returns estimated (partial) fees as additional information
#[rustfmt::skip]
fn check_v2_funding_inputs_sufficient(
- contribution_amount: i64, funding_inputs: &[FundingTxInput], is_initiator: bool,
- is_splice: bool, funding_feerate_sat_per_1000_weight: u32,
-) -> Result<u64, String> {
- let estimated_fee = estimate_v2_funding_transaction_fee(
- funding_inputs, &[], is_initiator, is_splice, funding_feerate_sat_per_1000_weight,
- );
-
- let mut total_input_sats = 0u64;
+ contributed_input_value: Amount, funding_inputs: &[FundingTxInput], outputs: &[TxOut],
+ is_initiator: bool, is_splice: bool, funding_feerate_sat_per_1000_weight: u32,
+) -> Result<Amount, String> {
+ let estimated_fee = Amount::from_sat(estimate_v2_funding_transaction_fee(
+ funding_inputs, outputs, is_initiator, is_splice, funding_feerate_sat_per_1000_weight,
+ ));
+
+ let mut total_input_value = Amount::ZERO;
for FundingTxInput { utxo, .. } in funding_inputs.iter() {
- total_input_sats = total_input_sats.checked_add(utxo.output.value.to_sat())
+ total_input_value = total_input_value.checked_add(utxo.output.value)
.ok_or("Sum of input values is greater than the total bitcoin supply")?;
}
@@ -6607,13 +6611,11 @@ fn check_v2_funding_inputs_sufficient(
// TODO(splicing): refine check including the fact wether a change will be added or not.
// Can be done once dual funding preparation is included.
- let minimal_input_amount_needed = contribution_amount.checked_add(estimated_fee as i64)
- .ok_or(format!("Our {contribution_amount} contribution plus the fee estimate exceeds the total bitcoin supply"))?;
- if i64::try_from(total_input_sats).map_err(|_| "Sum of input values is greater than the total bitcoin supply")?
- < minimal_input_amount_needed
- {
+ let minimal_input_amount_needed = contributed_input_value.checked_add(estimated_fee)
+ .ok_or(format!("{contributed_input_value} contribution plus {estimated_fee} fee estimate exceeds the total bitcoin supply"))?;
+ if total_input_value < minimal_input_amount_needed {
Err(format!(
- "Total input amount {total_input_sats} is lower than needed for contribution {contribution_amount}, considering fees of {estimated_fee}. Need more inputs.",
+ "Total input amount {total_input_value} is lower than needed for splice-in contribution {contributed_input_value}, considering fees of {estimated_fee}. Need more inputs.",
))
} else {
Ok(estimated_fee)
@@ -6679,7 +6681,7 @@ impl FundingNegotiationContext {
};
// Optionally add change output
- let change_value_opt = if self.our_funding_contribution > SignedAmount::ZERO {
+ let change_value_opt = if !self.our_funding_inputs.is_empty() {
match calculate_change_output_value(
&self,
self.shared_funding_input.is_some(),
@@ -12070,7 +12072,7 @@ where
});
}
- let our_funding_contribution = contribution.value();
+ let our_funding_contribution = contribution.net_value();
if our_funding_contribution == SignedAmount::ZERO {
return Err(APIError::APIMisuseError {
err: format!(
@@ -18525,6 +18527,13 @@ mod tests {
FundingTxInput::new_p2wpkh(prevtx, 0).unwrap()
}
+ fn funding_output_sats(output_value_sats: u64) -> TxOut {
+ TxOut {
+ value: Amount::from_sat(output_value_sats),
+ script_pubkey: ScriptBuf::new_p2wpkh(&WPubkeyHash::all_zeros()),
+ }
+ }
+
#[test]
#[rustfmt::skip]
fn test_check_v2_funding_inputs_sufficient() {
@@ -18535,16 +18544,83 @@ mod tests {
let expected_fee = if cfg!(feature = "grind_signatures") { 2278 } else { 2284 };
assert_eq!(
check_v2_funding_inputs_sufficient(
- 220_000,
+ Amount::from_sat(220_000),
+ &[
+ funding_input_sats(200_000),
+ funding_input_sats(100_000),
+ ],
+ &[],
+ true,
+ true,
+ 2000,
+ ).unwrap(),
+ Amount::from_sat(expected_fee),
+ );
+ }
+
+ // Net splice-in
+ {
+ let expected_fee = if cfg!(feature = "grind_signatures") { 2526 } else { 2532 };
+ assert_eq!(
+ check_v2_funding_inputs_sufficient(
+ Amount::from_sat(220_000),
+ &[
+ funding_input_sats(200_000),
+ funding_input_sats(100_000),
+ ],
+ &[
+ funding_output_sats(200_000),
+ ],
+ true,
+ true,
+ 2000,
+ ).unwrap(),
+ Amount::from_sat(expected_fee),
+ );
+ }
+
+ // Net splice-out
+ {
+ let expected_fee = if cfg!(feature = "grind_signatures") { 2526 } else { 2532 };
+ assert_eq!(
+ check_v2_funding_inputs_sufficient(
+ Amount::from_sat(220_000),
&[
funding_input_sats(200_000),
funding_input_sats(100_000),
],
+ &[
+ funding_output_sats(400_000),
+ ],
true,
true,
2000,
).unwrap(),
- expected_fee,
+ Amount::from_sat(expected_fee),
+ );
+ }
+
+ // Net splice-out, inputs insufficient to cover fees
+ {
+ let expected_fee = if cfg!(feature = "grind_signatures") { 113670 } else { 113940 };
+ assert_eq!(
+ check_v2_funding_inputs_sufficient(
+ Amount::from_sat(220_000),
+ &[
+ funding_input_sats(200_000),
+ funding_input_sats(100_000),
+ ],
+ &[
+ funding_output_sats(400_000),
+ ],
+ true,
+ true,
+ 90000,
+ ),
+ Err(format!(
+ "Total input amount 0.00300000 BTC is lower than needed for splice-in contribution 0.00220000 BTC, considering fees of {}. Need more inputs.",
+ Amount::from_sat(expected_fee),
+ )),
);
}
@@ -18553,17 +18629,18 @@ mod tests {
let expected_fee = if cfg!(feature = "grind_signatures") { 1736 } else { 1740 };
assert_eq!(
check_v2_funding_inputs_sufficient(
- 220_000,
+ Amount::from_sat(220_000),
&[
funding_input_sats(100_000),
],
+ &[],
true,
true,
2000,
),
Err(format!(
- "Total input amount 100000 is lower than needed for contribution 220000, considering fees of {}. Need more inputs.",
- expected_fee,
+ "Total input amount 0.00100000 BTC is lower than needed for splice-in contribution 0.00220000 BTC, considering fees of {}. Need more inputs.",
+ Amount::from_sat(expected_fee),
)),
);
}
@@ -18573,16 +18650,17 @@ mod tests {
let expected_fee = if cfg!(feature = "grind_signatures") { 2278 } else { 2284 };
assert_eq!(
check_v2_funding_inputs_sufficient(
- (300_000 - expected_fee - 20) as i64,
+ Amount::from_sat(300_000 - expected_fee - 20),
&[
funding_input_sats(200_000),
funding_input_sats(100_000),
],
+ &[],
true,
true,
2000,
).unwrap(),
- expected_fee,
+ Amount::from_sat(expected_fee),
);
}
@@ -18591,18 +18669,19 @@ mod tests {
let expected_fee = if cfg!(feature = "grind_signatures") { 2506 } else { 2513 };
assert_eq!(
check_v2_funding_inputs_sufficient(
- 298032,
+ Amount::from_sat(298032),
&[
funding_input_sats(200_000),
funding_input_sats(100_000),
],
+ &[],
true,
true,
2200,
),
Err(format!(
- "Total input amount 300000 is lower than needed for contribution 298032, considering fees of {}. Need more inputs.",
- expected_fee
+ "Total input amount 0.00300000 BTC is lower than needed for splice-in contribution 0.00298032 BTC, considering fees of {}. Need more inputs.",
+ Amount::from_sat(expected_fee),
)),
);
}
@@ -18612,16 +18691,17 @@ mod tests {
let expected_fee = if cfg!(feature = "grind_signatures") { 1084 } else { 1088 };
assert_eq!(
check_v2_funding_inputs_sufficient(
- (300_000 - expected_fee - 20) as i64,
+ Amount::from_sat(300_000 - expected_fee - 20),
&[
funding_input_sats(200_000),
funding_input_sats(100_000),
],
+ &[],
false,
false,
2000,
).unwrap(),
- expected_fee,
+ Amount::from_sat(expected_fee),
);
}
}
diff --git a/lightning/src/ln/funding.rs b/lightning/src/ln/funding.rs
index b7f8740..8092a0e 100644
--- a/lightning/src/ln/funding.rs
+++ b/lightning/src/ln/funding.rs
@@ -21,8 +21,10 @@ use crate::sign::{P2TR_KEY_PATH_WITNESS_WEIGHT, P2WPKH_WITNESS_WEIGHT};
/// The components of a splice's funding transaction that are contributed by one party.
#[derive(Debug, Clone)]
pub struct SpliceContribution {
- /// The amount to contribute to the splice.
- value: SignedAmount,
+ /// The amount from [`inputs`] to contribute to the splice.
+ ///
+ /// [`inputs`]: Self::inputs
+ value_added: Amount,
/// The inputs included in the splice's funding transaction to meet the contributed amount
/// plus fees. Any excess amount will be sent to a change output.
@@ -42,27 +44,45 @@ pub struct SpliceContribution {
impl SpliceContribution {
/// Creates a contribution for when funds are only added to a channel.
pub fn splice_in(
- value: Amount, inputs: Vec<FundingTxInput>, change_script: Option<ScriptBuf>,
+ value_added: Amount, inputs: Vec<FundingTxInput>, change_script: Option<ScriptBuf>,
) -> Self {
- let value_added = value.to_signed().unwrap_or(SignedAmount::MAX);
-
- Self { value: value_added, inputs, outputs: vec![], change_script }
+ Self { value_added, inputs, outputs: vec![], change_script }
}
/// Creates a contribution for when funds are only removed from a channel.
pub fn splice_out(outputs: Vec<TxOut>) -> Self {
- let value_removed = outputs
+ Self { value_added: Amount::ZERO, inputs: vec![], outputs, change_script: None }
+ }
+
+ /// Creates a contribution for when funds are both added to and removed from a channel.
+ ///
+ /// Note that `value_added` represents the value added by `inputs` but should not account for
+ /// value removed by `outputs`. The net value contributed can be obtained by calling
+ /// [`SpliceContribution::net_value`].
+ pub fn splice_in_and_out(
+ value_added: Amount, inputs: Vec<FundingTxInput>, outputs: Vec<TxOut>,
+ change_script: Option<ScriptBuf>,
+ ) -> Self {
+ Self { value_added, inputs, outputs, change_script }
+ }
+
+ /// The net value contributed to a channel by the splice. If negative, more value will be
+ /// spliced out than spliced in.
+ pub fn net_value(&self) -> SignedAmount {
+ let value_added = self.value_added.to_signed().unwrap_or(SignedAmount::MAX);
+ let value_removed = self
+ .outputs
.iter()
.map(|txout| txout.value)
.sum::<Amount>()
.to_signed()
.unwrap_or(SignedAmount::MAX);
- Self { value: -value_removed, inputs: vec![], outputs, change_script: None }
+ value_added - value_removed
}
- pub(super) fn value(&self) -> SignedAmount {
- self.value
+ pub(super) fn value_added(&self) -> Amount {
+ self.value_added
}
pub(super) fn inputs(&self) -> &[FundingTxInput] {
@@ -74,7 +94,7 @@ impl SpliceContribution {
}
pub(super) fn into_tx_parts(self) -> (Vec<FundingTxInput>, Vec<TxOut>, Option<ScriptBuf>) {
- let SpliceContribution { value: _, inputs, outputs, change_script } = self;
+ let SpliceContribution { value_added: _, inputs, outputs, change_script } = self;
(inputs, outputs, change_script)
}
}
diff --git a/lightning/src/ln/interactivetxs.rs b/lightning/src/ln/interactivetxs.rs
index 1ab9c6c..7ed8298 100644
--- a/lightning/src/ln/interactivetxs.rs
+++ b/lightning/src/ln/interactivetxs.rs
@@ -2338,9 +2338,6 @@ pub(super) fn calculate_change_output_value(
context: &FundingNegotiationContext, is_splice: bool, shared_output_funding_script: &ScriptBuf,
change_output_dust_limit: u64,
) -> Result<Option<Amount>, AbortReason> {
- assert!(context.our_funding_contribution > SignedAmount::ZERO);
- let our_funding_contribution = context.our_funding_contribution.to_unsigned().unwrap();
-
let mut total_input_value = Amount::ZERO;
let mut our_funding_inputs_weight = 0u64;
for FundingTxInput { utxo, .. } in context.our_funding_inputs.iter() {
@@ -2354,6 +2351,7 @@ pub(super) fn calculate_change_output_value(
let total_output_value = funding_outputs
.iter()
.fold(Amount::ZERO, |total, out| total.checked_add(out.value).unwrap_or(Amount::MAX));
+
let our_funding_outputs_weight = funding_outputs.iter().fold(0u64, |weight, out| {
weight.saturating_add(get_output_weight(&out.script_pubkey).to_wu())
});
@@ -2379,18 +2377,21 @@ pub(super) fn calculate_change_output_value(
let contributed_fees =
Amount::from_sat(fee_for_weight(context.funding_feerate_sat_per_1000_weight, weight));
- let net_total_less_fees = total_input_value
- .checked_sub(total_output_value)
- .unwrap_or(Amount::ZERO)
- .checked_sub(contributed_fees)
- .unwrap_or(Amount::ZERO);
- if net_total_less_fees < our_funding_contribution {
+
+ let contributed_input_value =
+ context.our_funding_contribution + total_output_value.to_signed().unwrap();
+ assert!(contributed_input_value > SignedAmount::ZERO);
+ let contributed_input_value = contributed_input_value.unsigned_abs();
+
+ let total_input_value_less_fees =
+ total_input_value.checked_sub(contributed_fees).unwrap_or(Amount::ZERO);
+ if total_input_value_less_fees < contributed_input_value {
// Not enough to cover contribution plus fees
return Err(AbortReason::InsufficientFees);
}
- let remaining_value = net_total_less_fees
- .checked_sub(our_funding_contribution)
+ let remaining_value = total_input_value_less_fees
+ .checked_sub(contributed_input_value)
.expect("remaining_value should not be negative");
if remaining_value.to_sat() < change_output_dust_limit {
// Enough to cover contribution plus fees, but leftover is below dust limit; no change
diff --git a/lightning/src/ln/splicing_tests.rs b/lightning/src/ln/splicing_tests.rs
index 58a81bb..db6680d 100644
--- a/lightning/src/ln/splicing_tests.rs
+++ b/lightning/src/ln/splicing_tests.rs
@@ -877,6 +877,170 @@ fn test_splice_out() {
let _ = send_payment(&nodes[0], &[&nodes[1]], htlc_limit_msat);
}
+#[test]
+fn test_splice_in_and_out() {
+ let chanmon_cfgs = create_chanmon_cfgs(2);
+ let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+ let mut config = test_default_channel_config();
+ config.channel_handshake_config.max_inbound_htlc_value_in_flight_percent_of_channel = 100;
+ let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, Some(config)]);
+ let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+ let initial_channel_value_sat = 100_000;
+ let (_, _, channel_id, _) =
+ create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_value_sat, 0);
+
+ let _ = send_payment(&nodes[0], &[&nodes[1]], 100_000);
+
+ let coinbase_tx1 = provide_anchor_reserves(&nodes);
+ let coinbase_tx2 = provide_anchor_reserves(&nodes);
+
+ // Contribute a net negative value, with fees taken from the contributed inputs and the
+ // remaining value sent to change
+ let htlc_limit_msat = nodes[0].node.list_channels()[0].next_outbound_htlc_limit_msat;
+ let added_value = Amount::from_sat(htlc_limit_msat / 1000);
+ let removed_value = added_value * 2;
+ let change_script = ScriptBuf::new_p2wpkh(&WPubkeyHash::all_zeros());
+ let fees = if cfg!(feature = "grind_signatures") {
+ Amount::from_sat(383)
+ } else {
+ Amount::from_sat(384)
+ };
+
+ assert!(htlc_limit_msat > initial_channel_value_sat / 2 * 1000);
+
+ let initiator_contribution = SpliceContribution::splice_in_and_out(
+ added_value,
+ vec![
+ FundingTxInput::new_p2wpkh(coinbase_tx1, 0).unwrap(),
+ FundingTxInput::new_p2wpkh(coinbase_tx2, 0).unwrap(),
+ ],
+ vec![
+ TxOut {
+ value: removed_value / 2,
+ script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
+ },
+ TxOut {
+ value: removed_value / 2,
+ script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
+ },
+ ],
+ Some(change_script.clone()),
+ );
+
+ let splice_tx = splice_channel(&nodes[0], &nodes[1], channel_id, initiator_contribution);
+ let expected_change = Amount::ONE_BTC * 2 - added_value - fees;
+ assert_eq!(
+ splice_tx.output.iter().find(|txout| txout.script_pubkey == change_script).unwrap().value,
+ expected_change,
+ );
+
+ mine_transaction(&nodes[0], &splice_tx);
+ mine_transaction(&nodes[1], &splice_tx);
+
+ let htlc_limit_msat = nodes[0].node.list_channels()[0].next_outbound_htlc_limit_msat;
+ assert!(htlc_limit_msat < added_value.to_sat() * 1000);
+ let _ = send_payment(&nodes[0], &[&nodes[1]], htlc_limit_msat);
+
+ lock_splice_after_blocks(&nodes[0], &nodes[1], ANTI_REORG_DELAY - 1);
+
+ let htlc_limit_msat = nodes[0].node.list_channels()[0].next_outbound_htlc_limit_msat;
+ assert!(htlc_limit_msat < added_value.to_sat() * 1000);
+ let _ = send_payment(&nodes[0], &[&nodes[1]], htlc_limit_msat);
+
+ let coinbase_tx1 = provide_anchor_reserves(&nodes);
+ let coinbase_tx2 = provide_anchor_reserves(&nodes);
+
+ // Contribute a net positive value, with fees taken from the contributed inputs and the
+ // remaining value sent to change
+ let added_value = Amount::from_sat(initial_channel_value_sat * 2);
+ let removed_value = added_value / 2;
+ let change_script = ScriptBuf::new_p2wpkh(&WPubkeyHash::all_zeros());
+ let fees = if cfg!(feature = "grind_signatures") {
+ Amount::from_sat(383)
+ } else {
+ Amount::from_sat(384)
+ };
+
+ let initiator_contribution = SpliceContribution::splice_in_and_out(
+ added_value,
+ vec![
+ FundingTxInput::new_p2wpkh(coinbase_tx1, 0).unwrap(),
+ FundingTxInput::new_p2wpkh(coinbase_tx2, 0).unwrap(),
+ ],
+ vec![
+ TxOut {
+ value: removed_value / 2,
+ script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
+ },
+ TxOut {
+ value: removed_value / 2,
+ script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
+ },
+ ],
+ Some(change_script.clone()),
+ );
+
+ let splice_tx = splice_channel(&nodes[0], &nodes[1], channel_id, initiator_contribution);
+ let expected_change = Amount::ONE_BTC * 2 - added_value - fees;
+ assert_eq!(
+ splice_tx.output.iter().find(|txout| txout.script_pubkey == change_script).unwrap().value,
+ expected_change,
+ );
+
+ mine_transaction(&nodes[0], &splice_tx);
+ mine_transaction(&nodes[1], &splice_tx);
+
+ let htlc_limit_msat = nodes[0].node.list_channels()[0].next_outbound_htlc_limit_msat;
+ assert_eq!(htlc_limit_msat, 0);
+
+ lock_splice_after_blocks(&nodes[0], &nodes[1], ANTI_REORG_DELAY - 1);
+
+ let htlc_limit_msat = nodes[0].node.list_channels()[0].next_outbound_htlc_limit_msat;
+ assert!(htlc_limit_msat > initial_channel_value_sat / 2 * 1000);
+ let _ = send_payment(&nodes[0], &[&nodes[1]], htlc_limit_msat);
+
+ let coinbase_tx1 = provide_anchor_reserves(&nodes);
+ let coinbase_tx2 = provide_anchor_reserves(&nodes);
+
+ // Fail adding a net contribution value of zero
+ let added_value = Amount::from_sat(initial_channel_value_sat * 2);
+ let removed_value = added_value;
+ let change_script = ScriptBuf::new_p2wpkh(&WPubkeyHash::all_zeros());
+
+ let initiator_contribution = SpliceContribution::splice_in_and_out(
+ added_value,
+ vec![
+ FundingTxInput::new_p2wpkh(coinbase_tx1, 0).unwrap(),
+ FundingTxInput::new_p2wpkh(coinbase_tx2, 0).unwrap(),
+ ],
+ vec![
+ TxOut {
+ value: removed_value / 2,
+ script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
+ },
+ TxOut {
+ value: removed_value / 2,
+ script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
+ },
+ ],
+ Some(change_script),
+ );
+
+ assert_eq!(
+ nodes[0].node.splice_channel(
+ &channel_id,
+ &nodes[1].node.get_our_node_id(),
+ initiator_contribution,
+ FEERATE_FLOOR_SATS_PER_KW,
+ None,
+ ),
+ Err(APIError::APIMisuseError {
+ err: format!("Channel {} cannot be spliced; contribution cannot be zero", channel_id),
+ }),
+ );
+}
+
#[cfg(test)]
#[derive(PartialEq)]
enum SpliceStatus {
Why this scored 34/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.