Add basic end-to-end splice tests
What changed, and why it matters
This commit is a routine development patch that adds and improves automated tests for a new Bitcoin Lightning channel feature called 'splicing'—a way to add or remove funds from an existing channel. It removes a temporary placeholder that deliberately aborted splicing during testing, because the tests now need the feature to run further. There is no indication this fixes a security bug or introduces a vulnerability.
No security action required; treat as normal feature/test development.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit refactors splicing tests in rust-lightning. The only production-code change is removing a forced Err(AbortReason::InternalError("Splicing not yet supported")) in channel.rs that previously aborted when a splice reached the commitment-signed stage. The rest of the diff adds helper functions (complete_interactive_funding_negotiation, sign_interactive_funding_transaction, splice_channel, lock_splice_after_blocks) and new end-to-end tests (test_splice_in, test_splice_out) plus a negative test for insufficient inputs. SpliceContribution and FundingTxInput also receive Debug derives. The change enables the previously stubbed-out splicing flow to be exercised by tests.
Changed components
lightning/src/ln/channel.rslightning/src/ln/funding.rslightning/src/ln/splicing_tests.rsInspect captured patch +331 / −251
diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index de894de..22fce25 100644
--- a/lightning/src/ln/channel.rs
+++ b/lightning/src/ln/channel.rs
@@ -6130,8 +6130,6 @@ where
holder_commitment_transaction_number,
self.counterparty_next_commitment_transaction_number,
);
- // TODO(splicing) Forced error, as the use case is not complete
- return Err(AbortReason::InternalError("Splicing not yet supported"));
} else {
self.assert_no_commitment_advancement(holder_commitment_transaction_number, "initial commitment_signed");
self.channel_state = ChannelState::FundingNegotiated(FundingNegotiatedFlags::new());
diff --git a/lightning/src/ln/funding.rs b/lightning/src/ln/funding.rs
index b0b8cd4..d0657a4 100644
--- a/lightning/src/ln/funding.rs
+++ b/lightning/src/ln/funding.rs
@@ -17,6 +17,7 @@ use crate::prelude::Vec;
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 enum SpliceContribution {
/// When funds are added to a channel.
SpliceIn {
@@ -85,7 +86,7 @@ impl SpliceContribution {
/// An input to contribute to a channel's funding transaction either when using the v2 channel
/// establishment protocol or when splicing.
-#[derive(Clone)]
+#[derive(Debug, Clone)]
pub struct FundingTxInput {
/// The unspent [`TxOut`] that the input spends.
///
diff --git a/lightning/src/ln/splicing_tests.rs b/lightning/src/ln/splicing_tests.rs
index 0a5a5a2..65461f2 100644
--- a/lightning/src/ln/splicing_tests.rs
+++ b/lightning/src/ln/splicing_tests.rs
@@ -7,70 +7,36 @@
// You may not use this file except in accordance with one or both of these
// licenses.
+use crate::chain::chaininterface::FEERATE_FLOOR_SATS_PER_KW;
+use crate::chain::channelmonitor::ANTI_REORG_DELAY;
+use crate::events::bump_transaction::sync::WalletSourceSync;
+use crate::events::Event;
+use crate::ln::chan_utils;
use crate::ln::functional_test_utils::*;
-use crate::ln::funding::SpliceContribution;
-use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, MessageSendEvent};
+use crate::ln::funding::{FundingTxInput, SpliceContribution};
+use crate::ln::msgs::{self, BaseMessageHandler, ChannelMessageHandler, MessageSendEvent};
+use crate::ln::types::ChannelId;
use crate::util::errors::APIError;
-use bitcoin::Amount;
+use bitcoin::{Amount, OutPoint as BitcoinOutPoint, ScriptBuf, Transaction, TxOut};
-/// Splicing test, simple splice-in flow. Starts with opening a V1 channel first.
-/// Builds on test_channel_open_simple()
#[test]
-fn test_v1_splice_in() {
+fn test_v1_splice_in_negative_insufficient_inputs() {
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
- // Initiator and Acceptor nodes
- let initiator_node_index = 0;
- let acceptor_node_index = 1;
- let initiator_node = &nodes[initiator_node_index];
- let acceptor_node = &nodes[acceptor_node_index];
- let initiator_node_id = initiator_node.node.get_our_node_id();
- let acceptor_node_id = acceptor_node.node.get_our_node_id();
-
- let channel_value_sat = 100_000;
- let channel_reserve_amnt_sat = 1_000;
- let expect_outputs_in_reverse = true;
-
- let (_, _, channel_id, _) = create_announced_chan_between_nodes_with_value(
- &nodes,
- initiator_node_index,
- acceptor_node_index,
- channel_value_sat,
- 0, // push_msat,
- );
-
- let expected_funded_channel_id =
- "ae3367da2c13bc1ceb86bf56418f62828f7ce9d6bfb15a46af5ba1f1ed8b124f";
- assert_eq!(channel_id.to_string(), expected_funded_channel_id);
-
- let expected_initiator_funding_key =
- "020abf01c18d5a2543124a12150d698ebf3a8e17df9993521151a49e115678ceea";
- let expected_acceptor_funding_key =
- "036b47248c628fca98159f30f6b03a6cf0be0c4808cff17c75dc855fe94a244766";
-
- // ==== Channel is now ready for normal operation
-
- // Expected balances
- let exp_balance1 = 1000 * channel_value_sat;
- let mut _exp_balance2 = 0;
-
- // === Start of Splicing
+ let (_, _, channel_id, _) =
+ create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 0);
// Amount being added to the channel through the splice-in
let splice_in_sats = 20_000;
- let post_splice_channel_value = channel_value_sat + splice_in_sats;
- let funding_feerate_per_kw = 1024;
-
- // Create additional inputs
- let extra_splice_funding_input_sats = 35_000;
- let funding_inputs = create_dual_funding_utxos_with_prev_txs(
- &initiator_node,
- &[extra_splice_funding_input_sats],
- );
+
+ // Create additional inputs, but insufficient
+ let extra_splice_funding_input_sats = splice_in_sats - 1;
+ let funding_inputs =
+ create_dual_funding_utxos_with_prev_txs(&nodes[0], &[extra_splice_funding_input_sats]);
let contribution = SpliceContribution::SpliceIn {
value: Amount::from_sat(splice_in_sats),
@@ -78,239 +44,354 @@ fn test_v1_splice_in() {
change_script: None,
};
- // Initiate splice-in
- initiator_node
- .node
- .splice_channel(
- &channel_id,
- &acceptor_node.node.get_our_node_id(),
- contribution,
- funding_feerate_per_kw,
- None, // locktime
- )
- .unwrap();
-
- let init_stfu = get_event_msg!(initiator_node, MessageSendEvent::SendStfu, acceptor_node_id);
- acceptor_node.node.handle_stfu(initiator_node_id, &init_stfu);
-
- let ack_stfu = get_event_msg!(acceptor_node, MessageSendEvent::SendStfu, initiator_node_id);
- initiator_node.node.handle_stfu(acceptor_node_id, &ack_stfu);
-
- // Extract the splice_init message
- let splice_init_msg =
- get_event_msg!(initiator_node, MessageSendEvent::SendSpliceInit, acceptor_node_id);
- assert_eq!(splice_init_msg.funding_contribution_satoshis, splice_in_sats as i64);
- assert_eq!(splice_init_msg.funding_feerate_per_kw, funding_feerate_per_kw);
- assert_eq!(splice_init_msg.funding_pubkey.to_string(), expected_initiator_funding_key);
- assert!(splice_init_msg.require_confirmed_inputs.is_none());
-
- acceptor_node.node.handle_splice_init(initiator_node.node.get_our_node_id(), &splice_init_msg);
- // Extract the splice_ack message
- let splice_ack_msg = get_event_msg!(
- acceptor_node,
- MessageSendEvent::SendSpliceAck,
- initiator_node.node.get_our_node_id()
+ // Initiate splice-in, with insufficient input contribution
+ let res = nodes[0].node.splice_channel(
+ &channel_id,
+ &nodes[1].node.get_our_node_id(),
+ contribution,
+ 1024, // funding_feerate_per_kw,
+ None, // locktime
);
- assert_eq!(splice_ack_msg.funding_contribution_satoshis, 0);
- assert_eq!(splice_ack_msg.funding_pubkey.to_string(), expected_acceptor_funding_key);
- assert!(splice_ack_msg.require_confirmed_inputs.is_none());
+ match res {
+ Err(APIError::APIMisuseError { err }) => {
+ assert!(err.contains("Need more inputs"))
+ },
+ _ => panic!("Wrong error {:?}", res.err().unwrap()),
+ }
+}
- // still pre-splice channel: capacity not updated, channel usable, and funding tx set
- assert_eq!(acceptor_node.node.list_channels().len(), 1);
- {
- let channel = &acceptor_node.node.list_channels()[0];
- assert_eq!(channel.channel_id.to_string(), expected_funded_channel_id);
- assert!(channel.is_usable);
- assert!(channel.is_channel_ready);
- assert_eq!(channel.channel_value_satoshis, channel_value_sat);
- assert_eq!(channel.outbound_capacity_msat, 0);
- assert!(channel.funding_txo.is_some());
- assert!(channel.confirmations.unwrap() > 0);
+fn complete_interactive_funding_negotiation<'a, 'b, 'c, 'd>(
+ initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>, channel_id: ChannelId,
+ initiator_contribution: SpliceContribution, new_funding_script: ScriptBuf,
+) -> msgs::CommitmentSigned {
+ let node_id_initiator = initiator.node.get_our_node_id();
+ let node_id_acceptor = acceptor.node.get_our_node_id();
+
+ let funding_outpoint = initiator
+ .node
+ .list_channels()
+ .iter()
+ .find(|channel| {
+ channel.counterparty.node_id == node_id_acceptor && channel.channel_id == channel_id
+ })
+ .map(|channel| channel.funding_txo.unwrap())
+ .unwrap();
+ let (initiator_inputs, initiator_outputs, initiator_change_script) =
+ initiator_contribution.into_tx_parts();
+ let mut expected_initiator_inputs = initiator_inputs
+ .iter()
+ .map(|input| input.utxo.outpoint)
+ .chain(core::iter::once(funding_outpoint.into_bitcoin_outpoint()))
+ .collect::<Vec<_>>();
+ let mut expected_initiator_scripts = initiator_outputs
+ .into_iter()
+ .map(|output| output.script_pubkey)
+ .chain(core::iter::once(new_funding_script))
+ .chain(initiator_change_script.into_iter())
+ .collect::<Vec<_>>();
+
+ let mut acceptor_sent_tx_complete = false;
+ loop {
+ if !expected_initiator_inputs.is_empty() {
+ let tx_add_input =
+ get_event_msg!(initiator, MessageSendEvent::SendTxAddInput, node_id_acceptor);
+ let input_prevout = BitcoinOutPoint {
+ txid: tx_add_input
+ .prevtx
+ .as_ref()
+ .map(|prevtx| prevtx.compute_txid())
+ .or(tx_add_input.shared_input_txid)
+ .unwrap(),
+ vout: tx_add_input.prevtx_out,
+ };
+ expected_initiator_inputs.remove(
+ expected_initiator_inputs.iter().position(|input| *input == input_prevout).unwrap(),
+ );
+ acceptor.node.handle_tx_add_input(node_id_initiator, &tx_add_input);
+ } else if !expected_initiator_scripts.is_empty() {
+ let tx_add_output =
+ get_event_msg!(initiator, MessageSendEvent::SendTxAddOutput, node_id_acceptor);
+ expected_initiator_scripts.remove(
+ expected_initiator_scripts
+ .iter()
+ .position(|script| *script == tx_add_output.script)
+ .unwrap(),
+ );
+ acceptor.node.handle_tx_add_output(node_id_initiator, &tx_add_output);
+ } else {
+ let mut msg_events = initiator.node.get_and_clear_pending_msg_events();
+ assert_eq!(
+ msg_events.len(),
+ if acceptor_sent_tx_complete { 2 } else { 1 },
+ "{msg_events:?}"
+ );
+ if let MessageSendEvent::SendTxComplete { ref msg, .. } = msg_events.remove(0) {
+ acceptor.node.handle_tx_complete(node_id_initiator, msg);
+ } else {
+ panic!();
+ }
+ if acceptor_sent_tx_complete {
+ if let MessageSendEvent::UpdateHTLCs { mut updates, .. } = msg_events.remove(0) {
+ return updates.commitment_signed.remove(0);
+ }
+ panic!();
+ }
+ }
+
+ let mut msg_events = acceptor.node.get_and_clear_pending_msg_events();
+ assert_eq!(msg_events.len(), 1, "{msg_events:?}");
+ if let MessageSendEvent::SendTxComplete { ref msg, .. } = msg_events.remove(0) {
+ initiator.node.handle_tx_complete(node_id_acceptor, msg);
+ } else {
+ panic!();
+ }
+ acceptor_sent_tx_complete = true;
}
+}
- initiator_node.node.handle_splice_ack(acceptor_node.node.get_our_node_id(), &splice_ack_msg);
+fn sign_interactive_funding_transaction<'a, 'b, 'c, 'd>(
+ initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>,
+ initial_commit_sig_for_acceptor: msgs::CommitmentSigned,
+) {
+ let node_id_initiator = initiator.node.get_our_node_id();
+ let node_id_acceptor = acceptor.node.get_our_node_id();
+
+ assert!(initiator.node.get_and_clear_pending_msg_events().is_empty());
+ acceptor.node.handle_commitment_signed(node_id_initiator, &initial_commit_sig_for_acceptor);
+
+ let mut msg_events = acceptor.node.get_and_clear_pending_msg_events();
+ assert_eq!(msg_events.len(), 2, "{msg_events:?}");
+ if let MessageSendEvent::UpdateHTLCs { mut updates, .. } = msg_events.remove(0) {
+ let commitment_signed = updates.commitment_signed.remove(0);
+ initiator.node.handle_commitment_signed(node_id_acceptor, &commitment_signed);
+ } else {
+ panic!();
+ }
+ if let MessageSendEvent::SendTxSignatures { ref msg, .. } = msg_events.remove(0) {
+ initiator.node.handle_tx_signatures(node_id_acceptor, msg);
+ } else {
+ panic!();
+ }
- // still pre-splice channel: capacity not updated, channel usable, and funding tx set
- assert_eq!(initiator_node.node.list_channels().len(), 1);
+ let event = get_event!(initiator, Event::FundingTransactionReadyForSigning);
+ if let Event::FundingTransactionReadyForSigning {
+ channel_id,
+ counterparty_node_id,
+ unsigned_transaction,
+ ..
+ } = event
{
- let channel = &initiator_node.node.list_channels()[0];
- assert_eq!(channel.channel_id.to_string(), expected_funded_channel_id);
- assert!(channel.is_usable);
- assert!(channel.is_channel_ready);
- assert_eq!(channel.channel_value_satoshis, channel_value_sat);
- assert_eq!(channel.outbound_capacity_msat, exp_balance1 - 1000 * channel_reserve_amnt_sat);
- assert!(channel.funding_txo.is_some());
- assert!(channel.confirmations.unwrap() > 0);
+ let partially_signed_tx = initiator.wallet_source.sign_tx(unsigned_transaction).unwrap();
+ initiator
+ .node
+ .funding_transaction_signed(&channel_id, &counterparty_node_id, partially_signed_tx)
+ .unwrap();
}
+ let tx_signatures =
+ get_event_msg!(initiator, MessageSendEvent::SendTxSignatures, node_id_acceptor);
+ acceptor.node.handle_tx_signatures(node_id_initiator, &tx_signatures);
- // exp_balance1 += 1000 * splice_in_sats; // increase in balance
+ check_added_monitors(&initiator, 1);
+ check_added_monitors(&acceptor, 1);
+}
- // Negotiate transaction inputs and outputs
+fn splice_channel<'a, 'b, 'c, 'd>(
+ initiator: &'a Node<'b, 'c, 'd>, acceptor: &'a Node<'b, 'c, 'd>, channel_id: ChannelId,
+ initiator_contribution: SpliceContribution,
+) -> Transaction {
+ let node_id_initiator = initiator.node.get_our_node_id();
+ let node_id_acceptor = acceptor.node.get_our_node_id();
- // First input
- let tx_add_input_msg = get_event_msg!(
- &initiator_node,
- MessageSendEvent::SendTxAddInput,
- acceptor_node.node.get_our_node_id()
- );
- // check which input is this (order is non-deterministic), based on the presense of prevtx
- let inputs_seen_in_reverse = tx_add_input_msg.prevtx.is_some();
- if !inputs_seen_in_reverse {
- // Input is the revious funding input
- assert_eq!(tx_add_input_msg.prevtx, None);
- assert_eq!(
- tx_add_input_msg.shared_input_txid.unwrap().to_string(),
- "4f128bedf1a15baf465ab1bfd6e97c8f82628f4156bf86eb1cbc132cda6733ae"
- );
- } else {
- // Input is the extra input
- let prevtx_value = tx_add_input_msg.prevtx.as_ref().unwrap().output
- [tx_add_input_msg.prevtx_out as usize]
- .value
- .to_sat();
- assert_eq!(prevtx_value, extra_splice_funding_input_sats);
- assert_eq!(tx_add_input_msg.shared_input_txid, None);
- }
-
- acceptor_node
+ initiator
.node
- .handle_tx_add_input(initiator_node.node.get_our_node_id(), &tx_add_input_msg);
- let tx_complete_msg = get_event_msg!(
- acceptor_node,
- MessageSendEvent::SendTxComplete,
- initiator_node.node.get_our_node_id()
- );
+ .splice_channel(
+ &channel_id,
+ &node_id_acceptor,
+ initiator_contribution.clone(),
+ FEERATE_FLOOR_SATS_PER_KW,
+ None,
+ )
+ .unwrap();
- initiator_node.node.handle_tx_complete(acceptor_node.node.get_our_node_id(), &tx_complete_msg);
- // Second input
- let tx_add_input2_msg = get_event_msg!(
- &initiator_node,
- MessageSendEvent::SendTxAddInput,
- acceptor_node.node.get_our_node_id()
+ let stfu_init = get_event_msg!(initiator, MessageSendEvent::SendStfu, node_id_acceptor);
+ acceptor.node.handle_stfu(node_id_initiator, &stfu_init);
+ let stfu_ack = get_event_msg!(acceptor, MessageSendEvent::SendStfu, node_id_initiator);
+ initiator.node.handle_stfu(node_id_acceptor, &stfu_ack);
+
+ let splice_init = get_event_msg!(initiator, MessageSendEvent::SendSpliceInit, node_id_acceptor);
+ acceptor.node.handle_splice_init(node_id_initiator, &splice_init);
+ let splice_ack = get_event_msg!(acceptor, MessageSendEvent::SendSpliceAck, node_id_initiator);
+ initiator.node.handle_splice_ack(node_id_acceptor, &splice_ack);
+
+ let new_funding_script = chan_utils::make_funding_redeemscript(
+ &splice_init.funding_pubkey,
+ &splice_ack.funding_pubkey,
+ )
+ .to_p2wsh();
+
+ let initial_commit_sig_for_acceptor = complete_interactive_funding_negotiation(
+ initiator,
+ acceptor,
+ channel_id,
+ initiator_contribution,
+ new_funding_script,
);
- if !inputs_seen_in_reverse {
- // Input is the extra input
- let prevtx_value = tx_add_input2_msg.prevtx.as_ref().unwrap().output
- [tx_add_input2_msg.prevtx_out as usize]
- .value
- .to_sat();
- assert_eq!(prevtx_value, extra_splice_funding_input_sats);
- assert_eq!(tx_add_input2_msg.shared_input_txid, None);
- } else {
- // Input is the revious funding input
- assert_eq!(tx_add_input2_msg.prevtx, None);
- assert_eq!(
- tx_add_input2_msg.shared_input_txid.unwrap().to_string(),
- "4f128bedf1a15baf465ab1bfd6e97c8f82628f4156bf86eb1cbc132cda6733ae"
- );
- }
+ sign_interactive_funding_transaction(initiator, acceptor, initial_commit_sig_for_acceptor);
+
+ let splice_tx = {
+ let mut initiator_txn = initiator.tx_broadcaster.txn_broadcast();
+ assert_eq!(initiator_txn.len(), 1);
+ let acceptor_txn = acceptor.tx_broadcaster.txn_broadcast();
+ assert_eq!(initiator_txn, acceptor_txn);
+ initiator_txn.remove(0)
+ };
+ splice_tx
+}
- acceptor_node
- .node
- .handle_tx_add_input(initiator_node.node.get_our_node_id(), &tx_add_input2_msg);
- let tx_complete_msg = get_event_msg!(
- acceptor_node,
- MessageSendEvent::SendTxComplete,
- initiator_node.node.get_our_node_id()
- );
+fn lock_splice_after_blocks<'a, 'b, 'c, 'd>(
+ node_a: &'a Node<'b, 'c, 'd>, node_b: &'a Node<'b, 'c, 'd>, channel_id: ChannelId,
+ num_blocks: u32,
+) {
+ let (prev_funding_outpoint, prev_funding_script) = node_a
+ .chain_monitor
+ .chain_monitor
+ .get_monitor(channel_id)
+ .map(|monitor| (monitor.get_funding_txo(), monitor.get_funding_script()))
+ .unwrap();
- initiator_node.node.handle_tx_complete(acceptor_node.node.get_our_node_id(), &tx_complete_msg);
+ connect_blocks(node_a, num_blocks);
+ connect_blocks(node_b, num_blocks);
- // TxAddOutput for the change output
- let tx_add_output_msg = get_event_msg!(
- &initiator_node,
- MessageSendEvent::SendTxAddOutput,
- acceptor_node.node.get_our_node_id()
- );
- if !expect_outputs_in_reverse {
- assert!(tx_add_output_msg.script.is_p2wsh());
- assert_eq!(tx_add_output_msg.sats, post_splice_channel_value);
+ let node_id_a = node_a.node.get_our_node_id();
+ let node_id_b = node_b.node.get_our_node_id();
+
+ let splice_locked_a = get_event_msg!(node_a, MessageSendEvent::SendSpliceLocked, node_id_b);
+ node_b.node.handle_splice_locked(node_id_a, &splice_locked_a);
+
+ let mut msg_events = node_b.node.get_and_clear_pending_msg_events();
+ assert_eq!(msg_events.len(), 2, "{msg_events:?}");
+ if let MessageSendEvent::SendSpliceLocked { msg, .. } = msg_events.remove(0) {
+ node_a.node.handle_splice_locked(node_id_b, &msg);
+ } else {
+ panic!();
+ }
+ if let MessageSendEvent::SendAnnouncementSignatures { msg, .. } = msg_events.remove(0) {
+ node_a.node.handle_announcement_signatures(node_id_b, &msg);
} else {
- assert!(tx_add_output_msg.script.is_p2wpkh());
- assert_eq!(tx_add_output_msg.sats, 13979); // extra_splice_funding_input_sats - splice_in_sats
+ panic!();
}
- acceptor_node
- .node
- .handle_tx_add_output(initiator_node.node.get_our_node_id(), &tx_add_output_msg);
- let tx_complete_msg = get_event_msg!(
- &acceptor_node,
- MessageSendEvent::SendTxComplete,
- initiator_node.node.get_our_node_id()
- );
+ expect_channel_ready_event(&node_a, &node_id_b);
+ check_added_monitors(&node_a, 1);
+ expect_channel_ready_event(&node_b, &node_id_a);
+ check_added_monitors(&node_b, 1);
- initiator_node.node.handle_tx_complete(acceptor_node.node.get_our_node_id(), &tx_complete_msg);
- // TxAddOutput for the splice funding
- let tx_add_output2_msg = get_event_msg!(
- &initiator_node,
- MessageSendEvent::SendTxAddOutput,
- acceptor_node.node.get_our_node_id()
- );
- if !expect_outputs_in_reverse {
- assert!(tx_add_output2_msg.script.is_p2wpkh());
- assert_eq!(tx_add_output2_msg.sats, 14146); // extra_splice_funding_input_sats - splice_in_sats
+ let mut msg_events = node_a.node.get_and_clear_pending_msg_events();
+ assert_eq!(msg_events.len(), 2, "{msg_events:?}");
+ if let MessageSendEvent::SendAnnouncementSignatures { msg, .. } = msg_events.remove(0) {
+ node_b.node.handle_announcement_signatures(node_id_a, &msg);
} else {
- assert!(tx_add_output2_msg.script.is_p2wsh());
- assert_eq!(tx_add_output2_msg.sats, post_splice_channel_value);
+ panic!();
+ }
+ if let MessageSendEvent::BroadcastChannelAnnouncement { .. } = msg_events.remove(0) {
+ } else {
+ panic!();
}
- acceptor_node
- .node
- .handle_tx_add_output(initiator_node.node.get_our_node_id(), &tx_add_output2_msg);
- let _tx_complete_msg = get_event_msg!(
- acceptor_node,
- MessageSendEvent::SendTxComplete,
- initiator_node.node.get_our_node_id()
- );
-
- // TODO(splicing) This is the last tx_complete, which triggers the commitment flow, which is not yet fully implemented
- initiator_node.node.handle_tx_complete(acceptor_node.node.get_our_node_id(), &tx_complete_msg);
- let events = initiator_node.node.get_and_clear_pending_msg_events();
- assert_eq!(events.len(), 1);
- match events[0] {
- MessageSendEvent::SendTxAbort { .. } => {},
- _ => panic!("Unexpected event {:?}", events[0]),
+ let mut msg_events = node_b.node.get_and_clear_pending_msg_events();
+ assert_eq!(msg_events.len(), 1, "{msg_events:?}");
+ if let MessageSendEvent::BroadcastChannelAnnouncement { .. } = msg_events.remove(0) {
+ } else {
+ panic!();
}
- // TODO(splicing): Continue with commitment flow, new tx confirmation, and shutdown
+ // Remove the corresponding outputs and transactions the chain source is watching.
+ node_a
+ .chain_source
+ .remove_watched_txn_and_outputs(prev_funding_outpoint, prev_funding_script.clone());
+ node_b.chain_source.remove_watched_txn_and_outputs(prev_funding_outpoint, prev_funding_script);
}
#[test]
-fn test_v1_splice_in_negative_insufficient_inputs() {
+fn test_splice_in() {
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
- let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+ 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, 100_000, 0);
+ 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);
+ let initiator_contribution = SpliceContribution::SpliceIn {
+ value: Amount::from_sat(initial_channel_value_sat * 2),
+ inputs: vec![
+ FundingTxInput::new_p2wpkh(coinbase_tx1, 0).unwrap(),
+ FundingTxInput::new_p2wpkh(coinbase_tx2, 0).unwrap(),
+ ],
+ change_script: Some(nodes[0].wallet_source.get_change_script().unwrap()),
+ };
- // Amount being added to the channel through the splice-in
- let splice_in_sats = 20_000;
+ let splice_tx = splice_channel(&nodes[0], &nodes[1], channel_id, initiator_contribution);
+ mine_transaction(&nodes[0], &splice_tx);
+ mine_transaction(&nodes[1], &splice_tx);
- // Create additional inputs, but insufficient
- let extra_splice_funding_input_sats = splice_in_sats - 1;
- let funding_inputs =
- create_dual_funding_utxos_with_prev_txs(&nodes[0], &[extra_splice_funding_input_sats]);
+ let htlc_limit_msat = nodes[0].node.list_channels()[0].next_outbound_htlc_limit_msat;
+ assert!(htlc_limit_msat < initial_channel_value_sat * 1000);
+ let _ = send_payment(&nodes[0], &[&nodes[1]], htlc_limit_msat);
- let contribution = SpliceContribution::SpliceIn {
- value: Amount::from_sat(splice_in_sats),
- inputs: funding_inputs,
- change_script: None,
+ lock_splice_after_blocks(&nodes[0], &nodes[1], channel_id, 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);
+ let _ = send_payment(&nodes[0], &[&nodes[1]], htlc_limit_msat);
+}
+
+#[test]
+fn test_splice_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 initiator_contribution = SpliceContribution::SpliceOut {
+ outputs: vec![
+ TxOut {
+ value: Amount::from_sat(initial_channel_value_sat / 4),
+ script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
+ },
+ TxOut {
+ value: Amount::from_sat(initial_channel_value_sat / 4),
+ script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
+ },
+ ],
};
- // Initiate splice-in, with insufficient input contribution
- let res = nodes[0].node.splice_channel(
- &channel_id,
- &nodes[1].node.get_our_node_id(),
- contribution,
- 1024, // funding_feerate_per_kw,
- None, // locktime
- );
- match res {
- Err(APIError::APIMisuseError { err }) => {
- assert!(err.contains("Need more inputs"))
- },
- _ => panic!("Wrong error {:?}", res.err().unwrap()),
- }
+ let splice_tx = splice_channel(&nodes[0], &nodes[1], channel_id, initiator_contribution);
+ 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 < initial_channel_value_sat / 2 * 1000);
+ let _ = send_payment(&nodes[0], &[&nodes[1]], htlc_limit_msat);
+
+ lock_splice_after_blocks(&nodes[0], &nodes[1], channel_id, 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);
}
Why this scored 15/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.